Revision 208cfc11c0a61d6d44ec395699ff32c6dcf6ab52 authored by Manuel Rego Casasnovas on 19 March 2018, 16:01:26 UTC, committed by Chromium WPT Sync on 19 March 2018, 16:01:26 UTC
LayoutBox::FillAvailableMeasure() was not considering the case of
orthogonal elements when computing the margins.
The margins ended up being properly calculated but the size of
the orthogonal elements was wrong, as they considered
to have more or less space than the available one.

The method is modified in order to use
the containing block inline size in order to resolve the percentages:
https://www.w3.org/TR/css-writing-modes-3/#dimension-mapping

BUG=808758
TEST=external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-00*.html

Change-Id: Ib8c81dcd14589b3fefe806de3f8f75c000b1cac9
Reviewed-on: https://chromium-review.googlesource.com/968522
Commit-Queue: Koji Ishii <kojii@chromium.org>
Reviewed-by: Koji Ishii <kojii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#544047}
1 parent 5dee619
Raw File
CustomElementRegistry.html
<!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({'name': 'TypeError'}, function () { customElements.define('badname', 1); },
        'customElements.define must throw a TypeError when the element interface is a number');
    assert_throws({'name': 'TypeError'}, function () { customElements.define('badname', '123'); },
        'customElements.define must throw a TypeError when the element interface is a string');
    assert_throws({'name': 'TypeError'}, function () { customElements.define('badname', {}); },
        'customElements.define must throw a TypeError when the element interface is an object');
    assert_throws({'name': '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({'name': 'SyntaxError'}, function () { customElements.define(null, MyCustomElement); },
        'customElements.define must throw a SyntaxError if the tag name is null');
    assert_throws({'name': 'SyntaxError'}, function () { customElements.define('', MyCustomElement); },
        'customElements.define must throw a SyntaxError if the tag name is empty');
    assert_throws({'name': 'SyntaxError'}, function () { customElements.define('abc', MyCustomElement); },
        'customElements.define must throw a SyntaxError if the tag name does not contain "-"');
    assert_throws({'name': '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({'name': '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({'name': '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({'name': '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({'name': '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({'name': '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({'name': '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")', {timeout: 100});

    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);
                    iframe.contentWindow.customElements.define('another-custom-element', InnerCustomElement);
                    return target[name];
                }
            })
            customElements.define('element-with-inner-element-define', proxy);
            assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"');
            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({'name': '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']);
}, 'customElements.define must get "prototype" property of the constructor');

test(function () {
    var proxy = new Proxy(class extends HTMLElement { }, {
        get: function (target, name) {
            throw {name: 'expectedError'};
        }
    });
    assert_throws({'name': 'expectedError'}, 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({'name': '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({'name': '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({'name': '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({'name': '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 = [];
    constructor.prototype = new Proxy(constructor.prototype, {
        get: function (target, name) {
            calls.push(name);
            if (name == 'disconnectedCallback')
                throw {name: 'expectedError'};
            return target[name];
        }
    });
    assert_throws({'name': 'expectedError'}, 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({'name': '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']);
}, '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 proxy = new Proxy(constructor, {
        get: function (target, name) {
            calls.push(name);
            if (name == 'observedAttributes')
                throw {name: 'expectedError'};
            return target[name];
        }
    });
    assert_throws({'name': 'expectedError'}, 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({'name': '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 constructor = function () {}
    constructor.prototype.attributeChangedCallback = function () { };
    constructor.observedAttributes = {[Symbol.iterator]: function *() {
        yield 'foo';
        throw {name: 'SomeError'};
    }};
    assert_throws({'name': 'SomeError'}, 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({'name': '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 () {
    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 () {
    customElements.define('preexisting-custom-element', class extends HTMLElement { });

    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, undefined,
            'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
        assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
    });
}, '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, undefined, 'The promise returned by "whenDefined" must be resolved with "undefined" 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, undefined, 'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
        assert_false('rejected' in promise2, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
    });
}, 'customElements.whenDefined must return a new resolved promise each time invoked when the registry contains the entry with the given name');

promise_test(function () {
    var promise = customElements.whenDefined('element-defined-after-whendefined');
    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', class extends HTMLElement { });
        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, undefined,
            'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
        assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');

        assert_true('resolved' in promiseAfterDefine, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
        assert_equals(promiseAfterDefine.resolved, undefined,
            'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
        assert_false('rejected' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
    });
}, 'A promise returned by customElements.whenDefined must be resolved by "define"');

</script>
</body>
</html>
back to top