Files
ladybird/Tests/LibWeb/Text/input/css-has-invalidation/fast-path-unrelated-mutation.html
Andreas Kling 85ff13870f LibWeb: Stop :has() invalidation walk when out of :has() scope
A DOM mutation under a document that uses any :has() rule currently
walks every ancestor up to the root, invoking invalidate_style_if_
affected_by_has() on each. Most of those ancestors have nothing to
do with :has(), so the work scales linearly with DOM depth.

Introduce an in_has_scope flag on Element, set while evaluating :has()
arguments for invalidation metadata. StyleScope's upward invalidation
walk now terminates at the first element that is neither in :has()
scope nor a :has() anchor, so it only traverses the region where some
:has() rule might actually care about the change.

Keep the existing fast :has() matching paths for normal selector
matching, but bypass them while collecting per-element metadata so the
scope markers still get populated. Node insertion also schedules the
parent for the :has() walk so newly inserted nodes still reach the real
anchor.

The css-has-invalidation suite adds focused coverage for these shapes
and updates the expected counters to reflect the shorter walks.
2026-04-20 13:20:41 +02:00

39 lines
1.3 KiB
HTML

<!DOCTYPE html>
<script src="../include.js"></script>
<script src="_helpers.js"></script>
<style>
.anchor:has(.match) { color: red; }
</style>
<!-- "anchor" is one subtree. "outside" is another subtree that has no :has()
rule pointing at it. Mutations inside "outside" must not walk any :has()
ancestors. -->
<div id="wrapper">
<div class="anchor"><span class="match">a</span></div>
<div id="outside">
<div>
<div>
<span id="target">target</span>
</div>
</div>
</div>
</div>
<script>
test(() => {
settleAndReset();
// The class "unrelated" is not used in any selector (including :has()).
// The walker should not run at all.
document.getElementById("target").classList.add("unrelated");
getComputedStyle(document.getElementById("target")).color;
printCounters("add unrelated class outside any :has() scope");
internals.resetStyleInvalidationCounters();
// The class "match" IS used in :has(.match), but target is not inside
// any anchor's scope. The walker should still terminate immediately.
document.getElementById("target").classList.add("match");
getComputedStyle(document.getElementById("target")).color;
printCounters("add .match class outside any :has() scope");
});
</script>