瀏覽代碼

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.
Andreas Kling 10 月之前
父節點
當前提交
df048e10f5
共有 1 個文件被更改,包括 35 次插入11 次删除
  1. 35 11
      Userland/Libraries/LibWeb/DOM/Node.cpp

+ 35 - 11
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<DOM::Element&>(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<DOM::Element&>(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();