渲染引擎
渲染引擎的職責(zé)是……渲染,也就是把請(qǐng)求的內(nèi)容顯示到瀏覽器屏幕上粪小。
默認(rèn)情況下渲染引擎可以顯示HTML大磺,XML文檔以及圖片。 通過插件(瀏覽器擴(kuò)展)它可以顯示其它類型文檔探膊。比如使用PDF viewer插件顯示PDF文件杠愧。我們會(huì)在一個(gè)專門的章節(jié)討論插件與擴(kuò)展。在這一節(jié)我們將專注渲染引擎的主要用途——顯示用CSS格式化的HTML與圖片逞壁。
我們提到的Firefox, Safari兩種瀏覽器構(gòu)建于兩種渲染引擎之上:Firefox使用Gecko —— Mozilla自家的渲染引擎流济;Safari 和 Chrome 都使用 Webkit。
Webkit 是一個(gè)開源的渲染引擎腌闯,它源自Linux平臺(tái)上的一個(gè)引擎绳瘟,經(jīng)過Apple公司的修改可以支持Mac與Windows平臺(tái)。更多信息可以參考:http://webkit.org/姿骏。
渲染引擎開始于從網(wǎng)絡(luò)層獲取請(qǐng)求內(nèi)容糖声,一般是不超過8K的數(shù)據(jù)塊。接下來就是渲染引擎的基本工作流程:
圖 2:渲染引擎的基本工作流程(解析HTML構(gòu)建DOM樹分瘦,渲染樹構(gòu)建蘸泻,渲染樹布局,繪制渲染樹)嘲玫。
渲染引擎會(huì)解析HTML文檔并把標(biāo)簽轉(zhuǎn)換成內(nèi)容樹中的DOM節(jié)點(diǎn)悦施。它會(huì)解析style元素和外部文件中的樣式數(shù)據(jù)。樣式數(shù)據(jù)和HTML中的顯示控制將共同用來創(chuàng)建另一棵樹——渲染樹去团。
渲染樹包含帶有顏色抡诞,尺寸等顯示屬性的矩形。這些矩形的順序與顯示順序一致土陪。
渲染樹構(gòu)建完成后就是”布局“處理沐绒,也就是確定每個(gè)節(jié)點(diǎn)在屏幕上的確切顯示位置。 下一個(gè)步驟是繪制—— 遍歷渲染樹并用UI后端層將每一個(gè)節(jié)點(diǎn)繪制出來旺坠。
一定要理解這是一個(gè)緩慢的過程乔遮,為了更好的用戶體驗(yàn),渲染引擎會(huì)嘗試盡快的把內(nèi)容顯示出來取刃。它不會(huì)等到所有HTML都被解析完才創(chuàng)建并布局渲染樹蹋肮。它會(huì) 在處理后續(xù)內(nèi)容的同時(shí)把處理過的局部?jī)?nèi)容先展示出來。
圖 3:Webkit主要流程
圖 4:Mozilla的Gecko渲染引擎主要流程(3.6)
從圖3和圖4中可以看出璧疗,盡管Webkit與Gecko使用略微不同的術(shù)語坯辩,這個(gè)過程還是基本相同的。
Gecko 里把格式化好的可視元素稱做“幀樹”(Frame tree)崩侠。每個(gè)元素就是一個(gè)幀(frame)漆魔。 Webkit 則使用”渲染樹”這個(gè)術(shù)語,渲染樹由”渲染對(duì)象”組成。Webkit 里使用”layout”表示元素的布局改抡,Gecko則稱為”Reflow”矢炼。Webkit使用”Attachment”來連接DOM節(jié)點(diǎn)與可視化信息以構(gòu)建渲染樹。一個(gè)非語義上的小差別是Gecko在HTML與DOM樹之間有一個(gè)附加的層 阿纤,稱作”content sink”句灌,是創(chuàng)建DOM對(duì)象的工廠。我們會(huì)討論流程中的每一部分欠拾。
因?yàn)榻馕鍪卿秩疽嬷幸粋€(gè)很重要的處理胰锌,我們會(huì)講的略深入一些。讓我們從一個(gè)小的解析介紹開始藐窄。
解析一個(gè)文檔意味著把它翻譯成有意義的結(jié)構(gòu)以供代碼使用资昧。解析的結(jié)果通常是一個(gè)表征文檔的由節(jié)點(diǎn)組成的樹,稱為解析樹或句法樹荆忍。
示例——解析表達(dá)式”2 + 3 – 1″可以返回下面的樹:
圖 5:數(shù)學(xué)表達(dá)式樹節(jié)點(diǎn)
解析是基于文檔所遵循的語法規(guī)則——書寫所用的語言或格式——來進(jìn)行的格带。每一種可以解析的格式必須由確定的語法與詞匯組成。這被稱之為上下文無關(guān)語法东揣。 人類語言并非此種語言践惑,所以不能用常規(guī)的解析技術(shù)來解析腹泌。
解析器有兩個(gè)處理過程——詞法分析與句法分析嘶卧。
詞法分析負(fù)責(zé)把輸入切分成符號(hào)序列,符號(hào)是語言的詞匯——由該語言所有合法的單詞組成专甩。
句法分析是對(duì)該語言句法法則的應(yīng)用。
解析器通常把工作分給兩個(gè)組件——分詞程序負(fù)責(zé)把輸入切分成合法符號(hào)序列棺耍,解析程序負(fù)責(zé)按照句法規(guī)則分析文檔結(jié)構(gòu)和構(gòu)建句法樹。詞法分析器知道如何過濾像空格蒙袍,換行之類的無關(guān)字符害幅。
圖 6:從源文檔到解析樹(文檔,詞法分析佣赖,句法分析,解析樹)蹂午。
解析過程是交互式的。解析器通常會(huì)從詞法分析器獲取新符號(hào)并嘗試匹配句法規(guī)則晚胡。如果匹配成功,就在句法樹上創(chuàng)建相應(yīng)的節(jié)點(diǎn)遣妥,并繼續(xù)從詞法分析器獲取下一個(gè)符號(hào)谭贪。如果沒有匹配的規(guī)則慨削,解析器會(huì)內(nèi)部保存這個(gè)符號(hào),并繼續(xù)從詞法分析器獲取符號(hào)猿规,直到內(nèi)部保存的所有符號(hào)能夠成功匹配一個(gè)規(guī)則蘸拔。如果最終無法匹配,解析器會(huì)拋出異常邓萨。這意味著文檔無效,含有句法錯(cuò)誤熏兄。
多數(shù)情況下解析樹并非最終結(jié)果帽揪。解析經(jīng)常是為了從輸入文檔轉(zhuǎn)換成另外一種格式。比如編譯器要把源碼編譯成機(jī)器碼查邢,會(huì)首先解析成解析樹扶踊,再把解析樹轉(zhuǎn)換成機(jī)器碼番官。
圖 7:編譯過程(源碼,解析,解析樹懂讯,轉(zhuǎn)換,機(jī)器碼)。
在圖5中我們構(gòu)建了一個(gè)數(shù)學(xué)表達(dá)式解析樹谨读。讓我們來試著定義一個(gè)簡(jiǎn)單的數(shù)學(xué)語言并看看解析是如何進(jìn)行的。
詞匯:我們的語言可以包含整數(shù)塑径,加號(hào)和減號(hào)。
句法:
句法塊由表達(dá)式,術(shù)語及操作符組成。
我們的語言可以包含任意數(shù)量表達(dá)式烹吵。
表達(dá)式定義為術(shù)語緊跟著操作符呀酸,再跟另外一個(gè)術(shù)語窿吩。
操作符是加號(hào)或減號(hào)。
術(shù)語可以是整數(shù)或表達(dá)式携冤。
讓我們分析輸入”2 + 3 – 1″扣猫。
第一個(gè)符合規(guī)則的子字符串是”2″癌幕,根據(jù)規(guī)則#5它是一個(gè)術(shù)語。第二個(gè)匹配是”2 + 3″,符合第二條規(guī)則——一個(gè)術(shù)語緊跟一個(gè)操作符再跟另外一個(gè)術(shù)語。下一個(gè)匹配出現(xiàn)在輸入結(jié)束時(shí)“牍粒”2 + 3 – 1″是一個(gè)表達(dá)式,因?yàn)槲覀円阎?+3”是一個(gè)術(shù)語,所以符合第二條規(guī)則。 “2 + + “不會(huì)匹配任何規(guī)則,所以是無效的輸入史煎。
詞匯通常用正則表達(dá)式來表示酝枢。
比如我們的語言可以定義為:
INTEGER :0|[1-9][0-9]*
PLUS : +
MINUS: -
如你所見袍患,整型是由正則表達(dá)式定義的滞欠。
句法常用BNF格式定義妖滔,我們的語言被定義為:
expression :=? term? operation? term
operation :=? PLUS | MINUS
term := INTEGER | expression
我們說過常規(guī)解析器只能解析上下文無關(guān)語法的語言采蚀。這種語言的一個(gè)直覺的定義是它的句法可以用BNF完整的表達(dá)亥鸠。其規(guī)范定義請(qǐng)參考http://en.wikipedia.org/wiki/Context-free_grammar
解析器有兩種基本類型——自上而下解析器和自下而上解析器家妆。主觀上可以認(rèn)為自上而下的解析器從上層句法結(jié)構(gòu)開始嘗試匹配句法哨坪;自下而上的則從輸入開始彼硫,慢慢轉(zhuǎn)換成句法規(guī)則,從底層規(guī)則開始慧妄,直到上層規(guī)則全部匹配运挫。
讓我們看看這兩種解析器將怎樣解析我們的例子:
自上而下解析器從上層規(guī)則開始,它會(huì)把”2 + 3″定義為表達(dá)式翘瓮,然后定義”2 + 3 – 1″為表達(dá)式(定義表達(dá)式的過程中也會(huì)匹配其它規(guī)則每庆,但起點(diǎn)是最高級(jí)別規(guī)則)帖鸦。
自下而上的解析器會(huì)掃描輸入攻锰,直到有匹配的規(guī)則,它會(huì)把輸入替換成規(guī)則。這樣一直到輸入結(jié)束物臂。部分匹配的規(guī)則會(huì)放入解析堆棧。
StackInput
2 + 3 – 1
term+ 3 – 1
term operation3 – 1
expression– 1
expression operation1
expression
這種自下而上的解析器叫作移位歸約解析器偎巢,因?yàn)檩斎氡幌蛴乙苿?dòng)(想象一下一個(gè)指針從指向輸入開始逐漸向右移動(dòng)) 并逐漸歸約到句法樹。
有一些工具可以為你創(chuàng)建解析器傲绣,它們通常稱為解析器生成器彪杉。你只需要提供語法——詞匯與句法規(guī)則——它就能生成一個(gè)可以工作的解析器渴丸。創(chuàng)建解析器需要對(duì)解析器有深入的了解,并且手動(dòng)創(chuàng)建一個(gè)優(yōu)化的解析器并不容易枢析,所以解析器生成工具很有用啊易。
HTML解析器的工作是解析HTML標(biāo)記到解析樹。
HTML的詞匯與句法定義在w3c組織創(chuàng)建的規(guī)范中亥至。當(dāng)前版本是HTML4衣吠,HTML5的工作正在進(jìn)行中惊搏。
在對(duì)解析器的介紹中看到向拆,語法可以用類似BNF的格式規(guī)范地定義浓恳。不幸的是所有常規(guī)解析器的討論都不適用于HTML(我提及它們并不是為了娛樂,它們可以用于解析CSS和JavaScript)厨疙。HTML無法用解析器所需的上下文無關(guān)的語法來定義梗醇。過去HTML格式規(guī)范由DTD (Document Type Definition)來定義叙谨,但它不是一個(gè)上下文無關(guān)語法。
HTML與XML相當(dāng)接近。XML有許多可用的解析器统捶。HTML還有一個(gè)XML變種叫XHTML,那么它們主要區(qū)別在哪里呢什黑?區(qū)別在于HTML應(yīng)用更加”寬容”凯力,它容許你漏掉一些開始或結(jié)束標(biāo)簽等。它整個(gè)是一個(gè)“軟”句法祈惶,不像XML那樣嚴(yán)格死板。 總的來說這一看似細(xì)微的差別造成了兩個(gè)不同的世界。一方面這使得HTML很流行可款,因?yàn)樗菽愕腻e(cuò)誤,使網(wǎng)頁(yè)作者的生活變得輕松摸恍。另一方面,它使編寫語法格式變得困難。所以綜合來說,HTML解析并不簡(jiǎn)單恼琼,現(xiàn)成的上下文相關(guān)解析器搞不定,XML解析器也不行颤难。
HTML的定義使用DTD文件。這種格式用來定義SGML族語言栅屏,它包含對(duì)所有允許的元素的定義,包括它們的屬性和層級(jí)關(guān)系。如我們前面所說蛀骇,HTML DTD構(gòu)不成上下文無關(guān)語法。
DTD有幾種不同類型岛马。嚴(yán)格模式完全尊守規(guī)范伞矩,但其它模式為了向前兼容可能包含對(duì)早期瀏覽器所用標(biāo)簽的支持。當(dāng)前的嚴(yán)格模式DTD:http://www.w3.org/TR/html4/strict.dtd
解析器輸出的樹是由DOM元素和屬性節(jié)點(diǎn)組成的湿诊。DOM的全稱為:Document Object Model。它是HTML文檔的對(duì)象化描述错沽,也是HTML元素與外界(如Javascript)的接口。
DOM與標(biāo)簽幾乎有著一一對(duì)應(yīng)的關(guān)系,如下面的標(biāo)簽
<html>
<body>
<p>Hello World.</p>
<div><img src="example.png"></div>
</body>
會(huì)被轉(zhuǎn)換成如的DOM樹:
Figure 8: DOM tree of the example markup
與HTML一樣,DOM規(guī)范也由w3c組織制訂。參考:http://www.w3.org/DOM/DOMTR. 這是一個(gè)操作文檔的通用規(guī)范织堂。有一個(gè)專門的模塊定義HTML特有元素:http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html.
當(dāng)我們說樹中包含DOM節(jié)點(diǎn)時(shí),意思就是這個(gè)樹是由實(shí)現(xiàn)了DOM接口的元素組成。這些實(shí)現(xiàn)包含了其它一些瀏覽器內(nèi)部所需的屬性。
如我們前面看到的肖爵,HTML無法使用自上而下或自下而上的解析器來解析。
理由如下:
語言的寬容特點(diǎn)
瀏覽器需要對(duì)無效HTML提供容錯(cuò)性的事實(shí)。
解析過程的反復(fù)。通常解析過程中源碼不會(huì)變化葛超。但在HTML中答渔,script標(biāo)簽包含”document.write”時(shí)可以添加內(nèi)容,即解析過程實(shí)際上還會(huì)改變?cè)创a。
瀏覽器創(chuàng)建了自己的解析器來解析HTML文檔笼沥。
HTML5規(guī)范里對(duì)解析算法有具體的說明诗良,解析由兩部分組成:分詞與構(gòu)建樹舞骆。
分詞屬于詞法分析部分,它把輸入解析成符號(hào)序列绪穆。在HTML中符號(hào)就是開始標(biāo)簽菠红,結(jié)束標(biāo)簽,屬性名稱和屬生值键袱。
分詞器識(shí)別這些符號(hào)并將其送入樹構(gòu)建者,然后繼續(xù)分析處理下一個(gè)符號(hào),直到輸入結(jié)束俊抵。
圖 6: HTML解析流程 (源自HTML5規(guī)范)
算法的輸出是HTML符號(hào)烛缔。算法可以用狀態(tài)機(jī)來描述。 每一個(gè)狀態(tài)從輸入流中消費(fèi)一個(gè)或多個(gè)字符喷舀,并根據(jù)它們更新下一狀態(tài)樊卓。決策受當(dāng)前符號(hào)狀態(tài)和樹的構(gòu)建狀態(tài)影響浇辜。這意味著同樣的字符可能會(huì)產(chǎn)生不同的結(jié)果,取決于當(dāng)前的狀態(tài)。算法太復(fù)雜绪囱,我們用一個(gè)例子來看看它的原理。
基礎(chǔ)示例,分析下面的標(biāo)簽:
Hello world
初始狀態(tài)是”Data state”粹排,當(dāng)遇到”<“時(shí)狀態(tài)改為“Tag open state”妙同。吃掉”a-z”字符組成的符號(hào)后產(chǎn)生了”Start tag token”,狀態(tài)變更為“Tag name state”粥帚。我們一直保持此狀態(tài)胰耗,直到遇到”>”。每個(gè)字符都被追加到新的符號(hào)名上芒涡。在我們的例子中,解出的符號(hào)就是”html”费尽。
當(dāng)碰到”>”時(shí)赠群,當(dāng)前符號(hào)完成,狀態(tài)改回“Data state”旱幼〔槊瑁””標(biāo)簽將會(huì)以同樣的方式處理。現(xiàn)在”html”與”body”標(biāo)簽都完成了柏卤,我們回到“Data state”狀態(tài)叹誉。吃掉”H”(”Hello world”第一個(gè)字母)時(shí)會(huì)產(chǎn)生一個(gè)字符符號(hào),直到碰到””的”<“符號(hào)闷旧,我們就完成了一個(gè)字符符號(hào)”Hello world”。
現(xiàn)在我們回到“Tag open state”狀態(tài)钧唐。吃掉下一個(gè)輸入”/”時(shí)會(huì)產(chǎn)生一個(gè)”end tag token”并變更為“Tag name state”狀態(tài)忙灼。同樣,此狀態(tài)保持到我們碰到”>”時(shí)。這時(shí)新標(biāo)簽符號(hào)完成该园,我們又回到“Data state”酸舍。同樣””也會(huì)被這樣處理。
圖 9: 示例輸入源的分詞處理
當(dāng)解析器被創(chuàng)建時(shí)里初,文檔對(duì)象也被創(chuàng)建了啃勉。在樹的構(gòu)建過程中DOM樹的根節(jié)點(diǎn)(Documen)將被修改,元素被添加到上面去双妨。每個(gè)分詞器完成的節(jié)點(diǎn)都會(huì)被樹構(gòu)建器處理淮阐。規(guī)范中定義了每一個(gè)符號(hào)與哪個(gè)DOM對(duì)象相關(guān)。除了把元素添加到DOM樹外刁品,它還會(huì)被添加到一個(gè)開放元素堆棧泣特。這個(gè)堆棧用于糾正嵌套錯(cuò)誤和標(biāo)簽未關(guān)閉錯(cuò)誤。這個(gè)算法也用狀態(tài)機(jī)描述挑随,它的狀態(tài)叫做”insertion modes”状您。
讓我們看看下面輸入的樹構(gòu)建過程:
Hello world
樹的構(gòu)建過程中,輸入就是分詞過程中得到的符號(hào)序列兜挨。第一個(gè)模式叫“initial mode”膏孟。接收 html 符號(hào)后會(huì)變成“before html”模式并重新處理此模式中的符號(hào)。這會(huì)創(chuàng)建一個(gè)HTMLHtmlElement元素并追加到根文檔節(jié)點(diǎn)拌汇。
然后狀態(tài)改變?yōu)?b>“before head”柒桑。我們收到”body”時(shí),會(huì)隱式創(chuàng)建一個(gè)HTMLHeadElement担猛,盡管我們沒有這個(gè)標(biāo)簽幕垦,它也會(huì)被創(chuàng)建并添加到樹中。
現(xiàn)在我們進(jìn)入“in head”模式傅联,然后是“after head”先改,Body會(huì)被重新處理,創(chuàng)建HTMLBodyElement元素并插入蒸走,然后進(jìn)入“in body”模式仇奶。
字符符號(hào)”Hello world”收到后會(huì)創(chuàng)建一個(gè)”Text”節(jié)點(diǎn),所有字符都被一一追加到上面比驻。
收到body結(jié)束標(biāo)簽后進(jìn)入“after body”模式该溯,收到html結(jié)束標(biāo)簽后進(jìn)入“after after body”模式。所有符號(hào)處理完后將終止解析别惦。
圖 10: 示例HTML樹的構(gòu)建
在這一階段瀏覽器會(huì)把文檔標(biāo)記為交互模式狈茉,并開始解析deferred模式的script〉УВ”deferred”意味著腳本應(yīng)該在文檔解析完成后執(zhí)行氯庆。腳本處理完成后將進(jìn)入”complete”狀態(tài)蹭秋,”load”事件發(fā)生。
HTML5規(guī)范中包含了完整的算法:http://www.w3.org/TR/html5/syntax.html#html-parser
你永遠(yuǎn)不會(huì)看到HTML頁(yè)面語法錯(cuò)誤堤撵。瀏覽器會(huì)修正錯(cuò)誤并繼續(xù)仁讨。看看下面的例子:
Really lousy HTML
我一定違背了幾百萬條規(guī)則(”my tag”是非法標(biāo)簽实昨,”p”與”div”元素嵌套錯(cuò)誤等等)洞豁,但瀏覽器仍然正確地顯示,沒有任何抱怨荒给。所以很多解析器代碼在修正這些HTML作者的錯(cuò)誤丈挟。
瀏覽器的錯(cuò)誤處理相當(dāng)統(tǒng)一,驚人的是這并不是當(dāng)前HTML規(guī)范的一部分锐墙,就像書簽礁哄、前進(jìn)、后退溪北,只是多年以來在瀏覽器中開發(fā)出來的桐绒。有些無效的HTML結(jié)構(gòu)出現(xiàn)在許多網(wǎng)站,瀏覽器會(huì)嘗試用和其它各種瀏覽器一致的方式修復(fù)這些錯(cuò)誤之拨。
HTML5規(guī)范中應(yīng)這一需求定義了一些東西茉继,Webkit在它的HTML解析器類開頭的注釋中很好的做了摘要:
解析器分析輸入符號(hào)生成文檔,并構(gòu)建文檔樹蚀乔。如果文檔格式良好烁竭,解析工作會(huì)很簡(jiǎn)單。
不幸的是吉挣,我們要處理很多格式不良的HTML文檔派撕,解析器需要寬容這些錯(cuò)誤。
我們至少需要照顧下列錯(cuò)誤:
1. 元素必需被插入在正確的位置睬魂。未關(guān)閉的標(biāo)簽應(yīng)該一一關(guān)閉终吼,直到可以添加新元素。
2. 不允許直接添加元素氯哮。用戶可能會(huì)漏掉一些標(biāo)簽际跪,比如:HTML HEAD BODY TBODY TR TD LI(我遺漏了什么?)喉钢。
3. 在inline元素里添加block元素時(shí)姆打,應(yīng)關(guān)閉所有inline元素,再添加block元素肠虽。
4. 如果以上不起作用幔戏,關(guān)閉所有元素,直到可以添加税课,或者忽略此標(biāo)簽评抚。
讓我們來看一些Webkit容錯(cuò)的例子:
使用
代替
有些站點(diǎn)使用
而不是
豹缀。為了更好的與IE和Firefox兼容,Webkit將其視為
慨代。代碼如下:
if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
reportError(MalformedBRError);
t->beginTag = true;
}
注意,這里的錯(cuò)誤處理是內(nèi)部的啸如,并不會(huì)顯示給用戶侍匙。
迷失的表格
像下面的例子這樣,一個(gè)表格包含在另外一個(gè)表格的內(nèi)容中叮雳,但不是在外部表格的單元格里:
inner table
outer table
Webkit會(huì)改變層級(jí)關(guān)系想暗,把它們處理成兩個(gè)相臨的表格:
outer table
inner table
代碼:
if (m_inStrayTableContent && localName == tableTag)
popBlock(tableTag);
Webkit用一個(gè)堆棧保存當(dāng)前元素,它會(huì)把里面的表格彈出到外部表格堆棧帘不,使它們成為兄弟表格说莫。
元素嵌套
為防止一表單的嵌套,第二個(gè)表單會(huì)被忽略寞焙。代碼:
if (!m_currentFormElement) {
m_currentFormElement = new HTMLFormElement(formTag,? ? m_document);
}
過深的元素層級(jí)
注釋不言自喻:
www.liceo.edu.mx是一個(gè)層級(jí)過深的典型储狭,它用大量的嵌套到1500個(gè)標(biāo)簽的深度。我們只允許同一標(biāo)簽連續(xù)出現(xiàn)20次捣郊,超過的話辽狈,所有此標(biāo)簽都會(huì)被忽略。
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;
}
錯(cuò)誤的html或body結(jié)束標(biāo)簽位置
注釋仍然很明了:
支持真正的錯(cuò)誤html
我們永遠(yuǎn)不關(guān)閉tag呛牲,因?yàn)橛行┯薮赖木W(wǎng)頁(yè)在文檔真正結(jié)束之前就關(guān)閉了它刮萌。
讓我們用end()來關(guān)閉標(biāo)簽。
if (t->tagName == htmlTag || t->tagName == bodyTag )
return;
所以網(wǎng)頁(yè)作者們小心了娘扩,除非你想寫一個(gè)Webkit容錯(cuò)的示例代碼着茸,否則請(qǐng)按正確格式書寫HTML。