define.html (7734B)
1 <!DOCTYPE html> 2 <title>Custom Elements: Element definition</title> 3 <script src="/resources/testharness.js"></script> 4 <script src="/resources/testharnessreport.js"></script> 5 <body> 6 <div id="log"></div> 7 <iframe id="iframe"></iframe> 8 <script> 9 'use strict'; 10 (() => { 11 // Element definition 12 // https://html.spec.whatwg.org/multipage/scripting.html#element-definition 13 14 // Use window from iframe to isolate the test. 15 const iframe = document.getElementById("iframe"); 16 const testWindow = iframe.contentDocument.defaultView; 17 const customElements = testWindow.customElements; 18 19 let testable = false; 20 test(() => { 21 assert_true('customElements' in testWindow, '"window.customElements" exists'); 22 assert_true('define' in customElements, '"window.customElements.define" exists'); 23 testable = true; 24 }, '"window.customElements.define" should exists'); 25 if (!testable) 26 return; 27 28 const expectTypeError = testWindow.TypeError; 29 // Following errors are DOMException, not JavaScript errors. 30 const expectSyntaxError = 'SYNTAX_ERR'; 31 const expectNotSupportedError = 'NOT_SUPPORTED_ERR'; 32 33 // 1. If IsConstructor(constructor) is false, 34 // then throw a TypeError and abort these steps. 35 test(() => { 36 assert_throws_js(expectTypeError, () => { 37 customElements.define(); 38 }); 39 }, 'If no arguments, should throw a TypeError'); 40 test(() => { 41 assert_throws_js(expectTypeError, () => { 42 customElements.define('test-define-one-arg'); 43 }); 44 }, 'If one argument, should throw a TypeError'); 45 [ 46 [ 'undefined', undefined ], 47 [ 'null', null ], 48 [ 'object', {} ], 49 [ 'string', 'string' ], 50 [ 'arrow function', () => {} ], // IsConstructor returns false for arrow functions 51 [ 'method', ({ m() { } }).m ], // IsConstructor returns false for methods 52 ].forEach(t => { 53 test(() => { 54 assert_throws_js(expectTypeError, () => { 55 customElements.define(`test-define-constructor-${t[0]}`, t[1]); 56 }); 57 }, `If constructor is ${t[0]}, should throw a TypeError`); 58 }); 59 60 // 2. If name is not a valid custom element name, 61 // then throw a SyntaxError and abort these steps. 62 let validCustomElementNames = [ 63 // [a-z] (PCENChar)* '-' (PCENChar)* 64 // https://html.spec.whatwg.org/multipage/scripting.html#valid-custom-element-name 65 'a-', 66 'a-a', 67 'aa-', 68 'aa-a', 69 'a-.-_', 70 'a-0123456789', 71 'a-\u6F22\u5B57', // Two CJK Unified Ideographs 72 'a-\uD840\uDC0B', // Surrogate pair U+2000B 73 'a-a\u00D7', 74 'a-a\u3000', 75 'a-a\uDB80\uDC00', // Surrogate pair U+F0000 76 ]; 77 let invalidCustomElementNames = [ 78 undefined, 79 null, 80 '', 81 '-', 82 'a', 83 'input', 84 'mycustomelement', 85 'A', 86 'A-', 87 '0-', 88 'a-A', 89 'a-Z', 90 'A-a', 91 // name must not be any of the hyphen-containing element names. 92 'annotation-xml', 93 'color-profile', 94 'font-face', 95 'font-face-src', 96 'font-face-uri', 97 'font-face-format', 98 'font-face-name', 99 'missing-glyph', 100 ]; 101 validCustomElementNames.forEach(name => { 102 test(() => { 103 customElements.define(name, class {}); 104 }, `Element names: defining an element named ${name} should succeed`); 105 }); 106 invalidCustomElementNames.forEach(name => { 107 test(() => { 108 assert_throws_dom(expectSyntaxError, testWindow.DOMException, () => { 109 customElements.define(name, class {}); 110 }); 111 }, `Element names: defining an element named ${name} should throw a SyntaxError`); 112 }); 113 114 // 3. If this CustomElementRegistry contains an entry with name name, 115 // then throw a NotSupportedError and abort these steps. 116 test(() => { 117 customElements.define('test-define-dup-name', class {}); 118 assert_throws_dom(expectNotSupportedError, testWindow.DOMException, () => { 119 customElements.define('test-define-dup-name', class {}); 120 }); 121 }, 'If the name is already defined, should throw a NotSupportedError'); 122 123 // 5. If this CustomElementRegistry contains an entry with constructor constructor, 124 // then throw a NotSupportedError and abort these steps. 125 test(() => { 126 class TestDupConstructor {}; 127 customElements.define('test-define-dup-constructor', TestDupConstructor); 128 assert_throws_dom(expectNotSupportedError, testWindow.DOMException, () => { 129 customElements.define('test-define-dup-ctor2', TestDupConstructor); 130 }); 131 }, 'If the constructor is already defined, should throw a NotSupportedError'); 132 133 // 12.1. Let prototype be Get(constructor, "prototype"). Rethrow any exceptions. 134 const err = new Error('check this is rethrown'); 135 err.name = 'rethrown'; 136 function assert_rethrown(func, description) { 137 assert_throws_exactly(err, func, description); 138 } 139 function throw_rethrown_error() { 140 throw err; 141 } 142 test(() => { 143 // Hack for prototype to throw while IsConstructor is true. 144 const BadConstructor = (function () { }).bind({}); 145 Object.defineProperty(BadConstructor, 'prototype', { 146 get() { throw_rethrown_error(); } 147 }); 148 assert_rethrown(() => { 149 customElements.define('test-define-constructor-prototype-rethrow', BadConstructor); 150 }); 151 }, 'If constructor.prototype throws, should rethrow'); 152 153 // 12.2. If Type(prototype) is not Object, 154 // then throw a TypeError exception. 155 test(() => { 156 const c = (function () { }).bind({}); // prototype is undefined. 157 assert_throws_js(expectTypeError, () => { 158 customElements.define('test-define-constructor-prototype-undefined', c); 159 }); 160 }, 'If Type(constructor.prototype) is undefined, should throw a TypeError'); 161 test(() => { 162 function c() {}; 163 c.prototype = 'string'; 164 assert_throws_js(expectTypeError, () => { 165 customElements.define('test-define-constructor-prototype-string', c); 166 }); 167 }, 'If Type(constructor.prototype) is string, should throw a TypeError'); 168 169 // 12.3. Let lifecycleCallbacks be a map with the four keys "connectedCallback", 170 // "disconnectedCallback", "adoptedCallback", and "attributeChangedCallback", 171 // each of which belongs to an entry whose value is null. 172 // 12.4. For each of the four keys callbackName in lifecycleCallbacks: 173 // 12.4.1. Let callbackValue be Get(prototype, callbackName). Rethrow any exceptions. 174 // 12.4.2. If callbackValue is not undefined, then set the value of the entry in 175 // lifecycleCallbacks with key callbackName to the result of converting callbackValue 176 // to the Web IDL Function callback type. Rethrow any exceptions from the conversion. 177 [ 178 'connectedCallback', 179 'disconnectedCallback', 180 'adoptedCallback', 181 'attributeChangedCallback', 182 ].forEach(name => { 183 test(() => { 184 class C { 185 get [name]() { throw_rethrown_error(); } 186 } 187 assert_rethrown(() => { 188 customElements.define(`test-define-${name.toLowerCase()}-rethrow`, C); 189 }); 190 }, `If constructor.prototype.${name} throws, should rethrow`); 191 192 [ 193 { name: 'undefined', value: undefined, success: true }, 194 { name: 'function', value: function () { }, success: true }, 195 { name: 'null', value: null, success: false }, 196 { name: 'object', value: {}, success: false }, 197 { name: 'integer', value: 1, success: false }, 198 ].forEach(data => { 199 test(() => { 200 class C { }; 201 C.prototype[name] = data.value; 202 if (data.success) { 203 customElements.define(`test-define-${name.toLowerCase()}-${data.name}`, C); 204 } else { 205 assert_throws_js(expectTypeError, () => { 206 customElements.define(`test-define-${name.toLowerCase()}-${data.name}`, C); 207 }); 208 } 209 }, `If constructor.prototype.${name} is ${data.name}, should ${data.success ? 'succeed' : 'throw a TypeError'}`); 210 }); 211 }); 212 })(); 213 </script> 214 </body>