summaryrefslogtreecommitdiffstats
path: root/devtools/shared/protocol/Pool.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/shared/protocol/Pool.js220
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;