瀏覽器是如何工做的?(工做原理)

簡介css

瀏覽器能夠被認爲是使用最普遍的軟件,本文將介紹瀏覽器的工做原理,咱們將看到,從你在地址欄輸入google.com到你看到google主頁過程當中都發生了什麼。html

將討論的瀏覽器html5

今天,有五種主流瀏覽器——IE、Firefox、Safari、Chrome及Opera。node

本文將基於一些開源瀏覽器的例子——Firefox、 Chrome及Safari,Safari是部分開源的。linux

根據W3C(World Wide Web Consortium 萬維網聯盟)的瀏覽器統計數據,當前(2011年9月),Firefox、Safari及Chrome的市場佔有率綜合已快接近50%。(原文爲2009年10月,數據沒有太大變化)所以,能夠說開源瀏覽器將近佔據了瀏覽器市場的半壁江山。web

瀏覽器的主要功能正則表達式

瀏覽器的主要功能是將用戶選擇得web資源呈現出來,它須要從服務器請求資源,並將其顯示在瀏覽器窗口中,資源的格式一般是HTML,也包括PDF、image及其餘格式。用戶用URI(Uniform Resource Identifier 統一資源標識符)來指定所請求資源的位置,在網絡一章有更多討論。算法

 

HTML和CSS規範中規定了瀏覽器解釋html文檔的方式,由 W3C組織對這些規範進行維護,W3C是負責制定web標準的組織。chrome

HTML規範的最新版本是HTML4(http://www.w3.org/TR/html401/),HTML5還在制定中(譯註:兩年前),最新的CSS規範版本是2(http://www.w3.org/TR/CSS2),CSS3也還正在制定中(譯註:一樣兩年前)。express

這些年來,瀏覽器廠商紛紛開發本身的擴展,對規範的遵循並不完善,這爲web開發者帶來了嚴重的兼容性問題。

可是,瀏覽器的用戶界面則差很少,常見的用戶界面元素包括:

  • 用來輸入URI的地址欄
  • 前進、後退按鈕
  • 書籤選項
  • 用於刷新及暫停當前加載文檔的刷新、暫停按鈕
  • 用於到達主頁的主頁按鈕

奇怪的是,並無哪一個正式公佈的規範對用戶界面作出規定,這些是多年來各瀏覽器廠商之間相互模仿和不斷改進得結果。

HTML5並無規定瀏覽器必須具備的UI元素,但列出了一些經常使用元素,包括地址欄、狀態欄及工具欄。還有一些瀏覽器有本身專有得功能,好比Firefox得下載管理。更多相關內容將在後面討論用戶界面時介紹。

瀏覽器的主要構成High Level Structure

瀏覽器的主要組件包括:

  1. 用戶界面- 包括地址欄、後退/前進按鈕、書籤目錄等,也就是你所看到的除了用來顯示你所請求頁面的主窗口以外的其餘部分
  2. 瀏覽器引擎- 用來查詢及操做渲染引擎的接口
  3. 渲染引擎- 用來顯示請求的內容,例如,若是請求內容爲html,它負責解析html及css,並將解析後的結果顯示出來
  4. 網絡- 用來完成網絡調用,例如http請求,它具備平臺無關的接口,能夠在不一樣平臺上工做
  5. UI 後端- 用來繪製相似組合選擇框及對話框等基本組件,具備不特定於某個平臺的通用接口,底層使用操做系統的用戶接口
  6. JS解釋器- 用來解釋執行JS代碼
  7. 數據存儲- 屬於持久層,瀏覽器須要在硬盤中保存相似cookie的各類數據,HTML5定義了web database技術,這是一種輕量級完整的客戶端存儲技術

圖1:瀏覽器主要組件

須要注意的是,不一樣於大部分瀏覽器,Chrome爲每一個Tab分配了各自的渲染引擎實例,每一個Tab就是一個獨立的進程。

對於構成瀏覽器的這些組件,後面會逐一詳細討論。

組件間的通訊 Communication between the components

Firefox和Chrome都開發了一個特殊的通訊結構,後面將有專門的一章進行討論。

渲染引擎 The rendering engine

渲染引擎的職責就是渲染,即在瀏覽器窗口中顯示所請求的內容。

默認狀況下,渲染引擎能夠顯示html、xml文檔及圖片,它也能夠藉助插件(一種瀏覽器擴展)顯示其餘類型數據,例如使用PDF閱讀器插件,能夠顯示PDF格式,將由專門一章講解插件及擴展,這裏只討論渲染引擎最主要的用途——顯示應用了CSS以後的html及圖片。

渲染引擎 Rendering engines

本文所討論得瀏覽器——Firefox、Chrome和Safari是基於兩種渲染引擎構建的,Firefox使用Geoko——Mozilla自主研發的渲染引擎,Safari和Chrome都使用webkit。

Webkit是一款開源渲染引擎,它原本是爲linux平臺研發的,後來由Apple移植到Mac及Windows上,相關內容請參考http://webkit.org

主流程 The main flow

渲染引擎首先經過網絡得到所請求文檔的內容,一般以8K分塊的方式完成。

下面是渲染引擎在取得內容以後的基本流程:

解析html以構建dom樹->構建render樹->佈局render樹->繪製render樹

圖2:渲染引擎基本流程

渲染引擎開始解析html,並將標籤轉化爲內容樹中的dom節點。接着,它解析外部CSS文件及style標籤中的樣式信息。這些樣式信息以及html中的可見性指令將被用來構建另外一棵樹——render樹。

Render樹由一些包含有顏色和大小等屬性的矩形組成,它們將被按照正確的順序顯示到屏幕上。

Render樹構建好了以後,將會執行佈局過程,它將肯定每一個節點在屏幕上的確切座標。再下一步就是繪製,即遍歷render樹,並使用UI後端層繪製每一個節點。

值得注意的是,這個過程是逐步完成的,爲了更好的用戶體驗,渲染引擎將會盡量早的將內容呈現到屏幕上,並不會等到全部的html都解析完成以後再去構建和佈局render樹。它是解析完一部份內容就顯示一部份內容,同時,可能還在經過網絡下載其他內容。

圖3:webkit主流程

圖4:Mozilla的Geoko 渲染引擎主流程

從圖3和4中能夠看出,儘管webkit和Gecko使用的術語稍有不一樣,他們的主要流程基本相同。Gecko稱可見的格式化元素組成的樹爲frame樹,每一個元素都是一個frame,webkit則使用render樹這個名詞來命名由渲染對象組成的樹。Webkit中元素的定位稱爲佈局,而Gecko中稱爲迴流。Webkit稱利用dom節點及樣式信息去構建render樹的過程爲attachment,Gecko在html和dom樹之間附加了一層,這層稱爲內容接收器,至關製造dom元素的工廠。下面將討論流程中的各個階段。

解析 Parsing-general

既然解析是渲染引擎中一個很是重要的過程,咱們將稍微深刻的研究它。首先簡要介紹一下解析。

解析一個文檔即將其轉換爲具備必定意義的結構——編碼能夠理解和使用的東西。解析的結果一般是表達文檔結構的節點樹,稱爲解析樹或語法樹。

例如,解析「2+3-1」這個表達式,可能返回這樣一棵樹。

圖5:數學表達式樹節點

文法 Grammars

解析基於文檔依據的語法規則——文檔的語言或格式。每種可被解析的格式必須具備由詞彙及語法規則組成的特定的文法,稱爲上下文無關文法。人類語言不具備這一特性,所以不能被通常的解析技術所解析。

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

解析能夠分爲兩個子過程——語法分析及詞法分析

詞法分析就是將輸入分解爲符號,符號是語言的詞彙表——基本有效單元的集合。對於人類語言來講,它至關於咱們字典中出現的全部單詞。

語法分析指對語言應用語法規則。

解析器通常將工做分配給兩個組件——詞法分析器(有時也叫分詞器)負責將輸入分解爲合法的符號,解析器則根據語言的語法規則分析文檔結構,從而構建解析樹,詞法分析器知道怎麼跳過空白和換行之類的無關字符。

圖6:從源文檔到解析樹

解析過程是迭代的,解析器從詞法分析器處取道一個新的符號,並試着用這個符號匹配一條語法規則, 若是匹配了一條規則,這個符號對應的節點將被添加到解析樹上,而後解析器請求另外一個符號。若是沒有匹配到規則,解析器將在內部保存該符號,並從詞法分析器 取下一個符號,直到全部內部保存的符號可以匹配一項語法規則。若是最終沒有找到匹配的規則,解析器將拋出一個異常,這意味着文檔無效或是包含語法錯誤。

轉換 Translation

不少時候,解析樹並非最終結果。解析通常在轉換中使用——將輸入文檔轉換爲另外一種格式。編譯就是個例子,編譯器在將一段源碼編譯爲機器碼的時候,先將源碼解析爲解析樹,而後將該樹轉換爲一個機器碼文檔。

圖7:編譯流程

解析實例 Parsing example

圖5中,咱們從一個數學表達式構建了一個解析樹,這裏定義一個簡單的數學語言來看下解析過程。

詞彙表:咱們的語言包括整數、加號及減號。

語法:

1. 該語言的語法基本單元包括表達式、term及操做符

2. 該語言能夠包括多個表達式

3. 一個表達式定義爲兩個term經過一個操做符鏈接

4. 操做符能夠是加號或減號

5. term能夠是一個整數或一個表達式

如今來分析一下「2+3-1」這個輸入

第一個匹配規則的子字符串是「2」,根據規則5,它是一個term,第二個匹配的是「2+3」,它符合第2條規則——一個操做符鏈接兩個term,下一次匹配發生在輸入的結束處。「2+3-1」是一個表達式,由於咱們已經知道「2+3」是一個term,因此咱們有了一個term緊跟着一個操做符及另外一個term。「2++」將不會匹配任何規則,所以是一個無效輸入。

詞彙表及語法的定義

詞彙表一般利用正則表達式來定義。

例如上面的語言能夠定義爲:

INTEGER:0|[1-9][0-9]*

PLUS:+

MINUS:-

正如看到的,這裏用正則表達式定義整數。

語法一般用BNF格式定義,咱們的語言能夠定義爲:

expression := term operation term

operation := PLUS | MINUS

term := INTEGER | expression

若是一個語言的文法是上下文無關的,則它能夠用正則解析器來解析。對上下文無關文法的一個直觀的定義是,該文法能夠用BNF來完整的表達。可查看http://en.wikipedia.org/wiki/Context-free_grammar

解析器類型 Types of parsers

有兩種基本的解析器——自頂向下解析及自底向上解析。比較直觀的解釋是,自頂向下解析,查看語法的最高層結構並試着匹配其中一個;自底向上解析則從輸入開始,逐步將其轉換爲語法規則,從底層規則開始直到匹配高層規則。

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

自頂向下解析器從最高層規則開始——它先識別出「2+3「,將其視爲一個表達式,而後識別出」2+3-1「爲一個表達式(識別表達式的過程當中匹配了其餘規則,但出發點是最高層規則)。

自底向上解析會掃描輸入直到匹配了一條規則,而後用該規則取代匹配的輸入,直到解析完全部輸入。部分匹配的表達式被放置在解析堆棧中。

Stack

Input

  2 + 3 – 1
term + 3 - 1
term operation 3 – 1
expression - 1
expression operation 1
expression  

自底向上解析器稱爲shift reduce 解析器,由於輸入向右移動(想象一個指針首先指向輸入開始處,並向右移動),並逐漸簡化爲語法規則。

自動化解析 Generating parse

解析器生成器這個工具能夠自動生成解析器,只須要指定語言的文法——詞彙表及語法規則,它就能夠生成一個解析器。建立一個解析器須要對解析有深刻的理解,並且手動的建立一個由較好性能的解析器並不容易,因此解析生成器頗有用。Webkit使用兩個知名的解析生成器——用於建立語法分析器的Flex及建立解析器的Bison(你可能接觸過Lex和Yacc)。Flex的輸入是一個包含了符號定義的正則表達式,Bison的輸入是用BNF格式表示的語法規則。rs automatically

HTML解析器 HTML Parser

HTML解析器的工做是將html標識解析爲解析樹。

HTML文法定義 The HTML grammar definition

W3C組織制定規範定義了HTML的詞彙表和語法。

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

正如在解析簡介中提到的,上下文無關文法的語法能夠用相似BNF的格式來定義。

不幸的是,全部的傳統解析方式都不適用於html(固然我提出它們並不僅是由於好玩,它們將用來解析css和js),html不能簡單的用解析所需的上下文無關文法來定義。

Html 有一個正式的格式定義——DTD(Document Type Definition 文檔類型定義)——但它並非上下文無關文法,html更接近於xml,如今有不少可用的xml解析器,html有個xml的變體——xhtml,它們間的不一樣在於,html更寬容,它容許忽略一些特定標籤,有時能夠省略開始或結束標籤。總的來講,它是一種soft語法,不像xml呆板、執拗。

顯然,這個看起來很小的差別卻帶來了很大的不一樣。一方面,這是html流行的緣由——它的寬容使web開發人員的工做更加輕鬆,但另外一方面,這也使很難去寫一個格式化的文法。因此,html的解析並不簡單,它既不能用傳統的解析器解析,也不能用xml解析器解析。

HTML DTD

Html適用DTD格式進行定義,這一格式是用於定義SGML家族的語言,包括了對全部容許元素及它們的屬性和層次關係的定義。正如前面提到的,html DTD並無生成一種上下文無關文法。

DTD有一些變種,標準模式只遵照規範,而其餘模式則包含了對瀏覽器過去所使用標籤的支持,這麼作是爲了兼容之前內容。最新的標準DTD在http://www.w3.org/TR/html4/strict.dtd

DOM

輸出的樹,也就是解析樹,是由DOM元素及屬性節點組成的。DOM是文檔對象模型的縮寫,它是html文檔的對象表示,做爲html元素的外部接口供js等調用。

樹的根是「document」對象。

DOM和標籤基本是一一對應的關係,例如,以下的標籤:

<html>
    <body>
        <p>
            Hello DOM
        </p>
        <div><img src=」example.png」 /></div>
    </body>
</html>

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

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

和html同樣,DOM的規範也是由W3C組織制定的。訪問http://www.w3.org/DOM/DOMTR,這是使用文檔的通常規範。一個模型描述一種特定的html元素,能夠在http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.htm 查看html定義。

這裏所謂的樹包含了DOM節點是說樹是由實現了DOM接口的元素構建而成的,瀏覽器使用已被瀏覽器內部使用的其餘屬性的具體實現。

解析算法 The parsing algorithm

正如前面章節中討論的,hmtl不能被通常的自頂向下或自底向上的解析器所解析。

緣由是:

1. 這門語言自己的寬容特性

2. 瀏覽器對一些常見的非法html有容錯機制

3. 解析過程是往復的,一般源碼不會在解析過程當中發生改變,但在html中,腳本標籤包含的「document.write 」可能添加標籤,這說明在解析過程當中實際上修改了輸入

不能使用正則解析技術,瀏覽器爲html定製了專屬的解析器。

Html5規範中描述了這個解析算法,算法包括兩個階段——符號化及構建樹。

符號化是詞法分析的過程,將輸入解析爲符號,html的符號包括開始標籤、結束標籤、屬性名及屬性值。

符號識別器識別出符號後,將其傳遞給樹構建器,並讀取下一個字符,以識別下一個符號,這樣直處處理完全部輸入。

圖9:HTML解析流程

符號識別算法 The tokenization algorithm

算法輸出html符號,該算法用狀態機表示。每次讀取輸入流中的一個或多個字符,並根據這些字符轉移到下一個狀態,當前的符號狀態及構建樹狀態共同影響結果,這意味着,讀取一樣的字符,可能由於當前狀態的不一樣,獲得不一樣的結果以進入下一個正確的狀態。

這個算法很複雜,這裏用一個簡單的例子來解釋這個原理。

基本示例——符號化下面的html:

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

初始狀態爲「Data State」,當遇到「<」字符,狀態變爲「Tag open state」,讀取一個a-z的字符將產生一個開始標籤符號,狀態相應變爲「Tag name state」,一直保持這個狀態直到讀取到「>」,每一個字符都附加到這個符號名上,例子中建立的是一個html符號。

當讀取到「>」,當前的符號就完成了,此時,狀態回到「Data state」,「<body>」重複這一處理過程。到這裏,html和body標籤都識別出來了。如今,回到「Data state」,讀取「Hello world」中的字符「H」將建立並識別出一個字符符號,這裏會爲「Hello world」中的每一個字符生成一個字符符號。

這樣直到遇到「</body>」中的「<」。如今,又回到了「Tag open state」,讀取下一個字符「/」將建立一個閉合標籤符號,而且狀態轉移到「Tag name state」,仍是保持這一狀態,直到遇到「>」。而後,產生一個新的標籤符號並回到「Data state」。後面的「</html>」將和「</body>」同樣處理。

圖10:符號化示例輸入

樹的構建算法 Tree construction algorithm

在樹的構建階段,將修改以Document爲根的DOM樹,將元素附加到樹上。每一個由符號識別器識別生成的節點將會被樹構造器進行處理,規範中定義了每一個符號相對應的Dom元素,對應的Dom元素將會被建立。這些元素除了會被添加到Dom樹上,還將被添加到開放元素堆棧中。這個堆棧用來糾正嵌套的未匹配和未閉合標籤,這個算法也是用狀態機來描述,全部的狀態採用插入模式。

來看一下示例中樹的建立過程:

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

構建樹這一階段的輸入是符號識別階段生成的符號序列。

首先是「initial mode」,接收到html符號後將轉換爲「before html」模式,在這個模式中對這個符號進行再處理。此時,建立了一個HTMLHtmlElement元素,並將其附加到根Document對象上。

狀態此時變爲「before head」,接收到body符號時,即便這裏沒有head符號,也將自動建立一個HTMLHeadElement元素並附加到樹上。

如今,轉到「in head」模式,而後是「after head」。到這裏,body符號會被再次處理,將建立一個HTMLBodyElement並插入到樹中,同時,轉移到「in body」模式。

而後,接收到字符串「Hello world」的字符符號,第一個字符將致使建立並插入一個text節點,其餘字符將附加到該節點。

接收到body結束符號時,轉移到「after body」模式,接着接收到html結束符號,這個符號意味着轉移到了「after after body」模式,當接收到文件結束符時,整個解析過程結束。

圖11:示例html樹的構建過程

解析結束時的處理 Action when the parsing is finished

在這個階段,瀏覽器將文檔標記爲可交互的,並開始解析處於延時模式中的腳本——這些腳本在文檔解析後執行。

文檔狀態將被設置爲完成,同時觸發一個load事件。

Html5規範中有符號化及構建樹的完整算法(http://www.w3.org/TR/html5/syntax.html#html-parser)。

瀏覽器容錯 Browsers error tolerance

你歷來不會在一個html頁面上看到「無效語法」這樣的錯誤,瀏覽器修復了無效內容並繼續工做。

如下面這段html爲例:

<html>
    <mytag>
    </mytag>
    <div>
    <p>
    </div>
        Really lousy HTML
    </p>
</html>

這段html違反了不少規則(mytag不是合法的標籤,p及div錯誤的嵌套等等),可是瀏覽器仍然能夠沒有任何怨言的繼續顯示,它在解析的過程當中修復了html做者的錯誤。

瀏覽器都具備錯誤處理的能力,可是,另人驚訝的是,這並非html最新規範的內容,就像書籤及前進後退按鈕同樣,它只是瀏覽器長期發展的結果。一些比較知名的非法html結構,在許多站點中出現過,瀏覽器都試着以一種和其餘瀏覽器一致的方式去修復。

Html5規範定義了這方面的需求,webkit在html解析類開始部分的註釋中作了很好的總結。

解析器將符號化的輸入解析爲文檔並建立文檔,但不幸的是,咱們必須處理不少沒有很好格式化的html文檔,至少要當心下面幾種錯誤狀況。

1. 在未閉合的標籤中添加明確禁止的元素。這種狀況下,應該先將前一標籤閉合

2. 不能直接添加元素。有些人在寫文檔的時候會忘了中間一些標籤(或者中間標籤是可選的),好比HTML HEAD BODY TR TD LI等

3. 想在一個行內元素中添加塊狀元素。關閉全部的行內元素,直到下一個更高的塊狀元素

4. 若是這些都不行,就閉合當前標籤直到能夠添加該元素。

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

</br>替代<br>

一些網站使用</br>替代<br>,爲了兼容IE和Firefox,webkit將其看做<br>。

代碼:

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

Note-這裏的錯誤處理在內部進行,用戶看不到。

迷路的表格

這指一個表格嵌套在另外一個表格中,但不在它的某個單元格內。

好比下面這個例子:

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

webkit將會將嵌套的表格變爲兩個兄弟表格:

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

代碼:

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

webkit使用堆棧存放當前的元素內容,它將從外部表格的堆棧中彈出內部的表格,則它們變爲了兄弟表格。

嵌套的表單元素

用戶將一個表單嵌套到另外一個表單中,則第二個表單將被忽略。

代碼:

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,由於一些愚蠢的網頁老是在還未真正結束時就閉合它。咱們依賴調用end方法去執行關閉的處理。

代碼:

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

因此,web開發者要當心了,除非你想成爲webkit容錯代碼的範例,不然仍是寫格式良好的html吧。

CSS解析 CSS parsing

還記得簡介中提到的解析的概念嗎,不一樣於html,css屬於上下文無關文法,能夠用前面所描述的解析器來解析。Css規範定義了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*] ')' ]
  ;

說明:一個規則集合有這樣的結構

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

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

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

這說明,一個規則集合具備一個或是可選個數的多個選擇器,這些選擇器以逗號和空格(S表示空格)進行分隔。每一個規則集合包含大括號及大括號中的一條或多條以分號隔開的聲明。聲明和選擇器在後面進行定義。

Webkit CSS 解析器 Webkit CSS parser

Webkit使用Flex和Bison解析生成器從CSS語法文件中自動生成解析器。回憶一下解析器的介紹,Bison建立一個自底向上的解析器,Firefox使用自頂向下解析器。它們都是將每一個css文件解析爲樣式表對象,每一個對象包含css規則,css規則對象包含選擇器和聲明對象,以及其餘一些符合css語法的對象。

圖12:解析css

腳本解析 Parsing scripts

本章將介紹Javascript。

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

腳本

web的模式是同步的,開發者但願解析到一個script標籤時當即解析執行腳本,並阻塞文檔的解析直到腳本執行完。若是腳本是外引的,則網絡必須先請求到這個資源——這個過程也是同步的,會阻塞文檔的解析直到資源被請求到。這個模式保持了不少年,而且在html4及html5中都特別指定了。開發者能夠將腳本標識爲defer,以使其不阻塞文檔解析,並在文檔解析結束後執行。Html5增長了標記腳本爲異步的選項,以使腳本的解析執行使用另外一個線程。

預解析 Speculative parsing

Webkit和Firefox都作了這個優化,當執行腳本時,另外一個線程解析剩下的文檔,並加載後面須要經過網絡加載的資源。這種方式可使資源並行加載從而使總體速度更快。須要注意的是,預解析並不改變Dom樹,它將這個工做留給主解析過程,本身只解析外部資源的引用,好比外部腳本、樣式表及圖片。

樣式表 Style sheets

樣式表採用另外一種不一樣的模式。理論上,既然樣式表不改變Dom樹,也就沒有必要停下文檔的解析等待它們,然而,存在一個問題,腳本可能在文檔的解析過程當中請求樣式信息,若是樣式尚未加載和解析,腳本將獲得錯誤的值,顯然這將會致使不少問題,這看起來是個邊緣狀況,但確實很常見。Firefox在存在樣式表還在加載和解析時阻塞全部的腳本,而chrome只在當腳本試圖訪問某些可能被未加載的樣式表所影響的特定的樣式屬性時才阻塞這些腳本。

渲染樹的構造 Render tree construction

當Dom樹構建完成時,瀏覽器開始構建另外一棵樹——渲染樹。渲染樹由元素顯示序列中的可見元素組成,它是文檔的可視化表示,構建這棵樹是爲了以正確的順序繪製文檔內容。

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
}

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

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中,若是一個元素想建立一個特殊的渲染對象,它須要複寫「createRenderer」方法,使渲染對象指向不包含幾何信息的樣式對象。

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

渲染對象和Dom元素相對應,但這種對應關係不是一對一的,不可見的Dom元素不會被插入渲染樹,例如head元素。另外,display屬性爲none的元素也不會在渲染樹中出現(visibility屬性爲hidden的元素將出如今渲染樹中)。

還有一些Dom元素對應幾個可見對象,它們通常是一些具備複雜結構的元素,沒法用一個矩形來描述。例如,select元素有三個渲染對象——一個顯示區域、一個下拉列表及一個按鈕。一樣,當文本由於寬度不夠而折行時,新行將做爲額外的渲染元素被添加。另外一個多個渲染對象的例子是不規範的html,根據css規範,一個行內元素只能僅包含行內元素或僅包含塊狀元素,在存在混合內容時,將會建立匿名的塊狀渲染對象包裹住行內元素。

一些渲染對象和所對應的Dom節點不在樹上相同的位置,例如,浮動和絕對定位的元素在文本流以外,在兩棵樹上的位置不一樣,渲染樹上標識出真實的結構,並用一個佔位結構標識出它們原來的位置。

圖12:渲染樹及對應的Dom樹

建立樹的流程 The flow of constructing the tree

Firefox中,表述爲一個監聽Dom更新的監聽器,將frame的建立委派給Frame Constructor,這個構建器計算樣式(參看樣式計算)並建立一個frame。

Webkit中,計算樣式並生成渲染對象的過程稱爲attachment,每一個Dom節點有一個attach方法,attachment的過程是同步的,調用新節點的attach方法將節點插入到Dom樹中。

處理html和body標籤將構建渲染樹的根,這個根渲染對象對應被css規範稱爲containing block的元素——包含了其餘全部塊元素的頂級塊元素。它的大小就是viewport——瀏覽器窗口的顯示區域,Firefox稱它爲viewPortFrame,webkit稱爲RenderView,這個就是文檔所指向的渲染對象,樹中其餘的部分都將做爲一個插入的Dom節點被建立。

樣式計算 Style Computation

建立渲染樹須要計算出每一個渲染對象的可視屬性,這能夠經過計算每一個元素的樣式屬性獲得。

樣式包括各類來源的樣式表,行內樣式元素及html中的可視化屬性(例如bgcolor),可視化屬性轉化爲css樣式屬性。

樣式表來源於瀏覽器默認樣式表,及頁面做者和用戶提供的樣式表——有些樣式是瀏覽器用戶提供的(瀏覽器容許用戶定義喜歡的樣式,例如,在Firefox中,能夠經過在Firefox Profile目錄下放置樣式表實現)。

計算樣式的一些困難:

1. 樣式數據是很是大的結構,保存大量的樣式屬性會帶來內存問題

2. 若是不進行優化,找到每一個元素匹配的規則會致使性能問題,爲每一個元素查找匹配的規則都須要遍歷整個規則表,這個過程有很大的工做量。選擇符可能有複雜的結構,匹配過程若是沿着一條開始看似正確,後來卻被證實是無用的路徑,則必須去嘗試另外一條路徑。

例如,下面這個複雜選擇符

div div div div{…}

這意味着規則應用到三個div的後代div元素,選擇樹上一條特定的路徑去檢查,這可能須要遍歷節點樹,最後卻發現它只是兩個div的後代,並不使用該規則,而後則須要沿着另外一條路徑去嘗試

3. 應用規則涉及很是複雜的級聯,它們定義了規則的層次

咱們來看一下瀏覽器如何處理這些問題:

共享樣式數據

webkit節點引用樣式對象(渲染樣式),某些狀況下,這些對象能夠被節點間共享,這些節點須要是兄弟或是表兄弟節點,而且:

  1. 這些元素必須處於相同的鼠標狀態(好比不能一個處於hover,而另外一個不是)
  2. 不能有元素具備id
  3. 標籤名必須匹配
  4. class屬性必須匹配
  5. 對應的屬性必須相同
  6. 連接狀態必須匹配
  7. 焦點狀態必須匹配
  8. 不能有元素被屬性選擇器影響
  9. 元素不能有行內樣式屬性
  10. 不能有生效的兄弟選擇器,webcore在任何兄弟選擇器相遇時只是簡單的拋出一個全局轉換,而且在它們顯示時使整個文檔的樣式共享失效,這些包括+選擇器和相似:first-child和:last-child這樣的選擇器。

Firefox規則樹 Firefox rule tree

Firefox用兩個樹用來簡化樣式計算-規則樹和樣式上下文樹,webkit也有樣式對象,但它們並無存儲在相似樣式上下文樹這樣的樹中,只是由Dom節點指向其相關的樣式。

圖14:Firefox樣式上下文樹

樣式上下文包含最終值,這些值是經過以正確順序應用全部匹配的規則,並將它們由邏輯值轉換爲具體的值,例如,若是邏輯值爲屏幕的百分比,則經過計算將其轉化爲絕對單位。樣式樹的使用確實很巧妙,它使得在節點中共享的這些值不須要被屢次計算,同時也節省了存儲空間。

全部匹配的規則都存儲在規則樹中,一條路徑中的底層節點擁有最高的優先級,這棵樹包含了所找到的 全部規則匹配的路徑(譯註:能夠取巧理解爲每條路徑對應一個節點,路徑上包含了該節點所匹配的全部規則)。規則樹並非一開始就爲全部節點進行計算,而是 在某個節點須要計算樣式時,才進行相應的計算並將計算後的路徑添加到樹中。

咱們將樹上的路徑當作辭典中的單詞,假如已經計算出了以下的規則樹:

假如須要爲內容樹中的另外一個節點匹配規則,如今知道匹配的規則(以正確的順序)爲B-E-I,由於咱們已經計算出了路徑A-B-E-I-L,因此樹上已經存在了這條路徑,剩下的工做就不多了。

如今來看一下樹如何保存。

結構化

樣式上下文按結構劃分,這些結構包括相似border或color這樣的特定分類的樣式信息。一個結構中的全部特性不是繼承的就是非繼承的,對繼承的特性,除非元素自身有定義,不然就從它的parent繼承。非繼承的特性(稱爲reset特性)若是沒有定義,則使用默認的值。

樣式上下文樹緩存完整的結構(包括計算後的值),這樣,若是底層節點沒有爲一個結構提供定義,則使用上層節點緩存的結構。

使用規則樹計算樣式上下文

當爲一個特定的元素計算樣式時,首先計算出規則樹中的一條路徑,或是使用已經存在的一條,而後使 用路徑中的規則去填充新的樣式上下文,從樣式的底層節點開始,它具備最高優先級(一般是最特定的選擇器),遍歷規則樹,直到填滿結構。若是在那個規則節點 沒有定義所需的結構規則,則沿着路徑向上,直到找到該結構規則。

若是最終沒有找到該結構的任何規則定義,那麼若是這個結構是繼承型的,則找到其在內容樹中的parent的結構,這種狀況下,咱們也成功的共享告終構;若是這個結構是reset型的,則使用默認的值。

若是特定的節點添加了值,那麼須要作一些額外的計算以將其轉換爲實際值,而後在樹上的節點緩存該值,使它的children可使用。

當一個元素和它的一個兄弟元素指向同一個樹節點時,完整的樣式上下文能夠被它們共享。

來看一個例子:假設有下面這段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>

以及下面這些規則

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結構包含四邊。

生成的規則樹以下(節點名:指向的規則)

上下文樹以下(節點名:指向的規則節點)

假設咱們解析html,遇到第二個div標籤,咱們須要爲這個節點建立樣式上下文,並填充它的樣式結構。

咱們進行規則匹配,找到這個div匹配的規則爲一、二、6,咱們發現規則樹上已經存在了一條咱們可使用的路徑一、2,咱們只需爲規則6新增一個節點添加到下面(就是規則樹中的F)。

而後建立一個樣式上下文並將其放到上下文樹中,新的樣式上下文將指向規則樹中的節點F。

如今咱們須要填充這個樣式上下文,先從填充margin結構開始,既然最後一個規則節點沒有添加margin結構,沿着路徑向上,直到找到緩存的前面插入節點計算出的結構,咱們發現B是最近的指定margin值的節點。由於已經有了color結構的定義,因此不能使用緩存的結構,既然color只有一個屬性,也就不須要沿着路徑向上填充其餘屬性。計算出最終值(將字符串轉換爲RGB等),並緩存計算後的結構。

第二個span元素更簡單,進行規則匹配後發現它指向規則G,和前一個span同樣,既然有兄弟節點指向同一個節點,就能夠共享完整的樣式上下文,只需指向前一個span的上下文。

由於結構中包含繼承自parent的規則,上下文樹作了緩存(color特性是繼承來的,但Firefox將其視爲reset並在規則樹中緩存)。

例如,若是咱們爲一個paragraph的文字添加規則:

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

那麼這個p在內容樹中的子節點div,會共享和它parent同樣的font結構,這種狀況發生在沒有爲這個div指定font規則時。

Webkit中,並無規則樹,匹配的聲明會被遍歷四次,先是應用非important的高優先級屬性(之因此先應用這些屬性,是由於其餘的依賴於它們-好比display),其次是高優先級important的,接着是通常優先級非important的,最後是通常優先級important的規則。這樣,出現屢次的屬性將被按照正確的級聯順序進行處理,最後一個生效。

總結一下,共享樣式對象(結構中完整或部份內容)解決了問題1和3,Firefox的規則樹幫助以正確的順序應用規則。

對規則進行處理以簡化匹配過程

樣式規則有幾個來源:

· 外部樣式表或style標籤內的css規則

· 行內樣式屬性

· html可視化屬性(映射爲相應的樣式規則)

後面兩個很容易匹配到元素,由於它們所擁有的樣式屬性和html屬性能夠將元素做爲key進行映射。

就像前面問題2所提到的,css的規則匹配可能很狡猾,爲了解決這個問題,能夠先對規則進行處理,以使其更容易被訪問。

解析完樣式表以後,規則會根據選擇符添加一些hash映射,映射能夠是根據id、class、標籤名或是任何不屬於這些分類的綜合映射。若是選擇符爲id,規則將被添加到id映射,若是是class,則被添加到class映射,等等。

這個處理是匹配規則更容易,不須要查看每一個聲明,咱們能從映射中找到一個元素的相關規則,這個優化使在進行規則匹配時減小了95+%的工做量。

來看下面的樣式規則:

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

第一條規則將被插入class映射,第二條插入id映射,第三條是標籤映射。

下面這個html片斷:

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

咱們首先找到p元素對應的規則,class映射將包含一個「error」的key,找到p.error的規則,div在id映射和標籤映射中都有相關的規則,剩下的工做就是找出這些由key對應的規則中哪些確實是正確匹配的。

例如,若是div的規則是

table div {margin:5px}

這也是標籤映射產生的,由於key是最右邊的選擇符,但它並不匹配這裏的div元素,由於這裏的div沒有table祖先。

Webkit和Firefox都會作這個處理。

以正確的級聯順序應用規則

樣式對象擁有對應全部可見屬性的屬性,若是特性沒有被任何匹配的規則所定義,那麼一些特性能夠從parent的樣式對象中繼承,另一些使用默認值。

這個問題的產生是由於存在不止一處的定義,這裏用級聯順序解決這個問題。

樣式表的級聯順序

一個樣式屬性的聲明可能在幾個樣式表中出現,或是在一個樣式表中出現屢次,所以,應用規則的順序相當重要,這個順序就是級聯順序。根據css2的規範,級聯順序爲(從低到高):

1. 瀏覽器聲明

2. 用戶聲明

3. 做者的通常聲明

4. 做者的important聲明

5. 用戶important聲明

瀏覽器聲明是最不重要的,用戶只有在聲明被標記爲important時纔會覆蓋做者的聲明。具備同等級別的聲明將根據specifity以及它們被定義時的順序進行排序。Html可視化屬性將被轉換爲匹配的css聲明,它們被視爲最低優先級的做者規則。

Specifity

Css2規範中定義的選擇符specifity以下:

· 若是聲明來自style屬性,而不是一個選擇器的規則,則計1,不然計0(=a)

· 計算選擇器中id屬性的數量(=b)

· 計算選擇器中class及僞類的數量(=c)

· 計算選擇器中元素名及僞元素的數量(=d)

鏈接a-b-c-d四個數量(用一個大基數的計算系統)將獲得specifity。這裏使用的基數由分類中最高的基數定義。例如,若是a爲14,可使用16進制。不一樣狀況下,a爲17時,則須要使用阿拉伯數字17做爲基數,這種狀況可能在這個選擇符時發生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 */

規則排序

規則匹配後,須要根據級聯順序對規則進行排序,webkit先將小列表用冒泡排序,再將它們合併爲一個大列表,webkit經過爲規則複寫「>」操做來執行排序:

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時樣式沒有徹底加載,則放置佔位符,並在文檔中標記,一旦樣式表完成加載就從新進行計算。

佈局 Layout

當渲染對象被建立並添加到樹中,它們並無位置和大小,計算這些值的過程稱爲layout或reflow。

Html使用基於流的佈局模型,意味着大部分時間,能夠以單一的途徑進行幾何計算。流中靠後的元素並不會影響前面元素的幾何特性,因此佈局能夠在文檔中從右向左、自上而下的進行。也存在一些例外,好比html tables。

座標系統相對於根frame,使用top和left座標。

佈局是一個遞歸的過程,由根渲染對象開始,它對應html文檔元素,佈局繼續遞歸的經過一些或全部的frame層級,爲每一個須要幾何信息的渲染對象進行計算。

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

全部的渲染對象都有一個layout或reflow方法,每一個渲染對象調用須要佈局的children的layout方法。

Dirty bit 系統

爲了避免由於每一個小變化都所有從新佈局,瀏覽器使用一個dirty bit系統,一個渲染對象發生了變化或是被添加了,就標記它及它的children爲dirty-須要layout。存在兩個標識-dirty及children are dirty,children are dirty說明即便這個渲染對象可能沒問題,但它至少有一個child須要layout。

全局和增量 layout

當layout在整棵渲染樹觸發時,稱爲全局layout,這可能在下面這些狀況下發生:

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

2. 窗口resize

layout也能夠是增量的,這樣只有標誌爲dirty的渲染對象會從新佈局(也將致使一些額外的佈局)。增量 layout會在渲染對象dirty時異步觸發,例如,當網絡接收到新的內容並添加到Dom樹後,新的渲染對象會添加到渲染樹中。

圖20:增量 layout

異步和同步layout

增量layout的過程是異步的,Firefox爲增量layout生成了reflow隊列,以及一個調度執行這些批處理命令。Webkit也有一個計時器用來執行增量layout-遍歷樹,爲dirty狀態的渲染對象從新佈局。

另外,當腳本請求樣式信息時,例如「offsetHeight」,會同步的觸發增量佈局。

全局的layout通常都是同步觸發。

有些時候,layout會被做爲一個初始layout以後的回調,好比滑動條的滑動。

優化

當一個layout由於resize或是渲染位置改變(並非大小改變)而觸發時,渲染對象的大小將會從緩存中讀取,而不會從新計算。

通常狀況下,若是隻有子樹發生改變,則layout並不從根開始。這種狀況發生在,變化發生在元素自身而且不影響它周圍元素,例如,將文本插入文本域(不然,每次擊鍵都將觸發從根開始的重排)。

layout過程

layout通常有下面這幾個部分:

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

2. parent渲染對象讀取chilidren,並:

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

2. 在須要時(它們當前爲dirty或是處於全局layout或者其餘緣由)調用child渲染對象的layout,這將計算child的高度

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

4. 將dirty標識設置爲false

Firefox使用一個「state」對象(nsHTMLReflowState)作爲參數去佈局(firefox稱爲reflow),state包含parent的寬度及其餘內容。

Firefox佈局的輸出是一個「metrics」對象(nsHTMLReflowMetrics)。它包括渲染對象計算出的高度。

寬度計算

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

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

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

· 容器的寬度是容器的可用寬度和0中的最大值,這裏的可用寬度爲:contentWidth=clientWidth()-paddingLeft()-paddingRight(),clientWidth和clientHeight表明一個對象內部的不包括border和滑動條的大小

· 元素的寬度指樣式屬性width的值,它能夠經過計算容器的百分比獲得一個絕對值

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

到這裏是最佳寬度的計算過程,如今計算寬度的最大值和最小值,若是最佳寬度大於最大寬度則使用最大寬度,若是小於最小寬度則使用最小寬度。最後緩存這個值,當須要layout但寬度未改變時使用。

Line breaking

當一個渲染對象在佈局過程當中須要折行時,則暫停並告訴它的parent它須要折行,parent將建立額外的渲染對象並調用它們的layout。

繪製 Painting

繪製階段,遍歷渲染樹並調用渲染對象的paint方法將它們的內容顯示在屏幕上,繪製使用UI基礎組件,這在UI的章節有更多的介紹。

全局和增量

和佈局同樣,繪製也能夠是全局的-繪製完整的樹-或增量的。在增量的繪製過程當中,一些渲染對象以不影響整棵樹的方式改變,改變的渲染對象使其在屏幕上的矩形區域失效,這將致使操做系統將其看做dirty區域,併產生一個paint事件,操做系統很巧妙的處理這個過程,並將多個區域合併爲一個。Chrome中,這個過程更復雜些,由於渲染對象在不一樣的進程中,而不是在主進程中。Chrome在必定程度上模擬操做系統的行爲,表現爲監聽事件並派發消息給渲染根,在樹中查找到相關的渲染對象,重繪這個對象(每每還包括它的children)。

繪製順序

css2定義了繪製過程的順序-http://www.w3.org/TR/CSS21/zindex.html。這個就是元素壓入堆棧的順序,這個順序影響着繪製,堆棧從後向前進行繪製。

一個塊渲染對象的堆棧順序是:

1. 背景色

2. 背景圖

3. border

4. children

5. outline

Firefox顯示列表

Firefox讀取渲染樹併爲繪製的矩形建立一個顯示列表,該列表以正確的繪製順序包含這個矩形相關的渲染對象。

用這樣的方法,可使重繪時只需查找一次樹,而不須要屢次查找——繪製全部的背景、全部的圖片、全部的border等等。

Firefox優化了這個過程,它不添加會被隱藏的元素,好比元素徹底在其餘不透明元素下面。

Webkit矩形存儲

重繪前,webkit將舊的矩形保存爲位圖,而後只繪製新舊矩形的差集。

動態變化

瀏覽器老是試着以最小的動做響應一個變化,因此一個元素顏色的變化將只致使該元素的重繪,元素位置的變化將大體元素的佈局和重繪,添加一個Dom節點,也會大體這個元素的佈局和重繪。一些主要的變化,好比增長html元素的字號,將會致使緩存失效,從而引發整數的佈局和重繪。

渲染引擎的線程

渲染引擎是單線程的,除了網絡操做之外,幾乎全部的事情都在單一的線程中處理,在Firefox和Safari中,這是瀏覽器的主線程,Chrome中這是tab的主線程。

網絡操做由幾個並行線程執行,並行鏈接的個數是受限的(一般是2-6個)。

事件循環

瀏覽器主線程是一個事件循環,它被設計爲無限循環以保持執行過程的可用,等待事件(例如layout和paint事件)並執行它們。下面是Firefox的主要事件循環代碼。

while (!mExiting)

NS_ProcessNextEvent(thread);

CSS2 可視模型 CSS2 visual module

畫布 The Canvas

根據CSS2規範,術語canvas用來描述格式化的結構所渲染的空間——瀏覽器繪製內容的地方。畫布對每一個維度空間都是無限大的,但瀏覽器基於viewport的大小選擇了一個初始寬度。

根據http://www.w3.org/TR/CSS2/zindex.html的定義,畫布若是是包含在其餘畫布內則是透明的,不然瀏覽器會指定一個顏色。

CSS盒模型

CSS盒模型描述了矩形盒,這些矩形盒是爲文檔樹中的元素生成的,並根據可視的格式化模型進行佈局。每一個box包括內容區域(如圖片、文本等)及可選的四周padding、border和margin區域。

每一個節點生成0-n個這樣的box。

全部的元素都有一個display屬性,用來決定它們生成box的類型,例如:

block-生成塊狀box

inline-生成一個或多個行內box

none-不生成box

默認的是inline,但瀏覽器樣式表設置了其餘默認值,例如,div元素默認爲block。能夠訪問http://www.w3.org/TR/CSS2/sample.html查看更多的默認樣式表示例。

定位策略 Position scheme

這裏有三種策略:

1. normal-對象根據它在文檔的中位置定位,這意味着它在渲染樹和在Dom樹中位置一致,並根據它的盒模型和大小進行佈局

2. float-對象先像普通流同樣佈局,而後儘量的向左或是向右移動

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

static和relative是normal,absolute和fixed屬於absolute。

在static定位中,不定義位置而使用默認的位置。其餘策略中,做者指定位置——top、bottom、left、right。

Box佈局的方式由這幾項決定:box的類型、box的大小、定位策略及擴展信息(好比圖片大小和屏幕尺寸)。

Box類型

Block box:構成一個塊,即在瀏覽器窗口上有本身的矩形

Inline box:並無本身的塊狀區域,但包含在一個塊狀區域內

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

Inline盒模型放置在行內或是line box中,每行至少和最高的box同樣高,當box以baseline對齊時——即一個元素的底部和另外一個box上除底部之外的某點對齊,行高能夠比最高的box高。當容器寬度不夠時,行內元素將被放到多行中,這在一個p元素中常常發生。

定位 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(可視區域)。

圖17:fixed

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

Layered representation

這個由CSS屬性中的z-index指定,表示盒模型的第三個大小,即在z軸上的位置。Box分發到堆棧中(稱爲堆棧上下文),每一個堆棧中靠後的元素將被較早繪製,棧頂靠前的元素離用戶最近,當發生交疊時,將隱藏靠後的元素。堆棧根據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後面,可能在正常流中也已經被繪製在後面,但z-index有更高優先級,因此在根box的堆棧中更靠前。

國外也有網友根據瀏覽器的工做原理繪製了幾張工做流程圖,方便你們經過簡易的圖片來了解這個辛苦的過程:

原文:http://taligarsiel.com/Projects/howbrowserswork1.htm
翻譯原文地址:http://blog.jobbole.com/12749/

相關文章
相關標籤/搜索