Version 1 (modified by Dimitri Glazkov, 12 years ago) (diff)


A ShadowTree is an extra thing that is attached to a DOM element. The ShadowTree is the stack of ShadowRoot elements; it has a list of ShadowRoots, a HTMLContentSelector.

Issue 1: HTMLContentSelector has a doubly linked list of RefPtr references to nodes--this needs to be fixed.

Each ShadowRoot is a document fragment because it is behaviorally similar to a document fragment. When you operate on it in DOM, you aren’t able to copy it. If you try to appendChild(shadowRoot), appendChild will steal its children but not disconnect the ShadowRoot from his host, etc.

apply-author-sheets: This controls whether stylesheets from the document apply to elements in the shadow.

When you look at the element and see how it operates on the shadow tree, it passes pretty much all of the style recalc things to it if you look at attach, and the shadow tree passes that on to the ShadowRoots in the stack.

Terminology--oldest and youngest ShadowRoots. Younger shadow roots are ones that were created later; they are towards the top of the stack. User-agent shadows are always the oldest, bottom-of-the-stack shadows.

There are two special nodes, HTMLContentElement and HTMLShadowElement, which are subtypes of InsertionPoint. These are special in Shadow DOM--see the spec--a thing that doesn't render but stuff goes into it. The InsertionPoint has a list of nodes that are distributed to the insertion point.

The InsertionPoint takes the children of the host and, using the a CSS selector, lays claim to light children of a node to populate this list.

(??? The CSS selector for <content> is actually a member of InsertionPoint. This is to simulate user-agent shadows for all elements. ???)

HTMLShadowElement is an InsertionPoint that is a reference to an older tree in the stack. There are pointers from the ShadowRoot to the HTMLShadowElement that includes it. We need these back pointers to walk nodes in the flattened tree -- we never explicitly construct the flattened tree, just leave enough breadcrumbs in the tree to construct it implicitly.

There's a thing called NodeRenderingContext which knows how to find the renderer that is the renderer of the node that is effectively the parent of a node in a flattened tree. For example, <content> and <shadow> are "erased" by the flattened tree, so if your nextSibling in the DOM is a <content>, NodeRenderingContext knows to recurse into what nodes it has selected. Since flattened tree construction is implicit, this is effected in how the renderers are wired up. Hence NodeRenderingContext::parentNodeForRenderingAndStyle.

Issues: NodeRenderingContext is a beast with phases, etc. etc. We like having all of this indirection logic in one class, just the class needs to be simplified.

Issues: Distribution should be separated from attachment.

With Shadow DOM suddenly you have an added level of complexity on node attachment and detachment, computing styles, etc.

Q? How do we calculate the insertion point for a node?

In InsertionPoint::attach, ShadowTree starts with the youngest ShadowRoot and distributes light children into its <content> insertion points and the next oldest ShadowRoot into its <shadow> insertion point (if any.) If the next oldest ShadowRoot was distributed into this ShadowRoot, assignment iterates and and does assignment (and attach) in the next oldest ShadowRoot on the stack.

HTMLContentSelector has the logic to populate itself with light children if they match a CSS selector.

First it collects all of the host children to distribute later. Then each <content> is traversed and candidates are drawn from the set.

HTMLContentSelector has a "phase" which is to prevent reentrancy. If a ShadowTree subtree is reattached, you don’t want to tickle the distribution in reattach. tree->selector().isSelecting().

HTMLContentSelector is basically the table mapping insertion point to the nodes distributed to it.

Bug: Rename ShadowTree to ShadowRootStack.

Bug: Switch from HTMLContentSelector phase to a flag as the reentrancy guard.

(TODO: UML diagram)

Bug: Remove the HTMLContent* (eg SelectionList, etc.) naming convention, because HTMLX is for HTML element X, and we already have HTMLContentElement. So let’s call it Content*.

Bug: Rename HTMLContentSelection to something. "Selection" is bad because it is like click-and-drag selection.

Bug: Can HTMLContentSelection be just a list of nodes in the insertion point, and get rid of HTMLContentSelection?

We need the table to find the rendering parent of the distributed node.

Could consider a design where a ShadowRoot has a bit whether it has been included with <shadow> or not, but not a pointer to the specific <shadow> element itself, and have each ShadowRoot point to its first docorder <shadow>.

ComposedShadowTreeWalker -- this class gives you a facility to walk the DOM as it is viewed as a result of the composition of this tree, kind of as the user sitting in front of the browser sees it: firstChild, lastChild, nextSibling, next (in tree order) etc. If you want to access the tree "as it all looks as it is composed", use ComposedTreeWalker. This is useful for event dispatch and things like that. For example FocusController uses this because it needs to walk the composed tree.

TreeScope -- Document is a tree scope, but you can have subtrees that are in separate tree scopes. It is a tree overlaid on the document tree. Shadow DOM subtrees are in their own TreeScope and it is used to control how style (CSSStyleSelector ... treeScope->applyAuthorSheets()) is applied in Shadow DOM.

Replaced Elements and Shadow DOM

Suppose you have an <img> element or a <video> element and you create a ShadowRoot for it. What the hell happens? ShadowRoot implies that there is content and it messes with (replaces) the content. But replaced elements are RenderReplaced and don’t render their content like a <div>, etc.

But what if you want to have an <img> and use Shadow DOM to give it a polaroid frame?

Conceptually this translates into the <img> node being built like this:

img + {SR} <<user-agent shadow root>>

+ {image surface}

Essentially what you have is an img node and then it has a Shadow Root built-in (user-agent ShadowRoot) and it has something magic called an image surface which really renders the image.

So if you have this:

img + {SR} <<author shadow root>>

+ div

+ div

+ <shadow>

+ {SR} <<user-agent shadow root>>

+ {image surface}

then you can achieve your Polaroid border.

But today RenderReplaced is a completely black box, so we’re looking for ideas about how to achieve this {image surface} thing.

For form elements we just converted them to use Shadow DOM. With the replaced elements like video and image... video is an interesting case because it has some surface which is part of its render replaced identity.

RenderMedia m_children has one element in there. It lays out the image to get the coordinates and then it lays out the controls.

rniwa wanted to add :before and :after to RenderReplaced, so this might be useful discussion for him.

We could shim RenderReplaceds that have Shadow DOM and keep the original RenderObject for use at <shadow>.

This applies to: iframe, object, embed, img, video, audio, etc.

One radical idea of dglazkov's: We create a different renderer depending on whether it has a ShadowRoot or not. In createRenderer we ask, do you have a ShadowRoot attached, and create a RenderBlock if it does. We still have the same exact RenderMedia, RenderImage, etc. that will set its size the right way and style will just work correctly. Let's try it and see. shinyak thinks it will work.

Events and Shadow DOM

Event retargeting is really really hard. hayato has a document ("Events in Shadow DOM") with ideas on how to improve it. It has to do with the retargeting of related target eevents. Suppose we are in the tree somewhere, moving a mouse from node X to node Y.

o + o

+ Y <<mouseover>>

+ o

+ X <<mouseout>>

the target for mouseout will be X and related target will by Y. The target for mouseover will be Y and related target will be X.

When you get into Shadow DOM this gets really complex. The code is simple but it is conceptually hard. It is OK if your nodes are in the same tree, but what if they’re in a different tree? For example:

o + {SR}

+ Y

+ X

you don't want to leak node Y. So you have to retarget it to o.

If you have

a + {SR}

+ Y + X

+ o

when you listen on a and retarget the events for X and Y to a, and the event bubbles, you have to not let the event escape. Otherwise you get nonsense like mouseout from a to a and mouseover form a to a.

When you have multiple shadow trees things get really complicated.

Event war story: The first implementation of the media controls generated a spew of mouseover mouseout as the mouse moved over the volume button to the volume slider and they were fired out of the video element, with mouseover video from video to video and mouseout of video from video to video etc.

Events are basically dispatched "as rendered."