首页

源码搜藏网

首页 > 开发教程 > 手机开发 >

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中有对应节点。本文接下来就分析网页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。

上一篇:最棒的开源 Android 应用:聊天、图像、音频等等
下一篇:Android开发中的线程与消息机制 15问15答

相关内容

热门推荐