tor-browser

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

network-events-stacktraces.js (7285B)


      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 loader.lazyRequireGetter(
      8  this,
      9  "ChannelEventSinkFactory",
     10  "resource://devtools/server/actors/network-monitor/channel-event-sink.js",
     11  true
     12 );
     13 
     14 const lazy = {};
     15 
     16 ChromeUtils.defineESModuleGetters(
     17  lazy,
     18  {
     19    NetworkUtils:
     20      "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs",
     21  },
     22  { global: "contextual" }
     23 );
     24 
     25 class NetworkEventStackTracesWatcher {
     26  /**
     27   * Start watching for all network event's stack traces related to a given Target actor.
     28   *
     29   * @param TargetActor targetActor
     30   *        The target actor from which we should observe the strack traces
     31   * @param Object options
     32   *        Dictionary object with following attributes:
     33   *        - onAvailable: mandatory
     34   *          This will be called for each resource.
     35   */
     36  async watch(targetActor, { onAvailable }) {
     37    this.stacktraces = new Map();
     38    this.onStackTraceAvailable = onAvailable;
     39    this.targetActor = targetActor;
     40 
     41    Services.obs.addObserver(this, "http-on-opening-request");
     42    Services.obs.addObserver(this, "document-on-opening-request");
     43    Services.obs.addObserver(this, "network-monitor-alternate-stack");
     44    ChannelEventSinkFactory.getService().registerCollector(this);
     45  }
     46 
     47  /**
     48   * Allows clearing of network stacktrace resources
     49   */
     50  clear() {
     51    this.stacktraces.clear();
     52  }
     53 
     54  /**
     55   * Stop watching for network event's strack traces related to a given Target Actor.
     56   */
     57  destroy() {
     58    this.clear();
     59    Services.obs.removeObserver(this, "http-on-opening-request");
     60    Services.obs.removeObserver(this, "document-on-opening-request");
     61    Services.obs.removeObserver(this, "network-monitor-alternate-stack");
     62    ChannelEventSinkFactory.getService().unregisterCollector(this);
     63  }
     64 
     65  onChannelRedirect(oldChannel, newChannel) {
     66    // We can be called with any nsIChannel, but are interested only in HTTP channels
     67    try {
     68      oldChannel.QueryInterface(Ci.nsIHttpChannel);
     69      newChannel.QueryInterface(Ci.nsIHttpChannel);
     70    } catch (ex) {
     71      return;
     72    }
     73 
     74    const oldId = oldChannel.channelId;
     75    const stacktrace = this.stacktraces.get(oldId);
     76    if (stacktrace) {
     77      this._setStackTrace(newChannel.channelId, stacktrace);
     78    }
     79  }
     80 
     81  observe(subject, topic, data) {
     82    let channel, id;
     83    try {
     84      // We need to QI nsIHttpChannel in order to load the interface's
     85      // methods / attributes for later code that could assume we are dealing
     86      // with a nsIHttpChannel.
     87      channel = subject.QueryInterface(Ci.nsIHttpChannel);
     88      id = channel.channelId;
     89    } catch (e1) {
     90      try {
     91        channel = subject.QueryInterface(Ci.nsIIdentChannel);
     92        id = channel.channelId;
     93      } catch (e2) {
     94        // WebSocketChannels do not have IDs, so use the serial. When a WebSocket is
     95        // opened in a content process, a channel is created locally but the HTTP
     96        // channel for the connection lives entirely in the parent process. When
     97        // the server code running in the parent sees that HTTP channel, it will
     98        // look for the creation stack using the websocket's serial.
     99        try {
    100          channel = subject.QueryInterface(Ci.nsIWebSocketChannel);
    101          id = channel.serial;
    102        } catch (e3) {
    103          // Try if the channel is a nsIWorkerChannelInfo which is the substitute
    104          // of the channel in the parent process.
    105          try {
    106            channel = subject.QueryInterface(Ci.nsIWorkerChannelInfo);
    107            id = channel.channelId;
    108          } catch (e4) {
    109            // Channels which don't implement the above interfaces can appear here,
    110            // such as nsIFileChannel. Ignore these channels.
    111            return;
    112          }
    113        }
    114      }
    115    }
    116 
    117    if (
    118      !lazy.NetworkUtils.matchRequest(channel, {
    119        targetActor: this.targetActor,
    120      })
    121    ) {
    122      return;
    123    }
    124 
    125    if (this.stacktraces.has(id)) {
    126      // We can get up to two stack traces for the same channel: one each from
    127      // the two observer topics we are listening to. Use the first stack trace
    128      // which is specified, and ignore any later one.
    129      return;
    130    }
    131 
    132    const stacktrace = [];
    133    switch (topic) {
    134      case "http-on-opening-request":
    135      case "document-on-opening-request": {
    136        // The channel is being opened on the main thread, associate the current
    137        // stack with it.
    138        //
    139        // Convert the nsIStackFrame XPCOM objects to a nice JSON that can be
    140        // passed around through message managers etc.
    141        let frame = Components.stack;
    142        if (frame?.caller) {
    143          frame = frame.caller;
    144          while (frame) {
    145            stacktrace.push({
    146              filename: frame.filename,
    147              lineNumber: frame.lineNumber,
    148              columnNumber: frame.columnNumber,
    149              functionName: frame.name,
    150              asyncCause: frame.asyncCause,
    151            });
    152            frame = frame.caller || frame.asyncCaller;
    153          }
    154        }
    155        break;
    156      }
    157      case "network-monitor-alternate-stack": {
    158        // An alternate stack trace is being specified for this channel.
    159        // The topic data is the JSON for the saved frame stack we should use,
    160        // so convert this into the expected format.
    161        //
    162        // This topic is used in the following cases:
    163        //
    164        // - The HTTP channel is opened asynchronously or on a different thread
    165        //   from the code which triggered its creation, in which case the stack
    166        //   from Components.stack will be empty. The alternate stack will be
    167        //   for the point we want to associate with the channel.
    168        //
    169        // - The channel is not a nsIHttpChannel, and we will receive no
    170        //   opening request notification for it.
    171        let frame = JSON.parse(data);
    172        while (frame) {
    173          stacktrace.push({
    174            filename: frame.source,
    175            lineNumber: frame.line,
    176            columnNumber: frame.column,
    177            functionName: frame.functionDisplayName,
    178            asyncCause: frame.asyncCause,
    179          });
    180          frame = frame.parent || frame.asyncParent;
    181        }
    182        break;
    183      }
    184      default:
    185        throw new Error("Unexpected observe() topic");
    186    }
    187 
    188    this._setStackTrace(id, stacktrace);
    189  }
    190 
    191  _setStackTrace(resourceId, stacktrace) {
    192    const isBrowserToolBox = this.targetActor.sessionContext.type == "all";
    193 
    194    const filteredStacktrace = isBrowserToolBox
    195      ? stacktrace
    196      : lazy.NetworkUtils.removeChromeFrames(stacktrace);
    197 
    198    this.stacktraces.set(resourceId, filteredStacktrace);
    199 
    200    const stacktraceAvailable =
    201      filteredStacktrace && !!filteredStacktrace.length;
    202 
    203    this.onStackTraceAvailable([
    204      {
    205        resourceId,
    206        stacktraceAvailable,
    207        lastFrame: stacktraceAvailable ? filteredStacktrace[0] : undefined,
    208      },
    209    ]);
    210  }
    211 
    212  getStackTrace(id) {
    213    let stacktrace = [];
    214    if (this.stacktraces.has(id)) {
    215      stacktrace = this.stacktraces.get(id);
    216    }
    217    return stacktrace;
    218  }
    219 }
    220 module.exports = NetworkEventStackTracesWatcher;