tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Addon.sys.mjs (7390B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
      9  ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.sys.mjs",
     10  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
     11 
     12  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
     13  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
     14  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
     15 });
     16 
     17 // from https://developer.mozilla.org/en-US/Add-ons/Add-on_Manager/AddonManager#AddonInstall_errors
     18 const ERRORS = {
     19  [-1]: "ERROR_NETWORK_FAILURE: A network error occurred.",
     20  [-2]: "ERROR_INCORRECT_HASH: The downloaded file did not match the expected hash.",
     21  [-3]: "ERROR_CORRUPT_FILE: The file appears to be corrupt.",
     22  [-4]: "ERROR_FILE_ACCESS: There was an error accessing the filesystem.",
     23  [-5]: "ERROR_SIGNEDSTATE_REQUIRED: The addon must be signed and isn't.",
     24  [-6]: "ERROR_UNEXPECTED_ADDON_TYPE: The downloaded add-on had a different type than expected (during an update).",
     25  [-7]: "ERROR_INCORRECT_ID: The addon did not have the expected ID (during an update).",
     26  [-8]: "ERROR_INVALID_DOMAIN: The addon install_origins does not list the 3rd party domain.",
     27  [-9]: "ERROR_UNEXPECTED_ADDON_VERSION: The downloaded add-on had a different version than expected (during an update).",
     28  [-10]: "ERROR_BLOCKLISTED: The add-on is blocklisted.",
     29  [-11]:
     30    "ERROR_INCOMPATIBLE: The add-on is incompatible (w.r.t. the compatibility range).",
     31  [-12]:
     32    "ERROR_UNSUPPORTED_ADDON_TYPE: The add-on type is not supported by the platform.",
     33 };
     34 
     35 async function installAddon(file, temporary, allowPrivateBrowsing) {
     36  let addon;
     37  try {
     38    if (temporary) {
     39      addon = await lazy.AddonManager.installTemporaryAddon(file);
     40    } else {
     41      const install = await lazy.AddonManager.getInstallForFile(file, null, {
     42        source: "internal",
     43      });
     44 
     45      if (install == null) {
     46        throw new lazy.error.UnknownError("Unknown error");
     47      }
     48 
     49      try {
     50        addon = await install.install();
     51      } catch {
     52        throw new lazy.error.UnknownError(ERRORS[install.error]);
     53      }
     54    }
     55  } catch (e) {
     56    throw new lazy.error.InvalidWebExtensionError(
     57      `Could not install Add-on: ${e.message}`
     58    );
     59  }
     60 
     61  if (allowPrivateBrowsing) {
     62    const perms = {
     63      permissions: ["internal:privateBrowsingAllowed"],
     64      origins: [],
     65    };
     66    await lazy.ExtensionPermissions.add(addon.id, perms);
     67    await addon.reload();
     68  }
     69 
     70  return addon;
     71 }
     72 
     73 /** Installs addons by path and uninstalls by ID. */
     74 export class Addon {
     75  /**
     76   * Install a Firefox addon with provided base64 string representation.
     77   *
     78   * Temporary addons will automatically be uninstalled on shutdown and
     79   * do not need to be signed, though they must be restartless.
     80   *
     81   * @param {string} base64
     82   *     Base64 string representation of the extension package archive.
     83   * @param {boolean=} temporary
     84   *     True to install the addon temporarily, false (default) otherwise.
     85   * @param {boolean=} allowPrivateBrowsing
     86   *     True to install the addon that is enabled in Private Browsing mode,
     87   *     false (default) otherwise.
     88   *
     89   * @returns {Promise.<string>}
     90   *     Addon ID.
     91   *
     92   * @throws {UnknownError}
     93   *     If there is a problem installing the addon.
     94   */
     95  static async installWithBase64(base64, temporary, allowPrivateBrowsing) {
     96    const decodedString = atob(base64);
     97    const fileContent = Uint8Array.from(decodedString, m => m.codePointAt(0));
     98 
     99    let path;
    100    try {
    101      path = PathUtils.join(
    102        PathUtils.profileDir,
    103        `addon-test-${lazy.generateUUID()}.xpi`
    104      );
    105      await IOUtils.write(path, fileContent);
    106    } catch (e) {
    107      throw new lazy.error.UnknownError(
    108        `Could not write add-on to file: ${e.message}`,
    109        e
    110      );
    111    }
    112 
    113    let addon;
    114    try {
    115      const file = new lazy.FileUtils.File(path);
    116      addon = await installAddon(file, temporary, allowPrivateBrowsing);
    117    } finally {
    118      await IOUtils.remove(path);
    119    }
    120 
    121    return addon.id;
    122  }
    123 
    124  /**
    125   * Install a Firefox addon with provided path.
    126   *
    127   * Temporary addons will automatically be uninstalled on shutdown and
    128   * do not need to be signed, though they must be restartless.
    129   *
    130   * @param {string} path
    131   *     Full path to the extension package archive.
    132   * @param {boolean=} temporary
    133   *     True to install the addon temporarily, false (default) otherwise.
    134   * @param {boolean=} allowPrivateBrowsing
    135   *     True to install the addon that is enabled in Private Browsing mode,
    136   *     false (default) otherwise.
    137   *
    138   * @returns {Promise.<string>}
    139   *     Addon ID.
    140   *
    141   * @throws {UnknownError}
    142   *     If there is a problem installing the addon.
    143   */
    144  static async installWithPath(path, temporary, allowPrivateBrowsing) {
    145    let file;
    146 
    147    // On Windows we can end up with a path with mixed \ and /
    148    // which doesn't work in Firefox.
    149    if (lazy.AppInfo.isWindows) {
    150      path = path.replace(/\//g, "\\");
    151    }
    152 
    153    try {
    154      file = new lazy.FileUtils.File(path);
    155    } catch (e) {
    156      throw new lazy.error.UnknownError(`Expected absolute path: ${e}`, e);
    157    }
    158 
    159    if (!file.exists()) {
    160      throw new lazy.error.UnknownError(`No such file or directory: ${path}`);
    161    }
    162 
    163    const addon = await installAddon(file, temporary, allowPrivateBrowsing);
    164 
    165    return addon.id;
    166  }
    167 
    168  /**
    169   * Uninstall a Firefox addon.
    170   *
    171   * If the addon is restartless it will be uninstalled right away.
    172   * Otherwise, Firefox must be restarted for the change to take effect.
    173   *
    174   * @param {string} id
    175   *     ID of the addon to uninstall.
    176   *
    177   * @returns {Promise}
    178   *
    179   * @throws {UnknownError}
    180   *     If there is a problem uninstalling the addon.
    181   * @throws {NoSuchWebExtensionError}
    182   *     Raised if the WebExtension with provided id could not be found.
    183   */
    184  static async uninstall(id) {
    185    let candidate = await lazy.AddonManager.getAddonByID(id);
    186    if (candidate === null) {
    187      // `AddonManager.getAddonByID` never rejects but instead
    188      // returns `null` if the requested addon cannot be found.
    189      throw new lazy.error.NoSuchWebExtensionError(
    190        `Add-on with ID "${id}" is not installed.`
    191      );
    192    }
    193 
    194    return new Promise((resolve, reject) => {
    195      let listener = {
    196        onOperationCancelled: addon => {
    197          if (addon.id === candidate.id) {
    198            lazy.AddonManager.removeAddonListener(listener);
    199            throw new lazy.error.UnknownError(
    200              `Uninstall of Add-on with ID "${candidate.id}" was canceled.`
    201            );
    202          }
    203        },
    204 
    205        onUninstalled: addon => {
    206          if (addon.id === candidate.id) {
    207            lazy.AddonManager.removeAddonListener(listener);
    208            resolve();
    209          }
    210        },
    211      };
    212 
    213      lazy.AddonManager.addAddonListener(listener);
    214      candidate.uninstall().catch(e => {
    215        lazy.AddonManager.removeAddonListener(listener);
    216        reject(
    217          new lazy.error.UnknownError(
    218            `Failed to uninstall Add-on with ID "${id}": ${e.message}`
    219          )
    220        );
    221      });
    222    });
    223  }
    224 }