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 { Cc, Ci } = require('chrome');
michael@0: const { Loader, LoaderWithHookedConsole } = require('sdk/test/loader');
michael@0: const timer = require('sdk/timers');
michael@0: const tabs = require('sdk/tabs');
michael@0: const windows = require('sdk/windows');
michael@0: const { set: setPref } = require("sdk/preferences/service");
michael@0: const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings";
michael@0:
michael@0: const tabsLen = tabs.length;
michael@0: const URL = 'data:text/html;charset=utf-8,
#title#';
michael@0:
michael@0: // Fennec error message dispatched on all currently unimplement tab features,
michael@0: // that match LoaderWithHookedConsole messages object pattern
michael@0: const ERR_FENNEC_MSG = {
michael@0: type: "error",
michael@0: msg: "This method is not yet supported by Fennec"
michael@0: };
michael@0:
michael@0: // TEST: tab unloader
michael@0: exports.testAutomaticDestroy = function(assert, done) {
michael@0: let called = false;
michael@0:
michael@0: let loader2 = Loader(module);
michael@0: let loader3 = Loader(module);
michael@0: let tabs2 = loader2.require('sdk/tabs');
michael@0: let tabs3 = loader3.require('sdk/tabs');
michael@0: let tabs2Len = tabs2.length;
michael@0:
michael@0: tabs2.on('open', function onOpen(tab) {
michael@0: assert.fail("an onOpen listener was called that should not have been");
michael@0: called = true;
michael@0: });
michael@0: tabs2.on('ready', function onReady(tab) {
michael@0: assert.fail("an onReady listener was called that should not have been");
michael@0: called = true;
michael@0: });
michael@0: tabs2.on('select', function onSelect(tab) {
michael@0: assert.fail("an onSelect listener was called that should not have been");
michael@0: called = true;
michael@0: });
michael@0: tabs2.on('close', function onClose(tab) {
michael@0: assert.fail("an onClose listener was called that should not have been");
michael@0: called = true;
michael@0: });
michael@0: loader2.unload();
michael@0:
michael@0: tabs3.on('open', function onOpen(tab) {
michael@0: assert.pass("an onOpen listener was called for tabs3");
michael@0:
michael@0: tab.on('ready', function onReady(tab) {
michael@0: assert.fail("an onReady listener was called that should not have been");
michael@0: called = true;
michael@0: });
michael@0: tab.on('select', function onSelect(tab) {
michael@0: assert.fail("an onSelect listener was called that should not have been");
michael@0: called = true;
michael@0: });
michael@0: tab.on('close', function onClose(tab) {
michael@0: assert.fail("an onClose listener was called that should not have been");
michael@0: called = true;
michael@0: });
michael@0: });
michael@0: tabs3.open(URL.replace(/#title#/, 'tabs3'));
michael@0: loader3.unload();
michael@0:
michael@0: // Fire a tab event and ensure that the destroyed tab is inactive
michael@0: tabs.once('open', function(tab) {
michael@0: assert.pass('tabs.once("open") works!');
michael@0:
michael@0: assert.equal(tabs2Len, tabs2.length, "tabs2 length was not changed");
michael@0: assert.equal(tabs.length, (tabs2.length+2), "tabs.length > tabs2.length");
michael@0:
michael@0: tab.once('ready', function() {
michael@0: assert.pass('tab.once("ready") works!');
michael@0:
michael@0: tab.once('close', function() {
michael@0: assert.pass('tab.once("close") works!');
michael@0:
michael@0: timer.setTimeout(function () {
michael@0: assert.ok(!called, "Unloaded tab module is destroyed and inactive");
michael@0:
michael@0: // end test
michael@0: done();
michael@0: });
michael@0: });
michael@0:
michael@0: tab.close();
michael@0: });
michael@0: });
michael@0:
michael@0: tabs.open('data:text/html;charset=utf-8,foo');
michael@0: };
michael@0:
michael@0: // TEST: tab properties
michael@0: exports.testTabProperties = function(assert, done) {
michael@0: setPref(DEPRECATE_PREF, true);
michael@0: let { loader, messages } = LoaderWithHookedConsole();
michael@0: let tabs = loader.require('sdk/tabs');
michael@0:
michael@0: let url = "data:text/html;charset=utf-8,foofoo";
michael@0: let tabsLen = tabs.length;
michael@0: tabs.open({
michael@0: url: url,
michael@0: onReady: function(tab) {
michael@0: assert.equal(tab.title, "foo", "title of the new tab matches");
michael@0: assert.equal(tab.url, url, "URL of the new tab matches");
michael@0: assert.ok(tab.favicon, "favicon of the new tab is not empty");
michael@0: // TODO: remove need for this test by implementing the favicon feature
michael@0: assert.equal(messages[0].msg,
michael@0: "tab.favicon is deprecated, and " +
michael@0: "currently favicon helpers are not yet supported " +
michael@0: "by Fennec",
michael@0: "favicon logs an error for now");
michael@0: assert.equal(tab.style, null, "style of the new tab matches");
michael@0: assert.equal(tab.index, tabsLen, "index of the new tab matches");
michael@0: assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
michael@0: assert.notEqual(tab.id, null, "a tab object always has an id property");
michael@0:
michael@0: tab.close(function() {
michael@0: loader.unload();
michael@0:
michael@0: // end test
michael@0: done();
michael@0: });
michael@0: }
michael@0: });
michael@0: };
michael@0:
michael@0: // TEST: tabs iterator and length property
michael@0: exports.testTabsIteratorAndLength = function(assert, done) {
michael@0: let newTabs = [];
michael@0: let startCount = 0;
michael@0: for each (let t in tabs) startCount++;
michael@0:
michael@0: assert.equal(startCount, tabs.length, "length property is correct");
michael@0:
michael@0: let url = "data:text/html;charset=utf-8,testTabsIteratorAndLength";
michael@0: tabs.open({url: url, onOpen: function(tab) newTabs.push(tab)});
michael@0: tabs.open({url: url, onOpen: function(tab) newTabs.push(tab)});
michael@0: tabs.open({
michael@0: url: url,
michael@0: onOpen: function(tab) {
michael@0: let count = 0;
michael@0: for each (let t in tabs) count++;
michael@0: assert.equal(count, startCount + 3, "iterated tab count matches");
michael@0: assert.equal(startCount + 3, tabs.length, "iterated tab count matches length property");
michael@0:
michael@0: let newTabsLength = newTabs.length;
michael@0: newTabs.forEach(function(t) t.close(function() {
michael@0: if (--newTabsLength > 0) return;
michael@0:
michael@0: tab.close(done);
michael@0: }));
michael@0: }
michael@0: });
michael@0: };
michael@0:
michael@0: // TEST: tab.url setter
michael@0: exports.testTabLocation = function(assert, done) {
michael@0: let url1 = "data:text/html;charset=utf-8,foo";
michael@0: let url2 = "data:text/html;charset=utf-8,bar";
michael@0:
michael@0: tabs.on('ready', function onReady(tab) {
michael@0: if (tab.url != url2)
michael@0: return;
michael@0:
michael@0: tabs.removeListener('ready', onReady);
michael@0: assert.pass("tab loaded the correct url");
michael@0:
michael@0: tab.close(done);
michael@0: });
michael@0:
michael@0: tabs.open({
michael@0: url: url1,
michael@0: onOpen: function(tab) {
michael@0: tab.url = url2;
michael@0: }
michael@0: });
michael@0: };
michael@0:
michael@0: // TEST: tab.move()
michael@0: exports.testTabMove = function(assert, done) {
michael@0: let { loader, messages } = LoaderWithHookedConsole();
michael@0: let tabs = loader.require('sdk/tabs');
michael@0:
michael@0: let url = "data:text/html;charset=utf-8,testTabMove";
michael@0:
michael@0: tabs.open({
michael@0: url: url,
michael@0: onOpen: function(tab1) {
michael@0: assert.ok(tab1.index >= 0, "opening a tab returns a tab w/ valid index");
michael@0:
michael@0: tabs.open({
michael@0: url: url,
michael@0: onOpen: function(tab) {
michael@0: let i = tab.index;
michael@0: assert.ok(tab.index > tab1.index, "2nd tab has valid index");
michael@0: tab.index = 0;
michael@0: assert.equal(tab.index, i, "tab index after move matches");
michael@0: assert.equal(JSON.stringify(messages),
michael@0: JSON.stringify([ERR_FENNEC_MSG]),
michael@0: "setting tab.index logs error");
michael@0: // end test
michael@0: tab1.close(function() tab.close(function() {
michael@0: loader.unload();
michael@0: done();
michael@0: }));
michael@0: }
michael@0: });
michael@0: }
michael@0: });
michael@0: };
michael@0:
michael@0: // TEST: open tab with default options
michael@0: exports.testTabsOpen_alt = function(assert, done) {
michael@0: let { loader, messages } = LoaderWithHookedConsole();
michael@0: let tabs = loader.require('sdk/tabs');
michael@0: let url = "data:text/html;charset=utf-8,default";
michael@0:
michael@0: tabs.open({
michael@0: url: url,
michael@0: onReady: function(tab) {
michael@0: assert.equal(tab.url, url, "URL of the new tab matches");
michael@0: assert.equal(tabs.activeTab, tab, "URL of active tab in the current window matches");
michael@0: assert.equal(tab.isPinned, false, "The new tab is not pinned");
michael@0: assert.equal(messages.length, 1, "isPinned logs error");
michael@0:
michael@0: // end test
michael@0: tab.close(function() {
michael@0: loader.unload();
michael@0: done();
michael@0: });
michael@0: }
michael@0: });
michael@0: };
michael@0:
michael@0: // TEST: open pinned tab
michael@0: exports.testOpenPinned_alt = function(assert, done) {
michael@0: let { loader, messages } = LoaderWithHookedConsole();
michael@0: let tabs = loader.require('sdk/tabs');
michael@0: let url = "about:blank";
michael@0:
michael@0: tabs.open({
michael@0: url: url,
michael@0: isPinned: true,
michael@0: onOpen: function(tab) {
michael@0: assert.equal(tab.isPinned, false, "The new tab is pinned");
michael@0: // We get two error message: one for tabs.open's isPinned argument
michael@0: // and another one for tab.isPinned
michael@0: assert.equal(JSON.stringify(messages),
michael@0: JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]),
michael@0: "isPinned logs error");
michael@0:
michael@0: // end test
michael@0: tab.close(function() {
michael@0: loader.unload();
michael@0: done();
michael@0: });
michael@0: }
michael@0: });
michael@0: };
michael@0:
michael@0: // TEST: pin/unpin opened tab
michael@0: exports.testPinUnpin_alt = function(assert, done) {
michael@0: let { loader, messages } = LoaderWithHookedConsole();
michael@0: let tabs = loader.require('sdk/tabs');
michael@0: let url = "data:text/html;charset=utf-8,default";
michael@0:
michael@0: tabs.open({
michael@0: url: url,
michael@0: onOpen: function(tab) {
michael@0: tab.pin();
michael@0: assert.equal(tab.isPinned, false, "The tab was pinned correctly");
michael@0: assert.equal(JSON.stringify(messages),
michael@0: JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]),
michael@0: "tab.pin() logs error");
michael@0:
michael@0: // Clear console messages for the following test
michael@0: messages.length = 0;
michael@0:
michael@0: tab.unpin();
michael@0: assert.equal(tab.isPinned, false, "The tab was unpinned correctly");
michael@0: assert.equal(JSON.stringify(messages),
michael@0: JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]),
michael@0: "tab.unpin() logs error");
michael@0:
michael@0: // end test
michael@0: tab.close(function() {
michael@0: loader.unload();
michael@0: done();
michael@0: });
michael@0: }
michael@0: });
michael@0: };
michael@0:
michael@0: // TEST: open tab in background
michael@0: exports.testInBackground = function(assert, done) {
michael@0: let activeUrl = tabs.activeTab.url;
michael@0: let url = "data:text/html;charset=utf-8,background";
michael@0: let window = windows.browserWindows.activeWindow;
michael@0: tabs.once('ready', function onReady(tab) {
michael@0: assert.equal(tabs.activeTab.url, activeUrl, "URL of active tab has not changed");
michael@0: assert.equal(tab.url, url, "URL of the new background tab matches");
michael@0: assert.equal(windows.browserWindows.activeWindow, window, "a new window was not opened");
michael@0: assert.notEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL");
michael@0:
michael@0: // end test
michael@0: tab.close(done);
michael@0: });
michael@0:
michael@0: tabs.open({
michael@0: url: url,
michael@0: inBackground: true
michael@0: });
michael@0: };
michael@0:
michael@0: // TEST: open tab in new window
michael@0: exports.testOpenInNewWindow = function(assert, done) {
michael@0: let url = "data:text/html;charset=utf-8,newwindow";
michael@0: let window = windows.browserWindows.activeWindow;
michael@0:
michael@0: tabs.open({
michael@0: url: url,
michael@0: inNewWindow: true,
michael@0: onReady: function(tab) {
michael@0: assert.equal(windows.browserWindows.length, 1, "a new window was not opened");
michael@0: assert.equal(windows.browserWindows.activeWindow, window, "old window is active");
michael@0: assert.equal(tab.url, url, "URL of the new tab matches");
michael@0: assert.equal(tabs.activeTab, tab, "tab is the activeTab");
michael@0:
michael@0: tab.close(done);
michael@0: }
michael@0: });
michael@0: };
michael@0:
michael@0: // TEST: onOpen event handler
michael@0: exports.testTabsEvent_onOpen = function(assert, done) {
michael@0: let url = URL.replace('#title#', 'testTabsEvent_onOpen');
michael@0: let eventCount = 0;
michael@0:
michael@0: // add listener via property assignment
michael@0: function listener1(tab) {
michael@0: eventCount++;
michael@0: };
michael@0: tabs.on('open', listener1);
michael@0:
michael@0: // add listener via collection add
michael@0: tabs.on('open', function listener2(tab) {
michael@0: assert.equal(++eventCount, 2, "both listeners notified");
michael@0: tabs.removeListener('open', listener1);
michael@0: tabs.removeListener('open', listener2);
michael@0:
michael@0: // ends test
michael@0: tab.close(done);
michael@0: });
michael@0:
michael@0: tabs.open(url);
michael@0: };
michael@0:
michael@0: // TEST: onClose event handler
michael@0: exports.testTabsEvent_onClose = function(assert, done) {
michael@0: let url = "data:text/html;charset=utf-8,onclose";
michael@0: let eventCount = 0;
michael@0:
michael@0: // add listener via property assignment
michael@0: function listener1(tab) {
michael@0: eventCount++;
michael@0: }
michael@0: tabs.on('close', listener1);
michael@0:
michael@0: // add listener via collection add
michael@0: tabs.on('close', function listener2(tab) {
michael@0: assert.equal(++eventCount, 2, "both listeners notified");
michael@0: tabs.removeListener('close', listener1);
michael@0: tabs.removeListener('close', listener2);
michael@0:
michael@0: // end test
michael@0: done();
michael@0: });
michael@0:
michael@0: tabs.on('ready', function onReady(tab) {
michael@0: tabs.removeListener('ready', onReady);
michael@0: tab.close();
michael@0: });
michael@0:
michael@0: tabs.open(url);
michael@0: };
michael@0:
michael@0: // TEST: onClose event handler when a window is closed
michael@0: exports.testTabsEvent_onCloseWindow = function(assert, done) {
michael@0: let closeCount = 0, individualCloseCount = 0;
michael@0: function listener() {
michael@0: closeCount++;
michael@0: }
michael@0: tabs.on('close', listener);
michael@0:
michael@0: // One tab is already open with the window
michael@0: let openTabs = 0;
michael@0: function testCasePossiblyLoaded(tab) {
michael@0: tab.close(function() {
michael@0: if (++openTabs == 3) {
michael@0: tabs.removeListener("close", listener);
michael@0:
michael@0: assert.equal(closeCount, 3, "Correct number of close events received");
michael@0: assert.equal(individualCloseCount, 3,
michael@0: "Each tab with an attached onClose listener received a close " +
michael@0: "event when the window was closed");
michael@0:
michael@0: done();
michael@0: }
michael@0: });
michael@0: }
michael@0:
michael@0: tabs.open({
michael@0: url: "data:text/html;charset=utf-8,tab2",
michael@0: onOpen: testCasePossiblyLoaded,
michael@0: onClose: function() individualCloseCount++
michael@0: });
michael@0:
michael@0: tabs.open({
michael@0: url: "data:text/html;charset=utf-8,tab3",
michael@0: onOpen: testCasePossiblyLoaded,
michael@0: onClose: function() individualCloseCount++
michael@0: });
michael@0:
michael@0: tabs.open({
michael@0: url: "data:text/html;charset=utf-8,tab4",
michael@0: onOpen: testCasePossiblyLoaded,
michael@0: onClose: function() individualCloseCount++
michael@0: });
michael@0: };
michael@0:
michael@0: // TEST: onReady event handler
michael@0: exports.testTabsEvent_onReady = function(assert, done) {
michael@0: let url = "data:text/html;charset=utf-8,onready";
michael@0: let eventCount = 0;
michael@0:
michael@0: // add listener via property assignment
michael@0: function listener1(tab) {
michael@0: eventCount++;
michael@0: };
michael@0: tabs.on('ready', listener1);
michael@0:
michael@0: // add listener via collection add
michael@0: tabs.on('ready', function listener2(tab) {
michael@0: assert.equal(++eventCount, 2, "both listeners notified");
michael@0: tabs.removeListener('ready', listener1);
michael@0: tabs.removeListener('ready', listener2);
michael@0:
michael@0: // end test
michael@0: tab.close(done);
michael@0: });
michael@0:
michael@0: tabs.open(url);
michael@0: };
michael@0:
michael@0: // TEST: onActivate event handler
michael@0: exports.testTabsEvent_onActivate = function(assert, done) {
michael@0: let url = "data:text/html;charset=utf-8,onactivate";
michael@0: let eventCount = 0;
michael@0:
michael@0: // add listener via property assignment
michael@0: function listener1(tab) {
michael@0: eventCount++;
michael@0: };
michael@0: tabs.on('activate', listener1);
michael@0:
michael@0: // add listener via collection add
michael@0: tabs.on('activate', function listener2(tab) {
michael@0: assert.equal(++eventCount, 2, "both listeners notified");
michael@0: assert.equal(tab, tabs.activeTab, 'the active tab is correct');
michael@0: tabs.removeListener('activate', listener1);
michael@0: tabs.removeListener('activate', listener2);
michael@0:
michael@0: // end test
michael@0: tab.close(done);
michael@0: });
michael@0:
michael@0: tabs.open(url);
michael@0: };
michael@0:
michael@0: // TEST: onDeactivate event handler
michael@0: exports.testTabsEvent_onDeactivate = function(assert, done) {
michael@0: let url = "data:text/html;charset=utf-8,ondeactivate";
michael@0: let eventCount = 0;
michael@0:
michael@0: // add listener via property assignment
michael@0: function listener1(tab) {
michael@0: eventCount++;
michael@0: };
michael@0: tabs.on('deactivate', listener1);
michael@0:
michael@0: // add listener via collection add
michael@0: tabs.on('deactivate', function listener2(tab) {
michael@0: assert.equal(++eventCount, 2, 'both listeners notified');
michael@0: assert.notEqual(tab, tabs.activeTab, 'the active tab is not the deactivated tab');
michael@0: tabs.removeListener('deactivate', listener1);
michael@0: tabs.removeListener('deactivate', listener2);
michael@0:
michael@0: // end test
michael@0: tab.close(done);
michael@0: });
michael@0:
michael@0: tabs.on('activate', function onActivate(tab) {
michael@0: tabs.removeListener('activate', onActivate);
michael@0: tabs.open("data:text/html;charset=utf-8,foo");
michael@0: tab.close();
michael@0: });
michael@0:
michael@0: tabs.open(url);
michael@0: };
michael@0:
michael@0: // TEST: per-tab event handlers
michael@0: exports.testPerTabEvents = function(assert, done) {
michael@0: let eventCount = 0;
michael@0:
michael@0: tabs.open({
michael@0: url: "data:text/html;charset=utf-8,foo",
michael@0: onOpen: function(tab) {
michael@0: // add listener via property assignment
michael@0: function listener1() {
michael@0: eventCount++;
michael@0: };
michael@0: tab.on('ready', listener1);
michael@0:
michael@0: // add listener via collection add
michael@0: tab.on('ready', function listener2() {
michael@0: assert.equal(eventCount, 1, "both listeners notified");
michael@0: tab.removeListener('ready', listener1);
michael@0: tab.removeListener('ready', listener2);
michael@0:
michael@0: // end test
michael@0: tab.close(done);
michael@0: });
michael@0: }
michael@0: });
michael@0: };
michael@0:
michael@0: exports.testUniqueTabIds = function(assert, done) {
michael@0: var tabs = require('sdk/tabs');
michael@0: var tabIds = {};
michael@0: var steps = [
michael@0: function (index) {
michael@0: tabs.open({
michael@0: url: "data:text/html;charset=utf-8,foo",
michael@0: onOpen: function(tab) {
michael@0: tabIds['tab1'] = tab.id;
michael@0: next(index);
michael@0: }
michael@0: });
michael@0: },
michael@0: function (index) {
michael@0: tabs.open({
michael@0: url: "data:text/html;charset=utf-8,bar",
michael@0: onOpen: function(tab) {
michael@0: tabIds['tab2'] = tab.id;
michael@0: next(index);
michael@0: }
michael@0: });
michael@0: },
michael@0: function (index) {
michael@0: assert.notEqual(tabIds.tab1, tabIds.tab2, "Tab ids should be unique.");
michael@0: done();
michael@0: }
michael@0: ];
michael@0:
michael@0: function next(index) {
michael@0: if (index === steps.length) {
michael@0: return;
michael@0: }
michael@0: let fn = steps[index];
michael@0: index++;
michael@0: fn(index);
michael@0: }
michael@0:
michael@0: next(0);
michael@0: }
michael@0:
michael@0: exports.testOnLoadEventWithDOM = function(assert, done) {
michael@0: let count = 0;
michael@0: let title = 'testOnLoadEventWithDOM';
michael@0:
michael@0: tabs.open({
michael@0: url: 'data:text/html;charset=utf-8,' + title + '',
michael@0: inBackground: true,
michael@0: onLoad: function(tab) {
michael@0: assert.equal(tab.title, title, 'tab passed in as arg, load called');
michael@0:
michael@0: if (++count > 1) {
michael@0: assert.pass('onLoad event called on reload');
michael@0: tab.close(done);
michael@0: }
michael@0: else {
michael@0: assert.pass('first onLoad event occured');
michael@0: tab.reload();
michael@0: }
michael@0: }
michael@0: });
michael@0: };
michael@0:
michael@0: require('sdk/test').run(exports);