Hello friends! In October, we moved style and layout off the main thread, built a JavaScript JIT, replaced our WebAssembly engine, redesigned the browser UI, and passed Safari Technology Preview on Web Platform Tests. :^)
Ladybird is entirely funded by companies and individuals who believe in the open web. You can see everyone supporting the project on our sponsors page, and we’re grateful to all of them. If you’re interested in sponsoring, please contact us.
Browsers normally run JavaScript, style, and layout on one main thread, one at a time. While a script runs, the page can’t update. A few simple animations can keep going on the compositor, but everything else has to wait.
We moved style and layout to their own thread, and paint recording to another (#12377, #12400). They now run at the same time as JavaScript (#12396, #12432, #12460). If a script asks for layout information, like an element’s size, it waits until pending layout work is finished, so it never gets stale data.

So two things now work during a long script:
width and font-size (#12433, #12447, #12451). :hover keeps working while JavaScript is busy. The render thread finds the element under the mouse, applies its hover styles, starts any transitions, updates the cursor, and draws a new frame. When the script ends, the main thread catches up and fires the hover events (#12530). As far as we know, no other browser can do this today. Here’s a text animation while JavaScript blocks the main thread in 1.5 second bursts, with Ladybird in the top left:
And here’s :hover on a page running JavaScript for 250ms at a time, with Ladybird on the left and Chrome on the right:
You can also try it yourself. The button blocks the main thread for three seconds. Most browsers keep the square spinning, but freeze the bar and the hover target.
On Linux and macOS, LibJS now compiles hot JavaScript to machine code, on both x86-64 and AArch64 (#12613).
We decided against a JIT in the past, and did what we could to push and tune the interpreter instead. It’s already competitive, so making it twice as fast would be a huge effort, and that would only turn a 300ms hang into a 150ms hang, which you’d still notice. So we needed an optimizing JIT.
The design is deliberately boring: speculative optimization based on runtime type profiling, like other JavaScript engines. For now, we just want to go reasonably fast.

While code warms up, we record things like which types and object shapes it sees (#12588). The compiled code uses those recordings to make guesses, like “this value is always an integer”, and inlines small functions (#12595, #12598).
To find bugs during development, we used a fuzzer that ran the same code in the JIT and the interpreter and compared the results (#12601).
We replaced our C++ Wasm engine and Cranelift with a new engine that has its own interpreter and JIT (#12963). It supports newer parts of WebAssembly that the old engine didn’t, like exception handling and GC. On the JetStream 3 Wasm workloads both engines can run, it scores about 7x higher.

In July, we built flap, a small language and compiler for writing LibJS’s interpreter. The Wasm interpreter is written in flap too, so one source builds it for x86-64, AArch64, and Windows x64. Hot functions move from there to the JIT, even in the middle of a running loop.
We’re making great progress on performance. JSLinux, an x86 PC emulator, now boots Alpine Linux to a shell in 1.8 seconds, while Chrome takes 1.5.

On October 26, we passed Safari Technology Preview on WPT. :^) We ended the month at 2,184,722 subtests, a gain of 75,650.

Most of that gain came in five days, from about 2.12 million on October 22 to about 2.18 million on October 27. We pointed AI coding agents at WPT and asked them to fill in missing functionality. They made lots of small fixes, and those added up. Our biggest gains for the month were about 30,000 in /css and 10,000 in /editing.

Until now, our UI had grown organically, with tinkering here and there. This was the first time we sat down and mapped out a complete design. We kept it modest and quiet, so the attention stays on the page.
It covers tabs, toolbars, menus, and our own about: pages on macOS and Linux, in light, dark, and a violet dark theme for private windows (#13271, #13169, #12815).

We rendered this page in Ladybird, using features that landed in October:

From the top left, that’s multi-column (#13026), display: grid-lanes for masonry (#13236), MathML (#13062, #13141), corner-shape (#12942), CSS Motion Path (#13027), much more of anchor positioning (#13021, #13079, #13166), and text-wrap: balance (#13461).
We also added:
calc-size(), which lets heights animate to and from auto (#13006), plus vertical writing modes (#13103), zoom (#13237), the stretch sizing keyword (#13006), shape() and border-shape (#12942), field-sizing (#13050), view progress timelines and animation-range (#12943), and the start of SVG animation (#12823, #13498). Cross-Origin-Opener-Policy (#12296), Paint Timing, Event Timing, Largest Contentful Paint (#13110), WebVTT cues (#12895), wide-gamut canvases (#13097), WebDriver BiDi (#13167), JavaScript’s using and await using (#13102), and many execCommand and contenteditable fixes (#13186, #13038). Web pages run in sandboxed renderer processes, so a malicious page that takes over its renderer should be stuck there.
We kept fixing security issues from many sources, including ways around that sandbox. For example, a compromised renderer could ask the browser process to open any file on your computer. Now only a renderer showing local files can read them (#12582, #12590). Renderers could also fake a user click to open popups or read the clipboard. The browser process no longer trusts them on this (#12597).
We also split the browser into more processes, so a compromised one can reach less:

On Windows, video now plays and YouTube works (#12646, #13195). Hardware video decoding moved to its own process and now works on Linux through Vulkan (#12868). We also read MP4 files ourselves now instead of using FFmpeg, which fixes problems with seeking and buffering (#12641).
LibJS’s runtime moved from C++ to Rust just before the JIT landed, so our JavaScript engine is now all Rust, apart from its flap interpreter and the garbage collector (#12492). The new Wasm engine is Rust as well. WebGL, 2D canvas, IndexedDB, Streams, and URL also moved over (#12699, #12935, #13287, #13083, #12998). We also deleted the C++ style computation, so style is now computed entirely in Rust (#12346).
Our first alpha is planned for later this year. This month we added:
.deb and .rpm packages, and Flatpak bundles (#13176, #13313, #13340). That’s it for October. Thanks for reading, and we’ll see you next month!