Embed Servo Engine in Rust for Rendering & WASM

Servo v0.1.0 crate exposes browser engine as embeddable Rust lib; use SoftwareRenderingContext for headless screenshots (servo-shot CLI: 150 lines renders URL to PNG); sub-crates like html5ever compile to 454KB WASM for browser SPAs.

Core API: Build Servo Engine and WebViews for Rendering

Start with ServoBuilder::default().opts(Opts).preferences(Preferences).event_loop_waker(EventLoopWaker).protocol_registry(ProtocolRegistry).build() to create a Servo handle. Pair it with RenderingContext implementations: SoftwareRenderingContext::new(PhysicalSize<u32>) for headless GPU-free rendering (ideal for CI/servers), or WindowRenderingContext with raw-window-handle for apps. Then WebViewBuilder::new(&servo, Rc<dyn RenderingContext>).url(Url).hidpi_scale_factor(Scale).delegate(Rc<dyn WebViewDelegate>).build() yields a clonable WebView. Drive via servo.spin_event_loop() in a loop; hook WebViewDelegate::notify_load_status_changed(LoadStatus::Complete) post-load event for screenshots using RenderingContext::read_to_image(Box2D<i32, DevicePixel>) -> Option<ImageBuffer<Rgba<u8>, Vec<u8>>>. This mirrors servoshell API but streamlined for embedders, handling input events, network intercepts via WebResourceLoad, and custom protocols.

Trade-offs: SoftwareRenderingContext avoids GPU/X11 (apt-get install libegl1 may help errors) but slower; call present() with PreserveBuffer::No before read_to_image; wait for notify_new_frame_ready after LoadStatus::Complete to settle requestAnimationFrame. Use Rc<Cell<bool>> or Arc<AtomicBool> for load flags; set Preferences::network_http_proxy_uri = ""; handle surfman::error::Error with anyhow::Context.

Headless Screenshots: servo-shot CLI in 150 Lines

servo-shot <url|html> [--width 1280] [--height 800] [--dpr 1.0] [--out shot.png] renders to PNG using only servo = "0.1.0", clap, image, url, anyhow, dpi, euclid. Flow: Init SoftwareRenderingContext from CLI sizes (dpi::PhysicalSize<u32>), build Servo/WebView with ShotDelegate waiting on LoadStatus::Complete, loop spin_event_loop() until flag, ctx.read_to_image(full_rect) to image::save. Produces sample.png from sample.html; scales to 1920x1080@2x DPR. Build: cargo build --release after installing cmake, clang, llvm, pkg-config. Avoids unsafe; gotchas include no webview.paint(), explicit present()/swap_buffers, proxy unset, euclid::Size2D conversions.

WASM: Servo Sub-Crates for Browser, Not Full Engine

Full servo to wasm32-unknown-unknown/wasm32-wasi fails due to mozjs_sys, SharedArrayBuffer, fetch(). Instead, compile subsystems: html5ever, markup5ever_rcdom, selectors, cssparser, url. Demo html5ever-wasm-demo: cargo build --release --target wasm32-unknown-unknown; wasm-bindgen --target web yields 454KB html5ever_wasm_demo_bg.wasm, 8KB JS, 4KB HTML SPA. Parses textarea HTML into <html>/<head>/<body>/<tbody> trees client-side. Serve via python3 -m http.server www 8000; live at simonw.github.io/.../www/. Enables !important CSS checkers, fetch-free parsers; stream full renderer via WebRTC if needed.

Summarized by x-ai/grok-4.1-fast via openrouter

7047 input / 1915 output tokens in 12763ms

© 2026 Edge