/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 'use strict'; const { Cc, Ci } = require('chrome'); const { Loader } = require('sdk/test/loader'); const systemEvents = require("sdk/system/events"); const { setTimeout, setImmediate } = require('sdk/timers'); const { modelFor } = require('sdk/model/core'); const { viewFor } = require('sdk/view/core'); const { getOwnerWindow } = require('sdk/tabs/utils'); const { windows, onFocus, getMostRecentBrowserWindow } = require('sdk/window/utils'); const { open, focus, close } = require('sdk/window/helpers'); const { observer: windowObserver } = require("sdk/windows/observer"); const tabs = require('sdk/tabs'); const { browserWindows } = require('sdk/windows'); const { set: setPref, get: getPref, reset: resetPref } = require("sdk/preferences/service"); const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings"; const OPEN_IN_NEW_WINDOW_PREF = 'browser.link.open_newwindow'; const DISABLE_POPUP_PREF = 'dom.disable_open_during_load'; const fixtures = require("../fixtures"); const { base64jpeg } = fixtures; const { cleanUI, before, after } = require("sdk/test/utils"); const { wait } = require('../event/helpers'); // Bug 682681 - tab.title should never be empty exports.testBug682681_aboutURI = function(assert, done) { let url = 'chrome://browser/locale/tabbrowser.properties'; let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"]. getService(Ci.nsIStringBundleService). createBundle(url); let emptyTabTitle = stringBundle.GetStringFromName('tabs.emptyTabTitle'); tabs.on('ready', function onReady(tab) { tabs.removeListener('ready', onReady); assert.equal(tab.title, emptyTabTitle, "title of about: tab is not blank"); tab.close(done); }); // open a about: url tabs.open({ url: "about:blank", inBackground: true }); }; // related to Bug 682681 exports.testTitleForDataURI = function(assert, done) { tabs.open({ url: "data:text/html;charset=utf-8,tab", inBackground: true, onReady: function(tab) { assert.equal(tab.title, "tab", "data: title is not Connecting..."); tab.close(done); } }); }; // TEST: 'BrowserWindow' instance creation on tab 'activate' event // See bug 648244: there was a infinite loop. exports.testBrowserWindowCreationOnActivate = function(assert, done) { let windows = require("sdk/windows").browserWindows; let gotActivate = false; tabs.once('activate', function onActivate(eventTab) { assert.ok(windows.activeWindow, "Is able to fetch activeWindow"); gotActivate = true; }); open().then(function(window) { assert.ok(gotActivate, "Received activate event"); return close(window); }).then(done).then(null, assert.fail); } // TEST: tab unloader exports.testAutomaticDestroyEventOpen = function(assert, done) { let called = false; let loader = Loader(module); let tabs2 = loader.require("sdk/tabs"); tabs2.on('open', _ => called = true); // Fire a tab event and ensure that the destroyed tab is inactive tabs.once('open', tab => { setTimeout(_ => { assert.ok(!called, "Unloaded tab module is destroyed and inactive"); tab.close(done); }); }); loader.unload(); tabs.open("data:text/html;charset=utf-8,testAutomaticDestroyEventOpen"); }; exports.testAutomaticDestroyEventActivate = function(assert, done) { let called = false; let loader = Loader(module); let tabs2 = loader.require("sdk/tabs"); tabs2.on('activate', _ => called = true); // Fire a tab event and ensure that the destroyed tab is inactive tabs.once('activate', tab => { setTimeout(_ => { assert.ok(!called, "Unloaded tab module is destroyed and inactive"); tab.close(done); }); }); loader.unload(); tabs.open("data:text/html;charset=utf-8,testAutomaticDestroyEventActivate"); }; exports.testAutomaticDestroyEventDeactivate = function(assert, done) { let called = false; let currentTab = tabs.activeTab; let loader = Loader(module); let tabs2 = loader.require("sdk/tabs"); tabs.open({ url: "data:text/html;charset=utf-8,testAutomaticDestroyEventDeactivate", onActivate: _ => setTimeout(_ => { tabs2.on('deactivate', _ => called = true); // Fire a tab event and ensure that the destroyed tab is inactive tabs.once('deactivate', tab => { setTimeout(_ => { assert.ok(!called, "Unloaded tab module is destroyed and inactive"); tab.close(done); }); }); loader.unload(); currentTab.activate(); }) }); }; exports.testAutomaticDestroyEventClose = function(assert, done) { let called = false; let loader = Loader(module); let tabs2 = loader.require("sdk/tabs"); tabs.open({ url: "data:text/html;charset=utf-8,testAutomaticDestroyEventClose", onReady: tab => { tabs2.on('close', _ => called = true); // Fire a tab event and ensure that the destroyed tab is inactive tabs.once('close', tab => { setTimeout(_ => { assert.ok(!called, "Unloaded tab module is destroyed and inactive"); done(); }); }); loader.unload(); tab.close(); } }); }; exports.testTabPropertiesInNewWindow = function(assert, done) { const { LoaderWithFilteredConsole } = require("sdk/test/loader"); let loader = LoaderWithFilteredConsole(module, function(type, message) { return true; }); let tabs = loader.require('sdk/tabs'); let { viewFor } = loader.require('sdk/view/core'); let count = 0; function onReadyOrLoad (tab) { if (count++) { close(getOwnerWindow(viewFor(tab))).then(done).then(null, assert.fail); } } let url = "data:text/html;charset=utf-8,foofoo"; tabs.open({ inNewWindow: true, url: url, onReady: function(tab) { assert.equal(tab.title, "foo", "title of the new tab matches"); assert.equal(tab.url, url, "URL of the new tab matches"); assert.equal(tab.favicon, undefined, "favicon of the new tab is undefined"); assert.equal(tab.style, null, "style of the new tab matches"); assert.equal(tab.index, 0, "index of the new tab matches"); assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches"); assert.notEqual(tab.id, null, "a tab object always has an id property."); onReadyOrLoad(tab); }, onLoad: function(tab) { assert.equal(tab.title, "foo", "title of the new tab matches"); assert.equal(tab.url, url, "URL of the new tab matches"); assert.equal(tab.favicon, undefined, "favicon of the new tab is undefined"); assert.equal(tab.style, null, "style of the new tab matches"); assert.equal(tab.index, 0, "index of the new tab matches"); assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches"); assert.notEqual(tab.id, null, "a tab object always has an id property."); onReadyOrLoad(tab); } }); }; exports.testTabPropertiesInSameWindow = function(assert, done) { const { LoaderWithFilteredConsole } = require("sdk/test/loader"); let loader = LoaderWithFilteredConsole(module, function(type, message) { return true; }); let tabs = loader.require('sdk/tabs'); // Get current count of tabs so we know the index of the // new tab, bug 893846 let tabCount = tabs.length; let count = 0; function onReadyOrLoad (tab) { if (count++) { tab.close(done); } } let url = "data:text/html;charset=utf-8,foofoo"; tabs.open({ url: url, onReady: function(tab) { assert.equal(tab.title, "foo", "title of the new tab matches"); assert.equal(tab.url, url, "URL of the new tab matches"); assert.equal(tab.favicon, undefined, "favicon of the new tab is undefined"); assert.equal(tab.style, null, "style of the new tab matches"); assert.equal(tab.index, tabCount, "index of the new tab matches"); assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches"); assert.notEqual(tab.id, null, "a tab object always has an id property."); onReadyOrLoad(tab); }, onLoad: function(tab) { assert.equal(tab.title, "foo", "title of the new tab matches"); assert.equal(tab.url, url, "URL of the new tab matches"); assert.equal(tab.favicon, undefined, "favicon of the new tab is undefined"); assert.equal(tab.style, null, "style of the new tab matches"); assert.equal(tab.index, tabCount, "index of the new tab matches"); assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches"); assert.notEqual(tab.id, null, "a tab object always has an id property."); onReadyOrLoad(tab); } }); }; // TEST: tab properties exports.testTabContentTypeAndReload = function(assert, done) { open().then(focus).then(function(window) { let url = "data:text/html;charset=utf-8,foofoo"; let urlXML = "data:text/xml;charset=utf-8,bar"; tabs.open({ url: url, onReady: function(tab) { if (tab.url === url) { assert.equal(tab.contentType, "text/html"); tab.url = urlXML; } else { assert.equal(tab.contentType, "text/xml"); close(window).then(done).then(null, assert.fail); } } }); }); }; // TEST: tabs iterator and length property exports.testTabsIteratorAndLength = function(assert, done) { open(null, { features: { chrome: true, toolbar: true } }).then(focus).then(function(window) { let startCount = 0; for (let t of tabs) startCount++; assert.equal(startCount, tabs.length, "length property is correct"); let url = "data:text/html;charset=utf-8,default"; tabs.open(url); tabs.open(url); tabs.open({ url: url, onOpen: function(tab) { let count = 0; for (let t of tabs) count++; assert.equal(count, startCount + 3, "iterated tab count matches"); assert.equal(startCount + 3, tabs.length, "iterated tab count matches length property"); close(window).then(done).then(null, assert.fail); } }); }); }; // TEST: tab.url setter exports.testTabLocation = function(assert, done) { open().then(focus).then(function(window) { let url1 = "data:text/html;charset=utf-8,foo"; let url2 = "data:text/html;charset=utf-8,bar"; tabs.on('ready', function onReady(tab) { if (tab.url != url2) return; tabs.removeListener('ready', onReady); assert.pass("tab.load() loaded the correct url"); close(window).then(done).then(null, assert.fail); }); tabs.open({ url: url1, onOpen: function(tab) { tab.url = url2 } }); }); }; // TEST: tab.close() exports.testTabClose = function(assert, done) { let testName = "testTabClose"; let url = "data:text/html;charset=utf-8," + testName; assert.notEqual(tabs.activeTab.url, url, "tab is not the active tab"); tabs.once('ready', function onReady(tab) { assert.equal(tabs.activeTab.url, tab.url, "tab is now the active tab"); assert.equal(url, tab.url, "tab url is the test url"); let secondOnCloseCalled = false; // Bug 699450: Multiple calls to tab.close should not throw tab.close(() => secondOnCloseCalled = true); try { tab.close(function () { assert.notEqual(tabs.activeTab.url, url, "tab is no longer the active tab"); assert.ok(secondOnCloseCalled, "The immediate second call to tab.close happened"); assert.notEqual(tabs.activeTab.url, url, "tab is no longer the active tab"); done(); }); } catch(e) { assert.fail("second call to tab.close() thrown an exception: " + e); } }); tabs.open(url); }; // TEST: tab.move() exports.testTabMove = function(assert, done) { open().then(focus).then(function(window) { let url = "data:text/html;charset=utf-8,foo"; tabs.open({ url: url, onOpen: function(tab) { assert.equal(tab.index, 1, "tab index before move matches"); tab.index = 0; assert.equal(tab.index, 0, "tab index after move matches"); close(window).then(done).then(null, assert.fail); } }); }).then(null, assert.fail); }; exports.testIgnoreClosing = function*(assert) { let url = "data:text/html;charset=utf-8,foobar"; let originalWindow = getMostRecentBrowserWindow(); let window = yield open().then(focus); assert.equal(tabs.length, 2, "should be two windows open each with one tab"); yield new Promise(resolve => { tabs.once("ready", (tab) => { let win = tab.window; assert.equal(win.tabs.length, 2, "should be two tabs in the new window"); assert.equal(tabs.length, 3, "should be three tabs in total"); tab.close(() => { assert.equal(win.tabs.length, 1, "should be one tab in the new window"); assert.equal(tabs.length, 2, "should be two tabs in total"); resolve(); }); }); tabs.open(url); }); }; // TEST: open tab with default options exports.testOpen = function(assert, done) { let url = "data:text/html;charset=utf-8,default"; tabs.open({ url: url, onReady: function(tab) { assert.equal(tab.url, url, "URL of the new tab matches"); assert.equal(tab.isPinned, false, "The new tab is not pinned"); tab.close(done); } }); }; // TEST: opening a pinned tab exports.testOpenPinned = function(assert, done) { let url = "data:text/html;charset=utf-8,default"; tabs.open({ url: url, isPinned: true, onOpen: function(tab) { assert.equal(tab.isPinned, true, "The new tab is pinned"); tab.close(done); } }); }; // TEST: pin/unpin opened tab exports.testPinUnpin = function(assert, done) { let url = "data:text/html;charset=utf-8,default"; tabs.open({ url: url, inBackground: true, onOpen: function(tab) { tab.pin(); assert.equal(tab.isPinned, true, "The tab was pinned correctly"); tab.unpin(); assert.equal(tab.isPinned, false, "The tab was unpinned correctly"); tab.close(done); } }); } // TEST: open tab in background exports.testInBackground = function(assert, done) { assert.equal(tabs.length, 1, "Should be one tab"); let window = getMostRecentBrowserWindow(); let activeUrl = tabs.activeTab.url; let url = "data:text/html;charset=utf-8,background"; assert.equal(getMostRecentBrowserWindow(), window, "getMostRecentBrowserWindow() matches this window"); tabs.on('ready', function onReady(tab) { tabs.removeListener('ready', onReady); assert.equal(tabs.activeTab.url, activeUrl, "URL of active tab has not changed"); assert.equal(tab.url, url, "URL of the new background tab matches"); assert.equal(getMostRecentBrowserWindow(), window, "a new window was not opened"); assert.notEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL"); tab.close(done); }); tabs.open({ url: url, inBackground: true }); } // TEST: open tab in new window exports.testOpenInNewWindow = function(assert, done) { let startWindowCount = windows().length; let url = "data:text/html;charset=utf-8,testOpenInNewWindow"; tabs.open({ url: url, inNewWindow: true, onReady: function(tab) { let newWindow = getOwnerWindow(viewFor(tab)); assert.equal(windows().length, startWindowCount + 1, "a new window was opened"); onFocus(newWindow).then(function() { assert.equal(getMostRecentBrowserWindow(), newWindow, "new window is active"); assert.equal(tab.url, url, "URL of the new tab matches"); assert.equal(newWindow.content.location, url, "URL of new tab in new window matches"); assert.equal(tabs.activeTab.url, url, "URL of activeTab matches"); return close(newWindow).then(done); }).then(null, assert.fail); } }); } // Test tab.open inNewWindow + onOpen combination exports.testOpenInNewWindowOnOpen = function(assert, done) { let startWindowCount = windows().length; let url = "data:text/html;charset=utf-8,newwindow"; tabs.open({ url: url, inNewWindow: true, onOpen: function(tab) { let newWindow = getOwnerWindow(viewFor(tab)); onFocus(newWindow).then(function() { assert.equal(windows().length, startWindowCount + 1, "a new window was opened"); assert.equal(getMostRecentBrowserWindow(), newWindow, "new window is active"); close(newWindow).then(done).then(null, assert.fail); }); } }); }; // TEST: onOpen event handler exports.testTabsEvent_onOpen = function(assert, done) { open().then(focus).then(window => { let url = "data:text/html;charset=utf-8,1"; let eventCount = 0; // add listener via property assignment function listener1(tab) { eventCount++; }; tabs.on('open', listener1); // add listener via collection add tabs.on('open', function listener2(tab) { assert.equal(++eventCount, 2, "both listeners notified"); tabs.removeListener('open', listener1); tabs.removeListener('open', listener2); close(window).then(done).then(null, assert.fail); }); tabs.open(url); }).then(null, assert.fail); }; // TEST: onClose event handler exports.testTabsEvent_onClose = function*(assert) { let window = yield open().then(focus); let url = "data:text/html;charset=utf-8,onclose"; let eventCount = 0; // add listener via property assignment function listener1(tab) { eventCount++; } tabs.on("close", listener1); yield new Promise(resolve => { // add listener via collection add tabs.on("close", function listener2(tab) { assert.equal(++eventCount, 2, "both listeners notified"); tabs.removeListener("close", listener2); resolve(); }); tabs.on('ready', function onReady(tab) { tabs.removeListener('ready', onReady); tab.close(); }); tabs.open(url); }); tabs.removeListener("close", listener1); assert.pass("done test!"); yield close(window); assert.pass("window was closed!"); }; // TEST: onClose event handler when a window is closed exports.testTabsEvent_onCloseWindow = function(assert, done) { let closeCount = 0; let individualCloseCount = 0; open().then(focus).then(window => { assert.pass('opened a new window'); tabs.on("close", function listener() { if (++closeCount == 4) { tabs.removeListener("close", listener); } }); function endTest() { if (++individualCloseCount < 3) { assert.pass('tab closed ' + individualCloseCount); return; } assert.equal(closeCount, 4, "Correct number of close events received"); assert.equal(individualCloseCount, 3, "Each tab with an attached onClose listener received a close " + "event when the window was closed"); done(); } // One tab is already open with the window let openTabs = 1; function testCasePossiblyLoaded() { if (++openTabs == 4) { window.close(); } assert.pass('tab opened ' + openTabs); } tabs.open({ url: "data:text/html;charset=utf-8,tab2", onOpen: testCasePossiblyLoaded, onClose: endTest }); tabs.open({ url: "data:text/html;charset=utf-8,tab3", onOpen: testCasePossiblyLoaded, onClose: endTest }); tabs.open({ url: "data:text/html;charset=utf-8,tab4", onOpen: testCasePossiblyLoaded, onClose: endTest }); }).then(null, assert.fail); } // TEST: onReady event handler exports.testTabsEvent_onReady = function(assert, done) { open().then(focus).then(window => { let url = "data:text/html;charset=utf-8,onready"; let eventCount = 0; // add listener via property assignment function listener1(tab) { eventCount++; }; tabs.on('ready', listener1); // add listener via collection add tabs.on('ready', function listener2(tab) { assert.equal(++eventCount, 2, "both listeners notified"); tabs.removeListener('ready', listener1); tabs.removeListener('ready', listener2); close(window).then(done); }); tabs.open(url); }).then(null, assert.fail); }; // TEST: onActivate event handler exports.testTabsEvent_onActivate = function(assert, done) { open().then(focus).then(window => { let url = "data:text/html;charset=utf-8,onactivate"; let eventCount = 0; // add listener via property assignment function listener1(tab) { eventCount++; }; tabs.on('activate', listener1); // add listener via collection add tabs.on('activate', function listener2(tab) { assert.equal(++eventCount, 2, "both listeners notified"); tabs.removeListener('activate', listener1); tabs.removeListener('activate', listener2); close(window).then(done).then(null, assert.fail); }); tabs.open(url); }).then(null, assert.fail); }; // onDeactivate event handler exports.testTabsEvent_onDeactivate = function*(assert) { let window = yield open().then(focus); let url = "data:text/html;charset=utf-8,ondeactivate"; let eventCount = 0; // add listener via property assignment function listener1(tab) { eventCount++; assert.pass("listener1 was called " + eventCount); }; tabs.on('deactivate', listener1); yield new Promise(resolve => { // add listener via collection add tabs.on('deactivate', function listener2(tab) { assert.equal(++eventCount, 2, "both listeners notified"); tabs.removeListener('deactivate', listener2); resolve(); }); tabs.on('open', function onOpen(tab) { assert.pass("tab opened"); tabs.removeListener('open', onOpen); tabs.open("data:text/html;charset=utf-8,foo"); }); tabs.open(url); }); tabs.removeListener('deactivate', listener1); assert.pass("listeners were removed"); }; // pinning exports.testTabsEvent_pinning = function(assert, done) { open().then(focus).then(window => { let url = "data:text/html;charset=utf-8,1"; tabs.on('open', function onOpen(tab) { tabs.removeListener('open', onOpen); tab.pin(); }); tabs.on('pinned', function onPinned(tab) { tabs.removeListener('pinned', onPinned); assert.ok(tab.isPinned, "notified tab is pinned"); tab.unpin(); }); tabs.on('unpinned', function onUnpinned(tab) { tabs.removeListener('unpinned', onUnpinned); assert.ok(!tab.isPinned, "notified tab is not pinned"); close(window).then(done).then(null, assert.fail); }); tabs.open(url); }).then(null, assert.fail); }; // TEST: per-tab event handlers exports.testPerTabEvents = function*(assert) { let window = yield open().then(focus); let eventCount = 0; let tab = yield new Promise(resolve => { tabs.open({ url: "data:text/html;charset=utf-8,foo", onOpen: (tab) => { assert.pass("the tab was opened"); // add listener via property assignment function listener1() { eventCount++; }; tab.on('ready', listener1); // add listener via collection add tab.on('ready', function listener2() { assert.equal(eventCount, 1, "listener1 called before listener2"); tab.removeListener('ready', listener1); tab.removeListener('ready', listener2); assert.pass("removed listeners"); eventCount++; resolve(); }); } }); }); assert.equal(eventCount, 2, "both listeners were notified."); }; exports.testAttachOnMultipleDocuments = function (assert, done) { // Example of attach that process multiple tab documents open().then(focus).then(window => { let firstLocation = "data:text/html;charset=utf-8,foobar"; let secondLocation = "data:text/html;charset=utf-8,bar"; let thirdLocation = "data:text/html;charset=utf-8,fox"; let onReadyCount = 0; let worker1 = null; let worker2 = null; let detachEventCount = 0; tabs.open({ url: firstLocation, onReady: function (tab) { onReadyCount++; if (onReadyCount == 1) { worker1 = tab.attach({ contentScript: 'self.on("message", ' + ' function () { return self.postMessage(document.location.href); }' + ');', onMessage: function (msg) { assert.equal(msg, firstLocation, "Worker url is equal to the 1st document"); tab.url = secondLocation; }, onDetach: function () { detachEventCount++; assert.pass("Got worker1 detach event"); assert.throws(function () { worker1.postMessage("ex-1"); }, /Couldn't find the worker/, "postMessage throw because worker1 is destroyed"); checkEnd(); } }); worker1.postMessage("new-doc-1"); } else if (onReadyCount == 2) { worker2 = tab.attach({ contentScript: 'self.on("message", ' + ' function () { return self.postMessage(document.location.href); }' + ');', onMessage: function (msg) { assert.equal(msg, secondLocation, "Worker url is equal to the 2nd document"); tab.url = thirdLocation; }, onDetach: function () { detachEventCount++; assert.pass("Got worker2 detach event"); assert.throws(function () { worker2.postMessage("ex-2"); }, /Couldn't find the worker/, "postMessage throw because worker2 is destroyed"); checkEnd(); } }); worker2.postMessage("new-doc-2"); } else if (onReadyCount == 3) { tab.close(); } } }); function checkEnd() { if (detachEventCount != 2) return; assert.pass("Got all detach events"); close(window).then(done).then(null, assert.fail); } }).then(null, assert.fail); } exports.testAttachWrappers = function (assert, done) { // Check that content script has access to wrapped values by default open().then(focus).then(window => { let document = "data:text/html;charset=utf-8,"; let count = 0; tabs.open({ url: document, onReady: function (tab) { let worker = tab.attach({ contentScript: 'try {' + ' self.postMessage(!("globalJSVar" in window));' + ' self.postMessage(typeof window.globalJSVar == "undefined");' + '} catch(e) {' + ' self.postMessage(e.message);' + '}', onMessage: function (msg) { assert.equal(msg, true, "Worker has wrapped objects ("+count+")"); if (count++ == 1) close(window).then(done).then(null, assert.fail); } }); } }); }).then(null, assert.fail); } /* // We do not offer unwrapped access to DOM since bug 601295 landed // See 660780 to track progress of unwrap feature exports.testAttachUnwrapped = function (assert, done) { // Check that content script has access to unwrapped values through unsafeWindow openBrowserWindow(function(window, browser) { let document = "data:text/html;charset=utf-8,"; let count = 0; tabs.open({ url: document, onReady: function (tab) { let worker = tab.attach({ contentScript: 'try {' + ' self.postMessage(unsafeWindow.globalJSVar);' + '} catch(e) {' + ' self.postMessage(e.message);' + '}', onMessage: function (msg) { assert.equal(msg, true, "Worker has access to javascript content globals ("+count+")"); close(window).then(done); } }); } }); }); } */ exports['test window focus changes active tab'] = function(assert, done) { let url1 = "data:text/html;charset=utf-8," + encodeURIComponent("test window focus changes active tab

Window #1"); let win1 = openBrowserWindow(function() { assert.pass("window 1 is open"); let win2 = openBrowserWindow(function() { assert.pass("window 2 is open"); focus(win2).then(function() { tabs.on("activate", function onActivate(tab) { tabs.removeListener("activate", onActivate); if (tab.readyState === 'uninitialized') { tab.once('ready', whenReady); } else { whenReady(tab); } function whenReady(tab) { assert.pass("activate was called on windows focus change."); assert.equal(tab.url, url1, 'the activated tab url is correct'); return close(win2).then(function() { assert.pass('window 2 was closed'); return close(win1); }).then(done).then(null, assert.fail); } }); win1.focus(); }); }, "data:text/html;charset=utf-8,test window focus changes active tab

Window #2"); }, url1); }; exports['test ready event on new window tab'] = function(assert, done) { let uri = encodeURI("data:text/html;charset=utf-8,Waiting for ready event!"); require("sdk/tabs").on("ready", function onReady(tab) { if (tab.url === uri) { require("sdk/tabs").removeListener("ready", onReady); assert.pass("ready event was emitted"); close(window).then(done).then(null, assert.fail); } }); let window = openBrowserWindow(function(){}, uri); }; exports['test unique tab ids'] = function(assert, done) { var windows = require('sdk/windows').browserWindows; var { all, defer } = require('sdk/core/promise'); function openWindow() { let deferred = defer(); let win = windows.open({ url: "data:text/html;charset=utf-8,foo", }); win.on('open', function(window) { assert.ok(window.tabs.length); assert.ok(window.tabs.activeTab); assert.ok(window.tabs.activeTab.id); deferred.resolve({ id: window.tabs.activeTab.id, win: win }); }); return deferred.promise; } var one = openWindow(), two = openWindow(); all([one, two]).then(function(results) { assert.notEqual(results[0].id, results[1].id, "tab Ids should not be equal."); results[0].win.close(function() { results[1].win.close(function () { done(); }); }); }); } // related to Bug 671305 exports.testOnLoadEventWithDOM = function(assert, done) { let count = 0; let title = 'testOnLoadEventWithDOM'; // open a about: url tabs.open({ url: 'data:text/html;charset=utf-8,' + title + '', inBackground: true, onLoad: function(tab) { assert.equal(tab.title, title, 'tab passed in as arg, load called'); if (++count > 1) { assert.pass('onLoad event called on reload'); tab.close(done); } else { assert.pass('first onLoad event occured'); tab.reload(); } } }); }; // related to Bug 671305 exports.testOnLoadEventWithImage = function(assert, done) { let count = 0; tabs.open({ url: base64jpeg, inBackground: true, onLoad: function(tab) { if (++count > 1) { assert.pass('onLoad event called on reload with image'); tab.close(done); } else { assert.pass('first onLoad event occured'); tab.reload(); } } }); }; exports.testNoDeadObjects = function(assert, done) { let loader = Loader(module); let myTabs = loader.require("sdk/tabs"); // Load a tab, unload our modules, and navigate the tab to trigger an event // on it. This would throw a dead object exception if our modules didn't // clean up their event handlers on unload. tabs.open({ url: "data:text/html;charset=utf-8,one", onLoad: function(tab) { // 2. Arrange to nuke the sandboxes and then trigger the load event // on the tab once the loader is kaput. systemEvents.on("sdk:loader:destroy", function onUnload() { systemEvents.off("sdk:loader:destroy", onUnload, true); // Defer this carnage till the end of the event queue, to avoid nuking // the sandboxes from under the modules as they're being cleaned up. setTimeout(function() { // 3. Arrange to close the tab once the second page loads. tab.on("load", function() { tab.close(function() { let { viewFor } = loader.require("sdk/view/core"); assert.equal(viewFor(tab), undefined, "didn't retain the closed tab"); done(); }); }); // Trigger a load event on the tab, to give the now-unloaded // myTabs a chance to choke on it. tab.url = "data:text/html;charset=utf-8,two"; }, 0); }, true); // 1. Start unloading the modules. Defer till the end of the event // queue, in case myTabs is attaching its own handlers here too. // We want it to latch on before we pull the rug from under it. setTimeout(function() { loader.unload(); }, 0); } }); }; exports.testTabDestroy = function(assert, done) { let loader = Loader(module); let myTabs = loader.require("sdk/tabs"); let { modelFor: myModelFor } = loader.require("sdk/model/core"); let { viewFor: myViewFor } = loader.require("sdk/view/core"); let myFirstTab = myTabs.activeTab; myTabs.open({ url: "data:text/html;charset=utf-8,destroy", onReady: (myTab) => setImmediate(() => { let tab = modelFor(myViewFor(myTab)); function badListener(event, tab) { // Ignore events for the other tabs if (tab != myTab) return; assert.fail("Should not have seen the " + event + " listener called"); } assert.ok(myTab, "Should have a tab in the test loader."); assert.equal(myViewFor(myTab), viewFor(tab), "Should have the right view."); assert.equal(myTabs.length, 2, "Should have the right number of global tabs."); assert.equal(myTab.window.tabs.length, 2, "Should have the right number of window tabs."); assert.equal(myTabs.activeTab, myTab, "Globally active tab is correct."); assert.equal(myTab.window.tabs.activeTab, myTab, "Window active tab is correct."); assert.equal(myTabs[1], myTab, "Global tabs list contains tab."); assert.equal(myTab.window.tabs[1], myTab, "Window tabs list contains tab."); myTab.once("ready", badListener.bind(null, "tab ready")); myTab.once("deactivate", badListener.bind(null, "tab deactivate")); myTab.once("activate", badListener.bind(null, "tab activate")); myTab.once("close", badListener.bind(null, "tab close")); myTab.destroy(); myTab.once("ready", badListener.bind(null, "new tab ready")); myTab.once("deactivate", badListener.bind(null, "new tab deactivate")); myTab.once("activate", badListener.bind(null, "new tab activate")); myTab.once("close", badListener.bind(null, "new tab close")); myTabs.once("ready", badListener.bind(null, "tabs ready")); myTabs.once("deactivate", badListener.bind(null, "tabs deactivate")); myTabs.once("activate", badListener.bind(null, "tabs activate")); myTabs.once("close", badListener.bind(null, "tabs close")); assert.equal(myViewFor(myTab), viewFor(tab), "Should have the right view."); assert.equal(myModelFor(viewFor(tab)), myTab, "Can still reach the tab object."); assert.equal(myTabs.length, 2, "Should have the right number of global tabs."); assert.equal(myTab.window.tabs.length, 2, "Should have the right number of window tabs."); assert.equal(myTabs.activeTab, myTab, "Globally active tab is correct."); assert.equal(myTab.window.tabs.activeTab, myTab, "Window active tab is correct."); assert.equal(myTabs[1], myTab, "Global tabs list still contains tab."); assert.equal(myTab.window.tabs[1], myTab, "Window tabs list still contains tab."); assert.equal(myTab.url, undefined, "url property is not usable"); assert.equal(myTab.contentType, undefined, "contentType property is not usable"); assert.equal(myTab.title, undefined, "title property is not usable"); assert.equal(myTab.id, undefined, "id property is not usable"); assert.equal(myTab.index, undefined, "index property is not usable"); myTab.pin(); assert.ok(!tab.isPinned, "pin method shouldn't work"); tabs.once("activate", () => setImmediate(() => { assert.equal(myTabs.activeTab, myFirstTab, "Globally active tab is correct."); assert.equal(myTab.window.tabs.activeTab, myFirstTab, "Window active tab is correct."); let sawActivate = false; tabs.once("activate", () => setImmediate(() => { sawActivate = true; assert.equal(myTabs.activeTab, myTab, "Globally active tab is correct."); assert.equal(myTab.window.tabs.activeTab, myTab, "Window active tab is correct."); // This shouldn't have any effect myTab.close(); tab.once("ready", () => setImmediate(() => { tab.close(() => { loader.unload(); done(); }); })); tab.url = "data:text/html;charset=utf-8,destroy2"; })); myTab.activate(); setImmediate(() => { assert.ok(!sawActivate, "activate method shouldn't have done anything"); tab.activate(); }); })); myFirstTab.activate(); }) }) }; // related to bug 942511 // https://bugzilla.mozilla.org/show_bug.cgi?id=942511 exports['test active tab properties defined on popup closed'] = function (assert, done) { setPref(OPEN_IN_NEW_WINDOW_PREF, 2); setPref(DISABLE_POPUP_PREF, false); let tabID = ""; let popupClosed = false; tabs.open({ url: 'about:blank', onReady: function (tab) { tabID = tab.id; tab.attach({ contentScript: 'var popup = window.open("about:blank");' + 'popup.close();' }); windowObserver.once('close', () => { popupClosed = true; }); windowObserver.on('activate', () => { // Only when the 'activate' event is fired after the popup was closed. if (popupClosed) { popupClosed = false; let activeTabID = tabs.activeTab.id; if (activeTabID) { assert.equal(tabID, activeTabID, 'ActiveTab properties are correct'); } else { assert.fail('ActiveTab properties undefined on popup closed'); } tab.close(done); } }); } }); }; // related to bugs 922956 and 989288 // https://bugzilla.mozilla.org/show_bug.cgi?id=922956 // https://bugzilla.mozilla.org/show_bug.cgi?id=989288 exports["test tabs ready and close after window.open"] = function*(assert, done) { // ensure popups open in a new window and disable popup blocker setPref(OPEN_IN_NEW_WINDOW_PREF, 2); setPref(DISABLE_POPUP_PREF, false); // open windows to trigger observers tabs.activeTab.attach({ contentScript: "window.open('about:blank');" + "window.open('about:blank', '', " + "'width=800,height=600,resizable=no,status=no,location=no');" }); let tab1 = yield wait(tabs, "ready"); assert.pass("first tab ready has occured"); let tab2 = yield wait(tabs, "ready"); assert.pass("second tab ready has occured"); tab1.close(); yield wait(tabs, "close"); assert.pass("first tab close has occured"); tab2.close(); yield wait(tabs, "close"); assert.pass("second tab close has occured"); }; // related to bug #939496 exports["test tab open event for new window"] = function(assert, done) { // ensure popups open in a new window and disable popup blocker setPref(OPEN_IN_NEW_WINDOW_PREF, 2); setPref(DISABLE_POPUP_PREF, false); tabs.once('open', function onOpen(window) { assert.pass("tab open has occured"); window.close(done); }); // open window to trigger observers browserWindows.open("about:logo"); }; after(exports, function*(name, assert) { resetPopupPrefs(); yield cleanUI(); }); const resetPopupPrefs = () => { resetPref(OPEN_IN_NEW_WINDOW_PREF); resetPref(DISABLE_POPUP_PREF); }; /******************* helpers *********************/ // Utility function to open a new browser window. function openBrowserWindow(callback, url) { let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. getService(Ci.nsIWindowWatcher); let urlString = Cc["@mozilla.org/supports-string;1"]. createInstance(Ci.nsISupportsString); urlString.data = url; let window = ww.openWindow(null, "chrome://browser/content/browser.xul", "_blank", "chrome,all,dialog=no", urlString); if (callback) { window.addEventListener("load", function onLoad(event) { if (event.target && event.target.defaultView == window) { window.removeEventListener("load", onLoad, true); let browsers = window.document.getElementsByTagName("tabbrowser"); try { setTimeout(function () { callback(window, browsers[0]); }, 10); } catch (e) { console.exception(e); } } }, true); } return window; } require("sdk/test").run(exports);