Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
requestLongerTimeout(2);
// Load subscript shared with all spaces tests.
Services.scriptloader.loadSubScript(
new URL("head_spaces.js", gTestPath).href,
this
);
add_setup(async () => {
// Reduce animations to prevent intermittent fails due to late theme changes.
Services.prefs.setIntPref("ui.prefersReducedMotion", 1);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("ui.prefersReducedMotion");
});
});
add_task(async function test_add_update_remove() {
async function background() {
const manifest = browser.runtime.getManifest();
const propertyClearValue = manifest.manifest_version == 2 ? "" : null;
const extensionIcon = manifest.icons
? browser.runtime.getURL(manifest.icons[16])
await window.sendMessage("checkUI", []);
// Test create().
browser.test.log("create(): Without id.");
await browser.test.assertThrows(
() => browser.spaces.create(),
/Incorrect argument types for spaces.create./,
"create() without name should throw."
);
browser.test.log("create(): Without default url.");
await browser.test.assertThrows(
() => browser.spaces.create("space_1"),
/Incorrect argument types for spaces.create./,
"create() without default url should throw."
);
browser.test.log("create(): With invalid default url.");
await browser.test.assertRejects(
browser.spaces.create("space_1", "invalid://url"),
`Failed to create space with name space_1: Invalid URL: invalid://url`,
"create() with an invalid default url should throw."
);
browser.test.log("create(): With default url only.");
const space_1 = await browser.spaces.create(
"space_1",
);
const expected_space_1 = {
name: "space_1",
title: "Generated extension",
icons: {
default: `url("${extensionIcon}")`,
},
};
await window.sendMessage("checkUI", [expected_space_1]);
browser.test.log("create(): With default url only, but existing id.");
await browser.test.assertRejects(
browser.spaces.create("space_1", "https://test.invalid"),
/Failed to create space with name space_1: Space already exists for this extension./,
"create() with existing id should throw."
);
browser.test.log("create(): With most properties.");
const space_2 = await browser.spaces.create("space_2", "/local/file.html", {
title: "Google",
defaultIcons: "default.png",
badgeText: "12",
badgeBackgroundColor: [50, 100, 150, 255],
});
const expected_space_2 = {
name: "space_2",
title: "Google",
url: browser.runtime.getURL("/local/file.html"),
icons: {
default: `url("${browser.runtime.getURL("default.png")}")`,
},
badgeText: "12",
badgeBackgroundColor: "rgb(50, 100, 150)",
};
await window.sendMessage("checkUI", [expected_space_1, expected_space_2]);
// Test update().
browser.test.log("update(): Without id.");
await browser.test.assertThrows(
() => browser.spaces.update(),
/Incorrect argument types for spaces.update./,
"update() without id should throw."
);
browser.test.log("update(): With invalid id.");
await browser.test.assertRejects(
browser.spaces.update(1234, "invalid://url"),
/Failed to update space with id 1234: Unknown id./,
"update() with invalid id should throw."
);
browser.test.log("update(): Without properties.");
await browser.test.assertThrows(
() => browser.spaces.update(space_1.id),
/Incorrect argument types for spaces.update./,
"update() without properties should throw."
);
browser.test.log("update(): Updating the badge.");
await browser.spaces.update(space_2.id, {
badgeText: "ok",
badgeBackgroundColor: "green",
});
expected_space_2.badgeText = "ok";
expected_space_2.badgeBackgroundColor = "rgb(0, 128, 0)";
await window.sendMessage("checkUI", [expected_space_1, expected_space_2]);
browser.test.log("update(): Removing the badge.");
await browser.spaces.update(space_2.id, {
badgeText: propertyClearValue,
});
delete expected_space_2.badgeText;
delete expected_space_2.badgeBackgroundColor;
await window.sendMessage("checkUI", [expected_space_1, expected_space_2]);
browser.test.log("update(): Changing the title.");
await browser.spaces.update(space_2.id, {
title: "Some other title",
});
expected_space_2.title = "Some other title";
await window.sendMessage("checkUI", [expected_space_1, expected_space_2]);
browser.test.log("update(): Removing the title.");
await browser.spaces.update(space_2.id, {
title: propertyClearValue,
});
expected_space_2.title = "Generated extension";
await window.sendMessage("checkUI", [expected_space_1, expected_space_2]);
browser.test.log("update(): Setting invalid default url.");
await browser.test.assertRejects(
browser.spaces.update(space_2.id, "invalid://url"),
`Failed to update space with id ${space_2.id}: Invalid URL: invalid://url`,
"update() with invalid default url should throw."
);
await browser.spaces.update(space_2.id, {
});
expected_space_2.url = "https://test.other.invalid";
await window.sendMessage("checkUI", [expected_space_1, expected_space_2]);
await browser.spaces.update(space_2.id, "https://test.more.invalid", {
title: "Bing",
});
expected_space_2.title = "Bing";
expected_space_2.url = "https://test.more.invalid";
await window.sendMessage("checkUI", [expected_space_1, expected_space_2]);
// Test remove().
browser.test.log("remove(): Removing without id.");
await browser.test.assertThrows(
() => browser.spaces.remove(),
/Incorrect argument types for spaces.remove./,
"remove() without id should throw."
);
browser.test.log("remove(): Removing with invalid id.");
await browser.test.assertRejects(
browser.spaces.remove(1234),
/Failed to remove space with id 1234: Unknown id./,
"remove() with invalid id should throw."
);
browser.test.log("remove(): Removing space_1.");
await browser.spaces.remove(space_1.id);
await window.sendMessage("checkUI", [expected_space_2]);
browser.test.notifyPass();
}
// Manifest V2 and V3 have a different schema for the SpaceButtonProperties,
// test them both.
for (const manifestVersion of [2, 3]) {
await test_space(background, {
manifestVersion,
selectedTheme: "default",
});
await test_space(background, {
manifestVersion,
selectedTheme: "default",
manifestIcons: { 16: "manifest.png" },
});
}
});
add_task(async function test_open_reload_close() {
async function background() {
await window.sendMessage("checkTabs", { openSpacesUrls: [] });
// Add spaces.
const space_1 = await browser.spaces.create("space_1", url1);
const space_2 = await browser.spaces.create("space_2", url2);
// Open spaces.
await window.sendMessage("checkTabs", {
action: "open",
url: url1,
spaceName: "space_1",
openSpacesUrls: [url1],
});
await window.sendMessage("checkTabs", {
action: "open",
url: url2,
spaceName: "space_2",
openSpacesUrls: [url1, url2],
});
// Switch to open spaces tab.
await window.sendMessage("checkTabs", {
action: "switch",
url: url1,
spaceName: "space_1",
openSpacesUrls: [url1, url2],
});
await window.sendMessage("checkTabs", {
action: "switch",
url: url2,
spaceName: "space_2",
openSpacesUrls: [url1, url2],
});
// TODO: Add test for tab reloading, once this has been implemented.
// Remove spaces and check that related spaces tab are closed.
await browser.spaces.remove(space_1.id);
await window.sendMessage("checkTabs", { openSpacesUrls: [url2] });
await browser.spaces.remove(space_2.id);
await window.sendMessage("checkTabs", { openSpacesUrls: [] });
browser.test.notifyPass();
}
await test_space(background, { selectedTheme: "default" });
});
add_task(async function test_icons() {
async function background() {
const manifest = browser.runtime.getManifest();
const propertyClearValue = manifest.manifest_version == 2 ? "" : null;
const extensionIcon = manifest.icons
? browser.runtime.getURL(manifest.icons[16])
// Test 1: Setting defaultIcons and themeIcons.
browser.test.log("create(): Setting defaultIcons and themeIcons.");
const space_1 = await browser.spaces.create(
"space_1",
{
},
{
title: "Google",
defaultIcons: "default.png",
themeIcons: [
{
dark: "dark.png",
light: "light.png",
size: 16,
},
],
}
);
const expected_space_1 = {
name: "space_1",
title: "Google",
icons: {
default: `url("${browser.runtime.getURL("default.png")}")`,
dark: `url("${browser.runtime.getURL("dark.png")}")`,
light: `url("${browser.runtime.getURL("light.png")}")`,
},
};
await window.sendMessage("checkUI", [expected_space_1]);
// Clearing defaultIcons.
await browser.spaces.update(space_1.id, {
defaultIcons: propertyClearValue,
});
expected_space_1.icons = {
default: null,
dark: `url("${browser.runtime.getURL("dark.png")}")`,
light: `url("${browser.runtime.getURL("light.png")}")`,
};
await window.sendMessage("checkUI", [expected_space_1]);
// Setting other defaultIcons.
await browser.spaces.update(space_1.id, {
defaultIcons: "other.png",
});
expected_space_1.icons = {
default: `url("${browser.runtime.getURL("other.png")}")`,
dark: `url("${browser.runtime.getURL("dark.png")}")`,
light: `url("${browser.runtime.getURL("light.png")}")`,
};
await window.sendMessage("checkUI", [expected_space_1]);
// Clearing themeIcons.
await browser.spaces.update(space_1.id, {
themeIcons: [],
});
expected_space_1.icons = {
default: `url("${browser.runtime.getURL("other.png")}")`,
dark: `url("${browser.runtime.getURL("other.png")}")`,
light: `url("${browser.runtime.getURL("other.png")}")`,
};
await window.sendMessage("checkUI", [expected_space_1]);
// Setting other themeIcons.
await browser.spaces.update(space_1.id, {
themeIcons: [
{
dark: "dark2.png",
light: "light2.png",
size: 16,
},
],
});
expected_space_1.icons = {
default: `url("${browser.runtime.getURL("other.png")}")`,
dark: `url("${browser.runtime.getURL("dark2.png")}")`,
light: `url("${browser.runtime.getURL("light2.png")}")`,
};
await window.sendMessage("checkUI", [expected_space_1]);
// Test 2: Setting themeIcons only.
browser.test.log("create(): Setting themeIcons only.");
const space_2 = await browser.spaces.create(
"space_2",
{
title: "Wikipedia",
themeIcons: [
{
dark: "dark2.png",
light: "light2.png",
size: 16,
},
],
}
);
// Not specifying defaultIcons but only themeIcons should always use the
// theme icons, even for the default theme (and not the extension icon).
const expected_space_2 = {
name: "space_2",
title: "Wikipedia",
icons: {
default: null,
dark: `url("${browser.runtime.getURL("dark2.png")}")`,
light: `url("${browser.runtime.getURL("light2.png")}")`,
},
};
await window.sendMessage("checkUI", [expected_space_1, expected_space_2]);
// Clearing themeIcons.
await browser.spaces.update(space_2.id, {
themeIcons: [],
});
expected_space_2.icons = {
default: `url("${extensionIcon}")`,
dark: `url("${extensionIcon}")`,
light: `url("${extensionIcon}")`,
};
await window.sendMessage("checkUI", [expected_space_1, expected_space_2]);
// Test 3: Setting defaultIcons only.
browser.test.log("create(): Setting defaultIcons only.");
const space_3 = await browser.spaces.create(
"space_3",
{
title: "Bing",
defaultIcons: "default.png",
}
);
const expected_space_3 = {
name: "space_3",
title: "Bing",
icons: {
default: `url("${browser.runtime.getURL("default.png")}")`,
dark: `url("${browser.runtime.getURL("default.png")}")`,
light: `url("${browser.runtime.getURL("default.png")}")`,
},
};
await window.sendMessage("checkUI", [
expected_space_1,
expected_space_2,
expected_space_3,
]);
// Clearing defaultIcons and setting themeIcons.
await browser.spaces.update(space_3.id, {
defaultIcons: propertyClearValue,
themeIcons: [
{
dark: "dark3.png",
light: "light3.png",
size: 16,
},
],
});
expected_space_3.icons = {
default: null,
dark: `url("${browser.runtime.getURL("dark3.png")}")`,
light: `url("${browser.runtime.getURL("light3.png")}")`,
};
await window.sendMessage("checkUI", [
expected_space_1,
expected_space_2,
expected_space_3,
]);
// Test 4: Setting no icons.
browser.test.log("create(): Setting no icons.");
const space_4 = await browser.spaces.create(
"space_4",
{
},
{
title: "DuckDuckGo",
}
);
const expected_space_4 = {
name: "space_4",
title: "DuckDuckGo",
icons: {
default: `url("${extensionIcon}")`,
dark: `url("${extensionIcon}")`,
light: `url("${extensionIcon}")`,
},
};
await window.sendMessage("checkUI", [
expected_space_1,
expected_space_2,
expected_space_3,
expected_space_4,
]);
// Setting and clearing default icons.
await browser.spaces.update(space_4.id, {
defaultIcons: "default.png",
});
expected_space_4.icons = {
default: `url("${browser.runtime.getURL("default.png")}")`,
dark: `url("${browser.runtime.getURL("default.png")}")`,
light: `url("${browser.runtime.getURL("default.png")}")`,
};
await window.sendMessage("checkUI", [
expected_space_1,
expected_space_2,
expected_space_3,
expected_space_4,
]);
await browser.spaces.update(space_4.id, {
defaultIcons: propertyClearValue,
});
expected_space_4.icons = {
default: `url("${extensionIcon}")`,
dark: `url("${extensionIcon}")`,
light: `url("${extensionIcon}")`,
};
await window.sendMessage("checkUI", [
expected_space_1,
expected_space_2,
expected_space_3,
expected_space_4,
]);
browser.test.notifyPass();
}
// Manifest V2 and V3 have a different schema for the SpaceButtonProperties,
// test them both.
for (const manifestVersion of [2, 3]) {
// Test with and without icons defined in the manifest.
for (const manifestIcons of [null, { 16: "manifest16.png" }]) {
const dark_theme = await AddonManager.getAddonByID(
"thunderbird-compact-dark@mozilla.org"
);
await dark_theme.enable();
await test_space(background, {
selectedTheme: "light",
manifestIcons,
manifestVersion,
});
const light_theme = await AddonManager.getAddonByID(
"thunderbird-compact-light@mozilla.org"
);
await light_theme.enable();
await test_space(background, {
selectedTheme: "dark",
manifestIcons,
manifestVersion,
});
// Disabling a theme will enable the default theme.
await light_theme.disable();
await test_space(background, {
selectedTheme: "default",
manifestIcons,
manifestVersion,
});
}
}
});
add_task(async function test_open_programmatically_with_cookieStoreId() {
async function background() {
await window.sendMessage("checkTabs", { openSpacesUrls: [] });
// Add spaces.
const space_1 = await browser.spaces.create("space_1", { url: url1 });
const space_2 = await browser.spaces.create("space_2", {
url: url2,
cookieStoreId: "firefox-container-1",
});
await window.sendMessage("checkTabs", { openSpacesUrls: [] });
async function openSpace(space, url) {
const loadPromise = new Promise(resolve => {
let urlSeen = false;
const listener = (tabId, changeInfo) => {
if (changeInfo.url && changeInfo.url == url) {
urlSeen = true;
}
if (changeInfo.status == "complete" && urlSeen) {
browser.tabs.onUpdated.removeListener(listener);
resolve();
}
};
browser.tabs.onUpdated.addListener(listener);
});
const tab = await browser.spaces.open(space.id);
await loadPromise;
browser.test.assertEq(
space.id,
tab.spaceId,
"The opened tab should belong to the correct space"
);
const queriedTabs = await browser.tabs.query({ spaceId: space.id });
browser.test.assertEq(
1,
queriedTabs.length,
"browser.tabs.query() should find exactly one tab belonging to the opened space"
);
browser.test.assertEq(
tab.id,
queriedTabs[0].id,
"browser.tabs.query() should find the correct tab belonging to the opened space"
);
}
// Open space #1.
await openSpace(space_1, url1);
// Verify cookieStoreIds.
const [spaceTab1] = await browser.tabs.query({ spaceId: space_1.id });
browser.test.assertEq(
"firefox-default",
spaceTab1.cookieStoreId,
`The cookieStoreId for space_1 should be correct.`
);
// Verify tab properties.
await window.sendMessage("checkTabs", {
spaceName: "space_1",
openSpacesUrls: [url1],
});
// Open space #2.
await openSpace(space_2, url2);
// Verify cookieStoreIds.
const [spaceTab2] = await browser.tabs.query({ spaceId: space_2.id });
browser.test.assertEq(
"firefox-container-1",
spaceTab2.cookieStoreId,
`The cookieStoreId for space_2 should be correct.`
);
// Verify tab properties.
await window.sendMessage("checkTabs", {
spaceName: "space_2",
openSpacesUrls: [url1, url2],
});
// Switch to open space tab.
await window.sendMessage("checkTabs", {
action: "switch",
url: url1,
spaceName: "space_1",
openSpacesUrls: [url1, url2],
});
await window.sendMessage("checkTabs", {
action: "switch",
url: url2,
spaceName: "space_2",
openSpacesUrls: [url1, url2],
});
// Remove spaces and check that related spaces tab are closed.
await browser.spaces.remove(space_1.id);
await window.sendMessage("checkTabs", { openSpacesUrls: [url2] });
await browser.spaces.remove(space_2.id);
await window.sendMessage("checkTabs", { openSpacesUrls: [] });
browser.test.notifyPass();
}
await test_space(background, {
selectedTheme: "default",
permissions: ["tabs", "cookies", "contextualIdentities"],
useAddonManager: "temporary",
});
});
// Test built-in spaces to make sure the space definition of the spaceTracker in
// ext-mails.js is matching the actual space definition in spacesToolbar.js
add_task(async function test_builtIn_spaces() {
const extension = ExtensionTestUtils.loadExtension({
files: {
"background.js": async () => {
const checkSpace = async (spaceId, spaceName) => {
const spaces = await browser.spaces.query({ id: spaceId });
browser.test.assertEq(spaces.length, 1, "Should find a single space");
browser.test.assertEq(
spaces[0].isBuiltIn,
true,
"Should find a built-in space"
);
browser.test.assertEq(
spaces[0].name,
spaceName,
"Should find the correct space"
);
};
// Test the already open mail space.
const mailTabs = await browser.tabs.query({ type: "mail" });
browser.test.assertEq(
mailTabs.length,
1,
"Should find a single mail tab"
);
await checkSpace(mailTabs[0].spaceId, "mail");
// Test all other spaces.
const builtInSpaces = [
"addressbook",
"calendar",
"tasks",
"chat",
"settings",
];
for (const spaceName of builtInSpaces) {
await new Promise(resolve => {
const listener = async tab => {
await checkSpace(tab.spaceId, spaceName);
browser.tabs.remove(tab.id);
browser.tabs.onCreated.removeListener(listener);
resolve();
};
browser.tabs.onCreated.addListener(listener);
browser.test.sendMessage("openSpace", spaceName);
});
}
browser.test.notifyPass();
},
"utils.js": await getUtilsJS(),
},
manifest: {
manifest_version: 2,
browser_specific_settings: {
gecko: {
id: "built-in-spaces@mochi.test",
},
},
background: { scripts: ["utils.js", "background.js"] },
},
});
extension.onMessage("openSpace", async spaceName => {
window.gSpacesToolbar.openSpace(
window.document.getElementById("tabmail"),
window.gSpacesToolbar.spaces.find(space => space.name == spaceName)
);
});
await extension.startup();
await extension.awaitFinish();
await extension.unload();
});