zoukankan      html  css  js  c++  java
  • Chromium网页DOM Tree创建过程分析

           在Chromium中。Render进程是通过Browser进程下载网页内容的。后者又是通过共享内存将下载回来的网页内容交给前者的。Render进程获得网页内容之后,会交给WebKit进行处理。WebKit所做的第一个处理就是对网页内容进行解析,解析的结果是得到一棵DOM Tree。DOM Tree是网页的一种结构化描写叙述。也是网页渲染的基础。

    本文接下来就对网页DOM Tree的创建过程进行具体分析。

    老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注。

    《Android系统源代码情景分析》一书正在进击的程序猿网(http://0xcc0xcd.com)中连载。点击进入。

           网页的DOM Tree的根节点是一个Document。

    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创建过程分析一文。

           上面描写叙述的各种对象的关系能够通过图1描写叙述。例如以下所看到的:


    图1 Frame、DOM Window和Document的关系

           从前面Chromium Frame Tree创建过程分析一文能够知道。有的Render Frame仅仅是一个Proxy,称为Render Frame Proxy。

    Render Frame Proxy描写叙述的是在另外一个Render进程中进行载入和渲染的网页。这样的网页在WebKit里面相应的Frame和DOM Window分别称为Remote Frame和Remote DOM Window。

    由于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() ?

    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(); }

           这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLConstructionSite.cpp中。

           从这里能够看到。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

  • 相关阅读:
    Java集合
    C#高级应用
    使用C#分层查询多个表数据
    数据库之SQL语句查询基础
    简要介绍一下MD5加密的书写
    C#简单工厂模式和单列设计模式潜要解析
    Struts2测试题
    小程序自定义组件
    flex布局笔记
    小程序的双线程模型
  • 原文地址:https://www.cnblogs.com/wgwyanfs/p/7018311.html
Copyright © 2011-2022 走看看