root.js (10669B)
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 "use strict"; 5 6 const { rootSpec } = require("resource://devtools/shared/specs/root.js"); 7 const { 8 FrontClassWithSpec, 9 registerFront, 10 } = require("resource://devtools/shared/protocol.js"); 11 12 loader.lazyRequireGetter( 13 this, 14 "getFront", 15 "resource://devtools/shared/protocol.js", 16 true 17 ); 18 19 class RootFront extends FrontClassWithSpec(rootSpec) { 20 constructor(client, targetFront, parentFront) { 21 super(client, targetFront, parentFront); 22 23 // Cache root form as this will always be the same value. 24 Object.defineProperty(this, "rootForm", { 25 get() { 26 delete this.rootForm; 27 this.rootForm = this.getRoot(); 28 return this.rootForm; 29 }, 30 configurable: true, 31 }); 32 33 // Cache of already created global scoped fronts 34 // [typeName:string => Front instance] 35 this.fronts = new Map(); 36 37 this._client = client; 38 } 39 40 form(form) { 41 // Root Front is a special Front. It is the only one to set its actor ID manually 42 // out of the form object returned by RootActor.sayHello which is called when calling 43 // DevToolsClient.connect(). 44 this.actorID = form.from; 45 46 this.applicationType = form.applicationType; 47 this.traits = form.traits; 48 } 49 /** 50 * Retrieve all service worker registrations with their corresponding workers. 51 * 52 * @param {Array} [workerTargets] (optional) 53 * Array containing the result of a call to `listAllWorkerTargets`. 54 * (this exists to avoid duplication of calls to that method) 55 * @return {object[]} result - An Array of Objects with the following format 56 * - {result[].registration} - The registration front 57 * - {result[].workers} Array of form-like objects for service workers 58 */ 59 async listAllServiceWorkers(workerTargets) { 60 const result = []; 61 const { registrations } = await this.listServiceWorkerRegistrations(); 62 const allWorkers = workerTargets 63 ? workerTargets 64 : await this.listAllWorkerTargets(); 65 66 for (const registrationFront of registrations) { 67 // workers from the registration, ordered from most recent to older 68 const workers = [ 69 registrationFront.activeWorker, 70 registrationFront.waitingWorker, 71 registrationFront.installingWorker, 72 registrationFront.evaluatingWorker, 73 ] 74 // filter out non-existing workers 75 .filter(w => !!w) 76 // build a worker object with its WorkerDescriptorFront 77 .map(workerFront => { 78 const workerDescriptorFront = allWorkers.find( 79 targetFront => targetFront.id === workerFront.id 80 ); 81 82 return { 83 id: workerFront.id, 84 name: workerFront.url, 85 state: workerFront.state, 86 stateText: workerFront.stateText, 87 url: workerFront.url, 88 origin: workerFront.origin, 89 workerDescriptorFront, 90 }; 91 }); 92 93 // TODO: return only the worker targets. See Bug 1620605 94 result.push({ 95 registration: registrationFront, 96 workers, 97 }); 98 } 99 100 return result; 101 } 102 103 /** 104 * Retrieve all service worker registrations as well as workers from the parent and 105 * content processes. Listing service workers involves merging information coming from 106 * registrations and workers, this method will combine this information to present a 107 * unified array of serviceWorkers. If you are only interested in other workers, use 108 * listWorkers. 109 * 110 * @return {object} 111 * - {Array} service 112 * array of form-like objects for serviceworkers 113 * - {Array} shared 114 * Array of WorkerTargetActor forms, containing shared workers. 115 * - {Array} other 116 * Array of WorkerTargetActor forms, containing other workers. 117 */ 118 async listAllWorkers() { 119 const allWorkers = await this.listAllWorkerTargets(); 120 const serviceWorkers = await this.listAllServiceWorkers(allWorkers); 121 122 // NOTE: listAllServiceWorkers() now returns all the workers belonging to 123 // a registration. To preserve the usual behavior at about:debugging, 124 // in which we show only the most recent one, we grab the first 125 // worker in the array only. 126 const result = { 127 service: serviceWorkers 128 .map(({ registration, workers }) => { 129 return workers.slice(0, 1).map(worker => { 130 return Object.assign(worker, { 131 registrationFront: registration, 132 fetch: registration.fetch, 133 }); 134 }); 135 }) 136 .flat(), 137 shared: [], 138 other: [], 139 }; 140 141 allWorkers.forEach(front => { 142 const worker = { 143 id: front.id, 144 url: front.url, 145 origin: front.origin, 146 name: front.url, 147 workerDescriptorFront: front, 148 }; 149 150 switch (front.type) { 151 case Ci.nsIWorkerDebugger.TYPE_SERVICE: 152 // do nothing, since we already fetched them in `serviceWorkers` 153 break; 154 case Ci.nsIWorkerDebugger.TYPE_SHARED: 155 result.shared.push(worker); 156 break; 157 default: 158 result.other.push(worker); 159 } 160 }); 161 162 return result; 163 } 164 165 /** Get the target fronts for all worker threads running in any process. */ 166 async listAllWorkerTargets() { 167 const listParentWorkers = async () => { 168 const { workers } = await this.listWorkers(); 169 return workers; 170 }; 171 const listChildWorkers = async () => { 172 const processes = await this.listProcesses(); 173 const processWorkers = await Promise.all( 174 processes.map(async processDescriptorFront => { 175 // Ignore parent process 176 if (processDescriptorFront.isParentProcessDescriptor) { 177 return []; 178 } 179 try { 180 const front = await processDescriptorFront.getTarget(); 181 if (!front) { 182 return []; 183 } 184 const response = await front.listWorkers(); 185 return response.workers; 186 } catch (e) { 187 if (e.message.includes("Connection closed")) { 188 return []; 189 } 190 throw e; 191 } 192 }) 193 ); 194 195 return processWorkers.flat(); 196 }; 197 198 const [parentWorkers, childWorkers] = await Promise.all([ 199 listParentWorkers(), 200 listChildWorkers(), 201 ]); 202 203 return parentWorkers.concat(childWorkers); 204 } 205 206 /** 207 * Fetch the ProcessDescriptorFront for the main process. 208 * 209 * `getProcess` requests allows to fetch the descriptor for any process and 210 * the main process is having the process ID zero. 211 */ 212 getMainProcess() { 213 return this.getProcess(0); 214 } 215 216 /** 217 * Fetch the tab descriptor for the currently selected tab, or for a specific 218 * tab given as first parameter. 219 * 220 * @param [optional] object filter 221 * A dictionary object with following optional attributes: 222 * - browserId: use to match any tab 223 * - tab: a reference to xul:tab element (used for local tab debugging) 224 * - isWebExtension: an optional boolean to flag TabDescriptors 225 * If nothing is specified, returns the actor for the currently 226 * selected tab. 227 */ 228 async getTab(filter) { 229 const packet = {}; 230 if (filter) { 231 if (typeof filter.browserId == "number") { 232 packet.browserId = filter.browserId; 233 } else if ("tab" in filter) { 234 const browser = filter.tab.linkedBrowser; 235 packet.browserId = browser.browserId; 236 } else { 237 // Throw if a filter object have been passed but without 238 // any clearly idenfified filter. 239 throw new Error("Unsupported argument given to getTab request"); 240 } 241 } 242 243 const descriptorFront = await super.getTab(packet); 244 245 // Will flag TabDescriptor used by WebExtension codebase. 246 if (filter?.isWebExtension) { 247 descriptorFront.setIsForWebExtension(true); 248 } 249 250 // If the tab is a local tab, forward it to the descriptor. 251 if (filter?.tab?.tagName == "tab") { 252 // Ignore the fake `tab` object we receive, where there is only a 253 // `linkedBrowser` attribute, but this isn't a real <tab> element. 254 // devtools/client/framework/test/browser_toolbox_target.js is passing such 255 // a fake tab. 256 descriptorFront.setLocalTab(filter.tab); 257 } 258 259 return descriptorFront; 260 } 261 262 /** 263 * Fetch the target front for a given add-on. 264 * This is just an helper on top of `listAddons` request. 265 * 266 * @param object filter 267 * A dictionary object with following attribute: 268 * - id: used to match the add-on to connect to. 269 */ 270 async getAddon({ id }) { 271 const addons = await this.listAddons(); 272 const webextensionDescriptorFront = addons.find(addon => addon.id === id); 273 return webextensionDescriptorFront; 274 } 275 276 /** 277 * Fetch the target front for a given worker. 278 * This is just an helper on top of `listAllWorkers` request. 279 * 280 * @param id 281 */ 282 async getWorker(id) { 283 const { service, shared, other } = await this.listAllWorkers(); 284 const worker = [...service, ...shared, ...other].find(w => w.id === id); 285 if (!worker) { 286 return null; 287 } 288 return worker.workerDescriptorFront || worker.registrationFront; 289 } 290 291 /** 292 * Test request that returns the object passed as first argument. 293 * 294 * `echo` is special as all the property of the given object have to be passed 295 * on the packet object. That's not something that can be achieve by requester helper. 296 */ 297 298 echo(packet) { 299 packet.type = "echo"; 300 return this.request(packet); 301 } 302 303 /** 304 * This function returns a protocol.js Front for any root actor. 305 * i.e. the one directly served from RootActor.listTabs or getRoot. 306 * 307 * @param String typeName 308 * The type name used in protocol.js's spec for this actor. 309 */ 310 async getFront(typeName) { 311 let front = this.fronts.get(typeName); 312 if (front) { 313 return front; 314 } 315 const rootForm = await this.rootForm; 316 front = getFront(this._client, typeName, rootForm); 317 this.fronts.set(typeName, front); 318 return front; 319 } 320 321 /** 322 * This function returns true if the root actor has a registered global actor 323 * with a given name. 324 * 325 * @param {string} actorName 326 * The name of a global actor. 327 * 328 * @return {boolean} 329 */ 330 async hasActor(actorName) { 331 const rootForm = await this.rootForm; 332 return !!rootForm[actorName + "Actor"]; 333 } 334 } 335 registerFront(RootFront);