Files
ladybird/Tests/LibWeb/Text/input/css/custom-property-chain-resolution.html
Andreas Kling 9e8e568b43 LibWeb: Use structural sharing for CSS custom properties
Replace per-element OrderedHashMap storage for custom properties with
a RefCounted chain (CustomPropertyData) that enables structural
sharing. Each chain node stores only the properties declared directly
on its element, with a parent pointer to the inherited chain.

Elements that don't override any custom properties share the parent's
data directly (just a RefPtr copy). During cascade, only entries that
actually differ from the parent are stored in own_values - the rest
are inherited through the chain. During var() resolution, resolved
values are compared against the parent's and matching entries are
dropped, enabling further sharing.

The chain uses a depth limit (max 32) with flattening, plus
absorption of small parent nodes (threshold 8) to keep lookups fast.

This reduces custom property memory from ~79 MB to ~5.7 MB on
cloudflare.com.
2026-02-13 14:57:15 +01:00

89 lines
2.6 KiB
HTML

<!DOCTYPE html>
<script src="../include.js"></script>
<style>
/* Test that var() resolves correctly through the chain */
:root {
--base-color: red;
--base-size: 10px;
--compound: A;
}
#parent {
--base-color: green;
--compound: B;
--ref-color: var(--base-color);
}
#child {
--compound: var(--compound) C;
color: var(--base-color);
font-size: var(--base-size);
}
/* Test inherit keyword */
#inherit-parent { --x: from-parent; }
#inherit-child { --x: overridden; }
#inherit-grandchild { --x: inherit; }
/* Test unset keyword on root */
:root { --unset-test: root-value; }
#unset-child { --unset-test: unset; }
/* Test var() with fallback when property is missing from chain */
#fallback-test {
--result: var(--nonexistent, fallback-value);
}
/* Test multiple var() references to chain */
#multi-var {
--a: hello;
--combined: var(--a) var(--base-color) var(--base-size);
}
</style>
<div id="parent">
<div id="child"></div>
</div>
<div id="inherit-parent">
<div id="inherit-child">
<div id="inherit-grandchild"></div>
</div>
</div>
<div id="unset-child"></div>
<div id="fallback-test"></div>
<div id="multi-var"></div>
<script>
test(() => {
const cs = (id) => getComputedStyle(document.getElementById(id));
// var() should resolve through the chain
println("=== var() through chain ===");
println(`child color: ${cs("child").color}`);
println(`parent --ref-color: ${cs("parent").getPropertyValue("--ref-color").trim()}`);
println(`child --ref-color (inherited): ${cs("child").getPropertyValue("--ref-color").trim()}`);
// inherit keyword
println("=== inherit keyword ===");
println(`inherit-parent: ${cs("inherit-parent").getPropertyValue("--x").trim()}`);
println(`inherit-child: ${cs("inherit-child").getPropertyValue("--x").trim()}`);
println(`inherit-grandchild: ${cs("inherit-grandchild").getPropertyValue("--x").trim()}`);
// unset on root (should become initial/empty)
println("=== unset keyword ===");
println(`unset-child: "${cs("unset-child").getPropertyValue("--unset-test").trim()}"`);
// var() with fallback
println("=== var() fallback ===");
println(`fallback: ${cs("fallback-test").getPropertyValue("--result").trim()}`);
// Multiple var() references
println("=== Multiple var() ===");
println(`combined: ${cs("multi-var").getPropertyValue("--combined").trim()}`);
});
</script>