Patrick Lidstone
Self-hosted

Development log

The browser draws the page

· Path OS

There has been a browser on this machine for a while, and until now it did not draw pages. It walked the DOM and emitted a flat list of headings and paragraphs, which is a readable document and is not the page. The file said so in its own doc comment, which is at least honest.

What was missing was not the engine. The engine cascades, lays out and paints, and the hosted version of it renders real sites on a desktop. Layout will not start without something that can measure a string, and a display list is not a picture until somebody replays it - and in ring 3 there was neither.

Both ends now exist. A new crate puts the machine's three typefaces behind the engine's metrics interface and replays its display list into a bitmap. It is a crate of its own rather than a module inside the browser, deliberately: the browser is a fetch state machine and this is a rasteriser, and only one of those two can be tested against a picture. What the machine will draw can therefore be drawn on a desktop and looked at.

The one construct that carried everything

var() was not implemented, and the way it failed is why nobody noticed. A declaration containing one is not a parse error. It is a value the property parser does not recognise, so it was dropped, the property took its initial value, and the page rendered black text on white at default sizes. It looked exactly like an engine that could not read CSS. It could read all of CSS except the one construct carrying the design.

Measured on an ordinary WordPress theme: 32 custom properties defined and 109 uses. Every colour, every font choice and every piece of spacing in that stylesheet was being thrown away.

Substitution has to happen at computed-value time, before the property parser sees the declaration. That is what the specification requires and it is the only order that works, because a var() may supply part of a value, a whole one, or several of them, so no individual property parser could do it. Custom properties inherit, so they travel down the tree beside the computed style, and one may be written in terms of another because that is how a theme is built.

The half that took longer to see: shorthands have to be expanded a second time, after substitution. Expansion happens when the stylesheet is parsed, which is right for a shorthand written out in full, but background: var(--bg) cannot be expanded until the value is known.

A second page, and the four faults it found

An engine that renders one page correctly has proved something about that page. The second target was Material for MkDocs: 150 KB of generated stylesheet, three columns, and the modern toolbox used all at once. It disagreed with the first page in four ways that had nothing to do with each other.

The worst was a selector the parser could not finish being truncated and matched anyway. An attribute selector is a block rather than a token, so .a [x=y]:checked+.c:before ended its parse after .a and became a selector for everything with that class. A rule written for one checked checkbox painted the whole article bright green. A selector that cannot be parsed has to be refused outright: a truncated selector is not a weaker version of the original, it is a different and far broader one. It is silent and it over-matches, which is the worst pair of properties a fault can have.

Then a percentage height against an auto-height container resolving to zero. CSS says such a percentage is circular and behaves as auto; this resolved it against nothing, which is not "no height" but a height of nothing. The documentation column is height: 100% inside an auto-height main element, so it measured zero - and everything in it was still painted, because painting walks children rather than heights, so the footer flowed up over the top of the article.

A rule for ::before was also being treated as a rule for the element itself, and gradients were not understood at all, which hid four separate flex defects behind each other until the gradients landed and the boxes became visible.

JavaScript can reach the document

The interpreter could run a program and not touch the page it came with. It can now, which is the difference between a document and an application, and it is what the next few weeks of this depend on.

What is not built

Nothing here has been run on the X1. It renders this site at the X1's own 2560x1440 in a host test, which asserts that it parsed, that it drew, and that it drew in colour, with a control that proves the assertion can fail. That is not the same as a picture of the laptop's panel.

A page that renders is not a page that works. Fetching, module loading and anything a modern application does after the first paint are all ahead, and the two pages that render are two pages.