Repository navigation
Releases: sachahjkl/wren
Release list
Wren 0.6.29
Wren 0.6.29
Wren 0.6.29 can install its own updates, signed and checked, and brings devtools, JIT and compatibility work.
This is still a beta, under development.
Highlights
- Signed updates (Windows): a release carries a manifest signed with a key of its own. About Wren checks for a newer release and, when you press "Install the update", downloads the program, compares its length and SHA-256 with the signed ones, and swaps it in with a way back. Nothing is installed without the button. The channel (address, files, key, signature required) is configurable: see docs/updates.md.
- Developer tools: find in the panels, a console drawer, a colour picker, style values.
- Faster scripts: the JIT compiles whole functions, calls, objects, strings and DOM getters.
- Compatibility: SVG
foreignObject, CSS module scripts, subresource integrity, script preparation, CSSOM rules andinnerText, among many fixes (see the list below). - Help: the licence help button opens a GitHub issue form with your order number filled in.
Added
- The update channel is configurable:
browser.update.require_signature(on by default) can be turned off for a channel of your own that does not sign (About Wren then says the update is UNSIGNED and only the length and SHA-256 are checked), next tobrowser.update.url,browser.update.base_urlandbrowser.update.public_key. docs/updates.md describes how to run a channel. - Signed updates (Windows). A release now carries
update-windows-x64.json, a manifest (version, file, length, SHA-256) signed with a key of its own (not the licence key), and.json.sig. About Wren has an "Updates" section: "Check for updates" asks GitHub, then fetches the manifest and checks its signature, platform, announced version and that it is newer; "Install the update" (also the command "Install the update") downloads the program, compares its length and SHA-256 with the signed ones, writes it beside the program, reads it back, and only then swaps it in, putting the old one back on failure. Nothing is installed without the button. Development and labelled builds, and Linux, only check.wrentool update keygen|pubkey|manifest|sign|verifymakes and checks the files;wrentool package -RequireUpdateManifestis used by the release workflow (secretWREN_UPDATE_SIGNING_KEY). - Values edited as in Chrome's Styles pane (
src/browser/devtools_styles.odin): the number at the caret of a value steps with Up / Down (1), Shift (10), Alt (0.1), Ctrl (100), Page Up / Page Down (10) and the wheel over the focused field, in any value (margin: 10px 5px,rgba(...),calc()), and the page follows; a typed name is suggested the engine's properties, a typed value the keywords the engine takes for the property (Up / Down choose, Enter / Tab / a click take one); Tab and Shift+Tab go between name, value and the declarations around, Escape puts the declaration back as it was (one it added goes), and an!importantdeclaration keeps it when edited. - A colour picker in the Styles pane (
src/browser/devtools_style_edit.odin, the toolkit'sui.color_picker): a swatch before every colour of a value (hex, the colour functions, the names; several in a value), a click opens the picker on it (square, hue and opacity bars, the old and new colour, hex / rgb / hsl / oklch); the page follows each change, the colour stays in the format it was written in (hex stays hex,rgb(1, 2, 3)keeps its commas; a name,hwb()orlab()becomes hex), Escape takes the value back, Enter or a click elsewhere keeps it. The Computed view's swatches read every colour syntax too. - Escape shows the Console under the panel (Chrome's drawer,
devtools_drawer_toggle): with the keyboard in the developer tools and nothing there taking it, Escape puts the Console in a stack of its own under the others (what "Show the Console under the other panels" does) and folds it away again; the layout keeps it. - Ctrl+F in the developer tools (
src/browser/devtools_elements.odin,src/dom/xpath.odin): in Elements, "Find by string, selector, or XPath" finds the nodes whose tag, attributes or text hold the query, those the query selects as a CSS selector (the page's own matcher) and as an XPath 1.0 expression (paths and axes, predicates, unions, comparisons,contains,starts-with,text(),last(),count()...), together in document order; it saysn of m, Enter / F3 go to the next match and Shift+Enter / Shift+F3 to the previous one, the tree opens and scrolls to it and the page shows its box, the matches follow the document as it changes, Escape closes it. Ctrl+F in the Console, the Network and the Application panels takes their filter field. F3 and Ctrl+F there are the developer tools', not the page's find. - Links that open in Wren: a web page can link to
wren-https://...,wren-http://...orwren-about:wren, and the link opens in Wren (a new tab of the Wren that runs, else a new Wren) with itswren-prefix taken away. Setup and Repair register the three schemes (Windows:HKCU\Software\Classes; Linux: the desktop entry's MIME types), the uninstall removes them. Only http and https addresses and about:wren come out of a link: any other scheme, a control character or a link over 8 KiB opens nothing.wren.exe --open-url <link>is what the system runs (docs/desktop-integration.md). sheet.media: a stylesheet'smediais itsMediaList(mediaTextserialized,length,item(),media[i]); settingmediaTexton a constructed sheet's list changes its media.- External profilers (
docs/performance.md, Profilers): a build withWREN_TRACY=1links Tracy's client (0.14.1, pinned;wrentool fetch tracyfetches its viewer), on demand, which shows the phases of each frame, the frames and each call of the JavaScript engine into the browser; the profile build is an ETW provider (Wren.Browser, TraceLogging) whose phases WPA shows as regions (tools/wren.wprp,tools/wren_regions.xml,wrentool trace start|stop);wasm.wasmtime.profiler=vtunenames the compiled Wasm functions in VTune, the JavaScript engine's among them; threads are named for profilers and debuggers (wren-window,wren-style,wren-layout, the Wasm engine's). The default build links none of it. - CSS module scripts:
import sheet from "./x.css" with {type: "css"}(andimport()with it) gives aCSSStyleSheetof the file's rules, to adopt withadoptedStyleSheets, as web component libraries do. A URL imported with one type and then another fails as in other browsers. - HTML inside inline SVG (
<foreignObject>): its content is laid out and painted in the SVG's coordinate space (viewBox, transforms), clipped by the<clipPath>around it and blurred by itsfeGaussianBlurfilter. Google's new "G" logo (a conic gradient clipped to the letter) shows in colour.
Changed
- Native functions called by compiled code cost less (
src/engine/jsrt/jit.odin): the engine's helper calls them as the interpreter's hot loop does, without the generic call's dispatch.a.push(i)/a.pop()in a compiled loop 116 -> 107 ns,Setadd/has 168 -> 150 ns,Math.sin/Math.pow160 -> 147 ns. - The JIT reads DOM attributes (
src/engine/jsrt/jit.odin): compiled code calls the native getters it finds on an object's prototypes (node.firstChild,list.childNodes.length,map.size), reads through the Web IDL prototypes of DOM nodes (dictionaries, guarded by a version of their keys) and treats DOM wrappers as ordinary objects; an exception in a getter unwinds exactly once, as if the interpreter had called it. A loop that walks a list of nodes reading four attributes 750 -> 530 ns an iteration,m.sizein a loop 115 -> 48 ns; a loop that reads the globaldocumentis not compiled yet. - The JIT compiles loops that concatenate and compare strings (
src/engine/jsrt/jit.odin):s += xanda + bwith a string are made by the engine's own concatenation (its ropes and append buffers), a conversion that could run code or throw left to the interpreter so that it happens once, and===/switchcompare strings by value. A switch on strings 105 -> 14 ns,s += "a"withcharCodeAt245 -> 127 ns. - The JIT compiles code that makes objects (
src/engine/jsrt/jit.odin):new F(...)of a plain constructor, object literals and array literals in compiled code make their objects through the engine's allocator (a helper of the engine, the collector's rules unchanged), and a property written that the object does not have yet is appended through the shape's transition (a constructor'sthis.x = x). A loop that makes objects stays compiled;new Point(x, y)in a loop 404 -> 112 ns,{a, b, c}261 -> 55 ns,[i, i + 1, i + 2]123 -> 80 ns. - The JIT calls the script's getters and setters (
src/engine/jsrt/jit.odin): a loop that reads a property through a getter of the script's (get x() {...}, a class's) or writes one through a setter, native or the script's, stays compiled, the accessor called as compiled code calls a function, an exception unwinding exactly once. A getter read in a loop 151 -> 78 ns, a setter 145 -> 81 ns,getter accessof macro.js 184 -> 92 ns. - The classic style is the default: tabs are trapezoids attached to the toolbar unless browser.ui.chrome_style is set to modern (docs/chrome-style.md).
- The pricing, terms and refunds pages (both styles) link to the licence help issue form instead of the bare issue list.
- Compiled code allocates objects itself (
src/engine/jsrt/jit.odin): an object literal and the object ofnew F()(the constructor'sprototypeguarded) are made by the code, from the engine's own pool and list of cells, without a call to the engine; the collector's contract is unchanged (allocation never collects).new Point(i, i)110 -> 51 ns,{a, b, c}57 -> 28 ns. - **The JIT compiles whole functions and calls compiled code directl...
Wren 0.6.28
Wren 0.6.28
Wren 0.6.28 gives its scroll bars room as Chrome's, docks its developer tools, keeps frames and popups in the history, and runs pages that rebuild their DOM faster again.
This is still a beta, under development.
Highlights
- Classic scroll bars: page and element scroll bars take their room, with arrows at their ends (
documentElement.clientWidthis the window less the bar, as in Chrome on Windows);scrollbar-gutterandscrollbar-widthapply. The old overlay bars arelayout.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.
- Names and keywords are matched ASCII case-insensitively everywhere, as the web platform says (a Kelvin sign is not a
K). - Menus are dense and scroll when the window is too low; a right click on a download opens its actions; Setup's status line no longer blinks.
- Built with Odin dev-2026-11, the C runtime linked statically on Windows.
Added
- CSP Embedded Enforcement:
<iframe csp="...">(andiframe.csp) requires a Content-Security-Policy of the documents the frame shows. Its requests say which (Sec-Required-CSP); a page that agrees (Allow-CSP-From) or has a policy as strict is shown with it enforced, about:blank, srcdoc,data:andblob:documents take it, any other is refused asX-Frame-Optionsrefuses. Thesandboxdirective of a Content-Security-Policy header now sandboxes the document. document.domainsetter: a page and its frames of other ports or subdomains of one site that set the samedocument.domainreach each other's documents and windows (HTML's same origin-domain), and a navigation between them fires the target'snavigateevent; a value that is not the host or a registrable suffix of it, or a sandboxed document, throws aSecurityError; underOrigin-Agent-Cluster: ?1the assignment changes nothing.::backdropof the top layer: a modal dialog, a showing popover and the fullscreen element get a box the size of the viewport just under them, above the page (the UA's faint grey behind a modal dialog, black behind a fullscreen element, styled by the page); a click on a dialog's backdrop is the dialog's, a popover's lets it through.getComputedStyle(el, "::backdrop")and"::placeholder"answer, and::part()::backdropis a valid selector.::placeholderon text fields and textareas: its color, opacity, font (size, family, weight, style) andtext-alignapply; the default is Chromium's grey (#757575) instead of the field's color faded.- Scroll anchoring (CSS Scroll Anchoring,
overflow-anchor): when content above what you are reading changes size (an image that loads late, a banner inserted or removed), the page keeps showing the same content instead of jumping; not at the top of a page, not whereoverflow-anchor: nonesays so. ReadingscrollX/scrollYfrom a script gives the offsets after the layout the script's changes call for. - The
scrollendevent: once a scroll container or the page stops moving (a script's scroll, the wheel, the keys, smooth scrolling), at the first frame where it did not move again; an element's does not bubble, the document's reaches the window. Ascrollevent now also fires for a container a script moved and put back before the next frame.
Changed
- WPT runner: windows with a history, a second http port: the windows a test opens keep a session history across their documents and follow
history.back(),history.go()andnavigation.traverseTo()between them, and the test server answers wptserve's second http port ({{ports[http][1]}}), so tests of another origin on another port run instead of timing out. - Styling no longer pays for
::backdropon every element: the rules of::backdrop(the browser's own, and resets such as Tailwind's*, ::before, ::after, ::backdrop) are tried only for the elements of the top layer (a modal dialog, a showing popover, the fullscreen element). A page of 10 000 elements restyled whole every frame: its style stage went from 0.84 to 0.75 ms. The static C runtime Wren links since Odin dev-2026-11 was measured against the dynamic one: the same speed. The numbers:docs/performance.md. - Pages that drop many nodes a frame collect them faster: the JavaScript engine no longer asks each node object a collection frees for the values it holds when it holds none, and the browser frees a removed tree without a list of its own for each. A page that builds and drops 10 000 nodes a frame: the collection that frees them went from 3.5 to 2.8 ms, its script stage from 12.2 to 11.4 ms a frame. The numbers, and what was measured and not kept:
docs/performance.md. - Developer tools: panels you dock (the toolkit's docking, gui_toolkit 0.71). The panels (Console, Elements, Network, Application, Performance, Memory) are stacks of tabs: drag a tab out of its strip and squares show over the stack under the pointer (into it, or beside it at a side), at the edges of the panel and between the tabs of a strip, with a translucent preview of where it lands; let go elsewhere, it floats over the panel (moved by its strip, resized by its corner, docked back by a double click on its strip); Escape cancels. Each stack's menu (its button, a right click or the menu key on a tab) does all of it from the keyboard; Ctrl+Tab in a stack shows its next panel. The bars between stacks resize them and fold the second one (a double click, Enter). The Elements panel has the same for the DOM tree and the views of the chosen node (Styles, Computed, Layout, Event Listeners, Accessibility): Styles below the tree, Computed beside it, one of them floating. The layouts are kept in the profile (
browser.devtools.layout,browser.devtools.elements.layout, one line each); they replace the drawer and the side pane's place (browser.devtools.drawer,drawer_tabs,drawer_tab,browser.devtools.elements.sidebar,side_tab), whose values a profile had become the new layouts (the drawer's tabs a stack under the others at its height, the side pane below the tree or folded as it was). By default the layout is the one before: one stack of the panels, the tree with its views at its right. - Built with Odin dev-2026-11 (
odin.versionand the CI's pins; the toolkit v0.70.0):wren.exelinks the whole C runtime in (libcmt, libucrt, libvcruntime: Lexbor and the Rust libraries are built against it, as Odin's own C library is) and imports no runtime DLL; WOFF tables inflate through the newcore:compress/deflate, bounded by their declared size. - Developer tools: panes you size. The panel docked under or beside the page goes from a header and a row to all the window but a strip of the page (it was held to 70 %); a floating one shrinks further. The Network tab's request details and the Application tab's value pane are resized by their bars (the network bar's drag ran away from the pointer; the value pane's bar did nothing).
- Classic scroll bars (
layout.scrollbar.overlay, off by default on every system, as Chrome and Edge on Windows and Linux): the page's scroll bars and those of scrollable elements take room instead of covering the content, with an arrow button at each end (a press scrolls a line,browser.scroll.key_line_px; held, it repeats after 250 ms every 50 ms):- the page's bar comes out of the initial containing block:
document.documentElement.clientWidthisinnerWidthless the bar (15 px,layout.scrollbar.classic.width_px),100vwand media queries still see the whole window, a full-width footer ends where the bar begins; fixed boxes and sticky ones keep to what the bars leave; - an element with
overflow: scrollhas its bars whether it overflows or not,overflow: autowhen it overflows (decided after a pass, laid out again: the first comparison of a layout may add or take bars away, the later ones only add, at most three more passes, so a bar that makes its content fit stays as in Blink); they sit between the border and the padding, a specifiedwidthkeeps its border box (the bar comes out of the content:clientWidthless the bar), and an auto width grows by it; scrollbar-width(thin: 11 px,layout.scrollbar.classic.thin_width_px;none: no bar, it still scrolls) andscrollbar-gutter(stable,stable both-edges, foroverflow: autoandhidden, on the root element too) are honoured;scrollbar-colorpaints them;- colors:
layout.scrollbar.classic.thumb_color,track_color,arrow_color, andbrowser.scrollbar.dark.classic_*in the dark scheme; - a viewport less than two arrows long either way (a tiny frame) h...
- the page's bar comes out of the initial containing block:
Wren 0.6.27
Wren 0.6.27
Wren 0.6.27 fixes links that all showed their hover style together, and follows the standards more closely for names, international domain names and forms.
This is still a beta, under development.
Highlights
- 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:
createElementNSandsetAttributeNSkeep 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.
- Full style passes are faster again (the page's color scheme read from its
<meta>elements, subtrees weighed
in one walk), and pages with columns build their display list on the layout threads.
Changed
- Pages that restyle everything style faster: before the style threads share a full pass, the browser weighs the document's subtrees in one walk instead of three, and a pass takes the slots of the pass recycled before it instead of faulting fresh memory in. A page of 10 000 elements restyled whole every frame: its styles 0.74 ms a frame instead of 0.90, its frames' CPU work 3.25 ms instead of 3.77. The numbers:
docs/performance.md. - Pages with columns and pages that restyle everything lay out and style faster: the display list of a page with columns is made on the layout threads as on any other page, a layout with columns and no vertical writing mode no longer turns every box to physical coordinates for nothing, a full style pass no longer walks the whole document for a
<meta name="color-scheme">nor asks the forms model whether every element is disabled, and the comparison of a full style pass with the one before compares each pair of shared styles once. A page that rebuilds a list of 2 500 items in six columns every frame: its layout 3.2 ms a frame instead of 3.6, its styles 1.7 ms instead of 1.85; a page restyled whole every frame: its styles about a third faster. The numbers:docs/performance.md.
Fixed
wrentool compareand the benchmarks run in a fresh checkout: the folder of their programs' logs (build\bench) is made when missing, where Edge failed to start ("its log cannot be opened") and no comparison was made.- Hovering one link of a nav no longer paints every link of it in its hover style (the new tab page's links, a site's menu): alike elements share one style record, and the paint-only refresh of the display list re-pointed items by their old record, so every link that shared it took the hovered one's new style. Each display item now takes its own box's or inline item's style.
classList.add()andremove()without a token,replace()with an empty one:add()andremove()with no argument still write the attribute back in its serialized form ("a b c a "becomes"a b c"), as the DOM's update steps say, andreplace()with an empty token throws aSyntaxErrorbefore looking for whitespace in the other.- Text, comments, fragments and processing instructions of a document of its own are its: what
createTextNode,createComment,createDocumentFragmentandcreateProcessingInstructionof a document of its own (createHTMLDocument's,new Document(),DOMParser's) make has that document asownerDocument, as its elements already had. - XMLHttpRequest and FileReader decode text as the Encoding Standard says: a byte order mark chooses the encoding (a UTF-16 response without a charset, or with one it contradicts, is read as UTF-16), and a label of the replacement encoding (
iso-2022-kr,hz-gb-2312...) gives one U+FFFD instead of the text read as UTF-8. - Forms submitted to several frames at once: a page that submits two forms in a row, each to a frame of its own, navigates both frames (the second submission replaced the first, whatever its target: a frame was left waiting forever). A newer submission still replaces one planned for the same target. WPT
encoding: the first part of each of the 45 form-encoding tests no longer times out, 18000 more subtests pass. - A timer runs after the tasks queued before it: a
setTimeout(0)set from a task of the page (a script the parser runs while the page loads) ran before the tasks that script had queued, so a dialog'stoggleevent came after it; it runs after them now, as it did from a timer's script. WPTtoggle-events.htmlpasses whole. - International domain names as the URL Standard has them: a host all of ASCII is only lower-cased (an
xn--label that does not decode, or decodes to what UTS #46 would refuse, no longer makes the URL invalid:xn--a,xn--unicode-.org); no length of a domain or of its labels is verified; a label beginning with a combining mark, or with a zero width joiner or non-joiner out of the context CheckJoiners allows, is refused (Lexbor 3.0.1 does not check them:src/shim/wb_idna.c, its tables generated from the Unicode Character Database bywrentool gen idna). WPTurl10946 -> 12056 passing. - XML documents in frames: a frame's response of an XML type (
application/xml,text/xml,+xml) is an XML document (it was shown as text), with itscontentType;document.open(),write()andclose()of an XML document throw anInvalidStateErroras HTML says. WPTdom/nodes+170 (the XML frames ofcreateElementandcreateElementNS),custom-elements+22. - Names as scripts give them:
createElementNSandsetAttributeNSkeep the case of the names they are given (localName,prefix,attributes), an element'stagNameupper-cases its whole qualified name in ASCII only,setAttributeon an element of another namespace than HTML or SVG keeps its case, andgetElementsByTagNamecompares names as the DOM says; the local name of a qualified name is all after its first colon (f::oo), any doctype name without whitespace, NULL or>is valid, andcreateDocument's content type follows its namespace argument. Selectors take the namespace prefixes*|(any namespace) and|(none:|div), in type selectors and attribute selectors. WPTdom/nodes10518 -> 11073 passing.
Changes since v0.6.26
- flake.nix: the Linux release 0.6.26
- Remote channel: what the pipe server and the socket server share in server.odin
- Tools: the process's memory from base.process_memory
- One disk cache of engine output (wasm.Disk_Cache, disk_cache.odin) for the compiled-code cache and the JavaScript bytecode cache: the profile key, the signed files, the writer thread, the trim
- One escaper of text for HTML and XML (base.write_escaped, base.escaped: & < > always, what Escapes adds: the double quote, the single quote, U+00A0 as ) for the browser's pages (demos …
- Numbers as text: base.itoa for the browser's forms and search engines, CSP's ports, WebSocket's host keys, the Linux desktop's icons and remote/api's /proc paths
- Copies of strings and bytes by strings.clone and slice.clone
- One escape of a profile file's fields (base.field_escape, field_escaped, field_unescape: backslash, tab, line breaks and NUL) for history.txt, session.txt, the search engines and the service workers' …
- JS glue: the interfaces built on a prelude
- JS glue: one ascii_ieq (wj's; web_ascii_ieq calls it), one is_nan and is_finite (wj/libc.odin), one is_ws (reflect.odin) in place of their copies
- A hex digit's value from base.hex_value (a byte or a rune, -1 when not a digit) for the blocker's scriptlets and regular expressions, CSS's colours and escapes, the forms' patterns, the browser's …
- One writer of JSON strings (base.write_json_string, base.json_quote: invalid UTF-8 as U+FFFD, U+2028 and U+2029 escaped), moved out of remote's protocol for the remote channel and the CDP, the …
- Writes through a temporary file renamed over the target by base.file_write_atomic
- An origin's file name in the profile by base.write_file_stem
- Web tests: http_get (through fetch's loader), net_get (the network client alone) and enc in web_harness.odin, their copies in the images, media, netapi, wsclose, frames, jsload and sitecompat tests …
- e2e: enter gives the window back at the end of the caller's scope by itself
- Blocker: the HTML filters use procedural.odin's close_paren and glob_match and rewrite.odin's contains_word
- Tools: tooling.median (blockerpages, wasmbench, endurance), tooling.parse_digits (the lenient parse_int of adblockbench, jsengine, jspage, test262 and the netapi test's count_of) and …
- Adblock: what $removeparam, $csp and $replace share in request_matches
- Media: the events of MediaSource, the track lists and the cues are posted by one post_object_event
- Names as scripts give them: createElementNS and setAttributeNS keep their names' case
- WPT expectations: encoding's utf-32 tests read the XML frames as XML documents now
- URL: the URL Standard's domain to ASCII where Lexbor's UTS 46 differs
- Event loop: a timer set by a posted task waits for the next turn, behind what that task posted
- Forms: a planned navigation per target (Page_Forms.pending: Planned_Navigation, planned_for), a newer submission replacing only the one of its target: two forms submitted at once to two frames both …
- XMLHttpRequest and FileReader text: Encoding's decode
- Tests: FileReader's readAsText with a byte order mark and with a label of the replacement encoding
- Documents of their own: createTextNode, createComment, createDocumentFragment and createProcessingInstruction o...
Wren 0.6.26
Wren 0.6.26
Wren 0.6.26 runs pages that build large documents again much faster, and reads pages written in legacy encodings right.
This is still a beta, under development.
Highlights
- Pages that rebuild their DOM are much faster: elements alike share their selector matching,
documentandwindow
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.characterSetsays it, level-2 kanji decode, and documents made by
a script keep their own encoding and URL. getComputedStylegives the used insets (top,right,bottom,leftin pixels) of positioned elements:
what tooltip and popover libraries read.<dialog>:beforetoggleandtoggleevents,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).
Added
document.createProcessingInstructionmakes aProcessingInstruction(it was not a function in HTML documents; the XML parser already made them),InvalidCharacterErrorfor a target that is not an XML name or data holding?>.
Changed
- Scripts that build and drop many DOM nodes run faster again: the collection that frees dropped nodes unpins them in one call to the browser instead of one per node, finds their script objects in an array by node instead of a hash table, the string arguments of DOM calls are read where the engine keeps them instead of being copied (ASCII text), and the names of the known elements are no longer copied for each element. A page that rebuilds a list of 2 500 items every frame: its script 14.3 ms a frame instead of 18.9, 142 frames/s instead of 119; the collection of the dropped nodes 3.8 ms instead of 5.4. The numbers:
docs/performance.md. - Scripts that build and drop many DOM nodes run faster:
documentandwindowno longer ask for named properties nobody implements on every property read, a wrapper's interface is found by the element name's first byte, an insertion walks the inserted nodes once instead of three times, a node's script object takes one allocation instead of three, the engine's API reads a value without decoding it, and a DOM getter is called where it is found. A page that rebuilds a list of 2 500 items every frame: its script 17.6 ms a frame instead of 26.7, 122 frames/s instead of 93; the collection of the dropped nodes 5.3 ms instead of 8.1. The numbers:docs/performance.md. - Alike elements share their selector matching: siblings with the same tag and attributes, and their cousins, take what the first of them matched and match again only the selectors that tell them apart (their position, their state, their content, an ancestor's), so a full style pass of a page made of repeated items costs a quarter to nearly two fifths less (a page that rebuilds a list of 2 500 items every frame: 1.8 ms a pass instead of 3.0, and 92 frames/s instead of 86). A partial layout that would end up rebuilding too much finds it out before looking for what to rebuild (that page's layout wait: 3.3 ms instead of 4.7). The numbers:
docs/performance.md. - Text on several threads waits less: a word's shape is read under a shared lock, and the glyphs of a run take the atlas lock once (
font.glyph_images,font.slug_glyphs); the toolkit's helpers (the emoji font, wgpu-native) are fetched by its Odin tooltools/tk.
Fixed
<dialog>as the standard has it now: opening and closing a dialog firebeforetoggle(cancelable when it opens: a listener can keep it closed) and atoggleevent from a task, coalesced as a popover's;dialog.closedByreflects theclosedbyattribute (closerequestfor a modal dialog without one, elsenone) and Escape leaves a modal dialog withclosedby=noneopen;show()of a dialog open as a modal one throwsInvalidStateError;requestClose()does nothing for a dialog out of the document or from its owncancellistener; a modal dialog taken out of the document leaves the top layer (put back, it is laid out where it is, not centered).- Legacy encodings, the rest of the way: in a Shift_JIS page the IBM extensions (Roman numerals,
ⅰ,纊...) go into links and forms as the IBM bytes (0xFA..0xFC) the standard and every server expect, not as NEC's duplicates; in GBK and gb18030 the private use code points GB18030-2022 gave to real characters encode as their bytes;TextDecoderandcharsetknow the labels the standard added (unicode,ucs-2,unicodefffe,unicode20utf8...) and refuse a label padded with a vertical tab or a no-break space. A frame's document that declares no encoding takes its same-origin parent's (it was windows-1252), and a text or JSON document in a frame is decoded in its response's charset. WPTencoding: 99.2 % to 99.99 % of its subtests. - A method of a frame's node given too few arguments throws the frame's
TypeError(e instanceof frame.contentWindow.TypeError), as in other browsers; it was the caller's since each window has its own built-ins. - Documents of their own keep their nodes: an element made by a document of its own (
createHTMLDocument's,new Document(),createDocument's,DOMParser's), adopted (adoptNode) or imported (importNode) into one, or removed from one, has that document asownerDocument(it was the page's until inserted), and its links encode their query in UTF-8 whatever the page's encoding;createDocumentTypeof such a document'simplementationmakes a doctype of it.document.URLanddocumentURIofcreateHTMLDocument's,createDocument's,new Document()andparseHTMLUnsafe's documents areabout:blank, not the page's. WithcreateProcessingInstructionand the frames'TypeError, 284 more subtests of WPTdom/nodespass. getComputedStylegives the used insets of positioned elements:top,right,bottomandleftof an absolutely positioned, fixed, relative or sticky element are pixels (a percentage against its containing block, anautoside where the box is, or the opposite side negated for a relative one) instead of the declared10%orautothat position libraries (tooltips, popovers, sticky polyfills) could not use; a sticky element keepsauto. Numbers of computed values are the shortest that read back the same (20.7px, not20.700001px). WPTcss/cssom: 56 % to 78 %.- Pages in legacy encodings (Shift_JIS, EUC-KR, Big5, GBK, windows-1252...) link and submit in their encoding:
document.characterSetsays the encoding a page was decoded from (it always said UTF-8; a document of its own,createHTMLDocument's orDOMParser's, is UTF-8, and so is an XHTML page that declares none, as XML wants), the query of its links (a.href, links a script or the user follows, against the<base>too) is percent-encoded in it, and forms submit in it (or in the first encodingaccept-charsetnames): query, urlencoded, text/plain and multipart bodies,_charset_,&#N;for what the encoding cannot represent. A search form of a Shift_JIS site sent mojibake. Shift_JIS text with a lead byte between 0xE1 and 0xFB (the level 2 kanji, the IBM extensions) is no longer U+FFFD, in pages and inTextDecoder. WPTencoding: 2.4 % to 97.7 % of its subtests.
Changes since v0.6.25
- flake.nix: the Linux release 0.6.25
- Toolkit 0.64.0: its helpers fetched through tools/tk
- Style: matching shared between siblings and cousins
- Layout of DOM-heavy pages: an incremental attempt gives up before its search of what to rebuild when the boxes of the removed elements alone pass the limit
- Performance, round 5 measured (docs/performance.md: matching shared by alike elements, the counter walk only when a box shows a counter, the check of what was inserted on bits, the incremental …
- docs/performance.md: item 6 after toolkit 0.64.0
- Legacy encodings: a page keeps the encoding it was decoded from
- getComputedStyle: the used insets of positioned elements
- Script stage of DOM-heavy pages: Document and Window plain platform objects
- Performance, round 6 measured (docs/performance.md: the named class, the interface by name, the insertion walks, the wrapper's allocations, the C API's decoding, the getters
- test_all: judge capi is a step (the engine's C API natively, 48 checks: it ran only by hand)
- Toolkit 0.65.0 (its unused public procedures removed: none was Wren's)
- Script stage of DOM-heavy pages, round 7: a collection unpins the nodes of the wrappers it finalized in one call
- Performance, round 7 measured (docs/performance.md: the collection's unpins in one call, the arguments' strings read in place, element names as the program's strings, the wrappers by handle index
- Documents of their own: a node's node document
- Encodings, the rest of the way: Shift_JIS encodes the IBM extensions as the standard does
- : show() and showModal() past their checks fire a cancelable beforetoggle and queue toggle
- : a modal dialog taken out of the document leaves the top layer and is not modal when it comes back
- Docs site: node documents, encodings, the dialog's toggle events and closedBy, the WPT runner's testdriver, the conformance numbers
- Wren 0.6.26
Wren 0.6.25
Wren 0.6.25
Wren 0.6.25 opens the DOM of same-origin frames, lays out columns and vertical text, and sleeps when nothing changes.
This is still a beta, under development.
Highlights
- Same-origin frames:
contentDocument,contentWindow.document,parent.document; each frame keeps its own
built-ins (instanceofacross frames is false, as in other browsers); a frame's navigations fire theirnavigate
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 incrypto.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 %. - A browser at rest draws nothing: no frame and almost no wake on a page that changes nothing, and a caret that blinks
redraws only its own pixels. - Faster and safer closing: no wait on the shutdown watchdog, the graphics device let go before the end,
wren ctl stopimmediate on a throwaway profile. - The interface has its own zoom (Ctrl+wheel over the tabs or toolbar), and its scroll bars show only when useful.
- Crashes and hangs fixed: WebGL while the GPU process restarts, an iframe moved into its own document, AV1 on a
machine short of processors, large Web Audio graphs, a worker freeing a page'slocalStorage, WebSockets that stopped
after a large message. - The User-Agent carries the Chromium tokens before
Wren/<version>, so sites that check for them (Google Sign-In)
answer, and scripts get 30 seconds before they're stopped.
Added
- Windows that scripts open:
window.open()to_blankor to a new name, and a script's click on a link to one, open a tab whose window has an opener (window.opener), a name other windows find,postMessageboth ways,closed, andclose()by the script that opened it;noopenerandnoreferrer(features, orrel) cut the relationship andwindow.openthen returns null. ACross-Origin-Opener-Policythat does not match the opener's severs it as other browsers do: the popup's opener is null and the opener sees the popup closed. The WPT runner's pages open the same windows, without a shell. - Names resolved by the browser itself, and connections kept on this machine (built-in network stack):
network.hosts(name=addresspairs; a name stands for its subdomains too) answers those names without asking the system or a hosts file, andnetwork.loopback_only(orWREN_NETWORK_LOOPBACK_ONLY=1) makes any connection to an address that is not this machine's fail at once, named on the standard error. The tests run with both: web-platform-tests under its own names (web-platform.test,www1.web-platform.test...), and nothing tested reaches the Internet. wren ctl stacks <process id>prints the call stack of each thread of a running process, named with its PDB (--dumpadds a minidump).wren ctl quitprints them when the Wren does not end, and the shutdown watchdog leaves them besideshutdown.logbefore it ends a stuck browser.- WebGL in workers:
OffscreenCanvas.getContext("webgl")and("webgl2")in a worker,transferToImageBitmap, andcanvas.transferControlToOffscreen(), whose OffscreenCanvas goes to a worker in a message and whose frames (WebGL or 2D) reach the page's canvas. - A
flake.nix:nix runstarts the latest Linux release,nix developgives whatbuild.shneeds. - Reference tests in the WPT runner (
tools\wpt.ps1 -Reftests,css/css-multicolandcss/css-writing-modesfor now): the test and its references laid out headless, drawn by a software rasteriser over the display list (src/tools/wpt/reftest: clips, transforms, opacity groups, backgrounds, gradients, images, shadows, borders of every style, Ahem exactly, other fonts as deterministic patterns), compared under WPT's rules (<meta name=fuzzy>, chains of references,reftest-wait). They count apart from testharness tests, with expectations of their own (wpt/expectations/<folder>.reftest), and the three pictures of a failure can be dumped outside the repository (-ReftestDump). - Vertical writing modes:
writing-mode(vertical-rl,vertical-lr,sideways-rl,sideways-lr),text-orientation,text-combine-upright; flow-relative properties mapped per element for its writing mode and direction (margin-inline-startin right-to-left text too); orthogonal flows; flex, grid, tables and columns in vertical text; a vertical document, whose scroll goes left; anchor positioning follows writing modes. Demo:examples/site/vertical.html. - Multi-column layout:
columns,column-count,column-width,column-gap,column-rule,column-span: all,column-fill,break-before,break-after,break-inside,orphans,widows, with balancing, overflow columns and nested containers; boxes are cut across columns,getClientRectsanswers per fragment (and per line for inline boxes), hit testing, selection and painting work column by column. Demo:examples/site/multicol.html. - The document scrolls horizontally: a page wider than the viewport has a horizontal scroll bar at the bottom and moves with a horizontal wheel, Shift+wheel, the left and right arrows, a pan, autoscroll and a selection dragged past an edge, smoothly as vertically.
window.scrollX,scrollTo,scrollBy,scrollLeft,scrollWidthandscrollIntoViewwork on that axis, rectangles are relative to the viewport on it,position: stickysticks to its edges, and the position comes back with the history.overflow-x: hiddenorclipon the root forbids it; withdirection: rtlon the root the offsets go below 0, to reach what overflows to the left. - P-521 in
crypto.subtle: ECDSA and ECDH on the curve, with generate, sign, verify, deriveBits, deriveKey, import and export in raw, spki, pkcs8 and jwk, wrap and unwrap, off the page's thread as the other curves are. The arithmetic is RustCrypto'sp521crate (native/crypto). - WebGL 2 transform feedback and real queries:
beginTransformFeedback,transformFeedbackVaryings,getTransformFeedbackVarying,bindBufferBase/bindBufferRangeonTRANSFORM_FEEDBACK_BUFFER,pause/resume(a variant of the vertex stage that writes to storage; an adapter without that saysMAX_TRANSFORM_FEEDBACK_*are 0),ANY_SAMPLES_PASSEDandANY_SAMPLES_PASSED_CONSERVATIVEby occlusion queries,TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, with results on a later task only; a vertex attribute divisor above 1. AudioWorklet:audioContext.audioWorklet.addModule(),AudioWorkletGlobalScopewithregisterProcessor,AudioWorkletProcessorandAudioWorkletNodewithparameters,portandprocessorerror, in realtime and offline contexts. The processors run in a realm on a thread of their own; a quantum waits for it with a deadline and is silence on a miss, and a realm that throws, stops or hangs (webaudio.worklet_stall_ms) isprocessorerror, never a hung render thread.- The browser's interface has its own zoom, independent of pages: Ctrl+wheel with the pointer over the tabs or the toolbar, Ctrl+Plus, Minus and 0 while the interface has the keyboard, or from the command palette. It's kept in
browser.ui.zoom. - The DOM of a same-origin frame is reachable:
iframe.contentDocument,contentWindow.document,parent.document,top.documentandframeElementare real documents and elements, with listeners from both realms, inserted scripts running in the frame's realm,document.open/write/close, and blank frames that show what is written in them.frame.contentWindow.fooandparent.foo()work through a value bridge between realms (primitives copied, objects and functions as proxies). A cross-origin or sandboxed frame stays out of reach by every route, with the origin checked on each call (docs/frames.md). - An image opened by itself is a viewer: Ctrl+wheel (and a touchpad's pinch) zooms the image around the pointer, + and - zoom around the center and 0 fits it again; a click toggles fit and 100 %; the image is dragged with the pointer (grab, grabbing), moved with the wheel and the arrows when it is larger than the window; R and Shift+R (and the context menu) turn it a quarter; a double click resets the view. Ctrl+Plus / Minus / 0 still zoom the page.
Changed
- One test program. The web tests moved into
src/tools/e2e(web_<subject>.odin):build\web_test.exeandbuild\e2e_test_check.exeare gone,build\e2e_test.exeremains, with every allocation tracked: every scenario now ends with nothing left allocated, or fails. The GPU process's life is tested once, in the real browser, with nothing asked of the program: killed and suspended from outside, under a page, two contexts and a worker; the browser has none of the hooks that test had (they stay for the tests ofnative/webglonly). - The test tooling is in Odin:
src/tools/test_all(a typed table of steps, a small state machine, the result cache, the profiler, its own desktop; on Windows and Linux) instead oftools\test_all.ps1,test_cache.ps1,test_profile.ps1,tools/linux_ci.sh;src/tools/wptandsrc/tools/test262are the whole of their runs (the pinned checkout, the second chance,-update);src/tools/buildjsbuilds the JavaScript engine,src/tools/enduranceis t...
Wren 0.6.24
Wren 0.6.24
Wren 0.6.24 draws WebGL and positions with CSS anchors: Three.js and MapLibre run.
This is still a beta, under development.
Highlights
- 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.setno longer traps on large arrays.- Scroll containers count the end margins of their children as scrollable overflow, as other browsers do:
scrollHeightmay grow.
Added
- WebGL 1 and 2:
canvas.getContext("webgl")and("webgl2"), on a thread and a wgpu device per context (Vulkan or Direct3D 12 on Windows, Vulkan on Linux, the system's software rasterizer without a GPU), GLSL ES through a rewrite in front of naga. Three.js examples and MapLibre GL JS work. No transform feedback; seedocs/js/webgl.mdfor the limits, and thewebgl.*preferences. - CSS anchor positioning:
anchor-name,anchor-scope,position-anchor,anchor()andanchor-size(),position-area,anchor-center,position-try-fallbacks,position-try-order,@position-try,position-visibility, the implicit anchor of a popover. Tooltips follow an anchor that scrolls, flip when they no longer fit, and pages no longer load the@oddbird/css-anchor-positioningpolyfill (it ran past the script limit on chatgpt.com).
Changes since v0.6.23
- CSS anchor positioning: anchor(), anchor-size(), position-area, fallbacks, position-visibility
- anchor positioning: anchor-center in all of the rectangle, what a frame chose is what stays, used insets
- wpt: css/css-anchor-position, run with a layout
- A demo of CSS anchor positioning in the demos page
- anchor positioning: a clamped position-area box against the scrolled containing block
- CSS anchor positioning in the documentation, a changelog fragment
- The demos index lists the anchor positioning demo
- wpt: css/css-anchor-position recorded with time for a cold start
- Site docs regenerated with the anchor page
- WPT expectations after anchor positioning and Web Audio
- Site docs after the expectations
- WebGL's device side: a crate with a GLSL ES translator over naga and GL's state machine over wgpu
- canvas.getContext("webgl") and ("webgl2"): the interfaces in lazily evaluated JavaScript, a context on its canvas's backing
- JIT: a return from inside a compiled loop in a function called from JavaScript
- WebGL: what Three.js needs of GLSL ES and of the page
- WebGL: limits a hostile shader cannot pass, frames without a conversion, IDL lengths, WPT webgl, documentation
- TypedArray.prototype.set of a large array traps no more
- WebGL: the SDK's module compiles (a parameter named like an Odin keyword), the demos index is up to date
- jswebtest webgl: no slow-script limit for a page that waits for the software rasterizer on a loaded machine
- jswebtest webgl: the slow-script limit is two minutes, not none
- Site docs with WebGL
- linux_smoke checks WebGL; a paint timing test of WebGL may time out in the runner
- Release 0.6.24: WebGL and WebGL 2, CSS anchor positioning
Wren 0.6.23
Wren 0.6.23
Wren 0.6.23 plays Web Audio, and vscode.dev, tldraw and reddit work.
This is still a beta, under development.
Highlights
- Web Audio.
AudioContext,OfflineAudioContext, the usual nodes,decodeAudioDataandScriptProcessorNode, on Windows and Linux, following the autoplay policy. The web-platform-tests ofwebaudiogo from 24 % to 92 %.AudioWorkletis 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
getComputedStyleanswers for shorthands. - tldraw's icons show: an SVG image's fragment now makes
:targetmatch. - Regular expressions search faster: Octane's regexp from 127 to about 165.
- A same-origin frame gives
navigator,screenandcrypto, which scripts often take from a frame they just made.
Added
- Web Audio:
AudioContextandOfflineAudioContextwith their nodes (buffer sources, oscillators withPeriodicWave, gain, constant source, biquad and IIR filters, delay, stereo and equal-power panners, splitter, merger, compressor, wave shaper, analyser, convolver,ScriptProcessorNode,MediaElementAudioSourceNode),AudioParamwith all its automation, anddecodeAudioData(off the page's thread: MP4, WebM, WAV, MP3, AAC, FLAC, Ogg). The graph renders in the browser (src/webaudio) on a thread per running context;media.autoplay.policyholds a context until a click or a key press; the tab's mute and speaker follow it. NoAudioWorklet,MediaStreamnodes or HRTF yet (docs/js/web-audio.md). The web-platform-tests ofwebaudio/go from 24.4 % to about 90 %.
Changed
- Regular expressions search faster: a regexp that is not sticky no longer goes through a bytecode loop for each position of the subject, and skips at once the positions where its first character or range cannot match;
replacecopies the text between matches in one go. Octane's regexp from 127 to about 165 (measured with other work on the machine).
Fixed
- Long Animation Frames entries no longer carry garbage as the invoker's name, which made the page's PerformanceObserver throw
JSON.parse: bad escape(reddit.com): the name was kept by reference, and read after it was gone when an event had no listener. getComputedStyleanswers for shorthands (border-radius,border,border-*,outline,inset,gap,flex,flex-flow,text-decoration,background,font) andmask-imageinstead of an empty string, andmarginandpaddingin their shortest form: reddit.com's scripts threw on the NaN they made of it.- An element removed from a page with a shadow root and rules on form controls (
:valid,:checked...) no longer hangs the browser while it takes all the memory: a walk of the document went from a shadow root to its host and back for ever (reddit.com, 22 GB in five minutes). - A form submitted while the page loads (a login's automatic POST, reddit's check page) navigates: it reached the browser before the shell had attached itself to the page, and was dropped with
form submission ignored: no navigator. - The console explains an
InternalError: interruptedfrom a promise reaction or an async function (javascript.slow_script_ms), as it did for a script, and no longer reports an error for a worker terminated while it starts. - A same-origin frame's
navigator,screenandcryptono longer throw a SecurityError: the frame gives this realm's own, as it did forconsole(x.com's scripts read them from a blank frame). - An SVG image addressed with a fragment (
icons.svg#save, in<img>,background-imageormask-image) gets:targetin its style sheets: sprites that show one icon that way no longer come out empty (the icons of tldraw.com). - A
<meta>Content-Security-Policy applies to inline code and eval from where it stands: a script the parser ran before it is no longer refused. - A service worker answering a no-cors request with an opaque response (
respondWith(fetch(e.request)), a stylesheet or script of another origin, also from the Cache API) no longer gives the page an empty body: the browser fetches that address for the page, scripts in the worker still never see the bytes. Such a response from the Cache API stays opaque, and a request that is not no-cors gets a network error. A second visit to vscode.dev stayed blank. - Compiling a large bundle no longer compares every reference with every top-level name: three to four times quicker on the 21 MB of vscode.dev, the same bytecode.
- Large scripts compile on their background threads whatever
wasm.max_call_mssays: past that limit (1 s) the thread's engine died and the interface thread compiled that bundle and all later ones itself, inside the timejavascript.slow_script_msallows a task (vscode.dev stayed blank).
Changes since v0.6.22
- Regexp search outside the bytecode, skipping positions that cannot match
- jsrt: name the bare numbers that carry a decision
- jscapi: the default stack size is jsrt's
- frontend: name the bare numbers that carry a decision
- idb: name the bare numbers that carry a decision
- forms: name the bare numbers that carry a decision
- a11y: name the bare numbers that carry a decision
- dom: name the bare numbers that carry a decision
- httpcache: name the bare numbers that carry a decision
- js: name the bare numbers that carry a decision
- css: name the bare numbers that carry a decision
- url: name the width of a character range the pattern engine folds
- webfont: name the bare numbers that carry a decision
- native/js/odin: name the bare numbers that carry a decision
- browser: name the control and row heights that about:blocker and the search engines share
- desktop: name the waits of the tests
- wasm: name the waits of the tests
- media: name the waits of the tests
- host: name the waits of the tests
- layout: name the waits of the tests
- The last interrupt poll named, a stray CR in a comment
- page: no call deadline on the compile threads
- frontend: names looked up in a table when a function's lists are long
- service workers: an opaque response reaches the page with its body
- csp: a policy applies to inline code from where it stands
- images: an SVG image's fragment makes :target match
- frames: a same-origin frame gives navigator, screen and crypto
- js: the slow script message for microtasks, none for a worker terminated at start
- style: the search for grouped controls does not go from a shadow root back to its host
- forms: a submission made while the page loads waits for the embedder
- framestest: a same-origin frame's navigator is reachable now
- wrencom_e2e: the launcher's message may wrap
- getComputedStyle: shorthands and mask-image have values
- forms: the navigate event of a submission still fires at once
- timeline: a long animation frame's invoker is a copy
- getComputedStyle: an unset border radius is 0px
- src/webaudio: a render graph for the Web Audio API, with no JavaScript in it
- Web Audio for pages: the interfaces in JavaScript, the graph through api/webaudio.widl
- Tests of Web Audio: jswebtest pages, a demo to drive, the webaudio folder of web-platform-tests
- Web Audio in the documentation: a page, the limits in the README and the site, a changelog fragment
- Web Audio: what a Wasm module of a page could break, a lost device, what test_all found
- webaudio test: wait for the clock of a resumed context, on a busy machine
- Release 0.6.23: Web Audio; vscode.dev, tldraw and reddit work; regexp search faster
- test_all: the desktop unit tests one at a time and marked flaky
Wren 0.6.22
Wren 0.6.22
Wren 0.6.22 comes to Linux, runs WebAssembly threads and a real crypto.subtle, and stays far smoother on pages that change every frame.
This is still a beta, under development.
Highlights
- A Linux package.
wren-0.6.22-linux-x64.tar.gzfor 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
SharedArrayBufferunder 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
asyncfunction returned could be collected before its promise took it: anArrayBufferturned up empty, a rejection reasonundefined. - More of the web works:
blob:URLs for every load, link preload, the common SVG DOM,FontFace, realCSSRuleobjects, adocumentfor blank frames. Excalidraw and tldraw load.
Added
- A real
crypto.subtle:CryptoKey,importKey/exportKey(raw, jwk, spki, pkcs8),generateKey,encrypt/decrypt(AES-GCM, AES-CBC, AES-CTR, RSA-OAEP),sign/verify(HMAC, ECDSA, RSASSA-PKCS1-v1_5, RSA-PSS, Ed25519),deriveBits/deriveKey(PBKDF2, HKDF, ECDH, X25519),wrapKey/unwrapKey(with AES-KW): in windows and workers, heavy work off the page's thread; a key's material stays in the browser, and aCryptoKeygoes throughstructuredClone,postMessageand IndexedDB. amazon.com stopped atimportKey. No P-521, whichcore:cryptodoes not have. - A Linux package: each release now has
wren-<version>-linux-x64.tar.gz(x86-64, an early preview), built by./build.sh releaseandtools/package_linux.shand started end to end on the runner before it is published. The site offers it. The main thread gets its 32 MiB stack by itself on Linux, without aulimit. - A blank same-origin frame has a
document:iframe.contentDocumentof anabout:blankorsrcdocframe is an inert document of the page's own realm (nothing written there shows), and a same-origin frame givesevalandSecurityPolicyViolationEventexists. A frame with an address of its own stays out of reach: each document has its own engine instance. sheet.cssRulesgives real rule objects:CSSStyleRule,CSSMediaRule,CSSLayerBlockRuleand the others exist forinstanceof, withtype,selectorText,style,conditionText,nameand nestedcssRules(read-only views of the sheet).FontFace: a face loaded from a URL or from bytes and added todocument.fontsis in force (Excalidraw and tldraw stopped on its absence).<link rel=preload>andmodulepreloadfetch and fireloadorerror, which code loaders wait for (Squoosh's never finished without it).- The common SVG DOM:
createSVGMatrix,createSVGPoint,createSVGRect,getBBox,getCTM,getScreenCTM; an<svg>with only aviewBoxno longer asks for 300 px in a grid track or a shrink-to-fit box. - WebAssembly threads and SharedArrayBuffer: under cross-origin isolation (
Cross-Origin-Opener-Policy: same-originwithCross-Origin-Embedder-Policy) a page and its workers share memory, withAtomics.waitandAtomics.notifyworking with a module'smemory.atomic.waitandnotify; aWebAssembly.Moduleand a sharedWebAssembly.Memorycan be sent to a worker.docs/shared-memory.md. dialog.showModal()is modal: the dialog is above everything, what is outside it does not take the pointer or Tab, Escape asks it to close, the focus goes in and comes back;:modalmatches,returnValuecan be set, andcloseis fired from a task.- Cross-Origin-Resource-Policy and Cross-Origin-Embedder-Policy are enforced: a response to a request without CORS can refuse other origins, and a page with
require-corporcredentiallessloads only what agrees. - A page's WebAssembly modules are kept compiled in the profile's disk cache, by top-level site.
Changed
- Pages that change every frame run much faster when a program uses UI Automation. A page's accessibility tree is brought up to date ten times a second at most, and less often when the update is costly (
accessibility.page.interval_ms); a screen reader's request is still answered at once. Any program that once asked UI Automation (a screenshot tool, PowerToys) kept that tree alive. Measured: flex 10 000 resize from 6.9 to 28.6 frames a second, a page of 10 000 changing nodes from 1.9 to 8.7.:has()invalidation walks each ancestor once per change. media.audio.backendworks on Windows too:nulllets audio go by in silence at the pace of the clock. The environment variableWREN_AUDIO_OUTPUT=nulldoes the same whatever the preference, and every test script sets it: the tests no longer make a sound.CHANGELOG.mdfollows Keep a Changelog 1.1.0: versions are linkable (## [0.6.21] - 2026-10-30, with compare links at the end of the file, written when a version is published), a fragment can also beDeprecatedorSecurity, andtools\changelog.ps1 -Checkenforces the form of the file.
Fixed
- The value an async function returned or threw could be lost, depending on when memory was reclaimed:
awaitthen gave an empty buffer, another object, orundefined. It showed incrypto.subtle.exportKey, whose result could come back empty, and as an unhandled rejection with no reason where a script had thrown an error. importScriptsrefuses ablob:ordata:URL whose type is not JavaScript (a blob with no type still runs). AssigningselectorTexton a CSS rule no longer throws.:first-childand its family stop at the first sibling met instead of counting the whole family.pointercancelis measured by the event timing.- Found on real sites:
performance.measurewith empty options and an end mark, element names such as_xor:xincreateElement, a<track>outside a media element having atrack. - Streams:
for awaitover a stream follows Web IDL's iterator,tee()passes errors and closes on at once and answerscancelwhen both branches are cancelled, and it clones every kind of chunk. blob:URLs work everywhere: an image, a stylesheet, a frame or a media element can load whatURL.createObjectURLmade, not onlyfetchand XHR of its realm.- Structured clone keeps lone surrogates, a resizable
ArrayBuffer's maximum and a view's length tracking; a view out of bounds throws instead of reaching a worker asnull; anErrorwithout a message has none. waitUntilworks for as long as the event has promises pending, and install and activate wait for those added late.- A module that throws no longer fires
unhandledrejectionas well aserror, and an import attribute other thantypeis an error (a SyntaxError in a module, a rejectedimport()). new Document(),EventSourcein a worker (it crashed the browser), syntax errors with their file and line,CSS.escapeand IndexedDB names with lone surrogates, IndexedDB's interfaces as plain properties of the global,cssTextset to the same value queuing a mutation record, andCSSLayerBlockRuleandCSSLayerStatementRuleexisting.- The README, the site and the documentation catch up with the code: the JavaScript engine in Odin (no QuickJS-ng, no Zig), the built-in network stack, text and image decoders, the HTTP cache, the single-file download, the counts of the API and of the bindings, and a list of limits that no longer names what exists (Canvas 2D, video, iframes, service workers,
ResizeObserver, Web Animations). - The site's gallery: a click on a capture opens it large (arrow keys to go through them, Esc to close), each capture stays a plain link to its image without the script, and the page says which version the captures show (0.6.1, not 0.2.0).
Changes since v0.6.21
- WebAssembly threads and SharedArrayBuffer under cross-origin isolation
- blob: URLs for every load, link preload, the common SVG DOM
- FontFace, and what the big sites stopped on: measure with empty options, element names, a detached track
- CHANGELOG in the form of Keep a Changelog 1.1.0: linkable versions, six types, a check that enforces it
- Real CSSRule objects, a document for blank frames, SecurityPolicyViolationEvent
- A real crypto.subtle: CryptoKey and the other eleven operations
- Tests make no sound, a Linux package for the releases, flaky steps marked, toolkit 0.57.1
- Linux package: LLVM 18 on the ubuntu-22.04 runner
- Linux package: built on ubuntu-24.04, glibc 2.39 or later
- Linux package: the quick profile of the Rust library, fat LTO does not link
- Accessibility of pages at most ten updates a second, :has() invalidation walks an ancestor once
- page: name the bare numbers that carry a decision
- browser: name the bare numbers that carry a decision
- net: name the bare numbers that carry a decision
- fetch: name the bare numbers that carry a decision
- media: name the bare numbers that carry a decision
- wasm: name the page limit of script-made memories
- layout: name the bare numbers that carry a decision
- style: name the bare numbers that carry a decision
- page: name the deadlines of offthread_test
- images: name the codec probe wait and the waits of its test
- host: name the shutdown defaults and the watchdog tick
- remote: name the waits of the pipe se...
Wren 0.6.21
Wren 0.6.21
Wren 0.6.21 runs far more of the web's WebAssembly, and starts it faster: DuckDB, Python and older .NET and C++ builds.
This is still a beta, under development, and Windows-only.
Highlights
- Modules built with the old form of WebAssembly exceptions run.
try,catch,rethrowanddelegateare 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 answersrunPython. - 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 torespondWith, 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
pipeTotests.
Changes you may notice
- Without cross-origin isolation a page has no global
SharedArrayBuffer, as in other browsers. Scripts that use it when available no longer wait on a copy that nothing shares: SQLite Fiddle now says OPFS is unavailable.
Limits
- Still no WebAssembly threads: a module declaring a shared memory does not compile, so Emscripten builds with pthreads do not run. Details:
docs/js/webassembly-api.md.
Added
- WebAssembly modules built with the old form of exceptions run (
try,catch,rethrow,delegate: older Emscripten and .NET builds, DuckDB): they are translated at compilation.new WebAssembly.Memory({shared: true})works for one agent, so Pyodide (Python) starts.
Changed
- A page's WebAssembly is much faster to start and to reach: a script going through a module's memory one element at a time is 3 to 4 times faster (the engine keeps the pages it reads), 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).
- Fewer limits on a page's WebAssembly: a module can catch what JavaScript throws (
catch_all,WebAssembly.JSTag), a call can have any number of values, externrefs are released once the module has dropped them,Memory.toFixedLengthBuffer. Without cross-origin isolation a page has noSharedArrayBuffer, as in other browsers: scripts that use it when available no longer wait on a copy that nothing shares (SQLite's OPFS helper). The toolkit moves to 0.56 (timelines and screencast for its development tools; nothing changes in Wren itself).
Fixed
- A service worker can call
event.waitUntil()from the promise it gaverespondWith, as the specification allows: Wren refused, so the request failed (bitplatform.dev's Blazor application could not start). - A stream's reader released while reading no longer reports an unhandled rejection ("Releasing Default reader"), which stopped a worker in DuckDB's shell and failed
pipeTotests: a promise marked as handled after its rejection now tells the rejection tracker.
Changes since v0.6.20
- WebAssembly limits: JavaScript's exceptions can be caught by a module, any number of values in a call, externrefs released, no SharedArrayBuffer without isolation
- WebAssembly: the old form of exceptions translated at compilation
- test262's image keeps the global SharedArrayBuffer: it measures ECMAScript
- Release 0.6.21: the memory of a module kept by pages in the engine, instances with thousands of exports, waitUntil from a pending respondWith
Wren 0.6.20
Wren 0.6.20
Wren 0.6.20 runs a page's own WebAssembly: Blazor, Rust and Emscripten applications now start.
This is still a beta, under development, and Windows-only.
Highlights
- The WebAssembly JavaScript API.
WebAssembly.compile,instantiate, the streaming forms,Module,Instance,Memory,Table,Global,TagandException, 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-testswasm/jsapi, 82 % of the subtests pass. - A large module does not stop the page.
WebAssembly.compileworks 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
WebAssemblypays nothing: its objects are made on first use. The page's Content Security Policy applies ('wasm-unsafe-eval').
Limits
- A script reading a module's memory element by element is slow (186 ns per read, against 15 ns for an ordinary array): the memory is reached through the browser. The module itself runs at Wasmtime's speed, and bulk operations (
set,slice,TextDecoder) are one call. - No shared memory, so no WebAssembly threads. Modules using the old form of exceptions (older Emscripten and .NET builds) do not compile; a Blazor Web App in Auto mode then stays on its server circuit.
javascript.webassembly.enabledturns the API off. Details:docs/js/webassembly-api.md.
Added
- The WebAssembly JavaScript API, on by default: a page's own modules run on Wasmtime beside the engine's.
WebAssembly.validate,compile,instantiate, the streaming forms,Module,Instance,Memory,Table,Global,Tag,Exceptionand the errors, in windows and workers.compileworks on another thread, so a large module does not stop the page. Blazor WebAssembly (.NET 10), wasm-bindgen and Emscripten applications run; the page's Content Security Policy applies ('wasm-unsafe-eval'). No shared memory, so no threads. A page that does not touchWebAssemblypays nothing.javascript.webassembly.enabledturns it off. Details and limits:docs/js/webassembly-api.md.
Changes since v0.6.19
- WebAssembly JS API, step 1: javascript.webassembly.enabled
- bindgen outputs of the webassembly module
- test262 and jsengine shells: the webassembly.WebAssembly_enabled import
- WebAssembly JS API, step 1: compile, Module
- Documentation site: the WebAssembly API pages
- WebAssembly.compile compiles on a thread of the blocking pool: the page goes on meanwhile
- WebAssembly JS API: Memory, Table, Global, Instance, instantiate, streaming: one store per realm, memory.buffer an ArrayBuffer whose bytes are reached through the browser
- WebAssembly JS API: Tag, Exception (both ways through calls)
- Release 0.6.20: the WebAssembly JavaScript API, on by default