commands-from-url.js (5333B)
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 DevToolsServer, 9 } = require("resource://devtools/server/devtools-server.js"); 10 const { 11 DevToolsClient, 12 } = require("resource://devtools/client/devtools-client.js"); 13 const { 14 remoteClientManager, 15 } = require("resource://devtools/client/shared/remote-debugging/remote-client-manager.js"); 16 const { 17 CommandsFactory, 18 } = require("resource://devtools/shared/commands/commands-factory.js"); 19 20 /** 21 * Construct a commands object for a given URL with various query parameters: 22 * 23 * - host, port & ws: See the documentation for clientFromURL 24 * 25 * - type: "tab", "extension", "worker" or "process" 26 * {String} The type of target to connect to. 27 * 28 * If type == "tab": 29 * - id: 30 * {Number} the tab browserId 31 * 32 * If type == "extension": 33 * - id: 34 * {String} the addonID of the webextension to debug. 35 * 36 * If type == "worker": 37 * - id: 38 * {String} the unique Worker id of the Worker to debug. 39 * 40 * If type == "process": 41 * - id: 42 * {Number} the process id to debug. Default to 0, which is the parent process. 43 * 44 * @param {URL} url 45 * The url to fetch query params from. 46 * 47 * @return A commands object 48 */ 49 exports.commandsFromURL = async function commandsFromURL(url) { 50 const client = await clientFromURL(url); 51 const params = url.searchParams; 52 53 // Clients retrieved from the remote-client-manager are already connected. 54 const isCachedClient = params.get("remoteId"); 55 if (!isCachedClient) { 56 // Connect any other client. 57 await client.connect(); 58 } 59 60 const id = params.get("id"); 61 const type = params.get("type"); 62 63 let commands; 64 try { 65 commands = await _commandsFromURL(client, id, type); 66 } catch (e) { 67 if (!isCachedClient) { 68 // If the client was not cached, then the client was created here. If the target 69 // creation failed, we should close the client. 70 await client.close(); 71 } 72 throw e; 73 } 74 75 // When opening about:debugging's toolboxes for remote runtimes, 76 // we create a new commands using a shared and cached client. 77 // Prevent closing the DevToolsClient on toolbox close and Commands destruction 78 // as this can be used by about:debugging and other toolboxes. 79 if (isCachedClient) { 80 commands.shouldCloseClient = false; 81 } 82 83 return commands; 84 }; 85 86 async function _commandsFromURL(client, id, type) { 87 if (!type) { 88 throw new Error("commandsFromURL, missing type parameter"); 89 } 90 91 let commands; 92 if (type === "tab") { 93 // Fetch target for a remote tab 94 id = parseInt(id, 10); 95 if (isNaN(id)) { 96 throw new Error( 97 `commandsFromURL, wrong tab id '${id}', should be a number` 98 ); 99 } 100 try { 101 commands = await CommandsFactory.forRemoteTab(id, { client }); 102 } catch (ex) { 103 if (ex.message.startsWith("Protocol error (noTab)")) { 104 throw new Error( 105 `commandsFromURL, tab with browserId '${id}' doesn't exist` 106 ); 107 } 108 throw ex; 109 } 110 } else if (type === "extension") { 111 commands = await CommandsFactory.forAddon(id, { client }); 112 113 if (!commands) { 114 throw new Error( 115 `commandsFromURL, extension with id '${id}' doesn't exist` 116 ); 117 } 118 } else if (type === "worker") { 119 commands = await CommandsFactory.forWorker(id, { client }); 120 121 if (!commands) { 122 throw new Error(`commandsFromURL, worker with id '${id}' doesn't exist`); 123 } 124 } else if (type == "process") { 125 // When debugging firefox itself, force the server to accept debugging processes. 126 DevToolsServer.allowChromeProcess = true; 127 commands = await CommandsFactory.forMainProcess({ client }); 128 } else { 129 throw new Error(`commandsFromURL, unsupported type '${type}' parameter`); 130 } 131 132 return commands; 133 } 134 135 /** 136 * Create a DevToolsClient for a given URL object having various query parameters: 137 * 138 * host: 139 * {String} The hostname or IP address to connect to. 140 * port: 141 * {Number} The TCP port to connect to, to use with `host` argument. 142 * remoteId: 143 * {String} Remote client id, for runtimes from the remote-client-manager 144 * ws: 145 * {Boolean} If true, connect via websocket instead of regular TCP connection. 146 * 147 * @param {URL} url 148 * The url to fetch query params from. 149 * @return a promise that resolves a DevToolsClient object 150 */ 151 async function clientFromURL(url) { 152 const params = url.searchParams; 153 154 // If a remote id was provided we should already have a connected client available. 155 const remoteId = params.get("remoteId"); 156 if (remoteId) { 157 const client = remoteClientManager.getClientByRemoteId(remoteId); 158 if (!client) { 159 throw new Error(`Could not find client with remote id: ${remoteId}`); 160 } 161 return client; 162 } 163 164 const host = params.get("host"); 165 const port = params.get("port"); 166 const webSocket = !!params.get("ws"); 167 168 let transport; 169 if (port) { 170 transport = await DevToolsClient.socketConnect({ host, port, webSocket }); 171 } else { 172 // Setup a server if we don't have one already running 173 DevToolsServer.init(); 174 DevToolsServer.registerAllActors(); 175 transport = DevToolsServer.connectPipe(); 176 } 177 return new DevToolsClient(transport); 178 }