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