做爲前端你不得不知-瀏覽器的工做原理:網絡瀏覽器幕後揭祕

序言css

這是一篇全面介紹 WebKit 和 Gecko 內部操做的入門文章,是以色列開發人員塔利·加希爾大量研究的成果。在過去的幾年中,她查閱了全部公開發布的關於瀏覽器內部機制的數據(請參見資源),並花了不少時間來研讀網絡瀏覽器的源代碼。她寫道:html

在 IE 佔據 90% 市場份額的年代,咱們除了把瀏覽器當成一個「黑箱」,什麼也作不了。可是如今,開放源代碼的瀏覽器擁有了過半的市場份額,所以,是時候來揭開神祕的面紗,一探網絡瀏覽器的內幕了。呃,裏面只有數以百萬行計的 C++ 代碼...html5

塔利在她的網站上公佈了本身的研究成果,可是咱們以爲它值得讓更多的人來了解,因此咱們在此從新整理並公佈。web

做爲一名網絡開發人員,學習瀏覽器的內部工做原理將有助於您做出更明智的決策,並理解那些最佳開發實踐的箇中原因。儘管這是一篇至關長的文檔,可是咱們建議您花些時間來仔細閱讀;讀完以後,您確定會以爲所費不虛。保羅·愛麗詩 (Paul Irish),Chrome 瀏覽器開發人員事務部瀏覽器

簡介緩存

網絡瀏覽器極可能是使用最廣的軟件。在這篇入門文章中,我將會介紹它們的幕後工做原理。咱們會了解到,從您在地址欄輸入google.com直到您在瀏覽器屏幕上看到 Google 首頁的整個過程當中都發生了些什麼。網絡

網絡的模型是同步的。網頁做者但願解析器遇到 標記時當即解析並執行腳本。文檔的解析將中止,直到腳本執行完畢。若是腳本是外部的,那麼解析過程會中止,直到從網絡同步抓取資源完成後再繼續。此模型已經使用了多年,也在 HTML4 和 HTML5 規範中進行了指定。做者也能夠將腳本標註爲「defer」,這樣它就不會中止文檔解析,而是等到解析結束才執行。HTML5 增長了一個選項,可將腳本標記爲異步,以便由其餘線程解析和執行。架構

咱們要討論的瀏覽器app


圖:呈現樹及其對應的 DOM 樹 (3.1)。初始容器 block 爲「viewport」,而在 WebKit 中則爲「RenderView」對象。框架

構建呈現樹的流程

在 Firefox 中,系統會針對 DOM 更新註冊展現層,做爲偵聽器。展現層將框架建立工做委託給FrameConstructor,由該構造器解析樣式(請參閱樣式計算)並建立框架。

在 WebKit 中,解析樣式和建立呈現器的過程稱爲「附加」。每一個 DOM 節點都有一個「attach」方法。附加是同步進行的,將節點插入 DOM 樹須要調用新的節點「attach」方法。

處理 html 和 body 標記就會構建呈現樹根節點。這個根節點呈現對象對應於 CSS 規範中所說的容器 block,這是最上層的 block,包含了其餘全部 block。它的尺寸就是視口,即瀏覽器窗口顯示區域的尺寸。Firefox 稱之爲ViewPortFrame,而 WebKit 稱之爲RenderView。這就是文檔所指向的呈現對象。呈現樹的其他部分以 DOM 樹節點插入的形式來構建。

請參閱關於處理模型的 CSS2 規範

樣式計算

構建呈現樹時,須要計算每個呈現對象的可視化屬性。這是經過計算每一個元素的樣式屬性來完成的。

樣式包括來自各類來源的樣式表、inline 樣式元素和 HTML 中的可視化屬性(例如「bgcolor」屬性)。其中後者將通過轉化以匹配 CSS 樣式屬性。

樣式表的來源包括瀏覽器的默認樣式表、由網頁做者提供的樣式表以及由瀏覽器用戶提供的用戶樣式表(瀏覽器容許您定義本身喜歡的樣式。以 Firefox 爲例,用戶能夠將本身喜歡的樣式表放在「Firefox Profile」文件夾下)。

樣式計算存在如下難點:

樣式數據是一個超大的結構,存儲了無數的樣式屬性,這可能形成內存問題。

若是不進行優化,爲每個元素查找匹配的規則會形成性能問題。要爲每個元素遍歷整個規則列表來尋找匹配規則,這是一項浩大的工程。選擇器會具備很複雜的結構,這就會致使某個匹配過程一開始看起來極可能是正確的,但最終發現實際上是徒勞的,必須嘗試其餘匹配路徑。

例以下面這個組合選擇器:

div div div div{...}

這意味着規則適用於做爲 3 個 div 元素的子代的

。若是您要檢查規則是否適用於某個指定的

元素,應選擇樹上的一條向上路徑進行檢查。您可能須要向上遍歷節點樹,結果發現只有兩個 div,並且規則並不適用。而後,您必須嘗試樹中的其餘路徑。

應用規則涉及到至關複雜的層疊規則(用於定義這些規則的層次)。

讓咱們來看看瀏覽器是如何處理這些問題的:

共享樣式數據

WebKit 節點會引用樣式對象 (RenderStyle)。這些對象在某些狀況下能夠由不一樣節點共享。這些節點是同級關係,而且:

這些元素必須處於相同的鼠標狀態(例如,不容許其中一個是「:hover」狀態,而另外一個不是)

任何元素都沒有 ID

標記名稱應匹配

類屬性應匹配

映射屬性的集合必須是徹底相同的

連接狀態必須匹配

焦點狀態必須匹配

任何元素都不該受屬性選擇器的影響,這裏所說的「影響」是指在選擇器中的任何位置有任何使用了屬性選擇器的選擇器匹配

元素中不能有任何 inline 樣式屬性

不能使用任何同級選擇器。WebCore 在遇到任何同級選擇器時,只會引起一個全局開關,並停用整個文檔的樣式共享(若是存在)。這包括 + 選擇器以及 :first-child 和 :last-child 等選擇器。

Firefox 規則樹

爲了簡化樣式計算,Firefox 還採用了另外兩種樹:規則樹和樣式上下文樹。WebKit 也有樣式對象,但它們不是保存在相似樣式上下文樹這樣的樹結構中,只是由 DOM 節點指向此類對象的相關樣式。


圖:Firefox 樣式上下文樹 (2.2)

樣式上下文包含端值。要計算出這些值,應按照正確順序應用全部的匹配規則,並將其從邏輯值轉化爲具體的值。例如,若是邏輯值是屏幕大小的百分比,則須要換算成絕對的單位。規則樹的點子真的很巧妙,它使得節點之間能夠共享這些值,以免重複計算,還能夠節約空間。

全部匹配的規則都存儲在樹中。路徑中的底層節點擁有較高的優先級。規則樹包含了全部已知規則匹配的路徑。規則的存儲是延遲進行的。規則樹不會在開始的時候就爲全部的節點進行計算,而是隻有當某個節點樣式須要進行計算時,纔會向規則樹添加計算的路徑。

這個想法至關於將規則樹路徑視爲詞典中的單詞。若是咱們已經計算出以下的規則樹:


假設咱們須要爲內容樹中的另外一個元素匹配規則,而且找到匹配路徑是 B - E - I(按照此順序)。因爲咱們在樹中已經計算出了路徑 A - B - E - I - L,所以就已經有了此路徑,這就減小了如今所需的工做量。

讓咱們看看規則樹如何幫助咱們減小工做。

結構劃分

樣式上下文可分割成多個結構。這些結構體包含了特定類別(如 border 或 color)的樣式信息。結構中的屬性都是繼承的或非繼承的。繼承屬性若是未由元素定義,則繼承自其父代。非繼承屬性(也稱爲「重置」屬性)若是未進行定義,則使用默認值。

規則樹經過緩存整個結構(包含計算出的端值)爲咱們提供幫助。這一想法假定底層節點沒有提供結構的定義,則可以使用上層節點中的緩存結構。

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

在計算某個特定元素的樣式上下文時,咱們首先計算規則樹中的對應路徑,或者使用現有的路徑。而後咱們沿此路徑應用規則,在新的樣式上下文中填充結構。咱們從路徑中擁有最高優先級的底層節點(一般也是最特殊的選擇器)開始,並向上遍歷規則樹,直到結構填充完畢。若是該規則節點對於此結構沒有任何規範,那麼咱們能夠實現更好的優化:尋找路徑更上層的節點,找到後指定完整的規範並指向相關節點便可。這是最好的優化方法,由於整個結構都能共享。這能夠減小端值的計算量並節約內存。

若是咱們找到了部分定義,就會向上遍歷規則樹,直到結構填充完畢。

若是咱們找不到結構的任何定義,那麼假如該結構是「繼承」類型,咱們會在上下文樹中指向父代的結構,這樣也能夠共享結構。若是是 reset 類型的結構,則會使用默認值。

若是最特殊的節點確實添加了值,那麼咱們須要另外進行一些計算,以便將這些值轉化成實際值。而後咱們將結果緩存在樹節點中,供子代使用。

若是某個元素與其同級元素都指向同一個樹節點,那麼它們就能夠共享整個樣式上下文

讓咱們來看一個例子,假設咱們有以下 HTML 代碼:

this is abig errorthis is also avery  big  errorerror

another error

還有以下規則:

div{margin:5px;color:black}

.err{color:red}

.big{margin-top:3px}

div span{margin-bottom:4px}

#div1{color:blue}

#div2{color:green}

爲了簡便起見,咱們只須要填充兩個結構:color 結構和 margin 結構。color 結構只包含一個成員(即「color」),而 margin 結構包含四條邊。

造成的規則樹以下圖所示(節點的標記方式爲「節點名 : 指向的規則序號」):


圖:規則樹

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


圖:上下文樹

假設咱們解析 HTML 時遇到了第二個

標記,咱們須要爲此節點建立樣式上下文,並填充其樣式結構。

通過規則匹配,咱們發現該

的匹配規則是第 一、2 和 6 條。這意味着規則樹中已有一條路徑可供咱們的元素使用,咱們只須要再爲其添加一個節點以匹配第 6 條規則(規則樹中的 F 節點)。

咱們將建立樣式上下文並將其放入上下文樹中。新的樣式上下文將指向規則樹中的 F 節點。

如今咱們須要填充樣式結構。首先要填充的是 margin 結構。因爲最後的規則節點 (F) 並無添加到 margin 結構,咱們須要上溯規則樹,直至找到在先前節點插入中計算過的緩存結構,而後使用該結構。咱們會在指定 margin 規則的最上層節點(即 B 節點)上找到該結構。

咱們已經有了 color 結構的定義,所以不能使用緩存的結構。因爲 color 有一個屬性,咱們無需上溯規則樹以填充其餘屬性。咱們將計算端值(將字符串轉化爲 RGB 等)並在此節點上緩存通過計算的結構。

第二個 元素處理起來更加簡單。咱們將匹配規則,最終發現它和以前的 span 同樣指向規則 G。因爲咱們找到了指向同一節點的同級,就能夠共享整個樣式上下文了,只需指向以前 span 的上下文便可。

對於包含了繼承自父代的規則的結構,緩存是在上下文樹中進行的(事實上 color 屬性是繼承的,可是 Firefox 將其視爲 reset 屬性,並緩存到規則樹上)。

例如,若是咱們在某個段落中添加 font 規則:

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

那麼,該段落元素做爲上下文樹中的 div 的子代,就會共享與其父代相同的 font 結構(前提是該段落沒有指定 font 規則)。

在 WebKit 中沒有規則樹,所以會對匹配的聲明遍歷 4 次。首先應用非重要高優先級的屬性(因爲做爲其餘屬性的依據而應首先應用的屬性,例如 display),接着是高優先級重要規則,而後是普通優先級非重要規則,最後是普通優先級重要規則。這意味着屢次出現的屬性會根據正確的層疊順序進行解析。最後出現的最終生效。

所以歸納來講,共享樣式對象(整個對象或者對象中的部分結構)能夠解決問題1和問題3。Firefox 規則樹還有助於按照正確的順序應用屬性。

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

樣式規則有一些來源:

外部樣式表或樣式元素中的 CSS 規則

p{color:blue}

inline 樣式屬性及相似內容

HTML 可視化屬性(映射到相關的樣式規則)

後兩種很容易和元素進行匹配,由於元素擁有樣式屬性,並且 HTML 屬性可使用元素做爲鍵值進行映射。

咱們以前在第 2 個問題中提到過,CSS 規則匹配可能比較棘手。爲了解決這一難題,能夠對 CSS 規則進行一些處理,以便訪問。

樣式表解析完畢後,系統會根據選擇器將 CSS 規則添加到某個哈希表中。這些哈希表的選擇器各不相同,包括 ID、類名稱、標記名稱等,還有一種通用哈希表,適合不屬於上述類別的規則。若是選擇器是 ID,規則就會添加到 ID 表中;若是選擇器是類,規則就會添加到類表中,依此類推。

這種處理能夠大大簡化規則匹配。咱們無需查看每一條聲明,只要從哈希表中提取元素的相關規則便可。這種優化方法可排除掉 95% 以上規則,所以在匹配過程當中根本就不用考慮這些規則了 (4.1)。

咱們以以下的樣式規則爲例:

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

第一條規則將插入類表,第二條將插入 ID 表,而第三條將插入標記表。

對於下面的 HTML 代碼段:

an error occurredthis is a message

咱們首先會爲 p 元素尋找匹配的規則。類表中有一個「error」鍵,在下面能夠找到「p.error」的規則。div 元素在 ID 表(鍵爲 ID)和標記表中有相關的規則。剩下的工做就是找出哪些根據鍵提取的規則是真正匹配的了。

例如,若是 div 的對應規則以下:

table div{margin:5px}

這條規則仍然會從標記表中提取出來,由於鍵是最右邊的選擇器,但這條規則並不匹配咱們的 div 元素,由於 div 沒有 table 祖先。

WebKit 和 Firefox 都進行了這一處理。

以正確的層疊順序應用規則

樣式對象具備與每一個可視化屬性一一對應的屬性(均爲 CSS 屬性但更爲通用)。若是某個屬性未由任何匹配規則所定義,那麼部分屬性就可由父代元素樣式對象繼承。其餘屬性具備默認值。

若是定義不止一個,就會出現問題,須要經過層疊順序來解決。

樣式表層疊順序

某個樣式屬性的聲明可能會出如今多個樣式表中,也可能在同一個樣式表中出現屢次。這意味着應用規則的順序極爲重要。這稱爲「層疊」順序。根據 CSS2 規範,層疊的順序爲(優先級從低到高):

瀏覽器聲明

用戶普通聲明

做者普通聲明

做者重要聲明

用戶重要聲明

瀏覽器聲明是重要程度最低的,而用戶只有將該聲明標記爲「重要」才能夠替換網頁做者的聲明。一樣順序的聲明會根據特異性進行排序,而後再是其指定順序。HTML 可視化屬性會轉換成匹配的 CSS 聲明。它們被視爲低優先級的網頁做者規則。

特異性

選擇器的特異性由CSS2 規範定義以下:

若是聲明來自於「style」屬性,而不是帶有選擇器的規則,則記爲 1,不然記爲 0 (= a)

記爲選擇器中 ID 屬性的個數 (= b)

記爲選擇器中其餘屬性和僞類的個數 (= c)

記爲選擇器中元素名稱和僞元素的個數 (= d)

將四個數字按 a-b-c-d 這樣鏈接起來(位於大數進制的數字系統中),構成特異性。

您使用的進製取決於上述類別中的最高計數。

例如,若是 a=14,您可使用十六進制。若是 a=17,那麼您須要使用十七進制;固然不太可能出現這種狀況,除非是存在以下的選擇器:html body div div p ...(在選擇器中出現了 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 經過替換「>」運算符來實現排序:

staticbooloperator>(CSSRuleData&r1,CSSRuleData&r2){intspec1=r1.selector()->specificity();intspec2=r2.selector()->specificity();return(spec1==spec2):r1.position()>r2.position():spec1>spec2;}

漸進式處理

WebKit 使用一個標記來表示是否全部的頂級樣式表(包括 @imports)均已加載完畢。若是在附加過程當中還沒有徹底加載樣式,則使用佔位符,並在文檔中進行標註,等樣式表加載完畢後再從新計算。

佈局

呈現器在建立完成並添加到呈現樹時,並不包含位置和大小信息。計算這些值的過程稱爲佈局或重排。

HTML 採用基於流的佈局模型,這意味着大多數狀況下只要一次遍歷就能計算出幾何信息。處於流中靠後位置元素一般不會影響靠前位置元素的幾何特徵,所以佈局能夠按從左至右、從上至下的順序遍歷文檔。可是也有例外狀況,好比 HTML 表格的計算就須要不止一次的遍歷 (3.5)。

座標系是相對於根框架而創建的,使用的是上座標和左座標。

佈局是一個遞歸的過程。它從根呈現器(對應於 HTML 文檔的元素)開始,而後遞歸遍歷部分或全部的框架層次結構,爲每個須要計算的呈現器計算幾何信息。

根呈現器的位置左邊是 0,0,其尺寸爲視口(也就是瀏覽器窗口的可見區域)。

全部的呈現器都有一個「layout」或者「reflow」方法,每個呈現器都會調用其須要進行佈局的子代的 layout 方法。

Dirty 位系統

爲避免對全部細小更改都進行總體佈局,瀏覽器採用了一種「dirty 位」系統。若是某個呈現器發生了更改,或者將自身及其子代標註爲「dirty」,則須要進行佈局。

有兩種標記:「dirty」和「children are dirty」。「children are dirty」表示儘管呈現器自身沒有變化,但它至少有一個子代須要佈局。

全局佈局和增量佈局

全局佈局是指觸發了整個呈現樹範圍的佈局,觸發緣由可能包括:

影響全部呈現器的全局樣式更改,例如字體大小更改。

屏幕大小調整。

佈局能夠採用增量方式,也就是隻對 dirty 呈現器進行佈局(這樣可能存在須要進行額外佈局的弊端)。

當呈現器爲 dirty 時,會異步觸發增量佈局。例如,當來自網絡的額外內容添加到 DOM 樹以後,新的呈現器附加到了呈現樹中。


圖:增量佈局 - 只有 dirty 呈現器及其子代進行佈局 (3.6)。

異步佈局和同步佈局

增量佈局是異步執行的。Firefox 將增量佈局的「reflow 命令」加入隊列,而調度程序會觸發這些命令的批量執行。WebKit 也有用於執行增量佈局的計時器:對呈現樹進行遍歷,並對 dirty 呈現器進行佈局。

請求樣式信息(例如「offsetHeight」)的腳本可同步觸發增量佈局。

全局佈局每每是同步觸發的。

有時,當初始佈局完成以後,若是一些屬性(如滾動位置)發生變化,佈局就會做爲回調而觸發。

優化

若是佈局是由「大小調整」或呈現器的位置(而非大小)改變而觸發的,那麼能夠從緩存中獲取呈現器的大小,而無需從新計算。

在某些狀況下,只有一個子樹進行了修改,所以無需從根節點開始佈局。這適用於在本地進行更改而不影響周圍元素的狀況,例如在文本字段中插入文本(不然每次鍵盤輸入都將觸發從根節點開始的佈局)。

佈局處理

佈局一般具備如下模式:

父呈現器肯定本身的寬度。

父呈現器依次處理子呈現器,而且:

放置子呈現器(設置 x,y 座標)。

若是有必要,調用子呈現器的佈局(若是子呈現器是 dirty 的,或者這是全局佈局,或出於其餘某些緣由),這會計算子呈現器的高度。

父呈現器根據子呈現器的累加高度以及邊距和補白的高度來設置自身高度,此值也可供父呈現器的父呈現器使用。

將其 dirty 位設置爲 false。

Firefox 使用「state」對象 (nsHTMLReflowState) 做爲佈局的參數(稱爲「reflow」),這其中包括了父呈現器的寬度。

Firefox 佈局的輸出爲「metrics」對象 (nsHTMLReflowMetrics),其包含計算得出的呈現器高度。

寬度計算

呈現器寬度是根據容器塊的寬度、呈現器樣式中的「width」屬性以及邊距和邊框計算得出的。

例如如下 div 的寬度:

將由 WebKit 計算以下(BenderBox 類,calcWidth 方法):

容器的寬度取容器的 availableWidth 和 0 中的較大值。availableWidth 在本例中至關於 contentWidth,計算公式以下:

clientWidth()-paddingLeft()-paddingRight()

clientWidth 和 clientHeight 表示一個對象的內部(除去邊框和滾動條)。

元素的寬度是「width」樣式屬性。它會根據容器寬度的百分比計算得出一個絕對值。

而後加上水平方向的邊框和補白。

如今計算得出的是「preferred width」。而後須要計算最小寬度和最大寬度。

若是首選寬度大於最大寬度,那麼應使用最大寬度。若是首選寬度小於最小寬度(最小的不可破開單位),那麼應使用最小寬度。

這些值會緩存起來,以用於須要佈局而寬度不變的狀況。

換行

若是呈現器在佈局過程當中須要換行,會當即中止佈局,並告知其父代須要換行。父代會建立額外的呈現器,並對其調用佈局。

繪製

在繪製階段,系統會遍歷呈現樹,並調用呈現器的「paint」方法,將呈現器的內容顯示在屏幕上。繪製工做是使用用戶界面基礎組件完成的。

全局繪製和增量繪製

和佈局同樣,繪製也分爲全局(繪製整個呈現樹)和增量兩種。在增量繪製中,部分呈現器發生了更改,可是不會影響整個樹。更改後的呈現器將其在屏幕上對應的矩形區域設爲無效,這致使 OS 將其視爲一塊「dirty 區域」,並生成「paint」事件。OS 會很巧妙地將多個區域合併成一個。在 Chrome 瀏覽器中,狀況要更復雜一些,由於 Chrome 瀏覽器的呈現器不在主進程上。Chrome 瀏覽器會在某種程度上模擬 OS 的行爲。展現層會偵聽這些事件,並將消息委託給呈現根節點。而後遍歷呈現樹,直到找到相關的呈現器,該呈現器會從新繪製本身(一般也包括其子代)。

繪製順序

CSS2 規範定義了繪製流程的順序。繪製的順序其實就是元素進入堆棧樣式上下文的順序。這些堆棧會從後往前繪製,所以這樣的順序會影響繪製。塊呈現器的堆棧順序以下:

背景顏色

背景圖片

邊框

子代

輪廓

Firefox 顯示列表

Firefox 遍歷整個呈現樹,爲繪製的矩形創建一個顯示列表。列表中按照正確的繪製順序(先是呈現器的背景,而後是邊框等等)包含了與矩形相關的呈現器。這樣等到從新繪製的時候,只需遍歷一次呈現樹,而不用屢次遍歷(繪製全部背景,而後繪製全部圖片,再繪製全部邊框等等)。

Firefox 對此過程進行了優化,也就是不添加隱藏的元素,例如被不透明元素徹底遮擋住的元素。

WebKit 矩形存儲

在從新繪製以前,WebKit 會將原來的矩形另存爲一張位圖,而後只繪製新舊矩形之間的差別部分。

動態變化

在發生變化時,瀏覽器會盡量作出最小的響應。所以,元素的顏色改變後,只會對該元素進行重繪。元素的位置改變後,只會對該元素及其子元素(可能還有同級元素)進行佈局和重繪。添加 DOM 節點後,會對該節點進行佈局和重繪。一些重大變化(例如增大「html」元素的字體)會致使緩存無效,使得整個呈現樹都會進行從新佈局和繪製。

呈現引擎的線程

呈現引擎採用了單線程。幾乎全部操做(除了網絡操做)都是在單線程中進行的。在 Firefox 和 Safari 中,該線程就是瀏覽器的主線程。而在 Chrome 瀏覽器中,該線程是標籤進程的主線程。

網絡操做可由多個並行線程執行。並行鏈接數是有限的(一般爲 2 至 6 個,以 Firefox 3 爲例是 6 個)。

事件循環

瀏覽器的主線程是事件循環。它是一個無限循環,永遠處於接受處理狀態,並等待事件(如佈局和繪製事件)發生,並進行處理。這是 Firefox 中關於主事件循環的代碼:

while(!mExiting)NS_ProcessNextEvent(thread);

CSS2 可視化模型

畫布

根據CSS2 規範,「畫布」這一術語是指「用來呈現格式化結構的空間」,也就是供瀏覽器繪製內容的區域。畫布的空間尺寸大小是無限的,可是瀏覽器會根據視口的尺寸選擇一個初始寬度。

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

CSS 框模型

CSS 框模型描述的是針對文檔樹中的元素而生成,並根據可視化格式模型進行佈局的矩形框。

每一個框都有一個內容區域(例如文本、圖片等),還有可選的周圍補白、邊框和邊距區域。


圖:CSS2 框模型

每個節點都會生成 0..n 個這樣的框。

全部元素都有一個「display」屬性,決定了它們所對應生成的框類型。示例:

block-generates a block box.inline-generates oneormoreinlineboxes.none-noboxisgenerated.

默認值是 inline,可是瀏覽器樣式表設置了其餘默認值。例如,「div」元素的 display 屬性默認值是 block。

您能夠在這裏找到默認樣式表示例:www.w3.org/TR/CSS2/sample.html

定位方案

有三種定位方案:

普通:根據對象在文檔中的位置進行定位,也就是說對象在呈現樹中的位置和它在 DOM 樹中的位置類似,並根據其框類型和尺寸進行佈局。

浮動:對象先按照普通流進行佈局,而後儘量地向左或向右移動。

絕對:對象在呈現樹中的位置和它在 DOM 樹中的位置不一樣。

定位方案是由「position」屬性和「float」屬性設置的。

若是值是 static 和 relative,就是普通流

若是值是 absolute 和 fixed,就是絕對定位

static 定位無需定義位置,而是使用默認定位。對於其餘方案,網頁做者須要指定位置:top、bottom、left、right。

框的佈局方式是由如下因素決定的:

框類型

框尺寸

定位方案

外部信息,例如圖片大小和屏幕大小

框類型

block 框:造成一個 block,在瀏覽器窗口中擁有其本身的矩形區域。


圖:block 框

inline 框:沒有本身的 block,可是位於容器 block 內。


圖:inline 框

block 採用的是一個接一個的垂直格式,而 inline 採用的是水平格式。


圖:block 和 inline 格式

inline 框放置在行中或「行框」中。這些行至少和最高的框同樣高,還能夠更高,當框根據「底線」對齊時,這意味着元素的底部須要根據其餘框中非底部的位置對齊。若是容器的寬度不夠,inline 元素就會分爲多行放置。在段落中常常發生這種狀況。


圖:行

定位

相對

相對定位:先按照普通方式定位,而後根據所需偏移量進行移動。


圖:相對定位

浮動

浮動框會移動到行的左邊或右邊。有趣的特徵在於,其餘框會浮動在它的周圍。下面這段 HTML 代碼:

Lorem ipsum dolor sit amet, consectetuer...

顯示效果以下:


圖:浮動

絕對定位和固定定位

這種佈局是準肯定義的,與普通流無關。元素不參與普通流。尺寸是相對於容器而言的。在固定定位中,容器就是可視區域。


圖:固定定位

請注意,即便在文檔滾動時,固定框也不會移動。

分層展現

這是由 z-index CSS 屬性指定的。它表明了框的第三個維度,也就是沿「z 軸」方向的位置。

這些框分散到多個堆棧(稱爲堆棧上下文)中。在每個堆棧中,會首先繪製後面的元素,而後在頂部繪製前面的元素,以便更靠近用戶。若是出現重疊,新繪製的元素就會覆蓋以前的元素。

堆棧是按照 z-index 屬性進行排序的。具備「z-index」屬性的框造成了本地堆棧。視口具備外部堆棧。

示例:

div{position:absolute;left:2in;top:2in;}

結果以下:

圖:固定定位

雖然紅色 div 在標記中的位置比綠色 div 靠前(按理應該在常規流程中優先繪製),可是 z-index 屬性的優先級更高,所以它移動到了根框所保持的堆棧中更靠前的位置。

長按掃碼,關注個人公衆號額


參考資料

瀏覽器架構

Grosskurth, Alan.A Reference Architecture for Web Browsers (pdf)

Gupta, Vineet.How Browsers Work - Part 1 - Architecture

解析

Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools(即「Dragon book」), Addison-Wesley, 1986

Rick Jelliffe.The Bold and the Beautiful: two new drafts for HTML 5.

Firefox

L. David Baron,Faster HTML and CSS: Layout Engine Internals for Web Developers.

L. David Baron,Faster HTML and CSS: Layout Engine Internals for Web Developers(Google 技術訪談視頻)

L. David Baron,Mozilla's Layout Engine

L. David Baron,Mozilla Style System Documentation

Chris Waterson,Notes on HTML Reflow

Chris Waterson,Gecko Overview

Alexander Larsson,The life of an HTML HTTP request

WebKit

David Hyatt,Implementing CSS(第一部分)

David Hyatt,An Overview of WebCore

David Hyatt,WebCore Rendering

David Hyatt,The FOUC Problem

W3C 規範

HTML 4.01 規範

W3C HTML5 規範

層疊樣式表第 2 級第 1 次修改 (CSS 2.1) 規範

瀏覽器構建說明

Firefox.https://developer.mozilla.org/en/Build_Documentation

WebKit.http://webkit.org/building/build.html