一文摸透從輸入URL到頁面渲染的過程

一、Chrome架構(gòu)

瀏覽器進(jìn)程。主要負(fù)責(zé)界面顯示、用戶交互、子進(jìn)程管理,同時提供存儲等功能。 渲染進(jìn)程。核心任務(wù)是將 HTML、CSS 和 JavaScript 轉(zhuǎn)換為用戶可以與之交互的網(wǎng)頁,排版引擎Blink和JavaScript引擎V8都是運行在該進(jìn)程中,默認(rèn)情況下,Chrome會為每個Tab標(biāo)簽創(chuàng)建一個渲染進(jìn)程。出于安全考慮,渲染進(jìn)程都是運行在沙箱模式下。 GPU進(jìn)程。其實,Chrome剛開始發(fā)布的時候是沒有GPU進(jìn)程的。而GPU的使用初衷是為了實現(xiàn)3D CSS的效果,只是隨后網(wǎng)頁、Chrome的UI界面都選擇采用GPU來繪制,這使得GPU成為瀏覽器普遍的需求。最后,Chrome在其多進(jìn)程架構(gòu)上也引入了GPU進(jìn)程。 網(wǎng)絡(luò)進(jìn)程。主要負(fù)責(zé)頁面的網(wǎng)絡(luò)資源加載,之前是作為一個模塊運行在瀏覽器進(jìn)程里面的,直至最近才獨立出來,成為一個單獨的進(jìn)程。 插件進(jìn)程。主要是負(fù)責(zé)插件的運行,因插件易崩潰,所以需要通過插件進(jìn)程來隔離,以保證插件進(jìn)程崩潰不會對瀏覽器和頁面造成影響
二、導(dǎo)航階段
Ⅰ.瀏覽器主進(jìn)程
1.用戶輸入URL
1、瀏覽器進(jìn)程檢查url,組裝協(xié)議,構(gòu)成完整的url,這時候有兩種情況: 輸入的是搜索內(nèi)容:地址欄會使用瀏覽器默認(rèn)的搜索引擎,來合成新的帶搜索關(guān)鍵字的URL。 輸入的是請求URL:地址欄會根據(jù)規(guī)則,給這段內(nèi)容加上協(xié)議,合成為完整的URL; 2、瀏覽器進(jìn)程通過進(jìn)程間通信(IPC)把url請求發(fā)送給網(wǎng)絡(luò)進(jìn)程;
Ⅱ.網(wǎng)絡(luò)進(jìn)程
2.URL請求過程
3、網(wǎng)絡(luò)進(jìn)程接收到url請求后檢查本地緩存是否緩存了該請求資源,如果有則將該資源返回給瀏覽器進(jìn)程;
這里涉及到瀏覽器與HTTP協(xié)議的緩存策略問題,有興趣的可以看這篇文章:詳解HTTP協(xié)議
4、準(zhǔn)備IP地址和端口:進(jìn)行DNS解析時先查找緩存,沒有再使用DNS服務(wù)器解析,查找順序為: 瀏覽器緩存; 本機(jī)緩存; hosts文件; 路由器緩存; ISP DNS緩存; DNS遞歸查詢(本地DNS服務(wù)器 -> 權(quán)限D(zhuǎn)NS服務(wù)器 -> 頂級DNS服務(wù)器 ->?13臺根DNS服務(wù)器) 5、等待TCP隊列:瀏覽器會為每個域名最多維護(hù)6個TCP連接,如果發(fā)起一個HTTP請求時,這 6個 TCP連接都處于忙碌狀態(tài),那么這個請求就會處于排隊狀態(tài);解決方案: 為什么要第三次揮手?避免服務(wù)器等待造成資源浪費,具體原因: 第三次:客戶端收到后,再給服務(wù)器發(fā)送一個確認(rèn)數(shù)據(jù)包,標(biāo)志位ACK=1,序號seq=x+1,確認(rèn)號ack=y+1,隨后進(jìn)入ESTABLISHED狀態(tài); 第二次:服務(wù)器根據(jù)收到數(shù)據(jù)包的SYN標(biāo)志位判斷為建立連接的請求,隨后返回一個確認(rèn)數(shù)據(jù)包,其中標(biāo)志位SYN=1,ACK=1,序號seq=y,確認(rèn)號ack=x + 1表示收到了客戶端傳輸過來的x字節(jié)數(shù)據(jù),并希望下次從x+1個字節(jié)開始傳,并進(jìn)入SYN-RCVD狀態(tài); 第一次:客戶端先向服務(wù)器端發(fā)送一個同步數(shù)據(jù)包,報文的TCP首部中:標(biāo)志位:同步SYN為1,表示這是一個請求建立連接的數(shù)據(jù)包;序號Seq=x,x為所傳送數(shù)據(jù)的第一個字節(jié)的序號,隨后進(jìn)入SYN-SENT狀態(tài); 7、構(gòu)建并發(fā)送HTTP請求信息; 8、服務(wù)器端處理請求; 9、客戶端處理響應(yīng),首先檢查服務(wù)器響應(yīng)報文的狀態(tài)碼: 如果是301/302表示服務(wù)器已更換域名需要重定向,這時網(wǎng)絡(luò)進(jìn)程會從響應(yīng)頭的Location字段里面讀取重定向的地址,然后再發(fā)起新的HTTP或者HTTPS請求,跳回第4步。 如果是200,就檢查Content-Type字段,值為text/html說明是HTML文檔,是application/octet-stream說明是文件下載;

標(biāo)志位值為1表示該標(biāo)志位有效。
這里要區(qū)分標(biāo)志位ACK和確認(rèn)號ack;
如果沒有最后一個數(shù)據(jù)包確認(rèn)(第三次握手),A先發(fā)出一個建立連接的請求數(shù)據(jù)包,由于網(wǎng)絡(luò)原因繞遠(yuǎn)路了。A經(jīng)過設(shè)定的超時時間后還未收到B的確認(rèn)數(shù)據(jù)包。 于是發(fā)出第二個建立連接的請求數(shù)據(jù)包,這次網(wǎng)路通暢,B的確認(rèn)數(shù)據(jù)包也很快就到達(dá)A。于是A與B開始傳輸數(shù)據(jù); 過了一會A第一次發(fā)出的建立連接的請求數(shù)據(jù)包到達(dá)了B,B以為是再次建立連接,所以又發(fā)出一個確認(rèn)數(shù)據(jù)包。由于A已經(jīng)收到了一個確認(rèn)數(shù)據(jù)包,所以會忽略B發(fā)來的第二個確認(rèn)數(shù)據(jù)包,但是B發(fā)出確認(rèn)數(shù)據(jù)包之后就要一直等待A的回復(fù),而A永遠(yuǎn)也不會回復(fù)。 由此造成服務(wù)器資源浪費,這種情況多了B計算機(jī)可能就停止響應(yīng)了。

10、請求結(jié)束,當(dāng)通用首部字段Conection不是Keep-Alive時,即不為TCP長連接時,通過四次揮手?jǐn)嚅_TCP連接:

第一次:客戶端(主動斷開連接)發(fā)送數(shù)據(jù)包給服務(wù)器,其中標(biāo)志位FIN=1,序號位seq=u,并停止發(fā)送數(shù)據(jù); 第二次:服務(wù)器收到數(shù)據(jù)包后,由于還需傳輸數(shù)據(jù),無法立即關(guān)閉連接,先返回一個標(biāo)志位ACK=1,序號seq=v,確認(rèn)號ack=u+1的數(shù)據(jù)包; 第三次:服務(wù)器準(zhǔn)備好斷開連接后,返回一個數(shù)據(jù)包,其中標(biāo)志位FIN=1,標(biāo)志位ACK=1,序號seq=w,確認(rèn)號ack=u+1; 第四次:客戶端收到數(shù)據(jù)包后,返回一個標(biāo)志位ACK=1,序號seq=u+1,確認(rèn)號ack=w+1的數(shù)據(jù)包。
詳細(xì)過程參見:詳解TCP連接的“三次握手”與“四次揮手”(上)
為什么要四次揮手?由于服務(wù)器不能馬上斷開連接,導(dǎo)致FIN釋放連接報文與ACK確認(rèn)接收報文需要分兩次傳輸,即第二次和第三次"揮手";
3.準(zhǔn)備渲染進(jìn)程
11、準(zhǔn)備渲染進(jìn)程:瀏覽器進(jìn)程檢查當(dāng)前url是否與之前打開了渲染進(jìn)程的頁面的根域名相同,如果相同,則復(fù)用原來的進(jìn)程,如果不同,則開啟新的渲染進(jìn)程;
4.提交文檔
12、提交文檔: 渲染進(jìn)程準(zhǔn)備好后,瀏覽器向渲染進(jìn)程發(fā)起“提交文檔”的消息,渲染進(jìn)程接收到消息后與網(wǎng)絡(luò)進(jìn)程建立傳輸數(shù)據(jù)的“管道” 渲染進(jìn)程接收完數(shù)據(jù)后,向瀏覽器發(fā)送“確認(rèn)提交” 瀏覽器進(jìn)程接收到確認(rèn)消息后更新瀏覽器界面狀態(tài):安全狀態(tài)、地址欄url、前進(jìn)后退的歷史狀態(tài)、更新web頁面

三、渲染階段
Ⅲ.渲染進(jìn)程

渲染進(jìn)程中的主線程部分
5.構(gòu)建DOM樹
13、先將請求回來的數(shù)據(jù)解壓,隨后HTML解析器將其中的HTML字節(jié)流通過分詞器拆分為一個個Token,然后生成節(jié)點Node,最后解析成瀏覽器識別的DOM樹結(jié)構(gòu)。 可以通過Chrome調(diào)試工具的Console選項打開控制臺輸入document查看DOM樹;
渲染引擎還有一個安全檢查模塊叫 XSSAuditor,是用來檢測詞法安全的。在分詞器解析出來 Token 之后,它會檢測這些模塊是否安全,比如是否引用了外部腳本,是否符合 CSP 規(guī)范,是否存在跨站點請求等。如果出現(xiàn)不符合規(guī)范的內(nèi)容,XSSAuditor 會對該腳本或者下載任務(wù)進(jìn)行攔截。

6.構(gòu)建CSSOM
14、CSS解析器將CSS轉(zhuǎn)換為瀏覽器能識別的styleSheets也就是CSSOM:可以通過控制臺輸入document.styleSheets查看; 這里要考慮一下阻塞的問題,由于JavaScript有修改CSS和HTML的能力,所以,需要先等到 CSS 文件下載完成并生成 CSSOM,然后再執(zhí)行 JavaScript 腳本,最后再繼續(xù)構(gòu)建 DOM。由于這種阻塞,導(dǎo)致了解析白屏;
優(yōu)化方案:
移除js和css的文件下載:通過內(nèi)聯(lián) JavaScript、內(nèi)聯(lián) CSS; 盡量減少文件大小:如通過 webpack 等工具移除不必要的注釋,并壓縮 js 文件; 將不進(jìn)行DOM操作或CSS樣式修改的 JavaScript 標(biāo)記上 sync 或者 defer異步引入; 使用媒體查詢屬性:將大的CSS文件拆分成多個不同用途的 CSS 文件,只有在特定的場景下才會加載特定的 CSS 文件。


7.樣式計算
15、轉(zhuǎn)換樣式表中的屬性值,使其標(biāo)準(zhǔn)化。比如將em轉(zhuǎn)換為px,color轉(zhuǎn)換為rgb; 16、計算DOM樹中每個節(jié)點的具體樣式,這里遵循CSS的繼承和層疊規(guī)則;可以通過Chrome調(diào)試工具的Elements選項的Computed查看某一標(biāo)簽的最終樣式;

8.布局階段
17、創(chuàng)建布局樹,遍歷DOM樹中的所有節(jié)點,去掉所有隱藏的節(jié)點(比如head,添加了display:none的節(jié)點),只在布局樹中保留可見的節(jié)點。 18、計算布局樹中節(jié)點的坐標(biāo)位置(較復(fù)雜,這里不展開);
9.分層
19、對布局樹進(jìn)行分層,并生成分層樹(Layer Tree),可以通過Chrome調(diào)試工具的Layer選項查看。分層樹中每一個節(jié)點都直接或間接的屬于一個圖層(如果一個節(jié)點沒有對應(yīng)的層,那么這個節(jié)點就從屬于父節(jié)點的圖層)

10.圖層繪制
20、為每個圖層生成繪制列表(即繪制指令),并將其提交到合成線程。以上操作都是在渲染進(jìn)程中的主線程中進(jìn)行的,提交到合成線程后就不阻塞主線程了;

渲染進(jìn)程中的合成線程部分

11.切分圖塊

Ⅳ.GPU進(jìn)程
12.柵格化操作
22、在光柵化線程池中將圖塊轉(zhuǎn)換成位圖,通常這個過程都會使用GPU來加速生成,使用GPU生成位圖的過程叫快速柵格化,或者GPU柵格化,生成的位圖被保存在GPU內(nèi)存中。

Ⅴ.瀏覽器主進(jìn)程
13.合成與顯示
23、合成:一旦所有圖塊都被光柵化,合成線程就會將它們合成為一張圖片,并生成一個繪制圖塊的命令——“DrawQuad”,然后將該命令提交給瀏覽器進(jìn)程。
注意了:合成的過程是在渲染進(jìn)程的合成線程中完成的,不會影響到渲染進(jìn)程的主線程執(zhí)行;
24、顯示:瀏覽器進(jìn)程里面有一個叫viz的組件,用來接收合成線程發(fā)過來的DrawQuad命令,然后根據(jù)DrawQuad命令,將其頁面內(nèi)容繪制到內(nèi)存中,最后再將內(nèi)存顯示在屏幕上。

評論
圖片
表情
