在浏览器里,从输入 URL 到页面展示,这中间发生了什么?-学习笔记
參考來源:極客時間-李兵專欄
從圖中可以看出,整個過程需要各個進程之間的配合,瀏覽器進程、渲染進程和網絡進程的職責如下:
- 瀏覽器進程主要負責用戶交互、子進程管理和文件儲存等功能。
- 網絡進程是面向渲染進程和瀏覽器進程等提供網絡下載功能。
- 渲染進程的主要職責是把從網絡下載的 HTML、JavaScript、CSS、圖片等資源解析為可以顯示和交互的頁面。
這個過程可以大致描述為如下。
- 首先,瀏覽器進程接收到用戶輸入的 URL 請求,瀏覽器進程便將該 URL 轉發給網絡進程。
- 然后,在網絡進程中發起真正的 URL 請求。
- 接著網絡進程接收到了響應頭數據,便解析響應頭數據,并將數據轉發給瀏覽器進程。
- 瀏覽器進程接收到網絡進程的響應頭數據之后,發送“提交導航 (CommitNavigation)”消息到渲染進程;
- 渲染進程接收到“提交導航”的消息之后,便開始準備接收 HTML 數據,接收數據的方式是直接和網絡進程建立數據管道;
- 最后渲染進程會向瀏覽器進程“確認提交”,這是告訴瀏覽器進程:“已經準備好接受和解析頁面數據了”。
- 瀏覽器進程接收到渲染進程“提交文檔”的消息之后,便開始移除之前舊的文檔,然后更新瀏覽器進程中的頁面狀態。
用戶發出 URL 請求到頁面開始解析的這個過程,就叫做導航
從輸入 URL 到頁面展示
1. 用戶輸入
當用戶在地址欄中輸入一個查詢關鍵字時,地址欄會判斷輸入的關鍵字是搜索內容,還是請求的 URL。
- 如果是搜索內容,地址欄會使用瀏覽器默認的搜索引擎,來合成新的帶搜索關鍵字的 URL。
- 如果判斷輸入內容符合 URL 規則,比如輸入的是 time.geekbang.org,那么地址欄會根據規則,把這段內容加上協議,合成為完整的 URL,如 https://time.geekbang.org。
當用戶輸入關鍵字并鍵入回車之后,這意味著當前頁面即將要被替換成新的頁面,不過在這個流程繼續之前,瀏覽器還給了當前頁面一次執行 beforeunload 事件的機會,beforeunload 事件允許頁面在退出之前執行一些數據清理操作,還可以詢問用戶是否要離開當前頁面,比如當前頁面可能有未提交完成的表單等情況,因此用戶可以通過 beforeunload 事件來取消導航,讓瀏覽器不再執行任何后續工作。當前頁面沒有監聽 beforeunload 事件或者同意了繼續后續流程,當瀏覽器剛開始加載一個地址之后,標簽頁上的圖標便進入了加載狀態。但此時圖中頁面顯示的依然是之前打開的頁面內容,并沒立即替換為極客時間的頁面。因為需要等待提交文檔階段,頁面內容才會被替換。
2. URL 請求過程
接下來,便進入了頁面資源請求過程。這時,瀏覽器進程會通過進程間通信(IPC)把 URL 請求發送至網絡進程,網絡進程接收到 URL 請求后,會在這里發起真正的 URL 請求流程。
- 首先,網絡進程會查找本地緩存是否緩存了該資源。如果有緩存資源,那么直接返回資源給瀏覽器進程;如果在緩存中沒有查找到資源,那么直接進入網絡請求流程。這請求前的第一步是要進行 DNS 解析,以獲取請求域名的服務器 IP 地址。如果請求協議是 HTTPS,那么還需要建立 TLS 連接。
- 接下來就是利用 IP 地址和服務器建立 TCP 連接。連接建立之后,瀏覽器端會構建請求行、請求頭等信息,并把和該域名相關的 Cookie 等數據附加到請求頭中,然后向服務器發送構建的請求信息。服務器接收到請求信息后,會根據請求信息生成響應數據(包括響應行、響應頭和響應體等信息),并發給網絡進程。等網絡進程接收了響應行和響應頭之后,就開始解析響應頭的內容了。(下面服務器返回的響應頭和響應行統稱為響應頭。)
(1)重定向
在接收到服務器返回的響應頭后,網絡進程開始解析響應頭,如果發現返回的狀態碼是 301 或者 302,那么說明服務器需要瀏覽器重定向到其他 URL。這時網絡進程會從響應頭的 Location 字段里面讀取重定向的地址,然后再發起新的 HTTP 或者 HTTPS 請求,一切又重頭開始了。
在導航過程中,如果服務器響應行的狀態碼包含了 301、302 一類的跳轉信息,瀏覽器會跳轉到新的地址繼續導航;如果響應行是 200,那么表示瀏覽器可以繼續處理該請求。
(2)響應數據類型處理
在處理了跳轉信息之后,我們繼續導航流程的分析。URL 請求的數據類型,有時候是一個下載類型,有時候是正常的 HTML 頁面,那么瀏覽器是如何區分它們呢?答案是 Content-Type。Content-Type 是 HTTP 頭中一個非常重要的字段, 它告訴瀏覽器服務器返回的響應體數據是什么類型,然后瀏覽器會根據 Content-Type 的值來決定如何顯示響應體的內容。
需要注意的是,如果服務器配置 Content-Type 不正確,比如將 text/html 類型配置成 application/octet-stream 類型,那么瀏覽器可能會曲解文件內容,比如會將一個本來是用來展示的頁面,變成了一個下載文件。
所以,不同 Content-Type 的后續處理流程也截然不同。如果 Content-Type 字段的值被瀏覽器判斷為下載類型,那么該請求會被提交給瀏覽器的下載管理器,同時該 URL 請求的導航流程就此結束。但如果是 HTML,那么瀏覽器則會繼續進行導航流程。由于 Chrome 的頁面渲染是運行在渲染進程中的,所以接下來就需要準備渲染進程了。
3. 準備渲染進程
默認情況下,Chrome 會為每個頁面分配一個渲染進程,也就是說,每打開一個新頁面就會配套創建一個新的渲染進程。但是,也有一些例外,在某些情況下,瀏覽器會讓多個頁面直接運行在同一個渲染進程中。
那什么情況下多個頁面會同時運行在一個渲染進程中呢?要解決這個問題,我們就需要先了解下什么是同一站點(same-site)。具體地講,我們將“同一站點”定義為根域名(例如,geekbang.org)加上協議(例如,https:// 或者 http://),還包含了該根域名下的所有子域名和不同的端口。
Chrome 的默認策略是,每個標簽對應一個渲染進程。但如果從一個頁面打開了另一個新頁面,而新頁面和當前頁面屬于同一站點的話,那么新頁面會復用父頁面的渲染進程。官方把這個默認策略叫 process-per-site-instance。
總結來說,打開一個新頁面采用的渲染進程策略就是:
- 通常情況下,打開新的頁面都會使用單獨的渲染進程;
- 如果從 A 頁面打開 B 頁面,且 A 和 B 都屬于同一站點的話,那么 B 頁面復用 A 頁面的渲染進程;如果是其他情況,瀏覽器進程則會為 B 創建一個新的渲染進程。
渲染進程準備好之后,還不能立即進入文檔解析狀態,因為此時的文檔數據還在網絡進程中,并沒有提交給渲染進程,所以下一步就進入了提交文檔階段。
4. 提交文檔
所謂提交文檔,就是指瀏覽器進程將網絡進程接收到的 HTML 數據提交給渲染進程,
- 首先當瀏覽器進程接收到網絡進程的響應頭數據之后,便向渲染進程發起“提交文檔”的消息;
- 渲染進程接收到“提交文檔”的消息后,會和網絡進程建立傳輸數據的“管道”;
- 等文檔數據傳輸完成之后,渲染進程會返回“確認提交”的消息給瀏覽器進程;
- 瀏覽器進程在收到“確認提交”的消息后,會更新瀏覽器界面狀態,包括了安全狀態、地址欄的 URL、前進后退的歷史狀態,并更新 Web 頁面。
渲染流程
1. 構建 DOM 樹
瀏覽器無法直接理解和使用 HTML,所以需要將 HTML 轉換為瀏覽器能夠理解的結構——DOM 樹。
構建 DOM 樹的輸入內容是一個非常簡單的 HTML 文件,然后經由 HTML 解析器解析,最終輸出樹狀結構的 DOM。
2. 樣式計算(Recalculate Style)
樣式計算的目的是為了計算出 DOM 節點中每個元素的具體樣式,這個階段大體可分為三步來完成。
1). 把 CSS 轉換為瀏覽器能夠理解的結構
CSS 樣式來源主要有三種: * 通過 link 引用的外部 CSS 文件 * style標記內的 CSS * 元素的 style 屬性內嵌的 CSS和 HTML 文件一樣,瀏覽器也是無法直接理解這些純文本的 CSS 樣式,所以當渲染引擎接收到 CSS 文本時,會執行一個轉換操作,將 CSS 文本轉換為瀏覽器可以理解的結構——styleSheets在控制臺中輸入 document.styleSheets,然后就看到如下圖所示的結構
從圖中可以看出,這個樣式表包含了很多種樣式,已經把那三種來源的樣式都包含進去了。該結構同時具備了查詢和修改功能,這會為后面的樣式操作提供基礎。
2). 轉換樣式表中的屬性值,使其標準化
body { font-size: 2em } p {color:blue;} span {display: none} div {font-weight: bold} div p {color:green;} div {color:red; }可以看到上面的 CSS 文本中有很多屬性值,如 2em、blue、bold,這些類型數值不容易被渲染引擎理解,所以需要將所有值轉換為渲染引擎容易理解的、標準化的計算值,這個過程就是屬性值標準化。
3). 計算出 DOM 樹中每個節點的具體樣式
這就涉及到 CSS 的繼承規則和層疊規則了。
首先是 CSS 繼承。CSS 繼承就是每個 DOM 節點都包含有父節點的樣式。
從圖中可以看出,所有子節點都繼承了父節點樣式。比如 body 節點的 font-size 屬性是 20,那 body 節點下面的所有節點的 font-size 都等于 20。
- 首先,可以選擇要查看的元素的樣式(位于圖中的區域 2 中),在圖中的第 1 個區域中點擊對應的元素,就可以在下面的區域查看該元素的樣式了。比如這里我們選擇的元素是
標簽,位于 html.body.div. 這個路徑下面。
- 其次,可以從樣式來源(位于圖中的區域 3 中)中查看樣式的具體來源信息,看看是來源于樣式文件,還是來源于 UserAgent 樣式表。這里需要特別提下 UserAgent 樣式,它是瀏覽器提供的一組默認樣式,如果你不提供任何樣式,默認使用的就是 UserAgent 樣式。
- 最后,可以通過區域 2 和區域 3 來查看樣式繼承的具體過程。
以上就是 CSS 繼承的一些特性,樣式計算過程中,會根據 DOM 節點的繼承關系來合理計算節點樣式。
樣式計算過程中的第二個規則是樣式層疊。層疊是 CSS 的一個基本特征,它是一個定義了如何合并來自多個源的屬性值的算法。它在 CSS 處于核心地位,CSS 的全稱“層疊樣式表”正是強調了這一點。關于層疊的具體規則這里就不做過多介紹了,網上資料也非常多,你可以自行搜索學習。總之,樣式計算階段的目的是為了計算出 DOM 節點中每個元素的具體樣式,在計算過程中需要遵守 CSS 的繼承和層疊兩個規則。這個階段最終輸出的內容是每個 DOM 節點的樣式,并被保存在 ComputedStyle 的結構內。
3. 布局階段
現在,我們有 DOM 樹和 DOM 樹中元素的樣式,但這還不足以顯示頁面,因為我們還不知道 DOM 元素的幾何位置信息。計算出 DOM 樹中可見元素的幾何位置,我們把這個計算過程叫做布局。
Chrome 在布局階段需要完成兩個任務:創建布局樹和布局計算。
1). 創建布局樹
從上圖可以看出,DOM 樹中所有不可見的節點都沒有包含到布局樹中。
為了構建布局樹,瀏覽器大體上完成了下面這些工作:
- 遍歷 DOM 樹中的所有可見節點,并把這些節點加到布局樹中;
- 而不可見的節點會被布局樹忽略掉,如 head 標簽下面的全部內容,再比如 body.p.span 這個元素,因為它的屬性包含 dispaly:none,所以這個元素也沒有被包進布局樹。
2). 布局計算
4. 分層
頁面中有很多復雜的效果,如一些復雜的 3D 變換、頁面滾動,或者使用 z-indexing 做 z 軸排序等,為了更加方便地實現這些效果,渲染引擎還需要為特定的節點生成專用的圖層,并生成一棵對應的圖層樹(LayerTree)
5.圖層繪制
在完成圖層樹的構建之后,渲染引擎會對圖層樹中的每個圖層進行繪制,如果給你一張紙,讓你先把紙的背景涂成藍色,然后在中間位置畫一個紅色的圓,最后再在圓上畫個綠色三角形。你會怎么操作呢?通常,你會把你的繪制操作分解為三步:繪制藍色背景;在中間繪制一個紅色的圓;再在圓上繪制綠色三角形。渲染引擎實現圖層的繪制與之類似,會把一個圖層的繪制拆分成很多小的繪制指令,然后再把這些指令按照順序組成一個待繪制列表,如下圖所示:
6.柵格化(raster)操作
繪制列表只是用來記錄繪制順序和繪制指令的列表,而實際上繪制操作是由渲染引擎中的合成線程來完成的。你可以結合下圖來看下渲染主線程和合成線程之間的關系:
合成線程會按照視口附近的圖塊來優先生成位圖,實際生成位圖的操作是由柵格化來執行的。所謂柵格化,是指將圖塊轉換為位圖。
7.合成和顯示
一旦所有圖塊都被光柵化,合成線程就會生成一個繪制圖塊的命令——“DrawQuad”,然后將該命令提交給瀏覽器進程。瀏覽器進程里面有一個叫 viz 的組件,用來接收合成線程發過來的 DrawQuad 命令,然后根據 DrawQuad 命令,將其頁面內容繪制到內存中,最后再將內存顯示在屏幕上。到這里,經過這一系列的階段,編寫好的 HTML、CSS、JavaScript 等文件,經過瀏覽器就會顯示出漂亮的頁面了。
結合上圖,一個完整的渲染流程大致可總結為如下:
總結
以上是生活随笔為你收集整理的在浏览器里,从输入 URL 到页面展示,这中间发生了什么?-学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浏览器工作原理与实践学习笔记
- 下一篇: new调用函数,new具体做了什么?