转载,请标注: from 你吧吧
当主资源load下来,我们来看看chromium如何去解析主资源,构建DOM树的。
整个DOM的创建过程比较复杂,这里简单分析下,如何将网络中download的资源,进行DOM树的解析构建过程的。
当主资源加载完,准确的讲,在主资源还没加载完,但是sandbox process已经收到从 browser process的部分主资源内容的时候,就开始了DOM树的构建。
我们这里从“sandbox process已经收到从 browser process的部分主资源内容”,这个时刻开始说起。
1. ResourceLoader.cpp文件的方法:ResourceLoader::didReceiveData,这是sandbox process收到了部分主资源
该方法中有代码:m_resource->appendData(data, length);
2. 上代码执行的是文件:RawResource.cpp中方法:RawResource::appendData
该方法中有代码:
while (RawResourceClient* c = w.next())
c->dataReceived(this, data, length);
3. 这里会调用DocumentLoader.cpp文件中方法:DocumentLoader::dataReceived
该方法中有代码:commitData(data, length);
执行的是同文件中方法:DocumentLoader::commitData
4. 上面提到的方法中有代码:m_writer->addData(bytes, length);
执行的是文件DocumentWriter.cpp文件中方法:DocumentWriter::addData
该方法中有代码:m_parser->appendBytes(bytes, length);
执行的是文件HTMLDocumentParser.cpp中方法:HTMLDocumentParser::appendBytes
从这个方法开始,则开始了复杂的Dom树的解析过程。
总体上说,从网络中读到的主资源以字符串的形式存在内存中,chromium内核(或者说blink内核)
如何将这些字符串流分析成一个个html 的标签呢?我们看下面的逻辑分析。
5. 在方法:HTMLDocumentParser::appendBytes中有代码:
HTMLParserThread::shared()->postTask(bind(&BackgroundHTMLParser::appendRawBytesFromMainThread, m_backgroundParser, buffer.release()));
这里执行的调用文件
BackgroundHTMLParser.cpp中方法:BackgroundHTMLParser::appendRawBytesFromMainThread
6. 该方法又会调用同文件中方法:BackgroundHTMLParser::updateDocument。该方法中有代码:
appendDecodedBytes(decodedData);
该代码调用的是同文件种方法:BackgroundHTMLParser::appendDecodedBytes
我们看看该方法:
void BackgroundHTMLParser::appendDecodedBytes(const String& input) { ASSERT(!m_input.current().isClosed()); m_input.append(input); pumpTokenizer(); }
这里会将多个主资源部分 都保存在变量 m_input中。
并调用同文件中方法:BackgroundHTMLParser::pumpTokenizer()
7. 上面的这个方法比较关键,贴出源码看看
void BackgroundHTMLParser::pumpTokenizer() { // No need to start speculating until the main thread has almost caught up. if (m_input.totalCheckpointTokenCount() > outstandingTokenLimit) return; while (true) { m_sourceTracker.start(m_input.current(), m_tokenizer.get(), *m_token); if (!m_tokenizer->nextToken(m_input.current(), *m_token)) { WTF_LOG(Vnbo,"vnbo BackgroundHTMLParser::pumpTokenizer() 1"); // We've reached the end of our current input. sendTokensToMainThread(); break; } m_sourceTracker.end(m_input.current(), m_tokenizer.get(), *m_token); { TextPosition position = TextPosition(m_input.current().currentLine(), m_input.current().currentColumn()); if (OwnPtr<XSSInfo> xssInfo = m_xssAuditor->filterToken(FilterTokenRequest(*m_token, m_sourceTracker, m_tokenizer->shouldAllowCDATA()))) { xssInfo->m_textPosition = position; m_pendingXSSInfos.append(xssInfo.release()); } CompactHTMLToken token(m_token.get(), TextPosition(m_input.current().currentLine(), m_input.current().currentColumn())); m_preloadScanner->scan(token, m_input.current(), m_pendingPreloads); m_pendingTokens->append(token); } m_token->clear(); if (!m_treeBuilderSimulator.simulate(m_pendingTokens->last(), m_tokenizer.get()) || m_pendingTokens->size() >= pendingTokenLimit) { sendTokensToMainThread(); // If we're far ahead of the main thread, yield for a bit to avoid consuming too much memory. if (m_input.totalCheckpointTokenCount() > outstandingTokenLimit) break; } } }
我们看看这个true循环中的逻辑。
在true循环中,会一直调用:HTMLSourceTracker.cpp中方法:HTMLSourceTracker::start和HTMLSourceTracker::end以及文件HTMLTokenizer.cpp中方法:HTMLTokenizer::nextToken
其大体逻辑是如此:
1). 从输入的字符串中依次读取,当读到<字符和>字符的时候,内核认为这两个符号之间的内容是一个自然字符串段。并记载下解析到的位置(文件HTMLToken.h中方法:setBaseOffset)。下次读取的时候,再从输入字符串的该位置读取。
2). 遇到<字符,如果该字符后面没有/字符,则认为是一个新的标签开始;如果<字符后面有/字符,则认为与之前的<字符之间的内容是一个标签。
3). 标签之间存在父子关系。
网上有个blog:http://blog.csdn.net/bertzhang/article/details/6695804 对这一点有较好的讲解。
4). 在主资源的解析过程中,遇到标签如 <script>、<img>标签等等,则会异步加载子资源。
8. true循环中还会CompactHTMLToken对象,然后将所有CompactHTMLToken对象保存到m_pendingTokens变量中。
当所有标签都打上token之后,会调用同文件中方法:BackgroundHTMLParser::sendTokensToMainThread()
并且跳出true循环。
9. 上面提到的方法,会调用文件:HTMLDocumentParser.cpp中方法:
HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser
10. 上面方法会调用同文件中方法:HTMLDocumentParser::pumpPendingSpeculations()
该方法会调用同文件中方法:HTMLDocumentParser::processParsedChunkFromBackgroundParser
11. 上面方法会调用同文件种方法:HTMLDocumentParser::constructTreeFromCompactHTMLToken
在这个方法中有代码:m_treeBuilder->constructTree(&token);