DevTools 實現(xiàn)原理與性能分析實戰(zhàn)

一、引言

從 2008 年 Google 釋放出第一版的 Chrome 后硬爆,整個 Web 開發(fā)領(lǐng)域仿佛被注入了一股新鮮血液欣舵,漸漸打破了 IE 一家獨大的時代。Chrome 和 Firefox 是 W3C Web 標(biāo)準(zhǔn)的堅定支持者缀磕,隨著這兩款開源瀏覽器市場份額逐漸加大缘圈,迎來了開發(fā)者的春天。這就迎來了一個新的職業(yè)分工——前端工程師 frontend-engineer袜蚕,前端工程師促進了 Web 應(yīng)用的繁榮糟把,功能強大的調(diào)試工具必不可少。Google 基于開源的基礎(chǔ)上順勢推出了 DevTools牲剃,廣受網(wǎng)頁開發(fā)者的好評遣疯,隨即也推動了 Chrome 的在商業(yè)的成功缠犀。

本文通過分析 Chrome 的 DevTools 的技術(shù)實現(xiàn)虐急,特別是在瀏覽器內(nèi)核中的實現(xiàn)部分,來展示這款被萬千開發(fā)者所喜愛的開發(fā)工具背后的秘密敬惦。本文適合閱讀對象主要有前端開發(fā)者、有志于開發(fā) Hybrid 應(yīng)用調(diào)試工具或重寫 webdriver 實現(xiàn)對 Chrome 或 WebView 控制的應(yīng)用工程師举哟。

注:本文所有代碼分析潜叛,基于 Android Chromium 87.0.4280.141 版本分析而成。由于筆者所在團隊主要從事 Android 平臺的 Blink 內(nèi)核開發(fā)椒舵,所以分析過程主要集中在移動端棱诱,其他平臺只是數(shù)據(jù)通路的區(qū)別炬灭,實現(xiàn)原理差別不大米愿。

二狈网、網(wǎng)頁調(diào)試工具發(fā)展史

2006 年之前勇垛,這屬于 IE 時代,在 IE 時代編寫 JavaScript 代碼時的調(diào)試手段讼积,主要靠 window.alert() 或?qū)⒄{(diào)試信息輸出到網(wǎng)頁上來分析邏輯 bug。這種硬 debug 的手段们颜,不亞于系統(tǒng)底層開發(fā),往往一個小問題要花費掉一整天時間阻问,開發(fā)效率極低。

2006 年 1 月份煌茬,Apple 的 WebKit 團隊釋放出第一版本的 Web Inspector,此版本功能還比較簡樸剔交,僅可以查看 DOM 節(jié)點的繼承關(guān)系,節(jié)點所應(yīng)用了哪些 CSS 的規(guī)則竭鞍。但此版本已經(jīng)奠定了今后多年的網(wǎng)頁調(diào)試工具的原型,具有劃時代意義晒夹。

image

WebKit 團隊的迭代速度非诚韬幔快,2006 年 6 月發(fā)布了一個重量級功能蟀俊,JavaScript 的斷點調(diào)試功能洼哎,此時已經(jīng)具備開發(fā)者神器的雛形锭沟。

同時開源陣營出現(xiàn)一款 Firefox 的插件 Firebug,專注于 Web 開發(fā)的調(diào)試贴妻,奠定了現(xiàn)代 DevTools 的 Web UI 的布局名惩。早期版本就支持了 JavaScript 的調(diào)試,CSS Box 模型可視化展示底循,支持 HTTP Archive 的性能分析等優(yōu)秀特性困檩,后來的 DevTools 參考了此插件的功能和產(chǎn)品定位等舔。2016 年 Firebug 整合到 Firefox 內(nèi)置調(diào)試工具,2017 年 Firebug 停止更新,一代神器就此謝幕。

image

此時迎來了一個開源界的狠角色 Google 團隊芙扎,基于 WebKit 加入瀏覽器研發(fā)允华,推出的 Chrome 以「安全汉额、極速怎茫、更穩(wěn)定」吸引了不少 IT 極客的關(guān)注,同時開發(fā)者工具這方面, Google 吸收多款調(diào)試工具的優(yōu)秀功能缝呕,推出了今天的主角 DevTools鸡捐。

image

早期版本現(xiàn)在看起來這個布局有點簡陋,但這可是十幾年前的作品。支持 DOM + CSS 查看赫悄,查看資源加載分析写隶,腳本調(diào)試以及性能調(diào)試。現(xiàn)在開發(fā)中常用 DevTools 的功能,基本也就這幾個功能。

那個年代的 DevTools详拙,基本是在跟隨 Firebug 的功能帝际,只是交互方式上的差異。2007 年 Steve Jobs 發(fā)布了第一代 iPhone 手機饶辙,Google 相繼推出了 Android 手機蹲诀,互聯(lián)網(wǎng)的發(fā)展來到移動互聯(lián)網(wǎng)時代。DevTools 此時開始超越同類工具弃揽,支持了遠(yuǎn)程真機調(diào)試冷冗。Chrome 是多進程架構(gòu)熄守,DOM 和 JavaScript 是運行在子進程中的羔砾,所以 DevTools 的底層實現(xiàn),已與同類產(chǎn)品完全不同盘寡。Chrome 的架構(gòu)師將 DevTools 實現(xiàn)架構(gòu)調(diào)成在 client-server 模式,這個架構(gòu)讓遠(yuǎn)程真機調(diào)試成為可能豁陆。為了方便網(wǎng)絡(luò)數(shù)據(jù)傳輸原押,Chrome 設(shè)計出了一套數(shù)據(jù)封裝協(xié)議 Chrome DevTools Protocal(CDP),接下來的幾年锁摔,這個架構(gòu)的調(diào)整在開源世界大放異彩籍胯。

image

yan Dahl 基于 Chromium 的 JavaScript 虛擬機 V8 設(shè)計了 Node.js次舌,Node.js 的面世讓 JavaScript 這款 Web 腳本語言走出了瀏覽器,打開了服務(wù)端編程、桌面編程可以使用 JavaScript 語言的新局面。依托于 DevTools 的 client-server 架構(gòu)以及 Node.js 的開發(fā)者的數(shù)量不斷增加氛赐,DevTools 也迅速出圈,Chrome 團隊于 2016 年開始支持 Node.js 的調(diào)試。DevTools 已從一款 Web 調(diào)試工具诞外,演變成 JavaScript 生態(tài)中重要一員啥纸,助力更多的開發(fā)者開發(fā)更多優(yōu)秀代碼鉴腻。Node.js 的生態(tài)都離不開 DevTools 厂汗,比如桌面開發(fā)框架 Electron鳍征、開發(fā)者喜愛的編輯器 Visual Studio Code 、前端架構(gòu) Vue.js面徽、Facebook 開源 Android 性能分析工具 Stetho等艳丛。

三、DevTools 架構(gòu)

DevTools 是 client-server 架構(gòu)趟紊,client 就是用戶操作的 Web UI 界面氮双,負(fù)責(zé)接收用戶操作指令,然后將操作指令發(fā)往瀏覽器內(nèi)核或 Node.js 中進行處理霎匈,并將處理結(jié)果數(shù)據(jù)展示在 Web UI 上戴差。server 啟動了兩類服務(wù),一種 HTTP 服務(wù)铛嘱;另一種 WebSocket 服務(wù)暖释。

HTTP 服務(wù)提供內(nèi)核信息查詢能力。比如獲取內(nèi)核版本墨吓、獲取調(diào)試頁的列表球匕、啟動或關(guān)閉調(diào)試。

WebSocket 服務(wù)提供與內(nèi)核進行真實數(shù)據(jù)通信的能力帖烘,負(fù)責(zé) Web UI 傳遞過來的所有操作指令的分發(fā)和處理亮曹,并將結(jié)果送回 Web UI 進行展示。

下圖展示出了 Android DevTools 的整體架構(gòu)圖,從左側(cè)開發(fā)者通過 Web UI 的發(fā)起的操作命令照卦,是怎么一步一步地將操作命令式矫,傳遞到手機中的 Browser Core(Browser Core 運行 Chrome 瀏覽器內(nèi)核的應(yīng)用,比如 Chrome 瀏覽器役耕、Android WebView采转、NodeJs 應(yīng)用等)中執(zhí)行的過程。

image

Android 平臺巧妙地使用 ADB forward 能力瞬痘,解決了 PC 上的 WebUI 與 Android 手機中的 Chrome 內(nèi)核的連接問題故慈。輕松了實現(xiàn)了遠(yuǎn)程調(diào)試的能力,不要小瞧這一實現(xiàn)图云,這對前端開發(fā)者效率提升是極大的惯悠。因為前端開發(fā)者的工作環(huán)境邻邮,目前來看基本是在 PC (Windows竣况、Mac、Linux 統(tǒng)稱為 PC)下筒严,通過遠(yuǎn)程調(diào)試能力的實現(xiàn)丹泉,讓移動端的開發(fā)實現(xiàn)了所見即所得。

正是 Chrome 團隊基于網(wǎng)絡(luò)通信方式鸭蛙,作為 DevTools 底層通信框架摹恨,才為后來的 Web 開發(fā)團隊百花齊放奠定了基礎(chǔ)。TCP/IP 是互聯(lián)網(wǎng)的基礎(chǔ)娶视,沒有哪種語言或平臺不支持 TCP/IP 的晒哄。DevTools 選型 TCP/IP 方式直接抹平了不同平臺或系統(tǒng)框架之間的差異。

Chrome DevTools Protocol(簡稱CDP) 這組開放協(xié)議的推出肪获,再一次將 DevTools 的實現(xiàn)寝凌,真正做到了跨平臺。CDP 本質(zhì)就是一組 JSON 格式的數(shù)據(jù)封裝協(xié)議孝赫,JSON 是輕量的文本交換協(xié)議较木,可以被任何平臺任何語言進行解析。正因為此青柄,官方推薦的支持 CDP 的語言庫多達(dá)近十種伐债。Google 官方推薦了 Node.js 版本 Puppeteer ,通過 Puppeteer 完整地實現(xiàn)了 CDP 協(xié)議致开,為 Chrome 內(nèi)核通信的方式打了一個樣峰锁,接著開源世界陸續(xù)推出了多個語言版本的 CDP 的使用庫。關(guān)于 CDP 協(xié)議双戳,在稍后的章節(jié)會詳細(xì)介紹祖今。

image

Chrome 的架構(gòu)師通過高度抽象能力,將 DevTools 的底層架構(gòu)抽象成 TCP/IP 和 CDP 兩個部分,奠定了 DevTools 的跨平臺跨終端的能力千诬。當(dāng)年 WebSocket 的實現(xiàn)方案還處在草案階段耍目,Chrome 架構(gòu)師就大膽地采用 WebSocket 實現(xiàn)了調(diào)試協(xié)議中的主協(xié)議部分。現(xiàn)在看來徐绑,開發(fā)者日常使用的頁面的實時截圖能力邪驮,可以實時觀察到遠(yuǎn)程網(wǎng)頁中所展示的界面,這個實時性就是基于 WebSocket 來提供的傲茄。筆者還很佩服 Chrome 架構(gòu)師的眼光和設(shè)計氣場毅访,正是他們優(yōu)秀的能力,將網(wǎng)頁開發(fā)者工具帶到新高度盘榨。

image

四喻粹、DevTools 通信協(xié)議

Chrome DevTools Protocol(簡稱CDP)此協(xié)議包含兩部分 HTTP 和 WebSocket,DevTools 的 Web UI 將控制命令發(fā)往瀏覽器內(nèi)核草巡,其中的控制命令守呜、參數(shù)以及返回值,都是通過 CDP 來進行封裝山憨。命令的發(fā)送時查乒,由 Web UI 進行封裝后,通過 WebSocket 發(fā)往瀏覽器內(nèi)核郁竟。接收到瀏覽器內(nèi)核反饋回結(jié)果后玛迄,再按協(xié)議進行解包,分發(fā)給Web UI棚亩。

image

為了分析 Web UI 與 Android 瀏覽器內(nèi)核通信過程蓖议,需要做一下環(huán)境準(zhǔn)備。

4.1 環(huán)境準(zhǔn)備

為了能訪問到內(nèi)核中數(shù)據(jù)讥蟆,瀏覽器內(nèi)核需要開啟 DevTools Server 勒虾,PC Chrome 和 Android Chrome / WebView 的開啟方式略有不同。

PC Chrome 啟動時攻询,增加一個啟動參數(shù) -remote-debugging-port=9222 , 這樣 DevTools Server 就會偵聽本地的端口从撼,可以向 http://localhost:9222 發(fā)起 HTTP / WebSocket 請求,即可獲取 DevTools 中的數(shù)據(jù)钧栖。

對于 Android Chrome 與 WebView 略有差異低零,由于 WebView 默認(rèn)是不開啟調(diào)試功能的,需要在客戶端手動開啟拯杠,才能啟動 Server掏婶。

// Android 4.4 以上 WebView 才真正使用 Blink 內(nèi)核,所以需要在此版本及以上系統(tǒng)潭陪。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    WebView.setWebContentsDebuggingEnabled(true);
}

此時 Android Chrome / WebView 在手機內(nèi)已啟動了 Server雄妥,但為了在 PC 上能夠訪問到最蕾,需要使用 ADB工具的端口轉(zhuǎn)發(fā)能力。

ADB 端口轉(zhuǎn)發(fā)
您可以使用 forward 命令設(shè)置任意端口轉(zhuǎn)發(fā)老厌,將特定主機端口上的請求轉(zhuǎn)發(fā)到設(shè)備上的其他端口瘟则。以下示例設(shè)置了主機端口 6100 到設(shè)備端口 7100 的轉(zhuǎn)發(fā):

adb forward tcp:6100 tcp:7100

通過 forward 可以打通 PC 與 Android 設(shè)備之間的網(wǎng)絡(luò)相互訪問

Android Chrome / WebView 使用 unix domain socket 建立的 Server 端,此 socket 的連接符為:

chrome_devtools_remote和 webview_devtools_remote_分別為 chrome 和 WebView 的連接符枝秤。WebView 的連接由于可能不同應(yīng)用都使用了 WebView醋拧,所以采用了進程 ID(PID)作為后綴來區(qū)分。

adb shell cat /proc/net/unix | grep "devtools_remote"
0000000000000000: 00000002 00000000 00010000 0001 01 528176 @chrome_devtools_remote
0000000000000000: 00000002 00000000 00010000 0001 01 276394 @webview_devtools_remote_23119

通過 ADB forward 淀弹,將 PC 與 Android 設(shè)備訪問打通丹壕,執(zhí)行如下命令:

# 在 PC 上偵聽 9222 端口,對 localhost:9222 的請求將會轉(zhuǎn)發(fā)到 android 設(shè)備上的 webview_devtools_remote_23119 上
adb forward tcp:9222 localabstract:webview_devtools_remote_23119

至此薇溃,就可以在 PC 上通過 9222 來訪問 Android 設(shè)備中的調(diào)試頁面了菌赖。

4.2 HTTP 協(xié)議分析

4.2.1 獲取內(nèi)核版本信息

# 使用 curl 工具,GET http://localhost:9222/json/version
curl http://localhost:9222/json/version                       
{
   "Android-Package": "com.vivo.browser",
   "Browser": "Chrome/87.0.4280.141",
   "Protocol-Version": "1.3",
   "User-Agent": "Mozilla/5.0 (Linux; Android 8.1.0; vivo X20Plus A Build/OPM1.171019.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Mobile Safari/537.36",
   "V8-Version": "8.7.220.31",
   "WebKit-Version": "537.36 (@9f05d1d9ee7483a73e9fe91ddcb8274ebcec9d7f)",
   "webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser"
}

從上面返回值沐序,可以得到如下幾個信息:

  • Android-Package琉用,使用 WebView 應(yīng)用的包名。

  • Browser薄啥,內(nèi)核的版本號辕羽。

  • Protocol-Version逛尚,為 CDP 的協(xié)議版本垄惧,當(dāng)前版本為 1.3,從 1.0 開始绰寞,還有 1.1到逊、1.2 等。

  • User-Agent滤钱,瀏覽器的 UA 信息觉壶。

  • V8-Version,所使用的 JavaScript 引擎版本號件缸。

  • WebKit-Version铜靶,由于 Blink 內(nèi)核是基于 WebKit 537.36 版本開發(fā),所以會有此版本信息他炊。

  • webSocketDebuggerUrl争剿,這是 WebSocket 的調(diào)試 URL。

4.2.2 獲取可調(diào)試頁面列表

# 使用 curl 工具痊末,GET http://localhost:9222/json/list
curl http://localhost:9222/json/list  
[ {
   "description": "{\"attached\":true,\"empty\":false,\"height\":1812,\"never_attached\":false,\"screenX\":0,\"screenY\":72,\"visible\":true,\"width\":1080}",
   "devtoolsFrontendUrl": "https://chrome-devtools-frontend.appspot.com/serve_rev/@9f05d1d9ee7483a73e9fe91ddcb8274ebcec9d7f/inspector.html?ws=localhost:9222/devtools/page/B86E67DEA526D5EEE83A170B1F62A72C",
   "faviconUrl": "https://mat1.gtimg.com/www/mobi/2017/image/logo/v0/192.png",
   "id": "B86E67DEA526D5EEE83A170B1F62A72C",
   "title": "騰訊網(wǎng)-QQ.COM",
   "type": "page",
   "url": "https://xw.qq.com/#news",
   "webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/B86E67DEA526D5EEE83A170B1F62A72C"
}, {
   "description": "{\"attached\":false,\"empty\":true,\"never_attached\":true,\"screenX\":0,\"screenY\":0,\"visible\":true}",
   "devtoolsFrontendUrl": "https://chrome-devtools-frontend.appspot.com/serve_rev/@9f05d1d9ee7483a73e9fe91ddcb8274ebcec9d7f/inspector.html?ws=localhost:9222/devtools/page/3F9E05905F1919D563DF01BAEC64D2E4",
   "id": "3F9E05905F1919D563DF01BAEC64D2E4",
   "title": "about:blank",
   "type": "page",
   "url": "about:blank",
   "webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/3F9E05905F1919D563DF01BAEC64D2E4"
} ]

返回了一個 JSON 的數(shù)組蚕苇,每一個調(diào)試頁占用一個數(shù)據(jù)元素,上面的返回值可以看出凿叠,筆者環(huán)境下 vivo 瀏覽器打開了兩個頁面涩笤,一個 https://xw.qq.com/#news 和 about:blank嚼吞。

  • description,是個 JSON 對象蹬碧,展示當(dāng)前頁面的狀態(tài)信息舱禽。比如頁面寬、高恩沽、在屏幕上的偏移呢蔫,WebView 是否已經(jīng) attached 到 view 上了,只有 attach 上的頁面飒筑,才會被展示出來片吊,能否被調(diào)試。

  • devtoolsFrontendUrl协屡,此值為一個 URL俏脊,就是日常使用 DevTools 的 WebUI 控制面板地址,這是個 Web APP 當(dāng)訪問過一次后肤晓,會就緩存一份在瀏覽器下爷贫。此頁面托管在某個在國內(nèi)無法正常訪問地址,所以經(jīng)常會出現(xiàn)打不開面板补憾,而顯示白屏的情況漫萄。Chrome 瀏覽器在打包時會內(nèi)置一份與當(dāng)前內(nèi)核匹配的 WebUI 版本,所以 Chrome 可以直接調(diào)試自己的頁面盈匾。

  • id腾务,這是每個打開頁面隨機生成的 GUID 值,用于生成 WebSocket 鏈接削饵,以區(qū)分不同頁面岩瘦。

  • title,打開網(wǎng)頁的標(biāo)題窿撬,對應(yīng)網(wǎng)頁 head 中的 title 標(biāo)簽內(nèi)容启昧。

  • type,頁面的類型劈伴,主要有以下幾類 page密末、iframe、worker 以及 service_worker 等跛璧。

  • URL严里,當(dāng)前打開的頁面 URL。

  • webSocketDebuggerUrl赡模,此參數(shù)為 WebSocket 連接的 URL田炭。

HTTP 協(xié)議還有其他幾個子命令,比如 protocol漓柑、new教硫、activate 等叨吮,主要是頁面控制類的,就不一一介紹了瞬矩。

4.2.3 WebSocket 協(xié)議分析

WebSocket 協(xié)議由四部分組成: Domain 茶鉴、Method 、 Event 和 Type 景用。

1)Domain涵叮,命名空間,類似 C++/Java 中的命名空間或包名伞插,用于分割不同的命令割粮。用于將眾多子命令按類劃分,方便使用者調(diào)用媚污,以及防止 Method 同名沖突舀瓢。以 1.3 版本的 CDP 協(xié)議,一共劃分出 15 個Domain耗美。

  • Browser: 用于管理瀏覽器對象京髓。

  • Debugger: 用于調(diào)試 JavaScript 的分類,比如斷點商架、調(diào)用棧等堰怨。

  • DOM: 所有 DOM 節(jié)點操作都在此 Domain 下,DOM 節(jié)點的修改蛇摸,遍歷等备图。

  • DOMDebugger: 管理 DOM 節(jié)點調(diào)試的 Domain,DevTools 中節(jié)點修改斷點皇型,就是通過這組 Domain 中提供的 Method 完成的诬烹。

  • Emulation: 此是一組環(huán)境模擬器集合砸烦,DevTools 中的修改設(shè)備尺寸弃鸦、UserAgent 等是由這個 Domain 實現(xiàn)。

  • Input: 事件分發(fā)方法的集合幢痘。

  • IO: I/O 流操作集合唬格。

  • Log: Log 控制 Method 集合。

  • Network: 瀏覽器網(wǎng)絡(luò)通信數(shù)據(jù)颜说,可能通過此 Domain 進行捕獲购岗。

  • Page: 基于 Blink 中的 Page 操作 Method 集合,比如刷新门粪,打開 URL喊积。

  • Performance: 集成了性能分析 Method。

  • Profiler: 采樣分析器的 Method 集成在此 Domain 下玄妈。

  • Runtime: 與 JavaScript 通信的 Method 被集成此 Domain 下乾吻,比如執(zhí)行 JavaScript 代碼髓梅。

  • Security: 安全類操作,比如證書錯誤绎签。

  • Target: DevTools 連接的一些控制類 Method 在此 Domain 下枯饿。

2)Method,方法名稱诡必,每個 Domain 下都會有一組 Method奢方,指明了具體操作瀏覽器內(nèi)核的功能。有三部分組成:名稱 爸舒、 參數(shù) 和 返回值 蟋字。與 C++/Java 中方法描述一致。

  • 名稱:Debugger.setBreakpointByUrl扭勉;

  • 參數(shù):lineNumber integer [愉老,url string,urlRegex string剖效,scriptHash string嫉入,columnNumber integer,condition string ]璧尸;

  • 返回值:breakpointId BreakpointId咒林,actualLocation Location。

// Debugger.setBreakpointByUrl 到內(nèi)核爷光,帶上如下參數(shù)
{
   "lineNumber":1,
   "url":"snippet:///Script%20snippet%20%231",
   "columnNumber":0,
   "condition":""
}
 
// 將會收到內(nèi)核的返回值垫竞,返回斷點成功信息
{
   "breakpointId":"1:1:0:snippet:///Script%20snippet%20%231",
   "locations":[]
}

3)Event,通知事件蛀序,網(wǎng)頁會有很多狀態(tài)通知欢瞪,需要同步到 WebUI 或其他控制端上來。Event 就是用于通知這些事件的徐裸。比如 DOM 屬性發(fā)生了變化時遣鼓,將會收到 Dom.attributeModified 事件;將 JavaScript 傳遞到內(nèi)核去執(zhí)行時重贺,將會收到內(nèi)核發(fā)回來的 Debugger.scriptParsed 事件和參數(shù)骑祟,參數(shù)如下:

{
   "scriptId":"238",
   "url":"",
   "startLine":0,
   "startColumn":0,
   "endLine":0,
   "embedderName":"",
   "endColumn":7,
   "endLine":0,
   "executionContextAuxData":{
      "isDefault":true,
      "type":"default",
      "frameId":"2059AA1A2C1A535CF4C480DC01E7FDEC"
   },
   "frameId":"2059AA1A2C1A535CF4C480DC01E7FDEC",
   "isDefault":true,
   "type":"default",
   "executionContextId":5,
   "hasSourceURL":false,
   "hash":"035a9e1738252e22523ed8f1c52d9dbf81abe278",
   "isLiveEdit":false,
   "isModule":false,
   "length":7,
   "scriptId":"238",
   "scriptLanguage":"JavaScript",
   "sourceMapURL":"",
   "startColumn":0,
   "startLine":0,
   "url":""
}

4)Type,是 Method 或 Event 傳遞參數(shù)的復(fù)雜數(shù)據(jù)類型气笙,這些類型與內(nèi)核的對象相對應(yīng)次企。比如 DOM.Node 類型就對應(yīng)著 Blink 中的 DOM 節(jié)點。主要屬性如下:

  • nodeId: NodeId 也是 Type潜圃,節(jié)點 id缸棵,根據(jù)此值可以在內(nèi)核找到對應(yīng)的節(jié)點。

  • parentId: NodeId 也是 Type谭期,父節(jié)點 id 堵第。

  • nodeType: integer稚晚,節(jié)點類型。

  • nodeName: string型诚,節(jié)點名稱客燕。

  • nodeValue:string, 節(jié)點內(nèi)容。

  • children: array狰贯,子節(jié)點數(shù)組也搓。

  • attributes: array, 節(jié)點屬性數(shù)組 通過 Node 上這些屬性涵紊,就可以將 DOM 樹的節(jié)點在內(nèi)存占用描述出來傍妒。DevTools 的 Web UI 中 Element 面板,就是通過 DOM.getDocument Method 將一棵 DOM 樹展現(xiàn)出來摸柄。

通過 CDP 的這種數(shù)據(jù)組織方式颤练,既可以傳遞控制命令來操作內(nèi)核,也可以接收內(nèi)核狀態(tài)通知(Event)嗦玖。通過 CDP 可以讓瀏覽器做任何事情,而且得到的信息遠(yuǎn)比使用 Chrome 圖形界面還要多跃脊。因此宇挫, Google 推出 Chrome Headless 版本,被廣泛應(yīng)用于 web 自動化測試酪术、網(wǎng)頁爬蟲以及網(wǎng)頁沙箱等領(lǐng)域器瘪。

當(dāng)調(diào)試移動端瀏覽器時,可以實時看到移動設(shè)備上的所瀏覽的屏幕绘雁,這是怎么做到的呢橡疼?

其實,就是一張一張截圖通過 Page.screencastFrame 事件將 base64 后的圖片發(fā)回到 Web UI 中展示的庐舟。

image

從 Page.screencastFrame 通知事件帶回了圖片和描述信息(Meta data):

{
   "data":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBw...",
   "metadata":{
      "deviceHeight":604,
      "deviceWidth":360,
      "offsetTop":60,
      "pageScaleFactor":1,
      "scrollOffsetX":0,
      "scrollOffsetY":832.6666870117188,
      "timestamp":1631018056.565802
   },
   "sessionId":2
}

通過描述信息欣除,即可將此圖片的信息展示在 WebUI 上。一張截圖近 1M 的大小继阻,由于 DevTools 利用了 WebSocket 的雙向長鏈接的特性耻涛,所以展示出來無比平滑和清晰。

4.3 DevTools 內(nèi)核實現(xiàn)

以上章節(jié)瘟檩,介紹了從 Web 開發(fā)者的角度出發(fā),將操作命令傳遞到移動端 Browser Core 的一個整體流程澈蟆,以及 CDP 通信協(xié)議相關(guān)內(nèi)容墨辛。本節(jié)重點介紹在 Browser Core 中的實現(xiàn)過程,先介紹 DevTools 在瀏覽器內(nèi)核中實現(xiàn)趴俘,后面筆者會挑選 JavaScript 如何從字符串傳遞到 V8 中執(zhí)行過程睹簇,展開來進行詳細(xì)介紹奏赘,這一行為的實現(xiàn)方案。

4.3.1 內(nèi)核架構(gòu)介紹

DevTools 以啟動一個 Web Server 為起點太惠,然后將調(diào)用命令發(fā)到相應(yīng)處理模塊磨淌,整體架構(gòu)圖如下:

image

DevTools 在內(nèi)核中大體上分為四層:

  • Server 層,用于接收外部網(wǎng)絡(luò)發(fā)過來的操作請求凿渊。

  • Agent 層梁只,對于 Server 層發(fā)過來的請求,進行拆解埃脏,根據(jù)操作的類型不同搪锣,再分發(fā)給不同的 Agent 來處理。

  • Session 層彩掐,Session 是對不同的業(yè)務(wù)模塊進行了一層抽象构舟。過了 Session 層后,將會進入不同的業(yè)務(wù)模塊堵幽,可以到達(dá) V8狗超, Blink 等。

  • 業(yè)務(wù)層朴下,就是具體的功能模塊抡谐,比如 V8 模塊丰包,主要負(fù)責(zé) JavaScript 的調(diào)試相關(guān)能力的支撐路翻。

Server 層由 DevToolsManager 這個單例對象來管理辉哥,由于是單例所以一個進程只會存在一個 Manger 對象尾菇,從而防止被重復(fù)創(chuàng)建出多個粘招,導(dǎo)致狀態(tài)錯亂算谈。

4.3.2 Web Server 數(shù)據(jù)接收入口

Server 收到的請求都會分發(fā)給 DevToolsHttpHandler 類痹栖,此類負(fù)責(zé)網(wǎng)絡(luò) Client 發(fā)過來的數(shù)據(jù)請求響應(yīng)和將處理結(jié)果發(fā)送回網(wǎng)絡(luò) Client辣恋, 此類有兩個重要方法 OnJsonRequest 和 OnWebSocketMessage 惫撰,分別用來處理 HTTP 協(xié)議和 WebSocket 協(xié)議羔沙。

void DevToolsHttpHandler::OnJsonRequest(
    int connection_id,
    const net::HttpServerRequestInfo& info) {
  // 查詢內(nèi)核版本信息
  if (command == "version") {
    base::DictionaryValue version;
    version.SetString("Protocol-Version",
                      DevToolsAgentHost::GetProtocolVersion());
    // ...
    SendJson(connection_id, net::HTTP_OK, &version, std::string());
    return;
  }
  // 獲取內(nèi)核所支持的協(xié)議
  if (command == "protocol") {
    DecompressAndSendJsonProtocol(connection_id);
    return;
  }
  // 獲取可調(diào)試頁
  if (command == "list") {
    DevToolsManager* manager = DevToolsManager::GetInstance();
    DevToolsAgentHost::List list =
        manager->delegate() ? manager->delegate()->RemoteDebuggingTargets()
                            : DevToolsAgentHost::GetOrCreateAll();
    RespondToJsonList(connection_id, info.GetHeaderValue("host"),
                      std::move(list));
    return;
  }
  // 啟動一個新調(diào)試
  if (command == "new") {
    // ...
    std::string host = info.GetHeaderValue("host");
    std::unique_ptr<base::DictionaryValue> dictionary(
        SerializeDescriptor(agent_host, host));
    SendJson(connection_id, net::HTTP_OK, dictionary.get(), std::string());
    return;
  }
  // 激活或關(guān)閉一個調(diào)試
  if (command == "activate" || command == "close") {
   // ...
  SendJson(connection_id, net::HTTP_NOT_FOUND, nullptr,
           "Unknown command: " + command);
}
 
void DevToolsHttpHandler::OnWebSocketRequest(
    int connection_id,
    const net::HttpServerRequestInfo& request) {
  // 創(chuàng)建調(diào)試的 Agent
  if (base::StartsWith(request.path, browser_guid_,
                       base::CompareCase::SENSITIVE)) {
    scoped_refptr<DevToolsAgentHost> browser_agent =
        DevToolsAgentHost::CreateForBrowser(
            thread_->task_runner(),
            base::BindRepeating(&DevToolsSocketFactory::CreateForTethering,
                                base::Unretained(socket_factory_.get())));
    connection_to_client_[connection_id] =
        std::make_unique<DevToolsAgentHostClientImpl>(
            thread_->task_runner(), server_wrapper_.get(), connection_id,
            browser_agent);
    AcceptWebSocket(connection_id, request);
    return;
  }
 
  connection_to_client_[connection_id] =
      std::make_unique<DevToolsAgentHostClientImpl>(
          thread_->task_runner(), server_wrapper_.get(), connection_id, agent);
    // Accept websocket
  AcceptWebSocket(connection_id, request);
}
 
// WebSocket 數(shù)據(jù)接收接口,所有 WebUI 的請求都通過此接口分發(fā)
void DevToolsHttpHandler::OnWebSocketMessage(int connection_id,
                                             std::string data) {
  auto it = connection_to_client_.find(connection_id);
  if (it != connection_to_client_.end()) {
    it->second->OnMessage(base::as_bytes(base::make_span(data)));
  }
}
  • DevToolsHttpHandler::OnJsonRequest 用于響應(yīng) HTTP 請求厨钻,用于查詢內(nèi)核狀態(tài)扼雏,比如內(nèi)核版本、當(dāng)前支持協(xié)議夯膀,將返回完整協(xié)議內(nèi)容诗充,方便開發(fā)者適配對應(yīng)的支持。

  • DevToolsHttpHandler::OnWebSocketRequest 用于接收 WebSocket 的連接诱建,根據(jù)此方法對不同的 Agent 對象進行了創(chuàng)建蝴蜓。

  • DevToolsHttpHandler::OnWebSocketMessage 所有調(diào)試請求數(shù)據(jù),都經(jīng)過此接口通過 Client 分發(fā)到不同的 Agent 上去。

Server 層數(shù)據(jù)響應(yīng)時通過上面的三個接口來達(dá)到數(shù)據(jù)接收和分發(fā)的能力茎匠。

4.3.3 JavaScript 執(zhí)行過程

V8 JavaScript 引擎用于解釋執(zhí)行網(wǎng)頁中的 JavaScript 腳本格仲,同時也可以通過 DevTools 接收外部傳遞過來的腳本,腳本在當(dāng)前網(wǎng)頁的 Context 下執(zhí)行诵冒,所以可以通過 JavaScript 來操作網(wǎng)頁行為凯肋,比如修改 DOM 節(jié)點屬性。CDP 中設(shè)計了執(zhí)行 JavaScript 接口 Runtime.evaluate 汽馋,引方法的參數(shù)如下:

{
    allowUnsafeEvalBlockedByCSP: false,
    awaitPromise: false,
    contextId: 14,
    expression: "alert('hi');",
    generatePreview: true,
    includeCommandLineAPI: true,
    objectGroup: "console",
    replMode: true,
    returnByValue: false,
    silent: false
}

其中侮东,最重要的一個參數(shù)就是 expression ,此為一個 string 類型的參數(shù)惭蟋,用于存放需要執(zhí)行的腳本內(nèi)容苗桂。上例將會在網(wǎng)頁中彈出一個內(nèi)容為 hi 的 alert 確認(rèn)框。

V8 中有個專門的模塊告组,V8RuntimeAgentImpl 用于支持 CDP 中 Runtime 的這個 Domain煤伟,當(dāng)然也有 V8DebuggerAgentImpl 是用來支持 Debug 這個 Domain 的具體實現(xiàn)。V8RuntimeAgentImpl 中 evaluate 方法木缝,就是用于負(fù)責(zé)接收 DevTools 發(fā)過來的執(zhí)行請求便锨。

void V8RuntimeAgentImpl::evaluate(
    const String16& expression, Maybe<String16> objectGroup,
    Maybe<bool> includeCommandLineAPI, Maybe<bool> silent,
    Maybe<int> executionContextId, Maybe<bool> returnByValue,
    Maybe<bool> generatePreview, Maybe<bool> userGesture,
    Maybe<bool> maybeAwaitPromise, Maybe<bool> throwOnSideEffect,
    Maybe<double> timeout, Maybe<bool> disableBreaks, Maybe<bool> maybeReplMode,
    Maybe<bool> allowUnsafeEvalBlockedByCSP,
    std::unique_ptr<EvaluateCallback> callback);

V8RuntimeAgentImpl::evaluate 會啟動一個 microtasks 來執(zhí)行腳本,最終會走到 v8::internal::Execution::Call 中我碟,Execution 模塊會負(fù)責(zé)將腳本進行語法解析和編譯成字節(jié)碼放案,最終調(diào)度到虛擬機器中運行。

image

執(zhí)行流程如上圖所示矫俺,Web UI 發(fā)出執(zhí)行腳本的字符串吱殉,WebSocket 的 OnWebSocketMessage 將會收到此命令,然后通過 DevToolsSession 逐層向 V8 分發(fā)厘托。由于 Chrome 是多進程架構(gòu)友雳,分為Browser 進程和 Render 進程,之間通過 IPC 進行通信铅匹。上圖左側(cè)在 Browser 端執(zhí)行流程押赊,右側(cè)為 Render 端執(zhí)行流程。

Render 端的DevToolsSession::DispatchProtocolCommand 是一個重要的分發(fā)接口包斑,所以發(fā)到 V8 或 Blink 的控制命令流礁,都會經(jīng)過此接口。接著就會將控制命令發(fā)送到 V8RuntimeAgentImpl罗丰,根據(jù)命令功能的不同神帅,調(diào)度到不同功能模塊進行處理。

4.4 網(wǎng)頁性能調(diào)優(yōu)

4.4.1 性能分析面板介紹

DevTools 提供一組功能強大的性能分析工具丸卷,網(wǎng)絡(luò)枕稀、JavaScript 調(diào)試、渲染谜嫉、內(nèi)存以及標(biāo)準(zhǔn)支持度檢測等萎坷。下面介紹 Performance 面板中一些性能分析時的一些功能。主界面被劃分為這幾塊:

image

1)幀率(FPS):線性展示了做 Performance 期間沐兰,網(wǎng)頁渲染的幀率哆档。

2)CPU 使用率:CPU 占用走勢圖

3)加載過程中截屏:定時采集了網(wǎng)頁截屏性能

4)網(wǎng)絡(luò)加載時序:展示網(wǎng)絡(luò)資源加載次序及耗時情況

5)幀耗時(Frames):展示了渲染每幀耗時情況,紅色表示存在耗時較長的幀住闯。

6)Web Vitals 指標(biāo):Google 推薦一套性能體驗指標(biāo)瓜浸,下面會詳細(xì)介紹。

7)內(nèi)核中主要線程:瀏覽器內(nèi)核中存在多個線程各有分工比原,當(dāng)出現(xiàn)耗時較長幀時插佛,需要在這些線程中排查,具體哪個線程在耗時量窘。主要分為這幾個:

  • Main雇寇,這是 Blink 主線程,負(fù)責(zé)網(wǎng)頁的排版蚌铜、解析锨侯、JavaScript 執(zhí)行等。

  • Raster冬殃,光柵化線程囚痴,用于將渲染對象轉(zhuǎn)化成 Bitmap。

  • GPU审葬,硬件加速渲染線程深滚,將 Texture 繪制到屏幕上。

  • Chrome_ChildIOThread涣觉,負(fù)責(zé)網(wǎng)絡(luò)資源痴荐,文件操作。

  • Compositor旨枯,合成線程蹬昌,負(fù)責(zé)將渲染時各個層,合成在一起然后進行光柵化攀隔。

  • ThreadPoolForegoundWorker皂贩,Worker 的工作線程池。

8)信息面板:用于展示選擇模塊詳細(xì)信息昆汹,幾個指標(biāo)含義:

  • Loading:網(wǎng)絡(luò)請求和 HTML 解析耗時明刷。

  • Scripting:JavaSript 解析、編譯满粗、在虛擬機中執(zhí)行辈末,以及 GC 耗時。

  • Rendering:Blink 排版渲染耗時。

  • Painting:繪制耗時挤聘,主要包含繪制轰枝、合成、圖片解碼以及上屏组去。

  • System 和 Idle:是系統(tǒng)調(diào)度和空閑耗時鞍陨。

4.4.2 性能分析常規(guī)思路

性能分析基本思路從問題入手,網(wǎng)頁常見性能問題从隆,筆者遇到的主要有這幾種情形寿烟。

  • 需要的資源沒有及時被請求回來购桑。排除服務(wù)器問題,資源請求發(fā)起太晚?資源太大?

  • 網(wǎng)頁分層太多,導(dǎo)致 Rendering 和 Painting 時間過長。

  • 內(nèi)存占用過多喇闸,頁面過于復(fù)雜宛琅、資源多且大痢艺、JavaScript 大塊資源持有生命周期太長舌缤。

  • 動畫多且消失后未移除玻墅。JavaScript 的輪播動畫、CSS 的動畫徐伐、帶有動畫的圖片資源,比如 GIF, SVG、WebP 等。

  • 事件偵聽不合理。事件偵聽過多且可能被高頻觸達(dá),比如節(jié)點變化霸褒、Move 事件等殊轴。

總的來說韧拒,不論是網(wǎng)頁性能優(yōu)化還是 Native 程序優(yōu)化楷掉,只要協(xié)調(diào)好這兩個資源占用即可:CPU + 內(nèi)存。只要挖掘出問題點墩虹,性能問題都會迎刃而解嘱巾,問題點的挖掘除了源碼級別的審查憨琳,DevTools 可以助一臂之力。

針對上面總結(jié)的常規(guī)場景旬昭,利用 DevTools 性能分析能力篙螟,先整體上審視 Profile 圖。

網(wǎng)絡(luò)請求次序和時長是否合理问拘;

Main Thread 的長任務(wù)是否合理遍略。

image

從 Network 板塊觀察資源請求發(fā)起的順序,是否存在長耗時任務(wù)骤坐,阻塞著首屏展示資源加載绪杏,如果不保證需要的及時加載,就會長時間白屏或油。

資源問題就緒后寞忿,就需要排查哪些長耗時任務(wù)執(zhí)行。先查看 Main Thread 中的 Long task顶岸,比如,上圖的 Long task 就是 Scripting 的占了較長時間叫编。通過 Bottom-Up / CallTree 查看具體的耗時點辖佣,相應(yīng)地優(yōu)化掉。

在排查具體優(yōu)化點時搓逾,有個小技巧卷谈。通常開發(fā)環(huán)境都是在 PC 上進行模擬,當(dāng)版本出去后霞篡,才能暴露出問題世蔗。由于移動設(shè)備的碎片化,很多用戶的設(shè)備朗兵,性能可能并不好污淋。那如何在開發(fā)環(huán)境優(yōu)化這類低配置機器上的表現(xiàn)呢?DevTools 提供了限流的模擬余掖,可以限制網(wǎng)絡(luò)制式為 2G/3G寸爆,CPU 降速。

image

在右上角有個“設(shè)置”盐欺,展開配置項目赁豆,可以看到 Network 和 CPU 的限流選項,選擇后重新錄制一下 Profile冗美。

上面提到魔种,網(wǎng)頁層數(shù)太多,極大地影響到網(wǎng)頁渲染性能粉洼〗谠ぃ“網(wǎng)頁層數(shù)” 是什么意思呢甲抖?目前,瀏覽器渲染引擎為了提升網(wǎng)頁繪制性能心铃,繪制時會對網(wǎng)頁進行分層准谚。這樣的好處就是,僅重繪修改過的層去扣,其他層內(nèi)容如果沒有變化柱衔,就不需要重新繪制,直接取上次繪制結(jié)果愉棱,從而提升繪制效率唆铐。不同的 WEB 引擎分層的策略不同,通常會將普通網(wǎng)頁奔滑、CSS 動畫艾岂、Canvas、WebGL朋其、Fix 標(biāo)簽等各分為一層王浴。分層會帶來渲染效率的提升,但也會帶來內(nèi)存的開銷梅猿,從而會影響到性能氓辣。DevTools 能否分析網(wǎng)頁層數(shù)嗎?可以袱蚓,在上面的“設(shè)置”中有一個選項 “Enable advanced paint instrumentation(slow)” 啟用它钞啸,重新做一次性能錄制。

image

在 “信息面板” 多了一個 “Layers” 標(biāo)簽喇潘,選擇后將會看到網(wǎng)頁分層情況体斩。如果存在不合理的分層,可以嘗試調(diào)整方式颖低,將分層進行合并絮吵,從而達(dá)到提升性能。

4.4.3 Web Vitals

Web Vitals 是 Google 推出的一套 Web 性能與體驗兼顧的衡量標(biāo)準(zhǔn)枫甲。原先的衡量策略基本是基于 “首字” 和 “首屏” 來衡量源武,但從用戶角度和技術(shù)優(yōu)化角度,這兩指標(biāo)都存在這樣那樣的問題想幻。所以粱栖, Google 推出了 Web Vitals 標(biāo)準(zhǔn),并與 DevTools 進行配合脏毯,方便開發(fā)者在開發(fā)階段闹究,就識別出 Web 的性能問題。由于標(biāo)準(zhǔn)一直隨著時代的發(fā)展食店,不斷變化渣淤,開發(fā)者一直追著指標(biāo)的變化有點吃不消赏寇,好在 Google 明確表示,目前推出的三個指標(biāo)价认,短時間內(nèi)不會變嗅定,筆者就不清楚這個短時間是多長時間。

第一個指標(biāo):Largest Contentful Paint (LCP)用踩,大面積鋪滿時間點渠退,2.5 秒以內(nèi)算優(yōu)秀。主要是指有大面積的文字脐彩、圖片被展示出來碎乃,就算達(dá)到了 LCP。

image

第二個指標(biāo):First Input Delay(FID)惠奸,首次可響應(yīng)外部輸入事件的時間點梅誓,100 ms 內(nèi)算優(yōu)秀。這個指標(biāo)是從用戶使用角度出發(fā)佛南,達(dá)到 FID 的時間點梗掰,意味著用戶可以操作網(wǎng)頁了。

image

第三個指標(biāo):Cumulative Layout Shift(CLS)共虑,排版跳躍指標(biāo)愧怜,0.1 為優(yōu)秀。在網(wǎng)頁加載過程中妈拌,如果出現(xiàn)排好版的元素,發(fā)現(xiàn)大面積的移動的話蓬蝶,這個指標(biāo)就會很高尘分。比如網(wǎng)頁中 img 標(biāo)簽不設(shè)置寬和高,當(dāng)圖片加載完畢后丸氛,按圖片實際大小來排版本培愁。這樣的就會觸發(fā)網(wǎng)頁重新排版,從用戶角度網(wǎng)頁被整體向下推了一個圖片高度缓窜,Google 認(rèn)為這個體驗不好定续。

image

LCP / FID / CLS 這三個指標(biāo),本質(zhì)上是從用戶視角看網(wǎng)頁的性能衡量指標(biāo)禾锤,開發(fā)者可以看看自己作品這三個指標(biāo)屬于什么水平私股。

五、工具在生態(tài)構(gòu)建中的重要性

image

(數(shù)據(jù)來自 statcounter.com)

Chrome 憑借著自己優(yōu)秀的產(chǎn)品特性恩掷,安全倡鲸、快速以及穩(wěn)定性,贏得了大批用戶青睞黄娘。從上圖 StatCounter 統(tǒng)計數(shù)據(jù)峭状,可以看出 Chrome 已成為絕對的瀏覽器界的一哥克滴,理所當(dāng)然地取得商業(yè)上的成功。但是 Chrome 在開源以及生態(tài)的建立优床,DevTools 可謂首功一件劝赔。Google 通過 DevTools 的超越競品的特性,吸引了大批前端開發(fā)者胆敞,轉(zhuǎn)到 Chrome 下開發(fā)自己的產(chǎn)品着帽。早期生態(tài)產(chǎn)品是 Chrome 插件,Chrome Store 中的插件數(shù)量就可以看出它的成功竿秆。

當(dāng) Node.js 的問世启摄,DevTools 首款支持 Node.js 的調(diào)試工具,推動了 Node.js 的普及幽钢。然后 DevTools 依托 Node.js 迅速出圈歉备。另一方面,開源世界也開始反哺了 DevTools 項目匪燕,目前支持 CDP 協(xié)議的開源方案多達(dá) 10 幾種語言蕾羊,常用的語言基本都支持上了。這個領(lǐng)域目前還在飛速發(fā)展中帽驯,期待這個領(lǐng)域可以有更好的發(fā)展龟再。

DevTools Web UI 已經(jīng)從 Chromium 倉庫中獨立出來,可以單獨 Clone 下來進行二次開發(fā)尼变,Web UI 本次限于篇幅利凑,未做實現(xiàn)原理分析。其實嫌术,Web UI 也是個非常優(yōu)秀的 Web APP哀澈,很適合前端開發(fā)者深度研究一下。

我們從優(yōu)秀開源項目中學(xué)習(xí)到的不僅是代碼實現(xiàn)與架構(gòu)度气,也可以學(xué)習(xí)到更高維度的東西割按,比如產(chǎn)品思維以及工具思維,并落地到自己項目中磷籍∈嗜伲回顧一下網(wǎng)頁調(diào)試領(lǐng)域發(fā)展過程,從一款 JavaScript 插件院领,是如何演變成今天的前端開發(fā)生態(tài)弛矛,其中有很多點值得學(xué)習(xí)。

六栅盲、結(jié)束語

筆者所在團隊長期致力于 Chromium 內(nèi)核的研究與學(xué)習(xí)汪诉,基于其衍生出來的產(chǎn)品,服務(wù)我們生態(tài)用戶,為其提供優(yōu)質(zhì)的上網(wǎng)體驗扒寄。同時鱼鼓,我們孵化出的 Web 瀏覽服務(wù),也為生態(tài)內(nèi)應(yīng)用提供強大该编、快速迄本、穩(wěn)定的 Web 服務(wù)能力。如果您有興趣于 Web 底層技術(shù)研究课竣,歡迎加入我們嘉赎,與一群志同道合的小伙伴共同成長,同時也能服務(wù)好億級用戶于樟。

七公条、參考文獻

[1] Google Chrome

[2] 10 Years of Web Inspector

[3] 10 years of Speed in Chrome

[4] Chrome DevTools

[5] Chrome DevTools Protocol protocol

[6] Web Vitals

作者:vivo 互聯(lián)網(wǎng)瀏覽器內(nèi)核團隊-Li Qingmei

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市迂曲,隨后出現(xiàn)的幾起案子靶橱,更是在濱河造成了極大的恐慌,老刑警劉巖路捧,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件关霸,死亡現(xiàn)場離奇詭異,居然都是意外死亡杰扫,警方通過查閱死者的電腦和手機队寇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來章姓,“玉大人佳遣,你說我怎么就攤上這事》惨粒” “怎么了苍日?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長窗声。 經(jīng)常有香客問我,道長辜纲,這世上最難降的妖魔是什么笨觅? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮耕腾,結(jié)果婚禮上见剩,老公的妹妹穿的比我還像新娘。我一直安慰自己扫俺,他們只是感情好苍苞,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般羹呵。 火紅的嫁衣襯著肌膚如雪骂际。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天冈欢,我揣著相機與錄音歉铝,去河邊找鬼。 笑死凑耻,一個胖子當(dāng)著我的面吹牛太示,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播香浩,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼类缤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了邻吭?” 一聲冷哼從身側(cè)響起餐弱,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎镜盯,沒想到半個月后岸裙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡速缆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年降允,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艺糜。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡剧董,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出破停,到底是詐尸還是另有隱情翅楼,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布真慢,位于F島的核電站毅臊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏黑界。R本人自食惡果不足惜管嬉,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望朗鸠。 院中可真熱鬧蚯撩,春花似錦、人聲如沸烛占。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至犹菇,卻和暖如春德迹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背项栏。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工浦辨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沼沈。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓流酬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親列另。 傳聞我的和親對象是個殘疾皇子芽腾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容