How layout output is checked against real browsers #
The engine's layout is judged by one question: for a given page, does every box come out
the same size and in the same place as in real browsers? This page explains how that is
answered, starting from nothing. The exact wire format is in docs/dump-format.md.
The idea in one picture #
flowchart LR
page["test page<br/>(HTML + CSS)"] --> chr["Chromium"]
page --> ff["Firefox"]
page --> wk["WebKit"]
page --> nev["this engine"]
chr --> vote["vote per<br/>measurement"]
ff --> vote
wk --> vote
vote --> cmp{"engine within<br/>1/60 px of the vote?"}
nev --> cmp
cmp --> result["pass / fail<br/>+ per-box error"]
The browser side runs once per page and is cached; only the engine side runs on every candidate.
Step by step #
1. Dump. Each browser loads the page through Playwright (oracle/oracle.mjs) at a
fixed 800×600 viewport, device pixel ratio 1, and walks every element in document order.
For each it records the element's box rectangles (getClientRects()), a subset of
computed style, and whether its content overflows. That list is a dump. The engine
produces the same dump from its own fragment tree (crates/harness/src/dump.rs).
2. Compare shape first. Both dumps must list the same elements in the same tree shape (tag, depth, parent). If they do not, the difference is in parsing, not layout, and the case stops there.
3. Compare sizes and relative offsets. For each box: its width and height, and its position relative to its parent. Relative offsets mean one misplaced box shows up as one wrong number instead of shifting every box below it, so the first wrong box names the rule that is wrong.
4. Vote. With three browsers, each measurement is classified
(crates/harness/src/compare.rs vote()):
flowchart TD
m["one measurement,<br/>three browser values"] --> q1{"all three agree<br/>within 1/60 px?"}
q1 -- yes --> u["unanimous:<br/>that value is the reference"]
q1 -- no --> q2{"two agree,<br/>one differs?"}
q2 -- yes --> o["2–1: the pair's value is the<br/>reference, the third is named outlier"]
q2 -- no --> s["split: no reference,<br/>measurement skipped"]
Two browsers could only report "they disagree" and throw the case away; three can keep it and name who is off.
5. Discard what cannot be trusted. A whole case is dropped rather than scored when
the browsers disagree about whether some box has a scrollbar, because a scrollbar takes a
platform-dependent width from everything inside it
(compare::scrollbar_dispute, docs/dump-format.md → "Scrollbar probe"). Unanimous
agreement that a scrollbar is there does not discard the case.
6. Score. A case passes when every trustworthy measurement of the engine is within 1/60 px of the reference. 1/60 px is the coarsest grid any of the browsers rounds to (Firefox's); Chromium and WebKit use 1/64 px. The raw difference is kept too, as a finer-grained signal than pass/fail.
A worked example #
Twelve nested boxes, each width: 83.333% of its parent, starting from 800 px (the
nesting-depth-12 recipe). At the innermost box:
| source | width at depth 12 |
|---|---|
| Chromium | 89.671875 px |
| WebKit | 89.671875 px |
| Firefox | 89.66667175292969 px (its own 1/60 px grid) |
| vote | unanimous — all three within 1/60 px; reference 89.671875 px |
| engine, exact arithmetic | 89.72098558887357 px — off by 0.049 px, fail |
| engine, rounding to nearest 1/64 px at each level | 89.71875 px — fail |
| engine, truncating to 1/64 px at each level | 89.671875 px — exact, pass |
The browsers' shared answer told the engine exactly how they round: truncate toward zero
on a 1/64 px grid at every step (crates/dom/src/px64.rs,
specs/device-pixel-rounding.md). On other boxes of the same shape Firefox's coarser
grid drifts up to 0.0375 px from the other two, past the tolerance; there the vote is 2–1
and Firefox is named the outlier. That drift turned out to be a real web-compatibility
bug (docs/firefox-app-units.md).
Where the test pages come from #
flowchart LR
recipes["recipe corpus<br/>227 cases"] --> gate["development signal<br/>and landing floor"]
seamc["per-function corpora<br/>104–109 cases each"] --> gate
grammar["grammar corpus<br/>200 cases"] --> held["held-out exam"]
wpt["WPT normal-flow pages<br/>11 usable"] --> held
- Recipe corpus (
crates/harness/src/cases.rs). About thirty recipes, each a small page generator for one branch of the spec's case analysis (auto margins on one side, a negative margin collapsing with a positive one, a percentage against an auto-height parent...). Each recipe is a category, so scores are reported per category. 27 fixed cases plus 200 seeded-random draws from the same recipes (seed0xC0FFEE) make 227. Values are deliberately fractional (33.333%, 1/64 px steps), because round numbers hide rounding bugs. - Per-function corpora (
absolute_cases.rs,float_cases.rs,relpos_cases.rs,stacking_cases.rs,block_height_cases.rs,overflow_cases.rs). The same recipe idea, one set per generated function, kept separate so adding cases does not reshuffle everyone else's random draw. - Grammar corpus (
crates/harness/src/grammar.rs). Pages composed freely from the same properties, with no recipe steering them, so it contains shapes no recipe thought of. Never used to generate code — only to check it. - WPT pages. The
css/CSS2/normal-flowpages that the engine can read (no text, no external stylesheet): 11 of 1056. Scored the same way, never trained against. - SMT-sampled cases (
crates/harness/src/smt.rs). A handful of margin-collapse cases whose values a solver chose to hit rule boundaries.
Floors #
A floor is a recorded score that a change may not go below; landing on main runs
them (bench/land.sh):
bench/fragment-floor.sh— the recipe corpus throughlayout()must pass 227 of 227.bench/verify.sh(bench/generated-scores.sh) — every generated function must pass at least as many cases of its own corpus as its<name>.floor.jsonholds, and the corpus must not shrink.
Floors are raised when more cases pass (by bench/generated-floors-update.sh after a
landing), never lowered to let a change through.
Current numbers #
- Recipe corpus: 227/227, worst per-box error 0.0000 px.
- Held-out WPT pages: 11/11. Grammar corpus: 61/164 scored, 36 discarded for scrollbar disagreement. The gap between 227/227 and 61/164 is the honest measure of how much the recipes still miss.
- Browser agreement: 699 of 705 browser pairs agreed across 235 documents; all six disagreements were the twelve-deep percentage shape above.
History of these numbers: roadmap.md → "Proof of concept".