- Revision
- 209582
- Author
- [email protected]
- Date
- 2016-12-08 16:53:32 -0800 (Thu, 08 Dec 2016)
Log Message
ASSERTION FAILED: m_items.isEmpty() in CustomElementReactionQueue destructor
https://bugs.webkit.org/show_bug.cgi?id=162029
<rdar://problem/28945851>
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 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.
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.
Modified Paths
Added Paths
Diff
Modified: trunk/LayoutTests/ChangeLog (209581 => 209582)
--- trunk/LayoutTests/ChangeLog 2016-12-09 00:44:05 UTC (rev 209581)
+++ trunk/LayoutTests/ChangeLog 2016-12-09 00:53:32 UTC (rev 209582)
@@ -1,3 +1,20 @@
+2016-12-07 Ryosuke Niwa <[email protected]>
+
+ ASSERTION FAILED: m_items.isEmpty() in CustomElementReactionQueue destructor
+ https://bugs.webkit.org/show_bug.cgi?id=162029
+ <rdar://problem/28945851>
+
+ 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.
+
2016-12-08 Ryan Haddad <[email protected]>
Marking compositing/rtl/rtl-fixed-overflow.html as failing on mac-wk1.
Added: trunk/LayoutTests/fast/custom-elements/disconnected-callback-in-detached-iframe-expected.txt (0 => 209582)
--- 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)
@@ -0,0 +1,3 @@
+
+PASS Removing a custom element inside a detached iframe must enqueue disconnectedCallback
+
Added: trunk/LayoutTests/fast/custom-elements/disconnected-callback-in-detached-iframe.html (0 => 209582)
--- 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)
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: disconnectedCallback must be enqueued inside a detached iframe</title>
+<meta name="author" title="Ryosuke Niwa" href=""
+<meta name="assert" content="disconnectedCallback must be enqueued inside a detached iframe">
+<meta name="help" content="https://dom.spec.whatwg.org/#concept-node-remove">
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+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(() => {
+ return create_iframe_and_wait_for_onload().then((iframe) => {
+ 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');
+
+</script>
+</body>
+</html>
Added: trunk/LayoutTests/fast/custom-elements/element-queue-during-document-destruction-expected.txt (0 => 209582)
--- 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)
@@ -0,0 +1,4 @@
+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.
Added: trunk/LayoutTests/fast/custom-elements/element-queue-during-document-destruction.html (0 => 209582)
--- 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)
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This tests removing an iframe with a custom element inside a callback. WebKit should not hit assertions.<br>
+WebKit should not hit any assertions.</p>
+<script>
+
+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 = '<test-element></test-element><test-element id="foo"></test-element>';
+ 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(`<p>PASS. WebKit did not hit any assertions.</p>`);
+}
+
+</script>
+</body>
+</html>
Modified: trunk/Source/WebCore/ChangeLog (209581 => 209582)
--- 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)
@@ -1,3 +1,30 @@
+2016-12-07 Ryosuke Niwa <[email protected]>
+
+ ASSERTION FAILED: m_items.isEmpty() in CustomElementReactionQueue destructor
+ https://bugs.webkit.org/show_bug.cgi?id=162029
+ <rdar://problem/28945851>
+
+ 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.
+
2016-12-08 Daniel Bates <[email protected]>
Add Strict Mixed Content Checking and Upgrade Insecure Requests to WebKit Feature Status dashboard
Modified: trunk/Source/WebCore/dom/CustomElementReactionQueue.cpp (209581 => 209582)
--- 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)
@@ -141,6 +141,7 @@
void CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(Element& element)
{
ASSERT(element.isDefinedCustomElement());
+ ASSERT(element.document().refCount() > 0);
auto& queue = CustomElementReactionStack::ensureCurrentQueue(element);
if (queue.m_interface->hasConnectedCallback())
queue.m_items.append({CustomElementReactionQueueItem::Type::Connected});
@@ -149,6 +150,8 @@
void CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded(Element& element)
{
ASSERT(element.isDefinedCustomElement());
+ if (element.document().refCount() <= 0)
+ return; // Don't enqueue disconnectedCallback if the entire document is getting destructed.
auto& queue = CustomElementReactionStack::ensureCurrentQueue(element);
if (queue.m_interface->hasDisconnectedCallback())
queue.m_items.append({CustomElementReactionQueueItem::Type::Disconnected});
@@ -157,6 +160,7 @@
void CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded(Element& element, Document& oldDocument, Document& newDocument)
{
ASSERT(element.isDefinedCustomElement());
+ ASSERT(element.document().refCount() > 0);
auto& queue = CustomElementReactionStack::ensureCurrentQueue(element);
if (queue.m_interface->hasAdoptedCallback())
queue.m_items.append({oldDocument, newDocument});
@@ -165,6 +169,7 @@
void CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
{
ASSERT(element.isDefinedCustomElement());
+ ASSERT(element.document().refCount() > 0);
auto& queue = CustomElementReactionStack::ensureCurrentQueue(element);
if (queue.m_interface->observesAttribute(attributeName.localName()))
queue.m_items.append({attributeName, oldValue, newValue});