/* * 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 namespace Web::DOM { enum class NodeType : u16 { INVALID = 0, ELEMENT_NODE = 1, ATTRIBUTE_NODE = 2, TEXT_NODE = 3, CDATA_SECTION_NODE = 4, ENTITY_REFERENCE_NODE = 5, ENTITY_NODE = 6, PROCESSING_INSTRUCTION_NODE = 7, COMMENT_NODE = 8, DOCUMENT_NODE = 9, DOCUMENT_TYPE_NODE = 10, DOCUMENT_FRAGMENT_NODE = 11, NOTATION_NODE = 12 }; enum class NameOrDescription { Name, Description }; struct GetRootNodeOptions { bool composed { false }; }; enum class FragmentSerializationMode { Inner, Outer, }; enum class IsDescendant { No, Yes, }; enum class ShouldComputeRole { No, Yes, }; #define ENUMERATE_STYLE_INVALIDATION_REASONS(X) \ X(ActiveElementChange) \ X(AdoptedStyleSheetsList) \ X(CSSFontLoaded) \ X(CSSImportRule) \ X(CustomElementStateChange) \ X(DidLoseFocus) \ X(DidReceiveFocus) \ X(EditingInsertion) \ X(ElementAttributeChange) \ X(ElementSetShadowRoot) \ X(FocusedElementChange) \ X(HTMLHyperlinkElementHrefChange) \ X(HTMLIFrameElementGeometryChange) \ X(HTMLInputElementSetChecked) \ X(HTMLObjectElementUpdateLayoutAndChildObjects) \ X(HTMLOptionElementSelectedChange) \ X(HTMLSelectElementSetIsOpen) \ X(Hover) \ X(MediaQueryChangedMatchState) \ X(NavigableSetViewportSize) \ X(NodeInsertBefore) \ X(NodeRemove) \ X(NodeSetTextContent) \ X(Other) \ X(ParentOfInsertedNode) \ X(SetSelectorText) \ X(SettingsChange) \ X(StyleSheetDeleteRule) \ X(StyleSheetInsertRule) \ X(StyleSheetListAddSheet) \ X(StyleSheetListRemoveSheet) \ X(TargetElementChange) enum class StyleInvalidationReason { #define __ENUMERATE_STYLE_INVALIDATION_REASON(reason) reason, ENUMERATE_STYLE_INVALIDATION_REASONS(__ENUMERATE_STYLE_INVALIDATION_REASON) #undef __ENUMERATE_STYLE_INVALIDATION_REASON }; class Node : public EventTarget { WEB_PLATFORM_OBJECT(Node, EventTarget); public: ParentNode* parent_or_shadow_host(); ParentNode const* parent_or_shadow_host() const { return const_cast(this)->parent_or_shadow_host(); } Element* parent_or_shadow_host_element(); Element const* parent_or_shadow_host_element() const { return const_cast(this)->parent_or_shadow_host_element(); } virtual ~Node(); NodeType type() const { return m_type; } bool is_element() const { return type() == NodeType::ELEMENT_NODE; } bool is_text() const { return type() == NodeType::TEXT_NODE || type() == NodeType::CDATA_SECTION_NODE; } bool is_exclusive_text() const { return type() == NodeType::TEXT_NODE; } bool is_document() const { return type() == NodeType::DOCUMENT_NODE; } bool is_document_type() const { return type() == NodeType::DOCUMENT_TYPE_NODE; } bool is_comment() const { return type() == NodeType::COMMENT_NODE; } bool is_character_data() const { return first_is_one_of(type(), NodeType::TEXT_NODE, NodeType::COMMENT_NODE, NodeType::CDATA_SECTION_NODE, NodeType::PROCESSING_INSTRUCTION_NODE); } bool is_document_fragment() const { return type() == NodeType::DOCUMENT_FRAGMENT_NODE; } bool is_parent_node() const { return is_element() || is_document() || is_document_fragment(); } bool is_slottable() const { return is_element() || is_text() || is_cdata_section(); } bool is_attribute() const { return type() == NodeType::ATTRIBUTE_NODE; } bool is_cdata_section() const { return type() == NodeType::CDATA_SECTION_NODE; } virtual bool is_shadow_root() const { return false; } virtual bool requires_svg_container() const { return false; } virtual bool is_svg_container() const { return false; } virtual bool is_svg_element() const { return false; } virtual bool is_svg_graphics_element() const { return false; } virtual bool is_svg_script_element() const { return false; } virtual bool is_svg_style_element() const { return false; } virtual bool is_svg_svg_element() const { return false; } virtual bool is_svg_use_element() const { return false; } bool in_a_document_tree() const; // NOTE: This is intended for the JS bindings. u16 node_type() const { return (u16)m_type; } bool is_editable() const; bool is_editing_host() const; bool is_editable_or_editing_host() const { return is_editable() || is_editing_host(); } virtual bool is_dom_node() const final { return true; } virtual bool is_html_element() const { return false; } virtual bool is_html_html_element() const { return false; } virtual bool is_html_anchor_element() const { return false; } virtual bool is_html_base_element() const { return false; } virtual bool is_html_body_element() const { return false; } virtual bool is_html_input_element() const { return false; } virtual bool is_html_link_element() const { return false; } virtual bool is_html_progress_element() const { return false; } virtual bool is_html_script_element() const { return false; } virtual bool is_html_style_element() const { return false; } virtual bool is_html_template_element() const { return false; } virtual bool is_html_table_element() const { return false; } virtual bool is_html_table_section_element() const { return false; } virtual bool is_html_table_row_element() const { return false; } virtual bool is_html_table_cell_element() const { return false; } virtual bool is_html_br_element() const { return false; } virtual bool is_html_button_element() const { return false; } virtual bool is_html_slot_element() const { return false; } virtual bool is_html_embed_element() const { return false; } virtual bool is_html_object_element() const { return false; } virtual bool is_html_form_element() const { return false; } virtual bool is_html_image_element() const { return false; } virtual bool is_navigable_container() const { return false; } virtual bool is_lazy_loading() const { return false; } WebIDL::ExceptionOr> pre_insert(GC::Ref, GC::Ptr); WebIDL::ExceptionOr> pre_remove(GC::Ref); WebIDL::ExceptionOr> append_child(GC::Ref); WebIDL::ExceptionOr> remove_child(GC::Ref); void insert_before(GC::Ref node, GC::Ptr child, bool suppress_observers = false); void remove(bool suppress_observers = false); void remove_all_children(bool suppress_observers = false); enum DocumentPosition : u16 { DOCUMENT_POSITION_EQUAL = 0, DOCUMENT_POSITION_DISCONNECTED = 1, DOCUMENT_POSITION_PRECEDING = 2, DOCUMENT_POSITION_FOLLOWING = 4, DOCUMENT_POSITION_CONTAINS = 8, DOCUMENT_POSITION_CONTAINED_BY = 16, DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32, }; u16 compare_document_position(GC::Ptr other); WebIDL::ExceptionOr> replace_child(GC::Ref node, GC::Ref child); WebIDL::ExceptionOr> clone_node(Document* document = nullptr, bool subtree = false, Node* parent = nullptr); WebIDL::ExceptionOr> clone_single_node(Document&); WebIDL::ExceptionOr> clone_node_binding(bool subtree); // NOTE: This is intended for the JS bindings. bool has_child_nodes() const { return has_children(); } GC::Ref child_nodes(); Vector> children_as_vector() const; virtual FlyString node_name() const = 0; String base_uri() const; virtual Optional alternative_text() const; String descendant_text_content() const; Optional text_content() const; void set_text_content(Optional const&); WebIDL::ExceptionOr normalize(); Optional node_value() const; void set_node_value(Optional const&); GC::Ptr navigable() const; Document& document() { return *m_document; } Document const& document() const { return *m_document; } GC::Ptr owner_document() const; const HTML::HTMLAnchorElement* enclosing_link_element() const; const HTML::HTMLElement* enclosing_html_element() const; const HTML::HTMLElement* enclosing_html_element_with_attribute(FlyString const&) const; String child_text_content() const; Node& root(); Node const& root() const { return const_cast(this)->root(); } Node& shadow_including_root(); Node const& shadow_including_root() const { return const_cast(this)->shadow_including_root(); } bool is_connected() const; [[nodiscard]] bool is_browsing_context_connected() const; Node* parent_node() { return parent(); } Node const* parent_node() const { return parent(); } Element* parent_element(); Element const* parent_element() const; virtual void inserted(); virtual void post_connection(); virtual void removed_from(Node*); virtual void children_changed() { } virtual void adopted_from(Document&) { } virtual WebIDL::ExceptionOr cloned(Node&, bool) { return {}; } Layout::Node const* layout_node() const { return m_layout_node; } Layout::Node* layout_node() { return m_layout_node; } Painting::PaintableBox const* paintable_box() const; Painting::PaintableBox* paintable_box(); Painting::Paintable const* paintable() const; Painting::Paintable* paintable(); void set_paintable(GC::Ptr); void clear_paintable(); void set_layout_node(Badge, GC::Ref); void detach_layout_node(Badge); virtual bool is_child_allowed(Node const&) const { return true; } bool needs_style_update() const { return m_needs_style_update; } void set_needs_style_update(bool); bool needs_inherited_style_update() const { return m_needs_inherited_style_update; } void set_needs_inherited_style_update(bool); bool child_needs_style_update() const { return m_child_needs_style_update; } void set_child_needs_style_update(bool b) { m_child_needs_style_update = b; } void invalidate_style(StyleInvalidationReason); void set_document(Badge, Document&); virtual EventTarget* get_parent(Event const&) override; template bool fast_is() const = delete; WebIDL::ExceptionOr ensure_pre_insertion_validity(GC::Ref node, GC::Ptr child) const; bool is_host_including_inclusive_ancestor_of(Node const&) const; bool is_scripting_enabled() const; bool is_scripting_disabled() const; bool contains(GC::Ptr) const; // Used for dumping the DOM Tree void serialize_tree_as_json(JsonObjectSerializer&) const; bool is_shadow_including_descendant_of(Node const&) const; bool is_shadow_including_inclusive_descendant_of(Node const&) const; bool is_shadow_including_ancestor_of(Node const&) const; bool is_shadow_including_inclusive_ancestor_of(Node const&) const; [[nodiscard]] UniqueNodeID unique_id() const { return m_unique_id; } static Node* from_unique_id(UniqueNodeID); WebIDL::ExceptionOr serialize_fragment(DOMParsing::RequireWellFormed, FragmentSerializationMode = FragmentSerializationMode::Inner) const; WebIDL::ExceptionOr unsafely_set_html(Element&, StringView); void replace_all(GC::Ptr); void string_replace_all(String const&); bool is_same_node(Node const*) const; bool is_equal_node(Node const*) const; GC::Ref get_root_node(GetRootNodeOptions const& options = {}); bool is_uninteresting_whitespace_node() const; String debug_description() const; size_t length() const; auto& registered_observer_list() { return m_registered_observer_list; } auto const& registered_observer_list() const { return m_registered_observer_list; } void add_registered_observer(RegisteredObserver&); void queue_mutation_record(FlyString const& type, Optional const& attribute_name, Optional const& attribute_namespace, Optional const& old_value, Vector> added_nodes, Vector> removed_nodes, Node* previous_sibling, Node* next_sibling) const; // https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-descendant template TraversalDecision for_each_shadow_including_inclusive_descendant(Callback); // https://dom.spec.whatwg.org/#concept-shadow-including-descendant template TraversalDecision for_each_shadow_including_descendant(Callback); Slottable as_slottable(); Node* parent() { return m_parent.ptr(); } Node const* parent() const { return m_parent.ptr(); } bool has_children() const { return m_first_child; } Node* next_sibling() { return m_next_sibling.ptr(); } Node* previous_sibling() { return m_previous_sibling.ptr(); } Node* first_child() { return m_first_child.ptr(); } Node* last_child() { return m_last_child.ptr(); } Node const* next_sibling() const { return m_next_sibling.ptr(); } Node const* previous_sibling() const { return m_previous_sibling.ptr(); } Node const* first_child() const { return m_first_child.ptr(); } Node const* last_child() const { return m_last_child.ptr(); } size_t child_count() const { size_t count = 0; for (auto* child = first_child(); child; child = child->next_sibling()) ++count; return count; } Node* child_at_index(int index) { int count = 0; for (auto* child = first_child(); child; child = child->next_sibling()) { if (count == index) return child; ++count; } return nullptr; } Node const* child_at_index(int index) const { return const_cast(this)->child_at_index(index); } // https://dom.spec.whatwg.org/#concept-tree-index size_t index() const { // The index of an object is its number of preceding siblings, or 0 if it has none. size_t index = 0; for (auto* node = previous_sibling(); node; node = node->previous_sibling()) ++index; return index; } bool is_ancestor_of(Node const&) const; bool is_inclusive_ancestor_of(Node const&) const; bool is_descendant_of(Node const&) const; bool is_inclusive_descendant_of(Node const&) const; bool is_following(Node const&) const; Node* next_in_pre_order() { if (first_child()) return first_child(); Node* node; if (!(node = next_sibling())) { node = parent(); while (node && !node->next_sibling()) node = node->parent(); if (node) node = node->next_sibling(); } return node; } Node* next_in_pre_order(Node const* stay_within) { if (first_child()) return first_child(); Node* node = static_cast(this); Node* next = nullptr; while (!(next = node->next_sibling())) { node = node->parent(); if (!node || node == stay_within) return nullptr; } return next; } Node const* next_in_pre_order() const { return const_cast(this)->next_in_pre_order(); } Node const* next_in_pre_order(Node const* stay_within) const { return const_cast(this)->next_in_pre_order(stay_within); } Node* previous_in_pre_order() { if (auto* node = previous_sibling()) { while (node->last_child()) node = node->last_child(); return node; } return parent(); } Node const* previous_in_pre_order() const { return const_cast(this)->previous_in_pre_order(); } bool is_before(Node const& other) const { if (this == &other) return false; for (auto* node = this; node; node = node->next_in_pre_order()) { if (node == &other) return true; } return false; } // https://dom.spec.whatwg.org/#concept-tree-preceding (Object A is 'typename U' and Object B is 'this') template bool has_preceding_node_of_type_in_tree_order() const { for (auto* node = previous_in_pre_order(); node; node = node->previous_in_pre_order()) { if (is(node)) return true; } return false; } // https://dom.spec.whatwg.org/#concept-tree-following (Object A is 'typename U' and Object B is 'this') template bool has_following_node_of_type_in_tree_order() const { for (auto* node = next_in_pre_order(); node; node = node->next_in_pre_order()) { if (is(node)) return true; } return false; } template TraversalDecision for_each_in_inclusive_subtree(Callback callback) const { if (auto decision = callback(static_cast(*this)); decision != TraversalDecision::Continue) return decision; for (auto* child = first_child(); child; child = child->next_sibling()) { if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break) return TraversalDecision::Break; } return TraversalDecision::Continue; } template TraversalDecision for_each_in_inclusive_subtree(Callback callback) { if (auto decision = callback(static_cast(*this)); decision != TraversalDecision::Continue) return decision; for (auto* child = first_child(); child; child = child->next_sibling()) { if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break) return TraversalDecision::Break; } return TraversalDecision::Continue; } template TraversalDecision for_each_in_inclusive_subtree_of_type(Callback callback) { if (is(static_cast(*this))) { if (auto decision = callback(static_cast(*this)); decision != TraversalDecision::Continue) return decision; } for (auto* child = first_child(); child; child = child->next_sibling()) { if (child->template for_each_in_inclusive_subtree_of_type(callback) == TraversalDecision::Break) return TraversalDecision::Break; } return TraversalDecision::Continue; } template TraversalDecision for_each_in_inclusive_subtree_of_type(Callback callback) const { if (is(static_cast(*this))) { if (auto decision = callback(static_cast(*this)); decision != TraversalDecision::Continue) return decision; } for (auto* child = first_child(); child; child = child->next_sibling()) { if (child->template for_each_in_inclusive_subtree_of_type(callback) == TraversalDecision::Break) return TraversalDecision::Break; } return TraversalDecision::Continue; } template TraversalDecision for_each_in_subtree(Callback callback) const { for (auto* child = first_child(); child; child = child->next_sibling()) { if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break) return TraversalDecision::Break; } return TraversalDecision::Continue; } template TraversalDecision for_each_in_subtree(Callback callback) { for (auto* child = first_child(); child; child = child->next_sibling()) { if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break) return TraversalDecision::Break; } return TraversalDecision::Continue; } template TraversalDecision for_each_in_subtree_of_type(Callback callback) { for (auto* child = first_child(); child; child = child->next_sibling()) { if (child->template for_each_in_inclusive_subtree_of_type(callback) == TraversalDecision::Break) return TraversalDecision::Break; } return TraversalDecision::Continue; } template TraversalDecision for_each_in_subtree_of_type(Callback callback) const { for (auto* child = first_child(); child; child = child->next_sibling()) { if (child->template for_each_in_inclusive_subtree_of_type(callback) == TraversalDecision::Break) return TraversalDecision::Break; } return TraversalDecision::Continue; } template void for_each_child(Callback callback) const { return const_cast(this)->for_each_child(move(callback)); } template void for_each_child(Callback callback) { for (auto* node = first_child(); node; node = node->next_sibling()) { if (callback(*node) == IterationDecision::Break) return; } } template void for_each_child_of_type(Callback callback) { for (auto* node = first_child(); node; node = node->next_sibling()) { if (is(node)) { if (callback(verify_cast(*node)) == IterationDecision::Break) return; } } } template void for_each_child_of_type(Callback callback) const { return const_cast(this)->template for_each_child_of_type(move(callback)); } template WebIDL::ExceptionOr for_each_child_of_type_fallible(Callback callback) { for (auto* node = first_child(); node; node = node->next_sibling()) { if (is(node)) { if (TRY(callback(verify_cast(*node))) == IterationDecision::Break) return {}; } } return {}; } template U const* next_sibling_of_type() const { return const_cast(this)->template next_sibling_of_type(); } template inline U* next_sibling_of_type() { for (auto* sibling = next_sibling(); sibling; sibling = sibling->next_sibling()) { if (is(*sibling)) return &verify_cast(*sibling); } return nullptr; } template U const* previous_sibling_of_type() const { return const_cast(this)->template previous_sibling_of_type(); } template U* previous_sibling_of_type() { for (auto* sibling = previous_sibling(); sibling; sibling = sibling->previous_sibling()) { if (is(*sibling)) return &verify_cast(*sibling); } return nullptr; } template U const* first_child_of_type() const { return const_cast(this)->template first_child_of_type(); } template U const* last_child_of_type() const { return const_cast(this)->template last_child_of_type(); } template U* first_child_of_type() { for (auto* child = first_child(); child; child = child->next_sibling()) { if (is(*child)) return &verify_cast(*child); } return nullptr; } template U* last_child_of_type() { for (auto* child = last_child(); child; child = child->previous_sibling()) { if (is(*child)) return &verify_cast(*child); } return nullptr; } template bool has_child_of_type() const { return first_child_of_type() != nullptr; } template U const* first_ancestor_of_type() const { return const_cast(this)->template first_ancestor_of_type(); } template U* first_ancestor_of_type() { for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) { if (is(*ancestor)) return &verify_cast(*ancestor); } return nullptr; } template U const* shadow_including_first_ancestor_of_type() const { return const_cast(this)->template shadow_including_first_ancestor_of_type(); } template U* shadow_including_first_ancestor_of_type(); bool is_parent_of(Node const& other) const { for (auto* child = first_child(); child; child = child->next_sibling()) { if (&other == child) return true; } return false; } ErrorOr accessible_name(Document const&, ShouldComputeRole = ShouldComputeRole::Yes) const; ErrorOr accessible_description(Document const&) const; Optional locate_a_namespace(Optional const& prefix) const; Optional lookup_namespace_uri(Optional prefix) const; Optional lookup_prefix(Optional namespace_) const; bool is_default_namespace(Optional namespace_) const; protected: Node(JS::Realm&, Document&, NodeType); Node(Document&, NodeType); void set_document(Document&); virtual void visit_edges(Cell::Visitor&) override; virtual void finalize() override; GC::Ptr m_document; GC::Ptr m_layout_node; GC::Ptr m_paintable; NodeType m_type { NodeType::INVALID }; bool m_needs_style_update { false }; bool m_needs_inherited_style_update { false }; bool m_child_needs_style_update { false }; UniqueNodeID m_unique_id; // https://dom.spec.whatwg.org/#registered-observer-list // "Nodes have a strong reference to registered observers in their registered observer list." https://dom.spec.whatwg.org/#garbage-collection OwnPtr>> m_registered_observer_list; void build_accessibility_tree(AccessibilityTreeNode& parent); ErrorOr name_or_description(NameOrDescription, Document const&, HashTable&, IsDescendant = IsDescendant::No, ShouldComputeRole = ShouldComputeRole::Yes) const; private: void queue_tree_mutation_record(Vector> added_nodes, Vector> removed_nodes, Node* previous_sibling, Node* next_sibling); void insert_before_impl(GC::Ref, GC::Ptr child); void append_child_impl(GC::Ref); void remove_child_impl(GC::Ref); static Optional first_valid_id(StringView, Document const&); GC::Ptr m_parent; GC::Ptr m_first_child; GC::Ptr m_last_child; GC::Ptr m_next_sibling; GC::Ptr m_previous_sibling; GC::Ptr m_child_nodes; }; } template<> inline bool JS::Object::fast_is() const { return is_dom_node(); }