749 lines
18 KiB
JavaScript
749 lines
18 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
|
*/
|
|
|
|
// Non-extension add-ons are not supported on Android, but some tests install
|
|
// non-extension add-ons, such as static themes, dictionaries, langpacks.
|
|
const skipOnAndroid = () => ({
|
|
skip_if: () => AppConstants.platform === "android",
|
|
});
|
|
|
|
const ID = "webextension1@tests.mozilla.org";
|
|
|
|
const profileDir = gProfD.clone();
|
|
profileDir.append("extensions");
|
|
|
|
const ADDONS = {
|
|
webextension_1: {
|
|
"manifest.json": {
|
|
name: "Web Extension Name",
|
|
version: "1.0",
|
|
manifest_version: 2,
|
|
browser_specific_settings: {
|
|
gecko: {
|
|
id: "webextension1@tests.mozilla.org",
|
|
},
|
|
},
|
|
icons: {
|
|
48: "icon48.png",
|
|
64: "icon64.png",
|
|
},
|
|
},
|
|
"chrome.manifest": "content webex ./\n",
|
|
},
|
|
webextension_3: {
|
|
"manifest.json": {
|
|
name: "Web Extensiøn __MSG_name__",
|
|
description: "Descriptïon __MSG_desc__ of add-on",
|
|
version: "1.0",
|
|
manifest_version: 2,
|
|
default_locale: "en",
|
|
browser_specific_settings: {
|
|
gecko: {
|
|
id: "webextension3@tests.mozilla.org",
|
|
},
|
|
},
|
|
},
|
|
"_locales/en/messages.json": {
|
|
name: {
|
|
message: "foo ☹",
|
|
description: "foo",
|
|
},
|
|
desc: {
|
|
message: "bar ☹",
|
|
description: "bar",
|
|
},
|
|
},
|
|
"_locales/fr/messages.json": {
|
|
name: {
|
|
message: "le foo ☺",
|
|
description: "foo",
|
|
},
|
|
desc: {
|
|
message: "le bar ☺",
|
|
description: "bar",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(
|
|
Ci.nsIChromeRegistry
|
|
);
|
|
|
|
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
|
|
|
|
const {
|
|
ExtensionParent: { GlobalManager },
|
|
} = ChromeUtils.importESModule(
|
|
"resource://gre/modules/ExtensionParent.sys.mjs"
|
|
);
|
|
|
|
add_task(async function test_1() {
|
|
await promiseStartupManager();
|
|
|
|
equal(GlobalManager.extensionMap.size, 0);
|
|
|
|
let { addon } = await AddonTestUtils.promiseInstallXPI(ADDONS.webextension_1);
|
|
|
|
equal(GlobalManager.extensionMap.size, 1);
|
|
ok(GlobalManager.extensionMap.has(ID));
|
|
|
|
Assert.throws(
|
|
() =>
|
|
chromeReg.convertChromeURL(
|
|
Services.io.newURI("chrome://webex/content/webex.xul")
|
|
),
|
|
error => error.result == Cr.NS_ERROR_FILE_NOT_FOUND,
|
|
"Chrome manifest should not have been registered"
|
|
);
|
|
|
|
let uri = do_get_addon_root_uri(profileDir, ID);
|
|
|
|
checkAddon(ID, addon, {
|
|
version: "1.0",
|
|
name: "Web Extension Name",
|
|
isCompatible: true,
|
|
appDisabled: false,
|
|
isActive: true,
|
|
isSystem: false,
|
|
type: "extension",
|
|
isWebExtension: true,
|
|
signedState: AddonManager.SIGNEDSTATE_PRIVILEGED,
|
|
iconURL: `${uri}icon48.png`,
|
|
});
|
|
|
|
// Should persist through a restart
|
|
await promiseShutdownManager();
|
|
|
|
equal(GlobalManager.extensionMap.size, 0);
|
|
|
|
await promiseStartupManager();
|
|
|
|
equal(GlobalManager.extensionMap.size, 1);
|
|
ok(GlobalManager.extensionMap.has(ID));
|
|
|
|
addon = await promiseAddonByID(ID);
|
|
|
|
uri = do_get_addon_root_uri(profileDir, ID);
|
|
|
|
checkAddon(ID, addon, {
|
|
version: "1.0",
|
|
name: "Web Extension Name",
|
|
isCompatible: true,
|
|
appDisabled: false,
|
|
isActive: true,
|
|
isSystem: false,
|
|
type: "extension",
|
|
signedState: AddonManager.SIGNEDSTATE_PRIVILEGED,
|
|
iconURL: `${uri}icon48.png`,
|
|
});
|
|
|
|
await addon.disable();
|
|
|
|
equal(GlobalManager.extensionMap.size, 0);
|
|
|
|
await addon.enable();
|
|
|
|
equal(GlobalManager.extensionMap.size, 1);
|
|
ok(GlobalManager.extensionMap.has(ID));
|
|
|
|
await addon.uninstall();
|
|
|
|
equal(GlobalManager.extensionMap.size, 0);
|
|
Assert.ok(!GlobalManager.extensionMap.has(ID));
|
|
|
|
await promiseShutdownManager();
|
|
});
|
|
|
|
// Writing the manifest direct to the profile should work
|
|
add_task(async function test_2() {
|
|
await promiseWriteWebManifestForExtension(
|
|
{
|
|
name: "Web Extension Name",
|
|
version: "1.0",
|
|
manifest_version: 2,
|
|
browser_specific_settings: {
|
|
gecko: {
|
|
id: ID,
|
|
},
|
|
},
|
|
},
|
|
profileDir
|
|
);
|
|
|
|
await promiseStartupManager();
|
|
|
|
let addon = await promiseAddonByID(ID);
|
|
checkAddon(ID, addon, {
|
|
version: "1.0",
|
|
name: "Web Extension Name",
|
|
isCompatible: true,
|
|
appDisabled: false,
|
|
isActive: true,
|
|
isSystem: false,
|
|
type: "extension",
|
|
signedState: AddonManager.SIGNEDSTATE_PRIVILEGED,
|
|
});
|
|
|
|
await addon.uninstall();
|
|
|
|
await promiseRestartManager();
|
|
});
|
|
|
|
add_task(async function test_manifest_localization() {
|
|
const extensionId = "webextension3@tests.mozilla.org";
|
|
|
|
let { addon } = await AddonTestUtils.promiseInstallXPI(ADDONS.webextension_3);
|
|
|
|
await addon.disable();
|
|
|
|
checkAddon(ID, addon, {
|
|
name: "Web Extensiøn foo ☹",
|
|
description: "Descriptïon bar ☹ of add-on",
|
|
});
|
|
|
|
await restartWithLocales(["fr-FR"]);
|
|
|
|
addon = await promiseAddonByID(extensionId);
|
|
checkAddon(ID, addon, {
|
|
name: "Web Extensiøn le foo ☺",
|
|
description: "Descriptïon le bar ☺ of add-on",
|
|
});
|
|
|
|
await restartWithLocales(["de"]);
|
|
|
|
addon = await promiseAddonByID(extensionId);
|
|
checkAddon(ID, addon, {
|
|
name: "Web Extensiøn foo ☹",
|
|
description: "Descriptïon bar ☹ of add-on",
|
|
});
|
|
|
|
await addon.uninstall();
|
|
});
|
|
|
|
// Missing version should cause a failure
|
|
add_task(async function test_3() {
|
|
await promiseWriteWebManifestForExtension(
|
|
{
|
|
name: "Web Extension Name",
|
|
manifest_version: 2,
|
|
browser_specific_settings: {
|
|
gecko: {
|
|
id: ID,
|
|
},
|
|
},
|
|
},
|
|
profileDir
|
|
);
|
|
|
|
await promiseRestartManager();
|
|
|
|
let addon = await promiseAddonByID(ID);
|
|
Assert.equal(addon, null);
|
|
|
|
let file = getFileForAddon(profileDir, ID);
|
|
Assert.ok(!file.exists());
|
|
|
|
await promiseRestartManager();
|
|
});
|
|
|
|
// Incorrect manifest version should cause a failure
|
|
add_task(async function test_4() {
|
|
await promiseWriteWebManifestForExtension(
|
|
{
|
|
name: "Web Extension Name",
|
|
version: "1.0",
|
|
manifest_version: 1,
|
|
browser_specific_settings: {
|
|
gecko: {
|
|
id: ID,
|
|
},
|
|
},
|
|
},
|
|
profileDir
|
|
);
|
|
|
|
await promiseRestartManager();
|
|
|
|
let addon = await promiseAddonByID(ID);
|
|
Assert.equal(addon, null);
|
|
|
|
let file = getFileForAddon(profileDir, ID);
|
|
Assert.ok(!file.exists());
|
|
|
|
await promiseRestartManager();
|
|
});
|
|
|
|
// Test that the "options_ui" manifest section is processed correctly.
|
|
add_task(async function test_options_ui() {
|
|
let OPTIONS_RE =
|
|
/^moz-extension:\/\/[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\/options\.html$/;
|
|
|
|
const extensionId = "webextension@tests.mozilla.org";
|
|
let addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
browser_specific_settings: { gecko: { id: extensionId } },
|
|
options_ui: {
|
|
page: "options.html",
|
|
},
|
|
},
|
|
});
|
|
|
|
checkAddon(extensionId, addon, {
|
|
optionsType: AddonManager.OPTIONS_TYPE_INLINE_BROWSER,
|
|
});
|
|
|
|
ok(
|
|
OPTIONS_RE.test(addon.optionsURL),
|
|
"Addon should have a moz-extension: options URL for /options.html"
|
|
);
|
|
|
|
await addon.uninstall();
|
|
|
|
const ID2 = "webextension2@tests.mozilla.org";
|
|
addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
browser_specific_settings: { gecko: { id: ID2 } },
|
|
options_ui: {
|
|
page: "options.html",
|
|
open_in_tab: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
checkAddon(ID2, addon, {
|
|
optionsType: AddonManager.OPTIONS_TYPE_TAB,
|
|
});
|
|
|
|
ok(
|
|
OPTIONS_RE.test(addon.optionsURL),
|
|
"Addon should have a moz-extension: options URL for /options.html"
|
|
);
|
|
|
|
await addon.uninstall();
|
|
|
|
info("Test again with options_page manifest property");
|
|
const ID3 = "options_page_alias@tests.mozilla.org";
|
|
addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
browser_specific_settings: { gecko: { id: ID3 } },
|
|
options_page: "options.html",
|
|
},
|
|
});
|
|
|
|
checkAddon(ID3, addon, {
|
|
optionsType: AddonManager.OPTIONS_TYPE_TAB,
|
|
});
|
|
|
|
ok(
|
|
OPTIONS_RE.test(addon.optionsURL),
|
|
"Addon should have a moz-extension: options URL for /options.html"
|
|
);
|
|
|
|
await addon.uninstall();
|
|
|
|
info("Test options_page and options_page set to a different page");
|
|
|
|
const ID4 = "options_page_warning@tests.mozilla.org";
|
|
addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
browser_specific_settings: { gecko: { id: ID4 } },
|
|
options_page: "options_page.html",
|
|
options_ui: {
|
|
page: "options.html",
|
|
open_in_tab: false,
|
|
},
|
|
},
|
|
});
|
|
|
|
checkAddon(ID4, addon, {
|
|
optionsType: AddonManager.OPTIONS_TYPE_INLINE_BROWSER,
|
|
});
|
|
|
|
ok(
|
|
OPTIONS_RE.test(addon.optionsURL),
|
|
"Addon should have a moz-extension: options URL for /options.html"
|
|
);
|
|
|
|
await addon.uninstall();
|
|
|
|
info("Test options_page and options_page are both set to the same page");
|
|
|
|
const ID5 = "options_page_and_ui_same_page@tests.mozilla.org";
|
|
addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
browser_specific_settings: { gecko: { id: ID5 } },
|
|
options_page: "options.html",
|
|
options_ui: { page: "options.html" },
|
|
},
|
|
});
|
|
|
|
checkAddon(ID5, addon, {
|
|
optionsType: AddonManager.OPTIONS_TYPE_INLINE_BROWSER,
|
|
});
|
|
|
|
ok(
|
|
OPTIONS_RE.test(addon.optionsURL),
|
|
"Addon should have a moz-extension: options URL for /options.html"
|
|
);
|
|
|
|
await addon.uninstall();
|
|
});
|
|
|
|
// Test that experiments permissions add the appropriate dependencies.
|
|
add_task(async function test_experiments_dependencies() {
|
|
let addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
browser_specific_settings: { gecko: { id: "meh@experiment" } },
|
|
permissions: ["experiments.meh"],
|
|
},
|
|
});
|
|
|
|
checkAddon(addon.id, addon, {
|
|
dependencies: ["meh@experiments.addons.mozilla.org"],
|
|
// Add-on should be app disabled due to missing dependencies
|
|
appDisabled: true,
|
|
});
|
|
|
|
await addon.uninstall();
|
|
});
|
|
|
|
add_task(async function developerShouldOverride() {
|
|
let addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
default_locale: "en",
|
|
developer: {
|
|
name: "__MSG_name__",
|
|
url: "__MSG_url__",
|
|
},
|
|
author: "Will be overridden by developer",
|
|
homepage_url: "https://will.be.overridden",
|
|
},
|
|
files: {
|
|
"_locales/en/messages.json": `{
|
|
"name": {
|
|
"message": "en name"
|
|
},
|
|
"url": {
|
|
"message": "https://example.net/en"
|
|
}
|
|
}`,
|
|
},
|
|
});
|
|
|
|
checkAddon(ID, addon, {
|
|
creator: "en name",
|
|
homepageURL: "https://example.net/en",
|
|
});
|
|
|
|
await addon.uninstall();
|
|
});
|
|
|
|
add_task(skipOnAndroid(), async function test_invalid_developer_is_ignored() {
|
|
for (const { type, manifestProps, files } of [
|
|
{
|
|
type: "dictionary",
|
|
manifestProps: {
|
|
dictionaries: {
|
|
"en-US": "en-US.dic",
|
|
},
|
|
},
|
|
files: {
|
|
"en-US.dic": "",
|
|
"en-US.aff": "",
|
|
},
|
|
},
|
|
{
|
|
type: "theme",
|
|
manifestProps: {
|
|
theme: {
|
|
colors: {
|
|
frame: "#FFF",
|
|
tab_background_text: "#000",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "locale",
|
|
manifestProps: {
|
|
langpack_id: "und",
|
|
languages: {
|
|
und: {
|
|
chrome_resources: {
|
|
global: "chrome/und/locale/und/global",
|
|
},
|
|
version: "20190326174300",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
]) {
|
|
const id = `${type}@mozilla.com`;
|
|
const creator = "Some author";
|
|
const homepageURL = "https://example.net";
|
|
|
|
info(`== loading add-on with id=${id} ==`);
|
|
|
|
for (let developer of [{}, null, { name: null, url: null }]) {
|
|
let addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
author: creator,
|
|
homepage_url: homepageURL,
|
|
developer,
|
|
browser_specific_settings: { gecko: { id } },
|
|
...manifestProps,
|
|
},
|
|
files,
|
|
});
|
|
|
|
checkAddon(id, addon, { type, creator, homepageURL });
|
|
|
|
await addon.uninstall();
|
|
}
|
|
}
|
|
});
|
|
|
|
add_task(async function authorNotString() {
|
|
ExtensionTestUtils.failOnSchemaWarnings(false);
|
|
for (let author of [{}, [], 42]) {
|
|
let addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
author,
|
|
manifest_version: 2,
|
|
name: "Web Extension Name",
|
|
version: "1.0",
|
|
},
|
|
});
|
|
|
|
checkAddon(ID, addon, {
|
|
creator: null,
|
|
});
|
|
|
|
await addon.uninstall();
|
|
}
|
|
ExtensionTestUtils.failOnSchemaWarnings(true);
|
|
});
|
|
|
|
add_task(skipOnAndroid(), async function testThemeExtension() {
|
|
let addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
author: "Some author",
|
|
manifest_version: 2,
|
|
name: "Web Extension Name",
|
|
version: "1.0",
|
|
theme: { images: { theme_frame: "example.png" } },
|
|
},
|
|
});
|
|
|
|
checkAddon(ID, addon, {
|
|
creator: "Some author",
|
|
version: "1.0",
|
|
name: "Web Extension Name",
|
|
isCompatible: true,
|
|
appDisabled: false,
|
|
isActive: false,
|
|
userDisabled: true,
|
|
isSystem: false,
|
|
type: "theme",
|
|
isWebExtension: true,
|
|
signedState: AddonManager.SIGNEDSTATE_PRIVILEGED,
|
|
});
|
|
|
|
await addon.uninstall();
|
|
|
|
// Also test one without a proper 'theme' section.
|
|
ExtensionTestUtils.failOnSchemaWarnings(false);
|
|
addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
author: "Some author",
|
|
manifest_version: 2,
|
|
name: "Web Extension Name",
|
|
version: "1.0",
|
|
theme: null,
|
|
},
|
|
});
|
|
ExtensionTestUtils.failOnSchemaWarnings(true);
|
|
|
|
checkAddon(ID, addon, {
|
|
type: "extension",
|
|
isWebExtension: true,
|
|
});
|
|
|
|
await addon.uninstall();
|
|
});
|
|
|
|
// Test that we can update from a webextension to a webextension-theme
|
|
add_task(skipOnAndroid(), async function test_theme_upgrade() {
|
|
// First install a regular webextension
|
|
let addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
version: "1.0",
|
|
name: "Test WebExtension 1 (temporary)",
|
|
browser_specific_settings: {
|
|
gecko: {
|
|
id: ID,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
checkAddon(ID, addon, {
|
|
version: "1.0",
|
|
name: "Test WebExtension 1 (temporary)",
|
|
isCompatible: true,
|
|
appDisabled: false,
|
|
isActive: true,
|
|
type: "extension",
|
|
signedState: AddonManager.SIGNEDSTATE_PRIVILEGED,
|
|
});
|
|
|
|
// Create a webextension theme with the same ID
|
|
addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
version: "2.0",
|
|
name: "Test WebExtension 1 (temporary)",
|
|
browser_specific_settings: {
|
|
gecko: {
|
|
id: ID,
|
|
},
|
|
},
|
|
theme: { images: { theme_frame: "example.png" } },
|
|
},
|
|
});
|
|
|
|
checkAddon(ID, addon, {
|
|
version: "2.0",
|
|
name: "Test WebExtension 1 (temporary)",
|
|
isCompatible: true,
|
|
appDisabled: false,
|
|
isActive: true,
|
|
// This is what we're really interested in:
|
|
type: "theme",
|
|
isWebExtension: true,
|
|
});
|
|
|
|
await addon.uninstall();
|
|
|
|
addon = await promiseAddonByID(ID);
|
|
Assert.equal(addon, null);
|
|
});
|
|
|
|
add_task(skipOnAndroid(), async function test_developer_properties() {
|
|
const name = "developer-name";
|
|
const url = "https://example.org";
|
|
|
|
for (const { type, manifestProps, files } of [
|
|
{
|
|
type: "dictionary",
|
|
manifestProps: {
|
|
dictionaries: {
|
|
"en-US": "en-US.dic",
|
|
},
|
|
},
|
|
files: {
|
|
"en-US.dic": "",
|
|
"en-US.aff": "",
|
|
},
|
|
},
|
|
{
|
|
type: "statictheme",
|
|
manifestProps: {
|
|
theme: {
|
|
colors: {
|
|
frame: "#FFF",
|
|
tab_background_text: "#000",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "langpack",
|
|
manifestProps: {
|
|
langpack_id: "und",
|
|
languages: {
|
|
und: {
|
|
chrome_resources: {
|
|
global: "chrome/und/locale/und/global",
|
|
},
|
|
version: "20190326174300",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
]) {
|
|
const id = `${type}@mozilla.com`;
|
|
|
|
info(`== loading add-on with id=${id} ==`);
|
|
|
|
let addon = await promiseInstallWebExtension({
|
|
manifest: {
|
|
developer: {
|
|
name,
|
|
url,
|
|
},
|
|
author: "Will be overridden by developer",
|
|
homepage_url: "https://will.be.overridden",
|
|
browser_specific_settings: { gecko: { id } },
|
|
...manifestProps,
|
|
},
|
|
files,
|
|
});
|
|
|
|
checkAddon(id, addon, { creator: name, homepageURL: url });
|
|
|
|
await addon.uninstall();
|
|
}
|
|
});
|
|
|
|
add_task(async function test_invalid_homepage_and_developer_urls() {
|
|
const INVALID_URLS = [
|
|
"chrome://browser/content/",
|
|
"data:text/json,...",
|
|
"javascript:;",
|
|
"/",
|
|
"not-an-url",
|
|
];
|
|
const EXPECTED_ERROR_RE =
|
|
/Access denied for URL|may not load or link to|is not a valid URL/;
|
|
|
|
for (let url of INVALID_URLS) {
|
|
// First, we verify `homepage_url`, which has a `url` "format" defined
|
|
// since it exists.
|
|
let normalized = await ExtensionTestUtils.normalizeManifest({
|
|
homepage_url: url,
|
|
});
|
|
ok(
|
|
EXPECTED_ERROR_RE.test(normalized.error),
|
|
`got expected error for ${url}`
|
|
);
|
|
|
|
// The `developer.url` now has a "format" but it was a late addition so we
|
|
// are only raising a warning instead of an error.
|
|
ExtensionTestUtils.failOnSchemaWarnings(false);
|
|
normalized = await ExtensionTestUtils.normalizeManifest({
|
|
developer: { url },
|
|
});
|
|
ok(!normalized.error, "expected no error");
|
|
ok(
|
|
// Despites this prop being named `errors`, we are checking the warnings
|
|
// here.
|
|
EXPECTED_ERROR_RE.test(normalized.errors[0]),
|
|
`got expected warning for ${url}`
|
|
);
|
|
ExtensionTestUtils.failOnSchemaWarnings(true);
|
|
}
|
|
});
|
|
|
|
add_task(async function test_valid_homepage_and_developer_urls() {
|
|
let normalized = await ExtensionTestUtils.normalizeManifest({
|
|
developer: { url: "https://example.com" },
|
|
});
|
|
ok(!normalized.error, "expected no error");
|
|
|
|
normalized = await ExtensionTestUtils.normalizeManifest({
|
|
homepage_url: "https://example.com",
|
|
});
|
|
ok(!normalized.error, "expected no error");
|
|
});
|