/* 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); loader.lazyRequireGetter(this, "BrowsingContextTargetFront", "devtools/shared/fronts/targets/browsing-context", true); loader.lazyRequireGetter(this, "ContentProcessTargetFront", "devtools/shared/fronts/targets/content-process", true); class RootFront extends FrontClassWithSpec(rootSpec) { constructor(client, form) { super(client); // 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 // DebuggerClient.connect(). this.actorID = form.from; this.applicationType = form.applicationType; this.traits = form.traits; // 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; } /** * 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() { let registrations = []; let workers = []; try { // List service worker registrations ({ registrations } = await this.listServiceWorkerRegistrations()); // List workers from the Parent process ({ workers } = await this.listWorkers()); // And then from the Child processes const { processes } = await this.listProcesses(); for (const process of processes) { // Ignore parent process if (process.parent) { continue; } const front = await this.getProcess(process.id); const response = await front.listWorkers(); workers = workers.concat(response.workers); } } catch (e) { // Something went wrong, maybe our client is disconnected? } const result = { service: [], shared: [], other: [], }; registrations.forEach(front => { // All the information is simply mirrored from the registration front. // However since registering workers will fetch similar information from the worker // target front and will not have a service worker registration front, consumers // should not read meta data directly on the registration front instance. result.service.push({ active: front.active, fetch: front.fetch, lastUpdateTime: front.lastUpdateTime, name: front.url, registrationFront: front, scope: front.scope, url: front.url, }); }); workers.forEach(front => { const worker = { name: front.url, url: front.url, workerTargetFront: front, }; switch (front.type) { case Ci.nsIWorkerDebugger.TYPE_SERVICE: const registration = result.service.find(r => r.scope === front.scope); if (registration) { // XXX: Race, sometimes a ServiceWorkerRegistrationInfo doesn't // have a scriptSpec, but its associated WorkerDebugger does. if (!registration.url) { registration.name = registration.url = front.url; } registration.workerTargetFront = front; } else { worker.fetch = front.fetch; // If a service worker registration could not be found, this means we are in // e10s, and registrations are not forwarded to other processes until they // reach the activated state. Augment the worker as a registration worker to // display it in aboutdebugging. worker.scope = front.scope; worker.active = false; result.service.push(worker); } break; case Ci.nsIWorkerDebugger.TYPE_SHARED: result.shared.push(worker); break; default: result.other.push(worker); } }); return result; } /** * Fetch the ParentProcessTargetActor for the main process. * * `getProcess` requests allows to fetch the target actor for any process * and the main process is having the process ID zero. */ getMainProcess() { return this.getProcess(0); } async getProcess(id) { // Do not use specification automatic marshalling as getProcess may return // two different type: ParentProcessTargetActor or ContentProcessTargetActor. // Also, we do want to memoize the fronts and return already existing ones. const { form } = await super.getProcess(id); let front = this.actor(form.actor); if (front) { return front; } // getProcess may return a ContentProcessTargetActor or a ParentProcessTargetActor // In most cases getProcess(0) will return the main process target actor, // which is a ParentProcessTargetActor, but not in xpcshell, which uses a // ContentProcessTargetActor. So select the right front based on the actor ID. if (form.actor.includes("contentProcessTarget")) { front = new ContentProcessTargetFront(this._client); } else { // ParentProcessTargetActor doesn't have a specific front, instead it uses // BrowsingContextTargetFront on the client side. front = new BrowsingContextTargetFront(this._client); } // As these fronts aren't instantiated by protocol.js, we have to set their actor ID // manually like that: front.actorID = form.actor; front.form(form); this.manage(front); return front; } /** * Override default listTabs request in order to return a list of * BrowsingContextTargetFronts while updating their selected state. */ async listTabs(options) { const { selected, tabs } = await super.listTabs(options); for (const i in tabs) { tabs[i].setIsSelected(i == selected); } return tabs; } /** * Fetch the target actor 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.tabParent) { // Tabs in child process packet.tabId = browser.frameLoader.tabParent.tabId; } else if (browser.outerWindowID) { // tabs in parent process packet.outerWindowID = browser.outerWindowID; } else { //