fix(dom): retarget adopted shadow trees before reinsertion

Retarget ShadowRoot ownership and descendants along with the adopted host,
and collect stylesheet owners from nested shadow trees. Cover closed and
nested roots, inactive documents, and deep iterative adoption.
This commit is contained in:
ldm0
2026-10-17 20:09:30 +08:00
committed by Donough Liu
parent db2c33810b
commit e0df01a2cd
2 changed files with 59 additions and 2 deletions
+58 -1
View File
@@ -170,7 +170,7 @@ impl DomHost {
.is_some_and(Node::is_document)
.then_some(())?;
self.node(handle).filter(|node| !node.is_document())?;
let owners = self
let mut owners = self
.dom
.mark_subtree_tree_scope_collecting_stylesheet_owners(
handle,
@@ -178,6 +178,7 @@ impl DomHost {
false,
false,
);
self.mark_shadow_tree_scope_for_subtree(handle, Some(document_handle), false, &mut owners);
let owner_changes = owners
.into_iter()
.map(|owner| {
@@ -783,6 +784,54 @@ mod tests {
);
}
#[test]
fn adoption_retargets_shadow_roots_and_descendants_before_reinsertion() {
let mut host = test_host();
let source_document = host.document_handle();
let target_document = host.create_detached_html_document();
assert!(!host.is_connected(target_document));
let root = host.create_element("section");
let shadow = host
.attach_shadow_root(root, "closed")
.expect("closed root");
let nested_host = host.create_element("div");
assert!(host.append_child(shadow, nested_host));
let nested_shadow = host
.attach_shadow_root(nested_host, "open")
.expect("nested root");
let media = host.create_element("video");
assert!(host.append_child(nested_shadow, media));
let text = host.create_text_node("fallback");
assert!(host.append_child(media, text));
assert!(host.append_child(source_document, root));
assert!(host.remove_child(source_document, root));
assert_eq!(host.adopt_node(target_document, root), Some(root));
for node in [root, shadow, nested_host, nested_shadow, media, text] {
assert_eq!(
host.node(node).and_then(Node::owner_document),
Some(target_document)
);
assert!(
!host.is_connected(node),
"adoption must not reconnect a removed subtree"
);
}
assert_eq!(host.shadow_root_handle(root), Some(shadow));
assert_eq!(host.shadow_root_mode(shadow).as_deref(), Some("closed"));
assert_eq!(host.shadow_root_handle(nested_host), Some(nested_shadow));
assert!(host.append_child(target_document, root));
assert_eq!(host.parent_node(root), Some(target_document));
assert!(
!host.is_connected(media),
"reinsertion must retain the inactive native Document's connection state"
);
assert_eq!(
host.node(media).and_then(Node::owner_document),
Some(target_document)
);
}
#[test]
fn deep_shadow_tree_scope_retarget_walks_iteratively() {
let mut host = test_host();
@@ -804,5 +853,13 @@ mod tests {
Some(document)
);
assert!(host.is_connected(shadow_host));
let target_document = host.create_detached_html_document();
assert!(host.remove_child(document, root));
assert_eq!(host.adopt_node(target_document, root), Some(root));
assert_eq!(
host.node(shadow_host).and_then(Node::owner_document),
Some(target_document)
);
assert!(!host.is_connected(shadow_host));
}
}
+1 -1
View File
@@ -1960,7 +1960,7 @@ impl DomHost {
true
}
fn mark_shadow_tree_scope_for_subtree(
pub(super) fn mark_shadow_tree_scope_for_subtree(
&mut self,
root: DomHandle,
owner_document: Option<DomHandle>,