ladybird/Tests/LibWeb/Text/input/css/scope-import-invalidation.html
Sam Atkins 18ac54a402 LibWeb: Invalidate styles for scoped imports
Include scoped import start and end selectors in stylesheet selector
insights, and treat scoped `@import` additions or removals as broad
stylesheet invalidation triggers. Scoped import boundaries can change
which descendants match imported rules, so add/remove invalidation
cannot rely only on the imported style rule selectors.

Add local coverage for changing an import-scope root, changing an end
boundary, replacing or removing a scoped import rule, selector-insight
tracking, and stylesheet removal invalidation.
2026-06-04 20:52:28 +01:00

85 lines
3.8 KiB
HTML

<!DOCTYPE html>
<script src="../include.js"></script>
<style id="root-style"></style>
<style id="end-style"></style>
<style id="replace-style"></style>
<div id="root-candidate">
<div id="root-target" class="x">root target</div>
</div>
<div id="end-scope" class="scope-root">
<div id="end-boundary">
<div id="end-target" class="x">end target</div>
</div>
</div>
<div id="replace-root" class="replace-root">
<div id="replace-target" class="x">replace target</div>
</div>
<script>
async function waitForImportedSheet(styleElement) {
while (styleElement.sheet.cssRules.length === 0 || styleElement.sheet.cssRules[0].styleSheet === null)
await animationFrame();
}
function propertyValue(id, property) {
return getComputedStyle(document.getElementById(id)).getPropertyValue(property);
}
function settleAndReset(triggerElement) {
getComputedStyle(triggerElement).color;
getComputedStyle(triggerElement).color;
internals.resetStyleInvalidationCounters();
}
asyncTest(async done => {
const rootImportURL = `data:text/css;base64,${btoa(".x { --root-hit: yes; }")}`;
const rootStyle = document.getElementById("root-style");
rootStyle.textContent = `@import "${rootImportURL}" scope((.scope-root));`;
await waitForImportedSheet(rootStyle);
println(`root before: ${propertyValue("root-target", "--root-hit")}`);
document.getElementById("root-candidate").classList.add("scope-root");
println(`root after: ${propertyValue("root-target", "--root-hit")}`);
const insightsStyle = document.createElement("style");
insightsStyle.textContent = `@import "${rootImportURL}" scope((.scope-root:has(.hit)));`;
document.head.appendChild(insightsStyle);
await waitForImportedSheet(insightsStyle);
println(`scope insights has: ${internals.styleSheetMayHaveHasSelectors(insightsStyle.sheet)}`);
const endImportURL = `data:text/css;base64,${btoa(".x { --end-hit: yes; }")}`;
const endStyle = document.getElementById("end-style");
endStyle.textContent = `@import "${endImportURL}" scope((.scope-root) to (.limit));`;
await waitForImportedSheet(endStyle);
println(`end before: ${propertyValue("end-target", "--end-hit")}`);
document.getElementById("end-boundary").classList.add("limit");
println(`end after: ${propertyValue("end-target", "--end-hit")}`);
const replaceImportURL = `data:text/css;base64,${btoa(".x { --replace-hit: yes; }")}`;
const replaceStyle = document.getElementById("replace-style");
replaceStyle.textContent = `@import "${replaceImportURL}" scope((.other-root));`;
await waitForImportedSheet(replaceStyle);
println(`replace before: ${propertyValue("replace-target", "--replace-hit")}`);
replaceStyle.sheet.deleteRule(0);
replaceStyle.sheet.insertRule(`@import "${replaceImportURL}" scope((.replace-root));`, 0);
await waitForImportedSheet(replaceStyle);
println(`replace after: ${propertyValue("replace-target", "--replace-hit")}`);
replaceStyle.sheet.deleteRule(0);
println(`remove after: ${propertyValue("replace-target", "--replace-hit")}`);
const removeStyle = document.createElement("style");
removeStyle.textContent = `@import "${replaceImportURL}" scope((.replace-root));`;
document.head.appendChild(removeStyle);
await waitForImportedSheet(removeStyle);
const replaceTarget = document.getElementById("replace-target");
settleAndReset(replaceTarget);
removeStyle.remove();
getComputedStyle(replaceTarget).color;
println(`stylesheet remove full invalidation: ${internals.getStyleInvalidationCounters().fullStyleInvalidations >= 1}`);
done();
});
</script>