diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 7c501b6e3e8..e0af60f8292 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -98,6 +98,7 @@ const PREF_INSTALL_DISTRO_ADDONS = "extensions.installDistroAddons"; const PREF_BRANCH_INSTALLED_ADDON = "extensions.installedDistroAddon."; const PREF_SHOWN_SELECTION_UI = "extensions.shownSelectionUI"; const PREF_INTERPOSITION_ENABLED = "extensions.interposition.enabled"; +const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet"; const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion"; const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion"; @@ -115,6 +116,7 @@ const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/exte const STRING_TYPE_NAME = "type.%ID%.name"; const DIR_EXTENSIONS = "extensions"; +const DIR_SYSTEM_ADDONS = "features"; const DIR_STAGE = "staged"; const DIR_TRASH = "trash"; @@ -130,6 +132,8 @@ const KEY_TEMPDIR = "TmpD"; const KEY_APP_DISTRIBUTION = "XREAppDist"; 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_SYSTEM_LOCAL = "app-system-local"; const KEY_APP_SYSTEM_SHARE = "app-system-share"; @@ -1968,7 +1972,7 @@ this.XPIStates = { for (let location of XPIProvider.installLocations) { // The list of add-on like file/directory names in the install location. - let addons = location.addonLocations; + let addons = location.getAddonLocations(); // The results of scanning this location. let foundAddons = new SerializableMap(); @@ -2324,6 +2328,28 @@ this.XPIProvider = { XPIProvider.installLocationsByName[location.name] = location; } + function addSystemAddonInstallLocation(aName, aKey, aPaths, aScope) { + try { + var dir = FileUtils.getDir(aKey, aPaths); + } + catch (e) { + // Some directories aren't defined on some platforms, ignore them + logger.debug("Skipping unavailable install location " + aName); + return; + } + + try { + var location = new SystemAddonInstallLocation(aName, dir, aScope, aAppChanged !== false); + } + catch (e) { + logger.warn("Failed to add system add-on install location " + aName, e); + return; + } + + XPIProvider.installLocations.push(location); + XPIProvider.installLocationsByName[location.name] = location; + } + function addRegistryInstallLocation(aName, aRootkey, aScope) { try { var location = new WinRegInstallLocation(aName, aRootkey, aScope); @@ -2366,6 +2392,14 @@ this.XPIProvider = { [DIR_EXTENSIONS], AddonManager.SCOPE_PROFILE, false); + addSystemAddonInstallLocation(KEY_APP_SYSTEM_ADDONS, KEY_PROFILEDIR, + [DIR_SYSTEM_ADDONS], + AddonManager.SCOPE_PROFILE); + + addDirectoryInstallLocation(KEY_APP_SYSTEM_DEFAULTS, KEY_APP_DISTRIBUTION, + [DIR_SYSTEM_ADDONS], + AddonManager.SCOPE_PROFILE, true); + if (enabledScopes & AddonManager.SCOPE_USER) { addDirectoryInstallLocation(KEY_APP_SYSTEM_USER, "XREUSysExt", [Services.appinfo.ID], @@ -6768,7 +6802,7 @@ function DirectoryInstallLocation(aName, aDirectory, aScope) { this._IDToFileMap = {}; this._linkedAddons = []; - if (!aDirectory.exists()) + if (!aDirectory || !aDirectory.exists()) return; if (!aDirectory.isDirectory()) throw new Error("Location must be a directory."); @@ -6893,7 +6927,7 @@ DirectoryInstallLocation.prototype = { /** * Gets an array of nsIFiles for add-ons installed in this location. */ - get addonLocations() { + getAddonLocations: function() { let locations = new Map(); for (let id in this._IDToFileMap) { locations.set(id, this._IDToFileMap[id].clone()); @@ -7212,6 +7246,128 @@ Object.assign(MutableDirectoryInstallLocation.prototype, { }, }); +/** + * An object which identifies a directory install location for system add-ons. + * The location consists of a directory which contains the add-ons installed in + * the location. + * + * @param aName + * The string identifier for the install location + * @param aDirectory + * The nsIFile directory for the install location + * @param aScope + * The scope of add-ons installed in this location + * @param aResetSet + * True to throw away the current add-on set + */ +function SystemAddonInstallLocation(aName, aDirectory, aScope, aResetSet) { + this._baseDir = aDirectory; + + if (aResetSet) { + this._addonSet = { schema: 1, addons: {} }; + this._saveAddonSet(this._addonSet); + } + else { + this._addonSet = this._loadAddonSet(); + } + + this._directory = null; + if (this._addonSet.directory) { + this._directory = aDirectory.clone(); + this._directory.append(this._addonSet.directory); + logger.info("SystemAddonInstallLocation scanning directory " + this._directory.path); + } + else { + logger.info("SystemAddonInstallLocation directory is missing"); + } + + DirectoryInstallLocation.call(this, aName, this._directory, aScope); + this.locked = true; +} + +SystemAddonInstallLocation.prototype = Object.create(DirectoryInstallLocation.prototype); +Object.assign(SystemAddonInstallLocation.prototype, { + /** + * Reads the current set of system add-ons + */ + _loadAddonSet: function() { + try { + let setStr = Preferences.get(PREF_SYSTEM_ADDON_SET, null); + if (setStr) { + let addonSet = JSON.parse(setStr); + if ((typeof addonSet == "object") && addonSet.schema == 1) + return addonSet; + } + } + catch (e) { + logger.error("Malformed system add-on set, resetting."); + } + + return { schema: 1, addons: {} }; + }, + + /** + * Saves the current set of system add-ons + */ + _saveAddonSet: function(aAddonSet) { + Preferences.set(PREF_SYSTEM_ADDON_SET, JSON.stringify(aAddonSet)); + }, + + getAddonLocations: function() { + let addons = DirectoryInstallLocation.prototype.getAddonLocations.call(this); + + // Strip out any unexpected add-ons from the list + for (let id of addons.keys()) { + if (!(id in this._addonSet.addons)) + addons.delete(id); + } + + return addons; + }, + + /** + * Tests whether updated system add-ons are expected. + */ + isActive: function() { + return this._directory != null; + }, + + /** + * Tests whether the loaded add-on information matches what is expected. + */ + isValid: function(aAddons) { + for (let id of Object.keys(this._addonSet.addons)) { + if (!aAddons.has(id)) { + logger.warn("Expected add-on " + id + " is missing from the system add-on location."); + return false; + } + + let addon = aAddons.get(id); + if (addon.appDisabled) { + logger.warn("System add-on " + id + " isn't compatible with the application."); + return false; + } + + if (addon.unpack) { + logger.warn("System add-on " + id + " isn't a packed add-on."); + return false; + } + + if (!addon.bootstrap) { + logger.warn("System add-on " + id + " isn't restartless."); + return false; + } + + if (addon.version != this._addonSet.addons[id].version) { + logger.warn("System add-on " + id + " wasn't the correct version."); + return false; + } + } + + return true; + }, +}); + #ifdef XP_WIN /** * An object that identifies a registry install location for add-ons. The location @@ -7318,7 +7474,7 @@ WinRegInstallLocation.prototype = { /** * Gets an array of nsIFiles for add-ons installed in this location. */ - get addonLocations() { + getAddonLocations: function() { let locations = new Map(); for (let id in this._IDToFileMap) { locations.set(id, this._IDToFileMap[id].clone()); diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js index 48b3ecec272..eca104eeb73 100644 --- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js @@ -53,6 +53,8 @@ const PREF_EM_DSS_ENABLED = "extensions.dss.enabled"; const PREF_EM_AUTO_DISABLED_SCOPES = "extensions.autoDisableScopes"; 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"; // Properties that only exist in the database @@ -1512,10 +1514,13 @@ this.XPIDatabaseReconcile = { * Returns a map of ID -> add-on. When the same add-on ID exists in multiple * install locations the highest priority location is chosen. */ - flattenByID(addonMap) { + flattenByID(addonMap, hideLocation) { let map = new Map(); for (let installLocation of XPIProvider.installLocations) { + if (installLocation.name == hideLocation) + continue; + let locationMap = addonMap.get(installLocation.name); if (!locationMap) continue; @@ -1623,7 +1628,12 @@ this.XPIDatabaseReconcile = { aNewAddon._installLocation = aInstallLocation; aNewAddon.installDate = aAddonState.mtime; aNewAddon.updateDate = aAddonState.mtime; - aNewAddon.foreignInstall = isDetectedInstall; + + // Assume that add-ons in the system add-ons install location aren't + // foreign and should default to enabled. + aNewAddon.foreignInstall = isDetectedInstall && + aInstallLocation.name != KEY_APP_SYSTEM_ADDONS && + aInstallLocation.name != KEY_APP_SYSTEM_DEFAULTS; // appDisabled depends on whether the add-on is a foreignInstall so update aNewAddon.appDisabled = !isUsableAddon(aNewAddon); @@ -1901,7 +1911,8 @@ this.XPIDatabaseReconcile = { // has changed let newAddon = loadedManifest(installLocation, id); if (newAddon || oldAddon.updateDate != xpiState.mtime || - (aUpdateCompatibility && installLocation.name == KEY_APP_GLOBAL)) { + (aUpdateCompatibility && (installLocation.name == KEY_APP_GLOBAL || + installLocation.name == KEY_APP_SYSTEM_DEFAULTS))) { newAddon = this.updateMetadata(installLocation, oldAddon, xpiState, newAddon); } else if (oldAddon.descriptor != xpiState.descriptor) { @@ -1958,8 +1969,24 @@ this.XPIDatabaseReconcile = { } } + // Validate the updated system add-ons + let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS]; + let addons = currentAddons.get(KEY_APP_SYSTEM_ADDONS) || new Map(); + + let hideLocation; + if (systemAddonLocation.isActive() && systemAddonLocation.isValid(addons)) { + // Hide the system add-on defaults + logger.info("Hiding the default system add-ons."); + hideLocation = KEY_APP_SYSTEM_DEFAULTS; + } + else { + // Hide the system add-on updates + logger.info("Hiding the updated system add-ons."); + hideLocation = KEY_APP_SYSTEM_ADDONS; + } + let previousVisible = this.getVisibleAddons(previousAddons); - let currentVisible = this.flattenByID(currentAddons); + let currentVisible = this.flattenByID(currentAddons, hideLocation); let sawActiveTheme = false; XPIProvider.bootstrappedAddons = {}; @@ -2027,11 +2054,9 @@ this.XPIDatabaseReconcile = { // and still exists then call its uninstall method. if (previousAddon.bootstrap && previousAddon._installLocation && currentAddon._installLocation != previousAddon._installLocation && - currentAddons.get(previousAddon._installLocation.name).has(id)) { + previousAddon._sourceBundle.exists()) { - let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); - file.persistentDescriptor = previousAddon._sourceBundle.persistentDescriptor; - XPIProvider.callBootstrapMethod(previousAddon, file, + XPIProvider.callBootstrapMethod(previousAddon, previousAddon._sourceBundle, "uninstall", installReason, { newVersion: currentAddon.version }); XPIProvider.unloadBootstrapScope(previousAddon.id); @@ -2086,6 +2111,15 @@ this.XPIDatabaseReconcile = { AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED, id); } + // Make sure add-ons from hidden locations are marked invisible and inactive + let locationAddonMap = currentAddons.get(hideLocation); + if (locationAddonMap) { + for (let addon of locationAddonMap.values()) { + addon.visible = false; + addon.active = false; + } + } + // None of the active add-ons match the selected theme, enable the default. if (!sawActiveTheme) { XPIProvider.enableDefaultTheme(); diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app0/empty b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app0/empty new file mode 100644 index 00000000000..e69de29bb2d diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system1@tests.mozilla.org.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system1@tests.mozilla.org.xpi new file mode 100644 index 00000000000..838b1b6584a Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system1@tests.mozilla.org.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system2@tests.mozilla.org.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system2@tests.mozilla.org.xpi new file mode 100644 index 00000000000..c346cf3b7d0 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system2@tests.mozilla.org.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/bootstrap.js new file mode 100644 index 00000000000..c5d862f45a7 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/bootstrap.js @@ -0,0 +1,18 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); + +const ID = "system1@tests.mozilla.org"; +const VERSION = "1.0"; + +function install(data, reason) { +} + +function startup(data, reason) { + Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION); +} + +function shutdown(data, reason) { + Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version"); +} + +function uninstall(data, reason) { +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/install.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/install.rdf new file mode 100644 index 00000000000..5ec174d0440 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/install.rdf @@ -0,0 +1,23 @@ + + + + + + system1@tests.mozilla.org + 1.0 + true + + + System Add-on 1 + + + + xpcshell@tests.mozilla.org + 1 + 5 + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/bootstrap.js new file mode 100644 index 00000000000..e9449de6ec9 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/bootstrap.js @@ -0,0 +1,18 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); + +const ID = "system2@tests.mozilla.org"; +const VERSION = "1.0"; + +function install(data, reason) { +} + +function startup(data, reason) { + Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION); +} + +function shutdown(data, reason) { + Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version"); +} + +function uninstall(data, reason) { +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/install.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/install.rdf new file mode 100644 index 00000000000..b6b4efc82ed --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/install.rdf @@ -0,0 +1,23 @@ + + + + + + system2@tests.mozilla.org + 1.0 + true + + + System Add-on 2 + + + + xpcshell@tests.mozilla.org + 1 + 5 + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system1@tests.mozilla.org.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system1@tests.mozilla.org.xpi new file mode 100644 index 00000000000..f82f607f9c7 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system1@tests.mozilla.org.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system3@tests.mozilla.org.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system3@tests.mozilla.org.xpi new file mode 100644 index 00000000000..7fa67926282 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system3@tests.mozilla.org.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/bootstrap.js new file mode 100644 index 00000000000..7a18c8ca3fa --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/bootstrap.js @@ -0,0 +1,18 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); + +const ID = "system1@tests.mozilla.org"; +const VERSION = "2.0"; + +function install(data, reason) { +} + +function startup(data, reason) { + Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION); +} + +function shutdown(data, reason) { + Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version"); +} + +function uninstall(data, reason) { +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/install.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/install.rdf new file mode 100644 index 00000000000..75a89f52d56 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/install.rdf @@ -0,0 +1,23 @@ + + + + + + system1@tests.mozilla.org + 2.0 + true + + + System Add-on 1 + + + + xpcshell@tests.mozilla.org + 1 + 5 + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/bootstrap.js new file mode 100644 index 00000000000..198946e6ea9 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/bootstrap.js @@ -0,0 +1,18 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); + +const ID = "system3@tests.mozilla.org"; +const VERSION = "1.0"; + +function install(data, reason) { +} + +function startup(data, reason) { + Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION); +} + +function shutdown(data, reason) { + Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version"); +} + +function uninstall(data, reason) { +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/install.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/install.rdf new file mode 100644 index 00000000000..83be8dcc912 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/install.rdf @@ -0,0 +1,23 @@ + + + + + + system3@tests.mozilla.org + 1.0 + true + + + System Add-on 3 + + + + xpcshell@tests.mozilla.org + 1 + 5 + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 6a4ac99bcf6..e6a72cd2fbe 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1026,7 +1026,7 @@ function getFileForAddon(aDir, aId) { function registerDirectory(aKey, aDir) { var dirProvider = { getFile: function(aProp, aPersistent) { - aPersistent.value = true; + aPersistent.value = false; if (aProp == aKey) return aDir.clone(); return null; diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js b/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js new file mode 100644 index 00000000000..159ba2f15b2 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js @@ -0,0 +1,200 @@ +// Tests that we reset to the default system add-ons correctly when switching +// application versions +const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet"; + +const featureDir = gProfD.clone(); +featureDir.append("features"); + +const distroDir = do_get_file("data/system_addons/app0"); +registerDirectory("XREAppDist", distroDir); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "0"); + +function makeUUID() { + let uuidGen = AM_Cc["@mozilla.org/uuid-generator;1"]. + getService(AM_Ci.nsIUUIDGenerator); + return uuidGen.generateUUID().toString(); +} + +function* check_installed(inProfile, ...versions) { + let expectedDir; + if (inProfile) { + expectedDir = featureDir; + } + else { + expectedDir = distroDir.clone(); + expectedDir.append("features"); + } + + for (let i = 0; i < versions.length; i++) { + let id = "system" + (i + 1) + "@tests.mozilla.org"; + let addon = yield promiseAddonByID(id); + + if (versions[i]) { + // Add-on should be installed + do_check_neq(addon, null); + do_check_eq(addon.version, versions[i]); + do_check_true(addon.isActive); + do_check_false(addon.foreignInstall); + + // Verify the add-ons file is in the right place + let file = expectedDir.clone(); + file.append(id + ".xpi"); + do_check_true(file.exists()); + do_check_true(file.isFile()); + + let uri = addon.getResourceURI(null); + do_check_true(uri instanceof AM_Ci.nsIFileURL); + do_check_eq(uri.file.path, file.path); + + // Verify the add-on actually started + let installed = Services.prefs.getCharPref("bootstraptest." + id + ".active_version"); + do_check_eq(installed, versions[i]); + } + else { + // Add-on should not be installed + do_check_eq(addon, null); + + try { + Services.prefs.getCharPref("bootstraptest." + id + ".active_version"); + do_throw("Expected pref to be missing"); + } + catch (e) { + } + } + } +} + +// Test with a missing features directory +add_task(function* test_missing_app_dir() { + startupManager(); + + yield check_installed(false, null, null, null); + + do_check_false(featureDir.exists()); + + yield promiseShutdownManager(); +}); + +// Add some features in a new version +add_task(function* test_new_version() { + gAppInfo.version = "1"; + distroDir.leafName = "app1"; + startupManager(); + + yield check_installed(false, "1.0", "1.0", null); + + do_check_false(featureDir.exists()); + + yield promiseShutdownManager(); +}); + +// Another new version swaps one feature and upgrades another +add_task(function* test_upgrade() { + gAppInfo.version = "2"; + distroDir.leafName = "app2"; + startupManager(); + + yield check_installed(false, "2.0", null, "1.0"); + + do_check_false(featureDir.exists()); + + yield promiseShutdownManager(); +}); + +// Downgrade +add_task(function* test_downgrade() { + gAppInfo.version = "1"; + distroDir.leafName = "app1"; + startupManager(); + + yield check_installed(false, "1.0", "1.0", null); + + do_check_false(featureDir.exists()); + + yield promiseShutdownManager(); +}); + +// Fake a mid-cycle install +add_task(function* test_updated() { + // Create a random dir to install into + let dirname = makeUUID(); + FileUtils.getDir("ProfD", ["features", dirname], true); + featureDir.append(dirname); + + // Copy in the system add-ons + let file = do_get_file("data/system_addons/app1/features/system2@tests.mozilla.org.xpi"); + file.copyTo(featureDir, file.leafName); + file = do_get_file("data/system_addons/app2/features/system3@tests.mozilla.org.xpi"); + file.copyTo(featureDir, file.leafName); + + // Inject it into the system set + let addonSet = { + schema: 1, + directory: dirname, + addons: { + "system2@tests.mozilla.org": { + version: "1.0" + }, + "system3@tests.mozilla.org": { + version: "1.0" + }, + } + }; + Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, JSON.stringify(addonSet)); + + startupManager(false); + + yield check_installed(true, null, "1.0", "1.0"); + + yield promiseShutdownManager(); +}); + +// An additional add-on in the directory should be ignored +add_task(function* test_skips_additional() { + // Copy in the system add-ons + let file = do_get_file("data/system_addons/app1/features/system1@tests.mozilla.org.xpi"); + file.copyTo(featureDir, file.leafName); + + startupManager(false); + + yield check_installed(true, null, "1.0", "1.0"); + + yield promiseShutdownManager(); +}); + +// Missing add-on should revert to the default set +add_task(function* test_revert() { + manuallyUninstall(featureDir, "system2@tests.mozilla.org"); + + startupManager(false); + + // With system add-on 2 gone the updated set is now invalid so it reverts to + // the default set which is system add-ons 1 and 2. + yield check_installed(false, "1.0", "1.0", null); + + yield promiseShutdownManager(); +}); + +// Putting it back will make the set work again +add_task(function* test_reuse() { + let file = do_get_file("data/system_addons/app1/features/system2@tests.mozilla.org.xpi"); + file.copyTo(featureDir, file.leafName); + + startupManager(false); + + yield check_installed(true, null, "1.0", "1.0"); + + yield promiseShutdownManager(); +}); + +// Making the pref corrupt should revert to the default set +add_task(function* test_corrupt_pref() { + Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, "foo"); + + startupManager(false); + + yield check_installed(false, "1.0", "1.0", null); + + yield promiseShutdownManager(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini index 03041c8f9f1..a87c4ac399e 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -24,6 +24,7 @@ skip-if = appname != "firefox" [test_provider_unsafe_access_shutdown.js] [test_provider_unsafe_access_startup.js] [test_shutdown.js] +[test_system_reset.js] [test_XPIcancel.js] [test_XPIStates.js]