Wren a web browser where WebAssembly runs everything, JavaScript included

Versions

Changelog

What each version of Wren brings, what the current preview does, and what it does not do yet. The release notes on GitHub list every change.

Releases

Eight versions in twelve days: the first public build, JavaScript, a JIT, a text engine of its own, a browser that speaks to screen readers, then a JavaScript engine written in Odin.

0.6.28

Latest
  • Classic scroll bars: page and element scroll bars take their room, with arrows at their ends (documentElement.clientWidth is the window less the bar, as in Chrome on Windows); scrollbar-gutter and scrollbar-width apply. The old overlay bars are layout.scrollbar.overlay.
  • Developer tools you dock: drag a tab onto a panel or an edge to split, onto a strip to join it, out to float it; panes resize from a few em to nearly the whole room and fold; the layout is kept in the profile.
  • History through frames: back and forward walk the navigations made inside frames, a frame keeps the Window of its first blank document, and so does a window opened by window.open; anchor links and back within a page scroll as the HTML standard says; scroll anchoring keeps the view when content loads above it.
  • Faster pages that rebuild their DOM: one call into the browser makes a node, the collection skips wrappers that hold nothing, the interpreter calls getters itself (a page that rebuilds 10,000 nodes every frame, measured on one machine: about 142 -> 163 frames a second).
  • More of the web platform: ::backdrop, ::placeholder, scrollend, document.domain, CSP Embedded Enforcement, clip, CDATA sections, namespaced attributes, URLPattern and URL setters as the standards say, SharedArrayBuffer between a page and its workers.
  • Rendering fixes from real sites: table rows grow to their cells, an absolutely positioned child of a flex or grid container is aligned, margins collapse through empty boxes, form controls take their own line height, style sheets resolve URLs against the base URL, and hovering one link of a menu lights only that link.

0.6.27

  • Hovering one link no longer restyles its look-alikes: links that share a style (a site's nav, the start page's Configuration, History and Content blocker links) each show their hover state alone again.
  • International domain names follow the URL Standard: xn-- hosts are only lowercased, labels starting with a combining mark or with a misplaced zero-width joiner are refused.
  • Names as scripts give them: createElementNS and setAttributeNS keep their case, namespace selectors (*|div, |div) work, and an iframe answered as XML is shown as an XML document.
  • Two forms sent at once to two frames both arrive (the second used to cancel the first).
  • A setTimeout(0) set by a script runs after the tasks that script queued, as in other browsers.
  • XHR and FileReader text honour a byte order mark.

0.6.26

  • Pages that rebuild their DOM are much faster: elements alike share their selector matching, document and window stay on the interpreter's fast path, a wrapper is one allocation, string arguments are read in place and a collection unpins its nodes in one call (a page that rebuilds 10 000 nodes every frame, measured on one machine: about 85 -> 142 frames a second since 0.6.25).
  • Legacy encodings: a page in Shift_JIS, EUC-KR, Big5, GBK or windows-1252 keeps its encoding for its links and forms (a search form no longer sends mojibake), document.characterSet says it, level-2 kanji decode, and documents made by a script keep their own encoding and URL.
  • getComputedStyle gives the used insets (top, right, bottom, left in pixels) of positioned elements: what tooltip and popover libraries read.
  • <dialog>: beforetoggle and toggle events, closedBy, and a modal dialog leaves the top layer when it is taken out of the document.
  • document.createProcessingInstruction, and an error of a method of another frame's node is of that frame.
  • Text on several threads waits less (the graphics toolkit's atlas and word cache locks).

0.6.25

  • Same-origin frames: contentDocument, contentWindow.document, parent.document; each frame keeps its own built-ins (instanceof across frames is false, as in other browsers); a frame's navigations fire their navigate event and keep their own session history.
  • Multi-column layout and vertical writing modes: columns, column-span, nested columns, writing-mode, text-orientation, and documents that scroll horizontally or start from the bottom.
  • A GPU process for WebGL, WebGL in workers and transferControlToOffscreen, WebGL 2 transform feedback and queries.
  • AudioWorklet, and P-521 in crypto.subtle.
  • Pages that build large documents run about three times faster: identical styles shared, columns laid out on the layout threads, wrappers of plain nodes freed without the cost of a full collection (a page that rebuilds 10 000 nodes every frame, measured on one machine: about 28 -> 85 frames a second).
  • An image opened by itself is a viewer: zoom around the pointer (Ctrl+wheel, pinch, + and -), drag to pan, R to turn it, a click between fitted and 100 %.

0.6.24

  • WebGL and WebGL 2, through wgpu and naga, with a device and a thread for each context. Three.js examples and MapLibre maps run: a simple scene at 60 frames a second, a heavy one at 20 to 30, on a good GPU. A machine with no adapter gets null, as a page expects. Transform feedback and real queries are not there yet, nor WebGL in workers. There is no separate GPU process: a crash inside a driver takes the browser down.
  • CSS anchor positioning: anchor(), anchor-size(), position-area, position-try-fallbacks, @position-try, position-visibility, implicit anchors of popovers. The web-platform-tests of the folder go from 13.7 % to 85 %. No writing modes or anchor names across shadow trees.
  • A compiled loop's return no longer leaks stack depth: pages could end in a false "Maximum call stack".
  • TypedArray.prototype.set no longer traps on large arrays.
  • Scroll containers count the end margins of their children as scrollable overflow, as other browsers do: scrollHeight may grow.

0.6.23

  • Web Audio. AudioContext, OfflineAudioContext, the usual nodes, decodeAudioData and ScriptProcessorNode, on Windows and Linux, following the autoplay policy. The web-platform-tests of webaudio go from 24 % to 92 %. AudioWorklet is not there yet.
  • vscode.dev works: its compile no longer runs into a time limit, large scripts compile about 3.6 times faster, a service worker's opaque answer reaches the page, and a <meta> policy no longer applies to code before it.
  • reddit works: a form submitted while the page loads goes through, a hang that grew to 22 GB of memory is gone, and getComputedStyle answers for shorthands.
  • tldraw's icons show: an SVG image's fragment now makes :target match.
  • Regular expressions search faster: Octane's regexp from 127 to about 165.
  • A same-origin frame gives navigator, screen and crypto, which scripts often take from a frame they just made.

0.6.22

  • A Linux package. wren-0.6.22-linux-x64.tar.gz for x86-64, an early preview: it needs glibc 2.39 or later (Ubuntu 24.04, Debian 13, Fedora 40). It is built and started on a clean machine before it is published. Keyboard, pointer and a real GPU on Linux were not checked for this release.
  • WebAssembly threads and SharedArrayBuffer under cross-origin isolation. Squoosh encodes an image in a worker.
  • A real crypto.subtle: AES, HMAC, PBKDF2, HKDF, ECDSA and ECDH on P-256 and P-384, RSA, Ed25519 and X25519, in pages and workers. amazon.com renders. P-521 is not there yet.
  • Much smoother with a UI Automation program around. A screenshot tool or PowerToys kept a page's accessibility tree updating on every frame. It now updates ten times a second at most: resizing a 10 000-item flex page went from 6.9 to about 28 frames a second, and a page of 10 000 changing nodes from 1.9 to about 8.
  • A value lost by the garbage collector. What an async function returned could be collected before its promise took it: an ArrayBuffer turned up empty, a rejection reason undefined.
  • More of the web works: blob: URLs for every load, link preload, the common SVG DOM, FontFace, real CSSRule objects, a document for blank frames. Excalidraw and tldraw load.

0.6.21

  • Modules built with the old form of WebAssembly exceptions run. try, catch, rethrow and delegate are what older Emscripten and .NET builds use, and Wasmtime refuses them: they are now translated at compilation. DuckDB's shell (3.8 MB of C++) starts.
  • Python runs. new WebAssembly.Memory({shared: true}) works for one agent, as other browsers allow without isolation, so Pyodide starts and answers runPython.
  • Faster. A script going through a module's memory one element at a time is 3 to 4 times faster, and an instance with thousands of exports takes 104 ms instead of 5.7 s: Pyodide starts in 5.7 s instead of 11.5 s.
  • A module can catch what JavaScript throws (catch_all, WebAssembly.JSTag), a call can have any number of values, and externrefs are released once the module has dropped them.
  • Service workers: event.waitUntil() works from the promise given to respondWith, as the specification allows. bitplatform.dev's Blazor application starts.
  • Streams: releasing a reader during a read no longer reports an unhandled rejection, which stopped DuckDB's worker and failed pipeTo tests.

0.6.20

  • The WebAssembly JavaScript API. WebAssembly.compile, instantiate, the streaming forms, Module, Instance, Memory, Table, Global, Tag and Exception, in windows and workers. A .NET 10 Blazor application that said "This browser does not support WebAssembly" now works, first through its server circuit, then with .NET itself in WebAssembly; a Yew (Rust) application and SQLite Fiddle (Emscripten) start too. On web-platform-tests wasm/jsapi, 82 % of the subtests pass.
  • A large module does not stop the page. WebAssembly.compile works on another thread: for a 34 MB module, the page's timers keep running (worst gap 25 ms) where a synchronous compile blocks for 440 ms.
  • A page that does not touch WebAssembly pays nothing: its objects are made on first use. The page's Content Security Policy applies ('wasm-unsafe-eval').

0.6.19

  • table-layout: fixed is honoured. A table with a width and table-layout: fixed took its columns from the content, so a commit message that does not wrap widened the file list of a GitHub repository page over its sidebar. The columns now come from the <col> elements and the first row, and the automatic ones share what is left.
  • A frame less between input and screen, from the latest gui_toolkit: on GitHub, with the pointer moving over the page, the time from an input to the refresh that shows it goes from 49.9 ms to 35.9 ms on average (60 Hz screen, same machine and page). The toolkit also gains a lazy column, ui.next_merge, ui.box_at and layout warnings in debug builds.

0.6.18

  • performance.getEntriesByType("navigation") read freed memory. The strings of the navigation entry were freed before they were turned into JSON. The Windows heap left them in place, so nothing showed; on Linux they were overwritten, and GitHub's web-vitals code failed with JSON.parse: bad escape, which left its repository page on "Unable to load page". Fixed.
  • JSON.parse errors say where they stopped, with the text around the position.
  • Wren links and runs on Linux (checked under WSLg on NixOS): the window, the start page, wren ctl, and GitHub's repository page. Nothing to download yet: ./build.sh deps and ./build.sh wren build it, and docs/portability.md lists what was checked and what was not.
  • A new tab no longer puts the keyboard in the address bar (browser.newtab.focus_address brings it back): Ctrl+T opens the new tab page, and the list of frequent pages does not drop over it.
  • The latest gui_toolkit.

0.6.17

  • Hovering no longer restyles the page. On GitHub's repository page, every move of the pointer over a link or a topic tag made a full style pass, about 120 ms each, so the page drew four frames a second under the pointer. Two rules were the cause: a :has() with a hover inside it, and an :is() with a state in it, which the partial restyle took for a match everywhere. Both are now scoped: a state change costs 1.3 ms of style work on average instead of 29 ms, and the share of frames over their budget while the pointer moves falls from 40 % to 4 %.
  • Large ES modules are compiled off the interface thread, like large classic scripts: the page does its other work while a compile thread makes the bytecode.

0.6.16

  • Each module is compiled once. The engine compiled every ES module twice: when it arrived, to learn what it imports, and again when the graph ran, because the first was not kept. It is now kept.
  • Modules are in the bytecode cache. Compiled modules go to the profile's cache, as classic scripts already did, so a module seen before is read instead of parsed. On the repository page of Wren on GitHub (about 130 modules) the page is stable after 4.0 s instead of 7.8 s on a visit that finds them in the cache, and the longest stall between two frames goes from 1.9 s to 0.7 s. The first visit gains about a second.

0.6.15

  • A style pass that really uses the threads. On a page whose elements sit under one chain of containers (GitHub, most web apps), the work was shared level by level, so one thread did nearly all of it. It is now shared by weight. A full pass on the Wren repository page on GitHub goes from about 190 ms to 52 ms; its styles take 1.5 s instead of 3.6 s while it loads, and the 95th percentile of the gaps between two frames goes from 480 to 330 ms.
  • A way to measure a real site: tools\perf.ps1 -Only github_load,github_scroll loads and scrolls the repository page with the profiler.

0.6.14

  • No Save dialog by default. browser.download.ask is now off: a file goes at once to the download folder, under a name that is free (the Downloads folder of Windows unless you chose another).
  • The folder is yours to choose. At the top of about:downloads, the folder in use with Change… (the folder picker of Windows) and Default (back to the Downloads folder of Windows), and a switch "Ask where to save each file" for those who prefer the Save dialog.

0.6.13

  • A downloads button and its drop-down. Once a download has run, a button joins the toolbar: a ring fills around its arrow as the files arrive, and the drop-down (it opens by itself when a download begins) lists the latest ones with a progress bar, the speed and the time left. The cross cancels, a click opens the file, the folder icon shows it in the file manager.
  • about:downloads (Ctrl+J). Every download by day, with a search box. Per file: Open, Show in folder, Copy download link, Download again, Delete the file, Remove from the list; Try again for a failed one; Clear the list for all the finished ones.
  • A list that stays. The finished downloads are kept across restarts (up to browser.download.list_max, 200 by default); a file moved or deleted since says so.
  • A progress bar that is good to look at: it eases toward the real figure, with a gloss on its upper half and a light band travelling over it; a comet runs while the size is not announced.

0.6.12

  • Middle-click autoscroll is half as fast: the top speed is 15000 px/s (was 30000), reached at the same distance; every speed in between is halved. browser.scroll.autoscroll.max_speed sets it.
  • The first painting comes sooner. With nothing downloaded yet, the smallest of the listed files goes first, and a file larger than browser.newtab.art.max_image_bytes is no longer downloaded just to fail. The page reads "Loading a painting…" instead of "No painting could be loaded yet".

0.6.11

  • A search box on the start page. It does what the address bar does with the same text: words go to the default search engine (a keyword such as @g cats picks another), an address opens as it is. The small texts at the bottom of the page are gone; the version and "by Froment Software" stay.
  • Suggestions on focus. A click in the address bar, Ctrl+L or a new tab lists the pages visited most and most lately before anything is typed. Nothing is chosen at first, so Enter still does what the field says. browser.urlbar.top_sites turns it off.

0.6.10

  • A quieter start page. The logo, the title and the tagline stay; the large cards are replaced by a row of thin links with a line icon each (Configuration, History, Content blocker, Demos & tests), and a link with a small coloured painting turns the painting start page on.
  • The painting page's shortcuts are the same four links, frameless and faint over the picture, instead of dark pills.

0.6.9

  • A switch on the pages themselves. The start page has a card, "A painting on every new tab", that turns the painting start page on; the painting page has a "Plain start page" link that turns it off. No more digging in the settings. Only the address bar or a link of the start page itself can flip it: a web page cannot.
  • The first painting arrives in place. The first new tab after turning the option on used to show "No painting could be loaded yet" for good; it now shows the first painting that is ready as soon as it is.
  • Discreet buttons. "Another painting", "Open the record" and "Plain start page" are small, frameless text links, faint until the pointer is on them.

0.6.8

  • Performance tab: Pictures and Export. Pictures records the window's frames as pictures; Export writes a folder to the downloads folder: a page (index.html) that shows the frames big, one at a time or in a strip, with how much each changed and its time, kind, scroll and difference from the one before; the pictures and a contact sheet; timeline.json; and trace.json, the phases of every frame as Chrome trace events (the Performance tab of Chrome and Perfetto open it).
  • wren ctl timeline stop writes the same index.html.

0.6.7

  • gui_toolkit at its latest master: a Linux window with its platform services, password fields, fuzzy_score, check boxes and switches that fit their mark and label.
  • The README no longer opens with the toolkit's old description and describes the JavaScript engine as the one written in Odin.

0.6.6

  • A sticky header stays at the top while the wheel scrolls the kept picture of a page that a script keeps busy: it was drawn once, lower, and not at its place for the frames made meanwhile (Bootstrap's documentation).
  • wren ctl timeline records every frame the renderer draws, chrome frames included, and writes them as pictures with a contact sheet and a JSON of what each frame was. It is how the header above was found.
  • Middle-click autoscroll is much faster: up to 30000 px/s, reached 300 px from the anchor, the speed doubling every 60 px.
  • The cursor over an iframe is its document's: jQuery UI's resize handles show the resize cursor.

0.6.5

  • Drags in frames. An iframe's scripts now get mousemove / pointermove and the boundary events, and a document in which a button was pressed keeps the moves while it is held. jQuery UI's demos (draggable, droppable, sortable, resizable, selectable, slider) work in their frames.
  • The pointer over an empty part of a page is over the root element: mousedown, mousemove, mouseup and click reach it, as do :hover. A script that waited for a mouseup over the background no longer believes the button is still down.
  • Order of the pointer's events. The move comes before the press or release that follows it in the same frame.
  • Autoscroll (middle click): the speed grows geometrically with the distance from the anchor, max * (2^(x/D) - 1) / (2^(F/D) - 1), and doubles every 80 px by default (browser.scroll.autoscroll.doubling_distance).
  • Images in shrink-to-fit boxes (blog.jquery.com): an image with a percentage max-width no longer makes its inline-block link as wide as itself.

0.6.4

  • Fixed and sticky boxes while a page is busy. When a script keeps the interface thread and the wheel scrolls the kept picture of the page, a fixed header or a sticky box (Bootstrap's documentation header) stays where the page drew it, instead of sliding with the picture and jumping back.
  • No more partial redraw. browser.gpu.partial_redraw never ran on a window; the compositor no longer hashes every chunk at every frame for it.
  • setAttribute with the same value changes nothing in the document: observers still see the write, but there is no restyle and no layout.
  • Tests. The end-to-end sessions that drive a Wren run on a developer's machine, not on the shared runner; the release workflow no longer runs the tests again.

0.6.3

  • Tabs. A middle click on a tab closes it again. A tab dragged onto another window's tabs shows where it would go: the carried window steps aside, the tabs make room and a faded copy of the tab stands in the gap.
  • Scrolling while a page is busy. The page's scroll bar no longer jumps up and down while a script keeps the interface thread: it was drawn into the kept picture of the page and moved with it. The loading bar and the page held during a navigation are the same in every frame.
  • Bootstrap's documentation. A grid item with a stated minimum no longer stretches the flexible tracks (the content column of its pages), <meta http-equiv="refresh"> redirects work, and a script that writes a style it already has no longer restyles the page: its table of contents did it at every scroll event.
  • The tab sidebar. A light line along its inner edge shows where the page begins, in both styles.

0.6.2

  • No Zig, no C library. The engine module is linked by the wasm-ld that ships with Odin. The heap, the string functions, strtod, snprintf and ldexp are Odin (native/js/odin/wj/libc.odin), exp is fdlibm's, and sin, cos and pow come from the host. Only Lexbor remains as external C.
  • The documentation site. Its pages use links relative to the site's root, so they have their style, logo and search under /wren/. Code wraps instead of overflowing. The changelog page and the version on the home page follow the releases.
  • The pictures of the home page. Four pictures of one window with four tabs, as the clickable zones expect, and new captures of every page from a current build.

0.6.1

  • Wren 0.6.1 makes the Odin JavaScript engine lighter. An empty realm takes 4.3 MiB of WebAssembly memory instead of 6.4, and is created in 1.3 ms instead of 2.3; a page with 650 KiB of script…

0.6.0

  • The JavaScript engine is Odin. jsrt is the default and only engine: parser, bytecode interpreter, a JIT for numeric loops (compiled to WebAssembly), regular expressions, BigInt, dates, number conversion and the module loader. The glue to the Web APIs is generated Odin (native/js/odin/bindings_gen.odin). Only lexbor remains as external C. Checked against the same suites as before: the JavaScript, DOM, load, web and frames test programs, test262 (no regression) and the JIT's own verification.
  • Speed and memory. The interpreter reaches about the speed of QuickJS in native code; under Wasmtime it is still 1.5 to 2.5 times slower than QuickJS in C on calls and slower to create a realm. An empty realm takes 6.4 MiB of WebAssembly memory (7.7 before), a page with 650 KiB of script 27 MiB (46 before).
  • More pages look like Edge's. A side-by-side comparison with Edge (tools\site_compare.ps1, 40 public pages) found and fixed: linked stylesheets applied in arrival order instead of document order (python.org), <dialog> without its default style (Bootstrap 6 pushed its page 185 px down), the page's CSS ignored inside inline <svg> (icons, light-dark()), <button> width in block and flex display, and text-wrap: balance. getbootstrap.com went from 49 % to 9 % of pixels different, python.org from 51 % to 11 %.
  • A crash fixed. Going from one CSS-heavy page to another in the same window could read freed memory (custom property layers pointed into rules a rebuild had freed).

0.5.0

  • A remote debugging port. wren --remote-debugging-port=9222 speaks the Chrome DevTools Protocol (Puppeteer and Playwright attach; /json/version and /json/list answer), and --headless runs with no window at all (it renders to an offscreen target, so captures still work; no desktop session is needed). wren ctl start keeps a window that is never shown (--hidden) unless you ask for --headless. Known limits are in docs/cdp.md (no Fetch.*, no response bodies).
  • More of the web. WebCodecs (VideoFrame, ImageDecoder, VideoDecoder, AudioDecoder, AudioData, encoded chunks; the encoders refuse honestly), URLPattern, scheduler.postTask / yield and TaskController, Web Locks, Intl.DurationFormat, bitmaprenderer canvases, WebVTT text tracks and audio and video tracks, requestVideoFrameCallback, a VP9 software decoder, Managed Media Source, long animation frames with their render timing, navigator.userActivation, element.sheet and document.styleSheets, emoji in a canvas, createImageBitmap from more sources.
  • Natively implemented. Intl, custom elements, shadow DOM, frames, the async clipboard and the IndexedDB façade moved from JavaScript prelude to native code (IndexedDB passes the same WPT as before).
  • Text. lang selects the locl feature, optical sizing, text-rendering, colour glyphs, and smoothing of canvas text on the CPU.
  • Pages and chrome. Middle-click autoscroll, a translucent − % + zoom indicator at the end of the address bar, an optional new-tab page of public-domain artworks (browser.newtab.art.enabled, off by default), a font preview for WOFF2, the content blocker's ##^ filters, DevTools improvements, a silent --uninstall and a winget manifest.
  • Partial redraw. The compositor can repaint only what changed (browser.gpu.partial_redraw, with a verifier that compares against a full frame).

0.4.0

  • Text without DirectWrite. Fonts, shaping and line breaking come from Fontations, HarfRust and ICU4X (Rust), the same on every machine; WREN_TEXT=dwrite brings DirectWrite back for comparison.
  • Accessibility. The browser's chrome and the pages are exposed to screen readers through UI Automation (AccessKit): roles, names, states, text fields with their caret and selection, iframes, and updates node by node when a page changes. Nothing is built or sent while nothing changes. Not tried with NVDA or Narrator yet.
  • A configuration page you can read. about:config has readable titles with the technical name beneath, categories with their counts, the value next to its name, a mark and a reset button on what you changed, a search over titles, names and descriptions, and a "modified only" filter.
  • Search engines. A list of engines (DuckDuckGo, Bing, Google, Qwant, Ecosia, Startpage, Wikipedia, and your own) with a default and keywords: @g cats searches with Google, Ctrl+Down picks another engine for one search. An existing choice is kept.
  • Tab sidebar, second part. The sidebar opens and closes with a fade; pinned tabs; tab groups (name, colour, fold, drag in and out); a speaker on the tab that plays, with a mute button per tab; 1000 tabs cost about 1 ms a frame instead of 11 ms.
  • Several windows, each on its own. Each window has its own developer tools (docked or detached), its own blinking caret, its own scale on a monitor of another DPI, and the drop mark of a tab dragged between windows is exactly where the tab lands.

0.3.0

  • A JIT for JavaScript that produces WebAssembly. Hot functions and loops of QuickJS-ng are compiled to Wasm modules on background threads, then by Wasmtime (Cranelift); guards fall back to the interpreter exactly. Modules are position-independent, shared by every page and kept in the disk cache: a second tab or a reload compiles nothing. Octane about 1.75 times as fast as the interpreter (navier-stokes ×4, zlib ×2.7), loops and property accesses ×4 to ×6, DOM getters twice as fast. test262 and web-platform-tests show no regression, with everything compiled at once too.
  • Iframes. Each <iframe> is a document of its own, with its origin, its scripts and its JavaScript engine instance: src, srcdoc, sandbox, allow (Permissions Policy), postMessage, cross-origin WindowProxy limits, X-Frame-Options and frame-ancestors, forms and links aimed at a named frame, storage and HTTP cache partitioned by top-level site.
  • The browser never waits for a page. A long script, style or layout pass no longer freezes tabs, the address bar or the menus: a 200 ms script task now holds the interface about 15 ms (it held it 210 ms). A busy page keeps its last picture, which scrolls at once.
  • An HTTP cache. Memory and disk, RFC 9111 (freshness, revalidation, Vary, partitioning by site), about:cache; F5 revalidates, Ctrl+F5 reloads everything. Web fonts come from the cache before the first paint: no more flash of the fallback font between pages.
  • Passkeys. navigator.credentials and WebAuthn through Windows (Windows Hello, security keys, phones), with the specification's origin and RP ID rules.
  • History as a tree. Going back restores the scroll position, zoom and filled forms; back and forward are instant (back/forward cache); branches are kept, about:history (Ctrl+H) lists every visit and draws each tab's tree, which can be panned, zoomed and shown horizontally or vertically.

0.2.1

  • Fixes a crash about three seconds after start when the pointer was over the window: the first frame read the interface state before it was built.

0.2.0

  • JavaScript inside WebAssembly. QuickJS-ng 0.17.0, compiled by a pinned Zig to a WASI module, pre-initialized at build time and embedded in wren.exe: a new page's realm starts in about 0.5 ms. Each page and each JavaScript worker has its own instance, with a heap cap, a clean stop for scripts that run too long and a hard deadline behind it; a crash of the engine loses that page's scripts, never the browser. JavaScript can be turned off globally (javascript.enabled) or per site (javascript.sites.disabled, javascript.sites.enabled). The engine passes 99.92 % of test262; it runs about 0.77 times as fast as the same engine compiled natively, and about 55 times slower than Edge on Octane.
  • The web platform for scripts, generated from the specifications. The bindings are generated from the official Web IDL (webref), with Web IDL's conversion rules; 2,630 of the 4,991 members of the specifications are implemented, and those that are not are absent, so feature detection works (docs/js/coverage.md). The DOM (tree, selectors, innerHTML, CSSOM, geometry, events, observers, ranges), script loading as HTML specifies it (defer / async, document.write, ES modules and import maps, inline handlers, <noscript>, CSP), custom elements, IntersectionObserver, PerformanceObserver, fetch, XMLHttpRequest, streams, Blob, URL, encoding, storage, history, crypto.subtle, WebSocket, EventSource, workers and a minimal Intl. About 106,000 web-platform-tests subtests pass, and every regression fails the test suite.
  • Shadow DOM. attachShadow and declarative shadow roots, which render even with JavaScript off; named and manual slots; encapsulated styles with :host, ::slotted(), ::part() and :state(); constructable stylesheets; event retargeting and delegatesFocus. Sites built from web components, such as MDN, now show their header bar and their theme, language and filter controls.
  • Pages closer to Chromium. Text is placed vertically as Chromium places it (font metrics rounded the same way, half-leading, vertical-align, flex and button centering, table cells, form controls). Old-style HTML attributes (bgcolor, align, <font>, table attributes) and quirks mode are honoured, image sizes follow the decoded image, and synthetic italics work for web fonts.
  • A more complete browser. Tabs with favicons, loading indicators and drag to reorder; error pages by cause; downloads with the Windows Save dialog; optional session restore; Windows paths in the address bar, files and folders dropped on the window, folders opening their index.html or a list of their files; about:demos grouped by theme, JavaScript included.
  • Faster and safer. Shaders are precompiled, so the start page is drawn about 0.3 s after launch instead of 1.8 s; resizing no longer reallocates the swap chain and media queries only restyle when their answer changes. A review of the whole engine fixed about 90 problems, among them use-after-frees, ways for a file: page to leave its folder, content that could crash or freeze the browser, and CORS on redirects; flaky tests were fixed at their cause rather than retried. Closing a WebSocket from a message handler no longer ends in an error, and a page that floods timers no longer stalls the interface.

0.1.0

  • WebAssembly runtime: Wasmtime 49 (Cranelift), compiled-code caches in memory and on disk (HMAC-authenticated), a deadline on every call (epoch interruption), several modules per document, memory64, dedicated workers.
  • Web API from one description: 21 modules, 289 functions (DOM, events, forms, timers and rAF, fetch with streams, AbortController, URL, encoding, crypto, storage, history, performance, images, EventSource, WebSocket, workers), with generated host trampolines, Odin SDK, documentation and conformance tests.
  • HTML and CSS: Lexbor parsing, cascade layers, custom properties and @property, :has / :is / :where, media and container queries, Color 4 / 5, math functions, web fonts (WOFF, WOFF2), transitions and animations, color-scheme.
  • Layout: block, inline (bidi), floats, positioning (sticky included), flexbox, grid with subgrid, tables; incremental relayout, parallel style and paragraph layout.
  • Painting: a retained GPU compositor on Direct3D 11; transforms, rounded clips, opacity groups, filters, backdrop filters, blend modes, clip paths, masks, exact box shadows, gradients in every color space; sRGB blending as in other browsers.
  • Text on DirectWrite: font fallback, OpenType shaping, bidi, color glyphs in every format.

Every version Releases on GitHub

What the 0.2.0 preview does

Every number on this site was measured on the development machine; the notes behind them are in the repository history.

The web platform, from its specifications

The JavaScript bindings are generated from the official Web IDL (webref) with Web IDL's rules: DOM, CSSOM, events, observers, custom elements, script loading with ES modules, fetch, XHR, streams, storage, WebSocket, workers, crypto.subtle. 2,630 of 4,991 members are implemented; the others are absent, so feature detection sees the truth.

Measured against the standards

The engine in WebAssembly passes 99.92 % of test262. 4,494 web-platform-tests files run with every change, 106,188 subtests pass, and any regression fails the test suite.

Shadow DOM

Declarative shadow roots that render even without JavaScript, slots, encapsulated styles with :host, ::slotted(), ::part() and :state(), constructable stylesheets, event retargeting: web components, such as MDN's, show up.

Nothing runs outside the sandbox

Every call from the browser into a module, JavaScript included, has a deadline (wasm.max_call_ms), and each module's memory is capped. A loop that never returns is interrupted; a crashed engine loses the page's scripts, not the browser.

Wasm pages, any number of modules

Modules are fetched concurrently and run in document order, each in its own instance sharing the DOM. Wasmtime 49 with Cranelift, a memory cache and an authenticated disk cache of compiled code.

Modern CSS

Cascade layers, custom properties and @property, container queries, CSS Color 4 and color-mix(), math functions, flexbox, Grid Level 1 with subgrid, transforms, animations and transitions; text placed vertically as Chromium places it.

Effects on the GPU

filter, backdrop-filter, the 16 blend modes plus plus-lighter and plus-darker, clip-path, masks, background-clip: text, linear, radial and conic gradients interpolated in sRGB, Oklab, Oklch, Lab and more.

Text on DirectWrite

Per-character font fallback, OpenType shaping and features, bidirectional text, line breaking with dictionaries for Thai, Lao, Khmer and Burmese, variable fonts, WOFF2, colour emoji in every font format.

Images

PNG, JPEG, GIF, BMP, ICO and TIFF through Windows; WebP, AVIF and SVG built in, whatever Windows has installed, with ICC colour management. Decoding runs on its own threads.

At the display's refresh rate

Frames follow the monitor's exact rate, with a budget of 1000/Hz. Wheel scrolling a page of 10,000 paragraphs: 1.4 ms CPU and 2.3 ms GPU at the 99th percentile, under the 5.56 ms of a 180 Hz screen. The start page is drawn 0.3 s after launch.

Security from the standards

One policy layer for every request: CORS with preflights, cookies (RFC 6265bis, third-party cookies blocked by default), Content Security Policy for scripts and Wasm modules, HSTS, mixed content blocking, referrer policy.

Everything is configurable

about:config lists every setting with its description, default, bounds and unit: rendering, scrolling, fonts, network limits, Wasm and JavaScript limits. Nothing is hard-wired.

Frame statistics built in

The status bar draws a graph of every frame by phase. A click opens CPU and GPU percentiles, compositor, layout, memory and the time spent in Wasm.

A native window

Tabs in the title bar with favicons and Snap Layouts, error pages, downloads, local folders and dropped files, pages that follow Windows' light or dark mode, selection, find in page, zoom, view-source: and a keyboard path through all of it.

What works, and what doesn't yet

Wren is a young engine with one goal, and an honest list of gaps.

Works well

  • Documents and CSS-heavy sites. Compared pixel by pixel with Edge at 1280×720: 0.7% of pixels differ on example.com, 4.5% on rust-lang.org, 7.3% on webassembly.org.
  • Sites whose scripts add to the page: menus, themes, web components, observers. Wikipedia, MDN, webassembly.org and odin-lang.org run their scripts.
  • Pages scripted in WebAssembly, with the DOM, events, timers, fetch, WebSocket, workers and storage.
  • HTTP/1.1, HTTP/2 and HTTP/3 on a built-in network stack, an HTTP cache, cookies, CORS and CSP.
  • Canvas 2D, <video> and <audio>, Media Source Extensions, WebCodecs decoders, service workers, IndexedDB and the WebAssembly JavaScript API.
  • Web fonts (WOFF, WOFF2, variable fonts), SVG, AVIF and WebP images.
  • Smooth scrolling, selection, find in page and zoom on long pages.

Not yet

  • JavaScript is interpreted, with a JIT for numeric loops only. Script-heavy pages are slow: before the Odin engine, about 55 times slower than Edge on the Octane benchmarks, and a simple DOM call cost about ten times as much.
  • About 72% of the members of the web platform's Web IDL. No WebGL, WebGPU, Web Audio or WebRTC, no video or audio encoder in WebCodecs. The full list is docs/js/coverage.md.
  • No HTTP authentication.
  • No IME for East Asian input, no vertical writing modes, no multi-column layout, grayscale text anti-aliasing only.
  • No Ogg, MP3 or WAV files in <audio>, no Encrypted Media Extensions.
  • The guest SDK exists for Odin only; the ABI is documented for other languages.
  • Releases for Windows only. The Linux build runs from source, with nothing to download yet.

Measurements

Medians or percentiles from the repository's tools, 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
A new JavaScript realm, from the pre-initialized image (median of 50)0.5 ms
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
about:config, building the interface per frame while scrolling34.4 → 0.29 ms

Arrows show a measured improvement, before and after the change that made it. The whole table is on the architecture page.