精讀《深入瞭解現代瀏覽器二》

黃子毅發表於2021-12-06

Inside look at modern web browser 是介紹瀏覽器實現原理的系列文章,共 4 篇,本次精讀介紹第二篇。

概述

本篇重點介紹了 瀏覽器路由跳轉後發生了什麼,下一篇會介紹瀏覽器的渲染程式是如何渲染網頁的,環環相扣。

在上一篇介紹了,browser process 包含 UI thread、network thread 和 storage thread,當我們在瀏覽器選單欄輸入網址並敲擊回車時,這套動作均由 browser process 的 UI thread 響應。

接下來,按照幾種不同的路由跳轉場景,分別介紹了內部流程。

普通的跳轉

第一步,UI thread 響應輸入,並判斷是否為一個合法的網址,當然輸入的也可能是個搜尋協議,這就會導致分發到另外的服務處理。

第二步,如果第一步輸入的是合法網址,則 UI thread 會通知 network thread 獲取網頁內容,network thread 會尋找合適的協議處理網路請求,一般會通過 DNS 協議 定址,通過 TLS 協議 建立安全連結。如果伺服器返回了比如 301 重定向資訊,network thread 會通知 UI thread 這個資訊,再啟動一遍第二步。

第三步,讀取響應內容,在這一步 network thread 會首先讀取首部一些位元組,即我們常說的響應頭,其中包含 Content-Type 告知返回內容是什麼。如果返回內容是 HTML,則 network thread 會將資料傳送給 renderer process。這一步還會校驗安全性,比如 CORBcross-site 問題。

第四步,尋找 renderer process。一旦所有檢查都完成,network thread 會通知 UI thread 已經準備好跳轉了(注意此時並沒有載入完所有資料,第三步只是檢查了首位元組),UI thread 會通知實力化 renderer process 進行渲染。為了提升效能,UI thread 在通知 network thread 的同時就會實力化一個 renderer process 等著,一旦 network thread 完畢後就可以立即進入渲染階段,如果檢查失敗則丟棄提前例項化的 renderer process。

第五步,確認導航。第四步後,browser process 通過 IPC 向 renderer process 傳送 stream(精讀《web streams》)資料。此時導航會被確認,瀏覽器的各個狀態(比如導航狀態、前進後退歷史)將會被修改,同時為了方便 tab 關閉後快速恢復,會話記錄會被儲存在硬碟。

額外步驟,載入完成。當 renderer process 載入完成後(具體做了什麼下一篇會說明),會通知 browser process onLoad 事件,此時瀏覽器完成最終載入完畢狀態,loading 圓圈也會消失,各類 onLoad 的回撥觸發。注意此時 js 可能會繼續載入遠端資源,但這都是載入狀態完成後的事了。

跳轉到別的網站

當你準備跳轉到別的網站時,在執行普通跳轉流程前,還會響應 beforeunload 事件,這個事件註冊在 renderer process,所以 browser process 需要檢查 renderer process 是否註冊了這個響應。註冊 beforeunload 無論如何都會拖慢關閉 tab 的速度,所以如無必要請勿註冊。

如果跳轉是 js 發出的,那麼執行跳轉就由 renderer process 觸發,browser process 來執行,後續流程就是普通的跳轉流程。要注意的是,當執行跳轉時,會觸發原網站 unload 等事件(網頁生命週期),所以這個由舊的 renderer process 響應,而新網站會建立一個新的 renderer process 處理,當舊網頁全部關閉時,才會銷燬舊的 renderer process。

也就是說,即便只有一個 tab,在跳轉時,也可能會在短時間記憶體在多個 renderer process。

Service Worker

Service Worker 可以在頁面載入前執行一些邏輯,甚至改變網頁內容,但瀏覽器仍然把 Service Worker 實現在了 renderer process 中。

當 Service Worker 被註冊後,會被丟到一個作用域中,當 UI thread 執行時會檢查這個作用域是否註冊了 Service Worker,如果有,則 network thread 會建立一個 renderer process 執行 Service Worker(因為是 js 程式碼)。然後網路響應會被 Service Worker 接管。

但這樣會慢一步,所以 UI thread 往往會在註冊 Service Worker 的同時告訴 network thread 傳送請求,這就是 Navigation Preload 機制。

本文介紹了網頁跳轉時發生的步驟,涉及 browser process、UI thread、network thread、renderer process 的協同。

精讀

也許你會有疑問,為什麼是 renderer process 而不是 renderer thread?因為相比 process(程式)相比 thread(執行緒),之間資料是被作業系統隔離的,為了網頁間無法相互讀取資料(mysite.com 讀取你 baidu.com 正在輸入的賬號密碼),瀏覽器必須為每個 tab 建立一個獨立的程式,甚至每個 iframe 都必須是獨立程式。

讀完第二篇,應該能更深切的感受到模組間合理分工的重要性。

UI thread 處理瀏覽器 UI 的展現與使用者互動,比如當前載入的狀態變化,歷史前進後退,瀏覽器位址列的輸入、校驗與監聽按下 Enter 等事件,但不會涉及諸如傳送請求、解析網頁內容、渲染等內容。

network thread 也僅處理網路相關的事情,它主要關心通訊協議、安全協議,目標就是快速準確的找到網站伺服器,並讀取其內容。network thread 會讀取內容頭做一些前置判斷,讀取內容和 renderer process 做的事情是有一定重合的,但 network thread 讀取內容頭僅為了判斷內容型別,以便交給渲染引擎還是下載管理器(比如一個 zip 檔案),所以為了不讓渲染引擎知道下載管理器的存在,讀取內容頭必須由 network thread 來做。

與 renderer process 的通訊也是由 browser process 來做的,也就是 UI thread、network thread 一旦要建立或與 renderer process 通訊,都會交由它們所在的 browser process 處理。

renderer process 僅處理渲染邏輯,它不關心是從哪來的,比如是網路請求過來的,還是 Service Worker 攔截後修改的,也不關心當前瀏覽器狀態是什麼,它只管按照約定的介面規範,在指定的節點丟擲回撥,而修改應用狀態由其它關心的模組負責,比如 onLoad 回撥觸發後,browser process 處理瀏覽器的狀態就是一個例子。

再比如 renderer process 裡點選了一個新的跳轉連結,這個事情發生在 renderer process,但會交給 browser process 處理,因為每個模組解耦的非常徹底,所以任何複雜工作都能找到一個能響應它的模組,而這個模組也只要處理這個複雜工作的一部分,其餘部分交給其它模組就好了,這就是大型應用維護的祕訣。

所以在瀏覽器執行週期裡,有著非常清晰的邏輯鏈路,這些模組必須事先規劃設計好,很難想象這些模組分工是在開發中逐漸形成的。

最後提到加速優化,Chrome 慣用技巧就是,用資源換時間。即寧可浪費潛在資源,也要讓事物儘可能的併發,這些從提前建立 renderer process、提前發起 network process 都能看出來。

總結

深入瞭解現代瀏覽器二介紹了網頁跳轉時發生的,browser process 與 renderer process 是如何協同的。

也許這篇文章可以幫助你回答 “聊聊在瀏覽器位址列輸入 www.baidu.com 並回車後發生了什麼事兒吧!”

討論地址是:精讀《深入瞭解現代瀏覽器二》· Issue #375 · dt-fe/weekly

如果你想參與討論,請 點選這裡,每週都有新的主題,週末或週一釋出。前端精讀 - 幫你篩選靠譜的內容。

關注 前端精讀微信公眾號

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證

相關文章