Add shadow tree flags to Bind/UnbindContext (#34863)

* Rename IS_IN_DOC flag to IS_IN_A_DOCUMENT_TREE

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

* Add BindContext::is_in_a_shadow_tree

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

* Add UnbindContext::tree_is_in_shadow_tree

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

* ./mach fmt

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

* Update test expectations

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

* fix build after rebasing

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-07 01:00:43 +01:00 committed by GitHub
parent 82bc7cb5bb
commit b6a5eaa3db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 77 additions and 43 deletions

View file

@ -303,7 +303,10 @@ impl DocumentOrShadowRoot {
root: DomRoot<Node>,
) {
debug!("Adding named element {:p}: {:p} id={}", self, element, id);
assert!(element.upcast::<Node>().is_connected_to_tree());
assert!(
element.upcast::<Node>().is_in_a_document_tree() ||
element.upcast::<Node>().is_in_a_shadow_tree()
);
assert!(!id.is_empty());
let mut id_map = id_map.borrow_mut();
let elements = id_map.entry(id.clone()).or_default();

View file

@ -544,7 +544,8 @@ impl Element {
let bind_context = BindContext {
tree_connected: self.upcast::<Node>().is_connected(),
tree_in_doc: self.upcast::<Node>().is_in_doc(),
tree_is_in_a_document_tree: self.upcast::<Node>().is_in_a_document_tree(),
tree_is_in_a_shadow_tree: true,
};
shadow_root.bind_to_tree(&bind_context);
@ -1355,7 +1356,7 @@ impl Element {
}
pub fn root_element(&self) -> DomRoot<Element> {
if self.node.is_in_doc() {
if self.node.is_in_a_document_tree() {
self.upcast::<Node>()
.owner_doc()
.GetDocumentElement()
@ -3512,7 +3513,7 @@ impl VirtualMethods for Element {
});
let containing_shadow_root = self.containing_shadow_root();
if node.is_connected_to_tree() {
if node.is_in_a_document_tree() || node.is_in_a_shadow_tree() {
let value = attr.value().as_atom().clone();
match mutation {
AttributeMutation::Set(old_value) => {
@ -3619,7 +3620,7 @@ impl VirtualMethods for Element {
shadow_root.bind_to_tree(context);
}
if !context.tree_connected {
if !context.is_in_tree() {
return;
}
@ -3652,7 +3653,7 @@ impl VirtualMethods for Element {
f.unbind_form_control_from_tree();
}
if !context.tree_connected {
if !context.tree_is_in_a_document_tree && !context.tree_is_in_a_shadow_tree {
return;
}
@ -3668,7 +3669,7 @@ impl VirtualMethods for Element {
if let Some(ref shadow_root) = self.containing_shadow_root() {
// Only unregister the element id if the node was disconnected from it's shadow root
// (as opposed to the whole shadow tree being disconnected as a whole)
if !self.upcast::<Node>().is_in_shadow_tree() {
if !self.upcast::<Node>().is_in_a_shadow_tree() {
shadow_root.unregister_element_id(self, value.clone());
}
} else {

View file

@ -126,11 +126,11 @@ impl VirtualMethods for HTMLBaseElement {
fn bind_to_tree(&self, context: &BindContext) {
self.super_type().unwrap().bind_to_tree(context);
self.bind_unbind(context.tree_in_doc);
self.bind_unbind(context.tree_is_in_a_document_tree);
}
fn unbind_from_tree(&self, context: &UnbindContext) {
self.super_type().unwrap().unbind_from_tree(context);
self.bind_unbind(context.tree_in_doc);
self.bind_unbind(context.tree_is_in_a_document_tree);
}
}

View file

@ -146,7 +146,7 @@ impl VirtualMethods for HTMLBodyElement {
s.bind_to_tree(context);
}
if !context.tree_in_doc {
if !context.tree_is_in_a_document_tree {
return;
}

View file

@ -949,7 +949,7 @@ impl HTMLScriptElement {
match script.type_ {
ScriptType::Classic => {
if self.upcast::<Node>().is_in_shadow_tree() {
if self.upcast::<Node>().is_in_a_shadow_tree() {
document.set_current_script(None)
} else {
document.set_current_script(Some(self))

View file

@ -194,7 +194,7 @@ impl VirtualMethods for HTMLStyleElement {
// "The element is not on the stack of open elements of an HTML parser or XML parser,
// and one of its child nodes is modified by a script."
// TODO: Handle Text child contents being mutated.
if self.upcast::<Node>().is_in_doc() && !self.in_stack_of_open_elements.get() {
if self.upcast::<Node>().is_in_a_document_tree() && !self.in_stack_of_open_elements.get() {
self.parse_own_css();
}
}
@ -218,7 +218,7 @@ impl VirtualMethods for HTMLStyleElement {
// Handles the case when:
// "The element is popped off the stack of open elements of an HTML parser or XML parser."
self.in_stack_of_open_elements.set(false);
if self.upcast::<Node>().is_in_doc() {
if self.upcast::<Node>().is_in_a_document_tree() {
self.parse_own_css();
}
}

View file

@ -57,7 +57,7 @@ impl HTMLTitleElement {
fn notify_title_changed(&self) {
let node = self.upcast::<Node>();
if node.is_in_doc() {
if node.is_in_a_document_tree() {
node.owner_doc().title_changed();
}
}
@ -97,7 +97,7 @@ impl VirtualMethods for HTMLTitleElement {
s.bind_to_tree(context);
}
let node = self.upcast::<Node>();
if context.tree_in_doc {
if context.tree_is_in_a_document_tree {
node.owner_doc().title_changed();
}
}

View file

@ -193,7 +193,9 @@ pub struct NodeFlags(u16);
bitflags! {
impl NodeFlags: u16 {
/// Specifies whether this node is in a document.
const IS_IN_DOC = 1 << 0;
///
/// <https://dom.spec.whatwg.org/#in-a-document-tree>
const IS_IN_A_DOCUMENT_TREE = 1 << 0;
/// Specifies whether this node needs style recalc on next reflow.
const HAS_DIRTY_DESCENDANTS = 1 << 1;
@ -225,6 +227,8 @@ bitflags! {
const IS_IN_SHADOW_TREE = 1 << 9;
/// Specifies whether this node's shadow-including root is a document.
///
/// <https://dom.spec.whatwg.org/#connected>
const IS_CONNECTED = 1 << 10;
/// Whether this node has a weird parser insertion mode. i.e whether setting innerHTML
@ -285,8 +289,8 @@ impl Node {
new_child.parent_node.set(Some(self));
self.children_count.set(self.children_count.get() + 1);
let parent_in_doc = self.is_in_doc();
let parent_in_shadow_tree = self.is_in_shadow_tree();
let parent_is_in_a_document_tree = self.is_in_a_document_tree();
let parent_in_shadow_tree = self.is_in_a_shadow_tree();
let parent_is_connected = self.is_connected();
for node in new_child.traverse_preorder(ShadowIncluding::No) {
@ -296,14 +300,19 @@ impl Node {
}
debug_assert!(node.containing_shadow_root().is_some());
}
node.set_flag(NodeFlags::IS_IN_DOC, parent_in_doc);
node.set_flag(
NodeFlags::IS_IN_A_DOCUMENT_TREE,
parent_is_in_a_document_tree,
);
node.set_flag(NodeFlags::IS_IN_SHADOW_TREE, parent_in_shadow_tree);
node.set_flag(NodeFlags::IS_CONNECTED, parent_is_connected);
// Out-of-document elements never have the descendants flag set.
debug_assert!(!node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS));
vtable_for(&node).bind_to_tree(&BindContext {
tree_connected: parent_is_connected,
tree_in_doc: parent_in_doc,
tree_is_in_a_document_tree: parent_is_in_a_document_tree,
tree_is_in_a_shadow_tree: parent_in_shadow_tree,
});
}
}
@ -317,7 +326,7 @@ impl Node {
/// Clean up flags and unbind from tree.
pub fn complete_remove_subtree(root: &Node, context: &UnbindContext) {
// Flags that reset when a node is disconnected
const RESET_FLAGS: NodeFlags = NodeFlags::IS_IN_DOC
const RESET_FLAGS: NodeFlags = NodeFlags::IS_IN_A_DOCUMENT_TREE
.union(NodeFlags::IS_CONNECTED)
.union(NodeFlags::HAS_DIRTY_DESCENDANTS)
.union(NodeFlags::HAS_SNAPSHOT)
@ -597,11 +606,12 @@ impl Node {
format!("{:?}", self.type_id())
}
pub fn is_in_doc(&self) -> bool {
self.flags.get().contains(NodeFlags::IS_IN_DOC)
/// <https://dom.spec.whatwg.org/#in-a-document-tree>
pub fn is_in_a_document_tree(&self) -> bool {
self.flags.get().contains(NodeFlags::IS_IN_A_DOCUMENT_TREE)
}
pub fn is_in_shadow_tree(&self) -> bool {
pub fn is_in_a_shadow_tree(&self) -> bool {
self.flags.get().contains(NodeFlags::IS_IN_SHADOW_TREE)
}
@ -615,15 +625,11 @@ impl Node {
self.set_flag(NodeFlags::HAS_WEIRD_PARSER_INSERTION_MODE, true)
}
/// <https://dom.spec.whatwg.org/#connected>
pub fn is_connected(&self) -> bool {
self.flags.get().contains(NodeFlags::IS_CONNECTED)
}
/// Return true iff the node's root is a Document or a ShadowRoot
pub fn is_connected_to_tree(&self) -> bool {
self.is_connected() || self.is_in_shadow_tree()
}
/// Returns the type ID of this node.
pub fn type_id(&self) -> NodeTypeId {
match *self.eventtarget.type_id() {
@ -1842,7 +1848,10 @@ impl Node {
#[allow(crown::unrooted_must_root)]
pub fn new_document_node() -> Node {
Node::new_(NodeFlags::IS_IN_DOC | NodeFlags::IS_CONNECTED, None)
Node::new_(
NodeFlags::IS_IN_A_DOCUMENT_TREE | NodeFlags::IS_CONNECTED,
None,
)
}
#[allow(crown::unrooted_must_root)]
@ -2679,7 +2688,7 @@ impl NodeMethods<crate::DomTypeHolder> for Node {
};
}
if self.is_in_doc() {
if self.is_in_a_document_tree() {
DomRoot::from_ref(self.owner_doc().upcast::<Node>())
} else {
self.inclusive_ancestors(ShadowIncluding::No)
@ -3558,9 +3567,24 @@ impl<'a> ChildrenMutation<'a> {
/// The context of the binding to tree of a node.
pub struct BindContext {
/// Whether the tree is connected.
///
/// <https://dom.spec.whatwg.org/#connected>
pub tree_connected: bool,
/// Whether the tree is in the document.
pub tree_in_doc: bool,
/// Whether the tree's root is a document.
///
/// <https://dom.spec.whatwg.org/#in-a-document-tree>
pub tree_is_in_a_document_tree: bool,
/// Whether the tree's root is a shadow root
pub tree_is_in_a_shadow_tree: bool,
}
impl BindContext {
/// Return true iff the tree is inside either a document- or a shadow tree.
pub fn is_in_tree(&self) -> bool {
self.tree_is_in_a_document_tree || self.tree_is_in_a_shadow_tree
}
}
/// The context of the unbinding from a tree of a node when one of its
@ -3574,12 +3598,19 @@ pub struct UnbindContext<'a> {
prev_sibling: Option<&'a Node>,
/// The next sibling of the inclusive ancestor that was removed.
pub next_sibling: Option<&'a Node>,
/// Whether the tree is connected.
///
/// A tree is connected iff it's root is a Document or a ShadowRoot.
/// <https://dom.spec.whatwg.org/#connected>
pub tree_connected: bool,
/// Whether the tree is in doc.
pub tree_in_doc: bool,
/// Whether the tree's root is a document.
///
/// <https://dom.spec.whatwg.org/#in-a-document-tree>
pub tree_is_in_a_document_tree: bool,
/// Whether the tree's root is a shadow root
pub tree_is_in_a_shadow_tree: bool,
}
impl<'a> UnbindContext<'a> {
@ -3595,8 +3626,9 @@ impl<'a> UnbindContext<'a> {
parent,
prev_sibling,
next_sibling,
tree_connected: parent.is_connected_to_tree(),
tree_in_doc: parent.is_in_doc(),
tree_connected: parent.is_connected(),
tree_is_in_a_document_tree: parent.is_in_a_document_tree(),
tree_is_in_a_shadow_tree: parent.is_in_a_shadow_tree(),
}
}

View file

@ -319,6 +319,7 @@ impl VirtualMethods for ShadowRoot {
}
let shadow_root = self.upcast::<Node>();
shadow_root.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected);
for node in shadow_root.children() {
node.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected);

View file

@ -168,7 +168,7 @@ impl<'dom> style::dom::TNode for ServoLayoutNode<'dom> {
}
fn is_in_document(&self) -> bool {
unsafe { self.node.get_flag(NodeFlags::IS_IN_DOC) }
unsafe { self.node.get_flag(NodeFlags::IS_IN_A_DOCUMENT_TREE) }
}
}

View file

@ -1,6 +1,3 @@
[form-control-form-attribute.html]
[Shadow form control's form attribute should work also in shadow DOM.]
expected: FAIL
[Form element as form control's ancestor should work also in shadow DOM.]
expected: FAIL