· 5 min read ·

Servo Is a Crate Now, and That Changes the Embedding Story for Rust

Source: simonwillison

Servo, Mozilla Research’s Rust-based browser engine, spent years being more of a research vehicle than a deployable piece of infrastructure. That changed when the servo crate landed on crates.io, making it possible to embed a full browser engine in a Rust application with a standard dependency declaration in Cargo.toml.

Simon Willison’s hands-on exploration of the servo crate works through the API and gets something running, surfacing questions about what it means to have a browser engine available as a first-class Rust library and where the rough edges remain. What his post points toward, beyond the immediate technical walkthrough, is a genuine shift in what embedded browser rendering looks like for Rust applications.

The Road to a Crate

Servo started at Mozilla Research in 2012 as an experiment in applying Rust’s memory safety and parallelism to browser engine design. Two components that came out of it, WebRender (a GPU-accelerated 2D renderer) and Stylo (a parallel CSS style engine), were adopted directly into Firefox as part of the Quantum project and shipped in Firefox 57 in 2017.

After Mozilla’s 2020 layoffs cut the browser team significantly, Servo was transferred to the Linux Foundation. Development slowed. Then in 2023, Igalia, a Spanish open-source consultancy with deep involvement in browser engine work across WebKit and Chromium, stepped in as a primary contributor. The project has maintained regular releases since then, with measurable progress on web compatibility.

The shift toward embedding-as-a-library has been one of the project’s explicit goals for the past couple of years. Verso, a browser built entirely on top of Servo as a library using winit for windowing, served as proof that the model was workable. Publishing the servo crate to crates.io formalizes that intent.

What the Crate Actually Exposes

The servo crate exposes Servo’s engine as an embeddable component. You add it as a dependency, implement a small set of traits, provide an OpenGL context, and the engine handles layout, styling, compositing, and rendering. The application shell handles windowing, input events, and presenting the rendered output to the screen.

The embedding API centers on a few core types. The Servo struct owns the engine state and drives the internal event loop. Applications pass events in and receive back actions to perform: presenting a frame, navigating to a URL, opening a dialog. The boundary between engine decisions and application side-effects is explicit and intentional.

A representative embedding looks roughly like this:

use servo::{Servo, ServoUrl};
use winit::event_loop::EventLoop;

let event_loop = EventLoop::new().unwrap();
let servo = Servo::new(embedding_options);

let url = ServoUrl::parse("https://example.com").unwrap();
servo.load_url(url);

event_loop.run(move |event, target| {
    // Forward platform events into Servo
    servo.handle_platform_event(&event);

    // Process any actions Servo wants the shell to take
    for action in servo.get_events() {
        match action {
            EmbedderMsg::ReadyToPresent(_) => { /* swap buffers */ }
            EmbedderMsg::NewBrowserCreated(id) => { servo.focus_browser(id); }
            // ...
        }
    }
});

This pattern parallels how you embed Chromium via CEF (Chromium Embedded Framework), where the application is the host and implements callbacks for events the engine surfaces. The difference is that everything here is Rust types, traits replace C function pointer tables, and Cargo manages the entire dependency graph. No CMake, no manual SDK downloads, no separate build step for the engine itself.

Comparing the Embedding Landscape

There are a few established ways to embed a web renderer in a desktop application, and Servo fits into the landscape differently from each of them.

CEF is the dominant option, powering Slack, Discord, VS Code, and the broader Electron ecosystem. It works reliably and has broad web compatibility, but it ships a copy of Chromium. That is roughly 100 to 150 megabytes of binary depending on platform, integrated via zip archives and CMake configuration. The build story is manual and the binary cost is significant.

Tauri and its underlying wry crate take the opposite approach: they delegate to whatever WebView the operating system provides. WKWebView on macOS, WebView2 on Windows, WebKitGTK on Linux. The binary stays small, but rendering consistency varies across platforms and you are subject to whatever WebView version the user has installed. On Linux in particular, WebKitGTK version fragmentation has historically been a source of friction.

Servo occupies a third position. It ships its own rendering stack with no system WebView dependency, keeping behavior consistent across platforms. It integrates as a Cargo dependency without any external build system involvement. WebRender, its rendering backend, has been production-proven inside Firefox for years. The tradeoff for all of this is dependency depth: Servo pulls in a substantial graph including WebRender, SpiderMonkey JavaScript bindings, OpenGL utilities, and the rest of the engine. For applications where those costs are acceptable, the integration story is considerably cleaner than CEF. For applications that need only minimal web rendering, the weight is hard to justify.

Web Compatibility and Current Limitations

Servo tracks its own results on the web-platform-tests suite, and the pass rate has been climbing steadily under Igalia’s stewardship. CSS support is strong because of Stylo, which shares implementation heritage with Firefox’s production style engine. HTML5 parsing and layout cover the common cases. JavaScript runs through SpiderMonkey with WebAssembly support included.

The gaps that remain are concentrated in less-common browser APIs, some media format support, and legacy compatibility edge cases. For the primary use case of embedded browser engines, where the application controls the content being rendered rather than pointing at arbitrary web URLs, those gaps matter considerably less than they would for a general-purpose browser. If you are rendering application UI defined in HTML and CSS, or loading known-good web content, Servo’s compatibility is largely sufficient.

This is worth emphasizing because embedded browser engines are not typically trying to be Firefox. CEF apps do not ship Chromium because they want to browse the open web; they ship it because they want a capable, consistent rendering substrate for their own UI or for rendering third-party content they have some degree of control over. Servo is competitive in that narrower scope even before it closes the remaining compatibility gaps.

The Deeper Implication for Rust’s Ecosystem

The more interesting question behind Willison’s exploration is what a Rust-native browser embedding means for the language’s GUI and application development ecosystem.

The landscape of Rust GUI toolkits, iced, egui, Slint, Dioxus, and others, is active but fragmented. None of them provide a fully capable browser renderer as a composable component. They each offer their own rendering and layout models, which vary in capability and maturity. Servo does provide a capable browser renderer, and it is now a Cargo dependency.

For applications that want to build part of their UI in HTML without shipping Electron, without depending on system WebViews with their platform inconsistencies, and without managing a C++ build system, the servo crate is the first option that fits naturally into how Rust projects are structured. That is a gap the ecosystem has had for a while.

The project’s GitHub repository shows sustained activity and a roadmap oriented toward making embedding more ergonomic. The servo crate exists, it compiles, and with effort it renders. That is a different baseline than where the project was two years ago, and the trajectory from here is toward reduced integration friction rather than continued research.

Willison’s exploration is useful precisely because getting something to compile and produce a rendered result is a different bar than reading a README. The hands-on account establishes that the crate is usable in practice, not just in principle.

Was this interesting?