<!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>[198115] 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/198115">198115</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2016-03-14 05:02:21 -0700 (Mon, 14 Mar 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>Add slotchange event
https://bugs.webkit.org/show_bug.cgi?id=155424
&lt;rdar://problem/24997534&gt;

Reviewed by Antti Koivisto.

Source/WebCore:

Added `slotchange` event as discussed on https://github.com/w3c/webcomponents/issues/288.

While the exact semantics of it could still evolve over time, this patch implements as
an asynchronous event that fires on a slot element whenever its distributed nodes change
(flattened assigned nodes):
http://w3c.github.io/webcomponents/spec/shadow/#dfn-distributed-nodes

Since inserting or removing an element from a shadow host could needs to enqueue this event
on the right slot element, this patch moves the invalidation point of element removals and
insertions from Element::childrenChanged to Element::insertedInto and Element::removedFrom.
Text nodes are still invalidated at Element::childrenChanged for performance reasons
since it could only appear within a default slot element.

Because this more fine-grained invalidation needs to be overridden by HTMLDetailsElement,
we now subclass SlotAssignment in HTMLDetailsElement instead of passing in a std::function.

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

* dom/Document.cpp:
(WebCore::Document::enqueueSlotchangeEvent): Added.
* dom/Document.h:
* dom/Element.cpp:
(WebCore::Element::attributeChanged): Call hostChildElementDidChangeSlotAttr.
(WebCore::Element::insertedInto): Call hostChildElementDidChange.
(WebCore::Element::removedFrom): Ditto.
(WebCore::Element::childrenChanged): Don't invalidate the slots on ElementInserted and
ElementRemoved since they're now done in Element::insertedInto and Element::removedFrom.
* dom/Event.cpp:
(WebCore::Event::scoped): slotchange event is scoped.
* dom/EventNames.h: Added eventNames().slotchange.
* dom/ShadowRoot.cpp:
(WebCore::ShadowRoot::invalidateSlotAssignments): Deleted.
(WebCore::ShadowRoot::invalidateDefaultSlotAssignments): Deleted.
* dom/ShadowRoot.h:
(ShadowRoot): Added more fine-grained invalidators, mirroring changes to SlotAssignment.
* dom/SlotAssignment.cpp:
(WebCore::SlotAssignment::SlotAssignment): Removed a variant that takes SlotNameFunction
since HTMLDetailsElement now subclasses SlotAssignment.
(WebCore::SlotAssignment::~SlotAssignment): Added now that the class is virtual.
(WebCore::recursivelyFireSlotChangeEvent): Added.
(WebCore::SlotAssignment::didChangeSlot): Added. Invalidates the style tree only if there
is a corresponding slot element, and fires slotchange event. When the slot element we found
in this shadow tree is assigned to a slot element inside an inner shadow tree, recursively
fire slotchange event on each such inner slots.
(WebCore::SlotAssignment::hostChildElementDidChange): Added. Update the matching slot when
an element is inserted or removed under a shadow host.
(WebCore::SlotAssignment::assignedNodesForSlot): Removed the superfluous early exit to an
release assert since addSlotElementByName should always create a SlotInfo for each element.
(WebCore::SlotAssignment::slotNameForHostChild): Added. This is the equivalent of old
m_slotNameFunction which DetailsSlotAssignment overrides.
(WebCore::SlotAssignment::invalidateDefaultSlot): Deleted.
(WebCore::SlotAssignment::findFirstSlotElement): Added an assertion. slotInfo.element must
be nullptr if elementCount is 0, and elementCount must be 0 if slotInfo.element is nullptr
after calling resolveAllSlotElements, which traverses the entire shadow tree to find all
slot elements.
(WebCore::SlotAssignment::assignSlots):
* dom/SlotAssignment.h: Implemented inline functions of ShadowRoot here to avoid including
SlotAssignment.h in ShadowRoot.h. Not inlining them results in extra function calls for all
builtin elements with shadow root without slot elements, which impacts performance.
(WebCore::ShadowRoot::didRemoveAllChildrenOfShadowHost): Added.
(WebCore::ShadowRoot::didChangeDefaultSlot): Added.
(WebCore::ShadowRoot::hostChildElementDidChange): Added.
(WebCore::ShadowRoot::hostChildElementDidChangeSlotAttribute): Added.
(WebCore::ShadowRoot::innerSlotDidChange):
* html/HTMLDetailsElement.cpp:
(WebCore::DetailsSlotAssignment): Added. Subclasses SlotAssignment to override
hostChildElementDidChange and slotNameForHostChild.
(WebCore::DetailsSlotAssignment::hostChildElementDidChange): Added. We don't check if this
is the first summary element since we don't know the answer when this function is called
inside Element::removedFrom.
(WebCore::DetailsSlotAssignment::slotNameForHostChild): Renamed from slotNameFunction. Also
removed the code to return nullAtom when details element is not open as that messes up new
fine-grained invalidation. Insert/remove the slot element in parseAttribute instead.
(WebCore::HTMLDetailsElement::didAddUserAgentShadowRoot): Don't insert the slot element for
the summary since the details element is not open now.
(WebCore::HTMLDetailsElement::parseAttribute): Remove and insert the slot element for the
summary here instead of changing the behavior of slotNameForHostChild.
* html/HTMLDetailsElement.h:
* html/HTMLSlotElement.cpp:
(WebCore::HTMLSlotElement::enqueueSlotChangeEvent): Added. Enqueues a new slotchange event
if we haven't done so for this element yet.
(WebCore::HTMLSlotElement::dispatchEvent): Added. Clear m_hasEnqueuedSlotChangeEvent when
dispatching a slotchange event so that a subsequent call to enqueueSlotChangeEvent would
enqueue a new event. Note scripts call EventTarget::dispatchEventForBindings instead.
* html/HTMLSlotElement.h:

LayoutTests:

Added a W3C style testharness.js test.

* fast/shadow-dom/ShadowRoot-interface-expected.txt:
* fast/shadow-dom/ShadowRoot-interface.html: Don't import testharness.css from svn.webkit.org.
* fast/shadow-dom/slotchange-event-expected.txt: Added.
* fast/shadow-dom/slotchange-event.html: Added.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkLayoutTestsfastshadowdomShadowRootinterfaceexpectedtxt">trunk/LayoutTests/fast/shadow-dom/ShadowRoot-interface-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastshadowdomShadowRootinterfacehtml">trunk/LayoutTests/fast/shadow-dom/ShadowRoot-interface.html</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoredomDocumentcpp">trunk/Source/WebCore/dom/Document.cpp</a></li>
<li><a href="#trunkSourceWebCoredomDocumenth">trunk/Source/WebCore/dom/Document.h</a></li>
<li><a href="#trunkSourceWebCoredomElementcpp">trunk/Source/WebCore/dom/Element.cpp</a></li>
<li><a href="#trunkSourceWebCoredomEventcpp">trunk/Source/WebCore/dom/Event.cpp</a></li>
<li><a href="#trunkSourceWebCoredomEventNamesh">trunk/Source/WebCore/dom/EventNames.h</a></li>
<li><a href="#trunkSourceWebCoredomShadowRootcpp">trunk/Source/WebCore/dom/ShadowRoot.cpp</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="#trunkSourceWebCorehtmlHTMLDetailsElementh">trunk/Source/WebCore/html/HTMLDetailsElement.h</a></li>
<li><a href="#trunkSourceWebCorehtmlHTMLSlotElementcpp">trunk/Source/WebCore/html/HTMLSlotElement.cpp</a></li>
<li><a href="#trunkSourceWebCorehtmlHTMLSlotElementh">trunk/Source/WebCore/html/HTMLSlotElement.h</a></li>
</ul>

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

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/LayoutTests/ChangeLog        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -1,3 +1,18 @@
</span><ins>+2016-03-14  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        Add slotchange event
+        https://bugs.webkit.org/show_bug.cgi?id=155424
+        &lt;rdar://problem/24997534&gt;
+
+        Reviewed by Antti Koivisto.
+
+        Added a W3C style testharness.js test.
+
+        * fast/shadow-dom/ShadowRoot-interface-expected.txt:
+        * fast/shadow-dom/ShadowRoot-interface.html: Don't import testharness.css from svn.webkit.org.
+        * fast/shadow-dom/slotchange-event-expected.txt: Added.
+        * fast/shadow-dom/slotchange-event.html: Added.
+
</ins><span class="cx"> 2016-03-13  Darin Adler  &lt;darin@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Add copy/paste plug-in check for XHTML document
</span></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomShadowRootinterfaceexpectedtxt"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/fast/shadow-dom/ShadowRoot-interface-expected.txt (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/shadow-dom/ShadowRoot-interface-expected.txt        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/LayoutTests/fast/shadow-dom/ShadowRoot-interface-expected.txt        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -1,4 +1,3 @@
</span><del>-Blocked access to external URL https://svn.webkit.org/repository/webkit/trunk/LayoutTests/resources/testharness.css
</del><span class="cx"> 
</span><span class="cx"> PASS Check the existence of ShadowRoot interface 
</span><span class="cx"> PASS ShadowRoot must inherit from DocumentFragment 
</span></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomShadowRootinterfacehtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/fast/shadow-dom/ShadowRoot-interface.html (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/shadow-dom/ShadowRoot-interface.html        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/LayoutTests/fast/shadow-dom/ShadowRoot-interface.html        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -7,7 +7,7 @@
</span><span class="cx"> &lt;link rel=&quot;help&quot; href=&quot;https://w3c.github.io/webcomponents/spec/shadow/#the-shadowroot-interface&quot;&gt;
</span><span class="cx"> &lt;script src=&quot;../../resources/testharness.js&quot;&gt;&lt;/script&gt;
</span><span class="cx"> &lt;script src=&quot;../../resources/testharnessreport.js&quot;&gt;&lt;/script&gt;
</span><del>-&lt;link rel='stylesheet' href='https://svn.webkit.org/repository/webkit/trunk/LayoutTests/resources/testharness.css'&gt;
</del><ins>+&lt;link rel='stylesheet' href='../../resources/testharness.css'&gt;
</ins><span class="cx"> &lt;/head&gt;
</span><span class="cx"> &lt;body&gt;
</span><span class="cx"> &lt;div id=&quot;log&quot;&gt;&lt;/div&gt;
</span></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomslotchangeeventexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/slotchange-event-expected.txt (0 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/shadow-dom/slotchange-event-expected.txt                                (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/slotchange-event-expected.txt        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -0,0 +1,32 @@
</span><ins>+hello
+hello
+
+PASS slotchange event must fire on a default slot element inside an open shadow root  in a document 
+PASS slotchange event must fire on a default slot element inside a closed shadow root  in a document 
+PASS slotchange event must fire on a default slot element inside an open shadow root  not in a document 
+PASS slotchange event must fire on a default slot element inside a closed shadow root  not in a document 
+PASS slotchange event must fire on a named slot element insidean open shadow root  in a document 
+PASS slotchange event must fire on a named slot element insidea closed shadow root  in a document 
+PASS slotchange event must fire on a named slot element insidean open shadow root  not in a document 
+PASS slotchange event must fire on a named slot element insidea closed shadow root  not in a document 
+PASS slotchange event must not fire on a slot element inside an open shadow root  in a document when another slot's assigned nodes change 
+PASS slotchange event must not fire on a slot element inside a closed shadow root  in a document when another slot's assigned nodes change 
+PASS slotchange event must not fire on a slot element inside an open shadow root  not in a document when another slot's assigned nodes change 
+PASS slotchange event must not fire on a slot element inside a closed shadow root  not in a document when another slot's assigned nodes change 
+PASS slotchange event must not fire on a slot element inside an open shadow root  in a document when the shadow host was mutated before the slot was inserted or after the slot was removed 
+PASS slotchange event must not fire on a slot element inside a closed shadow root  in a document when the shadow host was mutated before the slot was inserted or after the slot was removed 
+PASS slotchange event must not fire on a slot element inside an open shadow root  not in a document when the shadow host was mutated before the slot was inserted or after the slot was removed 
+PASS slotchange event must not fire on a slot element inside a closed shadow root  not in a document when the shadow host was mutated before the slot was inserted or after the slot was removed 
+PASS slotchange event must fire on a slot element inside an open shadow root  in a document even if the slot was removed immediately after the assigned nodes were mutated 
+PASS slotchange event must fire on a slot element inside a closed shadow root  in a document even if the slot was removed immediately after the assigned nodes were mutated 
+PASS slotchange event must fire on a slot element inside an open shadow root  not in a document even if the slot was removed immediately after the assigned nodes were mutated 
+PASS slotchange event must fire on a slot element inside a closed shadow root  not in a document even if the slot was removed immediately after the assigned nodes were mutated 
+PASS slotchange event must fire on a slot element inside an open shadow root  in a document when innerHTML modifies the children of the shadow host 
+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 
+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 
+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 
+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 
+
</ins></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomslotchangeeventhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/slotchange-event.html (0 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/shadow-dom/slotchange-event.html                                (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/slotchange-event.html        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -0,0 +1,539 @@
</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;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 treeName(mode, connectedToDocument)
+{
+    return (mode == 'open' ? 'an ' : 'a ') + mode + ' shadow root '
+        + (connectedToDocument ? '' : ' not') + ' in a document';
+}
+
+function testAppendingSpanToShadowRootWithDefaultSlot(mode, connectedToDocument)
+{
+    var test = async_test('slotchange event must fire on a default slot element inside '
+        + treeName(mode, connectedToDocument));
+
+    var host;
+    var slot;
+    var eventCount = 0;
+
+    test.step(function () {
+        host = document.createElement('div');
+        if (connectedToDocument)
+            document.body.appendChild(host);
+
+        var shadowRoot = host.attachShadow({'mode': mode});
+        slot = document.createElement('slot');
+
+        slot.addEventListener('slotchange', function (event) {
+            if (event.isFakeEvent)
+                return;
+
+            test.step(function () {
+                assert_equals(event.type, 'slotchange', 'slotchange event\'s type must be &quot;slotchange&quot;');
+                assert_equals(event.target, slot, 'slotchange event\'s target must be the slot element');
+                assert_equals(event.relatedTarget, undefined, 'slotchange must not set relatedTarget');
+            });
+            eventCount++;
+        });
+
+        shadowRoot.appendChild(slot);
+
+        host.appendChild(document.createElement('span'));
+        host.appendChild(document.createElement('b'));
+
+        assert_equals(eventCount, 0, 'slotchange event must not be fired synchronously');
+    });
+
+    setTimeout(function () {
+        test.step(function () {
+            assert_equals(eventCount, 1, 'slotchange must be fired exactly once after the assigned nodes changed');
+
+            host.appendChild(document.createElement('i'));
+        });
+
+        setTimeout(function () {
+            test.step(function () {
+                assert_equals(eventCount, 2, 'slotchange must be fired exactly once after the assigned nodes changed');
+
+                host.appendChild(document.createTextNode('hello'));
+
+                var fakeEvent = new Event('slotchange');
+                fakeEvent.isFakeEvent = true;
+                slot.dispatchEvent(fakeEvent);
+            });
+
+            setTimeout(function () {
+                test.step(function () {
+                    assert_equals(eventCount, 3, 'slotchange must be fired exactly once after the assigned nodes changed'
+                        + ' event if there was a synthetic slotchange event fired');
+                });
+                test.done();
+            }, 1);
+        }, 1);
+    }, 1);
+}
+
+testAppendingSpanToShadowRootWithDefaultSlot('open', true);
+testAppendingSpanToShadowRootWithDefaultSlot('closed', true);
+testAppendingSpanToShadowRootWithDefaultSlot('open', false);
+testAppendingSpanToShadowRootWithDefaultSlot('closed', false);
+
+function testAppendingSpanToShadowRootWithNamedSlot(mode, connectedToDocument)
+{
+    var test = async_test('slotchange event must fire on a named slot element inside'
+        + treeName(mode, connectedToDocument));
+
+    var host;
+    var slot;
+    var eventCount = 0;
+
+    test.step(function () {
+        host = document.createElement('div');
+        if (connectedToDocument)
+            document.body.appendChild(host);
+
+        var shadowRoot = host.attachShadow({'mode': mode});
+        slot = document.createElement('slot');
+        slot.name = 'someSlot';
+
+        slot.addEventListener('slotchange', function (event) {
+            if (event.isFakeEvent)
+                return;
+
+            test.step(function () {
+                assert_equals(event.type, 'slotchange', 'slotchange event\'s type must be &quot;slotchange&quot;');
+                assert_equals(event.target, slot, 'slotchange event\'s target must be the slot element');
+                assert_equals(event.relatedTarget, undefined, 'slotchange must not set relatedTarget');
+            });
+            eventCount++;
+        });
+
+        shadowRoot.appendChild(slot);
+
+        var span = document.createElement('span');
+        span.slot = 'someSlot';
+        host.appendChild(span);
+
+        var b = document.createElement('b');
+        b.slot = 'someSlot';
+        host.appendChild(b);
+
+        assert_equals(eventCount, 0, 'slotchange event must not be fired synchronously');
+    });
+
+    setTimeout(function () {
+        test.step(function () {
+            assert_equals(eventCount, 1, 'slotchange must be fired exactly once after the assigned nodes changed');
+
+            var i = document.createElement('i');
+            i.slot = 'someSlot';
+            host.appendChild(i);
+        });
+
+        setTimeout(function () {
+            test.step(function () {
+                assert_equals(eventCount, 2, 'slotchange must be fired exactly once after the assigned nodes changed');
+
+                var em = document.createElement('em');
+                em.slot = 'someSlot';
+                host.appendChild(em);
+
+                var fakeEvent = new Event('slotchange');
+                fakeEvent.isFakeEvent = true;
+                slot.dispatchEvent(fakeEvent);
+            });
+
+            setTimeout(function () {
+                test.step(function () {
+                    assert_equals(eventCount, 3, 'slotchange must be fired exactly once after the assigned nodes changed'
+                        + ' event if there was a synthetic slotchange event fired');
+                });
+                test.done();
+            }, 1);
+
+        }, 1);
+    }, 1);
+}
+
+testAppendingSpanToShadowRootWithNamedSlot('open', true);
+testAppendingSpanToShadowRootWithNamedSlot('closed', true);
+testAppendingSpanToShadowRootWithNamedSlot('open', false);
+testAppendingSpanToShadowRootWithNamedSlot('closed', false);
+
+function testSlotchangeDoesNotFireWhenOtherSlotsChange(mode, connectedToDocument)
+{
+    var test = async_test('slotchange event must not fire on a slot element inside '
+        + treeName(mode, connectedToDocument)
+        + ' when another slot\'s assigned nodes change');
+
+    var host;
+    var defaultSlotEventCount = 0;
+    var namedSlotEventCount = 0;
+
+    test.step(function () {
+        host = document.createElement('div');
+        if (connectedToDocument)
+            document.body.appendChild(host);
+
+        var shadowRoot = host.attachShadow({'mode': mode});
+        var defaultSlot = document.createElement('slot');
+        defaultSlot.addEventListener('slotchange', function (event) {
+            test.step(function () {
+                assert_equals(event.target, defaultSlot, 'slotchange event\'s target must be the slot element');
+            });
+            defaultSlotEventCount++;
+        });
+
+        var namedSlot = document.createElement('slot');
+        namedSlot.name = 'slotName';
+        namedSlot.addEventListener('slotchange', function (event) {
+            test.step(function () {
+                assert_equals(event.target, namedSlot, 'slotchange event\'s target must be the slot element');
+            });
+            namedSlotEventCount++;
+        });
+
+        shadowRoot.appendChild(defaultSlot);
+        shadowRoot.appendChild(namedSlot);
+
+        host.appendChild(document.createElement('span'));
+
+        assert_equals(defaultSlotEventCount, 0, 'slotchange event must not be fired synchronously');
+        assert_equals(namedSlotEventCount, 0, 'slotchange event must not be fired synchronously');
+    });
+
+    setTimeout(function () {
+        test.step(function () {
+            assert_equals(defaultSlotEventCount, 1,
+                'slotchange must be fired exactly once after the assigned nodes change on a default slot');
+            assert_equals(namedSlotEventCount, 0,
+                'slotchange must not be fired on a named slot after the assigned nodes change on a default slot');
+
+            var span = document.createElement('span');
+            span.slot = 'slotName';
+            host.appendChild(span);
+        });
+
+        setTimeout(function () {
+            test.step(function () {
+                assert_equals(defaultSlotEventCount, 1,
+                    'slotchange must not be fired on a default slot after the assigned nodes change on a named slot');
+                assert_equals(namedSlotEventCount, 1,
+                    'slotchange must be fired exactly once after the assigned nodes change on a default slot');
+            });
+            test.done();
+        }, 1);
+    }, 1);
+}
+
+testSlotchangeDoesNotFireWhenOtherSlotsChange('open', true);
+testSlotchangeDoesNotFireWhenOtherSlotsChange('closed', true);
+testSlotchangeDoesNotFireWhenOtherSlotsChange('open', false);
+testSlotchangeDoesNotFireWhenOtherSlotsChange('closed', false);
+
+function testSlotchangeDoesNotFireForMutationBeforeOrAfterSlotWasPresent(mode, connectedToDocument)
+{
+    var test = async_test('slotchange event must not fire on a slot element inside '
+        + treeName(mode, connectedToDocument)
+        + ' when the shadow host was mutated before the slot was inserted or after the slot was removed');
+
+    var host;
+    var slot;
+    var eventCount = 0;
+
+    test.step(function () {
+        host = document.createElement('div');
+        if (connectedToDocument)
+            document.body.appendChild(host);
+
+        var shadowRoot = host.attachShadow({'mode': mode});
+        slot = document.createElement('slot');
+        slot.addEventListener('slotchange', function (event) {
+            test.step(function () {
+                assert_equals(event.target, slot, 'slotchange event\'s target must be the slot element');
+            });
+            eventCount++;
+        });
+
+        host.appendChild(document.createElement('span'));
+        shadowRoot.appendChild(slot);
+
+        assert_equals(eventCount, 0, 'slotchange event must not be fired synchronously');
+    });
+
+    setTimeout(function () {
+        test.step(function () {
+            assert_equals(eventCount, 0,
+                'slotchange must not be fired on a slot element if the assigned nodes changed before the slot was inserted');
+            host.removeChild(host.firstChild);
+        });
+
+        setTimeout(function () {
+            test.step(function () {
+                assert_equals(eventCount, 1,
+                    'slotchange must be fired exactly once after the assigned nodes change on a slot while the slot element was in the tree');
+                slot.parentNode.removeChild(slot);
+                host.appendChild(document.createElement('span'));
+            });
+
+            setTimeout(function () {
+                assert_equals(eventCount, 1,
+                    'slotchange must not be fired on a slot element if the assigned nodes changed after the slot was removed');
+                test.done();
+            }, 1);
+        }, 1);
+    }, 1);
+}
+
+testSlotchangeDoesNotFireForMutationBeforeOrAfterSlotWasPresent('open', true);
+testSlotchangeDoesNotFireForMutationBeforeOrAfterSlotWasPresent('closed', true);
+testSlotchangeDoesNotFireForMutationBeforeOrAfterSlotWasPresent('open', false);
+testSlotchangeDoesNotFireForMutationBeforeOrAfterSlotWasPresent('closed', false);
+
+function testSlotchangeFiresOnTransientlyPresentSlot(mode, connectedToDocument)
+{
+    var test = async_test('slotchange event must fire on a slot element inside '
+        + treeName(mode, connectedToDocument)
+        + ' even if the slot was removed immediately after the assigned nodes were mutated');
+
+    var host;
+    var slot;
+    var anotherSlot;
+    var slotEventCount = 0;
+    var anotherSlotEventCount = 0;
+
+    test.step(function () {
+        host = document.createElement('div');
+        if (connectedToDocument)
+            document.body.appendChild(host);
+
+        var shadowRoot = host.attachShadow({'mode': mode});
+        slot = document.createElement('slot');
+        slot.name = 'someSlot';
+        slot.addEventListener('slotchange', function (event) {
+            test.step(function () {
+                assert_equals(event.target, slot, 'slotchange event\'s target must be the slot element');
+            });
+            slotEventCount++;
+        });
+
+        anotherSlot = document.createElement('slot');
+        anotherSlot.name = 'someSlot';
+        anotherSlot.addEventListener('slotchange', function (event) {
+            test.step(function () {
+                assert_equals(event.target, anotherSlot, 'slotchange event\'s target must be the slot element');
+            });
+            anotherSlotEventCount++;
+        });
+
+        shadowRoot.appendChild(slot);
+
+        var span = document.createElement('span');
+        span.slot = 'someSlot';
+        host.appendChild(span);
+
+        shadowRoot.removeChild(slot);
+        shadowRoot.appendChild(anotherSlot);
+
+        var span = document.createElement('span');
+        span.slot = 'someSlot';
+        host.appendChild(span);
+
+        shadowRoot.removeChild(anotherSlot);
+
+        assert_equals(slotEventCount, 0, 'slotchange event must not be fired synchronously');
+        assert_equals(anotherSlotEventCount, 0, 'slotchange event must not be fired synchronously');
+    });
+
+    setTimeout(function () {
+        test.step(function () {
+            assert_equals(slotEventCount, 1,
+                'slotchange must be fired on a slot element if the assigned nodes changed while the slot was present');
+            assert_equals(anotherSlotEventCount, 1,
+                'slotchange must be fired on a slot element if the assigned nodes changed while the slot was present');
+        });
+        test.done();
+    }, 1);
+}
+
+testSlotchangeFiresOnTransientlyPresentSlot('open', true);
+testSlotchangeFiresOnTransientlyPresentSlot('closed', true);
+testSlotchangeFiresOnTransientlyPresentSlot('open', false);
+testSlotchangeFiresOnTransientlyPresentSlot('closed', false);
+
+function testSlotchangeFiresOnInnerHTML(mode, connectedToDocument)
+{
+    var test = async_test('slotchange event must fire on a slot element inside '
+        + treeName(mode, connectedToDocument)
+        + ' when innerHTML modifies the children of the shadow host');
+
+    var host;
+    var defaultSlot;
+    var namedSlot;
+    var defaultSlotEventCount = 0;
+    var namedSlotEventCount = 0;
+
+    test.step(function () {
+        host = document.createElement('div');
+        if (connectedToDocument)
+            document.body.appendChild(host);
+
+        var shadowRoot = host.attachShadow({'mode': mode});
+        defaultSlot = document.createElement('slot');
+        defaultSlot.addEventListener('slotchange', function (event) {
+            test.step(function () {
+                assert_equals(event.target, defaultSlot, 'slotchange event\'s target must be the slot element');
+            });
+            defaultSlotEventCount++;
+        });
+
+        namedSlot = document.createElement('slot');
+        namedSlot.name = 'someSlot';
+        namedSlot.addEventListener('slotchange', function (event) {
+            test.step(function () {
+                assert_equals(event.target, namedSlot, 'slotchange event\'s target must be the slot element');
+            });
+            namedSlotEventCount++;
+        });
+        shadowRoot.appendChild(namedSlot);
+        shadowRoot.appendChild(defaultSlot);
+        host.innerHTML = 'foo &lt;b&gt;bar&lt;/b&gt;';
+
+        assert_equals(defaultSlotEventCount, 0, 'slotchange event must not be fired synchronously');
+        assert_equals(namedSlotEventCount, 0, 'slotchange event must not be fired synchronously');
+    });
+
+    setTimeout(function () {
+        test.step(function () {
+            assert_equals(defaultSlotEventCount, 1,
+                'slotchange must be fired on a slot element if the assigned nodes are changed by innerHTML');
+            assert_equals(namedSlotEventCount, 0,
+                'slotchange must not be fired on a slot element if the assigned nodes are not changed by innerHTML');
+            host.innerHTML = 'baz';
+        });
+        setTimeout(function () {
+            test.step(function () {
+                assert_equals(defaultSlotEventCount, 2,
+                    'slotchange must be fired on a slot element if the assigned nodes are changed by innerHTML');
+                assert_equals(namedSlotEventCount, 0,
+                    'slotchange must not be fired on a slot element if the assigned nodes are not changed by innerHTML');
+                host.innerHTML = '';
+            });
+            setTimeout(function () {
+                test.step(function () {
+                    assert_equals(defaultSlotEventCount, 3,
+                        'slotchange must be fired on a slot element if the assigned nodes are changed by innerHTML');
+                    assert_equals(namedSlotEventCount, 0,
+                        'slotchange must not be fired on a slot element if the assigned nodes are not changed by innerHTML');
+                    host.innerHTML = '&lt;b slot=&quot;someSlot&quot;&gt;content&lt;/b&gt;';
+                });
+                setTimeout(function () {
+                    test.step(function () {
+                        assert_equals(defaultSlotEventCount, 3,
+                            'slotchange must not be fired on a slot element if the assigned nodes are not changed by innerHTML');
+                        assert_equals(namedSlotEventCount, 1,
+                            'slotchange must not be fired on a slot element if the assigned nodes are changed by innerHTML');
+                        host.innerHTML = '';
+                    });
+                    setTimeout(function () {
+                        test.step(function () {
+                            // FIXME: This test would fail in the current implementation because we can't tell
+                            // whether a text node was removed in AllChildrenRemoved or not.
+//                            assert_equals(defaultSlotEventCount, 3,
+//                                'slotchange must not be fired on a slot element if the assigned nodes are not changed by innerHTML');
+                            assert_equals(namedSlotEventCount, 2,
+                                'slotchange must not be fired on a slot element if the assigned nodes are changed by innerHTML');
+                        });
+                        test.done();
+                    }, 1);
+                }, 1);
+            }, 1);
+        }, 1);
+    }, 1);
+}
+
+testSlotchangeFiresOnInnerHTML('open', true);
+testSlotchangeFiresOnInnerHTML('closed', true);
+testSlotchangeFiresOnInnerHTML('open', false);
+testSlotchangeFiresOnInnerHTML('closed', false);
+
+function testSlotchangeFiresWhenNestedSlotChange(mode, connectedToDocument)
+{
+    var test = async_test('slotchange event must fire on a slot element inside '
+        + treeName(mode, connectedToDocument)
+        + ' when nested slots\'s contents change');
+
+    var outerHost;
+    var innerHost;
+    var outerSlot;
+    var innerSlot;
+    var outerSlotEventCount = 0;
+    var innerSlotEventCount = 0;
+
+    test.step(function () {
+        outerHost = document.createElement('div');
+        if (connectedToDocument)
+            document.body.appendChild(outerHost);
+
+        var outerShadow = outerHost.attachShadow({'mode': mode});
+        outerShadow.appendChild(document.createElement('span'));
+        outerSlot = document.createElement('slot');
+        outerSlot.addEventListener('slotchange', function (event) {
+            event.stopPropagation();
+            test.step(function () {
+                assert_equals(event.target, outerSlot, 'slotchange event\'s target must be the slot element');
+            });
+            outerSlotEventCount++;
+        });
+
+        innerHost = document.createElement('div');
+        innerHost.appendChild(outerSlot);
+        outerShadow.appendChild(innerHost);
+
+        var innerShadow = innerHost.attachShadow({'mode': mode});
+        innerShadow.appendChild(document.createElement('span'));
+        innerSlot = document.createElement('slot');
+        innerSlot.addEventListener('slotchange', function (event) {
+            event.stopPropagation();
+            test.step(function () {
+                assert_equals(event.target, innerSlot, 'slotchange event\'s target must be the slot element');
+            });
+            innerSlotEventCount++;
+        });
+        innerShadow.appendChild(innerSlot);
+
+        outerHost.appendChild(document.createElement('span'));
+
+        assert_equals(innerSlotEventCount, 0, 'slotchange event must not be fired synchronously');
+        assert_equals(outerSlotEventCount, 0, 'slotchange event must not be fired synchronously');
+    });
+
+    setTimeout(function () {
+        test.step(function () {
+            assert_equals(innerSlotEventCount, 1,
+                'slotchange must be fired on a slot element if the assigned nodes changed');
+            assert_equals(outerSlotEventCount, 1,
+                'slotchange must be fired on a slot element if the assigned nodes of an inner slot changed');
+        });
+        test.done();
+    }, 1);
+}
+
+testSlotchangeFiresWhenNestedSlotChange('open', true);
+testSlotchangeFiresWhenNestedSlotChange('closed', true);
+testSlotchangeFiresWhenNestedSlotChange('open', false);
+testSlotchangeFiresWhenNestedSlotChange('closed', false);
+
+&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 (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/ChangeLog        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -1,3 +1,97 @@
</span><ins>+2016-03-14  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        Add slotchange event
+        https://bugs.webkit.org/show_bug.cgi?id=155424
+        &lt;rdar://problem/24997534&gt;
+
+        Reviewed by Antti Koivisto.
+
+        Added `slotchange` event as discussed on https://github.com/w3c/webcomponents/issues/288.
+
+        While the exact semantics of it could still evolve over time, this patch implements as
+        an asynchronous event that fires on a slot element whenever its distributed nodes change
+        (flattened assigned nodes):
+        http://w3c.github.io/webcomponents/spec/shadow/#dfn-distributed-nodes
+
+        Since inserting or removing an element from a shadow host could needs to enqueue this event
+        on the right slot element, this patch moves the invalidation point of element removals and
+        insertions from Element::childrenChanged to Element::insertedInto and Element::removedFrom.
+        Text nodes are still invalidated at Element::childrenChanged for performance reasons
+        since it could only appear within a default slot element.
+
+        Because this more fine-grained invalidation needs to be overridden by HTMLDetailsElement,
+        we now subclass SlotAssignment in HTMLDetailsElement instead of passing in a std::function.
+
+        Test: fast/shadow-dom/slotchange-event.html
+
+        * dom/Document.cpp:
+        (WebCore::Document::enqueueSlotchangeEvent): Added.
+        * dom/Document.h:
+        * dom/Element.cpp:
+        (WebCore::Element::attributeChanged): Call hostChildElementDidChangeSlotAttr.
+        (WebCore::Element::insertedInto): Call hostChildElementDidChange.
+        (WebCore::Element::removedFrom): Ditto.
+        (WebCore::Element::childrenChanged): Don't invalidate the slots on ElementInserted and
+        ElementRemoved since they're now done in Element::insertedInto and Element::removedFrom.
+        * dom/Event.cpp:
+        (WebCore::Event::scoped): slotchange event is scoped.
+        * dom/EventNames.h: Added eventNames().slotchange.
+        * dom/ShadowRoot.cpp:
+        (WebCore::ShadowRoot::invalidateSlotAssignments): Deleted.
+        (WebCore::ShadowRoot::invalidateDefaultSlotAssignments): Deleted.
+        * dom/ShadowRoot.h:
+        (ShadowRoot): Added more fine-grained invalidators, mirroring changes to SlotAssignment.
+        * dom/SlotAssignment.cpp:
+        (WebCore::SlotAssignment::SlotAssignment): Removed a variant that takes SlotNameFunction
+        since HTMLDetailsElement now subclasses SlotAssignment.
+        (WebCore::SlotAssignment::~SlotAssignment): Added now that the class is virtual.
+        (WebCore::recursivelyFireSlotChangeEvent): Added.
+        (WebCore::SlotAssignment::didChangeSlot): Added. Invalidates the style tree only if there
+        is a corresponding slot element, and fires slotchange event. When the slot element we found
+        in this shadow tree is assigned to a slot element inside an inner shadow tree, recursively
+        fire slotchange event on each such inner slots.
+        (WebCore::SlotAssignment::hostChildElementDidChange): Added. Update the matching slot when
+        an element is inserted or removed under a shadow host.
+        (WebCore::SlotAssignment::assignedNodesForSlot): Removed the superfluous early exit to an
+        release assert since addSlotElementByName should always create a SlotInfo for each element.
+        (WebCore::SlotAssignment::slotNameForHostChild): Added. This is the equivalent of old
+        m_slotNameFunction which DetailsSlotAssignment overrides.
+        (WebCore::SlotAssignment::invalidateDefaultSlot): Deleted.
+        (WebCore::SlotAssignment::findFirstSlotElement): Added an assertion. slotInfo.element must
+        be nullptr if elementCount is 0, and elementCount must be 0 if slotInfo.element is nullptr
+        after calling resolveAllSlotElements, which traverses the entire shadow tree to find all
+        slot elements.
+        (WebCore::SlotAssignment::assignSlots):
+        * dom/SlotAssignment.h: Implemented inline functions of ShadowRoot here to avoid including
+        SlotAssignment.h in ShadowRoot.h. Not inlining them results in extra function calls for all
+        builtin elements with shadow root without slot elements, which impacts performance.
+        (WebCore::ShadowRoot::didRemoveAllChildrenOfShadowHost): Added.
+        (WebCore::ShadowRoot::didChangeDefaultSlot): Added.
+        (WebCore::ShadowRoot::hostChildElementDidChange): Added.
+        (WebCore::ShadowRoot::hostChildElementDidChangeSlotAttribute): Added.
+        (WebCore::ShadowRoot::innerSlotDidChange):
+        * html/HTMLDetailsElement.cpp:
+        (WebCore::DetailsSlotAssignment): Added. Subclasses SlotAssignment to override
+        hostChildElementDidChange and slotNameForHostChild.
+        (WebCore::DetailsSlotAssignment::hostChildElementDidChange): Added. We don't check if this
+        is the first summary element since we don't know the answer when this function is called
+        inside Element::removedFrom.
+        (WebCore::DetailsSlotAssignment::slotNameForHostChild): Renamed from slotNameFunction. Also
+        removed the code to return nullAtom when details element is not open as that messes up new
+        fine-grained invalidation. Insert/remove the slot element in parseAttribute instead.
+        (WebCore::HTMLDetailsElement::didAddUserAgentShadowRoot): Don't insert the slot element for
+        the summary since the details element is not open now.
+        (WebCore::HTMLDetailsElement::parseAttribute): Remove and insert the slot element for the
+        summary here instead of changing the behavior of slotNameForHostChild.
+        * html/HTMLDetailsElement.h:
+        * html/HTMLSlotElement.cpp:
+        (WebCore::HTMLSlotElement::enqueueSlotChangeEvent): Added. Enqueues a new slotchange event
+        if we haven't done so for this element yet.
+        (WebCore::HTMLSlotElement::dispatchEvent): Added. Clear m_hasEnqueuedSlotChangeEvent when
+        dispatching a slotchange event so that a subsequent call to enqueueSlotChangeEvent would
+        enqueue a new event. Note scripts call EventTarget::dispatchEventForBindings instead.
+        * html/HTMLSlotElement.h:
+
</ins><span class="cx"> 2016-03-14  Youenn Fablet  &lt;youenn.fablet@crf.canon.fr&gt;
</span><span class="cx"> 
</span><span class="cx">         Introduce CallWith=Document in binding generator
</span></span></pre></div>
<a id="trunkSourceWebCoredomDocumentcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/Document.cpp (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/Document.cpp        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/dom/Document.cpp        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -4172,6 +4172,11 @@
</span><span class="cx">     m_eventQueue.enqueueEvent(WTFMove(event));
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void Document::enqueueSlotchangeEvent(Ref&lt;Event&gt;&amp;&amp; event)
+{
+    m_eventQueue.enqueueEvent(WTFMove(event));
+}
+
</ins><span class="cx"> RefPtr&lt;Event&gt; Document::createEvent(const String&amp; type, ExceptionCode&amp; ec)
</span><span class="cx"> {
</span><span class="cx">     // Please do *not* add new event classes to this function unless they are
</span></span></pre></div>
<a id="trunkSourceWebCoredomDocumenth"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/Document.h (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/Document.h        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/dom/Document.h        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -1088,6 +1088,7 @@
</span><span class="cx">     void enqueueWindowEvent(Ref&lt;Event&gt;&amp;&amp;);
</span><span class="cx">     void enqueueDocumentEvent(Ref&lt;Event&gt;&amp;&amp;);
</span><span class="cx">     void enqueueOverflowEvent(Ref&lt;Event&gt;&amp;&amp;);
</span><ins>+    void enqueueSlotchangeEvent(Ref&lt;Event&gt;&amp;&amp;);
</ins><span class="cx">     void enqueuePageshowEvent(PageshowEventPersistence);
</span><span class="cx">     void enqueueHashchangeEvent(const String&amp; oldURL, const String&amp; newURL);
</span><span class="cx">     void enqueuePopstateEvent(RefPtr&lt;SerializedScriptValue&gt;&amp;&amp; stateObject);
</span></span></pre></div>
<a id="trunkSourceWebCoredomElementcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/Element.cpp (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/Element.cpp        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/dom/Element.cpp        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -83,6 +83,7 @@
</span><span class="cx"> #include &quot;SelectorQuery.h&quot;
</span><span class="cx"> #include &quot;Settings.h&quot;
</span><span class="cx"> #include &quot;SimulatedClick.h&quot;
</span><ins>+#include &quot;SlotAssignment.h&quot;
</ins><span class="cx"> #include &quot;StyleProperties.h&quot;
</span><span class="cx"> #include &quot;StyleResolver.h&quot;
</span><span class="cx"> #include &quot;StyleTreeResolver.h&quot;
</span><span class="lines">@@ -1254,7 +1255,7 @@
</span><span class="cx">         else if (name == HTMLNames::slotAttr) {
</span><span class="cx">             if (auto* parent = parentElement()) {
</span><span class="cx">                 if (auto* shadowRoot = parent-&gt;shadowRoot())
</span><del>-                    shadowRoot-&gt;invalidateSlotAssignments();
</del><ins>+                    shadowRoot-&gt;hostChildElementDidChangeSlotAttribute(oldValue, newValue);
</ins><span class="cx">             }
</span><span class="cx">         }
</span><span class="cx"> #endif
</span><span class="lines">@@ -1496,6 +1497,11 @@
</span><span class="cx">         setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true);
</span><span class="cx"> #endif
</span><span class="cx"> 
</span><ins>+    if (parentNode() == &amp;insertionPoint) {
+        if (auto* shadowRoot = parentNode()-&gt;shadowRoot())
+            shadowRoot-&gt;hostChildElementDidChange(*this);
+    }
+
</ins><span class="cx">     if (!insertionPoint.isInTreeScope())
</span><span class="cx">         return InsertionDone;
</span><span class="cx"> 
</span><span class="lines">@@ -1575,6 +1581,11 @@
</span><span class="cx">         }
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    if (!parentNode()) {
+        if (auto* shadowRoot = insertionPoint.shadowRoot())
+            shadowRoot-&gt;hostChildElementDidChange(*this);
+    }
+
</ins><span class="cx">     ContainerNode::removedFrom(insertionPoint);
</span><span class="cx"> 
</span><span class="cx">     if (hasPendingResources())
</span><span class="lines">@@ -1832,13 +1843,15 @@
</span><span class="cx">         switch (change.type) {
</span><span class="cx">         case ElementInserted:
</span><span class="cx">         case ElementRemoved:
</span><ins>+            // For elements, we notify shadowRoot in Element::insertedInto and Element::removedFrom.
+            break;
</ins><span class="cx">         case AllChildrenRemoved:
</span><del>-            shadowRoot-&gt;invalidateSlotAssignments();
</del><ins>+            shadowRoot-&gt;didRemoveAllChildrenOfShadowHost();
</ins><span class="cx">             break;
</span><span class="cx">         case TextInserted:
</span><span class="cx">         case TextRemoved:
</span><span class="cx">         case TextChanged:
</span><del>-            shadowRoot-&gt;invalidateDefaultSlotAssignments();
</del><ins>+            shadowRoot-&gt;didChangeDefaultSlot();
</ins><span class="cx">             break;
</span><span class="cx">         case NonContentsChildChanged:
</span><span class="cx">             break;
</span></span></pre></div>
<a id="trunkSourceWebCoredomEventcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/Event.cpp (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/Event.cpp        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/dom/Event.cpp        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -103,7 +103,8 @@
</span><span class="cx">         || m_type == eventNames().resizeEvent
</span><span class="cx">         || m_type == eventNames().scrollEvent
</span><span class="cx">         || m_type == eventNames().selectEvent
</span><del>-        || m_type == eventNames().selectstartEvent;
</del><ins>+        || m_type == eventNames().selectstartEvent
+        || m_type == eventNames().slotchangeEvent;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> EventInterface Event::eventInterface() const
</span></span></pre></div>
<a id="trunkSourceWebCoredomEventNamesh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/EventNames.h (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/EventNames.h        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/dom/EventNames.h        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -195,6 +195,7 @@
</span><span class="cx">     macro(selectstart) \
</span><span class="cx">     macro(show) \
</span><span class="cx">     macro(signalingstatechange) \
</span><ins>+    macro(slotchange) \
</ins><span class="cx">     macro(soundend) \
</span><span class="cx">     macro(soundstart) \
</span><span class="cx">     macro(sourceclose) \
</span></span></pre></div>
<a id="trunkSourceWebCoredomShadowRootcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/ShadowRoot.cpp (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/ShadowRoot.cpp        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/dom/ShadowRoot.cpp        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -201,18 +201,6 @@
</span><span class="cx">     return m_slotAssignment-&gt;removeSlotElementByName(name, slot, *this);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void ShadowRoot::invalidateSlotAssignments()
-{
-    if (m_slotAssignment)
-        m_slotAssignment-&gt;invalidate(*this);
-}
-
-void ShadowRoot::invalidateDefaultSlotAssignments()
-{
-    if (m_slotAssignment)
-        m_slotAssignment-&gt;invalidateDefaultSlot(*this);
-}
-
</del><span class="cx"> const Vector&lt;Node*&gt;* ShadowRoot::assignedNodesForSlot(const HTMLSlotElement&amp; slot)
</span><span class="cx"> {
</span><span class="cx">     if (!m_slotAssignment)
</span></span></pre></div>
<a id="trunkSourceWebCoredomShadowRooth"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/ShadowRoot.h (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/ShadowRoot.h        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/dom/ShadowRoot.h        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -91,8 +91,11 @@
</span><span class="cx">     void addSlotElementByName(const AtomicString&amp;, HTMLSlotElement&amp;);
</span><span class="cx">     void removeSlotElementByName(const AtomicString&amp;, HTMLSlotElement&amp;);
</span><span class="cx"> 
</span><del>-    void invalidateSlotAssignments();
-    void invalidateDefaultSlotAssignments();
</del><ins>+    void didRemoveAllChildrenOfShadowHost();
+    void didChangeDefaultSlot();
+    void hostChildElementDidChange(const Element&amp;);
+    void hostChildElementDidChangeSlotAttribute(const AtomicString&amp; oldValue, const AtomicString&amp; newValue);
+    void innerSlotDidChange(const AtomicString&amp;);
</ins><span class="cx"> 
</span><span class="cx">     const Vector&lt;Node*&gt;* assignedNodesForSlot(const HTMLSlotElement&amp;);
</span><span class="cx"> #endif
</span></span></pre></div>
<a id="trunkSourceWebCoredomSlotAssignmentcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/SlotAssignment.cpp (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/SlotAssignment.cpp        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/dom/SlotAssignment.cpp        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -50,12 +50,10 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> SlotAssignment::SlotAssignment()
</span><del>-    : m_slotNameFunction(slotNameFromSlotAttribute)
</del><span class="cx"> {
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-SlotAssignment::SlotAssignment(SlotNameFunction function)
-    : m_slotNameFunction(WTFMove(function))
</del><ins>+SlotAssignment::~SlotAssignment()
</ins><span class="cx"> {
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -64,10 +62,7 @@
</span><span class="cx">     if (!is&lt;Text&gt;(node) &amp;&amp; !is&lt;Element&gt;(node))
</span><span class="cx">         return nullptr;
</span><span class="cx"> 
</span><del>-    auto slotName = m_slotNameFunction(node);
-    if (!slotName)
-        return nullptr;
-
</del><ins>+    auto slotName = slotNameForHostChild(node);
</ins><span class="cx">     auto it = m_slots.find(slotName);
</span><span class="cx">     if (it == m_slots.end())
</span><span class="cx">         return nullptr;
</span><span class="lines">@@ -133,17 +128,58 @@
</span><span class="cx">     ASSERT(slotInfo.element || m_needsToResolveSlotElements);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-const Vector&lt;Node*&gt;* SlotAssignment::assignedNodesForSlot(const HTMLSlotElement&amp; slotElement, ShadowRoot&amp; shadowRoot)
</del><ins>+static void recursivelyFireSlotChangeEvent(HTMLSlotElement&amp; slotElement)
</ins><span class="cx"> {
</span><del>-    if (!m_slotAssignmentsIsValid)
-        assignSlots(shadowRoot);
</del><ins>+    slotElement.enqueueSlotChangeEvent();
</ins><span class="cx"> 
</span><del>-    const AtomicString&amp; slotName = slotNameFromAttributeValue(slotElement.fastGetAttribute(nameAttr));
</del><ins>+    auto* slotParent = slotElement.parentElement();
+    if (!slotParent)
+        return;
+
+    auto* shadowRootOfSlotParent = slotParent-&gt;shadowRoot();
+    if (!shadowRootOfSlotParent)
+        return;
+
+    shadowRootOfSlotParent-&gt;innerSlotDidChange(slotElement.fastGetAttribute(slotAttr));
+}
+
+void SlotAssignment::didChangeSlot(const AtomicString&amp; slotAttrValue, ChangeType changeType, ShadowRoot&amp; shadowRoot)
+{
+    auto&amp; slotName = slotNameFromAttributeValue(slotAttrValue);
</ins><span class="cx">     auto it = m_slots.find(slotName);
</span><span class="cx">     if (it == m_slots.end())
</span><del>-        return nullptr;
</del><ins>+        return;
</ins><span class="cx"> 
</span><ins>+    HTMLSlotElement* slotElement = findFirstSlotElement(*it-&gt;value, shadowRoot);
+    if (!slotElement)
+        return;
+
+    if (changeType == ChangeType::DirectChild) {
+        shadowRoot.host()-&gt;setNeedsStyleRecalc(ReconstructRenderTree);
+        m_slotAssignmentsIsValid = false;
+    }
+
+    if (shadowRoot.type() == ShadowRoot::Type::UserAgent)
+        return;
+
+    recursivelyFireSlotChangeEvent(*slotElement);
+}
+
+void SlotAssignment::hostChildElementDidChange(const Element&amp; childElement, ShadowRoot&amp; shadowRoot)
+{
+    didChangeSlot(childElement.fastGetAttribute(slotAttr), ChangeType::DirectChild, shadowRoot);
+}
+
+const Vector&lt;Node*&gt;* SlotAssignment::assignedNodesForSlot(const HTMLSlotElement&amp; slotElement, ShadowRoot&amp; shadowRoot)
+{
+    ASSERT(slotElement.containingShadowRoot() == &amp;shadowRoot);
+    const AtomicString&amp; slotName = slotNameFromAttributeValue(slotElement.fastGetAttribute(nameAttr));
+    auto it = m_slots.find(slotName);
+    RELEASE_ASSERT(it != m_slots.end());
+
</ins><span class="cx">     auto&amp; slotInfo = *it-&gt;value;
</span><ins>+    if (!m_slotAssignmentsIsValid)
+        assignSlots(shadowRoot);
</ins><span class="cx"> 
</span><span class="cx">     if (!slotInfo.assignedNodes.size())
</span><span class="cx">         return nullptr;
</span><span class="lines">@@ -155,20 +191,11 @@
</span><span class="cx">     return &amp;slotInfo.assignedNodes;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void SlotAssignment::invalidate(ShadowRoot&amp; shadowRoot)
</del><ins>+const AtomicString&amp; SlotAssignment::slotNameForHostChild(const Node&amp; child) const
</ins><span class="cx"> {
</span><del>-    // FIXME: We should be able to do a targeted reconstruction.
-    shadowRoot.host()-&gt;setNeedsStyleRecalc(ReconstructRenderTree);
-    m_slotAssignmentsIsValid = false;
</del><ins>+    return slotNameFromSlotAttribute(child);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-void SlotAssignment::invalidateDefaultSlot(ShadowRoot&amp; shadowRoot)
-{
-    auto it = m_slots.find(defaultSlotName());
-    if (it != m_slots.end() &amp;&amp; it-&gt;value-&gt;elementCount)
-        invalidate(shadowRoot); // FIXME: We should be able to reconstruct only under the default slot.
-}
-
</del><span class="cx"> HTMLSlotElement* SlotAssignment::findFirstSlotElement(SlotInfo&amp; slotInfo, ShadowRoot&amp; shadowRoot)
</span><span class="cx"> {
</span><span class="cx">     if (slotInfo.shouldResolveSlotElement())
</span><span class="lines">@@ -176,6 +203,7 @@
</span><span class="cx"> 
</span><span class="cx"> #ifndef NDEBUG
</span><span class="cx">     ASSERT(!slotInfo.element || m_slotElementsForConsistencyCheck.contains(slotInfo.element));
</span><ins>+    ASSERT(!!slotInfo.element == !!slotInfo.elementCount);
</ins><span class="cx"> #endif
</span><span class="cx"> 
</span><span class="cx">     return slotInfo.element;
</span><span class="lines">@@ -223,9 +251,7 @@
</span><span class="cx">     for (auto* child = host.firstChild(); child; child = child-&gt;nextSibling()) {
</span><span class="cx">         if (!is&lt;Text&gt;(*child) &amp;&amp; !is&lt;Element&gt;(*child))
</span><span class="cx">             continue;
</span><del>-        auto slotName = m_slotNameFunction(*child);
-        if (!slotName)
-            continue;
</del><ins>+        auto slotName = slotNameForHostChild(*child);
</ins><span class="cx">         assignToSlot(*child, slotName);
</span><span class="cx">     }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoredomSlotAssignmenth"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/SlotAssignment.h (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/SlotAssignment.h        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/dom/SlotAssignment.h        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -28,6 +28,7 @@
</span><span class="cx"> 
</span><span class="cx"> #if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
</span><span class="cx"> 
</span><ins>+#include &quot;ShadowRoot.h&quot;
</ins><span class="cx"> #include &lt;wtf/HashMap.h&gt;
</span><span class="cx"> #include &lt;wtf/HashSet.h&gt;
</span><span class="cx"> #include &lt;wtf/Vector.h&gt;
</span><span class="lines">@@ -36,18 +37,15 @@
</span><span class="cx"> 
</span><span class="cx"> namespace WebCore {
</span><span class="cx"> 
</span><ins>+class Element;
</ins><span class="cx"> class HTMLSlotElement;
</span><span class="cx"> class Node;
</span><del>-class ShadowRoot;
</del><span class="cx"> 
</span><span class="cx"> class SlotAssignment {
</span><span class="cx">     WTF_MAKE_NONCOPYABLE(SlotAssignment);
</span><span class="cx"> public:
</span><del>-    using SlotNameFunction = std::function&lt;AtomicString (const Node&amp; child)&gt;;
-
</del><span class="cx">     SlotAssignment();
</span><del>-    SlotAssignment(SlotNameFunction);
-    ~SlotAssignment() { }
</del><ins>+    virtual ~SlotAssignment();
</ins><span class="cx"> 
</span><span class="cx">     static const AtomicString&amp; defaultSlotName() { return emptyAtom; }
</span><span class="cx"> 
</span><span class="lines">@@ -56,10 +54,13 @@
</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><ins>+    enum class ChangeType { DirectChild, InnerSlot };
+    void didChangeSlot(const AtomicString&amp;, ChangeType, ShadowRoot&amp;);
+    void enqueueSlotChangeEvent(const AtomicString&amp;, ShadowRoot&amp;);
+
</ins><span class="cx">     const Vector&lt;Node*&gt;* assignedNodesForSlot(const HTMLSlotElement&amp;, ShadowRoot&amp;);
</span><span class="cx"> 
</span><del>-    void invalidate(ShadowRoot&amp;);
-    void invalidateDefaultSlot(ShadowRoot&amp;);
</del><ins>+    virtual void hostChildElementDidChange(const Element&amp;, ShadowRoot&amp;);
</ins><span class="cx"> 
</span><span class="cx"> private:
</span><span class="cx">     struct SlotInfo {
</span><span class="lines">@@ -77,6 +78,8 @@
</span><span class="cx">         unsigned elementCount { 0 };
</span><span class="cx">         Vector&lt;Node*&gt; assignedNodes;
</span><span class="cx">     };
</span><ins>+    
+    virtual const AtomicString&amp; slotNameForHostChild(const Node&amp;) const;
</ins><span class="cx"> 
</span><span class="cx">     HTMLSlotElement* findFirstSlotElement(SlotInfo&amp;, ShadowRoot&amp;);
</span><span class="cx">     void resolveAllSlotElements(ShadowRoot&amp;);
</span><span class="lines">@@ -84,8 +87,6 @@
</span><span class="cx">     void assignSlots(ShadowRoot&amp;);
</span><span class="cx">     void assignToSlot(Node&amp; child, const AtomicString&amp; slotName);
</span><span class="cx"> 
</span><del>-    SlotNameFunction m_slotNameFunction;
-
</del><span class="cx">     HashMap&lt;AtomicString, std::unique_ptr&lt;SlotInfo&gt;&gt; m_slots;
</span><span class="cx"> 
</span><span class="cx"> #ifndef NDEBUG
</span><span class="lines">@@ -96,8 +97,40 @@
</span><span class="cx">     bool m_slotAssignmentsIsValid { false };
</span><span class="cx"> };
</span><span class="cx"> 
</span><ins>+inline void ShadowRoot::didRemoveAllChildrenOfShadowHost()
+{
+    if (m_slotAssignment) // FIXME: This is incorrect when there were no elements or text nodes removed.
+        m_slotAssignment-&gt;didChangeSlot(nullAtom, SlotAssignment::ChangeType::DirectChild, *this);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><ins>+inline void ShadowRoot::didChangeDefaultSlot()
+{
+    if (m_slotAssignment)
+        m_slotAssignment-&gt;didChangeSlot(nullAtom, SlotAssignment::ChangeType::DirectChild, *this);
+}
+
+inline void ShadowRoot::hostChildElementDidChange(const Element&amp; childElement)
+{
+    if (m_slotAssignment)
+        m_slotAssignment-&gt;hostChildElementDidChange(childElement, *this);
+}
+
+inline void ShadowRoot::hostChildElementDidChangeSlotAttribute(const AtomicString&amp; oldValue, const AtomicString&amp; newValue)
+{
+    if (m_slotAssignment) {
+        m_slotAssignment-&gt;didChangeSlot(oldValue, SlotAssignment::ChangeType::DirectChild, *this);
+        m_slotAssignment-&gt;didChangeSlot(newValue, SlotAssignment::ChangeType::DirectChild, *this);
+    }
+}
+
+inline void ShadowRoot::innerSlotDidChange(const AtomicString&amp; name)
+{
+    if (m_slotAssignment)
+        m_slotAssignment-&gt;didChangeSlot(name, SlotAssignment::ChangeType::InnerSlot, *this);
+}
+
+}
+
</ins><span class="cx"> #endif
</span><span class="cx"> 
</span><span class="cx"> #endif /* SlotAssignment_h */
</span></span></pre></div>
<a id="trunkSourceWebCorehtmlHTMLDetailsElementcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/html/HTMLDetailsElement.cpp (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/html/HTMLDetailsElement.cpp        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/html/HTMLDetailsElement.cpp        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -44,8 +44,24 @@
</span><span class="cx">     return summarySlot;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-static AtomicString slotNameFunction(const Node&amp; child)
</del><ins>+class DetailsSlotAssignment final : public SlotAssignment {
+private:
+    void hostChildElementDidChange(const Element&amp;, ShadowRoot&amp;) override;
+    const AtomicString&amp; slotNameForHostChild(const Node&amp;) const override;
+};
+
+void DetailsSlotAssignment::hostChildElementDidChange(const Element&amp; childElement, ShadowRoot&amp; shadowRoot)
</ins><span class="cx"> {
</span><ins>+    if (is&lt;HTMLSummaryElement&gt;(childElement)) {
+        // Don't check whether this is the first summary element
+        // since we don't know the answer when this function is called inside Element::removedFrom.
+        didChangeSlot(summarySlotName(), ChangeType::DirectChild, shadowRoot);
+    } else
+        didChangeSlot(SlotAssignment::defaultSlotName(), ChangeType::DirectChild, shadowRoot);
+}
+
+const AtomicString&amp; DetailsSlotAssignment::slotNameForHostChild(const Node&amp; child) const
+{
</ins><span class="cx">     auto&amp; parent = *child.parentNode();
</span><span class="cx">     ASSERT(is&lt;HTMLDetailsElement&gt;(parent));
</span><span class="cx">     auto&amp; details = downcast&lt;HTMLDetailsElement&gt;(parent);
</span><span class="lines">@@ -55,18 +71,13 @@
</span><span class="cx">         if (&amp;child == childrenOfType&lt;HTMLSummaryElement&gt;(details).first())
</span><span class="cx">             return summarySlotName();
</span><span class="cx">     }
</span><del>-    // Everything else is assigned to the default slot if details is open.
-    if (details.isOpen())
-        return SlotAssignment::defaultSlotName();
</del><ins>+    return SlotAssignment::defaultSlotName();
+}
</ins><span class="cx"> 
</span><del>-    // Otherwise don't render the content.
-    return nullAtom;
-};
-
</del><span class="cx"> Ref&lt;HTMLDetailsElement&gt; HTMLDetailsElement::create(const QualifiedName&amp; tagName, Document&amp; document)
</span><span class="cx"> {
</span><span class="cx">     auto details = adoptRef(*new HTMLDetailsElement(tagName, document));
</span><del>-    details-&gt;addShadowRoot(ShadowRoot::create(document, std::make_unique&lt;SlotAssignment&gt;(slotNameFunction)));
</del><ins>+    details-&gt;addShadowRoot(ShadowRoot::create(document, std::make_unique&lt;DetailsSlotAssignment&gt;()));
</ins><span class="cx">     return details;
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -94,8 +105,8 @@
</span><span class="cx">     summarySlot-&gt;appendChild(WTFMove(defaultSummary));
</span><span class="cx">     root-&gt;appendChild(WTFMove(summarySlot));
</span><span class="cx"> 
</span><del>-    auto defaultSlot = HTMLSlotElement::create(slotTag, document());
-    root-&gt;appendChild(WTFMove(defaultSlot));
</del><ins>+    m_defaultSlot = HTMLSlotElement::create(slotTag, document());
+    ASSERT(!m_isOpen);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> bool HTMLDetailsElement::isActiveSummary(const HTMLSummaryElement&amp; summary) const
</span><span class="lines">@@ -117,8 +128,14 @@
</span><span class="cx">     if (name == openAttr) {
</span><span class="cx">         bool oldValue = m_isOpen;
</span><span class="cx">         m_isOpen = !value.isNull();
</span><del>-        if (oldValue != m_isOpen)
-            shadowRoot()-&gt;invalidateSlotAssignments();
</del><ins>+        if (oldValue != m_isOpen) {
+            auto* root = shadowRoot();
+            ASSERT(root);
+            if (m_isOpen)
+                root-&gt;appendChild(*m_defaultSlot);
+            else
+                root-&gt;removeChild(*m_defaultSlot);
+        }
</ins><span class="cx">     } else
</span><span class="cx">         HTMLElement::parseAttribute(name, value);
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebCorehtmlHTMLDetailsElementh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/html/HTMLDetailsElement.h (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/html/HTMLDetailsElement.h        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/html/HTMLDetailsElement.h        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -47,6 +47,7 @@
</span><span class="cx">     bool m_isOpen { false };
</span><span class="cx">     HTMLSlotElement* m_summarySlot { nullptr };
</span><span class="cx">     HTMLSummaryElement* m_defaultSummary { nullptr };
</span><ins>+    RefPtr&lt;HTMLSlotElement&gt; m_defaultSlot;
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> } // namespace WebCore
</span></span></pre></div>
<a id="trunkSourceWebCorehtmlHTMLSlotElementcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/html/HTMLSlotElement.cpp (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/html/HTMLSlotElement.cpp        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/html/HTMLSlotElement.cpp        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -29,6 +29,8 @@
</span><span class="cx"> #if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
</span><span class="cx"> 
</span><span class="cx"> #include &quot;ElementChildIterator.h&quot;
</span><ins>+#include &quot;Event.h&quot;
+#include &quot;EventNames.h&quot;
</ins><span class="cx"> #include &quot;HTMLNames.h&quot;
</span><span class="cx"> #include &quot;ShadowRoot.h&quot;
</span><span class="cx"> 
</span><span class="lines">@@ -97,6 +99,27 @@
</span><span class="cx">     return shadowRoot-&gt;assignedNodesForSlot(*this);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void HTMLSlotElement::enqueueSlotChangeEvent()
+{
+    if (m_hasEnqueuedSlotChangeEvent)
+        return;
+
+    bool bubbles = false;
+    bool cancelable = false;
+    auto event = Event::create(eventNames().slotchangeEvent, bubbles, cancelable);
+    event-&gt;setTarget(this);
+    document().enqueueSlotchangeEvent(WTFMove(event));
+
+    m_hasEnqueuedSlotChangeEvent = true;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><ins>+bool HTMLSlotElement::dispatchEvent(Event&amp; event)
+{
+    if (event.type() == eventNames().slotchangeEvent)
+        m_hasEnqueuedSlotChangeEvent = false;
+    return HTMLElement::dispatchEvent(event);
+}
+
+}
+
</ins><span class="cx"> #endif
</span></span></pre></div>
<a id="trunkSourceWebCorehtmlHTMLSlotElementh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/html/HTMLSlotElement.h (198114 => 198115)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/html/HTMLSlotElement.h        2016-03-14 11:57:00 UTC (rev 198114)
+++ trunk/Source/WebCore/html/HTMLSlotElement.h        2016-03-14 12:02:21 UTC (rev 198115)
</span><span class="lines">@@ -39,12 +39,18 @@
</span><span class="cx"> 
</span><span class="cx">     const Vector&lt;Node*&gt;* assignedNodes() const;
</span><span class="cx"> 
</span><ins>+    void enqueueSlotChangeEvent();
+
</ins><span class="cx"> private:
</span><span class="cx">     HTMLSlotElement(const QualifiedName&amp;, Document&amp;);
</span><span class="cx"> 
</span><span class="cx">     InsertionNotificationRequest insertedInto(ContainerNode&amp;) override;
</span><span class="cx">     void removedFrom(ContainerNode&amp;) override;
</span><span class="cx">     void attributeChanged(const QualifiedName&amp;, const AtomicString&amp; oldValue, const AtomicString&amp; newValue, AttributeModificationReason) override;
</span><ins>+
+    bool dispatchEvent(Event&amp;) override;
+
+    bool m_hasEnqueuedSlotChangeEvent { false };
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> }
</span></span></pre>
</div>
</div>

</body>
</html>