Chromium網頁載入過程簡要介紹和學習計劃
Chromium載入網頁的過程,需要Browser程序和Render程序協作完成。載入網頁的過程由Browser程序發起,向伺服器請求網頁內容的過程也是由Browser程序完成。Render程序負責對下載回來的網頁內容進行解析,解析之後得到一個DOM Tree。有了這個DOM Tree之後,Render程序就可以對網頁進行渲染了。本文接下來就對上述過程涉及到的重要概念進行簡要介紹以及制定學習計劃。
《Android系統原始碼情景分析》一書正在進擊的程式設計師網(http://0xcc0xcd.com)中連載,點選進入!
第一個涉及到的重要概念是Chromium的模組劃分及其層次關係,如圖1所示:
圖1 Chromium模組劃分及其層次關係
1. WebKit:網頁渲染引擎層,定義在命令空間WebCore中。Port部分用來整合平臺相關服務,例如資源載入和繪圖服務。WebKit是一個平臺無關的網頁渲染引擎,但是用在具體的平臺上時,需要由平臺提供一些平臺相關的實現,才能讓WebKit跑起來。
2. WebKit glue:WebKit嵌入層,用來將WebKit型別轉化為Chromium型別,定義在命令空間blink中。Chromium不直接訪問WebKit介面,而是通過WebKit glue介面間接訪問。WebKit glue的物件命名有一個特點,均是以Web為字首。
3. Renderer/Renderer host:多程序嵌入層,定義在命令空間content中。其中,Renderer執行在Render程序中,Renderer host執行在Browser程序中。
4. WebContents:允許將一個HTML網頁以多程序方式渲染到一個區域中,定義在命令空間content中。
5. Browser:代表一個瀏覽器視窗,它可以包含多個WebContents。
6. Tab Helpers:附加在WebContents上,用來增加WebContents的功能,例如顯示InfoBar。
我們可以將第1層和第2層歸結為WebKit層,第3層和第4層歸結為Content層,第5層和第6層歸結為瀏覽器層。如果以程序為邊界,Tab Helpers、Browser、WebContents和Renderer host執行在Browser程序中,Renderer、WebKit glue和WebKit執行在Render程序中。
Content層是Chromium的核心模組,它實現了Chromium的多程序架構。Content層主要向外提供的介面是WebContents,瀏覽器層通過這個WebContents介面就可以將一個HTML網頁渲染在一個區域上。例如,Chrome就是通過Content層提供的WebContents介面實現一個瀏覽器的。同樣我們也可以通過Content層提供的WebContents介面實現一個與Chrome類似的瀏覽器,甚至我們也可以通過Content層提供的WebContents介面實現一個嵌入在應用程式的瀏覽器控制元件,例如Android 4.4的WebView。
在Content層中,一個用來渲染網頁的區域稱為RenderView。一個RenderView也稱為一個RenderWidget。RenderView與RenderWidget的區別是,前者是描述的是一個用來顯示網頁內容的控制元件,後者描述的是一個可以接收使用者輸入的控制元件,但是它不一定是用來顯示網頁內容的。例如,點選網頁上的選擇框彈出來的視窗,是一個RenderWidget,它裡面顯示的內容與網頁無關。
我們可以將RenderView和RenderWidget看作是一個介面。Browser程序和Render程序都需要實現這個介面。其中,Browser程序分別實現RenderView和RenderWidget介面的兩個類是RenderViewHostImpl和RenderWidgetHostImpl,Render程序分別實現RenderView和RenderWidget介面的兩個類是RenderViewImpl和RenderWidgetImpl。Browser程序的每一個RenderViewHostImpl物件和每一個RenderWidgetHostImpl物件在Render程序中都分別對應有一個RenderViewImpl物件和一個RenderWidgetImpl。RenderViewHostImpl物件與RenderViewImpl物件、RenderWidgetHostImpl物件與RenderWidgetImpl物件可以通過Browser程序與Render程序建立的IPC通道進行通訊。
WebKit層的WebView和WebWidget相當於Content層的RenderView和RenderWidget。我們注意到,WebKit層還有一個稱為WebFrame的物件。事實上,Content層也有一個類似的物件,稱為RenderFrame。WebFrame和RenderFrame都是用來描述網頁的。既然已經有了RenderView和RenderWidget,為什麼還需要WebFrame和RenderFrame呢?這與Chromium的Out-of-Process iframes(OOPIFs)專案有關。關於Chromium的Out-of-Process iframes(OOPIFs)專案的詳細資訊,可以參考官方文件:Out-of-Process iframes (OOPIFs)。
當網頁使用window.open在另外一個Tab開啟一個新的網頁,或者通過iframe標籤嵌入另外一個網頁時,源網頁和目標網頁有可能在同一個Render程序中,也有可能在不同的Render程序中,這取決於它們是否來自同一個站點。在HTML5規範中,源網頁和目標網頁組成了一個瀏覽上下文單元(Browsing Context)。當源網頁和目標網頁不在同一個Render程序時,源網頁如何通過HTML5規範中的window.postMessage介面發訊息給目標網頁呢?如圖2所示:
圖2 HTML5的window.postMessage實現
在圖2中,網頁A通過window.open打開了網頁B,它們分別在兩個不同的Tab中,這兩個Tab又是在不同的Render程序中。在Browser程序中,網頁A和網頁B分別對應有一個WebContents物件。也就是Browser程序會為每一個網頁建立一個WebContents物件,如前面的圖1所示。為了讓網頁A能夠通過window.postMessage介面給網頁B傳送訊息,負責渲染網頁A的Render程序會為網頁B建立一個代理物件,這個代理物件知道如何傳送訊息給網頁B(通過Chromium的IPC訊息傳送、接收和分發機制分析一文分析的Routing機制)。同樣,負責渲染網頁B的Render程序也會為網頁A建立一個代理物件,這個代理物件負責接收從網頁A傳送過來的訊息,並且分發給網頁B處理。注意,代理物件在圖2中均通過虛線框表示。通過這種代理物件方式,就實現了不在同一個Render程序中的兩個網頁的相互通訊。
Chromium是如何實現上述代理物件的呢?我們通過圖3所示的例子進行說明,如下所示:
圖3 在同一個Browsing Context中的網頁
網頁A在一個Tab顯示,並且它通過iframe標籤包含了網頁B和網頁C。網頁C通過window.open在另外一個Tab中打開了另外一個網頁C例項。新開啟的網頁C通過iframe標籤包含了網頁D,網頁D又通過iframe標籤包含了網頁A的另外一個例項。
這時候Browser程序會分別為圖3的兩個Tab建立一個WebContents物件,如圖4所示:
圖4 RenderFrameHost/RenderFrameProxyHost
每一個WebContents物件都關聯有一個Frame Tree。Frame Tree中的每一個Node代表一個網頁。第一個Tab的Frame Tree包含有三個Node,分別代表網頁A、B和C。第二個Tab的Frame Tree也包含有三個Node,分別代表網頁C、D和A。
代表網頁B的Node關聯有一個RenderFrameHost物件和三個RenderFrameProxyHost物件,其中,RenderFrameHost物件描述的是網頁B本身,另外三個RenderFrameProxyHosts物件描述的是網頁A、C和D。也就是說,在Browser程序中,代理物件是通過RenderFrameProxyHost類描述的。
圖3所示的網頁A、B、C和D分別在不同的Render程序中渲染,如圖5所示:
圖5 RenderFrame/RenderFrameProxy
在負責渲染網頁A的Render程序中,有兩個RenderFrame物件,分別代表圖3所示的兩個網頁A例項。負責渲染網頁A的Render程序還包含有四個RenderFrameProxy物件,分別代表網頁B、C和D。在負責渲染網頁B、C和D的Render程序中,也有類似的RenderFrame物件和RenderFrameProxy物件。其中,RenderFrameProxy物件就是前面描述的代理物件。
每一個RenderFrame物件和RenderFrameProxy物件在圖1所示的WebKit glue層中,分別對應有一個WebLocalFrame物件和WebRemoteFrame物件,如圖6所示:
圖6 WebLocalFrame/WebRemoteFrame
注意,WebLocalFrame類和WebRemoteFrame類都是從圖1的所示的WebFrame類繼承下來的,它們都是屬於WebKit glue層的。前面我們提到,WebKit glue層是用來封裝WebKit層的,WebLocalFrame物件和WebRemoteFrame物件封裝的便是WebKit層中的LocalFrame和RemoteFrame物件。Chromium將WebKit glue層和WebKit層統稱為Blink模組。這意味著在Blink模組中,前面描述的代理物件是通過WebRemoteFrame和RemoteFrame描述的。
從前面的分析我們就可以看到,在Chromium中,為什麼一個網頁既要使用RenderView、RenderWidget,又要使用RenderFrame、WebFrame來描述,它們的作用是不一樣的,總結來說,就是:
1. RenderView描述的是一個用來顯示網頁內容的RenderWidget。
2. RenderWidget描述的是一個UI控制元件。
3. RenderFrame用來建立網頁之間的訊息通道。
在WebKit層中,每一個LocalFrame物件都關聯有一個LocalDOMWindow物件,這個LocalDOMWindow物件又關聯有一個Document物件。這個Document物件描述的就是一個要在當前Render程序進行載入和渲染的網頁。
在WebKit層中,每一個RemoteFrame物件都關聯有一個RemoteDOMWindow物件。但是由於RemoteFrame物件描述的是一個代理物件,因此它關聯的RemoteDOMWindow物件是不關聯有Document物件的,只是作為一個Place Holder存在當前Render程序中。
LocalDOMWindow物件和Document物件描述的就是HTML規範的DOM Window和Document,它們是在網頁的載入和解析過程中建立的。其中,Document物件包含有一個DOM Tree,如圖7所示:
圖7 DOM Tree和Layer Tree
這個圖來自官方文件:GPU Accelerated Compositing in Chrome。DOM Tree中的每一個Node對應的就是網頁中的每一個HTML標籤。每一個HTML標籤都對應有一個Render Object,這些Render Object又形成一個Render Object Tree。Render Object知道如何繪製其描述的HTML標籤。
具有相同座標空間的Render Object都繪製在同一個Render Layer中,這些Render Layer又形成了一個Render Layer Tree,這意味著並不是所有的Render Object都有自己的Render Layer。Render Layer Tree的根節點一定對應有一個Render Layer,其餘的子節點如果不需要使用單獨的Render Layer,那麼就會與父節點使用相同的Render Layer。因此,Render Object與Render Layer是多對一的關係。典型地,設定有透明屬性、CSS Filter的Render Object有單獨的Render Layer,標籤canvas和video對應的Render Object也有單獨的Render Layer。Layer是圖形渲染引擎普遍使用的一個技術,是為了方便繪製一組具有某些相同屬性的圖形而生的,這樣就不會為每一個圖形都單獨執行相同的繪圖操作。
在圖形渲染引擎中,Layer會對應有一個Backing Surface。在軟體方式渲染中,Backing Surface就是一個記憶體Buffer。在硬體方式渲染中,Backing Surface就是一個FBO。為了減少Buffer和FBO開銷。WebKit不會為每一個Render Layer都分配一個Backing Surface,而是讓某些Render Layer共用同一個Backing Surface。這意味著Render Layer與Backing Surface是多對一的關係。在WebKit中,具有Backing Surface的Layer稱為Graphics Layer,這些Graphics Layer又形成了一個Graphics Layer Tree。每一個Graphics Layer都關聯有一個Graphics Context。這個Graphics Context是由Chromium提供給WebKit的,它知道如何繪製Render Layer的內容。Graphics Context繪製出來的內容最後會通過合成器(Compositor)渲染在螢幕上。
上面描述的Render Object Tree、Render Layer Tree和Graphics Layer都是與網頁渲染相關的,是我們下一個系列的文章要分析的內容。在這個系列的文章中,我們主要是分析DOM Tree以及前面描述的Frame Tree。
Frame Tree由Browser程序建立,DOM Tree由Render程序建立。Frame Tree是在網頁內容下載回來前就創建出來的,並且在後面網頁的解析和導航時增加或者移除子節點。DOM Tree是在網頁內容下載回來後進行建立的,並且是根據網頁的內容進行建立的。當一個網頁的DOM Tree創建出來之後,它的載入過程就完成了。
注意,網頁內容下載是由Browser程序執行的。Browser程序再將下載回來的網頁通過共享記憶體交給Render程序處理。為什麼不直接由Render程序下載呢?這是為了安全起見,Render程序按照最小許可權原則建立,它不具有網路訪問許可權,因此就不能從伺服器上下載網頁內容回來。
為了更好地理解網頁載入過程,以及這個過程中涉及到的各種物件,接下來我們將按照以下三個情景分析網頁的載入過程:
將網頁抽象為Graphics Layer Tree之後,WebKit就可以將網頁元素繪製在對應的Graphics Layer 之上了。最後,Graphics Layer又會由WebKit的使用者,即Chromium,進行渲染,也就是最終顯示在螢幕上。網頁渲染是我們下一個系列文章的關注點,同時也是瀏覽器的核心所在,因為評價一個瀏覽器是否流暢就取決於它渲染網頁的速度夠不夠快。因此敬請關注這個系列的文章!更多的資訊也可以關注老羅的新浪微薄:http://weibo.com/shengyangluo。