LibWeb: Report exceptions from custom element upgrades to global object
This commit is contained in:
parent
54b0476d70
commit
5be4825504
Notes:
github-actions[bot]
2024-11-24 00:17:49 +00:00
Author: https://github.com/ADKaster Commit: https://github.com/LadybirdBrowser/ladybird/commit/5be48255047 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2543 Reviewed-by: https://github.com/tcl3 ✅
6 changed files with 594 additions and 10 deletions
Libraries/LibWeb/Bindings
Tests/LibWeb/Text
expected
input
HTML
wpt-import/custom-elements
|
@ -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,2 +1,3 @@
|
|||
Entered TestElement constructor, throwing.
|
||||
Uncaught exception: test
|
||||
PASS! (Didn't crash)
|
||||
|
|
|
@ -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,6 +2,8 @@
|
|||
<script src="../include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
removeTestErrorHandler()
|
||||
window.addEventListener("error", (event) => { println(`${event.message}`); })
|
||||
class TestElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
];
|
||||
}
|
|
@ -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>
|
Loading…
Add table
Reference in a new issue