summaryrefslogtreecommitdiffstats
path: root/browser/components/extensions/test/browser/browser_ext_tabs_events.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/extensions/test/browser/browser_ext_tabs_events.js')
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_events.js794
1 files changed, 794 insertions, 0 deletions
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_events.js b/browser/components/extensions/test/browser/browser_ext_tabs_events.js
new file mode 100644
index 0000000000..fe9317b4a6
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_events.js
@@ -0,0 +1,794 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// A single monitor for the tests. If it receives any
+// incognito data in event listeners it will fail.
+let monitor;
+add_task(async function startup() {
+ monitor = await startIncognitoMonitorExtension();
+});
+registerCleanupFunction(async function finish() {
+ await monitor.unload();
+});
+
+// Test tab events from private windows, the monitor above will fail
+// if it receives any.
+add_task(async function test_tab_events_incognito_monitored() {
+ async function background() {
+ let incognito = true;
+ let events = [];
+ let eventPromise;
+ let checkEvents = () => {
+ if (eventPromise && events.length >= eventPromise.names.length) {
+ eventPromise.resolve();
+ }
+ };
+
+ browser.tabs.onCreated.addListener(tab => {
+ events.push({ type: "onCreated", tab });
+ checkEvents();
+ });
+
+ browser.tabs.onAttached.addListener((tabId, info) => {
+ events.push(Object.assign({ type: "onAttached", tabId }, info));
+ checkEvents();
+ });
+
+ browser.tabs.onDetached.addListener((tabId, info) => {
+ events.push(Object.assign({ type: "onDetached", tabId }, info));
+ checkEvents();
+ });
+
+ browser.tabs.onRemoved.addListener((tabId, info) => {
+ events.push(Object.assign({ type: "onRemoved", tabId }, info));
+ checkEvents();
+ });
+
+ browser.tabs.onMoved.addListener((tabId, info) => {
+ events.push(Object.assign({ type: "onMoved", tabId }, info));
+ checkEvents();
+ });
+
+ async function expectEvents(names) {
+ browser.test.log(`Expecting events: ${names.join(", ")}`);
+
+ await new Promise(resolve => {
+ eventPromise = { names, resolve };
+ checkEvents();
+ });
+
+ browser.test.assertEq(
+ names.length,
+ events.length,
+ "Got expected number of events"
+ );
+ for (let [i, name] of names.entries()) {
+ browser.test.assertEq(
+ name,
+ i in events && events[i].type,
+ `Got expected ${name} event`
+ );
+ }
+ return events.splice(0);
+ }
+
+ try {
+ let firstWindow = await browser.windows.create({
+ url: "about:blank",
+ incognito,
+ });
+ let otherWindow = await browser.windows.create({
+ url: "about:blank",
+ incognito,
+ });
+
+ let windowId = firstWindow.id;
+ let otherWindowId = otherWindow.id;
+
+ // Wait for a tab in each window
+ await expectEvents(["onCreated", "onCreated"]);
+ let initialTab = (
+ await browser.tabs.query({
+ active: true,
+ windowId: otherWindowId,
+ })
+ )[0];
+
+ browser.test.log("Create tab in window 1");
+ let tab = await browser.tabs.create({
+ windowId,
+ index: 0,
+ url: "about:blank",
+ });
+ let oldIndex = tab.index;
+ browser.test.assertEq(0, oldIndex, "Tab has the expected index");
+ browser.test.assertEq(tab.incognito, incognito, "Tab is incognito");
+
+ let [created] = await expectEvents(["onCreated"]);
+ browser.test.assertEq(tab.id, created.tab.id, "Got expected tab ID");
+ browser.test.assertEq(
+ oldIndex,
+ created.tab.index,
+ "Got expected tab index"
+ );
+
+ browser.test.log("Move tab to window 2");
+ await browser.tabs.move([tab.id], { windowId: otherWindowId, index: 0 });
+
+ let [detached, attached] = await expectEvents([
+ "onDetached",
+ "onAttached",
+ ]);
+ browser.test.assertEq(
+ tab.id,
+ detached.tabId,
+ "Expected onDetached tab ID"
+ );
+ browser.test.assertEq(
+ oldIndex,
+ detached.oldPosition,
+ "Expected old index"
+ );
+ browser.test.assertEq(
+ windowId,
+ detached.oldWindowId,
+ "Expected old window ID"
+ );
+
+ browser.test.assertEq(
+ tab.id,
+ attached.tabId,
+ "Expected onAttached tab ID"
+ );
+ browser.test.assertEq(0, attached.newPosition, "Expected new index");
+ browser.test.assertEq(
+ otherWindowId,
+ attached.newWindowId,
+ "Expected new window ID"
+ );
+
+ browser.test.log("Move tab within the same window");
+ let [moved] = await browser.tabs.move([tab.id], { index: 1 });
+ browser.test.assertEq(1, moved.index, "Expected new index");
+
+ [moved] = await expectEvents(["onMoved"]);
+ browser.test.assertEq(tab.id, moved.tabId, "Expected tab ID");
+ browser.test.assertEq(0, moved.fromIndex, "Expected old index");
+ browser.test.assertEq(1, moved.toIndex, "Expected new index");
+ browser.test.assertEq(
+ otherWindowId,
+ moved.windowId,
+ "Expected window ID"
+ );
+
+ browser.test.log("Remove tab");
+ await browser.tabs.remove(tab.id);
+ let [removed] = await expectEvents(["onRemoved"]);
+
+ browser.test.assertEq(
+ tab.id,
+ removed.tabId,
+ "Expected removed tab ID for tabs.remove"
+ );
+ browser.test.assertEq(
+ otherWindowId,
+ removed.windowId,
+ "Expected removed tab window ID"
+ );
+ // Note: We want to test for the actual boolean value false here.
+ browser.test.assertEq(
+ false,
+ removed.isWindowClosing,
+ "Expected isWindowClosing value"
+ );
+
+ browser.test.log("Close second window");
+ await browser.windows.remove(otherWindowId);
+ [removed] = await expectEvents(["onRemoved"]);
+ browser.test.assertEq(
+ initialTab.id,
+ removed.tabId,
+ "Expected removed tab ID for windows.remove"
+ );
+ browser.test.assertEq(
+ otherWindowId,
+ removed.windowId,
+ "Expected removed tab window ID"
+ );
+ browser.test.assertEq(
+ true,
+ removed.isWindowClosing,
+ "Expected isWindowClosing value"
+ );
+
+ browser.test.log("Create additional tab in window 1");
+ tab = await browser.tabs.create({ windowId, url: "about:blank" });
+ await expectEvents(["onCreated"]);
+ browser.test.assertEq(tab.incognito, incognito, "Tab is incognito");
+
+ browser.test.log("Create a new window, adopting the new tab");
+ // We have to explicitly wait for the event here, since its timing is
+ // not predictable.
+ let promiseAttached = new Promise(resolve => {
+ browser.tabs.onAttached.addListener(function listener(tabId) {
+ browser.tabs.onAttached.removeListener(listener);
+ resolve();
+ });
+ });
+
+ let [window] = await Promise.all([
+ browser.windows.create({ tabId: tab.id, incognito }),
+ promiseAttached,
+ ]);
+
+ [detached, attached] = await expectEvents(["onDetached", "onAttached"]);
+
+ browser.test.assertEq(
+ tab.id,
+ detached.tabId,
+ "Expected onDetached tab ID"
+ );
+ browser.test.assertEq(
+ 1,
+ detached.oldPosition,
+ "Expected onDetached old index"
+ );
+ browser.test.assertEq(
+ windowId,
+ detached.oldWindowId,
+ "Expected onDetached old window ID"
+ );
+
+ browser.test.assertEq(
+ tab.id,
+ attached.tabId,
+ "Expected onAttached tab ID"
+ );
+ browser.test.assertEq(
+ 0,
+ attached.newPosition,
+ "Expected onAttached new index"
+ );
+ browser.test.assertEq(
+ window.id,
+ attached.newWindowId,
+ "Expected onAttached new window id"
+ );
+
+ browser.test.log(
+ "Close the new window by moving the tab into former window"
+ );
+ await browser.tabs.move(tab.id, { index: 1, windowId });
+ [detached, attached] = await expectEvents(["onDetached", "onAttached"]);
+
+ browser.test.assertEq(
+ tab.id,
+ detached.tabId,
+ "Expected onDetached tab ID"
+ );
+ browser.test.assertEq(
+ 0,
+ detached.oldPosition,
+ "Expected onDetached old index"
+ );
+ browser.test.assertEq(
+ window.id,
+ detached.oldWindowId,
+ "Expected onDetached old window ID"
+ );
+
+ browser.test.assertEq(
+ tab.id,
+ attached.tabId,
+ "Expected onAttached tab ID"
+ );
+ browser.test.assertEq(
+ 1,
+ attached.newPosition,
+ "Expected onAttached new index"
+ );
+ browser.test.assertEq(
+ windowId,
+ attached.newWindowId,
+ "Expected onAttached new window id"
+ );
+ browser.test.assertEq(tab.incognito, incognito, "Tab is incognito");
+
+ browser.test.log("Remove the tab");
+ await browser.tabs.remove(tab.id);
+ browser.windows.remove(windowId);
+
+ browser.test.notifyPass("tabs-events");
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("tabs-events");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["tabs"],
+ },
+ background,
+ incognitoOverride: "spanning",
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("tabs-events");
+ await extension.unload();
+});
+
+add_task(async function testTabEventsSize() {
+ function background() {
+ function sendSizeMessages(tab, type) {
+ browser.test.sendMessage(`${type}-dims`, {
+ width: tab.width,
+ height: tab.height,
+ });
+ }
+
+ browser.tabs.onCreated.addListener(tab => {
+ sendSizeMessages(tab, "on-created");
+ });
+
+ browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
+ if (changeInfo.status == "complete") {
+ sendSizeMessages(tab, "on-updated");
+ }
+ });
+
+ browser.test.onMessage.addListener(async (msg, arg) => {
+ if (msg === "create-tab") {
+ let tab = await browser.tabs.create({ url: "https://example.com/" });
+ sendSizeMessages(tab, "create");
+ browser.test.sendMessage("created-tab-id", tab.id);
+ } else if (msg === "update-tab") {
+ let tab = await browser.tabs.update(arg, {
+ url: "https://example.org/",
+ });
+ sendSizeMessages(tab, "update");
+ } else if (msg === "remove-tab") {
+ browser.tabs.remove(arg);
+ browser.test.sendMessage("tab-removed");
+ }
+ });
+
+ browser.test.sendMessage("ready");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["tabs"],
+ },
+ background,
+ });
+
+ const RESOLUTION_PREF = "layout.css.devPixelsPerPx";
+ registerCleanupFunction(() => {
+ SpecialPowers.clearUserPref(RESOLUTION_PREF);
+ });
+
+ function checkDimensions(dims, type) {
+ is(
+ dims.width,
+ gBrowser.selectedBrowser.clientWidth,
+ `tab from ${type} reports expected width`
+ );
+ is(
+ dims.height,
+ gBrowser.selectedBrowser.clientHeight,
+ `tab from ${type} reports expected height`
+ );
+ }
+
+ await Promise.all([extension.startup(), extension.awaitMessage("ready")]);
+
+ for (let resolution of [2, 1]) {
+ SpecialPowers.setCharPref(RESOLUTION_PREF, String(resolution));
+ is(
+ window.devicePixelRatio,
+ resolution,
+ "window has the required resolution"
+ );
+
+ extension.sendMessage("create-tab");
+ let tabId = await extension.awaitMessage("created-tab-id");
+
+ checkDimensions(await extension.awaitMessage("create-dims"), "create");
+ checkDimensions(
+ await extension.awaitMessage("on-created-dims"),
+ "onCreated"
+ );
+ checkDimensions(
+ await extension.awaitMessage("on-updated-dims"),
+ "onUpdated"
+ );
+
+ extension.sendMessage("update-tab", tabId);
+
+ checkDimensions(await extension.awaitMessage("update-dims"), "update");
+ checkDimensions(
+ await extension.awaitMessage("on-updated-dims"),
+ "onUpdated"
+ );
+
+ extension.sendMessage("remove-tab", tabId);
+ await extension.awaitMessage("tab-removed");
+ }
+
+ await extension.unload();
+ SpecialPowers.clearUserPref(RESOLUTION_PREF);
+}).skip(); // Bug 1614075 perma-fail comparing devicePixelRatio
+
+add_task(async function testTabRemovalEvent() {
+ async function background() {
+ let events = [];
+
+ function awaitLoad(tabId) {
+ return new Promise(resolve => {
+ browser.tabs.onUpdated.addListener(function listener(
+ tabId_,
+ changed,
+ tab
+ ) {
+ if (tabId == tabId_ && changed.status == "complete") {
+ browser.tabs.onUpdated.removeListener(listener);
+ resolve();
+ }
+ });
+ });
+ }
+
+ chrome.tabs.onRemoved.addListener((tabId, info) => {
+ browser.test.assertEq(
+ 0,
+ events.length,
+ "No events recorded before onRemoved."
+ );
+ events.push("onRemoved");
+ browser.test.log(
+ "Make sure the removed tab is not available in the tabs.query callback."
+ );
+ chrome.tabs.query({}, tabs => {
+ for (let tab of tabs) {
+ browser.test.assertTrue(
+ tab.id != tabId,
+ "Tab query should not include removed tabId"
+ );
+ }
+ });
+ });
+
+ try {
+ let url =
+ "https://example.com/browser/browser/components/extensions/test/browser/context.html";
+ let tab = await browser.tabs.create({ url: url });
+ await awaitLoad(tab.id);
+
+ chrome.tabs.onActivated.addListener(info => {
+ browser.test.assertEq(
+ 1,
+ events.length,
+ "One event recorded before onActivated."
+ );
+ events.push("onActivated");
+ browser.test.assertEq(
+ "onRemoved",
+ events[0],
+ "onRemoved fired before onActivated."
+ );
+ browser.test.notifyPass("tabs-events");
+ });
+
+ await browser.tabs.remove(tab.id);
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("tabs-events");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["tabs"],
+ },
+
+ background,
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("tabs-events");
+ await extension.unload();
+});
+
+add_task(async function testTabCreateRelated() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.tabs.opentabfor.middleclick", true],
+ ["browser.tabs.insertRelatedAfterCurrent", true],
+ ],
+ });
+
+ async function background() {
+ let created;
+ browser.tabs.onCreated.addListener(tab => {
+ browser.test.log(`tabs.onCreated, index=${tab.index}`);
+ browser.test.assertEq(1, tab.index, "expecting tab index of 1");
+ created = tab.id;
+ });
+ browser.tabs.onMoved.addListener((id, info) => {
+ browser.test.log(
+ `tabs.onMoved, from ${info.fromIndex} to ${info.toIndex}`
+ );
+ browser.test.fail("tabMoved was received");
+ });
+ browser.tabs.onRemoved.addListener((tabId, info) => {
+ browser.test.assertEq(created, tabId, "removed id same as created");
+ browser.test.sendMessage("tabRemoved");
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["tabs"],
+ },
+
+ background,
+ });
+
+ // Create a *opener* tab page which has a link to "example.com".
+ let pageURL =
+ "https://example.com/browser/browser/components/extensions/test/browser/file_dummy.html";
+ let openerTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ pageURL
+ );
+ gBrowser.moveTabTo(openerTab, 0);
+
+ await extension.startup();
+
+ let newTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "https://example.com/#linkclick",
+ true
+ );
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#link_to_example_com",
+ { button: 1 },
+ gBrowser.selectedBrowser
+ );
+ let openTab = await newTabPromise;
+ is(
+ openTab.linkedBrowser.currentURI.spec,
+ "https://example.com/#linkclick",
+ "Middle click should open site to correct url."
+ );
+ BrowserTestUtils.removeTab(openTab);
+
+ await extension.awaitMessage("tabRemoved");
+ await extension.unload();
+
+ BrowserTestUtils.removeTab(openerTab);
+});
+
+add_task(async function testLastTabRemoval() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.tabs.closeWindowWithLastTab", false]],
+ });
+
+ async function background() {
+ let windowId;
+ browser.tabs.onCreated.addListener(tab => {
+ browser.test.assertEq(
+ windowId,
+ tab.windowId,
+ "expecting onCreated after onRemoved on the same window"
+ );
+ browser.test.sendMessage("tabCreated", `${tab.width}x${tab.height}`);
+ });
+ browser.tabs.onRemoved.addListener((tabId, info) => {
+ windowId = info.windowId;
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["tabs"],
+ },
+ background,
+ });
+
+ let newWin = await BrowserTestUtils.openNewBrowserWindow();
+ await extension.startup();
+
+ const oldBrowser = newWin.gBrowser.selectedBrowser;
+ const expectedDims = `${oldBrowser.clientWidth}x${oldBrowser.clientHeight}`;
+ BrowserTestUtils.removeTab(newWin.gBrowser.selectedTab);
+
+ const actualDims = await extension.awaitMessage("tabCreated");
+ is(
+ actualDims,
+ expectedDims,
+ "created tab reports a size same to the removed last tab"
+ );
+
+ await extension.unload();
+ await BrowserTestUtils.closeWindow(newWin);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function testTabActivationEvent() {
+ async function background() {
+ function makeExpectable() {
+ let expectation = null,
+ resolver = null;
+ const expectable = param => {
+ if (expectation === null) {
+ browser.test.fail("unexpected call to expectable");
+ } else {
+ try {
+ resolver(expectation(param));
+ } catch (e) {
+ resolver(Promise.reject(e));
+ } finally {
+ expectation = null;
+ }
+ }
+ };
+ expectable.expect = e => {
+ expectation = e;
+ return new Promise(r => {
+ resolver = r;
+ });
+ };
+ return expectable;
+ }
+ try {
+ const listener = makeExpectable();
+ browser.tabs.onActivated.addListener(listener);
+
+ const [
+ ,
+ {
+ tabs: [tab1],
+ },
+ ] = await Promise.all([
+ listener.expect(info => {
+ browser.test.assertEq(
+ undefined,
+ info.previousTabId,
+ "previousTabId should not be defined when window is first opened"
+ );
+ }),
+ browser.windows.create({ url: "about:blank" }),
+ ]);
+ const [, tab2] = await Promise.all([
+ listener.expect(info => {
+ browser.test.assertEq(
+ tab1.id,
+ info.previousTabId,
+ "Got expected previousTabId"
+ );
+ }),
+ browser.tabs.create({ url: "about:blank" }),
+ ]);
+
+ await Promise.all([
+ listener.expect(info => {
+ browser.test.assertEq(tab1.id, info.tabId, "Got expected tabId");
+ browser.test.assertEq(
+ tab2.id,
+ info.previousTabId,
+ "Got expected previousTabId"
+ );
+ }),
+ browser.tabs.update(tab1.id, { active: true }),
+ ]);
+
+ await Promise.all([
+ listener.expect(info => {
+ browser.test.assertEq(tab2.id, info.tabId, "Got expected tabId");
+ browser.test.assertEq(
+ undefined,
+ info.previousTabId,
+ "previousTabId should not be defined when previous tab was closed"
+ );
+ }),
+ browser.tabs.remove(tab1.id),
+ ]);
+
+ await browser.tabs.remove(tab2.id);
+
+ browser.test.notifyPass("tabs-events");
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("tabs-events");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["tabs"],
+ },
+ background,
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("tabs-events");
+ await extension.unload();
+});
+
+add_task(async function test_tabs_event_page() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.eventPages.enabled", true]],
+ });
+
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ browser_specific_settings: { gecko: { id: "eventpage@tabs" } },
+ permissions: ["tabs"],
+ background: { persistent: false },
+ },
+ background() {
+ const EVENTS = [
+ "onActivated",
+ "onAttached",
+ "onDetached",
+ "onRemoved",
+ "onMoved",
+ "onHighlighted",
+ "onUpdated",
+ ];
+ browser.tabs.onCreated.addListener(() => {
+ browser.test.sendMessage("onCreated");
+ });
+ for (let event of EVENTS) {
+ browser.tabs[event].addListener(() => {});
+ }
+ browser.test.sendMessage("ready");
+ },
+ });
+
+ const EVENTS = [
+ "onActivated",
+ "onAttached",
+ "onCreated",
+ "onDetached",
+ "onRemoved",
+ "onMoved",
+ "onHighlighted",
+ "onUpdated",
+ ];
+
+ await extension.startup();
+ await extension.awaitMessage("ready");
+ for (let event of EVENTS) {
+ assertPersistentListeners(extension, "tabs", event, {
+ primed: false,
+ });
+ }
+
+ // test events waken background
+ await extension.terminateBackground();
+ for (let event of EVENTS) {
+ assertPersistentListeners(extension, "tabs", event, {
+ primed: true,
+ });
+ }
+
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+
+ await extension.awaitMessage("ready");
+ await extension.awaitMessage("onCreated");
+ ok(true, "persistent event woke background");
+ for (let event of EVENTS) {
+ assertPersistentListeners(extension, "tabs", event, {
+ primed: false,
+ });
+ }
+ await BrowserTestUtils.closeWindow(win);
+
+ await extension.unload();
+ await SpecialPowers.popPrefEnv();
+});