<!DOCTYPE html><html><head><title>Custom Elements: HTMLElement must allow subclassing</title><metaname="author"title="Ryosuke Niwa"href="mailto:rniwa@webkit.org"><metaname="assert"content="HTMLElement must allow subclassing"><linkrel="help"href="https://html.spec.whatwg.org/#html-element-constructors"><scriptsrc="/resources/testharness.js"></script><scriptsrc="/resources/testharnessreport.js"></script></head><body><divid="log"></div><script>test(function(){customElements.define('html-custom-element',HTMLElement);assert_throws_js(TypeError,function(){newHTMLElement();});},'HTMLElement constructor must throw a TypeError when NewTarget is equal to itself');test(function(){customElements.define('html-proxy-custom-element',newProxy(HTMLElement,{}));assert_throws_js(TypeError,function(){newHTMLElement();});},'HTMLElement constructor must throw a TypeError when NewTarget is equal to itself via a Proxy object');test(function(){classSomeCustomElementextendsHTMLElement{};assert_throws_js(TypeError,function(){newSomeCustomElement;});},'HTMLElement constructor must throw TypeError when it has not been defined by customElements.define');test(function(){classSomeCustomElementextendsHTMLParagraphElement{};customElements.define('some-custom-element',SomeCustomElement);assert_throws_js(TypeError,function(){newSomeCustomElement();});},'Custom element constructor must throw TypeError when it does not extend HTMLElement');test(function(){classSomeCustomButtonElementextendsHTMLButtonElement{};customElements.define('some-custom-button-element',SomeCustomButtonElement,{extends:"p"});assert_throws_js(TypeError,function(){newSomeCustomButtonElement();});},'Custom element constructor must throw TypeError when it does not extend the proper element interface');test(function(){classCustomElementWithInferredTagNameextendsHTMLElement{};customElements.define('inferred-name',CustomElementWithInferredTagName);varinstance=newCustomElementWithInferredTagName;assert_true(instanceinstanceofElement,'A custom element must inherit from Element');assert_true(instanceinstanceofNode,'A custom element must inherit from Node');assert_equals(instance.localName,'inferred-name');assert_equals(instance.nodeName,'INFERRED-NAME');assert_equals(instance.namespaceURI,'http://www.w3.org/1999/xhtml','A custom HTML element must use HTML namespace');document.body.appendChild(instance);assert_equals(document.body.lastChild,instance,'document.body.appendChild must be able to insert a custom element');assert_equals(document.querySelector('inferred-name'),instance,'document.querySelector must be able to find a custom element by its tag name');},'HTMLElement constructor must infer the tag name from the element interface');test(function(){classConcreteCustomElementextendsHTMLElement{};classSubCustomElementextendsConcreteCustomElement{};customElements.define('concrete-custom-element',ConcreteCustomElement);customElements.define('sub-custom-element',SubCustomElement);varinstance=newConcreteCustomElement();assert_true(instanceinstanceofConcreteCustomElement);assert_false(instanceinstanceofSubCustomElement);assert_equals(instance.localName,'concrete-custom-element');assert_equals(instance.nodeName,'CONCRETE-CUSTOM-ELEMENT');varinstance=newSubCustomElement();assert_true(instanceinstanceofConcreteCustomElement);assert_true(instanceinstanceofSubCustomElement);assert_equals(instance.localName,'sub-custom-element');assert_equals(instance.nodeName,'SUB-CUSTOM-ELEMENT');},'HTMLElement constructor must allow subclassing a custom element');test(function(){classAbstractCustomElementextendsHTMLElement{};classConcreteSubCustomElementextendsAbstractCustomElement{};customElements.define('concrete-sub-custom-element',ConcreteSubCustomElement);varinstance=newConcreteSubCustomElement();assert_true(instanceinstanceofAbstractCustomElement);assert_true(instanceinstanceofConcreteSubCustomElement);assert_equals(instance.localName,'concrete-sub-custom-element');assert_equals(instance.nodeName,'CONCRETE-SUB-CUSTOM-ELEMENT');},'HTMLElement constructor must allow subclassing an user-defined subclass of HTMLElement');test(function(){classSomeCustomElementextendsHTMLElement{};vargetCount=0;varcountingProxy=newProxy(SomeCustomElement,{get:function(target,prop,receiver){if(prop=="prototype"){++getCount;}returnReflect.get(target,prop,receiver);}});customElements.define("success-counting-element-1",countingProxy);// define() gets the prototype of the constructor it's passed, so// reset the counter.getCount=0;varinstance=newcountingProxy();assert_equals(getCount,1,"Should have gotten .prototype once");assert_true(instanceinstanceofcountingProxy);assert_true(instanceinstanceofHTMLElement);assert_true(instanceinstanceofSomeCustomElement);assert_equals(instance.localName,"success-counting-element-1");assert_equals(instance.nodeName,"SUCCESS-COUNTING-ELEMENT-1");},'HTMLElement constructor must only get .prototype once, calling proxy constructor directly');test(function(){classSomeCustomElementextendsHTMLElement{};vargetCount=0;varcountingProxy=newProxy(SomeCustomElement,{get:function(target,prop,receiver){if(prop=="prototype"){++getCount;}returnReflect.get(target,prop,receiver);}});customElements.define("success-counting-element-2",countingProxy);// define() gets the prototype of the constructor it's passed, so// reset the counter.getCount=0;varinstance=Reflect.construct(HTMLElement,[],countingProxy);assert_equals(getCount,1,"Should have gotten .prototype once");assert_true(instanceinstanceofcountingProxy);assert_true(instanceinstanceofHTMLElement);assert_true(instanceinstanceofSomeCustomElement);assert_equals(instance.localName,"success-counting-element-2");assert_equals(instance.nodeName,"SUCCESS-COUNTING-ELEMENT-2");},'HTMLElement constructor must only get .prototype once, calling proxy constructor via Reflect');test(function(){classSomeCustomElement{};vargetCount=0;varcountingProxy=newProxy(SomeCustomElement,{get:function(target,prop,receiver){if(prop=="prototype"){++getCount;}returnReflect.get(target,prop,receiver);}});customElements.define("success-counting-element-3",countingProxy);// define() gets the prototype of the constructor it's passed, so// reset the counter.getCount=0;varinstance=Reflect.construct(HTMLElement,[],countingProxy);assert_equals(getCount,1,"Should have gotten .prototype once");assert_true(instanceinstanceofcountingProxy);assert_true(instanceinstanceofSomeCustomElement);assert_equals(instance.localName,undefined);assert_equals(instance.nodeName,undefined);},'HTMLElement constructor must only get .prototype once, calling proxy constructor via Reflect with no inheritance');test(function(){classSomeCustomElementextendsHTMLElement{};vargetCount=0;varcountingProxy=newProxy(SomeCustomElement,{get:function(target,prop,receiver){if(prop=="prototype"){++getCount;}returnReflect.get(target,prop,receiver);}});// Purposefully don't register it.assert_throws_js(TypeError,function(){Reflect.construct(HTMLElement,[],countingProxy)},"Should not be able to construct an HTMLElement named 'button'");assert_equals(getCount,0,"Should never have gotten .prototype");},'HTMLElement constructor must not get .prototype until it finishes its registration sanity checks, calling via Reflect');</script></body></html>