/* * Copyright (c) 2018-2024, Andreas Kling * Copyright (c) 2022-2023, San Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Web::DOM { Element::Element(Document& document, DOM::QualifiedName qualified_name) : ParentNode(document, NodeType::ELEMENT_NODE) , m_qualified_name(move(qualified_name)) { make_html_uppercased_qualified_name(); } Element::~Element() = default; void Element::initialize(JS::Realm& realm) { Base::initialize(realm); WEB_SET_PROTOTYPE_FOR_INTERFACE(Element); m_attributes = NamedNodeMap::create(*this); } void Element::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); SlottableMixin::visit_edges(visitor); Animatable::visit_edges(visitor); visitor.visit(m_attributes); visitor.visit(m_inline_style); visitor.visit(m_class_list); visitor.visit(m_shadow_root); visitor.visit(m_custom_element_definition); if (m_pseudo_element_nodes) { visitor.visit(m_pseudo_element_nodes->span()); } if (m_registered_intersection_observers) { for (auto& registered_intersection_observers : *m_registered_intersection_observers) visitor.visit(registered_intersection_observers.observer); } } // https://dom.spec.whatwg.org/#dom-element-getattribute Optional Element::get_attribute(FlyString const& name) const { // 1. Let attr be the result of getting an attribute given qualifiedName and this. auto const* attribute = m_attributes->get_attribute(name); // 2. If attr is null, return null. if (!attribute) return {}; // 3. Return attr’s value. return attribute->value(); } // https://dom.spec.whatwg.org/#dom-element-getattributens Optional Element::get_attribute_ns(Optional const& namespace_, FlyString const& name) const { // 1. Let attr be the result of getting an attribute given namespace, localName, and this. auto const* attribute = m_attributes->get_attribute_ns(namespace_, name); // 2. If attr is null, return null. if (!attribute) return {}; // 3. Return attr’s value. return attribute->value(); } // https://dom.spec.whatwg.org/#concept-element-attributes-get-value String Element::get_attribute_value(FlyString const& local_name, Optional const& namespace_) const { // 1. Let attr be the result of getting an attribute given namespace, localName, and element. auto const* attribute = m_attributes->get_attribute_ns(namespace_, local_name); // 2. If attr is null, then return the empty string. if (!attribute) return String {}; // 3. Return attr’s value. return attribute->value(); } // https://dom.spec.whatwg.org/#dom-element-getattributenode JS::GCPtr Element::get_attribute_node(FlyString const& name) const { // The getAttributeNode(qualifiedName) method steps are to return the result of getting an attribute given qualifiedName and this. return m_attributes->get_attribute(name); } // https://dom.spec.whatwg.org/#dom-element-getattributenodens JS::GCPtr Element::get_attribute_node_ns(Optional const& namespace_, FlyString const& name) const { // The getAttributeNodeNS(namespace, localName) method steps are to return the result of getting an attribute given namespace, localName, and this. return m_attributes->get_attribute_ns(namespace_, name); } // https://dom.spec.whatwg.org/#dom-element-setattribute WebIDL::ExceptionOr Element::set_attribute(FlyString const& name, String const& value) { // 1. If qualifiedName does not match the Name production in XML, then throw an "InvalidCharacterError" DOMException. if (!Document::is_valid_name(name.to_string())) return WebIDL::InvalidCharacterError::create(realm(), "Attribute name must not be empty or contain invalid characters"_fly_string); // 2. If this is in the HTML namespace and its node document is an HTML document, then set qualifiedName to qualifiedName in ASCII lowercase. bool insert_as_lowercase = namespace_uri() == Namespace::HTML && document().document_type() == Document::Type::HTML; // 3. Let attribute be the first attribute in this’s attribute list whose qualified name is qualifiedName, and null otherwise. auto* attribute = m_attributes->get_attribute(name); // 4. If attribute is null, create an attribute whose local name is qualifiedName, value is value, and node document // is this’s node document, then append this attribute to this, and then return. if (!attribute) { auto new_attribute = Attr::create(document(), insert_as_lowercase ? MUST(Infra::to_ascii_lowercase(name)) : name, value); m_attributes->append_attribute(new_attribute); return {}; } // 5. Change attribute to value. attribute->change_attribute(value); return {}; } // https://dom.spec.whatwg.org/#validate-and-extract WebIDL::ExceptionOr validate_and_extract(JS::Realm& realm, Optional namespace_, FlyString const& qualified_name) { // 1. If namespace is the empty string, then set it to null. if (namespace_.has_value() && namespace_.value().is_empty()) namespace_ = {}; // 2. Validate qualifiedName. TRY(Document::validate_qualified_name(realm, qualified_name)); // 3. Let prefix be null. Optional prefix = {}; // 4. Let localName be qualifiedName. auto local_name = qualified_name; // 5. If qualifiedName contains a U+003A (:), then strictly split the string on it and set prefix to the part before and localName to the part after. if (qualified_name.bytes_as_string_view().contains(':')) { auto parts = qualified_name.bytes_as_string_view().split_view(':'); prefix = MUST(FlyString::from_utf8(parts[0])); local_name = MUST(FlyString::from_utf8(parts[1])); } // 6. If prefix is non-null and namespace is null, then throw a "NamespaceError" DOMException. if (prefix.has_value() && !namespace_.has_value()) return WebIDL::NamespaceError::create(realm, "Prefix is non-null and namespace is null."_fly_string); // 7. If prefix is "xml" and namespace is not the XML namespace, then throw a "NamespaceError" DOMException. if (prefix == "xml"sv && namespace_ != Namespace::XML) return WebIDL::NamespaceError::create(realm, "Prefix is 'xml' and namespace is not the XML namespace."_fly_string); // 8. If either qualifiedName or prefix is "xmlns" and namespace is not the XMLNS namespace, then throw a "NamespaceError" DOMException. if ((qualified_name == "xmlns"sv || prefix == "xmlns"sv) && namespace_ != Namespace::XMLNS) return WebIDL::NamespaceError::create(realm, "Either qualifiedName or prefix is 'xmlns' and namespace is not the XMLNS namespace."_fly_string); // 9. If namespace is the XMLNS namespace and neither qualifiedName nor prefix is "xmlns", then throw a "NamespaceError" DOMException. if (namespace_ == Namespace::XMLNS && !(qualified_name == "xmlns"sv || prefix == "xmlns"sv)) return WebIDL::NamespaceError::create(realm, "Namespace is the XMLNS namespace and neither qualifiedName nor prefix is 'xmlns'."_fly_string); // 10. Return namespace, prefix, and localName. return QualifiedName { local_name, prefix, namespace_ }; } // https://dom.spec.whatwg.org/#dom-element-setattributens WebIDL::ExceptionOr Element::set_attribute_ns(Optional const& namespace_, FlyString const& qualified_name, String const& value) { // 1. Let namespace, prefix, and localName be the result of passing namespace and qualifiedName to validate and extract. auto extracted_qualified_name = TRY(validate_and_extract(realm(), namespace_, qualified_name)); // 2. Set an attribute value for this using localName, value, and also prefix and namespace. set_attribute_value(extracted_qualified_name.local_name(), value, extracted_qualified_name.prefix(), extracted_qualified_name.namespace_()); return {}; } // https://dom.spec.whatwg.org/#concept-element-attributes-append void Element::append_attribute(FlyString const& name, String const& value) { m_attributes->append_attribute(Attr::create(document(), name, value)); } // https://dom.spec.whatwg.org/#concept-element-attributes-append void Element::append_attribute(Attr& attribute) { m_attributes->append_attribute(attribute); } // https://dom.spec.whatwg.org/#concept-element-attributes-set-value void Element::set_attribute_value(FlyString const& local_name, String const& value, Optional const& prefix, Optional const& namespace_) { // 1. Let attribute be the result of getting an attribute given namespace, localName, and element. auto* attribute = m_attributes->get_attribute_ns(namespace_, local_name); // 2. If attribute is null, create an attribute whose namespace is namespace, namespace prefix is prefix, local name // is localName, value is value, and node document is element’s node document, then append this attribute to element, // and then return. if (!attribute) { QualifiedName name { local_name, prefix, namespace_ }; auto new_attribute = Attr::create(document(), move(name), value); m_attributes->append_attribute(new_attribute); return; } // 3. Change attribute to value. attribute->change_attribute(value); } // https://dom.spec.whatwg.org/#dom-element-setattributenode WebIDL::ExceptionOr> Element::set_attribute_node(Attr& attr) { // The setAttributeNode(attr) and setAttributeNodeNS(attr) methods steps are to return the result of setting an attribute given attr and this. return m_attributes->set_attribute(attr); } // https://dom.spec.whatwg.org/#dom-element-setattributenodens WebIDL::ExceptionOr> Element::set_attribute_node_ns(Attr& attr) { // The setAttributeNode(attr) and setAttributeNodeNS(attr) methods steps are to return the result of setting an attribute given attr and this. return m_attributes->set_attribute(attr); } // https://dom.spec.whatwg.org/#dom-element-removeattribute void Element::remove_attribute(FlyString const& name) { // The removeAttribute(qualifiedName) method steps are to remove an attribute given qualifiedName and this, and then return undefined. m_attributes->remove_attribute(name); } // https://dom.spec.whatwg.org/#dom-element-removeattributens void Element::remove_attribute_ns(Optional const& namespace_, FlyString const& name) { // The removeAttributeNS(namespace, localName) method steps are to remove an attribute given namespace, localName, and this, and then return undefined. m_attributes->remove_attribute_ns(namespace_, name); } // https://dom.spec.whatwg.org/#dom-element-removeattributenode WebIDL::ExceptionOr> Element::remove_attribute_node(JS::NonnullGCPtr attr) { return m_attributes->remove_attribute_node(attr); } // https://dom.spec.whatwg.org/#dom-element-hasattribute bool Element::has_attribute(FlyString const& name) const { return m_attributes->get_attribute(name) != nullptr; } // https://dom.spec.whatwg.org/#dom-element-hasattributens bool Element::has_attribute_ns(Optional const& namespace_, FlyString const& name) const { // 1. If namespace is the empty string, then set it to null. // 2. Return true if this has an attribute whose namespace is namespace and local name is localName; otherwise false. if (namespace_ == FlyString {}) return m_attributes->get_attribute_ns(OptionalNone {}, name) != nullptr; return m_attributes->get_attribute_ns(namespace_, name) != nullptr; } // https://dom.spec.whatwg.org/#dom-element-toggleattribute WebIDL::ExceptionOr Element::toggle_attribute(FlyString const& name, Optional force) { // 1. If qualifiedName does not match the Name production in XML, then throw an "InvalidCharacterError" DOMException. // FIXME: Proper name validation if (name.is_empty()) return WebIDL::InvalidCharacterError::create(realm(), "Attribute name must not be empty"_fly_string); // 2. If this is in the HTML namespace and its node document is an HTML document, then set qualifiedName to qualifiedName in ASCII lowercase. bool insert_as_lowercase = namespace_uri() == Namespace::HTML && document().document_type() == Document::Type::HTML; // 3. Let attribute be the first attribute in this’s attribute list whose qualified name is qualifiedName, and null otherwise. auto* attribute = m_attributes->get_attribute(name); // 4. If attribute is null, then: if (!attribute) { // 1. If force is not given or is true, create an attribute whose local name is qualifiedName, value is the empty // string, and node document is this’s node document, then append this attribute to this, and then return true. if (!force.has_value() || force.value()) { auto new_attribute = Attr::create(document(), insert_as_lowercase ? MUST(Infra::to_ascii_lowercase(name)) : name.to_string(), String {}); m_attributes->append_attribute(new_attribute); return true; } // 2. Return false. return false; } // 5. Otherwise, if force is not given or is false, remove an attribute given qualifiedName and this, and then return false. if (!force.has_value() || !force.value()) { m_attributes->remove_attribute(name); return false; } // 6. Return true. return true; } // https://dom.spec.whatwg.org/#dom-element-getattributenames Vector Element::get_attribute_names() const { // The getAttributeNames() method steps are to return the qualified names of the attributes in this’s attribute list, in order; otherwise a new list. Vector names; for (size_t i = 0; i < m_attributes->length(); ++i) { auto const* attribute = m_attributes->item(i); names.append(attribute->name().to_string()); } return names; } JS::GCPtr Element::create_layout_node(NonnullRefPtr style) { if (local_name() == "noscript" && document().is_scripting_enabled()) return nullptr; auto display = style->display(); return create_layout_node_for_display_type(document(), display, move(style), this); } JS::GCPtr Element::create_layout_node_for_display_type(DOM::Document& document, CSS::Display const& display, NonnullRefPtr style, Element* element) { if (display.is_table_inside() || display.is_table_row_group() || display.is_table_header_group() || display.is_table_footer_group() || display.is_table_row()) return document.heap().allocate_without_realm(document, element, move(style)); if (display.is_list_item()) return document.heap().allocate_without_realm(document, element, move(style)); if (display.is_table_cell()) return document.heap().allocate_without_realm(document, element, move(style)); if (display.is_table_column() || display.is_table_column_group() || display.is_table_caption()) { // FIXME: This is just an incorrect placeholder until we improve table layout support. return document.heap().allocate_without_realm(document, element, move(style)); } if (display.is_math_inside()) { // https://w3c.github.io/mathml-core/#new-display-math-value // MathML elements with a computed display value equal to block math or inline math control box generation // and layout according to their tag name, as described in the relevant sections. // FIXME: Figure out what kind of node we should make for them. For now, we'll stick with a generic Box. return document.heap().allocate_without_realm(document, element, move(style)); } if (display.is_inline_outside()) { if (display.is_flow_root_inside()) return document.heap().allocate_without_realm(document, element, move(style)); if (display.is_flow_inside()) return document.heap().allocate_without_realm(document, element, move(style)); if (display.is_flex_inside()) return document.heap().allocate_without_realm(document, element, move(style)); if (display.is_grid_inside()) return document.heap().allocate_without_realm(document, element, move(style)); dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Support display: {}", display.to_string()); return document.heap().allocate_without_realm(document, element, move(style)); } if (display.is_flex_inside() || display.is_grid_inside()) return document.heap().allocate_without_realm(document, element, move(style)); if (display.is_flow_inside() || display.is_flow_root_inside() || display.is_contents()) return document.heap().allocate_without_realm(document, element, move(style)); dbgln("FIXME: CSS display '{}' not implemented yet.", display.to_string()); return document.heap().allocate_without_realm(document, element, move(style)); } void Element::run_attribute_change_steps(FlyString const& local_name, Optional const& old_value, Optional const& value, Optional const& namespace_) { attribute_change_steps(local_name, old_value, value, namespace_); // AD-HOC: Run our own internal attribute change handler. attribute_changed(local_name, value); invalidate_style_after_attribute_change(local_name); document().bump_dom_tree_version(); } void Element::attribute_changed(FlyString const& name, Optional const& value) { auto value_or_empty = value.value_or(String {}); if (name == HTML::AttributeNames::id) { if (!value.has_value()) m_id = {}; else m_id = value_or_empty; document().element_id_changed({}, *this); } else if (name == HTML::AttributeNames::name) { if (!value.has_value()) m_name = {}; else m_name = value_or_empty; document().element_name_changed({}, *this); } else if (name == HTML::AttributeNames::class_) { auto new_classes = value_or_empty.bytes_as_string_view().split_view_if(Infra::is_ascii_whitespace); m_classes.clear(); m_classes.ensure_capacity(new_classes.size()); for (auto& new_class : new_classes) { m_classes.unchecked_append(FlyString::from_utf8(new_class).release_value_but_fixme_should_propagate_errors()); } if (m_class_list) m_class_list->associated_attribute_changed(value_or_empty); } else if (name == HTML::AttributeNames::style) { if (!value.has_value()) { if (m_inline_style) { m_inline_style = nullptr; set_needs_style_update(true); } } else { // https://drafts.csswg.org/cssom/#ref-for-cssstyledeclaration-updating-flag if (m_inline_style && m_inline_style->is_updating()) return; m_inline_style = parse_css_style_attribute(CSS::Parser::ParsingContext(document()), *value, *this); set_needs_style_update(true); } } else if (name == HTML::AttributeNames::dir) { // https://html.spec.whatwg.org/multipage/dom.html#attr-dir if (value_or_empty.equals_ignoring_ascii_case("ltr"sv)) m_dir = Dir::Ltr; else if (value_or_empty.equals_ignoring_ascii_case("rtl"sv)) m_dir = Dir::Rtl; else if (value_or_empty.equals_ignoring_ascii_case("auto"sv)) m_dir = Dir::Auto; else m_dir = {}; } } static CSS::RequiredInvalidationAfterStyleChange compute_required_invalidation(CSS::StyleProperties const& old_style, CSS::StyleProperties const& new_style) { CSS::RequiredInvalidationAfterStyleChange invalidation; if (!old_style.computed_font_list().equals(new_style.computed_font_list())) invalidation.relayout = true; for (auto i = to_underlying(CSS::first_property_id); i <= to_underlying(CSS::last_property_id); ++i) { auto property_id = static_cast(i); auto old_value = old_style.maybe_null_property(property_id); auto new_value = new_style.maybe_null_property(property_id); if (!old_value && !new_value) continue; invalidation |= CSS::compute_property_invalidation(property_id, old_value, new_value); } return invalidation; } CSS::RequiredInvalidationAfterStyleChange Element::recompute_style() { set_needs_style_update(false); VERIFY(parent()); auto new_computed_css_values = document().style_computer().compute_style(*this); // Tables must not inherit -libweb-* values for text-align. // FIXME: Find the spec for this. if (is(*this)) { auto text_align = new_computed_css_values->text_align(); if (text_align.has_value() && (text_align.value() == CSS::TextAlign::LibwebLeft || text_align.value() == CSS::TextAlign::LibwebCenter || text_align.value() == CSS::TextAlign::LibwebRight)) new_computed_css_values->set_property(CSS::PropertyID::TextAlign, CSS::IdentifierStyleValue::create(CSS::ValueID::Start)); } CSS::RequiredInvalidationAfterStyleChange invalidation; if (m_computed_css_values) invalidation = compute_required_invalidation(*m_computed_css_values, *new_computed_css_values); else invalidation = CSS::RequiredInvalidationAfterStyleChange::full(); if (invalidation.is_none()) return invalidation; m_computed_css_values = move(new_computed_css_values); computed_css_values_changed(); if (invalidation.repaint) document().set_needs_to_resolve_paint_only_properties(); if (!invalidation.rebuild_layout_tree && layout_node()) { // If we're keeping the layout tree, we can just apply the new style to the existing layout tree. layout_node()->apply_style(*m_computed_css_values); if (invalidation.repaint && paintable()) paintable()->set_needs_display(); } return invalidation; } NonnullRefPtr Element::resolved_css_values() { auto element_computed_style = CSS::ResolvedCSSStyleDeclaration::create(*this); auto properties = CSS::StyleProperties::create(); for (auto i = to_underlying(CSS::first_property_id); i <= to_underlying(CSS::last_property_id); ++i) { auto property_id = (CSS::PropertyID)i; auto maybe_value = element_computed_style->property(property_id); if (!maybe_value.has_value()) continue; properties->set_property(property_id, maybe_value.release_value().value, nullptr); } return properties; } void Element::reset_animated_css_properties() { if (!m_computed_css_values) return; m_computed_css_values->reset_animated_properties(); } DOMTokenList* Element::class_list() { if (!m_class_list) m_class_list = DOMTokenList::create(*this, HTML::AttributeNames::class_); return m_class_list; } // 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 Element::attach_a_shadow_root(Bindings::ShadowRootMode mode, bool clonable, bool serializable, bool delegates_focus, Bindings::SlotAssignmentMode slot_assignment) { // 1. If element’s 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 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 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()) { // 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); // 2. If definition is not null and definition’s disable shadow is true, then throw a "NotSupportedError" DOMException. if (definition && definition->disable_shadow()) return WebIDL::NotSupportedError::create(realm(), "Cannot attach a shadow root to a custom element that has disabled shadow roots"_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); // 3. Return. return {}; } // 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(realm(), document(), *this, mode); // 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) shadow->set_available_to_element_internals(true); // 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); // 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); return {}; } // https://dom.spec.whatwg.org/#dom-element-attachshadow WebIDL::ExceptionOr> 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 JS::GCPtr Element::shadow_root_for_bindings() const { // 1. Let shadow be this’s shadow root. auto shadow = m_shadow_root; // 2. If shadow is null or its mode is "closed", then return null. if (shadow == nullptr || shadow->mode() == Bindings::ShadowRootMode::Closed) return nullptr; // 3. Return shadow. return shadow; } // https://dom.spec.whatwg.org/#dom-element-matches WebIDL::ExceptionOr Element::matches(StringView selectors) const { // 1. Let s be the result of parse a selector from selectors. auto maybe_selectors = parse_selector(CSS::Parser::ParsingContext(static_cast(const_cast(*this))), selectors); // 2. If s is failure, then throw a "SyntaxError" DOMException. if (!maybe_selectors.has_value()) return WebIDL::SyntaxError::create(realm(), "Failed to parse selector"_fly_string); // 3. If the result of match a selector against an element, using s, this, and scoping root this, returns success, then return true; otherwise, return false. auto sel = maybe_selectors.value(); for (auto& s : sel) { if (SelectorEngine::matches(s, {}, *this, {}, static_cast(this))) return true; } return false; } // https://dom.spec.whatwg.org/#dom-element-closest WebIDL::ExceptionOr Element::closest(StringView selectors) const { // 1. Let s be the result of parse a selector from selectors. auto maybe_selectors = parse_selector(CSS::Parser::ParsingContext(static_cast(const_cast(*this))), selectors); // 2. If s is failure, then throw a "SyntaxError" DOMException. if (!maybe_selectors.has_value()) return WebIDL::SyntaxError::create(realm(), "Failed to parse selector"_fly_string); auto matches_selectors = [this](CSS::SelectorList const& selector_list, Element const* element) { // 4. For each element in elements, if match a selector against an element, using s, element, and scoping root this, returns success, return element. for (auto const& selector : selector_list) { if (SelectorEngine::matches(selector, {}, *element, {}, this)) return true; } return false; }; auto const selector_list = maybe_selectors.release_value(); // 3. Let elements be this’s inclusive ancestors that are elements, in reverse tree order. for (auto* element = this; element; element = element->parent_element()) { if (!matches_selectors(selector_list, element)) continue; return element; } // 5. Return null. return nullptr; } // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-element-innerhtml WebIDL::ExceptionOr Element::set_inner_html(StringView value) { // FIXME: 1. Let compliantString be the result of invoking the Get Trusted Type compliant string algorithm with TrustedHTML, this's relevant global object, the given value, "Element innerHTML", and "script". // 2. Let context be this. DOM::Node* context = this; // 3. Let fragment be the result of invoking the fragment parsing algorithm steps with context and compliantString. FIXME: Use compliantString. auto fragment = TRY(verify_cast(*context).parse_fragment(value)); // 4. If context is a template element, then set context to the template element's template contents (a DocumentFragment). if (is(*context)) context = verify_cast(*context).content(); // 5. Replace all with fragment within context. context->replace_all(fragment); // NOTE: We don't invalidate style & layout for