lazy-pool.js (7639B)
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 { Pool } = require("devtools/shared/protocol"); 8 9 /** 10 * A Special Pool for RootActor and WindowGlobalTargetActor, which allows lazy loaded 11 * actors to be added to the pool. 12 * 13 * Like the Pool, this is a protocol object that can manage the lifetime of other protocol 14 * objects. Pools are used on both sides of the connection to help coordinate lifetimes. 15 */ 16 class LazyPool extends Pool { 17 /** 18 * @param conn 19 * Is a DevToolsServerConnection. Must have 20 * addActorPool, removeActorPool, and poolFor. 21 */ 22 constructor(conn) { 23 super(conn); 24 } 25 26 // The actor for a given actor id stored in this pool 27 getActorByID(actorID) { 28 if (this.__poolMap) { 29 const entry = this._poolMap.get(actorID); 30 if (entry instanceof LazyActor) { 31 return entry.createActor(); 32 } 33 return entry; 34 } 35 return null; 36 } 37 } 38 39 exports.LazyPool = LazyPool; 40 41 /** 42 * Populate |parent._extraActors| as specified by |registeredActors|, reusing whatever 43 * actors are already there. Add all actors in the final extra actors table to 44 * |pool|. _extraActors is treated as a cache for lazy actors 45 * 46 * The target actor uses this to instantiate actors that other 47 * parts of the browser have specified with ActorRegistry.addTargetScopedActor 48 * 49 * @param factories 50 * An object whose own property names are the names of properties to add to 51 * some reply packet (say, a target actor grip or the "listTabs" response 52 * form), and whose own property values are actor constructor functions, as 53 * documented for addTargetScopedActor 54 * 55 * @param parent 56 * The parent TargetActor with which the new actors 57 * will be associated. It should support whatever API the |factories| 58 * constructor functions might be interested in, as it is passed to them. 59 * For the sake of CommonCreateExtraActors itself, it should have at least 60 * the following properties: 61 * 62 * - _extraActors 63 * An object whose own property names are factory table (and packet) 64 * property names, and whose values are no-argument actor constructors, 65 * of the sort that one can add to a Pool. 66 * 67 * - conn 68 * The DevToolsServerConnection in which the new actors will participate. 69 * 70 * - actorID 71 * The actor's name, for use as the new actors' parentID. 72 * @param pool 73 * An object which implements the protocol.js Pool interface, and has the 74 * following properties 75 * 76 * - manage 77 * a function which adds a given actor to an actor pool 78 */ 79 function createExtraActors(registeredActors, pool, parent) { 80 // Walk over global actors added by extensions. 81 const nameMap = {}; 82 for (const name in registeredActors) { 83 let actor = parent._extraActors[name]; 84 if (!actor) { 85 // Register another factory, but this time specific to this connection. 86 // It creates a fake actor that looks like an regular actor in the pool, 87 // but without actually instantiating the actor. 88 // It will only be instantiated on the first request made to the actor. 89 actor = new LazyActor(registeredActors[name], parent, pool); 90 parent._extraActors[name] = actor; 91 } 92 93 // If the actor already exists in the pool, it may have been instantiated, 94 // so make sure not to overwrite it by a non-instantiated version. 95 if (!pool.has(actor.actorID)) { 96 pool.manage(actor); 97 } 98 nameMap[name] = actor.actorID; 99 } 100 return nameMap; 101 } 102 103 exports.createExtraActors = createExtraActors; 104 105 /** 106 * Creates an "actor-like" object which responds in the same way as an ordinary actor 107 * but has fewer capabilities (ie, does not manage lifetimes or have it's own pool). 108 */ 109 110 class LazyActor { 111 /** 112 * @param factory 113 * An object whose own property names are the names of properties to add to 114 * some reply packet (say, a target actor grip or the "listTabs" response 115 * form), and whose own property values are actor constructor functions, as 116 * documented for addTargetScopedActor 117 * 118 * @param parent 119 * The parent TargetActor with which the new actors 120 * will be associated. It should support whatever API the |factories| 121 * constructor functions might be interested in, as it is passed to them. 122 * For the sake of CommonCreateExtraActors itself, it should have at least 123 * the following properties: 124 * 125 * - _extraActors 126 * An object whose own property names are factory table (and packet) 127 * property names, and whose values are no-argument actor constructors, 128 * of the sort that one can add to a Pool. 129 * 130 * - conn 131 * The DevToolsServerConnection in which the new actors will participate. 132 * 133 * - actorID 134 * The actor's name, for use as the new actors' parentID. 135 * @param pool 136 * An object which implements the protocol.js Pool interface, and has the 137 * following properties 138 * 139 * - manage 140 * a function which adds a given actor to an actor pool 141 */ 142 constructor(factory, parent, pool) { 143 this._options = factory.options; 144 this._parentActor = parent; 145 this._name = factory.name; 146 this._pool = pool; 147 148 // needed for taking a place in a pool 149 this.typeName = factory.name; 150 } 151 loadModule(id) { 152 const options = this._options; 153 try { 154 return require(id); 155 // Fetch the actor constructor 156 } catch (e) { 157 throw new Error( 158 `Unable to load actor module '${options.id}'\n${e.message}\n${e.stack}\n` 159 ); 160 } 161 } 162 163 getConstructor() { 164 const options = this._options; 165 if (options.constructorFun) { 166 // Actor definition registered by testing helpers 167 return options.constructorFun; 168 } 169 // Lazy actor definition, where options contains all the information 170 // required to load the actor lazily. 171 // Exposes `name` attribute in order to allow removeXXXActor to match 172 // the actor by its actor constructor name. 173 this.name = options.constructorName; 174 const module = this.loadModule(options.id); 175 const constructor = module[options.constructorName]; 176 if (!constructor) { 177 throw new Error( 178 `Unable to find actor constructor named '${this.name}'. (Is it exported?)` 179 ); 180 } 181 return constructor; 182 } 183 184 /** 185 * Return the parent pool for this lazy actor. 186 */ 187 getParent() { 188 return this.conn && this.conn.poolFor(this.actorID); 189 } 190 191 /** 192 * This will only happen if the actor is destroyed before it is created 193 * We do not want to use the Pool destruction method, because this actor 194 * has no pool. However, it might have a parent that should unmange this 195 * actor 196 */ 197 destroy() { 198 const parent = this.getParent(); 199 if (parent) { 200 parent.unmanage(this); 201 } 202 } 203 204 createActor() { 205 // Fetch the actor constructor 206 const Constructor = this.getConstructor(); 207 // Instantiate a new actor instance 208 const conn = this._parentActor.conn; 209 // this should be taken care of once all actors are moved to protocol.js 210 const instance = new Constructor(conn, this._parentActor); 211 instance.conn = conn; 212 213 // We want the newly-constructed actor to completely replace the factory 214 // actor. Reusing the existing actor ID will make sure Pool.manage 215 // replaces the old actor with the new actor. 216 instance.actorID = this.actorID; 217 218 this._pool.manage(instance); 219 220 return instance; 221 } 222 }