summaryrefslogtreecommitdiffstats
path: root/browser/components/extensions/test/browser/browser_ext_tabs_successors.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/extensions/test/browser/browser_ext_tabs_successors.js')
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_successors.js396
1 files changed, 396 insertions, 0 deletions
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_successors.js b/browser/components/extensions/test/browser/browser_ext_tabs_successors.js
new file mode 100644
index 0000000000..77549c44d5
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_successors.js
@@ -0,0 +1,396 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+async function background(tabCount, testFn) {
+ try {
+ const { TAB_ID_NONE } = browser.tabs;
+ const tabIds = await Promise.all(
+ Array.from({ length: tabCount }, () =>
+ browser.tabs.create({ url: "about:blank" }).then(t => t.id)
+ )
+ );
+
+ const toTabIds = i => tabIds[i];
+
+ const setSuccessors = mapping =>
+ Promise.all(
+ mapping.map((succ, i) =>
+ browser.tabs.update(tabIds[i], { successorTabId: tabIds[succ] })
+ )
+ );
+
+ const verifySuccessors = async function (mapping, name) {
+ const promises = [],
+ expected = [];
+ for (let i = 0; i < mapping.length; i++) {
+ if (mapping[i] !== undefined) {
+ promises.push(
+ browser.tabs.get(tabIds[i]).then(t => t.successorTabId)
+ );
+ expected.push(
+ mapping[i] === TAB_ID_NONE ? TAB_ID_NONE : tabIds[mapping[i]]
+ );
+ }
+ }
+ const results = await Promise.all(promises);
+ for (let i = 0; i < results.length; i++) {
+ browser.test.assertEq(
+ expected[i],
+ results[i],
+ `${name}: successorTabId of tab ${i} in mapping should be ${expected[i]}`
+ );
+ }
+ };
+
+ await testFn({
+ TAB_ID_NONE,
+ tabIds,
+ toTabIds,
+ setSuccessors,
+ verifySuccessors,
+ });
+
+ browser.test.notifyPass("background-script");
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("background-script");
+ }
+}
+
+async function runTabTest(tabCount, testFn) {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["tabs"],
+ },
+ background: `(${background})(${tabCount}, ${testFn});`,
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("background-script");
+ await extension.unload();
+}
+
+add_task(function testTabSuccessors() {
+ return runTabTest(3, async function ({ TAB_ID_NONE, tabIds }) {
+ const anotherWindow = await browser.windows.create({ url: "about:blank" });
+
+ browser.test.assertEq(
+ TAB_ID_NONE,
+ (await browser.tabs.get(tabIds[0])).successorTabId,
+ "Tabs default to an undefined successor"
+ );
+
+ // Basic getting and setting
+
+ await browser.tabs.update(tabIds[0], { successorTabId: tabIds[1] });
+ browser.test.assertEq(
+ tabIds[1],
+ (await browser.tabs.get(tabIds[0])).successorTabId,
+ "tabs.update assigned the correct successor"
+ );
+
+ await browser.tabs.update(tabIds[0], {
+ successorTabId: browser.tabs.TAB_ID_NONE,
+ });
+ browser.test.assertEq(
+ TAB_ID_NONE,
+ (await browser.tabs.get(tabIds[0])).successorTabId,
+ "tabs.update cleared successor"
+ );
+
+ await browser.tabs.update(tabIds[0], { successorTabId: tabIds[1] });
+ await browser.tabs.update(tabIds[0], { successorTabId: tabIds[0] });
+ browser.test.assertEq(
+ TAB_ID_NONE,
+ (await browser.tabs.get(tabIds[0])).successorTabId,
+ "Setting a tab as its own successor clears the successor instead"
+ );
+
+ // Validation tests
+
+ await browser.test.assertRejects(
+ browser.tabs.update(tabIds[0], { successorTabId: 1e8 }),
+ /Invalid successorTabId/,
+ "tabs.update should throw with an invalid successor tab ID"
+ );
+
+ await browser.test.assertRejects(
+ browser.tabs.update(tabIds[0], {
+ successorTabId: anotherWindow.tabs[0].id,
+ }),
+ /Successor tab must be in the same window as the tab being updated/,
+ "tabs.update should throw with a successor tab ID from another window"
+ );
+
+ // Make sure the successor is truly being assigned
+
+ await browser.tabs.update(tabIds[0], {
+ successorTabId: tabIds[2],
+ active: true,
+ });
+ await browser.tabs.remove(tabIds[0]);
+ browser.test.assertEq(
+ tabIds[2],
+ (await browser.tabs.query({ active: true }))[0].id
+ );
+
+ return browser.tabs.remove([
+ tabIds[1],
+ tabIds[2],
+ anotherWindow.tabs[0].id,
+ ]);
+ });
+});
+
+add_task(function testMoveInSuccession_appendFalse() {
+ return runTabTest(
+ 8,
+ async function ({
+ TAB_ID_NONE,
+ tabIds,
+ toTabIds,
+ setSuccessors,
+ verifySuccessors,
+ }) {
+ await browser.tabs.moveInSuccession([1, 0].map(toTabIds), tabIds[0]);
+ await verifySuccessors([TAB_ID_NONE, 0], "scenario 1");
+
+ await browser.tabs.moveInSuccession(
+ [0, 1, 2, 3].map(toTabIds),
+ tabIds[0]
+ );
+ await verifySuccessors([1, 2, 3, 0], "scenario 2");
+
+ await browser.tabs.moveInSuccession([1, 0].map(toTabIds), tabIds[0]);
+ await verifySuccessors(
+ [TAB_ID_NONE, 0],
+ "scenario 1 after tab 0 has a successor"
+ );
+
+ await browser.tabs.update(tabIds[7], { successorTabId: tabIds[0] });
+ await browser.tabs.moveInSuccession([4, 5, 6, 7].map(toTabIds));
+ await verifySuccessors(
+ new Array(4).concat([5, 6, 7, TAB_ID_NONE]),
+ "scenario 4"
+ );
+
+ await setSuccessors([7, 2, 3, 4, 3, 6, 7, 5]);
+ await browser.tabs.moveInSuccession(
+ [4, 6, 3, 2].map(toTabIds),
+ tabIds[7]
+ );
+ await verifySuccessors([7, TAB_ID_NONE, 7, 2, 6, 7, 3, 5], "scenario 5");
+
+ await setSuccessors([7, 2, 3, 4, 3, 6, 7, 5]);
+ await browser.tabs.moveInSuccession(
+ [4, 6, 3, 2].map(toTabIds),
+ tabIds[7],
+ {
+ insert: true,
+ }
+ );
+ await verifySuccessors(
+ [4, TAB_ID_NONE, 7, 2, 6, 4, 3, 5],
+ "insert = true"
+ );
+
+ await setSuccessors([1, 2, 3, 4, 0]);
+ await browser.tabs.moveInSuccession([3, 1, 2].map(toTabIds), tabIds[0], {
+ insert: true,
+ });
+ await verifySuccessors([4, 2, 0, 1, 3], "insert = true, part 2");
+
+ await browser.tabs.moveInSuccession([
+ tabIds[0],
+ tabIds[1],
+ 1e8,
+ tabIds[2],
+ ]);
+ await verifySuccessors([1, 2, TAB_ID_NONE], "unknown tab ID");
+
+ browser.test.assertTrue(
+ await browser.tabs.moveInSuccession([1e8]).then(
+ () => true,
+ () => false
+ ),
+ "When all tab IDs are unknown, tabs.moveInSuccession should not throw"
+ );
+
+ // Validation tests
+
+ await browser.test.assertRejects(
+ browser.tabs.moveInSuccession([tabIds[0], tabIds[1], tabIds[0]]),
+ /IDs must not occur more than once in tabIds/,
+ "tabs.moveInSuccession should throw when a tab is referenced more than once in tabIds"
+ );
+
+ await browser.test.assertRejects(
+ browser.tabs.moveInSuccession([tabIds[0], tabIds[1]], tabIds[0], {
+ insert: true,
+ }),
+ /Value of tabId must not occur in tabIds if append or insert is true/,
+ "tabs.moveInSuccession should throw when tabId occurs in tabIds and insert is true"
+ );
+
+ return browser.tabs.remove(tabIds);
+ }
+ );
+});
+
+add_task(function testMoveInSuccession_appendTrue() {
+ return runTabTest(
+ 8,
+ async function ({
+ TAB_ID_NONE,
+ tabIds,
+ toTabIds,
+ setSuccessors,
+ verifySuccessors,
+ }) {
+ await browser.tabs.moveInSuccession([1].map(toTabIds), tabIds[0], {
+ append: true,
+ });
+ await verifySuccessors([1, TAB_ID_NONE], "scenario 1");
+
+ await browser.tabs.update(tabIds[3], { successorTabId: tabIds[4] });
+ await browser.tabs.moveInSuccession([1, 2, 3].map(toTabIds), tabIds[0], {
+ append: true,
+ });
+ await verifySuccessors([1, 2, 3, TAB_ID_NONE], "scenario 2");
+
+ await browser.tabs.update(tabIds[0], { successorTabId: tabIds[1] });
+ await browser.tabs.moveInSuccession([1e8], tabIds[0], { append: true });
+ browser.test.assertEq(
+ TAB_ID_NONE,
+ (await browser.tabs.get(tabIds[0])).successorTabId,
+ "If no tabs get appended after the reference tab, it should lose its successor"
+ );
+
+ await setSuccessors([7, 2, 3, 4, 3, 6, 7, 5]);
+ await browser.tabs.moveInSuccession(
+ [4, 6, 3, 2].map(toTabIds),
+ tabIds[7],
+ {
+ append: true,
+ }
+ );
+ await verifySuccessors(
+ [7, TAB_ID_NONE, TAB_ID_NONE, 2, 6, 7, 3, 4],
+ "scenario 3"
+ );
+
+ await setSuccessors([7, 2, 3, 4, 3, 6, 7, 5]);
+ await browser.tabs.moveInSuccession(
+ [4, 6, 3, 2].map(toTabIds),
+ tabIds[7],
+ {
+ append: true,
+ insert: true,
+ }
+ );
+ await verifySuccessors(
+ [7, TAB_ID_NONE, 5, 2, 6, 7, 3, 4],
+ "insert = true"
+ );
+
+ await browser.tabs.moveInSuccession([0, 4].map(toTabIds), tabIds[7], {
+ append: true,
+ insert: true,
+ });
+ await verifySuccessors(
+ [4, undefined, undefined, undefined, 6, undefined, undefined, 0],
+ "insert = true, part 2"
+ );
+
+ await setSuccessors([1, 2, 3, 4, 0]);
+ await browser.tabs.moveInSuccession([3, 1, 2].map(toTabIds), tabIds[0], {
+ append: true,
+ insert: true,
+ });
+ await verifySuccessors([3, 2, 4, 1, 0], "insert = true, part 3");
+
+ await browser.tabs.update(tabIds[0], { successorTabId: tabIds[1] });
+ await browser.tabs.moveInSuccession([1e8], tabIds[0], {
+ append: true,
+ insert: true,
+ });
+ browser.test.assertEq(
+ tabIds[1],
+ (await browser.tabs.get(tabIds[0])).successorTabId,
+ "If no tabs get inserted after the reference tab, it should keep its successor"
+ );
+
+ // Validation tests
+
+ await browser.test.assertRejects(
+ browser.tabs.moveInSuccession([tabIds[0], tabIds[1]], tabIds[0], {
+ append: true,
+ }),
+ /Value of tabId must not occur in tabIds if append or insert is true/,
+ "tabs.moveInSuccession should throw when tabId occurs in tabIds and insert is true"
+ );
+
+ return browser.tabs.remove(tabIds);
+ }
+ );
+});
+
+add_task(function testMoveInSuccession_ignoreTabsInOtherWindows() {
+ return runTabTest(
+ 2,
+ async function ({
+ TAB_ID_NONE,
+ tabIds,
+ toTabIds,
+ setSuccessors,
+ verifySuccessors,
+ }) {
+ const anotherWindow = await browser.windows.create({
+ url: Array.from({ length: 3 }, () => "about:blank"),
+ });
+ tabIds.push(...anotherWindow.tabs.map(t => t.id));
+
+ await setSuccessors([1, 0, 3, 4, 2]);
+ await browser.tabs.moveInSuccession([1, 3, 2].map(toTabIds), tabIds[4]);
+ await verifySuccessors(
+ [1, 0, 4, 2, TAB_ID_NONE],
+ "first tab in another window"
+ );
+
+ await setSuccessors([1, 0, 3, 4, 2]);
+ await browser.tabs.moveInSuccession([3, 1, 2].map(toTabIds), tabIds[4]);
+ await verifySuccessors(
+ [1, 0, 4, 2, TAB_ID_NONE],
+ "middle tab in another window"
+ );
+
+ await setSuccessors([1, 0, 3, 4, 2]);
+ await browser.tabs.moveInSuccession([3, 1, 2].map(toTabIds));
+ await verifySuccessors(
+ [1, 0, TAB_ID_NONE, 2, TAB_ID_NONE],
+ "using the first tab to determine the window"
+ );
+
+ await setSuccessors([1, 0, 3, 4, 2]);
+ await browser.tabs.moveInSuccession([1, 3, 2].map(toTabIds), tabIds[4], {
+ append: true,
+ });
+ await verifySuccessors(
+ [1, 0, TAB_ID_NONE, 2, 3],
+ "first tab in another window, appending"
+ );
+
+ await setSuccessors([1, 0, 3, 4, 2]);
+ await browser.tabs.moveInSuccession([3, 1, 2].map(toTabIds), tabIds[4], {
+ append: true,
+ });
+ await verifySuccessors(
+ [1, 0, TAB_ID_NONE, 2, 3],
+ "middle tab in another window, appending"
+ );
+
+ return browser.tabs.remove(tabIds);
+ }
+ );
+});