客户端 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();