在Chromium中。Render進程是經過Browser進程下載網頁內容的。後者又是經過共享內存將下載回來的網頁內容交給前者的。Render進程得到網頁內容以後,會交給WebKit進行處理。WebKit所作的第一個處理就是對網頁內容進行解析,解析的結果是獲得一棵DOM Tree。DOM Tree是網頁的一種結構化描寫敘述。也是網頁渲染的基礎。html
本文接下來就對網頁DOM Tree的建立過程進行具體分析。算法
老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注。
後端
《Android系統源代碼情景分析》一書正在進擊的程序猿網(http://0xcc0xcd.com)中連載。點擊進入。
安全
網頁的DOM Tree的根節點是一個Document。session
Document是依附在一個DOM Window之上。DOM Window又是和一個Frame關聯在一塊兒的。Document、DOM Window和Frame都是WebKit裏面的概念。當中Frame又是和Chromium的Content模塊中的Render Frame相相應的。Render Frame是和網頁的Frame Tree相關的一個概念。關於網頁的Frame Tree,可以參考前面Chromium Frame Tree建立過程分析一文。app
上面描寫敘述的各類對象的關係可以經過圖1描寫敘述。例如如下所看到的:框架
圖1 Frame、DOM Window和Document的關係dom
從前面Chromium Frame Tree建立過程分析一文可以知道。有的Render Frame僅僅是一個Proxy,稱爲Render Frame Proxy。異步
Render Frame Proxy描寫敘述的是在另一個Render進程中進行載入和渲染的網頁。這樣的網頁在WebKit裏面相應的Frame和DOM Window分別稱爲Remote Frame和Remote DOM Window。async
因爲Render Frame Proxy描寫敘述的網頁不是在當前Render進程中載入和渲染。所以它是沒有Document的。
相應地。Render Frame描寫敘述的是在當前Render進程中進行載入和渲染的網頁,它是具備Document的。並且這樣的網頁在WebKit裏面相應的Frame和DOM Window分別稱爲Local Frame和Local DOM Window。
從圖1咱們還可以看到,在Render Frame和Local Frame之間,以及Render Frame Proxy和Remote Frame之間。分別存在一個Web Local Frame和Web Remote Frame。Web Local Frame和Web Remote Frame是屬於WebKit Glue層的概念。從前面Chromium網頁載入過程簡要介紹和學習計劃一文可以知道,WebKit Glue層的做用是將WebKit的對象類型轉化爲Chromium的對象類型。這樣Chromium的Content層就可以用統一的、自有的方式管理所有的對象。
關於Chromium的層次劃分和每一個層次的做用,可以參考前面Chromium網頁載入過程簡要介紹和學習計劃一文。
除了根節點。也就是Document節點,DOM Tree的每一個子結點相應的都是網頁裏面的一個HTML標籤。並不是所有的HTML標籤都是需要渲染的,好比script標籤就不需要進行渲染。對於需要渲染的HTML標籤,它們會關聯有一個Render Object。這些Render Object會造成一個Render Object Tree,如圖2所看到的:
圖2 DOM Tree與Render Object Tree、Render Layer Tree和Graphics Layer Tree的關係
爲了便於運行繪製操做,具備一樣座標空間的Render Object會繪製在同一個Render Layer中。這些Render Layer又會造成一個Render Layer Tree。
繪製操做是由圖形渲染引擎運行的。對於圖形渲染引擎來講,Layer是一個具備後端存儲的概念。在軟件渲染模式中。Layer的後端存儲實際上就是一個內存緩衝區。在硬件渲染模式中。Layer的後端存儲實際上就是一個FBO。爲了節約資源,WebKit不會爲每一個Render Layer都分配一個後端存儲。而是會讓某些Render Layer共用其它的Render Layer的後端存儲。那些具備本身的後端存儲的Render Layer,又稱爲Graphics Layer。這些Graphics Layer又造成了一個Graphics Layer Tree。
Render Object Tree、Render Layer Tree和Graphics Layer Tree都是和網頁渲染相關概念,它們是從DOM Tree發展而來的。
所以。在分析網頁的渲染機制以前,有必要了解網頁的DOM Tree的建立過程。
DOM Tree的建立發生在WebKit解析網頁內容的過程當中。WebKit在解析網頁內容的時候,會用到一個棧。
每當碰到一個HTML標籤的起始Token,就會將其壓入棧中。而當碰到該HTML標籤的結束Token時,就會將其彈出棧。在這些HTML標籤的壓棧和出棧過程當中,就可以獲得一棵DOM Tree。以圖2所看到的的DOM Tree片斷爲例。它相應的網頁內容爲:
<div> <p> <div></div> </p> <span></span> </div>各個標籤的壓棧和出棧過程如圖3所看到的:
圖3 網頁內容解析過程當中的HTML標籤壓棧和出棧操做
接下來,咱們就結合源代碼分析WebKit在解析網頁內容的過程當中建立DOM Tree的過程。從前面Chromium網頁URL載入過程分析一文可以知道。Browser進程一邊下載網頁的內容,一邊將下載回來的網頁交給Render進程的Content模塊。Render進程的Content模塊通過簡單的處理以後,又會交給WebKit進行解析。WebKit是從ResourceLoader類的成員函數didReceiveData開始接收Chromium的Content模塊傳遞過來的網頁內容的,所以咱們就從這個函數開始分析WebKit解析網頁內容的過程。也就是網頁DOM Tree的建立過程。
ResourceLoader類的成員函數didReceiveData的實現例如如下所看到的:
void ResourceLoader::didReceiveData(blink::WebURLLoader*, const char* data, int length, int encodedDataLength) { ...... m_resource->appendData(data, length); }
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/fetch/ResourceLoader.cpp中。
ResourceLoader類的成員變量m_resource描寫敘述的是一個RawResource對象。
這個RawResource對象的建立過程可以參考前面Chromium網頁URL載入過程分析一文。ResourceLoader類的成員函數didReceiveData調用這個RawResource對象的成員函數appendData處理下載回來的網頁內容。
RawResource類的成員函數appendData的實現例如如下所看到的:
void RawResource::appendData(const char* data, int length) { Resource::appendData(data, length); ResourcePtr<RawResource> protect(this); ResourceClientWalker<RawResourceClient> w(m_clients); while (RawResourceClient* c = w.next()) c->dataReceived(this, data, length); }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/fetch/RawResource.cpp中。
RawResource類的成員函數appendData主要是調用保存在成員變量m_clients中的每一個RawResourceClient對象的成員函數dataReceived,告知它們從Webserver中下載回來了新的數據。
從前面Chromium網頁URL載入過程分析一文可以知道,在RawResource類的成員變量m_clients中,保存有一個DocumentLoader對象。
這個DocumentLoader對象是從RawResourceClient類繼承下來的。它負責建立和載入網頁的文檔對象。
接下來咱們就繼續分析它的成員函數dataReceived的實現,例如如下所看到的:
void DocumentLoader::dataReceived(Resource* resource, const char* data, int length) { ..... commitData(data, length); ...... }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/loader/DocumentLoader.cpp中。
DocumentLoader類的成員函數dataReceived主要是調用另一個成員函數commitData處理從Webserver下載回來的網頁數據。後者的實現例如如下所看到的:
void DocumentLoader::commitData(const char* bytes, size_t length) { ensureWriter(m_response.mimeType()); ...... m_writer->addData(bytes, length); }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/loader/DocumentLoader.cpp中。
DocumentLoader類的成員函數commitData首先調用成員函數ensureWriter肯定成員變量m_writer指向了一個DocumentWriter對象,因爲接下來要調用這個DocumentWriter對象的成員函數addData對下載回來的網頁數據進行解析。
接下來。咱們首先分析DocumentLoader類的成員函數ensureWriter的實現。接下來再分析DocumentWriter類的成員函數addData的實現。
DocumentLoader類的成員函數ensureWriter的實現例如如下所看到的:
void DocumentLoader::ensureWriter(const AtomicString& mimeType, const KURL& overridingURL) { if (m_writer) return; ...... m_writer = createWriterFor(m_frame, 0, url(), mimeType, encoding, false, false); ...... }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/loader/DocumentLoader.cpp中。
DocumentLoader類的成員函數ensureWriter首先檢查成員變量m_writer是否指向了一個DocumentWriter對象。
假設已經指向。那麼就什麼也不用作就直接返回。不然的話,就會調用另一個成員函數createWriterFor爲當前正在載入的URL建立一個DocumentWriter對象,並且保存在成員變量m_writer中。
DocumentLoader類的成員函數createWriterFor的實現例如如下所看到的:
PassRefPtrWillBeRawPtr<DocumentWriter> DocumentLoader::createWriterFor(LocalFrame* frame, const Document* ownerDocument, const KURL& url, const AtomicString& mimeType, const AtomicString& encoding, bool userChosen, bool dispatch) { ...... // In some rare cases, we'll re-used a LocalDOMWindow for a new Document. For example, // when a script calls window.open("..."), the browser gives JavaScript a window // synchronously but kicks off the load in the window asynchronously. Web sites // expect that modifications that they make to the window object synchronously // won't be blown away when the network load commits. To make that happen, we // "securely transition" the existing LocalDOMWindow to the Document that results from // the network load. See also SecurityContext::isSecureTransitionTo. bool shouldReuseDefaultView = frame->loader().stateMachine()->isDisplayingInitialEmptyDocument() && frame->document()->isSecureTransitionTo(url); ...... if (!shouldReuseDefaultView) frame->setDOMWindow(LocalDOMWindow::create(*frame)); RefPtrWillBeRawPtr<Document> document = frame->domWindow()->installNewDocument(mimeType, init); ...... return DocumentWriter::create(document.get(), mimeType, encoding, userChosen); }
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/loader/DocumentLoader.cpp中。
從前面的調用過程可以知道。參數frame描寫敘述的LocalFrame對象來自於DocumentLoader類的成員變量m_frame,這個LocalFrame對象描寫敘述的是一個在當前Render進程中進行載入的網頁。
假設當前正在載入的網頁是經過JavaScript接口window.open打開的。那麼參數frame描寫敘述的LocalFrame對象已經關聯有一個默認的DOM Window。
在符合安全規則的狀況下,這個默認的DOM Window將會被使用。
假設不符合安全規則。或者當前載入的網頁不是經過JavaScript接口window.open打開的。那麼就需要爲參數frame描寫敘述的LocalFrame對象建立一個新的DOM Window。
這是經過調用LocalDOMWindow類的靜態成員函數create建立的,例如如下所看到的:
namespace WebCore { ...... class LocalDOMWindow FINAL : public RefCountedWillBeRefCountedGarbageCollected<LocalDOMWindow>, public ScriptWrappable, public EventTargetWithInlineData, public DOMWindowBase64, public FrameDestructionObserver, public WillBeHeapSupplementable<LocalDOMWindow>, public LifecycleContext<LocalDOMWindow> { ...... static PassRefPtrWillBeRawPtr<LocalDOMWindow> create(LocalFrame& frame) { return adoptRefWillBeRefCountedGarbageCollected(new LocalDOMWindow(frame)); } ...... }; ...... }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/frame/LocalDOMWindow.h中。
LocalDOMWindow類的靜態成員函數create爲參數frame指向的一個LocalFrame對象建立的是一個類型爲LocalDOMWindow的DOM Window。這是因爲參數frame指向的LocalFrame對象描寫敘述的是一個在當前Render進程載入的網頁。
回到DocumentLoader類的成員函數createWriterFor中,它調用LocalDOMWindow類的靜態成員函數create建立了一個LocalDOMWindow對象以後,會將這個LocalDOMWindow對象設置給參數frame描寫敘述的LocalFrame對象。
這是經過調用LocalFrame類的成員函數setDOMWindow實現的。
LocalFrame類的成員函數setDOMWindow的實現例如如下所看到的:
void LocalFrame::setDOMWindow(PassRefPtrWillBeRawPtr<LocalDOMWindow> domWindow) { ...... Frame::setDOMWindow(domWindow); }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/frame/LocalFrame.cpp中。
LocalFrame類的成員函數setDOMWindow會將參數domWindow描寫敘述的一個LocalDOMWindow對象交給父類Frame處理,這是經過調用父類Frame的成員函數setDOMWindow實現的。
Frame類的成員函數setDOMWindow的實現例如如下所看到的:
void Frame::setDOMWindow(PassRefPtrWillBeRawPtr<LocalDOMWindow> domWindow) { ...... m_domWindow = domWindow; }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/frame/Frame.cpp中。
Frame類的成員函數setDOMWindow將參數domWindow描寫敘述的一個LocalDOMWindow對象保存在成員變量m_domWindow中。
之後就可以經過調用Frame類的成員函數domWindow得到這個LocalDOMWindow對象,例如如下所看到的:
inline LocalDOMWindow* Frame::domWindow() const { return m_domWindow.get(); }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/frame/Frame.h中。
這一步運行完畢以後。WebKit就爲一個類型爲LocaFrame的Frame建立了一個類型爲LocalDOMWindow的DOM Window,正如圖1所看到的。
回到DocumentLoader類的成員函數createWriterFor中。接下來它會繼續爲上面建立的類型爲LocalDOMWindow的DOM Window建立一個Document。
這是經過調用LocalDOMWindow類的成員函數installNewDocument實現的,例如如下所看到的:
PassRefPtrWillBeRawPtr<Document> LocalDOMWindow::installNewDocument(const String& mimeType, const DocumentInit& init, bool forceXHTML) { ...... m_document = createDocument(mimeType, init, forceXHTML); ...... m_document->attach(); ...... }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp中。
LocalDOMWindow類的成員函數installNewDocument首先調用另一個成員函數createDocument建立一個HTMLDocument對象,並且保存在成員變量m_document中。接下來又調用這個HTMLDocument對象的成員函數attach爲其建立一個Render View。這個Render View即爲圖2所看到的的Render Object Tree的根節點。
接下來咱們首先分析LocalDOMWindow類的成員函數createDocument的實現。接着再分析HTMLDocument類的成員函數attach的實現。
LocalDOMWindow類的成員函數createDocument的實現例如如下所看到的:
PassRefPtrWillBeRawPtr<Document> LocalDOMWindow::createDocument(const String& mimeType, const DocumentInit& init, bool forceXHTML) { RefPtrWillBeRawPtr<Document> document = nullptr; if (forceXHTML) { // This is a hack for XSLTProcessor. See XSLTProcessor::createDocumentFromSource(). document = Document::create(init); } else { document = DOMImplementation::createDocument(mimeType, init, init.frame() ? init.frame()->inViewSourceMode() : false); ...... } return document.release(); }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp中。
當參數forceXHTML的值等於true的時候,表示當前載入的網頁的MIME Type爲「text/plain」。這時候LocalDOMWindow類的成員函數createDocument調用Document類的靜態成員函數create爲其建立一個類型Document的Document。 咱們考慮當前載入的網頁的MIME Type爲"text/html",這時候LocalDOMWindow類的成員函數createDocument調用DOMImplementation類的成員函數createDocument爲當前正在載入的網頁建立一個類型爲HTMLDocument的Document。
DOMImplementation類的成員函數createDocument的實現例如如下所看到的:
PassRefPtrWillBeRawPtr<Document> DOMImplementation::createDocument(const String& type, const DocumentInit& init, bool inViewSourceMode) { ...... if (type == "text/html") return HTMLDocument::create(init); ...... }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/dom/DOMImplementation.cpp中。
從這裏可以看到,假設當前正在載入的網頁的MIME Type爲"text/html"。那麼DOMImplementation類的成員函數createDocument就會調用HTMLDocument類的靜態成員函數create建立一個Document。
HTMLDocument類的靜態成員函數create的實現例如如下所看到的:
class HTMLDocument : public Document, public ResourceClient { public: static PassRefPtrWillBeRawPtr<HTMLDocument> create(const DocumentInit& initializer = DocumentInit()) { return adoptRefWillBeNoop(new HTMLDocument(initializer)); } ...... };這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLDocument.h中。
從這裏可以看到。HTMLDocument類的靜態成員函數create建立的Document的類型爲HTMLDocument。
回到LocalDOMWindow類的成員函數installNewDocument中。它調用成員函數createDocument建立了一個HTMLDocument對象以後,接下來會調用這個HTMLDocument對象的成員函數attach爲其建立一個Render View。
HTMLDocument類的成員函數attach是從父類Document繼承下來的。它的實現例如如下所看到的:
void Document::attach(const AttachContext& context) { ...... m_renderView = new RenderView(this); setRenderer(m_renderView); ...... }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/dom/Document.cpp。
Document類的成員函數attach首先是建立了一個RenderView對象保存在成員變量m_renderView中。這個RenderView對象就是圖2所看到的的Render Object Tree的根節點。接下來又調用另一個成員函數setRenderer將上述RenderView對象做爲與當前正在處理的Document對象相應的Render Object。之後咱們分析網頁的渲染過程時,再具體分析Render View的做用。
這一步運行完畢以後。WebKit就爲一個類型爲LocalDOMWindow的DOM Window建立了一個類型爲HTMLDocument的Document,正如圖1所看到的。回到DocumentLoader類的成員函數createWriterFor中。它最後調用DocumentWriter類的靜態成員函數create爲前面建立的類型爲HTMLDocument的Document建立一個DocumentWriter對象。
這個DocumentWriter對象負責解析從Webserver下載回來的網頁數據。
DocumentWriter類的靜態成員函數create的實現例如如下所看到的:
PassRefPtrWillBeRawPtr<DocumentWriter> DocumentWriter::create(Document* document, const AtomicString& mimeType, const AtomicString& encoding, bool encodingUserChoosen) { return adoptRefWillBeNoop(new DocumentWriter(document, mimeType, encoding, encodingUserChoosen)); }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/loader/DocumentWriter.cpp中。
從這裏可以看到,DocumentWriter類的靜態成員函數create建立的是一個DocumentWriter對象。
這個DocumentWriter對象的建立過程,即DocumentWriter類的構造函數的實現,例如如下所看到的:
DocumentWriter::DocumentWriter(Document* document, const AtomicString& mimeType, const AtomicString& encoding, bool encodingUserChoosen) : m_document(document) , ...... , m_parser(m_document->implicitOpen()) { ...... }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/loader/DocumentWriter.cpp中。
DocumentWriter類的構造函數首先將參數document描寫敘述的HTMLDocument對象保存在成員變量m_document中,接下來又調用這個HTMLDocument對象的成員函數implicitOpen建立了一個HTMLDocumentParser對象。
這個HTMLDocumentParser對象就是用來解析從Webserver下載回來網頁數據的。
HTMLDocument類的成員函數implicitOpen是從父類Document繼承下來的,它的實現例如如下所看到的:
PassRefPtrWillBeRawPtr<DocumentParser> Document::implicitOpen() { ...... m_parser = createParser(); ...... return m_parser; }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/dom/Document.cpp中。
Document類的成員函數implicitOpen調用另一個成員函數createParser建立了一個HTMLDocumentParser對象保存在成員變量m_parser中。並且這個HTMLDocumentParser對象會返回給調用者。
Document類的成員函數createParser的實現例如如下所看到的:
PassRefPtrWillBeRawPtr<DocumentParser> Document::createParser() { if (isHTMLDocument()) { bool reportErrors = InspectorInstrumentation::collectingHTMLParseErrors(page()); return HTMLDocumentParser::create(toHTMLDocument(*this), reportErrors); } ...... }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/dom/Document.cpp中。
因爲當前正在處理的其實是一個HTMLDocument對象。所以Document類的成員函數createParser調用另一個成員函數isHTMLDocument獲得的返回值會爲true,這時候Document類的成員函數就會調用HTMLDocumentParser類的靜態成員函數create建立一個HTMLDocumentParser對象。例如如下所看到的:
class HTMLDocumentParser : public ScriptableDocumentParser, private HTMLScriptRunnerHost { WTF_MAKE_FAST_ALLOCATED_WILL_BE_REMOVED; WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(HTMLDocumentParser); public: static PassRefPtrWillBeRawPtr<HTMLDocumentParser> create(HTMLDocument& document, bool reportErrors) { return adoptRefWillBeNoop(new HTMLDocumentParser(document, reportErrors)); } ...... };這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.h中。
從這裏可以看到,HTMLDocumentParser類的靜態成員函數create建立的是一個HTMLDocumentParser對象。這個HTMLDocumentParser對象會返回給調用者。
這一步運行完畢以後,回到DocumentLoader類的成員函數dataReceived中。它調用成員函數ensureWriter肯定成員變量m_writer指向了一個DocumentWriter對象以後,接下來要調用這個DocumentWriter對象的成員函數addData對下載回來的網頁數據進行解析。
DocumentWriter類的成員函數addData的實現例如如下所看到的:
void DocumentWriter::addData(const char* bytes, size_t length) { ...... m_parser->appendBytes(bytes, length); }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/loader/DocumentWriter.cpp中。
從前面的分析可以知道,DocumentWriter類的成員變量m_parser指向的是一個HTMLDocumentParser對象。DocumentWriter類的成員函數addData調用這個HTMLDocumentParser對象的成員函數appendBytes對下載回來的網頁數據進行解析。
HTMLDocumentParser類的成員函數appendBytes的實現例如如下所看到的:
void HTMLDocumentParser::appendBytes(const char* data, size_t length) { ...... if (shouldUseThreading()) { ...... OwnPtr<Vector<char> > buffer = adoptPtr(new Vector<char>(length)); memcpy(buffer->data(), data, length); ...... HTMLParserThread::shared()->postTask(bind(&BackgroundHTMLParser::appendRawBytesFromMainThread, m_backgroundParser, buffer.release())); return; } DecodedDataDocumentParser::appendBytes(data, length); }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp中。
HTMLDocumentParser類的成員函數appendBytes調用另一個成員函數shouldUseThreading推斷是否需要在一個專門的線程中對下載回來的網頁數據進行解析。假設需要的話。那麼就把下載回來的網頁數據複製到一個新的緩衝區中去交給專門的線程進行解析。不然的話。就在當前線程中調用父類DecodedDataDocumentParser類的成員函數appendBytes對下載回來的網頁數據進行解析。爲了簡單起見,咱們分析後一種狀況,也就是分析DecodedDataDocumentParser類的成員函數appendBytes的實現。
DecodedDataDocumentParser類的成員函數appendBytes的實現例如如下所看到的:
void DecodedDataDocumentParser::appendBytes(const char* data, size_t length) { ...... String decoded = m_decoder->decode(data, length); updateDocument(decoded); }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/dom/DecodedDataDocumentParser.cpp中。
DecodedDataDocumentParser類的成員變量m_decoder指向一個TextResourceDecoder對象。
這個TextResourceDecoder對象負責對下載回來的網頁數據進行解碼。解碼後獲得網頁數據的字符串表示。這個字符串將會交給由另一個成員函數updateDocument進行處理。
DecodedDataDocumentParser類的成員函數updateDocument的實現例如如下所看到的:
void DecodedDataDocumentParser::updateDocument(String& decodedData) { ...... if (!decodedData.isEmpty()) append(decodedData.releaseImpl()); }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/dom/DecodedDataDocumentParser.cpp中。
DecodedDataDocumentParser類的成員函數updateDocument又將參數decodedData描寫敘述的網頁內容交給由子類HTMLDocumentParser實現的成員函數append處理。
HTMLDocumentParser類的成員函數append的實現例如如下所看到的:
void HTMLDocumentParser::append(PassRefPtr<StringImpl> inputSource) { ...... String source(inputSource); ...... m_input.appendToEnd(source); ...... if (m_isPinnedToMainThread) pumpTokenizerIfPossible(ForceSynchronous); else pumpTokenizerIfPossible(AllowYield); ...... }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp中。
HTMLDocumentParser類的成員函數append首先將網頁內容附加在成員變量m_input描寫敘述的一個輸入流中,接下來再調用成員函數pumpTokenizerIfPossible對該輸入流中的網頁內容進行解析。
在調用成員函數pumpTokenizerIfPossible的時候,依據成員變量m_isPinnedToMainThread的值的不一樣而傳遞不一樣的參數。當成員變量m_isPinnedToMainThread的值等於true的時候,傳遞的參數爲ForceSynchronous,表示要以同步方式解析網頁的內容。當成員變量m_isPinnedToMainThread的值等於false的時候。傳遞的參數爲AllowYield,表示要以異步方式解析網頁的內容。
在同步解析網頁內容方式中,當前線程會一直運行到所有下載回來的網頁內容都解析完爲止,除非遇到有JavaScript需要運行。在異步解析網頁內容方式中,在遇到有JavaScript需要運行,或者解析的網頁內容超過必定量時。假設當前線程花在解析網頁內容的時間超過預設的閥值,那麼當前線程就會本身主動放棄CPU,經過一個定時器等待一小段時間後再繼續解析剩下的網頁內容。
接下來咱們就繼續分析HTMLDocumentParser類的成員函數pumpTokenizerIfPossible的實現。例如如下所看到的:
void HTMLDocumentParser::pumpTokenizerIfPossible(SynchronousMode mode) { ...... // Once a resume is scheduled, HTMLParserScheduler controls when we next pump. if (isScheduledForResume()) { ASSERT(mode == AllowYield); return; } pumpTokenizer(mode); }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp中。
HTMLDocumentParser類的成員函數pumpTokenizerIfPossible首先調用成員函數isScheduledForResume推斷當前正在處理的HTMLDocumentParser對象是否處於等待從新啓動繼續解析網頁內容的狀態中。假設是的話,等到定時器超時時,當前線程就會本身主動調用當前正在處理的HTMLDocumentParser對象的成員函數pumpTokenizer對剩下未解析的網頁內容進行解析。這樣的狀況必需要確保參數mode的值爲AllowYield。也就是確保當前正在處理的HTMLDocumentParser對象使用異步方式解析網頁內容。
假設當前正在處理的HTMLDocumentParser對象是以同步方式解析網頁內容。那麼HTMLDocumentParser類的成員函數pumpTokenizerIfPossible接下來就會當即調用成員函數pumpTokenizer對剛纔下載回來的網頁內容進行解析。
HTMLDocumentParser類的成員函數pumpTokenizer的實現例如如下所看到的:
void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode) { ...... PumpSession session(m_pumpSessionNestingLevel, contextForParsingSession()); ...... while (canTakeNextToken(mode, session) && !session.needsYield) { ...... if (!m_tokenizer->nextToken(m_input.current(), token())) break; ...... constructTreeFromHTMLToken(token()); ...... } ...... }
這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp中。
HTMLDocumentParser類的成員函數pumpTokenizer經過成員變量m_tokenizer描寫敘述的一個HTMLTokenizer對象的成員函數nextToken對網頁內容進行字符串解析。網頁內容被解析成一系列的Token。每一個Token描寫敘述的要麼是一個標籤,要麼是一個標籤的內容。也就是文本。
有了這些Token以後,HTMLDocumentParser類的成員函數pumpTokenizer就可以構造DOM Tree了。這是經過調用另一個成員函數constructTreeFromHTMLToken進行的。
注意。HTMLDocumentParser類的成員函數pumpTokenizer經過一個while循環依次提取網頁內容的Token,並且每提取一個Token,都會調用一次HTMLDocumentParser類的成員函數constructTreeFromHTMLToken。這個while循環在三種狀況下會結束。
第一種狀況是所有的Token均已提取並且處理完畢。
另一種狀況是在解析的過程當中遇到JavaScript腳本需要運行,這時候調用HTMLDocumentParser類的成員函數canTakeNextToken的返回值會等於false。第三種狀況出現在異步方式解析網頁內容時。這時候HTMLDocumentParser類的成員函數canTakeNextToken會將本地變量session描寫敘述的一個PumpSession對象的成員變量needsYield的值設置爲true。表示當前線程持續解析的網頁內容已經達到必定量並且持續的時間也超過了必定值。需要本身主動放棄使用CPU。
接下來咱們繼續分析HTMLDocumentParser類的成員函數constructTreeFromHTMLToken的實現。例如如下所看到的:
void HTMLDocumentParser::constructTreeFromHTMLToken(HTMLToken& rawToken) { AtomicHTMLToken token(rawToken); ...... m_treeBuilder->constructTree(&token); ...... }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp中。
HTMLDocumentParser類的成員函數constructTreeFromHTMLToken所作的事情就是依據參數rawToken描寫敘述的一個Token來不斷構造網頁的DOM Tree。這個構造過程是經過調用成員變量m_treeBuilder描寫敘述的一個HTMLTreeBuilder對象的成員函數constructTree實現的。
HTMLTreeBuilder類的成員函數constructTree的實現例如如下所看到的:
void HTMLTreeBuilder::constructTree(AtomicHTMLToken* token) { if (shouldProcessTokenInForeignContent(token)) processTokenInForeignContent(token); else processToken(token); ...... m_tree.executeQueuedTasks(); // We might be detached now. }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLTreeBuilder.cpp中。
HTMLTreeBuilder類的成員函數constructTree首先調用成員函數shouldProcessTokenInForeignContent推斷參數token描寫敘述的Token是否爲Foreign Content,即不是HTML標籤相關的內容,而是MathML和SVG這樣的外部標籤相關的內容。假設是的話。就調用成員函數processTokenInForeignContent對它進行處理。
假設參數token描寫敘述的是一個HTML標籤相關的內容,那麼HTMLTreeBuilder類的成員函數constructTree就會調用成員函數processToken對它進行處理。接下來咱們僅僅關注HTML標籤相關內容的處理過程。
處理完畢參數token描寫敘述的Token以後,HTMLTreeBuilder類的成員函數constructTree會調用成員變量m_tree描寫敘述的一個HTMLConstructionSite對象的成員函數executeQueuedTasks運行保存其內部的一個事件隊列中的任務。這些任務是處理參數token描寫敘述的標籤的過程當中加入到事件隊列中去的,主要是爲了處理那些在網頁中沒有正確嵌套的格式化標籤的。HTML標準規定了處理這些沒有正確嵌套的格式化標籤的算法。具體可以參考標準中的12.2.3.3小節:The list of active formatting elements。WebKit在實現這個算法的時候,就用到了上述的事件隊列。
接下來咱們繼續分析HTMLTreeBuilder類的成員函數processToken的實現,例如如下所看到的:
void HTMLTreeBuilder::processToken(AtomicHTMLToken* token) { if (token->type() == HTMLToken::Character) { processCharacter(token); return; } // Any non-character token needs to cause us to flush any pending text immediately. // NOTE: flush() can cause any queued tasks to execute, possibly re-entering the parser. m_tree.flush(); m_shouldSkipLeadingNewline = false; switch (token->type()) { case HTMLToken::Uninitialized: case HTMLToken::Character: ASSERT_NOT_REACHED(); break; case HTMLToken::DOCTYPE: processDoctypeToken(token); break; case HTMLToken::StartTag: processStartTag(token); break; case HTMLToken::EndTag: processEndTag(token); break; case HTMLToken::Comment: processComment(token); break; case HTMLToken::EndOfFile: processEndOfFile(token); break; } }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLTreeBuilder.cpp中。
假設參數token描寫敘述的Token的類型是HTMLToken::Character,就表示該Token表明的是一個普通文本。這些普通文本不會當即進行處理,而是先保存在內部的一個Pending Text緩衝區中,這是經過調用HTMLTreeBuilder類的成員函數processCharacter實現的。等到遇到下一個Token的類型不是HTMLToken::Character時,纔會對它們進行處理。這是經過調用成員變量m_tree描寫敘述的一個HTMLConstructionSite對象的成員函數flush實現的。
對於非HTMLToken::Character類型的Token。HTMLTreeBuilder類的成員函數processToken依據不一樣的類型調用不一樣的成員函數進行處理。
在處理的過程當中。就會使用圖3所看到的的棧構造DOM Tree,並且會遵循HTML規範,具體可以參考這裏:HTML Standard。好比。對於HTMLToken::StartTag類型的Token,就會調用成員函數processStartTag運行一個壓棧操做,而對於HTMLToken::EndTag類型的Token,就會調用成員函數processEndTag運行一個出棧操做。
接下來咱們主要分析HTMLTreeBuilder類的成員函數processStartTag的實現,主要是爲了解WebKit在內部是怎樣描寫敘述一個HTML標籤的。
HTMLTreeBuilder類的成員函數processStartTag的實現例如如下所看到的:
void HTMLTreeBuilder::processStartTag(AtomicHTMLToken* token) { ASSERT(token->type() == HTMLToken::StartTag); switch (insertionMode()) { ...... case InBodyMode: ASSERT(insertionMode() == InBodyMode); processStartTagForInBody(token); break; ...... } }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLTreeBuilder.cpp中。
HTMLTreeBuilder類在構造網頁的DOM Tree時,依據當前所處理的網頁內容而將內部狀態設置爲不一樣的Insertion Mode。這些Insertion Mode是由HTML規範定義的,具體可以參考12.2.3.1小節:The insertion mode。好比,當處理到網頁的body標籤裏面的內容時,Insertion Mode就設置爲InBodyMode,這時候HTMLTreeBuilder類的成員函數processStartTag就調用另一個成員函數processStartTagForInBody依照InBodyMode的Insertion Mode來處理參數token描寫敘述的Token。
接下來咱們繼續分析HTMLTreeBuilder類的成員函數processStartTagForInBody的實現,例如如下所看到的:
void HTMLTreeBuilder::processStartTagForInBody(AtomicHTMLToken* token) { ...... if (token->name() == addressTag || token->name() == articleTag || token->name() == asideTag || token->name() == blockquoteTag || token->name() == centerTag || token->name() == detailsTag || token->name() == dirTag || token->name() == divTag || token->name() == dlTag || token->name() == fieldsetTag || token->name() == figcaptionTag || token->name() == figureTag || token->name() == footerTag || token->name() == headerTag || token->name() == hgroupTag || token->name() == mainTag || token->name() == menuTag || token->name() == navTag || token->name() == olTag || token->name() == pTag || token->name() == sectionTag || token->name() == summaryTag || token->name() == ulTag) { ...... m_tree.insertHTMLElement(token); return; } ...... }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLTreeBuilder.cpp中。
咱們假設參數token描寫敘述的Token表明的是一個<div>標籤。那麼HTMLTreeBuilder類的成員函數processStartTagForInBody就會調用成員變量m_tree描寫敘述的一個HTMLConstructionSite對象的成員函數insertHTMLElement爲其建立一個HTMLElement對象。並且將這個HTMLElement對象壓入棧中去構造DOM Tree。
HTMLConstructionSite類的成員函數insertHTMLElement的實現例如如下所看到的:
void HTMLConstructionSite::insertHTMLElement(AtomicHTMLToken* token) { RefPtrWillBeRawPtr<Element> element = createHTMLElement(token); attachLater(currentNode(), element); m_openElements.push(HTMLStackItem::create(element.release(), token)); }這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLConstructionSite.cpp中。
HTMLConstructionSite類的成員函數insertHTMLElement首先調用成員函數createHTMLElement建立一個HTMLElement對象描寫敘述參數token表明的HTML標籤。接着調用成員函數attachLater稍後將該HTMLElement對象設置爲當前棧頂HTMLElement對象的子HTMLElement對象,最後又將該HTMLElement對象壓入成員變量m_openElements描寫敘述的棧中去。
接下來咱們主要分析HTMLConstructionSite類的成員函數createHTMLElement的實現,以便了解WebKit是怎樣描寫敘述一個HTML標籤的。
HTMLConstructionSite類的成員函數createHTMLElement的實現例如如下所看到的:
PassRefPtrWillBeRawPtr<Element> HTMLConstructionSite::createHTMLElement(AtomicHTMLToken* token) { Document& document = ownerDocumentForCurrentNode(); // Only associate the element with the current form if we're creating the new element // in a document with a browsing context (rather than in <template> contents). HTMLFormElement* form = document.frame() ?這個函數定義在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLConstructionSite.cpp中。m_form.get() : 0; // FIXME: This can't use HTMLConstructionSite::createElement because we // have to pass the current form element. We should rework form association // to occur after construction to allow better code sharing here. RefPtrWillBeRawPtr<Element> element = HTMLElementFactory::createHTMLElement(token->name(), document, form, true); setAttributes(element.get(), token, m_parserContentPolicy); ASSERT(element->isHTMLElement()); return element.release(); }
從這裏可以看到。HTMLConstructionSite類的成員函數createHTMLElement是調用HTMLElementFactory類的靜態成員函數createHTMLElement爲參數token描寫敘述的HTML標籤建立一個HTMLElement對象的。並且接下來還會調用另一個成員函數setAttributes依據Token的內容設置該HTMLElement對象的各個屬性值。
HTMLElementFactory類的靜態成員函數createHTMLElement的實現例如如下所看到的:
typedef HashMap<AtomicString, ConstructorFunction> FunctionMap; static FunctionMap* g_constructors = 0; ...... static void createHTMLFunctionMap() { ASSERT(!g_constructors); g_constructors = new FunctionMap; // Empty array initializer lists are illegal [dcl.init.aggr] and will not // compile in MSVC. If tags list is empty, add check to skip this. static const CreateHTMLFunctionMapData data[] = { { abbrTag, abbrConstructor }, ...... { divTag, divConstructor }, ...... { wbrTag, wbrConstructor }, }; for (size_t i = 0; i < WTF_ARRAY_LENGTH(data); i++) g_constructors->set(data[i].tag.localName(), data[i].func); } PassRefPtrWillBeRawPtr<HTMLElement> HTMLElementFactory::createHTMLElement( const AtomicString& localName, Document& document, HTMLFormElement* formElement, bool createdByParser) { ...... if (ConstructorFunction function = g_constructors->get(localName)) return function(document, formElement, createdByParser); ...... }這個函數定義在文件out/target/product/generic/obj/GYP/shared_intermediates/blink/core/HTMLElementFactory.cpp中。
HTMLElementFactory類的靜態成員函數createHTMLElement依據HTML標籤的名稱在全局變量g_constructors描寫敘述的一個Function Map中找到指定的函數爲該HTML標籤建立一個HTMLElement對象。好比,用來描寫敘述HTML標籤<div>的HTMLElement對象是經過調用函數divConstructor進行建立的。
函數divConstructor的實現例如如下所看到的:
static PassRefPtrWillBeRawPtr<HTMLElement> divConstructor( Document& document, HTMLFormElement* formElement, bool createdByParser) { return HTMLDivElement::create(document); }這個函數定義在文件out/target/product/generic/obj/GYP/shared_intermediates/blink/core/HTMLElementFactory.cpp中。
函數divConstructor調用HTMLDivElement類的靜態成員函數create建立了一個HTMLDivElement對象,並且返回給調用者。
這樣之後咱們需要了解<div>標籤的不少其它細節時,就可以參考HTMLDivElement類的實現。
這樣,咱們就分析完畢網頁的DOM Tree的建立過程了。
咱們沒有很是具體地描寫敘述這個建立過程,因爲這涉及到很是多實現細節,以及極其繁瑣的HTML規範。咱們提供了一個DOM Tree建立的框架。
有了這個框架以後,之後當咱們需要了解某一個細節時,就可以方便地找到相關源代碼進行分析。
網頁內容下載完畢以後,DOM Tree的構造過程就結束。接下來WebKit就會依據DOM Tree建立Render Object Tree。
在接下來一篇文章中。咱們就具體分析Render Object Tree的建立過程。敬請關注!不少其它的信息也可以關注老羅的新浪微博:http://weibo.com/shengyangluo。