michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: 'use strict'; michael@0: michael@0: const { Loader, LoaderWithHookedConsole } = require("sdk/test/loader"); michael@0: const { browserWindows } = require('sdk/windows'); michael@0: const tabs = require('sdk/tabs'); michael@0: const { isPrivate } = require('sdk/private-browsing'); michael@0: const { openDialog } = require('sdk/window/utils'); michael@0: const { isWindowPrivate } = require('sdk/window/utils'); michael@0: const { setTimeout } = require('sdk/timers'); michael@0: const { openWebpage } = require('./private-browsing/helper'); michael@0: const { isTabPBSupported, isWindowPBSupported } = require('sdk/private-browsing/utils'); michael@0: const app = require("sdk/system/xul-app"); michael@0: michael@0: const URL = 'data:text/html;charset=utf-8,#title#'; michael@0: michael@0: // TEST: tab count michael@0: exports.testTabCounts = function(assert, done) { michael@0: tabs.open({ michael@0: url: 'about:blank', michael@0: onReady: function(tab) { michael@0: let count1 = 0, michael@0: count2 = 0; michael@0: for each(let window in browserWindows) { michael@0: count1 += window.tabs.length; michael@0: for each(let tab in window.tabs) { michael@0: count2 += 1; michael@0: } michael@0: } michael@0: michael@0: assert.ok(tabs.length > 1, 'tab count is > 1'); michael@0: assert.equal(count1, tabs.length, 'tab count by length is correct'); michael@0: assert.equal(count2, tabs.length, 'tab count by iteration is correct'); michael@0: michael@0: // end test michael@0: tab.close(done); michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // TEST: tabs.activeTab getter michael@0: exports.testActiveTab_getter = function(assert, done) { michael@0: let evtCount = 0; michael@0: let activeTab = null; michael@0: michael@0: function endTest(type, tab) { michael@0: if (type == 'activate') { michael@0: assert.strictEqual(tabs.activeTab, tab, 'the active tab is the opened tab'); michael@0: activeTab = tabs.activeTab; michael@0: } michael@0: else { michael@0: assert.equal(tab.url, url, 'the opened tab has the correct url'); michael@0: } michael@0: michael@0: if (++evtCount != 2) michael@0: return; michael@0: michael@0: assert.strictEqual(activeTab, tab, 'the active tab is the ready tab'); michael@0: assert.strictEqual(tabs.activeTab, tab, 'the active tab is the ready tab'); michael@0: michael@0: tab.close(done); michael@0: } michael@0: michael@0: let url = URL.replace("#title#", "testActiveTab_getter"); michael@0: tabs.open({ michael@0: url: url, michael@0: onReady: endTest.bind(null, 'ready'), michael@0: onActivate: endTest.bind(null, 'activate') michael@0: }); michael@0: }; michael@0: michael@0: // TEST: tab.activate() michael@0: exports.testActiveTab_setter = function(assert, done) { michael@0: let url = URL.replace("#title#", "testActiveTab_setter"); michael@0: let tab1URL = URL.replace("#title#", "tab1"); michael@0: michael@0: tabs.open({ michael@0: url: tab1URL, michael@0: onReady: function(activeTab) { michael@0: let activeTabURL = tabs.activeTab.url; michael@0: michael@0: tabs.open({ michael@0: url: url, michael@0: inBackground: true, michael@0: onReady: function onReady(tab) { michael@0: assert.equal(tabs.activeTab.url, activeTabURL, "activeTab url has not changed"); michael@0: assert.equal(tab.url, url, "url of new background tab matches"); michael@0: michael@0: tab.once('activate', function onActivate(eventTab) { michael@0: assert.equal(tabs.activeTab.url, url, "url after activeTab setter matches"); michael@0: assert.equal(eventTab, tab, "event argument is the activated tab"); michael@0: assert.equal(eventTab, tabs.activeTab, "the tab is the active one"); michael@0: michael@0: activeTab.close(function() { michael@0: tab.close(done); michael@0: }); michael@0: }); michael@0: michael@0: tab.activate(); michael@0: } michael@0: }); michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: // TEST: tab.close() michael@0: exports.testTabClose_alt = function(assert, done) { michael@0: let url = URL.replace('#title#', 'TabClose_alt'); michael@0: let tab1URL = URL.replace('#title#', 'tab1'); michael@0: michael@0: tabs.open({ michael@0: url: tab1URL, michael@0: onReady: function(tab1) { michael@0: // make sure that our tab is not active first michael@0: assert.notEqual(tabs.activeTab.url, url, "tab is not the active tab"); michael@0: michael@0: tabs.open({ michael@0: url: url, michael@0: onReady: function(tab) { michael@0: assert.equal(tab.url, url, "tab is now the active tab"); michael@0: assert.equal(tabs.activeTab.url, url, "tab is now the active tab"); michael@0: michael@0: // another tab should be activated on close michael@0: tabs.once('activate', function() { michael@0: assert.notEqual(tabs.activeTab.url, url, "tab is no longer the active tab"); michael@0: michael@0: // end test michael@0: tab1.close(done); michael@0: }); michael@0: michael@0: tab.close(); michael@0: } michael@0: }); michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: exports.testAttachOnOpen_alt = function (assert, done) { michael@0: // Take care that attach has to be called on tab ready and not on tab open. michael@0: tabs.open({ michael@0: url: "data:text/html;charset=utf-8,foobar", michael@0: onOpen: function (tab) { michael@0: let worker = tab.attach({ michael@0: contentScript: 'self.postMessage(document.location.href); ', michael@0: onMessage: function (msg) { michael@0: assert.equal(msg, "about:blank", michael@0: "Worker document url is about:blank on open"); michael@0: worker.destroy(); michael@0: tab.close(done); michael@0: } michael@0: }); michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: exports.testAttachOnMultipleDocuments_alt = function (assert, done) { michael@0: // Example of attach that process multiple tab documents michael@0: let firstLocation = "data:text/html;charset=utf-8,foobar"; michael@0: let secondLocation = "data:text/html;charset=utf-8,bar"; michael@0: let thirdLocation = "data:text/html;charset=utf-8,fox"; michael@0: let onReadyCount = 0; michael@0: let worker1 = null; michael@0: let worker2 = null; michael@0: let detachEventCount = 0; michael@0: michael@0: tabs.open({ michael@0: url: firstLocation, michael@0: onReady: function (tab) { michael@0: onReadyCount++; michael@0: if (onReadyCount == 1) { michael@0: worker1 = tab.attach({ michael@0: contentScript: 'self.on("message", ' + michael@0: ' function () self.postMessage(document.location.href)' + michael@0: ');', michael@0: onMessage: function (msg) { michael@0: assert.equal(msg, firstLocation, michael@0: "Worker url is equal to the 1st document"); michael@0: tab.url = secondLocation; michael@0: }, michael@0: onDetach: function () { michael@0: detachEventCount++; michael@0: assert.pass("Got worker1 detach event"); michael@0: assert.throws(function () { michael@0: worker1.postMessage("ex-1"); michael@0: }, michael@0: /Couldn't find the worker/, michael@0: "postMessage throw because worker1 is destroyed"); michael@0: checkEnd(); michael@0: } michael@0: }); michael@0: worker1.postMessage("new-doc-1"); michael@0: } michael@0: else if (onReadyCount == 2) { michael@0: worker2 = tab.attach({ michael@0: contentScript: 'self.on("message", ' + michael@0: ' function () self.postMessage(document.location.href)' + michael@0: ');', michael@0: onMessage: function (msg) { michael@0: assert.equal(msg, secondLocation, michael@0: "Worker url is equal to the 2nd document"); michael@0: tab.url = thirdLocation; michael@0: }, michael@0: onDetach: function () { michael@0: detachEventCount++; michael@0: assert.pass("Got worker2 detach event"); michael@0: assert.throws(function () { michael@0: worker2.postMessage("ex-2"); michael@0: }, michael@0: /Couldn't find the worker/, michael@0: "postMessage throw because worker2 is destroyed"); michael@0: checkEnd(tab); michael@0: } michael@0: }); michael@0: worker2.postMessage("new-doc-2"); michael@0: } michael@0: else if (onReadyCount == 3) { michael@0: tab.close(); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: function checkEnd(tab) { michael@0: if (detachEventCount != 2) michael@0: return; michael@0: michael@0: assert.pass("Got all detach events"); michael@0: michael@0: done(); michael@0: } michael@0: }; michael@0: michael@0: exports.testAttachWrappers_alt = function (assert, done) { michael@0: // Check that content script has access to wrapped values by default michael@0: michael@0: let document = "data:text/html;charset=utf-8,"; michael@0: let count = 0; michael@0: michael@0: tabs.open({ michael@0: url: document, michael@0: onReady: function (tab) { michael@0: let worker = tab.attach({ michael@0: contentScript: 'try {' + michael@0: ' self.postMessage(!("globalJSVar" in window));' + michael@0: ' self.postMessage(typeof window.globalJSVar == "undefined");' + michael@0: '} catch(e) {' + michael@0: ' self.postMessage(e.message);' + michael@0: '}', michael@0: onMessage: function (msg) { michael@0: assert.equal(msg, true, "Worker has wrapped objects ("+count+")"); michael@0: if (count++ == 1) michael@0: tab.close(function() done()); michael@0: } michael@0: }); michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: // TEST: activeWindow getter and activeTab getter on tab 'activate' event michael@0: exports.testActiveWindowActiveTabOnActivate_alt = function(assert, done) { michael@0: michael@0: let activateCount = 0; michael@0: let newTabs = []; michael@0: let tabs = browserWindows.activeWindow.tabs; michael@0: michael@0: tabs.on('activate', function onActivate(tab) { michael@0: assert.equal(tabs.activeTab, tab, michael@0: "the active window's active tab is the tab provided"); michael@0: michael@0: if (++activateCount == 2) { michael@0: tabs.removeListener('activate', onActivate); michael@0: michael@0: newTabs.forEach(function(tab) { michael@0: tab.close(function() { michael@0: if (--activateCount == 0) { michael@0: done(); michael@0: } michael@0: }); michael@0: }); michael@0: } michael@0: else if (activateCount > 2) { michael@0: assert.fail("activateCount is greater than 2 for some reason.."); michael@0: } michael@0: }); michael@0: michael@0: tabs.open({ michael@0: url: URL.replace("#title#", "tabs.open1"), michael@0: onOpen: function(tab) newTabs.push(tab) michael@0: }); michael@0: tabs.open({ michael@0: url: URL.replace("#title#", "tabs.open2"), michael@0: onOpen: function(tab) newTabs.push(tab) michael@0: }); michael@0: }; michael@0: michael@0: // TEST: tab properties michael@0: exports.testTabContentTypeAndReload = function(assert, done) { michael@0: michael@0: let url = "data:text/html;charset=utf-8,foofoo"; michael@0: let urlXML = "data:text/xml;charset=utf-8,bar"; michael@0: tabs.open({ michael@0: url: url, michael@0: onReady: function(tab) { michael@0: if (tab.url === url) { michael@0: assert.equal(tab.contentType, "text/html"); michael@0: tab.url = urlXML; michael@0: } michael@0: else { michael@0: assert.equal(tab.contentType, "text/xml"); michael@0: tab.close(done); michael@0: } michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: // test that it isn't possible to open a private tab without the private permission michael@0: exports.testTabOpenPrivate = function(assert, done) { michael@0: michael@0: let url = 'about:blank'; michael@0: tabs.open({ michael@0: url: url, michael@0: isPrivate: true, michael@0: onReady: function(tab) { michael@0: assert.equal(tab.url, url, 'opened correct tab'); michael@0: assert.equal(isPrivate(tab), false, 'private tabs are not supported by default'); michael@0: michael@0: tab.close(done); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: // We need permission flag in order to see private window's tabs michael@0: exports.testPrivateAreNotListed = function (assert, done) { michael@0: let originalTabCount = tabs.length; michael@0: michael@0: let page = openWebpage("about:blank", true); michael@0: if (!page) { michael@0: assert.pass("Private browsing isn't supported in this release"); michael@0: return; michael@0: } michael@0: michael@0: page.ready.then(function (win) { michael@0: if (isTabPBSupported || isWindowPBSupported) { michael@0: assert.ok(isWindowPrivate(win), "the window is private"); michael@0: assert.equal(tabs.length, originalTabCount, michael@0: 'but the tab is *not* visible in tabs list'); michael@0: } michael@0: else { michael@0: assert.ok(!isWindowPrivate(win), "the window isn't private"); michael@0: assert.equal(tabs.length, originalTabCount + 1, michael@0: 'so that the tab is visible is tabs list'); michael@0: } michael@0: page.close().then(done); michael@0: }); michael@0: } michael@0: michael@0: // If we close the tab while being in `onOpen` listener, michael@0: // we end up synchronously consuming TabOpen, closing the tab and still michael@0: // synchronously consuming the related TabClose event before the second michael@0: // loader have a change to process the first TabOpen event! michael@0: exports.testImmediateClosing = function (assert, done) { michael@0: let tabURL = 'data:text/html,foo'; michael@0: michael@0: let { loader, messages } = LoaderWithHookedConsole(module, onMessage); michael@0: let concurrentTabs = loader.require("sdk/tabs"); michael@0: concurrentTabs.on("open", function (tab) { michael@0: // On Firefox, It shouldn't receive such event as the other loader will just michael@0: // open and destroy the tab without giving a chance to other loader to even michael@0: // know about the existance of this tab. michael@0: if (app.is("Firefox")) { michael@0: assert.fail("Concurrent loader received a tabs `open` event"); michael@0: } michael@0: else { michael@0: // On mobile, we can still receive an open event, michael@0: // but not the related ready event michael@0: tab.on("ready", function () { michael@0: assert.fail("Concurrent loader received a tabs `ready` event"); michael@0: }); michael@0: } michael@0: }); michael@0: function onMessage(type, msg) { michael@0: assert.fail("Unexpected mesage on concurrent loader: " + msg); michael@0: } michael@0: michael@0: tabs.open({ michael@0: url: tabURL, michael@0: onOpen: function(tab) { michael@0: tab.close(function () { michael@0: assert.pass("Tab succesfully removed"); michael@0: // Let a chance to the concurrent loader to receive a TabOpen event michael@0: // on the next event loop turn michael@0: setTimeout(function () { michael@0: loader.unload(); michael@0: done(); michael@0: }, 0); michael@0: }); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: // TEST: tab.reload() michael@0: exports.testTabReload = function(assert, done) { michael@0: michael@0: let url = "data:text/html;charset=utf-8,"; michael@0: michael@0: tabs.open({ michael@0: url: url, michael@0: onReady: function onReady(tab) { michael@0: tab.removeListener('ready', onReady); michael@0: michael@0: tab.once( michael@0: 'ready', michael@0: function onReload() { michael@0: assert.pass("the tab was loaded again"); michael@0: assert.equal(tab.url, url, "the tab has the same URL"); michael@0: michael@0: tab.close(function() done()); michael@0: } michael@0: ); michael@0: michael@0: tab.reload(); michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: exports.testOnPageShowEvent = function (assert, done) { michael@0: let events = []; michael@0: let firstUrl = 'data:text/html;charset=utf-8,First'; michael@0: let secondUrl = 'data:text/html;charset=utf-8,Second'; michael@0: michael@0: let counter = 0; michael@0: function onPageShow (tab, persisted) { michael@0: events.push('pageshow'); michael@0: counter++; michael@0: if (counter === 1) { michael@0: assert.equal(persisted, false, 'page should not be cached on initial load'); michael@0: tab.url = secondUrl; michael@0: } michael@0: else if (counter === 2) { michael@0: assert.equal(persisted, false, 'second test page should not be cached either'); michael@0: tab.attach({ michael@0: contentScript: 'setTimeout(function () { window.history.back(); }, 0)' michael@0: }); michael@0: } michael@0: else { michael@0: assert.equal(persisted, true, 'when we get back to the fist page, it has to' + michael@0: 'come from cache'); michael@0: tabs.removeListener('pageshow', onPageShow); michael@0: tabs.removeListener('open', onOpen); michael@0: tabs.removeListener('ready', onReady); michael@0: tab.close(() => { michael@0: ['open', 'ready', 'pageshow', 'ready', michael@0: 'pageshow', 'pageshow'].map((type, i) => { michael@0: assert.equal(type, events[i], 'correct ordering of events'); michael@0: }); michael@0: done() michael@0: }); michael@0: } michael@0: } michael@0: michael@0: function onOpen () events.push('open'); michael@0: function onReady () events.push('ready'); michael@0: michael@0: tabs.on('pageshow', onPageShow); michael@0: tabs.on('open', onOpen); michael@0: tabs.on('ready', onReady); michael@0: tabs.open({ michael@0: url: firstUrl michael@0: }); michael@0: }; michael@0: michael@0: exports.testOnPageShowEventDeclarative = function (assert, done) { michael@0: let events = []; michael@0: let firstUrl = 'data:text/html;charset=utf-8,First'; michael@0: let secondUrl = 'data:text/html;charset=utf-8,Second'; michael@0: michael@0: let counter = 0; michael@0: function onPageShow (tab, persisted) { michael@0: events.push('pageshow'); michael@0: counter++; michael@0: if (counter === 1) { michael@0: assert.equal(persisted, false, 'page should not be cached on initial load'); michael@0: tab.url = secondUrl; michael@0: } michael@0: else if (counter === 2) { michael@0: assert.equal(persisted, false, 'second test page should not be cached either'); michael@0: tab.attach({ michael@0: contentScript: 'setTimeout(function () { window.history.back(); }, 0)' michael@0: }); michael@0: } michael@0: else { michael@0: assert.equal(persisted, true, 'when we get back to the fist page, it has to' + michael@0: 'come from cache'); michael@0: tabs.removeListener('pageshow', onPageShow); michael@0: tabs.removeListener('open', onOpen); michael@0: tabs.removeListener('ready', onReady); michael@0: tab.close(() => { michael@0: ['open', 'ready', 'pageshow', 'ready', michael@0: 'pageshow', 'pageshow'].map((type, i) => { michael@0: assert.equal(type, events[i], 'correct ordering of events'); michael@0: }); michael@0: done() michael@0: }); michael@0: } michael@0: } michael@0: michael@0: function onOpen () events.push('open'); michael@0: function onReady () events.push('ready'); michael@0: michael@0: tabs.open({ michael@0: url: firstUrl, michael@0: onPageShow: onPageShow, michael@0: onOpen: onOpen, michael@0: onReady: onReady michael@0: }); michael@0: }; michael@0: michael@0: require('sdk/test').run(exports);