tor-browser

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

Pool.js (6055B)


      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 var EventEmitter = require("resource://devtools/shared/event-emitter.js");
      8 
      9 /**
     10 * Actor and Front implementations
     11 */
     12 
     13 /**
     14 * A protocol object that can manage the lifetime of other protocol
     15 * objects. Pools are used on both sides of the connection to help coordinate lifetimes.
     16 *
     17 * @param {DevToolsServerConnection|DevToolsClient} [conn]
     18 *   Either a DevToolsServerConnection or a DevToolsClient. Must have
     19 *   addActorPool, removeActorPool, and poolFor.
     20 *   conn can be null if the subclass provides a conn property.
     21 * @param {string} [label]
     22 *   An optional label for the Pool.
     23 * @class
     24 */
     25 class Pool extends EventEmitter {
     26  constructor(conn, label) {
     27    super();
     28 
     29    if (conn) {
     30      this.conn = conn;
     31    }
     32    this.label = label;
     33 
     34    // Will be individually flipped to true by Actor/Front classes.
     35    // Will also only be exposed via Actor/Front::isDestroyed().
     36    this._isDestroyed = false;
     37  }
     38 
     39  __poolMap = null;
     40  parentPool = null;
     41 
     42  /**
     43   * Return the parent pool for this client.
     44   */
     45  getParent() {
     46    return this.parentPool;
     47  }
     48 
     49  /**
     50   * A pool is at the top of its pool hierarchy if it has:
     51   * - no parent
     52   * - or it is its own parent
     53   */
     54  isTopPool() {
     55    const parent = this.getParent();
     56    return !parent || parent === this;
     57  }
     58 
     59  poolFor(actorID) {
     60    return this.conn.poolFor(actorID);
     61  }
     62 
     63  /**
     64   * Override this if you want actors returned by this actor
     65   * to belong to a different actor by default.
     66   */
     67  marshallPool() {
     68    return this;
     69  }
     70 
     71  /**
     72   * Pool is the base class for all actors, even leaf nodes.
     73   * If the child map is actually referenced, go ahead and create
     74   * the stuff needed by the pool.
     75   */
     76  get _poolMap() {
     77    if (this.__poolMap) {
     78      return this.__poolMap;
     79    }
     80    this.__poolMap = new Map();
     81    this.conn.addActorPool(this);
     82    return this.__poolMap;
     83  }
     84 
     85  /**
     86   * Add an actor as a child of this pool.
     87   */
     88  manage(actor) {
     89    if (!actor.actorID) {
     90      actor.actorID = this.conn.allocID(actor.typeName);
     91    } else {
     92      // If the actor is already registered in a pool, remove it without destroying it.
     93      // This happens for example when an addon is reloaded. To see this behavior, take a
     94      // look at devtools/server/tests/xpcshell/test_addon_reload.js
     95 
     96      const parent = actor.getParent();
     97      if (parent && parent !== this) {
     98        parent.unmanage(actor);
     99      }
    100    }
    101    // Duplicate the ID into another field as `actorID` will be cleared on destruction
    102    actor.persistedActorID = actor.actorID;
    103 
    104    this._poolMap.set(actor.actorID, actor);
    105    actor.parentPool = this;
    106  }
    107 
    108  unmanageChildren(FrontType) {
    109    for (const front of this.poolChildren()) {
    110      if (!FrontType || front instanceof FrontType) {
    111        this.unmanage(front);
    112      }
    113    }
    114  }
    115 
    116  /**
    117   * Remove an actor as a child of this pool.
    118   */
    119  unmanage(actor) {
    120    if (this.__poolMap) {
    121      this.__poolMap.delete(actor.actorID);
    122    }
    123    actor.parentPool = null;
    124  }
    125 
    126  // true if the given actor ID exists in the pool.
    127  has(actorID) {
    128    return this.__poolMap && this._poolMap.has(actorID);
    129  }
    130 
    131  /**
    132   * Search for an actor in this pool, given an actorID
    133   *
    134   * @param {string} actorID
    135   * @returns {Actor/null} Returns null if the actor wasn't found
    136   */
    137  getActorByID(actorID) {
    138    if (this.__poolMap) {
    139      return this._poolMap.get(actorID);
    140    }
    141    return null;
    142  }
    143 
    144  // Generator that yields each non-self child of the pool.
    145  *poolChildren() {
    146    if (!this.__poolMap) {
    147      return;
    148    }
    149    for (const actor of this.__poolMap.values()) {
    150      // Self-owned actors are ok, but don't need visiting twice.
    151      if (actor === this) {
    152        continue;
    153      }
    154      yield actor;
    155    }
    156  }
    157 
    158  isDestroyed() {
    159    // Note: _isDestroyed is only flipped from Actor and Front subclasses for
    160    // now, so this method should not be called on pure Pool instances.
    161    // See Bug 1717811.
    162    return this._isDestroyed;
    163  }
    164 
    165  /**
    166   * Pools can override this method in order to opt-out of a destroy sequence.
    167   *
    168   * For instance, Fronts are destroyed during the toolbox destroy. However when
    169   * the toolbox is destroyed, the document holding the toolbox is also
    170   * destroyed. So it should not be necessary to cleanup Fronts during toolbox
    171   * destroy.
    172   *
    173   * For the time being, Fronts (or Pools in general) which want to opt-out of
    174   * toolbox destroy can override this method and check the value of
    175   * `this.conn.isToolboxDestroy`.
    176   */
    177  skipDestroy() {
    178    return false;
    179  }
    180 
    181  /**
    182   * Destroy this item, removing it from a parent if it has one,
    183   * and destroying all children if necessary.
    184   */
    185  destroy() {
    186    const parent = this.getParent();
    187    if (parent) {
    188      parent.unmanage(this);
    189    }
    190    if (!this.__poolMap) {
    191      return;
    192    }
    193    // Immediately clear the poolmap so that we bail out early if the code is reentrant.
    194    const poolMap = this.__poolMap;
    195    this.__poolMap = null;
    196 
    197    for (const actor of poolMap.values()) {
    198      // Self-owned actors are ok, but don't need destroying twice.
    199      if (actor === this) {
    200        continue;
    201      }
    202 
    203      // Some pool-managed values don't extend Pool and won't have skipDestroy
    204      // defined. For instance, the root actor and the lazy actors.
    205      if (typeof actor.skipDestroy === "function" && actor.skipDestroy()) {
    206        continue;
    207      }
    208 
    209      const destroy = actor.destroy;
    210      if (destroy) {
    211        // Disconnect destroy while we're destroying in case of (misbehaving)
    212        // circular ownership.
    213        actor.destroy = null;
    214        destroy.call(actor);
    215        actor.destroy = destroy;
    216      }
    217    }
    218 
    219    // `conn` might be null for LazyPool in an unexplained way.
    220    if (this.conn) {
    221      this.conn.removeActorPool(this);
    222      this.conn = null;
    223    }
    224  }
    225 }
    226 
    227 exports.Pool = Pool;