瀏覽器是如何工做的

瀏覽器能夠被認爲是使用最普遍的軟件,本文將介紹瀏覽器的工做原理,咱們將看到從你在地址欄輸入google.cn 到你看到google 主頁過程當中都發生了什麼。接下來將基於一些開源瀏覽器的例子---firfox 、chrom及safari。

瀏覽器的主要功能
瀏覽器主要功能是將用戶選擇得web 資源呈現出來,它須要從服務器請求資源,並將其顯示在瀏覽器窗口中,資源的格式一般是HTML,也包括PDF、Image 及其餘格式。用戶用URI(統一資源標識符) 來指定所請求資源的位置。
html 和css 規範中規定了瀏覽器解釋 html 文檔的方式,由W3C 組織對這些規範進行維護,W3C 是負責指定web 標準的組織。

瀏覽器常見的用戶界面元素包括:

用來輸入URI的地址欄css

· 前進、後退按鈕html

· 書籤選項html5

· 用於刷新及暫停當前加載文檔的刷新、暫停按鈕node

· 用於到達主頁的主頁按鈕web


瀏覽器的主要組件包括:算法

1. 用戶界面 -- 包括地址欄、後腿/前進按鈕、書籤目錄等,也就是你所看到的除了用來顯示你鎖清秋頁面的主窗口以外的其餘部分chrome

2. 瀏覽器引擎- 用來查詢及操做渲染引擎的接口後端

3. 渲染引擎 - 用來顯示請求的年日用 (例如若是請求內容爲html 它負責解析html 及css,並將解析後的結果顯示出來)瀏覽器

4.網絡 - 用來完成網絡調用,例如http請求,他具備平臺無關的接口,能夠在不一樣平臺上工做緩存

5. UI後端 - 用來繪製相似組合選擇框及對話框等基本組件,具備不特定於某個平臺的通用接口,底層使用操做系統的用戶接口

6.JS解釋器-  用來解釋執行js 代碼

7. 數據存儲- 屬於持久層,瀏覽器須要在硬盤中保存類型cookie 的各類數據,html5定義了web database 技術,這是一種輕量級完整的客戶端存儲技術。


1、渲染引擎

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

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

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


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

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

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

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

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

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

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


2、解析

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

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

解析樹,是有DOM 元素及屬性節點組成的。DOM是文檔對象模型的縮寫,它是html 文檔對象標識,做爲html 元素的外部接口供js 等調用。

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

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

將會被轉換爲DOM樹:

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

解析算法

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

緣由是:

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

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

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

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


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>」同樣處理。


樹的建立過程

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

<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」模式,當接收到文件結束符時,整個解析過程結束。


html樹的構建過程

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

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

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


瀏覽器容錯 Browsers error tolerance

詳見 http://www.aiuxian.com/article/p-1832762.html



三 、腳本解析 Parsing scripts

web的模式是同步的,開發者但願解析到一個script 標籤時當即解析執行腳本,並阻塞文檔的解析直到腳本執行完,若是腳本是外因的,則網絡必須先請求到這個資源 --- 這個過程也是同步的,會阻塞文檔的解析直到資源被請求到。


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

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

渲染樹的構造 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屬性決定某個節點建立何種類型的渲染對象。


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;

}

元素的類型也須要考慮,例如,表單控件和表格帶有特殊的框架。


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

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

四: 佈局
當渲染對象被建立並添加到樹中,它們比你更沒有位置和大小,計算這些值的過程稱爲layout 和reflow
html 是基於流的佈局模型, 意味着發部分時間,能夠以單一的途徑進行幾何計算。流中靠後的元素並不會影響前面元素的幾何特性。因此佈局能夠在文檔中從右到左、自上而下進行。也存在一些例外,好比html table
座標系統相對於根frame ,使用top 和left 座標
佈局是一個遞歸的過程,由根渲染對象開始,它對應html 文檔元素,佈局繼續遞歸的過程一些或全部的frame 層級,爲我每一個須要幾何信息的渲染對象進行計算。
根渲染對象的位置是0,1 ,它的大小是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樹後,新的渲染對象會添加到渲染樹中。


增量layout
增量layout 的過程是異步的,firefox爲整理layout 生成了reflow 隊列,以及一個調度執行這些處理命令。webkit 也有一個計時器用來執行增量layout 遍歷樹,爲dirty狀態的渲染對象從新佈局
另外,當腳本請求樣式信息時,例如「offsetHeight」,會同步的觸發增量佈局。
全局的layout 通常都是同步觸發
有些時候,layout 會被做爲一個初始layout 以後的回調,好比滑動條的滑動

優化
當一個layout由於resize 或是渲染位置改版(並非大小改變)而觸發時,渲染對象的大小將會從緩存中讀取,而不會從新計算。
通常狀況下,若是隻有子樹發生改變,則layout 並不從跟開始,這種狀況發生在,變化發生在元素自身但並不影響其餘周圍元素,例如,將文本插入文本域(不然每次都將觸發從根開始的重排)

layout過程

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

1. 父渲染對象決定它本身的寬度

2. 父渲染對象讀取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定義了繪製過程的順序。這個就是元素壓入堆棧的順序,這個順序影響着繪製,堆棧從後向前進行繪製。

一個塊渲染對象的堆棧順序是:
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);


CSS盒模型


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

block-生成塊狀box

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

none-不生成box

默認的是inline,但瀏覽器樣式表設置了其餘默認值,例如,div元素默認爲block。


定位策略 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的堆棧中更靠前。











相關文章
相關標籤/搜索