Selaa lähdekoodia

LibWeb: Report exceptions from custom element upgrades to global object

Andrew Kaster 8 kuukautta sitten
vanhempi
commit
5be4825504

+ 18 - 10
Libraries/LibWeb/Bindings/MainThreadVM.cpp

@@ -739,24 +739,32 @@ void invoke_custom_element_reactions(Vector<GC::Root<DOM::Element>>& element_que
             // 1. Remove the first element of reactions, and let reaction be that element. Switch on reaction's type:
             auto reaction = reactions->take_first();
 
-            auto maybe_exception = reaction.visit(
-                [&](DOM::CustomElementUpgradeReaction const& custom_element_upgrade_reaction) -> JS::ThrowCompletionOr<void> {
+            reaction.visit(
+                [&](DOM::CustomElementUpgradeReaction const& custom_element_upgrade_reaction) -> void {
                     // -> upgrade reaction
                     //      Upgrade element using reaction's custom element definition.
-                    return element->upgrade_element(*custom_element_upgrade_reaction.custom_element_definition);
+                    auto maybe_exception = element->upgrade_element(*custom_element_upgrade_reaction.custom_element_definition);
+                    // If this throws an exception, catch it, and report it for reaction's custom element definition's constructor's corresponding JavaScript object's associated realm's global object.
+                    if (maybe_exception.is_error()) {
+                        // FIXME: Should it be easier to get to report an exception from an IDL callback?
+                        auto& callback = custom_element_upgrade_reaction.custom_element_definition->constructor();
+                        auto& realm = callback.callback->shape().realm();
+                        auto& global = realm.global_object();
+
+                        auto* window_or_worker = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&global);
+                        VERIFY(window_or_worker);
+                        window_or_worker->report_an_exception(maybe_exception.error_value());
+                    }
                 },
-                [&](DOM::CustomElementCallbackReaction& custom_element_callback_reaction) -> JS::ThrowCompletionOr<void> {
+                [&](DOM::CustomElementCallbackReaction& custom_element_callback_reaction) -> void {
                     // -> callback reaction
                     //      Invoke reaction's callback function with reaction's arguments, and with element as the callback this value.
                     auto result = WebIDL::invoke_callback(*custom_element_callback_reaction.callback, element.ptr(), custom_element_callback_reaction.arguments);
+                    // FIXME: The error from CustomElementCallbackReaction is supposed
+                    //     to use the new steps for IDL callback error reporting
                     if (result.is_abrupt())
-                        return result.release_error();
-                    return {};
+                        HTML::report_exception(result, element->realm());
                 });
-
-            // If this throws an exception, catch it, and report the exception.
-            if (maybe_exception.is_throw_completion())
-                HTML::report_exception(maybe_exception, element->realm());
         }
     }
 }

+ 1 - 0
Tests/LibWeb/Text/expected/HTML/custom-elements-throw-in-constructor.txt

@@ -1,2 +1,3 @@
 Entered TestElement constructor, throwing.
+Uncaught exception: test
 PASS! (Didn't crash)

+ 39 - 0
Tests/LibWeb/Text/expected/wpt-import/custom-elements/upgrading.txt

@@ -0,0 +1,39 @@
+Summary
+
+Harness status: OK
+
+Rerun
+
+Found 28 tests
+
+25 Pass
+3 Fail
+Details
+Result	Test Name	MessagePass	Creating an element in the document of the template elements must not enqueue a custom element upgrade reaction because the document does not have a browsing context	
+Pass	Creating an element in the document of the template elements and inserting into the document must not enqueue a custom element upgrade reaction	
+Pass	Creating an element in the document of the template elements and adopting back to a document with browsing context must enqueue a custom element upgrade reaction	
+Pass	Creating an element in a new document must not enqueue a custom element upgrade reaction because the document does not have a browsing context	
+Pass	Creating an element in a new document and inserting into the document must not enqueue a custom element upgrade reaction	
+Pass	Creating an element in a new document and adopting back to a document with browsing context must enqueue a custom element upgrade reaction	
+Pass	Creating an element in a cloned document must not enqueue a custom element upgrade reaction because the document does not have a browsing context	
+Pass	Creating an element in a cloned document and inserting into the document must not enqueue a custom element upgrade reaction	
+Pass	Creating an element in a cloned document and adopting back to a document with browsing context must enqueue a custom element upgrade reaction	
+Pass	Creating an element in a 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 a document created by createHTMLDocument and inserting into the document must not enqueue a custom element upgrade reaction	
+Pass	Creating an element in a 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 an 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 an HTML document created by createDocument and inserting into the document must not enqueue a custom element upgrade reaction	
+Pass	Creating an element in an HTML document created by createDocument and adopting back to a document with browsing context must enqueue a custom element upgrade reaction	
+Fail	Creating an element in an HTML document fetched by XHR must not enqueue a custom element upgrade reaction because the document does not have a browsing context	
+Fail	Creating an element in an HTML document fetched by XHR and inserting into the document must not enqueue a custom element upgrade reaction	
+Fail	Creating an element in an 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 the document of an iframe must not enqueue a custom element upgrade reaction if there is no matching definition	
+Pass	Creating an element in the document of an iframe must enqueue a custom element upgrade reaction if there is a matching definition	
+Pass	"define" in the document of an iframe must not enqueue a custom element upgrade reaction on a disconnected unresolved custom element	
+Pass	Inserting an unresolved custom element into the document of an iframe must enqueue a custom element upgrade reaction	
+Pass	"define" in the document of 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 the document of an iframe must not enqueue a custom element upgrade reaction	
+Pass	Adopting and inserting an unresolved custom element into the document of an iframe must enqueue a custom element upgrade reaction	
+Pass	If definition's disable shadow is true and element's shadow root is non-null, then throw a "NotSupportedError" DOMException.	
+Pass	Infinite constructor recursion with upgrade(this) should not be possible	
+Pass	Infinite constructor recursion with appendChild should not be possible	

+ 2 - 0
Tests/LibWeb/Text/input/HTML/custom-elements-throw-in-constructor.html

@@ -2,6 +2,8 @@
 <script src="../include.js"></script>
 <script>
     test(() => {
+        removeTestErrorHandler()
+        window.addEventListener("error", (event) => { println(`${event.message}`); })
         class TestElement extends HTMLElement {
             constructor() {
                 super();

+ 276 - 0
Tests/LibWeb/Text/input/wpt-import/custom-elements/resources/custom-elements-helpers.js

@@ -0,0 +1,276 @@
+function create_window_in_test(t, srcdoc) {
+  let p = new Promise((resolve) => {
+    let f = document.createElement('iframe');
+    f.srcdoc = srcdoc ? srcdoc : '';
+    f.onload = (event) => {
+      let w = f.contentWindow;
+      t.add_cleanup(() => f.remove());
+      resolve(w);
+    };
+    document.body.appendChild(f);
+  });
+  return p;
+}
+
+function create_window_in_test_async(test, mime, doc) {
+    return new Promise((resolve) => {
+        let iframe = document.createElement('iframe');
+        blob = new Blob([doc], {type: mime});
+        iframe.src = URL.createObjectURL(blob);
+        iframe.onload = (event) => {
+            let contentWindow = iframe.contentWindow;
+            test.add_cleanup(() => iframe.remove());
+            resolve(contentWindow);
+        };
+        document.body.appendChild(iframe);
+    });
+}
+
+function test_with_window(f, name, srcdoc) {
+  promise_test((t) => {
+    return create_window_in_test(t, srcdoc)
+    .then((w) => {
+      f(w, w.document);
+    });
+  }, name);
+}
+
+function define_custom_element_in_window(window, name, observedAttributes) {
+    let log = [];
+
+    class CustomElement extends window.HTMLElement {
+        constructor() {
+            super();
+            log.push(create_constructor_log(this));
+        }
+        attributeChangedCallback(...args) {
+            log.push(create_attribute_changed_callback_log(this, ...args));
+        }
+        connectedCallback() { log.push(create_connected_callback_log(this)); }
+        disconnectedCallback() { log.push(create_disconnected_callback_log(this)); }
+        adoptedCallback(oldDocument, newDocument) { log.push({type: 'adopted', element: this, oldDocument: oldDocument, newDocument: newDocument}); }
+    }
+    CustomElement.observedAttributes = observedAttributes;
+
+    window.customElements.define(name, CustomElement);
+
+    return {
+        name: name,
+        class: CustomElement,
+        takeLog: function () {
+            let currentLog = log; log = [];
+            currentLog.types = () => currentLog.map((entry) => entry.type);
+            currentLog.last = () => currentLog[currentLog.length - 1];
+            return currentLog;
+        }
+    };
+}
+
+function create_constructor_log(element) {
+    return {type: 'constructed', element: element};
+}
+
+function assert_constructor_log_entry(log, element) {
+    assert_equals(log.type, 'constructed');
+    assert_equals(log.element, element);
+}
+
+function create_connected_callback_log(element) {
+    return {type: 'connected', element: element};
+}
+
+function assert_connected_log_entry(log, element) {
+    assert_equals(log.type, 'connected');
+    assert_equals(log.element, element);
+}
+
+function create_disconnected_callback_log(element) {
+    return {type: 'disconnected', element: element};
+}
+
+function assert_disconnected_log_entry(log, element) {
+    assert_equals(log.type, 'disconnected');
+    assert_equals(log.element, element);
+}
+
+function assert_adopted_log_entry(log, element) {
+    assert_equals(log.type, 'adopted');
+    assert_equals(log.element, element);
+}
+
+function create_adopted_callback_log(element) {
+    return {type: 'adopted', element: element};
+}
+
+function create_attribute_changed_callback_log(element, name, oldValue, newValue, namespace) {
+    return {
+        type: 'attributeChanged',
+        element: element,
+        name: name,
+        namespace: namespace,
+        oldValue: oldValue,
+        newValue: newValue,
+        actualValue: element.getAttributeNS(namespace, name)
+    };
+}
+
+function assert_attribute_log_entry(log, expected) {
+    assert_equals(log.type, 'attributeChanged');
+    assert_equals(log.name, expected.name);
+    assert_equals(log.oldValue, expected.oldValue);
+    assert_equals(log.newValue, expected.newValue);
+    assert_equals(log.actualValue, expected.newValue);
+    assert_equals(log.namespace, expected.namespace);
+}
+
+
+function define_new_custom_element(observedAttributes) {
+    let log = [];
+    let name = 'custom-element-' + define_new_custom_element._element_number++;
+
+    class CustomElement extends HTMLElement {
+        constructor() {
+            super();
+            log.push({type: 'constructed', element: this});
+        }
+        attributeChangedCallback(...args) {
+            log.push(create_attribute_changed_callback_log(this, ...args));
+        }
+        connectedCallback() { log.push({type: 'connected', element: this}); }
+        disconnectedCallback() { log.push({type: 'disconnected', element: this}); }
+        adoptedCallback(oldDocument, newDocument) { log.push({type: 'adopted', element: this, oldDocument: oldDocument, newDocument: newDocument}); }
+    }
+    CustomElement.observedAttributes = observedAttributes;
+
+    customElements.define(name, CustomElement);
+
+    return {
+        name: name,
+        class: CustomElement,
+        takeLog: function () {
+            let currentLog = log; log = [];
+            currentLog.types = () => currentLog.map((entry) => entry.type);
+            currentLog.last = () => currentLog[currentLog.length - 1];
+            return currentLog;
+        }
+    };
+}
+define_new_custom_element._element_number = 1;
+
+function define_build_in_custom_element(observedAttributes, extendedElement, extendsOption) {
+    let log = [];
+    let name = 'custom-element-' + define_build_in_custom_element._element_number++;
+
+    class CustomElement extends extendedElement {
+        constructor() {
+            super();
+            log.push({type: 'constructed', element: this});
+        }
+        attributeChangedCallback(...args) {
+            log.push(create_attribute_changed_callback_log(this, ...args));
+        }
+        connectedCallback() { log.push({type: 'connected', element: this}); }
+        disconnectedCallback() { log.push({type: 'disconnected', element: this}); }
+        adoptedCallback(oldDocument, newDocument) { log.push({type: 'adopted', element: this, oldDocument: oldDocument, newDocument: newDocument}); }
+    }
+    CustomElement.observedAttributes = observedAttributes;
+    customElements.define(name, CustomElement, { extends: extendsOption});
+
+    return {
+        name: name,
+        class: CustomElement,
+        takeLog: function () {
+            let currentLog = log; log = [];
+            currentLog.types = () => currentLog.map((entry) => entry.type);
+            currentLog.last = () => currentLog[currentLog.length - 1];
+            return currentLog;
+        }
+    };
+}
+define_build_in_custom_element._element_number = 1;
+
+function document_types() {
+    return [
+        {
+            name: 'the document',
+            create: function () { return Promise.resolve(document); },
+            isOwner: true,
+            hasBrowsingContext: true,
+        },
+        {
+            name: 'the document of the template elements',
+            create: function () {
+                return new Promise(function (resolve) {
+                    var template = document.createElementNS('http://www.w3.org/1999/xhtml', 'template');
+                    var doc = template.content.ownerDocument;
+                    if (!doc.documentElement)
+                        doc.appendChild(doc.createElement('html'));
+                    resolve(doc);
+                });
+            },
+            hasBrowsingContext: false,
+        },
+        {
+            name: 'a new document',
+            create: function () {
+                return new Promise(function (resolve) {
+                    var doc = new Document();
+                    doc.appendChild(doc.createElement('html'));
+                    resolve(doc);
+                });
+            },
+            hasBrowsingContext: false,
+        },
+        {
+            name: 'a cloned document',
+            create: function () {
+                return new Promise(function (resolve) {
+                    var doc = document.cloneNode(false);
+                    doc.appendChild(doc.createElement('html'));
+                    resolve(doc);
+                });
+            },
+            hasBrowsingContext: false,
+        },
+        {
+            name: 'a document created by createHTMLDocument',
+            create: function () {
+                return Promise.resolve(document.implementation.createHTMLDocument());
+            },
+            hasBrowsingContext: false,
+        },
+        {
+            name: 'an HTML document created by createDocument',
+            create: function () {
+                return Promise.resolve(document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null));
+            },
+            hasBrowsingContext: false,
+        },
+        {
+            name: 'the document of an iframe',
+            create: function () {
+                return new Promise(function (resolve, reject) {
+                    var iframe = document.createElement('iframe');
+                    iframe.onload = function () { resolve(iframe.contentDocument); }
+                    iframe.onerror = function () { reject('Failed to load an empty iframe'); }
+                    document.body.appendChild(iframe);
+                });
+            },
+            hasBrowsingContext: true,
+        },
+        {
+            name: 'an HTML document fetched by XHR',
+            create: function () {
+                return new Promise(function (resolve, reject) {
+                    var xhr = new XMLHttpRequest();
+                    xhr.open('GET', 'resources/empty-html-document.html');
+                    xhr.overrideMimeType('text/xml');
+                    xhr.onload = function () { resolve(xhr.responseXML); }
+                    xhr.onerror = function () { reject('Failed to fetch the document'); }
+                    xhr.send();
+                });
+            },
+            hasBrowsingContext: false,
+        }
+    ];
+}

+ 258 - 0
Tests/LibWeb/Text/input/wpt-import/custom-elements/upgrading.html

@@ -0,0 +1,258 @@
+<!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>
+<script src="resources/custom-elements-helpers.js"></script>
+</head>
+<body>
+<infinite-cloning-element-1></infinite-cloning-element-1>
+<infinite-cloning-element-2 id="a"></infinite-cloning-element-2>
+<infinite-cloning-element-2 id="b"></infinite-cloning-element-2>
+<div id="log"></div>
+<script>
+setup({allow_uncaught_exception:true});
+
+class PredefinedCustomElement extends HTMLElement {}
+customElements.define('predefined-custom-element', PredefinedCustomElement);
+
+var customElementNumber = 1;
+function generateNextCustomElementName() { return 'custom-' + customElementNumber++; }
+
+// Tests for documents without a browsing context.
+document_types().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(Object.getPrototypeOf(unresolvedElement), 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(Object.getPrototypeOf(unresolvedElementInDoc), 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(Object.getPrototypeOf(unresolvedElementInDoc), prototype, '"define" must not upgrade a disconnected unresolved custom elements');
+            doc.documentElement.appendChild(unresolvedElementInDoc);
+            assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), 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(Object.getPrototypeOf(unresolvedElement), 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(Object.getPrototypeOf(unresolvedElementInDoc), 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(Object.getPrototypeOf(unresolvedElementInDoc), 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(Object.getPrototypeOf(unresolvedElementInDoc), someCustomElement.prototype,
+                    'Inserting an element into a document with a browsing context must enqueue a custom element upgrade reaction');
+            } else {
+                assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), 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');
+
+});
+
+// Tests for documents with a browsing context.
+document_types().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(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype);
+            assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), docWindow.HTMLElement.prototype);
+
+            docWindow.customElements.define('unresolved-element', UnresolvedElement);
+
+            assert_equals(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype);
+            assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), 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(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype);
+            assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), docWindow.HTMLElement.prototype);
+
+            docWindow.customElements.define('unresolved-element', UnresolvedElement);
+            doc.documentElement.appendChild(unresolvedElementInDoc);
+
+            assert_equals(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype);
+            assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), 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(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype);
+            assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), docWindow.HTMLElement.prototype);
+
+            docWindow.customElements.define('unresolved-element', UnresolvedElement);
+
+            assert_equals(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype);
+            assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), 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');
+
+});
+
+test(() => {
+    class ShadowDisabledElement extends HTMLElement {
+      static get disabledFeatures() { return ['shadow']; }
+    }
+    let error = null;
+    window.addEventListener('error', e => { error = e.error; }, {once: true});
+    let element = document.createElement('shadow-disabled');
+    element.attachShadow({mode: 'open'});
+    customElements.define('shadow-disabled', ShadowDisabledElement);
+    customElements.upgrade(element);
+    assert_false(element instanceof ShadowDisabledElement,
+                 'Upgrading should fail.');
+    assert_true(error instanceof DOMException);
+    assert_equals(error.name, 'NotSupportedError');
+}, 'If definition\'s disable shadow is true and element\'s shadow root is ' +
+    'non-null, then throw a "NotSupportedError" DOMException.');
+
+test(() => {
+    var log = [];
+
+    customElements.define('infinite-cloning-element-1',class extends HTMLElement {
+        constructor() {
+            super();
+            log.push([this, 'begin']);
+            // Potential infinite recursion:
+            customElements.upgrade(this);
+            log.push([this, 'end']);
+        }
+    });
+
+    assert_equals(log.length, 2);
+    const instance = document.querySelector("infinite-cloning-element-1");
+    assert_array_equals(log[0], [instance, 'begin']);
+    assert_array_equals(log[1], [instance, 'end']);
+}, 'Infinite constructor recursion with upgrade(this) should not be possible');
+
+test(() => {
+    var log = [];
+
+    customElements.define('infinite-cloning-element-2',class extends HTMLElement {
+        constructor() {
+            super();
+            log.push([this, 'begin']);
+            const b = document.querySelector("#b");
+            b.remove();
+            // While this constructor is running for "a", "b" is still
+            // undefined, and so inserting it into the document will enqueue a
+            // second upgrade reaction for "b" in addition to the one enqueued
+            // by defining x-foo.
+            document.body.appendChild(b);
+            log.push([this, 'end']);
+        }
+    });
+
+    assert_equals(log.length, 4);
+    const instanceA = document.querySelector("#a");
+    const instanceB = document.querySelector("#b");
+    assert_array_equals(log[0], [instanceA, 'begin']);
+    assert_array_equals(log[1], [instanceB, 'begin']);
+    assert_array_equals(log[2], [instanceB, 'end']);
+    assert_array_equals(log[3], [instanceA, 'end']);
+}, 'Infinite constructor recursion with appendChild should not be possible');
+
+
+</script>
+</body>
+</html>