Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(198)

Side by Side Diff: third_party/WebKit/LayoutTests/custom-elements/spec/define-element.html

Issue 2306923002: Prevent recursion in critical part of CustomElementRegistry::define. (Closed)
Patch Set: Feedback Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | third_party/WebKit/Source/core/dom/custom/CustomElementRegistry.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 <!DOCTYPE html> 1 <!DOCTYPE html>
2 <title>Custom Elements: defineElement</title> 2 <title>Custom Elements: defineElement</title>
3 <link rel="help" href="https://html.spec.whatwg.org/multipage/scripting.html#cus tomelementsregistry"> 3 <link rel="help" href="https://html.spec.whatwg.org/multipage/scripting.html#cus tomelementsregistry">
4 <meta name="author" title="Dominic Cooney" href="mailto:dominicc@chromium.org"> 4 <meta name="author" title="Dominic Cooney" href="mailto:dominicc@chromium.org">
5 <script src="../../resources/testharness.js"></script> 5 <script src="../../resources/testharness.js"></script>
6 <script src="../../resources/testharnessreport.js"></script> 6 <script src="../../resources/testharnessreport.js"></script>
7 <script src="resources/custom-elements-helpers.js"></script> 7 <script src="resources/custom-elements-helpers.js"></script>
8 <body> 8 <body>
9 <script> 9 <script>
10 // TODO(dominicc): Merge these tests with 10 // TODO(dominicc): Merge these tests with
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
65 65
66 test_with_window((w) => { 66 test_with_window((w) => {
67 class Y extends w.HTMLElement {} 67 class Y extends w.HTMLElement {}
68 let X = (function () {}).bind({}); 68 let X = (function () {}).bind({});
69 Object.defineProperty(X, 'prototype', { 69 Object.defineProperty(X, 'prototype', {
70 get() { 70 get() {
71 assert_throws_dom_exception(w, 'NotSupportedError', () => { 71 assert_throws_dom_exception(w, 'NotSupportedError', () => {
72 w.customElements.define('a-a', Y); 72 w.customElements.define('a-a', Y);
73 }, 'defining an element with a name that is being defined should ' + 73 }, 'defining an element with a name that is being defined should ' +
74 'throw a NotSupportedError'); 74 'throw a NotSupportedError');
75 return new Object(); 75 return {};
76 } 76 }
77 }); 77 });
78 w.customElements.define('a-a', X); 78 w.customElements.define('a-a', X);
79 assert_equals(w.customElements.get('a-a'), X, 'the first definition should hav e worked'); 79 assert_equals(w.customElements.get('a-a'), X, 'the first definition should hav e worked');
80 }, 'Duplicate name defined recursively'); 80 }, 'Duplicate name defined recursively');
81 81
82 test_with_window((w) => { 82 test_with_window((w) => {
83 class X extends w.HTMLElement {} 83 class X extends w.HTMLElement {}
84 w.customElements.define('a-a', X); 84 w.customElements.define('a-a', X);
85 assert_throws_dom_exception(w, 'NotSupportedError', () => { 85 assert_throws_dom_exception(w, 'NotSupportedError', () => {
86 w.customElements.define('a-b', X); 86 w.customElements.define('a-b', X);
87 }, 'defining an element with a constructor that is already in the ' + 87 }, 'defining an element with a constructor that is already in the ' +
88 'registry should throw a NotSupportedError'); 88 'registry should throw a NotSupportedError');
89 }, 'Reused constructor'); 89 }, 'Reused constructor');
90 90
91 promise_test((t) => {
92 return Promise.all([create_window_in_test(t), create_window_in_test(t)])
93 .then(([w1, w2]) => {
94 class X extends w2.HTMLElement { };
95 w1.customElements.define('first-name', X);
96 w2.customElements.define('second-name', X);
97 assert_equals(
98 new X().localName, 'second-name',
99 'the current global object should determine which definition is ' +
100 'operative; because X extends w2.HTMLElement, w2 is operative');
101 });
102 }, 'HTMLElement constructor looks up definitions in the current global-' +
103 'reused constructor');
104
105 promise_test((t) => {
106 return Promise.all([create_window_in_test(t), create_window_in_test(t)])
107 .then(([w1, w2]) => {
108 class X extends w2.HTMLElement { };
109 w1.customElements.define('x-x', X);
110 assert_throws(
111 TypeError.prototype, () => new X(),
112 'the current global object (w2) should not find the definition in w1');
113 });
114 }, 'HTMLElement constructor looks up definitions in the current global');
115
91 test_with_window((w) => { 116 test_with_window((w) => {
92 let X = (function () {}).bind({}); 117 let X = (function () {}).bind({});
93 Object.defineProperty(X, 'prototype', { 118 Object.defineProperty(X, 'prototype', {
94 get() { 119 get() {
95 assert_throws_dom_exception(w, 'NotSupportedError', () => { 120 assert_throws_dom_exception(w, 'NotSupportedError', () => {
96 w.customElements.define('second-name', X); 121 w.customElements.define('second-name', X);
97 }, 'defining an element with a constructor that is being defined ' + 122 }, 'defining an element with a constructor that is being defined ' +
98 'should throw a NotSupportedError'); 123 'should throw a NotSupportedError');
99 return new Object(); 124 return {};
100 } 125 }
101 }); 126 });
102 w.customElements.define('first-name', X); 127 w.customElements.define('first-name', X);
103 assert_equals(w.customElements.get('first-name'), X, 'the first definition sho uld have worked'); 128 assert_equals(w.customElements.get('first-name'), X, 'the first definition sho uld have worked');
104 }, 'Reused constructor recursively'); 129 }, 'Reused constructor recursively');
105 130
106 test_with_window((w) => { 131 test_with_window((w) => {
132 let X = (function () {}).bind({});
133 Object.defineProperty(X, 'prototype', {
134 get() {
135 assert_throws_dom_exception(w, 'NotSupportedError', () => {
136 w.customElements.define('second-name', class extends HTMLElement { });
137 }, 'defining an element while element definition is running should ' +
138 'throw a NotSupportedError');
139 return {};
140 }
141 });
142 w.customElements.define('first-name', X);
143 assert_equals(w.customElements.get('first-name'), X,
144 'the first definition should have worked');
145 }, 'Define while element definition is running');
146
147 promise_test((t) => {
148 return Promise.all([create_window_in_test(t), create_window_in_test(t)])
149 .then(([w1, w2]) => {
150 let X = (function () {}).bind({});
151 class Y extends w2.HTMLElement { };
152 Object.defineProperty(X, 'prototype', {
153 get() {
154 w2.customElements.define('second-name', Y);
155 return {};
156 }
157 });
158 w1.customElements.define('first-name', X);
159 assert_equals(w1.customElements.get('first-name'), X,
160 'the first definition should have worked');
161 assert_equals(w2.customElements.get('second-name'), Y,
162 'the second definition should have worked, too');
163 });
164 }, 'Define while element definition is running in a separate registry');
165
166 test_with_window((w) => {
167 class Y extends w.HTMLElement { };
168 class X extends w.HTMLElement {
169 constructor() {
170 super();
171 w.customElements.define('second-name', Y);
172 }
173 };
174 // the element definition flag while first-name is processed should
175 // be reset before doing upgrades
176 w.customElements.define('first-name', X);
177 assert_equals(w.customElements.get('second-name'), Y,
178 'the second definition should have worked');
179 }, 'Element definition flag resets before upgrades',
180 '<first-name></first-name>');
181
182 test_with_window((w) => {
107 assert_throws(TypeError.prototype, () => { 183 assert_throws(TypeError.prototype, () => {
108 let not_a_constructor = () => {}; 184 let not_a_constructor = () => {};
109 let invalid_name = 'annotation-xml'; 185 let invalid_name = 'annotation-xml';
110 w.customElements.define(invalid_name, not_a_constructor); 186 w.customElements.define(invalid_name, not_a_constructor);
111 }, 'defining an element with an invalid name and invalid constructor ' + 187 }, 'defining an element with an invalid name and invalid constructor ' +
112 'should throw a TypeError for the constructor and not a SyntaxError'); 188 'should throw a TypeError for the constructor and not a SyntaxError');
113 189
114 class C extends w.HTMLElement {} 190 class C extends w.HTMLElement {}
115 w.customElements.define('a-a', C); 191 w.customElements.define('a-a', C);
116 assert_throws_dom_exception(w, 'SYNTAX_ERR', () => { 192 assert_throws_dom_exception(w, 'SYNTAX_ERR', () => {
(...skipping 11 matching lines...) Expand all
128 <p> 204 <p>
129 <a-a id="b"></a-a> 205 <a-a id="b"></a-a>
130 <a-a id="c"></a-a> 206 <a-a id="c"></a-a>
131 </p> 207 </p>
132 <a-a id="d"></a-a> 208 <a-a id="d"></a-a>
133 </a-a>`; 209 </a-a>`;
134 let invocations = []; 210 let invocations = [];
135 class C extends w.HTMLElement { 211 class C extends w.HTMLElement {
136 constructor() { 212 constructor() {
137 super(); 213 super();
138 console.log(this.getAttribute('id'));
139 invocations.push(this); 214 invocations.push(this);
140 } 215 }
141 } 216 }
142 w.customElements.define('a-a', C); 217 w.customElements.define('a-a', C);
143 assert_array_equals(['a', 'b', 'c', 'd'], invocations.map((e) => e.id), 218 assert_array_equals(['a', 'b', 'c', 'd'], invocations.map((e) => e.id),
144 'four elements should have been upgraded in doc order'); 219 'four elements should have been upgraded in doc order');
145 }, 'Upgrade: existing elements'); 220 }, 'Upgrade: existing elements');
146 221
147 test_with_window((w) => { 222 test_with_window((w) => {
148 let doc = w.document; 223 let doc = w.document;
(...skipping 29 matching lines...) Expand all
178 invocations.push(this); 253 invocations.push(this);
179 } 254 }
180 } 255 }
181 w.customElements.define('a-a', C); 256 w.customElements.define('a-a', C);
182 assert_array_equals([a], invocations, 257 assert_array_equals([a], invocations,
183 'the constructor should have been invoked once for the ' + 258 'the constructor should have been invoked once for the ' +
184 'elements in the shadow tree'); 259 'elements in the shadow tree');
185 }, 'Upgrade: shadow tree'); 260 }, 'Upgrade: shadow tree');
186 261
187 // Final step in Step 14 262 // Final step in Step 14
188 // 14. Finally, if the first set of steps threw an exception, then rethrow that exception, 263 // 14. Finally, if the first set of steps threw an exception, then rethrow that exception,
189 // and terminate this algorithm. 264 // and terminate this algorithm.
190 test_with_window((w) => { 265 test_with_window((w) => {
191 class Y extends w.HTMLElement {} 266 class Y extends w.HTMLElement {}
192 let X = (function () {}).bind({}); 267 let X = (function () {}).bind({});
193 Object.defineProperty(X, 'prototype', { 268 Object.defineProperty(X, 'prototype', {
194 get() { throw { name: 42 }; } 269 get() { throw { name: 42 }; }
195 }); 270 });
196 assert_throws({ name: 42 }, () => { 271 assert_throws({ name: 42 }, () => {
197 w.customElements.define('a-a', X); 272 w.customElements.define('a-a', X);
198 }, 'should rethrow constructor exception'); 273 }, 'should rethrow constructor exception');
199 w.customElements.define('a-a', Y); 274 w.customElements.define('a-a', Y);
(...skipping 28 matching lines...) Expand all
228 // Callbacks are defined in reverse order. 303 // Callbacks are defined in reverse order.
229 let callbacks_in_reverse = ['attributeChangedCallback', 'disconnectedCallback', 'connectedCallback']; 304 let callbacks_in_reverse = ['attributeChangedCallback', 'disconnectedCallback', 'connectedCallback'];
230 function F_for_callbacks_in_reverse() {}; 305 function F_for_callbacks_in_reverse() {};
231 callbacks_in_reverse.forEach((callback) => { 306 callbacks_in_reverse.forEach((callback) => {
232 test_with_window((w) => { 307 test_with_window((w) => {
233 Object.defineProperty(F_for_callbacks_in_reverse.prototype, callback, { 308 Object.defineProperty(F_for_callbacks_in_reverse.prototype, callback, {
234 get() { throw { name: callback }; } 309 get() { throw { name: callback }; }
235 }); 310 });
236 assert_throws({ name: callback }, () => { 311 assert_throws({ name: callback }, () => {
237 w.customElements.define('a-a', F_for_callbacks_in_reverse); 312 w.customElements.define('a-a', F_for_callbacks_in_reverse);
238 }, 'Exception from Get(prototype, callback) should be rethrown'); 313 }, 'Exception from Get(prototype, callback) should be rethrown');
239 }, 'Rethrow any exceptions thrown while retrieving ' + callback); 314 }, 'Rethrow any exceptions thrown while retrieving ' + callback);
240 }); 315 });
241 316
242 // 14.4 If connectedCallback is not undefined, and IsCallable(connectedCallback) is false, 317 // 14.4 If connectedCallback is not undefined, and IsCallable(connectedCallback) is false,
243 // then throw a TypeError exception. 318 // then throw a TypeError exception.
244 // 14.6 If disconnectedCallback is not undefined, and IsCallable(disconnectedCal lback) is false, 319 // 14.6 If disconnectedCallback is not undefined, and IsCallable(disconnectedCal lback) is false,
245 // then throw a TypeError exception. 320 // then throw a TypeError exception.
246 // 14.9. If attributeChangedCallback is not undefined, then 321 // 14.9. If attributeChangedCallback is not undefined, then
247 // 1. If IsCallable(attributeChangedCallback) is false, then throw a TypeE rror exception. 322 // 1. If IsCallable(attributeChangedCallback) is false, then throw a TypeE rror exception.
248 callbacks_in_reverse.forEach((callback) => { 323 callbacks_in_reverse.forEach((callback) => {
249 test_with_window((w) => { 324 test_with_window((w) => {
250 function F() {} 325 function F() {}
251 Object.defineProperty(F.prototype, callback, { 326 Object.defineProperty(F.prototype, callback, {
252 get() { return new Object(); } 327 get() { return {}; }
253 }); 328 });
254 assert_throws(TypeError.prototype, () => { 329 assert_throws(TypeError.prototype, () => {
255 w.customElements.define('a-a', F); 330 w.customElements.define('a-a', F);
256 }, 'defining an element with a constructor with a callback that is ' + 331 }, 'defining an element with a constructor with a callback that is ' +
257 'not undefined and not callable should throw a TypeError'); 332 'not undefined and not callable should throw a TypeError');
258 }, 'If retrieved callback '+ callback + ' is not undefined and not callable, t hrow TypeError'); 333 }, 'If retrieved callback '+ callback + ' is not undefined and not callable, t hrow TypeError');
259 }); 334 });
260 335
261 // 14.9.2 Let observedAttributesIterable be Get(constructor, "observedAttributes "). 336 // 14.9.2 Let observedAttributesIterable be Get(constructor, "observedAttributes ").
262 // Rethrow any exceptions. 337 // Rethrow any exceptions.
263 test_with_window((w) => { 338 test_with_window((w) => {
264 class X extends w.HTMLElement{ 339 class X extends w.HTMLElement{
265 constructor() { super(); } 340 constructor() { super(); }
266 attributeChangedCallback() {} 341 attributeChangedCallback() {}
267 static get observedAttributes() { throw { name: 'observedAttributes throws' }; } 342 static get observedAttributes() { throw { name: 'observedAttributes throws' }; }
268 } 343 }
269 assert_throws({ name: 'observedAttributes throws' }, () => { 344 assert_throws({ name: 'observedAttributes throws' }, () => {
270 w.customElements.define('a-a', X); 345 w.customElements.define('a-a', X);
271 }, 'Exception from Get(constructor, observedAttributes) should be rethrown'); 346 }, 'Exception from Get(constructor, observedAttributes) should be rethrown');
272 }, 'Rethrow any exceptions thrown while getting observedAttributes'); 347 }, 'Rethrow any exceptions thrown while getting observedAttributes');
273 348
274 // 14.9.3 If observedAttributesIterable is not undefined, then set observedAttri butes 349 // 14.9.3 If observedAttributesIterable is not undefined, then set observedAttri butes
275 // to the result of converting observedAttributesIterable to a sequence<D OMString>. 350 // to the result of converting observedAttributesIterable to a sequence<D OMString>.
276 // Rethrow any exceptions. 351 // Rethrow any exceptions.
277 test_with_window((w) => { 352 test_with_window((w) => {
278 class X extends w.HTMLElement{ 353 class X extends w.HTMLElement{
279 constructor() { super(); } 354 constructor() { super(); }
280 attributeChangedCallback() {} 355 attributeChangedCallback() {}
281 static get observedAttributes() { return new RegExp(); } 356 static get observedAttributes() { return new RegExp(); }
282 } 357 }
283 assert_throws(TypeError.prototype, () => { 358 assert_throws(TypeError.prototype, () => {
284 w.customElements.define('a-a', X); 359 w.customElements.define('a-a', X);
285 }, 'converting RegExp to sequence<DOMString> should throw TypeError'); 360 }, 'converting RegExp to sequence<DOMString> should throw TypeError');
286 }, 'exception thrown while converting observedAttributes to sequence<DOMString> ' + 361 }, 'exception thrown while converting observedAttributes to ' +
287 'should be rethrown'); 362 'sequence<DOMString> should be rethrown');
288 363
289 // 14.9.2 test Get(constructor, observedAttributes) does not throw if 364 // 14.9.2 test Get(constructor, observedAttributes) does not throw if
290 // attributeChangedCallback is undefined. 365 // attributeChangedCallback is undefined.
291 test_with_window((w) => { 366 test_with_window((w) => {
292 let observedAttributes_invoked = false; 367 let observedAttributes_invoked = false;
293 let X = (function () {}).bind({}); 368 let X = (function () {}).bind({});
294 Object.defineProperty(X, 'observedAttributes', { 369 Object.defineProperty(X, 'observedAttributes', {
295 get() { observedAttributes_invoked = true; } 370 get() { observedAttributes_invoked = true; }
296 }); 371 });
297 assert_false( observedAttributes_invoked, 'Get(constructor, observedAttributes ) should not be invoked'); 372 assert_false( observedAttributes_invoked, 'Get(constructor, observedAttributes ) should not be invoked');
298 }, 'Get(constructor, observedAttributes)' + 373 }, 'Get(constructor, observedAttributes) should not execute if ' +
299 'should not execute if attributeChangedCallback is undefined'); 374 'attributeChangedCallback is undefined');
375
376 // TODO(dominicc): I think the order and timing of checks the tests
377 // below exercise has changed. Update these tests. crbug.com/643052
300 378
301 // step 2 379 // step 2
302 // 2. If constructor is an interface object whose corresponding interface either is 380 // 2. If constructor is an interface object whose corresponding interface either is
303 // HTMLElement or has HTMLElement in its set of inherited interfaces, throw 381 // HTMLElement or has HTMLElement in its set of inherited interfaces, throw
304 // a TypeError and abort these steps. 382 // a TypeError and abort these steps.
305 // 3. If name is not a valid custom element name, then throw a "SyntaxError" DOM Exception 383 // 3. If name is not a valid custom element name, then throw a "SyntaxError" DOM Exception
306 // and abort these steps. 384 // and abort these steps.
307 test_with_window((w) => { 385 test_with_window((w) => {
308 let invalid_name = 'annotation-xml'; 386 let invalid_name = 'annotation-xml';
309 // TODO(davaajav): change this to TypeError, when we add a failure expectation to this file 387 // TODO(davaajav): change this to TypeError, when we add a failure expectation to this file
310 assert_throws_dom_exception(w, 'SYNTAX_ERR', () => { 388 assert_throws_dom_exception(w, 'SYNTAX_ERR', () => {
311 w.customElements.define(invalid_name, HTMLElement); 389 w.customElements.define(invalid_name, HTMLElement);
312 }, 'defining a constructor that is an interface object whose interface is HTML Element' + 390 }, 'defining a constructor that is an interface object whose interface is HTML Element' +
313 'should throw TypeError not SyntaxError'); 391 'should throw TypeError not SyntaxError');
314 }, 'Invalid constructor'); 392 }, 'Invalid constructor');
315 393
316 // step 2 394 // step 2
317 test_with_window((w) => { 395 test_with_window((w) => {
318 let invalid_name = 'annotation-xml'; 396 let invalid_name = 'annotation-xml';
319 assert_throws_dom_exception(w, 'SYNTAX_ERR', () => { 397 assert_throws_dom_exception(w, 'SYNTAX_ERR', () => {
320 w.customElements.define(invalid_name, HTMLButtonElement); 398 w.customElements.define(invalid_name, HTMLButtonElement);
321 }, 'defining a constructor that is an interface object who has HTMLElement' + 399 }, 'defining a constructor that is an interface object who has HTMLElement' +
322 'in its set of inhertied interfaces should throw TypeError not SyntaxErro r'); 400 'in its set of inhertied interfaces should throw TypeError not SyntaxError' );
323 }, 'Invalid constructor'); 401 }, 'Invalid constructor');
324 402
325 // step 2 403 // step 2
326 test_with_window((w) => { 404 test_with_window((w) => {
327 let invalid_name = 'annotation-xml'; 405 let invalid_name = 'annotation-xml';
328 assert_throws_dom_exception(w, 'SYNTAX_ERR', () => { 406 assert_throws_dom_exception(w, 'SYNTAX_ERR', () => {
329 w.customElements.define(invalid_name, class extends HTMLElement {}); 407 w.customElements.define(invalid_name, class extends HTMLElement {});
330 }, 'defining author-defined custom element constructor' + 408 }, 'defining author-defined custom element constructor should pass this ' +
331 'should pass this step without throwing TypeError'); 409 'step without throwing TypeError');
332 }, 'Invalid constructor'); 410 }, 'Invalid constructor');
333 </script> 411 </script>
334 </body> 412 </body>
OLDNEW
« no previous file with comments | « no previous file | third_party/WebKit/Source/core/dom/custom/CustomElementRegistry.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698