扩展开发者工具

概览

开发者工具扩展程序可为 Chrome 开发者工具添加功能。它可以添加新的界面面板和边栏、与被检查的页面交互、获取有关网络请求的信息,等等。查看精选的开发者工具扩展程序。开发者工具扩展程序可以访问另外一组特定于开发者工具的扩展程序 API:

DevTools 扩展程序的结构类似于任何其他扩展程序:它可以具有后台页面、内容脚本和其他项。此外,每个开发者工具扩展程序都有一个开发者工具页面,您可以通过该页面访问开发者工具 API。

显示开发者工具页面与被检查的窗口和后台页面通信的架构图。后台网页显示了与内容脚本进行通信并访问扩展 API 的屏幕。
“开发者工具”页面可以访问开发者工具 API,例如,创建面板。

“开发者工具”页面

每次打开开发者工具窗口时,系统都会创建该扩展程序的“开发者工具”页面的实例。“开发者工具”页面会在开发者工具窗口的整个生命周期内一直存在。“开发者工具”页面可以访问开发者工具 API 和一组有限的扩展程序 API。具体而言,开发者工具页面可以:

“开发者工具”页面无法直接使用大多数扩展程序 API。它有权访问内容脚本可以访问的 extensionruntime API 中的同一部分。与内容脚本一样,开发者工具页面可以使用消息传递功能与后台页面进行通信。如需查看示例,请参阅注入内容脚本

创建开发者工具扩展程序

如需为您的扩展程序创建开发者工具页面,请在扩展程序清单中添加 devtools_page 字段:

{
  "name": ...
  "version": "1.0",
  "minimum_chrome_version": "10.0",
  "devtools_page": "devtools.html",
  ...
}

对于打开的每个开发者工具窗口,系统都会创建扩展程序清单中指定的 devtools_page 实例。该页面可以使用 devtools.panels API 将其他扩展程序页面作为面板和边栏添加到开发者工具窗口中。

chrome.devtools.* API 模块仅适用于在开发者工具窗口中加载的页面。内容脚本和其他扩展网页没有这些 API。因此,这些 API 仅在开发者工具窗口的整个生命周期内可用。

还有一些开发者工具 API 仍处于实验阶段。请参阅 chrome.experimental.* API - 获取实验性 API 列表及其使用方法指南。

开发者工具界面元素:面板和边栏窗格

除了常见的扩展程序界面元素(例如浏览器操作、上下文菜单和弹出式窗口)之外,DevTools 扩展程序还可以向开发者工具窗口添加界面元素:

  • 面板是顶级标签页,与“元素”“来源”和“网络”面板一样。
  • 边栏窗格会呈现与面板相关的补充界面。“元素”面板上的“样式”窗格和“事件监听器”窗格都是边栏窗格的示例。(请注意,边栏窗格的外观可能与图片不一致,具体取决于您使用的 Chrome 版本以及开发者工具窗口的停靠位置)。

显示“元素”面板和“样式”边栏窗格的开发者工具窗口。

每个面板都是其自己的 HTML 文件,其中可以包含其他资源(JavaScript、CSS、图片等)。基本面板的创建过程如下所示:

chrome.devtools.panels.create("My Panel",
    "MyPanelIcon.png",
    "Panel.html",
    function(panel) {
      // code invoked on panel creation
    }
);

在面板或边栏窗格中执行的 JavaScript 可以访问与开发者工具页面相同的 API。

为 Elements 面板创建基本边栏窗格如下所示:

chrome.devtools.panels.elements.createSidebarPane("My Sidebar",
    function(sidebar) {
        // sidebar initialization code here
        sidebar.setObject({ some_data: "Some data to show" });
});

您可以通过以下几种方式在边栏窗格中显示内容:

  • HTML 内容。调用 setPage 以指定要在窗格中显示的 HTML 页面。
  • JSON 数据。将 JSON 对象传递给 setObject
  • JavaScript 表达式。向 setExpression 传递一个表达式。开发者工具会在被检查页面的上下文中评估表达式,并显示返回值。

对于 setObjectsetExpression,此窗格会显示其在开发者工具控制台中显示的值。不过,setExpression 允许您显示 DOM 元素和任意 JavaScript 对象,而 setObject 仅支持 JSON 对象。

在扩展程序组件之间通信

以下部分介绍了在开发者工具扩展程序的不同组件之间进行通信的一些典型场景。

注入内容脚本

“开发者工具”页面无法直接调用 tabs.executeScript。如需从“开发者工具”页面注入内容脚本,您必须使用 inspectedWindow.tabId 属性检索被检查窗口标签页的 ID,并向后台页面发送消息。从后台页面中,调用 tabs.executeScript 以注入脚本。

以下代码段展示了如何使用 executeScript 注入内容脚本。

// DevTools page -- devtools.js
// Create a connection to the background page
var backgroundPageConnection = chrome.runtime.connect({
    name: "devtools-page"
});

backgroundPageConnection.onMessage.addListener(function (message) {
    // Handle responses from the background page, if any
});

// Relay the tab ID to the background page
chrome.runtime.sendMessage({
    tabId: chrome.devtools.inspectedWindow.tabId,
    scriptToInject: "content_script.js"
});

背景页的代码:

// Background page -- background.js
chrome.runtime.onConnect.addListener(function(devToolsConnection) {
    // assign the listener function to a variable so we can remove it later
    var devToolsListener = function(message, sender, sendResponse) {
        // Inject a content script into the identified tab
        chrome.tabs.executeScript(message.tabId,
            { file: message.scriptToInject });
    }
    // add the listener
    devToolsConnection.onMessage.addListener(devToolsListener);

    devToolsConnection.onDisconnect.addListener(function() {
         devToolsConnection.onMessage.removeListener(devToolsListener);
    });
});

在已检查的窗口中评估 JavaScript

您可以使用 inspectedWindow.eval 方法在被检查的页面中执行 JavaScript 代码。您可以从开发者工具页面、面板或边栏窗格调用 eval 方法。

默认情况下,系统会在网页主框架的上下文中对表达式求值。现在,您可能已经熟悉了开发者工具 Commandline API 功能,例如元素检查 (inspect(elem))、函数中断 (debug(fn))、复制到剪贴板 (copy()) 等。 inspectedWindow.eval() 使用与在开发者工具控制台中输入的代码相同的脚本执行上下文和选项,以允许在评估中访问这些 API。例如,SOAK 使用它来检查元素:

chrome.devtools.inspectedWindow.eval(
  "inspect($$('head script[data-soak=main]')[0])",
  function(result, isException) { }
);

或者,使用 inspectedWindow.eval()useContentScriptContext: true 选项,在与内容脚本相同的上下文中评估表达式。使用 useContentScriptContext: true 调用 eval 不会创建内容脚本上下文,因此您必须先加载上下文脚本,然后再调用 eval(通过调用 executeScript 或在 manifest.json 文件中指定内容脚本)。

存在上下文脚本上下文后,您可以使用此选项注入其他内容脚本。

在适当上下文中使用时,eval 方法功能强大,使用不当会带来危险。如果您不需要访问所检查页面的 JavaScript 上下文,请使用 tabs.executeScript 方法。如需了解详细的注意事项以及这两种方法的比较,请参阅 inspectedWindow

将所选元素传递到内容脚本

内容脚本无法直接访问当前所选元素。不过,使用 inspectedWindow.eval 执行的任何代码都可以访问开发者工具控制台和命令行 API。例如,在求值代码中,您可以使用 $0 访问所选元素。

如需将所选元素传递到内容脚本,请执行以下操作:

  • 在内容脚本中创建一个将所选元素作为参数的方法。
  • 使用带有 useContentScriptContext: true 选项的 inspectedWindow.eval 从“开发者工具”页面调用该方法。

内容脚本中的代码可能如下所示:

function setSelectedElement(el) {
    // do something with the selected element
}

从开发者工具页面调用该方法,如下所示:

chrome.devtools.inspectedWindow.eval("setSelectedElement($0)",
    { useContentScriptContext: true });

useContentScriptContext: true 选项指定必须在与内容脚本相同的上下文中计算表达式,以便该表达式可以访问 setSelectedElement 方法。

获取引用面板的 window

如需在开发者工具面板中执行 postMessage,您需要引用其 window 对象。从 panel.onShown 事件处理脚本中获取面板的 iframe 窗口:

onShown.addListener(function callback)
extensionPanel.onShown.addListener(function (extPanelWindow) {
    extPanelWindow instanceof Window; // true
    extPanelWindow.postMessage( // …
});

从内容脚本向“开发者工具”页面发送消息

开发者工具页面和内容脚本之间的消息传递是间接,通过后台页面传递。

向内容脚本发送消息时,后台页面可以使用 tabs.sendMessage 方法,该方法会将消息定向到特定标签页中的内容脚本,如注入内容脚本中所示。

从内容脚本发送消息时,没有现成的方法可以将消息传送到与当前标签页关联的正确开发者工具页面实例。解决方法之一是,您可以让开发者工具页面与后台页面建立长期有效的连接,并让后台页面保留标签页 ID 到连接的映射,以便将每条消息路由到正确的连接。

// background.js
var connections = {};

chrome.runtime.onConnect.addListener(function (port) {

    var extensionListener = function (message, sender, sendResponse) {

        // The original connection event doesn't include the tab ID of the
        // DevTools page, so we need to send it explicitly.
        if (message.name == "init") {
          connections[message.tabId] = port;
          return;
        }

    // other message handling
    }

    // Listen to messages sent from the DevTools page
    port.onMessage.addListener(extensionListener);

    port.onDisconnect.addListener(function(port) {
        port.onMessage.removeListener(extensionListener);

        var tabs = Object.keys(connections);
        for (var i=0, len=tabs.length; i < len; i++) {
          if (connections[tabs[i]] == port) {
            delete connections[tabs[i]]
            break;
          }
        }
    });
});

// Receive message from content script and relay to the devTools page for the
// current tab
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    // Messages from content scripts should have sender.tab set
    if (sender.tab) {
      var tabId = sender.tab.id;
      if (tabId in connections) {
        connections[tabId].postMessage(request);
      } else {
        console.log("Tab not found in connection list.");
      }
    } else {
      console.log("sender.tab not defined.");
    }
    return true;
});

DevTools 页面(或面板或边栏窗格)会建立如下所示的连接:

// Create a connection to the background page
var backgroundPageConnection = chrome.runtime.connect({
    name: "panel"
});

backgroundPageConnection.postMessage({
    name: 'init',
    tabId: chrome.devtools.inspectedWindow.tabId
});

从注入的脚本向“开发者工具”页面传递消息

虽然上述解决方案适用于内容脚本,但直接注入到网页中的代码(例如,通过附加 <script> 标记或通过 inspectedWindow.eval 注入)则需要采用不同的策略。在这种情况下,runtime.sendMessage 不会按预期将消息传递给后台脚本。

如需解决此问题,您可以将注入的脚本与充当中介的内容脚本结合使用。如需将消息传递给内容脚本,您可以使用 window.postMessage API。以下示例假设使用上一部分中的后台脚本:

// injected-script.js

window.postMessage({
  greeting: 'hello there!',
  source: 'my-devtools-extension'
}, '*');
// content-script.js

window.addEventListener('message', function(event) {
  // Only accept messages from the same frame
  if (event.source !== window) {
    return;
  }

  var message = event.data;

  // Only accept messages that we know are ours
  if (typeof message !== 'object' || message === null ||
      !message.source === 'my-devtools-extension') {
    return;
  }

  chrome.runtime.sendMessage(message);
});

您的消息现在将从注入的脚本流向内容脚本,再流向后台脚本,最后流向开发者工具页面。

您还可以考虑此处的两种替代消息传递方法

检测开发者工具何时打开和关闭

如果您的扩展程序需要跟踪开发者工具窗口是否打开,您可以向后台页面添加一个 onConnect 监听器,然后从“开发者工具”页面调用 connect。由于每个标签页都可以打开自己的开发者工具窗口,因此您可能会收到多个连接事件。要跟踪是否打开了任何开发者工具窗口,您需要统计连接和断开连接事件,如下所示:

// background.js
var openCount = 0;
chrome.runtime.onConnect.addListener(function (port) {
    if (port.name == "devtools-page") {
      if (openCount == 0) {
        alert("DevTools window opening.");
      }
      openCount++;

      port.onDisconnect.addListener(function(port) {
          openCount--;
          if (openCount == 0) {
            alert("Last DevTools window closing.");
          }
      });
    }
});

“开发者工具”页面会创建如下连接:

// devtools.js

// Create a connection to the background page
var backgroundPageConnection = chrome.runtime.connect({
    name: "devtools-page"
});

开发者工具扩展程序示例

浏览以下开发者工具扩展程序示例的源代码:

更多信息

如需了解扩展程序可以使用的标准 API,请参阅 chrome.* APIWeb API

向我们提供反馈!您的意见和建议有助于我们改进 API。

示例

您可以在示例中找到使用开发者工具 API 的示例。