Bug 1031609 - Observer service shims (r=mconley)
authorBill McCloskey <wmccloskey@mozilla.com>
Mon, 14 Jul 2014 22:10:06 -0700
changeset 215897 2edb6251c7952fe7c483d970dcb0fc3da81c6df7
parent 215896 9bf7525ef861a3b19eb5429acc9cd2ea817a5f69
child 215898 8723969ebaec6f57c1ff0d74effbcb40c0b2111d
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1031609
milestone33.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 1031609 - Observer service shims (r=mconley)
toolkit/components/addoncompat/RemoteAddonsChild.jsm
toolkit/components/addoncompat/RemoteAddonsParent.jsm
--- a/toolkit/components/addoncompat/RemoteAddonsChild.jsm
+++ b/toolkit/components/addoncompat/RemoteAddonsChild.jsm
@@ -145,12 +145,40 @@ let ContentPolicyChild = {
     if (outer) {
       throw Cr.NS_ERROR_NO_AGGREGATION;
     }
     return this.QueryInterface(iid);
   },
 };
 ContentPolicyChild.init();
 
+// This code registers observers in the child whenever an add-on in
+// the parent asks for notifications on the given topic.
+let ObserverChild = {
+  init: function() {
+    NotificationTracker.watch("observer", (path, count) => this.track(path, count));
+  },
+
+  track: function(path, count) {
+    let topic = path[1];
+    if (count) {
+      Services.obs.addObserver(this, topic, false);
+    } else {
+      Services.obs.removeObserver(this, topic);
+    }
+  },
+
+  observe: function(subject, topic, data) {
+    let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
+               .getService(Ci.nsISyncMessageSender);
+    cpmm.sendRpcMessage("Addons:Observer:Run", {}, {
+      topic: topic,
+      subject: subject,
+      data: data
+    });
+  }
+};
+ObserverChild.init();
+
 let RemoteAddonsChild = {
   init: function(global) {
   },
 };
--- a/toolkit/components/addoncompat/RemoteAddonsParent.jsm
+++ b/toolkit/components/addoncompat/RemoteAddonsParent.jsm
@@ -167,28 +167,103 @@ CategoryManagerInterposition.methods.del
   function(addon, target, category, entry, persist) {
     if (category == "content-policy") {
       ContentPolicyParent.remoteContentPolicy(entry);
     }
 
     target.deleteCategoryEntry(category, entry, persist);
   };
 
+// This object manages add-on observers that might fire in the child
+// process. Rather than managing the observers itself, it uses the
+// parent's observer service. When an add-on listens on topic T,
+// ObserverParent asks the child process to listen on T. It also adds
+// an observer in the parent for the topic e10s-T. When the T observer
+// fires in the child, the parent fires all the e10s-T observers,
+// passing them CPOWs for the subject and data. We don't want to use T
+// in the parent because there might be non-add-on T observers that
+// won't expect to get notified in this case.
+let ObserverParent = {
+  init: function() {
+    let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+               .getService(Ci.nsIMessageBroadcaster);
+    ppmm.addMessageListener("Addons:Observer:Run", this);
+  },
+
+  addObserver: function(observer, topic, ownsWeak) {
+    Services.obs.addObserver(observer, "e10s-" + topic, ownsWeak);
+    NotificationTracker.add(["observer", topic]);
+  },
+
+  removeObserver: function(observer, topic) {
+    Services.obs.removeObserver(observer, "e10s-" + topic);
+    NotificationTracker.remove(["observer", topic]);
+  },
+
+  receiveMessage: function(msg) {
+    switch (msg.name) {
+      case "Addons:Observer:Run":
+        this.notify(msg.objects.subject, msg.objects.topic, msg.objects.data);
+        break;
+    }
+  },
+
+  notify: function(subject, topic, data) {
+    let e = Services.obs.enumerateObservers("e10s-" + topic);
+    while (e.hasMoreElements()) {
+      let obs = e.getNext().QueryInterface(Ci.nsIObserver);
+      try {
+        obs.observe(subject, topic, data);
+      } catch (e) {
+        Cu.reportError(e);
+      }
+    }
+  }
+};
+ObserverParent.init();
+
+// We only forward observers for these topics.
+let TOPIC_WHITELIST = ["content-document-global-created",
+                       "document-element-inserted",];
+
+// This interposition listens for
+// nsIObserverService.{add,remove}Observer.
+let ObserverInterposition = new Interposition();
+
+ObserverInterposition.methods.addObserver =
+  function(addon, target, observer, topic, ownsWeak) {
+    if (TOPIC_WHITELIST.indexOf(topic) >= 0) {
+      ObserverParent.addObserver(observer, topic);
+    }
+
+    target.addObserver(observer, topic, ownsWeak);
+  };
+
+ObserverInterposition.methods.removeObserver =
+  function(addon, target, observer, topic) {
+    if (TOPIC_WHITELIST.indexOf(topic) >= 0) {
+      ObserverParent.removeObserver(observer, topic);
+    }
+
+    target.removeObserver(observer, topic);
+  };
+
 let RemoteAddonsParent = {
   init: function() {
   },
 
   getInterfaceInterpositions: function() {
     let result = {};
 
     function register(intf, interp) {
       result[intf.number] = interp;
     }
 
     register(Ci.nsICategoryManager, CategoryManagerInterposition);
+    register(Ci.nsIObserverService, ObserverInterposition);
 
     return result;
   },
 
   getTaggedInterpositions: function() {
     return {};
   },
 };