Set descendant's attribute's owner document in Node::adopt (#35076)

* Set descendant's attribute's owner document in Node::adopt

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Add test that adopting an element into a new doc updates the attribute' owner docs

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

---------

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
Simon Wülker 2025-01-20 00:06:28 +01:00 committed by GitHub
parent dabe162d44
commit c070372d1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 38 additions and 7 deletions

View file

@ -160,6 +160,7 @@ use crate::task::TaskOnce;
// and when the element enters or leaves a browsing context container.
// https://html.spec.whatwg.org/multipage/#selector-focus
/// <https://dom.spec.whatwg.org/#element>
#[dom_struct]
pub(crate) struct Element {
node: Node,

View file

@ -1886,30 +1886,46 @@ impl Node {
pub(crate) fn adopt(node: &Node, document: &Document) {
document.add_script_and_layout_blocker();
// Step 1.
// Step 1. Let oldDocument be nodes node document.
let old_doc = node.owner_doc();
old_doc.add_script_and_layout_blocker();
// Step 2.
// Step 2. If nodes parent is non-null, then remove node.
node.remove_self();
// Step 3.
// Step 3. If document is not oldDocument:
if &*old_doc != document {
// Step 3.1.
// Step 3.1. For each inclusiveDescendant in nodes shadow-including inclusive descendants:
for descendant in node.traverse_preorder(ShadowIncluding::Yes) {
// Step 3.1.1 Set inclusiveDescendants node document to document.
descendant.set_owner_doc(document);
// Step 3.1.2 If inclusiveDescendant is an element, then set the node document of each
// attribute in inclusiveDescendants attribute list to document.
if let Some(element) = descendant.downcast::<Element>() {
for attribute in element.attrs().iter() {
attribute.upcast::<Node>().set_owner_doc(document);
}
}
}
// Step 3.2 For each inclusiveDescendant in nodes shadow-including inclusive descendants
// that is custom, enqueue a custom element callback reaction with inclusiveDescendant,
// callback name "adoptedCallback", and « oldDocument, document ».
for descendant in node
.traverse_preorder(ShadowIncluding::Yes)
.filter_map(|d| d.as_custom_element())
{
// Step 3.2.
ScriptThread::enqueue_callback_reaction(
&descendant,
CallbackReaction::Adopted(old_doc.clone(), DomRoot::from_ref(document)),
None,
);
}
// Step 3.3 For each inclusiveDescendant in nodes shadow-including inclusive descendants,
// in shadow-including tree order, run the adopting steps with inclusiveDescendant and oldDocument.
for descendant in node.traverse_preorder(ShadowIncluding::Yes) {
// Step 3.3.
vtable_for(&descendant).adopting_steps(&old_doc);
}
}

View file

@ -606523,7 +606523,7 @@
]
],
"Node-mutation-adoptNode.html": [
"9c9594c07b22a4ac94b3703f8ab1711b41a759e4",
"812a2e4d3af326c1e73115c6fc50726bc4fc4b23",
[
null,
{}

View file

@ -20,4 +20,18 @@ test(() => {
assert_equals(div.firstChild.ownerDocument, document);
}, "simple append of foreign div with text");
test(function() {
var div = document.createElement("div");
div.id = "foobar";
assert_equals(div.ownerDocument, document);
assert_equals(div.attributes[0].ownerDocument, document);
var other_doc = document.implementation.createHTMLDocument();
other_doc.body.appendChild(div);
assert_equals(div.ownerDocument, other_doc);
assert_equals(div.attributes[0].ownerDocument, other_doc);
}, "Adopting an element into a different document update's the element's owner doc as well as the owner docs of it's attributes")
</script>