testactors.js (7812B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { 7 LazyPool, 8 createExtraActors, 9 } = require("resource://devtools/shared/protocol/lazy-pool.js"); 10 const { RootActor } = require("resource://devtools/server/actors/root.js"); 11 const { 12 WatcherActor, 13 } = require("resource://devtools/server/actors/watcher.js"); 14 const { ThreadActor } = require("resource://devtools/server/actors/thread.js"); 15 const { 16 DevToolsServer, 17 } = require("resource://devtools/server/devtools-server.js"); 18 const { 19 ActorRegistry, 20 } = require("resource://devtools/server/actors/utils/actor-registry.js"); 21 const { 22 SourcesManager, 23 } = require("resource://devtools/server/actors/utils/sources-manager.js"); 24 const makeDebugger = require("resource://devtools/server/actors/utils/make-debugger.js"); 25 const protocol = require("resource://devtools/shared/protocol.js"); 26 const { 27 windowGlobalTargetSpec, 28 } = require("resource://devtools/shared/specs/targets/window-global.js"); 29 const { 30 tabDescriptorSpec, 31 } = require("resource://devtools/shared/specs/descriptors/tab.js"); 32 const Targets = require("resource://devtools/server/actors/targets/index.js"); 33 const { 34 createContentProcessSessionContext, 35 } = require("resource://devtools/server/actors/watcher/session-context.js"); 36 const { TargetActorRegistry } = ChromeUtils.importESModule( 37 "resource://devtools/server/actors/targets/target-actor-registry.sys.mjs", 38 { global: "shared" } 39 ); 40 const { 41 BaseTargetActor, 42 } = require("resource://devtools/server/actors/targets/base-target-actor.js"); 43 const Resources = require("resource://devtools/server/actors/resources/index.js"); 44 45 var gTestGlobals = new Set(); 46 DevToolsServer.addTestGlobal = function (global) { 47 gTestGlobals.add(global); 48 }; 49 DevToolsServer.removeTestGlobal = function (global) { 50 gTestGlobals.delete(global); 51 }; 52 53 DevToolsServer.getTestGlobal = function (name) { 54 for (const g of gTestGlobals) { 55 if (g.title == name) { 56 return g; 57 } 58 } 59 60 return null; 61 }; 62 63 var gAllowNewThreadGlobals = false; 64 DevToolsServer.allowNewThreadGlobals = function () { 65 gAllowNewThreadGlobals = true; 66 }; 67 DevToolsServer.disallowNewThreadGlobals = function () { 68 gAllowNewThreadGlobals = false; 69 }; 70 71 // A mock tab list, for use by tests. This simply presents each global in 72 // gTestGlobals as a tab, and the list is fixed: it never calls its 73 // onListChanged handler. 74 // 75 // As implemented now, we consult gTestGlobals when we're constructed, not 76 // when we're iterated over, so tests have to add their globals before the 77 // root actor is created. 78 class TestTabList { 79 constructor(connection) { 80 this.conn = connection; 81 82 // An array of actors for each global added with 83 // DevToolsServer.addTestGlobal. 84 this._descriptorActors = []; 85 86 // A pool mapping those actors' names to the actors. 87 this._descriptorActorPool = new LazyPool(connection); 88 89 for (const global of gTestGlobals) { 90 const actor = new TestTargetActor(connection, global); 91 this._descriptorActorPool.manage(actor); 92 93 // Register the target actor, so that the Watcher actor can have access to it. 94 TargetActorRegistry.registerXpcShellTargetActor(actor); 95 96 const descriptorActor = new TestDescriptorActor(connection, actor); 97 this._descriptorActorPool.manage(descriptorActor); 98 99 this._descriptorActors.push(descriptorActor); 100 } 101 } 102 destroy() {} 103 getList() { 104 return Promise.resolve([...this._descriptorActors]); 105 } 106 // Helper method only available for the xpcshell implementation of tablist. 107 getTargetActorForTab(title) { 108 const descriptorActor = this._descriptorActors.find(d => d.title === title); 109 if (!descriptorActor) { 110 return null; 111 } 112 return descriptorActor._targetActor; 113 } 114 } 115 116 exports.createRootActor = function createRootActor(connection) { 117 ActorRegistry.registerModule("devtools/server/actors/webconsole", { 118 prefix: "console", 119 constructor: "WebConsoleActor", 120 type: { target: true }, 121 }); 122 const root = new RootActor(connection, { 123 tabList: new TestTabList(connection), 124 globalActorFactories: ActorRegistry.globalActorFactories, 125 }); 126 127 root.applicationType = "xpcshell-tests"; 128 return root; 129 }; 130 131 class TestDescriptorActor extends protocol.Actor { 132 constructor(conn, targetActor) { 133 super(conn, tabDescriptorSpec); 134 this._targetActor = targetActor; 135 } 136 137 // We don't exercise the selected tab in xpcshell tests. 138 get selected() { 139 return false; 140 } 141 142 get title() { 143 return this._targetActor.title; 144 } 145 146 form() { 147 const form = { 148 actor: this.actorID, 149 traits: { 150 watcher: true, 151 }, 152 selected: this.selected, 153 title: this._targetActor.title, 154 url: this._targetActor.url, 155 }; 156 157 return form; 158 } 159 160 getWatcher() { 161 const sessionContext = { 162 type: "all", 163 supportedTargets: {}, 164 supportedResources: [ 165 Resources.TYPES.SOURCE, 166 Resources.TYPES.CONSOLE_MESSAGE, 167 Resources.TYPES.THREAD_STATE, 168 ], 169 }; 170 const watcherActor = new WatcherActor(this.conn, sessionContext); 171 return watcherActor; 172 } 173 174 getFavicon() { 175 return ""; 176 } 177 178 getTarget() { 179 return this._targetActor.form(); 180 } 181 } 182 183 class TestTargetActor extends BaseTargetActor { 184 constructor(conn, global) { 185 super(conn, Targets.TYPES.FRAME, windowGlobalTargetSpec); 186 187 this.sessionContext = createContentProcessSessionContext(); 188 this._global = global; 189 try { 190 this._global.wrappedJSObject = Cu.unwaiveXrays(global); 191 } catch (e) {} 192 this.threadActor = new ThreadActor(this, this._global); 193 this.conn.addActor(this.threadActor); 194 this._extraActors = {}; 195 // This is a hack in order to enable threadActor to be accessed from getFront 196 this._extraActors.threadActor = this.threadActor; 197 this.makeDebugger = makeDebugger.bind(null, { 198 findDebuggees: () => [this._global], 199 shouldAddNewGlobalAsDebuggee: () => gAllowNewThreadGlobals, 200 }); 201 this.dbg = this.makeDebugger(); 202 this.notifyResources = this.notifyResources.bind(this); 203 } 204 205 targetType = Targets.TYPES.FRAME; 206 207 // This is still used by the web console startListeners method 208 get window() { 209 return this._global; 210 } 211 212 get targetGlobal() { 213 return this._global; 214 } 215 216 // Both title and url point to this._global.title 217 get title() { 218 return this._global.document.title; 219 } 220 221 get url() { 222 return this._global.title; 223 } 224 225 get sourcesManager() { 226 if (!this._sourcesManager) { 227 this._sourcesManager = new SourcesManager(this.threadActor); 228 } 229 return this._sourcesManager; 230 } 231 232 form() { 233 const response = { 234 actor: this.actorID, 235 title: this.title, 236 threadActor: this.threadActor.actorID, 237 targetType: this.targetType, 238 }; 239 240 // Walk over target-scoped actors and add them to a new LazyPool. 241 const actorPool = new LazyPool(this.conn); 242 const actors = createExtraActors( 243 ActorRegistry.targetScopedActorFactories, 244 actorPool, 245 this 246 ); 247 if (actorPool?._poolMap.size > 0) { 248 this._descriptorActorPool = actorPool; 249 this.conn.addActorPool(this._descriptorActorPool); 250 } 251 252 return { ...response, ...actors }; 253 } 254 255 detach() { 256 this.threadActor.destroy(); 257 return { type: "detached" }; 258 } 259 260 reload() { 261 this.sourcesManager.reset(); 262 this.threadActor.clearDebuggees(); 263 this.threadActor.dbg.addDebuggees(); 264 return {}; 265 } 266 267 removeActorByName(name) { 268 const actor = this._extraActors[name]; 269 if (this._descriptorActorPool) { 270 this._descriptorActorPool.removeActor(actor); 271 } 272 delete this._extraActors[name]; 273 } 274 275 notifyResources(updateType, resourceType, resources) { 276 this.emit(`resources-${updateType}-array`, [[resourceType, resources]]); 277 } 278 }