1. Chrome DevTools Extension
熟悉React的同學,可能對React Developer Tools並不陌生,

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

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

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

注意,每個tab頁都有自己獨立的DevTools Window,
只是切換tab頁的時候,只會顯示當前tab頁的DevTools Window。
(3)DevTools Page 和 Panel
下面我們來創建一個Chrome DevTools Extension項目,目錄結構如下,
chrome-devtools-extension-example
├── devtools.html // DevTool Page ├── devtools.js // DevTool Page中引用的js ├── manifest.json // 入口 ├── panel.html // 開發者工具欄選項卡頁面 └── 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對用戶是不可見的,如果需要在開發者工具欄中創建新的DevTool選項卡,
還需要在DevTools Page使用一下方法來創建,DevTool選項卡,官方稱為Panel。
原生的Panel包括,Elements,Console,Network,等等。
// 創建一個Panel chrome.devtools.panels.create( // title 'ChromeDevToolsExtensionExample', // iconPath null, // pagePath 'panel.html' );
以上,我們在DevTool Page中創建了一個新的Panel,名字為ChromeDevToolsExtensionExample
。

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

上文我們提到了,Chrome DevTools Extension可以訪問devtools.network API,
現在我們來展示使用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)}); `); // 注冊回調,每一個http請求響應后,都觸發該回調 chrome.devtools.network.onRequestFinished.addListener(async (...args) => { try { const [{ // 請求的類型,查詢參數,以及url request: { method, queryString, url }, // 該方法可用於獲取響應體 getContent, }] = args; log(method, queryString, url); // 將callback轉為await promise // warn: content在getContent回調函數中,而不是getContent的返回值 const content = await new Promise((res, rej) => getContent(res)); log(content); } catch (err) { log(err.stack || err.toString()); } });
以上就是panel.js
的完整內容了,我們還需要做以下幾點說明,

(1)Chrome DevTools Extension中,不能直接使用console.log
,
所以本例中使用了devtools.inspectedWindow API中的chrome.devtools.inspectedWindow.eval
方法,
在當前審查的窗口中直接求值一段js代碼,從而間接實現打印日志的功能。
(2)與獲取http請求的method
,queryString
,url
不同的是,
我們需要調用getContent
方法來獲取http響應體,
並且,getContent
是一個高階的異步函數。
所謂高階函數,指的是,它接受一個回調函數作為參數getContent(content=>{ })
,
這個回調函數的參數content
,才是對應http請求的響應體。
所謂異步,指的是,當回調函數還沒觸發的時候,getContent
就已經返回了。
這也導致了事件監聽函數,也不得不具有異步性。
(3)由於事件監聽函數是異步的,
所以,有可能在上一個onRequestFinished
事件還未處理完的情況下,
下一個onRequestFinished
的監聽函數就又被觸發了。
這就導致了,以上例子中,log(method, queryString, url);
和log(content);
,
可能是亂序打印的。
這個問題我們曾經討論過,可參考:怎樣按觸發順序執行異步任務。
總結
到此為止,我們最簡版的Chrome DevTools Extension示例已經介紹完了,
以下是可以運行的源碼地址:github: chrome-extension-example
(注:不是master
分支,而是simply
分支。
Chrome擴展遵循一種優秀的設計原則,
那就是在設計系統的時候,應該想辦法讓擴展對用戶而言,與原生功能平權。
這種對稱性,會拉近原生與擴展之間的距離,從而讓系統架構從一開始就建立在靈活的基礎之上。
作者:何幻
鏈接:https://www.jianshu.com/p/4ce7f58b8c84
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。