<!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>[209582] 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/209582">209582</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2016-12-08 16:53:32 -0800 (Thu, 08 Dec 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>ASSERTION FAILED: m_items.isEmpty() in CustomElementReactionQueue destructor
https://bugs.webkit.org/show_bug.cgi?id=162029
&lt;rdar://problem/28945851&gt;

Reviewed by Chris Dumez.

Source/WebCore:

The bug was caused by Document::removedLastRef enqueuing disconnectedCallback during a tear down.
Don't enqueue a disconnectedCallback while a document is getting torn down since that should not be
observable to author scripts. The connected, adopted, and attributeChanged callbacks are immune from
this problem since they don't happen during a document destruction.

Note that this was also the case prior to this patch since the disconnectedCallback would have been
added to the current CustomElementReactionQueue which will be destructed without invoking callbacks
(or hit a release assertion added in <a href="http://trac.webkit.org/projects/webkit/changeset/208785">r208785</a> and <a href="http://trac.webkit.org/projects/webkit/changeset/209426">r209426</a> for now).

Tests: fast/custom-elements/disconnected-callback-in-detached-iframe.html
       fast/custom-elements/element-queue-during-document-destruction.html

* dom/CustomElementReactionQueue.cpp:
(WebCore::CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded): Added an assertion that
document's refCount hasn't reached zero yet.
(WebCore::CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded): Fixed the bug.
(WebCore::CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded): Added the same assertion.
(WebCore::CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded): Ditto.

LayoutTests:

Added a regression test that reliably reproduces the crash in DumpRenderTree / WebKitTestRunner.

Also added a W3C style testharness.js test for the behavior I broke in an earlier iteration of the patch.

* fast/custom-elements/disconnected-callback-in-detached-iframe-expected.txt: Added.
* fast/custom-elements/disconnected-callback-in-detached-iframe.html: Added.
* fast/custom-elements/element-queue-during-document-destruction-expected.txt: Added.
* fast/custom-elements/element-queue-during-document-destruction.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="#trunkSourceWebCoredomCustomElementReactionQueuecpp">trunk/Source/WebCore/dom/CustomElementReactionQueue.cpp</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsfastcustomelementsdisconnectedcallbackindetachediframeexpectedtxt">trunk/LayoutTests/fast/custom-elements/disconnected-callback-in-detached-iframe-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastcustomelementsdisconnectedcallbackindetachediframehtml">trunk/LayoutTests/fast/custom-elements/disconnected-callback-in-detached-iframe.html</a></li>
<li><a href="#trunkLayoutTestsfastcustomelementselementqueueduringdocumentdestructionexpectedtxt">trunk/LayoutTests/fast/custom-elements/element-queue-during-document-destruction-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastcustomelementselementqueueduringdocumentdestructionhtml">trunk/LayoutTests/fast/custom-elements/element-queue-during-document-destruction.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (209581 => 209582)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2016-12-09 00:44:05 UTC (rev 209581)
+++ trunk/LayoutTests/ChangeLog        2016-12-09 00:53:32 UTC (rev 209582)
</span><span class="lines">@@ -1,3 +1,20 @@
</span><ins>+2016-12-07  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        ASSERTION FAILED: m_items.isEmpty() in CustomElementReactionQueue destructor
+        https://bugs.webkit.org/show_bug.cgi?id=162029
+        &lt;rdar://problem/28945851&gt;
+
+        Reviewed by Chris Dumez.
+
+        Added a regression test that reliably reproduces the crash in DumpRenderTree / WebKitTestRunner.
+
+        Also added a W3C style testharness.js test for the behavior I broke in an earlier iteration of the patch.
+
+        * fast/custom-elements/disconnected-callback-in-detached-iframe-expected.txt: Added.
+        * fast/custom-elements/disconnected-callback-in-detached-iframe.html: Added.
+        * fast/custom-elements/element-queue-during-document-destruction-expected.txt: Added.
+        * fast/custom-elements/element-queue-during-document-destruction.html: Added.
+
</ins><span class="cx"> 2016-12-08  Ryan Haddad  &lt;ryanhaddad@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Marking compositing/rtl/rtl-fixed-overflow.html as failing on mac-wk1.
</span></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementsdisconnectedcallbackindetachediframeexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/custom-elements/disconnected-callback-in-detached-iframe-expected.txt (0 => 209582)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/disconnected-callback-in-detached-iframe-expected.txt                                (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/disconnected-callback-in-detached-iframe-expected.txt        2016-12-09 00:53:32 UTC (rev 209582)
</span><span class="lines">@@ -0,0 +1,3 @@
</span><ins>+
+PASS Removing a custom element inside a detached iframe must enqueue disconnectedCallback 
+
</ins></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementsdisconnectedcallbackindetachediframehtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/custom-elements/disconnected-callback-in-detached-iframe.html (0 => 209582)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/disconnected-callback-in-detached-iframe.html                                (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/disconnected-callback-in-detached-iframe.html        2016-12-09 00:53:32 UTC (rev 209582)
</span><span class="lines">@@ -0,0 +1,46 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+&lt;title&gt;Custom Elements: disconnectedCallback must be enqueued inside a detached iframe&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;disconnectedCallback must be enqueued inside a detached iframe&quot;&gt;
+&lt;meta name=&quot;help&quot; content=&quot;https://dom.spec.whatwg.org/#concept-node-remove&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;script src=&quot;../../imported/w3c/web-platform-tests/custom-elements/reactions/resources/reactions.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_iframe_and_wait_for_onload() {
+    return new Promise(function (resolve) {
+        let iframe = document.createElement('iframe');
+        iframe.onload = function () { resolve(iframe); }
+        document.body.appendChild(iframe);
+    });
+}
+
+let element = define_custom_element_in_window(window, 'test-element');
+
+promise_test(() =&gt; {
+    return create_iframe_and_wait_for_onload().then((iframe) =&gt; {
+        let testElement = document.createElement('test-element');
+        assert_array_equals(element.takeLog().types(), ['constructed']);
+
+        let contentDocument = iframe.contentDocument;
+        iframe.contentDocument.body.appendChild(testElement);
+        assert_array_equals(element.takeLog().types(), ['adopted', 'connected']);
+
+        iframe.remove();
+        assert_array_equals(element.takeLog().types(), []);
+
+        contentDocument.body.remove();
+        assert_array_equals(element.takeLog().types(), ['disconnected']);
+    });
+}, 'Removing a custom element inside a detached iframe must enqueue disconnectedCallback');
+
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementselementqueueduringdocumentdestructionexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/custom-elements/element-queue-during-document-destruction-expected.txt (0 => 209582)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/element-queue-during-document-destruction-expected.txt                                (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/element-queue-during-document-destruction-expected.txt        2016-12-09 00:53:32 UTC (rev 209582)
</span><span class="lines">@@ -0,0 +1,4 @@
</span><ins>+This tests removing an iframe with a custom element inside a callback. WebKit should not hit assertions.
+WebKit should not hit any assertions.
+
+PASS. WebKit did not hit any assertions.
</ins></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementselementqueueduringdocumentdestructionhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/custom-elements/element-queue-during-document-destruction.html (0 => 209582)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/element-queue-during-document-destruction.html                                (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/element-queue-during-document-destruction.html        2016-12-09 00:53:32 UTC (rev 209582)
</span><span class="lines">@@ -0,0 +1,42 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;body&gt;
+&lt;p&gt;This tests removing an iframe with a custom element inside a callback. WebKit should not hit assertions.&lt;br&gt;
+WebKit should not hit any assertions.&lt;/p&gt;
+&lt;script&gt;
+
+customElements.define('test-element', class extends HTMLElement {
+    disconnectedCallback() { }
+    attributeChangedCallback(name, oldValue, newValue) {
+        if (newValue != 'bar')
+            return;
+        removeIframe();
+        GCController.collect();
+    }
+    static get observedAttributes() { return ['id']; }
+});
+
+function insertIframe() {
+    let iframe = document.createElement('iframe');
+    document.body.appendChild(iframe);
+    let container = document.createElement('div');
+    container.innerHTML = '&lt;test-element&gt;&lt;/test-element&gt;&lt;test-element id=&quot;foo&quot;&gt;&lt;/test-element&gt;';
+    iframe.contentDocument.body.appendChild(container);
+}
+
+function removeIframe() {
+    document.querySelector('iframe').remove();
+}
+
+if (!window.GCController)
+    document.write('This test requires GCController');
+else {
+    testRunner.dumpAsText();
+    insertIframe();
+    document.createElement('test-element').id = 'bar';
+    document.write(`&lt;p&gt;PASS. WebKit did not hit any assertions.&lt;/p&gt;`);
+}
+
+&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 (209581 => 209582)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2016-12-09 00:44:05 UTC (rev 209581)
+++ trunk/Source/WebCore/ChangeLog        2016-12-09 00:53:32 UTC (rev 209582)
</span><span class="lines">@@ -1,3 +1,30 @@
</span><ins>+2016-12-07  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        ASSERTION FAILED: m_items.isEmpty() in CustomElementReactionQueue destructor
+        https://bugs.webkit.org/show_bug.cgi?id=162029
+        &lt;rdar://problem/28945851&gt;
+
+        Reviewed by Chris Dumez.
+
+        The bug was caused by Document::removedLastRef enqueuing disconnectedCallback during a tear down.
+        Don't enqueue a disconnectedCallback while a document is getting torn down since that should not be
+        observable to author scripts. The connected, adopted, and attributeChanged callbacks are immune from
+        this problem since they don't happen during a document destruction.
+
+        Note that this was also the case prior to this patch since the disconnectedCallback would have been
+        added to the current CustomElementReactionQueue which will be destructed without invoking callbacks
+        (or hit a release assertion added in r208785 and r209426 for now).
+
+        Tests: fast/custom-elements/disconnected-callback-in-detached-iframe.html
+               fast/custom-elements/element-queue-during-document-destruction.html
+
+        * dom/CustomElementReactionQueue.cpp:
+        (WebCore::CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded): Added an assertion that
+        document's refCount hasn't reached zero yet.
+        (WebCore::CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded): Fixed the bug.
+        (WebCore::CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded): Added the same assertion.
+        (WebCore::CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded): Ditto.
+
</ins><span class="cx"> 2016-12-08  Daniel Bates  &lt;dabates@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Add Strict Mixed Content Checking and Upgrade Insecure Requests to WebKit Feature Status dashboard
</span></span></pre></div>
<a id="trunkSourceWebCoredomCustomElementReactionQueuecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/CustomElementReactionQueue.cpp (209581 => 209582)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/CustomElementReactionQueue.cpp        2016-12-09 00:44:05 UTC (rev 209581)
+++ trunk/Source/WebCore/dom/CustomElementReactionQueue.cpp        2016-12-09 00:53:32 UTC (rev 209582)
</span><span class="lines">@@ -141,6 +141,7 @@
</span><span class="cx"> void CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(Element&amp; element)
</span><span class="cx"> {
</span><span class="cx">     ASSERT(element.isDefinedCustomElement());
</span><ins>+    ASSERT(element.document().refCount() &gt; 0);
</ins><span class="cx">     auto&amp; queue = CustomElementReactionStack::ensureCurrentQueue(element);
</span><span class="cx">     if (queue.m_interface-&gt;hasConnectedCallback())
</span><span class="cx">         queue.m_items.append({CustomElementReactionQueueItem::Type::Connected});
</span><span class="lines">@@ -149,6 +150,8 @@
</span><span class="cx"> void CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded(Element&amp; element)
</span><span class="cx"> {
</span><span class="cx">     ASSERT(element.isDefinedCustomElement());
</span><ins>+    if (element.document().refCount() &lt;= 0)
+        return; // Don't enqueue disconnectedCallback if the entire document is getting destructed.
</ins><span class="cx">     auto&amp; queue = CustomElementReactionStack::ensureCurrentQueue(element);
</span><span class="cx">     if (queue.m_interface-&gt;hasDisconnectedCallback())
</span><span class="cx">         queue.m_items.append({CustomElementReactionQueueItem::Type::Disconnected});
</span><span class="lines">@@ -157,6 +160,7 @@
</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><ins>+    ASSERT(element.document().refCount() &gt; 0);
</ins><span class="cx">     auto&amp; queue = CustomElementReactionStack::ensureCurrentQueue(element);
</span><span class="cx">     if (queue.m_interface-&gt;hasAdoptedCallback())
</span><span class="cx">         queue.m_items.append({oldDocument, newDocument});
</span><span class="lines">@@ -165,6 +169,7 @@
</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><ins>+    ASSERT(element.document().refCount() &gt; 0);
</ins><span class="cx">     auto&amp; queue = CustomElementReactionStack::ensureCurrentQueue(element);
</span><span class="cx">     if (queue.m_interface-&gt;observesAttribute(attributeName.localName()))
</span><span class="cx">         queue.m_items.append({attributeName, oldValue, newValue});
</span></span></pre>
</div>
</div>

</body>
</html>