| # 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) |