| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Custom Elements: CustomElementRegistry interface</title> | |
| <meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> | |
| <meta name="assert" content="CustomElementRegistry interface must exist"> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| </head> | |
| <body> | |
| <div id="log"></div> | |
| <script> | |
| test(function () { | |
| assert_true('define' in CustomElementRegistry.prototype, '"define" exists on CustomElementRegistry.prototype'); | |
| assert_true('define' in customElements, '"define" exists on window.customElements'); | |
| }, 'CustomElementRegistry interface must have define as a method'); | |
| test(function () { | |
| assert_throws_js(TypeError, function () { customElements.define('badname', 1); }, | |
| 'customElements.define must throw a TypeError when the element interface is a number'); | |
| assert_throws_js(TypeError, function () { customElements.define('badname', '123'); }, | |
| 'customElements.define must throw a TypeError when the element interface is a string'); | |
| assert_throws_js(TypeError, function () { customElements.define('badname', {}); }, | |
| 'customElements.define must throw a TypeError when the element interface is an object'); | |
| assert_throws_js(TypeError, function () { customElements.define('badname', []); }, | |
| 'customElements.define must throw a TypeError when the element interface is an array'); | |
| }, 'customElements.define must throw when the element interface is not a constructor'); | |
| test(function () { | |
| customElements.define('custom-html-element', HTMLElement); | |
| }, 'customElements.define must not throw the constructor is HTMLElement'); | |
| test(function () { | |
| class MyCustomElement extends HTMLElement {}; | |
| assert_throws_dom('SyntaxError', function () { customElements.define(null, MyCustomElement); }, | |
| 'customElements.define must throw a SyntaxError if the tag name is null'); | |
| assert_throws_dom('SyntaxError', function () { customElements.define('', MyCustomElement); }, | |
| 'customElements.define must throw a SyntaxError if the tag name is empty'); | |
| assert_throws_dom('SyntaxError', function () { customElements.define('abc', MyCustomElement); }, | |
| 'customElements.define must throw a SyntaxError if the tag name does not contain "-"'); | |
| assert_throws_dom('SyntaxError', function () { customElements.define('a-Bc', MyCustomElement); }, | |
| 'customElements.define must throw a SyntaxError if the tag name contains an upper case letter'); | |
| var builtinTagNames = [ | |
| 'annotation-xml', | |
| 'color-profile', | |
| 'font-face', | |
| 'font-face-src', | |
| 'font-face-uri', | |
| 'font-face-format', | |
| 'font-face-name', | |
| 'missing-glyph' | |
| ]; | |
| for (var tagName of builtinTagNames) { | |
| assert_throws_dom('SyntaxError', function () { customElements.define(tagName, MyCustomElement); }, | |
| 'customElements.define must throw a SyntaxError if the tag name is "' + tagName + '"'); | |
| } | |
| }, 'customElements.define must throw with an invalid name'); | |
| test(function () { | |
| class SomeCustomElement extends HTMLElement {}; | |
| var calls = []; | |
| var OtherCustomElement = new Proxy(class extends HTMLElement {}, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| return target[name]; | |
| } | |
| }) | |
| customElements.define('some-custom-element', SomeCustomElement); | |
| assert_throws_dom('NotSupportedError', function () { customElements.define('some-custom-element', OtherCustomElement); }, | |
| 'customElements.define must throw a NotSupportedError if the specified tag name is already used'); | |
| assert_array_equals(calls, [], 'customElements.define must validate the custom element name before getting the prototype of the constructor'); | |
| }, 'customElements.define must throw when there is already a custom element of the same name'); | |
| test(function () { | |
| class AnotherCustomElement extends HTMLElement {}; | |
| customElements.define('another-custom-element', AnotherCustomElement); | |
| assert_throws_dom('NotSupportedError', function () { customElements.define('some-other-element', AnotherCustomElement); }, | |
| 'customElements.define must throw a NotSupportedError if the specified class already defines an element'); | |
| }, 'customElements.define must throw a NotSupportedError when there is already a custom element with the same class'); | |
| test(function () { | |
| var outerCalls = []; | |
| var OuterCustomElement = new Proxy(class extends HTMLElement { }, { | |
| get: function (target, name) { | |
| outerCalls.push(name); | |
| customElements.define('inner-custom-element', InnerCustomElement); | |
| return target[name]; | |
| } | |
| }); | |
| var innerCalls = []; | |
| var InnerCustomElement = new Proxy(class extends HTMLElement { }, { | |
| get: function (target, name) { | |
| outerCalls.push(name); | |
| return target[name]; | |
| } | |
| }); | |
| assert_throws_dom('NotSupportedError', function () { customElements.define('outer-custom-element', OuterCustomElement); }, | |
| 'customElements.define must throw a NotSupportedError if the specified class already defines an element'); | |
| assert_array_equals(outerCalls, ['prototype'], 'customElements.define must get "prototype"'); | |
| assert_array_equals(innerCalls, [], | |
| 'customElements.define must throw a NotSupportedError when element definition is running flag is set' | |
| + ' before getting the prototype of the constructor'); | |
| }, 'customElements.define must throw a NotSupportedError when element definition is running flag is set'); | |
| test(function () { | |
| var calls = []; | |
| var ElementWithBadInnerConstructor = new Proxy(class extends HTMLElement { }, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| customElements.define('inner-custom-element', 1); | |
| return target[name]; | |
| } | |
| }); | |
| assert_throws_js(TypeError, function () { | |
| customElements.define('element-with-bad-inner-constructor', ElementWithBadInnerConstructor); | |
| }, 'customElements.define must throw a NotSupportedError if IsConstructor(constructor) is false'); | |
| assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"'); | |
| }, 'customElements.define must check IsConstructor on the constructor before checking the element definition is running flag'); | |
| test(function () { | |
| var calls = []; | |
| var ElementWithBadInnerName = new Proxy(class extends HTMLElement { }, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| customElements.define('badname', class extends HTMLElement {}); | |
| return target[name]; | |
| } | |
| }); | |
| assert_throws_dom('SyntaxError', function () { | |
| customElements.define('element-with-bad-inner-name', ElementWithBadInnerName); | |
| }, 'customElements.define must throw a SyntaxError if the specified name is not a valid custom element name'); | |
| 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")'); | |
| var iframe = document.createElement('iframe'); | |
| iframe.onload = function () { | |
| testCase.step(function () { | |
| var InnerCustomElement = class extends iframe.contentWindow.HTMLElement {}; | |
| var calls = []; | |
| var proxy = new Proxy(class extends HTMLElement { }, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| if (name === "prototype") { | |
| iframe.contentWindow.customElements.define('another-custom-element', InnerCustomElement); | |
| } | |
| return target[name]; | |
| } | |
| }) | |
| customElements.define('element-with-inner-element-define', proxy); | |
| assert_array_equals(calls, ['prototype', 'disabledFeatures', 'formAssociated'], | |
| 'customElements.define must get "prototype", "disabledFeatures", and "formAssociated" on the constructor'); | |
| assert_true(iframe.contentDocument.createElement('another-custom-element') instanceof InnerCustomElement); | |
| }); | |
| document.body.removeChild(iframe); | |
| testCase.done(); | |
| } | |
| document.body.appendChild(iframe); | |
| })(); | |
| test(function () { | |
| var calls = []; | |
| var ElementWithBadInnerName = new Proxy(class extends HTMLElement { }, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| customElements.define('badname', class extends HTMLElement {}); | |
| return target[name]; | |
| } | |
| }); | |
| assert_throws_dom('SyntaxError', function () { | |
| customElements.define('element-with-bad-inner-name', ElementWithBadInnerName); | |
| }, 'customElements.define must throw a SyntaxError if the specified name is not a valid custom element name'); | |
| assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"'); | |
| }, ''); | |
| test(function () { | |
| var calls = []; | |
| var proxy = new Proxy(class extends HTMLElement { }, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| return target[name]; | |
| } | |
| }); | |
| customElements.define('proxy-element', proxy); | |
| assert_array_equals(calls, ['prototype', 'disabledFeatures', 'formAssociated']); | |
| }, 'customElements.define must get "prototype", "disabledFeatures", and "formAssociated" property of the constructor'); | |
| test(function () { | |
| var err = {name: 'expectedError'} | |
| var proxy = new Proxy(class extends HTMLElement { }, { | |
| get: function (target, name) { | |
| throw err; | |
| } | |
| }); | |
| assert_throws_exactly(err, function () { customElements.define('element-with-string-prototype', proxy); }); | |
| }, 'customElements.define must rethrow an exception thrown while getting "prototype" property of the constructor'); | |
| test(function () { | |
| var returnedValue; | |
| var proxy = new Proxy(class extends HTMLElement { }, { | |
| get: function (target, name) { return returnedValue; } | |
| }); | |
| returnedValue = null; | |
| assert_throws_js(TypeError, function () { customElements.define('element-with-string-prototype', proxy); }, | |
| 'customElements.define must throw when "prototype" property of the constructor is null'); | |
| returnedValue = undefined; | |
| assert_throws_js(TypeError, function () { customElements.define('element-with-string-prototype', proxy); }, | |
| 'customElements.define must throw when "prototype" property of the constructor is undefined'); | |
| returnedValue = 'hello'; | |
| assert_throws_js(TypeError, function () { customElements.define('element-with-string-prototype', proxy); }, | |
| 'customElements.define must throw when "prototype" property of the constructor is a string'); | |
| returnedValue = 1; | |
| assert_throws_js(TypeError, function () { customElements.define('element-with-string-prototype', proxy); }, | |
| 'customElements.define must throw when "prototype" property of the constructor is a number'); | |
| }, 'customElements.define must throw when "prototype" property of the constructor is not an object'); | |
| test(function () { | |
| var constructor = function () {} | |
| var calls = []; | |
| constructor.prototype = new Proxy(constructor.prototype, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| return target[name]; | |
| } | |
| }); | |
| customElements.define('element-with-proxy-prototype', constructor); | |
| assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback', 'adoptedCallback', 'attributeChangedCallback']); | |
| }, 'customElements.define must get callbacks of the constructor prototype'); | |
| test(function () { | |
| var constructor = function () {} | |
| var calls = []; | |
| var err = {name: 'expectedError'} | |
| constructor.prototype = new Proxy(constructor.prototype, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| if (name == 'disconnectedCallback') | |
| throw err; | |
| return target[name]; | |
| } | |
| }); | |
| assert_throws_exactly(err, function () { customElements.define('element-with-throwing-callback', constructor); }); | |
| assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback'], | |
| 'customElements.define must not get callbacks after one of the get throws'); | |
| }, 'customElements.define must rethrow an exception thrown while getting callbacks on the constructor prototype'); | |
| test(function () { | |
| var constructor = function () {} | |
| var calls = []; | |
| constructor.prototype = new Proxy(constructor.prototype, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| if (name == 'adoptedCallback') | |
| return 1; | |
| return target[name]; | |
| } | |
| }); | |
| assert_throws_js(TypeError, function () { customElements.define('element-with-throwing-callback', constructor); }); | |
| assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback', 'adoptedCallback'], | |
| 'customElements.define must not get callbacks after one of the conversion throws'); | |
| }, 'customElements.define must rethrow an exception thrown while converting a callback value to Function callback type'); | |
| test(function () { | |
| var constructor = function () {} | |
| constructor.prototype.attributeChangedCallback = function () { }; | |
| var prototypeCalls = []; | |
| var callOrder = 0; | |
| constructor.prototype = new Proxy(constructor.prototype, { | |
| get: function (target, name) { | |
| if (name == 'prototype' || name == 'observedAttributes') | |
| throw 'Unexpected access to observedAttributes'; | |
| prototypeCalls.push(callOrder++); | |
| prototypeCalls.push(name); | |
| return target[name]; | |
| } | |
| }); | |
| var constructorCalls = []; | |
| var proxy = new Proxy(constructor, { | |
| get: function (target, name) { | |
| constructorCalls.push(callOrder++); | |
| constructorCalls.push(name); | |
| return target[name]; | |
| } | |
| }); | |
| customElements.define('element-with-attribute-changed-callback', proxy); | |
| assert_array_equals(prototypeCalls, [1, 'connectedCallback', 2, 'disconnectedCallback', 3, 'adoptedCallback', 4, 'attributeChangedCallback']); | |
| assert_array_equals(constructorCalls, [0, 'prototype', | |
| 5, 'observedAttributes', | |
| 6, 'disabledFeatures', | |
| 7, 'formAssociated']); | |
| }, 'customElements.define must get "observedAttributes" property on the constructor prototype when "attributeChangedCallback" is present'); | |
| test(function () { | |
| var constructor = function () {} | |
| constructor.prototype.attributeChangedCallback = function () { }; | |
| var calls = []; | |
| var err = {name: 'expectedError'}; | |
| var proxy = new Proxy(constructor, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| if (name == 'observedAttributes') | |
| throw err; | |
| return target[name]; | |
| } | |
| }); | |
| assert_throws_exactly(err, function () { customElements.define('element-with-throwing-observed-attributes', proxy); }); | |
| assert_array_equals(calls, ['prototype', 'observedAttributes'], | |
| 'customElements.define must get "prototype" and "observedAttributes" on the constructor'); | |
| }, 'customElements.define must rethrow an exception thrown while getting observedAttributes on the constructor prototype'); | |
| test(function () { | |
| var constructor = function () {} | |
| constructor.prototype.attributeChangedCallback = function () { }; | |
| var calls = []; | |
| var proxy = new Proxy(constructor, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| if (name == 'observedAttributes') | |
| return 1; | |
| return target[name]; | |
| } | |
| }); | |
| assert_throws_js(TypeError, function () { customElements.define('element-with-invalid-observed-attributes', proxy); }); | |
| assert_array_equals(calls, ['prototype', 'observedAttributes'], | |
| 'customElements.define must get "prototype" and "observedAttributes" on the constructor'); | |
| }, 'customElements.define must rethrow an exception thrown while converting the value of observedAttributes to sequence<DOMString>'); | |
| test(function () { | |
| var err = {name: 'SomeError'}; | |
| var constructor = function () {} | |
| constructor.prototype.attributeChangedCallback = function () { }; | |
| constructor.observedAttributes = {[Symbol.iterator]: function *() { | |
| yield 'foo'; | |
| throw err; | |
| }}; | |
| assert_throws_exactly(err, function () { customElements.define('element-with-generator-observed-attributes', constructor); }); | |
| }, 'customElements.define must rethrow an exception thrown while iterating over observedAttributes to sequence<DOMString>'); | |
| test(function () { | |
| var constructor = function () {} | |
| constructor.prototype.attributeChangedCallback = function () { }; | |
| constructor.observedAttributes = {[Symbol.iterator]: 1}; | |
| assert_throws_js(TypeError, function () { customElements.define('element-with-observed-attributes-with-uncallable-iterator', constructor); }); | |
| }, 'customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on observedAttributes'); | |
| test(function () { | |
| var constructor = function () {} | |
| constructor.observedAttributes = 1; | |
| customElements.define('element-without-callback-with-invalid-observed-attributes', constructor); | |
| }, 'customElements.define must not throw even if "observedAttributes" fails to convert if "attributeChangedCallback" is not defined'); | |
| test(function () { | |
| var constructor = function () {} | |
| var calls = []; | |
| var err = {name: 'expectedError'} | |
| var proxy = new Proxy(constructor, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| if (name == 'disabledFeatures') | |
| throw err; | |
| return target[name]; | |
| } | |
| }); | |
| assert_throws_exactly(err, () => customElements.define('element-with-throwing-disabled-features', proxy)); | |
| assert_array_equals(calls, ['prototype', 'disabledFeatures'], | |
| 'customElements.define must get "prototype" and "disabledFeatures" on the constructor'); | |
| }, 'customElements.define must rethrow an exception thrown while getting disabledFeatures on the constructor prototype'); | |
| test(function () { | |
| var constructor = function () {} | |
| var calls = []; | |
| var proxy = new Proxy(constructor, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| if (name == 'disabledFeatures') | |
| return 1; | |
| return target[name]; | |
| } | |
| }); | |
| assert_throws_js(TypeError, () => customElements.define('element-with-invalid-disabled-features', proxy)); | |
| assert_array_equals(calls, ['prototype', 'disabledFeatures'], | |
| 'customElements.define must get "prototype" and "disabledFeatures" on the constructor'); | |
| }, 'customElements.define must rethrow an exception thrown while converting the value of disabledFeatures to sequence<DOMString>'); | |
| test(function () { | |
| var constructor = function () {} | |
| var err = {name: 'SomeError'}; | |
| constructor.disabledFeatures = {[Symbol.iterator]: function *() { | |
| yield 'foo'; | |
| throw err; | |
| }}; | |
| assert_throws_exactly(err, () => customElements.define('element-with-generator-disabled-features', constructor)); | |
| }, 'customElements.define must rethrow an exception thrown while iterating over disabledFeatures to sequence<DOMString>'); | |
| test(function () { | |
| var constructor = function () {} | |
| constructor.disabledFeatures = {[Symbol.iterator]: 1}; | |
| assert_throws_js(TypeError, () => customElements.define('element-with-disabled-features-with-uncallable-iterator', constructor)); | |
| }, 'customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on disabledFeatures'); | |
| test(function () { | |
| var constructor = function () {} | |
| var calls = []; | |
| var err = {name: 'expectedError'}; | |
| var proxy = new Proxy(constructor, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| if (name == 'formAssociated') | |
| throw err; | |
| return target[name]; | |
| } | |
| }); | |
| assert_throws_exactly(err, | |
| () => customElements.define('element-with-throwing-form-associated', proxy)); | |
| assert_array_equals(calls, ['prototype', 'disabledFeatures', 'formAssociated'], | |
| 'customElements.define must get "prototype", "disabledFeatures", and ' + | |
| '"formAssociated" on the constructor'); | |
| }, 'customElements.define must rethrow an exception thrown while getting ' + | |
| 'formAssociated on the constructor prototype'); | |
| test(function () { | |
| var constructor = function () {} | |
| var prototypeCalls = []; | |
| constructor.prototype = new Proxy(constructor.prototype, { | |
| get: function(target, name) { | |
| prototypeCalls.push(name) | |
| return target[name]; | |
| } | |
| }); | |
| var constructorCalls = []; | |
| var proxy = new Proxy(constructor, { | |
| get: function (target, name) { | |
| constructorCalls.push(name); | |
| if (name == 'formAssociated') | |
| return 1; | |
| return target[name]; | |
| } | |
| }); | |
| customElements.define('element-with-form-associated-true', proxy); | |
| assert_array_equals(constructorCalls, | |
| ['prototype', 'disabledFeatures', 'formAssociated'], | |
| 'customElements.define must get "prototype", "disabledFeatures", and ' + | |
| '"formAssociated" on the constructor'); | |
| assert_array_equals( | |
| prototypeCalls, | |
| ['connectedCallback', 'disconnectedCallback', 'adoptedCallback', | |
| 'attributeChangedCallback', 'formAssociatedCallback', | |
| 'formResetCallback', 'formDisabledCallback', | |
| 'formStateRestoreCallback'], | |
| 'customElements.define must get 8 callbacks on the prototype'); | |
| }, 'customElements.define must get four additional callbacks on the prototype' + | |
| ' if formAssociated is converted to true'); | |
| test(function () { | |
| var constructor = function() {}; | |
| var proxy = new Proxy(constructor, { | |
| get: function(target, name) { | |
| if (name == 'formAssociated') | |
| return {}; // Any object is converted to 'true'. | |
| return target[name]; | |
| } | |
| }); | |
| var calls = []; | |
| var err = {name: 'expectedError'}; | |
| constructor.prototype = new Proxy(constructor.prototype, { | |
| get: function (target, name) { | |
| calls.push(name); | |
| if (name == 'formDisabledCallback') | |
| throw err; | |
| return target[name]; | |
| } | |
| }); | |
| assert_throws_exactly(err, | |
| () => customElements.define('element-with-throwing-callback-2', proxy)); | |
| assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback', | |
| 'adoptedCallback', 'attributeChangedCallback', | |
| 'formAssociatedCallback', 'formResetCallback', | |
| 'formDisabledCallback'], | |
| 'customElements.define must not get callbacks after one of the get throws'); | |
| var calls2 = []; | |
| constructor.prototype = new Proxy(constructor.prototype, { | |
| get: function (target, name) { | |
| calls2.push(name); | |
| if (name == 'formResetCallback') | |
| return 43; // Can't convert to a Function. | |
| return target[name]; | |
| } | |
| }); | |
| assert_throws_js(TypeError, | |
| () => customElements.define('element-with-throwing-callback-3', proxy)); | |
| assert_array_equals(calls2, ['connectedCallback', 'disconnectedCallback', | |
| 'adoptedCallback', 'attributeChangedCallback', | |
| 'formAssociatedCallback', 'formResetCallback'], | |
| 'customElements.define must not get callbacks after one of the get throws'); | |
| }, 'customElements.define must rethrow an exception thrown while getting ' + | |
| 'additional formAssociated callbacks on the constructor prototype'); | |
| test(function () { | |
| class MyCustomElement extends HTMLElement {}; | |
| customElements.define('my-custom-element', MyCustomElement); | |
| var instance = new MyCustomElement; | |
| assert_true(instance instanceof MyCustomElement, | |
| 'An instance of a custom HTML element be an instance of the associated interface'); | |
| assert_true(instance instanceof HTMLElement, | |
| 'An instance of a custom HTML element must inherit from HTMLElement'); | |
| assert_equals(instance.localName, 'my-custom-element', | |
| 'An instance of a custom element must use the associated tag name'); | |
| assert_equals(instance.namespaceURI, 'http://www.w3.org/1999/xhtml', | |
| 'A custom element HTML must use HTML namespace'); | |
| }, '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); | |
| }, '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); | |
| assert_equals(customElements.get('span'), undefined); | |
| assert_equals(customElements.get('div'), undefined); | |
| assert_equals(customElements.get('g'), undefined); | |
| assert_equals(customElements.get('ab'), undefined); | |
| }, '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); | |
| }, '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); | |
| }, 'customElements.whenDefined must return a promise for a valid custom element name'); | |
| test(function () { | |
| assert_equals(customElements.whenDefined('some-name'), customElements.whenDefined('some-name')); | |
| }, '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; | |
| var rejected = false; | |
| customElements.whenDefined('a-b').then(function () { resolved = true; }, function () { rejected = true; }); | |
| return Promise.resolve().then(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'); | |
| }); | |
| }, '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'); | |
| promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; }); | |
| assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); | |
| assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); | |
| return Promise.resolve().then(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'); | |
| }); | |
| }, 'customElements.whenDefined must return a rejected promise when the given name is not a valid custom element name'); | |
| promise_test(function () { | |
| class PreexistingCustomElement extends HTMLElement { }; | |
| customElements.define('preexisting-custom-element', PreexistingCustomElement); | |
| var promise = customElements.whenDefined('preexisting-custom-element'); | |
| promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; }); | |
| assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); | |
| assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); | |
| return Promise.resolve().then(function () { | |
| assert_true('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined'); | |
| assert_equals(promise.resolved, PreexistingCustomElement, | |
| 'The promise returned by "whenDefined" must be resolved with the constructor of the element 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'); | |
| }); | |
| }, 'customElements.whenDefined must return a resolved promise when the registry contains the entry with the given name'); | |
| promise_test(function () { | |
| class AnotherExistingCustomElement extends HTMLElement {}; | |
| customElements.define('another-existing-custom-element', AnotherExistingCustomElement); | |
| var promise1 = customElements.whenDefined('another-existing-custom-element'); | |
| var promise2 = customElements.whenDefined('another-existing-custom-element'); | |
| promise1.then(function (value) { promise1.resolved = value; }, function (value) { promise1.rejected = value; }); | |
| promise2.then(function (value) { promise2.resolved = value; }, function (value) { promise2.rejected = value; }); | |
| assert_not_equals(promise1, promise2); | |
| assert_false('resolved' in promise1, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); | |
| assert_false('resolved' in promise2, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); | |
| assert_false('rejected' in promise1, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); | |
| assert_false('rejected' in promise2, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); | |
| return Promise.resolve().then(function () { | |
| assert_true('resolved' in promise1, 'The promise returned by "whenDefined" must be resolved when a custom element is defined'); | |
| assert_equals(promise1.resolved, AnotherExistingCustomElement, 'The promise returned by "whenDefined" must be resolved with the constructor of the element when a custom element is defined'); | |
| assert_false('rejected' in promise1, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined'); | |
| assert_true('resolved' in promise2, 'The promise returned by "whenDefined" must be resolved when a custom element is defined'); | |
| assert_equals(promise2.resolved, AnotherExistingCustomElement, 'The promise returned by "whenDefined" must be resolved with the constructor of the element 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'); | |
| }); | |
| }, 'customElements.whenDefined must return a new resolved promise each time invoked when the registry contains the entry with the given name'); | |
| promise_test(function () { | |
| class ElementDefinedAfterWhenDefined extends HTMLElement { }; | |
| var promise = customElements.whenDefined('element-defined-after-whendefined'); | |
| promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; }); | |
| assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); | |
| assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); | |
| var promiseAfterDefine; | |
| return Promise.resolve().then(function () { | |
| assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the element is defined'); | |
| assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the element is defined'); | |
| assert_equals(customElements.whenDefined('element-defined-after-whendefined'), promise, | |
| '"whenDefined" must return the same unresolved promise before the custom element is defined'); | |
| customElements.define('element-defined-after-whendefined', ElementDefinedAfterWhenDefined); | |
| assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); | |
| assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); | |
| promiseAfterDefine = customElements.whenDefined('element-defined-after-whendefined'); | |
| promiseAfterDefine.then(function (value) { promiseAfterDefine.resolved = value; }, function (value) { promiseAfterDefine.rejected = value; }); | |
| assert_not_equals(promiseAfterDefine, promise, '"whenDefined" must return a resolved promise once the custom element is defined'); | |
| assert_false('resolved' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); | |
| assert_false('rejected' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); | |
| }).then(function () { | |
| assert_true('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined'); | |
| assert_equals(promise.resolved, ElementDefinedAfterWhenDefined, | |
| 'The promise returned by "whenDefined" must be resolved with the constructor of the element 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'); | |
| assert_true('resolved' in promiseAfterDefine, 'The promise returned by "whenDefined" must be resolved when a custom element is defined'); | |
| assert_equals(promiseAfterDefine.resolved, ElementDefinedAfterWhenDefined, | |
| 'The promise returned by "whenDefined" must be resolved with the constructor of the element 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 customElements.whenDefined must be resolved by "define"'); | |
| promise_test(function () { | |
| class ResolvedCustomElement extends HTMLElement {}; | |
| customElements.define('resolved-custom-element', ResolvedCustomElement); | |
| return customElements.whenDefined('resolved-custom-element').then(function (value) { | |
| assert_equals(value, ResolvedCustomElement, 'The promise returned by "whenDefined" must resolve with the defined class'); | |
| }); | |
| }, 'A promise returned by customElements.whenDefined must be resolved with the defined class'); | |
| promise_test(function () { | |
| var promise = customElements.whenDefined('not-resolved-yet-custom-element').then(function (value) { | |
| assert_equals(value, NotResolvedYetCustomElement, 'The promise returned by "whenDefined" must resolve with the defined class once such class is defined'); | |
| }); | |
| class NotResolvedYetCustomElement extends HTMLElement {}; | |
| customElements.define('not-resolved-yet-custom-element', NotResolvedYetCustomElement); | |
| return promise; | |
| }, 'A promise returned by customElements.whenDefined must be resolved with the defined class once such class is defined'); | |
| </script> | |
| </body> | |
| </html> |