01 Jun 2026

feedPlanet Mozilla

Andreas Farre: Session History Diagrams in Firefox DevTools

I've spent a lot of time at Mozilla working on session history, the machinery that keeps track of where you've been so the back and forward buttons do something sensible. It's one of those parts of the browser that sounds simple from the outside and turns out to be anything but. Once you add iframes, nested iframes, and the subtle rules about when a navigation creates a new entry versus replacing the current one, the state you're reasoning about gets large and hard to hold in your head.

For years my main tool for understanding that state was reading code and printing things to a log. That works, but it's slow, and it never quite shows you the shape of the thing. So I built a way to see it: a new DevTools panel in Firefox Nightly called Session History Diagrams.

Enabling it

The panel is available in Firefox today, behind a pref. It's been there in some form since Firefox 150, growing more stable with each release. To turn it on, set devtools.application.sessionHistory.enabled to true in about:config, then reload DevTools. The new panel lives under the Application tab, next to Service Workers and Manifest, and it draws the browser's session history as a diagram that updates as you navigate.

Since Firefox 153 it also works over remote debugging. Connect to a device from about:debugging and you can watch the session history of a page running on Android, the same as you would on the desktop.

Jake diagrams

I didn't invent the idea of drawing this. The HTML spec already has a notation for it, called a Jake diagram after Jake Archibald, and that's where I started. It's a tabular notation where columns represent steps in session history, and rows represent navigables (the top-level browsing context plus any iframes). Background colors identify documents, a fresh color marking a new document loaded in that navigable, and the current step is shown in bold. It's a genuinely useful way to capture multi-navigable interactions that are otherwise hard to describe in prose.

A Jake diagram showing a top-level navigable and two iframes across five history steps

These diagrams don't have to be drawn by hand. Domenic Denicola, one of the HTML spec editors, built a Jake diagram generator that turns a description of a navigation sequence into a rendered diagram. That's where I first started playing with a more dynamic approach to the visualization. The thing I missed the most was being able to build a history up step by step rather than describe a finished sequence all at once. So I wrote rejake, a small tool that draws diagrams in the same style1, but lets you construct the history one step at a time.

But rejake, like the spec's diagrams and Domenic's generator before it, was stuck with a limitation the spec itself admits to, that they only work with a single level of nesting. That was exactly my problem. Real pages nest iframes inside iframes, and the bugs I was chasing usually lived down in that deeper nesting, precisely where the diagram stops being able to help. And however I drew them, I was still typing the history out by hand. It's a short step from there to wanting the diagram to draw itself from the browser's actual session history instead2.

Firefox Session History Diagrams

So the panel extends Jake diagrams to handle arbitrary nesting. Every column is a step in the session history. Every row is a frame, listed in pre-order from the frame tree: top-level document first, then its first iframe, then that iframe's children, and so on. The current entry is highlighted in blue, and the diagram updates live as you navigate.

The recording above is an ordinary bit of browsing, a handful of pages visited one after another. The top row tracks the page you're actually looking at, and the current position is the one in blue. The interesting part is everything underneath that top row.

Some of those pages didn't just load a single document. They pulled in nested frames of their own, and the diagram stacks those below the page that owns them. None of that is visible in the address bar or anywhere in the page chrome, and the frames come and go as you move through the history. Normally you'd have no way of knowing they were ever there. Here you can read straight off the diagram which frames a given step carried, when each one entered, and when it dropped away again.

Who else might want this

I built this for myself, working on Gecko's session history internals, where being able to watch the diagram change while reproducing a bug turns opaque state into something I can point at. But it turns out I'm not the only one who hits this wall. Plenty of people working elsewhere in Gecko, anywhere near navigation, end up reasoning about the same state, and now we all share one picture of it.

If you build single-page applications, or work with the History API or Navigation API, you've probably run into the same kind of confusion from the other side. A push where you expected a replace, a missing history entry, an iframe that accumulated entries unexpectedly. These are hard to reason about without seeing the state directly, and that's exactly what the diagram gives you.

Session history isn't a Firefox-specific problem either. Every engine implements the same part of the HTML spec, and Jake diagrams come from that shared spec. The panel only ever shows Firefox's state, but the rules are the same everywhere, so if you work on another engine it can still be a useful reference for how one implementation behaves. It's often the only practical way to surface an interoperability difference, which might be a bug in any of the engines, but stays hidden until you can actually see it.

Thanks

A big thanks to Nicolas Chevobbe, whose assistance was invaluable in getting the DevTools integration right. The work, including what's still to come, is tracked in Bug 2015726. There's a fair bit still on that list, like marking whether a step was a push or a replace, surfacing back/forward cache state, tying the diagram into the Network and Inspector panels, and more, all heading toward fuller DevTools support for Navigation and Session History.

Notes
  1. Which, naturally, meant re-implementing the whole of Session History along the way.

  2. Getting nerd-sniped by Jan Jaeschke definitely contributed as well.

01 Jun 2026 12:00am GMT

31 May 2026

feedPlanet Mozilla

Olivier Mehani: Optional Docker services and dependencies

Like many, docker and and compose have become my go-to tool to create software that can be conveniently deployed to production with a limited amount of headache. However, many tasks, and sometimes whole services, pertain only to the development side of the workflow, and need to stay there.

Moreover, some tasks, such as time-consuming provisioning tasks, are only on-demand one-offs. They shouldn't run at all most of the time, but they should slot into the dependency graph correctly when needed.

tl;dr: I realised that docker compose supports profiles, which allows services to be enabled conditionally, along with the depends_on.[].required option, to ignore them when they are disabled. Profiles are also useful to package actions and triggers to run on demand, so they are not started by default.

We can start with a simple setup where our long-running main service depends on an init service to perform preliminary steps. This can be setup with depends_on the compose.yaml.

services:
  main:
    image: debian:latest
    command: "sh -c 'while : ; do echo main; sleep 10; done"
    depends_on:
      init:
        condition: service_completed_successfully

  init:
    image: debian:latest
    command: sh -c 'echo init; sleep 10'

Even when run ning the main container, we get the right dependency (and delay). So far so good (though up will show the output from all containers.

Screenshot of a terminal. ``` [21:25:38] ~/docker-profiles$ docker compose run main 5s [+] 2/2t 2/22 ✔ Network docker-profiles_default Created 0.1s ✔ Container docker-profiles-init-1 Started 0.3s Container docker-profiles-init-1 Waiting Container docker-profiles-init-1 Exited Container docker-profiles-main-run-17209b1867a1 Creating Container docker-profiles-main-run-17209b1867a1 Created main main main ^C [21:26:50] ~/docker-profiles$ docker compose down 33s 130 ↵ [+] down 2/2 ✔ Container docker-profiles-init-1 Removed 0.0s ✔ Network docker-profiles_default Removed 0.1s [21:26:58] ~/docker-profiles$ docker compose up 1s WARN[0000] Found orphan containers ([docker-profiles-main-run-17209b1867a1]) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up. [+] up 3/3 ✔ Network docker-profiles_default Created 0.1s ✔ Container docker-profiles-init-1 Created 0.1s ✔ Container docker-profiles-main-1 Created 0.0s Attaching to init-1, main-1 init-1 | init Container docker-profiles-init-1 Waiting init-1 exited with code 0 Container docker-profiles-init-1 Exited main-1 | main main-1 | main Gracefully Stopping... press Ctrl+C again to force Container docker-profiles-main-1 Stopping main-1 | main Container docker-profiles-main-1 Stopped Container docker-profiles-init-1 Stopping Container docker-profiles-init-1 Stopped main-1 exited with code 137 [21:28:26] ~/docker-profiles$ ```

But what if we have another, much more time consuming, initialisation step?

services:
   [...]
   opt-init:
    image: debian:latest
    command: sh -c 'echo opt-init; sleep 100'

Perhaps we are lucky, and while it needs to run once, we don't need it to run everytime (think: database setup).

Docker compose can use profiles to select when services are started. It will then only be started when this profile is selected. Services without explicit profile will always be started, but any service with one or more profile listed will only get started iff that profile is selected.

We can make the opt-init service part of the opt profile. We can also make the main service dependent on it, so it is started beforehand.

services:
  main:
    [...]
    depends_on:
      [...]
      opt-init:
        condition: service_completed_successfully

  opt-init:
    [...]
    profiles:
      - opt

This works well enough when the opt profile is specified but… Oh no! If the profile is not specified, the dependency on the opt-init isn't resolvable, and none of the stack can spin up with just docker compose up

Screenshot of a terminal. ``` [21:36:50] ~/docker-profiles$ docker compose --profile opt up 130 ↵ [+] up 4/4 ✔ Network docker-profiles_default Created 0.1s ✔ Container docker-profiles-opt-init-1 Created 0.1s ✔ Container docker-profiles-init-1 Created 0.1s ✔ Container docker-profiles-main-1 Created 0.0s Attaching to init-1, main-1, opt-init-1 opt-init-1 | opt-init init-1 | init Container docker-profiles-opt-init-1 Waiting Container docker-profiles-init-1 Waiting Container docker-profiles-opt-init-1 Exited opt-init-1 exited with code 0 init-1 exited with code 0 Container docker-profiles-init-1 Exited main-1 | main main-1 exited with code 0 [21:36:55] ~/docker-profiles$ docker compose up 2s service "main" depends on undefined service "opt-init": invalid compose project ```

Fortunately, this is easily solved with the required attribute of the depends_on objects.

services:
  main:
    [...]
      opt-init:
        condition: service_completed_successfully
        required: false

And that's really all there is to it: with the right profile, the optional dependency is started in the desired order, but its absence is otherwise transparently ignored. Both docker compose up and docker compose --profile opt work as desired.

Screenshot of a terminal. ``` [21:40:31] ~/docker-profiles$ docker compose up 4s Attaching to init-1, main-1 init-1 | init Container docker-profiles-init-1 Waiting init-1 exited with code 0 Container docker-profiles-init-1 Exited main-1 | main main-1 exited with code 0 [21:40:35] ~/docker-profiles$ docker compose --profile opt up 3s Attaching to init-1, main-1, opt-init-1 opt-init-1 | opt-init init-1 | init Container docker-profiles-init-1 Waiting Container docker-profiles-opt-init-1 Waiting Container docker-profiles-opt-init-1 Exited opt-init-1 exited with code 0 init-1 exited with code 0 Container docker-profiles-init-1 Exited main-1 | main main-1 exited with code 0 ```

Profiles afford us another useful trick: on-demand tasks not started by default. This can be handy for maintenance tasks (data cleanup, garbage collection, …) or test scripts (running test workload, sending message, …). Those are handy during development, but would not be necessary, or take a different form, in other deployments.

services:
  [...]
  say-hello:
    image: debian:latest
    profiles:
      - hello
    command: echo hello
    depends_on:
      main:
        condition: service_started

Conveniently, when explicitly running a service, it is not necessary to request a matching profile, keeping the command line lean: docker compose run say-hello.

Screenshot of a terminal. ``` [21:47:53] ~/docker-profiles$ docker compose --profile opt down --remove-orphans 130 ↵ [+] down 5/5 ✔ Container docker-profiles-main-1 Removed 10.2s ✔ Container docker-profiles-init-1 Removed 0.0s ✔ Container docker-profiles-opt-init-1 Removed 0.0s ✔ Container docker-profiles-say-hello-run-c26e752b1edd Removed 0.0s ✔ Network docker-profiles_default Removed 0.1s [21:48:05] ~/docker-profiles$ docker compose run say-hello 11s [+] 3/3t 3/33 ✔ Network docker-profiles_default Created 0.1s ✔ Container docker-profiles-init-1 Exited 1.9s ✔ Container docker-profiles-main-1 Started 2.1s Container docker-profiles-say-hello-run-76efc04798b4 Creating Container docker-profiles-say-hello-run-76efc04798b4 Created hello ```

So here we are. Compose profiles allow us to control which services get started, and mark some as conditional. This, coupled with the ability to mark some depends_on rules as not required is a good way to seamlessly prevent heavy or otherwise time consuming services from starting when not needed, while retaining proper dependency ordering when enabled.

For completeness, the full, final, compose.yaml looks as follow.

services:
  main:
    image: debian:latest
    command: "sh -c 'while : ; do echo main; sleep 10; done'"
    depends_on:
      init:
        condition: service_completed_successfully
      opt-init:
        condition: service_completed_successfully
        required: false

  init:
    image: debian:latest
    command: sh -c 'echo init; sleep 10'

  opt-init:
    image: debian:latest
    profiles:
      - opt
    command: sh -c 'echo opt-init; sleep 100'

  say-hello:
    image: debian:latest
    profiles:
      - hello
    command: echo hello
    depends_on:
      main:
        condition: service_started


The post Optional Docker services and dependencies first appeared on Narf.

31 May 2026 11:58am GMT

The Servo Blog: April in Servo: new Android UI, focus, forms, security fixes, and more!

Servo 0.2.0 contains all of the changes we landed in April, which came out to yet another record 534 commits (March: 530). For security fixes, see § Security.

servoshell 0.2.0 showing several new features: better wrapping for CJK scripts, ‘tab-size’, better file pickers and `<textarea>`, `<select multiple>`, ‘::details-content::before’ and ‘::details-content::after’, and ‘color-mix()’ with any number of colors

We've shipped several new web platform features:

Plus a bunch of new DOM APIs:

Servo's support for text in Chinese, Japanese, and Korean languages has improved, with correct wrapping in the layout engine (@SharanRP, #43744), and CJK fonts now enabled in servoshell's browser UI on Windows, Linux, and FreeBSD (@yezhizhen, @CynthiaOketch, @nortti0, #44055, #44138, #44514).

Navigating to a JSON file as the top-level document now renders the JSON with an interactive pretty-printer (@webbeef, @TimvdLippe, #43702).

April was a big milestone for Servo, with some automated tests failing because they had hard-coded cookie expiry dates set to April 2016 plus ten years. Surprise! We're still here. Here's to the next 100 years of Servo (@jdm, #44341).

This is another big update, so here's an outline:

Security

Crypto­Key now zeroes buffers containing key material after use (@kkoyung, #44597).

With only a few exceptions, you can only access DOM APIs in another document if that document is in the same origin. But if that document is in the same site with a different port number, Servo currently allows these accesses even though it shouldn't. We've fixed some (but not all) of these incorrect accesses, specifically those that involve binding a Window or Location method in this document with a this from the other document (@yvt, @jdm, #28583).

We've fixed a bug where local­Storage and session­Storage were usable in sandboxed <iframe> and shared with every other sandboxed <iframe>, rather than throwing Security­Error (@Taym95, #44002).

We've fixed a bug where local­Storage and session­Storage were shared between all <iframe srcdoc> documents, rather than isolated using the origin of the containing document (@niyabits, #43988, #44038).

We've fixed a bug where IndexedDB was usable in sandboxed <iframe> and data: URL web workers (@Taym95, #44088).

We've fixed a bug where pages in some IP address origins can evict cookies from other IP address origins (@officialasishkumar, #44152). Only evicting cookies was possible, not reading or writing them.

We've fixed an out-of-bounds memory read in tex­Image3D() on Web­GL2­Rendering­Context (@simartin, #44270), and fixed some undefined behaviour in servoshell's signal handler (@Narfinger, #43891).

Work in progress

IndexedDB is now enabled in servoshell's experimental mode (@arihant2math, #44245). As always, embedders can enable it with Preferences::dom­_indexeddb­_enabled (@arihant2math, #44245, #44283).

IndexedDB now uses Servo's new "client storage" system, which is based on the Storage Standard and will allow us to have a unified on-disk format and quota management for all web platform features that persistently store data (@gterzian, #44374, #43900). We've also made key range queries more efficient (@arihant2math, #39009), landed improvements to IDB­Database, IDB­Object­Store, IDB­Cursor, IDB­Key­Range, IDB­Request, and to the handling of transactions, keys, values, and exceptions (@Taym95, #44128, #43901, #44009, #43914, #44161, #44183, #44059, #44215, #42998, #43805).

We've made more progress on the Intersection­Observer API, under --pref dom­_intersection­_observer­_enabled (@stevennovaryo, @jdm, #42204).

We're continuing to implement document.exec­Command() for rich text editing (@TimvdLippe, #44529), under --pref dom­_exec­_command­_enabled. This release adds support for the 'bold', 'font­Name', 'font­Size', 'italic', 'strikethrough', and 'underline' commands (@TimvdLippe, @jdm, @mrobinson, #44511, #43287, #44432, #44410, #44194, #44030, #44039, #44041, #44075, #44234, #44250, #44331, #44390, #44137, #44293, #44312, #44347).

All of the features above are enabled in servoshell's experimental mode.

Servo can now build a very basic accessibility tree for web contents, under --pref accessibility­_enabled (@alice, @delan, @lukewarlow, #42338, #43558, #44437, #44438). This includes text runs, plus nine other non-interactive accessibility roles (@alice, @delan, #44255). We've also fixed a crash when reloading pages with accessibility enabled (@alice, #44473), and made accessibility tree updates more efficient (@alice, #44208).

We've started implementing the Sanitizer API, under --pref dom­_sanitizer­_enabled (@kkoyung, #44198, #44290, #44335, #44421, #44452, #44481, #44585, #44594).

We've also started implementing Shared­Worker, under --pref dom­_sharedworker­_enabled (@Taym95, #44375, #44440).

We're working on the Wake­Lock API too, under --pref dom­_wakelock­_enabled (@TG199, @rovertrack, #43617, #44343).

servoshell

servoshell for Android now has a revamped browser UI, including a new history view (@espy, #43795), the apk is 30% smaller (@jschwe, #44278, #44182), and we've fixed the black screen bug when closing settings or switching back from another app (@yezhizhen, #44327). You can now close tabs on OpenHarmony too (@Narfinger, #42713).

servoshell 0.2.0 showing the revamped browser UI on Android. from left to right: viewing a web page, the settings view, the history view

As for servoshell on desktop platforms, we've fixed some focus- and IME-related bugs (@mrobinson, #43872, #43932), and on Windows, we now install a normal shortcut without the strange behaviour of an "advertised" shortcut (@yezhizhen, #44223).

For developers

When using the Inspector tab in the Firefox DevTools, the Rules panel now includes declarations in '@layer' rules (@arabson99, #43912).

When logging expressions in the Console tab, and when hovering over symbols in the Debugger tab, you can now get more information about the contents of functions, arrays, objects, and other values (@atbrakhi, @eerii, #44172, #44173, #44022, #44233, #44196, #44181, #44064, #44023, #44164, #44369, #44262).

When using the Debugger tab, you can now use the Scopes panel to inspect local and global variables (@eerii, @atbrakhi, #43792, #43791), you can now debug web worker scripts (@atbrakhi, #43981), and we've started implementing blackboxing, aka the Ignore source button (@freyacodes, #44142).

We've also landed some initial support for the Style Editor tab (@rovertrack, #44517, #44462).

We're working towards re-enabling our automated DevTools tests in CI, which should make the feature more reliable (@freyacodes, #44577), and we've landed a small build reproducibility fix too (@jschwe, #44459).

For developers of Servo itself, please note that the Cargo 'release' profile is no longer #[cfg(debug­_assertions)] (@jschwe, @mrobinson, #44177). If you've been using 'release' as a "faster 'debug' with assertions" build locally, consider switching to 'checked-release' or 'medium'.

The pull request template has been updated (@mrobinson, #44135). 'Testing' and 'Fixes' should go at the bottom of the PR description, and 'Testing' is about automated tests, not how you tested the PR locally.

We've made more progress on the new dev container, which will provide an alternative to our usual procedures for setting up a Servo build environment (@jschwe, @sagudev, #44126, #44111, #44162, #44641, #44109). Keep an eye out for that in the book!

In the meantime, did you know that you can use Lix or Nix to build Servo on Linux with a lot less hassle, even if you're not using NixOS? For now at least, head to the NixOS page in the book to learn more. We've also fixed a regression that made --debug-mozjs and MOZJS­_FROM­_SOURCE builds take much longer to complete on Linux when not using Nix (@jschwe, #44346).

We've fixed building Servo with the 'jitspew' feature in mozjs, allowing you to set IONFLAGS to enable JIT logging (@simonwuelker, #44010). We've also fixed build issues on Windows and FreeBSD (@zhangxichang, @mrobinson, #44264, #44591).

Embedding API

With this second monthly release of the Servo library, we have some quick notes about API stability and semver compatibility:

We've fixed a build failure affecting embedders with a new or updated Cargo.lock (@jschwe, #44093), and landed several other changes to help us with the Servo library release process (@jschwe, @mukilan, #43972, #44642, #43182, #43866, #44086, #43797).

Breaking changes:

You can now load a URL with custom request headers by calling Web­View::load­_request (@Narfinger, @longvatrong111, @mrobinson, #43338).

You can now retrieve cookies asynchronously by calling Site­Data­Manager::cookies­_for­_url­_async (@longvatrong111, #43794).

The synchronous version of that method, Site­Data­Manager::cookies­_for­_url, was previously not callable because Cookie­Source was not exposed to the public API, but we've fixed that now (@TG199, #44124).

You can now clear session cookies without clearing permanent cookies by calling Site­Data­Manager::clear­_session­_cookies (@longvatrong111, #44166).

When intercepting requests with Servo­Delegate:: and Web­View­Delegate::load­_web­_resource, we now include a destination and referrer­_url in the Web­Resource­Request, which can be helpful if you're implementing ad blocking (@webbeef, #44493).

You can configure Servo to write all of its storage to a unique directory for that session by enabling Opts::temporary­_storage (@janvarga, #44433). Note that these unique directories currently persist after Servo exits, so it's an isolation feature, not a privacy feature.

Window­Rendering­Context::new and Software­Rendering­Context::new now return an error if the given size is less than 1x1 (@freyacodes, @mrobinson, #44011).

We've improved our API docs for Web­View, Web­View­Builder, Web­View­Delegate, ServoDelegate, Prompt­Dialog, Web­Resource­Load, Web­Xr­Registry, Preferences, and servoshell's EXPERIMENTAL­_PREFS (@simonwuelker, @TG199, @sabbCodes, @jdm, @rovertrack, #43892, #43787, #44171, #43947).

We've also improved our API docs for Opts, Output­Options, Diagnostics­Logging, Pref­Value, servo::opts, and servo­_config (@mukilan, #43802).

More on the web platform

Tab navigation now works across <iframe> boundaries (@mrobinson, #44397), and Ctrl+Backspace (or ) now deletes a whole word in input fields (@mrobinson, #43940).

Tab characters are now rendered correctly in <pre> (and other elements with 'white-space: pre'), with proper tab stops (@mrobinson, @SimonSapin, #44480). Spaces are now rendered correctly in 2D <canvas>, instead of twice as wide as they should be (@mrobinson, #43899).

<a href> now correctly resolves the URL with the page encoding (@sabbCodes, #43822).

We've improved the default appearance of <input type=file> (@sabbCodes, #44496) and <textarea placeholder> (@mrobinson, #43770).

All keyboard events, mouse events, wheel events, and pointer events, other than 'pointerenter' and 'pointerleave', now bubble out of shadow roots (@simonwuelker, @webbeef, #43799, #44094). 'error' events on Window now report the correct filename (source in onerror) and lineno (@Gae24, #43632).

console.log() and friends now support printf-style formatting directives, although for now %c is ignored (@TG199, #43897).

file: URLs are now considered secure contexts, so they can now use features like crypto.subtle and crypto.random­UUID (@simonwuelker, #43989).

Exception messages have improved in Location, Static­Range, and the HTML­Element family of types (@arihant2math, @MuhammadMouostafa, @treetmitterglad, #44282, #43260, #43882).

We've improved the conformance of fetch algorithms (@yezhizhen, #43970, #43798), focus and tab navigation (@mrobinson, #43842, #44029, #44360, #43859, #44535), form submission (@TG199, #43700), JS modules (@elomscansio, @Gae24, #43741, #44179, #44042), page navigation (@TimvdLippe, #43857), <svg view­Box> (@yezhizhen, #44420), 'attr()' (@Loirooriol, #43878), ':focus' (@mrobinson, #43873), 'font' (@RichardTjokroutomo, #44061), '@keyframes' (@simonwuelker, #43461), '@property' (@Loirooriol, #43878), 'load' events (@jdm, @arabson99, #43807, #44046), fetch­Later() (@TimvdLippe, #43627), axes and buttons on Gamepad (@log101, @rovertrack, #44411, #44357), copy­Tex­Image­2D() on Web­GL­Rendering­Context (@simartin, @mrobinson, #43608), tex­Image3D() on Web­GL2­Rendering­Context (@simartin, #44367), environment­Blend­Mode on XR­Session (@msub2, #44155), mark() and measure() on Performance (@shubhamg13, @simonwuelker, #44471, #44199, #43990, #43753), and Performance­Resource­Timing (@shubhamg13, #44228).

We've fixed bugs related to console logging (@sabbCodes, #44243), 'animation' (@mrobinson, #44299), 'box-shadow' (@yezhizhen, #44474, #44457), 'display: contents' (@Loirooriol, @mrobinson, #44551, #44299), 'display: inline-flex' (@SimonSapin, #44281), 'display: table-cell' (@Loirooriol, #44550), 'display: table-row-group' (@Veercodeprog, #43674), 'overflow-x: clip' and 'overflow-y: clip' (@Messi002, #43620), 'position: absolute' on grid items (@nicoburns, #44324), 'word-spacing: <percentage>' (@sabbCodes, #44031), remove­Child() on Document (@rovertrack, #44133), and URL.revoke­Object­URL() (@simonwuelker, @jdm, #43746, #43977, #44035).

Performance and stability

We've fixed some big inefficiencies in Servo. append­Child() with nested shadow roots is no longer O ( 2 n ) (@yezhizhen, @webbeef, #44016), and we've halved the time it takes to load the ECMAScript spec by fixing the O ( whole DOM tree ) processing of 'id' and 'name' attributes (@simonwuelker, #44120, #44127, #44117).

Servo makes its first TLS connection in each session 30-60 ms faster (@jschwe, #44242), and we've instrumented the Servo and servoshell startup processes to find more opportunities for optimisation (@jschwe, #44443, #44456).

Like most browser engines, Servo is a multi-threaded (and sometimes multi-process) system requiring a great deal of IPC messages to keep everything connected. Two key components of this system are the constellation thread, which manages the engine as a whole, and the script threads (or web processes), which render the web pages. Sending these messages can be expensive though, so to reduce unnecessary IPC traffic, we've landed an optimisation that allows script threads to selectively receive only the relevant messages from the constellation (@webbeef, #43124).

We've reduced the memory usage of each Attr, Text, and Character­Data node in the DOM by 16 bytes (@mrobinson, @Loirooriol, #44074), and fixed a memory leak when deleting <video controls> or <audio controls> (@Messi002, #43983).

Our about:memory page is more accurate now too, with new tracking of libc memory allocations on macOS, improved tracking of libc memory allocations on Linux (@jschwe, #44037), and more accurate tracking of Path­Buf and types in tokio, http, data­_url, and urlpattern (@Narfinger, #43858).

Less memory usage isn't always better in browser engines though, because there are many kinds of caches and other optimisations we can do to make browsing the web faster, at the expense of increased memory usage. For example, we can greatly speed up prototype checks for DOM objects by storing a number in each object that identifies the concrete type, at the expense of making each DOM object 64 bits larger (@webbeef, #44364).

Layout can now reuse fragments in later reflows, in many cases that involve block layout or 'position: absolute' (@mrobinson, @lukewarlow, @Loirooriol, #42904, #44231). We're also working on reusing shaping results in later reflows, and making inline layout more efficient (@mrobinson, #44370, #43974, #44436).

We've landed several changes that should reduce the binary size of Servo (@rovertrack, @mrobinson, @nicoburns, @Narfinger, #44227, #44221, #44303, #44338, #44428, #44134).

We've also reduced clones, allocations, borrow checks, GC rooting steps, and other operations in many parts of Servo (@rovertrack, @Narfinger, @Loirooriol, @yezhizhen, @simonwuelker, #44008, #44544, #44271, #44279, #43826, #44052, #44139).

Several crashes have been fixed:

We fixed a crash in servoshell when pressing keys like Ctrl+2 or ⌘2 with not enough tabs open (@mrobinson, #44070).

DOM data structures (#[dom­_struct]) can refer to one another, with the help of garbage collection. But when DOM objects are being destroyed, those references can become invalid for a brief moment, depending on the order the GC finalizers run in. This can be unsound if those references are accessed, which is a very easy mistake to make if the type has an impl Drop. To help prevent that class of bug, we're reworking our DOM types so that none of them have #[dom­_struct] and impl Drop at the same time (@willypuzzle, #44119, #44501, #44513).

We've improved our static analysis for GC rooting (@officialasishkumar, #44489), and we've continued our long-running effort to use the Rust type system to make certain kinds of dynamic borrow failures impossible (@sagudev, @TimvdLippe, @Narfinger, @elomscansio, @Gae24, @rovertrack, @yezhizhen, @nodelpit, #43174, #43524, #43928, #43943, #43942, #43944, #43946, #43952, #43975, #44018, #44175, #44241, #44368, #44406, #44441, #44422, #44475, #44478, #44484, #44476, #44490, #44477, #44494, #44497, #44498, #44495, #44505, #44506, #44507, #44508, #44509, #44510, #44512, #44482, #44527, #44528, #44531, #44534, #44542, #44533, #44543, #44553, #44547, #44563, #44562, #44565, #44558, #44583, #44606, #44605, #44608, #44602, #44584, #44620, #44590, #44254, #44628, #44629, #44638, #44626, #44081).

Thanks to a wide range of people, we've also landed a bunch of cleanups and refactors (@delan, @alice, @Skgland, @atbrakhi, @eerii, @sabbCodes, @jdm, @thebabalola, @CynthiaOketch, @kkoyung, @TimvdLippe, @rovertrack, @webbeef, @arabson99, @yezhizhen, @simonwuelker, @mrobinson, @nicoburns, @longvatrong111, @niyabits, @treetmitterglad, @foresterre, @mukilan, @elomscansio, @freyacodes, @StaySafe020, @TG199, #43772, #44006, #43860, #44121, #44160, #43884, #44154, #44569, #43939, #44003, #44110, #44122, #43824, #44635, #44103, #43978, #44092, #44114, #44277, #44454, #44274, #44237, #44232, #44167, #44214, #43820, #43825, #43810, #43838, #43841, #43847, #43875, #43876, #43889, #43893, #43896, #43881, #43906, #43913, #43908, #43917, #43910, #43921, #43924, #43925, #43907, #43923, #43916, #43909, #43911, #43957, #43969, #43967, #43915, #43954, #43963, #43959, #43955, #44067, #44068, #44071, #44084, #44265, #44115, #44358, #43848).

Donations

Thanks again for your generous support! We are now receiving 7349 USD/month (+2.5% from March) in recurring donations. This helps us cover the cost of our speedy CI and benchmarking servers, one of our latest Outreachy interns, and funding maintainer work that helps more people contribute to Servo.

Servo is also on thanks.dev, and already 33 GitHub users (−4 from March) that depend on Servo are sponsoring us there. If you use Servo libraries like url, html5ever, selectors, or cssparser, signing up for thanks.dev could be a good way for you (or your employer) to give back to the community.

We now have sponsorship tiers that allow you or your organisation to donate to the Servo project with public acknowlegement of your support. If you're interested in this kind of sponsorship, please contact us at join@servo.org.

7349 USD/month
10000

Use of donations is decided transparently via the Technical Steering Committee's public funding request process, and active proposals are tracked in servo/project#187. For more details, head to our Sponsorship page.

31 May 2026 12:00am GMT