Bug 777204 - Webapps.jsm broadcasts information to all content processes [r=gwagner]
authorFabrice Desré <fabrice@mozilla.com>
Tue, 18 Sep 2012 10:34:55 -0700
changeset 110985 47825c36d6e8c141700931ce924fea316d51efd6
parent 110984 5560ff65b090bcde611394c2b741bc2d8183f286
child 110986 cf0512c87af743650f613a6e6f15a26485a0e65d
push idunknown
push userunknown
push dateunknown
reviewersgwagner
bugs777204
milestone18.0a1
Bug 777204 - Webapps.jsm broadcasts information to all content processes [r=gwagner]
b2g/chrome/content/shell.js
browser/modules/webappsUI.jsm
dom/apps/src/Webapps.js
dom/apps/src/Webapps.jsm
mobile/android/chrome/content/browser.js
webapprt/WebappsHandler.jsm
webapprt/content/mochitest.js
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -589,16 +589,18 @@ var WebappsHelper = {
       case "webapps-install-denied":
         DOMApplicationRegistry.denyInstall(installer);
         break;
     }
   },
 
   observe: function webapps_observe(subject, topic, data) {
     let json = JSON.parse(data);
+    json.mm = subject;
+
     switch(topic) {
       case "webapps-launch":
         DOMApplicationRegistry.getManifestFor(json.origin, function(aManifest) {
           if (!aManifest)
             return;
 
           let manifest = new DOMApplicationManifest(aManifest, json.origin);
           shell.sendChromeEvent({
--- a/browser/modules/webappsUI.jsm
+++ b/browser/modules/webappsUI.jsm
@@ -24,16 +24,17 @@ let webappsUI = {
   uninit: function webappsUI_uninit() {
     Services.obs.removeObserver(this, "webapps-ask-install");
     Services.obs.removeObserver(this, "webapps-launch");
     Services.obs.removeObserver(this, "webapps-uninstall");
   },
 
   observe: function webappsUI_observe(aSubject, aTopic, aData) {
     let data = JSON.parse(aData);
+    data.mm = aSubject;
 
     switch(aTopic) {
       case "webapps-ask-install":
         let [chromeWin, browser] = this._getBrowserForId(data.oid);
         if (chromeWin)
           this.doInstall(data, browser, chromeWin);
         break;
       case "webapps-launch":
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -63,16 +63,17 @@ WebappsRegistry.prototype = {
     if (!req)
       return;
     let app = msg.app;
     switch (aMessage.name) {
       case "Webapps:Install:Return:OK":
         Services.DOMRequest.fireSuccess(req, createApplicationObject(this._window, app));
         break;
       case "Webapps:Install:Return:KO":
+      dump("XxXxX Webapps:Install:Return:KO\n");
         Services.DOMRequest.fireError(req, msg.error || "DENIED");
         break;
       case "Webapps:GetSelf:Return:OK":
         if (msg.apps.length) {
           app = msg.apps[0];
           Services.DOMRequest.fireSuccess(req, createApplicationObject(this._window, app));
         } else {
           Services.DOMRequest.fireSuccess(req, null);
@@ -163,16 +164,18 @@ WebappsRegistry.prototype = {
   get mgmt() {
     if (!this._mgmt)
       this._mgmt = new WebappsApplicationMgmt(this._window);
     return this._mgmt;
   },
 
   uninit: function() {
     this._mgmt = null;
+    cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
+                          ["Webapps:Install:Return:OK"]);
   },
 
   // mozIDOMApplicationRegistry2 implementation
 
   installPackage: function(aPackageURL, aParams) {
     let request = this.createRequest();
     let requestID = this.getRequestId(request);
 
@@ -193,16 +196,18 @@ WebappsRegistry.prototype = {
   // nsIDOMGlobalPropertyInitializer implementation
   init: function(aWindow) {
     this.initHelper(aWindow, ["Webapps:Install:Return:OK", "Webapps:Install:Return:KO",
                               "Webapps:GetInstalled:Return:OK",
                               "Webapps:GetSelf:Return:OK", "Webapps:GetSelf:Return:KO"]);
 
     let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
     this._id = util.outerWindowID;
+    cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
+                          ["Webapps:Install:Return:OK"]);
   },
 
   classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplicationRegistry,
 #ifdef MOZ_PHOENIX
 # Firefox Desktop: installPackage not implemented
 #elifdef ANDROID
@@ -265,16 +270,18 @@ WebappsApplication.prototype = {
     this.installTime = aApp.installTime;
     this.status = "installed";
     this.removable = aApp.removable;
     this.progress = NaN;
     this._onprogress = null;
     this.initHelper(aWindow, ["Webapps:Uninstall:Return:OK",
                               "Webapps:Uninstall:Return:KO",
                               "Webapps:OfflineCache"]);
+    cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
+                          ["Webapps:Uninstall:Return:OK", "Webapps:OfflineCache"]);
   },
 
   set onprogress(aCallback) {
     this._onprogress = aCallback;
   },
 
   get onprogress() {
     return this._onprogress;
@@ -295,16 +302,18 @@ WebappsApplication.prototype = {
     cpmm.sendAsyncMessage("Webapps:Uninstall", { origin: this.origin,
                                                  oid: this._id,
                                                  requestID: this.getRequestId(request) });
     return request;
   },
 
   uninit: function() {
     this._onprogress = null;
+    cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
+                          ["Webapps:Uninstall:Return:OK", "Webapps:OfflineCache"]);
   },
 
   receiveMessage: function(aMessage) {
     var msg = aMessage.json;
     let req = this.takeRequest(msg.requestID);
     if ((msg.oid != this._id || !req) && aMessage.name !== "Webapps:OfflineCache")
       return;
     switch (aMessage.name) {
@@ -351,32 +360,37 @@ function WebappsApplicationMgmt(aWindow)
 
   //only pages with perm set can use some functions
   this.hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
 
   this.initHelper(aWindow, ["Webapps:GetAll:Return:OK", "Webapps:GetAll:Return:KO",
                             "Webapps:Install:Return:OK", "Webapps:Uninstall:Return:OK",
                             "Webapps:GetNotInstalled:Return:OK"]);
 
+  cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
+                        ["Webapps:Install:Return:OK", "Webapps:Uninstall:Return:OK"]);
+
   this._oninstall = null;
   this._onuninstall = null;
 }
 
 WebappsApplicationMgmt.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
   __exposedProps__: {
                       getAll: 'r',
                       getNotInstalled: 'r',
                       oninstall: 'rw',
                       onuninstall: 'rw'
                      },
 
   uninit: function() {
     this._oninstall = null;
     this._onuninstall = null;
+    cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
+                          ["Webapps:Install:Return:OK", "Webapps:Uninstall:Return:OK"]);
   },
 
   getAll: function() {
     let request = this.createRequest();
     cpmm.sendAsyncMessage("Webapps:GetAll", { oid: this._id,
                                               requestID: this.getRequestId(request),
                                               hasPrivileges: this.hasPrivileges });
     return request;
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -55,17 +55,18 @@ let DOMApplicationRegistry = {
   allAppsLaunchable: false,
 
   init: function() {
     this.messages = ["Webapps:Install", "Webapps:Uninstall",
                      "Webapps:GetSelf",
                      "Webapps:GetInstalled", "Webapps:GetNotInstalled",
                      "Webapps:Launch", "Webapps:GetAll",
                      "Webapps:InstallPackage", "Webapps:GetBasePath",
-                     "Webapps:GetList"];
+                     "Webapps:GetList", "Webapps:RegisterForMessages",
+                     "Webapps:UnregisterForMessages"];
 
     this.messages.forEach((function(msgName) {
       ppmm.addMessageListener(msgName, this);
     }).bind(this));
 
     Services.obs.addObserver(this, "xpcom-shutdown", false);
 
     this.appsFile = FileUtils.getFile(DIRECTORY_NAME,
@@ -314,62 +315,108 @@ let DOMApplicationRegistry = {
     } catch (ex) {
       Cu.reportError("DOMApplicationRegistry: Could not read from " +
                      aFile.path + " : " + ex);
       if (aCallback)
         aCallback(null);
     }
   },
 
+  addMessageListener: function(aMsgNames, aMm) {
+    aMsgNames.forEach(function (aMsgName) {
+      if (!(aMsgName in this.children)) {
+        this.children[aMsgName] = [];
+      }
+      this.children[aMsgName].push(aMm);
+    }, this);
+  },
+
+  removeMessageListener: function(aMsgNames, aMm) {
+    aMsgNames.forEach(function (aMsgName) {
+      if (!(aMsgName in this.children)) {
+        return;
+      }
+
+      let index;
+      if ((index = this.children[aMsgName].indexOf(aMm)) != -1) {
+        this.children[aMsgName].splice(index, 1);
+      }
+    }, this);
+  },
+
   receiveMessage: function(aMessage) {
     // nsIPrefBranch throws if pref does not exist, faster to simply write
     // the pref instead of first checking if it is false.
     Services.prefs.setBoolPref("dom.mozApps.used", true);
 
     let msg = aMessage.json;
+    let mm = aMessage.target;
+    msg.mm = mm;
 
     switch (aMessage.name) {
       case "Webapps:Install":
         // always ask for UI to install
-        Services.obs.notifyObservers(this, "webapps-ask-install", JSON.stringify(msg));
+        Services.obs.notifyObservers(mm, "webapps-ask-install", JSON.stringify(msg));
         break;
       case "Webapps:GetSelf":
-        this.getSelf(msg);
+        this.getSelf(msg, mm);
         break;
       case "Webapps:Uninstall":
-        Services.obs.notifyObservers(this, "webapps-uninstall", JSON.stringify(msg));
+        Services.obs.notifyObservers(mm, "webapps-uninstall", JSON.stringify(msg));
         this.uninstall(msg);
         break;
       case "Webapps:Launch":
-        Services.obs.notifyObservers(this, "webapps-launch", JSON.stringify(msg));
+        Services.obs.notifyObservers(mm, "webapps-launch", JSON.stringify(msg));
         break;
       case "Webapps:GetInstalled":
-        this.getInstalled(msg);
+        this.getInstalled(msg, mm);
         break;
       case "Webapps:GetNotInstalled":
-        this.getNotInstalled(msg);
+        this.getNotInstalled(msg, mm);
         break;
       case "Webapps:GetAll":
         if (msg.hasPrivileges)
-          this.getAll(msg);
+          this.getAll(msg, mm);
         else
-          ppmm.broadcastAsyncMessage("Webapps:GetAll:Return:KO", msg);
+          mm.sendAsyncMessage("Webapps:GetAll:Return:KO", msg);
         break;
       case "Webapps:InstallPackage":
-        this.installPackage(msg);
+        this.installPackage(msg, mm);
         break;
       case "Webapps:GetBasePath":
         return this.webapps[msg.id].basePath;
         break;
+      case "Webapps:RegisterForMessages":
+        this.addMessageListener(msg, mm);
+        break;
+      case "Webapps:UnregisterForMessages":
+        this.removeMessageListener(msg, mm);
+        break;
       case "Webapps:GetList":
-        this.children.push(aMessage.target);
+        this.addMessageListener(["Webapps:AddApp", "Webapps:RemoveApp"], mm);
         return this.webapps;
     }
   },
 
+  // Some messages can be listened by several content processes:
+  // Webapps:AddApp
+  // Webapps:RemoveApp
+  // Webapps:Install:Return:OK
+  // Webapps:Uninstall:Return:OK
+  // Webapps:OfflineCache
+  broadcastMessage: function broadcastMessage(aMsgName, aContent) {
+    if (!(aMsgName in this.children)) {
+      return;
+    }
+
+    this.children[aMsgName].forEach(function _doBroadcast(aMsgMgr) {
+      aMsgMgr.sendAsyncMessage(aMsgName, aContent);
+    });
+  },
+
   _getAppDir: function(aId) {
     return FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
   },
 
   _writeFile: function ss_writeFile(aFile, aData, aCallbak) {
     // Initialize the file output stream.
     let ostream = FileUtils.openSafeFileOutputStream(aFile);
 
@@ -391,17 +438,17 @@ let DOMApplicationRegistry = {
     if (packageId) {
       let dir = FileUtils.getDir("TmpD", ["webapps", packageId],
                                  true, true);
       try {
         dir.remove(true);
       } catch(e) {
       }
     }
-    ppmm.broadcastAsyncMessage("Webapps:Install:Return:KO", aData);
+    aData.mm.sendAsyncMessage("Webapps:Install:Return:KO", aData);
   },
 
   confirmInstall: function(aData, aFromSync, aProfileDir, aOfflineCacheObserver) {
     let app = aData.app;
     app.removable = true;
     let id = app.syncId || this._appId(app.origin);
     let localId = this.getAppLocalIdByManifestURL(app.manifestURL);
 
@@ -453,21 +500,19 @@ let DOMApplicationRegistry = {
 
     appObject.status = "installed";
     appObject.name = app.manifest.name;
 
     let manifest = new DOMApplicationManifest(app.manifest, app.origin);
 
     if (!aFromSync)
       this._saveApps((function() {
-        ppmm.broadcastAsyncMessage("Webapps:Install:Return:OK", aData);
+        this.broadcastMessage("Webapps:Install:Return:OK", aData);
         Services.obs.notifyObservers(this, "webapps-sync-install", appNote);
-        this.children.forEach(function(aMsgMgr) {
-          aMsgMgr.sendAsyncMessage("Webapps:AddApp", { id: id, app: appObject });
-        });
+        this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject });
       }).bind(this));
 
 #ifdef MOZ_SYS_MSG
     this._registerSystemMessages(app.manifest, app);
     this._registerActivities(app.manifest, app);
 #endif
 
     // if the manifest has an appcache_path property, use it to populate the appcache
@@ -534,17 +579,17 @@ let DOMApplicationRegistry = {
       aData[index].manifest = aJSON;
       if (index == aData.length - 1)
         aFinalCallback(aData);
       else
         this._readManifests(aData, aFinalCallback, index + 1);
     }).bind(this));
   },
 
-  installPackage: function(aData) {
+  installPackage: function(aData, aMm) {
     // Here are the steps when installing a package:
     // - create a temp directory where to store the app.
     // - download the zip in this directory.
     // - extract the manifest from the zip and check it.
     // - ask confirmation to the user.
     // - add the new app to the registry.
     // If we fail at any step, we backout the previous ones and return an error.
 
@@ -579,20 +624,20 @@ let DOMApplicationRegistry = {
       return true;
     }
 
     // Removes the directory we created, and sends an error to the DOM side.
     function cleanup(aError) {
       try {
         dir.remove(true);
       } catch (e) { }
-      ppmm.broadcastAsyncMessage("Webapps:Install:Return:KO",
-                            { oid: aData.oid,
-                              requestID: aData.requestID,
-                              error: aError });
+      aMm.sendAsyncMessage("Webapps:Install:Return:KO",
+                           { oid: aData.oid,
+                             requestID: aData.requestID,
+                             error: aError });
     }
 
     function getInferedStatus() {
       // XXX Update once we have digital signatures (bug 772365)
       return Ci.nsIPrincipal.APP_STATUS_INSTALLED;
     }
 
     function getAppManifestStatus(aManifest) {
@@ -720,101 +765,99 @@ let DOMApplicationRegistry = {
       let dir = this._getAppDir(id);
       try {
         dir.remove(true);
       } catch (e) {}
 
       delete this.webapps[id];
 
       this._saveApps((function() {
-        ppmm.broadcastAsyncMessage("Webapps:Uninstall:Return:OK", aData);
+        this.broadcastMessage("Webapps:Uninstall:Return:OK", aData);
         Services.obs.notifyObservers(this, "webapps-sync-uninstall", appNote);
-        this.children.forEach(function(aMsgMgr) {
-          aMsgMgr.broadcastAsyncMessage("Webapps:RemoveApp", { id: id });
-        });
+        this.broadcastMessage("Webapps:RemoveApp", { id: id });
       }).bind(this));
     }
 
     if (!found) {
-      ppmm.broadcastAsyncMessage("Webapps:Uninstall:Return:KO", aData);
+      aData.mm.broadcastMessage("Webapps:Uninstall:Return:KO", aData);
     }
   },
 
-  getSelf: function(aData) {
+  getSelf: function(aData, aMm) {
     aData.apps = [];
     let tmp = [];
     let id = this._appId(aData.origin);
 
     if (id && this._isLaunchable(this.webapps[id].origin)) {
       let app = AppsUtils.cloneAppObject(this.webapps[id]);
       aData.apps.push(app);
       tmp.push({ id: id });
     }
 
     this._readManifests(tmp, (function(aResult) {
       for (let i = 0; i < aResult.length; i++)
         aData.apps[i].manifest = aResult[i].manifest;
-      ppmm.broadcastAsyncMessage("Webapps:GetSelf:Return:OK", aData);
+      aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData);
     }).bind(this));
   },
 
-  getInstalled: function(aData) {
+  getInstalled: function(aData, aMm) {
     aData.apps = [];
     let tmp = [];
 
     for (let id in this.webapps) {
       if (this.webapps[id].installOrigin == aData.origin &&
           this._isLaunchable(this.webapps[id].origin)) {
         aData.apps.push(AppsUtils.cloneAppObject(this.webapps[id]));
         tmp.push({ id: id });
       }
     }
 
     this._readManifests(tmp, (function(aResult) {
       for (let i = 0; i < aResult.length; i++)
         aData.apps[i].manifest = aResult[i].manifest;
-      ppmm.broadcastAsyncMessage("Webapps:GetInstalled:Return:OK", aData);
+      aMm.sendAsyncMessage("Webapps:GetInstalled:Return:OK", aData);
     }).bind(this));
   },
 
-  getNotInstalled: function(aData) {
+  getNotInstalled: function(aData, aMm) {
     aData.apps = [];
     let tmp = [];
 
     for (let id in this.webapps) {
       if (!this._isLaunchable(this.webapps[id].origin)) {
         aData.apps.push(AppsUtils.cloneAppObject(this.webapps[id]));
         tmp.push({ id: id });
       }
     }
 
     this._readManifests(tmp, (function(aResult) {
       for (let i = 0; i < aResult.length; i++)
         aData.apps[i].manifest = aResult[i].manifest;
-      ppmm.broadcastAsyncMessage("Webapps:GetNotInstalled:Return:OK", aData);
+      aMm.sendAsyncMessage("Webapps:GetNotInstalled:Return:OK", aData);
     }).bind(this));
   },
 
-  getAll: function(aData) {
+  getAll: function(aData, aMm) {
     aData.apps = [];
     let tmp = [];
 
     for (let id in this.webapps) {
       let app = AppsUtils.cloneAppObject(this.webapps[id]);
       if (!this._isLaunchable(app.origin))
         continue;
 
       aData.apps.push(app);
       tmp.push({ id: id });
     }
 
     this._readManifests(tmp, (function(aResult) {
       for (let i = 0; i < aResult.length; i++)
         aData.apps[i].manifest = aResult[i].manifest;
-      ppmm.broadcastAsyncMessage("Webapps:GetAll:Return:OK", aData);
+      aMm.sendAsyncMessage("Webapps:GetAll:Return:OK", aData);
     }).bind(this));
   },
 
   getManifestFor: function(aOrigin, aCallback) {
     if (!aCallback)
       return;
 
     let id = this._appId(aOrigin);
@@ -878,25 +921,25 @@ let DOMApplicationRegistry = {
           continue;
         let origin = this.webapps[record.id].origin;
         delete this.webapps[record.id];
         let dir = this._getAppDir(record.id);
         try {
           dir.remove(true);
         } catch (e) {
         }
-        ppmm.broadcastAsyncMessage("Webapps:Uninstall:Return:OK", { origin: origin });
+        this.broadcastMessage("Webapps:Uninstall:Return:OK", { origin: origin });
       } else {
         if (this.webapps[record.id]) {
           this.webapps[record.id] = record.value;
           delete this.webapps[record.id].manifest;
         } else {
           let data = { app: record.value };
           this.confirmInstall(data, true);
-          ppmm.broadcastAsyncMessage("Webapps:Install:Return:OK", data);
+          this.broadcastMessage("Webapps:Install:Return:OK", data);
         }
       }
     }
     this._saveApps(aCallback);
   },
 
   getAllIDs: function() {
     let apps = {};
@@ -992,17 +1035,19 @@ AppcacheObserver.prototype = {
   // nsIOfflineCacheUpdateObserver implementation
   updateStateChanged: function appObs_Update(aUpdate, aState) {
     let mustSave = false;
     let app = this.app;
 
     let setStatus = function appObs_setStatus(aStatus) {
       mustSave = (app.status != aStatus);
       app.status = aStatus;
-      ppmm.broadcastAsyncMessage("Webapps:OfflineCache", { manifest: app.manifestURL, status: aStatus });
+      DOMApplicationRegistry.broadcastMessage("Webapps:OfflineCache",
+                                              { manifest: app.manifestURL,
+                                                status: aStatus });
     }
 
     switch (aState) {
       case Ci.nsIOfflineCacheUpdateObserver.STATE_ERROR:
         aUpdate.removeObserver(this);
         setStatus("cache-error");
         break;
       case Ci.nsIOfflineCacheUpdateObserver.STATE_NOUPDATE:
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -6155,16 +6155,17 @@ var WebappsUI = {
     Services.obs.removeObserver(this, "WebApps:InstallMarketplace", false);
   },
 
   DEFAULT_PREFS_FILENAME: "default-prefs.js",
 
   observe: function observe(aSubject, aTopic, aData) {
     let data = {};
     try { data = JSON.parse(aData); }
+    data.mm = aSubject;
     catch(ex) { }
     switch (aTopic) {
       case "webapps-install-error":
         let msg = "";
         switch (aData) {
           case "INVALID_MANIFEST":
           case "MANIFEST_PARSE_ERROR":
             msg = Strings.browser.GetStringFromName("webapps.manifestInstallError");
--- a/webapprt/WebappsHandler.jsm
+++ b/webapprt/WebappsHandler.jsm
@@ -19,16 +19,17 @@ let WebappsHandler = {
   init: function() {
     Services.obs.addObserver(this, "webapps-ask-install", false);
     Services.obs.addObserver(this, "webapps-launch", false);
     Services.obs.addObserver(this, "webapps-uninstall", false);
   },
 
   observe: function(subject, topic, data) {
     data = JSON.parse(data);
+    data.mm = subject;
 
     switch (topic) {
       case "webapps-ask-install":
         let chromeWin = this._getWindowByOuterId(data.oid);
         if (chromeWin)
           this.doInstall(data, chromeWin);
         break;
       case "webapps-launch":
--- a/webapprt/content/mochitest.js
+++ b/webapprt/content/mochitest.js
@@ -56,16 +56,17 @@ function becomeWebapp(manifestURL, param
     // We load DOMApplicationRegistry into a local scope to avoid appearing
     // to leak it.
 
     let scope = {};
     Cu.import("resource://gre/modules/Webapps.jsm", scope);
     scope.DOMApplicationRegistry.confirmInstall(JSON.parse(data));
 
     let installRecord = JSON.parse(data);
+    installRecord.mm = subj;
     installRecord.registryDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
     WebappRT.config = installRecord;
 
     onBecome();
   }
   Services.obs.addObserver(observeInstall, "webapps-ask-install", false);
 
   // Step 1: Install the app at the URL specified by the manifest.