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;