tor-browser

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

message.sys.mjs (8820B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
      9  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
     10  truncate: "chrome://remote/content/shared/Format.sys.mjs",
     11 });
     12 
     13 /** Representation of the packets transported over the wire. */
     14 export class Message {
     15  /**
     16   * @param {number} messageID
     17   *     Message ID unique identifying this message.
     18   */
     19  constructor(messageID) {
     20    this.id = lazy.assert.integer(messageID);
     21  }
     22 
     23  toString() {
     24    function replacer(key, value) {
     25      if (typeof value === "string") {
     26        return lazy.truncate`${value}`;
     27      }
     28      return value;
     29    }
     30 
     31    return JSON.stringify(this.toPacket(), replacer);
     32  }
     33 
     34  /**
     35   * Converts a data packet into a {@link Command} or {@link Response}.
     36   *
     37   * @param {Array.<number, number, ?, ?>} data
     38   *     A four element array where the elements, in sequence, signifies
     39   *     message type, message ID, method name or error, and parameters
     40   *     or result.
     41   *
     42   * @returns {Message}
     43   *     Based on the message type, a {@link Command} or {@link Response}
     44   *     instance.
     45   *
     46   * @throws {TypeError}
     47   *     If the message type is not recognised.
     48   */
     49  static fromPacket(data) {
     50    const [type] = data;
     51 
     52    switch (type) {
     53      case Command.Type:
     54        return Command.fromPacket(data);
     55 
     56      case Response.Type:
     57        return Response.fromPacket(data);
     58 
     59      default:
     60        throw new TypeError(
     61          "Unrecognised message type in packet: " + JSON.stringify(data)
     62        );
     63    }
     64  }
     65 }
     66 
     67 /**
     68 * Messages may originate from either the server or the client.
     69 * Because the remote protocol is full duplex, both endpoints may be
     70 * the origin of both commands and responses.
     71 *
     72 * @enum
     73 * @see {@link Message}
     74 */
     75 Message.Origin = {
     76  /** Indicates that the message originates from the client. */
     77  Client: 0,
     78  /** Indicates that the message originates from the server. */
     79  Server: 1,
     80 };
     81 
     82 /**
     83 * A command is a request from the client to run a series of remote end
     84 * steps and return a fitting response.
     85 *
     86 * The command can be synthesised from the message passed over the
     87 * Marionette socket using the {@link fromPacket} function.  The format of
     88 * a message is:
     89 *
     90 * <pre>
     91 *     [<var>type</var>, <var>id</var>, <var>name</var>, <var>params</var>]
     92 * </pre>
     93 *
     94 * where
     95 *
     96 * <dl>
     97 *   <dt><var>type</var> (integer)
     98 *   <dd>
     99 *     Must be zero (integer).  Zero means that this message is
    100 *     a command.
    101 *
    102 *   <dt><var>id</var> (integer)
    103 *   <dd>
    104 *     Integer used as a sequence number.  The server replies with
    105 *     the same ID for the response.
    106 *
    107 *   <dt><var>name</var> (string)
    108 *   <dd>
    109 *     String representing the command name with an associated set
    110 *     of remote end steps.
    111 *
    112 *   <dt><var>params</var> (JSON Object or null)
    113 *   <dd>
    114 *     Object of command function arguments.  The keys of this object
    115 *     must be strings, but the values can be arbitrary values.
    116 * </dl>
    117 *
    118 * A command has an associated message <var>id</var> that prevents
    119 * the dispatcher from sending responses in the wrong order.
    120 *
    121 * The command may also have optional error- and result handlers that
    122 * are called when the client returns with a response.  These are
    123 * <code>function onerror({Object})</code>,
    124 * <code>function onresult({Object})</code>, and
    125 * <code>function onresult({Response})</code>:
    126 *
    127 * @param {number} messageID
    128 *     Message ID unique identifying this message.
    129 * @param {string} name
    130 *     Command name.
    131 * @param {Record<string, ?>} params
    132 *     Command parameters.
    133 */
    134 export class Command extends Message {
    135  constructor(messageID, name, params = {}) {
    136    super(messageID);
    137 
    138    this.name = lazy.assert.string(name);
    139    this.parameters = lazy.assert.object(params);
    140 
    141    this.onerror = null;
    142    this.onresult = null;
    143 
    144    this.origin = Message.Origin.Client;
    145    this.sent = false;
    146  }
    147 
    148  /**
    149   * Calls the error- or result handler associated with this command.
    150   * This function can be replaced with a custom response handler.
    151   *
    152   * @param {Response} resp
    153   *     The response to pass on to the result or error to the
    154   *     <code>onerror</code> or <code>onresult</code> handlers to.
    155   */
    156  onresponse(resp) {
    157    if (this.onerror && resp.error) {
    158      this.onerror(resp.error);
    159    } else if (this.onresult && resp.body) {
    160      this.onresult(resp.body);
    161    }
    162  }
    163 
    164  /**
    165   * Encodes the command to a packet.
    166   *
    167   * @returns {Array}
    168   *     Packet.
    169   */
    170  toPacket() {
    171    return [Command.Type, this.id, this.name, this.parameters];
    172  }
    173 
    174  /**
    175   * Converts a data packet into {@link Command}.
    176   *
    177   * @param {Array.<number, number, *, *>} payload
    178   *     A four element array where the elements, in sequence, signifies
    179   *     message type, message ID, command name, and parameters.
    180   *
    181   * @returns {Command}
    182   *     Representation of packet.
    183   *
    184   * @throws {TypeError}
    185   *     If the message type is not recognised.
    186   */
    187  static fromPacket(payload) {
    188    let [type, msgID, name, params] = payload;
    189    lazy.assert.that(n => n === Command.Type)(type);
    190 
    191    // if parameters are given but null, treat them as undefined
    192    if (params === null) {
    193      params = undefined;
    194    }
    195 
    196    return new Command(msgID, name, params);
    197  }
    198 }
    199 
    200 Command.Type = 0;
    201 
    202 /**
    203 * @callback ResponseCallback
    204 *
    205 * @param {Response} resp
    206 *     Response to handle.
    207 */
    208 
    209 /**
    210 * Represents the response returned from the remote end after execution
    211 * of its corresponding command.
    212 *
    213 * The response is a mutable object passed to each command for
    214 * modification through the available setters.  To send data in a response,
    215 * you modify the body property on the response.  The body property can
    216 * also be replaced completely.
    217 *
    218 * The response is sent implicitly by
    219 * {@link server.TCPConnection#execute when a command has finished
    220 * executing, and any modifications made subsequent to that will have
    221 * no effect.
    222 *
    223 * @param {number} messageID
    224 *     Message ID tied to the corresponding command request this is
    225 *     a response for.
    226 * @param {ResponseHandler} respHandler
    227 *     Function callback called on sending the response.
    228 */
    229 export class Response extends Message {
    230  constructor(messageID, respHandler = () => {}) {
    231    super(messageID);
    232 
    233    this.respHandler_ = lazy.assert.callable(respHandler);
    234 
    235    this.error = null;
    236    this.body = { value: null };
    237 
    238    this.origin = Message.Origin.Server;
    239    this.sent = false;
    240  }
    241 
    242  /**
    243   * Sends response conditionally, given a predicate.
    244   *
    245   * @param {function(Response): boolean} predicate
    246   *     A predicate taking a Response object and returning a boolean.
    247   */
    248  sendConditionally(predicate) {
    249    if (predicate(this)) {
    250      this.send();
    251    }
    252  }
    253 
    254  /**
    255   * Sends response using the response handler provided on
    256   * construction.
    257   *
    258   * @throws {RangeError}
    259   *     If the response has already been sent.
    260   */
    261  send() {
    262    if (this.sent) {
    263      throw new RangeError("Response has already been sent: " + this);
    264    }
    265    this.respHandler_(this);
    266    this.sent = true;
    267  }
    268 
    269  /**
    270   * Send error to client.
    271   *
    272   * Turns the response into an error response, clears any previously
    273   * set body data, and sends it using the response handler provided
    274   * on construction.
    275   *
    276   * @param {Error} err
    277   *     The Error instance to send.
    278   *
    279   * @throws {Error}
    280   *     If <var>err</var> is not a {@link WebDriverError}, the error
    281   *     is propagated, i.e. rethrown.
    282   */
    283  sendError(err) {
    284    this.error = lazy.error.wrap(err).toJSON();
    285    this.body = null;
    286    this.send();
    287 
    288    // propagate errors which are implementation problems
    289    if (!lazy.error.isWebDriverError(err)) {
    290      throw err;
    291    }
    292  }
    293 
    294  /**
    295   * Encodes the response to a packet.
    296   *
    297   * @returns {Array}
    298   *     Packet.
    299   */
    300  toPacket() {
    301    return [Response.Type, this.id, this.error, this.body];
    302  }
    303 
    304  /**
    305   * Converts a data packet into {@link Response}.
    306   *
    307   * @param {Array.<number, number, ?, ?>} payload
    308   *     A four element array where the elements, in sequence, signifies
    309   *     message type, message ID, error, and result.
    310   *
    311   * @returns {Response}
    312   *     Representation of packet.
    313   *
    314   * @throws {TypeError}
    315   *     If the message type is not recognised.
    316   */
    317  static fromPacket(payload) {
    318    let [type, msgID, err, body] = payload;
    319    lazy.assert.that(n => n === Response.Type)(type);
    320 
    321    let resp = new Response(msgID);
    322    resp.error = lazy.assert.string(err);
    323 
    324    resp.body = body;
    325    return resp;
    326  }
    327 }
    328 
    329 Response.Type = 1;