Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C liability, trademark and document use rules apply.
This specification describes a method of establishing and maintaining functional boundaries between DOM subtrees and how these subtrees interact with each other within a document tree, thus enabling better functional encapsulation within DOM.
This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at http://www.w3.org/TR/.
This document was published by the Web Applications Working Group as a Working Draft. If you wish to make comments regarding this document, please send them to public-webapps@w3.org (subscribe, archives). All feedback is welcome.
Publication as a Working Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.
This document was produced by a group operating under the 5 February 2004 W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.
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:
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 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.
This document relies on the following specifications:
The following terms are used throughout the specification:
Web application developers often encounter the need to provide encapsulation of a DOM structure. Despite being part of one document tree, there are typically many functional fragments of DOM (or DOM subtrees), as well as assumptions about these fragments operating independently. This type of encapsulation is called functional encapsulation, as opposed to trust encapsulation, which deals with limiting information flow based on trust and ensuring security of data and state within an application.
Functional encapsulation is primarily concerned with establishing functional boundaries in a document tree. A functional boundary (or just boundary hereon) is a delineation of functional concerns between two loosely coupled units of functionality.
A Web application user interface is commonly composed of several user interface elements (or widgets), each a DOM subtree. In cases where a widget is tasked with hosting other widgets, the need arises for the widget to understand where its DOM subtree ends and another widget's DOM subtree begins.
This need for observing the functional boundaries in a document tree is even larger when a widget is operated on—added, moved, or removed in the document tree—by an outside actor, such as the Web application that consumes these widgets. Unless the widget consumer knows exactly how a widget's DOM structure is designed, it is impossible for the consumer to reasonably operate on the widget. A typical workaround has been providing alternative means of operation by the widget developer, which, in striving for API consistency quickly extrapolates into a complete set of widget-specific, DOM-like APIs.
To solve this problem at its core, a new abstraction is introduced. The shadow DOM allows multiple DOM subtrees (in addition to the document tree) to be composed into one larger tree when rendered. The existence of multiple DOM subtrees is enabled by letting any element in the document tree to host one or more additional DOM subtrees. These shadow DOM subtrees are governed by a set of rules that establish encapsulation boundaries while retaining the standard DOM composability semantics.
The encapsulation boundaries between shadow DOM subtrees are called shadow boundaries. The elements that host shadow DOM subtrees are called shadow hosts, and the root nodes of the shadow DOM subtrees are called shadow roots.
When rendered, the shadow DOM subtree takes place of the shadow host's content.
To enable composition of shadow host's children and the shadow DOM subtree, a notion of insertion points is added to the abstraction. An insertion point is a defined location in the shadow DOM subtree, to which the shadow host's children are transposed when rendering. The mechanism that determines which shadow host's children are transposed into which insertion points is called distribution.
Thus, the encapsulation of a shadow DOM subtree can be viewed as a two-fold problem:
To maintain the upper-boundary encapsulation, the following scoping constraints must apply to all nodes in a shadow DOM subtree:
ownerDocument
property refers to the document of the shadow hostNodeList
, HTMLCollection
, or DOMElementMap
instancesFor convenience, the shadow root provides its own set of DOM tree accessor methods. No nodes other than shadow root descendants are accessible with these methods.
The parentNode
and parentElement
attributes of the shadow root object must always return null
.
To maintain the lower-boundary encapsulation, the distribution of child nodes of the shadow host among the insertion points in the associated shadow DOM subtree must have the following traits:
An insertion point may be active or inactive. An active insertion point participates in the distribution process, whereas the inactive insertion does not. If not specifically set to be inactive, the insertion point must be considered active.
If an insertion point is not in a shadow DOM subtree, it must have the same rendering behavior as the HTMLUnknownElement
.
The distribution algorithm must produce an outcome that is equivalent of the outcome of processing these steps:
The matching criteria for insertion point is defined as a set of selector fragments. Each selector fragment is indeed a fragment in the selector (shadow-host)>(fragment)
, where (shadow-host)
is a selector that uniquely identifies the shadow host, and (fragment)
is the selector fragment.
A valid selector fragment may contain:
:link
:visited
:target
:enabled
:disabled
:checked
:indeterminate
:nth-child()
:nth-last-child()
:nth-of-type()
:nth-last-of-type()
:first-child
:last-child
:first-of-type
:last-of-type
:only-of-type
If any other types of selectors are present in the selector fragment, the fragment must be considered invalid.
A conforming UAs must consider a node as matching a set of selector fragments in the context of a given shadow host, if it:
Reference combinators match the children of a shadow host, distributed to the insertion points within a shadow DOM subtree. To match, all of these conditions must apply:
select
For example, .some-insertion-point /select/ div.special
will match all div
elements that have class
attribute set to special
and have been distributed to an insertion point that has a class attribute set to some-insertion-point
.
All other types of selectors must not cross the shadow boundary from a shadow DOM subtree to the tree of its shadow host.
A shadow host may host more than one shadow DOM subtree. In such cases, the subtrees are stacked in the order they were added the the host, starting with the subtree added most recently. This set of trees is called a tree stack. The more recently added subtree is called the younger tree, and the less recently added subtree is called the older tree. The most recently added subtree is called the youngest tree.
To facilitate composing multiple shadow subtrees of the same host, a special kind of insertion point is defined. The shadow insertion point designates a place in the shadow DOM subtree, where an older tree is inserted when rendering. If multiple shadow insertion points exist in a shadow DOM subtree, only the first, in tree order, is recognized. It is said that the shadow DOM subtree is assigned to a shadow insertion point if the shadow DOM subtree is rendered in place of this shadow insertion point.
Just like other insertion points, the shadow insertion points can be active or inactive.
The composition is performed with the tree composition algorithm, which must be equivalent to processing the following steps:
When an insertion point or a shadow insertion point has nothing assigned or distributed to them, the fallback content must be used instead when rendering. The fallback content is all descendants of the DOM element that represents the insertion point. The insertion points or shadow insertion points in fallback content must be considered inactive.
Any element in a shadow DOM subtree can be a shadow host, thus producing nested shadow DOM subtrees. A shadow DOM subtree is nested when its shadow host is itself a part of a shadow DOM subtree.
A shadow DOM subtree is enclosed by another shadow DOM subtree when the enclosing subtree nests the enclosed subtree through one or more levels of nesting.
Rendering of shadow DOM subtrees, or presenting them visually, is defined as a specialization of rendering any DOM subtree, and must happen as these steps:
This process of rendering produces a structure that is a composition of several DOM subtrees, including the document tree. The term "as rendered" is used to refer to this structure.
When an event is dispatched in a shadow DOM subtree, its path either crosses the shadow boundary or is terminated at the shadow boundary. One exception are the mutation events. The mutation event types must never be dispatched in a shadow DOM subtree.
For event path to cross the shadow boundary, it must be populated with adjusted parents, or nodes that appear as parents as rendered. The parent calculation algorithm is used to determine the adjusted parent of any given node and create the list of ancestors for event dispatch. This algorithm must be equivalent to processing the following steps:
In the cases where events cross the shadow boundaries, the event's information about the target of the event is adjusted in order to maintain upper boundary 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 DOM node that most accurately represents the target of a dispatched event at a given ancestor while maintaining the upper boundary encapsulation.
The retargeting algorithm is used to determine relative targets, and it must be equivalent to processing the following steps:
The retargeting process must occur prior to dispatch of an event.
relatedTarget
Some events have a relatedTarget
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 DOM subtree, the conforming UAs must not leak its actual value outside of this subtree. In cases where both relatedTarget
and target
are part of the same shadow DOM subtree, the conforming UAs must stop events at the shadow boundary 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:
relatedTarget
must be changed to its ancestor (or self) that is in the same shadow DOM subtree as the nodetarget
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:
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.
The following events must always be stopped at the nearest shadow boundary:
abort
error
select
change
load
reset
resize
scroll
selectstart
At the time of event dispatch:
Event
target
and currentTarget
attributes must return the relative target for the node on which event listeners are invokedUIEvent
relatedTarget
attribute must return the adjusted related targetrelatedTarget
and target
are the same for a given node, its the event listeners must not be invokedEvent
eventPhase attribute must return AT_TARGET if the relative target is same as the node on which event listeners are invokedUpon 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 DOM subtrees.
Suppose we have a user interface for a media controller, represented by this DOM tree, composed of both document and the shadow DOM subtrees. 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 class="play-button">PLAY</button>
<input type="range" id="timeline">
<shadow-root id="timeline-shadow-root">
<div class="slider-thumb" id="timeline-slider-thumb"></div>
</shadow-root>
</input>
<div class="volume-slider-container">
<input type="range" class="volume-slider">
<shadow-root id="volume-shadow-root">
<div class="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
.
Just before the event is dispatched, we perform retargeting:
#volume-slider-thumb
is not an insertion point, we push it onto STACK, and add the first tuple to TARGETS as (#volume-slider-thumb
, #volume-slider-thumb
)#volume-shadow-root
and proceed to second iteration, adding (#volume-slider-thumb
, #volume-shadow-root
) tuple to TARGETS#volume-shadow-root
is a shadow root, we pop STACK, thus emptying it#volume-slider
, and so we begin the third iteration#volume-slider
into it and add (#volume-slider
, #volume-slider
) to TARGETS#volume-slider
is #volume-slider-container
not a shadow root and not an insertion point, which dictates that we add (#volume-slider
, #volume-slider-container
) to TARGETS#controls
, again neither a shadow root nor an insertion point, which means we add (##volume-slider
, #controls
) to TARGETS#player-shadow-root
, a shadow root, and thus we add it (#volume-slider
, #player-shadow-root
) to TARGETS, then pop STACK, emptying it#player
, neither shadow root nor insertion point, and STACK is empty, so we push #player
to STACK, then add (#player
, #player
) to TARGETSAt the end of this process, 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 study 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 algorithm:
#volume-slider-thumb
and RELATED as #timeline-slider-thumb
,#timeline-slider-thumb
, and we push it into STACK#timeline-shadow-root
, a shadow root, so we pop STACK, emptying it#timeline
, we push it into STACK#controls
and after it, #player-shadow-root
, a shadow root, so we pop STACK, emptying it#player
, we push it into STACK#volume-shadow-root
#volume-slider
and#timeline
, the ANCESTOR and TARGET are in the same tree, and thus#timeline
as the adjusted relatedTarget
value.
Performing this computation with NODE as #player
yields the result of both target
and relatedTarget
being the same value (#player
), which means that we do not dispatch the event on this node and its ancestors.
Each shadow root has an associated list of zero or more style sheets, named shadow root style sheets. This is an ordered list that contains all style sheets, associated with the shadow root, in tree order.
When a shadow host has multiple shadow roots, the sorting order of CSS rules, defined in shadow roow style sheets, in ascending order of precedence must match the order of the tree stack, ending with the youngest tree.
To enforce upper-boundary encapsulation, CSS rules declared by the document must not apply in a shadow DOM subtree, unless the apply-author-styles flag is set for this subtree. The flag signals that document CSS rules are applicable in the shadow DOM subtree.
Even when the apply-author-styles is set, the selectors still must not cross the shadow boundary per scoping constraints. In other words, with apply-author-styles set, the document CSS rules only match wholly inside or outside of the shadow DOM subtree.
Conversely, to enforce lower-boundary encapsulation, CSS rules declared in a shadow root style sheets must not apply in the document tree, with two exceptions:
select
reference combinators and match elements in the document tree;@host
@-rule that matches a shadow host in a document tree.The rule applicability algorithm is used to determine whether any given rule is applicable to any DOM tree, and it must be equivalent to processing the following steps:
@host
@-rule or contains a select
reference combinator and RULE is declared in shadow root style sheets:
In a document that contains shadow DOM subtrees, the CSS properties must be inherited from parent nodes, produced using parent calculation algorithm. This requirement has the following effects:
If the reset-style-inheritance flag is set for a shadow DOM subtree, all inheritable CSS properties must behave as if they were explicitly set to the initial value at the upper boundary of the subtree.
If the reset-style-inheritance flag is set for an insertion point in a shadow DOM subtree, all inheritable CSS properties must behave as if they were explicitly set to the initial value at the lower boundary of the subtree.
The shadow host styles being inherited by the children of the shadow root must also apply to CSS Variables. This provides a way for document DOM tree styles to send signals into the shadow DOM subtrees, and for the shadow DOM subtrees to receive these signals with the use of the var()
function.
text-decoration
PropertyThe text decorations, specified by the text-decoration
property must not be propagated from shadow hosts to shadow DOM subtrees.
@host
@-ruleWithin styles, specified in shadow DOM subtrees, the author may use a @host
@-rule. The syntax of this rule is defined as this addition to CSS Grammar:
The following production is added to the grammar:
host
: HOST_SYM S* '{' S* ruleset* '}' S*
;
The following rule is added to the tokenizer:
@{H}{O}{S}{T} {return HOST_SYM;}
The declarations of the rules in a @host
@-rule must only be matched against the shadow host of the shadow DOM subtree in which the style is specified, but only if this shadow DOM subtree is rendered. When the shadow DOM subtree is not rendered (when it's an older tree that is not assigned to a shadow insertion point), the declarations must not be applied.
When a rule in @host
@-rule matches an element, it must have higher specificity than any selector, but lower specificity than declarations from a style
attribute.
Since a DOM node in a document tree and a DOM node in a shadow DOM subtree never have the same root, there may never exist a valid DOM range that spans either both a document tree and a shadow DOM subtree, or multiple shadow DOM subtrees.
Accordingly, selections may only exist within one tree, because they are defined by a single range. To maintain upper boundary encapsulation, the selection, returned by the window.getSelection()
method must never return a selection within a shadow DOM subtree.
The getSelection()
method of the shadow root object must return the current selection in this shadow DOM subtree.
Shadow DOM subtrees participate in sequential or directional focus navigation as part of the document tree. The navigation order within a shadow DOM subtree must be computed as a list of focusable elements in tree order as-rendered, with the exception of any elements, distributed its insertion points, and is called shadow DOM navigation order.
For sequential focus navigation, the shadow DOM navigation order sequence must be inserted into the document navigation order:
auto
for determining its position.For directional focus navigation, it is up to the user agent to integrate the shadow DOM navigation order into the document navigation order.
To maintain upper-boundary 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 DOM subtree.
The active element adjustment algorithm is used to determine the values of the respective activeElement properties, and it must be equivalent to processing the following steps:
The value of the contenteditable
attribute must not propagate from shadow host to its shadow DOM subtrees.
User agents with assistive technology traverse the document tree as rendered, and thus enable full use of of WAI-ARIA semantics in the shadow DOM subtrees.
Comparatively, a shadow DOM subtree can be seen as somewhere between just a DOM subtree in a document and a document fragment. Since it is rendered, a shadow DOM subtree aims to retain the traits of a typical DOM subtree in a document. At the same time, it is an encapsulation abstraction, so it has to avoid affecting the document DOM tree. Thus, the HTML elements must behave as specified in the shadow DOM subtrees, with a few exceptions.
A subset of HTML elements must behave as inert, or not part of the document tree. This is consistent how the HTML elements would behave in a document fragment. These elements are:
All other HTML elements in the shadow DOM subtrees must behave as if they were part of the document tree, with the scoping constraints of their respective subtrees applied.
The forms in HTML are scoped at the document level. That is, all form
elements and form-associated elements are accessible using the document DOM object's tree accessors. Per scoping constraints, this must exclude elements in the shadow DOM subtrees.
Instead, each shadow DOM subtree must scope its form
elements and form-associated elements. Because the form
's ownerDocument
is the shadow host's document, the form submission must continue to work as specified.
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 shadow DOM tree composition, all HTML elements must have an equivalent of a shadow DOM subtree that is created and populated at the time of element instantiation. It is up to a user agent to define the content of these subtrees. However, all conforming user agents must satisfy the following requirements:
HTML Element | Shadow DOM Subtree Requirements |
---|---|
img , iframe , embed , object , video , audio , canvas , map , input , textarea , progress , meter |
If the element that can have fallback content, contains one insertion point. The matching criteria value is universal selector only when the element needs to show fallback content. Otherwise, contains no insertion points or an insertion points that matches nothing. |
fieldset |
Contains two insertion points with the following matching criteria:
|
details |
Contains two insertion points with the following matching criteria:
|
All other elements | Contains one insertion point with the universal selector as the matching criteria |
ShadowRoot
ObjectThe ShadowRoot
object represents the shadow root.
[Constructor(in HTMLElement host) raises (DOMException)]
interface ShadowRoot : DocumentFragment {
HTMLElement getElementById(in DOMString elementId);
NodeList getElementsByClassName(in DOMString tagName);
NodeList getElementsByTagName(in DOMString className);
NodeList getElementsByTagNameNS(DOMString namespace, DOMString localName);
Selection getSelection();
CSSStyleSheet addStyleSheet(HTMLElement element) raises (DOMException);
CSSStyleSheet removeStyleSheet(CSSStyleSheet styleSheet) raises (DOMException);
attribute bool applyAuthorStyles;
attribute bool resetStyleInheritance;
readonly attribute Element activeElement;
attribute DOMString innerHTML;
readonly attribute StyleSheetList styleSheets;
}
ShadowRoot
Attributesfalse
(default value), the author styles are not applied to the shadow DOM subtree. If true
, the author styles are applied.resetStyleInheritance
of type bool
false
(default value), the properties continue to inherit. If true
, the properties are set to initial value.activeElement
of type Element
, readonlynull
, if there is none.innerHTML
of type DOMString
ShadowRoot
's contents.shadow host
.shadow host
styleSheets
of type StyleSheetList
, readonlyStyleSheetList
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"
.
ShadowRoot
Methodsconstructor
Parameter | Type | Nullable | Optional | Description |
---|---|---|---|---|
element |
Element |
No | No | Element that will be the shadow host of the newly created shadow DOM subtree. |
HIERARCHY_REQUEST_ERR
element
parameter is not a valid Element
.
When invoked, the ShadowRoot()
constructor must run these steps:
ShadowRoot
objectelement
argument as the shadow host for the ShadowRoot
objectShadowRoot
objectgetElementById
getElementsByClassName
getElementsByTagName
getElementsByTagNameNS
getSelection
addStyleSheet
link
header with the exception that the newly created style sheet must be appended to the shadow root style sheets, not document style sheetsTypeMismatchError
exception.removeStyleSheet
addStyleSheet
.addStyleSheet
:
NoModificationAllowedError
exception.Invoking the cloneNode()
method on a ShadowRoot
instance must always throw a DATA_CLONE_ERR
exception.
content
HTML elementThe content
HTML element represents an insertion point in the shadow DOM subtree.
select
, a set of comma-separated tokensselect
attribute is considered invalid.
reset-style-inheritance
, a boolean attribute
interface HTMLContentElement : HTMLElement {
attribute DOMString select;
attribute boolean resetStyleInheritance;
}
select
of type DOMString
resetStyleInhertiance
of type boolean
shadow
HTML elementThe shadow
HTML element represents an shadow insertion point in a shadow DOM subtree.
reset-style-inheritance
, a boolean attribute
interface HTMLShadowElement : HTMLElement {
attribute boolean resetStyleInheritance;
}
resetStyleInhertiance
of type boolean
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 DOM subtree, 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 DOM subtree 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.scoped = true;
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 makeShadowSubtree(storyList)
{
var root = new ShadowRoot(storyList);
root.appendChild(createStyle());
root.appendChild(createStoryGroup('breaking', '.breaking'));
root.appendChild(createStoryGroup('other', ''));
}
document.addEventListener('DOMContentLoaded', function() {
[].forEach.call(document.querySelectorAll('ul.stories'), makeShadowSubtree);
});
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 WoW.
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 DOM subtree 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');
style.scoped = true;
// TODO(alice): Check designer's desk for hallucinogens.
style.textContent = 'div.election { color: Magenta;font-size: 24px; border: 2px dotted Fuchsia; }';
return style;
}
function makeElectionShadowSubtree(storyList)
{
var root = new ShadowRoot(storyList);
// 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'), makeElectionShadowSubtree);
});
}
// 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 DOM subtree composition in such a cool way.
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, Brian Kardell, Darin Fisher, Deepak Sherveghar, Edward O'Connor, Elisée Maurer, Erik Arvidsson, Glenn Adams, Hayato Ito, Jonas Sicking, Malte Ubl, Oliver Nightingale, Olli Pettay, Rafael Weinstein, Richard Bradshaw, Sam Dutton, Shinya Kawanaka, Tab Atkins, and Takashi Sakamoto 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.