/* * 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 #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_aria_active_descendant_element); 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); visitor.visit(m_cascaded_properties); visitor.visit(m_computed_properties); if (m_pseudo_element_data) { for (auto& pseudo_element : *m_pseudo_element_data) { visitor.visit(pseudo_element.cascaded_properties); visitor.visit(pseudo_element.computed_properties); visitor.visit(pseudo_element.layout_node); } } 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 GC::Ptr 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 GC::Ptr 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"_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 ? name.to_ascii_lowercase() : 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) { // To validate and extract a namespace and qualifiedName, run these steps: // 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 (:): if (qualified_name.bytes_as_string_view().contains(':')) { // 1. Let splitResult be the result of running strictly split given qualifiedName and U+003A (:). // FIXME: Use the "strictly split" algorithm auto split_result = qualified_name.bytes_as_string_view().split_view(':'); // 2. Set prefix to splitResult[0]. prefix = MUST(FlyString::from_utf8(split_result[0])); // 3. Set localName to splitResult[1]. local_name = MUST(FlyString::from_utf8(split_result[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."_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."_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."_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'."_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(GC::Ref 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. if (!Document::is_valid_name(name.to_string())) return WebIDL::InvalidCharacterError::create(realm(), "Attribute name must not be empty or contain invalid characters"_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 ? name.to_ascii_lowercase() : 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; } GC::Ptr Element::create_layout_node(GC::Ref style) { if (local_name() == "noscript" && document().is_scripting_enabled()) return nullptr; auto display = style->display(); return create_layout_node_for_display_type(document(), display, style, this); } GC::Ptr Element::create_layout_node_for_display_type(DOM::Document& document, CSS::Display const& display, GC::Ref 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(document, element, move(style)); if (display.is_list_item()) return document.heap().allocate(document, element, move(style)); if (display.is_table_cell()) return document.heap().allocate(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(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(document, element, move(style)); } if (display.is_inline_outside()) { if (display.is_flow_root_inside()) return document.heap().allocate(document, element, move(style)); if (display.is_flow_inside()) return document.heap().allocate(document, element, move(style)); if (display.is_flex_inside()) return document.heap().allocate(document, element, move(style)); if (display.is_grid_inside()) return document.heap().allocate(document, element, move(style)); dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Support display: {}", display.to_string()); return document.heap().allocate(document, element, move(style)); } if (display.is_flex_inside() || display.is_grid_inside()) return document.heap().allocate(document, element, move(style)); if (display.is_flow_inside() || display.is_flow_root_inside() || display.is_contents()) return document.heap().allocate(document, element, move(style)); dbgln("FIXME: CSS display '{}' not implemented yet.", display.to_string()); // FIXME: We don't actually support `display: block ruby`, this is just a hack to prevent a crash if (display.is_ruby_inside()) return document.heap().allocate(document, element, move(style)); return document.heap().allocate(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_changed(local_name, old_value, value, namespace_); if (old_value != value) { invalidate_style_after_attribute_change(local_name, old_value, value); document().bump_dom_tree_version(); } } static CSS::RequiredInvalidationAfterStyleChange compute_required_invalidation(CSS::ComputedProperties const& old_style, CSS::ComputedProperties 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() { VERIFY(parent()); m_affected_by_has_pseudo_class_in_subject_position = false; m_affected_by_has_pseudo_class_in_non_subject_position = false; m_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator = false; m_affected_by_direct_sibling_combinator = false; m_affected_by_indirect_sibling_combinator = false; m_affected_by_first_or_last_child_pseudo_class = false; m_affected_by_nth_child_pseudo_class = false; m_sibling_invalidation_distance = 0; auto& style_computer = document().style_computer(); auto new_computed_properties = 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_properties->text_align(); if (text_align == CSS::TextAlign::LibwebLeft || text_align == CSS::TextAlign::LibwebCenter || text_align == CSS::TextAlign::LibwebRight) new_computed_properties->set_property(CSS::PropertyID::TextAlign, CSS::CSSKeywordValue::create(CSS::Keyword::Start)); } bool had_list_marker = false; CSS::RequiredInvalidationAfterStyleChange invalidation; if (m_computed_properties) { invalidation = compute_required_invalidation(*m_computed_properties, new_computed_properties); had_list_marker = m_computed_properties->display().is_list_item(); } else { invalidation = CSS::RequiredInvalidationAfterStyleChange::full(); } auto old_display_is_none = m_computed_properties ? m_computed_properties->display().is_none() : true; auto new_display_is_none = new_computed_properties->display().is_none(); if (!invalidation.is_none()) set_computed_properties(move(new_computed_properties)); if (old_display_is_none != new_display_is_none) { play_or_cancel_animations_after_display_property_change(); } // Any document change that can cause this element's style to change, could also affect its pseudo-elements. auto recompute_pseudo_element_style = [&](CSS::PseudoElement pseudo_element) { style_computer.push_ancestor(*this); auto pseudo_element_style = pseudo_element_computed_properties(pseudo_element); auto new_pseudo_element_style = style_computer.compute_pseudo_element_style_if_needed(*this, pseudo_element); // TODO: Can we be smarter about invalidation? if (pseudo_element_style && new_pseudo_element_style) { invalidation |= compute_required_invalidation(*pseudo_element_style, *new_pseudo_element_style); } else if (pseudo_element_style || new_pseudo_element_style) { invalidation = CSS::RequiredInvalidationAfterStyleChange::full(); } set_pseudo_element_computed_properties(pseudo_element, move(new_pseudo_element_style)); style_computer.pop_ancestor(*this); }; recompute_pseudo_element_style(CSS::PseudoElement::Before); recompute_pseudo_element_style(CSS::PseudoElement::After); if (had_list_marker || m_computed_properties->display().is_list_item()) recompute_pseudo_element_style(CSS::PseudoElement::Marker); if (invalidation.is_none()) return invalidation; 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_properties); if (invalidation.repaint && paintable()) paintable()->set_needs_display(); // Do the same for pseudo-elements. for (auto i = 0; i < to_underlying(CSS::PseudoElement::KnownPseudoElementCount); i++) { auto pseudo_element_type = static_cast(i); auto pseudo_element = get_pseudo_element(pseudo_element_type); if (!pseudo_element.has_value() || !pseudo_element->layout_node) continue; auto pseudo_element_style = pseudo_element_computed_properties(pseudo_element_type); if (!pseudo_element_style) continue; if (auto* node_with_style = dynamic_cast(pseudo_element->layout_node.ptr())) { node_with_style->apply_style(*pseudo_element_style); if (invalidation.repaint && node_with_style->first_paintable()) node_with_style->first_paintable()->set_needs_display(); } } } return invalidation; } CSS::RequiredInvalidationAfterStyleChange Element::recompute_inherited_style() { auto computed_properties = this->computed_properties(); if (!m_cascaded_properties || !computed_properties || !layout_node()) return {}; CSS::RequiredInvalidationAfterStyleChange invalidation; HashMap> old_values_with_relative_units; for (auto i = to_underlying(CSS::first_property_id); i <= to_underlying(CSS::last_property_id); ++i) { auto property_id = static_cast(i); auto const& preabsolutized_value = m_cascaded_properties->property(property_id); RefPtr old_value = computed_properties->maybe_null_property(property_id); // Update property if it uses relative units as it might have been affected by a change in ancestor element style. if (preabsolutized_value && preabsolutized_value->is_length() && preabsolutized_value->as_length().length().is_font_relative()) { auto is_inherited = computed_properties->is_property_inherited(property_id); computed_properties->set_property(property_id, *preabsolutized_value, is_inherited ? CSS::ComputedProperties::Inherited::Yes : CSS::ComputedProperties::Inherited::No); old_values_with_relative_units.set(i, old_value); } if (!computed_properties->is_property_inherited(property_id)) continue; RefPtr new_value = CSS::StyleComputer::get_inherit_value(property_id, this); computed_properties->set_property(property_id, *new_value, CSS::ComputedProperties::Inherited::Yes); invalidation |= CSS::compute_property_invalidation(property_id, old_value, new_value); } if (invalidation.is_none() && old_values_with_relative_units.is_empty()) return invalidation; document().style_computer().compute_font(*computed_properties, this, {}); document().style_computer().absolutize_values(*computed_properties, this); for (auto [property_id, old_value] : old_values_with_relative_units) { auto new_value = computed_properties->maybe_null_property(static_cast(property_id)); invalidation |= CSS::compute_property_invalidation(static_cast(property_id), old_value, new_value); } if (invalidation.is_none()) return invalidation; layout_node()->apply_style(*computed_properties); return invalidation; } GC::Ref Element::resolved_css_values(Optional type) { auto element_computed_style = CSS::CSSStyleProperties::create_resolved_style({ *this, type }); auto properties = heap().allocate(); 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); } return properties; } void Element::reset_animated_css_properties() { if (!m_computed_properties) return; m_computed_properties->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 static 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"_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"_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"_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"_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 = realm().create(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 GC::Ref { *shadow_root() }; } // https://dom.spec.whatwg.org/#dom-element-shadowroot GC::Ptr 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::ParsingParams(document()), 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"_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) { SelectorEngine::MatchContext context; if (SelectorEngine::matches(s, *this, nullptr, context, {}, 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::ParsingParams(document()), 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"_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) { SelectorEngine::MatchContext context; if (SelectorEngine::matches(selector, *element, nullptr, context, {}, 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(as(*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 = as(*context).content(); // 5. Replace all with fragment within context. context->replace_all(fragment); // NOTE: We don't invalidate style & layout for