当前位置:首页 > 开发教程 > 手机开发 >

Render Object Tree创建过程分析 Chromium网页

时间:2016-02-15 11:49 来源:互联网 作者:源码搜藏 收藏

在前面一文中,我们分析了网页DOM Tree的创建过程。网页DOM Tree创建完成之后,WebKit会根据它的内容创建一个Render Object Tree。Render Object Tree是和网页渲染有关的一个Tree。这意味着只有在DOM Tree中需要渲染的节点才会在Render Object Tree中有对应

在前面一文中,我们分析了网页DOM Tree的创建过程。网页DOM Tree创建完成之后,WebKit会根据它的内容创建一个Render Object Tree。Render Object Tree是和网页渲染有关的一个Tree。这意味着只有在DOM Tree中需要渲染的节点才会在Render Object Tree中有对应节点。本文接下来就分析网页Render Object Tree的创建过程。

       从前面Chromium DOM Tree创建过程分析一文可以知道,每一个HTML标签在DOM Tree中都有一个对应的HTMLElement节点。相应地,在DOM Tree中每一个需要渲染的HTMLElement节点在Render Object Tree中都有一个对应的RenderObject节点,如图1所示:

Render Object Tree创建过程分析 Chromium网页

图1 Render Object Tree与DOM Tree、Render Layer Tree和Graphics Layer Tree的关系

       从图1还可以看到,Render Object Tree创建完成之后,WebKit还会继续根据它的内容创建一个Render Layer Tree和一个Graphics Layer Tree。本文主要关注Render Object Tree的创建过程。

       从前面Chromium DOM Tree创建过程分析一文还可以知道,DOM Tree是在网页内容的下载过程中创建的。一旦网页内容下载完成,DOM Tree就创建完成了。网页的Render Object Tree与DOM Tree不一样,它是在网页内容下载完成之后才开始创建的。因此,接下来我们就从网页内容下载完成时开始分析网页的Render Object Tree的创建过程。

       从前面Chromium网页URL加载过程分析一文可以知道,WebKit是通过Browser进程下载网页内容的。Browser进程一方面通过Net模块中的URLRequest类去Web服务器请求网页内容,另一方面又通过Content模块中的ResourceLoader类的成员函数OnReadCompleted不断地获得URLRequest类请求回来的网页内容,如下所示:

 

[cpp] view plain
  1. void ResourceLoader::OnReadCompleted(net::URLRequest* unused, int bytes_read) {    
  2.   ......    
  3.     
  4.   CompleteRead(bytes_read);    
  5.     
  6.   ......    
  7.     
  8.   if (bytes_read > 0) {    
  9.     StartReading(true);  // Read the next chunk.    
  10.   } else {    
  11.     // URLRequest reported an EOF. Call ResponseCompleted.    
  12.     DCHECK_EQ(0, bytes_read);    
  13.     ResponseCompleted();    
  14.   }    
  15. }    
       这个函数定义在文件external/chromium_org/content/browser/loader/resource_loader.cc中。

 

       参数bytes_read表示当前这次从URLRequest类中读取回来的网页内容的长度。当这个长度值等于0的时候,就表示所有的网页内容已经读取完毕。这时候ResourceLoader类的成员函数OnReadCompleted就会调用另外一个成员函数ResponseCompleted进行下一步处理。

       ResourceLoader类的成员函数ResponseCompleted的实现如下所示:

 

[cpp] view plain
  1. void ResourceLoader::ResponseCompleted() {  
  2.   ......  
  3.   
  4.   handler_->OnResponseCompleted(request_->status(), security_info, &defer);  
  5.     
  6.   ......  
  7. }  
      这个函数定义在文件external/chromium_org/content/browser/loader/resource_loader.cc中。

 

      在前面Chromium网页URL加载过程分析一文中,我们假设ResourceLoader类的成员变量handler_指向的是一个AsyncResourceHandler对象。ResourceLoader类的成员函数ResponseCompleted调用这个AsyncResourceHandler对象的成员函数OnResponseCompleted进行下一步处理。

      AsyncResourceHandler类的成员函数OnResponseCompleted的实现如下所示:

 

[cpp] view plain
  1. void AsyncResourceHandler::OnResponseCompleted(  
  2.     const net::URLRequestStatus& status,  
  3.     const std::string& security_info,  
  4.     bool* defer) {  
  5.   const ResourceRequestInfoImpl* info = GetRequestInfo();  
  6.   ......  
  7.   
  8.   ResourceMsg_RequestCompleteData request_complete_data;  
  9.   request_complete_data.error_code = error_code;  
  10.   request_complete_data.was_ignored_by_handler = was_ignored_by_handler;  
  11.   request_complete_data.exists_in_cache = request()->response_info().was_cached;  
  12.   request_complete_data.security_info = security_info;  
  13.   request_complete_data.completion_time = TimeTicks::Now();  
  14.   request_complete_data.encoded_data_length =  
  15.       request()->GetTotalReceivedBytes();  
  16.   info->filter()->Send(  
  17.       new ResourceMsg_RequestComplete(GetRequestID(), request_complete_data));  
  18. }  
       这个函数定义在文件external/chromium_org/content/browser/loader/async_resource_handler.cc中。

 

       AsyncResourceHandler类的成员函数OnResponseCompleted所做的事情是向Render进程发送一个类型为ResourceMsg_RequestComplete的IPC消息,用来通知Render进程它所请求的网页内容已下载完毕。

       Render进程是通过ResourceDispatcher类的成员函数DispatchMessage接收类型为ResourceMsg_RequestComplete的IPC消息的,如下所示:

 

[cpp] view plain
  1. void ResourceDispatcher::DispatchMessage(const IPC::Message& message) {  
  2.   IPC_BEGIN_MESSAGE_MAP(ResourceDispatcher, message)  
  3.     ......  
  4.     IPC_MESSAGE_HANDLER(ResourceMsg_RequestComplete, OnRequestComplete)  
  5.   IPC_END_MESSAGE_MAP()  
  6. }  
      这个函数定义在文件external/chromium_org/content/child/resource_dispatcher.cc中。

 

      从这里可以看到,ResourceDispatcher类的成员函数DispatchMessage将类型为ResourceMsg_RequestComplete的IPC消息分发给另外一个成员函数OnRequestComplete处理。

      ResourceDispatcher类的成员函数OnRequestComplete的实现如下所示:

 

[cpp] view plain
  1. void ResourceDispatcher::OnRequestComplete(  
  2.     int request_id,  
  3.     const ResourceMsg_RequestCompleteData& request_complete_data) {  
  4.   ......  
  5.   
  6.   PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);  
  7.   ......  
  8.   
  9.   RequestPeer* peer = request_info->peer;  
  10.   ......  
  11.   
  12.   peer->OnCompletedRequest(request_complete_data.error_code,  
  13.                            request_complete_data.was_ignored_by_handler,  
  14.                            request_complete_data.exists_in_cache,  
  15.                            request_complete_data.security_info,  
  16.                            renderer_completion_time,  
  17.                            request_complete_data.encoded_data_length);  
  18. }  

       这个函数定义在文件external/chromium_org/content$ vi child/resource_dispatcher.cc中。

       从前面Chromium网页URL加载过程分析一文可以知道,Render进程在请求Browser进程下载指定URL对应的网页内容之前,会创建一个PendingRequestInfo对象。这个PendingRequestInfo对象以一个Request ID为键值保存在ResourceDispatcher类的内部。这个Request ID即为参数request_id描述的Request ID。因此,ResourceDispatcher类的成员函数OnRequestComplete可以通过参数request_id获得一个PendingRequestInfo对象。有了这个PendingRequestInfo对象之后,ResourceDispatcher类的成员函数OnSetDataBuffer再通过它的成员变量peer获得一个WebURLLoaderImpl::Context对象,并且调用它的成员函数OnCompletedRequest通知它下载网页内容的请求已完成。

       WebURLLoaderImpl::Context类的成员函数OnCompletedRequest的实现如下所示:

 

[cpp] view plain
  1. void WebURLLoaderImpl::Context::OnCompletedRequest(  
  2.     int error_code,  
  3.     bool was_ignored_by_handler,  
  4.     bool stale_copy_in_cache,  
  5.     const std::string& security_info,  
  6.     const base::TimeTicks& completion_time,  
  7.     int64 total_transfer_size) {  
  8.   ......  
  9.   
  10.   if (client_) {  
  11.     if (error_code != net::OK) {  
  12.       client_->didFail(loader_, CreateError(request_.url(),  
  13.                                             stale_copy_in_cache,  
  14.                                             error_code));  
  15.     } else {  
  16.       client_->didFinishLoading(  
  17.           loader_, (completion_time - TimeTicks()).InSecondsF(),  
  18.           total_transfer_size);  
  19.     }  
  20.   }  
  21.   
  22.   ......  
  23. }  
       这个函数定义在文件external/chromium_org/content/child/web_url_loader_impl.cc中。

 

       从前面Chromium网页URL加载过程分析一文可以知道,WebURLLoaderImpl::Context类的成员变量client_指向的是WebKit模块中的一个ResourceLoader对象。在成功下载完成网页内容的情况下,WebURLLoaderImpl::Context类的成员函数OnCompletedRequest调用这个ResourceLoader对象的成员函数didFinishLoading通知WebKit结束解析网页内容。

       ResourceLoader类的成员函数didFinishLoading的实现如下所示:

 

[cpp] view plain
  1. void ResourceLoader::didFinishLoading(blink::WebURLLoader*, double finishTime, int64 encodedDataLength)  
  2. {  
  3.     ......  
  4.   
  5.     m_resource->finish(finishTime);  
  6.   
  7.     ......  
  8. }  

 

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

        ResourceLoader类的成员变量m_resource描述的是一个RawResource对象。这个RawResource对象的创建过程可以参考前面Chromium网页URL加载过程分析一文。ResourceLoader类的成员函数didFinishLoading调用这个RawResource对象的成员函数finish结束加载网页内容。

        RawResource类的成员函数finish是从父类Resource继承下来的,它的实现如下所示:

 

[cpp] view plain
  1. void Resource::finish(double finishTime)  
  2. {  
  3.     ......  
  4.     finishOnePart();  
  5.     ......  
  6. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/fetch/Resource.cpp中。

 

       Resource类的成员函数finish调用另外一个成员函数finishOnePart结束加载网页的内容。注意Resource类的成员函数finishOnePart的命名。有前面Chromium网页URL加载过程分析一文中,我们提到,当网页内容的MIME类型为“multipart/x-mixed-replace”时,下载回来网页内容实际是包含多个部分的,每一个部分都有着自己的MIME类型。每一个部分下载完成时,都会调用Resource类的成员函数finishOnePart进行处理。为了统一接口,对于MIME类型不是“multipart/x-mixed-replace”的网页内容而言,下载回来的网页内容也是当作一个部分进行整体处理。

       Resource类的成员函数finishOnePart的实现如下所示:

 

[cpp] view plain
  1. void Resource::finishOnePart()  
  2. {  
  3.     ......  
  4.     checkNotify();  
  5. }  
      这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/fetch/Resource.cpp中。

 

      Resource类的成员函数finishOnePart调用另外一个成员函数checkNotify通知当前正在前处理的Resource对象的Client,它们所关注的资源,也就是网页内容,已经下载完成了。

      Resource类的成员函数checkNotify的实现如下所示:

 

[cpp] view plain
  1. void Resource::checkNotify()  
  2. {  
  3.     ......  
  4.   
  5.     ResourceClientWalker<ResourceClient> w(m_clients);  
  6.     while (ResourceClient* c = w.next())  
  7.         c->notifyFinished(this);  
  8. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/fetch/Resource.cpp中。

 

       从前面Chromium网页URL加载过程分析一文可以知道,在Resource类的成员变量m_clients中,保存有一个DocumentLoader对象。这个DocumentLoader对象是从ResourceClient类继承下来的,它负责创建和加载网页的文档对象。Resource类的成员函数checkNotify会调用这个DocumentLoader对象的成员函数notifyFinished通知它要加载的网页的内容已经下载完成了。

       DocumentLoader类的成员函数notifyFinished的实现如下所示:

 

[cpp] view plain
  1. void DocumentLoader::notifyFinished(Resource* resource)  
  2. {  
  3.     ......  
  4.   
  5.     if (!m_mainResource->errorOccurred() && !m_mainResource->wasCanceled()) {  
  6.         finishedLoading(m_mainResource->loadFinishTime());  
  7.         return;  
  8.     }  
  9.   
  10.     ......  
  11. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/loader/DocumentLoader.cpp中。

 

       DocumentLoader类的成员变量m_mainResource指向的是一个RawResource对象。这个RawResource对象和前面分析的ResourceLoader类的成员变量m_resource指向的是同一个RawResource对象。这个RawResource对象代表正在请求下载的网页内容。在网页内容成功下载完成的情况下,DocumentLoader类的成员函数notifyFinished就会调用另外一个成员函数finishedLoading进行结束处理。

       DocumentLoader类的成员函数finishedLoading的实现如下所示:

 

[cpp] view plain
  1. void DocumentLoader::finishedLoading(double finishTime)  
  2. {  
  3.     ......  
  4.   
  5.     endWriting(m_writer.get());  
  6.   
  7.     ......  
  8. }  
      这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/loader/DocumentLoader.cpp中。

 

      从前面Chromium网页DOM Tree创建过程分析一文可以知道,DocumentLoader类的成员变量m_writer指向的是一个DocumentWriter对象。DocumentLoader类的成员函数finishedLoading调用另外一个成员函数endWriting告诉这个DocumentWriter对象结束对正在加载的网页内容的解析。

      DocumentLoader类的成员函数endWriting的实现如下所示:

 

[cpp] view plain
  1. void DocumentLoader::endWriting(DocumentWriter* writer)  
  2. {  
  3.     ......  
  4.     m_writer->end();  
  5.     .....  
  6. }  
      这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/loader/DocumentLoader.cpp中。

 

      DocumentLoader类的成员函数endWriting调用上述DocumentWriter对象的成员函数end结束对正在加载的网页内容的解析。

      DocumentWriter类的成员函数end的实现如下所示:

 

[cpp] view plain
  1. void DocumentWriter::end()  
  2. {  
  3.     ......  
  4.   
  5.     m_parser->finish();  
  6.       
  7.     ......  
  8. }  
      这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/loader/DocumentWriter.cpp中。

 

      从前面Chromium网页DOM Tree创建过程分析一文可以知道,DocumentWriter类的成员变量m_parser指向的是一个HTMLDocumentParser对象。DocumentWriter类的成员函数end调用这个HTMLDocumentParser对象的成员函数finish结束对正在加载的网页内容的解析。

      HTMLDocumentParser类的成员函数finish的实现如下所示:

 

[cpp] view plain
  1. void HTMLDocumentParser::finish()  
  2. {  
  3.     ......  
  4.   
  5.     attemptToEnd();  
  6. }  
      这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp中。

 

      HTMLDocumentParser类的成员函数finish调用另外一个成员函数attemptToEnd结束对正在加载的网页内容的解析。

      HTMLDocumentParser类的成员函数attemptToEnd的实现如下所示:

 

[cpp] view plain
  1. void HTMLDocumentParser::attemptToEnd()  
  2. {  
  3.     // finish() indicates we will not receive any more data. If we are waiting on  
  4.     // an external script to load, we can't finish parsing quite yet.  
  5.   
  6.     if (shouldDelayEnd()) {  
  7.         m_endWasDelayed = true;  
  8.         return;  
  9.     }  
  10.     prepareToStopParsing();  
  11. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp中。

 

       如果网页包含外部JavaScript脚本,并且这些外部JavaScript脚本还没有下载回来,那么这时候HTMLDocumentParser类的成员函数attemptToEnd就还不能结束对正在加载的网页内容的解析,必须要等到外部JavaScript脚本下载回来之后才能进行结束。等到外部JavaScript脚本下载回来之后,HTMLDocumentParser类的成员函数

       另一方面,如果网页没有包含外部JavaScript脚本,那么HTMLDocumentParser类的成员函数attemptToEnd就会马上调用另外一个成员函数prepareToStopParsing结束对正在加载的网页内容的解析。在网页包含外部JavaScript脚本的情况下,等到这些外部JavaScript脚本下载回来处理之后,HTMLDocumentParser类的成员函数prepareToStopParsing也是同样会被调用的。因此,接下来我们就继续分析HTMLDocumentParser类的成员函数prepareToStopParsing的实现。

       HTMLDocumentParser类的成员函数prepareToStopParsing的实现如下所示:

 

[cpp] view plain
  1. void HTMLDocumentParser::prepareToStopParsing()  
  2. {  
  3.     ......  
  4.   
  5.     attemptToRunDeferredScriptsAndEnd();  
  6. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp中。

 

       HTMLDocumentParser类的成员函数prepareToStopParsing调用另外一个成员函数attemptToRunDeferredScriptsAndEnd执行那些被延后执行的JavaScript脚本,以及结束对正在加载的网页内容的解析。

       HTMLDocumentParser类的成员函数attemptToRunDeferredScriptsAndEnd的实现如下所示:

 

[cpp] view plain
  1. void HTMLDocumentParser::attemptToRunDeferredScriptsAndEnd()  
  2. {  
  3.     ......  
  4.   
  5.     if (m_scriptRunner && !m_scriptRunner->executeScriptsWaitingForParsing())  
  6.         return;  
  7.     end();  
  8. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp中。

 

       HTMLDocumentParser类的成员变量m_scriptRunner指向的是一个HTMLScriptRunner对象。HTMLDocumentParser类的成员函数attemptToRunDeferredScriptsAndEnd调用这个HTMLScriptRunner对象的成员函数executeScriptsWaitingForParsing执行那些被延后执行的JavaScript脚本之后,就会调用HTMLDocumentParser类的成员函数end结束对正在加载的网页内容的解析。

       HTMLDocumentParser类的成员函数end的实现如下所示:

 

[cpp] view plain
  1. void HTMLDocumentParser::end()  
  2. {  
  3.     ......  
  4.   
  5.     // Informs the the rest of WebCore that parsing is really finished (and deletes this).  
  6.     m_treeBuilder->finished();  
  7. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp中。

 

       HTMLDocumentParser类的成员变量m_treeBuilder指向的是一个HTMLTreeBuilder对象。HTMLDocumentParser类的成员函数end调用这个HTMLTreeBuilder对象的成员函数finished告诉它结束对网页内容的解析。

       HTMLTreeBuilder类的成员函数finished的实现如下所示:

 

[cpp] view plain
  1. void HTMLTreeBuilder::finished()  
  2. {  
  3.     ......  
  4.   
  5.     m_tree.finishedParsing();  
  6. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLTreeBuilder.cpp中。

 

       HTMLTreeBuilder类的成员变量m_tree描述的是一个HTMLConstructionSite对象。从前面Chromium网页DOM Tree创建过程分析一文可以知道,这个HTMLConstructionSite对象就是用来构造网页的DOM Tree的,HTMLTreeBuilder类的成员函数finished调用它的成员函数finishedParsing告诉它结束构造网页的DOM Tree。

       HTMLConstructionSite类的成员函数finishedParsing的实现如下所示:

 

[cpp] view plain
  1. void HTMLConstructionSite::finishedParsing()  
  2. {  
  3.     ......  
  4.   
  5.     m_document->finishedParsing();  
  6. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/parser/HTMLConstructionSite.cpp中。

 

       HTMLConstructionSite类的成员变量m_document指向的是一个HTMLDocument对象。这个HTMLDocument对象描述的是网页的DOM Tree的根节点,HTMLConstructionSite类的成员函数finishedParsing调用它的成员函数finishedParsing通知它DOM Tree创建结束。

       HTMLDocument类的成员函数finishedParsing是从父类Document类继承下来的,它的实现如下所示:

 

[cpp] view plain
  1. void Document::finishedParsing()  
  2. {  
  3.     ......  
  4.   
  5.     if (RefPtr<LocalFrame> f = frame()) {  
  6.         ......  
  7.         const bool mainResourceWasAlreadyRequested =  
  8.             m_frame->loader().stateMachine()->committedFirstRealDocumentLoad();  
  9.   
  10.         ......  
  11.         if (mainResourceWasAlreadyRequested)  
  12.             updateRenderTreeIfNeeded();  
  13.   
  14.         ......  
  15.     }  
  16.   
  17.     ......  
  18. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Document.cpp中。

       HTMLDocument类的成员函数finishedParsing首先判断网页的主资源是否已经请求回来了。在请求回来的情况下,才会调用另外一个成员函数updateRenderTreeIfNeeded创建一个Render Object Tree。网页的主资源,指的就是网页文本类型的内容,不包括Image、CSS和Script等资源。

       HTMLDocument类的成员函数updateRenderTreeIfNeeded的实现如下所示:

 

[cpp] view plain
  1. class Document : public ContainerNode, public TreeScope, public SecurityContext, public ExecutionContext, public ExecutionContextClient  
  2.     , public DocumentSupplementable, public LifecycleContext<Document> {  
  3.     ......  
  4. public:  
  5.     ......  
  6.   
  7.     void updateRenderTreeIfNeeded() { updateRenderTree(NoChange); }  
  8.   
  9.     ......  
  10. };  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Document.h中。

 

       HTMLDocument类的成员函数updateRenderTreeIfNeeded调用另外一个成员函数updateRenderTree创建一个Render Object Tree。

       HTMLDocument类的成员函数updateRenderTree的实现如下所示:

 

[cpp] view plain
  1. void Document::updateRenderTree(StyleRecalcChange change)  
  2. {  
  3.     ......  
  4.   
  5.     updateStyle(change);  
  6.   
  7.     ......  
  8. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Document.cpp中。

 

       HTMLDocument类的成员函数updateRenderTree会调用另外一个成员函数updateStyle更新网页各个元素的CSS属性。HTMLDocument类的成员函数updateStyle在更新网页各个元素的CSS属性的过程中,会分别为它们创建一个对应的Render Object。这些Render Object最终就会形成一个Render Object Tree。

       HTMLDocument类的成员函数updateStyle的实现如下所示:

 

[cpp] view plain
  1. void Document::updateStyle(StyleRecalcChange change)  
  2. {  
  3.     ......  
  4.   
  5.     if (styleChangeType() >= SubtreeStyleChange)  
  6.         change = Force;  
  7.   
  8.     ......  
  9.   
  10.     if (change == Force) {  
  11.         ......  
  12.         RefPtr<RenderStyle> documentStyle = StyleResolver::styleForDocument(*this);  
  13.         StyleRecalcChange localChange = RenderStyle::stylePropagationDiff(documentStyle.get(), renderView()->style());  
  14.         if (localChange != NoChange)  
  15.             renderView()->setStyle(documentStyle.release());  
  16.     }  
  17.   
  18.     ......  
  19.   
  20.     if (Element* documentElement = this->documentElement()) {  
  21.         ......  
  22.         if (documentElement->shouldCallRecalcStyle(change))  
  23.             documentElement->recalcStyle(change);  
  24.         ......  
  25.     }  
  26.   
  27.     ......  
  28. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Document.cpp中。

 

       从前面Chromium网页DOM Tree创建过程分析一文可以知道,当前正在处理的Document对象实际上是一个HTMLDocument对象。这个HTMLDocument对象即为网页DOM Tree的根节点,它的子孙节点就是网页中的各个HTML标签。在DOM Tree创建之初,这些HTML标签的CSS属性还没有进行计算,因此这时候DOM Tree的根节点就会被标记为子树CSS属性需要进行计算,也就是调用当前正在处理的Document对象的成员函数styleChangeType获得的值会等于SubtreeStyleChange。在这种情况下,参数change的值也会被修改为Force,表示要对每一个HTML标签的CSS属性进行一次计算和设置。

       接下来,HTMLDocument类的成员函数updateStyle首先是计算根节点的CSS属性,这是通过调用StyleResolver类的静态成员函数styleForDocument实现的,接着又比较根节点新的CSS属性与旧的CSS属性是否有不同的地方。如果有不同的地方,那么就会将新的CSS属性值保存在与根节点对应的Render Object中。

       从前面Chromium网页DOM Tree创建过程分析一文可以知道,DOM Tree的根节点,也就是一个HTMLDocument对象,是在解析网页内容之前就已经创建好了的,并且在创建这个HTMLDocument对象的时候,会给它关联一个Render Object。这个Render Object实际上是一个RenderView对象。这个RenderView对象就作为网页Render Object Tree的根节点。

       HTMLDocument类的成员函数updateStyle调用另外一个成员函数renderView()可以获得上面描述的RenderView对象。有了这个RenderView对象之后,调用它的成员函数style就可以获得它原来设置的CSS属性,同时调用它的成员函数setStyle可以给它设置新的CSS。

       更新好DOM Tree的根节点的CSS属性之后,HTMLDocument类的成员函数updateStyle接下来继续更新它的子节点的CSS属性,也就是网页的<html>标签的CSS属性。从前面Chromium网页DOM Tree创建过程分析一文可以知道,网页的<html>标签在DOM Tree中通过一个HTMLHtmlElement对象描述。这个HTMLHtmlElement对象可以通过调用当前正在处理的Document对象的成员函数documentElement获得。有了这个HTMLHtmlElement对象之后,就可以调用它的成员函数recalcStyle更新它以及它的子节点的CSS属性了。

       HTMLHtmlElement类的成员函数recalcStyle是从父类Element继承下来的,它的实现如下所示:

 

[cpp] view plain
  1. void Element::recalcStyle(StyleRecalcChange change, Text* nextTextSibling)  
  2. {  
  3.     ......  
  4.   
  5.     if (change >= Inherit || needsStyleRecalc()) {  
  6.         ......  
  7.         if (parentRenderStyle())  
  8.             change = recalcOwnStyle(change);  
  9.         ......  
  10.     }  
  11.   
  12.     ......  
  13. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Element.cpp中。

 

       从前面的调用过程可以知道,参数change的值等于Force,它的值是大于Inherit的。在这种情况下,如果当前正在处理的DOM节点的父节点的CSS属性已经计算好,也就是调用成员函数parentRenderStyle的返回值不等于NULL,那么Element类的成员函数recalcStyle就会重新计算当前正在处理的DOM节点的CSS属性。这是通过调用Element类的成员函数recalcOwnStyle实现的。

       另一方面,如果参数change的值小于Inherit,但是当前正在处理的DOM节点记录了它的CSS属性确实发生了变化需要重新计算,也就是调用成员函数needsStyleRecalc获得的返值为true。那么Element类的成员函数recalcStyle也会调用另外一个成员函数recalcOwnStyle重新计算当前正在处理的DOM节点的CSS属性。

       Element类的成员函数recalcOwnStyle的实现如下所示:

 

[cpp] view plain
  1. StyleRecalcChange Element::recalcOwnStyle(StyleRecalcChange change)  
  2. {  
  3.     ......  
  4.   
  5.     RefPtr<RenderStyle> oldStyle = renderStyle();  
  6.     RefPtr<RenderStyle> newStyle = styleForRenderer();  
  7.     StyleRecalcChange localChange = RenderStyle::stylePropagationDiff(oldStyle.get(), newStyle.get());  
  8.   
  9.     ......  
  10.   
  11.     if (localChange == Reattach) {  
  12.         AttachContext reattachContext;  
  13.         reattachContext.resolvedStyle = newStyle.get();  
  14.         ......  
  15.         reattach(reattachContext);  
  16.         ......  
  17.     }  
  18.   
  19.     ......  
  20. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Element.cpp中。

 

       Element类的成员函数recalcOwnStyle首先调用成员函数renderStyle获得当前正处理的DOM节点的原来设置的CSS属性。由于当前正在处理的DOM节点还没有计算过CSS属性,因此前面获得的CSS属性就为空。Element类的成员函数recalcOwnStyle接下来又调用成员函数styleForRenderer计算当前正在处理的DOM节点的CSS属性,这是通过解析网页内容得到的。在这种情况下调用RenderStyle类的静态成员函数stylePropagationDiff比较前面获得的两个CSS属性,会得一个值为Reattach的返回值,表示要为当前正在处理的DOM节点创建一个Render Object,并且将这个Render Object加入到网页的Render Object Tree中去。这是通过调用Element类的成员函数reattach实现的。

       Element类的成员函数reattach是从父类Node继承下来的,它的实现如下所示:

 

[cpp] view plain
  1. void Node::reattach(const AttachContext& context)  
  2. {  
  3.     AttachContext reattachContext(context);  
  4.     reattachContext.performingReattach = true;  
  5.   
  6.     ......  
  7.   
  8.     attach(reattachContext);  
  9. }  

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

      Node类的成员函数reattach主要是调用另外一个成员函数attach为当前正在处理的DOM节点创建一个Render Object。从前面的分析可以知道,当前正在处理的DOM节点是DOM Tree的根节点,也就是一个HTMLHtmlElement对象。HTMLHtmlElement类是从Element类继承下来的,Element类又是从Node类继承下来的,并且它重写了Node类的成员函数attach。因此,在我们这个情景中,Node类的成员函数reattach实际上调用的是Element类的成员函数attach。

      Element类的成员函数attach的实现如下所示:

 

[cpp] view plain
  1. void Element::attach(const AttachContext& context)  
  2. {  
  3.     ......  
  4.   
  5.     RenderTreeBuilder(this, context.resolvedStyle).createRendererForElementIfNeeded();  
  6.   
  7.     ......  
  8.   
  9.     ContainerNode::attach(context);  
  10.   
  11.     ......  
  12. }  
      这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Element.cpp中。

 

      Element类的成员函数attach首先是根据当前正在处理的DOM节点的CSS属性创建一个RenderTreeBuilder对象,接着调用这个RenderTreeBuilder对象的成员函数createRendererForElementIfNeeded判断是否需要为当前正在处理的DOM节点创建的一个Render Object,并且在需要的情况下进行创建。

      Element类的成员函数attach最后还会调用父类ContainerNode的成员函数attach递归为当前正在处理的DOM节点的所有子孙节点分别创建一个Render Object,从而就得到一个Render Object Tree。

      接下来,我们首先分析RenderTreeBuilder类的成员函数createRendererForElementIfNeeded的实现,接着再分析ContainerNode类的成员函数attach的实现。

      RenderTreeBuilder类的成员函数createRendererForElementIfNeeded的实现如下所示:

 

[cpp] view plain
  1. void RenderTreeBuilder::createRendererForElementIfNeeded()  
  2. {  
  3.     ......  
  4.   
  5.     Element* element = toElement(m_node);  
  6.     RenderStyle& style = this->style();  
  7.   
  8.     if (!element->rendererIsNeeded(style))  
  9.         return;  
  10.   
  11.     RenderObject* newRenderer = element->createRenderer(&style);  
  12.     ......  
  13.   
  14.     RenderObject* parentRenderer = this->parentRenderer();  
  15.     ......  
  16.   
  17.     element->setRenderer(newRenderer);  
  18.     newRenderer->setStyle(&style); // setStyle() can depend on renderer() already being set.  
  19.   
  20.     parentRenderer->addChild(newRenderer, nextRenderer);  
  21. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/RenderTreeBuilder.cpp中。

 

       RenderTreeBuilder类的成员变量m_node描述的是当前正在处理的DOM节点。这个DOM节点对象类型一定是从Element类继承下来的,因此RenderTreeBuilder类的成员函数createRendererForElementIfNeeded可以通过调用另外一个成员函数toElment将其转换为一个Element对象。

       RenderTreeBuilder类的成员函数createRendererForElementIfNeeded接下来还会通过调用另外一个成员函数style获得当前正在处理的DOM节点的CSS属性对象,然后再以这个CSS属性对象为参数,调用上面获得的Element对象的成员函数rendererIsNeeded判断是否需要为当前正在处理的DOM节点创建一个Render Object。如果不需要,那么RenderTreeBuilder类的成员函数createRendererForElementIfNeeded就直接返回了。

       Element类的成员函数rendererIsNeeded的实现如下所示:

 

[cpp] view plain
  1. bool Element::rendererIsNeeded(const RenderStyle& style)  
  2. {  
  3.     return style.display() != NONE;  
  4. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Element.cpp

 

       从这里可以看到,当一个DOM节点的display属性被设置为none时,WebKit就不会为它创建一个Render Object,也就是当一个DOM节点不需要渲染或者不可见时,就不需要为它创建一个Render Object。

       回到RenderTreeBuilder类的成员函数createRendererForElementIfNeeded中,假设需要为前正在处理的DOM节点创建Render Object,那么RenderTreeBuilder类的成员函数createRendererForElementIfNeeded接下来就会调用上面获得的Element对象的成员函数createRenderer为其创建一个Render Object。

       为当前正在处理的DOM节点创建了Render Object之后,RenderTreeBuilder类的成员函数createRendererForElementIfNeeded接下来还做了三件事情:

       1. 将新创建的Render Object与当前正在处理的DOM节点关联起来。这是通过调用Element类的成员函数setRenderer实现的。

       2. 将用来描述当前正在处理的DOM节点的CSS属性对象设置给新创建的Render Object,以便新创建的Render Object后面可以根据这个CSS属性对象绘制自己。这是通过调用RenderObject类的成员函数setStyle实现的。

       3. 获得与当前正在处理的DOM节点对应的Render Object,并且将新创建的Render Object作为这个Render Object的子节点,从而形成一个Render Object Tree。这是通过调用RenderObject类的成员函数addChild实现的。

       接下来,我们主要分析Element类的成员函数createRenderer为一个DOM节点创建一个Render Object的过程,如下所示:

 

[cpp] view plain
  1. RenderObject* Element::createRenderer(RenderStyle* style)  
  2. {  
  3.     return RenderObject::createObject(this, style);  
  4. }  
      这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Element.cpp中。

 

      Element类的成员函数createRenderer是通过调用RenderObject类的静态成员函数createObject为当前正在处理的DOM节点创建一个Render Object的。

      RenderObject类的静态成员函数createObject的实现如下所示:

 

[cpp] view plain
  1. RenderObject* RenderObject::createObject(Element* element, RenderStyle* style)  
  2. {  
  3.     ......  
  4.   
  5.     switch (style->display()) {  
  6.     case NONE:  
  7.         return 0;  
  8.     case INLINE:  
  9.         return new RenderInline(element);  
  10.     case BLOCK:  
  11.     case INLINE_BLOCK:  
  12.         return new RenderBlockFlow(element);  
  13.     case LIST_ITEM:  
  14.         return new RenderListItem(element);  
  15.     case TABLE:  
  16.     case INLINE_TABLE:  
  17.         return new RenderTable(element);  
  18.     case TABLE_ROW_GROUP:  
  19.     case TABLE_HEADER_GROUP:  
  20.     case TABLE_FOOTER_GROUP:  
  21.         return new RenderTableSection(element);  
  22.     case TABLE_ROW:  
  23.         return new RenderTableRow(element);  
  24.     case TABLE_COLUMN_GROUP:  
  25.     case TABLE_COLUMN:  
  26.         return new RenderTableCol(element);  
  27.     case TABLE_CELL:  
  28.         return new RenderTableCell(element);  
  29.     case TABLE_CAPTION:  
  30.         return new RenderTableCaption(element);  
  31.     case BOX:  
  32.     case INLINE_BOX:  
  33.         return new RenderDeprecatedFlexibleBox(element);  
  34.     case FLEX:  
  35.     case INLINE_FLEX:  
  36.         return new RenderFlexibleBox(element);  
  37.     case GRID:  
  38.     case INLINE_GRID:  
  39.         return new RenderGrid(element);  
  40.     }  
  41.   
  42.     return 0;  
  43. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderObject.cpp中。

 

       RenderObject类的静态成员函数createObject主要是根据参数element描述的一个DOM节点的display属性值创建一个具体的Render Object。例如,如果参数element描述的DOM节点的display属性值为BLOCK或者INLINE_BLOCK,那么RenderObject类的静态成员函数createObject为它创建的就是一个类型为RenderBlockFlow的Render Object。

       不管是哪一种类型的Render Object,它们都是间接从RenderBoxModelObject类继承下来的。RenderBoxModelObject类又是间接从RenderObject类继承下来的,它描述的是一个CSS Box Model,如图2所示:

Render Object Tree创建过程分析 Chromium网页

图2 CSS Box Model

       关于CSS Box Model的详细描述,可以参考这篇文章:CSS Box Model and Positioning。简单来说,就是一个CSS Box Model由margin、border、padding和content四部分组成。其中,margin、border和padding又分为top、bottom、left和right四个值。一个Render Object在绘制之前,会先进行Layout。Layout的目的就是确定一个Render Object的CSS Box Model的margin、border和padding值。一旦这些值确定之后,再结合content值,就可以对一个Render Object进行绘制了。

       这一步执行完成后,回到Element类的成员函数attach中,接下来它会调用父类ContainerNode的成员函数attach递归为当前正在处理的DOM节点的所有子孙节点分别创建一个Render Object,从而形成一个Render Object Tree。

       ContainerNode类的成员函数attach的实现如下所示:

 

[cpp] view plain
  1. void ContainerNode::attach(const AttachContext& context)  
  2. {  
  3.     attachChildren(context);  
  4.     ......  
  5. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/ContainerNode.cpp中。

 

       ContainerNode类的成员函数attach主要是调用另外一个成员函数attachChildren递归为当前正在处理的DOM节点的所有子孙节点分别创建一个Render Object,如下所示:

 

[cpp] view plain
  1. inline void ContainerNode::attachChildren(const AttachContext& context)  
  2. {  
  3.     AttachContext childrenContext(context);  
  4.     childrenContext.resolvedStyle = 0;  
  5.   
  6.     for (Node* child = firstChild(); child; child = child->nextSibling()) {  
  7.         ASSERT(child->needsAttach() || childAttachedAllowedWhenAttachingChildren(this));  
  8.         if (child->needsAttach())  
  9.             child->attach(childrenContext);  
  10.     }  
  11. }  
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/ContainerNode.h中。

 

       从这里就可以看到,ContainerNode类的成员函数attachChildren会依次遍历当前正在处理的DOM节点的每一个子节点,并且对于需要创建Render Object的子节点,会调用它的成员函数attach进行创建,也就是调用我们前面分析过的Element类的成员函数attach进行创建。这个过程会一直重复下去,直到遍历至DOM树的叶子节点为止。这时候就会得到图1所示的Render Object Tree。

       至此,网页的Render Object Tree的创建过程就分析完成了,网页的Render Object Tree是在DOM Tree构造完成时开始创建的,并且Render Object Tree的每一个节点与DOM Tree的某一个节点对应,但是又不是每一个在DOM Tree的节点在Render Object Tree中都有对应的节点,只有那些需要进行渲染的DOM节点才会在Render Object Tree中有对应的节点。

       事实上,WebKit在为网页创建Render Object Tree的过程中,也会为网页创建图1所示的Render Layer Tree。


手机开发阅读排行

最新文章