diff options
Diffstat (limited to 'addon-sdk/source/test/tabs')
-rw-r--r-- | addon-sdk/source/test/tabs/test-fennec-tabs.js | 595 | ||||
-rw-r--r-- | addon-sdk/source/test/tabs/test-firefox-tabs.js | 1305 | ||||
-rw-r--r-- | addon-sdk/source/test/tabs/utils.js | 24 |
3 files changed, 1924 insertions, 0 deletions
diff --git a/addon-sdk/source/test/tabs/test-fennec-tabs.js b/addon-sdk/source/test/tabs/test-fennec-tabs.js new file mode 100644 index 000000000..d7e362d41 --- /dev/null +++ b/addon-sdk/source/test/tabs/test-fennec-tabs.js @@ -0,0 +1,595 @@ +/* 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, LoaderWithHookedConsole } = require('sdk/test/loader'); +const timer = require('sdk/timers'); +const tabs = require('sdk/tabs'); +const windows = require('sdk/windows'); +const { set: setPref } = require("sdk/preferences/service"); +const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings"; + +const tabsLen = tabs.length; +const URL = 'data:text/html;charset=utf-8,<html><head><title>#title#</title></head></html>'; + +// Fennec error message dispatched on all currently unimplement tab features, +// that match LoaderWithHookedConsole messages object pattern +const ERR_FENNEC_MSG = { + type: "error", + msg: "This method is not yet supported by Fennec" +}; + +// TEST: tab unloader +exports.testAutomaticDestroy = function(assert, done) { + let called = false; + + let loader2 = Loader(module); + let loader3 = Loader(module); + let tabs2 = loader2.require('sdk/tabs'); + let tabs3 = loader3.require('sdk/tabs'); + let tabs2Len = tabs2.length; + + tabs2.on('open', function onOpen(tab) { + assert.fail("an onOpen listener was called that should not have been"); + called = true; + }); + tabs2.on('ready', function onReady(tab) { + assert.fail("an onReady listener was called that should not have been"); + called = true; + }); + tabs2.on('select', function onSelect(tab) { + assert.fail("an onSelect listener was called that should not have been"); + called = true; + }); + tabs2.on('close', function onClose(tab) { + assert.fail("an onClose listener was called that should not have been"); + called = true; + }); + loader2.unload(); + + tabs3.on('open', function onOpen(tab) { + assert.pass("an onOpen listener was called for tabs3"); + + tab.on('ready', function onReady(tab) { + assert.fail("an onReady listener was called that should not have been"); + called = true; + }); + tab.on('select', function onSelect(tab) { + assert.fail("an onSelect listener was called that should not have been"); + called = true; + }); + tab.on('close', function onClose(tab) { + assert.fail("an onClose listener was called that should not have been"); + called = true; + }); + }); + tabs3.open(URL.replace(/#title#/, 'tabs3')); + loader3.unload(); + + // Fire a tab event and ensure that the destroyed tab is inactive + tabs.once('open', function(tab) { + assert.pass('tabs.once("open") works!'); + + assert.equal(tabs2Len, tabs2.length, "tabs2 length was not changed"); + assert.equal(tabs.length, (tabs2.length+2), "tabs.length > tabs2.length"); + + tab.once('ready', function() { + assert.pass('tab.once("ready") works!'); + + tab.once('close', function() { + assert.pass('tab.once("close") works!'); + + timer.setTimeout(function () { + assert.ok(!called, "Unloaded tab module is destroyed and inactive"); + + // end test + done(); + }); + }); + + tab.close(); + }); + }); + + tabs.open('data:text/html;charset=utf-8,foo'); +}; + +// TEST: tab properties +exports.testTabProperties = function(assert, done) { + let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>"; + let tabsLen = tabs.length; + 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.style, null, "style of the new tab matches"); + assert.equal(tab.index, tabsLen, "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"); + + tab.close(function() { + loader.unload(); + + // end test + done(); + }); + } + }); +}; + +// TEST: tabs iterator and length property +exports.testTabsIteratorAndLength = function(assert, done) { + let newTabs = []; + 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,testTabsIteratorAndLength"; + tabs.open({url: url, onOpen: tab => newTabs.push(tab)}); + tabs.open({url: url, onOpen: tab => newTabs.push(tab)}); + 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"); + + let newTabsLength = newTabs.length; + newTabs.forEach(t => t.close(function() { + if (--newTabsLength > 0) return; + + tab.close(done); + })); + } + }); +}; + +// TEST: tab.url setter +exports.testTabLocation = function(assert, done) { + 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 loaded the correct url"); + + tab.close(done); + }); + + tabs.open({ + url: url1, + onOpen: function(tab) { + tab.url = url2; + } + }); +}; + +// TEST: tab.move() +exports.testTabMove = function(assert, done) { + let { loader, messages } = LoaderWithHookedConsole(); + let tabs = loader.require('sdk/tabs'); + + let url = "data:text/html;charset=utf-8,testTabMove"; + + tabs.open({ + url: url, + onOpen: function(tab1) { + assert.ok(tab1.index >= 0, "opening a tab returns a tab w/ valid index"); + + tabs.open({ + url: url, + onOpen: function(tab) { + let i = tab.index; + assert.ok(tab.index > tab1.index, "2nd tab has valid index"); + tab.index = 0; + assert.equal(tab.index, i, "tab index after move matches"); + assert.equal(JSON.stringify(messages), + JSON.stringify([ERR_FENNEC_MSG]), + "setting tab.index logs error"); + // end test + tab1.close(() => tab.close(function() { + loader.unload(); + done(); + })); + } + }); + } + }); +}; + +// TEST: open tab with default options +exports.testTabsOpen_alt = function(assert, done) { + let { loader, messages } = LoaderWithHookedConsole(); + let tabs = loader.require('sdk/tabs'); + 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(tabs.activeTab, tab, "URL of active tab in the current window matches"); + assert.equal(tab.isPinned, false, "The new tab is not pinned"); + assert.equal(messages.length, 1, "isPinned logs error"); + + // end test + tab.close(function() { + loader.unload(); + done(); + }); + } + }); +}; + +// TEST: open pinned tab +exports.testOpenPinned_alt = function(assert, done) { + let { loader, messages } = LoaderWithHookedConsole(); + let tabs = loader.require('sdk/tabs'); + let url = "about:blank"; + + tabs.open({ + url: url, + isPinned: true, + onOpen: function(tab) { + assert.equal(tab.isPinned, false, "The new tab is pinned"); + // We get two error message: one for tabs.open's isPinned argument + // and another one for tab.isPinned + assert.equal(JSON.stringify(messages), + JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]), + "isPinned logs error"); + + // end test + tab.close(function() { + loader.unload(); + done(); + }); + } + }); +}; + +// TEST: pin/unpin opened tab +exports.testPinUnpin_alt = function(assert, done) { + let { loader, messages } = LoaderWithHookedConsole(); + let tabs = loader.require('sdk/tabs'); + let url = "data:text/html;charset=utf-8,default"; + + tabs.open({ + url: url, + onOpen: function(tab) { + tab.pin(); + assert.equal(tab.isPinned, false, "The tab was pinned correctly"); + assert.equal(JSON.stringify(messages), + JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]), + "tab.pin() logs error"); + + // Clear console messages for the following test + messages.length = 0; + + tab.unpin(); + assert.equal(tab.isPinned, false, "The tab was unpinned correctly"); + assert.equal(JSON.stringify(messages), + JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]), + "tab.unpin() logs error"); + + // end test + tab.close(function() { + loader.unload(); + done(); + }); + } + }); +}; + +// TEST: open tab in background +exports.testInBackground = function(assert, done) { + let activeUrl = tabs.activeTab.url; + let url = "data:text/html;charset=utf-8,background"; + let window = windows.browserWindows.activeWindow; + tabs.once('ready', function onReady(tab) { + 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(windows.browserWindows.activeWindow, window, "a new window was not opened"); + assert.notEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL"); + + // end test + tab.close(done); + }); + + tabs.open({ + url: url, + inBackground: true + }); +}; + +// TEST: open tab in new window +exports.testOpenInNewWindow = function(assert, done) { + let url = "data:text/html;charset=utf-8,newwindow"; + let window = windows.browserWindows.activeWindow; + + tabs.open({ + url: url, + inNewWindow: true, + onReady: function(tab) { + assert.equal(windows.browserWindows.length, 1, "a new window was not opened"); + assert.equal(windows.browserWindows.activeWindow, window, "old window is active"); + assert.equal(tab.url, url, "URL of the new tab matches"); + assert.equal(tabs.activeTab, tab, "tab is the activeTab"); + + tab.close(done); + } + }); +}; + +// TEST: onOpen event handler +exports.testTabsEvent_onOpen = function(assert, done) { + let url = URL.replace('#title#', 'testTabsEvent_onOpen'); + 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); + + // ends test + tab.close(done); + }); + + tabs.open(url); +}; + +// TEST: onClose event handler +exports.testTabsEvent_onClose = function(assert, done) { + 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); + + // add listener via collection add + tabs.on('close', function listener2(tab) { + assert.equal(++eventCount, 2, "both listeners notified"); + tabs.removeListener('close', listener1); + tabs.removeListener('close', listener2); + + // end test + done(); + }); + + tabs.on('ready', function onReady(tab) { + tabs.removeListener('ready', onReady); + tab.close(); + }); + + tabs.open(url); +}; + +// TEST: onClose event handler when a window is closed +exports.testTabsEvent_onCloseWindow = function(assert, done) { + let closeCount = 0, individualCloseCount = 0; + function listener() { + closeCount++; + } + tabs.on('close', listener); + + // One tab is already open with the window + let openTabs = 0; + function testCasePossiblyLoaded(tab) { + tab.close(function() { + if (++openTabs == 3) { + tabs.removeListener("close", listener); + + assert.equal(closeCount, 3, "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(); + } + }); + } + + tabs.open({ + url: "data:text/html;charset=utf-8,tab2", + onOpen: testCasePossiblyLoaded, + onClose: () => individualCloseCount++ + }); + + tabs.open({ + url: "data:text/html;charset=utf-8,tab3", + onOpen: testCasePossiblyLoaded, + onClose: () => individualCloseCount++ + }); + + tabs.open({ + url: "data:text/html;charset=utf-8,tab4", + onOpen: testCasePossiblyLoaded, + onClose: () => individualCloseCount++ + }); +}; + +// TEST: onReady event handler +exports.testTabsEvent_onReady = function(assert, done) { + 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); + + // end test + tab.close(done); + }); + + tabs.open(url); +}; + +// TEST: onActivate event handler +exports.testTabsEvent_onActivate = function(assert, done) { + 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"); + assert.equal(tab, tabs.activeTab, 'the active tab is correct'); + tabs.removeListener('activate', listener1); + tabs.removeListener('activate', listener2); + + // end test + tab.close(done); + }); + + tabs.open(url); +}; + +// TEST: onDeactivate event handler +exports.testTabsEvent_onDeactivate = function(assert, done) { + let url = "data:text/html;charset=utf-8,ondeactivate"; + let eventCount = 0; + + // add listener via property assignment + function listener1(tab) { + eventCount++; + }; + tabs.on('deactivate', listener1); + + // add listener via collection add + tabs.on('deactivate', function listener2(tab) { + assert.equal(++eventCount, 2, 'both listeners notified'); + assert.notEqual(tab, tabs.activeTab, 'the active tab is not the deactivated tab'); + tabs.removeListener('deactivate', listener1); + tabs.removeListener('deactivate', listener2); + + // end test + tab.close(done); + }); + + tabs.on('activate', function onActivate(tab) { + tabs.removeListener('activate', onActivate); + tabs.open("data:text/html;charset=utf-8,foo"); + tab.close(); + }); + + tabs.open(url); +}; + +// TEST: per-tab event handlers +exports.testPerTabEvents = function(assert, done) { + let eventCount = 0; + + tabs.open({ + url: "data:text/html;charset=utf-8,foo", + onOpen: function(tab) { + // 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, "both listeners notified"); + tab.removeListener('ready', listener1); + tab.removeListener('ready', listener2); + + // end test + tab.close(done); + }); + } + }); +}; + +exports.testUniqueTabIds = function(assert, done) { + var tabs = require('sdk/tabs'); + var tabIds = {}; + var steps = [ + function (index) { + tabs.open({ + url: "data:text/html;charset=utf-8,foo", + onOpen: function(tab) { + tabIds['tab1'] = tab.id; + next(index); + } + }); + }, + function (index) { + tabs.open({ + url: "data:text/html;charset=utf-8,bar", + onOpen: function(tab) { + tabIds['tab2'] = tab.id; + next(index); + } + }); + }, + function (index) { + assert.notEqual(tabIds.tab1, tabIds.tab2, "Tab ids should be unique."); + done(); + } + ]; + + function next(index) { + if (index === steps.length) { + return; + } + let fn = steps[index]; + index++; + fn(index); + } + + next(0); +} + +exports.testOnLoadEventWithDOM = function(assert, done) { + let count = 0; + let title = 'testOnLoadEventWithDOM'; + + tabs.open({ + url: 'data:text/html;charset=utf-8,<title>' + title + '</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(); + } + } + }); +}; + +require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/tabs/test-firefox-tabs.js b/addon-sdk/source/test/tabs/test-firefox-tabs.js new file mode 100644 index 000000000..368ed02ba --- /dev/null +++ b/addon-sdk/source/test/tabs/test-firefox-tabs.js @@ -0,0 +1,1305 @@ +/* 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,<title>tab</title>", + 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,<html><head><title>foo</title></head><body>foo</body></html>"; + 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,<html><head><title>foo</title></head><body>foo</body></html>"; + 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,<html><head><title>foo</title></head><body>foo</body></html>"; + let urlXML = "data:text/xml;charset=utf-8,<foo>bar</foo>"; + 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,<script>var globalJSVar = true; " + + " document.getElementById = 3;</script>"; + 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,<script>var globalJSVar=true;</script>"; + 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</br><h1>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</br><h1>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,<html>foo</html>", + }); + + 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>' + title + '</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); diff --git a/addon-sdk/source/test/tabs/utils.js b/addon-sdk/source/test/tabs/utils.js new file mode 100644 index 000000000..4981a4d08 --- /dev/null +++ b/addon-sdk/source/test/tabs/utils.js @@ -0,0 +1,24 @@ +/* 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 { openTab: makeTab, getTabContentWindow } = require("sdk/tabs/utils"); + +function openTab(rawWindow, url) { + return new Promise(resolve => { + let tab = makeTab(rawWindow, url); + let window = getTabContentWindow(tab); + if (window.document.readyState == "complete") { + return resolve(); + } + + window.addEventListener("load", function onLoad() { + window.removeEventListener("load", onLoad, true); + resolve(); + }, true); + + return null; + }) +} +exports.openTab = openTab; |