瀏覽器探究——webkit部分——解析(1)HTML起源



該篇只學習到數據從接收到,到創建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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章