Source code

Revision control

Copy as Markdown

Other Tools

/* 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,
const { DevToolsLoader } = ChromeUtils.importESModule(
);
loader.lazyRequireGetter(
this,
"DevToolsServer",
true
);
// eslint-disable-next-line mozilla/reject-some-requires
loader.lazyRequireGetter(
this,
"DevToolsClient",
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(
);
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;
}