--- a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
@@ -1975,17 +1975,17 @@ this.XPIDatabase = {
if (!aAddon.isPlatformCompatible) {
logger.warn(`Add-on ${aAddon.id} is not compatible with platform.`);
return false;
}
if (aAddon.dependencies.length) {
let isActive = id => {
let active = XPIProvider.activeAddons.get(id);
- return active && !active.disable;
+ return active && !active._pendingDisable;
};
if (aAddon.dependencies.some(id => !isActive(id)))
return false;
}
if (this.isDisabledLegacy(aAddon)) {
logger.warn(`disabling legacy extension ${aAddon.id}`);
@@ -2311,26 +2311,22 @@ this.XPIDatabase = {
if (isDisabled) {
AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false);
} else {
AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false);
}
this.updateAddonActive(aAddon, !isDisabled);
+ let bootstrap = XPIInternal.BootstrapScope.get(aAddon);
if (isDisabled) {
- if (XPIProvider.activeAddons.has(aAddon.id)) {
- XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
- BOOTSTRAP_REASONS.ADDON_DISABLE);
- XPIProvider.unloadBootstrapScope(aAddon.id);
- }
+ bootstrap.disable();
AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
} else {
- XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
- BOOTSTRAP_REASONS.ADDON_ENABLE);
+ bootstrap.startup(BOOTSTRAP_REASONS.ADDON_ENABLE);
AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
}
}
// Notify any other providers that a new theme has been enabled
if (isTheme(aAddon.type)) {
if (!isDisabled) {
AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type);
@@ -2861,17 +2857,17 @@ this.XPIDatabaseReconcile = {
addonStates.get(addon));
this.applyStartupChange(addon, previousVisible.get(id), xpiState);
previousVisible.delete(id);
}
for (let [id, addon] of previousVisible) {
if (addonExists(addon)) {
- this.callBootstrapUninstall(addon, BOOTSTRAP_REASONS.ADDON_UNINSTALL);
+ XPIInternal.BootstrapScope.get(addon).uninstall();
}
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED, id);
XPIStates.removeAddon(addon.location, id);
addon.visible = false;
addon.active = false;
}
if (previousVisible.size) {
@@ -2927,34 +2923,26 @@ this.XPIDatabaseReconcile = {
let isActive = !currentAddon.disabled;
let wasActive = previousAddon ? previousAddon.active : currentAddon.active;
if (previousAddon) {
if (previousAddon !== currentAddon) {
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, id);
- let installReason = Services.vc.compare(previousAddon.version, currentAddon.version) < 0 ?
- BOOTSTRAP_REASONS.ADDON_UPGRADE :
- BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
-
if (previousAddon._installLocation &&
previousAddon._sourceBundle.exists() &&
!previousAddon._sourceBundle.equals(currentAddon._sourceBundle)) {
- this.callBootstrapUninstall(previousAddon, installReason,
- { newVersion: currentAddon.version });
+ XPIInternal.BootstrapScope.get(previousAddon).update(
+ currentAddon);
+ } else {
+ let reason = XPIInstall.newVersionReason(previousAddon.version, currentAddon.version);
+ XPIInternal.BootstrapScope.get(currentAddon).install(
+ reason, false, {oldVersion: previousAddon.version});
}
-
- XPIInstall.flushChromeCaches();
-
- let file = currentAddon._sourceBundle.clone();
- XPIProvider.callBootstrapMethod(currentAddon, file, "install", installReason,
- { oldVersion: previousAddon.version });
- if (currentAddon.disabled)
- XPIProvider.unloadBootstrapScope(currentAddon.id);
}
if (isActive != wasActive) {
let change = isActive ? AddonManager.STARTUP_CHANGE_ENABLED
: AddonManager.STARTUP_CHANGE_DISABLED;
AddonManagerPrivate.addStartupChange(change, id);
}
} else if (xpiState && xpiState.wasRestored) {
@@ -2969,23 +2957,16 @@ this.XPIDatabaseReconcile = {
// If the add-on is softblocked then assume it is softDisabled
if (currentAddon.blocklistState == Services.blocklist.STATE_SOFTBLOCKED)
currentAddon.softDisabled = true;
else
currentAddon.userDisabled = true;
}
} else {
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED, id);
- XPIProvider.callBootstrapMethod(currentAddon, currentAddon._sourceBundle,
- "install", BOOTSTRAP_REASONS.ADDON_INSTALL);
- if (!isActive)
- XPIProvider.unloadBootstrapScope(currentAddon.id);
+ let scope = XPIInternal.BootstrapScope.get(currentAddon);
+ scope.install();
}
XPIDatabase.makeAddonVisible(currentAddon);
currentAddon.active = isActive;
},
-
- callBootstrapUninstall(addon, reason, extraArgs) {
- XPIProvider.callBootstrapMethod(addon, addon._sourceBundle, "uninstall", reason, extraArgs);
- XPIProvider.unloadBootstrapScope(addon.id);
- },
};
--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm
@@ -1763,101 +1763,67 @@ class AddonInstall {
stagedAddon.append(`${this.addon.id}.xpi`);
await this.stageInstall(false, stagedAddon, isUpgrade);
// The install is completed so it should be removed from the active list
XPIProvider.removeActiveInstall(this);
- // Deactivate and remove the old add-on as necessary
- let reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
- let callUpdate = false;
- if (this.existingAddon) {
- if (Services.vc.compare(this.existingAddon.version, this.addon.version) < 0)
- reason = BOOTSTRAP_REASONS.ADDON_UPGRADE;
- else
- reason = BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
-
- callUpdate = isWebExtension(this.addon.type) && isWebExtension(this.existingAddon.type);
-
- let file = this.existingAddon._sourceBundle;
- if (this.existingAddon.active) {
- XPIProvider.callBootstrapMethod(this.existingAddon, file,
- "shutdown", reason,
- { newVersion: this.addon.version });
- }
-
- if (!callUpdate) {
- XPIProvider.callBootstrapMethod(this.existingAddon, file,
- "uninstall", reason,
- { newVersion: this.addon.version });
- }
- XPIProvider.unloadBootstrapScope(this.existingAddon.id);
- flushChromeCaches();
-
- if (!isUpgrade && this.existingAddon.active) {
+ let install = () => {
+ if (this.existingAddon && this.existingAddon.active && !isUpgrade) {
XPIDatabase.updateAddonActive(this.existingAddon, false);
}
- }
-
- // Install the new add-on into its final location
- let existingAddonID = this.existingAddon ? this.existingAddon.id : null;
- let file = this.installLocation.installAddon({
- id: this.addon.id,
- source: stagedAddon,
- existingAddonID
- });
-
- // Update the metadata in the database
- this.addon._sourceBundle = file;
- this.addon.visible = true;
-
- if (isUpgrade) {
- this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
- file.path);
- let state = XPIStates.getAddon(this.installLocation.name, this.addon.id);
- if (state) {
- state.syncWithDB(this.addon, true);
+
+ // Install the new add-on into its final location
+ let existingAddonID = this.existingAddon ? this.existingAddon.id : null;
+ let file = this.installLocation.installAddon({
+ id: this.addon.id,
+ source: stagedAddon,
+ existingAddonID
+ });
+
+ // Update the metadata in the database
+ this.addon._sourceBundle = file;
+ this.addon.visible = true;
+
+ if (isUpgrade) {
+ this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
+ file.path);
+ let state = XPIStates.getAddon(this.installLocation.name, this.addon.id);
+ if (state) {
+ state.syncWithDB(this.addon, true);
+ } else {
+ logger.warn("Unexpected missing XPI state for add-on ${id}", this.addon);
+ }
} else {
- logger.warn("Unexpected missing XPI state for add-on ${id}", this.addon);
+ this.addon.active = (this.addon.visible && !this.addon.disabled);
+ this.addon = XPIDatabase.addAddonMetadata(this.addon, file.path);
+ XPIStates.addAddon(this.addon);
+ this.addon.installDate = this.addon.updateDate;
+ XPIDatabase.saveChanges();
}
+ XPIStates.save();
+
+ AddonManagerPrivate.callAddonListeners("onInstalled",
+ this.addon.wrapper);
+
+ logger.debug(`Install of ${this.sourceURI.spec} completed.`);
+ this.state = AddonManager.STATE_INSTALLED;
+ this._callInstallListeners("onInstallEnded", this.addon.wrapper);
+ };
+
+ if (this.existingAddon) {
+ XPIInternal.BootstrapScope.get(this.existingAddon).update(
+ this.addon, !this.addon.disabled, install);
} else {
- this.addon.active = (this.addon.visible && !this.addon.disabled);
- this.addon = XPIDatabase.addAddonMetadata(this.addon, file.path);
- XPIStates.addAddon(this.addon);
- this.addon.installDate = this.addon.updateDate;
- XPIDatabase.saveChanges();
- }
- XPIStates.save();
-
- let extraParams = {};
- if (this.existingAddon) {
- extraParams.oldVersion = this.existingAddon.version;
+ install();
+ XPIInternal.BootstrapScope.get(this.addon).install(undefined, true);
}
- let method = callUpdate ? "update" : "install";
- XPIProvider.callBootstrapMethod(this.addon, file, method,
- reason, extraParams);
-
- AddonManagerPrivate.callAddonListeners("onInstalled",
- this.addon.wrapper);
-
- logger.debug("Install of " + this.sourceURI.spec + " completed.");
- this.state = AddonManager.STATE_INSTALLED;
- this._callInstallListeners("onInstallEnded", this.addon.wrapper);
-
- if (this.addon.active) {
- XPIProvider.callBootstrapMethod(this.addon, file, "startup",
- reason, extraParams);
- } else {
- // XXX this makes it dangerous to do some things in onInstallEnded
- // listeners because important cleanup hasn't been done yet
- XPIProvider.unloadBootstrapScope(this.addon.id);
- }
XPIDatabase.recordAddonTelemetry(this.addon);
// Notify providers that a new theme has been enabled.
if (isTheme(this.addon.type) && this.addon.active)
AddonManagerPrivate.notifyAddonChanged(this.addon.id, this.addon.type);
})().catch((e) => {
logger.warn(`Failed to install ${this.file.path} from ${this.sourceURI.spec} to ${stagedAddon.path}`, e);
@@ -3544,52 +3510,41 @@ var XPIInstall = {
if (XPIDatabase.mustSign(addon.type) &&
addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
throw new Error(`Refusing to install staged add-on ${id} with signed state ${addon.signedState}`);
}
addon.importMetadata(metadata);
- var oldBootstrap = null;
logger.debug(`Processing install of ${id} in ${location.name}`);
let existingAddon = XPIStates.findAddon(id);
- if (existingAddon && existingAddon.bootstrapped) {
+ if (existingAddon) {
try {
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);
-
- XPIProvider.callBootstrapMethod(existingAddon,
- file, "uninstall", uninstallReason,
- { newVersion });
- XPIProvider.unloadBootstrapScope(id);
- flushChromeCaches();
+ let newVersion = existingAddon.version;
+ let reason = newVersionReason(existingAddon.version, newVersion);
+
+ XPIInternal.get(existingAddon).uninstall(reason, {newVersion});
}
} catch (e) {
Cu.reportError(e);
}
}
try {
addon._sourceBundle = location.installAddon({
id, source, existingAddonID: id,
});
XPIStates.addAddon(addon);
} catch (e) {
- if (oldBootstrap) {
+ if (existingAddon) {
// Re-install the old add-on
- XPIProvider.callBootstrapMethod(oldBootstrap, existingAddon, "install",
- BOOTSTRAP_REASONS.ADDON_INSTALL);
+ XPIInternal.get(existingAddon).install();
}
throw e;
}
return addon;
},
async updateSystemAddons() {
@@ -3879,81 +3834,55 @@ var XPIInstall = {
}
if (app.maxVersion) {
message += ` add-on maxVersion: ${app.maxVersion}.`;
}
}
throw new Error(message);
}
- let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
let oldAddon = await XPIDatabase.getVisibleAddonForID(addon.id);
- let callUpdate = false;
let extraParams = {};
extraParams.temporarilyInstalled = true;
+
+ let install = () => {
+ 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, addon._sourceBundle.path);
+
+ XPIStates.addAddon(addon);
+ XPIDatabase.saveChanges();
+ XPIStates.save();
+ };
+
if (oldAddon) {
- logger.warn("Addon with ID " + oldAddon.id + " already installed,"
- + " older version will be disabled");
+ 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();
+ XPIInternal.BootstrapScope.get(oldAddon).update(
+ addon, true, install);
} else {
addon.installDate = Date.now();
+
+ install();
+ let bootstrap = XPIInternal.BootstrapScope.get(addon);
+ bootstrap.install(undefined, true, {temporarilyInstalled: true});
}
- 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))
@@ -4026,72 +3955,56 @@ var XPIInstall = {
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);
- }
-
+
+ let bootstrap = XPIInternal.BootstrapScope.get(aAddon);
if (!aForcePending) {
- if (aAddon.active) {
- XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
- reason);
- }
-
- if (!callUpdate) {
- XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "uninstall",
- reason);
+ let existing;
+ if (existingAddon) {
+ existing = await XPIDatabase.getAddonInLocation(aAddon.id, existingAddon.location.name);
}
- 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);
+
+ let uninstall = () => {
+ XPIStates.disableAddon(aAddon.id);
+
+ aAddon._installLocation.uninstallAddon(aAddon.id);
+ XPIDatabase.removeAddonMetadata(aAddon);
+ XPIStates.removeAddon(aAddon.location, aAddon.id);
+ AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
+
+ if (existing) {
+ XPIDatabase.makeAddonVisible(existing);
+ AddonManagerPrivate.callAddonListeners("onInstalling", existing.wrapper, false);
+
+ if (!existing.disabled) {
+ XPIDatabase.updateAddonActive(existing, true);
+ }
}
-
- 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);
+ };
+
+ if (existing) {
+ bootstrap.update(existing, !existing.disabled, uninstall);
+
+ AddonManagerPrivate.callAddonListeners("onInstalled", existing.wrapper);
+ } else {
+ XPIStates.removeAddon(aAddon.location, aAddon.id);
+ bootstrap.uninstall();
+ uninstall();
}
} else if (aAddon.active) {
- XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown", reason);
XPIStates.disableAddon(aAddon.id);
- XPIProvider.unloadBootstrapScope(aAddon.id);
+ bootstrap.shutdown(BOOTSTRAP_REASONS.ADDON_UNINSTALL);
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);
},
@@ -4122,18 +4035,17 @@ var XPIInstall = {
Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
// TODO hide hidden add-ons (bug 557710)
let wrapper = aAddon.wrapper;
AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);
if (!aAddon.disabled) {
- XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
- BOOTSTRAP_REASONS.ADDON_INSTALL);
+ XPIInternal.BootstrapScope.get(aAddon).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);
},
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -45,18 +45,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
XPIInstall: "resource://gre/modules/addons/XPIInstall.jsm",
verifyBundleSignedState: "resource://gre/modules/addons/XPIInstall.jsm",
});
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_BOOTSTRAP_ADDONS = "extensions.bootstrappedAddons";
const PREF_PENDING_OPERATIONS = "extensions.pendingOperations";
const PREF_EM_ENABLED_SCOPES = "extensions.enabledScopes";
@@ -159,20 +157,16 @@ const BOOTSTRAP_REASONS = {
// externally
const TYPE_ALIASES = {
"webextension": "extension",
"webextension-dictionary": "dictionary",
"webextension-langpack": "locale",
"webextension-theme": "theme",
};
-const CHROME_TYPES = new Set([
- "extension",
-]);
-
const SIGNED_TYPES = new Set([
"extension",
"webextension",
"webextension-langpack",
"webextension-theme",
]);
const ALL_EXTERNAL_TYPES = new Set([
@@ -1248,16 +1242,406 @@ var XPIStates = {
logger.debug(`Disabling XPIState for ${aId}`);
let state = this.findAddon(aId);
if (state) {
state.enabled = false;
}
},
};
+/**
+ * A helper class to manage the lifetime of and interaction with
+ * bootstrap scopes for an add-on.
+ *
+ * @param {Object} addon
+ * The add-on which owns this scope. Should be either an
+ * AddonInternal or XPIState object.
+ */
+class BootstrapScope {
+ constructor(addon) {
+ if (!addon.id || !addon.version || !addon.type) {
+ throw new Error("Addon must include an id, version, and type");
+ }
+
+ this.addon = addon;
+ this.instanceID = null;
+ this.scope = null;
+ this.started = false;
+ }
+
+ /**
+ * Returns a BootstrapScope object for the given add-on. If an active
+ * scope exists, it is returned. Otherwise a new one is created.
+ *
+ * @param {Object} addon
+ * The add-on which owns this scope, as accepted by the
+ * constructor.
+ * @returns {BootstrapScope}
+ */
+ static get(addon) {
+ let scope = XPIProvider.activeAddons.get(addon.id);
+ if (!scope) {
+ scope = new this(addon);
+ }
+ return scope;
+ }
+
+ get file() {
+ return this.addon.file || this.addon._sourceBundle;
+ }
+
+ get runInSafeMode() {
+ return "runInSafeMode" in this.addon ? this.addon.runInSafeMode : canRunInSafeMode(this.addon);
+ }
+
+ /**
+ * Calls a bootstrap method for an add-on.
+ *
+ * @param {string} aMethod
+ * The name of the bootstrap method to call
+ * @param {integer} aReason
+ * The reason flag to pass to the bootstrap's startup method
+ * @param {Object} [aExtraParams = {}]
+ * An object of additional key/value pairs to pass to the method in
+ * the params argument
+ */
+ callBootstrapMethod(aMethod, aReason, aExtraParams = {}) {
+ let {addon, runInSafeMode} = this;
+ if (Services.appinfo.inSafeMode && !runInSafeMode)
+ return;
+
+ let timeStart = new Date();
+ if (addon.type == "extension" && aMethod == "startup") {
+ logger.debug(`Registering manifest for ${this.file.path}`);
+ Components.manager.addBootstrappedManifestLocation(this.file);
+ }
+
+ try {
+ if (!this.scope) {
+ this.loadBootstrapScope(aReason);
+ }
+
+ if (aMethod == "startup" || aMethod == "shutdown") {
+ aExtraParams.instanceID = this.instanceID;
+ }
+
+ let method = undefined;
+ let {scope} = this;
+ try {
+ method = scope[aMethod] || Cu.evalInSandbox(`${aMethod};`, scope);
+ } catch (e) {
+ // An exception will be caught if the expected method is not defined.
+ // That will be logged below.
+ }
+
+ if (aMethod == "startup") {
+ this.started = true;
+ } else if (aMethod == "shutdown") {
+ this.started = false;
+
+ // Extensions are automatically deinitialized in the correct order at shutdown.
+ if (aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
+ this._pendingDisable = true;
+ for (let addon of XPIProvider.getDependentAddons(this.addon)) {
+ if (addon.active)
+ XPIDatabase.updateAddonDisabledState(addon);
+ }
+ }
+ }
+
+ let installLocation = addon._installLocation || null;
+ let params = {
+ id: addon.id,
+ version: addon.version,
+ installPath: this.file.clone(),
+ resourceURI: getURIForResourceInFile(this.file, ""),
+ signedState: addon.signedState,
+ temporarilyInstalled: installLocation == TemporaryInstallLocation,
+ builtIn: installLocation instanceof BuiltInInstallLocation,
+ };
+
+ if (aMethod == "startup" && addon.startupData) {
+ params.startupData = addon.startupData;
+ }
+
+ Object.assign(params, aExtraParams);
+
+ if (addon.hasEmbeddedWebExtension) {
+ let reason = Object.keys(BOOTSTRAP_REASONS).find(
+ key => BOOTSTRAP_REASONS[key] == aReason
+ );
+
+ if (aMethod == "startup") {
+ const webExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor(params);
+ params.webExtension = {
+ startup: () => webExtension.startup(reason),
+ };
+ } else if (aMethod == "shutdown") {
+ LegacyExtensionsUtils.getEmbeddedExtensionFor(params).shutdown(reason);
+ }
+ }
+
+ if (!method) {
+ logger.warn(`Add-on ${addon.id} is missing bootstrap method ${aMethod}`);
+ } else {
+ logger.debug(`Calling bootstrap method ${aMethod} on ${addon.id} version ${addon.version}`);
+
+ let result;
+ try {
+ result = method.call(scope, params, aReason);
+ } catch (e) {
+ logger.warn(`Exception running bootstrap method ${aMethod} on ${addon.id}`, e);
+ }
+
+ if (aMethod == "startup") {
+ this.startupPromise = Promise.resolve(result);
+ this.startupPromise.catch(Cu.reportError);
+ }
+ }
+ } finally {
+ // Extensions are automatically initialized in the correct order at startup.
+ if (aMethod == "startup" && aReason != BOOTSTRAP_REASONS.APP_STARTUP) {
+ for (let addon of XPIProvider.getDependentAddons(this.addon)) {
+ XPIDatabase.updateAddonDisabledState(addon);
+ }
+ }
+
+ if (addon.type == "extension" && aMethod == "shutdown" &&
+ aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
+ logger.debug(`Removing manifest for ${this.file.path}`);
+ Components.manager.removeBootstrappedManifestLocation(this.file);
+ }
+ XPIProvider.setTelemetry(addon.id, `${aMethod}_MS`, new Date() - timeStart);
+ }
+ }
+
+ /**
+ * Loads a bootstrapped add-on's bootstrap.js into a sandbox and the reason
+ * values as constants in the scope.
+ *
+ * @param {integer?} [aReason]
+ * The reason this bootstrap is being loaded, as passed to a
+ * bootstrap method.
+ */
+ loadBootstrapScope(aReason) {
+ this.instanceID = Symbol(this.addon.id);
+ this._pendingDisable = false;
+
+ XPIProvider.activeAddons.set(this.addon.id, this);
+
+ // Mark the add-on as active for the crash reporter before loading.
+ // But not at app startup, since we'll already have added all of our
+ // annotations before starting any loads.
+ if (aReason !== BOOTSTRAP_REASONS.APP_STARTUP) {
+ XPIProvider.addAddonsToCrashReporter();
+ }
+
+ logger.debug(`Loading bootstrap scope from ${this.file.path}`);
+
+ let principal = Services.scriptSecurityManager.getSystemPrincipal();
+
+ if (!this.file.exists()) {
+ this.scope =
+ new Cu.Sandbox(principal, { sandboxName: this.file.path,
+ addonId: this.addon.id,
+ wantGlobalProperties: ["ChromeUtils"],
+ metadata: { addonID: this.addon.id } });
+ logger.error(`Attempted to load bootstrap scope from missing directory ${this.file.path}`);
+ return;
+ }
+
+ if (isWebExtension(this.addon.type)) {
+ this.scope = Extension.getBootstrapScope(this.addon.id, this.file);
+ } else if (this.addon.type === "webextension-langpack") {
+ this.scope = Langpack.getBootstrapScope(this.addon.id, this.file);
+ } else if (this.addon.type === "webextension-dictionary") {
+ this.scope = Dictionary.getBootstrapScope(this.addon.id, this.file);
+ } else {
+ let uri = getURIForResourceInFile(this.file, "bootstrap.js").spec;
+
+ this.scope =
+ new Cu.Sandbox(principal, { sandboxName: uri,
+ addonId: this.addon.id,
+ wantGlobalProperties: ["ChromeUtils"],
+ metadata: { addonID: this.addon.id, URI: uri } });
+
+ try {
+ // Copy the reason values from the global object into the bootstrap scope.
+ for (let name in BOOTSTRAP_REASONS)
+ this.scope[name] = BOOTSTRAP_REASONS[name];
+
+ // Add other stuff that extensions want.
+ Object.assign(this.scope, {Worker, ChromeWorker});
+
+ // Define a console for the add-on
+ XPCOMUtils.defineLazyGetter(
+ this.scope, "console",
+ () => new ConsoleAPI({ consoleID: `addon/${this.addon.id}` }));
+
+ this.scope.__SCRIPT_URI_SPEC__ = uri;
+ Services.scriptloader.loadSubScript(uri, this.scope);
+ } catch (e) {
+ logger.warn(`Error loading bootstrap.js for ${this.addon.id}`, e);
+ }
+ }
+
+ // Notify the BrowserToolboxProcess that a new addon has been loaded.
+ let wrappedJSObject = { id: this.addon.id, options: { global: this.scope }};
+ Services.obs.notifyObservers({ wrappedJSObject }, "toolbox-update-addon-options");
+ }
+
+ /**
+ * Unloads a bootstrap scope by dropping all references to it and then
+ * updating the list of active add-ons with the crash reporter.
+ */
+ unloadBootstrapScope() {
+ XPIProvider.activeAddons.delete(this.addon.id);
+ XPIProvider.addAddonsToCrashReporter();
+
+ this.scope = null;
+ this.startupPromise = null;
+ this.instanceID = null;
+
+ // Notify the BrowserToolboxProcess that an addon has been unloaded.
+ let wrappedJSObject = { id: this.addon.id, options: { global: null }};
+ Services.obs.notifyObservers({ wrappedJSObject }, "toolbox-update-addon-options");
+ }
+
+ /**
+ * Calls the bootstrap scope's startup method, with the given reason
+ * and extra parameters.
+ *
+ * @param {integer} reason
+ * The reason code for the startup call.
+ * @param {Object} [aExtraParams]
+ * Optional extra parameters to pass to the bootstrap method.
+ */
+ startup(reason, aExtraParams) {
+ this.callBootstrapMethod("startup", reason, aExtraParams);
+ }
+
+ /**
+ * Calls the bootstrap scope's shutdown method, with the given reason
+ * and extra parameters.
+ *
+ * @param {integer} reason
+ * The reason code for the shutdown call.
+ * @param {Object} [aExtraParams]
+ * Optional extra parameters to pass to the bootstrap method.
+ */
+ shutdown(reason, aExtraParams) {
+ this.callBootstrapMethod("shutdown", reason, aExtraParams);
+ }
+
+ /**
+ * If the add-on is already running, calls its "shutdown" method, and
+ * unloads its bootstrap scope.
+ *
+ * @param {integer} reason
+ * The reason code for the shutdown call.
+ * @param {Object} [aExtraParams]
+ * Optional extra parameters to pass to the bootstrap method.
+ */
+ disable() {
+ if (this.started) {
+ this.shutdown(BOOTSTRAP_REASONS.ADDON_DISABLE);
+ this.unloadBootstrapScope();
+ }
+ }
+
+ /**
+ * Calls the bootstrap scope's install method, and optionally its
+ * startup method.
+ *
+ * @param {integer} reason
+ * The reason code for the calls.
+ * @param {boolean} [startup = false]
+ * If true, and the add-on is active, calls its startup method
+ * after its install method.
+ * @param {Object} [extraArgs]
+ * Optional extra parameters to pass to the bootstrap method.
+ */
+ install(reason = BOOTSTRAP_REASONS.ADDON_INSTALL, startup, extraArgs) {
+ this._install(reason, false, startup, extraArgs);
+ }
+
+ _install(reason, callUpdate, startup, extraArgs) {
+ if (callUpdate) {
+ this.callBootstrapMethod("update", reason, extraArgs);
+ } else {
+ this.callBootstrapMethod("install", reason, extraArgs);
+ }
+
+ if (startup && this.addon.active) {
+ this.startup(reason, extraArgs);
+ } else if (this.addon.disabled) {
+ this.unloadBootstrapScope();
+ }
+ }
+
+ /**
+ * Calls the bootstrap scope's uninstall method, and unloads its
+ * bootstrap scope. If the extension is already running, its shutdown
+ * method is called before its uninstall method.
+ *
+ * @param {integer} reason
+ * The reason code for the calls.
+ * @param {Object} [extraArgs]
+ * Optional extra parameters to pass to the bootstrap method.
+ */
+ uninstall(reason = BOOTSTRAP_REASONS.ADDON_UNINSTALL, extraArgs) {
+ this._uninstall(reason, false, extraArgs);
+ }
+
+ _uninstall(reason, callUpdate, extraArgs) {
+ if (this.started) {
+ this.shutdown(reason, extraArgs);
+ }
+ if (!callUpdate) {
+ this.callBootstrapMethod("uninstall", reason, extraArgs);
+ }
+ this.unloadBootstrapScope();
+ XPIInstall.flushChromeCaches();
+ }
+
+ /**
+ * Calls the appropriate sequence of shutdown, uninstall, update,
+ * startup, and install methods for updating the current scope's
+ * add-on to the given new add-on, depending on the current state of
+ * the scope.
+ *
+ * @param {Object} newAddon
+ * The new add-on which is being installed, as expected by the
+ * constructor.
+ * @param {boolean} [startup = false]
+ * If true, and the new add-on is enabled, calls its startup
+ * method as its final operation.
+ * @param {function} [updateCallback]
+ * An optional callback function to call between uninstalling
+ * the old add-on and installing the new one. This callback
+ * should update any database state which is necessary for the
+ * startup of the new add-on.
+ */
+ update(newAddon, startup = false, updateCallback) {
+ let reason = XPIInstall.newVersionReason(this.addon.version, newAddon.version);
+ let extraArgs = {oldVersion: this.addon.version, newVersion: newAddon.version};
+
+ let callUpdate = isWebExtension(this.addon.type) && isWebExtension(newAddon.type);
+
+ this._uninstall(reason, callUpdate, extraArgs);
+
+ if (updateCallback) {
+ updateCallback();
+ }
+
+ this.addon = newAddon;
+ this._install(reason, callUpdate, startup, extraArgs);
+ }
+}
+
var XPIProvider = {
get name() {
return "XPIProvider";
},
BOOTSTRAP_REASONS: Object.freeze(BOOTSTRAP_REASONS),
// An array of known install locations
@@ -1595,70 +1979,65 @@ var XPIProvider = {
}
try {
let reason = BOOTSTRAP_REASONS.APP_STARTUP;
// Eventually set INSTALLED reason when a bootstrap addon
// is dropped in profile folder and automatically installed
if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
.includes(addon.id))
reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
- this.callBootstrapMethod(addon, addon.file, "startup", reason);
+ BootstrapScope.get(addon).startup(reason);
} catch (e) {
logger.error("Failed to load bootstrap addon " + addon.id + " from " +
addon.descriptor, e);
}
}
AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_end");
} catch (e) {
logger.error("bootstrap startup failed", e);
AddonManagerPrivate.recordException("XPI-BOOTSTRAP", "startup failed", e);
}
// Let these shutdown a little earlier when they still have access to most
// of XPCOM
- Services.obs.addObserver({
- observe(aSubject, aTopic, aData) {
- XPIProvider.cleanupTemporaryAddons();
- XPIProvider._closing = true;
- for (let addon of XPIProvider.sortBootstrappedAddons().reverse()) {
- // If no scope has been loaded for this add-on then there is no need
- // to shut it down (should only happen when a bootstrapped add-on is
- // pending enable)
- let activeAddon = XPIProvider.activeAddons.get(addon.id);
- if (!activeAddon || !activeAddon.started) {
- continue;
+ Services.obs.addObserver(function observer() {
+ XPIProvider.cleanupTemporaryAddons();
+ XPIProvider._closing = true;
+ for (let addon of XPIProvider.sortBootstrappedAddons().reverse()) {
+ // If no scope has been loaded for this add-on then there is no need
+ // to shut it down (should only happen when a bootstrapped add-on is
+ // pending enable)
+ let activeAddon = XPIProvider.activeAddons.get(addon.id);
+ if (!activeAddon || !activeAddon.started) {
+ continue;
+ }
+
+ // If the add-on was pending disable then shut it down and remove it
+ // from the persisted data.
+ let reason = BOOTSTRAP_REASONS.APP_SHUTDOWN;
+ if (addon._pendingDisable) {
+ 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 = XPIInstall.newVersionReason(addon.version, existing.version);
}
-
- // If the add-on was pending disable then shut it down and remove it
- // from the persisted data.
- 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 = XPIInstall.newVersionReason(addon.version, existing.version);
- }
- }
- XPIProvider.callBootstrapMethod(addon, addon.file,
- "shutdown", reason);
}
- Services.obs.removeObserver(this, "quit-application-granted");
+ BootstrapScope.get(addon).shutdown(reason);
}
+ Services.obs.removeObserver(observer, "quit-application-granted");
}, "quit-application-granted");
// Detect final-ui-startup for telemetry reporting
- Services.obs.addObserver({
- observe(aSubject, aTopic, aData) {
- AddonManagerPrivate.recordTimestamp("XPI_finalUIStartup");
- XPIProvider.runPhase = XPI_AFTER_UI_STARTUP;
- Services.obs.removeObserver(this, "final-ui-startup");
- }
+ Services.obs.addObserver(function observer() {
+ AddonManagerPrivate.recordTimestamp("XPI_finalUIStartup");
+ XPIProvider.runPhase = XPI_AFTER_UI_STARTUP;
+ Services.obs.removeObserver(observer, "final-ui-startup");
}, "final-ui-startup");
// If we haven't yet loaded the XPI database, schedule loading it
// to occur once other important startup work is finished. We want
// this to happen relatively quickly after startup so the telemetry
// environment has complete addon information.
//
// Unfortunately we have to use a variety of ways do detect when it
@@ -1666,32 +2045,30 @@ var XPIProvider = {
// sessionstore-windows-restored. In a browser toolbox process
// we wait for the toolbox to show up, based on xul-window-visible
// and a visible toolbox window.
// Finally, we have a test-only event called test-load-xpi-database
// as a temporary workaround for bug 1372845. The latter can be
// cleaned up when that bug is resolved.
if (!this.isDBLoaded) {
const EVENTS = [ "sessionstore-windows-restored", "xul-window-visible", "test-load-xpi-database" ];
- let observer = {
- observe(subject, topic, data) {
- if (topic == "xul-window-visible" &&
- !Services.wm.getMostRecentWindow("devtools:toolbox")) {
- return;
- }
-
- for (let event of EVENTS) {
- Services.obs.removeObserver(observer, event);
- }
-
- // It would be nice to defer some of the work here until we
- // have idle time but we can't yet use requestIdleCallback()
- // from chrome. See bug 1358476.
- XPIDatabase.asyncLoadDB();
- },
+ let observer = (subject, topic, data) => {
+ if (topic == "xul-window-visible" &&
+ !Services.wm.getMostRecentWindow("devtools:toolbox")) {
+ return;
+ }
+
+ for (let event of EVENTS) {
+ Services.obs.removeObserver(observer, event);
+ }
+
+ // It would be nice to defer some of the work here until we
+ // have idle time but we can't yet use requestIdleCallback()
+ // from chrome. See bug 1358476.
+ XPIDatabase.asyncLoadDB();
};
for (let event of EVENTS) {
Services.obs.addObserver(observer, event);
}
}
AddonManagerPrivate.recordTimestamp("XPI_startup_end");
@@ -1756,41 +2133,32 @@ var XPIProvider = {
},
cleanupTemporaryAddons() {
let tempLocation = XPIStates.getLocation(TemporaryInstallLocation.name);
if (tempLocation) {
for (let [id, addon] of tempLocation.entries()) {
tempLocation.delete(id);
- let reason = BOOTSTRAP_REASONS.ADDON_UNINSTALL;
-
+ let bootstrap = BootstrapScope.get(addon);
let existing = XPIStates.findAddon(id, loc => loc != tempLocation);
- let callUpdate = false;
- if (existing) {
- 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);
- TemporaryInstallLocation.uninstallAddon(id);
- XPIStates.removeAddon(TemporaryInstallLocation.name, id);
+
+ let cleanup = () => {
+ TemporaryInstallLocation.uninstallAddon(id);
+ XPIStates.removeAddon(TemporaryInstallLocation.name, id);
+ };
if (existing) {
- let newAddon = XPIDatabase.makeAddonLocationVisible(id, existing.location.name);
-
- let file = new nsIFile(newAddon.path);
-
- let data = {oldVersion: addon.version};
- let method = callUpdate ? "update" : "install";
- this.callBootstrapMethod(newAddon, file, method, reason, data);
+ bootstrap.update(existing, false, () => {
+ cleanup();
+ XPIDatabase.makeAddonLocationVisible(id, existing.location.name);
+ });
+ } else {
+ bootstrap.uninstall();
+ cleanup();
}
}
}
},
/**
* Verifies that all installed add-ons are still correctly signed.
*/
@@ -2475,271 +2843,16 @@ var XPIProvider = {
case PREF_XPI_SIGNATURES_REQUIRED:
case PREF_LANGPACK_SIGNATURES:
case PREF_ALLOW_LEGACY:
this.updateAddonAppDisabledStates();
break;
}
}
},
-
- /**
- * Loads a bootstrapped add-on's bootstrap.js into a sandbox and the reason
- * values as constants in the scope. This will also add information about the
- * add-on to the bootstrappedAddons dictionary and notify the crash reporter
- * that new add-ons have been loaded.
- *
- * @param {string} aId
- * The add-on's ID
- * @param {nsIFile} aFile
- * The nsIFile for the add-on
- * @param {string} aVersion
- * The add-on's version
- * @param {string} aType
- * The type for the add-on
- * @param {boolean} aRunInSafeMode
- * Boolean indicating whether the add-on can run in safe mode.
- * @param {string[]} aDependencies
- * An array of add-on IDs on which this add-on depends.
- * @param {boolean} hasEmbeddedWebExtension
- * Boolean indicating whether the add-on has an embedded webextension.
- * @param {integer?} [aReason]
- * The reason this bootstrap is being loaded, as passed to a
- * bootstrap method.
- */
- loadBootstrapScope(aId, aFile, aVersion, aType, aRunInSafeMode, aDependencies,
- hasEmbeddedWebExtension, aReason) {
- this.activeAddons.set(aId, {
- bootstrapScope: null,
- // a Symbol passed to this add-on, which it can use to identify itself
- instanceID: Symbol(aId),
- started: false,
- });
-
- // Mark the add-on as active for the crash reporter before loading.
- // But not at app startup, since we'll already have added all of our
- // annotations before starting any loads.
- if (aReason !== BOOTSTRAP_REASONS.APP_STARTUP) {
- this.addAddonsToCrashReporter();
- }
-
- let activeAddon = this.activeAddons.get(aId);
-
- logger.debug("Loading bootstrap scope from " + aFile.path);
-
- let principal = Cc["@mozilla.org/systemprincipal;1"].
- createInstance(Ci.nsIPrincipal);
-
- if (!aFile.exists()) {
- activeAddon.bootstrapScope =
- new Cu.Sandbox(principal, { sandboxName: aFile.path,
- addonId: aId,
- wantGlobalProperties: ["ChromeUtils"],
- metadata: { addonID: aId } });
- logger.error("Attempted to load bootstrap scope from missing directory " + aFile.path);
- return;
- }
-
- if (isWebExtension(aType)) {
- activeAddon.bootstrapScope = Extension.getBootstrapScope(aId, aFile);
- } else if (aType === "webextension-langpack") {
- activeAddon.bootstrapScope = Langpack.getBootstrapScope(aId, aFile);
- } else if (aType === "webextension-dictionary") {
- activeAddon.bootstrapScope = Dictionary.getBootstrapScope(aId, aFile);
- } else {
- let uri = getURIForResourceInFile(aFile, "bootstrap.js").spec;
-
- activeAddon.bootstrapScope =
- new Cu.Sandbox(principal, { sandboxName: uri,
- addonId: aId,
- wantGlobalProperties: ["ChromeUtils"],
- metadata: { addonID: aId, URI: uri } });
-
- try {
- // Copy the reason values from the global object into the bootstrap scope.
- for (let name in BOOTSTRAP_REASONS)
- activeAddon.bootstrapScope[name] = BOOTSTRAP_REASONS[name];
-
- // Add other stuff that extensions want.
- Object.assign(activeAddon.bootstrapScope, {Worker, ChromeWorker});
-
- // Define a console for the add-on
- XPCOMUtils.defineLazyGetter(
- activeAddon.bootstrapScope, "console",
- () => new ConsoleAPI({ consoleID: "addon/" + aId }));
-
- activeAddon.bootstrapScope.__SCRIPT_URI_SPEC__ = uri;
- Services.scriptloader.loadSubScript(uri, activeAddon.bootstrapScope);
- } catch (e) {
- logger.warn("Error loading bootstrap.js for " + aId, e);
- }
- }
-
- // Notify the BrowserToolboxProcess that a new addon has been loaded.
- let wrappedJSObject = { id: aId, options: { global: activeAddon.bootstrapScope }};
- Services.obs.notifyObservers({ wrappedJSObject }, "toolbox-update-addon-options");
- },
-
- /**
- * Unloads a bootstrap scope by dropping all references to it and then
- * updating the list of active add-ons with the crash reporter.
- *
- * @param {string} aId
- * The add-on's ID
- */
- unloadBootstrapScope(aId) {
- this.activeAddons.delete(aId);
- this.addAddonsToCrashReporter();
-
- // Notify the BrowserToolboxProcess that an addon has been unloaded.
- let wrappedJSObject = { id: aId, options: { global: null }};
- Services.obs.notifyObservers({ wrappedJSObject }, "toolbox-update-addon-options");
- },
-
- /**
- * Calls a bootstrap method for an add-on.
- *
- * @param {Object} aAddon
- * An object representing the add-on, with `id`, `type` and `version`
- * @param {nsIFile} aFile
- * The nsIFile for the add-on
- * @param {string} aMethod
- * The name of the bootstrap method to call
- * @param {integer} aReason
- * The reason flag to pass to the bootstrap's startup method
- * @param {Object?} [aExtraParams]
- * An object of additional key/value pairs to pass to the method in
- * the params argument
- */
- callBootstrapMethod(aAddon, aFile, aMethod, aReason, aExtraParams) {
- if (!aAddon.id || !aAddon.version || !aAddon.type) {
- throw new Error("aAddon must include an id, version, and type");
- }
-
- // Only run in safe mode if allowed to
- let runInSafeMode = "runInSafeMode" in aAddon ? aAddon.runInSafeMode : canRunInSafeMode(aAddon);
- if (Services.appinfo.inSafeMode && !runInSafeMode)
- return;
-
- let timeStart = new Date();
- if (CHROME_TYPES.has(aAddon.type) && aMethod == "startup") {
- logger.debug("Registering manifest for " + aFile.path);
- Components.manager.addBootstrappedManifestLocation(aFile);
- }
-
- try {
- // Load the scope if it hasn't already been loaded
- let activeAddon = this.activeAddons.get(aAddon.id);
- if (!activeAddon) {
- this.loadBootstrapScope(aAddon.id, aFile, aAddon.version, aAddon.type,
- runInSafeMode, aAddon.dependencies,
- aAddon.hasEmbeddedWebExtension || false,
- aReason);
- activeAddon = this.activeAddons.get(aAddon.id);
- }
-
- if (aMethod == "startup" || aMethod == "shutdown") {
- if (!aExtraParams) {
- aExtraParams = {};
- }
- aExtraParams.instanceID = this.activeAddons.get(aAddon.id).instanceID;
- }
-
- let method = undefined;
- let scope = activeAddon.bootstrapScope;
- try {
- method = scope[aMethod] || Cu.evalInSandbox(`${aMethod};`, scope);
- } catch (e) {
- // An exception will be caught if the expected method is not defined.
- // That will be logged below.
- }
-
- if (aMethod == "startup") {
- activeAddon.started = true;
- } else if (aMethod == "shutdown") {
- activeAddon.started = false;
-
- // Extensions are automatically deinitialized in the correct order at shutdown.
- if (aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
- activeAddon.disable = true;
- for (let addon of this.getDependentAddons(aAddon)) {
- if (addon.active)
- XPIDatabase.updateAddonDisabledState(addon);
- }
- }
- }
-
- let installLocation = aAddon._installLocation || null;
- let params = {
- id: aAddon.id,
- version: aAddon.version,
- installPath: aFile.clone(),
- resourceURI: getURIForResourceInFile(aFile, ""),
- signedState: aAddon.signedState,
- temporarilyInstalled: installLocation == TemporaryInstallLocation,
- builtIn: installLocation instanceof BuiltInInstallLocation,
- };
-
- if (aMethod == "startup" && aAddon.startupData) {
- params.startupData = aAddon.startupData;
- }
-
- if (aExtraParams) {
- for (let key in aExtraParams) {
- params[key] = aExtraParams[key];
- }
- }
-
- if (aAddon.hasEmbeddedWebExtension) {
- let reason = Object.keys(BOOTSTRAP_REASONS).find(
- key => BOOTSTRAP_REASONS[key] == aReason
- );
-
- if (aMethod == "startup") {
- const webExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor(params);
- params.webExtension = {
- startup: () => webExtension.startup(reason),
- };
- } else if (aMethod == "shutdown") {
- LegacyExtensionsUtils.getEmbeddedExtensionFor(params).shutdown(reason);
- }
- }
-
- if (!method) {
- logger.warn("Add-on " + aAddon.id + " is missing bootstrap method " + aMethod);
- } else {
- logger.debug("Calling bootstrap method " + aMethod + " on " + aAddon.id + " version " +
- aAddon.version);
-
- let result;
- try {
- result = method.call(scope, params, aReason);
- } catch (e) {
- logger.warn("Exception running bootstrap method " + aMethod + " on " + aAddon.id, e);
- }
-
- if (aMethod == "startup") {
- activeAddon.startupPromise = Promise.resolve(result);
- activeAddon.startupPromise.catch(Cu.reportError);
- }
- }
- } finally {
- // Extensions are automatically initialized in the correct order at startup.
- if (aMethod == "startup" && aReason != BOOTSTRAP_REASONS.APP_STARTUP) {
- for (let addon of this.getDependentAddons(aAddon))
- XPIDatabase.updateAddonDisabledState(addon);
- }
-
- if (CHROME_TYPES.has(aAddon.type) && aMethod == "shutdown" && aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
- logger.debug("Removing manifest for " + aFile.path);
- Components.manager.removeBootstrappedManifestLocation(aFile);
- }
- this.setTelemetry(aAddon.id, aMethod + "_MS", new Date() - timeStart);
- }
- },
};
for (let meth of ["cancelUninstallAddon", "getInstallForFile",
"getInstallForURL", "installTemporaryAddon",
"isInstallAllowed", "isInstallEnabled",
"uninstallAddon", "updateSystemAddons"]) {
XPIProvider[meth] = function() {
return XPIInstall[meth](...arguments);
@@ -3250,16 +3363,17 @@ class WinRegInstallLocation extends Dire
*/
isLinkedAddon(aId) {
return true;
}
}
var XPIInternal = {
BOOTSTRAP_REASONS,
+ BootstrapScope,
DB_SCHEMA,
KEY_APP_SYSTEM_ADDONS,
KEY_APP_SYSTEM_DEFAULTS,
KEY_APP_TEMPORARY,
PREF_BRANCH_INSTALLED_ADDON,
PREF_SYSTEM_ADDON_SET,
SIGNED_TYPES,
SystemAddonInstallLocation,
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -241,17 +241,18 @@ this.BootstrapMonitor = {
this.installPromises.push(resolve);
});
},
checkMatches(cached, current) {
Assert.notEqual(cached, undefined);
Assert.equal(current.data.version, cached.data.version);
Assert.equal(current.data.installPath, cached.data.installPath);
- Assert.equal(current.data.resourceURI, cached.data.resourceURI);
+ Assert.ok(Services.io.newURI(current.data.resourceURI).equals(Services.io.newURI(cached.data.resourceURI)),
+ `Resource URIs match: "${current.data.resourceURI}" == "${cached.data.resourceURI}"`);
},
checkAddonStarted(id, version = undefined) {
let started = this.started.get(id);
Assert.notEqual(started, undefined);
if (version != undefined)
Assert.equal(started.data.version, version);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
@@ -979,20 +979,20 @@ add_task(async function test_19() {
ok(b1.isActive);
ok(!b1.isSystem);
equal(getShutdownReason(), ADDON_DOWNGRADE);
equal(getUninstallReason(), ADDON_DOWNGRADE);
equal(getInstallReason(), ADDON_DOWNGRADE);
equal(getStartupReason(), ADDON_DOWNGRADE);
- equal(getShutdownNewVersion(), undefined);
- equal(getUninstallNewVersion(), undefined);
- equal(getInstallOldVersion(), undefined);
- equal(getStartupOldVersion(), undefined);
+ equal(getShutdownNewVersion(), "1.0");
+ equal(getUninstallNewVersion(), "1.0");
+ equal(getInstallOldVersion(), "2.0");
+ equal(getStartupOldVersion(), "2.0");
await checkBootstrappedPref();
});
// Check that a new profile extension detected at startup replaces the non-profile
// one
add_task(async function test_20() {
await promiseShutdownManager();
--- a/toolkit/mozapps/extensions/test/xpcshell/test_undouninstall.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_undouninstall.js
@@ -4,17 +4,17 @@
// This verifies that forcing undo for uninstall works
const APP_STARTUP = 1;
const APP_SHUTDOWN = 2;
const ADDON_DISABLE = 4;
const ADDON_INSTALL = 5;
const ADDON_UNINSTALL = 6;
-const ADDON_DOWNGRADE = 8;
+const ADDON_UPGRADE = 7;
const ID = "undouninstall1@tests.mozilla.org";
const INCOMPAT_ID = "incompatible@tests.mozilla.org";
const profileDir = gProfD.clone();
profileDir.append("extensions");
@@ -239,19 +239,19 @@ add_task(async function reinstallAddonAw
await promiseInstallAllFiles([do_get_addon("test_undouninstall1")]);
a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
ensure_test_completed();
BootstrapMonitor.checkAddonInstalled(ID, "1.0");
BootstrapMonitor.checkAddonStarted(ID, "1.0");
- Assert.equal(getUninstallReason(ID), ADDON_DOWNGRADE);
- Assert.equal(getInstallReason(ID), ADDON_DOWNGRADE);
- Assert.equal(getStartupReason(ID), ADDON_DOWNGRADE);
+ Assert.equal(getUninstallReason(ID), ADDON_UPGRADE);
+ Assert.equal(getInstallReason(ID), ADDON_UPGRADE);
+ Assert.equal(getStartupReason(ID), ADDON_UPGRADE);
Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
Assert.ok(a1.isActive);
Assert.ok(!a1.userDisabled);
await promiseShutdownManager();
Assert.equal(getShutdownReason(ID), APP_SHUTDOWN);
@@ -464,18 +464,18 @@ add_task(async function reinstallDisable
await promiseInstallAllFiles([do_get_addon("test_undouninstall1")]);
a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
ensure_test_completed();
BootstrapMonitor.checkAddonInstalled(ID, "1.0");
BootstrapMonitor.checkAddonNotStarted(ID, "1.0");
- Assert.equal(getUninstallReason(ID), ADDON_DOWNGRADE);
- Assert.equal(getInstallReason(ID), ADDON_DOWNGRADE);
+ Assert.equal(getUninstallReason(ID), ADDON_UPGRADE);
+ Assert.equal(getInstallReason(ID), ADDON_UPGRADE);
Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
Assert.ok(!a1.isActive);
Assert.ok(a1.userDisabled);
await promiseRestartManager();
a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");