diff options
Diffstat (limited to 'devtools/shared/protocol/Pool.js')
-rw-r--r-- | devtools/shared/protocol/Pool.js | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/devtools/shared/protocol/Pool.js b/devtools/shared/protocol/Pool.js new file mode 100644 index 0000000000..b5cb4c3eb1 --- /dev/null +++ b/devtools/shared/protocol/Pool.js @@ -0,0 +1,220 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var EventEmitter = require("resource://devtools/shared/event-emitter.js"); + +/** + * Actor and Front implementations + */ + +/** + * A protocol object that can manage the lifetime of other protocol + * objects. Pools are used on both sides of the connection to help coordinate lifetimes. + * + * @param {DevToolsServerConnection|DevToolsClient} [conn] + * Either a DevToolsServerConnection or a DevToolsClient. Must have + * addActorPool, removeActorPool, and poolFor. + * conn can be null if the subclass provides a conn property. + * @param {String} [label] + * An optional label for the Pool. + * @constructor + */ +class Pool extends EventEmitter { + constructor(conn, label) { + super(); + + if (conn) { + this.conn = conn; + } + this.label = label; + + // Will be individually flipped to true by Actor/Front classes. + // Will also only be exposed via Actor/Front::isDestroyed(). + this._isDestroyed = false; + } + + __poolMap = null; + parentPool = null; + + /** + * Return the parent pool for this client. + */ + getParent() { + return this.parentPool; + } + + /** + * A pool is at the top of its pool hierarchy if it has: + * - no parent + * - or it is its own parent + */ + isTopPool() { + const parent = this.getParent(); + return !parent || parent === this; + } + + poolFor(actorID) { + return this.conn.poolFor(actorID); + } + + /** + * Override this if you want actors returned by this actor + * to belong to a different actor by default. + */ + marshallPool() { + return this; + } + + /** + * Pool is the base class for all actors, even leaf nodes. + * If the child map is actually referenced, go ahead and create + * the stuff needed by the pool. + */ + get _poolMap() { + if (this.__poolMap) { + return this.__poolMap; + } + this.__poolMap = new Map(); + this.conn.addActorPool(this); + return this.__poolMap; + } + + /** + * Add an actor as a child of this pool. + */ + manage(actor) { + if (!actor.actorID) { + actor.actorID = this.conn.allocID(actor.typeName); + } else { + // If the actor is already registered in a pool, remove it without destroying it. + // This happens for example when an addon is reloaded. To see this behavior, take a + // look at devtools/server/tests/xpcshell/test_addon_reload.js + + const parent = actor.getParent(); + if (parent && parent !== this) { + parent.unmanage(actor); + } + } + + this._poolMap.set(actor.actorID, actor); + actor.parentPool = this; + } + + unmanageChildren(FrontType) { + for (const front of this.poolChildren()) { + if (!FrontType || front instanceof FrontType) { + this.unmanage(front); + } + } + } + + /** + * Remove an actor as a child of this pool. + */ + unmanage(actor) { + if (this.__poolMap) { + this.__poolMap.delete(actor.actorID); + } + actor.parentPool = null; + } + + // true if the given actor ID exists in the pool. + has(actorID) { + return this.__poolMap && this._poolMap.has(actorID); + } + + /** + * Search for an actor in this pool, given an actorID + * @param {String} actorID + * @returns {Actor/null} Returns null if the actor wasn't found + */ + getActorByID(actorID) { + if (this.__poolMap) { + return this._poolMap.get(actorID); + } + return null; + } + + // Generator that yields each non-self child of the pool. + *poolChildren() { + if (!this.__poolMap) { + return; + } + for (const actor of this.__poolMap.values()) { + // Self-owned actors are ok, but don't need visiting twice. + if (actor === this) { + continue; + } + yield actor; + } + } + + isDestroyed() { + // Note: _isDestroyed is only flipped from Actor and Front subclasses for + // now, so this method should not be called on pure Pool instances. + // See Bug 1717811. + return this._isDestroyed; + } + + /** + * Pools can override this method in order to opt-out of a destroy sequence. + * + * For instance, Fronts are destroyed during the toolbox destroy. However when + * the toolbox is destroyed, the document holding the toolbox is also + * destroyed. So it should not be necessary to cleanup Fronts during toolbox + * destroy. + * + * For the time being, Fronts (or Pools in general) which want to opt-out of + * toolbox destroy can override this method and check the value of + * `this.conn.isToolboxDestroy`. + */ + skipDestroy() { + return false; + } + + /** + * Destroy this item, removing it from a parent if it has one, + * and destroying all children if necessary. + */ + destroy() { + const parent = this.getParent(); + if (parent) { + parent.unmanage(this); + } + if (!this.__poolMap) { + return; + } + // Immediately clear the poolmap so that we bail out early if the code is reentrant. + const poolMap = this.__poolMap; + this.__poolMap = null; + + for (const actor of poolMap.values()) { + // Self-owned actors are ok, but don't need destroying twice. + if (actor === this) { + continue; + } + + // Some pool-managed values don't extend Pool and won't have skipDestroy + // defined. For instance, the root actor and the lazy actors. + if (typeof actor.skipDestroy === "function" && actor.skipDestroy()) { + continue; + } + + const destroy = actor.destroy; + if (destroy) { + // Disconnect destroy while we're destroying in case of (misbehaving) + // circular ownership. + actor.destroy = null; + destroy.call(actor); + actor.destroy = destroy; + } + } + this.conn.removeActorPool(this); + this.conn = null; + } +} + +exports.Pool = Pool; |