nsIStyleSheet
implementationsA 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 rule
tree), 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.
Describe nsFrameManager::ReResolveStyleContext
and
nsIFrame::GetParentStyleContextFrame
...
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.
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 CSS2
6.1.1), which is important for the rule
tree.
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.
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 [name of node: rule it points to]
:
[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.
[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).]
Problems: The stylesheet representation uses way too much memory.
nsIStyleSheet
implementationsProblems: 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.
(Back to Mozilla Stuff, David Baron)
LDB, dbaron@dbaron.org, 2002-04-02