轉自http://www.ccthere.com/article/2205048
從指定一個HTML文字檔,到繪製出一幅布局複雜,字型多樣,內含圖片音頻視頻等等多媒體內容的網頁,這是一個複雜的過程。在這個過程中
Webkit所做的一切,都是圍繞DOM Tree和Rendering
Tree這兩個核心。上一章我們談到這兩棵樹各自的功用,這一章,我們借一個簡單的HTML檔案,展示一下DOM Tree和Rendering
Tree的具體構成,同時解剖一下Webkit是如何構造這兩棵樹的。
Figure 1. From HTML to webpage, and the underlying DOM tree and rendering tree.
Courtesy連結出處
1. DOM Tree 與 Rendering Tree 的結構
Figure
1中左上是一個簡單的HTML文字檔,右上是Webkit rendering
engine繪製出來的頁面。頁面的內容包括一個標題,“AI”,一行本文,“Ape's
Intelligence”,以及一幅照片。整個頁面分成前後兩個層面,標題和本文繪製在前一個層面,照片處於後一個層面。L君和我亦步亦趨地跟蹤了
Webkit,從解析這個HTML文字檔,到產生DOM Tree和Rendering Tree的各個步驟,目的是為了瞭解DOM
Tree和Rendering Tree的具體構成,和構造過程。
先說Figure 1中左下角的DOM
Tree。基本上HTML文字檔中每個tag,在webkit/webcore/html中都有一個class與之對應。譬如<HTML>
tag 對應HTMLHtmlElement,<HEAD> tag 對應HTMLHeadElement,<STYLE>
tag 對應HTMLStyleElement 等等。比較特別的是DOM
Tree的根節點,HTMLDocument,在HTML文字檔中沒有哪個tag與之對應。關於HTMLDocument的作用,我們稍後介紹。整個
DOM Tree的結構,與HTML文字檔中各個tags的嵌套關係也一一對應。一言以蔽之,DOM
Tree就是把HTML文字檔翻譯成object樹狀結構。
需要強調的是,DOM
Tree是一個通用資料結構,任何XML文字檔都可以翻譯成DOM Tree,而不僅僅限於HTML文字檔。webkit/webcore/html
中林林總總html classes,基本上都是webkit/webcore/dom 中的某個class的子類,也就是說,/html 是
/dom的一個特例。這樣的設計,為將來把Webkit拓展到HTML格式以外的頁面的布局和渲染,埋下了伏筆。所以嚴格地講,Figure
1中左下的DOM Tree,實際上是一個HTML DOM Tree。
再看Rendering Tree,顯著的特點在於,
a. 整個Rendering Tree樹狀結構,與HTML DOM Tree樹狀結構一一對應。也就是說,幾乎每個HTML DOM Tree中的節點,在Rendering Tree中都有對應的節點。節點與節點之間的父子或兄弟關係也一一對應。
例
外的是,在HTML DOM Tree有HTMLStyleElement葉子節點,而在Rendering
Tree中,沒有相應的葉子節點。原因是,Rendering
Tree各個節點,都涉及頁面中某塊地區的布局和渲染。而HTMLStyleElement,並不直接涉及某塊地區的布局和渲染,HTML DOM
Tree中HTMLStyleElement葉子節點包含的內容,已經融入Rendering
Tree中RenderImage葉子節點的屬性中去了。另外,因為Rendering
Tree中不存在與HTMLStyleElement相應的葉子節點,所以,與HTMLHeadElement對應的節點也沒有必要存在。
b. webkit/webcore/rendering中各個class與HTML tags並沒有一一對應的關係。
Rendering
Tree是一個通用的規劃頁面配置和渲染的機制,這個通用機制可以服務於HTML頁面,但是並不僅僅限於為HTML頁面服務,我們可以用
Rendering Tree來規劃其它格式的頁面的布局和渲染。以DOM Tree和Rendering
Tree為核心的Webkit渲染機,是一個功能強大,擴充性良好的通用渲染機。它不僅可以用來繪製HTML頁面,也可以用來渲染其它格式的頁面,譬如可
以用它來製作email閱讀和管理器,製作資料庫管理工具,甚至製作遊戲介面。
稍微讓人有點吃驚的是,對於
HTMLHtmlElement,HTMLBodyElement,HTMLHeadingElement和HTMLParagraphElement,
在Rendering
Tree中通通以RenderBlock呼應。如果說HTMLHeadingElement和HTMLParagraphElement的區別不大,僅僅
是字型和對齊有些微小的差別,所以Rendering
Tree可以用RenderBlock來統一應對。那麼問題是,HTMLHtmlElement和HTMLBodyElement是兩種容器,總是出現在
DOM Tree的中部,而從來不會作為葉子節點出現,對應於這樣的容器節點,為什麼Rendering
Tree不另設一種class,與RenderBlock有所區別呢?不過話又說回來,這不是個大問題,最多是個美感的問題。
Figure 2. The construction sequence of the root of the DOM tree.
Courtesy連結出處
2. DOM Tree 與 Rendering Tree 的根節點
前一節中我們提到HTMLDocument是一個比較特殊的class,它是整個HTML DOM Tree的根節點,但是不對應任何HTML tag。JavaScript中經常出現的document,指的就是這個根。例如,
“document.getElementById(x).style.background="yellow";”
HTML文字檔,通常是以<HTML>開頭,以</HTML>結尾。但是<HTML> tag並不對應DOM Tree的根節點,而是根以下的第一個子節點,即HTMLHtmlElement節點。
初
看Figure 2 覺得有點意外,當使用者在瀏覽器裡開啟一個空白頁面的時候,立刻產生了DOM
Tree的根節點HTMLDocument,與Rendering
Tree的根節點RenderView。而這個時候,使用者並沒有給定URL,也就是說,對於瀏覽器來講,這時候具體的HTML文字檔並不存在。根節點與
具體HTML內容相脫節,或許暗示了Webkit的兩個設計思路,
a. DOM Tree的根節點HTMLDocument,與Rendering Tree的根節點RenderView,可以重複利用。
當
使用者在同一個瀏覽器頁面中,先後開啟兩個不同的URLs,也就是兩個不同的HTML文字檔時,HTMLDocument和RenderView兩個根節
點並沒有發生改變,改變的是HTMLHtmlElement以下的子樹,以及對應的Rendering Tree的子樹。
為什麼這樣設計?
原因是HTMLDocument和RenderView服從於瀏覽器頁面的設定,譬如頁面的大小和在整個螢幕中的位置等等。這些設定與頁面中要顯示什麼的
內容無關。同時HTMLDocument綁定HTMLTokenizer和HTMLParser,這兩個構件也與某一個具體的HTML內容無關。
b. 同一個DOM Tree的根節點可以懸掛多個HTML子樹,同一個Rendering Tree的根節點可以懸掛多個RenderBlock子樹。
在
我們目前所見到的瀏覽器中,每一個頁面通常只顯示一個HTML檔案。雖然一個HTML檔案可以分割成多個frames,每個frame承載一個獨立的
HTML檔案,但是從DOM
Tree結構來講,HTMLDocument根節點以下,只有一個子節點,這個子節點是HTMLHtmlElement,它領銜某個HTML文字檔對應
的子樹。Rendering Tree也一樣,目前我們見到的網頁中,一個RenderView根節點以下,也只有一個RenderBlock子節點。
但
是Webkit的設計,卻允許同一個根以下,懸掛多個HTML子樹。雖然我們目前沒有看到一個頁面中,並存多個HTML檔案,並存多個布局和渲染風格的情
景,但是Webkit為將來的拓展留下了空間。前文中所設想的個人化,多皮膚,多視角的瀏覽器頁面繪製,用Webkit實現起來難度不大。
Figure 3. The construction sequence of the DOM Tree and the Rendering Tree.
Courtesy連結出處
3. DOM Tree 與 Rendering Tree 的構築
HTMLDocument
根節點包含的最重要的構件是HTMLTokenizer,而HTMLTokenizer又包含HTMLParser這個構件。HTMLTokenizer
從前到後讀取HTML文字檔中每一個字元,並從中提取出各個HTML tags以及它們的內容。而HTMLParser不僅負責HTML DOM
Tree的構築,而且也同時負責Rendering Tree的構築。
在Figure
3中,從第8步到第11步,HTMLParser根據一個HTML Tag產生一個HTML DOM
Tree節點。從第12步到第17步,產生相應的Rendering Tree的節點,並把它和HTML DOM
Tree的節點勾連在一起。這張圖的細節過多,讀解不容易。Figure 4把第8步到第17步示範了一下。
Figure 4. An illustration of the construction of a DOM tree node and its corresponding Rendering tree node.
Courtesy連結出處
值得注意的是,每當HTMLParser產生一個DOM Tree的節點的時候,相應地,也同時產生一個Rendering Tree節點。然後把它們兩個新節點勾連在一起。換而言之,Rendering Tree與DOM Tree同步生長。
Webkit
值得讚賞的地方非常多,但是HTMLParser讓DOM Tree和Rendering
Tree同步生長的做法,卻值得商榷。如果同步生長,那麼Rendering Tree必然平鋪直敘地刻板地忠實於DOM Tree。假設先產生DOM
Tree,再產生Rendering
Tree,把兩者割裂開,就有機會讓Webkit發揮更加奇妙的布局和渲染。平鋪直敘固然符合大多數人在大多數時間裡的閱讀習慣,但是離經叛道的設計,也
會有市場。一個例子就是上一章末尾處那張多視點的地圖。如果讓DOM Tree與Rendering Tree同步生長,這樣的布局和渲染是難以想像的。