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

Mariko Kosaka

轉譯器程序的內部工作

這是探討瀏覽器運作方式的網誌系列文章 (共 4 集),此為系列文章第 3 部分。我們先前已介紹過多程序架構導覽流程。在本文中,我們將說明轉譯器程序內部發生的情況。

轉譯器程序涵蓋網頁效能的許多層面,由於轉譯器程序中進行了許多活動,因此本文只是一般總覽說明。如要深入瞭解相關資訊,請前往 Web Fundamentals 的「效能」部分瞭解詳情。

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

轉譯器程序會負責分頁內發生的所有內容。在轉譯器程序中,主執行緒會處理您傳送給使用者的大部分程式碼。如果您使用網路工作站或服務工作站,有時 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"> 能夠告知瀏覽器目前的瀏覽中絕對需要該資源,而您想要盡快下載。詳情請參閱「資源優先化 - 讓瀏覽器助您一臂之力」。

樣式計算

因為我們能在 CSS 中設定網頁元素樣式,所以使用 DOM 不足以瞭解網頁的外觀。主執行緒會剖析 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 Conference 的演講,並歡迎您立即觀看。

塗料

繪畫遊戲
圖 7:一個人在拿著畫筆的畫布前,懷疑自己是否應先繪製圓形

包含 DOM、樣式和版面配置仍不足以轉譯網頁。假設您要重現一幅畫作您知道元素的大小、形狀和位置,但仍需決定元素的繪製順序。

舉例來說,某些元素可能會針對 z-index 設定,這樣系統就會按照在 HTML 中編寫的元素順序繪製順序,導致算繪錯誤。

Z-index 失敗
圖 8:網頁元素按照 HTML 標記順序顯示,導致算繪的圖片不正確,因為系統並未將 Z-index 納入考量

在這個繪製步驟中,主執行緒會引導版面配置樹狀結構建立繪製記錄。繪製記錄是「先從背景、文字再到矩形」等繪製程序的附註。如果您已使用 JavaScript 在 <canvas> 元素上繪圖,那麼您可能很熟悉這項程序。

油漆記錄
圖 9:主執行緒行經版面配置樹狀結構並產生繪製記錄

更新轉譯管道所費不貲

圖 10:如何產生 DOM+樣式、版面配置和繪製樹狀結構

關於轉譯管道,最重要的重點是,在先前作業的每個步驟結果中,都會用來建立新資料。舉例來說,如果版面配置樹狀結構中有異動,就必須為受影響的文件部分重新產生套用順序。

如果要為元素建立動畫效果,瀏覽器必須在每個頁框之間執行這些作業。我們大部分的螢幕每秒都會重新整理畫面 (每秒 60 個影格)。當您在每個影格中移動項目時,動畫就會呈現平滑的視覺效果。不過,如果動畫沒有移除影格中的影格,頁面就會顯示「卡頓」。

因缺少影格而發生 jage 資源浪費
圖 11:時間軸上的動畫影格

即使您的轉譯作業會跟螢幕重新整理一樣,這些計算都在主執行緒上執行,所以在應用程式執行 JavaScript 時,可能會遭到封鎖。

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

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

要求動畫影格
圖 13:在含有動畫影格的時間軸上執行較小的 JavaScript 區塊

合成中

如何繪製頁面?

圖 14:自然光柵程序的動畫

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

處理這種情況最簡單的方式,是在可視區域內的光柵部分。如果使用者捲動網頁,然後移動光柵頁框,再按光柵來填滿缺少的部分。這是 Chrome 首次發布光柵化時的處理方式。不過,新式瀏覽器會執行更複雜的程序,稱為合成。

什麼是組成

圖 15:合成程序的動畫

合成是一項技術,可將網頁的某些部分分拆成不同圖層、分別將各部分光柵化,並在另一個稱為合成器執行緒的獨立執行緒中合併成一個頁面。如果捲動發生,由於圖層已經光柵化,您只需合併新的影格即可。動畫可透過移動圖層及合併新影格的方式達成。

您可以使用「Layers」面板,在開發人員工具中瞭解網站如何分割成不同圖層。

分割為多個圖層

為了找出元素需要位於哪個圖層,主執行緒會逐步完成版面配置樹狀結構,建立圖層樹狀結構 (這個部分在開發人員工具效能面板中稱為「更新層樹狀結構」)。如果頁面上應該獨立圖層 (例如滑入式選單) 的某些部分沒有出現,您可以在 CSS 中使用 will-change 屬性向瀏覽器提示。

多層樹狀結構
圖 16:主執行緒行經版面配置樹狀結構產生層樹狀結構

您可能想要為每個元素提供圖層,但若融合多餘的圖層,可能會導致運作速度變慢,而不是每個影格只光柵化頁面的微小部分,因此請務必評估應用程式的轉譯效能。如要進一步瞭解相關主題,請參閱「Stick to Compositor-Only Properties and Manage Layer Count」一文。

來自主執行緒的光柵和複合資料

建立圖層樹狀結構並確定繪製順序後,主執行緒會將該資訊提交至合成器執行緒。接著,合成器執行緒會點光每個圖層。圖層可以很大,就像整個頁面的長度一樣,因此合成器執行緒會將其分割成資訊方塊,並將每個圖塊傳送至光柵執行緒。光柵執行緒會光柵執行緒擷取每個資訊方塊,並將其儲存在 GPU 記憶體中。

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

合成器執行緒可以優先處理不同的光柵執行緒,以便先對可視區域 (或鄰近) 中的項目進行光柵化。此外,圖層也有不同解析度的多種圖塊,用於處理放大動作等項目。

圖塊送出後,合成器執行緒會蒐集名為「繪製序列」的資訊方塊資訊,藉此建立合成器框架

繪製四頭形 包含資訊,例如在記憶體中的圖塊位置,以及要在網頁中繪製圖塊時,會將該方塊納入考量的頁面位置。
合成器框架 一組代表頁面頁框的繪製四邊形。

隨後,合成器框架會透過 IPC 提交至瀏覽器程序。此時,您可以透過 UI 執行緒新增另一個合成器影格,用於瀏覽器 UI 變更,或是從其他轉譯器程序的擴充功能轉譯程序。這些合成器影格會傳送到 GPU,並在螢幕上顯示。如果有捲動事件,合成器執行緒會建立另一個合成器影格並傳送至 GPU。

合成
圖 18:建立合成框架的合成器執行緒。影格會先傳送至瀏覽器程序,再傳送至 GPU

合成的好處是無需與主執行緒互動就能完成。因此合成器執行緒無須等待樣式計算或 JavaScript 執行作業。因此,只組合動畫是為了確保流暢效能。如果需要再次計算版面配置或繪製作業,就必須涉及主執行緒。

總結

在這篇文章中,我們探討了從剖析到合成等轉譯管道。希望您現在能閱讀更多網站效能最佳化的相關資訊

在本系列的下一篇文章中,我們將更詳細地介紹合成器執行緒,並說明 mouse moveclick 等使用者輸入內容進入後會發生什麼情況。

你喜歡這則貼文嗎?如果您對日後貼文有任何疑問或建議,歡迎您透過下方的留言專區或在 Twitter 使用 @kosamari 聽到您的想法。

下一步:合成器即將開始輸入內容