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

導航時發生了什麼

這是關於 Chrome 內部工做的 4 篇博客系列的第 2 篇。在上一篇文章中,咱們研究了不一樣的進程和線程如何處理瀏覽器的不一樣部分。在這篇文章中,咱們會更深刻研究每一個進程和線程如何進行通訊以展現網站。前端

讓咱們看一個網絡瀏覽的簡單用例:你在瀏覽器中鍵入 URL,而後瀏覽器從互聯網獲取數據並顯示一個頁面。在這篇文章中,咱們將重點放在用戶請求站點和瀏覽器準備渲染頁面部分 —— 亦即導航。android

它以瀏覽器進程開始

瀏覽器進程

圖 1:頂部是瀏覽器 UI,底部是擁有 UI、網絡和存儲線程的瀏覽器進程圖ios

正如咱們在第 1 部分:CPU、GPU、內存和多進程架構中所述,tab 外的一切都被瀏覽器進程處理。瀏覽器進程有不少線程,例如繪製瀏覽器按鈕和輸入欄的 UI 線程、處理網絡棧以從因特網獲取數據的網絡線程、控制文件訪問的存儲線程等。當你在地址欄中鍵入 URL 時,你的輸入將由瀏覽器進程的 UI 線程處理。git

一個簡單導航

第 1 步:處理輸入

當用戶開始在地址欄鍵入時,UI 線程要問的第一件事是 「這是一次搜索查詢仍是一個 URL 地址?」。在 Chrome 中,地址欄同時也是一個搜索輸入欄,因此 UI 線程須要解析和決定把你的請求發送到搜索引擎,或是你要請求的網站。github

處理用戶輸入

圖 1:UI 線程詢問輸入內容是搜索查詢仍是 URL 地址web

第 2 步:開始導航

當用戶按下 Enter 鍵時,UI 線程啓用網絡調取去獲取站點內容。加載動畫會顯示在標籤頁的一角,網絡線程會經過適當的協議,像 DNS 查找和爲請求創建 TLS 鏈接。後端

導航開始

圖 2:UI 線程告訴網絡線程要導航到 mysite.comapi

在這時,網絡線程可能會收到像 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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索