CSS Style Calculation in Blink

Rendered

About this document

This is is a description of how Blink calculates which style rules apply to each element. It is not a comprehensive guide to the whole style computation/update process but it should be accurate for what it does cover. Further additions are welcome.

It ignores all V0 Shadow DOM logic.

High-level view

Process

The process of calculating styles for the elements is broken into 3 phases:

  • Gathering, partitioning and indexing the style rules present in all of the style sheets
  • Visiting each element and finding all of the rules which apply to that element
  • Combining those rules and other information to produce the final computed style

Main classes

A catalogue of the classes involved. Read their class docs.

The following are long-lived objects that remain static during the calculation of each element's style.

The following are short-lived objects that are used when computing a single element's style.

Compiling and indexing

When changes occur in the style sheet, either in an existing TreeScope or with the introduction of a new TreeScope, the ScopedStyleResolver for that scope must be updated. This is done by calling AppendActiveStyleSheets and passing in a collection of style sheets. These style sheets are appended to the list of active style sheets in the TreeScope and also partitioned and indexed by FindBestRuleSetAndAdd. Within each RuleSet are several maps of maps of RuleData objects. FindBestRuleSetAndAdd looks at the right-most compound selector of each selector and chooses which of these maps to hold this RuleData. E.g. the selector p.cname's right-most simple selector matches against class="cname", so this is added to the ClassRules map with a key of "cname".

At the end of this process, each TreeScope in the document has a ScopedStyleResolver containing all of the style rules defined directly in that scope, partitioned into various RuleSets.

Calculating styles for each element - WIP

This guide starts with the simplest operation and works backwards.

SelectorChecker::MatchSelector - Checking if a rule applies to an element

Match is the public entrypoint and MatchSelector is the recursive core of checking if a rule applies to an element. Read their docs and also SelectorCheckingContext and CheckOne.

The whole process is started by ElementRuleCollector::CollectMatchingRulesForList which creates the initial SelectorCheckingContext, pointing to the element we are considering and to first simple selector of the CSSSelector array. Read CSSSelector's class description to understand how complex selectors are represented by arrays of CSSSelectors.

StyleForLayoutObject - Calculating the computed style for an element

If there are no custom style callbacks or animations StyleForLayoutObject leads to StyleResolver::StyleForElement which is where the bulk of the work occurs. First by finding all of the rules which match the element and then using that and other info to compute the final style.

Finding all rules which match the element

Blink considers all of the relevant style sheets for each element by partitioning and indexing the rules in each stylesheet inside the RuleSet object, Blink is able to avoid considering many irrelevant rules for the current element. E.g. if the element has class="cname" then Blink looks in the RuleSet's ClassRules map under the key “cname” and considers all of the RuleData objects found there. This allows it to avoid considering any rules with selectors that end in ".othercname" which would have been under "othercname" in the ClassRules map.

In this way, Blink works through various lists of RuleData for the element calling CollectMatchingRulesForList on each list, how that works is described below.

Inside this method, context is set up that is used while calculating the style.

With all of this context set up, it calls MatchAllRules which matches the rules from the

MatchAuthorRules splits applies the following steps (read the method docs):

CollectMatchingRulesForList - testing some rules against an element

This is at the core of all the code paths. It takes

This creates a SelectorChecker and SelectorCheckerContext for the element and uses it to check try match, one by one, against each RuleData object in the input list. If checker.Match(context, result) returns true then this rule applies to this element and it is added to the collection with DidMatchRule.

Computing style from matched rules

TODO

Descending the DOM trees

Document::UpdateStyleAndLayoutTree is the starting point for computing or recomputing the styles of elements in the document. This calls UpdateActiveStyle which calls UpdateActiveStyle and leads into the compiling and index above. Then it calls UpdateStyleInvalidationIfNeeded() (see here) and then UpdateStyle which is what starts the traversal of the Element tree.

The tree is traversed in shadow-including tree oreder. There are 2 recursive paths that can be taken. The simpler one is in the case where the change being applied is ComputedStyleConstants::kReattach. It recurses through ContainerNode::RecalcDescendantStylesForReattach and involves methods with names like RecalcFooStyleForReattach. The more complex recursion is similar. It recurses through ContainerNode::RecalcDescendantStyles and involves methods with names like RecalcFooStyle but it can enter the reattach code also. In both cases, the actual style calculation is performed by Element::StyleForLayoutObject.

Omissions