客户端 API¶
DevTools 具有一个客户端模块,允许编写应用程序,这些应用程序使用 远程调试协议 调试或检查网页。
开始通信¶
为了进行通信,必须创建客户端和服务器实例,并建立协议连接。连接可以通过 TCP 套接字或 nsIPipe 进行。下面显示的 start
函数建立了基于 nsIPipe 的连接
const { DevToolsServer } = require("devtools/server/devtools-server");
const { DevToolsClient } = require("devtools/client/devtools-client");
function start() {
// Start the server.
DevToolsServer.init();
DevToolsServer.registerAllActors();
// Listen to an nsIPipe
let transport = DevToolsServer.connectPipe();
// Start the client.
client = new DevToolsClient(transport);
client.connect((type, traits) => {
// Now the client is connected to the server.
debugTab();
});
}
如果需要 TCP 套接字,则应将函数分成两部分,服务器端和客户端,如下所示
const { DevToolsServer } = require("devtools/server/devtools-server");
const { DevToolsClient } = require("devtools/client/devtools-client");
function startServer() {
// Start the server.
DevToolsServer.init();
DevToolsServer.registerAllActors();
// For an nsIServerSocket we do this:
DevToolsServer.openListener(2929); // A connection on port 2929.
}
async function startClient() {
let transport = await DevToolsClient.socketConnect({ host: "localhost", port: 2929 });
// Start the client.
client = new DevToolsClient(transport);
client.connect((type, traits) => {
// Now the client is connected to the server.
debugTab();
});
}
关闭¶
应用程序完成后,它必须通知客户端关闭协议连接。这确保避免内存泄漏,并以有序方式终止服务器。关闭非常简单
function shutdown() {
client.close();
}
附加到浏览器标签页¶
附加到浏览器标签页需要枚举可用的标签页并附加到其中一个
function attachToTab() {
// Get the list of tabs to find the one to attach to.
client.mainRoot.listTabs().then(tabs => {
// Find the active tab.
let targetFront = tabs.find(tab => tab.selected);
// Attach listeners for client events.
targetFront.on("tabNavigated", onTab);
});
}
devtools 客户端将发送有关应用程序可能感兴趣的若干事件的事件通知。这些事件包括调试器中的状态更改,例如暂停和恢复、堆栈帧或源脚本已准备好检索等。
处理位置更改¶
当用户从页面导航离开时,将触发 tabNavigated
事件。处理此事件的正确方法是从以前的线程和标签页分离,并附加到新的线程和标签页
async function onTab() {
// Detach from the previous tab.
await targetFront.detach();
// Start debugging the new tab.
start();
}
调试在浏览器标签页中运行的 JavaScript¶
应用程序附加到标签页后,它可以附加到其线程以与 JavaScript 调试器交互
// Assuming the application is already attached to the tab, and response is the first
// argument of the attachTarget callback.
client.attachThread(response.threadActor).then(function(threadFront) {
if (!threadFront) {
return;
}
// Attach listeners for thread events.
threadFront.on("paused", onPause);
threadFront.on("resumed", fooListener);
// Debugger is now ready and debuggee is running.
});
调试器应用程序示例¶
以下是完整调试器应用程序的源代码
/*
* Debugger API demo.
*/
const { DevToolsServer } = require("devtools/server/devtools-server");
const { DevToolsClient } = require("devtools/client/devtools-client");
let client;
let threadFront;
function startDebugger() {
// Start the server.
DevToolsServer.init();
DevToolsServer.registerAllActors();
// Listen to an nsIPipe
let transport = DevToolsServer.connectPipe();
// For an nsIServerSocket we do this:
// DevToolsServer.openListener(port);
// ...and this at the client:
// let transport = debuggerSocketConnect(host, port);
// Start the client.
client = new DevToolsClient(transport);
client.connect((type, traits) => {
// Now the client is connected to the server.
debugTab();
});
}
function shutdownDebugger() {
client.close();
}
/**
* Start debugging the current tab.
*/
async function debugTab() {
// Get the list of tabs to find the one to attach to.
const tabs = await client.mainRoot.listTabs();
// Find the active tab.
let targetFront = tabs.find(tab => tab.selected);
// Attach to the thread (context).
const threadFront = await targetFront.attachThread();
// Attach listeners for thread events.
threadFront.on("paused", onPause);
threadFront.on("resumed", fooListener);
// Debugger is now ready and debuggee is running.
}
/**
* Handler for location changes.
*/
function onTab() {
// Detach from the previous tab.
client.detach().then(() => {
// Start debugging the new tab.
debugTab();
});
}
/**
* Helper function to inspect the provided frame.
*/
function inspectFrame(frame) {
// Get the "this" object.
if (frame["this"]) {
getObjectProperties(frame["this"]);
}
// Add "arguments".
if (frame.arguments && frame.arguments.length > 0) {
// frame.arguments is a regular Array.
dump("frame.arguments: " + frame.arguments.toSource() + "\n");
// Add variables for every argument.
let objClient = client.activeThread.pauseGrip(frame.callee);
objClient.getSignature(response => {
for (let i = 0; i < response.parameters.length; i++) {
let name = response.parameters[i];
let value = frame.arguments[i];
if (typeof value == "object" && value.type == "object") {
getObjectProperties(value);
}
}
});
}
}
/**
* Helper function that retrieves the specified object's properties.
*/
function getObjectProperties(object) {
let thisClient = client.activeThread.pauseGrip(object);
thisClient.getPrototypeAndProperties(response => {
// Get prototype as a protocol-specified grip.
if (response.prototype.type != "null") {
dump("__proto__: " + response.prototype.toSource() + "\n");
}
// Get the rest of the object's own properties as protocol-specified grips.
for (let prop of Object.keys(response.ownProperties)) {
dump(prop + ": " + response.ownProperties[prop].toSource() + "\n");
}
});
}
/**
* Generic event listener.
*/
function fooListener(event) {
dump(event + "\n");
}
// Run the program.
startDebugger();
// Execute the following line to stop the program.
//shutdownDebugger();