
1. Chrome DevTools Extension
熟悉React的同學(xué),可能對React Developer Tools并不陌生,

剛看到的時候,我也覺得很神奇,
因為React Developer Tools和其他Chrome Extension不同,
它居然出現(xiàn)在了Chrome開發(fā)者工具欄中,和原生的DevTools一樣強大。
例如,可以審查元素,查看元素的屬性,等等。
后來才知道,像這種出現(xiàn)在Chrome開發(fā)者工具欄中的擴展,稱為Chrome DevTools Extension。
比起普通的Chrome Extension,Chrome DevTools Extension可以訪問更多API,例如,
(1)devtools.inspectedWindow
(2)devtools.network
(3)devtools.panels
其中包括了,與當(dāng)前審查窗口相關(guān)的,與網(wǎng)絡(luò)請求相關(guān)的,以及與開發(fā)者工具欄相關(guān)的API。
2. 背景 & 基本概念

在某一具體項目中,有一個這樣的需求,
我們需要選擇頁面中發(fā)起的http請求,然后將它們保存到數(shù)據(jù)庫中。
由于頁面發(fā)起的請求可能會發(fā)往不同的服務(wù)器,所以在服務(wù)器端解決這個問題就顯得比較麻煩,
而編寫一個Chrome DevTools Extension會更簡單直接。
下文我將這個功能的核心抽離出來,作為一個例子,來還原Chrome DevTools Extension的編寫方法。
為此,我們先熟悉幾個基本的概念。
(1)tab頁
Chrome瀏覽器是由tab頁組成的,一個瀏覽器實例中可以打開多個tab頁。

(2)DevTools Window
每個tab頁,都可以打開自己的開發(fā)者工具窗口,稱為DevTools Window。

注意,每個tab頁都有自己獨立的DevTools Window,
只是切換tab頁的時候,只會顯示當(dāng)前tab頁的DevTools Window。
(3)DevTools Page 和 Panel
下面我們來創(chuàng)建一個Chrome DevTools Extension項目,目錄結(jié)構(gòu)如下,
chrome-devtools-extension-example
├── devtools.html // DevTool Page
├── devtools.js // DevTool Page中引用的js
├── manifest.json // 入口
├── panel.html // 開發(fā)者工具欄選項卡頁面
└── panel.js // 選項卡頁面中引用的js
其中manifest.json是入口,它會聲明一個對用戶不可見的DevTools Page。
在本例中為devtools.html,
{
"name": "PageRecorder",
"version": "1.1.0",
"minimum_chrome_version": "10.0",
"description": "Record all http requests in a page.",
"devtools_page": "devtools.html",
"manifest_version": 2
}
DevTools Page引入的js,具有訪問DevTools API的能力,
包括上文提到的那些API,devtools.inspectedWindow,devtools.network,devtools.panels。
DevTools Page對用戶是不可見的,如果需要在開發(fā)者工具欄中創(chuàng)建新的DevTool選項卡,
還需要在DevTools Page使用一下方法來創(chuàng)建,DevTool選項卡,官方稱為Panel。
原生的Panel包括,Elements,Console,Network,等等。
// 創(chuàng)建一個Panel
chrome.devtools.panels.create(
// title
'ChromeDevToolsExtensionExample',
// iconPath
null,
// pagePath
'panel.html'
);
以上,我們在DevTool Page中創(chuàng)建了一個新的Panel,名字為ChromeDevToolsExtensionExample。

其中,panel.html,我們只是簡單的寫了一個Hello World!。
值得注意的是,每個Panel都可以加載自己的html,js和css,且具有和DevTools Page一樣的權(quán)限。
(4)Panel的生命周期
Panel只有在第一次被激活的時候,才進行實例化,
同一個DevTools Window中的不同Panel切換時,不會重新加載。
當(dāng)前tab頁刷新時,Panel也不會重新加載。
DevTools Window關(guān)閉后,Panel將被銷毀。
因此,我們要想使用Chrome DevTools Extension,就必須先打開開發(fā)者工具窗口,
然后再激活我們新建的DevTools Panel。
3. 監(jiān)聽請求

上文我們提到了,Chrome DevTools Extension可以訪問devtools.network API,
現(xiàn)在我們來展示使用chrome.devtools.network.onRequestFinished.addListener來獲取請求。
為此,我們新建了一個panel.js文件,并在panel.html中引用它。
// Chrome DevTools Extension中不能使用console.log
const log = (...args) => chrome.devtools.inspectedWindow.eval(`
console.log(...${JSON.stringify(args)});
`);
// 注冊回調(diào),每一個http請求響應(yīng)后,都觸發(fā)該回調(diào)
chrome.devtools.network.onRequestFinished.addListener(async (...args) => {
try {
const [{
// 請求的類型,查詢參數(shù),以及url
request: { method, queryString, url },
// 該方法可用于獲取響應(yīng)體
getContent,
}] = args;
log(method, queryString, url);
// 將callback轉(zhuǎn)為await promise
// warn: content在getContent回調(diào)函數(shù)中,而不是getContent的返回值
const content = await new Promise((res, rej) => getContent(res));
log(content);
} catch (err) {
log(err.stack || err.toString());
}
});
以上就是panel.js的完整內(nèi)容了,我們還需要做以下幾點說明,

(1)Chrome DevTools Extension中,不能直接使用console.log,
所以本例中使用了devtools.inspectedWindow API中的chrome.devtools.inspectedWindow.eval方法,
在當(dāng)前審查的窗口中直接求值一段js代碼,從而間接實現(xiàn)打印日志的功能。
(2)與獲取http請求的method,queryString,url不同的是,
我們需要調(diào)用getContent方法來獲取http響應(yīng)體,
并且,getContent是一個高階的異步函數(shù)。
所謂高階函數(shù),指的是,它接受一個回調(diào)函數(shù)作為參數(shù)getContent(content=>{ }),
這個回調(diào)函數(shù)的參數(shù)content,才是對應(yīng)http請求的響應(yīng)體。
所謂異步,指的是,當(dāng)回調(diào)函數(shù)還沒觸發(fā)的時候,getContent就已經(jīng)返回了。
這也導(dǎo)致了事件監(jiān)聽函數(shù),也不得不具有異步性。
(3)由于事件監(jiān)聽函數(shù)是異步的,
所以,有可能在上一個onRequestFinished事件還未處理完的情況下,
下一個onRequestFinished的監(jiān)聽函數(shù)就又被觸發(fā)了。
這就導(dǎo)致了,以上例子中,log(method, queryString, url);和log(content);,
可能是亂序打印的。
這個問題我們曾經(jīng)討論過,可參考:怎樣按觸發(fā)順序執(zhí)行異步任務(wù)。
總結(jié)
到此為止,我們最簡版的Chrome DevTools Extension示例已經(jīng)介紹完了,
以下是可以運行的源碼地址:github: chrome-extension-example
(注:不是master分支,而是simply分支。
Chrome擴展遵循一種優(yōu)秀的設(shè)計原則,
那就是在設(shè)計系統(tǒng)的時候,應(yīng)該想辦法讓擴展對用戶而言,與原生功能平權(quán)。
這種對稱性,會拉近原生與擴展之間的距離,從而讓系統(tǒng)架構(gòu)從一開始就建立在靈活的基礎(chǔ)之上。
參考
Chrome Extension
Extending DevTools
devtools.network#onRequestFinished
github: chrome-extension-example
簡書:怎樣按觸發(fā)順序執(zhí)行異步任務(wù)