摘要:解析器的工作通常分為兩個(gè)內(nèi)容詞法分析器有時(shí)稱(chēng)為標(biāo)記生成器負(fù)責(zé)把輸入分解為很多符號(hào),解析器負(fù)責(zé)根據(jù)該語(yǔ)言的語(yǔ)法規(guī)則來(lái)分析文檔結(jié)構(gòu),從而構(gòu)建解析樹(shù)。解析器通常會(huì)向詞法分析器詢(xún)問(wèn)是否有新的符號(hào),并且試圖通過(guò)一條語(yǔ)法規(guī)則的來(lái)進(jìn)行匹配。
瀏覽器是如何工作的(How browser work)
1. 介紹
1.1 本文涉及到的瀏覽器
1.2 瀏覽器的主要功能
1.3 瀏覽器的主要結(jié)構(gòu)
1.4 組件之前的通信
2. 渲染引擎
2.1 渲染引擎
2.2 主要的流程
2.3 流程示例
2.4 解析以及DOM樹(shù)的結(jié)構(gòu)
2.5 渲染樹(shù)(Render tree)的結(jié)構(gòu)
2.6 布局
2.7 繪制(Painting)
2.8 動(dòng)態(tài)改變
2.9 渲染引擎的線(xiàn)程
2.10 css2 虛擬模型
2.11 資源
1. 介紹瀏覽器可謂是使用最廣泛的軟件. 這篇文章我將要解釋瀏覽器在底層是如何工作的. 我們將會(huì)了解當(dāng)你在瀏覽器地址欄里輸入"google.com"直到頁(yè)面呈現(xiàn)出來(lái)這一過(guò)程都發(fā)生了什么。
1.1 本文涉及到的瀏覽器目前市面上主要有5款瀏覽器: Internet Explorer, Firefox, Safari, Chrome 以及 Opera。
我將使用開(kāi)源的瀏覽器中進(jìn)行舉例,包含F(xiàn)irefox, Chrome 以及 部分開(kāi)源的Fafari。
根據(jù)W3C browser statistics, 當(dāng)前時(shí)間是2009年10月,使用Firefox, Safari 以及 Chrome 的比例占據(jù)將近60%。
所以目前開(kāi)源瀏覽器占據(jù)的瀏覽器市場(chǎng)很大的份額。
瀏覽器的主要功能是把你從服務(wù)器請(qǐng)求到的網(wǎng)絡(luò)資源呈現(xiàn)在瀏覽器窗口上。資源通常包含了HTML,PDF, 圖片等等。資源通常是由用戶(hù)指定的URI(Unifor resource Identifier 統(tǒng)一資源定位符)來(lái)定位的。稍后的章節(jié)會(huì)介紹。
瀏覽器解釋和呈現(xiàn)HTML文件的方式是通過(guò)HTML和CSS規(guī)范來(lái)實(shí)現(xiàn)的。 這些規(guī)范是由W3C組織進(jìn)行維護(hù)的,該組織是互聯(lián)網(wǎng)的標(biāo)準(zhǔn)制定者。長(zhǎng)久以來(lái)各個(gè)瀏覽器廠(chǎng)商只實(shí)現(xiàn)了一部分規(guī)范,并且開(kāi)發(fā)自己的擴(kuò)展程序。這導(dǎo)致了在不同的瀏覽器當(dāng)中很?chē)?yán)重的兼容性問(wèn)題。到目前為止,大部分的瀏覽器都大多實(shí)現(xiàn)了規(guī)范。
不同的瀏覽器UI有很多相同的部分:
輸入U(xiǎn)RI的地址欄
前進(jìn)和后退按鈕
書(shū)簽操作
操作當(dāng)前加載文檔的刷新和停止按鈕
返回主頁(yè)的主頁(yè)按鈕
但是比較奇怪的是, 瀏覽器的UI并沒(méi)有一個(gè)通用的規(guī)范,它只是不同的瀏覽器廠(chǎng)商從長(zhǎng)期的使用習(xí)慣中積累的經(jīng)驗(yàn)。HTML5規(guī)范并沒(méi)有規(guī)定瀏覽器的UI必須包含哪些元素,只是列出了一些通用的元素。地址欄、狀態(tài)欄、工具欄以及各個(gè)瀏覽器指定的特定,例如Firefox的下載管理。更多參見(jiàn)用戶(hù)界面章節(jié)。
1.3 瀏覽器的主要結(jié)構(gòu)瀏覽器的主要組成部分:
用戶(hù)界面(The user interface) - 包含地址欄、前進(jìn)/后退按鈕、書(shū)簽等等。除了主要的窗口之外你所看到的就是請(qǐng)求的頁(yè)面。
瀏覽器引擎 - 查詢(xún)和操作渲染引擎的入口。
渲染引擎 - 負(fù)責(zé)呈現(xiàn)請(qǐng)求內(nèi)容。例如請(qǐng)求內(nèi)容是HTML, 渲染引擎負(fù)責(zé)解析HTML以及CSS,并且渲染解析后的內(nèi)容到屏幕上。
網(wǎng)絡(luò)鏈接 - 處理形如HTTP的網(wǎng)絡(luò)請(qǐng)求。它有針對(duì)不同平臺(tái)的實(shí)現(xiàn)接口。
用戶(hù)界面的后臺(tái)處理程序(UI Backend) - 用于繪制類(lèi)似于 bombo 盒子的小部件以及一些窗口。它拋出了各個(gè)平臺(tái)通用的接口。它的底層是
使用了操作系統(tǒng)的用戶(hù)界面方法。
Javascript 解釋器。 用于解析和執(zhí)行Javascrip代碼。
數(shù)據(jù)存儲(chǔ)。 這是一個(gè)持久層。瀏覽器需要在硬盤(pán)上保存各種各樣的數(shù)據(jù),比如coocies。HTNL5規(guī)范定義了"web database",針對(duì)瀏覽器的完整的數(shù)據(jù)庫(kù)(盡管比較輕量)
Figure 1: Browser main components.
值得注意的是,Chrome不像其他的瀏覽器, 它給每一個(gè)tab分配一個(gè)渲染引擎的實(shí)例,每一個(gè)tab都是一個(gè)獨(dú)立的進(jìn)程。
1.4 組件間的通信Firefox 和 Chrome 都獨(dú)自開(kāi)發(fā)了一套特別的通信機(jī)制。
2. 渲染引擎渲染引擎的職責(zé)就是進(jìn)行渲染, 也就是負(fù)責(zé)把請(qǐng)求到的內(nèi)容呈現(xiàn)在瀏覽器屏幕上。
在默認(rèn)情況下,渲染引擎能夠展示HTML,XML以及image文檔。也能通過(guò)插件來(lái)展示其他類(lèi)型的文檔。例如通過(guò)PDF視圖插件可以展示PDF。我們將會(huì)在特定的章節(jié)討論插件和擴(kuò)展程序。本章節(jié)主要著重于主要的情況-如何展示由css格式化的HTML和images。
2.1 渲染引擎我們參考的Firefox,Chrome,Safari瀏覽器都是基于兩個(gè)渲染引擎建立的。 Firefox 使用 Gecko, 一個(gè)Mozilla自己開(kāi)發(fā)的引擎。Safari 和 Chrome 都是使用的Webkit引擎。Webkit 引擎最開(kāi)始是用于linux平臺(tái)的開(kāi)源引擎。后續(xù)被修改用于支持Apple的Mac以及 Windows系統(tǒng)。 詳情移步http://webkit.org/
2.2 主要渲染流程渲染引擎將會(huì)從網(wǎng)絡(luò)層請(qǐng)求到內(nèi)容開(kāi)始進(jìn)行工作。這通常的大小在8k以?xún)?nèi)。
在這之后,以下就是渲染引擎基本的流程:
Figure 2: Render engine basic flow.
解析HTML, 生成DOM tree -> 渲染render tree結(jié)構(gòu) -> 組織render tree 的布局 -> 在窗口繪制 render tree
渲染引擎會(huì)解析HTML文檔,把HTML文檔解析為“內(nèi)容樹(shù)(content tree)”, 并把HTML標(biāo)簽轉(zhuǎn)換為樹(shù)中的DOM節(jié)點(diǎn)。渲染引擎還要解析樣式文件,包含外鏈樣式文件以及內(nèi)聯(lián)樣式元素。樣式信息和HTML當(dāng)中可視化的指令將會(huì)用于創(chuàng)建另外一個(gè)樹(shù) - 渲染樹(shù) (render tree)。
渲染樹(shù)包含了具有顏色以及尺寸等可視化屬性的矩形盒子集合。這些矩形盒子都是按照在屏幕上的顯示順序排序的。
在構(gòu)造晚渲染樹(shù)之后,將會(huì)經(jīng)過(guò)“l(fā)ayout”過(guò)程。意思就是給每一個(gè)節(jié)點(diǎn)設(shè)置在屏幕上顯示的確切坐標(biāo)位置。下一個(gè)階段是繪制(painting) - 渲染樹(shù)將會(huì)通過(guò)UI的后臺(tái)處理層,每一個(gè)節(jié)點(diǎn)都將會(huì)被繪制。
了解渲染的過(guò)程是一個(gè)循序漸進(jìn)的過(guò)程很重要。為了達(dá)到更好的用戶(hù)體驗(yàn),渲染引擎將會(huì)盡可能快的把內(nèi)容展示在屏幕上。它并不會(huì)等到所有的HTML都解析完之后才去構(gòu)建和布局渲染樹(shù)。當(dāng)請(qǐng)求到一部分內(nèi)容的時(shí)候,引擎將會(huì)解析和渲染這一部分內(nèi)容,同時(shí)程序也將繼續(xù)解析從網(wǎng)絡(luò)中請(qǐng)求到的余下的內(nèi)容。
2.3 渲染流程示例Figure 3: Webkit main flow
Figure 4: Mozilla"s Gecko rendering engine main flow
從圖3 和圖4 中可以看到盡管Webkit 和 Gecko 使用了稍微不同的術(shù)語(yǔ),但是流程是基本相同的。
Gecko 把格式化的元素形象的稱(chēng)為:Frame tree(結(jié)構(gòu)樹(shù))。每一個(gè)元素都是一個(gè)框架。Webkit 使用術(shù)語(yǔ):Render tree, 它由Render Object 組成。Webkit把設(shè)置元素的位置稱(chēng)為layout,而 Gecko稱(chēng)為Reflow。 Webkit 把連接DOM節(jié)點(diǎn)和視覺(jué)信息生成渲染樹(shù)稱(chēng)為Attachment。另外一個(gè)較小的非語(yǔ)義上的差別是Gecko在HTML與DOM樹(shù)之間多了額外的一層。叫做content sink, 它是創(chuàng)建DOM元素的工廠(chǎng)。我們將會(huì)逐個(gè)了解流程的每一部分。
通常的解析
既然解析在渲染引擎內(nèi)是一個(gè)非常重要的過(guò)程,我們將會(huì)深入的了解它。
文檔解析,亦即把它轉(zhuǎn)換為一種代碼可以理解和使用的結(jié)構(gòu)。解析的結(jié)果通常是一個(gè)表示文檔結(jié)構(gòu)的節(jié)點(diǎn)樹(shù)。它被稱(chēng)為解析樹(shù)或者語(yǔ)法樹(shù)。
例如:2 + 3 - 1 的表達(dá)式解析結(jié)果為
Figure 5: 運(yùn)算表達(dá)式的樹(shù)節(jié)點(diǎn)
文法
解析是基于創(chuàng)建文檔語(yǔ)言所遵循的語(yǔ)法規(guī)則。每一個(gè)你能夠解析的格式,都有一個(gè)由詞法和語(yǔ)法規(guī)則組成的確切的文法。它被稱(chēng)為context free grammar(上下文無(wú)關(guān)的語(yǔ)法)。人類(lèi)語(yǔ)言不是這樣的語(yǔ)言,也就是說(shuō)沒(méi)法用常規(guī)的解析技術(shù)來(lái)進(jìn)行解析。
解析器 - 詞法組合
解析可以被分為兩個(gè)步驟 - 詞法分析 以及 語(yǔ)法分析。
詞法分析是把輸入的內(nèi)容分解為很多符號(hào)的一個(gè)過(guò)程。這些符號(hào)是構(gòu)成語(yǔ)言的詞匯(構(gòu)建語(yǔ)言有效的塊集合)。在人類(lèi)的語(yǔ)言中,它就是某種語(yǔ)言在字典中的所有單詞所組成的。
語(yǔ)法分析就是語(yǔ)言語(yǔ)法規(guī)則的應(yīng)用。
解析器的工作通常分為兩個(gè)內(nèi)容:詞法分析器(有時(shí)稱(chēng)為 標(biāo)記生成器)負(fù)責(zé)把輸入分解為很多符號(hào),解析器負(fù)責(zé)根據(jù)該語(yǔ)言的語(yǔ)法規(guī)則來(lái)分析文檔結(jié)構(gòu),從而構(gòu)建解析樹(shù)。詞法分析器知道如何區(qū)分和解釋特殊的字符,例如空格和換行符。
Figure 6: from source document to parse trees
解析的過(guò)程是迭代式的。解析器通常會(huì)向詞法分析器詢(xún)問(wèn)是否有新的符號(hào),并且試圖通過(guò)一條語(yǔ)法規(guī)則的來(lái)進(jìn)行匹配。如果符合某條語(yǔ)法規(guī)則,該符號(hào)對(duì)應(yīng)的節(jié)點(diǎn)將會(huì)被添加到解析樹(shù),緊接著解析器會(huì)詢(xún)問(wèn)另外一個(gè)符號(hào)就行解析。
如果沒(méi)有規(guī)則匹配,解析器會(huì)在內(nèi)部存儲(chǔ)這個(gè)符號(hào),并繼續(xù)詢(xún)問(wèn)下一個(gè)符號(hào)直到某條規(guī)則匹配所有的內(nèi)部存儲(chǔ)的符號(hào)。如果沒(méi)有找到對(duì)應(yīng)的規(guī)則,解析器就回拋出一個(gè)異常。這意味著這個(gè)文檔無(wú)效,并且包含語(yǔ)法錯(cuò)誤。
翻譯
通常解析樹(shù)并不是最終的結(jié)果。解析結(jié)果通常被翻譯-把文檔翻譯為另外一種格式。一個(gè)例子就是匯編。編譯器會(huì)把源碼編譯為機(jī)器碼,首先會(huì)把源碼解析為解析樹(shù),然后再把解析樹(shù)翻譯為機(jī)器碼文檔。
Figure 7: compilation flow
解析實(shí)例
在圖5中,我們從一個(gè)數(shù)學(xué)表達(dá)式中創(chuàng)建了一個(gè)解析樹(shù)。 讓我們來(lái)定義一個(gè)簡(jiǎn)單的數(shù)學(xué)語(yǔ)言來(lái)了解解析過(guò)程。
詞匯: 我們的語(yǔ)言包含整數(shù),加法符號(hào),減法符號(hào)
語(yǔ)法:
1. 構(gòu)成語(yǔ)法的元素包含表達(dá)式,運(yùn)算項(xiàng),運(yùn)算符。 2. 我們的語(yǔ)言能夠包含任意數(shù)量的表達(dá)式。 3. 一個(gè)表達(dá)式定義為: 一個(gè)運(yùn)算項(xiàng) 跟著一個(gè) 操作符,再跟著另外一個(gè)運(yùn)算項(xiàng)。 4. 操作符為加號(hào)或者減號(hào) 5. 運(yùn)算項(xiàng)為一個(gè)整數(shù)或者一個(gè)表達(dá)式。
分析下:"2 + 3 - 1"。
根據(jù)上面第5條規(guī)則,第一個(gè)匹配規(guī)則的子串是"2"。第二個(gè)匹配的的結(jié)果是"2 + 3",它對(duì)應(yīng)第二條規(guī)則。下一個(gè)匹配的結(jié)果已經(jīng)到了該輸入項(xiàng)的結(jié)尾。我們已經(jīng)知道了形如?2 + 3?表示一個(gè)完整項(xiàng),那么 "2 + 3 + 1"就是一個(gè)表達(dá)式。"2 + +" 是一個(gè)無(wú)效的輸入,因?yàn)闆](méi)有匹配任何規(guī)則。
正式的定義詞匯和語(yǔ)法
詞匯通常都通過(guò)常規(guī)的表達(dá)式來(lái)表示。
例如我們將會(huì)像下面這樣來(lái)定義我們的語(yǔ)言:
INTER :0|1-9*
PLUS : +
MINUS : -
如你所見(jiàn),整數(shù)是通過(guò)常規(guī)的表達(dá)式來(lái)表達(dá)的。
語(yǔ)法是遵循BNF(Backus Naur form).html)來(lái)定義的。我們的語(yǔ)言將會(huì)做如下的定義:
expression := term operation tem
operation := PLUS | MINUS
term := INTEGER | expression
我們之前說(shuō)過(guò),如果程序的語(yǔ)法是一個(gè)上下文無(wú)關(guān)的語(yǔ)法,就可以使用通常的解析器進(jìn)行解析。上下文無(wú)關(guān)的語(yǔ)法,最直觀的定義就是可以完全使用BNF來(lái)表示??梢詤⒁?jiàn)http://en.wikipedia.org/wiki/Context-free_grammar
解析器類(lèi)型
解析器有兩種類(lèi)型: 自上而下 和 自下而上 的解析器。 自上而下的解析器是從語(yǔ)法層級(jí)比較高的地方著手進(jìn)行匹配解析。自下而上的解析方式是從輸入開(kāi)始,逐級(jí)向上翻譯為對(duì)應(yīng)的語(yǔ)法規(guī)則,直到語(yǔ)法層級(jí)較高的規(guī)則為止。
讓我們結(jié)合實(shí)例來(lái)看看這兩種解析方式:
自上而下的解析將會(huì)從層級(jí)比較高的規(guī)則開(kāi)始: 它將把 2 + 3 定義為一個(gè)表達(dá)式。然后再把 2 + 3 -1 定義為一個(gè)表達(dá)式。
自下而上的解析將會(huì)掃描整個(gè)輸入的字符串,如果有符合的規(guī)則, 則會(huì)根據(jù)規(guī)則替換匹配項(xiàng),直到替換玩整個(gè)輸入。匹配的表達(dá)式將會(huì)存儲(chǔ)在解析器棧里。
Stack | Input |
---|---|
2 + 3 - 1 | |
term | + 3 - 1 |
term operation | 3 - 1 |
expression | - 1 |
expression operation | 1 |
expression |
自下而上的解析方式又稱(chēng)之為移動(dòng)減少解析器(shift reduce parser),因?yàn)檩斎胧菑淖笙蛴乙苿?dòng)的,并且根據(jù)規(guī)則匹配主鍵減少。
自動(dòng)生成解析器
可以通過(guò)工具生成解析器,被稱(chēng)之為解析器生成器。 你只需要提供語(yǔ)言的詞匯以及語(yǔ)法規(guī)則,它就能夠生成一個(gè)可用的解析器。創(chuàng)建一個(gè)解析器徐傲對(duì)解析有深入的理解。不太容易手動(dòng)創(chuàng)建一個(gè)解析器,所有解析器生成器會(huì)比較有用。
Webkit 使用兩個(gè)比較出名的解析器生成器: Flex 用于創(chuàng)建詞法分析器, Bison用于創(chuàng)建解析器(你可以使用Lex 和 Yacc來(lái)運(yùn)行)。Flex的輸入是包含通常的表達(dá)式定義的一個(gè)文件。Bison 的輸入是BNF格式的語(yǔ)法規(guī)則。
HTML 解析器
HTML解析器的職責(zé)是把HTML標(biāo)記轉(zhuǎn)換為解析樹(shù)。
HTML 語(yǔ)法定義
HTML的詞匯和語(yǔ)法在w3c創(chuàng)建的規(guī)范里定義。
非上下文無(wú)關(guān)的程序語(yǔ)言
在解析一節(jié)的介紹里,我們知道程序語(yǔ)法可以通過(guò)BNF格式進(jìn)行定義。
但是不幸的是,所有常規(guī)的解析器都不適用于HTML。HTML不能夠被輕易的定義為解析器需要的上下文無(wú)關(guān)的程序語(yǔ)法。
有一個(gè)定義HTML的通用格式-DTD(Document Type Definition), 不過(guò)并不是上下文無(wú)關(guān)的語(yǔ)法。
一眼看上去, HTML與XML非常的接近。 有很多的XML解析器。有一個(gè)HTML的XML變體-XXHTML。這二者有什么不用呢?
不同之處在于HTML的目的在于非嚴(yán)謹(jǐn)?shù)?,它允許忽略你某些標(biāo)簽,并隱式的添加上,例如有時(shí)候允許忽略開(kāi)始或者結(jié)束標(biāo)簽。不同于XML語(yǔ)法的嚴(yán)格和硬性要求,HTML整體上都是比較寬泛的。
一方面這也是HTML如此瀏覽的一個(gè)原因,允許你犯錯(cuò),讓web開(kāi)發(fā)更容易。另一方面,它導(dǎo)致很難定義一個(gè)語(yǔ)法格式??偨Y(jié)起來(lái)說(shuō), HTML比較難解析,由于并不是一個(gè)上下文無(wú)關(guān)的編程語(yǔ)法,它不能夠被普通的解析器解析, 也不能被XML解析器解析。
HTML DTD
HTML是通過(guò)DTD來(lái)定義的。 這個(gè)格式用于定義SGML(Standard Generalized Markup Language)語(yǔ)言。 它定義了所有允許的元素,屬性以及層級(jí)。正如我們之前所說(shuō)的, HTML DTD 不能形成上下文無(wú)關(guān)的語(yǔ)言。
DTD有一些變動(dòng),嚴(yán)格模式嚴(yán)格符合規(guī)范,其他的模式支持歷史版本的瀏覽器。 目的也是為了兼容老版本的瀏覽器。 最新的嚴(yán)格DTD地址:http://www.w3.org/TR/html4/strict.dtd
DOM
解析樹(shù)是由DOM元素以及屬性節(jié)點(diǎn)組成的。 DOM是Document Objectd Model 的簡(jiǎn)稱(chēng)。 它是HTML文檔的對(duì)象形式以及其他外部語(yǔ)言(形如Javascript)的接口。樹(shù)的根節(jié)點(diǎn)是 Document 對(duì)象。
DOM與標(biāo)簽之前有著一對(duì)一的對(duì)應(yīng)關(guān)系。 例如:
Hello World
將會(huì)被翻譯為以下的DOM樹(shù):
Figure 8: DOM tree of the example markup
跟HTML一樣, DOM也是被w3c組織定義和管理的。詳見(jiàn)http://www.w3.org/DOM/DOMTR。 它是操作文檔的通用規(guī)范。 一個(gè)特定的模塊描述了HTML特定的元素。 HTML定義可以參見(jiàn)http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html。
當(dāng)我說(shuō)樹(shù)包含了DOM節(jié)點(diǎn), 意即樹(shù)是由實(shí)現(xiàn)了DOM接口的元素構(gòu)建的。 不同的瀏覽器使用了具體的實(shí)現(xiàn),這些實(shí)現(xiàn)包含了瀏覽器內(nèi)部使用的其他屬性。
解析算法
在前幾節(jié)中,我們知道, HTML不能夠通過(guò)自上而下或者自下而上的解析器解析。
原因如下:
1. 語(yǔ)言的非嚴(yán)謹(jǐn)性。 2. 瀏覽器具有傳統(tǒng)的容錯(cuò)機(jī)制,用于支持很好的辨別無(wú)效的HTML。 3. 解析進(jìn)程的反復(fù)迭代機(jī)制。 在解析的過(guò)程中,解析源通常都不會(huì)被改變, 但是在HTML中, script標(biāo)簽包含了document.write, 可以添 加額外的元素,所以解析過(guò)程中修改了原始輸入。
由于不能夠使用通常的解析器就行解析, 瀏覽器為解析HTML創(chuàng)建了定制的解析器。
解析算法在HTML5規(guī)范中有詳細(xì)的描述。 算法由兩步構(gòu)成: 符號(hào)化 和 構(gòu)建樹(shù)。
符號(hào)化即詞法分析,把輸入解析為一組符號(hào)。 HTML的符號(hào)包括開(kāi)始標(biāo)簽, 結(jié)束標(biāo)簽, 屬性名和屬性值。
標(biāo)記生成器識(shí)別不同的標(biāo)記, 并把它傳遞給樹(shù)構(gòu)造器,緊接著識(shí)別下一個(gè)標(biāo)記, 周而復(fù)始, 直到結(jié)束。
Figure 6: HTML parsing flow (taken from HTML5 spec)
符號(hào)化的算法
算法的輸出結(jié)果是一個(gè)HTML的標(biāo)簽。 算法被表示為狀態(tài)機(jī)。 每一個(gè)狀態(tài)消耗一個(gè)或者多個(gè)輸入流的字符,然后根據(jù)選中的字符跟更新下一個(gè)狀態(tài)。 當(dāng)前的執(zhí)行會(huì)被符號(hào)化的狀態(tài)和構(gòu)建樹(shù)的狀態(tài)所影響。 這意味著, 相同的符號(hào)處理,將會(huì)產(chǎn)生不同的結(jié)果,根據(jù)當(dāng)前的狀體來(lái)糾正下一個(gè)狀態(tài)。這個(gè)算法太復(fù)雜了, 因此不能完整的呈現(xiàn)出來(lái)。 所有我們通過(guò)一個(gè)簡(jiǎn)單的實(shí)例來(lái)幫助我們理解這個(gè)原則。
基礎(chǔ)實(shí)例: 符號(hào)化以下的HTML:
Hello world
初始狀態(tài)是"Data state"。當(dāng)遇到"<"符號(hào)的時(shí)候 ,狀態(tài)被變更為"Tag open state"。在處理"a-z"之間的字符時(shí)會(huì)創(chuàng)建"Start tag token",狀態(tài)被變更為"Tag name state"。狀態(tài)會(huì)一直保持,直到遇到">"字符。每一個(gè)字符都會(huì)被添加到新的標(biāo)簽名里。在我們的事例里創(chuàng)建的是一個(gè)"html"標(biāo)簽。
當(dāng)處理到">"符號(hào)的時(shí)候,當(dāng)前的標(biāo)簽就回被發(fā)送出去,同時(shí)狀態(tài)會(huì)變更回"Data state"。"
"標(biāo)簽也以同樣的方式進(jìn)行處理。到目前為止,"html"和"body"標(biāo)簽都被觸發(fā)。我們現(xiàn)在回到了"Data state"。處理"Hello world"中的"H"字符會(huì)創(chuàng)建和出發(fā)一個(gè)字符標(biāo)簽,直到遇到""的"<"符號(hào)為止。我們會(huì)為"Hello world"的每一個(gè)字符都觸發(fā)一個(gè)字符標(biāo)簽。
現(xiàn)在我們回到"Tag open state"。處理"/"會(huì)創(chuàng)建一個(gè)"end tag token",并且狀態(tài)變更為"Tag name state"。我們依然保留當(dāng)前狀態(tài)直到遇到">"為止。之后新的標(biāo)簽就回被出發(fā),狀態(tài)返回"Data state"。""的處理方式雷同。
Figure 9: Tokenizing the example input
樹(shù)結(jié)構(gòu)算法
當(dāng)解析器被創(chuàng)建的時(shí)候,文檔對(duì)象也會(huì)被創(chuàng)建。在構(gòu)建樹(shù)結(jié)構(gòu)的過(guò)程中,文檔的DOM樹(shù)將會(huì)被修改,相應(yīng)的元素將會(huì)被添加進(jìn)去。標(biāo)記生成器創(chuàng)建的每一個(gè)節(jié)點(diǎn)都會(huì)被樹(shù)構(gòu)造器處理。規(guī)范中定義的每一個(gè)DOM元素關(guān)聯(lián)的標(biāo)記都會(huì)被創(chuàng)建。除了把元素添加到DOM樹(shù)之外,還會(huì)被添加到"open elements"棧中。這個(gè)棧被用于糾正不匹配的嵌套以及處理未關(guān)閉的標(biāo)簽。這個(gè)算法過(guò)程也被描述為一個(gè)狀態(tài)機(jī)。狀態(tài)被稱(chēng)為"insertion modes"。
讓我們看看事例中構(gòu)造樹(shù)的過(guò)程:
Hello world
樹(shù)構(gòu)造階段接收的輸入是從字符化階段傳入的字符序列。第一個(gè)模式是"initial mode"。當(dāng)接收到html標(biāo)簽的時(shí)候,會(huì)移動(dòng)到"before html"模式,同時(shí)再對(duì)標(biāo)簽進(jìn)行處理。此時(shí)會(huì)創(chuàng)建一個(gè)HTMLHtmlElement元素,這個(gè)元素會(huì)被添加到文檔對(duì)象的根節(jié)點(diǎn)。
之后狀態(tài)將會(huì)變?yōu)?before head"。我們會(huì)接收到body標(biāo)簽,此時(shí)將會(huì)隱式的創(chuàng)建一個(gè)HTMLHeadElementut元素并添加到DOM樹(shù)里,盡管示例中并沒(méi)有head標(biāo)簽。
緊接著移動(dòng)到"in head",然后是"after head"。body標(biāo)簽會(huì)被再加工,一個(gè)HTMLBodyElement將會(huì)被創(chuàng)建和添加到DOM樹(shù), 模式會(huì)移動(dòng)到"in body"。
接下來(lái)會(huì)接收到"Hello world"字符串。處理第一個(gè)字符的時(shí)候會(huì)創(chuàng)建一個(gè)"Text"節(jié)點(diǎn),其他的字符會(huì)被添加到這個(gè)節(jié)點(diǎn)中。
當(dāng)接收到body結(jié)束標(biāo)簽的時(shí)候會(huì)移動(dòng)到"after body"模式。此時(shí)我們會(huì)接收到html結(jié)束標(biāo)簽,會(huì)移動(dòng)到"after after body"模式。接收到文件結(jié)束標(biāo)簽的時(shí)候?qū)?huì)結(jié)束解析。
Figure 10: tree construction of example html
解析之后的動(dòng)作
在這一步瀏覽器將會(huì)把文檔標(biāo)記為可交互的,同時(shí)開(kāi)始解析在“defferred”模式下的scripts文件(在文檔解析完成之后將會(huì)被執(zhí)行)。文檔的狀態(tài)將會(huì)被修改為“complete”,同時(shí)觸發(fā)一個(gè)“l(fā)oad”事件。
你可以在HTML5規(guī)范里查看標(biāo)記化以及構(gòu)建樹(shù)的完整算法。https://www.w3.org/TR/html5/syntax.html
瀏覽器的容錯(cuò)
在HTML頁(yè)面里你永遠(yuǎn)不會(huì)收到一個(gè)“語(yǔ)法無(wú)效”的錯(cuò)誤。瀏覽器會(huì)處理無(wú)效的內(nèi)容。
以下面的HTML為例:
Really lousy HTML
我已經(jīng)違反了很多規(guī)則(“mytag”不是標(biāo)準(zhǔn)的標(biāo)簽,“p”和“div”標(biāo)簽的錯(cuò)誤嵌套等等),但是瀏覽器仍然會(huì)把內(nèi)容正確的展示出來(lái),并不會(huì)報(bào)錯(cuò)。所以有很多的解析代碼來(lái)修復(fù)了HTML開(kāi)發(fā)者的錯(cuò)誤。
瀏覽器中的錯(cuò)誤處理始終是一致的,但是讓人比較驚訝的是它并不是當(dāng)前HTML規(guī)范的一部分。它就像書(shū)簽和前進(jìn)后退按鈕一樣,只是多年以來(lái)在瀏覽器中開(kāi)發(fā)出來(lái)的某個(gè)東西。 在很多站點(diǎn)中都已知很多無(wú)效的HTML結(jié)構(gòu),瀏覽器會(huì)試著已一致的方式修復(fù)它們,以順應(yīng)其他瀏覽器。
HTML5規(guī)范針對(duì)這些要求做了一些定義。Webkit在HTML解析類(lèi)開(kāi)始的注釋中做了很好的描述:
The parser parses tokenized input into the document, building up the document tree. If the document is well-formed, parsing it is straightforward. Unfortunately, we have to handle many HTML documents that are not well-formed, so the parser has to be tolerant about errors. We have to take care of at least the following error conditions: 1. The element being added is explicitly forbidden inside some outer tag. In this case we should close all tags up to the one, which forbids the element, and add it afterwards. 2. We are not allowed to add the element directly. It could be that the person writing the document forgot some tag in between (or that the tag in between is optional). This could be the case with the following tags: HTML HEAD BODY TBODY TR TD LI (did I forget any?). 3. We want to add a block element inside to an inline element. Close all inline elements up to the next higher block element. 4. If this doesn"t help, close elements until we are allowed to add the element or ignore the tag.
讓我們看看Webkit的容錯(cuò)示例:
instead of
某些站點(diǎn)使用代替
。為了兼容IE和Firefox, Webkit 使用
。
代碼:
if (t->isCloseTag(brTag) && m_document->inCompatMode()) { reportError(MalformedBRError); t->beginTag = true; } 注意: 錯(cuò)誤處理是在內(nèi)容,并不會(huì)呈現(xiàn)給用戶(hù)。
錯(cuò)亂的table
錯(cuò)亂偏離的table是指在另外一個(gè)table里但是卻不在table cell里的table。
就像下面的例子:
inner table |
Webkit 就會(huì)把結(jié)構(gòu)修改為兩個(gè)子table
outer table |
inner table |
代碼:
if (m_inStrayTableContent && localName == tableTag) popBlock(tableTag);
Webkit 使用棧來(lái)管理當(dāng)前元素內(nèi)容,它會(huì)彈出內(nèi)部table,再入棧到外部table的棧中。table至此就相鄰了。
嵌套的表單元素
以防用戶(hù)在form 中放置另外一個(gè)form, 第二個(gè)form將會(huì)被忽略。
if (!m_currentFormElement) { m_currentFormElement = new HTMLFormElement(formTag, m_document); }
過(guò)深的標(biāo)簽層級(jí)
注釋不言而喻。
www.liceo.edu.mx is an example of a site that achieves a level of nesting of about 1500 tags, all from a bunch of s. We will only allow at most 20 nested tags of the same type before just ignoring them all together.
代碼:
bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName) { unsigned i = 0; for (HTMLStackElem* curr = m_blockStack; i < cMaxRedundantTagDepth && curr && curr->tagName == tagName; curr = curr->next, i++) { } return i != cMaxRedundantTagDepth; }
html和body結(jié)束標(biāo)簽的錯(cuò)放
看注釋?zhuān)?/p>
Support for really broken html. We never close the body tag, since some stupid web pages close it before the actual end of the doc. Let"s rely on the end() call to close things. if (t->tagName == htmlTag || t->tagName == bodyTag ) return;
所以web開(kāi)發(fā)者需要注意: 除非你想呈現(xiàn)一個(gè)Webkit容錯(cuò)的示例代碼,否則請(qǐng)編寫(xiě)完整的HTML標(biāo)簽。
CSS解析
待續(xù)...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/114855.html
摘要:下面我們從前端基礎(chǔ)和底層原理開(kāi)始講起。對(duì)于和這三個(gè)對(duì)應(yīng)于矢量圖位圖和圖的渲染來(lái)說(shuō),給前端開(kāi)發(fā)帶來(lái)了重武器,很多小游戲也因此蓬勃發(fā)展。這篇文章受眾之大,后來(lái)被人重新整理并發(fā)布為,其中還包括中文版。 showImg(https://segmentfault.com/img/bVbjM5r?w=1142&h=640); 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你! 這...
摘要:解析器的工作通常分為兩個(gè)內(nèi)容詞法分析器有時(shí)稱(chēng)為標(biāo)記生成器負(fù)責(zé)把輸入分解為很多符號(hào),解析器負(fù)責(zé)根據(jù)該語(yǔ)言的語(yǔ)法規(guī)則來(lái)分析文檔結(jié)構(gòu),從而構(gòu)建解析樹(shù)。解析器通常會(huì)向詞法分析器詢(xún)問(wèn)是否有新的符號(hào),并且試圖通過(guò)一條語(yǔ)法規(guī)則的來(lái)進(jìn)行匹配。 瀏覽器是如何工作的(How browser work) 1. 介紹 1.1 本文涉及到的瀏覽器 1.2 瀏覽器的主要功能 1.3 瀏覽器的主要結(jié)構(gòu) 1.4...
摘要:解析器的工作通常分為兩個(gè)內(nèi)容詞法分析器有時(shí)稱(chēng)為標(biāo)記生成器負(fù)責(zé)把輸入分解為很多符號(hào),解析器負(fù)責(zé)根據(jù)該語(yǔ)言的語(yǔ)法規(guī)則來(lái)分析文檔結(jié)構(gòu),從而構(gòu)建解析樹(shù)。解析器通常會(huì)向詞法分析器詢(xún)問(wèn)是否有新的符號(hào),并且試圖通過(guò)一條語(yǔ)法規(guī)則的來(lái)進(jìn)行匹配。 瀏覽器是如何工作的(How browser work) 1. 介紹 1.1 本文涉及到的瀏覽器 1.2 瀏覽器的主要功能 1.3 瀏覽器的主要結(jié)構(gòu) 1.4...
摘要:好的,我也不想多說(shuō),爬蟲(chóng)的代碼我會(huì)分享到去轉(zhuǎn)盤(pán)網(wǎng),想下載本爬蟲(chóng)代碼的孩子請(qǐng)點(diǎn)我下載,如果沒(méi)有下載到,請(qǐng)點(diǎn)擊這個(gè)鏈接。 上一篇我寫(xiě)了如何爬取百度網(wǎng)盤(pán)的爬蟲(chóng),在這里還是重溫一下,把鏈接附上: http://www.cnblogs.com/huangx... 這一篇我想寫(xiě)寫(xiě)如何爬取百度圖片的爬蟲(chóng),這個(gè)爬蟲(chóng)也是:搜搜gif(在線(xiàn)制作功能點(diǎn)我) 的爬蟲(chóng)代碼,其實(shí)爬蟲(chóng)整體框架還是差不多的,但就是會(huì)...
閱讀 564·2024-11-06 13:38
閱讀 853·2024-09-10 13:19
閱讀 1007·2024-08-22 19:45
閱讀 1395·2021-11-19 09:40
閱讀 2649·2021-11-18 13:14
閱讀 4305·2021-10-09 10:02
閱讀 2340·2021-08-21 14:12
閱讀 1295·2019-08-30 15:54