From ffd5503dcb936c96f10197fae8f5624fe7547ed3 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Wed, 2 Jul 2025 16:13:52 +0100 Subject: [PATCH] LibWeb/DOM: Serialize pseudo-elements in the correct order Make Element responsible for serializing all its children, so it can put them in order. --- Libraries/LibWeb/DOM/Element.cpp | 49 ++++++++++++++++++++++++++++---- Libraries/LibWeb/DOM/Element.h | 3 +- Libraries/LibWeb/DOM/Node.cpp | 36 +++++++++++------------ Libraries/LibWeb/DOM/Node.h | 1 + 4 files changed, 63 insertions(+), 26 deletions(-) diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 27a56d41f88..e8fc97f6ac3 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -1585,21 +1585,60 @@ void Element::clear_pseudo_element_nodes(Badge) } } -void Element::serialize_pseudo_elements_as_json(JsonArraySerializer& children_array) const +void Element::serialize_children_as_json(JsonObjectSerializer& element_object) const { - if (!m_pseudo_element_data) + bool has_pseudo_elements = this->has_pseudo_elements(); + if (!is_shadow_host() && !has_child_nodes() && !has_pseudo_elements) return; - for (auto const& [pseudo_element_type, pseudo_element] : (*m_pseudo_element_data)) { + + auto children = MUST(element_object.add_array("children"sv)); + + auto serialize_pseudo_element = [&](CSS::PseudoElement pseudo_element_type, auto const& pseudo_element) { // FIXME: Find a way to make these still inspectable? (eg, `::before { display: none }`) if (!pseudo_element->layout_node()) - continue; - auto object = MUST(children_array.add_object()); + return; + auto object = MUST(children.add_object()); MUST(object.add("name"sv, MUST(String::formatted("::{}", CSS::pseudo_element_name(pseudo_element_type))))); MUST(object.add("type"sv, "pseudo-element")); MUST(object.add("parent-id"sv, unique_id().value())); MUST(object.add("pseudo-element"sv, to_underlying(pseudo_element_type))); MUST(object.finish()); + }; + + if (has_pseudo_elements) { + if (auto backdrop = m_pseudo_element_data->get(CSS::PseudoElement::Backdrop); backdrop.has_value()) { + serialize_pseudo_element(CSS::PseudoElement::Backdrop, backdrop.value()); + } + if (auto marker = m_pseudo_element_data->get(CSS::PseudoElement::Marker); marker.has_value()) { + serialize_pseudo_element(CSS::PseudoElement::Marker, marker.value()); + } + if (auto before = m_pseudo_element_data->get(CSS::PseudoElement::Before); before.has_value()) { + serialize_pseudo_element(CSS::PseudoElement::Before, before.value()); + } } + + if (is_shadow_host()) + serialize_child_as_json(children, *shadow_root()); + + auto add_child = [this, &children](Node const& child) { + return serialize_child_as_json(children, child); + }; + for_each_child(add_child); + + if (has_pseudo_elements) { + if (auto after = m_pseudo_element_data->get(CSS::PseudoElement::After); after.has_value()) { + serialize_pseudo_element(CSS::PseudoElement::After, after.value()); + } + + // Any other pseudo-elements, as a catch-all. + for (auto const& [type, pseudo_element] : *m_pseudo_element_data) { + if (first_is_one_of(type, CSS::PseudoElement::After, CSS::PseudoElement::Backdrop, CSS::PseudoElement::Before, CSS::PseudoElement::Marker)) + continue; + serialize_pseudo_element(type, pseudo_element); + } + } + + MUST(children.finish()); } // https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex diff --git a/Libraries/LibWeb/DOM/Element.h b/Libraries/LibWeb/DOM/Element.h index 291623bbdc7..ebe0e4e61b8 100644 --- a/Libraries/LibWeb/DOM/Element.h +++ b/Libraries/LibWeb/DOM/Element.h @@ -288,7 +288,8 @@ public: 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; + + void serialize_children_as_json(JsonObjectSerializer&) const; i32 tab_index() const; void set_tab_index(i32 tab_index); diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index ac5894f2738..f2b4b3b50b2 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -1909,6 +1909,16 @@ bool Node::is_uninteresting_whitespace_node() const return false; } +IterationDecision Node::serialize_child_as_json(JsonArraySerializer& children_array, Node const& child) const +{ + if (child.is_uninteresting_whitespace_node()) + return IterationDecision::Continue; + JsonObjectSerializer child_object = MUST(children_array.add_object()); + child.serialize_tree_as_json(child_object); + MUST(child_object.finish()); + return IterationDecision::Continue; +} + void Node::serialize_tree_as_json(JsonObjectSerializer& object) const { MUST(object.add("name"sv, node_name())); @@ -1967,29 +1977,15 @@ void Node::serialize_tree_as_json(JsonObjectSerializer& object) c MUST((object.add("visible"sv, !!layout_node()))); - auto const* element = is_element() ? static_cast(this) : nullptr; - - if (has_child_nodes() - || (element && (element->is_shadow_host() || element->has_pseudo_elements()))) { + if (auto const* element = as_if(this)) { + element->serialize_children_as_json(object); + } else if (has_child_nodes()) { auto children = MUST(object.add_array("children"sv)); - auto add_child = [&children](DOM::Node const& child) { - if (child.is_uninteresting_whitespace_node()) - return IterationDecision::Continue; - JsonObjectSerializer child_object = MUST(children.add_object()); - child.serialize_tree_as_json(child_object); - MUST(child_object.finish()); - return IterationDecision::Continue; + auto add_child = [this, &children](Node const& child) { + return serialize_child_as_json(children, child); }; + for_each_child(add_child); - - if (element) { - // Pseudo-elements don't have DOM nodes,so we have to add them separately. - element->serialize_pseudo_elements_as_json(children); - - if (element->is_shadow_host()) - add_child(*element->shadow_root()); - } - MUST(children.finish()); } } diff --git a/Libraries/LibWeb/DOM/Node.h b/Libraries/LibWeb/DOM/Node.h index 31d8559eab8..e364f4971a0 100644 --- a/Libraries/LibWeb/DOM/Node.h +++ b/Libraries/LibWeb/DOM/Node.h @@ -382,6 +382,7 @@ public: // Used for dumping the DOM Tree void serialize_tree_as_json(JsonObjectSerializer&) const; + IterationDecision serialize_child_as_json(JsonArraySerializer& children_array, Node const& child) const; bool is_shadow_including_descendant_of(Node const&) const; bool is_shadow_including_inclusive_descendant_of(Node const&) const;