gecko-dev/testing/marionette/addon.js
Alessio Placitelli 9b95cf64c2 Bug 1693022 - Report errors when uninstalling unknown addons in marionette. r=whimboo,marionette-reviewers
The AddonManager does not throw, but instead reports null, when
trying to get information about an addon if the id is unknown.
This makes sure marionette addresses this case.

Differential Revision: https://phabricator.services.mozilla.com/D105467
2021-02-19 16:14:09 +00:00

142 lines
4.0 KiB
JavaScript

/* 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";
const EXPORTED_SYMBOLS = ["Addon"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
AddonManager: "resource://gre/modules/AddonManager.jsm",
FileUtils: "resource://gre/modules/FileUtils.jsm",
error: "chrome://marionette/content/error.js",
});
// from https://developer.mozilla.org/en-US/Add-ons/Add-on_Manager/AddonManager#AddonInstall_errors
const ERRORS = {
[-1]: "ERROR_NETWORK_FAILURE: A network error occured.",
[-2]: "ERROR_INCORECT_HASH: The downloaded file did not match the expected hash.",
[-3]: "ERROR_CORRUPT_FILE: The file appears to be corrupt.",
[-4]: "ERROR_FILE_ACCESS: There was an error accessing the filesystem.",
[-5]: "ERROR_SIGNEDSTATE_REQUIRED: The addon must be signed and isn't.",
};
async function installAddon(file) {
let install = await AddonManager.getInstallForFile(file, null, {
source: "internal",
});
if (install.error) {
throw new error.UnknownError(ERRORS[install.error]);
}
return install.install().catch(err => {
throw new error.UnknownError(ERRORS[install.error]);
});
}
/** Installs addons by path and uninstalls by ID. */
class Addon {
/**
* Install a Firefox addon.
*
* If the addon is restartless, it can be used right away. Otherwise a
* restart is required.
*
* Temporary addons will automatically be uninstalled on shutdown and
* do not need to be signed, though they must be restartless.
*
* @param {string} path
* Full path to the extension package archive.
* @param {boolean=} temporary
* True to install the addon temporarily, false (default) otherwise.
*
* @return {Promise.<string>}
* Addon ID.
*
* @throws {UnknownError}
* If there is a problem installing the addon.
*/
static async install(path, temporary = false) {
let addon;
let file;
try {
file = new FileUtils.File(path);
} catch (e) {
throw new error.UnknownError(`Expected absolute path: ${e}`, e);
}
if (!file.exists()) {
throw new error.UnknownError(`No such file or directory: ${path}`);
}
try {
if (temporary) {
addon = await AddonManager.installTemporaryAddon(file);
} else {
addon = await installAddon(file);
}
} catch (e) {
throw new error.UnknownError(
`Could not install add-on: ${path}: ${e.message}`,
e
);
}
return addon.id;
}
/**
* Uninstall a Firefox addon.
*
* If the addon is restartless it will be uninstalled right away.
* Otherwise, Firefox must be restarted for the change to take effect.
*
* @param {string} id
* ID of the addon to uninstall.
*
* @return {Promise}
*
* @throws {UnknownError}
* If there is a problem uninstalling the addon.
*/
static async uninstall(id) {
let candidate = await AddonManager.getAddonByID(id);
if (candidate === null) {
// `AddonManager.getAddonByID` never rejects but instead
// returns `null` if the requested addon cannot be found.
throw new error.UnknownError(`Addon ${id} is not installed`);
}
return new Promise(resolve => {
let listener = {
onOperationCancelled: addon => {
if (addon.id === candidate.id) {
AddonManager.removeAddonListener(listener);
throw new error.UnknownError(
`Uninstall of ${candidate.id} has been canceled`
);
}
},
onUninstalled: addon => {
if (addon.id === candidate.id) {
AddonManager.removeAddonListener(listener);
resolve();
}
},
};
AddonManager.addAddonListener(listener);
candidate.uninstall();
});
}
}
this.Addon = Addon;