Browse Source

LibWeb: Refactor Element.attachShadow() after spec changes

The bulk of this function is moved to a new "attach a shadow root"
helper, designed to be used both from here and the HTML parser.
Andreas Kling 1 year ago
parent
commit
043ad0eb76

+ 72 - 26
Userland/Libraries/LibWeb/DOM/Element.cpp

@@ -593,25 +593,33 @@ DOMTokenList* Element::class_list()
     return m_class_list;
     return m_class_list;
 }
 }
 
 
-// https://dom.spec.whatwg.org/#dom-element-attachshadow
-WebIDL::ExceptionOr<JS::NonnullGCPtr<ShadowRoot>> Element::attach_shadow(ShadowRootInit init)
+// https://dom.spec.whatwg.org/#valid-shadow-host-name
+bool is_valid_shadow_host_name(FlyString const& name)
+{
+    // A valid shadow host name is:
+    // - a valid custom element name
+    // - "article", "aside", "blockquote", "body", "div", "footer", "h1", "h2", "h3", "h4", "h5", "h6", "header", "main", "nav", "p", "section", or "span"
+    if (!HTML::is_valid_custom_element_name(name)
+        && !name.is_one_of("article", "aside", "blockquote", "body", "div", "footer", "h1", "h2", "h3", "h4", "h5", "h6", "header", "main", "nav", "p", "section", "span")) {
+        return false;
+    }
+    return true;
+}
+
+// https://dom.spec.whatwg.org/#concept-attach-a-shadow-root
+WebIDL::ExceptionOr<void> Element::attach_a_shadow_root(Bindings::ShadowRootMode mode, bool clonable, bool serializable, bool delegates_focus, Bindings::SlotAssignmentMode slot_assignment)
 {
 {
-    // 1. If this’s namespace is not the HTML namespace, then throw a "NotSupportedError" DOMException.
+    // 1. If element’s namespace is not the HTML namespace, then throw a "NotSupportedError" DOMException.
     if (namespace_uri() != Namespace::HTML)
     if (namespace_uri() != Namespace::HTML)
         return WebIDL::NotSupportedError::create(realm(), "Element's namespace is not the HTML namespace"_fly_string);
         return WebIDL::NotSupportedError::create(realm(), "Element's namespace is not the HTML namespace"_fly_string);
 
 
-    // 2. If this’s local name is not one of the following:
-    //    - a valid custom element name
-    //    - "article", "aside", "blockquote", "body", "div", "footer", "h1", "h2", "h3", "h4", "h5", "h6", "header", "main", "nav", "p", "section", or "span"
-    if (!HTML::is_valid_custom_element_name(local_name())
-        && !local_name().is_one_of("article", "aside", "blockquote", "body", "div", "footer", "h1", "h2", "h3", "h4", "h5", "h6", "header", "main", "nav", "p", "section", "span")) {
-        // then throw a "NotSupportedError" DOMException.
-        return WebIDL::NotSupportedError::create(realm(), MUST(String::formatted("Element '{}' cannot be a shadow host", local_name())));
-    }
+    // 2. If element’s local name is not a valid shadow host name, then throw a "NotSupportedError" DOMException.
+    if (!is_valid_shadow_host_name(local_name()))
+        return WebIDL::NotSupportedError::create(realm(), "Element's local name is not a valid shadow host name"_fly_string);
 
 
-    // 3. If this’s local name is a valid custom element name, or this’s is value is not null, then:
+    // 3. If element’s local name is a valid custom element name, or element’s is value is not null, then:
     if (HTML::is_valid_custom_element_name(local_name()) || m_is_value.has_value()) {
     if (HTML::is_valid_custom_element_name(local_name()) || m_is_value.has_value()) {
-        // 1. Let definition be the result of looking up a custom element definition given this’s node document, its namespace, its local name, and its is value.
+        // 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.
         auto definition = document().lookup_custom_element_definition(namespace_uri(), local_name(), m_is_value);
         auto definition = document().lookup_custom_element_definition(namespace_uri(), local_name(), m_is_value);
 
 
         // 2. If definition is not null and definition’s disable shadow is true, then throw a "NotSupportedError" DOMException.
         // 2. If definition is not null and definition’s disable shadow is true, then throw a "NotSupportedError" DOMException.
@@ -619,28 +627,66 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<ShadowRoot>> Element::attach_shadow(ShadowR
             return WebIDL::NotSupportedError::create(realm(), "Cannot attach a shadow root to a custom element that has disabled shadow roots"_fly_string);
             return WebIDL::NotSupportedError::create(realm(), "Cannot attach a shadow root to a custom element that has disabled shadow roots"_fly_string);
     }
     }
 
 
-    // 4. If this is a shadow host, then throw an "NotSupportedError" DOMException.
-    if (is_shadow_host())
-        return WebIDL::NotSupportedError::create(realm(), "Element already is a shadow host"_fly_string);
+    // 4. If element is a shadow host, then:
+    if (is_shadow_host()) {
+        // 1. Let currentShadowRoot be element’s shadow root.
+        auto current_shadow_root = shadow_root();
+
+        // 2. If any of the following are true:
+        // - currentShadowRoot’s declarative is false; or
+        // - currentShadowRoot’s mode is not mode,
+        // then throw a "NotSupportedError" DOMException.
+        if (!current_shadow_root->declarative() || current_shadow_root->mode() != mode) {
+            return WebIDL::NotSupportedError::create(realm(), "Element already is a shadow host"_fly_string);
+        }
+
+        // 3. Otherwise:
+        //    1. Remove all of currentShadowRoot’s children, in tree order.
+        current_shadow_root->remove_all_children();
+
+        //    2. Set currentShadowRoot’s declarative to false.
+        current_shadow_root->set_declarative(false);
 
 
-    // 5. Let shadow be a new shadow root whose node document is this’s node document, host is this, and mode is init["mode"].
-    auto shadow = heap().allocate<ShadowRoot>(realm(), document(), *this, init.mode);
+        //    3. Return.
+        return {};
+    }
 
 
-    // 6. Set shadow’s delegates focus to init["delegatesFocus"].
-    shadow->set_delegates_focus(init.delegates_focus);
+    // 5. Let shadow be a new shadow root whose node document is element’s node document, host is this, and mode is mode.
+    auto shadow = heap().allocate<ShadowRoot>(realm(), document(), *this, mode);
 
 
-    // 7. If this’s custom element state is "precustomized" or "custom", then set shadow’s available to element internals to true.
+    // 6. Set shadow’s delegates focus to delegatesFocus".
+    shadow->set_delegates_focus(delegates_focus);
+
+    // 7. If element’s custom element state is "precustomized" or "custom", then set shadow’s available to element internals to true.
     if (m_custom_element_state == CustomElementState::Precustomized || m_custom_element_state == CustomElementState::Custom)
     if (m_custom_element_state == CustomElementState::Precustomized || m_custom_element_state == CustomElementState::Custom)
         shadow->set_available_to_element_internals(true);
         shadow->set_available_to_element_internals(true);
 
 
-    // 8. Set shadow’s slot assignment to init["slotAssignment"].
-    shadow->set_slot_assignment(init.slot_assignment);
+    // 8. Set shadow’s slot assignment to slotAssignment.
+    shadow->set_slot_assignment(slot_assignment);
+
+    // 9. Set shadow’s declarative to false.
+    shadow->set_declarative(false);
 
 
-    // 9. Set this’s shadow root to shadow.
+    // 10. Set shadow’s clonable to clonable.
+    shadow->set_clonable(clonable);
+
+    // 11. Set shadow’s serializable to serializable.
+    shadow->set_serializable(serializable);
+
+    // 12. Set element’s shadow root to shadow.
     set_shadow_root(shadow);
     set_shadow_root(shadow);
 
 
-    // 10. Return shadow.
-    return shadow;
+    return {};
+}
+
+// https://dom.spec.whatwg.org/#dom-element-attachshadow
+WebIDL::ExceptionOr<JS::NonnullGCPtr<ShadowRoot>> Element::attach_shadow(ShadowRootInit init)
+{
+    // 1. Run attach a shadow root with this, init["mode"], init["clonable"], init["serializable"], init["delegatesFocus"], and init["slotAssignment"].
+    TRY(attach_a_shadow_root(init.mode, init.clonable, init.serializable, init.delegates_focus, init.slot_assignment));
+
+    // 2. Return this’s shadow root.
+    return JS::NonnullGCPtr { *shadow_root() };
 }
 }
 
 
 // https://dom.spec.whatwg.org/#dom-element-shadowroot
 // https://dom.spec.whatwg.org/#dom-element-shadowroot

+ 3 - 0
Userland/Libraries/LibWeb/DOM/Element.h

@@ -33,6 +33,8 @@ struct ShadowRootInit {
     Bindings::ShadowRootMode mode;
     Bindings::ShadowRootMode mode;
     bool delegates_focus = false;
     bool delegates_focus = false;
     Bindings::SlotAssignmentMode slot_assignment { Bindings::SlotAssignmentMode::Named };
     Bindings::SlotAssignmentMode slot_assignment { Bindings::SlotAssignmentMode::Named };
+    bool clonable = false;
+    bool serializable = false;
 };
 };
 
 
 // https://w3c.github.io/csswg-drafts/cssom-view-1/#dictdef-scrollintoviewoptions
 // https://w3c.github.io/csswg-drafts/cssom-view-1/#dictdef-scrollintoviewoptions
@@ -126,6 +128,7 @@ public:
     DOMTokenList* class_list();
     DOMTokenList* class_list();
 
 
     WebIDL::ExceptionOr<JS::NonnullGCPtr<ShadowRoot>> attach_shadow(ShadowRootInit init);
     WebIDL::ExceptionOr<JS::NonnullGCPtr<ShadowRoot>> attach_shadow(ShadowRootInit init);
+    WebIDL::ExceptionOr<void> attach_a_shadow_root(Bindings::ShadowRootMode mode, bool clonable, bool serializable, bool delegates_focus, Bindings::SlotAssignmentMode slot_assignment);
     JS::GCPtr<ShadowRoot> shadow_root() const;
     JS::GCPtr<ShadowRoot> shadow_root() const;
 
 
     WebIDL::ExceptionOr<bool> matches(StringView selectors) const;
     WebIDL::ExceptionOr<bool> matches(StringView selectors) const;

+ 2 - 0
Userland/Libraries/LibWeb/DOM/Element.idl

@@ -104,6 +104,8 @@ dictionary ShadowRootInit {
     required ShadowRootMode mode;
     required ShadowRootMode mode;
     boolean delegatesFocus = false;
     boolean delegatesFocus = false;
     SlotAssignmentMode slotAssignment = "named";
     SlotAssignmentMode slotAssignment = "named";
+    boolean clonable = false;
+    boolean serializable = false;
 };
 };
 
 
 Element includes ParentNode;
 Element includes ParentNode;

+ 18 - 0
Userland/Libraries/LibWeb/DOM/ShadowRoot.h

@@ -25,6 +25,15 @@ public:
     bool delegates_focus() const { return m_delegates_focus; }
     bool delegates_focus() const { return m_delegates_focus; }
     void set_delegates_focus(bool delegates_focus) { m_delegates_focus = delegates_focus; }
     void set_delegates_focus(bool delegates_focus) { m_delegates_focus = delegates_focus; }
 
 
+    [[nodiscard]] bool declarative() const { return m_declarative; }
+    void set_declarative(bool declarative) { m_declarative = declarative; }
+
+    [[nodiscard]] bool clonable() const { return m_clonable; }
+    void set_clonable(bool clonable) { m_clonable = clonable; }
+
+    [[nodiscard]] bool serializable() const { return m_serializable; }
+    void set_serializable(bool serializable) { m_serializable = serializable; }
+
     void set_onslotchange(WebIDL::CallbackType*);
     void set_onslotchange(WebIDL::CallbackType*);
     WebIDL::CallbackType* onslotchange();
     WebIDL::CallbackType* onslotchange();
 
 
@@ -68,6 +77,15 @@ private:
     bool m_delegates_focus { false };
     bool m_delegates_focus { false };
     bool m_available_to_element_internals { false };
     bool m_available_to_element_internals { false };
 
 
+    // https://dom.spec.whatwg.org/#shadowroot-declarative
+    bool m_declarative { false };
+
+    // https://dom.spec.whatwg.org/#shadowroot-clonable
+    bool m_clonable { false };
+
+    // https://dom.spec.whatwg.org/#shadowroot-serializable
+    bool m_serializable { false };
+
     JS::GCPtr<CSS::StyleSheetList> m_style_sheets;
     JS::GCPtr<CSS::StyleSheetList> m_style_sheets;
     mutable JS::GCPtr<WebIDL::ObservableArray> m_adopted_style_sheets;
     mutable JS::GCPtr<WebIDL::ObservableArray> m_adopted_style_sheets;
 };
 };