summaryrefslogtreecommitdiffstats
path: root/remote/marionette/addon.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'remote/marionette/addon.sys.mjs')
-rw-r--r--remote/marionette/addon.sys.mjs142
1 files changed, 142 insertions, 0 deletions
diff --git a/remote/marionette/addon.sys.mjs b/remote/marionette/addon.sys.mjs
new file mode 100644
index 0000000000..f83671694b
--- /dev/null
+++ b/remote/marionette/addon.sys.mjs
@@ -0,0 +1,142 @@
+/* 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/. */
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
+ FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
+ error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
+});
+
+// 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_INCORRECT_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.",
+ [-6]: "ERROR_UNEXPECTED_ADDON_TYPE: The downloaded add-on had a different type than expected (during an update).",
+ [-7]: "ERROR_INCORRECT_ID: The addon did not have the expected ID (during an update).",
+ [-8]: "ERROR_INVALID_DOMAIN: The addon install_origins does not list the 3rd party domain.",
+ [-9]: "ERROR_UNEXPECTED_ADDON_VERSION: The downloaded add-on had a different version than expected (during an update).",
+ [-10]: "ERROR_BLOCKLISTED: The add-on is blocklisted.",
+ [-11]:
+ "ERROR_INCOMPATIBLE: The add-on is incompatible (w.r.t. the compatibility range).",
+ [-12]:
+ "ERROR_UNSUPPORTED_ADDON_TYPE: The add-on type is not supported by the platform.",
+};
+
+async function installAddon(file) {
+ let install = await lazy.AddonManager.getInstallForFile(file, null, {
+ source: "internal",
+ });
+
+ if (install.error) {
+ throw new lazy.error.UnknownError(ERRORS[install.error]);
+ }
+
+ return install.install().catch(err => {
+ throw new lazy.error.UnknownError(ERRORS[install.error]);
+ });
+}
+
+/** Installs addons by path and uninstalls by ID. */
+export 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.
+ *
+ * @returns {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 lazy.FileUtils.File(path);
+ } catch (e) {
+ throw new lazy.error.UnknownError(`Expected absolute path: ${e}`, e);
+ }
+
+ if (!file.exists()) {
+ throw new lazy.error.UnknownError(`No such file or directory: ${path}`);
+ }
+
+ try {
+ if (temporary) {
+ addon = await lazy.AddonManager.installTemporaryAddon(file);
+ } else {
+ addon = await installAddon(file);
+ }
+ } catch (e) {
+ throw new lazy.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.
+ *
+ * @returns {Promise}
+ *
+ * @throws {UnknownError}
+ * If there is a problem uninstalling the addon.
+ */
+ static async uninstall(id) {
+ let candidate = await lazy.AddonManager.getAddonByID(id);
+ if (candidate === null) {
+ // `AddonManager.getAddonByID` never rejects but instead
+ // returns `null` if the requested addon cannot be found.
+ throw new lazy.error.UnknownError(`Addon ${id} is not installed`);
+ }
+
+ return new Promise(resolve => {
+ let listener = {
+ onOperationCancelled: addon => {
+ if (addon.id === candidate.id) {
+ lazy.AddonManager.removeAddonListener(listener);
+ throw new lazy.error.UnknownError(
+ `Uninstall of ${candidate.id} has been canceled`
+ );
+ }
+ },
+
+ onUninstalled: addon => {
+ if (addon.id === candidate.id) {
+ lazy.AddonManager.removeAddonListener(listener);
+ resolve();
+ }
+ },
+ };
+
+ lazy.AddonManager.addAddonListener(listener);
+ candidate.uninstall();
+ });
+ }
+}