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.

(cherry picked from commit 043ad0eb7644a529c1ffbd4de9a4b89771808c84,
and amended to make `is_valid_shadow_host_name()` static.
This commit is contained in:
Andreas Kling 2024-06-25 09:42:51 +02:00 committed by Nico Weber
parent 3a2260b28b
commit 34a4b28b89
4 changed files with 95 additions and 26 deletions

View file

@ -593,25 +593,33 @@ DOMTokenList* Element::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
static bool is_valid_shadow_host_name(FlyString const& name)
{
// 1. If thiss namespace is not the HTML namespace, then throw a "NotSupportedError" DOMException.
// 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 elements namespace is not the HTML namespace, then throw a "NotSupportedError" DOMException.
if (namespace_uri() != Namespace::HTML)
return WebIDL::NotSupportedError::create(realm(), "Element's namespace is not the HTML namespace"_fly_string);
// 2. If thiss 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 elements 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 thiss local name is a valid custom element name, or thiss is value is not null, then:
// 3. If elements local name is a valid custom element name, or elements is value is not null, then:
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 thiss 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 elements 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);
// 2. If definition is not null and definitions 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);
}
// 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 elements shadow root.
auto current_shadow_root = shadow_root();
// 5. Let shadow be a new shadow root whose node document is thiss node document, host is this, and mode is init["mode"].
auto shadow = heap().allocate<ShadowRoot>(realm(), document(), *this, init.mode);
// 2. If any of the following are true:
// - currentShadowRoots declarative is false; or
// - currentShadowRoots 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);
}
// 6. Set shadows delegates focus to init["delegatesFocus"].
shadow->set_delegates_focus(init.delegates_focus);
// 3. Otherwise:
// 1. Remove all of currentShadowRoots children, in tree order.
current_shadow_root->remove_all_children();
// 7. If thiss custom element state is "precustomized" or "custom", then set shadows available to element internals to true.
// 2. Set currentShadowRoots declarative to false.
current_shadow_root->set_declarative(false);
// 3. Return.
return {};
}
// 5. Let shadow be a new shadow root whose node document is elements node document, host is this, and mode is mode.
auto shadow = heap().allocate<ShadowRoot>(realm(), document(), *this, mode);
// 6. Set shadows delegates focus to delegatesFocus".
shadow->set_delegates_focus(delegates_focus);
// 7. If elements custom element state is "precustomized" or "custom", then set shadows available to element internals to true.
if (m_custom_element_state == CustomElementState::Precustomized || m_custom_element_state == CustomElementState::Custom)
shadow->set_available_to_element_internals(true);
// 8. Set shadows slot assignment to init["slotAssignment"].
shadow->set_slot_assignment(init.slot_assignment);
// 8. Set shadows slot assignment to slotAssignment.
shadow->set_slot_assignment(slot_assignment);
// 9. Set thiss shadow root to shadow.
// 9. Set shadows declarative to false.
shadow->set_declarative(false);
// 10. Set shadows clonable to clonable.
shadow->set_clonable(clonable);
// 11. Set shadows serializable to serializable.
shadow->set_serializable(serializable);
// 12. Set elements shadow root to 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 thiss shadow root.
return JS::NonnullGCPtr { *shadow_root() };
}
// https://dom.spec.whatwg.org/#dom-element-shadowroot

View file

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

View file

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

View file

@ -25,6 +25,15 @@ public:
bool delegates_focus() const { return m_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*);
WebIDL::CallbackType* onslotchange();
@ -68,6 +77,15 @@ private:
bool m_delegates_focus { 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;
mutable JS::GCPtr<WebIDL::ObservableArray> m_adopted_style_sheets;
};