#############################################

本文为极度寒冰原创,转载请注明出处 #############################################
将一个网页解析成一个一个的标签,并且对应去具体的类去处理这些标签内容应该也是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是进行读取的操作。但是这个部分还没有研究,具体的流程和原因,接下来的一篇文章进行研究。


更多相关文章

  1. C语言函数的递归(上)
  2. android手机屏幕横竖屏切换禁止调用生命周期函数方法
  3. Android(安卓)5.0(Lollipop)中的SurfaceTexture,TextureView, Sur
  4. 深入浅出Android(安卓)Handler
  5. android常用包介绍
  6. WebViewJavascriptBridge传递Header、Cookie
  7. AndroidStudio使用GreenDao实战
  8. Android对文件的操作(简单的文件读取与写入)
  9. Android使用AnalogClock和DigitalClock

随机推荐

  1. 210426 PHP 输出方法,数据类型,变量,作用域,
  2. 「PostgreSQL高级特性」PostgreSQL 数据
  3. 争议 | 多个数据中心上SDN背景下,跨数据中
  4. 【PostgreSQL技巧】PostgreSQL中的物化视
  5. 【PostgreSQL】PostgreSQL扩展:pg_stat_st
  6. 17 个 Ceph 日常运维常见难点和故障的解
  7. 「PostgreSQL」用MapReduce的方式思考,但
  8. 企业终端防御体系十大措施的设计部署与安
  9. 争议 | 基于存储网关 vs 基于存储引擎,两
  10. 【Postgres扩展】pg_auto_failover支持高