從 Chrome 看瀏覽器的渲染機制

玩弄心裡的鬼發表於2022-05-13

Chrome 的多程式架構

程式 & 執行緒

在談瀏覽器多程式架構之前,我們先聊聊程式和執行緒的概念。

程式是系統進行資源排程和分配的的基本單位,一個程式可以認為是一個程式的執行例項。啟動一個程式的時候,作業系統會為該程式建立一塊記憶體,用來存放程式碼、執行中的資料和一個執行任務的主執行緒,我們把這樣的一個執行環境叫程式。

執行緒是依附於程式的,是作業系統可識別的最小執行和排程單位,而程式中使用多執行緒並行處理能提升運算效率,多執行緒可以並行處理任務,但是執行緒是不能單獨存在的,它是由程式來啟動和管理的。我們最熟悉的 JS 的執行就是執行緒這個維度。

下面是程式和執行緒間的一些區別:

  • 一個執行緒依附於一個程式,一個程式可以有多個執行緒;
  • 執行緒之間共享程式中的資料。
  • 程式中的任意一執行緒執行出錯,都會導致整個程式的崩潰,而程式之前不會相互影響。
  • 當一個程式關閉之後,作業系統會回收程式所佔用的記憶體。
  • 程式之間的內容相互隔離,只能通過 IPC 通訊。

我覺得知乎上一個用火車比喻程式和執行緒的例子比較形象:程式好比火車,執行緒好比車廂,一輛火車有多節車廂,不同的火車之間互不干擾,但是一節車廂失火會殃及多節車廂。

Chrome 架構

image.png

Chrome 瀏覽器包括:1 個 Browser 主程式、1 個 GPU 程式、1個 Utility 程式、多個 Renderer 程式和多個 Plugin 程式。

  • Browser 主程式(瀏覽器程式):主要負責介面顯示、使用者互動、子程式管理,同時還包含網路請求和檔案訪問等。
  • GPU 程式:與其他程式隔離處理 GPU 任務。
  • Renderer 程式(渲染程式):核心任務是將 HTML、CSS 和 JavaScript 轉換為使用者可以與之互動的網頁,排版引擎 Blink 和 JavaScript 引擎 V8 都是執行在該程式中,預設情況下,Chrome 會為每個 Tab 標籤建立一個渲染程式。出於安全考慮,渲染程式都是執行在沙箱模式下。
  • Plugin 程式:主要是負責外掛的執行,因外掛易崩潰,所以需要通過外掛程式來隔離,以保證外掛程式崩潰不會對瀏覽器和頁面造成影響。

瀏覽器渲染機制

渲染程式的核心工作是將 HTML、CSS 和 JavaScript 轉換為使用者可以與之互動的網頁。在這個工作過程中,輸入的 HTML 經過一些子階段,最後輸出畫素。按照渲染的時間順序,這些子階段大致可分為:構建 DOM 樹、計算樣式、佈局、分層、繪製、分塊、柵格化和合成。

image.png

構建 DOM 樹

當渲染程式開始接收 HTML 資料時,主執行緒開始解析 HTML 並將其轉換為瀏覽器能夠理解的 DOM 樹結構。

image.png

在 DOM 樹的解析過程中,如果遇到 img、css 或者 js 資源時,主執行緒會向 Browser 主程式的網路執行緒傳送請求以獲取對應的資源。

當解析的過程中遇到 <script> 標籤時,主執行緒會暫停 HTML 的解析,從而進行 js 程式碼的載入、解析和執行。因為 js 程式碼中可能涉及對頁面結構的修改,主執行緒必須等待 js 執行才能恢復對 HTML 文件的解析。因此我們可以通過在 <script> 標籤上加上 async 或者 defer 屬性來非同步載入執行 js 程式碼,避免 js 阻塞 HTML 的解析。

樣式計算

樣式計算的目的是為了計算出 DOM 節點中每個元素的具體樣式,在計算過程中需要遵守 CSS 的繼承和層疊兩個規則,這個階段大體可分為三步來完成:

  • 把 CSS 轉換為瀏覽器能夠理解的結構:當渲染引擎接收到 CSS 文字時,會執行一個轉換操作,將 CSS 文字轉換為瀏覽器可以理解的結構——StyleSheets;
  • 轉換樣式表中的屬性值,使其標準化:例如 rem 這些屬性,需要將所有值轉換為渲染引擎容易理解的、標準化的計算值;
  • 計算出 DOM 樹中每個節點的具體樣式

image.png
在瀏覽器中,我們可以通過 Computed 皮膚檢視當前節點的 Computed Style。

image.png

佈局

有了 DOM 樹和 DOM 對應的 Computed Style 之後還不足以顯示頁面,接下來還需要計算出 DOM 樹中可見元素的幾何位置,這個計算過程叫做佈局。

image.png

Chrome 在佈局階段需要完成兩個任務:建立佈局樹和佈局計算。

  • 建立佈局樹:瀏覽器會遍歷 DOM 樹中的所有可見節點,並把這些節點加到佈局樹中,而不可見的節點會被佈局樹忽略掉,如圖中 span 這個元素被設定為 dispaly:none,這個元素會被佈局樹忽略;
  • 佈局計算:有了佈局樹後,瀏覽器會計算佈局節點的座標位置;

分層

有了佈局樹後,對於一些簡單頁面已經具備繪製條件了,但是對於我們現代的頁面來說,有很多複雜的效果,如一些複雜的 3D 轉換、 z-index 做 z 軸排序等,對於這些場景為了頁面展示的正確性,渲染引擎還會為特定的節點生成專用的圖層,並生成一棵對應的圖層樹。

image.png

需要注意的是,並不是每個節點都包含一個圖層,如果一個節點沒有對應的層,那麼這個節點就從屬於父節點所在的圖層。最終每一個節點都會直接或者間接地從屬於一個圖層。

通常滿足下面兩點便會提升為一個單獨的圖層:

  • 擁有層疊上下文屬性的元素會被提升為單獨的一層。層疊上下文
  • 需要剪裁的地方也會被建立為圖層(overflow)。

我們可以在瀏覽器的 layers 皮膚看到當前頁面的分層情況:

image.png

繪製

在確定了 DOM 樹、計算樣式以及佈局樹仍然不足以繪製頁面,這裡還需要有明確的繪製順序,在此過程中主執行緒會遍歷佈局樹並建立繪製記錄。

image.png

同樣,我們可以在瀏覽器的 layers 皮膚看到當前頁面對應圖層的繪製記錄:

image.png

光柵化(柵格化)

在確定了佈局樹並建立了圖層以及對應的繪製順序之後,主執行緒會將資訊提交給合成執行緒。合成執行緒會去光柵化每一個圖層。

由於我們瀏覽器的視口是有限的,但是頁面的長度可能很長,有些圖層可能超過視口很多,而使用者對頁面的感知是視口維度的,一次性渲染整個圖層未免有些浪費,因此合成執行緒會對圖層進行分塊處理

有了圖塊之後,合成執行緒會將每一個圖塊傳送到光柵執行緒(Raster thread),光柵執行緒會光柵化每一個圖塊並存在 GPU 記憶體中。在這個過程中,合成執行緒會優先選擇視口內的圖塊提交給光柵執行緒。

image.png

合成和展示

光柵化完成後,合成執行緒會建立合成幀通過 IPC 通訊提交給瀏覽器程式。瀏覽器程式接收到指令後會將內容繪製在記憶體中並展示在螢幕上。

image.png

至此,從接收 HTML 資料到頁面的展示全流程就結束了。下面我們再結合瀏覽器的渲染流程看下什麼是重排、重繪和合成。

重排、重繪和直接合成

重排

當我們通過 js 或者 css 屬性更新了元素的幾何屬性,例如元素的寬度、高度,此時瀏覽器會重新觸釋出局並重新執行後面全部的渲染流程,因此,重排的開銷是最大的。

image.png

重繪

當我們通過 js 或者 css 更新元素的繪製屬性,例如元素的背景色、文字的顏色等,此時佈局和分層階段被省略,只執行後續的流程,因此重繪的開銷相比重排會小很多。

image.png

直接合成

為什麼我們為了避免重排和重繪而去採用 css3 的 transform 等屬性呢?因為此時整個主執行緒的流程會被全部跳過,執行後續的流程,而後續的流程交給了在執行執行緒、光柵執行緒和 GPU 程式上執行沒有佔據主執行緒的資源,因此效率是最高的。

image.png

參考文章

相關文章