该篇只学习到数据从接收到,到创建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