Extending DevTools(扩展开发者工具)
警告
此页面直接从 MV2 文档集迁移而来。它尚未经过验证是否符合 Manifest V3。
# Overview
DevTools 扩展为 Chrome DevTools 添加了功能。它可以添加新的 UI 面板和侧边栏,与检查的页面交互,获取有关网络请求的信息等等。查看特色 DevTools 扩展(featured DevTools extensions)。 DevTools 扩展可以访问一组额外的 DevTools 特定的扩展 API:
DevTools 扩展的结构与任何其他扩展一样:它可以有一个背景页面、内容脚本和其他项目。此外,每个 DevTools 扩展都有一个 DevTools 页面,可以访问 DevTools API。
# The DevTools page(开发者工具页面)
每次打开 DevTools 窗口时,都会创建一个扩展的 DevTools 页面实例。 DevTools 页面在 DevTools 窗口的整个生命周期内都存在。DevTools 页面可以访问 DevTools API 和一组有限的扩展 API。具体来说,DevTools 页面可以:
- 使用
devtools.panels
API 创建面板并与之交互。 - 使用
devtools.inspectedWindow
API 获取有关被检查窗口的信息并评估被检查窗口中的代码。 - 使用
devtools.network
API 获取有关网络请求的信息。
DevTools 页面不能直接使用大多数扩展 API。它可以访问与内容脚本可以访问的相同的扩展(extension
)和运行时(runtime
) API 子集。与内容脚本一样,DevTools 页面可以使用消息传递(Message Passing)与后台页面进行通信。有关示例,请参阅注入内容脚本(Injecting a Content Script)。
# Creating a DevTools extension(创建 DevTools 扩展)
要为您的扩展创建 DevTools 页面,请在扩展清单中添加 devtools_page
字段:
{
"name": ...
"version": "1.0",
"minimum_chrome_version": "10.0",
"devtools_page": "devtools.html",
...
}
为每个打开的 DevTools 窗口创建一个在您的扩展清单中指定的 devtools_page
实例。该页面可以使用 devtools.panels
API 将其他扩展页面作为面板和侧边栏添加到 DevTools 窗口。
devtools_page 字段必须指向一个 HTML 页面。这与用于指定背景页面的背景字段不同,后者可让您直接指定 JavaScript 文件。DevTools 页面必须位于您的扩展程序的本地,因此最好使用相对 URL 指定它。
chrome.devtools.*
API 模块仅适用于在 DevTools 窗口中加载的页面。内容脚本和其他扩展页面没有这些 API。因此,这些 API 仅在 DevTools 窗口的生命周期内可用。
还有一些 DevTools API 仍处于试验阶段。请参阅 chrome.experimental.* APIs 以获取实验性 API 的列表以及有关如何使用它们的指南。
# DevTools UI elements: panels and sidebar panes(DevTools UI 元素:面板和侧边栏面板)
除了通常的扩展 UI 元素(例如浏览器操作、上下文菜单和弹出窗口)之外,DevTools 扩展还可以将 UI 元素添加到 DevTools 窗口:
- 面板是顶级选项卡,与元素、来源和网络面板类似。
- 侧边栏窗格显示与面板相关的补充 UI。 Elements 面板上的 Styles、Computed Styles 和 Event Listeners 窗格是侧边栏窗格的示例。(请注意,侧边栏窗格的外观可能与图像不匹配,具体取决于您使用的 Chrome 版本以及 DevTools 窗口的停靠位置。)
每个面板都是自己的 HTML 文件,其中可以包含其他资源(JavaScript、CSS、图像等)。创建一个基本面板如下所示:
chrome.devtools.panels.create("My Panel",
"MyPanelIcon.png",
"Panel.html",
function(panel) {
// code invoked on panel creation
}
);
在面板或侧边栏面板中执行的 JavaScript 可以访问与 DevTools 页面相同的 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
。 DevTools 在被检查页面的上下文中计算表达式,并显示返回值。
对于 setObject
和 setExpression
,该窗格显示的值与 DevTools 控制台中的值相同。但是,setExpression
允许您显示 DOM 元素和任意 JavaScript 对象,而 setObject
仅支持 JSON 对象。
# Communicating between extension components(扩展组件之间的通信)
以下部分描述了 DevTools 扩展的不同组件之间通信的一些典型场景。
# Injecting a content script(注入内容脚本)
DevTools 页面不能直接调用 scripting.executeScript
。要从 DevTools 页面注入内容脚本,您必须使用 inspectedWindow.tabId
属性检索被检查窗口选项卡的 ID,并向后台页面发送消息。从后台页面调用 scripting.executeScript
注入脚本。
如果已注入内容脚本,则可以使用
eval
方法添加其他上下文脚本。有关更多信息,请参阅将选定元素传递给内容脚本Passing the Selected Element to a Content Script。
以下代码片段展示了如何使用 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
backgroundPageConnection.postMessage({
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.scripting.executeScript(message.tabId,
{ file: message.scriptToInject });
}
// add the listener
devToolsConnection.onMessage.addListener(devToolsListener);
devToolsConnection.onDisconnect.addListener(function() {
devToolsConnection.onMessage.removeListener(devToolsListener);
});
});
# Evaluating JavaScript in the inspected window(在检查窗口中评估 JavaScript)
您可以使用inspectedWindow.eval
方法在被检查页面的上下文中执行JavaScript 代码。您可以从 DevTools 页面、面板或侧边栏窗格调用 eval
方法。
默认情况下,表达式是在页面主框架的上下文中计算的。现在,您可能熟悉 DevTools commandline 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
不会创建内容脚本上下文,因此您必须在调用 eval
之前加载上下文脚本,方法是调用 executeScript
或在 manifest.json
文件中指定内容脚本。
一旦存在上下文脚本上下文,您就可以使用此选项来注入其他内容脚本。
eval
方法在正确的上下文中使用时功能强大,而在使用不当时则很危险。如果您不需要访问被检查页面的 JavaScript 上下文,请使用 scripting.executeScript
方法。有关详细注意事项和两种方法的比较,请参阅inspectedWindow
。
# Passing the selected element to a content script(将所选元素传递给内容脚本)
内容脚本不能直接访问当前选定的元素。但是,您使用 inspectedWindow.eval
执行的任何代码都可以访问 DevTools 控制台和命令行 API。例如,在评估代码中,您可以使用 $0
访问所选元素。
要将所选元素传递给内容脚本:
- 在内容脚本中创建一个将所选元素作为参数的方法。
- 使用
inspectedWindow.eval
和useContentScriptContext: true
选项从DevTools 页面调用该方法。
您的内容脚本中的代码可能如下所示:
function setSelectedElement(el) {
// do something with the selected element
}
从 DevTools 页面调用该方法,如下所示:
chrome.devtools.inspectedWindow.eval("setSelectedElement($0)",
{ useContentScriptContext: true });
useContentScriptContext: true
选项指定表达式必须在与内容脚本相同的上下文中计算,以便它可以访问 setSelectedElement
方法。
# Getting a reference panel's window
(获取参考面板的窗口)
要从 devtools 面板 postMessage
,您需要对其 window
对象的引用。从 panel.onShown
事件处理程序中获取面板的 iframe 窗口:
onShown.addListener(function callback)
extensionPanel.onShown.addListener(function (extPanelWindow) {
extPanelWindow instanceof Window; // true
extPanelWindow.postMessage( // …
});
# Messaging from content scripts to the DevTools page(从内容脚本到 DevTools 页面的消息传递)
DevTools 页面和内容脚本之间的消息传递是间接的,通过后台页面的方式。
向内容脚本发送消息时,后台页面可以使用 tabs.sendMessage
方法,该方法将消息定向到特定选项卡中的内容脚本,如注入内容脚本所示Injecting a Content Script。
从内容脚本发送消息时,没有现成的方法可以将消息传送到与当前选项卡关联的正确 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
});
# Messaging from injected scripts to the DevTools page(从注入的脚本向 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 页面。
您还可以在此处考虑两种替代的消息传递技术(two alternative message passing techniques here)。
# Detecting when DevTools opens and closes(检测 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 extension examples(DevTools 扩展示例)
浏览这些 DevTools 扩展示例的来源:
- Polymer Devtools Extension - 使用在宿主页面中运行的许多助手来查询 DOM/JS 状态以发送回自定义面板。
- React DevTools Extension - 使用 Blink 的子模块重用 DevTools UI 组件。
- Ember Inspector - 带有适用于 Chrome 和 Firefox 的适配器的共享扩展核心。
- Coquette-inspect - 一个干净的基于 React 的扩展,带有注入主机页面的调试代理。
- 我们的 DevTools Extension Gallery 和 Sample Extensions 有更多值得安装、试用和学习的扩展。
# More information(更多信息)
有关扩展可以使用的标准 API 的信息,请参阅 chrome.* APIs。
给我们反馈(Give us feedback!)!您的意见和建议有助于我们改进 API。
# Examples
您可以在示例中找到使用 DevTools API 的Samples。
By.一粒技术服务.