Bug 1385057: Remove most code for handling unpacked side-loaded extensions. r?aswan,keeler draft
authorKris Maglione <maglione.k@gmail.com>
Wed, 09 May 2018 16:04:04 -0700
changeset 793763 8d5675fb257b5c75eee7490b998a39e8cef10648
parent 793387 963a9908ed338e5a4d867706d103533ba538851b
child 793821 01dddf86ed753de89f216dd7f0eebae214435ed7
child 793871 c1b5597b9c3e2bbc8c7748323cff4c1432fbba26
child 793872 dfeac510bfac0c7b6abfbe03ae2be1d94ded68bd
child 793900 5e71e42f35ee7feba1e65aa95fddbacb46555f73
child 794387 31110012986a530ddfaccc565bee834cefb80b7f
push id109490
push usermaglione.k@gmail.com
push dateThu, 10 May 2018 17:45:47 +0000
reviewersaswan, keeler
bugs1385057
milestone62.0a1
Bug 1385057: Remove most code for handling unpacked side-loaded extensions. r?aswan,keeler MozReview-Commit-ID: H4cSRBws4Ml
security/apps/AppSignatureVerification.cpp
security/manager/ssl/nsIX509CertDB.idl
security/manager/ssl/tests/unit/test_signed_dir.js
security/manager/ssl/tests/unit/test_signed_dir/lightbeam_for_firefox-1.3.1-fx.xpi
security/manager/ssl/tests/unit/xpcshell.ini
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
toolkit/mozapps/extensions/internal/XPIInstall.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/addons/test_cache_certdb/bootstrap.js
toolkit/mozapps/extensions/test/xpcshell/data/test_delay_update_complete/bootstrap.js
toolkit/mozapps/extensions/test/xpcshell/data/test_delay_update_defer/bootstrap.js
toolkit/mozapps/extensions/test/xpcshell/data/test_delay_update_ignore/bootstrap.js
toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/bootstrap.js
toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/install.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/subdir/dummy.txt
toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/subdir/subdir2/dummy2.txt
toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js
toolkit/mozapps/extensions/test/xpcshell/test_delay_update.js
toolkit/mozapps/extensions/test/xpcshell/test_distribution.js
toolkit/mozapps/extensions/test/xpcshell/test_install_from_sources.js
toolkit/mozapps/extensions/test/xpcshell/test_proxy.js
toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini
toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
--- a/security/apps/AppSignatureVerification.cpp
+++ b/security/apps/AppSignatureVerification.cpp
@@ -1448,17 +1448,8 @@ nsNSSCertificateDB::OpenSignedAppFileAsy
                         static_cast<int32_t>(sDefaultSignaturePolicy));
   SignaturePolicy policy(policyInt);
   RefPtr<OpenSignedAppFileTask> task(new OpenSignedAppFileTask(aTrustedRoot,
                                                                aJarFile,
                                                                policy,
                                                                aCallback));
   return task->Dispatch("SignedJAR");
 }
-
-NS_IMETHODIMP
-nsNSSCertificateDB::VerifySignedDirectoryAsync(AppTrustedRoot, nsIFile*,
-  nsIVerifySignedDirectoryCallback* aCallback)
-{
-  NS_ENSURE_ARG_POINTER(aCallback);
-  return aCallback->VerifySignedDirectoryFinished(
-    NS_ERROR_SIGNED_JAR_NOT_SIGNED, nullptr);
-}
--- a/security/manager/ssl/nsIX509CertDB.idl
+++ b/security/manager/ssl/nsIX509CertDB.idl
@@ -23,25 +23,16 @@ typedef uint32_t AppTrustedRoot;
 [scriptable, function, uuid(fc2b60e5-9a07-47c2-a2cd-b83b68a660ac)]
 interface nsIOpenSignedAppFileCallback : nsISupports
 {
   void openSignedAppFileFinished(in nsresult rv,
                                  in nsIZipReader aZipReader,
                                  in nsIX509Cert aSignerCert);
 };
 
-// Only relevant while we transition away from legacy add-ons. rv will always be
-// NS_ERROR_SIGNED_JAR_NOT_SIGNED. aSignerCert will always be null.
-[scriptable, function, uuid(d5f97827-622a-488f-be08-d850432ac8ec)]
-interface nsIVerifySignedDirectoryCallback : nsISupports
-{
-  void verifySignedDirectoryFinished(in nsresult rv,
-                                     in nsIX509Cert aSignerCert);
-};
-
 /**
  * Callback type for use with asyncVerifyCertAtTime.
  * If aPRErrorCode is PRErrorCodeSuccess (i.e. 0), aVerifiedChain represents the
  * verified certificate chain determined by asyncVerifyCertAtTime. aHasEVPolicy
  * represents whether or not the end-entity certificate verified as EV.
  * If aPRErrorCode is non-zero, it represents the error encountered during
  * verification. aVerifiedChain is null in that case and aHasEVPolicy has no
  * meaning.
@@ -253,26 +244,16 @@ interface nsIX509CertDB : nsISupports {
    * "network.http.packaged-apps-developer-trusted-root".
    */
   const AppTrustedRoot DeveloperImportedRoot = 10;
   [must_use]
   void openSignedAppFileAsync(in AppTrustedRoot trustedRoot,
                               in nsIFile aJarFile,
                               in nsIOpenSignedAppFileCallback callback);
 
-  /**
-   * Vestigial implementation of verifying signed unpacked add-ons. trustedRoot
-   * and aUnpackedDir are ignored. The callback is always called with
-   * NS_ERROR_SIGNED_JAR_NOT_SIGNED and a null signer cert.
-   */
-  [must_use]
-  void verifySignedDirectoryAsync(in AppTrustedRoot trustedRoot,
-                                  in nsIFile aUnpackedDir,
-                                  in nsIVerifySignedDirectoryCallback callback);
-
   /*
    * Add a cert to a cert DB from a binary string.
    *
    * @param certDER The raw DER encoding of a certificate.
    * @param trust String describing the trust settings to assign the
    *              certificate. Decoded by CERT_DecodeTrustString. Consists of 3
    *              comma separated sets of characters, indicating SSL, Email, and
    *              Object signing trust. The object signing trust flags are
deleted file mode 100644
--- a/security/manager/ssl/tests/unit/test_signed_dir.js
+++ /dev/null
@@ -1,68 +0,0 @@
-// Any copyright is dedicated to the Public Domain.
-// http://creativecommons.org/publicdomain/zero/1.0/
-"use strict";
-
-// Tests that signed extensions extracted/unpacked into a directory do not pass
-// signature verification, because that's no longer supported.
-
-const { ZipUtils } = ChromeUtils.import("resource://gre/modules/ZipUtils.jsm", {});
-
-do_get_profile(); // must be called before getting nsIX509CertDB
-const certdb = Cc["@mozilla.org/security/x509certdb;1"]
-                 .getService(Ci.nsIX509CertDB);
-
-/**
- * Signed test extension. This is any arbitrary Mozilla signed XPI that
- * preferably has recently been signed (but note that it actually doesn't
- * matter, since we ignore expired certificates when checking signing).
- * @type nsIFile
- */
-var gSignedXPI =
-  do_get_file("test_signed_dir/lightbeam_for_firefox-1.3.1-fx.xpi", false);
-/**
- * The directory that the test extension will be extracted to.
- * @type nsIFile
- */
-var gTarget = FileUtils.getDir("TmpD", ["test_signed_dir"]);
-gTarget.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
-
-/**
- * Extracts the signed XPI into a directory, and tampers the files in that
- * directory if instructed.
- *
- * @returns {nsIFile}
- *          The directory where the XPI was extracted to.
- */
-function prepare() {
-  ZipUtils.extractFiles(gSignedXPI, gTarget);
-  return gTarget;
-}
-
-function checkResult(expectedRv, dir, resolve) {
-  return function verifySignedDirCallback(rv, aSignerCert) {
-    equal(rv, expectedRv, "Actual and expected return value should match");
-    equal(aSignerCert != null, Components.isSuccessCode(expectedRv),
-          "expecting certificate:");
-    dir.remove(true);
-    resolve();
-  };
-}
-
-function verifyDirAsync(expectedRv) {
-  let targetDir = prepare();
-  return new Promise((resolve, reject) => {
-    certdb.verifySignedDirectoryAsync(
-      Ci.nsIX509CertDB.AddonsPublicRoot, targetDir,
-      checkResult(expectedRv, targetDir, resolve));
-  });
-}
-
-add_task(async function testAPIFails() {
-  await verifyDirAsync(Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED);
-});
-
-registerCleanupFunction(function() {
-  if (gTarget.exists()) {
-    gTarget.remove(true);
-  }
-});
deleted file mode 100644
index 902b731f36731e91e40265a429d212c00e021079..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -26,17 +26,16 @@ support-files =
   test_missing_intermediate/**
   test_name_constraints/**
   test_ocsp_url/**
   test_onecrl/**
   test_pinning_dynamic/**
   test_sdr_preexisting/**
   test_sdr_preexisting_with_password/**
   test_signed_apps/**
-  test_signed_dir/**
   test_startcom_wosign/**
   test_symantec_apple_google/**
   test_validity/**
   tlsserver/**
 
 [test_add_preexisting_cert.js]
 [test_baseline_requirements_subject_common_name.js]
 [test_broken_fips.js]
@@ -145,18 +144,16 @@ requesttimeoutfactor = 2
 [test_sdr.js]
 [test_sdr_preexisting.js]
 [test_sdr_preexisting_with_password.js]
 # Not relevant to Android. See the comment in the test.
 skip-if = toolkit == 'android'
 [test_session_resumption.js]
 run-sequentially = hardcoded ports
 [test_signed_apps.js]
-[test_signed_dir.js]
-tags = addons psm
 [test_ssl_status.js]
 [test_sss_enumerate.js]
 [test_sss_eviction.js]
 [test_sss_originAttributes.js]
 [test_sss_readstate.js]
 [test_sss_readstate_child.js]
 support-files = sss_readstate_child_worker.js
 # bug 1124289 - run_test_in_child violates the sandbox on android
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -2051,29 +2051,16 @@ var AddonManagerInternal = {
     if (!(aFile instanceof Ci.nsIFile))
       throw Components.Exception("aFile must be a nsIFile",
                                  Cr.NS_ERROR_INVALID_ARG);
 
     return AddonManagerInternal._getProviderByName("XPIProvider")
                                .installTemporaryAddon(aFile);
   },
 
-  installAddonFromSources(aFile) {
-    if (!gStarted)
-      throw Components.Exception("AddonManager is not initialized",
-                                 Cr.NS_ERROR_NOT_INITIALIZED);
-
-    if (!(aFile instanceof Ci.nsIFile))
-      throw Components.Exception("aFile must be a nsIFile",
-                                 Cr.NS_ERROR_INVALID_ARG);
-
-    return AddonManagerInternal._getProviderByName("XPIProvider")
-                               .installAddonFromSources(aFile);
-  },
-
   /**
    * Returns an Addon corresponding to an instance ID.
    * @param aInstanceID
    *        An Addon Instance ID symbol
    * @return {Promise}
    * @resolves The found Addon or null if no such add-on exists.
    * @rejects  Never
    * @throws if the aInstanceID argument is not specified
@@ -3436,20 +3423,16 @@ var AddonManager = {
   installAddonFromAOM(aBrowser, aUri, aInstall) {
     AddonManagerInternal.installAddonFromAOM(aBrowser, aUri, aInstall);
   },
 
   installTemporaryAddon(aDirectory) {
     return AddonManagerInternal.installTemporaryAddon(aDirectory);
   },
 
-  installAddonFromSources(aDirectory) {
-    return AddonManagerInternal.installAddonFromSources(aDirectory);
-  },
-
   getAddonByInstanceID(aInstanceID) {
     return AddonManagerInternal.getAddonByInstanceID(aInstanceID);
   },
 
   addManagerListener(aListener) {
     AddonManagerInternal.addManagerListener(aListener);
   },
 
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -670,26 +670,16 @@ var AddonTestUtils = {
         this._genuine.openSignedAppFileAsync(root, file, (result, zipReader, cert) => {
           verifyCert(file.clone(), result, cert, callback)
             .then(([callback, result, cert]) => {
               callback.openSignedAppFileFinished(result, zipReader, cert);
             });
         });
       },
 
-      verifySignedDirectoryAsync(root, dir, callback) {
-        // First try calling the real cert DB
-        this._genuine.verifySignedDirectoryAsync(root, dir, (result, cert) => {
-          verifyCert(dir.clone(), result, cert, callback)
-            .then(([callback, result, cert]) => {
-              callback.verifySignedDirectoryFinished(result, cert);
-            });
-        });
-      },
-
       QueryInterface: ChromeUtils.generateQI([Ci.nsIX509CertDB]),
     };
 
     // Unregister the real database. This only works because the add-ons manager
     // hasn't started up and grabbed the certificate database yet.
     MockRegistrar.register(CERTDB_CONTRACTID, FakeCertDB);
 
     // Initialize the mock service.
--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm
@@ -317,32 +317,18 @@ DirPackage = class DirPackage extends Pa
       }
     });
   }
 
   readBinary(...path) {
     return OS.File.read(OS.Path.join(this.filePath, ...path));
   }
 
-  verifySignedStateForRoot(addon, root) {
-    return new Promise(resolve => {
-      let callback = {
-        verifySignedDirectoryFinished(aRv, aCert) {
-          resolve({
-            signedState: getSignedStatus(aRv, aCert, addon.id),
-            cert: aCert,
-          });
-        }
-      };
-      // This allows the certificate DB to get the raw JS callback object so the
-      // test code can pass through objects that XPConnect would reject.
-      callback.wrappedJSObject = callback;
-
-      gCertDB.verifySignedDirectoryAsync(root, this.file, callback);
-    });
+  async verifySignedStateForRoot(addon, root) {
+    return {signedState: AddonManager.SIGNEDSTATE_UNKNOWN, cert: null};
   }
 };
 
 XPIPackage = class XPIPackage extends Package {
   constructor(file) {
     super(file, getJarURI(file));
 
     this.zipReader = new ZipReader(file);
@@ -3027,19 +3013,17 @@ class MutableDirectoryInstallLocation ex
             transaction.moveTo(oldDataDir, newDataDir);
           }
         }
       }
 
       if (action == "copy") {
         transaction.copy(source, this._directory);
       } else if (action == "move") {
-        if (source.isFile())
-          flushJarCache(source);
-
+        flushJarCache(source);
         transaction.moveUnder(source, this._directory);
       }
       // Do nothing for the proxy file as we sideload an addon permanently
     } finally {
       // It isn't ideal if this cleanup fails but it isn't worth rolling back
       // the install because of it.
       try {
         recursiveRemove(trashDir);
@@ -3461,19 +3445,17 @@ class SystemAddonInstallLocation extends
    */
   installAddon({id, source}) {
     let trashDir = this.getTrashDir();
     let transaction = new SafeInstallOperation();
 
     // If any of these operations fails the finally block will clean up the
     // temporary directory
     try {
-      if (source.isFile()) {
-        flushJarCache(source);
-      }
+      flushJarCache(source);
 
       transaction.moveUnder(source, this._directory);
     } finally {
       // It isn't ideal if this cleanup fails but it isn't worth rolling back
       // the install because of it.
       try {
         recursiveRemove(trashDir);
       } catch (e) {
@@ -3891,61 +3873,30 @@ var XPIInstall = {
   /**
    * Temporarily installs add-on from a local XPI file or directory.
    * As this is intended for development, the signature is not checked and
    * the add-on does not persist on application restart.
    *
    * @param {nsIFile} aFile
    *        An nsIFile for the unpacked add-on directory or XPI file.
    *
-   * @returns {Addon}
-   *        See installAddonFromLocation return value.
-   */
-  installTemporaryAddon(aFile) {
-    return this.installAddonFromLocation(aFile, XPIInternal.TemporaryInstallLocation);
-  },
-
-  /**
-   * Permanently installs add-on from a local XPI file or directory.
-   * The signature is checked but the add-on persist on application restart.
-   *
-   * @param {nsIFile} aFile
-   *        An nsIFile for the unpacked add-on directory or XPI file.
-   *
-   * @returns {Addon}
-   *        See installAddonFromLocation return value.
-   */
-  async installAddonFromSources(aFile) {
-    let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
-    return this.installAddonFromLocation(aFile, location, "proxy");
-  },
-
-  /**
-   * Installs add-on from a local XPI file or directory.
-   *
-   * @param {nsIFile} aFile
-   *        An nsIFile for the unpacked add-on directory or XPI file.
-   * @param {InstallLocation} aInstallLocation
-   *        Define a custom install location object to use for the install.
-   * @param {string?} [aInstallAction]
-   *        Optional action mode to use when installing the addon
-   *        (see MutableDirectoryInstallLocation.installAddon)
-   *
    * @returns {Promise<Addon>}
    *        A Promise that resolves to an Addon object on success, or rejects
    *        if the add-on is not a valid restartless add-on or if the
    *        same ID is already installed.
    */
-  async installAddonFromLocation(aFile, aInstallLocation, aInstallAction) {
+  async installTemporaryAddon(aFile) {
+    let installLocation = XPIInternal.TemporaryInstallLocation;
+
     if (aFile.exists() && aFile.isFile()) {
       flushJarCache(aFile);
     }
-    let addon = await loadManifestFromFile(aFile, aInstallLocation);
-
-    aInstallLocation.installAddon({ id: addon.id, source: aFile, action: aInstallAction });
+    let addon = await loadManifestFromFile(aFile, installLocation);
+
+    installLocation.installAddon({ id: addon.id, source: aFile });
 
     if (addon.appDisabled) {
       let message = `Add-on ${addon.id} is not compatible with application version.`;
 
       let app = addon.matchingTargetApplication;
       if (app) {
         if (app.minVersion) {
           message += ` add-on minVersion: ${app.minVersion}.`;
@@ -3960,17 +3911,17 @@ var XPIInstall = {
     if (!addon.bootstrap) {
       throw new Error(`Only restartless (bootstrap) add-ons can be installed from sources: ${addon.id}`);
     }
     let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
     let oldAddon = await XPIDatabase.getVisibleAddonForID(addon.id);
     let callUpdate = false;
 
     let extraParams = {};
-    extraParams.temporarilyInstalled = aInstallLocation === XPIInternal.TemporaryInstallLocation;
+    extraParams.temporarilyInstalled = true;
     if (oldAddon) {
       if (!oldAddon.bootstrap) {
         logger.warn("Non-restartless Add-on is already installed", addon.id);
         throw new Error("Non-restartless add-on with ID "
                         + oldAddon.id + " is already installed");
       } else {
         logger.warn("Addon with ID " + oldAddon.id + " already installed,"
                     + " older version will be disabled");
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -450,17 +450,17 @@ function getAllAliasesForTypes(aTypes) {
  *        XPI file
  * @param {string} aPath
  *        The path to find the resource at, "/" separated. If aPath is empty
  *        then the uri to the root of the contained files will be returned
  * @returns {nsIURI}
  *        An nsIURI pointing at the resource
  */
 function getURIForResourceInFile(aFile, aPath) {
-  if (aFile.exists() && aFile.isDirectory()) {
+  if (!aFile.leafName.toLowerCase().endsWith(".xpi")) {
     let resource = aFile.clone();
     if (aPath)
       aPath.split("/").forEach(part => resource.append(part));
 
     return Services.io.newFileURI(resource);
   }
 
   return buildJarURI(aFile, aPath);
@@ -684,18 +684,18 @@ class XPIState {
    *        The add-on ID.
    * @returns {boolean}
    *       True if the time stamp has changed.
    */
   getModTime(aFile, aId) {
     // Modified time is the install manifest time, if any. If no manifest
     // exists, we assume this is a packed .xpi and use the time stamp of
     // {path}
-    let mtime = (tryGetMtime(getManifestFileForDir(aFile)) ||
-                 tryGetMtime(aFile));
+    let mtime = (aFile.leafName.toLowerCase().endsWith(".xpi") ?
+                 tryGetMtime(aFile) : tryGetMtime(getManifestFileForDir(aFile)));
     if (!mtime) {
       logger.warn("Can't get modified time of ${file}", {file: aFile.path});
     }
 
     this.changed = mtime != this.lastModifiedTime;
     this.lastModifiedTime = mtime;
     return this.changed;
   }
@@ -1951,46 +1951,35 @@ var XPIProvider = {
    *        See checkForChanges
    * @returns {boolean}
    *        True if any new add-ons were installed
    */
   installDistributionAddons(aManifests, aAppChanged) {
     let distroDir;
     try {
       distroDir = FileUtils.getDir(KEY_APP_DISTRIBUTION, [DIR_EXTENSIONS]);
+      if (!distroDir.isDirectory())
+        return false;
     } catch (e) {
       return false;
     }
 
-    if (!distroDir.exists())
-      return false;
-
-    if (!distroDir.isDirectory())
-      return false;
-
     let changed = false;
     let profileLocation = this.installLocationsByName[KEY_APP_PROFILE];
 
     let entries = distroDir.directoryEntries
                            .QueryInterface(Ci.nsIDirectoryEnumerator);
     let entry;
     while ((entry = entries.nextFile)) {
 
       let id = entry.leafName;
-
-      if (entry.isFile()) {
-        if (id.endsWith(".xpi")) {
-          id = id.slice(0, -4);
-        } else {
-          logger.debug("Ignoring distribution add-on that isn't an XPI: " + entry.path);
-          continue;
-        }
-      } else if (!entry.isDirectory()) {
-        logger.debug("Ignoring distribution add-on that isn't a file or directory: " +
-            entry.path);
+      if (id.endsWith(".xpi")) {
+        id = id.slice(0, -4);
+      } else {
+        logger.debug("Ignoring distribution add-on that isn't an XPI: " + entry.path);
         continue;
       }
 
       if (!gIDTest.test(id)) {
         logger.debug("Ignoring distribution add-on whose name is not a valid add-on ID: " +
             entry.path);
         continue;
       }
@@ -2770,20 +2759,19 @@ var XPIProvider = {
         Components.manager.removeBootstrappedManifestLocation(aFile);
       }
       this.setTelemetry(aAddon.id, aMethod + "_MS", new Date() - timeStart);
     }
   },
 };
 
 for (let meth of ["cancelUninstallAddon", "getInstallForFile",
-                  "getInstallForURL", "installAddonFromLocation",
-                  "installAddonFromSources", "installTemporaryAddon",
-                  "isInstallAllowed", "isInstallEnabled", "uninstallAddon",
-                  "updateSystemAddons"]) {
+                  "getInstallForURL", "installTemporaryAddon",
+                  "isInstallAllowed", "isInstallEnabled",
+                  "uninstallAddon", "updateSystemAddons"]) {
   XPIProvider[meth] = function() {
     return XPIInstall[meth](...arguments);
   };
 }
 
 function forwardInstallMethods(cls, methods) {
   let {prototype} = cls;
   for (let meth of methods) {
@@ -2914,30 +2902,28 @@ class DirectoryInstallLocation {
     // embedded filesystem has this issue, see bug 772238).
     let entries = getDirectoryEntries(this._directory);
     for (let entry of entries) {
       let id = entry.leafName;
 
       if (id == DIR_STAGE || id == DIR_TRASH)
         continue;
 
-      let directLoad = false;
-      if (entry.isFile() &&
-          id.substring(id.length - 4).toLowerCase() == ".xpi") {
-        directLoad = true;
+      let isFile = id.toLowerCase().endsWith(".xpi");
+      if (isFile) {
         id = id.substring(0, id.length - 4);
       }
 
       if (!gIDTest.test(id)) {
         logger.debug("Ignoring file entry whose name is not a valid add-on ID: " +
-             entry.path);
+                     entry.path);
         continue;
       }
 
-      if (!directLoad && (entry.isFile() || entry.isSymlink())) {
+      if (!isFile && (entry.isFile() || entry.isSymlink())) {
         let newEntry = this._readDirectoryFromFile(entry);
         if (!newEntry) {
           logger.debug("Deleting stale pointer file " + entry.path);
           try {
             entry.remove(true);
           } catch (e) {
             logger.warn("Failed to remove stale pointer file " + entry.path, e);
             // Failing to remove the stale pointer file is ignorable
--- a/toolkit/mozapps/extensions/test/addons/test_cache_certdb/bootstrap.js
+++ b/toolkit/mozapps/extensions/test/addons/test_cache_certdb/bootstrap.js
@@ -1,10 +1,8 @@
-var AM_Ci = Ci;
-
 const CERTDB_CONTRACTID = "@mozilla.org/security/x509certdb;1";
 const CERTDB_CID = Components.ID("{fb0bbc5c-452e-4783-b32c-80124693d871}");
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const CERT = `MIIDITCCAgmgAwIBAgIJALAv8fydd6nBMA0GCSqGSIb3DQEBBQUAMCcxJTAjBgNV
 BAMMHGJvb3RzdHJhcDFAdGVzdHMubW96aWxsYS5vcmcwHhcNMTYwMjAyMjMxNjUy
 WhcNMjYwMTMwMjMxNjUyWjAnMSUwIwYDVQQDDBxib290c3RyYXAxQHRlc3RzLm1v
@@ -20,35 +18,31 @@ HNQwHwYDVR0jBBgwFoAUac36ccv+99N5HxYa8dCD
 p4RqHrukHZSgKOyWjkRk7t6NXzNcnHco9HFv7FQRAXSJ5zObmyu+TMZlu4jHHCav
 GMcV3C/4SUGtlipZbgNe00UAIm6tM3Wh8dr38W7VYg4KGAwXou5XhQ9gCAnSn90o
 H/42NqHTjJsR4v18izX2aO25ARQdMby7Lsr5j9RqweHywiSlPusFcKRseqOnIP0d
 JT3+qh78LeMbNBO2mYD3SP/zu0TAmkAVNcj2KPw0+a0kVZ15rvslPC/K3xn9msMk
 fQthv3rDAcsWvi9YO7T+vylgZBgJfn1ZqpQqy58xN96uh6nPOw==`;
 
 function overrideCertDB() {
   // Unregister the real database.
-  let registrar = Components.manager.QueryInterface(AM_Ci.nsIComponentRegistrar);
-  let factory = registrar.getClassObject(CERTDB_CID, AM_Ci.nsIFactory);
+  let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+  let factory = registrar.getClassObject(CERTDB_CID, Ci.nsIFactory);
   registrar.unregisterFactory(CERTDB_CID, factory);
 
   // Get the real DB
-  let realCertDB = factory.createInstance(null, AM_Ci.nsIX509CertDB);
+  let realCertDB = factory.createInstance(null, Ci.nsIX509CertDB);
 
   let fakeCert = realCertDB.constructX509FromBase64(CERT.replace(/\n/g, ""));
 
   let fakeCertDB = {
     openSignedAppFileAsync(root, file, callback) {
       callback.openSignedAppFileFinished(Cr.NS_OK, null, fakeCert);
     },
 
-    verifySignedDirectoryAsync(root, dir, callback) {
-      callback.verifySignedDirectoryFinished(Cr.NS_OK, fakeCert);
-    },
-
-    QueryInterface: ChromeUtils.generateQI([AM_Ci.nsIX509CertDB])
+    QueryInterface: ChromeUtils.generateQI([Ci.nsIX509CertDB])
   };
 
   for (let property of Object.keys(realCertDB)) {
     if (property in fakeCertDB) {
       continue;
     }
 
     if (typeof realCertDB[property] == "function") {
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_update_complete/bootstrap.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/* exported startup, shutdown, install, uninstall, ADDON_ID, INSTALL_COMPLETE_PREF */
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
-
-const ADDON_ID = "test_delay_update_complete@tests.mozilla.org";
-const INSTALL_COMPLETE_PREF = "bootstraptest.install_complete_done";
-
-function install(data, reason) {}
-
-// normally we would use BootstrapMonitor here, but we need a reference to
-// the symbol inside `XPIProvider.jsm`.
-function startup(data, reason) {
-  // apply update immediately
-  if (data.hasOwnProperty("instanceID") && data.instanceID) {
-    AddonManager.addUpgradeListener(data.instanceID, (upgrade) => {
-      upgrade.install();
-    });
-  } else {
-    throw Error("no instanceID passed to bootstrap startup");
-  }
-}
-
-function shutdown(data, reason) {}
-
-function uninstall(data, reason) {}
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_update_defer/bootstrap.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/* exported startup, shutdown, install, uninstall, ADDON_ID, INSTALL_COMPLETE_PREF */
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
-
-const ADDON_ID = "test_delay_update_complete@tests.mozilla.org";
-const INSTALL_COMPLETE_PREF = "bootstraptest.install_complete_done";
-
-// global reference to hold upgrade object
-let gUpgrade;
-
-function install(data, reason) {}
-
-// normally we would use BootstrapMonitor here, but we need a reference to
-// the symbol inside `XPIProvider.jsm`.
-function startup(data, reason) {
-  // do not apply update immediately, hold on to for later
-  if (data.hasOwnProperty("instanceID") && data.instanceID) {
-    AddonManager.addUpgradeListener(data.instanceID, (upgrade) => {
-      gUpgrade = upgrade;
-    });
-  } else {
-    throw Error("no instanceID passed to bootstrap startup");
-  }
-
-  // add a listener so the test can pass control back
-  AddonManager.addAddonListener({
-    onFakeEvent: () => {
-      gUpgrade.install();
-    }
-  });
-}
-
-function shutdown(data, reason) {}
-
-function uninstall(data, reason) {}
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_update_ignore/bootstrap.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/* exported startup, shutdown, install, uninstall, ADDON_ID */
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
-
-const ADDON_ID = "test_delay_update_ignore@tests.mozilla.org";
-const TEST_IGNORE_PREF = "delaytest.ignore";
-
-function install(data, reason) {}
-
-// normally we would use BootstrapMonitor here, but we need a reference to
-// the symbol inside `XPIProvider.jsm`.
-function startup(data, reason) {
-  Services.prefs.setBoolPref(TEST_IGNORE_PREF, false);
-
-  // explicitly ignore update, will be queued for next restart
-  if (data.hasOwnProperty("instanceID") && data.instanceID) {
-    AddonManager.addUpgradeListener(data.instanceID, (upgrade) => {
-      Services.prefs.setBoolPref(TEST_IGNORE_PREF, true);
-    });
-  } else {
-    throw Error("no instanceID passed to bootstrap startup");
-  }
-}
-
-function shutdown(data, reason) {}
-
-function uninstall(data, reason) {}
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/bootstrap.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/* exported startup, shutdown, install, uninstall */
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-function install(data, reason) {
-  Services.prefs.setIntPref("bootstraptest.installed_version", 2);
-  Services.prefs.setIntPref("bootstraptest.install_reason", reason);
-}
-
-function startup(data, reason) {
-  Services.prefs.setIntPref("bootstraptest.active_version", 2);
-  Services.prefs.setIntPref("bootstraptest.startup_reason", reason);
-}
-
-function shutdown(data, reason) {
-  Services.prefs.setIntPref("bootstraptest.active_version", 0);
-  Services.prefs.setIntPref("bootstraptest.shutdown_reason", reason);
-}
-
-function uninstall(data, reason) {
-  Services.prefs.setIntPref("bootstraptest.installed_version", 0);
-  Services.prefs.setIntPref("bootstraptest.uninstall_reason", reason);
-}
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/install.rdf
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>addon2@tests.mozilla.org</em:id>
-    <em:version>2.0</em:version>
-
-    <!-- Front End MetaData -->
-    <em:name>Distributed add-ons test</em:name>
-    <em:bootstrap>true</em:bootstrap>
-
-    <em:targetApplication>
-      <Description>
-        <em:id>xpcshell@tests.mozilla.org</em:id>
-        <em:minVersion>1</em:minVersion>
-        <em:maxVersion>5</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-  </Description>
-</RDF>
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/subdir/dummy.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-Test of a file in a sub directory
\ No newline at end of file
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/subdir/subdir2/dummy2.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-Nested dummy file
\ No newline at end of file
--- a/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js
@@ -42,45 +42,16 @@ add_task(async function setup() {
     bootstrap: true,
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: "1",
       maxVersion: "1"
     }],
     name: "Packed, Disabled",
   }, profileDir);
-
-  // Unpacked, enabled
-  await promiseWriteInstallRDFToDir({
-    id: "unpacked-enabled@tests.mozilla.org",
-    version: "1.0",
-    bootstrap: true,
-    unpack: true,
-    targetApplications: [{
-      id: "xpcshell@tests.mozilla.org",
-      minVersion: "1",
-      maxVersion: "1"
-    }],
-    name: "Unpacked, Enabled",
-  }, profileDir, undefined, "extraFile.js");
-
-
-  // Unpacked, disabled
-  await promiseWriteInstallRDFToDir({
-    id: "unpacked-disabled@tests.mozilla.org",
-    version: "1.0",
-    bootstrap: true,
-    unpack: true,
-    targetApplications: [{
-      id: "xpcshell@tests.mozilla.org",
-      minVersion: "1",
-      maxVersion: "1"
-    }],
-    name: "Unpacked, disabled",
-  }, profileDir, undefined, "extraFile.js");
 });
 
 // Keep track of the last time stamp we've used, so that we can keep moving
 // it forward (if we touch two different files in the same add-on with the same
 // timestamp we may not consider the change significant)
 var lastTimestamp = Date.now();
 
 /*
@@ -108,92 +79,56 @@ function getXS() {
 async function getXSJSON() {
   await AddonTestUtils.loadAddonsList(true);
 
   return aomStartup.readStartupData();
 }
 
 add_task(async function detect_touches() {
   await promiseStartupManager();
-  let [/* pe */, pd, /* ue */, ud] = await promiseAddonsByIDs([
+  let [/* pe */, pd] = await promiseAddonsByIDs([
          "packed-enabled@tests.mozilla.org",
          "packed-disabled@tests.mozilla.org",
-         "unpacked-enabled@tests.mozilla.org",
-         "unpacked-disabled@tests.mozilla.org"
          ]);
 
   info("Disable test add-ons");
   pd.userDisabled = true;
-  ud.userDisabled = true;
 
   let XS = getXS();
 
   // Should be no changes detected here, because everything should start out up-to-date.
   Assert.ok(!XS.getInstallState());
 
   let states = XS.getLocation("app-profile");
 
   // State should correctly reflect enabled/disabled
   Assert.ok(states.get("packed-enabled@tests.mozilla.org").enabled);
   Assert.ok(!states.get("packed-disabled@tests.mozilla.org").enabled);
-  Assert.ok(states.get("unpacked-enabled@tests.mozilla.org").enabled);
-  Assert.ok(!states.get("unpacked-disabled@tests.mozilla.org").enabled);
 
   // Touch various files and make sure the change is detected.
 
   // We notice that a packed XPI is touched for an enabled add-on.
   let peFile = profileDir.clone();
   peFile.append("packed-enabled@tests.mozilla.org.xpi");
   checkChange(XS, peFile, true);
 
   // We should notice the packed XPI change for a disabled add-on too.
   let pdFile = profileDir.clone();
   pdFile.append("packed-disabled@tests.mozilla.org.xpi");
   checkChange(XS, pdFile, true);
-
-  // We notice changing install.rdf for an enabled unpacked add-on.
-  let ueDir = profileDir.clone();
-  ueDir.append("unpacked-enabled@tests.mozilla.org");
-  let manifest = ueDir.clone();
-  manifest.append("install.rdf");
-  checkChange(XS, manifest, true);
-
-  // We notice changing install.rdf for a *disabled* unpacked add-on.
-  let udDir = profileDir.clone();
-  udDir.append("unpacked-disabled@tests.mozilla.org");
-  manifest = udDir.clone();
-  manifest.append("install.rdf");
-  checkChange(XS, manifest, true);
-  // Finally, the case we actually care about...
-  // We *don't* notice changing another file for disabled unpacked add-on.
-  let otherFile = udDir.clone();
-  otherFile.append("extraFile.js");
-  checkChange(XS, otherFile, false);
-
-  /*
-   * When we enable an unpacked add-on that was modified while it was
-   * disabled, we reflect the new timestamp in the add-on DB (otherwise, we'll
-   * think it changed on next restart).
-   */
-  ud.userDisabled = false;
-  let xState = XS.getAddon("app-profile", ud.id);
-  Assert.ok(xState.enabled);
-  Assert.equal(xState.mtime, ud.updateDate.getTime());
 });
 
 /*
  * Uninstalling bootstrap add-ons should immediately remove them from the
  * extensions.xpiState preference.
  */
 add_task(async function uninstall_bootstrap() {
-  let [pe, /* pd, ue, ud */] = await promiseAddonsByIDs([
+  let [pe, /* pd */] = await promiseAddonsByIDs([
          "packed-enabled@tests.mozilla.org",
          "packed-disabled@tests.mozilla.org",
-         "unpacked-enabled@tests.mozilla.org",
-         "unpacked-disabled@tests.mozilla.org"
          ]);
   pe.uninstall();
 
   let xpiState = await getXSJSON();
   Assert.equal(false, "packed-enabled@tests.mozilla.org" in xpiState["app-profile"].addons);
 });
 
 /*
--- a/toolkit/mozapps/extensions/test/xpcshell/test_delay_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_delay_update.js
@@ -23,76 +23,154 @@ const TEST_IGNORE_PREF = "delaytest.igno
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
 // Create and configure the HTTP server.
 var testserver = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
 testserver.registerDirectory("/data/", do_get_file("data"));
 testserver.registerDirectory("/addons/", do_get_file("addons"));
 
 async function createIgnoreAddon() {
-  await promiseWriteInstallRDFToDir({
+  await promiseWriteInstallRDFToXPI({
     id: IGNORE_ID,
     version: "1.0",
     bootstrap: true,
     unpack: true,
     updateURL: `http://example.com/data/test_delay_updates_ignore_legacy.json`,
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: "1",
       maxVersion: "1"
     }],
     name: "Test Delay Update Ignore",
-  }, profileDir, IGNORE_ID, "bootstrap.js");
+  }, profileDir, IGNORE_ID, {
+    "bootstrap.js": String.raw`
+      ChromeUtils.import("resource://gre/modules/Services.jsm");
+      ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
+
+      const ADDON_ID = "test_delay_update_ignore@tests.mozilla.org";
+      const TEST_IGNORE_PREF = "delaytest.ignore";
+
+      function install(data, reason) {}
+
+      // normally we would use BootstrapMonitor here, but we need a reference to
+      // the symbol inside XPIProvider.jsm.
+      function startup(data, reason) {
+        Services.prefs.setBoolPref(TEST_IGNORE_PREF, false);
 
-  let unpacked_addon = profileDir.clone();
-  unpacked_addon.append(IGNORE_ID);
-  do_get_file("data/test_delay_update_ignore/bootstrap.js")
-    .copyTo(unpacked_addon, "bootstrap.js");
+        // explicitly ignore update, will be queued for next restart
+        if (data.hasOwnProperty("instanceID") && data.instanceID) {
+          AddonManager.addUpgradeListener(data.instanceID, (upgrade) => {
+            Services.prefs.setBoolPref(TEST_IGNORE_PREF, true);
+          });
+        } else {
+          throw Error("no instanceID passed to bootstrap startup");
+        }
+      }
+
+      function shutdown(data, reason) {}
+
+      function uninstall(data, reason) {}
+    `,
+  });
 }
 
 async function createCompleteAddon() {
-  await promiseWriteInstallRDFToDir({
+  await promiseWriteInstallRDFToXPI({
     id: COMPLETE_ID,
     version: "1.0",
     bootstrap: true,
     unpack: true,
     updateURL: `http://example.com/data/test_delay_updates_complete_legacy.json`,
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: "1",
       maxVersion: "1"
     }],
     name: "Test Delay Update Complete",
-  }, profileDir, COMPLETE_ID, "bootstrap.js");
+  }, profileDir, COMPLETE_ID, {
+    "bootstrap.js": String.raw`
+      ChromeUtils.import("resource://gre/modules/Services.jsm");
+      ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
+
+      const ADDON_ID = "test_delay_update_complete@tests.mozilla.org";
+      const INSTALL_COMPLETE_PREF = "bootstraptest.install_complete_done";
+
+      function install(data, reason) {}
 
-  let unpacked_addon = profileDir.clone();
-  unpacked_addon.append(COMPLETE_ID);
-  do_get_file("data/test_delay_update_complete/bootstrap.js")
-    .copyTo(unpacked_addon, "bootstrap.js");
+      // normally we would use BootstrapMonitor here, but we need a reference to
+      // the symbol inside XPIProvider.jsm.
+      function startup(data, reason) {
+        // apply update immediately
+        if (data.hasOwnProperty("instanceID") && data.instanceID) {
+          AddonManager.addUpgradeListener(data.instanceID, (upgrade) => {
+            upgrade.install();
+          });
+        } else {
+          throw Error("no instanceID passed to bootstrap startup");
+        }
+      }
+
+      function shutdown(data, reason) {}
+
+      function uninstall(data, reason) {}
+    `,
+  });
 }
 
 async function createDeferAddon() {
-  await promiseWriteInstallRDFToDir({
+  await promiseWriteInstallRDFToXPI({
     id: DEFER_ID,
     version: "1.0",
     bootstrap: true,
     unpack: true,
     updateURL: `http://example.com/data/test_delay_updates_defer_legacy.json`,
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: "1",
       maxVersion: "1"
     }],
     name: "Test Delay Update Defer",
-  }, profileDir, DEFER_ID, "bootstrap.js");
+  }, profileDir, DEFER_ID, {
+    "bootstrap.js": String.raw`
+      ChromeUtils.import("resource://gre/modules/Services.jsm");
+      ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
+
+      const ADDON_ID = "test_delay_update_complete@tests.mozilla.org";
+      const INSTALL_COMPLETE_PREF = "bootstraptest.install_complete_done";
+
+      // global reference to hold upgrade object
+      let gUpgrade;
+
+      function install(data, reason) {}
 
-  let unpacked_addon = profileDir.clone();
-  unpacked_addon.append(DEFER_ID);
-  do_get_file("data/test_delay_update_defer/bootstrap.js")
-    .copyTo(unpacked_addon, "bootstrap.js");
+      // normally we would use BootstrapMonitor here, but we need a reference to
+      // the symbol inside XPIProvider.jsm.
+      function startup(data, reason) {
+        // do not apply update immediately, hold on to for later
+        if (data.hasOwnProperty("instanceID") && data.instanceID) {
+          AddonManager.addUpgradeListener(data.instanceID, (upgrade) => {
+            gUpgrade = upgrade;
+          });
+        } else {
+          throw Error("no instanceID passed to bootstrap startup");
+        }
+
+        // add a listener so the test can pass control back
+        AddonManager.addAddonListener({
+          onFakeEvent: () => {
+            gUpgrade.install();
+          }
+        });
+      }
+
+      function shutdown(data, reason) {}
+
+      function uninstall(data, reason) {}
+    `,
+  });
 }
 
 // add-on registers upgrade listener, and ignores update.
 add_task(async function() {
 
   await createIgnoreAddon();
 
   await promiseStartupManager();
--- a/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js
@@ -48,24 +48,16 @@ var addon1_3 = {
   bootstrap: true,
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "1",
     maxVersion: "5"
   }]
 };
 
-function getActiveVersion() {
-  return Services.prefs.getIntPref("bootstraptest.active_version");
-}
-
-function getInstalledVersion() {
-  return Services.prefs.getIntPref("bootstraptest.installed_version");
-}
-
 async function setOldModificationTime() {
   // Make sure the installed extension has an old modification time so any
   // changes will be detected
   await promiseShutdownManager();
   let extension = gProfD.clone();
   extension.append("extensions");
   extension.append("addon1@tests.mozilla.org.xpi");
   setExtensionModifiedTime(extension, Date.now() - MAKE_FILE_OLD_DIFFERENCE);
@@ -208,54 +200,10 @@ async function run_test_8() {
 
   let a1 = await AddonManager.getAddonByID("addon1@tests.mozilla.org");
   Assert.notEqual(a1, null);
   Assert.equal(a1.version, "3.0");
   Assert.ok(a1.isActive);
   Assert.equal(a1.scope, AddonManager.SCOPE_PROFILE);
 
   a1.uninstall();
-  executeSoon(run_test_9);
-}
-
-// Tests that bootstrapped add-ons distributed start up correctly, also that
-// add-ons with multiple directories get copied fully
-async function run_test_9() {
-  await promiseRestartManager();
-
-  // Copy the test add-on to the distro dir
-  let addon = do_get_file("data/test_distribution2_2");
-  addon.copyTo(distroDir, "addon2@tests.mozilla.org");
-
-  await promiseRestartManager("5");
-
-  let a2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
-  Assert.notEqual(a2, null);
-  Assert.ok(a2.isActive);
-
-  Assert.equal(getInstalledVersion(), 2);
-  Assert.equal(getActiveVersion(), 2);
-
-  Assert.ok(a2.hasResource("bootstrap.js"));
-  Assert.ok(a2.hasResource("subdir/dummy.txt"));
-  Assert.ok(a2.hasResource("subdir/subdir2/dummy2.txt"));
-
-  // Currently installs are unpacked if the source is a directory regardless
-  // of the install.rdf property or the global preference
-
-  let addonDir = profileDir.clone();
-  addonDir.append("addon2@tests.mozilla.org");
-  Assert.ok(addonDir.exists());
-  Assert.ok(addonDir.isDirectory());
-  addonDir.append("subdir");
-  Assert.ok(addonDir.exists());
-  Assert.ok(addonDir.isDirectory());
-  addonDir.append("subdir2");
-  Assert.ok(addonDir.exists());
-  Assert.ok(addonDir.isDirectory());
-  addonDir.append("dummy2.txt");
-  Assert.ok(addonDir.exists());
-  Assert.ok(addonDir.isFile());
-
-  a2.uninstall();
-
   executeSoon(do_test_finished);
 }
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_install_from_sources.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-const ID = "bootstrap1@tests.mozilla.org";
-createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
-
-BootstrapMonitor.init();
-
-// Partial list of bootstrap reasons from XPIProvider.jsm
-const BOOTSTRAP_REASONS = {
-  ADDON_INSTALL: 5,
-  ADDON_UPGRADE: 7,
-  ADDON_DOWNGRADE: 8,
-};
-
-// Install an unsigned add-on with no existing add-on present.
-// Restart and make sure it is still around.
-add_task(async function() {
-  await promiseStartupManager();
-
-  let extInstallCalled = false;
-  AddonManager.addInstallListener({
-    onExternalInstall: (aInstall) => {
-      Assert.equal(aInstall.id, ID);
-      Assert.equal(aInstall.version, "1.0");
-      extInstallCalled = true;
-    },
-  });
-
-  let installingCalled = false;
-  let installedCalled = false;
-  AddonManager.addAddonListener({
-    onInstalling: (aInstall) => {
-      Assert.equal(aInstall.id, ID);
-      Assert.equal(aInstall.version, "1.0");
-      installingCalled = true;
-    },
-    onInstalled: (aInstall) => {
-      Assert.equal(aInstall.id, ID);
-      Assert.equal(aInstall.version, "1.0");
-      installedCalled = true;
-    },
-    onInstallStarted: (aInstall) => {
-      do_throw("onInstallStarted called unexpectedly");
-    }
-  });
-
-  await AddonManager.installAddonFromSources(do_get_file("data/from_sources/"));
-
-  Assert.ok(extInstallCalled);
-  Assert.ok(installingCalled);
-  Assert.ok(installedCalled);
-
-  let install = BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-  equal(install.reason, BOOTSTRAP_REASONS.ADDON_INSTALL);
-  BootstrapMonitor.checkAddonStarted(ID, "1.0");
-
-  let addon = await promiseAddonByID(ID);
-
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.version, "1.0");
-  Assert.equal(addon.name, "Test Bootstrap 1");
-  Assert.ok(addon.isCompatible);
-  Assert.ok(!addon.appDisabled);
-  Assert.ok(addon.isActive);
-  Assert.equal(addon.type, "extension");
-  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
-
-  await promiseRestartManager();
-
-  install = BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-  equal(install.reason, BOOTSTRAP_REASONS.ADDON_INSTALL);
-  BootstrapMonitor.checkAddonStarted(ID, "1.0");
-
-  addon = await promiseAddonByID(ID);
-  Assert.notEqual(addon, null);
-
-  await promiseRestartManager();
-});
-
--- a/toolkit/mozapps/extensions/test/xpcshell/test_proxy.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_proxy.js
@@ -5,16 +5,18 @@
 const ID = "proxy1@tests.mozilla.org";
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
 BootstrapMonitor.init();
 
 // Ensure that a proxy file to an add-on with a valid manifest works.
 add_task(async function() {
+  Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, false);
+
   await promiseStartupManager();
 
   let tempdir = gTmpD.clone();
   await promiseWriteInstallRDFToDir({
     id: ID,
     version: "1.0",
     bootstrap: true,
     unpack: true,
@@ -45,17 +47,17 @@ add_task(async function() {
 
   Assert.notEqual(addon, null);
   Assert.equal(addon.version, "1.0");
   Assert.equal(addon.name, "Test Bootstrap 1 (proxy)");
   Assert.ok(addon.isCompatible);
   Assert.ok(!addon.appDisabled);
   Assert.ok(addon.isActive);
   Assert.equal(addon.type, "extension");
-  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_UNKNOWN : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
 
   Assert.ok(proxyFile.exists());
 
   addon.uninstall();
   unpackedAddon.remove(true);
 
   await promiseRestartManager();
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
@@ -166,17 +166,17 @@ add_task(async function() {
             maxVersion: "1"
           }],
           name: "Test Bootstrap 1 (temporary)",
         }),
         "bootstrap.js": bootstrapJS,
       };
 
       let target;
-      if (packed) {
+      if (!packed) {
         target = tempdir.clone();
         target.append(ID);
 
         await AddonTestUtils.promiseWriteFilesToDir(target.path, files);
       } else {
         target = tempdir.clone();
         target.append(`${ID}.xpi`);
 
@@ -209,25 +209,27 @@ add_task(async function() {
 
       let startup = await onStartup;
       equal(startup.data.version, newversion);
       equal(startup.reason, reason);
       equal(startup.data.oldVersion, "2.0");
 
       addon = await promiseAddonByID(ID);
 
+      let signedState = packed ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_UNKNOWN;
+
       // temporary add-on is installed and started
       Assert.notEqual(addon, null);
       Assert.equal(addon.version, newversion);
       Assert.equal(addon.name, "Test Bootstrap 1 (temporary)");
       Assert.ok(addon.isCompatible);
       Assert.ok(!addon.appDisabled);
       Assert.ok(addon.isActive);
       Assert.equal(addon.type, "extension");
-      Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+      Assert.equal(addon.signedState, mozinfo.addon_signing ? signedState : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
 
       // Now restart, the temporary addon will go away which should
       // be the opposite action (ie, if the temporary addon was an
       // upgrade, then removing it is a downgrade and vice versa)
       reason = reason == BOOTSTRAP_REASONS.ADDON_UPGRADE ?
                BOOTSTRAP_REASONS.ADDON_DOWNGRADE :
                BOOTSTRAP_REASONS.ADDON_UPGRADE;
 
@@ -425,17 +427,17 @@ add_task(async function() {
   // temporary add-on is installed and started
   Assert.notEqual(addon, null);
   Assert.equal(addon.version, "2.0");
   Assert.equal(addon.name, "Test Bootstrap 1 (temporary)");
   Assert.ok(addon.isCompatible);
   Assert.ok(!addon.appDisabled);
   Assert.ok(addon.isActive);
   Assert.equal(addon.type, "extension");
-  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_UNKNOWN : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
 
   addon.uninstall();
 
   await new Promise(executeSoon);
   addon = await promiseAddonByID(ID);
 
   BootstrapMonitor.checkAddonInstalled(ID);
   BootstrapMonitor.checkAddonStarted(ID);
@@ -671,17 +673,17 @@ add_task(async function() {
   // temporary add-on is installed and started
   Assert.notEqual(tempAddon, null);
   Assert.equal(tempAddon.version, "2.0");
   Assert.equal(tempAddon.name, "Test Bootstrap 1 (temporary)");
   Assert.ok(tempAddon.isCompatible);
   Assert.ok(!tempAddon.appDisabled);
   Assert.ok(tempAddon.isActive);
   Assert.equal(tempAddon.type, "extension");
-  Assert.equal(tempAddon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+  Assert.equal(tempAddon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_UNKNOWN : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
 
   tempAddon.uninstall();
   unpacked_addon.remove(true);
 
   addon.userDisabled = false;
   await new Promise(executeSoon);
   addon = await promiseAddonByID(ID);
 
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini
@@ -3,12 +3,10 @@ head = head_addons.js head_unpack.js
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android'
 dupe-manifest =
 tags = addons
 
 [test_webextension_paths.js]
 tags = webextensions
-[test_webextension_theme.js]
-tags = webextensions
 
 [test_filepointer.js]
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -135,17 +135,16 @@ tags = blocklist
 [test_gfxBlacklist_prefs.js]
 # Bug 1248787 - consistently fails
 skip-if = true
 tags = blocklist
 [test_gmpProvider.js]
 skip-if = appname != "firefox"
 [test_harness.js]
 [test_install.js]
-[test_install_from_sources.js]
 [test_install_icons.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_invalid_install_rdf.js]
 [test_isDebuggable.js]
 [test_isReady.js]
 [test_json_updatecheck.js]
 [test_legacy.js]
@@ -177,16 +176,17 @@ tags = blocklist
 skip-if = os == "android"
 [test_plugins.js]
 [test_pref_properties.js]
 [test_provider_markSafe.js]
 [test_provider_shutdown.js]
 [test_provider_unsafe_access_shutdown.js]
 [test_provider_unsafe_access_startup.js]
 [test_proxies.js]
+skip-if = require_signing
 [test_proxy.js]
 [test_registerchrome.js]
 [test_registry.js]
 skip-if = os != 'win'
 [test_reload.js]
 # Bug 676992: test consistently hangs on Android
 # There's a problem removing a temp file without manually clearing the cache on Windows
 skip-if = os == "android" || os == "win"
@@ -283,8 +283,10 @@ tags = webextensions
 skip-if = appname == "thunderbird"
 tags = webextensions
 [test_webextension_install_syntax_error.js]
 skip-if = appname == "thunderbird"
 tags = webextensions
 [test_webextension_langpack.js]
 skip-if = appname == "thunderbird"
 tags = webextensions
+[test_webextension_theme.js]
+tags = webextensions