将一个网页解析成一个一个的标签,并且对应去具体的类去处理这些标签内容应该也是webkit的核心功能之一了。
现在准备对这部分的内容进行一个详细的分析并记录。
首先在HTMLDocumentParser这个类里面有几个数据成员:
HTMLInputStream m_input;
// We hold m_token here because it might be partially complete.
HTMLToken m_token;
OwnPtr<HTMLTokenizer> m_tokenizer;
OwnPtr<HTMLScriptRunner> m_scriptRunner;
OwnPtr<HTMLTreeBuilder> m_treeBuilder;
OwnPtr<HTMLPreloadScanner> m_preloadScanner;
OwnPtr<HTMLParserScheduler> m_parserScheduler;
HTMLSourceTracker m_sourceTracker;
HTMLConstructionSite& m_tree;
m_input 经过前面的分析应该已经很熟悉了,将从Server上下载下来的数据流经过编解码放到了m_input里面。
m_token 这个变量的具体作用是什么呢?
看这个之前应该先明白m_tokenizer的作用,Tokenizer的输入是一个字符串的输入流,输出是一个一个的token。在解析出一个完整的token时,会将该token发出,并通过发出的token来构建dom树.
而m_token的作用就是标识输出的Token,它是由HTMLTokenizer生成,交给HTMLTreeBuilder使用。
m_tokenizer 的作用刚才也解释了,从它HTMLTokenizer的类型就可以看出这个变量代表的就是一个解析器。
m_treeBuilder 的作用就是刚才解析出来的token去构建dom树的实例,并且对其语义进行检查。
m_preloadScanner 的作用是负责查找和加载子资源的对象,它通过扫描节点中的“src”,“link”等属性,找到外部链接资源后,通过CachedResourceLoader进行预加载.
m_tree HTMLConstructionSite类负责创建HTML元素,并最终完成DOM树组装的函数。例如:将<img>对象生成一个HTMLImageElement对象。通过HTMLElementFactory来实现一些其他标签的创建。
所以在m_tokenizer->nextToken(m_input.current(), m_token)里面我们可以明白使用HTMLTokenizer中的nextToken方法进行处理,传入的两个参数分别为当前的数据流的输入,以及Token。
将字符流转化为HTMLToken对象。
然后转换后的m_token对象将会m_treeBuilder->constructTreeFromToken(m_token),将对象转交给了HTMLTreeBuilder去进行处理。
在TreeBuilder这边,会先去分析标签是不是期望的(HTMLTreeBuilder::processToken)
在processToken里面,如果是我们现在知道的标签的内容的话,则会去调用相应的处理函数。
比如: 如果是HTMLToken::StartTag的话,就去调用processStartTag(token);
还是以processStartTag中的StartTag举个例子。
在判断为是StartTag的话,就去调用processStartTag。 这个函数的原型为:
HTMLTreeBuilder::processStartTag(AtomicHTMLToken& token)
看一下函数的具体实现:
1. 首先会去运行insertionMode()去得到m_insertionMode,而m_insertionMode是什么呢?
这是一个枚举的对象,原型为:
enum InsertionMode {
InitialMode,
BeforeHTMLMode,
BeforeHeadMode,
InHeadMode,
InHeadNoscriptMode,
AfterHeadMode,
InBodyMode,
TextMode,
InTableMode,
InTableTextMode,
InCaptionMode,
InColumnGroupMode,
InTableBodyMode,
InRowMode,
InCellMode,
InSelectMode,
InSelectInTableMode,
InForeignContentMode,
AfterBodyMode,
InFramesetMode,
AfterFramesetMode,
AfterAfterBodyMode,
AfterAfterFramesetMode,
};
这个是来源当前所处的状态。
2. 每一个状态都对应了一些处理的操作。然后会通过HTMLConstructionSite的对象m_tree去进行将标签插入到HTMLConstructionSite中的操作。
比如: m_tree.insertHTMLHtmlStartTagInBody(token); m_tree.insertHTMLElement(token); m_tree.insertSelfClosingHTMLElement(token); 等。。
在插入到HTMLConstructionSite的过程中是怎么操作的呢?
随便举个例子:
void HTMLConstructionSite::insertHTMLElement(AtomicHTMLToken& token)
{
m_openElements.push(attachToCurrent(createHTMLElement(token)));
}
首先会去创建HTML的element,RefPtr<Element> element = HTMLElementFactory::createHTMLElement(tagName, currentNode()->document(), form(), true); 可能HTMLElementFactory这个类是找不到的。
这个类是在编译过程中生成的,具体位置在target/product/generic/obj/STATIC_LIBRARIES/libwebcore_intermediates/Source/WebCore/HTMLElementFactory.cpp
从类里面看一下具体的过程:
PassRefPtr<HTMLElement> HTMLElementFactory::createHTMLElement(const QualifiedName& qName, Document* document, HTMLFormElement* formElement, bool createdByParser)
{
if (!document)
return 0;
if (!gFunctionMap)
createFunctionMap();
if (ConstructorFunction function = gFunctionMap->get(qName.localName().impl()))
return function(qName, document, formElement, createdByParser);
return HTMLElement::create(qName, document);
}
好了,我们可以看到最终的创建的操作了HTMLElement::create.
而createFunctionMap()这个函数就是根据不同的Tag,用来创建当前的map。
在void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode)函数中,除了这个while循环以外,还有一部分也经常被人提到。
if (isWaitingForScripts()) {
ASSERT(m_tokenizer->state() == HTMLTokenizer::DataState);
if (!m_preloadScanner) {
m_preloadScanner.set(new HTMLPreloadScanner(document()));
m_preloadScanner->appendToEnd(m_input.current());
}
m_preloadScanner->scan();
}
这边又涉及到了我们刚才看到的一个变量,m_preloadScanner。
具体的实现是怎么实现的呢?
首先是isWaitingForScripts()的操作,函数返回的是HTMLTreeBuilder类的m_isPaused对象。这个参数使用到的情况好像很少。
首先是在初始话的时候这个函数被初始化为false,然后只有在一种情况下被置为了true. 当(token.name() == scriptTag)的时候,m_isPaused = true
在源码中有一句注释,Pause ourselves so that parsing stops until the script can be processed by the caller.
理解的大概意思就是,当我们的脚本被 调用的时候再开始解析。
这个时候,如果m_preloadScanner是null的时候,我们去重新new这个对象并对其完成初始化。
如果m_preloadScanner对象有值的时候,就会调用scan函数去进行操作。
函数原型为:
void HTMLPreloadScanner::scan()
{
// FIXME: We should save and re-use these tokens in HTMLDocumentParser if
// the pending script doesn't end up calling document.write.
while (m_tokenizer->nextToken(m_source, m_token)) {
processToken();
m_token.clear();
}
}
注意的是这边也有一个processToken,但是这个和刚才的是不一样的。这是HTMLPreloadScanner类中的处理函数。
这边会先生成一个Preload的Task: PreloadTask task(m_token);
需要注意的是在PreloadTask的构造函数里面会有一个processAttributes(token.attributes())操作。主要是对scriptTag,inputTag,linkTag, scriptTag标签进行属性的解析。
解析完了之后会通过setUrlToLoad去设置m_urlToLoad。
然后会调用它的perload函数。task.preload(m_document, scanningBody());
preload中的操作其实很简单:
void preload(Document* document, bool scanningBody)
{
if (m_urlToLoad.isEmpty())
return;
CachedResourceLoader* cachedResourceLoader = document->cachedResourceLoader();
if (m_tagName == scriptTag)
cachedResourceLoader->preload(CachedResource::Script, m_urlToLoad, m_charset, scanningBody);
else if (m_tagName == imgTag || (m_tagName == inputTag && m_inputIsImage))
cachedResourceLoader->preload(CachedResource::ImageResource, m_urlToLoad, String(), scanningBody);
else if (m_tagName == linkTag && m_linkIsStyleSheet && m_linkMediaAttributeIsScreen)
cachedResourceLoader->preload(CachedResource::CSSStyleSheet, m_urlToLoad, m_charset, scanningBody);
}
操作主要就是调用CachedResourceLoader进行预加载的工作。
而预加载的内容是如何被调用的呢?
首先我们知道了HTMLPreloadScanner发现并调用CachedresorceLoader加载资源。
举一个例子,在android上打开百度页面,再进入百度音乐的时候:我们可以 从log中看到预先加载的url信息。
E/external/webkit/Source/WebCore/loader/cache/CachedResourceLoader.cpp( 701): url = http://m.baidu.com/static/img/webapp/pkg/aio_729bf5aa.css
E/external/webkit/Source/WebCore/loader/cache/CachedResourceLoader.cpp( 701): url = http://m.baidu.com/static/img/webapp/pkg/aio_1d339828.js
具体怎么打印出来WTF的String类型呢? 后面将会讲到。
刚才的分析我们也知道了,CachedresorceLoader中的资源是通过URL来进行唯一的标识与预下载的。
先贴一个堆栈信息
#0 WebCore::ImageLoader::updateFromElement (this=0x2acef4ec) at external/webkit/Source/WebCore/loader/ImageLoader.cpp:163
#1 0x48d2dc6c in WebCore::HTMLImageElement::parseMappedAttribute (this=0x2acef4b0, attr=0x2ab94720) at external/webkit/Source/WebCore/html/HTMLImageElement.cpp:108
#2 0x48efb706 in WebCore::StyledElement::attributeChanged (this=0x2acef4b0, attr=0x2ab94720, preserveDecls=<value optimized out>) at external/webkit/Source/WebCore/dom/StyledElement.cpp:187
#3 0x48ef0d4a in WebCore::Element::setAttributeMap (this=0x2acef4b0, list=<value optimized out>, scriptingPermission=<value optimized out>) at external/webkit/Source/WebCore/dom/Element.cpp:844
#4 0x48fc5f7a in WebCore::HTMLConstructionSite::createHTMLElement (this=0x2ab4b60c, token=...) at external/webkit/Source/WebCore/html/parser/HTMLConstructionSite.cpp:380
#5 0x48fc6476 in WebCore::HTMLConstructionSite::insertSelfClosingHTMLElement (this=0x2ab4b60c, token=<value optimized out>) at external/webkit/Source/WebCore/html/parser/HTMLConstructionSite.cpp:296
#6 0x48f2f908 in WebCore::HTMLTreeBuilder::processStartTagForInBody (this=0x2ab4b5f8, token=...) at external/webkit/Source/WebCore/html/parser/HTMLTreeBuilder.cpp:929
#7 0x48f30c5a in WebCore::HTMLTreeBuilder::processStartTag (this=0x2ab4b5f8, token=...) at external/webkit/Source/WebCore/html/parser/HTMLTreeBuilder.cpp:1335
#8 0x48f323d4 in WebCore::HTMLTreeBuilder::constructTreeFromAtomicToken (this=0x2ab4b5f8, token=<value optimized out>) at external/webkit/Source/WebCore/html/parser/HTMLTreeBuilder.cpp:461
#9 0x48f3250a in WebCore::HTMLTreeBuilder::constructTreeFromToken (this=0x2ab4b5f8, rawToken=...) at external/webkit/Source/WebCore/html/parser/HTMLTreeBuilder.cpp:451
#10 0x48f26aba in WebCore::HTMLDocumentParser::pumpTokenizer (this=0x2ab94770, mode=WebCore::HTMLDocumentParser::AllowYield) at external/webkit/Source/WebCore/html/parser/HTMLDocumentParser.cpp:276
#11 0x48f26b4e in WebCore::HTMLDocumentParser::resumeParsingAfterYield (this=0x2ab94770) at external/webkit/Source/WebCore/html/parser/HTMLDocumentParser.cpp:192
而在void ImageLoader::updateFromElement() 里
void ImageLoader::updateFromElement()
{
....
//重要:获取 src属性的值
AtomicString attr = client()->sourceElement()->getAttribute(client()->sourceElement()->imageSourceAttributeName());
...
// Do not load any image if the 'src' attribute is missing or if it is
// an empty string.
CachedResourceHandle<CachedImage> newImage = 0;
if (!attr.isNull() && !stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
//重要:根据url(attr的值),创建一个request,通过该request获取图片
CachedResourceRequest request(ResourceRequest(document()->completeURL(sourceURI(attr))));
request.setInitiator(client()->sourceElement());
.....
if (m_loadManually) {
bool autoLoadOtherImages = document()->cachedResourceLoader()->autoLoadImages();
<strong> </strong>document()->cachedResourceLoader()->setAutoLoadImages(false);
newImage = new CachedImage(request.resourceRequest()); //创建image对象
newImage->setLoading(true);
newImage->setOwningCachedResourceLoader(document()->cachedResourceLoader());
document()->cachedResourceLoader()->m_documentResources.set(newImage->url(), newImage.get());
document()->cachedResourceLoader()->setAutoLoadImages(autoLoadOtherImages);
} else
newImage = document()->cachedResourceLoader()->requestImage(request); //直接获取一个cached的image对象
....
}
怎么才会进行这个load过程中的cache读取的操作呢?
接下来进行研究。
总结一下:
这篇文章主要从HTMLDocumentParser类中的几个成员着手,主要分析了上一篇文章中的while循环,来进行tag的解析和dom tree的构建的工作。
然后发现在这个过程中,会对scriptTag,inputTag,linkTag, scriptTag这几个标签进行预下载的工作。预下载可能也是webkit渲染速度比较快的一个原因。
预下载的过程主要是通过CachedResourceLoader去进行的,下载的标识是 “URL”来进行标识的。
从网上找到一篇文章发现,ImageLoader::updateFromElement是进行读取的操作。但是这个部分还没有研究,具体的流程和原因,接下来的一篇文章进行研究。