前言
在我們面試過程中,面試官經常會問到這么一個問題,那就是從在瀏覽器地址欄中輸入URL到頁面顯示,瀏覽器到底發生了什么?這個問題看起來是老生常談,但是這個問題回答的好壞,確實可以很好的反映出面試者知識的廣度和深度。
本文從瀏覽器角度來告訴你,URL后輸入后按回車,瀏覽器內部究竟發生了什么,讀完本文后,你將了解到:
瀏覽器架構
進程()是程序的一次執行過程,是一個動態概念,是程序在執行過程中分配和管理資源的基本單位,線程()是CPU調度和分派的基本單位,它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。
簡單的說呢,進程可以理解成正在執行的應用程序,而線程呢,可以理解成我們應用程序中的代碼的執行器。而他們的關系可想而知,線程是跑在進程里面的,一個進程里面可能有一個或者多個線程,而一個線程,只能隸屬于一個進程。
大家都知道,瀏覽器屬于一個應用程序,而應用程序的一次執行,可以理解為計算機啟動了一個進程,進程啟動后,CPU會給該進程分配相應的內存空間,當我們的進程得到了內存之后,就可以使用線程進行資源調度,進而完成我們應用程序的功能。
而在應用程序中,為了滿足功能的需要,啟動的進程會創建另外的新的進程來處理其他任務,這些創建出來的新的進程擁有全新的獨立的內存空間,不能與原來的進程內向內存,如果這些進程之間需要通信,可以通過IPC機制(Inter )來進行。
很多應用程序都會采取這種多進程的方式來工作,因為進程和進程之間是互相獨立的它們互不影響,也就是說,當其中一個進程掛掉了之后,不會影響到其他進程的執行,只需要重啟掛掉的進程就可以恢復運行。
瀏覽器的多進程架構
假如我們去開發一個瀏覽器,它的架構可以是一個單進程多線程的應用程序,也可以是一個使用IPC通信的多進程應用程序。
不同的瀏覽器使用不同的架構,下面主要以為例,介紹瀏覽器的多進程架構。
在中,主要的進程有4個:
這4個進程之間的關系是什么呢?
首先,當我們是要瀏覽一個網頁,我們會在瀏覽器的地址欄里輸入URL,這個時候 會向這個URL發送請求,獲取這個URL的HTML內容,然后將HTML交給 , 解析HTML內容,解析遇到需要請求網絡的資源又返回來交給 進行加載,同時通知 ,需要 加載插件資源,執行插件代碼。解析完成后, 計算得到圖像幀,并將這些圖像幀交給GPU ,GPU 將其轉化為圖像顯示屏幕。
多進程架構的好處
為什么要使用多進程架構呢?
第一,更高的容錯性。當今WEB應用中,HTML,和CSS日益復雜,這些跑在渲染引擎的代碼,頻繁的出現BUG,而有些BUG會直接導致渲染引擎崩潰,多進程架構使得每一個渲染引擎運行在各自的進程中,相互之間不受影響,也就是說,當其中一個頁面崩潰掛掉之后,其他頁面還可以正常的運行不收影響。
第二,更高的安全性和沙盒性()。渲染引擎會經常性的在網絡上遇到不可信、甚至是惡意的代碼,它們會利用這些漏洞在你的電腦上安裝惡意的軟件,針對這一問題,瀏覽器對不同進程限制了不同的權限,并為其提供沙盒運行環境,使其更安全更可靠
第三js實現鼠標移上去顯示詳細信息,更高的響應速度。在單進程的架構中,各個任務相互競爭搶奪CPU資源,使得瀏覽器響應速度變慢,而多進程架構正好規避了這一缺點。
多進程架構優化
之前的我們說到, 的作用是負責一個Tab內的顯示相關的工作,這就意味著,一個Tab,就會有一個 ,這些進程之間的內存無法進行共享,而不同進程的內存常常需要包含相同的內容。
瀏覽器的進程模式
為了節省內存,提供了四種進程模式( ),不同的進程模式會對 tab 進程做不同的處理。
這里需要給出 site 和 site- 的定義
理解了概念之后,下面解釋四個進程模式
首先是 ,顧名思義,單進程模式,所有tab都會使用同一個進程。接下來是-per-tabjs實現鼠標移上去顯示詳細信息,也是顧名思義,每打開一個tab,會新建一個進程。而對于-per-site,當你打開 頁面,在打開 的頁面,這兩個頁面的tab使用的是共一個進程,因為這兩個頁面的site相同,而如此一來,如果其中一個tab崩潰了,而另一個tab也會崩潰。
-per-site-是最重要的,因為這個是 默認使用的模式,也就是幾乎所有的用戶都在用的模式。當你打開一個 tab 訪問 ,然后再打開一個 tab 訪問 ,這兩個 tab 會使用兩個進程。而如果你在 中,通過JS代碼打開了 頁面,這兩個 tab 會使用同一個進程。
默認模式選擇
那么為什么瀏覽器使用-per-site-作為默認的進程模式呢?
-per-site-兼容了性能與易用性,是一個比較中庸通用的模式。
導航過程都發生了什么
前面我們講了瀏覽器的多進程架構,講了多進程架構的各種好處,和是怎么優化多進程架構的,下面從用戶瀏覽網頁這一簡單的場景,來深入了解進程和線程是如何呈現我們的網站頁面的。
網頁加載過程
之前我們我們提到,tab以外的大部分工作由瀏覽器進程 負責,針對工作的不同, 劃分出不同的工作線程:
第一步:處理輸入
當我們在瀏覽器的地址欄輸入內容按下回車時,UI 會判斷輸入的內容是搜索關鍵詞( query)還是URL,如果是搜索關鍵詞,跳轉至默認搜索引擎對應都搜索URL,如果輸入的內容是URL,則開始請求URL。
第二步:開始導航
回車按下后,UI 將關鍵詞搜索對應的URL或輸入的URL交給網絡線程 ,此時UI線程使Tab前的圖標展示為加載中狀態,然后網絡進程進行一系列諸如DNS尋址,建立TLS連接等操作進行資源請求,如果收到服務器的301重定向響應,它就會告知UI線程進行重定向然后它會再次發起一個新的網絡請求。
第三步:讀取響應
接收到服務器的響應后,開始解析HTTP響應報文,然后根據響應頭中的-Type字段來確定響應主體的媒體類型(MIME Type),如果媒體類型是一個HTML文件,則將響應數據交給渲染進程( )來進行下一步的工作,如果是 zip 文件或者其它文件,會把相關數據傳輸給下載管理器。
與此同時,瀏覽器會進行 Safe 安全檢查,如果域名或者請求內容匹配到已知的惡意站點, 會展示一個警告頁。除此之外,網絡線程還會做 CORB(Cross Read )檢查來確定那些敏感的跨站數據不會被發送至渲染進程。
第四步:查找渲染進程
各種檢查完畢以后, 確信瀏覽器可以導航到請求網頁, 會通知 UI 數據已經準備好,UI 會查找到一個 進行網頁的渲染。
瀏覽器為了對查找渲染進程這一步驟進行優化,考慮到網絡請求獲取響應需要時間,所以在第二步開始,瀏覽器已經預先查找和啟動了一個渲染進程,如果中間步驟一切順利,當 接收到數據時,渲染進程已經準備好了,但是如果遇到重定向,這個準備好的渲染進程也許就不可用了,這個時候會重新啟動一個渲染進程。
第五步:提交導航
到了這一步,數據和渲染進程都準備好了, 會向 發送IPC消息來確認導航,此時,瀏覽器進程將準備好的數據發送給渲染進程,渲染進程接收到數據之后,又發送IPC消息給瀏覽器進程,告訴瀏覽器進程導航已經提交了,頁面開始加載。
這個時候導航欄會更新,安全指示符更新(地址前面的小鎖),訪問歷史列表( tab)更新,即可以通過前進后退來切換該頁面。
第六步:初始化加載完成
當導航提交完成后,渲染進程開始加載資源及渲染頁面(詳細內容下文介紹),當頁面渲染完成后(頁面及內部的都觸發了事件),會向瀏覽器進程發送IPC消息,告知瀏覽器進程,這個時候UI 會停止展示tab中的加載中圖標。
網頁渲染原理
導航過程完成之后,瀏覽器進程把數據交給了渲染進程,渲染進程負責tab內的所有事情,核心目的就是將HTML/CSS/JS代碼,轉化為用戶可進行交互的web頁面。那么渲染進程是如何工作的呢?
渲染進程中,包含線程分別是:
不同的線程,有著不同的工作職責。
構建DOM
當渲染進程接受到導航的確認信息后,開始接受來自瀏覽器進程的數據,這個時候,主線程會解析數據轉化為DOM( Model)對象。
DOM為WEB開發人員通過與網頁進行交互的數據結構及API。
子資源加載
在構建DOM的過程中,會解析到圖片、CSS、腳本等資源,這些資源是需要從網絡或者緩存中獲取的,主線程在構建DOM過程中如果遇到了這些資源,逐一發起請求去獲取,而為了提升效率,瀏覽器也會運行預加載掃描( )程序,如果HTML中存在img、link等標簽,預加載掃描程序會把這些請求傳遞給 的 進行資源下載。
的下載與執行
構建DOM過程中,如果遇到