DevTools扩展API
总览
一个 DevTools 插件能增加功能到 Chrome DevTools 中来.它能够增加新的 UI 面板和侧边栏,能与被检查的页面进行通信,能获得关于网络请求的信息,以及其他的功能。详见显式的 DevTools 插件。DevTools 插件能够访问一组额外特定 DevTools 扩展 API:
- devtools.inspectedWindow
- devtools.network
- devtools.panels
一个 DevTools 插件的结构像其他插件一样 : 它可以有一个后台页面,内容脚本和其他主体。此外,每个 DevTools 插件有一个 DevTools 页面,能访问到它的 DevTools API。
DevTools 页面
一个插件 DevTools 页面的实例每次随着 DevTools 窗口打开而被创建。DevTools 页面在 DevTools 窗口生命周期内存在。DevTools 页面能访问 DevTools API 和有限的一组扩展 API。具体来说,DevTools 页面可以:
- 通过 devtools.panels 创建面板并与面板交互。
- 获取检查窗口的信息、在检查窗口使用 devtools.inspectedWindow API 测试代码。
- 使用 devtools.network API 获取。
DevTools 页面不能直接使用大多数的扩展 API。它可以访问扩展并运行着的 API 子集,因为这些 API 的内容脚本可以被访问到。像一个内容脚本一样,一个 DevTools 页面可以使用 消息传递(Message Passing)与后台页面交互,详见注入内容脚本 Message Passing。
创建一个 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 页面。这与
background
域不同,background
域用来具体一个后台页面,能让你直接指定 JavaScript 文件。
chrome.develop.*
API 模型只能在载入了 DevTools 窗口的页面使用。内容脚本和其他扩展页面并没有这些 API 。因此,这些 API 只有在 DevTools 窗口生命周期内才能使用。
也有一些 DevTools 的 API 仍然是在测试状态。请参阅 chrome.experimental.* API,例举了用于测试的 API 和如何使用它们的指南。
DevTools 界面元素:面板和侧边栏窗格
除了常用扩展 UI 元素,如浏览器的行为,文本菜单和弹出窗口,一个 DevTools 插件可以添加 UI 元素到 DevTools 窗口:
- 面板是一个顶级标签,像元素(Elements),源(Sources)和网络(Network)板。
- 侧边栏窗格 显示补充 UI 相关的面板。固有样式,设定的样式以及元素 (Elements) 面板上的事件监听器窗格的都是侧边栏窗格的实例。目前你的插件只能在元素 (Elements) 面板加侧边栏窗格。 (请注意,侧边栏面板的外观可能与图像不匹配,这取决于你正在使用 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。
如下,为元素面板创建一个基础的侧边窗格,像这样:
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 对象。
插件组件之间的通信
下面的部分描述了 DevTools 插件的不同组件之间通信的一些典型场景。
注入脚本内容
该 DevTools 页不能直接调用tabs.executeScript。为了从 DevTools 页面注入内容脚本,必须使用inspectedWindow.tabId
属性检索的检查窗口选项卡的 ID ,并且发送一个消息到后台页面。在后台页面,调用 tabs.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 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(function() { devToolsConnection.onMessage.removeListener(devToolsListener); }); }
在检查窗口测试 JavaScript 代码
你可以使用 inspectedWindow.eval 方法在检查页面的上下文中执行 JavaScript 代码。然后你可以在DevTools页,面板或侧边栏窗格中调用 eval
方法。
默认情况下,表达式在页面的主框架文档中被计算。现在,你可能熟悉 DevTools 命令行API(commandline API) 功能像元素检查(inspect(elem)
), 函数中断(debug(fn)
),复制内容到剪贴板(copy()
) ,或许更多。
inspectedWindow.eval()
使用相同的脚本执行上下文,在 DevTools 控制台的选项输入代码,它允许使在测试范围内访问这些 API。例如,SOAK 使用它来检测一个元素:
chrome.devtools.inspectedWindow.eval( "inspect($$('head script[data-soak=main]')[0])", function(result, isException) { } );
或者,使用 inspectedWindow.eval()
的 useContentScriptContext
:true 选项,以计算在和内容脚本相同的上下文内容中的表达式。在 useContentScriptContext:true
域调用 eval
不会创建内容脚本的环境,所以你必须在调用 evel
之前,载入内容脚本,或者通过在 manifest.json 文件中指定内容脚本来调用执行脚本(executeScript
)。
一旦上下文文脚本内容环境存在,你可以使用此选项来注入额外的内容脚本。
eval
方法是强大的当它在正确的应用情景中使用的时候,但,如果没有被正确使用,它同样也是危险的。如果你不需要获取检查页的 JavaScript 的内容,使用 tabs.executeScript 方法。有关详细的注意事项和两种方法的比较,请参阅 inspectedWindow。
传递选定元素到内容脚本
内容脚本不能直接访问当前选中的元素。但是,任何使用 inspectedWindow.eval 来执行的代码都可以在 DevTools 控制台和命令行的 API 中使用。例如,在测试代码时,你可以使用 $0
访问当前被选定的元素。
要传递选中的元素到内容脚本,可以如下完成:
- 在内容脚本中,创建一个函数,将选定参数作为这个函数的参数。
- 在 DevTools 页面中使用在
useContentScriptContext:true
的选项中的inspectedWindow.eval
来该函数方法。
在内容脚本中你的函数代码可能是这个样子:
function setSelectedElement(el) { // do something with the selected element }
在 DevTools 页面调用这个方法,像这样:
chrome.devtools.inspectedWindow.eval("setSelectedElement($0)", { useContentScriptContext: true });
useContentScriptContext:true
选项限定的是表达必须在相同的上下文中的内容脚本中进行计算,所以它可以使用setSelectedElement
方法。
获得一个参考板的窗口
从 devtools 面板 postMessage
,你需要它的 window
对象的一个参考。获取面板的 iframe 窗口从该 panel.onShown 事件处理程序;
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
不会如预期一样向后台脚本传递消息。
作为一种变通方法,你可以将内容脚本和注入脚本结合起来。将消息传递给内容脚本,你可以使用 API window.postMessage
。这里有一个例子,假设后台脚本是上一节中的:
// 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 打开和关闭状态
如果你的插件需要跟踪 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 示例源代码:
- Polymer Devtools Extension- 使用在主机页运行的许多助手,来查询并获取 DOM/ JS 状态,发送回自定义面板。
- React DevTools Extension - 使用 Bink 的一个子模块来重用 DevTools UI 组件。
- Ember Inspector - 与 Chrome 和 Firefox 上的适配器,共享插件核心。
- Coquette-inspect - 一个干净的基于 rect 的插件,调试器被注入到 host 页面。
- 我们的 DevTools Extension Gallery和Sample Extensions是值得安装,试用,并从学习更多的应用程序。
更多信息
插件能够使用的更多标准 API 信息,见 chrome.* APIs和 Other APIs.
关注我们的 facebook!你的评价和建议能够帮助我们提高 API 性能。
实例
你可以在 Samples 中找到使用 DevTools API 的使用实例。