[Bug 22344] [Shadow]: projecting into <shadow>

https://www.w3.org/Bugs/Public/show_bug.cgi?id=22344

--- Comment #6 from Dominic Cooney <dominicc@chromium.org> ---
(In reply to comment #5)
> I feel like I'm missing a tremendous amount of context here.  What exactly
> is the problem we're trying to solve, and what is the proposed solution?

First, a specific problem:

> It sounds like the idea is to be able to have component A inherit from
> component B, for component B to define some sort of shadow tree and for
> component A to also define a shadow tree that doesn't just override the one
> from B but instead interacts with it somehow, right?

Basically, yes. Here's an example:

We have a component x-icon-button. Its "markup API" is DOM like this:

<x-icon-button>
  <img class="icon" ...>
  (button text)
</x-icon-button>

It sets up Shadow DOM, you don't know the details, it is part of the
x-icon-button abstraction.

Now I want to define x-warning-button. Warning buttons are also x-icon-buttons
(maybe there is API for shrinking to a small version that just displays the
icon or something.)

I want to have markup like this:

<x-warning-button>
  Launch missiles
</x-warning-button>

My intent is that all warning buttons have the same warning icon. How do I do
it?

There are a few approaches you can take today, but all of them have problems,
for example:

1. Use composition: Put another x-icon-button inside the x-warning-button's
Shadow DOM. Don't use <shadow>. The major downside is that you have to forward
all of the API into the x-icon-button, and there are some things that are
basically impossible to forward, like styles.

2. Use cut-and-paste: Open up the x-icon-button and duplicate it into
x-warning-button. There might be a few bits of pure behavior you can share in a
behavior-only base type. The major downside is code duplication and less reuse.

You have an equivalent problem most times you have more than one ShadowRoot
attached to an element.

Let me use an analogy to explain this problem in general:

Today, the child nodes of a shadow host are like a set of global variables.
(The variable "names" are a selector which matches them.) Once you've used a
variable  the variable is "used up" (because a node can only be rendered once.)

The <shadow> element is like GOSUB. It "invokes" the next ShadowRoot whose
<content> elements might read from the set of variables.

What we're proposing is to make <shadow> like a function call; the next
ShadowRoot gets its own "parameter list" from the body of the <shadow>. It
doesn't chew on global variables any more.

Here's an example:

<button id="host">
  Launch missiles
</button>

#host's youngest ShadowRoot
{SR}
  <shadow>
    <img src="..." alt="Warning!" class="icon">
    <content></content>
  </shadow>
{/SR}

#host's older ShadowRoot
{SR}
  <div class="icon-container">
    <content select="img.icon"></content>
  </div>
  <content></content>
{/SR}

The youngest ShadowRoot is processed first and produces this intermediate tree:

<shadow>
  <img src="..." alt="Warning!" class="icon">
  Launch missiles
</shadow>

Then the <shadow> is processed, but instead of drawing from the host's child
list, it draws from the <shadow>'s distributed child list. So the final
flattened tree would be:

<button id="host">
  <div class="icon-container">
    <img src="..." alt="Warning!" class="icon">
  </div>
  Launch missiles
</button>

This example doesn't use Custom Elements, but you can see that the youngest
ShadowRoot is what an x-warning-button would create and the older ShadowRoot is
what an x-icon-button would create. The x-warning-button "calls" the
x-icon-button using reprojection under the <shadow> (of course it could use
<content> outside of the shadow too.)

Some things that DON'T change as a result of this proposal:

1. Nodes are still only output at most once.

2. The algorithm still distributes nodes to all of the <content> elements in
one ShadowRoot before "calling" the next oldest ShadowRoot, if any.

You can emulate the old semantics under this new proposal simply by writing

<shadow><content>Fallback content</content></shadow>

provided that all of your <content> selectors outside <shadow> precede it, so
they get distributed to first. (I believe there's a separate bug about doing
distribution in an author-controlled order, but that deserves a separate
discussion.)

This proposal is very flexible and powerful. You could imagine, for example,
the x-icon-button actually breaks its functionality up into two ShadowRoots.
The oldest one is a "collapser" that flips the Shadow DOM between two trees,
one that is just

<content select=".icon-container"></content>

for the collapsed state and another that grabs everything

<content></content>

for the expanded state. This kind of thing isn't possible today because a
ShadowRoot in the stack can't introduce anything new to the older ShadowRoot
(so, the x-icon-button cannot introduce .icon-holder to the collapser; the
x-warning-button cannot introduce the warning icon img to the x-icon-button;
etc.) and if you try compete to use something in the host child list (the
collapser might try to use the img directly, for example) it will have been
"used up" already by the younger one in the stack.

This will make versioning Custom Elements much easier: If x-icon-button gets
some embellishment in the future, but maintains backwards-compatible markup
API, my x-warning-button won't break, because it is just using that API. This
is in contrast to today where you have to be careful not to "touch" anything
that the older ShadowRoot is going to output.

FWIW I poked at this in Blink and frankly it will be a hill for us to climb.
But I think there is a valley on the other side and the code will ultimately be
cleaner because, as Scott says, fundamentally this is just reprojection at
work.

-- 
You are receiving this mail because:
You are the QA Contact for the bug.

Received on Friday, 14 June 2013 06:45:49 UTC