深入瞭解新式網路瀏覽器 (第 3 部分)

Mariko Kosaka

轉譯器程序內部運作

本系列文章的第 3 集是說明瀏覽器的運作方式 (共 4 部分)。我們之前已介紹多程序架構導覽流程。在本文中,我們將說明轉譯器程序會發生什麼事。

轉譯器程序涉及網路效能的許多層面,由於轉譯器程序包含許多資訊,因此本文只是概略說明。如果您有興趣深入瞭解,Web Fundamentals 的效能部分提供了更多資源。

轉譯器程序會處理網路內容

轉譯器程序負責對分頁中發生的一切行為。在轉譯器程序中,主要執行緒會處理您傳送給使用者的大部分程式碼。如果您使用網路工作站或 Service Worker,工作站執行緒有時會處理 JavaScript 的某些部分。合成器和光柵執行緒也會在轉譯器程序中執行,因此能夠有效率地轉譯頁面。

轉譯器程序的核心工作,是將 HTML、CSS 和 JavaScript 轉換成使用者可以互動的網頁。

轉譯器程序
圖 1:具有主執行緒、工作站執行緒、合成器執行緒和內部光柵執行緒的轉譯器程序

剖析

建構 DOM

當轉譯器程序收到導覽的修訂訊息,並開始接收 HTML 資料時,主執行緒會開始剖析文字字串 (HTML) 並轉換成 Document Object Model (DOM)。

DOM 是瀏覽器內部的網頁表示法,以及網頁程式開發人員可透過 JavaScript 互動的資料結構和 API。

將 HTML 文件剖析為 DOM 是由 HTML 標準定義。您可能已註意到,將 HTML 提供給瀏覽器不會擲回錯誤。例如缺少 </p> 結尾標記是有效的 HTML。系統會將 Hi! <b>I'm <i>Chrome</b>!</i> 等錯誤標記 (b 標記在 i 標記之前關閉) 視為您編寫 Hi! <b>I'm <i>Chrome</i></b><i>!</i>。這是因為 HTML 規格旨在妥善處理這些錯誤。如需瞭解這些運作方式,請參閱 HTML 規格的「簡介處理及剖析器中的異常案例」一節。

子資源載入

網站通常會使用圖片、CSS 和 JavaScript 等外部資源。這些檔案必須從網路或快取載入。主要執行緒在剖析建構 DOM 時,「可以」逐一要求這些執行緒,但為了加快速度,系統會並行執行「預先載入掃描器」。如果 HTML 文件中有 <img><link> 之類的內容,預先載入掃描器會檢查 HTML 剖析器產生的符記,並在瀏覽器程序中將要求傳送至網路執行緒。

DOM
圖 2:剖析 HTML 及建構 DOM 樹狀結構的主要執行緒

JavaScript 可能會阻擋剖析

HTML 剖析器找到 <script> 標記時,會暫停剖析 HTML 文件,而且必須載入、剖析及執行 JavaScript 程式碼。為什麼?由於 JavaScript 可以使用 document.write() 等項目變更文件形狀,進而變更整個 DOM 結構 (HTML 規格的剖析模型總覽有一張精美圖表)。因此,HTML 剖析器必須等待 JavaScript 執行完畢,才能繼續剖析 HTML 文件。如果您想知道 JavaScript 執行的情況,V8 團隊提供了講座和網誌文章

提示瀏覽器載入資源的方式

網頁程式開發人員可以透過多種方式向瀏覽器傳送提示,以順利載入資源。 如果 JavaScript 未使用 document.write(),您可以在 <script> 標記中加入 asyncdefer 屬性。接著,瀏覽器會以非同步方式載入並執行 JavaScript 程式碼,且不會封鎖剖析。或者,您也可以視情況使用 JavaScript 模組<link rel="preload"> 可讓瀏覽器告知使用目前瀏覽功能所需的資源,而您想要盡快下載。如要瞭解詳情,請參閱「資源優先順序 - 取得瀏覽器的協助」。

樣式計算

使用 DOM 並不夠瞭解網頁的外觀,這是因為我們可以為 CSS 中的網頁元素設定樣式。主執行緒會剖析 CSS,並確定每個 DOM 節點的計算樣式。本文將介紹如何根據 CSS 選取器,套用至各個元素。您可以在開發人員工具的 computed 部分查看這項資訊。

計算樣式
圖 3:剖析 CSS 以加入計算樣式的主要執行緒

即使您未提供任何 CSS,每個 DOM 節點都有計算樣式。<h1> 標記顯示的尺寸比 <h2> 標記還要大,且每個元素定義的邊界也更大。這是因為瀏覽器有預設的樣式表。如要瞭解 Chrome 的預設 CSS,請參閱這裡的原始碼。

版面配置

現在,轉譯器程序知道文件結構以及每個節點的樣式,但還不足以轉譯頁面。假設您嘗試用手機向朋友說一幅畫作,「有一個大的紅色圓圈和小的藍色正方形」資訊不足,無法讓好友知道那幅畫作的確切樣貌。

人類傳真機遊戲
圖 4:一個人站在畫作前方,連接另一人的電話線

版面配置是尋找元素幾何圖形的程序。主執行緒會逐步檢查 DOM 和運算樣式,並建立版面配置樹狀結構,其中包含 x y 座標和定界框大小等資訊。版面配置樹狀結構可能與 DOM 樹狀結構類似,但只包含頁面顯示內容的相關資訊。如果套用 display: none,該元素就不會是版面配置樹狀結構的一部分 (不過含有 visibility: hidden 的元素位於版面配置樹狀結構中)。同樣地,如果套用內容的虛擬類別包含 p::before{content:"Hi!"} 之類的內容,即使該類別不在 DOM 中,也會納入版面配置樹狀結構中。

版面配置
圖 5:主要執行緒導向計算樣式的 DOM 樹狀結構,並產生版面配置樹狀結構
圖 6:因分行符號變更而移動段落的方塊版面配置

決定網頁的版面配置是一項挑戰。即使是簡單的網頁版面配置 (如由上到下的區塊流) 也必須考量字型的大小和分行的位置,因為這些版面配置會影響段落的大小和形狀,進而影響後續段落的位置。

CSS 可以讓元素浮動至同一側、遮蓋溢位項目,以及變更書寫方向。您可以想像,這個版面配置階段有一項可能的工作。在 Chrome 中,整個工程師團隊負責開發版面配置,如果您想查看工作詳細資料,則系統會錄下 BlinkOn conf 的幾場講座,並不想觀看。

塗料

繪圖遊戲
圖 7:一個人在拿著畫筆的畫布前,想知道他們應該先繪製圓形還是方形

即使已有 DOM、樣式和版面配置,仍不足以轉譯網頁。假設您要重現一幅畫作您知道元素的大小、形狀和位置,但仍然必須判斷這些元素的繪製順序。

舉例來說,系統可能會為特定元素設定 z-index,在這種情況下,如果依照 HTML 中編寫的元素順序進行繪製,將導致算繪錯誤。

Z-index 失敗
圖 8:依 HTML 標記順序顯示網頁元素,導致圖片算繪錯誤,因為 Z-index 並未納入考量

在此繪製步驟中,主要執行緒會逐步走向版面配置樹狀結構,藉此建立繪製記錄。顏料記錄是繪製流程的注意,例如「先背景,然後是文字,再用矩形」。如果您使用 JavaScript 來繪製 <canvas> 元素,應該對這項程序可能就很熟悉。

繪畫記錄
圖 9:主執行緒穿越版面配置樹狀結構,並產生繪製記錄

更新轉譯管道所費不貲

圖 10:DOM+樣式、版面配置和繪製樹狀圖的產生順序

最值得注意的是,在轉譯管道的每個步驟中,上一個作業的結果都會用來建立新的資料。舉例來說,如果版面配置樹狀結構中有內容變更,就必須針對文件的受影響部分重新產生繪製順序。

如要為元素建立動畫效果,瀏覽器必須在每個頁框之間執行這些作業。 大多數的螢幕每秒都會重新整理畫面 60 次 (每秒 60 個影格);在每個影格之間移動項目時,動畫會流暢地顯示人類的眼睛。但如果動畫漏掉了影格,頁面就會顯示「資源浪費」。

因缺漏畫面而造成資源浪費
圖 11:時間軸上的動畫影格

即使您的轉譯作業跟畫面重新整理一樣,這些計算作業會在主執行緒上執行,也就是說,當應用程式執行 JavaScript 時,可能會遭到封鎖。

由 JavaScript 提供的 jage jank
圖 12:時間軸上的動畫影格,但一個影格遭到 JavaScript 封鎖

您可以使用 requestAnimationFrame() 將 JavaScript 作業分割成數個小區塊,並安排在每個影格中執行。如要進一步瞭解這個主題,請參閱「最佳化 JavaScript 執行作業」。您也可以在 Web Worker 中執行 JavaScript,以避免封鎖主執行緒。

要求動畫頁框
圖 13:在含有動畫影格的時間軸上運作的小型 JavaScript 區塊

合成中

您會如何繪製頁面?

圖 14:航海光柵處理的動畫

現在瀏覽器已經知道文件的結構、每個元素的樣式、頁面的幾何圖形及繪製順序,接著該如何繪製頁面?將這項資訊轉化為螢幕上的像素稱為光柵化。

或許有個單純的處理方法,就是在可視區域內光柵部分。如果使用者捲動頁面,然後移動光柵頁框,並更光柵化更多部分,即可填入遺漏的部分。這是 Chrome 在首次發布時處理光柵化的方式。然而,新式瀏覽器會執行一個更複雜的程序,稱為合成。

什麼是合成

圖 15:合成程序的動畫

組合技術可以將網頁的部分內容分為多個圖層、個別光柵化,以及在另一個執行緒 (稱為「合成器執行緒」) 中以頁面方式進行合成。如果捲動發生,由於圖層已光柵化,因此只需要合併新的影格。您可以用同樣的方式移動圖層並組合新影格,達到動畫效果。

您可以採用「Layers」面板,在開發人員工具中將網站分為多個圖層。

分割為多個圖層

為了瞭解哪些元素需要加入哪些圖層,主執行緒會逐步檢查版面配置樹狀結構,藉此建立圖層樹狀結構 (這個部分在開發人員工具效能面板中稱為「更新圖層樹狀結構」)。如果網頁上應分屬單獨圖層的某些部分 (例如滑入側邊選單),您可以在 CSS 中使用 will-change 屬性提示瀏覽器。

樹
圖 16:主要執行緒逐步穿過版面配置樹狀結構產生圖層樹狀結構

您可能會想要為每個元素提供圖層,但組合過多圖層的數量可能會讓作業速度變慢,且每個影格的較小部分需要光柵化,因此請務必評估應用程式的轉譯效能。如要進一步瞭解相關主題,請參閱「保持僅限合成屬性及管理圖層計數」。

將主要執行緒進行光柵化和合成

建立圖層樹狀結構並確定繪製順序後,主要執行緒會將這項資訊修訂至合成器執行緒。接著,合成器執行緒會光柵化每個圖層。圖層可能比頁面整個寬度一樣大,因此合成器執行緒會將這些圖塊分割為圖塊,並將每個圖塊傳送至光柵執行緒。光柵執行緒可以光柵化每個圖塊,並儲存在 GPU 記憶體中。

光柵
圖 17:光柵執行緒建立圖塊點陣圖並傳送至 GPU

合成器執行緒可以優先處理不同的光柵執行緒,因此可以先在可視區域中 (或附近) 進行光柵。圖層也提供多種不同解析度的傾斜度,用於處理放大動作等事項。

將圖塊光柵化後,合成器執行緒會收集稱為「繪圖四邊形」的圖塊資訊,以建立合成器框架

繪製四角 包含資訊,例如圖塊在記憶體中的位置,以及該在頁面哪個位置將圖塊組合納入考量。
合成器框架 代表網頁頁框的一系列繪製四邊。

接著,合成器頁框透過 IPC 提交給瀏覽器程序。此時,瀏覽器使用者介面變更或擴充功能的其他轉譯器程序,可以從 UI 執行緒新增另一個合成器頁框。這些合成器影格會傳送至 GPU,以便在螢幕上顯示。如果有捲動事件,合成器執行緒會建立另一個要傳送至 GPU 的合成器頁框。

合成
圖 18:建立合成框架的合成器執行緒。頁框會傳送至瀏覽器程序,然後傳送至 GPU

組合的優點是,執行時無需與主執行緒。合成器執行緒不需要等待樣式計算或 JavaScript 執行。這也是為什麼僅組合動畫是確保流暢效能的最佳選擇。如果必須重新計算版面配置或繪圖,則必須涉及主要執行緒。

總結

在本文中,我們探討了從剖析到合成的轉譯管道。希望您現在已經開始 進一步瞭解網站的效能最佳化。

在本系列的下一篇和最後一篇文章中,我們會進一步查看合成器執行緒,並看看 mouse moveclick 等使用者輸入內容出現時會發生什麼事。

您喜歡這篇貼文嗎?如果您對日後貼文有任何疑問或建議,歡迎透過下方留言專區或 Twitter 上的 @kosamari 與我們分享,

下一步:輸入內容會用於合成器