tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CustomElementRegistry.html (40855B)


      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4 <title>Custom Elements: CustomElementRegistry interface</title>
      5 <meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
      6 <meta name="assert" content="CustomElementRegistry interface must exist">
      7 <script src="/resources/testharness.js"></script>
      8 <script src="/resources/testharnessreport.js"></script>
      9 </head>
     10 <body>
     11 <div id="log"></div>
     12 <script>
     13 
     14 const moveBefore_supported = ("moveBefore" in Element.prototype);
     15 
     16 test(function () {
     17    assert_true('define' in CustomElementRegistry.prototype, '"define" exists on CustomElementRegistry.prototype');
     18    assert_true('define' in customElements, '"define" exists on window.customElements');
     19 }, 'CustomElementRegistry interface must have define as a method');
     20 
     21 test(function () {
     22    assert_throws_js(TypeError, function () { customElements.define('badname', 1); },
     23        'customElements.define must throw a TypeError when the element interface is a number');
     24    assert_throws_js(TypeError, function () { customElements.define('badname', '123'); },
     25        'customElements.define must throw a TypeError when the element interface is a string');
     26    assert_throws_js(TypeError, function () { customElements.define('badname', {}); },
     27        'customElements.define must throw a TypeError when the element interface is an object');
     28    assert_throws_js(TypeError, function () { customElements.define('badname', []); },
     29        'customElements.define must throw a TypeError when the element interface is an array');
     30 }, 'customElements.define must throw when the element interface is not a constructor');
     31 
     32 test(function () {
     33    customElements.define('custom-html-element', HTMLElement);
     34 }, 'customElements.define must not throw the constructor is HTMLElement');
     35 
     36 test(function () {
     37    class MyCustomElement extends HTMLElement {};
     38 
     39    assert_throws_dom('SyntaxError', function () { customElements.define(null, MyCustomElement); },
     40        'customElements.define must throw a SyntaxError if the tag name is null');
     41    assert_throws_dom('SyntaxError', function () { customElements.define('', MyCustomElement); },
     42        'customElements.define must throw a SyntaxError if the tag name is empty');
     43    assert_throws_dom('SyntaxError', function () { customElements.define('abc', MyCustomElement); },
     44        'customElements.define must throw a SyntaxError if the tag name does not contain "-"');
     45    assert_throws_dom('SyntaxError', function () { customElements.define('a-Bc', MyCustomElement); },
     46        'customElements.define must throw a SyntaxError if the tag name contains an upper case letter');
     47 
     48    var builtinTagNames = [
     49        'annotation-xml',
     50        'color-profile',
     51        'font-face',
     52        'font-face-src',
     53        'font-face-uri',
     54        'font-face-format',
     55        'font-face-name',
     56        'missing-glyph'
     57    ];
     58 
     59    for (var tagName of builtinTagNames) {
     60        assert_throws_dom('SyntaxError', function () { customElements.define(tagName, MyCustomElement); },
     61            'customElements.define must throw a SyntaxError if the tag name is "' + tagName + '"');
     62    }
     63 
     64 }, 'customElements.define must throw with an invalid name');
     65 
     66 test(function () {
     67    class SomeCustomElement extends HTMLElement {};
     68 
     69    var calls = [];
     70    var OtherCustomElement = new Proxy(class extends HTMLElement {}, {
     71        get: function (target, name) {
     72            calls.push(name);
     73            return target[name];
     74        }
     75    })
     76 
     77    customElements.define('some-custom-element', SomeCustomElement);
     78    assert_throws_dom('NotSupportedError', function () { customElements.define('some-custom-element', OtherCustomElement); },
     79        'customElements.define must throw a NotSupportedError if the specified tag name is already used');
     80    assert_array_equals(calls, [], 'customElements.define must validate the custom element name before getting the prototype of the constructor');
     81 
     82 }, 'customElements.define must throw when there is already a custom element of the same name');
     83 
     84 test(function () {
     85    class AnotherCustomElement extends HTMLElement {};
     86 
     87    customElements.define('another-custom-element', AnotherCustomElement);
     88    assert_throws_dom('NotSupportedError', function () { customElements.define('some-other-element', AnotherCustomElement); },
     89        'customElements.define must throw a NotSupportedError if the specified class already defines an element');
     90 
     91 }, 'customElements.define must throw a NotSupportedError when there is already a custom element with the same class');
     92 
     93 test(function () {
     94    var outerCalls = [];
     95    var OuterCustomElement = new Proxy(class extends HTMLElement { }, {
     96        get: function (target, name) {
     97            outerCalls.push(name);
     98            customElements.define('inner-custom-element', InnerCustomElement);
     99            return target[name];
    100        }
    101    });
    102    var innerCalls = [];
    103    var InnerCustomElement = new Proxy(class extends HTMLElement { }, {
    104        get: function (target, name) {
    105            outerCalls.push(name);
    106            return target[name];
    107        }
    108    });
    109 
    110    assert_throws_dom('NotSupportedError', function () { customElements.define('outer-custom-element', OuterCustomElement); },
    111        'customElements.define must throw a NotSupportedError if the specified class already defines an element');
    112    assert_array_equals(outerCalls, ['prototype'], 'customElements.define must get "prototype"');
    113    assert_array_equals(innerCalls, [],
    114        'customElements.define must throw a NotSupportedError when element definition is running flag is set'
    115        + ' before getting the prototype of the constructor');
    116 
    117 }, 'customElements.define must throw a NotSupportedError when element definition is running flag is set');
    118 
    119 test(function () {
    120    var calls = [];
    121    var ElementWithBadInnerConstructor = new Proxy(class extends HTMLElement { }, {
    122        get: function (target, name) {
    123            calls.push(name);
    124            customElements.define('inner-custom-element', 1);
    125            return target[name];
    126        }
    127    });
    128 
    129    assert_throws_js(TypeError, function () {
    130        customElements.define('element-with-bad-inner-constructor', ElementWithBadInnerConstructor);
    131    }, 'customElements.define must throw a NotSupportedError if IsConstructor(constructor) is false');
    132 
    133    assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"');
    134 }, 'customElements.define must check IsConstructor on the constructor before checking the element definition is running flag');
    135 
    136 test(function () {
    137    var calls = [];
    138    var ElementWithBadInnerName = new Proxy(class extends HTMLElement { }, {
    139        get: function (target, name) {
    140            calls.push(name);
    141            customElements.define('badname', class extends HTMLElement {});
    142            return target[name];
    143        }
    144    });
    145 
    146    assert_throws_dom('SyntaxError', function () {
    147        customElements.define('element-with-bad-inner-name', ElementWithBadInnerName);
    148    }, 'customElements.define must throw a SyntaxError if the specified name is not a valid custom element name');
    149 
    150    assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"');
    151 }, 'customElements.define must validate the custom element name before checking the element definition is running flag');
    152 
    153 test(function () {
    154    var unresolvedElement = document.createElement('constructor-calls-define');
    155    document.body.appendChild(unresolvedElement);
    156    var elementUpgradedDuringUpgrade = document.createElement('defined-during-upgrade');
    157    document.body.appendChild(elementUpgradedDuringUpgrade);
    158 
    159    var DefinedDuringUpgrade = class extends HTMLElement { };
    160 
    161    class ConstructorCallsDefine extends HTMLElement {
    162        constructor() {
    163            customElements.define('defined-during-upgrade', DefinedDuringUpgrade);
    164            assert_false(unresolvedElement instanceof ConstructorCallsDefine);
    165            assert_true(elementUpgradedDuringUpgrade instanceof DefinedDuringUpgrade);
    166            super();
    167            assert_true(unresolvedElement instanceof ConstructorCallsDefine);
    168            assert_true(elementUpgradedDuringUpgrade instanceof DefinedDuringUpgrade);
    169        }
    170    }
    171 
    172    assert_false(unresolvedElement instanceof ConstructorCallsDefine);
    173    assert_false(elementUpgradedDuringUpgrade instanceof DefinedDuringUpgrade);
    174 
    175    customElements.define('constructor-calls-define', ConstructorCallsDefine);
    176 }, 'customElements.define unset the element definition is running flag before upgrading custom elements');
    177 
    178 (function () {
    179    var testCase = async_test('customElements.define must not throw'
    180        +' when defining another custom element in a different global object during Get(constructor, "prototype")');
    181 
    182    var iframe = document.createElement('iframe');
    183    iframe.onload = function () {
    184        testCase.step(function () {
    185            var InnerCustomElement = class extends iframe.contentWindow.HTMLElement {};
    186            var calls = [];
    187            var proxy = new Proxy(class extends HTMLElement { }, {
    188                get: function (target, name) {
    189                    calls.push(name);
    190                    if (name === "prototype") {
    191                        iframe.contentWindow.customElements.define('another-custom-element', InnerCustomElement);
    192                    }
    193                    return target[name];
    194                }
    195            })
    196            customElements.define('element-with-inner-element-define', proxy);
    197            assert_array_equals(calls, ['prototype', 'disabledFeatures', 'formAssociated'],
    198                                'customElements.define must get "prototype", "disabledFeatures", and "formAssociated" on the constructor');
    199            assert_true(iframe.contentDocument.createElement('another-custom-element') instanceof InnerCustomElement);
    200        });
    201        document.body.removeChild(iframe);
    202        testCase.done();
    203    }
    204 
    205    document.body.appendChild(iframe);
    206 })();
    207 
    208 test(function () {
    209    var calls = [];
    210    var ElementWithBadInnerName = new Proxy(class extends HTMLElement { }, {
    211        get: function (target, name) {
    212            calls.push(name);
    213            customElements.define('badname', class extends HTMLElement {});
    214            return target[name];
    215        }
    216    });
    217 
    218    assert_throws_dom('SyntaxError', function () {
    219        customElements.define('element-with-bad-inner-name', ElementWithBadInnerName);
    220    }, 'customElements.define must throw a SyntaxError if the specified name is not a valid custom element name');
    221 
    222    assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"');
    223 }, '');
    224 
    225 test(function () {
    226    var calls = [];
    227    var proxy = new Proxy(class extends HTMLElement { }, {
    228        get: function (target, name) {
    229            calls.push(name);
    230            return target[name];
    231        }
    232    });
    233    customElements.define('proxy-element', proxy);
    234    assert_array_equals(calls, ['prototype', 'disabledFeatures', 'formAssociated']);
    235 }, 'customElements.define must get "prototype", "disabledFeatures", and "formAssociated" property of the constructor');
    236 
    237 test(function () {
    238    var err = {name: 'expectedError'}
    239    var proxy = new Proxy(class extends HTMLElement { }, {
    240        get: function (target, name) {
    241            throw err;
    242        }
    243    });
    244    assert_throws_exactly(err, function () { customElements.define('element-with-string-prototype', proxy); });
    245 }, 'customElements.define must rethrow an exception thrown while getting "prototype" property of the constructor');
    246 
    247 test(function () {
    248    var returnedValue;
    249    var proxy = new Proxy(class extends HTMLElement { }, {
    250        get: function (target, name) { return returnedValue; }
    251    });
    252 
    253    returnedValue = null;
    254    assert_throws_js(TypeError, function () { customElements.define('element-with-string-prototype', proxy); },
    255        'customElements.define must throw when "prototype" property of the constructor is null');
    256    returnedValue = undefined;
    257    assert_throws_js(TypeError, function () { customElements.define('element-with-string-prototype', proxy); },
    258        'customElements.define must throw when "prototype" property of the constructor is undefined');
    259    returnedValue = 'hello';
    260    assert_throws_js(TypeError, function () { customElements.define('element-with-string-prototype', proxy); },
    261        'customElements.define must throw when "prototype" property of the constructor is a string');
    262    returnedValue = 1;
    263    assert_throws_js(TypeError, function () { customElements.define('element-with-string-prototype', proxy); },
    264        'customElements.define must throw when "prototype" property of the constructor is a number');
    265 
    266 }, 'customElements.define must throw when "prototype" property of the constructor is not an object');
    267 
    268 test(function () {
    269    var constructor = function () {}
    270    var calls = [];
    271    constructor.prototype = new Proxy(constructor.prototype, {
    272        get: function (target, name) {
    273            calls.push(name);
    274            return target[name];
    275        }
    276    });
    277    customElements.define('element-with-proxy-prototype', constructor);
    278    assert_array_equals(calls,
    279        moveBefore_supported ? ['connectedCallback', 'disconnectedCallback', 'connectedMoveCallback', 'adoptedCallback', 'attributeChangedCallback'] :
    280        ['connectedCallback', 'disconnectedCallback', 'adoptedCallback', 'attributeChangedCallback']);
    281 }, 'customElements.define must get callbacks of the constructor prototype');
    282 
    283 test(function () {
    284    var constructor = function () {}
    285    var calls = [];
    286    var err = {name: 'expectedError'}
    287    constructor.prototype = new Proxy(constructor.prototype, {
    288        get: function (target, name) {
    289            calls.push(name);
    290            if (name == 'disconnectedCallback')
    291                throw err;
    292            return target[name];
    293        }
    294    });
    295    assert_throws_exactly(err, function () { customElements.define('element-with-throwing-callback', constructor); });
    296    assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback'],
    297        'customElements.define must not get callbacks after one of the get throws');
    298 }, 'customElements.define must rethrow an exception thrown while getting callbacks on the constructor prototype');
    299 
    300 test(function () {
    301    var constructor = function () {}
    302    var calls = [];
    303    constructor.prototype = new Proxy(constructor.prototype, {
    304        get: function (target, name) {
    305            calls.push(name);
    306            if (name == 'adoptedCallback')
    307                return 1;
    308            return target[name];
    309        }
    310    });
    311    assert_throws_js(TypeError, function () { customElements.define('element-with-throwing-callback', constructor); });
    312    assert_array_equals(calls,
    313        moveBefore_supported ?
    314            ['connectedCallback', 'disconnectedCallback', 'connectedMoveCallback', 'adoptedCallback'] :
    315            ['connectedCallback', 'disconnectedCallback', 'adoptedCallback'],
    316        'customElements.define must not get callbacks after one of the conversion throws');
    317 }, 'customElements.define must rethrow an exception thrown while converting a callback value to Function callback type');
    318 
    319 test(function () {
    320    var constructor = function () {}
    321    constructor.prototype.attributeChangedCallback = function () { };
    322    var prototypeCalls = [];
    323    var callOrder = 0;
    324    constructor.prototype = new Proxy(constructor.prototype, {
    325        get: function (target, name) {
    326            if (name == 'prototype' || name == 'observedAttributes')
    327                throw 'Unexpected access to observedAttributes';
    328            prototypeCalls.push(callOrder++);
    329            prototypeCalls.push(name);
    330            return target[name];
    331        }
    332    });
    333    var constructorCalls = [];
    334    var proxy = new Proxy(constructor, {
    335        get: function (target, name) {
    336            constructorCalls.push(callOrder++);
    337            constructorCalls.push(name);
    338            return target[name];
    339        }
    340    });
    341    customElements.define('element-with-attribute-changed-callback', proxy);
    342    assert_array_equals(prototypeCalls,
    343        moveBefore_supported ?
    344            [1, 'connectedCallback', 2, 'disconnectedCallback', 3, 'connectedMoveCallback', 4, 'adoptedCallback', 5, 'attributeChangedCallback'] :
    345            [1, 'connectedCallback', 2, 'disconnectedCallback', 3, 'adoptedCallback', 4, 'attributeChangedCallback']);
    346    assert_array_equals(constructorCalls, [0, 'prototype',
    347                                           6, 'observedAttributes',
    348                                           7, 'disabledFeatures',
    349                                           8, 'formAssociated']);
    350 }, 'customElements.define must get "observedAttributes" property on the constructor prototype when "attributeChangedCallback" is present');
    351 
    352 test(function () {
    353    var constructor = function () {}
    354    constructor.prototype.attributeChangedCallback = function () { };
    355    var calls = [];
    356    var err = {name: 'expectedError'};
    357    var proxy = new Proxy(constructor, {
    358        get: function (target, name) {
    359            calls.push(name);
    360            if (name == 'observedAttributes')
    361                throw err;
    362            return target[name];
    363        }
    364    });
    365    assert_throws_exactly(err, function () { customElements.define('element-with-throwing-observed-attributes', proxy); });
    366    assert_array_equals(calls, ['prototype', 'observedAttributes'],
    367        'customElements.define must get "prototype" and "observedAttributes" on the constructor');
    368 }, 'customElements.define must rethrow an exception thrown while getting observedAttributes on the constructor prototype');
    369 
    370 test(function () {
    371    var constructor = function () {}
    372    constructor.prototype.attributeChangedCallback = function () { };
    373    var calls = [];
    374    var proxy = new Proxy(constructor, {
    375        get: function (target, name) {
    376            calls.push(name);
    377            if (name == 'observedAttributes')
    378                return 1;
    379            return target[name];
    380        }
    381    });
    382    assert_throws_js(TypeError, function () { customElements.define('element-with-invalid-observed-attributes', proxy); });
    383    assert_array_equals(calls, ['prototype', 'observedAttributes'],
    384        'customElements.define must get "prototype" and "observedAttributes" on the constructor');
    385 }, 'customElements.define must rethrow an exception thrown while converting the value of observedAttributes to sequence<DOMString>');
    386 
    387  test(function () {
    388    var err = {name: 'SomeError'};
    389    var constructor = function () {}
    390    constructor.prototype.attributeChangedCallback = function () { };
    391    constructor.observedAttributes = {[Symbol.iterator]: function *() {
    392        yield 'foo';
    393        throw err;
    394    }};
    395    assert_throws_exactly(err, function () { customElements.define('element-with-generator-observed-attributes', constructor); });
    396 }, 'customElements.define must rethrow an exception thrown while iterating over observedAttributes to sequence<DOMString>');
    397 
    398 test(function () {
    399    var constructor = function () {}
    400    constructor.prototype.attributeChangedCallback = function () { };
    401    constructor.observedAttributes = {[Symbol.iterator]: 1};
    402    assert_throws_js(TypeError, function () { customElements.define('element-with-observed-attributes-with-uncallable-iterator', constructor); });
    403 }, 'customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on observedAttributes');
    404 
    405 test(function () {
    406    var constructor = function () {}
    407    constructor.observedAttributes = 1;
    408    customElements.define('element-without-callback-with-invalid-observed-attributes', constructor);
    409 }, 'customElements.define must not throw even if "observedAttributes" fails to convert if "attributeChangedCallback" is not defined');
    410 
    411 test(function () {
    412    var constructor = function () {}
    413    var calls = [];
    414    var err = {name: 'expectedError'}
    415    var proxy = new Proxy(constructor, {
    416        get: function (target, name) {
    417            calls.push(name);
    418            if (name == 'disabledFeatures')
    419                throw err;
    420            return target[name];
    421        }
    422    });
    423    assert_throws_exactly(err, () => customElements.define('element-with-throwing-disabled-features', proxy));
    424    assert_array_equals(calls, ['prototype', 'disabledFeatures'],
    425        'customElements.define must get "prototype" and "disabledFeatures" on the constructor');
    426 }, 'customElements.define must rethrow an exception thrown while getting disabledFeatures on the constructor prototype');
    427 
    428 test(function () {
    429    var constructor = function () {}
    430    var calls = [];
    431    var proxy = new Proxy(constructor, {
    432        get: function (target, name) {
    433            calls.push(name);
    434            if (name == 'disabledFeatures')
    435                return 1;
    436            return target[name];
    437        }
    438    });
    439    assert_throws_js(TypeError, () => customElements.define('element-with-invalid-disabled-features', proxy));
    440    assert_array_equals(calls, ['prototype', 'disabledFeatures'],
    441        'customElements.define must get "prototype" and "disabledFeatures" on the constructor');
    442 }, 'customElements.define must rethrow an exception thrown while converting the value of disabledFeatures to sequence<DOMString>');
    443 
    444 test(function () {
    445    var constructor = function () {}
    446    var err = {name: 'SomeError'};
    447    constructor.disabledFeatures = {[Symbol.iterator]: function *() {
    448        yield 'foo';
    449        throw err;
    450    }};
    451    assert_throws_exactly(err, () => customElements.define('element-with-generator-disabled-features', constructor));
    452 }, 'customElements.define must rethrow an exception thrown while iterating over disabledFeatures to sequence<DOMString>');
    453 
    454 test(function () {
    455    var constructor = function () {}
    456    constructor.disabledFeatures = {[Symbol.iterator]: 1};
    457    assert_throws_js(TypeError, () => customElements.define('element-with-disabled-features-with-uncallable-iterator', constructor));
    458 }, 'customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on disabledFeatures');
    459 
    460 test(function () {
    461    var constructor = function () {}
    462    var calls = [];
    463    var err = {name: 'expectedError'};
    464    var proxy = new Proxy(constructor, {
    465        get: function (target, name) {
    466            calls.push(name);
    467            if (name == 'formAssociated')
    468                throw err;
    469            return target[name];
    470        }
    471    });
    472    assert_throws_exactly(err,
    473        () => customElements.define('element-with-throwing-form-associated', proxy));
    474    assert_array_equals(calls, ['prototype', 'disabledFeatures', 'formAssociated'],
    475        'customElements.define must get "prototype", "disabledFeatures", and ' +
    476        '"formAssociated" on the constructor');
    477 }, 'customElements.define must rethrow an exception thrown while getting ' +
    478   'formAssociated on the constructor prototype');
    479 
    480 test(function () {
    481    var constructor = function () {}
    482    var prototypeCalls = [];
    483    constructor.prototype = new Proxy(constructor.prototype, {
    484        get: function(target, name) {
    485            prototypeCalls.push(name)
    486            return target[name];
    487        }
    488    });
    489    var constructorCalls = [];
    490    var proxy = new Proxy(constructor, {
    491        get: function (target, name) {
    492            constructorCalls.push(name);
    493            if (name == 'formAssociated')
    494                return 1;
    495            return target[name];
    496        }
    497    });
    498    customElements.define('element-with-form-associated-true', proxy);
    499    assert_array_equals(constructorCalls,
    500        ['prototype', 'disabledFeatures', 'formAssociated'],
    501        'customElements.define must get "prototype", "disabledFeatures", and ' +
    502        '"formAssociated" on the constructor');
    503    assert_array_equals(
    504        prototypeCalls,
    505        moveBefore_supported ? ['connectedCallback', 'disconnectedCallback', 'connectedMoveCallback', 'adoptedCallback',
    506         'attributeChangedCallback', 'formAssociatedCallback',
    507         'formResetCallback', 'formDisabledCallback',
    508         'formStateRestoreCallback'] :
    509        ['connectedCallback', 'disconnectedCallback', 'adoptedCallback',
    510         'attributeChangedCallback', 'formAssociatedCallback',
    511         'formResetCallback', 'formDisabledCallback',
    512         'formStateRestoreCallback'],
    513        'customElements.define must get 8 callbacks on the prototype');
    514 }, 'customElements.define must get four additional callbacks on the prototype' +
    515   ' if formAssociated is converted to true');
    516 
    517 test(function () {
    518    var constructor = function() {};
    519    var proxy = new Proxy(constructor, {
    520        get: function(target, name) {
    521            if (name == 'formAssociated')
    522                return {};  // Any object is converted to 'true'.
    523            return target[name];
    524        }
    525    });
    526    var calls = [];
    527    var err = {name: 'expectedError'};
    528    constructor.prototype = new Proxy(constructor.prototype, {
    529        get: function (target, name) {
    530            calls.push(name);
    531            if (name == 'formDisabledCallback')
    532                throw err;
    533            return target[name];
    534        }
    535    });
    536    assert_throws_exactly(err,
    537        () => customElements.define('element-with-throwing-callback-2', proxy));
    538    assert_array_equals(calls, moveBefore_supported ? ['connectedCallback', 'disconnectedCallback', 'connectedMoveCallback',
    539                                'adoptedCallback', 'attributeChangedCallback',
    540                                'formAssociatedCallback', 'formResetCallback',
    541                                'formDisabledCallback'] : ['connectedCallback', 'disconnectedCallback',
    542                                'adoptedCallback', 'attributeChangedCallback',
    543                                'formAssociatedCallback', 'formResetCallback',
    544                                'formDisabledCallback'],
    545        'customElements.define must not get callbacks after one of the get throws');
    546 
    547    var calls2 = [];
    548    constructor.prototype = new Proxy(constructor.prototype, {
    549        get: function (target, name) {
    550            calls2.push(name);
    551            if (name == 'formResetCallback')
    552                return 43;  // Can't convert to a Function.
    553            return target[name];
    554        }
    555    });
    556    assert_throws_js(TypeError,
    557        () => customElements.define('element-with-throwing-callback-3', proxy));
    558    assert_array_equals(calls2, moveBefore_supported ? ['connectedCallback', 'disconnectedCallback', 'connectedMoveCallback',
    559                                 'adoptedCallback', 'attributeChangedCallback',
    560                                 'formAssociatedCallback', 'formResetCallback'] : ['connectedCallback', 'disconnectedCallback',
    561                                 'adoptedCallback', 'attributeChangedCallback',
    562                                 'formAssociatedCallback', 'formResetCallback'],
    563        'customElements.define must not get callbacks after one of the get throws');
    564 }, 'customElements.define must rethrow an exception thrown while getting ' +
    565   'additional formAssociated callbacks on the constructor prototype');
    566 
    567 test(function () {
    568    class MyCustomElement extends HTMLElement {};
    569    customElements.define('my-custom-element', MyCustomElement);
    570 
    571    var instance = new MyCustomElement;
    572    assert_true(instance instanceof MyCustomElement,
    573        'An instance of a custom HTML element be an instance of the associated interface');
    574 
    575    assert_true(instance instanceof HTMLElement,
    576        'An instance of a custom HTML element must inherit from HTMLElement');
    577 
    578    assert_equals(instance.localName, 'my-custom-element',
    579        'An instance of a custom element must use the associated tag name');
    580 
    581    assert_equals(instance.namespaceURI, 'http://www.w3.org/1999/xhtml',
    582        'A custom element HTML must use HTML namespace');
    583 
    584 }, 'customElements.define must define an instantiatable custom element');
    585 
    586 test(function () {
    587    var disconnectedElement = document.createElement('some-custom');
    588    var connectedElementBeforeShadowHost = document.createElement('some-custom');
    589    var connectedElementAfterShadowHost = document.createElement('some-custom');
    590    var elementInShadowTree = document.createElement('some-custom');
    591    var childElementOfShadowHost = document.createElement('some-custom');
    592    var customShadowHost = document.createElement('some-custom');
    593    var elementInNestedShadowTree = document.createElement('some-custom');
    594 
    595    var container = document.createElement('div');
    596    var shadowHost = document.createElement('div');
    597    var shadowRoot = shadowHost.attachShadow({mode: 'closed'});
    598    container.appendChild(connectedElementBeforeShadowHost);
    599    container.appendChild(shadowHost);
    600    container.appendChild(connectedElementAfterShadowHost);
    601    shadowHost.appendChild(childElementOfShadowHost);
    602    shadowRoot.appendChild(elementInShadowTree);
    603    shadowRoot.appendChild(customShadowHost);
    604 
    605    var innerShadowRoot = customShadowHost.attachShadow({mode: 'closed'});
    606    innerShadowRoot.appendChild(elementInNestedShadowTree);
    607 
    608    var calls = [];
    609    class SomeCustomElement extends HTMLElement {
    610        constructor() {
    611            super();
    612            calls.push(this);
    613        }
    614    };
    615 
    616    document.body.appendChild(container);
    617    customElements.define('some-custom', SomeCustomElement);
    618    assert_array_equals(calls, [connectedElementBeforeShadowHost, elementInShadowTree, customShadowHost, elementInNestedShadowTree, childElementOfShadowHost, connectedElementAfterShadowHost]);
    619 }, 'customElements.define must upgrade elements in the shadow-including tree order');
    620 
    621 test(function () {
    622    assert_true('get' in CustomElementRegistry.prototype, '"get" exists on CustomElementRegistry.prototype');
    623    assert_true('get' in customElements, '"get" exists on window.customElements');
    624 }, 'CustomElementRegistry interface must have get as a method');
    625 
    626 test(function () {
    627    assert_equals(customElements.get('a-b'), undefined);
    628 }, 'customElements.get must return undefined when the registry does not contain an entry with the given name');
    629 
    630 test(function () {
    631    assert_equals(customElements.get('html'), undefined);
    632    assert_equals(customElements.get('span'), undefined);
    633    assert_equals(customElements.get('div'), undefined);
    634    assert_equals(customElements.get('g'), undefined);
    635    assert_equals(customElements.get('ab'), undefined);
    636 }, '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');
    637 
    638 test(function () {
    639    assert_equals(customElements.get('existing-custom-element'), undefined);
    640    class ExistingCustomElement extends HTMLElement {};
    641    customElements.define('existing-custom-element', ExistingCustomElement);
    642    assert_equals(customElements.get('existing-custom-element'), ExistingCustomElement);
    643 }, 'customElements.get return the constructor of the entry with the given name when there is a matching entry.');
    644 
    645 test(function () {
    646    assert_true(customElements.whenDefined('some-name') instanceof Promise);
    647 }, 'customElements.whenDefined must return a promise for a valid custom element name');
    648 
    649 test(function () {
    650    assert_equals(customElements.whenDefined('some-name'), customElements.whenDefined('some-name'));
    651 }, 'customElements.whenDefined must return the same promise each time invoked for a valid custom element name which has not been defined');
    652 
    653 promise_test(function () {
    654    var resolved = false;
    655    var rejected = false;
    656    customElements.whenDefined('a-b').then(function () { resolved = true; }, function () { rejected = true; });
    657    return Promise.resolve().then(function () {
    658        assert_false(resolved, 'The promise returned by "whenDefined" must not be resolved until a custom element is defined');
    659        assert_false(rejected, 'The promise returned by "whenDefined" must not be rejected until a custom element is defined');
    660    });
    661 }, 'customElements.whenDefined must return an unresolved promise when the registry does not contain the entry with the given name')
    662 
    663 promise_test(function () {
    664    var promise = customElements.whenDefined('badname');
    665    promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; });
    666 
    667    assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
    668    assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
    669 
    670    return Promise.resolve().then(function () {
    671        assert_false('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
    672        assert_true('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
    673    });
    674 }, 'customElements.whenDefined must return a rejected promise when the given name is not a valid custom element name');
    675 
    676 promise_test(function () {
    677    class PreexistingCustomElement extends HTMLElement { };
    678    customElements.define('preexisting-custom-element', PreexistingCustomElement);
    679 
    680    var promise = customElements.whenDefined('preexisting-custom-element');
    681    promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; });
    682 
    683    assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
    684    assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
    685 
    686    return Promise.resolve().then(function () {
    687        assert_true('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
    688        assert_equals(promise.resolved, PreexistingCustomElement,
    689            'The promise returned by "whenDefined" must be resolved with the constructor of the element when a custom element is defined');
    690        assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
    691    });
    692 }, 'customElements.whenDefined must return a resolved promise when the registry contains the entry with the given name');
    693 
    694 promise_test(function () {
    695    class AnotherExistingCustomElement extends HTMLElement {};
    696    customElements.define('another-existing-custom-element', AnotherExistingCustomElement);
    697 
    698    var promise1 = customElements.whenDefined('another-existing-custom-element');
    699    var promise2 = customElements.whenDefined('another-existing-custom-element');
    700    promise1.then(function (value) { promise1.resolved = value; }, function (value) { promise1.rejected = value; });
    701    promise2.then(function (value) { promise2.resolved = value; }, function (value) { promise2.rejected = value; });
    702 
    703    assert_not_equals(promise1, promise2);
    704    assert_false('resolved' in promise1, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
    705    assert_false('resolved' in promise2, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
    706    assert_false('rejected' in promise1, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
    707    assert_false('rejected' in promise2, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
    708 
    709    return Promise.resolve().then(function () {
    710        assert_true('resolved' in promise1, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
    711        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');
    712        assert_false('rejected' in promise1, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
    713 
    714        assert_true('resolved' in promise2, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
    715        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');
    716        assert_false('rejected' in promise2, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
    717    });
    718 }, 'customElements.whenDefined must return a new resolved promise each time invoked when the registry contains the entry with the given name');
    719 
    720 promise_test(function () {
    721    class ElementDefinedAfterWhenDefined extends HTMLElement { };
    722    var promise = customElements.whenDefined('element-defined-after-whendefined');
    723    promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; });
    724 
    725    assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
    726    assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
    727 
    728    var promiseAfterDefine;
    729    return Promise.resolve().then(function () {
    730        assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the element is defined');
    731        assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the element is defined');
    732        assert_equals(customElements.whenDefined('element-defined-after-whendefined'), promise,
    733            '"whenDefined" must return the same unresolved promise before the custom element is defined');
    734        customElements.define('element-defined-after-whendefined', ElementDefinedAfterWhenDefined);
    735        assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
    736        assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
    737 
    738        promiseAfterDefine = customElements.whenDefined('element-defined-after-whendefined');
    739        promiseAfterDefine.then(function (value) { promiseAfterDefine.resolved = value; }, function (value) { promiseAfterDefine.rejected = value; });
    740        assert_not_equals(promiseAfterDefine, promise, '"whenDefined" must return a resolved promise once the custom element is defined');
    741        assert_false('resolved' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
    742        assert_false('rejected' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
    743    }).then(function () {
    744        assert_true('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
    745        assert_equals(promise.resolved, ElementDefinedAfterWhenDefined,
    746            'The promise returned by "whenDefined" must be resolved with the constructor of the element when a custom element is defined');
    747        assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
    748 
    749        assert_true('resolved' in promiseAfterDefine, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
    750        assert_equals(promiseAfterDefine.resolved, ElementDefinedAfterWhenDefined,
    751            'The promise returned by "whenDefined" must be resolved with the constructor of the element when a custom element is defined');
    752        assert_false('rejected' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
    753    });
    754 }, 'A promise returned by customElements.whenDefined must be resolved by "define"');
    755 
    756 promise_test(function () {
    757    class ResolvedCustomElement extends HTMLElement {};
    758    customElements.define('resolved-custom-element', ResolvedCustomElement);
    759    return customElements.whenDefined('resolved-custom-element').then(function (value) {
    760        assert_equals(value, ResolvedCustomElement, 'The promise returned by "whenDefined" must resolve with the defined class');
    761    });
    762 }, 'A promise returned by customElements.whenDefined must be resolved with the defined class');
    763 
    764 promise_test(function () {
    765    var promise = customElements.whenDefined('not-resolved-yet-custom-element').then(function (value) {
    766        assert_equals(value, NotResolvedYetCustomElement, 'The promise returned by "whenDefined" must resolve with the defined class once such class is defined');
    767    });
    768    class NotResolvedYetCustomElement extends HTMLElement {};
    769    customElements.define('not-resolved-yet-custom-element', NotResolvedYetCustomElement);
    770    return promise;
    771 }, 'A promise returned by customElements.whenDefined must be resolved with the defined class once such class is defined');
    772 </script>
    773 </body>
    774 </html>