Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* -*- 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);
}
);
});