android webkit 网页标签的解析与预下载的实现
16lz
2021-01-24
#############################################
本文为极度寒冰原创,转载请注明出处 #############################################将一个网页解析成一个一个的标签,并且对应去具体的类去处理这些标签内容应该也是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是进行读取的操作。但是这个部分还没有研究,具体的流程和原因,接下来的一篇文章进行研究。
更多相关文章
- C语言函数的递归(上)
- android手机屏幕横竖屏切换禁止调用生命周期函数方法
- Android(安卓)5.0(Lollipop)中的SurfaceTexture,TextureView, Sur
- 深入浅出Android(安卓)Handler
- android常用包介绍
- WebViewJavascriptBridge传递Header、Cookie
- AndroidStudio使用GreenDao实战
- Android对文件的操作(简单的文件读取与写入)
- Android使用AnalogClock和DigitalClock