|
1 <?xml version="1.0"?> |
|
2 <!-- |
|
3 Any copyright is dedicated to the Public Domain. |
|
4 http://creativecommons.org/publicdomain/zero/1.0/ |
|
5 --> |
|
6 <?xml-stylesheet href="chrome://global/skin" type="text/css"?> |
|
7 <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> |
|
8 |
|
9 <window title="DOMRequestHelper Test" |
|
10 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" |
|
11 onload="start();"> |
|
12 |
|
13 <script type="application/javascript" |
|
14 src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> |
|
15 |
|
16 <script type="application/javascript"> |
|
17 <![CDATA[ |
|
18 const Cc = Components.classes; |
|
19 const Cu = Components.utils; |
|
20 const Ci = Components.interfaces; |
|
21 Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); |
|
22 let obs = Cc["@mozilla.org/observer-service;1"]. |
|
23 getService(Ci.nsIObserverService); |
|
24 |
|
25 function DummyHelperSubclass() { |
|
26 this.onuninit = null; |
|
27 } |
|
28 DummyHelperSubclass.prototype = { |
|
29 __proto__: DOMRequestIpcHelper.prototype, |
|
30 uninit: function() { |
|
31 if (typeof this.onuninit === "function") { |
|
32 this.onuninit(); |
|
33 } |
|
34 this.onuninit = null; |
|
35 } |
|
36 }; |
|
37 |
|
38 var dummy = new DummyHelperSubclass(); |
|
39 |
|
40 /** |
|
41 * Init & destroy. |
|
42 */ |
|
43 function initDOMRequestHelperTest(aMessages) { |
|
44 is(dummy._requests, undefined, "Request is undefined"); |
|
45 is(dummy._messages, undefined, "Messages is undefined"); |
|
46 is(dummy._window, undefined, "Window is undefined"); |
|
47 |
|
48 dummy.initDOMRequestHelper(window, aMessages); |
|
49 |
|
50 ok(dummy._window, "Window exists"); |
|
51 is(dummy._window, window, "Correct window"); |
|
52 if (aMessages) { |
|
53 is(typeof dummy._listeners, "object", "Listeners is an object"); |
|
54 } |
|
55 } |
|
56 |
|
57 function destroyDOMRequestHelperTest() { |
|
58 dummy.destroyDOMRequestHelper(); |
|
59 |
|
60 is(dummy._requests, undefined, "Request is undefined"); |
|
61 is(dummy._messages, undefined, "Messages is undefined"); |
|
62 is(dummy._window, undefined, "Window is undefined"); |
|
63 } |
|
64 |
|
65 /** |
|
66 * Message listeners. |
|
67 */ |
|
68 function checkMessageListeners(aExpectedListeners, aCount) { |
|
69 ok(true, "Checking message listeners\n" + "Expected listeners " + |
|
70 JSON.stringify(aExpectedListeners) + " \nExpected count " + aCount); |
|
71 let count = 0; |
|
72 Object.keys(dummy._listeners).forEach(function(name) { |
|
73 count++; |
|
74 is(aExpectedListeners[name], dummy._listeners[name], |
|
75 "Message found " + name + " - Same listeners"); |
|
76 }); |
|
77 is(aCount, count, "Correct number of listeners"); |
|
78 } |
|
79 |
|
80 function addMessageListenersTest(aMessages, aExpectedListeners, aCount) { |
|
81 dummy.addMessageListeners(aMessages); |
|
82 ok(true, JSON.stringify(dummy._listeners)); |
|
83 checkMessageListeners(aExpectedListeners, aCount); |
|
84 } |
|
85 |
|
86 function removeMessageListenersTest(aMessages, aExpectedListeners, aCount) { |
|
87 dummy.removeMessageListeners(aMessages); |
|
88 checkMessageListeners(aExpectedListeners, aCount); |
|
89 } |
|
90 |
|
91 /** |
|
92 * Utility function to test window destruction behavior. In general this |
|
93 * function does the following: |
|
94 * |
|
95 * 1) Create a new iframe |
|
96 * 2) Create a new DOMRequestHelper |
|
97 * 3) initDOMRequestHelper(), optionally with weak or strong listeners |
|
98 * 4) Optionally force a garbage collection to reap weak references |
|
99 * 5) Destroy the iframe triggering an inner-window-destroyed event |
|
100 * 6) Callback with a boolean indicating if DOMRequestHelper.uninit() was |
|
101 * called. |
|
102 * |
|
103 * Example usage: |
|
104 * |
|
105 * checkWindowDestruction({ messages: ["foo"], gc: true }, |
|
106 * function(uninitCalled) { |
|
107 * // expect uninitCalled === false since GC with only weak refs |
|
108 * }); |
|
109 */ |
|
110 const TOPIC = "inner-window-destroyed"; |
|
111 function checkWindowDestruction(aOptions, aCallback) { |
|
112 aOptions = aOptions || {}; |
|
113 aOptions.messages = aOptions.messages || []; |
|
114 aOptions.gc = !!aOptions.gc; |
|
115 |
|
116 if (typeof aCallback !== "function") { |
|
117 aCallback = function() { }; |
|
118 } |
|
119 |
|
120 let uninitCalled = false; |
|
121 |
|
122 // Use a secondary observer so we know when to expect the uninit(). We |
|
123 // can then reasonably expect uninitCalled to be set properly on the |
|
124 // next tick. |
|
125 let observer = { |
|
126 observe: function(aSubject, aTopic, aData) { |
|
127 if (aTopic !== TOPIC) { |
|
128 return; |
|
129 } |
|
130 obs.removeObserver(observer, TOPIC); |
|
131 setTimeout(function() { |
|
132 aCallback(uninitCalled); |
|
133 }); |
|
134 } |
|
135 }; |
|
136 |
|
137 let frame = document.createElement("iframe"); |
|
138 frame.onload = function() { |
|
139 obs.addObserver(observer, TOPIC, false); |
|
140 // Create dummy DOMRequestHelper specific to checkWindowDestruction() |
|
141 let cwdDummy = new DummyHelperSubclass(); |
|
142 cwdDummy.onuninit = function() { |
|
143 uninitCalled = true; |
|
144 }; |
|
145 cwdDummy.initDOMRequestHelper(frame.contentWindow, aOptions.messages); |
|
146 // Make sure to clear our strong ref here so that we can test our |
|
147 // weak reference listeners and observer. |
|
148 cwdDummy = null; |
|
149 if (aOptions.gc) { |
|
150 Cu.schedulePreciseGC(function() { |
|
151 SpecialPowers.DOMWindowUtils.cycleCollect(); |
|
152 SpecialPowers.DOMWindowUtils.garbageCollect(); |
|
153 SpecialPowers.DOMWindowUtils.garbageCollect(); |
|
154 document.documentElement.removeChild(frame); |
|
155 }); |
|
156 return; |
|
157 } |
|
158 document.documentElement.removeChild(frame); |
|
159 }; |
|
160 document.documentElement.appendChild(frame); |
|
161 } |
|
162 |
|
163 /** |
|
164 * Test steps. |
|
165 */ |
|
166 var tests = [ |
|
167 function() { |
|
168 ok(true, "== InitDOMRequestHelper no messages"); |
|
169 initDOMRequestHelperTest(null); |
|
170 next(); |
|
171 }, |
|
172 function() { |
|
173 ok(true, "== DestroyDOMRequestHelper"); |
|
174 destroyDOMRequestHelperTest(); |
|
175 next(); |
|
176 }, |
|
177 function() { |
|
178 ok(true, "== InitDOMRequestHelper empty array"); |
|
179 initDOMRequestHelperTest([]); |
|
180 checkMessageListeners({}, 0); |
|
181 next(); |
|
182 }, |
|
183 function() { |
|
184 ok(true, "== DestroyDOMRequestHelper"); |
|
185 destroyDOMRequestHelperTest(); |
|
186 next(); |
|
187 }, |
|
188 function() { |
|
189 ok(true, "== InitDOMRequestHelper with strings array"); |
|
190 initDOMRequestHelperTest(["name1", "nameN"]); |
|
191 checkMessageListeners({"name1": false, "nameN": false}, 2); |
|
192 next(); |
|
193 }, |
|
194 function() { |
|
195 ok(true, "== DestroyDOMRequestHelper"); |
|
196 destroyDOMRequestHelperTest(); |
|
197 next(); |
|
198 }, |
|
199 function() { |
|
200 ok(true, "== InitDOMRequestHelper with objects array"); |
|
201 initDOMRequestHelperTest([{ |
|
202 name: "name1", |
|
203 weakRef: false |
|
204 }, { |
|
205 name: "nameN", |
|
206 weakRef: true |
|
207 }]); |
|
208 checkMessageListeners({"name1": false, "nameN": true}, 2); |
|
209 next(); |
|
210 }, |
|
211 function() { |
|
212 ok(true, "== AddMessageListeners empty array"); |
|
213 addMessageListenersTest([], {"name1": false, "nameN": true}, 2); |
|
214 next(); |
|
215 }, |
|
216 function() { |
|
217 ok(true, "== AddMessageListeners null"); |
|
218 addMessageListenersTest(null, {"name1": false, "nameN": true}, 2); |
|
219 next(); |
|
220 }, |
|
221 function() { |
|
222 ok(true, "== AddMessageListeners new listener, string only"); |
|
223 addMessageListenersTest("name2", { |
|
224 "name1": false, |
|
225 "name2": false, |
|
226 "nameN": true |
|
227 }, 3); |
|
228 next(); |
|
229 }, |
|
230 function() { |
|
231 ok(true, "== AddMessageListeners new listeners, strings array"); |
|
232 addMessageListenersTest(["name3", "name4"], { |
|
233 "name1": false, |
|
234 "name2": false, |
|
235 "name3": false, |
|
236 "name4": false, |
|
237 "nameN": true |
|
238 }, 5); |
|
239 next(); |
|
240 }, |
|
241 function() { |
|
242 ok(true, "== AddMessageListeners new listeners, objects array"); |
|
243 addMessageListenersTest([{ |
|
244 name: "name5", |
|
245 weakRef: true |
|
246 }, { |
|
247 name: "name6", |
|
248 weakRef: false |
|
249 }], { |
|
250 "name1": false, |
|
251 "name2": false, |
|
252 "name3": false, |
|
253 "name4": false, |
|
254 "name5": true, |
|
255 "name6": false, |
|
256 "nameN": true |
|
257 }, 7); |
|
258 next(); |
|
259 }, |
|
260 function() { |
|
261 ok(true, "== RemoveMessageListeners, null"); |
|
262 removeMessageListenersTest(null, { |
|
263 "name1": false, |
|
264 "name2": false, |
|
265 "name3": false, |
|
266 "name4": false, |
|
267 "name5": true, |
|
268 "name6": false, |
|
269 "nameN": true |
|
270 }, 7); |
|
271 next(); |
|
272 }, |
|
273 function() { |
|
274 ok(true, "== RemoveMessageListeners, one message"); |
|
275 removeMessageListenersTest("name1", { |
|
276 "name2": false, |
|
277 "name3": false, |
|
278 "name4": false, |
|
279 "name5": true, |
|
280 "name6": false, |
|
281 "nameN": true |
|
282 }, 6); |
|
283 next(); |
|
284 }, |
|
285 function() { |
|
286 ok(true, "== RemoveMessageListeners, array of messages"); |
|
287 removeMessageListenersTest(["name2", "name3"], { |
|
288 "name4": false, |
|
289 "name5": true, |
|
290 "name6": false, |
|
291 "nameN": true |
|
292 }, 4); |
|
293 next(); |
|
294 }, |
|
295 function() { |
|
296 ok(true, "== RemoveMessageListeners, unknown message"); |
|
297 removeMessageListenersTest("unknown", { |
|
298 "name4": false, |
|
299 "name5": true, |
|
300 "name6": false, |
|
301 "nameN": true |
|
302 }, 4); |
|
303 next(); |
|
304 }, |
|
305 function() { |
|
306 try { |
|
307 ok(true, "== AddMessageListeners, same message, same kind"); |
|
308 addMessageListenersTest("name4", { |
|
309 "name4": false, |
|
310 "name5": true, |
|
311 "name6": false, |
|
312 "nameN": true |
|
313 }, 4); |
|
314 next(); |
|
315 } catch (ex) { |
|
316 ok(false, "Unexpected exception " + ex); |
|
317 } |
|
318 }, |
|
319 function() { |
|
320 ok(true, "== AddMessageListeners, same message, different kind"); |
|
321 try { |
|
322 addMessageListenersTest({name: "name4", weakRef: true}, { |
|
323 "name4": true, |
|
324 "name5": true, |
|
325 "name6": false, |
|
326 "nameN": true |
|
327 }, 4); |
|
328 ok(false, "Should have thrown an exception"); |
|
329 } catch (ex) { |
|
330 ok(true, "Expected exception"); |
|
331 next(); |
|
332 } |
|
333 }, |
|
334 function() { |
|
335 ok(true, "== Test createRequest()"); |
|
336 ok(DOMRequest, "DOMRequest object exists"); |
|
337 var req = dummy.createRequest(); |
|
338 ok(req instanceof DOMRequest, "Returned a DOMRequest"); |
|
339 next(); |
|
340 }, |
|
341 function() { |
|
342 ok(true, "== Test getRequestId(), removeRequest() and getRequest()"); |
|
343 var req = dummy.createRequest(); |
|
344 var id = dummy.getRequestId(req); |
|
345 is(typeof id, "string", "id is a string"); |
|
346 var req_ = dummy.getRequest(id); |
|
347 is(req, req_, "Got correct request"); |
|
348 dummy.removeRequest(id); |
|
349 req = dummy.getRequest(id); |
|
350 is(req, null, "No request"); |
|
351 next(); |
|
352 }, |
|
353 function() { |
|
354 ok(true, "== Test createPromise()"); |
|
355 ok(Promise, "Promise object exists"); |
|
356 var promise = dummy.createPromise(function(resolve, reject) { |
|
357 resolve(true); |
|
358 }); |
|
359 ok(promise instanceof Promise, "Returned a Promise"); |
|
360 promise.then(next); |
|
361 }, |
|
362 function() { |
|
363 ok(true, "== Test getResolver()"); |
|
364 var id; |
|
365 var resolver; |
|
366 var promise = dummy.createPromise(function(resolve, reject) { |
|
367 var r = { resolve: resolve, reject: reject }; |
|
368 id = dummy.getPromiseResolverId(r); |
|
369 resolver = r; |
|
370 ok(typeof id === "string", "id is a string"); |
|
371 r.resolve(true); |
|
372 }).then(function(unused) { |
|
373 var r = dummy.getPromiseResolver(id); |
|
374 ok(resolver === r, "Get succeeded"); |
|
375 next(); |
|
376 }); |
|
377 }, |
|
378 function() { |
|
379 ok(true, "== Test removeResolver"); |
|
380 var id; |
|
381 var promise = dummy.createPromise(function(resolve, reject) { |
|
382 var r = { resolve: resolve, reject: reject }; |
|
383 id = dummy.getPromiseResolverId(r); |
|
384 ok(typeof id === "string", "id is a string"); |
|
385 |
|
386 var resolver = dummy.getPromiseResolver(id); |
|
387 ok(true, "Got resolver " + JSON.stringify(resolver)); |
|
388 ok(resolver === r, "Resolver get succeeded"); |
|
389 |
|
390 r.resolve(true); |
|
391 }).then(function(unused) { |
|
392 dummy.removePromiseResolver(id); |
|
393 var resolver = dummy.getPromiseResolver(id); |
|
394 ok(resolver === undefined, "removeResolver: get failed"); |
|
395 next(); |
|
396 }); |
|
397 }, |
|
398 function() { |
|
399 ok(true, "== Test takeResolver"); |
|
400 var id; |
|
401 var resolver; |
|
402 var promise = dummy.createPromise(function(resolve, reject) { |
|
403 var r = { resolve: resolve, reject: reject }; |
|
404 id = dummy.getPromiseResolverId(r); |
|
405 resolver = r; |
|
406 ok(typeof id === "string", "id is a string"); |
|
407 |
|
408 var gotR = dummy.getPromiseResolver(id); |
|
409 ok(gotR === r, "resolver get succeeded"); |
|
410 |
|
411 r.resolve(true); |
|
412 }).then(function(unused) { |
|
413 var r = dummy.takePromiseResolver(id); |
|
414 ok(resolver === r, "take should succeed"); |
|
415 |
|
416 r = dummy.getPromiseResolver(id); |
|
417 ok(r === undefined, "takeResolver: get failed"); |
|
418 next(); |
|
419 }); |
|
420 }, |
|
421 function() { |
|
422 ok(true, "== Test window destroyed without messages and without GC"); |
|
423 checkWindowDestruction({ gc: false }, function(uninitCalled) { |
|
424 ok(uninitCalled, "uninit() should have been called"); |
|
425 next(); |
|
426 }); |
|
427 }, |
|
428 function() { |
|
429 ok(true, "== Test window destroyed without messages and with GC"); |
|
430 checkWindowDestruction({ gc: true }, function(uninitCalled) { |
|
431 ok(!uninitCalled, "uninit() should NOT have been called"); |
|
432 next(); |
|
433 }); |
|
434 }, |
|
435 function() { |
|
436 ok(true, "== Test window destroyed with weak messages and without GC"); |
|
437 checkWindowDestruction({ messages: [{ name: "foo", weakRef: true }], |
|
438 gc: false }, function(uninitCalled) { |
|
439 ok(uninitCalled, "uninit() should have been called"); |
|
440 next(); |
|
441 }); |
|
442 }, |
|
443 function() { |
|
444 ok(true, "== Test window destroyed with weak messages and with GC"); |
|
445 checkWindowDestruction({ messages: [{ name: "foo", weakRef: true }], |
|
446 gc: true }, function(uninitCalled) { |
|
447 ok(!uninitCalled, "uninit() should NOT have been called"); |
|
448 next(); |
|
449 }); |
|
450 }, |
|
451 function() { |
|
452 ok(true, "== Test window destroyed with strong messages and without GC"); |
|
453 checkWindowDestruction({ messages: [{ name: "foo", weakRef: false }], |
|
454 gc: false }, function(uninitCalled) { |
|
455 ok(uninitCalled, "uninit() should have been called"); |
|
456 next(); |
|
457 }); |
|
458 }, |
|
459 function() { |
|
460 ok(true, "== Test window destroyed with strong messages and with GC"); |
|
461 checkWindowDestruction({ messages: [{ name: "foo", weakRef: false }], |
|
462 gc: true }, function(uninitCalled) { |
|
463 ok(uninitCalled, "uninit() should have been called"); |
|
464 next(); |
|
465 }); |
|
466 } |
|
467 ]; |
|
468 |
|
469 function next() { |
|
470 if (!tests.length) { |
|
471 SimpleTest.finish(); |
|
472 return; |
|
473 } |
|
474 |
|
475 var test = tests.shift(); |
|
476 test(); |
|
477 } |
|
478 |
|
479 function start() { |
|
480 SimpleTest.waitForExplicitFinish(); |
|
481 next(); |
|
482 } |
|
483 ]]> |
|
484 </script> |
|
485 |
|
486 <body xmlns="http://www.w3.org/1999/xhtml"> |
|
487 <p id="display"></p> |
|
488 <div id="content" style="display: none"></div> |
|
489 <pre id="test"></pre> |
|
490 </body> |
|
491 </window> |