一 综述
上一篇分析从地址栏输入网址,Application --> content-->blink-->Network stack,到调用网络库从网络上进行资源下载,这篇将分析从resource得到资源后,再到如何建立DOM Tree的完整过程。
在上一篇“资源下载”中进分析资源下载的整个流程,并未涉及到页面的相关结构,一个页面blink对外部(content)是WebView,而对内部是Page,每个Page都会有Main Frame,也可以存在零个或多个Sub Frame,每个Frame都有自己的Document,Document中包含各种各样的资源(resource),它的结构如图1所示。
图1 页面结构
因此,我们再前一篇看到,在资源下载过程中,是依次创建或调用FrameLoader,DocumentLoader,还有ResourceLoader。
在DocumentLoader的startLoadingMainResource()中,会执行m_mainResource = m_fetcher->fetchMainResource(cachedResourceRequest, m_substituteData);
开始主文档资源下载,接着执行m_mainResource->addClient(this);m_mainResource 就是RawResource。这里的this 就是指DocumentLoader, DocumentLoader是继承于RawResourceClient的子类,当RawResource从网络上获取到数据后,会调用RawResourceClinet->dataReceived(this, data, length);
这里就会走到DocumentLoader中的dataReceived(),接着调用到commitData(data, length),
commitData()中做了两件很重要的事情,
#1 创建DocumentWriter:ensureWriter(m_response.mimeType());
#2 向DocumentWriter中添加数据:m_writer->addData(bytes, length);
它会把这些数据给DocumentParser处理,调用的是m_parser->appendBytes(bytes, length);
ensureWriter()继续会调用DocumentLoader::createWriterFor(),这里会根据mimeType创建具体哪些文档类型的Document,最常见的Document类型是XML(XHTML)和HTML。
在createWriterFor()有个重要的参数ParserSynchronizationPolicy,创建DocumentWriter后,DocumentWriter会继续创建DocumentParser,这是一个基类,它会根据解析不同类型文档
,创建不同的DocumentParser,因为这里是HTML文档,所以它创建的DocumentParser是HTMLDocumentParser。创建DOMTree是HTMLDocumentParser的主要工作。
下面来看看DOMTree是什么样的,假如有如下图图2是一段非常简单的html页面:
图2 测试页面
有了html页面,下来再blink添加一些简单的接口(dumpDOMTree)就可以将其DOMTree打印出来,如果为了方便调试,可以将这些接口(dumpDOMTree)添加到Document上,这样可以在chrome://inspect的远程调试中调用,结果如下图3所示。
图3 测试页面的DOM Tree
其实,图3中的DOMTree结构已经非常清晰了,根节点是document,它的子节点HTML,HTML有两个子节点分别是HEAD,BODY;BODY有两个子节点都是DIV,相同缩进的节点在tree结构中属于同一层。如果把图3内容有一颗树来表示是这样的,实际上,html页面与其DOMTree是一一完全对应的,如图4所示。
图4 DOM Tree
下面来看看具体的代码流程,在HTMLDocumentParser中,有
OwnPtr<HTMLToken> m_token;
OwnPtr<HTMLTokenizer> m_tokenizer;
OwnPtrWillBeMember<HTMLTreeBuilder> m_treeBuilder;
主要是用来创建DOMTree,还有
OwnPtr<HTMLParserScheduler> m_parserScheduler;
WeakPtrFactory<HTMLDocumentParser> m_weakFactory;
WeakPtr<BackgroundHTMLParser> m_backgroundParser;
这里会创建一个后台线程m_backgroundParser,用来处理一些解析任务。主要有两个任务:
#1 把网络上获取的资源一段段数据解码后变成tokens;这个是在BackgroundHTMLParser::pumpTokenizer()
中进行处理,把当前获取的一段数据处理完,就把处理好的tokens通过sendTokensToMainThread()发送到主线程,继续处理;
#2 sendTokensToMainThread()会把数据打包到ParsedChunk中,然后
发给主线程HTMLDocumentParser::processParsedChunkFromBackgroundParser(PassOwnPtr<ParsedChunk> popChunk)进行后续处理。
现在主要看HTMLDocumentParser::processParsedChunkFromBackgroundParser(PassOwnPtr<ParsedChunk> popChunk),每次它都会解析一段数据,
popChunk中包含一个tokens的列表,做一个循环,对每一个token进行解析:
for (Vector<CompactHTMLToken>::const_iterator it = tokens->begin(); it != tokens->end(); ++it) {
......
constructTreeFromCompactHTMLToken(*it);
.....
}
constructTreeFromCompactHTMLToken()
--->HTMLTreeBuilder::constructTree():m_treeBuilder->constructTree(&token);
---->HTMLTreeBuilder::constructTree(AtomicHTMLToken* token);
---> HTMLTreeBuilder::processToken(AtomicHTMLToken* token):
processToken()函数内部结构非常清晰:
396 void HTMLTreeBuilder::processToken(AtomicHTMLToken* token)
397 {
398 if (token->type() == HTMLToken::Character) {
399 processCharacter(token);
400 return;
401 }
402
403 // Any non-character token needs to cause us to flush any pending text immediately.
404 // NOTE: flush() can cause any queued tasks to execute, possibly re-entering the parser.
405 m_tree.flush(FlushAlways);
406 m_shouldSkipLeadingNewline = false;
407
408 switch (token->type()) {
409 case HTMLToken::Uninitialized:
410 case HTMLToken::Character:
411 ASSERT_NOT_REACHED();
412 break;
413 case HTMLToken::DOCTYPE:
414 processDoctypeToken(token);
415 break;
416 case HTMLToken::StartTag:
417 processStartTag(token);
418 break;
419 case HTMLToken::EndTag:
420 processEndTag(token);
421 break;
422 case HTMLToken::Comment:
423 processComment(token);
424 break;
425 case HTMLToken::EndOfFile:
426 processEndOfFile(token);
427 break;
428 }
429 }
版权声明:本文为博主原创文章,未经博主允许不得转载。
参考文献:
2 http://www.w3.org/TR/DOM-Level-3-Core/core.html
3 http://wenku.baidu.com/view/7fa3ad6e58fafab069dc02b8.html