Implement ServoLayoutNode::traversal_parent (#35338)

This fixes common crash related to slottables, currently present on wpt.fyi.

Previously, the traversal parent of `Text` nodes was incorrectly
assumed to always be the parent or shadow host. That caused crashes
inside stylo's bloom filter. Now the traversal parent is the slot
that the node is assigned to, if any, and the parent/shadow host otherwise.

The slottable data for Text/Element nodes is now stored in NodeRareData.
This is very cheap, because NodeRareData will already be instantiated
for assigned slottables anyways, because the containing_shadow_root
field will be set (since assigned slottables are always in a shadow
tree). This change is necessary because we need to hand out references
to the assigned slot to stylo and that is not possible to do (without
unsafe code) if we need to downcast the node first.

As a side effect, this reduces the size of `Text` from 256 to 232 bytes,
because the slottable data is no longer stored there.

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
Simon Wülker 2025-02-07 02:05:27 +01:00 committed by GitHub
parent 9faa7be302
commit 5a5d796988
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 77 additions and 167 deletions

View file

@ -613,43 +613,6 @@ impl Element {
Some(node) => node.is::<Document>(), Some(node) => node.is::<Document>(),
} }
} }
pub(crate) fn assigned_slot(&self) -> Option<DomRoot<HTMLSlotElement>> {
let assigned_slot = self
.rare_data
.borrow()
.as_ref()?
.slottable_data
.assigned_slot
.as_ref()?
.as_rooted();
Some(assigned_slot)
}
pub(crate) fn set_assigned_slot(&self, assigned_slot: Option<&HTMLSlotElement>) {
self.ensure_rare_data().slottable_data.assigned_slot = assigned_slot.map(Dom::from_ref);
}
pub(crate) fn manual_slot_assignment(&self) -> Option<DomRoot<HTMLSlotElement>> {
let manually_assigned_slot = self
.rare_data
.borrow()
.as_ref()?
.slottable_data
.manual_slot_assignment
.as_ref()?
.as_rooted();
Some(manually_assigned_slot)
}
pub(crate) fn set_manual_slot_assignment(
&self,
manually_assigned_slot: Option<&HTMLSlotElement>,
) {
self.ensure_rare_data()
.slottable_data
.manual_slot_assignment = manually_assigned_slot.map(Dom::from_ref);
}
} }
/// <https://dom.spec.whatwg.org/#valid-shadow-host-name> /// <https://dom.spec.whatwg.org/#valid-shadow-host-name>
@ -727,7 +690,6 @@ pub(crate) trait LayoutElementHelpers<'dom> {
) -> Option<&'dom AttrValue>; ) -> Option<&'dom AttrValue>;
fn get_attr_val_for_layout(self, namespace: &Namespace, name: &LocalName) -> Option<&'dom str>; fn get_attr_val_for_layout(self, namespace: &Namespace, name: &LocalName) -> Option<&'dom str>;
fn get_attr_vals_for_layout(self, name: &LocalName) -> Vec<&'dom AttrValue>; fn get_attr_vals_for_layout(self, name: &LocalName) -> Vec<&'dom AttrValue>;
fn get_assigned_slot(&self) -> Option<LayoutDom<'dom, HTMLSlotElement>>;
} }
impl LayoutDom<'_, Element> { impl LayoutDom<'_, Element> {
@ -1261,20 +1223,6 @@ impl<'dom> LayoutElementHelpers<'dom> for LayoutDom<'dom, Element> {
}) })
.collect() .collect()
} }
#[allow(unsafe_code)]
fn get_assigned_slot(&self) -> Option<LayoutDom<'dom, HTMLSlotElement>> {
unsafe {
self.unsafe_get()
.rare_data
.borrow_for_layout()
.as_ref()?
.slottable_data
.assigned_slot
.as_ref()
.map(|slot| slot.to_layout())
}
}
} }
impl Element { impl Element {

View file

@ -41,7 +41,6 @@ use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods; use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::InheritTypes::{CharacterDataTypeId, NodeTypeId};
use crate::dom::bindings::codegen::UnionTypes::{ use crate::dom::bindings::codegen::UnionTypes::{
AddEventListenerOptionsOrBoolean, EventListenerOptionsOrBoolean, EventOrString, AddEventListenerOptionsOrBoolean, EventListenerOptionsOrBoolean, EventOrString,
}; };
@ -61,7 +60,6 @@ use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlformelement::FormControlElementHelpers; use crate::dom::htmlformelement::FormControlElementHelpers;
use crate::dom::node::{Node, NodeTraits}; use crate::dom::node::{Node, NodeTraits};
use crate::dom::shadowroot::ShadowRoot; use crate::dom::shadowroot::ShadowRoot;
use crate::dom::text::Text;
use crate::dom::virtualmethods::VirtualMethods; use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::window::Window; use crate::dom::window::Window;
use crate::dom::workerglobalscope::WorkerGlobalScope; use crate::dom::workerglobalscope::WorkerGlobalScope;
@ -821,25 +819,7 @@ impl EventTarget {
if let Some(node) = self.downcast::<Node>() { if let Some(node) = self.downcast::<Node>() {
// > A nodes get the parent algorithm, given an event, returns the nodes assigned slot, // > A nodes get the parent algorithm, given an event, returns the nodes assigned slot,
// > if node is assigned; otherwise nodes parent. // > if node is assigned; otherwise nodes parent.
let assigned_slot = match node.type_id() { return node.assigned_slot().map(DomRoot::upcast).or_else(|| {
NodeTypeId::Element(_) => {
let element = node.downcast::<Element>().unwrap();
element
.assigned_slot()
.map(|slot| DomRoot::from_ref(slot.upcast::<EventTarget>()))
},
NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => {
let text = node.downcast::<Text>().unwrap();
text.slottable_data()
.borrow()
.assigned_slot
.as_ref()
.map(|slot| DomRoot::from_ref(slot.upcast::<EventTarget>()))
},
_ => None,
};
return assigned_slot.or_else(|| {
node.GetParentNode() node.GetParentNode()
.map(|parent| DomRoot::from_ref(parent.upcast::<EventTarget>())) .map(|parent| DomRoot::from_ref(parent.upcast::<EventTarget>()))
}); });

View file

@ -29,7 +29,6 @@ use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlelement::HTMLElement;
use crate::dom::mutationobserver::MutationObserver; use crate::dom::mutationobserver::MutationObserver;
use crate::dom::node::{Node, NodeDamage, ShadowIncluding}; use crate::dom::node::{Node, NodeDamage, ShadowIncluding};
use crate::dom::text::Text;
use crate::dom::virtualmethods::VirtualMethods; use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc; use crate::script_runtime::CanGc;
use crate::ScriptThread; use crate::ScriptThread;
@ -437,53 +436,23 @@ impl Slottable {
} }
pub(crate) fn assigned_slot(&self) -> Option<DomRoot<HTMLSlotElement>> { pub(crate) fn assigned_slot(&self) -> Option<DomRoot<HTMLSlotElement>> {
self.match_slottable( self.node().assigned_slot()
|element: &Element| element.assigned_slot(),
|text: &Text| {
let assigned_slot = text
.slottable_data()
.borrow()
.assigned_slot
.as_ref()?
.as_rooted();
Some(assigned_slot)
},
)
} }
pub(crate) fn set_assigned_slot(&self, assigned_slot: Option<&HTMLSlotElement>) { pub(crate) fn set_assigned_slot(&self, assigned_slot: Option<&HTMLSlotElement>) {
self.match_slottable( self.node().set_assigned_slot(assigned_slot);
|element: &Element| element.set_assigned_slot(assigned_slot),
|text: &Text| {
text.slottable_data().borrow_mut().assigned_slot = assigned_slot.map(Dom::from_ref);
},
)
} }
pub(crate) fn set_manual_slot_assignment( pub(crate) fn set_manual_slot_assignment(
&self, &self,
manually_assigned_slot: Option<&HTMLSlotElement>, manually_assigned_slot: Option<&HTMLSlotElement>,
) { ) {
self.match_slottable( self.node()
|element: &Element| element.set_manual_slot_assignment(manually_assigned_slot), .set_manual_slot_assignment(manually_assigned_slot);
|text: &Text| {
text.slottable_data().borrow_mut().manual_slot_assignment =
manually_assigned_slot.map(Dom::from_ref)
},
)
} }
pub(crate) fn manual_slot_assignment(&self) -> Option<DomRoot<HTMLSlotElement>> { pub(crate) fn manual_slot_assignment(&self) -> Option<DomRoot<HTMLSlotElement>> {
self.match_slottable( self.node().manual_slot_assignment()
|element: &Element| element.manual_slot_assignment(),
|text: &Text| {
text.slottable_data()
.borrow()
.manual_slot_assignment
.as_ref()
.map(Dom::as_rooted)
},
)
} }
fn name(&self) -> DOMString { fn name(&self) -> DOMString {
@ -494,26 +463,6 @@ impl Slottable {
element.get_string_attribute(&local_name!("slot")) element.get_string_attribute(&local_name!("slot"))
} }
/// Call the `element_function` if the slottable is an Element, otherwise the
/// `text_function`
pub(crate) fn match_slottable<E, T, R>(&self, element_function: E, text_function: T) -> R
where
E: FnOnce(&Element) -> R,
T: FnOnce(&Text) -> R,
{
match self.0.type_id() {
NodeTypeId::Element(_) => {
let element: &Element = self.0.downcast::<Element>().unwrap();
element_function(element)
},
NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => {
let text: &Text = self.0.downcast::<Text>().unwrap();
text_function(text)
},
_ => unreachable!(),
}
}
} }
impl VirtualMethods for HTMLSlotElement { impl VirtualMethods for HTMLSlotElement {

View file

@ -1361,6 +1361,43 @@ impl Node {
} }
} }
} }
pub(crate) fn assigned_slot(&self) -> Option<DomRoot<HTMLSlotElement>> {
let assigned_slot = self
.rare_data
.borrow()
.as_ref()?
.slottable_data
.assigned_slot
.as_ref()?
.as_rooted();
Some(assigned_slot)
}
pub(crate) fn set_assigned_slot(&self, assigned_slot: Option<&HTMLSlotElement>) {
self.ensure_rare_data().slottable_data.assigned_slot = assigned_slot.map(Dom::from_ref);
}
pub(crate) fn manual_slot_assignment(&self) -> Option<DomRoot<HTMLSlotElement>> {
let manually_assigned_slot = self
.rare_data
.borrow()
.as_ref()?
.slottable_data
.manual_slot_assignment
.as_ref()?
.as_rooted();
Some(manually_assigned_slot)
}
pub(crate) fn set_manual_slot_assignment(
&self,
manually_assigned_slot: Option<&HTMLSlotElement>,
) {
self.ensure_rare_data()
.slottable_data
.manual_slot_assignment = manually_assigned_slot.map(Dom::from_ref);
}
} }
/// Iterate through `nodes` until we find a `Node` that is not in `not_in` /// Iterate through `nodes` until we find a `Node` that is not in `not_in`
@ -1395,6 +1432,7 @@ pub(crate) trait LayoutNodeHelpers<'dom> {
fn owner_doc_for_layout(self) -> LayoutDom<'dom, Document>; fn owner_doc_for_layout(self) -> LayoutDom<'dom, Document>;
fn containing_shadow_root_for_layout(self) -> Option<LayoutDom<'dom, ShadowRoot>>; fn containing_shadow_root_for_layout(self) -> Option<LayoutDom<'dom, ShadowRoot>>;
fn assigned_slot_for_layout(self) -> Option<LayoutDom<'dom, HTMLSlotElement>>;
fn is_element_for_layout(&self) -> bool; fn is_element_for_layout(&self) -> bool;
unsafe fn get_flag(self, flag: NodeFlags) -> bool; unsafe fn get_flag(self, flag: NodeFlags) -> bool;
@ -1517,6 +1555,21 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
} }
} }
#[inline]
#[allow(unsafe_code)]
fn assigned_slot_for_layout(self) -> Option<LayoutDom<'dom, HTMLSlotElement>> {
unsafe {
self.unsafe_get()
.rare_data
.borrow_for_layout()
.as_ref()?
.slottable_data
.assigned_slot
.as_ref()
.map(|assigned_slot| assigned_slot.to_layout())
}
}
// FIXME(nox): get_flag/set_flag (especially the latter) are not safe because // FIXME(nox): get_flag/set_flag (especially the latter) are not safe because
// they mutate stuff while values of this type can be used from multiple // they mutate stuff while values of this type can be used from multiple
// threads at once, this should be revisited. // threads at once, this should be revisited.
@ -2379,19 +2432,7 @@ impl Node {
parent.remove_child(node, cached_index); parent.remove_child(node, cached_index);
// Step 12. If node is assigned, then run assign slottables for nodes assigned slot. // Step 12. If node is assigned, then run assign slottables for nodes assigned slot.
let assigned_slot = node if let Some(slot) = node.assigned_slot() {
.downcast::<Element>()
.and_then(|e| e.assigned_slot())
.or_else(|| {
node.downcast::<Text>().and_then(|text| {
text.slottable_data()
.borrow()
.assigned_slot
.as_ref()
.map(Dom::as_rooted)
})
});
if let Some(slot) = assigned_slot {
slot.assign_slottables(); slot.assign_slottables();
} }

View file

@ -32,6 +32,8 @@ pub(crate) struct NodeRareData {
pub(crate) mutation_observers: Vec<RegisteredObserver>, pub(crate) mutation_observers: Vec<RegisteredObserver>,
/// Lazily-generated Unique Id for this node. /// Lazily-generated Unique Id for this node.
pub(crate) unique_id: Option<UniqueId>, pub(crate) unique_id: Option<UniqueId>,
pub(crate) slottable_data: SlottableData,
} }
#[derive(Default, JSTraceable, MallocSizeOf)] #[derive(Default, JSTraceable, MallocSizeOf)]
@ -39,8 +41,6 @@ pub(crate) struct NodeRareData {
pub(crate) struct ElementRareData { pub(crate) struct ElementRareData {
/// <https://dom.spec.whatwg.org/#dom-element-shadowroot> /// <https://dom.spec.whatwg.org/#dom-element-shadowroot>
/// The ShadowRoot this element is host of. /// The ShadowRoot this element is host of.
/// XXX This is currently not exposed to web content. Only for
/// internal use.
pub(crate) shadow_root: Option<Dom<ShadowRoot>>, pub(crate) shadow_root: Option<Dom<ShadowRoot>>,
/// <https://html.spec.whatwg.org/multipage/#custom-element-reaction-queue> /// <https://html.spec.whatwg.org/multipage/#custom-element-reaction-queue>
pub(crate) custom_element_reaction_queue: Vec<CustomElementReaction>, pub(crate) custom_element_reaction_queue: Vec<CustomElementReaction>,
@ -58,6 +58,4 @@ pub(crate) struct ElementRareData {
pub(crate) client_rect: Option<LayoutValue<Rect<i32>>>, pub(crate) client_rect: Option<LayoutValue<Rect<i32>>>,
/// <https://html.spec.whatwg.org/multipage#elementinternals> /// <https://html.spec.whatwg.org/multipage#elementinternals>
pub(crate) element_internals: Option<Dom<ElementInternals>>, pub(crate) element_internals: Option<Dom<ElementInternals>>,
pub(crate) slottable_data: SlottableData,
} }

View file

@ -2,8 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::RefCell;
use dom_struct::dom_struct; use dom_struct::dom_struct;
use js::rust::HandleObject; use js::rust::HandleObject;
@ -19,7 +17,7 @@ use crate::dom::bindings::str::DOMString;
use crate::dom::characterdata::CharacterData; use crate::dom::characterdata::CharacterData;
use crate::dom::document::Document; use crate::dom::document::Document;
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlslotelement::{HTMLSlotElement, Slottable, SlottableData}; use crate::dom::htmlslotelement::{HTMLSlotElement, Slottable};
use crate::dom::node::Node; use crate::dom::node::Node;
use crate::dom::window::Window; use crate::dom::window::Window;
use crate::script_runtime::CanGc; use crate::script_runtime::CanGc;
@ -28,14 +26,12 @@ use crate::script_runtime::CanGc;
#[dom_struct] #[dom_struct]
pub(crate) struct Text { pub(crate) struct Text {
characterdata: CharacterData, characterdata: CharacterData,
slottable_data: RefCell<SlottableData>,
} }
impl Text { impl Text {
pub(crate) fn new_inherited(text: DOMString, document: &Document) -> Text { pub(crate) fn new_inherited(text: DOMString, document: &Document) -> Text {
Text { Text {
characterdata: CharacterData::new_inherited(text, document), characterdata: CharacterData::new_inherited(text, document),
slottable_data: Default::default(),
} }
} }
@ -56,10 +52,6 @@ impl Text {
can_gc, can_gc,
) )
} }
pub(crate) fn slottable_data(&self) -> &RefCell<SlottableData> {
&self.slottable_data
}
} }
impl TextMethods<crate::DomTypeHolder> for Text { impl TextMethods<crate::DomTypeHolder> for Text {

View file

@ -140,11 +140,6 @@ impl<'dom> ServoLayoutElement<'dom> {
Some(node) => matches!(node.script_type_id(), NodeTypeId::Document(_)), Some(node) => matches!(node.script_type_id(), NodeTypeId::Document(_)),
} }
} }
fn assigned_slot(&self) -> Option<Self> {
let slot = self.element.get_assigned_slot()?;
Some(Self::from_layout_js(slot.upcast()))
}
} }
pub enum DOMDescendantIterator<E> pub enum DOMDescendantIterator<E>
@ -204,11 +199,7 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
} }
fn traversal_parent(&self) -> Option<Self> { fn traversal_parent(&self) -> Option<Self> {
if let Some(assigned_slot) = self.assigned_slot() { self.as_node().traversal_parent()
Some(assigned_slot)
} else {
self.as_node().traversal_parent()
}
} }
fn is_html_element(&self) -> bool { fn is_html_element(&self) -> bool {
@ -752,7 +743,7 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
#[allow(unsafe_code)] #[allow(unsafe_code)]
fn assigned_slot(&self) -> Option<Self> { fn assigned_slot(&self) -> Option<Self> {
self.assigned_slot() self.as_node().assigned_slot()
} }
fn is_html_element_in_html_document(&self) -> bool { fn is_html_element_in_html_document(&self) -> bool {

View file

@ -94,6 +94,14 @@ impl<'dom> ServoLayoutNode<'dom> {
pub(crate) fn get_jsmanaged(self) -> LayoutDom<'dom, Node> { pub(crate) fn get_jsmanaged(self) -> LayoutDom<'dom, Node> {
self.node self.node
} }
pub(crate) fn assigned_slot(self) -> Option<ServoLayoutElement<'dom>> {
self.node
.assigned_slot_for_layout()
.as_ref()
.map(LayoutDom::upcast)
.map(ServoLayoutElement::from_layout_js)
}
} }
impl style::dom::NodeInfo for ServoLayoutNode<'_> { impl style::dom::NodeInfo for ServoLayoutNode<'_> {
@ -139,6 +147,9 @@ impl<'dom> style::dom::TNode for ServoLayoutNode<'dom> {
} }
fn traversal_parent(&self) -> Option<ServoLayoutElement<'dom>> { fn traversal_parent(&self) -> Option<ServoLayoutElement<'dom>> {
if let Some(assigned_slot) = self.assigned_slot() {
return Some(assigned_slot);
}
let parent = self.parent_node()?; let parent = self.parent_node()?;
if let Some(shadow) = parent.as_shadow_root() { if let Some(shadow) = parent.as_shadow_root() {
return Some(shadow.host()); return Some(shadow.host());

View file

@ -35,5 +35,5 @@ sizeof_checker!(size_element, Element, 376);
sizeof_checker!(size_htmlelement, HTMLElement, 392); sizeof_checker!(size_htmlelement, HTMLElement, 392);
sizeof_checker!(size_div, HTMLDivElement, 392); sizeof_checker!(size_div, HTMLDivElement, 392);
sizeof_checker!(size_span, HTMLSpanElement, 392); sizeof_checker!(size_span, HTMLSpanElement, 392);
sizeof_checker!(size_text, Text, 256); sizeof_checker!(size_text, Text, 232);
sizeof_checker!(size_characterdata, CharacterData, 232); sizeof_checker!(size_characterdata, CharacterData, 232);