From a747fd9d60841bffe87733eda0a833a85a23c0ed Mon Sep 17 00:00:00 2001 From: "rniwa@webkit.org" Date: Wed, 12 Dec 2018 03:58:41 +0000 Subject: [PATCH] connectedCallback is invoked during the removal of the element inside another element's connectedCallback https://bugs.webkit.org/show_bug.cgi?id=183586 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed by Frédéric Wang. Source/WebCore: Align WebKit's behavior with Chrome/Firefox with regards to https://github.com/w3c/webcomponents/issues/760 After much discussion, it's unclear that there is a clear path forward to fixing the oddness that the presence of a custom element reaction changes the timing at which another reaction callback gets invoked. So matching Chrome/Firefox behaviors in this case seems the path of the least resistance to interoperability. Namely, this patch makes WebKit not insert a custom element to the appropriate element queue when the element does not have a matching reaction callback. Put it another way, steps 3-5 in would be done before step 6 in: https://html.spec.whatwg.org/multipage/custom-elements.html#enqueue-a-custom-element-callback-reaction 1. Let definition be element's custom element definition. 2. Let callback be the value of the entry in definition's lifecycle callbacks with key callbackName. 3. If callback is null, then return 4. If callbackName is "attributeChangedCallback", then: 1. Let attributeName be the first element of args. 2. If definition's observed attributes does not contain attributeName, then return. 5. Add a new callback reaction to element's custom element reaction queue, with callback function callback and arguments args. 6. Enqueue an element on the appropriate element queue given element. Test: fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback.html * dom/CustomElementReactionQueue.cpp: (WebCore::CustomElementReactionQueue::enqueueElementUpgrade): (WebCore::CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded): (WebCore::CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded): (WebCore::CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded): (WebCore::CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded): (WebCore::CustomElementReactionQueue::enqueuePostUpgradeReactions): (WebCore::CustomElementReactionQueue::enqueueElementOnAppropriateElementQueue): Renamed from ensureCurrentQueue. * dom/CustomElementReactionQueue.h: LayoutTests: Added a W3C style testharness test. * fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback-expected.txt: Added. * fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback.html: Added. git-svn-id: http://svn.webkit.org/repository/webkit/trunk@239096 268f45cc-cd09-0410-ab3c-d52691b4dbfc --- LayoutTests/ChangeLog | 13 + ...tions-inside-another-callback-expected.txt | 10 + ...ack-reactions-inside-another-callback.html | 223 ++++++++++++++++++ Source/WebCore/ChangeLog | 39 +++ .../dom/CustomElementReactionQueue.cpp | 61 +++-- .../WebCore/dom/CustomElementReactionQueue.h | 2 +- 6 files changed, 324 insertions(+), 24 deletions(-) create mode 100644 LayoutTests/fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback-expected.txt create mode 100644 LayoutTests/fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback.html diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog index e400ac9779c3..5462e48844f2 100644 --- a/LayoutTests/ChangeLog +++ b/LayoutTests/ChangeLog @@ -1,3 +1,16 @@ +2018-12-10 Ryosuke Niwa + + connectedCallback is invoked during the removal of the element inside another element's connectedCallback + https://bugs.webkit.org/show_bug.cgi?id=183586 + + + Reviewed by Frédéric Wang. + + Added a W3C style testharness test. + + * fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback-expected.txt: Added. + * fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback.html: Added. + 2018-12-11 Justin Fan [WebGPU] Implement WebGPUBuffer diff --git a/LayoutTests/fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback-expected.txt b/LayoutTests/fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback-expected.txt new file mode 100644 index 000000000000..351898c58363 --- /dev/null +++ b/LayoutTests/fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback-expected.txt @@ -0,0 +1,10 @@ + +PASS Disconnecting an element with disconnectedCallback while it has a connectedCallback in its custom element reaction queue must result in connectedCallback getting invoked before the removal completes +PASS Disconnecting an element without disconnectedCallback while it has a connectedCallback in its custom element reaction queue must not result in connectedCallback getting invoked before the removal completes +PASS Connecting a element with connectedCallback while it has a disconnectedCallback in its custom element reaction queue must result in disconnectedCallback getting invoked before the insertion completes +PASS Connecting an element without connectedCallback while it has a disconnectedCallback in its custom element reaction queue must not result in disconnectedCallback getting invoked before the insertion completes +PASS Adopting an element with adoptingCallback while it has a connectedCallback in its custom element reaction queue must result in connectedCallback getting invoked before the adoption completes +PASS Adopting an element without adoptingCallback while it has a connectedCallback in its custom element reaction queue must not result in connectedCallback getting invoked before the adoption completes +PASS Setting an observed attribute on an element with attributeChangedCallback while it has a connectedCallback in its custom element reaction queue must result in connectedCallback getting invoked before the attribute change completes +PASS Setting an observed attribute on an element with attributeChangedCallback while it has a connectedCallback in its custom element reaction queue must not result in connectedCallback getting invoked before the attribute change completes + diff --git a/LayoutTests/fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback.html b/LayoutTests/fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback.html new file mode 100644 index 000000000000..21d66feaeec5 --- /dev/null +++ b/LayoutTests/fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback.html @@ -0,0 +1,223 @@ + + + +Custom Elements: must enqueue an element on the appropriate element queue after checking callback is null and the attribute name + + + + + + + + + + + + diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog index 0696547bd7fd..558371996196 100644 --- a/Source/WebCore/ChangeLog +++ b/Source/WebCore/ChangeLog @@ -1,3 +1,42 @@ +2018-12-10 Ryosuke Niwa + + connectedCallback is invoked during the removal of the element inside another element's connectedCallback + https://bugs.webkit.org/show_bug.cgi?id=183586 + + + Reviewed by Frédéric Wang. + + Align WebKit's behavior with Chrome/Firefox with regards to https://github.com/w3c/webcomponents/issues/760 + + After much discussion, it's unclear that there is a clear path forward to fixing the oddness that + the presence of a custom element reaction changes the timing at which another reaction callback gets invoked. + So matching Chrome/Firefox behaviors in this case seems the path of the least resistance to interoperability. + + Namely, this patch makes WebKit not insert a custom element to the appropriate element queue when the element + does not have a matching reaction callback. Put it another way, steps 3-5 in would be done before step 6 in: + https://html.spec.whatwg.org/multipage/custom-elements.html#enqueue-a-custom-element-callback-reaction + 1. Let definition be element's custom element definition. + 2. Let callback be the value of the entry in definition's lifecycle callbacks with key callbackName. + 3. If callback is null, then return + 4. If callbackName is "attributeChangedCallback", then: + 1. Let attributeName be the first element of args. + 2. If definition's observed attributes does not contain attributeName, then return. + 5. Add a new callback reaction to element's custom element reaction queue, with callback function callback + and arguments args. + 6. Enqueue an element on the appropriate element queue given element. + + Test: fast/custom-elements/enqueue-custom-element-callback-reactions-inside-another-callback.html + + * dom/CustomElementReactionQueue.cpp: + (WebCore::CustomElementReactionQueue::enqueueElementUpgrade): + (WebCore::CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded): + (WebCore::CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded): + (WebCore::CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded): + (WebCore::CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded): + (WebCore::CustomElementReactionQueue::enqueuePostUpgradeReactions): + (WebCore::CustomElementReactionQueue::enqueueElementOnAppropriateElementQueue): Renamed from ensureCurrentQueue. + * dom/CustomElementReactionQueue.h: + 2018-12-11 Justin Fan [WebGPU] Implement WebGPUBuffer, and some nullibility consistency in WebGPU diff --git a/Source/WebCore/dom/CustomElementReactionQueue.cpp b/Source/WebCore/dom/CustomElementReactionQueue.cpp index 4007c476e838..e0d13d82c9b8 100644 --- a/Source/WebCore/dom/CustomElementReactionQueue.cpp +++ b/Source/WebCore/dom/CustomElementReactionQueue.cpp @@ -120,12 +120,15 @@ void CustomElementReactionQueue::clear() void CustomElementReactionQueue::enqueueElementUpgrade(Element& element, bool alreadyScheduledToUpgrade) { ASSERT(CustomElementReactionDisallowedScope::isReactionAllowed()); - auto& queue = ensureCurrentQueue(element); + ASSERT(element.reactionQueue()); + auto& queue = *element.reactionQueue(); if (alreadyScheduledToUpgrade) { ASSERT(queue.m_items.size() == 1); ASSERT(queue.m_items[0].type() == CustomElementReactionQueueItem::Type::ElementUpgrade); - } else + } else { queue.m_items.append({CustomElementReactionQueueItem::Type::ElementUpgrade}); + enqueueElementOnAppropriateElementQueue(element); + } } void CustomElementReactionQueue::enqueueElementUpgradeIfDefined(Element& element) @@ -152,9 +155,12 @@ void CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(Element& eleme ASSERT(CustomElementReactionDisallowedScope::isReactionAllowed()); ASSERT(element.isDefinedCustomElement()); ASSERT(element.document().refCount() > 0); - auto& queue = ensureCurrentQueue(element); - if (queue.m_interface->hasConnectedCallback()) - queue.m_items.append({CustomElementReactionQueueItem::Type::Connected}); + ASSERT(element.reactionQueue()); + auto& queue = *element.reactionQueue(); + if (!queue.m_interface->hasConnectedCallback()) + return; + queue.m_items.append({CustomElementReactionQueueItem::Type::Connected}); + enqueueElementOnAppropriateElementQueue(element); } void CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded(Element& element) @@ -163,9 +169,12 @@ void CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded(Element& el ASSERT(element.isDefinedCustomElement()); if (element.document().refCount() <= 0) return; // Don't enqueue disconnectedCallback if the entire document is getting destructed. - auto& queue = ensureCurrentQueue(element); - if (queue.m_interface->hasDisconnectedCallback()) - queue.m_items.append({CustomElementReactionQueueItem::Type::Disconnected}); + ASSERT(element.reactionQueue()); + auto& queue = *element.reactionQueue(); + if (!queue.m_interface->hasDisconnectedCallback()) + return; + queue.m_items.append({CustomElementReactionQueueItem::Type::Disconnected}); + enqueueElementOnAppropriateElementQueue(element); } void CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded(Element& element, Document& oldDocument, Document& newDocument) @@ -173,9 +182,12 @@ void CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded(Element& element ASSERT(CustomElementReactionDisallowedScope::isReactionAllowed()); ASSERT(element.isDefinedCustomElement()); ASSERT(element.document().refCount() > 0); - auto& queue = ensureCurrentQueue(element); - if (queue.m_interface->hasAdoptedCallback()) - queue.m_items.append({oldDocument, newDocument}); + ASSERT(element.reactionQueue()); + auto& queue = *element.reactionQueue(); + if (!queue.m_interface->hasAdoptedCallback()) + return; + queue.m_items.append({oldDocument, newDocument}); + enqueueElementOnAppropriateElementQueue(element); } void CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue) @@ -183,9 +195,12 @@ void CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded(Element ASSERT(CustomElementReactionDisallowedScope::isReactionAllowed()); ASSERT(element.isDefinedCustomElement()); ASSERT(element.document().refCount() > 0); - auto& queue = ensureCurrentQueue(element); - if (queue.m_interface->observesAttribute(attributeName.localName())) - queue.m_items.append({attributeName, oldValue, newValue}); + ASSERT(element.reactionQueue()); + auto& queue = *element.reactionQueue(); + if (!queue.m_interface->observesAttribute(attributeName.localName())) + return; + queue.m_items.append({attributeName, oldValue, newValue}); + enqueueElementOnAppropriateElementQueue(element); } void CustomElementReactionQueue::enqueuePostUpgradeReactions(Element& element) @@ -195,18 +210,18 @@ void CustomElementReactionQueue::enqueuePostUpgradeReactions(Element& element) if (!element.hasAttributes() && !element.isConnected()) return; - auto* queue = element.reactionQueue(); - ASSERT(queue); + ASSERT(element.reactionQueue()); + auto& queue = *element.reactionQueue(); if (element.hasAttributes()) { for (auto& attribute : element.attributesIterator()) { - if (queue->m_interface->observesAttribute(attribute.localName())) - queue->m_items.append({attribute.name(), nullAtom(), attribute.value()}); + if (queue.m_interface->observesAttribute(attribute.localName())) + queue.m_items.append({attribute.name(), nullAtom(), attribute.value()}); } } - if (element.isConnected() && queue->m_interface->hasConnectedCallback()) - queue->m_items.append({CustomElementReactionQueueItem::Type::Connected}); + if (element.isConnected() && queue.m_interface->hasConnectedCallback()) + queue.m_items.append({CustomElementReactionQueueItem::Type::Connected}); } bool CustomElementReactionQueue::observesStyleAttribute() const @@ -273,20 +288,20 @@ inline void CustomElementReactionQueue::ElementQueue::processQueue(JSC::ExecStat } } -CustomElementReactionQueue& CustomElementReactionQueue::ensureCurrentQueue(Element& element) +// https://html.spec.whatwg.org/multipage/custom-elements.html#enqueue-an-element-on-the-appropriate-element-queue +void CustomElementReactionQueue::enqueueElementOnAppropriateElementQueue(Element& element) { ASSERT(element.reactionQueue()); if (!CustomElementReactionStack::s_currentProcessingStack) { auto& queue = ensureBackupQueue(); queue.add(element); - return *element.reactionQueue(); + return; } auto*& queue = CustomElementReactionStack::s_currentProcessingStack->m_queue; if (!queue) // We use a raw pointer to avoid genearing code to delete it in ~CustomElementReactionStack. queue = new ElementQueue; queue->add(element); - return *element.reactionQueue(); } #if !ASSERT_DISABLED diff --git a/Source/WebCore/dom/CustomElementReactionQueue.h b/Source/WebCore/dom/CustomElementReactionQueue.h index a57ec5f8a354..a7f8002aa8cc 100644 --- a/Source/WebCore/dom/CustomElementReactionQueue.h +++ b/Source/WebCore/dom/CustomElementReactionQueue.h @@ -77,7 +77,7 @@ class CustomElementReactionQueue { }; private: - static CustomElementReactionQueue& ensureCurrentQueue(Element&); + static void enqueueElementOnAppropriateElementQueue(Element&); static ElementQueue& ensureBackupQueue(); static ElementQueue& backupElementQueue();