Style System
Style sheets & rules
↓
Rule tree
↓
Style context interface
Style sheets & rules
nsIStyleRuleProcessor
andnsIStyleSheet
describe in C++ what a CSS stylesheet can donsIStyleRule
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.
- inherited:
- 1 matching rule: use value
- 2+ matching rules: cascade decides which wins: sort by
- origin (UA, user, author) & weight (
!important
), - then specificity of selector,
- then order
- origin (UA, user, author) & weight (
- if 0 rules matching the element have the property: some properties inherit and some properties use initial value.
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 ofnsIStyleRule
. - 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 objectCSSImportantRule
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 onBODY
and onFRAME
.- 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 theirstyle
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 oneCSSRuleProcessor
per origin (rather than one per stylesheet). HTMLStyleSheetImpl
(HTML attributes) andHTMLCSSStyleSheet
(style attributes) also implementnsIStyleRuleProcessor
.
The nsIStyleRuleProcessor
interface
- Implemented by
CSSRuleProcessor
,HTMLStyleSheetImpl
, andHTMLCSSStyleSheetImpl
- Has a
RulesMatching
method, which is required to callnsRuleWalker::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 callSelectorMatchesTree
to find which selectors really match. - Matching of
class
andid
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'smStyleContext
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
callsnsRuleNode::Get*Data
, which initializes the correct one of the data structs on the stack (the structs used bynsCSSDeclaration
)
Style data computation
Get*Data
callsnsRuleNode::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 callsnsRuleNode::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 onnsIPresContext
: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 thatReResolveStyleContext
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 onnsIStyleRuleProcessor
). - We optimize event state changes (
:hover
,:active
, etc.) usingnsIStyleRuleProcessor::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 catchp: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
callsFrameManager::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.