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();

View file

@ -90,10 +90,10 @@ interface Element : Node {
dictionary ShadowRootInit {
required ShadowRootMode mode;
// boolean delegatesFocus = false;
boolean delegatesFocus = false;
SlotAssignmentMode slotAssignment = "named";
boolean clonable = false;
// boolean serializable = false;
boolean serializable = false;
};
// http://dev.w3.org/csswg/cssom-view/#extensions-to-the-element-interface

View file

@ -9,10 +9,10 @@
[Exposed=Window]
interface ShadowRoot : DocumentFragment {
readonly attribute ShadowRootMode mode;
// readonly attribute boolean delegatesFocus;
readonly attribute boolean delegatesFocus;
readonly attribute SlotAssignmentMode slotAssignment;
readonly attribute boolean clonable;
// readonly attribute boolean serializable;
readonly attribute boolean serializable;
readonly attribute Element host;
attribute EventHandler onslotchange;
};

View file

@ -1,4 +0,0 @@
[element-internals-shadowroot.html]
expected: ERROR
[ElementInternals.shadowRoot doesn't reveal pre-attached closed shadowRoot]
expected: FAIL

View file

@ -164,9 +164,6 @@
[AbortSignal interface: new AbortController().signal must inherit property "abort()" with the proper type]
expected: FAIL
[ShadowRoot interface: attribute delegatesFocus]
expected: FAIL
[XSLTProcessor interface: existence and properties of interface object]
expected: FAIL
@ -347,9 +344,6 @@
[DocumentFragment interface: operation replaceChildren((Node or TrustedScript or DOMString)...)]
expected: FAIL
[ShadowRoot interface: attribute serializable]
expected: FAIL
[Element interface: operation prepend((Node or TrustedScript or DOMString)...)]
expected: FAIL
@ -442,6 +436,3 @@
[Element interface: calling moveBefore(Node, Node?) on element with too few arguments must throw TypeError]
expected: FAIL
[idlharness.window.html?include=Node]

View file

@ -1,6 +0,0 @@
[Element-interface-attachShadow-custom-element.html]
[Element.attachShadow for an autonomous custom element with disabledFeatures=["shadow"\] should throw a NotSupportedError]
expected: FAIL
[Element.attachShadow for a customized built-in element with disabledFeatures=["shadow"\] should throw a NotSupportedError]
expected: FAIL

View file

@ -1,3 +0,0 @@
[Element-interface-attachShadow.html]
[Element.attachShadow must throw a NotSupportedError if the context object already hosts a shadow tree]
expected: FAIL

View file

@ -50,57 +50,6 @@
[Declarative Shadow DOM as a child of <span>, with mode=open, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <article>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <aside>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <blockquote>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <div>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <footer>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <h1>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <h2>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <h3>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <h4>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <h5>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <h6>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <header>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <main>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <nav>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <p>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <section>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <span>, with mode=closed, delegatesFocus=false. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <article>, with mode=open, delegatesFocus=true. Should be safelisted.]
expected: FAIL
@ -151,54 +100,3 @@
[Declarative Shadow DOM as a child of <span>, with mode=open, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <article>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <aside>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <blockquote>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <div>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <footer>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <h1>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <h2>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <h3>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <h4>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <h5>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <h6>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <header>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <main>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <nav>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <p>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <section>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL
[Declarative Shadow DOM as a child of <span>, with mode=closed, delegatesFocus=true. Should be safelisted.]
expected: FAIL

View file

@ -4,6 +4,3 @@
[Declarative Shadow DOM: Fragment parser basic test]
expected: FAIL
[Declarative Shadow DOM: delegates focus attribute]
expected: FAIL

View file

@ -1,3 +0,0 @@
[declarative-with-disabled-shadow.html]
[Declarative Shadow DOM: declarative shadow should fail if attachShadow() already called]
expected: FAIL

View file

@ -1,9 +0,0 @@
[ShadowRoot-delegatesFocus.html]
[default delegatesFocus value]
expected: FAIL
[delegatesFocus set to false in init dict]
expected: FAIL
[delegatesFocus set to true in init dict]
expected: FAIL