summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/test/xpcshell/test_webextension.js')
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_webextension.js676
1 files changed, 676 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js b/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
new file mode 100644
index 0000000000..cd4b376117
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
@@ -0,0 +1,676 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+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();
+});
+
+// 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(async function test_invalid_developer_does_not_override() {
+ 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(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(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(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");
+});