# CSS Style Invalidation in Blink [Rendered](https://chromium.googlesource.com/chromium/src/+/HEAD/third_party/blink/renderer/core/css/style-invalidation.md) # About this document The doc gives a high-level overview of the style invalidation process. It does not cover [sibling invalidation](https://goo.gl/z0Z9gn). # 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](https://goo.gl/3ane6s) and the [sibling invalidation design doc](https://goo.gl/z0Z9gn). # 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: * [`Node::NeedsStyleInvalidation`](https://cs.chromium.org/?q=symbol:%5Eblink::Node::NeedsStyleInvalidation$) * [`Node::ChildNeedsStyleInvalidation`](https://cs.chromium.org/?q=symbol:%5Eblink::Node::ChildNeedsStyleInvalidation$) * [`Node::NeedsStyleRecalc`](https://cs.chromium.org/?q=symbol:%5Eblink::Node::NeedsStyleRecalc$) * [`Node::ChildNeedsStyleRecalc`](https://cs.chromium.org/?q=symbol:%5Eblink::Node::ChildNeedsStyleRecalc$) ## 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`](https://cs.chromium.org/?q=symbol:%5Eblink::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`](https://cs.chromium.org/?q=symbol:%5Eblink::RuleSet$) produces its own [`RuleFeatureSet`](https://cs.chromium.org/?q=symbol:%5Eblink::RuleFeatureSet$) by calling [`CollectFeaturesFromRuleData`](https://cs.chromium.org/?q=symbol:%5Eblink::RuleFeatureSet::CollectFeaturesFromRuleData$) for each [`RuleData`](https://cs.chromium.org/?q=symbol:%5Eblink::RuleData$). These contain several indexed collections of [`InvalidationSet`](https://cs.chromium.org/?q=symbol:%5Eblink::InvalidationSet$)s and some miscellaneous properties. All of these are merged together to form a final [`RuleFeatureSet`](https://cs.chromium.org/?q=symbol:%5Eblink::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`](https://cs.chromium.org/?q=symbol:%5Eblink::Node$) (e.g. a change in class name) this leads to a call into [`StyleEngine`](https://cs.chromium.org/?q=symbol:%5Eblink::StyleEngine$) to record this style-impacting change via one of several [`FooChangedForElement`](https://cs.chromium.org/?q=symbol:%5Eblink::StyleEngine::.*ChangedForElement$) methods. Depending on the type of change, [`StyleEngine`](https://cs.chromium.org/?q=symbol:%5Eblink::StyleEngine$) gathers the relevant [`InvalidationSet`](https://cs.chromium.org/?q=symbol:%5Eblink::InvalidationSet$)s and calls [`PendingInvalidations::ScheduleInvalidationSetsForNode`](https://cs.chromium.org/?q=symbol:%5Eblink::PendingInvalidations::ScheduleInvalidationSetsForNode$) which will do one or both of * call [`Node::SetNeedsStyleInvalidation`](https://cs.chromium.org/?q=symbol:%5Eblink::Node::SetNeedsStyleInvalidation$) which ensures that the invalidation process will consider this node and add InvalidationSets for this node to the pending invalidation set map. * call [`Node::SetNeedsStyleRecalc`](https://cs.chromium.org/?q=symbol:%5Eblink::Node::SetNeedsStyleRecalc$) with either [`kLocalStyleChange`](https://cs.chromium.org/?q=symbol:%5Eblink::StyleChangeType::kLocalStyleChange$) or [`kSubtreeStyleChange`](https://cs.chromium.org/?q=symbol:%5Eblink::StyleChangeType::kSubtreeStyleChange$) ## 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`](https://cs.chromium.org/?q=symbol:%5Eblink::ContainerNode$) in the DOM tree we have 0 or more descendant [`InvalidationSet`](https://cs.chromium.org/?q=symbol:%5Eblink::InvalidationSet$) waiting to be applied. The invalidation process starts with a call to [`StyleInvalidator::Invalidate`](https://cs.chromium.org/?q=symbol:%5Eblink::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](https://goo.gl/3ane6s) [Sibling invalidation design doc](https://goo.gl/z0Z9gn)