微信公衆號:愛寫bugger的阿拉斯加 若有問題或建議,請後臺留言,我會盡力解決你的問題。html
此文章是我最近在看的【WebKit 技術內幕】一書的一些理解和作的筆記。前端
而【WebKit 技術內幕】是基於 WebKit 的 Chromium 項目的講解。git
書接上文 瀏覽器內核之 CSS 解釋器和樣式佈局github
本文剖析 WebKit 爲網頁渲染而構造的各類類型的內部結構表示,並介紹基本的網頁軟件渲染方式。web
WebKit的佈局計算使用 RenderObject 樹並保存計算結果到 RenderObject 樹。 RenderObject 樹同其餘樹(如 RenderLayer 樹等),構成了 WebKit 渲染的爲要基礎設施。canvas
爲了解釋本章內容,我使用如下基礎的前端代碼來講明。後端
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<div>abc</div>
<canvas id="webg1" width="80" height="80"></canvas>
<a href="mailto:joe@example.com?subject=feedback">email me</a>
<img src="" alt="">
<input type="button" name="" />
<select name="" multiple>
<option value="">option</option>
</select>
<table>
<tr>
<td>data</td>
</tr>
</table>
<script>
var canvas = document.getElementById('webg1')
var g1 = canvas.getContext('experimental-webg1')
if (g1) {
alert("There's no WebGl context available. ")
return
}
</script>
</body>
</html>
複製代碼
上面代碼通過 WebKit 解釋以後,生成 DOM 樹,也很容易想象到。在 DOM 樹構建完成以後,WebKit 會爲 DOM 樹節點構建 RenderObject 樹。請聽我娓娓道來。瀏覽器
不可視節點: 在 DOM 樹中,該節點用戶不可見,只是起到一些其餘方面而不是顯示內容的做用。如 「meta」 、「head」、「script」 節點等。緩存
可視節點: 在 DOM 樹中,該節點用戶可見,能夠顯示一塊區域,如文字、圖片、2D 圖形等。bash
對這些 「可視節點」,由於 WebKit 須要將它們的內容繪製到最終的網頁結果中,因此 WebKit 會爲它們創建相應的 RenderObject 對象。
一個 RenderObject 對象保存了爲繪製 DOM 節點所須要的各類信息,例如樣式佈局信息,通過 WebKit 的處理以後,RenderObject 對象知道如何繪製本身。
這些 RenderObject 對象同 DOM 的節點對象相似,它們也構成一棵樹,在這裏咱們稱這爲 RenderObject 樹。RenderObject 樹是基於 DOM 樹創建起來的一棵新樹,是爲了佈局計算和渲染等機制而構建的一種新的內部表示。RenderObject 樹節點和 DOM 節點不是一一對應關係。
是根據如下三條規則出發爲 DOM 節點建立一個 RenderObject 對象的:
DOM 樹的 document 節點。
DOM 樹中的可視化節點,例如 html 、body、div 等。而 WebKit 不會爲非可視化節點建立 RenderObject 節點。
某些狀況下 WebKit 須要創建匿名的 RenderObject 節點,該節點不對應於 DOM 樹中的任何節點,而是 WebKit 處理上的須要,典型的例子就是匿名的 RenderBlock 節點。
WebKit 處理影子 DOM 沒有什麼特別的不一樣,雖然 JavaScript 代碼無法訪問影子 DOM ,可是 WebKit 須要建立並渲染 RenderObject。
WebKit 在建立 DOM 樹的同時也建立 RenderObject 對象。若是 DOM 樹被動態加入了新節點,WebKit 也可能建立相應的 RenderObject 對象。
每一個 Element 對象都會遞歸調用 「attach」 函數,該函數檢查 Element 對象是否須要建立 RenderObject 對象,若是須要,該函數會使用 NodeRenderingContext 類來根據 DOM 節點的類型來建立對應的 RenderObject 節點。
DOM 樹中,元素節點包含不少類型。同 DOM 樹同樣,RenderObject 樹中的節點也有不少類型。
而圖中間的 RenderObject 類還包含了 RenderObject 的主要虛函數,還能夠分爲如下 5 類:
RenderObject 對象構成了一棵樹。RenderObject 樹的建立過程主要是由 NodeRenderingContext 類來負責。
思路:首先 WebKit 檢查該 DOM 節點是否須要建立 RenderObject 對象。若是須要,WebKit 創建或者獲取一個建立 RenderObject 對象的 NodeRenderingContext 對象,NodeRenderingContext 對象會分析須要建立的 RenderObject 對象的父親節點、兄弟節點等,設置這些信息後完成插入樹的動做。
根據上面的代碼生成圖 7-4 所示的 DOM 樹和 RenderObject 樹。
上圖中使用虛線箭頭表示兩種樹的節點對應關係,其中 HTMLDocument 節點對應 RenderView 節點,RenderView 節點是 RenderObject 樹的根節點。另外,WebKit 沒有 HTMLHeadElement 節點(非可視化元素),由於沒有被建立 RenderObject 子類的對象。
網頁是有層次結構的,能夠分層的,一是爲了方便網頁開發者開發網頁並設置網頁的層次,二是爲了 WebKit 處理上的便利,爲了簡化渲染的邏輯。
WebKit 會爲網頁的層次建立相應的 RenderLayer 對象。當某些類型RenderLayer 的節點或者具備某些 CSS 樣式的 RenderLayer 節點出現的時候,WebKit 就會爲這些節點建立 RenderLayer 對象。通常來講,某個 RenderObject 節點的後代都屬於該節點,除非 WebKit 根據規則爲某個後代 RenderObject 節點建立了一個新的 RenderLayer 對象。
**RenderLayer 樹是基於 RenderObject 樹創建起來的一棵新樹。 **
並且有結論:RenderLayer 節點和 RenderObject 節點不是一一對應關係,而是一對多的關係。
RenderObject 節點須要創建新的 RenderLayer 節點,是根據如下基本規則:
DOM 樹的 Document 節點對應的 RenderView 節點。
DOM 樹中的 Document 的子女節點,也就是 HTML 節點對應的 RenderBlock 節點。
顯式的指定 CSS 位置的 RenderObject 節點。
有透明效果的 RenderObject 節點。
節點有溢出(Overflow)、alpha 或者反射等效果的 RenderObject 節點。
使用 Canvas 2D 和 3D(WebGl)技術的 RenderObject 節點。
Video 節點對應的 RenderObject 節點。
除了根節點也就是 RenderLayer 節點,一個 RenderLayer 節點的父親就是該RenderLayer 節點對應的 RenderObject 節點的祖先鏈中最近的祖先,而且祖先所在的RenderLayer 節點同該節點的 RenderLayer 節點不一樣。基於這一原理,這些 RenderLayer 節點也構成了一棵 RenderLayer 樹。
每一個 RenderLayer 節點包含的 RenderObject 節點實際上是一棵 RenderLayer 子樹。 理想狀況下,每一個 RenderLayer 對象都會有一個後端類,該後端類用來存儲該 RenderLayer 對象繪製的結果。實際狀況中則比較複雜,在不一樣的渲染模式下,不一樣 WebKit 的移植中,狀況都不同。RenderLayer 節點的使用能夠有效地減小網頁結構的複雜程度,並在不少狀況下可以減小從新渲染的開銷。
在 WebKit 建立 RenderObject 樹以後,WebKit 也會建立 RenderLayer 樹。固然,某些 RenderLayer 節點也有可能在執行 JavaScript 代碼時或者更新頁面的樣式被建立。同 RenderObject 類不一樣的是,RenderLayer 類沒有子類,它表示的是網頁的一個層次,並無 「子層次」 的說法。
構建 RenderLayer 樹的過程很是簡單,甚至比構建 RenderObject 樹還要簡單。根據前面所述的條件來判斷一個 RenderObject 節點是否須要創建一個新的 RenderLayer 對象,並設置 RenderLayer 對象的父親和兄弟關係便可。
根據剛開始的代碼,WebKit 中的 RenderObject 樹表示如圖 7-5 左邊所示的結構。右邊描述是就是 WebKit 所生成的對應的 RenderLayer 樹。根據 RenderLayer 對象建立的條件來看,該示例代碼的 RenderLayer 樹應該包含三個 RenderLayer 節點——根節點和它的子女,以及葉子節點。
根據上面最初的代碼,生成 圖 7-6 ,表示 WebKit 內部表示的具體結構 RenderObject 樹、RenderLayer 樹和佈局信息的中大小和位置信息。
首先,圖中 ‘layer at(x,x)’ 表示的是不一樣的 RenderLayer 節點,下面全部 RenderObject 子類的對象均屬於該 RenderLayer 對象。
以第一個 RenderLayer 節點爲例,它對應於 DOM 樹中的 Document 節點。後面的 「(0,0)」 表示該節點在網頁座標系中的位置,最後的 「1028X683」 表示該節點的大小,第一層包含的 RenderView 節點後面的信息也是一樣的意思。
其次,看第二個 layer ,其包含了 HTML 中的絕大部分元素。這裏面有三點須要解釋一下:
一,「head」 元素沒有相應的 RenderObject 對象,由於 「head」 是一個不可視的元素;
二,「canvas」 元素並不在第二個 layer 中,而是在第三個 layer(RenderHTMLCanvas)中,雖然該元素仍然是 RenderBody 節點的子女;
三,該 layer 層中包含一個匿名(Anonymous)的 RenderBlock 節點,該匿名節點包含了 RenderText 和 RenderLnline 等子節點。
再次,第三個 layer 層,由於 JavaScript 代碼爲 「canvas」 元素建立了一個 WebGl 的 3D 繪圖上下文對象,WebKit 須要從新生成一個新的 RenderLayer 對象。
最後,來講明一下三個層次的建立時間。在建立 DOM 樹以後,WebKit 會接着建立第一個和第二個 layer 層。可是,第三個 RenderLayer 對象是在 WebKit 執行 JavaScript 代碼時才被建立的,這是由於 WebKit 須要檢查出 JavaScript 代碼是否爲 「canvas」 確實建立了 3D 繪圖上下文,而不是在遇到 」canvas「 元素時建立新的 RenderLayer 對象。
1.3.1 繪圖上下文(GraphicsContext)
RenderObject 對象是用什麼來繪製內容的呢?在 WebKit 中,繪圖操做被定義了一個抽象層,就是繪圖上下文,全部繪圖的操做都是在該上下文中來進行的。
繪圖上下文能夠分紅兩種類型:一,是 2D 圖形上下文(GraphicsContext),用來繪製 2D 圖形的的上下文;二是 3D 繪圖上下文,是用來繪製 3D 圖形的上下文。
這兩種上下文都是抽象基類,它們只提供接口,由於 WebKit 須要支持不一樣的移植。而這兩個抽象基類的具體繪製則由不一樣的移植提供不一樣的實現,每一個移植使用的實際繪圖類很是不同,依賴的圖形率也不同。
2D 繪圖上下文的具體做用就是提供基本繪圖單元的繪製接口以及設置繪圖的樣式。繪圖接口包括畫點,畫線、畫圖片、畫多邊形、畫文字等,繪圖樣式包括顏色、線寬、字號大小、漸變等。RenderObject 對象知道本身須要畫什麼樣的點,什麼樣的圖片,因此 RenderObject 對象調用繪圖上下文的這些基本操做就是繪製實際的顯示結果。關係看 圖 7-8 。
關於 3D 繪圖上下文,它的主要用處是支持 CSS3D、WebGL 等。
在現有的網頁中,因爲 HTML5 標準引入了不少新的技術,因此同一網頁中可能既須要使用 2D 繪圖上下文,也須要使用 3D 繪圖上下文。對於 2D 繪圖上下文來講,其平臺相關的實現既可使用 CPU 來完成 2D 相關的操做,也可使用 3D 圖形接口(如 OpenGL)來完成 2D 的操做。而對於 3D 繪圖上下文來講,由於性能問題,WebKit 的移植一般都是使用 3D 圖形接口(如 OpenGL 或者 Direct3D 等技術)來實現。
在完成構建 DOM 樹以後,WebKit 會構建渲染的內部表示並使用圖形庫將這些模型繪製出來。 網頁的渲染方式,有三種方式,一是軟件渲染,二是硬件加速渲染,三是混合模式。
每一個 RenderLayer 對象能夠被想象成圖像中的一個層,各個層一同構成了一個圖像。在渲染的過程當中,瀏覽器也能夠做一樣的理解。每一個層對應網頁中的一個或者一些可視元素,這些元素都繪製內容到該層上,在本書中,一概把這一過程稱爲繪圖操做。
若是繪圖操做使用 CPU 來完成,稱之爲軟件繪圖。若是繪圖操做由 GPU 來完成,稱之爲 GPU 硬件加速繪圖。理想狀況下,每一個層都有個繪製的存儲區域,這個存儲區域用來保存繪圖的結果。最後,須要將這些層的內容合併到同一個圖像之中,本書稱之爲合成(Compositing),使用了合成技術的渲染稱之爲合成化渲染。
因此在 RenderObject 樹和 RenderLayer 樹以後,WebKit 的機制操做將內部模型轉換成可視的結果分爲兩個階段:每層的內部進行繪圖工做及以後將這些繪圖的結果合成一個圖像。對於軟件渲染機制,WebKit 須要使用 CPU 來繪製每層的內容,而軟件渲染機制是沒有合成階段的,由於沒有必要,在軟件渲染中,一般渲染的結果就是一個位圖(Bitmap),繪製每一層的時候都使用該位圖,區別在於繪製的位置可能不同,固然每一層都按照從後到前的順序。固然,你也能夠爲每層分配一個位圖,問題是,一個位圖就已經可以解決全部的問題。
從上圖可能看到,軟件渲染中網頁使用的一個位圖,實際上就是一塊 CPU 使用的內存空間。而圖中的第二和第三種方式,都是使用了合成化的渲染技術,也就是使用 GPU 硬件來加速合成這些網頁層,合成的工做都是由 GPU 來作,稱爲硬件加速合成(Accelerated Compositing)。可是,對於每一個層,這兩種方式有不一樣的選擇,其中某些層,第二種方式使用 CPU 來繪圖,另一些層使用 GPU 來繪圖。對於使用 CPU 來繪圖的層,該層的結果首先固然保存在 CPU 內存中,以後被傳輸到 GPU 的內存中,這主要是爲了後面的合成工做。第三種渲染方式使用使用 GPU 來繪製全部合成層。第二和第三種方式其實都屬於硬件加速渲染方式。前面的這些描述,是把 RenderLayer 對象和實際的存儲空間對應,現實中不是這樣的,這只是理想的狀況。
渲染的基本知識:
首先,對於常見的 2D 繪圖操做,使用 GPU 來繪圖不必定比使用 CPU 繪圖在性能上有優點,例如繪製文字、點、線等,緣由是 CPU 的使用緩存機制有效減小了重複繪製的開銷並且不須要 GPU 並行性。
其次,GPU 的內存資源相對 CPU 的內存資源來講比較緊張,並且網頁的分層使得 GPU 的內存使用相對較多。
因此就目前的狀況來看,三者的存在是有其合理性的。
在不少狀況下,也就是沒有那些須要硬件加速內容的時候,WebKit 可使用軟件渲染技術來完成頁面的繪製工做(除非讀者強行打開硬件加速機制),目前用戶瀏覽的不少門戶網站、論壇網站、社交網站等所設計的網頁,都是採用這項技術來完成頁面的渲染。
而軟件渲染過程須要關注兩個方面,一是 RenderLayer 樹,二是每一個 RenderLayer 所包含的 RenderObject 樹。WebKit 遍歷 RenderLayer 樹來繪製各個層。
對於每一個 RenderObject 對象,須要三個階段繪製本身。
一是繪製該層中全部塊的背景和邊框
二是繪製浮動內容
三是前景(Foreground),也就是內容部分、輪廓等部分。固然,每一個階段還可能會有一些子階段。
值得指出的是,內嵌元素的背景、邊框、前景等都是在第三階段中被繪製的。
圖 7-10 描述了一個 RenderLayer 層是如何繪製本身和子女的,這過程是一個遞歸過程。 且是一個大體的過程。
最開始的時候,也就是 WebKit 第一次繪製網頁的時候,WebKit 繪製的區域等同於可視區域大小。而這在以後,WebKit 只是首先計算須要更新的區域,而後繪製同這些區域有交集的 RenderObject 節點。也就是說,若是更新區域跟某個 RenderLayer 節點有交集,WebKit 會斷續查找 RenderLayer 樹中包含的 RenderObject 子樹中的特定一個或一些節點,而不是繪製整個 RenderLayer 對應的 RenderObject 子樹。圖 7-12 描述了在軟件渲染過程當中 WebKit 實際更新的區域,也就是以前描述軟件渲染過程的生成結果。
Chromium 的設計與實現中,由於引入了多進程模型,因此 Chromium 須要將渲染結果從 Renderer 進程傳遞到 Browser 進程。
先是 Renderer 進程。
WebKit 的 Chromium 移植的接口類是 RenderViewImpl,該類包含一個用於表示一個網頁的渲染結果的 WebViewImpl 類。其實 RenderViewImpl 類還有一個做用就是同 Browser 進程通訊,因此它繼承自 RenderWidget 類。RenderWidget 類不只負責頁面渲染和頁面更新到實際的 WebViewImpl 類等操做,並且它負責同 Browser 進程的通訊。
另一個重要的設施是 PlatformCanvas 類,也就是 SkiaCanvas(Skia j是一個 2D 圖形庫),RenderObject 樹的實際繪製操做和繪製結果都由該類來完成,它相似於 2D 繪圖上下文和後端存儲的結合體。
再次是 Browser 進程。
第一個設施就是 RenderWidgetHost 類,同樣的必不可少,它負責同 Renderer 進程的通訊。RenderWidgetHost 類的做用是傳遞 Browser 進程中網頁操做的請求給 Renderer 進程的 RenderWidget 類,並接收自對方的請求。
第二個是 BackingStore 類,顧名思義,它就是一個後端的存儲空間,它的大小一般就是網頁可視區域的大小,該空間存儲的數據就是頁面的顯示結果。
BackingStore 類的做用很明顯,第一,它保存當前的可視結果,因此 Renderer 進程的繪製工做不會影響該網頁結果的顯示;第二,WebKit 只須要繪製網頁的變更部分,由於其他的部分保存在該後端存儲空間,Chromium 只須要將網頁的變更更新到該後端存儲中便可。
最後是兩個進程傳遞信息和繪製內容的實現過程。
兩個進程傳遞繪製結果是經過 TransportDIDB 類來完成,該類在 Linux 系統下實際上是一個共享內存的實現。對 Renderer 進程來講,Skia Canvvas 把內容繪製到位圖中,該位圖的後端便是共享的 CPU 內存。當 Browser 進程接收到 Renderer 進程關於繪製完成的通知信息,Browser 進程會把共享內存的內容複製到 BackingStore 對象中,而後釋放共享內存。
根據上面的組成部分,一個多進程軟件渲染過程大體以下:
RenderWidget 類接收到更新請求時,Chromium 建立一個共享內存區域。而後 Chromium 建立 Skia 的 SkCanvas 對象,而且 RenderWidget 會把實際繪製的工做派發給 RenderObject 樹。具體來說,WebKit 負責遍歷 RenderObject 樹,每一個 RenderObject 節點根據須要來繪製本身和子女的內容並存儲到目標存儲空間,也就是 SkCanvas 對象所對應的共享內存的位圖中。最後,RenderWidgetHost 類把位圖複製到 BackingStore 對象相應區域中,並調用 」Pint「 函數來把結果繪製到窗口中。
兩種會觸發從新繪製網頁某些區域的請求:
前端請求: 該類型的請求從 Browser 進程發起的請求,多是瀏覽器自身的一些需求,也有多是 X 窗口系統(或者其餘窗口系統)的請求。一個典型的例子就是用戶因操做網頁引發的變化。
後端請求: 因爲頁面自制的邏輯而發起更新部分區域的請求,例如 HTML 元素或者樣式的改變、動畫等。一個典型的例子是 JavaScript 代碼每隔 50ms 便會更新網頁樣式,這時樣式更新會觸發部分區域的重繪。
Renderer 進程的消息循環(Message Loop)調用處理 」界面失效「的回調函數,該函數主要調用 RenderWidget::DoDeferredUpdate 來完成繪製請求。
RenderWidget::DoDeferredUpdate 函數首先調用 Layout 函數來觸發檢查是否有須要從新計算的佈局和更新請求。
RenderWidget 類調用 TransportDIB 類來建立共享內存,內存大小爲繪製區域的 高X寬X4 ,同時調用 Skia 圖形庫來建立一個 SkCanvas 對象。SKCanvas cf 對象的繪製目標是一個使用共享內存存儲的位圖。
當渲染該頁面的所有或者部分時,ScrollView 類請求按照從前到後的順序遍歷並繪製全部 RenderLayer 對象的內容到目標的位圖中。WebKit 繪製每一個 RenderLay 對象經過如下步驟來完成:首先 WebKit 計算重繪的區域是否呼 RenderLyaer 對象有重疊,若是有,WebKit 要求繪製該層中的所在 RenderObject 對象。
繪製完成後,Renderer 進程發送 UpdateRect 的消息給 Browser 進程,Renderer 進程同時返回以完成渲染的過程。Browser 進程接收到消息後首先由 BackingStoreManagere 類來獲取或者建立 BackingStoreX 對象(在Linux 平臺上),BackingStoreX 對象的大小與可視區域相同,包含整個網頁的座標信息,它根據 UpdateRect 的更新區域的位置信息將共享內存的內容繪製到本身的對應存儲區域中。
最後 Browser 進程將 UpdataRect 的回覆消息發送到 Renderer 進程,這是由於 Renderer 進程知道 Browser 進程已經使用完該共享內存,可能進行回收利用等操做,就樣就完成了整個過程。
一個 RenderObject 對象保存了爲繪製 DOM 節點所須要的各類信息
RenderObject 樹是基於 DOM 樹創建起來的一棵新樹,是爲了佈局計算和渲染等機制而構建的一種新的內部表示。RenderObject 樹節點和 DOM 節點不是一一對應關係
WebKit 在建立 DOM 樹的同時也建立 RenderObject 對象。若是 DOM 樹被動態加入了新節點,WebKit 也可能建立相應的 RenderObject 對象。
網頁是有層次結構的,能夠分層的,RenderLayer 樹是基於 RenderObject 樹創建起來的一棵新樹。
RenderObject 對象是用繪圖上下文來繪製內容的,全部繪圖的操做都是在該上下文中來進行的。
Chromium 須要將渲染結果從 Renderer 進程傳遞到 Browser 進程
但願本文對你有點幫助。
下期分享 第八章 硬件加速機制 敬請期待。