Design
This is a very high-level overview of Servo's design. Servo remains an early prototype and portions of the design are not yet represented in the actual code. Some important aspects of the system have not been thought through in detail.
Overview and goals
Servo is a research project to develop a new parallel web browser engine. It has two primary goals: to prevent common, exploitable security errors using strong, static typing; and to design an architecture that takes advantage of parallelism at many levels. Servo is explicitly not aiming to create a full web browser - it is focused on just the engine, not the chrome.
To those ends Servo is written in Rust, a new language designed specifically with Servo's requirements in mind. Rust provides a strong type system, task-based parallelism, and data-race freedom.
Strategies for parallelism
- Task-based architecture - major components in the system should be factored into actors with isolated heaps. This alone will provide some incidental parallelism, but more importantly will force the system to be decomposed types and interfaces that are most amenable to future parallel research. (implemented)
- Copy-on-write DOM - in Servo, the DOM is a versioned data structure that is shared between the content (JS) task and the layout task, allowing layout to run even while scripts are reading and writing DOM nodes. (implemented)
- Tiled rendering - Divide the screen into a grid of tiles and render each one in parallel. Needed for mobile performance regardless of its benefits for parallelism. (implemented)
- Layered rendering - Divide the display list into subtrees that are transformed independently of each other (think moving sprites), either automatically or not, and render them in parallel.
- Parallel selector matching - embarrassingly parallel. Unlike gecko, Servo will will do selector matching in a separate pass from layout so that it may be more easily parallelized.
- Parallel layout - this is a very hard problem. Will defer to future. Could make meaty intern projects. Meyerovich has done lots of interesting research here.
Strategies for security
- Multiprocess architecture
Challenges
- Parallel data structures - maintaining good straight-line performance, making complex shared types type-safe
- Language immaturity - crashy Rust compiler, no libraries, changing language
- Thread-hostile libraries - some 3rd party libraries we need don't play well in multi-threaded environments. fonts in particular have been difficult. sometimes there are big global locks that destroy parallelism.
The task architecture
Each engine instance can for now be thought of as a single tab or window, and manages a pipeline of tasks that accepts input, runs JavaScript against the DOM, performs layout, builds display lists, renders display lists to tiles and finally composites the final image to a surface.
The pipeline consists of three main tasks:
- Content - Content's primary mission is to create and own the DOM and execute the JavaScript engine. It receives events from multiple sources, including navigation events, and routes them as necessary. When the content task needs to query information about layout it must send a request to the layout task.
- Layout - Layout takes a snapshot of the DOM, calculates styles, and constructs the main layout data structure, the flow tree. The flow tree is used to calculate the layout of nodes and from there build a [display list], which is sent to the render task.
- Renderer - The renderer receives a display list and renders visible portions to one or more tiles, possibly in parallel, and composites them to a surface for the application to display.
Two complex data structures are involved in multi-task communication in this pipeline, the DOM and the display list. The DOM is communicated from content to layout and the display list from layout to the renderer. Figuring out an efficient and type-safe way to represent, share, and/or transmit these two structures is one of the major challenges for the project.
Resource management
Asynchrony, caching strategy.