這是來自谷歌官方博客: inside modern browser 的四篇系列文章,也有人作了全文翻譯,參見承香墨影。本文可看做一個abstract,對全文進行了重點摘要,以期讀完後對chrome瀏覽器的體系結構,處理機制有整體的瞭解。有個經典的前端面試問題:在導航欄輸入url敲下回車後發生了什麼?本文可以看做一個比較全面、深入一點的回答。
瀏覽器體系結構
- CPU 一個接一個地處理多項任務
- GPU 同時、跨內核處理多個簡單任務
開啓application時,機器的物理硬件在最底層,中間是操作系統,最上層纔是應用本身。
瀏覽器沒有一定之規。有的是單進程多線程,有的是多進程,一個進程有少量的多個線程IPC。
進程 | 功能 |
---|---|
Browser | controls ‘chrome’ part of the app and privileged parts such as network requests and file access |
Renderer | anything inside of the tab where a website is displayed |
Plugin | any plugins used by the web like flash |
GPU | Handles GPU tasks in isolation from other processes. It is separated into different process because GPUs handles requests from multiple apps and draw them in the same surface. |
- 出於安全和沙箱考慮,每個tab有獨立的renderer process
- 因爲每個進程有獨立的內存空間,這會造成一定的資源浪費(can’t share common infrasructure like v8), chrome限制了它可以啓動的進程數量。如果達到了限制,則會把多個tab上那些同源的網頁合併到一個進程中。
- chrome 正在做的事情是,把瀏https://www.cnblogs.com/peida/archive/2013/03/05/2943698.html覽器程序的每個部分作爲一項可拆分的服務。當 Chrome 在強大的硬件上運行時,它可能會將每個服務拆分爲不同的進程,從而提供更高的穩定性,但如果它位於資源約束的設備上,Chrome 會將服務整合到一個進程中,從而節省內存佔用。
- 整合進程以節省資源的策略已經被用於其他平臺,如andriod上。
每個frame有獨立渲染進程,站點隔離
導航過程
tab之外的一切都是瀏覽器進程控制。瀏覽器進程包括:
線程 | 功能 |
---|---|
UI線程 | 繪製按鈕和輸入框 |
網絡線程 | 處理網絡請求和堆棧 |
存儲線程 | 控制對文件的讀取 |
輸入url是由UI線程控制的
- 處理輸入: isSearchInput? to search engine : to the site you requested
- 開始導航: UI線程控制loading spinner, 網絡線程建立連接
- 讀取res:網絡線程,response’s Content-Type header不一定可信,嗅探MIME TYPE判斷響應體的格式,安全檢查,CORB。
- html ----> 交給renderer進程渲染
- zip ----> 下載管理器download
- UI線程找到一個渲染進程,優化:在步驟2的同時,UI線程主動去找或者開啓一個渲染進程。
- commit navigation:瀏覽器進程和渲染進程IPC,完成導航。地址欄、session 歷史更新。
導航commit後,渲染進程結束渲染,所有onload事件都觸發、執行完畢後,IPC告訴瀏覽器進程,UI線程停止loading spinner
導航到別的站點前,會處理所有的beforeunload事件
新導航會新開一個渲染進程,老的渲染新城處理unload事件等
- service worker:在渲染進程中運行,從緩存中加載資源
- 導航預加載:加載資源和開啓service worker同時進行
渲染進程
渲染進程包括:
- 主線程
- service worker/web worker 線程
- compositor thread
- raster thread
link, img 會preload, 但script 會阻塞html繼續解析,直至script被執行完
-
async/defer
-
js module
-
< link rel='proload >
-
先計算每個node 的樣式,主線程再算出整體的佈局樹,包括x,y 座標和盒模型的大小
-
layout tree 類似dom tree,但只包含頁面可見部分的結構。display: none 會被忽略。visibility: hidden不會,僞元素如果有content也不會。
-
這之後要決定繪製的順序(層次)。z-index。主線程會生成paint records,一條條記錄繪製的順序。
-
repaint 很費cpu。 大多數瀏覽器刷新頁面的速度是60次/秒。最佳實踐是把js操作分成小粒度,用requestAnimationFrame()來調用。
-
組合。turn all above information into pixels(rasterizing/位圖化)。
-
css: will-change 提醒瀏覽器該元素要分成單獨的層。
-
compositor frame 被提交給瀏覽器進程。如果滾動,compositor thread 會生成一個新的compositor frame 給GPU。
componsitor處理輸入交互
對瀏覽器而言,任何用戶的gesture都屬於input
- 瀏覽器進程先獲取到gesture, 再把event的類型和座標發給渲染進程
- 渲染進程找到事件的目標,and run attached event listeners.
- compositor thread marks ‘non-fast scrollable region’, a region of the page that has event handlers attached. 如果事件在該區域發生了,compositor thread就發送input event 給主線程。如果是在其他區域有input, compositor thread 繼續組裝新的frame,不用等main thread.
- 事件委託可能會不必要地擴大了non-fast scrollable region, 造成平滑滾動效果被減弱。想要暗示瀏覽器的組裝器繼續組裝而不是等待主線程,可以這麼做:
// 但實際上,chrome會報錯說can't prevent default inside passive...建議直接用css: touch-action來改。
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
}, {passive: true});
- 離散的事件立即分發,連續事件如wheel, mousewheel, mousemove, pointermove, touchmove 會延遲分發,卡在requestAnimationFrame的每一幀變換上。(input事件的傳達速度多在60fps以上,這是一種優化)
- getCoalescedEvents 來獲取這些被合併的事件
window.addEventListener('pointermove', event => {
const events = event.getCoalescedEvents();
for (let event of events) {
const x = event.pageX;
const y = event.pageY;
// draw a line using x and y coordinates.
}
})