| <!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)"); | |
| 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; | |
| } | |
| // Create the proxy in the subframe, which should not affect what our | |
| // prototype ends up as. | |
| const ElementWithDynamicPrototype = new w.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 GetFunctionRealm (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 => { | |
| // 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; | |
| } | |
| // Create the proxy in the subframe, which should not affect what our | |
| // prototype ends up as. | |
| const ElementWithDynamicPrototype = new w.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 GetFunctionRealm (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> |