tor-browser

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

legacy-workers-watcher.js (8465B)


      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 LegacyProcessesWatcher = require("resource://devtools/shared/commands/target/legacy-target-watchers/legacy-processes-watcher.js");
      8 
      9 class LegacyWorkersWatcher {
     10  constructor(targetCommand, onTargetAvailable, onTargetDestroyed) {
     11    this.targetCommand = targetCommand;
     12    this.rootFront = targetCommand.rootFront;
     13 
     14    this.onTargetAvailable = onTargetAvailable;
     15    this.onTargetDestroyed = onTargetDestroyed;
     16 
     17    this.targetsByProcess = new WeakMap();
     18    this.targetsListeners = new WeakMap();
     19 
     20    this._onProcessAvailable = this._onProcessAvailable.bind(this);
     21    this._onProcessDestroyed = this._onProcessDestroyed.bind(this);
     22  }
     23 
     24  async _onProcessAvailable({ targetFront }) {
     25    this.targetsByProcess.set(targetFront, new Set());
     26    // Listen for worker which will be created later
     27    const listener = this._workerListChanged.bind(this, targetFront);
     28    this.targetsListeners.set(targetFront, listener);
     29 
     30    // If this is the browser toolbox, we have to listen from the RootFront
     31    // (see comment in _workerListChanged)
     32    const front = targetFront.isParentProcess ? this.rootFront : targetFront;
     33    front.on("workerListChanged", listener);
     34 
     35    // We also need to process the already existing workers
     36    await this._workerListChanged(targetFront);
     37  }
     38 
     39  async _onProcessDestroyed({ targetFront }) {
     40    const existingTargets = this.targetsByProcess.get(targetFront);
     41 
     42    // Process the new list to detect the ones being destroyed
     43    // Force destroying the targets
     44    for (const target of existingTargets) {
     45      this.onTargetDestroyed(target);
     46 
     47      target.destroy();
     48      existingTargets.delete(target);
     49    }
     50    this.targetsByProcess.delete(targetFront);
     51    this.targetsListeners.delete(targetFront);
     52  }
     53 
     54  _supportWorkerTarget(workerTarget) {
     55    // subprocess workers are ignored because they take several seconds to
     56    // attach to when opening the browser toolbox. See bug 1594597.
     57    // When attaching we get the following error:
     58    // JavaScript error: resource://devtools/server/startup/worker.js,
     59    //   line 37: NetworkError: WorkerDebuggerGlobalScope.loadSubScript: Failed to load worker script at resource://devtools/shared/worker/loader.js (nsresult = 0x805e0006)
     60    return (
     61      workerTarget.isDedicatedWorker &&
     62      !/resource:\/\/gre\/modules\/subprocess\/subprocess_.*\.worker\.js/.test(
     63        workerTarget.url
     64      )
     65    );
     66  }
     67 
     68  async _workerListChanged(targetFront) {
     69    // If we're in the Browser Toolbox, query workers from the Root Front instead of the
     70    // ParentProcessTarget as the ParentProcess Target filters out the workers to only
     71    // show the one from the top level window, whereas we expect the one from all the
     72    // windows, and also the window-less ones.
     73    // TODO: For Content Toolbox, expose SW of the page, maybe optionally?
     74    const front = targetFront.isParentProcess ? this.rootFront : targetFront;
     75    if (!front || front.isDestroyed() || this.targetCommand.isDestroyed()) {
     76      return;
     77    }
     78 
     79    let workers;
     80    try {
     81      ({ workers } = await front.listWorkers());
     82    } catch (e) {
     83      // Workers may be added/removed at anytime so that listWorkers request
     84      // can be spawn during a toolbox destroy sequence and easily fail
     85      if (front.isDestroyed()) {
     86        return;
     87      }
     88      throw e;
     89    }
     90 
     91    // Fetch the list of already existing worker targets for this process target front.
     92    const existingTargets = this.targetsByProcess.get(targetFront);
     93    if (!existingTargets) {
     94      // unlisten was called while processing the workerListChanged callback.
     95      return;
     96    }
     97 
     98    // Process the new list to detect the ones being destroyed
     99    // Force destroying the targets
    100    for (const target of existingTargets) {
    101      if (!workers.includes(target)) {
    102        this.onTargetDestroyed(target);
    103 
    104        target.destroy();
    105        existingTargets.delete(target);
    106      }
    107    }
    108 
    109    const promises = workers.map(workerTarget =>
    110      this._processNewWorkerTarget(workerTarget, existingTargets)
    111    );
    112    await Promise.all(promises);
    113  }
    114 
    115  // This is overloaded for Service Workers, which records all SW targets,
    116  // but only notify about a subset of them.
    117  _recordWorkerTarget(workerTarget) {
    118    return this._supportWorkerTarget(workerTarget);
    119  }
    120 
    121  async _processNewWorkerTarget(workerTarget, existingTargets) {
    122    if (
    123      !this._recordWorkerTarget(workerTarget) ||
    124      existingTargets.has(workerTarget) ||
    125      this.targetCommand.isDestroyed()
    126    ) {
    127      return;
    128    }
    129 
    130    // Add the new worker targets to the local list
    131    existingTargets.add(workerTarget);
    132 
    133    if (this._supportWorkerTarget(workerTarget)) {
    134      await this.onTargetAvailable(workerTarget);
    135    }
    136  }
    137 
    138  async listen() {
    139    // Listen to the current target front.
    140    this.target = this.targetCommand.targetFront;
    141 
    142    if (this.target.isParentProcess) {
    143      await this.targetCommand.watchTargets({
    144        types: [this.targetCommand.TYPES.PROCESS],
    145        onAvailable: this._onProcessAvailable,
    146        onDestroyed: this._onProcessDestroyed,
    147      });
    148 
    149      // The ParentProcessTarget front is considered to be a FRAME instead of a PROCESS.
    150      // So process it manually here.
    151      await this._onProcessAvailable({ targetFront: this.target });
    152      return;
    153    }
    154 
    155    if (this._isSharedWorkerWatcher) {
    156      // Here we're not in the browser toolbox, and SharedWorker targets are not supported
    157      // in regular toolbox (See Bug 1607778)
    158      return;
    159    }
    160 
    161    if (this._isServiceWorkerWatcher) {
    162      this._legacyProcessesWatcher = new LegacyProcessesWatcher(
    163        this.targetCommand,
    164        async targetFront => {
    165          // Service workers only live in content processes.
    166          if (!targetFront.isParentProcess) {
    167            await this._onProcessAvailable({ targetFront });
    168          }
    169        },
    170        targetFront => {
    171          if (!targetFront.isParentProcess) {
    172            this._onProcessDestroyed({ targetFront });
    173          }
    174        }
    175      );
    176      await this._legacyProcessesWatcher.listen();
    177      return;
    178    }
    179 
    180    // Here, we're handling Dedicated Workers in content toolbox.
    181    this.targetsByProcess.set(
    182      this.target,
    183      this.targetsByProcess.get(this.target) || new Set()
    184    );
    185    this._workerListChangedListener = this._workerListChanged.bind(
    186      this,
    187      this.target
    188    );
    189    this.target.on("workerListChanged", this._workerListChangedListener);
    190    await this._workerListChanged(this.target);
    191  }
    192 
    193  _getProcessTargets() {
    194    return this.targetCommand.getAllTargets([this.targetCommand.TYPES.PROCESS]);
    195  }
    196 
    197  unlisten({ isTargetSwitching } = {}) {
    198    // Stop listening for new process targets.
    199    if (this.target.isParentProcess) {
    200      this.targetCommand.unwatchTargets({
    201        types: [this.targetCommand.TYPES.PROCESS],
    202        onAvailable: this._onProcessAvailable,
    203        onDestroyed: this._onProcessDestroyed,
    204      });
    205    } else if (this._isServiceWorkerWatcher) {
    206      this._legacyProcessesWatcher.unlisten();
    207    }
    208 
    209    // Cleanup the targetsByProcess/targetsListeners maps, and unsubscribe from
    210    // all targetFronts. Process target fronts are either stored locally when
    211    // watching service workers for the content toolbox, or can be retrieved via
    212    // the TargetCommand API otherwise (see _getProcessTargets implementations).
    213    if (this.target.isParentProcess || this._isServiceWorkerWatcher) {
    214      for (const targetFront of this._getProcessTargets()) {
    215        const listener = this.targetsListeners.get(targetFront);
    216        targetFront.off("workerListChanged", listener);
    217 
    218        // When unlisten is called from a target switch or when we observe service workers targets
    219        // we don't want to remove the targets from targetsByProcess
    220        if (!isTargetSwitching || !this._isServiceWorkerWatcher) {
    221          this.targetsByProcess.delete(targetFront);
    222        }
    223        this.targetsListeners.delete(targetFront);
    224      }
    225    } else {
    226      this.target.off("workerListChanged", this._workerListChangedListener);
    227      delete this._workerListChangedListener;
    228      this.targetsByProcess.delete(this.target);
    229      this.targetsListeners.delete(this.target);
    230    }
    231  }
    232 }
    233 
    234 module.exports = LegacyWorkersWatcher;