/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { Ci } = require("chrome"); const { rootSpec } = require("devtools/shared/specs/root"); const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); loader.lazyRequireGetter(this, "getFront", "devtools/shared/protocol", true); class RootFront extends FrontClassWithSpec(rootSpec) { constructor(client, targetFront, parentFront) { super(client, targetFront, parentFront); // Cache root form as this will always be the same value. Object.defineProperty(this, "rootForm", { get() { delete this.rootForm; this.rootForm = this.getRoot(); return this.rootForm; }, configurable: true, }); // Cache of already created global scoped fronts // [typeName:string => Front instance] this.fronts = new Map(); this._client = client; } form(form) { // Root Front is a special Front. It is the only one to set its actor ID manually // out of the form object returned by RootActor.sayHello which is called when calling // DevToolsClient.connect(). this.actorID = form.from; this.applicationType = form.applicationType; this.traits = form.traits; } /** * Retrieve all service worker registrations with their corresponding workers. * @param {Array} [workerTargets] (optional) * Array containing the result of a call to `listAllWorkerTargets`. * (this exists to avoid duplication of calls to that method) * @return {Object[]} result - An Array of Objects with the following format * - {result[].registration} - The registration front * - {result[].workers} Array of form-like objects for service workers */ async listAllServiceWorkers(workerTargets) { const result = []; const { registrations } = await this.listServiceWorkerRegistrations(); const allWorkers = workerTargets ? workerTargets : await this.listAllWorkerTargets(); for (const registrationFront of registrations) { // workers from the registration, ordered from most recent to older const workers = [ registrationFront.activeWorker, registrationFront.waitingWorker, registrationFront.installingWorker, registrationFront.evaluatingWorker, ] // filter out non-existing workers .filter(w => !!w) // build a worker object with its WorkerDescriptorFront .map(workerFront => { const workerDescriptorFront = allWorkers.find( targetFront => targetFront.id === workerFront.id ); return { id: workerFront.id, name: workerFront.url, state: workerFront.state, stateText: workerFront.stateText, url: workerFront.url, workerDescriptorFront, }; }); // TODO: return only the worker targets. See Bug 1620605 result.push({ registration: registrationFront, workers, }); } return result; } /** * Retrieve all service worker registrations as well as workers from the parent and * content processes. Listing service workers involves merging information coming from * registrations and workers, this method will combine this information to present a * unified array of serviceWorkers. If you are only interested in other workers, use * listWorkers. * * @return {Object} * - {Array} service * array of form-like objects for serviceworkers * - {Array} shared * Array of WorkerTargetActor forms, containing shared workers. * - {Array} other * Array of WorkerTargetActor forms, containing other workers. */ async listAllWorkers() { const allWorkers = await this.listAllWorkerTargets(); const serviceWorkers = await this.listAllServiceWorkers(allWorkers); // NOTE: listAllServiceWorkers() now returns all the workers belonging to // a registration. To preserve the usual behavior at about:debugging, // in which we show only the most recent one, we grab the first // worker in the array only. const result = { service: serviceWorkers .map(({ registration, workers }) => { return workers.slice(0, 1).map(worker => { return Object.assign(worker, { registrationFront: registration, fetch: registration.fetch, }); }); }) .flat(), shared: [], other: [], }; allWorkers.forEach(front => { const worker = { id: front.id, url: front.url, name: front.url, workerDescriptorFront: front, }; switch (front.type) { case Ci.nsIWorkerDebugger.TYPE_SERVICE: // do nothing, since we already fetched them in `serviceWorkers` break; case Ci.nsIWorkerDebugger.TYPE_SHARED: result.shared.push(worker); break; default: result.other.push(worker); } }); return result; } /** Get the target fronts for all worker threads running in any process. */ async listAllWorkerTargets() { const listParentWorkers = async () => { const { workers } = await this.listWorkers(); return workers; }; const listChildWorkers = async () => { const processes = await this.listProcesses(); const processWorkers = await Promise.all( processes.map(async processDescriptorFront => { // Ignore parent process if (processDescriptorFront.isParent) { return []; } const front = await processDescriptorFront.getTarget(); if (!front) { return []; } const response = await front.listWorkers(); return response.workers; }) ); return processWorkers.flat(); }; const [parentWorkers, childWorkers] = await Promise.all([ listParentWorkers(), listChildWorkers(), ]); return parentWorkers.concat(childWorkers); } /** * Fetch the ProcessDescriptorFront for the main process. * * `getProcess` requests allows to fetch the descriptor for any process and * the main process is having the process ID zero. */ getMainProcess() { return this.getProcess(0); } /** * Fetch the tab descriptor for the currently selected tab, or for a specific * tab given as first parameter. * * @param [optional] object filter * A dictionary object with following optional attributes: * - outerWindowID: used to match tabs in parent process * - tabId: used to match tabs in child processes * - tab: a reference to xul:tab element * If nothing is specified, returns the actor for the currently * selected tab. */ async getTab(filter) { const packet = {}; if (filter) { if (typeof filter.outerWindowID == "number") { packet.outerWindowID = filter.outerWindowID; } else if (typeof filter.tabId == "number") { packet.tabId = filter.tabId; } else if ("tab" in filter) { const browser = filter.tab.linkedBrowser; if (browser.frameLoader.remoteTab) { // Tabs in child process packet.tabId = browser.frameLoader.remoteTab.tabId; } else { // or