This document is a working document within the XBL task force which discusses one of the outstanding issues in depth: how do CSS selectors match against elements that are part of the "shadow content" (quoted because there are actually multiple possible definitions of "shadow content").
The latest draft of sXBL (22Nov2004) includes the following request for public feedback:
Request for public feedback
There is an open technical issue regarding CSS selector processing on shadow trees. Should selectors match only based on Core DOM parent-child links, ignoring XBL DOM links both in the bound document and the xblShadowTree subtree; or should they match against just the flattened tree traversals at each scope; or always match against both; or provide a switch to match against either?
Within the Binding Task Force, there is general agreement on the following:
The major open question is the tree topology that should be used for CSS selector matching. Here are some specific questions:
The proposals below attempt to provide answers to these questions.
Mozilla XBL includes a section titled Anonymous Content and CSS which includes the following text:
4.6.1. Selectors and Scopes
Bindings can interleave anonymous elements between the bound element and its explicit children. See 4.4. Insertion Points for more information. In this situation, a new tree emerges that is different from the explicit content node tree. In addition to having a single explicit parent (the bound element) the explicit children also have an arbitrary set of anonymous parents (created by bindings when child insertion points or element tags were used). Child, descendant and sibling selectors will match on any path of anonymous and explicit elements.
As far as CSS is concerned, anonymous content nodes are children (or descendants) of the bound element, they are ancestors of explicit content, and they are siblings of the explicit content. Style rules using the child, descendant or sibling selectors transparently cross binding scopes and operate on the altered and original content models.
The final modified content tree determines how CSS properties (e.g., fonts and colors) are inherited. An element either ends up underneath its explicit parent (just as in the content model), or it ends up being nested through a series of insertion points. When nested, it inherits from the innermost anonymous parent.
4.6.2. Binding Stylesheets
A binding file can load stylesheets using the stylesheet element. By default these stylesheets apply to the bound element and to all anonymous content generated by all bindings attached to the bound element. These sheets have the same origin as the sheet with the rule responsible for the binding. Stylesheets loaded by bindings that are attached using the DOM are treated as author-level sheets.
[Editor's Note: Binding inheritance complicates this cascade, since an author-level DOM binding could inherit from a user-level binding. If both load sheets, what level do those sheets belong to in the cascade?]
Sheets are always walked from the innermost scope to the outermost scope. With this ordering a binding that defines a widget can define a default look for the widget that can then be easily overridden by a client of the widget. For multiple bindings attached to the same element, the sheets are walked from the base binding down to the most derived binding.
Bindings can fine-tune the control of the stylesheet scoping with two attributes. The first, applyauthorstyles, indicates whether or not author sheets defined at outer scopes affect the anonymous content generated by the binding. For the primary generating binding only, this attribute is checked to see if any author sheets at outer levels of scoping should be applied to the anonymous content generated by the bindings attached to the bound element. If this attribute is set, the rules specified in any author sheets at outer scopes are not walked.
By default, stylesheets specified in bindings files are applied only to the bound element and to anonymous content generated by bindings attached to the element. A second attribute, styleexplicitcontent can be used to indicate that all children of the bound element, both anonymous and explicit, can be styled by the sheets in the binding's document.
User agent sheets and user sheets are always applied to all scopes.
The proposal in 4.1 is an evolved form of the behaviour specified in Mozilla XBL.
The description of RCC only says that CSS styling rules are an open issue. Adobe's early implementation of RCC applied selectors using Core DOM traversals, which is a proper subset of Proposal 2.
The following are possible additional requirements on the definition of XBL that came up during discussion of this issue:
The next two requirements are general XBL requirements that are of relevance here:
xforms|label { color:blue }
"). A similar requirement
would be the ability to implement HTML's own form controls using SVG+sXBL
(e.g., implement <html:button> using XBL with SVG shadow trees).In short, the proposal is that selectors be matched against certain variants of the flattened tree where deeper scopes are ignored one at a time until the element is considered ignoring all deeper scopes.
In sXBL (and in XBL2 by default unless certain attributes are set), an extra condition applies, namely that only nodes in the same scope are ever considered.
The goal of this proposal is to maintain encapsulation and information hiding while not exposing XBL internals to the styling model. In addition, forwards-compatibility is ensured by defining the model in a way that handles important XBL2 use cases.
Note: The proposal below sometimes refers to possible features for XBL2. These possible features are speculative since the XBL task force has not engaged yet in rigorous discussion about XBL2.
Specifically, the definitions would be as follows.
A > B
B
if B.xblParentNode == A
B
if ∃ i: B.xblParentNodes[i] == A
A B
B
if B.xblParentNode == A
or if B.xblParentNode == C
and A C
matches C
.
A + B
B
if either:
B.xblParentNode.xblChildNodes
contains A
followed by B
with no intervening elementsB.xblParentNode.xblScopedChildNodes
contains A
followed by B
with no intervening elementsB
if either:
B.xblParentNode.xblChildNodes
contains A
followed by B
with no intervening elements∃ i: B.xblParentNodes[i].xblScopedChildNodes
contains A
followed by B
with no intervening elementsA ~ B
A + B
but there may be intervening elements.
Note: This selector is not in CSS 2.0 or 2.1 but is included in the Selectors Candidate Recommendation.
In all cases, for sXBL and XBL2 (when the proposed
allowSelectorsThrough
attribute is false
,
the default),
there is the additional condition that:
B.xblBoundElement == A.xblBoundElement (might be null)
This condition ensures that selectors don't cross scopes. In XBL2,
the condition would be removed if an appropriate attributes were
present (allowSelectorsThrough
on the
template
elements of the shadow trees that contain
B
and A
, for those that are in shadow
trees).
In addition, pseudo-classes would be defined as follows:
B:empty
matches B
when B.xblScopedChildNodes
is empty.B:first-child
matches B
when it is the
first element in B.xblParentNode.xblScopedChildNodes
(and analogously for all structural pseudo-classes).B:root
matches B
if B.xblParentNode
is null.In these definitions, B.xblParentNodes
is the list of
elements that contain content
elements that acted as
insertion points for B
, from the nearest to the furthest
(in terms of the flattened tree).
B.xblParentNode
is the same as B.xblParentNodes[0]
.
B.xblScopedChildNodes
is the list of children in what
would be the flattened tree at that point if the element
B
had no xblShadowTree
(same as
childNodes
if the element is from the original DOM, and
same as xblChildNodes
if the element isn't a bound
element).
Strictly, xblScopedChildNodes
would be defined as:
"The xblScopedChildNodes
attribute is a
NodeList
that represents the children of the node after
content
elements have been applied, ignoring the
element's own shadow tree, if any. The attribute's value is the same
as xblChildNodes
if the node is not bound, and the same
as the childNodes
attribute if the element is not from a
binding.
B.xblChildNodes
is the list of children in the
flattened tree at that point.
This proposal doesn't allow any stylesheets to apply to other
scopes. For example, shadow content cannot be styled by bound document
sheets, and binding document sheets cannot style bound document
elements. (The only exception being the bound element, which is styled
by both.) XBL2 would presumably address this using the proposed
applyAuthorSheets
and applyBindingSheets
attributes, to allow (respectively) author sheets to apply to shadow
trees, and binding sheets to apply to children of the bound
element.
bound element: <theBoundElement/> binding template: <xbl:template> <aShadow/> <xbl:content/> <bShadow/> </xbl:template> binding document stylesheet: aShadow + xbl|content + bShadow { color: red; }
Using this proposal, the rule above would not match any elements in this example.
[XBL-TF open question: Should it match the element?]
bound element: <my:list> <html:li/> </my:list> binding template: <xbl:template> <html:ul> <xbl:content/> </html:ul> </xbl:template>
In XBL2, selectors like this one:
html|ul > html|li
...will need to match the above <html:li>
element (for the
UA stylesheet if nothing else).
This proposal can be trivially
extended to support this by making the "B.xblBoundElement ==
A.xblBoundElement
" condition only be used in the sXBL model,
and removing that restriction when an appropriate attribute is
present.
[XBL-TF open question: The XBL task force has not engaged yet in rigorous discussion about XBL2,
but at least some task force members believe that in this case
html|ul > html|li
should not match in XBL2 in the default case since
the original content has a <my:list>
element instead of an <html:ul>
element,
and instead only match if the binding
definition says the selectors should match against the flattened tree.]
Now extend this case so that the <html:ul>
element itself (in
the <my:list>
element's binding) is bound to:
<xbl:template> <my:dummy> <xbl:content/> </my:dummy> </xbl:template>
In XBL2, the selector given above would still have to match. The proposal given here supports this intrinsically. It also needs to match these two selectors at the same time:
html|ul > my|dummy my|dummy > html|li
(The first to cover cases where instead of my|dummy
we're talking
about another html|li
, the second for the case where my|dummy
is
actually an html|ul
.) Again, this proposal handles all this.
Here is another example which illustrates this approach:
<foo:bar> <svg:circle/> </foo:bar>
<foo:bar> is the bound element with the following xblShadowTree:
<xbl:shadowTree> <svg:text id="t"> ... </svg:text> <svg:g id="g1"> <svg:rect id="r"/> </svg:g> <svg:g id="g2"> <xbl:content/> </svg:g> </xbl:shadowTree>
Then, assuming a stylesheet in the binding document:
#t
— would match the 't' elementsvg|g > svg|rect
— would match the "r" elementfoo|bar svg|rect
— would not match — selector
matching does not match across elements with different
xblBoundElement
s in sXBL (but in XBL2, it would match
if the allowSelectorsThrough
attribute was set)svg|circle
— would not match — selector matching
does not go outside of xblShadowTree (although the same selector
within a stylesheet in the bound document would match)#g2 svg|circle
— would not match — selector matching
does not go outside of xblShadowTree. In XBL2, the same selector
within a stylesheet in the bound document would only match if the
allowSelectorsThrough
attribute was set.A similar example:
<foo:bar1> <foo:bar2> <svg:circle id="c"/> </foo:bar2> </foo:bar1>
Assume the bound element is <foo:bar2> and it has the following xblShadowTree:
<xbl:shadowTree> <svg:text id="t"> <svg:g id="g"> <svg:rect id="r"> </svg:g> <xbl:content/> </xbl:shadowTree>
#t
— would match the 't' elementsvg|g > svg|rect
— would match the "r" elementfoo|bar1 svg|rect
— would match "r" in XBL2, if
allowSelectorsThrough
is set.foo|bar2 svg|rect
— would match "r" in XBL2, if
allowSelectorsThrough
is set.foo|bar1 > svg|g
— would never match.foo|bar2 > svg|g
— would match "g" in XBL2, if
allowSelectorsThrough
is set.svg|text + svg|g
— would always match "g".svg|g + svg|circle
— would match "c" in the bound document
stylesheet in XBL2, if allowSelectorsThrough
is
set.Arguments in favor of this proposal:
Arguments against:
allowSelectorsThrough
set to true, and with a large
number of nested bindings, does scalability suffer. Therefore I
disagree that this is a disadvantage of this proposal.
<html:ul><xbl:content></html:ul>
where the bound element has a child <html:li>
and there is a selector html|ul > html|li
.
The proposal, which is already too complicated for sXBL, gains incremental complexity with the proposals
for XBL2, including the additions of having to loop through multiple different traversal paths
to check for matches.
In contrast, the second proposal supports this use case as proposed without waiting for XBL2
and without requiring the hard-to-understand processing model,
the negative performance implications, and side-effects from this proposal.
<aShadow>
,
<xbl:content>
, and <bShadow>
within the shadow tree.
The write-up says that the formulas will not match against the selector
aShadow + xbl|content + bShadow
with the comment that it makes no sense to match.
However, component developers might have a good reason to do such a sibling match where one
of the siblings is an <xbl:content>
element. Such a sibling match makes
as much sense as any sibling match -- the content
creator might want the final sibling (bShadow in this case) to be styled a certain way no matter what content is
referenced by the <xbl:content>
element.
The above proposal prevents future growth where such Core DOM traversal matching involving an
<xbl:content>
element would ever being allowed. This is in
contrast to the second proposal below which supports both options for sibling selector matching.
With the second proposal, stylingMode="domTree"
supports sibling matches involving an
<xbl:content>
element; stylingMode="flattenedTree"
supports sibling match after flattening out the
<xbl:content>
element;
and there is a colored box which mentions a possible third option (stylingMode="importContent"
)
for extra flexibility.<xbl:definition element="foo:bar"> <xbl:template> <z> <xbl:content includes="a"/> <xbl:content includes="b"/> </z> </xbl:template> </xbl:definition> <foo:bar> <b/> <a/> </foo:bar>It appears that according to the above definitions that all of these selectors within binding document stylesheets will match (and the first two are surprising!):
a:first-child a + b b + aNotes:
Another significant and unintuitive problem: If we change the above example by adding <svg:metadata> or <smil:animate> or <html:div display="none"> as a first child of z, then no :first-child selector within a bound document stylesheet will ever match any child of <foo:bar>, which will be very surprising to the author of the bound document.
There have been justifiable requests for CSS selector matching that supports fully encapsulated components (as how things worked in RCC) and for situations where the effect of the bindings are transparent. These two approaches align with standard programming techniques where sometimes it is best to use function calls (which generally are implemented using black-box methods where the underlying implementation is unknown) and other times it is best to use macros (where the result of the macro is transparent to the code that invokes the macro). To satisfy the various requirements, this proposal suggests introducing an attribute which allows CSS selectors to work off of the Core DOM or off of the flattened DOM.
To satisfy both requirements, this proposal suggests the addition of a 'stylingMode' attribute to the <xbl:definition> element as follows:
Attribute name: | stylingMode |
Possible values: | domTree|flattenedTree (default=domTree) |
For elements that are descendants of <xbl:shadowTree> or descendants of the bound element which are the targets for <xbl:content> elements:
stylingMode="domTree"
, CSS selectors
use standard Core DOM
parent/child/sibling traversals following the subtree defined by xblShadowTree.
CSS ancestor selectors do not match against
the <xbl:shadowTree>
element that is the root of xblShadowTree, the bound element,
or the bound element's ancestors.stylingMode="flattenedTree"
,
CSS selectors use XBL
parent/child/sibling traversals (e.g., xblFirstChild, xblNextSibling, xblParentNode).
CSS ancestor selectors match through the bound element and the bound element's ancestors
following XBL parent traversals (i.e., xblParentNode).In many cases, the component developer wants all styling controlled by binding document stylesheets, but in other cases he wants shadow content to be "transparent" to the bound document such that bound document stylesheets can apply to elements in the shadow tree. To satisfy these requirements, this proposal suggests the addition of an 'ownerDocument' attribute to the <xbl:definition> element as follows:
Attribute name: | ownerDocument |
Possible values: | bindingDocument|boundDocument (default=bindingDocument) |
This attribute controls the value of the DOM Core "ownerDocument" property on the Node interface for the <xbl:shadowTree> element and all nodes which are cloned from the <xbl:template> element and inserted as descendants of the <xbl:shadowTree> element. Setting the DOM Core "ownerDocument" property controls which set of stylesheets are active on elements which are descendants of <xbl:shadowTree>. When ownerDocument="bindingDocument", binding document stylesheets apply to these elements. When ownerDocument="boundDocument", then bound document stylesheets apply.
Here is an example which illustrates the stylingMode="domTree"
approach:
<foo:bar> <svg:circle/> </foo:bar>
Assume the bound element has the following xblShadowTree:
<xbl:shadowTree> <svg:text id="t"> <svg:g id="g1"> <svg:rect id="r"> </svg:g> <svg:g id="g2"> <xbl:content/> </svg:g> </xbl:shadowTree>
With a value of stylingMode="domTree"
on the
<xbl:definition>
element for the binding,
the following shows how various style declarations from a binding document stylesheet
match against the shadow content (assuming ownerDocument="bindingDocument"
):
Here is an example which illustrates the stylingMode="flattenedTree"
approach:
<foo:bar1> <foo:bar2> <svg:circle id="c"/> </foo:bar2> </foo:bar1>
Assume the bound element has the following xblShadowTree:
<xbl:shadowTree> <svg:text id="t"> <svg:g id="g"> <svg:rect id="r"> </svg:g> <xbl:content/> </xbl:shadowTree>
The fully flattened XBL topology (i.e., using xblParentNode, xblFirstChild, xblNextSibling, xblChildNodes, etc.) would be:
<foo:bar1> <foo:bar2> <svg:text id="t"> <svg:g id="g"> <svg:rect id="r"> </svg:g> <svg:circle id="c"/> </foo:bar2> </foo:bar1>
With a value of stylingMode="flattenedTree"
on the
<xbl:definition>
element for the binding,
the following shows how various style declarations from a binding document stylesheet
match against the shadow content:
The following example was copied from one of the arguments against Proposal 1. Assume the following binding definition:
<xbl:definition element="foo:bar" stylingMode="???" ownerDocument="???"> <xbl:template> <z> <xbl:content includes="a"/> <xbl:content includes="b"/> </z> </xbl:template> </xbl:definition>
And the following bound element:
<foo:bar> <b/> <a/> </foo:bar>
And assume the following selectors:
a:first-child a + b b + a
The topology of the fully flattened XBL tree in this case would be:
<foo:bar> <z> <a/> <b/> </z> </foo:bar>
Here is a table which shows the results for the possibles values of stylingMode
and ownerDocument
:
Selector | selectors are in binding document stylesheet, <xbl:definition> has ownerDocument="bindingDocument" |
selectors are in bound document stylesheet, <xbl:definition> has ownerDocument="boundDocument" |
||
<xbl:definition> has stylingMode="domTree" | <xbl:definition> has stylingMode="flattenedTree" | <xbl:definition> has stylingMode="domTree" | <xbl:definition> has stylingMode="flattenedTree" | |
a:first-child | Would not match | Would not match | Would not match | Would not match |
a + b | Would not match - b's ownerDocument is the bound document, so binding document stylesheets do not apply | Would not match - b's ownerDocument is the bound document, so binding document stylesheets do not apply | Would not match. Using regular DOM traversals, it is (b, a), not (a, b). | Would match. b's ownerDocument is the bound doument and in fully flattened topology, xblChildNodes is (a, b). |
b + a | Would not match - a's ownerDocument is the bound document, so binding document stylesheets do not apply | Would not match - a's ownerDocument is the bound document, so binding document stylesheets do not apply | Would match. a's ownerDocument is the bound doument and in regular DOM traversals, it is (b, a). | Would not match. Using flattened DOM traversals, it is (a, b), not (b, a) |
z > a | Would not match - a's ownerDocument is the bound document, so binding document stylesheets do not apply | Would not match - a's ownerDocument is the bound document, so binding document stylesheets do not apply | Would not match. In Core DOM traversals, a is not a child of z | Would match. Using flattened DOM traversals, a is a child of z |
foo|bar > z | Would not match - Selector traversals do not cross from xblShadowTree to bound element. | Would match - in flattened DOM, z is a child of foo:bar | Would not match - z's ownerDocument is the binding document, so bound document stylesheets do not apply. Also, it wouldn't be possible to cross from z to foo:bar using Core DOM traversals. | Would match - in flattened DOM, z is a child of foo:bar |
foo|bar a | Would not match - a's ownerDocument is the bound document, so binding document stylesheets do not apply | Would not match - a's ownerDocument is the bound document, so binding document stylesheets do not apply | Would match - Using regular DOM traversals, a is a descendant of foo:bar. | Would match - in flattened DOM, a is a descendant of foo:bar. |
foo|bar z a | Would not match - a's ownerDocument is the bound document, so binding document stylesheets do not apply | Would not match - a's ownerDocument is the bound document, so binding document stylesheets do not apply | Would not match - Using regular DOM traversals, a is a direct child of foo:bar. | Would match - in flattened DOM, a is a descendant of z which is a descendant of foo:bar. |
Arguments in favor of this proposal:
Arguments against:
content
elements) to the cascade engine.xblScopedChildNodes
in the first proposal.)ownerDocument
DOM attribute has nothing to do
with styling and it seems very odd to be relating the two in this
way. (Is this an implementation detail that is leaking into a spec
proposal?)Since neither of the proposals listed above were included in the latest public sXBL draft specification, it makes sense to wait for feedback on the above proposals before attempting to summarize public feedback on this issue.