扩展开发者工具

概述

开发者工具扩展程序为 Chrome 浏览器的开发者工具增加新功能,可以在用户界面中添加新的面板和侧边栏、与审查的网页交互、获取网络请求的有关信息等。开发者工具扩展程序可以访问专门的开发者工具扩展程序 API:

开发者工具扩展程序与其他扩展程序的结构类似:可以拥有后台网页、内容脚本等。此外,每一个开发者工具扩展程序都有一个开发者工具网页,它能够访问开发者工具 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 添加其他扩展程序网页,作为开发者工具窗口中的面板和侧边栏。

devtools_page 字段必须指向 HTML 网页,这一点与 指定后台网页的 background 字段不同,它允许您直接指定 JavaScript 文件。

chrome.devtools.* API 模块仅对扩展程序窗口中加载的网页可用,内容脚本以及其他扩展程序网页不能访问这些 API,因此这些 API 仅在开发者工具窗口的生命周期内可用。

有一些开发者工具 API 还是实验性的,有关实验性 API 以及如何使用它们请参见 chrome.experimental.* API

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

除了通常的扩展程序用户界面元素,如浏览器按钮、右键菜单和弹出菜单,开发者工具扩展程序还可以向开发者工具窗口添加用户界面元素:

  • 面板是顶层标签页,如 Elements(元素)、Sources(源代码)和 Network(网络)面板。
  • 侧边栏窗格显示与面板相关的辅助用户界面,例如 Elements(元素)面板中的 Styles(样式)、Computed Styles(计算后的样式)和 Event Listeners(事件监听器)侧边栏窗格。目前,您的扩展程序只能在 Elements(元素)面板添加侧边栏窗格。(注意,侧边栏窗格的外观可能与图片中不匹配,这取决于您正在使用的 Chrome 浏览器版本以及开发者工具窗口固定的位置。)
显示 Elements(元素)面板及 Styles(样式)侧边栏窗格的开发者工具窗口。

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

chrome.devtools.panels.create("我的面板",
    "MyPanelIcon.png",
    "Panel.html",
    function(panel) {
      // 面板创建时调用的代码
    }
);

面板或侧边栏窗格中执行的 JavaScript 能够访问的 API 与开发者工具网页一样。

为 Elements(元素)面板创建一个基本侧边栏窗格的代码如下所示:

chrome.devtools.panels.elements.createSidebarPane("我的侧边栏",  
    function(sidebar) {
        // 这里是侧边栏的初始化代码
        sidebar.setObject({ some_data: "要显示的某些数据" });
});

您可以用各种方式在侧边栏窗格中显示内容:

  • HTML 内容:调用 setPage 指定窗格中显示的 HTML 网页。

  • JSON 数据:向 setObject 传递一个 JSON 对象。

  • JavaScript 表达式:向 setExpression 传递一个表达式,开发者工具会在审查的网页上下文中对表达式进行求值,然后显示返回值。

对于 setObjectsetExpression 来说,窗格中显示值的方式与开发者工具的控制台类似。setExpression允许您显示 DOM 元素和任意 JavaScript 对象,但是 setObject 只支持 JSON 对象。

在扩展程序的不同部件之间通信

这一部分描述了开发者工具扩展程序不同部件之间通信的典型情况:

插入内容脚本

开发者工具网页不能直接调用 tabs.executeScript。如果要从开发者工具网页中插入内容脚本,您必须先使用 inspectedWindow.tabId 获取审查窗口的标签页标识符,然后向后台网页发送消息。在后台网页中,调用 tabs.executeScript 插入脚本。

如果内容脚本已经插入,您可以使用 eval 方法添加其他内容脚本,有关更多信息请参见将选定元素传递给内容脚本

如下代码片段演示如何使用 executeScript 插入内容脚本。

// 开发者工具网页——devtools.js
// 连接到后台网页
var backgroundPageConnection = chrome.runtime.connect({
    name: "devtools-page"
});

backgroundPageConnection.onMessage.addListener(function (message) {
    // 处理后台网页的响应(如果有的话)。
});

// 将标签页标识符发送至后台网页
chrome.runtime.sendMessage({
    tabId: chrome.devtools.inspectedWindow.tabId,
    scriptToInject: "content_script.js"
});

后台网页的代码:

// 后台网页——background.js
chrome.runtime.onConnect.addListener(function(devToolsConnection) {
    // 将监听器函数赋给某个变量,以便之后移除
    var devToolsListener = function(message, sender, sendResponse) {
        // 向指定标签页插入内容脚本
        chrome.tabs.executeScript(message.tabId,
            { file: message.scriptToInject });
    }
    // 添加监听器
    devToolsConnection.onMessage.addListener(devToolsListener);

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

在审查的窗口中执行 JavaScript

您可以使用 inspectedWindow.eval 方法在审查的网页上下文中执行 JavaScript 代码,您可以在开发者工具网页、面板或侧边栏网页中调用 eval 方法。

默认情况下,表达式在网页的主框架上下文中求值,使用 useContentScriptContext: true 选项在内容脚本的上下文中对表达式求值。

调用 eval 时指定 useContentScriptContext: true 并不会创建内容脚本上下文,所以调用 eval 前您必须先加载一个内容脚本,可以调用 executeScript,也可以在 manifest.json 文件中指定内容脚本。

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

如果在正确的情况下使用,eval 方法是很有用的,但是不恰当的使用也是很危险的。如果您不需要访问审查网页的 JavaScript 上下文,请使用 tabs.executeScript 方法。有关详细的注意事项以及两种方法的比较,请参见 inspectedWindow

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

内容脚本并不能直接访问当前选定元素,但是您使用 inspectedWindow.eval 执行的代码能够使用开发者工具的控制台与命令行 API。例如,在求值的代码中您可以使用 $0 访问选定元素。

如果要将选定元素传递给内容脚本:

  • 在内容脚本中创建一个方法,以选定元素为参数。
  • 使用 inspectedWindow.eval,指定 useContentScriptContext: true 选项,从开发者工具网页中调用该方法。

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

function setSelectedElement(el) {
    // 对选定元素进行某些操作
}

在开发者工具网页中如下所示调用该方法:

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

useContentScriptContext: true 选项指定表达式必须在内容脚本的上下文中求值,这样就能访问 setSelectedElement 方法。

内容脚本与开发者工具网页间的消息传递

开发者工具网页与内容脚本间的消息传递是间接的,通过后台网页进行。

内容脚本发送消息时,后台网页可以使用 tabs.sendMessage 方法,将消息发送至指定标签页中的内容脚本,如插入内容脚本部分所述。

内容脚本发送消息时,没有现有的方法可以将消息投递至与当前标签页对应的开发者工具网页。一种变通的方法是,您可以在开发者工具网页与后台网页间建立长时间的连接,并且让后台网页维护标签页与连接之间的对应关系,这样它就能将每一条消息转发至正确的连接。

var connections = {};

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

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

        // 原始的连接事件不包含开发者工具网页的标签页标识符,
        // 所以我们需要显式发送它。
        if (message.name == "init") {
          connections[message.tabId] = port;
          return;
        }

	// 其他消息的处理
    }

    // 监听开发者工具网页发来的消息
    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;
          }
        }
    });
});

// 从内容脚本接收消息,并转发至当前
// 标签页对应的开发者工具网页
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    // 来自内容脚本的消息应该已经设置 sender.tab
    if (sender.tab) {
      var tabId = sender.tab.id;
      if (tabId in connections) {
        connections[tabId].postMessage(request);
      } else {
        console.log("连接列表中找不到该标签页。");
      }
    } else {
      console.log("sender.tab 未定义。");
    }
    return true;
});

开发者工具网页(或面板或侧边栏窗格)如下所示建立连接:

// 创建连接至后台网页
var backgroundPageConnection = chrome.runtime.connect({
    name: "panel"
});

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

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

如果您的扩展程序需要跟踪开发者工具窗口是否打开,您可以在后台网页中添加 onConnect 监听器,并在开发者工具网页中调用 connect。由于每一个标签页都可以打开各自的开发者工具窗口,您可能会收到多个连接事件。要跟踪是否有打开的开发者工具窗口,您需要统计连接与断开连接事件的次数,如下所示:

var openCount = 0;
chrome.runtime.onConnect.addListener(function (port) {
    if (port.name == "devtools-page") {
      if (openCount == 0) {
        alert("开发者工具窗口打开。");
      }
      openCount++;
   
      port.onDisconnect.addListener(function(port) {
          openCount--;
          if (openCount == 0) {
            alert("最后一个开发者工具窗口关闭。");
          }
      });
    }
});

开发者工具网页如下所示创建连接:

// 创建连接至后台网页
var backgroundPageConnection = chrome.runtime.connect({
    name: "devtools-page"
});

更多信息

有关扩展程序可以使用的标准 API,请参见 chrome.* APIs其他 API

给我们反馈!您的评论与建议可以帮助我们改进这些 API。

例子

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