走進WebKit

來源:互聯網
上載者:User

 

Webkit是開源項目,它的原始碼可以去這裡下載,http://webkit.org/building/checkout.html。Webkit是一個相當複雜的軟體系統,開啟原始碼,可以看到裡面有眾多檔案夾。但是Webkit的原始碼組織得很好,雖然程式多,但是結構清楚。相對而言,Firefox瀏覽器使用的Gecko渲染機的原始碼,個人的感覺比較亂,不容易理出頭緒。

毛主席教導我們,“我們的方針是路線決定一切,人多,槍多,代替不了正確的路線。路線正確就有一切,路線不正確有了也可以丟掉。路線是個綱,綱舉目張”。對於軟體工程,主席的教導依然具有指導意義。不妨把主席的教導改動一下,“軟體工程的方針是結構決定一切,代碼多,功能多,代替不了正確的結構。結構正確了,不斷改進以後就有一切,結構不正確,有了也可以丟掉。結構是個綱,綱舉目張”。現代軟體工程的語彙裡,結構多半特指object-oriented的結構。Webkit原始碼十分嚴格地遵循object-oriented的原則來組織,這樣做的好處不僅僅有利於後續開發和維護,而且也便於研讀原始碼。

Webkit
原始碼由三大部分組成,1. WebCore,2. WebKit,3. JavaScript。當輸入一個HTML檔案,WebCore的職責是解析每個HTML
Tag,以及它們之間的從屬關係,產生一棵樹狀的資料結構(DOM Tree),然後結合HTML檔案中指定的CSS定義,確定DOM
Tree中每個節點的在整個頁面中的位置,以及顏色字型等等,也就是布局與渲染。布局與渲染效果的確定,以屬性和屬性值的形式,存放在另一棵樹狀資料結構中,這另一棵樹被稱為Rendering
Tree。通常情況下,Rendering Tree的結構與DOM Tree大致相同,基本上DOM Tree裡面每一個節點,在Rendering
Tree裡都有對應的節點。

這裡有兩個疑問,1. 為什麼既有DOM Tree,又有Rendering Tree,合二為一不是更省記憶體嗎?2.
Rendering Tree為什麼要與DOM Tree保持一致?不一樣又如何?這兩個問題,我們稍後討論。

Rendering
Tree只是確定了該如何渲染HTML頁面,有點像司令部裡的參謀們制訂作戰計劃。但是具體的渲染,包括畫點畫線字型等等,在不同的OS,甚至不同的硬體環境下,實現的方式各不相同,這就像衝鋒陷陣,還得靠前線將士。Webkit原始碼中的WebKit
package,裡麵包含的程式,就是這些前線將士。WebKit package中有 win, mac,  gtk, qt, wx等等
subpackages,就是針對各個不同OSes,以及各種不同的跨平台的圖形庫,所採取的因地制宜的渲染手段。與各個不同的OSes相關的,不僅僅是渲染,還有滑鼠移動和鍵盤點擊等等使用者觸發的UI事件的捕捉。所有這些與具體Oses相關的程式,通通被放置在WebKit
package中。

Webkit原始碼中第三個主要部分,是JavaScript engine。JavaScript
engine用來解析JavaScript代碼,並執行。這個系列裡不展開介紹JavaScript engine。

DOM Tree 與
Rendering Tree 是否必須一致?


天下文章一大抄”,這話說來難聽,但是卻是實情。很少有文章從頭到尾,字字原創,而絕大多數都是在消化整理前人和他人的知識基礎上,添加自己的一點點發揮和創造而成。人類的文明進步,就是這樣一點一滴,逐漸積累起來的。既然文章離不開引經據典,旁徵博引,閱讀文章的時候,也就免不了需要查閱相關文獻。在Web出現以前,查閱文獻是一件費時費力的事情。1989年3月,在歐洲原子能研究組織(CERN)工作的Tim
Berners-Lee,提議在TCP/IP協議基礎之上,建立一個相互連結的資訊系統。這個建議很快發展成為全球資訊網(World Wide
Web,簡稱Web),極大地方便了文獻的查閱。Web的核心,是hyperlink。在撰寫網頁時,可以對於某些詞句,設定隱含的
hyperlinks。當讀者點擊這些詞句時,電腦就會根據詞句背後隱含的hyperlink,自動開啟另一個網頁。請注意“隱含的”這個詞,這意味著文章的顯示(Presentation),與文章的內容(Content)並不完全一致。Tim Berners-Lee考慮到這一問題,於是建議給每個Web網頁制訂一個規範,這個規範就是“超文本標識語言”,簡稱HTML。最初的HTML制訂了
22個標識,標註兩方面的功能,1. 內容,包括引用,2.
顯示格式。在同一份HTML檔案裡,把內容和格式混雜在一起,這是一個設計錯誤。20年過去了,現在的HTML檔案,基本上是以內容為主,格式被分離出去,由CSS定義。除此之外,還增加了與讀者互動的動作,這些互動動作,通常由JavaScript定義。

為什麼分離比合并好?同一份內容,針對不同的讀者,可以通過不同的CSS,改變顯示的方式,舉幾個例子。針對新人類,可以用卡通表徵圖替代某些特定文字。針對老派讀者可以換用大號字型,從上往下,從右往左排版。對於正在開車的聽眾,可以用朗誦替代文字顯示。

內容與格式分離,並不等同於DOM
Tree與Rendering Tree並存。假如內容由HTML承載,格式由CSS定義,我們可以只用一棵樹,不僅存放內容,也存放格式屬性。DOM
Tree與Rendering Tree分離,好處在於同一棵DOM Tree,可以對應多棵Rendering
Trees,也就是同一個內容,可以由多種不同的方式來布局和渲染。在當今的瀏覽器裡,一棵DOM Tree對應多棵Rendering
Trees的情況不常見,因為同一個頁面,通常只有一種風格的布局和渲染。但是在電子遊戲中,同一個情境會有多個不同視角,譬如槍戰遊戲中,有槍手本人視角,有旁觀者視角,還有俯瞰視角等等。換句話說,Webkit不僅滿足了當今瀏覽器的普通需要,而且提供了一些尚沒有被廣泛利用的潛在的功能。把DOM
Tree與Rendering
Tree分離的做法,雖然浪費了一些記憶體空間,但是著眼於未來,Webkit這樣的結構設計,為未來的發展埋下了伏筆。

目前而言,Rendering
Tree的結構基本上與DOM Tree的保持一致,DOM Tree裡每一個節點,在Rendering
Tree都有對應節點,父子節點之間的繼承關係也保持一致。但是Webkit的代碼,給Rendering Tree結構的異化,也埋下了伏筆。Rendering
Tree的結構,不一定與DOM
Tree保持一致。舉個例子,或許未來的網頁可以提供個人化的編輯方式。當讀者第一次開啟某個頁面的時候,看到的是標準的頁面,也就是Rendering
Tree與DOM
Tree保持一致的頁面。讀者可以摘錄網頁中某些段落,刪除不感興趣的段落,改變著色,改變字型,甚至重新布局。以後再次造訪這個頁面時,這位讀者就可以看到個人化的頁面,而所謂個人化的實現方式,其實就是構築另一棵Rendering
Tree。假如他想恢複標準的網頁,只需要重新調用標準的Rendering Tree,重新渲染一遍網頁即可。在這個例子裡,我們可以看到個人化的Rendering
Tree,與DOM Tree保持一致的標準的Rendering
Tree,兩者並存的情境。

再舉一個例子,通常的地圖,都是俯瞰圖,整個地圖保持同一種視角。能不能在同一張地圖中包含多個視角?聽起來匪夷所思,但是有人嘗試了這種新奇大膽的設想,見figure
1。如果套用DOM Tree和Rendering Tree的概念,可以解釋為DOM Tree與Rendering
Tree不完全一致。上半段的俯瞰視角的地圖,對應的Rendering Tree的子樹,與DOM
Tree相關子樹一一對應。但是下半段的平視視角的地圖,對應的Rendering Tree的子樹,是DOM
Tree相關子樹的一個子集,因為有些樓宇和道路,被前方的樓宇遮擋住了。

Figure 1. Here-and-there map of Mahanttan
Courtesy

http://farm3.static.flickr.com/2464/3545962330_8bc666f68e_o.jpg

WebKit的結構與解構

 

從指定一個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

http://farm4.static.flickr.com/3351/3556972420_23a30366c2_o.jpg

1. DOM
Tree 與 Rendering Tree 的結構

Figure 1中左上是一個簡單的HTML文字檔,右上是Webkit rendering
engine繪製出來的頁面。頁面的內容包括一個標題,“AI”,一行本文,“Ape's
Intelligence”,以及一幅照片。整個頁面分成前後兩個層面,標題和本文繪製在前一個層面,照片處於後一個層面。L君和我亦步亦趨地跟蹤了,從解析這個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

http://farm4.static.flickr.com/3010/3554310018_e34d271344_o.jpg

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

http://farm4.static.flickr.com/3627/3554182242_b0bec88534_b.jpg

 
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

http://farm4.static.flickr.com/3306/3554259140_3deb9736ea_o.jpg

值得注意的是,每當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同步生長,這樣的布局和渲染是難以想像的。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.