Bug 910466 - Refactor IPC app state updating. r=fabrice
authorFernando Jiménez <ferjmoreno@gmail.com>
Thu, 17 Oct 2013 14:47:58 +0200
changeset 165889 f7e452886b4f339ab7a3cee08fd96ae49414758d
parent 165888 84cf5a56188eb202e7d267254aa210067ae9072c
child 165890 616980966644f34c3e96a2b44517ca8f553364ff
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs910466
milestone27.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 910466 - Refactor IPC app state updating. r=fabrice
browser/modules/webappsUI.jsm
dom/apps/src/Webapps.js
dom/apps/src/Webapps.jsm
dom/apps/tests/test_packaged_app_update.html
toolkit/devtools/server/actors/webapps.js
--- a/browser/modules/webappsUI.jsm
+++ b/browser/modules/webappsUI.jsm
@@ -25,62 +25,54 @@ this.webappsUI = {
   // List of promises for in-progress installations
   installations: {},
 
   init: function webappsUI_init() {
     Services.obs.addObserver(this, "webapps-ask-install", false);
     Services.obs.addObserver(this, "webapps-launch", false);
     Services.obs.addObserver(this, "webapps-uninstall", false);
     cpmm.addMessageListener("Webapps:Install:Return:OK", this);
-    cpmm.addMessageListener("Webapps:OfflineCache", this);
     cpmm.addMessageListener("Webapps:Install:Return:KO", this);
-    cpmm.addMessageListener("Webapps:PackageEvent", this);
+    cpmm.addMessageListener("Webapps:UpdateState", this);
   },
 
   uninit: function webappsUI_uninit() {
     Services.obs.removeObserver(this, "webapps-ask-install");
     Services.obs.removeObserver(this, "webapps-launch");
     Services.obs.removeObserver(this, "webapps-uninstall");
     cpmm.removeMessageListener("Webapps:Install:Return:OK", this);
-    cpmm.removeMessageListener("Webapps:OfflineCache", this);
     cpmm.removeMessageListener("Webapps:Install:Return:KO", this);
-    cpmm.removeMessageListener("Webapps:PackageEvent", this);
+    cpmm.removeMessageListener("Webapps:UpdateState", this);
   },
 
   receiveMessage: function(aMessage) {
     let data = aMessage.data;
 
     let manifestURL = data.manifestURL ||
                       (data.app && data.app.manifestURL) ||
                       data.manifest;
 
     if (!this.installations[manifestURL]) {
       return;
     }
 
-    if (aMessage.name == "Webapps:OfflineCache") {
+    if (aMessage.name == "Webapps:UpdateState") {
       if (data.error) {
         this.installations[manifestURL].reject(data.error);
-      } else if (data.installState == "installed") {
+      } else if (data.app.installState == "installed") {
         this.installations[manifestURL].resolve();
       }
     } else if (aMessage.name == "Webapps:Install:Return:OK" &&
                !data.isPackage) {
       let manifest = new ManifestHelper(data.app.manifest, data.app.origin);
       if (!manifest.appcache_path) {
         this.installations[manifestURL].resolve();
       }
     } else if (aMessage.name == "Webapps:Install:Return:KO") {
       this.installations[manifestURL].reject(data.error);
-    } else if (aMessage.name == "Webapps:PackageEvent") {
-      if (data.type == "installed") {
-        this.installations[manifestURL].resolve();
-      } else if (data.type == "error") {
-        this.installations[manifestURL].reject(data.error);
-      }
     }
   },
 
   observe: function webappsUI_observe(aSubject, aTopic, aData) {
     let data = JSON.parse(aData);
     data.mm = aSubject;
 
     switch(aTopic) {
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -342,49 +342,48 @@ WebappsApplication.prototype = {
     this._ondownloadsuccess = null;
     this._ondownloaderror = null;
     this._ondownloadavailable = null;
     this._ondownloadapplied = null;
 
     this._downloadError = null;
 
     this.initDOMRequestHelper(aWindow, [
-      "Webapps:OfflineCache",
-      "Webapps:CheckForUpdate:Return:OK",
       "Webapps:CheckForUpdate:Return:KO",
-      "Webapps:PackageEvent",
       "Webapps:Connect:Return:OK",
       "Webapps:Connect:Return:KO",
-      "Webapps:GetConnections:Return:OK"
+      "Webapps:FireEvent",
+      "Webapps:GetConnections:Return:OK",
+      "Webapps:UpdateState"
     ]);
 
-    cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
-                          {
-                            messages: ["Webapps:OfflineCache",
-                                       "Webapps:PackageEvent",
-                                       "Webapps:CheckForUpdate:Return:OK"],
-                            app: {
-                              id: this.id,
-                              manifestURL: this.manifestURL,
-                              installState: this.installState,
-                              downloading: this.downloading
-                            }
-                          });
+    cpmm.sendAsyncMessage("Webapps:RegisterForMessages", {
+      messages: ["Webapps:FireEvent",
+                 "Webapps:UpdateState"],
+      app: {
+        id: this.id,
+        manifestURL: this.manifestURL,
+        installState: this.installState,
+        downloading: this.downloading
+      }
+    });
   },
 
   get manifest() {
     return manifestCache.get(this.manifestURL,
                              this._manifest,
                              this._window,
                              this.innerWindowID);
   },
 
   get updateManifest() {
-    return this.updateManifest = this._updateManifest ? ObjectWrapper.wrap(this._updateManifest, this._window)
-                                                      : null;
+    return this.updateManifest =
+      this._updateManifest ? ObjectWrapper.wrap(this._updateManifest,
+                                                this._window)
+                           : null;
   },
 
   set onprogress(aCallback) {
     this._onprogress = aCallback;
   },
 
   get onprogress() {
     return this._onprogress;
@@ -507,189 +506,142 @@ WebappsApplication.prototype = {
                                 resolve: aResolve,
                                 reject: aReject
                               })});
     }.bind(this));
   },
 
   uninit: function() {
     this._onprogress = null;
-    cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
-                          ["Webapps:OfflineCache",
-                           "Webapps:PackageEvent",
-                           "Webapps:CheckForUpdate:Return:OK"]);
+    cpmm.sendAsyncMessage("Webapps:UnregisterForMessages", [
+      "Webapps:FireEvent",
+      "Webapps:PackageEvent"
+    ]);
 
     manifestCache.evict(this.manifestURL, this.innerWindowID);
   },
 
-  _fireEvent: function(aName, aHandler) {
-    if (aHandler) {
-      let event = new this._window.MozApplicationEvent(aName, { application: this });
-      aHandler.handleEvent(event);
+  _fireEvent: function(aName) {
+    let handler = this["_on" + aName];
+    if (handler) {
+      let event = new this._window.MozApplicationEvent(aName, {
+        application: this
+      });
+      try {
+        handler.handleEvent(event);
+      } catch (ex) {
+        dump("Event handler expection " + ex + "\n");
+      }
+    }
+  },
+
+  _updateState: function(aMsg) {
+    if (aMsg.app) {
+      for (let prop in aMsg.app) {
+        this[prop] = aMsg.app[prop];
+      }
+    }
+
+    if (aMsg.error) {
+      this._downloadError = aMsg.error;
+    }
+
+    if (aMsg.manifest) {
+      this._manifest = aMsg.manifest;
+      manifestCache.evict(this.manifestURL, this.innerWindowID);
     }
   },
 
   receiveMessage: function(aMessage) {
     let msg = aMessage.json;
     let req;
     if (aMessage.name == "Webapps:Connect:Return:OK" ||
         aMessage.name == "Webapps:Connect:Return:KO" ||
         aMessage.name == "Webapps:GetConnections:Return:OK") {
       req = this.takePromiseResolver(msg.requestID);
     } else {
       req = this.takeRequest(msg.requestID);
     }
 
     // ondownload* callbacks should be triggered on all app instances
     if ((msg.oid != this._id || !req) &&
-        aMessage.name !== "Webapps:OfflineCache" &&
-        aMessage.name !== "Webapps:PackageEvent" &&
-        aMessage.name !== "Webapps:CheckForUpdate:Return:OK")
+        aMessage.name !== "Webapps:FireEvent" &&
+        aMessage.name !== "Webapps:UpdateState") {
       return;
+    }
+
     switch (aMessage.name) {
       case "Webapps:Launch:Return:KO":
         this.removeMessageListeners(["Webapps:Launch:Return:OK",
                                      "Webapps:Launch:Return:KO"]);
         Services.DOMRequest.fireError(req, "APP_INSTALL_PENDING");
         break;
       case "Webapps:Launch:Return:OK":
         this.removeMessageListeners(["Webapps:Launch:Return:OK",
                                      "Webapps:Launch:Return:KO"]);
         Services.DOMRequest.fireSuccess(req, null);
         break;
       case "Webapps:CheckForUpdate:Return:KO":
         Services.DOMRequest.fireError(req, msg.error);
         break;
-      case "Webapps:CheckForUpdate:Return:OK":
-        if (msg.manifestURL != this.manifestURL)
-          return;
-
-        manifestCache.evict(this.manifestURL, this.innerWindowID);
-
-        let hiddenProps = ["manifest", "updateManifest"];
-        let updatableProps = ["installOrigin", "installTime", "installState",
-            "lastUpdateCheck", "updateTime", "progress", "downloadAvailable",
-            "downloading", "readyToApplyDownload", "downloadSize"];
-        // Props that we don't update: origin, receipts, manifestURL, removable.
+      case "Webapps:FireEvent":
+        if (msg.manifestURL != this.manifestURL) {
+           return;
+        }
 
-        updatableProps.forEach(function(prop) {
-          if (msg.app[prop]) {
-            this[prop] = msg.app[prop];
-          }
-        }, this);
+        // The parent might ask childs to trigger more than one event in one
+        // shot, so in order to avoid needless IPC we allow an array for the
+        // 'eventType' IPC message field.
+        if (!Array.isArray(msg.eventType)) {
+          msg.eventType = [msg.eventType];
+        }
 
-        hiddenProps.forEach(function(prop) {
-          if (msg.app[prop]) {
-            this["_" + prop] = msg.app[prop];
+        msg.eventType.forEach((aEventType) => {
+          if ("_on" + aEventType in this) {
+            this._fireEvent(aEventType);
+          } else {
+            dump("Unsupported event type " + aEventType + "\n");
           }
-        }, this);
-
-        if (msg.event == "downloadapplied") {
-          this._fireEvent("downloadapplied", this._ondownloadapplied);
-        } else if (msg.event == "downloadavailable") {
-          this._fireEvent("downloadavailable", this._ondownloadavailable);
-        }
+        });
 
         if (req) {
           Services.DOMRequest.fireSuccess(req, this.manifestURL);
         }
         break;
-      case "Webapps:OfflineCache":
-        if (msg.manifest != this.manifestURL)
+      case "Webapps:UpdateState":
+        if (msg.manifestURL != this.manifestURL) {
           return;
-
-        if ("installState" in msg) {
-          this.installState = msg.installState;
-          this.progress = msg.progress;
-          if (this.installState == "installed") {
-            this._downloadError = null;
-            this.downloading = false;
-            this.downloadAvailable = false;
-            this._fireEvent("downloadsuccess", this._ondownloadsuccess);
-            this._fireEvent("downloadapplied", this._ondownloadapplied);
-          } else {
-            this.downloading = true;
-            this._fireEvent("downloadprogress", this._onprogress);
-          }
-        } else if (msg.error) {
-          this._downloadError = msg.error;
-          this.downloading = false;
-          this._fireEvent("downloaderror", this._ondownloaderror);
         }
-        break;
-      case "Webapps:PackageEvent":
-        if (msg.manifestURL != this.manifestURL)
-          return;
 
-        // Set app values according to parent process results.
-        let app = msg.app;
-        this.downloading = app.downloading;
-        this.downloadAvailable = app.downloadAvailable;
-        this.downloadSize = app.downloadSize || 0;
-        this.installState = app.installState;
-        this.progress = app.progress || msg.progress || 0;
-        this.readyToApplyDownload = app.readyToApplyDownload;
-        this.updateTime = app.updateTime;
-        this.origin = app.origin;
-
-        switch(msg.type) {
-          case "error":
-          case "canceled":
-            this._downloadError = msg.error;
-            this._fireEvent("downloaderror", this._ondownloaderror);
-            break;
-          case "progress":
-            this._fireEvent("downloadprogress", this._onprogress);
-            break;
-          case "installed":
-            manifestCache.evict(this.manifestURL, this.innerWindowID);
-            this._manifest = msg.manifest;
-            this._fireEvent("downloadsuccess", this._ondownloadsuccess);
-            this._fireEvent("downloadapplied", this._ondownloadapplied);
-            break;
-          case "downloaded":
-            // We don't update the packaged apps manifests until they
-            // are installed or until the update is unstaged.
-            if (msg.manifest) {
-              manifestCache.evict(this.manifestURL, this.innerWindowID);
-              this._manifest = msg.manifest;
-            }
-            this._fireEvent("downloadsuccess", this._ondownloadsuccess);
-            break;
-          case "applied":
-            manifestCache.evict(this.manifestURL, this.innerWindowID);
-            this._manifest = msg.manifest;
-            this._fireEvent("downloadapplied", this._ondownloadapplied);
-            break;
-        }
+        this._updateState(msg);
         break;
       case "Webapps:ClearBrowserData:Return":
         this.removeMessageListeners(aMessage.name);
         Services.DOMRequest.fireSuccess(req, null);
         break;
       case "Webapps:Connect:Return:OK":
         let messagePorts = [];
-        msg.messagePortIDs.forEach(function(aPortID) {
+        msg.messagePortIDs.forEach((aPortID) => {
           let port = new this._window.MozInterAppMessagePort(aPortID);
           messagePorts.push(port);
-        }, this);
+        });
         req.resolve(messagePorts);
         break;
       case "Webapps:Connect:Return:KO":
         req.reject("No connections registered");
         break;
       case "Webapps:GetConnections:Return:OK":
         let connections = [];
-        msg.connections.forEach(function(aConnection) {
+        msg.connections.forEach((aConnection) => {
           let connection =
             new this._window.MozInterAppConnection(aConnection.keyword,
                                                    aConnection.pubAppManifestURL,
                                                    aConnection.subAppManifestURL);
           connections.push(connection);
-        }, this);
+        });
         req.resolve(connections);
         break;
     }
   },
 
   classID: Components.ID("{723ed303-7757-4fb0-b261-4f78b1f6bd22}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplication,
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -38,17 +38,17 @@ function supportUseCurrentProfile() {
   return Services.prefs.getBoolPref("dom.webapps.useCurrentProfile");
 }
 
 function supportSystemMessages() {
   return Services.prefs.getBoolPref("dom.sysmsg.enabled");
 }
 
 // Minimum delay between two progress events while downloading, in ms.
-const MIN_PROGRESS_EVENT_DELAY = 1000;
+const MIN_PROGRESS_EVENT_DELAY = 1500;
 
 const WEBAPP_RUNTIME = Services.appinfo.ID == "webapprt@mozilla.org";
 
 XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
   Cu.import("resource://gre/modules/NetUtil.jsm");
   return NetUtil;
 });
 
@@ -940,18 +940,16 @@ this.DOMApplicationRegistry = {
       if (aCallback)
         aCallback(null);
     }
   },
 
   addMessageListener: function(aMsgNames, aApp, aMm) {
     aMsgNames.forEach(function (aMsgName) {
       let man = aApp && aApp.manifestURL;
-      debug("Adding messageListener for: " + aMsgName + "App: " +
-            man);
       if (!(aMsgName in this.children)) {
         this.children[aMsgName] = [];
       }
 
       let mmFound = this.children[aMsgName].some(function(mmRef) {
         if (mmRef.mm === aMm) {
           mmRef.refCount++;
           return true;
@@ -961,18 +959,18 @@ this.DOMApplicationRegistry = {
 
       if (!mmFound) {
         this.children[aMsgName].push({
           mm: aMm,
           refCount: 1
         });
 
         // If it wasn't registered before, let's update its state
-        if ((aMsgName === 'Webapps:PackageEvent') ||
-            (aMsgName === 'Webapps:OfflineCache')) {
+        if ((aMsgName === 'Webapps:FireEvent') ||
+            (aMsgName === 'Webapps:UpdateState')) {
           if (man) {
             let app = this.getAppByManifestURL(aApp.manifestURL);
             if (app && ((aApp.installState !== app.installState) ||
                         (aApp.downloading !== app.downloading))) {
               debug("Got a registration from an outdated app: " +
                     aApp.manifestURL);
               let aEvent ={
                 type: app.installState,
@@ -1119,47 +1117,48 @@ this.DOMApplicationRegistry = {
   },
 
   // Some messages can be listened by several content processes:
   // Webapps:AddApp
   // Webapps:RemoveApp
   // Webapps:Install:Return:OK
   // Webapps:Uninstall:Return:OK
   // Webapps:Uninstall:Broadcast:Return:OK
-  // Webapps:OfflineCache
+  // Webapps:FireEvent
   // Webapps:checkForUpdate:Return:OK
-  // Webapps:PackageEvent
+  // Webapps:UpdateState
   broadcastMessage: function broadcastMessage(aMsgName, aContent) {
     if (!(aMsgName in this.children)) {
       return;
     }
     this.children[aMsgName].forEach(function(mmRef) {
       mmRef.mm.sendAsyncMessage(aMsgName, aContent);
     });
   },
 
   _getAppDir: function(aId) {
     return FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
   },
 
-  _writeFile: function ss_writeFile(aFile, aData, aCallbak) {
+  _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 (aCallbak)
-        aCallbak();
+      if (aCallback) {
+        aCallback();
+      }
     });
   },
 
   doLaunch: function (aData, aMm) {
     this.launch(
       aData.manifestURL,
       aData.startPoint,
       aData.timestamp,
@@ -1211,46 +1210,47 @@ this.DOMApplicationRegistry = {
     if (!download) {
       debug("Could not find a download for " + aManifestURL);
       return;
     }
 
     let app = this.webapps[download.appId];
 
     if (download.cacheUpdate) {
-      // Cancel hosted app download.
-      app.isCanceling = true;
       try {
         download.cacheUpdate.cancel();
       } catch (e) {
-        delete app.isCanceling;
         debug (e);
       }
     } else if (download.channel) {
-      // Cancel packaged app download.
-      app.isCanceling = true;
       try {
         download.channel.cancel(Cr.NS_BINDING_ABORTED);
-      } catch(e) {
-        delete app.isCanceling;
-      }
+      } catch(e) { }
     } else {
       return;
     }
 
     let app = this.webapps[download.appId];
     app.progress = 0;
     app.installState = download.previousState;
     app.downloading = false;
     this._saveApps((function() {
-      this.broadcastMessage("Webapps:PackageEvent",
-                             { type: "canceled",
-                               manifestURL:  app.manifestURL,
-                               app: app,
-                               error: error });
+      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];
@@ -1262,21 +1262,24 @@ this.DOMApplicationRegistry = {
     if (app.downloading) {
       debug("app is already downloading. Ignoring.");
       return;
     }
 
     // If the caller is trying to start a download but we have nothing to
     // download, send an error.
     if (!app.downloadAvailable) {
-      this.broadcastMessage("Webapps:PackageEvent",
-                            { type: "canceled",
-                              manifestURL: app.manifestURL,
-                              app: app,
-                              error: "NO_DOWNLOAD_AVAILABLE" });
+      this.broadcastMessage("Webapps:UpdateState", {
+        error: "NO_DOWNLOAD_AVAILABLE",
+        manifestURL: app.manifestURL
+      });
+      this.broadcastMessage("Webapps:FireEvent", {
+        eventType: "downloaderror",
+        manifestURL: app.manifestURL
+      });
       return;
     }
 
     // First of all, we check if the download is supposed to update an
     // already installed application.
     let isUpdate = (app.installState == "installed");
 
     // An app download would only be triggered for two reasons: an app
@@ -1298,35 +1301,41 @@ this.DOMApplicationRegistry = {
       this._readManifests([{ id: id }], (function readManifest(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
+          // 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.broadcastMessage("Webapps:PackageEvent",
-                                                  { type: "downloaded",
-                                                    manifestURL: aManifestURL,
-                                                    app: app,
-                                                    manifest: jsonManifest });
-          DOMApplicationRegistry._saveApps();
+          DOMApplicationRegistry._saveApps(function() {
+            DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+              app: app,
+              manifest: jsonManifest,
+              manifestURL: aManifestURL
+            });
+            DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
+              eventType: "downloadsuccess",
+              manifestURL: aManifestURL
+            });
+          });
         }
       }).bind(this));
 
       return;
     }
 
     this._loadJSONAsync(file, (function(aJSON) {
       if (!aJSON) {
-        debug("startDownload: No update manifest found at " + file.path + " " + aManifestURL);
+        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,
@@ -1345,21 +1354,24 @@ this.DOMApplicationRegistry = {
 
           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() {
-            debug("About to fire Webapps:PackageEvent");
-            DOMApplicationRegistry.broadcastMessage("Webapps:PackageEvent",
-                                                    { type: "downloaded",
-                                                      manifestURL: aManifestURL,
-                                                      app: app });
+            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));
   },
@@ -1437,21 +1449,25 @@ this.DOMApplicationRegistry = {
             PermissionsInstaller.installPermissions(
               { manifest: aData,
                 origin: app.origin,
                 manifestURL: app.manifestURL },
               true);
           }
           this.updateDataStore(this.webapps[id].localId, app.origin,
                                app.manifestURL, aData);
-          this.broadcastMessage("Webapps:PackageEvent",
-                                { type: "applied",
-                                  manifestURL: app.manifestURL,
-                                  app: app,
-                                  manifest: aData });
+          this.broadcastMessage("Webapps:UpdateState", {
+            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;
@@ -1470,16 +1486,24 @@ this.DOMApplicationRegistry = {
       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.broadcastMessage("Webapps:UpdateState", {
+        app: {
+          downloading: true,
+          installState: aApp.installState,
+          progress: 0
+        },
+        manifestURL: aApp.manifestURL
+      });
       let cacheUpdate = aProfileDir
         ? updateSvc.scheduleCustomProfileUpdate(appcacheURI, docURI, aProfileDir)
         : updateSvc.scheduleAppUpdate(appcacheURI, docURI, aApp.localId, false);
 
       // We save the download details for potential further usage like
       // cancelling it.
       let download = {
         cacheUpdate: cacheUpdate,
@@ -1593,26 +1617,27 @@ this.DOMApplicationRegistry = {
       manFile.append("staged-update.webapp");
       this._writeFile(manFile, JSON.stringify(aManifest), function() { });
 
       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;
-      aData.event = "downloadavailable";
-      aData.app = {
-        downloadAvailable: true,
-        downloadSize: manifest.size,
-        updateManifest: aManifest
-      }
+      app.updateManifest = aManifest;
       DOMApplicationRegistry._saveApps(function() {
-        DOMApplicationRegistry.broadcastMessage("Webapps:CheckForUpdate:Return:OK",
-                                                aData);
-        delete aData.app.updateManifest;
+        DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+          app: app,
+          manifestURL: app.manifestURL
+        });
+        DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
+          eventType: "downloadavailable",
+          manifestURL: app.manifestURL,
+          requestID: aData.requestID
+        });
       });
     }
 
     // A hosted app is updated if the app manifest or the appcache needs
     // updating. Even if the app manifest has not changed, we still check
     // for changes in the app cache.
     // 'aNewManifest' would contain the updated app manifest if
     // it has actually been updated, while 'aOldManifest' contains the
@@ -1657,35 +1682,52 @@ this.DOMApplicationRegistry = {
       } else {
         manifest = new ManifestHelper(aOldManifest, app.origin);
       }
 
       // Update the registry.
       this.webapps[id] = app;
       this._saveApps(function() {
         let reg = DOMApplicationRegistry;
-        aData.app = app;
         if (!manifest.appcache_path) {
-          aData.event = "downloadapplied";
-          reg.broadcastMessage("Webapps:CheckForUpdate:Return:OK", aData);
+          reg.broadcastMessage("Webapps:UpdateState", {
+            app: app,
+            manifest: app.manifest,
+            manifestURL: app.manifestURL
+          });
+          reg.broadcastMessage("Webapps:FireEvent", {
+            eventType: "downloadapplied",
+            manifestURL: app.manifestURL,
+            requestID: aData.requestID
+          });
         } else {
           // Check if the appcache is updatable, and send "downloadavailable" or
           // "downloadapplied".
           let updateObserver = {
             observe: function(aSubject, aTopic, aObsData) {
               debug("updateHostedApp: updateSvc.checkForUpdate return for " +
                     app.manifestURL + " - event is " + aTopic);
-              aData.event =
+              let eventType =
                 aTopic == "offline-cache-update-available" ? "downloadavailable"
                                                            : "downloadapplied";
-              aData.app.downloadAvailable = (aData.event == "downloadavailable");
-              reg._saveApps();
-              reg.broadcastMessage("Webapps:CheckForUpdate:Return:OK", aData);
+              app.downloadAvailable = (eventType == "downloadavailable");
+              reg._saveApps(function() {
+                reg.broadcastMessage("Webapps:UpdateState", {
+                  app: app,
+                  manifest: app.manifest,
+                  manifestURL: app.manifestURL
+                });
+                reg.broadcastMessage("Webapps:FireEvent", {
+                  eventType: eventType,
+                  manifestURL: app.manifestURL,
+                  requestID: aData.requestID
+                });
+              });
             }
-          }
+          };
           debug("updateHostedApp: updateSvc.checkForUpdate for " +
                 manifest.fullAppcachePath());
           updateSvc.checkForUpdate(Services.io.newURI(manifest.fullAppcachePath(), null, null),
                                    app.localId, false, updateObserver);
         }
         delete app.manifest;
       });
     }
@@ -1710,18 +1752,18 @@ this.DOMApplicationRegistry = {
     }
 
     // For non-removable hosted apps that lives in the core apps dir we
     // only check the appcache because we can't modify the manifest even
     // if it has changed.
     let onlyCheckAppCache = false;
 
 #ifdef MOZ_WIDGET_GONK
-      let appDir = FileUtils.getDir("coreAppsDir", ["webapps"], false);
-      onlyCheckAppCache = (app.basePath == appDir.path);
+    let appDir = FileUtils.getDir("coreAppsDir", ["webapps"], false);
+    onlyCheckAppCache = (app.basePath == appDir.path);
 #endif
 
     if (onlyCheckAppCache) {
       // Bail out for packaged apps.
       if (app.origin.startsWith("app://")) {
         aData.error = "NOT_UPDATABLE";
         aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
         return;
@@ -1739,29 +1781,34 @@ this.DOMApplicationRegistry = {
         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") {
-              aData.event = "downloadavailable";
               app.downloadAvailable = true;
-              aData.app = app;
               this._saveApps(function() {
-                DOMApplicationRegistry.broadcastMessage(
-                  "Webapps:CheckForUpdate:Return:OK", aData);
+                DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+                  app: app,
+                  manifestURL: app.manifestURL
+                });
+                DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
+                  eventType: "downloadavailable",
+                  manifestURL: app.manifestURL,
+                  requestID: aData.requestID
+                });
               });
             } else {
               aData.error = "NOT_UPDATABLE";
               aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
             }
           }
-        }
+        };
         let helper = new ManifestHelper(manifest);
         debug("onlyCheckAppCache - launch updateSvc.checkForUpdate for " +
               helper.fullAppcachePath());
         updateSvc.checkForUpdate(Services.io.newURI(helper.fullAppcachePath(), null, null),
                                  app.localId, false, updateObserver);
       });
       return;
     }
@@ -1801,46 +1848,58 @@ this.DOMApplicationRegistry = {
             app.etag = xhr.getResponseHeader("Etag");
           }
 
           app.lastCheckedUpdate = Date.now();
           if (isPackage) {
             if (oldHash != hash) {
               updatePackagedApp.call(this, manifest);
             } else {
-              // Like if we got a 304, just send a 'downloadapplied'
-              // or downloadavailable event.
-              aData.event = app.downloadAvailable ? "downloadavailable"
-                                                  : "downloadapplied";
-              aData.app = {
-                lastCheckedUpdate: app.lastCheckedUpdate
-              }
-              aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:OK", aData);
-              this._saveApps();
+              this._saveApps(function() {
+                // 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
+                });
+                aMm.sendAsyncMessage("Webapps:FireEvent", {
+                  eventType: eventType,
+                  manifestURL: app.manifestURL,
+                  requestID: aData.requestID
+                });
+              });
             }
           } else {
             // Update only the appcache if the manifest has not changed
             // based on the hash value.
             updateHostedApp.call(this, oldManifest,
                                  oldHash == hash ? null : manifest);
           }
         }
       } else if (xhr.status == 304) {
         // The manifest has not changed.
         if (isPackage) {
-          // If the app is a packaged app, we just send a 'downloadapplied'
-          // or downloadavailable event.
           app.lastCheckedUpdate = Date.now();
-          aData.event = app.downloadAvailable ? "downloadavailable"
-                                              : "downloadapplied";
-          aData.app = {
-            lastCheckedUpdate: app.lastCheckedUpdate
-          }
-          aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:OK", aData);
-          this._saveApps();
+          this._saveApps(function() {
+            // 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
+            });
+            aMm.sendAsyncMessage("Webapps:FireEvent", {
+              eventType: eventType,
+              manifestURL: app.manifestURL,
+              requestID: aData.requestID
+            });
+          });
         } else {
           // For hosted apps, even if the manifest has not changed, we check
           // for offline cache updates.
           updateHostedApp.call(this, oldManifest, null);
         }
       } else {
         sendError("MANIFEST_URL_ERROR");
       }
@@ -2141,31 +2200,35 @@ this.DOMApplicationRegistry = {
         this._saveApps((function() {
           this.updateAppHandlers(null, aManifest, appObject);
           this.broadcastMessage("Webapps:AddApp", { id: aId, app: appObject });
           Services.obs.notifyObservers(null, "webapps-installed",
             JSON.stringify({ manifestURL: appObject.manifestURL }));
 
           if (supportUseCurrentProfile()) {
             // Update the permissions for this app.
-            PermissionsInstaller.installPermissions({ manifest: aManifest,
-                                                      origin: appObject.origin,
-                                                      manifestURL: appObject.manifestURL },
-                                                    true);
+            PermissionsInstaller.installPermissions({
+              manifest: aManifest,
+              origin: appObject.origin,
+              manifestURL: appObject.manifestURL
+            }, true);
           }
 
           this.updateDataStore(this.webapps[aId].localId, appObject.origin,
                                appObject.manifestURL, aManifest);
 
-          debug("About to fire Webapps:PackageEvent 'installed'");
-          this.broadcastMessage("Webapps:PackageEvent",
-                                { type: "installed",
-                                  manifestURL: appObject.manifestURL,
-                                  app: app,
-                                  manifest: aManifest });
+          this.broadcastMessage("Webapps:UpdateState", {
+            app: app,
+            manifest: aManifest,
+            manifestURL: appObject.manifestURL
+          });
+          this.broadcastMessage("Webapps:FireEvent", {
+            eventType: ["downloadsuccess", "downloadapplied"],
+            manifestURL: appObject.manifestURL
+          });
           if (installSuccessCallback) {
             installSuccessCallback(aManifest, zipFile.path);
           }
         }).bind(this));
       }).bind(this));
     }
   },
 
@@ -2421,53 +2484,56 @@ this.DOMApplicationRegistry = {
     // Removes the directory we created, and sends an error to the DOM side.
     function cleanup(aError) {
       debug("Cleanup: " + aError);
       let dir = FileUtils.getDir("TmpD", ["webapps", id], true, true);
       try {
         dir.remove(true);
       } catch (e) { }
 
-      // We avoid notifying the error to the DOM side if the app download
-      // was cancelled via cancelDownload, which already sends its own
-      // notification.
-      if (app.isCanceling) {
-        delete app.isCanceling;
-        return;
-      }
-
       let download = AppDownloadManager.get(aApp.manifestURL);
       app.downloading = false;
 
       // If there were not enough storage to download the package we
       // won't have a record of the download details, so we just set the
       // installState to 'pending' at first download and to 'installed' when
       // updating.
       app.installState = download ? download.previousState
                                   : aIsUpdate ? "installed"
                                   : "pending";
 
       if (app.staged) {
         delete app.staged;
       }
 
-      self.broadcastMessage("Webapps:PackageEvent",
-                            { type: "error",
-                              manifestURL:  aApp.manifestURL,
-                              error: aError,
-                              app: app });
-      self._saveApps();
+      self._saveApps(function() {
+        self.broadcastMessage("Webapps:UpdateState", {
+          app: app,
+          error: aError,
+          manifestURL: aApp.manifestURL
+        });
+        self.broadcastMessage("Webapps:FireEvent", {
+          eventType: "downloaderror",
+          manifestURL:  aApp.manifestURL
+        });
+      });
       AppDownloadManager.remove(aApp.manifestURL);
     }
 
-    function sendProgressEvent() {
-      self.broadcastMessage("Webapps:PackageEvent",
-                            { type: "progress",
-                              manifestURL: aApp.manifestURL,
-                              app: app });
+    function sendProgressEvent(aProgress) {
+      self.broadcastMessage("Webapps:UpdateState", {
+        app: {
+          progress: aProgress
+        },
+        manifestURL: aApp.manifestURL
+      });
+      self.broadcastMessage("Webapps:FireEvent", {
+        eventType: "progress",
+        manifestURL: aApp.manifestURL
+      });
     }
 
     // aStoreId must be a string of the form
     //   <installOrigin>#<storeId from ids.json>
     // aStoreVersion must be a positive integer.
     function checkForStoreIdMatch(aStoreId, aStoreVersion) {
       // Things to check:
       // 1. if it's a update:
@@ -2543,17 +2609,17 @@ this.DOMApplicationRegistry = {
           return this.QueryInterface(aIID);
         },
         onProgress: function notifProgress(aRequest, aContext,
                                            aProgress, aProgressMax) {
           app.progress = aProgress;
           let now = Date.now();
           if (now - lastProgressTime > MIN_PROGRESS_EVENT_DELAY) {
             debug("onProgress: " + aProgress + "/" + aProgressMax);
-            sendProgressEvent();
+            sendProgressEvent(aProgress);
             lastProgressTime = now;
             self._saveApps();
           }
         },
         onStatus: function notifStatus(aRequest, aContext, aStatus, aStatusArg) { },
 
         // nsILoadContext
         appId: app.installerAppId,
@@ -2641,26 +2707,27 @@ this.DOMApplicationRegistry = {
                 // Move the staged update manifest to a non staged one.
                 let dirPath = self._getAppDir(id).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"));
               }
 
-              self.broadcastMessage("Webapps:PackageEvent", {
-                                      type: "downloaded",
-                                      manifestURL: aApp.manifestURL,
-                                      app: app });
-              self.broadcastMessage("Webapps:PackageEvent", {
-                                      type: "applied",
-                                      manifestURL: aApp.manifestURL,
-                                      app: app });
               // Save the updated registry, and cleanup the tmp directory.
-              self._saveApps();
+              self._saveApps(function() {
+                self.broadcastMessage("Webapps:UpdateState", {
+                  app: app,
+                  manifestURL: aApp.manifestURL
+                });
+                self.broadcastMessage("Webapps:FireEvent", {
+                  manifestURL: aApp.manifestURL,
+                  eventType: ["downloadsuccess", "downloadapplied"]
+                });
+              });
               let file = FileUtils.getFile("TmpD", ["webapps", id], false);
               if (file && file.exists()) {
                 file.remove(true);
               }
               return;
             }
 
             let certdb;
@@ -2902,17 +2969,17 @@ this.DOMApplicationRegistry = {
             });
           });
         }
       });
 
       requestChannel.asyncOpen(listener, null);
 
       // send a first progress event to correctly set the DOM object's properties
-      sendProgressEvent();
+      sendProgressEvent(0);
     };
 
     let checkDownloadSize = function (freeBytes) {
        if (freeBytes) {
          debug("Free storage: " + freeBytes + ". Download size: " +
                aApp.downloadSize);
          if (freeBytes <=
              aApp.downloadSize + AppDownloadManager.MIN_REMAINING_FREESPACE) {
@@ -3300,83 +3367,97 @@ this.DOMApplicationRegistry = {
  * Appcache download observer
  */
 let AppcacheObserver = function(aApp) {
   debug("Creating AppcacheObserver for " + aApp.origin +
         " - " + aApp.installState);
   this.app = aApp;
   this.startStatus = aApp.installState;
   this.lastProgressTime = 0;
-  // send a first progress event to correctly set the DOM object's properties
+  // Send a first progress event to correctly set the DOM object's properties.
   this._sendProgressEvent();
 };
 
 AppcacheObserver.prototype = {
   // nsIOfflineCacheUpdateObserver implementation
   _sendProgressEvent: function() {
     let app = this.app;
-    DOMApplicationRegistry.broadcastMessage("Webapps:OfflineCache",
-                                            { manifest: app.manifestURL,
-                                              installState: app.installState,
-                                              progress: app.progress });
+    DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+      app: app,
+      manifestURL: app.manifestURL
+    });
+    DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
+      eventType: "progress",
+      manifestURL: app.manifestURL
+    });
   },
 
   updateStateChanged: function appObs_Update(aUpdate, aState) {
     let mustSave = false;
     let app = this.app;
 
     debug("Offline cache state change for " + app.origin + " : " + aState);
 
     var self = this;
     let setStatus = function appObs_setStatus(aStatus, aProgress) {
       debug("Offlinecache setStatus to " + aStatus + " with progress " +
-          aProgress + " for " + app.origin);
+            aProgress + " for " + app.origin);
       mustSave = (app.installState != aStatus);
+
       app.installState = aStatus;
       app.progress = aProgress;
-      if (aStatus == "installed") {
-        app.updateTime = Date.now();
-        app.downloading = false;
-        app.downloadAvailable = false;
+      if (aStatus != "installed") {
+        self._sendProgressEvent();
+        return;
       }
-      self._sendProgressEvent();
+
+      app.updateTime = Date.now();
+      app.downloading = false;
+      app.downloadAvailable = false;
+      DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+        app: app,
+        manifestURL: app.manifestURL
+      });
+      DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
+        eventType: ["downloadsuccess", "downloadapplied"],
+        manifestURL: app.manifestURL
+      });
     }
 
     let setError = function appObs_setError(aError) {
       debug("Offlinecache setError to " + aError);
-      // If we are canceling the download, we already send a DOWNLOAD_CANCELED
-      // error.
-      if (!app.isCanceling) {
-        DOMApplicationRegistry.broadcastMessage("Webapps:OfflineCache",
-                                                { manifest: app.manifestURL,
-                                                  error: aError });
-      } else {
-        delete app.isCanceling;
-      }
-
       app.downloading = false;
+      DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+        app: app,
+        manifestURL: app.manifestURL
+      });
+      DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
+        error: aError,
+        eventType: "downloaderror",
+        manifestURL: app.manifestURL
+      });
       mustSave = true;
     }
 
     switch (aState) {
       case Ci.nsIOfflineCacheUpdateObserver.STATE_ERROR:
         aUpdate.removeObserver(this);
         AppDownloadManager.remove(app.manifestURL);
         setError("APP_CACHE_DOWNLOAD_ERROR");
         break;
       case Ci.nsIOfflineCacheUpdateObserver.STATE_NOUPDATE:
       case Ci.nsIOfflineCacheUpdateObserver.STATE_FINISHED:
         aUpdate.removeObserver(this);
         AppDownloadManager.remove(app.manifestURL);
         setStatus("installed", aUpdate.byteProgress);
         break;
       case Ci.nsIOfflineCacheUpdateObserver.STATE_DOWNLOADING:
-      case Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMSTARTED:
         setStatus(this.startStatus, aUpdate.byteProgress);
         break;
+      case Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMSTARTED:
       case Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMPROGRESS:
         let now = Date.now();
         if (now - this.lastProgressTime > MIN_PROGRESS_EVENT_DELAY) {
           setStatus(this.startStatus, aUpdate.byteProgress);
           this.lastProgressTime = now;
         }
         break;
     }
--- a/dom/apps/tests/test_packaged_app_update.html
+++ b/dom/apps/tests/test_packaged_app_update.html
@@ -154,17 +154,17 @@ var steps = [
     ok(true, "== TEST == Check for Update after getting a new package");
     checkForUpdate(false);
   },
   function() {
     PackagedTestHelper.setAppVersion(4, PackagedTestHelper.next, true);
   },
   function() {
     ok(true, "== TEST == Update packaged app - same package");
-    updateApp(false, 3, 4);
+    updateApp(false, 3, 3);
   },
   function() {
     ok(true, "== TEST == Check for Update after getting the same package");
     checkForUpdate(false);
   },
   function() {
     PackagedTestHelper.setAppVersion(1, PackagedTestHelper.next);
   },
--- a/toolkit/devtools/server/actors/webapps.js
+++ b/toolkit/devtools/server/actors/webapps.js
@@ -171,29 +171,31 @@ WebappsActor.prototype = {
       reg.updateAppHandlers(null, manifest, aApp);
 
       reg._saveApps(function() {
         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:PackageEvent",
-                             { type: "installed",
-                               manifestURL: aApp.manifestURL,
-                               app: aApp,
-                               manifest: manifest
-                             });
-
+        reg.broadcastMessage("Webapps:UpdateState", {
+          app: aApp,
+          manifest: manifest,
+          manifestURL: aApp.manifestURL
+        });
+        reg.broadcastMessage("Webapps:FireEvent", {
+          eventType: ["downloadsuccess", "downloadapplied"],
+          manifestURL: aApp.manifestURL
+        });
         reg.broadcastMessage("Webapps:AddApp", { id: aId, app: aApp });
-        reg.broadcastMessage("Webapps:Install:Return:OK",
-                             { app: aApp,
-                               oid: "foo",
-                               requestID: "bar"
-                             });
+        reg.broadcastMessage("Webapps:Install:Return:OK", {
+          app: aApp,
+          oid: "foo",
+          requestID: "bar"
+        });
 
         Services.obs.notifyObservers(null, "webapps-installed",
           JSON.stringify({ manifestURL: aApp.manifestURL }));
 
         delete aApp.manifest;
         aDeferred.resolve({ appId: aId, path: aDir.path });
 
         // We can't have appcache for packaged apps.