actor-registry.js (13696B)
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 gRegisteredModules = Object.create(null); 8 9 const ActorRegistry = { 10 // Map of global actor names to actor constructors. 11 globalActorFactories: {}, 12 // Map of target-scoped actor names to actor constructors. 13 targetScopedActorFactories: {}, 14 init(connections) { 15 this._connections = connections; 16 }, 17 18 /** 19 * Register a CommonJS module with the devtools server. 20 * 21 * @param id string 22 * The ID of a CommonJS module. 23 * The actor is going to be registered immediately, but loaded only 24 * when a client starts sending packets to an actor with the same id. 25 * 26 * @param options object 27 * An object with 3 mandatory attributes: 28 * - prefix (string): 29 * The prefix of an actor is used to compute: 30 * - the `actorID` of each new actor instance (ex: prefix1). (See Pool.manage) 31 * - the actor name in the listTabs request. Sending a listTabs 32 * request to the root actor returns actor IDs. IDs are in 33 * dictionaries, with actor names as keys and actor IDs as values. 34 * The actor name is the prefix to which the "Actor" string is 35 * appended. So for an actor with the `console` prefix, the actor 36 * name will be `consoleActor`. 37 * - constructor (string): 38 * the name of the exported symbol to be used as the actor 39 * constructor. 40 * - type (a dictionary of booleans with following attribute names): 41 * - "global" 42 * registers a global actor instance, if true. 43 * A global actor has the root actor as its parent. 44 * - "target" 45 * registers a target-scoped actor instance, if true. 46 * A new actor will be created for each target, such as a tab. 47 */ 48 registerModule(id, options) { 49 if (id in gRegisteredModules) { 50 return; 51 } 52 53 if (!options) { 54 throw new Error( 55 "ActorRegistry.registerModule requires an options argument" 56 ); 57 } 58 const { prefix, constructor, type } = options; 59 if (typeof prefix !== "string") { 60 throw new Error( 61 `Lazy actor definition for '${id}' requires a string ` + 62 `'prefix' option.` 63 ); 64 } 65 if (typeof constructor !== "string") { 66 throw new Error( 67 `Lazy actor definition for '${id}' requires a string ` + 68 `'constructor' option.` 69 ); 70 } 71 if (!("global" in type) && !("target" in type)) { 72 throw new Error( 73 `Lazy actor definition for '${id}' requires a dictionary ` + 74 `'type' option whose attributes can be 'global' or 'target'.` 75 ); 76 } 77 const name = prefix + "Actor"; 78 const mod = { 79 id, 80 prefix, 81 constructorName: constructor, 82 type, 83 globalActor: type.global, 84 targetScopedActor: type.target, 85 }; 86 gRegisteredModules[id] = mod; 87 if (mod.targetScopedActor) { 88 this.addTargetScopedActor(mod, name); 89 } 90 if (mod.globalActor) { 91 this.addGlobalActor(mod, name); 92 } 93 }, 94 95 /** 96 * Unregister a previously-loaded CommonJS module from the devtools server. 97 */ 98 unregisterModule(id) { 99 const mod = gRegisteredModules[id]; 100 if (!mod) { 101 throw new Error( 102 "Tried to unregister a module that was not previously registered." 103 ); 104 } 105 106 // Lazy actors 107 if (mod.targetScopedActor) { 108 this.removeTargetScopedActor(mod); 109 } 110 if (mod.globalActor) { 111 this.removeGlobalActor(mod); 112 } 113 114 delete gRegisteredModules[id]; 115 }, 116 117 /** 118 * Install Firefox-specific actors. 119 * 120 * /!\ Be careful when adding a new actor, especially global actors. 121 * Any new global actor will be exposed and returned by the root actor. 122 */ 123 addBrowserActors() { 124 this.registerModule("devtools/server/actors/preference", { 125 prefix: "preference", 126 constructor: "PreferenceActor", 127 type: { global: true }, 128 }); 129 this.registerModule("devtools/server/actors/addon/addons", { 130 prefix: "addons", 131 constructor: "AddonsActor", 132 type: { global: true }, 133 }); 134 this.registerModule("devtools/server/actors/device", { 135 prefix: "device", 136 constructor: "DeviceActor", 137 type: { global: true }, 138 }); 139 this.registerModule("devtools/server/actors/heap-snapshot-file", { 140 prefix: "heapSnapshotFile", 141 constructor: "HeapSnapshotFileActor", 142 type: { global: true }, 143 }); 144 // Always register this as a global module, even while there is a pref turning 145 // on and off the other performance actor. This actor shouldn't conflict with 146 // the other one. These are also lazily loaded so there shouldn't be a performance 147 // impact. 148 this.registerModule("devtools/server/actors/perf", { 149 prefix: "perf", 150 constructor: "PerfActor", 151 type: { global: true }, 152 }); 153 /** 154 * Always register parent accessibility actor as a global module. This 155 * actor is responsible for all top level accessibility actor functionality 156 * that relies on the parent process. 157 */ 158 this.registerModule( 159 "devtools/server/actors/accessibility/parent-accessibility", 160 { 161 prefix: "parentAccessibility", 162 constructor: "ParentAccessibilityActor", 163 type: { global: true }, 164 } 165 ); 166 167 this.registerModule("devtools/server/actors/screenshot", { 168 prefix: "screenshot", 169 constructor: "ScreenshotActor", 170 type: { global: true }, 171 }); 172 }, 173 174 /** 175 * Install target-scoped actors. 176 */ 177 addTargetScopedActors() { 178 this.registerModule("devtools/server/actors/webconsole", { 179 prefix: "console", 180 constructor: "WebConsoleActor", 181 type: { target: true }, 182 }); 183 this.registerModule("devtools/server/actors/inspector/inspector", { 184 prefix: "inspector", 185 constructor: "InspectorActor", 186 type: { target: true }, 187 }); 188 this.registerModule("devtools/server/actors/style-sheets", { 189 prefix: "styleSheets", 190 constructor: "StyleSheetsActor", 191 type: { target: true }, 192 }); 193 this.registerModule("devtools/server/actors/memory", { 194 prefix: "memory", 195 constructor: "MemoryActor", 196 type: { target: true }, 197 }); 198 this.registerModule("devtools/server/actors/reflow", { 199 prefix: "reflow", 200 constructor: "ReflowActor", 201 type: { target: true }, 202 }); 203 this.registerModule("devtools/server/actors/css-properties", { 204 prefix: "cssProperties", 205 constructor: "CssPropertiesActor", 206 type: { target: true }, 207 }); 208 this.registerModule("devtools/server/actors/animation", { 209 prefix: "animations", 210 constructor: "AnimationsActor", 211 type: { target: true }, 212 }); 213 this.registerModule("devtools/server/actors/emulation/responsive", { 214 prefix: "responsive", 215 constructor: "ResponsiveActor", 216 type: { target: true }, 217 }); 218 this.registerModule( 219 "devtools/server/actors/addon/webextension-inspected-window", 220 { 221 prefix: "webExtensionInspectedWindow", 222 constructor: "WebExtensionInspectedWindowActor", 223 type: { target: true }, 224 } 225 ); 226 this.registerModule("devtools/server/actors/accessibility/accessibility", { 227 prefix: "accessibility", 228 constructor: "AccessibilityActor", 229 type: { target: true }, 230 }); 231 this.registerModule("devtools/server/actors/manifest", { 232 prefix: "manifest", 233 constructor: "ManifestActor", 234 type: { target: true }, 235 }); 236 this.registerModule( 237 "devtools/server/actors/network-monitor/network-content", 238 { 239 prefix: "networkContent", 240 constructor: "NetworkContentActor", 241 type: { target: true }, 242 } 243 ); 244 this.registerModule("devtools/server/actors/screenshot-content", { 245 prefix: "screenshotContent", 246 constructor: "ScreenshotContentActor", 247 type: { target: true }, 248 }); 249 this.registerModule("devtools/server/actors/tracer", { 250 prefix: "tracer", 251 constructor: "TracerActor", 252 type: { target: true }, 253 }); 254 this.registerModule("devtools/server/actors/objects-manager", { 255 prefix: "objectsManager", 256 constructor: "ObjectsManagerActor", 257 type: { target: true }, 258 }); 259 }, 260 261 /** 262 * Registers handlers for new target-scoped request types defined dynamically. 263 * 264 * Note that the name of the request type is not allowed to clash with existing protocol 265 * packet properties, like 'title', 'url' or 'actor', since that would break the protocol. 266 * 267 * @param options object 268 * - constructorName: (required) 269 * name of actor constructor, which is also used when removing the actor. 270 * One of the following: 271 * - id: 272 * module ID that contains the actor 273 * - constructorFun: 274 * a function to construct the actor 275 * @param name string 276 * The name of the new request type. 277 */ 278 addTargetScopedActor(options, name) { 279 if (!name) { 280 throw Error("addTargetScopedActor requires the `name` argument"); 281 } 282 if (["title", "url", "actor"].includes(name)) { 283 throw Error(name + " is not allowed"); 284 } 285 if (this.targetScopedActorFactories.hasOwnProperty(name)) { 286 throw Error(name + " already exists"); 287 } 288 this.targetScopedActorFactories[name] = { options, name }; 289 }, 290 291 /** 292 * Unregisters the handler for the specified target-scoped request type. 293 * 294 * When unregistering an existing target-scoped actor, we remove the actor factory as 295 * well as all existing instances of the actor. 296 * 297 * @param actor object, string 298 * In case of object: 299 * The `actor` object being given to related addTargetScopedActor call. 300 * In case of string: 301 * The `name` string being given to related addTargetScopedActor call. 302 */ 303 removeTargetScopedActor(actorOrName) { 304 let name; 305 if (typeof actorOrName == "string") { 306 name = actorOrName; 307 } else { 308 const actor = actorOrName; 309 for (const factoryName in this.targetScopedActorFactories) { 310 const handler = this.targetScopedActorFactories[factoryName]; 311 if ( 312 handler.options.constructorName == actor.name || 313 handler.options.id == actor.id 314 ) { 315 name = factoryName; 316 break; 317 } 318 } 319 } 320 if (!name) { 321 return; 322 } 323 delete this.targetScopedActorFactories[name]; 324 for (const connID of Object.getOwnPropertyNames(this._connections)) { 325 // DevToolsServerConnection in child process don't have rootActor 326 if (this._connections[connID].rootActor) { 327 this._connections[connID].rootActor.removeActorByName(name); 328 } 329 } 330 }, 331 332 /** 333 * Registers handlers for new browser-scoped request types defined dynamically. 334 * 335 * Note that the name of the request type is not allowed to clash with existing protocol 336 * packet properties, like 'from', 'tabs' or 'selected', since that would break the protocol. 337 * 338 * @param options object 339 * - constructorName: (required) 340 * name of actor constructor, which is also used when removing the actor. 341 * One of the following: 342 * - id: 343 * module ID that contains the actor 344 * - constructorFun: 345 * a function to construct the actor 346 * @param name string 347 * The name of the new request type. 348 */ 349 addGlobalActor(options, name) { 350 if (!name) { 351 throw Error("addGlobalActor requires the `name` argument"); 352 } 353 if (["from", "tabs", "selected"].includes(name)) { 354 throw Error(name + " is not allowed"); 355 } 356 if (this.globalActorFactories.hasOwnProperty(name)) { 357 throw Error(name + " already exists"); 358 } 359 this.globalActorFactories[name] = { options, name }; 360 }, 361 362 /** 363 * Unregisters the handler for the specified browser-scoped request type. 364 * 365 * When unregistering an existing global actor, we remove the actor factory as well as 366 * all existing instances of the actor. 367 * 368 * @param actor object, string 369 * In case of object: 370 * The `actor` object being given to related addGlobalActor call. 371 * In case of string: 372 * The `name` string being given to related addGlobalActor call. 373 */ 374 removeGlobalActor(actorOrName) { 375 let name; 376 if (typeof actorOrName == "string") { 377 name = actorOrName; 378 } else { 379 const actor = actorOrName; 380 for (const factoryName in this.globalActorFactories) { 381 const handler = this.globalActorFactories[factoryName]; 382 if ( 383 handler.options.constructorName == actor.name || 384 handler.options.id == actor.id 385 ) { 386 name = factoryName; 387 break; 388 } 389 } 390 } 391 if (!name) { 392 return; 393 } 394 delete this.globalActorFactories[name]; 395 for (const connID of Object.getOwnPropertyNames(this._connections)) { 396 // DevToolsServerConnection in child process don't have rootActor 397 if (this._connections[connID].rootActor) { 398 this._connections[connID].rootActor.removeActorByName(name); 399 } 400 } 401 }, 402 403 destroy() { 404 for (const id of Object.getOwnPropertyNames(gRegisteredModules)) { 405 this.unregisterModule(id); 406 } 407 gRegisteredModules = Object.create(null); 408 409 this.globalActorFactories = {}; 410 this.targetScopedActorFactories = {}; 411 }, 412 }; 413 414 exports.ActorRegistry = ActorRegistry;