Title: [266269] trunk
Revision
266269
Author
[email protected]
Date
2020-08-27 19:55:21 -0700 (Thu, 27 Aug 2020)

Log Message

Prevent infinite recursion when upgrading custom elements
https://bugs.webkit.org/show_bug.cgi?id=206605

Reviewed by Antti Koivisto.

LayoutTests/imported/w3c:

Rebaselined the test now that one more test case is passing.

* web-platform-tests/custom-elements/upgrading-expected.txt:

Source/WebCore:

This patch updates our implementation of the concept to upgrade an element [1] and related algorithms
to match the latest HTML5 specification. In particular, it incorporates the algorithmic change [2] to
prevent infinite recursion an element is re-inserted into a document tree inside its constructor.

The key code change is in JSCustomElementInterface::upgradeElement where this patch adds an early exit
when custom element is not "undefined" or "uncustomized" and the custom element state is set to "failed"
immediately before invoking the constructor. The rest of code changes deals with this "failed" state
appearing during upgrades and updates various debug assertions.

[1] https://html.spec.whatwg.org/multipage/custom-elements.html#concept-upgrade-an-element
[2] https://github.com/whatwg/html/pull/5126
[3] https://html.spec.whatwg.org/multipage/custom-elements.html#concept-try-upgrade

Test: imported/w3c/web-platform-tests/custom-elements/upgrading.html

* bindings/js/JSCustomElementInterface.cpp:
(WebCore::JSCustomElementInterface::constructElementWithFallback):
(WebCore::JSCustomElementInterface::upgradeElement): Implements the new behavior. Note that we still
need to clear the queue where we used to set the custom element state to "failed" to avoid memory leaks.
* dom/CustomElementReactionQueue.cpp:
(WebCore::CustomElementReactionQueue::hasJustUpgradeReaction const): Added.
(WebCore::CustomElementReactionQueue::enqueueElementUpgrade): Enqueue the element to the element queue
even if it had already been scheduled to upgrade previously. This makes the innermost attempt to upgrade
to succeed instead of the outermost. Also updated debug assertions.
(WebCore::CustomElementReactionQueue::tryToUpgradeElement): Renamed from enqueueElementUpgradeIfDefined
to match the spec's name [3]. Unlike the concept in the spec, this function doesn't get called when
the custom element state of the elemnt is either "undefined" or "uncustomized" to avoid unnecessary work.
* dom/CustomElementReactionQueue.h:
(WebCore::CustomElementReactionQueue::isEmpty const): Added.
* dom/CustomElementRegistry.cpp:
(WebCore::upgradeElementsInShadowIncludingDescendants):
(WebCore::CustomElementRegistry::upgrade):
* dom/Document.cpp:
(WebCore::createFallbackHTMLElement): Call setIsCustomElementUpgradeCandidate on a newly created since
we can no longer update node flags in enqueueToUpgrade
* dom/Element.cpp:
(WebCore::Element::insertedIntoAncestor):
(WebCore::Element::setIsFailedCustomElement): Removed the unused function argument.
(WebCore::Element::setIsFailedCustomElementWithoutClearingReactionQueue): Extracted from
setIsFailedCustomElement.
(WebCore::Element::clearReactionQueueFromFailedCustomElement): Ditto.
(WebCore::Element::enqueueToUpgrade): No longer updates node flags as this would clear "failed" state
from a custom element which is currently being upgraded and cause all sorts of issues.
(WebCore::Element::reactionQueue const): Updated debug assertions.
* dom/Element.h:

LayoutTests:

Removed the crash expectation from a test now that it's passing.

* TestExpectations:

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (266268 => 266269)


--- trunk/LayoutTests/ChangeLog	2020-08-28 02:32:55 UTC (rev 266268)
+++ trunk/LayoutTests/ChangeLog	2020-08-28 02:55:21 UTC (rev 266269)
@@ -1,3 +1,14 @@
+2020-08-27  Ryosuke Niwa  <[email protected]>
+
+        Prevent infinite recursion when upgrading custom elements
+        https://bugs.webkit.org/show_bug.cgi?id=206605
+
+        Reviewed by Antti Koivisto.
+
+        Removed the crash expectation from a test now that it's passing.
+
+        * TestExpectations:
+
 2020-08-27  Alexey Shvayka  <[email protected]>
 
         __proto__ in object literal should perform [[SetPrototypeOf]] directly

Modified: trunk/LayoutTests/TestExpectations (266268 => 266269)


--- trunk/LayoutTests/TestExpectations	2020-08-28 02:32:55 UTC (rev 266268)
+++ trunk/LayoutTests/TestExpectations	2020-08-28 02:55:21 UTC (rev 266269)
@@ -567,7 +567,6 @@
 imported/w3c/web-platform-tests/html/cross-origin-opener-policy [ Skip ]
 
 # Newly imported WPT tests that are crashing.
-[ Debug ] imported/w3c/web-platform-tests/custom-elements/upgrading.html [ Crash ]
 imported/w3c/web-platform-tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-04.html [ ImageOnlyFailure Crash ]
 
 # Newly imported WPT tests that are flaky.

Modified: trunk/LayoutTests/imported/w3c/ChangeLog (266268 => 266269)


--- trunk/LayoutTests/imported/w3c/ChangeLog	2020-08-28 02:32:55 UTC (rev 266268)
+++ trunk/LayoutTests/imported/w3c/ChangeLog	2020-08-28 02:55:21 UTC (rev 266269)
@@ -1,3 +1,14 @@
+2020-08-27  Ryosuke Niwa  <[email protected]>
+
+        Prevent infinite recursion when upgrading custom elements
+        https://bugs.webkit.org/show_bug.cgi?id=206605
+
+        Reviewed by Antti Koivisto.
+
+        Rebaselined the test now that one more test case is passing.
+
+        * web-platform-tests/custom-elements/upgrading-expected.txt:
+
 2020-08-27  Chris Dumez  <[email protected]>
 
         Fix AudioParam.linearRampToValueAtTime() formula to match specification

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/custom-elements/upgrading-expected.txt (266268 => 266269)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/custom-elements/upgrading-expected.txt	2020-08-28 02:32:55 UTC (rev 266268)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/custom-elements/upgrading-expected.txt	2020-08-28 02:55:21 UTC (rev 266269)
@@ -26,5 +26,5 @@
 PASS Adopting and inserting an unresolved custom element into the document of an iframe must enqueue a custom element upgrade reaction 
 FAIL If definition's disable shadow is true and element's shadow root is non-null, then throw a "NotSupportedError" DOMException. assert_false: Upgrading should fail. expected false got true
 PASS Infinite constructor recursion with upgrade(this) should not be possible 
-FAIL Infinite constructor recursion with appendChild should not be possible assert_array_equals: expected property 0 to be Element node <infinite-cloning-element-2 id="b"></infinite-cloning-ele... but got Element node <infinite-cloning-element-2 id="a"></infinite-cloning-ele... (expected array [Element node <infinite-cloning-element-2 id="b"></infinite-cloning-ele..., "begin"] got [Element node <infinite-cloning-element-2 id="a"></infinite-cloning-ele..., "end"])
+PASS Infinite constructor recursion with appendChild should not be possible 
  

Modified: trunk/Source/WebCore/ChangeLog (266268 => 266269)


--- trunk/Source/WebCore/ChangeLog	2020-08-28 02:32:55 UTC (rev 266268)
+++ trunk/Source/WebCore/ChangeLog	2020-08-28 02:55:21 UTC (rev 266269)
@@ -1,3 +1,56 @@
+2020-08-27  Ryosuke Niwa  <[email protected]>
+
+        Prevent infinite recursion when upgrading custom elements
+        https://bugs.webkit.org/show_bug.cgi?id=206605
+
+        Reviewed by Antti Koivisto.
+
+        This patch updates our implementation of the concept to upgrade an element [1] and related algorithms
+        to match the latest HTML5 specification. In particular, it incorporates the algorithmic change [2] to
+        prevent infinite recursion an element is re-inserted into a document tree inside its constructor.
+
+        The key code change is in JSCustomElementInterface::upgradeElement where this patch adds an early exit
+        when custom element is not "undefined" or "uncustomized" and the custom element state is set to "failed"
+        immediately before invoking the constructor. The rest of code changes deals with this "failed" state
+        appearing during upgrades and updates various debug assertions.
+
+        [1] https://html.spec.whatwg.org/multipage/custom-elements.html#concept-upgrade-an-element
+        [2] https://github.com/whatwg/html/pull/5126
+        [3] https://html.spec.whatwg.org/multipage/custom-elements.html#concept-try-upgrade
+
+        Test: imported/w3c/web-platform-tests/custom-elements/upgrading.html
+
+        * bindings/js/JSCustomElementInterface.cpp:
+        (WebCore::JSCustomElementInterface::constructElementWithFallback):
+        (WebCore::JSCustomElementInterface::upgradeElement): Implements the new behavior. Note that we still
+        need to clear the queue where we used to set the custom element state to "failed" to avoid memory leaks.
+        * dom/CustomElementReactionQueue.cpp:
+        (WebCore::CustomElementReactionQueue::hasJustUpgradeReaction const): Added.
+        (WebCore::CustomElementReactionQueue::enqueueElementUpgrade): Enqueue the element to the element queue
+        even if it had already been scheduled to upgrade previously. This makes the innermost attempt to upgrade
+        to succeed instead of the outermost. Also updated debug assertions.
+        (WebCore::CustomElementReactionQueue::tryToUpgradeElement): Renamed from enqueueElementUpgradeIfDefined
+        to match the spec's name [3]. Unlike the concept in the spec, this function doesn't get called when
+        the custom element state of the elemnt is either "undefined" or "uncustomized" to avoid unnecessary work.
+        * dom/CustomElementReactionQueue.h:
+        (WebCore::CustomElementReactionQueue::isEmpty const): Added.
+        * dom/CustomElementRegistry.cpp:
+        (WebCore::upgradeElementsInShadowIncludingDescendants):
+        (WebCore::CustomElementRegistry::upgrade):
+        * dom/Document.cpp:
+        (WebCore::createFallbackHTMLElement): Call setIsCustomElementUpgradeCandidate on a newly created since
+        we can no longer update node flags in enqueueToUpgrade
+        * dom/Element.cpp:
+        (WebCore::Element::insertedIntoAncestor):
+        (WebCore::Element::setIsFailedCustomElement): Removed the unused function argument.
+        (WebCore::Element::setIsFailedCustomElementWithoutClearingReactionQueue): Extracted from
+        setIsFailedCustomElement.
+        (WebCore::Element::clearReactionQueueFromFailedCustomElement): Ditto.
+        (WebCore::Element::enqueueToUpgrade): No longer updates node flags as this would clear "failed" state
+        from a custom element which is currently being upgraded and cause all sorts of issues.
+        (WebCore::Element::reactionQueue const): Updated debug assertions.
+        * dom/Element.h:
+
 2020-08-27  John Wilander  <[email protected]>
 
         Remove the feature flag for capped cookies set in 3rd-party CNAME cloaked HTTP responses and add ITP debug logging

Modified: trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp (266268 => 266269)


--- trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp	2020-08-28 02:32:55 UTC (rev 266268)
+++ trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp	2020-08-28 02:55:21 UTC (rev 266269)
@@ -64,7 +64,7 @@
 
     auto element = HTMLUnknownElement::create(QualifiedName(nullAtom(), localName, HTMLNames::xhtmlNamespaceURI), document);
     element->setIsCustomElementUpgradeCandidate();
-    element->setIsFailedCustomElement(*this);
+    element->setIsFailedCustomElement();
 
     return element;
 }
@@ -79,7 +79,7 @@
 
     auto element = HTMLUnknownElement::create(name, document);
     element->setIsCustomElementUpgradeCandidate();
-    element->setIsFailedCustomElement(*this);
+    element->setIsFailedCustomElement();
 
     return element;
 }
@@ -162,9 +162,14 @@
     return wrappedElement;
 }
 
+// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-upgrade-an-element
 void JSCustomElementInterface::upgradeElement(Element& element)
 {
     ASSERT(element.tagQName() == name());
+
+    if (element.isDefinedCustomElement() || element.isFailedCustomElement())
+        return; // If element's custom element state is not "undefined" or "uncustomized", then return.
+
     ASSERT(element.isCustomElementUpgradeCandidate());
     if (!canInvokeCallback())
         return;
@@ -193,6 +198,10 @@
 
     CustomElementReactionQueue::enqueuePostUpgradeReactions(element);
 
+    // Unlike spec, set element's custom element state to "failed" after enqueueing post-upgrade reactions
+    // to avoid hitting debug assertions in enqueuePostUpgradeReactions.
+    element.setIsFailedCustomElementWithoutClearingReactionQueue();
+
     m_constructionStack.append(&element);
 
     MarkedArgumentBuffer args;
@@ -204,7 +213,7 @@
     m_constructionStack.removeLast();
 
     if (UNLIKELY(scope.exception())) {
-        element.setIsFailedCustomElement(*this);
+        element.clearReactionQueueFromFailedCustomElement();
         reportException(lexicalGlobalObject, scope.exception());
         return;
     }
@@ -211,7 +220,7 @@
 
     Element* wrappedElement = JSElement::toWrapped(vm, returnedElement);
     if (!wrappedElement || wrappedElement != &element) {
-        element.setIsFailedCustomElement(*this);
+        element.clearReactionQueueFromFailedCustomElement();
         reportException(lexicalGlobalObject, createDOMException(lexicalGlobalObject, TypeError, "Custom element constructor returned a wrong element"));
         return;
     }

Modified: trunk/Source/WebCore/dom/CustomElementReactionQueue.cpp (266268 => 266269)


--- trunk/Source/WebCore/dom/CustomElementReactionQueue.cpp	2020-08-28 02:32:55 UTC (rev 266268)
+++ trunk/Source/WebCore/dom/CustomElementReactionQueue.cpp	2020-08-28 02:55:21 UTC (rev 266269)
@@ -118,21 +118,27 @@
     m_items.clear();
 }
 
+#if ASSERT_ENABLED
+bool CustomElementReactionQueue::hasJustUpgradeReaction() const
+{
+    return m_items.size() == 1 && m_items[0].type() == CustomElementReactionQueueItem::Type::ElementUpgrade;
+}
+#endif
+
 void CustomElementReactionQueue::enqueueElementUpgrade(Element& element, bool alreadyScheduledToUpgrade)
 {
     ASSERT(CustomElementReactionDisallowedScope::isReactionAllowed());
     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 {
+    if (alreadyScheduledToUpgrade)
+        ASSERT(queue.hasJustUpgradeReaction());
+    else
         queue.m_items.append({CustomElementReactionQueueItem::Type::ElementUpgrade});
-        enqueueElementOnAppropriateElementQueue(element);
-    }
+    enqueueElementOnAppropriateElementQueue(element);
 }
 
-void CustomElementReactionQueue::enqueueElementUpgradeIfDefined(Element& element)
+// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-try-upgrade
+void CustomElementReactionQueue::tryToUpgradeElement(Element& element)
 {
     ASSERT(CustomElementReactionDisallowedScope::isReactionAllowed());
     ASSERT(element.isCustomElementUpgradeCandidate());

Modified: trunk/Source/WebCore/dom/CustomElementReactionQueue.h (266268 => 266269)


--- trunk/Source/WebCore/dom/CustomElementReactionQueue.h	2020-08-28 02:32:55 UTC (rev 266268)
+++ trunk/Source/WebCore/dom/CustomElementReactionQueue.h	2020-08-28 02:55:21 UTC (rev 266269)
@@ -70,7 +70,7 @@
     ~CustomElementReactionQueue();
 
     static void enqueueElementUpgrade(Element&, bool alreadyScheduledToUpgrade);
-    static void enqueueElementUpgradeIfDefined(Element&);
+    static void tryToUpgradeElement(Element&);
     static void enqueueConnectedCallbackIfNeeded(Element&);
     static void enqueueDisconnectedCallbackIfNeeded(Element&);
     static void enqueueAdoptedCallbackIfNeeded(Element&, Document& oldDocument, Document& newDocument);
@@ -80,6 +80,10 @@
     bool observesStyleAttribute() const;
     void invokeAll(Element&);
     void clear();
+    bool isEmpty() const { return m_items.isEmpty(); }
+#if ASSERT_ENABLED
+    bool hasJustUpgradeReaction() const;
+#endif
 
     static void processBackupQueue(CustomElementQueue&);
 

Modified: trunk/Source/WebCore/dom/CustomElementRegistry.cpp (266268 => 266269)


--- trunk/Source/WebCore/dom/CustomElementRegistry.cpp	2020-08-28 02:32:55 UTC (rev 266268)
+++ trunk/Source/WebCore/dom/CustomElementRegistry.cpp	2020-08-28 02:55:21 UTC (rev 266269)
@@ -117,7 +117,7 @@
 {
     for (auto& element : descendantsOfType<Element>(root)) {
         if (element.isCustomElementUpgradeCandidate())
-            CustomElementReactionQueue::enqueueElementUpgradeIfDefined(element);
+            CustomElementReactionQueue::tryToUpgradeElement(element);
         if (auto* shadowRoot = element.shadowRoot())
             upgradeElementsInShadowIncludingDescendants(*shadowRoot);
     }
@@ -129,7 +129,7 @@
         return;
 
     if (is<Element>(root) && downcast<Element>(root).isCustomElementUpgradeCandidate())
-        CustomElementReactionQueue::enqueueElementUpgradeIfDefined(downcast<Element>(root));
+        CustomElementReactionQueue::tryToUpgradeElement(downcast<Element>(root));
 
     upgradeElementsInShadowIncludingDescendants(downcast<ContainerNode>(root));
 }

Modified: trunk/Source/WebCore/dom/Document.cpp (266268 => 266269)


--- trunk/Source/WebCore/dom/Document.cpp	2020-08-28 02:32:55 UTC (rev 266268)
+++ trunk/Source/WebCore/dom/Document.cpp	2020-08-28 02:55:21 UTC (rev 266269)
@@ -1151,6 +1151,7 @@
         if (UNLIKELY(registry)) {
             if (auto* elementInterface = registry->findInterface(name)) {
                 auto element = HTMLElement::create(name, document);
+                element->setIsCustomElementUpgradeCandidate();
                 element->enqueueToUpgrade(*elementInterface);
                 return element;
             }

Modified: trunk/Source/WebCore/dom/Element.cpp (266268 => 266269)


--- trunk/Source/WebCore/dom/Element.cpp	2020-08-28 02:32:55 UTC (rev 266268)
+++ trunk/Source/WebCore/dom/Element.cpp	2020-08-28 02:55:21 UTC (rev 266269)
@@ -2173,7 +2173,7 @@
     if (becomeConnected) {
         if (UNLIKELY(isCustomElementUpgradeCandidate())) {
             ASSERT(isConnected());
-            CustomElementReactionQueue::enqueueElementUpgradeIfDefined(*this);
+            CustomElementReactionQueue::tryToUpgradeElement(*this);
         }
         if (UNLIKELY(isDefinedCustomElement()))
             CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(*this);
@@ -2413,18 +2413,28 @@
     InspectorInstrumentation::didChangeCustomElementState(*this);
 }
 
-void Element::setIsFailedCustomElement(JSCustomElementInterface&)
+void Element::setIsFailedCustomElement()
 {
+    setIsFailedCustomElementWithoutClearingReactionQueue();
+    clearReactionQueueFromFailedCustomElement();
+}
+
+void Element::setIsFailedCustomElementWithoutClearingReactionQueue()
+{
     ASSERT(isUndefinedCustomElement());
     ASSERT(getFlag(IsEditingTextOrUndefinedCustomElementFlag));
     clearFlag(IsCustomElement);
+    InspectorInstrumentation::didChangeCustomElementState(*this);
+}
 
+void Element::clearReactionQueueFromFailedCustomElement()
+{
+    ASSERT(isFailedCustomElement());
     if (hasRareData()) {
         // Clear the queue instead of deleting it since this function can be called inside CustomElementReactionQueue::invokeAll during upgrades.
         if (auto* queue = elementRareData()->customElementReactionQueue())
             queue->clear();
     }
-    InspectorInstrumentation::didChangeCustomElementState(*this);
 }
 
 void Element::setIsCustomElementUpgradeCandidate()
@@ -2437,11 +2447,8 @@
 
 void Element::enqueueToUpgrade(JSCustomElementInterface& elementInterface)
 {
+    ASSERT(isCustomElementUpgradeCandidate());
     ASSERT(!isDefinedCustomElement() && !isFailedCustomElement());
-    setFlag(IsCustomElement);
-    setFlag(IsEditingTextOrUndefinedCustomElementFlag);
-    InspectorInstrumentation::didChangeCustomElementState(*this);
-
     auto& data = ""
     bool alreadyScheduledToUpgrade = data.customElementReactionQueue();
     if (!alreadyScheduledToUpgrade)
@@ -2451,7 +2458,14 @@
 
 CustomElementReactionQueue* Element::reactionQueue() const
 {
-    ASSERT(isDefinedCustomElement() || isCustomElementUpgradeCandidate());
+#if ASSERT_ENABLED
+    if (isFailedCustomElement()) {
+        auto* queue = elementRareData()->customElementReactionQueue();
+        ASSERT(queue);
+        ASSERT(queue->isEmpty() || queue->hasJustUpgradeReaction());
+    } else
+        ASSERT(isDefinedCustomElement() || isCustomElementUpgradeCandidate());
+#endif
     if (!hasRareData())
         return nullptr;
     return elementRareData()->customElementReactionQueue();

Modified: trunk/Source/WebCore/dom/Element.h (266268 => 266269)


--- trunk/Source/WebCore/dom/Element.h	2020-08-28 02:32:55 UTC (rev 266268)
+++ trunk/Source/WebCore/dom/Element.h	2020-08-28 02:55:21 UTC (rev 266269)
@@ -303,7 +303,9 @@
     WEBCORE_EXPORT ShadowRoot& ensureUserAgentShadowRoot();
 
     void setIsDefinedCustomElement(JSCustomElementInterface&);
-    void setIsFailedCustomElement(JSCustomElementInterface&);
+    void setIsFailedCustomElement();
+    void setIsFailedCustomElementWithoutClearingReactionQueue();
+    void clearReactionQueueFromFailedCustomElement();
     void setIsCustomElementUpgradeCandidate();
     void enqueueToUpgrade(JSCustomElementInterface&);
     CustomElementReactionQueue* reactionQueue() const;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to