--- enabled: true touches: [crates/layout/src/bin/custom_properties_check.rs] depends-on: [] --- # `custom-properties`: measure `--*` and `var()` against the three browsers The Web Features map (`docs/web-features-map.json`, feature `custom-properties`) lists it as `none` with 249 WPT test files under `css/css-variables`. Substitution of `var()`, fallbacks, inheritance and cycle detection all happen in Stylo's cascade (`crates/dom/src/style.rs` hands it the author stylesheets and reads back computed values), so the engine's layout should already see the substituted value in `width`, `margin-*`, `padding-*` and the rest. Nothing in the repository measures that, so the map cannot claim it and a regression would go unseen. This spec adds the measurement and nothing else. The smallest thing that stands on its own is a corpus and a check binary: if the engine already agrees with the browsers, no engine code changes; if it does not, the disagreements are the finding, and fixing them is a separate spec, because a fix would edit `crates/dom/src/computed.rs` or `crates/layout/src/fragment.rs`, which the queued generated-function specs edit and which this spec's `touches` therefore leaves alone. **Scope.** In: block-level, non-replaced boxes without text, in normal flow or absolutely positioned, where every declaration reaching layout is a length, a percentage or `auto` after substitution. Custom properties declared on the box itself, on an ancestor and on `:root`; inheritance and a child overriding an inherited value; `var(--x)` as the whole value and inside a shorthand (`margin`, `padding`); fallbacks (`var(--missing, 10px)`, a fallback that is itself a `var()`); a custom property that references another; a cycle (`--a: var(--b); --b: var(--a)`), whose users compute as unset; an invalid substitution (`width: var(--x)` with `--x: red`), which is invalid at computed-value time and leaves `width` at `auto`; `!important` on a custom property; `var()` inside a length-only `calc()` (`calc(var(--a) + 5px)`, no percentage, so Stylo folds it to a plain length). Out: registered custom properties (`@property`, the feature's `css.at-rules.property` keys), `var()` in properties the engine does not lay out, percentage-mixed `calc()` (its own spec, `calc-lengths`), `env()`, scripted `setProperty` and every test that reads `getComputedStyle`. Say so in the binary's module doc. **Build.** One file, `crates/layout/src/bin/custom_properties_check.rs`, modelled on `crates/layout/src/bin/fragment_check.rs`. `crates/layout/Cargo.toml` needs no edit because `src/bin/*.rs` is discovered. - A `const CASES: &[(&str, &str, &str)]` of (id, category, html) with literal documents, since `crates/harness/src/cases.rs` `Elem` is `pub(crate)`. Every document opens with the same zeroing rule the recipe corpus's `RESET` uses, so body margin does not enter the comparison. At least three cases per category above, and at least 40 in all. - The engine side is `fragment_check.rs` `engine_dump` copied, not shared: `layout` binaries cannot import each other. - The browser side is `harness::oracle::Oracle::start` and `Oracle::dump`. A case counts only when every browser dump that came back agrees with the others under `harness::compare::compare` (`outcome.passed()`); a case whose browsers disagree, or that a browser failed to dump, is dropped and listed with the reason. It passes when the engine agrees with every dump that counted. - A `#[cfg(test)]` module checks that ids are unique, categories are non-empty and every document parses with `dom::Document::parse` and yields a box tree. This is the part `cargo test --workspace` runs; the browsers are not needed. - The binary exits non-zero when fewer than 20 cases were compared, so an oracle that silently produced nothing cannot pass. It exits zero when some cases disagree with the engine; those cases are the result. **Where the result goes.** In the binary's module doc: the counts (cases written, dropped with reasons, compared, passed, per category) and, for each failing case, its id and the one-line cause found in Stylo's output or in `computed.rs`. No other file records it, so the spec touches nothing shared. No `docs/feature-map.json` rule is added: an exact-key rule for a check-only result could only be `hand-written`, which the map treats as `partial`, and that file is edited by every queued generated-function spec. Heavy steps go to a build lane and never run on the development box: 1. `bench/on-lane.sh --lane-only "$PWD" cargo test -p layout --bin custom_properties_check` 2. `bench/on-lane.sh --lane-only "$PWD" cargo run --release -p layout --bin custom_properties_check` If no lane answers, report that instead of running either step locally. The model-first steps the generated-function specs carry (ground-truth cache, generation loop, model batches through `bench/batch-lane.sh`) do not apply: this spec adds no generated function, since Stylo, not engine code, does the substitution, and the corpus is the entire deliverable. **Gates.** `cargo test --workspace` passes. `fragment_check --random` stays 227/227: no engine source changes.