This month in Servo: network inspector, a11y first steps, WebDriver, and more!

Plus some big perf gains, thanks to our incremental layout work.

Posted 2025-07-17

June was Servo’s busiest month in years, with 349 pull requests landing in our nightly builds!

Servo now supports viewport meta tags (@shubhamg13, @xiaochengh, #35901), the exportparts attribute (@simonwuelker, #37345), the ‘::part()’ selector (@simonwuelker, #37307), and several new web API features:

You can now use a screen reader to help you control servoshell’s browser UI (@jdm, #37519). Note that Servo’s webviews are not yet accessible to screen readers.

Screenshot of servoshell with the location bar focused, annotated with key presses and Orca screen reader output: “frame” TAB “button” TAB “button” TAB “button” TAB “entry https colon slash slash servo dot org slash selected”
Navigating servoshell with Orca, a screen reader. The back, forward, and reload buttons are not yet distinguishable (#38130).

You can now call setTransform() with a dictionary on CanvasRenderingContext2D and CanvasPattern (@tharkum, #37692, #37731).

servoshell showing various examples of calling setTransform() on CanvasRenderingContext2D with a dictionary

Abort handling on the web is a lot more viable now, with support for abort() and signal on AbortController (@gterzian, #37192, #37217), plus aborted, reason, and throwIfAborted() on AbortSignal (@gterzian, #37218, #37227, #37245).

Our experimental multiprocess mode (-M / --multiprocess) now works on Windows (@wusyong, #37580).

We’ve fixed several bugs, notably including a bug in the encoding of HTML form submissions in non-Unicode documents (@simonwuelker, #37541), which single-handedly fixed over 97000 subtests in the Web Platform Tests.

Outreachy intern Jerens Lensun (@jerensl) is now working on improving linting in mach! Keep an eye out for his patches, and in the meantime, check out the blog post he wrote about his experiences.

Devtools

Servo’s devtools support is becoming more capable! We now have basic support for the Network tab (@uthmaniv, #37384, #37263, #37543, #37707), including listing requests and viewing Headers and Timings.

This work on our network inspector would not be possible without our Outreachy intern Usman Baba Yahaya (@uthmaniv)! Until next month, check out the blog post he wrote about his time so far.

In addition, our devtools are now compatible with Firefox 139 (@eerii, #37247), and we’ve landed some preliminary work towards supporting the Sources tab (@atbrakhi, @delan, #36774, #37456, #37197).

Network tab in the Servo devtools, showing a list of requests, the Headers panel with request and response headers for a request, and the Timings panel with the time each phase of that request took

Performance

We’ve landed several improvements towards incremental layout, a broad class of optimisations that ensure that layout work is only done when something has changed and never done twice. That work is some subset of these five steps:

  • Style, that is, calculating the styles for the DOM tree
  • Box tree construction, taking the styled DOM tree as input
  • Fragment tree construction, for pages, columns, and lines
  • Stacking context tree construction, sorting it into CSS painting order
  • Display list construction, yielding the input we send to WebRender

Servo can now skip display list construction when nothing would change (@mrobinson, @Loirooriol, #37186). This change is especially noticeable when moving the mouse cursor around on a page.

Script queries, like offsetParent and getBoundingClientRect(), read back information from style and layout. When answering script queries, we can often skip some steps that are not relevant, but three steps were previously unavoidable. Script queries can now skip style, box tree, and fragment tree updates when those are up to date (@mrobinson, @Loirooriol, #37677). This means some queries can now be answered without doing any work at all!

You can now change ‘transform’, ‘scale’, ‘rotate’, ‘translate’, and ‘perspective’ without a full layout in many cases (@Loirooriol, @mrobinson, #37380).

WebDriver

Work continues on our WebDriver server, which can be used to automate Servo and will also power our support for testdriver.js-based Web Platform Tests. We now better handle operations for switching contexts (@yezhizhen, @longvatrong111, #37685, #37632, #37411), sending input (@longvatrong111, @yezhizhen, @PotatoCP, #37484, #37624, #37403, #37260, #37423, #37224, #37393, #37153, #37095), inspecting the page (@yezhizhen, #37521, #37532, #37502, #37452, #37425, #37470), and working with shadow roots (@yezhizhen, @longvatrong111, #37546, #37578, #37280).

Want to try automating Servo with WebDriver? It’s so easy it fits in a blog post!

$ cargo new app
$ cd app
$ cargo add [email protected]
use std::{
    error::Error, net::{Shutdown, TcpStream},
    process::Command, thread::sleep, time::Duration,
};

use webdriver_client::{
    Driver, HttpDriverBuilder, LocationStrategy,
    messages::{ExecuteCmd, NewSessionCmd},
};

fn main() -> Result<(), Box<dyn Error>> {
    // Run servoshell.
    Command::new("/path/to/servo")
        .args(["--webdriver", "about:blank"])
        .spawn()?;

    let driver = HttpDriverBuilder::default()
        .url("http://127.0.0.1:7000")
        .build()?;
    let mut params = NewSessionCmd::default();

    // Remove the unsupported `goog:chromeOptions` capability, which Servo rejects
    // with a “Session not created due to invalid capabilities” error.
    params.reset_always_match();

    // Wait for the WebDriver server to start.
    loop {
        sleep(Duration::from_millis(250));
        if let Ok(stream) = TcpStream::connect("127.0.0.1:7000") {
            stream.shutdown(Shutdown::Both)?;
            break;
        }
    }

    // Connect to the WebDriver server and control Servo.
    let session = driver.session(&params)?;
    session.go("https://bucket.daz.cat/work/igalia/servo/webdriver-example/")?;
    session.execute(ExecuteCmd {
        script: "scrollBy(0,9001)".to_owned(),
        args: vec![],
    })?;

    let diffie = session.find_element(
        "#thread-diffie img",
        LocationStrategy::Css,
    )?;
    diffie.click()?;

    sleep(Duration::from_secs(1));
    session.execute(ExecuteCmd {
        script: "const h1 = document.querySelector('h1');
            h1.innerHTML = 'hello from webdriver!';
            scrollBy(0,300)".to_owned(),
        args: vec![],
    })?;

    Ok(())
}
Servo showing a page that was modified by a WebDriver client

Donations

Thanks again for your generous support! We are now receiving 4464 USD/month (−2.8% over May) in recurring donations. This helps cover the cost of our self-hosted CI runners and one of our latest Outreachy interns!

Keep an eye out for further improvements to our CI system in the coming months, including dedicated benchmarking runners and ten-minute WPT builds, all thanks to your support.

Servo is also on thanks.dev, and already 25 GitHub users (same as May) 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.

4464 USD/month
10000

As always, use of these funds will be decided transparently in the Technical Steering Committee. For more details, head to our Sponsorship page.

Back