Changeset 205315 in webkit
- Timestamp:
- Sep 1, 2016 2:16:40 PM (8 years ago)
- Location:
- trunk
- Files:
-
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r205314 r205315 1 2016-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 1 13 2016-09-01 Nikita Vasilyev <nvasilyev@apple.com> 2 14 -
trunk/LayoutTests/fast/custom-elements/CustomElementRegistry-expected.txt
r205263 r205315 28 28 PASS "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 29 29 PASS "get" return the constructor of the entry with the given name when there is a matching entry. 30 PASS "whenDefined" must return a promise for a valid custom element name 31 PASS "whenDefined" must return the same promise each time invoked for a valid custom element name which has not been defined 32 PASS "whenDefined" must return an unresolved promise when the registry does not contain the entry with the given name 33 PASS "whenDefined" must return a resolved promise when the registry contains the entry with the given name 34 PASS "whenDefined" must return a new resolved promise each time invoked when the registry contains the entry with the given name 35 PASS A promise returned by "whenDefined" must be resolved by "define" 30 36 -
trunk/LayoutTests/fast/custom-elements/CustomElementRegistry.html
r205263 r205315 408 408 }, '"get" return the constructor of the entry with the given name when there is a matching entry.'); 409 409 410 test(function () { 411 assert_true(customElements.whenDefined('some-name') instanceof Promise); 412 }, '"whenDefined" must return a promise for a valid custom element name'); 413 414 test(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 418 promise_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 /* 428 promise_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 */ 441 promise_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 458 promise_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 484 promise_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 410 519 </script> 411 520 </body> -
trunk/Source/WebCore/ChangeLog
r205312 r205315 1 2016-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 1 29 2016-09-01 Alex Christensen <achristensen@webkit.org> 2 30 -
trunk/Source/WebCore/bindings/js/JSCustomElementRegistryCustom.cpp
r205261 r205315 33 33 #include "JSDOMBinding.h" 34 34 #include "JSDOMConvert.h" 35 #include "JSDOMPromise.h" 35 36 36 37 using namespace JSC; … … 57 58 } 58 59 60 static 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 59 81 // https://html.spec.whatwg.org/#dom-customelementregistry-define 60 82 JSValue JSCustomElementRegistry::define(ExecState& state) … … 75 97 JSObject* constructor = constructorValue.getObject(); 76 98 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(); 93 101 94 102 CustomElementRegistry& registry = wrapped(); … … 156 164 // FIXME: 17. Let map be registry's upgrade candidates map. 157 165 // 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); 159 171 160 172 return jsUndefined(); 161 173 } 174 175 // https://html.spec.whatwg.org/#dom-customelementregistry-whendefined 176 static 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 203 JSValue 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 162 218 #endif 163 219 -
trunk/Source/WebCore/dom/CustomElementRegistry.cpp
r205220 r205315 32 32 #include "Element.h" 33 33 #include "JSCustomElementInterface.h" 34 #include "JSDOMPromise.h" 34 35 #include "MathMLNames.h" 35 36 #include "QualifiedName.h" -
trunk/Source/WebCore/dom/CustomElementRegistry.h
r205261 r205315 44 44 45 45 class CustomElementRegistry; 46 class DeferredWrapper; 46 47 class Element; 47 48 class JSCustomElementInterface; … … 65 66 JSC::JSValue get(const AtomicString&); 66 67 68 HashMap<AtomicString, Ref<DeferredWrapper>>& promiseMap() { return m_promiseMap; } 69 67 70 private: 68 71 CustomElementRegistry(); … … 71 74 HashMap<AtomicString, Ref<JSCustomElementInterface>> m_nameMap; 72 75 HashMap<const JSC::JSObject*, JSCustomElementInterface*> m_constructorMap; 76 HashMap<AtomicString, Ref<DeferredWrapper>> m_promiseMap; 73 77 74 78 bool m_elementDefinitionIsRunning { false }; -
trunk/Source/WebCore/dom/CustomElementRegistry.idl
r205220 r205315 33 33 [CEReactions, Custom] void define(DOMString name, Function constructor); 34 34 any get(DOMString name); 35 [RaisesExceptionWithMessage, Custom] Promise whenDefined(DOMString name); 35 36 36 37 };
Note: See TracChangeset
for help on using the changeset viewer.