Bob Cheng
Web

How browser render a page

1. Navigation

  • 用戶輸入 URL (或是點擊連結等)
  • browser 執行 DNS lookup 來獲取對應的 IP address
    • 如果之前有存取過該網頁,有可能會直接從 cache 拿就不用 DNS lookup
    • 如果同一個網頁內的不同資源 (e.g. 字體、圖片) 來自不同 host,則 browser 也會對這些做 DNS lookup
  • browser 對該 IP 做 TCP handshake (3 round trips)
    • HTTPS 則需要再多做一個 TLS negotiation,用來決定加密使用的密碼 (5 round trips)

2. Response

  • browser 傳送 initial HTTP GET request
    • 這個 response 只會包含整個 HTML file 的 first byte (通常是 14 KB)
    • Time to First Byte (TTFB) 的計算方式就是 responseStart - navigationStart

接下來的步驟又稱為 Critical Rendering Path (CRP): DOM → CSSOM → Render Tree → Layout → Paint

3. Parsing

當收到 first byte 後,main thread 就會開始逐行解析 HTML file 並根據情形決定要執行以下四種 task 的哪一個:

  • html parsing
  • css parsing
  • javascript execution
  • event handler

雖然此時 browser 還不一定有完整的 HTML 檔案,不過提前渲染可以減少用戶等待的時間,因此在前 14KB 就提供必要的 CSS 和 HTML 對效能優化很重要

現代的瀏覽器架構 (e.g. chromium) 都是 multi-process 的,不過在單一個 tab 上 (也就是一個網頁) 仍只會有一個 renderer process (main thread) 來進行渲染,所以同時間只能執行一個 task

HTML Parsing

  • 如果碰到的是一般的 html tags,就會將該 html elements 解析成 node 之後加入 DOM tree 裡面

CSS Parsing

  • 當遇到一個 <style> 或是 <link rel="stylesheet" /> 的時候,main thread 就會載入該 CSS 片段並解析
    • 如果是 external 的 stylesheet,則 main thread 會將下載的執行交給其他 thread,等到下載完成後才會通知 main thread 來執行 CSS Parsing
  • 接著會將解析出來的 nodes “疊加” 到 CSSOM 內,為什麼說是 “疊加” 是因為後面的 rules 可能會覆蓋到前面的 rules
    • 所以在還沒有把整個 CSSOM 建構出來前,都不會執行下一步 (render)
  • 另外,Javascript 如果有關於 style 相關的操作,也會 block 直到整個 CSSOM 被建構出來

Javascript Execution

  • 當遇到 <script> 時,main thread 就會下載並執行該 javascript 程式 (blocking)
  • 如果有 defer 屬性,下載會變成 non-blocking 而下載完後會等到整個 DOM 都解析完才執行
  • 如果有 async 屬性,下載則會變成 non-blocking 而等到下載完成時會直接中斷當前 task 來執行
    • 也就是說執行的時候 DOM 可能還沒完全解析完,如果有操作 DOM 的話很容易出錯
    • 而且也無法保證 script 之間的執行順序

Preload Scanner

  • 其實大部分的解析時間都不會很長,反而是下載資源的時間佔了很大部分,因此就有了 preload scanner 來減少等待資源下載的時間。
  • 當 main thread 在逐行解析 HTML 時,preload scanner 會同步執行來查看文件內有 rel="preload" 的資源,並做 non-blocking 的下載

4. Render

4-1. Style (Render Tree)

  • 將 DOM 和 CSSOM 做結合並生成 render tree
  • render tree 只會包含 visible content
    • 也就是說 <head> 裡面的內容通常不會被包進去
    • display: none; 和它的子元素也不會
  • render tree 的重建會發生在 DOM 或 CSSOM 的結構改變時

4-2. Layout

  • browser 會根據 render tree 的每個元素的位置、尺寸等
  • 第一次決定每個元素的位置叫做 layout;後續對部分元素做更新叫做 reflow
  • reflow 會發生在元素的位置、尺寸改變時,舉例來說:
    • viewport 改變時 (因為 vw 等依賴 viewport 的屬性需要重算,flexbox 和 grid 也要重排)
    • 文字內容改變時 (因為可能會影響元素的尺寸)

4-3. Paint

  • 現在知道了每個元素的內容、style、位置、尺寸,接著 browser 就會把每個元素都繪製到螢幕上
    • 繪製又被稱為 rasterization (柵格化),也就是將向量圖形格式轉換成可以顯示在螢幕上的點陣圖
  • 第一次繪製出「與背景顏色不同的 pixels」的時間點叫做 First Paint (FP)
  • 在 FP 之後對部分元素做重新繪製叫做 repaint
  • repaint 會發生在元素的外觀等不影響 layout 的屬性改變時

Composite

  • 在 paint 的時候,browser 會決定哪一些元素需要獨立出來形成一個新的 compositing layer
    • 這些 compositing layer 的元素會使用 GPU 來繪製,而不會占用到 CPU 的資源,而且更快
    • 像是有 transform, opacity 屬性的元素、或是 z-index 不同的元素
    • 或是有 will-change 的元素

      一般而言,browser 在 compositing layer 不用後就會刪除來節省 memory,不過使用 will-change 會告訴 browser 哪些元素會(頻繁)改變,因此 browser 就會維持該元素的 compositing layer 來隨時使用,所以如果毫無節制的使用 will-change 反而會大量占用 memory

    • 具體怎麼分層還是看 browser 本身的優化方式
  • 有了這些 compositing layer,在處理某些 scrolling 或 animation 的時候就不需要對整個頁面做 reflow, repaint,而是針對特定 layer 來 repaint 就好,可以有效的提升性能
  • 當有 compositing layer 時,就需要執行 composite 來組合不同的 layer 並確保順序是正確的

每次重做前面的步驟後,後面的也要重做,也就是說 reflow 後也要 repaint 和 re-composite,因此在確保用戶體驗上,每次重新 render (從 style 到 repaint 完成) 的過程不能超過 16.67ms

5. Interactivity

  • 前面有提到,如果 script 有 defer 屬性,則會等到 DOM 解析完後才會開始執行,這時候如果頁面已經 paint 完成,也就是說用戶可以看到畫面了,但由於 main thread 忙著執行 script,沒有辦法處理用戶的 interaction event,變成頁面卡死的情形
  • Time to Interactive (TTI) 是從第一個 request 到頁面真正可以互動的時間

Reference