diff options
Diffstat (limited to 'mobile/android/modules/Messaging.jsm')
-rw-r--r-- | mobile/android/modules/Messaging.jsm | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/mobile/android/modules/Messaging.jsm b/mobile/android/modules/Messaging.jsm new file mode 100644 index 000000000..30b7f5a96 --- /dev/null +++ b/mobile/android/modules/Messaging.jsm @@ -0,0 +1,183 @@ +/* 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/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + +this.EXPORTED_SYMBOLS = ["sendMessageToJava", "Messaging"]; + +XPCOMUtils.defineLazyServiceGetter(this, "uuidgen", + "@mozilla.org/uuid-generator;1", + "nsIUUIDGenerator"); + +function sendMessageToJava(aMessage, aCallback) { + Cu.reportError("sendMessageToJava is deprecated. Use Messaging API instead."); + + if (aCallback) { + Messaging.sendRequestForResult(aMessage) + .then(result => aCallback(result, null), + error => aCallback(null, error)); + } else { + Messaging.sendRequest(aMessage); + } +} + +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 + * // 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 aListener Listener callback taking a single data parameter (see + * example usage above). + * @param aMessage Event name that this listener should observe. + */ + addListener: function (aListener, aMessage) { + requestHandler.addListener(aListener, aMessage); + }, + + /** + * Removes a listener for a given message. + * + * @param aMessage The event to stop listening for. + */ + removeListener: function (aMessage) { + requestHandler.removeListener(aMessage); + }, + + /** + * Sends a request to Java. + * + * @param aMessage Message to send; must be an object with a "type" property + */ + sendRequest: function (aMessage) { + Services.androidBridge.handleGeckoMessage(aMessage); + }, + + /** + * Sends a request to Java, returning a Promise that resolves to the response. + * + * @param aMessage Message to send; must be an object with a "type" property + * @returns A Promise resolving to the response + */ + sendRequestForResult: function (aMessage) { + return new Promise((resolve, reject) => { + let id = uuidgen.generateUUID().toString(); + let obs = { + observe: function (aSubject, aTopic, aData) { + let data = JSON.parse(aData); + if (data.__guid__ != id) { + return; + } + + Services.obs.removeObserver(obs, aMessage.type + ":Response"); + + if (data.status === "success") { + resolve(data.response); + } else { + reject(data.response); + } + } + }; + + aMessage.__guid__ = id; + Services.obs.addObserver(obs, aMessage.type + ":Response", false); + + this.sendRequest(aMessage); + }); + }, + + /** + * Handles a request from Java, using the given listener method. + * This is mainly an internal method used by the RequestHandler object, but can be + * used in nsIObserver.observe implmentations that fall outside the normal usage + * patterns. + * + * @param aTopic The string name of the message + * @param aData The data sent to the observe method from Java + * @param aListener A function that takes a JSON data argument and returns a + * response which is sent to Java. + */ + handleRequest: Task.async(function* (aTopic, aData, aListener) { + let wrapper = JSON.parse(aData); + + try { + let response = yield aListener(wrapper.data); + if (typeof response !== "object" || response === null) { + throw new Error("Gecko request listener did not return an object"); + } + + Messaging.sendRequest({ + type: "Gecko:Request" + wrapper.id, + response: response + }); + } catch (e) { + Cu.reportError("Error in Messaging handler for " + aTopic + ": " + e); + + Messaging.sendRequest({ + type: "Gecko:Request" + wrapper.id, + error: { + message: e.message || (e && e.toString()), + stack: e.stack || Components.stack.formattedStack, + } + }); + } + }) +}; + +var requestHandler = { + _listeners: {}, + + addListener: function (aListener, aMessage) { + if (aMessage in this._listeners) { + throw new Error("Error in addListener: A listener already exists for message " + aMessage); + } + + if (typeof aListener !== "function") { + throw new Error("Error in addListener: Listener must be a function for message " + aMessage); + } + + this._listeners[aMessage] = aListener; + Services.obs.addObserver(this, aMessage, false); + }, + + removeListener: function (aMessage) { + if (!(aMessage in this._listeners)) { + throw new Error("Error in removeListener: There is no listener for message " + aMessage); + } + + delete this._listeners[aMessage]; + Services.obs.removeObserver(this, aMessage); + }, + + observe: function(aSubject, aTopic, aData) { + let listener = this._listeners[aTopic]; + Messaging.handleRequest(aTopic, aData, listener); + } +}; |