summaryrefslogtreecommitdiffstats
path: root/devtools/shared/commands/commands-factory.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/commands/commands-factory.js')
-rw-r--r--devtools/shared/commands/commands-factory.js245
1 files changed, 245 insertions, 0 deletions
diff --git a/devtools/shared/commands/commands-factory.js b/devtools/shared/commands/commands-factory.js
new file mode 100644
index 0000000000..257f50ce2f
--- /dev/null
+++ b/devtools/shared/commands/commands-factory.js
@@ -0,0 +1,245 @@
+/* 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 {
+ createCommandsDictionary,
+} = require("resource://devtools/shared/commands/index.js");
+const { DevToolsLoader } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+);
+loader.lazyRequireGetter(
+ this,
+ "DevToolsServer",
+ "resource://devtools/server/devtools-server.js",
+ true
+);
+// eslint-disable-next-line mozilla/reject-some-requires
+loader.lazyRequireGetter(
+ this,
+ "DevToolsClient",
+ "resource://devtools/client/devtools-client.js",
+ true
+);
+
+/**
+ * Functions for creating Commands for all debuggable contexts.
+ *
+ * All methods of this `CommandsFactory` object receive argument to describe to
+ * which particular context we want to debug. And all returns a new instance of `commands` object.
+ * Commands are implemented by modules defined in devtools/shared/commands.
+ */
+exports.CommandsFactory = {
+ /**
+ * Create commands for a given local tab.
+ *
+ * @param {Tab} tab: A local Firefox tab, running in this process.
+ * @param {Object} options
+ * @param {DevToolsClient} options.client: An optional DevToolsClient. If none is passed,
+ * a new one will be created.
+ * @param {DevToolsClient} options.isWebExtension: An optional boolean to flag commands
+ * that are created for the WebExtension codebase.
+ * @returns {Object} Commands
+ */
+ async forTab(tab, { client, isWebExtension } = {}) {
+ if (!client) {
+ client = await createLocalClient();
+ }
+
+ const descriptor = await client.mainRoot.getTab({ tab, isWebExtension });
+ descriptor.doNotAttachThreadActor = isWebExtension;
+ const commands = await createCommandsDictionary(descriptor);
+ return commands;
+ },
+
+ /**
+ * Chrome mochitest don't have access to any "tab",
+ * so that the only way to attach to a fake tab is call RootFront.getTab
+ * without any argument.
+ */
+ async forCurrentTabInChromeMochitest() {
+ const client = await createLocalClient();
+ const descriptor = await client.mainRoot.getTab();
+ const commands = await createCommandsDictionary(descriptor);
+ return commands;
+ },
+
+ /**
+ * Create commands for the main process.
+ *
+ * @param {Object} options
+ * @param {DevToolsClient} options.client: An optional DevToolsClient. If none is passed,
+ * a new one will be created.
+ * @returns {Object} Commands
+ */
+ async forMainProcess({ client } = {}) {
+ if (!client) {
+ client = await createLocalClient();
+ }
+
+ const descriptor = await client.mainRoot.getMainProcess();
+ const commands = await createCommandsDictionary(descriptor);
+ return commands;
+ },
+
+ /**
+ * Create commands for a given remote tab.
+ *
+ * Note that it can also be used for local tab, but isLocalTab attribute
+ * on commands.descriptorFront will be false.
+ *
+ * @param {Number} browserId: Identify which tab we should create commands for.
+ * @param {Object} options
+ * @param {DevToolsClient} options.client: An optional DevToolsClient. If none is passed,
+ * a new one will be created.
+ * @returns {Object} Commands
+ */
+ async forRemoteTab(browserId, { client } = {}) {
+ if (!client) {
+ client = await createLocalClient();
+ }
+
+ const descriptor = await client.mainRoot.getTab({ browserId });
+ const commands = await createCommandsDictionary(descriptor);
+ return commands;
+ },
+
+ /**
+ * Create commands for a given main process worker.
+ *
+ * @param {String} id: WorkerDebugger's id, which is a unique ID computed by the platform code.
+ * These ids are exposed via WorkerDescriptor's id attributes.
+ * WorkerDescriptors can be retrieved via MainFront.listAllWorkers()/listWorkers().
+ * @param {Object} options
+ * @param {DevToolsClient} options.client: An optional DevToolsClient. If none is passed,
+ * a new one will be created.
+ * @returns {Object} Commands
+ */
+ async forWorker(id, { client } = {}) {
+ if (!client) {
+ client = await createLocalClient();
+ }
+
+ const descriptor = await client.mainRoot.getWorker(id);
+ const commands = await createCommandsDictionary(descriptor);
+ return commands;
+ },
+
+ /**
+ * Create commands for a Web Extension.
+ *
+ * @param {String} id The Web Extension ID to debug.
+ * @param {Object} options
+ * @param {DevToolsClient} options.client: An optional DevToolsClient. If none is passed,
+ * a new one will be created.
+ * @returns {Object} Commands
+ */
+ async forAddon(id, { client } = {}) {
+ if (!client) {
+ client = await createLocalClient();
+ }
+
+ const descriptor = await client.mainRoot.getAddon({ id });
+ const commands = await createCommandsDictionary(descriptor);
+ return commands;
+ },
+
+ /**
+ * This method will spawn a special `DevToolsClient`
+ * which is meant to debug the same Firefox instance
+ * and especially be able to debug chrome code.
+ * The chrome code typically runs in the system principal.
+ * This principal is a singleton which is shared among most Firefox internal codebase
+ * (JSM, privileged html documents, JS-XPCOM,...)
+ * In order to be able to debug these script we need to connect to a special DevToolsServer
+ * that runs in a dedicated and distinct system principal which is different from
+ * the one shared with the rest of Firefox frontend codebase.
+ */
+ async spawnClientToDebugSystemPrincipal() {
+ // The Browser console ends up using the debugger in autocomplete.
+ // Because the debugger can't be running in the same compartment than its debuggee,
+ // we have to load the server in a dedicated Loader, flagged with
+ // `freshCompartment`, which will force it to be loaded in another compartment.
+ // We aren't using `invisibleToDebugger` in order to allow the Browser toolbox to
+ // debug the Browser console. This is fine as they will spawn distinct Loaders and
+ // so distinct `DevToolsServer` and actor modules.
+ const customLoader = new DevToolsLoader({
+ freshCompartment: true,
+ });
+ const { DevToolsServer: customDevToolsServer } = customLoader.require(
+ "resource://devtools/server/devtools-server.js"
+ );
+
+ customDevToolsServer.init();
+
+ // We want all the actors (root, browser and target-scoped) to be registered on the
+ // DevToolsServer. This is needed so the Browser Console can retrieve:
+ // - the console actors, which are target-scoped (See Bug 1416105)
+ // - the screenshotActor, which is browser-scoped (for the `:screenshot` command)
+ customDevToolsServer.registerAllActors();
+
+ customDevToolsServer.allowChromeProcess = true;
+
+ const client = new DevToolsClient(customDevToolsServer.connectPipe());
+ await client.connect();
+
+ return client;
+ },
+
+ /**
+ * One method to handle the whole setup sequence to connect to RDP backend for the Browser Console.
+ *
+ * This will instantiate a special DevTools module loader for the DevToolsServer.
+ * Then spawn a DevToolsClient to connect to it.
+ * Get a Main Process Descriptor from it.
+ * Finally spawn a commands object for this descriptor.
+ */
+ async forBrowserConsole() {
+ // The Browser console ends up using the debugger in autocomplete.
+ // Because the debugger can't be running in the same compartment than its debuggee,
+ // we have to load the server in a dedicated Loader and so spawn a special client
+ const client = await this.spawnClientToDebugSystemPrincipal();
+
+ const descriptor = await client.mainRoot.getMainProcess();
+
+ descriptor.doNotAttachThreadActor = true;
+
+ // Force fetching the first top level target right away.
+ await descriptor.getTarget();
+
+ const commands = await createCommandsDictionary(descriptor);
+ return commands;
+ },
+};
+
+async function createLocalClient() {
+ // Make sure the DevTools server is started.
+ ensureDevToolsServerInitialized();
+
+ // Create the client and connect it to the local server.
+ const client = new DevToolsClient(DevToolsServer.connectPipe());
+ await client.connect();
+
+ return client;
+}
+// Also expose this method for tests which would like to create a client
+// without involving commands. This would typically be tests against the Watcher actor
+// and requires to prevent having TargetCommand from running.
+// Or tests which are covering RootFront or global actor's fronts.
+exports.createLocalClientForTests = createLocalClient;
+
+function ensureDevToolsServerInitialized() {
+ // Since a remote protocol connection will be made, let's start the
+ // DevToolsServer here, once and for all tools.
+ DevToolsServer.init();
+
+ // Enable all the actors. We may not need all of them and registering
+ // only root and target might be enough
+ DevToolsServer.registerAllActors();
+
+ // Enable being able to get child process actors
+ // Same, this might not be useful
+ DevToolsServer.allowChromeProcess = true;
+}