<!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>[208817] 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/208817">208817</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2016-11-16 15:06:47 -0800 (Wed, 16 Nov 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>slotchange event should bubble and dispatched once
https://bugs.webkit.org/show_bug.cgi?id=164770

Reviewed by Antti Koivisto.

LayoutTests/imported/w3c:

Rebaselined the test. Some test cases fail as they do on Chrome because it's testing an outdated version of the spec.
Will fix the test upstream later.

* web-platform-tests/shadow-dom/slotchange-event-expected.txt:

Source/WebCore:

Updated our implementation of slotchange event to match the latest specification after:
https://github.com/w3c/webcomponents/issues/571
https://dom.spec.whatwg.org/#signal-a-slot-change
The new behavior matches that of Google Chrome Canary.

In the latest specification, we no longer dispatch a separate event on ancestor slots.
Instead, we fire a single slotchange event to which a new node is assigned or from which
an existing assigned node is removed. This patch mostly removes the code that existed to
locate ancestor slot elements, and makes the event bubble up by changing a single line in
HTMLSlotElement::dispatchSlotChangeEvent.

Test: fast/shadow-dom/slotchange-event-bubbling.html

* dom/ShadowRoot.h:
* dom/SlotAssignment.cpp:
(WebCore::recursivelyFireSlotChangeEvent): Deleted.
(WebCore::SlotAssignment::didChangeSlot): Removed ChangeType from the arguments since we
no longer notify the ancestor slot elements.
(WebCore::SlotAssignment::hostChildElementDidChange):
* dom/SlotAssignment.h:
(WebCore::ShadowRoot::didRemoveAllChildrenOfShadowHost):
(WebCore::ShadowRoot::didChangeDefaultSlot):
(WebCore::ShadowRoot::hostChildElementDidChangeSlotAttribute):
(WebCore::ShadowRoot::innerSlotDidChange): Deleted.
* html/HTMLDetailsElement.cpp:
(WebCore::DetailsSlotAssignment::hostChildElementDidChange):
* html/HTMLSlotElement.cpp:
(WebCore::HTMLSlotElement::dispatchSlotChangeEvent): Make slotchange event bubble.

LayoutTests:

* fast/shadow-dom/slotchange-event-bubbling-expected.txt: Added.
* fast/shadow-dom/slotchange-event-bubbling.html: Added.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkLayoutTestsimportedw3cChangeLog">trunk/LayoutTests/imported/w3c/ChangeLog</a></li>
<li><a href="#trunkLayoutTestsimportedw3cwebplatformtestsshadowdomslotchangeeventexpectedtxt">trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-event-expected.txt</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoredomShadowRooth">trunk/Source/WebCore/dom/ShadowRoot.h</a></li>
<li><a href="#trunkSourceWebCoredomSlotAssignmentcpp">trunk/Source/WebCore/dom/SlotAssignment.cpp</a></li>
<li><a href="#trunkSourceWebCoredomSlotAssignmenth">trunk/Source/WebCore/dom/SlotAssignment.h</a></li>
<li><a href="#trunkSourceWebCorehtmlHTMLDetailsElementcpp">trunk/Source/WebCore/html/HTMLDetailsElement.cpp</a></li>
<li><a href="#trunkSourceWebCorehtmlHTMLSlotElementcpp">trunk/Source/WebCore/html/HTMLSlotElement.cpp</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsfastshadowdomslotchangeeventbubblingexpectedtxt">trunk/LayoutTests/fast/shadow-dom/slotchange-event-bubbling-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastshadowdomslotchangeeventbubblinghtml">trunk/LayoutTests/fast/shadow-dom/slotchange-event-bubbling.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (208816 => 208817)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2016-11-16 23:05:00 UTC (rev 208816)
+++ trunk/LayoutTests/ChangeLog        2016-11-16 23:06:47 UTC (rev 208817)
</span><span class="lines">@@ -1,3 +1,13 @@
</span><ins>+2016-11-16  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        slotchange event should bubble and dispatched once
+        https://bugs.webkit.org/show_bug.cgi?id=164770
+
+        Reviewed by Antti Koivisto.
+
+        * fast/shadow-dom/slotchange-event-bubbling-expected.txt: Added.
+        * fast/shadow-dom/slotchange-event-bubbling.html: Added.
+
</ins><span class="cx"> 2016-11-16  Simon Fraser  &lt;simon.fraser@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         UIScriptController: script with no async tasks fails if an earlier script registered a callback
</span></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomslotchangeeventbubblingexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/slotchange-event-bubbling-expected.txt (0 => 208817)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/shadow-dom/slotchange-event-bubbling-expected.txt                                (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/slotchange-event-bubbling-expected.txt        2016-11-16 23:06:47 UTC (rev 208817)
</span><span class="lines">@@ -0,0 +1,18 @@
</span><ins>+
+PASS slotchange event must bubble in a disconnected closed-mode shadow tree 
+PASS slotchange event must bubble in a connected closed-mode shadow tree 
+PASS slotchange event must bubble in a disconnected open-mode shadow tree 
+PASS slotchange event must bubble in a connected open-mode shadow tree 
+PASS A single slotchange event must bubble from a disconnected closed-mode shadow tree toa slot in its parent closed-mode shadow tree 
+PASS A single slotchange event must bubble from a connected closed-mode shadow tree toa slot in its parent closed-mode shadow tree 
+PASS A single slotchange event must bubble from a disconnected open-mode shadow tree toa slot in its parent closed-mode shadow tree 
+PASS A single slotchange event must bubble from a connected open-mode shadow tree toa slot in its parent closed-mode shadow tree 
+PASS A single slotchange event must bubble from a disconnected closed-mode shadow tree toa slot in its parent open-mode shadow tree 
+PASS A single slotchange event must bubble from a connected closed-mode shadow tree toa slot in its parent open-mode shadow tree 
+PASS A single slotchange event must bubble from a disconnected open-mode shadow tree toa slot in its parent open-mode shadow tree 
+PASS A single slotchange event must bubble from a connected open-mode shadow tree toa slot in its parent open-mode shadow tree 
+PASS slotchange event must be fired in a disconnected closed-mode shadow tree even when the slot element itself lacks a event listener. 
+PASS slotchange event must be fired in a connected closed-mode shadow tree even when the slot element itself lacks a event listener. 
+PASS slotchange event must be fired in a disconnected open-mode shadow tree even when the slot element itself lacks a event listener. 
+PASS slotchange event must be fired in a connected open-mode shadow tree even when the slot element itself lacks a event listener. 
+
</ins></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomslotchangeeventbubblinghtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/slotchange-event-bubbling.html (0 => 208817)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/shadow-dom/slotchange-event-bubbling.html                                (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/slotchange-event-bubbling.html        2016-11-16 23:06:47 UTC (rev 208817)
</span><span class="lines">@@ -0,0 +1,187 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+&lt;title&gt;Shadow DOM: slotchange event&lt;/title&gt;
+&lt;meta name=&quot;author&quot; title=&quot;Ryosuke Niwa&quot; href=&quot;mailto:rniwa@webkit.org&quot;&gt;
+&lt;link rel=&quot;help&quot; href=&quot;https://dom.spec.whatwg.org/#signaling-slot-change&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;/head&gt;
+&lt;body&gt;
+&lt;script&gt;
+
+function create_slotchange_observer() {
+    let log = [];
+    const listener = function (event) {
+        log.push({node: this, event: event, eventType: event.type, eventTarget: event.target});
+    }
+    return {
+        observe: (node) =&gt; node.addEventListener('slotchange', listener),
+        takeLog: () =&gt; {
+            const currentLog = log;
+            log = [];
+            return currentLog;
+        }
+    };
+}
+
+function assert_slotchange_log(logEntry, node, target, description) {
+    assert_equals(logEntry.node, node, description);
+    assert_equals(logEntry.eventType, 'slotchange', description);
+    assert_equals(logEntry.eventTarget, target, description);
+}
+
+function test_slotchange_event_bubbles(mode, connected) {
+    promise_test(() =&gt; {
+        const host = document.createElement('div');
+        if (connected)
+            document.body.appendChild(host);
+
+        const shadowRoot = host.attachShadow({'mode': mode});
+        shadowRoot.innerHTML = '&lt;div&gt;&lt;slot&gt;&lt;/slot&gt;&lt;/div&gt;';
+        const container = shadowRoot.querySelector('div');
+        const slot = shadowRoot.querySelector('slot');
+
+        const observer = create_slotchange_observer();
+        observer.observe(slot);
+        observer.observe(container);
+        observer.observe(shadowRoot);
+        observer.observe(host);
+        observer.observe(document);
+        observer.observe(window);
+
+        shadowRoot.appendChild(container);
+        host.appendChild(document.createElement('span'));
+        host.appendChild(document.createElement('b'));
+
+        assert_array_equals(observer.takeLog(), [], 'slotchange event must not be fired synchronously');
+        return Promise.resolve().then(() =&gt; {
+            const log = observer.takeLog();
+
+            const events = new Set(log.map((entry) =&gt; entry.event));
+            assert_equals(events.size, 1, 'Mutating the assigned content of a slot must fire exactly one slotchange event');
+
+            assert_slotchange_log(log[0], slot, slot, 'slotchange event must be dispatched at the slot element first');
+            assert_slotchange_log(log[1], container, slot, 'slotchange event must bubble up to the parent node of the slot');
+            assert_slotchange_log(log[2], shadowRoot, slot, 'slotchange event must bubble up to the shadow root');
+            assert_equals(log.length, 3, 'slotchange must not bubble beyond the shadow root');
+        });
+    }, `slotchange event must bubble in a ${connected ? 'connected' : 'disconnected'} ${mode}-mode shadow tree`);
+}
+
+test_slotchange_event_bubbles('closed', false);
+test_slotchange_event_bubbles('closed', true);
+test_slotchange_event_bubbles('open', false);
+test_slotchange_event_bubbles('open', true);
+
+function test_single_slotchange_event_for_nested_slots(outerMode, innerMode, connected) {
+    promise_test(() =&gt; {
+        const outerHost = document.createElement('outer-host');
+        if (connected)
+            document.body.appendChild(outerHost);
+
+        const outerShadow = outerHost.attachShadow({'mode': outerMode});
+        outerShadow.innerHTML = '&lt;div&gt;&lt;inner-host&gt;&lt;slot&gt;&lt;/slot&gt;&lt;/inner-host&gt;&lt;/div&gt;';
+        const outerHostParent = outerShadow.querySelector('div');
+        const outerSlot = outerShadow.querySelector('slot');
+
+        const innerHost = outerShadow.querySelector('inner-host');
+        const innerShadow = innerHost.attachShadow({'mode': innerMode});
+        innerShadow.innerHTML = '&lt;div&gt;&lt;slot&gt;&lt;/slot&gt;&lt;/div&gt;';
+        const innerSlotParent = innerShadow.querySelector('div');
+        const innerSlot = innerShadow.querySelector('slot');
+
+        const observer = create_slotchange_observer();
+        observer.observe(outerSlot);
+        observer.observe(innerHost);
+
+        observer.observe(window);
+        observer.observe(document);
+        observer.observe(outerHost);
+        observer.observe(outerShadow);
+        observer.observe(outerHostParent);
+        observer.observe(outerSlot);
+        observer.observe(innerHost);
+        observer.observe(innerShadow);
+        observer.observe(innerSlotParent);
+        observer.observe(innerSlot);
+
+        outerHost.textContent = ' ';
+
+        assert_array_equals(observer.takeLog(), [], 'slotchange event must not be fired synchronously');
+        return Promise.resolve().then(() =&gt; {
+            const log = observer.takeLog();
+
+            const events = new Set(log.map((entry) =&gt; entry.event));
+            assert_equals(events.size, 1, 'Mutating the assigned content of a slot must fire exactly one slotchange event');
+
+            assert_slotchange_log(log[0], outerSlot, outerSlot, 'slotchange event must be dispatched at the slot element first');
+            assert_slotchange_log(log[1], innerSlot, outerSlot, 'slotchange event must bubble up from a slot element to its assigned slot');
+            assert_slotchange_log(log[2], innerSlotParent, outerSlot, 'slotchange event must bubble up to the parent node of a slot');
+            assert_slotchange_log(log[3], innerShadow, outerSlot, 'slotchange event must bubble up to the shadow root');
+            assert_slotchange_log(log[4], innerHost, outerSlot,
+                'slotchange event must bubble up to the shadow host if the host is a descendent of the tree in which the event was fired');
+            assert_slotchange_log(log[5], outerHostParent, outerSlot,
+                'slotchange event must bubble up to the parent of an inner shadow host');
+            assert_slotchange_log(log[6], outerShadow, outerSlot, 'slotchange event must bubble up to the shadow root');
+            assert_equals(log.length, 7, 'slotchange must not bubble beyond the shadow root in which the event was fired');
+        });
+    }, `A single slotchange event must bubble from a ${connected ? 'connected' : 'disconnected'} ${innerMode}-mode shadow tree to`
+        + `a slot in its parent ${outerMode}-mode shadow tree`);
+}
+
+test_single_slotchange_event_for_nested_slots('closed', 'closed', false);
+test_single_slotchange_event_for_nested_slots('closed', 'closed', true);
+test_single_slotchange_event_for_nested_slots('closed', 'open', false);
+test_single_slotchange_event_for_nested_slots('closed', 'open', true);
+
+test_single_slotchange_event_for_nested_slots('open', 'closed', false);
+test_single_slotchange_event_for_nested_slots('open', 'closed', true);
+test_single_slotchange_event_for_nested_slots('open', 'open', false);
+test_single_slotchange_event_for_nested_slots('open', 'open', true);
+
+function test_slotchange_event_fired_without_listener_on_slot(mode, connected) {
+    promise_test(() =&gt; {
+        const host = document.createElement('div');
+        if (connected)
+            document.body.appendChild(host);
+
+        const shadowRoot = host.attachShadow({'mode': mode});
+        shadowRoot.innerHTML = '&lt;div&gt;&lt;slot&gt;&lt;/slot&gt;&lt;/div&gt;';
+        const container = shadowRoot.querySelector('div');
+        const slot = shadowRoot.querySelector('slot');
+
+        const observer = create_slotchange_observer();
+        observer.observe(container);
+        observer.observe(shadowRoot);
+        observer.observe(host);
+        observer.observe(document);
+        observer.observe(window);
+
+        shadowRoot.appendChild(container);
+        host.appendChild(document.createElement('span'));
+        host.appendChild(document.createElement('b'));
+
+        assert_array_equals(observer.takeLog(), [], 'slotchange event must not be fired synchronously');
+        return Promise.resolve().then(() =&gt; {
+            const log = observer.takeLog();
+
+            const events = new Set(log.map((entry) =&gt; entry.event));
+            assert_equals(events.size, 1, 'Mutating the assigned content of a slot must fire exactly one slotchange event');
+
+            assert_slotchange_log(log[0], container, slot, 'slotchange event must bubble up to the parent node of the slot');
+            assert_slotchange_log(log[1], shadowRoot, slot, 'slotchange event must bubble up to the shadow root');
+            assert_equals(log.length, 2, 'slotchange must not bubble beyond the shadow root');
+        });
+    }, `slotchange event must be fired in a ${connected ? 'connected' : 'disconnected'} ${mode}-mode shadow tree`
+        + ` even when the slot element itself lacks a event listener.`);
+}
+
+test_slotchange_event_fired_without_listener_on_slot('closed', false);
+test_slotchange_event_fired_without_listener_on_slot('closed', true);
+test_slotchange_event_fired_without_listener_on_slot('open', false);
+test_slotchange_event_fired_without_listener_on_slot('open', true);
+
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsimportedw3cChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/imported/w3c/ChangeLog (208816 => 208817)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/imported/w3c/ChangeLog        2016-11-16 23:05:00 UTC (rev 208816)
+++ trunk/LayoutTests/imported/w3c/ChangeLog        2016-11-16 23:06:47 UTC (rev 208817)
</span><span class="lines">@@ -1,3 +1,15 @@
</span><ins>+2016-11-16  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        slotchange event should bubble and dispatched once
+        https://bugs.webkit.org/show_bug.cgi?id=164770
+
+        Reviewed by Antti Koivisto.
+
+        Rebaselined the test. Some test cases fail as they do on Chrome because it's testing an outdated version of the spec.
+        Will fix the test upstream later.
+
+        * web-platform-tests/shadow-dom/slotchange-event-expected.txt:
+
</ins><span class="cx"> 2016-11-14  Jiewen Tan  &lt;jiewen_tan@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Update SubtleCrypto::exportKey to match the latest spec
</span></span></pre></div>
<a id="trunkLayoutTestsimportedw3cwebplatformtestsshadowdomslotchangeeventexpectedtxt"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-event-expected.txt (208816 => 208817)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-event-expected.txt        2016-11-16 23:05:00 UTC (rev 208816)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-event-expected.txt        2016-11-16 23:06:47 UTC (rev 208817)
</span><span class="lines">@@ -23,14 +23,14 @@
</span><span class="cx"> PASS slotchange event must fire on a slot element inside a closed shadow root  in a document when innerHTML modifies the children of the shadow host 
</span><span class="cx"> PASS slotchange event must fire on a slot element inside an open shadow root  not in a document when innerHTML modifies the children of the shadow host 
</span><span class="cx"> PASS slotchange event must fire on a slot element inside a closed shadow root  not in a document when innerHTML modifies the children of the shadow host 
</span><del>-PASS slotchange event must fire on a slot element inside an open shadow root  in a document when nested slots's contents change 
-PASS slotchange event must fire on a slot element inside a closed shadow root  in a document when nested slots's contents change 
-PASS slotchange event must fire on a slot element inside an open shadow root  not in a document when nested slots's contents change 
-PASS slotchange event must fire on a slot element inside a closed shadow root  not in a document when nested slots's contents change 
-PASS slotchange event must fire at the end of current microtask after mutation observers are invoked inside an open shadow root  in a document when slots's contents change 
-PASS slotchange event must fire at the end of current microtask after mutation observers are invoked inside a closed shadow root  in a document when slots's contents change 
-PASS slotchange event must fire at the end of current microtask after mutation observers are invoked inside an open shadow root  not in a document when slots's contents change 
-PASS slotchange event must fire at the end of current microtask after mutation observers are invoked inside a closed shadow root  not in a document when slots's contents change 
</del><ins>+FAIL slotchange event must fire on a slot element inside an open shadow root  in a document when nested slots's contents change assert_equals: slotchange must be fired on a slot element if the assigned nodes changed expected 1 but got 0
+FAIL slotchange event must fire on a slot element inside a closed shadow root  in a document when nested slots's contents change assert_equals: slotchange must be fired on a slot element if the assigned nodes changed expected 1 but got 0
+FAIL slotchange event must fire on a slot element inside an open shadow root  not in a document when nested slots's contents change assert_equals: slotchange must be fired on a slot element if the assigned nodes changed expected 1 but got 0
+FAIL slotchange event must fire on a slot element inside a closed shadow root  not in a document when nested slots's contents change assert_equals: slotchange must be fired on a slot element if the assigned nodes changed expected 1 but got 0
+FAIL slotchange event must fire at the end of current microtask after mutation observers are invoked inside an open shadow root  in a document when slots's contents change assert_array_equals: slotchange event must be fired during a single compound microtask lengths differ, expected 2 got 1
+FAIL slotchange event must fire at the end of current microtask after mutation observers are invoked inside a closed shadow root  in a document when slots's contents change assert_array_equals: slotchange event must be fired during a single compound microtask lengths differ, expected 2 got 1
+FAIL slotchange event must fire at the end of current microtask after mutation observers are invoked inside an open shadow root  not in a document when slots's contents change assert_array_equals: slotchange event must be fired during a single compound microtask lengths differ, expected 2 got 1
+FAIL slotchange event must fire at the end of current microtask after mutation observers are invoked inside a closed shadow root  not in a document when slots's contents change assert_array_equals: slotchange event must be fired during a single compound microtask lengths differ, expected 2 got 1
</ins><span class="cx"> hello
</span><span class="cx"> hello
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (208816 => 208817)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2016-11-16 23:05:00 UTC (rev 208816)
+++ trunk/Source/WebCore/ChangeLog        2016-11-16 23:06:47 UTC (rev 208817)
</span><span class="lines">@@ -1,3 +1,39 @@
</span><ins>+2016-11-16  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        slotchange event should bubble and dispatched once
+        https://bugs.webkit.org/show_bug.cgi?id=164770
+
+        Reviewed by Antti Koivisto.
+
+        Updated our implementation of slotchange event to match the latest specification after:
+        https://github.com/w3c/webcomponents/issues/571
+        https://dom.spec.whatwg.org/#signal-a-slot-change
+        The new behavior matches that of Google Chrome Canary.
+
+        In the latest specification, we no longer dispatch a separate event on ancestor slots.
+        Instead, we fire a single slotchange event to which a new node is assigned or from which
+        an existing assigned node is removed. This patch mostly removes the code that existed to
+        locate ancestor slot elements, and makes the event bubble up by changing a single line in
+        HTMLSlotElement::dispatchSlotChangeEvent.
+
+        Test: fast/shadow-dom/slotchange-event-bubbling.html
+
+        * dom/ShadowRoot.h:
+        * dom/SlotAssignment.cpp:
+        (WebCore::recursivelyFireSlotChangeEvent): Deleted.
+        (WebCore::SlotAssignment::didChangeSlot): Removed ChangeType from the arguments since we
+        no longer notify the ancestor slot elements.
+        (WebCore::SlotAssignment::hostChildElementDidChange):
+        * dom/SlotAssignment.h:
+        (WebCore::ShadowRoot::didRemoveAllChildrenOfShadowHost):
+        (WebCore::ShadowRoot::didChangeDefaultSlot):
+        (WebCore::ShadowRoot::hostChildElementDidChangeSlotAttribute):
+        (WebCore::ShadowRoot::innerSlotDidChange): Deleted.
+        * html/HTMLDetailsElement.cpp:
+        (WebCore::DetailsSlotAssignment::hostChildElementDidChange):
+        * html/HTMLSlotElement.cpp:
+        (WebCore::HTMLSlotElement::dispatchSlotChangeEvent): Make slotchange event bubble.
+
</ins><span class="cx"> 2016-11-16  Alex Christensen  &lt;achristensen@webkit.org&gt;
</span><span class="cx"> 
</span><span class="cx">         REGRESSION (r207162): [debug] loader/stateobjects LayoutTests timing out
</span></span></pre></div>
<a id="trunkSourceWebCoredomShadowRooth"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/ShadowRoot.h (208816 => 208817)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/ShadowRoot.h        2016-11-16 23:05:00 UTC (rev 208816)
+++ trunk/Source/WebCore/dom/ShadowRoot.h        2016-11-16 23:06:47 UTC (rev 208817)
</span><span class="lines">@@ -78,7 +78,6 @@
</span><span class="cx">     void didChangeDefaultSlot();
</span><span class="cx">     void hostChildElementDidChange(const Element&amp;);
</span><span class="cx">     void hostChildElementDidChangeSlotAttribute(const AtomicString&amp; oldValue, const AtomicString&amp; newValue);
</span><del>-    void innerSlotDidChange(const AtomicString&amp;);
</del><span class="cx"> 
</span><span class="cx">     const Vector&lt;Node*&gt;* assignedNodesForSlot(const HTMLSlotElement&amp;);
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoredomSlotAssignmentcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/SlotAssignment.cpp (208816 => 208817)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/SlotAssignment.cpp        2016-11-16 23:05:00 UTC (rev 208816)
+++ trunk/Source/WebCore/dom/SlotAssignment.cpp        2016-11-16 23:06:47 UTC (rev 208817)
</span><span class="lines">@@ -127,23 +127,8 @@
</span><span class="cx">     ASSERT(slotInfo.element || m_needsToResolveSlotElements);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-static void recursivelyFireSlotChangeEvent(HTMLSlotElement&amp; slotElement)
</del><ins>+void SlotAssignment::didChangeSlot(const AtomicString&amp; slotAttrValue, ShadowRoot&amp; shadowRoot)
</ins><span class="cx"> {
</span><del>-    slotElement.enqueueSlotChangeEvent();
-
-    auto* slotParent = slotElement.parentElement();
-    if (!slotParent)
-        return;
-
-    auto* shadowRootOfSlotParent = slotParent-&gt;shadowRoot();
-    if (!shadowRootOfSlotParent)
-        return;
-
-    shadowRootOfSlotParent-&gt;innerSlotDidChange(slotElement.attributeWithoutSynchronization(slotAttr));
-}
-
-void SlotAssignment::didChangeSlot(const AtomicString&amp; slotAttrValue, ChangeType changeType, ShadowRoot&amp; shadowRoot)
-{
</del><span class="cx">     auto&amp; slotName = slotNameFromAttributeValue(slotAttrValue);
</span><span class="cx">     auto it = m_slots.find(slotName);
</span><span class="cx">     if (it == m_slots.end())
</span><span class="lines">@@ -153,20 +138,18 @@
</span><span class="cx">     if (!slotElement)
</span><span class="cx">         return;
</span><span class="cx"> 
</span><del>-    if (changeType == ChangeType::DirectChild) {
-        shadowRoot.host()-&gt;invalidateStyleAndRenderersForSubtree();
-        m_slotAssignmentsIsValid = false;
-    }
</del><ins>+    shadowRoot.host()-&gt;invalidateStyleAndRenderersForSubtree();
+    m_slotAssignmentsIsValid = false;
</ins><span class="cx"> 
</span><span class="cx">     if (shadowRoot.mode() == ShadowRootMode::UserAgent)
</span><span class="cx">         return;
</span><span class="cx"> 
</span><del>-    recursivelyFireSlotChangeEvent(*slotElement);
</del><ins>+    slotElement-&gt;enqueueSlotChangeEvent();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void SlotAssignment::hostChildElementDidChange(const Element&amp; childElement, ShadowRoot&amp; shadowRoot)
</span><span class="cx"> {
</span><del>-    didChangeSlot(childElement.attributeWithoutSynchronization(slotAttr), ChangeType::DirectChild, shadowRoot);
</del><ins>+    didChangeSlot(childElement.attributeWithoutSynchronization(slotAttr), shadowRoot);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> const Vector&lt;Node*&gt;* SlotAssignment::assignedNodesForSlot(const HTMLSlotElement&amp; slotElement, ShadowRoot&amp; shadowRoot)
</span></span></pre></div>
<a id="trunkSourceWebCoredomSlotAssignmenth"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/SlotAssignment.h (208816 => 208817)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/SlotAssignment.h        2016-11-16 23:05:00 UTC (rev 208816)
+++ trunk/Source/WebCore/dom/SlotAssignment.h        2016-11-16 23:06:47 UTC (rev 208817)
</span><span class="lines">@@ -51,8 +51,7 @@
</span><span class="cx">     void addSlotElementByName(const AtomicString&amp;, HTMLSlotElement&amp;, ShadowRoot&amp;);
</span><span class="cx">     void removeSlotElementByName(const AtomicString&amp;, HTMLSlotElement&amp;, ShadowRoot&amp;);
</span><span class="cx"> 
</span><del>-    enum class ChangeType { DirectChild, InnerSlot };
-    void didChangeSlot(const AtomicString&amp;, ChangeType, ShadowRoot&amp;);
</del><ins>+    void didChangeSlot(const AtomicString&amp;, ShadowRoot&amp;);
</ins><span class="cx">     void enqueueSlotChangeEvent(const AtomicString&amp;, ShadowRoot&amp;);
</span><span class="cx"> 
</span><span class="cx">     const Vector&lt;Node*&gt;* assignedNodesForSlot(const HTMLSlotElement&amp;, ShadowRoot&amp;);
</span><span class="lines">@@ -99,13 +98,13 @@
</span><span class="cx"> inline void ShadowRoot::didRemoveAllChildrenOfShadowHost()
</span><span class="cx"> {
</span><span class="cx">     if (m_slotAssignment) // FIXME: This is incorrect when there were no elements or text nodes removed.
</span><del>-        m_slotAssignment-&gt;didChangeSlot(nullAtom, SlotAssignment::ChangeType::DirectChild, *this);
</del><ins>+        m_slotAssignment-&gt;didChangeSlot(nullAtom, *this);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> inline void ShadowRoot::didChangeDefaultSlot()
</span><span class="cx"> {
</span><span class="cx">     if (m_slotAssignment)
</span><del>-        m_slotAssignment-&gt;didChangeSlot(nullAtom, SlotAssignment::ChangeType::DirectChild, *this);
</del><ins>+        m_slotAssignment-&gt;didChangeSlot(nullAtom, *this);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> inline void ShadowRoot::hostChildElementDidChange(const Element&amp; childElement)
</span><span class="lines">@@ -117,15 +116,9 @@
</span><span class="cx"> inline void ShadowRoot::hostChildElementDidChangeSlotAttribute(const AtomicString&amp; oldValue, const AtomicString&amp; newValue)
</span><span class="cx"> {
</span><span class="cx">     if (m_slotAssignment) {
</span><del>-        m_slotAssignment-&gt;didChangeSlot(oldValue, SlotAssignment::ChangeType::DirectChild, *this);
-        m_slotAssignment-&gt;didChangeSlot(newValue, SlotAssignment::ChangeType::DirectChild, *this);
</del><ins>+        m_slotAssignment-&gt;didChangeSlot(oldValue, *this);
+        m_slotAssignment-&gt;didChangeSlot(newValue, *this);
</ins><span class="cx">     }
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-inline void ShadowRoot::innerSlotDidChange(const AtomicString&amp; name)
-{
-    if (m_slotAssignment)
-        m_slotAssignment-&gt;didChangeSlot(name, SlotAssignment::ChangeType::InnerSlot, *this);
-}
-
</del><span class="cx"> } // namespace WebCore
</span></span></pre></div>
<a id="trunkSourceWebCorehtmlHTMLDetailsElementcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/html/HTMLDetailsElement.cpp (208816 => 208817)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/html/HTMLDetailsElement.cpp        2016-11-16 23:05:00 UTC (rev 208816)
+++ trunk/Source/WebCore/html/HTMLDetailsElement.cpp        2016-11-16 23:06:47 UTC (rev 208817)
</span><span class="lines">@@ -64,9 +64,9 @@
</span><span class="cx">     if (is&lt;HTMLSummaryElement&gt;(childElement)) {
</span><span class="cx">         // Don't check whether this is the first summary element
</span><span class="cx">         // since we don't know the answer when this function is called inside Element::removedFrom.
</span><del>-        didChangeSlot(summarySlotName(), ChangeType::DirectChild, shadowRoot);
</del><ins>+        didChangeSlot(summarySlotName(), shadowRoot);
</ins><span class="cx">     } else
</span><del>-        didChangeSlot(SlotAssignment::defaultSlotName(), ChangeType::DirectChild, shadowRoot);
</del><ins>+        didChangeSlot(SlotAssignment::defaultSlotName(), shadowRoot);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> const AtomicString&amp; DetailsSlotAssignment::slotNameForHostChild(const Node&amp; child) const
</span></span></pre></div>
<a id="trunkSourceWebCorehtmlHTMLSlotElementcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/html/HTMLSlotElement.cpp (208816 => 208817)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/html/HTMLSlotElement.cpp        2016-11-16 23:05:00 UTC (rev 208816)
+++ trunk/Source/WebCore/html/HTMLSlotElement.cpp        2016-11-16 23:06:47 UTC (rev 208817)
</span><span class="lines">@@ -144,7 +144,7 @@
</span><span class="cx"> {
</span><span class="cx">     m_inSignalSlotList = false;
</span><span class="cx"> 
</span><del>-    bool bubbles = false;
</del><ins>+    bool bubbles = true;
</ins><span class="cx">     bool cancelable = false;
</span><span class="cx">     Ref&lt;Event&gt; event = Event::create(eventNames().slotchangeEvent, bubbles, cancelable);
</span><span class="cx">     event-&gt;setTarget(this);
</span></span></pre>
</div>
</div>

</body>
</html>