Bug 1322586 - 2. Support content event forwarding in Messaging.jsm; r=esawin draft
authorJim Chen <nchen@mozilla.com>
Thu, 20 Jul 2017 17:52:13 -0400
changeset 612607 3ab93052de3676244843c55c50ae5ade63ab8f11
parent 612606 99170bc0b19d605dde4dbf08690bb31d310ed113
child 612608 fe949fde01ddba207b12f2bd49e52ea2bc8b8c5e
push id69550
push userbmo:nchen@mozilla.com
push dateThu, 20 Jul 2017 21:53:06 +0000
reviewersesawin
bugs1322586
milestone56.0a1
Bug 1322586 - 2. Support content event forwarding in Messaging.jsm; r=esawin For a lot of GeckoView content process code, we send a message from content to chrome, using a message manager, only to forward the message to Java as an event, using an event dispatcher. This patch gives Messaging.jsm the ability to be used in content process directly, and lets events transparently pass through to Java. MozReview-Commit-ID: 15dKKpQCXqJ
mobile/android/modules/geckoview/Messaging.jsm
--- a/mobile/android/modules/geckoview/Messaging.jsm
+++ b/mobile/android/modules/geckoview/Messaging.jsm
@@ -1,25 +1,29 @@
 /* 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"
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 this.EXPORTED_SYMBOLS = ["sendMessageToJava", "Messaging", "EventDispatcher"];
 
-XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
+XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "UUIDGen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
+const IS_PARENT_PROCESS = (Services.appinfo.processType ==
+                           Services.appinfo.PROCESS_TYPE_DEFAULT);
+
 function sendMessageToJava(aMessage, aCallback) {
   Cu.reportError("sendMessageToJava is deprecated. Use EventDispatcher instead.");
 
   if (aCallback) {
     EventDispatcher.instance.sendRequestForResult(aMessage)
       .then(result => aCallback(result, null),
             error => aCallback(null, error));
   } else {
@@ -34,39 +38,72 @@ function DispatcherDelegate(dispatcher) 
 DispatcherDelegate.prototype = {
   /**
    * Register a listener to be notified of event(s).
    *
    * @param listener Target listener implementing nsIAndroidEventListener.
    * @param events   String or array of strings of events to listen to.
    */
   registerListener: function (listener, events) {
+    if (!IS_PARENT_PROCESS) {
+      throw new Error("Can only listen in parent process");
+    }
     this._dispatcher.registerListener(listener, events);
   },
 
   /**
    * Unregister a previously-registered listener.
    *
    * @param listener Registered listener implementing nsIAndroidEventListener.
    * @param events   String or array of strings of events to stop listening to.
    */
   unregisterListener: function (listener, events) {
+    if (!IS_PARENT_PROCESS) {
+      throw new Error("Can only listen in parent process");
+    }
     this._dispatcher.unregisterListener(listener, events);
   },
 
   /**
    * Dispatch an event to registered listeners for that event, and pass an
    * optional data object and/or a optional callback interface to the
    * listeners.
    *
    * @param event    Name of event to dispatch.
    * @param data     Optional object containing data for the event.
    * @param callback Optional callback implementing nsIAndroidEventCallback.
    */
   dispatch: function (event, data, callback) {
+    if (!IS_PARENT_PROCESS) {
+      let mm = this._dispatcher || Services.cpmm;
+      let data = {
+        global: !this._dispatcher,
+        event: event,
+        data: data,
+      };
+
+      if (callback) {
+        data.uuid = UUIDGen.generateUUID().toString();
+        mm.addMessageListener("GeckoView:MessagingReply", function listener(msg) {
+          if (msg.data.uuid === data.uuid) {
+            mm.removeMessageListener(msg.name, listener);
+            if (msg.data.type === "success") {
+              callback.onSuccess(msg.data.response);
+            } else if (msg.data.type === "error") {
+              callback.onError(msg.data.response);
+            } else {
+              throw new Error("invalid reply type");
+            }
+          }
+        });
+      }
+
+      mm.sendAsyncMessage("GeckoView:Messaging", data);
+      return;
+    }
     this._dispatcher.dispatch(event, data, callback);
   },
 
   /**
    * Implementations of Messaging APIs for backwards compatibility.
    */
 
   /**
@@ -87,18 +124,18 @@ DispatcherDelegate.prototype = {
    * @returns A Promise resolving to the response
    */
   sendRequestForResult: function (msg) {
     return new Promise((resolve, reject) => {
       let type = msg.type;
       msg.type = undefined;
 
       this.dispatch(type, msg, {
-        onSuccess: response => resolve(response),
-        onError: response => reject(response)
+        onSuccess: resolve,
+        onError: reject,
       });
     });
   },
 
   /**
    * Add a listener for the given event.
    *
    * Only one request listener can be registered for a given event.
@@ -170,28 +207,67 @@ DispatcherDelegate.prototype = {
           stack: e.stack || Components.stack.formattedStack,
         });
       }
     }),
   },
 };
 
 var EventDispatcher = {
-  instance: new DispatcherDelegate(Services.androidBridge),
+  instance: new DispatcherDelegate(IS_PARENT_PROCESS ? Services.androidBridge : undefined),
 
   for: function (window) {
+    if (!IS_PARENT_PROCESS) {
+      if (!window.messageManager) {
+        throw new Error("window does not have message manager");
+      }
+      return new DispatcherDelegate(window.messageManager);
+    }
     let view = window && window.arguments && window.arguments[0] &&
         window.arguments[0].QueryInterface(Ci.nsIAndroidView);
     if (!view) {
       throw new Error("window is not a GeckoView-connected window");
     }
     return new DispatcherDelegate(view);
   },
+
+  receiveMessage: function (aMsg) {
+    // aMsg.data includes keys: global, event, data, uuid
+    let callback;
+    if (aMsg.data.uuid) {
+      let reply = (type, response) => {
+        let mm = aMsg.data.global ? aMsg.target : aMsg.target.messageManager;
+        mm.sendAsyncMessage("GeckoView:MessagingReply", {
+          type: type,
+          response: response,
+          uuid: aMsg.data.uuid,
+        });
+      };
+      callback = {
+        onSuccess: response => reply("success", response),
+        onError: error => reply("error", error),
+      };
+    }
+
+    if (aMsg.data.global) {
+      this.instance.dispatch(aMsg.data.event, aMsg.data.data. callback);
+      return;
+    }
+
+    let win = aMsg.target.contentWindow || aMsg.target.ownerGlobal;
+    let dispatcher = win.WindowEventDispatcher || this.for(win);
+    dispatcher.dispatch(aMsg.data.event, aMsg.data.data, callback);
+  },
 };
 
+if (IS_PARENT_PROCESS) {
+  Services.mm.addMessageListener("GeckoView:Messaging", EventDispatcher);
+  Services.ppmm.addMessageListener("GeckoView:Messaging", EventDispatcher);
+}
+
 // For backwards compatibility.
 var Messaging = {};
 
 function _addMessagingGetter(name) {
   Messaging[name] = function() {
     Cu.reportError("Messaging." + name + " is deprecated. " +
                    "Use EventDispatcher object instead.");