/* * Copyright (c) 2018-2024, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #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 { struct ShadowRootInit { Bindings::ShadowRootMode mode; bool delegates_focus = false; Bindings::SlotAssignmentMode slot_assignment { Bindings::SlotAssignmentMode::Named }; bool clonable = false; bool serializable = false; }; struct GetHTMLOptions { bool serializable_shadow_roots { false }; Vector> shadow_roots {}; }; // https://w3c.github.io/csswg-drafts/cssom-view-1/#dictdef-scrollintoviewoptions struct ScrollIntoViewOptions : public HTML::ScrollOptions { Bindings::ScrollLogicalPosition block { Bindings::ScrollLogicalPosition::Start }; Bindings::ScrollLogicalPosition inline_ { Bindings::ScrollLogicalPosition::Nearest }; Bindings::ScrollIntoViewContainer container { Bindings::ScrollIntoViewContainer::All }; }; // https://drafts.csswg.org/cssom-view-1/#dictdef-checkvisibilityoptions struct CheckVisibilityOptions { bool check_opacity = false; bool check_visibility_css = false; bool content_visibility_auto = false; bool opacity_property = false; bool visibility_property = false; }; // https://html.spec.whatwg.org/multipage/custom-elements.html#upgrade-reaction // An upgrade reaction, which will upgrade the custom element and contains a custom element definition; or struct CustomElementUpgradeReaction { GC::Root custom_element_definition; }; // https://html.spec.whatwg.org/multipage/custom-elements.html#callback-reaction // A callback reaction, which will call a lifecycle callback, and contains a callback function as well as a list of arguments. struct CustomElementCallbackReaction { GC::Root callback; GC::RootVector arguments; }; // https://dom.spec.whatwg.org/#concept-element-custom-element-state // An element’s custom element state is one of "undefined", "failed", "uncustomized", "precustomized", or "custom". enum class CustomElementState { Undefined, Failed, Uncustomized, Precustomized, Custom, }; // https://drafts.csswg.org/css-contain/#proximity-to-the-viewport // An element that has content-visibility: auto is in one of three states when it comes to its proximity to the viewport: enum class ProximityToTheViewport { // - The element is close to the viewport: CloseToTheViewport, // - The element is far away from the viewport: FarAwayFromTheViewport, // - The element’s proximity to the viewport is not determined: NotDetermined, }; class Element : public ParentNode , public ChildNode , public NonDocumentTypeChildNode , public SlottableMixin , public ARIA::ARIAMixin , public Animations::Animatable { WEB_PLATFORM_OBJECT(Element, ParentNode); public: virtual ~Element() override; FlyString const& qualified_name() const { return m_qualified_name.as_string(); } FlyString const& html_uppercased_qualified_name() const { return m_html_uppercased_qualified_name; } virtual FlyString node_name() const final { return html_uppercased_qualified_name(); } FlyString const& local_name() const { return m_qualified_name.local_name(); } // NOTE: This is for the JS bindings FlyString const& tag_name() const { return html_uppercased_qualified_name(); } Optional const& prefix() const { return m_qualified_name.prefix(); } void set_prefix(Optional value); Optional locate_a_namespace_prefix(Optional const& namespace_) const; // NOTE: This is for the JS bindings Optional const& namespace_uri() const { return m_qualified_name.namespace_(); } bool has_attribute(FlyString const& name) const; bool has_attribute_ns(Optional const& namespace_, FlyString const& name) const; bool has_attributes() const; Optional attribute(FlyString const& name) const { return get_attribute(name); } Optional get_attribute(FlyString const& name) const; Optional get_attribute_ns(Optional const& namespace_, FlyString const& name) const; String get_attribute_value(FlyString const& local_name, Optional const& namespace_ = {}) const; Optional lang() const; WebIDL::ExceptionOr set_attribute(FlyString const& name, String const& value); WebIDL::ExceptionOr set_attribute_ns(Optional const& namespace_, FlyString const& qualified_name, String const& value); void set_attribute_value(FlyString const& local_name, String const& value, Optional const& prefix = {}, Optional const& namespace_ = {}); WebIDL::ExceptionOr> set_attribute_node(Attr&); WebIDL::ExceptionOr> set_attribute_node_ns(Attr&); void append_attribute(FlyString const& name, String const& value); void append_attribute(Attr&); void remove_attribute(FlyString const& name); void remove_attribute_ns(Optional const& namespace_, FlyString const& name); WebIDL::ExceptionOr> remove_attribute_node(GC::Ref); WebIDL::ExceptionOr toggle_attribute(FlyString const& name, Optional force); size_t attribute_list_size() const; GC::Ptr attributes() const; GC::Ptr attributes(); Vector get_attribute_names() const; GC::Ptr get_attribute_node(FlyString const& name) const; GC::Ptr get_attribute_node_ns(Optional const& namespace_, FlyString const& name) const; DOMTokenList* class_list(); WebIDL::ExceptionOr> attach_shadow(ShadowRootInit init); WebIDL::ExceptionOr attach_a_shadow_root(Bindings::ShadowRootMode mode, bool clonable, bool serializable, bool delegates_focus, Bindings::SlotAssignmentMode slot_assignment); GC::Ptr shadow_root_for_bindings() const; WebIDL::ExceptionOr matches(StringView selectors) const; WebIDL::ExceptionOr closest(StringView selectors) const; int client_top() const; int client_left() const; int client_width() const; int client_height() const; [[nodiscard]] double current_css_zoom() const; void for_each_attribute(Function) const; void for_each_attribute(Function) const; bool has_class(FlyString const&, CaseSensitivity = CaseSensitivity::CaseSensitive) const; Vector const& class_names() const { return m_classes; } // https://html.spec.whatwg.org/multipage/embedded-content-other.html#dimension-attributes virtual bool supports_dimension_attributes() const { return false; } virtual bool is_presentational_hint(FlyString const&) const { return false; } virtual void apply_presentational_hints(GC::Ref) const { } void run_attribute_change_steps(FlyString const& local_name, Optional const& old_value, Optional const& value, Optional const& namespace_); CSS::RequiredInvalidationAfterStyleChange recompute_style(); CSS::RequiredInvalidationAfterStyleChange recompute_inherited_style(); Optional use_pseudo_element() const { return m_use_pseudo_element; } void set_use_pseudo_element(Optional use_pseudo_element) { m_use_pseudo_element = move(use_pseudo_element); } GC::Ptr layout_node(); GC::Ptr layout_node() const; GC::Ptr computed_properties() { return m_computed_properties; } GC::Ptr computed_properties() const { return m_computed_properties; } void set_computed_properties(GC::Ptr); GC::Ref resolved_css_values(Optional = {}); [[nodiscard]] GC::Ptr cascaded_properties(Optional) const; void set_cascaded_properties(Optional, GC::Ptr); void set_pseudo_element_computed_properties(CSS::PseudoElement, GC::Ptr); GC::Ptr pseudo_element_computed_properties(CSS::PseudoElement); void reset_animated_css_properties(); GC::Ptr inline_style() { return m_inline_style; } GC::Ptr inline_style() const { return m_inline_style; } void set_inline_style(GC::Ptr); GC::Ref style_for_bindings(); CSS::StyleSheetList& document_or_shadow_root_style_sheets(); ElementByIdMap& document_or_shadow_root_element_by_id_map(); WebIDL::ExceptionOr> parse_fragment(StringView markup); WebIDL::ExceptionOr inner_html() const; WebIDL::ExceptionOr set_inner_html(StringView); WebIDL::ExceptionOr set_html_unsafe(StringView); WebIDL::ExceptionOr get_html(GetHTMLOptions const&) const; WebIDL::ExceptionOr insert_adjacent_html(String const& position, String const&); WebIDL::ExceptionOr outer_html() const; WebIDL::ExceptionOr set_outer_html(String const&); bool is_focused() const; bool is_active() const; bool is_target() const; bool is_document_element() const; bool is_shadow_host() const; GC::Ptr shadow_root() { return m_shadow_root; } GC::Ptr shadow_root() const { return m_shadow_root; } void set_shadow_root(GC::Ptr); void set_custom_properties(Optional, HashMap custom_properties); [[nodiscard]] HashMap const& custom_properties(Optional) const; bool style_uses_css_custom_properties() const { return m_style_uses_css_custom_properties; } void set_style_uses_css_custom_properties(bool value) { m_style_uses_css_custom_properties = value; } // NOTE: The function is wrapped in a GC::HeapFunction immediately. HTML::TaskID queue_an_element_task(HTML::Task::Source, Function); bool is_void_element() const; bool serializes_as_void() const; [[nodiscard]] CSSPixelRect get_bounding_client_rect() const; [[nodiscard]] GC::Ref get_bounding_client_rect_for_bindings() const; [[nodiscard]] Vector get_client_rects() const; [[nodiscard]] GC::Ref get_client_rects_for_bindings() const; virtual GC::Ptr create_layout_node(GC::Ref); virtual void adjust_computed_style(CSS::ComputedProperties&) { } virtual void did_receive_focus() { } virtual void did_lose_focus() { } static GC::Ptr create_layout_node_for_display_type(DOM::Document&, CSS::Display const&, GC::Ref, Element*); [[nodiscard]] bool affected_by_pseudo_class(CSS::PseudoClass) const; bool includes_properties_from_invalidation_set(CSS::InvalidationSet const&) const; void set_pseudo_element_node(Badge, CSS::PseudoElement, GC::Ptr); GC::Ptr get_pseudo_element_node(CSS::PseudoElement) const; bool has_pseudo_element(CSS::PseudoElement) const; bool has_pseudo_elements() const; void clear_pseudo_element_nodes(Badge); void serialize_pseudo_elements_as_json(JsonArraySerializer& children_array) const; i32 tab_index() const; void set_tab_index(i32 tab_index); bool is_potentially_scrollable() const; double scroll_top() const; double scroll_left() const; void set_scroll_top(double y); void set_scroll_left(double x); int scroll_width() const; int scroll_height() const; bool is_actually_disabled() const; WebIDL::ExceptionOr> insert_adjacent_element(String const& where, GC::Ref element); WebIDL::ExceptionOr insert_adjacent_text(String const& where, String const& data); // https://w3c.github.io/csswg-drafts/cssom-view-1/#dom-element-scrollintoview ErrorOr scroll_into_view(Optional> = {}); // https://www.w3.org/TR/wai-aria-1.2/#ARIAMixin #define __ENUMERATE_ARIA_ATTRIBUTE(name, attribute) \ Optional name() const override \ { \ return get_attribute(ARIA::AttributeNames::name); \ } \ \ WebIDL::ExceptionOr set_##name(Optional const& value) override \ { \ if (value.has_value()) \ TRY(set_attribute(ARIA::AttributeNames::name, *value)); \ else \ remove_attribute(ARIA::AttributeNames::name); \ return {}; \ } ENUMERATE_ARIA_ATTRIBUTES #undef __ENUMERATE_ARIA_ATTRIBUTE GC::Ptr aria_active_descendant_element() { return m_aria_active_descendant_element; } void set_aria_active_descendant_element(GC::Ptr value) { m_aria_active_descendant_element = value; } virtual bool exclude_from_accessibility_tree() const override; virtual bool include_in_accessibility_tree() const override; virtual Element const* to_element() const override { return this; } bool is_hidden() const; bool has_hidden_ancestor() const; bool is_referenced() const; bool has_referenced_and_hidden_ancestor() const; void enqueue_a_custom_element_upgrade_reaction(HTML::CustomElementDefinition& custom_element_definition); void enqueue_a_custom_element_callback_reaction(FlyString const& callback_name, GC::RootVector arguments); using CustomElementReactionQueue = Vector>; CustomElementReactionQueue* custom_element_reaction_queue() { return m_custom_element_reaction_queue; } CustomElementReactionQueue const* custom_element_reaction_queue() const { return m_custom_element_reaction_queue; } CustomElementReactionQueue& ensure_custom_element_reaction_queue(); JS::ThrowCompletionOr upgrade_element(GC::Ref custom_element_definition); void try_to_upgrade(); bool is_defined() const; bool is_custom() const; Optional const& is_value() const { return m_is_value; } void set_is_value(Optional const& is) { m_is_value = is; } void set_custom_element_state(CustomElementState); void setup_custom_element_from_constructor(HTML::CustomElementDefinition& custom_element_definition, Optional const& is_value); void scroll(HTML::ScrollToOptions); void scroll(double x, double y); void scroll_by(HTML::ScrollToOptions); void scroll_by(double x, double y); bool check_visibility(Optional); void register_intersection_observer(Badge, IntersectionObserver::IntersectionObserverRegistration); void unregister_intersection_observer(Badge, GC::Ref); IntersectionObserver::IntersectionObserverRegistration& get_intersection_observer_registration(Badge, IntersectionObserver::IntersectionObserver const&); enum class ScrollOffsetFor { Self, PseudoBefore, PseudoAfter }; CSSPixelPoint scroll_offset(ScrollOffsetFor type) const { return m_scroll_offset[to_underlying(type)]; } void set_scroll_offset(ScrollOffsetFor type, CSSPixelPoint offset) { m_scroll_offset[to_underlying(type)] = offset; } enum class Dir { Ltr, Rtl, Auto, }; Optional dir() const { return m_dir; } enum class Directionality { Ltr, Rtl, }; Directionality directionality() const; Optional const& id() const { return m_id; } Optional const& name() const { return m_name; } virtual GC::Ptr> take_lazy_load_resumption_steps(Badge) { return nullptr; } // An element el is in the top layer if el is contained in its node document’s top layer // but not contained in its node document’s pending top layer removals. void set_in_top_layer(bool in_top_layer) { m_in_top_layer = in_top_layer; } bool in_top_layer() const { return m_in_top_layer; } // An element el is rendered in the top layer if el is contained in its node document’s top layer, // FIXME: and el has overlay: auto. void set_rendered_in_top_layer(bool rendered_in_top_layer) { m_rendered_in_top_layer = rendered_in_top_layer; } bool rendered_in_top_layer() const { return m_rendered_in_top_layer; } bool has_non_empty_counters_set() const { return m_counters_set; } Optional counters_set(); CSS::CountersSet& ensure_counters_set(); void resolve_counters(CSS::ComputedProperties&); void inherit_counters(); ProximityToTheViewport proximity_to_the_viewport() const { return m_proximity_to_the_viewport; } void determine_proximity_to_the_viewport(); bool is_relevant_to_the_user(); // https://drafts.csswg.org/css-contain-2/#skips-its-contents bool skips_its_contents(); // https://drafts.csswg.org/css-contain-2/#containment-types bool has_size_containment() const; bool has_inline_size_containment() const; bool has_layout_containment() const; bool has_style_containment() const; bool has_paint_containment() const; bool matches_enabled_pseudo_class() const; bool matches_disabled_pseudo_class() const; bool matches_checked_pseudo_class() const; bool matches_placeholder_shown_pseudo_class() const; bool matches_link_pseudo_class() const; bool matches_local_link_pseudo_class() const; void invalidate_style_if_affected_by_has(); bool affected_by_has_pseudo_class_in_subject_position() const { return m_affected_by_has_pseudo_class_in_subject_position; } void set_affected_by_has_pseudo_class_in_subject_position(bool value) { m_affected_by_has_pseudo_class_in_subject_position = value; } bool affected_by_has_pseudo_class_in_non_subject_position() const { return m_affected_by_has_pseudo_class_in_non_subject_position; } void set_affected_by_has_pseudo_class_in_non_subject_position(bool value) { m_affected_by_has_pseudo_class_in_non_subject_position = value; } bool affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator() const { return m_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator; } void set_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator(bool value) { m_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator = value; } bool affected_by_direct_sibling_combinator() const { return m_affected_by_direct_sibling_combinator; } void set_affected_by_direct_sibling_combinator(bool value) { m_affected_by_direct_sibling_combinator = value; } bool affected_by_indirect_sibling_combinator() const { return m_affected_by_indirect_sibling_combinator; } void set_affected_by_indirect_sibling_combinator(bool value) { m_affected_by_indirect_sibling_combinator = value; } bool affected_by_first_or_last_child_pseudo_class() const { return m_affected_by_first_or_last_child_pseudo_class; } void set_affected_by_first_or_last_child_pseudo_class(bool value) { m_affected_by_first_or_last_child_pseudo_class = value; } bool affected_by_nth_child_pseudo_class() const { return m_affected_by_nth_child_pseudo_class; } void set_affected_by_nth_child_pseudo_class(bool value) { m_affected_by_nth_child_pseudo_class = value; } size_t sibling_invalidation_distance() const { return m_sibling_invalidation_distance; } void set_sibling_invalidation_distance(size_t value) { m_sibling_invalidation_distance = value; } bool style_affected_by_structural_changes() const { return affected_by_direct_sibling_combinator() || affected_by_indirect_sibling_combinator() || affected_by_first_or_last_child_pseudo_class() || affected_by_nth_child_pseudo_class(); } size_t number_of_owned_list_items() const; Element const* list_owner() const; size_t ordinal_value() const; void set_pointer_capture(WebIDL::Long pointer_id); void release_pointer_capture(WebIDL::Long pointer_id); bool has_pointer_capture(WebIDL::Long pointer_id); virtual bool contributes_a_script_blocking_style_sheet() const { return false; } protected: Element(Document&, DOM::QualifiedName); virtual void initialize(JS::Realm&) override; virtual void inserted() override; virtual void removed_from(Node* old_parent, Node& old_root) override; virtual void children_changed(ChildrenChangedMetadata const*) override; virtual i32 default_tab_index_value() const; // https://dom.spec.whatwg.org/#concept-element-attributes-change-ext virtual void attribute_changed(FlyString const& local_name, Optional const& old_value, Optional const& value, Optional const& namespace_); virtual void computed_properties_changed() { } virtual void visit_edges(Cell::Visitor&) override; virtual bool id_reference_exists(String const&) const override; CustomElementState custom_element_state() const { return m_custom_element_state; } private: void make_html_uppercased_qualified_name(); void invalidate_style_after_attribute_change(FlyString const& attribute_name, Optional const& old_value, Optional const& new_value); WebIDL::ExceptionOr> insert_adjacent(StringView where, GC::Ref node); void enqueue_an_element_on_the_appropriate_element_queue(); Optional auto_directionality() const; Optional contained_text_auto_directionality(bool can_exclude_root) const; Directionality parent_directionality() const; bool is_auto_directionality_form_associated_element() const; QualifiedName m_qualified_name; FlyString m_html_uppercased_qualified_name; GC::Ptr m_attributes; GC::Ptr m_inline_style; GC::Ptr m_class_list; GC::Ptr m_shadow_root; GC::Ptr m_cascaded_properties; GC::Ptr m_computed_properties; HashMap m_custom_properties; struct PseudoElement { GC::Ptr layout_node; GC::Ptr cascaded_properties; GC::Ptr computed_properties; HashMap custom_properties; }; // TODO: CSS::Selector::PseudoElement includes a lot of pseudo-elements that exist in shadow trees, // and so we don't want to include data for them here. using PseudoElementData = Array; mutable OwnPtr m_pseudo_element_data; Optional get_pseudo_element(CSS::PseudoElement) const; PseudoElement& ensure_pseudo_element(CSS::PseudoElement) const; Optional m_use_pseudo_element; Vector m_classes; Optional m_dir; Optional m_id; Optional m_name; // https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-reaction-queue // All elements have an associated custom element reaction queue, initially empty. Each item in the custom element reaction queue is of one of two types: // NOTE: See the structs at the top of this header. OwnPtr m_custom_element_reaction_queue; // https://dom.spec.whatwg.org/#concept-element-custom-element-state CustomElementState m_custom_element_state { CustomElementState::Undefined }; // https://dom.spec.whatwg.org/#concept-element-custom-element-definition GC::Ptr m_custom_element_definition; // https://dom.spec.whatwg.org/#concept-element-is-value Optional m_is_value; // https://www.w3.org/TR/intersection-observer/#dom-element-registeredintersectionobservers-slot // Element objects have an internal [[RegisteredIntersectionObservers]] slot, which is initialized to an empty list. OwnPtr> m_registered_intersection_observers; Array m_scroll_offset; bool m_in_top_layer : 1 { false }; bool m_rendered_in_top_layer : 1 { false }; bool m_style_uses_css_custom_properties : 1 { false }; bool m_affected_by_has_pseudo_class_in_subject_position : 1 { false }; bool m_affected_by_has_pseudo_class_in_non_subject_position : 1 { false }; bool m_affected_by_direct_sibling_combinator : 1 { false }; bool m_affected_by_indirect_sibling_combinator : 1 { false }; bool m_affected_by_first_or_last_child_pseudo_class : 1 { false }; bool m_affected_by_nth_child_pseudo_class : 1 { false }; bool m_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator : 1 { false }; size_t m_sibling_invalidation_distance { 0 }; OwnPtr m_counters_set; GC::Ptr m_aria_active_descendant_element; // https://drafts.csswg.org/css-contain/#proximity-to-the-viewport ProximityToTheViewport m_proximity_to_the_viewport { ProximityToTheViewport::NotDetermined }; }; template<> inline bool Node::fast_is() const { return is_element(); } inline GC::Ptr Node::parent_element() { return as_if(this->parent()); } inline GC::Ptr Node::parent_element() const { return as_if(this->parent()); } inline bool Element::has_class(FlyString const& class_name, CaseSensitivity case_sensitivity) const { if (case_sensitivity == CaseSensitivity::CaseSensitive) { return any_of(m_classes, [&](auto& it) { return it == class_name; }); } return any_of(m_classes, [&](auto& it) { return it.equals_ignoring_ascii_case(class_name); }); } inline bool Element::has_pseudo_element(CSS::PseudoElement type) const { if (!m_pseudo_element_data) return false; if (!CSS::Selector::PseudoElementSelector::is_known_pseudo_element_type(type)) return false; return m_pseudo_element_data->at(to_underlying(type)).layout_node; } WebIDL::ExceptionOr validate_and_extract(JS::Realm&, Optional namespace_, FlyString const& qualified_name); }