<!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>[207810] 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/207810">207810</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2016-10-24 23:18:13 -0700 (Mon, 24 Oct 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>Custom elements reactions should have a queue per element
https://bugs.webkit.org/show_bug.cgi?id=163878

Reviewed by Antti Koivisto.

Source/WebCore:

This patch splits the custom elements reaction queue into per element to match the latest HTML specifications:
https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reaction-queue
and introduces the backup element queue:
https://html.spec.whatwg.org/multipage/scripting.html#backup-element-queue

In terms of code changes, CustomElementReactionStack now holds onto ElementQueue, an ordered list of elements,
and make each ElementRareData keep its own CustomElementReactionQueue. CustomElementReactionQueue is created
for each custom element when it is synchronously constructed or enqueued to upgrade.

Because each reaction queue is now specific to each element, CustomElementReactionQueue instead of
CustomElementReactionQueueItem stores JSCustomElementInterface.

The backup element queue is created as a singleton returned by CustomElementReactionStack's backupElementQueue,
and ensureBackupQueue() schedules a new mirotask to process the backup queue when there isn't already one.

ensureCurrentQueue() now returns a reference to CustomElementReactionQueue instead of a pointer since it can
fallback to the backup queue when the stack is empty as specified:
https://html.spec.whatwg.org/multipage/scripting.html#enqueue-an-element-on-the-appropriate-element-queue

Note that ensureCurrentQueue() may insert the same element multiple times into the element queue for now since
avoiding this duplication would require either doing O(n) iteration on m_elements or adding a HashSet.
We can revisit this in the future if the reaction queue is found to grow beyond a few entries since elements in
the element queue will have duplicates only when each reaction queue has more than one item.

Tests: fast/custom-elements/backup-element-queue.html
       fast/custom-elements/custom-element-reaction-queue.html

* bindings/js/JSCustomElementInterface.cpp:
(WebCore::JSCustomElementInterface::upgradeElement):
* dom/CustomElementReactionQueue.cpp:
(WebCore::CustomElementReactionQueueItem::CustomElementReactionQueueItem):
(WebCore::CustomElementReactionQueueItem::invoke): Removed the check for isFailedCustomElement since the queue
is explicitly cleared in Element::setIsFailedCustomElement.
(WebCore::CustomElementReactionQueue::CustomElementReactionQueue): Now takes JSCustomElementInterface since
each item in the queue no longer stores Element or JSCustomElementInterface.
(WebCore::CustomElementReactionQueue::clear):
(WebCore::CustomElementReactionQueue::enqueueElementUpgrade):
(WebCore::CustomElementReactionQueue::enqueueElementUpgradeIfDefined):
(WebCore::CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded):
(WebCore::CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded):
(WebCore::CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded):
(WebCore::CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded):
(WebCore::CustomElementReactionQueue::enqueuePostUpgradeReactions):
(WebCore::CustomElementReactionQueue::invokeAll):
(WebCore::CustomElementReactionStack::ElementQueue::add): Added.
(WebCore::CustomElementReactionStack::ElementQueue::invokeAll): Added.
(WebCore::CustomElementReactionStack::ensureCurrentQueue):
(WebCore::BackupElementQueueMicrotask): Added.
(WebCore::CustomElementReactionStack::ensureBackupQueue): Added.
(WebCore::CustomElementReactionStack::processBackupQueue): Added.
(WebCore::CustomElementReactionStack::backupElementQueue): Added.
* dom/CustomElementReactionQueue.h:
* dom/CustomElementRegistry.cpp:
(WebCore::enqueueUpgradeInShadowIncludingTreeOrder):
* dom/Document.cpp:
(WebCore::createFallbackHTMLElement):
* dom/Element.cpp:
(WebCore::Element::setIsDefinedCustomElement): Create a new reaction queue if there isn't already one; when
this element had been upgraded, the reaction queue have already been created in Element::enqueueToUpgrade.
(WebCore::Element::setIsFailedCustomElement): Clear the reaction queue when the upgrading had failed.
(WebCore::Element::enqueueToUpgrade): Added.
(WebCore::Element::reactionQueue): Added.
* dom/Element.h:
* dom/ElementRareData.h:
(WebCore::ElementRareData::customElementReactionQueue): Replaced customElementInterface.
(WebCore::ElementRareData::setCustomElementReactionQueue): Replaced setCustomElementReactionQueue.

LayoutTests:

Added a W3C style testharness.js test for making sure the custom element reaction queue exists per element,
and added a WebKit style test for making sure that the backup element queue exists.

* fast/custom-elements/backup-element-queue-expected.txt: Added.
* fast/custom-elements/backup-element-queue.html: Added.
* fast/custom-elements/custom-element-reaction-queue-expected.txt: Added.
* fast/custom-elements/custom-element-reaction-queue.html: Added.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCorebindingsjsJSCustomElementInterfacecpp">trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp</a></li>
<li><a href="#trunkSourceWebCoredomCustomElementReactionQueuecpp">trunk/Source/WebCore/dom/CustomElementReactionQueue.cpp</a></li>
<li><a href="#trunkSourceWebCoredomCustomElementReactionQueueh">trunk/Source/WebCore/dom/CustomElementReactionQueue.h</a></li>
<li><a href="#trunkSourceWebCoredomCustomElementRegistrycpp">trunk/Source/WebCore/dom/CustomElementRegistry.cpp</a></li>
<li><a href="#trunkSourceWebCoredomDocumentcpp">trunk/Source/WebCore/dom/Document.cpp</a></li>
<li><a href="#trunkSourceWebCoredomElementcpp">trunk/Source/WebCore/dom/Element.cpp</a></li>
<li><a href="#trunkSourceWebCoredomElementh">trunk/Source/WebCore/dom/Element.h</a></li>
<li><a href="#trunkSourceWebCoredomElementRareDatah">trunk/Source/WebCore/dom/ElementRareData.h</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsfastcustomelementsbackupelementqueueexpectedtxt">trunk/LayoutTests/fast/custom-elements/backup-element-queue-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastcustomelementsbackupelementqueuehtml">trunk/LayoutTests/fast/custom-elements/backup-element-queue.html</a></li>
<li><a href="#trunkLayoutTestsfastcustomelementscustomelementreactionqueueexpectedtxt">trunk/LayoutTests/fast/custom-elements/custom-element-reaction-queue-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastcustomelementscustomelementreactionqueuehtml">trunk/LayoutTests/fast/custom-elements/custom-element-reaction-queue.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (207809 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2016-10-25 06:07:04 UTC (rev 207809)
+++ trunk/LayoutTests/ChangeLog        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -1,3 +1,18 @@
</span><ins>+2016-10-24  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        Custom elements reactions should have a queue per element
+        https://bugs.webkit.org/show_bug.cgi?id=163878
+
+        Reviewed by Antti Koivisto.
+
+        Added a W3C style testharness.js test for making sure the custom element reaction queue exists per element,
+        and added a WebKit style test for making sure that the backup element queue exists.
+
+        * fast/custom-elements/backup-element-queue-expected.txt: Added.
+        * fast/custom-elements/backup-element-queue.html: Added.
+        * fast/custom-elements/custom-element-reaction-queue-expected.txt: Added.
+        * fast/custom-elements/custom-element-reaction-queue.html: Added.
+
</ins><span class="cx"> 2016-10-24  Jiewen Tan  &lt;jiewen_tan@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Update SubtleCrypto::generateKey to match the latest spec
</span></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementsbackupelementqueueexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/custom-elements/backup-element-queue-expected.txt (0 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/backup-element-queue-expected.txt                                (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/backup-element-queue-expected.txt        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+This tests the existence of the backup element queue. To manually test, press the delete key once the page is loaded.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+PASS constructed is false
+PASS editor.innerHTML = &quot;a&lt;test-element&gt;b&lt;/test-element&gt;c&quot;; constructed is true
+PASS editor.focus(); getSelection().selectAllChildren(editor); disconnected is false
+PASS &quot;Before the end of the micro task&quot;; disconnected is false
+PASS &quot;At the end of the micro task&quot;; disconnected is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
</ins></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementsbackupelementqueuehtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/custom-elements/backup-element-queue.html (0 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/backup-element-queue.html                                (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/backup-element-queue.html        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -0,0 +1,51 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;body&gt;
+&lt;script src=&quot;../../resources/js-test.js&quot;&gt;&lt;/script&gt;
+&lt;div id=&quot;editor&quot; contenteditable&gt;&lt;/div&gt;
+&lt;script&gt;
+
+description('This tests the existence of the backup element queue. To manually test, press the delete key once the page is loaded.');
+
+var jsTestIsAsync = true;
+
+var constructed = false;
+var disconnected = false;
+class TestElement extends HTMLElement {
+    constructor() {
+        super();
+        constructed = true;
+    }
+    disconnectedCallback() {
+        disconnected = true;
+    }
+}
+customElements.define('test-element', TestElement);
+
+var editor = document.getElementById('editor');
+shouldBe('constructed', 'false');
+shouldBe('editor.innerHTML = &quot;a&lt;test-element&gt;b&lt;/test-element&gt;c&quot;; constructed', 'true');
+shouldBe('editor.focus(); getSelection().selectAllChildren(editor); disconnected', 'false');
+
+function checkDisconnectedAtEndOfMictotask()
+{
+    shouldBe('&quot;Before the end of the micro task&quot;; disconnected', 'false');
+    Promise.resolve().then(function () {
+        shouldBe('&quot;At the end of the micro task&quot;; disconnected', 'true');
+        finishJSTest();
+    });
+}
+
+if (window.testRunner) {
+    testRunner.execCommand('delete', false, null);
+    checkDisconnectedAtEndOfMictotask();
+} else {
+    editor.oninput = function () {
+        checkDisconnectedAtEndOfMictotask();
+    }
+}
+
+
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementscustomelementreactionqueueexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/custom-elements/custom-element-reaction-queue-expected.txt (0 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/custom-element-reaction-queue-expected.txt                                (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/custom-element-reaction-queue-expected.txt        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -0,0 +1,5 @@
</span><ins>+
+PASS Upgrading a custom element must invoke attributeChangedCallback and connectedCallback before start upgrading another element 
+PASS Mutating a undefined custom element while upgrading a custom element must not enqueue or invoke reactions on the mutated element 
+PASS Mutating another custom element inside adopted callback must invoke all pending callbacks on the mutated element 
+
</ins></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementscustomelementreactionqueuehtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/custom-elements/custom-element-reaction-queue.html (0 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/custom-element-reaction-queue.html                                (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/custom-element-reaction-queue.html        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -0,0 +1,181 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+&lt;title&gt;Custom Elements: Each element must have its own custom element reaction queue&lt;/title&gt;
+&lt;meta name=&quot;author&quot; title=&quot;Ryosuke Niwa&quot; href=&quot;mailto:rniwa@webkit.org&quot;&gt;
+&lt;meta name=&quot;assert&quot; content=&quot;Each element must have its own custom element reaction queue&quot;&gt;
+&lt;meta name=&quot;help&quot; content=&quot;https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reaction-queue&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;script src=&quot;../../imported/w3c/web-platform-tests/custom-elements/resources/custom-elements-helpers.js&quot;&gt;&lt;/script&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;div id=&quot;log&quot;&gt;&lt;/div&gt;
+&lt;script&gt;
+
+function create_constructor_log(element) {
+    return {type: 'constructed', element: element};
+}
+
+function assert_constructor_log_entry(log, element) {
+    assert_equals(log.type, 'constructed');
+    assert_equals(log.element, element);
+}
+
+function assert_adopted_log_entry(log, element) {
+    assert_equals(log.type, 'adopted');
+    assert_equals(log.element, element);
+}
+
+function create_adopted_callback_log(element) {
+    return {type: 'adopted', element: element};
+}
+
+function create_connected_callback_log(element) {
+    return {type: 'connected', element: element};
+}
+
+function assert_connected_log_entry(log, element) {
+    assert_equals(log.type, 'connected');
+    assert_equals(log.element, element);
+}
+
+test_with_window(function (contentWindow) {
+    const contentDocument = contentWindow.document;
+    contentDocument.write('&lt;test-element id=&quot;first-element&quot;&gt;');
+    contentDocument.write('&lt;test-element id=&quot;second-element&quot;&gt;');
+
+    const element1 = contentDocument.getElementById('first-element');
+    const element2 = contentDocument.getElementById('second-element');
+    assert_equals(Object.getPrototypeOf(element1), contentWindow.HTMLElement.prototype);
+    assert_equals(Object.getPrototypeOf(element2), contentWindow.HTMLElement.prototype);
+
+    let log = [];
+    class TestElement extends contentWindow.HTMLElement {
+        constructor() {
+            super();
+            log.push(create_constructor_log(this));
+        }
+        connectedCallback(...args) {
+            log.push(create_connected_callback_log(this, ...args));
+        }
+        attributeChangedCallback(...args) {
+            log.push(create_attribute_changed_callback_log(this, ...args));
+        }
+        static get observedAttributes() { return ['id']; }
+    }
+    contentWindow.customElements.define('test-element', TestElement);
+    assert_equals(Object.getPrototypeOf(element1), TestElement.prototype);
+    assert_equals(Object.getPrototypeOf(element2), TestElement.prototype);
+
+    assert_equals(log.length, 6);
+    assert_constructor_log_entry(log[0], element1);
+    assert_attribute_log_entry(log[1], {name: 'id', oldValue: null, newValue: 'first-element', namespace: null});
+    assert_connected_log_entry(log[2], element1);
+    assert_constructor_log_entry(log[3], element2);
+    assert_attribute_log_entry(log[4], {name: 'id', oldValue: null, newValue: 'second-element', namespace: null});
+    assert_connected_log_entry(log[5], element2);
+}, 'Upgrading a custom element must invoke attributeChangedCallback and connectedCallback before start upgrading another element');
+
+test_with_window(function (contentWindow) {
+    const contentDocument = contentWindow.document;
+    contentDocument.write('&lt;test-element id=&quot;first-element&quot;&gt;');
+    contentDocument.write('&lt;test-element id=&quot;second-element&quot;&gt;');
+
+    const element1 = contentDocument.getElementById('first-element');
+    const element2 = contentDocument.getElementById('second-element');
+    assert_equals(Object.getPrototypeOf(element1), contentWindow.HTMLElement.prototype);
+    assert_equals(Object.getPrototypeOf(element2), contentWindow.HTMLElement.prototype);
+
+    let log = [];
+    class TestElement extends contentWindow.HTMLElement {
+        constructor() {
+            super();
+            log.push(create_constructor_log(this));
+            if (this == element1)
+                element2.setAttribute('class', 'foo');
+        }
+        connectedCallback(...args) {
+            log.push(create_connected_callback_log(this, ...args));
+        }
+        attributeChangedCallback(...args) {
+            log.push(create_attribute_changed_callback_log(this, ...args));
+        }
+        static get observedAttributes() { return ['id', 'class']; }
+    }
+    contentWindow.customElements.define('test-element', TestElement);
+    assert_equals(Object.getPrototypeOf(element1), TestElement.prototype);
+    assert_equals(Object.getPrototypeOf(element2), TestElement.prototype);
+
+    assert_equals(log.length, 7);
+    assert_constructor_log_entry(log[0], element1);
+    assert_attribute_log_entry(log[1], {name: 'id', oldValue: null, newValue: 'first-element', namespace: null});
+    assert_connected_log_entry(log[2], element1);
+    assert_constructor_log_entry(log[3], element2);
+    assert_attribute_log_entry(log[4], {name: 'id', oldValue: null, newValue: 'second-element', namespace: null});
+    assert_attribute_log_entry(log[5], {name: 'class', oldValue: null, newValue: 'foo', namespace: null});
+    assert_connected_log_entry(log[6], element2);
+}, 'Mutating a undefined custom element while upgrading a custom element must not enqueue or invoke reactions on the mutated element');
+
+test_with_window(function (contentWindow) {
+    let log = [];
+    let element1;
+    let element2;
+    class TestElement extends contentWindow.HTMLElement {
+        constructor() {
+            super();
+            log.push(create_constructor_log(this));
+        }
+        adoptedCallback(...args) {
+            log.push(create_adopted_callback_log(this, ...args));
+            if (this == element1)
+                element3.setAttribute('id', 'foo');
+        }
+        connectedCallback(...args) {
+            log.push(create_connected_callback_log(this, ...args));
+        }
+        attributeChangedCallback(...args) {
+            log.push(create_attribute_changed_callback_log(this, ...args));
+        }
+        static get observedAttributes() { return ['id', 'class']; }
+    }
+
+    contentWindow.customElements.define('test-element', TestElement);
+
+    let contentDocument = contentWindow.document;
+    element1 = contentDocument.createElement('test-element');
+    element2 = contentDocument.createElement('test-element');
+    element3 = contentDocument.createElement('test-element');
+    assert_equals(Object.getPrototypeOf(element1), TestElement.prototype);
+    assert_equals(Object.getPrototypeOf(element2), TestElement.prototype);
+    assert_equals(Object.getPrototypeOf(element3), TestElement.prototype);
+
+    assert_equals(log.length, 3);
+    assert_constructor_log_entry(log[0], element1);
+    assert_constructor_log_entry(log[1], element2);
+    assert_constructor_log_entry(log[2], element3);
+    log = [];
+
+    const container = contentDocument.createElement('div');
+    container.appendChild(element1);
+    container.appendChild(element2);
+    container.appendChild(element3);
+
+    const anotherDocument = document.implementation.createHTMLDocument();
+    anotherDocument.documentElement.appendChild(container);
+
+    assert_equals(log.length, 7);
+    assert_adopted_log_entry(log[0], element1);
+    assert_adopted_log_entry(log[1], element3);
+    assert_connected_log_entry(log[2], element3);
+    assert_attribute_log_entry(log[3], {name: 'id', oldValue: null, newValue: 'foo', namespace: null});
+    assert_connected_log_entry(log[4], element1);
+    assert_adopted_log_entry(log[5], element2);
+    assert_connected_log_entry(log[6], element2);
+
+}, 'Mutating another custom element inside adopted callback must invoke all pending callbacks on the mutated element');
+
+
+&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 (207809 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2016-10-25 06:07:04 UTC (rev 207809)
+++ trunk/Source/WebCore/ChangeLog        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -1,3 +1,77 @@
</span><ins>+2016-10-24  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        Custom elements reactions should have a queue per element
+        https://bugs.webkit.org/show_bug.cgi?id=163878
+
+        Reviewed by Antti Koivisto.
+
+        This patch splits the custom elements reaction queue into per element to match the latest HTML specifications:
+        https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reaction-queue
+        and introduces the backup element queue:
+        https://html.spec.whatwg.org/multipage/scripting.html#backup-element-queue
+
+        In terms of code changes, CustomElementReactionStack now holds onto ElementQueue, an ordered list of elements,
+        and make each ElementRareData keep its own CustomElementReactionQueue. CustomElementReactionQueue is created
+        for each custom element when it is synchronously constructed or enqueued to upgrade.
+
+        Because each reaction queue is now specific to each element, CustomElementReactionQueue instead of
+        CustomElementReactionQueueItem stores JSCustomElementInterface.
+
+        The backup element queue is created as a singleton returned by CustomElementReactionStack's backupElementQueue,
+        and ensureBackupQueue() schedules a new mirotask to process the backup queue when there isn't already one.
+
+        ensureCurrentQueue() now returns a reference to CustomElementReactionQueue instead of a pointer since it can
+        fallback to the backup queue when the stack is empty as specified:
+        https://html.spec.whatwg.org/multipage/scripting.html#enqueue-an-element-on-the-appropriate-element-queue
+
+        Note that ensureCurrentQueue() may insert the same element multiple times into the element queue for now since
+        avoiding this duplication would require either doing O(n) iteration on m_elements or adding a HashSet.
+        We can revisit this in the future if the reaction queue is found to grow beyond a few entries since elements in
+        the element queue will have duplicates only when each reaction queue has more than one item.
+
+        Tests: fast/custom-elements/backup-element-queue.html
+               fast/custom-elements/custom-element-reaction-queue.html
+
+        * bindings/js/JSCustomElementInterface.cpp:
+        (WebCore::JSCustomElementInterface::upgradeElement):
+        * dom/CustomElementReactionQueue.cpp:
+        (WebCore::CustomElementReactionQueueItem::CustomElementReactionQueueItem):
+        (WebCore::CustomElementReactionQueueItem::invoke): Removed the check for isFailedCustomElement since the queue
+        is explicitly cleared in Element::setIsFailedCustomElement.
+        (WebCore::CustomElementReactionQueue::CustomElementReactionQueue): Now takes JSCustomElementInterface since
+        each item in the queue no longer stores Element or JSCustomElementInterface.
+        (WebCore::CustomElementReactionQueue::clear):
+        (WebCore::CustomElementReactionQueue::enqueueElementUpgrade):
+        (WebCore::CustomElementReactionQueue::enqueueElementUpgradeIfDefined):
+        (WebCore::CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded):
+        (WebCore::CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded):
+        (WebCore::CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded):
+        (WebCore::CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded):
+        (WebCore::CustomElementReactionQueue::enqueuePostUpgradeReactions):
+        (WebCore::CustomElementReactionQueue::invokeAll):
+        (WebCore::CustomElementReactionStack::ElementQueue::add): Added.
+        (WebCore::CustomElementReactionStack::ElementQueue::invokeAll): Added.
+        (WebCore::CustomElementReactionStack::ensureCurrentQueue):
+        (WebCore::BackupElementQueueMicrotask): Added.
+        (WebCore::CustomElementReactionStack::ensureBackupQueue): Added.
+        (WebCore::CustomElementReactionStack::processBackupQueue): Added.
+        (WebCore::CustomElementReactionStack::backupElementQueue): Added.
+        * dom/CustomElementReactionQueue.h:
+        * dom/CustomElementRegistry.cpp:
+        (WebCore::enqueueUpgradeInShadowIncludingTreeOrder):
+        * dom/Document.cpp:
+        (WebCore::createFallbackHTMLElement):
+        * dom/Element.cpp:
+        (WebCore::Element::setIsDefinedCustomElement): Create a new reaction queue if there isn't already one; when
+        this element had been upgraded, the reaction queue have already been created in Element::enqueueToUpgrade.
+        (WebCore::Element::setIsFailedCustomElement): Clear the reaction queue when the upgrading had failed.
+        (WebCore::Element::enqueueToUpgrade): Added.
+        (WebCore::Element::reactionQueue): Added.
+        * dom/Element.h:
+        * dom/ElementRareData.h:
+        (WebCore::ElementRareData::customElementReactionQueue): Replaced customElementInterface.
+        (WebCore::ElementRareData::setCustomElementReactionQueue): Replaced setCustomElementReactionQueue.
+
</ins><span class="cx"> 2016-10-24  Jiewen Tan  &lt;jiewen_tan@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Update SubtleCrypto::generateKey to match the latest spec
</span></span></pre></div>
<a id="trunkSourceWebCorebindingsjsJSCustomElementInterfacecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp (207809 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp        2016-10-25 06:07:04 UTC (rev 207809)
+++ trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -182,7 +182,7 @@
</span><span class="cx">         return;
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    CustomElementReactionQueue::enqueuePostUpgradeReactions(element, *this);
</del><ins>+    CustomElementReactionQueue::enqueuePostUpgradeReactions(element);
</ins><span class="cx"> 
</span><span class="cx">     m_constructionStack.append(&amp;element);
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoredomCustomElementReactionQueuecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/CustomElementReactionQueue.cpp (207809 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/CustomElementReactionQueue.cpp        2016-10-25 06:07:04 UTC (rev 207809)
+++ trunk/Source/WebCore/dom/CustomElementReactionQueue.cpp        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -34,6 +34,7 @@
</span><span class="cx"> #include &quot;Element.h&quot;
</span><span class="cx"> #include &quot;JSCustomElementInterface.h&quot;
</span><span class="cx"> #include &quot;JSDOMBinding.h&quot;
</span><ins>+#include &quot;Microtasks.h&quot;
</ins><span class="cx"> #include &lt;heap/Heap.h&gt;
</span><span class="cx"> #include &lt;wtf/Optional.h&gt;
</span><span class="cx"> #include &lt;wtf/Ref.h&gt;
</span><span class="lines">@@ -50,49 +51,41 @@
</span><span class="cx">         AttributeChanged,
</span><span class="cx">     };
</span><span class="cx"> 
</span><del>-    CustomElementReactionQueueItem(Type type, Element&amp; element, JSCustomElementInterface&amp; elementInterface)
</del><ins>+    CustomElementReactionQueueItem(Type type)
</ins><span class="cx">         : m_type(type)
</span><del>-        , m_element(element)
-        , m_interface(elementInterface)
</del><span class="cx">     { }
</span><span class="cx"> 
</span><del>-    CustomElementReactionQueueItem(Element&amp; element, JSCustomElementInterface&amp; elementInterface, Document&amp; oldDocument, Document&amp; newDocument)
</del><ins>+    CustomElementReactionQueueItem(Document&amp; oldDocument, Document&amp; newDocument)
</ins><span class="cx">         : m_type(Type::Adopted)
</span><del>-        , m_element(element)
-        , m_interface(elementInterface)
</del><span class="cx">         , m_oldDocument(&amp;oldDocument)
</span><span class="cx">         , m_newDocument(&amp;newDocument)
</span><span class="cx">     { }
</span><span class="cx"> 
</span><del>-    CustomElementReactionQueueItem(Element&amp; element, JSCustomElementInterface&amp; elementInterface, const QualifiedName&amp; attributeName, const AtomicString&amp; oldValue, const AtomicString&amp; newValue)
</del><ins>+    CustomElementReactionQueueItem(const QualifiedName&amp; attributeName, const AtomicString&amp; oldValue, const AtomicString&amp; newValue)
</ins><span class="cx">         : m_type(Type::AttributeChanged)
</span><del>-        , m_element(element)
-        , m_interface(elementInterface)
</del><span class="cx">         , m_attributeName(attributeName)
</span><span class="cx">         , m_oldValue(oldValue)
</span><span class="cx">         , m_newValue(newValue)
</span><span class="cx">     { }
</span><span class="cx"> 
</span><del>-    void invoke()
</del><ins>+    void invoke(Element&amp; element, JSCustomElementInterface&amp; elementInterface)
</ins><span class="cx">     {
</span><del>-        if (m_element-&gt;isFailedCustomElement())
-            return;
</del><span class="cx">         switch (m_type) {
</span><span class="cx">         case Type::ElementUpgrade:
</span><del>-            m_interface-&gt;upgradeElement(m_element.get());
</del><ins>+            elementInterface.upgradeElement(element);
</ins><span class="cx">             break;
</span><span class="cx">         case Type::Connected:
</span><del>-            m_interface-&gt;invokeConnectedCallback(m_element.get());
</del><ins>+            elementInterface.invokeConnectedCallback(element);
</ins><span class="cx">             break;
</span><span class="cx">         case Type::Disconnected:
</span><del>-            m_interface-&gt;invokeDisconnectedCallback(m_element.get());
</del><ins>+            elementInterface.invokeDisconnectedCallback(element);
</ins><span class="cx">             break;
</span><span class="cx">         case Type::Adopted:
</span><del>-            m_interface-&gt;invokeAdoptedCallback(m_element.get(), *m_oldDocument, *m_newDocument);
</del><ins>+            elementInterface.invokeAdoptedCallback(element, *m_oldDocument, *m_newDocument);
</ins><span class="cx">             break;
</span><span class="cx">         case Type::AttributeChanged:
</span><span class="cx">             ASSERT(m_attributeName);
</span><del>-            m_interface-&gt;invokeAttributeChangedCallback(m_element.get(), m_attributeName.value(), m_oldValue, m_newValue);
</del><ins>+            elementInterface.invokeAttributeChangedCallback(element, m_attributeName.value(), m_oldValue, m_newValue);
</ins><span class="cx">             break;
</span><span class="cx">         }
</span><span class="cx">     }
</span><span class="lines">@@ -99,8 +92,6 @@
</span><span class="cx"> 
</span><span class="cx"> private:
</span><span class="cx">     Type m_type;
</span><del>-    Ref&lt;Element&gt; m_element;
-    Ref&lt;JSCustomElementInterface&gt; m_interface;
</del><span class="cx">     RefPtr&lt;Document&gt; m_oldDocument;
</span><span class="cx">     RefPtr&lt;Document&gt; m_newDocument;
</span><span class="cx">     Optional&lt;QualifiedName&gt; m_attributeName;
</span><span class="lines">@@ -108,7 +99,8 @@
</span><span class="cx">     AtomicString m_newValue;
</span><span class="cx"> };
</span><span class="cx"> 
</span><del>-CustomElementReactionQueue::CustomElementReactionQueue()
</del><ins>+CustomElementReactionQueue::CustomElementReactionQueue(JSCustomElementInterface&amp; elementInterface)
+    : m_interface(elementInterface)
</ins><span class="cx"> { }
</span><span class="cx"> 
</span><span class="cx"> CustomElementReactionQueue::~CustomElementReactionQueue()
</span><span class="lines">@@ -116,13 +108,17 @@
</span><span class="cx">     ASSERT(m_items.isEmpty());
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void CustomElementReactionQueue::enqueueElementUpgrade(Element&amp; element, JSCustomElementInterface&amp; elementInterface)
</del><ins>+void CustomElementReactionQueue::clear()
</ins><span class="cx"> {
</span><del>-    ASSERT(element.tagQName() == elementInterface.name());
-    if (auto* queue = CustomElementReactionStack::ensureCurrentQueue())
-        queue-&gt;m_items.append({CustomElementReactionQueueItem::Type::ElementUpgrade, element, elementInterface});
</del><ins>+    m_items.clear();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void CustomElementReactionQueue::enqueueElementUpgrade(Element&amp; element)
+{
+    auto&amp; queue = CustomElementReactionStack::ensureCurrentQueue(element);
+    queue.m_items.append({CustomElementReactionQueueItem::Type::ElementUpgrade});
+}
+
</ins><span class="cx"> void CustomElementReactionQueue::enqueueElementUpgradeIfDefined(Element&amp; element)
</span><span class="cx"> {
</span><span class="cx">     ASSERT(element.inDocument());
</span><span class="lines">@@ -139,97 +135,102 @@
</span><span class="cx">     if (!elementInterface)
</span><span class="cx">         return;
</span><span class="cx"> 
</span><del>-    enqueueElementUpgrade(element, *elementInterface);
</del><ins>+    element.enqueueToUpgrade(*elementInterface);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(Element&amp; element)
</span><span class="cx"> {
</span><span class="cx">     ASSERT(element.isDefinedCustomElement());
</span><del>-    auto* elementInterface = element.customElementInterface();
-    ASSERT(elementInterface);
-    if (!elementInterface-&gt;hasConnectedCallback())
-        return;
-
-    if (auto* queue = CustomElementReactionStack::ensureCurrentQueue())
-        queue-&gt;m_items.append({CustomElementReactionQueueItem::Type::Connected, element, *elementInterface});
</del><ins>+    auto&amp; queue = CustomElementReactionStack::ensureCurrentQueue(element);
+    if (queue.m_interface-&gt;hasConnectedCallback())
+        queue.m_items.append({CustomElementReactionQueueItem::Type::Connected});
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded(Element&amp; element)
</span><span class="cx"> {
</span><span class="cx">     ASSERT(element.isDefinedCustomElement());
</span><del>-    auto* elementInterface = element.customElementInterface();
-    ASSERT(elementInterface);
-    if (!elementInterface-&gt;hasDisconnectedCallback())
-        return;
-
-    if (auto* queue = CustomElementReactionStack::ensureCurrentQueue())
-        queue-&gt;m_items.append({CustomElementReactionQueueItem::Type::Disconnected, element, *elementInterface});
</del><ins>+    auto&amp; queue = CustomElementReactionStack::ensureCurrentQueue(element);
+    if (queue.m_interface-&gt;hasDisconnectedCallback())
+        queue.m_items.append({CustomElementReactionQueueItem::Type::Disconnected});
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded(Element&amp; element, Document&amp; oldDocument, Document&amp; newDocument)
</span><span class="cx"> {
</span><span class="cx">     ASSERT(element.isDefinedCustomElement());
</span><del>-    auto* elementInterface = element.customElementInterface();
-    ASSERT(elementInterface);
-    if (!elementInterface-&gt;hasAdoptedCallback())
-        return;
-
-    if (auto* queue = CustomElementReactionStack::ensureCurrentQueue())
-        queue-&gt;m_items.append({element, *elementInterface, oldDocument, newDocument});
</del><ins>+    auto&amp; queue = CustomElementReactionStack::ensureCurrentQueue(element);
+    if (queue.m_interface-&gt;hasAdoptedCallback())
+        queue.m_items.append({oldDocument, newDocument});
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded(Element&amp; element, const QualifiedName&amp; attributeName, const AtomicString&amp; oldValue, const AtomicString&amp; newValue)
</span><span class="cx"> {
</span><span class="cx">     ASSERT(element.isDefinedCustomElement());
</span><del>-    auto* elementInterface = element.customElementInterface();
-    ASSERT(elementInterface);
-    if (!elementInterface-&gt;observesAttribute(attributeName.localName()))
-        return;
-
-    if (auto* queue = CustomElementReactionStack::ensureCurrentQueue())
-        queue-&gt;m_items.append({element, *elementInterface, attributeName, oldValue, newValue});
</del><ins>+    auto&amp; queue = CustomElementReactionStack::ensureCurrentQueue(element);
+    if (queue.m_interface-&gt;observesAttribute(attributeName.localName()))
+        queue.m_items.append({attributeName, oldValue, newValue});
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-void CustomElementReactionQueue::enqueuePostUpgradeReactions(Element&amp; element, JSCustomElementInterface&amp; elementInterface)
</del><ins>+void CustomElementReactionQueue::enqueuePostUpgradeReactions(Element&amp; element)
</ins><span class="cx"> {
</span><ins>+    ASSERT(element.isCustomElementUpgradeCandidate());
</ins><span class="cx">     if (!element.hasAttributes() &amp;&amp; !element.inDocument())
</span><span class="cx">         return;
</span><span class="cx"> 
</span><del>-    auto* queue = CustomElementReactionStack::ensureCurrentQueue();
-    if (!queue)
-        return;
</del><ins>+    auto* queue = element.reactionQueue();
+    ASSERT(queue);
</ins><span class="cx"> 
</span><span class="cx">     if (element.hasAttributes()) {
</span><span class="cx">         for (auto&amp; attribute : element.attributesIterator()) {
</span><del>-            if (elementInterface.observesAttribute(attribute.localName()))
-                queue-&gt;m_items.append({element, elementInterface, attribute.name(), nullAtom, attribute.value()});
</del><ins>+            if (queue-&gt;m_interface-&gt;observesAttribute(attribute.localName()))
+                queue-&gt;m_items.append({attribute.name(), nullAtom, attribute.value()});
</ins><span class="cx">         }
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    if (element.inDocument() &amp;&amp; elementInterface.hasConnectedCallback())
-        queue-&gt;m_items.append({CustomElementReactionQueueItem::Type::Connected, element, elementInterface});
</del><ins>+    if (element.inDocument() &amp;&amp; queue-&gt;m_interface-&gt;hasConnectedCallback())
+        queue-&gt;m_items.append({CustomElementReactionQueueItem::Type::Connected});
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-void CustomElementReactionQueue::invokeAll()
</del><ins>+void CustomElementReactionQueue::invokeAll(Element&amp; element)
</ins><span class="cx"> {
</span><del>-    // FIXME: This queue needs to be per element.
</del><span class="cx">     while (!m_items.isEmpty()) {
</span><span class="cx">         Vector&lt;CustomElementReactionQueueItem&gt; items = WTFMove(m_items);
</span><span class="cx">         for (auto&amp; item : items)
</span><del>-            item.invoke();
</del><ins>+            item.invoke(element, m_interface.get());
</ins><span class="cx">     }
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-CustomElementReactionQueue* CustomElementReactionStack::ensureCurrentQueue()
</del><ins>+inline void CustomElementReactionStack::ElementQueue::add(Element&amp; element)
</ins><span class="cx"> {
</span><del>-    // FIXME: This early exit indicates a bug that some DOM API is missing CEReactions
-    if (!s_currentProcessingStack)
-        return nullptr;
</del><ins>+    // FIXME: Avoid inserting the same element multiple times.
+    m_elements.append(element);
+}
</ins><span class="cx"> 
</span><ins>+inline void CustomElementReactionStack::ElementQueue::invokeAll()
+{
+    Vector&lt;Ref&lt;Element&gt;&gt; elements;
+    elements.swap(m_elements);
+    for (auto&amp; element : elements) {
+        auto* queue = element-&gt;reactionQueue();
+        ASSERT(queue);
+        queue-&gt;invokeAll(element.get());
+    }
+    ASSERT(m_elements.isEmpty());
+}
+
+CustomElementReactionQueue&amp; CustomElementReactionStack::ensureCurrentQueue(Element&amp; element)
+{
+    ASSERT(element.reactionQueue());
+    if (!s_currentProcessingStack) {
+        auto&amp; queue = CustomElementReactionStack::ensureBackupQueue();
+        queue.add(element);
+        return *element.reactionQueue();
+    }
+
</ins><span class="cx">     auto*&amp; queue = s_currentProcessingStack-&gt;m_queue;
</span><span class="cx">     if (!queue) // We use a raw pointer to avoid genearing code to delete it in ~CustomElementReactionStack.
</span><del>-        queue = new CustomElementReactionQueue;
-    return queue;
</del><ins>+        queue = new ElementQueue;
+    queue-&gt;add(element);
+    return *element.reactionQueue();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> CustomElementReactionStack* CustomElementReactionStack::s_currentProcessingStack = nullptr;
</span><span class="lines">@@ -242,6 +243,39 @@
</span><span class="cx">     m_queue = nullptr;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+class BackupElementQueueMicrotask final : public Microtask {
+    WTF_MAKE_FAST_ALLOCATED;
+private:
+    Result run() final
+    {
+        CustomElementReactionStack::processBackupQueue();
+        return Result::Done;
+    }
+};
+
+static bool s_processingBackupElementQueue = false;
+
+CustomElementReactionStack::ElementQueue&amp; CustomElementReactionStack::ensureBackupQueue()
+{
+    if (!s_processingBackupElementQueue) {
+        s_processingBackupElementQueue = true;
+        MicrotaskQueue::mainThreadQueue().append(std::make_unique&lt;BackupElementQueueMicrotask&gt;());
+    }
+    return backupElementQueue();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void CustomElementReactionStack::processBackupQueue()
+{
+    backupElementQueue().invokeAll();
+    s_processingBackupElementQueue = false;
+}
+
+CustomElementReactionStack::ElementQueue&amp; CustomElementReactionStack::backupElementQueue()
+{
+    static NeverDestroyed&lt;ElementQueue&gt; queue;
+    return queue.get();
+}
+
+}
+
</ins><span class="cx"> #endif
</span></span></pre></div>
<a id="trunkSourceWebCoredomCustomElementReactionQueueh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/CustomElementReactionQueue.h (207809 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/CustomElementReactionQueue.h        2016-10-25 06:07:04 UTC (rev 207809)
+++ trunk/Source/WebCore/dom/CustomElementReactionQueue.h        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -42,20 +42,22 @@
</span><span class="cx"> class CustomElementReactionQueue {
</span><span class="cx">     WTF_MAKE_NONCOPYABLE(CustomElementReactionQueue);
</span><span class="cx"> public:
</span><del>-    CustomElementReactionQueue();
</del><ins>+    CustomElementReactionQueue(JSCustomElementInterface&amp;);
</ins><span class="cx">     ~CustomElementReactionQueue();
</span><span class="cx"> 
</span><del>-    static void enqueueElementUpgrade(Element&amp;, JSCustomElementInterface&amp;);
</del><ins>+    static void enqueueElementUpgrade(Element&amp;);
</ins><span class="cx">     static void enqueueElementUpgradeIfDefined(Element&amp;);
</span><span class="cx">     static void enqueueConnectedCallbackIfNeeded(Element&amp;);
</span><span class="cx">     static void enqueueDisconnectedCallbackIfNeeded(Element&amp;);
</span><span class="cx">     static void enqueueAdoptedCallbackIfNeeded(Element&amp;, Document&amp; oldDocument, Document&amp; newDocument);
</span><span class="cx">     static void enqueueAttributeChangedCallbackIfNeeded(Element&amp;, const QualifiedName&amp;, const AtomicString&amp; oldValue, const AtomicString&amp; newValue);
</span><del>-    static void enqueuePostUpgradeReactions(Element&amp;, JSCustomElementInterface&amp;);
</del><ins>+    static void enqueuePostUpgradeReactions(Element&amp;);
</ins><span class="cx"> 
</span><del>-    void invokeAll();
</del><ins>+    void invokeAll(Element&amp;);
+    void clear();
</ins><span class="cx"> 
</span><span class="cx"> private:
</span><ins>+    Ref&lt;JSCustomElementInterface&gt; m_interface;
</ins><span class="cx">     Vector&lt;CustomElementReactionQueueItem&gt; m_items;
</span><span class="cx"> };
</span><span class="cx"> 
</span><span class="lines">@@ -74,15 +76,28 @@
</span><span class="cx">         s_currentProcessingStack = m_previousProcessingStack;
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    // FIXME: This should be a reference once &quot;ensure&quot; starts to work.
-    static CustomElementReactionQueue* ensureCurrentQueue();
</del><ins>+    static CustomElementReactionQueue&amp; ensureCurrentQueue(Element&amp;);
</ins><span class="cx"> 
</span><span class="cx">     static bool hasCurrentProcessingStack() { return s_currentProcessingStack; }
</span><span class="cx"> 
</span><ins>+    static void processBackupQueue();
+
</ins><span class="cx"> private:
</span><ins>+    class ElementQueue {
+    public:
+        void add(Element&amp;);
+        void invokeAll();
+
+    private:
+        Vector&lt;Ref&lt;Element&gt;&gt; m_elements;
+    };
+
</ins><span class="cx">     WEBCORE_EXPORT void processQueue();
</span><span class="cx"> 
</span><del>-    CustomElementReactionQueue* m_queue { nullptr };
</del><ins>+    static ElementQueue&amp; ensureBackupQueue();
+    static ElementQueue&amp; backupElementQueue();
+
+    ElementQueue* m_queue { nullptr };
</ins><span class="cx">     CustomElementReactionStack* m_previousProcessingStack;
</span><span class="cx"> 
</span><span class="cx">     WEBCORE_EXPORT static CustomElementReactionStack* s_currentProcessingStack;
</span></span></pre></div>
<a id="trunkSourceWebCoredomCustomElementRegistrycpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/CustomElementRegistry.cpp (207809 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/CustomElementRegistry.cpp        2016-10-25 06:07:04 UTC (rev 207809)
+++ trunk/Source/WebCore/dom/CustomElementRegistry.cpp        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -60,7 +60,7 @@
</span><span class="cx"> {
</span><span class="cx">     for (Element* element = ElementTraversal::firstWithin(node); element; element = ElementTraversal::next(*element)) {
</span><span class="cx">         if (element-&gt;isCustomElementUpgradeCandidate() &amp;&amp; element-&gt;tagQName() == elementInterface.name())
</span><del>-            CustomElementReactionQueue::enqueueElementUpgrade(*element, elementInterface);
</del><ins>+            element-&gt;enqueueToUpgrade(elementInterface);
</ins><span class="cx">         if (auto* shadowRoot = element-&gt;shadowRoot()) {
</span><span class="cx">             if (shadowRoot-&gt;mode() != ShadowRoot::Mode::UserAgent)
</span><span class="cx">                 enqueueUpgradeInShadowIncludingTreeOrder(*shadowRoot, elementInterface);
</span></span></pre></div>
<a id="trunkSourceWebCoredomDocumentcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/Document.cpp (207809 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/Document.cpp        2016-10-25 06:07:04 UTC (rev 207809)
+++ trunk/Source/WebCore/dom/Document.cpp        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -1075,8 +1075,7 @@
</span><span class="cx">         if (UNLIKELY(registry)) {
</span><span class="cx">             if (auto* elementInterface = registry-&gt;findInterface(name)) {
</span><span class="cx">                 auto element = HTMLElement::create(name, document);
</span><del>-                element-&gt;setIsCustomElementUpgradeCandidate();
-                CustomElementReactionQueue::enqueueElementUpgrade(element.get(), *elementInterface);
</del><ins>+                element-&gt;enqueueToUpgrade(*elementInterface);
</ins><span class="cx">                 return element;
</span><span class="cx">             }
</span><span class="cx">         }
</span></span></pre></div>
<a id="trunkSourceWebCoredomElementcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/Element.cpp (207809 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/Element.cpp        2016-10-25 06:07:04 UTC (rev 207809)
+++ trunk/Source/WebCore/dom/Element.cpp        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -1902,15 +1902,22 @@
</span><span class="cx"> {
</span><span class="cx">     clearFlag(IsEditingTextOrUndefinedCustomElementFlag);
</span><span class="cx">     setFlag(IsCustomElement);
</span><del>-    ensureElementRareData().setCustomElementInterface(elementInterface);
</del><ins>+    auto&amp; data = ensureElementRareData();
+    if (!data.customElementReactionQueue())
+        data.setCustomElementReactionQueue(std::make_unique&lt;CustomElementReactionQueue&gt;(elementInterface));
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-void Element::setIsFailedCustomElement(JSCustomElementInterface&amp; elementInterface)
</del><ins>+void Element::setIsFailedCustomElement(JSCustomElementInterface&amp;)
</ins><span class="cx"> {
</span><span class="cx">     ASSERT(isUndefinedCustomElement());
</span><span class="cx">     ASSERT(getFlag(IsEditingTextOrUndefinedCustomElementFlag));
</span><span class="cx">     clearFlag(IsCustomElement);
</span><del>-    ensureElementRareData().setCustomElementInterface(elementInterface);
</del><ins>+
+    if (hasRareData()) {
+        // Clear the queue instead of deleting it since this function can be called inside CustomElementReactionQueue::invokeAll during upgrades.
+        if (auto* queue = elementRareData()-&gt;customElementReactionQueue())
+            queue-&gt;clear();
+    }
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void Element::setIsCustomElementUpgradeCandidate()
</span><span class="lines">@@ -1920,12 +1927,25 @@
</span><span class="cx">     setFlag(IsEditingTextOrUndefinedCustomElementFlag);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-JSCustomElementInterface* Element::customElementInterface() const
</del><ins>+void Element::enqueueToUpgrade(JSCustomElementInterface&amp; elementInterface)
</ins><span class="cx"> {
</span><del>-    ASSERT(isDefinedCustomElement());
</del><ins>+    ASSERT(!isDefinedCustomElement() &amp;&amp; !isFailedCustomElement());
+    setFlag(IsCustomElement);
+    setFlag(IsEditingTextOrUndefinedCustomElementFlag);
+
+    auto&amp; data = ensureElementRareData();
+    ASSERT(!data.customElementReactionQueue());
+
+    data.setCustomElementReactionQueue(std::make_unique&lt;CustomElementReactionQueue&gt;(elementInterface));
+    data.customElementReactionQueue()-&gt;enqueueElementUpgrade(*this);
+}
+
+CustomElementReactionQueue* Element::reactionQueue() const
+{
+    ASSERT(isDefinedCustomElement() || isCustomElementUpgradeCandidate());
</ins><span class="cx">     if (!hasRareData())
</span><span class="cx">         return nullptr;
</span><del>-    return elementRareData()-&gt;customElementInterface();
</del><ins>+    return elementRareData()-&gt;customElementReactionQueue();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> #endif
</span></span></pre></div>
<a id="trunkSourceWebCoredomElementh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/Element.h (207809 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/Element.h        2016-10-25 06:07:04 UTC (rev 207809)
+++ trunk/Source/WebCore/dom/Element.h        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -39,6 +39,7 @@
</span><span class="cx"> 
</span><span class="cx"> class ClientRect;
</span><span class="cx"> class ClientRectList;
</span><ins>+class CustomElementReactionQueue;
</ins><span class="cx"> class DatasetDOMStringMap;
</span><span class="cx"> class Dictionary;
</span><span class="cx"> class DOMTokenList;
</span><span class="lines">@@ -284,7 +285,8 @@
</span><span class="cx">     void setIsDefinedCustomElement(JSCustomElementInterface&amp;);
</span><span class="cx">     void setIsFailedCustomElement(JSCustomElementInterface&amp;);
</span><span class="cx">     void setIsCustomElementUpgradeCandidate();
</span><del>-    JSCustomElementInterface* customElementInterface() const;
</del><ins>+    void enqueueToUpgrade(JSCustomElementInterface&amp;);
+    CustomElementReactionQueue* reactionQueue() const;
</ins><span class="cx"> #endif
</span><span class="cx"> 
</span><span class="cx">     // FIXME: this should not be virtual, do not override this.
</span></span></pre></div>
<a id="trunkSourceWebCoredomElementRareDatah"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/ElementRareData.h (207809 => 207810)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/ElementRareData.h        2016-10-25 06:07:04 UTC (rev 207809)
+++ trunk/Source/WebCore/dom/ElementRareData.h        2016-10-25 06:18:13 UTC (rev 207810)
</span><span class="lines">@@ -22,6 +22,7 @@
</span><span class="cx"> #ifndef ElementRareData_h
</span><span class="cx"> #define ElementRareData_h
</span><span class="cx"> 
</span><ins>+#include &quot;CustomElementReactionQueue.h&quot;
</ins><span class="cx"> #include &quot;DOMTokenList.h&quot;
</span><span class="cx"> #include &quot;DatasetDOMStringMap.h&quot;
</span><span class="cx"> #include &quot;JSCustomElementInterface.h&quot;
</span><span class="lines">@@ -95,8 +96,8 @@
</span><span class="cx">     void setShadowRoot(RefPtr&lt;ShadowRoot&gt;&amp;&amp; shadowRoot) { m_shadowRoot = WTFMove(shadowRoot); }
</span><span class="cx"> 
</span><span class="cx"> #if ENABLE(CUSTOM_ELEMENTS)
</span><del>-    JSCustomElementInterface* customElementInterface() { return m_customElementInterface.get(); }
-    void setCustomElementInterface(JSCustomElementInterface&amp; customElementInterface) { m_customElementInterface = &amp;customElementInterface; }
</del><ins>+    CustomElementReactionQueue* customElementReactionQueue() { return m_customElementReactionQueue.get(); }
+    void setCustomElementReactionQueue(std::unique_ptr&lt;CustomElementReactionQueue&gt;&amp;&amp; queue) { m_customElementReactionQueue = WTFMove(queue); }
</ins><span class="cx"> #endif
</span><span class="cx"> 
</span><span class="cx">     NamedNodeMap* attributeMap() const { return m_attributeMap.get(); }
</span><span class="lines">@@ -156,7 +157,7 @@
</span><span class="cx">     std::unique_ptr&lt;DOMTokenList&gt; m_classList;
</span><span class="cx">     RefPtr&lt;ShadowRoot&gt; m_shadowRoot;
</span><span class="cx"> #if ENABLE(CUSTOM_ELEMENTS)
</span><del>-    RefPtr&lt;JSCustomElementInterface&gt; m_customElementInterface;
</del><ins>+    std::unique_ptr&lt;CustomElementReactionQueue&gt; m_customElementReactionQueue;
</ins><span class="cx"> #endif
</span><span class="cx">     std::unique_ptr&lt;NamedNodeMap&gt; m_attributeMap;
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>