|
1 <!DOCTYPE HTML> |
|
2 <html> |
|
3 <!-- |
|
4 https://bugzilla.mozilla.org/show_bug.cgi?id=783129 |
|
5 --> |
|
6 <head> |
|
7 <title>Test for document.registerElement lifecycle callback</title> |
|
8 <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> |
|
9 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> |
|
10 </head> |
|
11 <body> |
|
12 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a> |
|
13 <div id="container"> |
|
14 <x-hello id="hello"></x-hello> |
|
15 <button id="extbutton" is="x-button"></button> |
|
16 </div> |
|
17 <script> |
|
18 |
|
19 var container = document.getElementById("container"); |
|
20 |
|
21 // Tests callbacks after registering element type that is already in the document. |
|
22 // create element in document -> register -> remove from document |
|
23 function testRegisterUnresolved() { |
|
24 var helloElem = document.getElementById("hello"); |
|
25 |
|
26 var createdCallbackCalled = false; |
|
27 var attachedCallbackCalled = false; |
|
28 var detachedCallbackCalled = false; |
|
29 |
|
30 var p = Object.create(HTMLElement.prototype); |
|
31 p.createdCallback = function() { |
|
32 is(helloElem.__proto__, p, "Prototype should be adjusted just prior to invoking the created callback."); |
|
33 is(createdCallbackCalled, false, "Created callback should only be called once in this tests."); |
|
34 is(this, helloElem, "The 'this' value should be the custom element."); |
|
35 createdCallbackCalled = true; |
|
36 }; |
|
37 |
|
38 p.attachedCallback = function() { |
|
39 is(createdCallbackCalled, true, "Created callback should be called before attached"); |
|
40 is(attachedCallbackCalled, false, "attached callback should only be called once in this test."); |
|
41 is(this, helloElem, "The 'this' value should be the custom element."); |
|
42 attachedCallbackCalled = true; |
|
43 }; |
|
44 |
|
45 p.detachedCallback = function() { |
|
46 is(attachedCallbackCalled, true, "attached callback should be called before detached"); |
|
47 is(detachedCallbackCalled, false, "detached callback should only be called once in this test."); |
|
48 detachedCallbackCalled = true; |
|
49 is(this, helloElem, "The 'this' value should be the custom element."); |
|
50 runNextTest(); |
|
51 }; |
|
52 |
|
53 p.attributeChangedCallback = function(name, oldValue, newValue) { |
|
54 ok(false, "attributeChanged callback should never be called in this test."); |
|
55 }; |
|
56 |
|
57 document.registerElement("x-hello", { prototype: p }); |
|
58 is(createdCallbackCalled, true, "created callback should be called when control returns to script from user agent code"); |
|
59 |
|
60 // Remove element from document to trigger detached callback. |
|
61 container.removeChild(helloElem); |
|
62 } |
|
63 |
|
64 // Tests callbacks after registering an extended element type that is already in the document. |
|
65 // create element in document -> register -> remove from document |
|
66 function testRegisterUnresolvedExtended() { |
|
67 var buttonElem = document.getElementById("extbutton"); |
|
68 |
|
69 var createdCallbackCalled = false; |
|
70 var attachedCallbackCalled = false; |
|
71 var detachedCallbackCalled = false; |
|
72 |
|
73 var p = Object.create(HTMLButtonElement.prototype); |
|
74 p.createdCallback = function() { |
|
75 is(buttonElem.__proto__, p, "Prototype should be adjusted just prior to invoking the created callback."); |
|
76 is(createdCallbackCalled, false, "Created callback should only be called once in this tests."); |
|
77 is(this, buttonElem, "The 'this' value should be the custom element."); |
|
78 createdCallbackCalled = true; |
|
79 }; |
|
80 |
|
81 p.attachedCallback = function() { |
|
82 is(createdCallbackCalled, true, "Created callback should be called before attached"); |
|
83 is(attachedCallbackCalled, false, "attached callback should only be called once in this test."); |
|
84 is(this, buttonElem, "The 'this' value should be the custom element."); |
|
85 attachedCallbackCalled = true; |
|
86 }; |
|
87 |
|
88 p.detachedCallback = function() { |
|
89 is(attachedCallbackCalled, true, "attached callback should be called before detached"); |
|
90 is(detachedCallbackCalled, false, "detached callback should only be called once in this test."); |
|
91 detachedCallbackCalled = true; |
|
92 is(this, buttonElem, "The 'this' value should be the custom element."); |
|
93 runNextTest(); |
|
94 }; |
|
95 |
|
96 p.attributeChangedCallback = function(name, oldValue, newValue) { |
|
97 ok(false, "attributeChanged callback should never be called in this test."); |
|
98 }; |
|
99 |
|
100 document.registerElement("x-button", { prototype: p, extends: "button" }); |
|
101 is(createdCallbackCalled, true, "created callback should be called when control returns to script from user agent code"); |
|
102 |
|
103 // Remove element from document to trigger detached callback. |
|
104 container.removeChild(buttonElem); |
|
105 } |
|
106 |
|
107 function testInnerHTML() { |
|
108 var createdCallbackCalled = false; |
|
109 |
|
110 var p = Object.create(HTMLElement.prototype); |
|
111 p.createdCallback = function() { |
|
112 is(createdCallbackCalled, false, "created callback should only be called once in this test."); |
|
113 createdCallbackCalled = true; |
|
114 }; |
|
115 |
|
116 document.registerElement("x-inner-html", { prototype: p }); |
|
117 var div = document.createElement(div); |
|
118 div.innerHTML = '<x-inner-html></x-inner-html>'; |
|
119 is(createdCallbackCalled, true, "created callback should be called after setting innerHTML."); |
|
120 runNextTest(); |
|
121 } |
|
122 |
|
123 function testInnerHTMLExtended() { |
|
124 var createdCallbackCalled = false; |
|
125 |
|
126 var p = Object.create(HTMLButtonElement.prototype); |
|
127 p.createdCallback = function() { |
|
128 is(createdCallbackCalled, false, "created callback should only be called once in this test."); |
|
129 createdCallbackCalled = true; |
|
130 }; |
|
131 |
|
132 document.registerElement("x-inner-html-extended", { prototype: p, extends: "button" }); |
|
133 var div = document.createElement(div); |
|
134 div.innerHTML = '<button is="x-inner-html-extended"></button>'; |
|
135 is(createdCallbackCalled, true, "created callback should be called after setting innerHTML."); |
|
136 runNextTest(); |
|
137 } |
|
138 |
|
139 function testInnerHTMLUpgrade() { |
|
140 var createdCallbackCalled = false; |
|
141 |
|
142 var div = document.createElement(div); |
|
143 div.innerHTML = '<x-inner-html-upgrade></x-inner-html-upgrade>'; |
|
144 |
|
145 var p = Object.create(HTMLElement.prototype); |
|
146 p.createdCallback = function() { |
|
147 is(createdCallbackCalled, false, "created callback should only be called once in this test."); |
|
148 createdCallbackCalled = true; |
|
149 }; |
|
150 |
|
151 document.registerElement("x-inner-html-upgrade", { prototype: p }); |
|
152 is(createdCallbackCalled, true, "created callback should be called after registering."); |
|
153 runNextTest(); |
|
154 } |
|
155 |
|
156 function testInnerHTMLExtendedUpgrade() { |
|
157 var createdCallbackCalled = false; |
|
158 |
|
159 var div = document.createElement(div); |
|
160 div.innerHTML = '<button is="x-inner-html-extended-upgrade"></button>'; |
|
161 |
|
162 var p = Object.create(HTMLButtonElement.prototype); |
|
163 p.createdCallback = function() { |
|
164 is(createdCallbackCalled, false, "created callback should only be called once in this test."); |
|
165 createdCallbackCalled = true; |
|
166 }; |
|
167 |
|
168 document.registerElement("x-inner-html-extended-upgrade", { prototype: p, extends: "button" }); |
|
169 is(createdCallbackCalled, true, "created callback should be called after registering."); |
|
170 runNextTest(); |
|
171 } |
|
172 |
|
173 // Test callback when creating element after registering an element type. |
|
174 // register -> create element -> insert into document -> remove from document |
|
175 function testRegisterResolved() { |
|
176 var createdCallbackCalled = false; |
|
177 var attachedCallbackCalled = false; |
|
178 var detachedCallbackCalled = false; |
|
179 |
|
180 var createdCallbackThis; |
|
181 |
|
182 var p = Object.create(HTMLElement.prototype); |
|
183 p.createdCallback = function() { |
|
184 is(createdCallbackCalled, false, "Created callback should only be called once in this test."); |
|
185 createdCallbackThis = this; |
|
186 createdCallbackCalled = true; |
|
187 }; |
|
188 |
|
189 p.attachedCallback = function() { |
|
190 is(createdCallbackCalled, true, "created callback should be called before attached callback."); |
|
191 is(attachedCallbackCalled, false, "attached callback should only be called on in this test."); |
|
192 is(this, createdElement, "The 'this' value should be the custom element."); |
|
193 attachedCallbackCalled = true; |
|
194 }; |
|
195 |
|
196 p.detachedCallback = function() { |
|
197 is(attachedCallbackCalled, true, "attached callback should be called before detached"); |
|
198 is(detachedCallbackCalled, false, "detached callback should only be called once in this test."); |
|
199 is(this, createdElement, "The 'this' value should be the custom element."); |
|
200 detachedCallbackCalled = true; |
|
201 runNextTest(); |
|
202 }; |
|
203 |
|
204 p.attributeChangedCallback = function() { |
|
205 ok(false, "attributeChanged callback should never be called in this test."); |
|
206 }; |
|
207 |
|
208 document.registerElement("x-resolved", { prototype: p }); |
|
209 is(createdCallbackCalled, false, "Created callback should not be called when custom element instance has not been created."); |
|
210 |
|
211 var createdElement = document.createElement("x-resolved"); |
|
212 is(createdCallbackThis, createdElement, "The 'this' value in the created callback should be the custom element."); |
|
213 is(createdElement.__proto__, p, "Prototype of custom element should be the registered prototype."); |
|
214 |
|
215 // Insert element into document to trigger attached callback. |
|
216 container.appendChild(createdElement); |
|
217 |
|
218 // Remove element from document to trigger detached callback. |
|
219 container.removeChild(createdElement); |
|
220 } |
|
221 |
|
222 // Callbacks should always be the same ones when registered. |
|
223 function testChangingCallback() { |
|
224 var p = Object.create(HTMLElement.prototype); |
|
225 var callbackCalled = false; |
|
226 p.attributeChangedCallback = function(name, oldValue, newValue) { |
|
227 is(callbackCalled, false, "Callback should only be called once in this test."); |
|
228 callbackCalled = true; |
|
229 runNextTest(); |
|
230 }; |
|
231 |
|
232 document.registerElement("x-test-callback", { prototype: p }); |
|
233 |
|
234 p.attributeChangedCallback = function(name, oldValue, newValue) { |
|
235 ok(false, "Only callbacks at registration should be called."); |
|
236 }; |
|
237 |
|
238 var elem = document.createElement("x-test-callback"); |
|
239 elem.setAttribute("foo", "bar"); |
|
240 } |
|
241 |
|
242 function testAttributeChanged() { |
|
243 var createdCallbackCalled = false; |
|
244 |
|
245 var createdElement; |
|
246 var createdCallbackThis; |
|
247 |
|
248 var p = Object.create(HTMLElement.prototype); |
|
249 p.createdCallback = function() { |
|
250 is(createdCallbackCalled, false, "Created callback should only be called once in this test."); |
|
251 createdCallbackThis = this; |
|
252 createdCallbackCalled = true; |
|
253 }; |
|
254 |
|
255 // Sequence of callback arguments that we expect from attribute changed callback |
|
256 // after changing attributes values in a specific order. |
|
257 var expectedCallbackArguments = [ |
|
258 // [oldValue, newValue] |
|
259 [null, "newvalue"], // Setting the attribute value to "newvalue" |
|
260 ["newvalue", "nextvalue"], // Changing the attribute value from "newvalue" to "nextvalue" |
|
261 ["nextvalue", ""], // Changing the attribute value from "nextvalue" to empty string |
|
262 ["", null], // Removing the attribute. |
|
263 ]; |
|
264 |
|
265 p.attributeChangedCallback = function(name, oldValue, newValue) { |
|
266 is(createdCallbackCalled, true, "created callback should be called before attribute changed."); |
|
267 is(this, createdElement, "The 'this' value should be the custom element."); |
|
268 ok(expectedCallbackArguments.length > 0, "Attribute changed callback should not be called more than expected."); |
|
269 |
|
270 is(name, "changeme", "name arugment in attribute changed callback should be the name of the changed attribute."); |
|
271 |
|
272 var expectedArgs = expectedCallbackArguments.shift(); |
|
273 is(oldValue, expectedArgs[0], "The old value argument should match the expected value."); |
|
274 is(newValue, expectedArgs[1], "The new value argument should match the expected value."); |
|
275 |
|
276 if (expectedCallbackArguments.length === 0) { |
|
277 // Done with the attribute changed callback test. |
|
278 runNextTest(); |
|
279 } |
|
280 }; |
|
281 |
|
282 document.registerElement("x-attrchange", { prototype: p }); |
|
283 |
|
284 var createdElement = document.createElement("x-attrchange"); |
|
285 is(createdCallbackThis, createdElement, "The 'this' value in the created callback should be the custom element."); |
|
286 createdElement.setAttribute("changeme", "newvalue"); |
|
287 createdElement.setAttribute("changeme", "nextvalue"); |
|
288 createdElement.setAttribute("changeme", ""); |
|
289 createdElement.removeAttribute("changeme"); |
|
290 } |
|
291 |
|
292 function testAttributeChangedExtended() { |
|
293 var p = Object.create(HTMLButtonElement.prototype); |
|
294 var callbackCalled = false; |
|
295 p.attributeChangedCallback = function(name, oldValue, newValue) { |
|
296 is(callbackCalled, false, "Callback should only be called once in this test."); |
|
297 callbackCalled = true; |
|
298 runNextTest(); |
|
299 }; |
|
300 |
|
301 document.registerElement("x-extended-attribute-change", { prototype: p, extends: "button" }); |
|
302 |
|
303 var elem = document.createElement("button", "x-extended-attribute-change"); |
|
304 elem.setAttribute("foo", "bar"); |
|
305 } |
|
306 |
|
307 // Creates a custom element that is an upgrade candidate (no registration) |
|
308 // and mutate the element in ways that would call callbacks for registered |
|
309 // elements. |
|
310 function testUpgradeCandidate() { |
|
311 var createdElement = document.createElement("x-upgrade-candidate"); |
|
312 container.appendChild(createdElement); |
|
313 createdElement.setAttribute("foo", "bar"); |
|
314 container.removeChild(createdElement); |
|
315 ok(true, "Nothing bad should happen when trying to mutating upgrade candidates."); |
|
316 runNextTest(); |
|
317 } |
|
318 |
|
319 function testNotInDocEnterLeave() { |
|
320 var p = Object.create(HTMLElement.prototype); |
|
321 |
|
322 p.attached = function() { |
|
323 ok(false, "attached should not be called when not entering the document."); |
|
324 }; |
|
325 |
|
326 p.detached = function() { |
|
327 ok(false, "leaveView should not be called when not leaving the document."); |
|
328 }; |
|
329 |
|
330 var createdElement = document.createElement("x-destined-for-fragment"); |
|
331 |
|
332 document.registerElement("x-destined-for-fragment", { prototype: p }); |
|
333 |
|
334 var fragment = new DocumentFragment(); |
|
335 fragment.appendChild(createdElement); |
|
336 fragment.removeChild(createdElement); |
|
337 |
|
338 var divNotInDoc = document.createElement("div"); |
|
339 divNotInDoc.appendChild(createdElement); |
|
340 divNotInDoc.removeChild(createdElement); |
|
341 |
|
342 runNextTest(); |
|
343 } |
|
344 |
|
345 function testEnterLeaveView() { |
|
346 var attachedCallbackCalled = false; |
|
347 var detachedCallbackCalled = false; |
|
348 |
|
349 var p = Object.create(HTMLElement.prototype); |
|
350 p.attachedCallback = function() { |
|
351 is(attachedCallbackCalled, false, "attached callback should only be called on in this test."); |
|
352 attachedCallbackCalled = true; |
|
353 }; |
|
354 |
|
355 p.detachedCallback = function() { |
|
356 is(attachedCallbackCalled, true, "attached callback should be called before detached"); |
|
357 is(detachedCallbackCalled, false, "detached callback should only be called once in this test."); |
|
358 detachedCallbackCalled = true; |
|
359 runNextTest(); |
|
360 }; |
|
361 |
|
362 var div = document.createElement("div"); |
|
363 document.registerElement("x-element-in-div", { prototype: p }); |
|
364 var customElement = document.createElement("x-element-in-div"); |
|
365 div.appendChild(customElement); |
|
366 is(attachedCallbackCalled, false, "Appending a custom element to a node that is not in the document should not call the attached callback."); |
|
367 |
|
368 container.appendChild(div); |
|
369 container.removeChild(div); |
|
370 } |
|
371 |
|
372 var testFunctions = [ |
|
373 testRegisterUnresolved, |
|
374 testRegisterUnresolvedExtended, |
|
375 testInnerHTML, |
|
376 testInnerHTMLExtended, |
|
377 testInnerHTMLUpgrade, |
|
378 testInnerHTMLExtendedUpgrade, |
|
379 testRegisterResolved, |
|
380 testAttributeChanged, |
|
381 testAttributeChangedExtended, |
|
382 testUpgradeCandidate, |
|
383 testChangingCallback, |
|
384 testNotInDocEnterLeave, |
|
385 testEnterLeaveView, |
|
386 SimpleTest.finish |
|
387 ]; |
|
388 |
|
389 function runNextTest() { |
|
390 if (testFunctions.length > 0) { |
|
391 var nextTestFunction = testFunctions.shift(); |
|
392 nextTestFunction(); |
|
393 } |
|
394 } |
|
395 |
|
396 SimpleTest.waitForExplicitFinish(); |
|
397 |
|
398 runNextTest(); |
|
399 |
|
400 </script> |
|
401 </body> |
|
402 </html> |