瀏覽器原理

前端必讀:瀏覽器內(nèi)部工作原理

作者: Tali Garsiel??發(fā)布時(shí)間: 2012-02-09 14:32??閱讀: 144873 次??推薦: 168原文鏈接[收藏]

目錄

一、介紹

二、渲染引擎

三翁涤、解析與DOM樹構(gòu)建

四劫侧、渲染樹構(gòu)建

五、布局

六厌漂、繪制

七萨醒、動(dòng)態(tài)變化

八、渲染引擎的線程

九苇倡、CSS2可視模型

英文原文:How Browsers Work: Behind the Scenes of Modern Web Browsers

一富纸、介紹

瀏覽器可以被認(rèn)為是使用最廣泛的軟件,本文將介紹瀏覽器的工作原理雏节,我們將看到胜嗓,從你在地址欄輸入google.com到你看到google主頁(yè)過(guò)程中都發(fā)生了什么。

將討論的瀏覽器

今天钩乍,有五種主流瀏覽器——IE辞州、Firefox、Safari寥粹、Chrome及Opera变过。

本文將基于一些開源瀏覽器的例子——Firefox、Chrome及Safari涝涤,Safari是部分開源的媚狰。

根據(jù)W3C(World Wide Web Consortium萬(wàn)維網(wǎng)聯(lián)盟)的瀏覽器統(tǒng)計(jì)數(shù)據(jù),當(dāng)前(2011年5月)阔拳,F(xiàn)irefox崭孤、Safari及Chrome的市場(chǎng)占有率綜合已接近60%。(原文為2009年10月,數(shù)據(jù)沒(méi)有太大變化)因此辨宠,可以說(shuō)開源瀏覽器已經(jīng)占據(jù)了瀏覽器市場(chǎng)的半壁江山遗锣。

瀏覽器的主要功能

瀏覽器的主要功能是將用戶選擇的web資源呈現(xiàn)出來(lái),它需要從服務(wù)器請(qǐng)求資源嗤形,并將其顯示在瀏覽器窗口中精偿,資源的格式通常是HTML,也包括PDF赋兵、image及其他格式笔咽。用戶用URI(Uniform Resource Identifier統(tǒng)一資源標(biāo)識(shí)符)來(lái)指定所請(qǐng)求資源的位置,在網(wǎng)絡(luò)一章有更多討論霹期。

HTML和CSS規(guī)范中規(guī)定了瀏覽器解釋html文檔的方式叶组,由W3C組織對(duì)這些規(guī)范進(jìn)行維護(hù),W3C是負(fù)責(zé)制定web標(biāo)準(zhǔn)的組織经伙。

HTML規(guī)范的最新版本是HTML4(http://www.w3.org/TR/html401/)扶叉,HTML5還在制定中(譯注:兩年前),最新的CSS規(guī)范版本是2(http://www.w3.org/TR/CSS2)帕膜,CSS3也還正在制定中(譯注:同樣兩年前)枣氧。

這些年來(lái),瀏覽器廠商紛紛開發(fā)自己的擴(kuò)展垮刹,對(duì)規(guī)范的遵循并不完善达吞,這為web開發(fā)者帶來(lái)了嚴(yán)重的兼容性問(wèn)題。

但是荒典,瀏覽器的用戶界面則差不多酪劫,常見(jiàn)的用戶界面元素包括:

用來(lái)輸入U(xiǎn)RI的地址欄

前進(jìn)、后退按鈕

書簽選項(xiàng)

用于刷新及暫停當(dāng)前加載文檔的刷新寺董、暫停按鈕

用于到達(dá)主頁(yè)的主頁(yè)按鈕

奇怪的是覆糟,并沒(méi)有哪個(gè)正式公布的規(guī)范對(duì)用戶界面做出規(guī)定,這些是多年來(lái)各瀏覽器廠商之間相互模仿和不斷改進(jìn)的結(jié)果遮咖。

HTML5并沒(méi)有規(guī)定瀏覽器必須具有的UI元素滩字,但列出了一些常用元素,包括地址欄御吞、狀態(tài)欄及工具欄麦箍。還有一些瀏覽器有自己專有的功能,比如Firefox的下載管理陶珠。更多相關(guān)內(nèi)容將在后面討論用戶界面時(shí)介紹挟裂。

瀏覽器的主要構(gòu)成(High Level Structure)

瀏覽器的主要組件包括:

1. 用戶界面 - 包括地址欄、后退/前進(jìn)按鈕揍诽、書簽?zāi)夸浀染魅兀簿褪悄闼吹降某擞脕?lái)顯示你所請(qǐng)求頁(yè)面的主窗口之外的其他部分栗竖。

2. 瀏覽器引擎 - 用來(lái)查詢及操作渲染引擎的接口。

3. 渲染引擎 - 用來(lái)顯示請(qǐng)求的內(nèi)容渠啤,例如划滋,如果請(qǐng)求內(nèi)容為html,它負(fù)責(zé)解析html及css埃篓,并將解析后的結(jié)果顯示出來(lái)。

4. 網(wǎng)絡(luò) - 用來(lái)完成網(wǎng)絡(luò)調(diào)用根资,例如http請(qǐng)求架专,它具有平臺(tái)無(wú)關(guān)的接口,可以在不同平臺(tái)上工作玄帕。

5. UI后端 - 用來(lái)繪制類似組合選擇框及對(duì)話框等基本組件部脚,具有不特定于某個(gè)平臺(tái)的通用接口,底層使用操作系統(tǒng)的用戶接口裤纹。

6. JS解釋器 - 用來(lái)解釋執(zhí)行JS代碼委刘。

7. 數(shù)據(jù)存儲(chǔ) - 屬于持久層,瀏覽器需要在硬盤中保存類似cookie的各種數(shù)據(jù)鹰椒,HTML5定義了web database技術(shù)锡移,這是一種輕量級(jí)完整的客戶端存儲(chǔ)技術(shù)

圖1:瀏覽器主要組件

需要注意的是,不同于大部分瀏覽器漆际,Chrome為每個(gè)Tab分配了各自的渲染引擎實(shí)例淆珊,每個(gè)Tab就是一個(gè)獨(dú)立的進(jìn)程。

對(duì)于構(gòu)成瀏覽器的這些組件奸汇,后面會(huì)逐一詳細(xì)討論施符。

二、渲染引擎(The rendering engine)

渲染引擎的職責(zé)就是渲染擂找,即在瀏覽器窗口中顯示所請(qǐng)求的內(nèi)容戳吝。

默認(rèn)情況下,渲染引擎可以顯示html贯涎、xml文檔及圖片听哭,它也可以借助插件(一種瀏覽器擴(kuò)展)顯示其他類型數(shù)據(jù),例如使用PDF閱讀器插件柬采,可以顯示PDF格式欢唾,將由專門一章講解插件及擴(kuò)展,這里只討論渲染引擎最主要的用途——顯示應(yīng)用了CSS之后的html及圖片粉捻。

渲染引擎簡(jiǎn)介

本文所討論的瀏覽器——Firefox礁遣、Chrome和Safari是基于兩種渲染引擎構(gòu)建的,F(xiàn)irefox使用Geoko——Mozilla自主研發(fā)的渲染引擎肩刃,Safari和Chrome都使用webkit祟霍。

Webkit是一款開源渲染引擎杏头,它本來(lái)是為L(zhǎng)inux平臺(tái)研發(fā)的,后來(lái)由Apple移植到Mac及Windows上沸呐,相關(guān)內(nèi)容請(qǐng)參考http://webkit.org醇王。

渲染主流程(The main flow)

渲染引擎首先通過(guò)網(wǎng)絡(luò)獲得所請(qǐng)求文檔的內(nèi)容,通常以8K分塊的方式完成崭添。

下面是渲染引擎在取得內(nèi)容之后的基本流程:

解析html以構(gòu)建dom樹 -> 構(gòu)建render樹 -> 布局render樹 -> 繪制render樹

圖2:渲染引擎基本流程

渲染引擎開始解析html寓娩,并將標(biāo)簽轉(zhuǎn)化為內(nèi)容樹中的dom節(jié)點(diǎn)。接著呼渣,它解析外部CSS文件及style標(biāo)簽中的樣式信息棘伴。這些樣式信息以及html中的可見(jiàn)性指令將被用來(lái)構(gòu)建另一棵樹——render樹。

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

Render樹構(gòu)建好了之后,將會(huì)執(zhí)行布局過(guò)程蓝角,它將確定每個(gè)節(jié)點(diǎn)在屏幕上的確切坐標(biāo)。再下一步就是繪制使鹅,即遍歷render樹揪阶,并使用UI后端層繪制每個(gè)節(jié)點(diǎn)。

值得注意的是患朱,這個(gè)過(guò)程是逐步完成的遣钳,為了更好的用戶體驗(yàn),渲染引擎將會(huì)盡可能早的將內(nèi)容呈現(xiàn)到屏幕上麦乞,并不會(huì)等到所有的html都解析完成之后再去構(gòu)建和布局render樹蕴茴。它是解析完一部分內(nèi)容就顯示一部分內(nèi)容,同時(shí)姐直,可能還在通過(guò)網(wǎng)絡(luò)下載其余內(nèi)容倦淀。

圖3:webkit主流程

圖4:Mozilla的Geoko渲染引擎主流程

從圖3和4中可以看出,盡管webkit和Gecko使用的術(shù)語(yǔ)稍有不同声畏,他們的主要流程基本相同撞叽。Gecko稱可見(jiàn)的格式化元素組成的樹為frame樹,每個(gè)元素都是一個(gè)frame插龄,webkit則使用render樹這個(gè)名詞來(lái)命名由渲染對(duì)象組成的樹愿棋。Webkit中元素的定位稱為布局,而Gecko中稱為回流均牢。Webkit稱利用dom節(jié)點(diǎn)及樣式信息去構(gòu)建render樹的過(guò)程為attachment糠雨,Gecko在html和dom樹之間附加了一層,這層稱為內(nèi)容接收器徘跪,相當(dāng)制造dom元素的工廠甘邀。下面將討論流程中的各個(gè)階段琅攘。

三、解析與DOM樹構(gòu)建(Parsing and DOM tree construction)

解析(Parsing-general)

既然解析是渲染引擎中一個(gè)非常重要的過(guò)程松邪,我們將稍微深入的研究它坞琴。首先簡(jiǎn)要介紹一下解析。

解析一個(gè)文檔即將其轉(zhuǎn)換為具有一定意義的結(jié)構(gòu)——編碼可以理解和使用的東西逗抑。解析的結(jié)果通常是表達(dá)文檔結(jié)構(gòu)的節(jié)點(diǎn)樹剧辐,稱為解析樹或語(yǔ)法樹。

例如邮府,解析“2+3-1”這個(gè)表達(dá)式浙于,可能返回這樣一棵樹。

圖5:數(shù)學(xué)表達(dá)式樹節(jié)點(diǎn)

文法(Grammars)

解析基于文檔依據(jù)的語(yǔ)法規(guī)則——文檔的語(yǔ)言或格式挟纱。每種可被解析的格式必須具有由詞匯及語(yǔ)法規(guī)則組成的特定的文法,稱為上下文無(wú)關(guān)文法腐宋。人類語(yǔ)言不具有這一特性紊服,因此不能被一般的解析技術(shù)所解析。

解析器-詞法分析器(Parser-Lexer combination)

解析可以分為兩個(gè)子過(guò)程——語(yǔ)法分析及詞法分析

詞法分析就是將輸入分解為符號(hào)胸竞,符號(hào)是語(yǔ)言的詞匯表——基本有效單元的集合欺嗤。對(duì)于人類語(yǔ)言來(lái)說(shuō)咖刃,它相當(dāng)于我們字典中出現(xiàn)的所有單詞篱瞎。

語(yǔ)法分析指對(duì)語(yǔ)言應(yīng)用語(yǔ)法規(guī)則拂盯。

解析器一般將工作分配給兩個(gè)組件——詞法分析器(有時(shí)也叫分詞器)負(fù)責(zé)將輸入分解為合法的符號(hào)熟丸,解析器則根據(jù)語(yǔ)言的語(yǔ)法規(guī)則分析文檔結(jié)構(gòu)揽祥,從而構(gòu)建解析樹舟舒,詞法分析器知道怎么跳過(guò)空白和換行之類的無(wú)關(guān)字符抒抬。

圖6:從源文檔到解析樹

解析過(guò)程是迭代的置侍,解析器從詞法分析器處取到一個(gè)新的符號(hào)马篮,并試著用這個(gè)符號(hào)匹配一條語(yǔ)法規(guī)則沾乘,如果匹配了一條規(guī)則,這個(gè)符號(hào)對(duì)應(yīng)的節(jié)點(diǎn)將被添加到解析樹上浑测,然后解析器請(qǐng)求另一個(gè)符號(hào)翅阵。如果沒(méi)有匹配到規(guī)則,解析器將在內(nèi)部保存該符號(hào)迁央,并從詞法分析器取下一個(gè)符號(hào)掷匠,直到所有內(nèi)部保存的符號(hào)能夠匹配一項(xiàng)語(yǔ)法規(guī)則。如果最終沒(méi)有找到匹配的規(guī)則岖圈,解析器將拋出一個(gè)異常讹语,這意味著文檔無(wú)效或是包含語(yǔ)法錯(cuò)誤。

轉(zhuǎn)換(Translation)

很多時(shí)候蜂科,解析樹并不是最終結(jié)果募强。解析一般在轉(zhuǎn)換中使用——將輸入文檔轉(zhuǎn)換為另一種格式株灸。編譯就是個(gè)例子,編譯器在將一段源碼編譯為機(jī)器碼的時(shí)候擎值,先將源碼解析為解析樹慌烧,然后將該樹轉(zhuǎn)換為一個(gè)機(jī)器碼文檔。

圖7:編譯流程

解析實(shí)例Parsing example

圖5中鸠儿,我們從一個(gè)數(shù)學(xué)表達(dá)式構(gòu)建了一個(gè)解析樹屹蚊,這里定義一個(gè)簡(jiǎn)單的數(shù)學(xué)語(yǔ)言來(lái)看下解析過(guò)程。

詞匯表:我們的語(yǔ)言包括整數(shù)进每、加號(hào)及減號(hào)汹粤。

語(yǔ)法:

1. 該語(yǔ)言的語(yǔ)法基本單元包括表達(dá)式、term及操作符

2. 該語(yǔ)言可以包括多個(gè)表達(dá)式

3. 一個(gè)表達(dá)式定義為兩個(gè)term通過(guò)一個(gè)操作符連接

4. 操作符可以是加號(hào)或減號(hào)

5. term可以是一個(gè)整數(shù)或一個(gè)表達(dá)式

現(xiàn)在來(lái)分析一下“2+3-1”這個(gè)輸入

第一個(gè)匹配規(guī)則的子字符串是“2”田晚,根據(jù)規(guī)則5嘱兼,它是一個(gè)term,第二個(gè)匹配的是“2+3”贤徒,它符合第2條規(guī)則——一個(gè)操作符連接兩個(gè)term芹壕,下一次匹配發(fā)生在輸入的結(jié)束處〗幽危“2+3-1”是一個(gè)表達(dá)式踢涌,因?yàn)槲覀円呀?jīng)知道“2+3”是一個(gè)term,所以我們有了一個(gè)term緊跟著一個(gè)操作符及另一個(gè)term序宦≌霰冢“2++”將不會(huì)匹配任何規(guī)則,因此是一個(gè)無(wú)效輸入互捌。

詞匯表及語(yǔ)法的定義

詞匯表通常利用正則表達(dá)式來(lái)定義潘明。

例如上面的語(yǔ)言可以定義為:

INTEGER:0|[1-9][0-9]*

PLUS:+

MINUS:-

正如看到的,這里用正則表達(dá)式定義整數(shù)秕噪。

語(yǔ)法通常用BNF格式定義钉疫,我們的語(yǔ)言可以定義為:

expression := term operation term

operation := PLUS | MINUS

term := INTEGER | expression

如果一個(gè)語(yǔ)言的文法是上下文無(wú)關(guān)的,則它可以用正則解析器來(lái)解析巢价。對(duì)上下文無(wú)關(guān)文法的一個(gè)直觀的定義是牲阁,該文法可以用BNF來(lái)完整的表達(dá)∪蓝悖可查看http://en.wikipedia.org/wiki/Context-free_grammar城菊。

解析器類型(Types of parsers)

有兩種基本的解析器——自頂向下解析及自底向上解析。比較直觀的解釋是碉克,自頂向下解析凌唬,查看語(yǔ)法的最高層結(jié)構(gòu)并試著匹配其中一個(gè);自底向上解析則從輸入開始漏麦,逐步將其轉(zhuǎn)換為語(yǔ)法規(guī)則客税,從底層規(guī)則開始直到匹配高層規(guī)則况褪。

來(lái)看一下這兩種解析器如何解析上面的例子:

自頂向下解析器從最高層規(guī)則開始——它先識(shí)別出“2+3“,將其視為一個(gè)表達(dá)式更耻,然后識(shí)別出”2+3-1“為一個(gè)表達(dá)式(識(shí)別表達(dá)式的過(guò)程中匹配了其他規(guī)則测垛,但出發(fā)點(diǎn)是最高層規(guī)則)。

自底向上解析會(huì)掃描輸入直到匹配了一條規(guī)則秧均,然后用該規(guī)則取代匹配的輸入食侮,直到解析完所有輸入。部分匹配的表達(dá)式被放置在解析堆棧中目胡。

StackInput

2 + 3 – 1

term+ 3 - 1

term operation3 – 1

expression- 1

expression operation1

expression

自底向上解析器稱為shift reduce解析器锯七,因?yàn)檩斎胂蛴乙苿?dòng)(想象一個(gè)指針首先指向輸入開始處,并向右移動(dòng))誉己,并逐漸簡(jiǎn)化為語(yǔ)法規(guī)則眉尸。

自動(dòng)化解析(Generating parsers automatically)

解析器生成器這個(gè)工具可以自動(dòng)生成解析器,只需要指定語(yǔ)言的文法——詞匯表及語(yǔ)法規(guī)則巨双,它就可以生成一個(gè)解析器噪猾。創(chuàng)建一個(gè)解析器需要對(duì)解析有深入的理解,而且手動(dòng)的創(chuàng)建一個(gè)由較好性能的解析器并不容易炉峰,所以解析生成器很有用。Webkit使用兩個(gè)知名的解析生成器——用于創(chuàng)建語(yǔ)法分析器的Flex及創(chuàng)建解析器的Bison(你可能接觸過(guò)Lex和Yacc)脉执。Flex的輸入是一個(gè)包含了符號(hào)定義的正則表達(dá)式疼阔,Bison的輸入是用BNF格式表示的語(yǔ)法規(guī)則。

HTML解析器(HTML Parser)

HTML解析器的工作是將html標(biāo)識(shí)解析為解析樹半夷。

HTML文法定義(The HTML grammar definition)

W3C組織制定規(guī)范定義了HTML的詞匯表和語(yǔ)法婆廊。

非上下文無(wú)關(guān)文法(Not a context free grammar)

正如在解析簡(jiǎn)介中提到的,上下文無(wú)關(guān)文法的語(yǔ)法可以用類似BNF的格式來(lái)定義巫橄。

不幸的是淘邻,所有的傳統(tǒng)解析方式都不適用于html(當(dāng)然我提出它們并不只是因?yàn)楹猛妫鼈儗⒂脕?lái)解析css和js)湘换,html不能簡(jiǎn)單的用解析所需的上下文無(wú)關(guān)文法來(lái)定義宾舅。

Html有一個(gè)正式的格式定義——DTD(Document Type Definition文檔類型定義)——但它并不是上下文無(wú)關(guān)文法,html更接近于xml彩倚,現(xiàn)在有很多可用的xml解析器筹我,html有個(gè)xml的變體——xhtml,它們間的不同在于帆离,html更寬容蔬蕊,它允許忽略一些特定標(biāo)簽,有時(shí)可以省略開始或結(jié)束標(biāo)簽哥谷“逗唬總的來(lái)說(shuō)麻献,它是一種soft語(yǔ)法,不像xml呆板猜扮、固執(zhí)勉吻。

顯然,這個(gè)看起來(lái)很小的差異卻帶來(lái)了很大的不同破镰。一方面餐曼,這是html流行的原因——它的寬容使web開發(fā)人員的工作更加輕松,但另一方面鲜漩,這也使很難去寫一個(gè)格式化的文法源譬。所以,html的解析并不簡(jiǎn)單孕似,它既不能用傳統(tǒng)的解析器解析踩娘,也不能用xml解析器解析。

HTML DTD

Html適用DTD格式進(jìn)行定義喉祭,這一格式是用于定義SGML家族的語(yǔ)言养渴,包括了對(duì)所有允許元素及它們的屬性和層次關(guān)系的定義。正如前面提到的泛烙,html DTD并沒(méi)有生成一種上下文無(wú)關(guān)文法理卑。

DTD有一些變種,標(biāo)準(zhǔn)模式只遵守規(guī)范蔽氨,而其他模式則包含了對(duì)瀏覽器過(guò)去所使用標(biāo)簽的支持藐唠,這么做是為了兼容以前內(nèi)容。最新的標(biāo)準(zhǔn)DTD在http://www.w3.org/TR/html4/strict.dtd

DOM

輸出的樹鹉究,也就是解析樹宇立,是由DOM元素及屬性節(jié)點(diǎn)組成的。DOM是文檔對(duì)象模型的縮寫自赔,它是html文檔的對(duì)象表示妈嘹,作為html元素的外部接口供js等調(diào)用。

樹的根是“document”對(duì)象绍妨。

DOM和標(biāo)簽基本是一一對(duì)應(yīng)的關(guān)系润脸,例如,如下的標(biāo)簽:

Hello DOM

將會(huì)被轉(zhuǎn)換為下面的DOM樹:

圖8:示例標(biāo)簽對(duì)應(yīng)的DOM樹

和html一樣他去,DOM的規(guī)范也是由W3C組織制定的津函。訪問(wèn)http://www.w3.org/DOM/DOMTR,這是使用文檔的一般規(guī)范孤页。一個(gè)模型描述一種特定的html元素尔苦,可以在http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.htm查看html定義。

這里所謂的樹包含了DOM節(jié)點(diǎn)是說(shuō)樹是由實(shí)現(xiàn)了DOM接口的元素構(gòu)建而成的,瀏覽器使用已被瀏覽器內(nèi)部使用的其他屬性的具體實(shí)現(xiàn)允坚。

解析算法(The parsing algorithm)

正如前面章節(jié)中討論的魂那,hmtl不能被一般的自頂向下或自底向上的解析器所解析。

原因是:

1. 這門語(yǔ)言本身的寬容特性

2. 瀏覽器對(duì)一些常見(jiàn)的非法html有容錯(cuò)機(jī)制

3. 解析過(guò)程是往復(fù)的稠项,通常源碼不會(huì)在解析過(guò)程中發(fā)生改變涯雅,但在html中,腳本標(biāo)簽包含的“document.write”可能添加標(biāo)簽展运,這說(shuō)明在解析過(guò)程中實(shí)際上修改了輸入活逆。

不能使用正則解析技術(shù),瀏覽器為html定制了專屬的解析器拗胜。

Html5規(guī)范中描述了這個(gè)解析算法蔗候,算法包括兩個(gè)階段——符號(hào)化及構(gòu)建樹。

符號(hào)化是詞法分析的過(guò)程埂软,將輸入解析為符號(hào)锈遥,html的符號(hào)包括開始標(biāo)簽、結(jié)束標(biāo)簽勘畔、屬性名及屬性值所灸。

符號(hào)識(shí)別器識(shí)別出符號(hào)后,將其傳遞給樹構(gòu)建器炫七,并讀取下一個(gè)字符爬立,以識(shí)別下一個(gè)符號(hào),這樣直到處理完所有輸入万哪。

圖9:HTML解析流程

符號(hào)識(shí)別算法(The tokenization algorithm)

算法輸出html符號(hào)侠驯,該算法用狀態(tài)機(jī)表示。每次讀取輸入流中的一個(gè)或多個(gè)字符壤圃,并根據(jù)這些字符轉(zhuǎn)移到下一個(gè)狀態(tài)陵霉,當(dāng)前的符號(hào)狀態(tài)及構(gòu)建樹狀態(tài)共同影響結(jié)果琅轧,這意味著伍绳,讀取同樣的字符,可能因?yàn)楫?dāng)前狀態(tài)的不同乍桂,得到不同的結(jié)果以進(jìn)入下一個(gè)正確的狀態(tài)冲杀。

這個(gè)算法很復(fù)雜,這里用一個(gè)簡(jiǎn)單的例子來(lái)解釋這個(gè)原理睹酌。

基本示例——符號(hào)化下面的html:

Hello world

初始狀態(tài)為“Data State”权谁,當(dāng)遇到“<”字符,狀態(tài)變?yōu)椤癟ag open state”憋沿,讀取一個(gè)a-z的字符將產(chǎn)生一個(gè)開始標(biāo)簽符號(hào)旺芽,狀態(tài)相應(yīng)變?yōu)椤癟ag name state”,一直保持這個(gè)狀態(tài)直到讀取到“>”,每個(gè)字符都附加到這個(gè)符號(hào)名上采章,例子中創(chuàng)建的是一個(gè)html符號(hào)运嗜。

當(dāng)讀取到“>”,當(dāng)前的符號(hào)就完成了悯舟,此時(shí)担租,狀態(tài)回到“Data state”,“”重復(fù)這一處理過(guò)程抵怎。到這里奋救,html和body標(biāo)簽都識(shí)別出來(lái)了。現(xiàn)在反惕,回到“Data state”尝艘,讀取“Hello world”中的字符“H”將創(chuàng)建并識(shí)別出一個(gè)字符符號(hào),這里會(huì)為“Hello world”中的每個(gè)字符生成一個(gè)字符符號(hào)承璃。

這樣直到遇到“”中的“<”±#現(xiàn)在,又回到了“Tag open state”盔粹,讀取下一個(gè)字符“/”將創(chuàng)建一個(gè)閉合標(biāo)簽符號(hào)隘梨,并且狀態(tài)轉(zhuǎn)移到“Tag name state”,還是保持這一狀態(tài)舷嗡,直到遇到“>”轴猎。然后,產(chǎn)生一個(gè)新的標(biāo)簽符號(hào)并回到“Data state”进萄。后面的“”將和“”一樣處理捻脖。

圖10:符號(hào)化示例輸入

樹的構(gòu)建算法(Tree construction algorithm)

在樹的構(gòu)建階段,將修改以Document為根的DOM樹中鼠,將元素附加到樹上可婶。每個(gè)由符號(hào)識(shí)別器識(shí)別生成的節(jié)點(diǎn)將會(huì)被樹構(gòu)造器進(jìn)行處理,規(guī)范中定義了每個(gè)符號(hào)相對(duì)應(yīng)的Dom元素援雇,對(duì)應(yīng)的Dom元素將會(huì)被創(chuàng)建矛渴。這些元素除了會(huì)被添加到Dom樹上,還將被添加到開放元素堆棧中惫搏。這個(gè)堆棧用來(lái)糾正嵌套的未匹配和未閉合標(biāo)簽具温,這個(gè)算法也是用狀態(tài)機(jī)來(lái)描述,所有的狀態(tài)采用插入模式筐赔。

來(lái)看一下示例中樹的創(chuàng)建過(guò)程:

Hello world

構(gòu)建樹這一階段的輸入是符號(hào)識(shí)別階段生成的符號(hào)序列铣猩。

首先是“initial mode”,接收到html符號(hào)后將轉(zhuǎn)換為“before html”模式茴丰,在這個(gè)模式中對(duì)這個(gè)符號(hào)進(jìn)行再處理达皿。此時(shí)天吓,創(chuàng)建了一個(gè)HTMLHtmlElement元素,并將其附加到根Document對(duì)象上峦椰。

狀態(tài)此時(shí)變?yōu)椤癰efore head”失仁,接收到body符號(hào)時(shí),即使這里沒(méi)有head符號(hào)们何,也將自動(dòng)創(chuàng)建一個(gè)HTMLHeadElement元素并附加到樹上悲敷。

現(xiàn)在彬呻,轉(zhuǎn)到“in head”模式哑蔫,然后是“after head”案铺。到這里,body符號(hào)會(huì)被再次處理鹦蠕,將創(chuàng)建一個(gè)HTMLBodyElement并插入到樹中冒签,同時(shí),轉(zhuǎn)移到“in body”模式钟病。

然后萧恕,接收到字符串“Hello world”的字符符號(hào),第一個(gè)字符將導(dǎo)致創(chuàng)建并插入一個(gè)text節(jié)點(diǎn)肠阱,其他字符將附加到該節(jié)點(diǎn)票唆。

接收到body結(jié)束符號(hào)時(shí),轉(zhuǎn)移到“after body”模式屹徘,接著接收到html結(jié)束符號(hào)走趋,這個(gè)符號(hào)意味著轉(zhuǎn)移到了“after after body”模式,當(dāng)接收到文件結(jié)束符時(shí)噪伊,整個(gè)解析過(guò)程結(jié)束簿煌。

圖11:示例html樹的構(gòu)建過(guò)程

解析結(jié)束時(shí)的處理(Action when the parsing is finished)

在這個(gè)階段,瀏覽器將文檔標(biāo)記為可交互的鉴吹,并開始解析處于延時(shí)模式中的腳本——這些腳本在文檔解析后執(zhí)行姨伟。

文檔狀態(tài)將被設(shè)置為完成,同時(shí)觸發(fā)一個(gè)load事件豆励。

Html5規(guī)范中有符號(hào)化及構(gòu)建樹的完整算法(http://www.w3.org/TR/html5/syntax.html#html-parser)夺荒。

瀏覽器容錯(cuò)(Browsers error tolerance)

你從來(lái)不會(huì)在一個(gè)html頁(yè)面上看到“無(wú)效語(yǔ)法”這樣的錯(cuò)誤,瀏覽器修復(fù)了無(wú)效內(nèi)容并繼續(xù)工作肆糕。

以下面這段html為例:

Really lousy HTML

這段html違反了很多規(guī)則(mytag不是合法的標(biāo)簽般堆,p及div錯(cuò)誤的嵌套等等)在孝,但是瀏覽器仍然可以沒(méi)有任何怨言的繼續(xù)顯示诚啃,它在解析的過(guò)程中修復(fù)了html作者的錯(cuò)誤。

瀏覽器都具有錯(cuò)誤處理的能力私沮,但是始赎,另人驚訝的是,這并不是html最新規(guī)范的內(nèi)容,就像書簽及前進(jìn)后退按鈕一樣造垛,它只是瀏覽器長(zhǎng)期發(fā)展的結(jié)果魔招。一些比較知名的非法html結(jié)構(gòu),在許多站點(diǎn)中出現(xiàn)過(guò)五辽,瀏覽器都試著以一種和其他瀏覽器一致的方式去修復(fù)办斑。

Html5規(guī)范定義了這方面的需求,webkit在html解析類開始部分的注釋中做了很好的總結(jié)杆逗。

解析器將符號(hào)化的輸入解析為文檔并創(chuàng)建文檔乡翅,但不幸的是,我們必須處理很多沒(méi)有很好格式化的html文檔罪郊,至少要小心下面幾種錯(cuò)誤情況蠕蚜。

1. 在未閉合的標(biāo)簽中添加明確禁止的元素。這種情況下悔橄,應(yīng)該先將前一標(biāo)簽閉合

2. 不能直接添加元素靶累。有些人在寫文檔的時(shí)候會(huì)忘了中間一些標(biāo)簽(或者中間標(biāo)簽是可選的),比如HTML HEAD BODY TR TD LI等

3. 想在一個(gè)行內(nèi)元素中添加塊狀元素癣疟。關(guān)閉所有的行內(nèi)元素挣柬,直到下一個(gè)更高的塊狀元素

4. 如果這些都不行,就閉合當(dāng)前標(biāo)簽直到可以添加該元素睛挚。

下面來(lái)看一些webkit容錯(cuò)的例子:

替代

一些網(wǎng)站使用
替代
凛忿,為了兼容IE和Firefox,webkit將其看作
竞川。

代碼:

if(t->isCloseTag(brTag) && m_document->inCompatMode()) {

reportError(MalformedBRError);

t->beginTag =true;

}

Note -這里的錯(cuò)誤處理在內(nèi)部進(jìn)行店溢,用戶看不到。

迷路的表格

這指一個(gè)表格嵌套在另一個(gè)表格中委乌,但不在它的某個(gè)單元格內(nèi)床牧。

比如下面這個(gè)例子:

inner table

outer table

webkit將會(huì)將嵌套的表格變?yōu)閮蓚€(gè)兄弟表格:

outer table

inner table

代碼:

if(m_inStrayTableContent && localName == tableTag)

popBlock(tableTag);

webkit使用堆棧存放當(dāng)前的元素內(nèi)容,它將從外部表格的堆棧中彈出內(nèi)部的表格遭贸,則它們變?yōu)榱诵值鼙砀瘛?/p>

嵌套的表單元素

用戶將一個(gè)表單嵌套到另一個(gè)表單中戈咳,則第二個(gè)表單將被忽略。

代碼:

if(!m_currentFormElement) {

m_currentFormElement =newHTMLFormElement(formTag,m_document);

}

太深的標(biāo)簽繼承

www.liceo.edu.mx是一個(gè)由嵌套層次的站點(diǎn)的例子壕吹,最多只允許20個(gè)相同類型的標(biāo)簽嵌套著蛙,多出來(lái)的將被忽略。

代碼:

bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName)

{

unsigned i = 0;

for(HTMLStackElem* curr = m_blockStack;

i < cMaxRedundantTagDepth && curr && curr->tagName == tagName;

curr = curr->next, i++) { }

returni != cMaxRedundantTagDepth;

}

放錯(cuò)了地方的html耳贬、body閉合標(biāo)簽

又一次不言自明踏堡。

支持不完整的html。我們從來(lái)不閉合body咒劲,因?yàn)橐恍┯薮赖木W(wǎng)頁(yè)總是在還未真正結(jié)束時(shí)就閉合它顷蟆。我們依賴調(diào)用end方法去執(zhí)行關(guān)閉的處理诫隅。

代碼:

if(t->tagName == htmlTag || t->tagName == bodyTag )

return;

所以,web開發(fā)者要小心了帐偎,除非你想成為webkit容錯(cuò)代碼的范例逐纬,否則還是寫格式良好的html吧。

CSS解析(CSS parsing)

還記得簡(jiǎn)介中提到的解析的概念嗎削樊,不同于html豁生,css屬于上下文無(wú)關(guān)文法,可以用前面所描述的解析器來(lái)解析漫贞。Css規(guī)范定義了css的詞法及語(yǔ)法文法沛硅。

看一些例子:

每個(gè)符號(hào)都由正則表達(dá)式定義了詞法文法(詞匯表):

comment///*[^*]*/*+([^/*][^*]*/*+)*//

num[0-9]+|[0-9]*"."[0-9]+

nonascii[/200-/377]

nmstart[_a-z]|{nonascii}|{escape}

nmchar[_a-z0-9-]|{nonascii}|{escape}

name{nmchar}+

ident{nmstart}{nmchar}*

“ident”是識(shí)別器的縮寫,相當(dāng)于一個(gè)class名绕辖,“name”是一個(gè)元素id(用“R〖。”引用)。

語(yǔ)法用BNF進(jìn)行描述:

ruleset

: selector [ ',' S* selector ]*

'{' S* declaration [ ';' S* declaration ]* '}' S*

;

selector

: simple_selector [ combinator selector | S+ [ combinator selector ] ]

;

simple_selector

: element_name [ HASH | class | attrib | pseudo ]*

| [ HASH | class | attrib | pseudo ]+

;

class

: '.' IDENT

;

element_name

: IDENT | '*'

;

attrib

: '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*

[ IDENT | STRING ] S* ] ']'

;

pseudo

: ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ]

;

說(shuō)明:一個(gè)規(guī)則集合有這樣的結(jié)構(gòu)

div.error , a.error {

color:red;

font-weight:bold;

}

div.error和a.error時(shí)選擇器仪际,大括號(hào)中的內(nèi)容包含了這條規(guī)則集合中的規(guī)則围小,這個(gè)結(jié)構(gòu)在下面的定義中正式的定義了:

ruleset

: selector [ ',' S* selector ]*

'{' S* declaration [ ';' S* declaration ]* '}' S*

;

這說(shuō)明,一個(gè)規(guī)則集合具有一個(gè)或是可選個(gè)數(shù)的多個(gè)選擇器树碱,這些選擇器以逗號(hào)和空格(S表示空格)進(jìn)行分隔肯适。每個(gè)規(guī)則集合包含大括號(hào)及大括號(hào)中的一條或多條以分號(hào)隔開的聲明。聲明和選擇器在后面進(jìn)行定義成榜。

Webkit CSS解析器(Webkit CSS parser)

Webkit使用Flex和Bison解析生成器從CSS語(yǔ)法文件中自動(dòng)生成解析器框舔。回憶一下解析器的介紹赎婚,Bison創(chuàng)建一個(gè)自底向上的解析器刘绣,F(xiàn)irefox使用自頂向下解析器。它們都是將每個(gè)css文件解析為樣式表對(duì)象挣输,每個(gè)對(duì)象包含css規(guī)則纬凤,css規(guī)則對(duì)象包含選擇器和聲明對(duì)象,以及其他一些符合css語(yǔ)法的對(duì)象撩嚼。

圖12:解析css

處理腳本及樣式表的順序(The order of processing scripts and style sheets)

腳本

web的模式是同步的停士,開發(fā)者希望解析到一個(gè)script標(biāo)簽時(shí)立即解析執(zhí)行腳本,并阻塞文檔的解析直到腳本執(zhí)行完完丽。如果腳本是外引的恋技,則網(wǎng)絡(luò)必須先請(qǐng)求到這個(gè)資源——這個(gè)過(guò)程也是同步的,會(huì)阻塞文檔的解析直到資源被請(qǐng)求到逻族。這個(gè)模式保持了很多年蜻底,并且在html4及html5中都特別指定了。開發(fā)者可以將腳本標(biāo)識(shí)為defer瓷耙,以使其不阻塞文檔解析朱躺,并在文檔解析結(jié)束后執(zhí)行。Html5增加了標(biāo)記腳本為異步的選項(xiàng)搁痛,以使腳本的解析執(zhí)行使用另一個(gè)線程长搀。

預(yù)解析(Speculative parsing)

Webkit和Firefox都做了這個(gè)優(yōu)化,當(dāng)執(zhí)行腳本時(shí)鸡典,另一個(gè)線程解析剩下的文檔源请,并加載后面需要通過(guò)網(wǎng)絡(luò)加載的資源。這種方式可以使資源并行加載從而使整體速度更快彻况。需要注意的是谁尸,預(yù)解析并不改變Dom樹,它將這個(gè)工作留給主解析過(guò)程纽甘,自己只解析外部資源的引用良蛮,比如外部腳本、樣式表及圖片悍赢。

樣式表(Style sheets)

樣式表采用另一種不同的模式决瞳。理論上,既然樣式表不改變Dom樹左权,也就沒(méi)有必要停下文檔的解析等待它們皮胡,然而,存在一個(gè)問(wèn)題赏迟,腳本可能在文檔的解析過(guò)程中請(qǐng)求樣式信息屡贺,如果樣式還沒(méi)有加載和解析,腳本將得到錯(cuò)誤的值锌杀,顯然這將會(huì)導(dǎo)致很多問(wèn)題甩栈,這看起來(lái)是個(gè)邊緣情況,但確實(shí)很常見(jiàn)糕再。Firefox在存在樣式表還在加載和解析時(shí)阻塞所有的腳本谤职,而Chrome只在當(dāng)腳本試圖訪問(wèn)某些可能被未加載的樣式表所影響的特定的樣式屬性時(shí)才阻塞這些腳本。

四亿鲜、渲染樹構(gòu)建(Render tree construction)

當(dāng)Dom樹構(gòu)建完成時(shí)允蜈,瀏覽器開始構(gòu)建另一棵樹——渲染樹。渲染樹由元素顯示序列中的可見(jiàn)元素組成蒿柳,它是文檔的可視化表示饶套,構(gòu)建這棵樹是為了以正確的順序繪制文檔內(nèi)容。

Firefox將渲染樹中的元素稱為frames垒探,WebKit則用renderer或渲染對(duì)象來(lái)描述這些元素妓蛮。

一個(gè)渲染對(duì)象知道怎么布局及繪制自己及它的children。

RenderObject是Webkit的渲染對(duì)象基類圾叼,它的定義如下:

class RenderObject{

virtualvoidlayout();

virtualvoidpaint(PaintInfo);

virtualvoidrect repaintRect();

Node* node;//the DOM node

RenderStyle* style;//the computed style

RenderLayer* containgLayer;//the containing z-index layer

}

每個(gè)渲染對(duì)象用一個(gè)和該節(jié)點(diǎn)的css盒模型相對(duì)應(yīng)的矩形區(qū)域來(lái)表示蛤克,正如css2所描述的那樣捺癞,它包含諸如寬、高和位置之類的幾何信息构挤。盒模型的類型受該節(jié)點(diǎn)相關(guān)的display樣式屬性的影響(參考樣式計(jì)算章節(jié))髓介。下面的webkit代碼說(shuō)明了如何根據(jù)display屬性決定某個(gè)節(jié)點(diǎn)創(chuàng)建何種類型的渲染對(duì)象。

RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)

{

Document* doc = node->document();

RenderArena* arena = doc->renderArena();

...

RenderObject* o = 0;

switch(style->display()) {

caseNONE:

break;

caseINLINE:

o =new(arena) RenderInline(node);

break;

caseBLOCK:

o =new(arena) RenderBlock(node);

break;

caseINLINE_BLOCK:

o =new(arena) RenderBlock(node);

break;

caseLIST_ITEM:

o =new(arena) RenderListItem(node);

break;

...

}

returno;

}

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

在Webkit中矾飞,如果一個(gè)元素想創(chuàng)建一個(gè)特殊的渲染對(duì)象一膨,它需要重寫“createRenderer”方法,使渲染對(duì)象指向不包含幾何信息的樣式對(duì)象洒沦。

渲染樹和Dom樹的關(guān)系(The render tree relation to the DOM tree)

渲染對(duì)象和Dom元素相對(duì)應(yīng)豹绪,但這種對(duì)應(yīng)關(guān)系不是一對(duì)一的,不可見(jiàn)的Dom元素不會(huì)被插入渲染樹申眼,例如head元素森篷。另外,display屬性為none的元素也不會(huì)在渲染樹中出現(xiàn)(visibility屬性為hidden的元素將出現(xiàn)在渲染樹中)豺型。

還有一些Dom元素對(duì)應(yīng)幾個(gè)可見(jiàn)對(duì)象仲智,它們一般是一些具有復(fù)雜結(jié)構(gòu)的元素,無(wú)法用一個(gè)矩形來(lái)描述姻氨。例如钓辆,select元素有三個(gè)渲染對(duì)象——一個(gè)顯示區(qū)域、一個(gè)下拉列表及一個(gè)按鈕肴焊。同樣前联,當(dāng)文本因?yàn)閷挾炔粔蚨坌袝r(shí),新行將作為額外的渲染元素被添加娶眷。另一個(gè)多個(gè)渲染對(duì)象的例子是不規(guī)范的html似嗤,根據(jù)css規(guī)范,一個(gè)行內(nèi)元素只能僅包含行內(nèi)元素或僅包含塊狀元素届宠,在存在混合內(nèi)容時(shí)烁落,將會(huì)創(chuàng)建匿名的塊狀渲染對(duì)象包裹住行內(nèi)元素。

一些渲染對(duì)象和所對(duì)應(yīng)的Dom節(jié)點(diǎn)不在樹上相同的位置豌注,例如伤塌,浮動(dòng)和絕對(duì)定位的元素在文本流之外,在兩棵樹上的位置不同轧铁,渲染樹上標(biāo)識(shí)出真實(shí)的結(jié)構(gòu)每聪,并用一個(gè)占位結(jié)構(gòu)標(biāo)識(shí)出它們?cè)瓉?lái)的位置。

圖13:渲染樹及對(duì)應(yīng)的Dom樹

創(chuàng)建樹的流程(The flow of constructing the tree)

Firefox中,表述為一個(gè)監(jiān)聽(tīng)Dom更新的監(jiān)聽(tīng)器药薯,將frame的創(chuàng)建委派給Frame Constructor绑洛,這個(gè)構(gòu)建器計(jì)算樣式(參看樣式計(jì)算)并創(chuàng)建一個(gè)frame。

Webkit中童本,計(jì)算樣式并生成渲染對(duì)象的過(guò)程稱為attachment真屯,每個(gè)Dom節(jié)點(diǎn)有一個(gè)attach方法,attachment的過(guò)程是同步的巾陕,調(diào)用新節(jié)點(diǎn)的attach方法將節(jié)點(diǎn)插入到Dom樹中讨跟。

處理html和body標(biāo)簽將構(gòu)建渲染樹的根纪他,這個(gè)根渲染對(duì)象對(duì)應(yīng)被css規(guī)范稱為containing block的元素——包含了其他所有塊元素的頂級(jí)塊元素鄙煤。它的大小就是viewport——瀏覽器窗口的顯示區(qū)域,F(xiàn)irefox稱它為viewPortFrame茶袒,webkit稱為RenderView梯刚,這個(gè)就是文檔所指向的渲染對(duì)象,樹中其他的部分都將作為一個(gè)插入的Dom節(jié)點(diǎn)被創(chuàng)建薪寓。

樣式計(jì)算(Style Computation)

創(chuàng)建渲染樹需要計(jì)算出每個(gè)渲染對(duì)象的可視屬性亡资,這可以通過(guò)計(jì)算每個(gè)元素的樣式屬性得到。

樣式包括各種來(lái)源的樣式表向叉,行內(nèi)樣式元素及html中的可視化屬性(例如bgcolor)锥腻,可視化屬性轉(zhuǎn)化為css樣式屬性。

樣式表來(lái)源于瀏覽器默認(rèn)樣式表母谎,及頁(yè)面作者和用戶提供的樣式表——有些樣式是瀏覽器用戶提供的(瀏覽器允許用戶定義喜歡的樣式瘦黑,例如,在Firefox中奇唤,可以通過(guò)在Firefox Profile目錄下放置樣式表實(shí)現(xiàn))幸斥。

計(jì)算樣式的一些困難:

1. 樣式數(shù)據(jù)是非常大的結(jié)構(gòu),保存大量的樣式屬性會(huì)帶來(lái)內(nèi)存問(wèn)題咬扇。

2. 如果不進(jìn)行優(yōu)化甲葬,找到每個(gè)元素匹配的規(guī)則會(huì)導(dǎo)致性能問(wèn)題,為每個(gè)元素查找匹配的規(guī)則都需要遍歷整個(gè)規(guī)則表懈贺,這個(gè)過(guò)程有很大的工作量经窖。選擇符可能有復(fù)雜的結(jié)構(gòu),匹配過(guò)程如果沿著一條開始看似正確梭灿,后來(lái)卻被證明是無(wú)用的路徑钠至,則必須去嘗試另一條路徑。

例如胎源,下面這個(gè)復(fù)雜選擇符

div div div div{…}

這意味著規(guī)則應(yīng)用到三個(gè)div的后代div元素棉钧,選擇樹上一條特定的路徑去檢查,這可能需要遍歷節(jié)點(diǎn)樹涕蚤,最后卻發(fā)現(xiàn)它只是兩個(gè)div的后代宪卿,并不使用該規(guī)則的诵,然后則需要沿著另一條路徑去嘗試

3. 應(yīng)用規(guī)則涉及非常復(fù)雜的級(jí)聯(lián),它們定義了規(guī)則的層次

我們來(lái)看一下瀏覽器如何處理這些問(wèn)題:

共享樣式數(shù)據(jù)(Sharing style data)

WebkKit節(jié)點(diǎn)引用樣式對(duì)象(渲染樣式)佑钾,某些情況下西疤,這些對(duì)象可以被節(jié)點(diǎn)間共享,這些節(jié)點(diǎn)需要是兄弟或是表兄弟節(jié)點(diǎn)休溶,并且:

1. 這些元素必須處于相同的鼠標(biāo)狀態(tài)(比如不能一個(gè)處于hover代赁,而另一個(gè)不是)

2. 不能有元素具有id

3. 標(biāo)簽名必須匹配

4. class屬性必須匹配

5. 對(duì)應(yīng)的屬性必須相同

6. 鏈接狀態(tài)必須匹配

7. 焦點(diǎn)狀態(tài)必須匹配

8. 不能有元素被屬性選擇器影響

9. 元素不能有行內(nèi)樣式屬性

10. 不能有生效的兄弟選擇器,webcore在任何兄弟選擇器相遇時(shí)只是簡(jiǎn)單的拋出一個(gè)全局轉(zhuǎn)換兽掰,并且在它們顯示時(shí)使整個(gè)文檔的樣式共享失效芭碍,這些包括+選擇器和類似:first-child和:last-child這樣的選擇器。

Firefox規(guī)則樹(Firefox rule tree)

Firefox用兩個(gè)樹用來(lái)簡(jiǎn)化樣式計(jì)算-規(guī)則樹和樣式上下文樹孽尽,WebKit也有樣式對(duì)象窖壕,但它們并沒(méi)有存儲(chǔ)在類似樣式上下文樹這樣的樹中,只是由Dom節(jié)點(diǎn)指向其相關(guān)的樣式杉女。

圖14:Firefox樣式上下文樹

樣式上下文包含最終值瞻讽,這些值是通過(guò)以正確順序應(yīng)用所有匹配的規(guī)則,并將它們由邏輯值轉(zhuǎn)換為具體的值熏挎,例如速勇,如果邏輯值為屏幕的百分比,則通過(guò)計(jì)算將其轉(zhuǎn)化為絕對(duì)單位坎拐。樣式樹的使用確實(shí)很巧妙烦磁,它使得在節(jié)點(diǎn)中共享的這些值不需要被多次計(jì)算,同時(shí)也節(jié)省了存儲(chǔ)空間廉白。

所有匹配的規(guī)則都存儲(chǔ)在規(guī)則樹中个初,一條路徑中的底層節(jié)點(diǎn)擁有最高的優(yōu)先級(jí),這棵樹包含了所找到的所有規(guī)則匹配的路徑(譯注:可以取巧理解為每條路徑對(duì)應(yīng)一個(gè)節(jié)點(diǎn)猴蹂,路徑上包含了該節(jié)點(diǎn)所匹配的所有規(guī)則)院溺。規(guī)則樹并不是一開始就為所有節(jié)點(diǎn)進(jìn)行計(jì)算,而是在某個(gè)節(jié)點(diǎn)需要計(jì)算樣式時(shí)磅轻,才進(jìn)行相應(yīng)的計(jì)算并將計(jì)算后的路徑添加到樹中珍逸。

我們將樹上的路徑看成辭典中的單詞,假如已經(jīng)計(jì)算出了如下的規(guī)則樹:

假如需要為內(nèi)容樹中的另一個(gè)節(jié)點(diǎn)匹配規(guī)則聋溜,現(xiàn)在知道匹配的規(guī)則(以正確的順序)為B-E-I谆膳,因?yàn)槲覀円呀?jīng)計(jì)算出了路徑A-B-E-I-L,所以樹上已經(jīng)存在了這條路徑撮躁,剩下的工作就很少了漱病。

現(xiàn)在來(lái)看一下樹如何保存。

結(jié)構(gòu)化

樣式上下文按結(jié)構(gòu)劃分,這些結(jié)構(gòu)包括類似border或color這樣的特定分類的樣式信息杨帽。一個(gè)結(jié)構(gòu)中的所有特性不是繼承的就是非繼承的漓穿,對(duì)繼承的特性,除非元素自身有定義注盈,否則就從它的parent繼承晃危。非繼承的特性(稱為reset特性)如果沒(méi)有定義,則使用默認(rèn)的值。

樣式上下文樹緩存完整的結(jié)構(gòu)(包括計(jì)算后的值),這樣迁杨,如果底層節(jié)點(diǎn)沒(méi)有為一個(gè)結(jié)構(gòu)提供定義,則使用上層節(jié)點(diǎn)緩存的結(jié)構(gòu)鳍鸵。

使用規(guī)則樹計(jì)算樣式上下文

當(dāng)為一個(gè)特定的元素計(jì)算樣式時(shí),首先計(jì)算出規(guī)則樹中的一條路徑朴则,或是使用已經(jīng)存在的一條权纤,然后使用路徑中的規(guī)則去填充新的樣式上下文钓简,從樣式的底層節(jié)點(diǎn)開始乌妒,它具有最高優(yōu)先級(jí)(通常是最特定的選擇器),遍歷規(guī)則樹外邓,直到填滿結(jié)構(gòu)撤蚊。如果在那個(gè)規(guī)則節(jié)點(diǎn)沒(méi)有定義所需的結(jié)構(gòu)規(guī)則,則沿著路徑向上损话,直到找到該結(jié)構(gòu)規(guī)則侦啸。

如果最終沒(méi)有找到該結(jié)構(gòu)的任何規(guī)則定義,那么如果這個(gè)結(jié)構(gòu)是繼承型的丧枪,則找到其在內(nèi)容樹中的parent的結(jié)構(gòu)光涂,這種情況下,我們也成功的共享了結(jié)構(gòu)拧烦;如果這個(gè)結(jié)構(gòu)是reset型的忘闻,則使用默認(rèn)的值。

如果特定的節(jié)點(diǎn)添加了值恋博,那么需要做一些額外的計(jì)算以將其轉(zhuǎn)換為實(shí)際值齐佳,然后在樹上的節(jié)點(diǎn)緩存該值,使它的children可以使用债沮。

當(dāng)一個(gè)元素和它的一個(gè)兄弟元素指向同一個(gè)樹節(jié)點(diǎn)時(shí)炼吴,完整的樣式上下文可以被它們共享。

來(lái)看一個(gè)例子:假設(shè)有下面這段html

this is a

big error

this is also a

verybigerror

error

another error

以及下面這些規(guī)則

1.div {margin:5px;color:black}

2..err {color:red}

3..big {margin-top:3px}

4.div span {margin-bottom:4px}

5.#div1 {color:blue}

6.#div2 {color:green}

簡(jiǎn)化下問(wèn)題疫衩,我們只填充兩個(gè)結(jié)構(gòu)——color和margin硅蹦,color結(jié)構(gòu)只包含一個(gè)成員-顏色,margin結(jié)構(gòu)包含四邊。

生成的規(guī)則樹如下(節(jié)點(diǎn)名:指向的規(guī)則)

上下文樹如下(節(jié)點(diǎn)名:指向的規(guī)則節(jié)點(diǎn))

假設(shè)我們解析html童芹,遇到第二個(gè)div標(biāo)簽命爬,我們需要為這個(gè)節(jié)點(diǎn)創(chuàng)建樣式上下文,并填充它的樣式結(jié)構(gòu)辐脖。

我們進(jìn)行規(guī)則匹配饲宛,找到這個(gè)div匹配的規(guī)則為1、2嗜价、6艇抠,我們發(fā)現(xiàn)規(guī)則樹上已經(jīng)存在了一條我們可以使用的路徑1、2久锥,我們只需為規(guī)則6新增一個(gè)節(jié)點(diǎn)添加到下面(就是規(guī)則樹中的F)家淤。

然后創(chuàng)建一個(gè)樣式上下文并將其放到上下文樹中,新的樣式上下文將指向規(guī)則樹中的節(jié)點(diǎn)F瑟由。

現(xiàn)在我們需要填充這個(gè)樣式上下文絮重,先從填充margin結(jié)構(gòu)開始,既然最后一個(gè)規(guī)則節(jié)點(diǎn)沒(méi)有添加margin結(jié)構(gòu)歹苦,沿著路徑向上青伤,直到找到緩存的前面插入節(jié)點(diǎn)計(jì)算出的結(jié)構(gòu),我們發(fā)現(xiàn)B是最近的指定margin值的節(jié)點(diǎn)殴瘦。因?yàn)橐呀?jīng)有了color結(jié)構(gòu)的定義狠角,所以不能使用緩存的結(jié)構(gòu),既然color只有一個(gè)屬性蚪腋,也就不需要沿著路徑向上填充其他屬性丰歌。計(jì)算出最終值(將字符串轉(zhuǎn)換為RGB等),并緩存計(jì)算后的結(jié)構(gòu)屉凯。

第二個(gè)span元素更簡(jiǎn)單立帖,進(jìn)行規(guī)則匹配后發(fā)現(xiàn)它指向規(guī)則G,和前一個(gè)span一樣悠砚,既然有兄弟節(jié)點(diǎn)指向同一個(gè)節(jié)點(diǎn)晓勇,就可以共享完整的樣式上下文,只需指向前一個(gè)span的上下文哩簿。

因?yàn)榻Y(jié)構(gòu)中包含繼承自parent的規(guī)則宵蕉,上下文樹做了緩存(color特性是繼承來(lái)的,但Firefox將其視為reset并在規(guī)則樹中緩存)节榜。

例如羡玛,如果我們?yōu)橐粋€(gè)paragraph的文字添加規(guī)則:

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

那么這個(gè)p在內(nèi)容樹中的子節(jié)點(diǎn)div,會(huì)共享和它parent一樣的font結(jié)構(gòu)宗苍,這種情況發(fā)生在沒(méi)有為這個(gè)div指定font規(guī)則時(shí)稼稿。

Webkit中薄榛,并沒(méi)有規(guī)則樹,匹配的聲明會(huì)被遍歷四次让歼,先是應(yīng)用非important的高優(yōu)先級(jí)屬性(之所以先應(yīng)用這些屬性敞恋,是因?yàn)槠渌囊蕾囉谒鼈儯热鏳isplay),其次是高優(yōu)先級(jí)important的谋右,接著是一般優(yōu)先級(jí)非important的硬猫,最后是一般優(yōu)先級(jí)important的規(guī)則。這樣改执,出現(xiàn)多次的屬性將被按照正確的級(jí)聯(lián)順序進(jìn)行處理啸蜜,最后一個(gè)生效。

總結(jié)一下辈挂,共享樣式對(duì)象(結(jié)構(gòu)中完整或部分內(nèi)容)解決了問(wèn)題1和3衬横,F(xiàn)irefox的規(guī)則樹幫助以正確的順序應(yīng)用規(guī)則。

對(duì)規(guī)則進(jìn)行處理以簡(jiǎn)化匹配過(guò)程

樣式規(guī)則有幾個(gè)來(lái)源:

外部樣式表或style標(biāo)簽內(nèi)的css規(guī)則

行內(nèi)樣式屬性

html可視化屬性(映射為相應(yīng)的樣式規(guī)則)

后面兩個(gè)很容易匹配到元素终蒂,因?yàn)樗鼈兯鶕碛械臉邮綄傩院蚳tml屬性可以將元素作為key進(jìn)行映射蜂林。

就像前面問(wèn)題2所提到的,css的規(guī)則匹配可能很狡猾拇泣,為了解決這個(gè)問(wèn)題噪叙,可以先對(duì)規(guī)則進(jìn)行處理,以使其更容易被訪問(wèn)挫酿。

解析完樣式表之后构眯,規(guī)則會(huì)根據(jù)選擇符添加一些hash映射愕难,映射可以是根據(jù)id早龟、class、標(biāo)簽名或是任何不屬于這些分類的綜合映射猫缭。如果選擇符為id葱弟,規(guī)則將被添加到id映射,如果是class猜丹,則被添加到class映射芝加,等等。

這個(gè)處理是匹配規(guī)則更容易射窒,不需要查看每個(gè)聲明藏杖,我們能從映射中找到一個(gè)元素的相關(guān)規(guī)則,這個(gè)優(yōu)化使在進(jìn)行規(guī)則匹配時(shí)減少了95+%的工作量脉顿。

來(lái)看下面的樣式規(guī)則:

p.error {color:red}

#messageDiv {height:50px}

div {margin:5px}

第一條規(guī)則將被插入class映射蝌麸,第二條插入id映射,第三條是標(biāo)簽映射艾疟。

下面這個(gè)html片段:

an error occurred

this is a message

我們首先找到p元素對(duì)應(yīng)的規(guī)則来吩,class映射將包含一個(gè)“error”的key敢辩,找到p.error的規(guī)則,div在id映射和標(biāo)簽映射中都有相關(guān)的規(guī)則弟疆,剩下的工作就是找出這些由key對(duì)應(yīng)的規(guī)則中哪些確實(shí)是正確匹配的戚长。

例如,如果div的規(guī)則是

table div {margin:5px}

這也是標(biāo)簽映射產(chǎn)生的怠苔,因?yàn)閗ey是最右邊的選擇符同廉,但它并不匹配這里的div元素,因?yàn)檫@里的div沒(méi)有table祖先柑司。

Webkit和Firefox都會(huì)做這個(gè)處理恤溶。

以正確的級(jí)聯(lián)順序應(yīng)用規(guī)則

樣式對(duì)象擁有對(duì)應(yīng)所有可見(jiàn)屬性的屬性,如果特性沒(méi)有被任何匹配的規(guī)則所定義帜羊,那么一些特性可以從parent的樣式對(duì)象中繼承咒程,另外一些使用默認(rèn)值。

這個(gè)問(wèn)題的產(chǎn)生是因?yàn)榇嬖诓恢挂惶幍亩x讼育,這里用級(jí)聯(lián)順序解決這個(gè)問(wèn)題帐姻。

樣式表的級(jí)聯(lián)順序

一個(gè)樣式屬性的聲明可能在幾個(gè)樣式表中出現(xiàn),或是在一個(gè)樣式表中出現(xiàn)多次奶段,因此饥瓷,應(yīng)用規(guī)則的順序至關(guān)重要,這個(gè)順序就是級(jí)聯(lián)順序痹籍。根據(jù)css2的規(guī)范呢铆,級(jí)聯(lián)順序?yàn)椋◤牡偷礁撸?/p>

1. 瀏覽器聲明

2. 用戶聲明

3. 作者的一般聲明

4. 作者的important聲明

5. 用戶important聲明

瀏覽器聲明是最不重要的,用戶只有在聲明被標(biāo)記為important時(shí)才會(huì)覆蓋作者的聲明蹲缠。具有同等級(jí)別的聲明將根據(jù)specifity以及它們被定義時(shí)的順序進(jìn)行排序棺克。Html可視化屬性將被轉(zhuǎn)換為匹配的css聲明,它們被視為最低優(yōu)先級(jí)的作者規(guī)則线定。

Specifity

Css2規(guī)范中定義的選擇符specifity如下:

如果聲明來(lái)自style屬性娜谊,而不是一個(gè)選擇器的規(guī)則,則計(jì)1斤讥,否則計(jì)0(=a)

計(jì)算選擇器中id屬性的數(shù)量(=b)

計(jì)算選擇器中class及偽類的數(shù)量(=c)

計(jì)算選擇器中元素名及偽元素的數(shù)量(=d)

連接a-b-c-d四個(gè)數(shù)量(用一個(gè)大基數(shù)的計(jì)算系統(tǒng))將得到specifity纱皆。這里使用的基數(shù)由分類中最高的基數(shù)定義。例如芭商,如果a為14派草,可以使用16進(jìn)制。不同情況下铛楣,a為17時(shí)近迁,則需要使用阿拉伯?dāng)?shù)字17作為基數(shù),這種情況可能在這個(gè)選擇符時(shí)發(fā)生html body div div …(選擇符中有17個(gè)標(biāo)簽蛉艾,一般不太可能)钳踊。

一些例子:

*{}/* 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 */

/* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */

規(guī)則排序

規(guī)則匹配后衷敌,需要根據(jù)級(jí)聯(lián)順序?qū)σ?guī)則進(jìn)行排序,WebKit先將小列表用冒泡排序拓瞪,再將它們合并為一個(gè)大列表缴罗,WebKit通過(guò)為規(guī)則復(fù)寫“>”操作來(lái)執(zhí)行排序:

static bool operator >(CSSRuleData& r1, CSSRuleData& r2)

{

intspec1 = r1.selector()->specificity();

intspec2 = r2.selector()->specificity();

return(spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2;

}

逐步處理Gradual process

webkit使用一個(gè)標(biāo)志位標(biāo)識(shí)所有頂層樣式表都已加載,如果在attch時(shí)樣式?jīng)]有完全加載祭埂,則放置占位符面氓,并在文檔中標(biāo)記,一旦樣式表完成加載就重新進(jìn)行計(jì)算蛆橡。

五舌界、布局(Layout)

當(dāng)渲染對(duì)象被創(chuàng)建并添加到樹中,它們并沒(méi)有位置和大小泰演,計(jì)算這些值的過(guò)程稱為layout或reflow呻拌。

Html使用基于流的布局模型,意味著大部分時(shí)間睦焕,可以以單一的途徑進(jìn)行幾何計(jì)算藐握。流中靠后的元素并不會(huì)影響前面元素的幾何特性,所以布局可以在文檔中從右向左垃喊、自上而下的進(jìn)行猾普。也存在一些例外,比如html tables本谜。

坐標(biāo)系統(tǒng)相對(duì)于根frame初家,使用top和left坐標(biāo)。

布局是一個(gè)遞歸的過(guò)程乌助,由根渲染對(duì)象開始溜在,它對(duì)應(yīng)html文檔元素,布局繼續(xù)遞歸的通過(guò)一些或所有的frame層級(jí)眷茁,為每個(gè)需要幾何信息的渲染對(duì)象進(jìn)行計(jì)算炕泳。

根渲染對(duì)象的位置是0,0,它的大小是viewport-瀏覽器窗口的可見(jiàn)部分上祈。

所有的渲染對(duì)象都有一個(gè)layout或reflow方法,每個(gè)渲染對(duì)象調(diào)用需要布局的children的layout方法浙芙。

Dirty bit系統(tǒng)

為了不因?yàn)槊總€(gè)小變化都全部重新布局登刺,瀏覽器使用一個(gè)dirty bit系統(tǒng),一個(gè)渲染對(duì)象發(fā)生了變化或是被添加了嗡呼,就標(biāo)記它及它的children為dirty——需要layout纸俭。存在兩個(gè)標(biāo)識(shí)——dirty及children are dirty,children are dirty說(shuō)明即使這個(gè)渲染對(duì)象可能沒(méi)問(wèn)題南窗,但它至少有一個(gè)child需要layout揍很。

全局和增量layout

當(dāng)layout在整棵渲染樹觸發(fā)時(shí)郎楼,稱為全局layout,這可能在下面這些情況下發(fā)生:

1. 一個(gè)全局的樣式改變影響所有的渲染對(duì)象窒悔,比如字號(hào)的改變呜袁。

2. 窗口resize。

layout也可以是增量的简珠,這樣只有標(biāo)志為dirty的渲染對(duì)象會(huì)重新布局(也將導(dǎo)致一些額外的布局)阶界。增量layout會(huì)在渲染對(duì)象dirty時(shí)異步觸發(fā),例如聋庵,當(dāng)網(wǎng)絡(luò)接收到新的內(nèi)容并添加到Dom樹后膘融,新的渲染對(duì)象會(huì)添加到渲染樹中。

圖20:增量layout

異步和同步layout

增量layout的過(guò)程是異步的祭玉,F(xiàn)irefox為增量layout生成了reflow隊(duì)列氧映,以及一個(gè)調(diào)度執(zhí)行這些批處理命令。WebKit也有一個(gè)計(jì)時(shí)器用來(lái)執(zhí)行增量layout-遍歷樹脱货,為dirty狀態(tài)的渲染對(duì)象重新布局屯耸。

另外,當(dāng)腳本請(qǐng)求樣式信息時(shí)蹭劈,例如“offsetHeight”疗绣,會(huì)同步的觸發(fā)增量布局。

全局的layout一般都是同步觸發(fā)铺韧。

有些時(shí)候多矮,layout會(huì)被作為一個(gè)初始layout之后的回調(diào),比如滑動(dòng)條的滑動(dòng)哈打。

優(yōu)化

當(dāng)一個(gè)layout因?yàn)閞esize或是渲染位置改變(并不是大小改變)而觸發(fā)時(shí)塔逃,渲染對(duì)象的大小將會(huì)從緩存中讀取,而不會(huì)重新計(jì)算料仗。

一般情況下湾盗,如果只有子樹發(fā)生改變,則layout并不從根開始立轧。這種情況發(fā)生在格粪,變化發(fā)生在元素自身并且不影響它周圍元素,例如氛改,將文本插入文本域(否則帐萎,每次擊鍵都將觸發(fā)從根開始的重排)。

layout過(guò)程

layout一般有下面這幾個(gè)部分:

1. parent渲染對(duì)象決定它的寬度

2. parent渲染對(duì)象讀取chilidren胜卤,并:

a.?放置child渲染對(duì)象(設(shè)置它的x和y)

b.?在需要時(shí)(它們當(dāng)前為dirty或是處于全局layout或者其他原因)調(diào)用child渲染對(duì)象的layout疆导,這將計(jì)算child的高度

c.?parent渲染對(duì)象使用child渲染對(duì)象的累積高度,以及margin和padding的高度來(lái)設(shè)置自己的高度-這將被parent渲染對(duì)象的parent使用

d. 將dirty標(biāo)識(shí)設(shè)置為false

Firefox使用一個(gè)“state”對(duì)象(nsHTMLReflowState)做為參數(shù)去布局(firefox稱為reflow)葛躏,state包含parent的寬度及其他內(nèi)容澈段。

Firefox布局的輸出是一個(gè)“metrics”對(duì)象(nsHTMLReflowMetrics)悠菜。它包括渲染對(duì)象計(jì)算出的高度。

寬度計(jì)算

渲染對(duì)象的寬度使用容器的寬度败富、渲染對(duì)象樣式中的寬度及margin悔醋、border進(jìn)行計(jì)算。例如囤耳,下面這個(gè)div的寬度:

webkit中寬度的計(jì)算過(guò)程是(RenderBox類的calcWidth方法):

容器的寬度是容器的可用寬度和0中的最大值篙顺,這里的可用寬度為:contentWidth=clientWidth()-paddingLeft()-paddingRight(),clientWidth和clientHeight代表一個(gè)對(duì)象內(nèi)部的不包括border和滑動(dòng)條的大小

元素的寬度指樣式屬性width的值充择,它可以通過(guò)計(jì)算容器的百分比得到一個(gè)絕對(duì)值

加上水平方向上的border和padding

到這里是最佳寬度的計(jì)算過(guò)程德玫,現(xiàn)在計(jì)算寬度的最大值和最小值,如果最佳寬度大于最大寬度則使用最大寬度椎麦,如果小于最小寬度則使用最小寬度宰僧。最后緩存這個(gè)值,當(dāng)需要layout但寬度未改變時(shí)使用观挎。

Line breaking

當(dāng)一個(gè)渲染對(duì)象在布局過(guò)程中需要折行時(shí)琴儿,則暫停并告訴它的parent它需要折行,parent將創(chuàng)建額外的渲染對(duì)象并調(diào)用它們的layout嘁捷。

六造成、繪制(Painting)

繪制階段,遍歷渲染樹并調(diào)用渲染對(duì)象的paint方法將它們的內(nèi)容顯示在屏幕上雄嚣,繪制使用UI基礎(chǔ)組件晒屎,這在UI的章節(jié)有更多的介紹。

全局和增量

和布局一樣缓升,繪制也可以是全局的——繪制完整的樹——或增量的鼓鲁。在增量的繪制過(guò)程中,一些渲染對(duì)象以不影響整棵樹的方式改變港谊,改變的渲染對(duì)象使其在屏幕上的矩形區(qū)域失效骇吭,這將導(dǎo)致操作系統(tǒng)將其看作dirty區(qū)域,并產(chǎn)生一個(gè)paint事件歧寺,操作系統(tǒng)很巧妙的處理這個(gè)過(guò)程燥狰,并將多個(gè)區(qū)域合并為一個(gè)。Chrome中成福,這個(gè)過(guò)程更復(fù)雜些碾局,因?yàn)殇秩緦?duì)象在不同的進(jìn)程中,而不是在主進(jìn)程中奴艾。Chrome在一定程度上模擬操作系統(tǒng)的行為,表現(xiàn)為監(jiān)聽(tīng)事件并派發(fā)消息給渲染根内斯,在樹中查找到相關(guān)的渲染對(duì)象蕴潦,重繪這個(gè)對(duì)象(往往還包括它的children)像啼。

繪制順序

css2定義了繪制過(guò)程的順序——http://www.w3.org/TR/CSS21/zindex.html。這個(gè)就是元素壓入堆棧的順序潭苞,這個(gè)順序影響著繪制忽冻,堆棧從后向前進(jìn)行繪制。

一個(gè)塊渲染對(duì)象的堆棧順序是:

1. 背景色

2. 背景圖

3. border

4. children

5. outline

Firefox顯示列表

Firefox讀取渲染樹并為繪制的矩形創(chuàng)建一個(gè)顯示列表此疹,該列表以正確的繪制順序包含這個(gè)矩形相關(guān)的渲染對(duì)象僧诚。

用這樣的方法,可以使重繪時(shí)只需查找一次樹蝗碎,而不需要多次查找——繪制所有的背景湖笨、所有的圖片、所有的border等等蹦骑。

Firefox優(yōu)化了這個(gè)過(guò)程慈省,它不添加會(huì)被隱藏的元素,比如元素完全在其他不透明元素下面眠菇。

WebKit矩形存儲(chǔ)

重繪前边败,WebKit將舊的矩形保存為位圖,然后只繪制新舊矩形的差集捎废。

七笑窜、動(dòng)態(tài)變化

瀏覽器總是試著以最小的動(dòng)作響應(yīng)一個(gè)變化,所以一個(gè)元素顏色的變化將只導(dǎo)致該元素的重繪登疗,元素位置的變化將大致元素的布局和重繪排截,添加一個(gè)Dom節(jié)點(diǎn),也會(huì)大致這個(gè)元素的布局和重繪谜叹。一些主要的變化匾寝,比如增加html元素的字號(hào),將會(huì)導(dǎo)致緩存失效荷腊,從而引起整數(shù)的布局和重繪艳悔。

八、渲染引擎的線程

渲染引擎是單線程的女仰,除了網(wǎng)絡(luò)操作以外猜年,幾乎所有的事情都在單一的線程中處理,在Firefox和Safari中疾忍,這是瀏覽器的主線程乔外,Chrome中這是tab的主線程。

網(wǎng)絡(luò)操作由幾個(gè)并行線程執(zhí)行一罩,并行連接的個(gè)數(shù)是受限的(通常是2-6個(gè))杨幼。

事件循環(huán)

瀏覽器主線程是一個(gè)事件循環(huán),它被設(shè)計(jì)為無(wú)限循環(huán)以保持執(zhí)行過(guò)程的可用,等待事件(例如layout和paint事件)并執(zhí)行它們差购。下面是Firefox的主要事件循環(huán)代碼四瘫。

while(!mExiting)

NS_ProcessNextEvent(thread);

九、CSS2可視模型(CSS2 visual module)

畫布The Canvas

根據(jù)CSS2規(guī)范欲逃,術(shù)語(yǔ)canvas用來(lái)描述格式化的結(jié)構(gòu)所渲染的空間——瀏覽器繪制內(nèi)容的地方找蜜。畫布對(duì)每個(gè)維度空間都是無(wú)限大的,但瀏覽器基于viewport的大小選擇了一個(gè)初始寬度。

根據(jù)http://www.w3.org/TR/CSS2/zindex.html的定義,畫布如果是包含在其他畫布內(nèi)則是透明的歇万,否則瀏覽器會(huì)指定一個(gè)顏色妓笙。

CSS盒模型

CSS盒模型描述了矩形盒,這些矩形盒是為文檔樹中的元素生成的,并根據(jù)可視的格式化模型進(jìn)行布局。每個(gè)box包括內(nèi)容區(qū)域(如圖片、文本等)及可選的四周padding咬清、border和margin區(qū)域。

每個(gè)節(jié)點(diǎn)生成0-n個(gè)這樣的box奴潘。

所有的元素都有一個(gè)display屬性旧烧,用來(lái)決定它們生成box的類型,例如:

block -生成塊狀box

inline -生成一個(gè)或多個(gè)行內(nèi)box

none -不生成box

默認(rèn)的是inline画髓,但瀏覽器樣式表設(shè)置了其他默認(rèn)值掘剪,例如,div元素默認(rèn)為block奈虾《崴可以訪問(wèn)http://www.w3.org/TR/CSS2/sample.html查看更多的默認(rèn)樣式表示例。

定位策略Position scheme

這里有三種策略:

1. normal -對(duì)象根據(jù)它在文檔的中位置定位肉微,這意味著它在渲染樹和在Dom樹中位置一致匾鸥,并根據(jù)它的盒模型和大小進(jìn)行布局。

2. float -對(duì)象先像普通流一樣布局碉纳,然后盡可能的向左或是向右移動(dòng)勿负。

3. absolute -對(duì)象在渲染樹中的位置和Dom樹中位置無(wú)關(guān)。

static和relative是normal劳曹,absolute和fixed屬于absolute奴愉。

在static定位中,不定義位置而使用默認(rèn)的位置铁孵。其他策略中锭硼,作者指定位置——top、bottom蜕劝、left檀头、right轰异。

Box布局的方式由這幾項(xiàng)決定:box的類型、box的大小鳖擒、定位策略及擴(kuò)展信息(比如圖片大小和屏幕尺寸)溉浙。

Box類型

Block box:構(gòu)成一個(gè)塊烫止,即在瀏覽器窗口上有自己的矩形

Inline box:并沒(méi)有自己的塊狀區(qū)域蒋荚,但包含在一個(gè)塊狀區(qū)域內(nèi)

block一個(gè)挨著一個(gè)垂直格式化,inline則在水平方向上格式化馆蠕。

Inline盒模型放置在行內(nèi)或是line box中期升,每行至少和最高的box一樣高,當(dāng)box以baseline對(duì)齊時(shí)——即一個(gè)元素的底部和另一個(gè)box上除底部以外的某點(diǎn)對(duì)齊互躬,行高可以比最高的box高播赁。當(dāng)容器寬度不夠時(shí),行內(nèi)元素將被放到多行中吼渡,這在一個(gè)p元素中經(jīng)常發(fā)生容为。

定位Position

Relative

相對(duì)定位——先按照一般的定位,然后按所要求的差值移動(dòng)寺酪。

Floats

一個(gè)浮動(dòng)的box移動(dòng)到一行的最左邊或是最右邊坎背,其余的box圍繞在它周圍。下面這段html:

將顯示為:

Absolute和Fixed

這種情況下的布局完全不顧普通的文檔流寄雀,元素不屬于文檔流的一部分得滤,大小取決于容器。Fixed時(shí)盒犹,容器為viewport(可視區(qū)域)懂更。

圖17:fixed

注意-fixed即使在文檔流滾動(dòng)時(shí)也不會(huì)移動(dòng)。

Layered representation

這個(gè)由CSS屬性中的z-index指定急膀,表示盒模型的第三個(gè)大小沮协,即在z軸上的位置。Box分發(fā)到堆棧中(稱為堆棧上下文)卓嫂,每個(gè)堆棧中靠后的元素將被較早繪制慷暂,棧頂靠前的元素離用戶最近,當(dāng)發(fā)生交疊時(shí)命黔,將隱藏靠后的元素呜呐。堆棧根據(jù)z-index屬性排序,擁有z-index屬性的box形成了一個(gè)局部堆棧悍募,viewport有外部堆棧蘑辑,例如:

div

{

position

:absolute;

left

:2in;

top

:2in;}

>

>

結(jié)果是:

雖然綠色div排在紅色div后面,可能在正常流中也已經(jīng)被繪制在后面坠宴,但z-index有更高優(yōu)先級(jí)洋魂,所以在根box的堆棧中更靠前。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市副砍,隨后出現(xiàn)的幾起案子衔肢,更是在濱河造成了極大的恐慌,老刑警劉巖豁翎,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件角骤,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡心剥,警方通過(guò)查閱死者的電腦和手機(jī)邦尊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)优烧,“玉大人蝉揍,你說(shuō)我怎么就攤上這事∑杪Γ” “怎么了又沾?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)熙卡。 經(jīng)常有香客問(wèn)我杖刷,道長(zhǎng),這世上最難降的妖魔是什么再膳? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任挺勿,我火速辦了婚禮,結(jié)果婚禮上喂柒,老公的妹妹穿的比我還像新娘不瓶。我一直安慰自己,他們只是感情好灾杰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布蚊丐。 她就那樣靜靜地躺著,像睡著了一般艳吠。 火紅的嫁衣襯著肌膚如雪麦备。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天昭娩,我揣著相機(jī)與錄音凛篙,去河邊找鬼。 笑死栏渺,一個(gè)胖子當(dāng)著我的面吹牛呛梆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播磕诊,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼填物,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼纹腌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起滞磺,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤升薯,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后击困,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涎劈,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年沛励,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了责语。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡目派,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胁赢,到底是詐尸還是另有隱情企蹭,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布智末,位于F島的核電站谅摄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏系馆。R本人自食惡果不足惜送漠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望由蘑。 院中可真熱鬧闽寡,春花似錦、人聲如沸尼酿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)裳擎。三九已至涎永,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鹿响,已是汗流浹背羡微。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惶我,地道東北人妈倔。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像指孤,于是被迫代替她去往敵國(guó)和親启涯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贬堵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容