tor-browser

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

local-transport.js (7885B)


      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 "use strict";
      6 
      7 const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
      8 const { dumpn } = DevToolsUtils;
      9 const flags = require("resource://devtools/shared/flags.js");
     10 const StreamUtils = require("resource://devtools/shared/transport/stream-utils.js");
     11 
     12 loader.lazyGetter(this, "Pipe", () => {
     13  return Components.Constructor("@mozilla.org/pipe;1", "nsIPipe", "init");
     14 });
     15 
     16 /**
     17 * An adapter that handles data transfers between the devtools client and
     18 * server when they both run in the same process. It presents the same API as
     19 * DebuggerTransport, but instead of transmitting serialized messages across a
     20 * connection it merely calls the packet dispatcher of the other side.
     21 *
     22 * @see DebuggerTransport
     23 */
     24 
     25 class LocalDebuggerTransport {
     26  /**
     27   * @param {LocalDebuggerTransport} other
     28   *        The other endpoint for this debugger connection.
     29   */
     30  constructor(other) {
     31    this.other = other;
     32    this.hooks = null;
     33 
     34    // A packet number, shared between this and this.other. This isn't used by the
     35    // protocol at all, but it makes the packet traces a lot easier to follow.
     36    this._serial = this.other ? this.other._serial : { count: 0 };
     37    this.close = this.close.bind(this);
     38  }
     39 
     40  /**
     41   * Boolean to help identify DevToolsClient instances connected to a LocalDevToolsTransport pipe
     42   * and so connected to the same runtime as the frontend.
     43   */
     44  isLocalTransport = true;
     45 
     46  /**
     47   * Transmit a message by directly calling the onPacket handler of the other
     48   * endpoint.
     49   */
     50  send(packet) {
     51    const serial = this._serial.count++;
     52    if (flags.wantLogging) {
     53      // Check 'from' first, as 'echo' packets have both.
     54      if (packet.from) {
     55        dumpn("Packet " + serial + " sent from " + JSON.stringify(packet.from));
     56      } else if (packet.to) {
     57        dumpn("Packet " + serial + " sent to " + JSON.stringify(packet.to));
     58      }
     59    }
     60    this._deepFreeze(packet);
     61    const other = this.other;
     62    if (other) {
     63      DevToolsUtils.executeSoon(
     64        DevToolsUtils.makeInfallible(() => {
     65          // Avoid the cost of JSON.stringify() when logging is disabled.
     66          if (flags.wantLogging) {
     67            dumpn(
     68              "Received packet " +
     69                serial +
     70                ": " +
     71                JSON.stringify(packet, null, 2)
     72            );
     73          }
     74          if (other.hooks) {
     75            other.hooks.onPacket(packet);
     76          }
     77        }, "LocalDebuggerTransport instance's this.other.hooks.onPacket")
     78      );
     79    }
     80  }
     81 
     82  /**
     83   * Send a streaming bulk packet directly to the onBulkPacket handler of the
     84   * other endpoint.
     85   *
     86   * This case is much simpler than the full DebuggerTransport, since there is
     87   * no primary stream we have to worry about managing while we hand it off to
     88   * others temporarily.  Instead, we can just make a single use pipe and be
     89   * done with it.
     90   */
     91  startBulkSend(sentPacket) {
     92    const { actor, type, length } = sentPacket;
     93    const serial = this._serial.count++;
     94    dumpn("Sent bulk packet " + serial + " for actor " + actor);
     95 
     96    if (!this.other) {
     97      const error = new Error("startBulkSend: other side of transport missing");
     98      return Promise.reject(error);
     99    }
    100 
    101    const pipe = new Pipe(true, true, 0, 0, null);
    102 
    103    DevToolsUtils.executeSoon(
    104      DevToolsUtils.makeInfallible(() => {
    105        // Avoid the cost of JSON.stringify() when logging is disabled.
    106        if (flags.wantLogging) {
    107          dumpn(
    108            "Received bulk packet " +
    109              serial +
    110              ": " +
    111              JSON.stringify(sentPacket, null, 2)
    112          );
    113        }
    114        if (!this.other.hooks) {
    115          return;
    116        }
    117 
    118        // Receiver
    119        new Promise(receiverResolve => {
    120          const receivedPacket = {
    121            actor,
    122            type,
    123            length,
    124            copyTo: output => {
    125              const copying = StreamUtils.copyStream(
    126                pipe.inputStream,
    127                output,
    128                length
    129              );
    130              receiverResolve(copying);
    131              return copying;
    132            },
    133            copyToBuffer: outputBuffer => {
    134              if (outputBuffer.byteLength !== length) {
    135                throw new Error(
    136                  `In copyToBuffer, the output buffer needs to have the same length as the data to read. ${outputBuffer.byteLength} !== ${length}`
    137                );
    138              }
    139              const copying = StreamUtils.copyAsyncStreamToArrayBuffer(
    140                pipe.inputStream,
    141                outputBuffer
    142              );
    143              receiverResolve(copying);
    144              return copying;
    145            },
    146            stream: pipe.inputStream,
    147            done: receiverResolve,
    148          };
    149 
    150          this.other.hooks.onBulkPacket(receivedPacket);
    151        })
    152          // Await the result of reading from the stream
    153          .then(() => pipe.inputStream.close(), this.close);
    154      }, "LocalDebuggerTransport instance's this.other.hooks.onBulkPacket")
    155    );
    156 
    157    // Sender
    158    return new Promise(senderResolve => {
    159      // The remote transport is not capable of resolving immediately here, so we
    160      // shouldn't be able to either.
    161      DevToolsUtils.executeSoon(() => {
    162        return (
    163          new Promise(copyResolve => {
    164            senderResolve({
    165              copyFrom: input => {
    166                const copying = StreamUtils.copyStream(
    167                  input,
    168                  pipe.outputStream,
    169                  length
    170                );
    171                copyResolve(copying);
    172                return copying;
    173              },
    174              copyFromBuffer: inputBuffer => {
    175                if (inputBuffer.byteLength !== length) {
    176                  throw new Error(
    177                    `In copyFromBuffer, the input buffer needs to have the same length as the data to write. ${inputBuffer.byteLength} !== ${length}`
    178                  );
    179                }
    180                const copying = StreamUtils.copyArrayBufferToAsyncStream(
    181                  inputBuffer,
    182                  pipe.outputStream
    183                );
    184                copyResolve(copying);
    185                return copying;
    186              },
    187              stream: pipe.outputStream,
    188              done: copyResolve,
    189            });
    190          })
    191            // Await the result of writing to the stream
    192            .then(() => pipe.outputStream.close(), this.close)
    193        );
    194      });
    195    });
    196  }
    197 
    198  /**
    199   * Close the transport.
    200   */
    201  close() {
    202    if (this.other) {
    203      // Remove the reference to the other endpoint before calling close(), to
    204      // avoid infinite recursion.
    205      const other = this.other;
    206      this.other = null;
    207      other.close();
    208    }
    209    if (this.hooks) {
    210      try {
    211        if (this.hooks.onTransportClosed) {
    212          this.hooks.onTransportClosed();
    213        }
    214      } catch (ex) {
    215        console.error(ex);
    216      }
    217      this.hooks = null;
    218    }
    219  }
    220 
    221  /**
    222   * An empty method for emulating the DebuggerTransport API.
    223   */
    224  ready() {}
    225 
    226  /**
    227   * Helper function that makes an object fully immutable.
    228   */
    229  _deepFreeze(object) {
    230    Object.freeze(object);
    231    for (const prop in object) {
    232      // Freeze the properties that are objects, not on the prototype, and not
    233      // already frozen. Note that this might leave an unfrozen reference
    234      // somewhere in the object if there is an already frozen object containing
    235      // an unfrozen object.
    236      if (
    237        object.hasOwnProperty(prop) &&
    238        typeof object === "object" &&
    239        !Object.isFrozen(object)
    240      ) {
    241        this._deepFreeze(object[prop]);
    242      }
    243    }
    244  }
    245 }
    246 
    247 exports.LocalDebuggerTransport = LocalDebuggerTransport;