全書地址
Chromium中文文檔 for https://www.chromium.org/developers/design-documents
持續(xù)更新ing,歡迎star
gitbook地址:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//
github地址: https://github.com/ahangchen/Chromium_doc_zh
這個文檔描述了Chromium的高層架構
問題
構建一個從不會掛起或崩潰的渲染引擎幾乎是不可能的栏尚。構建一個完全安全的渲染引擎也是幾乎不可能的。
在某種程度上,web瀏覽器當前狀態(tài)就像一個與過去的多任務操作系統(tǒng)合作的單獨的用戶兢榨。正如在一個這樣的操作系統(tǒng)中的錯誤程序會讓整個系統(tǒng)掛掉别智,所以一個錯誤的web頁面也可以讓一個現(xiàn)代瀏覽器掛掉晓折。僅僅需要一個瀏覽器或插件的bug,就餓能讓整個瀏覽器和所有正在運行的標簽頁停止運行乖坠。
現(xiàn)代操作系統(tǒng)更加魯棒,因為他們把應用程序分成了彼此隔離的獨立線程刀闷。一個程序中的crash通常不會影響其他程序或整個操作系統(tǒng)熊泵,每個用戶對用戶數(shù)據(jù)的訪問也是有限制的。
架構概覽
我們?yōu)闉g覽器的標簽頁使用獨立的進程甸昏,以此保護整個應用程序免受渲染引擎中的bug和故障的傷害顽分。我們也會限制每個渲染引擎進程的相互訪問,以及他們與系統(tǒng)其他部分的訪問施蜜。某些程度上卒蘸,這為web瀏覽提供了內(nèi)存保護,為操作系統(tǒng)提供了訪問控制花墩。
我們把運行UI的進程叫做主進程(main)悬秉,把插件進程稱為“瀏覽器進程”或“瀏覽器(Browser)”。相似的冰蘑,標簽頁相關的進程被稱作“渲染線程”或“渲染器(renderer)”和泌。渲染器使用WebKit開源引擎來實現(xiàn)中斷與html的布局。
管理渲染進程
每個渲染進程有一個全局的RenderProcess對象祠肥,管理它與父瀏覽器進程之間的通信武氓,維護全局的狀態(tài)。瀏覽器為每個渲染進程維護一個對應的RenderViewHost,用來管理瀏覽器狀態(tài)县恕,并與渲染器交流东羹。瀏覽器與渲染器使用Chromium's IPC system進行交流。
管理view
每個渲染進程有一個以上的RenderView對象忠烛,由RenderProcess管理(它與標簽頁的內(nèi)容相關)属提。對應的RenderProcessHost維護一個與渲染器中每個view相關的RenderViewHost。每個view被賦予一個view ID,以區(qū)分同一個渲染器中的不同view美尸。這些ID在每個渲染器內(nèi)是唯一的冤议,但在瀏覽器中不是,所以區(qū)分一個view需要一個RenderProcessHost和一個view ID师坎。
瀏覽器與一個包含內(nèi)容的特定標簽頁之間的交流是通過這些RenderViewHost對象來完成的恕酸,它們知道如何通過他們的RenderProcessHost向RenderProcess和RenderView送消息。
組件與接口
在渲染進程中:
RenderProcess處理與瀏覽器中對應的RenderProcessHost的通信胯陋。每個渲染進程就有唯一的一個RenderProcess對象蕊温。這就是所有瀏覽器-渲染器之間的交互發(fā)生的方式。
RenderView對象與它在瀏覽器進程中對應的RenderViewHost和我們的webkit嵌入層通信(通過RenderProcess)遏乔。這個對象代表了一個網(wǎng)頁在標簽頁或一個彈出窗口的內(nèi)容义矛。
在瀏覽器進程中:
- Browser對象代表了頂級瀏覽器窗口
- RenderProcessHost對象代表了瀏覽器端瀏覽器的與渲染器的IPC連接。在瀏覽器進程里按灶,每個渲染進程有一個RenderProcessHost對象症革。
- RenderViewHost對象封裝了與遠端瀏覽器的交流,RenderWidgetHost處理輸入并在瀏覽器中為RenderWidget進行繪制鸯旁。
想要得到更多關于這種嵌入是如何工作的詳細信息噪矛,可以查看How Chromium displays web pages design document。
共享繪制器進程
通常铺罢,每個新的window或標簽頁是在一個新進程里打開的艇挨。瀏覽器會生成一個新的進程,然后指導它去創(chuàng)建一個RenderView韭赘。
有時候缩滨,有這樣一種必要或欲望在標簽頁或窗口間共享渲染進程。一個web應用程序會在期望同步交流時泉瞻,打開一個新的窗口脉漏,比如,在javascript里使用window.open袖牙。這種情況下侧巨,當我們創(chuàng)建一個新的window或標簽頁時,我們需要重用打開這個window的進程鞭达。我們也有一些策略來把新的標簽頁分配的已有的進程(如果總的進程數(shù)太大的話司忱,或者如果用戶已經(jīng)為這個域名打開了一個進程)皇忿。這些策略在Process Models里也有闡述。
檢測crash或者失誤的渲染
每個到瀏覽器進程的IPC連接會觀察進程句柄坦仍。如果這些句柄是signaled(有信號的)鳍烁,那么渲染進程已經(jīng)掛了,標簽頁會得到一個通知繁扎。從這時開始幔荒,我們會展示一個“sad tab”畫面來通知用戶渲染器已經(jīng)掛掉了。這個頁面可以按刷新按鈕或者通過打開一個新的導航來重新加載锻离。這時铺峭,我們會注意到?jīng)]有對應的進程,然后創(chuàng)建一個新的汽纠。
渲染器中的沙箱
給定的WebKit是運行在獨立的進程中的,所以我們有機會限制它對系統(tǒng)資源的訪問傀履。例如虱朵,我們可以確保渲染器唯一的網(wǎng)絡權限是通過它的父瀏覽器進程實現(xiàn)。相似的钓账,我們可以限制它對文件系統(tǒng)的訪問權限來使用host操作系統(tǒng)內(nèi)置的權限碴犬。
除了限制渲染器對文件系統(tǒng)和網(wǎng)絡的訪問權限,我們也可以限制它對用戶的顯示器以及相關的東西的一些權限梆暮。我們在獨立的windows桌面(對用戶不可見)中運行每個進程服协。這避免了讓渲染器在新的標簽頁或捕捉按鍵之間妥協(xié)。
歸還內(nèi)存
讓渲染器運行在獨立的進程中啦粹,賦予隱藏的標簽頁更低的優(yōu)先級會更加直接偿荷。通常,Windows平臺上的最小化的進程會把它們的內(nèi)存自動房東一個“可用內(nèi)存”池里唠椭。在低內(nèi)存的情況下跳纳,Windows會在交換這部分內(nèi)存到更高優(yōu)先級內(nèi)存前,把它們交換到磁盤贪嫂,以保證用戶可見的程序更易響應寺庄。我們可以對隱藏的標簽頁使用相同的策略。當渲染器進程沒有頂層標簽頁時力崇,我們可以釋放進程的“工作集”空間斗塘,作為一個給系統(tǒng)的信號,讓它如果必要的話亮靴,優(yōu)先把這些內(nèi)存交換到磁盤馍盟。因為我們發(fā)現(xiàn),當用戶在兩個標簽頁間切換時台猴,減少工作集大小也會減少標簽頁切換性能朽合,所以我們是逐漸釋放這部分內(nèi)存的俱两。這意味著如果用戶切換回最近使用的標簽頁,這個標簽頁的內(nèi)存比最近較少訪問的標簽頁更可能被換入曹步。有著足夠內(nèi)存的用戶運行他們所有的程序時根本不會注意到這個進程:事實上Windows只會在需要的時候重新聲明這塊數(shù)據(jù)宪彩,所以在有充分內(nèi)存時,不會有性能瓶頸讲婚。
這能幫助我們在低內(nèi)存的情況下得到最佳的內(nèi)存軌跡尿孔。幾乎不被使用的后臺標簽頁相關的內(nèi)存可以被完全交換掉,前臺標簽頁的數(shù)據(jù)可以被完全加載進內(nèi)存筹麸。相反的活合,一個單進程瀏覽器會在它的內(nèi)存里隨機分配所有標簽頁的數(shù)據(jù),并且不可能如此清晰地隔離已使用的和未使用的數(shù)據(jù)物赶,導致了內(nèi)存和性能上的浪費白指。
插件
Firefox風格的NPAPI插件運行在他們自己的進程里,與渲染器隔離酵紫。這會在Plugin Architecture中描述告嘲。
如何添加新特性(不用擴充RenderView/RenderViewHost/WebContents)
問題
過去,新的特性(比如奖地,自動填充選取樣例)可以通過把新特性的代碼導入到RenderView類(在渲染器進程里)和RenderViewHost類(在瀏覽器進程里)橄唬。如果一個新的特性是在瀏覽器進程的IO線程里處理的,那么它的IPC信息由BrowserMessageFilter調度参歹。RenderViewHost會只為了調用WebContent對象進程調用IPC信息仰楚,這會調用另一塊代碼。所有的瀏覽器與渲染器之間的IPC信息會被聲明在一個巨大的render_messages_internal.h里犬庇,為每個新特性修改所有的這些文件意味著這些類會變得臃腫僧界。
解決方案
我們增加了helper類和對上面的每個線程IPC信息的過濾的機制。這使得編寫自洽的特性更加容易械筛。
渲染器端
如果你想要過濾和發(fā)送IPC信息捎泻,實現(xiàn)RenderViewObserver接口(content/renderer/render_view_observer.h)。RenderViewObserver基類持有一個RenderView類埋哟,管理對象的生命周期笆豁,使其綁定到RenderView(它是可重寫的)。這個類就可以過濾和發(fā)送IPC消息赤赊,此外還可以獲得許多特性需要的關于頁面加載與關閉的通知闯狱。作為一個例子,可以查看ChromeExtensionHelper (chrome/renderer/extensions/chrome_extension_helper.h)抛计。
如果你的特性有一部分代碼是在WebKit內(nèi)的哄孤,避免通過WebViewClient接口回調,這樣我們就不會使得WebViewClient變得龐大吹截∈莩拢考慮創(chuàng)建新的WebKit接口給WebKit代碼調用凝危,讓渲染器端的類去實現(xiàn)它。作為一個例子晨逝,查看WebAutoFillClient (WebKit/chromium/public/WebAutoFillClient.h).
瀏覽器UI線程
WebContentsObserver (content/public/browser/web_contents_observer.h)接口允許UI線程的對象過濾IPC信息乾戏,以及給出關于頁面導航的通知遏佣。作為一個例子:查看TabHelper (chrome/browser/extensions/tab_helper.h)殿托。
瀏覽器其他線程
為了過濾和發(fā)送IPC信息給其他的瀏覽器線程暖侨,比如IO/FILE/WEBKIT等等,實現(xiàn)BrowserMessageFilter接口(content/browser/browser_message_filter.h)趁窃。BrowserRenderProcessHost對象在它的CreateMessageFilters函數(shù)里創(chuàng)造和增加過濾器牧挣。
通常,如果一個特性有許多IPC消息醒陆,這些消息應該移動到一個獨立的文件(例如瀑构,不要加到render_messages_internal.h里)。這對過濾線程(除了IO線程)也有幫助统求。作為一個例子检碗,查看content/common/pepper_file_messages.h。這允許他們的過濾器PepperFileMessageFilter方便的發(fā)送文件到file線程码邻,而不用在很多位置指定它們的ID。
void PepperFileMessageFilter::OverrideThreadForMessage(
const IPC::Message& message,
BrowserThread::ID* thread) {
if (IPC_MESSAGE_CLASS(message) == PepperFileMsgStart)
*thread = BrowserThread::FILE;
}