Changeset 205315 in webkit


Ignore:
Timestamp:
Sep 1, 2016 2:16:40 PM (8 years ago)
Author:
rniwa@webkit.org
Message:

Add "whenDefined" to CustomElementRegistry
https://bugs.webkit.org/show_bug.cgi?id=161425

Reviewed by Yusuke Suzuki.

Source/WebCore:

Add the support for "whenDefined" method on CustomElementRegistry:
https://html.spec.whatwg.org/#dom-customelementregistry-whendefined

Because it needs to store the newly created promise when the queried custom element has not been defined yet,
we need to write custom binding code instead of relying on the binding generator.

Tests: fast/custom-elements/CustomElementRegistry.html

  • bindings/js/JSCustomElementRegistryCustom.cpp:

(WebCore::validateCustomElementNameAndThrowIfNeeded): Extracted out of JSCustomElementRegistry::define.
(WebCore::JSCustomElementRegistry::define): Fulfill the "whenDefined" promise when the definition succeeds.
(WebCore::whenDefinedPromise): Added. Return an existing promise if there is one, or create a new promise.
We cache the created promise only if the custom element had not been defined yet since we'll indefinitely
retain the resolved promise otherwise.
(WebCore::JSCustomElementRegistry::whenDefined): Added. Calls whenDefinedPromise and returns a rejected
promise when there was an exception.

  • dom/CustomElementRegistry.cpp:
  • dom/CustomElementRegistry.h:

(WebCore::CustomElementRegistry::promiseMap): Added.

  • dom/CustomElementRegistry.idl:

LayoutTests:

Added test cases for "whenDefined" method.

  • fast/custom-elements/CustomElementRegistry-expected.txt:
  • fast/custom-elements/CustomElementRegistry.html:
Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r205314 r205315  
     12016-09-01  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Add "whenDefined" to CustomElementRegistry
     4        https://bugs.webkit.org/show_bug.cgi?id=161425
     5
     6        Reviewed by Yusuke Suzuki.
     7
     8        Added test cases for "whenDefined" method.
     9
     10        * fast/custom-elements/CustomElementRegistry-expected.txt:
     11        * fast/custom-elements/CustomElementRegistry.html:
     12
    1132016-09-01  Nikita Vasilyev  <nvasilyev@apple.com>
    214
  • trunk/LayoutTests/fast/custom-elements/CustomElementRegistry-expected.txt

    r205263 r205315  
    2828PASS "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
    2929PASS "get" return the constructor of the entry with the given name when there is a matching entry.
     30PASS "whenDefined" must return a promise for a valid custom element name
     31PASS "whenDefined" must return the same promise each time invoked for a valid custom element name which has not been defined
     32PASS "whenDefined" must return an unresolved promise when the registry does not contain the entry with the given name
     33PASS "whenDefined" must return a resolved promise when the registry contains the entry with the given name
     34PASS "whenDefined" must return a new resolved promise each time invoked when the registry contains the entry with the given name
     35PASS A promise returned by "whenDefined" must be resolved by "define"
    3036
  • trunk/LayoutTests/fast/custom-elements/CustomElementRegistry.html

    r205263 r205315  
    408408}, '"get" return the constructor of the entry with the given name when there is a matching entry.');
    409409
     410test(function () {
     411    assert_true(customElements.whenDefined('some-name') instanceof Promise);
     412}, '"whenDefined" must return a promise for a valid custom element name');
     413
     414test(function () {
     415    assert_equals(customElements.whenDefined('some-name'), customElements.whenDefined('some-name'));
     416}, '"whenDefined" must return the same promise each time invoked for a valid custom element name which has not been defined');
     417
     418promise_test(function () {
     419    var resolved = false;
     420    var rejected = false;
     421    customElements.whenDefined('a-b').then(function () { resolved = true; }, function () { rejected = true; });
     422    return Promise.resolve().then(function () {
     423        assert_false(resolved, 'The promise returned by "whenDefined" must not be resolved until a custom element is defined');
     424        assert_false(rejected, 'The promise returned by "whenDefined" must not be rejected until a custom element is defined');
     425    });   
     426}, '"whenDefined" must return an unresolved promise when the registry does not contain the entry with the given name')
     427/*
     428promise_test(function () {
     429    var promise = customElements.whenDefined('badname');
     430    promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; });
     431
     432    assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
     433    assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
     434
     435    return Promise.resolve().then(function () {
     436        assert_false('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
     437        assert_true('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
     438    });
     439}, '"whenDefined" must return a rejected promise when the given name is not a valid custom element name');
     440*/
     441promise_test(function () {
     442    customElements.define('preexisting-custom-element', class extends HTMLElement { });
     443
     444    var promise = customElements.whenDefined('preexisting-custom-element');
     445    promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; });
     446
     447    assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
     448    assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
     449
     450    return Promise.resolve().then(function () {
     451        assert_true('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
     452        assert_equals(promise.resolved, undefined,
     453            'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
     454        assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
     455    });
     456}, '"whenDefined" must return a resolved promise when the registry contains the entry with the given name');
     457
     458promise_test(function () {
     459    class AnotherExistingCustomElement extends HTMLElement {};
     460    customElements.define('another-existing-custom-element', AnotherExistingCustomElement);
     461
     462    var promise1 = customElements.whenDefined('another-existing-custom-element');
     463    var promise2 = customElements.whenDefined('another-existing-custom-element');
     464    promise1.then(function (value) { promise1.resolved = value; }, function (value) { promise1.rejected = value; });
     465    promise2.then(function (value) { promise2.resolved = value; }, function (value) { promise2.rejected = value; });
     466
     467    assert_not_equals(promise1, promise2);
     468    assert_false('resolved' in promise1, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
     469    assert_false('resolved' in promise2, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
     470    assert_false('rejected' in promise1, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
     471    assert_false('rejected' in promise2, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
     472
     473    return Promise.resolve().then(function () {
     474        assert_true('resolved' in promise1, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
     475        assert_equals(promise1.resolved, undefined, 'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
     476        assert_false('rejected' in promise1, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
     477
     478        assert_true('resolved' in promise2, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
     479        assert_equals(promise2.resolved, undefined, 'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
     480        assert_false('rejected' in promise2, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
     481    });
     482}, '"whenDefined" must return a new resolved promise each time invoked when the registry contains the entry with the given name');
     483
     484promise_test(function () {
     485    var promise = customElements.whenDefined('element-defined-after-whendefined');
     486    promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; });
     487
     488    assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
     489    assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
     490
     491    var promiseAfterDefine;
     492    return Promise.resolve().then(function () {
     493        assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the element is defined');
     494        assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the element is defined');
     495        assert_equals(customElements.whenDefined('element-defined-after-whendefined'), promise,
     496            '"whenDefined" must return the same unresolved promise before the custom element is defined');
     497        customElements.define('element-defined-after-whendefined', class extends HTMLElement { });
     498        assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
     499        assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
     500
     501        promiseAfterDefine = customElements.whenDefined('element-defined-after-whendefined');
     502        promiseAfterDefine.then(function (value) { promiseAfterDefine.resolved = value; }, function (value) { promiseAfterDefine.rejected = value; });
     503        assert_not_equals(promiseAfterDefine, promise, '"whenDefined" must return a resolved promise once the custom element is defined');
     504        assert_false('resolved' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
     505        assert_false('rejected' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
     506    }).then(function () {
     507        assert_true('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
     508        assert_equals(promise.resolved, undefined,
     509            'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
     510        assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
     511
     512        assert_true('resolved' in promiseAfterDefine, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
     513        assert_equals(promiseAfterDefine.resolved, undefined,
     514            'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
     515        assert_false('rejected' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
     516    });
     517}, 'A promise returned by "whenDefined" must be resolved by "define"');
     518
    410519</script>
    411520</body>
  • trunk/Source/WebCore/ChangeLog

    r205312 r205315  
     12016-09-01  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Add "whenDefined" to CustomElementRegistry
     4        https://bugs.webkit.org/show_bug.cgi?id=161425
     5
     6        Reviewed by Yusuke Suzuki.
     7
     8        Add the support for "whenDefined" method on CustomElementRegistry:
     9        https://html.spec.whatwg.org/#dom-customelementregistry-whendefined
     10
     11        Because it needs to store the newly created promise when the queried custom element has not been defined yet,
     12        we need to write custom binding code instead of relying on the binding generator.
     13
     14        Tests: fast/custom-elements/CustomElementRegistry.html
     15
     16        * bindings/js/JSCustomElementRegistryCustom.cpp:
     17        (WebCore::validateCustomElementNameAndThrowIfNeeded): Extracted out of JSCustomElementRegistry::define.
     18        (WebCore::JSCustomElementRegistry::define): Fulfill the "whenDefined" promise when the definition succeeds.
     19        (WebCore::whenDefinedPromise): Added. Return an existing promise if there is one, or create a new promise.
     20        We cache the created promise only if the custom element had not been defined yet since we'll indefinitely
     21        retain the resolved promise otherwise.
     22        (WebCore::JSCustomElementRegistry::whenDefined): Added. Calls whenDefinedPromise and returns a rejected
     23        promise when there was an exception.
     24        * dom/CustomElementRegistry.cpp:
     25        * dom/CustomElementRegistry.h:
     26        (WebCore::CustomElementRegistry::promiseMap): Added.
     27        * dom/CustomElementRegistry.idl:
     28
    1292016-09-01  Alex Christensen  <achristensen@webkit.org>
    230
  • trunk/Source/WebCore/bindings/js/JSCustomElementRegistryCustom.cpp

    r205261 r205315  
    3333#include "JSDOMBinding.h"
    3434#include "JSDOMConvert.h"
     35#include "JSDOMPromise.h"
    3536
    3637using namespace JSC;
     
    5758}
    5859
     60static bool validateCustomElementNameAndThrowIfNeeded(ExecState& state, const AtomicString& name)
     61{
     62    auto scope = DECLARE_THROW_SCOPE(state.vm());
     63
     64    switch (Document::validateCustomElementName(name)) {
     65    case CustomElementNameValidationStatus::Valid:
     66        return true;
     67    case CustomElementNameValidationStatus::ConflictsWithBuiltinNames:
     68        throwSyntaxError(&state, scope, ASCIILiteral("Custom element name cannot be same as one of the builtin elements"));
     69        return false;
     70    case CustomElementNameValidationStatus::NoHyphen:
     71        throwSyntaxError(&state, scope, ASCIILiteral("Custom element name must contain a hyphen"));
     72        return false;
     73    case CustomElementNameValidationStatus::ContainsUpperCase:
     74        throwSyntaxError(&state, scope, ASCIILiteral("Custom element name cannot contain an upper case letter"));
     75        return false;
     76    }
     77    ASSERT_NOT_REACHED();
     78    return false;
     79}
     80
    5981// https://html.spec.whatwg.org/#dom-customelementregistry-define
    6082JSValue JSCustomElementRegistry::define(ExecState& state)
     
    7597    JSObject* constructor = constructorValue.getObject();
    7698
    77     // FIXME: Throw a TypeError if constructor doesn't inherit from HTMLElement.
    78     // https://github.com/w3c/webcomponents/issues/541
    79 
    80     switch (Document::validateCustomElementName(localName)) {
    81     case CustomElementNameValidationStatus::Valid:
    82         break;
    83     case CustomElementNameValidationStatus::ConflictsWithBuiltinNames:
    84         return throwSyntaxError(&state, scope, ASCIILiteral("Custom element name cannot be same as one of the builtin elements"));
    85     case CustomElementNameValidationStatus::NoHyphen:
    86         return throwSyntaxError(&state, scope, ASCIILiteral("Custom element name must contain a hyphen"));
    87     case CustomElementNameValidationStatus::ContainsUpperCase:
    88         return throwSyntaxError(&state, scope, ASCIILiteral("Custom element name cannot contain an upper case letter"));
    89     }
    90 
    91     // FIXME: Check re-entrancy here.
    92     // https://github.com/w3c/webcomponents/issues/545
     99    if (!validateCustomElementNameAndThrowIfNeeded(state, localName))
     100        return jsUndefined();
    93101
    94102    CustomElementRegistry& registry = wrapped();
     
    156164    // FIXME: 17. Let map be registry's upgrade candidates map.
    157165    // FIXME: 18. Upgrade a newly-defined element given map and definition.
    158     // FIXME: 19. Resolve whenDefined promise.
     166
     167    auto& promiseMap = registry.promiseMap();
     168    auto promise = promiseMap.take(localName);
     169    if (promise)
     170        promise.value()->resolve(nullptr);
    159171
    160172    return jsUndefined();
    161173}
     174
     175// https://html.spec.whatwg.org/#dom-customelementregistry-whendefined
     176static JSValue whenDefinedPromise(ExecState& state, JSDOMGlobalObject& globalObject, CustomElementRegistry& registry)
     177{
     178    auto scope = DECLARE_THROW_SCOPE(state.vm());
     179
     180    if (UNLIKELY(state.argumentCount() < 1))
     181        return throwException(&state, scope, createNotEnoughArgumentsError(&state));
     182
     183    AtomicString localName(state.uncheckedArgument(0).toString(&state)->toAtomicString(&state));
     184    if (UNLIKELY(state.hadException()))
     185        return jsUndefined();
     186
     187    if (!validateCustomElementNameAndThrowIfNeeded(state, localName))
     188        return jsUndefined();
     189
     190    if (registry.findInterface(localName)) {
     191        auto& jsPromise = *JSPromiseDeferred::create(&state, &globalObject);
     192        DeferredWrapper::create(&state, &globalObject, &jsPromise)->resolve(nullptr);
     193        return jsPromise.promise();
     194    }
     195
     196    auto result = registry.promiseMap().ensure(localName, [&] {
     197        return DeferredWrapper::create(&state, &globalObject, JSPromiseDeferred::create(&state, &globalObject));
     198    });
     199
     200    return result.iterator->value->promise();
     201}
     202
     203JSValue JSCustomElementRegistry::whenDefined(ExecState& state)
     204{
     205    JSDOMGlobalObject& globalObject = *jsCast<JSDOMGlobalObject*>(state.lexicalGlobalObject());
     206    auto& promiseDeferred = *JSPromiseDeferred::create(&state, &globalObject);
     207    JSValue promise = whenDefinedPromise(state, globalObject, wrapped());
     208
     209    if (state.hadException()) {
     210        rejectPromiseWithExceptionIfAny(state, globalObject, promiseDeferred);
     211        ASSERT(!state.hadException());
     212        return promiseDeferred.promise();
     213    }
     214
     215    return promise;
     216}
     217
    162218#endif
    163219
  • trunk/Source/WebCore/dom/CustomElementRegistry.cpp

    r205220 r205315  
    3232#include "Element.h"
    3333#include "JSCustomElementInterface.h"
     34#include "JSDOMPromise.h"
    3435#include "MathMLNames.h"
    3536#include "QualifiedName.h"
  • trunk/Source/WebCore/dom/CustomElementRegistry.h

    r205261 r205315  
    4444
    4545class CustomElementRegistry;
     46class DeferredWrapper;
    4647class Element;
    4748class JSCustomElementInterface;
     
    6566    JSC::JSValue get(const AtomicString&);
    6667
     68    HashMap<AtomicString, Ref<DeferredWrapper>>& promiseMap() { return m_promiseMap; }
     69
    6770private:
    6871    CustomElementRegistry();
     
    7174    HashMap<AtomicString, Ref<JSCustomElementInterface>> m_nameMap;
    7275    HashMap<const JSC::JSObject*, JSCustomElementInterface*> m_constructorMap;
     76    HashMap<AtomicString, Ref<DeferredWrapper>> m_promiseMap;
    7377
    7478    bool m_elementDefinitionIsRunning { false };
  • trunk/Source/WebCore/dom/CustomElementRegistry.idl

    r205220 r205315  
    3333    [CEReactions, Custom] void define(DOMString name, Function constructor);
    3434    any get(DOMString name);
     35    [RaisesExceptionWithMessage, Custom] Promise whenDefined(DOMString name);
    3536
    3637};
Note: See TracChangeset for help on using the changeset viewer.