瀏覽器的工作原理:網絡瀏覽器幕後揭祕

序言

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

在 IE 佔據 90% 市場份額的年代,我們除了把瀏覽器當成一個“黑箱”,什麼也做不了。但是現在,開放源代碼的瀏覽器擁有了過半的市場份額,因此,是時候來揭開神祕的面紗,一探網絡瀏覽器的內幕了。

塔利在她的網站上公佈了自己的研究成果,但是我們覺得它值得讓更多的人來了解,所以我們在此重新整理並公佈。

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

簡介

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

網絡的模型是同步的。網頁作者希望解析器遇到 標記時立即解析並執行腳本。文檔的解析將停止,直到腳本執行完畢。如果腳本是外部的,那麼解析過程會停止,直到從網絡同步抓取資源完成後再繼續。此模型已經使用了多年,也在 HTML4 和 HTML5 規範中進行了指定。作者也可以將腳本標註爲“defer”,這樣它就不會停止文檔解析,而是等到解析結束才執行。Html5 增加了一個選項,可將腳本標記爲異步,以便由其他線程解析和執行。

我們要討論的瀏覽器


圖:呈現樹及其對應的 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 時遇到了第二個

標記,我們需要爲此節點創建樣式上下文,並填充其樣式結構。

經過規則匹配,我們發現該

的匹配規則是第 1、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 屬性的優先級更高,因此它移動到了根框所保持的堆棧中更靠前的位置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章