LibWeb/DOM: Serialize pseudo-elements in the correct order

Make Element responsible for serializing all its children, so it can put
them in order.
This commit is contained in:
Sam Atkins 2025-07-02 16:13:52 +01:00
commit ffd5503dcb
Notes: github-actions[bot] 2025-07-03 08:57:58 +00:00
4 changed files with 63 additions and 26 deletions

View file

@ -1585,21 +1585,60 @@ void Element::clear_pseudo_element_nodes(Badge<Layout::TreeBuilder>)
}
}
void Element::serialize_pseudo_elements_as_json(JsonArraySerializer<StringBuilder>& children_array) const
void Element::serialize_children_as_json(JsonObjectSerializer<StringBuilder>& 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

View file

@ -288,7 +288,8 @@ public:
bool has_pseudo_element(CSS::PseudoElement) const;
bool has_pseudo_elements() const;
void clear_pseudo_element_nodes(Badge<Layout::TreeBuilder>);
void serialize_pseudo_elements_as_json(JsonArraySerializer<StringBuilder>& children_array) const;
void serialize_children_as_json(JsonObjectSerializer<StringBuilder>&) const;
i32 tab_index() const;
void set_tab_index(i32 tab_index);

View file

@ -1909,6 +1909,16 @@ bool Node::is_uninteresting_whitespace_node() const
return false;
}
IterationDecision Node::serialize_child_as_json(JsonArraySerializer<StringBuilder>& children_array, Node const& child) const
{
if (child.is_uninteresting_whitespace_node())
return IterationDecision::Continue;
JsonObjectSerializer<StringBuilder> 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<StringBuilder>& object) const
{
MUST(object.add("name"sv, node_name()));
@ -1967,29 +1977,15 @@ void Node::serialize_tree_as_json(JsonObjectSerializer<StringBuilder>& object) c
MUST((object.add("visible"sv, !!layout_node())));
auto const* element = is_element() ? static_cast<DOM::Element const*>(this) : nullptr;
if (has_child_nodes()
|| (element && (element->is_shadow_host() || element->has_pseudo_elements()))) {
if (auto const* element = as_if<Element>(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<StringBuilder> 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());
}
}

View file

@ -382,6 +382,7 @@ public:
// Used for dumping the DOM Tree
void serialize_tree_as_json(JsonObjectSerializer<StringBuilder>&) const;
IterationDecision serialize_child_as_json(JsonArraySerializer<StringBuilder>& 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;