Bug 1356826: Part 1 - Uninstall temporary add-ons at shutdown rather than startup. r?rhelmer
MozReview-Commit-ID: LQLsqEayEUU
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -94,16 +94,22 @@ XPCOMUtils.defineLazyGetter(this, "IconD
});
Cu.importGlobalProperties(["URL"]);
const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
"initWithPath");
+function getFile(descriptor) {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.persistentDescriptor = descriptor;
+ return file;
+}
+
const PREF_DB_SCHEMA = "extensions.databaseSchema";
const PREF_INSTALL_CACHE = "extensions.installCache";
const PREF_XPI_STATE = "extensions.xpiState";
const PREF_BOOTSTRAP_ADDONS = "extensions.bootstrappedAddons";
const PREF_PENDING_OPERATIONS = "extensions.pendingOperations";
const PREF_SKIN_SWITCHPENDING = "extensions.dss.switchPending";
const PREF_SKIN_TO_SELECT = "extensions.lastSelectedSkin";
const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
@@ -2245,18 +2251,18 @@ XPIState.prototype = {
XPIDatabase.saveChanges();
}
}
},
};
// Constructor for an ES6 Map that knows how to convert itself into a
// regular object for toJSON().
-function SerializableMap() {
- let m = new Map();
+function SerializableMap(arg) {
+ let m = new Map(arg);
m.toJSON = function() {
let out = {}
for (let [key, val] of m) {
out[key] = val;
}
return out;
};
return m;
@@ -2432,17 +2438,20 @@ this.XPIStates = {
XPIProvider.setTelemetry(aAddon.id, "location", aAddon.location);
},
/**
* Save the current state of installed add-ons.
* XXX this *totally* should be a .json file using DeferredSave...
*/
save() {
- let cache = JSON.stringify(this.db);
+ let db = new SerializableMap(this.db);
+ db.delete(TemporaryInstallLocation.name);
+
+ let cache = JSON.stringify(db);
Services.prefs.setCharPref(PREF_XPI_STATE, cache);
},
/**
* Remove the XPIState for an add-on and save the new state.
* @param aLocation The name of the add-on location.
* @param aId The ID of the add-on.
*/
@@ -2894,18 +2903,17 @@ this.XPIProvider = {
this.addAddonsToCrashReporter();
}
try {
AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_begin");
for (let addon of this.sortBootstrappedAddons()) {
try {
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- file.persistentDescriptor = addon.descriptor;
+ let file = getFile(addon.descriptor);
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)
.indexOf(addon.id) !== -1)
reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
this.callBootstrapMethod(createAddonDetails(addon.id, addon),
file, "startup", reason);
@@ -2927,18 +2935,17 @@ this.XPIProvider = {
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)
if (!XPIProvider.activeAddons.has(addon.id))
continue;
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- file.persistentDescriptor = addon.descriptor;
+ let file = getFile(addon.descriptor);
let addonDetails = createAddonDetails(addon.id, addon);
// If the add-on was pending disable then shut it down and remove it
// from the persisted data.
if (addon.disable) {
XPIProvider.callBootstrapMethod(addonDetails, file, "shutdown",
BOOTSTRAP_REASONS.ADDON_DISABLE);
delete XPIProvider.bootstrappedAddons[addon.id];
@@ -2983,16 +2990,43 @@ this.XPIProvider = {
* 0 otherwise.
*/
shutdown() {
logger.debug("shutdown");
// Stop anything we were doing asynchronously
this.cancelAll();
+ // Uninstall any temporary add-ons.
+ let tempLocation = XPIStates.getLocation(TemporaryInstallLocation.name);
+ if (tempLocation) {
+ for (let [id, addon] of tempLocation.entries()) {
+ tempLocation.delete(id);
+
+ let file = getFile(addon.descriptor);
+
+ this.callBootstrapMethod(createAddonDetails(id, this.bootstrappedAddons[id]),
+ file, "uninstall",
+ BOOTSTRAP_REASONS.ADDON_UNINSTALL);
+ this.unloadBootstrapScope(id);
+ TemporaryInstallLocation.uninstallAddon(id);
+
+ let [locationName, ] = XPIStates.findAddon(id);
+ if (locationName) {
+ let newAddon = XPIDatabase.makeAddonLocationVisible(id, locationName);
+
+ let file = getFile(newAddon.descriptor);
+
+ this.callBootstrapMethod(createAddonDetails(id, newAddon),
+ file, "install",
+ BOOTSTRAP_REASONS.ADDON_INSTALL);
+ }
+ }
+ }
+
this.bootstrappedAddons = {};
this.activeAddons.clear();
this.enabledAddons = null;
this.allAppGlobal = true;
// If there are pending operations then we must update the list of active
// add-ons
if (Preferences.get(PREF_PENDING_OPERATIONS, false)) {
@@ -3497,18 +3531,17 @@ this.XPIProvider = {
seenFiles.push(jsonfile.leafName);
existingAddonID = addon.existingAddonID || id;
var oldBootstrap = null;
logger.debug("Processing install of " + id + " in " + location.name);
if (existingAddonID in this.bootstrappedAddons) {
try {
- var existingAddon = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- existingAddon.persistentDescriptor = this.bootstrappedAddons[existingAddonID].descriptor;
+ var existingAddon = getFile(this.bootstrappedAddons[existingAddonID].descriptor);
if (existingAddon.exists()) {
oldBootstrap = this.bootstrappedAddons[existingAddonID];
// We'll be replacing a currently active bootstrapped add-on so
// call its uninstall method
let newVersion = addon.version;
let oldVersion = oldBootstrap.version;
let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ?
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -59,16 +59,17 @@ const PREF_EM_AUTO_DISABLED_SCOPES =
const PREF_E10S_BLOCKED_BY_ADDONS = "extensions.e10sBlockedByAddons";
const PREF_E10S_MULTI_BLOCKED_BY_ADDONS = "extensions.e10sMultiBlockedByAddons";
const PREF_E10S_HAS_NONEXEMPT_ADDON = "extensions.e10s.rollout.hasAddon";
const KEY_APP_PROFILE = "app-profile";
const KEY_APP_SYSTEM_ADDONS = "app-system-addons";
const KEY_APP_SYSTEM_DEFAULTS = "app-system-defaults";
const KEY_APP_GLOBAL = "app-global";
+const KEY_APP_TEMPORARY = "app-temporary";
// Properties that only exist in the database
const DB_METADATA = ["syncGUID",
"installDate",
"updateDate",
"size",
"sourceURI",
"releaseNotesURI",
@@ -483,17 +484,18 @@ this.XPIDatabase = {
toJSON() {
if (!this.addonDB) {
// We never loaded the database?
throw new Error("Attempt to save database without loading it first");
}
let toSave = {
schemaVersion: DB_SCHEMA,
- addons: [...this.addonDB.values()]
+ addons: Array.from(this.addonDB.values())
+ .filter(addon => addon.location != KEY_APP_TEMPORARY),
};
return toSave;
},
/**
* Pull upgrade information from an existing SQLITE database
*
* @return false if there is no SQLITE database
@@ -1303,16 +1305,45 @@ this.XPIDatabase = {
otherAddon.active = false;
}
}
aAddon.visible = true;
this.saveChanges();
},
/**
+ * Synchronously marks a given add-on ID visible in a given location,
+ * instances with the same ID as not visible.
+ *
+ * @param aAddon
+ * The DBAddonInternal to make visible
+ */
+ makeAddonLocationVisible(aId, aLocation) {
+ logger.debug(`Make addon ${aId} visible in location ${aLocation}`);
+ let result;
+ for (let [, addon] of this.addonDB) {
+ if (addon.id != aId) {
+ continue;
+ }
+ if (addon.location == aLocation) {
+ logger.debug("Reveal addon " + addon._key);
+ addon.visible = true;
+ addon.active = true;
+ result = addon;
+ } else {
+ logger.debug("Hide addon " + addon._key);
+ addon.visible = false;
+ addon.active = false;
+ }
+ }
+ this.saveChanges();
+ return result;
+ },
+
+ /**
* Synchronously sets properties for an add-on.
*
* @param aAddon
* The DBAddonInternal being updated
* @param aProperties
* A dictionary of properties to set
*/
setAddonProperties(aAddon, aProperties) {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
@@ -43,17 +43,17 @@ const BOOTSTRAP_REASONS = {
function waitForBootstrapEvent(expectedEvent, addonId) {
return new Promise(resolve => {
const observer = {
observe: (subject, topic, data) => {
const info = JSON.parse(data);
const targetAddonId = info.data.id;
if (targetAddonId === addonId && info.event === expectedEvent) {
resolve(info);
- Services.obs.removeObserver(observer);
+ Services.obs.removeObserver(observer, "bootstrapmonitor-event");
} else {
do_print(
`Ignoring bootstrap event: ${info.event} for ${targetAddonId}`);
}
},
};
Services.obs.addObserver(observer, "bootstrapmonitor-event");
});