[轉]瀏覽器的工做原理:新式網絡瀏覽器幕後揭祕

源文件:http://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/css

寫的很好,雖然長仍是值得一看。涉及到解析、css計算等等算法有一些具體幫助理解的實例,挺複雜的,建議花點時間閱讀原文。html

主要結構

· user interface- 包括地址欄、前進/後退按鈕、書籤菜單等。除了瀏覽器主窗口示的您求的頁面外,其餘顯示的各個部分都屬於用戶界面。html5

  browser engine -在用戶界面和呈現引擎之間傳送指令。web

  rendering engine -負責顯求的。若是請求的內容是 HTML,它就負責解析HTML 和 CSS 內容,並將解析後的內容顯示在屏幕上。算法

  networking -用於網絡調用,好比HTTP 請求。其接口與平臺無關,併爲全部平臺提供底層實現。數據庫

  UI backend -用於繪製基本的窗口小部件,好比組合框和窗口。其公開了與平臺無關的通用接口,而在底使用操做系統的用戶界面方法。後端

  JavaScript interpreter。用於解析和執行JavaScript 代碼。瀏覽器

  data Persistence。這是持久層。瀏覽器須要在硬盤上保存各類數據,例如Cookie。新的 HTML 規範(HTML5) 定義了「網絡數據庫」,這是一個完整(可是輕便)的瀏覽器內數據庫。網絡

 


值得注意的是,和大多數瀏覽器不一樣,Chrome瀏覽器的每一個標籤頁都分別對應一個呈現引擎實例。每一個標籤頁都是一個獨立的進程。併發

Rendering engine

本文所討論的瀏覽器(Firefox、Chrome 瀏覽器和 Safari)是基於兩種呈現引擎構建的。Firefox 使用的是 Gecko,這是 Mozilla 公司「自制」的呈現引擎。而 Safari 和 Chrome 瀏覽器使用的都是 WebKit。

Rendering Engine主流程

呈現引擎一開始會從網絡層獲取請求文檔的內容,內容的大小通常限制在 8000 個塊之內。

而後進行以下所示的基本流程:

圖:呈現引擎的基本流程。

呈現引擎將開始解析 HTML 文檔,並將各標記逐個轉化成「內容樹」上的 DOM 節點。同時也會解析外部 CSS 文件以及樣式元素中的樣式數據。HTML 中這些帶有視覺指令的樣式信息將用於建立另外一個樹結構:呈現樹

呈現樹包含多個帶有視覺屬性(如顏色和尺寸)的矩形。這些矩形的排列順序就是它們將在屏幕上顯示的順序。

呈現樹構建完畢以後,進入「佈局」處理階段,也就是爲每一個節點分配一個應出如今屏幕上的確切座標。下一個階段是繪製 - 呈現引擎會遍歷呈現樹,由用戶界面後端層將每一個節點繪製出來。

須要着重指出的是,這是一個漸進的過程。爲達到更好的用戶體驗,呈現引擎會力求儘快將內容顯示在屏幕上。它沒必要等到整個 HTML 文檔解析完畢以後,就會開始構建呈現樹和設置佈局。在不斷接收和處理來自網絡的其他內容的同時,呈現引擎會將部份內容解析並顯示出來。

主流程示例

圖:WebKit 主流程 圖:Mozilla 的 Gecko 呈現引擎主流程 ( 3.6)

從圖 3 和圖 4 能夠看出,雖然 WebKit 和 Gecko 使用的術語略有不一樣,但總體流程是基本相同的。

HTML Parser

HTML 沒法用常規的自上而下或自下而上的解析器進行解析。

緣由在於:

  1. 語言的寬容本質。
  2. 瀏覽器從來對一些常見的無效 HTML 用法採起包容態度。
  3. 解析過程須要不斷地反覆。源內容在解析過程當中一般不會改變,可是在 HTML 中,腳本標記若是包含 document.write,就會添加額外的標記,這樣解析過程實際上就更改了輸入內容。

因爲不能使用常規的解析技術,瀏覽器就建立了自定義的解析器來解析 HTML。

HTML5 規範詳細地描述瞭解析算法。此算法由兩個階段組成:標記化和樹構建

標記化算法


經過一個簡單的示例來幫助你們理解其原理。

基本示例 - 將下面的 HTML 代碼標記化:

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

初始狀態是數據狀態。遇到字符 < 時,狀態更改成「標記打開狀態」。接收一個 a-z字符會建立「起始標記」,狀態更改成「標記名稱狀態」。這個狀態會一直保持到接收 >字符。在此期間接收的每一個字符都會附加到新的標記名稱上。在本例中,咱們建立的標記是 html 標記。

遇到 > 標記時,會發送當前的標記,狀態改回「數據狀態」<body> 標記也會進行一樣的處理。目前 html 和 body 標記均已發出(發給樹構造器)。如今咱們回到「數據狀態」。接收到Hello world 中的 H 字符時,將建立併發送字符標記,直到接收 </body> 中的 <。咱們將爲 Hello world 中的每一個字符都發送一個字符標記。

如今咱們回到「標記打開狀態」。接收下一個輸入字符 / 時,會建立 end tag token並改成「標記名稱狀態」。咱們會再次保持這個狀態,直到接收 >。而後將發送新的標記,並回到「數據狀態」</html> 輸入也會進行一樣的處理

樹構建算法

在建立解析器的同時,也會建立 Document 對象。

處理腳本和樣式表的順序

腳本

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

預解析

WebKit 和 Firefox 都進行了這項優化。在執行腳本時,其餘線程會解析文檔的其他部分,找出並加載須要經過網絡加載的其餘資源。經過這種方式,資源能夠在並行鏈接上加載,從而提升整體速度。請注意,預解析器不會修改 DOM 樹,而是將這項工做交由主解析器處理;預解析器只會解析外部資源(例如外部腳本、樣式表和圖片)的引用。

樣式表

另外一方面,樣式表有着不一樣的模型。理論上來講,應用樣式表不會更改 DOM 樹,所以彷佛沒有必要等待樣式表並中止文檔解析。但這涉及到一個問題,就是腳本在文檔解析階段會請求樣式信息。若是當時尚未加載和解析樣式,腳本就會得到錯誤的回覆,這樣顯然會產生不少問題。這看上去是一個非典型案例,但事實上很是廣泛。Firefox 在樣式表加載和解析的過程當中,會禁止全部腳本。而對於 WebKit 而言,僅當腳本嘗試訪問的樣式屬性可能受還沒有加載的樣式表影響時,它纔會禁止該腳本。

呈現樹和 DOM 樹的關係
呈現器是和 DOM 元素相對應的,但並不是一一對應。非可視化的 DOM 元素不會插入呈現樹中,例如「head」元素。 若是元素的 display 屬性值爲「none」,那麼也不會顯示在呈現樹中(可是 visibility 屬性值爲「hidden」的元素仍會顯示)

有一些 DOM 元素對應多個可視化對象。它們每每是具備複雜結構的元素,沒法用單一的矩形來描述。例如,「select」元素有 3 個呈現器:一個用於顯示區域,一個用於下拉列表框,還有一個用於按鈕。若是因爲寬度不夠,文本沒法在一行中顯示而分爲多行,那麼新的行也會做爲新的呈現器而添加。
有一些呈現對象對應於 DOM 節點,但在樹中所在的位置與 DOM 節點不一樣。 浮動定位和絕對定位的元素就是這樣,它們處於正常的流程以外,放置在樹中的其餘地方,並映射到真正的框架,而放在原位的是佔位框架


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

呈現引擎的線程

呈現引擎採用 了單線程 。幾乎全部操做(除了網絡操做)都是在單線程中進行的。在 Firefox 和 Safari 中,該線程就是瀏覽器的主線程。而在 Chrome 瀏覽器中,該線程是 標籤進程 的主線程。 
網絡操做可由多個並行線程執行。並行鏈接數是有限的(一般爲 2 至 6 個,以 Firefox 3 爲例是 6 個)。

事件循環

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