| <!DOCTYPE html> |
| <title>Custom Elements: [HTMLConstructor] derives prototype from NewTarget</title> |
| <meta name="author" title="Domenic Denicola" href="mailto:d@domenic.me"> |
| <meta name="help" content="https://html.spec.whatwg.org/multipage/dom.html#htmlconstructor"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="../resources/custom-elements-helpers.js"></script> |
| <body> |
| <script> |
| "use strict"; |
| |
| test_with_window(w => { |
| let beforeDefinition = true; |
| const proto1 = { "proto": "number one" }; |
| const proto2 = { "proto": "number two" }; |
| |
| function TestElement() { |
| const o = Reflect.construct(w.HTMLElement, [], new.target); |
| assert_equals(Object.getPrototypeOf(o), proto2, |
| "Must use the value returned from new.target.prototype"); |
| assert_not_equals(Object.getPrototypeOf(o), proto1, |
| "Must not use the prototype stored at definition time"); |
| } |
| |
| const ElementWithDynamicPrototype = new Proxy(TestElement, { |
| get: function (target, name) { |
| if (name == "prototype") |
| return beforeDefinition ? proto1 : proto2; |
| return target[name]; |
| } |
| }); |
| |
| w.customElements.define("test-element", ElementWithDynamicPrototype); |
| |
| beforeDefinition = false; |
| new ElementWithDynamicPrototype(); |
| }, "Use NewTarget's prototype, not the one stored at definition time"); |
| |
| test_with_window(w => { |
| // We have to not throw during define(), but throw during super() |
| let throws = false; |
| |
| function TestElement() { |
| throws = true; |
| assert_throws({ name: "prototype throws" }, () => { |
| Reflect.construct(w.HTMLElement, [], new.target); |
| }); |
| } |
| |
| const ElementWithDynamicPrototype = new Proxy(TestElement, { |
| get: function (target, name) { |
| if (throws && name == "prototype") |
| throw { name: "prototype throws" }; |
| return target[name]; |
| } |
| }); |
| |
| w.customElements.define("test-element", ElementWithDynamicPrototype); |
| |
| new ElementWithDynamicPrototype(); |
| |
| }, "Rethrow any exceptions thrown while getting the prototype"); |
| |
| [null, undefined, 5, "string"].forEach(function (notAnObject) { |
| test_with_window(w => { |
| // We have to return an object during define(), but not during super() |
| let returnNotAnObject = false; |
| |
| function TestElement() { |
| const o = Reflect.construct(w.HTMLElement, [], new.target); |
| |
| assert_equals(Object.getPrototypeOf(new.target), window.Function.prototype); |
| assert_equals(Object.getPrototypeOf(o), window.HTMLElement.prototype, |
| "Must use the HTMLElement from the realm of NewTarget"); |
| assert_not_equals(Object.getPrototypeOf(o), w.HTMLElement.prototype, |
| "Must not use the HTMLElement from the realm of the active function object (w.HTMLElement)"); |
| |
| return o; |
| } |
| |
| const ElementWithDynamicPrototype = new Proxy(TestElement, { |
| get: function (target, name) { |
| if (name == "prototype") |
| return returnNotAnObject ? notAnObject : {}; |
| return target[name]; |
| } |
| }); |
| |
| w.customElements.define("test-element", ElementWithDynamicPrototype); |
| |
| returnNotAnObject = true; |
| new ElementWithDynamicPrototype(); |
| }, "If prototype is not object (" + notAnObject + "), derives the fallback from NewTarget's realm (autonomous custom elements)"); |
| }); |
| |
| [null, undefined, 5, "string"].forEach(function (notAnObject) { |
| test_with_window(w => { |
| // We have to return an object during define(), but not during super() |
| let returnNotAnObject = false; |
| |
| function TestElement() { |
| const o = Reflect.construct(w.HTMLParagraphElement, [], new.target); |
| |
| assert_equals(Object.getPrototypeOf(o), window.HTMLParagraphElement.prototype, |
| "Must use the HTMLParagraphElement from the realm of NewTarget"); |
| assert_not_equals(Object.getPrototypeOf(o), w.HTMLParagraphElement.prototype, |
| "Must not use the HTMLParagraphElement from the realm of the active function object (w.HTMLParagraphElement)"); |
| |
| return o; |
| } |
| |
| const ElementWithDynamicPrototype = new Proxy(TestElement, { |
| get: function (target, name) { |
| if (name == "prototype") |
| return returnNotAnObject ? notAnObject : {}; |
| return target[name]; |
| } |
| }); |
| |
| w.customElements.define("test-element", ElementWithDynamicPrototype, { extends: "p" }); |
| |
| returnNotAnObject = true; |
| new ElementWithDynamicPrototype(); |
| }, "If prototype is not object (" + notAnObject + "), derives the fallback from NewTarget's realm (customized built-in elements)"); |
| }); |
| |
| test_with_window(w => { |
| class SomeCustomElement extends HTMLParagraphElement {}; |
| var getCount = 0; |
| var countingProxy = new Proxy(SomeCustomElement, { |
| get: function(target, prop, receiver) { |
| if (prop == "prototype") { |
| ++getCount; |
| } |
| return Reflect.get(target, prop, receiver); |
| } |
| }); |
| w.customElements.define("failure-counting-element", countingProxy); |
| // define() gets the prototype of the constructor it's passed, so |
| // reset the counter. |
| getCount = 0; |
| assert_throws({'name': 'TypeError'}, |
| function () { new countingProxy() }, |
| "Should not be able to construct an HTMLParagraphElement not named 'p'"); |
| assert_equals(getCount, 0, "Should never have gotten .prototype"); |
| }, 'HTMLParagraphElement constructor must not get .prototype until it finishes its extends sanity checks, calling proxy constructor directly'); |
| |
| test_with_window(w => { |
| class SomeCustomElement extends HTMLParagraphElement {}; |
| var getCount = 0; |
| var countingProxy = new Proxy(SomeCustomElement, { |
| get: function(target, prop, receiver) { |
| if (prop == "prototype") { |
| ++getCount; |
| } |
| return Reflect.get(target, prop, receiver); |
| } |
| }); |
| w.customElements.define("failure-counting-element", countingProxy); |
| // define() gets the prototype of the constructor it's passed, so |
| // reset the counter. |
| getCount = 0; |
| assert_throws({'name': 'TypeError'}, |
| function () { Reflect.construct(HTMLParagraphElement, [], countingProxy) }, |
| "Should not be able to construct an HTMLParagraphElement not named 'p'"); |
| assert_equals(getCount, 0, "Should never have gotten .prototype"); |
| }, 'HTMLParagraphElement constructor must not get .prototype until it finishes its extends sanity checks, calling via Reflect'); |
| </script> |
| </body> |