寫在前面
進程、線程立美、內(nèi)存棕所、詞法語法解析都了解過,但是作為一個前端工程師悯辙,瀏覽器對我們來說更像一個黑盒。比如頁面間怎么通信的迎吵?擴展和頁面怎么通信的躲撰?dom css js 是怎么被瀏覽器解析的?普通?js 下載運行為什么會阻塞 dom 的解析击费?
瀏覽器每個小細節(jié)的實現(xiàn)都包含了很多的內(nèi)容拢蛋,本文沒有覆蓋所有問題,也可能沒有把每個問題講解的足夠深入細致蔫巩。但希望可以讓你對瀏覽器本身的運行機制有一些好奇心~
聲明
本文所有觀點均基于桌面 chrome 瀏覽器谆棱。
一快压、為什么是多進程架構(gòu)
由圖可以看到,瀏覽器是多進程的垃瞧。那我們探索下為什么瀏覽器是多進程的呢蔫劣?
二、瀏覽器多進程架構(gòu)
一般个从,當 Chrome 在強大的硬件上運行時脉幢,它可能會將每個服務拆分為不同的進程,從而提供更高的穩(wěn)定性嗦锐,但如果它位于資源約束設備上嫌松,Chrome會將部分服務整合到一個進程中,從而節(jié)省內(nèi)存占用奕污。 下面介紹下主要的進程萎羔。
三、主要進程有哪些碳默?
Browser 進程:瀏覽器的主進程贾陷,負責瀏覽器的界面顯示、各個頁面的管理腻窒,是所有其他類型進程的祖先昵宇,負責他們的創(chuàng)建和銷毀調(diào)用等工作,它有且僅有一個儿子。
Renderer進程:網(wǎng)頁的渲染進程瓦哎,負責頁面的渲染工作主要在這個進程中完成,會有多個柔逼。
Until very recently, Chrome gave each tab a process when it could; now it tries to give each site its own process, including iframes (seeSite Isolation).
when it could 是什么意思呢蒋譬?
In order to save memory, Chrome puts a limit on how many processes it can spin up. The limit varies depending on how much memory and CPU power your device has, but when Chrome hits the limit, it starts to run multiple tabs from the same site in one process.
插件進程:其創(chuàng)建的基本原則是每種類型的插件只創(chuàng)建一次,而且僅當使用時才創(chuàng)建愉适。當多個網(wǎng)頁需要使用同一種類型的插件時犯助,插件進程會為每個使用者創(chuàng)建一個實例,所以插件進程是被共享的维咸。
GPU進程:最多只有一個剂买,當且僅當 GPU 硬件加速打開的時候才會被創(chuàng)建,主要用于對 3D 圖形加速調(diào)用的實現(xiàn)
其他進程
四癌蓖、多進程的優(yōu)點
1瞬哼、browser 和 render 是分開的,避免單個頁面render? crash 影響整個瀏覽器
(https://developers.google.com/web/updates/images/inside-browser/part1/tabs.svg)
2租副、它方便了安全機制的實現(xiàn)坐慰,沙箱模型是基于多進程架構(gòu)的
?對于網(wǎng)絡上的網(wǎng)頁,瀏覽器認為它們是不安全的用僧,因為網(wǎng)頁總是存在各種可能性结胀,也許是無意的或有意的攻擊赞咙。如果有一種機制,將網(wǎng)頁的運行限制在一個特定的環(huán)境中糟港,也就是一個沙箱中攀操,使它只能訪問有限的功能。那么着逐,即使網(wǎng)頁工作的渲染引擎被攻擊崔赌,它也不能夠獲取渲染引擎工作的主機系統(tǒng)中的任何權(quán)限,這一思想就是沙箱模型耸别。
五健芭、當一個 url 輸入時,哪些進程被調(diào)用
我們知道瀏覽器 Tab 外的工作主要由 Browser Process 掌控秀姐,Browser Process 又對這些工作進一步劃分慈迈,使用不同線程進行處理:
UI thread : 控制瀏覽器上的按鈕及輸入框;
network thread: 處理網(wǎng)絡請求省有,從網(wǎng)上獲取數(shù)據(jù)痒留;
storage thread: 控制文件等的訪問;
回到我們的問題蠢沿,當我們在瀏覽器地址欄中輸入文字伸头,并點擊回車獲得頁面內(nèi)容的過程在瀏覽器看來可以分為以下幾步:
1. 處理輸入
UI thread 需要判斷用戶輸入的是 URL 還是 query;
2. 開始導航
當用戶點擊回車鍵舷蟀,UI thread 通知 network thread 獲取網(wǎng)頁內(nèi)容恤磷,并控制 tab 上的 spinner 展現(xiàn),表示正在加載中野宜。
network thread 會執(zhí)行 DNS 查詢扫步,隨后為請求建立 TLS 連接。
UI thread 通知 Network thread 加載相關(guān)信息
如果 network thread 接收到了重定向請求頭如 301匈子,network thread 會通知 UI thread 服務器要求重定向河胎,之后,另外一個 URL 請求會被觸發(fā)虎敦。
3. 讀取響應
當請求響應返回的時候游岳,network thread 會依據(jù) Content-Type 及 MIME Type sniffing 判斷響應內(nèi)容的格式。
判斷響應內(nèi)容的格式
如果響應內(nèi)容的格式是 HTML 其徙,下一步將會把這些數(shù)據(jù)傳遞給 renderer process吭历,如果是 zip 文件或者其它文件,會把相關(guān)數(shù)據(jù)傳輸給下載管理器擂橘。
Safe Browsing 檢查也會在此時觸發(fā),如果域名或者請求內(nèi)容匹配到已知的惡意站點摩骨,network thread 會展示一個警告頁通贞。此外 CORB 檢測也會觸發(fā)確保敏感數(shù)據(jù)不會被傳遞給渲染進程朗若。
4. 查找渲染進程
當上述所有檢查完成,network thread 確信瀏覽器可以導航到請求網(wǎng)頁昌罩,network thread 會通知 UI thread 數(shù)據(jù)已經(jīng)準備好哭懈,UI thread 會查找到一個 renderer process 進行網(wǎng)頁的渲染掏熬。
收到 Network thread 返回的數(shù)據(jù)后带到,UI thread 查找相關(guān)的渲染進程
由于網(wǎng)絡請求獲取響應需要時間,這里其實還存在著一個加速方案患朱。當 UI thread 發(fā)送 URL 請求給 network thread 時轨功,瀏覽器其實已經(jīng)知道了將要導航到那個站點旭斥。UI thread 會并行的預先查找和啟動一個渲染進程,如果一切正常古涧,當 network thread 接收到數(shù)據(jù)時垂券,渲染進程已經(jīng)準備就緒了,但是如果遇到重定向羡滑,準備好的渲染進程也許就不可用了菇爪,這時候就需要重啟一個新的渲染進程。
5. 確認導航
進過了上述過程柒昏,數(shù)據(jù)以及渲染進程都可用了凳宙, Browser Process 會給 renderer process 發(fā)送 IPC 消息來確認導航,一旦 Browser Process 收到 renderer process 的渲染確認消息职祷,導航過程結(jié)束氏涩,頁面加載過程開始。
此時堪旧,地址欄會更新削葱,展示出新頁面的網(wǎng)頁信息。history tab 會更新淳梦,可通過返回鍵返回導航來的頁面析砸,為了讓關(guān)閉 tab 或者窗口后便于恢復,這些信息會存放在硬盤中爆袍。
6. 額外的步驟
一旦導航被確認首繁,renderer process 會使用相關(guān)的資源渲染頁面,下文中我們將重點介紹渲染流程陨囊。當 renderer process 渲染結(jié)束(渲染結(jié)束意味著該頁面內(nèi)的所有的頁面弦疮,包括所有 iframe 都觸發(fā)了 onload 時),會發(fā)送 IPC 信號到 Browser process蜘醋, UI thread 會停止展示 tab 中的 spinner胁塞。
Renderer Process 發(fā)送 IPC 消息通知 browser process 頁面已經(jīng)加載完成。
當然上面的流程只是網(wǎng)頁首幀渲染完成,在此之后啸罢,客戶端依舊可下載額外的資源渲染出新的視圖编检。
在這里我們可以明確一點,所有的 JS 代碼其實都由 renderer Process 控制的扰才,所以在你瀏覽網(wǎng)頁內(nèi)容的過程大部分時候不會涉及到其它的進程允懂。不過也許你也曾經(jīng)監(jiān)聽過 beforeunload 事件,這個事件再次涉及到 Browser Process 和 renderer Process 的交互衩匣,當當前頁面關(guān)閉時(關(guān)閉 Tab 蕾总,刷新等等),Browser Process 需要通知 renderer Process 進行相關(guān)的檢查琅捏,對相關(guān)事件進行處理生百。
瀏覽器進程發(fā)送 IPC 消息給渲染進程,通知要離開當前網(wǎng)站了
如果導航由 renderer process 觸發(fā)(比如在用戶點擊某鏈接午绳,或者 JS 執(zhí)行?window.location = "http://newsite.com"?) renderer process 會首先檢查是否有?beforeunload?事件處理器置侍,導航請求由 renderer process 傳遞給 Browser process。
如果導航到新的網(wǎng)站拦焚,會啟用一個新的 render process 來處理新頁面的渲染蜡坊,老的進程會留下來處理類似?unload?等事件。
關(guān)于頁面的生命周期赎败,更多內(nèi)容可參考 Page Lifecycle API 秕衙。
瀏覽器進程發(fā)送 IPC 消息到新的渲染進程通知渲染新的頁面,同時通知舊的渲染進程卸載僵刮。
除了上述流程据忘,有些頁面還擁有 Service Worker (服務工作線程),Service Worker 讓開發(fā)者對本地緩存及判斷何時從網(wǎng)絡上獲取信息有了更多的控制權(quán)搞糕,如果 Service Worker 被設置為從本地 cache 中加載數(shù)據(jù)勇吊,那么就沒有必要從網(wǎng)上獲取更多數(shù)據(jù)了。
值得注意的是 service worker 也是運行在渲染進程中的 JS 代碼窍仰,因此對于擁有 Service Worker 的頁面汉规,上述流程有些許的不同。
當有 Service Worker 被注冊時驹吮,其作用域會被保存针史,當有導航時,network thread 會在注冊過的 Service Worker 的作用域中檢查相關(guān)域名碟狞,如果存在對應的 Service worker啄枕,UI thread 會找到一個 renderer process 來處理相關(guān)代碼,Service Worker 可能會從 cache 中加載數(shù)據(jù)族沃,從而終止對網(wǎng)絡的請求频祝,也可能從網(wǎng)上請求新的數(shù)據(jù)泌参。
Service Worker 依據(jù)具體情形做處理。
關(guān)于 Service Worker 的更多內(nèi)容可參考:
https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle
如果 Service Worker 最終決定通過網(wǎng)上獲取數(shù)據(jù)常空,Browser 進程 和 renderer 進程的交互其實會延后數(shù)據(jù)的請求時間 及舍。Navigation Preload 是一種與 Service Worker 并行的加速加載資源的機制,服務端通過請求頭可以識別這類請求窟绷,而做出相應的處理。
更多內(nèi)容可參考:
https://developers.google.com/web/updates/2017/02/navigation-preload
六咐柜、1兼蜈、那么進程間怎么進行通信?2拙友、擴展與頁面間的通信为狸?3、hybrid app 中 native 與 web 間的通信遗契?4辐棒、頁面間的通信?
1牍蜂、Inter?Process?Communication (IPC)
2漾根、chrome.runtime.onMessage.addListener &&?chrome.tabs.sendMessage
由于Extension運行時需要調(diào)用“chrome.*”接口,我們必須了解這些接口是如何被擴展和實現(xiàn)的鲫竞。 從基本過程上來看辐怕,簡單地講應該是Chromium的Extension機制在V8引擎中注入一些代碼,然后當JavaScript代碼調(diào)用這些接口的時候从绘,V8引擎調(diào)用注入的本地代碼寄疏,這些代碼會將調(diào)用接口的請求從Renderer進程發(fā)送給Browser進程。在Browser進程中僵井,接收這些請求并派發(fā)給相應的實現(xiàn)類陕截,請求完成后按需要返回調(diào)用結(jié)果。
3批什、js 與 java 通過對象注入(類似的js 與 oc window.webkit.messageHandlers )
網(wǎng)頁開發(fā)者使用 html农曲、css、js開發(fā)網(wǎng)頁時渊季,有時會覺得瀏覽器能力不足朋蔫,希望通過傳統(tǒng)語言例如 c/c++ 來開發(fā)一些庫,這些庫可以被網(wǎng)頁調(diào)用却汉,這樣來滿足應用的要求驯妄,這里稱之為混合編程。
V8 提供什么機制來擴展 js引擎的能力的呢合砂?1青扔、idl 文件 2、擴展 extension 基類
4、window.postMessage()
七微猖、渲染進程(瀏覽器內(nèi)核)
渲染進程包含的線程
1. 主線程 Main thread
2. 工作線程 Worker thread
3. 排版線程 Compositor thread
4. 光柵線程 Raster thread
八、渲染的流程
主線程解析 dom 構(gòu)建 dom 樹——》preload scanner 頁面中資源請求傳遞給 Browser process 中的 network thread 進行相關(guān)資源的下載——》JS凛剥、CSS 的下載與執(zhí)行(注意思考各種阻塞的原因)——》主進程還會基于 CSS 選擇器解析 CSS 獲取每一個節(jié)點的最終的計算樣式值侠仇。——》layout 通過遍歷 DOM 及相關(guān)元素的計算樣式犁珠,主線程會構(gòu)建出包含每個元素的坐標信息及盒子大小的布局樹逻炊。——》在繪制階段犁享,主線程會遍歷布局樹以創(chuàng)建繪制記錄余素。繪制記錄可以看做是記錄各元素繪制先后順序的筆記〈独ィ——》主線程遍歷布局樹生成層樹——》一旦層樹被創(chuàng)建桨吊,渲染順序被確定,主線程會把這些信息通知給合成器線程凤巨,合成器線程會柵格化每一層视乐。有的層的可以達到整個頁面的大小,因此磅甩,合成器線程將它們分成多個磁貼炊林,并將每個磁貼發(fā)送到柵格線程,柵格線程會柵格化每一個磁貼并存儲在 GPU 顯存中卷要。
九渣聚、小碎片
1、render 進程中 worker thread
Web Worker 為 Web 內(nèi)容在后臺線程中運行腳本提供了一種簡單的方法僧叉,可以用于處理大型計算奕枝。只屬于某個頁面,不會和其他頁面的Render進程(瀏覽器內(nèi)核進程)共享
Service Worker 在后臺線程中運行的線程瓶堕。是一個可編程的網(wǎng)絡代理隘道,允許開發(fā)者控制頁面上處理的網(wǎng)絡請求。
2郎笆、SharedWorker 和上面的 worker 是一回事么谭梗?
sharedWorker 是由瀏覽器管理的進程,可以被多個 render 進程共享宛蚓。不管 sharedWorker 被創(chuàng)建多少次激捏,每個瀏覽器只存在一個 sharedworker 進程。
3凄吏、css 加載不會阻塞 dom 樹解析远舅,但是會阻塞 dom 樹渲染
瀏覽器是解析 DOM 生成 DOM Tree闰蛔,結(jié)合 CSS 生成的 CSS Tree,最終組成 render tree图柏,再渲染頁面序六。可見 css 并不影響 dom tree 的生成蚤吹。但是到了渲染階段例诀,渲染是有成本的,瀏覽器會盡量減少渲染的次數(shù)裁着。
4余佃、js 阻塞 DOM 解析和渲染(此處忽略 defer async)
js 會影響 dom tree 的生成,如果不阻塞解析跨算,很可能之前的解析工作都是一些無用功,包括解析后的很多工作都是無用功椭懊,所以此處的阻塞也是出于對瀏覽器性能的優(yōu)化诸蚕。
5、reflow 與 repaint 相差的主要是哪個過程?
layout
6氧猬、event loop 相關(guān)的線程背犯?
a、JS引擎線程
也稱為JS內(nèi)核盅抚,負責處理Javascript腳本程序漠魏。(例如V8引擎)JS引擎線程負責解析Javascript腳本,運行代碼妄均。JS引擎一直等待著任務隊列中任務的到來柱锹,然后加以處理,一個Tab頁(renderer進程)中無論什么時候都只有一個JS線程在運行JS程序
b丰包、事件觸發(fā)線程
歸屬于瀏覽器而不是JS引擎禁熏,用來控制事件循環(huán)(可以理解,JS引擎自己都忙不過來邑彪,需要瀏覽器另開線程協(xié)助)
當JS引擎執(zhí)行代碼塊如setTimeOut時(也可來自瀏覽器內(nèi)核的其他線程,如鼠標點擊瞧毙、AJAX異步請求等),會將對應任務添加到事件線程中
當對應的事件符合觸發(fā)條件被觸發(fā)時寄症,該線程會把事件添加到待處理隊列的隊尾宙彪,等待JS引擎的處理
注意,由于JS的單線程關(guān)系有巧,所以這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閑時才會去執(zhí)行)
c释漆、定時觸發(fā)器線程
傳說中的setInterval與setTimeout所在線程
瀏覽器定時計數(shù)器并不是由JavaScript引擎計數(shù)的,(因為JavaScript引擎是單線程的, 如果處于阻塞線程狀態(tài)就會影響記計時的準確)
因此通過單獨線程來計時并觸發(fā)定時(計時完畢后,添加到事件隊列中剪决,等待JS引擎空閑后執(zhí)行)
注意灵汪,W3C在HTML標準中規(guī)定檀训,規(guī)定要求setTimeout中低于4ms的時間間隔算為4ms。
d享言、異步http請求線程
在XMLHttpRequest在連接后是通過瀏覽器新開一個線程請求
將檢測到狀態(tài)變更時峻凫,如果設置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件览露,將這個回調(diào)再放入事件隊列中荧琼。再由JavaScript引擎執(zhí)行。
參考文檔
https://developers.google.com/web/updates/2018/09/inside-browser-part1 (官方文檔)
https://www.infoq.cn/article/CS9-WZQlNR5h05HHDo1b (對官方文檔的部分翻譯)
https://segmentfault.com/a/1190000012925872
https://www.imweb.io/topic/58e3bfa845e5c13468f567d5
https://juejin.im/post/5a6547d0f265da3e283a1df7?