summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js')
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js694
1 files changed, 694 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js
new file mode 100644
index 0000000000..c01f3ad703
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js
@@ -0,0 +1,694 @@
+let profileDir;
+add_task(async function setup() {
+ profileDir = gProfD.clone();
+ profileDir.append("extensions");
+
+ if (!profileDir.exists()) {
+ profileDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ }
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+ await promiseStartupManager();
+});
+
+const IMPLICIT_ID_XPI = "data/webext-implicit-id.xpi";
+const IMPLICIT_ID_ID = "webext_implicit_id@tests.mozilla.org";
+
+// webext-implicit-id.xpi has a minimal manifest with no
+// applications or browser_specific_settings, so its id comes
+// from its signature, which should be the ID constant defined below.
+add_task(async function test_implicit_id() {
+ let addon = await promiseAddonByID(IMPLICIT_ID_ID);
+ equal(addon, null, "Add-on is not installed");
+
+ await promiseInstallFile(do_get_file(IMPLICIT_ID_XPI));
+
+ addon = await promiseAddonByID(IMPLICIT_ID_ID);
+ notEqual(addon, null, "Add-on is installed");
+
+ await addon.uninstall();
+});
+
+// We should also be able to install webext-implicit-id.xpi temporarily
+// and it should look just like the regular install (ie, the ID should
+// come from the signature)
+add_task(async function test_implicit_id_temp() {
+ let addon = await promiseAddonByID(IMPLICIT_ID_ID);
+ equal(addon, null, "Add-on is not installed");
+
+ let xpifile = do_get_file(IMPLICIT_ID_XPI);
+ await AddonManager.installTemporaryAddon(xpifile);
+
+ addon = await promiseAddonByID(IMPLICIT_ID_ID);
+ notEqual(addon, null, "Add-on is installed");
+
+ // The sourceURI of a temporary installed addon should be equal to the
+ // file url of the installed xpi file.
+ equal(
+ addon.sourceURI && addon.sourceURI.spec,
+ Services.io.newFileURI(xpifile).spec,
+ "SourceURI of the add-on has the expected value"
+ );
+
+ await addon.uninstall();
+});
+
+// Test that extension install error attach the detailed error messages to the
+// Error object.
+add_task(async function test_invalid_extension_install_errors() {
+ const manifest = {
+ name: "invalid",
+ browser_specific_settings: {
+ gecko: {
+ id: "invalid@tests.mozilla.org",
+ },
+ },
+ description: "extension with an invalid 'matches' value",
+ manifest_version: 2,
+ content_scripts: [
+ {
+ matches: "*://*.foo.com/*",
+ js: ["content.js"],
+ },
+ ],
+ version: "1.0",
+ };
+
+ const addonDir = await promiseWriteWebManifestForExtension(
+ manifest,
+ gTmpD,
+ "the-addon-sub-dir"
+ );
+
+ await Assert.rejects(
+ AddonManager.installTemporaryAddon(addonDir),
+ err => {
+ return (
+ err.additionalErrors.length == 1 &&
+ err.additionalErrors[0] ==
+ `Reading manifest: Error processing content_scripts.0.matches: ` +
+ `Expected array instead of "*://*.foo.com/*"`
+ );
+ },
+ "Exception has the proper additionalErrors details"
+ );
+
+ Services.obs.notifyObservers(addonDir, "flush-cache-entry");
+ addonDir.remove(true);
+});
+
+// We should be able to temporarily install an unsigned web extension
+// that does not have an ID in its manifest.
+add_task(async function test_unsigned_no_id_temp_install() {
+ AddonTestUtils.useRealCertChecks = true;
+ const manifest = {
+ name: "no ID",
+ description: "extension without an ID",
+ manifest_version: 2,
+ version: "1.0",
+ };
+
+ const addonDir = await promiseWriteWebManifestForExtension(
+ manifest,
+ gTmpD,
+ "the-addon-sub-dir"
+ );
+ const testDate = new Date();
+ const addon = await AddonManager.installTemporaryAddon(addonDir);
+
+ ok(addon.id, "ID should have been auto-generated");
+ ok(
+ Math.abs(addon.installDate - testDate) < 10000,
+ "addon has an expected installDate"
+ );
+ ok(
+ Math.abs(addon.updateDate - testDate) < 10000,
+ "addon has an expected updateDate"
+ );
+
+ // The sourceURI of a temporary installed addon should be equal to the
+ // file url of the installed source dir.
+ equal(
+ addon.sourceURI && addon.sourceURI.spec,
+ Services.io.newFileURI(addonDir).spec,
+ "SourceURI of the add-on has the expected value"
+ );
+
+ // Install the same directory again, as if re-installing or reloading.
+ const secondAddon = await AddonManager.installTemporaryAddon(addonDir);
+ // The IDs should be the same.
+ equal(secondAddon.id, addon.id, "Reinstalled add-on has the expected ID");
+ equal(
+ secondAddon.installDate.valueOf(),
+ addon.installDate.valueOf(),
+ "Reloaded add-on has the expected installDate."
+ );
+
+ await secondAddon.uninstall();
+ Services.obs.notifyObservers(addonDir, "flush-cache-entry");
+ addonDir.remove(true);
+ AddonTestUtils.useRealCertChecks = false;
+});
+
+// We should be able to install two extensions from manifests without IDs
+// at different locations and get two unique extensions.
+add_task(async function test_multiple_no_id_extensions() {
+ AddonTestUtils.useRealCertChecks = true;
+ const manifest = {
+ name: "no ID",
+ description: "extension without an ID",
+ manifest_version: 2,
+ version: "1.0",
+ };
+
+ let extension1 = ExtensionTestUtils.loadExtension({
+ manifest,
+ useAddonManager: "temporary",
+ });
+
+ let extension2 = ExtensionTestUtils.loadExtension({
+ manifest,
+ useAddonManager: "temporary",
+ });
+
+ await Promise.all([extension1.startup(), extension2.startup()]);
+
+ const allAddons = await AddonManager.getAllAddons();
+
+ info(`Found these add-ons: ${allAddons.map(a => a.name).join(", ")}`);
+ const filtered = allAddons.filter(addon => addon.name === manifest.name);
+ // Make sure we have two add-ons by the same name.
+ equal(filtered.length, 2, "Two add-ons are installed with the same name");
+
+ await extension1.unload();
+ await extension2.unload();
+ AddonTestUtils.useRealCertChecks = false;
+});
+
+// Test that we can get the ID from browser_specific_settings
+add_task(async function test_bss_id() {
+ const ID = "webext_bss_id@tests.mozilla.org";
+
+ let manifest = {
+ name: "bss test",
+ description: "test that ID may be in browser_specific_settings",
+ manifest_version: 2,
+ version: "1.0",
+
+ browser_specific_settings: {
+ gecko: {
+ id: ID,
+ },
+ },
+ };
+
+ let addon = await promiseAddonByID(ID);
+ equal(addon, null, "Add-on is not installed");
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest,
+ useAddonManager: "temporary",
+ });
+ await extension.startup();
+
+ addon = await promiseAddonByID(ID);
+ notEqual(addon, null, "Add-on is installed");
+
+ await extension.unload();
+});
+
+// Test that if we have IDs in both browser_specific_settings and applications,
+// that we prefer the ID in browser_specific_settings.
+add_task(async function test_two_ids() {
+ const GOOD_ID = "two_ids@tests.mozilla.org";
+ const BAD_ID = "i_am_obsolete@tests.mozilla.org";
+
+ let manifest = {
+ name: "two id test",
+ description:
+ "test a web extension with ids in both applications and browser_specific_settings",
+ manifest_version: 2,
+ version: "1.0",
+
+ applications: {
+ gecko: {
+ id: BAD_ID,
+ },
+ },
+
+ browser_specific_settings: {
+ gecko: {
+ id: GOOD_ID,
+ },
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest,
+ useAddonManager: "temporary",
+ });
+ await extension.startup();
+
+ let addon = await promiseAddonByID(BAD_ID);
+ equal(addon, null, "Add-on is not found using bad ID");
+ addon = await promiseAddonByID(GOOD_ID);
+ notEqual(addon, null, "Add-on is found using good ID");
+
+ await extension.unload();
+});
+
+// Test that strict_min_version and strict_max_version are enforced for
+// loading temporary extension.
+add_task(async function test_strict_min_max() {
+ // the app version being compared to is 1.9.2
+ const addonId = "strict_min_max@tests.mozilla.org";
+ const MANIFEST = {
+ name: "strict min max test",
+ description: "test strict min and max with temporary loading",
+ manifest_version: 2,
+ version: "1.0",
+ };
+
+ // bad max good min
+ let apps = {
+ browser_specific_settings: {
+ gecko: {
+ id: addonId,
+ strict_min_version: "1",
+ strict_max_version: "1",
+ },
+ },
+ };
+ let testManifest = Object.assign(apps, MANIFEST);
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: testManifest,
+ useAddonManager: "temporary",
+ });
+
+ let expectedMsg = new RegExp(
+ "Add-on strict_min_max@tests.mozilla.org is not compatible with application version. " +
+ "add-on minVersion: 1. add-on maxVersion: 1."
+ );
+ await Assert.rejects(
+ extension.startup(),
+ expectedMsg,
+ "Install rejects when specified maxVersion is not valid"
+ );
+
+ let addon = await promiseAddonByID(addonId);
+ equal(addon, null, "Add-on is not installed");
+
+ // bad min good max
+ apps = {
+ browser_specific_settings: {
+ gecko: {
+ id: addonId,
+ strict_min_version: "2",
+ strict_max_version: "2",
+ },
+ },
+ };
+ testManifest = Object.assign(apps, MANIFEST);
+
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: testManifest,
+ useAddonManager: "temporary",
+ });
+
+ expectedMsg = new RegExp(
+ "Add-on strict_min_max@tests.mozilla.org is not compatible with application version. " +
+ "add-on minVersion: 2. add-on maxVersion: 2."
+ );
+ await Assert.rejects(
+ extension.startup(),
+ expectedMsg,
+ "Install rejects when specified minVersion is not valid"
+ );
+
+ addon = await promiseAddonByID(addonId);
+ equal(addon, null, "Add-on is not installed");
+
+ // bad both
+ apps = {
+ browser_specific_settings: {
+ gecko: {
+ id: addonId,
+ strict_min_version: "2",
+ strict_max_version: "1",
+ },
+ },
+ };
+ testManifest = Object.assign(apps, MANIFEST);
+
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: testManifest,
+ useAddonManager: "temporary",
+ });
+
+ expectedMsg = new RegExp(
+ "Add-on strict_min_max@tests.mozilla.org is not compatible with application version. " +
+ "add-on minVersion: 2. add-on maxVersion: 1."
+ );
+ await Assert.rejects(
+ extension.startup(),
+ expectedMsg,
+ "Install rejects when specified minVersion and maxVersion are not valid"
+ );
+
+ addon = await promiseAddonByID(addonId);
+ equal(addon, null, "Add-on is not installed");
+
+ // bad only min
+ apps = {
+ browser_specific_settings: {
+ gecko: {
+ id: addonId,
+ strict_min_version: "2",
+ },
+ },
+ };
+ testManifest = Object.assign(apps, MANIFEST);
+
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: testManifest,
+ useAddonManager: "temporary",
+ });
+
+ expectedMsg = new RegExp(
+ "Add-on strict_min_max@tests.mozilla.org is not compatible with application version. " +
+ "add-on minVersion: 2."
+ );
+ await Assert.rejects(
+ extension.startup(),
+ expectedMsg,
+ "Install rejects when specified minVersion and maxVersion are not valid"
+ );
+
+ addon = await promiseAddonByID(addonId);
+ equal(addon, null, "Add-on is not installed");
+
+ // bad only max
+ apps = {
+ browser_specific_settings: {
+ gecko: {
+ id: addonId,
+ strict_max_version: "1",
+ },
+ },
+ };
+ testManifest = Object.assign(apps, MANIFEST);
+
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: testManifest,
+ useAddonManager: "temporary",
+ });
+
+ expectedMsg = new RegExp(
+ "Add-on strict_min_max@tests.mozilla.org is not compatible with application version. " +
+ "add-on maxVersion: 1."
+ );
+ await Assert.rejects(
+ extension.startup(),
+ expectedMsg,
+ "Install rejects when specified minVersion and maxVersion are not valid"
+ );
+
+ addon = await promiseAddonByID(addonId);
+ equal(addon, null, "Add-on is not installed");
+
+ // good both
+ apps = {
+ browser_specific_settings: {
+ gecko: {
+ id: addonId,
+ strict_min_version: "1",
+ strict_max_version: "2",
+ },
+ },
+ };
+ testManifest = Object.assign(apps, MANIFEST);
+
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: testManifest,
+ useAddonManager: "temporary",
+ });
+
+ await extension.startup();
+ addon = await promiseAddonByID(addonId);
+
+ notEqual(addon, null, "Add-on is installed");
+ equal(addon.id, addonId, "Installed add-on has the expected ID");
+ await extension.unload();
+
+ // good only min
+ let newId = "strict_min_only@tests.mozilla.org";
+ apps = {
+ browser_specific_settings: {
+ gecko: {
+ id: newId,
+ strict_min_version: "1",
+ },
+ },
+ };
+ testManifest = Object.assign(apps, MANIFEST);
+
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: testManifest,
+ useAddonManager: "temporary",
+ });
+
+ await extension.startup();
+ addon = await promiseAddonByID(newId);
+
+ notEqual(addon, null, "Add-on is installed");
+ equal(addon.id, newId, "Installed add-on has the expected ID");
+
+ await extension.unload();
+
+ // good only max
+ newId = "strict_max_only@tests.mozilla.org";
+ apps = {
+ browser_specific_settings: {
+ gecko: {
+ id: newId,
+ strict_max_version: "2",
+ },
+ },
+ };
+ testManifest = Object.assign(apps, MANIFEST);
+
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: testManifest,
+ useAddonManager: "temporary",
+ });
+
+ await extension.startup();
+ addon = await promiseAddonByID(newId);
+
+ notEqual(addon, null, "Add-on is installed");
+ equal(addon.id, newId, "Installed add-on has the expected ID");
+
+ await extension.unload();
+
+ // * in min will throw an error
+ for (let version of ["0.*", "0.*.0"]) {
+ newId = "strict_min_star@tests.mozilla.org";
+ let minStarApps = {
+ browser_specific_settings: {
+ gecko: {
+ id: newId,
+ strict_min_version: version,
+ },
+ },
+ };
+
+ let minStarTestManifest = Object.assign(minStarApps, MANIFEST);
+
+ let minStarExtension = ExtensionTestUtils.loadExtension({
+ manifest: minStarTestManifest,
+ useAddonManager: "temporary",
+ });
+
+ await Assert.rejects(
+ minStarExtension.startup(),
+ /The use of '\*' in strict_min_version is invalid/,
+ "loading an extension with a * in strict_min_version throws an exception"
+ );
+
+ let minStarAddon = await promiseAddonByID(newId);
+ equal(minStarAddon, null, "Add-on is not installed");
+ }
+
+ // incompatible extension but with compatibility checking off
+ newId = "checkCompatibility@tests.mozilla.org";
+ apps = {
+ browser_specific_settings: {
+ gecko: {
+ id: newId,
+ strict_max_version: "1",
+ },
+ },
+ };
+ testManifest = Object.assign(apps, MANIFEST);
+
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: testManifest,
+ useAddonManager: "temporary",
+ });
+
+ let savedCheckCompatibilityValue = AddonManager.checkCompatibility;
+ AddonManager.checkCompatibility = false;
+ await extension.startup();
+ addon = await promiseAddonByID(newId);
+
+ notEqual(addon, null, "Add-on is installed");
+ equal(addon.id, newId, "Installed add-on has the expected ID");
+
+ await extension.unload();
+ AddonManager.checkCompatibility = savedCheckCompatibilityValue;
+});
+
+// Check permissions prompt
+add_task(async function test_permissions_prompt() {
+ const manifest = {
+ name: "permissions test",
+ description: "permissions test",
+ manifest_version: 2,
+ version: "1.0",
+
+ permissions: ["tabs", "storage", "https://*.example.com/*", "<all_urls>"],
+ };
+
+ let xpi = ExtensionTestCommon.generateXPI({ manifest });
+
+ let install = await AddonManager.getInstallForFile(xpi);
+
+ let perminfo;
+ install.promptHandler = info => {
+ perminfo = info;
+ return Promise.resolve();
+ };
+
+ await install.install();
+
+ notEqual(perminfo, undefined, "Permission handler was invoked");
+ equal(
+ perminfo.existingAddon,
+ null,
+ "Permission info does not include an existing addon"
+ );
+ notEqual(perminfo.addon, null, "Permission info includes the new addon");
+ let perms = perminfo.addon.userPermissions;
+ deepEqual(
+ perms.permissions,
+ ["tabs", "storage"],
+ "API permissions are correct"
+ );
+ deepEqual(
+ perms.origins,
+ ["https://*.example.com/*", "<all_urls>"],
+ "Host permissions are correct"
+ );
+
+ let addon = await promiseAddonByID(perminfo.addon.id);
+ notEqual(addon, null, "Extension was installed");
+
+ await addon.uninstall();
+ await IOUtils.remove(xpi.path);
+});
+
+// Check permissions prompt cancellation
+add_task(async function test_permissions_prompt_cancel() {
+ const manifest = {
+ name: "permissions test",
+ description: "permissions test",
+ manifest_version: 2,
+ version: "1.0",
+
+ permissions: ["webRequestBlocking"],
+ };
+
+ let xpi = ExtensionTestCommon.generateXPI({ manifest });
+
+ let install = await AddonManager.getInstallForFile(xpi);
+
+ let perminfo;
+ install.promptHandler = info => {
+ perminfo = info;
+ return Promise.reject();
+ };
+
+ await promiseCompleteInstall(install);
+
+ notEqual(perminfo, undefined, "Permission handler was invoked");
+
+ let addon = await promiseAddonByID(perminfo.addon.id);
+ equal(addon, null, "Extension was not installed");
+
+ await IOUtils.remove(xpi.path);
+});
+
+// Test that presence of 'edge' property in 'browser_specific_settings' doesn't prevent installation from completing successfully
+add_task(async function test_non_gecko_bss_install() {
+ const ID = "ms_edge@tests.mozilla.org";
+
+ const manifest = {
+ name: "MS Edge and unknown browser test",
+ description:
+ "extension with bss properties for 'edge', and 'unknown_browser'",
+ manifest_version: 2,
+ version: "1.0",
+ applications: { gecko: { id: ID } },
+ browser_specific_settings: {
+ edge: {
+ browser_action_next_to_addressbar: true,
+ },
+ unknown_browser: {
+ unknown_setting: true,
+ },
+ },
+ };
+
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest,
+ useAddonManager: "temporary",
+ });
+ ExtensionTestUtils.failOnSchemaWarnings(false);
+ await extension.startup();
+ ExtensionTestUtils.failOnSchemaWarnings(true);
+
+ const addon = await promiseAddonByID(ID);
+ notEqual(addon, null, "Add-on is installed");
+
+ await extension.unload();
+});
+
+// Test that bss overrides applications if both are present.
+add_task(async function test_duplicate_bss() {
+ const ID = "expected@tests.mozilla.org";
+
+ const manifest = {
+ manifest_version: 2,
+ version: "1.0",
+ applications: {
+ gecko: { id: "unexpected@tests.mozilla.org" },
+ },
+ browser_specific_settings: {
+ gecko: { id: ID },
+ },
+ };
+
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest,
+ useAddonManager: "temporary",
+ });
+ ExtensionTestUtils.failOnSchemaWarnings(false);
+ await extension.startup();
+ ExtensionTestUtils.failOnSchemaWarnings(true);
+
+ const addon = await promiseAddonByID(ID);
+ notEqual(addon, null, "Add-on is installed");
+
+ await extension.unload();
+});