Icons, text, and the tree
The small things that say the browser is real
A tab with no icon, a logo that renders as empty space, a window a screen reader cannot describe. None of these stop a page from working, and all of them are the difference between something that renders HTML and something a person would use. This batch is that kind of work.
/favicon.ico, which no standard describes
Servo only ever took a favicon from <link rel=icon>. Plenty of sites declare no such link — crates.io and old.reddit.com among many others — and rely on the icon being served from /favicon.ico at the root of the origin. The HTML standard does not describe that fallback. Every browser performs it anyway, so a site that depends on it is not doing anything unusual; it is depending on the behaviour rather than the spec.
A top-level document that reaches the interactive ready state having seen no icon link now fetches /favicon.ico. Waiting for interactive is what keeps it from racing: a link element parsed late still wins, and the fallback only runs once the parser has had its chance to find one.
A message with nowhere to land
A vector favicon is rasterized off the main thread, so the first call to rasterize_vector_image returns None and the caller registers for the completion message. Window::handle_image_rasterization_complete_notification only ever looked at pending_images_for_rasterization, the list layout keeps so it can mark a node dirty when an image finishes.
A favicon has no node in that list. It is not in the document; it is a property of the page that the embedder draws in its own chrome. So the completion message arrived, matched nothing, and was dropped, and every site with an SVG icon showed none. A favicon's registration is now looked at as well, so the icon arrives when its rasterization completes.
An icon that outlived its document
WebViewInner.favicon was only ever assigned, never reset. Navigate from a site that has an icon to one that does not, and the old icon stayed in the tab bar for the rest of that tab's life — for the rest of the tab's life, not until the next icon, because a site without an icon never sends one to replace it.
The icon belongs to the document, so it is now dropped when a top-level load starts, and the embedder is told, exactly as it is told when a new icon arrives.
Generic font families, pointed somewhere real
fontdb starts out naming Windows fonts for the generic families: "Times New Roman" for serif, "Courier New" for monospace. usvg falls back to the same for an SVG that names no family. On a system without those fonts every generic resolves to nothing, usvg drops the text that asked for one, and the SVG rasterizes without it.
An icon drawn with <text> then comes out fully transparent. syrakon.com's favicon is a monospace wordmark, so it was invisible — and invisible in a way that looks like the icon code failing rather than the font database, which is why it took a while to find. The generic families now point at installed fonts.
One more in the same family of bug: Servo reflected an <a> inside an <svg> to the plain SVGElement, and the SVGAElement global did not exist at all. A script that names it throws ReferenceError: SVGAElement is not defined — and SvelteKit's runtime names it, when it decides how to treat a click target. The rejected promise took the rest of that module with it, so everything the module was going to render never appeared. The interface is a stub inheriting SVGGraphicsElement, which is enough for the name to resolve.
A window that can be read aloud
GPUI's element tree is presentational. Nothing in it says that a div is a button or that a glyph run is that button's label, which is exactly the information a screen reader needs and exactly the information a renderer optimised for drawing does not keep.
Elements now declare it, opt-in and explicitly:
div().a11y(AccessRole::Button, "Recargar")
Each annotated element emits an AccessNode during paint, carrying role, label, value, selected state, painted bounds and the focus handle it tracks. The nodes accumulate in the frame in paint order, which gives the tree a reading order for free — paint order is close enough to reading order in a chrome laid out like rumb's.
The Wayland and X11 windows convert each presented frame's node list into an AccessKit tree and push it to accesskit_unix, which speaks AT-SPI to Orca and the rest of the desktop's assistive technology. AccessKit is display-server agnostic — it is D-Bus, not Wayland or X11 — so both backends share one bridge instead of growing one each.
A wheel that glides
A wheel step moved the page in one jump, which is correct and feels like a 1998 scrollbar. WebView::glide_wheel_event spreads the scroll of a wheel event over the frames that follow, once script has handled the event without preventing it: each frame covers a share of what is left, in whole pixels, so the page starts fast and eases out whatever the frame rate happens to be. Turning the wheel the other way drops what was left rather than fighting it.
Whole pixels per frame is the part worth keeping. A share of the remaining distance is a geometric series, and in fractional pixels it never quite arrives; rounding each frame's step up to a pixel is what makes it terminate.
Servo paints no scrollbars, so the shell has to draw them, and until now it had no way to know how far the page could scroll. WebView::viewport_scroll_extent reports it.
What a screen reader still cannot do
The tree is one level deep: a root node for the window, labelled with its title, and the annotated elements beneath it. There is no nesting, so nothing expresses that a button is inside a toolbar inside a tab strip.
The page itself is not in that tree at all. Servo has its own accessibility story and this bridge describes only the chrome, which means a screen reader can find the reload button and not a single heading on the page it reloads. Keyboard navigation of the chrome is the other half of this work and is not done.
And the labels are in Spanish, hardcoded, like the rest of the chrome strings — "Recargar" is not a placeholder in that example, it is what the code says.