diff --git a/components/script/dom/htmlslotelement.rs b/components/script/dom/htmlslotelement.rs
index 2685179af08..6d4d03f518a 100644
--- a/components/script/dom/htmlslotelement.rs
+++ b/components/script/dom/htmlslotelement.rs
@@ -29,7 +29,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, NodeDamage, ShadowIncluding};
+use crate::dom::node::{Node, NodeDamage, NodeTraits, ShadowIncluding};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
@@ -407,17 +407,7 @@ impl Slottable {
// Step 6. Return the first slot in tree order in shadow’s descendants whose
// name is slottable’s name, if any; otherwise null.
- for node in shadow_root
- .upcast::()
- .traverse_preorder(ShadowIncluding::No)
- {
- if let Some(slot) = node.downcast::() {
- if slot.Name() == self.name() {
- return Some(DomRoot::from_ref(slot));
- }
- }
- }
- None
+ shadow_root.slot_for_name(&self.name())
}
///
@@ -475,11 +465,50 @@ impl VirtualMethods for HTMLSlotElement {
self.super_type().unwrap().attribute_mutated(attr, mutation);
if attr.local_name() == &local_name!("name") && attr.namespace() == &ns!() {
+ if let Some(shadow_root) = self.containing_shadow_root() {
+ // Shadow roots keep a list of slot descendants, so we need to tell it
+ // about our name change
+ let old_value = match mutation {
+ AttributeMutation::Set(old) => old
+ .map(|value| value.to_string().into())
+ .unwrap_or_default(),
+ AttributeMutation::Removed => attr.value().to_string().into(),
+ };
+
+ shadow_root.unregister_slot(old_value, self);
+ shadow_root.register_slot(self);
+ }
+
+ // Changing the name might cause slot assignments to change
self.upcast::()
.GetRootNode(&GetRootNodeOptions::empty())
.assign_slottables_for_a_tree()
}
}
+
+ fn bind_to_tree(&self, context: &super::node::BindContext) {
+ if let Some(s) = self.super_type() {
+ s.bind_to_tree(context);
+ }
+
+ if !context.tree_is_in_a_shadow_tree {
+ return;
+ }
+
+ self.containing_shadow_root()
+ .expect("not in a shadow tree")
+ .register_slot(self);
+ }
+
+ fn unbind_from_tree(&self, context: &super::node::UnbindContext) {
+ if let Some(s) = self.super_type() {
+ s.unbind_from_tree(context);
+ }
+
+ if let Some(shadow_root) = self.containing_shadow_root() {
+ shadow_root.unregister_slot(self.Name(), self);
+ }
+ }
}
impl js::gc::Rootable for Slottable {}
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
index d4e404dc030..4a4c9f53462 100644
--- a/components/script/dom/node.rs
+++ b/components/script/dom/node.rs
@@ -1362,7 +1362,11 @@ impl Node {
// NOTE: This method traverses all descendants of the node and is potentially very
// expensive. If the node is not a shadow root then assigning slottables to it won't
// have any effect, so we take a fast path out.
- if !self.is::() {
+ let Some(shadow_root) = self.downcast::() else {
+ return;
+ };
+
+ if !shadow_root.has_slot_descendants() {
return;
}
diff --git a/components/script/dom/shadowroot.rs b/components/script/dom/shadowroot.rs
index 357594a0948..c1adb60f566 100644
--- a/components/script/dom/shadowroot.rs
+++ b/components/script/dom/shadowroot.rs
@@ -2,6 +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::collections::HashMap;
+use std::collections::hash_map::Entry;
+
use dom_struct::dom_struct;
use servo_arc::Arc;
use servo_atoms::Atom;
@@ -13,6 +16,7 @@ use style::stylist::{CascadeData, Stylist};
use crate::conversions::Convert;
use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::HTMLSlotElementBinding::HTMLSlotElement_Binding::HTMLSlotElementMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
ShadowRootMode, SlotAssignmentMode,
@@ -27,8 +31,10 @@ use crate::dom::document::Document;
use crate::dom::documentfragment::DocumentFragment;
use crate::dom::documentorshadowroot::{DocumentOrShadowRoot, StyleSheetInDocument};
use crate::dom::element::Element;
+use crate::dom::htmlslotelement::HTMLSlotElement;
use crate::dom::node::{
BindContext, Node, NodeDamage, NodeFlags, NodeTraits, ShadowIncluding, UnbindContext,
+ VecPreOrderInsertionHelper,
};
use crate::dom::stylesheetlist::{StyleSheetList, StyleSheetListOwner};
use crate::dom::types::EventTarget;
@@ -65,6 +71,8 @@ pub(crate) struct ShadowRoot {
///
clonable: bool,
+
+ slots: DomRefCell>>>,
}
impl ShadowRoot {
@@ -95,6 +103,7 @@ impl ShadowRoot {
mode,
slot_assignment_mode,
clonable,
+ slots: Default::default(),
}
}
@@ -209,6 +218,40 @@ impl ShadowRoot {
root,
);
}
+
+ pub(crate) fn register_slot(&self, slot: &HTMLSlotElement) {
+ debug!("Registering slot with name={:?}", slot.Name().str());
+
+ let mut slots = self.slots.borrow_mut();
+
+ let slots_with_the_same_name = slots.entry(slot.Name()).or_default();
+
+ // Insert the slot before the first element that comes after it in tree order
+ slots_with_the_same_name.insert_pre_order(slot, self.upcast::());
+ }
+
+ pub(crate) fn unregister_slot(&self, name: DOMString, slot: &HTMLSlotElement) {
+ debug!("Unregistering slot with name={:?}", name.str());
+
+ let mut slots = self.slots.borrow_mut();
+ let Entry::Occupied(mut entry) = slots.entry(name) else {
+ panic!("slot is not registered");
+ };
+ entry.get_mut().retain(|s| slot != &**s);
+ }
+
+ /// Find the first slot with the given name among this root's descendants in tree order
+ pub(crate) fn slot_for_name(&self, name: &DOMString) -> Option> {
+ self.slots
+ .borrow()
+ .get(name)
+ .and_then(|slots| slots.first())
+ .map(|slot| slot.as_rooted())
+ }
+
+ pub(crate) fn has_slot_descendants(&self) -> bool {
+ !self.slots.borrow().is_empty()
+ }
}
impl ShadowRootMethods for ShadowRoot {