diff options
author | Simon Wülker <simon.wuelker@arcor.de> | 2025-02-02 21:49:42 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-02 20:49:42 +0000 |
commit | 6a2e37183c1995aa7c4bc31dfd8e871f28dcbae7 (patch) | |
tree | 821cb8ea46526f14278a14941dcc289383b46d3b | |
parent | 938baf6bf36336d812277b0bc056d1a614c472cc (diff) | |
download | servo-6a2e37183c1995aa7c4bc31dfd8e871f28dcbae7.tar.gz servo-6a2e37183c1995aa7c4bc31dfd8e871f28dcbae7.zip |
Lay out the contents of slot elements (#35220)
* Make Slottable match layout/alignment of NonNull<Node>
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Implement ServoLayoutElement::slotted_nodes
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Bump mozjs
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Layout the contents of slot elements
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Implement ServoLayoutElement::assigned_slot
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* implement ServoLayoutElement::traversal_parent
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Simplify slottable name update
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Don't iterate over children of shadow hosts
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Recompute slot style when contents change
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Change match_slottable to a function instead of a macro
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Fix crown errors
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Update WPT expectations
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Reset a slottable's assigned slot when it's removed from the slot
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
---------
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
26 files changed, 264 insertions, 144 deletions
diff --git a/Cargo.lock b/Cargo.lock index f7945018c6d..d5dab81eff5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,7 +469,7 @@ dependencies = [ "bitflags 2.8.0", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.10.5", "proc-macro2", "quote", "regex", @@ -1008,7 +1008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -1923,7 +1923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2450,7 +2450,7 @@ dependencies = [ "gobject-sys", "libc", "system-deps", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3902,7 +3902,7 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4248,7 +4248,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -4650,7 +4650,7 @@ dependencies = [ [[package]] name = "mozjs" version = "0.14.1" -source = "git+https://github.com/servo/mozjs#0081fc4a3f5fc9891d4377844a874651f7c46041" +source = "git+https://github.com/servo/mozjs#87cabf4e9ddf9fafe19713a3d6bc8c5e6105544c" dependencies = [ "bindgen 0.71.1", "cc", @@ -4663,7 +4663,7 @@ dependencies = [ [[package]] name = "mozjs_sys" version = "0.128.6-1" -source = "git+https://github.com/servo/mozjs#0081fc4a3f5fc9891d4377844a874651f7c46041" +source = "git+https://github.com/servo/mozjs#87cabf4e9ddf9fafe19713a3d6bc8c5e6105544c" dependencies = [ "bindgen 0.71.1", "cc", @@ -6128,7 +6128,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7430,7 +7430,7 @@ dependencies = [ "getrandom", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -8729,7 +8729,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/components/layout_2020/dom_traversal.rs b/components/layout_2020/dom_traversal.rs index 7c7697be740..3a66a34e30c 100644 --- a/components/layout_2020/dom_traversal.rs +++ b/components/layout_2020/dom_traversal.rs @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::borrow::Cow; +use std::iter::FusedIterator; use html5ever::{local_name, LocalName}; use log::warn; @@ -464,18 +465,49 @@ where } } -pub(crate) fn iter_child_nodes<'dom, Node>(parent: Node) -> impl Iterator<Item = Node> +pub enum ChildNodeIterator<Node> { + /// Iterating over the children of a node + Node(Option<Node>), + /// Iterating over the assigned nodes of a `HTMLSlotElement` + Slottables(<Vec<Node> as IntoIterator>::IntoIter), +} + +#[allow(clippy::unnecessary_to_owned)] // Clippy is wrong. +pub(crate) fn iter_child_nodes<'dom, Node>(parent: Node) -> ChildNodeIterator<Node> where Node: NodeExt<'dom>, { - if let Some(shadow) = parent.as_element().and_then(|e| e.shadow_root()) { - return iter_child_nodes(shadow.as_node()); - }; - - let mut next = parent.first_child(); - std::iter::from_fn(move || { - next.inspect(|child| { - next = child.next_sibling(); - }) - }) + if let Some(element) = parent.as_element() { + if let Some(shadow) = element.shadow_root() { + return iter_child_nodes(shadow.as_node()); + }; + + let slotted_nodes = element.slotted_nodes(); + if !slotted_nodes.is_empty() { + return ChildNodeIterator::Slottables(slotted_nodes.to_owned().into_iter()); + } + } + + let first = parent.first_child(); + ChildNodeIterator::Node(first) +} + +impl<'dom, Node> Iterator for ChildNodeIterator<Node> +where + Node: NodeExt<'dom>, +{ + type Item = Node; + + fn next(&mut self) -> Option<Self::Item> { + match self { + Self::Node(node) => { + let old = *node; + *node = old?.next_sibling(); + old + }, + Self::Slottables(slots) => slots.next(), + } + } } + +impl<'dom, Node> FusedIterator for ChildNodeIterator<Node> where Node: NodeExt<'dom> {} diff --git a/components/script/dom/bindings/root.rs b/components/script/dom/bindings/root.rs index d6b7d451362..912ed9acee4 100644 --- a/components/script/dom/bindings/root.rs +++ b/components/script/dom/bindings/root.rs @@ -335,6 +335,7 @@ where /// /// This should only be used as a field in other DOM objects. #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +#[repr(transparent)] pub(crate) struct Dom<T> { ptr: ptr::NonNull<T>, } @@ -444,6 +445,7 @@ where /// An unrooted reference to a DOM object for use in layout. `Layout*Helpers` /// traits must be implemented on this. #[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_interior)] +#[repr(transparent)] pub(crate) struct LayoutDom<'dom, T> { value: &'dom T, } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 2f27b11bc87..4ed92704d60 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -626,8 +626,8 @@ impl Element { Some(assigned_slot) } - pub(crate) fn set_assigned_slot(&self, assigned_slot: DomRoot<HTMLSlotElement>) { - self.ensure_rare_data().slottable_data.assigned_slot = Some(assigned_slot.as_traced()); + 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>> { @@ -727,6 +727,7 @@ pub(crate) trait LayoutElementHelpers<'dom> { ) -> Option<&'dom AttrValue>; 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_assigned_slot(&self) -> Option<LayoutDom<'dom, HTMLSlotElement>>; } impl LayoutDom<'_, Element> { @@ -1260,6 +1261,20 @@ impl<'dom> LayoutElementHelpers<'dom> for LayoutDom<'dom, Element> { }) .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 { @@ -3525,7 +3540,7 @@ impl ElementMethods<crate::DomTypeHolder> for Element { // > The assignedSlot getter steps are to return the result of // > find a slot given this and with the open flag set. - rooted!(in(*cx) let slottable = Slottable::Element(Dom::from_ref(self))); + rooted!(in(*cx) let slottable = Slottable(Dom::from_ref(self.upcast::<Node>()))); slottable.find_a_slot(true) } } @@ -3671,7 +3686,8 @@ impl VirtualMethods for Element { &local_name!("slot") => { // Update slottable data let cx = GlobalScope::get_cx(); - rooted!(in(*cx) let slottable = Slottable::Element(Dom::from_ref(self))); + + rooted!(in(*cx) let slottable = Slottable(Dom::from_ref(self.upcast::<Node>()))); // Slottable name change steps from https://dom.spec.whatwg.org/#light-tree-slotables if let Some(assigned_slot) = slottable.assigned_slot() { diff --git a/components/script/dom/htmlslotelement.rs b/components/script/dom/htmlslotelement.rs index 3bb7ad21cb8..ccabca0972c 100644 --- a/components/script/dom/htmlslotelement.rs +++ b/components/script/dom/htmlslotelement.rs @@ -2,11 +2,11 @@ * 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/. */ -use std::cell::{Cell, RefCell}; +use std::cell::{Cell, Ref, RefCell}; use dom_struct::dom_struct; use html5ever::{local_name, namespace_url, ns, LocalName, Prefix}; -use js::gc::{RootedGuard, RootedVec}; +use js::gc::RootedVec; use js::rust::HandleObject; use crate::dom::attr::Attr; @@ -18,6 +18,7 @@ use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Bindi use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{ ShadowRootMode, SlotAssignmentMode, }; +use crate::dom::bindings::codegen::InheritTypes::{CharacterDataTypeId, NodeTypeId}; use crate::dom::bindings::codegen::UnionTypes::ElementOrText; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::{Dom, DomRoot}; @@ -27,7 +28,7 @@ use crate::dom::element::{AttributeMutation, Element}; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlelement::HTMLElement; use crate::dom::mutationobserver::MutationObserver; -use crate::dom::node::{Node, ShadowIncluding}; +use crate::dom::node::{Node, NodeDamage, ShadowIncluding}; use crate::dom::text::Text; use crate::dom::virtualmethods::VirtualMethods; use crate::script_runtime::CanGc; @@ -103,8 +104,8 @@ impl HTMLSlotElementMethods<crate::DomTypeHolder> for HTMLSlotElement { // Step 3. For each node of nodes: for element_or_text in nodes.into_iter() { rooted!(in(*cx) let node = match element_or_text { - ElementOrText::Element(element) => Slottable::Element(Dom::from_ref(&element)), - ElementOrText::Text(text) => Slottable::Text(Dom::from_ref(&text)), + ElementOrText::Element(element) => Slottable(Dom::from_ref(element.upcast())), + ElementOrText::Text(text) => Slottable(Dom::from_ref(text.upcast())), }); // Step 3.1 If node's manual slot assignment refers to a slot, @@ -139,13 +140,17 @@ impl HTMLSlotElementMethods<crate::DomTypeHolder> for HTMLSlotElement { } /// <https://dom.spec.whatwg.org/#concept-slotable> +/// +/// The contained node is assumed to be either `Element` or `Text` +/// +/// This field is public to make it easy to construct slottables. +/// As such, it is possible to put Nodes that are not slottables +/// in there. Using a [Slottable] like this will quickly lead to +/// a panic. #[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -pub(crate) enum Slottable { - Element(Dom<Element>), - Text(Dom<Text>), -} - +#[repr(transparent)] +pub(crate) struct Slottable(pub Dom<Node>); /// Data shared between all [slottables](https://dom.spec.whatwg.org/#concept-slotable) /// /// Note that the [slottable name](https://dom.spec.whatwg.org/#slotable-name) is not @@ -213,10 +218,13 @@ impl HTMLSlotElement { // child of slot, in tree order, to slottables. if slottables.is_empty() { for child in self.upcast::<Node>().children() { - if let Some(element) = child.downcast::<Element>() { - slottables.push(Slottable::Element(Dom::from_ref(element))); - } else if let Some(text) = child.downcast::<Text>() { - slottables.push(Slottable::Text(Dom::from_ref(text))); + let is_slottable = matches!( + child.type_id(), + NodeTypeId::Element(_) | + NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) + ); + if is_slottable { + slottables.push(Slottable(child.as_traced())); } } } @@ -224,12 +232,7 @@ impl HTMLSlotElement { // Step 5. For each node in slottables: for slottable in slottables.iter() { // Step 5.1 If node is a slot whose root is a shadow root: - // NOTE: Only elements can be slots - let maybe_slot_element = match &slottable { - Slottable::Element(element) => element.downcast::<HTMLSlotElement>(), - Slottable::Text(_) => None, - }; - match maybe_slot_element { + match slottable.0.downcast::<HTMLSlotElement>() { Some(slot_element) if slot_element .upcast::<Node>() @@ -291,24 +294,21 @@ impl HTMLSlotElement { } // Step 6. Otherwise, for each slottable child slottable of host, in tree order: else { - let mut for_slottable = |slottable: RootedGuard<Slottable>| { - // Step 6.1 Let foundSlot be the result of finding a slot given slottable. - let found_slot = slottable.find_a_slot(false); - - // Step 6.2 If foundSlot is slot, then append slottable to result. - if found_slot.is_some_and(|found_slot| &*found_slot == self) { - result.push(slottable.clone()); - } - }; for child in host.upcast::<Node>().children() { - if let Some(element) = child.downcast::<Element>() { - rooted!(in(*cx) let slottable = Slottable::Element(Dom::from_ref(element))); - for_slottable(slottable); - continue; - } - if let Some(text) = child.downcast::<Text>() { - rooted!(in(*cx) let slottable = Slottable::Text(Dom::from_ref(text))); - for_slottable(slottable); + let is_slottable = matches!( + child.type_id(), + NodeTypeId::Element(_) | + NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) + ); + if is_slottable { + rooted!(in(*cx) let slottable = Slottable(child.as_traced())); + // Step 6.1 Let foundSlot be the result of finding a slot given slottable. + let found_slot = slottable.find_a_slot(false); + + // Step 6.2 If foundSlot is slot, then append slottable to result. + if found_slot.is_some_and(|found_slot| &*found_slot == self) { + result.push(slottable.clone()); + } } } } @@ -329,17 +329,26 @@ impl HTMLSlotElement { self.signal_a_slot_change(); } + // NOTE: This is not written in the spec, which is likely a bug (https://github.com/whatwg/dom/issues/1352) + // If we don't disconnect the old slottables from this slot then they'll stay implictly + // connected, which causes problems later on + for slottable in self.assigned_nodes().iter() { + slottable.set_assigned_slot(None); + } + // Step 3. Set slot’s assigned nodes to slottables. *self.assigned_nodes.borrow_mut() = slottables.iter().cloned().collect(); // Step 4. For each slottable in slottables, set slottable’s assigned slot to slot. for slottable in slottables.iter() { - slottable.set_assigned_slot(DomRoot::from_ref(self)); + slottable.set_assigned_slot(Some(self)); } } /// <https://dom.spec.whatwg.org/#signal-a-slot-change> pub(crate) fn signal_a_slot_change(&self) { + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + if self.is_in_agents_signal_slots.get() { return; } @@ -356,6 +365,12 @@ impl HTMLSlotElement { debug_assert!(self.is_in_agents_signal_slots.get()); self.is_in_agents_signal_slots.set(false); } + + /// Returns the slot's assigned nodes if the root's slot assignment mode + /// is "named", or the manually assigned nodes otherwise + pub(crate) fn assigned_nodes(&self) -> Ref<'_, [Slottable]> { + Ref::map(self.assigned_nodes.borrow(), Vec::as_slice) + } } impl Slottable { @@ -418,16 +433,13 @@ impl Slottable { } fn node(&self) -> &Node { - match self { - Self::Element(element) => element.upcast(), - Self::Text(text) => text.upcast(), - } + &self.0 } pub(crate) fn assigned_slot(&self) -> Option<DomRoot<HTMLSlotElement>> { - match self { - Self::Element(element) => element.assigned_slot(), - Self::Text(text) => { + self.match_slottable( + |element: &Element| element.assigned_slot(), + |text: &Text| { let assigned_slot = text .slottable_data() .borrow() @@ -436,51 +448,72 @@ impl Slottable { .as_rooted(); Some(assigned_slot) }, - } + ) } - pub(crate) fn set_assigned_slot(&self, assigned_slot: DomRoot<HTMLSlotElement>) { - match self { - Self::Element(element) => element.set_assigned_slot(assigned_slot), - Self::Text(text) => { - text.slottable_data().borrow_mut().assigned_slot = Some(assigned_slot.as_traced()) + pub(crate) fn set_assigned_slot(&self, assigned_slot: Option<&HTMLSlotElement>) { + self.match_slottable( + |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( &self, manually_assigned_slot: Option<&HTMLSlotElement>, ) { - match self { - Self::Element(element) => element.set_manual_slot_assignment(manually_assigned_slot), - Self::Text(text) => { + self.match_slottable( + |element: &Element| element.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>> { - match self { - Self::Element(element) => element.manual_slot_assignment(), - Self::Text(text) => text - .slottable_data() - .borrow() - .manual_slot_assignment - .as_ref() - .map(Dom::as_rooted), - } + self.match_slottable( + |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 { // NOTE: Only elements have non-empty names - let Self::Element(element) = self else { + let Some(element) = self.0.downcast::<Element>() else { return DOMString::new(); }; 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 { diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 8a309080f0a..709c2d39936 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -2155,12 +2155,8 @@ impl Node { if let Some(shadow_root) = parent.downcast::<Element>().and_then(Element::shadow_root) { if shadow_root.SlotAssignment() == SlotAssignmentMode::Named { let cx = GlobalScope::get_cx(); - if let Some(element) = node.downcast::<Element>() { - rooted!(in(*cx) let slottable = Slottable::Element(Dom::from_ref(element))); - slottable.assign_a_slot(); - } - if let Some(text) = node.downcast::<Text>() { - rooted!(in(*cx) let slottable = Slottable::Text(Dom::from_ref(text))); + if node.is::<Element>() || node.is::<Text>() { + rooted!(in(*cx) let slottable = Slottable(Dom::from_ref(node))); slottable.assign_a_slot(); } } diff --git a/components/script/dom/text.rs b/components/script/dom/text.rs index 3f81f0be6e2..e34c9d2bd68 100644 --- a/components/script/dom/text.rs +++ b/components/script/dom/text.rs @@ -136,7 +136,7 @@ impl TextMethods<crate::DomTypeHolder> for Text { // > The assignedSlot getter steps are to return the result of // > find a slot given this and with the open flag set. - rooted!(in(*cx) let slottable = Slottable::Text(Dom::from_ref(self))); + rooted!(in(*cx) let slottable = Slottable(Dom::from_ref(self.upcast::<Node>()))); slottable.find_a_slot(true) } } diff --git a/components/script/layout_dom/element.rs b/components/script/layout_dom/element.rs index 45ff4450542..1abd64e1fbf 100644 --- a/components/script/layout_dom/element.rs +++ b/components/script/layout_dom/element.rs @@ -2,9 +2,9 @@ * 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/. */ -use std::fmt; use std::hash::Hash; use std::sync::atomic::Ordering; +use std::{fmt, slice}; use atomic_refcell::{AtomicRef, AtomicRefMut}; use html5ever::{local_name, namespace_url, ns, LocalName, Namespace}; @@ -40,12 +40,13 @@ use style_dom::ElementState; use crate::dom::attr::AttrHelpersForLayout; use crate::dom::bindings::inheritance::{ - CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId, HTMLElementTypeId, NodeTypeId, - TextTypeId, + Castable, CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId, HTMLElementTypeId, + NodeTypeId, TextTypeId, }; use crate::dom::bindings::root::LayoutDom; use crate::dom::characterdata::LayoutCharacterDataHelpers; use crate::dom::element::{Element, LayoutElementHelpers}; +use crate::dom::htmlslotelement::HTMLSlotElement; use crate::dom::node::{LayoutNodeHelpers, Node, NodeFlags}; use crate::layout_dom::{ServoLayoutNode, ServoShadowRoot, ServoThreadSafeLayoutNode}; @@ -138,26 +139,77 @@ impl<'dom> ServoLayoutElement<'dom> { 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> +where + E: TElement, +{ + /// Iterating over the children of a node, including children of a potential + /// [ShadowRoot](crate::dom::shadow_root::ShadowRoot) + Children(DomChildren<E::ConcreteNode>), + /// Iterating over the content's of a [`<slot>`](HTMLSlotElement) element. + Slottables { slot: E, index: usize }, +} + +impl<E> Iterator for DOMDescendantIterator<E> +where + E: TElement, +{ + type Item = E::ConcreteNode; + + fn next(&mut self) -> Option<Self::Item> { + match self { + Self::Children(children) => children.next(), + Self::Slottables { slot, index } => { + let slottables = slot.slotted_nodes(); + let slot = slottables.get(*index)?; + *index += 1; + Some(*slot) + }, + } + } } impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> { type ConcreteNode = ServoLayoutNode<'dom>; - type TraversalChildrenIterator = DomChildren<Self::ConcreteNode>; + type TraversalChildrenIterator = DOMDescendantIterator<Self>; fn as_node(&self) -> ServoLayoutNode<'dom> { ServoLayoutNode::from_layout_js(self.element.upcast()) } fn traversal_children(&self) -> LayoutIterator<Self::TraversalChildrenIterator> { - let iterator = if let Some(shadow_root) = self.shadow_root() { - shadow_root.as_node().dom_children() + let iterator = if self.slotted_nodes().is_empty() { + let children = if let Some(shadow_root) = self.shadow_root() { + shadow_root.as_node().dom_children() + } else { + self.as_node().dom_children() + }; + DOMDescendantIterator::Children(children) } else { - self.as_node().dom_children() + DOMDescendantIterator::Slottables { + slot: *self, + index: 0, + } }; LayoutIterator(iterator) } + fn traversal_parent(&self) -> Option<Self> { + if let Some(assigned_slot) = self.assigned_slot() { + Some(assigned_slot) + } else { + self.as_node().traversal_parent() + } + } + fn is_html_element(&self) -> bool { ServoLayoutElement::is_html_element(self) } @@ -457,6 +509,24 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> { servo_layout_node.as_element().unwrap() } } + + fn slotted_nodes(&self) -> &[Self::ConcreteNode] { + let Some(slot_element) = self.element.unsafe_get().downcast::<HTMLSlotElement>() else { + return &[]; + }; + let assigned_nodes = slot_element.assigned_nodes(); + + // SAFETY: + // Self::ConcreteNode (aka ServoLayoutNode) and Slottable are guaranteed to have the same + // layout and alignment as ptr::NonNull<T>. Lifetimes are not an issue because the + // slottables are being kept alive by the slot element. + unsafe { + slice::from_raw_parts( + assigned_nodes.as_ptr() as *const Self::ConcreteNode, + assigned_nodes.len(), + ) + } + } } impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> { @@ -670,7 +740,12 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> { } fn is_html_slot_element(&self) -> bool { - self.element.is_html_element() && self.local_name() == &local_name!("slot") + self.element.is::<HTMLSlotElement>() + } + + #[allow(unsafe_code)] + fn assigned_slot(&self) -> Option<Self> { + self.assigned_slot() } fn is_html_element_in_html_document(&self) -> bool { diff --git a/components/script/layout_dom/node.rs b/components/script/layout_dom/node.rs index 2ef11841ca6..1d9d5310a3b 100644 --- a/components/script/layout_dom/node.rs +++ b/components/script/layout_dom/node.rs @@ -45,6 +45,7 @@ use crate::dom::text::Text; /// should only be used on a single thread. If you need to use nodes across /// threads use ServoThreadSafeLayoutNode. #[derive(Clone, Copy, PartialEq)] +#[repr(transparent)] pub struct ServoLayoutNode<'dom> { /// The wrapped private DOM node. pub(super) node: LayoutDom<'dom, Node>, diff --git a/tests/wpt/meta/css/css-content/quotes-slot-scoping.html.ini b/tests/wpt/meta/css/css-content/quotes-slot-scoping.html.ini deleted file mode 100644 index d6d7a8fd026..00000000000 --- a/tests/wpt/meta/css/css-content/quotes-slot-scoping.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[quotes-slot-scoping.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-display/display-contents-slot-attach-whitespace.html.ini b/tests/wpt/meta/css/css-display/display-contents-slot-attach-whitespace.html.ini deleted file mode 100644 index 040a59456d7..00000000000 --- a/tests/wpt/meta/css/css-display/display-contents-slot-attach-whitespace.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[display-contents-slot-attach-whitespace.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-lists/counter-slot-order-scoping.html.ini b/tests/wpt/meta/css/css-lists/counter-slot-order-scoping.html.ini deleted file mode 100644 index 9e06a8d2ccb..00000000000 --- a/tests/wpt/meta/css/css-lists/counter-slot-order-scoping.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[counter-slot-order-scoping.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-lists/counter-slot-order.html.ini b/tests/wpt/meta/css/css-lists/counter-slot-order.html.ini deleted file mode 100644 index 96ad6ce2679..00000000000 --- a/tests/wpt/meta/css/css-lists/counter-slot-order.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[counter-slot-order.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-text-decor/text-decoration-propagation-shadow.html.ini b/tests/wpt/meta/css/css-text-decor/text-decoration-propagation-shadow.html.ini deleted file mode 100644 index 74c4b81399e..00000000000 --- a/tests/wpt/meta/css/css-text-decor/text-decoration-propagation-shadow.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[text-decoration-propagation-shadow.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-text/white-space/remove-slotted-with-whitespace-sibling.html.ini b/tests/wpt/meta/css/css-text/white-space/remove-slotted-with-whitespace-sibling.html.ini deleted file mode 100644 index 9470360b861..00000000000 --- a/tests/wpt/meta/css/css-text/white-space/remove-slotted-with-whitespace-sibling.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[remove-slotted-with-whitespace-sibling.html] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/2d.text-outside-of-the-flat-tree.html.ini b/tests/wpt/meta/html/canvas/element/2d.text-outside-of-the-flat-tree.html.ini deleted file mode 100644 index e4c366d067c..00000000000 --- a/tests/wpt/meta/html/canvas/element/2d.text-outside-of-the-flat-tree.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[2d.text-outside-of-the-flat-tree.html] - expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html.ini b/tests/wpt/meta/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html.ini index d462e9daaec..3cc32e416f7 100644 --- a/tests/wpt/meta/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html.ini +++ b/tests/wpt/meta/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html.ini @@ -1,7 +1,4 @@ [DocumentOrShadowRoot-prototype-elementFromPoint.html] - [document.elementFromPoint and shadowRoot.elementFromPoint must return the shadow host when the hit-tested text node is assigned to a slot and the host has display: inline] - expected: FAIL - [document.elementFromPoint and shadowRoot.elementFromPoint must return the element assigned to a slot when hit-tested text node under an element is assigned to a slot in the shadow tree and the shadow host of the slot has display: block] expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/focus/focus-pseudo-on-shadow-host-2.html.ini b/tests/wpt/meta/shadow-dom/focus/focus-pseudo-on-shadow-host-2.html.ini new file mode 100644 index 00000000000..808653f1453 --- /dev/null +++ b/tests/wpt/meta/shadow-dom/focus/focus-pseudo-on-shadow-host-2.html.ini @@ -0,0 +1,2 @@ +[focus-pseudo-on-shadow-host-2.html] + expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/imperative-slot-fallback-clear.html.ini b/tests/wpt/meta/shadow-dom/imperative-slot-fallback-clear.html.ini deleted file mode 100644 index 2fec56352db..00000000000 --- a/tests/wpt/meta/shadow-dom/imperative-slot-fallback-clear.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[imperative-slot-fallback-clear.html] - [Text node fallback should be cleared in a subsequently layout] - expected: FAIL - - [Element fallback should be cleared in a subsequent layout] - expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/imperative-slot-layout-invalidation-001.html.ini b/tests/wpt/meta/shadow-dom/imperative-slot-layout-invalidation-001.html.ini deleted file mode 100644 index 42aa5541510..00000000000 --- a/tests/wpt/meta/shadow-dom/imperative-slot-layout-invalidation-001.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[imperative-slot-layout-invalidation-001.html] - expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/layout-slot-no-longer-fallback.html.ini b/tests/wpt/meta/shadow-dom/layout-slot-no-longer-fallback.html.ini deleted file mode 100644 index 0b6e3431ed9..00000000000 --- a/tests/wpt/meta/shadow-dom/layout-slot-no-longer-fallback.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[layout-slot-no-longer-fallback.html] - expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/slot-fallback-content-001.html.ini b/tests/wpt/meta/shadow-dom/slot-fallback-content-001.html.ini deleted file mode 100644 index 99cfddb8bf3..00000000000 --- a/tests/wpt/meta/shadow-dom/slot-fallback-content-001.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[slot-fallback-content-001.html] - expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/slot-fallback-content-002.html.ini b/tests/wpt/meta/shadow-dom/slot-fallback-content-002.html.ini deleted file mode 100644 index 2ec0637e458..00000000000 --- a/tests/wpt/meta/shadow-dom/slot-fallback-content-002.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[slot-fallback-content-002.html] - expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/slot-fallback-content-007.html.ini b/tests/wpt/meta/shadow-dom/slot-fallback-content-007.html.ini deleted file mode 100644 index f25986dbaff..00000000000 --- a/tests/wpt/meta/shadow-dom/slot-fallback-content-007.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[slot-fallback-content-007.html] - expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/untriaged/shadow-trees/reprojection/reprojection-001.html.ini b/tests/wpt/meta/shadow-dom/untriaged/shadow-trees/reprojection/reprojection-001.html.ini deleted file mode 100644 index e229960af83..00000000000 --- a/tests/wpt/meta/shadow-dom/untriaged/shadow-trees/reprojection/reprojection-001.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[reprojection-001.html] - expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/untriaged/shadow-trees/shadow-root-002.html.ini b/tests/wpt/meta/shadow-dom/untriaged/shadow-trees/shadow-root-002.html.ini deleted file mode 100644 index 07a6ef6989e..00000000000 --- a/tests/wpt/meta/shadow-dom/untriaged/shadow-trees/shadow-root-002.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[shadow-root-002.html] - expected: FAIL |