圖解浏覽器的多進程渲染機制

程序員咋不禿頭 2024-07-10 06:58:36

作者簡介:高揚,來自抖音社區安全前端團隊,團隊負責的工作重點在于降低社區中不良內容與行爲對用戶造成的傷害。

引言

觀察浏覽器的任務管理器可以發現,打開浏覽器的一個頁面需要多個進程,包括浏覽器進程、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

0 阅读:1

程序員咋不禿頭

簡介:感謝大家的關注