summaryrefslogtreecommitdiffstats
path: root/devtools/client/fronts/root.js
blob: 92d780ea2440f6047e8c15869bf8d4cedae618b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
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);