Implement shadow dom slots (#35013)

* Implement slot-related algorithms

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

* Hook up slot elements to DOM creation logic

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

* Set a slot assignment mode for servo-internal shadow roots

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

* Assign slots when a slottable's slot attribute changes

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

* Properly compute slot name

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

* ./mach test-tidy

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

* Update <slot> name when name attribute changes

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

* Implement fast path for Node::assign_slottables_for_a_tree

assign_slottables_for_a_tree 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.

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

* Move slottable data into ElementRareData

This shrinks all element descendants back to their
original size.

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

* Address review comments

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

* Update WPT expectations

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-19 15:05:05 +01:00 committed by GitHub
parent 8bb50fa3c9
commit dabe162d44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 758 additions and 355 deletions

View file

@ -74,7 +74,7 @@ use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
ShadowRootMethods, ShadowRootMode,
ShadowRootMethods, ShadowRootMode, SlotAssignmentMode,
};
use crate::dom::bindings::codegen::Bindings::WindowBinding::{
ScrollBehavior, ScrollToOptions, WindowMethods,
@ -105,6 +105,7 @@ use crate::dom::domrectlist::DOMRectList;
use crate::dom::domtokenlist::DOMTokenList;
use crate::dom::elementinternals::ElementInternals;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlanchorelement::HTMLAnchorElement;
use crate::dom::htmlbodyelement::{HTMLBodyElement, HTMLBodyElementLayoutHelpers};
use crate::dom::htmlbuttonelement::HTMLButtonElement;
@ -124,6 +125,7 @@ use crate::dom::htmlobjectelement::HTMLObjectElement;
use crate::dom::htmloptgroupelement::HTMLOptGroupElement;
use crate::dom::htmloutputelement::HTMLOutputElement;
use crate::dom::htmlselectelement::HTMLSelectElement;
use crate::dom::htmlslotelement::{HTMLSlotElement, Slottable};
use crate::dom::htmlstyleelement::HTMLStyleElement;
use crate::dom::htmltablecellelement::{HTMLTableCellElement, HTMLTableCellElementLayoutHelpers};
use crate::dom::htmltableelement::{HTMLTableElement, HTMLTableElementLayoutHelpers};
@ -510,6 +512,7 @@ impl Element {
is_ua_widget: IsUserAgentWidget,
mode: ShadowRootMode,
clonable: bool,
slot_assignment_mode: SlotAssignmentMode,
) -> Fallible<DomRoot<ShadowRoot>> {
// Step 1.
// If elements namespace is not the HTML namespace,
@ -536,7 +539,13 @@ impl Element {
}
// Steps 4, 5 and 6.
let shadow_root = ShadowRoot::new(self, &self.node.owner_doc(), mode, clonable);
let shadow_root = ShadowRoot::new(
self,
&self.node.owner_doc(),
mode,
slot_assignment_mode,
clonable,
);
self.ensure_rare_data().shadow_root = Some(Dom::from_ref(&*shadow_root));
shadow_root
.upcast::<Node>()
@ -603,6 +612,43 @@ impl Element {
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: DomRoot<HTMLSlotElement>) {
self.ensure_rare_data().slottable_data.assigned_slot = Some(assigned_slot.as_traced());
}
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>
@ -3084,7 +3130,12 @@ impl ElementMethods<crate::DomTypeHolder> for Element {
fn AttachShadow(&self, init: &ShadowRootInit) -> Fallible<DomRoot<ShadowRoot>> {
// Step 1. Run attach a shadow root with this, init["mode"], init["clonable"], init["serializable"],
// init["delegatesFocus"], and init["slotAssignment"].
let shadow_root = self.attach_shadow(IsUserAgentWidget::No, init.mode, init.clonable)?;
let shadow_root = self.attach_shadow(
IsUserAgentWidget::No,
init.mode,
init.clonable,
init.slotAssignment,
)?;
// Step 2. Return thiss shadow root.
Ok(shadow_root)
@ -3460,6 +3511,16 @@ impl ElementMethods<crate::DomTypeHolder> for Element {
fn SetAriaValueText(&self, value: Option<DOMString>, can_gc: CanGc) {
self.set_nullable_string_attribute(&local_name!("aria-valuetext"), value, can_gc);
}
/// <https://dom.spec.whatwg.org/#dom-slotable-assignedslot>
fn GetAssignedSlot(&self) -> Option<DomRoot<HTMLSlotElement>> {
let cx = GlobalScope::get_cx();
// > 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)));
slottable.find_a_slot(true)
}
}
impl VirtualMethods for Element {
@ -3600,6 +3661,12 @@ 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)));
slottable.update_slot_name(attr, mutation, CanGc::note())
},
_ => {
// FIXME(emilio): This is pretty dubious, and should be done in
// the relevant super-classes.