從輸入 URL 到頁面展現,中間發生了什麼?

總體流程如上圖所示,大概可描述爲:javascript

  1. 用戶從瀏覽器裏輸入請求信息
  2. 網絡進程發起 URL 請求
  3. 服務器響應 URL 請求以後,瀏覽器進程開始準備渲染進程
  4. 渲染進程準備完畢後,向渲染進程提交頁面數據(提交文檔階段)
  5. 渲染進程接受完文檔信息後,開始解析頁面和加載子資源,完成頁面渲染

從輸入 URL 到頁面展現詳細過程

用戶輸入

用戶在地址欄輸入關鍵字後,地址欄會根據關鍵字來判斷是搜索內容仍是請求 URLcss

  • 若判斷爲搜索內容,地址欄使用默認搜索引擎,合成新的帶搜索關鍵字的 URL(q 後爲搜索關鍵字) https://www.google.com/search?q=google&xxxxxx
  • 若判斷內容符合 URL 規則,則地址欄會根據輸入內容,合稱爲完整的 URL www.baidu.com => https://www.baidu.com/

輸入關鍵字後回車,標籤上的圖標變成 loading 狀態,此時頁面沒有當即替換爲目標頁面;須要等待提交文檔階段結束,頁面內容纔會被替換。(見下圖) html

開始加載 URL 時瀏覽器狀態

URL 請求過程

頁面請求資源過程,瀏覽器進程經過 IPC(進程間通訊) 把 URL 請求發送至網絡進程,網絡進程收到後發起真正的 URL 請求。具體過程以下:java

  1. 查找本地有無緩存資源。有,返回給瀏覽器進程。
  2. 沒有,直接發起網絡請求。
  3. DNS 解析來獲取請求域名的服務器 IP 地址。
  4. 若請求協議時 HTTPS,還須要創建 TLS(安全傳輸層協議) 鏈接。
  5. 用 IP 地址和服務器創建 TCP 鏈接。(同一域名同時最多隻能創建 6 個鏈接,超過的排隊等待)(經過傳輸層、網絡層等加上相應的頭)
  6. 創建鏈接後,構建請求頭、行等信息,將相關 cookie 等數據附加到請求頭中,而後發送給服務器
  7. 服務器接受到請求信息後,根據請求信息生成響應數據,發給網絡進程。(響應數據又順着應用層——傳輸層——網絡層——網絡層——傳輸層——應用層的順序返回到網絡進程)
  8. 網絡進程接收了響應行、頭後便開始解析響應內容。
  9. 若返回的狀態時是 301 或 302,則瀏覽器須要重定向到其餘 URL,一切從頭開始。
  10. 瀏覽器根據響應頭中的Content-Type字段判斷響應體的數據類型;如果application/octet-stream字節流類型,瀏覽器會按下載類型來處理,該請求會被提交給瀏覽器的下載管理器,流程結束。如果 HTML,則進入下個流程:準備渲染進程

準備渲染進程 & 提交文檔

不一樣主域名的頁面使用不一樣的渲染進程,主域名相同的使用同一個渲染進程;web

  1. 渲染進程準備完畢後,瀏覽器進程發出提交文檔的消息。
  2. 渲染進程收到後,會和網絡進程創建傳輸數據的管道。
  3. 傳輸完畢後,渲染進程會返回「確認提交」的消息給瀏覽器進程。
  4. 瀏覽器進程收到確認的消息後,會更新瀏覽器界面狀態(安全狀態、URL、前進後退的歷史狀態)並更新 web 頁面。(此時 web 頁面是空白頁面)
  5. 導航流程結束,進入渲染階段。
    瀏覽器導航完成狀態

渲染階段

按照渲染的時間順序,流水線可分爲: 構建 DOM 樹 -> 樣式計算 -> 佈局 -> 分層 -> 繪製 -> 分塊 -> 光柵化 -> 合成等階段。chrome

每一個階段的流程以下:接收輸入的內容 -> 處理 -> 輸出內容瀏覽器

構建 DOM 樹

接收 html 文件 -> html 解析器解析 -> 輸出樹狀解構的 DOM緩存

樣式計算

大致流程:接受 css 文本 -> 計算出 dom 節點的具體樣式 -> 輸出計算完成後的 DOM 樣式安全

具體步驟以下:服務器

  • 接收 css 文本,將其轉爲瀏覽器可理解的結構:stylesheets css 三種來源:內聯、外部引入、style

  • 轉換 stylesheets 中屬性值,使其標準化

body { font-size: 2em } 
p {color:blue;} 
div { font-weight: bold}

// 標準化後
body { font-size: 32px } 
p {color: rgb(0,0,255);} 
div { font-weight: 700}
複製代碼
  • 計算出 DOM 樹中每一個節點的具體樣式 計算遵照 css 繼承、層疊兩個規則,以下圖:
    計算後的 DOM 樣式

全部子節點都繼承父節點的樣式。計算完後輸出每一個 DOM 節點的樣式(保存在 ComputedStyle 解構內,可在 elements -> computed 標籤下查看)

佈局

通過上面過程,有了 DOM 樹和樹中元素的樣式,但每一個元素的位置還不肯定,不足以顯示頁面。

Chrome 在佈局階段的任務:建立佈局樹與佈局計算。

  • 建立佈局樹 遍歷 DOM 中的可見節點,將其添加到佈局樹中。不可見的節點會被忽略,好比:head 標籤下的內容,display 爲 none

    佈局樹構造過程

  • 佈局計算

計算佈局樹節點的座標位置,計算完執行佈局時會把運算的結果會從新寫入佈局樹中,因此佈局樹既是輸入內容也是輸出內容;此處設計不合理,chrome 正在重構,下一代佈局系統 LayoutNG 試圖分離輸入與輸出。

分層

佈局完以後不會當即繪製,渲染引擎會爲特定的節點生成專用的圖層,並生成一棵對應的圖層樹(LayerTree)。在開發者工具下的 Layers 標籤能夠看見頁面的分層狀況。

頁面分層
最終的顯示的頁面就是由這些圖層疊加在一塊兒。

  • 佈局樹和圖層樹的關係

    佈局樹和圖層樹的關係
    由上圖可得,不是每一個節點都有圖層,若沒有,就屬於它父節點的圖層。

  • 什麼狀況下,渲染引擎會爲特定節點建立新圖層?

  1. 擁有層疊上下文的屬性會被提高爲單獨的一層。 層疊上下文
    層疊上下文讓 HTML 具備三維概念,按照自身屬性優先級垂直分佈在 z 軸上。
    層疊上下文
  2. 須要剪裁(clip)的地方也會被建立爲圖層。 文字內容比較多,超出顯示區域,這時就產生裁剪。渲染引擎會爲文字部分單首創建一個層,若出現滾動條,滾動條也會被提高爲單獨的層。
    被裁剪的內容、滾動條單獨一層

圖層繪製

畫圖例子: 給你一張紙,畫一個背景藍色,中間爲紅色的圓,再在圓上畫一個綠色三角形,如何畫?

  1. 繪製藍色背景
  2. 在中間繪製紅色圓
  3. 在圓上繪製綠色三角形

圖層繪製與之相似,將一個圖層拆分紅不少小的繪圖指令,把指令按順序組成列表去繪製。(每一個元素背景、前景、邊框都須要單獨的繪圖指令) 開發者工具 Layers 標籤,選中 document 層,可重現繪製過程,以下。

圖層繪製列表

柵格化(raster)操做

GPU 柵格化

繪製列表只用來記錄繪製順序與指令,真正的繪製操做由渲染引擎中的合成線程來完成。如上圖,繪製列表準備好後,主線程會把它提交給合成線程。

有的圖層很大,頁面要使用滾動條滾很久才能到底部;經過視口,用戶只能看到一小部分,這種狀況下若是繪製全部圖層內容,會產生很大開銷且不必。

合成線程如何處理?
合成線程會將圖層劃分紅圖塊(tile),大小一般爲(256256,512512)。

圖層劃分圖塊示意圖
合成線程會按照視口附近的圖塊來優先生成位圖,實際生成位圖的操做由 柵格化(將圖塊轉爲位圖)執行。圖塊是柵格化執行的最小單位。渲染進程維護了一個柵格化的線程池,全部圖塊柵格化都在線程池內執行。

柵格化過程會使用 GPU 來加速生成,生成的位圖被保留在 GPU 內存中。(GPU 操做在 GPU 進程中,柵格化在渲染進程中,這個過程涉及跨進程操做。)

合成與顯示

當全部圖塊都被柵格化,合成進程會生成一個繪製圖塊的命令DrawQuad,而後將該命令提交給瀏覽器進程。

瀏覽器進程裏有個 viz 組件,接收到 DrawQuad 命令後,根據其內容,將頁面繪製到內存,最後再將內存顯示到屏幕上。

完整渲染圖

完整渲染流程總結

  1. 渲染進程將 HTML 內容轉換爲可以讀懂的DOM 樹結構。
  2. 渲染引擎將 CSS 樣式錶轉化爲瀏覽器能夠理解的styleSheets,計算出 DOM 節點的樣式。
  3. 建立佈局樹,並計算元素的佈局信息。
  4. 對佈局樹進行分層,並生成分層樹。
  5. 爲每一個圖層生成繪製列表,提交給合成線程。
  6. 合成線程將圖層分紅圖塊,並在柵格化線程池中將圖塊轉化爲位圖。
  7. 合成線程發送繪製圖塊命令 DrawQuad 給瀏覽器進程。
  8. 瀏覽器進程根據 DrawQuad 消息生成頁面,並顯示到顯示器上。

重排、重繪、合成

  • 重排
    更新元素幾何屬性,開銷最大
    更新元素幾何屬性
  • 重繪
    更新元素顏色
    更新元素背景
  • 合成
    直接合成,避開重排重繪。
    transform 動畫

Some question

  • 同一站點共用一個渲染進程,那假設有2個標籤頁是同一站點,我在A標籤頁面寫個死循環,致使頁面卡死,B頁面是否也是卡死了呢?

    多個頁面公用一個渲染進程,也就意味着多個頁面公用同一個主線程,全部頁面的任務都是在同一個主線程上執行,這些任務包括渲染流程,JavaScript執行,用戶交互的事件的響應等等,可是若是一個標籤頁裏面執行一個死循環,那麼意味着該JavaScript代碼會一直霸佔主線程,這樣就致使了其它的頁面沒法使用該主線程,從而讓全部頁面都失去響應!

  • 若下載 CSS 文件阻塞了,會阻塞 DOM 樹的合成嗎?會阻塞頁面的顯示嗎?
    不會阻塞 DOM 樹合成,html 轉爲 DOM 樹的過程當中,發現文件請求會交給網絡進程去請求文件,渲染進程繼續解析 html。 會阻塞頁面顯示,計算樣式時須要 css 文件資源,若資源阻塞,會等待至網絡超時,network 報出響應錯誤,渲染進程繼續層疊樣式計算。

  • JS 和 CSS 均可能阻塞 DOM 解析

  1. 當從服務器接收HTML頁面的第一批數據時,DOM解析器就開始工做了,在解析過程當中,若是遇到了JS腳本,以下所示:

    <html>
        <body>
            掘金
            <script> document.write("--foo") </script>
        </body>
    </html>
    複製代碼

    那麼DOM解析器會先執行JavaScript腳本,執行完成以後,再繼續往下解析。

  2. 那麼第二種狀況複雜點了,咱們內聯的腳本替換成js外部文件,以下所示:

    <html>
        <body>
            掘金
            <script type="text/javascript" src="foo.js"></script>
        </body>
    </html>
    複製代碼

    這種狀況下,當解析到JavaScript的時候,會先暫停DOM解析,並下載foo.js文件,下載完成以後執行該段JS文件,而後再繼續往下解析DOM。這就是JavaScript文件爲何會阻塞DOM渲染。

  3. 咱們再看第三種狀況,仍是看下面代碼:

    <html>
        <head>
            <style type="text/css" src = "theme.css" /> </head> <body> <p>掘金</p> <script> let e = document.getElementsByTagName('p')[0] e.style.color = 'blue' </script> </body> </html> 複製代碼

    當我在JavaScript中訪問了某個元素的樣式,那麼這時候就須要等待這個樣式被下載完成才能繼續往下執行,因此在這種狀況下,CSS也會阻塞DOM的解析。

  • 渲染進程的幀是什麼?
    能夠拿放電影電影來解釋,一般,電影的幀速是24,也就是說每秒切換24幅畫面,其中的每幅畫面就是一幀。

    理解什麼是幀後,咱們在回過頭看看咱們的頁面。因爲目前大多數設備的屏幕刷新率爲 60 次/秒。所以,若是頁面中有一個動畫、或一個漸變效果、或者用戶正在滾動頁面,那麼瀏覽器渲染動畫的頻率至少要和刷新頻率保持一致,也就是每秒須要更新60次,這樣咱們就能計算出來生成每幀的預算只有(1/60)毫秒,也就是16毫秒多一點(1 秒/ 60 = 16.66 毫秒)。若是超過16毫秒,幀率將降低,而且會出現畫面抖動現象,此現象一般被稱爲卡頓,會對用戶體驗產生負面影響。

    因此,若是想要保證畫面的流暢,就須要儘可能下降每幀的渲染時間,因此局部更新流水線顯得很是重要了,能大大減小處理每幀所消耗的時間。

參考資料

瀏覽器工做原理與實踐

相關文章
相關標籤/搜索