From 302752d09231992f58b351af3f362ac37a7ebe1f Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Thu, 4 Jan 2024 13:46:55 +0100 Subject: [PATCH] LibWeb: Ignore layout/paint invalidations in subtree under display:none The entire subtree of an element with display:none is irrelevant for purposes of layout and/or paint invalidation. We now simply ignore invalidation triggers inside such subtrees. This avoids a *lot* of redundant busywork when running CSS animations inside not-even-rendered content. As an example, this avoids repainting YouTube embeds repeatedly due to animating-but-hidden progress indicators. Note that the subtree *root* (i.e the `display:none` element itself) still gets to trigger invalidation, since we may need to rebuild the layout tree when the `display` property changes. --- Userland/Libraries/LibWeb/DOM/Document.cpp | 23 +++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 4b70f917fc..189027e003 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -1077,26 +1077,39 @@ void Document::update_layout() m_layout_update_timer->stop(); } -[[nodiscard]] static Element::RequiredInvalidationAfterStyleChange update_style_recursively(DOM::Node& node) +[[nodiscard]] static Element::RequiredInvalidationAfterStyleChange update_style_recursively(Node& node) { bool const needs_full_style_update = node.document().needs_full_style_update(); Element::RequiredInvalidationAfterStyleChange invalidation; + // NOTE: If the current node has `display:none`, we can disregard all invalidation + // caused by its children, as they will not be rendered anyway. + // We will still recompute style for the children, though. + bool is_display_none = false; + if (is(node)) { invalidation |= static_cast(node).recompute_style(); + is_display_none = static_cast(node).computed_css_values()->display().is_none(); } node.set_needs_style_update(false); if (needs_full_style_update || node.child_needs_style_update()) { if (node.is_element()) { if (auto* shadow_root = static_cast(node).shadow_root_internal()) { - if (needs_full_style_update || shadow_root->needs_style_update() || shadow_root->child_needs_style_update()) - invalidation |= update_style_recursively(*shadow_root); + if (needs_full_style_update || shadow_root->needs_style_update() || shadow_root->child_needs_style_update()) { + auto subtree_invalidation = update_style_recursively(*shadow_root); + if (!is_display_none) + invalidation |= subtree_invalidation; + } } } + node.for_each_child([&](auto& child) { - if (needs_full_style_update || child.needs_style_update() || child.child_needs_style_update()) - invalidation |= update_style_recursively(child); + if (needs_full_style_update || child.needs_style_update() || child.child_needs_style_update()) { + auto subtree_invalidation = update_style_recursively(child); + if (!is_display_none) + invalidation |= subtree_invalidation; + } return IterationDecision::Continue; }); }