tor-browser

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

base-target-actor.js (11096B)


      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 { Actor } = require("resource://devtools/shared/protocol.js");
      8 const {
      9  TYPES: { DOCUMENT_EVENT, NETWORK_EVENT_STACKTRACE, CONSOLE_MESSAGE },
     10  getResourceWatcher,
     11 } = require("resource://devtools/server/actors/resources/index.js");
     12 const Targets = require("devtools/server/actors/targets/index");
     13 const {
     14  ObjectActorPool,
     15 } = require("resource://devtools/server/actors/object/ObjectActorPool.js");
     16 
     17 const { throttle } = require("resource://devtools/shared/throttle.js");
     18 const RESOURCES_THROTTLING_DELAY = 100;
     19 
     20 loader.lazyRequireGetter(
     21  this,
     22  "SessionDataProcessors",
     23  "resource://devtools/server/actors/targets/session-data-processors/index.js",
     24  true
     25 );
     26 
     27 class BaseTargetActor extends Actor {
     28  constructor(conn, targetType, spec) {
     29    super(conn, spec);
     30 
     31    /**
     32     * Type of target, a string of Targets.TYPES.
     33     *
     34     * @return {string}
     35     */
     36    this.targetType = targetType;
     37 
     38    // Lists of resources available/updated/destroyed RDP packet
     39    // currently queued which will be emitted after a throttle delay.
     40    this.#throttledResources = {
     41      available: [],
     42      updated: [],
     43      destroyed: [],
     44    };
     45 
     46    this.#throttledEmitResources = throttle(
     47      this.emitResources.bind(this),
     48      RESOURCES_THROTTLING_DELAY
     49    );
     50  }
     51 
     52  // Whenever createValueGripForTarget is used, any Object Actor will be added to this pool
     53  get objectsPool() {
     54    if (this._objectsPool) {
     55      return this._objectsPool;
     56    }
     57    this._objectsPool = new ObjectActorPool(this.threadActor, "target-objects");
     58    this.manage(this._objectsPool);
     59    return this._objectsPool;
     60  }
     61 
     62  #throttledResources;
     63  #throttledEmitResources;
     64 
     65  /**
     66   * Process a new data entry, which can be watched resources, breakpoints, ...
     67   *
     68   * @param string type
     69   *        The type of data to be added
     70   * @param Array<Object> entries
     71   *        The values to be added to this type of data
     72   * @param Boolean isDocumentCreation
     73   *        Set to true if this function is called just after a new document (and its
     74   *        associated target) is created.
     75   * @param String updateType
     76   *        "add" will only add the new entries in the existing data set.
     77   *        "set" will update the data set with the new entries.
     78   */
     79  async addOrSetSessionDataEntry(
     80    type,
     81    entries,
     82    isDocumentCreation = false,
     83    updateType
     84  ) {
     85    const processor = SessionDataProcessors[type];
     86    if (processor) {
     87      await processor.addOrSetSessionDataEntry(
     88        this,
     89        entries,
     90        isDocumentCreation,
     91        updateType
     92      );
     93    }
     94  }
     95 
     96  /**
     97   * Remove data entries that have been previously added via addOrSetSessionDataEntry
     98   *
     99   * See addOrSetSessionDataEntry for argument description.
    100   */
    101  removeSessionDataEntry(type, entries) {
    102    const processor = SessionDataProcessors[type];
    103    if (processor) {
    104      processor.removeSessionDataEntry(this, entries);
    105    }
    106  }
    107 
    108  /**
    109   * Called by Resource Watchers, when new resources are available, updated or destroyed.
    110   * This will only accumulate resource update packets into throttledResources object.
    111   * The actualy sending of resources will happen from emitResources.
    112   *
    113   * @param String updateType
    114   *        Can be "available", "updated" or "destroyed"
    115   * @param String resourceType
    116   *        The type of resources to be notified about.
    117   * @param Array<json|string> resources
    118   *        For "available", the array will be a list of new resource JSON objects sent as-is to the client.
    119   *        It can contain actor IDs, actor forms, to be manually marshalled by the client.
    120   *        For "updated", the array will contain a list of objects with the attributes documented in
    121   *        `ResourceCommand._onResourceUpdated` jsdoc.
    122   *        For "destroyed", the array will contain a list of resource IDs (strings).
    123   */
    124  notifyResources(updateType, resourceType, resources) {
    125    if (resources.length === 0 || this.isDestroyed()) {
    126      // Don't try to emit if the resources array is empty or the actor was
    127      // destroyed.
    128      return;
    129    }
    130 
    131    const shouldEmitSynchronously =
    132      resourceType == NETWORK_EVENT_STACKTRACE ||
    133      (resourceType == DOCUMENT_EVENT &&
    134        resources.some(resource => resource.name == "will-navigate"));
    135 
    136    // If the last throttled resources were of the same resource type,
    137    // augment the resources array with the new resources
    138    const lastResourceInThrottleCache =
    139      this.#throttledResources[updateType].at(-1);
    140    if (
    141      lastResourceInThrottleCache &&
    142      lastResourceInThrottleCache[0] === resourceType
    143    ) {
    144      lastResourceInThrottleCache[1].push.apply(
    145        lastResourceInThrottleCache[1],
    146        resources
    147      );
    148    } else {
    149      // Otherwise, add a new item in the throttle queue with the resource type
    150      this.#throttledResources[updateType].push([resourceType, resources]);
    151    }
    152 
    153    // Force firing resources immediately when:
    154    // * we receive DOCUMENT_EVENT's will-navigate
    155    // This will force clearing resources on the client side ASAP.
    156    // Otherwise we might emit some other RDP event (outside of resources),
    157    // which will be cleared by the throttled/delayed will-navigate.
    158    // * we receive NETWORK_EVENT_STACKTRACE which are meant to be dispatched *before*
    159    // the related NETWORK_EVENT fired from the parent process which are also throttled.
    160    if (shouldEmitSynchronously) {
    161      this.emitResources();
    162    } else {
    163      this.#throttledEmitResources();
    164    }
    165  }
    166 
    167  /**
    168   * Flush resources to DevTools transport layer, actually sending all resource update packets
    169   */
    170  emitResources() {
    171    if (this.isDestroyed()) {
    172      return;
    173    }
    174    for (const updateType of ["available", "updated", "destroyed"]) {
    175      const resources = this.#throttledResources[updateType];
    176      if (!resources.length) {
    177        continue;
    178      }
    179      this.#throttledResources[updateType] = [];
    180      this.emit(`resources-${updateType}-array`, resources);
    181    }
    182  }
    183 
    184  // List of actor prefixes (string) which have already been instantiated via getTargetScopedActor method.
    185  #instantiatedTargetScopedActors = new Set();
    186 
    187  /**
    188   * Try to return any target scoped actor instance, if it exists.
    189   * They are lazily instantiated and so will only be available
    190   * if the client called at least one of their method.
    191   *
    192   * @param {string} prefix
    193   *        Prefix for the actor we would like to retrieve.
    194   *        Defined in devtools/server/actors/utils/actor-registry.js
    195   */
    196  getTargetScopedActor(prefix) {
    197    if (this.isDestroyed()) {
    198      return null;
    199    }
    200    const form = this.form();
    201    this.#instantiatedTargetScopedActors.add(prefix);
    202    return this.conn._getOrCreateActor(form[prefix + "Actor"]);
    203  }
    204 
    205  /**
    206   * Returns true, if the related target scoped actor has already been queried
    207   * and instantiated via `getTargetScopedActor` method.
    208   *
    209   * @param {string} prefix
    210   *        See getTargetScopedActor definition
    211   * @return Boolean
    212   *         True, if the actor has already been instantiated.
    213   */
    214  hasTargetScopedActor(prefix) {
    215    return this.#instantiatedTargetScopedActors.has(prefix);
    216  }
    217 
    218  /**
    219   * Server side boolean to know if the tracer has been enabled by the user.
    220   *
    221   * By enabled, we mean the feature has been exposed to the user,
    222   * not that the tracer is actively tracing executions.
    223   */
    224  isTracerFeatureEnabled = false;
    225 
    226  /**
    227   * Apply target-specific options.
    228   *
    229   * This will be called by the watcher when the DevTools target-configuration
    230   * is updated, or when a target is created via JSWindowActors.
    231   *
    232   * @param {JSON} options
    233   *        Configuration object provided by the client.
    234   *        See target-configuration actor.
    235   * @param {boolean} calledFromDocumentCreate
    236   *        True, when this is called with initial configuration when the related target
    237   *        actor is instantiated.
    238   */
    239  updateTargetConfiguration(options = {}, calledFromDocumentCreation = false) {
    240    if (typeof options.isTracerFeatureEnabled === "boolean") {
    241      this.isTracerFeatureEnabled = options.isTracerFeatureEnabled;
    242    }
    243    // If there is some tracer options, we should start tracing, otherwise we should stop (if we were)
    244    if (options.tracerOptions) {
    245      // Ignore the SessionData update if the user requested to start the tracer on next page load and:
    246      //   - we apply it to an already loaded WindowGlobal,
    247      //   - the target isn't the top level one.
    248      if (
    249        options.tracerOptions.traceOnNextLoad &&
    250        (!calledFromDocumentCreation || !this.isTopLevelTarget)
    251      ) {
    252        if (this.isTopLevelTarget) {
    253          const consoleMessageWatcher = getResourceWatcher(
    254            this,
    255            CONSOLE_MESSAGE
    256          );
    257          if (consoleMessageWatcher) {
    258            consoleMessageWatcher.emitMessages([
    259              {
    260                arguments: [
    261                  "Waiting for next navigation or page reload before starting tracing",
    262                ],
    263                styles: [],
    264                level: "jstracer",
    265                chromeContext: false,
    266                timeStamp: ChromeUtils.dateNow(),
    267              },
    268            ]);
    269          }
    270        }
    271        return;
    272      }
    273      // Bug 1874204: For now, in the browser toolbox, only frame and workers are traced.
    274      // Content process targets are ignored as they would also include each document/frame target.
    275      // This would require some work to ignore FRAME targets from here, only in case of browser toolbox,
    276      // and also handle all content process documents for DOM Event logging.
    277      //
    278      // Bug 1874219: Also ignore extensions for now as they are all running in the same process,
    279      // whereas we can only spawn one tracer per thread.
    280      if (
    281        this.targetType == Targets.TYPES.PROCESS ||
    282        this.url?.startsWith("moz-extension://")
    283      ) {
    284        return;
    285      }
    286      // In the browser toolbox, when debugging the parent process, we should only toggle the tracer in the Parent Process Target Actor.
    287      // We have to ignore any frame target which may run in the parent process.
    288      // For example DevTools documents or a tab running in the parent process.
    289      // (PROCESS_TYPE_DEFAULT refers to the parent process)
    290      if (
    291        this.sessionContext.type == "all" &&
    292        this.targetType === Targets.TYPES.FRAME &&
    293        this.typeName != "parentProcessTarget" &&
    294        Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT
    295      ) {
    296        return;
    297      }
    298      const tracerActor = this.getTargetScopedActor("tracer");
    299      tracerActor.startTracing(options.tracerOptions);
    300    } else if (this.hasTargetScopedActor("tracer")) {
    301      const tracerActor = this.getTargetScopedActor("tracer");
    302      tracerActor.stopTracing();
    303    }
    304  }
    305 }
    306 exports.BaseTargetActor = BaseTargetActor;