[譯] 現代瀏覽器內部揭祕(第二部分)

子非發表於2019-03-01

導航時發生了什麼

這是關於 Chrome 內部工作的 4 篇部落格系列的第 2 篇。在上一篇文章中,我們研究了不同的程式和執行緒如何處理瀏覽器的不同部分。在這篇文章中,我們會更深入研究每個程式和執行緒如何進行通訊以展示網站。

讓我們看一個網路瀏覽的簡單用例:你在瀏覽器中鍵入 URL,然後瀏覽器從網際網路獲取資料並顯示一個頁面。在這篇文章中,我們將重點放在使用者請求站點和瀏覽器準備渲染頁面部分 —— 亦即導航。

它以瀏覽器程式開始

瀏覽器程式

圖 1:頂部是瀏覽器 UI,底部是擁有 UI、網路和儲存執行緒的瀏覽器程式圖

正如我們在第 1 部分:CPU、GPU、記憶體和多程式架構中所述,tab 外的一切都被瀏覽器程式處理。瀏覽器程式有很多執行緒,例如繪製瀏覽器按鈕和輸入欄的 UI 執行緒、處理網路棧以從因特網獲取資料的網路執行緒、控制檔案訪問的儲存執行緒等。當你在位址列中鍵入 URL 時,你的輸入將由瀏覽器程式的 UI 執行緒處理。

一個簡單導航

第 1 步:處理輸入

當使用者開始在位址列鍵入時,UI 執行緒要問的第一件事是 “這是一次搜尋查詢還是一個 URL 地址?”。在 Chrome 中,位址列同時也是一個搜尋輸入欄,所以 UI 執行緒需要解析和決定把你的請求傳送到搜尋引擎,或是你要請求的網站。

處理使用者輸入

圖 1:UI 執行緒詢問輸入內容是搜尋查詢還是 URL 地址

第 2 步:開始導航

當使用者按下 Enter 鍵時,UI 執行緒啟用網路調取去獲取站點內容。載入動畫會顯示在標籤頁的一角,網路執行緒會通過適當的協議,像 DNS 查詢和為請求建立 TLS 連線。

導航開始

圖 2:UI 執行緒告訴網路執行緒要導航到 mysite.com

在這時,網路執行緒可能會收到像 HTTP 301 那樣的伺服器重定向頭。這種情況下,網路執行緒會告訴 UI 執行緒,伺服器正在請求重定向。然後,另一個 URL 請求會被啟動。

第 3 步:讀取響應

HTTP 響應

圖 3:包含 Content-Type 的響應頭以及作為實際資料的 payload

一旦開始收到響應主體(payload),網路執行緒會在必要時檢視資料流的前幾個位元組。響應報文的 Content-Type 欄位會宣告資料的型別,但是它有可能會丟失或者錯誤,所以就有了 MIME 型別嗅探來解決這個問題。這是原始碼中評論的“棘手的問題”。你可以閱讀註釋看一下不同瀏覽器是怎麼匹配 content-type 和 payload 的。

如果響應是一個 HTML 檔案,那麼下一步就會把資料傳給渲染程式,但是如果是一個壓縮檔案或是其他檔案,那麼意味著它是一個下載請求,因此需要將資料傳遞給下載管理器。

MIME 型別嗅探

圖 4:網路執行緒詢問一個響應資料是否是從安全網站來的 HTML

此時也會進行 SafeBrowsing 檢查。如果域名和響應資料似乎匹配到一個已知的惡意網站,那麼網路執行緒會顯示一個警告頁面。除此之外,還會發生 Cross Origin Read Blocking(CORB檢查,以確保敏感的跨域資料不被傳給渲染程式。

第 4 步:查詢渲染程式

一旦所有的檢查執行完畢並且網路執行緒確信瀏覽器會導航到請求的站點,網路執行緒會告訴 UI 執行緒所有的資料準備完畢。UI 執行緒會尋找渲染程式去開始渲染 web 頁面。

尋找渲染程式

圖 5:網路執行緒告訴 UI 執行緒去查詢渲染程式

由於網路請求會花費幾百毫秒才獲取迴響應,因此可以應用一個優化措施。當第 2 步 UI 執行緒正傳送一個 URL 請求給網路執行緒時,它已經知道它們會導航到哪個站點。在網路請求的同時,UI 並行地執行緒嘗試主動尋找或開啟一個渲染程式。這樣,如果一切按預期進行,渲染程式在網路執行緒接受到資料時就已經處於待命狀態。如果導航跨域重定向,這個待命程式也許不會被用到,這種情況下也許會用到另一個程式。

第 5 步:提交導航

現在資料和渲染程式已經就緒,瀏覽器程式會傳送一個 IPC(程式間通訊)到渲染程式去提交導航。它也會傳遞資料流,所以渲染程式可以保持接收 HTML 資料。一旦瀏覽器程式收到渲染程式已經提交的確認訊息,導航完畢並且文件載入解析開始。

這時,位址列已經更新,安全指示器和站點設定 UI 會反映新頁面的站點資訊。此標籤頁的 session 歷史記錄會被更新,所以前進/後退按鈕會走向剛導航過的站點。當你關閉標籤頁或者視窗,為了優化 tab/session 的還原,session 歷史被儲存在硬碟上。

提交導航

圖 6:瀏覽器和渲染程式間的 IPC,請求渲染頁面。

額外的步驟:初始載入完畢

一旦導航被提交,渲染程式開始載入資源和渲染頁面。我們將在下一篇文章中講解這個階段發生什麼的細節。一旦渲染程式渲染“完畢”。它會傳送一個 IPC 返回給瀏覽器程式(這會在頁面所有的 frame 的 onload 事件已經觸發和執行完畢後發生)。這時,UI 執行緒停止標籤頁上的載入動畫。

我之所以說“結束”,是因為客戶端 JavaScript 可以在這時之後仍然載入額外的資源並且渲染新檢視。

頁面載入結束

圖 7:渲染程式傳送 IPC 到瀏覽器程式通知頁面“已被載入”

導航到另一個站點

簡單導航已經完畢!但是使用者在位址列輸入另一個 URL 會怎樣呢?好吧,瀏覽器程式會執行相同的步驟來導航到一個不同的站點。但是在它做這個之前,它會檢查當前已經渲染的站點是否關心 beforeunload 事件。

beforeunload 可以在你試圖導航離開或關閉標籤頁時建立“離開此站點?”警告。包括你的 JavaScript 程式碼,所有標籤頁內的東西都是由渲染程式處理,所以當新的導航請求到來時,瀏覽器程式必須要跟當前的渲染程式核對。

注意: 不要新增無條件的 beforeunload 處理程式。它會產生更多延遲,因為處理程式需要在導航開始之前執行。應僅在需要時新增此事件處理程式,例如如果需要警告使用者他們可能會丟失他們在頁面上輸入的資料。

beforeunload 事件處理程式

圖 8:瀏覽器程式向渲染程式傳送 IPC 告訴它將要導航到另一個站點

如果渲染程式已經啟動了導航(像使用者點選一個連結或者客戶端 JavaScript 執行 window.location = "https://newsite.com"),渲染程式會先檢查 beforeunload 事件處理程式。然後,它會像瀏覽器處理啟動導航一樣執行相同的步驟。唯一不同的是導航請求是由渲染程式傳送到瀏覽器程式的。

當新導航到的站點不同於當前已渲染的站點時,會呼叫一個獨立的渲染程式來處理新導航,同時保持當前的渲染程式來處理類似 unload 的事件。有關更多資訊,請檢視頁面生命週期概覽以及如何使用頁面宣告週期 API 掛鉤事件。

新導航與 unload

圖 9:2 個 IPC(從瀏覽器程式到新渲染程式)告知渲染頁面並告知舊渲染程式解除安裝

如果有 Service Worker

最近對導航過程的改變是引入了 service worker。service worker 是一種在你的應用程式碼中編寫網路代理的方法;允許 Web 開發者更好地控制本地快取內容以及何時從網路獲取新資料。如果將 service worker 設定為從快取載入頁面,則無需從網路請求資料。

要記住的重要部分是 Service Worker 是在渲染程式中執行的 JavaScript 程式碼。但是當導航請求進入時,瀏覽器程式如何知道該站點有 service worker?

service worker 作用域檢查

圖 10:瀏覽器程式中的網路執行緒查詢 service worker 作用域

當註冊一個 service worker 時,保持 service worker 的作用域作為一個引用(你可以在這篇文章 The Service Worker Lifecycle 中閱讀更多關於作用域的知識)。當一個導航發生時,網路執行緒用已註冊的 service worker 作用域來檢查域名,如果已經為該 URL 註冊了一個 service worker,UI 執行緒會找一個渲染執行緒來執行 service worker 的程式碼。service worker 可能從快取中載入資料,無需從網路請求資料,或者可以從網路請求新資源。

service worker 導航

圖 11:瀏覽器中的 UI 執行緒啟動渲染程式來處理 service workers;然後,渲染程式中的工作執行緒從網路請求資料

導航預載入

你可以看到,如果 service worker 最終決定從網路請求資料,則瀏覽器程式和渲染器程式之間的往返可能會導致延遲。導航預載入是一種通過與 service worker 啟動並行載入資源來加速此過程的機制。它用一個頭部來標記這些請求,允許伺服器決定為這些請求傳送不同的內容;例如,只更新資料而不是完整文件。

導航預載入

圖 12:瀏覽器程式中的 UI 執行緒啟動渲染程式以在並行啟動網路請求的同時處理 service worker

總結

在這篇文章中,我們研究了導航過程中發生了什麼,以及你的 Web 應用程式碼(例如響應頭和客戶端 JavaScript)如何與瀏覽器互動。瞭解瀏覽器通過網路獲取資料的步驟,可以更容易地理解為什麼開發導航預載入等 API。在下一篇文章中,我們將深入探討瀏覽器如何分析 HTML/CSS/JavaScript 以渲染頁面。

你喜歡這篇文章嗎?如果您對以後的文章有任何問題或建議,歡迎在下面的評論區或在 Twitter @kosamari 上留下您的寶貴意見。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章