Only update connected custom elements
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 2 Sep 2016 06:17:17 +0000 (06:17 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 2 Sep 2016 06:17:17 +0000 (06:17 +0000)
https://bugs.webkit.org/show_bug.cgi?id=161480

Reviewed by Yusuke Suzuki.

Source/WebCore:

In the latest specs, creating an element only upgrades an element if the custom element had already been defined:
https://dom.spec.whatwg.org/#concept-create-element

Otherwise, an element remains unresolved until it gets connected to the document associated with the global object:
https://dom.spec.whatwg.org/#concept-node-insert

This patch removes the upgrade candidate map in CustomElementRegistry, and traverses the entire document associated
with global object (DOMWindow) in addElementDefinition: https://html.spec.whatwg.org/#dom-customelementregistry-define

The traversal is done in the shadow-including tree order (different from depth-first preorder traversal of flat tree)
since it doesn't enter slots and children of shadow hosts are always visited even if they are not assigned to a slot:
https://dom.spec.whatwg.org/#concept-shadow-including-tree-order

Test: fast/custom-elements/enqueue-custom-element-upgrade-reaction.html

* bindings/js/JSCustomElementInterface.cpp:
(WebCore::JSCustomElementInterface::upgradeElement): Assert that the element being upgraded as the same qualified name
as the custom element interface.
* bindings/js/JSCustomElementRegistryCustom.cpp:
(WebCore::JSCustomElementRegistry::define): Moved the code to resolve the promise from here to addElementDefinition.
Also cleaned up the code to extract callbacks a little.
* dom/CustomElementReactionQueue.cpp:
(WebCore::CustomElementReactionQueue::enqueueElementUpgrade): Added an assertion.
(WebCore::CustomElementReactionQueue::enqueueElementUpgradeIfDefined): Added. Upgrade an element if the custom element
had already been defined.
* dom/CustomElementReactionQueue.h:
* dom/CustomElementRegistry.cpp:
(WebCore::CustomElementRegistry::create): Stores the reference to DOMWindow to find its document in addElementDefinition.
(WebCore::CustomElementRegistry::CustomElementRegistry): Ditto.
(WebCore::enqueueUpgradeInShadowIncludingTreeOrder): Added. Enqueue upgrade reactions in shadow-including tree order.
(WebCore::CustomElementRegistry::addElementDefinition): Upgrade all unresolved elements that matches this definition and
resolve the the promise returned by "whenDefined" if there is any.
(WebCore::CustomElementRegistry::addUpgradeCandidate): Deleted.
(WebCore::CustomElementRegistry::findInterface): Added a new variant that takes an element.
* dom/CustomElementRegistry.h:
* dom/Document.cpp:
(WebCore::createUpgradeCandidateElement): No longer takes DOMWindow since we don't upgrade synchronously here. It's also
wrong not to mark the element as unresolved custom element in a document without a browsing context per new semantics.
(WebCore::createHTMLElementWithNameValidation): Ditto.
(WebCore::createFallbackHTMLElement): Ditto.
* dom/Element.cpp:
(WebCore::Element::insertedInto): Enqueue an upgrade reaction if this is an unsolved custom element and there is now
a definition for it (the latter condition is checked in enqueueElementUpgradeIfDefined).
* html/parser/HTMLConstructionSite.cpp:
(WebCore::HTMLConstructionSite::createHTMLElementOrFindCustomElementInterface): Don't upgrade this element until it gets
connected to a document in Element::insertedInto.
* page/DOMWindow.cpp:
(WebCore::DOMWindow::ensureCustomElementRegistry):

LayoutTests:

Added a W3c-style testharness.js test for https://html.spec.whatwg.org/#enqueue-a-custom-element-upgrade-reaction
and added more test cases for :defined and customElements.define.

* fast/custom-elements/CustomElementRegistry.html: Revised descriptions for "get" and "whenDefined" test cases consistent
with ones for "define".
* fast/custom-elements/defined-pseudo-class-expected.txt:
* fast/custom-elements/defined-pseudo-class.html:
* fast/custom-elements/enqueue-custom-element-upgrade-reaction-expected.txt: Added.
* fast/custom-elements/enqueue-custom-element-upgrade-reaction.html: Added.
* fast/custom-elements/resources/document-types.js:
(create):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@205340 268f45cc-cd09-0410-ab3c-d52691b4dbfc

19 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/custom-elements/CustomElementRegistry-expected.txt
LayoutTests/fast/custom-elements/CustomElementRegistry.html
LayoutTests/fast/custom-elements/defined-pseudo-class-expected.txt
LayoutTests/fast/custom-elements/defined-pseudo-class.html
LayoutTests/fast/custom-elements/enqueue-custom-element-upgrade-reaction-expected.txt [new file with mode: 0644]
LayoutTests/fast/custom-elements/enqueue-custom-element-upgrade-reaction.html [new file with mode: 0644]
LayoutTests/fast/custom-elements/resources/document-types.js
Source/WebCore/ChangeLog
Source/WebCore/bindings/js/JSCustomElementInterface.cpp
Source/WebCore/bindings/js/JSCustomElementRegistryCustom.cpp
Source/WebCore/dom/CustomElementReactionQueue.cpp
Source/WebCore/dom/CustomElementReactionQueue.h
Source/WebCore/dom/CustomElementRegistry.cpp
Source/WebCore/dom/CustomElementRegistry.h
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Element.cpp
Source/WebCore/html/parser/HTMLConstructionSite.cpp
Source/WebCore/page/DOMWindow.cpp

index b08c0f6..3b8dc1c 100644 (file)
@@ -1,3 +1,22 @@
+2016-09-01  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Only update connected custom elements
+        https://bugs.webkit.org/show_bug.cgi?id=161480
+
+        Reviewed by Yusuke Suzuki.
+
+        Added a W3c-style testharness.js test for https://html.spec.whatwg.org/#enqueue-a-custom-element-upgrade-reaction
+        and added more test cases for :defined and customElements.define.
+
+        * fast/custom-elements/CustomElementRegistry.html: Revised descriptions for "get" and "whenDefined" test cases consistent
+        with ones for "define".
+        * fast/custom-elements/defined-pseudo-class-expected.txt:
+        * fast/custom-elements/defined-pseudo-class.html:
+        * fast/custom-elements/enqueue-custom-element-upgrade-reaction-expected.txt: Added.
+        * fast/custom-elements/enqueue-custom-element-upgrade-reaction.html: Added.
+        * fast/custom-elements/resources/document-types.js:
+        (create):
+
 2016-09-01  Gyuyoung Kim  <gyuyoung.kim@webkit.org>
 
         [EFL] Mark failing tests to failure
index b272a38..35ec5ee 100644 (file)
@@ -8,6 +8,7 @@ PASS customElements.define must throw a NotSupportedError when there is already
 PASS customElements.define must throw a NotSupportedError when element definition is running flag is set 
 PASS customElements.define must check IsConstructor on the constructor before checking the element definition is running flag 
 PASS customElements.define must validate the custom element name before checking the element definition is running flag 
+PASS customElements.define unset the element definition is running flag before upgrading custom elements 
 PASS customElements.define must not throw when defining another custom element in a different global object during Get(constructor, "prototype") 
 PASS Custom Elements: CustomElementRegistry interface 
 PASS customElements.define must get "prototype" property of the constructor 
@@ -23,15 +24,16 @@ PASS customElements.define must rethrow an exception thrown while iterating over
 PASS customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on observedAttributes 
 PASS customElements.define must not throw even if "observedAttributes" fails to convert if "attributeChangedCallback" is not defined 
 PASS customElements.define must define an instantiatable custom element 
+PASS customElements.define must upgrade elements in the shadow-including tree order 
 PASS CustomElementRegistry interface must have get as a method 
-PASS "get" must return undefined when the registry does not contain an entry with the given name 
-PASS "get" must return undefined when the registry does not contain an entry with the given name even if the name was not a valid custom element name 
-PASS "get" return the constructor of the entry with the given name when there is a matching entry. 
-PASS "whenDefined" must return a promise for a valid custom element name 
-PASS "whenDefined" must return the same promise each time invoked for a valid custom element name which has not been defined 
-PASS "whenDefined" must return an unresolved promise when the registry does not contain the entry with the given name 
-PASS "whenDefined" must return a rejected promise when the given name is not a valid custom element name 
-PASS "whenDefined" must return a resolved promise when the registry contains the entry with the given name 
-PASS "whenDefined" must return a new resolved promise each time invoked when the registry contains the entry with the given name 
-PASS A promise returned by "whenDefined" must be resolved by "define" 
+PASS customElements.get must return undefined when the registry does not contain an entry with the given name 
+PASS customElements.get must return undefined when the registry does not contain an entry with the given name even if the name was not a valid custom element name 
+PASS customElements.get return the constructor of the entry with the given name when there is a matching entry. 
+PASS customElements.whenDefined must return a promise for a valid custom element name 
+PASS customElements.whenDefined must return the same promise each time invoked for a valid custom element name which has not been defined 
+PASS customElements.whenDefined must return an unresolved promise when the registry does not contain the entry with the given name 
+PASS customElements.whenDefined must return a rejected promise when the given name is not a valid custom element name 
+PASS customElements.whenDefined must return a resolved promise when the registry contains the entry with the given name 
+PASS customElements.whenDefined must return a new resolved promise each time invoked when the registry contains the entry with the given name 
+PASS A promise returned by customElements.whenDefined must be resolved by "define" 
 
index 40407b3..d0ffb6e 100644 (file)
@@ -149,6 +149,31 @@ test(function () {
     assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"');
 }, 'customElements.define must validate the custom element name before checking the element definition is running flag');
 
+test(function () {
+    var unresolvedElement = document.createElement('constructor-calls-define');
+    document.body.appendChild(unresolvedElement);
+    var elementUpgradedDuringUpgrade = document.createElement('defined-during-upgrade');
+    document.body.appendChild(elementUpgradedDuringUpgrade);
+
+    var DefinedDuringUpgrade = class extends HTMLElement { };
+
+    class ConstructorCallsDefine extends HTMLElement {
+        constructor() {
+            customElements.define('defined-during-upgrade', DefinedDuringUpgrade);
+            assert_false(unresolvedElement instanceof ConstructorCallsDefine);
+            assert_true(elementUpgradedDuringUpgrade instanceof DefinedDuringUpgrade);
+            super();
+            assert_true(unresolvedElement instanceof ConstructorCallsDefine);
+            assert_true(elementUpgradedDuringUpgrade instanceof DefinedDuringUpgrade);
+        }
+    }
+
+    assert_false(unresolvedElement instanceof ConstructorCallsDefine);
+    assert_false(elementUpgradedDuringUpgrade instanceof DefinedDuringUpgrade);
+
+    customElements.define('constructor-calls-define', ConstructorCallsDefine);
+}, 'customElements.define unset the element definition is running flag before upgrading custom elements');
+
 (function () {
     var testCase = async_test('customElements.define must not throw'
         +' when defining another custom element in a different global object during Get(constructor, "prototype")', {timeout: 100});
@@ -384,13 +409,48 @@ test(function () {
 }, 'customElements.define must define an instantiatable custom element');
 
 test(function () {
+    var disconnectedElement = document.createElement('some-custom');
+    var connectedElementBeforeShadowHost = document.createElement('some-custom');
+    var connectedElementAfterShadowHost = document.createElement('some-custom');
+    var elementInShadowTree = document.createElement('some-custom');
+    var childElementOfShadowHost = document.createElement('some-custom');
+    var customShadowHost = document.createElement('some-custom');
+    var elementInNestedShadowTree = document.createElement('some-custom');
+
+    var container = document.createElement('div');
+    var shadowHost = document.createElement('div');
+    var shadowRoot = shadowHost.attachShadow({mode: 'closed'});
+    container.appendChild(connectedElementBeforeShadowHost);
+    container.appendChild(shadowHost);
+    container.appendChild(connectedElementAfterShadowHost);
+    shadowHost.appendChild(childElementOfShadowHost);
+    shadowRoot.appendChild(elementInShadowTree);
+    shadowRoot.appendChild(customShadowHost);
+
+    var innerShadowRoot = customShadowHost.attachShadow({mode: 'closed'});
+    innerShadowRoot.appendChild(elementInNestedShadowTree);
+
+    var calls = [];
+    class SomeCustomElement extends HTMLElement {
+        constructor() {
+            super();
+            calls.push(this);
+        }
+    };
+
+    document.body.appendChild(container);
+    customElements.define('some-custom', SomeCustomElement);
+    assert_array_equals(calls, [connectedElementBeforeShadowHost, elementInShadowTree, customShadowHost, elementInNestedShadowTree, childElementOfShadowHost, connectedElementAfterShadowHost]);
+}, 'customElements.define must upgrade elements in the shadow-including tree order');
+
+test(function () {
     assert_true('get' in CustomElementRegistry.prototype, '"get" exists on CustomElementRegistry.prototype');
     assert_true('get' in customElements, '"get" exists on window.customElements');
 }, 'CustomElementRegistry interface must have get as a method');
 
 test(function () {
     assert_equals(customElements.get('a-b'), undefined);
-}, '"get" must return undefined when the registry does not contain an entry with the given name');
+}, 'customElements.get must return undefined when the registry does not contain an entry with the given name');
 
 test(function () {
     assert_equals(customElements.get('html'), undefined);
@@ -398,22 +458,22 @@ test(function () {
     assert_equals(customElements.get('div'), undefined);
     assert_equals(customElements.get('g'), undefined);
     assert_equals(customElements.get('ab'), undefined);
-}, '"get" must return undefined when the registry does not contain an entry with the given name even if the name was not a valid custom element name');
+}, 'customElements.get must return undefined when the registry does not contain an entry with the given name even if the name was not a valid custom element name');
 
 test(function () {
     assert_equals(customElements.get('existing-custom-element'), undefined);
     class ExistingCustomElement extends HTMLElement {};
     customElements.define('existing-custom-element', ExistingCustomElement);
     assert_equals(customElements.get('existing-custom-element'), ExistingCustomElement);
-}, '"get" return the constructor of the entry with the given name when there is a matching entry.');
+}, 'customElements.get return the constructor of the entry with the given name when there is a matching entry.');
 
 test(function () {
     assert_true(customElements.whenDefined('some-name') instanceof Promise);
-}, '"whenDefined" must return a promise for a valid custom element name');
+}, 'customElements.whenDefined must return a promise for a valid custom element name');
 
 test(function () {
     assert_equals(customElements.whenDefined('some-name'), customElements.whenDefined('some-name'));
-}, '"whenDefined" must return the same promise each time invoked for a valid custom element name which has not been defined');
+}, 'customElements.whenDefined must return the same promise each time invoked for a valid custom element name which has not been defined');
 
 promise_test(function () {
     var resolved = false;
@@ -423,7 +483,7 @@ promise_test(function () {
         assert_false(resolved, 'The promise returned by "whenDefined" must not be resolved until a custom element is defined');
         assert_false(rejected, 'The promise returned by "whenDefined" must not be rejected until a custom element is defined');
     });    
-}, '"whenDefined" must return an unresolved promise when the registry does not contain the entry with the given name')
+}, 'customElements.whenDefined must return an unresolved promise when the registry does not contain the entry with the given name')
 
 promise_test(function () {
     var promise = customElements.whenDefined('badname');
@@ -436,7 +496,7 @@ promise_test(function () {
         assert_false('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
         assert_true('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
     });
-}, '"whenDefined" must return a rejected promise when the given name is not a valid custom element name');
+}, 'customElements.whenDefined must return a rejected promise when the given name is not a valid custom element name');
 
 promise_test(function () {
     customElements.define('preexisting-custom-element', class extends HTMLElement { });
@@ -453,7 +513,7 @@ promise_test(function () {
             'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
         assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
     });
-}, '"whenDefined" must return a resolved promise when the registry contains the entry with the given name');
+}, 'customElements.whenDefined must return a resolved promise when the registry contains the entry with the given name');
 
 promise_test(function () {
     class AnotherExistingCustomElement extends HTMLElement {};
@@ -479,7 +539,7 @@ promise_test(function () {
         assert_equals(promise2.resolved, undefined, 'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
         assert_false('rejected' in promise2, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
     });
-}, '"whenDefined" must return a new resolved promise each time invoked when the registry contains the entry with the given name');
+}, 'customElements.whenDefined must return a new resolved promise each time invoked when the registry contains the entry with the given name');
 
 promise_test(function () {
     var promise = customElements.whenDefined('element-defined-after-whendefined');
@@ -514,7 +574,7 @@ promise_test(function () {
             'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
         assert_false('rejected' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
     });
-}, 'A promise returned by "whenDefined" must be resolved by "define"');
+}, 'A promise returned by customElements.whenDefined must be resolved by "define"');
 
 </script>
 </body>
index f91c495..34513a9 100644 (file)
@@ -1,7 +1,9 @@
 
 PASS The defined flag of a custom element must not be set if a custom element has not been upgraded yet 
+PASS The defined flag of a custom element must not be set if a custom element has not been upgraded yet even if the element has been defined 
 PASS The defined flag of a custom element must be set when a custom element is successfully upgraded 
 PASS The defined flag of a custom element must be set if there is a matching definition 
+PASS The defined flag of an upgraded custom element must be set 
 PASS The defined flag of a custom element created by HTML parser must be unset if there is no matching definition 
 PASS The defined flag of a custom element created by HTML parser must be set if there is a matching definition 
 PASS The defined flag of a custom element created by HTML parser must be set after checking the returned result is an instance of HTMLElement 
index bb59b9d..acbd45e 100644 (file)
@@ -28,6 +28,11 @@ class MyElement extends HTMLElement {
 customElements.define('my-element', MyElement);
 
 test(function () {
+    assert_false(upgradeCandidate.matches(':defined'));
+}, 'The defined flag of a custom element must not be set if a custom element has not been upgraded yet even if the element has been defined');
+
+test(function () {
+    document.body.appendChild(upgradeCandidate);
     assert_true(upgradeCandidate.matches(':defined'));
     assert_false(matchInsideConstructor, 'Upgrading a custom element must set defined flag after invoking the constructor');
 }, 'The defined flag of a custom element must be set when a custom element is successfully upgraded');
@@ -38,6 +43,12 @@ test(function () {
     assert_false(matchInsideConstructor, 'Creating a custom element must set defined flag after invoking the constructor');
 }, 'The defined flag of a custom element must be set if there is a matching definition');
 
+test(function () {
+    var upgradedElement = document.createElement('my-element').cloneNode(true);
+    assert_true(upgradedElement.matches(':defined'));
+    assert_false(matchInsideConstructor, 'Creating a custom element must set defined flag after invoking the constructor');
+}, 'The defined flag of an upgraded custom element must be set');
+
 document.write('<my-other-element></my-other-element>');
 
 test(function () {
diff --git a/LayoutTests/fast/custom-elements/enqueue-custom-element-upgrade-reaction-expected.txt b/LayoutTests/fast/custom-elements/enqueue-custom-element-upgrade-reaction-expected.txt
new file mode 100644 (file)
index 0000000..be44774
--- /dev/null
@@ -0,0 +1,27 @@
+
+PASS Creating an element in document of a template element must not enqueue a custom element upgrade reaction because the document does not have a browsing context 
+PASS Creating an element in document of a template element and inserting into the document must not enqueue a custom element upgrade reaction 
+PASS Creating an element in document of a template element and adopting back to a document with browsing context must enqueue a custom element upgrade reaction 
+PASS Creating an element in new document must not enqueue a custom element upgrade reaction because the document does not have a browsing context 
+PASS Creating an element in new document and inserting into the document must not enqueue a custom element upgrade reaction 
+PASS Creating an element in new document and adopting back to a document with browsing context must enqueue a custom element upgrade reaction 
+PASS Creating an element in cloned document must not enqueue a custom element upgrade reaction because the document does not have a browsing context 
+PASS Creating an element in cloned document and inserting into the document must not enqueue a custom element upgrade reaction 
+PASS Creating an element in cloned document and adopting back to a document with browsing context must enqueue a custom element upgrade reaction 
+PASS Creating an element in document created by createHTMLDocument must not enqueue a custom element upgrade reaction because the document does not have a browsing context 
+PASS Creating an element in document created by createHTMLDocument and inserting into the document must not enqueue a custom element upgrade reaction 
+PASS Creating an element in document created by createHTMLDocument and adopting back to a document with browsing context must enqueue a custom element upgrade reaction 
+PASS Creating an element in HTML document created by createDocument must not enqueue a custom element upgrade reaction because the document does not have a browsing context 
+PASS Creating an element in HTML document created by createDocument and inserting into the document must not enqueue a custom element upgrade reaction 
+PASS Creating an element in HTML document created by createDocument and adopting back to a document with browsing context must enqueue a custom element upgrade reaction 
+PASS Creating an element in HTML document fetched by XHR must not enqueue a custom element upgrade reaction because the document does not have a browsing context 
+PASS Creating an element in HTML document fetched by XHR and inserting into the document must not enqueue a custom element upgrade reaction 
+PASS Creating an element in HTML document fetched by XHR and adopting back to a document with browsing context must enqueue a custom element upgrade reaction 
+PASS Creating an element in document in an iframe must not enqueue a custom element upgrade reaction if there is no matching definition 
+PASS Creating an element in document in an iframe must enqueue a custom element upgrade reaction if there is a matching definition 
+PASS "define" in document in an iframe must not enqueue a custom element upgrade reaction on a disconnected unresolved custom element 
+PASS Inserting an unresolved custom element into document in an iframe must enqueue a custom element upgrade reaction 
+PASS "define" in document in an iframe must enqueue a custom element upgrade reaction on a connected unresolved custom element 
+PASS Adopting (and leaving disconnceted) an unresolved custom element into document in an iframe must not enqueue a custom element upgrade reaction 
+PASS Adopting and inserting an unresolved custom element into document in an iframe must enqueue a custom element upgrade reaction 
+
diff --git a/LayoutTests/fast/custom-elements/enqueue-custom-element-upgrade-reaction.html b/LayoutTests/fast/custom-elements/enqueue-custom-element-upgrade-reaction.html
new file mode 100644 (file)
index 0000000..7511dc1
--- /dev/null
@@ -0,0 +1,189 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: Enqueue a custom element upgrade reaction</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="Enqueue a custom element upgrade reaction must upgrade a custom element">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/scripting.html#concept-try-upgrade">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-upgrade-reaction">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<link rel='stylesheet' href='../../resources/testharness.css'>
+<script src="resources/document-types.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+class PredefinedCustomElement extends HTMLElement {}
+customElements.define('predefined-custom-element', PredefinedCustomElement);
+
+var customElementNumber = 1;
+function generateNextCustomElementName() { return 'custom-' + customElementNumber++; }
+
+DocumentTypes.filter(function (entry) { return !entry.isOwner && !entry.hasBrowsingContext; }).forEach(function (entry) {
+    var documentName = entry.name;
+    var getDocument = entry.create;
+
+    promise_test(function () {
+        return getDocument().then(function (doc) {
+            assert_false(doc.createElement('predefined-custom-element') instanceof PredefinedCustomElement);
+        });
+    }, 'Creating an element in ' + documentName + ' must not enqueue a custom element upgrade reaction'
+        + ' because the document does not have a browsing context');
+
+    promise_test(function () {
+        var name = generateNextCustomElementName();
+        var unresolvedElement = document.createElement(name);
+
+        assert_equals(unresolvedElement.__proto__, HTMLElement.prototype,
+            '[[Prototype]] internal slot of the unresolved custom element must be the HTMLElement prototype');
+
+        return getDocument().then(function (doc) {
+            var unresolvedElementInDoc = doc.createElement(name);
+            var prototype = (unresolvedElementInDoc.namespaceURI == 'http://www.w3.org/1999/xhtml' ? HTMLElement : Element).prototype;
+
+            assert_equals(unresolvedElementInDoc.__proto__, prototype,
+                '[[Prototype]] internal slot of the unresolved custom element must be the ' + prototype.toString() + ' prototype');
+            var someCustomElement = class extends HTMLElement {};
+            customElements.define(name, someCustomElement);
+            assert_equals(unresolvedElementInDoc.__proto__, prototype, '"define" must not upgrade a disconnected unresolved custom elements');
+            doc.documentElement.appendChild(unresolvedElementInDoc);
+            assert_equals(unresolvedElementInDoc.__proto__, prototype,
+                'Inserting an element into a document without a browsing context must not enqueue a custom element upgrade reaction');
+        });
+    }, 'Creating an element in ' + documentName + ' and inserting into the document must not enqueue a custom element upgrade reaction');
+
+    promise_test(function () {
+        var name = generateNextCustomElementName();
+        var unresolvedElement = document.createElement(name);
+
+        assert_equals(unresolvedElement.__proto__, HTMLElement.prototype,
+            '[[Prototype]] internal slot of the unresolved custom element must be the HTMLElement prototype');
+
+        return getDocument().then(function (doc) {
+            var unresolvedElementInDoc = doc.createElement(name);
+            var prototype = (unresolvedElementInDoc.namespaceURI == 'http://www.w3.org/1999/xhtml' ? HTMLElement : Element).prototype;
+
+            assert_equals(unresolvedElementInDoc.__proto__, prototype,
+                '[[Prototype]] internal slot of the unresolved custom element must be the ' + prototype.toString() + ' prototype');
+            var someCustomElement = class extends HTMLElement {};
+            customElements.define(name, someCustomElement);
+            assert_equals(unresolvedElementInDoc.__proto__, prototype, '"define" must not upgrade a disconnected unresolved custom elements');
+            document.body.appendChild(unresolvedElementInDoc);
+
+            if (unresolvedElementInDoc.namespaceURI == 'http://www.w3.org/1999/xhtml') {
+                assert_equals(unresolvedElementInDoc.__proto__, someCustomElement.prototype,
+                    'Inserting an element into a document with a browsing context must enqueue a custom element upgrade reaction');
+            } else {
+                assert_equals(unresolvedElementInDoc.__proto__, prototype,
+                    'Looking up a custom element definition must return null if the element is not in the HTML namespace');
+            }
+        });
+    }, 'Creating an element in ' + documentName + ' and adopting back to a document with browsing context must enqueue a custom element upgrade reaction');
+
+});
+
+DocumentTypes.filter(function (entry) { return !entry.isOwner && entry.hasBrowsingContext; }).forEach(function (entry) {
+    var documentName = entry.name;
+    var getDocument = entry.create;
+
+    promise_test(function () {
+        return getDocument().then(function (doc) {
+            assert_false(doc.createElement('predefined-custom-element') instanceof PredefinedCustomElement);
+        });
+    }, 'Creating an element in ' + documentName + ' must not enqueue a custom element upgrade reaction if there is no matching definition');
+
+    promise_test(function () {
+        return getDocument().then(function (doc) {
+            var docWindow = doc.defaultView;
+            class DistinctPredefinedCustomElement extends docWindow.HTMLElement { };
+            docWindow.customElements.define('predefined-custom-element', DistinctPredefinedCustomElement);
+            assert_true(doc.createElement('predefined-custom-element') instanceof DistinctPredefinedCustomElement);
+        });
+    }, 'Creating an element in ' + documentName + ' must enqueue a custom element upgrade reaction if there is a matching definition');
+
+    promise_test(function () {
+        var unresolvedElement = document.createElement('unresolved-element');
+        return getDocument().then(function (doc) {
+            var docWindow = doc.defaultView;
+            class UnresolvedElement extends docWindow.HTMLElement { };
+            var unresolvedElementInDoc = doc.createElement('unresolved-element');
+
+            assert_equals(unresolvedElement.__proto__, HTMLElement.prototype);
+            assert_equals(unresolvedElementInDoc.__proto__, docWindow.HTMLElement.prototype);
+
+            docWindow.customElements.define('unresolved-element', UnresolvedElement);
+
+            assert_equals(unresolvedElement.__proto__, HTMLElement.prototype);
+            assert_equals(unresolvedElementInDoc.__proto__, docWindow.HTMLElement.prototype);
+
+        });
+    }, '"define" in ' + documentName + ' must not enqueue a custom element upgrade reaction on a disconnected unresolved custom element');
+
+    promise_test(function () {
+        var unresolvedElement = document.createElement('unresolved-element');
+        return getDocument().then(function (doc) {
+            var docWindow = doc.defaultView;
+            class UnresolvedElement extends docWindow.HTMLElement { };
+            var unresolvedElementInDoc = doc.createElement('unresolved-element');
+
+            assert_equals(unresolvedElement.__proto__, HTMLElement.prototype);
+            assert_equals(unresolvedElementInDoc.__proto__, docWindow.HTMLElement.prototype);
+
+            docWindow.customElements.define('unresolved-element', UnresolvedElement);
+            doc.documentElement.appendChild(unresolvedElementInDoc);
+
+            assert_equals(unresolvedElement.__proto__, HTMLElement.prototype);
+            assert_equals(unresolvedElementInDoc.__proto__, UnresolvedElement.prototype);
+        });
+    }, 'Inserting an unresolved custom element into ' + documentName + ' must enqueue a custom element upgrade reaction');
+
+    promise_test(function () {
+        var unresolvedElement = document.createElement('unresolved-element');
+        return getDocument().then(function (doc) {
+            var docWindow = doc.defaultView;
+            class UnresolvedElement extends docWindow.HTMLElement { };
+            var unresolvedElementInDoc = doc.createElement('unresolved-element');
+            doc.documentElement.appendChild(unresolvedElementInDoc);
+
+            assert_equals(unresolvedElement.__proto__, HTMLElement.prototype);
+            assert_equals(unresolvedElementInDoc.__proto__, docWindow.HTMLElement.prototype);
+
+            docWindow.customElements.define('unresolved-element', UnresolvedElement);
+
+            assert_equals(unresolvedElement.__proto__, HTMLElement.prototype);
+            assert_equals(unresolvedElementInDoc.__proto__, UnresolvedElement.prototype);
+        });
+    }, '"define" in ' + documentName + ' must enqueue a custom element upgrade reaction on a connected unresolved custom element');
+
+    promise_test(function () {
+        var unresolvedElement = document.createElement('unresolved-element');
+        return getDocument().then(function (doc) {
+            var docWindow = doc.defaultView;
+            class UnresolvedElement extends docWindow.HTMLElement { };
+            assert_false(unresolvedElement instanceof UnresolvedElement);
+            docWindow.customElements.define('unresolved-element', UnresolvedElement);
+            doc.adoptNode(unresolvedElement);
+            assert_false(unresolvedElement instanceof UnresolvedElement);
+        });
+    }, 'Adopting (and leaving disconnceted) an unresolved custom element into ' + documentName + ' must not enqueue a custom element upgrade reaction');
+
+    promise_test(function () {
+        var unresolvedElement = document.createElement('unresolved-element');
+        return getDocument().then(function (doc) {
+            var docWindow = doc.defaultView;
+            class UnresolvedElement extends docWindow.HTMLElement { };
+            assert_false(unresolvedElement instanceof UnresolvedElement);
+            docWindow.customElements.define('unresolved-element', UnresolvedElement);
+            doc.documentElement.appendChild(unresolvedElement);
+            assert_true(unresolvedElement instanceof UnresolvedElement);
+        });
+    }, 'Adopting and inserting an unresolved custom element into ' + documentName + ' must enqueue a custom element upgrade reaction');
+
+});
+
+</script>
+</body>
+</html>
index 5749bad..55284d5 100644 (file)
@@ -3,6 +3,7 @@ const DocumentTypes = [
         name: 'document',
         create: function () { return Promise.resolve(document); },
         isOwner: true,
+        hasBrowsingContext: true,
     },
     {
         name: 'document of a template element',
@@ -14,7 +15,8 @@ const DocumentTypes = [
                     doc.appendChild(doc.createElement('html'));
                 resolve(doc);
             });
-        }
+        },
+        hasBrowsingContext: false,
     },
     {
         name: 'new document',
@@ -24,7 +26,8 @@ const DocumentTypes = [
                 doc.appendChild(doc.createElement('html'));
                 resolve(doc);
             });
-        }
+        },
+        hasBrowsingContext: false,
     },
     {
         name: 'cloned document',
@@ -34,19 +37,22 @@ const DocumentTypes = [
                 doc.appendChild(doc.createElement('html'));
                 resolve(doc);
             });
-        }
+        },
+        hasBrowsingContext: false,
     },
     {
         name: 'document created by createHTMLDocument',
         create: function () {
             return Promise.resolve(document.implementation.createHTMLDocument());
-        }
+        },
+        hasBrowsingContext: false,
     },
     {
         name: 'HTML document created by createDocument',
         create: function () {
             return Promise.resolve(document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null));
-        }
+        },
+        hasBrowsingContext: false,
     },
     {
         name: 'document in an iframe',
@@ -57,7 +63,8 @@ const DocumentTypes = [
                 iframe.onerror = function () { reject('Failed to load an empty iframe'); }
                 document.body.appendChild(iframe);
             });
-        }
+        },
+        hasBrowsingContext: true,
     },
     {
         name: 'HTML document fetched by XHR',
@@ -70,6 +77,7 @@ const DocumentTypes = [
                 xhr.onerror = function () { reject('Failed to fetch the document'); }
                 xhr.send();
             });
-        }
+        },
+        hasBrowsingContext: false,
     }
 ];
\ No newline at end of file
index ad16e86..4b96a2f 100644 (file)
@@ -1,3 +1,59 @@
+2016-09-01  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Only update connected custom elements
+        https://bugs.webkit.org/show_bug.cgi?id=161480
+
+        Reviewed by Yusuke Suzuki.
+
+        In the latest specs, creating an element only upgrades an element if the custom element had already been defined:
+        https://dom.spec.whatwg.org/#concept-create-element
+
+        Otherwise, an element remains unresolved until it gets connected to the document associated with the global object:
+        https://dom.spec.whatwg.org/#concept-node-insert
+
+        This patch removes the upgrade candidate map in CustomElementRegistry, and traverses the entire document associated
+        with global object (DOMWindow) in addElementDefinition: https://html.spec.whatwg.org/#dom-customelementregistry-define
+
+        The traversal is done in the shadow-including tree order (different from depth-first preorder traversal of flat tree)
+        since it doesn't enter slots and children of shadow hosts are always visited even if they are not assigned to a slot:
+        https://dom.spec.whatwg.org/#concept-shadow-including-tree-order
+
+        Test: fast/custom-elements/enqueue-custom-element-upgrade-reaction.html
+
+        * bindings/js/JSCustomElementInterface.cpp:
+        (WebCore::JSCustomElementInterface::upgradeElement): Assert that the element being upgraded as the same qualified name
+        as the custom element interface.
+        * bindings/js/JSCustomElementRegistryCustom.cpp:
+        (WebCore::JSCustomElementRegistry::define): Moved the code to resolve the promise from here to addElementDefinition.
+        Also cleaned up the code to extract callbacks a little.
+        * dom/CustomElementReactionQueue.cpp:
+        (WebCore::CustomElementReactionQueue::enqueueElementUpgrade): Added an assertion.
+        (WebCore::CustomElementReactionQueue::enqueueElementUpgradeIfDefined): Added. Upgrade an element if the custom element
+        had already been defined.
+        * dom/CustomElementReactionQueue.h:
+        * dom/CustomElementRegistry.cpp:
+        (WebCore::CustomElementRegistry::create): Stores the reference to DOMWindow to find its document in addElementDefinition.
+        (WebCore::CustomElementRegistry::CustomElementRegistry): Ditto.
+        (WebCore::enqueueUpgradeInShadowIncludingTreeOrder): Added. Enqueue upgrade reactions in shadow-including tree order.
+        (WebCore::CustomElementRegistry::addElementDefinition): Upgrade all unresolved elements that matches this definition and
+        resolve the the promise returned by "whenDefined" if there is any.
+        (WebCore::CustomElementRegistry::addUpgradeCandidate): Deleted.
+        (WebCore::CustomElementRegistry::findInterface): Added a new variant that takes an element.
+        * dom/CustomElementRegistry.h:
+        * dom/Document.cpp:
+        (WebCore::createUpgradeCandidateElement): No longer takes DOMWindow since we don't upgrade synchronously here. It's also
+        wrong not to mark the element as unresolved custom element in a document without a browsing context per new semantics.
+        (WebCore::createHTMLElementWithNameValidation): Ditto.
+        (WebCore::createFallbackHTMLElement): Ditto.
+        * dom/Element.cpp:
+        (WebCore::Element::insertedInto): Enqueue an upgrade reaction if this is an unsolved custom element and there is now
+        a definition for it (the latter condition is checked in enqueueElementUpgradeIfDefined).
+        * html/parser/HTMLConstructionSite.cpp:
+        (WebCore::HTMLConstructionSite::createHTMLElementOrFindCustomElementInterface): Don't upgrade this element until it gets
+        connected to a document in Element::insertedInto.
+        * page/DOMWindow.cpp:
+        (WebCore::DOMWindow::ensureCustomElementRegistry):
+
 2016-09-01  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         Add toJS for JSC::PrivateName
index cd06128..673cd7a 100644 (file)
@@ -104,6 +104,7 @@ RefPtr<Element> JSCustomElementInterface::constructElement(const AtomicString& t
 
 void JSCustomElementInterface::upgradeElement(Element& element)
 {
+    ASSERT(element.tagQName() == name());
     ASSERT(element.isUnresolvedCustomElement());
     if (!canInvokeCallback())
         return;
index 025b27f..c617f96 100644 (file)
@@ -127,23 +127,20 @@ JSValue JSCustomElementRegistry::define(ExecState& state)
     QualifiedName name(nullAtom, localName, HTMLNames::xhtmlNamespaceURI);
     auto elementInterface = JSCustomElementInterface::create(name, constructor, globalObject());
 
-    auto* connectedCallback = getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "connectedCallback"));
+    if (auto* connectedCallback = getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "connectedCallback")))
+        elementInterface->setConnectedCallback(connectedCallback);
     if (state.hadException())
         return jsUndefined();
-    if (connectedCallback)
-        elementInterface->setConnectedCallback(connectedCallback);
 
-    auto* disconnectedCallback = getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "disconnectedCallback"));
+    if (auto* disconnectedCallback = getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "disconnectedCallback")))
+        elementInterface->setDisconnectedCallback(disconnectedCallback);
     if (state.hadException())
         return jsUndefined();
-    if (disconnectedCallback)
-        elementInterface->setDisconnectedCallback(disconnectedCallback);
 
-    auto* adoptedCallback = getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "adoptedCallback"));
+    if (auto* adoptedCallback = getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "adoptedCallback")))
+        elementInterface->setAdoptedCallback(adoptedCallback);
     if (state.hadException())
         return jsUndefined();
-    if (adoptedCallback)
-        elementInterface->setAdoptedCallback(adoptedCallback);
 
     auto* attributeChangedCallback = getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "attributeChangedCallback"));
     if (state.hadException())
@@ -161,14 +158,6 @@ JSValue JSCustomElementRegistry::define(ExecState& state)
 
     registry.addElementDefinition(WTFMove(elementInterface));
 
-    // FIXME: 17. Let map be registry's upgrade candidates map.
-    // FIXME: 18. Upgrade a newly-defined element given map and definition.
-
-    auto& promiseMap = registry.promiseMap();
-    auto promise = promiseMap.take(localName);
-    if (promise)
-        promise.value()->resolve(nullptr);
-
     return jsUndefined();
 }
 
index 64a7bcc..cd37951 100644 (file)
@@ -116,10 +116,30 @@ CustomElementReactionQueue::~CustomElementReactionQueue()
 
 void CustomElementReactionQueue::enqueueElementUpgrade(Element& element, JSCustomElementInterface& elementInterface)
 {
+    ASSERT(element.tagQName() == elementInterface.name());
     if (auto* queue = CustomElementReactionStack::ensureCurrentQueue())
         queue->m_items.append({CustomElementReactionQueueItem::Type::ElementUpgrade, element, elementInterface});
 }
 
+void CustomElementReactionQueue::enqueueElementUpgradeIfDefined(Element& element)
+{
+    ASSERT(element.inDocument());
+    ASSERT(element.isUnresolvedCustomElement());
+    auto* window = element.document().domWindow();
+    if (!window)
+        return;
+
+    auto* registry = window->customElementRegistry();
+    if (!registry)
+        return;
+
+    auto* elementInterface = registry->findInterface(element);
+    if (!elementInterface)
+        return;
+
+    enqueueElementUpgrade(element, *elementInterface);
+}
+
 void CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(Element& element)
 {
     ASSERT(element.isCustomElement());
index 2ba913b..cc38103 100644 (file)
@@ -46,6 +46,7 @@ public:
     ~CustomElementReactionQueue();
 
     static void enqueueElementUpgrade(Element&, JSCustomElementInterface&);
+    static void enqueueElementUpgradeIfDefined(Element&);
     static void enqueueConnectedCallbackIfNeeded(Element&);
     static void enqueueDisconnectedCallbackIfNeeded(Element&);
     static void enqueueAdoptedCallbackIfNeeded(Element&, Document& oldDocument, Document& newDocument);
index 3709522..c758c3c 100644 (file)
 
 #if ENABLE(CUSTOM_ELEMENTS)
 
+#include "CustomElementReactionQueue.h"
+#include "DOMWindow.h"
 #include "Document.h"
 #include "Element.h"
+#include "ElementTraversal.h"
 #include "JSCustomElementInterface.h"
 #include "JSDOMPromise.h"
 #include "MathMLNames.h"
 #include "QualifiedName.h"
 #include "SVGNames.h"
+#include "ShadowRoot.h"
 #include <runtime/JSCJSValueInlines.h>
 #include <wtf/text/AtomicString.h>
 
 namespace WebCore {
 
-Ref<CustomElementRegistry> CustomElementRegistry::create()
+Ref<CustomElementRegistry> CustomElementRegistry::create(DOMWindow& window)
 {
-    return adoptRef(*new CustomElementRegistry());
+    return adoptRef(*new CustomElementRegistry(window));
 }
 
-CustomElementRegistry::CustomElementRegistry()
+CustomElementRegistry::CustomElementRegistry(DOMWindow& window)
+    : m_window(window)
 { }
 
 CustomElementRegistry::~CustomElementRegistry()
 { }
 
+// https://dom.spec.whatwg.org/#concept-shadow-including-tree-order
+static void enqueueUpgradeInShadowIncludingTreeOrder(ContainerNode& node, JSCustomElementInterface& elementInterface)
+{
+    for (Element* element = ElementTraversal::firstWithin(node); element; element = ElementTraversal::next(*element)) {
+        if (element->isUnresolvedCustomElement() && element->tagQName() == elementInterface.name())
+            CustomElementReactionQueue::enqueueElementUpgrade(*element, elementInterface);
+        if (auto* shadowRoot = element->shadowRoot()) {
+            if (shadowRoot->mode() != ShadowRoot::Mode::UserAgent)
+                enqueueUpgradeInShadowIncludingTreeOrder(*shadowRoot, elementInterface);
+        }
+    }
+}
+
 void CustomElementRegistry::addElementDefinition(Ref<JSCustomElementInterface>&& elementInterface)
 {
     AtomicString localName = elementInterface->name().localName();
@@ -58,35 +76,23 @@ void CustomElementRegistry::addElementDefinition(Ref<JSCustomElementInterface>&&
     m_constructorMap.add(elementInterface->constructor(), elementInterface.ptr());
     m_nameMap.add(localName, elementInterface.copyRef());
 
-    auto candidateList = m_upgradeCandidatesMap.find(localName);
-    if (candidateList == m_upgradeCandidatesMap.end())
-        return;
-
-    Vector<RefPtr<Element>> list(WTFMove(candidateList->value));
-
-    m_upgradeCandidatesMap.remove(localName);
-
-    for (auto& candidate : list) {
-        ASSERT(candidate);
-        elementInterface->upgradeElement(*candidate);
-    }
+    if (auto* document = m_window.document())
+        enqueueUpgradeInShadowIncludingTreeOrder(*document, elementInterface.get());
 
-    // We should not be adding more upgrade candidate for this local name.
-    ASSERT(!m_upgradeCandidatesMap.contains(localName));
+    if (auto promise = m_promiseMap.take(localName))
+        promise.value()->resolve(nullptr);
 }
 
-void CustomElementRegistry::addUpgradeCandidate(Element& candidate)
+JSCustomElementInterface* CustomElementRegistry::findInterface(const Element& element) const
 {
-    auto result = m_upgradeCandidatesMap.ensure(candidate.localName(), [] {
-        return Vector<RefPtr<Element>>();
-    });
-    auto& nodeVector = result.iterator->value;
-    ASSERT(!nodeVector.contains(&candidate));
-    nodeVector.append(&candidate);
+    return findInterface(element.tagQName());
 }
 
 JSCustomElementInterface* CustomElementRegistry::findInterface(const QualifiedName& name) const
 {
+    ASSERT(!name.hasPrefix());
+    if (name.namespaceURI() != HTMLNames::xhtmlNamespaceURI)
+        return nullptr;
     auto it = m_nameMap.find(name.localName());
     return it == m_nameMap.end() || it->value->name() != name ? nullptr : const_cast<JSCustomElementInterface*>(it->value.ptr());
 }
index 0607d6d..5761a47 100644 (file)
@@ -43,6 +43,7 @@ class JSValue;
 namespace WebCore {
 
 class CustomElementRegistry;
+class DOMWindow;
 class DeferredWrapper;
 class Element;
 class JSCustomElementInterface;
@@ -50,14 +51,14 @@ class QualifiedName;
 
 class CustomElementRegistry : public RefCounted<CustomElementRegistry> {
 public:
-    static Ref<CustomElementRegistry> create();
+    static Ref<CustomElementRegistry> create(DOMWindow&);
     ~CustomElementRegistry();
 
     void addElementDefinition(Ref<JSCustomElementInterface>&&);
-    void addUpgradeCandidate(Element&);
 
     bool& elementDefinitionIsRunning() { return m_elementDefinitionIsRunning; }
 
+    JSCustomElementInterface* findInterface(const Element&) const;
     JSCustomElementInterface* findInterface(const QualifiedName&) const;
     JSCustomElementInterface* findInterface(const AtomicString&) const;
     JSCustomElementInterface* findInterface(const JSC::JSObject*) const;
@@ -68,9 +69,9 @@ public:
     HashMap<AtomicString, Ref<DeferredWrapper>>& promiseMap() { return m_promiseMap; }
 
 private:
-    CustomElementRegistry();
+    CustomElementRegistry(DOMWindow&);
 
-    HashMap<AtomicString, Vector<RefPtr<Element>>> m_upgradeCandidatesMap;
+    DOMWindow& m_window;
     HashMap<AtomicString, Ref<JSCustomElementInterface>> m_nameMap;
     HashMap<const JSC::JSObject*, JSCustomElementInterface*> m_constructorMap;
     HashMap<AtomicString, Ref<DeferredWrapper>> m_promiseMap;
index 3e48a70..5cc73d2 100644 (file)
@@ -881,9 +881,9 @@ void Document::childrenChanged(const ChildChange& change)
 }
 
 #if ENABLE(CUSTOM_ELEMENTS)
-static ALWAYS_INLINE RefPtr<HTMLElement> createUpgradeCandidateElement(Document& document, DOMWindow* window, const QualifiedName& name)
+static ALWAYS_INLINE RefPtr<HTMLElement> createUpgradeCandidateElement(Document& document, const QualifiedName& name)
 {
-    if (!window || !RuntimeEnabledFeatures::sharedFeatures().customElementsEnabled())
+    if (!RuntimeEnabledFeatures::sharedFeatures().customElementsEnabled())
         return nullptr;
 
     if (Document::validateCustomElementName(name.localName()) != CustomElementNameValidationStatus::Valid)
@@ -891,7 +891,6 @@ static ALWAYS_INLINE RefPtr<HTMLElement> createUpgradeCandidateElement(Document&
 
     auto element = HTMLElement::create(name, document);
     element->setIsUnresolvedCustomElement();
-    window->ensureCustomElementRegistry().addUpgradeCandidate(element.get());
     return WTFMove(element);
 }
 #endif
@@ -921,7 +920,7 @@ static RefPtr<Element> createHTMLElementWithNameValidation(Document& document, c
     QualifiedName qualifiedName(nullAtom, localName, xhtmlNamespaceURI);
 
 #if ENABLE(CUSTOM_ELEMENTS)
-    if (auto element = createUpgradeCandidateElement(document, window, qualifiedName))
+    if (auto element = createUpgradeCandidateElement(document, qualifiedName))
         return WTFMove(element);
 #endif
 
@@ -1099,7 +1098,7 @@ static Ref<HTMLElement> createFallbackHTMLElement(Document& document, const Qual
         }
     }
     // FIXME: Should we also check the equality of prefix between the custom element and name?
-    if (auto element = createUpgradeCandidateElement(document, window, name))
+    if (auto element = createUpgradeCandidateElement(document, name))
         return element.releaseNonNull();
 #endif
     return HTMLUnknownElement::create(name, document);
index a2b36a7..8725de7 100644 (file)
@@ -1606,8 +1606,13 @@ Node::InsertionNotificationRequest Element::insertedInto(ContainerNode& insertio
     }
 
 #if ENABLE(CUSTOM_ELEMENTS)
-    if (becomeConnected && UNLIKELY(isCustomElement()))
-        CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(*this);
+    if (becomeConnected) {
+        if (UNLIKELY(isUnresolvedCustomElement()))
+            CustomElementReactionQueue::enqueueElementUpgradeIfDefined(*this);
+        if (UNLIKELY(isCustomElement()))
+            CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(*this);
+    }
+
 #endif
 
     return InsertionDone;
index 4ff88ad..1dc0905 100644 (file)
@@ -678,7 +678,6 @@ RefPtr<Element> HTMLConstructionSite::createHTMLElementOrFindCustomElementInterf
         if (window && Document::validateCustomElementName(localName) == CustomElementNameValidationStatus::Valid) {
             element = HTMLElement::create(qualifiedName, ownerDocument);
             element->setIsUnresolvedCustomElement();
-            window->ensureCustomElementRegistry().addUpgradeCandidate(*element);
         } else
 #endif
             element = HTMLUnknownElement::create(qualifiedName, ownerDocument);
index ca7956d..36cdbca 100644 (file)
@@ -625,7 +625,7 @@ bool DOMWindow::isCurrentlyDisplayedInFrame() const
 CustomElementRegistry& DOMWindow::ensureCustomElementRegistry()
 {
     if (!m_customElementRegistry)
-        m_customElementRegistry = CustomElementRegistry::create();
+        m_customElementRegistry = CustomElementRegistry::create(*this);
     return *m_customElementRegistry;
 }
 #endif