commands-factory.js (8999B)
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 { 8 createCommandsDictionary, 9 } = require("resource://devtools/shared/commands/index.js"); 10 const { DevToolsLoader } = ChromeUtils.importESModule( 11 "resource://devtools/shared/loader/Loader.sys.mjs" 12 ); 13 loader.lazyRequireGetter( 14 this, 15 "DevToolsServer", 16 "resource://devtools/server/devtools-server.js", 17 true 18 ); 19 // eslint-disable-next-line mozilla/reject-some-requires 20 loader.lazyRequireGetter( 21 this, 22 "DevToolsClient", 23 "resource://devtools/client/devtools-client.js", 24 true 25 ); 26 27 /** 28 * Functions for creating Commands for all debuggable contexts. 29 * 30 * All methods of this `CommandsFactory` object receive argument to describe to 31 * which particular context we want to debug. And all returns a new instance of `commands` object. 32 * Commands are implemented by modules defined in devtools/shared/commands. 33 */ 34 exports.CommandsFactory = { 35 /** 36 * Create commands for a given local tab. 37 * 38 * @param {Tab} tab: A local Firefox tab, running in this process. 39 * @param {object} options 40 * @param {DevToolsClient} options.client: An optional DevToolsClient. If none is passed, 41 * a new one will be created. 42 * @param {DevToolsClient} options.isWebExtension: An optional boolean to flag commands 43 * that are created for the WebExtension codebase. 44 * @returns {object} Commands 45 */ 46 async forTab(tab, { client, isWebExtension } = {}) { 47 if (!client) { 48 client = await createLocalClient(); 49 } 50 51 const descriptor = await client.mainRoot.getTab({ tab, isWebExtension }); 52 descriptor.doNotAttachThreadActor = isWebExtension; 53 const commands = await createCommandsDictionary(descriptor); 54 return commands; 55 }, 56 57 /** 58 * Chrome mochitest don't have access to any "tab", 59 * so that the only way to attach to a fake tab is call RootFront.getTab 60 * without any argument. 61 */ 62 async forCurrentTabInChromeMochitest() { 63 const client = await createLocalClient(); 64 const descriptor = await client.mainRoot.getTab(); 65 const commands = await createCommandsDictionary(descriptor); 66 return commands; 67 }, 68 69 /** 70 * Create commands for the main process. 71 * 72 * @param {object} options 73 * @param {DevToolsClient} options.client: An optional DevToolsClient. If none is passed, 74 * a new one will be created. 75 * @param {boolean} enableWindowGlobalThreadActors: An optional boolean for on test. 76 * @returns {object} Commands 77 */ 78 async forMainProcess({ 79 client, 80 enableWindowGlobalThreadActors = false, 81 } = {}) { 82 if (!client) { 83 client = await createLocalClient(); 84 } 85 86 const descriptor = await client.mainRoot.getMainProcess(); 87 const commands = await createCommandsDictionary( 88 descriptor, 89 enableWindowGlobalThreadActors 90 ); 91 return commands; 92 }, 93 94 /** 95 * Create commands for a given remote tab. 96 * 97 * Note that it can also be used for local tab, but isLocalTab attribute 98 * on commands.descriptorFront will be false. 99 * 100 * @param {number} browserId: Identify which tab we should create commands for. 101 * @param {object} options 102 * @param {DevToolsClient} options.client: An optional DevToolsClient. If none is passed, 103 * a new one will be created. 104 * @returns {object} Commands 105 */ 106 async forRemoteTab(browserId, { client } = {}) { 107 if (!client) { 108 client = await createLocalClient(); 109 } 110 111 const descriptor = await client.mainRoot.getTab({ browserId }); 112 const commands = await createCommandsDictionary(descriptor); 113 return commands; 114 }, 115 116 /** 117 * Create commands for a given main process worker. 118 * 119 * @param {string} id: WorkerDebugger's id, which is a unique ID computed by the platform code. 120 * These ids are exposed via WorkerDescriptor's id attributes. 121 * WorkerDescriptors can be retrieved via MainFront.listAllWorkers()/listWorkers(). 122 * @param {object} options 123 * @param {DevToolsClient} options.client: An optional DevToolsClient. If none is passed, 124 * a new one will be created. 125 * @returns {object} Commands 126 */ 127 async forWorker(id, { client } = {}) { 128 if (!client) { 129 client = await createLocalClient(); 130 } 131 132 const descriptor = await client.mainRoot.getWorker(id); 133 const commands = await createCommandsDictionary(descriptor); 134 return commands; 135 }, 136 137 /** 138 * Create commands for a Web Extension. 139 * 140 * @param {string} id The Web Extension ID to debug. 141 * @param {object} options 142 * @param {DevToolsClient} options.client: An optional DevToolsClient. If none is passed, 143 * a new one will be created. 144 * @returns {object} Commands 145 */ 146 async forAddon(id, { client } = {}) { 147 if (!client) { 148 client = await createLocalClient(); 149 } 150 151 const descriptor = await client.mainRoot.getAddon({ id }); 152 const commands = await createCommandsDictionary(descriptor); 153 return commands; 154 }, 155 156 /** 157 * This method will spawn a special `DevToolsClient` 158 * which is meant to debug the same Firefox instance 159 * and especially be able to debug chrome code. 160 * The chrome code typically runs in the system principal. 161 * This principal is a singleton which is shared among most Firefox internal codebase 162 * (JSM, privileged html documents, JS-XPCOM,...) 163 * In order to be able to debug these script we need to connect to a special DevToolsServer 164 * that runs in a dedicated and distinct system principal which is different from 165 * the one shared with the rest of Firefox frontend codebase. 166 */ 167 async spawnClientToDebugSystemPrincipal() { 168 // The Browser console ends up using the debugger in autocomplete. 169 // Because the debugger can't be running in the same compartment than its debuggee, 170 // we have to load the server in a dedicated Loader, flagged with 171 // `freshCompartment`, which will force it to be loaded in another compartment. 172 const customLoader = new DevToolsLoader({ 173 freshCompartment: true, 174 }); 175 const { DevToolsServer: customDevToolsServer } = customLoader.require( 176 "resource://devtools/server/devtools-server.js" 177 ); 178 179 customDevToolsServer.init(); 180 181 // We want all the actors (root, browser and target-scoped) to be registered on the 182 // DevToolsServer. This is needed so the Browser Console can retrieve: 183 // - the console actors, which are target-scoped (See Bug 1416105) 184 // - the screenshotActor, which is browser-scoped (for the `:screenshot` command) 185 customDevToolsServer.registerAllActors(); 186 187 customDevToolsServer.allowChromeProcess = true; 188 189 const client = new DevToolsClient(customDevToolsServer.connectPipe()); 190 await client.connect(); 191 192 return client; 193 }, 194 195 /** 196 * One method to handle the whole setup sequence to connect to RDP backend for the Browser Console. 197 * 198 * This will instantiate a special DevTools module loader for the DevToolsServer. 199 * Then spawn a DevToolsClient to connect to it. 200 * Get a Main Process Descriptor from it. 201 * Finally spawn a commands object for this descriptor. 202 */ 203 async forBrowserConsole() { 204 // The Browser console ends up using the debugger in autocomplete. 205 // Because the debugger can't be running in the same compartment than its debuggee, 206 // we have to load the server in a dedicated Loader and so spawn a special client 207 const client = await this.spawnClientToDebugSystemPrincipal(); 208 209 const descriptor = await client.mainRoot.getMainProcess(); 210 211 descriptor.doNotAttachThreadActor = true; 212 213 // Force fetching the first top level target right away. 214 await descriptor.getTarget(); 215 216 const commands = await createCommandsDictionary(descriptor); 217 return commands; 218 }, 219 }; 220 221 async function createLocalClient() { 222 // Make sure the DevTools server is started. 223 ensureDevToolsServerInitialized(); 224 225 // Create the client and connect it to the local server. 226 const client = new DevToolsClient(DevToolsServer.connectPipe()); 227 await client.connect(); 228 229 return client; 230 } 231 // Also expose this method for tests which would like to create a client 232 // without involving commands. This would typically be tests against the Watcher actor 233 // and requires to prevent having TargetCommand from running. 234 // Or tests which are covering RootFront or global actor's fronts. 235 exports.createLocalClientForTests = createLocalClient; 236 237 function ensureDevToolsServerInitialized() { 238 // Since a remote protocol connection will be made, let's start the 239 // DevToolsServer here, once and for all tools. 240 DevToolsServer.init(); 241 242 // Enable all the actors. We may not need all of them and registering 243 // only root and target might be enough 244 DevToolsServer.registerAllActors(); 245 246 // Enable being able to get child process actors 247 // Same, this might not be useful 248 DevToolsServer.allowChromeProcess = true; 249 }