圖解瀏覽器的多進程渲染機制
作者簡介:高揚,來自抖音社區安全前端團隊,團隊負責的工作重點在於降低社區中不良內容與行爲對用戶造成的傷害。
引言
觀察瀏覽器的任務管理器可以發現,打開瀏覽器的一個頁面需要多個進程,包括瀏覽器進程、GPU 進程、網絡進程、渲染進程等,有插件的話還會包括各種插件進程(Chrome 選項 -> 更多工具 -> 任務管理器)。
本文將聚焦於瀏覽器的各個進程間是如何配合,將頁面呈現給用戶的。
📌 你將瞭解到
-
瀏覽器在歷史發展過程中,其進程架構做了哪些調整,爲什麼這樣調整,以及解決了哪些問題?
-
從用戶在地址欄輸入 URL,到頁面渲染完成這之間發生了什麼?迴流和重繪是如何對瀏覽器性能造成影響的?
1. 瀏覽器進程架構的演化
進程和線程
- 進程
-
一個進程就是一個程序的運行實例,它是由用來存放代碼、運行中的數據以及一個執行任務的主線程的內存組成的運行環境;
-
當一個進程關閉後,操作系統會回收爲該進程分配的內存(即使該進程中存在因操作不當導致內存泄漏的線程);
-
進程之間的內容是相互隔離的,這是爲了保護操作系統中的進程互不干擾;
-
當進程之間需要進行通信時,可使用進程間通信(IPC)機制。
- 線程
-
線程是由進程來啓動和管理的,一個應用程序在執行的時候會存在多個子任務的情況,使用多線程並行處理可以大大提升性能;
-
由於線程依附於進程,進程中的任一線程執行出錯也會導致整個進程的崩潰(因爲內存是共享的);
-
同一進程中的多個線程可共享進程所擁有的資源。這種資源包括內存空間,也包括操作系統的權限。
單進程和多進程瀏覽器
單進程瀏覽器
單進程瀏覽器是指所有功能模塊(網絡、插件、JS 運行環境、渲染引擎、頁面等)都運行在同一進程中的瀏覽器(早期的 IE、Firefox)。
單進程瀏覽器存在的問題:
-
【不穩定】
-
瀏覽器中的插件運行在瀏覽器的進程之中,插件的崩潰會引起整個瀏覽器的崩潰;
-
渲染引擎通常也是不穩定的,例如複雜的 JS 腳本也會引起渲染引擎的崩潰,最終導致瀏覽器崩潰。
-
【不流暢】
-
CPU 在某個時間點只能執行某個進程中的某一條線程。由於單進程瀏覽器中所有的頁面的各種模塊都在同一線程中運行,即同一時刻只能有一個模塊可以執行。
-
當一個頁面的某個模塊阻塞了該線程,就會導致整個瀏覽器失去響應;此外,頁面的內存泄漏也會導致單進程瀏覽器使用時間越長,反應越慢。
-
【不安全】
-
線程共享進程資源,因而插件就能獲取到瀏覽器運行過程中的數據,以及擁有和瀏覽器同等的系統權限。
-
例如,插件可使用 C/C++ 編寫,通過插件可以獲取到操作系統任意資源;腳本也可以通過瀏覽器的漏洞來獲取系統權限,引發安全問題。
多進程瀏覽器
Chrome 一問世便使用了多進程的架構,其頁面運行在了單獨的渲染進程中,插件運行在單獨的插件進行中,進程間使用 IPC 進行通信。
瀏覽器的主要進程有哪些:
-
瀏覽器進程。相當於瀏覽器的大腦,主要負責界面顯示、用戶交互、子進程管理,同時提供存儲等功能。
-
渲染進程。核心任務是將 HTML、CSS 和 JavaScript 轉換爲用戶可以與之交互的網頁,排版引擎 Blink 和 JavaScript 引擎 V8 都是運行在該進程中。
默認情況下,Chrome 會爲每個 Tab 標籤創建一個渲染進程。因爲渲染進程所有的內容都是通過網絡獲取的,會存在一些惡意代碼利用瀏覽器漏洞對系統進行攻擊,所以運行在渲染進程裏面的代碼是不被信任的。這也是爲什麼 Chrome 會讓渲染進程運行在安全沙箱裏,就是爲了保證系統的安全。
-
網絡進程。主要負責頁面的網絡資源加載,之前是作爲一個模塊運行在瀏覽器進程裏面的,目前已獨立出來,成爲一個單獨的進程。
-
插件進程。主要是負責插件的運行,因插件易崩潰,所以需要通過插件進程來隔離,以保證插件進程崩潰不會對瀏覽器和頁面造成影響。
-
GPU 進程。當頁面使用了硬件加速時,會使用它來渲染頁面。
其實,Chrome 剛開始發佈的時候是沒有單獨 GPU 進程的,都是放到瀏覽器主進程中的。而 GPU 的使用初衷是爲了實現 3D CSS 的效果,只是隨後網頁、Chrome 的 UI 界面都選擇採用 GPU 來繪製,這使得 GPU 成爲瀏覽器普遍的需求。最後,Chrome 在其多進程架構上也引入了 GPU 進程。
多進程瀏覽器是如何解決單進程瀏覽的問題的:
-
【不穩定】正是由於進程之間相互隔離,當一個頁面或者插件崩潰時只會影響當前的進程,不會影響到瀏覽器和其他頁面。
-
【不流暢】由於 JS 腳本運行在渲染進程中,即使 JS 阻塞了渲染進程,也只會影響當前頁面的渲染,而其他頁面的腳本則會運行在他們自己的渲染進程中,不受影響;此外,內存泄漏導致的不流暢問題也會隨着一個頁面的關閉導致一個進程的結束而解決。
-
【不安全】多進程架構的安全沙箱,相當於是操作系統給進程上了一把鎖,沙箱中的程序可運行不可寫入、不可讀取敏感數據。
多進程瀏覽器存在的問題:
-
更高的資源佔用。以 Chrome 瀏覽器爲例,其將爲每個頁面分配單獨的渲染進程,爲每個插件分配單獨的插件進程,因此會消耗更多內存資源。
-
更復雜的體系架構。瀏覽器各個模塊之間耦合度高、擴展性差目前的架構較難適應新需。
2. 導航流程
從用戶發出 URL 請求到頁面開始解析的過程,叫做導航,是網絡加載流程和渲染流程之間的橋樑。
-
首先,瀏覽器進程接收到用戶輸入的 URL 請求,瀏覽器進程便將該 URL 通過 IPC 轉發給網絡進程。
-
然後,在網絡進程中發起真正的 URL 請求。
-
接着網絡進程接收到了響應頭數據,便解析響應頭數據,並將數據轉發給瀏覽器進程。
-
瀏覽器進程接收到網絡進程的響應頭數據之後,發送 “提交文檔 (CommitNavigation)” 消息到渲染進程。
-
渲染進程接收到 “提交文檔” 的消息之後,便開始準備接收 HTML 數據,接收數據的方式是直接和網絡進程建立數據管道。
-
待網絡進程中文檔數據傳輸完成,渲染進程會向瀏覽器進程 “確認提交”,這是告訴瀏覽器進程:“已經準備好接收和解析頁面數據了”。
-
瀏覽器進程接收到渲染進程 “確認提交” 的消息之後,導航流程就結束了。此時,渲染進程就會開始解析頁面和加載子資源了,瀏覽器進程將開始移除之前舊的文檔,然後更新瀏覽器進程中的頁面狀態。
3. 渲染流程
渲染流水線
渲染流水線可分爲如下幾個子階段:構建 DOM 樹、樣式計算、佈局、分層、繪製、分塊、光柵化和合成。
- 構建 DOM 樹(DOM)
- 瀏覽器無法直接理解和使用 HTML,所以要將其轉化爲瀏覽器能夠理解的解構——經過 HTML 解析器解析,輸出樹狀結構的 DOM
- 樣式計算(Style)
-
目的是計算 DOM 節點中的每個元素具體樣式,可分爲三步
-
渲染引擎把 CSS 文本轉爲瀏覽器可理解的結構——styleSheets 樣式表
-
標準化樣式表中的屬性值。這是由於渲染引擎無法理解 CSS 文本中的各種屬性值,這些值會被轉爲標準化的計算值(例如
{color: blue}
→{color: rgb(0, 0, 225)}
、{font-weight: bold}
→{font-weight: 700}
) -
計算出 DOM 樹中每個節點的具體樣式,計算過程遵守 CSS 的繼承和層疊規則,被保存在 ComputedStyle 結構內
- 佈局階段(Layout)
-
計算 DOM 樹中可見元素的幾何位置信息,包括創建佈局樹和佈局****計算兩個階段
-
創建佈局樹
-
遍歷 DOM 樹中的所有需要渲染節點,並添加到佈局樹中
-
不可見的節點如 head 標籤下的全部內容,display: none 的標籤等會被忽略
-
佈局計算
-
計算 DOM 節點的位置座標,佈局運算的結果會被寫回佈局樹中
- 分層(Layer)
- 針對頁面中的複雜效果,例如複雜的 3D 變換、頁面滾動、z 軸排序等,渲染引擎將爲特定節點生成專用的圖層,並生成一顆圖層樹(Layer Tree)
- 擁有層疊上下文屬性的元素會被提升爲單獨的一層;需要剪裁的地方也會被創建爲單獨的圖層
注意,並非佈局樹的每個節點都包含一個圖層,一個節點可以直接或間接地屬於一個層,例如一個節點可以從屬於父節點的圖層
- 圖層繪製(Paint)
- 渲染引擎會對圖層樹中每個圖層進行繪製,將一個圖層的繪製拆分成很多小的繪製指令,然後把這些指令按順序組成一個待繪製列表
- 柵格化(生成位圖)
-
繪製列表準備好後,主線程將其提交給合成線程,實際的繪製操作由渲染引擎中的合成線程來完成
-
合成線程會根據視口位置和大小,將圖層(layer)劃分爲塊(圖塊 tile)
-
合成線程會按照視口附近的圖塊來優先生成位圖,實際生成位圖的操作由柵格化(將圖塊轉換爲位圖)來執行,圖塊是柵格化的最小單位
-
渲染進程會維護一個柵格化的線程池,柵格化過程通常都會使用 GPU 來加速生成,使用 GPU 生成位圖的過程叫做快速柵格化,生成的位圖被保存在 GPU 內存中
- 合成與顯示
-
所有圖塊都被柵格化後,合成線程將生成繪製圖塊命令 DrawQuad 提交給瀏覽器進程
-
瀏覽器進程中 viz 組件接收 DrawQuad 命令,根據此命令,將其頁面內容繪製在內存中,最後再顯示到屏幕上
流水線總結
-
渲染進程將 HTML 內容轉換爲能夠讀懂的 DOM 樹結構。
-
渲染引擎將 CSS 樣式錶轉化爲瀏覽器可以理解的 styleSheets,計算出 DOM 節點的樣式。
-
創建佈局樹,並計算元素的佈局信息。
-
對佈局樹進行分層,並生成分層樹。
-
爲每個圖層生成繪製列表,並將其提交到合成線程。
-
合成線程將圖層分成圖塊,並在光柵化線程池中將圖塊轉換成位圖。
-
合成線程發送繪製圖塊命令 DrawQuad 給瀏覽器進程。
-
瀏覽器進程根據 DrawQuad 消息生成頁面,並顯示到顯示器上。
迴流和重繪
基於上述瀏覽器的渲染原理,我們可以理解迴流和重繪是如何對瀏覽器性能造成影響的。由於瀏覽器渲染頁面默認使用流式佈局模型,當某個 DOM 或 CSS 幾何屬性發生改變後,文檔流就會受到波動,就需要對 DOM 重新進行計算,重新佈局頁面,引發迴流。
-
更新元素幾何屬性 —— 迴流
-
幾何屬性的修改會觸發瀏覽器重新佈局(Layout & Layer),渲染樹需要重新生成,解析後來的一系列子階段
-
因此迴流需要更新完整的渲染流水線,開銷是最大的
-
更新元素繪製屬性 —— 重繪
-
繪製屬性的修改並沒有導致幾何位置的變化,所以不會導致佈局階段的執行,會直接進入繪製階段,然後執行後來的子階段
-
重繪操作相比迴流省去了佈局和分層階段,效率高於迴流
-
GPU 加速 —— 直接合成
-
如果更改的屬性不需要佈局和繪製,渲染引擎會跳過佈局和繪製,直接進入非主線程——合成線程執行後續合成操作(比如利用 CSS3 的
transform
、opacity
、filter
這些屬性就可以實現合成效果) -
例如,使用 CSS transform 實現動畫效果的渲染流水線如下:一是避開了重繪、迴流,因此避開了佈局和繪製階段;二是直接在非主線程執行合成動畫操作,未佔用主線程資源。相比於重繪和迴流,合成大大提升了繪製效率
Reference
[1] 瀏覽器工作原理與實踐:https://time.geekbang.org/column/intro/100033601
[2] 瀏覽器進程架構的演化:https://zhuanlan.zhihu.com/p/96957235
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/6QR7niKFHY4StFZwDRfyKQ