瀏覽器的工作原理

簡介

瀏覽器可以被認為是使用最廣泛的軟件屿岂,本文將介紹瀏覽器的工 作原理痰洒,我們將看到崇呵,從你在地址欄輸入google.com到你看到google主頁過程中都發(fā)生了什么。

將討論的瀏覽器

今天学辱,有五種主流瀏覽器——IE、Firefox环形、Safari策泣、Chrome及Opera。 本文將基于一些開源瀏覽器的例子——Firefox抬吟、 Chrome及Safari萨咕,Safari是部分開源的。 根據(jù)W3C(World Wide Web Consortium 萬維網(wǎng)聯(lián)盟)的瀏覽器統(tǒng)計數(shù)據(jù)火本,當前(2011年9月)危队,F(xiàn)irefox、Safari及Chrome的市場占有率綜合已快接近50%钙畔。(原文為2009年10月茫陆,數(shù)據(jù)沒有太大變化)因此,可以說開源瀏覽器將近占據(jù)了瀏覽器市場的半壁江山刃鳄。

瀏覽器的主要功能

瀏覽器的主要功能是將用戶選擇得web資源呈現(xiàn)出來盅弛,它需要從服務器請求資源,并將其顯示在瀏覽器窗口中叔锐,資源的格式通常是HTML挪鹏,也包括PDF、image及其他格式愉烙。用戶用URI(Uniform Resource Identifier 統(tǒng)一資源標識符)來指定所請求資源的位置讨盒,在網(wǎng)絡一章有更多討論。 HTML和CSS規(guī)范中規(guī)定了瀏覽器解釋html文檔的方式步责,由 W3C組織對這些規(guī)范進行維護返顺,W3C是負責制定web標準的組織禀苦。 HTML規(guī)范的最新版本是HTML4(http://www.w3.org/TR/html401/),HTML5還在制定中(譯注:兩年前)遂鹊,最新的CSS規(guī)范版本是2(http://www.w3.org/TR/CSS2)振乏,CSS3也還正在制定中(譯注:同樣兩年前)。 這些年來秉扑,瀏覽器廠商紛紛開發(fā)自己的擴展慧邮,對規(guī)范的遵循并不完善,這為web開發(fā)者帶來了嚴重的兼容性問題舟陆。 但是误澳,瀏覽器的用戶界面則差不多,常見的用戶界面元素包括:

  • 用來輸入URI的地址欄
  • 前進秦躯、后退按鈕
  • 書簽選項
  • 用于刷新及暫停當前加載文檔的刷新忆谓、暫停按鈕
  • 用于到達主頁的主頁按鈕

奇怪的是,并沒有哪個正式公布的規(guī)范對用戶界面做出規(guī)定踱承,這些是多年來各瀏覽器廠商之間相互模仿和不斷改進得結果倡缠。 HTML5并沒有規(guī)定瀏覽器必須具有的UI元素,但列出了一些常用元素勾扭,包括地址欄毡琉、狀態(tài)欄及工具欄。還有一些瀏覽器有自己專有得功能妙色,比如Firefox得下載管理桅滋。更多相關內(nèi)容將在后面討論用戶界面時介紹。

瀏覽器的主要構成(High Level Structure)

瀏覽器的主要組件包括:

  1. 用戶界面- 包括地址欄身辨、后退/前進按鈕丐谋、書簽目錄等,也就是你所看到的除了用來顯示你所請求頁面的主窗口之外的其他部分
  2. 瀏覽器引擎- 用來查詢及操作渲染引擎的接口
  3. 渲染引擎- 用來顯示請求的內(nèi)容煌珊,例如号俐,如果請求內(nèi)容為html,它負責解析html及css定庵,并將解析后的結果顯示出來
  4. 網(wǎng)絡- 用來完成網(wǎng)絡調(diào)用吏饿,例如http請求,它具有平臺無關的接口蔬浙,可以在不同平臺上工作
  5. UI 后端- 用來繪制類似組合選擇框及對話框等基本組件猪落,具有不特定于某個平臺的通用接口,底層使用操作系統(tǒng)的用戶接口
  6. JS解釋器- 用來解釋執(zhí)行JS代碼
  7. 數(shù)據(jù)存儲- 屬于持久層畴博,瀏覽器需要在硬盤中保存類似cookie的各種數(shù)據(jù)笨忌,HTML5定義了web database技術,這是一種輕量級完整的客戶端存儲技術


    圖1:瀏覽器主要組件

    需要注意的是俱病,不同于大部分瀏覽器官疲,Chrome為每個Tab分配了各自的渲染引擎實例袱结,每個Tab就是一個獨立的進程。 對于構成瀏覽器的這些組件途凫,后面會逐一詳細討論垢夹。

組件間的通信 Communication between the components

Firefox和Chrome都開發(fā)了一個特殊的通信結構,后面將有專門的一章進行討論颖榜。

渲染引擎 (The rendering engine)

渲染引擎的職責就是渲染硼砰,即在瀏覽器窗口中顯示所請求的內(nèi)容豹障。 默認情況下累魔,渲染引擎可以顯示html、xml文檔及圖片,它也可以借助插件(一種瀏覽器擴展)顯示其他類型數(shù)據(jù),例如使用PDF閱讀器插件,可以顯示PDF格式,將由專門一章講解插件及擴展搅荞,這里只討論渲染引擎最主要的用途——顯示應用了CSS之后的html及圖片塞栅。

渲染引擎 Rendering engines

本文所討論得瀏覽器——Firefox放椰、Chrome和Safari是基于兩種渲染引擎構建的砾医,F(xiàn)irefox使用Geoko——Mozilla自主研發(fā)的渲染引擎衣厘,Safari和Chrome都使用webkit。 Webkit是一款開源渲染引擎,它本來是為linux平臺研發(fā)的倾芝,后來由Apple移植到Mac及Windows上,相關內(nèi)容請參考http://webkit.org谱姓。

主流程 The main flow

渲染引擎首先通過網(wǎng)絡獲得所請求文檔的內(nèi)容借尿,通常以8K分塊的方式完成。 下面是渲染引擎在取得內(nèi)容之后的基本流程: 解析html以構建dom樹->構建render樹->布局render樹->繪制render樹


圖2:渲染引擎基本流程

渲染引擎開始解析html,并將標簽轉化為內(nèi)容樹中的dom節(jié)點路翻。接著狈癞,它解析外部CSS文件及style標簽中的樣式信息。這些樣式信息以及html中的可見性指令將被用來構建另一棵樹——render樹茂契。

Render樹由一些包含有顏色和大小等屬性的矩形組成蝶桶,它們將被按照正確的順序顯示到屏幕上。 Render樹構建好了之后掉冶,將會執(zhí)行布局過程真竖,它將確定每個節(jié)點在屏幕上的確切坐標。再下一步就是繪制厌小,即遍歷render樹恢共,并使用UI后端層繪制每個節(jié)點。

值得注意的是召锈,這個過程是逐步完成的旁振,為了更好的用戶體驗,渲染引擎將會盡可能早的將內(nèi)容呈現(xiàn)到屏幕上涨岁,并不會等到所有的html都解析完成之后再去構建和布局render樹。它是解析完一部分內(nèi)容就顯示一部分內(nèi)容吉嚣,同時梢薪,可能還在通過網(wǎng)絡下載其余內(nèi)容。

主要流程
圖3:webkit主流程

圖4:Mozilla的Geoko

渲染引擎主流程 從圖3和4中可以看出尝哆,盡管webkit和Gecko使用的術語稍有不同秉撇,他們的主要流程基本相同。Gecko稱可見的格式化元素組成的樹為frame樹秋泄,每個元素都是一個frame琐馆,webkit則使用render樹這個名詞來命名由渲染對象組成的樹。Webkit中元素的定位稱為布局恒序,而Gecko中稱為回流瘦麸。Webkit稱利用dom節(jié)點及樣式信息去構建render樹的過程為attachment,Gecko在html和dom樹之間附加了一層歧胁,這層稱為內(nèi)容接收器滋饲,相當制造dom元素的工廠屠缭。下面將討論流程中的各個階段奄喂。

解析 Parsing-general

既然解析是渲染引擎中一個非常重要的過程玻蝌,我們將稍微深入的研究它。首先簡要介紹一下解析。 解析一個文檔即將其轉換為具有一定意義的結構——編碼可以理解和使用的東西。解析的結果通常是表達文檔結構的節(jié)點樹,稱為解析樹或語法樹巢掺。 例如,解析“2+3-1”這個表達式,可能返回這樣一棵樹拗小。


圖5:數(shù)學表達式樹節(jié)點
文法 Grammars

解析基于文檔依據(jù)的語法規(guī)則——文檔的語言或格式搅幅。每種可被解析的格式必須具有由詞匯及語法規(guī)則組成的特定的文法蝇更,稱為上下文無關文法。人類語言不具有這一特性蚁廓,因此不能被一般的解析技術所解析厨幻。

解析器-詞法分析器 Parser-Lexer combination

解析可以分為兩個子過程——語法分析及詞法分析况脆。

詞法分析就是將輸入分解為符號,符號是語言的詞匯表——基本有效單元的集合满败。對于人類語言來說,它相當于我們字典中出現(xiàn)的所有單詞叹括。 語法分析指對語言應用語法規(guī)則算墨。

解析器一般將工作分配給兩個組件——詞法分析器(有時也叫分詞器)負責將輸入分解為合法的符號,解析器則根據(jù)語言的語法規(guī)則分析文檔結構汁雷,從而構建解析樹净嘀,詞法分析器知道怎么跳過空白和換行之類的無關字符。


圖6:從源文檔到解析樹

解析過程是迭代的侠讯,解析器從詞法分析器處取道一個新的符號挖藏,并試著用這個符號匹配一條語法規(guī)則, 如果匹配了一條規(guī)則厢漩,這個符號對應的節(jié)點將被添加到解析樹上膜眠,然后解析器請求另一個符號。

如果沒有匹配到規(guī)則,解析器將在內(nèi)部保存該符號宵膨,并從詞法分析器 取下一個符號架谎,直到所有內(nèi)部保存的符號能夠匹配一項語法規(guī)則。如果最終沒有找到匹配的規(guī)則辟躏,解析器將拋出一個異常谷扣,這意味著文檔無效或是包含語法錯誤。

轉換 Translation

很多時候捎琐,解析樹并不是最終結果会涎。解析一般在轉換中使用——將輸入文檔轉換為另一種格式。編譯就是個例子野哭,編譯器在將一段源碼編譯為機器碼的時候在塔,先將源碼解析為解析樹,然后將該樹轉換為一個機器碼文檔拨黔。


圖7:編譯流程
解析實例 Parsing example

圖5中蛔溃,我們從一個數(shù)學表達式構建了一個解析樹,這里定義一個簡單的數(shù)學語言來看下解析過程篱蝇。 詞匯表:我們的語言包括整數(shù)贺待、加號及減號。
語法:

  1. 該語言的語法基本單元包括表達式零截、term及操作符
  2. 該語言可以包括多個表達式
  3. 一個表達式定義為兩個term通過一個操作符連接
  4. 操作符可以是加號或減號
  5. term可以是一個整數(shù)或一個表達式

現(xiàn)在來分析一下“2+3-1”這個輸入麸塞、 第一個匹配規(guī)則的子字符串是“2”,根據(jù)規(guī)則5涧衙,它是一個term哪工,第二個匹配的是“2+3”,它符合第2條規(guī)則——一個操作符連接兩個term弧哎,下一次匹配發(fā)生在輸入的結束處雁比。“2+3-1”是一個表達式撤嫩,因為我們已經(jīng)知道“2+3”是一個term偎捎,所以我們有了一個term緊跟著一個操作符及另一個term⌒蛉粒“2++”將不會匹配任何規(guī)則茴她,因此是一個無效輸入。

詞匯表及語法的定義

詞匯表通常利用正則表達式來定義程奠。 例如上面的語言可以定義為:

 INTEGER :0|[1-9][0-9]* 
 PLUS : +  
 MINUS: -

正如看到的丈牢,這里用正則表達式定義整數(shù)。

語法通常用BNF格式定義梦染,我們的語言可以定義為:

expression :=  term operation term
operation :=  PLUS | MINUS 
term := INTEGER | expression  

如果一個語言的文法是上下文無關的赡麦,則它可以用正則解析器來解析朴皆。對上下文無關文法的一個直觀的定義是,該文法可以用BNF來完整的表達泛粹∷煺。可查看http://en.wikipedia.org/wiki/Context-free_grammar

解析器類型 Types of parsers

有兩種基本的解析器——自頂向下解析及自底向上解析晶姊。比較直觀的解釋是扒接,自頂向下解析,查看語法的最高層結構并試著匹配其中一個们衙;自底向上解析則從輸入開始钾怔,逐步將其轉換為語法規(guī)則,從底層規(guī)則開始直到匹配高層規(guī)則蒙挑。

來看一下這兩種解析器如何解析上面的例子:

自頂向下解析器從最高層規(guī)則開始——它先識別出“2+3“宗侦,將其視為一個表達式,然后識別出”2+3-1“為一個表達式(識別表達式的過程中匹配了其他規(guī)則忆蚀,但出發(fā)點是最高層規(guī)則)矾利。 自底向上解析會掃描輸入直到匹配了一條規(guī)則,然后用該規(guī)則取代匹配的輸入馋袜,直到解析完所有輸入男旗。部分匹配的表達式被放置在解析堆棧中。



自底向上解析器稱為shift reduce 解析器欣鳖,因為輸入向右移動(想象一個指針首先指向輸入開始處察皇,并向右移動),并逐漸簡化為語法規(guī)則泽台。

自動化解析 Generating parsers automatically

解析器生成器這個工具可以自動生成解析器什荣,只需要指定語言的文法——詞匯表及語法規(guī)則,它就可以生成一個解析器怀酷。創(chuàng)建一個解析器需要對解析有深入的理解溃睹,而且手動的創(chuàng)建一個由較好性能的解析器并不容易,所以解析生成器很有用胰坟。Webkit使用兩個知名的解析生成器——用于創(chuàng)建語法分析器的Flex及創(chuàng)建解析器的Bison(你可能接觸過Lex和Yacc)。Flex的輸入是一個包含了符號定義的正則表達式泞辐,Bison的輸入是用BNF格式表示的語法規(guī)則笔横。

HTML解析器 HTML Parser HTML解析器的工作是將html標識解析為解析樹。

HTML文法定義 The HTML grammar definition

W3C組織制定規(guī)范定義了HTML的詞匯表和語法咐吼。

非上下文無關文法 Not a context free grammar

正如在解析簡介中提到的吹缔,上下文無關文法的語法可以用類似BNF的格式來定義。 不幸的是锯茄,所有的傳統(tǒng)解析方式都不適用于html(當然我提出它們并不只是因為好玩厢塘,它們將用來解析css和js)茶没,html不能簡單的用解析所需的上下文無關文法來定義。

Html 有一個正式的格式定義——DTD(Document Type Definition 文檔類型定義)——但它并不是上下文無關文法晚碾。 html更接近于xml抓半,現(xiàn)在有很多可用的xml解析器,html有個xml的變體——xhtml格嘁,它們間的不同在于笛求,html更寬容,它允許忽略一些特定標簽糕簿,有時可以省略開始或結束標簽探入。總的來說懂诗,它是一種soft語法蜂嗽,不像xml呆板、固執(zhí)殃恒。 顯然植旧,這個看起來很小的差異卻帶來了很大的不同。一方面芋类,這是html流行的原因——它的寬容使web開發(fā)人員的工作更加輕松隆嗅,但另一方面,這也使很難去寫一個格式化的文法侯繁。所以掖鱼,html的解析并不簡單,它既不能用傳統(tǒng)的解析器解析祖娘,也不能用xml解析器解析掷倔。

HTML DTD

Html適用DTD格式進行定義,這一格式是用于定義SGML家族的語言咕别,包括了對所有允許元素及它們的屬性和層次關系的定義技健。 正如前面提到的,html DTD并沒有生成一種上下文無關文法惰拱。 DTD有一些變種雌贱,標準模式只遵守規(guī)范,而其他模式則包含了對瀏覽器過去所使用標簽的支持偿短,這么做是為了兼容以前內(nèi)容欣孤。最新的標準DTD在http://www.w3.org/TR/html4/strict.dtd

DOM

輸出的樹昔逗,也就是解析樹降传,是由DOM元素及屬性節(jié)點組成的。DOM是文檔對象模型的縮寫勾怒,它是html文檔的對象表示婆排,作為html元素的外部接口供js等調(diào)用声旺。 樹的根是“document”對象。 DOM和標簽基本是一一對應的關系段只,例如腮猖,如下的標簽:

<html> 
    <body>
        <p>             Hello World         </p>
        <div> <img src="example.png"/></div>
    </body>
</html>  

將會被轉換為下面的DOM樹:


圖8:示例標簽對應的DOM樹

和html一樣,DOM的規(guī)范也是由W3C組織制定的翼悴。訪問http://www.w3.org/DOM/DOMTR缚够,這是使用文檔的一般規(guī)范。一個模型描述一種特定的html元素鹦赎,可以在http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.htm 查看html定義谍椅。 這里所謂的樹包含了DOM節(jié)點是說樹是由實現(xiàn)了DOM接口的元素構建而成的,瀏覽器使用已被瀏覽器內(nèi)部使用的其他屬性的具體實現(xiàn)古话。

解析算法 The parsing algorithm

正如前面章節(jié)中討論的雏吭,hmtl不能被一般的自頂向下或自底向上的解析器所解析。 原因是:

  1. 這門語言本身的寬容特性
  2. 瀏覽器對一些常見的非法html有容錯機制
  3. 解析過程是往復的陪踩,通常源碼不會在解析過程中發(fā)生改變杖们,但在html中,腳本標簽包含的“document.write ”可能添加標簽肩狂,這說明在解析過程中實際上修改了輸入.

不能使用正則解析技術摘完,瀏覽器為html定制了專屬的解析器。
Html5規(guī)范中描述了這個解析算法傻谁,算法包括兩個階段——符號化及構建樹孝治。
符號化是詞法分析的過程,將輸入解析為符號审磁,html的符號包括開始標簽谈飒、結束標簽、屬性名及屬性值态蒂。
符號識別器識別出符號后杭措,將其傳遞給樹構建器,并讀取下一個字符钾恢,以識別下一個符號手素,這樣直到處理完所有輸入。

圖9:HTML解析流程

符號識別算法 The tokenization algorithm

算法輸出html符號瘩蚪,該算法用狀態(tài)機表示刑桑。每次讀取輸入流中的一個或多個字符,并根據(jù)這些字符轉移到下一個狀態(tài)募舟,當前的符號狀態(tài)及構建樹狀態(tài)共同影響結果,這意味著闻察,讀取同樣的字符拱礁,可能因為當前狀態(tài)的不同琢锋,得到不同的結果以進入下一個正確的狀態(tài)。 這個算法很復雜呢灶,這里用一個簡單的例子來解釋這個原理吴超。 基本示例——符號化下面的html:

  <html>
    <body>          Hello world     </body>
  </html>  

初始狀態(tài)為“Data State”,當遇到“<”字符鸯乃,狀態(tài)變?yōu)?“Tag open state”鲸阻,讀取一個a-z的字符將產(chǎn)生一個開始標簽符號,狀態(tài)相應變?yōu)?“Tag name state”缨睡,一直保持這個狀態(tài)直到讀取到“>”鸟悴,每個字符都附加到這個符號名上,例子中創(chuàng)建的是一個html符號奖年。
當讀取到“>”细诸,當前的符號就完成了,此時陋守,狀態(tài)回到 “Data state”震贵,“”重復這一處理過程。到這里水评,html和body標簽都識別出來了⌒上担現(xiàn)在,回到“Data state”中燥,讀取“Hello world”中的字符“H”將創(chuàng)建并識別出一個字符符號寇甸,這里會為“Hello world”中的每個字符生成一個字符符號。 這樣直到遇到“”中的“<”⊥誓牵現(xiàn)在幽纷,又回到了“Tag open state”,讀取下一個字符“/”將創(chuàng)建一個閉合標簽符號博敬,并且狀態(tài)轉移到“Tag name state”友浸,還是保持這一狀態(tài),直到遇到“>”偏窝。然后收恢,產(chǎn)生一個新的標簽符號并回到“Data state”伦意。后面的“”將和“”一樣處理驮肉。

圖10:符號化示例輸入

樹的構建算法 Tree construction algorithm

在樹的構建階段票编,將修改以Document為根的DOM樹慧域,將元素附加到樹上。每個由符號識別器識別生成的節(jié)點將會被樹構造器進行處理浪读,規(guī)范中定義了每個符號相對應的Dom元素昔榴,對應的Dom元素將會被創(chuàng)建。這些元素除了會被添加到Dom樹上碘橘,還將被添加到開放元素堆棧中互订。這個堆棧用來糾正嵌套的未匹配和未閉合標簽,這個算法也是用狀態(tài)機來描述蛹屿,所有的狀態(tài)采用插入模式屁奏。
來看一下示例中樹的創(chuàng)建過程:

<html>
        <body>
            Hello world     
        </body>
  </html>  

構建樹這一階段的輸入是符號識別階段生成的符號序列。 首先是“initial mode”错负,接收到html符號后將轉換為“before html”模式坟瓢,在這個模式中對這個符號進行再處理。此時犹撒,創(chuàng)建了一個HTMLHtmlElement元素折联,并將其附加到根Document對象上。
狀態(tài)此時變?yōu)?strong>“before head”识颊,接收到body符號時清笨,即使這里沒有head符號桨昙,也將自動創(chuàng)建一個HTMLHeadElement元素并附加到樹上。
現(xiàn)在凹蜂,轉到“in head”模式,然后是“after head”菱父。到這里官辽,body符號會被再次處理俗批,將創(chuàng)建一個HTMLBodyElement并插入到樹中,同時,轉移到“in body”模式麻汰。
然后臣镣,接收到字符串“Hello world”的字符符號,第一個字符將導致創(chuàng)建并插入一個text節(jié)點,其他字符將附加到該節(jié)點苗踪。 接收到body結束符號時颅夺,轉移到“after body”模式,接著接收到html結束符號,這個符號意味著轉移到了“after after body”模式,當接收到文件結束符時昌讲,整個解析過程結束筹裕。 !

圖11:示例html樹的構建過程
解析結束時的處理 Action when the parsing is finished

在這個階段抗斤,瀏覽器將文檔標記為可交互的,并開始解析處于延時模式中的腳本——這些腳本在文檔解析后執(zhí)行辆影。 文檔狀態(tài)將被設置為完成灭衷,同時觸發(fā)一個load事件。 Html5規(guī)范中有符號化及構建樹的完整算法(http://www.w3.org/TR/html5/syntax.html#html-parser)傅蹂。

瀏覽器容錯 Browsers error tolerance 你從來不會在一個html頁面上看到“無效語法”這樣的錯誤氓轰,瀏覽器修復了無效內(nèi)容并繼續(xù)工作。 以下面這段html為例:
  <html>
   <mytag>
   </mytag>
   <div>
   <p>
   </div>
    Really lousy HTML
   </p>
  </html>   

這段html違反了很多規(guī)則(mytag不是合法的標簽,p及div錯誤的嵌套等等),但是瀏覽器仍然可以沒有任何怨言的繼續(xù)顯示,它在解析的過程中修復了html作者的錯誤。
瀏覽器都具有錯誤處理的能力,但是,另人驚訝的是,這并不是html最新規(guī)范的內(nèi)容畦戒,就像書簽及前進后退按鈕一樣垃环,它只是瀏覽器長期發(fā)展的結果秸谢。
一些比較知名的非法html結構元媚,在許多站點中出現(xiàn)過,瀏覽器都試著以一種和其他瀏覽器一致的方式去修復待逞。 Html5規(guī)范定義了這方面的需求甥角,webkit在html解析類開始部分的注釋中做了很好的總結。 解析器將符號化的輸入解析為文檔并創(chuàng)建文檔识樱,但不幸的是嗤无,我們必須處理很多沒有很好格式化的html文檔,至少要小心下面幾種錯誤情況。

  1. 在未閉合的標簽中添加明確禁止的元素卜壕。這種情況下亲桥,應該先將前一標簽閉合
  2. 不能直接添加元素墓塌。有些人在寫文檔的時候會忘了中間一些標簽(或者中間標簽是可選的)哲泊,比如HTML HEAD BODY TR TD LI等
  3. 想在一個行內(nèi)元素中添加塊狀元素。關閉所有的行內(nèi)元素抑堡,直到下一個更高的塊狀元素
  4. 如果這些都不行,就閉合當前標簽直到可以添加該元素。

下面來看一些webkit容錯的例子:

 </br> 而不是 <br>  

一些網(wǎng)站使用 </br> 而不是 <br>
為了兼容IE和Firefox帅矗,webkit將其看作<br>
代碼:

  if (t->isCloseTag(brTag) && m_document->inCompatMode()) {      reportError(MalformedBRError);      t->beginTag = true;  }  

Note-這里的錯誤處理在內(nèi)部進行达布,用戶看不到匕累。

A stray table

這指一個表格嵌套在另一個表格中瑟枫,但不在它的某個單元格內(nèi)堤结。 比如下面這個例子:

  <table>
    <table>
        <tr><td>inner table</td></tr>
          </table> 
    <tr><td>outer table</td></tr>
  </table>  

webkit將其改變?yōu)閮蓚€兄弟表

  <table>
    <tr><td>outer table</td></tr>
  </table>
  <table>
    <tr><td>inner table</td></tr>  </table>

代碼:

if (m_inStrayTableContent && localName == tableTag)
         popBlock(tableTag);  

webkit使用堆棧存放當前的元素內(nèi)容,它將從外部表格的堆棧中彈出內(nèi)部的表格鸭丛,則它們變?yōu)榱诵值鼙砀瘛?/p>

嵌套的表單元素

用戶將一個表單嵌套到另一個表單中竞穷,則第二個表單將被忽略。
代碼:

if (!m_currentFormElement) {
         m_currentFormElement = new HTMLFormElement(formTag,    m_document);  } 

太深的標簽繼承

如題目鳞溉。
www.liceo.edu.mx是一個由嵌套層次的站點的例子瘾带,最多只允許20個相同類型的標簽嵌套,多出來的將被忽略穿挨。
代碼:

  bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName) 
 {
   unsigned i = 0;
  for (HTMLStackElem* curr = m_blockStack; 
       i < cMaxRedundantTagDepth && curr && curr->tagName == tagName; 
     curr = curr->next, i++) { }
  return i != cMaxRedundantTagDepth;  
}  
放錯了地方的html或body中的標簽

又一次不言自明月弛。
支持不完整的html肴盏。我們從來不閉合body科盛,因為一些愚蠢的網(wǎng)頁總是在還未真正結束時就閉合它。 我們依賴調(diào)用end方法去執(zhí)行關閉的處理菜皂。
代碼:

  if (t->tagName == htmlTag || t->tagName == bodyTag )
         return;  

所以贞绵,web開發(fā)者要小心了,除非你想成為webkit容錯代碼的范例恍飘,否則還是寫格式良好的html吧榨崩。

CSS解析 CSS parsing

還記得簡介中提到的解析的概念嗎谴垫,不同于html,css屬于上下文無關文法母蛛,可以用前面所描述的解析器來解析翩剪。Css規(guī)范定義了css的詞法及語法文法。 看一些例子: 每個符號都由正則表達式定義了詞法文法(詞匯表):

  comment   \/\*[^*]*\*+([^/*][^*]*\*+)*\/
  num       [0-9]+|[0-9]*"."[0-9]+
  nonascii  [\200-\377]
  nmstart   [_a-z]|{nonascii}|{escape}
  nmchar    [_a-z0-9-]|{nonascii}|{escape}
  name      {nmchar}+
  ident     {nmstart}{nmchar}*

“ident”是識別器的縮寫彩郊,相當于一個class名前弯,“name”是一個元素id(用“#”引用)秫逝。
語法用BNF進行描述:

ruleset  : selector [ ',' S* selector ]*  '{' S* declaration [ ';' S* declaration ]* '}' S*  ;  
selector  : simple_selector [ combinator selector | S+ [ combinator selector ] ]  ;  simple_selector  : element_name [ HASH | class | attrib | pseudo ]*  | [ HASH | class | attrib | pseudo ]+  ; 
class  : '.' IDENT  ;  element_name  : IDENT | '*'  ; 
attrib  : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*  [ IDENT | STRING ] S* ] ']'  ; 
pseudo  : ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ]  ; 

說明:一個規(guī)則集合有這樣的結構

  div.error , a.error {
    color:red;
    font-weight:bold;
  }  

div.error和a.error時選擇器恕出,大括號中的內(nèi)容包含了這條規(guī)則集合中的規(guī)則,這個結構在下面的定義中正式的定義了:

 ruleset   :
 selector [ ',' S* selector ]*
     '{' S* declaration [ ';' S* declaration ]* '}' S* 
  ; 

這說明违帆,一個規(guī)則集合具有一個或是可選個數(shù)的多個選擇器浙巫,這些選擇器以逗號和空格(S表示空格)進行分隔。每個規(guī)則集合包含大括號及大括號中的一條或多條以分號隔開的聲明刷后。聲明和選擇器在后面進行定義的畴。

Webkit CSS 解析器 Webkit CSS parser

Webkit使用Flex和Bison解析生成器從CSS語法文件中自動生成解析器』菹眨回憶一下解析器的介紹苗傅,Bison創(chuàng)建一個自底向上的解析器,F(xiàn)irefox使用自頂向下解析器班巩。它們都是將每個css文件解析為樣式表對象渣慕,每個對象包含css規(guī)則,css規(guī)則對象包含選擇器和聲明對象抱慌,以及其他一些符合css語法的對象逊桦。


圖12:解析css

腳本解析 Parsing scripts

本章將介紹Javascript。

處理腳本及樣式表的順序 The order of processing scripts and style sheets
scripts

web的模式是同步的抑进,開發(fā)者希望解析到一個script標簽時立即解析執(zhí)行腳本强经,并阻塞文檔的解析直到腳本執(zhí)行完。如果腳本是外引的寺渗,則網(wǎng)絡必須先請求到這個資源——這個過程也是同步的匿情,會阻塞文檔的解析直到資源被請求到。這個模式保持了很多年信殊,并且在html4及html5中都特別指定了炬称。開發(fā)者可以將腳本標識為defer,以使其不阻塞文檔解析涡拘,并在文檔解析結束后執(zhí)行玲躯。Html5增加了標記腳本為異步的選項,以使腳本的解析執(zhí)行使用另一個線程。

預解析 Speculative parsing

Webkit和Firefox都做了這個優(yōu)化跷车,當執(zhí)行腳本時棘利,另一個線程解析剩下的文檔,并加載后面需要通過網(wǎng)絡加載的資源朽缴。這種方式可以使資源并行加載從而使整體速度更快善玫。需要注意的是,預解析并不改變Dom樹密强,它將這個工作留給主解析過程蝌焚,自己只解析外部資源的引用,比如外部腳本誓斥、樣式表及圖片只洒。

樣式表 Style sheets

樣式表采用另一種不同的模式。理論上劳坑,既然樣式表不改變Dom樹毕谴,也就沒有必要停下文檔的解析等待它們,然而距芬,存在一個問題涝开,腳本可能在文檔的解析過程中請求樣式信息,如果樣式還沒有加載和解析框仔,腳本將得到錯誤的值舀武,顯然這將會導致很多問題,這看起來是個邊緣情況离斩,但確實很常見银舱。Firefox在存在樣式表還在加載和解析時阻塞所有的腳本,而chrome只在當腳本試圖訪問某些可能被未加載的樣式表所影響的特定的樣式屬性時才阻塞這些腳本跛梗。

渲染樹的構造 Render tree construction

當Dom樹構建完成時寻馏,瀏覽器開始構建另一棵樹——渲染樹。渲染樹由元素顯示序列中的可見元素組成核偿,它是文檔的可視化表示诚欠,構建這棵樹是為了以正確的順序繪制文檔內(nèi)容。

Firefox將渲染樹中的元素稱為frames漾岳,webkit則用renderer或渲染對象來描述這些元素轰绵。 一個渲染對象直到怎么布局及繪制自己及它的children。 RenderObject是Webkit的渲染對象基類尼荆,它的定義如下:

  class RenderObject{
    virtual void layout(); 
    virtual void paint(PaintInfo);
    virtual void rect repaintRect();
    Node* node;  //the DOM node
    RenderStyle* style;  // the computed style
    RenderLayer* containgLayer; //the containing z-index layer
  }  

每個渲染對象用一個和該節(jié)點的css盒模型相對應的矩形區(qū)域來表示左腔,正如css2所描述的那樣,它包含諸如寬耀找、高和位置之類的幾何信息翔悠。盒模型的類型受該節(jié)點相關的display樣式屬性的影響(參考樣式計算章節(jié))。下面的webkit代碼說明了如何根據(jù)display屬性決定某個節(jié)點創(chuàng)建何種類型的渲染對象野芒。

  RenderObject* RenderObject::createObject(Node* node, RenderStyle* style) 
 {
     Document* doc = node->document();
     RenderArena* arena = doc->renderArena();
     ... 
    RenderObject* o = 0;
       switch (style->display()) { 
        case NONE:
             break;
         case INLINE:
             o = new (arena) RenderInline(node);
             break;
         case BLOCK: 
            o = new (arena) RenderBlock(node); 
            break;
         case INLINE_BLOCK:
             o = new (arena) RenderBlock(node); 
            break;
         case LIST_ITEM:
             o = new (arena) RenderListItem(node); 
            break;
        ...
     }       
return o;  
}  

元素的類型也需要考慮蓄愁,例如,表單控件和表格帶有特殊的框架狞悲。 在webkit中撮抓,如果一個元素想創(chuàng)建一個特殊的渲染對象,它需要復寫“createRenderer”方法摇锋,使渲染對象指向不包含幾何信息的樣式對象丹拯。

渲染樹和Dom樹的關系 The render tree relation to the DOM tree

渲染對象和Dom元素相對應,但這種對應關系不是一對一的荸恕,不可見的Dom元素不會被插入渲染樹乖酬,例如head元素。另外融求,display屬性為none的元素也不會在渲染樹中出現(xiàn)(visibility屬性為hidden的元素將出現(xiàn)在渲染樹中)咬像。

還有一些Dom元素對應幾個可見對象,它們一般是一些具有復雜結構的元素生宛,無法用一個矩形來描述县昂。例如,select元素有三個渲染對象——一個顯示區(qū)域陷舅、一個下拉列表及一個按鈕倒彰。同樣,當文本因為寬度不夠而折行時莱睁,新行將作為額外的渲染元素被添加待讳。

另一個多個渲染對象的例子是不規(guī)范的html,根據(jù)css規(guī)范仰剿,一個行內(nèi)元素只能僅包含行內(nèi)元素或僅包含塊狀元素耙箍,在存在混合內(nèi)容時,將會創(chuàng)建匿名的塊狀渲染對象包裹住行內(nèi)元素酥馍。

一些渲染對象和所對應的Dom節(jié)點不在樹上相同的位置辩昆,例如,浮動和絕對定位的元素在文本流之外旨袒,在兩棵樹上的位置不同汁针,渲染樹上標識出真實的結構,并用一個占位結構標識出它們原來的位置砚尽。


圖12:渲染樹及對應的Dom樹
創(chuàng)建樹的流程 The flow of constructing the tree

Firefox中施无,表述為一個監(jiān)聽Dom更新的監(jiān)聽器,將frame的創(chuàng)建委派給Frame Constructor必孤,這個構建器計算樣式(參看樣式計算)并創(chuàng)建一個frame猾骡。

Webkit中瑞躺,計算樣式并生成渲染對象的過程稱為attachment狮辽,每個Dom節(jié)點有一個attach方法陵且,attachment的過程是同步的峭咒,調(diào)用新節(jié)點的attach方法將節(jié)點插入到Dom樹中兵罢。

處理html和body標簽將構建渲染樹的根洁仗,這個根渲染對象對應被css規(guī)范稱為containing block的元素——包含了其他所有塊元素的頂級塊元素浩蓉。它的大小就是viewport——瀏覽器窗口的顯示區(qū)域础钠,F(xiàn)irefox稱它為viewPortFrame浩峡,webkit稱為RenderView毙替,這個就是文檔所指向的渲染對象岸售,樹中其他的部分都將作為一個插入的Dom節(jié)點被創(chuàng)建。

樣式計算 Style Computation

創(chuàng)建渲染樹需要計算出每個渲染對象的可視屬性厂画,這可以通過計算每個元素的樣式屬性得到凸丸。

樣式包括各種來源的樣式表,行內(nèi)樣式元素及html中的可視化屬性(例如bgcolor)袱院,可視化屬性轉化為css樣式屬性甲雅。

樣式表來源于瀏覽器默認樣式表,及頁面作者和用戶提供的樣式表——有些樣式是瀏覽器用戶提供的(瀏覽器允許用戶定義喜歡的樣式坑填,例如抛人,在Firefox中,可以通過在Firefox Profile目錄下放置樣式表實現(xiàn))脐瑰。

計算樣式的一些困難:

  1. 樣式數(shù)據(jù)是非常大的結構妖枚,保存大量的樣式屬性會帶來內(nèi)存問題
  2. 如果不進行優(yōu)化,找到每個元素匹配的規(guī)則會導致性能問題苍在,為每個元素查找匹配的規(guī)則都需要遍歷整個規(guī)則表绝页,這個過程有很大的工作量。選擇符可能有復雜的結構寂恬,匹配過程如果沿著一條開始看似正確续誉,后來卻被證明是無用的路徑,則必須去嘗試另一條路徑初肉。
    例如酷鸦,下面這個復雜選擇符
 div div div div{
  ...     
 } 

這意味著規(guī)則應用到三個div的后代div元素,選擇樹上一條特定的路徑去檢查牙咏,這可能需要遍歷節(jié)點樹臼隔,最后卻發(fā)現(xiàn)它只是兩個div的后代,并不使用該規(guī)則妄壶,然后則需要沿著另一條路徑去嘗試

  1. 應用規(guī)則涉及非常復雜的級聯(lián)摔握,它們定義了規(guī)則的層次 我們來看一下瀏覽器如何處理這些問題:
共享樣式數(shù)據(jù)

webkit節(jié)點引用樣式對象(渲染樣式),某些情況下丁寄,這些對象可以被節(jié)點間共享氨淌,這些節(jié)點需要是兄弟或是表兄弟節(jié)點泊愧,并且:

  1. 這些元素必須處于相同的鼠標狀態(tài)(比如不能一個處于hover,而另一個不是)
  2. 不能有元素具有id
  3. 標簽名必須匹配
  4. class屬性必須匹配
  5. 對應的屬性必須相同
  6. 鏈接狀態(tài)必須匹配
  7. 焦點狀態(tài)必須匹配
  8. 不能有元素被屬性選擇器影響
  9. 元素不能有行內(nèi)樣式屬性
  10. 不能有生效的兄弟選擇器盛正,webcore在任何兄弟選擇器相遇時只是簡單的拋出一個全局轉換删咱,并且在它們顯示時使整個文檔的樣式共享失效,這些包括+選擇器和類似:first-child和:last-child這樣的選擇器蛮艰。
Firefox規(guī)則樹 Firefox rule tree

Firefox用兩個樹用來簡化樣式計算-規(guī)則樹和樣式上下文樹,webkit也有樣式對象雀彼,但它們并沒有存儲在類似樣式上下文樹這樣的樹中壤蚜,只是由Dom節(jié)點指向其相關的樣式。


圖14:Firefox樣式上下文樹

樣式上下文包含最終值徊哑,這些值是通過以正確順序應用所有匹配的規(guī)則袜刷,并將它們由邏輯值轉換為具體的值,例如莺丑,如果邏輯值為屏幕的百分比著蟹,則通過計算將其轉化為絕對單位。樣式樹的使用確實很巧妙梢莽,它使得在節(jié)點中共享的這些值不需要被多次計算萧豆,同時也節(jié)省了存儲空間。

所有匹配的規(guī)則都存儲在規(guī)則樹中昏名,一條路徑中的底層節(jié)點擁有最高的優(yōu)先級涮雷,這棵樹包含了所找到的 所有規(guī)則匹配的路徑(譯注:可以取巧理解為每條路徑對應一個節(jié)點,路徑上包含了該節(jié)點所匹配的所有規(guī)則)轻局。規(guī)則樹并不是一開始就為所有節(jié)點進行計算洪鸭,而是 在某個節(jié)點需要計算樣式時,才進行相應的計算并將計算后的路徑添加到樹中仑扑。

我們將樹上的路徑看成辭典中的單詞览爵,假如已經(jīng)計算出了如下的規(guī)則樹:


假如需要為內(nèi)容樹中的另一個節(jié)點匹配規(guī)則,現(xiàn)在知道匹配的規(guī)則(以正確的順序)為B-E-I镇饮,因為我們已經(jīng)計算出了路徑A-B-E-I-L蜓竹,所以樹上已經(jīng)存在了這條路徑,剩下的工作就很少了储藐。 現(xiàn)在來看一下樹如何保存梅肤。

結構化(Division into structs)

樣式上下文按結構劃分,這些結構包括類似border或color這樣的特定分類的樣式信息邑茄。一個結構中的所有特性不是繼承的就是非繼承的姨蝴,對繼承的特性,除非元素自身有定義肺缕,否則就從它的parent繼承左医。非繼承的特性(稱為reset特性)如果沒有定義授帕,則使用默認的值。

樣式上下文樹緩存完整的結構(包括計算后的值)浮梢,這樣跛十,如果底層節(jié)點沒有為一個結構提供定義,則使用上層節(jié)點緩存的結構秕硝。

使用規(guī)則樹計算樣式上下文(Computing the style contexts using the rule tree)

當為一個特定的元素計算樣式時芥映,首先計算出規(guī)則樹中的一條路徑,或是使用已經(jīng)存在的一條远豺,然后使 用路徑中的規(guī)則去填充新的樣式上下文奈偏,從樣式的底層節(jié)點開始,它具有最高優(yōu)先級(通常是最特定的選擇器)躯护,遍歷規(guī)則樹惊来,直到填滿結構。

如果在那個規(guī)則節(jié)點 沒有定義所需的結構規(guī)則棺滞,則沿著路徑向上裁蚁,直到找到該結構規(guī)則。

如果最終沒有找到該結構的任何規(guī)則定義继准,那么如果這個結構是繼承型的枉证,則找到其在內(nèi)容樹中的parent的結構,這種情況下移必,我們也成功的共享了結構刽严;如果這個結構是reset型的,則使用默認的值避凝。

如果特定的節(jié)點添加了值舞萄,那么需要做一些額外的計算以將其轉換為實際值,然后在樹上的節(jié)點緩存該值管削,使它的children可以使用。

當一個元素和它的一個兄弟元素指向同一個樹節(jié)點時含潘,完整的樣式上下文可以被它們共享。
來看一個例子:假設有下面這段html

  <html>
    <body>
        <div class="err" id="div1">
            <p>
                           this is a <span class="big"> big error </span>
                           this is also a
                           <span class="big"> very  big  error</span> error
                </p>
        </div> 
        <div class="err" id="div2">another error</div>
        </body>
  </html>

以及下面這些規(guī)則:

 1. div {margin:5px;color:black}
 2. .err {color:red} 
 3. .big {margin-top:3px} 
 4. div span {margin-bottom:4px} 
 5. #div1 {color:blue}
 6. #div 2 {color:green} 

簡化下問題诀黍,我們只填充兩個結構——color和margin模叙,color結構只包含一個成員-顏色权旷,margin結構包含四邊鄙麦。
生成的規(guī)則樹如下(節(jié)點名:指向的規(guī)則)


上下文樹如下(節(jié)點名:指向的規(guī)則節(jié)點)

假設我們解析html影所,遇到第二個div標簽议忽,我們需要為這個節(jié)點創(chuàng)建樣式上下文蔓榄,并填充它的樣式結構并炮。
我們進行規(guī)則匹配店展,找到這個div匹配的規(guī)則為1萍丐、2基茵、6,我們發(fā)現(xiàn)規(guī)則樹上已經(jīng)存在了一條我們可以使用的路徑1壳影、2拱层,我們只需為規(guī)則6新增一個節(jié)點添加到下面(就是規(guī)則樹中的F)。
然后創(chuàng)建一個樣式上下文并將其放到上下文樹中宴咧,新的樣式上下文將指向規(guī)則樹中的節(jié)點F根灯。
現(xiàn)在我們需要填充這個樣式上下文,先從填充margin結構開始掺栅,既然最后一個規(guī)則節(jié)點沒有添加margin結構烙肺,沿著路徑向上,直到找到緩存的前面插入節(jié)點計算出的結構柿冲,我們發(fā)現(xiàn)B是最近的指定margin值的節(jié)點茬高。

因為已經(jīng)有了color結構的定義兆旬,所以不能使用緩存的結構假抄,既然color只有一個屬性,也就不需要沿著路徑向上填充其他屬性丽猬。計算出最終值(將字符串轉換為RGB等)宿饱,并緩存計算后的結構。

第二個span元素更簡單脚祟,進行規(guī)則匹配后發(fā)現(xiàn)它指向規(guī)則G谬以,和前一個span一樣,既然有兄弟節(jié)點指向同一個節(jié)點由桌,就可以共享完整的樣式上下文为黎,只需指向前一個span的上下文。

因為結構中包含繼承自parent的規(guī)則行您,上下文樹做了緩存(color特性是繼承來的铭乾,但Firefox將其視為reset并在規(guī)則樹中緩存)。 例如娃循,如果我們?yōu)橐粋€paragraph的文字添加規(guī)則:

  p {font-family:Verdana;font size:10px;font-weight:bold} 

那么這個p在內(nèi)容樹中的子節(jié)點div炕檩,會共享和它parent一樣的font結構,這種情況發(fā)生在沒有為這個div指定font規(guī)則時捌斧。

Webkit中笛质,并沒有規(guī)則樹泉沾,匹配的聲明會被遍歷四次,先是應用非important的高優(yōu)先級屬性(之所以先應用這些屬性妇押,是因為其他的依賴于它們-比如display)跷究,其次是高優(yōu)先級important的,接著是一般優(yōu)先級非important的敲霍,最后是一般優(yōu)先級important的規(guī)則揭朝。

這樣,出現(xiàn)多次的屬性將被按照正確的級聯(lián)順序進行處理色冀,最后一個生效潭袱。 總結一下,共享樣式對象(結構中完整或部分內(nèi)容)解決了問題1和3锋恬,F(xiàn)irefox的規(guī)則樹幫助以正確的順序應用規(guī)則屯换。

對規(guī)則進行處理以簡化匹配過程(Manipulating the rules for an easy match)

樣式規(guī)則有幾個來源:

  • 外部樣式表或style標簽內(nèi)的css規(guī)則
  p {color:blue}  
  • 行內(nèi)樣式屬性
  <p style="color:blue" />  
  • html可視化屬性(映射為相應的樣式規(guī)則)
  <p bgcolor="blue" />

后面兩個很容易匹配到元素,因為它們所擁有的樣式屬性和html屬性可以將元素作為key進行映射与学。

就像前面問題2所提到的彤悔,css的規(guī)則匹配可能很狡猾,為了解決這個問題索守,可以先對規(guī)則進行處理晕窑,以使其更容易被訪問。

解析完樣式表之后卵佛,規(guī)則會根據(jù)選擇符添加一些hash映射杨赤,映射可以是根據(jù)id、class截汪、標簽名或是任何不屬于這些分類的綜合映射疾牲。如果選擇符為id,規(guī)則將被添加到id映射衙解,如果是class阳柔,則被添加到class映射,等等蚓峦。 這個處理是匹配規(guī)則更容易舌剂,不需要查看每個聲明,我們能從映射中找到一個元素的相關規(guī)則暑椰,這個優(yōu)化使在進行規(guī)則匹配時減少了95+%的工作量霍转。
來看下面的樣式規(guī)則:

  p.error {color:red}  #messageDiv {height:50px}  div {margin:5px}

第一條規(guī)則將被插入class映射,第二條插入id映射干茉,第三條是標簽映射谴忧。 下面這個html片段:

  <p class="error">an error occurred </p>
  <div id=" messageDiv">this is a message</div>

我們首先找到p元素對應的規(guī)則,class映射將包含一個“error”的key,找到p.error的規(guī)則沾谓,div在id映射和標簽映射中都有相關的規(guī)則委造,剩下的工作就是找出這些由key對應的規(guī)則中哪些確實是正確匹配的。
例如均驶,如果div的規(guī)則是

  table div {margin:5px}  

這也是標簽映射產(chǎn)生的昏兆,因為key是最右邊的選擇符,但它并不匹配這里的div元素腾它,因為這里的div沒有table祖先虏两。

Webkit和Firefox都會做這個處理境蜕。

以正確的級聯(lián)順序應用規(guī)則(Applying the rules in the correct cascade order)

樣式對象擁有對應所有可見屬性的屬性拉队,如果特性沒有被任何匹配的規(guī)則所定義鳍咱,那么一些特性可以從parent的樣式對象中繼承,另外一些使用默認值胳岂。

這個問題的產(chǎn)生是因為存在不止一處的定義,這里用級聯(lián)順序解決這個問題。

樣式表的級聯(lián)順序(Style sheet cascade order)

一個樣式屬性的聲明可能在幾個樣式表中出現(xiàn)井赌,或是在一個樣式表中出現(xiàn)多次,因此贵涵,應用規(guī)則的順序至關重要片林,這個順序就是級聯(lián)順序韧献。根據(jù)css2的規(guī)范璧针,級聯(lián)順序為(從低到高):

  1. 瀏覽器聲明
  2. 用戶聲明
  3. 作者的一般聲明
  4. 作者的important聲明
  5. 用戶important聲明

瀏覽器聲明是最不重要的,用戶只有在聲明被標記為important時才會覆蓋作者的聲明渊啰。具有同等級別的聲明將根據(jù)specifity以及它們被定義時的順序進行排序探橱。Html可視化屬性將被轉換為匹配的css聲明,它們被視為最低優(yōu)先級的作者規(guī)則虽抄。

Specifity

Css2規(guī)范中定義的選擇符specifity如下:

  • 如果聲明來自style屬性走搁,而不是一個選擇器的規(guī)則,則計1迈窟,否則計0(=a)
  • 計算選擇器中id屬性的數(shù)量(=b)
  • 計算選擇器中class及偽類的數(shù)量(=c)
  • 計算選擇器中元素名及偽元素的數(shù)量(=d) 連接a-b-c-d四個數(shù)量(用一個大基數(shù)的計算系統(tǒng))將得到specifity。

這里使用的基數(shù)由分類中最高的基數(shù)定義忌栅。
例如车酣,如果a為14曲稼,可以使用16進制。不同情況下湖员,a為17時贫悄,則需要使用阿拉伯數(shù)字17作為基數(shù),這種情況可能在這個選擇符時發(fā)生html body div div …(選擇符中有17個標簽娘摔,一般不太可能)窄坦。
一些例子:

  *             {}  /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
  li            {}  /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
  li:first-line {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
  ul li         {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
  ul ol+li      {}  /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
  h1 + *[rel=up]{}  /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
  ul ol li.red  {}  /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
  li.red.level  {}  /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
  #x34y         {}  /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
  style=""          /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */
規(guī)則排序(Sorting the rules)

規(guī)則匹配后,需要根據(jù)級聯(lián)順序對規(guī)則進行排序凳寺,webkit先將小列表用冒泡排序鸭津,再將它們合并為一個大列表,webkit通過為規(guī)則復寫“>”操作來執(zhí)行排序:

  static bool operator >(CSSRuleData& r1, CSSRuleData& r2)  {
     int spec1 = r1.selector()->specificity();
     int spec2 = r2.selector()->specificity(); 
    return (spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2; 
 }  
逐步處理(Gradual process)

webkit使用一個標志位標識所有頂層樣式表都已加載肠缨,如果在attch時樣式?jīng)]有完全加載逆趋,則放置占位符,并在文檔中標記晒奕,一旦樣式表完成加載就重新進行計算闻书。

布局 Layout

當渲染對象被創(chuàng)建并添加到樹中,它們并沒有位置和大小脑慧,計算這些值的過程稱為layout或reflow魄眉。
Html使用基于流的布局模型,意味著大部分時間闷袒,可以以單一的途徑進行幾何計算杆融。流中靠后的元素并不會影響前面元素的幾何特性,所以布局可以在文檔中從右向左霜运、自上而下的進行脾歇。也存在一些例外,比如html tables淘捡。

坐標系統(tǒng)相對于根frame藕各,使用top和left坐標。

布局是一個遞歸的過程焦除,由根渲染對象開始激况,它對應html文檔元素,布局繼續(xù)遞歸的通過一些或所有的frame層級膘魄,為每個需要幾何信息的渲染對象進行計算乌逐。

根渲染對象的位置是0,0,它的大小是viewport-瀏覽器窗口的可見部分创葡。

所有的渲染對象都有一個layout或reflow方法浙踢,每個渲染對象調(diào)用需要布局的children的layout方法。

Dirty bit 系統(tǒng)

為了不因為每個小變化都全部重新布局灿渴,瀏覽器使用一個dirty bit系統(tǒng)洛波,一個渲染對象發(fā)生了變化或是被添加了胰舆,就標記它及它的children為dirty-需要layout。

存在兩個標識-dirty及children are dirty蹬挤,children are dirty說明即使這個渲染對象可能沒問題缚窿,但它至少有一個child需要layout。

全局和增量 layout(Global and incremental layout)

當layout在整棵渲染樹觸發(fā)時焰扳,稱為全局layout倦零,這可能在下面這些情況下發(fā)生:

  1. 一個全局的樣式改變影響所有的渲染對象,比如字號的改變

  2. 窗口resize

layout也可以是增量的吨悍,這樣只有標志為dirty的渲染對象會重新布局(也將導致一些額外的布局)扫茅。

增量 layout會在渲染對象dirty時異步觸發(fā),例如畜份,當網(wǎng)絡接收到新的內(nèi)容并添加到Dom樹后诞帐,新的渲染對象會添加到渲染樹中。

圖20:增量 layout
異步和同步layout(Asynchronous and Synchronous layout)

增量layout的過程是異步的爆雹,F(xiàn)irefox為增量layout生成了reflow隊列停蕉,以及一個調(diào)度執(zhí)行這些批處理命令。Webkit也有一個計時器用來執(zhí)行增量layout-遍歷樹钙态,為dirty狀態(tài)的渲染對象重新布局慧起。
另外,當腳本請求樣式信息時册倒,例如“offsetHeight”蚓挤,會同步的觸發(fā)增量布局。
全局的layout一般都是同步觸發(fā)驻子。 有些時候灿意,layout會被作為一個初始layout之后的回調(diào),比如滑動條的滑動崇呵。

優(yōu)化(Optimizations)

當一個layout因為resize或是渲染位置改變(并不是大小改變)而觸發(fā)時缤剧,渲染對象的大小將會從緩存中讀取,而不會重新計算域慷。 一般情況下荒辕,如果只有子樹發(fā)生改變,則layout并不從根開始犹褒。這種情況發(fā)生在抵窒,變化發(fā)生在元素自身并且不影響它周圍元素,例如叠骑,將文本插入文本域(否則李皇,每次擊鍵都將觸發(fā)從根開始的重排)。

layout過程

layout一般有下面這幾個部分:

  1. parent渲染對象決定它的寬度

  2. parent渲染對象讀取chilidren座云,并:

    1. 放置child渲染對象(設置它的x和y)

    2. 在需要時(它們當前為dirty或是處于全局layout或者其他原因)調(diào)用child渲染對象的layout疙赠,這將計算child的高度

  3. parent渲染對象使用child渲染對象的累積高度付材,以及margin和padding的高度來設置自己的高度-這將被parent渲染對象的parent使用

  4. 將dirty標識設置為false

Firefox使用一個“state”對象(nsHTMLReflowState)做為參數(shù)去布局(firefox稱為reflow)朦拖,state包含parent的寬度及其他內(nèi)容圃阳。
Firefox布局的輸出是一個“metrics”對象(nsHTMLReflowMetrics)。它包括渲染對象計算出的高度璧帝。

寬度計算

渲染對象的寬度使用容器的寬度捍岳、渲染對象樣式中的寬度及margin、border進行計算睬隶。例如锣夹,下面這個div的寬度:

<div style="width:30%"/>

webkit中寬度的計算過程是(RenderBox類的calcWidth方法):

  • 容器的寬度是容器的可用寬度和0中的最大值,這里的可用寬度為:

    clientWidth() - paddingLeft() - paddingRight()
    
    

    clientWidth和clientHeight代表一個對象內(nèi)部的不包括border和滑動條的大小

  • 元素的寬度指樣式屬性width的值苏潜,它可以通過計算容器的百分比得到一個絕對值

  • 加上水平方向上的border和padding

到這里是最佳寬度的計算過程银萍,現(xiàn)在計算寬度的最大值和最小值。
如果最佳寬度大于最大寬度則使用最大寬度恤左,如果小于最小寬度則使用最小寬度贴唇。

最后緩存這個值,當需要layout但寬度未改變時使用飞袋。

Line breaking

當一個渲染對象在布局過程中需要折行時戳气,則暫停并告訴它的parent它需要折行,parent將創(chuàng)建額外的渲染對象并調(diào)用它們的layout巧鸭。

繪制 Painting

繪制階段瓶您,遍歷渲染樹并調(diào)用渲染對象的paint方法將它們的內(nèi)容顯示在屏幕上,繪制使用UI基礎組件纲仍,這在UI的章節(jié)有更多的介紹呀袱。

全局和增量(Global and Incremental)

和布局一樣,繪制也可以是全局的-繪制完整的樹-或增量的郑叠。在增量的繪制過程中夜赵,一些渲染對象以不影響整棵樹的方式改變,改變的渲染對象使其在屏幕上的矩形區(qū)域失效锻拘,這將導致操作系統(tǒng)將其看作dirty區(qū)域油吭,并產(chǎn)生一個paint事件,操作系統(tǒng)很巧妙的處理這個過程署拟,并將多個區(qū)域合并為一個婉宰。Chrome中妥畏,這個過程更復雜些衅斩,因為渲染對象在不同的進程中,而不是在主進程中障簿。Chrome在一定程度上模擬操作系統(tǒng)的行為馒铃,表現(xiàn)為監(jiān)聽事件并派發(fā)消息給渲染根蟹腾,在樹中查找到相關的渲染對象痕惋,重繪這個對象(往往還包括它的children)。

繪制順序(The painting order)

css2定義了繪制過程的順序-http://www.w3.org/TR/CSS21/zindex.html娃殖。這個就是元素壓入堆棧的順序值戳,這個順序影響著繪制,堆棧從后向前進行繪制炉爆。 一個塊渲染對象的堆棧順序是:

  1. 背景色

  2. 背景圖

  3. border

  4. children

  5. outline

Firefox顯示列表

Firefox讀取渲染樹并為繪制的矩形創(chuàng)建一個顯示列表堕虹,該列表以正確的繪制順序包含這個矩形相關的渲染對象。
用這樣的方法芬首,可以使重繪時只需查找一次樹赴捞,而不需要多次查找——繪制所有的背景、所有的圖片郁稍、所有的border等等赦政。
Firefox優(yōu)化了這個過程,它不添加會被隱藏的元素耀怜,比如元素完全在其他不透明元素下面恢着。

Webkit矩形存儲

重繪前,webkit將舊的矩形保存為位圖封寞,然后只繪制新舊矩形的差集然评。

動態(tài)變化

瀏覽器總是試著以最小的動作響應一個變化,所以一個元素顏色的變化將只導致該元素的重繪狈究,元素位置的變化將大致元素的布局和重繪碗淌,添加一個Dom節(jié)點,也會大致這個元素的布局和重繪抖锥。一些主要的變化亿眠,比如增加html元素的字號,將會導致緩存失效磅废,從而引起整數(shù)的布局和重繪纳像。

渲染引擎的線程

渲染引擎是單線程的,除了網(wǎng)絡操作以外拯勉,幾乎所有的事情都在單一的線程中處理竟趾,在Firefox和Safari中,這是瀏覽器的主線程宫峦,Chrome中這是tab的主線程岔帽。 網(wǎng)絡操作由幾個并行線程執(zhí)行,并行連接的個數(shù)是受限的(通常是2-6個)导绷。

事件循環(huán)(Event loop)

瀏覽器主線程是一個事件循環(huán)犀勒,它被設計為無限循環(huán)以保持執(zhí)行過程的可用,等待事件(例如layout和paint事件)并執(zhí)行它們。下面是Firefox的主要事件循環(huán)代碼贾费。

while (!mExiting)
    NS_ProcessNextEvent(thread);

CSS2 可視模型 CSS2 visual module

畫布 The Canvas

根據(jù)CSS2規(guī)范钦购,術語canvas用來描述格式化的結構所渲染的空間——瀏覽器繪制內(nèi)容的地方。畫布對每個維度空間都是無限大的褂萧,但瀏覽器基于viewport的大小選擇了一個初始寬度押桃。

根據(jù)http://www.w3.org/TR/CSS2/zindex.html的定義,畫布如果是包含在其他畫布內(nèi)則是透明的箱玷,否則瀏覽器會指定一個顏色怨规。

CSS盒模型

CSS盒模型描述了矩形盒陌宿,這些矩形盒是為文檔樹中的元素生成的锡足,并根據(jù)可視的格式化模型進行布局。每個box包括內(nèi)容區(qū)域(如圖片壳坪、文本等)及可選的四周padding舶得、border和margin區(qū)域。

每個節(jié)點生成0-n個這樣的box爽蝴。
所有的元素都有一個display屬性沐批,用來決定它們生成box的類型,
例如:

block-生成塊狀box 
inline-生成一個或多個行內(nèi)box 
none-不生成box 

默認的是inline蝎亚,但瀏覽器樣式表設置了其他默認值九孩,例如,div元素默認為block发框。
可以訪問http://www.w3.org/TR/CSS2/sample.html查看更多的默認樣式表示例躺彬。

定位策略 Position scheme

這里有三種策略:

  1. normal-對象根據(jù)它在文檔的中位置定位,這意味著它在渲染樹和在Dom樹中位置一致梅惯,并根據(jù)它的盒模型和大小進行布局

  2. float-對象先像普通流一樣布局宪拥,然后盡可能的向左或是向右移動

  3. absolute-對象在渲染樹中的位置和Dom樹中位置無關

定位方案由”位置”屬性和”浮動”屬性設置。

  • static和relative是normal

  • absolute和fixed屬于absolute铣减。

在static定位中她君,不定義位置而使用默認的位置。其他策略中葫哗,作者指定位置——top缔刹、bottom、left劣针、right校镐。

Box布局的方式由這幾項決定:

  • box的類型

  • box的大小

  • 定位策略

  • 擴展信息(比如圖片大小和屏幕尺寸)。

Box類型

Block box:構成一個塊酿秸,即在瀏覽器窗口上有自己的矩形

Inline box:并沒有自己的塊狀區(qū)域灭翔,但包含在一個塊狀區(qū)域內(nèi)

block一個挨著一個垂直格式化,inline則在水平方向上格式化。

Inline盒模型放置在行內(nèi)或是line box中肝箱,每行至少和最高的box一樣高哄褒,當box以baseline對齊時——即一個元素的底部和另一個box上除底部以外的某點對齊,行高可以比最高的box高煌张。當容器寬度不夠時呐赡,行內(nèi)元素將被放到多行中,這在一個p元素中經(jīng)常發(fā)生骏融。

定位 Position

Relative

相對定位——先按照一般的定位链嘀,然后按所要求的差值移動。

Floats

一個浮動的box移動到一行的最左邊或是最右邊档玻,其余的box圍繞在它周圍怀泊。下面這段html:

<p>
<img style="float:right" src="images/image.gif" width="100" height="100">Lorem ipsum dolor sit amet, consectetuer...
</p>

將顯示為:

Absolute和Fixed

這種情況下的布局完全不顧普通的文檔流,元素不屬于文檔流的一部分误趴,大小取決于容器霹琼。Fixed時,容器為viewport(可視區(qū)域)凉当。

圖17:fixed

注意-fixed即使在文檔流滾動時也不會移動枣申。

Layered representation

這個由CSS屬性中的z-index指定,表示盒模型的第三個大小看杭,即在z軸上的位置忠藤。

Box分發(fā)到堆棧中(稱為堆棧上下文),每個堆棧中靠后的元素將被較早繪制楼雹,棧頂靠前的元素離用戶最近模孩,當發(fā)生交疊時,將隱藏靠后的元素烘豹。堆棧根據(jù)z-index屬性排序瓜贾,擁有z-index屬性的box形成了一個局部堆棧,viewport有外部堆棧携悯,
例如:

<STYLE type="text/css">
      div { 
        position: absolute; 
        left: 2in; 
        top: 2in; 
      }
    </STYLE>

  <P>   
        <DIV 
             style="z-index: 3;background-color:red; width: 1in; height: 1in; ">
        </DIV>
        <DIV
             style="z-index: 1;background-color:green;width: 2in; height: 2in;">
        </DIV>
   </p>

結果是:

雖然綠色div排在紅色div后面祭芦,可能在正常流中也已經(jīng)被繪制在后面,但z-index有更高優(yōu)先級憔鬼,所以在根box的堆棧中更靠前龟劲。

資料

  1. Browser architecture
    1.1 Grosskurth, Alan. A Reference Architecture for Web Browsers. http://grosskurth.ca/papers/browser-refarch.pdf.

  2. Parsing
    2.1 Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (aka the “Dragon book”), Addison-Wesley, 1986
    2.2 Rick Jelliffe. The Bold and the Beautiful: two new drafts for HTML 5. http://broadcast.oreilly.com/2009/05/the-bold-and-the-beautiful-two.html.

  3. Firefox
    3.1 L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers. http://dbaron.org/talks/2008-11-12-faster-html-and-css/slide-6.xhtml.
    3.2 L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers(Google tech talk video). http://www.youtube.com/watch?v=a2_6bGNZ7bA.
    3.3 L. David Baron, Mozilla’s Layout Engine. http://www.mozilla.org/newlayout/doc/layout-2006-07-12/slide-6.xhtml.
    3.4 L. David Baron, Mozilla Style System Documentation. http://www.mozilla.org/newlayout/doc/style-system.html.
    3.5 Chris Waterson, Notes on HTML Reflow. http://www.mozilla.org/newlayout/doc/reflow.html.
    3.6 Chris Waterson, Gecko Overview. http://www.mozilla.org/newlayout/doc/gecko-overview.htm.
    3.7 Alexander Larsson, The life of an HTML HTTP request. https://developer.mozilla.org/en/The_life_of_an_HTML_HTTP_request.

  4. Webkit
    4.1 David Hyatt, Implementing CSS(part 1). http://weblogs.mozillazine.org/hyatt/archives/cat_safari.html.
    4.2 David Hyatt, An Overview of WebCore. http://weblogs.mozillazine.org/hyatt/WebCore/chapter2.html.
    4.3 David Hyatt, WebCore Rendering. http://webkit.org/blog/114/.
    4.4 David Hyatt, The FOUC Problem. http://webkit.org/blog/66/the-fouc-problem/.

  5. W3C Specifications
    5.1 HTML 4.01 Specification. http://www.w3.org/TR/html4/.
    5.2 HTML5 Specification. http://dev.w3.org/html5/spec/Overview.html.
    5.3 Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification. http://www.w3.org/TR/CSS2/.

  6. Browsers build instructions
    6.1 Firefox. https://developer.mozilla.org/en/Build_Documentation
    6.2 Webkit. http://webkit.org/building/build.html

原文:http://taligarsiel.com/Projects/howbrowserswork1.html

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市轴或,隨后出現(xiàn)的幾起案子昌跌,更是在濱河造成了極大的恐慌,老刑警劉巖照雁,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚕愤,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機萍诱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門悬嗓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人裕坊,你說我怎么就攤上這事包竹。” “怎么了籍凝?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵周瞎,是天一觀的道長。 經(jīng)常有香客問我饵蒂,道長声诸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任苹享,我火速辦了婚禮双絮,結果婚禮上,老公的妹妹穿的比我還像新娘得问。我一直安慰自己,他們只是感情好软免,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布宫纬。 她就那樣靜靜地躺著,像睡著了一般膏萧。 火紅的嫁衣襯著肌膚如雪漓骚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天榛泛,我揣著相機與錄音蝌蹂,去河邊找鬼。 笑死曹锨,一個胖子當著我的面吹牛孤个,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沛简,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼齐鲤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了椒楣?” 一聲冷哼從身側響起给郊,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捧灰,沒想到半個月后淆九,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年炭庙,在試婚紗的時候發(fā)現(xiàn)自己被綠了跪另。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡煤搜,死狀恐怖免绿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情擦盾,我是刑警寧澤嘲驾,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站迹卢,受9級特大地震影響辽故,放射性物質發(fā)生泄漏。R本人自食惡果不足惜腐碱,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一誊垢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧症见,春花似錦喂走、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至遵蚜,卻和暖如春帖池,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吭净。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工睡汹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人寂殉。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓囚巴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親不撑。 傳聞我的和親對象是個殘疾皇子文兢,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容