配色: 字号:
Chromium网页Render Object Tree创建过程分析
2016-10-25 | 阅:  转:  |  分享 
  
Chromium网页RenderObjectTree创建过程分析

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



从前面一文可以知道,每一个HTML标签在DOMTree中都有一个对应的HTMLElement节点。相应地,在DOMTree中每一个需要渲染的HTMLElement节点在RenderObjectTree中都有一个对应的RenderObject节点,如图1所示:



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



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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidResourceLoader::OnReadCompleted(net::URLRequestunused,intbytes_read){

......



CompleteRead(bytes_read);



......



if(bytes_read>0){

StartReading(true);//Readthenextchunk.

}else{

//URLRequestreportedanEOF.CallResponseCompleted.

DCHECK_EQ(0,bytes_read);

ResponseCompleted();

}

}

这个函数定义在文件external/chromium_org/content/browser/loader/resource_loader.cc中。

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidResourceLoader::ResponseCompleted(){

......



handler_->OnResponseCompleted(request_->status(),security_info,&defer);



......

}

这个函数定义在文件external/chromium_org/content/browser/loader/resource_loader.cc中。

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidAsyncResourceHandler::OnResponseCompleted(

constnet::URLRequestStatus&status,

conststd::string&security_info,

booldefer){

constResourceRequestInfoImplinfo=GetRequestInfo();

......



ResourceMsg_RequestCompleteDatarequest_complete_data;

request_complete_data.error_code=error_code;

request_complete_data.was_ignored_by_handler=was_ignored_by_handler;

request_complete_data.exists_in_cache=request()->response_info().was_cached;

request_complete_data.security_info=security_info;

request_complete_data.completion_time=TimeTicks::Now();

request_complete_data.encoded_data_length=

request()->GetTotalReceivedBytes();

info->filter()->Send(

newResourceMsg_RequestComplete(GetRequestID(),request_complete_data));

}

这个函数定义在文件external/chromium_org/content/browser/loader/async_resource_handler.cc中。

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidResourceDispatcher::DispatchMessage(constIPC::Message&message){

IPC_BEGIN_MESSAGE_MAP(ResourceDispatcher,message)

......

IPC_MESSAGE_HANDLER(ResourceMsg_RequestComplete,OnRequestComplete)

IPC_END_MESSAGE_MAP()

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidResourceDispatcher::OnRequestComplete(

intrequest_id,

constResourceMsg_RequestCompleteData&request_complete_data){

......



PendingRequestInforequest_info=GetPendingRequestInfo(request_id);

......



RequestPeerpeer=request_info->peer;

......



peer->OnCompletedRequest(request_complete_data.error_code,

request_complete_data.was_ignored_by_handler,

request_complete_data.exists_in_cache,

request_complete_data.security_info,

renderer_completion_time,

request_complete_data.encoded_data_length);

}

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



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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidWebURLLoaderImpl::Context::OnCompletedRequest(

interror_code,

boolwas_ignored_by_handler,

boolstale_copy_in_cache,

conststd::string&security_info,

constbase::TimeTicks&completion_time,

int64total_transfer_size){

......



if(client_){

if(error_code!=net::OK){

client_->didFail(loader_,CreateError(request_.url(),

stale_copy_in_cache,

error_code));

}else{

client_->didFinishLoading(

loader_,(completion_time-TimeTicks()).InSecondsF(),

total_transfer_size);

}

}



......

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidResourceLoader::didFinishLoading(blink::WebURLLoader,doublefinishTime,int64encodedDataLength)

{

......



m_resource->finish(finishTime);



......

}

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



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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidResource::finish(doublefinishTime)

{

......

finishOnePart();

......

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidResource::finishOnePart()

{

......

checkNotify();

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidResource::checkNotify()

{

......



ResourceClientWalkerw(m_clients);

while(ResourceClientc=w.next())

c->notifyFinished(this);

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidDocumentLoader::notifyFinished(Resourceresource)

{

......



if(!m_mainResource->errorOccurred()&&!m_mainResource->wasCanceled()){

finishedLoading(m_mainResource->loadFinishTime());

return;

}



......

}

这个函数定义在文件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]viewplaincopy在CODE上查看代码片派生到我的代码片

voidDocumentLoader::finishedLoading(doublefinishTime)

{

......



endWriting(m_writer.get());



......

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidDocumentLoader::endWriting(DocumentWriterwriter)

{

......

m_writer->end();

.....

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidDocumentWriter::end()

{

......



m_parser->finish();



......

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidHTMLDocumentParser::finish()

{

......



attemptToEnd();

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidHTMLDocumentParser::attemptToEnd()

{

//finish()indicateswewillnotreceiveanymoredata.Ifwearewaitingon

//anexternalscripttoload,wecan''tfinishparsingquiteyet.



if(shouldDelayEnd()){

m_endWasDelayed=true;

return;

}

prepareToStopParsing();

}

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

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



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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidHTMLDocumentParser::prepareToStopParsing()

{

......



attemptToRunDeferredScriptsAndEnd();

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidHTMLDocumentParser::attemptToRunDeferredScriptsAndEnd()

{

......



if(m_scriptRunner&&!m_scriptRunner->executeScriptsWaitingForParsing())

return;

end();

}

这个函数定义在文件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]viewplaincopy在CODE上查看代码片派生到我的代码片

voidHTMLDocumentParser::end()

{

......



//InformsthetherestofWebCorethatparsingisreallyfinished(anddeletesthis).

m_treeBuilder->finished();

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidHTMLTreeBuilder::finished()

{

......



m_tree.finishedParsing();

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidHTMLConstructionSite::finishedParsing()

{

......



m_document->finishedParsing();

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidDocument::finishedParsing()

{

......



if(RefPtrf=frame()){

......

constboolmainResourceWasAlreadyRequested=

m_frame->loader().stateMachine()->committedFirstRealDocumentLoad();



......

if(mainResourceWasAlreadyRequested)

updateRenderTreeIfNeeded();



......

}



......

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

classDocument:publicContainerNode,publicTreeScope,publicSecurityContext,publicExecutionContext,publicExecutionContextClient

,publicDocumentSupplementable,publicLifecycleContext{

......

public:

......



voidupdateRenderTreeIfNeeded(){updateRenderTree(NoChange);}



......

};

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidDocument::updateRenderTree(StyleRecalcChangechange)

{

......



updateStyle(change);



......

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidDocument::updateStyle(StyleRecalcChangechange)

{

......



if(styleChangeType()>=SubtreeStyleChange)

change=Force;



......



if(change==Force){

......

RefPtrdocumentStyle=StyleResolver::styleForDocument(this);

StyleRecalcChangelocalChange=RenderStyle::stylePropagationDiff(documentStyle.get(),renderView()->style());

if(localChange!=NoChwww.shanxiwang.netange)

renderView()->setStyle(documentStyle.release());

}



......



if(ElementdocumentElement=this->documentElement()){

......

if(documentElement->shouldCallRecalcStyle(change))

documentElement->recalcStyle(change);

......

}



......

}

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

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



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



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



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



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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidElement::recalcStyle(StyleRecalcChangechange,TextnextTextSibling)

{

......



if(change>=Inherit||needsStyleRecalc()){

......

if(parentRenderStyle())

change=recalcOwnStyle(change);

......

}



......

}

这个函数定义在文件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]viewplaincopy在CODE上查看代码片派生到我的代码片

StyleRecalcChangeElement::recalcOwnStyle(StyleRecalcChangechange)

{

......



RefPtroldStyle=renderStyle();

RefPtrnewStyle=styleForRenderer();

StyleRecalcChangelocalChange=RenderStyle::stylePropagationDiff(oldStyle.get(),newStyle.get());



......



if(localChange==Reattach){

AttachContextreattachContext;

reattachContext.resolvedStyle=newStyle.get();

......

reattach(reattachContext);

......

}



......

}

这个函数定义在文件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节点创建一个RenderObject,并且将这个RenderObject加入到网页的RenderObjectTree中去。这是通过调用Element类的成员函数reattach实现的。



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidNode::reattach(constAttachContext&context)

{

AttachContextreattachContext(context);

reattachContext.performingReattach=true;



......



attach(reattachContext);

}

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



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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidElement::attach(constAttachContext&context)

{

......



RenderTreeBuilder(this,context.resolvedStyle).createRendererForElementIfNeeded();



......



ContainerNode::attach(context);



......

}

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

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



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



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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidRenderTreeBuilder::createRendererForElementIfNeeded()

{

......



Elementelement=toElement(m_node);

RenderStyle&style=this->style();



if(!element->rendererIsNeeded(style))

return;



RenderObjectnewRenderer=element->createRenderer(&style);

......



RenderObjectparentRenderer=this->parentRenderer();

......



element->setRenderer(newRenderer);

newRenderer->setStyle(&style);//setStyle()candependonrenderer()alreadybeingset.



parentRenderer->addChild(newRenderer,nextRenderer);

}

这个函数定义在文件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节点创建一个RenderObject。如果不需要,那么RenderTreeBuilder类的成员函数createRendererForElementIfNeeded就直接返回了。



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

boolElement::rendererIsNeeded(constRenderStyle&style)

{

returnstyle.display()!=NONE;

}

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

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



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



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



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



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



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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

RenderObjectElement::createRenderer(RenderStylestyle)

{

returnRenderObject::createObject(this,style);

}

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

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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

RenderObjectRenderObject::createObject(Elementelement,RenderStylestyle)

{

......



switch(style->display()){

caseNONE:

return0;

caseINLINE:

returnnewRenderInline(element);

caseBLOCK:

caseINLINE_BLOCK:

returnnewRenderBlockFlow(element);

caseLIST_ITEM:

returnnewRenderListItem(element);

caseTABLE:

caseINLINE_TABLE:

returnnewRenderTable(element);

caseTABLE_ROW_GROUP:

caseTABLE_HEADER_GROUP:

caseTABLE_FOOTER_GROUP:

returnnewRenderTableSection(element);

caseTABLE_ROW:

returnnewRenderTableRow(element);

caseTABLE_COLUMN_GROUP:

caseTABLE_COLUMN:

returnnewRenderTableCol(element);

caseTABLE_CELL:

returnnewRenderTableCell(element);

caseTABLE_CAPTION:

returnnewRenderTableCaption(element);

caseBOX:

caseINLINE_BOX:

returnnewRenderDeprecatedFlexibleBox(element);

caseFLEX:

caseINLINE_FLEX:

returnnewRenderFlexibleBox(element);

caseGRID:

caseINLINE_GRID:

returnnewRenderGrid(element);

}



return0;

}

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

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



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



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



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



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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

voidContainerNode::attach(constAttachContext&context)

{

attachChildren(context);

......

}

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

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



[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

inlinevoidContainerNode::attachChildren(constAttachContext&context)

{

AttachContextchildrenContext(context);

childrenContext.resolvedStyle=0;



for(Nodechild=firstChild();child;child=child->nextSibling()){

ASSERT(child->needsAttach()||childAttachedAllowedWhenAttachingChildren(this));

if(child->needsAttach())

child->attach(childrenContext);

}

}

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

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



至此,网页的RenderObjectTree的创建过程就分析完成了.

献花(0)
+1
(本文系网络学习天...首藏)