This specification describes a method of combining multiple DOM trees into one hierarchy and how these trees interact with each other within a document, thus enabling better composition of the DOM.

Conformance

All diagrams, examples, notes, are non-normative, as well as sections explicitly marked as non-normative. Everything else in this specification is normative.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in the normative parts of this document are to be interpreted as described in [[!RFC2119]]. For readability, these words do not appear in all uppercase letters in this specification.

To help with layering and to avoid circular dependencies between various parts of specification, this document consists of three consecutive narratives:

  1. setting up the stage for the specification,
  2. explaining of the conceptual model and algorithms behind it, and
  3. expressing this model with DOM interfaces and HTML elements.

In a sense, these parts can be viewed as math, which sets up the reasoning environment, physics, which is the theoretical reasoning about the concept, and mechanics, which is the practical application of this reasoning.

Any point, at which a conforming UA must make decisions about the state or reaction to the state of the conceptual model, is captured as algorithm. The algorithms are defined in terms of processing equivalence. The processing equivalence is a constraint imposed on the algorithm implementors, requiring the output of the both UA-implemented and the specified algorithm to be exactly the same for all inputs.

Concepts

Introduction

See the Shadow DOM 101 as a non-normative introduction.

Shadow trees

A document tree is a node tree [[!DOM]] whose root node is a document.

Any element can have an associated ordered list of zero or more node trees.

An element hosts a node tree if the node tree is a member of this associated list.

A shadow host is an element that hosts one or more node trees.

A shadow tree is a node tree hosted by a shadow host.

A shadow tree has an associated flag, called an encapsulation mode, which is either open or closed.

The behavior of a closed shadow tree is not well defined in this spec yet. The status is tracked in W3C bug 27775. Unless otherwise mentioned, an encapsulation mode of a shadow tree is open. The default-to-open is still under discussion and it may change. The status is tracked in W3C bug 20144.

A shadow root is the root node of a shadow tree.

If more than one shadow tree is hosted by the same shadow host, the more recently added shadow tree is called the younger shadow tree and the less recently added shadow tree is called the older shadow tree.

If there is no older shadow tree than a given shadow tree, the shadow tree is called the oldest shadow tree.

If there is no younger shadow tree than a given shadow tree, the shadow tree is called the youngest shadow tree.

The older shadow root is the root node of the older shadow tree.

The younger shadow root is the root node of the younger shadow tree.

The oldest shadow root is the root node of the oldest shadow tree.

The youngest shadow root is the root node of the youngest shadow tree.

A node A is called a child or hosted shadow root of a node B, if either A is a child of B or A is the root node of the shadow tree that B hosts.

A node A is called a deep descendant of a node B, if either A is a child or hosted shadow root of B or A is child or hosted shadow root of a node C that is a deep descendant of B.

An inclusive deep descendant is a node or one of its deep descendants.

When an element is an inclusive deep descendant of a document element, it is in a document deeply.

Trees of trees

A tree of trees is a tree of node trees.

The purpose of introducing a tree of trees here is to define algorithms easily in the following sections. This is a kind of a notation techchique to make the this specification simpler.

Just like a node tree is defined as a set of relationships between nodes, a tree of trees is similarly defined as a set of relationships between node trees:

The ownerDocument property of a node in a shadow tree must refers to the document of the shadow host which hosts the shadow tree.

Window object named properties [[!HTML]] must access the nodes in the document tree.

Example tree of trees

A tree of trees.

In the figure, there are six node trees, named A, B, C, D, E and F. The node trees, C, D and E, are hosted by the same shadow host, which participates in the node tree A. The node tree C is the oldest shadow tree. The node tree E is the youngest shadow tree. The following set of relationships holds in the figure:

As for a relationship between nodes, it's worth mentioning that there is no ancestor/descendant relationships between two nodes if they participate in different node trees. A shadow root is not a child node of the shadow host. The parent node of a shadow root doesn't exist. Because of this nature, most of existing APIs are scoped and don't affect other node trees, even though they are forming one tree of trees. For example, document.getElementById(elementId) never returns an element in a shadow tree, even when the element has the given elementId.

The same thing also applies to CSS Selectors matching. For example, a descendant combinator never descends into a node in a child shadow tree because a shadow root is not a child node of the shadow host. Unless a special CSS Selector for Shadow DOM, which is mentioned later, is used, a CSS Selector never matches an element in a different node tree.

Because ShadowRoot inherits DocumentFragment, as specified later, you can use ShadowRoot.getElementByID(elementId) to get a node in the shadow tree.

Composed trees

A composed tree is a node tree which is constructed out of nodes from multiple node trees in a tree of trees. The exact algorithm of constructing a composed tree is specified later.

A composed tree

If an element doesn't participate in a composed tree whose root node is a document, the element must not appear in the formating structure [[!CSS21]] nor create any CSS box. This behavior must not be overridden by setting the 'display' property.

In resolving CSS inheritance, an element must inherit from the parent node in the composed tree, if applicable.

The editor's draft of CSS Scoping specification [[css-scoping-1]] defines the selectors which are related to Shadow DOM. For those who would search this Shadow DOM specifaction with the name of these selectors, mentioning these selectores here:

  • :shadow pseudo element
  • /deep/ combinator, which was replaced with a >>> combinator (or shadow piecing descendant combinator)
  • ::content pseudo-element
  • :host-context() functional pseudo-class

Distributions

Insertion Points

An insertion point is a defined location where nodes in a different node tree appear instead of the nodes's original position when constructing a composed tree.

A distribution

A distribution is the mechanism that determines which nodes appear at each insertion point. The exact algorithm of a distribution is specified later.

Content Insertion Points

A content insertion point is an insertion point to where the child nodes of the shadow host are distributed. The content element that satisfies all of the following conditions represents a content insertion point:

Shadow Insertion Points

A shadow insertion point is an insertion point to where the children of the older shadow root are distributed. The shadow element that satisfies of the following conditions represents a shadow insertion point:

Distribution Results

Each tree of trees has the distribution result which describes the result of distributions. The distribution result must be equivalent of the following:

  1. Each insertion point has an ordered list, called distributed nodes, which consists of nodes which are distributed into the insertion point.
  2. Each node that is not an insertion point has an ordered list, called destination insertion points, which consists of insertion points to where the node is distributed

An insertion point A is the final destination of a node B if A is the last item of the destination insertion points of B.

When a node A is distributed into an insertion point B, the following steps must happen:

One case that deserves special consideration is the situation when an insertion point is a child node of another shadow host. In such situations, the nodes distributed into that insertion point appear as if they were child nodes of the shadow host in the context of distribution. Thus, the nodes distributed to a shadow tree could have already been distributed from its parent tree.

Despite being distributed to more than one insertion point, a node still only appears once in the composed tree at the final destination.

A re-distribution. In the figure, a node child 1 is distributed into insertion point 1. Then child1 is re-distributed into insertion point 3. The destination insertion points of child 1 is [insertion point 1, insertion point 3] and insertion point 3 is the final destination of child 1. The distributed nodes of insertion point 1 and insertion point 3 is [child 1] and [child 1, child 3], respectively.

Distribution Algorithms

The distribution algorithm must be used to determine the distribution result for a tree of trees and must be equivalent to processing the following steps:

Input
TREE-OF-TREES, a tree of trees
Output
The distribution result of TREE-OF-TREES is updated
  1. Let all distributed nodes and destination insertion points owned by nodes in TREE-OF-TREES be empty
  2. Let ROOT-TREE be the root tree of TREE-OF-TREES
  3. Run the distribution resolution algorithm with ROOT-TREE as input

The distribution resolution algorithm must be used to determine the distribution result for a given node tree and its descendant trees, and must be equivalent to processing the following steps:

Input
NODE-TREE, a node tree
Output
The distribution result is updated for the inclusive descendant trees of NODE-TREE
  1. For each shadow host, SHADOW-HOST, which participates in NODE-TREE, in tree order:
    1. Let POOL be the result of the pool population algorithm with SHADOW-HOST as input
    2. For each shadow tree, SHADOW-TREE, which SHADOW-HOST hosts, in order from the youngest shadow tree to the oldest shadow tree:
      1. Run the pool distribution algorithm with SHADOW-TREE and POOL as input
    3. For each shadow tree, SHADOW-TREE, that SHADOW-HOST hosts, in order from the oldest shadow tree to the youngest shadow tree:
      1. Let SHADOW be the shadow insertion point which participates in SHADOW-TREE
      2. If such a SHADOW exits:
        1. If SHADOW-TREE is not the oldest shadow tree:
          1. Let POOL be the result of the pool population algorithm with the root node of the older shadow tree relative to SHADOW-TREE as input.
        2. For each node, CHILD, in POOL
          1. Distribute CHILD into SHADOW
      3. Run the distribution resolution algorithm, recursively, with SHADOW-TREE as input

The algorithm implies that a shadow insertion point can be used in the oldest shadow tree, where a shadow insertion point behaves in a similar way to a content insertion point.

The pool population algorithm must be used to populate nodes from the child nodes of a given node and must be equivalent to processing the following steps:

Input
NODE, a node
Output
POOL, an ordered list of nodes
  1. Let POOL be an empty ordered list.
  2. For each child node, CHILD, of NODE:
    1. If CHILD is an insertion point:
      1. Add all nodes in the distributed nodes of CHILD to POOL
    2. Otherwise:
      1. Add CHILD to POOL

The pool distribution algorithm must be used to distribute nodes in a pool into the content insertion points in a shadow tree and must be equivalent to processing the following steps:

Input
SHADOW-TREE, a shadow tree
POOL, an ordered list of nodes
Output
Nodes in POOL are distributed into the content insertion points in the tree.
  1. For each content insertion point, CONTENT, which participates in SHADOW-TREE, in tree order:
    1. For each node, NODE, in POOL
      1. If NODE satisfies CONTENT's matching criteria:
        1. Distribute NODE into CONTENT
        2. Remove NODE from POOL
    2. If no nodes are distributed to CONTENT:
      1. For each child, CHILD, of CONTENT
        1. Distribute CHILD into CONTENT

If no nodes are distributed into a content insertion point CONTENT, the child nodes of CONTENT are distributed into CONTENT as fallback nodes.

If any condition which affects the distribution result changes, the distribution result must be updated before any use of the distribution result.

Satisfying Matching Criteria

The matching criteria for an insertion point is a set of compound selectors [[!SELECTORS4]]. These compound selectors are restricted to contain only these simple selectors:

A node satisfies a matching criteria only if:

  1. all compound selectors in the set, contain only the simple selectors specified above; and
  2. a node matches at least one compound selectors in the set or the set is empty.

Composition

The composed tree children calculation algorithm must be used to determine the child nodes of a node in the composed tree and must be equivalent to processing the following steps:

Input
NODE, a node which participates in a composed tree
Output
CHILDREN, the child nodes of NODE in the composed tree.
  1. Let CHILDREN be an empty ordered list of nodes
  2. If NODE is a shadow host:
    1. Let CHILD-POOL be the children of the youngest shadow root which NODE hosts.
  3. Otherwise:
    1. Let CHILD-POOL be the child nodes of NODE
  4. For each node, CHILD, in CHILD-POOL:
    1. If CHILD is an insertion point:
      1. For each node, DISTRIBUTED-NODE, in the distributed nodes of the insertion point CHILD:
        1. If CHILD is the final destination of DISTRIBUTED-NODE, add DISTRIBUTED-NODE to CHILDREN
    2. Otherwise:
      1. Add CHILD to CHILDREN

For a given tree of trees TREE-OF-TREES, the composed tree constructed from TREE-OF-TREES must be equivalent to the following tree:

Events

In each algorithm in this section, the Window must be considered as if it were the parent node of the Document so that the Window also receives an event.

When an event is dispatched in a shadow tree, its path either crosses the shadow trees or is terminated at the shadow root. One exception are the mutation events. The mutation event types must never be dispatched in a shadow tree.

Events that are Always Stopped

The following events must always be stopped at the youngest shadow root:

The exact algorithm is specified later.

It's not a good idea to have a fixed list of events. Alternative ideas are being discussed in W3C bug 20247.

Event Paths

The event path calculation algorithm must be used to determine event path and must be equivalent to processing the following steps:

Input
NODE, a node
EVENT, an event
Output
PATH, an event path, a ordered list of an event target
  1. Let PATH be the empty ordered list of nodes
  2. Let CURRENT be NODE
  3. Add CURRENT to PATH
  4. Repeat while CURRENT exists:
    1. If the destination insertion points of CURRENT is not empty:
      1. For each insertion point, INSERTION-POINT, in the destination insertion points of CURRENT:
        1. If INSERTION-POINT is a shadow insertion point:
          1. Let SHADOW-ROOT be the root node of INSERTION-POINT
          2. If SHADOW-ROOT is not the oldest shadow root:
            1. Add the older shadow root relative to SHADOW-ROOT to PATH
        2. Add INSERTION-POINT to PATH
      2. Let CURRENT be the final destination of CURRENT
    2. Otherwise:
      1. If CURRENT is a shadow root:
        1. Let SHADOW-HOST be the shadow host which hosts CURRENT
        2. If SHADOW-HOST hosts the node tree which NODE participates in and EVENT is one of the events which must be stopped:
          1. Stop this algorithm
        3. Let CURRENT be the shadow host which hosts CURRENT
        4. Add CURRENT to PATH
      2. Otherwise:
        1. Let CURRENT be the parent node of CURRENT
        2. If CURRENT exists:
          1. Add CURRENT to PATH
The algorithm above is under discussion at W3C bug 23887, and there are two candidate algorithms to resolve the issue discussed there.

Candidate 1:

  1. Let PATH be the empty ordered list of nodes
  2. Let CURRENT be NODE
  3. Let INSERTION-POINTS be an empty stack of nodes
  4. Add CURRENT to PATH
  5. Repeat while CURRENT exists:
    1. If the destination insertion points of CURRENT is not empty:
      1. Push the destination insertion points into INSERTION-POINTS in order of first destination to final destination
      2. Pop INSERTION-POINTS and set CURRENT to be the popped node
    2. Otherwise if CURRENT is a shadow root:
      1. Let SHADOW-HOST-OR-SHADOW-INSERTION-POINT be the shadow host which hosts CURRENT
      2. If SHADOW-HOST-OR-SHADOW-INSERTION-POINT hosts the node tree which NODE participates in and EVENT is one of the events which must be stopped:
        1. Stop this algorithm
      3. If CURRENT is not the youngest shadow root hosted by SHADOW-HOST-OR-SHADOW-INSERTION-POINT, and CURRENT has a first child, and the first child is distributed to a shadow insertion point:
        1. Let SHADOW-HOST-OR-SHADOW-INSERTION-POINT be the shadow insertion point in the younger shadow tree relative to the shadow tree whose root is CURRENT shadow root.
      4. If INSERTION-POINTS is not empty and if the most recent node in the INSERTION-POINTS is in the same node tree as SHADOW-HOST-OR-SHADOW-INSERTION-POINT:
        1. Pop INSERTION-POINTS and set CURRENT to be the popped node.
      5. Otherwise:
        1. Set CURRENT to SHADOW-HOST-OR-SHADOW-INSERTION-POINT and skip step 4
    3. Otherwise:
      1. Let CURRENT be the parent node of CURRENT.
    4. Repeat while CURRENT is a shadow insertion point and there is a shadow tree distributed to it:
      1. Let DISTRIBUTED-SHADOW-ROOT be the shadow root of the shadow tree distributed into CURRENT.
      2. If INSERTION-POINTS is not empty and if the most recent node in the INSERTION-POINTS is in the same node tree as DISTRIBUTED-SHADOW-ROOT:
        1. Pop INSERTION-POINTS and set CURRENT to be the popped node.
      3. Otherwise:
        1. Set CURRENT to DISTRIBUTED-SHADOW-ROOT.
    5. If CURRENT exists:
      1. Add CURRENT to PATH.

Candidate 2:

  1. Let PATH be the empty ordered list of nodes
  2. Let INSERTION-POINT-STACK be an empty stack of nodes
  3. Let CURRENT be NODE
  4. Repeat while CURRENT exists:
    1. Add CURRENT to PATH
    2. Let INSERTION-POINTS be the destination insertion points of CURRENT
    3. Exclude shadow insertion points whose containing shadow root has older shadow roots from INSERTION-POINTS
    4. If INSERTION-POINTS is not empty:
      1. Push INSERTION-POINTS into INSERTION-POINT-STACK in order of first destination to final destination
      2. Pop INSERTION-POINT-STACK and set CURRENT to be the popped node.
    5. Otherwise if CURRENT is a shadow root:
      1. Let SHADOW-HOST be the shadow host which hosts CURRENT
      2. If INSERTION-POINT-STACK is not empty and the most recent node in the INSERTION-POINT-STACK is in the same node tree as SHADOW-HOST
        1. Pop INSERTION-POINT-STACK and set CURRENT to be the popped node.
      3. Otherwise if CURRENT has a younger shadow root and it has a shadow insertion point:
        1. Let CURRENT be the shadow insertion point
      4. Otherwise if SHADOW-HOST hosts the node tree which NODE participates in and EVENT is one of the events which must be stopped:
        1. Stop this algorithm
      5. Otherwise:
        1. Let CURRENT be SHADOW-HOST
    6. Otherwise:
      1. Let CURRENT be the parent node of CURRENT

Event Paths Example

Suppose we have the following tree of trees:

An example tree of trees. Nodes which are not involved in the example event path, which is explained later, are omitted.

This tree of trees has the following seven node trees, one document tree and six shadow trees:

Let's assume that the distribution result of this tree of trees is:

In this case, if an event is dispatched on node D, the event path will be:

[D, C, I, M, L, P, R, Q, O, N, K, J, H, G, U, T, S, F, E, X, W, V, B, A] (Window is omitted)

With the proposed algorithm from Mozilla, this will be (from comment 115 in W3C bug 23887):

[D, C, M, L, R, Q, P, O, N, K, J, I, H, G, U, T, S, F, E, X, W, V, B, A]

Note that the event path calculation algorithm is designed to achieve the following goals:

  1. If there is a node, CHILD, in the event path and CHILD has a parent node, PARENT, in the node tree, the event path also includes PARENT. PARENT always appears somewhere after CHILD in the event path.
  2. Nodes in the event path form a linear ancestor chain in each node tree. There are no branch points in each node tree.
>
The relationship between an event path and node trees. In the figure, a number shown in a left-side of each node represents a zero-based position of each node in the event path. A parent node always have a larger number than that of its child node in each node tree.

That means if we focus on one node tree and forget all other node trees, the event path would be seen as if the event happened only on the node tree which we are focused on. This is an important aspect in a sense that hosting shadow trees doesn't have any effect to the event path within the node tree the shadow host participate in as long as the event is not stopped somewhere in the descendant trees.

For example, from the view of the document tree 1, the event path would be seen as [D, C, B, A]. From the view of the shadow tree 2, the event path would be seen as [I, H, G, F, E]. The similar things also apply to other node trees.

It is also worth pointing out that if we exclude all insertion points and shadow roots from an event path, the result would be equivalent to the inclusive ancestors of the node on which the event is dispatched, in the composed tree.

The relationship between an event path and the composed tree. The event path used in the example is shown in the left-hand side and the composed tree is shown in the right-hand side. If we exclude all insertion points and shadow roots from the event path, the result would be equivalent to the inclusive ancestors of the node, D, in the composed tree.

Event Retargeting

In the cases where event path is across multiple node trees, the event's information about the target of the event is adjusted in order to maintain encapsulation. Event retargeting is a process of computing relative targets for each ancestor of the node at which the event is dispatched. A relative target is a node that most accurately represents the target of a dispatched event at a given ancestor while maintaining the encapsulation.

The retargeting algorithm is used to determine relative targets, and it must be equivalent to processing the following steps:

Input
EVENT-PATH, an event path
CURRENT-TARGET, a node where the event listener is invoked.
Output
RELATIVE-TARGET, adjusted target
  1. Let CURRENT-TARGET-TREE be the node tree which CURRENT-TARGET participates in
  2. Let ORIGINAL-TARGET be the first item in EVENT-PATH
  3. Let ORIGINAL-TARGET-TREE be the node tree which ORIGINAL-TARGET participates in
  4. Let RELATIVE-TARGET-TREE be the lowest common inclusive ancestor tree of CURRENT-TARGET-TREE and ORIGINAL-TARGET-TREE
  5. Let RELATIVE-TARGET be the first node in EVENT-PATH which satisfies the following condition:
    1. The node participates in RELATIVE-TARGET-TREE

The retargeting process must occur prior to dispatch of an event.

Retargeting relatedTarget

Some events have a relatedTarget [[!DOM-Level-3-Events]] property, which holds a node that's not the event's target, but is related to the event.

For instance, a mouseover event's relatedTarget may hold the node from which the mouse has moved to event's target. In the case where relatedTarget is in a shadow tree, the conforming UAs must not leak its actual value outside of this tree. In cases where both relatedTarget and target are part of the same shadow tree, the conforming UAs must stop events at the shadow root to avoid the appearance of spurious mouseover and mouseout events firing from the same node.

Thus, if an event has a relatedTarget, its value and extent of event dispatch must be adjusted. In general:

  1. For a given node, the relatedTarget must be changed to its ancestor (or self) that is in the same shadow tree as the node
  2. Event listeners must not be invoked on a node for which the target and relatedTarget are the same.

The related target resolution algorithm must be used to determine the value of the relatedTarget property and must be equivalent to processing the following steps:

Input
EVENT, an event
CURRENT-TARGET, the node on which event listeners would be invoked
RELATED-TARGET, the related target for the event
Output
ADJUSTED-RELATED-TARGET, the adjusted related target for CURRENT-TARGET
  1. Let CURRENT-TARGET-TREE be the node tree which CURRENT-TARGET participates in
  2. Let RELATED-TARGET-TREE be the node tree which RELATED-TARGET participates in
  3. Let RELATED-TARGET-EVENT-PATH be the result of the event path calculation algorithm with RELATED-TARGET and EVENT as input
  4. If CURRENT-TARGET-TREE and RELATED-TARGET-TREE participate in the same tree of trees:
    1. Let LOWEST-COMMON-ANCESTOR-TREE be the lowest common inclusive ancestor tree of CURRENT-TARGET-TREE and RELATED-TARGET-TREE
  5. Otherwise:
    1. Let LOWEST-COMMON-ANCESTOR-TREE be the root tree of RELATED-TARGET-TREE
  6. For each inclusive ancestor tree, COMMON-ANCESTOR-TREE, of the LOWEST-COMMON-ANCESTOR-TREE, in ascending order:
    1. Let ADJUSTED-RELATED-TARGET be the first node in RELATED-TARGET-EVENT-PATH which satisfies the following condition:
      1. The node participates in COMMON-ANCESTOR-TREE
    2. If such a ADJUSTED-RELATED-TARGET exists:
      1. Stops this algorithm

The result of the related target resolution algorithm is not always null. Unless that, you should file a bug for this specification.

The relatedTarget retargeting process must occur prior to dispatch of an event.

Retargeting Touch Events

The Touch target [[!TOUCH-EVENTS]] attribute must be adjusted in the same way as an event with a relatedTarget. Each Touch target in the TouchList returned from TouchEvent touches(), changedTouches() and targetTouches() must be the result of related target resolution algorithm, given NODE and Touch target as arguments.

Retargeting Focus Events

The focus, DOMFocusIn, blur, and DOMFocusOut events must be treated in the same way as events with a relatedTarget, where the corresponding node that is losing focus as a result of target gaining focus or the node that is gaining focus, and thus causing the blurring of target acts as the related target.

Event Dispatch

At the time of event dispatch:

Upon completion of the event dispatch, the Event object's target and currentTarget must be to the highest ancestor's relative target. Since it is possible for a script to hold on to the Event object past the scope of event dispatch, this step is necessary to avoid revealing the nodes in shadow trees.

Event Retargeting Example

Suppose we have a user interface for a media controller, represented by this tree, composed of both document tree and the shadow trees. In this example, we will assume that selectors are allowed to cross the shadow boundaries and we will use these selectors to identify the elements. Also, we will invent a fictional shadow-root element to demarcate the shadow boundaries and represent shadow roots:

<div id="player">
    <shadow-root id="player-shadow-root">
        <div id="controls">
            <button id="play-button">PLAY</button>
            <input type="range" id="timeline">
                <shadow-root id="timeline-shadow-root">
                    <div id="slider-thumb" id="timeline-slider-thumb"></div>
                </shadow-root>
            </input>
            <div id="volume-slider-container">
                <input type="range" id="volume-slider">
                    <shadow-root id="volume-shadow-root">
                        <div id="slider-thumb" id="volume-slider-thumb"></div>
                    </shadow-root>
                </input>
            </div>
        </div>
    </shadow-root>
</div>
        

Let's have a user position their pointing device over the volume slider's thumb (#volume-slider-thumb), thus triggering a mouseover event on that node. For this event, let's pretend it has no associated relatedTarget.

Per the retargeting algorithm, we should have the following set of ancestors and relative targets:

Ancestor Relative Target
#player #player
#player-shadow-root #volume-slider
#controls #volume-slider
#volume-slider-container #volume-slider
#volume-slider #volume-slider
#volume-shadow-root #volume-slider-thumb
#volume-slider-thumb #volume-slider-thumb

After we dispatch the mouseover event using these newly computed relative targets, the user decides to move their pointing device over the thumb of the timeline (#timeline-slider-thumb). This triggers both a mouseout event for the volume slider thumb and the mouseover event for the timeline thumb.

Let's see how the relatedTarget value of the volume thumb's mouseout event is affected. For this event, the relatedTarget is the timeline thumb (#timeline-slider-thumb). Per the related target resolution algorithm, we should have the following set of ancestors and adjusted related targets:

Ancestor Relative Target Adjusted related Target
#player #player #player
#player-shadow-root #volume-slider #timeline
#controls #volume-slider #timeline
#volume-slider-container #volume-slider #timeline
#volume-slider #volume-slider #timeline
#volume-shadow-root #volume-slider-thumb #timeline
#volume-slider-thumb #volume-slider-thumb #timeline

The node, #player, has both target and relatedTarget being the same value (#player), which means that we do not dispatch the event on this node and its ancestors.

User Interaction

Ranges and Selections

Selection [[!EDITING]] is not defined. Implementation should do their best to do what's best for them. Here's one possible, admittedly naive way:

Since nodes which are in the different node trees never have the same root, there may never exist a valid DOM range that spans multiple node trees.

Accordingly, selections may only exist within one node tree, because they are defined by a single range. The selection, returned by the window.getSelection() method never returns a selection within a shadow tree.

The getSelection() method of the shadow root object returns the current selection in this shadow tree.

Focus Navigation

If a node doesn’t participate in the composed tree, the node must be skipped from the navigation order [[!CSS3UI]] sequence.

For sequential focus navigation, the navigation order sequence for a given shadow tree A must be inserted into the navigation order for other node tree as follow:

  1. If A is the youngest shadow tree:
    1. Let HOST be the shadow host which hosts A
    2. Let B be the node tree which HOST participates in
    3. The navigation order for A must be inserted into the navigation order for B:
      1. immediately after HOST, if HOST is focusable; or
      2. in place of the HOST as if HOST were assigned the value of auto for determining its position.
  2. Otherwise:
    1. Let B be the younger shadow tree relative to A
    2. Let SHADOW be the shadow insertion point in B
    3. If SHADOW exists, the navigation order for A must be inserted into the navigation order for B immediately after SHADOW as if SHADOW were assigned the value of auto for determining its position.

For directional focus navigation, it is up to the user agent to integrate the navigation orders for shadow trees into the document navigation order.

Active Element

To maintain encapsulation, the value of the Document object's focus API property activeElement must be adjusted. To prevent loss of information when adjusting this value, each shadow root must also have an activeElement property to store the value of the focused element in the shadow tree.

The active element adjustment algorithm is used to determine the value of the activeElement property, and it must be equivalent to processing the following steps:

Input
ELEMENT, the focused element
ROOT, either a document or a shadow root
Output
ADJUSTED, an adjusted activeElement property of ROOT.
  1. Let PATH be the result of the event path calculation algorithm with ELEMENT and null as input
  2. Let ADJUSTED be the result of the retargeting algorithm with PATH and ROOT as input

Editing

The value of the contenteditable attribute must not propagate from shadow host to its shadow trees.

Assistive Technology

User agents with assistive technology traverse the composed tree, and thus enable full use of WAI-ARIA [[!WAI-ARIA]] semantics in the shadow trees.

HTML Elements in Shadow Trees

Inertness of HTML Elements in a shadow tree

Comparatively, a shadow tree can be seen as somewhere between just part of a document and itself being a document fragment. Since it is rendered, a shadow tree aims to retain the traits of a typical tree in a document. At the same time, it is an encapsulation abstraction, so it has to avoid affecting the document tree. Thus, the HTML elements must behave as specified [[!HTML]] in the shadow trees, with a few exceptions.

According to the [[!HTML]], some HTML Elements would have different behavior if they participate in a shadow tree, instead of a document tree, because their definitions require the elements to be in a document as a necessary condition for them to work. In other words, they shouldn't work if they participate in a shadow tree, even when they are in a document deeply. We must fill this gap because we expect that most of HTML Elements behave in the same way as in a document, as long as they are in a document deeply. See W3C Bug 26365 and Bug 27406 for the details. The following is the tentative summary of the discussions in the W3C bugs. We, however, haven't covered all HTML Elements and their behaviors here yet. For HTML Elements which are not explicitly stated here, they should be considered as active in a shadow tree. We are trying to update [[!HTML]] itself, instead of having monkey patches here.

HTML Elements are classified into the following categories:

Attributes

When [[!HTML]] defines the processing algorithms to traverse trees for the following attributes, they must use the composed tree.

This list does not include attributes that are defined elsewhere in this specification. Such attributes include:

HTML Elements and Their Shadow Trees

Per specification, some HTML elements are designed to either not render their contents or have special requirements in regard to contents rendering. In order to reconcile these differences in rendering behavior with the composed tree, all HTML elements must have an equivalent of a closed shadow tree that is created and populated at the time of element instantiation. It is up to a user agent to define the content of these trees. However, all conforming user agents must satisfy the following requirements:

HTML Element Shadow Tree Requirements
img, iframe, embed, object, video, audio, canvas, map, input, textarea, progress, meter If the element can have fallback content, contains one content insertion point. The matching criteria value is the universal selector only when the element needs to show fallback content. Otherwise, contains no content insertion points or an content insertion point that matches nothing.
fieldset Contains two content insertion points with the following matching criteria:
  1. legend:first-of-type
  2. universal selector
details Contains two content insertion points with the following matching criteria:
  1. summary:first-of-type
  2. universal selector
All other elements Contains one content insertion point with the universal selector as the matching criteria

Elements and DOM interfaces

The ShadowRoot interface

The ShadowRoot interface represents the shadow root.

Selection? getSelection()

Returns the current selection in the shadow tree.

When invoked, it must return the selection in the shadow tree.

Element? elementFromPoint(double x, double y)

Returns an element at specified coordinates.

Eventually, this needs to be part of CSSOM View Module specification [[!CSSOM-VIEW]]. See also W3C bug 27829.

When invoked, it must return result of running the following steps:

  1. If context object is not a ShadowRoot instance, throw an InvalidNodeTypeError.
  2. If either argument is negative, x is greater than the viewport width excluding the size of a rendered scroll bar (if any), or if y is greater than the viewport height excluding the size of a rendered scroll bar (if any), return null.
  3. Let HIT be the element at coordinates x and y in the viewport, determined through hit testing
  4. Let PATH be the result of running the event path calculation algorithm with HIT and null as input
  5. Return the result of running the retargeting algorithm with PATH and context object as input
sequence<Element> elementsFromPoint(double x, double y)

It could be defined in roughly the same way as elementFromPoint. Eventually, this needs to be part of CSSOM View Module specification [[!CSSOM-VIEW]]. See also W3C bug 27829.

CaretPosition? caretPositionFromPoint(double x, double y)

It could be defined in roughly the same way as caretPositionFromPoint [[!CSSOM-VIEW]]. Eventually, this needs to be part of CSSOM View Module specification [[!CSSOM-VIEW]]. See also W3C bug 27829.

readonly attribute Element? activeElement

Represents the currently focused element in the shadow tree.

On getting, the attribute must return the currently focused element in the shadow tree or null, if there is none.

readonly attribute Element host

Represents the shadow host which hosts the context object.

On getting, the attribute must return the shadow host which hosts the context object.

readonly attribute ShadowRoot? olderShadowRoot

Represents the older shadow root relative to the context object

On getting, the attribute must return a result that is equivalent to running the following steps:

  1. If the context object is the oldest shadow root, return null.
  2. Return the older shadow root relative to the context object.

If the older shadow root is closed, the attribute must return null.

attribute DOMString innerHTML

Represents the markup of ShadowRoot's contents.

On getting, the attribute must return the result of running the HTML fragment serialization algorithm with the context object as shadow host.

On setting, these steps must be run:

  1. Let FRAGMENT be the result of invoking the fragment parsing algorithm [[!DOM-PARSING]] with the new value as MARKUP, and the context object as shadow host
  2. Replace all with FRAGMENT within the shadow root.
readonly attribute StyleSheetList styleSheets

Represents the shadow root style sheets.

On getting, the attribute must return a StyleSheetList sequence containing the shadow root style sheets.

The nodeType attribute of a ShadowRoot instance must return DOCUMENT_FRAGMENT_NODE. Accordingly, the nodeName attribute of a ShadowRoot instance must return "#document-fragment".

Invoking the cloneNode() method on a ShadowRoot instance must always throw a DATA_CLONE_ERR exception.

Extensions to Element Interface

ShadowRoot createShadowRoot()
When invoked, these steps must be run:
  1. Create a new instance of the ShadowRoot object, whose encapsulation mode is open.
  2. Add the ShadowRoot object to the ordered list of shadow roots associated with the context object as the youngest shadow root
  3. Return ShadowRoot object.
NodeList getDestinationInsertionPoints()
When invoked, the method must return a static NodeList consisting of insertion points in the destination insertion points of the context object, excluding an insertion point which participates in a closed shadow tree.
readonly attribute ShadowRoot? shadowRoot

Represents the youngest shadow root that context object hosts.

On getting, the attribute must return the youngest shadow root that context object hosts if there is and it is open. Otherwise must return null.

Extensions to Text Interface

NodeList getDestinationInsertionPoints()
When invoked, the method must return a static NodeList consisting of insertion points in the destination insertion points of the context object, excluding an insertion point which participates in a closed shadow tree.

The content element

The content element represents an insertion point in the shadow tree.

If a content element does not satisfy the condition of an insertion point, it must have the same rendering behavior as the HTMLUnknownElement.

Context
Where flow content is expected.
Content model
Transparent
Children
Anything as fallback content
Content attributes
Global attributes
select, a set of comma-separated tokens
Represents the matching criteria for distributing child nodes of the shadow host. Each token must be a compound selector.
DOM Interface
attribute DOMString select
Must reflect the select attribute.
NodeList getDistributedNodes()
When invoked, it must return result of running the following steps:
  1. If the context object is a content insertion point:
    1. Return a static NodeList consisting of nodes in the distributed nodes of the context object, but excluding a node which participates in a closed shadow tree.
  2. Otherwise:
    1. Return an empty static NodeList.

The shadow element

The shadow element represents an shadow insertion point in a shadow tree.

If a shadow element does not satisfy the condition of an insertion point, it must have the same rendering behavior as the HTMLUnknownElement.

Context
Where flow content is expected.
Content model
Transparent
Children
Anything
DOM Interface
NodeList getDistributedNodes()
When invoked, it must return result of running the following steps:
  1. If the context object is a shadow insertion point:
    1. Return a static NodeList consisting of nodes in the distributed nodes of the context object, but excluding a node which participates in a closed shadow tree.
  2. Otherwise:
    1. Return an empty static NodeList.

Extensions to Event Interface

readonly attribute object path

Represents the event path.

On getting, the attribute must create and return a new JavaScript Array object, that must be equivalent of the result of the event path calculation algorithm with the context object and the event target which is not adjusted by the retargeting algorithm as input if the event is being dispatched. This event path calculation algorithm must be done before capture phase. In other words, changes to the DOM while the event is dispatched must not affect the result. The same thing also applies to the case when the event dispatching is finished. If the event hasn't been dispatched, the attribute must create and return a new empty array.

Use Array as the return type of the path attribute in WebIDL. WebIDL bugs: Array subclassing and class, not interface.

Shadow DOM Example

Bob was asked to turn a simple list of links into a News Widget, which has links organized into two categories: breaking news and just news. The current document markup for the stories looks like this:

<ul class="stories">
    <li><a href="//example.com/stories/1">A story</a></li>
    <li><a href="//example.com/stories/2">Another story</a></li>
    <li class="breaking"><a href="//example.com/stories/3">Also a story</a></li>
    <li><a href="//example.com/stories/4">Yet another story</a></li>
    <li><a href="//example.com/stories/4">Awesome story</a></li>
    <li class="breaking"><a href="//example.com/stories/5">Horrible story</a></li>
</ul>
      

To organize the stories, Bob decides to use shadow DOM. Doing so will allow Bob to keep the document markup uncluttered, and harnessing the power of insertion point makes sorting stories by class name a very simple task. After getting another cup of Green Eye, he quickly mocks up the following shadow tree, to be hosted by the ul element:

<div class="breaking">
    <ul>
        <content select=".breaking"></content> <!-- insertion point for breaking news -->
    </ul>
</div>
<div class="other">
    <ul>
        <content></content> <!-- insertion point for the rest of the news -->
    </ul>
</div>
      

Bob then styles the newborn widget according to comps from the designer by adding this to the shadow tree mockup:

<style>
    div.breaking {
        color: Red;
        font-size: 20px;
        border: 1px dashed Purple;
    }
    div.other {
        padding: 2px 0 0 0;
        border: 1px solid Cyan;
    }
</style>
      

While pondering if his company should start looking for a new designer, Bob converts the mockup to code:

function createStoryGroup(className, contentSelector)
{
    var group = document.createElement('div');
    group.className = className;
    // Empty string in select attribute or absence thereof work the same, so no need for special handling.
    group.innerHTML = '<ul><content select="' + contentSelector + '"></content></ul>';
    return group;
}

function createStyle()
{
    var style = document.createElement('style');
    style.textContent = 'div.breaking { color: Red;font-size: 20px; border: 1px dashed Purple; }' +
        'div.other { padding: 2px 0 0 0; border: 1px solid Cyan; }';
    return style;
}

function makeShadowTree(storyList)
{
    var root = storyList.createShadowRoot();
    root.appendChild(createStyle());
    root.appendChild(createStoryGroup('breaking', '.breaking'));
    root.appendChild(createStoryGroup('other', ''));
}

document.addEventListener('DOMContentLoaded', function() {
    [].forEach.call(document.querySelectorAll('ul.stories'), makeShadowTree);
});
      

Well done, Bob! With the cup of coffee still half-full, the work is complete. Recognizing his awesomeness, Bob returns to teaching n00bs the ways of Puyo Puyo.

A few months pass.

It's election time. With Bob at his annual conference, Alice is charged with adding another, temporary box to the news widget, filled with election-related stories. Alice studies Bob's code, reads up on the shadow DOM spec and realizes that, thanks to multiple shadow tree support, she doesn't have to touch his code. As usual, her solution is elegant and simple, fitting neatly right under Bob's code:

// TODO(alice): BEGIN -- DELETE THIS CODE AFTER ELECTIONS ARE OVER.
var ELECTION_BOX_REMOVAL_DEADLINE = ...;

function createElectionStyle()
{
    var style = document.createElement('style');
    // TODO(alice): Check designer's desk for hallucinogens.
    style.textContent = 'div.election { color: Magenta; font-size: 24px; border: 2px dotted Fuchsia; }';
    return style;
}

function makeElectionShadowTree(storyList)
{
    var root = storyList.createShadowRoot();
    // Add and style election story box.
    root.appendChild(createElectionStyle());
    root.appendChild(createStoryGroup('election', '.election'));
    // Insert Bob's shadow tree under the election story box.
    root.appendChild(document.createElement('shadow'));
}

if (Date.now() < ELECTION_BOX_REMOVAL_DEADLINE) {
    document.addEventListener('DOMContentLoaded', function() {
        [].forEach.call(document.querySelectorAll('ul.stories'), makeElectionShadowTree);
    });
}
// TODO(alice): END -- DELETE THIS CODE AFTER ELECTIONS ARE OVER.
      

Using the shadow element allows Alice to compose Bob's widget inside of hers—without having to change a line of production code. Smiling to herself, Alice realizes that Bob may have come up with a way to keep the document markup clean, but she is the one who takes the cake for using shadow tree composition in such a cool way.

Acknowledgements

David Hyatt developed XBL 1.0, and Ian Hickson co-wrote XBL 2.0. These documents provided tremendous insight into the problem of functional encapsulation and greatly influenced this specification.

Alex Russell and his considerable forethought triggered a new wave of enthusiasm around the subject of shadow DOM and how it can be applied practically on the Web.

Dominic Cooney, Hajime Morrita, and Roland Steiner worked tirelessly to scope the problem of functional encapsulation within the confines of the Web platform and provided a solid foundation for this document.

The editor would also like to thank Alex Komoroske, Anne van Kesteren, Brandon Payton, Brian Kardell, Darin Fisher, Eric Bidelman, Deepak Sherveghar, Edward O'Connor, Elisée Maurer, Elliott Sprehn, Erik Arvidsson, Glenn Adams, Jonas Sicking, Koji Ishii, Malte Ubl, Mike Taylor, Oliver Nightingale, Olli Pettay, Rafael Weinstein, Richard Bradshaw, Ruud Steltenpool, Sam Dutton, Sergey G. Grekhov, Shinya Kawanaka, Tab Atkins, Takashi Sakamoto, and Yoshinori Sano for their comments and contributions to this specification.

This list is too short. There's a lot of work left to do. Please contribute by reviewing and filing bugs—and don't forget to ask the editor to add your name into this section.