一文摸透從輸入URL
到頁面渲染的過程
從輸入URL
到頁面渲染需要Chrome
瀏覽器的多個進程配合,所以我們先來談談現階段Chrome
瀏覽器的多進程架構。
一、Chrome
架構
目前Chrome
采用的是多進程的架構模式,可分為主要的五類進程,分別是:瀏覽器(Browser
)主進程、 GPU
進程、網絡(NetWork
)進程、多個渲染進程和多個插件進程;
- 瀏覽器進程。主要負責界面顯示、用戶交互、子進程管理,同時提供存儲等功能。
- 渲染進程。核心任務是將
HTML
、CSS
和JavaScript
轉換為用戶可以與之交互的網頁,排版引擎Blink
和JavaScript
引擎V8
都是運行在該進程中,默認情況下,Chrome
會為每個Tab
標簽創建一個渲染進程。出於安全考慮,渲染進程都是運行在沙箱模式下。 GPU
進程。其實,Chrome
剛開始發布的時候是沒有GPU
進程的。而GPU
的使用初衷是為了實現3D CSS
的效果,只是隨后網頁、Chrome
的UI
界面都選擇采用GPU
來繪制,這使得GPU
成為瀏覽器普遍的需求。最后,Chrome
在其多進程架構上也引入了GPU
進程。- 網絡進程。主要負責頁面的網絡資源加載,之前是作為一個模塊運行在瀏覽器進程里面的,直至最近才獨立出來,成為一個單獨的進程。
- 插件進程。主要是負責插件的運行,因插件易崩潰,所以需要通過插件進程來隔離,以保證插件進程崩潰不會對瀏覽器和頁面造成影響
了解了Chrome
的多進程架構,就能夠從宏觀上理解從輸入URL
到頁面渲染的過程了,這個過程主要分為導航階段和渲染階段。
二、導航階段
Ⅰ.瀏覽器主進程
1.用戶輸入URL
- 1、瀏覽器進程檢查
url
,組裝協議,構成完整的url
,這時候有兩種情況:- 輸入的是搜索內容:地址欄會使用瀏覽器默認的搜索引擎,來合成新的帶搜索關鍵字的
URL
。 - 輸入的是請求
URL
:地址欄會根據規則,給這段內容加上協議,合成為完整的URL
;
- 輸入的是搜索內容:地址欄會使用瀏覽器默認的搜索引擎,來合成新的帶搜索關鍵字的
- 2、瀏覽器進程通過進程間通信(
IPC
)把url
請求發送給網絡進程;
Ⅱ.網絡進程
2.URL
請求過程
- 3、網絡進程接收到
url
請求后檢查本地緩存是否緩存了該請求資源,如果有則將該資源返回給瀏覽器進程;
這里涉及到瀏覽器與HTTP協議的緩存策略問題,有興趣的可以看這篇文章:詳解HTTP協議
-
4、准備
IP
地址和端口:進行DNS
解析時先查找緩存,沒有再使用DNS
服務器解析,查找順序為:- 瀏覽器緩存;
- 本機緩存;
hosts
文件;- 路由器緩存;
ISP DNS
緩存;DNS
遞歸查詢(本地DNS
服務器 -> 權限DNS
服務器 -> 頂級DNS
服務器 ->13
台根DNS
服務器)
-
5、等待
TCP
隊列:瀏覽器會為每個域名最多維護6
個TCP
連接,如果發起一個HTTP
請求時,這6
個TCP
連接都處於忙碌狀態,那么這個請求就會處於排隊狀態;解決方案:- 采用域名分片技術:將一個站點的資源放在多個(
CDN
)域名下面。 - 升級為
HTTP2
,就沒有6
個TCP
連接的限制了;
- 采用域名分片技術:將一個站點的資源放在多個(
-
6、通過三次握手建立
TCP
連接:- 第一次:客戶端先向服務器端發送一個同步數據包,報文的
TCP
首部中:標志位:同步SYN
為1
,表示這是一個請求建立連接的數據包;序號Seq=x
,x
為所傳送數據的第一個字節的序號,隨后進入SYN-SENT
狀態;
標志位值為
1
表示該標志位有效。- 第二次:服務器根據收到數據包的
SYN
標志位判斷為建立連接的請求,隨后返回一個確認數據包,其中標志位SYN=1
,ACK=1
,序號seq=y
,確認號ack=x + 1
表示收到了客戶端傳輸過來的x
字節數據,並希望下次從x+1
個字節開始傳,並進入SYN-RCVD
狀態;
這里要區分標志位
ACK
和確認號ack
;- 第三次:客戶端收到后,再給服務器發送一個確認數據包,標志位
ACK=1
,序號seq=x+1
,確認號ack=y+1
,隨后進入ESTABLISHED
狀態;
服務器端收到后,也進入
ESTABLISHED
狀態,由此成功建立了TCP
連接,可以開始數據傳送;- 為什么要第三次揮手?避免服務器等待造成資源浪費,具體原因:
如果沒有最后一個數據包確認(第三次握手),
A
先發出一個建立連接的請求數據包,由於網絡原因繞遠路了。A
經過設定的超時時間后還未收到B
的確認數據包。於是發出第二個建立連接的請求數據包,這次網路通暢,
B
的確認數據包也很快就到達A
。於是A
與B
開始傳輸數據;過了一會
A
第一次發出的建立連接的請求數據包到達了B
,B
以為是再次建立連接,所以又發出一個確認數據包。由於A已經收到了一個確認數據包,所以會忽略B
發來的第二個確認數據包,但是B
發出確認數據包之后就要一直等待A
的回復,而A
永遠也不會回復。由此造成服務器資源浪費,這種情況多了
B
計算機可能就停止響應了。 - 第一次:客戶端先向服務器端發送一個同步數據包,報文的
-
7、構建並發送
HTTP
請求信息; -
8、服務器端處理請求;
-
9、客戶端處理響應,首先檢查服務器響應報文的狀態碼:
- 如果是
301/302
表示服務器已更換域名需要重定向,這時網絡進程會從響應頭的Location
字段里面讀取重定向的地址,然后再發起新的HTTP
或者HTTPS
請求,跳回第4
步。 - 如果是
200
,就檢查Content-Type
字段,值為text/html
說明是HTML
文檔,是application/octet-stream
說明是文件下載;
- 如果是
- 10、請求結束,當通用首部字段
Conection
不是Keep-Alive
時,即不為TCP
長連接時,通過四次揮手斷開TCP
連接:
- 第一次:客戶端(主動斷開連接)發送數據包給服務器,其中標志位
FIN=1
,序號位seq=u
,並停止發送數據; - 第二次:服務器收到數據包后,由於還需傳輸數據,無法立即關閉連接,先返回一個標志位
ACK=1
,序號seq=v
,確認號ack=u+1
的數據包; - 第三次:服務器准備好斷開連接后,返回一個數據包,其中標志位
FIN=1
,標志位ACK=1
,序號seq=w
,確認號ack=u+1
; - 第四次:客戶端收到數據包后,返回一個標志位
ACK=1
,序號seq=u+1
,確認號ack=w+1
的數據包。
由此通過四次揮手斷開TCP
連接。
詳細過程參見:詳解TCP連接的“三次握手”與“四次揮手”(上)
- 為什么要四次揮手?由於服務器不能馬上斷開連接,導致
FIN
釋放連接報文與ACK
確認接收報文需要分兩次傳輸,即第二次和第三次"揮手";
3.准備渲染進程
- 11、准備渲染進程:瀏覽器進程檢查當前
url
是否與之前打開了渲染進程的頁面的根域名相同,如果相同,則復用原來的進程,如果不同,則開啟新的渲染進程;
4.提交文檔
- 12、提交文檔:
- 渲染進程准備好后,瀏覽器向渲染進程發起“提交文檔”的消息,渲染進程接收到消息后與網絡進程建立傳輸數據的“管道”
- 渲染進程接收完數據后,向瀏覽器發送“確認提交”
- 瀏覽器進程接收到確認消息后更新瀏覽器界面狀態:安全狀態、地址欄
url
、前進后退的歷史狀態、更新web
頁面
三、渲染階段
在渲染階段通過渲染流水線在渲染進程的主線程和合成線程配合下,完成頁面的渲染;
Ⅲ.渲染進程
渲染進程中的主線程部分
5.構建DOM
樹
-
13、先將請求回來的數據解壓,隨后
HTML
解析器將其中的HTML
字節流通過分詞器拆分為一個個Token
,然后生成節點Node
,最后解析成瀏覽器識別的DOM
樹結構。可以通過
Chrome
調試工具的Console
選項打開控制台輸入document
查看DOM
樹;
渲染引擎還有一個安全檢查模塊叫
XSSAuditor
,是用來檢測詞法安全的。在分詞器解析出來Token
之后,它會檢測這些模塊是否安全,比如是否引用了外部腳本,是否符合CSP
規范,是否存在跨站點請求等。如果出現不符合規范的內容,XSSAuditor
會對該腳本或者下載任務進行攔截。
首次解析HTML
時渲染進程會開啟一個預解析線程,遇到HTML
文檔中內嵌的JavaScript
和CSS
外部引用就會同步提前下載這些文件,下載時間以最后下載完的文件為准。
6.構建CSSOM
-
14、
CSS
解析器將CSS
轉換為瀏覽器能識別的styleSheets
也就是CSSOM
:可以通過控制台輸入document.styleSheets
查看;這里要考慮一下阻塞的問題,由於
JavaScript
有修改CSS
和HTML
的能力,所以,需要先等到CSS
文件下載完成並生成CSSOM
,然后再執行JavaScript
腳本,最后再繼續構建DOM
。由於這種阻塞,導致了解析白屏;
優化方案:
- 移除
js
和css
的文件下載:通過內聯JavaScript
、內聯CSS
;- 盡量減少文件大小:如通過
webpack
等工具移除不必要的注釋,並壓縮js
文件;- 將不進行
DOM
操作或CSS
樣式修改的JavaScript
標記上sync
或者defer
異步引入;- 使用媒體查詢屬性:將大的
CSS
文件拆分成多個不同用途的CSS
文件,只有在特定的場景下才會加載特定的CSS
文件。
可以通過瀏覽器調試工具的Network
面板中的DOMContentLoaded
查看最后生成DOM
樹所需的時間;
7.樣式計算
- 15、轉換樣式表中的屬性值,使其標准化。比如將
em
轉換為px
,color
轉換為rgb
; - 16、計算
DOM
樹中每個節點的具體樣式,這里遵循CSS
的繼承和層疊規則;可以通過Chrome
調試工具的Elements
選項的Computed
查看某一標簽的最終樣式;
8.布局階段
-
17、創建布局樹,遍歷
DOM
樹中的所有節點,去掉所有隱藏的節點(比如head
,添加了display:none
的節點),只在布局樹中保留可見的節點。 -
18、計算布局樹中節點的坐標位置(較復雜,這里不展開);
9.分層
- 19、對布局樹進行分層,並生成分層樹(
Layer Tree
),可以通過Chrome
調試工具的Layer
選項查看。分層樹中每一個節點都直接或間接的屬於一個圖層(如果一個節點沒有對應的層,那么這個節點就從屬於父節點的圖層)
10.圖層繪制
- 20、為每個圖層生成繪制列表(即繪制指令),並將其提交到合成線程。以上操作都是在渲染進程中的主線程中進行的,提交到合成線程后就不阻塞主線程了;
渲染進程中的合成線程部分
11.切分圖塊
21、合成線程將圖層切分成大小固定的圖塊(256x256
或者512x512
)然后優先繪制靠近視口的圖塊,這樣就可以大大加速頁面的顯示速度;
Ⅳ.GPU
進程
12.柵格化操作
- 22、在光柵化線程池中將圖塊轉換成位圖,通常這個過程都會使用
GPU
來加速生成,使用GPU
生成位圖的過程叫快速柵格化,或者GPU
柵格化,生成的位圖被保存在GPU
內存中。
Ⅴ.瀏覽器主進程
13.合成與顯示
- 23、合成:一旦所有圖塊都被光柵化,合成線程就會將它們合成為一張圖片,並生成一個繪制圖塊的命令——“
DrawQuad
”,然后將該命令提交給瀏覽器進程。
注意了:合成的過程是在渲染進程的合成線程中完成的,不會影響到渲染進程的主線程執行;
- 24、顯示:瀏覽器進程里面有一個叫
viz
的組件,用來接收合成線程發過來的DrawQuad
命令,然后根據DrawQuad
命令,將其頁面內容繪制到內存中,最后再將內存顯示在屏幕上。
到這里,經過這一系列的階段,編寫好的HTML
、CSS
、JavaScript
等文件,經過瀏覽器就會顯示出漂亮的頁面了。
參考資料:瀏覽器工作原理與實踐