--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.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 = [
"UpdateChecker",
"XPIInstall",
"verifyBundleSignedState",
];
/* globals DownloadAddonInstall, LocalAddonInstall */
@@ -140,17 +142,17 @@ function getFile(path, base = null) {
let file = base.clone();
file.appendRelativePath(path);
return file;
}
/**
* Sends local and remote notifications to flush a JAR file cache entry
*
- * @param aJarFile
+ * @param {nsIFile} aJarFile
* The ZIP/XPI/JAR file as a nsIFile
*/
function flushJarCache(aJarFile) {
Services.obs.notifyObservers(aJarFile, "flush-cache-entry");
Services.mm.broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path);
}
const PREF_EM_UPDATE_BACKGROUND_URL = "extensions.update.background.url";
@@ -417,17 +419,18 @@ XPIPackage = class XPIPackage extends Pa
/**
* Determine the reason to pass to an extension's bootstrap methods when
* switch between versions.
*
* @param {string} oldVersion The version of the existing extension instance.
* @param {string} newVersion The version of the extension being installed.
*
- * @return {BOOSTRAP_REASONS.ADDON_UPGRADE|BOOSTRAP_REASONS.ADDON_DOWNGRADE}
+ * @returns {integer}
+ * BOOSTRAP_REASONS.ADDON_UPGRADE or BOOSTRAP_REASONS.ADDON_DOWNGRADE
*/
function newVersionReason(oldVersion, newVersion) {
return Services.vc.compare(oldVersion, newVersion) <= 0 ?
BOOTSTRAP_REASONS.ADDON_UPGRADE :
BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
}
// Behaves like Promise.all except waits for all promises to resolve/reject
@@ -450,51 +453,53 @@ function waitForAllPromises(promises) {
function EM_R(aProperty) {
return gRDF.GetResource(PREFIX_NS_EM + aProperty);
}
/**
* Converts an RDF literal, resource or integer into a string.
*
- * @param aLiteral
- * The RDF object to convert
- * @return a string if the object could be converted or null
+ * @param {nsISupports} aLiteral
+ * The RDF object to convert
+ * @returns {string?}
+ * A string if the object could be converted or null
*/
function getRDFValue(aLiteral) {
if (aLiteral instanceof Ci.nsIRDFLiteral)
return aLiteral.Value;
if (aLiteral instanceof Ci.nsIRDFResource)
return aLiteral.Value;
if (aLiteral instanceof Ci.nsIRDFInt)
return aLiteral.Value;
return null;
}
/**
* Gets an RDF property as a string
*
- * @param aDs
- * The RDF datasource to read the property from
- * @param aResource
- * The RDF resource to read the property from
- * @param aProperty
- * The property to read
- * @return a string if the property existed or null
+ * @param {nsIRDFDataSource} aDs
+ * The RDF datasource to read the property from
+ * @param {nsIRDFResource} aResource
+ * The RDF resource to read the property from
+ * @param {string} aProperty
+ * The property to read
+ * @returns {string?}
+ * A string if the property existed or null
*/
function getRDFProperty(aDs, aResource, aProperty) {
return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true));
}
/**
* Reads an AddonInternal object from a manifest stream.
*
- * @param aUri
- * A |file:| or |jar:| URL for the manifest
- * @return an AddonInternal object
+ * @param {nsIURI} aUri
+ * A |file:| or |jar:| URL for the manifest
+ * @returns {AddonInternal}
* @throws if the install manifest in the stream is corrupt or could not
* be read
*/
async function loadManifestFromWebManifest(aUri) {
// We're passed the URI for the manifest file. Get the URI for its
// parent directory.
let uri = Services.io.newURI("./", null, aUri);
@@ -612,21 +617,21 @@ async function loadManifestFromWebManife
addon.softDisabled = addon.blocklistState == nsIBlocklistService.STATE_SOFTBLOCKED;
return addon;
}
/**
* Reads an AddonInternal object from an RDF stream.
*
- * @param aUri
- * The URI that the manifest is being read from
- * @param aData
- * The manifest text
- * @return an AddonInternal object
+ * @param {nsIURI} aUri
+ * The URI that the manifest is being read from
+ * @param {string} aData
+ * The manifest text
+ * @returns {AddonInternal}
* @throws if the install manifest in the RDF stream is corrupt or could not
* be read
*/
async function loadManifestFromRDF(aUri, aData) {
function getPropertyArray(aDs, aSource, aProperty) {
let values = [];
let targets = aDs.GetTargets(aSource, EM_R(aProperty), true);
while (targets.hasMoreElements())
@@ -634,28 +639,29 @@ async function loadManifestFromRDF(aUri,
return values;
}
/**
* Reads locale properties from either the main install manifest root or
* an em:localized section in the install manifest.
*
- * @param aDs
- * The nsIRDFDatasource to read from
- * @param aSource
- * The nsIRDFResource to read the properties from
- * @param isDefault
- * True if the locale is to be read from the main install manifest
- * root
- * @param aSeenLocales
- * An array of locale names already seen for this install manifest.
- * Any locale names seen as a part of this function will be added to
- * this array
- * @return an object containing the locale properties
+ * @param {nsIRDFDataSource} aDs
+ * The datasource to read from.
+ * @param {nsIRDFResource} aSource
+ * The resource to read the properties from.
+ * @param {boolean} isDefault
+ * True if the locale is to be read from the main install manifest
+ * root
+ * @param {string[]} aSeenLocales
+ * An array of locale names already seen for this install manifest.
+ * Any locale names seen as a part of this function will be added to
+ * this array
+ * @returns {Object}
+ * an object containing the locale properties
*/
function readLocale(aDs, aSource, isDefault, aSeenLocales) {
let locale = { };
if (!isDefault) {
locale.locales = [];
let targets = ds.GetTargets(aSource, EM_R("locale"), true);
while (targets.hasMoreElements()) {
let localeName = getRDFValue(targets.getNext());
@@ -940,29 +946,44 @@ var loadManifest = async function(aPacka
await addon.updateBlocklistState({oldAddon: aOldAddon});
addon.appDisabled = !isUsableAddon(addon);
defineSyncGUID(addon);
return addon;
};
+/**
+ * Loads an add-on's manifest from the given file or directory.
+ *
+ * @param {nsIFile} aFile
+ * The file to load the manifest from.
+ * @param {InstallLocation} aInstallLocation
+ * The install location the add-on is installed in, or will be
+ * installed to.
+ * @param {AddonInternal?} aOldAddon
+ * The currently-installed add-on with the same ID, if one exist.
+ * This is used to migrate user settings like the add-on's
+ * disabled state.
+ * @returns {AddonInternal}
+ * The parsed Addon object for the file's manifest.
+ */
var loadManifestFromFile = async function(aFile, aInstallLocation, aOldAddon) {
let pkg = Package.get(aFile);
try {
let addon = await loadManifest(pkg, aInstallLocation, aOldAddon);
return addon;
} finally {
pkg.close();
}
};
-/**
- * A synchronous method for loading an add-on's manifest. This should only ever
- * be used during startup or a sync load of the add-ons DB
+/*
+ * A synchronous method for loading an add-on's manifest. Do not use
+ * this.
*/
function syncLoadManifestFromFile(aFile, aInstallLocation, aOldAddon) {
return XPIInternal.awaitPromise(loadManifestFromFile(aFile, aInstallLocation, aOldAddon));
}
function flushChromeCaches() {
// Init this, so it will get the notification.
Services.obs.notifyObservers(null, "startupcache-invalidate");
@@ -971,30 +992,44 @@ function flushChromeCaches() {
// Also dispatch this event to child processes
Services.mm.broadcastAsyncMessage(MSG_MESSAGE_MANAGER_CACHES_FLUSH, null);
}
/**
* Creates and returns a new unique temporary file. The caller should delete
* the file when it is no longer needed.
*
- * @return an nsIFile that points to a randomly named, initially empty file in
- * the OS temporary files directory
+ * @returns {nsIFile}
+ * An nsIFile that points to a randomly named, initially empty file in
+ * the OS temporary files directory
*/
function getTemporaryFile() {
let file = FileUtils.getDir(KEY_TEMPDIR, []);
let random = Math.round(Math.random() * 36 ** 3).toString(36);
file.append("tmp-" + random + ".xpi");
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
return file;
}
/**
* Returns the signedState for a given return code and certificate by verifying
* it against the expected ID.
+ *
+ * @param {nsresult} aRv
+ * The result code returned by the signature checker for the
+ * signature check operation.
+ * @param {nsIX509Cert?} aCert
+ * The certificate the add-on was signed with, if a valid
+ * certificate exists.
+ * @param {string?} aAddonID
+ * The expected ID of the add-on. If passed, this must match the
+ * ID in the certificate's CN field.
+ * @returns {number}
+ * A SIGNEDSTATE result code constant, as defined on the
+ * AddonManager class.
*/
function getSignedStatus(aRv, aCert, aAddonID) {
let expectedCommonName = aAddonID;
if (aAddonID && aAddonID.length > 64) {
let data = new Uint8Array(new TextEncoder().encode(aAddonID));
let crypto = CryptoHash("sha256");
crypto.update(data, data.length);
@@ -1046,46 +1081,48 @@ function shouldVerifySignedState(aAddon)
// of the signed types.
return AddonSettings.ADDON_SIGNING && SIGNED_TYPES.has(aAddon.type);
}
/**
* Verifies that a bundle's contents are all correctly signed by an
* AMO-issued certificate
*
- * @param aBundle
- * the nsIFile for the bundle to check, either a directory or zip file
- * @param aAddon
- * the add-on object to verify
- * @return a Promise that resolves to an AddonManager.SIGNEDSTATE_* constant.
+ * @param {nsIFile}aBundle
+ * The nsIFile for the bundle to check, either a directory or zip file.
+ * @param {AddonInternal} aAddon
+ * The add-on object to verify.
+ * @returns {Prommise<number>}
+ * A Promise that resolves to an AddonManager.SIGNEDSTATE_* constant.
*/
var verifyBundleSignedState = async function(aBundle, aAddon) {
let pkg = Package.get(aBundle);
try {
let {signedState} = await pkg.verifySignedState(aAddon);
return signedState;
} finally {
pkg.close();
}
};
/**
* Replaces %...% strings in an addon url (update and updateInfo) with
* appropriate values.
*
- * @param aAddon
- * The AddonInternal representing the add-on
- * @param aUri
- * The uri to escape
- * @param aUpdateType
- * An optional number representing the type of update, only applicable
- * when creating a url for retrieving an update manifest
- * @param aAppVersion
- * The optional application version to use for %APP_VERSION%
- * @return the appropriately escaped uri.
+ * @param {AddonInternal} aAddon
+ * The AddonInternal representing the add-on
+ * @param {string} aUri
+ * The URI to escape
+ * @param {integer?} aUpdateType
+ * An optional number representing the type of update, only applicable
+ * when creating a url for retrieving an update manifest
+ * @param {string?} aAppVersion
+ * The optional application version to use for %APP_VERSION%
+ * @returns {string}
+ * The appropriately escaped URI.
*/
function escapeAddonURI(aAddon, aUri, aUpdateType, aAppVersion) {
let uri = AddonManager.escapeAddonURI(aAddon, aUri, aAppVersion);
// If there is an updateType then replace the UPDATE_TYPE string
if (aUpdateType)
uri = uri.replace(/%UPDATE_TYPE%/g, aUpdateType);
@@ -1106,16 +1143,21 @@ function escapeAddonURI(aAddon, aUri, aU
compatMode = "strict";
uri = uri.replace(/%COMPATIBILITY_MODE%/g, compatMode);
return uri;
}
/**
* Converts an iterable of addon objects into a map with the add-on's ID as key.
+ *
+ * @param {sequence<AddonInternal>} addons
+ * A sequence of AddonInternal objects.
+ *
+ * @returns {Map<string, AddonInternal>}
*/
function addonMap(addons) {
return new Map(addons.map(a => [a.id, a]));
}
async function removeAsync(aFile) {
let info = null;
try {
@@ -1129,18 +1171,18 @@ async function removeAsync(aFile) {
throw e;
// The file has already gone away
}
}
/**
* Recursively removes a directory or file fixing permissions when necessary.
*
- * @param aFile
- * The nsIFile to remove
+ * @param {nsIFile} aFile
+ * The nsIFile to remove
*/
function recursiveRemove(aFile) {
let isDir = null;
try {
isDir = aFile.isDirectory();
} catch (e) {
// If the file has already gone away then don't worry about it, this can
@@ -1180,37 +1222,37 @@ function recursiveRemove(aFile) {
logger.error("Failed to remove empty directory " + aFile.path, e);
throw e;
}
}
/**
* Sets permissions on a file
*
- * @param aFile
- * The file or directory to operate on.
- * @param aPermissions
- * The permissions to set
+ * @param {nsIFile} aFile
+ * The file or directory to operate on.
+ * @param {integer} aPermissions
+ * The permissions to set
*/
function setFilePermissions(aFile, aPermissions) {
try {
aFile.permissions = aPermissions;
} catch (e) {
logger.warn("Failed to set permissions " + aPermissions.toString(8) + " on " +
aFile.path, e);
}
}
/**
* Write a given string to a file
*
- * @param file
- * The nsIFile instance to write into
- * @param string
- * The string to write
+ * @param {nsIFile} file
+ * The nsIFile instance to write into
+ * @param {string} string
+ * The string to write
*/
function writeStringToFile(file, string) {
let fileStream = new FileOutputStream(
file, (FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
FileUtils.MODE_TRUNCATE),
FileUtils.PERMS_FILE, 0);
try {
@@ -1341,60 +1383,60 @@ SafeInstallOperation.prototype = {
throw e;
}
},
/**
* Moves a file or directory into a new directory. If an error occurs then all
* files that have been moved will be moved back to their original location.
*
- * @param aFile
- * The file or directory to be moved.
- * @param aTargetDirectory
- * The directory to move into, this is expected to be an empty
- * directory.
+ * @param {nsIFile} aFile
+ * The file or directory to be moved.
+ * @param {nsIFile} aTargetDirectory
+ * The directory to move into, this is expected to be an empty
+ * directory.
*/
moveUnder(aFile, aTargetDirectory) {
try {
this._installDirEntry(aFile, aTargetDirectory, false);
} catch (e) {
this.rollback();
throw e;
}
},
/**
* Renames a file to a new location. If an error occurs then all
* files that have been moved will be moved back to their original location.
*
- * @param aOldLocation
- * The old location of the file.
- * @param aNewLocation
- * The new location of the file.
+ * @param {nsIFile} aOldLocation
+ * The old location of the file.
+ * @param {nsIFile} aNewLocation
+ * The new location of the file.
*/
moveTo(aOldLocation, aNewLocation) {
try {
let oldFile = aOldLocation.clone(), newFile = aNewLocation.clone();
oldFile.moveTo(newFile.parent, newFile.leafName);
this._installedFiles.push({ oldFile, newFile, isMoveTo: true});
} catch (e) {
this.rollback();
throw e;
}
},
/**
* Copies a file or directory into a new directory. If an error occurs then
* all new files that have been created will be removed.
*
- * @param aFile
- * The file or directory to be copied.
- * @param aTargetDirectory
- * The directory to copy into, this is expected to be an empty
- * directory.
+ * @param {nsIFile} aFile
+ * The file or directory to be copied.
+ * @param {nsIFile} aTargetDirectory
+ * The directory to copy into, this is expected to be an empty
+ * directory.
*/
copy(aFile, aTargetDirectory) {
try {
this._installDirEntry(aFile, aTargetDirectory, true);
} catch (e) {
this.rollback();
throw e;
}
@@ -1424,21 +1466,22 @@ SafeInstallOperation.prototype = {
while (this._createdDirs.length > 0)
recursiveRemove(this._createdDirs.pop());
}
};
/**
* 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 {nsIFile} aDir
+ * Directory to look at
+ * @param {boolean} aSortEntries
+ * True to sort entries by filename
+ * @returns {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);
@@ -1475,37 +1518,37 @@ function getHashStringForCrypto(aCrypto)
/**
* Base class for objects that manage the installation of an addon.
* This class isn't instantiated directly, see the derived classes below.
*/
class AddonInstall {
/**
* Instantiates an AddonInstall.
*
- * @param installLocation
- * The install location the add-on will be installed into
- * @param url
- * The nsIURL to get the add-on from. If this is an nsIFileURL then
- * the add-on will not need to be downloaded
- * @param options
- * Additional options for the install
- * @param options.hash
- * An optional hash for the add-on
- * @param options.existingAddon
- * The add-on this install will update if known
- * @param options.name
- * An optional name for the add-on
- * @param options.type
- * An optional type for the add-on
- * @param options.icons
- * Optional icons for the add-on
- * @param options.version
- * An optional version for the add-on
- * @param options.promptHandler
- * A callback to prompt the user before installing.
+ * @param {InstallLocation} installLocation
+ * The install location the add-on will be installed into
+ * @param {nsIURL} url
+ * The nsIURL to get the add-on from. If this is an nsIFileURL then
+ * the add-on will not need to be downloaded
+ * @param {Object} [options = {}]
+ * Additional options for the install
+ * @param {string} [options.hash]
+ * An optional hash for the add-on
+ * @param {AddonInternal} [options.existingAddon]
+ * The add-on this install will update if known
+ * @param {string} [options.name]
+ * An optional name for the add-on
+ * @param {string} [options.type]
+ * An optional type for the add-on
+ * @param {object} [options.icons]
+ * Optional icons for the add-on
+ * @param {string} [options.version]
+ * An optional version for the add-on
+ * @param {function(string) : Promise<void>} [options.promptHandler]
+ * A callback to prompt the user before installing.
*/
constructor(installLocation, url, options = {}) {
this.wrapper = new AddonInstallWrapper(this);
this.installLocation = installLocation;
this.sourceURI = url;
if (options.hash) {
let hashSplit = options.hash.toLowerCase().split(":");
@@ -1551,16 +1594,17 @@ class AddonInstall {
/**
* Starts installation of this add-on from whatever state it is currently at
* if possible.
*
* Note this method is overridden to handle additional state in
* the subclassses below.
*
+ * @returns {Promise<Addon>}
* @throws if installation cannot proceed from the current state
*/
install() {
switch (this.state) {
case AddonManager.STATE_DOWNLOADED:
this.checkPrompt();
break;
case AddonManager.STATE_PROMPTS_DONE:
@@ -1646,29 +1690,29 @@ class AddonInstall {
" from this state (" + this.state + ")");
}
}
/**
* Adds an InstallListener for this instance if the listener is not already
* registered.
*
- * @param aListener
- * The InstallListener to add
+ * @param {InstallListener} aListener
+ * The InstallListener to add
*/
addListener(aListener) {
if (!this.listeners.some(function(i) { return i == aListener; }))
this.listeners.push(aListener);
}
/**
* Removes an InstallListener for this instance if it is registered.
*
- * @param aListener
- * The InstallListener to remove
+ * @param {InstallListener} aListener
+ * The InstallListener to remove
*/
removeListener(aListener) {
this.listeners = this.listeners.filter(function(i) {
return i != aListener;
});
}
/**
@@ -1702,20 +1746,19 @@ class AddonInstall {
if (this.releaseNotesURI)
this.addon.releaseNotesURI = this.releaseNotesURI.spec;
}
/**
* Called after the add-on is a local file and the signature and install
* manifest can be read.
*
- * @param aCallback
- * A function to call when the manifest has been loaded
- * @throws if the add-on does not contain a valid install manifest or the
- * XPI is incorrectly signed
+ * @param {nsIFile} file
+ * The file from which to load the manifest.
+ * @returns {Promise<void>}
*/
async loadManifest(file) {
let pkg;
try {
pkg = Package.get(file);
} catch (e) {
return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
}
@@ -2018,17 +2061,26 @@ class AddonInstall {
this._callInstallListeners("onInstallFailed");
}).then(() => {
this.removeTemporaryFile();
return this.installLocation.releaseStagingDir();
});
}
/**
- * Stages an upgrade for next application restart.
+ * Stages an add-on for install.
+ *
+ * @param {boolean} restartRequired
+ * If true, the final installation will be deferred until the
+ * next app startup.
+ * @param {AddonInternal} stagedAddon
+ * The AddonInternal object for the staged install.
+ * @param {boolean} isUpgrade
+ * True if this installation is an upgrade for an existing
+ * add-on.
*/
async stageInstall(restartRequired, stagedAddon, isUpgrade) {
// First stage the file regardless of whether restarting is necessary
if (this.addon.unpack) {
logger.debug("Addon " + this.addon.id + " will be installed as " +
"an unpacked directory");
stagedAddon.leafName = this.addon.id;
await OS.File.makeDir(stagedAddon.path);
@@ -2053,30 +2105,33 @@ class AddonInstall {
delete this.existingAddon.pendingUpgrade;
this.existingAddon.pendingUpgrade = this.addon;
}
}
}
/**
* Removes any previously staged upgrade.
+ *
+ * @param {nsIFile} stagingDir
+ * The staging directory from which to unstage the install.
*/
- async unstageInstall(stagedAddon) {
+ async unstageInstall(stagingDir) {
XPIStates.getLocation(this.installLocation.name).unstageAddon(this.addon.id);
- await removeAsync(getFile(this.addon.id, stagedAddon));
-
- await removeAsync(getFile(`${this.addon.id}.xpi`, stagedAddon));
+ await removeAsync(getFile(this.addon.id, stagingDir));
+
+ await removeAsync(getFile(`${this.addon.id}.xpi`, stagingDir));
}
/**
* Postone a pending update, until restart or until the add-on resumes.
*
- * @param {Function} resumeFn - a function for the add-on to run
- * when resuming.
+ * @param {function} resumeFn
+ * A function for the add-on to run when resuming.
*/
async postpone(resumeFn) {
this.state = AddonManager.STATE_POSTPONED;
let stagingDir = this.installLocation.getStagingDir();
await this.installLocation.requestStagingDir();
await this.unstageInstall(stagingDir);
@@ -2130,19 +2185,16 @@ class AddonInstall {
return AddonManagerPrivate.callInstallListeners(event, this.listeners, this.wrapper,
...args);
}
}
var LocalAddonInstall = class extends AddonInstall {
/**
* Initialises this install to be an install from a local file.
- *
- * @returns Promise
- * A Promise that resolves when the object is ready to use.
*/
async init() {
this.file = this.sourceURI.QueryInterface(Ci.nsIFileURL).file;
if (!this.file.exists()) {
logger.warn("XPI file " + this.file.path + " does not exist");
this.state = AddonManager.STATE_DOWNLOAD_FAILED;
this.error = AddonManager.ERROR_NETWORK_FAILURE;
@@ -2231,39 +2283,39 @@ var LocalAddonInstall = class extends Ad
return super.install();
}
};
var DownloadAddonInstall = class extends AddonInstall {
/**
* Instantiates a DownloadAddonInstall
*
- * @param installLocation
- * The InstallLocation the add-on will be installed into
- * @param url
- * The nsIURL to get the add-on from
- * @param options
- * Additional options for the install
- * @param options.hash
- * An optional hash for the add-on
- * @param options.existingAddon
- * The add-on this install will update if known
- * @param options.browser
- * The browser performing the install, used to display
- * authentication prompts.
- * @param options.name
- * An optional name for the add-on
- * @param options.type
- * An optional type for the add-on
- * @param options.icons
- * Optional icons for the add-on
- * @param options.version
- * An optional version for the add-on
- * @param options.promptHandler
- * A callback to prompt the user before installing.
+ * @param {InstallLocation} installLocation
+ * The InstallLocation the add-on will be installed into
+ * @param {nsIURL} url
+ * The nsIURL to get the add-on from
+ * @param {Object} [options = {}]
+ * Additional options for the install
+ * @param {string} [options.hash]
+ * An optional hash for the add-on
+ * @param {AddonInternal} [options.existingAddon]
+ * The add-on this install will update if known
+ * @param {XULElement} [options.browser]
+ * The browser performing the install, used to display
+ * authentication prompts.
+ * @param {string} [options.name]
+ * An optional name for the add-on
+ * @param {string} [options.type]
+ * An optional type for the add-on
+ * @param {Object} [options.icons]
+ * Optional icons for the add-on
+ * @param {string} [options.version]
+ * An optional version for the add-on
+ * @param {function(string) : Promise<void>} [options.promptHandler]
+ * A callback to prompt the user before installing.
*/
constructor(installLocation, url, options = {}) {
super(installLocation, url, options);
this.browser = options.browser;
this.state = AddonManager.STATE_AVAILABLE;
@@ -2384,30 +2436,30 @@ var DownloadAddonInstall = class extends
logger.warn("Failed to start download for addon " + this.sourceURI.spec, e);
this.state = AddonManager.STATE_DOWNLOAD_FAILED;
this.error = AddonManager.ERROR_NETWORK_FAILURE;
XPIProvider.removeActiveInstall(this);
this._callInstallListeners("onDownloadFailed");
}
}
- /**
+ /*
* Update the crypto hasher with the new data and call the progress listeners.
*
* @see nsIStreamListener
*/
onDataAvailable(aRequest, aContext, aInputstream, aOffset, aCount) {
this.crypto.updateFromStream(aInputstream, aCount);
this.progress += aCount;
if (!this._callInstallListeners("onDownloadProgress")) {
// TODO cancel the download and make it available again (bug 553024)
}
}
- /**
+ /*
* Check the redirect response for a hash of the target XPI and verify that
* we don't end up on an insecure channel.
*
* @see nsIChannelEventSink
*/
asyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCallback) {
if (!this.hash && aOldChannel.originalURI.schemeIs("https") &&
aOldChannel instanceof Ci.nsIHttpChannel) {
@@ -2427,17 +2479,17 @@ var DownloadAddonInstall = class extends
if (!this.hash)
this.badCertHandler.asyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCallback);
else
aCallback.onRedirectVerifyCallback(Cr.NS_OK);
this.channel = aNewChannel;
}
- /**
+ /*
* This is the first chance to get at real headers on the channel.
*
* @see nsIStreamListener
*/
onStartRequest(aRequest, aContext) {
if (this.hash) {
try {
this.crypto = CryptoHash(this.hash.algorithm);
@@ -2462,17 +2514,17 @@ var DownloadAddonInstall = class extends
this.maxProgress = aRequest.contentLength;
} catch (e) {
}
logger.debug("Download started for " + this.sourceURI.spec + " to file " +
this.file.path);
}
}
- /**
+ /*
* The download is complete.
*
* @see nsIStreamListener
*/
onStopRequest(aRequest, aContext, aStatus) {
this.stream.close();
this.channel = null;
this.badCerthandler = null;
@@ -2545,20 +2597,20 @@ var DownloadAddonInstall = class extends
} else {
this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, aStatus);
}
}
/**
* Notify listeners that the download failed.
*
- * @param aReason
- * Something to log about the failure
- * @param error
- * The error code to pass to the listeners
+ * @param {string} aReason
+ * Something to log about the failure
+ * @param {integer} aError
+ * The error code to pass to the listeners
*/
downloadFailed(aReason, aError) {
logger.warn("Download of " + this.sourceURI.spec + " failed", aError);
this.state = AddonManager.STATE_DOWNLOAD_FAILED;
this.error = aReason;
XPIProvider.removeActiveInstall(this);
this._callInstallListeners("onDownloadFailed");
@@ -2622,22 +2674,22 @@ var DownloadAddonInstall = class extends
return this.badCertHandler.getInterface(iid);
}
};
/**
* Creates a new AddonInstall for an update.
*
- * @param aCallback
- * The callback to pass the new AddonInstall to
- * @param aAddon
- * The add-on being updated
- * @param aUpdate
- * The metadata about the new version from the update manifest
+ * @param {function} aCallback
+ * The callback to pass the new AddonInstall to
+ * @param {AddonInternal} aAddon
+ * The add-on being updated
+ * @param {Object} aUpdate
+ * The metadata about the new version from the update manifest
*/
function createUpdate(aCallback, aAddon, aUpdate) {
let url = Services.io.newURI(aUpdate.updateURL);
(async function() {
let opts = {
hash: aUpdate.updateHash,
existingAddon: aAddon,
@@ -2666,18 +2718,18 @@ function createUpdate(aCallback, aAddon,
// Maps instances of AddonInstall to AddonInstallWrapper
const wrapperMap = new WeakMap();
let installFor = wrapper => wrapperMap.get(wrapper);
/**
* Creates a wrapper for an AddonInstall that only exposes the public API
*
- * @param install
- * The AddonInstall to create a wrapper for
+ * @param {AddonInstall} aInstall
+ * The AddonInstall to create a wrapper for
*/
function AddonInstallWrapper(aInstall) {
wrapperMap.set(this, aInstall);
}
AddonInstallWrapper.prototype = {
get __AddonInstallInternal__() {
return AppConstants.DEBUG ? installFor(this) : undefined;
@@ -2734,26 +2786,26 @@ AddonInstallWrapper.prototype = {
},
enumerable: true,
});
});
/**
* Creates a new update checker.
*
- * @param aAddon
- * The add-on to check for updates
- * @param aListener
- * An UpdateListener to notify of updates
- * @param aReason
- * The reason for the update check
- * @param aAppVersion
- * An optional application version to check for updates for
- * @param aPlatformVersion
- * An optional platform version to check for updates for
+ * @param {AddonInternal} aAddon
+ * The add-on to check for updates
+ * @param {UpdateListener} aListener
+ * An UpdateListener to notify of updates
+ * @param {integer} aReason
+ * The reason for the update check
+ * @param {string} [aAppVersion]
+ * An optional application version to check for updates for
+ * @param {string} [aPlatformVersion]
+ * An optional platform version to check for updates for
* @throws if the aListener or aReason arguments are not valid
*/
var UpdateChecker = function(aAddon, aListener, aReason, aAppVersion, aPlatformVersion) {
if (!aListener || !aReason)
throw Cr.NS_ERROR_INVALID_ARG;
ChromeUtils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm");
@@ -2792,35 +2844,37 @@ UpdateChecker.prototype = {
appVersion: null,
platformVersion: null,
syncCompatibility: null,
/**
* Calls a method on the listener passing any number of arguments and
* consuming any exceptions.
*
- * @param aMethod
- * The method to call on the listener
+ * @param {string} aMethod
+ * The method to call on the listener
+ * @param {any[]} aArgs
+ * Additional arguments to pass to the listener.
*/
callListener(aMethod, ...aArgs) {
if (!(aMethod in this.listener))
return;
try {
this.listener[aMethod].apply(this.listener, aArgs);
} catch (e) {
logger.warn("Exception calling UpdateListener method " + aMethod, e);
}
},
/**
* Called when AddonUpdateChecker completes the update check
*
- * @param updates
- * The list of update details for the add-on
+ * @param {object[]} aUpdates
+ * The list of update details for the add-on
*/
async onUpdateCheckComplete(aUpdates) {
XPIProvider.done(this.addon._updateCheck);
this.addon._updateCheck = null;
let AUC = AddonUpdateChecker;
let ignoreMaxVersion = false;
let ignoreStrictCompat = false;
@@ -2906,18 +2960,18 @@ UpdateChecker.prototype = {
} else {
sendUpdateAvailableMessages(this, null);
}
},
/**
* Called when AddonUpdateChecker fails the update check
*
- * @param aError
- * An error status
+ * @param {any} aError
+ * An error status
*/
onUpdateCheckError(aError) {
XPIProvider.done(this.addon._updateCheck);
this.addon._updateCheck = null;
this.callListener("onNoCompatibilityUpdateAvailable", this.addon.wrapper);
this.callListener("onNoUpdateAvailable", this.addon.wrapper);
this.callListener("onUpdateFinished", this.addon.wrapper, aError);
},
@@ -2933,22 +2987,22 @@ UpdateChecker.prototype = {
parser.cancel();
}
}
};
/**
* Creates a new AddonInstall to install an add-on from a local file.
*
- * @param file
- * The file to install
- * @param location
- * The location to install to
- * @returns Promise
- * A Promise that resolves with the new install object.
+ * @param {nsIFile} file
+ * The file to install
+ * @param {InstallLocation} location
+ * The location to install to
+ * @returns {Promise<AddonInstall>}
+ * A Promise that resolves with the new install object.
*/
function createLocalInstall(file, location) {
if (!location) {
location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
}
let url = Services.io.newFileURI(file);
try {
@@ -2967,17 +3021,17 @@ function createLocalInstall(file, locati
// an instance the class as defined in XPIProvider.
class DirectoryInstallLocation {}
class MutableDirectoryInstallLocation extends DirectoryInstallLocation {
/**
* Gets the staging directory to put add-ons that are pending install and
* uninstall into.
*
- * @return an nsIFile
+ * @returns {nsIFile}
*/
getStagingDir() {
return getFile(DIR_STAGE, this._directory);
}
requestStagingDir() {
this._stagingDirLock++;
@@ -3004,19 +3058,19 @@ class MutableDirectoryInstallLocation ex
return Promise.resolve();
}
/**
* Removes the specified files or directories in the staging directory and
* then if the staging directory is empty attempts to remove it.
*
- * @param aLeafNames
- * An array of file or directory to remove from the directory, the
- * array may be empty
+ * @param {string[]} [aLeafNames = []]
+ * An array of file or directory to remove from the directory, the
+ * array may be empty
*/
cleanStagingDir(aLeafNames = []) {
let dir = this.getStagingDir();
for (let name of aLeafNames) {
let file = getFile(name, dir);
recursiveRemove(file);
}
@@ -3042,17 +3096,17 @@ class MutableDirectoryInstallLocation ex
}
/**
* Returns a directory that is normally on the same filesystem as the rest of
* the install location and can be used for temporarily storing files during
* safe move operations. Calling this method will delete the existing trash
* directory and its contents.
*
- * @return an nsIFile
+ * @returns {nsIFile}
*/
getTrashDir() {
let trashDir = getFile(DIR_TRASH, this._directory);
let trashDirExists = trashDir.exists();
try {
if (trashDirExists)
recursiveRemove(trashDir);
trashDirExists = false;
@@ -3063,32 +3117,35 @@ class MutableDirectoryInstallLocation ex
trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
return trashDir;
}
/**
* Installs an add-on into the install location.
*
- * @param id
- * The ID of the add-on to install
- * @param source
- * The source nsIFile to install from
- * @param existingAddonID
- * The ID of an existing add-on to uninstall at the same time
- * @param action
- * What to we do with the given source file:
- * "move"
- * Default action, the source files will be moved to the new
- * location,
- * "copy"
- * The source files will be copied,
- * "proxy"
- * A "proxy file" is going to refer to the source file path
- * @return an nsIFile indicating where the add-on was installed to
+ * @param {Object} options
+ * Installation options.
+ * @param {string} options.id
+ * The ID of the add-on to install
+ * @param {nsIFile} options.source
+ * The source nsIFile to install from
+ * @param {string?} [options.existingAddonID]
+ * The ID of an existing add-on to uninstall at the same time
+ * @param {string} options.action
+ * What to we do with the given source file:
+ * "move"
+ * Default action, the source files will be moved to the new
+ * location,
+ * "copy"
+ * The source files will be copied,
+ * "proxy"
+ * A "proxy file" is going to refer to the source file path
+ * @returns {nsIFile}
+ * An nsIFile indicating where the add-on was installed to
*/
installAddon({ id, source, existingAddonID, action = "move" }) {
let trashDir = this.getTrashDir();
let transaction = new SafeInstallOperation();
let moveOldAddon = aId => {
let file = getFile(aId, this._directory);
@@ -3177,18 +3234,18 @@ class MutableDirectoryInstallLocation ex
}
return newFile;
}
/**
* Uninstalls an add-on from this location.
*
- * @param aId
- * The ID of the add-on to uninstall
+ * @param {string} aId
+ * The ID of the add-on to uninstall
* @throws if the ID does not match any of the add-ons installed
*/
uninstallAddon(aId) {
let file = this._IDToFileMap[aId];
if (!file) {
logger.warn("Attempted to remove " + aId + " from " +
this._name + " but it was already gone");
return;
@@ -3247,17 +3304,18 @@ class SystemAddonInstallLocation extends
static _loadAddonSet() {
return XPIInternal.SystemAddonInstallLocation._loadAddonSet();
}
/**
* Gets the staging directory to put add-ons that are pending install and
* uninstall into.
*
- * @return {nsIFile} - staging directory for system add-on upgrades.
+ * @returns {nsIFile}
+ * Staging directory for system add-on upgrades.
*/
getStagingDir() {
this._addonSet = SystemAddonInstallLocation._loadAddonSet();
let dir = null;
if (this._addonSet.directory) {
this._directory = getFile(this._addonSet.directory, this._baseDir);
dir = getFile(DIR_STAGE, this._directory);
} else {
@@ -3281,16 +3339,21 @@ class SystemAddonInstallLocation extends
return false;
}
return true;
}
/**
* Tests whether the loaded add-on information matches what is expected.
+ *
+ * @param {Map<string, AddonInternal>} aAddons
+ * The set of add-ons to check.
+ * @returns {boolean}
+ * True if all of the given add-ons are valid.
*/
isValid(aAddons) {
for (let id of Object.keys(this._addonSet.addons)) {
if (!aAddons.has(id)) {
logger.warn(`Expected add-on ${id} is missing from the system add-on location.`);
return false;
}
@@ -3496,16 +3559,19 @@ class SystemAddonInstallLocation extends
logger.warn(`Failed to remove failed system add-on directory ${newDir.path}.`, e);
}
throw e;
}
}
/**
* Resumes upgrade of a previously-delayed add-on set.
+ *
+ * @param {AddonInstall[]} installs
+ * The set of installs to resume.
*/
async resumeAddonSet(installs) {
async function resumeAddon(install) {
install.state = AddonManager.STATE_DOWNLOADED;
install.installLocation.releaseStagingDir();
install.install();
}
@@ -3521,17 +3587,17 @@ class SystemAddonInstallLocation extends
}
/**
* Returns a directory that is normally on the same filesystem as the rest of
* the install location and can be used for temporarily storing files during
* safe move operations. Calling this method will delete the existing trash
* directory and its contents.
*
- * @return an nsIFile
+ * @returns {nsIFile}
*/
getTrashDir() {
let trashDir = getFile(DIR_TRASH, this._directory);
let trashDirExists = trashDir.exists();
try {
if (trashDirExists)
recursiveRemove(trashDir);
trashDirExists = false;
@@ -3542,21 +3608,22 @@ class SystemAddonInstallLocation extends
trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
return trashDir;
}
/**
* Installs an add-on into the install location.
*
- * @param id
- * The ID of the add-on to install
- * @param source
- * The source nsIFile to install from
- * @return an nsIFile indicating where the add-on was installed to
+ * @param {string} id
+ * The ID of the add-on to install
+ * @param {nsIFile} source
+ * The source nsIFile to install from
+ * @returns {nsIFile}
+ * An nsIFile indicating where the add-on was installed to
*/
installAddon({id, source}) {
let trashDir = this.getTrashDir();
let transaction = new SafeInstallOperation();
// If any of these operations fails the finally block will clean up the
// temporary directory
try {
@@ -3853,51 +3920,55 @@ var XPIInstall = {
logger.info("Installing new system add-on set");
await systemAddonLocation.installAddonSet(Array.from(addonList.values())
.map(a => a.addon));
},
/**
* Called to test whether installing XPI add-ons is enabled.
*
- * @return true if installing is enabled
+ * @returns {boolean}
+ * True if installing is enabled.
*/
isInstallEnabled() {
// Default to enabled if the preference does not exist
return Services.prefs.getBoolPref(PREF_XPI_ENABLED, true);
},
/**
* Called to test whether installing XPI add-ons by direct URL requests is
* whitelisted.
*
- * @return true if installing by direct requests is whitelisted
+ * @returns {boolean}
+ * True if installing by direct requests is whitelisted
*/
isDirectRequestWhitelisted() {
// Default to whitelisted if the preference does not exist.
return Services.prefs.getBoolPref(PREF_XPI_DIRECT_WHITELISTED, true);
},
/**
* Called to test whether installing XPI add-ons from file referrers is
* whitelisted.
*
- * @return true if installing from file referrers is whitelisted
+ * @returns {boolean}
+ * True if installing from file referrers is whitelisted
*/
isFileRequestWhitelisted() {
// Default to whitelisted if the preference does not exist.
return Services.prefs.getBoolPref(PREF_XPI_FILE_WHITELISTED, true);
},
/**
* Called to test whether installing XPI add-ons from a URI is allowed.
*
- * @param aInstallingPrincipal
- * The nsIPrincipal that initiated the install
- * @return true if installing is allowed
+ * @param {nsIPrincipal} aInstallingPrincipal
+ * The nsIPrincipal that initiated the install
+ * @returns {boolean}
+ * True if installing is allowed
*/
isInstallAllowed(aInstallingPrincipal) {
if (!this.isInstallEnabled())
return false;
let uri = aInstallingPrincipal.URI;
// Direct requests without a referrer are either whitelisted or blocked.
@@ -3925,28 +3996,29 @@ var XPIInstall = {
return false;
return true;
},
/**
* Called to get an AddonInstall to download and install an add-on from a URL.
*
- * @param aUrl
+ * @param {nsIURI} aUrl
* The URL to be installed
- * @param aHash
- * A hash for the install
- * @param aName
- * A name for the install
- * @param aIcons
- * Icon URLs for the install
- * @param aVersion
- * A version for the install
- * @param aBrowser
- * The browser performing the install
+ * @param {string?} [aHash]
+ * A hash for the install
+ * @param {string} [aName]
+ * A name for the install
+ * @param {Object} [aIcons]
+ * Icon URLs for the install
+ * @param {string} [aVersion]
+ * A version for the install
+ * @param {XULElement?} [aBrowser]
+ * The browser performing the install
+ * @returns {AddonInstall}
*/
async getInstallForURL(aUrl, aHash, aName, aIcons, aVersion, aBrowser) {
let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
let url = Services.io.newURI(aUrl);
let options = {
hash: aHash,
browser: aBrowser,
@@ -3963,66 +4035,70 @@ var XPIInstall = {
let install = new DownloadAddonInstall(location, url, options);
return install.wrapper;
},
/**
* Called to get an AddonInstall to install an add-on from a local file.
*
- * @param aFile
- * The file to be installed
+ * @param {nsIFile} aFile
+ * The file to be installed
+ * @returns {AddonInstall?}
*/
async getInstallForFile(aFile) {
let install = await createLocalInstall(aFile);
return install ? install.wrapper : null;
},
/**
* Temporarily installs add-on from a local XPI file or directory.
* As this is intended for development, the signature is not checked and
* the add-on does not persist on application restart.
*
- * @param aFile
+ * @param {nsIFile} aFile
* An nsIFile for the unpacked add-on directory or XPI file.
*
- * @return See installAddonFromLocation return value.
+ * @returns {Addon}
+ * See installAddonFromLocation return value.
*/
installTemporaryAddon(aFile) {
return this.installAddonFromLocation(aFile, XPIInternal.TemporaryInstallLocation);
},
/**
* Permanently installs add-on from a local XPI file or directory.
* The signature is checked but the add-on persist on application restart.
*
- * @param aFile
+ * @param {nsIFile} aFile
* An nsIFile for the unpacked add-on directory or XPI file.
*
- * @return See installAddonFromLocation return value.
+ * @returns {Addon}
+ * See installAddonFromLocation return value.
*/
async installAddonFromSources(aFile) {
let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
return this.installAddonFromLocation(aFile, location, "proxy");
},
/**
* Installs add-on from a local XPI file or directory.
*
- * @param aFile
+ * @param {nsIFile} aFile
* An nsIFile for the unpacked add-on directory or XPI file.
- * @param aInstallLocation
+ * @param {InstallLocation} aInstallLocation
* Define a custom install location object to use for the install.
- * @param aInstallAction
+ * @param {string?} [aInstallAction]
* Optional action mode to use when installing the addon
* (see MutableDirectoryInstallLocation.installAddon)
*
- * @return a Promise that resolves to an Addon object on success, or rejects
- * if the add-on is not a valid restartless add-on or if the
- * same ID is already installed.
+ * @returns {Promise<Addon>}
+ * A Promise that resolves to an Addon object on success, or rejects
+ * if the add-on is not a valid restartless add-on or if the
+ * same ID is already installed.
*/
async installAddonFromLocation(aFile, aInstallLocation, aInstallAction) {
if (aFile.exists() && aFile.isFile()) {
flushJarCache(aFile);
}
let addon = await loadManifestFromFile(aFile, aInstallLocation);
aInstallLocation.installAddon({ id: addon.id, source: aFile, action: aInstallAction });
@@ -4128,22 +4204,22 @@ var XPIInstall = {
return addon.wrapper;
},
/**
* Uninstalls an add-on, immediately if possible or marks it as pending
* uninstall if not.
*
- * @param aAddon
- * The DBAddonInternal to uninstall
- * @param aForcePending
- * Force this addon into the pending uninstall state (used
- * e.g. while the add-on manager is open and offering an
- * "undo" button)
+ * @param {DBAddonInternal} aAddon
+ * The DBAddonInternal to uninstall
+ * @param {boolean} aForcePending
+ * Force this addon into the pending uninstall state (used
+ * e.g. while the add-on manager is open and offering an
+ * "undo" button)
* @throws if the addon cannot be uninstalled because it is in an install
* location that does not allow it
*/
async uninstallAddon(aAddon, aForcePending) {
if (!(aAddon.inDatabase))
throw new Error("Cannot uninstall addon " + aAddon.id + " because it is not installed");
if (aAddon._installLocation.locked)
@@ -4263,18 +4339,18 @@ var XPIInstall = {
// Notify any other providers that a new theme has been enabled
if (isTheme(aAddon.type) && aAddon.active)
AddonManagerPrivate.notifyAddonChanged(null, aAddon.type);
},
/**
* Cancels the pending uninstall of an add-on.
*
- * @param aAddon
- * The DBAddonInternal to cancel uninstall for
+ * @param {DBAddonInternal} aAddon
+ * The DBAddonInternal to cancel uninstall for
*/
cancelUninstallAddon(aAddon) {
if (!(aAddon.inDatabase))
throw new Error("Can only cancel uninstall for installed addons.");
if (!aAddon.pendingUninstall)
throw new Error("Add-on is not marked to be uninstalled");
if (aAddon._installLocation.name != KEY_APP_TEMPORARY)
@@ -4307,18 +4383,22 @@ var XPIInstall = {
AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false);
},
/**
* If the application has been upgraded and there are add-ons outside the
* application directory then we may need to synchronize compatibility
* information but only if the mismatch UI isn't disabled.
*
- * @returns null if no update check is needed, otherwise an array of add-on
- * IDs to check for updates.
+ * @param {boolean} aAppChanged
+ * True id the app version has changed since the last startup.
+ *
+ * @returns {Array<string>?}
+ * null if no update check is needed, otherwise an array of add-on
+ * IDs to check for updates.
*/
shouldForceUpdateCheck(aAppChanged) {
AddonManagerPrivate.recordSimpleMeasure("XPIDB_metadata_age", AddonRepository.metadataAge());
let startupChanges = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
logger.debug("shouldForceUpdateCheck startupChanges: " + startupChanges.toSource());
AddonManagerPrivate.recordSimpleMeasure("XPIDB_startup_disabled", startupChanges.length);