[譯]從內部瞭解現代瀏覽器(3)

biorz發表於2018-10-06

原文Mariko Kosaka

[譯]從內部瞭解現代瀏覽器(1)

[譯]從內部瞭解現代瀏覽器(2)

[譯]從內部瞭解現代瀏覽器(3)

渲染器程式的內部工作原理

這是本系列文章的的第3部分。 在前2篇,我們介紹了多程式架構和導航流程。在這篇文章中,我們將看看渲染器程式內部發生了什麼。 渲染器程式涉及Web效能的許多方面。 由於渲染器過程中發生了很多事情,因此本文僅作為一般概述。 如果您想深入挖掘裡面的細節,the Performance section of Web Fundamentals中有更多資源。

渲染器程式處理web內容

渲染器程式負責選項卡內的所有事情。 在渲染器程式中,主執行緒處理您傳送給使用者的大部分程式碼。 如果您使用web workerservice worker,JavaScript的一部分可能將由工作執行緒處理。合成器執行緒和光柵執行緒也在渲染器程式內執行,以高效,流暢地呈現頁面。

渲染器程式的核心工作是將HTML,CSS和JavaScript轉換為使用者可以與之互動的網頁。

[譯]從內部瞭解現代瀏覽器(3)

圖1:具有主執行緒,工作執行緒,合成器執行緒和柵格執行緒的渲染器程式

解析

構建dom

當渲染器程式收到導航的提交訊息並開始接收HTML資料時,主執行緒開始解析文字字串(HTML)並將其轉換為文件物件模型(DOM); DOM是瀏覽器頁面的內部表示,是Web開發人員可以通過JavaScript與之互動的資料結構和API。 HTML標準規範將HTML文件解析為DOM。 您可能已經注意到,即使是錯誤的HTML也不會丟擲異常。 例如,缺少結束</p>標記。 像Hi! <b>I'm<i> Chrome </b>!</i>(b標籤在i標籤之前關閉)這樣的錯誤標記會被視為<b>I'm<i> Chrome </i> </b> <i>!</i>。 這是因為HTML規範旨在優雅地處理這些錯誤。 如果您對如何完成這些工作感到好奇,可以閱讀HTML規範中的“解析器中的錯誤處理和異常情況介紹”部分。

子資源載入

網站通常會使用影像,CSS和JavaScript等外部資源。 這些檔案需要從網路或快取中載入。 主執行緒可以在解析構建DOM時逐個請求它們,但為了加快速度,“預載入掃描器”會同時執行。 如果HTML文件中存在[譯]從內部瞭解現代瀏覽器(3)或之類的元素 ,則預載入掃描程式會檢查由HTML解析器生成的標記,並向網路執行緒傳送請求。

[譯]從內部瞭解現代瀏覽器(3)

圖2:主執行緒解析dom並生成dom樹

JavaScript 可以阻止解析

當HTML解析器找到<script>標記時,它會停止解析HTML文件,並且必須載入,解析和執行JavaScript程式碼。 為什麼? 因為JavaScript可以使用像document.write()那樣改變整個DOM結構的東西來改變文件的結構(HTML規範中的模型解析概述有一個很好的圖示)。 這就是HTML解析器在重新解析HTML文件之前必須等待JavaScript執行結束的原因。 如果您對JavaScript執行過程中發生的事情感到好奇,V8團隊有對此的討論和部落格文章

提示瀏覽器如何載入資源

Web開發人員可以通過多種方式提示瀏覽器如何更好地載入資源。 如果您的JavaScript不使用document.write(),則可以向<script>標記新增asyncdefer屬性。 然後,瀏覽器將非同步載入和執行JavaScript程式碼,不會阻止DOM解析。 如果合適,您也可以使用JavaScript模組<link rel =“preload”>是一種通知瀏覽器當前導航必定需要該資源的方法,如果您希望儘快下載。 您可以在資源優先順序 - 瀏覽器幫助您瞭解更多資訊。

計算樣式

擁有DOM並不足以知道頁面的外觀,因為我們可以在CSS中設定頁面元素的樣式。 主執行緒解析CSS並確定每個DOM節點的計算樣式。 這是有關基於CSS選擇器將哪種樣式應用於某個元素的資訊。 您可以在DevTools的computed部分中看到此資訊。

[譯]從內部瞭解現代瀏覽器(3)

圖3:主執行緒解析CSS以新增計算樣式

即使您不提供任何CSS,每個DOM節點都具有它的計算樣式。 <h1>標籤字型大於<h2>標籤,每個元素也都定義了邊距。 這是因為瀏覽器具有預設的樣式表。 如果您想知道Chrome的預設CSS是什麼樣的,您可以在此處檢視原始碼

佈局

現在,渲染器程式知道每個節點的文件和樣式的結構,但這不足以呈現頁面。 想象一下,你正試圖通過手機向朋友描述一幅畫。 “有一個大的紅色圓圈和一個小的藍色方塊”,這些並不足以讓你的朋友瞭解這幅畫的樣子。

[譯]從內部瞭解現代瀏覽器(3)

圖4:一個人通過電話向別人描述一幅畫的樣子

佈局是查詢元素幾何位置的過程。 主執行緒遍歷DOM並計算樣式並建立佈局樹,其中包含x y座標和邊界框大小等資訊。 佈局樹可以是與DOM樹類似的結構,但它僅包含與頁面上可見內容相關的資訊。 如果display:none,則該元素不會是佈局樹的一部分(但是,visibility:hidden的元素在佈局樹中)。 類似地,如果應用具有類似p :: before {content:“Hi!”}之類的偽類,則它也將包含在佈局樹中,即使它不在DOM中。

[譯]從內部瞭解現代瀏覽器(3)

圖5:主執行緒通過計算樣式遍歷DOM樹並生成佈局樹

確定頁面的佈局是一項具有挑戰性的任務。 即使是最簡單的頁面佈局,比如從上到下的標準流,也必須考慮字型的大小以及在哪裡劃分它們,因為它們會影響段落的大小和形狀; 並且影響下一段的位置。

[譯]從內部瞭解現代瀏覽器(3)
圖6:由於換行符而移動的段落的盒子佈局

CSS可以使元素浮動到一側,隱去溢位項,並且更改寫入的方向。 你可以想象,這個佈局階段是一項艱鉅的任務。 在Chrome中,有一整個工程師團隊負責佈局。 如果你想看到他們工作的細節,很少有關於BlinkOn會議的演講被記錄下來,非常有趣。

繪製

擁有DOM,樣式和佈局仍然不足以呈現頁面。假設您正在嘗試重現一幅畫。您知道元素的大小,形狀和位置,但您仍需要判斷繪製它們的順序。

[譯]從內部瞭解現代瀏覽器(3)

圖7:一個拿著畫筆站在畫布前面的人,想知道是應該先畫圓圈還是先畫方塊

例如,可以為某些元素設定z-index,在這種情況下,按HTML中編寫的元素順序繪製將導致不正確的呈現。

[譯]從內部瞭解現代瀏覽器(3)

圖8:頁面元素按HTML標記的順序出現,導致錯誤的渲染影像,因為沒有考慮z-index

在繪製步驟中,主執行緒遍歷佈局樹以建立繪製記錄。 繪畫記錄是一個繪畫過程的註釋,像是“背景優先,然後是文字,然後是矩形”。 如果您使用JavaScript繪製了<canvas>元素,那麼您可能對此過程感到熟悉。

[譯]從內部瞭解現代瀏覽器(3)

圖9:主執行緒遍歷佈局樹並生成繪製記錄

更新渲染流的成本很高

渲染流中最重要的是,在每個步驟中,都使用前面操作的結果來建立新資料 例如,如果佈局樹中的某些內容發生更改,則需要為文件的受影響部分重新生成“繪製”順序

[譯]從內部瞭解現代瀏覽器(3)

圖10:DOM + Style,Layout和Paint樹的生成順序

如果要為元素設定動畫,則瀏覽器必須在每個幀之間執行這些操作。 我們的大多數顯示器每秒重新整理螢幕60次(60 fps); 當你在每一幀移動螢幕時,動畫對人眼來說是平滑的。 但是,如果動畫遺漏了中間的幀,則頁面就會出現“janky”。

[譯]從內部瞭解現代瀏覽器(3)

圖11:時間軸上的動畫幀

即使您的渲染操作與螢幕重新整理保持一致,這些計算也會在主執行緒上執行,這意味著當您的應用程式執行JavaScript時,它可能會被阻塞。

[譯]從內部瞭解現代瀏覽器(3)

圖12:時間軸上的動畫幀,但JavaScript阻止了一幀

您可以將JavaScript操作劃分為小塊,並計劃使用requestAnimationFrame()在每一幀執行。 有關此主題的更多資訊,請參閱優化JavaScript執行。 您也可以在Web Workers中執行JavaScript以避免阻塞主執行緒。

[譯]從內部瞭解現代瀏覽器(3)

圖13:在動畫幀的時間軸上執行的較小的JavaScript塊

合成

你會如何繪製一個頁面?

既然瀏覽器知道了文件的結構、每個元素的樣式、頁面的幾何形狀和繪製順序,那麼它如何繪製頁面呢?將這些資訊轉換成螢幕上的畫素稱為光柵化。 處理這個問題的一種簡單的方法可能是在視口內部使用光柵部件。如果使用者滾動頁面,則移動已光柵化的框架,並通過更多光柵填充缺少的部分。這是Chrome首次釋出時處理光柵化的方式。然而,現代瀏覽器執行一個更復雜的過程,稱為合成。

[譯]從內部瞭解現代瀏覽器(3)

圖14:簡單光柵過程的動畫

什麼是合成

合成是一種將頁面的各個部分分層,分別柵格化,並在稱為合成器執行緒的單獨執行緒中合成為頁面的技術。 如果發生滾動,由於圖層已經光柵化,因此它所要做的就是合成一個新幀。 通過移動圖層和合成新幀,可以以相同的方式實現動畫。 您可以使用“圖層”皮膚檢視您的網站在DevTools中如何劃分為多個圖層。

[譯]從內部瞭解現代瀏覽器(3)

圖15:合成過程的動畫

分層

為了找出哪些元素需要在哪些層中,主執行緒遍歷佈局樹以建立層樹(此部分在DevTools效能皮膚中稱為“更新層樹”)。 如果頁面的某些部分應該是單獨的圖層(如滑入式側面選單)卻沒有得到一個,那麼您可以使用CSS中的will-change屬性提示瀏覽器。

[譯]從內部瞭解現代瀏覽器(3)

圖16:主執行緒遍歷佈局樹生成層樹

您可能想到給每個元素都新增層,但是在過多的層之間進行組合可能會導致操作速度比在每一幀中對頁面的小部分進行光柵化要慢,因此度量應用程式的渲染效能至關重要。有關主題的更多資訊,請參閱Stick to Compositor-Only Properties和Manage Layer Count.

光柵和合成器執行緒不涉及主執行緒

一旦建立了層樹並確定了繪製順序,主執行緒將該資訊提交給合成器執行緒。然後合成執行緒將每個層進行柵格化。一個層可能很大,就像整個頁面的長度一樣,所以合成執行緒將它們分割成塊,並將每個分塊傳送到光柵執行緒。光柵執行緒光柵化每個分塊並將它們儲存在GPU記憶體中。

[譯]從內部瞭解現代瀏覽器(3)

圖17:光柵執行緒建立點陣圖併傳送到GPU

合成器執行緒可以對不同的aster執行緒進行優先順序排序,以便視口(或附近)內的事物可以先被光柵化。 一個層還具有多個不同解析度的分塊,可以處理放大操作等內容。

一旦分塊被光柵化,合成器執行緒會收集平鋪資訊,稱為繪製矩形,以建立一個合成幀

- -
繪製矩形 包含諸如分塊在記憶體中的位置以及在考慮頁面合成的情況下繪製分塊的頁面中的位置等資訊。
合成幀 表示頁面的幀的繪製矩形集合。

然後通過IPC將合成幀提交給瀏覽器程式。 此時,可以從UI執行緒新增另一個合成幀以用於瀏覽器UI更改,或者從其他渲染器程式新增擴充套件。 這些合成幀被髮送到GPU以在螢幕上顯示。 如果發生滾動事件,合成器執行緒會建立另一個合成幀以傳送到GPU。

[譯]從內部瞭解現代瀏覽器(3)

圖18:合成器執行緒建立合成幀。先傳送到瀏覽器程式,然後傳送到GPU

合成的好處是它可以在不涉及主執行緒的情況下完成。 合成器執行緒不需要等待樣式計算或JavaScript執行。 這就是為什麼僅合成動畫被認為是平滑效能的最佳選擇。 如果需要再次計算佈局或繪圖,則必須涉及主執行緒。

總結

在這篇文章中,我們研究了從解析到合成的渲染過程。 希望您現在能夠閱讀更多關於網站效能優化的內容。 在本系列的下一篇也是最後一篇文章中,我們將更詳細地研究合成器執行緒,看看當使用者輸入(如滑鼠移動和單擊)進入時發生了什麼。 你喜歡這個帖子嗎?如果您對以後的帖子有任何問題或建議,我很樂意在下面的評論部分或Twitter上@kosamari收到您的來信。

相關文章