Bug 959420 - Refactor saveApps, getManifestFor, readManifests, writeFile functions to use promises. r=fabrice
authorMarco Castelluccio <mar.castelluccio@studenti.unina.it>
Thu, 16 Jan 2014 16:26:24 -0800
changeset 163925 75a010e3f038064cd1418a59e0ba2e69ebbd452b
parent 163924 8c5aed160d8f56f2edf1c47d6c0f80b34e5a4623
child 163926 f9f2a954785820c2ce387a4b81f31ff66afe5da4
push id38584
push usercbook@mozilla.com
push dateFri, 17 Jan 2014 10:04:30 +0000
treeherdermozilla-inbound@28a9d7e2416f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs959420
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 959420 - Refactor saveApps, getManifestFor, readManifests, writeFile functions to use promises. r=fabrice
b2g/chrome/content/shell.js
dom/apps/src/Webapps.jsm
toolkit/devtools/server/actors/webapps.js
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -811,17 +811,17 @@ var AlertsHelper = {
   registerListener: function alert_registerListener(alertId, cookie, alertListener) {
     this._listeners[alertId] = { observer: alertListener, cookie: cookie };
   },
 
   registerAppListener: function alert_registerAppListener(uid, listener) {
     this._listeners[uid] = listener;
 
     let app = DOMApplicationRegistry.getAppByManifestURL(listener.manifestURL);
-    DOMApplicationRegistry.getManifestFor(app.manifestURL, function(manifest) {
+    DOMApplicationRegistry.getManifestFor(app.manifestURL).then((manifest) => {
       let helper = new ManifestHelper(manifest, app.origin);
       let getNotificationURLFor = function(messages) {
         if (!messages)
           return null;
 
         for (let i = 0; i < messages.length; i++) {
           let message = messages[i];
           if (message === "notification") {
@@ -868,17 +868,17 @@ var AlertsHelper = {
     if (!manifestUrl || !manifestUrl.length) {
       send(null, null);
       return;
     }
 
     // If we have a manifest URL, get the icon and title from the manifest
     // to prevent spoofing.
     let app = DOMApplicationRegistry.getAppByManifestURL(manifestUrl);
-    DOMApplicationRegistry.getManifestFor(manifestUrl, function(aManifest) {
+    DOMApplicationRegistry.getManifestFor(manifestUrl).then((aManifest) => {
       let helper = new ManifestHelper(aManifest, app.origin);
       send(helper.name, helper.iconURLForSize(128));
     });
   },
 
   showAlertNotification: function alert_showAlertNotification(imageUrl,
                                                               title,
                                                               text,
@@ -967,17 +967,17 @@ var WebappsHelper = {
   },
 
   observe: function webapps_observe(subject, topic, data) {
     let json = JSON.parse(data);
     json.mm = subject;
 
     switch(topic) {
       case "webapps-launch":
-        DOMApplicationRegistry.getManifestFor(json.manifestURL, function(aManifest) {
+        DOMApplicationRegistry.getManifestFor(json.manifestURL).then((aManifest) => {
           if (!aManifest)
             return;
 
           let manifest = new ManifestHelper(aManifest, json.origin);
           let payload = {
             __exposedProps__: {
               timestamp: "r",
               url: "r",
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -123,16 +123,17 @@ XPCOMUtils.defineLazyGetter(this, "updat
 // We'll use this to identify privileged apps that have been preinstalled
 // For those apps we'll set
 // STORE_ID_PENDING_PREFIX + installOrigin
 // as the storeID. This ensures it's unique and can't be set from a legit
 // store even by error.
 const STORE_ID_PENDING_PREFIX = "#unknownID#";
 
 this.DOMApplicationRegistry = {
+  // Path to the webapps.json file where we store the registry data.
   appsFile: null,
   webapps: { },
   children: [ ],
   allAppsLaunchable: false,
 
   init: function() {
     this.messages = ["Webapps:Install", "Webapps:Uninstall",
                      "Webapps:GetSelf", "Webapps:CheckInstalled",
@@ -155,97 +156,92 @@ this.DOMApplicationRegistry = {
     cpmm.addMessageListener("Activities:Register:OK", this);
 
     Services.obs.addObserver(this, "xpcom-shutdown", false);
     Services.obs.addObserver(this, "memory-pressure", false);
 
     AppDownloadManager.registerCancelFunction(this.cancelDownload.bind(this));
 
     this.appsFile = FileUtils.getFile(DIRECTORY_NAME,
-                                      ["webapps", "webapps.json"], true);
+                                      ["webapps", "webapps.json"], true).path;
 
     this.loadAndUpdateApps();
   },
 
   // loads the current registry, that could be empty on first run.
-  // aNext() is called after we load the current webapps list.
-  loadCurrentRegistry: function loadCurrentRegistry(aNext) {
-    let file = FileUtils.getFile(DIRECTORY_NAME, ["webapps", "webapps.json"], false);
-    if (file && file.exists()) {
-      this._loadJSONAsync(file, (function loadRegistry(aData) {
-        if (aData) {
-          this.webapps = aData;
-          let appDir = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], false);
-          for (let id in this.webapps) {
-            let app = this.webapps[id];
-            if (!app) {
-              delete this.webapps[id];
-              continue;
-            }
-
-            app.id = id;
-
-            // Make sure we have a localId
-            if (app.localId === undefined) {
-              app.localId = this._nextLocalId();
-            }
-
-            if (app.basePath === undefined) {
-              app.basePath = appDir.path;
-            }
-
-            // Default to removable apps.
-            if (app.removable === undefined) {
-              app.removable = true;
-            }
-
-            // Default to a non privileged status.
-            if (app.appStatus === undefined) {
-              app.appStatus = Ci.nsIPrincipal.APP_STATUS_INSTALLED;
-            }
-
-            // Default to NO_APP_ID and not in browser.
-            if (app.installerAppId === undefined) {
-              app.installerAppId = Ci.nsIScriptSecurityManager.NO_APP_ID;
-            }
-            if (app.installerIsBrowser === undefined) {
-              app.installerIsBrowser = false;
-            }
-
-            // Default installState to "installed", and reset if we shutdown
-            // during an update.
-            if (app.installState === undefined ||
-                app.installState === "updating") {
-              app.installState = "installed";
-            }
-
-            // Default storeId to "" and storeVersion to 0
-            if (this.webapps[id].storeId === undefined) {
-              this.webapps[id].storeId = "";
-            }
-            if (this.webapps[id].storeVersion === undefined) {
-              this.webapps[id].storeVersion = 0;
-            }
-
-            // Default role to "".
-            if (this.webapps[id].role === undefined) {
-              this.webapps[id].role = "";
-            }
-
-            // At startup we can't be downloading, and the $TMP directory
-            // will be empty so we can't just apply a staged update.
-            app.downloading = false;
-            app.readyToApplyDownload = false;
-          };
+  loadCurrentRegistry: function() {
+    return this._loadJSONAsync(this.appsFile).then((aData) => {
+      if (!aData) {
+        return;
+      }
+
+      this.webapps = aData;
+      let appDir = OS.Path.dirname(this.appsFile);
+      for (let id in this.webapps) {
+        let app = this.webapps[id];
+        if (!app) {
+          delete this.webapps[id];
+          continue;
+        }
+
+        app.id = id;
+
+        // Make sure we have a localId
+        if (app.localId === undefined) {
+          app.localId = this._nextLocalId();
+        }
+
+        if (app.basePath === undefined) {
+          app.basePath = appDir;
+        }
+
+        // Default to removable apps.
+        if (app.removable === undefined) {
+          app.removable = true;
+        }
+
+        // Default to a non privileged status.
+        if (app.appStatus === undefined) {
+          app.appStatus = Ci.nsIPrincipal.APP_STATUS_INSTALLED;
         }
-        aNext();
-      }).bind(this));
-    } else {
-      aNext();
-    }
+
+        // Default to NO_APP_ID and not in browser.
+        if (app.installerAppId === undefined) {
+          app.installerAppId = Ci.nsIScriptSecurityManager.NO_APP_ID;
+        }
+        if (app.installerIsBrowser === undefined) {
+          app.installerIsBrowser = false;
+        }
+
+        // Default installState to "installed", and reset if we shutdown
+        // during an update.
+        if (app.installState === undefined ||
+            app.installState === "updating") {
+          app.installState = "installed";
+        }
+
+        // Default storeId to "" and storeVersion to 0
+        if (this.webapps[id].storeId === undefined) {
+          this.webapps[id].storeId = "";
+        }
+        if (this.webapps[id].storeVersion === undefined) {
+          this.webapps[id].storeVersion = 0;
+        }
+
+        // Default role to "".
+        if (this.webapps[id].role === undefined) {
+          this.webapps[id].role = "";
+        }
+
+        // At startup we can't be downloading, and the $TMP directory
+        // will be empty so we can't just apply a staged update.
+        app.downloading = false;
+        app.readyToApplyDownload = false;
+      }
+    });
   },
 
   // Notify we are starting with registering apps.
   notifyAppsRegistryStart: function notifyAppsRegistryStart() {
     Services.obs.notifyObservers(this, "webapps-registry-start", null);
   },
 
   // Notify we are done with registering apps and save a copy of the registry.
@@ -268,88 +264,88 @@ this.DOMApplicationRegistry = {
           !isAbsoluteURI(redirect.to)) {
         res.push(redirect);
       }
     }
     return res.length > 0 ? res : null;
   },
 
   // Registers all the activities and system messages.
-  registerAppsHandlers: function registerAppsHandlers(aRunUpdate) {
+  registerAppsHandlers: function(aRunUpdate) {
     this.notifyAppsRegistryStart();
     let ids = [];
     for (let id in this.webapps) {
       ids.push({ id: id });
     }
     if (supportSystemMessages()) {
       this._processManifestForIds(ids, aRunUpdate);
     } else {
       // Read the CSPs and roles. If MOZ_SYS_MSG is defined this is done on
       // _processManifestForIds so as to not reading the manifests
       // twice
-      this._readManifests(ids, (function readCSPs(aResults) {
-        aResults.forEach(function registerManifest(aResult) {
+      this._readManifests(ids).then((aResults) => {
+        aResults.forEach((aResult) => {
           if (!aResult.manifest) {
             // If we can't load the manifest, we probably have a corrupted
             // registry. We delete the app since we can't do anything with it.
             delete this.webapps[aResult.id];
             return;
           }
           let app = this.webapps[aResult.id];
           app.csp = aResult.manifest.csp || "";
           app.role = aResult.manifest.role || "";
           if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
             app.redirects = this.sanitizeRedirects(aResult.redirects);
           }
-        }, this);
-      }).bind(this));
+        });
+      });
 
       // Nothing else to do but notifying we're ready.
       this.notifyAppsRegistryReady();
     }
   },
 
   updateDataStoreForApp: function(aId) {
     if (!this.webapps[aId]) {
       return;
     }
 
     // Create or Update the DataStore for this app
-    this._readManifests([{ id: aId }], (function(aResult) {
+    this._readManifests([{ id: aId }]).then((aResult) => {
       let app = this.webapps[aId];
       this.updateDataStore(app.localId, app.origin, app.manifestURL,
                            aResult[0].manifest, app.appStatus);
-    }).bind(this));
+    });
   },
 
-  updatePermissionsForApp: function updatePermissionsForApp(aId) {
+  updatePermissionsForApp: function(aId) {
     if (!this.webapps[aId]) {
       return;
     }
 
     // Install the permissions for this app, as if we were updating
     // to cleanup the old ones if needed.
     // TODO It's not clear what this should do when there are multiple profiles.
     if (supportUseCurrentProfile()) {
-      this._readManifests([{ id: aId }], (function(aResult) {
+      this._readManifests([{ id: aId }]).then((aResult) => {
         let data = aResult[0];
         PermissionsInstaller.installPermissions({
           manifest: data.manifest,
           manifestURL: this.webapps[aId].manifestURL,
           origin: this.webapps[aId].origin
         }, true, function() {
           debug("Error installing permissions for " + aId);
         });
-      }).bind(this));
+      });
     }
   },
 
-  updateOfflineCacheForApp: function updateOfflineCacheForApp(aId) {
+  updateOfflineCacheForApp: function(aId) {
     let app = this.webapps[aId];
-    this._readManifests([{ id: aId }], function(aResult) {
+    this._readManifests([{ id: aId }]).then((aResult) => {
       let manifest = new ManifestHelper(aResult[0].manifest, app.origin);
       OfflineCacheInstaller.installCache({
         cachePath: app.cachePath,
         appId: aId,
         origin: Services.io.newURI(app.origin, null, null),
         localId: app.localId,
         appcache_path: manifest.fullAppcachePath()
       });
@@ -408,18 +404,17 @@ this.DOMApplicationRegistry = {
           file.copyTo(destDir, aFile);
         } catch(e) {
           debug("Error: Failed to copy " + file.path + " to " + destDir.path);
         }
       });
 
     app.installState = "installed";
     app.cachePath = app.basePath;
-    app.basePath = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true)
-                            .path;
+    app.basePath = OS.Path.dirname(this.appsFile);
 
     if (!isPackage) {
       return;
     }
 
     app.origin = "app://" + aId;
 
     // Do this for all preinstalled apps... we can't know at this
@@ -479,73 +474,72 @@ this.DOMApplicationRegistry = {
 
   // Implements the core of bug 787439
   // if at first run, go through these steps:
   //   a. load the core apps registry.
   //   b. uninstall any core app from the current registry but not in the
   //      new core apps registry.
   //   c. for all apps in the new core registry, install them if they are not
   //      yet in the current registry, and run installPermissions()
-  installSystemApps: function installSystemApps(aNext) {
-    let file;
-    try {
-      file = FileUtils.getFile("coreAppsDir", ["webapps", "webapps.json"], false);
-    } catch(e) { }
-
-    if (file && file.exists()) {
+  installSystemApps: function() {
+    return Task.spawn(function() {
+      let file;
+      try {
+        file = FileUtils.getFile("coreAppsDir", ["webapps", "webapps.json"], false);
+      } catch(e) { }
+
+      if (!file || !file.exists()) {
+        return;
+      }
+
       // a
-      this._loadJSONAsync(file, (function loadCoreRegistry(aData) {
-        if (!aData) {
-          aNext();
-          return;
-        }
-
-        // b : core apps are not removable.
-        for (let id in this.webapps) {
-          if (id in aData || this.webapps[id].removable)
-            continue;
-          // Remove the permissions, cookies and private data for this app.
-          let localId = this.webapps[id].localId;
-          let permMgr = Cc["@mozilla.org/permissionmanager;1"]
-                          .getService(Ci.nsIPermissionManager);
-          permMgr.removePermissionsForApp(localId, false);
-          Services.cookies.removeCookiesForApp(localId, false);
-          this._clearPrivateData(localId, false);
-          delete this.webapps[id];
-        }
-
-        let appDir = FileUtils.getDir("coreAppsDir", ["webapps"], false);
-        // c
-        for (let id in aData) {
-          // Core apps have ids matching their domain name (eg: dialer.gaiamobile.org)
-          // Use that property to check if they are new or not.
-          if (!(id in this.webapps)) {
-            this.webapps[id] = aData[id];
-            this.webapps[id].basePath = appDir.path;
-
-            this.webapps[id].id = id;
-
-            // Create a new localId.
-            this.webapps[id].localId = this._nextLocalId();
-
-            // Core apps are not removable.
-            if (this.webapps[id].removable === undefined) {
-              this.webapps[id].removable = false;
-            }
+      let data = yield this._loadJSONAsync(file.path);
+      if (!data) {
+        return;
+      }
+
+      // b : core apps are not removable.
+      for (let id in this.webapps) {
+        if (id in data || this.webapps[id].removable)
+          continue;
+        // Remove the permissions, cookies and private data for this app.
+        let localId = this.webapps[id].localId;
+        let permMgr = Cc["@mozilla.org/permissionmanager;1"]
+                        .getService(Ci.nsIPermissionManager);
+        permMgr.removePermissionsForApp(localId, false);
+        Services.cookies.removeCookiesForApp(localId, false);
+        this._clearPrivateData(localId, false);
+        delete this.webapps[id];
+      }
+
+      let appDir = FileUtils.getDir("coreAppsDir", ["webapps"], false);
+      // c
+      for (let id in data) {
+        // Core apps have ids matching their domain name (eg: dialer.gaiamobile.org)
+        // Use that property to check if they are new or not.
+        if (!(id in this.webapps)) {
+          this.webapps[id] = data[id];
+          this.webapps[id].basePath = appDir.path;
+
+          this.webapps[id].id = id;
+
+          // Create a new localId.
+          this.webapps[id].localId = this._nextLocalId();
+
+          // Core apps are not removable.
+          if (this.webapps[id].removable === undefined) {
+            this.webapps[id].removable = false;
           }
         }
-        aNext();
-      }).bind(this));
-    } else {
-      aNext();
-    }
+      }
+    }.bind(this)).then(null, Cu.reportError);
   },
 
 #ifdef MOZ_WIDGET_GONK
-  fixIndexedDb: function fixIndexedDb() {
+  fixIndexedDb: function() {
     debug("Fixing indexedDb folder names");
     let idbDir = FileUtils.getDir("indexedDBPDir", ["indexedDB"]);
 
     if (!idbDir.exists() || !idbDir.isDirectory()) {
       return;
     }
 
     let re = /^(\d+)\+(.*)\+(f|t)$/;
@@ -562,27 +556,33 @@ this.DOMApplicationRegistry = {
         try {
           entry.moveTo(idbDir, newName);
         } catch(e) { }
       }
     }
   },
 #endif
 
-  loadAndUpdateApps: function loadAndUpdateApps() {
-    let runUpdate = AppsUtils.isFirstRun(Services.prefs);
+  loadAndUpdateApps: function() {
+    return Task.spawn(function() {
+      let runUpdate = AppsUtils.isFirstRun(Services.prefs);
 
 #ifdef MOZ_WIDGET_GONK
-    if (runUpdate) {
-      this.fixIndexedDb();
-    }
+      if (runUpdate) {
+        this.fixIndexedDb();
+      }
 #endif
 
-    let onAppsLoaded = (function onAppsLoaded() {
+      yield this.loadCurrentRegistry();
+
       if (runUpdate) {
+#ifdef MOZ_WIDGET_GONK
+        yield this.installSystemApps();
+#endif
+
         // At first run, install preloaded apps and set up their permissions.
         for (let id in this.webapps) {
           this.installPreinstalledApp(id);
           this.removeIfHttpsDuplicate(id);
           if (!this.webapps[id]) {
             continue;
           }
           this.updateOfflineCacheForApp(id);
@@ -594,29 +594,17 @@ this.DOMApplicationRegistry = {
       }
 
       // DataStores must be initialized at startup.
       for (let id in this.webapps) {
         this.updateDataStoreForApp(id);
       }
 
       this.registerAppsHandlers(runUpdate);
-    }).bind(this);
-
-    this.loadCurrentRegistry((function() {
-#ifdef MOZ_WIDGET_GONK
-      // if first run, merge the system apps.
-      if (runUpdate)
-        this.installSystemApps(onAppsLoaded);
-      else
-        onAppsLoaded();
-#else
-      onAppsLoaded();
-#endif
-    }).bind(this));
+    }.bind(this)).then(null, Cu.reportError);
   },
 
   updateDataStore: function(aId, aOrigin, aManifestURL, aManifest, aAppStatus) {
     // Just Certified Apps can use DataStores
     let prefName = "dom.testing.datastore_enabled_for_hosted_apps";
     if (aAppStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
         (Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID ||
          !Services.prefs.getBoolPref(prefName))) {
@@ -924,19 +912,19 @@ this.DOMApplicationRegistry = {
 
   // Better to directly use |_unregisterActivitiesForApps()| if we have
   // multiple apps to be unregistered for activities.
   _unregisterActivities: function(aManifest, aApp) {
     this._unregisterActivitiesForApps([{ manifest: aManifest, app: aApp }]);
   },
 
   _processManifestForIds: function(aIds, aRunUpdate) {
-    this._readManifests(aIds, (function registerManifests(aResults) {
+    this._readManifests(aIds).then((aResults) => {
       let appsToRegister = [];
-      aResults.forEach(function registerManifest(aResult) {
+      aResults.forEach((aResult) => {
         let app = this.webapps[aResult.id];
         let manifest = aResult.manifest;
         if (!manifest) {
           // If we can't load the manifest, we probably have a corrupted
           // registry. We delete the app since we can't do anything with it.
           delete this.webapps[aResult.id];
           return;
         }
@@ -944,74 +932,75 @@ this.DOMApplicationRegistry = {
         app.csp = manifest.csp || "";
         app.role = manifest.role || "";
         if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
           app.redirects = this.sanitizeRedirects(manifest.redirects);
         }
         this._registerSystemMessages(manifest, app);
         this._registerInterAppConnections(manifest, app);
         appsToRegister.push({ manifest: manifest, app: app });
-      }, this);
+      });
       this._registerActivitiesForApps(appsToRegister, aRunUpdate);
-    }).bind(this));
+    });
   },
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "xpcom-shutdown") {
       this.messages.forEach((function(msgName) {
         ppmm.removeMessageListener(msgName, this);
       }).bind(this));
       Services.obs.removeObserver(this, "xpcom-shutdown");
       cpmm = null;
       ppmm = null;
     } else if (aTopic == "memory-pressure") {
       // Clear the manifest cache on memory pressure.
       this._manifestCache = {};
     }
   },
 
-  _loadJSONAsync: function(aFile, aCallback) {
+  _loadJSONAsync: function(aPath) {
+    let deferred = Promise.defer();
+
     try {
-      let channel = NetUtil.newChannel(aFile);
+      let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+      file.initWithPath(aPath);
+      let channel = NetUtil.newChannel(file);
       channel.contentType = "application/json";
       NetUtil.asyncFetch(channel, function(aStream, aResult) {
         if (!Components.isSuccessCode(aResult)) {
           Cu.reportError("DOMApplicationRegistry: Could not read from json file "
-                         + aFile.path);
-          if (aCallback)
-            aCallback(null);
-          return;
+                         + aPath);
+          deferred.resolve(null);
         }
 
-        // Read json file into a string
-        let data = null;
         try {
           // Obtain a converter to read from a UTF-8 encoded input stream.
           let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                           .createInstance(Ci.nsIScriptableUnicodeConverter);
           converter.charset = "UTF-8";
 
-          data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
+          // Read json file into a string
+          let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
                                                             aStream.available()) || ""));
           aStream.close();
-          if (aCallback)
-            aCallback(data);
+
+          deferred.resolve(data);
         } catch (ex) {
           Cu.reportError("DOMApplicationRegistry: Could not parse JSON: " +
-                         aFile.path + " " + ex + "\n" + ex.stack);
-          if (aCallback)
-            aCallback(null);
+                         aPath + " " + ex + "\n" + ex.stack);
+          deferred.resolve(null);
         }
       });
     } catch (ex) {
       Cu.reportError("DOMApplicationRegistry: Could not read from " +
-                     aFile.path + " : " + ex + "\n" + ex.stack);
-      if (aCallback)
-        aCallback(null);
+                     aPath + " : " + ex + "\n" + ex.stack);
+      deferred.resolve(null);
     }
+
+    return deferred.promise;
   },
 
   addMessageListener: function(aMsgNames, aApp, aMm) {
     aMsgNames.forEach(function (aMsgName) {
       let man = aApp && aApp.manifestURL;
       if (!(aMsgName in this.children)) {
         this.children[aMsgName] = [];
       }
@@ -1210,33 +1199,23 @@ this.DOMApplicationRegistry = {
       mmRef.mm.sendAsyncMessage(aMsgName, aContent);
     });
   },
 
   _getAppDir: function(aId) {
     return FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
   },
 
-  _writeFile: function _writeFile(aFile, aData, aCallback) {
-    debug("Saving " + aFile.path);
-    // Initialize the file output stream.
-    let ostream = FileUtils.openSafeFileOutputStream(aFile);
-
-    // Obtain a converter to convert our data to a UTF-8 encoded input stream.
-    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
-                    .createInstance(Ci.nsIScriptableUnicodeConverter);
-    converter.charset = "UTF-8";
-
-    // Asynchronously copy the data to the file.
-    let istream = converter.convertToInputStream(aData);
-    NetUtil.asyncCopy(istream, ostream, function(rc) {
-      if (aCallback) {
-        aCallback();
-      }
-    });
+  _writeFile: function(aPath, aData) {
+    debug("Saving " + aPath);
+
+    return OS.File.writeAtomic(aPath,
+                               new TextEncoder().encode(aData),
+                               { tmpPath: aPath + ".tmp" })
+                  .then(null, Cu.reportError);
   },
 
   doLaunch: function (aData, aMm) {
     this.launch(
       aData.manifestURL,
       aData.startPoint,
       aData.timestamp,
       function onsuccess() {
@@ -1300,31 +1279,31 @@ this.DOMApplicationRegistry = {
     } else if (download.channel) {
       try {
         download.channel.cancel(Cr.NS_BINDING_ABORTED);
       } catch(e) { }
     } else {
       return;
     }
 
-    this._saveApps((function() {
+    this._saveApps().then(() => {
       this.broadcastMessage("Webapps:UpdateState", {
         app: {
           progress: 0,
           installState: download.previousState,
           downloading: false
         },
         error: error,
         manifestURL: app.manifestURL,
       })
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL: app.manifestURL
       });
-    }).bind(this));
+    });
     AppDownloadManager.remove(aManifestURL);
   },
 
   startDownload: function startDownload(aManifestURL) {
     debug("startDownload for " + aManifestURL);
     let id = this._appIdForManifestURL(aManifestURL);
     let app = this.webapps[id];
     if (!app) {
@@ -1366,104 +1345,100 @@ this.DOMApplicationRegistry = {
                                  ["webapps", id,
                                   isUpdate ? "staged-update.webapp"
                                            : "update.webapp"],
                                  true);
 
     if (!file.exists()) {
       // This is a hosted app, let's check if it has an appcache
       // and download it.
-      this._readManifests([{ id: id }], (function readManifest(aResults) {
+      this._readManifests([{ id: id }]).then((aResults) => {
         let jsonManifest = aResults[0].manifest;
         let manifest = new ManifestHelper(jsonManifest, app.origin);
 
         if (manifest.appcache_path) {
           debug("appcache found");
           this.startOfflineCacheDownload(manifest, app, null, isUpdate);
         } else {
           // Hosted app with no appcache, nothing to do, but we fire a
           // downloaded event.
           debug("No appcache found, sending 'downloaded' for " + aManifestURL);
           app.downloadAvailable = false;
-          DOMApplicationRegistry._saveApps(function() {
-            DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+          this._saveApps().then(() => {
+            this.broadcastMessage("Webapps:UpdateState", {
               app: app,
               manifest: jsonManifest,
               manifestURL: aManifestURL
             });
-            DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
+            this.broadcastMessage("Webapps:FireEvent", {
               eventType: "downloadsuccess",
               manifestURL: aManifestURL
             });
           });
         }
-      }).bind(this));
+      });
 
       return;
     }
 
-    this._loadJSONAsync(file, (function(aJSON) {
+    this._loadJSONAsync(file.path).then((aJSON) => {
       if (!aJSON) {
         debug("startDownload: No update manifest found at " + file.path + " " +
               aManifestURL);
         return;
       }
 
       let manifest = new ManifestHelper(aJSON, app.installOrigin);
       this.downloadPackage(manifest, {
           manifestURL: aManifestURL,
           origin: app.origin,
           installOrigin: app.installOrigin,
           downloadSize: app.downloadSize
         }, isUpdate).then(function([aId, aManifest]) {
           // Success! Keep the zip in of TmpD, we'll move it out when
           // applyDownload() will be called.
-          let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
-
           // Save the manifest in TmpD also
-          let manFile = tmpDir.clone();
-          manFile.append("manifest.webapp");
-          DOMApplicationRegistry._writeFile(manFile,
-                                            JSON.stringify(aManifest),
-                                            function() { });
+          let manFile = OS.Path.join(OS.Constants.Path.tmpDir, "webapps", aId,
+                                     "manifest.webapp");
+          DOMApplicationRegistry._writeFile(manFile, JSON.stringify(aManifest));
 
           app = DOMApplicationRegistry.webapps[aId];
           // Set state and fire events.
           app.downloading = false;
           app.downloadAvailable = false;
           app.readyToApplyDownload = true;
           app.updateTime = Date.now();
-          DOMApplicationRegistry._saveApps(function() {
+          DOMApplicationRegistry._saveApps().then(() => {
             DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
               app: app,
               manifestURL: aManifestURL
             });
             DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
               eventType: "downloadsuccess",
               manifestURL: aManifestURL
             });
             if (app.installState == "pending") {
               // We restarted a failed download, apply it automatically.
               DOMApplicationRegistry.applyDownload(aManifestURL);
             }
           });
         });
-    }).bind(this));
+    });
   },
 
   applyDownload: function applyDownload(aManifestURL) {
     debug("applyDownload for " + aManifestURL);
     let id = this._appIdForManifestURL(aManifestURL);
     let app = this.webapps[id];
     if (!app || (app && !app.readyToApplyDownload)) {
       return;
     }
 
     // We need to get the old manifest to unregister web activities.
-    this.getManifestFor(aManifestURL, (function(aOldManifest) {
+    this.getManifestFor(aManifestURL).then((aOldManifest) => {
       // Move the application.zip and manifest.webapp files out of TmpD
       let tmpDir = FileUtils.getDir("TmpD", ["webapps", id], true, true);
       let manFile = tmpDir.clone();
       manFile.append("manifest.webapp");
       let appFile = tmpDir.clone();
       appFile.append("application.zip");
 
       let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
@@ -1491,34 +1466,34 @@ this.DOMApplicationRegistry = {
 
       // Flush the zip reader cache to make sure we use the new application.zip
       // when re-launching the application.
       let zipFile = dir.clone();
       zipFile.append("application.zip");
       Services.obs.notifyObservers(zipFile, "flush-cache-entry", null);
 
       // Get the manifest, and set properties.
-      this.getManifestFor(aManifestURL, (function(aData) {
+      this.getManifestFor(aManifestURL).then((aData) => {
         app.downloading = false;
         app.downloadAvailable = false;
         app.downloadSize = 0;
         app.installState = "installed";
         app.readyToApplyDownload = false;
 
         // Update the staged properties.
         if (app.staged) {
           for (let prop in app.staged) {
             app[prop] = app.staged[prop];
           }
           delete app.staged;
         }
 
         delete app.retryingDownload;
 
-        this._saveApps((function() {
+        this._saveApps().then(() => {
           // Update the handlers and permissions for this app.
           this.updateAppHandlers(aOldManifest, aData, app);
           if (supportUseCurrentProfile()) {
             PermissionsInstaller.installPermissions(
               { manifest: aData,
                 origin: app.origin,
                 manifestURL: app.manifestURL },
               true);
@@ -1529,19 +1504,19 @@ this.DOMApplicationRegistry = {
             app: app,
             manifest: aData,
             manifestURL: app.manifestURL
           });
           this.broadcastMessage("Webapps:FireEvent", {
             eventType: "downloadapplied",
             manifestURL: app.manifestURL
           });
-        }).bind(this));
-      }).bind(this));
-    }).bind(this));
+        });
+      });
+    });
   },
 
   startOfflineCacheDownload: function(aManifest, aApp, aProfileDir, aIsUpdate) {
     if (!aManifest.appcache_path) {
       return;
     }
 
     // If the manifest has an appcache_path property, use it to populate the
@@ -1556,17 +1531,17 @@ this.DOMApplicationRegistry = {
     if (aIsUpdate) {
       aApp.installState = "updating";
     }
 
     // We set the 'downloading' flag and update the apps registry right before
     // starting the app download/update.
     aApp.downloading = true;
     aApp.progress = 0;
-    DOMApplicationRegistry._saveApps((function() {
+    DOMApplicationRegistry._saveApps().then(() => {
       DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
         app: {
           downloading: true,
           installState: aApp.installState,
           progress: 0
         },
         manifestURL: aApp.manifestURL
       });
@@ -1580,17 +1555,17 @@ this.DOMApplicationRegistry = {
         cacheUpdate: cacheUpdate,
         appId: this._appIdForManifestURL(aApp.manifestURL),
         previousState: aIsUpdate ? "installed" : "pending"
       };
       AppDownloadManager.add(aApp.manifestURL, download);
 
       cacheUpdate.addObserver(new AppcacheObserver(aApp), false);
 
-    }).bind(this));
+    });
   },
 
   // Returns the MD5 hash of the manifest.
   computeManifestHash: function(aManifest) {
     return AppsUtils.computeHash(JSON.stringify(aManifest));
   },
 
   // Updates the redirect mapping, activities and system message handlers.
@@ -1632,33 +1607,32 @@ this.DOMApplicationRegistry = {
       // if the app manifestURL has a app:// scheme, we can't have an
       // update.
       if (app.manifestURL.startsWith("app://")) {
         aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
         return;
       }
 
       // Store the new update manifest.
-      let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
-      let manFile = dir.clone();
-      manFile.append("staged-update.webapp");
-      this._writeFile(manFile, JSON.stringify(aManifest), function() { });
+      let dir = this._getAppDir(id).path;
+      let manFile = OS.Path.join(dir, "staged-update.webapp");
+      this._writeFile(manFile, JSON.stringify(aManifest));
 
       let manifest = new ManifestHelper(aManifest, app.manifestURL);
       // A package is available: set downloadAvailable to fire the matching
       // event.
       app.downloadAvailable = true;
       app.downloadSize = manifest.size;
       app.updateManifest = aManifest;
-      DOMApplicationRegistry._saveApps(function() {
-        DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+      this._saveApps().then(() => {
+        this.broadcastMessage("Webapps:UpdateState", {
           app: app,
           manifestURL: app.manifestURL
         });
-        DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
+        this.broadcastMessage("Webapps:FireEvent", {
           eventType: "downloadavailable",
           manifestURL: app.manifestURL,
           requestID: aData.requestID
         });
       });
     }
 
     // A hosted app is updated if the app manifest or the appcache needs
@@ -1677,20 +1651,19 @@ this.DOMApplicationRegistry = {
 
       app.manifest = aNewManifest || aOldManifest;
 
       let manifest;
       if (aNewManifest) {
         this.updateAppHandlers(aOldManifest, aNewManifest, app);
 
         // Store the new manifest.
-        let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
-        let manFile = dir.clone();
-        manFile.append("manifest.webapp");
-        this._writeFile(manFile, JSON.stringify(aNewManifest), function() { });
+        let dir = this._getAppDir(id).path;
+        let manFile = OS.Path.join(dir, "manifest.webapp");
+        this._writeFile(manFile, JSON.stringify(aNewManifest));
         manifest = new ManifestHelper(aNewManifest, app.origin);
 
         if (supportUseCurrentProfile()) {
           // Update the permissions for this app.
           PermissionsInstaller.installPermissions({
             manifest: app.manifest,
             origin: app.origin,
             manifestURL: aData.manifestURL
@@ -1705,17 +1678,17 @@ this.DOMApplicationRegistry = {
         app.role = manifest.role || "";
         app.updateTime = Date.now();
       } else {
         manifest = new ManifestHelper(aOldManifest, app.origin);
       }
 
       // Update the registry.
       this.webapps[id] = app;
-      this._saveApps(function() {
+      this._saveApps().then(() => {
         let reg = DOMApplicationRegistry;
         if (!manifest.appcache_path) {
           reg.broadcastMessage("Webapps:UpdateState", {
             app: app,
             manifest: app.manifest,
             manifestURL: app.manifestURL
           });
           reg.broadcastMessage("Webapps:FireEvent", {
@@ -1729,17 +1702,17 @@ this.DOMApplicationRegistry = {
           let updateObserver = {
             observe: function(aSubject, aTopic, aObsData) {
               debug("updateHostedApp: updateSvc.checkForUpdate return for " +
                     app.manifestURL + " - event is " + aTopic);
               let eventType =
                 aTopic == "offline-cache-update-available" ? "downloadavailable"
                                                            : "downloadapplied";
               app.downloadAvailable = (eventType == "downloadavailable");
-              reg._saveApps(function() {
+              reg._saveApps().then(() => {
                 reg.broadcastMessage("Webapps:UpdateState", {
                   app: app,
                   manifest: app.manifest,
                   manifestURL: app.manifestURL
                 });
                 reg.broadcastMessage("Webapps:FireEvent", {
                   eventType: eventType,
                   manifestURL: app.manifestURL,
@@ -1790,39 +1763,39 @@ this.DOMApplicationRegistry = {
       // Bail out for packaged apps.
       if (app.origin.startsWith("app://")) {
         aData.error = "NOT_UPDATABLE";
         aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
         return;
       }
 
       // We need the manifest to check if we have an appcache.
-      this._readManifests([{ id: id }], function(aResult) {
+      this._readManifests([{ id: id }]).then((aResult) => {
         let manifest = aResult[0].manifest;
         if (!manifest.appcache_path) {
           aData.error = "NOT_UPDATABLE";
           aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
           return;
         }
 
         debug("Checking only appcache for " + aData.manifestURL);
         // Check if the appcache is updatable, and send "downloadavailable" or
         // "downloadapplied".
         let updateObserver = {
           observe: function(aSubject, aTopic, aObsData) {
             debug("onlyCheckAppCache updateSvc.checkForUpdate return for " +
                   app.manifestURL + " - event is " + aTopic);
             if (aTopic == "offline-cache-update-available") {
               app.downloadAvailable = true;
-              this._saveApps(function() {
-                DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+              this._saveApps().then(() => {
+                this.broadcastMessage("Webapps:UpdateState", {
                   app: app,
                   manifestURL: app.manifestURL
                 });
-                DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
+                this.broadcastMessage("Webapps:FireEvent", {
                   eventType: "downloadavailable",
                   manifestURL: app.manifestURL,
                   requestID: aData.requestID
                 });
               });
             } else {
               aData.error = "NOT_UPDATABLE";
               aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
@@ -1873,17 +1846,17 @@ this.DOMApplicationRegistry = {
             app.etag = xhr.getResponseHeader("Etag");
           }
 
           app.lastCheckedUpdate = Date.now();
           if (isPackage) {
             if (oldHash != hash) {
               updatePackagedApp.call(this, manifest);
             } else {
-              this._saveApps(function() {
+              this._saveApps().then(() => {
                 // Like if we got a 304, just send a 'downloadapplied'
                 // or downloadavailable event.
                 let eventType = app.downloadAvailable ? "downloadavailable"
                                                       : "downloadapplied";
                 aMm.sendAsyncMessage("Webapps:UpdateState", {
                   app: app,
                   manifestURL: app.manifestURL
                 });
@@ -1900,17 +1873,17 @@ this.DOMApplicationRegistry = {
             updateHostedApp.call(this, oldManifest,
                                  oldHash == hash ? null : manifest);
           }
         }
       } else if (xhr.status == 304) {
         // The manifest has not changed.
         if (isPackage) {
           app.lastCheckedUpdate = Date.now();
-          this._saveApps(function() {
+          this._saveApps().then(() => {
             // If the app is a packaged app, we just send a 'downloadapplied'
             // or downloadavailable event.
             let eventType = app.downloadAvailable ? "downloadavailable"
                                                   : "downloadapplied";
             aMm.sendAsyncMessage("Webapps:UpdateState", {
               app: app,
               manifestURL: app.manifestURL
             });
@@ -1954,33 +1927,33 @@ this.DOMApplicationRegistry = {
         sendError("NETWORK_ERROR");
       }).bind(this), false);
 
       debug("Checking manifest at " + aData.manifestURL);
       xhr.send(null);
     }
 
     // Read the current app manifest file
-    this._readManifests([{ id: id }], (function(aResult) {
+    this._readManifests([{ id: id }]).then((aResult) => {
       let extraHeaders = [];
 #ifdef MOZ_WIDGET_GONK
       let pingManifestURL;
       try {
         pingManifestURL = Services.prefs.getCharPref("ping.manifestURL");
       } catch(e) { }
 
       if (pingManifestURL && pingManifestURL == aData.manifestURL) {
         // Get the device info.
         let device = libcutils.property_get("ro.product.model");
         extraHeaders.push({ name: "X-MOZ-B2G-DEVICE",
                             value: device || "unknown" });
       }
 #endif
       doRequest.call(this, aResult[0].manifest, extraHeaders);
-    }).bind(this));
+    });
   },
 
   // Creates a nsILoadContext object with a given appId and isBrowser flag.
   createLoadContext: function createLoadContext(aAppId, aIsBrowser) {
     return {
        associatedWindow: null,
        topWindow : null,
        appId: aAppId,
@@ -2307,36 +2280,35 @@ onInstallSuccessAck: function onInstallS
     } else {
       appObject.installState = "installed";
       appObject.downloadAvailable = false;
       appObject.downloading = false;
       appObject.readyToApplyDownload = false;
     }
 
     appObject.localId = aLocalId;
-    appObject.basePath =
-      FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true).path;
+    appObject.basePath = OS.Path.dirname(this.appsFile);
     appObject.name = aManifest.name;
     appObject.csp = aManifest.csp || "";
     appObject.role = aManifest.role || "";
     appObject.installerAppId = aData.appId;
     appObject.installerIsBrowser = aData.isBrowser;
 
     return appObject;
   },
 
   _writeManifestFile: function(aId, aIsPackage, aJsonManifest) {
     debug("_writeManifestFile");
-    let dir = this._getAppDir(aId);
-    let manFile = dir.clone();
 
     // For packaged apps, keep the update manifest distinct from the app manifest.
     let manifestName = aIsPackage ? "update.webapp" : "manifest.webapp";
-    manFile.append(manifestName);
-    this._writeFile(manFile, JSON.stringify(aJsonManifest), function() { });
+
+    let dir = this._getAppDir(aId).path;
+    let manFile = OS.Path.join(dir, manifestName);
+    this._writeFile(manFile, JSON.stringify(aJsonManifest));
   },
 
   confirmInstall: function(aData, aProfileDir, aInstallSuccessCallback) {
     debug("confirmInstall");
 
     let origin = Services.io.newURI(aData.app.origin, null, null);
     let id = this._appIdForManifestURL(aData.app.manifestURL);
     let manifestURL = origin.resolve(aData.app.manifestURL);
@@ -2399,32 +2371,32 @@ onInstallSuccessAck: function onInstallS
         app: appObject,
         profileDir: aProfileDir
       }
     }
 
     // We notify about the successful installation via mgmt.oninstall and the
     // corresponging DOMRequest.onsuccess event as soon as the app is properly
     // saved in the registry.
-    this._saveApps((function() {
+    this._saveApps().then(() => {
       this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject });
       if (aData.isPackage && aData.autoInstall) {
         // Skip directly to onInstallSuccessAck, since there isn't
         // a WebappsRegistry to receive Webapps:Install:Return:OK and respond
         // Webapps:Install:Return:Ack when an app is being auto-installed.
         this.onInstallSuccessAck(app.manifestURL);
       } else {
         // Broadcast Webapps:Install:Return:OK so the WebappsRegistry can notify
         // the installing page about the successful install, after which it'll
         // respond Webapps:Install:Return:Ack, which calls onInstallSuccessAck.
         this.broadcastMessage("Webapps:Install:Return:OK", aData);
       }
       Services.obs.notifyObservers(null, "webapps-installed",
         JSON.stringify({ manifestURL: app.manifestURL }));
-    }).bind(this));
+    });
 
     if (!aData.isPackage) {
       this.updateAppHandlers(null, app.manifest, app);
       if (aInstallSuccessCallback) {
         aInstallSuccessCallback(app.manifest);
       }
     }
     let dontNeedNetwork = false;
@@ -2484,24 +2456,23 @@ onInstallSuccessAck: function onInstallS
     let dir = this._getAppDir(aId);
     zipFile.moveTo(dir, "application.zip");
     let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
     try {
       tmpDir.remove(true);
     } catch(e) { }
 
     // Save the manifest
-    let manFile = dir.clone();
-    manFile.append("manifest.webapp");
-    this._writeFile(manFile, JSON.stringify(aManifest), function() { });
+    let manFile = OS.Path.join(dir.path, "manifest.webapp");
+    this._writeFile(manFile, JSON.stringify(aManifest));
     // Set state and fire events.
     app.installState = "installed";
     app.downloading = false;
     app.downloadAvailable = false;
-    this._saveApps((function() {
+    this._saveApps().then(() => {
       this.updateAppHandlers(null, aManifest, aNewApp);
       this.broadcastMessage("Webapps:AddApp", { id: aId, app: aNewApp });
       Services.obs.notifyObservers(null, "webapps-installed",
         JSON.stringify({ manifestURL: aNewApp.manifestURL }));
 
       if (supportUseCurrentProfile()) {
         // Update the permissions for this app.
         PermissionsInstaller.installPermissions({
@@ -2521,17 +2492,17 @@ onInstallSuccessAck: function onInstallS
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: ["downloadsuccess", "downloadapplied"],
         manifestURL: aNewApp.manifestURL
       });
       if (aInstallSuccessCallback) {
         aInstallSuccessCallback(aManifest, zipFile.path);
       }
-    }).bind(this));
+    });
   },
 
   _nextLocalId: function() {
     let id = Services.prefs.getIntPref("dom.mozApps.maxLocalId") + 1;
 
     while (this.getManifestURLByLocalId(id)) {
       id++;
     }
@@ -2549,64 +2520,58 @@ onInstallSuccessAck: function onInstallS
     return null;
   },
 
   makeAppId: function() {
     let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
     return uuidGenerator.generateUUID().toString();
   },
 
-  _saveApps: function(aCallback) {
-    this._writeFile(this.appsFile, JSON.stringify(this.webapps, null, 2),
-                    aCallback);
+  _saveApps: function() {
+    return this._writeFile(this.appsFile, JSON.stringify(this.webapps, null, 2));
   },
 
   /**
     * Asynchronously reads a list of manifests
     */
 
   _manifestCache: {},
 
-  _readManifests: function(aData, aFinalCallback, aIndex) {
-    if (!aData.length) {
-      aFinalCallback(aData);
-      return;
-    }
-
-    let index = aIndex || 0;
-    let id = aData[index].id;
-
-    // Use the cached manifest instead of reading the file again from disk.
-    if (id in this._manifestCache) {
-      aData[index].manifest = this._manifestCache[id];
-      if (index == aData.length - 1)
-        aFinalCallback(aData);
-      else
-        this._readManifests(aData, aFinalCallback, index + 1);
-      return;
-    }
-
-    // the manifest file used to be named manifest.json, so fallback on this.
-    let baseDir = this.webapps[id].basePath == this.getCoreAppsBasePath()
-                    ? "coreAppsDir" : DIRECTORY_NAME;
-    let file = FileUtils.getFile(baseDir, ["webapps", id, "manifest.webapp"], true);
-    if (!file.exists()) {
-      file = FileUtils.getFile(baseDir, ["webapps", id, "update.webapp"], true);
-    }
-    if (!file.exists()) {
-      file = FileUtils.getFile(baseDir, ["webapps", id, "manifest.json"], true);
-    }
-
-    this._loadJSONAsync(file, (function(aJSON) {
-      aData[index].manifest = this._manifestCache[id] = aJSON;
-      if (index == aData.length - 1)
-        aFinalCallback(aData);
-      else
-        this._readManifests(aData, aFinalCallback, index + 1);
-    }).bind(this));
+  _readManifests: function(aData) {
+    return Task.spawn(function*() {
+      if (!aData.length) {
+        return aData;
+      }
+
+      for (let elem of aData) {
+        let id = elem.id;
+
+        if (!this._manifestCache[id]) {
+          // the manifest file used to be named manifest.json, so fallback on this.
+          let baseDir = this.webapps[id].basePath == this.getCoreAppsBasePath()
+                          ? "coreAppsDir" : DIRECTORY_NAME;
+
+          let file = FileUtils.getFile(baseDir, ["webapps", id, "manifest.webapp"], true);
+
+          if (!file.exists()) {
+            file = FileUtils.getFile(baseDir, ["webapps", id, "update.webapp"], true);
+          }
+
+          if (!file.exists()) {
+            file = FileUtils.getFile(baseDir, ["webapps", id, "manifest.json"], true);
+          }
+
+          this._manifestCache[id] = yield this._loadJSONAsync(file.path);
+        }
+
+        elem.manifest = this._manifestCache[id];
+      }
+
+      return aData;
+    }.bind(this)).then(null, Cu.reportError);
   },
 
   downloadPackage: function(aManifest, aNewApp, aIsUpdate, aOnSuccess) {
     // Here are the steps when installing a package:
     // - create a temp directory where to store the app.
     // - download the zip in this directory.
     // - check the signature on the zip.
     // - extract the manifest from the zip and check it.
@@ -2954,26 +2919,26 @@ onInstallSuccessAck: function onInstallS
       let dirPath = this._getAppDir(aId).path;
 
       // We don't really mind much if this fails.
       OS.File.move(OS.Path.join(dirPath, "staged-update.webapp"),
                    OS.Path.join(dirPath, "update.webapp"));
     }
 
     // Save the updated registry, and cleanup the tmp directory.
-    this._saveApps((function() {
+    this._saveApps().then(() => {
       this.broadcastMessage("Webapps:UpdateState", {
         app: aOldApp,
         manifestURL: aNewApp.manifestURL
       });
       this.broadcastMessage("Webapps:FireEvent", {
         manifestURL: aNewApp.manifestURL,
         eventType: ["downloadsuccess", "downloadapplied"]
       });
-    }).bind(this));
+    });
     let file = FileUtils.getFile("TmpD", ["webapps", aId], false);
     if (file && file.exists()) {
       file.remove(true);
     }
   },
 
   _openAndReadPackage: function(aZipFile, aOldApp, aNewApp, aIsLocalFileInstall,
                                 aIsUpdate, aManifest, aRequestChannel, aHash) {
@@ -3337,27 +3302,27 @@ onInstallSuccessAck: function onInstallS
     aOldApp.installState = download ? download.previousState
                                     : aIsUpdate ? "installed"
                                                 : "pending";
 
     if (aOldApp.staged) {
       delete aOldApp.staged;
     }
 
-    this._saveApps((function() {
+    this._saveApps().then(() => {
       this.broadcastMessage("Webapps:UpdateState", {
         app: aOldApp,
         error: aError,
         manifestURL: aNewApp.manifestURL
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL:  aNewApp.manifestURL
       });
-    }).bind(this));
+    });
     AppDownloadManager.remove(aNewApp.manifestURL);
   },
 
   doUninstall: function(aData, aMm) {
     this.uninstall(aData.manifestURL,
       function onsuccess() {
         aMm.sendAsyncMessage("Webapps:Uninstall:Return:OK", aData);
       },
@@ -3400,41 +3365,41 @@ onInstallSuccessAck: function onInstallS
 
     // Then notify observers.
     // We have to clone the app object as nsIDOMApplication objects are
     // stringified as an empty object. (see bug 830376)
     let appClone = AppsUtils.cloneAppObject(app);
     Services.obs.notifyObservers(null, "webapps-uninstall", JSON.stringify(appClone));
 
     if (supportSystemMessages()) {
-      this._readManifests([{ id: id }], (function unregisterManifest(aResult) {
+      this._readManifests([{ id: id }]).then((aResult) => {
         this._unregisterActivities(aResult[0].manifest, app);
-      }).bind(this));
+      });
     }
 
     let dir = this._getAppDir(id);
     try {
       dir.remove(true);
     } catch (e) {}
 
     delete this.webapps[id];
 
-    this._saveApps((function() {
+    this._saveApps().then(() => {
       this.broadcastMessage("Webapps:Uninstall:Broadcast:Return:OK", appClone);
       // Catch exception on callback call to ensure notifying observers after
       try {
         if (aOnSuccess) {
           aOnSuccess();
         }
       } catch(ex) {
         Cu.reportError("DOMApplicationRegistry: Exception on app uninstall: " +
                        ex + "\n" + ex.stack);
       }
       this.broadcastMessage("Webapps:RemoveApp", { id: id });
-    }).bind(this));
+    });
   },
 
   getSelf: function(aData, aMm) {
     aData.apps = [];
 
     if (aData.appId == Ci.nsIScriptSecurityManager.NO_APP_ID ||
         aData.appId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID) {
       aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData);
@@ -3454,80 +3419,80 @@ onInstallSuccessAck: function onInstallS
       }
     }
 
     if (!aData.apps.length) {
       aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData);
       return;
     }
 
-    this._readManifests(tmp, (function(aResult) {
+    this._readManifests(tmp).then((aResult) => {
       for (let i = 0; i < aResult.length; i++)
         aData.apps[i].manifest = aResult[i].manifest;
       aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData);
-    }).bind(this));
+    });
   },
 
   checkInstalled: function(aData, aMm) {
     aData.app = null;
     let tmp = [];
 
     for (let appId in this.webapps) {
       if (this.webapps[appId].manifestURL == aData.manifestURL &&
           this._isLaunchable(this.webapps[appId])) {
         aData.app = AppsUtils.cloneAppObject(this.webapps[appId]);
         tmp.push({ id: appId });
         break;
       }
     }
 
-    this._readManifests(tmp, (function(aResult) {
+    this._readManifests(tmp).then((aResult) => {
       for (let i = 0; i < aResult.length; i++) {
         aData.app.manifest = aResult[i].manifest;
         break;
       }
       aMm.sendAsyncMessage("Webapps:CheckInstalled:Return:OK", aData);
-    }).bind(this));
+    });
   },
 
   getInstalled: function(aData, aMm) {
     aData.apps = [];
     let tmp = [];
 
     for (let id in this.webapps) {
       if (this.webapps[id].installOrigin == aData.origin &&
           this._isLaunchable(this.webapps[id])) {
         aData.apps.push(AppsUtils.cloneAppObject(this.webapps[id]));
         tmp.push({ id: id });
       }
     }
 
-    this._readManifests(tmp, (function(aResult) {
+    this._readManifests(tmp).then((aResult) => {
       for (let i = 0; i < aResult.length; i++)
         aData.apps[i].manifest = aResult[i].manifest;
       aMm.sendAsyncMessage("Webapps:GetInstalled:Return:OK", aData);
-    }).bind(this));
+    });
   },
 
   getNotInstalled: function(aData, aMm) {
     aData.apps = [];
     let tmp = [];
 
     for (let id in this.webapps) {
       if (!this._isLaunchable(this.webapps[id])) {
         aData.apps.push(AppsUtils.cloneAppObject(this.webapps[id]));
         tmp.push({ id: id });
       }
     }
 
-    this._readManifests(tmp, (function(aResult) {
+    this._readManifests(tmp).then((aResult) => {
       for (let i = 0; i < aResult.length; i++)
         aData.apps[i].manifest = aResult[i].manifest;
       aMm.sendAsyncMessage("Webapps:GetNotInstalled:Return:OK", aData);
-    }).bind(this));
+    });
   },
 
   doGetAll: function(aData, aMm) {
     this.getAll(function (apps) {
       aData.apps = apps;
       aMm.sendAsyncMessage("Webapps:GetAll:Return:OK", aData);
     });
   },
@@ -3541,36 +3506,32 @@ onInstallSuccessAck: function onInstallS
       let app = AppsUtils.cloneAppObject(this.webapps[id]);
       if (!this._isLaunchable(app))
         continue;
 
       apps.push(app);
       tmp.push({ id: id });
     }
 
-    this._readManifests(tmp, (function(aResult) {
+    this._readManifests(tmp).then((aResult) => {
       for (let i = 0; i < aResult.length; i++)
         apps[i].manifest = aResult[i].manifest;
       aCallback(apps);
-    }).bind(this));
+    });
   },
 
-  getManifestFor: function(aManifestURL, aCallback) {
-    if (!aCallback)
-      return;
-
+  getManifestFor: function(aManifestURL) {
     let id = this._appIdForManifestURL(aManifestURL);
     let app = this.webapps[id];
     if (!id || (app.installState == "pending" && !app.retryingDownload)) {
-      aCallback(null);
-      return;
+      return Promise.resolve(null);
     }
 
-    this._readManifests([{ id: id }], function(aResult) {
-      aCallback(aResult[0].manifest);
+    return this._readManifests([{ id: id }]).then((aResult) => {
+      return aResult[0].manifest;
     });
   },
 
   getAppByManifestURL: function(aManifestURL) {
     return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
   },
 
   getCSPByLocalId: function(aLocalId) {
@@ -3594,18 +3555,18 @@ onInstallSuccessAck: function onInstallS
   getAppLocalIdByManifestURL: function(aManifestURL) {
     return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL);
   },
 
   getCoreAppsBasePath: function() {
     return AppsUtils.getCoreAppsBasePath();
   },
 
-  getWebAppsBasePath: function getWebAppsBasePath() {
-    return FileUtils.getDir(DIRECTORY_NAME, ["webapps"], false).path;
+  getWebAppsBasePath: function() {
+    return OS.Path.dirname(this.appsFile);
   },
 
   _isLaunchable: function(aApp) {
     if (this.allAppsLaunchable)
       return true;
 
     return WebappOSUtils.isLaunchable(aApp);
   },
--- a/toolkit/devtools/server/actors/webapps.js
+++ b/toolkit/devtools/server/actors/webapps.js
@@ -160,22 +160,22 @@ WebappsActor.prototype = {
     aApp.id = aId;
     aApp.basePath = reg.getWebAppsBasePath();
     aApp.localId = (aId in reg.webapps) ? reg.webapps[aId].localId
                                         : reg._nextLocalId();
 
     reg.webapps[aId] = aApp;
     reg.updatePermissionsForApp(aId);
 
-    reg._readManifests([{ id: aId }], function(aResult) {
+    reg._readManifests([{ id: aId }]).then((aResult) => {
       let manifest = aResult[0].manifest;
       aApp.name = manifest.name;
       reg.updateAppHandlers(null, manifest, aApp);
 
-      reg._saveApps(function() {
+      reg._saveApps().then(() => {
         aApp.manifest = manifest;
 
         // Needed to evict manifest cache on content side
         // (has to be dispatched first, otherwise other messages like
         // Install:Return:OK are going to use old manifest version)
         reg.broadcastMessage("Webapps:UpdateState", {
           app: aApp,
           manifest: manifest,
@@ -259,71 +259,54 @@ WebappsActor.prototype = {
     debug("installHostedApp");
     let self = this;
     let deferred = promise.defer();
 
     function readManifest() {
       if (aManifest) {
         return promise.resolve(aManifest);
       } else {
-        let deferred = promise.defer();
-        let manFile = aDir.clone();
-        manFile.append("manifest.webapp");
-        DOMApplicationRegistry._loadJSONAsync(manFile, function(aManifest) {
-          if (!aManifest) {
-            deferred.reject("Error parsing manifest.webapp.");
-          } else {
-            deferred.resolve(aManifest);
-          }
-        });
-        return deferred.promise;
+        let manFile = OS.Path.join(aDir.path, "manifest.webapp");
+        return AppsUtils.loadJSONAsync(manFile);
       }
     }
     function checkSideloading(aManifest) {
       return self._getAppType(aManifest.type);
     }
     function writeManifest(aAppType) {
       // Move manifest.webapp to the destination directory.
       // The destination directory for this app.
       let installDir = DOMApplicationRegistry._getAppDir(aId);
       if (aManifest) {
-        let deferred = promise.defer();
-        let manFile = installDir.clone();
-        manFile.append("manifest.webapp");
-        DOMApplicationRegistry._writeFile(manFile, JSON.stringify(aManifest), function () {
-          deferred.resolve(aAppType);
+        let manFile = OS.Path.join(installDir.path, "manifest.webapp");
+        return DOMApplicationRegistry._writeFile(manFile, JSON.stringify(aManifest)).then(() => {
+          return aAppType;
         });
-        return deferred.promise;
       } else {
         let manFile = aDir.clone();
         manFile.append("manifest.webapp");
         manFile.moveTo(installDir, "manifest.webapp");
       }
       return null;
     }
     function readMetadata(aAppType) {
       if (aMetadata) {
         return { metadata: aMetadata, appType: aAppType };
       }
       // Read the origin and manifest url from metadata.json
-      let deferred = promise.defer();
-      let metaFile = aDir.clone();
-      metaFile.append("metadata.json");
-      DOMApplicationRegistry._loadJSONAsync(metaFile, function(aMetadata) {
+      let metaFile = OS.Path.join(aDir.path, "metadata.json");
+      return AppsUtils.loadJSONAsync(metaFile).then((aMetadata) => {
         if (!aMetadata) {
-          deferred.reject("Error parsing metadata.json.");
-          return;
+          throw("Error parsing metadata.json.");
         }
         if (!aMetadata.origin) {
-          deferred.reject("Missing 'origin' property in metadata.json");
-          return;
+          throw("Missing 'origin' property in metadata.json");
         }
-        deferred.resolve({ metadata: aMetadata, appType: aAppType });
+        return { metadata: aMetadata, appType: aAppType };
       });
-      return deferred.promise;
     }
     let runnable = {
       run: function run() {
         try {
           readManifest().
             then(writeManifest).
             then(checkSideloading).
             then(readMetadata).
@@ -640,17 +623,17 @@ WebappsActor.prototype = {
   },
 
   _findManifestByURL: function wa__findManifestByURL(aManifestURL) {
     let deferred = promise.defer();
 
     let reg = DOMApplicationRegistry;
     let id = reg._appIdForManifestURL(aManifestURL);
 
-    reg._readManifests([{ id: id }], function (aResults) {
+    reg._readManifests([{ id: id }]).then((aResults) => {
       deferred.resolve(aResults[0].manifest);
     });
 
     return deferred.promise;
   },
 
   getIconAsDataURL: function (aRequest) {
     debug("getIconAsDataURL");