зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1363925: Part 4 - Move XPIProvider install methods to XPIInstall. r=aswan
MozReview-Commit-ID: DiPA01emGA9 --HG-- extra : rebase_source : e7e755c7ced75b2d884e85349989326d57cdd533 extra : histedit_source : e1227a92de4edbb1994c4f8981d4fc7ec46a637e
This commit is contained in:
Родитель
33ff77dabf
Коммит
58a8659f77
|
@ -5,11 +5,8 @@
|
|||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = [
|
||||
"DownloadAddonInstall",
|
||||
"LocalAddonInstall",
|
||||
"UpdateChecker",
|
||||
"XPIInstall",
|
||||
"loadManifestFromFile",
|
||||
"verifyBundleSignedState",
|
||||
];
|
||||
|
||||
|
@ -36,12 +33,14 @@ ChromeUtils.defineModuleGetter(this, "FileUtils",
|
|||
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");
|
||||
|
||||
|
@ -76,8 +75,15 @@ ChromeUtils.defineModuleGetter(this, "XPIProvider",
|
|||
|
||||
const PREF_ALLOW_NON_RESTARTLESS = "extensions.legacy.non-restartless.enabled";
|
||||
const PREF_DISTRO_ADDONS_PERMS = "extensions.distroAddons.promptForPermissions";
|
||||
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, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign, recordAddonTelemetry */
|
||||
/* 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",
|
||||
|
@ -89,6 +95,7 @@ const XPI_INTERNAL_SYMBOLS = [
|
|||
"SIGNED_TYPES",
|
||||
"TEMPORARY_ADDON_SUFFIX",
|
||||
"TOOLKIT_ID",
|
||||
"XPI_PERMISSION",
|
||||
"XPIDatabase",
|
||||
"XPIStates",
|
||||
"getExternalType",
|
||||
|
@ -405,6 +412,21 @@ XPIPackage = class XPIPackage extends Package {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
@ -1084,6 +1106,13 @@ function escapeAddonURI(aAddon, aUri, aUpdateType, aAppVersion) {
|
|||
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 {
|
||||
|
@ -3563,6 +3592,7 @@ var XPIInstall = {
|
|||
createLocalInstall,
|
||||
flushChromeCaches,
|
||||
flushJarCache,
|
||||
newVersionReason,
|
||||
recursiveRemove,
|
||||
syncLoadManifestFromFile,
|
||||
|
||||
|
@ -3622,6 +3652,585 @@ var XPIInstall = {
|
|||
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,
|
||||
};
|
||||
|
|
|
@ -23,31 +23,21 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
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"]);
|
||||
|
||||
|
@ -63,20 +53,14 @@ 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";
|
||||
|
@ -442,28 +426,6 @@ function findMatchingStaticBlocklistItem(aAddon) {
|
|||
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.
|
||||
|
@ -1842,7 +1804,7 @@ var XPIProvider = {
|
|||
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,
|
||||
|
@ -1971,7 +1933,7 @@ var XPIProvider = {
|
|||
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));
|
||||
}
|
||||
|
||||
|
@ -2145,133 +2107,6 @@ var XPIProvider = {
|
|||
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.
|
||||
*/
|
||||
|
@ -2406,7 +2241,7 @@ var XPIProvider = {
|
|||
// 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,
|
||||
|
@ -2709,286 +2544,11 @@ var XPIProvider = {
|
|||
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.
|
||||
|
@ -3676,186 +3236,17 @@ var XPIProvider = {
|
|||
|
||||
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);
|
||||
|
@ -5363,8 +4754,10 @@ var XPIInternal = {
|
|||
PREF_SYSTEM_ADDON_SET,
|
||||
SIGNED_TYPES,
|
||||
SystemAddonInstallLocation,
|
||||
TemporaryInstallLocation,
|
||||
TEMPORARY_ADDON_SUFFIX,
|
||||
TOOLKIT_ID,
|
||||
XPI_PERMISSION,
|
||||
XPIStates,
|
||||
awaitPromise,
|
||||
getExternalType,
|
||||
|
|
Загрузка…
Ссылка в новой задаче