轉(zhuǎn)載請(qǐng)注明出處:
https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh/Start_Here_Background_Reading/How_Chromium_Displays_Web_Pages.html
全書地址
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如何展示網(wǎng)頁(yè)
這個(gè)文檔從底層描述了Chromium是如何展示網(wǎng)頁(yè)的。請(qǐng)確認(rèn)你已經(jīng)讀過(guò)多進(jìn)程架構(gòu)這篇文章。你會(huì)特別想要了解主要組件的框架。你也可能對(duì)多進(jìn)程資源加載感興趣,以了解網(wǎng)頁(yè)是如何從網(wǎng)絡(luò)中獲取到的。
應(yīng)用概念層
(關(guān)于這個(gè)闡述的原始Google文檔是http://goo.gl/MsEJX,開放給所有@chromium.org的人編輯)
每個(gè)矩形代表了一個(gè)應(yīng)用概念層,每一層都不了解上一層,也對(duì)上一層沒有依賴。
WebKit:Safari,Chromium和其他所有基于WebKit的瀏覽器共享的渲染引擎。WebKit Port是WebKit的一個(gè)部分,用來(lái)集成平臺(tái)獨(dú)立的系統(tǒng)服務(wù),比如資源加載與圖像。
Glue:將WebKit的類型轉(zhuǎn)為Chromium的類型。這就是我們的“WebKit嵌入層”。這是兩個(gè)browser,Chromium,和test_shell(允許我們測(cè)試WebKit)的基礎(chǔ)。
Renderer / Render host: 這是Chromium的“多進(jìn)程嵌入層”。它代理通知,并跨過(guò)進(jìn)程邊界執(zhí)行指令。
WebContents:一個(gè)可重用的組件,是內(nèi)容模塊的主類。它易于嵌入,允許多進(jìn)程將HTML繪制成View。查看content module pages以獲得更多信息。
Browser: 代表瀏覽器窗口,包含多個(gè)WebContent。
Tab Helpers:可以被綁定到WebContent的獨(dú)立對(duì)象(通過(guò)WebContentsUserData混雜)。瀏覽器將這些獨(dú)立對(duì)象中的一種綁定到WebContent給它持有,一個(gè)給網(wǎng)站圖標(biāo),一個(gè)給信息欄,等等。
WebKit
我們使用WebKit開源工程來(lái)布局web頁(yè)面。這部分代碼是從Apple中pull過(guò)來(lái)的,存儲(chǔ)在/third_party/WebKit目錄。WebKit主要由“WebCore”組成,這代表了核心的布局功能,還有“JavaScriptCore”,這被用來(lái)運(yùn)行JavaScript。我們只在測(cè)試時(shí)運(yùn)行JavaScriptCore,通常情況下,我們用我們自己高性能的V8 Javascript引擎來(lái)代替它。事實(shí)上,我們不完全是使用Apple稱之為“WebKit”的那一層,這是WebCore和OS X應(yīng)用程序(比如Safari)之間的嵌入API。為了方便,我們通常把從Apple學(xué)到的代碼稱為“WebKit”。
The WebKit port
在最低層,我們有我們的WebKit “port”。這是我們對(duì)于需要的平臺(tái)相關(guān)功能的實(shí)現(xiàn),它們與平臺(tái)無(wú)關(guān)的WebCore代碼交互。這些文件在WebKit樹上,通常在chromium目錄,或以Chromium為后綴的文件中。我們的port中的大部分其實(shí)是與操作系統(tǒng)無(wú)關(guān)的:你可以把它認(rèn)為WebCore的“Chromium port”。但某些方面,比如字體渲染,必須在不同平臺(tái)上做不同的處理。
- 網(wǎng)絡(luò)交流由我們的多進(jìn)程資源加載系統(tǒng)處理,而非直接從渲染線程跳到操作系統(tǒng)處理
- 圖像使用了為Android開發(fā)的Skia圖形庫(kù)。這是一個(gè)跨平臺(tái)的圖形庫(kù),處理所有的圖形和圖像,除了文本。Skia在/third_party/skia里。圖形操作的主要入口是/webkit/port/platform/graphics/GraphicsContextSkia.cpp。它在這個(gè)目錄里,使用了許多其他的文件,還有那些/base/gfx里的文件。
The WebKit glue(膠水)
Chromium應(yīng)用程序使用不同的類型,編碼風(fēng)格,以及代碼布局和第三方的WebKit代碼。WebKit膠水使用Google編碼傳統(tǒng)與類型為WebKit提供了一個(gè)更加方便的嵌入式API(例如,我們使用std::string而非WebCore::String,使用GURL而非KURL)。膠水代碼位于/webkit/glue。glue對(duì)象通常有與WebKit對(duì)象相似的命名,但在開頭有Web前綴。例如, WebCore::Frame變成了WebFrame。
WebKit膠水層將Chromium代碼的其他部分與WebCore數(shù)據(jù)類型隔離開,以幫助減少WebCore的改變對(duì)Chromium代碼基礎(chǔ)的影響。因此,WebCore數(shù)據(jù)類型從不直接被Chromium使用。為了Chromium的便利,需要碰一些WebCore對(duì)象時(shí),會(huì)把API加入WebKit的膠水層。
test shell應(yīng)用程序是一個(gè)為測(cè)試我們的WebKit port和膠水代碼的裸web瀏覽器。它在與WebKit交流時(shí),像Chromium那樣使用一樣的膠水接口。它為開發(fā)者提供了簡(jiǎn)單的方式去測(cè)試新的代碼,而不用理會(huì)許多復(fù)雜的瀏覽器特性,線程和進(jìn)程。這個(gè)應(yīng)用程序也被用于運(yùn)行自動(dòng)化WebKit測(cè)試。然而,test shell的缺點(diǎn)在于,它不像Chromium那樣用多進(jìn)程方式實(shí)踐WebKit。內(nèi)容模塊嵌入在一個(gè)被稱為“content shell”的應(yīng)用程序,它很快就能用于測(cè)試工作。
渲染器進(jìn)程
Chromium的瀏覽器進(jìn)程使用膠水接口嵌入在我們的WebKit port中,它不包含很多代碼:它的工作主要是作為渲染器端到瀏覽器的IPC通道。渲染器中最重要的類是RenderView,位于/content/renderer/render_view_impl.cc。這個(gè)對(duì)象代表一個(gè)web頁(yè)面。它處理與瀏覽器之間所有導(dǎo)航相關(guān)的命令。它驅(qū)動(dòng)RenderWidget提供繪圖和輸入事件處理。
RenderView與瀏覽器進(jìn)程通過(guò)全局(每個(gè)渲染器進(jìn)程)RenderProcess對(duì)象與瀏覽器進(jìn)程交流。
FAQ:RenderWidget和RenderViewHost之間的區(qū)別在哪里?RenderWidget通過(guò)在膠水層實(shí)現(xiàn)抽象接口(稱為WebWidgetDelegate)映射到一個(gè)WebCore::Widget對(duì)象?;疽粋€(gè)屏幕上的window接收輸入事件和我們畫進(jìn)去的東西。一個(gè)RenderView繼承自RenderWidget,并且是一個(gè)標(biāo)簽頁(yè)或一個(gè)填出窗口的內(nèi)容。除了繪制與組件輸入事件外,它還處理導(dǎo)航指令。只有一種情況下,RenderWidget可以在沒有RenderView時(shí)存在,就是網(wǎng)頁(yè)中的下拉選擇框(select box)。下拉選擇框必須用native window來(lái)渲染,這樣他們可以在任何其他空間上方出現(xiàn),并在必要時(shí)彈出。這些window需要繪制和接受輸入,但他們沒有獨(dú)立的web頁(yè)面(RenderView)。
渲染器中的線程
每個(gè)渲染器有兩個(gè)線程(查看多進(jìn)程架構(gòu)頁(yè)面來(lái)查看圖表,或者threading in Chromium來(lái)理解如何用它們編程)。渲染線程是主要的對(duì)象,比如RenderView和所有的WebKit代碼運(yùn)行的地方。當(dāng)它與瀏覽器交流時(shí),消息一開始發(fā)送到主線程,主線程輪流分發(fā)消息給瀏覽器進(jìn)程。在其他情況里,這允許我們從渲染器同步發(fā)送消息到瀏覽器。當(dāng)一個(gè)來(lái)自瀏覽器的結(jié)果是用于后續(xù)操作時(shí),這可以用于小量的操作。一個(gè)例子是,JavaScript從網(wǎng)頁(yè)請(qǐng)求cookie。渲染器線程會(huì)阻塞,主線程會(huì)讓所有的接收到的消息排隊(duì),直到得到正確的響應(yīng)。此時(shí)任何接收到的消息會(huì)突然發(fā)送給渲染器線程以執(zhí)行普通的處理。
瀏覽器進(jìn)程
底層瀏覽器進(jìn)程對(duì)象
所有的與渲染器進(jìn)程交流的IPC是在瀏覽器的I/O線程完成的。這個(gè)線程也處理所有的網(wǎng)絡(luò)交流,使得它不受用戶界面的干擾。
當(dāng)一個(gè)RenderProcessHost對(duì)象在主線程完成初始化(當(dāng)用戶界面運(yùn)行時(shí)),它會(huì)創(chuàng)造新的渲染器進(jìn)程和一個(gè)通道代理IPC對(duì)象(有一個(gè)命名了的管道通向渲染器),自動(dòng)轉(zhuǎn)發(fā)所有的消息回給UI線程的RenderProcessHost。一個(gè)ResourceMessageFilter會(huì)安裝在這個(gè)通道,它會(huì)過(guò)濾我們指定的消息,以直接在I/O線程處理(比如網(wǎng)絡(luò)請(qǐng)求)。這個(gè)過(guò)濾器發(fā)生在ResourceMessageFilter::OnMessageReceived里。
UI線程中的RenderProcessHost負(fù)責(zé)分發(fā)所有view相關(guān)消息給合適的RenderViewHost(它自己處理有限數(shù)量的與View相關(guān)的消息)。這種分發(fā)發(fā)生在RenderProcessHost::OnMessageReceived。
上層瀏覽器進(jìn)程對(duì)象
View相關(guān)消息出現(xiàn)在RenderViewHost::OnMessageReceived。這里處理的大部分消息,剩下的部分轉(zhuǎn)發(fā)給RenderWidgetHost基類。這兩個(gè)對(duì)象在渲染器里里映射到RenderView和RenderWidget(查看上面的“渲染器進(jìn)程”來(lái)理解它們的含義)。每個(gè)平臺(tái)有一個(gè)view類(RenderWidgetHostView[Aura|Gtk|Mac|Win])以集成到native view系統(tǒng)。
在RenderView/Widget上面是WebContents對(duì)象,大部分的消息事實(shí)上結(jié)束于這個(gè)對(duì)象的函數(shù)調(diào)用。一個(gè)WebContent代表網(wǎng)頁(yè)的內(nèi)容。它是內(nèi)容模塊的頂層對(duì)象,并且負(fù)責(zé)在一個(gè)矩形的view中展示網(wǎng)頁(yè)。查看內(nèi)容模塊頁(yè)面獲取更多信息。
WebContents對(duì)象包含在一個(gè)TabContentsWrapper中,它位于chrome/。負(fù)責(zé)標(biāo)簽頁(yè)。
說(shuō)明樣例
額外的例子(包含了導(dǎo)航和啟動(dòng)相關(guān)代碼)在Getting Around the Chromium Source Code里。
“設(shè)置光標(biāo)”消息的生命周期
設(shè)置光標(biāo)是一個(gè)渲染器發(fā)往瀏覽器的典型消息的例子。在渲染器端,以下是發(fā)生的事情:
設(shè)置光標(biāo)消息由WebKit內(nèi)部生成,通常是作為輸入事件的響應(yīng)。設(shè)置光標(biāo)消息開始于 content/renderer/render_widget.cc中的RenderWidget::SetCursor。
它會(huì)調(diào)用RenderWidget::Send來(lái)分發(fā)消息。這個(gè)方法也用于RenderView向browser分發(fā)消息。它會(huì)調(diào)用 RenderThread::Send.
這會(huì)調(diào)用IPC::SyncChannel,它在內(nèi)部代理消息到渲染器的主線程,并將其發(fā)送給命名的管道以發(fā)送給瀏覽器。
然后瀏覽器獲得了控制權(quán):
-
RenderProcessHost中的IPC::ChannelProxy通過(guò)瀏覽器的I/O線程接收所有的消息。它首先把他們通過(guò)ResourceMessageFilter(它在I/O線程上直接分發(fā)網(wǎng)絡(luò)請(qǐng)求與相關(guān)的消息)發(fā)送出去。由于我們的消息沒有被過(guò)濾掉,它繼續(xù)發(fā)送到瀏覽器的UI線程(IPC::ChannelProxy在內(nèi)部完成這個(gè)事情)。
content/browser/renderer_host/render_process_host_impl.cc中的RenderProcessHost::OnMessageReceived為所有的View在對(duì)應(yīng)的渲染進(jìn)程獲取消息。它直接處理幾種消息,并把剩下的部分轉(zhuǎn)發(fā)到合適的與發(fā)送消息的源RenderView對(duì)應(yīng)的RenderViewHost。
消息到達(dá)content/browser/renderer_host/render_view_host_impl.cc中的RenderViewHost::OnMessageReceived。許多消息是在這里處理的,但我們這時(shí)的消息不是,因?yàn)樗且粋€(gè)從RenderWidget來(lái),由RenderWidgetHost處理的消息。
RenderViewHost中所有未處理的消息自動(dòng)轉(zhuǎn)發(fā)給了RenderWidgetHost,包括我們的設(shè)置光標(biāo)消息。
這個(gè)映射到content/browser/renderer_host/render_view_host_impl.cc的消息最終在RenderWidgetHost::OnMsgSetCursor接收到消息,并調(diào)用合適的UI函數(shù)來(lái)設(shè)置鼠標(biāo)的光標(biāo)。
“鼠標(biāo)點(diǎn)擊”消息的生命周期
發(fā)送一個(gè)鼠標(biāo)點(diǎn)擊是一個(gè)經(jīng)典的瀏覽器到渲染器的例子。
Windows消息在瀏覽器的UI線程被RenderWidgetHostViewWin::OnMouseEvent接收,然后在同一個(gè)類中調(diào)用ForwardMouseEventToRenderer。
轉(zhuǎn)發(fā)函數(shù)打包輸入時(shí)間為一個(gè)跨平臺(tái)的WebMouseEvent,最后把它發(fā)送到它所關(guān)聯(lián)的RenderWidgetHost.
RenderWidgetHost::ForwardInputEvent創(chuàng)建一個(gè)IPC消息ViewMsg_HandleInputEvent,將這個(gè)WebInputEvent對(duì)象序列化進(jìn)去,然后調(diào)用RenderWidgetHost::Send。
這只是轉(zhuǎn)發(fā)到自己的RenderProcessHost::Send函數(shù),它會(huì)輪流將消息發(fā)送給IPC::ChannelProxy。
在內(nèi)部,IPC::ChannelProxy會(huì)將消息代理到瀏覽器的I/O線程,將它寫入渲染器對(duì)應(yīng)的命名管道。
注意,許多種消息創(chuàng)建于WebContents,特別是導(dǎo)航類的消息。這些消息遵循一個(gè)相似的從WebContents到RenderViewHost的路徑。
然后渲染器得到了控制權(quán):
渲染器主線程的IPC::Channel讀取瀏覽器發(fā)送的消息,然后IPC::ChannelProxy將消息代理到渲染線程。
RenderView::OnMessageReceived拿到這個(gè)消息。許多種消息在這里直接處理。由于點(diǎn)擊事件不是,它繼續(xù)往下走(和其他所有沒有被處理的消息一起)到RenderWidget::OnMessageReceived,它會(huì)輪流把消息轉(zhuǎn)發(fā)給RenderWidget::OnHandleInputEvent。
輸入事件被交給WebWidgetImpl::HandleInputEvent,在這里它被轉(zhuǎn)換成一個(gè)WebKit PlatformMouseEvent類,然后交給WebKit內(nèi)的WebCore::Widget類。