瀏覽器介紹
如今,瀏覽器格局基本上是五分天下,分別是:IE、Firefox、Safari、Chrome、Opera,而瀏覽器引擎就更加集中了,主要是四大巨頭:IE的瀏覽器排版引擎Trident,目前隨IE10發布了Trident6.0;Mozilla的排版引擎Gecko,今年4月2號發布了Gecko21預覽版,穩定版本為Gecko20;Google Chrome和Apple Safari使用的是WebKit引擎,它已經成為市場佔用率最高的排版引擎;另外還有Opera的Presto引擎,不過在今年的2月13號,Opera Software宣布未來產品將以Webkti和V8為主,逐步放棄基於Presto引擎產品。也就是說,如今的瀏覽器排版引擎基本上是三分天下。
據國外媒體報道,Google當地時間周三宣布將放棄渲染引擎WebKit,開發名為Blink的渲染引擎。Opera也宣布將採用Blink。主要的原因是,與其他WebKit 瀏覽器相比,Chromium 使用的是一個不同的多過程架構體系。在過去多年為了支援多個架構體系,WebKit 和 Chromium 社區面臨的問題越來越複雜,延緩了創新的整體速度。
瀏覽器的功能
瀏覽器的主要功能是將使用者選擇的web資源用網頁的形式顯示出來,這些資源套件括:文字、映像、等其他資訊,而web資源都是通過URI(uniform resource identifier)來定位。大部分網頁都是HTML格式,網頁的樣式都是使用CSS來定義,相關的規範由W3C組織來進行制定和維護。但是,這些年來,瀏覽器開發商紛紛為了搶佔市場及其擴充強化自己的產品,對相關的規範並不完全遵守,這也導致了如今web領域嚴重的相容性問題。
瀏覽器內部結構
①、使用者介面(UI) - 包括功能表列、工具列、地址欄、後退/前進按鈕、書籤目錄等,也就是能看到的除了顯示頁面的主視窗之外的部分;
②、瀏覽器引擎(Rendering engine) - 也被稱為瀏覽器核心、渲染引起,主要負責取得頁面內容、整理資訊(應用CSS)、計算頁面的顯示方式,然後會輸出到顯示器或者印表機;
③、JS解譯器 - 也可以稱為JS核心,主要負責處理javascript指令碼程式,一般都會附帶在瀏覽器之中,例如chrome的V8引擎;
④、網路部分 - 主要用於網路調用,例如:HTTP請求,其介面與平台無關,並為所有的平台提供底層實現;
⑤、UI後端 - 用於繪製基本的視窗組件,比如組合框和視窗等。
⑥、資料存放區 - 儲存類似於cookie、storage等資料部分,HTML5新增了web database技術,一種完整的輕量級用戶端儲存技術。
值得注意的是,IE只為每個瀏覽器視窗啟用單獨的進程,而chrome瀏覽器卻為每個tab頁面單獨啟用進程,也就是每個tab都有獨立的渲染引擎執行個體。
渲染引擎
渲染並不是電腦領域的術語,而是來自於繪畫領域,繪畫時經常使用水墨或者淡色塗抹畫布,以烘托物象,分出陰陽向背,主要用來加強美術效果,明代 - 楊慎《藝林伐山·浮渲梳頭》:“畫家以墨飾美人鬢髮謂之渲染。”在這裡就不扯遠了,在電腦繪圖中, 是指軟體從模型產生映像的過程,包括幾何、視點、紋理、和照明資訊等。
瀏覽器的渲染引擎也主要是幹這勾當的,負責顯示HTML文檔、XML文檔以及圖片、動畫等資訊,還可以借組瀏覽器的一些差距顯示其他資料類型,例如:flash、pdf等。
渲染的主要步驟
渲染引擎首先通過網路部分擷取request響應的文檔內容,一般以8K為單位分塊進行。解析HTML用以構建DOM樹結構 -> 構建Render樹 -> Render樹的布局 -> 繪製Render樹。
渲染引擎解釋HTML文檔,首先將標籤轉換成DOM樹中的DOM node;接著,解釋對應的CSS樣式檔案資訊,而這些樣式資訊以及HTML中可見的指令(<b></b>etc)將被用來構建另外的Render樹。這棵樹主要由一些包含顏色和寬高等屬性群組成的矩形組成,它們將依次按順序顯示在螢幕上。
待Renter樹構建完成,將執行版面配置階段,確定每個節點在螢幕上對應的座標,及其覆蓋和迴流情況等,然後瀏覽器會遍曆Renter樹,並使用UI後端層繪製每個節點。這整個過程都是逐步進行的,HTML內容顯示一部分,將構建和布局一部分Renter樹,就先顯示出來。也就是解釋完一部分內容就顯示一部分出來,而不是等著HTML內容解釋完成才進行構建Renter樹,同時,還有可能通過網路層進行下載其餘的內容。
解析與DOM樹構建
解析就是將一個文檔轉換成指定的結構,解析的結構通常是表達文檔結構的節點樹。例如:num1 + num2 * num3,可以解析成如下的DOM樹。
文法
解析基於文檔依據的文法規則 —— 文檔的語言和格式,每種可以被解析的格式必須是具有由詞法及其文法規則群組成的特定文法。
詞法
解析可以分為兩個過程:文法分析和詞法分析。
詞法分析就是將輸入分解成符號,也可以理解為將字元序列轉換為單詞序列的過程,進行詞法分析的程式或者函數叫做詞法分析器(Lexical analyzer) - Lexer,一般以函數的形式存在,供文法分析器調用。這裡說的單詞序列也就是字串序列,是構成原始碼的最小單位。從輸入字元流中產生單詞的過程叫做單詞話,在這個過程中,詞法分析器會對單詞進行分類,但通常不關心單詞之間的關係,那屬於文法分析的範疇,不會出現越俎代庖的情形。
文法分析(Syntactic analysis)是根據某種給定的形式文法對由單詞序列構成的文本進行分析並確定其文法結構的一個過程。文法分析器主要作用就是進行語法檢查、並且構建由單片語成的資料結構(一般是文法分析樹、抽象文法樹等層次化的結構)。
基本的流程:document -> Lexical analyzer -> Syntactic analysis -> Parse Tree。解析過程是迭代的,解析器從詞法分析器處獲得一個新的符號,並試著用這個符號匹配一條文法規則,若匹配則添加到解析樹種,否則先暫時儲存該符號,並繼續匹配下一個符號;若最終都沒有找到可以匹配的規則,解析器就會拋出一個異常,一般是語法錯誤。
轉換
解析樹並不是最終的結果,還需要進行格式轉換。例如編譯的流程:Source Code -> Parsing ->parse Tree -> Translation ->Machine Code。
CSS解析
CSS屬於上下文無關文法,可以按照前面所述的解析器解析,CSS規範定義了CSS詞法及文法文法。
CSS解析產生器將每個CSS檔案解析城樣式表對象,每個對象包含CSS規則,CSS規則對象包含選取器和聲明對象,以及其他一些符合CSS文法的對象。如案例所示。
指令碼處理和CSS順序
web的模式是同步的,解析到一個script標籤立即解析執行指令碼,並阻塞文檔的解析直到指令碼執行完成。如果指令碼是外引的,則網路必須先請求到這個資源 —— 這個過程也是同步的,阻塞文檔的解析直到資源被請求到。不過,開發人員可以將指令碼標示為defer,以使其不阻塞文檔的正常解析,並在文檔的解析完成後執行。Html5增加了標記指令碼為非同步選項,以使指令碼的解析執行可以使用另一個線程。
預解析
Chrome和Firefox進行瞭解析的最佳化,當執行指令碼時,另一個線程解析剩餘的文檔,並載入後面需要通過網路載入的資源。這種方式可以使用資源並行載入,從而使整體速度加快。但是,預解析並不改變DOM樹,它只是解析外部資源的引用,例如:外部指令碼、樣式表、圖片等。
理論上,樣式表的解析並不改變DOM樹,但是指令碼可能在文檔的解析過程中請求樣式資訊,假如樣式資訊還沒有載入和解析,指令碼將得到錯誤的資訊,顯然這會導致很多問題。Chrome只在當指令碼試圖訪問可能被未載入的樣式表所影響的特定樣式屬性時才會阻塞指令碼。
渲染樹的構建
當DOM樹構建完成,又可是構建另一棵 —— 渲染樹。渲染樹由元素顯示序列中的可見元素組成,是文檔的可視化表示,構建這棵樹是為了以正確的順序繪製文檔內容。
渲染樹和DOM樹的關係
渲染對象和DOM元素是相對應,但這種關係關係不是一對一的,不可見的DOM元素不會被插入渲染樹之中。例如:display為none,但是visibility為hidden的元素卻將出現在渲染樹種,因為它需要渲染,只是不顯示在螢幕上而已。
另外還有一些DOM元素對應幾個可見的對象,例如:select元素,它是個相對複雜結構的元素,由三個渲染對象組成:顯示地區、下拉式清單、按鈕。同樣,當文本輸入因為寬度不夠而折行時,新增的行將作為額外的渲染元素被添加。
還有一些渲染對象和應當對應的DOM節點不在兩棵樹相同的位置,例如:浮動和絕對位置的元素在正常流之外,渲染樹上標識出真實的結構,並用一個佔位結構標識出它們原來的位置。
處理html和body標籤將構建渲染樹的根,這個根渲染對象對應被css規範為containing block的元素,包含著其他所有塊元素的頂級塊元素。它的大小就是viewport,Firefox稱之為viewPortFrame,WebKit稱之為RenderView,也就是文檔對應的渲染對象。
樣式計算
建立渲染樹需要計算出每個渲染對象的可視屬性,這可以通過計算每個元素的樣式屬性而得到。樣式包括各種來源的樣式表,包括行內樣式表、外部樣式表等。計算樣式資料量比較大,不進行最佳化的話,尋找每個元素匹配的規則會導致效能問題,為每個元素尋找匹配的規則都需要遍曆整個規則表,這個過程有很大的工作量。
我們來看一下瀏覽器如何處理這些問題:
共用樣式資料(Sharing style data)
WebkKit節點引用樣式對象(渲染樣式),某些情況下,這些對象可以被節點間共用,這些節點需要是兄弟或是表兄弟節點,並且:
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節點指向其相關的樣式。
樣式上下文包含最終值,這些值是通過以正確順序應用所有匹配的規則,並將它們由邏輯值轉換為具體的值,例如,如果邏輯值為螢幕的百分比,則通過計算將其轉化為絕對單位。樣式樹的使用確實很巧妙,它使得在節點中共用的這些值不需要被多次計算,同時也節省了儲存空間。
所有匹配的規則都儲存在規則樹中,一條路徑中的底層節點擁有最高的優先順序,這棵樹包含了所找到的所有規則匹配的路徑(譯註:可以取巧理解為每條路徑對 應一個節點,路徑上包含了該節點所匹配的所有規則)。規則樹並不是一開始就為所有節點進行計算,而是在某個節點需要計算樣式時,才進行相應的計算並將計算 後的路徑添加到樹中。
我們將樹上的路徑看成辭典中的單詞,假如已經計算出了如下的規則樹:
假如需要為內容樹中的另一個節點匹配規則,現在知道匹配的規則(以正確的順序)為B-E-I,因為我們已經計算出了路徑A-B-E-I-L,所以樹上已經存在了這條路徑,剩下的工作就很少了。
繪製(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
動態變化
瀏覽器總是試著以最小的動作響應一個變化,所以一個元素顏色的變化將只導致該元素的重繪,元素位置的變化將大致元素的布局和重繪,添加一個Dom節 點,也會大致這個元素的布局和重繪。一些主要的變化,比如增加html元素的字型大小,將會導致緩衝失效,從而引起整數的布局和重
渲染引擎的線程
渲染引擎是單線程的,除了網路操作以外,幾乎所有的事情都在單一的線程中處理,在Firefox和Safari中,這是瀏覽器的主線程,Chrome中這是tab的主線程。
網路操作由幾個並行線程執行,並行串連的個數是受限的(通常是2-6個)。
事件迴圈
瀏覽器主線程是一個事件迴圈,它被設計為無限迴圈以保持執行過程的可用,等待事件(例如layout和paint事件)並執行它們。
參考文章:
①、http://www.html5rocks.com/en/tutorials/internals/howbrowserswork/
②、http://en.wikipedia.org/wiki/Web_browser_engine