前言
現代瀏覽器十分複雜,頗有執行在作業系統之上的"作業系統"的意思,我們將盡可能用簡單容易理解的例子來簡單概括它主要的工作邏輯。
目錄:
- 程式與執行緒概述;
- 瀏覽器架構;
- 瀏覽器視角下的輸入;
- 頁面如何渲染;
- 如何進行互動;
Part 1. 程式與執行緒概述
計算機的核心是 CPU,它承擔了幾乎所有的計算任務。
你可以把 CPU 想象成是一個工廠,時刻在執行著。
假設這個工廠的電力有限,同一時刻只能供一個車間使用。這也就意味著,一個車間正在使用,其他車間都將不會被使用。
程式就好比車間,是工廠將要執行的任務。潛臺詞就是說,單個 CPU 任意時刻總是隻能執行一個任務。
一個車間可以有很多的工人,它們協同完成同一個任務。
執行緒就是車間裡的工人。
假設工人都是很耗電的機器人,靠著分得工廠給的電力進行任務,每一次給的電力剛好夠完成本次的任務,而工廠同一時刻又只能給一個機器人供電。
這幾乎就是單核 CPU 的工作方式了:同一時刻只能做一個工作。
但你仍然感覺到許多不同的任務正在 "同時" 執行著,這是因為當切換任務的速度足夠快時,你將感知不到 CPU 同一時刻只能做一個工作的特性:
我們的 CPU 就這樣飛速地奔騰著。
每當我們開啟一個應用,就會啟動一個程式。程式也會建立一個或多個執行緒來幫助它完成工作。
作業系統會為程式提供一個可使用的 "一塊" 記憶體,就像開工廠佔地一樣,所有應用程式的狀態資訊都會儲存在該私有記憶體空間中。程式關閉時,相應程式會消失,作業系統也會釋放記憶體。
程式可以請求作業系統啟動另一個程式來執行不同的任務。此時記憶體不同區域會分給新程式。
如果兩個程式需要對話,他們可以通過 程式間通訊(IPC) 來進行。
許多應用程式就是這樣設計的,如果一個工作程式失去響應,該程式就可以在不停止應用程式的情況下靠著其他程式重新啟動。
Part 2. 瀏覽器架構
那麼如何通過程式和執行緒構建 web 瀏覽器呢?
雖然對於如何構建 web 瀏覽器沒有明確的標準,但現在擁有一個導航欄、輸入框、標籤頁這樣類似的設計卻是不同瀏覽器之間默契的共同選擇。
瀏覽器的架構也總體分為兩類:
現在已經很難看到單程式的架構方式了,因為單程式的瀏覽器需要處理的事情太多(網路、渲染、管理外掛等),極不穩定和安全。因此市面上主流的瀏覽器都已經升級為多程式的方式。
就拿 Chrome 舉例來說,就採取了下方的架構方式:
- 最頂層是瀏覽器程式,負責協調處理其他程式模組的任務。
- UI 程式負責控制位址列、標籤頁等;
- 渲染程式控制標籤頁內網站的展示。
- 外掛程式控制站點使用的任意外掛,比如:Flash。
- GPU 程式單獨處理來自不同應用傳送的繪製請求。
- ....
多程式的好處顯而易見。比如當你開啟了三個標籤頁,其中一個崩潰了,你可以關掉它而不會影響其他兩個標籤頁:
並且由於程式的資料是私有的,所以一定程度上能夠保證安全性。
但缺點也顯而易見。我們上面用車間來類比程式,用工人來類比執行緒,顯然「建一座車間」比「招聘一個工人」消耗的資源要大得多——哪怕車間只有一個工人——這裡比較明顯的是對記憶體的消耗。
為了避免過大的記憶體消耗,Chrome 把一些服務做了聚合:
這樣就能一定程度上減少記憶體的開銷。
Part 3. 瀏覽器視角下的輸入
當在瀏覽器中鍵入一個 URL 地址,瀏覽器會做什麼處理呢?
第一步:處理輸入
我們已經習慣了一個連結開啟就對應一個外部網站,但它還可能是瀏覽器本身的設定頁(如 chrome://settings/
),或是本地硬碟的地址(如 Mac 下的 \
):
所以我們的第一步就是要判斷這個輸入到底是個啥:
第二步:開始導航
隨著使用者輸入完畢按下 Enter 鍵,UI 執行緒知道要啟用網路去調取網站的資訊。網路執行緒會負責聯絡目標主機並獲取到資訊:
網路執行緒獲取資訊的過程,發生了很多事,比如 DNS 域名解析、TLS 建立連線等,如果不熟悉可以看看之前的系列文章。
第三步:讀取響應
總之網路執行緒為我們取到了來自網站的響應,大概長這樣:
響應分為 header 和 payload 兩個部分。header 類似於一本書的版權、作者介紹等相關資訊,而 payload 才是真實的資料內容。
瀏覽器需要根據響應頭裡的 Content-Type
來區分對應內容的型別,例如 text/html
時瀏覽器會對內容進行 HTML 解析,image/png
則呼叫圖片渲染器。
然而完全信任網站響應的 Content-Type
是不行的,因為一旦 Content-Type
未指定或者是一個錯誤的值的時候,就會發生未知的錯誤。
所以當收到響應主體(payload)時,網路執行緒會在必要時檢查資料的前幾個位元組,以確保資料內容與 header 裡標識的資料型別(Content-Type)一致。如果不一致,那麼就需要進行 MIME 型別嗅探來猜測該資料的型別。
當響應是一個 HTML 檔案時,此時也會進行安全檢查(SafeBrowsing 檢查)。如果域名和相應資料似乎匹配到了一個已知的惡意網站,那麼網路執行緒會顯示一個警告頁面。
除此之外,還會發生 Cross Origin Read Blocking(CORB)檢查,以確保敏感的跨域資料不被傳給渲染程式。
第四步:查詢渲染程式
一旦所有的檢查執行完畢並且網路執行緒確信瀏覽器會導航到請求的站點,網路執行緒會告訴 UI 執行緒所有的資料準備完畢。UI 執行緒會尋找渲染程式去開始渲染 web 頁面。
由於網路請求會花費幾百毫秒才獲取迴響應,因此可以應用一個優化措施。
當第 2 步 UI 執行緒正傳送一個 URL 請求給網路執行緒時,它已經知道它們會導航到哪個站點。在網路請求的同時,UI 執行緒並行地嘗試主動尋找或開啟一個渲染程式。
這樣,如果一切按預期進行,渲染程式在網路執行緒接受到資料時就已經處於待命狀態。
第五步:提交導航
現在資料和渲染程式已經就緒,瀏覽器程式會傳送一個 IPC(程式間通訊)到渲染程式去提交導航。
這時位址列會更新、標籤頁的歷史記錄也會更新,前進/後退按鈕會走向剛導航過的站點。渲染程式根據 HTML 內容開始解析並渲染頁面。最終您將看到網站設計者設計的網站。
Part 4. 頁面如何渲染
渲染程式涉及 Web 效能的許多方面,流程非常複雜,我們只做必要的理解。如果您想要深入瞭解,可以在 web.dev
找到相關資源。
渲染程式內部包含主執行緒、工作執行緒、合成執行緒和光柵執行緒。
在詳細說明之前,請先想象一個這樣的場景:您站在一副簡單繪畫的面前,如何通過打電話來讓您的朋友知道這幅畫究竟長什麼樣子呢?
如果您真打算這麼做,這裡參考 HTML 解析的過程給您提供一些建議。
首先,圖中的元素以及具體元素的屬性分開描述(如:圖裡有一個圓是元素,圓有多大具體在什麼位置等是屬性):
這樣做的好處是可閱讀性變高了,有哪些元素,以及元素哪些屬性一目瞭然,也利於分別維護和修改。(類似於書的目錄和對應內容一樣)
另外是你可以提煉一些通用的屬性來減少描述:
然後,最好是分層進行描述,因為圖畫是有層次的,光有元素大小、位置等資訊是不夠的:
元素實際上就是我們通常說的 HTML 檔案,HTML 檔案中包含了描述元素屬性的 CSS 樣式檔案。每個瀏覽器對應常見的樣式都會有預設的樣式。
瀏覽器實際上要知道繪製些什麼元素,每個元素屬性如何是要分成三步的:1)通過 HTML 繪製元素樹(俗稱 DOM 樹);2)通過 CSS 檔案繪製樣式樹(俗稱 CSSOM 樹);3)綜合兩顆樹繪製渲染樹(俗稱 Render Tree);
現在瀏覽器知道文件的結構、每個元素的樣式、頁面的幾何形狀和繪製順序,它是如何繪製頁面的?把這些資訊轉換為螢幕上的畫素,我們稱為光柵化。
處理這種情況的一種簡單的方法是,先在光柵化視窗內的畫面,如果使用者滾動頁面,則移動光柵框,並光柵化填充缺少的部分。這就是 Chrome 首次釋出時處理光柵化的方式。
但是,現代瀏覽器會執行一個更復雜的過程,我們稱為合成。
合成是一種將頁面的各個部分分層,分別光柵化,並在稱為合成執行緒的單獨執行緒中合成為頁面的技術。如果發生滾動,由於圖層已經光柵化,因此它所要做的只是合成一個新幀。動畫也可以以相同的方式(移動圖層和合成新幀)實現。
另外需要說明的是如何進行描述是有相當的技巧的。例如「正中心有一個 半徑為 2 的圓」和「正中心有一個 直徑為頁面寬度 50% 的圓」是完全不同的:
如何進行組織描述,這需要網站建設者的經驗。
Part 5. 如何進行互動
在瀏覽器眼中,使用者的一切行為都是輸入。不單單是滾動滑鼠滑輪,或是點選螢幕、按下按鍵等。
對於瀏覽器程式來說只存在事件和對應座標,只有渲染程式知道頁面究竟長啥樣,以及究竟該如何處理事件。瀏覽器程式只負責把事件和座標傳送給渲染程式。
我們也可以編寫自己的邏輯檔案(js 檔案)來監聽某一事件進行對應的處理。然後再統一由渲染程式進行合成。為了瀏覽流暢,瀏覽器需要保證渲染程式的渲染速度與螢幕重新整理率一致(大概每秒 60 幀)。
另外為了降低主執行緒中傳遞過量的呼叫,Chrome 也會把一些連續的事件進行合併。
瀏覽器程式監聽併傳送事件給渲染程式進行渲染,這大概就是瀏覽器互動的基本方式。
後記
瀏覽器的複雜遠不是一篇文章能解釋清楚的,本篇文章也只是想讓大家理解瀏覽器的基本過程和原理。儘可能使用動圖的形式清晰地表達,希望大家能用餐愉快。
本文大量借鑑了 Chrome 官方 developer 分享的系列文章(下2),如果有想更加深入瞭解的小夥伴也可以閱讀更加硬核的瀏覽器工作原理揭祕文章(下4)
至此,我們對瀏覽器已經有了相當的瞭解了。後續也會繼續跟大家一起學習計算機網路的基礎知識,也會嘗試著跟著後端學習路線圖的腳步跟著大家一起學習進階。
這裡是我沒有三顆心臟,歡迎關注公眾號 wmyskxz,2021,與您在 Be Better 的路上共同成長!
參考資料
- 程式與執行緒的簡單解釋 - http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
- 轉載:現代瀏覽器內部揭祕 - https://hasaki.xyz/blog/2020-01-20-轉載現代瀏覽器內部揭祕/
- 深入淺出瀏覽器渲染原理 - https://blog.fundebug.com/2019/01/03/understand-browser-rendering/
- 瀏覽器工作原理幕後揭祕 - https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/
(完)