Make Element::attach_shadow() and ShadowRoot closer to spec (#36024)

Signed-off-by: batu_hoang <longvatrong111@gmail.com>
This commit is contained in:
batu_hoang 2025-03-20 01:58:16 +08:00 committed by GitHub
parent 11d47558b3
commit db74179dc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 117 additions and 158 deletions

View file

@ -508,24 +508,25 @@ impl Element {
}
/// <https://dom.spec.whatwg.org/#dom-element-attachshadow>
#[allow(clippy::too_many_arguments)]
pub(crate) fn attach_shadow(
&self,
// TODO: remove is_ua_widget argument
is_ua_widget: IsUserAgentWidget,
mode: ShadowRootMode,
clonable: bool,
serializable: bool,
delegates_focus: bool,
slot_assignment_mode: SlotAssignmentMode,
can_gc: CanGc,
) -> Fallible<DomRoot<ShadowRoot>> {
// Step 1.
// If elements namespace is not the HTML namespace,
// Step 1. If elements namespace is not the HTML namespace,
// then throw a "NotSupportedError" DOMException.
if self.namespace != ns!(html) {
return Err(Error::NotSupported);
}
// Step 2.
// If elements local name is not a valid shadow host name,
// Step 2. If elements local name is not a valid shadow host name,
// then throw a "NotSupportedError" DOMException.
if !is_valid_shadow_host_name(self.local_name()) {
// UA shadow roots may be attached to anything
@ -534,13 +535,51 @@ impl Element {
}
}
// TODO: Update the following steps to align with the newer spec.
// Step 3.
if self.is_shadow_host() {
return Err(Error::InvalidState);
// Step 3. If elements local name is a valid custom element name,
// or elements is value is non-null
if is_valid_custom_element_name(self.local_name()) || self.get_is().is_some() {
// Step 3.1. Let definition be the result of looking up a custom element definition
// given elements node document, its namespace, its local name, and its is value.
let definition = self.get_custom_element_definition();
// Step 3.2. If definition is not null and definitions disable shadow
// is true, then throw a "NotSupportedError" DOMException.
if definition.is_some_and(|definition| definition.disable_shadow) {
return Err(Error::NotSupported);
}
}
// Steps 4, 5 and 6.
// Step 4. If element is a shadow host:
// Step 4.1. Let currentShadowRoot be elements shadow root.
if let Some(current_shadow_root) = self.shadow_root() {
// Step 4.2. If currentShadowRoots declarative is false
// or currentShadowRoots mode is not mode
// then throw a "NotSupportedError" DOMException.
if !current_shadow_root.is_declarative() ||
current_shadow_root.shadow_root_mode() != mode
{
return Err(Error::NotSupported);
}
// Step 4.3.1. Remove all of currentShadowRoots children, in tree order.
let node = self.upcast::<Node>();
for child in node.children() {
child.remove_self();
}
// Step 4.3.2. Set currentShadowRoots declarative to false.
current_shadow_root.set_declarative(false);
// Step 4.3.3. Return
return Ok(current_shadow_root);
}
// Step 5. Let shadow be a new shadow root whose node document
// is elements node document, host is element, and mode is mode
//
// Step 8. Set shadows slot assignment to slotAssignment
//
// Step 10. Set shadows clonable to clonable
let shadow_root = ShadowRoot::new(
self,
&self.node.owner_doc(),
@ -551,6 +590,9 @@ impl Element {
can_gc,
);
// Step 6. Set shadow's delegates focus to delegatesFocus
shadow_root.set_delegates_focus(delegates_focus);
// Step 7. If elements custom element state is "precustomized" or "custom",
// then set shadows available to element internals to true.
if matches!(
@ -560,6 +602,13 @@ impl Element {
shadow_root.set_available_to_element_internals(true);
}
// Step 9. Set shadow's declarative to false
shadow_root.set_declarative(false);
// Step 11. Set shadow's serializable to serializable
shadow_root.set_serializable(serializable);
// Step 12. Set elements shadow root to shadow
self.ensure_rare_data().shadow_root = Some(Dom::from_ref(&*shadow_root));
shadow_root
.upcast::<Node>()
@ -3234,6 +3283,8 @@ impl ElementMethods<crate::DomTypeHolder> for Element {
IsUserAgentWidget::No,
init.mode,
init.clonable,
init.serializable,
init.delegatesFocus,
init.slotAssignment,
CanGc::note(),
)?;

View file

@ -109,6 +109,8 @@ impl HTMLDetailsElement {
IsUserAgentWidget::Yes,
ShadowRootMode::Closed,
false,
false,
false,
SlotAssignmentMode::Manual,
can_gc,
)

View file

@ -1898,6 +1898,8 @@ impl HTMLMediaElement {
IsUserAgentWidget::Yes,
ShadowRootMode::Closed,
false,
false,
false,
SlotAssignmentMode::Manual,
can_gc,
)

View file

@ -85,6 +85,8 @@ impl HTMLMeterElement {
IsUserAgentWidget::Yes,
ShadowRootMode::Closed,
false,
false,
false,
SlotAssignmentMode::Manual,
can_gc,
)

View file

@ -83,6 +83,8 @@ impl HTMLProgressElement {
IsUserAgentWidget::Yes,
ShadowRootMode::Closed,
false,
false,
false,
SlotAssignmentMode::Manual,
can_gc,
)

View file

@ -2731,13 +2731,16 @@ impl Node {
copy_elem.attach_shadow(
IsUserAgentWidget::No,
shadow_root.Mode(),
true,
shadow_root.Clonable(),
shadow_root.Serializable(),
shadow_root.DelegatesFocus(),
shadow_root.SlotAssignment(),
can_gc
)
.expect("placement of attached shadow root must be valid, as this is a copy of an existing one");
// TODO: Step 7.3 Set copys shadow roots declarative to nodes shadow roots declarative.
// Step 7.3 Set copys shadow roots declarative to nodes shadow roots declarative.
copy_shadow_root.set_declarative(shadow_root.is_declarative());
// Step 7.4 For each child child of nodes shadow root, in tree order: append the result of
// cloning child with document and the clone children flag set, to copys shadow root.

View file

@ -1423,8 +1423,8 @@ impl TreeSink for Sink {
// has a shadowrootserializable attribute; otherwise false.
let mut shadow_root_mode = ShadowRootMode::Open;
let mut clonable = false;
let mut _delegatesfocus = false;
let mut _serializable = false;
let mut delegatesfocus = false;
let mut serializable = false;
let attrs: Vec<ElementAttribute> = attrs
.clone()
@ -1448,10 +1448,10 @@ impl TreeSink for Sink {
clonable = true;
},
local_name!("shadowrootdelegatesfocus") => {
_delegatesfocus = true;
delegatesfocus = true;
},
local_name!("shadowrootserializable") => {
_serializable = true;
serializable = true;
},
_ => {},
});
@ -1462,6 +1462,8 @@ impl TreeSink for Sink {
IsUserAgentWidget::No,
shadow_root_mode,
clonable,
serializable,
delegatesfocus,
SlotAssignmentMode::Manual,
CanGc::note(),
) {

View file

@ -82,6 +82,12 @@ pub(crate) struct ShadowRoot {
/// <https://dom.spec.whatwg.org/#shadowroot-declarative>
declarative: Cell<bool>,
/// <https://dom.spec.whatwg.org/#shadowroot-serializable>
serializable: Cell<bool>,
/// <https://dom.spec.whatwg.org/#shadowroot-delegates-focus>
delegates_focus: Cell<bool>,
}
impl ShadowRoot {
@ -117,6 +123,8 @@ impl ShadowRoot {
slots: Default::default(),
is_user_agent_widget: is_user_agent_widget == IsUserAgentWidget::Yes,
declarative: Cell::new(false),
serializable: Cell::new(false),
delegates_focus: Cell::new(false),
}
}
@ -284,6 +292,22 @@ impl ShadowRoot {
pub(crate) fn set_declarative(&self, declarative: bool) {
self.declarative.set(declarative);
}
pub(crate) fn is_declarative(&self) -> bool {
self.declarative.get()
}
pub(crate) fn shadow_root_mode(&self) -> ShadowRootMode {
self.mode
}
pub(crate) fn set_serializable(&self, serializable: bool) {
self.serializable.set(serializable);
}
pub(crate) fn set_delegates_focus(&self, delegates_focus: bool) {
self.delegates_focus.set(delegates_focus);
}
}
impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot {
@ -349,11 +373,21 @@ impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot {
self.mode
}
/// <https://dom.spec.whatwg.org/#dom-delegates-focus>
fn DelegatesFocus(&self) -> bool {
self.delegates_focus.get()
}
/// <https://dom.spec.whatwg.org/#dom-shadowroot-clonable>
fn Clonable(&self) -> bool {
self.clonable
}
/// <https://dom.spec.whatwg.org/#dom-serializable>
fn Serializable(&self) -> bool {
self.serializable.get()
}
/// <https://dom.spec.whatwg.org/#dom-shadowroot-host>
fn Host(&self) -> DomRoot<Element> {
let host = self.host.get();