Dimitri Glazkov

Web and About

Posts Tagged ‘webkit

What the Heck is Shadow DOM?

with 100 comments

If you build Web sites, you probably use Javascript libraries. If so, you are probably grateful to the nameless heroes who make these libraries not suck.

One common problem these brave soldiers of the Web have to face is encapsulation. You know, one of them turtles on which the Object-Oriented Programming foundation sits, upon which stands most of the modern software engineering. How do you create that boundary between the code that you wrote and the code that will consume it?

With the exception of SVG (more on that later), today’s Web platform offers only one built-in mechanism to isolate one chunk of code from another — and it ain’t pretty. Yup, I am talking about iframes. For most encapsulation needs, frames are too heavy and restrictive.

What do you mean I must put each of my custom buttons in a separate iframe? What kind of insane are you?

So we need something better. Turns out, most browsers have been sneakily employing a powerful technique to hide their gory implementation details. This technique is called the shadow DOM.

My name is DOM, Shadow DOM

Shadow DOM refers to the ability of the browser to include a subtree of DOM elements into the rendering of a document, but not into the main document DOM tree. Consider a simple slider:

<input id="foo" type="range">
 

Pop this code into any WebKit-powered browser, and it’ll appear like so:

Typical Slider Control on WebKit

Simple enough. There’s a slider track and there’s a thumb, which you can slide along the track.

Wait, what? There’s a separate movable element inside of the input element? How come I can’t see it from Javascript?

var slider = document.getElementsById("foo");
console.log(slider.firstChild); // returns null
 

Is this some sort of magic?

No magic, my fair person of the Web. Just shadow DOM in action. You see, browser developers realized that coding the appearance and behavior of HTML elements completely by hand is a) hard and b) silly. So they sort of cheated.

They created a boundary between what you, the Web developer can reach and what’s considered implementation details, thus inaccessible to you. The browser however, can traipse across this boundary at will. With this boundary in place, they were able to build all HTML elements using the same good-old Web technologies, out of the divs and spans just like you would.

Some of these are simple, like the slider above. Some get pretty complex. Check out the video element. It’s got trigger buttons, timelines, a hover-appearing volume control, you name it:

WebKit Video Element With Controls

All of this is just HTML and CSS — hidden inside of a shadow DOM subtree.

To borrow a verse from that magnetic meme duo, “how does it work?” To build a better mental model, let’s pretend we have a way to poke at it with Javascript. Given this simple page:

<html>
<head>
<style> p { color: Green; } </style>
</head>
<body>
<p>My Future is so bright</p>
<div id="foo"></div>
<script>
    var foo = document.getElementById('foo');
    // WARNING: Pseudocode, not a real API.
    foo.shadow = document.createElement('p');
    foo.shadow.textContent = 'I gotta wear shades';
</script>
</body>
</html>
 

We get the DOM tree like this:

<p>My Future is so bright</p>
<div id="foo"></div>
 

But it is rendered as if it were this:

<p>My Future is so bright</p>
<div id="foo"> <!-- shadow subtree begins -->
    <p>I gotta wear shades</p>
</div> <!-- shadow subtree ends -->
 

Or visually like so:

Shadow DOM Example

Notice how the second part of the rendered sentence is not green? That’s because the p selector I have in my document can’t reach into the shadow DOM. How cool is that?! What would a framework developer give to have powers like this? The ability to write your widget and not worry about some random selector fiddling with your style seems … downright intoxicating.

Course of Events

To keep things natural, events fired in shadow DOM subtree can be listened to in the document. For instance, if you click on the mute button in the audio element, your event listeners on an enclosing div would hear the click:

<div onclick="alert('who dat?')">
    <audio controls src="test.wav"></audio>
</div>
 

However, if you ask to identify who fired the event, you’ll find out it was the audio element itself, not some button inside of it.

<div onclick="alert('fired by:' + event.target)">
    <audio controls src="test.wav"></audio>
</div>
 

Why? Because when crossing the shadow DOM boundary, the events are re-targeted to avoid exposing things inside of the shadow subtree. This way, you get to hear the events, fired from the shadow DOM, and the implementor gets to keep their details hidden from you.

Reaching into Shadows with CSS

One other trick up the sleeve is the ability to control how and whether CSS reaches into the shadow subtree. Suppose I want to customize the look of my slider. Instead of the standard OS-specific appearance, I want it be stylish, like so:

input[type=range].custom {
    -webkit-appearance: none;
    background-color: Red;
    width: 200px;
}
 

The result I get is:

Slider with a custom-styled track

Ok, that’s nice, but how do I style the thumb? We already determined the that our usual selectors don’t go into the shadow DOM tree. Turns out, there’s a handy pseudo attribute capability, which allows shadow DOM subtrees to associate an arbitrary pseudo-element identifier with an element in the subtree. For example, the thumb in the WebKit slider can be reached at:

input[type=range].custom::-webkit-slider-thumb {
    -webkit-appearance: none;
    background-color: Green;
    opacity: 0.5;
    width: 10px;
    height: 40px;
}
 

Which gives us:

Fully custom-styled slider

Ain’t it great? Think about it. You can style elements in the shadow DOM without actually being able to access them. And the builder of the shadow DOM subtree gets to decide which specific parts of their tree can be styled. Don’t you wish you had powers like this when building your UI widget toolkit?

Shadows with Holes, How’s that for a Mind-bender?

Speaking of awesome powers, what happens when you add a child to an element with a shadow DOM subtree? Let’s experiment:

// Create an element with a shadow DOM subtree.
var input = document.body.appendChild(document.createElement('input'));
// Add a child to it.
var test = input.appendChild(document.createElement('p'));
// .. with some text.
test.textContent = 'Team Edward';
 

Displaying as:

Input Element and Nothing Else

Whoa. Welcome to the twilight DOM — a chunk of document that’s accessible by traversal but not rendered on the page. Is it useful? Not very. But it’s there for you, if you need it. Teens seem to like it.

But what if we did have the ability to show element’s children as part of its shadow DOM subtree? Think of the shadow DOM as a template with a hole, through which the element’s children peek:

// WARNING: Pseudocode, not a real API.
var element = document.getElementById('element');
// Create a shadow subtree.
element.shadow = document.createElement('div');
element.shadow.innerHTML = '<h1>Think of the Children</h1>' +
    <div class="children">{{children-go-here}}</div>';
// Now add some children.
var test = element.appendChild(document.createElement('p'));
test.textContent = 'I see the light!';
 

As a result, if you traverse the DOM you will see this:

<div id="element">
    <p>I see the light</p>
</div>
 

But it will render like this:

<div id="element">
    <div> <!-- shadow tree begins -->
        <h1>Think of the Children</h1>
        <div class="children"> <!-- shadow tree hole begins -->
            <p>I see the light</p>
        </div> <!-- shadow tree hole ends -->
    </div> <!-- shadow tree ends --> 
</div>
 

As you add children to element, they act as normal children if you look at them from the DOM, but rendering-wise, they are teleported into a hole in the shadow DOM subtree.

This is the point where you admit that this is pretty cool and start asking:

When can I have it in my browser?

Homework Assignment

Did you think you’d read through all this preaching and get away without homework? As a Javascript library or framework developer, try to think of all the different great things having shadow DOM would allow you to do. Then think of specific use cases (actual/pseudo code a plus) of where shadow DOM could be applied. To help you get your thinking groove going, here is current list of use cases.

Finally. share your use cases on public-webapps mailing list. The discussion about adding these capabilities to the Web platform is under way and your help is needed.

If you aren’t a much a framework writer, you can still participate — by cheering for the shadow DOM and spreading the joy on your favorite social networking site. Because joy is what’s it’s all about.

PS. SVG and Shadow DOM

Almost forgot. Believe it or not, SVG has actually had shadow DOM since the beginning. The trouble is, its shadow DOM is very… shady. No, that’s not it. There’s another qualifier that also begins with “sh” and ends with a “y”. Yeah, that one. I could go on, but trust me on this. Or read the spec.

Written by Dimitri Glazkov

January 14, 2011 at 4:40 pm

Posted in WebKit

Tagged with , , ,

Follow

Get every new post delivered to your Inbox.

Join 30 other followers