Repository: servo/servo (language: python)

Issue:
Support ShadowDom
We have two existing issues

- https://github.com/servo/servo/issues/23590
- https://github.com/servo/servo/issues/23591

that are referring to an old spec that doesn't exist anymore. So I will close those so we can refocus here. 

The shadow DOM seems to be specified in various places of the DOM spec, such as:
- https://dom.spec.whatwg.org/#dom-element-attachshadow
- https://dom.spec.whatwg.org/#interface-shadowroot

-----------------

First question: what do we need to support the shadow dom? 

There has been some recent work such as https://github.com/servo/servo/pull/34306 and https://github.com/servo/servo/pull/34298, and it would be good to have a list of TODO's in one issue for work that remains to be done. 

For example, it appears to me that to support the shadow DOM, one needs to implement related concepts such as [`HTMLSlotElement`](https://html.spec.whatwg.org/multipage/scripting.html#the-slot-element) and [`shadow-tree-slots`](https://dom.spec.whatwg.org/#shadow-tree-slots). Since this is a meta issue, this could be the place to list all the various elements(pun intended) needed to implement shadow DOM, and supplement this with standalone issues for individual elements. 

There are also other concepts that rely on the shadow DOM, such as [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components), and we could list them here as well(if we plan on implementing them). 


Discussion / hints:

Fix patch:
diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs
index 75f8f4d4b20a..594c63bbab6a 100644
--- a/components/script/dom/element.rs
+++ b/components/script/dom/element.rs
@@ -507,8 +507,10 @@ impl Element {
     /// <https://dom.spec.whatwg.org/#dom-element-attachshadow>
     pub fn attach_shadow(
         &self,
+        // TODO: remove is_ua_widget argument
         is_ua_widget: IsUserAgentWidget,
         mode: ShadowRootMode,
+        clonable: bool,
     ) -> Fallible<DomRoot<ShadowRoot>> {
         // Step 1.
         if self.namespace != ns!(html) {
@@ -546,7 +548,7 @@ impl Element {
         }
 
         // Steps 4, 5 and 6.
-        let shadow_root = ShadowRoot::new(self, &self.node.owner_doc(), mode);
+        let shadow_root = ShadowRoot::new(self, &self.node.owner_doc(), mode, clonable);
         self.ensure_rare_data().shadow_root = Some(Dom::from_ref(&*shadow_root));
         shadow_root
             .upcast::<Node>()
@@ -3034,7 +3036,7 @@ 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)?;
+        let shadow_root = self.attach_shadow(IsUserAgentWidget::No, init.mode, init.clonable)?;
 
         // Step 2. Return this’s shadow root.
         Ok(shadow_root)
diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs
index e7b2fdbb30e0..18bca9decee0 100644
--- a/components/script/dom/htmlmediaelement.rs
+++ b/components/script/dom/htmlmediaelement.rs
@@ -1910,7 +1910,7 @@ impl HTMLMediaElement {
             return;
         }
         let shadow_root = element
-            .attach_shadow(IsUserAgentWidget::Yes, ShadowRootMode::Closed)
+            .attach_shadow(IsUserAgentWidget::Yes, ShadowRootMode::Closed, false)
             .unwrap();
         let document = document_from_node(self);
         let script = HTMLScriptElement::new(
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
index 00501ea17187..3c7df12b6334 100644
--- a/components/script/dom/node.rs
+++ b/components/script/dom/node.rs
@@ -98,7 +98,7 @@ use crate::dom::nodelist::NodeList;
 use crate::dom::processinginstruction::ProcessingInstruction;
 use crate::dom::range::WeakRangeVec;
 use crate::dom::raredata::NodeRareData;
-use crate::dom::shadowroot::{LayoutShadowRootHelpers, ShadowRoot};
+use crate::dom::shadowroot::{IsUserAgentWidget, LayoutShadowRootHelpers, ShadowRoot};
 use crate::dom::stylesheetlist::StyleSheetListOwner;
 use crate::dom::svgsvgelement::{LayoutSVGSVGElementHelpers, SVGSVGElement};
 use crate::dom::text::Text;
@@ -2247,13 +2247,13 @@ impl Node {
         clone_children: CloneChildrenFlag,
         can_gc: CanGc,
     ) -> DomRoot<Node> {
-        // Step 1.
+        // Step 1. If document is not given, let document be node’s node document.
         let document = match maybe_doc {
             Some(doc) => DomRoot::from_ref(doc),
             None => node.owner_doc(),
         };
 
-        // Step 2.
+        // Step 2. / Step 3.
         // XXXabinader: clone() for each node as trait?
         let copy: DomRoot<Node> = match node.type_id() {
             NodeTypeId::DocumentType => {
@@ -2337,14 +2337,15 @@ impl Node {
             },
         };
 
-        // Step 3.
+        // Step 4. Set copy’s node document and document to copy, if copy is a document,
+        // and set copy’s node document to document otherwise.
         let document = match copy.downcast::<Document>() {
             Some(doc) => DomRoot::from_ref(doc),
             None => DomRoot::from_ref(&*document),
         };
         assert!(copy.owner_doc() == document);
 
-        // Step 4 (some data already copied in step 2).
+        // TODO: The spec tells us to do this in step 3.
         match node.type_id() {
             NodeTypeId::Document(_) => {
                 let node_doc = node.downcast::<Document>().unwrap();
@@ -2370,10 +2371,12 @@ impl Node {
             _ => (),
         }
 
-        // Step 5: cloning steps.
+        // Step 5: Run any cloning steps defined for node in other applicable specifications and pass copy,
+        // node, document, and the clone children flag if set, as parameters.
         vtable_for(node).cloning_steps(&copy, maybe_doc, clone_children);
 
-        // Step 6.
+        // Step 6. If the clone children flag is set, then for each child child of node, in tree order: append the
+        // result of cloning child with document and the clone children flag set, to copy.
         if clone_children == CloneChildrenFlag::CloneChildren {
             for child in node.children() {
                 let child_copy = Node::clone(&child, Some(&document), clone_children, can_gc);
@@ -2381,7 +2384,43 @@ impl Node {
             }
         }
 
-        // Step 7.
+        // Step 7. If node is a shadow host whose shadow root’s clonable is true:
+        // NOTE: Only elements can be shadow hosts
+        if matches!(node.type_id(), NodeTypeId::Element(_)) {
+            let node_elem = node.downcast::<Element>().unwrap();
+            let copy_elem = copy.downcast::<Element>().unwrap();
+
+            if let Some(shadow_root) = node_elem.shadow_root().filter(|r| r.Clonable()) {
+                // Step 7.1 Assert: copy is not a shadow host.
+                assert!(!copy_elem.is_shadow_host());
+
+                // Step 7.2 Run attach a shadow root with copy, node’s shadow root’s mode, true,
+                // node’s shadow root’s serializable, node’s shadow root’s delegates focus,
+                // and node’s shadow root’s slot assignment.
+                let copy_shadow_root =
+                    copy_elem.attach_shadow(IsUserAgentWidget::No, shadow_root.Mode(), true)
+                    .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.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.
+                for child in shadow_root.upcast::<Node>().children() {
+                    let child_copy = Node::clone(
+                        &child,
+                        Some(&document),
+                        CloneChildrenFlag::CloneChildren,
+                        can_gc,
+                    );
+
+                    // TODO: Should we handle the error case here and in step 6?
+                    let _inserted_node =
+                        Node::pre_insert(&child_copy, copy_shadow_root.upcast::<Node>(), None);
+                }
+            }
+        }
+
+        // Step 8. Return copy.
         copy
     }
 
diff --git a/components/script/dom/shadowroot.rs b/components/script/dom/shadowroot.rs
index da918ec0f516..77c4ba99b2a0 100644
--- a/components/script/dom/shadowroot.rs
+++ b/components/script/dom/shadowroot.rs
@@ -55,11 +55,19 @@ pub struct ShadowRoot {
 
     /// <https://dom.spec.whatwg.org/#dom-shadowroot-mode>
     mode: ShadowRootMode,
+
+    /// <https://dom.spec.whatwg.org/#dom-shadowroot-clonable>
+    clonable: bool,
 }
 
 impl ShadowRoot {
     #[allow(crown::unrooted_must_root)]
-    fn new_inherited(host: &Element, document: &Document, mode: ShadowRootMode) -> ShadowRoot {
+    fn new_inherited(
+        host: &Element,
+        document: &Document,
+        mode: ShadowRootMode,
+        clonable: bool,
+    ) -> ShadowRoot {
         let document_fragment = DocumentFragment::new_inherited(document);
         let node = document_fragment.upcast::<Node>();
         node.set_flag(NodeFlags::IS_IN_SHADOW_TREE, true);
@@ -77,12 +85,18 @@ impl ShadowRoot {
             stylesheet_list: MutNullableDom::new(None),
             window: Dom::from_ref(document.window()),
             mode,
+            clonable,
         }
     }
 
-    pub fn new(host: &Element, document: &Document, mode: ShadowRootMode) -> DomRoot<ShadowRoot> {
+    pub fn new(
+        host: &Element,
+        document: &Document,
+        mode: ShadowRootMode,
+        clonable: bool,
+    ) -> DomRoot<ShadowRoot> {
         reflect_dom_object(
-            Box::new(ShadowRoot::new_inherited(host, document, mode)),
+            Box::new(ShadowRoot::new_inherited(host, document, mode, clonable)),
             document.window(),
         )
     }
@@ -238,6 +252,11 @@ impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot {
         self.mode
     }
 
+    /// <https://dom.spec.whatwg.org/#dom-shadowroot-clonable>
+    fn Clonable(&self) -> bool {
+        self.clonable
+    }
+
     /// <https://dom.spec.whatwg.org/#dom-shadowroot-host>
     fn Host(&self) -> DomRoot<Element> {
         let host = self.host.get();
diff --git a/components/script/dom/webidls/Element.webidl b/components/script/dom/webidls/Element.webidl
index 99d17117221d..1a99d5ec4802 100644
--- a/components/script/dom/webidls/Element.webidl
+++ b/components/script/dom/webidls/Element.webidl
@@ -91,7 +91,7 @@ dictionary ShadowRootInit {
   required ShadowRootMode mode;
   // boolean delegatesFocus = false;
   // SlotAssignmentMode slotAssignment = "named";
-  // boolean clonable = false;
+  boolean clonable = false;
   // boolean serializable = false;
 };
 
diff --git a/components/script/dom/webidls/ShadowRoot.webidl b/components/script/dom/webidls/ShadowRoot.webidl
index a1ecdc5c10fd..40301f48517b 100644
--- a/components/script/dom/webidls/ShadowRoot.webidl
+++ b/components/script/dom/webidls/ShadowRoot.webidl
@@ -9,9 +9,15 @@
 [Exposed=Window]
 interface ShadowRoot : DocumentFragment {
   readonly attribute ShadowRootMode mode;
+  // readonly attribute boolean delegatesFocus;
+  // readonly attribute SlotAssignmentMode slotAssignment;
+  readonly attribute boolean clonable;
+  // readonly attribute boolean serializable;
   readonly attribute Element host;
+  // attribute EventHandler onslotchange;
 };
 
+
 enum ShadowRootMode { "open", "closed"};
 // enum SlotAssignmentMode { "manual", "named" };
 


Test patch:
diff --git a/tests/wpt/meta/dom/idlharness.window.js.ini b/tests/wpt/meta/dom/idlharness.window.js.ini
index 7bca20fc4407..b07aaa607843 100644
--- a/tests/wpt/meta/dom/idlharness.window.js.ini
+++ b/tests/wpt/meta/dom/idlharness.window.js.ini
@@ -694,9 +694,6 @@
   [AbortSignal interface: calling any(sequence<AbortSignal>) on new AbortController().signal with too few arguments must throw TypeError]
     expected: FAIL
 
-  [ShadowRoot interface: attribute clonable]
-    expected: FAIL
-
   [Document interface: operation prepend((Node or TrustedScript or DOMString)...)]
     expected: FAIL
 
diff --git a/tests/wpt/meta/shadow-dom/shadow-root-clonable.html.ini b/tests/wpt/meta/shadow-dom/shadow-root-clonable.html.ini
index b2033ddfecea..9aa211bbff2d 100644
--- a/tests/wpt/meta/shadow-dom/shadow-root-clonable.html.ini
+++ b/tests/wpt/meta/shadow-dom/shadow-root-clonable.html.ini
@@ -1,13 +1,4 @@
 [shadow-root-clonable.html]
-  [attachShadow with clonable: true]
-    expected: FAIL
-
-  [attachShadow with clonable: false]
-    expected: FAIL
-
-  [attachShadow with clonable: undefined]
-    expected: FAIL
-
   [declarative shadow roots do *not* get clonable: true automatically]
     expected: FAIL