今天关注webkit中dom树是怎么构建的,HTML是怎么分析的。
1、dom相关的代码在webcore中dom目录下,有很多类,比较重要的是
Document.h、DocumentFragment.h、DocumentParser.h
DocumentParser是一个基类感觉好像没干什么事,只是传入了一个开始parse的状态,在DocumentFragment中有parseHTML,好像开始了真正的parse操作;
2、HTML的parse操作,从DocumentFragment中的parseHTML开始,相关的parser放置在WebCore/html/parser下,具体是由HTMLDocumentParser来完成的。
DocumentFragment::parseHTML()
-->HTMLDocumentParser::parseDocumentFragment()
-->HTMLDocuemntParser::insert()
{
设置好输入流
调用pumpTokenlizerIfPossible准备建树
-->HTMLDocumentParser::pumpTokenlizerIfPossible()
-->HTMLDocumentParser::pumpTokenlizer()
{
while() {
HTMLTokenizer::nextToken(stream, HTMLToken::m_token)
HTMLTreeBuilder::constructTreeFromToken(m_token)
{
将HTMLToken转成AtomicHTMLToken;
再调用建树函数constructTreeFromAtomicToken(m_token);
-->HTMLTreeBuilder::constructTreeFromAtomicToken()
{
根据token的类型分别调用:
HTMLTreeBuilder::ASSERT_NOT_REACHED()
HTMLTreeBuilder::processDoctypeToken()
HTMLTreeBuilder::processStartTag()
\ HTMLTreeBuilder::processEndTag()
HTMLTreeBuilder::processComment()
HTMLTreeBuilder::processCharacter()
HTMLTreeBuilder::processEndOfFile()
完成树的构建
}
}
}
}
}
整个Dom树的构建过程非常清晰,其中一些比较重要的类,包括:HTMLDocumentParser完成parse的整体流程,协调HTMLInputStream、HTMLTokenlizer、HTMLTreeBuilder的工作,其中:
1)HTMLInputStream负责HTML数据的输入;
2)HTMLTokenlizer根据输入的数据流把HTML进行tokenlize;
3)HTMLTreeBuilder根据输入的token进行建树、完成树的构建;
HTMLTokenlizer的实现就是一个大的状态机,根据input的数据,不断变换着内部的状态,完成tokenlization的过程,吐出一个个的token。
树的构建过程是通过HTMLTreeBuilder和HTMLContructionSite等类来完成的,Dom树的构建过程是比较复杂的,但原理是比较清晰的,根据HTML文件的嵌套顺序,通过一个栈(HTMLElementStack m_openElements)来保存当前打开的节点标签。假设一段HTML的数据如下:
<div>
<p>
<a>
</a>
</p>
<br>
</br>
</div>
我们知道div节点有两个子节点,p和br,而p节点又有一个子节点a,具体处理过程简单描述如下:
假设当前栈为空;
首先处理<div>,遇到<div>时,调用processStartTag(),m_openElements中首先压入div;
继续处理,遇到<p>标签,调用processStartTag(),此时栈顶的div就是p节点的父节点,建立好p和div的连接,在加入节点p;
继续处理,遇到<a>标签,调用processStartTag(),此时栈定节点是p,当前节点是a,那么a的父节点是p,建立好父子关系,并将a压入栈;
继续处理,遇到</a>标签,调用processEndTag(),弹出栈定元素a(代码中使用“弹出直到标签a”一提高容错性,因为有可能会有没封闭的节点,导致栈混乱,使用弹出知道标签xxx,可以将没有封闭的标签带来的问题,限制在局部范围内,防止扩散);
继续处理,遇到</p>标签,调用processEndTag(),弹出p
继续处理,遇到<br>标签,调用processStartTag(),此时栈顶的div就是br节点的父节点,建立好br和div的连接,在加入节点br;
继续处理,遇到</br>标签,调用processEndTag(),弹出br
继续处理,遇到</div>标签,调用processEndTag(),弹出div
DOM树建立完毕。
原理虽然如此,但因为浏览器为了用户的方便,允许网页中有各种的错误,浏览器必须能够尽可能的显示出用户的数据,这就给DOM树的构建增加了很多复杂的处理,具体来说,DOM的数构建是由一个状态机来控制和容错的,比如在processStartTag函数中,根据当前构建过程的状态和获得的HTML token来决定下一步的动作,构建过程的状态包括:InitialMode, BeforeHTMLMode, BeforeHeadMode, InHeadMode, AfterHeadMode, InBodyMode, InTableMode, InCapationMode, InColumnGroupMode,...等等,如何根据这些状态和输入的token执行动作,在参考文献[1]中有全面的描述。经过这样的过程Dom树应就算建立完成了。
参考文献:
[1]http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-inbody