Bug 1190661 - Send push only to child processes when in e10s mode. r=smaug
authorNikhil Marathe <nsm.nikhil@gmail.com>
Thu, 30 Jul 2015 20:00:21 -0700
changeset 287918 b8a39e65c778d7c6a6c213e46e5655207f51bded
parent 287917 8caf2b95b2752e55d785fab9ff82c581db9b94f9
child 287919 ef291c49717330a7e9132b075a68baccbd2e8748
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1190661, 1182117
milestone42.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 1190661 - Send push only to child processes when in e10s mode. r=smaug Earlier, the in-process child process message manager and any content process child process message managers received the push event. This is because broadcastAsyncMessage is used, but on e10s we want ServiceWorkers to run in the child process, so the push should only be dispatched to it. This patch introduces a list of child process listeners in the PushService (running on the parent). PushServiceChildPreload sends a message to the PushService iff it is running in a child process. If there are non-zero child listeners, the PushService will send a message to all of them, otherwise it will fall back to broadcastAsyncMessage. We currently do not add support for precise targeting of child processes. This is because until Bug 1182117 is fixed, all child process ServiceWorkerManagers maintain all the service worker registrations internally. Yes this is a bug, but that is the way things are right now. This makes it impossible to distinguish which child should handle the notification for a given origin. Considering we don't ship multi-process e10s, I would like to land this right now. When Bug 1182117 is fixed, we can remove this code since the ServiceWorkerManager will manage registrations in the parent, and it will know which child process is running which ServiceWorker.
dom/push/PushService.jsm
dom/push/PushServiceChildPreload.jsm
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -37,17 +37,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 this.EXPORTED_SYMBOLS = ["PushService"];
 
 const prefs = new Preferences("dom.push.");
 // Set debug first so that all debugging actually works.
 gDebuggingEnabled = prefs.get("debug");
 
 const kCHILD_PROCESS_MESSAGES = ["Push:Register", "Push:Unregister",
-                                 "Push:Registration"];
+                                 "Push:Registration", "Push:RegisterEventNotificationListener",
+                                 "child-process-shutdown"];
 
 const PUSH_SERVICE_UNINIT = 0;
 const PUSH_SERVICE_INIT = 1; // No serverURI
 const PUSH_SERVICE_ACTIVATING = 2;//activating db
 const PUSH_SERVICE_CONNECTION_DISABLE = 3;
 const PUSH_SERVICE_ACTIVE_OFFLINE = 4;
 const PUSH_SERVICE_RUNNING = 5;
 
@@ -80,16 +81,18 @@ const UNINIT_EVENT = 3;
  */
 this.PushService = {
   _service: null,
   _state: PUSH_SERVICE_UNINIT,
   _db: null,
   _options: null,
   _alarmID: null,
 
+  _childListeners: [],
+
   // When serverURI changes (this is used for testing), db is cleaned up and a
   // a new db is started. This events must be sequential.
   _serverURIProcessQueue: null,
   _serverURIProcessEnqueue: function(op) {
     if (!this._serverURIProcessQueue) {
       this._serverURIProcessQueue = Promise.resolve();
     }
 
@@ -585,16 +588,18 @@ this.PushService = {
     Services.obs.removeObserver(this, this._networkStateChangeEventName);
     Services.obs.removeObserver(this, "webapps-clear-data");
     Services.obs.removeObserver(this, "idle-daily");
   },
 
   uninit: function() {
     debug("uninit()");
 
+    this._childListeners = [];
+
     if (this._state == PUSH_SERVICE_UNINIT) {
       return;
     }
 
     this._setState(PUSH_SERVICE_UNINIT);
 
     prefs.ignore("serverURL", this);
     Services.obs.removeObserver(this, "xpcom-shutdown");
@@ -663,19 +668,35 @@ this.PushService = {
       record.scope
     );
 
     let data = {
       originAttributes: record.originAttributes,
       scope: record.scope
     };
 
-    let ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1']
-                 .getService(Ci.nsIMessageListenerManager);
-    ppmm.broadcastAsyncMessage('pushsubscriptionchange', data);
+    this._notifyListeners('pushsubscriptionchange', data);
+  },
+
+  _notifyListeners: function(name, data) {
+    if (this._childListeners.length > 0) {
+      // Try to send messages to all listeners, but remove any that fail since
+      // the receiver is likely gone away.
+      for (var i = this._childListeners.length - 1; i >= 0; --i) {
+        try {
+          this._childListeners[i].sendAsyncMessage(name, data);
+        } catch(e) {
+          this._childListeners.splice(i, 1);
+        }
+      }
+    } else {
+      let ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1']
+                   .getService(Ci.nsIMessageListenerManager);
+      ppmm.broadcastAsyncMessage(name, data);
+    }
   },
 
   // Fires a push-register system message to all applications that have
   // registration.
   _notifyAllAppsRegister: function() {
     debug("notifyAllAppsRegister()");
     // records are objects describing the registration as stored in IndexedDB.
     return this._db.getAllUnexpired().then(records =>
@@ -793,19 +814,17 @@ this.PushService = {
 
     // TODO data.
     let data = {
       payload: message,
       originAttributes: aPushRecord.originAttributes,
       scope: aPushRecord.scope
     };
 
-    let ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1']
-                 .getService(Ci.nsIMessageListenerManager);
-    ppmm.broadcastAsyncMessage('push', data);
+    this._notifyListeners('push', data);
   },
 
   getByKeyID: function(aKeyID) {
     return this._db.getByKeyID(aKeyID);
   },
 
   getAllUnexpired: function() {
     return this._db.getAllUnexpired();
@@ -904,16 +923,33 @@ this.PushService = {
   receiveMessage: function(aMessage) {
     debug("receiveMessage(): " + aMessage.name);
 
     if (kCHILD_PROCESS_MESSAGES.indexOf(aMessage.name) == -1) {
       debug("Invalid message from child " + aMessage.name);
       return;
     }
 
+    if (aMessage.name === "Push:RegisterEventNotificationListener") {
+      debug("Adding child listener");
+      this._childListeners.push(aMessage.target);
+      return;
+    }
+
+    if (aMessage.name === "child-process-shutdown") {
+      debug("Possibly removing child listener");
+      for (var i = this._childListeners.length - 1; i >= 0; --i) {
+        if (this._childListeners[i] == aMessage.target) {
+          debug("Removed child listener");
+          this._childListeners.splice(i, 1);
+        }
+      }
+      return;
+    }
+
     if (!aMessage.target.assertPermission("push")) {
       debug("Got message from a child process that does not have 'push' permission.");
       return null;
     }
 
     let mm = aMessage.target.QueryInterface(Ci.nsIMessageSender);
     let pageRecord = aMessage.data;
 
--- a/dom/push/PushServiceChildPreload.jsm
+++ b/dom/push/PushServiceChildPreload.jsm
@@ -1,27 +1,37 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [];
 
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
 const Cu = Components.utils;
-
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "swm",
                                    "@mozilla.org/serviceworkers/manager;1",
                                    "nsIServiceWorkerManager");
 
+let processType = Cc["@mozilla.org/xre/app-info;1"]
+                    .getService(Ci.nsIXULRuntime).processType;
+let isParent = processType === Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+
 Services.cpmm.addMessageListener("push", function (aMessage) {
   swm.sendPushEvent(aMessage.data.originAttributes,
                     aMessage.data.scope, aMessage.data.payload);
 });
 
 Services.cpmm.addMessageListener("pushsubscriptionchange", function (aMessage) {
   swm.sendPushSubscriptionChangeEvent(aMessage.data.originAttributes,
                                       aMessage.data.scope);
 });
+
+if (!isParent) {
+  Services.cpmm.sendAsyncMessage("Push:RegisterEventNotificationListener", null, null, null);
+}