Wren a web browser where WebAssembly runs everything, JavaScript included

Architecture

Wren is one executable, written in Odin, with a handful of statically linked libraries. This page walks through the parts and the rules that hold them together. The source is the reference: github.com/sachahjkl/wren.

The layers

Wren's own code · the WebAssembly boundary · a library or a system service

All the Rust is one static library, wren_native.lib, linked into wren.exe: nothing sits beside the executable.

The packages

PackageRole
baseScopes (an ownership tree with cleanups) and generational handles with stable pointers.
hostThe event loop: thread-safe posts with coalesced wake-ups, timers, a pool for blocking work.
netTransport only: HTTP(S), HTTP/3 and WebSockets on a Rust stack (WinHTTP stays selectable), per-host and global queues, back-pressure, cancellation.
fetchThe policy layer of the Fetch Standard: modes, CORS and preflights, credentials, redirects, HSTS, referrer policy, mixed content, MIME checks.
url · cookies · csp · httpWHATWG URLs and the Public Suffix List, the cookie jar (RFC 6265bis), Content Security Policy 3, headers and MIME types.
html · domHTML parsing with Lexbor, charset sniffing, quirks mode; the document tree, shadow trees and slots, event dispatch with retargeting.
css · styleTokenizer, parser, selectors, media and container queries; the cascade with layers, var(), a rule index, a Bloom filter and partial restyles.
layoutBlock, inline, flex, grid and tables, bidi and line breaking, incremental relayout, display lists cut into paint chunks.
wasmWasmtime: instances, imports defined on the linker with address checks, deadlines, limits, compiled-code caches, the WASI subset the JavaScript engine needs, pre-initialization of its image.
js · native/jsThe JavaScript engine, written in Odin and compiled to WebAssembly (src/engine), with its glue: bindings generated from the specifications' Web IDL, hand-written members; on the browser side, its preferences and the bytecode cache.
pageOne document: its DOM, Wasm instances, JavaScript realm and script loading, requests, timers, origin and CSP; the implementations behind the generated imports.
images · webfontImage decoding on threads (Rust crates for every format; WIC for HEIF and on request), WOFF and WOFF2.
browserThe shell: window, tabs, navigation, address bar, painter and compositor, about:config, status bar, profiling.

Rules that keep it from breaking

  1. One ownership tree. Process, tab, navigation, page, then the page's instances, DOM, requests and timers. Closing a tab or navigating closes a scope; cleanups run newest first, children before parents, so nothing outlives what it points to.
  2. Handles, never pointers, cross boundaries. A handle is a generation and an index. A destroyed or recycled slot fails to resolve: late network events and callbacks for dead objects are dropped.
  3. One UI thread, everything else posts. The network stack's threads, the blocking pool, image decoders and the colour glyph renderer only post results back. The DOM and the Wasm instances of a page live on the UI thread; text measurement runs in parallel on layout threads.
  4. Mechanism and policy are separate. net moves bytes and never follows a redirect or stores a cookie. fetch decides, per hop, with the document's environment in hand. A module, a module load and a navigation all go through the same code.
  5. Everything is bounded, each bound with a reason. Six requests per host and 64 in total, at most 20 redirects, cookies per site and in total, the bytes a page may hold, the memory of a module, the duration of every call into it. The limits a user may want to change are preferences in about:config.
  6. A module cannot reach browser memory. Every address and length an import receives is checked against the module's own memory before the browser reads or writes it.

The WebAssembly boundary

Every import is described once, in a dialect of Web IDL (api/*.widl: modules, functions, typed handles, enums, flags, structs, callbacks, errors, availability in windows and workers). A generator, bindgen, writes everything else.

Host trampolines

One per import, defined directly on the Wasmtime linker. It checks addresses, validates UTF-8, looks up handles by kind, range-checks enums, then calls the implementation with ordinary Odin values. A missing or mistyped implementation does not compile.

The Odin SDK

sdk/odin/web: distinct handle types, strings and slices, (T, Error) results. Modules whose memory is 64-bit get the same imports with 64-bit addresses.

Documentation

docs/api: every function of the 58 modules, with the ABI, so that SDKs for other languages can follow.

Conformance tests

2,256 generated cases over 1,482 functions: addresses out of bounds, invalid UTF-8, dead handles, enums out of range, buffers too small. A pointer outside the memory is a trap, a bad argument is an error or a trap, never undefined behaviour.

Modules are compiled by Cranelift. Compiled code is kept in memory (keyed by the SHA-256 of the module) and on disk in the profile, authenticated with an HMAC whose key is protected by Windows DPAPI. Interruption uses Wasmtime's epochs, advanced by a thread that only runs while a call is in progress: an idle browser wakes nothing.

JavaScript, as one more module

Wren has no native JavaScript engine. The scripts of a page run in a WebAssembly module, so they are held to the same rules as every other guest: its own instance, its own memory, a deadline on every call.

Our own engine, in WebAssembly

An engine written in Odin (a compiler to QuickJS-ng bytecode, an interpreter, a loop JIT), compiled to a WebAssembly module linked by the wasm-ld that ships with Odin, with no C library. Each page and each JavaScript worker gets its own instance, a realm. A trap loses that realm (the status bar says JS stopped), never the browser.

Pre-initialized at build time

The interfaces, prototypes and preludes of the web platform are built once, when the browser is built, and saved in the module's memory image, which is embedded in the executable. A new realm starts in about 1.3 ms.

Bindings from the specifications

bindgen reads the Web IDL of webref and writes the glue with Web IDL's rules: conversions, dictionaries, unions, overloads, reflected attributes, iterables. The engine reaches the browser through the same api/*.widl imports as Wasm pages. A member that is not implemented is absent.

Two heaps, one lifetime

A wrapper keeps its DOM node alive; the wrappers of one DOM tree are grouped at each collection, so a detached tree that nothing holds any more is freed on both sides. Scripts load as HTML specifies: parser order, defer, async, modules, document.write.

Rendering

Retained compositor

The display list is cut into paint chunks, recorded once as GPU pictures and replayed. Scrolling, sticky offsets and transform animations change a matrix or a slot; nothing is re-recorded. Only chunks whose content changed are recorded again.

Effects kept between frames

Filtered layers, backdrops and blended pixels are cached on the GPU while their content is unchanged; colour matrices are folded into the composition. Animating opacity or a blend runs no extra pass.

Colours like the web

Blending happens on sRGB values as other browsers do, gradients and box shadows are computed exactly in the shader, and text uses grayscale anti-aliasing with contrast tuned like Chromium's.

Frame pacing

The refresh rate is read from the display path, frames are stamped with the vblank they start on, and animations, requestAnimationFrame and smooth scrolling advance in whole periods. Nothing is drawn when nothing changes.

Measurements

Medians or percentiles from the repository's tools (wasmbench, wrentool bench perf, wrentool bench js, wrentool engine test262), on the development machine (RTX 4070 SUPER). Frame budgets are those of a 180 Hz display: 5.56 ms.

MeasureResult
Call from a module into the browser, no arguments7.3 ns
Call with an address and a length8.4 ns
Call with a 64-byte string, UTF-8 checked12.1 ns
Instantiating a module already compiled (memory cache)0.03 ms
Loading the same module from the disk cache after a restart0.52 ms
Numeric loop, 50 M iterations: Cranelift, against the interpreter Wren used before124 ms vs 3146 ms
A new JavaScript realm, from the pre-initialized image1.3 ms
Octane, QuickJS-ng in Wasm / the same engine native / Edge (measured before the Odin engine)1664 / 2169 / 91318
From JavaScript: node.nodeType, getElementById, createElement + appendChild64 / 193 / 1311 ns
test262, engine in Wasm: runs passed99.92 % of 82,220
First frame of the start page after launch (shaders precompiled)1.79 → 0.29 s
Wheel scrolling 10,000 paragraphs, CPU / GPU per frame, 99th percentile1.41 / 2.28 ms
Painting while scrolling over content already seen (median)0.013 ms
The effects page (blurs, backdrops, 18 blend modes), GPU per frame, 99th percentile3.2–3.6 → 1.2–1.3 ms
about:config, building the interface per frame while scrolling34.4 → 0.29 ms
Resizing the window: layout of one step on 10,000 paragraphs7.1 → 2.5 ms

Arrows show a measured improvement, before and after the change that made it.

Credits

Wren is written by sachahjkl. It stands on these projects:

  • Odin, the language everything is written in.
  • gui_toolkit, the GPU drawing, text and UI layer.
  • Wasmtime (Bytecode Alliance), the WebAssembly engine.
  • QuickJS-ng, after Fabrice Bellard's QuickJS: the reference of Wren's engine, whose bytecode format it follows and whose number and Unicode libraries it ports.
  • emmalloc, through Odin's base:runtime, the heap of the JavaScript module.
  • webref, the Web IDL of the specifications.
  • Lexbor, HTML parsing, URLs and encodings.
  • hyper, rustls, quinn and tungstenite, the network stack.
  • Fontations, HarfRust and ICU4X, fonts, shaping and line breaks.
  • AccessKit, the accessibility tree.
  • image, PNG, JPEG, GIF and TIFF.
  • resvg, SVG rendering.
  • rav1d and zenavif-parse, AVIF.
  • image-webp, WebP.
  • qcms, colour management.
  • brotli-decompressor, WOFF2 fonts.
  • Public Suffix List, sites and cookies.
  • test262 and web-platform-tests, the conformance suites.
  • Windows: Direct3D 11, Media Foundation, WASAPI, UI Automation; DirectWrite, WinHTTP and WIC as options.
  • The web standards of the WHATWG, the W3C, TC39 and the WebAssembly Community Group.