|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 'use strict'; |
|
5 |
|
6 const { Cc, Ci } = require('chrome'); |
|
7 const { Loader, LoaderWithHookedConsole } = require('sdk/test/loader'); |
|
8 const timer = require('sdk/timers'); |
|
9 const tabs = require('sdk/tabs'); |
|
10 const windows = require('sdk/windows'); |
|
11 const { set: setPref } = require("sdk/preferences/service"); |
|
12 const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings"; |
|
13 |
|
14 const tabsLen = tabs.length; |
|
15 const URL = 'data:text/html;charset=utf-8,<html><head><title>#title#</title></head></html>'; |
|
16 |
|
17 // Fennec error message dispatched on all currently unimplement tab features, |
|
18 // that match LoaderWithHookedConsole messages object pattern |
|
19 const ERR_FENNEC_MSG = { |
|
20 type: "error", |
|
21 msg: "This method is not yet supported by Fennec" |
|
22 }; |
|
23 |
|
24 // TEST: tab unloader |
|
25 exports.testAutomaticDestroy = function(assert, done) { |
|
26 let called = false; |
|
27 |
|
28 let loader2 = Loader(module); |
|
29 let loader3 = Loader(module); |
|
30 let tabs2 = loader2.require('sdk/tabs'); |
|
31 let tabs3 = loader3.require('sdk/tabs'); |
|
32 let tabs2Len = tabs2.length; |
|
33 |
|
34 tabs2.on('open', function onOpen(tab) { |
|
35 assert.fail("an onOpen listener was called that should not have been"); |
|
36 called = true; |
|
37 }); |
|
38 tabs2.on('ready', function onReady(tab) { |
|
39 assert.fail("an onReady listener was called that should not have been"); |
|
40 called = true; |
|
41 }); |
|
42 tabs2.on('select', function onSelect(tab) { |
|
43 assert.fail("an onSelect listener was called that should not have been"); |
|
44 called = true; |
|
45 }); |
|
46 tabs2.on('close', function onClose(tab) { |
|
47 assert.fail("an onClose listener was called that should not have been"); |
|
48 called = true; |
|
49 }); |
|
50 loader2.unload(); |
|
51 |
|
52 tabs3.on('open', function onOpen(tab) { |
|
53 assert.pass("an onOpen listener was called for tabs3"); |
|
54 |
|
55 tab.on('ready', function onReady(tab) { |
|
56 assert.fail("an onReady listener was called that should not have been"); |
|
57 called = true; |
|
58 }); |
|
59 tab.on('select', function onSelect(tab) { |
|
60 assert.fail("an onSelect listener was called that should not have been"); |
|
61 called = true; |
|
62 }); |
|
63 tab.on('close', function onClose(tab) { |
|
64 assert.fail("an onClose listener was called that should not have been"); |
|
65 called = true; |
|
66 }); |
|
67 }); |
|
68 tabs3.open(URL.replace(/#title#/, 'tabs3')); |
|
69 loader3.unload(); |
|
70 |
|
71 // Fire a tab event and ensure that the destroyed tab is inactive |
|
72 tabs.once('open', function(tab) { |
|
73 assert.pass('tabs.once("open") works!'); |
|
74 |
|
75 assert.equal(tabs2Len, tabs2.length, "tabs2 length was not changed"); |
|
76 assert.equal(tabs.length, (tabs2.length+2), "tabs.length > tabs2.length"); |
|
77 |
|
78 tab.once('ready', function() { |
|
79 assert.pass('tab.once("ready") works!'); |
|
80 |
|
81 tab.once('close', function() { |
|
82 assert.pass('tab.once("close") works!'); |
|
83 |
|
84 timer.setTimeout(function () { |
|
85 assert.ok(!called, "Unloaded tab module is destroyed and inactive"); |
|
86 |
|
87 // end test |
|
88 done(); |
|
89 }); |
|
90 }); |
|
91 |
|
92 tab.close(); |
|
93 }); |
|
94 }); |
|
95 |
|
96 tabs.open('data:text/html;charset=utf-8,foo'); |
|
97 }; |
|
98 |
|
99 // TEST: tab properties |
|
100 exports.testTabProperties = function(assert, done) { |
|
101 setPref(DEPRECATE_PREF, true); |
|
102 let { loader, messages } = LoaderWithHookedConsole(); |
|
103 let tabs = loader.require('sdk/tabs'); |
|
104 |
|
105 let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>"; |
|
106 let tabsLen = tabs.length; |
|
107 tabs.open({ |
|
108 url: url, |
|
109 onReady: function(tab) { |
|
110 assert.equal(tab.title, "foo", "title of the new tab matches"); |
|
111 assert.equal(tab.url, url, "URL of the new tab matches"); |
|
112 assert.ok(tab.favicon, "favicon of the new tab is not empty"); |
|
113 // TODO: remove need for this test by implementing the favicon feature |
|
114 assert.equal(messages[0].msg, |
|
115 "tab.favicon is deprecated, and " + |
|
116 "currently favicon helpers are not yet supported " + |
|
117 "by Fennec", |
|
118 "favicon logs an error for now"); |
|
119 assert.equal(tab.style, null, "style of the new tab matches"); |
|
120 assert.equal(tab.index, tabsLen, "index of the new tab matches"); |
|
121 assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches"); |
|
122 assert.notEqual(tab.id, null, "a tab object always has an id property"); |
|
123 |
|
124 tab.close(function() { |
|
125 loader.unload(); |
|
126 |
|
127 // end test |
|
128 done(); |
|
129 }); |
|
130 } |
|
131 }); |
|
132 }; |
|
133 |
|
134 // TEST: tabs iterator and length property |
|
135 exports.testTabsIteratorAndLength = function(assert, done) { |
|
136 let newTabs = []; |
|
137 let startCount = 0; |
|
138 for each (let t in tabs) startCount++; |
|
139 |
|
140 assert.equal(startCount, tabs.length, "length property is correct"); |
|
141 |
|
142 let url = "data:text/html;charset=utf-8,testTabsIteratorAndLength"; |
|
143 tabs.open({url: url, onOpen: function(tab) newTabs.push(tab)}); |
|
144 tabs.open({url: url, onOpen: function(tab) newTabs.push(tab)}); |
|
145 tabs.open({ |
|
146 url: url, |
|
147 onOpen: function(tab) { |
|
148 let count = 0; |
|
149 for each (let t in tabs) count++; |
|
150 assert.equal(count, startCount + 3, "iterated tab count matches"); |
|
151 assert.equal(startCount + 3, tabs.length, "iterated tab count matches length property"); |
|
152 |
|
153 let newTabsLength = newTabs.length; |
|
154 newTabs.forEach(function(t) t.close(function() { |
|
155 if (--newTabsLength > 0) return; |
|
156 |
|
157 tab.close(done); |
|
158 })); |
|
159 } |
|
160 }); |
|
161 }; |
|
162 |
|
163 // TEST: tab.url setter |
|
164 exports.testTabLocation = function(assert, done) { |
|
165 let url1 = "data:text/html;charset=utf-8,foo"; |
|
166 let url2 = "data:text/html;charset=utf-8,bar"; |
|
167 |
|
168 tabs.on('ready', function onReady(tab) { |
|
169 if (tab.url != url2) |
|
170 return; |
|
171 |
|
172 tabs.removeListener('ready', onReady); |
|
173 assert.pass("tab loaded the correct url"); |
|
174 |
|
175 tab.close(done); |
|
176 }); |
|
177 |
|
178 tabs.open({ |
|
179 url: url1, |
|
180 onOpen: function(tab) { |
|
181 tab.url = url2; |
|
182 } |
|
183 }); |
|
184 }; |
|
185 |
|
186 // TEST: tab.move() |
|
187 exports.testTabMove = function(assert, done) { |
|
188 let { loader, messages } = LoaderWithHookedConsole(); |
|
189 let tabs = loader.require('sdk/tabs'); |
|
190 |
|
191 let url = "data:text/html;charset=utf-8,testTabMove"; |
|
192 |
|
193 tabs.open({ |
|
194 url: url, |
|
195 onOpen: function(tab1) { |
|
196 assert.ok(tab1.index >= 0, "opening a tab returns a tab w/ valid index"); |
|
197 |
|
198 tabs.open({ |
|
199 url: url, |
|
200 onOpen: function(tab) { |
|
201 let i = tab.index; |
|
202 assert.ok(tab.index > tab1.index, "2nd tab has valid index"); |
|
203 tab.index = 0; |
|
204 assert.equal(tab.index, i, "tab index after move matches"); |
|
205 assert.equal(JSON.stringify(messages), |
|
206 JSON.stringify([ERR_FENNEC_MSG]), |
|
207 "setting tab.index logs error"); |
|
208 // end test |
|
209 tab1.close(function() tab.close(function() { |
|
210 loader.unload(); |
|
211 done(); |
|
212 })); |
|
213 } |
|
214 }); |
|
215 } |
|
216 }); |
|
217 }; |
|
218 |
|
219 // TEST: open tab with default options |
|
220 exports.testTabsOpen_alt = function(assert, done) { |
|
221 let { loader, messages } = LoaderWithHookedConsole(); |
|
222 let tabs = loader.require('sdk/tabs'); |
|
223 let url = "data:text/html;charset=utf-8,default"; |
|
224 |
|
225 tabs.open({ |
|
226 url: url, |
|
227 onReady: function(tab) { |
|
228 assert.equal(tab.url, url, "URL of the new tab matches"); |
|
229 assert.equal(tabs.activeTab, tab, "URL of active tab in the current window matches"); |
|
230 assert.equal(tab.isPinned, false, "The new tab is not pinned"); |
|
231 assert.equal(messages.length, 1, "isPinned logs error"); |
|
232 |
|
233 // end test |
|
234 tab.close(function() { |
|
235 loader.unload(); |
|
236 done(); |
|
237 }); |
|
238 } |
|
239 }); |
|
240 }; |
|
241 |
|
242 // TEST: open pinned tab |
|
243 exports.testOpenPinned_alt = function(assert, done) { |
|
244 let { loader, messages } = LoaderWithHookedConsole(); |
|
245 let tabs = loader.require('sdk/tabs'); |
|
246 let url = "about:blank"; |
|
247 |
|
248 tabs.open({ |
|
249 url: url, |
|
250 isPinned: true, |
|
251 onOpen: function(tab) { |
|
252 assert.equal(tab.isPinned, false, "The new tab is pinned"); |
|
253 // We get two error message: one for tabs.open's isPinned argument |
|
254 // and another one for tab.isPinned |
|
255 assert.equal(JSON.stringify(messages), |
|
256 JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]), |
|
257 "isPinned logs error"); |
|
258 |
|
259 // end test |
|
260 tab.close(function() { |
|
261 loader.unload(); |
|
262 done(); |
|
263 }); |
|
264 } |
|
265 }); |
|
266 }; |
|
267 |
|
268 // TEST: pin/unpin opened tab |
|
269 exports.testPinUnpin_alt = function(assert, done) { |
|
270 let { loader, messages } = LoaderWithHookedConsole(); |
|
271 let tabs = loader.require('sdk/tabs'); |
|
272 let url = "data:text/html;charset=utf-8,default"; |
|
273 |
|
274 tabs.open({ |
|
275 url: url, |
|
276 onOpen: function(tab) { |
|
277 tab.pin(); |
|
278 assert.equal(tab.isPinned, false, "The tab was pinned correctly"); |
|
279 assert.equal(JSON.stringify(messages), |
|
280 JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]), |
|
281 "tab.pin() logs error"); |
|
282 |
|
283 // Clear console messages for the following test |
|
284 messages.length = 0; |
|
285 |
|
286 tab.unpin(); |
|
287 assert.equal(tab.isPinned, false, "The tab was unpinned correctly"); |
|
288 assert.equal(JSON.stringify(messages), |
|
289 JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]), |
|
290 "tab.unpin() logs error"); |
|
291 |
|
292 // end test |
|
293 tab.close(function() { |
|
294 loader.unload(); |
|
295 done(); |
|
296 }); |
|
297 } |
|
298 }); |
|
299 }; |
|
300 |
|
301 // TEST: open tab in background |
|
302 exports.testInBackground = function(assert, done) { |
|
303 let activeUrl = tabs.activeTab.url; |
|
304 let url = "data:text/html;charset=utf-8,background"; |
|
305 let window = windows.browserWindows.activeWindow; |
|
306 tabs.once('ready', function onReady(tab) { |
|
307 assert.equal(tabs.activeTab.url, activeUrl, "URL of active tab has not changed"); |
|
308 assert.equal(tab.url, url, "URL of the new background tab matches"); |
|
309 assert.equal(windows.browserWindows.activeWindow, window, "a new window was not opened"); |
|
310 assert.notEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL"); |
|
311 |
|
312 // end test |
|
313 tab.close(done); |
|
314 }); |
|
315 |
|
316 tabs.open({ |
|
317 url: url, |
|
318 inBackground: true |
|
319 }); |
|
320 }; |
|
321 |
|
322 // TEST: open tab in new window |
|
323 exports.testOpenInNewWindow = function(assert, done) { |
|
324 let url = "data:text/html;charset=utf-8,newwindow"; |
|
325 let window = windows.browserWindows.activeWindow; |
|
326 |
|
327 tabs.open({ |
|
328 url: url, |
|
329 inNewWindow: true, |
|
330 onReady: function(tab) { |
|
331 assert.equal(windows.browserWindows.length, 1, "a new window was not opened"); |
|
332 assert.equal(windows.browserWindows.activeWindow, window, "old window is active"); |
|
333 assert.equal(tab.url, url, "URL of the new tab matches"); |
|
334 assert.equal(tabs.activeTab, tab, "tab is the activeTab"); |
|
335 |
|
336 tab.close(done); |
|
337 } |
|
338 }); |
|
339 }; |
|
340 |
|
341 // TEST: onOpen event handler |
|
342 exports.testTabsEvent_onOpen = function(assert, done) { |
|
343 let url = URL.replace('#title#', 'testTabsEvent_onOpen'); |
|
344 let eventCount = 0; |
|
345 |
|
346 // add listener via property assignment |
|
347 function listener1(tab) { |
|
348 eventCount++; |
|
349 }; |
|
350 tabs.on('open', listener1); |
|
351 |
|
352 // add listener via collection add |
|
353 tabs.on('open', function listener2(tab) { |
|
354 assert.equal(++eventCount, 2, "both listeners notified"); |
|
355 tabs.removeListener('open', listener1); |
|
356 tabs.removeListener('open', listener2); |
|
357 |
|
358 // ends test |
|
359 tab.close(done); |
|
360 }); |
|
361 |
|
362 tabs.open(url); |
|
363 }; |
|
364 |
|
365 // TEST: onClose event handler |
|
366 exports.testTabsEvent_onClose = function(assert, done) { |
|
367 let url = "data:text/html;charset=utf-8,onclose"; |
|
368 let eventCount = 0; |
|
369 |
|
370 // add listener via property assignment |
|
371 function listener1(tab) { |
|
372 eventCount++; |
|
373 } |
|
374 tabs.on('close', listener1); |
|
375 |
|
376 // add listener via collection add |
|
377 tabs.on('close', function listener2(tab) { |
|
378 assert.equal(++eventCount, 2, "both listeners notified"); |
|
379 tabs.removeListener('close', listener1); |
|
380 tabs.removeListener('close', listener2); |
|
381 |
|
382 // end test |
|
383 done(); |
|
384 }); |
|
385 |
|
386 tabs.on('ready', function onReady(tab) { |
|
387 tabs.removeListener('ready', onReady); |
|
388 tab.close(); |
|
389 }); |
|
390 |
|
391 tabs.open(url); |
|
392 }; |
|
393 |
|
394 // TEST: onClose event handler when a window is closed |
|
395 exports.testTabsEvent_onCloseWindow = function(assert, done) { |
|
396 let closeCount = 0, individualCloseCount = 0; |
|
397 function listener() { |
|
398 closeCount++; |
|
399 } |
|
400 tabs.on('close', listener); |
|
401 |
|
402 // One tab is already open with the window |
|
403 let openTabs = 0; |
|
404 function testCasePossiblyLoaded(tab) { |
|
405 tab.close(function() { |
|
406 if (++openTabs == 3) { |
|
407 tabs.removeListener("close", listener); |
|
408 |
|
409 assert.equal(closeCount, 3, "Correct number of close events received"); |
|
410 assert.equal(individualCloseCount, 3, |
|
411 "Each tab with an attached onClose listener received a close " + |
|
412 "event when the window was closed"); |
|
413 |
|
414 done(); |
|
415 } |
|
416 }); |
|
417 } |
|
418 |
|
419 tabs.open({ |
|
420 url: "data:text/html;charset=utf-8,tab2", |
|
421 onOpen: testCasePossiblyLoaded, |
|
422 onClose: function() individualCloseCount++ |
|
423 }); |
|
424 |
|
425 tabs.open({ |
|
426 url: "data:text/html;charset=utf-8,tab3", |
|
427 onOpen: testCasePossiblyLoaded, |
|
428 onClose: function() individualCloseCount++ |
|
429 }); |
|
430 |
|
431 tabs.open({ |
|
432 url: "data:text/html;charset=utf-8,tab4", |
|
433 onOpen: testCasePossiblyLoaded, |
|
434 onClose: function() individualCloseCount++ |
|
435 }); |
|
436 }; |
|
437 |
|
438 // TEST: onReady event handler |
|
439 exports.testTabsEvent_onReady = function(assert, done) { |
|
440 let url = "data:text/html;charset=utf-8,onready"; |
|
441 let eventCount = 0; |
|
442 |
|
443 // add listener via property assignment |
|
444 function listener1(tab) { |
|
445 eventCount++; |
|
446 }; |
|
447 tabs.on('ready', listener1); |
|
448 |
|
449 // add listener via collection add |
|
450 tabs.on('ready', function listener2(tab) { |
|
451 assert.equal(++eventCount, 2, "both listeners notified"); |
|
452 tabs.removeListener('ready', listener1); |
|
453 tabs.removeListener('ready', listener2); |
|
454 |
|
455 // end test |
|
456 tab.close(done); |
|
457 }); |
|
458 |
|
459 tabs.open(url); |
|
460 }; |
|
461 |
|
462 // TEST: onActivate event handler |
|
463 exports.testTabsEvent_onActivate = function(assert, done) { |
|
464 let url = "data:text/html;charset=utf-8,onactivate"; |
|
465 let eventCount = 0; |
|
466 |
|
467 // add listener via property assignment |
|
468 function listener1(tab) { |
|
469 eventCount++; |
|
470 }; |
|
471 tabs.on('activate', listener1); |
|
472 |
|
473 // add listener via collection add |
|
474 tabs.on('activate', function listener2(tab) { |
|
475 assert.equal(++eventCount, 2, "both listeners notified"); |
|
476 assert.equal(tab, tabs.activeTab, 'the active tab is correct'); |
|
477 tabs.removeListener('activate', listener1); |
|
478 tabs.removeListener('activate', listener2); |
|
479 |
|
480 // end test |
|
481 tab.close(done); |
|
482 }); |
|
483 |
|
484 tabs.open(url); |
|
485 }; |
|
486 |
|
487 // TEST: onDeactivate event handler |
|
488 exports.testTabsEvent_onDeactivate = function(assert, done) { |
|
489 let url = "data:text/html;charset=utf-8,ondeactivate"; |
|
490 let eventCount = 0; |
|
491 |
|
492 // add listener via property assignment |
|
493 function listener1(tab) { |
|
494 eventCount++; |
|
495 }; |
|
496 tabs.on('deactivate', listener1); |
|
497 |
|
498 // add listener via collection add |
|
499 tabs.on('deactivate', function listener2(tab) { |
|
500 assert.equal(++eventCount, 2, 'both listeners notified'); |
|
501 assert.notEqual(tab, tabs.activeTab, 'the active tab is not the deactivated tab'); |
|
502 tabs.removeListener('deactivate', listener1); |
|
503 tabs.removeListener('deactivate', listener2); |
|
504 |
|
505 // end test |
|
506 tab.close(done); |
|
507 }); |
|
508 |
|
509 tabs.on('activate', function onActivate(tab) { |
|
510 tabs.removeListener('activate', onActivate); |
|
511 tabs.open("data:text/html;charset=utf-8,foo"); |
|
512 tab.close(); |
|
513 }); |
|
514 |
|
515 tabs.open(url); |
|
516 }; |
|
517 |
|
518 // TEST: per-tab event handlers |
|
519 exports.testPerTabEvents = function(assert, done) { |
|
520 let eventCount = 0; |
|
521 |
|
522 tabs.open({ |
|
523 url: "data:text/html;charset=utf-8,foo", |
|
524 onOpen: function(tab) { |
|
525 // add listener via property assignment |
|
526 function listener1() { |
|
527 eventCount++; |
|
528 }; |
|
529 tab.on('ready', listener1); |
|
530 |
|
531 // add listener via collection add |
|
532 tab.on('ready', function listener2() { |
|
533 assert.equal(eventCount, 1, "both listeners notified"); |
|
534 tab.removeListener('ready', listener1); |
|
535 tab.removeListener('ready', listener2); |
|
536 |
|
537 // end test |
|
538 tab.close(done); |
|
539 }); |
|
540 } |
|
541 }); |
|
542 }; |
|
543 |
|
544 exports.testUniqueTabIds = function(assert, done) { |
|
545 var tabs = require('sdk/tabs'); |
|
546 var tabIds = {}; |
|
547 var steps = [ |
|
548 function (index) { |
|
549 tabs.open({ |
|
550 url: "data:text/html;charset=utf-8,foo", |
|
551 onOpen: function(tab) { |
|
552 tabIds['tab1'] = tab.id; |
|
553 next(index); |
|
554 } |
|
555 }); |
|
556 }, |
|
557 function (index) { |
|
558 tabs.open({ |
|
559 url: "data:text/html;charset=utf-8,bar", |
|
560 onOpen: function(tab) { |
|
561 tabIds['tab2'] = tab.id; |
|
562 next(index); |
|
563 } |
|
564 }); |
|
565 }, |
|
566 function (index) { |
|
567 assert.notEqual(tabIds.tab1, tabIds.tab2, "Tab ids should be unique."); |
|
568 done(); |
|
569 } |
|
570 ]; |
|
571 |
|
572 function next(index) { |
|
573 if (index === steps.length) { |
|
574 return; |
|
575 } |
|
576 let fn = steps[index]; |
|
577 index++; |
|
578 fn(index); |
|
579 } |
|
580 |
|
581 next(0); |
|
582 } |
|
583 |
|
584 exports.testOnLoadEventWithDOM = function(assert, done) { |
|
585 let count = 0; |
|
586 let title = 'testOnLoadEventWithDOM'; |
|
587 |
|
588 tabs.open({ |
|
589 url: 'data:text/html;charset=utf-8,<title>' + title + '</title>', |
|
590 inBackground: true, |
|
591 onLoad: function(tab) { |
|
592 assert.equal(tab.title, title, 'tab passed in as arg, load called'); |
|
593 |
|
594 if (++count > 1) { |
|
595 assert.pass('onLoad event called on reload'); |
|
596 tab.close(done); |
|
597 } |
|
598 else { |
|
599 assert.pass('first onLoad event occured'); |
|
600 tab.reload(); |
|
601 } |
|
602 } |
|
603 }); |
|
604 }; |
|
605 |
|
606 require('sdk/test').run(exports); |