From df048e10f5a84d7fd90b1115c6bb90f45acd75ec Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Thu, 19 Sep 2024 13:18:34 +0200 Subject: [PATCH] LibWeb: Include siblings+descendants when invalidating style When an element is invalidated, it's possible for any subsequent sibling or any of their descendants to also need invalidation. (Due to the CSS sibling combinators, `+` and `~`) For DOM node insertion/removal, we must also invalidate preceding siblings, since they could be affected by :first-child, :last-child or :nth-child() selectors. This increases the amount of invalidation we do, but it's more correct. In the future, we will implement optimizations that drastically reduce the number of elements invalidated. --- Userland/Libraries/LibWeb/DOM/Node.cpp | 46 ++++++++++++++++++++------ 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/Userland/Libraries/LibWeb/DOM/Node.cpp b/Userland/Libraries/LibWeb/DOM/Node.cpp index 93a07ffa7f5..a8fd88b8eba 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.cpp +++ b/Userland/Libraries/LibWeb/DOM/Node.cpp @@ -405,18 +405,42 @@ void Node::invalidate_style(StyleInvalidationReason reason) return; } - for_each_in_inclusive_subtree([&](Node& node) { - node.m_needs_style_update = true; - if (node.has_children()) - node.m_child_needs_style_update = true; - if (auto shadow_root = node.is_element() ? static_cast(node).shadow_root() : nullptr) { - node.m_child_needs_style_update = true; - shadow_root->m_needs_style_update = true; - if (shadow_root->has_children()) - shadow_root->m_child_needs_style_update = true; + // When invalidating style for a node, we actually invalidate: + // - the node itself + // - all of its descendants + // - all of its preceding siblings and their descendants (only on DOM insert/remove) + // - all of its subsequent siblings and their descendants + // FIXME: This is a lot of invalidation and we should implement more sophisticated invalidation to do less work! + + auto invalidate_entire_subtree = [&](Node& subtree_root) { + subtree_root.for_each_in_inclusive_subtree([&](Node& node) { + node.m_needs_style_update = true; + if (node.has_children()) + node.m_child_needs_style_update = true; + if (auto shadow_root = node.is_element() ? static_cast(node).shadow_root() : nullptr) { + node.m_child_needs_style_update = true; + shadow_root->m_needs_style_update = true; + if (shadow_root->has_children()) + shadow_root->m_child_needs_style_update = true; + } + return TraversalDecision::Continue; + }); + }; + + invalidate_entire_subtree(*this); + + if (reason == StyleInvalidationReason::NodeInsertBefore || reason == StyleInvalidationReason::NodeRemove) { + for (auto* sibling = previous_sibling(); sibling; sibling = sibling->previous_sibling()) { + if (sibling->is_element()) + invalidate_entire_subtree(*sibling); } - return TraversalDecision::Continue; - }); + } + + for (auto* sibling = next_sibling(); sibling; sibling = sibling->next_sibling()) { + if (sibling->is_element()) + invalidate_entire_subtree(*sibling); + } + for (auto* ancestor = parent_or_shadow_host(); ancestor; ancestor = ancestor->parent_or_shadow_host()) ancestor->m_child_needs_style_update = true; document().schedule_style_update();