浏览器探究——webkit部分——解析(1)HTML起源

本文详细介绍了网页从加载到解析的全过程,包括DocumentLoader、DocumentWriter的创建与使用,Document与RenderView的关联,以及DocumentParser尤其是HTMLDocumentParser的创建过程。
摘要由CSDN通过智能技术生成


该篇只学习到数据从接收到,到创建Document,创建DocumentParser的过程。

主要讲述到

DocumentParser::appendBytes

DocumentParser::finish

的调用处,后续篇章会学习这两个函数的实现部分。

 

测试页面:

<html>

<body>

<p>First name: </p>

<input type="text"name="fname" />

Last name: <input type="text"name="lname" />

</body>

</html>

解析的起源

回顾下LoadUrl的情况,

首先通过jni调用到WebCoreFrameBridge.cpp中的LoadUrl,然后回调用到FrameLoader的load,在FrameLoader::load中会创建DocumentLoader,DocumentLoader会被FrameLoader维护。如下调用栈所示:

#0WebCore::DocumentLoader::create

#1android::FrameLoaderClientAndroid::createDocumentLoader

#2WebCore::FrameLoader::load

#3WebCore::FrameLoader::load

#4 LoadUrl

在DocumentLoader中有成员mutable DocumentWriter m_writer;该成员是在DocumentLoader构造时一并创建出来的。那么上述的调用栈也就是DocumentWriter的创建的流程了。

DocumentWriter

该类位于WebCore/loader目录下,也就是说DocumentWriter还只是loader中的内容,并不是parser中的。这个是android4.0中才开始有的,在2.3中并没有该文件。

DocumentWriter相当于 loader与parser的桥梁。这里先看下loader与parser之间的关联的流程。

在最初接收到数据时,即执行didiReceiveData的流程里,WebUrlLoaderClient这个网络相关的类最先受到了回调,执行了WebUrlLoaderClient::didReceiveData。接着回调会通过ResourceHandle找到MainResourceLoader,调用它的回调接口MainResourceLoader::didReceiveData。MainResourceLoader是继承自ResourceLoader,在ResourceLoader中有成员DocumentLoader。这样MainResourceLoader中把收到的数据转交给了DocumentLoader来处理,调用了DocumentLoader::receiveData。

前面说过,DocumentLoader中维护了DocumentWriter成员,那么在DocumentLoader中就会开始使用DocumentWriter来处理收到的数据了,在进一步的调用后会调用到DocumentLoader::commitData。

看下调用栈情况:

#0 WebCore::DocumentLoader::commitData

#1 android::FrameLoaderClientAndroid::committedLoad

#2 WebCore::DocumentLoader::commitLoad

#3 WebCore::DocumentLoader::receivedData

#4 WebCore::MainResourceLoader::addData

#5 WebCore::ResourceLoader::didReceiveData

#6 WebCore::MainResourceLoader::didReceiveData

#7 WebCore::ResourceLoader::didReceiveData

#8 android::WebUrlLoaderClient::didReceiveData

在DocumentLoader::commitData中执行了二个使用DocumentWriter的操作。

1.      对DocumentWriter设置编码,DocumentWriter::setEncoding

2.      把收到的数据转交给DocumentWriter,DocumentWriter::addData。

解析的结束

当数据接收完毕后,会用WebUrlLoaderClient::didFinishLoading这个回调函数,像didiReceiveData的流程那样,会调用MainResourceLoader的回调函数didFinishLoading,MainResourceLoader中会再调用DocumentLoader的回调函数。

在DocumentLoader中,则会调用其成员DocumentWriter的end函数,来表明完成解析。

看下调用栈情况:

#0WebCore::DocumentWriter::end

#1WebCore::DocumentLoader::finishedLoading

#2WebCore::FrameLoader::finishedLoading

#3WebCore::MainResourceLoader::didFinishLoading

#4WebCore::ResourceLoader::didFinishLoading

#5android::WebUrlLoaderClient::didFinishLoading

由此可见,在结束接收数据时调用了DocumentWriter::end函数。

由上述内容可知,DocumentWriter被维护在DocumentLoader中,但是DocumentLoader主要就调用了3个函数来操作DocumentWriter。分别是

DocumentWriter::setEncoding

DocumentWriter::addData

DocumentWriter::end

看下DocumentWriter类,该类有个begin函数。而在DocumentLoader中并没有调用该函数。应该是begin-> addData-> end才符合逻辑的。事实上也是如此,DocumentWriter::begin函数其实是在DocumentWriter::setEncoding的调用链上被调用的。

除了在DocumentLoader中直接操作DocumentWriter,在FrameLoader中也会通过DocumentLoader找到DocumentWriter,并调用DocumentWriter提供的函数,注意下FrameLoader中有维护了DocumentLoader。

那么接下来看下这三个函数如何跟paser关联的。

dom关联的起源

DocumentWriter::setEncoding

该函数会通过成员Frame找到FrameLoader,然后调用FrameLoader::willSetEcoding。这个函数会进一步调用FrameLoader::receivedFirstData,看名字,接收第一次数据,好吧,既然是第一次数据那么这里就算是开始了,就可以调用DocumentWriter::begin了。调用了DocumentWriter::begin之后,会记录传入的参数Encoding名字以及是否是用户选择的。

看下调用栈:

#0WebCore::DocumentWriter::begin

#1WebCore::FrameLoader::receivedFirstData

#2WebCore::FrameLoader::willSetEncoding

#3WebCore::DocumentWriter::setEncoding

#4WebCore::DocumentLoader::commitData

DocumentWriter::begin

在这里首先会通过DocumentWriter::createDocument创建一个Document,Document是什么?一个页面可以叫Document,这个页面的DOMTree可以叫Document。看下Document的继承体系。

进入dom

Document的创建

Document

Node

ContainerNode

TreeScope   ScriptExecutionContext

Document

由此可见Document是个Node,它其实是整个页面DOM Tree的根Node。这样就了然了,它是根Node,那么通过它可以遍历整个DOM Tree,也就是可以找到页面中的每一个元素,那么也就是它可以概括的认为是这个DOM Tree的标识,所有的Node都是它家族的成员,它是老祖宗。他就代表了家族。他就代表了这个页面。

具体创建Document的函数是DOMImplementation::createDocument。注意下名字,DOMImplementation充分表明了Document与DOM是关联的,而DOMImplementation这个类又是在WebCore/dom/目录下的,可见到达Document时,代码流程已经从loader部分走如了dom部分了。

在DOMImplementation::createDocument中会根据参数传入的MimeType来创建具体的Document子类。这些具体的子类绝大多数都是在WebCore/html目录下定义的,但是像WMLDocument则是定义在WebCore/wml目录下。有个特别的是,如果类型是XHTML或者是XML,则会创建Document这个类。

当前是HTML的页面,所以创建的是HTMLDocument。该类在WebCore/html目录下定义.

HTMLDocument

看下该类的继承体系

Node

ContainerNode

TreeScope   ScriptExecutionContext

Document       CachedResourceClient

HTMLDocument

该类是html的DOM的根节点,也就是代表了一个html页面,代表了一个html页面的元素的集合,但其实它也就是个Node。

但是这个Node不同于一般的Node,这个Node里有很多页面的信息,还有个重要的函数HTMLDocument::createElement,只有通过这个函数,才能知道怎么创建html相关的Node。

回到DocumentWrite::begin中,创建完Document后,会把这个Document设置给DocumentWrite的成员Frame中。通过函数Frame::setDocument。

Frame::setDocument

在这个函数中首先会把参数传入的Document记录在Frame中,这样通过Frame就能找到Document了,Frame中只有一个Document成员,Document中也只有一个Frame成员,也就是他们之间是一一对应的。Frame相当于一个页面总的数据结构,它包含了跟一个页面相关的很多信息。而Document只是页面中具体的数据,即具体的Node集合的数据结构(当然这只是它表示的含义,别忘了Document实际上就是个Node,是个根Node)。

在把Document设置给Frame::m_doc后,调用了Document::attach。那么Frame什么时候设置给Document的呢?是在Document构造函数时,在构造函数中会传入Frame参数,这个Frame被设置给Document::m_frame。

RenderView的创建

Document::attach

这个虚函数的定义最初来源于其祖先类Node。这里先看一下Node的一些基础情况:

Node

先看下Node的几个重要的成员

Document* m_document;

   Node* m_previous;

   Node* m_next;

   RenderObject* m_renderer;

mutableuint32_t m_nodeFlags;

注意Document虽然是继承自Node的,但是Node中有该成员,应该是表示该Node位于哪个Document所在的DOM树中吧。

m_renderer是个重要的成员,Node有个跟自己对应的RenderObject。这样Node组成的DOM树就有个对应的Render树。而Node与RenderObject关联和接关联就是通过attach和detach函数。这连个函数在Node中定义如下:

// Attaches this node to the renderingtree. This calculates the style to be applied to the node and creates an

   // appropriate RenderObject which will be inserted into the tree (exceptwhen the style has display: none). This

   // makes the node visible in the FrameView.

   virtual void attach();

 

   // Detaches the node from the rendering tree, making it invisible in therendered view. This method will remove

   // the node's rendering object from the rendering tree and delete it.

virtual voiddetach();

RenderObject有成员Node* m_node;用于记录与之对应的Node。即Node与RenderObject是一一对应的。Node::setRenderer用于把RenderObject设置给Node,而RenderObject的构造函数中有参数Node,在构造时直接把Node设置给RenderObject。

Node的情况暂时就提这么多。

继续回到Document::attach中,看下Document的另一个祖先类ContainerNode,该类也实现了attach虚函数,ContainerNode:: attach的实现就是调用其每个子Node的attach,最后调用其基类的Node::attach。因为ContainerNode是个容器,所以ContainerNode有一堆子Node,因为有子Node,所以子Node也要调用attach。

那么Document::attach的处理呢?Document也是个Node,也有自己对应的RenderObject。

但是Document是个特殊的Node,它是整个DOM树的根,所以它对应的RenderObject也要特殊,他对应的是RenderView,他是Render树的根。

Document::attach中创建了RenderView,并把它设置给Document,

Document::attach主要的工作就是这些,创建并设置RenderView。

看下上述两个处理的调用栈情况:

创建RenderView

#0 RenderView

#1WebCore::Document::attach

#2WebCore::Frame::setDocument

#3 WebCore::DocumentWriter::begin

把设置RenderView给Document

#0WebCore::Node::setRenderer

#1WebCore::Document::attach

#2WebCore::Frame::setDocument

#3WebCore::DocumentWriter::begin

Frame::setDocument主要的工作就是设置了Document并调用了Document::attach。

这样回到了DocumentWriter::begin。

回顾下刚才做了什么,刚才创建了Document,又创建了RenderView,并把Document与RenderView关联起来。然后把Document设置给了Frame。以后通过Frame就能找到Document了。

继续DocumentWriter::begin。

接着会通过Frame找到FrameLoader,然后调用FrameLoader::didBeginDocument。

FrameLoader::didBeginDocument

这个函数中会做一些设置,这些处理不细看,要注意一点,这里通过Frame找到Document,然后调用Document::setReadyState。

Document中定义了如下状态

enum ReadyState {

       Loading,

       Interactive,

       Complete

};

Document::setReadyState在设置状态后还会触发事件的处理dispatchEvent。Document本身没定义自己的事件处理,用的是其祖先类Node的dispatchEvent。

执行了FrameLoader::didBeginDocument之后,会调用Document::implicitOpen。这个Document::implicitOpen是DocumentWriter::begin最后操作Document的地方。看来这个函数很重要,回顾下DocumentWriter是在WebCore/loader/目录下定义的,Document是在WebCore/dom/目录下定义的。DocumentWriter::begin是在DocumentWriter::setEncoding 中被调用的,后续还会调用到DocumentWriter::addData 。即DocumentWriter::begin中应该配置好跟解析相关的类,然后等待来数据时就进行解析的处理了。

数据都是从ResourceLoader(MainResourceLoader)中传到DocumentLoader 再进一步传到DocumentWriter,最后传给Document 的。ResourceLoader(MainResourceLoader),DocumentLoader,DocumentWriter都是在WebCore/loader/目录下定义的,所以之前的传递都还是处于loader模块中,到了Document才真正进入到dom模块中,进行dom的构建。

继续看Document::implicitOpen,当前只是创建了Document以及RenderView。而解析所需要的解析器还没有创建呢。

DocumentParser的创建

Document::implicitOpen

该函数先取消之前的parse操作,然后移出子节点,即做了一下清理的操作。

然后设置了个compatibility状态。

接着创建一个Parser。Document中有重要的成员RefPtr<DocumentParser> m_parser;它是该Document的解析器。用来解析Document对应的这个页面的。

创建了Parser后,设置parsing状态为真,和设置Ready状态为Loading。

通过以上步骤就完成了Document::implicitOpen的操作。可见Document::implicitOpen最重要的就是创建了DocumentParser。

DocumentParser

DocumentParser被定义在WebCore/dom目录下。

DocumentParser是一个虚基类,具体的XXXDocument会创建与之对应的XXXDocumentParser。

他有成员m_document,用于与使用他的Document关联。

// Every DocumentParser needs a pointer back to the document.

   // m_document will be 0 after the parser is stopped.

   Document* m_document;

在Document类中,有虚函数PassRefPtr<DocumentParser> createParser();用于创建一个与该Document相关的DocumentParser。这里是HTMLDocument(Documen的子类),他的createParser创建的是HTMLDocumentParser(DocumentParser的子类)

Document与DocumentParser也是一一对应的,Document中有成员Document::m_parser,DocumentParser中有成员DocumentParser::m_document。

Document:: m_parser是在Document::implicitOpen中通过Document::createParser创建完DocumentParser时,直接赋值的。

DocumentParser::m_document是在DocumentParser的构造函数中,通过传入的参数Document直接赋值的。

HTMLDocumentParser

这个类是在WebCore/html/parser/目录下定义的。到了这里终于到达了html相关的parse目录了。

首先看下他的继承体系:

DocumentParser

DecodedDataDocumentParser

ScriptableDocumentParser      HTMLScriptRunnerHost      CachedResourceClient

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;

   XSSFilter m_xssFilter;

   bool m_endWasDelayed;

unsignedm_pumpSessionNestingLevel;

在HTMLDocumentParser的构造函数中HTMLTokenizer,HTMLTreeBuilder都会被创建。

经过以上可知Document::implicitOpen中创建了HTMLDocumentParser,之后就可以利用这个HTMLDocumentParser来进行解析了。看下创建HTMLDocumentParser的栈情况。

#0 WebCore::HTMLDocumentParser::create

#1 WebCore::HTMLDocument::createParser

#2 WebCore::Document::implicitOpen

#3 WebCore::DocumentWriter::begin

#4 WebCore::FrameLoader::receivedFirstData

#5 WebCore::FrameLoader::willSetEncoding

#6 WebCore::DocumentWriter::setEncoding

#7 WebCore::DocumentLoader::commitData

#8 android::FrameLoaderClientAndroid::committedLoad

#9 WebCore::DocumentLoader::commitLoad

#10 WebCore::DocumentLoader::receivedData

#11WebCore::MainResourceLoader::addData

#12 WebCore::ResourceLoader::didReceiveData

#13 WebCore::MainResourceLoader::didReceiveData

#14 WebCore::ResourceLoader::didReceiveData

#15 android::WebUrlLoaderClient::didReceiveData

到了这里再回顾下,ResourceLoader(MainResourceLoader)->传递回调->DocumentLoader->构造时一并创建->DocumentWriter->setEncoding-> begin->创建HTMLDocument(与RenderView)-> implicitOpen->创建HTMLDocumentParser。这里另外注意一点,setEncoding只有在第一次接收到数据时才会调用begin后面的操作,每次接收数据setEncoding都会被调用,但是后续的数据接收并不会每次都创建一遍HTMLDocument了。

这里暂且不看HTMLDocumentParser里的内容,只是先知道这里创建了HTMLDocumentParser并且创建了HTMLDocumentParser中的HTMLTokenizer,HTMLTreeBuilder等成员。

经过以上的一堆处理后,我们先不看这个调用链其他地方的细节,直接回到DocumentLoader::commitData。DocumentLoader::commitData在执行完DocumentWriter::setEncoding后会调用DocumentWriter::addData。也就是文章最初说的DocumentLoader操作DocumentWriter的三大函数的第二个。

进入DocumentParser的解析

DocumentWriter::addData

这个函数的参数会附带接收到的数据,这个函数其实没做什么,只是通过Frame找到Document,进一步找到DocumentParser。然后调用DocumentParser::appendBytes,把参数传进来的数据转交给DocumentParser::appendBytes就完了。

完成解析

DocumentWriter::end

先执行一个DocumentParser::appendBytes(0, 0, true)这个true表示flush。即通知解析器把Buffer存的数据都解析完。

然后执行Document::finishParsing。该函数会进一步执行DocumentParser::finish。

由上可见,真正的数据的处理还是由DocumentParser::appendBytes以及DocumentParser::finish来完成的。那么之后我们主要就关心这两个函数即可。

另外记得一下,当前的Document是HTMLDocument,当前的DocumentParser是HTMLDocumentParser。

后续我们讨论下面两个主要的函数。

DocumentParser::appendBytes

DocumentParser::finish

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值