Files
ladybird/Tests/LibWeb/Text/input/css/has-result-caching.html
Aliaksandr Kalenik 3353ec9663 LibWeb: Add result caching for :has() pseudo-class matching
The `:has()` pseudo-class requires traversing descendants (or siblings)
to find matches.

With this change we cache results keyed by `(Selector*, Element*)`
pairs. The cache is stored in `StyleComputer` and cleared at the start
of each style computation pass in `Document::update_style()`.

When `:has()` uses a descendant combinator and we find a match, we also
cache that all ancestors between the matching descendant and the
anchor match. For example with `div:has(.target)`:

```html
<div id="A">  <!-- checking :has(.target) here -->
  <div id="B">
    <div id="C">
      <span class="target"/>
    </div>
  </div>
</div>
```

When we find `.target` while checking `div#A`, we also cache that
`div#B` and `div#C` match `:has(.target)` since they also contain
`.target`. Later when styling these elements, we get cache hits and skip
traversal.
2026-01-04 19:36:40 +01:00

36 lines
1.0 KiB
HTML

<!DOCTYPE html>
<style>
div:has(.target) { --match: 1; }
</style>
<div id="root"></div>
<script src="../include.js"></script>
<script>
test(() => {
const root = document.getElementById("root");
// Build nested structure: root > d0 > d1 > ... > d19 > span.target
let current = root;
for (let i = 0; i < 20; i++) {
const div = document.createElement("div");
div.id = `d${i}`;
current.appendChild(div);
current = div;
}
current.innerHTML = '<span class="target"></span>';
// Force style computation
document.body.offsetHeight;
// All ancestors should match :has(.target)
for (let i = 0; i < 20; i++) {
const el = document.getElementById(`d${i}`);
const val = getComputedStyle(el).getPropertyValue("--match").trim();
if (val !== "1") {
println(`FAIL: d${i} should match :has(.target)`);
return;
}
}
println("PASS");
});
</script>