Bug 1348981 - only load system add-ons from a built-in list r=kmag
authorRobert Helmer <rhelmer@mozilla.com>
Mon, 02 Oct 2017 18:41:20 -0700
changeset 427243 34663fd2712db9af38265940b057ad609e9ae398
parent 427242 388768fdc403a256ebdf8a9513586cae0937dbb3
child 427244 1e8554fbafadd2b1e403e6f8de91dfc1b2912d06
push id97
push userfmarier@mozilla.com
push dateSat, 14 Oct 2017 01:12:59 +0000
reviewerskmag
bugs1348981
milestone58.0a1
Bug 1348981 - only load system add-ons from a built-in list r=kmag MozReview-Commit-ID: A6c5kaLmNPP
services/sync/tests/unit/test_addons_store.js
toolkit/components/telemetry/tests/unit/head.js
toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/head_addons.js
toolkit/mozapps/extensions/test/xpcshell/test_nodisable_hidden.js
toolkit/mozapps/extensions/test/xpcshell/test_system_allowed.js
toolkit/mozapps/extensions/test/xpcshell/test_system_delay_update.js
toolkit/mozapps/extensions/test/xpcshell/test_system_repository.js
toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
toolkit/mozapps/extensions/test/xpcshell/test_system_update.js
toolkit/mozapps/extensions/test/xpcshell/test_system_update_blank.js
toolkit/mozapps/extensions/test/xpcshell/test_system_update_checkSizeHash.js
toolkit/mozapps/extensions/test/xpcshell/test_system_update_custom.js
toolkit/mozapps/extensions/test/xpcshell/test_system_update_empty.js
toolkit/mozapps/extensions/test/xpcshell/test_system_update_fail.js
toolkit/mozapps/extensions/test/xpcshell/test_system_update_newset.js
toolkit/mozapps/extensions/test/xpcshell/test_system_update_overlapping.js
toolkit/mozapps/extensions/test/xpcshell/test_system_update_upgrades.js
toolkit/mozapps/extensions/test/xpcshell/test_update.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
--- a/services/sync/tests/unit/test_addons_store.js
+++ b/services/sync/tests/unit/test_addons_store.js
@@ -32,16 +32,17 @@ function loadSystemAddon() {
   systemAddonFile.lastModifiedTime = Date.now();
   // As we're not running in application, we need to setup the features directory
   // used by system add-ons.
   registerDirectory("XREAppFeat", distroDir);
 }
 
 loadAddonTestFunctions();
 loadSystemAddon();
+awaitPromise(overrideBuiltIns({ "system": [SYSTEM_ADDON_ID] }));
 startupManager();
 
 let engine;
 let tracker;
 let store;
 let reconciler;
 
 /**
--- a/toolkit/components/telemetry/tests/unit/head.js
+++ b/toolkit/components/telemetry/tests/unit/head.js
@@ -183,16 +183,18 @@ function loadAddonManager(...args) {
   AddonTestUtils.init(gGlobalScope);
   AddonTestUtils.overrideCertDB();
   createAppInfo(...args);
 
   // As we're not running in application, we need to setup the features directory
   // used by system add-ons.
   const distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "app0"], true);
   AddonTestUtils.registerDirectory("XREAppFeat", distroDir);
+  AddonTestUtils.awaitPromise(AddonTestUtils.overrideBuiltIns(
+          {"system": ["tel-system-xpi@tests.mozilla.org"]}));
   return AddonTestUtils.promiseStartupManager();
 }
 
 function finishAddonManagerStartup() {
   Services.obs.notifyObservers(null, "test-load-xpi-database");
 }
 
 var gAppInfo = null;
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
@@ -864,17 +864,19 @@ add_task(async function setup() {
   LightweightThemeManager.currentTheme =
     spoofTheme(PERSONA_ID, PERSONA_NAME, PERSONA_DESCRIPTION);
 
   // The test runs in a fresh profile so starting the AddonManager causes
   // the addons database to be created (as does setting new theme).
   // For test_addonsStartup below, we want to test a "warm" startup where
   // there is already a database on disk.  Simulate that here by just
   // restarting the AddonManager.
-  await AddonTestUtils.promiseRestartManager();
+  await AddonTestUtils.promiseShutdownManager();
+  await AddonTestUtils.overrideBuiltIns({"system": []});
+  await AddonTestUtils.promiseStartupManager();
 
   // Register a fake plugin host for consistent flash version data.
   registerFakePluginHost();
 
   // Setup a webserver to serve Addons, Plugins, etc.
   gHttpServer = new HttpServer();
   gHttpServer.start(-1);
   let port = gHttpServer.identity.primaryPort;
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -215,16 +215,17 @@ class AddonsList {
 var AddonTestUtils = {
   addonIntegrationService: null,
   addonsList: null,
   appInfo: null,
   addonStartup: null,
   testUnpacked: false,
   useRealCertChecks: false,
   usePrivilegedSignatures: true,
+  overrideEntry: null,
 
   init(testScope) {
     this.testScope = testScope;
 
     // Get the profile directory for tests to use.
     this.profileDir = testScope.do_get_profile();
 
     this.profileExtensions = this.profileDir.clone();
@@ -588,16 +589,20 @@ var AddonTestUtils = {
     // Load the add-ons list as it was after extension registration
     await this.loadAddonsList(true);
   },
 
   promiseShutdownManager() {
     if (!this.addonIntegrationService)
       return Promise.resolve(false);
 
+    if (this.overrideEntry) {
+      this.overrideEntry.destruct();
+    }
+
     Services.obs.notifyObservers(null, "quit-application-granted");
     return MockAsyncShutdown.hook()
       .then(() => {
         this.emit("addon-manager-shutdown");
 
         this.addonIntegrationService = null;
 
         // Load the add-ons list as it was after application shutdown
@@ -1268,16 +1273,39 @@ var AddonTestUtils = {
       Management.on("ready", function listener(event, extension) {
         if (!id || extension.id == id) {
           Management.off("ready", listener);
           resolve(extension);
         }
       });
     });
   },
+
+  /**
+   * Override chrome URL for specifying allowed built-in add-ons.
+   *
+   * @param {object} data - An object specifying which add-on IDs are permitted
+   *                        to load, for instance: { "system": ["id1", "..."] }
+   */
+  async overrideBuiltIns(data) {
+    // We need to set this in order load the URL preloader service, which
+    // is only possible when running in automation.
+    Services.prefs.setBoolPref(PREF_DISABLE_SECURITY, true);
+    aomStartup.initializeURLPreloader();
+
+    let file = FileUtils.getFile("TmpD", "override.txt");
+    let manifest = Services.io.newFileURI(file);
+    await OS.File.writeAtomic(file.path,
+      new TextEncoder().encode(JSON.stringify(data)));
+    this.overrideEntry = aomStartup.registerChrome(manifest, [
+      ["override", "chrome://browser/content/built_in_addons.json",
+       Services.io.newFileURI(file).spec],
+    ]);
+    Services.prefs.setBoolPref(PREF_DISABLE_SECURITY, false);
+  }
 };
 
 for (let [key, val] of Object.entries(AddonTestUtils)) {
   if (typeof val == "function")
     AddonTestUtils[key] = val.bind(AddonTestUtils);
 }
 
 EventEmitter.decorate(AddonTestUtils);
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -97,16 +97,19 @@ const PREF_ALLOW_LEGACY               = 
 const PREF_ALLOW_NON_MPC              = "extensions.allow-non-mpc-extensions";
 
 const PREF_EM_MIN_COMPAT_APP_VERSION      = "extensions.minCompatibleAppVersion";
 const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
 
 const PREF_EM_HOTFIX_ID               = "extensions.hotfix.id";
 const PREF_EM_LAST_APP_BUILD_ID       = "extensions.lastAppBuildId";
 
+// Specify a list of valid built-in add-ons to load.
+const BUILT_IN_ADDONS_URI             = "chrome://browser/content/built_in_addons.json";
+
 const OBSOLETE_PREFERENCES = [
   "extensions.bootstrappedAddons",
   "extensions.enabledAddons",
   "extensions.xpiState",
   "extensions.installCache",
 ];
 
 const URI_EXTENSION_STRINGS           = "chrome://mozapps/locale/extensions/extensions.properties";
@@ -2034,16 +2037,28 @@ this.XPIProvider = {
         logger.warn("Failed to add directory install location " + aName, e);
         return;
       }
 
       XPIProvider.installLocations.push(location);
       XPIProvider.installLocationsByName[location.name] = location;
     }
 
+    function addBuiltInInstallLocation(name, key, paths, scope) {
+      try {
+        let dir = FileUtils.getDir(key, paths);
+        let location = new BuiltInInstallLocation(name, dir, scope);
+
+        XPIProvider.installLocations.push(location);
+        XPIProvider.installLocationsByName[location.name] = location;
+      } catch (e) {
+        logger.warn(`Failed to add built-in install location ${name}`, e);
+      }
+    }
+
     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;
       }
@@ -2102,18 +2117,18 @@ this.XPIProvider = {
       addDirectoryInstallLocation(KEY_APP_PROFILE, KEY_PROFILEDIR,
                                   [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_FEATURES,
-                                  [], AddonManager.SCOPE_PROFILE, true);
+      addBuiltInInstallLocation(KEY_APP_SYSTEM_DEFAULTS, KEY_APP_FEATURES,
+                                [], AddonManager.SCOPE_PROFILE);
 
       if (enabledScopes & AddonManager.SCOPE_USER) {
         addDirectoryInstallLocation(KEY_APP_SYSTEM_USER, "XREUSysExt",
                                     [Services.appinfo.ID],
                                     AddonManager.SCOPE_USER, true);
         if (hasRegistry) {
           addRegistryInstallLocation("winreg-app-user",
                                      Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
@@ -6447,16 +6462,59 @@ class MutableDirectoryInstallLocation ex
 
     XPIStates.removeAddon(this.name, aId);
 
     delete this._IDToFileMap[aId];
   }
 }
 
 /**
+ * An object which identifies a built-in install location for add-ons, such
+ * as default system add-ons.
+ *
+ * This location should point either to a XPI, or a directory in a local build.
+ */
+class BuiltInInstallLocation extends DirectoryInstallLocation {
+  /**
+   * Read the manifest of allowed add-ons and build a mapping between ID and URI
+   * for each.
+   */
+  _readAddons() {
+    let manifest;
+    try {
+      let url = Services.io.newURI(BUILT_IN_ADDONS_URI);
+      let data = Cu.readURI(url);
+      manifest = JSON.parse(data);
+    } catch (e) {
+      logger.warn("List of valid built-in add-ons could not be parsed.", e);
+      return;
+    }
+
+    if (!("system" in manifest)) {
+      logger.warn("No list of valid system add-ons found.");
+      return;
+    }
+
+    for (let id of manifest.system) {
+      let file = new FileUtils.File(this._directory.path);
+      file.append(`${id}.xpi`);
+
+      // Only attempt to load unpacked directory if unofficial build.
+      if (!AppConstants.MOZILLA_OFFICIAL && !file.exists()) {
+        file = new FileUtils.File(this._directory.path);
+        file.append(`${id}`);
+      }
+
+      this._IDToFileMap[id] = file;
+      XPIProvider._addURIMapping(id, file);
+    }
+  }
+}
+
+/**
  * An object which identifies a directory install location for system add-ons
  * updates.
  */
 class SystemAddonInstallLocation extends MutableDirectoryInstallLocation {
   /**
     * The location consists of a directory which contains the add-ons installed.
     *
     * @param  aName
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -19,16 +19,18 @@ const PREF_EM_MIN_COMPAT_APP_VERSION    
 const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
 const PREF_GETADDONS_BYIDS               = "extensions.getAddons.get.url";
 const PREF_GETADDONS_BYIDS_PERFORMANCE   = "extensions.getAddons.getWithPerformance.url";
 const PREF_XPI_SIGNATURES_REQUIRED    = "xpinstall.signatures.required";
 const PREF_SYSTEM_ADDON_SET           = "extensions.systemAddonSet";
 const PREF_SYSTEM_ADDON_UPDATE_URL    = "extensions.systemAddon.update.url";
 const PREF_APP_UPDATE_ENABLED         = "app.update.enabled";
 const PREF_ALLOW_NON_MPC              = "extensions.allow-non-mpc-extensions";
+const PREF_DISABLE_SECURITY = ("security.turn_off_all_security_so_that_" +
+                               "viruses_can_take_over_this_computer");
 
 // Forcibly end the test if it runs longer than 15 minutes
 const TIMEOUT_MS = 900000;
 
 // Maximum error in file modification times. Some file systems don't store
 // modification times exactly. As long as we are closer than this then it
 // still passes.
 const MAX_TIME_DIFFERENCE = 3000;
@@ -69,16 +71,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
 const {
   awaitPromise,
   createAppInfo,
   createInstallRDF,
   createTempWebExtensionFile,
   createUpdateRDF,
   getFileForAddon,
   manuallyUninstall,
+  overrideBuiltIns,
   promiseAddonEvent,
   promiseCompleteAllInstalls,
   promiseCompleteInstall,
   promiseConsoleOutput,
   promiseFindAddonUpdates,
   promiseInstallAllFiles,
   promiseInstallFile,
   promiseRestartManager,
@@ -1524,22 +1527,33 @@ async function getSystemAddonDirectories
  *  of the form {isUpgrade: false, version: null}
  *
  * @param {nsIFile} distroDir - the system add-on distribution directory (the "features" dir in the app directory)
  */
 async function setupSystemAddonConditions(setup, distroDir) {
   do_print("Clearing existing database.");
   Services.prefs.clearUserPref(PREF_SYSTEM_ADDON_SET);
   distroDir.leafName = "empty";
+
+  let updateList = [];
+  awaitPromise(overrideBuiltIns({ "system": updateList }));
   startupManager(false);
   await promiseShutdownManager();
 
   do_print("Setting up conditions.");
   await setup.setup();
 
+  if (distroDir) {
+    if (distroDir.path.endsWith("hidden")) {
+      updateList = ["system1@tests.mozilla.org", "system2@tests.mozilla.org"];
+    } else if (distroDir.path.endsWith("prefilled")) {
+      updateList = ["system2@tests.mozilla.org", "system3@tests.mozilla.org"];
+    }
+  }
+  awaitPromise(overrideBuiltIns({ "system": updateList }));
   startupManager(false);
 
   // Make sure the initial state is correct
   do_print("Checking initial state.");
   await checkInstalledSystemAddons(setup.initialState, distroDir);
 }
 
 /**
@@ -1575,17 +1589,29 @@ async function verifySystemAddonState(in
   do_print("Checking final state.");
 
   let dirs = await getSystemAddonDirectories();
   do_check_eq(dirs.length, expectedDirs);
 
   await checkInstalledSystemAddons(...finalState, distroDir);
 
   // Check that the new state is active after a restart
-  await promiseRestartManager();
+  await promiseShutdownManager();
+
+  let updateList = [];
+
+  if (distroDir) {
+    if (distroDir.path.endsWith("hidden")) {
+      updateList = ["system1@tests.mozilla.org", "system2@tests.mozilla.org"];
+    } else if (distroDir.path.endsWith("prefilled")) {
+      updateList = ["system2@tests.mozilla.org", "system3@tests.mozilla.org"];
+    }
+  }
+  awaitPromise(overrideBuiltIns({ "system": updateList }));
+  startupManager();
   await checkInstalledSystemAddons(finalState, distroDir);
 }
 
 /**
  * Run system add-on tests and compare the results against a set of expected conditions.
  *
  * @param {String} setupName - name of the current setup conditions.
  * @param {Object<function, Array<Object>} setup -  Defines the set of initial conditions to run each test against. Each should
--- a/toolkit/mozapps/extensions/test/xpcshell/test_nodisable_hidden.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_nodisable_hidden.js
@@ -13,17 +13,17 @@ registerDirectory("XREAppFeat", distroDi
 const NORMAL_ID = "normal@tests.mozilla.org";
 const SYSTEM_ID = "system@tests.mozilla.org";
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
 // normal add-ons can be user disabled.
 add_task(async function() {
 
-  writeInstallRDFToDir({
+  writeInstallRDFToXPI({
     id: NORMAL_ID,
     version: "1.0",
     bootstrap: true,
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: "1",
       maxVersion: "1"
     }],
@@ -57,28 +57,30 @@ add_task(async function() {
   addon.uninstall();
 
   shutdownManager();
 });
 
 // system add-ons can never be user disabled.
 add_task(async function() {
 
-  writeInstallRDFToDir({
+  writeInstallRDFToXPI({
     id: SYSTEM_ID,
     version: "1.0",
     bootstrap: true,
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: "1",
       maxVersion: "1"
     }],
     name: "Test disabling hidden add-ons, hidden system add-on case.",
   }, distroDir, SYSTEM_ID);
 
+  await overrideBuiltIns({ "system": [SYSTEM_ID] });
+
   startupManager();
 
   let addon = await promiseAddonByID(SYSTEM_ID);
   do_check_neq(addon, null);
   do_check_eq(addon.version, "1.0");
   do_check_eq(addon.name, "Test disabling hidden add-ons, hidden system add-on case.");
   do_check_true(addon.isCompatible);
   do_check_false(addon.appDisabled);
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_allowed.js
@@ -0,0 +1,48 @@
+// Tests that only allowed built-in system add-ons are loaded on startup.
+
+BootstrapMonitor.init();
+
+// Build the test set
+var distroDir = FileUtils.getDir("ProfD", ["sysfeatures"], true);
+do_get_file("data/system_addons/system1_1.xpi").copyTo(distroDir, "system1@tests.mozilla.org.xpi");
+do_get_file("data/system_addons/system2_1.xpi").copyTo(distroDir, "system2@tests.mozilla.org.xpi");
+do_get_file("data/system_addons/system3_1.xpi").copyTo(distroDir, "system3@tests.mozilla.org.xpi");
+registerDirectory("XREAppFeat", distroDir);
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "0");
+
+// Ensure that only allowed add-ons are loaded.
+add_task(async function test_allowed_addons() {
+  // 1 and 2 are allowed, 3 is not.
+  let validAddons = { "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org"]};
+  await overrideBuiltIns(validAddons);
+
+  await promiseStartupManager();
+
+  let addon = await AddonManager.getAddonByID("system1@tests.mozilla.org");
+  notEqual(addon, null);
+
+  addon = await AddonManager.getAddonByID("system2@tests.mozilla.org");
+  notEqual(addon, null);
+
+  addon = await AddonManager.getAddonByID("system3@tests.mozilla.org");
+  do_check_eq(addon, null);
+  equal(addon, null);
+
+  // 3 is now allowed, 1 and 2 are not.
+  validAddons = { "system": ["system3@tests.mozilla.org"]};
+  await overrideBuiltIns(validAddons);
+
+  await promiseRestartManager();
+
+  addon = await AddonManager.getAddonByID("system1@tests.mozilla.org");
+  equal(addon, null);
+
+  addon = await AddonManager.getAddonByID("system2@tests.mozilla.org");
+  equal(addon, null);
+
+  addon = await AddonManager.getAddonByID("system3@tests.mozilla.org");
+  notEqual(addon, null);
+
+  await promiseShutdownManager();
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_delay_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_delay_update.js
@@ -9,17 +9,16 @@ const profileDir = gProfD.clone();
 profileDir.append("extensions");
 
 const IGNORE_ID = "system_delay_ignore@tests.mozilla.org";
 const COMPLETE_ID = "system_delay_complete@tests.mozilla.org";
 const DEFER_ID = "system_delay_defer@tests.mozilla.org";
 const DEFER_ALSO_ID = "system_delay_defer_also@tests.mozilla.org";
 const NORMAL_ID = "system1@tests.mozilla.org";
 
-
 const TEST_IGNORE_PREF = "delaytest.ignore";
 
 const distroDir = FileUtils.getDir("ProfD", ["sysfeatures"], true);
 registerDirectory("XREAppFeat", distroDir);
 
 let testserver = new HttpServer();
 testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
 testserver.start();
@@ -113,16 +112,18 @@ function promiseInstallDeferred(addonID1
 // add-on registers upgrade listener, and ignores update.
 add_task(async function() {
   // discard system addon updates
   Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, "");
 
   do_get_file("data/system_addons/system_delay_ignore.xpi").copyTo(distroDir, "system_delay_ignore@tests.mozilla.org.xpi");
   do_get_file("data/system_addons/system1_1.xpi").copyTo(distroDir, "system1@tests.mozilla.org.xpi");
 
+  await overrideBuiltIns({ "system": [IGNORE_ID, NORMAL_ID] })
+
   startupManager();
   let updateList = [
     { id: IGNORE_ID, version: "2.0", path: "system_delay_ignore_2.xpi" },
     { id: NORMAL_ID, version: "2.0", path: "system1_2.xpi" },
   ];
 
   let postponed = promiseInstallPostponed(IGNORE_ID, NORMAL_ID);
   await installSystemAddons(await buildSystemAddonUpdates(updateList, root), testserver);
@@ -176,16 +177,18 @@ add_task(async function() {
 // add-on registers upgrade listener, and allows update.
 add_task(async function() {
   // discard system addon updates
   Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, "");
 
   do_get_file("data/system_addons/system_delay_complete.xpi").copyTo(distroDir, "system_delay_complete@tests.mozilla.org.xpi");
   do_get_file("data/system_addons/system1_1.xpi").copyTo(distroDir, "system1@tests.mozilla.org.xpi");
 
+  await overrideBuiltIns({ "system": [COMPLETE_ID, NORMAL_ID] })
+
   startupManager();
 
   let updateList = [
     { id: COMPLETE_ID, version: "2.0", path: "system_delay_complete_2.xpi" },
     { id: NORMAL_ID, version: "2.0", path: "system1_2.xpi" },
   ];
 
   // initial state
@@ -260,16 +263,18 @@ add_task(async function() {
 // add-on registers upgrade listener, initially defers update then allows upgrade
 add_task(async function() {
   // discard system addon updates
   Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, "");
 
   do_get_file("data/system_addons/system_delay_defer.xpi").copyTo(distroDir, "system_delay_defer@tests.mozilla.org.xpi");
   do_get_file("data/system_addons/system1_1.xpi").copyTo(distroDir, "system1@tests.mozilla.org.xpi");
 
+  await overrideBuiltIns({ "system": [DEFER_ID, NORMAL_ID] })
+
   startupManager();
 
   let updateList = [
     { id: DEFER_ID, version: "2.0", path: "system_delay_defer_2.xpi" },
     { id: NORMAL_ID, version: "2.0", path: "system1_2.xpi" },
   ];
 
   let postponed = promiseInstallPostponed(DEFER_ID, NORMAL_ID);
@@ -349,16 +354,18 @@ add_task(async function() {
 // multiple add-ons registers upgrade listeners, initially defers then each unblock in turn.
 add_task(async function() {
   // discard system addon updates.
   Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, "");
 
   do_get_file("data/system_addons/system_delay_defer.xpi").copyTo(distroDir, "system_delay_defer@tests.mozilla.org.xpi");
   do_get_file("data/system_addons/system_delay_defer_also.xpi").copyTo(distroDir, "system_delay_defer_also@tests.mozilla.org.xpi");
 
+  await overrideBuiltIns({ "system": [DEFER_ID, DEFER_ALSO_ID] })
+
   startupManager();
 
   let updateList = [
     { id: DEFER_ID, version: "2.0", path: "system_delay_defer_2.xpi" },
     { id: DEFER_ALSO_ID, version: "2.0", path: "system_delay_defer_also_2.xpi" },
   ];
 
   let postponed = promiseInstallPostponed(DEFER_ID, DEFER_ALSO_ID);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_repository.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_repository.js
@@ -25,16 +25,18 @@ function getCachedAddon(id) {
 add_task(async function test_app_addons() {
   Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
   Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, `http://localhost:${gServer.identity.primaryPort}/get?%IDS%`);
 
   gServer.registerPathHandler("/get", (request, response) => {
     do_throw("Unexpected request to server.");
   });
 
+  awaitPromise(overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org"] }))
+
   startupManager();
 
   await new Promise((resolve) => {
     AddonRepository.cacheAddons(["system1@tests.mozilla.org",
                                  "system2@tests.mozilla.org",
                                  "system3@tests.mozilla.org"], resolve);
   });
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
@@ -90,16 +90,17 @@ async function check_installed(condition
       else
         BootstrapMonitor.checkAddonNotInstalled(id);
     }
   }
 }
 
 // Test with a missing features directory
 add_task(async function test_missing_app_dir() {
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@tests.mozilla.org"] });
   startupManager();
 
   let conditions = [
       { isUpgrade: false, version: null },
       { isUpgrade: false, version: null },
       { isUpgrade: false, version: null },
   ];
 
@@ -109,16 +110,17 @@ add_task(async function test_missing_app
 
   await promiseShutdownManager();
 });
 
 // Add some features in a new version
 add_task(async function test_new_version() {
   gAppInfo.version = "1";
   distroDir.leafName = "app1";
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@tests.mozilla.org"] });
   startupManager();
 
   let conditions = [
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: false, version: null },
   ];
 
@@ -128,16 +130,17 @@ add_task(async function test_new_version
 
   await promiseShutdownManager();
 });
 
 // Another new version swaps one feature and upgrades another
 add_task(async function test_upgrade() {
   gAppInfo.version = "2";
   distroDir.leafName = "app2";
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@tests.mozilla.org"] });
   startupManager();
 
   let conditions = [
       { isUpgrade: false, version: "2.0" },
       { isUpgrade: false, version: null },
       { isUpgrade: false, version: "1.0" },
   ];
 
@@ -147,16 +150,17 @@ add_task(async function test_upgrade() {
 
   await promiseShutdownManager();
 });
 
 // Downgrade
 add_task(async function test_downgrade() {
   gAppInfo.version = "1";
   distroDir.leafName = "app1";
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@tests.mozilla.org"] });
   startupManager();
 
   let conditions = [
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: false, version: null },
   ];
 
@@ -190,16 +194,17 @@ add_task(async function test_updated() {
       },
       "system3@tests.mozilla.org": {
         version: "2.0"
       },
     }
   };
   Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, JSON.stringify(addonSet));
 
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@tests.mozilla.org"] });
   startupManager(false);
 
   let conditions = [
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: true, version: "2.0" },
       { isUpgrade: true, version: "2.0" },
   ];
 
@@ -207,32 +212,34 @@ add_task(async function test_updated() {
 
   await promiseShutdownManager();
 });
 
 // Entering safe mode should disable the updated system add-ons and use the
 // default system add-ons
 add_task(async function safe_mode_disabled() {
   gAppInfo.inSafeMode = true;
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@tests.mozilla.org"] });
   startupManager(false);
 
   let conditions = [
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: false, version: null },
   ];
 
   await check_installed(conditions);
 
   await promiseShutdownManager();
 });
 
 // Leaving safe mode should re-enable the updated system add-ons
 add_task(async function normal_mode_enabled() {
   gAppInfo.inSafeMode = false;
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@tests.mozilla.org"] });
   startupManager(false);
 
   let conditions = [
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: true, version: "2.0" },
       { isUpgrade: true, version: "2.0" },
   ];
 
@@ -242,16 +249,17 @@ add_task(async function normal_mode_enab
 });
 
 // An additional add-on in the directory should be ignored
 add_task(async function test_skips_additional() {
   // Copy in the system add-ons
   let file = do_get_file("data/system_addons/system4_1.xpi");
   file.copyTo(updatesDir, "system4@tests.mozilla.org.xpi");
 
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@tests.mozilla.org"] });
   startupManager(false);
 
   let conditions = [
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: true, version: "2.0" },
       { isUpgrade: true, version: "2.0" },
   ];
 
@@ -262,16 +270,17 @@ add_task(async function test_skips_addit
 
 // Missing add-on should revert to the default set
 add_task(async function test_revert() {
   manuallyUninstall(updatesDir, "system2@tests.mozilla.org");
 
   // With the add-on physically gone from disk we won't see uninstall events
   BootstrapMonitor.clear("system2@tests.mozilla.org");
 
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@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.
   let conditions = [
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: false, version: null },
@@ -282,16 +291,17 @@ add_task(async function test_revert() {
   await promiseShutdownManager();
 });
 
 // Putting it back will make the set work again
 add_task(async function test_reuse() {
   let file = do_get_file("data/system_addons/system2_2.xpi");
   file.copyTo(updatesDir, "system2@tests.mozilla.org.xpi");
 
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@tests.mozilla.org"] });
   startupManager(false);
 
   let conditions = [
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: true, version: "2.0" },
       { isUpgrade: true, version: "2.0" },
   ];
 
@@ -299,16 +309,17 @@ add_task(async function test_reuse() {
 
   await promiseShutdownManager();
 });
 
 // Making the pref corrupt should revert to the default set
 add_task(async function test_corrupt_pref() {
   Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, "foo");
 
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@tests.mozilla.org"] });
   startupManager(false);
 
   let conditions = [
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: false, version: null },
   ];
 
@@ -335,16 +346,17 @@ add_task(async function test_bad_profile
       },
       "system3@tests.mozilla.org": {
         version: "1.0"
       },
     }
   };
   Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, JSON.stringify(addonSet));
 
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@tests.mozilla.org"] });
   startupManager(false);
 
   let conditions = [
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: false, version: "1.0" },
       { isUpgrade: false, version: null },
   ];
 
@@ -352,16 +364,17 @@ add_task(async function test_bad_profile
 
   await promiseShutdownManager();
 });
 
 // Switching to app defaults that contain a bad certificate should still work
 add_task(async function test_bad_app_cert() {
   gAppInfo.version = "3";
   distroDir.leafName = "app3";
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@tests.mozilla.org"] });
   startupManager();
 
   // Since we updated the app version, the system addon set should be reset as well.
   let addonSet = Services.prefs.getCharPref(PREF_SYSTEM_ADDON_SET);
   do_check_eq(addonSet, `{"schema":1,"addons":{}}`);
 
   // Add-on will still be present
   let addon = await promiseAddonByID("system1@tests.mozilla.org");
@@ -402,19 +415,19 @@ add_task(async function test_updated_bad
       },
       "system_failed_update@tests.mozilla.org": {
         version: "1.0"
       },
     }
   };
   Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, JSON.stringify(addonSet));
 
+  await overrideBuiltIns({ "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org", "system3@tests.mozilla.org", "system5@tests.mozilla.org"] });
   startupManager(false);
 
   let conditions = [
       { isUpgrade: false, version: "1.0" },
   ];
 
   await check_installed(conditions);
 
   await promiseShutdownManager();
 });
-
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_update.js
+++ /dev/null
@@ -1,500 +0,0 @@
-// Tests that system add-on upgrades work.
-
-Components.utils.import("resource://testing-common/httpd.js");
-
-BootstrapMonitor.init();
-
-createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2");
-
-var testserver = new HttpServer();
-testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
-testserver.start();
-var root = testserver.identity.primaryScheme + "://" +
-           testserver.identity.primaryHost + ":" +
-           testserver.identity.primaryPort + "/data/"
-Services.prefs.setCharPref(PREF_SYSTEM_ADDON_UPDATE_URL, root + "update.xml");
-
-let distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "empty"], true);
-registerDirectory("XREAppFeat", distroDir);
-initSystemAddonDirs();
-
-/**
- * Defines the set of initial conditions to run each test against. Each should
- * define the following properties:
- *
- * setup:        A task to setup the profile into the initial state.
- * initialState: The initial expected system add-on state after setup has run.
- */
-const TEST_CONDITIONS = {
-  // Runs tests with no updated or default system add-ons initially installed
-  blank: {
-    setup() {
-      clearSystemAddonUpdatesDir();
-      distroDir.leafName = "empty";
-    },
-    initialState: [
-      { isUpgrade: false, version: null},
-      { isUpgrade: false, version: null},
-      { isUpgrade: false, version: null},
-      { isUpgrade: false, version: null},
-      { isUpgrade: false, version: null}
-    ],
-  },
-  // Runs tests with default system add-ons installed
-  withAppSet: {
-    setup() {
-      clearSystemAddonUpdatesDir();
-      distroDir.leafName = "prefilled";
-    },
-    initialState: [
-      { isUpgrade: false, version: null},
-      { isUpgrade: false, version: "2.0"},
-      { isUpgrade: false, version: "2.0"},
-      { isUpgrade: false, version: null},
-      { isUpgrade: false, version: null}
-    ]
-  },
-
-  // Runs tests with updated system add-ons installed
-  withProfileSet: {
-    setup() {
-      buildPrefilledUpdatesDir();
-      distroDir.leafName = "empty";
-    },
-    initialState: [
-      { isUpgrade: false, version: null},
-      { isUpgrade: true, version: "2.0"},
-      { isUpgrade: true, version: "2.0"},
-      { isUpgrade: false, version: null},
-      { isUpgrade: false, version: null}
-    ]
-  },
-
-  // Runs tests with both default and updated system add-ons installed
-  withBothSets: {
-    setup() {
-      buildPrefilledUpdatesDir();
-      distroDir.leafName = "hidden";
-    },
-    initialState: [
-      { isUpgrade: false, version: "1.0"},
-      { isUpgrade: true, version: "2.0"},
-      { isUpgrade: true, version: "2.0"},
-      { isUpgrade: false, version: null},
-      { isUpgrade: false, version: null}
-    ]
-  },
-};
-
-
-/**
- * The tests to run. Each test must define an updateList or test. The following
- * properties are used:
- *
- * updateList: The set of add-ons the server should respond with.
- * test:       A function to run to perform the update check (replaces
- *             updateList)
- * fails:      An optional property, if true the update check is expected to
- *             fail.
- * finalState: An optional property, the expected final state of system add-ons,
- *             if missing the test condition's initialState is used.
- */
-const TESTS = {
-  // Test that a blank response does nothing
-  blank: {
-    updateList: null,
-  },
-
-  // Test that an empty list removes existing updates, leaving defaults.
-  empty: {
-    updateList: [],
-    finalState: {
-      blank: [
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null}
-      ],
-      withAppSet: [
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: "2.0"},
-        { isUpgrade: false, version: "2.0"},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null}
-      ],
-      withProfileSet: [
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null}
-      ],
-      withBothSets: [
-        { isUpgrade: false, version: "1.0"},
-        { isUpgrade: false, version: "1.0"},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null},
-        // Set this to `true` to so `verifySystemAddonState()` expects a blank profile dir
-        { isUpgrade: true, version: null}
-      ]
-    },
-  },
-  // Tests that a new set of system add-ons gets installed
-  newset: {
-    updateList: [
-      { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" },
-      { id: "system5@tests.mozilla.org", version: "1.0", path: "system5_1.xpi" }
-    ],
-    finalState: {
-      blank: [
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null},
-        { isUpgrade: true, version: "1.0"},
-        { isUpgrade: true, version: "1.0"}
-      ],
-      withAppSet: [
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: "2.0"},
-        { isUpgrade: false, version: "2.0"},
-        { isUpgrade: true, version: "1.0"},
-        { isUpgrade: true, version: "1.0"}
-      ],
-      withProfileSet: [
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null},
-        { isUpgrade: true, version: "1.0"},
-        { isUpgrade: true, version: "1.0"}
-      ],
-      withBothSets: [
-        { isUpgrade: false, version: "1.0"},
-        { isUpgrade: false, version: "1.0"},
-        { isUpgrade: false, version: null},
-        { isUpgrade: true, version: "1.0"},
-        { isUpgrade: true, version: "1.0"}
-      ]
-    }
-  },
-
-  // Tests that an upgraded set of system add-ons gets installed
-  upgrades: {
-    updateList: [
-      { id: "system2@tests.mozilla.org", version: "3.0", path: "system2_3.xpi" },
-      { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi" }
-    ],
-    finalState: {
-      blank: [
-        { isUpgrade: false, version: null},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null}
-      ],
-      withAppSet: [
-        { isUpgrade: false, version: null},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null}
-      ],
-      withProfileSet: [
-        { isUpgrade: false, version: null},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null}
-      ],
-      withBothSets: [
-        { isUpgrade: false, version: "1.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: false, version: null},
-        { isUpgrade: false, version: null}
-      ]
-    }
-  },
-
-  // Tests that a set of system add-ons, some new, some existing gets installed
-  overlapping: {
-    updateList: [
-      { id: "system1@tests.mozilla.org", version: "2.0", path: "system1_2.xpi" },
-      { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
-      { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi" },
-      { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" }
-    ],
-    finalState: {
-      blank: [
-        { isUpgrade: true, version: "2.0"},
-        { isUpgrade: true, version: "2.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: true, version: "1.0"},
-        { isUpgrade: false, version: null}
-      ],
-      withAppSet: [
-        { isUpgrade: true, version: "2.0"},
-        { isUpgrade: true, version: "2.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: true, version: "1.0"},
-        { isUpgrade: false, version: null}
-      ],
-      withProfileSet: [
-        { isUpgrade: true, version: "2.0"},
-        { isUpgrade: true, version: "2.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: true, version: "1.0"},
-        { isUpgrade: false, version: null}
-      ],
-      withBothSets: [
-        { isUpgrade: true, version: "2.0"},
-        { isUpgrade: true, version: "2.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: true, version: "1.0"},
-        { isUpgrade: false, version: null}
-      ]
-    }
-  },
-
-  // Correct sizes and hashes should work
-  checkSizeHash: {
-    updateList: [
-      { id: "system2@tests.mozilla.org", version: "3.0", path: "system2_3.xpi", size: 4697 },
-      { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi", hashFunction: "sha1", hashValue: "a4c7198d56deb315511c02937fd96c696de6cb84" },
-      { id: "system5@tests.mozilla.org", version: "1.0", path: "system5_1.xpi", size: 4691, hashFunction: "sha1", hashValue: "6887b916a1a9a5338b0df4181f6187f5396861eb" }
-    ],
-    finalState: {
-      blank: [
-        { isUpgrade: false, version: null},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: false, version: null},
-        { isUpgrade: true, version: "1.0"}
-      ],
-      withAppSet: [
-        { isUpgrade: false, version: null},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: false, version: null},
-        { isUpgrade: true, version: "1.0"}
-      ],
-      withProfileSet: [
-        { isUpgrade: false, version: null},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: false, version: null},
-        { isUpgrade: true, version: "1.0"}
-      ],
-      withBothSets: [
-        { isUpgrade: false, version: "1.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: true, version: "3.0"},
-        { isUpgrade: false, version: null},
-        { isUpgrade: true, version: "1.0"}
-      ]
-    }
-  }
-}
-
-add_task(async function setup() {
-  // Initialise the profile
-  startupManager();
-  await promiseShutdownManager();
-});
-
-add_task(async function() {
-  for (let setupName of Object.keys(TEST_CONDITIONS)) {
-    for (let testName of Object.keys(TESTS)) {
-        do_print("Running test " + setupName + " " + testName);
-
-        let setup = TEST_CONDITIONS[setupName];
-        let test = TESTS[testName];
-
-        await execSystemAddonTest(setupName, setup, test, distroDir, root, testserver);
-    }
-  }
-});
-
-// Some custom tests
-// Test that the update check is performed as part of the regular add-on update
-// check
-add_task(async function test_addon_update() {
-  await setupSystemAddonConditions(TEST_CONDITIONS.blank, distroDir);
-
-  await updateAllSystemAddons(await buildSystemAddonUpdates([
-    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
-    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
-  ], root), testserver);
-
-  await verifySystemAddonState(TEST_CONDITIONS.blank.initialState, [
-    {isUpgrade: false, version: null},
-    {isUpgrade: true, version: "2.0"},
-    {isUpgrade: true, version: "2.0"},
-    {isUpgrade: false, version: null},
-    {isUpgrade: false, version: null}
-  ], false, distroDir);
-
-  await promiseShutdownManager();
-});
-
-// Disabling app updates should block system add-on updates
-add_task(async function test_app_update_disabled() {
-  await setupSystemAddonConditions(TEST_CONDITIONS.blank, distroDir);
-
-  Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, false);
-  await updateAllSystemAddons(await buildSystemAddonUpdates([
-    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
-    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
-  ], root), testserver);
-  Services.prefs.clearUserPref(PREF_APP_UPDATE_ENABLED);
-
-  await verifySystemAddonState(TEST_CONDITIONS.blank.initialState, undefined, false, distroDir);
-
-  await promiseShutdownManager();
-});
-
-// Safe mode should block system add-on updates
-add_task(async function test_safe_mode() {
-  gAppInfo.inSafeMode = true;
-
-  await setupSystemAddonConditions(TEST_CONDITIONS.blank, distroDir);
-
-  Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, false);
-  await updateAllSystemAddons(await buildSystemAddonUpdates([
-    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
-    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
-  ], root), testserver);
-  Services.prefs.clearUserPref(PREF_APP_UPDATE_ENABLED);
-
-  await verifySystemAddonState(TEST_CONDITIONS.blank.initialState, undefined, false, distroDir);
-
-  await promiseShutdownManager();
-
-  gAppInfo.inSafeMode = false;
-});
-
-// Tests that a set that matches the default set does nothing
-add_task(async function test_match_default() {
-  await setupSystemAddonConditions(TEST_CONDITIONS.withAppSet, distroDir);
-
-  await installSystemAddons(await buildSystemAddonUpdates([
-    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
-    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
-  ], root), testserver);
-
-  // Shouldn't have installed an updated set
-  await verifySystemAddonState(TEST_CONDITIONS.withAppSet.initialState, undefined, false, distroDir);
-
-  await promiseShutdownManager();
-});
-
-// Tests that a set that matches the hidden default set works
-add_task(async function test_match_default_revert() {
-  await setupSystemAddonConditions(TEST_CONDITIONS.withBothSets, distroDir);
-
-  await installSystemAddons(await buildSystemAddonUpdates([
-    { id: "system1@tests.mozilla.org", version: "1.0", path: "system1_1.xpi" },
-    { id: "system2@tests.mozilla.org", version: "1.0", path: "system2_1.xpi" }
-  ], root), testserver);
-
-  // This should revert to the default set instead of installing new versions
-  // into an updated set.
-  await verifySystemAddonState(TEST_CONDITIONS.withBothSets.initialState, [
-    {isUpgrade: false, version: "1.0"},
-    {isUpgrade: false, version: "1.0"},
-    {isUpgrade: false, version: null},
-    {isUpgrade: false, version: null},
-    {isUpgrade: false, version: null}
-  ], false, distroDir);
-
-  await promiseShutdownManager();
-});
-
-// Tests that a set that matches the current set works
-add_task(async function test_match_current() {
-  await setupSystemAddonConditions(TEST_CONDITIONS.withBothSets, distroDir);
-
-  await installSystemAddons(await buildSystemAddonUpdates([
-    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
-    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
-  ], root), testserver);
-
-  // This should remain with the current set instead of creating a new copy
-  let set = JSON.parse(Services.prefs.getCharPref(PREF_SYSTEM_ADDON_SET));
-  do_check_eq(set.directory, "prefilled");
-
-  await verifySystemAddonState(TEST_CONDITIONS.withBothSets.initialState, undefined, false, distroDir);
-
-  await promiseShutdownManager();
-});
-
-// Tests that a set with a minor change doesn't re-download existing files
-add_task(async function test_no_download() {
-  await setupSystemAddonConditions(TEST_CONDITIONS.withBothSets, distroDir);
-
-  // The missing file here is unneeded since there is a local version already
-  await installSystemAddons(await buildSystemAddonUpdates([
-    { id: "system2@tests.mozilla.org", version: "2.0", path: "missing.xpi" },
-    { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" }
-  ], root), testserver);
-
-  await verifySystemAddonState(TEST_CONDITIONS.withBothSets.initialState, [
-    {isUpgrade: false, version: "1.0"},
-    {isUpgrade: true, version: "2.0"},
-    {isUpgrade: false, version: null},
-    {isUpgrade: true, version: "1.0"},
-    {isUpgrade: false, version: null}
-  ], false, distroDir);
-
-  await promiseShutdownManager();
-});
-
-// Tests that a second update before a restart works
-add_task(async function test_double_update() {
-  await setupSystemAddonConditions(TEST_CONDITIONS.withAppSet, distroDir);
-
-  await installSystemAddons(await buildSystemAddonUpdates([
-    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
-    { id: "system3@tests.mozilla.org", version: "1.0", path: "system3_1.xpi" }
-  ], root), testserver);
-
-  await installSystemAddons(await buildSystemAddonUpdates([
-    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" },
-    { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" }
-  ], root), testserver);
-
-  await verifySystemAddonState(TEST_CONDITIONS.withAppSet.initialState, [
-    {isUpgrade: false, version: null},
-    {isUpgrade: false, version: "2.0"},
-    {isUpgrade: true, version: "2.0"},
-    {isUpgrade: true, version: "1.0"},
-    {isUpgrade: false, version: null}
-  ], true, distroDir);
-
-  await promiseShutdownManager();
-});
-
-// A second update after a restart will delete the original unused set
-add_task(async function test_update_purges() {
-  await setupSystemAddonConditions(TEST_CONDITIONS.withBothSets, distroDir);
-
-  await installSystemAddons(await buildSystemAddonUpdates([
-    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
-    { id: "system3@tests.mozilla.org", version: "1.0", path: "system3_1.xpi" }
-  ], root), testserver);
-
-  await verifySystemAddonState(TEST_CONDITIONS.withBothSets.initialState, [
-    {isUpgrade: false, version: "1.0"},
-    {isUpgrade: true, version: "2.0"},
-    {isUpgrade: true, version: "1.0"},
-    {isUpgrade: false, version: null},
-    {isUpgrade: false, version: null}
-  ], false, distroDir);
-
-  await installSystemAddons(await buildSystemAddonUpdates(null), testserver);
-
-  let dirs = await getSystemAddonDirectories();
-  do_check_eq(dirs.length, 1);
-
-  await promiseShutdownManager();
-});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update_blank.js
@@ -0,0 +1,126 @@
+// Tests that system add-on upgrades work.
+
+Components.utils.import("resource://testing-common/httpd.js");
+
+BootstrapMonitor.init();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2");
+
+var testserver = new HttpServer();
+testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
+testserver.start();
+var root = testserver.identity.primaryScheme + "://" +
+           testserver.identity.primaryHost + ":" +
+           testserver.identity.primaryPort + "/data/"
+Services.prefs.setCharPref(PREF_SYSTEM_ADDON_UPDATE_URL, root + "update.xml");
+
+let distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "empty"], true);
+registerDirectory("XREAppFeat", distroDir);
+initSystemAddonDirs();
+
+/**
+ * Defines the set of initial conditions to run each test against. Each should
+ * define the following properties:
+ *
+ * setup:        A task to setup the profile into the initial state.
+ * initialState: The initial expected system add-on state after setup has run.
+ */
+const TEST_CONDITIONS = {
+  // Runs tests with no updated or default system add-ons initially installed
+  blank: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ],
+  },
+  // Runs tests with default system add-ons installed
+  withAppSet: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "prefilled";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with updated system add-ons installed
+  withProfileSet: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with both default and updated system add-ons installed
+  withBothSets: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "hidden";
+    },
+    initialState: [
+      { isUpgrade: false, version: "1.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+};
+
+/**
+ * The tests to run. Each test must define an updateList or test. The following
+ * properties are used:
+ *
+ * updateList: The set of add-ons the server should respond with.
+ * test:       A function to run to perform the update check (replaces
+ *             updateList)
+ * fails:      An optional property, if true the update check is expected to
+ *             fail.
+ * finalState: An optional property, the expected final state of system add-ons,
+ *             if missing the test condition's initialState is used.
+ */
+const TESTS = {
+  // Test that a blank response does nothing
+  blank: {
+    updateList: null,
+  }
+}
+
+add_task(async function setup() {
+  // Initialise the profile
+  awaitPromise(overrideBuiltIns({ "system": [] }));
+  startupManager();
+  await promiseShutdownManager();
+});
+
+add_task(async function() {
+  for (let setupName of Object.keys(TEST_CONDITIONS)) {
+    for (let testName of Object.keys(TESTS)) {
+        do_print("Running test " + setupName + " " + testName);
+
+        let setup = TEST_CONDITIONS[setupName];
+        let test = TESTS[testName];
+
+        await execSystemAddonTest(setupName, setup, test, distroDir, root, testserver);
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update_checkSizeHash.js
@@ -0,0 +1,160 @@
+// Tests that system add-on upgrades work.
+
+Components.utils.import("resource://testing-common/httpd.js");
+
+BootstrapMonitor.init();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2");
+
+var testserver = new HttpServer();
+testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
+testserver.start();
+var root = testserver.identity.primaryScheme + "://" +
+           testserver.identity.primaryHost + ":" +
+           testserver.identity.primaryPort + "/data/"
+Services.prefs.setCharPref(PREF_SYSTEM_ADDON_UPDATE_URL, root + "update.xml");
+
+let distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "empty"], true);
+registerDirectory("XREAppFeat", distroDir);
+initSystemAddonDirs();
+
+/**
+ * Defines the set of initial conditions to run each test against. Each should
+ * define the following properties:
+ *
+ * setup:        A task to setup the profile into the initial state.
+ * initialState: The initial expected system add-on state after setup has run.
+ */
+const TEST_CONDITIONS = {
+  // Runs tests with no updated or default system add-ons initially installed
+  blank: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ],
+  },
+  // Runs tests with default system add-ons installed
+  withAppSet: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "prefilled";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with updated system add-ons installed
+  withProfileSet: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with both default and updated system add-ons installed
+  withBothSets: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "hidden";
+    },
+    initialState: [
+      { isUpgrade: false, version: "1.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+};
+
+/**
+ * The tests to run. Each test must define an updateList or test. The following
+ * properties are used:
+ *
+ * updateList: The set of add-ons the server should respond with.
+ * test:       A function to run to perform the update check (replaces
+ *             updateList)
+ * fails:      An optional property, if true the update check is expected to
+ *             fail.
+ * finalState: An optional property, the expected final state of system add-ons,
+ *             if missing the test condition's initialState is used.
+ */
+const TESTS = {
+  // Correct sizes and hashes should work
+  checkSizeHash: {
+    updateList: [
+      { id: "system2@tests.mozilla.org", version: "3.0", path: "system2_3.xpi", size: 4697 },
+      { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi", hashFunction: "sha1", hashValue: "a4c7198d56deb315511c02937fd96c696de6cb84" },
+      { id: "system5@tests.mozilla.org", version: "1.0", path: "system5_1.xpi", size: 4691, hashFunction: "sha1", hashValue: "6887b916a1a9a5338b0df4181f6187f5396861eb" }
+    ],
+    finalState: {
+      blank: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"}
+      ],
+      withAppSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"}
+      ],
+      withProfileSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"}
+      ],
+      withBothSets: [
+        { isUpgrade: false, version: "1.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"}
+      ]
+    }
+  }
+}
+
+add_task(async function setup() {
+  // Initialise the profile
+  awaitPromise(overrideBuiltIns({ "system": [] }));
+  startupManager();
+  await promiseShutdownManager();
+});
+
+add_task(async function() {
+  for (let setupName of Object.keys(TEST_CONDITIONS)) {
+    for (let testName of Object.keys(TESTS)) {
+        do_print("Running test " + setupName + " " + testName);
+
+        let setup = TEST_CONDITIONS[setupName];
+        let test = TESTS[testName];
+
+        await execSystemAddonTest(setupName, setup, test, distroDir, root, testserver);
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update_custom.js
@@ -0,0 +1,270 @@
+// Tests that system add-on upgrades work.
+
+Components.utils.import("resource://testing-common/httpd.js");
+
+BootstrapMonitor.init();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2");
+
+var testserver = new HttpServer();
+testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
+testserver.start();
+var root = testserver.identity.primaryScheme + "://" +
+           testserver.identity.primaryHost + ":" +
+           testserver.identity.primaryPort + "/data/"
+Services.prefs.setCharPref(PREF_SYSTEM_ADDON_UPDATE_URL, root + "update.xml");
+
+let distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "empty"], true);
+registerDirectory("XREAppFeat", distroDir);
+initSystemAddonDirs();
+
+/**
+ * Defines the set of initial conditions to run each test against. Each should
+ * define the following properties:
+ *
+ * setup:        A task to setup the profile into the initial state.
+ * initialState: The initial expected system add-on state after setup has run.
+ */
+const TEST_CONDITIONS = {
+  // Runs tests with no updated or default system add-ons initially installed
+  blank: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ],
+  },
+  // Runs tests with default system add-ons installed
+  withAppSet: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "prefilled";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with updated system add-ons installed
+  withProfileSet: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with both default and updated system add-ons installed
+  withBothSets: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "hidden";
+    },
+    initialState: [
+      { isUpgrade: false, version: "1.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+};
+
+// Test that the update check is performed as part of the regular add-on update
+// check
+add_task(async function test_addon_update() {
+  await setupSystemAddonConditions(TEST_CONDITIONS.blank, distroDir);
+
+  await updateAllSystemAddons(await buildSystemAddonUpdates([
+    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
+    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
+  ], root), testserver);
+
+  await verifySystemAddonState(TEST_CONDITIONS.blank.initialState, [
+    {isUpgrade: false, version: null},
+    {isUpgrade: true, version: "2.0"},
+    {isUpgrade: true, version: "2.0"},
+    {isUpgrade: false, version: null},
+    {isUpgrade: false, version: null}
+  ], false, distroDir);
+
+  await promiseShutdownManager();
+});
+
+// Disabling app updates should block system add-on updates
+add_task(async function test_app_update_disabled() {
+  await setupSystemAddonConditions(TEST_CONDITIONS.blank, distroDir);
+
+  Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, false);
+  await updateAllSystemAddons(await buildSystemAddonUpdates([
+    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
+    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
+  ], root), testserver);
+  Services.prefs.clearUserPref(PREF_APP_UPDATE_ENABLED);
+
+  await verifySystemAddonState(TEST_CONDITIONS.blank.initialState, undefined, false, distroDir);
+
+  await promiseShutdownManager();
+});
+
+// Safe mode should block system add-on updates
+add_task(async function test_safe_mode() {
+  gAppInfo.inSafeMode = true;
+
+  await setupSystemAddonConditions(TEST_CONDITIONS.blank, distroDir);
+
+  Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, false);
+  await updateAllSystemAddons(await buildSystemAddonUpdates([
+    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
+    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
+  ], root), testserver);
+  Services.prefs.clearUserPref(PREF_APP_UPDATE_ENABLED);
+
+  await verifySystemAddonState(TEST_CONDITIONS.blank.initialState, undefined, false, distroDir);
+
+  await promiseShutdownManager();
+
+  gAppInfo.inSafeMode = false;
+});
+
+// Tests that a set that matches the default set does nothing
+add_task(async function test_match_default() {
+  await setupSystemAddonConditions(TEST_CONDITIONS.withAppSet, distroDir);
+
+  await installSystemAddons(await buildSystemAddonUpdates([
+    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
+    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
+  ], root), testserver);
+
+  // Shouldn't have installed an updated set
+  await verifySystemAddonState(TEST_CONDITIONS.withAppSet.initialState, undefined, false, distroDir);
+
+  await promiseShutdownManager();
+});
+
+// Tests that a set that matches the hidden default set works
+add_task(async function test_match_default_revert() {
+  await setupSystemAddonConditions(TEST_CONDITIONS.withBothSets, distroDir);
+
+  await installSystemAddons(await buildSystemAddonUpdates([
+    { id: "system1@tests.mozilla.org", version: "1.0", path: "system1_1.xpi" },
+    { id: "system2@tests.mozilla.org", version: "1.0", path: "system2_1.xpi" }
+  ], root), testserver);
+
+  // This should revert to the default set instead of installing new versions
+  // into an updated set.
+  await verifySystemAddonState(TEST_CONDITIONS.withBothSets.initialState, [
+    {isUpgrade: false, version: "1.0"},
+    {isUpgrade: false, version: "1.0"},
+    {isUpgrade: false, version: null},
+    {isUpgrade: false, version: null},
+    {isUpgrade: false, version: null}
+  ], false, distroDir);
+
+  await promiseShutdownManager();
+});
+
+// Tests that a set that matches the current set works
+add_task(async function test_match_current() {
+  await setupSystemAddonConditions(TEST_CONDITIONS.withBothSets, distroDir);
+
+  await installSystemAddons(await buildSystemAddonUpdates([
+    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
+    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
+  ], root), testserver);
+
+  // This should remain with the current set instead of creating a new copy
+  let set = JSON.parse(Services.prefs.getCharPref(PREF_SYSTEM_ADDON_SET));
+  do_check_eq(set.directory, "prefilled");
+
+  await verifySystemAddonState(TEST_CONDITIONS.withBothSets.initialState, undefined, false, distroDir);
+
+  await promiseShutdownManager();
+});
+
+// Tests that a set with a minor change doesn't re-download existing files
+add_task(async function test_no_download() {
+  await setupSystemAddonConditions(TEST_CONDITIONS.withBothSets, distroDir);
+
+  // The missing file here is unneeded since there is a local version already
+  await installSystemAddons(await buildSystemAddonUpdates([
+    { id: "system2@tests.mozilla.org", version: "2.0", path: "missing.xpi" },
+    { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" }
+  ], root), testserver);
+
+  await verifySystemAddonState(TEST_CONDITIONS.withBothSets.initialState, [
+    {isUpgrade: false, version: "1.0"},
+    {isUpgrade: true, version: "2.0"},
+    {isUpgrade: false, version: null},
+    {isUpgrade: true, version: "1.0"},
+    {isUpgrade: false, version: null}
+  ], false, distroDir);
+
+  await promiseShutdownManager();
+});
+
+// Tests that a second update before a restart works
+add_task(async function test_double_update() {
+  await setupSystemAddonConditions(TEST_CONDITIONS.withAppSet, distroDir);
+
+  await installSystemAddons(await buildSystemAddonUpdates([
+    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
+    { id: "system3@tests.mozilla.org", version: "1.0", path: "system3_1.xpi" }
+  ], root), testserver);
+
+  await installSystemAddons(await buildSystemAddonUpdates([
+    { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" },
+    { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" }
+  ], root), testserver);
+
+  await verifySystemAddonState(TEST_CONDITIONS.withAppSet.initialState, [
+    {isUpgrade: false, version: null},
+    {isUpgrade: false, version: "2.0"},
+    {isUpgrade: true, version: "2.0"},
+    {isUpgrade: true, version: "1.0"},
+    {isUpgrade: false, version: null}
+  ], true, distroDir);
+
+  await promiseShutdownManager();
+});
+
+// A second update after a restart will delete the original unused set
+add_task(async function test_update_purges() {
+  await setupSystemAddonConditions(TEST_CONDITIONS.withBothSets, distroDir);
+
+  await installSystemAddons(await buildSystemAddonUpdates([
+    { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
+    { id: "system3@tests.mozilla.org", version: "1.0", path: "system3_1.xpi" }
+  ], root), testserver);
+
+  await verifySystemAddonState(TEST_CONDITIONS.withBothSets.initialState, [
+    {isUpgrade: false, version: "1.0"},
+    {isUpgrade: true, version: "2.0"},
+    {isUpgrade: true, version: "1.0"},
+    {isUpgrade: false, version: null},
+    {isUpgrade: false, version: null}
+  ], false, distroDir);
+
+  await installSystemAddons(await buildSystemAddonUpdates(null), testserver);
+
+  let dirs = await getSystemAddonDirectories();
+  do_check_eq(dirs.length, 1);
+
+  await promiseShutdownManager();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update_empty.js
@@ -0,0 +1,150 @@
+// Tests that system add-on upgrades work.
+
+Components.utils.import("resource://testing-common/httpd.js");
+
+BootstrapMonitor.init();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2");
+
+var testserver = new HttpServer();
+testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
+testserver.start();
+var root = testserver.identity.primaryScheme + "://" +
+           testserver.identity.primaryHost + ":" +
+           testserver.identity.primaryPort + "/data/"
+Services.prefs.setCharPref(PREF_SYSTEM_ADDON_UPDATE_URL, root + "update.xml");
+
+let distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "empty"], true);
+registerDirectory("XREAppFeat", distroDir);
+initSystemAddonDirs();
+
+/**
+ * Defines the set of initial conditions to run each test against. Each should
+ * define the following properties:
+ *
+ * setup:        A task to setup the profile into the initial state.
+ * initialState: The initial expected system add-on state after setup has run.
+ */
+const TEST_CONDITIONS = {
+  // Runs tests with no updated or default system add-ons initially installed
+  blank: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ],
+  },
+  // Runs tests with default system add-ons installed
+  withAppSet: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "prefilled";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with updated system add-ons installed
+  withProfileSet: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with both default and updated system add-ons installed
+  withBothSets: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "hidden";
+    },
+    initialState: [
+      { isUpgrade: false, version: "1.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+};
+
+/**
+ * The tests to run. Each test must define an updateList or test. The following
+ * properties are used:
+ *
+ * updateList: The set of add-ons the server should respond with.
+ * test:       A function to run to perform the update check (replaces
+ *             updateList)
+ * fails:      An optional property, if true the update check is expected to
+ *             fail.
+ * finalState: An optional property, the expected final state of system add-ons,
+ *             if missing the test condition's initialState is used.
+ */
+const TESTS = {
+  // Test that an empty list removes existing updates, leaving defaults.
+  empty: {
+    updateList: [],
+    finalState: {
+      blank: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ],
+      withAppSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: "2.0"},
+        { isUpgrade: false, version: "2.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ],
+      withProfileSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ],
+      withBothSets: [
+        { isUpgrade: false, version: "1.0"},
+        { isUpgrade: false, version: "1.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        // Set this to `true` to so `verifySystemAddonState()` expects a blank profile dir
+        { isUpgrade: true, version: null}
+      ]
+    }
+  }
+}
+
+add_task(async function() {
+  for (let setupName of Object.keys(TEST_CONDITIONS)) {
+    for (let testName of Object.keys(TESTS)) {
+        do_print("Running test " + setupName + " " + testName);
+
+        let setup = TEST_CONDITIONS[setupName];
+        let test = TESTS[testName];
+
+        await execSystemAddonTest(setupName, setup, test, distroDir, root, testserver);
+    }
+  }
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_update_fail.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update_fail.js
@@ -81,17 +81,16 @@ const TEST_CONDITIONS = {
       { isUpgrade: true, version: "2.0"},
       { isUpgrade: true, version: "2.0"},
       { isUpgrade: false, version: null},
       { isUpgrade: false, version: null}
     ]
   },
 };
 
-
 /**
  * The tests to run. Each test must define an updateList or test. The following
  * properties are used:
  *
  * updateList: The set of add-ons the server should respond with.
  * test:       A function to run to perform the update check (replaces
  *             updateList)
  * fails:      An optional property, if true the update check is expected to
@@ -161,16 +160,17 @@ const TESTS = {
       { id: "system6@tests.mozilla.org", version: "1.0", path: "system6_3_notMultiprocess.xpi" },
       { id: "system3@tests.mozilla.org", version: "1.0", path: "system3_1.xpi" }
     ],
   }
 }
 
 add_task(async function setup() {
   // Initialise the profile
+  await overrideBuiltIns({ "system": [] });
   startupManager();
   await promiseShutdownManager();
 });
 
 add_task(async function() {
   for (let setupName of Object.keys(TEST_CONDITIONS)) {
     for (let testName of Object.keys(TESTS)) {
         do_print("Running test " + setupName + " " + testName);
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update_newset.js
@@ -0,0 +1,159 @@
+// Tests that system add-on upgrades work.
+
+Components.utils.import("resource://testing-common/httpd.js");
+
+BootstrapMonitor.init();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2");
+
+var testserver = new HttpServer();
+testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
+testserver.start();
+var root = testserver.identity.primaryScheme + "://" +
+           testserver.identity.primaryHost + ":" +
+           testserver.identity.primaryPort + "/data/"
+Services.prefs.setCharPref(PREF_SYSTEM_ADDON_UPDATE_URL, root + "update.xml");
+
+let distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "empty"], true);
+registerDirectory("XREAppFeat", distroDir);
+initSystemAddonDirs();
+
+/**
+ * Defines the set of initial conditions to run each test against. Each should
+ * define the following properties:
+ *
+ * setup:        A task to setup the profile into the initial state.
+ * initialState: The initial expected system add-on state after setup has run.
+ */
+const TEST_CONDITIONS = {
+  // Runs tests with no updated or default system add-ons initially installed
+  blank: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ],
+  },
+  // Runs tests with default system add-ons installed
+  withAppSet: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "prefilled";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with updated system add-ons installed
+  withProfileSet: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with both default and updated system add-ons installed
+  withBothSets: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "hidden";
+    },
+    initialState: [
+      { isUpgrade: false, version: "1.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+};
+
+/**
+ * The tests to run. Each test must define an updateList or test. The following
+ * properties are used:
+ *
+ * updateList: The set of add-ons the server should respond with.
+ * test:       A function to run to perform the update check (replaces
+ *             updateList)
+ * fails:      An optional property, if true the update check is expected to
+ *             fail.
+ * finalState: An optional property, the expected final state of system add-ons,
+ *             if missing the test condition's initialState is used.
+ */
+const TESTS = {
+  // Tests that a new set of system add-ons gets installed
+  newset: {
+    updateList: [
+      { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" },
+      { id: "system5@tests.mozilla.org", version: "1.0", path: "system5_1.xpi" }
+    ],
+    finalState: {
+      blank: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: true, version: "1.0"}
+      ],
+      withAppSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: "2.0"},
+        { isUpgrade: false, version: "2.0"},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: true, version: "1.0"}
+      ],
+      withProfileSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: true, version: "1.0"}
+      ],
+      withBothSets: [
+        { isUpgrade: false, version: "1.0"},
+        { isUpgrade: false, version: "1.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: true, version: "1.0"}
+      ]
+    }
+  }
+}
+
+add_task(async function setup() {
+  // Initialise the profile
+  awaitPromise(overrideBuiltIns({ "system": [] }));
+  startupManager();
+  await promiseShutdownManager();
+});
+
+add_task(async function() {
+  for (let setupName of Object.keys(TEST_CONDITIONS)) {
+    for (let testName of Object.keys(TESTS)) {
+        do_print("Running test " + setupName + " " + testName);
+
+        let setup = TEST_CONDITIONS[setupName];
+        let test = TESTS[testName];
+
+        await execSystemAddonTest(setupName, setup, test, distroDir, root, testserver);
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update_overlapping.js
@@ -0,0 +1,161 @@
+// Tests that system add-on upgrades work.
+
+Components.utils.import("resource://testing-common/httpd.js");
+
+BootstrapMonitor.init();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2");
+
+var testserver = new HttpServer();
+testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
+testserver.start();
+var root = testserver.identity.primaryScheme + "://" +
+           testserver.identity.primaryHost + ":" +
+           testserver.identity.primaryPort + "/data/"
+Services.prefs.setCharPref(PREF_SYSTEM_ADDON_UPDATE_URL, root + "update.xml");
+
+let distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "empty"], true);
+registerDirectory("XREAppFeat", distroDir);
+initSystemAddonDirs();
+
+/**
+ * Defines the set of initial conditions to run each test against. Each should
+ * define the following properties:
+ *
+ * setup:        A task to setup the profile into the initial state.
+ * initialState: The initial expected system add-on state after setup has run.
+ */
+const TEST_CONDITIONS = {
+  // Runs tests with no updated or default system add-ons initially installed
+  blank: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ],
+  },
+  // Runs tests with default system add-ons installed
+  withAppSet: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "prefilled";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with updated system add-ons installed
+  withProfileSet: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with both default and updated system add-ons installed
+  withBothSets: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "hidden";
+    },
+    initialState: [
+      { isUpgrade: false, version: "1.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+};
+
+/**
+ * The tests to run. Each test must define an updateList or test. The following
+ * properties are used:
+ *
+ * updateList: The set of add-ons the server should respond with.
+ * test:       A function to run to perform the update check (replaces
+ *             updateList)
+ * fails:      An optional property, if true the update check is expected to
+ *             fail.
+ * finalState: An optional property, the expected final state of system add-ons,
+ *             if missing the test condition's initialState is used.
+ */
+const TESTS = {
+  // Tests that a set of system add-ons, some new, some existing gets installed
+  overlapping: {
+    updateList: [
+      { id: "system1@tests.mozilla.org", version: "2.0", path: "system1_2.xpi" },
+      { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
+      { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi" },
+      { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" }
+    ],
+    finalState: {
+      blank: [
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: false, version: null}
+      ],
+      withAppSet: [
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: false, version: null}
+      ],
+      withProfileSet: [
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: false, version: null}
+      ],
+      withBothSets: [
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: false, version: null}
+      ]
+    }
+  }
+}
+
+add_task(async function setup() {
+  // Initialise the profile
+  awaitPromise(overrideBuiltIns({ "system": [] }));
+  startupManager();
+  await promiseShutdownManager();
+});
+
+add_task(async function() {
+  for (let setupName of Object.keys(TEST_CONDITIONS)) {
+    for (let testName of Object.keys(TESTS)) {
+        do_print("Running test " + setupName + " " + testName);
+
+        let setup = TEST_CONDITIONS[setupName];
+        let test = TESTS[testName];
+
+        await execSystemAddonTest(setupName, setup, test, distroDir, root, testserver);
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update_upgrades.js
@@ -0,0 +1,159 @@
+// Tests that system add-on upgrades work.
+
+Components.utils.import("resource://testing-common/httpd.js");
+
+BootstrapMonitor.init();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2");
+
+var testserver = new HttpServer();
+testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
+testserver.start();
+var root = testserver.identity.primaryScheme + "://" +
+           testserver.identity.primaryHost + ":" +
+           testserver.identity.primaryPort + "/data/"
+Services.prefs.setCharPref(PREF_SYSTEM_ADDON_UPDATE_URL, root + "update.xml");
+
+let distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "empty"], true);
+registerDirectory("XREAppFeat", distroDir);
+initSystemAddonDirs();
+
+/**
+ * Defines the set of initial conditions to run each test against. Each should
+ * define the following properties:
+ *
+ * setup:        A task to setup the profile into the initial state.
+ * initialState: The initial expected system add-on state after setup has run.
+ */
+const TEST_CONDITIONS = {
+  // Runs tests with no updated or default system add-ons initially installed
+  blank: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ],
+  },
+  // Runs tests with default system add-ons installed
+  withAppSet: {
+    setup() {
+      clearSystemAddonUpdatesDir();
+      distroDir.leafName = "prefilled";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with updated system add-ons installed
+  withProfileSet: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "empty";
+    },
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+
+  // Runs tests with both default and updated system add-ons installed
+  withBothSets: {
+    setup() {
+      buildPrefilledUpdatesDir();
+      distroDir.leafName = "hidden";
+    },
+    initialState: [
+      { isUpgrade: false, version: "1.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
+  },
+};
+
+/**
+ * The tests to run. Each test must define an updateList or test. The following
+ * properties are used:
+ *
+ * updateList: The set of add-ons the server should respond with.
+ * test:       A function to run to perform the update check (replaces
+ *             updateList)
+ * fails:      An optional property, if true the update check is expected to
+ *             fail.
+ * finalState: An optional property, the expected final state of system add-ons,
+ *             if missing the test condition's initialState is used.
+ */
+const TESTS = {
+  // Tests that an upgraded set of system add-ons gets installed
+  upgrades: {
+    updateList: [
+      { id: "system2@tests.mozilla.org", version: "3.0", path: "system2_3.xpi" },
+      { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi" }
+    ],
+    finalState: {
+      blank: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ],
+      withAppSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ],
+      withProfileSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ],
+      withBothSets: [
+        { isUpgrade: false, version: "1.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ]
+    }
+  }
+}
+
+add_task(async function setup() {
+  // Initialise the profile
+  awaitPromise(overrideBuiltIns({ "system": [] }));
+  startupManager();
+  await promiseShutdownManager();
+});
+
+add_task(async function() {
+  for (let setupName of Object.keys(TEST_CONDITIONS)) {
+    for (let testName of Object.keys(TESTS)) {
+        do_print("Running test " + setupName + " " + testName);
+
+        let setup = TEST_CONDITIONS[setupName];
+        let test = TESTS[testName];
+
+        await execSystemAddonTest(setupName, setup, test, distroDir, root, testserver);
+    }
+  }
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js
@@ -1354,33 +1354,39 @@ function check_test_7_cache() {
     do_check_true(p1.installDate.getTime() < p1.updateDate.getTime());
 
     run_next_test();
   });
 }
 
 // Test that the update check returns nothing for addons in locked install
 // locations.
-add_test(function run_test_locked_install() {
+add_test(async function run_test_locked_install() {
   const lockedDir = gProfD.clone();
   lockedDir.append("locked_extensions");
   registerDirectory("XREAppFeat", lockedDir);
-  restartManager();
-  writeInstallRDFForExtension({
+
+  await promiseShutdownManager();
+
+  writeInstallRDFToXPI({
     id: "addon13@tests.mozilla.org",
     version: "1.0",
     updateURL: "http://localhost:" + gPort + "/data/test_update.rdf",
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: "0.1",
       maxVersion: "0.2"
     }],
     name: "Test Addon 13",
   }, lockedDir);
-  restartManager();
+
+  let validAddons = { "system": ["addon13@tests.mozilla.org"] };
+  await overrideBuiltIns(validAddons);
+
+  await promiseStartupManager();
 
   AddonManager.getAddonByID("addon13@tests.mozilla.org", function(a13) {
     do_check_neq(a13, null);
 
     a13.findUpdates({
       onCompatibilityUpdateAvailable() {
         ok(false, "Should have not have seen compatibility information");
       },
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -25,18 +25,25 @@ skip-if = appname != "firefox"
 [test_pluginInfoURL.js]
 tags = blocklist
 [test_provider_markSafe.js]
 [test_provider_shutdown.js]
 [test_provider_unsafe_access_shutdown.js]
 [test_provider_unsafe_access_startup.js]
 [test_ProductAddonChecker.js]
 [test_shutdown.js]
-[test_system_update.js]
+[test_system_update_blank.js]
+[test_system_update_checkSizeHash.js]
+[test_system_update_custom.js]
+[test_system_update_empty.js]
+skip-if = true # Failing intermittently due to a race condition in the test, see bug 1348981
 [test_system_update_fail.js]
+[test_system_update_newset.js]
+[test_system_update_overlapping.js]
+[test_system_update_upgrades.js]
 [test_system_repository.js]
 [test_system_reset.js]
 [test_XPIcancel.js]
 [test_XPIStates.js]
 [test_temporary.js]
 tags = webextensions
 [test_install_from_sources.js]
 [test_proxies.js]
@@ -46,10 +53,11 @@ tags = webextensions
 [test_nodisable_hidden.js]
 [test_delay_update_webextension.js]
 skip-if = appname == "thunderbird"
 tags = webextensions
 [test_dependencies.js]
 [test_system_delay_update.js]
 [test_schema_change.js]
 [test_registerchrome.js]
+[test_system_allowed.js]
 
 [include:xpcshell-shared.ini]