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;