HTMLElement-constructor.html (8465B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Custom Elements: HTMLElement must allow subclassing</title> 5 <meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> 6 <meta name="assert" content="HTMLElement must allow subclassing"> 7 <link rel="help" href="https://html.spec.whatwg.org/#html-element-constructors"> 8 <script src="/resources/testharness.js"></script> 9 <script src="/resources/testharnessreport.js"></script> 10 </head> 11 <body> 12 <div id="log"></div> 13 <script> 14 15 test(function () { 16 customElements.define('html-custom-element', HTMLElement); 17 assert_throws_js(TypeError, function () { new HTMLElement(); }); 18 }, 'HTMLElement constructor must throw a TypeError when NewTarget is equal to itself'); 19 20 test(function () { 21 customElements.define('html-proxy-custom-element', new Proxy(HTMLElement, {})); 22 assert_throws_js(TypeError, function () { new HTMLElement(); }); 23 }, 'HTMLElement constructor must throw a TypeError when NewTarget is equal to itself via a Proxy object'); 24 25 test(function () { 26 class SomeCustomElement extends HTMLElement {}; 27 assert_throws_js(TypeError, function () { new SomeCustomElement; }); 28 }, 'HTMLElement constructor must throw TypeError when it has not been defined by customElements.define'); 29 30 test(function () { 31 class SomeCustomElement extends HTMLParagraphElement {}; 32 customElements.define('some-custom-element', SomeCustomElement); 33 assert_throws_js(TypeError, function () { new SomeCustomElement(); }); 34 }, 'Custom element constructor must throw TypeError when it does not extend HTMLElement'); 35 36 test(function () { 37 class SomeCustomButtonElement extends HTMLButtonElement {}; 38 customElements.define('some-custom-button-element', SomeCustomButtonElement, { extends: "p" }); 39 assert_throws_js(TypeError, function () { new SomeCustomButtonElement(); }); 40 }, 'Custom element constructor must throw TypeError when it does not extend the proper element interface'); 41 42 test(function () { 43 class CustomElementWithInferredTagName extends HTMLElement {}; 44 customElements.define('inferred-name', CustomElementWithInferredTagName); 45 46 var instance = new CustomElementWithInferredTagName; 47 assert_true(instance instanceof Element, 'A custom element must inherit from Element'); 48 assert_true(instance instanceof Node, 'A custom element must inherit from Node'); 49 assert_equals(instance.localName, 'inferred-name'); 50 assert_equals(instance.nodeName, 'INFERRED-NAME'); 51 assert_equals(instance.namespaceURI, 'http://www.w3.org/1999/xhtml', 'A custom HTML element must use HTML namespace'); 52 53 document.body.appendChild(instance); 54 assert_equals(document.body.lastChild, instance, 55 'document.body.appendChild must be able to insert a custom element'); 56 assert_equals(document.querySelector('inferred-name'), instance, 57 'document.querySelector must be able to find a custom element by its tag name'); 58 59 }, 'HTMLElement constructor must infer the tag name from the element interface'); 60 61 test(function () { 62 class ConcreteCustomElement extends HTMLElement { }; 63 class SubCustomElement extends ConcreteCustomElement { }; 64 customElements.define('concrete-custom-element', ConcreteCustomElement); 65 customElements.define('sub-custom-element', SubCustomElement); 66 67 var instance = new ConcreteCustomElement(); 68 assert_true(instance instanceof ConcreteCustomElement); 69 assert_false(instance instanceof SubCustomElement); 70 assert_equals(instance.localName, 'concrete-custom-element'); 71 assert_equals(instance.nodeName, 'CONCRETE-CUSTOM-ELEMENT'); 72 73 var instance = new SubCustomElement(); 74 assert_true(instance instanceof ConcreteCustomElement); 75 assert_true(instance instanceof SubCustomElement); 76 assert_equals(instance.localName, 'sub-custom-element'); 77 assert_equals(instance.nodeName, 'SUB-CUSTOM-ELEMENT'); 78 79 }, 'HTMLElement constructor must allow subclassing a custom element'); 80 81 test(function () { 82 class AbstractCustomElement extends HTMLElement { }; 83 class ConcreteSubCustomElement extends AbstractCustomElement { }; 84 customElements.define('concrete-sub-custom-element', ConcreteSubCustomElement); 85 86 var instance = new ConcreteSubCustomElement(); 87 assert_true(instance instanceof AbstractCustomElement); 88 assert_true(instance instanceof ConcreteSubCustomElement); 89 assert_equals(instance.localName, 'concrete-sub-custom-element'); 90 assert_equals(instance.nodeName, 'CONCRETE-SUB-CUSTOM-ELEMENT'); 91 92 }, 'HTMLElement constructor must allow subclassing an user-defined subclass of HTMLElement'); 93 94 test(function() { 95 class SomeCustomElement extends HTMLElement {}; 96 var getCount = 0; 97 var countingProxy = new Proxy(SomeCustomElement, { 98 get: function(target, prop, receiver) { 99 if (prop == "prototype") { 100 ++getCount; 101 } 102 return Reflect.get(target, prop, receiver); 103 } 104 }); 105 customElements.define("success-counting-element-1", countingProxy); 106 // define() gets the prototype of the constructor it's passed, so 107 // reset the counter. 108 getCount = 0; 109 var instance = new countingProxy(); 110 assert_equals(getCount, 1, "Should have gotten .prototype once"); 111 assert_true(instance instanceof countingProxy); 112 assert_true(instance instanceof HTMLElement); 113 assert_true(instance instanceof SomeCustomElement); 114 assert_equals(instance.localName, "success-counting-element-1"); 115 assert_equals(instance.nodeName, "SUCCESS-COUNTING-ELEMENT-1"); 116 }, 'HTMLElement constructor must only get .prototype once, calling proxy constructor directly'); 117 118 test(function() { 119 class SomeCustomElement extends HTMLElement {}; 120 var getCount = 0; 121 var countingProxy = new Proxy(SomeCustomElement, { 122 get: function(target, prop, receiver) { 123 if (prop == "prototype") { 124 ++getCount; 125 } 126 return Reflect.get(target, prop, receiver); 127 } 128 }); 129 customElements.define("success-counting-element-2", countingProxy); 130 // define() gets the prototype of the constructor it's passed, so 131 // reset the counter. 132 getCount = 0; 133 var instance = Reflect.construct(HTMLElement, [], countingProxy); 134 assert_equals(getCount, 1, "Should have gotten .prototype once"); 135 assert_true(instance instanceof countingProxy); 136 assert_true(instance instanceof HTMLElement); 137 assert_true(instance instanceof SomeCustomElement); 138 assert_equals(instance.localName, "success-counting-element-2"); 139 assert_equals(instance.nodeName, "SUCCESS-COUNTING-ELEMENT-2"); 140 }, 'HTMLElement constructor must only get .prototype once, calling proxy constructor via Reflect'); 141 142 test(function() { 143 class SomeCustomElement {}; 144 var getCount = 0; 145 var countingProxy = new Proxy(SomeCustomElement, { 146 get: function(target, prop, receiver) { 147 if (prop == "prototype") { 148 ++getCount; 149 } 150 return Reflect.get(target, prop, receiver); 151 } 152 }); 153 customElements.define("success-counting-element-3", countingProxy); 154 // define() gets the prototype of the constructor it's passed, so 155 // reset the counter. 156 getCount = 0; 157 var instance = Reflect.construct(HTMLElement, [], countingProxy); 158 assert_equals(getCount, 1, "Should have gotten .prototype once"); 159 assert_true(instance instanceof countingProxy); 160 assert_true(instance instanceof SomeCustomElement); 161 assert_equals(instance.localName, undefined); 162 assert_equals(instance.nodeName, undefined); 163 }, 'HTMLElement constructor must only get .prototype once, calling proxy constructor via Reflect with no inheritance'); 164 165 test(function() { 166 class SomeCustomElement extends HTMLElement {}; 167 var getCount = 0; 168 var countingProxy = new Proxy(SomeCustomElement, { 169 get: function(target, prop, receiver) { 170 if (prop == "prototype") { 171 ++getCount; 172 } 173 return Reflect.get(target, prop, receiver); 174 } 175 }); 176 177 // Purposefully don't register it. 178 assert_throws_js(TypeError, 179 function () { Reflect.construct(HTMLElement, [], countingProxy) }, 180 "Should not be able to construct an HTMLElement named 'button'"); 181 assert_equals(getCount, 0, "Should never have gotten .prototype"); 182 }, 'HTMLElement constructor must not get .prototype until it finishes its registration sanity checks, calling via Reflect'); 183 </script> 184 </body> 185 </html>