Building a browser benchmark that can't hang
Benchmarks fail in embarrassing ways. They hang at 97%. They report a number that changes 30% between runs. They award your laptop a lower score for having a nicer screen. Rebuilding ours for 2.0, we kept a list of these failure modes taped above the desk and engineered against each one specifically. Some notes from the process.
Inverting the measurement
Fixed workload + measured FPS is the traditional shape, and in a browser it’s nearly useless: fast machines pin at the display’s refresh rate (the browser won’t render faster than vsync, so everything good reports “60” or “120”), and slow machines produce a slideshow that measures patience, not performance.
So we fixed the target instead — a 16.67 ms frame budget — and made the workload the variable. Each scene ramps its particle count until frames blow the budget, then binary-searches the boundary, then demands three consecutive clean confirmation windows before accepting a level. The result, “max stable load,” has a physical meaning you can picture: this machine can push 840,000 nebula particles before it starts dropping frames.
An inverted benchmark also fails gracefully by construction. A weak device doesn’t produce a broken run; it just settles at a lower rung of the same ladder.
Percentiles, because stutter lies
Every window is judged on its mean frame time and its 95th percentile. The p95 criterion exists because averages launder stutter: a machine that renders 95 frames on time and 5 at 40 ms feels bad and averages fine. Percentile gates are how “feels bad” becomes a number. One anomalous window (a notification, a GC pause) gets retried rather than counted — but two consecutive failures reject the level. Forgiving of accidents, unforgiving of patterns.
The paranoid state machine
The “can’t hang” requirement produced the least glamorous and most load-bearing code in the project. Every scene runs a state machine — preflight, baseline, warmup, ramp, bracket, confirm, anchor — and every state has a hard exit:
- a scene gets 30 seconds, a run gets 150, period;
- backgrounding the tab voids the current measurement window and pauses; more than 30 seconds hidden ends the run with a named reason;
- a resize or pixel-ratio change rebuilds the scene and re-warms rather than measuring garbage;
- WebGL context loss, a wedged GPU (no frame for several seconds), a scene that throws — each maps to its own exit reason, and a watchdog interval keeps ticking even when the render loop can’t;
- Escape works during every one of those states, because a cancel button that only works when things are healthy isn’t a cancel button.
The run can end early, degraded, or cancelled. The one thing it cannot do is sit there.
Thermal honesty
Sustained GPU load heats the machine; a heated machine slows down; a benchmark that ignores this quietly averages your device’s best minute with its worst. Every scene therefore measures a light baseline load twice — once at the start, once at the end. If the second reading is more than 15% slower, the scene is flagged degraded, the flag survives into the results sheet and the score (a capped deduction), and — if you submit a sample — into the dataset, so public medians can exclude throttled runs instead of absorbing them.
High-refresh fairness
A subtle trap: judged naively against its own refresh interval, a 120 Hz laptop must render every frame in 8.3 ms to “hold refresh” — twice the demand placed on a 60 Hz machine. Our budget is fixed at 16.67 ms for everyone. High-refresh screens still feel their advantage (they display the extra frames when load is light), but the pass/fail line sits at the same place for every panel. Your monitor is a viewing choice; the benchmark’s job is to measure the computer behind it.
What we deliberately left out
No WebGPU yet — it’ll arrive, if it does, as a separate profile with its own score space, never silently swapped underneath the same number. No web workers for rendering in this version: the added failure surface (worker death, context transfer, cross-thread input) fought the never-hangs requirement, and the measured workload is identical either way. And no percentile claims on the results sheet until the anonymous dataset crosses real sample thresholds — a percentile computed over forty runs is a vibe wearing a number’s clothes.
The full spec, including the scoring math, is written up in the methodology. If you find a device where any of the above fails to hold, that’s a bug: admin at screentest.pro.