How Chromium Calculates CSS in Style Recalculation

This article explains the mechanics behind Chromium's style recalculation phase, detailing how the Blink rendering engine processes CSS rules and applies them to the Document Object Model (DOM). It covers the exact steps Chromium takes, starting from CSS rule indexing and selector matching, moving through cascade resolution, and ending with the generation of the final computed styles required for page layout.

Rule Indexing and Bucketing

Before matching styles to any DOM elements, Chromium's Blink engine organizes parsed CSS rules into an optimized data structure called a RuleSet. Instead of comparing every CSS rule in a stylesheet against every DOM node, Chromium groups rules into specific "buckets" (hash maps) based on their rightmost selector, known as the key selector.

There are separate buckets for IDs, class names, tag names, and attributes, plus a universal bucket for general rules. For example, in the selector .nav > ul li.active, the key selector is .active, so the rule is placed into the active class bucket. This pre-processing drastically reduces the number of rules the engine must evaluate for any given element.

DOM Traversal and Selector Matching

When a change triggers the style recalculation phase, Chromium traverses the DOM tree in a top-down manner. For each element that needs its styles calculated, Chromium instantiates an ElementRuleCollector.

The collector inspects the element's ID, classes, attributes, and tag name, and pulls only the relevant candidate rules from the corresponding RuleSet buckets. Chromium then performs selector matching on these candidates. To maximize performance, it evaluates complex selectors from right to left. It starts at the target element (matching the key selector) and walks up or across the DOM tree to verify parent, ancestor, or sibling combinators. If a rule fails to match at any point, the engine immediately abandons that rule and moves to the next candidate.

Resolving the Cascade

Once all matching CSS rules for an element are collected, Chromium must determine which declarations win when property conflicts arise. The ElementRuleCollector sorts the matched rules based on the standard CSS cascade criteria:

  1. Origin and Importance: The engine separates user-agent styles (browser defaults), user styles, and author styles (website CSS), while prioritizing rules marked with !important.
  2. Cascade Layers: Rules are ordered according to any defined @layer rules.
  3. Specificity: Chromium calculates a specificity score based on the components of the selector (IDs outweigh classes, which outweigh tags).
  4. Order of Appearance: If specificity is tied, rules declared later in the stylesheet or document override earlier ones.

Generating the ComputedStyle

After sorting the matched rules, Chromium uses a component called the StyleBuilder to take the winning property declarations and create an immutable ComputedStyle object for the element.

During this step, the engine resolves relative values into absolute, usable values. For instance, relative units like em, rem, and vh are converted into concrete pixel values based on the parent element, root element, or viewport dimensions.

CSS inheritance is also finalized during this step. Because Chromium traverses the DOM from top to bottom, the parent element's ComputedStyle is always ready before the child is processed. If an inheritable property (like color or font-size) is not explicitly defined on the child element, the StyleBuilder simply copies the resolved value from the parent's ComputedStyle.

Style Invalidation Optimization

To maintain high frame rates, Chromium avoids recalculating styles for the entire DOM tree when minor changes occur. When the DOM or CSSOM is mutated—such as a JavaScript function toggling a class on a div—Chromium uses "Invalidation Sets."

These sets allow the engine to quickly determine the exact descendants or siblings that might be affected by that specific class change. Chromium marks only those affected nodes as "dirty." When the style recalculation phase actually runs, the DOM traversal skips clean nodes entirely and only executes the bucketing, matching, and cascade processes on the dirty nodes, updating their ComputedStyle objects before passing the updated tree to the Layout phase.