扩展开发者工具

概览

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

与任何其他扩展程序一样,DevTools 扩展程序的结构也大同小异:它可以包含后台页面、内容脚本和其他内容。此外,每个 DevTools 扩展程序都有一个 DevTools 页面,该页面可以访问 DevTools API。

架构图,显示 DevTools 页面与被检查的窗口和后台页面进行通信。显示了与内容脚本通信并访问扩展程序 API 的后台页面。
       “DevTools”页面可以访问 DevTools API,例如创建面板。

“开发者工具”页面

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

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

创建 DevTools 扩展程序

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

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

系统会为打开的每个 DevTools 窗口创建一个扩展程序清单中指定的 devtools_page 实例。该页面可以使用 devtools.panels API 将其他扩展程序页面添加为 DevTools 窗口中的面板和边栏。

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

此外,还有一些 DevTools API 仍处于实验阶段。请参阅 chrome.experimental.* API,查看实验性 API 列表以及有关如何使用这些 API 的指南。

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

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

  • 面板是顶级标签页,例如“元素”“来源”和“网络”面板。
  • 侧边栏窗格用于显示与某个面板相关的补充界面。“元素”面板上的“样式”“计算样式”和“事件监听器”窗格就是边栏窗格的示例。(请注意,边栏窗格的外观可能与图片不符,具体取决于您使用的 Chrome 版本以及 DevTools 窗口的停靠位置。)

显示“Elements”面板和“Styles”边栏窗格的 DevTools 窗口。

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

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

在面板或边栏窗格中执行的 JavaScript 可以访问与 DevTools 页面相同的 API。

为“元素”面板创建基本边栏窗格如下所示:

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,该窗格会显示与 DevTools 控制台中显示的值相同的值。不过,setExpression 可让您显示 DOM 元素和任意 JavaScript 对象,而 setObject 仅支持 JSON 对象。

在扩展程序组件之间通信

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

注入内容脚本

DevTools 页面无法直接调用 tabs.executeScript。如需从 DevTools 页面注入内容脚本,您必须使用 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 代码。您可以从 DevTools 页面、面板或边栏窗格调用 eval 方法。

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

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

或者,您也可以为 inspectedWindow.eval() 使用 useContentScriptContext: true 选项,以便在与内容脚本相同的上下文中评估表达式。使用 useContentScriptContext: true 调用 eval 不会create内容脚本上下文,因此您必须先加载上下文脚本,然后才能调用 eval,具体方法是调用 executeScript 或在 manifest.json 文件中指定内容脚本。

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

eval 方法在正确的上下文中使用时非常强大,如果使用不当,则非常危险。如果您不需要访问被检查网页的 JavaScript 上下文,请使用 tabs.executeScript 方法。如需详细了解注意事项以及这两种方法的比较,请参阅 inspectedWindow

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

内容脚本无法直接访问当前所选元素。不过,您使用 inspectedWindow.eval 执行的任何代码都具有对 DevTools 控制台和命令行 API 的访问权限。例如,在已评估的代码中,您可以使用 $0 访问所选元素。

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

  • 在内容脚本中创建一个方法,将所选元素作为参数。
  • 使用 inspectedWindow.evaluseContentScriptContext: true 选项从 DevTools 页面调用该方法。

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

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

从 DevTools 页面调用该方法,如下所示:

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

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

获取参考面板的 window

如需从 devtools 面板执行 postMessage,您需要引用其 window 对象。通过 panel.onShown 事件处理脚本获取面板的 iframe 窗口:

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

从内容脚本向 DevTools 页面发送消息

DevTools 页面和内容脚本之间的消息传递是通过后台页面进行的间接传递。

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

内容脚本发送消息时,没有现成的用于将消息传递给与当前标签页关联的正确 DevTools 页面实例的方法。作为一种权宜解决方法,您可以让 DevTools 页面与后台页面建立长连接,并让后台页面保留一个标签页 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
});

从注入的脚本向 DevTools 页面发送消息

虽然上述解决方案适用于内容脚本,但直接注入到网页中的代码(例如通过附加 <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);
});

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

您还可以考虑此处介绍的两种替代消息传递技术

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

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

// 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 页面会创建如下所示的连接:

// devtools.js

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

DevTools 扩展程序示例

浏览以下 DevTools 扩展程序示例的源代码:

更多信息

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

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

示例

您可以在示例中找到使用 DevTools API 的示例。