--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm
@@ -1,20 +1,17 @@
/* 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";
var EXPORTED_SYMBOLS = [
- "DownloadAddonInstall",
- "LocalAddonInstall",
"UpdateChecker",
"XPIInstall",
- "loadManifestFromFile",
"verifyBundleSignedState",
];
/* globals DownloadAddonInstall, LocalAddonInstall */
Cu.importGlobalProperties(["TextDecoder", "TextEncoder", "fetch"]);
ChromeUtils.import("resource://gre/modules/Services.jsm");
@@ -31,22 +28,24 @@ ChromeUtils.defineModuleGetter(this, "Ce
"resource://gre/modules/CertUtils.jsm");
ChromeUtils.defineModuleGetter(this, "ExtensionData",
"resource://gre/modules/Extension.jsm");
ChromeUtils.defineModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "IconDetails", () => {
return ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm", {}).ExtensionParent.IconDetails;
});
-ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
- "resource://gre/modules/LightweightThemeManager.jsm");
ChromeUtils.defineModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
ChromeUtils.defineModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
+ChromeUtils.defineModuleGetter(this, "ProductAddonChecker",
+ "resource://gre/modules/addons/ProductAddonChecker.jsm");
+ChromeUtils.defineModuleGetter(this, "UpdateUtils",
+ "resource://gre/modules/UpdateUtils.jsm");
ChromeUtils.defineModuleGetter(this, "ZipUtils",
"resource://gre/modules/ZipUtils.jsm");
const {nsIBlocklistService} = Ci;
const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
"initWithPath");
@@ -71,29 +70,37 @@ XPCOMUtils.defineLazyServiceGetters(this
ChromeUtils.defineModuleGetter(this, "XPIInternal",
"resource://gre/modules/addons/XPIProvider.jsm");
ChromeUtils.defineModuleGetter(this, "XPIProvider",
"resource://gre/modules/addons/XPIProvider.jsm");
const PREF_ALLOW_NON_RESTARTLESS = "extensions.legacy.non-restartless.enabled";
const PREF_DISTRO_ADDONS_PERMS = "extensions.distroAddons.promptForPermissions";
-
-/* globals AddonInternal, BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPIDatabase, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign, recordAddonTelemetry */
+const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin";
+const PREF_PENDING_OPERATIONS = "extensions.pendingOperations";
+const PREF_SYSTEM_ADDON_UPDATE_URL = "extensions.systemAddon.update.url";
+const PREF_XPI_ENABLED = "xpinstall.enabled";
+const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest";
+const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest";
+const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required";
+
+/* globals AddonInternal, BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPIDatabase, XPI_PERMISSION, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign, recordAddonTelemetry */
const XPI_INTERNAL_SYMBOLS = [
"AddonInternal",
"BOOTSTRAP_REASONS",
"KEY_APP_SYSTEM_ADDONS",
"KEY_APP_SYSTEM_DEFAULTS",
"KEY_APP_TEMPORARY",
"PREF_BRANCH_INSTALLED_ADDON",
"PREF_SYSTEM_ADDON_SET",
"SIGNED_TYPES",
"TEMPORARY_ADDON_SUFFIX",
"TOOLKIT_ID",
+ "XPI_PERMISSION",
"XPIDatabase",
"XPIStates",
"getExternalType",
"isTheme",
"isUsableAddon",
"isWebExtension",
"mustSign",
"recordAddonTelemetry",
@@ -400,16 +407,31 @@ XPIPackage = class XPIPackage extends Pa
}
flushCache() {
flushJarCache(this.file);
this.needFlush = false;
}
};
+/**
+ * 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}
+ */
+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
// before resolving/rejecting itself
function waitForAllPromises(promises) {
return new Promise((resolve, reject) => {
let shouldReject = false;
let rejectValue = null;
let newPromises = promises.map(
@@ -1079,16 +1101,23 @@ function escapeAddonURI(aAddon, aUri, aU
compatMode = "ignore";
else if (AddonManager.strictCompatibility)
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.
+ */
+function addonMap(addons) {
+ return new Map(addons.map(a => [a.id, a]));
+}
+
async function removeAsync(aFile) {
let info = null;
try {
info = await OS.File.stat(aFile.path);
if (info.isDir)
await OS.File.removeDir(aFile.path);
else
await OS.File.remove(aFile.path);
@@ -3558,16 +3587,17 @@ class SystemAddonInstallLocation extends
// old system add-on upgrade dirs get automatically removed
uninstallAddon(aAddon) {}
}
var XPIInstall = {
createLocalInstall,
flushChromeCaches,
flushJarCache,
+ newVersionReason,
recursiveRemove,
syncLoadManifestFromFile,
/**
* @param {string} id
* The expected ID of the add-on.
* @param {nsIFile} file
* The XPI file to install the add-on from.
@@ -3617,11 +3647,590 @@ var XPIInstall = {
XPIStates.addAddon(addon);
logger.debug("Installed distribution add-on " + id);
Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true);
return addon;
},
+ async updateSystemAddons() {
+ let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
+ if (!systemAddonLocation)
+ return;
+
+ // Don't do anything in safe mode
+ if (Services.appinfo.inSafeMode)
+ return;
+
+ // Download the list of system add-ons
+ let url = Services.prefs.getStringPref(PREF_SYSTEM_ADDON_UPDATE_URL, null);
+ if (!url) {
+ await systemAddonLocation.cleanDirectories();
+ return;
+ }
+
+ url = await UpdateUtils.formatUpdateURL(url);
+
+ logger.info(`Starting system add-on update check from ${url}.`);
+ let res = await ProductAddonChecker.getProductAddonList(url);
+
+ // If there was no list then do nothing.
+ if (!res || !res.gmpAddons) {
+ logger.info("No system add-ons list was returned.");
+ await systemAddonLocation.cleanDirectories();
+ return;
+ }
+
+ let addonList = new Map(
+ res.gmpAddons.map(spec => [spec.id, { spec, path: null, addon: null }]));
+
+ let setMatches = (wanted, existing) => {
+ if (wanted.size != existing.size)
+ return false;
+
+ for (let [id, addon] of existing) {
+ let wantedInfo = wanted.get(id);
+
+ if (!wantedInfo)
+ return false;
+ if (wantedInfo.spec.version != addon.version)
+ return false;
+ }
+
+ return true;
+ };
+
+ // If this matches the current set in the profile location then do nothing.
+ let updatedAddons = addonMap(await XPIDatabase.getAddonsInLocation(KEY_APP_SYSTEM_ADDONS));
+ if (setMatches(addonList, updatedAddons)) {
+ logger.info("Retaining existing updated system add-ons.");
+ await systemAddonLocation.cleanDirectories();
+ return;
+ }
+
+ // If this matches the current set in the default location then reset the
+ // updated set.
+ let defaultAddons = addonMap(await XPIDatabase.getAddonsInLocation(KEY_APP_SYSTEM_DEFAULTS));
+ if (setMatches(addonList, defaultAddons)) {
+ logger.info("Resetting system add-ons.");
+ systemAddonLocation.resetAddonSet();
+ await systemAddonLocation.cleanDirectories();
+ return;
+ }
+
+ // Download all the add-ons
+ async function downloadAddon(item) {
+ try {
+ let sourceAddon = updatedAddons.get(item.spec.id);
+ if (sourceAddon && sourceAddon.version == item.spec.version) {
+ // Copying the file to a temporary location has some benefits. If the
+ // file is locked and cannot be read then we'll fall back to
+ // downloading a fresh copy. It also means we don't have to remember
+ // whether to delete the temporary copy later.
+ try {
+ let path = OS.Path.join(OS.Constants.Path.tmpDir, "tmpaddon");
+ let unique = await OS.File.openUnique(path);
+ unique.file.close();
+ await OS.File.copy(sourceAddon._sourceBundle.path, unique.path);
+ // Make sure to update file modification times so this is detected
+ // as a new add-on.
+ await OS.File.setDates(unique.path);
+ item.path = unique.path;
+ } catch (e) {
+ logger.warn(`Failed make temporary copy of ${sourceAddon._sourceBundle.path}.`, e);
+ }
+ }
+ if (!item.path) {
+ item.path = await ProductAddonChecker.downloadAddon(item.spec);
+ }
+ item.addon = await loadManifestFromFile(nsIFile(item.path), systemAddonLocation);
+ } catch (e) {
+ logger.error(`Failed to download system add-on ${item.spec.id}`, e);
+ }
+ }
+ await Promise.all(Array.from(addonList.values()).map(downloadAddon));
+
+ // The download promises all resolve regardless, now check if they all
+ // succeeded
+ let validateAddon = (item) => {
+ if (item.spec.id != item.addon.id) {
+ logger.warn(`Downloaded system add-on expected to be ${item.spec.id} but was ${item.addon.id}.`);
+ return false;
+ }
+
+ if (item.spec.version != item.addon.version) {
+ logger.warn(`Expected system add-on ${item.spec.id} to be version ${item.spec.version} but was ${item.addon.version}.`);
+ return false;
+ }
+
+ if (!systemAddonLocation.isValidAddon(item.addon))
+ return false;
+
+ return true;
+ };
+
+ if (!Array.from(addonList.values()).every(item => item.path && item.addon && validateAddon(item))) {
+ throw new Error("Rejecting updated system add-on set that either could not " +
+ "be downloaded or contained unusable add-ons.");
+ }
+
+ // Install into the install location
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ isInstallAllowed(aInstallingPrincipal) {
+ if (!this.isInstallEnabled())
+ return false;
+
+ let uri = aInstallingPrincipal.URI;
+
+ // Direct requests without a referrer are either whitelisted or blocked.
+ if (!uri)
+ return this.isDirectRequestWhitelisted();
+
+ // Local referrers can be whitelisted.
+ if (this.isFileRequestWhitelisted() &&
+ (uri.schemeIs("chrome") || uri.schemeIs("file")))
+ return true;
+
+ XPIProvider.importPermissions();
+
+ let permission = Services.perms.testPermissionFromPrincipal(aInstallingPrincipal, XPI_PERMISSION);
+ if (permission == Ci.nsIPermissionManager.DENY_ACTION)
+ return false;
+
+ let requireWhitelist = Services.prefs.getBoolPref(PREF_XPI_WHITELIST_REQUIRED, true);
+ if (requireWhitelist && (permission != Ci.nsIPermissionManager.ALLOW_ACTION))
+ return false;
+
+ let requireSecureOrigin = Services.prefs.getBoolPref(PREF_INSTALL_REQUIRESECUREORIGIN, true);
+ let safeSchemes = ["https", "chrome", "file"];
+ if (requireSecureOrigin && !safeSchemes.includes(uri.scheme))
+ return false;
+
+ return true;
+ },
+
+ /**
+ * Called to get an AddonInstall to download and install an add-on from a URL.
+ *
+ * @param 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
+ */
+ 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,
+ name: aName,
+ icons: aIcons,
+ version: aVersion,
+ };
+
+ if (url instanceof Ci.nsIFileURL) {
+ let install = new LocalAddonInstall(location, url, options);
+ await install.init();
+ return install.wrapper;
+ }
+
+ 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
+ */
+ 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
+ * An nsIFile for the unpacked add-on directory or XPI file.
+ *
+ * @return 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
+ * An nsIFile for the unpacked add-on directory or XPI file.
+ *
+ * @return 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
+ * An nsIFile for the unpacked add-on directory or XPI file.
+ * @param aInstallLocation
+ * Define a custom install location object to use for the install.
+ * @param 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.
+ */
+ 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 });
+
+ if (addon.appDisabled) {
+ let message = `Add-on ${addon.id} is not compatible with application version.`;
+
+ let app = addon.matchingTargetApplication;
+ if (app) {
+ if (app.minVersion) {
+ message += ` add-on minVersion: ${app.minVersion}.`;
+ }
+ if (app.maxVersion) {
+ message += ` add-on maxVersion: ${app.maxVersion}.`;
+ }
+ }
+ throw new Error(message);
+ }
+
+ if (!addon.bootstrap) {
+ throw new Error(`Only restartless (bootstrap) add-ons can be installed from sources: ${addon.id}`);
+ }
+ let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
+ let oldAddon = await XPIDatabase.getVisibleAddonForID(addon.id);
+ let callUpdate = false;
+
+ let extraParams = {};
+ extraParams.temporarilyInstalled = aInstallLocation === XPIInternal.TemporaryInstallLocation;
+ if (oldAddon) {
+ if (!oldAddon.bootstrap) {
+ logger.warn("Non-restartless Add-on is already installed", addon.id);
+ throw new Error("Non-restartless add-on with ID "
+ + oldAddon.id + " is already installed");
+ } else {
+ logger.warn("Addon with ID " + oldAddon.id + " already installed,"
+ + " older version will be disabled");
+
+ addon.installDate = oldAddon.installDate;
+
+ let existingAddonID = oldAddon.id;
+ let existingAddon = oldAddon._sourceBundle;
+
+ // We'll be replacing a currently active bootstrapped add-on so
+ // call its uninstall method
+ let newVersion = addon.version;
+ let oldVersion = oldAddon.version;
+
+ installReason = newVersionReason(oldVersion, newVersion);
+ let uninstallReason = installReason;
+
+ extraParams.newVersion = newVersion;
+ extraParams.oldVersion = oldVersion;
+
+ callUpdate = isWebExtension(oldAddon.type) && isWebExtension(addon.type);
+
+ if (oldAddon.active) {
+ XPIProvider.callBootstrapMethod(oldAddon, existingAddon,
+ "shutdown", uninstallReason,
+ extraParams);
+ }
+
+ if (!callUpdate) {
+ XPIProvider.callBootstrapMethod(oldAddon, existingAddon,
+ "uninstall", uninstallReason, extraParams);
+ }
+ XPIProvider.unloadBootstrapScope(existingAddonID);
+ flushChromeCaches();
+ }
+ } else {
+ addon.installDate = Date.now();
+ }
+
+ let file = addon._sourceBundle;
+
+ let method = callUpdate ? "update" : "install";
+ XPIProvider.callBootstrapMethod(addon, file, method, installReason, extraParams);
+ addon.state = AddonManager.STATE_INSTALLED;
+ logger.debug("Install of temporary addon in " + aFile.path + " completed.");
+ addon.visible = true;
+ addon.enabled = true;
+ addon.active = true;
+ // WebExtension themes are installed as disabled, fix that here.
+ addon.userDisabled = false;
+
+ addon = XPIDatabase.addAddonMetadata(addon, file.path);
+
+ XPIStates.addAddon(addon);
+ XPIDatabase.saveChanges();
+ XPIStates.save();
+
+ AddonManagerPrivate.callAddonListeners("onInstalling", addon.wrapper,
+ false);
+ XPIProvider.callBootstrapMethod(addon, file, "startup", installReason, extraParams);
+ AddonManagerPrivate.callInstallListeners("onExternalInstall",
+ null, addon.wrapper,
+ oldAddon ? oldAddon.wrapper : null,
+ false);
+ AddonManagerPrivate.callAddonListeners("onInstalled", addon.wrapper);
+
+ // Notify providers that a new theme has been enabled.
+ if (isTheme(addon.type))
+ AddonManagerPrivate.notifyAddonChanged(addon.id, addon.type, false);
+
+ 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)
+ * @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)
+ throw new Error("Cannot uninstall addon " + aAddon.id
+ + " from locked install location " + aAddon._installLocation.name);
+
+ if (aForcePending && aAddon.pendingUninstall)
+ throw new Error("Add-on is already marked to be uninstalled");
+
+ aAddon._hasResourceCache.clear();
+
+ if (aAddon._updateCheck) {
+ logger.debug("Cancel in-progress update check for " + aAddon.id);
+ aAddon._updateCheck.cancel();
+ }
+
+ let wasPending = aAddon.pendingUninstall;
+
+ if (aForcePending) {
+ // We create an empty directory in the staging directory to indicate
+ // that an uninstall is necessary on next startup. Temporary add-ons are
+ // automatically uninstalled on shutdown anyway so there is no need to
+ // do this for them.
+ if (aAddon._installLocation.name != KEY_APP_TEMPORARY) {
+ let stage = getFile(aAddon.id, aAddon._installLocation.getStagingDir());
+ if (!stage.exists())
+ stage.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ }
+
+ XPIDatabase.setAddonProperties(aAddon, {
+ pendingUninstall: true
+ });
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
+ let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id);
+ if (xpiState) {
+ xpiState.enabled = false;
+ XPIStates.save();
+ } else {
+ logger.warn("Can't find XPI state while uninstalling ${id} from ${location}", aAddon);
+ }
+ }
+
+ // If the add-on is not visible then there is no need to notify listeners.
+ if (!aAddon.visible)
+ return;
+
+ let wrapper = aAddon.wrapper;
+
+ // If the add-on wasn't already pending uninstall then notify listeners.
+ if (!wasPending) {
+ AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper,
+ !!aForcePending);
+ }
+
+ let reason = BOOTSTRAP_REASONS.ADDON_UNINSTALL;
+ let callUpdate = false;
+ let existingAddon = XPIStates.findAddon(aAddon.id, loc =>
+ loc.name != aAddon._installLocation.name);
+ if (existingAddon) {
+ reason = newVersionReason(aAddon.version, existingAddon.version);
+ callUpdate = isWebExtension(aAddon.type) && isWebExtension(existingAddon.type);
+ }
+
+ if (!aForcePending) {
+ if (aAddon.bootstrap) {
+ if (aAddon.active) {
+ XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
+ reason);
+ }
+
+ if (!callUpdate) {
+ XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "uninstall",
+ reason);
+ }
+ XPIStates.disableAddon(aAddon.id);
+ XPIProvider.unloadBootstrapScope(aAddon.id);
+ flushChromeCaches();
+ }
+ aAddon._installLocation.uninstallAddon(aAddon.id);
+ XPIDatabase.removeAddonMetadata(aAddon);
+ XPIStates.removeAddon(aAddon.location, aAddon.id);
+ AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
+
+ if (existingAddon) {
+ let existing = await XPIDatabase.getAddonInLocation(aAddon.id, existingAddon.location.name);
+ XPIDatabase.makeAddonVisible(existing);
+
+ let wrappedAddon = existing.wrapper;
+ AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false);
+
+ if (!existing.disabled) {
+ XPIDatabase.updateAddonActive(existing, true);
+ }
+
+ if (aAddon.bootstrap) {
+ let method = callUpdate ? "update" : "install";
+ XPIProvider.callBootstrapMethod(existing, existing._sourceBundle,
+ method, reason);
+
+ if (existing.active) {
+ XPIProvider.callBootstrapMethod(existing, existing._sourceBundle,
+ "startup", reason);
+ } else {
+ XPIProvider.unloadBootstrapScope(existing.id);
+ }
+ }
+
+ AddonManagerPrivate.callAddonListeners("onInstalled", wrappedAddon);
+ }
+ } else if (aAddon.bootstrap && aAddon.active) {
+ XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown", reason);
+ XPIStates.disableAddon(aAddon.id);
+ XPIProvider.unloadBootstrapScope(aAddon.id);
+ XPIDatabase.updateAddonActive(aAddon, false);
+ }
+
+ // 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
+ */
+ 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)
+ aAddon._installLocation.cleanStagingDir([aAddon.id]);
+
+ XPIDatabase.setAddonProperties(aAddon, {
+ pendingUninstall: false
+ });
+
+ if (!aAddon.visible)
+ return;
+
+ XPIStates.getAddon(aAddon.location, aAddon.id).syncWithDB(aAddon);
+ XPIStates.save();
+
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
+
+ // TODO hide hidden add-ons (bug 557710)
+ let wrapper = aAddon.wrapper;
+ AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);
+
+ if (aAddon.bootstrap && !aAddon.disabled) {
+ XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
+ BOOTSTRAP_REASONS.ADDON_INSTALL);
+ XPIDatabase.updateAddonActive(aAddon, true);
+ }
+
+ // Notify any other providers that this theme is now enabled again.
+ if (isTheme(aAddon.type) && aAddon.active)
+ AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false);
+ },
+
MutableDirectoryInstallLocation,
SystemAddonInstallLocation,
};
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -18,70 +18,54 @@ XPCOMUtils.defineLazyModuleGetters(this,
AppConstants: "resource://gre/modules/AppConstants.jsm",
Extension: "resource://gre/modules/Extension.jsm",
Langpack: "resource://gre/modules/Extension.jsm",
LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
FileUtils: "resource://gre/modules/FileUtils.jsm",
PermissionsUtils: "resource://gre/modules/PermissionsUtils.jsm",
OS: "resource://gre/modules/osfile.jsm",
ConsoleAPI: "resource://gre/modules/Console.jsm",
- ProductAddonChecker: "resource://gre/modules/addons/ProductAddonChecker.jsm",
- UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
JSONFile: "resource://gre/modules/JSONFile.jsm",
LegacyExtensionsUtils: "resource://gre/modules/LegacyExtensionsUtils.jsm",
setTimeout: "resource://gre/modules/Timer.jsm",
clearTimeout: "resource://gre/modules/Timer.jsm",
- DownloadAddonInstall: "resource://gre/modules/addons/XPIInstall.jsm",
- LocalAddonInstall: "resource://gre/modules/addons/XPIInstall.jsm",
UpdateChecker: "resource://gre/modules/addons/XPIInstall.jsm",
XPIInstall: "resource://gre/modules/addons/XPIInstall.jsm",
- loadManifestFromFile: "resource://gre/modules/addons/XPIInstall.jsm",
verifyBundleSignedState: "resource://gre/modules/addons/XPIInstall.jsm",
});
const {nsIBlocklistService} = Ci;
-XPCOMUtils.defineLazyServiceGetters(this, {
- AddonPolicyService: ["@mozilla.org/addons/policy-service;1", "nsIAddonPolicyService"],
- aomStartup: ["@mozilla.org/addons/addon-manager-startup;1", "amIAddonManagerStartup"],
-});
-
-XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => {
- return new TextDecoder();
-});
+XPCOMUtils.defineLazyServiceGetter(this, "aomStartup",
+ "@mozilla.org/addons/addon-manager-startup;1",
+ "amIAddonManagerStartup");
Cu.importGlobalProperties(["URL"]);
const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
"initWithPath");
const PREF_DB_SCHEMA = "extensions.databaseSchema";
const PREF_XPI_STATE = "extensions.xpiState";
const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL";
const PREF_BOOTSTRAP_ADDONS = "extensions.bootstrappedAddons";
const PREF_PENDING_OPERATIONS = "extensions.pendingOperations";
const PREF_EM_EXTENSION_FORMAT = "extensions.";
const PREF_EM_ENABLED_SCOPES = "extensions.enabledScopes";
const PREF_EM_STARTUP_SCAN_SCOPES = "extensions.startupScanScopes";
const PREF_EM_SHOW_MISMATCH_UI = "extensions.showMismatchUI";
-const PREF_XPI_ENABLED = "xpinstall.enabled";
-const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required";
-const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest";
-const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest";
// xpinstall.signatures.required only supported in dev builds
const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required";
const PREF_XPI_SIGNATURES_DEV_ROOT = "xpinstall.signatures.dev-root";
const PREF_LANGPACK_SIGNATURES = "extensions.langpacks.signatures.required";
const PREF_XPI_PERMISSIONS_BRANCH = "xpinstall.";
-const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin";
const PREF_INSTALL_DISTRO_ADDONS = "extensions.installDistroAddons";
const PREF_BRANCH_INSTALLED_ADDON = "extensions.installedDistroAddon.";
const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet";
-const PREF_SYSTEM_ADDON_UPDATE_URL = "extensions.systemAddon.update.url";
const PREF_ALLOW_LEGACY = "extensions.legacy.enabled";
const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion";
const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
const PREF_EM_LAST_APP_BUILD_ID = "extensions.lastAppBuildId";
// Specify a list of valid built-in add-ons to load.
@@ -438,38 +422,16 @@ function findMatchingStaticBlocklistItem
return item;
}
}
}
return null;
}
/**
- * 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}
- */
-function newVersionReason(oldVersion, newVersion) {
- return Services.vc.compare(oldVersion, newVersion) <= 0 ?
- BOOTSTRAP_REASONS.ADDON_UPGRADE :
- BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
-}
-
-/**
- * Converts an iterable of addon objects into a map with the add-on's ID as key.
- */
-function addonMap(addons) {
- return new Map(addons.map(a => [a.id, a]));
-}
-
-/**
* Helper function that determines whether an addon of a certain type is a
* WebExtension.
*
* @param {String} type
* @return {Boolean}
*/
function isWebExtension(type) {
return type == "webextension" || type == "webextension-theme";
@@ -1837,17 +1799,17 @@ var XPIProvider = {
let reason = BOOTSTRAP_REASONS.APP_SHUTDOWN;
if (addon.disable) {
reason = BOOTSTRAP_REASONS.ADDON_DISABLE;
} else if (addon.location.name == KEY_APP_TEMPORARY) {
reason = BOOTSTRAP_REASONS.ADDON_UNINSTALL;
let existing = XPIStates.findAddon(addon.id, loc =>
loc.name != TemporaryInstallLocation.name);
if (existing) {
- reason = newVersionReason(addon.version, existing.version);
+ reason = XPIInstall.newVersionReason(addon.version, existing.version);
}
}
XPIProvider.callBootstrapMethod(addon, addon.file,
"shutdown", reason);
}
Services.obs.removeObserver(this, "quit-application-granted");
}
}, "quit-application-granted");
@@ -1966,17 +1928,17 @@ var XPIProvider = {
for (let [id, addon] of tempLocation.entries()) {
tempLocation.delete(id);
let reason = BOOTSTRAP_REASONS.ADDON_UNINSTALL;
let existing = XPIStates.findAddon(id, loc => loc != tempLocation);
let callUpdate = false;
if (existing) {
- reason = newVersionReason(addon.version, existing.version);
+ reason = XPIInstall.newVersionReason(addon.version, existing.version);
callUpdate = (isWebExtension(addon.type) && isWebExtension(existing.type));
}
this.callBootstrapMethod(addon, addon.file, "shutdown", reason);
if (!callUpdate) {
this.callBootstrapMethod(addon, addon.file, "uninstall", reason);
}
this.unloadBootstrapScope(id);
@@ -2140,143 +2102,16 @@ var XPIProvider = {
clearTimeout(timer);
if (window) {
window.close();
}
return started;
},
- async updateSystemAddons() {
- let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
- if (!systemAddonLocation)
- return;
-
- // Don't do anything in safe mode
- if (Services.appinfo.inSafeMode)
- return;
-
- // Download the list of system add-ons
- let url = Services.prefs.getStringPref(PREF_SYSTEM_ADDON_UPDATE_URL, null);
- if (!url) {
- await systemAddonLocation.cleanDirectories();
- return;
- }
-
- url = await UpdateUtils.formatUpdateURL(url);
-
- logger.info(`Starting system add-on update check from ${url}.`);
- let res = await ProductAddonChecker.getProductAddonList(url);
-
- // If there was no list then do nothing.
- if (!res || !res.gmpAddons) {
- logger.info("No system add-ons list was returned.");
- await systemAddonLocation.cleanDirectories();
- return;
- }
-
- let addonList = new Map(
- res.gmpAddons.map(spec => [spec.id, { spec, path: null, addon: null }]));
-
- let setMatches = (wanted, existing) => {
- if (wanted.size != existing.size)
- return false;
-
- for (let [id, addon] of existing) {
- let wantedInfo = wanted.get(id);
-
- if (!wantedInfo)
- return false;
- if (wantedInfo.spec.version != addon.version)
- return false;
- }
-
- return true;
- };
-
- // If this matches the current set in the profile location then do nothing.
- let updatedAddons = addonMap(await XPIDatabase.getAddonsInLocation(KEY_APP_SYSTEM_ADDONS));
- if (setMatches(addonList, updatedAddons)) {
- logger.info("Retaining existing updated system add-ons.");
- await systemAddonLocation.cleanDirectories();
- return;
- }
-
- // If this matches the current set in the default location then reset the
- // updated set.
- let defaultAddons = addonMap(await XPIDatabase.getAddonsInLocation(KEY_APP_SYSTEM_DEFAULTS));
- if (setMatches(addonList, defaultAddons)) {
- logger.info("Resetting system add-ons.");
- systemAddonLocation.resetAddonSet();
- await systemAddonLocation.cleanDirectories();
- return;
- }
-
- // Download all the add-ons
- async function downloadAddon(item) {
- try {
- let sourceAddon = updatedAddons.get(item.spec.id);
- if (sourceAddon && sourceAddon.version == item.spec.version) {
- // Copying the file to a temporary location has some benefits. If the
- // file is locked and cannot be read then we'll fall back to
- // downloading a fresh copy. It also means we don't have to remember
- // whether to delete the temporary copy later.
- try {
- let path = OS.Path.join(OS.Constants.Path.tmpDir, "tmpaddon");
- let unique = await OS.File.openUnique(path);
- unique.file.close();
- await OS.File.copy(sourceAddon._sourceBundle.path, unique.path);
- // Make sure to update file modification times so this is detected
- // as a new add-on.
- await OS.File.setDates(unique.path);
- item.path = unique.path;
- } catch (e) {
- logger.warn(`Failed make temporary copy of ${sourceAddon._sourceBundle.path}.`, e);
- }
- }
- if (!item.path) {
- item.path = await ProductAddonChecker.downloadAddon(item.spec);
- }
- item.addon = await loadManifestFromFile(nsIFile(item.path), systemAddonLocation);
- } catch (e) {
- logger.error(`Failed to download system add-on ${item.spec.id}`, e);
- }
- }
- await Promise.all(Array.from(addonList.values()).map(downloadAddon));
-
- // The download promises all resolve regardless, now check if they all
- // succeeded
- let validateAddon = (item) => {
- if (item.spec.id != item.addon.id) {
- logger.warn(`Downloaded system add-on expected to be ${item.spec.id} but was ${item.addon.id}.`);
- return false;
- }
-
- if (item.spec.version != item.addon.version) {
- logger.warn(`Expected system add-on ${item.spec.id} to be version ${item.spec.version} but was ${item.addon.version}.`);
- return false;
- }
-
- if (!systemAddonLocation.isValidAddon(item.addon))
- return false;
-
- return true;
- };
-
- if (!Array.from(addonList.values()).every(item => item.path && item.addon && validateAddon(item))) {
- throw new Error("Rejecting updated system add-on set that either could not " +
- "be downloaded or contained unusable add-ons.");
- }
-
- // Install into the install location
- logger.info("Installing new system add-on set");
- await systemAddonLocation.installAddonSet(Array.from(addonList.values())
- .map(a => a.addon));
- },
-
/**
* Verifies that all installed add-ons are still correctly signed.
*/
async verifySignatures() {
try {
let addons = await XPIDatabase.getAddonList(a => true);
let changes = {
@@ -2401,17 +2236,17 @@ var XPIProvider = {
var file = existingAddon.file;
if (file.exists()) {
oldBootstrap = existingAddon;
// We'll be replacing a currently active bootstrapped add-on so
// call its uninstall method
let newVersion = addon.version;
let oldVersion = existingAddon;
- let uninstallReason = newVersionReason(oldVersion, newVersion);
+ let uninstallReason = XPIInstall.newVersionReason(oldVersion, newVersion);
this.callBootstrapMethod(existingAddon,
file, "uninstall", uninstallReason,
{ newVersion });
this.unloadBootstrapScope(id);
XPIInstall.flushChromeCaches();
}
} catch (e) {
@@ -2704,297 +2539,22 @@ var XPIProvider = {
* @param aMimetype
* The mimetype to check for
* @return true if the mimetype is application/x-xpinstall
*/
supportsMimetype(aMimetype) {
return aMimetype == "application/x-xpinstall";
},
- /**
- * Called to test whether installing XPI add-ons is enabled.
- *
- * @return 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
- */
- 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
- */
- 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
- */
- isInstallAllowed(aInstallingPrincipal) {
- if (!this.isInstallEnabled())
- return false;
-
- let uri = aInstallingPrincipal.URI;
-
- // Direct requests without a referrer are either whitelisted or blocked.
- if (!uri)
- return this.isDirectRequestWhitelisted();
-
- // Local referrers can be whitelisted.
- if (this.isFileRequestWhitelisted() &&
- (uri.schemeIs("chrome") || uri.schemeIs("file")))
- return true;
-
- this.importPermissions();
-
- let permission = Services.perms.testPermissionFromPrincipal(aInstallingPrincipal, XPI_PERMISSION);
- if (permission == Ci.nsIPermissionManager.DENY_ACTION)
- return false;
-
- let requireWhitelist = Services.prefs.getBoolPref(PREF_XPI_WHITELIST_REQUIRED, true);
- if (requireWhitelist && (permission != Ci.nsIPermissionManager.ALLOW_ACTION))
- return false;
-
- let requireSecureOrigin = Services.prefs.getBoolPref(PREF_INSTALL_REQUIRESECUREORIGIN, true);
- let safeSchemes = ["https", "chrome", "file"];
- if (requireSecureOrigin && !safeSchemes.includes(uri.scheme))
- return false;
-
- return true;
- },
-
// Identify temporary install IDs.
isTemporaryInstallID(id) {
return id.endsWith(TEMPORARY_ADDON_SUFFIX);
},
/**
- * Called to get an AddonInstall to download and install an add-on from a URL.
- *
- * @param 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
- */
- 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,
- name: aName,
- icons: aIcons,
- version: aVersion,
- };
-
- if (url instanceof Ci.nsIFileURL) {
- let install = new LocalAddonInstall(location, url, options);
- await install.init();
- return install.wrapper;
- }
-
- 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
- */
- async getInstallForFile(aFile) {
- let install = await XPIInstall.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
- * An nsIFile for the unpacked add-on directory or XPI file.
- *
- * @return See installAddonFromLocation return value.
- */
- installTemporaryAddon(aFile) {
- return this.installAddonFromLocation(aFile, 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
- * An nsIFile for the unpacked add-on directory or XPI file.
- *
- * @return 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
- * An nsIFile for the unpacked add-on directory or XPI file.
- * @param aInstallLocation
- * Define a custom install location object to use for the install.
- * @param 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.
- */
- async installAddonFromLocation(aFile, aInstallLocation, aInstallAction) {
- if (aFile.exists() && aFile.isFile()) {
- XPIInstall.flushJarCache(aFile);
- }
- let addon = await loadManifestFromFile(aFile, aInstallLocation);
-
- aInstallLocation.installAddon({ id: addon.id, source: aFile, action: aInstallAction });
-
- if (addon.appDisabled) {
- let message = `Add-on ${addon.id} is not compatible with application version.`;
-
- let app = addon.matchingTargetApplication;
- if (app) {
- if (app.minVersion) {
- message += ` add-on minVersion: ${app.minVersion}.`;
- }
- if (app.maxVersion) {
- message += ` add-on maxVersion: ${app.maxVersion}.`;
- }
- }
- throw new Error(message);
- }
-
- if (!addon.bootstrap) {
- throw new Error("Only restartless (bootstrap) add-ons"
- + " can be installed from sources:", addon.id);
- }
- let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
- let oldAddon = await XPIDatabase.getVisibleAddonForID(addon.id);
- let callUpdate = false;
-
- let extraParams = {};
- extraParams.temporarilyInstalled = aInstallLocation === TemporaryInstallLocation;
- if (oldAddon) {
- if (!oldAddon.bootstrap) {
- logger.warn("Non-restartless Add-on is already installed", addon.id);
- throw new Error("Non-restartless add-on with ID "
- + oldAddon.id + " is already installed");
- } else {
- logger.warn("Addon with ID " + oldAddon.id + " already installed,"
- + " older version will be disabled");
-
- addon.installDate = oldAddon.installDate;
-
- let existingAddonID = oldAddon.id;
- let existingAddon = oldAddon._sourceBundle;
-
- // We'll be replacing a currently active bootstrapped add-on so
- // call its uninstall method
- let newVersion = addon.version;
- let oldVersion = oldAddon.version;
-
- installReason = newVersionReason(oldVersion, newVersion);
- let uninstallReason = installReason;
-
- extraParams.newVersion = newVersion;
- extraParams.oldVersion = oldVersion;
-
- callUpdate = isWebExtension(oldAddon.type) && isWebExtension(addon.type);
-
- if (oldAddon.active) {
- XPIProvider.callBootstrapMethod(oldAddon, existingAddon,
- "shutdown", uninstallReason,
- extraParams);
- }
-
- if (!callUpdate) {
- this.callBootstrapMethod(oldAddon, existingAddon,
- "uninstall", uninstallReason, extraParams);
- }
- this.unloadBootstrapScope(existingAddonID);
- XPIInstall.flushChromeCaches();
- }
- } else {
- addon.installDate = Date.now();
- }
-
- let file = addon._sourceBundle;
-
- let method = callUpdate ? "update" : "install";
- XPIProvider.callBootstrapMethod(addon, file, method, installReason, extraParams);
- addon.state = AddonManager.STATE_INSTALLED;
- logger.debug("Install of temporary addon in " + aFile.path + " completed.");
- addon.visible = true;
- addon.enabled = true;
- addon.active = true;
- // WebExtension themes are installed as disabled, fix that here.
- addon.userDisabled = false;
-
- addon = XPIDatabase.addAddonMetadata(addon, file.path);
-
- XPIStates.addAddon(addon);
- XPIDatabase.saveChanges();
- XPIStates.save();
-
- AddonManagerPrivate.callAddonListeners("onInstalling", addon.wrapper,
- false);
- XPIProvider.callBootstrapMethod(addon, file, "startup", installReason, extraParams);
- AddonManagerPrivate.callInstallListeners("onExternalInstall",
- null, addon.wrapper,
- oldAddon ? oldAddon.wrapper : null,
- false);
- AddonManagerPrivate.callAddonListeners("onInstalled", addon.wrapper);
-
- // Notify providers that a new theme has been enabled.
- if (isTheme(addon.type))
- AddonManagerPrivate.notifyAddonChanged(addon.id, addon.type, false);
-
- return addon.wrapper;
- },
-
- /**
* Sets startupData for the given addon. The provided data will be stored
* in addonsStartup.json so it is available early during browser startup.
* Note that this file is read synchronously at startup, so startupData
* should be used with care.
*
* @param {string} aID
* The id of the addon to save startup data for.
* @param {any} aData
@@ -3671,196 +3231,27 @@ var XPIProvider = {
}
} else if (isDisabled && !aBecauseSelecting) {
AddonManagerPrivate.notifyAddonChanged(null, "theme");
}
}
return isDisabled;
},
-
- /**
- * 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)
- * @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)
- throw new Error("Cannot uninstall addon " + aAddon.id
- + " from locked install location " + aAddon._installLocation.name);
-
- if (aForcePending && aAddon.pendingUninstall)
- throw new Error("Add-on is already marked to be uninstalled");
-
- aAddon._hasResourceCache.clear();
-
- if (aAddon._updateCheck) {
- logger.debug("Cancel in-progress update check for " + aAddon.id);
- aAddon._updateCheck.cancel();
- }
-
- let wasPending = aAddon.pendingUninstall;
-
- if (aForcePending) {
- // We create an empty directory in the staging directory to indicate
- // that an uninstall is necessary on next startup. Temporary add-ons are
- // automatically uninstalled on shutdown anyway so there is no need to
- // do this for them.
- if (aAddon._installLocation.name != KEY_APP_TEMPORARY) {
- let stage = getFile(aAddon.id, aAddon._installLocation.getStagingDir());
- if (!stage.exists())
- stage.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
- }
-
- XPIDatabase.setAddonProperties(aAddon, {
- pendingUninstall: true
- });
- Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
- let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id);
- if (xpiState) {
- xpiState.enabled = false;
- XPIStates.save();
- } else {
- logger.warn("Can't find XPI state while uninstalling ${id} from ${location}", aAddon);
- }
- }
-
- // If the add-on is not visible then there is no need to notify listeners.
- if (!aAddon.visible)
- return;
-
- let wrapper = aAddon.wrapper;
-
- // If the add-on wasn't already pending uninstall then notify listeners.
- if (!wasPending) {
- AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper,
- !!aForcePending);
- }
-
- let reason = BOOTSTRAP_REASONS.ADDON_UNINSTALL;
- let callUpdate = false;
- let existingAddon = XPIStates.findAddon(aAddon.id, loc =>
- loc.name != aAddon._installLocation.name);
- if (existingAddon) {
- reason = newVersionReason(aAddon.version, existingAddon.version);
- callUpdate = isWebExtension(aAddon.type) && isWebExtension(existingAddon.type);
- }
-
- if (!aForcePending) {
- if (aAddon.bootstrap) {
- if (aAddon.active) {
- this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
- reason);
- }
-
- if (!callUpdate) {
- this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "uninstall",
- reason);
- }
- XPIStates.disableAddon(aAddon.id);
- this.unloadBootstrapScope(aAddon.id);
- XPIInstall.flushChromeCaches();
- }
- aAddon._installLocation.uninstallAddon(aAddon.id);
- XPIDatabase.removeAddonMetadata(aAddon);
- XPIStates.removeAddon(aAddon.location, aAddon.id);
- AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
-
- if (existingAddon) {
- let existing = await XPIDatabase.getAddonInLocation(aAddon.id, existingAddon.location.name);
- XPIDatabase.makeAddonVisible(existing);
-
- let wrappedAddon = existing.wrapper;
- AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false);
-
- if (!existing.disabled) {
- XPIDatabase.updateAddonActive(existing, true);
- }
-
- if (aAddon.bootstrap) {
- let method = callUpdate ? "update" : "install";
- XPIProvider.callBootstrapMethod(existing, existing._sourceBundle,
- method, reason);
-
- if (existing.active) {
- XPIProvider.callBootstrapMethod(existing, existing._sourceBundle,
- "startup", reason);
- } else {
- XPIProvider.unloadBootstrapScope(existing.id);
- }
- }
-
- AddonManagerPrivate.callAddonListeners("onInstalled", wrappedAddon);
- }
- } else if (aAddon.bootstrap && aAddon.active) {
- this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown", reason);
- XPIStates.disableAddon(aAddon.id);
- this.unloadBootstrapScope(aAddon.id);
- XPIDatabase.updateAddonActive(aAddon, false);
- }
-
- // 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
- */
- 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)
- aAddon._installLocation.cleanStagingDir([aAddon.id]);
-
- XPIDatabase.setAddonProperties(aAddon, {
- pendingUninstall: false
- });
-
- if (!aAddon.visible)
- return;
-
- XPIStates.getAddon(aAddon.location, aAddon.id).syncWithDB(aAddon);
- XPIStates.save();
-
- Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
-
- // TODO hide hidden add-ons (bug 557710)
- let wrapper = aAddon.wrapper;
- AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);
-
- if (aAddon.bootstrap && !aAddon.disabled) {
- this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
- BOOTSTRAP_REASONS.ADDON_INSTALL);
- XPIDatabase.updateAddonActive(aAddon, true);
- }
-
- // Notify any other providers that this theme is now enabled again.
- if (isTheme(aAddon.type) && aAddon.active)
- AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false);
- }
};
+for (let meth of ["cancelUninstallAddon", "getInstallForFile",
+ "getInstallForURL", "installAddonFromLocation",
+ "installAddonFromSources", "installTemporaryAddon",
+ "isInstallAllowed", "uninstallAddon", "updateSystemAddons"]) {
+ XPIProvider[meth] = function() {
+ return XPIInstall[meth](...arguments);
+ };
+}
+
// Maps instances of AddonInternal to AddonWrapper
const wrapperMap = new WeakMap();
let addonFor = wrapper => wrapperMap.get(wrapper);
/**
* The AddonInternal is an internal only representation of add-ons. It may
* have come from the database (see DBAddonInternal in XPIProviderUtils.jsm)
* or an install manifest.
@@ -5238,21 +4629,21 @@ class SystemAddonInstallLocation extends
*/
isActive() {
return this._directory != null;
}
}
forwardInstallMethods(SystemAddonInstallLocation,
["cleanDirectories", "cleanStagingDir", "getStagingDir",
- "getTrashDir", "installAddon", "installAddon",
- "installAddonSet", "isValid", "isValidAddon",
- "releaseStagingDir", "requestStagingDir",
- "resetAddonSet", "resumeAddonSet", "uninstallAddon",
- "uninstallAddon"]);
+ "getTrashDir", "installAddon", "installAddon",
+ "installAddonSet", "isValid", "isValidAddon",
+ "releaseStagingDir", "requestStagingDir",
+ "resetAddonSet", "resumeAddonSet", "uninstallAddon",
+ "uninstallAddon"]);
/** An object which identifies an install location for temporary add-ons.
*/
const TemporaryInstallLocation = { locked: false, name: KEY_APP_TEMPORARY,
scope: AddonManager.SCOPE_TEMPORARY,
getAddonLocations: () => [], isLinkedAddon: () => false, installAddon:
() => {}, uninstallAddon: (aAddon) => {}, getStagingDir: () => {},
};
@@ -5358,18 +4749,20 @@ var XPIInternal = {
BOOTSTRAP_REASONS,
KEY_APP_SYSTEM_ADDONS,
KEY_APP_SYSTEM_DEFAULTS,
KEY_APP_TEMPORARY,
PREF_BRANCH_INSTALLED_ADDON,
PREF_SYSTEM_ADDON_SET,
SIGNED_TYPES,
SystemAddonInstallLocation,
+ TemporaryInstallLocation,
TEMPORARY_ADDON_SUFFIX,
TOOLKIT_ID,
+ XPI_PERMISSION,
XPIStates,
awaitPromise,
getExternalType,
isTheme,
isUsableAddon,
isWebExtension,
mustSign,
recordAddonTelemetry,