tor-browser

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

tracer.js (8697B)


      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 lazy = {};
      8 ChromeUtils.defineESModuleGetters(
      9  lazy,
     10  {
     11    JSTracer: "resource://devtools/server/tracer/tracer.sys.mjs",
     12  },
     13  { global: "contextual" }
     14 );
     15 
     16 const { Actor } = require("resource://devtools/shared/protocol.js");
     17 const { createValueGrip } = require("devtools/server/actors/object/utils");
     18 const {
     19  ObjectActorPool,
     20 } = require("resource://devtools/server/actors/object/ObjectActorPool.js");
     21 const {
     22  tracerSpec,
     23  TRACER_LOG_METHODS,
     24 } = require("resource://devtools/shared/specs/tracer.js");
     25 
     26 loader.lazyRequireGetter(
     27  this,
     28  "StdoutTracingListener",
     29  "resource://devtools/server/actors/tracer/stdout.js",
     30  true
     31 );
     32 loader.lazyRequireGetter(
     33  this,
     34  "ResourcesTracingListener",
     35  "resource://devtools/server/actors/tracer/resources.js",
     36  true
     37 );
     38 loader.lazyRequireGetter(
     39  this,
     40  "ProfilerTracingListener",
     41  "resource://devtools/server/actors/tracer/profiler.js",
     42  true
     43 );
     44 
     45 // Indexes of each data type within the array describing a trace
     46 exports.TRACER_FIELDS_INDEXES = {
     47  // This is shared with all the data types
     48  TYPE: 0,
     49 
     50  // Frame traces are slightly special and do not share any field with the other data types
     51  FRAME_IMPLEMENTATION: 1,
     52  FRAME_NAME: 2,
     53  FRAME_SOURCEID: 3,
     54  FRAME_LINE: 4,
     55  FRAME_COLUMN: 5,
     56  FRAME_URL: 6,
     57 
     58  // These fields are shared with all but frame data types
     59  PREFIX: 1,
     60  FRAME_INDEX: 2,
     61  TIMESTAMP: 3,
     62  DEPTH: 4,
     63 
     64  EVENT_NAME: 5,
     65 
     66  ENTER_ARGS: 5,
     67  ENTER_ARG_NAMES: 6,
     68 
     69  EXIT_PARENT_FRAME_ID: 5,
     70  EXIT_RETURNED_VALUE: 6,
     71  EXIT_WHY: 7,
     72 
     73  DOM_MUTATION_TYPE: 5,
     74  DOM_MUTATION_ELEMENT: 6,
     75 };
     76 
     77 const VALID_LOG_METHODS = Object.values(TRACER_LOG_METHODS);
     78 
     79 class TracerActor extends Actor {
     80  constructor(conn, targetActor) {
     81    super(conn, tracerSpec);
     82    this.targetActor = targetActor;
     83  }
     84 
     85  // When the tracer is stopped, save the result of the Listener Class.
     86  // This is used by the profiler log method and the getProfile method.
     87  #stopResult = null;
     88 
     89  // A Pool for all JS values emitted by the Tracer Actor.
     90  // This helps instantiate a unique Object Actor per JS Object communicated to the client.
     91  // This also helps share the same Object Actor instances when evaluating JS via
     92  // the console actor.
     93  // This pool is created lazily, only once we start a new trace.
     94  // We also clear the pool before starting the trace.
     95  #tracerPool = null;
     96 
     97  destroy() {
     98    this.stopTracing();
     99  }
    100 
    101  getLogMethod() {
    102    return this.logMethod;
    103  }
    104 
    105  /**
    106   * Toggle tracing JavaScript.
    107   * Meant for the WebConsole command in order to pass advanced
    108   * configuration directly to JavaScriptTracer class.
    109   *
    110   * @param {object} options
    111   *        Options used to configure JavaScriptTracer.
    112   *        See `JavaScriptTracer.startTracing`.
    113   * @return {boolean}
    114   *         True if the tracer starts, or false if it was stopped.
    115   */
    116  toggleTracing(options) {
    117    if (!this.tracingListener) {
    118      this.startTracing(options);
    119      return true;
    120    }
    121    this.stopTracing();
    122    return false;
    123  }
    124 
    125  /**
    126   * Start tracing.
    127   *
    128   * @param {object} options
    129   *        Options used to configure JavaScriptTracer.
    130   *        See `JavaScriptTracer.startTracing`.
    131   */
    132  // eslint-disable-next-line complexity
    133  startTracing(options = {}) {
    134    if (options.logMethod && !VALID_LOG_METHODS.includes(options.logMethod)) {
    135      throw new Error(
    136        `Invalid log method '${options.logMethod}'. Only supports: ${VALID_LOG_METHODS}`
    137      );
    138    }
    139    if (options.prefix && typeof options.prefix != "string") {
    140      throw new Error("Invalid prefix, only support string type");
    141    }
    142    if (options.maxDepth && typeof options.maxDepth != "number") {
    143      throw new Error("Invalid max-depth, only support numbers");
    144    }
    145    if (options.maxRecords && typeof options.maxRecords != "number") {
    146      throw new Error("Invalid max-records, only support numbers");
    147    }
    148 
    149    // When tracing on next user interaction is enabled,
    150    // disable logging from workers as this makes the tracer work
    151    // against visible documents and is actived per document thread.
    152    if (options.traceOnNextInteraction && isWorker) {
    153      return;
    154    }
    155 
    156    // Ignore WindowGlobal target actors for WindowGlobal of iframes running in the same process and thread as their parent document.
    157    // isProcessRoot will be true for each WindowGlobal being the top parent within a given process.
    158    // It will typically be true for WindowGlobal of iframe running in a distinct origin and process,
    159    // but only for the top iframe document. It will also be true for the top level tab document.
    160    if (
    161      this.targetActor.window &&
    162      !this.targetActor.window.windowGlobalChild?.isProcessRoot
    163    ) {
    164      return;
    165    }
    166 
    167    // Flush any previous recorded data only when we start a new tracer
    168    // as we may still analyse trace data after stopping the trace.
    169    // The pool will then be re-created on demand from createValueGrip.
    170    if (this.#tracerPool) {
    171      this.#tracerPool.destroy();
    172      this.#tracerPool = null;
    173    }
    174 
    175    this.logMethod = options.logMethod || TRACER_LOG_METHODS.STDOUT;
    176 
    177    let ListenerClass = null;
    178    // Currently only the profiler output is supported with the native tracer.
    179    let useNativeTracing = false;
    180    switch (this.logMethod) {
    181      case TRACER_LOG_METHODS.STDOUT:
    182        ListenerClass = StdoutTracingListener;
    183        break;
    184      case TRACER_LOG_METHODS.CONSOLE:
    185      case TRACER_LOG_METHODS.DEBUGGER_SIDEBAR:
    186        // Console and debugger sidebar are both using JSTRACE_STATE/JSTRACE_TRACE resources
    187        // to receive tracing data.
    188        ListenerClass = ResourcesTracingListener;
    189        break;
    190      case TRACER_LOG_METHODS.PROFILER:
    191        ListenerClass = ProfilerTracingListener;
    192        // Recording function returns is mandatory when recording profiler output.
    193        // Otherwise frames are not closed and mixed up in the profiler frontend.
    194        options.traceFunctionReturn = true;
    195        useNativeTracing = true;
    196        break;
    197    }
    198    this.tracingListener = new ListenerClass({
    199      targetActor: this.targetActor,
    200      traceValues: !!options.traceValues,
    201      traceActor: this,
    202    });
    203    lazy.JSTracer.addTracingListener(this.tracingListener);
    204 
    205    this.traceValues = !!options.traceValues;
    206    try {
    207      lazy.JSTracer.startTracing({
    208        global: this.targetActor.targetGlobal,
    209        prefix: options.prefix || "",
    210        // Enable receiving the `currentDOMEvent` being passed to `onTracingFrame`
    211        traceDOMEvents: true,
    212        // Enable tracing DOM Mutations
    213        traceDOMMutations: options.traceDOMMutations,
    214        // Enable tracing function arguments as well as returned values
    215        traceValues: !!options.traceValues,
    216        // Enable tracing only on next user interaction
    217        traceOnNextInteraction: !!options.traceOnNextInteraction,
    218        // Notify about frame exit / function call returning
    219        traceFunctionReturn: !!options.traceFunctionReturn,
    220        // Use the native tracing implementation
    221        useNativeTracing,
    222        // Ignore frames beyond the given depth
    223        maxDepth: options.maxDepth,
    224        // Stop the tracing after a number of top level frames
    225        maxRecords: options.maxRecords,
    226      });
    227    } catch (e) {
    228      // If startTracing throws, it probably rejected one of its options and we should
    229      // unregister the tracing listener.
    230      this.stopTracing();
    231      throw e;
    232    }
    233  }
    234 
    235  async stopTracing() {
    236    if (!this.tracingListener) {
    237      return;
    238    }
    239    // Remove before stopping to prevent receiving the stop notification
    240    lazy.JSTracer.removeTracingListener(this.tracingListener);
    241    // Save the result of the stop request for the profiler and the getProfile RDP method
    242    this.#stopResult = this.tracingListener.stop();
    243    this.tracingListener = null;
    244 
    245    lazy.JSTracer.stopTracing();
    246    this.logMethod = null;
    247  }
    248 
    249  /**
    250   * Queried by THREAD_STATE watcher to send the gecko profiler data
    251   * as part of THREAD STATE "stop" resource.
    252   *
    253   * @return {object} Gecko profiler profile object.
    254   */
    255  async getProfile() {
    256    // #stopResult is a promise
    257    return this.#stopResult;
    258  }
    259 
    260  createValueGrip(value) {
    261    if (!this.#tracerPool) {
    262      this.#tracerPool = new ObjectActorPool(
    263        this.targetActor.threadActor,
    264        "tracer",
    265        true
    266      );
    267      this.manage(this.#tracerPool);
    268    }
    269    return createValueGrip(this, value, this.#tracerPool);
    270  }
    271 }
    272 exports.TracerActor = TracerActor;