Bug 1317604 - 1. Add EventDispatcher to Messaging.jsm; r=snorp r=sebastian
authorJim Chen <nchen@mozilla.com>
Mon, 21 Nov 2016 10:01:30 -0500
changeset 323677 22411be95565f743e9287f188d425f5108435a24
parent 323676 de975fd9cbf654f2b1c17db01a7544995da65611
child 323678 946e15013499fe1bae4c1d8e910bacaf26638ade
push id30981
push usercbook@mozilla.com
push dateTue, 22 Nov 2016 14:53:38 +0000
treeherdermozilla-central@1a3194836cb4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp, sebastian
bugs1317604
milestone53.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 1317604 - 1. Add EventDispatcher to Messaging.jsm; r=snorp r=sebastian Add a new EventDispatcher interface to Messaging.jsm, and provide means to access either the global EventDispatcher through EventDispatcher.instance or a per-window EventDispatcher through EventDispatcher.for(window). The old Messaging object is retained until we can convert all existing uses of it in Fennec to use EventDispatcher, at which point `Messaging` will be made to point to `EventDispatcher.instance`.
mobile/android/chrome/content/browser.js
mobile/android/modules/Messaging.jsm
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -37,16 +37,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/UITelemetry.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "EventDispatcher",
+                                  "resource://gre/modules/Messaging.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
                                   "resource://gre/modules/Messaging.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "UserAgentOverrides",
                                   "resource://gre/modules/UserAgentOverrides.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
                                   "resource://gre/modules/LoginManagerContent.jsm");
@@ -327,16 +330,19 @@ const kFormHelperModeDisabled = 0;
 const kFormHelperModeEnabled = 1;
 const kFormHelperModeDynamic = 2;   // disabled on tablets
 const kMaxHistoryListSize = 50;
 
 function InitLater(fn, object, name) {
   return DelayedInit.schedule(fn, object, name, 15000 /* 15s max wait */);
 }
 
+XPCOMUtils.defineLazyGetter(this, "GlobalEventDispatcher", () => EventDispatcher.instance);
+XPCOMUtils.defineLazyGetter(this, "WindowEventDispatcher", () => EventDispatcher.for(window));
+
 var BrowserApp = {
   _tabs: [],
   _selectedTab: null,
 
   get isTablet() {
     let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
     delete this.isTablet;
     return this.isTablet = sysInfo.get("tablet");
--- a/mobile/android/modules/Messaging.jsm
+++ b/mobile/android/modules/Messaging.jsm
@@ -1,20 +1,20 @@
 /* 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");
-Cu.import("resource://gre/modules/Task.jsm");
 
-this.EXPORTED_SYMBOLS = ["sendMessageToJava", "Messaging"];
+this.EXPORTED_SYMBOLS = ["sendMessageToJava", "Messaging", "EventDispatcher"];
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 function sendMessageToJava(aMessage, aCallback) {
   Cu.reportError("sendMessageToJava is deprecated. Use Messaging API instead.");
 
@@ -22,16 +22,181 @@ function sendMessageToJava(aMessage, aCa
     Messaging.sendRequestForResult(aMessage)
       .then(result => aCallback(result, null),
             error => aCallback(null, error));
   } else {
     Messaging.sendRequest(aMessage);
   }
 }
 
+function DispatcherDelegate(dispatcher) {
+  this._dispatcher = 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) {
+    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) {
+    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) {
+    this._dispatcher.dispatch(event, data, callback);
+  },
+
+  /**
+   * Implementations of Messaging APIs for backwards compatibility.
+   */
+
+  /**
+   * Sends a request to Java.
+   *
+   * @param msg Message to send; must be an object with a "type" property
+   */
+  sendRequest: function (msg) {
+    let type = msg.type;
+    msg.type = undefined;
+    this.dispatch(type, msg);
+  },
+
+  /**
+   * Sends a request to Java, returning a Promise that resolves to the response.
+   *
+   * @param msg Message to send; must be an object with a "type" property
+   * @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)
+      });
+    });
+  },
+
+  /**
+   * Add a listener for the given event.
+   *
+   * Only one request listener can be registered for a given event.
+   *
+   * Example usage:
+   *   // aData is data sent from Java with the request. The return value is
+   *   // used to respond to the request. The return type *must* be an instance
+   *   // of Object.
+   *   let listener = function (aData) {
+   *     if (aData == "foo") {
+   *       return { response: "bar" };
+   *     }
+   *     return {};
+   *   };
+   *   Messaging.addListener(listener, "Demo:Request");
+   *
+   * The listener may also be a generator function, useful for performing a
+   * task asynchronously. For example:
+   *   let listener = function* (aData) {
+   *     // Respond with "bar" after 2 seconds.
+   *     yield new Promise(resolve => setTimeout(resolve, 2000));
+   *     return { response: "bar" };
+   *   };
+   *   Messaging.addListener(listener, "Demo:Request");
+   *
+   * @param listener Listener callback taking a single data parameter
+   *                 (see example usage above).
+   * @param event    Event name that this listener should observe.
+   */
+  addListener: function (listener, event) {
+    if (this._requestHandler.listeners[event]) {
+      throw new Error("Error in addListener: A listener already exists for event " + event);
+    }
+    if (typeof listener !== "function") {
+      throw new Error("Error in addListener: Listener must be a function for event " + event);
+    }
+
+    this._requestHandler.listeners[event] = listener;
+    this.registerListener(this._requestHandler, event);
+  },
+
+  /**
+   * Removes a listener for a given event.
+   *
+   * @param event The event to stop listening for.
+   */
+  removeListener: function (event) {
+    if (!this._requestHandler.listeners[event]) {
+      throw new Error("Error in removeListener: There is no listener for event " + event);
+    }
+
+    this._requestHandler.listeners[event] = undefined;
+    this.unregisterListener(this._requestHandler, event);
+  },
+
+  _requestHandler: {
+    listeners: {},
+
+    onEvent: Task.async(function* (event, data, callback) {
+      try {
+        let response = yield this.listeners[event](data.data);
+        callback.onSuccess(response);
+
+      } catch (e) {
+        Cu.reportError("Error in Messaging handler for " + event + ": " + e);
+
+        callback.onError({
+          message: e.message || (e && e.toString()),
+          stack: e.stack || Components.stack.formattedStack,
+        });
+      }
+    }),
+  },
+};
+
+const EventDispatcher = {
+  instance: new DispatcherDelegate(Services.androidBridge),
+
+  for: function (window) {
+    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);
+  },
+};
+
+// For backwards compatibility.
+// const Messaging = EventDispatcher.instance;
+
+
+// Legacy code
 var Messaging = {
   /**
    * Add a listener for the given message.
    *
    * Only one request listener can be registered for a given message.
    *
    * Example usage:
    *   // aData is data sent from Java with the request. The return value is