summaryrefslogtreecommitdiffstats
path: root/mobile/android/modules/geckoview/Messaging.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/modules/geckoview/Messaging.sys.mjs')
-rw-r--r--mobile/android/modules/geckoview/Messaging.sys.mjs319
1 files changed, 319 insertions, 0 deletions
diff --git a/mobile/android/modules/geckoview/Messaging.sys.mjs b/mobile/android/modules/geckoview/Messaging.sys.mjs
new file mode 100644
index 0000000000..e67161fede
--- /dev/null
+++ b/mobile/android/modules/geckoview/Messaging.sys.mjs
@@ -0,0 +1,319 @@
+/* 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/. */
+
+const IS_PARENT_PROCESS =
+ Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT;
+
+class ChildActorDispatcher {
+ constructor(actor) {
+ this._actor = actor;
+ }
+
+ // TODO: Bug 1658980
+ registerListener(aListener, aEvents) {
+ throw new Error("Cannot registerListener in child actor");
+ }
+ unregisterListener(aListener, aEvents) {
+ throw new Error("Cannot registerListener in child actor");
+ }
+
+ /**
+ * Sends a request to Java.
+ *
+ * @param aMsg Message to send; must be an object with a "type" property
+ */
+ sendRequest(aMsg) {
+ this._actor.sendAsyncMessage("DispatcherMessage", aMsg);
+ }
+
+ /**
+ * Sends a request to Java, returning a Promise that resolves to the response.
+ *
+ * @param aMsg Message to send; must be an object with a "type" property
+ * @return A Promise resolving to the response
+ */
+ sendRequestForResult(aMsg) {
+ return this._actor.sendQuery("DispatcherQuery", aMsg);
+ }
+}
+
+function DispatcherDelegate(aDispatcher, aMessageManager) {
+ this._dispatcher = aDispatcher;
+ this._messageManager = aMessageManager;
+
+ if (!aDispatcher) {
+ // Child process.
+ // TODO: this doesn't work with Fission, remove this code path once every
+ // consumer has been migrated. Bug 1569360.
+ this._replies = new Map();
+ (aMessageManager || Services.cpmm).addMessageListener(
+ "GeckoView:MessagingReply",
+ this
+ );
+ }
+}
+
+DispatcherDelegate.prototype = {
+ /**
+ * Register a listener to be notified of event(s).
+ *
+ * @param aListener Target listener implementing nsIAndroidEventListener.
+ * @param aEvents String or array of strings of events to listen to.
+ */
+ registerListener(aListener, aEvents) {
+ if (!this._dispatcher) {
+ throw new Error("Can only listen in parent process");
+ }
+ this._dispatcher.registerListener(aListener, aEvents);
+ },
+
+ /**
+ * Unregister a previously-registered listener.
+ *
+ * @param aListener Registered listener implementing nsIAndroidEventListener.
+ * @param aEvents String or array of strings of events to stop listening to.
+ */
+ unregisterListener(aListener, aEvents) {
+ if (!this._dispatcher) {
+ throw new Error("Can only listen in parent process");
+ }
+ this._dispatcher.unregisterListener(aListener, aEvents);
+ },
+
+ /**
+ * 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 aEvent Name of event to dispatch.
+ * @param aData Optional object containing data for the event.
+ * @param aCallback Optional callback implementing nsIAndroidEventCallback.
+ * @param aFinalizer Optional finalizer implementing nsIAndroidEventFinalizer.
+ */
+ dispatch(aEvent, aData, aCallback, aFinalizer) {
+ if (this._dispatcher) {
+ this._dispatcher.dispatch(aEvent, aData, aCallback, aFinalizer);
+ return;
+ }
+
+ const mm = this._messageManager || Services.cpmm;
+ const forwardData = {
+ global: !this._messageManager,
+ event: aEvent,
+ data: aData,
+ };
+
+ if (aCallback) {
+ const uuid = Services.uuid.generateUUID().toString();
+ this._replies.set(uuid, {
+ callback: aCallback,
+ finalizer: aFinalizer,
+ });
+ forwardData.uuid = uuid;
+ }
+
+ mm.sendAsyncMessage("GeckoView:Messaging", forwardData);
+ },
+
+ /**
+ * Sends a request to Java.
+ *
+ * @param aMsg Message to send; must be an object with a "type" property
+ * @param aCallback Optional callback implementing nsIAndroidEventCallback.
+ */
+ sendRequest(aMsg, aCallback) {
+ const type = aMsg.type;
+ aMsg.type = undefined;
+ this.dispatch(type, aMsg, aCallback);
+ },
+
+ /**
+ * Sends a request to Java, returning a Promise that resolves to the response.
+ *
+ * @param aMsg Message to send; must be an object with a "type" property
+ * @return A Promise resolving to the response
+ */
+ sendRequestForResult(aMsg) {
+ return new Promise((resolve, reject) => {
+ const type = aMsg.type;
+ aMsg.type = undefined;
+
+ // Manually release the resolve/reject functions after one callback is
+ // received, so the JS GC is not tied up with the Java GC.
+ const onCallback = (callback, ...args) => {
+ if (callback) {
+ callback(...args);
+ }
+ resolve = undefined;
+ reject = undefined;
+ };
+ const callback = {
+ onSuccess: result => onCallback(resolve, result),
+ onError: error => onCallback(reject, error),
+ onFinalize: _ => onCallback(reject),
+ };
+ this.dispatch(type, aMsg, callback, callback);
+ });
+ },
+
+ finalize() {
+ if (!this._replies) {
+ return;
+ }
+ this._replies.forEach(reply => {
+ if (typeof reply.finalizer === "function") {
+ reply.finalizer();
+ } else if (reply.finalizer) {
+ reply.finalizer.onFinalize();
+ }
+ });
+ this._replies.clear();
+ },
+
+ receiveMessage(aMsg) {
+ const { uuid, type } = aMsg.data;
+ const reply = this._replies.get(uuid);
+ if (!reply) {
+ return;
+ }
+
+ if (type === "success") {
+ reply.callback.onSuccess(aMsg.data.response);
+ } else if (type === "error") {
+ reply.callback.onError(aMsg.data.response);
+ } else if (type === "finalize") {
+ if (typeof reply.finalizer === "function") {
+ reply.finalizer();
+ } else if (reply.finalizer) {
+ reply.finalizer.onFinalize();
+ }
+ this._replies.delete(uuid);
+ } else {
+ throw new Error("invalid reply type");
+ }
+ },
+};
+
+export var EventDispatcher = {
+ instance: new DispatcherDelegate(
+ IS_PARENT_PROCESS ? Services.androidBridge : undefined
+ ),
+
+ /**
+ * Return an EventDispatcher instance for a chrome DOM window. In a content
+ * process, return a proxy through the message manager that automatically
+ * forwards events to the main process.
+ *
+ * To force using a message manager proxy (for example in a frame script
+ * environment), call forMessageManager.
+ *
+ * @param aWindow a chrome DOM window.
+ */
+ for(aWindow) {
+ const view =
+ aWindow &&
+ aWindow.arguments &&
+ aWindow.arguments[0] &&
+ aWindow.arguments[0].QueryInterface(Ci.nsIAndroidView);
+
+ if (!view) {
+ const mm = !IS_PARENT_PROCESS && aWindow && aWindow.messageManager;
+ if (!mm) {
+ throw new Error(
+ "window is not a GeckoView-connected window and does" +
+ " not have a message manager"
+ );
+ }
+ return this.forMessageManager(mm);
+ }
+
+ return new DispatcherDelegate(view);
+ },
+
+ /**
+ * Returns a named EventDispatcher, which can communicate with the
+ * corresponding EventDispatcher on the java side.
+ */
+ byName(aName) {
+ if (!IS_PARENT_PROCESS) {
+ return undefined;
+ }
+ const dispatcher = Services.androidBridge.getDispatcherByName(aName);
+ return new DispatcherDelegate(dispatcher);
+ },
+
+ /**
+ * Return an EventDispatcher instance for a message manager associated with a
+ * window.
+ *
+ * @param aWindow a message manager.
+ */
+ forMessageManager(aMessageManager) {
+ return new DispatcherDelegate(null, aMessageManager);
+ },
+
+ /**
+ * Return the EventDispatcher instance associated with an actor.
+ *
+ * @param aActor an actor
+ */
+ forActor(aActor) {
+ return new ChildActorDispatcher(aActor);
+ },
+
+ receiveMessage(aMsg) {
+ // aMsg.data includes keys: global, event, data, uuid
+ let callback;
+ if (aMsg.data.uuid) {
+ const reply = (type, response) => {
+ const mm = aMsg.data.global ? aMsg.target : aMsg.target.messageManager;
+ if (!mm) {
+ if (type === "finalize") {
+ // It's normal for the finalize call to come after the browser has
+ // been destroyed. We can gracefully handle that case despite
+ // having no message manager.
+ return;
+ }
+ throw Error(
+ `No message manager for ${aMsg.data.event}:${type} reply`
+ );
+ }
+ mm.sendAsyncMessage("GeckoView:MessagingReply", {
+ type,
+ response,
+ uuid: aMsg.data.uuid,
+ });
+ };
+ callback = {
+ onSuccess: response => reply("success", response),
+ onError: error => reply("error", error),
+ onFinalize: () => reply("finalize"),
+ };
+ }
+
+ try {
+ if (aMsg.data.global) {
+ this.instance.dispatch(
+ aMsg.data.event,
+ aMsg.data.data,
+ callback,
+ callback
+ );
+ return;
+ }
+
+ const win = aMsg.target.ownerGlobal;
+ const dispatcher = win.WindowEventDispatcher || this.for(win);
+ dispatcher.dispatch(aMsg.data.event, aMsg.data.data, callback, callback);
+ } catch (e) {
+ callback?.onError(`Error getting dispatcher: ${e}`);
+ throw e;
+ }
+ },
+};
+
+if (IS_PARENT_PROCESS) {
+ Services.mm.addMessageListener("GeckoView:Messaging", EventDispatcher);
+ Services.ppmm.addMessageListener("GeckoView:Messaging", EventDispatcher);
+}