CSS Style Invalidation in Blink

Rendered

About this document

The doc gives a high-level overview of the style invalidation process. It does not cover sibling invalidation.

Overview of invalidation

Invalidation is the process of marking which elements need their style recalculated in response to a change in the DOM. The simplest possible approach is to invalidate everything in response to every change.

Invalidation sets give us a way to find a smaller set of elements which need recalculation They are not perfect, they err on the side of correctness, so we invalidate elements that do not need recalculation but this are significantly better than recalculating everything. An invalidation set represents

  • criteria for matching against a node
  • instructions for whether/how to descend the tree into the node's children

If we have a style rule ".c1 div.c2 { ... }" then style recalculation is needed when a c1-class is added or removed as an ancestor of a c2-class or when a c2-class is added or removed from a div that is a descendant of a c1-class element (here adding/removing can be adding/removing an element or just adding/removing these classes on existing elements). We don't want to do full style recalc at the time of adding/removing because there may be more mutations coming. If we can tell immediately that a change forces style recalc then we mark the node as that, otherwise we collect everything as pending invalidation sets.

In the example, if a c1-class is added to an element in the tree, we need to invalidate all of its descendants which have class c2. Rather than perform a search right now, we just mark the element with a pending invalidation set that matches against c2 and descends into all light-descendants. (We won't invalidate all div descendants, as class c2 is more specific.)

If class c2 is added to an element in the tree, then it needs recalculation if it has a c1-class ancestor. We never search up the tree at this point or during style invalidation, we only do that during recalculation, so this becomes an immediate invalidation, even though it may be unnecessary. Similarly, we don't check if the c2-class element is a div.

Eventually all DOM changes have been turned into immediate invalidations or pending invalidation sets. At this point, we apply all the pending invalidations and then recalculate style for all of the invalidated elements.

For more details see the original invalidation sets design doc and the sibling invalidation design doc.

State involved

RuleFeatureSet

The data in RuleFeatureSet is built from the style rules and does not change unless the style rules change.

DOM Node's style states

DOM nodes have several bits of style-related state that control style invalidation and recalculation. These are accessed through:

PendingInvalidationsMap

This is a map from DOM Node to invalidation sets that should be applied due to updates to that node.

Processes

Overview

The overview of style invalidation and recalculation is that

  • Style rules are compiled down to a collection of InvalidationSets and other data in RuleFeatureSet
  • The following process is then applied continuously
    • Changes to the DOM cause nodes or subtrees to be immediately invalidated or to accumulate pending invalidations.
    • A point is reached where style is being read (e.g. in order to render a new frame)
    • Style invalidation process finds all pending invalidations and decides on what will actually be recalculated
    • Style recalculation process recalculates style for all nodes that need it

Building the RuleFeatureSet

Each RuleSet produces its own RuleFeatureSet by calling CollectFeaturesFromRuleData for each RuleData. These contain several indexed collections of InvalidationSets and some miscellaneous properties.

All of these are merged together to form a final RuleFeatureSet which used for style purposes.

Turning DOM changes into pending/immediate invalidations

Changes in the DOM that require updates to styles may get turned into either immediate invalidations or pending invalidations. When a DOM change that could impact style occurs inside a Node (e.g. a change in class name) this leads to a call into StyleEngine to record this style-impacting change via one of several FooChangedForElement methods.

Depending on the type of change, StyleEngine gathers the relevant InvalidationSets and calls PendingInvalidations::ScheduleInvalidationSetsForNode which will do one or both of

Pushing the pending invalidations

When style is about to be read, the map of pending invalidations which has been built up needs to be pushed. For each ContainerNode in the DOM tree we have 0 or more descendant InvalidationSet waiting to be applied. The invalidation process starts with a call to StyleInvalidator::Invalidate which recurses down the tree, depth first. Read the method's inline documentation to understand more about the process.

See Also

Invalidation sets design doc

Sibling invalidation design doc