aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authorSimon Wülker <simon.wuelker@arcor.de>2025-02-02 21:49:42 +0100
committerGitHub <noreply@github.com>2025-02-02 20:49:42 +0000
commit6a2e37183c1995aa7c4bc31dfd8e871f28dcbae7 (patch)
tree821cb8ea46526f14278a14941dcc289383b46d3b /components
parent938baf6bf36336d812277b0bc056d1a614c472cc (diff)
downloadservo-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>
Diffstat (limited to 'components')
-rw-r--r--components/layout_2020/dom_traversal.rs54
-rw-r--r--components/script/dom/bindings/root.rs2
-rw-r--r--components/script/dom/element.rs24
-rw-r--r--components/script/dom/htmlslotelement.rs165
-rw-r--r--components/script/dom/node.rs8
-rw-r--r--components/script/dom/text.rs2
-rw-r--r--components/script/layout_dom/element.rs91
-rw-r--r--components/script/layout_dom/node.rs1
8 files changed, 251 insertions, 96 deletions
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>,