diff options
Diffstat (limited to 'devtools/client/fronts/root.js')
-rw-r--r-- | devtools/client/fronts/root.js | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/devtools/client/fronts/root.js b/devtools/client/fronts/root.js new file mode 100644 index 0000000000..92d780ea24 --- /dev/null +++ b/devtools/client/fronts/root.js @@ -0,0 +1,332 @@ +/* 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 { rootSpec } = require("resource://devtools/shared/specs/root.js"); +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); + +loader.lazyRequireGetter( + this, + "getFront", + "resource://devtools/shared/protocol.js", + 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.isParentProcessDescriptor) { + return []; + } + try { + const front = await processDescriptorFront.getTarget(); + if (!front) { + return []; + } + const response = await front.listWorkers(); + return response.workers; + } catch (e) { + if (e.message.includes("Connection closed")) { + return []; + } + throw e; + } + }) + ); + + 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: + * - browserId: use to match any tab + * - tab: a reference to xul:tab element (used for local tab debugging) + * - isWebExtension: an optional boolean to flag TabDescriptors + * If nothing is specified, returns the actor for the currently + * selected tab. + */ + async getTab(filter) { + const packet = {}; + if (filter) { + if (typeof filter.browserId == "number") { + packet.browserId = filter.browserId; + } else if ("tab" in filter) { + const browser = filter.tab.linkedBrowser; + packet.browserId = browser.browserId; + } else { + // Throw if a filter object have been passed but without + // any clearly idenfified filter. + throw new Error("Unsupported argument given to getTab request"); + } + } + + const descriptorFront = await super.getTab(packet); + + // Will flag TabDescriptor used by WebExtension codebase. + if (filter?.isWebExtension) { + descriptorFront.setIsForWebExtension(true); + } + + // If the tab is a local tab, forward it to the descriptor. + if (filter?.tab?.tagName == "tab") { + // Ignore the fake `tab` object we receive, where there is only a + // `linkedBrowser` attribute, but this isn't a real <tab> element. + // devtools/client/framework/test/browser_toolbox_target.js is passing such + // a fake tab. + descriptorFront.setLocalTab(filter.tab); + } + + return descriptorFront; + } + + /** + * Fetch the target front for a given add-on. + * This is just an helper on top of `listAddons` request. + * + * @param object filter + * A dictionary object with following attribute: + * - id: used to match the add-on to connect to. + */ + async getAddon({ id }) { + const addons = await this.listAddons(); + const webextensionDescriptorFront = addons.find(addon => addon.id === id); + return webextensionDescriptorFront; + } + + /** + * Fetch the target front for a given worker. + * This is just an helper on top of `listAllWorkers` request. + * + * @param id + */ + async getWorker(id) { + const { service, shared, other } = await this.listAllWorkers(); + const worker = [...service, ...shared, ...other].find(w => w.id === id); + if (!worker) { + return null; + } + return worker.workerDescriptorFront || worker.registrationFront; + } + + /** + * Test request that returns the object passed as first argument. + * + * `echo` is special as all the property of the given object have to be passed + * on the packet object. That's not something that can be achieve by requester helper. + */ + + echo(packet) { + packet.type = "echo"; + return this.request(packet); + } + + /* + * This function returns a protocol.js Front for any root actor. + * i.e. the one directly served from RootActor.listTabs or getRoot. + * + * @param String typeName + * The type name used in protocol.js's spec for this actor. + */ + async getFront(typeName) { + let front = this.fronts.get(typeName); + if (front) { + return front; + } + const rootForm = await this.rootForm; + front = getFront(this._client, typeName, rootForm); + this.fronts.set(typeName, front); + return front; + } + + /* + * This function returns true if the root actor has a registered global actor + * with a given name. + * @param {String} actorName + * The name of a global actor. + * + * @return {Boolean} + */ + async hasActor(actorName) { + const rootForm = await this.rootForm; + return !!rootForm[actorName + "Actor"]; + } +} +exports.RootFront = RootFront; +registerFront(RootFront); |