Style System Overview

Style System

Style sheets & rules

Rule tree

Style context interface

Style sheets & rules

  • nsIStyleRuleProcessor and nsIStyleSheet describe in C++ what a CSS stylesheet can do
  • nsIStyleRule describes in C++ what a CSS style rule can do
  • Main implementations are for CSS, but we have other implementations in C++ that:
    • do what CSS can't do
    • do things faster than CSS would

CSS Style Sheets

  • At basic level, sheet is collection of rules
  • Other special things: @import, @media, @namespace, etc.
  • A rule is a selector and a declaration block.
  • A declaration block is a group of declarations.
  • A declaration is a property and a value.
 p {
   color: green;
   font-size: 12em;
 }

 selector {
   property: value;
   property: value;
 }

CSS Style Rules

What do style rules mean?

  • Selector matches elements in the document.
  • Rule applies to matched elements.
  • “cascaded” value for property + element:
    • if 0 rules matching the element have the property: some properties inherit and some properties use initial value.
      • inherited: 'font-size', 'color', etc.
      • “reset”: 'border', 'background', etc.
    • 1 matching rule: use value
    • 2+ matching rules: cascade decides which wins: sort by
      1. origin (UA, user, author) & weight (!important),
      2. then specificity of selector,
      3. then order

Example document source

 <doc>
   <title>A few quotes</title>
   <para class="emph">
     Franklin said that <quote>"A penny
     saved is a penny earned."</quote>
   </para>
   <para>
     FDR said <quote>"We have nothing to
     fear but <span class="emph">fear
     itself.</span>"</quote>
   </para>
 </doc>

Example document tree

doc
title para class="emph" para
quote quote
span class="emph"

Example stylesheet

 doc {
   display: block;
   text-indent: 1em;
 }

 title {
   display: block;
   font-size: 3em;
 }

 para { display: block; }
 [class="emph"] { font-style: italic; }

CSS style rule representation

  • Each declaration block is represented by an nsCSSDeclaration
  • An CSSStyleRuleImpl contains each selector associated with that declaration, and the declaration, and is the most important implementation of nsIStyleRule.
  • The declarations (properties & values) are stored in data structs (nsCSS*).
  • The data format of the structs and selectors is mostly clear from the code and not worth going into (although the implementation of :not() is confusing).

CSS style rule representation

 h1, h2 {
   color: green;
   text-align: right;
   text-indent: 0;
 }
CSSStyleRuleImpl CSSStyleRuleImpl
h1 nsCSSDeclaration h2
nsCSSColor

color: green
nsCSSText

text-align: right
text-indent: 0

CSS style rule representation

  • Problem: The rule structures use too much memory (a few hundred kilobytes for all our chrome), and require large numbers of allocations to construct.
  • Detail: !important declarations cause an extra rule object CSSImportantRule to be created since they are in a separate part of the cascade

Other nsIStyleRule implementations

  • nsHTMLMappedAttributes represents stylistic HTML attributes turned into a style rule (one instance per unique set of attributes)
  • BodyRule handles marginwidth/marginheight mixes on BODY and on FRAME.
  • Various rules in nsHTMLStyleSheet.cpp do other things with presentational color-related attributes and with tables.
  • Content nodes (instead of stylesheets) also hold onto CSSStyleRuleImpl objects that represent their style attributes.

The cascade

  • StyleSetImpl manages the different origins of rules in the cascade (UA, user, author)
  • Style set gets the nsIStyleRuleProcessor implementations from the style sheets, and the CSS stylesheets force one CSSRuleProcessor per origin (rather than one per stylesheet).
  • HTMLStyleSheetImpl (HTML attributes) and HTMLCSSStyleSheet (style attributes) also implement nsIStyleRuleProcessor.

The nsIStyleRuleProcessor interface

  • Implemented by CSSRuleProcessor, HTMLStyleSheetImpl, and HTMLCSSStyleSheetImpl
  • Has a RulesMatching method, which is required to call nsRuleWalker::Forward on any rules that match the element. (We'll see what this does later.)
  • The main argument to RulesMatching is a structure of enumeration data that implementations use to determine more quickly which rules match.
  • Has a separate RulesMatching method for pseudo-elements.

CSSRuleProcessor

  • One CSSRuleProcessor per origin (UA, User, Author)
  • CSS rule processor sorts all the rules in cascade order, and then puts them in RuleHash, which remembers order and then hashes by first of id, class, tag, namespace, or unhashed.
  • To match rules, we do lookups in the RuleHash's tables, remerge the lists of rules using stored indices, and then call SelectorMatchesTree to find which selectors really match.
  • Matching of class and id selectors is much faster than, say, attribute selectors. (Important advice for chrome CSS authors.)
  • Pseudo-elements are hashed in element hash, so for matching pseudo-elements we only need one hashtable lookup.

Style contexts

  • Each style context (nsStyleContext), which is the interface through which layout accesses the style data for a given element, points to one rule node.
  • We create one style context per frame, since frames point to style contexts rather than the other way around.
  • Style contexts own their parents, since inheritance operates through style context parents (ugly when we have multiple frames per content node).
  • There is one rule node on the path from the root rule node to the style context's rule node for each rule that the element matches.

Style context API

  • The style context API allows data to be obtained through a set of structs (see nsStyleStruct.h).
  • A struct for a style context can be obtained through nsIStyleContext::GetStyleData. nsIFrame::GetStyleData does the same thing for the frame's mStyleContext member, and the global ::GetStyleData is a typesafe helper that doesn't require the style struct ID.
  • This style struct is always const, and should always be declared as such (evil old-style casts often used with the non-typesafe forms sometimes hide this error), since the struct may be shared with other elements.

The rule tree

  • As we call nsRuleWalker::Forward on all the rules that are matched, we build or walk along the rule tree.
  • The rule tree is a lexicographic tree of matched rules, where each node in the tree is represented by a nsRuleNode.
  • Some style data is cached on nsRuleNode objects to speed up computation and reduce memory use.

The rule tree

Rules:

 /* rule 1 */ doc { display: block; text-indent: 1em; }
 /* rule 2 */ title { display: block; font-size: 3em; }
 /* rule 3 */ para { display: block; }
 /* rule 4 */ [class="emph"] { font-style: italic; }

Rule tree:

A: null
B: 1 C: 2 D: 3 E: 4
F: 4

Style context tree:

doc: B
title: C para: F
class="emph"
para: D
quote: A quote: A
span: E
class="emph"

The rule tree and style data

  • All of the style structs that can be obtained through a style context add up to around 900 bytes. This is big, and slow to fill in. The rule tree allows sharing.
  • To allow for optimizations, each style struct contains only inherited properties or only “reset” properties. (Thus these structs can be called inherited structs or reset structs.)
  • In the normal case, a style rule has a small number of declarations (hitting only a small number of structs).

The rule tree and style data

We optimize for the structs for which the rules have no declarations. In this case:

  • inherited structs: same value as parent style context (optimization breaks when property has non-inherit value)
  • reset structs: same struct for every style context using rule node (optimization breaks when a value is explicit inherit)
  • reset structs: rule nodes have the same shared struct as their parent (optimization breaks when a property is specified with a different value or when there is an explicit inherit value).

Inherited structs act like reset ones in the descendants of any rule node for which the data are fully specified.

Style data cached in style context tree

  • If a style struct for a context depends on the data in the parent context, we will cache that struct in the style context tree. This happens when properties are inherited or when percentage units are used in a way that is handled by the style code (rather than reflow).
  • If the data are exactly the same as those of the parent, we copy the struct to the child style context and set a bit saying that it doesn't own the struct. (The context tree can be deep.)
  • Each rule node has a per-struct set of “none bits” that say that the rule node's set of rules (the rules on the path to the root) specify nothing non-inherited for the struct.

Style data cached in rule tree

  • If the data struct doesn't depend on the parent style context in any ways (inheritance, perhaps by omission; percentages and ems when computed), we can cache it in the rule tree.
  • When we compute a data struct, we cache it as high as possible in the rule tree -- on the lowest of the rule nodes on the path to the root that specified something for that struct.
  • A rule node that uses the same struct as its parent is marked with a “dependent bit” for that struct, which tells nsRuleNode::GetStyleData to just get the struct from the parent.

Style data computation

  • All the style struct computation described in the previous few slides happens lazily.
  • First, nsStyleContext::GetStyleData checks for a cached struct on the style context, and returns it if present.
  • Then, nsRuleNode::GetStyleData checks for a cached struct or a dependent bit on the rule node, and returns the struct if present.
  • Otherwise, we need to compute the struct, so nsRuleNode::GetStyleData calls nsRuleNode::Get*Data, which initializes the correct one of the data structs on the stack (the structs used by nsCSSDeclaration)

Style data computation

  • Get*Data calls nsRuleNode::WalkRuleTree, which walks from the style context's rule node towards the root rule node.
  • To fill in the data, we call nsIStyleRule::MapRuleInfoInto on the rules.
  • MapRuleInfoInto implementations must check that the property is not filled in before filling it in.
  • WalkRuleTree stops walking up when it finds either a none bit, a cached struct, a dependent bit, or all the properties have been filled in.
  • WalkRuleTree also remembers the first rule that contributed non-empty data.

Style data computation

  • After WalkRuleTree stops walking up, it calls nsRuleNode::Compute*Data to turn the specified values into the mostly-computed style data in the style struct.
  • Compute*Data use either a default or a start struct as the basis for the computation. A start struct is a cached struct in the rule tree that we can just copy and add to.
  • If the computation in Compute*Data used any data from the parent style context, we cache the struct on the style context.
  • Otherwise, we cache the struct in the rule tree, on the first rule node that contributed any data, and mark dependent bits on the path up to that rule node.

Managing style contexts

  • Style contexts must (in most cases) be created before frames are constructed, to determine what frame to create.
  • Parent style context determines inheritance; it should always be the content parent. [design flaw in frame/SC relationship]
  • Three functions for creating style contexts on nsIStyleSet, wrapped by similarly named ones on nsIPresContext:
    • ResolveStyleContextFor: For elements.
    • ResolvePseudoStyleContextFor: for pseudo-elements (:first-letter, :before, etc.)
    • ResolveStyleContextForNonElement: skips rule matching and uses root rule node (text frame optimization)

Managing style contexts

  • Style context resolving functions will walk the rule processors in StyleSetImpl::FileRules, find the correct rule node, and find a current child of the parent (“sibling sharing”) or create a new child.
  • Style context doesn't hold pointer to content, just rule node.

Dynamic changes to content

  • FrameManager::ReResolveStyleContext destroys and recreates style contexts for existing frames (rule node pointer immutable).
  • ReResolveStyleContext is messy because it needs to create and parent style contexts correctly (sibling sharing may not be the same) rather than just changing data. [design flaw, again]
  • Any specially-parented style contexts (not along frame parents, which need not be content parents) are reconstructed using nsIFrame::GetParentStyleContextFrame.
  • Can return same style context due to sibling sharing unless we're destroying the rule tree for a style sheet/rule removal.

Dynamic changes to content

  • ReResolveStyleContext calculates differences (repaint, reflow, reframe, etc.) between style old and new style contexts and does appropriate cleanup
  • It uses nsIStyleContext::CalcStyleDifference, which only computes differences for structs that have been requested. (I'll call this the data-struct-based hint mechanism.)
  • Caller of nsIFrameManager::ComputeStyleChangeFor processes the change list, which has been built to avoid duplication.
  • We also have ReParentStyleContext, used in a few places (usually during frame construction), but it's broken (has many bugs that ReResolveStyleContext used to have).

Dynamic changes to content: optimizations

  • We optimize attribute changes by storing all the attributes that have an effect on which rules match and only doing a ReResolveStyleContext if the attribute has an effect. nsIStyleSheet::AttributeAffectsStyle (should be on nsIStyleRuleProcessor).
  • We optimize event state changes (:hover, :active, etc.) using nsIStyleRuleProcessor::HasStateDependentStyle, which is much more accurate. The CSSRuleProcessor implementation does a slightly modified form of selector matching to implement it (includes matching on the middle of selectors to catch p:hover a).

Style attribute changes

  • We handle style attribute (“inline style”) changes in a different way from other changes to style rules.
  • As for other style changes, we have to “walk” the rule tree and clear all the style data coming from the old inline style nsIStyleRule, since there could be an !important rule that overrides it, which would allow dynamic changes to put the style attribute in multiple places in the rule tree. However, we maintain a hashtable just for inline style rules so that we don't have to walk the whole tree to find the nodes.
  • nsCSSFrameConstructor::AttributeChanged only re-resolves style on the subtree of the element, just like other attribute changes.
  • Different hint mechanism (from rule structs, not data structs) could make AttributeChanged just go straight to a frame-change, instead.
  • Bugs due to failure to call nsIFrame::DidSetStyleContext.

Style sheet addition and removal

  • Handled in pres shell.
  • PresShell::ReconstructStyleData calls FrameManager::ComputeStyleChangeFor (ReResolve) and then processes the frame-change list.
  • Rebuilds rule tree if stylesheet was removed to avoid dangling pointers (and perhaps aliasing that would cause problems). Otherwise we'd have to walk rule tree and compare each rule node to every rule in the sheet (O(rules * rule-nodes)).
  • When rebuilding rule tree, we have to clear cached style contexts from XUL menus and trees.

Style rule changes

  • Handling of style rule changes is done in frame constructor (called from style set, called from pres shell, which is a document observer) and in the pres shell. Code should be merged.
  • Rule change applies the rule-struct hint as if the rule matched the root element. (inefficient) It does clearing of style data (through StyleSetImpl::ClearStyleData) by walking the rule tree and then the style context tree. (could be handled by simultaneous clearing and difference calculation of data (somewhat tricky))
  • Rule addition and removal just rebuild the entire world. We could at least do what we do for sheet addition/removal, or slightly better, by searching the rule tree (only one rule this time) instead of rebuilding it.
  • Lots of room for optimization here. (but beware DidSetStyleContext)

The style system

Style sheets & rules

Rule tree

Style context interface

Original Document Information

  • Author(s): David Baron
  • Last Updated Date: June 6, 2003
  • Copyright Information: Portions of this content are © 1998–2007 by individual mozilla.org contributors; content available under a Creative Commons license | Details.