Bug 1314177 - use class sugar and async/await for install location classes r?aswan draft
authorRobert Helmer <rhelmer@mozilla.com>
Tue, 28 Mar 2017 12:24:41 -0700
changeset 556289 1237a9b88402db286ab4144d5113c24533d0fabe
parent 555725 b043233ec04f06768d59dcdfb9e928142280f3cc
child 556290 17e1aaa1b8d0b66c2e6af6c2c739e1a9db03357b
child 556494 1e8486acfd2b5fde03ef0837de2b3d53ac82bfa7
push id52492
push userrhelmer@mozilla.com
push dateWed, 05 Apr 2017 15:30:41 +0000
reviewersaswan
bugs1314177
milestone55.0a1
Bug 1314177 - use class sugar and async/await for install location classes r?aswan MozReview-Commit-ID: K1uUDIk7ATZ
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -3093,41 +3093,41 @@ this.XPIProvider = {
              getService(Ci.nsIWindowWatcher);
     ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant);
 
     // Ensure any changes to the add-ons list are flushed to disk
     Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
                                !XPIDatabase.writeAddonsList());
   },
 
-  updateSystemAddons: Task.async(function*() {
+  async updateSystemAddons() {
     let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
     if (!systemAddonLocation)
       return;
 
     // Don't do anything in safe mode
     if (Services.appinfo.inSafeMode)
       return;
 
     // Download the list of system add-ons
     let url = Preferences.get(PREF_SYSTEM_ADDON_UPDATE_URL, null);
     if (!url) {
-      yield systemAddonLocation.cleanDirectories();
+      await systemAddonLocation.cleanDirectories();
       return;
     }
 
     url = UpdateUtils.formatUpdateURL(url);
 
     logger.info(`Starting system add-on update check from ${url}.`);
-    let res = yield ProductAddonChecker.getProductAddonList(url);
+    let res = await ProductAddonChecker.getProductAddonList(url);
 
     // If there was no list then do nothing.
     if (!res || !res.gmpAddons) {
       logger.info("No system add-ons list was returned.");
-      yield systemAddonLocation.cleanDirectories();
+      await systemAddonLocation.cleanDirectories();
       return;
     }
 
     let addonList = new Map(
       res.gmpAddons.map(spec => [spec.id, { spec, path: null, addon: null }]));
 
     let getAddonsInLocation = (location) => {
       return new Promise(resolve => {
@@ -3147,64 +3147,64 @@ this.XPIProvider = {
         if (wantedInfo.spec.version != addon.version)
           return false;
       }
 
       return true;
     };
 
     // If this matches the current set in the profile location then do nothing.
-    let updatedAddons = addonMap(yield getAddonsInLocation(KEY_APP_SYSTEM_ADDONS));
+    let updatedAddons = addonMap(await getAddonsInLocation(KEY_APP_SYSTEM_ADDONS));
     if (setMatches(addonList, updatedAddons)) {
       logger.info("Retaining existing updated system add-ons.");
-      yield systemAddonLocation.cleanDirectories();
+      await systemAddonLocation.cleanDirectories();
       return;
     }
 
     // If this matches the current set in the default location then reset the
     // updated set.
-    let defaultAddons = addonMap(yield getAddonsInLocation(KEY_APP_SYSTEM_DEFAULTS));
+    let defaultAddons = addonMap(await getAddonsInLocation(KEY_APP_SYSTEM_DEFAULTS));
     if (setMatches(addonList, defaultAddons)) {
       logger.info("Resetting system add-ons.");
       systemAddonLocation.resetAddonSet();
-      yield systemAddonLocation.cleanDirectories();
+      await systemAddonLocation.cleanDirectories();
       return;
     }
 
     // Download all the add-ons
-    let downloadAddon = Task.async(function*(item) {
+    async function downloadAddon(item) {
       try {
         let sourceAddon = updatedAddons.get(item.spec.id);
         if (sourceAddon && sourceAddon.version == item.spec.version) {
           // Copying the file to a temporary location has some benefits. If the
           // file is locked and cannot be read then we'll fall back to
           // downloading a fresh copy. It also means we don't have to remember
           // whether to delete the temporary copy later.
           try {
             let path = OS.Path.join(OS.Constants.Path.tmpDir, "tmpaddon");
-            let unique = yield OS.File.openUnique(path);
+            let unique = await OS.File.openUnique(path);
             unique.file.close();
-            yield OS.File.copy(sourceAddon._sourceBundle.path, unique.path);
+            await OS.File.copy(sourceAddon._sourceBundle.path, unique.path);
             // Make sure to update file modification times so this is detected
             // as a new add-on.
-            yield OS.File.setDates(unique.path);
+            await OS.File.setDates(unique.path);
             item.path = unique.path;
           } catch (e) {
             logger.warn(`Failed make temporary copy of ${sourceAddon._sourceBundle.path}.`, e);
           }
         }
         if (!item.path) {
-          item.path = yield ProductAddonChecker.downloadAddon(item.spec);
+          item.path = await ProductAddonChecker.downloadAddon(item.spec);
         }
-        item.addon = yield loadManifestFromFile(nsIFile(item.path), systemAddonLocation);
+        item.addon = await loadManifestFromFile(nsIFile(item.path), systemAddonLocation);
       } catch (e) {
         logger.error(`Failed to download system add-on ${item.spec.id}`, e);
       }
-    });
-    yield Promise.all(Array.from(addonList.values()).map(downloadAddon));
+    }
+    await Promise.all(Array.from(addonList.values()).map(downloadAddon));
 
     // The download promises all resolve regardless, now check if they all
     // succeeded
     let validateAddon = (item) => {
       if (item.spec.id != item.addon.id) {
         logger.warn(`Downloaded system add-on expected to be ${item.spec.id} but was ${item.addon.id}.`);
         return false;
       }
@@ -3222,19 +3222,19 @@ this.XPIProvider = {
 
     if (!Array.from(addonList.values()).every(item => item.path && item.addon && validateAddon(item))) {
       throw new Error("Rejecting updated system add-on set that either could not " +
                       "be downloaded or contained unusable add-ons.");
     }
 
     // Install into the install location
     logger.info("Installing new system add-on set");
-    yield systemAddonLocation.installAddonSet(Array.from(addonList.values())
+    await systemAddonLocation.installAddonSet(Array.from(addonList.values())
       .map(a => a.addon));
-  }),
+  },
 
   /**
    * Verifies that all installed add-ons are still correctly signed.
    */
   verifySignatures() {
     XPIDatabase.getAddonList(a => true, (addons) => {
       Task.spawn(function*() {
         let changes = {
@@ -7965,48 +7965,46 @@ PROP_LOCALE_MULTI.forEach(function(aProp
   });
 });
 
 /**
  * An object which identifies a directory install location for add-ons. The
  * location consists of a directory which contains the add-ons installed in the
  * location.
  *
- * Each add-on installed in the location is either a directory containing the
- * add-on's files or a text file containing an absolute path to the directory
- * containing the add-ons files. The directory or text file must have the same
- * name as the add-on's ID.
- *
- * @param  aName
- *         The string identifier for the install location
- * @param  aDirectory
- *         The nsIFile directory for the install location
- * @param  aScope
- *         The scope of add-ons installed in this location
  */
-function DirectoryInstallLocation(aName, aDirectory, aScope) {
-  this._name = aName;
-  this.locked = true;
-  this._directory = aDirectory;
-  this._scope = aScope
-  this._IDToFileMap = {};
-  this._linkedAddons = [];
-
-  if (!aDirectory || !aDirectory.exists())
-    return;
-  if (!aDirectory.isDirectory())
-    throw new Error("Location must be a directory.");
-
-  this._readAddons();
-}
-
-DirectoryInstallLocation.prototype = {
-  _name: "",
-  _directory: null,
-  _IDToFileMap: null,  // mapping from add-on ID to nsIFile
+class DirectoryInstallLocation {
+  /**
+   * Each add-on installed in the location is either a directory containing the
+   * add-on's files or a text file containing an absolute path to the directory
+   * containing the add-ons files. The directory or text file must have the same
+   * name as the add-on's ID.
+   *
+   * @param  aName
+   *         The string identifier for the install location
+   * @param  aDirectory
+   *         The nsIFile directory for the install location
+   * @param  aScope
+   *         The scope of add-ons installed in this location
+  */
+  constructor(aName, aDirectory, aScope) {
+    this._name = aName;
+    this.locked = true;
+    this._directory = aDirectory;
+    this._scope = aScope
+    this._IDToFileMap = {};
+    this._linkedAddons = [];
+
+    if (!aDirectory || !aDirectory.exists())
+      return;
+    if (!aDirectory.isDirectory())
+      throw new Error("Location must be a directory.");
+
+    this._readAddons();
+  }
 
   /**
    * Reads a directory linked to in a file.
    *
    * @param   file
    *          The file containing the directory path
    * @return  An nsIFile object representing the linked directory.
    */
@@ -8054,17 +8052,17 @@ DirectoryInstallLocation.prototype = {
         return null;
       }
 
       return linkedDirectory;
     }
 
     logger.warn("File pointer " + aFile.path + " does not contain a path");
     return null;
-  },
+  }
 
   /**
    * Finds all the add-ons installed in this location.
    */
   _readAddons() {
     // Use a snapshot of the directory contents to avoid possible issues with
     // iterating over a directory while removing files from it (the YAFFS2
     // embedded filesystem has this issue, see bug 772238).
@@ -8103,126 +8101,127 @@ DirectoryInstallLocation.prototype = {
 
         entry = newEntry;
         this._linkedAddons.push(id);
       }
 
       this._IDToFileMap[id] = entry;
       XPIProvider._addURIMapping(id, entry);
     }
-  },
+  }
 
   /**
    * Gets the name of this install location.
    */
   get name() {
     return this._name;
-  },
+  }
 
   /**
    * Gets the scope of this install location.
    */
   get scope() {
     return this._scope;
-  },
+  }
 
   /**
    * Gets an array of nsIFiles for add-ons installed in this location.
    */
   getAddonLocations() {
     let locations = new Map();
     for (let id in this._IDToFileMap) {
       locations.set(id, this._IDToFileMap[id].clone());
     }
     return locations;
-  },
+  }
 
   /**
    * Gets the directory that the add-on with the given ID is installed in.
    *
    * @param  aId
    *         The ID of the add-on
    * @return The nsIFile
    * @throws if the ID does not match any of the add-ons installed
    */
   getLocationForID(aId) {
     if (aId in this._IDToFileMap)
       return this._IDToFileMap[aId].clone();
     throw new Error("Unknown add-on ID " + aId);
-  },
+  }
 
   /**
    * Returns true if the given addon was installed in this location by a text
    * file pointing to its real path.
    *
    * @param aId
    *        The ID of the addon
    */
   isLinkedAddon(aId) {
     return this._linkedAddons.indexOf(aId) != -1;
   }
-};
+}
 
 /**
  * An extension of DirectoryInstallLocation which adds methods to installing
  * and removing add-ons from the directory at runtime.
- *
- * @param  aName
- *         The string identifier for the install location
- * @param  aDirectory
- *         The nsIFile directory for the install location
- * @param  aScope
- *         The scope of add-ons installed in this location
  */
-function MutableDirectoryInstallLocation(aName, aDirectory, aScope) {
-  DirectoryInstallLocation.call(this, aName, aDirectory, aScope);
-  this.locked = false;
-  this._stagingDirLock = 0;
-}
-
-MutableDirectoryInstallLocation.prototype = Object.create(DirectoryInstallLocation.prototype);
-Object.assign(MutableDirectoryInstallLocation.prototype, {
+class MutableDirectoryInstallLocation extends DirectoryInstallLocation {
+  /**
+   * @param  aName
+   *         The string identifier for the install location
+   * @param  aDirectory
+   *         The nsIFile directory for the install location
+   * @param  aScope
+   *         The scope of add-ons installed in this location
+   */
+  constructor(aName, aDirectory, aScope) {
+    super(aName, aDirectory, aScope);
+
+    this.locked = false;
+    this._stagingDirLock = 0;
+  }
+
   /**
    * Gets the staging directory to put add-ons that are pending install and
    * uninstall into.
    *
    * @return an nsIFile
    */
   getStagingDir() {
     let dir = this._directory.clone();
     dir.append(DIR_STAGE);
     return dir;
-  },
+  }
 
   requestStagingDir() {
     this._stagingDirLock++;
 
     if (this._stagingDirPromise)
       return this._stagingDirPromise;
 
     OS.File.makeDir(this._directory.path);
     let stagepath = OS.Path.join(this._directory.path, DIR_STAGE);
     return this._stagingDirPromise = OS.File.makeDir(stagepath).then(null, (e) => {
       if (e instanceof OS.File.Error && e.becauseExists)
         return;
       logger.error("Failed to create staging directory", e);
       throw e;
     });
-  },
+  }
 
   releaseStagingDir() {
     this._stagingDirLock--;
 
     if (this._stagingDirLock == 0) {
       this._stagingDirPromise = null;
       this.cleanStagingDir();
     }
 
     return Promise.resolve();
-  },
+  }
 
   /**
    * Removes the specified files or directories in the staging directory and
    * then if the staging directory is empty attempts to remove it.
    *
    * @param  aLeafNames
    *         An array of file or directory to remove from the directory, the
    *         array may be empty
@@ -8249,17 +8248,17 @@ Object.assign(MutableDirectoryInstallLoc
 
     try {
       setFilePermissions(dir, FileUtils.PERMS_DIRECTORY);
       dir.remove(false);
     } catch (e) {
       logger.warn("Failed to remove staging dir", e);
       // Failing to remove the staging directory is ignorable
     }
-  },
+  }
 
   /**
    * Returns a directory that is normally on the same filesystem as the rest of
    * the install location and can be used for temporarily storing files during
    * safe move operations. Calling this method will delete the existing trash
    * directory and its contents.
    *
    * @return an nsIFile
@@ -8274,17 +8273,17 @@ Object.assign(MutableDirectoryInstallLoc
       trashDirExists = false;
     } catch (e) {
       logger.warn("Failed to remove trash directory", e);
     }
     if (!trashDirExists)
       trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
 
     return trashDir;
-  },
+  }
 
   /**
    * Installs an add-on into the install location.
    *
    * @param  id
    *         The ID of the add-on to install
    * @param  source
    *         The source nsIFile to install from
@@ -8393,17 +8392,17 @@ Object.assign(MutableDirectoryInstallLoc
     XPIProvider._addURIMapping(id, newFile);
 
     if (existingAddonID && existingAddonID != id &&
         existingAddonID in this._IDToFileMap) {
       delete this._IDToFileMap[existingAddonID];
     }
 
     return newFile;
-  },
+  }
 
   /**
    * Uninstalls an add-on from this location.
    *
    * @param  aId
    *         The ID of the add-on to uninstall
    * @throws if the ID does not match any of the add-ons installed
    */
@@ -8445,60 +8444,67 @@ Object.assign(MutableDirectoryInstallLoc
       try {
         recursiveRemove(trashDir);
       } catch (e) {
         logger.warn("Failed to remove trash directory when uninstalling " + aId, e);
       }
     }
 
     delete this._IDToFileMap[aId];
-  },
-});
+  }
+}
 
 /**
  * An object which identifies a directory install location for system add-ons
- * upgrades.
- *
- * The location consists of a directory which contains the add-ons installed.
- *
- * @param  aName
- *         The string identifier for the install location
- * @param  aDirectory
- *         The nsIFile directory for the install location
- * @param  aScope
- *         The scope of add-ons installed in this location
- * @param  aResetSet
- *         True to throw away the current add-on set
+ * updates.
  */
-function SystemAddonInstallLocation(aName, aDirectory, aScope, aResetSet) {
-  this._baseDir = aDirectory;
-  this._nextDir = null;
-
-  this._stagingDirLock = 0;
-
-  if (aResetSet)
-    this.resetAddonSet();
-
-  this._addonSet = this._loadAddonSet();
-
-  this._directory = null;
-  if (this._addonSet.directory) {
-    this._directory = aDirectory.clone();
-    this._directory.append(this._addonSet.directory);
-    logger.info("SystemAddonInstallLocation scanning directory " + this._directory.path);
-  } else {
-    logger.info("SystemAddonInstallLocation directory is missing");
-  }
-
-  DirectoryInstallLocation.call(this, aName, this._directory, aScope);
-  this.locked = false;
-}
-
-SystemAddonInstallLocation.prototype = Object.create(DirectoryInstallLocation.prototype);
-Object.assign(SystemAddonInstallLocation.prototype, {
+class SystemAddonInstallLocation extends MutableDirectoryInstallLocation {
+  /**
+    * The location consists of a directory which contains the add-ons installed.
+    *
+    * @param  aName
+    *         The string identifier for the install location
+    * @param  aDirectory
+    *         The nsIFile directory for the install location
+    * @param  aScope
+    *         The scope of add-ons installed in this location
+    * @param  aResetSet
+    *         True to throw away the current add-on set
+    */
+  constructor(aName, aDirectory, aScope, aResetSet) {
+    let addonSet = SystemAddonInstallLocation._loadAddonSet();
+    let directory = null;
+
+    // The system add-on update directory is stored in a pref.
+    // Therefore, this is looked up before calling the
+    // constructor on the superclass.
+    if (addonSet.directory) {
+      directory = aDirectory.clone();
+      directory.append(addonSet.directory);
+      logger.info("SystemAddonInstallLocation scanning directory " + directory.path);
+    } else {
+      logger.info("SystemAddonInstallLocation directory is missing");
+    }
+
+    super(aName, directory, aScope);
+
+    this._addonSet = addonSet;
+    this._baseDir = aDirectory;
+    this._nextDir = null;
+    this._directory = directory;
+
+    this._stagingDirLock = 0;
+
+    if (aResetSet) {
+      this.resetAddonSet();
+    }
+
+    this.locked = false;
+  }
+
   /**
    * Removes the specified files or directories in the staging directory and
    * then if the staging directory is empty attempts to remove it.
    *
    * @param  aLeafNames
    *         An array of file or directory to remove from the directory, the
    *         array may be empty
    */
@@ -8524,121 +8530,124 @@ Object.assign(SystemAddonInstallLocation
 
     try {
       setFilePermissions(dir, FileUtils.PERMS_DIRECTORY);
       dir.remove(false);
     } catch (e) {
       logger.warn("Failed to remove staging dir", e);
       // Failing to remove the staging directory is ignorable
     }
-  },
+  }
 
   /**
    * Gets the staging directory to put add-ons that are pending install and
    * uninstall into.
    *
    * @return {nsIFile} - staging directory for system add-on upgrades.
    */
   getStagingDir() {
-    this._addonSet = this._loadAddonSet();
+    this._addonSet = SystemAddonInstallLocation._loadAddonSet();
     let dir = null;
     if (this._addonSet.directory) {
       this._directory = this._baseDir.clone();
       this._directory.append(this._addonSet.directory);
       dir = this._directory.clone();
       dir.append(DIR_STAGE);
     } else {
       logger.info("SystemAddonInstallLocation directory is missing");
     }
 
     return dir;
-  },
+  }
 
   requestStagingDir() {
     this._stagingDirLock++;
     if (this._stagingDirPromise)
       return this._stagingDirPromise;
 
-    this._addonSet = this._loadAddonSet();
+    this._addonSet = SystemAddonInstallLocation._loadAddonSet();
     if (this._addonSet.directory) {
       this._directory = this._baseDir.clone();
       this._directory.append(this._addonSet.directory);
     }
 
     OS.File.makeDir(this._directory.path);
     let stagepath = OS.Path.join(this._directory.path, DIR_STAGE);
     return this._stagingDirPromise = OS.File.makeDir(stagepath).then(null, (e) => {
       if (e instanceof OS.File.Error && e.becauseExists)
         return;
       logger.error("Failed to create staging directory", e);
       throw e;
     });
-  },
+  }
 
   releaseStagingDir() {
     this._stagingDirLock--;
 
     if (this._stagingDirLock == 0) {
       this._stagingDirPromise = null;
       this.cleanStagingDir();
     }
 
     return Promise.resolve();
-  },
+  }
 
   /**
    * Reads the current set of system add-ons
    */
-  _loadAddonSet() {
+  static _loadAddonSet() {
     try {
       let setStr = Preferences.get(PREF_SYSTEM_ADDON_SET, null);
       if (setStr) {
         let addonSet = JSON.parse(setStr);
-        if ((typeof addonSet == "object") && addonSet.schema == 1)
+        if ((typeof addonSet == "object") && addonSet.schema == 1) {
           return addonSet;
+        }
       }
     } catch (e) {
       logger.error("Malformed system add-on set, resetting.");
     }
 
     return { schema: 1, addons: {} };
-  },
+  }
 
   /**
    * Saves the current set of system add-ons
    *
    * @param {Object} aAddonSet - object containing schema, directory and set
    *                 of system add-on IDs and versions.
    */
   _saveAddonSet(aAddonSet) {
     Preferences.set(PREF_SYSTEM_ADDON_SET, JSON.stringify(aAddonSet));
-  },
+  }
 
   getAddonLocations() {
     // Updated system add-ons are ignored in safe mode
-    if (Services.appinfo.inSafeMode)
+    if (Services.appinfo.inSafeMode) {
       return new Map();
-
-    let addons = DirectoryInstallLocation.prototype.getAddonLocations.call(this);
+    }
+
+    let addons = super.getAddonLocations();
 
     // Strip out any unexpected add-ons from the list
     for (let id of addons.keys()) {
-      if (!(id in this._addonSet.addons))
+      if (!(id in this._addonSet.addons)) {
         addons.delete(id);
+      }
     }
 
     return addons;
-  },
+  }
 
   /**
    * Tests whether updated system add-ons are expected.
    */
   isActive() {
     return this._directory != null;
-  },
+  }
 
   isValidAddon(aAddon) {
     if (aAddon.appDisabled) {
       logger.warn(`System add-on ${aAddon.id} isn't compatible with the application.`);
       return false;
     }
 
     if (aAddon.unpack) {
@@ -8652,17 +8661,17 @@ Object.assign(SystemAddonInstallLocation
     }
 
     if (!aAddon.multiprocessCompatible) {
       logger.warn(`System add-on ${aAddon.id} isn't multiprocess compatible.`);
       return false;
     }
 
     return true;
-  },
+  }
 
   /**
    * Tests whether the loaded add-on information matches what is expected.
    */
   isValid(aAddons) {
     for (let id of Object.keys(this._addonSet.addons)) {
       if (!aAddons.has(id)) {
         logger.warn(`Expected add-on ${id} is missing from the system add-on location.`);
@@ -8675,17 +8684,17 @@ Object.assign(SystemAddonInstallLocation
         return false;
       }
 
       if (!this.isValidAddon(addon))
         return false;
     }
 
     return true;
-  },
+  }
 
   /**
    * Resets the add-on set so on the next startup the default set will be used.
    */
   resetAddonSet() {
     logger.info("Removing all system add-on upgrades.");
 
     // remove everything from the pref first, if uninstall
@@ -8704,83 +8713,81 @@ Object.assign(SystemAddonInstallLocation
       for (let id of Object.keys(this._addonSet.addons)) {
         AddonManager.getAddonByID(id, addon => {
           if (addon) {
             addon.uninstall();
           }
         });
       }
     }
-  },
+  }
 
   /**
    * Removes any directories not currently in use or pending use after a
    * restart. Any errors that happen here don't really matter as we'll attempt
    * to cleanup again next time.
    */
-  cleanDirectories: Task.async(function*() {
-
+  async cleanDirectories() {
     // System add-ons directory does not exist
-    if (!(yield OS.File.exists(this._baseDir.path))) {
+    if (!(await OS.File.exists(this._baseDir.path))) {
       return;
     }
 
     let iterator;
     try {
       iterator = new OS.File.DirectoryIterator(this._baseDir.path);
     } catch (e) {
       logger.error("Failed to clean updated system add-ons directories.", e);
       return;
     }
 
     try {
-      let entries = [];
-
-      yield iterator.forEach(entry => {
+      for (let promise in iterator) {
+        let entry = await promise;
+
         // Skip the directory currently in use
-        if (this._directory && this._directory.path == entry.path)
-          return;
+        if (this._directory && this._directory.path == entry.path) {
+          continue;
+        }
 
         // Skip the next directory
-        if (this._nextDir && this._nextDir.path == entry.path)
-          return;
-
-        entries.push(entry);
-      });
-
-      for (let entry of entries) {
+        if (this._nextDir && this._nextDir.path == entry.path) {
+          continue;
+        }
+
         if (entry.isDir) {
-          yield OS.File.removeDir(entry.path, {
-            ignoreAbsent: true,
-            ignorePermissions: true,
-          });
-        } else {
-          yield OS.File.remove(entry.path, {
-            ignoreAbsent: true,
-          });
-        }
-      }
+           await OS.File.removeDir(entry.path, {
+             ignoreAbsent: true,
+             ignorePermissions: true,
+           });
+         } else {
+           await OS.File.remove(entry.path, {
+             ignoreAbsent: true,
+           });
+         }
+       }
+
     } catch (e) {
       logger.error("Failed to clean updated system add-ons directories.", e);
     } finally {
       iterator.close();
     }
-  }),
+  }
 
   /**
    * Installs a new set of system add-ons into the location and updates the
    * add-on set in prefs.
    *
    * @param {Array} aAddons - An array of addons to install.
    */
-  installAddonSet: Task.async(function*(aAddons) {
+  async installAddonSet(aAddons) {
     // Make sure the base dir exists
-    yield OS.File.makeDir(this._baseDir.path, { ignoreExisting: true });
-
-    let addonSet = this._loadAddonSet();
+    await OS.File.makeDir(this._baseDir.path, { ignoreExisting: true });
+
+    let addonSet = SystemAddonInstallLocation._loadAddonSet();
 
     // Remove any add-ons that are no longer part of the set.
     for (let addonID of Object.keys(addonSet.addons)) {
       if (!aAddons.includes(addonID)) {
         AddonManager.getAddonByID(addonID, a => a.uninstall());
       }
     }
 
@@ -8789,115 +8796,115 @@ Object.assign(SystemAddonInstallLocation
     let uuidGen = Cc["@mozilla.org/uuid-generator;1"].
                   getService(Ci.nsIUUIDGenerator);
     newDir.append("blank");
 
     while (true) {
       newDir.leafName = uuidGen.generateUUID().toString();
 
       try {
-        yield OS.File.makeDir(newDir.path, { ignoreExisting: false });
+        await OS.File.makeDir(newDir.path, { ignoreExisting: false });
         break;
       } catch (e) {
         logger.debug("Could not create new system add-on updates dir, retrying", e);
       }
     }
 
     // Record the new upgrade directory.
     let state = { schema: 1, directory: newDir.leafName, addons: {} };
     this._saveAddonSet(state);
 
     this._nextDir = newDir;
     let location = this;
 
     let installs = [];
     for (let addon of aAddons) {
-      let install = yield createLocalInstall(addon._sourceBundle, location);
+      let install = await createLocalInstall(addon._sourceBundle, location);
       installs.push(install);
     }
 
-    let installAddon = Task.async(function*(install) {
+    async function installAddon(install) {
       // Make the new install own its temporary file.
       install.ownsTempFile = true;
       install.install();
-    });
-
-    let postponeAddon = Task.async(function*(install) {
+    }
+
+    async function postponeAddon(install) {
       let resumeFn;
       if (AddonManagerPrivate.hasUpgradeListener(install.addon.id)) {
         logger.info(`system add-on ${install.addon.id} has an upgrade listener, postponing upgrade set until restart`);
         resumeFn = () => {
           logger.info(`${install.addon.id} has resumed a previously postponed addon set`);
           install.installLocation.resumeAddonSet(installs);
         }
       }
-      yield install.postpone(resumeFn);
-    });
+      await install.postpone(resumeFn);
+    }
 
     let previousState;
 
     try {
       // All add-ons in position, create the new state and store it in prefs
       state = { schema: 1, directory: newDir.leafName, addons: {} };
       for (let addon of aAddons) {
         state.addons[addon.id] = {
           version: addon.version
         }
       }
 
-      previousState = this._loadAddonSet();
+      previousState = SystemAddonInstallLocation._loadAddonSet();
       this._saveAddonSet(state);
 
       let blockers = aAddons.filter(
         addon => AddonManagerPrivate.hasUpgradeListener(addon.id)
       );
 
       if (blockers.length > 0) {
-        yield waitForAllPromises(installs.map(postponeAddon));
+        await waitForAllPromises(installs.map(postponeAddon));
       } else {
-        yield waitForAllPromises(installs.map(installAddon));
+        await waitForAllPromises(installs.map(installAddon));
       }
     } catch (e) {
       // Roll back to previous upgrade set (if present) on restart.
       if (previousState) {
         this._saveAddonSet(previousState);
       }
       // Otherwise, roll back to built-in set on restart.
       // TODO try to do these restartlessly
       this.resetAddonSet();
 
       try {
-        yield OS.File.removeDir(newDir.path, { ignorePermissions: true });
+        await OS.File.removeDir(newDir.path, { ignorePermissions: true });
       } catch (e) {
         logger.warn(`Failed to remove failed system add-on directory ${newDir.path}.`, e);
       }
       throw e;
     }
-  }),
+  }
 
  /**
   * Resumes upgrade of a previously-delayed add-on set.
   */
-  resumeAddonSet: Task.async(function*(installs) {
-    let resumeAddon = Task.async(function*(install) {
+  async resumeAddonSet(installs) {
+    function resumeAddon(install) {
       install.state = AddonManager.STATE_DOWNLOADED;
       install.installLocation.releaseStagingDir();
       install.install();
-    });
+    }
 
     let blockers = installs.filter(
       install => AddonManagerPrivate.hasUpgradeListener(install.addon.id)
     );
 
     if (blockers.length > 1) {
       logger.warn("Attempted to resume system add-on install but upgrade blockers are still present");
     } else {
-      yield waitForAllPromises(installs.map(resumeAddon));
-    }
-  }),
+      await waitForAllPromises(installs.map(resumeAddon));
+    }
+  }
 
   /**
    * Returns a directory that is normally on the same filesystem as the rest of
    * the install location and can be used for temporarily storing files during
    * safe move operations. Calling this method will delete the existing trash
    * directory and its contents.
    *
    * @return an nsIFile
@@ -8912,17 +8919,17 @@ Object.assign(SystemAddonInstallLocation
       trashDirExists = false;
     } catch (e) {
       logger.warn("Failed to remove trash directory", e);
     }
     if (!trashDirExists)
       trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
 
     return trashDir;
-  },
+  }
 
   /**
    * Installs an add-on into the install location.
    *
    * @param  id
    *         The ID of the add-on to install
    * @param  source
    *         The source nsIFile to install from
@@ -8957,21 +8964,21 @@ Object.assign(SystemAddonInstallLocation
       newFile.lastModifiedTime = Date.now();
     } catch (e) {
       logger.warn("failed to set lastModifiedTime on " + newFile.path, e);
     }
     this._IDToFileMap[id] = newFile;
     XPIProvider._addURIMapping(id, newFile);
 
     return newFile;
-  },
+  }
 
   // old system add-on upgrade dirs get automatically removed
-  uninstallAddon: (aAddon) => {},
-});
+  uninstallAddon(aAddon) {}
+}
 
 /**
  * An object which identifies an install location for temporary add-ons.
  */
 const TemporaryInstallLocation = {
   locked: false,
   name: KEY_APP_TEMPORARY,
   scope: AddonManager.SCOPE_TEMPORARY,
@@ -8982,51 +8989,48 @@ const TemporaryInstallLocation = {
   getStagingDir: () => {},
 }
 
 /**
  * An object that identifies a registry install location for add-ons. The location
  * consists of a registry key which contains string values mapping ID to the
  * path where an add-on is installed
  *
- * @param  aName
- *         The string identifier of this Install Location.
- * @param  aRootKey
- *         The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey).
- * @param  scope
- *         The scope of add-ons installed in this location
  */
-function WinRegInstallLocation(aName, aRootKey, aScope) {
-  this.locked = true;
-  this._name = aName;
-  this._rootKey = aRootKey;
-  this._scope = aScope;
-  this._IDToFileMap = {};
-
-  let path = this._appKeyPath + "\\Extensions";
-  let key = Cc["@mozilla.org/windows-registry-key;1"].
-            createInstance(Ci.nsIWindowsRegKey);
-
-  // Reading the registry may throw an exception, and that's ok.  In error
-  // cases, we just leave ourselves in the empty state.
-  try {
-    key.open(this._rootKey, path, Ci.nsIWindowsRegKey.ACCESS_READ);
-  } catch (e) {
-    return;
-  }
-
-  this._readAddons(key);
-  key.close();
-}
-
-WinRegInstallLocation.prototype = {
-  _name: "",
-  _rootKey: null,
-  _scope: null,
-  _IDToFileMap: null,  // mapping from ID to nsIFile
+class WinRegInstallLocation extends DirectoryInstallLocation {
+  /**
+    * @param  aName
+    *         The string identifier of this Install Location.
+    * @param  aRootKey
+    *         The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey).
+    * @param  scope
+    *         The scope of add-ons installed in this location
+    */
+  constructor(aName, aRootKey, aScope) {
+    this.locked = true;
+    this._name = aName;
+    this._rootKey = aRootKey;
+    this._scope = aScope;
+    this._IDToFileMap = {};
+
+    let path = this._appKeyPath + "\\Extensions";
+    let key = Cc["@mozilla.org/windows-registry-key;1"].
+              createInstance(Ci.nsIWindowsRegKey);
+
+    // Reading the registry may throw an exception, and that's ok.  In error
+    // cases, we just leave ourselves in the empty state.
+    try {
+      key.open(this._rootKey, path, Ci.nsIWindowsRegKey.ACCESS_READ);
+    } catch (e) {
+      return;
+    }
+
+    this._readAddons(key);
+    key.close();
+  }
 
   /**
    * Retrieves the path of this Application's data key in the registry.
    */
   get _appKeyPath() {
     let appVendor = Services.appinfo.vendor;
     let appName = Services.appinfo.name;
 
@@ -9034,17 +9038,17 @@ WinRegInstallLocation.prototype = {
     if (AppConstants.MOZ_APP_NAME == "thunderbird" && appVendor == "")
       appVendor = "Mozilla";
 
     // XULRunner-based apps may intentionally not specify a vendor
     if (appVendor != "")
       appVendor += "\\";
 
     return "SOFTWARE\\" + appVendor + appName;
-  },
+  }
 
   /**
    * Read the registry and build a mapping between ID and path for each
    * installed add-on.
    *
    * @param  key
    *         The key that contains the ID to path mapping
    */
@@ -9058,50 +9062,50 @@ WinRegInstallLocation.prototype = {
       if (!file.exists()) {
         logger.warn("Ignoring missing add-on in " + file.path);
         continue;
       }
 
       this._IDToFileMap[id] = file;
       XPIProvider._addURIMapping(id, file);
     }
-  },
+  }
 
   /**
    * Gets the name of this install location.
    */
   get name() {
     return this._name;
-  },
+  }
 
   /**
    * Gets the scope of this install location.
    */
   get scope() {
     return this._scope;
-  },
+  }
 
   /**
    * Gets an array of nsIFiles for add-ons installed in this location.
    */
   getAddonLocations() {
     let locations = new Map();
     for (let id in this._IDToFileMap) {
       locations.set(id, this._IDToFileMap[id].clone());
     }
     return locations;
-  },
+  }
 
   /**
    * @see DirectoryInstallLocation
    */
   isLinkedAddon(aId) {
     return true;
   }
-};
+}
 
 var addonTypes = [
   new AddonManagerPrivate.AddonType("extension", URI_EXTENSION_STRINGS,
                                     STRING_TYPE_NAME,
                                     AddonManager.VIEW_TYPE_LIST, 4000,
                                     AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL),
   new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS,
                                     STRING_TYPE_NAME,
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
@@ -170,17 +170,17 @@ add_task(function* test_downgrade() {
 // Fake a mid-cycle install
 add_task(function* test_updated() {
   // Create a random dir to install into
   let dirname = makeUUID();
   FileUtils.getDir("ProfD", ["features", dirname], true);
   updatesDir.append(dirname);
 
   // Copy in the system add-ons
-    let file = do_get_file("data/system_addons/system2_2.xpi");
+  let file = do_get_file("data/system_addons/system2_2.xpi");
   file.copyTo(updatesDir, "system2@tests.mozilla.org.xpi");
   file = do_get_file("data/system_addons/system3_2.xpi");
   file.copyTo(updatesDir, "system3@tests.mozilla.org.xpi");
 
   // Inject it into the system set
   let addonSet = {
     schema: 1,
     directory: updatesDir.leafName,
@@ -375,17 +375,17 @@ add_task(function* test_bad_app_cert() {
   ];
 
   yield check_installed(conditions);
 
   yield promiseShutdownManager();
 });
 
 // A failed upgrade should revert to the default set.
-add_task(function* test_updated() {
+add_task(function* test_updated_bad_update_set() {
   // Create a random dir to install into
   let dirname = makeUUID();
   FileUtils.getDir("ProfD", ["features", dirname], true);
   updatesDir.append(dirname);
 
   // Copy in the system add-ons
   let file = do_get_file("data/system_addons/system2_2.xpi");
   file.copyTo(updatesDir, "system2@tests.mozilla.org.xpi");