--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -1,14 +1,16 @@
/* 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";
+/* eslint "valid-jsdoc": [2, {requireReturn: false, requireReturnDescription: false, prefer: {return: "returns"}}] */
+
var EXPORTED_SYMBOLS = ["XPIProvider", "XPIInternal"];
/* globals WebExtensionPolicy */
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
@@ -423,43 +425,46 @@ function findMatchingStaticBlocklistItem
}
return null;
}
/**
* Helper function that determines whether an addon of a certain type is a
* WebExtension.
*
- * @param {String} type
- * @return {Boolean}
+ * @param {string} type
+ * The add-on type to check.
+ * @returns {boolean}
*/
function isWebExtension(type) {
return type == "webextension" || type == "webextension-theme";
}
var gThemeAliases = null;
/**
* Helper function that determines whether an addon of a certain type is a
* theme.
*
- * @param {String} type
- * @return {Boolean}
+ * @param {string} type
+ * The add-on type to check.
+ * @returns {boolean}
*/
function isTheme(type) {
if (!gThemeAliases)
gThemeAliases = getAllAliasesForTypes(["theme"]);
return gThemeAliases.includes(type);
}
/**
* Evaluates whether an add-on is allowed to run in safe mode.
*
- * @param aAddon
- * The add-on to check
- * @return true if the add-on should run in safe mode
+ * @param {AddonInternal} aAddon
+ * The add-on to check
+ * @returns {boolean}
+ * True if the add-on should run in safe mode
*/
function canRunInSafeMode(aAddon) {
// Even though the updated system add-ons aren't generally run in safe mode we
// include them here so their uninstall functions get called when switching
// back to the default set.
// TODO product should make the call about temporary add-ons running
// in safe mode. assuming for now that they are.
@@ -495,19 +500,20 @@ function isDisabledLegacy(addon) {
// Properly signed legacy extensions are allowed.
addon.signedState !== AddonManager.SIGNEDSTATE_PRIVILEGED);
}
/**
* Calculates whether an add-on should be appDisabled or not.
*
- * @param aAddon
- * The add-on to check
- * @return true if the add-on should not be appDisabled
+ * @param {AddonInternal} aAddon
+ * The add-on to check
+ * @returns {boolean}
+ * True if the add-on should not be appDisabled
*/
function isUsableAddon(aAddon) {
if (mustSign(aAddon.type) && !aAddon.isCorrectlySigned) {
logger.warn(`Add-on ${aAddon.id} is not correctly signed.`);
if (Services.prefs.getBoolPref(PREF_XPI_SIGNATURES_DEV_ROOT, false)) {
logger.warn(`Preference ${PREF_XPI_SIGNATURES_DEV_ROOT} is set.`);
}
return false;
@@ -563,19 +569,20 @@ function isUsableAddon(aAddon) {
}
return true;
}
/**
* Converts an internal add-on type to the type presented through the API.
*
- * @param aType
- * The internal add-on type
- * @return an external add-on type
+ * @param {string} aType
+ * The internal add-on type
+ * @returns {string}
+ * An external add-on type
*/
function getExternalType(aType) {
if (aType in TYPE_ALIASES)
return TYPE_ALIASES[aType];
return aType;
}
function getManifestFileForDir(aDir) {
@@ -587,19 +594,20 @@ function getManifestFileForDir(aDir) {
return file;
return null;
}
/**
* Converts a list of API types to a list of API types and any aliases for those
* types.
*
- * @param aTypes
- * An array of types or null for all types
- * @return an array of types or null for all types
+ * @param {Array<string>?} aTypes
+ * An array of types or null for all types
+ * @returns {Array<string>?}
+ * An array of types or null for all types
*/
function getAllAliasesForTypes(aTypes) {
if (!aTypes)
return null;
// Build a set of all requested types and their aliases
let typeset = new Set(aTypes);
@@ -615,59 +623,62 @@ function getAllAliasesForTypes(aTypes) {
return [...typeset];
}
/**
* Gets an nsIURI for a file within another file, either a directory or an XPI
* file. If aFile is a directory then this will return a file: URI, if it is an
* XPI file then it will return a jar: URI.
*
- * @param aFile
- * The file containing the resources, must be either a directory or an
- * XPI file
- * @param aPath
- * The path to find the resource at, "/" separated. If aPath is empty
- * then the uri to the root of the contained files will be returned
- * @return an nsIURI pointing at the resource
+ * @param {nsIFile} aFile
+ * The file containing the resources, must be either a directory or an
+ * XPI file
+ * @param {string} aPath
+ * The path to find the resource at, "/" separated. If aPath is empty
+ * then the uri to the root of the contained files will be returned
+ * @returns {nsIURI}
+ * An nsIURI pointing at the resource
*/
function getURIForResourceInFile(aFile, aPath) {
if (aFile.exists() && aFile.isDirectory()) {
let resource = aFile.clone();
if (aPath)
aPath.split("/").forEach(part => resource.append(part));
return Services.io.newFileURI(resource);
}
return buildJarURI(aFile, aPath);
}
/**
* Creates a jar: URI for a file inside a ZIP file.
*
- * @param aJarfile
- * The ZIP file as an nsIFile
- * @param aPath
- * The path inside the ZIP file
- * @return an nsIURI for the file
+ * @param {nsIFile} aJarfile
+ * The ZIP file as an nsIFile
+ * @param {string} aPath
+ * The path inside the ZIP file
+ * @returns {nsIURI}
+ * An nsIURI for the file
*/
function buildJarURI(aJarfile, aPath) {
let uri = Services.io.newFileURI(aJarfile);
uri = "jar:" + uri.spec + "!/" + aPath;
return Services.io.newURI(uri);
}
/**
* Gets a snapshot of directory entries.
*
- * @param aDir
- * Directory to look at
- * @param aSortEntries
- * True to sort entries by filename
- * @return An array of nsIFile, or an empty array if aDir is not a readable directory
+ * @param {nsIURI} aDir
+ * Directory to look at
+ * @param {boolean} aSortEntries
+ * True to sort entries by filename
+ * @returns {Array<nsIFile>}
+ * An array of nsIFile, or an empty array if aDir is not a readable directory
*/
function getDirectoryEntries(aDir, aSortEntries) {
let dirEnum;
try {
dirEnum = aDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
let entries = [];
while (dirEnum.hasMoreElements())
entries.push(dirEnum.nextFile);
@@ -687,18 +698,23 @@ function getDirectoryEntries(aDir, aSort
} finally {
if (dirEnum) {
dirEnum.close();
}
}
}
/**
- * Record a bit of per-addon telemetry
- * @param aAddon the addon to record
+ * Record a bit of per-addon telemetry.
+ *
+ * Yes, this description is extremely helpful. How dare you question its
+ * utility?
+ *
+ * @param {AddonInternal} aAddon
+ * The addon to record
*/
function recordAddonTelemetry(aAddon) {
let locale = aAddon.defaultLocale;
if (locale) {
if (locale.name)
XPIProvider.setTelemetry(aAddon.id, "name", locale.name);
if (locale.creator)
XPIProvider.setTelemetry(aAddon.id, "creator", locale.creator);
@@ -756,21 +772,22 @@ class XPIState {
/**
* Migrates an add-on's data from xpiState and bootstrappedAddons
* preferences, and returns an XPIState object for it.
*
* @param {XPIStateLocation} location
* The location of the add-on.
* @param {string} id
* The ID of the add-on to migrate.
- * @param {object} state
+ * @param {object} saved
* The add-on's data from the xpiState preference.
* @param {object} [bootstrapped]
* The add-on's data from the bootstrappedAddons preference, if
* applicable.
+ * @returns {XPIState}
*/
static migrate(location, id, saved, bootstrapped) {
let data = {
enabled: saved.e,
path: descriptorToPath(saved.d, location.dir),
lastModifiedTime: saved.mt || saved.st,
version: saved.v,
};
@@ -824,16 +841,18 @@ class XPIState {
return path;
}
return this.path;
}
/**
* Returns a JSON-compatible representation of this add-on's state
* data, to be saved to addonStartup.json.
+ *
+ * @returns {Object}
*/
toJSON() {
let json = {
enabled: this.enabled,
lastModifiedTime: this.lastModifiedTime,
path: this.relativePath,
version: this.version,
};
@@ -849,19 +868,23 @@ class XPIState {
if (this.startupData) {
json.startupData = this.startupData;
}
return json;
}
/**
* Update the last modified time for an add-on on disk.
- * @param aFile: nsIFile path of the add-on.
- * @param aId: The add-on ID.
- * @return True if the time stamp has changed.
+ *
+ * @param {nsIFile} aFile
+ * The location of the add-on.
+ * @param {string} aId
+ * The add-on ID.
+ * @returns {boolean}
+ * True if the time stamp has changed.
*/
getModTime(aFile, aId) {
// Modified time is the install manifest time, if any. If no manifest
// exists, we assume this is a packed .xpi and use the time stamp of
// {path}
let mtime = (tryGetMtime(getManifestFileForDir(aFile)) ||
tryGetMtime(aFile));
if (!mtime) {
@@ -872,19 +895,23 @@ class XPIState {
this.lastModifiedTime = mtime;
return this.changed;
}
/**
* Update the XPIState to match an XPIDatabase entry; if 'enabled' is changed to true,
* update the last-modified time. This should probably be made async, but for now we
* don't want to maintain parallel sync and async versions of the scan.
+ *
* Caller is responsible for doing XPIStates.save() if necessary.
- * @param aDBAddon The DBAddonInternal for this add-on.
- * @param aUpdated The add-on was updated, so we must record new modified time.
+ *
+ * @param {DBAddonInternal} aDBAddon
+ * The DBAddonInternal for this add-on.
+ * @param {boolean} [aUpdated = false]
+ * The add-on was updated, so we must record new modified time.
*/
syncWithDB(aDBAddon, aUpdated = false) {
logger.debug("Updating XPIState for " + JSON.stringify(aDBAddon));
// If the add-on changes from disabled to enabled, we should re-check the modified time.
// If this is a newly found add-on, it won't have an 'enabled' field but we
// did a full recursive scan in that case, so we don't need to do it again.
// We don't use aDBAddon.active here because it's not updated until after restart.
let mustGetMod = (aDBAddon.visible && !aDBAddon.disabled && !this.enabled);
@@ -942,16 +969,18 @@ class XPIStateLocation extends Map {
// Make a note that this state was restored from saved data.
xpiState.wasRestored = true;
}
}
/**
* Returns a JSON-compatible representation of this location's state
* data, to be saved to addonStartup.json.
+ *
+ * @returns {Object}
*/
toJSON() {
let json = {
addons: {},
staged: this.staged,
};
if (this.path) {
@@ -1137,16 +1166,18 @@ var XPIStates = {
logger.debug("Migrated data: ${}", data);
return data;
},
/**
* Load extension state data from addonStartup.json, or migrates it
* from legacy state preferences, if they exist.
+ *
+ * @returns {Object}
*/
loadExtensionState() {
let state;
try {
state = aomStartup.readStartupData();
} catch (e) {
logger.warn("Error parsing extensions state: ${error}",
{error: e});
@@ -1165,21 +1196,22 @@ var XPIStates = {
logger.debug("Loaded add-on state: ${}", state);
return state || {};
},
/**
* Walk through all install locations, highest priority first,
* comparing the on-disk state of extensions to what is stored in prefs.
*
- * @param {bool} [ignoreSideloads = true]
+ * @param {boolean} [ignoreSideloads = true]
* If true, ignore changes in scopes where we don't accept
* side-loads.
*
- * @return true if anything has changed.
+ * @returns {boolean}
+ * True if anything has changed.
*/
getInstallState(ignoreSideloads = true) {
if (!this.db) {
this.db = new Map();
}
let oldState = this.initialStateData || this.loadExtensionState();
this.initialStateData = oldState;
@@ -1245,18 +1277,27 @@ var XPIStates = {
logger.debug("getInstallState changed: ${rv}, state: ${state}",
{rv: changed, state: this.db});
return changed;
},
/**
* Get the Map of XPI states for a particular location.
- * @param name The name of the install location.
- * @return XPIStateLocation (id -> XPIState) or null if there are no add-ons in the location.
+ *
+ * @param {string} name
+ * The name of the install location.
+ * @param {string?} [path]
+ * The expected path of the location, if known.
+ * @param {Object?} [saved]
+ * The saved data for the location, as read from the
+ * addonStartup.json file.
+ *
+ * @returns {XPIStateLocation?}
+ * (id -> XPIState) or null if there are no add-ons in the location.
*/
getLocation(name, path, saved) {
let location = this.db.get(name);
if (path && location && location.path != path) {
location = null;
saved = null;
}
@@ -1269,35 +1310,40 @@ var XPIStates = {
}
}
return location;
},
/**
* Get the XPI state for a specific add-on in a location.
* If the state is not in our cache, return null.
- * @param aLocation The name of the location where the add-on is installed.
- * @param aId The add-on ID
- * @return The XPIState entry for the add-on, or null.
+ *
+ * @param {string} aLocation
+ * The name of the location where the add-on is installed.
+ * @param {string} aId
+ * The add-on ID
+ *
+ * @returns {XPIState?}
+ * The XPIState entry for the add-on, or null.
*/
getAddon(aLocation, aId) {
let location = this.db.get(aLocation);
return location && location.get(aId);
},
/**
* Find the highest priority location of an add-on by ID and return the
* XPIState.
* @param {string} aId
* The add-on IDa
* @param {function} aFilter
* An optional filter to apply to install locations. If provided,
* addons in locations that do not match the filter are not considered.
*
- * @return {XPIState?}
+ * @returns {XPIState?}
*/
findAddon(aId, aFilter = location => true) {
// Fortunately the Map iterator returns in order of insertion, which is
// also our highest -> lowest priority order.
for (let location of this.db.values()) {
if (!aFilter(location)) {
continue;
}
@@ -1341,17 +1387,19 @@ var XPIStates = {
if (addon.bootstrapped) {
yield addon;
}
}
},
/**
* Add a new XPIState for an add-on and synchronize it with the DBAddonInternal.
- * @param aAddon DBAddonInternal for the new add-on.
+ *
+ * @param {DBAddonInternal} aAddon
+ * The add-on to add.
*/
addAddon(aAddon) {
let location = this.getLocation(aAddon._installLocation.name);
location.addAddon(aAddon);
},
/**
* Save the current state of installed add-ons.
@@ -1376,33 +1424,40 @@ var XPIStates = {
data[key] = loc;
}
}
return data;
},
/**
* Remove the XPIState for an add-on and save the new state.
- * @param aLocation The name of the add-on location.
- * @param aId The ID of the add-on.
+ *
+ * @param {string} aLocation
+ * The name of the add-on location.
+ * @param {string} aId
+ * The ID of the add-on.
+ *
*/
removeAddon(aLocation, aId) {
logger.debug("Removing XPIState for " + aLocation + ":" + aId);
let location = this.db.get(aLocation);
if (location) {
location.delete(aId);
if (location.size == 0) {
this.db.delete(aLocation);
}
this.save();
}
},
/**
* Disable the XPIState for an add-on.
+ *
+ * @param {string} aId
+ * The ID of the add-on.
*/
disableAddon(aId) {
logger.debug(`Disabling XPIState for ${aId}`);
let state = this.findAddon(aId);
if (state) {
state.enabled = false;
}
},
@@ -1543,28 +1598,28 @@ var XPIProvider = {
logger.warn("Cancel failed", e);
}
}
},
/**
* Starts the XPI provider initializes the install locations and prefs.
*
- * @param aAppChanged
- * A tri-state value. Undefined means the current profile was created
- * for this session, true means the profile already existed but was
- * last used with an application with a different version number,
- * false means that the profile was last used by this version of the
- * application.
- * @param aOldAppVersion
- * The version of the application last run with this profile or null
- * if it is a new profile or the version is unknown
- * @param aOldPlatformVersion
- * The version of the platform last run with this profile or null
- * if it is a new profile or the version is unknown
+ * @param {boolean?} aAppChanged
+ * A tri-state value. Undefined means the current profile was created
+ * for this session, true means the profile already existed but was
+ * last used with an application with a different version number,
+ * false means that the profile was last used by this version of the
+ * application.
+ * @param {string?} [aOldAppVersion]
+ * The version of the application last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @param {string?} [aOldPlatformVersion]
+ * The version of the platform last run with this profile or null
+ * if it is a new profile or the version is unknown
*/
startup(aAppChanged, aOldAppVersion, aOldPlatformVersion) {
function addDirectoryInstallLocation(aName, aKey, aPaths, aScope, aLocked) {
try {
var dir = FileUtils.getDir(aKey, aPaths);
} catch (e) {
// Some directories aren't defined on some platforms, ignore them
logger.debug("Skipping unavailable install location " + aName);
@@ -2021,20 +2076,21 @@ var XPIProvider = {
ChromeUtils.import("resource://gre/modules/TelemetrySession.jsm", {}).TelemetrySession;
TelemetrySession.setAddOns(data);
},
/**
* Check the staging directories of install locations for any add-ons to be
* installed or add-ons to be uninstalled.
*
- * @param aManifests
+ * @param {Object} aManifests
* A dictionary to add detected install manifests to for the purpose
* of passing through updated compatibility information
- * @return true if an add-on was installed or uninstalled
+ * @returns {boolean}
+ * True if an add-on was installed or uninstalled
*/
processPendingFileChanges(aManifests) {
let changed = false;
for (let location of this.installLocations) {
aManifests[location.name] = {};
// We can't install or uninstall anything in locked locations
if (location.locked) {
continue;
@@ -2080,22 +2136,23 @@ var XPIProvider = {
},
/**
* Installs any add-ons located in the extensions directory of the
* application's distribution specific directory into the profile unless a
* newer version already exists or the user has previously uninstalled the
* distributed add-on.
*
- * @param aManifests
- * A dictionary to add new install manifests to to save having to
- * reload them later
- * @param aAppChanged
- * See checkForChanges
- * @return true if any new add-ons were installed
+ * @param {Object} aManifests
+ * A dictionary to add new install manifests to to save having to
+ * reload them later
+ * @param {string} [aAppChanged]
+ * See checkForChanges
+ * @returns {boolean}
+ * True if any new add-ons were installed
*/
installDistributionAddons(aManifests, aAppChanged) {
let distroDir;
try {
distroDir = FileUtils.getDir(KEY_APP_DISTRIBUTION, [DIR_EXTENSIONS]);
} catch (e) {
return false;
}
@@ -2182,29 +2239,30 @@ var XPIProvider = {
return Array.from(XPIDatabase.getAddons())
.filter(addon => addon.dependencies.includes(aAddon.id));
},
/**
* Checks for any changes that have occurred since the last time the
* application was launched.
*
- * @param aAppChanged
- * A tri-state value. Undefined means the current profile was created
- * for this session, true means the profile already existed but was
- * last used with an application with a different version number,
- * false means that the profile was last used by this version of the
- * application.
- * @param aOldAppVersion
- * The version of the application last run with this profile or null
- * if it is a new profile or the version is unknown
- * @param aOldPlatformVersion
- * The version of the platform last run with this profile or null
- * if it is a new profile or the version is unknown
- * @return true if a change requiring a restart was detected
+ * @param {boolean?} [aAppChanged]
+ * A tri-state value. Undefined means the current profile was created
+ * for this session, true means the profile already existed but was
+ * last used with an application with a different version number,
+ * false means that the profile was last used by this version of the
+ * application.
+ * @param {string?} [aOldAppVersion]
+ * The version of the application last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @param {string?} [aOldPlatformVersion]
+ * The version of the platform last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @returns {boolean}
+ * True if a change requiring a restart was detected
*/
checkForChanges(aAppChanged, aOldAppVersion, aOldPlatformVersion) {
logger.debug("checkForChanges");
// Keep track of whether and why we need to open and update the database at
// startup time.
let updateReasons = [];
if (aAppChanged) {
@@ -2325,19 +2383,20 @@ var XPIProvider = {
return addons.filter(addon => (addon.seen === false &&
addon.permissions & AddonManager.PERM_CAN_ENABLE));
},
/**
* Called to test whether this provider supports installing a particular
* mimetype.
*
- * @param aMimetype
- * The mimetype to check for
- * @return true if the mimetype is application/x-xpinstall
+ * @param {string} aMimetype
+ * The mimetype to check for
+ * @returns {boolean}
+ * True if the mimetype is application/x-xpinstall
*/
supportsMimetype(aMimetype) {
return aMimetype == "application/x-xpinstall";
},
// Identify temporary install IDs.
isTemporaryInstallID(id) {
return id.endsWith(TEMPORARY_ADDON_SUFFIX);
@@ -2357,22 +2416,23 @@ var XPIProvider = {
setStartupData(aID, aData) {
let state = XPIStates.findAddon(aID);
state.startupData = aData;
XPIStates.save();
},
/**
* Returns an Addon corresponding to an instance ID.
- * @param aInstanceID
+ *
+ * @param {Symbol} aInstanceID
* An Addon Instance ID
- * @return {Promise}
- * @resolves The found Addon or null if no such add-on exists.
- * @rejects Never
- * @throws if the aInstanceID argument is not specified
+ *
+ * @returns {AddonInternal?}
+ *
+ * @throws if the aInstanceID argument is not valid.
*/
getAddonByInstanceID(aInstanceID) {
let id = this.getAddonIDByInstanceID(aInstanceID);
if (id) {
return this.syncGetAddonByID(id);
}
return null;
@@ -2390,28 +2450,29 @@ var XPIProvider = {
}
return null;
},
/**
* Removes an AddonInstall from the list of active installs.
*
- * @param install
- * The AddonInstall to remove
+ * @param {AddonInstall} aInstall
+ * The AddonInstall to remove
*/
removeActiveInstall(aInstall) {
this.installs.delete(aInstall);
},
/**
* Called to get an Addon with a particular ID.
*
- * @param aId
- * The ID of the add-on to retrieve
+ * @param {string} aId
+ * The ID of the add-on to retrieve
+ * @returns {Addon?}
*/
async getAddonByID(aId) {
let aAddon = await XPIDatabase.getVisibleAddonForID(aId);
return aAddon ? aAddon.wrapper : null;
},
/**
* Synchronously returns the Addon object for the add-on with the
@@ -2431,34 +2492,35 @@ var XPIProvider = {
syncGetAddonByID(aId) {
let aAddon = XPIDatabase.syncGetVisibleAddonForID(aId);
return aAddon ? aAddon.wrapper : null;
},
/**
* Called to get Addons of a particular type.
*
- * @param aTypes
- * An array of types to fetch. Can be null to get all types.
+ * @param {Array<string>?} aTypes
+ * An array of types to fetch. Can be null to get all types.
+ * @returns {Addon[]}
*/
async getAddonsByTypes(aTypes) {
let typesToGet = getAllAliasesForTypes(aTypes);
if (typesToGet && !typesToGet.some(type => ALL_EXTERNAL_TYPES.has(type))) {
return [];
}
let addons = await XPIDatabase.getVisibleAddons(typesToGet);
return addons.map(a => a.wrapper);
},
/**
* Called to get active Addons of a particular type
*
- * @param aTypes
- * An array of types to fetch. Can be null to get all types.
+ * @param {Array<string>?} aTypes
+ * An array of types to fetch. Can be null to get all types.
* @returns {Promise<Array<Addon>>}
*/
async getActiveAddons(aTypes) {
// If we already have the database loaded, returning full info is fast.
if (this.isDBLoaded) {
let addons = await this.getAddonsByTypes(aTypes);
return {
addons: addons.filter(addon => addon.isActive),
@@ -2495,29 +2557,31 @@ var XPIProvider = {
return {addons: result, fullData: false};
},
/**
* Obtain an Addon having the specified Sync GUID.
*
- * @param aGUID
- * String GUID of add-on to retrieve
+ * @param {string} aGUID
+ * String GUID of add-on to retrieve
+ * @returns {Addon?}
*/
async getAddonBySyncGUID(aGUID) {
let addon = await XPIDatabase.getAddonBySyncGUID(aGUID);
return addon ? addon.wrapper : null;
},
/**
* Called to get Addons that have pending operations.
*
- * @param aTypes
- * An array of types to fetch. Can be null to get all types
+ * @param {Array<string>?} aTypes
+ * An array of types to fetch. Can be null to get all types
+ * @returns {Addon[]}
*/
async getAddonsWithOperationsByTypes(aTypes) {
let typesToGet = getAllAliasesForTypes(aTypes);
let aAddons = await XPIDatabase.getVisibleAddonsWithPendingOperations(typesToGet);
let results = aAddons.map(a => a.wrapper);
for (let install of XPIProvider.installs) {
if (install.state == AddonManager.STATE_INSTALLED &&
@@ -2526,38 +2590,39 @@ var XPIProvider = {
}
return results;
},
/**
* Called to get the current AddonInstalls, optionally limiting to a list of
* types.
*
- * @param aTypes
- * An array of types or null to get all types
+ * @param {Array<string>?} aTypes
+ * An array of types or null to get all types
+ * @returns {AddonInstall[]}
*/
getInstallsByTypes(aTypes) {
let results = [...this.installs];
if (aTypes) {
results = results.filter(install => {
return aTypes.includes(getExternalType(install.type));
});
}
return results.map(install => install.wrapper);
},
/**
* Called when a new add-on has been enabled when only one add-on of that type
* can be enabled.
*
- * @param aId
- * The ID of the newly enabled add-on
- * @param aType
- * The type of the newly enabled add-on
+ * @param {string} aId
+ * The ID of the newly enabled add-on
+ * @param {string} aType
+ * The type of the newly enabled add-on
*/
addonChanged(aId, aType) {
// We only care about themes in this provider
if (!isTheme(aType))
return;
let addons = XPIDatabase.getAddonsByType("webextension-theme");
for (let theme of addons) {
@@ -2608,17 +2673,17 @@ var XPIProvider = {
return;
for (let [id, val] of this.activeAddons) {
connection.setAddonOptions(
id, { global: val.bootstrapScope });
}
},
- /**
+ /*
* Notified when a preference we're interested in has changed.
*
* @see nsIObserver
*/
observe(aSubject, aTopic, aData) {
if (aTopic == NOTIFICATION_FLUSH_PERMISSIONS) {
if (!aData || aData == XPI_PERMISSION) {
this.importPermissions();
@@ -2651,31 +2716,30 @@ var XPIProvider = {
},
/**
* Loads a bootstrapped add-on's bootstrap.js into a sandbox and the reason
* values as constants in the scope. This will also add information about the
* add-on to the bootstrappedAddons dictionary and notify the crash reporter
* that new add-ons have been loaded.
*
- * @param aId
- * The add-on's ID
- * @param aFile
- * The nsIFile for the add-on
- * @param aVersion
- * The add-on's version
- * @param aType
- * The type for the add-on
- * @param aRunInSafeMode
- * Boolean indicating whether the add-on can run in safe mode.
- * @param aDependencies
- * An array of add-on IDs on which this add-on depends.
- * @param hasEmbeddedWebExtension
- * Boolean indicating whether the add-on has an embedded webextension.
- * @return a JavaScript scope
+ * @param {string} aId
+ * The add-on's ID
+ * @param {nsIFile} aFile
+ * The nsIFile for the add-on
+ * @param {string} aVersion
+ * The add-on's version
+ * @param {string} aType
+ * The type for the add-on
+ * @param {boolean} aRunInSafeMode
+ * Boolean indicating whether the add-on can run in safe mode.
+ * @param {string[]} aDependencies
+ * An array of add-on IDs on which this add-on depends.
+ * @param {boolean} hasEmbeddedWebExtension
+ * Boolean indicating whether the add-on has an embedded webextension.
*/
loadBootstrapScope(aId, aFile, aVersion, aType, aRunInSafeMode, aDependencies,
hasEmbeddedWebExtension) {
this.activeAddons.set(aId, {
bootstrapScope: null,
// a Symbol passed to this add-on, which it can use to identify itself
instanceID: Symbol(aId),
started: false,
@@ -2740,42 +2804,42 @@ var XPIProvider = {
let wrappedJSObject = { id: aId, options: { global: activeAddon.bootstrapScope }};
Services.obs.notifyObservers({ wrappedJSObject }, "toolbox-update-addon-options");
},
/**
* Unloads a bootstrap scope by dropping all references to it and then
* updating the list of active add-ons with the crash reporter.
*
- * @param aId
- * The add-on's ID
+ * @param {string} aId
+ * The add-on's ID
*/
unloadBootstrapScope(aId) {
this.activeAddons.delete(aId);
this.addAddonsToCrashReporter();
// Notify the BrowserToolboxProcess that an addon has been unloaded.
let wrappedJSObject = { id: aId, options: { global: null }};
Services.obs.notifyObservers({ wrappedJSObject }, "toolbox-update-addon-options");
},
/**
* Calls a bootstrap method for an add-on.
*
- * @param aAddon
- * An object representing the add-on, with `id`, `type` and `version`
- * @param aFile
- * The nsIFile for the add-on
- * @param aMethod
- * The name of the bootstrap method to call
- * @param aReason
- * The reason flag to pass to the bootstrap's startup method
- * @param aExtraParams
- * An object of additional key/value pairs to pass to the method in
- * the params argument
+ * @param {Object} aAddon
+ * An object representing the add-on, with `id`, `type` and `version`
+ * @param {nsIFile} aFile
+ * The nsIFile for the add-on
+ * @param {string} aMethod
+ * The name of the bootstrap method to call
+ * @param {integer} aReason
+ * The reason flag to pass to the bootstrap's startup method
+ * @param {Object?} [aExtraParams]
+ * An object of additional key/value pairs to pass to the method in
+ * the params argument
*/
callBootstrapMethod(aAddon, aFile, aMethod, aReason, aExtraParams) {
if (!aAddon.id || !aAddon.version || !aAddon.type) {
throw new Error("aAddon must include an id, version, and type");
}
// Only run in safe mode if allowed to
let runInSafeMode = "runInSafeMode" in aAddon ? aAddon.runInSafeMode : canRunInSafeMode(aAddon);
@@ -2897,28 +2961,29 @@ var XPIProvider = {
}
},
/**
* Updates the disabled state for an add-on. Its appDisabled property will be
* calculated and if the add-on is changed the database will be saved and
* appropriate notifications will be sent out to the registered AddonListeners.
*
- * @param aAddon
- * The DBAddonInternal to update
- * @param aUserDisabled
- * Value for the userDisabled property. If undefined the value will
- * not change
- * @param aSoftDisabled
- * Value for the softDisabled property. If undefined the value will
- * not change. If true this will force userDisabled to be true
- * @param {boolean} aBecauseSelecting
+ * @param {DBAddonInternal} aAddon
+ * The DBAddonInternal to update
+ * @param {boolean?} [aUserDisabled]
+ * Value for the userDisabled property. If undefined the value will
+ * not change
+ * @param {boolean?} [aSoftDisabled]
+ * Value for the softDisabled property. If undefined the value will
+ * not change. If true this will force userDisabled to be true
+ * @param {boolean?} [aBecauseSelecting]
* True if we're disabling this add-on because we're selecting
* another.
- * @return a tri-state indicating the action taken for the add-on:
+ * @returns {boolean?}
+ * A tri-state indicating the action taken for the add-on:
* - undefined: The add-on did not change state
* - true: The add-on because disabled
* - false: The add-on became enabled
* @throws if addon is not a DBAddonInternal
*/
updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled, aBecauseSelecting) {
if (!(aAddon.inDatabase))
throw new Error("Can only update addon states for installed addons.");
@@ -3354,24 +3419,21 @@ AddonInternal.prototype = {
},
/**
* toJSON is called by JSON.stringify in order to create a filtered version
* of this object to be serialized to a JSON file. A new object is returned
* with copies of all non-private properties. Functions, getters and setters
* are not copied.
*
- * @param aKey
- * The key that this object is being serialized as in the JSON.
- * Unused here since this is always the main object serialized
- *
- * @return an object containing copies of the properties of this object
- * ignoring private properties, functions, getters and setters
+ * @returns {Object}
+ * An object containing copies of the properties of this object
+ * ignoring private properties, functions, getters and setters.
*/
- toJSON(aKey) {
+ toJSON() {
let obj = {};
for (let prop in this) {
// Ignore the wrapper property
if (prop == "wrapper")
continue;
// Ignore private properties
if (prop.substring(0, 1) == "_")
@@ -3395,18 +3457,18 @@ AddonInternal.prototype = {
return obj;
},
/**
* When an add-on install is pending its metadata will be cached in a file.
* This method reads particular properties of that metadata that may be newer
* than that in the install manifest, like compatibility information.
*
- * @param aObj
- * A JS object containing the cached metadata
+ * @param {Object} aObj
+ * A JS object containing the cached metadata
*/
importMetadata(aObj) {
for (let prop of PENDING_INSTALL_METADATA) {
if (!(prop in aObj))
continue;
this[prop] = aObj[prop];
}
@@ -3451,16 +3513,19 @@ AddonInternal.prototype = {
return permissions;
},
};
/**
* The AddonWrapper wraps an Addon to provide the data visible to consumers of
* the public API.
+ *
+ * @param {AddonInternal} aAddon
+ * The add-on object to wrap.
*/
function AddonWrapper(aAddon) {
wrapperMap.set(this, aAddon);
}
AddonWrapper.prototype = {
get __AddonInternal__() {
return AppConstants.DEBUG ? addonFor(this) : undefined;
@@ -3868,17 +3933,17 @@ AddonWrapper.prototype = {
/**
* Reloads the add-on.
*
* For temporarily installed add-ons, this uninstalls and re-installs the
* add-on. Otherwise, the addon is disabled and then re-enabled, and the cache
* is flushed.
*
- * @return Promise
+ * @returns {Promise}
*/
reload() {
return new Promise((resolve) => {
const addon = addonFor(this);
logger.debug(`reloading add-on ${addon.id}`);
if (!this.temporarilyInstalled) {
@@ -3895,20 +3960,20 @@ AddonWrapper.prototype = {
},
/**
* Returns a URI to the selected resource or to the add-on bundle if aPath
* is null. URIs to the bundle will always be file: URIs. URIs to resources
* will be file: URIs if the add-on is unpacked or jar: URIs if the add-on is
* still an XPI file.
*
- * @param aPath
- * The path in the add-on to get the URI for or null to get a URI to
- * the file or directory the add-on is installed as.
- * @return an nsIURI
+ * @param {string?} aPath
+ * The path in the add-on to get the URI for or null to get a URI to
+ * the file or directory the add-on is installed as.
+ * @returns {nsIURI}
*/
getResourceURI(aPath) {
let addon = addonFor(this);
if (!aPath)
return Services.io.newFileURI(addon._sourceBundle);
return getURIForResourceInFile(addon._sourceBundle, aPath);
}
@@ -4065,22 +4130,22 @@ function forwardInstallMethods(cls, meth
*/
class DirectoryInstallLocation {
/**
* Each add-on installed in the location is either a directory containing the
* add-on's files or a text file containing an absolute path to the directory
* containing the add-ons files. The directory or text file must have the same
* name as the add-on's ID.
*
- * @param aName
- * The string identifier for the install location
- * @param aDirectory
- * The nsIFile directory for the install location
- * @param aScope
- * The scope of add-ons installed in this location
+ * @param {string} aName
+ * The string identifier for the install location
+ * @param {nsIFile} aDirectory
+ * The nsIFile directory for the install location
+ * @param {integer} aScope
+ * The scope of add-ons installed in this location
*/
constructor(aName, aDirectory, aScope) {
this._name = aName;
this.locked = true;
this._directory = aDirectory;
this._scope = aScope;
this._IDToFileMap = {};
this._linkedAddons = [];
@@ -4098,19 +4163,20 @@ class DirectoryInstallLocation {
get path() {
return this._directory && this._directory.path;
}
/**
* Reads a directory linked to in a file.
*
- * @param file
- * The file containing the directory path
- * @return An nsIFile object representing the linked directory.
+ * @param {nsIFile} aFile
+ * The file containing the directory path
+ * @returns {nsIFile}
+ * An nsIFile object representing the linked directory.
*/
_readDirectoryFromFile(aFile) {
let linkedDirectory;
if (aFile.isSymlink()) {
linkedDirectory = aFile.clone();
try {
linkedDirectory.normalize();
} catch (e) {
@@ -4155,16 +4221,20 @@ class DirectoryInstallLocation {
}
logger.warn("File pointer " + aFile.path + " does not contain a path");
return null;
}
/**
* Finds all the add-ons installed in this location.
+ *
+ * @param {boolean} [rescan = false]
+ * True if the directory should be re-scanned, even if it has
+ * already been initialized.
*/
_readAddons(rescan = false) {
if ((this.initialized && !rescan) || !this._directory) {
return;
}
this.initialized = true;
// Use a snapshot of the directory contents to avoid possible issues with
@@ -4222,68 +4292,75 @@ class DirectoryInstallLocation {
* Gets the scope of this install location.
*/
get scope() {
return this._scope;
}
/**
* Gets an array of nsIFiles for add-ons installed in this location.
+ *
+ * @param {boolean} [rescan = false]
+ * True if the directory should be re-scanned, even if it has
+ * already been initialized.
+ *
+ * @returns {nsIFile[]}
*/
getAddonLocations(rescan = false) {
this._readAddons(rescan);
let locations = new Map();
for (let id in this._IDToFileMap) {
locations.set(id, this._IDToFileMap[id].clone());
}
return locations;
}
/**
* Gets the directory that the add-on with the given ID is installed in.
*
- * @param aId
- * The ID of the add-on
- * @return The nsIFile
+ * @param {string} aId
+ * The ID of the add-on
+ * @returns {nsIFile}
* @throws if the ID does not match any of the add-ons installed
*/
getLocationForID(aId) {
if (!(aId in this._IDToFileMap))
this._readAddons();
if (aId in this._IDToFileMap)
return this._IDToFileMap[aId].clone();
throw new Error("Unknown add-on ID " + aId);
}
/**
* Returns true if the given addon was installed in this location by a text
* file pointing to its real path.
*
- * @param aId
+ * @param {string} aId
* The ID of the addon
+ * @returns {boolean}
*/
isLinkedAddon(aId) {
return this._linkedAddons.includes(aId);
}
}
/**
* An extension of DirectoryInstallLocation which adds methods to installing
* and removing add-ons from the directory at runtime.
*/
class MutableDirectoryInstallLocation extends DirectoryInstallLocation {
/**
- * @param aName
- * The string identifier for the install location
- * @param aDirectory
- * The nsIFile directory for the install location
- * @param aScope
- * The scope of add-ons installed in this location
+ * @param {string} aName
+ * The string identifier for the install location
+ * @param {nsIFile} aDirectory
+ * The nsIFile directory for the install location
+ * @param {integer} aScope
+ * The scope of add-ons installed in this location
*/
constructor(aName, aDirectory, aScope) {
super(aName, aDirectory, aScope);
this.locked = false;
this._stagingDirLock = 0;
}
}
@@ -4337,24 +4414,24 @@ class BuiltInInstallLocation extends Dir
/**
* An object which identifies a directory install location for system add-ons
* updates.
*/
class SystemAddonInstallLocation extends MutableDirectoryInstallLocation {
/**
* The location consists of a directory which contains the add-ons installed.
*
- * @param aName
- * The string identifier for the install location
- * @param aDirectory
- * The nsIFile directory for the install location
- * @param aScope
- * The scope of add-ons installed in this location
- * @param aResetSet
- * True to throw away the current add-on set
+ * @param {string} aName
+ * The string identifier for the install location
+ * @param {nsIFile} aDirectory
+ * The nsIFile directory for the install location
+ * @param {integer} aScope
+ * The scope of add-ons installed in this location
+ * @param {boolean} aResetSet
+ * True to throw away the current add-on set
*/
constructor(aName, aDirectory, aScope, aResetSet) {
let addonSet = SystemAddonInstallLocation._loadAddonSet();
let directory = null;
// The system add-on update directory is stored in a pref.
// Therefore, this is looked up before calling the
// constructor on the superclass.
@@ -4378,16 +4455,18 @@ class SystemAddonInstallLocation extends
this.resetAddonSet();
}
this.locked = false;
}
/**
* Reads the current set of system add-ons
+ *
+ * @returns {Object}
*/
static _loadAddonSet() {
try {
let setStr = Services.prefs.getStringPref(PREF_SYSTEM_ADDON_SET, null);
if (setStr) {
let addonSet = JSON.parse(setStr);
if ((typeof addonSet == "object") && addonSet.schema == 1) {
return addonSet;
@@ -4415,16 +4494,18 @@ class SystemAddonInstallLocation extends
}
}
return addons;
}
/**
* Tests whether updated system add-ons are expected.
+ *
+ * @returns {boolean}
*/
isActive() {
return this._directory != null;
}
}
forwardInstallMethods(SystemAddonInstallLocation,
["cleanDirectories", "cleanStagingDir", "getStagingDir",
@@ -4445,22 +4526,22 @@ const TemporaryInstallLocation = { locke
/**
* An object that identifies a registry install location for add-ons. The location
* consists of a registry key which contains string values mapping ID to the
* path where an add-on is installed
*
*/
class WinRegInstallLocation extends DirectoryInstallLocation {
/**
- * @param aName
- * The string identifier of this Install Location.
- * @param aRootKey
- * The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey).
- * @param scope
- * The scope of add-ons installed in this location
+ * @param {string} aName
+ * The string identifier of this Install Location.
+ * @param {integer} aRootKey
+ * The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey).
+ * @param {integer} aScope
+ * The scope of add-ons installed in this location
*/
constructor(aName, aRootKey, aScope) {
super(aName, undefined, aScope);
this.locked = true;
this._name = aName;
this._rootKey = aRootKey;
this._scope = aScope;
@@ -4499,18 +4580,18 @@ class WinRegInstallLocation extends Dire
return "SOFTWARE\\" + appVendor + appName;
}
/**
* Read the registry and build a mapping between ID and path for each
* installed add-on.
*
- * @param key
- * The key that contains the ID to path mapping
+ * @param {nsIWindowsRegKey} aKey
+ * The key that contains the ID to path mapping
*/
_readAddons(aKey) {
let count = aKey.valueCount;
for (let i = 0; i < count; ++i) {
let id = aKey.getValueName(i);
let file = new nsIFile(aKey.readStringValue(id));
@@ -4525,17 +4606,17 @@ class WinRegInstallLocation extends Dire
/**
* Gets the name of this install location.
*/
get name() {
return this._name;
}
- /**
+ /*
* @see DirectoryInstallLocation
*/
isLinkedAddon(aId) {
return true;
}
}
var XPIInternal = {