diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index e410e73bb56..49bc12d3ae7 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -508,24 +508,25 @@ impl Element { } /// + #[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> { - // Step 1. - // If element’s namespace is not the HTML namespace, + // Step 1. If element’s namespace is not the HTML namespace, // then throw a "NotSupportedError" DOMException. if self.namespace != ns!(html) { return Err(Error::NotSupported); } - // Step 2. - // If element’s local name is not a valid shadow host name, + // Step 2. If element’s 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 element’s local name is a valid custom element name, + // or element’s 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 element’s 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 definition’s 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 element’s shadow root. + if let Some(current_shadow_root) = self.shadow_root() { + // Step 4.2. If currentShadowRoot’s declarative is false + // or currentShadowRoot’s 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 currentShadowRoot’s children, in tree order. + let node = self.upcast::(); + for child in node.children() { + child.remove_self(); + } + + // Step 4.3.2. Set currentShadowRoot’s 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 element’s node document, host is element, and mode is mode + // + // Step 8. Set shadow’s slot assignment to slotAssignment + // + // Step 10. Set shadow’s 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 element’s custom element state is "precustomized" or "custom", // then set shadow’s 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 element’s shadow root to shadow self.ensure_rare_data().shadow_root = Some(Dom::from_ref(&*shadow_root)); shadow_root .upcast::() @@ -3234,6 +3283,8 @@ impl ElementMethods for Element { IsUserAgentWidget::No, init.mode, init.clonable, + init.serializable, + init.delegatesFocus, init.slotAssignment, CanGc::note(), )?; diff --git a/components/script/dom/htmldetailselement.rs b/components/script/dom/htmldetailselement.rs index b6bcaf8a5bc..2a0f5b9d0dc 100644 --- a/components/script/dom/htmldetailselement.rs +++ b/components/script/dom/htmldetailselement.rs @@ -109,6 +109,8 @@ impl HTMLDetailsElement { IsUserAgentWidget::Yes, ShadowRootMode::Closed, false, + false, + false, SlotAssignmentMode::Manual, can_gc, ) diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index c68d225c6a3..49de8a3cc68 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -1898,6 +1898,8 @@ impl HTMLMediaElement { IsUserAgentWidget::Yes, ShadowRootMode::Closed, false, + false, + false, SlotAssignmentMode::Manual, can_gc, ) diff --git a/components/script/dom/htmlmeterelement.rs b/components/script/dom/htmlmeterelement.rs index c4726dafc61..2a72972a5b3 100644 --- a/components/script/dom/htmlmeterelement.rs +++ b/components/script/dom/htmlmeterelement.rs @@ -85,6 +85,8 @@ impl HTMLMeterElement { IsUserAgentWidget::Yes, ShadowRootMode::Closed, false, + false, + false, SlotAssignmentMode::Manual, can_gc, ) diff --git a/components/script/dom/htmlprogresselement.rs b/components/script/dom/htmlprogresselement.rs index 07e13f907f6..1dfd1111ce6 100644 --- a/components/script/dom/htmlprogresselement.rs +++ b/components/script/dom/htmlprogresselement.rs @@ -83,6 +83,8 @@ impl HTMLProgressElement { IsUserAgentWidget::Yes, ShadowRootMode::Closed, false, + false, + false, SlotAssignmentMode::Manual, can_gc, ) diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index e9bd8df07fa..2358fedc253 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -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 copy’s shadow root’s declarative to node’s shadow root’s declarative. + // Step 7.3 Set copy’s shadow root’s declarative to node’s shadow root’s declarative. + copy_shadow_root.set_declarative(shadow_root.is_declarative()); // Step 7.4 For each child child of node’s shadow root, in tree order: append the result of // cloning child with document and the clone children flag set, to copy’s shadow root. diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 64cff0ab71a..78eaedd50c9 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -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 = 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(), ) { diff --git a/components/script/dom/shadowroot.rs b/components/script/dom/shadowroot.rs index 39383493873..0266170f770 100644 --- a/components/script/dom/shadowroot.rs +++ b/components/script/dom/shadowroot.rs @@ -82,6 +82,12 @@ pub(crate) struct ShadowRoot { /// declarative: Cell, + + /// + serializable: Cell, + + /// + delegates_focus: Cell, } 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 for ShadowRoot { @@ -349,11 +373,21 @@ impl ShadowRootMethods for ShadowRoot { self.mode } + /// + fn DelegatesFocus(&self) -> bool { + self.delegates_focus.get() + } + /// fn Clonable(&self) -> bool { self.clonable } + /// + fn Serializable(&self) -> bool { + self.serializable.get() + } + /// fn Host(&self) -> DomRoot { let host = self.host.get(); diff --git a/components/script_bindings/webidls/Element.webidl b/components/script_bindings/webidls/Element.webidl index ba6d2d44204..83552e06ac9 100644 --- a/components/script_bindings/webidls/Element.webidl +++ b/components/script_bindings/webidls/Element.webidl @@ -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 diff --git a/components/script_bindings/webidls/ShadowRoot.webidl b/components/script_bindings/webidls/ShadowRoot.webidl index 4818c62fc20..af05ff8b891 100644 --- a/components/script_bindings/webidls/ShadowRoot.webidl +++ b/components/script_bindings/webidls/ShadowRoot.webidl @@ -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; }; diff --git a/tests/wpt/meta/custom-elements/element-internals-shadowroot.html.ini b/tests/wpt/meta/custom-elements/element-internals-shadowroot.html.ini deleted file mode 100644 index fb222786f6a..00000000000 --- a/tests/wpt/meta/custom-elements/element-internals-shadowroot.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[element-internals-shadowroot.html] - expected: ERROR - [ElementInternals.shadowRoot doesn't reveal pre-attached closed shadowRoot] - expected: FAIL diff --git a/tests/wpt/meta/dom/idlharness.window.js.ini b/tests/wpt/meta/dom/idlharness.window.js.ini index e50d8b5fb02..0e40be437e7 100644 --- a/tests/wpt/meta/dom/idlharness.window.js.ini +++ b/tests/wpt/meta/dom/idlharness.window.js.ini @@ -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] diff --git a/tests/wpt/meta/shadow-dom/Element-interface-attachShadow-custom-element.html.ini b/tests/wpt/meta/shadow-dom/Element-interface-attachShadow-custom-element.html.ini deleted file mode 100644 index aa68710fb42..00000000000 --- a/tests/wpt/meta/shadow-dom/Element-interface-attachShadow-custom-element.html.ini +++ /dev/null @@ -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 diff --git a/tests/wpt/meta/shadow-dom/Element-interface-attachShadow.html.ini b/tests/wpt/meta/shadow-dom/Element-interface-attachShadow.html.ini deleted file mode 100644 index fc87801c777..00000000000 --- a/tests/wpt/meta/shadow-dom/Element-interface-attachShadow.html.ini +++ /dev/null @@ -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 diff --git a/tests/wpt/meta/shadow-dom/declarative/declarative-shadow-dom-attachment.html.ini b/tests/wpt/meta/shadow-dom/declarative/declarative-shadow-dom-attachment.html.ini index 99619c27f10..117b3f75b0e 100644 --- a/tests/wpt/meta/shadow-dom/declarative/declarative-shadow-dom-attachment.html.ini +++ b/tests/wpt/meta/shadow-dom/declarative/declarative-shadow-dom-attachment.html.ini @@ -50,57 +50,6 @@ [Declarative Shadow DOM as a child of , with mode=open, delegatesFocus=false. Should be safelisted.] expected: FAIL - [Declarative Shadow DOM as a child of
, with mode=closed, delegatesFocus=false. Should be safelisted.] - expected: FAIL - - [Declarative Shadow DOM as a child of