Backed out changeset 6058a0d53426 (bug 903291) for causing bug 977215
authorWes Kocher <wkocher@mozilla.com>
Fri, 28 Feb 2014 15:25:21 -0800
changeset 171829 061c67d92d57322b153e967fa3fa3576b19d001c
parent 171828 28d7d39701f5faffa06f1a2684d269e3f0461801
child 171830 b89b8c11ca7dbc62ed5096f38fed35f1bfd24e0f
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
bugs903291, 977215
milestone30.0a1
backs out6058a0d53426616688938f2fd0f963370eb44a11
Backed out changeset 6058a0d53426 (bug 903291) for causing bug 977215
dom/apps/src/AppsServiceChild.jsm
dom/apps/src/Webapps.js
dom/apps/src/Webapps.jsm
dom/apps/tests/test_packaged_app_common.js
dom/apps/tests/test_packaged_app_update.html
dom/apps/tests/test_receipt_operations.html
toolkit/devtools/server/actors/webapps.js
--- a/dom/apps/src/AppsServiceChild.jsm
+++ b/dom/apps/src/AppsServiceChild.jsm
@@ -3,287 +3,76 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
-// This module exposes a subset of the functionalities of the parent DOM
-// Registry to content processes, to be used from the AppsService component.
+// This module exposes a subset of the functionnalities of the parent DOM
+// Registry to content processes, to be be used from the AppsService component.
 
-this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry", "WrappedManifestCache"];
+this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry"];
 
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 function debug(s) {
   //dump("-*- AppsServiceChild.jsm: " + s + "\n");
 }
 
-const APPS_IPC_MSG_NAMES = [
-  "Webapps:AddApp",
-  "Webapps:RemoveApp",
-  "Webapps:CheckForUpdate:Return:KO",
-  "Webapps:FireEvent",
-  "Webapps:UpdateState"
-];
-
-// A simple cache for the wrapped manifests.
-this.WrappedManifestCache = {
-  _cache: { },
-
-  // Gets an entry from the cache, and populates the cache if needed.
-  get: function mcache_get(aManifestURL, aManifest, aWindow, aInnerWindowID) {
-    if (!(aManifestURL in this._cache)) {
-      this._cache[aManifestURL] = { };
-    }
-
-    let winObjs = this._cache[aManifestURL];
-    if (!(aInnerWindowID in winObjs)) {
-      winObjs[aInnerWindowID] = Cu.cloneInto(aManifest, aWindow);
-    }
-
-    return winObjs[aInnerWindowID];
-  },
-
-  // Invalidates an entry in the cache.
-  evict: function mcache_evict(aManifestURL, aInnerWindowID) {
-    debug("Evicting manifest " + aManifestURL + " window ID " +
-          aInnerWindowID);
-    if (aManifestURL in this._cache) {
-      let winObjs = this._cache[aManifestURL];
-      if (aInnerWindowID in winObjs) {
-        delete winObjs[aInnerWindowID];
-      }
-
-      if (Object.keys(winObjs).length == 0) {
-        delete this._cache[aManifestURL];
-      }
-    }
-  },
-
-  observe: function(aSubject, aTopic, aData) {
-    // Clear the cache on memory pressure.
-    this._cache = { };
-    Cu.forceGC();
-  },
-
-  init: function() {
-    Services.obs.addObserver(this, "memory-pressure", false);
-  }
-};
-
-this.WrappedManifestCache.init();
-
-
-// DOMApplicationRegistry keeps a cache containing a list of apps in the device.
-// This information is updated with the data received from the main process and
-// it is queried by the DOM objects to set their state.
-// This module handle all the messages broadcasted from the parent process,
-// including DOM events, which are dispatched to the corresponding DOM objects.
-
 this.DOMApplicationRegistry = {
-  // DOMApps will hold a list of arrays of weak references to
-  // mozIDOMApplication objects indexed by manifest URL.
-  DOMApps: {},
-
   init: function init() {
+    debug("init");
     this.cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
                   .getService(Ci.nsISyncMessageSender);
 
-    APPS_IPC_MSG_NAMES.forEach((function(aMsgName) {
+    ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) {
       this.cpmm.addMessageListener(aMsgName, this);
     }).bind(this));
 
-    this.cpmm.sendAsyncMessage("Webapps:RegisterForMessages", {
-      messages: APPS_IPC_MSG_NAMES
-    });
-
     // We need to prime the cache with the list of apps.
-    // XXX should we do this async and block callers if it's not yet there?
-    let list = this.cpmm.sendSyncMessage("Webapps:GetList", { })[0];
-    this.webapps = list.webapps;
+    // XXX shoud we do this async and block callers if it's not yet there?
+    this.webapps = this.cpmm.sendSyncMessage("Webapps:GetList", { })[0];
 
     // We need a fast mapping from localId -> app, so we add an index.
-    // And add the manifest to the app object.
     this.localIdIndex = { };
     for (let id in this.webapps) {
       let app = this.webapps[id];
       this.localIdIndex[app.localId] = app;
-      app.manifest = list.manifests[id];
     }
 
     Services.obs.addObserver(this, "xpcom-shutdown", false);
   },
 
   observe: function(aSubject, aTopic, aData) {
-    // cpmm.addMessageListener causes the DOMApplicationRegistry object to
-    // live forever if we don't clean up properly.
+    // cpmm.addMessageListener causes the DOMApplicationRegistry object to live
+    // forever if we don't clean up properly.
     this.webapps = null;
-    this.DOMApps = null;
-
-    APPS_IPC_MSG_NAMES.forEach((aMsgName) => {
+    ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) {
       this.cpmm.removeMessageListener(aMsgName, this);
-    });
-
-    this.cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
-                               APPS_IPC_MSG_NAMES)
+    }).bind(this));
   },
 
   receiveMessage: function receiveMessage(aMessage) {
     debug("Received " + aMessage.name + " message.");
-    let msg = aMessage.data;
+    let msg = aMessage.json;
     switch (aMessage.name) {
       case "Webapps:AddApp":
         this.webapps[msg.id] = msg.app;
         this.localIdIndex[msg.app.localId] = msg.app;
         break;
       case "Webapps:RemoveApp":
-        delete this.DOMApps[this.webapps[msg.id].manifestURL];
         delete this.localIdIndex[this.webapps[msg.id].localId];
         delete this.webapps[msg.id];
         break;
-      case "Webapps:FireEvent":
-        this._fireEvent(aMessage);
-        break;
-      case "Webapps:UpdateState":
-        this._updateState(msg);
-        break;
-      case "Webapps:CheckForUpdate:Return:KO":
-        let DOMApps = this.DOMApps[msg.manifestURL];
-        if (!DOMApps || !msg.requestID) {
-          return;
-        }
-        DOMApps.forEach((DOMApp) => {
-          let domApp = DOMApp.get();
-          if (msg.requestID) {
-            domApp._fireRequestResult(aMessage, true /* aIsError */);
-          }
-        });
-        break;
     }
   },
 
-  /**
-   * mozIDOMApplication management
-   */
-
-  // Every time a DOM app is created, we save a weak reference to it that will
-  // be used to dispatch events and fire request results.
-  addDOMApp: function(aApp, aManifestURL, aId) {
-    let weakRef = Cu.getWeakReference(aApp);
-
-    if (!this.DOMApps[aManifestURL]) {
-      this.DOMApps[aManifestURL] = [];
-    }
-
-    let apps = this.DOMApps[aManifestURL];
-
-    // Get rid of dead weak references.
-    for (let i = 0; i < apps.length; i++) {
-      if (!apps[i].get()) {
-        apps.splice(i);
-      }
-    }
-
-    apps.push(weakRef);
-
-    // Each DOM app contains a proxy object used to build their state. We
-    // return the handler for this proxy object with traps to get and set
-    // app properties kept in the DOMApplicationRegistry app cache.
-    return {
-      get: function(target, prop) {
-        if (!DOMApplicationRegistry.webapps[aId]) {
-          return;
-        }
-        return DOMApplicationRegistry.webapps[aId][prop];
-      },
-      set: function(target, prop, val) {
-        if (!DOMApplicationRegistry.webapps[aId]) {
-          return;
-        }
-        DOMApplicationRegistry.webapps[aId][prop] = val;
-        return;
-      },
-    };
-  },
-
-  _fireEvent: function(aMessage) {
-    let msg = aMessage.data;
-    debug("_fireEvent " + JSON.stringify(msg));
-    if (!this.DOMApps || !msg.manifestURL || !msg.eventType) {
-      return;
-    }
-
-    let DOMApps = this.DOMApps[msg.manifestURL];
-    if (!DOMApps) {
-      return;
-    }
-
-    // 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];
-    }
-
-    DOMApps.forEach((DOMApp) => {
-      let domApp = DOMApp.get();
-      msg.eventType.forEach((aEventType) => {
-        if ('on' + aEventType in domApp) {
-          domApp._fireEvent(aEventType);
-        }
-      });
-
-      if (msg.requestID) {
-        aMessage.data.result = msg.manifestURL;
-        domApp._fireRequestResult(aMessage);
-      }
-    });
-  },
-
-  _updateState: function(aMessage) {
-    if (!this.DOMApps || !aMessage.id) {
-      return;
-    }
-
-    let app = this.webapps[aMessage.id];
-    if (!app) {
-      return;
-    }
-
-    if (aMessage.app) {
-      for (let prop in aMessage.app) {
-        app[prop] = aMessage.app[prop];
-      }
-    }
-
-    if (aMessage.error) {
-      app.downloadError = aMessage.error;
-    }
-
-    if (aMessage.manifest) {
-      app.manifest = aMessage.manifest;
-      // Evict the wrapped manifest cache for all the affected DOM objects.
-      let DOMApps = this.DOMApps[app.manifestURL];
-      if (!DOMApps) {
-        return;
-      }
-      DOMApps.forEach((DOMApp) => {
-        let domApp = DOMApp.get();
-        WrappedManifestCache.evict(app.manifestURL, domApp.innerWindowID);
-      });
-    }
-  },
-
-  /**
-   * nsIAppsService API
-   */
   getAppByManifestURL: function getAppByManifestURL(aManifestURL) {
     debug("getAppByManifestURL " + aManifestURL);
     return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
   },
 
   getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aManifestURL) {
     debug("getAppLocalIdByManifestURL " + aManifestURL);
     return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL);
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -7,17 +7,16 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
-Cu.import("resource://gre/modules/AppsServiceChild.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
 function convertAppsArray(aApps, aWindow) {
   let apps = Cu.createArrayIn(aWindow);
   for (let i = 0; i < aApps.length; i++) {
@@ -260,121 +259,134 @@ WebappsRegistry.prototype = {
                                     flags: Ci.nsIClassInfo.DOM_OBJECT,
                                     classDescription: "Webapps Registry"})
 }
 
 /**
   * mozIDOMApplication object
   */
 
+// A simple cache for the wrapped manifests.
+let manifestCache = {
+  _cache: { },
+
+  // Gets an entry from the cache, and populates the cache if needed.
+  get: function mcache_get(aManifestURL, aManifest, aWindow, aInnerWindowID) {
+    if (!(aManifestURL in this._cache)) {
+      this._cache[aManifestURL] = { };
+    }
+
+    let winObjs = this._cache[aManifestURL];
+    if (!(aInnerWindowID in winObjs)) {
+      winObjs[aInnerWindowID] = Cu.cloneInto(aManifest, aWindow);
+    }
+
+    return winObjs[aInnerWindowID];
+  },
+
+  // Invalidates an entry in the cache.
+  evict: function mcache_evict(aManifestURL, aInnerWindowID) {
+    if (aManifestURL in this._cache) {
+      let winObjs = this._cache[aManifestURL];
+      if (aInnerWindowID in winObjs) {
+        delete winObjs[aInnerWindowID];
+      }
+
+      if (Object.keys(winObjs).length == 0) {
+        delete this._cache[aManifestURL];
+      }
+    }
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    // Clear the cache on memory pressure.
+    this._cache = { };
+  },
+
+  init: function() {
+    Services.obs.addObserver(this, "memory-pressure", false);
+  }
+};
+
 function createApplicationObject(aWindow, aApp) {
-  let app = Cc["@mozilla.org/webapps/application;1"]
-              .createInstance(Ci.mozIDOMApplication);
+  let app = Cc["@mozilla.org/webapps/application;1"].createInstance(Ci.mozIDOMApplication);
   app.wrappedJSObject.init(aWindow, aApp);
   return app;
 }
 
 function WebappsApplication() {
   this.wrappedJSObject = this;
 }
 
 WebappsApplication.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
   init: function(aWindow, aApp) {
-    let proxyHandler = DOMApplicationRegistry.addDOMApp(this,
-                                                        aApp.manifestURL,
-                                                        aApp.id);
-    this._proxy = new Proxy(this, proxyHandler);
-
     this._window = aWindow;
+    let principal = this._window.document.nodePrincipal;
+    this._appStatus = principal.appStatus;
+    this.origin = aApp.origin;
+    this._manifest = aApp.manifest;
+    this._updateManifest = aApp.updateManifest;
+    this.manifestURL = aApp.manifestURL;
+    this.receipts = aApp.receipts;
+    this.installOrigin = aApp.installOrigin;
+    this.installTime = aApp.installTime;
+    this.installState = aApp.installState || "installed";
+    this.removable = aApp.removable;
+    this.lastUpdateCheck = aApp.lastUpdateCheck ? aApp.lastUpdateCheck
+                                                : Date.now();
+    this.updateTime = aApp.updateTime ? aApp.updateTime
+                                      : aApp.installTime;
+    this.progress = NaN;
+    this.downloadAvailable = aApp.downloadAvailable;
+    this.downloading = aApp.downloading;
+    this.readyToApplyDownload = aApp.readyToApplyDownload;
+    this.downloadSize = aApp.downloadSize || 0;
 
     this._onprogress = null;
     this._ondownloadsuccess = null;
     this._ondownloaderror = null;
     this._ondownloadavailable = null;
     this._ondownloadapplied = null;
 
-    this.initDOMRequestHelper(aWindow);
-  },
-
-  get _appStatus() {
-    return this._proxy.appStatus;
-  },
-
-  get downloadAvailable() {
-    return this._proxy.downloadAvailable;
-  },
-
-  get downloading() {
-    return this._proxy.downloading;
-  },
+    this._downloadError = null;
 
-  get downloadSize() {
-    return this._proxy.downloadSize;
-  },
-
-  get installOrigin() {
-    return this._proxy.installOrigin;
-  },
-
-  get installState() {
-    return this._proxy.installState;
-  },
-
-  get installTime() {
-    return this._proxy.installTime;
-  },
+    this.initDOMRequestHelper(aWindow, [
+      { name: "Webapps:CheckForUpdate:Return:KO", weakRef: true },
+      { name: "Webapps:Connect:Return:OK", weakRef: true },
+      { name: "Webapps:Connect:Return:KO", weakRef: true },
+      { name: "Webapps:FireEvent", weakRef: true },
+      { name: "Webapps:GetConnections:Return:OK", weakRef: true },
+      { name: "Webapps:UpdateState", weakRef: true }
+    ]);
 
-  get lastUpdateCheck() {
-    return this._proxy.lastUpdateCheck;
-  },
-
-  get manifestURL() {
-    return this._proxy.manifestURL;
-  },
-
-  get origin() {
-    return this._proxy.origin;
-  },
-
-  get progress() {
-    return this._proxy.progress;
-  },
-
-  get readyToApplyDownload() {
-    return this._proxy.readyToApplyDownload;
-  },
-
-  get receipts() {
-    return this._proxy.receipts;
-  },
-
-  set receipts(aReceipts) {
-    this._proxy.receipts = aReceipts;
-  },
-
-  get removable() {
-    return this._proxy.removable;
-  },
-
-  get updateTime() {
-    return this._proxy.updateTime;
+    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 WrappedManifestCache.get(this.manifestURL,
-                                    this._proxy.manifest,
-                                    this._window,
-                                    this.innerWindowID);
+    return manifestCache.get(this.manifestURL,
+                             this._manifest,
+                             this._window,
+                             this.innerWindowID);
   },
 
   get updateManifest() {
-    return this._proxy.updateManifest ?
-      Cu.cloneInto(this._proxy.updateManifest, this._window) : null;
+    return this.updateManifest =
+      this._updateManifest ? Cu.cloneInto(this._updateManifest, this._window)
+                           : null;
   },
 
   set onprogress(aCallback) {
     this._onprogress = aCallback;
   },
 
   get onprogress() {
     return this._onprogress;
@@ -408,17 +420,17 @@ WebappsApplication.prototype = {
     this._ondownloadapplied = aCallback;
   },
 
   get ondownloadapplied() {
     return this._ondownloadapplied;
   },
 
   get downloadError() {
-    return new this._window.DOMError(this._proxy.downloadError || '');
+    return new this._window.DOMError(this._downloadError || '');
   },
 
   download: function() {
     cpmm.sendAsyncMessage("Webapps:Download",
                           { manifestURL: this.manifestURL });
   },
 
   cancelDownload: function() {
@@ -450,56 +462,52 @@ WebappsApplication.prototype = {
   },
 
   clearBrowserData: function() {
     let request = this.createRequest();
     let browserChild =
       BrowserElementPromptService.getBrowserElementChildForWindow(this._window);
     if (browserChild) {
       this.addMessageListeners("Webapps:ClearBrowserData:Return");
-      browserChild.messageManager.sendAsyncMessage("Webapps:ClearBrowserData", {
-        manifestURL: this.manifestURL,
-        oid: this._id,
-        requestID: this.getRequestId(request)
-      });
+      browserChild.messageManager.sendAsyncMessage(
+        "Webapps:ClearBrowserData",
+        { manifestURL: this.manifestURL,
+          oid: this._id,
+          requestID: this.getRequestId(request) }
+      );
     } else {
       Services.DOMRequest.fireErrorAsync(request, "NO_CLEARABLE_BROWSER");
     }
     return request;
   },
 
   connect: function(aKeyword, aRules) {
-    this.addMessageListeners(["Webapps:Connect:Return:OK",
-                              "Webapps:Connect:Return:KO"]);
     return this.createPromise(function (aResolve, aReject) {
-      cpmm.sendAsyncMessage("Webapps:Connect", {
-        keyword: aKeyword,
-        rules: aRules,
-        manifestURL: this.manifestURL,
-        outerWindowID: this._id,
-        appStatus: this._appStatus,
-        requestID: this.getPromiseResolverId({
-          resolve: aResolve,
-          reject: aReject
-        })
-      });
+      cpmm.sendAsyncMessage("Webapps:Connect",
+                            { keyword: aKeyword,
+                              rules: aRules,
+                              manifestURL: this.manifestURL,
+                              outerWindowID: this._id,
+                              appStatus: this._appStatus,
+                              requestID: this.getPromiseResolverId({
+                                resolve: aResolve,
+                                reject: aReject
+                              })});
     }.bind(this));
   },
 
   getConnections: function() {
-    this.addMessageListeners("Webapps:getConnections:Return:OK");
     return this.createPromise(function (aResolve, aReject) {
-      cpmm.sendAsyncMessage("Webapps:GetConnections", {
-        manifestURL: this.manifestURL,
-        outerWindowID: this._id,
-        requestID: this.getPromiseResolverId({
-          resolve: aResolve,
-          reject: aReject
-        })
-      });
+      cpmm.sendAsyncMessage("Webapps:GetConnections",
+                            { manifestURL: this.manifestURL,
+                              outerWindowID: this._id,
+                              requestID: this.getPromiseResolverId({
+                                resolve: aResolve,
+                                reject: aReject
+                              })});
     }.bind(this));
   },
 
   addReceipt: function(receipt) {
     let request = this.createRequest();
 
     this.addMessageListeners(["Webapps:AddReceipt:Return:OK",
                               "Webapps:AddReceipt:Return:KO"]);
@@ -538,91 +546,134 @@ WebappsApplication.prototype = {
                                                       oid: this._id,
                                                       requestID: this.getRequestId(request) });
 
     return request;
   },
 
   uninit: function() {
     this._onprogress = null;
-    WrappedManifestCache.evict(this.manifestURL, this.innerWindowID);
+    cpmm.sendAsyncMessage("Webapps:UnregisterForMessages", [
+      "Webapps:FireEvent",
+      "Webapps:UpdateState"
+    ]);
+
+    manifestCache.evict(this.manifestURL, this.innerWindowID);
   },
 
   _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");
       }
     }
   },
 
-  _fireRequestResult: function(aMessage, aIsError) {
-    let req;
-    let msg = aMessage.data;
-    req = this.takeRequest(msg.requestID);
-    if (!req) {
-      return;
+  _updateState: function(aMsg) {
+    if (aMsg.app) {
+      for (let prop in aMsg.app) {
+        this[prop] = aMsg.app[prop];
+      }
     }
-    aIsError ? Services.DOMRequest.fireError(req, msg.error)
-             : Services.DOMRequest.fireSuccess(req, msg.result);
+
+    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);
     }
 
-    if (msg.oid != this._id || !req) {
+    // ondownload* callbacks should be triggered on all app instances
+    if ((msg.oid != this._id || !req) &&
+        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:FireEvent":
+        if (msg.manifestURL != this.manifestURL) {
+           return;
+        }
+
+        // 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];
+        }
+
+        msg.eventType.forEach((aEventType) => {
+          if ("_on" + aEventType in this) {
+            this._fireEvent(aEventType);
+          } else {
+            dump("Unsupported event type " + aEventType + "\n");
+          }
+        });
+
+        if (req) {
+          Services.DOMRequest.fireSuccess(req, this.manifestURL);
+        }
+        break;
+      case "Webapps:UpdateState":
+        if (msg.manifestURL != this.manifestURL) {
+          return;
+        }
+
+        this._updateState(msg);
+        break;
       case "Webapps:ClearBrowserData:Return":
         this.removeMessageListeners(aMessage.name);
         Services.DOMRequest.fireSuccess(req, null);
         break;
       case "Webapps:Connect:Return:OK":
-        this.removeMessageListeners(["Webapps:Connect:Return:OK",
-                                     "Webapps:Connect:Return:KO"]);
         let messagePorts = [];
         msg.messagePortIDs.forEach((aPortID) => {
           let port = new this._window.MozInterAppMessagePort(aPortID);
           messagePorts.push(port);
         });
         req.resolve(messagePorts);
         break;
       case "Webapps:Connect:Return:KO":
-        this.removeMessageListeners(["Webapps:Connect:Return:OK",
-                                     "Webapps:Connect:Return:KO"]);
         req.reject("No connections registered");
         break;
       case "Webapps:GetConnections:Return:OK":
-        this.removeMessageListeners(aMessage.name);
         let connections = [];
         msg.connections.forEach((aConnection) => {
           let connection =
             new this._window.MozInterAppConnection(aConnection.keyword,
                                                    aConnection.pubAppManifestURL,
                                                    aConnection.subAppManifestURL);
           connections.push(connection);
         });
@@ -794,18 +845,22 @@ WebappsApplicationMgmt.prototype = {
           let app = msg.app;
           let event = new this._window.MozApplicationEvent("applicationinstall",
                            { application : createApplicationObject(this._window, app) });
           this._oninstall.handleEvent(event);
         }
         break;
       case "Webapps:Uninstall:Broadcast:Return:OK":
         if (this._onuninstall) {
+          let detail = {
+            manifestURL: msg.manifestURL,
+            origin: msg.origin
+          };
           let event = new this._window.MozApplicationEvent("applicationuninstall",
-                           { application : createApplicationObject(this._window, msg) });
+                           { application : createApplicationObject(this._window, detail) });
           this._onuninstall.handleEvent(event);
         }
         break;
       case "Webapps:Uninstall:Return:OK":
         Services.DOMRequest.fireSuccess(req, msg.origin);
         break;
       case "Webapps:Uninstall:Return:KO":
         Services.DOMRequest.fireError(req, "NOT_INSTALLED");
@@ -824,10 +879,12 @@ WebappsApplicationMgmt.prototype = {
 
   classInfo: XPCOMUtils.generateCI({classID: Components.ID("{8c1bca96-266f-493a-8d57-ec7a95098c15}"),
                                     contractID: "@mozilla.org/webapps/application-mgmt;1",
                                     interfaces: [Ci.mozIDOMApplicationMgmt],
                                     flags: Ci.nsIClassInfo.DOM_OBJECT,
                                     classDescription: "Webapps Application Mgmt"})
 }
 
+manifestCache.init();
+
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebappsRegistry,
                                                      WebappsApplication]);
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -1137,17 +1137,18 @@ this.DOMApplicationRegistry = {
         break;
       case "Webapps:UnregisterForMessages":
         this.removeMessageListener(msg, mm);
         break;
       case "child-process-shutdown":
         this.removeMessageListener(["Webapps:Internal:AllMessages"], mm);
         break;
       case "Webapps:GetList":
-        return { webapps: this.webapps, manifests: this._manifestCache };
+        this.addMessageListener(["Webapps:AddApp", "Webapps:RemoveApp"], null, mm);
+        return this.webapps;
       case "Webapps:Download":
         this.startDownload(msg.manifestURL);
         break;
       case "Webapps:CancelDownload":
         this.cancelDownload(msg.manifestURL);
         break;
       case "Webapps:CheckForUpdate":
         this.checkForUpdate(msg, mm);
@@ -1282,17 +1283,17 @@ this.DOMApplicationRegistry = {
     this._saveApps().then(() => {
       this.broadcastMessage("Webapps:UpdateState", {
         app: {
           progress: 0,
           installState: download.previousState,
           downloading: false
         },
         error: error,
-        id: app.id
+        manifestURL: app.manifestURL,
       })
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL: app.manifestURL
       });
     });
     AppDownloadManager.remove(aManifestURL);
   },
@@ -1311,17 +1312,17 @@ this.DOMApplicationRegistry = {
       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:UpdateState", {
         error: "NO_DOWNLOAD_AVAILABLE",
-        id: app.id
+        manifestURL: app.manifestURL
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL: app.manifestURL
       });
       return;
     }
 
@@ -1356,17 +1357,17 @@ this.DOMApplicationRegistry = {
           // 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;
           this._saveApps().then(() => {
             this.broadcastMessage("Webapps:UpdateState", {
               app: app,
               manifest: jsonManifest,
-              id: app.id
+              manifestURL: aManifestURL
             });
             this.broadcastMessage("Webapps:FireEvent", {
               eventType: "downloadsuccess",
               manifestURL: aManifestURL
             });
           });
         }
       });
@@ -1399,17 +1400,17 @@ this.DOMApplicationRegistry = {
           // Set state and fire events.
           app.downloading = false;
           app.downloadAvailable = false;
           app.readyToApplyDownload = true;
           app.updateTime = Date.now();
           DOMApplicationRegistry._saveApps().then(() => {
             DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
               app: app,
-              id: app.id
+              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);
@@ -1493,17 +1494,17 @@ this.DOMApplicationRegistry = {
                 manifestURL: app.manifestURL },
               true);
           }
           this.updateDataStore(this.webapps[id].localId, app.origin,
                                app.manifestURL, aData, app.appStatus);
           this.broadcastMessage("Webapps:UpdateState", {
             app: app,
             manifest: aData,
-            id: app.id
+            manifestURL: app.manifestURL
           });
           this.broadcastMessage("Webapps:FireEvent", {
             eventType: "downloadapplied",
             manifestURL: app.manifestURL
           });
         });
       });
     });
@@ -1533,17 +1534,17 @@ this.DOMApplicationRegistry = {
     aApp.progress = 0;
     DOMApplicationRegistry._saveApps().then(() => {
       DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
         app: {
           downloading: true,
           installState: aApp.installState,
           progress: 0
         },
-        id: aApp.id
+        manifestURL: aApp.manifestURL
       });
       let cacheUpdate = updateSvc.scheduleAppUpdate(
         appcacheURI, docURI, aApp.localId, false, aProfileDir);
 
       // We save the download details for potential further usage like
       // cancelling it.
       let download = {
         cacheUpdate: cacheUpdate,
@@ -1583,17 +1584,16 @@ this.DOMApplicationRegistry = {
       this.notifyAppsRegistryReady();
     }
   },
 
   checkForUpdate: function(aData, aMm) {
     debug("checkForUpdate for " + aData.manifestURL);
 
     function sendError(aError) {
-      debug("checkForUpdate error " + aError);
       aData.error = aError;
       aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
     }
 
     let id = this._appIdForManifestURL(aData.manifestURL);
     let app = this.webapps[id];
 
     // We cannot update an app that does not exists.
@@ -1613,67 +1613,71 @@ this.DOMApplicationRegistry = {
       sendError("APP_IS_DOWNLOADING");
       return;
     }
 
     // If the app is packaged and its manifestURL has an app:// scheme,
     // then we can't have an update.
     if (app.origin.startsWith("app://") &&
         app.manifestURL.startsWith("app://")) {
-      sendError("NOT_UPDATABLE");
+      aData.error = "NOT_UPDATABLE";
+      aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
       return;
     }
 
     // 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);
 #endif
 
     if (onlyCheckAppCache) {
       // Bail out for packaged apps.
       if (app.origin.startsWith("app://")) {
-        sendError("NOT_UPDATABLE");
+        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 }]).then((aResult) => {
         let manifest = aResult[0].manifest;
         if (!manifest.appcache_path) {
-          sendError("NOT_UPDATABLE");
+          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().then(() => {
                 this.broadcastMessage("Webapps:UpdateState", {
                   app: app,
-                  id: app.id
+                  manifestURL: app.manifestURL
                 });
                 this.broadcastMessage("Webapps:FireEvent", {
                   eventType: "downloadavailable",
                   manifestURL: app.manifestURL,
                   requestID: aData.requestID
                 });
               });
             } else {
-              sendError("NOT_UPDATABLE");
+              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);
@@ -1723,17 +1727,17 @@ this.DOMApplicationRegistry = {
             } else {
               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,
-                  id: app.id
+                  manifestURL: app.manifestURL
                 });
                 aMm.sendAsyncMessage("Webapps:FireEvent", {
                   eventType: eventType,
                   manifestURL: app.manifestURL,
                   requestID: aData.requestID
                 });
               });
             }
@@ -1750,17 +1754,17 @@ this.DOMApplicationRegistry = {
           app.lastCheckedUpdate = Date.now();
           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,
-              id: app.id
+              manifestURL: app.manifestURL
             });
             aMm.sendAsyncMessage("Webapps:FireEvent", {
               eventType: eventType,
               manifestURL: app.manifestURL,
               requestID: aData.requestID
             });
           });
         } else {
@@ -1858,17 +1862,17 @@ this.DOMApplicationRegistry = {
     // A package is available: set downloadAvailable to fire the matching
     // event.
     aApp.downloadAvailable = true;
     aApp.downloadSize = manifest.size;
     aApp.updateManifest = aNewManifest;
     this._saveApps().then(() => {
       this.broadcastMessage("Webapps:UpdateState", {
         app: aApp,
-        id: aApp.id
+        manifestURL: aApp.manifestURL
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloadavailable",
         manifestURL: aApp.manifestURL,
         requestID: aData.requestID
       });
     });
   },
@@ -1922,17 +1926,17 @@ this.DOMApplicationRegistry = {
     // Update the registry.
     this.webapps[aId] = aApp;
     this._saveApps().then(() => {
       let reg = DOMApplicationRegistry;
       if (!manifest.appcache_path) {
         reg.broadcastMessage("Webapps:UpdateState", {
           app: aApp,
           manifest: aApp.manifest,
-          id: aApp.id
+          manifestURL: aApp.manifestURL
         });
         reg.broadcastMessage("Webapps:FireEvent", {
           eventType: "downloadapplied",
           manifestURL: aApp.manifestURL,
           requestID: aData.requestID
         });
       } else {
         // Check if the appcache is updatable, and send "downloadavailable" or
@@ -1944,17 +1948,17 @@ this.DOMApplicationRegistry = {
             let eventType =
               aTopic == "offline-cache-update-available" ? "downloadavailable"
                                                          : "downloadapplied";
             aApp.downloadAvailable = (eventType == "downloadavailable");
             reg._saveApps().then(() => {
               reg.broadcastMessage("Webapps:UpdateState", {
                 app: aApp,
                 manifest: aApp.manifest,
-                id: aApp.id
+                manifestURL: aApp.manifestURL
               });
               reg.broadcastMessage("Webapps:FireEvent", {
                 eventType: eventType,
                 manifestURL: aApp.manifestURL,
                 requestID: aData.requestID
               });
             });
           }
@@ -2209,18 +2213,18 @@ this.DOMApplicationRegistry = {
   },
 
   // This function is called after we called the onsuccess callback on the
   // content side. This let the webpage the opportunity to set event handlers
   // on the app before we start firing progress events.
   queuedDownload: {},
   queuedPackageDownload: {},
 
-  onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
-                                                    aDontNeedNetwork) {
+onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
+                                                  aDontNeedNetwork) {
     // If we are offline, register to run when we'll be online.
     if ((Services.io.offline) && !aDontNeedNetwork) {
       let onlineWrapper = {
         observe: function(aSubject, aTopic, aData) {
           Services.obs.removeObserver(onlineWrapper,
                                       "network:offline-status-changed");
           DOMApplicationRegistry.onInstallSuccessAck(aManifestURL);
         }
@@ -2340,16 +2344,17 @@ this.DOMApplicationRegistry = {
       localId = this._nextLocalId();
     }
 
     let app = this._setupApp(aData, id);
 
     let jsonManifest = aData.isPackage ? app.updateManifest : app.manifest;
     this._writeManifestFile(id, aData.isPackage, jsonManifest);
 
+    debug("app.origin: " + app.origin);
     let manifest = new ManifestHelper(jsonManifest, app.origin);
 
     let appObject = this._cloneApp(aData, app, manifest, id, localId);
 
     this.webapps[id] = appObject;
 
     // For package apps, the permissions are not in the mini-manifest, so
     // don't update the permissions yet.
@@ -2383,18 +2388,16 @@ this.DOMApplicationRegistry = {
         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().then(() => {
-      aData.isPackage ? appObject.updateManifest = jsonManifest :
-                        appObject.manifest = jsonManifest;
       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
@@ -2501,17 +2504,17 @@ this.DOMApplicationRegistry = {
       }
 
       this.updateDataStore(this.webapps[aId].localId, aNewApp.origin,
                            aNewApp.manifestURL, aManifest, aNewApp.appStatus);
 
       this.broadcastMessage("Webapps:UpdateState", {
         app: app,
         manifest: aManifest,
-        id: app.id
+        manifestURL: aNewApp.manifestURL
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: ["downloadsuccess", "downloadapplied"],
         manifestURL: aNewApp.manifestURL
       });
       if (aInstallSuccessCallback) {
         aInstallSuccessCallback(aManifest, zipFile.path);
       }
@@ -2611,21 +2614,24 @@ this.DOMApplicationRegistry = {
 
       debug("About to download " + fullPackagePath);
 
       let requestChannel = this._getRequestChannel(fullPackagePath,
                                                    isLocalFileInstall,
                                                    oldApp,
                                                    aNewApp);
 
-      AppDownloadManager.add(aNewApp.manifestURL, {
-        channel: requestChannel,
-        appId: id,
-        previousState: aIsUpdate ? "installed" : "pending"
-      });
+      AppDownloadManager.add(
+        aNewApp.manifestURL,
+        {
+          channel: requestChannel,
+          appId: id,
+          previousState: aIsUpdate ? "installed" : "pending"
+        }
+      );
 
       // We set the 'downloading' flag to true right before starting the fetch.
       oldApp.downloading = true;
 
       // We determine the app's 'installState' according to its previous
       // state. Cancelled download should remain as 'pending'. Successfully
       // installed apps should morph to 'updating'.
       oldApp.installState = aIsUpdate ? "updating" : "pending";
@@ -2638,17 +2644,17 @@ this.DOMApplicationRegistry = {
 
       let responseStatus = requestChannel.responseStatus;
       let oldPackage = (responseStatus == 304 || hash == oldApp.packageHash);
 
       if (oldPackage) {
         debug("package's etag or hash unchanged; sending 'applied' event");
         // The package's Etag or hash has not changed.
         // We send a "applied" event right away.
-        this._sendAppliedEvent(oldApp);
+        this._sendAppliedEvent(aNewApp, oldApp, id);
         return;
       }
 
       let newManifest = yield this._openAndReadPackage(zipFile, oldApp, aNewApp,
               isLocalFileInstall, aIsUpdate, aManifest, requestChannel, hash);
 
       AppDownloadManager.remove(aNewApp.manifestURL);
 
@@ -2778,17 +2784,17 @@ this.DOMApplicationRegistry = {
     return requestChannel;
   },
 
   _sendDownloadProgressEvent: function(aNewApp, aProgress) {
     this.broadcastMessage("Webapps:UpdateState", {
       app: {
         progress: aProgress
       },
-      id: aNewApp.id
+      manifestURL: aNewApp.manifestURL
     });
     this.broadcastMessage("Webapps:FireEvent", {
       eventType: "progress",
       manifestURL: aNewApp.manifestURL
     });
   },
 
   _getPackage: function(aRequestChannel, aId, aOldApp, aNewApp) {
@@ -2906,49 +2912,49 @@ this.DOMApplicationRegistry = {
    * package is identical to the last one we installed.  Presumably we do
    * something similar after updating the app, and we could refactor both cases
    * to use the same code to send the "applied" event.
    *
    * @param aNewApp {Object} the new app data
    * @param aOldApp {Object} the currently stored app data
    * @param aId {String} the unique id of the app
    */
-  _sendAppliedEvent: function(aApp) {
-    aApp.downloading = false;
-    aApp.downloadAvailable = false;
-    aApp.downloadSize = 0;
-    aApp.installState = "installed";
-    aApp.readyToApplyDownload = false;
-    if (aApp.staged && aApp.staged.manifestHash) {
+  _sendAppliedEvent: function(aNewApp, aOldApp, aId) {
+    aOldApp.downloading = false;
+    aOldApp.downloadAvailable = false;
+    aOldApp.downloadSize = 0;
+    aOldApp.installState = "installed";
+    aOldApp.readyToApplyDownload = false;
+    if (aOldApp.staged && aOldApp.staged.manifestHash) {
       // If we're here then the manifest has changed but the package
       // hasn't. Let's clear this, so we don't keep offering
       // a bogus update to the user
-      aApp.manifestHash = aApp.staged.manifestHash;
-      aApp.etag = aApp.staged.etag || aApp.etag;
-      aApp.staged = {};
+      aOldApp.manifestHash = aOldApp.staged.manifestHash;
+      aOldApp.etag = aOldApp.staged.etag || aOldApp.etag;
+      aOldApp.staged = {};
       // Move the staged update manifest to a non staged one.
-      let dirPath = this._getAppDir(aApp.id).path;
+      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().then(() => {
       this.broadcastMessage("Webapps:UpdateState", {
-        app: aApp,
-        id: aApp.id
+        app: aOldApp,
+        manifestURL: aNewApp.manifestURL
       });
       this.broadcastMessage("Webapps:FireEvent", {
-        manifestURL: aApp.manifestURL,
+        manifestURL: aNewApp.manifestURL,
         eventType: ["downloadsuccess", "downloadapplied"]
       });
     });
-    let file = FileUtils.getFile("TmpD", ["webapps", aApp.id], false);
+    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) {
     return Task.spawn((function*() {
@@ -3317,17 +3323,17 @@ this.DOMApplicationRegistry = {
     if (aOldApp.staged) {
       delete aOldApp.staged;
     }
 
     this._saveApps().then(() => {
       this.broadcastMessage("Webapps:UpdateState", {
         app: aOldApp,
         error: aError,
-        id: aNewApp.id
+        manifestURL: aNewApp.manifestURL
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL:  aNewApp.manifestURL
       });
     });
     AppDownloadManager.remove(aNewApp.manifestURL);
   },
@@ -3867,17 +3873,17 @@ let AppcacheObserver = function(aApp) {
 };
 
 AppcacheObserver.prototype = {
   // nsIOfflineCacheUpdateObserver implementation
   _sendProgressEvent: function() {
     let app = this.app;
     DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
       app: app,
-      id: app.id
+      manifestURL: app.manifestURL
     });
     DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
       eventType: "progress",
       manifestURL: app.manifestURL
     });
   },
 
   updateStateChanged: function appObs_Update(aUpdate, aState) {
@@ -3899,30 +3905,30 @@ AppcacheObserver.prototype = {
         return;
       }
 
       app.updateTime = Date.now();
       app.downloading = false;
       app.downloadAvailable = false;
       DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
         app: app,
-        id: app.id
+        manifestURL: app.manifestURL
       });
       DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
         eventType: ["downloadsuccess", "downloadapplied"],
         manifestURL: app.manifestURL
       });
     }
 
     let setError = function appObs_setError(aError) {
       debug("Offlinecache setError to " + aError);
       app.downloading = false;
       DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
         app: app,
-        id: app.id
+        manifestURL: app.manifestURL
       });
       DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
         error: aError,
         eventType: "downloaderror",
         manifestURL: app.manifestURL
       });
       mustSave = true;
     }
--- a/dom/apps/tests/test_packaged_app_common.js
+++ b/dom/apps/tests/test_packaged_app_common.js
@@ -94,17 +94,16 @@ var PackagedTestHelper = (function Packa
       ok(false, "Got unexpected " + evt.target.error.name);
       finish();
     };
 
     navigator.mozApps.mgmt.oninstall = function(evt) {
       var aApp = evt.application;
       aApp.ondownloaderror = function(evt) {
         var error = aApp.downloadError.name;
-        ok(true, "Got downloaderror " + error);
         if (error == aExpectedError) {
           ok(true, "Got expected " + aExpectedError);
           var expected = {
             name: aName,
             manifestURL: aMiniManifestURL,
             installOrigin: gInstallOrigin,
             progress: 0,
             installState: "pending",
--- a/dom/apps/tests/test_packaged_app_update.html
+++ b/dom/apps/tests/test_packaged_app_update.html
@@ -74,25 +74,26 @@ function checkLastAppState(aMiniManifest
 
 function updateApp(aExpectedReady, aPreviousVersion, aNextVersion) {
   var lApp = PackagedTestHelper.gApp;
 
   var ondownloadappliedhandler =
     checkLastAppState.bind(PackagedTestHelper, miniManifestURL, false, false,
                            aNextVersion, PackagedTestHelper.next);
 
-  var ondownloadsuccesshandler =
-    checkLastAppState.bind(undefined, miniManifestURL,
-                           aExpectedReady, false, aPreviousVersion,
-                           function() {
-      navigator.mozApps.mgmt.applyDownload(lApp);
-  });
+    var ondownloadsuccesshandler =
+      checkLastAppState.bind(undefined, miniManifestURL,
+                             aExpectedReady, false, aPreviousVersion,
+                             function() {
+        navigator.mozApps.mgmt.applyDownload(lApp);
+    });
 
-  checkForUpdate(true, ondownloadsuccesshandler, ondownloadappliedhandler, null,
-                 true);
+    checkForUpdate(true, ondownloadsuccesshandler, ondownloadappliedhandler, null,
+                   true);
+
 }
 
 
 var steps = [
   function() {
     // Set up
     PackagedTestHelper.launchableValue =
       SpecialPowers.setAllAppsLaunchable(true);
@@ -169,17 +170,17 @@ var steps = [
   },
   function() {
     ok(true, "== TEST == Update packaged app - Updating a pending app");
     miniManifestURL = PackagedTestHelper.gSJS +
                       "?getManifest=true" +
                       "&appName=arandomname" +
                       "&appToFail1";
     PackagedTestHelper.checkAppDownloadError(miniManifestURL,
-                                            "MANIFEST_MISMATCH", 1, false, true,
+                                            "MANIFEST_MISMATCH", 2, false, true,
                                              "arandomname",
                                              function () {
       checkForUpdate(false, null, null, null, false,
                      function (request) {
         if (request.error.name === "PENDING_APP_NOT_UPDATABLE") {
           ok(true, "Got expected PENDING_APP_NOT_UPDATEABLE");
         } else {
           ok(false, "Got unexpected " + request.error.name);
--- a/dom/apps/tests/test_receipt_operations.html
+++ b/dom/apps/tests/test_receipt_operations.html
@@ -242,9 +242,9 @@ function runTest() {
   finish();
 }
 
 addLoadEvent(go);
 
 </script>
 </pre>
 </body>
-</html>
+</html>
\ No newline at end of file
--- a/toolkit/devtools/server/actors/webapps.js
+++ b/toolkit/devtools/server/actors/webapps.js
@@ -174,17 +174,17 @@ WebappsActor.prototype = {
         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,
-          id: aApp.id
+          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,