252 lines
9 KiB
JavaScript
252 lines
9 KiB
JavaScript
/* 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.
|
|
* @param {Boolean} enableWindowGlobalThreadActors: An optional boolean for on test.
|
|
* @returns {Object} Commands
|
|
*/
|
|
async forMainProcess({
|
|
client,
|
|
enableWindowGlobalThreadActors = false,
|
|
} = {}) {
|
|
if (!client) {
|
|
client = await createLocalClient();
|
|
}
|
|
|
|
const descriptor = await client.mainRoot.getMainProcess();
|
|
const commands = await createCommandsDictionary(
|
|
descriptor,
|
|
enableWindowGlobalThreadActors
|
|
);
|
|
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;
|
|
}
|