android webkit 网页标签的解析与预下载的实现

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

本文为极度寒冰原创,转载请注明出处
#############################################

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


你可能感兴趣的:(android,android,webkit,webkit,browser,预下载,HTMLToken)