mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 01:35:08 +02:00
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.
36 lines
1.0 KiB
HTML
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>
|