tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Messaging.sys.mjs (8225B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const IS_PARENT_PROCESS =
      6  Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT;
      7 
      8 function DispatcherDelegate(aDispatcher, aMessageManager) {
      9  this._dispatcher = aDispatcher;
     10  this._messageManager = aMessageManager;
     11 
     12  if (!aDispatcher) {
     13    // Child process.
     14    // TODO: this doesn't work with Fission, remove this code path once every
     15    // consumer has been migrated. Bug 1569360.
     16    this._replies = new Map();
     17    (aMessageManager || Services.cpmm).addMessageListener(
     18      "GeckoView:MessagingReply",
     19      this
     20    );
     21  }
     22 }
     23 
     24 DispatcherDelegate.prototype = {
     25  /**
     26   * Register a listener to be notified of event(s).
     27   *
     28   * @param aListener Target listener implementing nsIGeckoViewEventListener.
     29   * @param aEvents   String or array of strings of events to listen to.
     30   */
     31  registerListener(aListener, aEvents) {
     32    if (!this._dispatcher) {
     33      throw new Error("Can only listen in parent process");
     34    }
     35    this._dispatcher.registerListener(aListener, aEvents);
     36  },
     37 
     38  /**
     39   * Unregister a previously-registered listener.
     40   *
     41   * @param aListener Registered listener implementing nsIGeckoViewEventListener.
     42   * @param aEvents   String or array of strings of events to stop listening to.
     43   */
     44  unregisterListener(aListener, aEvents) {
     45    if (!this._dispatcher) {
     46      throw new Error("Can only listen in parent process");
     47    }
     48    this._dispatcher.unregisterListener(aListener, aEvents);
     49  },
     50 
     51  /**
     52   * Dispatch an event to registered listeners for that event, and pass an
     53   * optional data object and/or a optional callback interface to the
     54   * listeners.
     55   *
     56   * @param aEvent     Name of event to dispatch.
     57   * @param aData      Optional object containing data for the event.
     58   * @param aCallback  Optional callback implementing nsIGeckoViewEventCallback.
     59   * @param aFinalizer Optional finalizer implementing nsIGeckoViewEventFinalizer.
     60   */
     61  dispatch(aEvent, aData, aCallback, aFinalizer) {
     62    if (this._dispatcher) {
     63      this._dispatcher.dispatch(aEvent, aData, aCallback, aFinalizer);
     64      return;
     65    }
     66 
     67    const mm = this._messageManager || Services.cpmm;
     68    const forwardData = {
     69      global: !this._messageManager,
     70      event: aEvent,
     71      data: aData,
     72    };
     73 
     74    if (aCallback) {
     75      const uuid = Services.uuid.generateUUID().toString();
     76      this._replies.set(uuid, {
     77        callback: aCallback,
     78        finalizer: aFinalizer,
     79      });
     80      forwardData.uuid = uuid;
     81    }
     82 
     83    mm.sendAsyncMessage("GeckoView:Messaging", forwardData);
     84  },
     85 
     86  /**
     87   * Sends a request to Java.
     88   *
     89   * @param aMsg      Message to send; must be an object with a "type" property
     90   * @param aCallback Optional callback implementing nsIGeckoViewEventCallback.
     91   */
     92  sendRequest(aMsg, aCallback) {
     93    const type = aMsg.type;
     94    aMsg.type = undefined;
     95    this.dispatch(type, aMsg, aCallback);
     96  },
     97 
     98  /**
     99   * Sends a request to Java, returning a Promise that resolves to the response.
    100   *
    101   * @param aMsg Message to send; must be an object with a "type" property
    102   * @return A Promise resolving to the response
    103   */
    104  sendRequestForResult(aMsg) {
    105    return new Promise((resolve, reject) => {
    106      const type = aMsg.type;
    107      aMsg.type = undefined;
    108 
    109      // Manually release the resolve/reject functions after one callback is
    110      // received, so the JS GC is not tied up with the Java GC.
    111      const onCallback = (callback, ...args) => {
    112        if (callback) {
    113          callback(...args);
    114        }
    115        resolve = undefined;
    116        reject = undefined;
    117      };
    118      const callback = {
    119        onSuccess: result => onCallback(resolve, result),
    120        onError: error => onCallback(reject, error),
    121        onFinalize: _ => onCallback(reject),
    122      };
    123      this.dispatch(type, aMsg, callback, callback);
    124    });
    125  },
    126 
    127  finalize() {
    128    if (!this._replies) {
    129      return;
    130    }
    131    this._replies.forEach(reply => {
    132      if (typeof reply.finalizer === "function") {
    133        reply.finalizer();
    134      } else if (reply.finalizer) {
    135        reply.finalizer.onFinalize();
    136      }
    137    });
    138    this._replies.clear();
    139  },
    140 
    141  receiveMessage(aMsg) {
    142    const { uuid, type } = aMsg.data;
    143    const reply = this._replies.get(uuid);
    144    if (!reply) {
    145      return;
    146    }
    147 
    148    if (type === "success") {
    149      reply.callback.onSuccess(aMsg.data.response);
    150    } else if (type === "error") {
    151      reply.callback.onError(aMsg.data.response);
    152    } else if (type === "finalize") {
    153      if (typeof reply.finalizer === "function") {
    154        reply.finalizer();
    155      } else if (reply.finalizer) {
    156        reply.finalizer.onFinalize();
    157      }
    158      this._replies.delete(uuid);
    159    } else {
    160      throw new Error("invalid reply type");
    161    }
    162  },
    163 };
    164 
    165 export var EventDispatcher = {
    166  instance: new DispatcherDelegate(
    167    IS_PARENT_PROCESS ? Services.geckoviewBridge : undefined
    168  ),
    169 
    170  /**
    171   * Return an EventDispatcher instance for a chrome DOM window. In a content
    172   * process, return a proxy through the message manager that automatically
    173   * forwards events to the main process.
    174   *
    175   * To force using a message manager proxy (for example in a frame script
    176   * environment), call forMessageManager.
    177   *
    178   * @param aWindow a chrome DOM window.
    179   */
    180  for(aWindow) {
    181    const view =
    182      aWindow &&
    183      aWindow.arguments &&
    184      aWindow.arguments[0] &&
    185      aWindow.arguments[0].QueryInterface(Ci.nsIGeckoViewView);
    186 
    187    if (!view) {
    188      const mm = !IS_PARENT_PROCESS && aWindow && aWindow.messageManager;
    189      if (!mm) {
    190        throw new Error(
    191          "window is not a GeckoView-connected window and does" +
    192            " not have a message manager"
    193        );
    194      }
    195      return this.forMessageManager(mm);
    196    }
    197 
    198    return new DispatcherDelegate(view);
    199  },
    200 
    201  /**
    202   * Returns a named EventDispatcher, which can communicate with the
    203   * corresponding EventDispatcher on the java side.
    204   */
    205  byName(aName) {
    206    if (!IS_PARENT_PROCESS) {
    207      return undefined;
    208    }
    209    const dispatcher = Services.geckoviewBridge.getDispatcherByName(aName);
    210    return new DispatcherDelegate(dispatcher);
    211  },
    212 
    213  /**
    214   * Return an EventDispatcher instance for a message manager associated with a
    215   * window.
    216   *
    217   * @param aWindow a message manager.
    218   */
    219  forMessageManager(aMessageManager) {
    220    return new DispatcherDelegate(null, aMessageManager);
    221  },
    222 
    223  receiveMessage(aMsg) {
    224    // aMsg.data includes keys: global, event, data, uuid
    225    let callback;
    226    if (aMsg.data.uuid) {
    227      const reply = (type, response) => {
    228        const mm = aMsg.data.global ? aMsg.target : aMsg.target.messageManager;
    229        if (!mm) {
    230          if (type === "finalize") {
    231            // It's normal for the finalize call to come after the browser has
    232            // been destroyed. We can gracefully handle that case despite
    233            // having no message manager.
    234            return;
    235          }
    236          throw Error(
    237            `No message manager for ${aMsg.data.event}:${type} reply`
    238          );
    239        }
    240        mm.sendAsyncMessage("GeckoView:MessagingReply", {
    241          type,
    242          response,
    243          uuid: aMsg.data.uuid,
    244        });
    245      };
    246      callback = {
    247        onSuccess: response => reply("success", response),
    248        onError: error => reply("error", error),
    249        onFinalize: () => reply("finalize"),
    250      };
    251    }
    252 
    253    try {
    254      if (aMsg.data.global) {
    255        this.instance.dispatch(
    256          aMsg.data.event,
    257          aMsg.data.data,
    258          callback,
    259          callback
    260        );
    261        return;
    262      }
    263 
    264      const win = aMsg.target.ownerGlobal;
    265      const dispatcher = win.WindowEventDispatcher || this.for(win);
    266      dispatcher.dispatch(aMsg.data.event, aMsg.data.data, callback, callback);
    267    } catch (e) {
    268      callback?.onError(`Error getting dispatcher: ${e}`);
    269      throw e;
    270    }
    271  },
    272 };
    273 
    274 if (IS_PARENT_PROCESS) {
    275  Services.mm.addMessageListener("GeckoView:Messaging", EventDispatcher);
    276  Services.ppmm.addMessageListener("GeckoView:Messaging", EventDispatcher);
    277 }