[css-scoping] Handling global name-defining constructs in shadow trees · Issue #1995 · w3c/csswg-drafts (original) (raw)

Since the introduction of Shadow DOM, we've been struggling with what to do with name-defining things, like @font-face, which define global names for other things to reference.

Right now the answer is a collective shrug. I think I have an answer, however:

  1. Every name-defining thing (such as @font-face) is valid inside of shadow trees, and is scoped to the TreeScope that its defining stylesheet is in. Nested shadows can use name-defining things defined in higher trees (see below), but can't directly refer to them.
  2. Every reference to a defined name (such as a font-family font name) is implicitly a tuple of (name, defining scope), where the defining scope is the TreeScope the reference's stylesheet is in. (In other words, it's always a reference to the local thing defining the name, not something further up the scope tree.)
  3. Applying a style from a stylesheet in one TreeScope to an element in a different TreeScope (such as via ::part()) thus resolves the name against the stylesheet's TreeScope, even if the element's TreeScope has that name redefined to something else. (So an outer page setting an ::part(foo) { animation: foo 1s; } uses the @keyframes foo {...} from the outer page, not anything from inside the shadow tree that that "foo" part is in.)
  4. The value/scope tuple is inherited normally thru TreeScopes, meaning that a particular reference refers to the same name-defining construct no matter how deeply it gets inherited.
  5. This does not affect how the value serializes - the reference continues to serialize as just a keyword, with no tree scope mentioned. So setting a property on an element to its own computed value is not always a no-op when shadow DOM is involved; the new value will be referring to the element's tree scope, which may not have that name defined or have it defined to something else.
  6. This does affect how the value reifies in TypedOM - keywords will gain a nullable .scope attribute or something, which points to the tree scope the keyword is being resolved against.

This has some implications. Since the defining scope is implicitly captured by a reference, it doesn't change as the value inherits. Thus, in this situation:

ONE <::shadow>

TWO

THREE

FOUR </> </> </>


Scripting is a slightly thornier problem here. When setting styles, we can use the rules I've already laid out - you're always setting the style in some stylesheet (perhaps the implicit one attached to an element and accessed via el.style), so there's a consistent notion of an associated TreeScope. (This may not always be obvious, but it's clear - a script that pokes around inside of the shadows of its components and sets styles needs to be aware of what scope the stylesheet is in and what scope the name-defining thing it's trying to reference is in.)


(Edited to take into account the compromise to drop the scoped() syntax and only allow implicit references via the string-based API.)