From 9b26f7eb0faaca63ee9109c6eda44c82a6acfeaf Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Thu, 6 Mar 2025 21:13:21 +0100 Subject: [PATCH] LibWeb: Use partial layout tree rebuild in element's style invalidation This allows us to avoid a full layout tree rebuild after change of "display" property, which happens frequently in practice. It also allows us to avoid a full rebuild after DOM node insertion, since previously, computing styles for newly inserted nodes would trigger a complete layout tree rebuild. --- Libraries/LibWeb/DOM/Document.cpp | 32 +++++++++++++++++++------------ Libraries/LibWeb/DOM/Node.cpp | 3 +++ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 1a389704285..b00b05d38a4 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -1403,14 +1403,25 @@ void Document::update_layout() // We will still recompute style for the children, though. bool is_display_none = false; + CSS::RequiredInvalidationAfterStyleChange node_invalidation; if (is(node)) { if (needs_full_style_update || node.needs_style_update()) { - invalidation |= static_cast(node).recompute_style(); + node_invalidation = static_cast(node).recompute_style(); } else if (needs_inherited_style_update) { - invalidation |= static_cast(node).recompute_inherited_style(); + node_invalidation = static_cast(node).recompute_inherited_style(); } is_display_none = static_cast(node).computed_properties()->display().is_none(); } + if (node_invalidation.rebuild_layout_tree) { + // We mark layout tree for rebuild starting from parent element to correctly invalidate + // "display" property change to/from "contents" value. + if (auto* parent_element = node.parent_element()) { + parent_element->set_needs_layout_tree_update(true); + } else { + node.set_needs_layout_tree_update(true); + } + } + invalidation |= node_invalidation; node.set_needs_style_update(false); bool children_need_inherited_style_update = !invalidation.is_none(); @@ -1501,17 +1512,12 @@ void Document::update_style() style_computer().reset_ancestor_filter(); auto invalidation = update_style_recursively(*this, style_computer(), false); - if (!invalidation.is_none()) { + if (!invalidation.is_none()) invalidate_display_list(); - } - if (invalidation.rebuild_layout_tree) { - invalidate_layout_tree(); - } else { - if (invalidation.relayout) - set_needs_layout(); - if (invalidation.rebuild_stacking_context_tree) - invalidate_stacking_context_tree(); - } + if (invalidation.relayout) + set_needs_layout(); + if (invalidation.rebuild_stacking_context_tree) + invalidate_stacking_context_tree(); m_needs_full_style_update = false; } @@ -5937,6 +5943,7 @@ void Document::add_an_element_to_the_top_layer(GC::Ref element) // FIXME: 4. At the UA !important cascade origin, add a rule targeting el containing an overlay: auto declaration. element->set_rendered_in_top_layer(true); element->set_needs_style_update(true); + invalidate_layout_tree(); } // https://drafts.csswg.org/css-position-4/#request-an-element-to-be-removed-from-the-top-layer @@ -5951,6 +5958,7 @@ void Document::request_an_element_to_be_remove_from_the_top_layer(GC::Refset_rendered_in_top_layer(false); element->set_needs_style_update(true); + invalidate_layout_tree(); // 4. Append el to doc’s pending top layer removals. m_top_layer_pending_removals.set(element); diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index 8f294010206..3cd48e5b3a7 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -787,6 +787,9 @@ void Node::insert_before(GC::Ref node, GC::Ptr child, bool suppress_ } if (is_connected()) { + if (layout_node() && layout_node()->display().is_contents() && parent_element()) { + parent_element()->set_needs_layout_tree_update(true); + } set_needs_layout_tree_update(true); }