tor-browser

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

websockets.js (5093B)


      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 {
      8  LongStringActor,
      9 } = require("resource://devtools/server/actors/string.js");
     10 
     11 const webSocketEventService = Cc[
     12  "@mozilla.org/websocketevent/service;1"
     13 ].getService(Ci.nsIWebSocketEventService);
     14 
     15 class WebSocketWatcher {
     16  constructor() {
     17    this.windowIds = new Set();
     18    // Maintains a map of all the connection channels per websocket
     19    // The map item is keyed on the `webSocketSerialID` and stores
     20    // the `httpChannelId` as value.
     21    this.connections = new Map();
     22    this.onWindowReady = this.onWindowReady.bind(this);
     23    this.onWindowDestroy = this.onWindowDestroy.bind(this);
     24  }
     25 
     26  static createResource(wsMessageType, eventParams) {
     27    return {
     28      wsMessageType,
     29      ...eventParams,
     30    };
     31  }
     32 
     33  static prepareFramePayload(targetActor, frame) {
     34    const payload = new LongStringActor(targetActor.conn, frame.payload);
     35    targetActor.manage(payload);
     36    return payload.form();
     37  }
     38 
     39  watch(targetActor, { onAvailable }) {
     40    this.targetActor = targetActor;
     41    this.onAvailable = onAvailable;
     42 
     43    for (const window of this.targetActor.windows) {
     44      const { innerWindowId } = window.windowGlobalChild;
     45      this.startListening(innerWindowId);
     46    }
     47 
     48    // On navigate/reload we should re-start listening with the
     49    // new `innerWindowID`
     50    this.targetActor.on("window-ready", this.onWindowReady);
     51    this.targetActor.on("window-destroyed", this.onWindowDestroy);
     52  }
     53 
     54  onWindowReady({ window }) {
     55    if (!this.targetActor.followWindowGlobalLifeCycle) {
     56      const { innerWindowId } = window.windowGlobalChild;
     57      this.startListening(innerWindowId);
     58    }
     59  }
     60 
     61  onWindowDestroy({ id }) {
     62    this.stopListening(id);
     63  }
     64 
     65  startListening(innerWindowId) {
     66    if (!this.windowIds.has(innerWindowId)) {
     67      this.windowIds.add(innerWindowId);
     68      webSocketEventService.addListener(innerWindowId, this);
     69    }
     70  }
     71 
     72  stopListening(innerWindowId) {
     73    if (this.windowIds.has(innerWindowId)) {
     74      this.windowIds.delete(innerWindowId);
     75      if (!webSocketEventService.hasListenerFor(innerWindowId)) {
     76        // The listener might have already been cleaned up on `window-destroy`.
     77        console.warn(
     78          "Already stopped listening to websocket events for this window."
     79        );
     80        return;
     81      }
     82      webSocketEventService.removeListener(innerWindowId, this);
     83    }
     84  }
     85 
     86  destroy() {
     87    for (const id of this.windowIds) {
     88      this.stopListening(id);
     89    }
     90    this.targetActor.off("window-ready", this.onWindowReady);
     91    this.targetActor.off("window-destroyed", this.onWindowDestroy);
     92  }
     93 
     94  // methods for the nsIWebSocketEventService
     95  webSocketCreated() {}
     96 
     97  webSocketOpened(
     98    webSocketSerialID,
     99    effectiveURI,
    100    protocols,
    101    extensions,
    102    httpChannelId
    103  ) {
    104    this.connections.set(webSocketSerialID, httpChannelId);
    105    const resource = WebSocketWatcher.createResource("webSocketOpened", {
    106      httpChannelId,
    107      effectiveURI,
    108      protocols,
    109      extensions,
    110    });
    111 
    112    this.onAvailable([resource]);
    113  }
    114 
    115  webSocketMessageAvailable() {}
    116 
    117  webSocketClosed(webSocketSerialID, wasClean, code, reason) {
    118    const httpChannelId = this.connections.get(webSocketSerialID);
    119    this.connections.delete(webSocketSerialID);
    120 
    121    const resource = WebSocketWatcher.createResource("webSocketClosed", {
    122      httpChannelId,
    123      wasClean,
    124      code,
    125      reason,
    126    });
    127 
    128    this.onAvailable([resource]);
    129  }
    130 
    131  frameReceived(webSocketSerialID, frame) {
    132    const httpChannelId = this.connections.get(webSocketSerialID);
    133    if (!httpChannelId) {
    134      return;
    135    }
    136 
    137    const payload = WebSocketWatcher.prepareFramePayload(
    138      this.targetActor,
    139      frame
    140    );
    141    const resource = WebSocketWatcher.createResource("frameReceived", {
    142      httpChannelId,
    143      data: {
    144        type: "received",
    145        payload,
    146        timeStamp: frame.timeStamp,
    147        finBit: frame.finBit,
    148        rsvBit1: frame.rsvBit1,
    149        rsvBit2: frame.rsvBit2,
    150        rsvBit3: frame.rsvBit3,
    151        opCode: frame.opCode,
    152        mask: frame.mask,
    153        maskBit: frame.maskBit,
    154      },
    155    });
    156 
    157    this.onAvailable([resource]);
    158  }
    159 
    160  frameSent(webSocketSerialID, frame) {
    161    const httpChannelId = this.connections.get(webSocketSerialID);
    162 
    163    if (!httpChannelId) {
    164      return;
    165    }
    166 
    167    const payload = WebSocketWatcher.prepareFramePayload(
    168      this.targetActor,
    169      frame
    170    );
    171    const resource = WebSocketWatcher.createResource("frameSent", {
    172      httpChannelId,
    173      data: {
    174        type: "sent",
    175        payload,
    176        timeStamp: frame.timeStamp,
    177        finBit: frame.finBit,
    178        rsvBit1: frame.rsvBit1,
    179        rsvBit2: frame.rsvBit2,
    180        rsvBit3: frame.rsvBit3,
    181        opCode: frame.opCode,
    182        mask: frame.mask,
    183        maskBit: frame.maskBit,
    184      },
    185    });
    186 
    187    this.onAvailable([resource]);
    188  }
    189 }
    190 
    191 module.exports = WebSocketWatcher;