summaryrefslogtreecommitdiffstats
path: root/devtools/client/fronts/root.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/fronts/root.js')
-rw-r--r--devtools/client/fronts/root.js332
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);