Mozilla Style System Documentation

See also the style techtalk for more complete although less detailed documentation.

Style context management

A style context (class nsStyleContext, currently also interface nsIStyleContext although the interface should go away when all of the style system can be moved back into the layout DLL) represents the style data for a CSS formatting object. In the layout system, these formatting objects are represented as frames (interface nsIFrame), although table formatting objects are represented by a pair of frames.

The CSS specification describes formatting objects that correspond to elements in the content model and formatting objects that correspond to pseudo-elements. (Mozilla has a bunch of its own pseudo-elements that are not in the CSS specification.) We also create style contexts for some things that are not CSS formatting objects: text nodes and placeholder frames. These three types of style contexts correspond to the three ways of creating a style context: nsIPresContext::ResolveStyleContextFor, nsIPresContext::ResolvePseudoStyleContextFor, and nsIPresContext::ResolveStyleContextForNonElement. There is also a fourth method, nsIPresContext::ProbePseudoStyleContextFor, which creates a style context only if there are style rules that match the pseudo-element. This is useful for the pseudo-elements defined in the CSS specification (:before, :after, :first-line, :first-letter), but few of Mozilla's custom pseudo-elements, many of which are hacks for extra formatting objects that we create. The pres context just forwards these calls to its style set object (StyleSetImpl, interface nsIStyleSet), which does the real work (and also maintains the lists of stylesheets and owns the rule tree). These methods may all return an existing style context rather than a new one (see StyleSetImpl::GetContext), if there is an existing style context with the same parent, that matches the same rules (a check that is easy because of the ruletree), and is for the same pseudo-element (or not for a pseudo-element, or for a "non-element"). This is more than just sibling-sharing, since if the parent is shared, it could be cousin-sharing.

In CSS, some of the style data for an element depends on the style data for that element's parent. Likewise, some of the style data for a pseudo-element depends on the data for the pseudo-element's parent element. Thus we create a style context tree so that the style contexts can find their ancestors and their descendants easily. However, siblings in the style context tree are unordered, since the order is not relevant. Thus the parent style context is an argument to the functions that create style contexts. If the parent is not given, the style context is taken to be the root of the style context tree. The functions that create the style context automatically adds the context to the tree correctly.

Dynamic changes

Describe nsFrameManager::ReResolveStyleContext and nsIFrame::GetParentStyleContextFrame ...</p>

Describe nsCSSFrameConstructor::AttributeChanged hack for style attribute that avoids style context tree manipulation.

Problems: Dynamic style changes scrap the whole thing and start over. This is very hard to fix because of the "sibling-sharing" optimization. Things dealing with dynamic style changes are also difficult because the style contexts don't have pointers to their frames, but rather we have the reverse, and is probably the biggest flaw in the design of this part of the system.

Getting style data from a style context

To get style data from a style context, use the GetStyleData method to fill in a pointer to a style struct. Each of the style structs contains a group of properties. The structs are listed in nsStyleStruct.h and the many of the values are in nsStyleConsts.h. Each of the structs contains either only properties that are inherited by default or only properties that are set to the initial value by default (see CSS26.1.1), which is important for the ruletree.

The basic way to get a style struct from a style context looks like this:

const nsStyleDisplay *display = NS_STATIC_CAST(const nsStyleDisplay*,
                                    sc->GetStyleData(eStyleStruct_Display));

There is also a (non-virtual) method on nsIFrame to get the style data from a frame's style context (saving the refcounting needed to get the style context):

const nsStyleDisplay *display;
frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display);

However, there are equivalent typesafe global function templates that (should) compile to the same thing but use the type of the template parameter to pass the correct nsStyleStructID parameter. For frames:

const nsStyleDisplay *display;
::GetStyleData(frame, &display);

or for style contexts:

const nsStyleDisplay *display;
::GetStyleData(sc, &display);

These functions cause an appropriate struct to be computed if it hasn't been computed already, and then fill in the struct pointer. The struct is owned by the style system (either on the style context or on the rule node). These functions should only fill in a null pointer on allocation failure.

Style contexts and the rule tree

When the style system creates a style context it walks through the style sheets (interface nsIStyleSheet) attached to a document in the order defined by the CSS cascade and finds the style rules (interface nsIStyleRule) that match the content node or content node + pseudo-element pair. (The "non-element" style contexts are defined never to match any rules.) These interfaces nsIStyleSheet and nsIStyleRule correspond to the CSS concepts of style sheets and style rules, except they are more general, and are used by other code that needs to add style information to the document. For the CSS stylesheets, this process corresponds to CSS selector matching. The output of CSS selector matching as defined by the CSS specification is an ordered list of rules, where the order determines which declarations override other declarations. (In CSS, declarations are the property-value pairs within style rules. In Mozilla, nsCSSDeclaration objects correspond to CSS declaration-blocks.) Due to the great similarity of these lists between elements in the content tree, Mozilla stores the output of the selector matching process in a lexicographic tree, the rule tree. This double tree (style context tree and rule tree) allows for sharing of style data, which allows the data to take up less memory and allows the data computation to take less time.

For example, suppose we had the CSS stylesheet:

/* 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; }

and the following document:

<doc>
    <title>A few quotes</title>
    <para class="emph">Benjamin Franklin said that <quote>"A penny saved
    is a penny earned."</quote></para>
    <para>Franklin D. Roosevelt said that <quote>"We have nothing to
    fear but <span class="emph">fear itself</span>."</para>
</doc>
This will lead to a rule tree that looks like this, where each node is in the format <code>[name of node: rule it points to]</code>:
<pre class="tree">
                  [A: null]
            ,------' /  \ `------.
        [B: 1]  [C: 2]  [D: 3]  [E:  4]
                          |
                        [F: 4]

Note that two rule nodes point to rule 4.

The style context tree will look like this, ignoring all the style contexts for the text nodes (all of which have style contexts pointing to rule node A), with each style context in the format [element type: rule node]:

                      [doc: B]
         ,------------'  |   `-----------.
    [title: C]       [para: F]         [para: D] 
                         |                 |
                    [quote: A]         [quote: A]
                         |
                     [span: E]

The reason the rule tree shares style data naturally is that most style rules specify properties in very few structs. As stated above, each style struct contains only properties that are inherited by default or those that are set to their initial value ("reset") by default. This leads to (or perhaps three, depending on how you count) two types of sharing. For those structs where all the values are inherited by default, a style context can often (when none of the rules matched by the style context specify any properties in the struct, or when explicit inherit is used) use the same struct as its parent style context. For the structs where all the properties are reset by default, if no explicit inherit values or em or similar units are used, the style struct can be cached on the rule node rather than the style context and shared between all style contexts pointing to that rule node. Furthermore, if the rule specifies no values for a struct, it can use the same struct as its parent. While the style context tree is generally quite deep, since it corresponds roughly to the content tree, the rule tree is generally quite broad (but are there cases where it is quite deep??), since the depth of a node in the tree corresponds to the number of rules matched. Therefore, inherited structs are cached on the style context (but only the top style contexts pointing to them actually "owns" them), but structs that are shared between rule nodes are stored only on the highest rule node to which they apply and then retrieved from that highest rule node every time they are needed. The code that does this sharing work is mostly in nsStyleContext::GetStyleData, nsRuleNode::GetStyleData, and nsRuleNode::WalkRuleTree.

When nsRuleNode::WalkRuleTree computes a struct, it walks the rules, starting with the style context's rule node, towards the root of the rule tree. It uses a the appropriate declaration struct and has each rule fill in any properties specified by that rule that are not filled in already. The walking stops when all of the properties are filled in or when the root of the rule tree is reached. Then the style struct is computed from the declaration and stored at the appropriate location in the rule tree or on the style context.

Problems: Dynamic style changes often destroy too much data.

CSS Stylesheet backend

CSS Stylesheet Loading

[This section needs to be written. I'm reluctant to write it both since I don't know much about it.]

Problems:A bunch the code needs to be rewritten to prevent stylesheets from blocking the parser and to reduce string copying (although that partly goes with parsing).]

Parsing

Stylesheet representation

Problems: The stylesheet representation uses way too much memory.

Other nsIStyleSheet implementations

Problems: Some of the HTML style information is implemented in the content node classes. It should be consolidated so that the style system code can be moved back within the layout DLL and nsIStyleContext can be de-COM-ified.

Original Document Information

  • Author(s): L. 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.

Document Tags and Contributors

Tags: 
 Contributors to this page: teoli, DBaron, Kohei
 Last updated by: DBaron,