一個(gè) DevTools 插件能增加功能到 Chrome DevTools 中來(lái).它能夠增加新的 UI 面板和側(cè)邊欄,能與被檢查的頁(yè)面進(jìn)行通信,能獲得關(guān)于網(wǎng)絡(luò)請(qǐng)求的信息,以及其他的功能。詳見(jiàn)顯式的 DevTools 插件。DevTools 插件能夠訪問(wèn)一組額外特定 DevTools 擴(kuò)展 API:
一個(gè) DevTools 插件的結(jié)構(gòu)像其他插件一樣 : 它可以有一個(gè)后臺(tái)頁(yè)面,內(nèi)容腳本和其他主體。此外,每個(gè) DevTools 插件有一個(gè) DevTools 頁(yè)面,能訪問(wèn)到它的 DevTools API。
一個(gè)插件 DevTools 頁(yè)面的實(shí)例每次隨著 DevTools 窗口打開(kāi)而被創(chuàng)建。DevTools 頁(yè)面在 DevTools 窗口生命周期內(nèi)存在。DevTools 頁(yè)面能訪問(wèn) DevTools API 和有限的一組擴(kuò)展 API。具體來(lái)說(shuō),DevTools 頁(yè)面可以:
DevTools 頁(yè)面不能直接使用大多數(shù)的擴(kuò)展 API。它可以訪問(wèn)擴(kuò)展并運(yùn)行著的 API 子集,因?yàn)檫@些 API 的內(nèi)容腳本可以被訪問(wèn)到。像一個(gè)內(nèi)容腳本一樣,一個(gè) DevTools 頁(yè)面可以使用 消息傳遞(Message Passing)與后臺(tái)頁(yè)面交互,詳見(jiàn)注入內(nèi)容腳本 Message Passing。
為你的插件創(chuàng)建一個(gè) DevTools 頁(yè)面,在插件的注冊(cè)清單文件中添加 devtools_page
域:
{
"name": ...
"version": "1.0",
"minimum_chrome_version": "10.0",
"devtools_page": "devtools.html",
...
}
每個(gè) DevTools 窗口被打開(kāi)時(shí),在插件清單中指定的 devtools_page
的實(shí)例都會(huì)被創(chuàng)建。該頁(yè)面可以使用 devtools.panels API 添加其它擴(kuò)展程序的網(wǎng)頁(yè),作為面板和側(cè)邊欄到 DevTools 窗口。
devtools_page 域必須指向一個(gè) HTML 頁(yè)面。這與
background
域不同,background
域用來(lái)具體一個(gè)后臺(tái)頁(yè)面,能讓你直接指定 JavaScript 文件。
chrome.develop.*
API 模型只能在載入了 DevTools 窗口的頁(yè)面使用。內(nèi)容腳本和其他擴(kuò)展頁(yè)面并沒(méi)有這些 API 。因此,這些 API 只有在 DevTools 窗口生命周期內(nèi)才能使用。
也有一些 DevTools 的 API 仍然是在測(cè)試狀態(tài)。請(qǐng)參閱 chrome.experimental.* API,例舉了用于測(cè)試的 API 和如何使用它們的指南。
除了常用擴(kuò)展 UI 元素,如瀏覽器的行為,文本菜單和彈出窗口,一個(gè) DevTools 插件可以添加 UI 元素到 DevTools 窗口:
每個(gè)面板都是其自身的 HTML 文件,可以包括其它資源(JavaScript,CSS,圖片,等等)。像這樣創(chuàng)建一個(gè)基本的面板:
chrome.devtools.panels.create("My Panel",
"MyPanelIcon.png",
"Panel.html",
function(panel) {
// code invoked on panel creation
}
);
在面板上或者在側(cè)邊欄窗格中執(zhí)行的 JavaScript 對(duì)象能訪問(wèn) DevTools 頁(yè)面有權(quán)訪問(wèn)的 API。
如下,為元素面板創(chuàng)建一個(gè)基礎(chǔ)的側(cè)邊窗格,像這樣:
chrome.devtools.panels.elements.createSidebarPane("My Sidebar",
function(sidebar) {
// sidebar initialization code here
sidebar.setObject({ some_data: "Some data to show" });
});
有幾種方法來(lái)顯示一個(gè)側(cè)邊欄窗格中的內(nèi)容:
利用HTML 文檔。調(diào)用 setPage 指定一個(gè) HTML 頁(yè)面在窗格中顯示。
利用 JSON 數(shù)據(jù)。傳遞一個(gè)JSON 對(duì)象給 setObject方法。
對(duì)于這兩種方法 setObject
和setExpression
,當(dāng)他們它輸入進(jìn) DevTools 控制臺(tái)后,窗格會(huì)輸出該值,但是,setExpression
可以顯示 DOM 元素和任意 JavaScript 對(duì)象,而 setObject
只支持 JSON 對(duì)象。
下面的部分描述了 DevTools 插件的不同組件之間通信的一些典型場(chǎng)景。
該 DevTools 頁(yè)不能直接調(diào)用tabs.executeScript。為了從 DevTools 頁(yè)面注入內(nèi)容腳本,必須使用inspectedWindow.tabId
屬性檢索的檢查窗口選項(xiàng)卡的 ID ,并且發(fā)送一個(gè)消息到后臺(tái)頁(yè)面。在后臺(tái)頁(yè)面,調(diào)用 tabs.executeScript 注入腳本。
如果內(nèi)容腳本已經(jīng)被注入,你可以使用 eval
方法來(lái)添加其他內(nèi)容腳本。請(qǐng)參見(jiàn)傳遞選定元素為內(nèi)容腳本(Passing the Selected Element to a Content Script ) 以獲取更多信息。
下面的代碼片段展示了如何使用 executeScript
注入一個(gè)腳本內(nèi)容:
// 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"
});
后臺(tái)頁(yè)面的代碼:
// 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);
});
}
你可以使用 inspectedWindow.eval 方法在檢查頁(yè)面的上下文中執(zhí)行 JavaScript 代碼。然后你可以在DevTools頁(yè),面板或側(cè)邊欄窗格中調(diào)用 eval
方法。
默認(rèn)情況下,表達(dá)式在頁(yè)面的主框架文檔中被計(jì)算?,F(xiàn)在,你可能熟悉 DevTools 命令行API(commandline API) 功能像元素檢查(inspect(elem)
), 函數(shù)中斷(debug(fn)
),復(fù)制內(nèi)容到剪貼板(copy()
) ,或許更多。
inspectedWindow.eval()
使用相同的腳本執(zhí)行上下文,在 DevTools 控制臺(tái)的選項(xiàng)輸入代碼,它允許使在測(cè)試范圍內(nèi)訪問(wèn)這些 API。例如,SOAK 使用它來(lái)檢測(cè)一個(gè)元素:
chrome.devtools.inspectedWindow.eval(
"inspect($$('head script[data-soak=main]')[0])",
function(result, isException) { }
);
或者,使用 inspectedWindow.eval()
的 useContentScriptContext
:true 選項(xiàng),以計(jì)算在和內(nèi)容腳本相同的上下文內(nèi)容中的表達(dá)式。在 useContentScriptContext:true
域調(diào)用 eval
不會(huì)創(chuàng)建內(nèi)容腳本的環(huán)境,所以你必須在調(diào)用 evel
之前,載入內(nèi)容腳本,或者通過(guò)在 manifest.json 文件中指定內(nèi)容腳本來(lái)調(diào)用執(zhí)行腳本(executeScript
)。
一旦上下文文腳本內(nèi)容環(huán)境存在,你可以使用此選項(xiàng)來(lái)注入額外的內(nèi)容腳本。
eval
方法是強(qiáng)大的當(dāng)它在正確的應(yīng)用情景中使用的時(shí)候,但,如果沒(méi)有被正確使用,它同樣也是危險(xiǎn)的。如果你不需要獲取檢查頁(yè)的 JavaScript 的內(nèi)容,使用 tabs.executeScript 方法。有關(guān)詳細(xì)的注意事項(xiàng)和兩種方法的比較,請(qǐng)參閱 inspectedWindow。
內(nèi)容腳本不能直接訪問(wèn)當(dāng)前選中的元素。但是,任何使用 inspectedWindow.eval 來(lái)執(zhí)行的代碼都可以在 DevTools 控制臺(tái)和命令行的 API 中使用。例如,在測(cè)試代碼時(shí),你可以使用 $0
訪問(wèn)當(dāng)前被選定的元素。
要傳遞選中的元素到內(nèi)容腳本,可以如下完成:
useContentScriptContext:true
的選項(xiàng)中的inspectedWindow.eval
來(lái)該函數(shù)方法。在內(nèi)容腳本中你的函數(shù)代碼可能是這個(gè)樣子:
function setSelectedElement(el) {
// do something with the selected element
}
在 DevTools 頁(yè)面調(diào)用這個(gè)方法,像這樣:
chrome.devtools.inspectedWindow.eval("setSelectedElement($0)",
{ useContentScriptContext: true });
useContentScriptContext:true
選項(xiàng)限定的是表達(dá)必須在相同的上下文中的內(nèi)容腳本中進(jìn)行計(jì)算,所以它可以使用setSelectedElement
方法。
從 devtools 面板 postMessage
,你需要它的 window
對(duì)象的一個(gè)參考。獲取面板的 iframe 窗口從該 panel.onShown 事件處理程序;
onShown.addListener(function callback)
extensionPanel.onShown.addListener(function (extPanelWindow) {
extPanelWindow instanceof Window; // true
extPanelWindow.postMessage( // …
});
在 DevTools 頁(yè)面和內(nèi)容腳本之間傳遞消息并不是直接的,而是通過(guò)后臺(tái)頁(yè)面。
當(dāng)將消息發(fā)送到內(nèi)容腳本,后臺(tái)頁(yè)面可以使用 tabs.sendMessage 方法,該方法在指定的選項(xiàng)卡中發(fā)送消息到內(nèi)容腳本,就如同注入一個(gè)內(nèi)容腳本。
當(dāng)從內(nèi)容腳本發(fā)送消息出來(lái),也沒(méi)有現(xiàn)成的方法來(lái)傳遞消息到與當(dāng)前選項(xiàng)卡相關(guān)聯(lián)的確切的 DevTools 頁(yè)面的實(shí)例。作為一種變通方法,你可以讓 DevTools 頁(yè)面與后臺(tái)頁(yè)面建立長(zhǎng)生命周期的連接,并讓后臺(tái)頁(yè)持有 ID 選項(xiàng)卡到連接的映射,這樣它可以路由的每條消息到正確連接處。
// 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 頁(yè)面(面板或側(cè)邊欄窗格)像這樣建立連接:
// Create a connection to the background page
var backgroundPageConnection = chrome.runtime.connect({
name: "panel"
});
backgroundPageConnection.postMessage({
name: 'init',
tabId: chrome.devtools.inspectedWindow.tabId
});
雖然上述的解決方案適用于內(nèi)容腳本,即直接注入頁(yè)面代碼(例如通過(guò)附加一個(gè) script 標(biāo)簽或通過(guò) inspectedWindow.eval
)需要一個(gè)不同的策略。在這方面,runtime.sendMessage
不會(huì)如預(yù)期一樣向后臺(tái)腳本傳遞消息。
作為一種變通方法,你可以將內(nèi)容腳本和注入腳本結(jié)合起來(lái)。將消息傳遞給內(nèi)容腳本,你可以使用 API window.postMessage
。這里有一個(gè)例子,假設(shè)后臺(tái)腳本是上一節(jié)中的:
// 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);
});
你的信息現(xiàn)在將從注入腳本,傳遞到內(nèi)容腳本,再傳遞到后臺(tái)腳本,最后傳到 DevTools 頁(yè)。
你也可以在這里參考兩種備選消息傳遞技術(shù)。
如果你的插件需要跟蹤 DevTools 窗口是否打開(kāi),你可以添加一個(gè) onConnect 的監(jiān)聽(tīng)器到后臺(tái)頁(yè)面中,并在 DevTools 頁(yè)調(diào)用 connect 方法。由于每個(gè)標(biāo)簽可以讓它自己的 DevTools 窗口打開(kāi),你可能會(huì)收到多個(gè)連接的事件。要跟蹤 DevTools 窗口何時(shí)打開(kāi),你需要計(jì)算連接事件和斷開(kāi)事件,如下所示:
// 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 頁(yè)面建立連接,如下:
// devtools.js
// Create a connection to the background page
var backgroundPageConnection = chrome.runtime.connect({
name: "devtools-page"
});
瀏覽這些 DevTools 示例源代碼:
更多建議: