<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[190214] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/190214">190214</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2015-09-24 12:00:11 -0700 (Thu, 24 Sep 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>Make event dispatching respect slotting
https://bugs.webkit.org/show_bug.cgi?id=149243

Reviewed by Antti Koivisto.

Source/WebCore:

There are primarily two cases to consider: getting out of a shadow root to its host,
and moving into a slot from an assigned node.

When getting out of a shadow root, either the event originated in its shadow tree
including its nodes' shadow trees (1) or it moved into this shadow tree via a slot (2).
In (1), event.target should be set to the shadow host. In (2), it should be set to the
first node in the event path that belongs to the same tree as the shadow host. In order
to find such a node in O(1), we use a stack of event targets in each (shadow) tree. We
push event.target of the current tree whenever we move up to a slot from an assigned node
and pop it out of the stack when we move out of the shadow tree.

A follow up patch is needed to update the code to resolve related targets.

Tests: fast/shadow-dom/event-inside-shadow-tree.html
       fast/shadow-dom/event-inside-slotted-node.html

* dom/EventDispatcher.cpp:
(WebCore::EventPath::EventPath):

LayoutTests:

Added tests for dispatching events inside shadow trees and nodes assigned to slots using testharness.js.

* fast/shadow-dom/event-inside-shadow-tree-expected.txt: Added.
* fast/shadow-dom/event-inside-shadow-tree.html: Added.
* fast/shadow-dom/event-inside-slotted-node-expected.txt: Added.
* fast/shadow-dom/event-inside-slotted-node.html: Added.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoredomEventDispatchercpp">trunk/Source/WebCore/dom/EventDispatcher.cpp</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsfastshadowdomeventinsideshadowtreeexpectedtxt">trunk/LayoutTests/fast/shadow-dom/event-inside-shadow-tree-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastshadowdomeventinsideshadowtreehtml">trunk/LayoutTests/fast/shadow-dom/event-inside-shadow-tree.html</a></li>
<li><a href="#trunkLayoutTestsfastshadowdomeventinsideslottednodeexpectedtxt">trunk/LayoutTests/fast/shadow-dom/event-inside-slotted-node-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastshadowdomeventinsideslottednodehtml">trunk/LayoutTests/fast/shadow-dom/event-inside-slotted-node.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (190213 => 190214)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2015-09-24 18:38:35 UTC (rev 190213)
+++ trunk/LayoutTests/ChangeLog        2015-09-24 19:00:11 UTC (rev 190214)
</span><span class="lines">@@ -1,3 +1,17 @@
</span><ins>+2015-09-24  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        Make event dispatching respect slotting
+        https://bugs.webkit.org/show_bug.cgi?id=149243
+
+        Reviewed by Antti Koivisto.
+
+        Added tests for dispatching events inside shadow trees and nodes assigned to slots using testharness.js.
+
+        * fast/shadow-dom/event-inside-shadow-tree-expected.txt: Added.
+        * fast/shadow-dom/event-inside-shadow-tree.html: Added.
+        * fast/shadow-dom/event-inside-slotted-node-expected.txt: Added.
+        * fast/shadow-dom/event-inside-slotted-node.html: Added.
+
</ins><span class="cx"> 2015-09-24  David Hyatt  &lt;hyatt@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Add support for CSS Custom Properties (in preparation for implementing CSS Variables).
</span></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomeventinsideshadowtreeexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/event-inside-shadow-tree-expected.txt (0 => 190214)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/shadow-dom/event-inside-shadow-tree-expected.txt                                (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/event-inside-shadow-tree-expected.txt        2015-09-24 19:00:11 UTC (rev 190214)
</span><span class="lines">@@ -0,0 +1,14 @@
</span><ins>+
+PASS Firing an event inside a grand child of a detached open mode shadow tree 
+PASS Firing an event inside a grand child of a detached closed mode shadow tree 
+PASS Firing an event inside a grand child of an in-document open mode shadow tree 
+PASS Firing an event inside a grand child of an in-document closed mode shadow tree 
+PASS Firing an event inside a detached open mode shadow tree inside open mode shadow tree 
+PASS Firing an event inside a detached open mode shadow tree inside closed mode shadow tree 
+PASS Firing an event inside a detached closed mode shadow tree inside open mode shadow tree 
+PASS Firing an event inside a detached closed mode shadow tree inside closed mode shadow tree 
+PASS Firing an event inside an in-document open mode shadow tree inside open mode shadow tree 
+PASS Firing an event inside an in-document open mode shadow tree inside closed mode shadow tree 
+PASS Firing an event inside an in-document closed mode shadow tree inside open mode shadow tree 
+PASS Firing an event inside an in-document closed mode shadow tree inside closed mode shadow tree 
+
</ins></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomeventinsideshadowtreehtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/event-inside-shadow-tree.html (0 => 190214)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/shadow-dom/event-inside-shadow-tree.html                                (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/event-inside-shadow-tree.html        2015-09-24 19:00:11 UTC (rev 190214)
</span><span class="lines">@@ -0,0 +1,149 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+&lt;title&gt;Shadow DOM: Firing an event inside a shadow tree&lt;/title&gt;
+&lt;meta name=&quot;author&quot; title=&quot;Ryosuke Niwa&quot; href=&quot;mailto:rniwa@webkit.org&quot;&gt;
+&lt;meta name=&quot;assert&quot; content=&quot;The event path calculation algorithm must be used to determine event path&quot;&gt;
+&lt;link rel=&quot;help&quot; href=&quot;https://w3c.github.io/webcomponents/spec/shadow/#event-paths&quot;&gt;
+&lt;script src=&quot;../../resources/testharness.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../resources/testharnessreport.js&quot;&gt;&lt;/script&gt;
+&lt;link rel='stylesheet' href='../../resources/testharness.css'&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;div id=&quot;log&quot;&gt;&lt;/div&gt;
+&lt;script&gt;
+
+function dispatchEventWithLog(target, event) {
+    var log = [];
+
+    for (var node = target; node; node = node.parentNode || node.host) {
+        node.addEventListener(event.type, (function (event) {
+            log.push([this, event.target]);
+        }).bind(node));
+    }
+
+    target.dispatchEvent(event);
+
+    return log;
+}
+
+function createShadowRootWithGrandChild(mode) {
+    var host = document.createElement('div');
+    var root = host.attachShadow({mode: mode});
+
+    var parent = document.createElement('span');
+    root.appendChild(parent);
+
+    var target = document.createElement('b');
+    parent.appendChild(target);
+    return {target: target, parent: parent, root: root, host: host};
+}
+
+function testEventInDetachedShadowTree(mode) {
+    test(function () {
+        var shadow = createShadowRootWithGrandChild(mode);
+
+        log = dispatchEventWithLog(shadow.target, new Event('foo', {bubbles: true}));
+
+        assert_array_equals(log.length, 4, 'EventPath must contain [target, parent, shadow root, shadow host]');
+        assert_array_equals(log[0], [shadow.target, shadow.target], 'EventPath[0] must be the target');
+        assert_array_equals(log[1], [shadow.parent, shadow.target], 'EventPath[1] must be the parent of the target');
+        assert_array_equals(log[2], [shadow.root, shadow.target], 'EventPath[2] must be the shadow root');
+        assert_array_equals(log[3], [shadow.host, shadow.host], 'EventPath[3] must be the shadow host');
+
+    }, 'Firing an event inside a grand child of a detached ' + mode + ' mode shadow tree');
+}
+
+testEventInDetachedShadowTree('open');
+testEventInDetachedShadowTree('closed');
+
+function testEventInShadowTreeInsideDocument(mode) {
+    test(function () {
+        var shadow = createShadowRootWithGrandChild(mode);
+        document.body.appendChild(shadow.host);
+
+        log = dispatchEventWithLog(shadow.target, new Event('foo', {bubbles: true}));
+
+        assert_array_equals(log.length, 7, 'EventPath must contain [target, parent, shadow root, shadow host, body, html, document]');
+        assert_array_equals(log[0], [shadow.target, shadow.target], 'EventPath[0] must be the target');
+        assert_array_equals(log[1], [shadow.parent, shadow.target], 'EventPath[1] must be the parent of the target');
+        assert_array_equals(log[2], [shadow.root, shadow.target], 'EventPath[2] must be the shadow root');
+        assert_array_equals(log[3], [shadow.host, shadow.host], 'EventPath[3] must be the shadow host');
+        assert_array_equals(log[4], [document.body, shadow.host], 'EventPath[4] must be the body element (parent of shadow host)');
+        assert_array_equals(log[5], [document.documentElement, shadow.host], 'EventPath[5] must be the html element');
+        assert_array_equals(log[6], [document, shadow.host], 'EventPath[6] must be the document node');
+
+    }, 'Firing an event inside a grand child of an in-document ' + mode + ' mode shadow tree');
+}
+
+testEventInShadowTreeInsideDocument('open');
+testEventInShadowTreeInsideDocument('closed');
+
+function createNestedShadowRoot(innerMode, outerMode) {
+    var outerHost = document.createElement('div');
+    var outerRoot = outerHost.attachShadow({mode: outerMode});
+
+    var outerChild = document.createElement('p');
+    outerRoot.appendChild(outerChild);
+
+    var innerHost = document.createElement('span');
+    outerChild.appendChild(innerHost);
+
+    var innerRoot = innerHost.attachShadow({mode: innerMode});
+    var innerChild = document.createElement('span');
+    innerRoot.appendChild(innerChild);
+
+    return {target: innerChild, innerRoot: innerRoot, innerHost: innerHost, outerChild: outerChild, outerRoot: outerRoot, outerHost: outerHost};
+}
+
+function testEventInDetachedNestedShadowTree(innerMode, outerMode) {
+    test(function () {
+        var shadow = createNestedShadowRoot(innerMode, outerMode);
+
+        log = dispatchEventWithLog(shadow.target, new Event('bar', {bubbles: true}));
+
+        assert_array_equals(log.length, 6, 'EventPath must contain [target, inner root, inner host, parent, outer root, outer host]');
+        assert_array_equals(log[0], [shadow.target, shadow.target], 'EventPath[0] must be the target');
+        assert_array_equals(log[1], [shadow.innerRoot, shadow.target], 'EventPath[1] must be the inner shadow root');
+        assert_array_equals(log[2], [shadow.innerHost, shadow.innerHost], 'EventPath[2] must be the inner shadow host');
+        assert_array_equals(log[3], [shadow.outerChild, shadow.innerHost], 'EventPath[3] must be the parent of the inner shadow host');
+        assert_array_equals(log[4], [shadow.outerRoot, shadow.innerHost], 'EventPath[4] must be the outer shadow root');
+        assert_array_equals(log[5], [shadow.outerHost, shadow.outerHost], 'EventPath[5] must be the outer shadow host');
+
+    }, 'Firing an event inside a detached ' + innerMode + ' mode shadow tree inside ' + outerMode + ' mode shadow tree');
+}
+
+testEventInDetachedNestedShadowTree('open',  'open');
+testEventInDetachedNestedShadowTree('open',  'closed');
+testEventInDetachedNestedShadowTree('closed', 'open');
+testEventInDetachedNestedShadowTree('closed', 'closed');
+
+function testEventInNestedShadowTreeInsideDocument(innerMode, outerMode) {
+    test(function () {
+        var shadow = createNestedShadowRoot(innerMode, outerMode);
+        document.body.appendChild(shadow.outerHost);
+
+        log = dispatchEventWithLog(shadow.target, new Event('bar', {bubbles: true}));
+
+        assert_array_equals(log.length, 6, 'EventPath must contain [target, inner root, inner host, parent, outer root, outer host]');
+        assert_array_equals(log[0], [shadow.target, shadow.target], 'EventPath[0] must be the target');
+        assert_array_equals(log[1], [shadow.innerRoot, shadow.target], 'EventPath[1] must be the inner shadow root');
+        assert_array_equals(log[2], [shadow.innerHost, shadow.innerHost], 'EventPath[2] must be the inner shadow host');
+        assert_array_equals(log[3], [shadow.outerChild, shadow.innerHost], 'EventPath[3] must be the parent of the inner shadow host');
+        assert_array_equals(log[4], [shadow.outerRoot, shadow.innerHost], 'EventPath[4] must be the outer shadow root');
+        assert_array_equals(log[5], [shadow.outerHost, shadow.outerHost], 'EventPath[5] must be the outer shadow host');
+        assert_array_equals(log[6], [document.body, shadow.outerHost], 'EventPath[6] must be the body element');
+        assert_array_equals(log[7], [document.documentElement, shadow.outerHost], 'EventPath[7] must be the html element');
+        assert_array_equals(log[8], [document, shadow.outerHost], 'EventPath[8] must be the document node');
+
+    }, 'Firing an event inside an in-document ' + innerMode + ' mode shadow tree inside ' + outerMode + ' mode shadow tree');
+}
+
+testEventInNestedShadowTreeInsideDocument('open',  'open');
+testEventInNestedShadowTreeInsideDocument('open',  'closed');
+testEventInNestedShadowTreeInsideDocument('closed', 'open');
+testEventInNestedShadowTreeInsideDocument('closed', 'closed');
+
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomeventinsideslottednodeexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/event-inside-slotted-node-expected.txt (0 => 190214)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/shadow-dom/event-inside-slotted-node-expected.txt                                (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/event-inside-slotted-node-expected.txt        2015-09-24 19:00:11 UTC (rev 190214)
</span><span class="lines">@@ -0,0 +1,22 @@
</span><ins>+
+PASS Firing an event inside a grand child of a detached open mode shadow host 
+PASS Firing an event inside a grand child of a detached closed mode shadow host 
+PASS Firing an event inside a grand child of an in-document open mode shadow host 
+PASS Firing an event inside a grand child of an in-document closed mode shadow host 
+PASS Firing an event on a node with two ancestors with a detached open and open shadow trees with an inner open shadow tree 
+PASS Firing an event on a node with two ancestors with a detached open and open shadow trees with an inner closed shadow tree 
+PASS Firing an event on a node with two ancestors with a detached open and closed shadow trees with an inner open shadow tree 
+PASS Firing an event on a node with two ancestors with a detached open and closed shadow trees with an inner closed shadow tree 
+PASS Firing an event on a node with two ancestors with a detached closed and open shadow trees with an inner open shadow tree 
+PASS Firing an event on a node with two ancestors with a detached closed and open shadow trees with an inner closed shadow tree 
+PASS Firing an event on a node with two ancestors with a detached closed and closed shadow trees with an inner open shadow tree 
+PASS Firing an event on a node with two ancestors with a detached closed and closed shadow trees with an inner closed shadow tree 
+PASS Firing an event on a node with two ancestors with a detached open and open shadow trees with an inner open shadow tree 
+PASS Firing an event on a node with two ancestors with a detached open and open shadow trees with an inner closed shadow tree 
+PASS Firing an event on a node with two ancestors with a detached open and closed shadow trees with an inner open shadow tree 
+PASS Firing an event on a node with two ancestors with a detached open and closed shadow trees with an inner closed shadow tree 
+PASS Firing an event on a node with two ancestors with a detached closed and open shadow trees with an inner open shadow tree 
+PASS Firing an event on a node with two ancestors with a detached closed and open shadow trees with an inner closed shadow tree 
+PASS Firing an event on a node with two ancestors with a detached closed and closed shadow trees with an inner open shadow tree 
+PASS Firing an event on a node with two ancestors with a detached closed and closed shadow trees with an inner closed shadow tree 
+
</ins></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomeventinsideslottednodehtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/event-inside-slotted-node.html (0 => 190214)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/shadow-dom/event-inside-slotted-node.html                                (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/event-inside-slotted-node.html        2015-09-24 19:00:11 UTC (rev 190214)
</span><span class="lines">@@ -0,0 +1,259 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+    &lt;title&gt;Shadow DOM: Firing an event inside a node assigned to a slot&lt;/title&gt;
+    &lt;meta name=&quot;author&quot; title=&quot;Ryosuke Niwa&quot; href=&quot;mailto:rniwa@webkit.org&quot;&gt;
+    &lt;meta name=&quot;assert&quot; content=&quot;The event path calculation algorithm must be used to determine event path&quot;&gt;
+    &lt;link rel=&quot;help&quot; href=&quot;https://w3c.github.io/webcomponents/spec/shadow/#event-paths&quot;&gt;
+    &lt;script src=&quot;../../resources/testharness.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;../../resources/testharnessreport.js&quot;&gt;&lt;/script&gt;
+    &lt;link rel='stylesheet' href='../../resources/testharness.css'&gt;
+&lt;/head&gt;
+&lt;body&gt;
+    &lt;div id=&quot;log&quot;&gt;&lt;/div&gt;
+    &lt;script&gt;
+
+        function dispatchEventWithLog(shadow, event) {
+            var log = [];
+
+            var attachedNodes = [];
+            for (var nodeKey in shadow) {
+                var startingNode = shadow[nodeKey];
+                for (var node = startingNode; node; node = node.parentNode) {
+                    if (attachedNodes.indexOf(node) &gt;= 0)
+                        continue;
+                    attachedNodes.push(node);
+                    node.addEventListener(event.type, (function (event) {
+                        log.push([this, event.target]);
+                    }).bind(node));
+                }
+            }
+
+            shadow.target.dispatchEvent(event);
+
+            return log;
+        }
+
+        function element(name, children, className) {
+            var element = document.createElement(name);
+            if (className)
+                element.className = className;
+            if (children) {
+                for (var child of children)
+                    element.appendChild(child);
+            }
+            return element;
+        }
+
+        function attachShadow(host, mode, children) {
+            var shadowRoot = host.attachShadow({mode: mode});
+            if (children) {
+                for (var child of children)
+                    shadowRoot.appendChild(child);
+            }
+            return shadowRoot;
+        }
+
+        function createShadowHostWithAssignedGrandChild(mode) {
+            var host = element('div', [
+                element('b', [
+                    element('i')
+                ])
+            ]);
+
+            var root = attachShadow(host, mode, [
+                element('span', [
+                    element('slot')
+                ])
+            ]);
+
+            return {target: host.querySelector('i'), targetParent: host.querySelector('b'), host: host,
+                    slot: root.querySelector('slot'), slotParent: root.querySelector('span'), root: root};
+        }
+
+        function testEventInDetachedShadowHostDescendant(mode) {
+            test(function () {
+                var shadow = createShadowHostWithAssignedGrandChild(mode);
+
+                log = dispatchEventWithLog(shadow, new Event('foo', {bubbles: true}));
+
+                assert_equals(log.length, 6, 'EventPath must contain [target, target parent, slot, slot parent, shadow root, shadow host]');
+                assert_array_equals(log[0], [shadow.target, shadow.target], 'EventPath[0] must be the target');
+                assert_array_equals(log[1], [shadow.targetParent, shadow.target], 'EventPath[1] must be the parent of the target');
+                assert_array_equals(log[2], [shadow.slot, shadow.slot], 'EventPath[2] must be the slot');
+                assert_array_equals(log[3], [shadow.slotParent, shadow.slot], 'EventPath[3] must be the parent of the slot');
+                assert_array_equals(log[4], [shadow.root, shadow.slot], 'EventPath[4] must be the shadow root');
+                assert_array_equals(log[5], [shadow.host, shadow.target], 'EventPath[5] must be the shadow host');
+
+            }, 'Firing an event inside a grand child of a detached ' + mode + ' mode shadow host');
+        }
+
+        testEventInDetachedShadowHostDescendant('open');
+        testEventInDetachedShadowHostDescendant('closed');
+
+        function testEventInShadowHostDescendantInsideDocument(mode) {
+            test(function () {
+                var shadow = createShadowHostWithAssignedGrandChild(mode);
+                document.body.appendChild(shadow.host);
+
+                log = dispatchEventWithLog(shadow, new Event('foo', {bubbles: true}));
+
+                assert_equals(log.length, 9, 'EventPath must contain [target, target parent, slot, slot parent, shadow root, shadow host, body, html, document]');
+                assert_array_equals(log[0], [shadow.target, shadow.target], 'EventPath[0] must be the target');
+                assert_array_equals(log[1], [shadow.targetParent, shadow.target], 'EventPath[1] must be the parent of the target');
+                assert_array_equals(log[2], [shadow.slot, shadow.slot], 'EventPath[2] must be the slot');
+                assert_array_equals(log[3], [shadow.slotParent, shadow.slot], 'EventPath[3] must be the parent of the slot');
+                assert_array_equals(log[4], [shadow.root, shadow.slot], 'EventPath[4] must be the shadow root');
+                assert_array_equals(log[5], [shadow.host, shadow.target], 'EventPath[5] must be the shadow host');
+                assert_array_equals(log[6], [document.body, shadow.target], 'EventPath[6] must be the body element');
+                assert_array_equals(log[7], [document.documentElement, shadow.target], 'EventPath[7] must be the html element');
+                assert_array_equals(log[8], [document, shadow.target], 'EventPath[8] must be the html element');
+
+            }, 'Firing an event inside a grand child of an in-document ' + mode + ' mode shadow host');
+        }
+
+        testEventInShadowHostDescendantInsideDocument('open');
+        testEventInShadowHostDescendantInsideDocument('closed');
+
+        function createNestedShadowTreesWithSlots(innerMode, outerUpperMode, outerLowerMode) {
+            var upperHost = element('upper-host', [
+                element('p', [
+                    element('lower-host', [
+                        element('a')
+                    ])
+                ])
+            ]);
+
+            var upperShadow = attachShadow(upperHost, outerUpperMode, [
+                element('b', [
+                    element('slot', [], 'upper-slot')
+                ])
+            ]);
+
+            var lowerHost = upperHost.querySelector('lower-host');
+            var lowerShadow = attachShadow(lowerHost, outerLowerMode, [
+                element('em', [
+                    element('inner-host', [
+                        element('span', [
+                            element('slot', [], 'lower-slot')
+                        ])
+                    ])
+                ])
+            ]);
+
+            innerShadow = attachShadow(lowerShadow.querySelector('inner-host'), innerMode, [
+                element('i', [
+                    element('slot', [], 'inner-slot')
+                ])
+            ]);
+
+            return {
+                host: upperHost,
+                target: upperHost.querySelector('a'),
+                upperShadow: upperShadow,
+                upperSlot: upperShadow.querySelector('slot'),
+                lowerShadow: lowerShadow,
+                lowerSlot: lowerShadow.querySelector('slot'),
+                innerShadow: innerShadow,
+                innerSlot: innerShadow.querySelector('slot'),
+            };
+        }
+
+        /*
+        upper-host (14) -- (upperShadow; 13)
+         + p (10)          + b (12)
+          |                  + slot (upperSlot; 11)
+          + lower-host (9) -- (lowerShadow; 8)
+            + a (target; 0)   + em (7)
+                                + inner-host (6) -------- (innerShadow; 5)
+                                  + span (2)              + i (4)
+                                    + slot (lowerSlot; 1) + slot (innerSlot; 3)
+        */
+
+        function testEventUnderTwoShadowRoots(outerUpperMode, outerLowerMode, innerMode) {
+            test(function () {
+                var shadow = createNestedShadowTreesWithSlots(innerMode, outerUpperMode, outerLowerMode);
+
+                log = dispatchEventWithLog(shadow, new Event('foo', {bubbles: true}));
+
+                assert_equals(log.length, 15, 'EventPath must contain 15 targets');
+
+                assert_array_equals(log[0], [shadow.target, shadow.target], 'EventPath[0] must be the target');
+                assert_array_equals(log[1], [shadow.lowerSlot, shadow.lowerSlot], 'EventPath[1] must be the slot inside the lower shadow tree');
+                assert_array_equals(log[2], [shadow.lowerSlot.parentNode, shadow.lowerSlot], 'EventPath[2] must be the parent of the slot inside the lower shadow tree');
+                assert_array_equals(log[3], [shadow.innerSlot, shadow.innerSlot], 'EventPath[3] must be the slot inside the shadow tree inside the lower shadow tree');
+                assert_array_equals(log[4], [shadow.innerSlot.parentNode, shadow.innerSlot], 'EventPath[4] must be the child of the inner shadow root');
+                assert_array_equals(log[5], [shadow.innerShadow, shadow.innerSlot], 'EventPath[5] must be the inner shadow root');
+                assert_array_equals(log[6], [shadow.innerShadow.host, shadow.lowerSlot], 'EventPath[6] must be the host of the inner shadow tree');
+                assert_array_equals(log[7], [shadow.lowerShadow.firstChild, shadow.lowerSlot], 'EventPath[7] must be the parent of the inner shadow host');
+                assert_array_equals(log[8], [shadow.lowerShadow, shadow.lowerSlot], 'EventPath[8] must be the lower shadow root');
+                assert_array_equals(log[9], [shadow.lowerShadow.host, shadow.target], 'EventPath[9] must be the lower shadow host');
+                assert_array_equals(log[10], [shadow.host.firstChild, shadow.target], 'EventPath[10] must be the parent of the grand parent of the target');
+                assert_array_equals(log[11], [shadow.upperSlot, shadow.upperSlot], 'EventPath[11] must be the slot inside the upper shadow tree');
+                assert_array_equals(log[12], [shadow.upperSlot.parentNode, shadow.upperSlot], 'EventPath[12] must be the parent of the slot inside the upper shadow tree');
+                assert_array_equals(log[13], [shadow.upperShadow, shadow.upperSlot], 'EventPath[13] must be the upper shadow root');
+                assert_array_equals(log[14], [shadow.host, shadow.target], 'EventPath[14] must be the host');
+
+            }, 'Firing an event on a node with two ancestors with a detached ' + outerUpperMode + ' and ' + outerLowerMode
+                + ' shadow trees with an inner ' + innerMode + ' shadow tree');
+        }
+
+        testEventUnderTwoShadowRoots('open', 'open', 'open');
+        testEventUnderTwoShadowRoots('open', 'open', 'closed');
+        testEventUnderTwoShadowRoots('open', 'closed', 'open');
+        testEventUnderTwoShadowRoots('open', 'closed', 'closed');
+        testEventUnderTwoShadowRoots('closed', 'open', 'open');
+        testEventUnderTwoShadowRoots('closed', 'open', 'closed');
+        testEventUnderTwoShadowRoots('closed', 'closed', 'open');
+        testEventUnderTwoShadowRoots('closed', 'closed', 'closed');
+
+        /*
+        upper-host (11) -- (upperShadow; 10)
+         + p (7)           + b (9)
+          |                  + slot (upperSlot; 8)
+          + lower-host (6) -- (lowerShadow; 5)
+            + a               + em (4)
+                                + inner-host (3) -- (innerShadow; 2)
+                                  + span            + i (1)
+                                    + slot            + slot (innerSlot; 0)
+        */
+
+        function testEventInsideNestedShadowsUnderAnotherShadow(outerUpperMode, outerLowerMode, innerMode) {
+            test(function () {
+                var shadow = createNestedShadowTreesWithSlots(innerMode, outerUpperMode, outerLowerMode);
+                shadow.deepestNodeInLightDOM = shadow.target; // Needed for dispatchEventWithLog to attach event listeners. 
+                shadow.target = shadow.innerSlot;
+
+                log = dispatchEventWithLog(shadow, new Event('foo', {bubbles: true}));
+
+                assert_equals(log.length, 12, 'EventPath must contain 12 targets');
+
+                assert_array_equals(log[0], [shadow.target, shadow.target], 'EventPath[0] must be the target');
+                assert_array_equals(log[1], [shadow.target.parentNode, shadow.target], 'EventPath[1] must be the parent of the target');
+                assert_array_equals(log[2], [shadow.innerShadow, shadow.target], 'EventPath[2] must be the inner shadow root');
+                assert_array_equals(log[3], [shadow.innerShadow.host, shadow.innerShadow.host], 'EventPath[3] must be the inner shadow host');
+                assert_array_equals(log[4], [shadow.lowerShadow.firstChild, shadow.innerShadow.host], 'EventPath[4] must be the parent of the inner shadow host');
+                assert_array_equals(log[5], [shadow.lowerShadow, shadow.innerShadow.host], 'EventPath[5] must be the lower (but outer) shadow root');
+                assert_array_equals(log[6], [shadow.lowerShadow.host, shadow.lowerShadow.host], 'EventPath[6] must be the lower (but outer) shadow root');
+                assert_array_equals(log[7], [shadow.host.firstChild, shadow.lowerShadow.host], 'EventPath[7] must be the slot inside the upper shadow tree');
+                assert_array_equals(log[8], [shadow.upperSlot, shadow.upperSlot], 'EventPath[8] must be the slot inside the upper shadow tree');
+                assert_array_equals(log[9], [shadow.upperSlot.parentNode, shadow.upperSlot], 'EventPath[9] must be the parent of the slot inside the upper shadow tree');
+                assert_array_equals(log[10], [shadow.upperShadow, shadow.upperSlot], 'EventPath[10] must be the upper shadow root');
+                assert_array_equals(log[11], [shadow.upperShadow.host, shadow.lowerShadow.host], 'EventPath[11] must be the host');
+
+            }, 'Firing an event on a node with two ancestors with a detached ' + outerUpperMode + ' and ' + outerLowerMode
+                + ' shadow trees with an inner ' + innerMode + ' shadow tree');
+        }
+
+        testEventInsideNestedShadowsUnderAnotherShadow('open', 'open', 'open');
+        testEventInsideNestedShadowsUnderAnotherShadow('open', 'open', 'closed');
+        testEventInsideNestedShadowsUnderAnotherShadow('open', 'closed', 'open');
+        testEventInsideNestedShadowsUnderAnotherShadow('open', 'closed', 'closed');
+        testEventInsideNestedShadowsUnderAnotherShadow('closed', 'open', 'open');
+        testEventInsideNestedShadowsUnderAnotherShadow('closed', 'open', 'closed');
+        testEventInsideNestedShadowsUnderAnotherShadow('closed', 'closed', 'open');
+        testEventInsideNestedShadowsUnderAnotherShadow('closed', 'closed', 'closed');
+
+    &lt;/script&gt;
+    &lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (190213 => 190214)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2015-09-24 18:38:35 UTC (rev 190213)
+++ trunk/Source/WebCore/ChangeLog        2015-09-24 19:00:11 UTC (rev 190214)
</span><span class="lines">@@ -1,3 +1,29 @@
</span><ins>+2015-09-24  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        Make event dispatching respect slotting
+        https://bugs.webkit.org/show_bug.cgi?id=149243
+
+        Reviewed by Antti Koivisto.
+
+        There are primarily two cases to consider: getting out of a shadow root to its host,
+        and moving into a slot from an assigned node.
+
+        When getting out of a shadow root, either the event originated in its shadow tree
+        including its nodes' shadow trees (1) or it moved into this shadow tree via a slot (2).
+        In (1), event.target should be set to the shadow host. In (2), it should be set to the
+        first node in the event path that belongs to the same tree as the shadow host. In order
+        to find such a node in O(1), we use a stack of event targets in each (shadow) tree. We
+        push event.target of the current tree whenever we move up to a slot from an assigned node
+        and pop it out of the stack when we move out of the shadow tree.
+
+        A follow up patch is needed to update the code to resolve related targets.
+
+        Tests: fast/shadow-dom/event-inside-shadow-tree.html
+               fast/shadow-dom/event-inside-slotted-node.html
+
+        * dom/EventDispatcher.cpp:
+        (WebCore::EventPath::EventPath):
+
</ins><span class="cx"> 2015-09-24  Chris Dumez  &lt;cdumez@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Drop XPATH_NAMESPACE_NODE from Node::NodeType enum
</span></span></pre></div>
<a id="trunkSourceWebCoredomEventDispatchercpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/EventDispatcher.cpp (190213 => 190214)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/EventDispatcher.cpp        2015-09-24 18:38:35 UTC (rev 190213)
+++ trunk/Source/WebCore/dom/EventDispatcher.cpp        2015-09-24 19:00:11 UTC (rev 190214)
</span><span class="lines">@@ -31,6 +31,7 @@
</span><span class="cx"> #include &quot;FrameView.h&quot;
</span><span class="cx"> #include &quot;HTMLInputElement.h&quot;
</span><span class="cx"> #include &quot;HTMLMediaElement.h&quot;
</span><ins>+#include &quot;HTMLSlotElement.h&quot;
</ins><span class="cx"> #include &quot;InsertionPoint.h&quot;
</span><span class="cx"> #include &quot;InspectorInstrumentation.h&quot;
</span><span class="cx"> #include &quot;MouseEvent.h&quot;
</span><span class="lines">@@ -413,20 +414,24 @@
</span><span class="cx">     return is&lt;PseudoElement&gt;(*node) ? downcast&lt;PseudoElement&gt;(*node).hostElement() : node;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-EventPath::EventPath(Node&amp; targetNode, Event&amp; event)
</del><ins>+EventPath::EventPath(Node&amp; originalTarget, Event&amp; event)
</ins><span class="cx"> {
</span><del>-    bool isSVGElement = targetNode.isSVGElement();
</del><ins>+#if ENABLE(SHADOW_DOM)
+    Vector&lt;EventTarget*, 16&gt; targetStack;
+#endif
+
</ins><span class="cx">     bool isMouseOrFocusEvent = event.isMouseEvent() || event.isFocusEvent();
</span><span class="cx"> #if ENABLE(TOUCH_EVENTS) &amp;&amp; !PLATFORM(IOS)
</span><span class="cx">     bool isTouchEvent = event.isTouchEvent();
</span><span class="cx"> #endif
</span><span class="cx">     EventTarget* target = nullptr;
</span><span class="cx"> 
</span><del>-    Node* node = nodeOrHostIfPseudoElement(&amp;targetNode);
</del><ins>+    Node* node = nodeOrHostIfPseudoElement(&amp;originalTarget);
</ins><span class="cx">     while (node) {
</span><del>-        if (!target || !isSVGElement) // FIXME: This code doesn't make sense once we've climbed out of the SVG subtree in a HTML document.
</del><ins>+        if (!target)
</ins><span class="cx">             target = eventTargetRespectingTargetRules(*node);
</span><del>-        for (; node; node = node-&gt;parentNode()) {
</del><ins>+        ContainerNode* parent;
+        for (; node; node = parent) {
</ins><span class="cx">             EventTarget* currentTarget = eventTargetRespectingTargetRules(*node);
</span><span class="cx">             if (isMouseOrFocusEvent)
</span><span class="cx">                 m_path.append(std::make_unique&lt;MouseOrFocusEventContext&gt;(node, currentTarget, target));
</span><span class="lines">@@ -438,10 +443,36 @@
</span><span class="cx">                 m_path.append(std::make_unique&lt;EventContext&gt;(node, currentTarget, target));
</span><span class="cx">             if (is&lt;ShadowRoot&gt;(*node))
</span><span class="cx">                 break;
</span><ins>+            parent = node-&gt;parentNode();
+            if (!parent)
+                return;
+#if ENABLE(SHADOW_DOM)
+            if (ShadowRoot* shadowRootOfParent = parent-&gt;shadowRoot()) {
+                if (auto* assignedSlot = shadowRootOfParent-&gt;findAssignedSlot(*node)) {
+                    // node is assigned to a slot. Continue dispatching the event at this slot.
+                    targetStack.append(target);
+                    parent = assignedSlot;
+                    target = assignedSlot;
+                }
+            }
+#endif
+            node = parent;
</ins><span class="cx">         }
</span><del>-        if (!node || !shouldEventCrossShadowBoundary(event, downcast&lt;ShadowRoot&gt;(*node), *target))
</del><ins>+
+        ShadowRoot&amp; shadowRoot = downcast&lt;ShadowRoot&gt;(*node);
+        // At a shadow root. Continue dispatching the event at the shadow host.
+#if ENABLE(SHADOW_DOM)
+        if (!targetStack.isEmpty()) {
+            // Move target back to a descendant of the shadow host if the event did not originate in this shadow tree or its inner shadow trees.
+            target = targetStack.last();
+            targetStack.removeLast();
+            ASSERT(shadowRoot.host()-&gt;contains(target-&gt;toNode()));
+        } else
+            target = nullptr;
+#endif
+        if (!shouldEventCrossShadowBoundary(event, shadowRoot, originalTarget))
</ins><span class="cx">             return;
</span><del>-        node = downcast&lt;ShadowRoot&gt;(*node).host();
</del><ins>+        node = shadowRoot.host();
</ins><span class="cx">     }
</span><span class="cx"> }
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>