Journal

Long readEngines, issue 7

The quiet craft of browser engines

Between a typed address and a painted frame sits one of the most patient pieces of software we use every day. A walk through its rooms, from the tokenizer to the compositor.

Nobody notices an engine when it works. You type, you wait a fraction of a second, and a page arrives, already laid out, already the right size for your hand. The work that made it happen is invisible on purpose. This is an attempt to make some of it visible, the way a stagehand might walk you across an empty theatre after the show.

Every engine, whatever its lineage, answers the same question many times a second: given this document, these styles and this window, what should each pixel be? The answer is assembled in stages, and each stage hands a smaller, more certain thing to the next.1

Bytes become a tree

The first room is the quietest. Bytes arrive from the network in whatever order the network likes, and the tokenizer reads them one character at a time, deciding whether it is inside a tag, an attribute, a comment or plain text. It is forgiving to a degree that surprises people: a missing closing tag is not an error but an instruction, resolved by rules written down long before most of today's sites existed.

The tree builder takes those tokens and grows the document object model, the structure every inspector shows in its Elements view. It does this while the bytes are still arriving, so the top of a page can exist before the bottom has left the server.

Pipeline diagram: bytes, tokens, DOM, style, layout, paint and composite, left to rightBytes01Tokens02DOM03Style04Layout05Paint06Composite07
Fig. 1 The rooms of an engine. Each stage hands a smaller, more certain artefact to the next.

Rules meet elements

Style is where the engine becomes a negotiator. For every element it gathers the declarations that might apply, sorts them by origin, layer, specificity and order, and settles on one value for each property. Custom properties stay as tokens until the last moment; color-mix() and relative colours are resolved only when the engine knows what they refer to.

The cascade is not a bug in CSS. It is a very old, very polite agreement about who gets the last word.

A good engine avoids redoing this work. Change one class and it marks only the elements that might be affected, then recomputes those. The cost of a style change is decided less by how much you change than by how many elements your selectors could possibly touch.

/* A selector the engine can scope cheaply */
.card:has(input:checked) {
  --finish: oklch(0.62 0.2 264);
  outline: 1.5px solid var(--finish);
}

Boxes find their places

Layout turns styled elements into boxes with positions and sizes. It is the stage that most often surprises authors, because it is where the window finally matters. A grid track sized in fractions, a container query, a line of text that wraps one word earlier on a narrower phone: all of it is decided here, top to bottom, with the occasional second pass when a child's size changes its parent's mind.2

Box model: a content box inside padding, border and margin, each labelledmarginborderpadding320 × 68
Fig. 2 Layout's answer for one element. The numbers here are illustrative, not measured.

Paint, then compose

Painting records what to draw: backgrounds, borders, text, shadows, in the right order. Compositing decides what can be drawn once and moved cheaply. When an animation touches only transform oropacity, the engine can skip the earlier rooms entirely and hand the work to the compositor, which is why those two properties are the polite way to move things.

A frame budget at 60 frames per second, in one fictional engine
StageTypical shareTriggered by
Style2.1 msClass, attribute or state changes
Layout3.4 msSize, position, text or font changes
Paint2.8 msColour, shadow, background changes
Composite0.9 msTransform and opacity
Headroom7.5 msYour scripts
Total16.7 msOne frame

Sixteen milliseconds is a long time for a machine and no time at all for a careless script.

Why it stays quiet

The craft is in what the engine refuses to do. It refuses to lay out what nobody can see, to repaint what did not change, to block the first paint on a script that could have waited. The best engines are conservative in the way good editors are: they keep what works and touch only what must move.

Opening an inspector is a way of thanking that work. Every row in the Elements tree, every crossed-out declaration in Styles, every bar in a network waterfall is the engine showing its notes. Read them slowly. They are more honest than they look.3

Noor Hallberg is a fictional writer. This article is original demo content for Debrowser's Reader mode; any resemblance to a real engine's internals is approximate.

Notes

  1. Real engines interleave these stages far more than a diagram can show; this essay keeps them in order for clarity. ↩
  2. Some layouts need more than two passes, which is one reason intrinsic sizing is harder than it looks. ↩
  3. Honest within limits: an inspector can only report what the engine exposes, and a good one says so when a value is unavailable. ↩