How Quantum CSS (Stylo) Parallelized Styling in Firefox

This article explores Quantum CSS, codenamed Stylo, the revolutionary style engine integrated into the Firefox web browser. It covers the origins of Stylo within Mozilla's Project Quantum, the limitations of traditional single-threaded CSS processing, and the specific technical mechanisms—such as the Rust programming language and work-stealing algorithms—that allowed Stylo to safely parallelize style computation across multiple CPU cores.

What was Quantum CSS (Stylo)?

Quantum CSS, internally known as Stylo, was a major component of Mozilla’s Project Quantum, which launched in Firefox 57 in November 2017. Stylo replaced Firefox’s legacy C++ CSS engine with a modern engine written in the Rust programming language.

Stylo originated from Servo, Mozilla's experimental browser engine designed from the ground up to take advantage of modern multi-core hardware. By integrating Servo's style system directly into Gecko (Firefox's existing layout engine), Mozilla successfully shipped the first production-grade, parallelized CSS engine in a major web browser.

The Traditional Style Computation Bottleneck

To render a webpage, a browser parses HTML into a Document Object Model (DOM) tree and parses CSS into a list of rules. Style computation is the process of figuring out which CSS rules apply to which DOM elements (selector matching) and calculating the final values for every CSS property on every element (cascading).

Historically, browsers performed style computation on a single CPU thread. On modern, complex websites with thousands of DOM nodes and massive stylesheets, this single-threaded approach created a severe performance bottleneck. While hardware manufacturers were adding more cores to CPUs, browsers were still only using one core to calculate styles.

Browser engineers avoided parallelizing this process in C++ because multithreading complex tree structures is notoriously difficult. A single mistake can lead to data races—bugs where two threads access the same memory simultaneously, leading to crashes and critical security vulnerabilities.

How Stylo Parallelized Style Computation

Stylo solved the multithreading problem by combining the safety guarantees of the Rust programming language with an efficient parallel processing architecture.

1. Fearless Concurrency with Rust

Rust enforces strict memory management and ownership rules at compile time. If a developer writes code that could result in a data race between two threads, the Rust compiler simply refuses to build the program. This allowed Mozilla engineers to write complex, multithreaded style computation code without the risk of introducing memory safety bugs.

2. Top-Down Tree Traversal

CSS relies heavily on inheritance; a child element often needs to know the computed style of its parent (for example, to inherit a font-size) before its own style can be calculated. Therefore, Stylo must traverse the DOM tree from the top down.

Stylo starts at the root of the DOM tree. Once a parent node's style is computed, its child nodes become independent of one another and can be processed simultaneously on different CPU cores.

3. Chunking into Work Units

Assigning every single DOM node to a separate thread would create too much communication overhead between CPU cores. Instead, Stylo groups adjacent DOM nodes into "work units" (typically up to 16 nodes). A thread claims an entire work unit, processes those nodes sequentially, and then outputs the newly discovered child nodes as new work units.

4. The Work-Stealing Algorithm

Webpage DOM trees are rarely symmetrical. One branch of the tree might be shallow, while another is deeply nested. If the browser simply split the tree in half and gave each half to a different CPU core, one core would finish early and sit idle while the other remained overloaded.

To solve this, Stylo used a Rust library called Rayon, which implements a "work-stealing" queue:

  • Stylo spins up a pool of worker threads matching the number of available CPU cores.
  • Each thread has its own local queue of DOM work units to style.
  • When a thread finishes styling a parent node, it pushes the children into its local queue.
  • If a thread runs out of work in its own queue, it looks at the queues of other busy threads and "steals" their waiting work units.

This dynamic load balancing ensures that all CPU cores remain at near 100% utilization until the entire DOM tree is styled, regardless of how unevenly the HTML is structured.

5. Thread-Local Style Sharing

Modern web pages contain many identical elements, such as lists of links or paragraphs. To avoid recalculating styles from scratch for identical elements, Stylo utilizes a Style Sharing Cache.

As threads traverse the DOM, they check a cache to see if a similar sibling or cousin element has already been styled. If a match is found, the thread simply copies the computed style pointer rather than running the selector matching process again. Stylo made this cache safe for parallel execution, allowing multiple threads to benefit from shared styles without slowing each other down.