Layout Object Tree 创建

站在老罗的肩膀上:https://blog.csdn.net/luoshengyang/article/details/50615628

每一个HTML标签在DOM Tree中都有一个对应的HTMLElement节点。相应地,在DOM Tree中每一个需要渲染的HTMLElement节点在Layout  Object Tree中都有一个对应的Layout  Object节点,如图1所示(To Update):

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

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

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

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

void ResourceLoader::OnReadCompleted(net::URLRequest* unused, int bytes_read) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
               "ResourceLoader::OnReadCompleted");
  DCHECK_EQ(request_.get(), unused);
  DVLOG(1) << "OnReadCompleted: \"" << request_->url().spec() << "\""
           << " bytes_read = " << bytes_read;

  pending_read_ = false;

  // bytes_read == -1 always implies an error.
  if (bytes_read == -1 || !request_->status().is_success()) {
    ResponseCompleted();
    return;
  }

  CompleteRead(bytes_read);
}

  src/content/browser/loader/resource_loader.cc

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

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

void ResourceLoader::ResponseCompleted() {
  TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::ResponseCompleted", this,
                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);

  DVLOG(1) << "ResponseCompleted: " << request_->url().spec();

  ScopedDeferral scoped_deferral(this, DEFERRED_FINISH);
  handler_->OnResponseCompleted(request_->status(),
                                std::make_unique<Controller>(this));
}

src/content/browser/loader/mojo_async_resource_handler.cc 

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

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

void MojoAsyncResourceHandler::OnResponseCompleted(
    const net::URLRequestStatus& request_status,
    std::unique_ptr<ResourceController> controller) {
  ...
  url_loader_client_->OnComplete(loader_status);
  ...
}

src/content/browser/loader/mojo_async_resource_handler.cc 

void URLLoaderClientProxy::OnComplete(
    const network::URLLoaderCompletionStatus& in_status) {
  ...
  auto message = URLLoaderClientProxy_OnComplete_Message::Build(
      kSerialize, kExpectsResponse, kIsSync, std::move(in_status));
  ...
  ignore_result(receiver_->Accept(&message));
}

src/out/Debug/gen/services/network/public/mojom/url_loader.mojom.cc 

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

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

How renderer process accept message from browser
...Missing...

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

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

void ResourceDispatcher::OnRequestComplete(
    int request_id,
    const network::URLLoaderCompletionStatus& status) {
  TRACE_EVENT0("loader", "ResourceDispatcher::OnRequestComplete");

  PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
  ...
  RequestPeer* peer = request_info->peer.get();
  ...
  peer->OnCompletedRequest(renderer_status);
}

src/content/renderer/loader/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的实现如下所示:

void WebURLLoaderImpl::RequestPeerImpl::OnCompletedRequest(
    const network::URLLoaderCompletionStatus& status) {
  context_->OnCompletedRequest(status);
}
void WebURLLoaderImpl::Context::OnCompletedRequest(
    const network::URLLoaderCompletionStatus& status) {
  ...
  if (client_) {
    ...
      client_->DidFail(
          status.cors_error_status
              ? WebURLError(*status.cors_error_status, has_copy_in_cache, url_)
              : WebURLError(status.error_code, status.extended_error_code,
                            has_copy_in_cache,
                            WebURLError::IsWebSecurityViolation::kFalse, url_),
          total_transfer_size, encoded_body_size, status.decoded_body_length);
    } else {
      client_->DidFinishLoading(status.completion_time, total_transfer_size,
                                encoded_body_size, status.decoded_body_length,
                                status.should_report_corb_blocking);
    }
  }
}

src/content/renderer/loader/web_url_loader_impl.cc 

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

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

void ResourceLoader::DidFinishLoading(TimeTicks finish_time,
                                      int64_t encoded_data_length,
                                      int64_t encoded_body_length,
                                      int64_t decoded_body_length,
                                      bool should_report_corb_blocking) {
  resource_->SetEncodedDataLength(encoded_data_length);
  resource_->SetEncodedBodyLength(encoded_body_length);
  resource_->SetDecodedBodyLength(decoded_body_length);

  if (is_downloading_to_blob_ && !blob_finished_ && blob_response_started_) {
    load_did_finish_before_blob_ =
        DeferedFinishLoadingInfo{finish_time, should_report_corb_blocking};
    return;
  }

  Release(ResourceLoadScheduler::ReleaseOption::kReleaseAndSchedule,
          ResourceLoadScheduler::TrafficReportHints(encoded_data_length,
                                                    decoded_body_length));
  loader_.reset();

  network_instrumentation::EndResourceLoad(
      resource_->Identifier(),
      network_instrumentation::RequestOutcome::kSuccess);

  fetcher_->HandleLoaderFinish(
      resource_.Get(), finish_time, ResourceFetcher::kDidFinishLoading,
      inflight_keepalive_bytes_, should_report_corb_blocking);
}

 fetch_描述的是一个ResourceFetcher对象。调用这个RawResource对象的成员函数HandleLoaderFinish结束加载网页内容,它的实现如下所示:

void ResourceFetcher::HandleLoaderFinish(Resource* resource,
                                         TimeTicks finish_time,
                                         LoaderFinishType type,
                                         uint32_t inflight_keepalive_bytes,
                                         bool should_report_corb_blocking) {
  ...
  ResourceLoader* loader = resource->Loader();
  ...
  if (type == kDidFinishLoading) {
    resource->Finish(finish_time, Context().GetLoadingTaskRunner().get());
    ...
  }
  HandleLoadCompletion(resource);
}

src/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc 

调用Resource类的成员函数Finish, Resource类的成员函数finishOnePart的实现如下所示:

void Resource::Finish(TimeTicks load_finish_time,
                      base::SingleThreadTaskRunner* task_runner) {
  ...
  TriggerNotificationForFinishObservers(task_runner);
  NotifyFinished();
}

src/third_party/blink/renderer/platform/loader/fetch/resource.cc

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

void Resource::NotifyFinished() {
  DCHECK(IsLoaded());

  ResourceClientWalker<ResourceClient> w(clients_);
  while (ResourceClient* c = w.Next()) {
    MarkClientFinished(c);
    c->NotifyFinished(this);
  }
}

src/third_party/blink/renderer/platform/loader/fetch/resource.cc

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

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

void DocumentLoader::NotifyFinished(Resource* resource) {
  ...
  if (!resource->ErrorOccurred() && !resource->WasCanceled()) {
    FinishedLoading(resource->LoadFinishTime());
    return;
  }
  ...
}

src/third_party/blink/renderer/core/loader/document_loader.cc 

在网页内容成功下载完成的情况下,DocumentLoader类的成员函数NotifyFinished就会调用另外一个成员函数FinishedLoading进行结束处理。DocumentLoader类的成员函数finishedLoading的实现如下所示:

void DocumentLoader::FinishedLoading(TimeTicks finish_time) {
  ...
  GetTiming().SetResponseEnd(response_end_time);
  ...
  application_cache_host_->FinishedLoadingMainResource();
  if (parser_) {
    if (parser_blocked_count_) {
      finished_loading_ = true;
    } else {
      parser_->Finish();
      parser_.Clear();
    }
  }
  ClearResource();
}

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

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

void HTMLDocumentParser::Finish() {
  ...
  AttemptToEnd();
}


void HTMLDocumentParser::AttemptToEnd() {
  // finish() indicates we will not receive any more data. If we are waiting on
  // an external script to load, we can't finish parsing quite yet.

  if (ShouldDelayEnd()) {
    end_was_delayed_ = true;
    return;
  }
  PrepareToStopParsing();
}

src/third_party/blink/renderer/core/html/parser/html_document_parser.cc  

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

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

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

void HTMLDocumentParser::PrepareToStopParsing() {
  ...
  AttemptToRunDeferredScriptsAndEnd();
}


void HTMLDocumentParser::AttemptToRunDeferredScriptsAndEnd() {
  ...
  if (script_runner_ && !script_runner_->ExecuteScriptsWaitingForParsing())
    return;
  end();
}

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

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

void HTMLDocumentParser::end() {
  ...
  tree_builder_->Finished();
  ...
  DocumentParser::StopParsing();
}


void HTMLTreeBuilder::Finished() {
  ...
  tree_.FinishedParsing();
}

src/third_party/blink/renderer/core/html/parser/html_document_parser.cc 

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

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

void HTMLConstructionSite::FinishedParsing() {
  ...
  document_->FinishedParsing();
}

src/third_party/blink/renderer/core/html/parser/html_construction_site.cc 

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

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

void Document::FinishedParsing() {
  ...
    const bool main_resource_was_already_requested =
        frame->Loader().StateMachine()->CommittedFirstRealDocumentLoad();
    ...
    if (main_resource_was_already_requested)
      UpdateStyleAndLayoutTree();
    ...
  }
  ...
}


 HTMLDocument类的成员函数finishedParsing首先判断网页的主资源是否已经请求回来了。在请求回来的情况下,才会调用另外一个成员函数UpdateStyleAndLayoutTree创建一个Layout  Object Tree



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

void Document::UpdateStyleAndLayoutTree() {
  ……
  UpdateStyle();
  ……
}


void Document::UpdateStyle() {
  ...
  if (Element* document_element = documentElement()) {
    if (document_element->ShouldCallRecalcStyle(change)) {
      ...
      // calculate CSS propertities
      document_element->RecalcStyle(change);
         ...
    }
    GetStyleEngine().MarkForWhitespaceReattachment();
    PropagateStyleToViewport();
    if (document_element->NeedsReattachLayoutTree() ||
        document_element->ChildNeedsReattachLayoutTree()) {
      ...
      ReattachLegacyLayoutObjectList legacy_layout_objects(*this);
      WhitespaceAttacher whitespace_attacher;
      // build layout tree
      document_element->RebuildLayoutTree(whitespace_attacher);
      legacy_layout_objects.ForceLegacyLayoutIfNeeded();
    }
  }
  ...
}


inline bool Node::ShouldCallRecalcStyle(StyleRecalcChange change) {
  if (NeedsReattachLayoutTree())
    return false;
  return change >= kIndependentInherit || NeedsStyleRecalc() ||
         ChildNeedsStyleRecalc();
}

src/third_party/blink/renderer/core/dom/document.cc 

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

The states are as follows:
1. NoChange -> No style recalc change is needed.
2. NoInherit -> Only perform style recalc on the Node itself.
3. UpdatePseudoElements -> Causes an update of just the pseudo element children in the case where the styles for the element itself were recalced, but resulted in NoChange or NoInherit.
4. IndependentInherit -> Same as Inherit except style recalc stops early if only independent properties were changed. We still visit every descendant, but we apply the styles directly instead of doing selector matching to compute a new style. Independent properties are those which do not depend on and do not affect any other properties on ComputedStyle (e.g. visibility and Pointer Events).
5. Inherit -> Do a full style recalc of children.
6. Force -> Fallback that causes us to do a full style recalc. This is as we don't know what changes. The primary reason for it is SubtreeStyleChange.
7. Reattach -> reattachLayoutTree() needs to be performed.

       接下来,Document类的成员函数updateStyle首先是计算根节点的CSS属性,这是通过调用StyleResolver::StyleForViewport实现的,然后又用Style::StylePropagationDiff比较根节点新的CSS属性与旧的CSS属性是否有不同的地方。如果有不同的地方,那么就通过LayoutView的SetStyle将新的CSS属性值保存在与根节点对应的layout_view_中。

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

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

void Element::RecalcStyle(StyleRecalcChange change) {
  ...
  if (HasCustomStyleCallbacks())
    WillRecalcStyle(change);
  if (change >= kIndependentInherit || NeedsStyleRecalc()) {
    ...
    if (ParentComputedStyle()) {
      change = RecalcOwnStyle(change);
    } else if (!CanParticipateInFlatTree()) {
      ...
    }
    ...
  }

  ...

  if (ShouldCallRecalcStyleForChildren(change)) {

    UpdatePseudoElement(kPseudoIdBefore, change);

    if (change > kUpdatePseudoElements || ChildNeedsStyleRecalc()) {
      SelectorFilterParentScope filter_scope(*this);
      if (ShadowRoot* root = GetShadowRoot()) {
        if (root->ShouldCallRecalcStyle(change))
          root->RecalcStyle(change);
      }
      RecalcDescendantStyles(change);
    }
    ...
  }

  if (HasCustomStyleCallbacks())
    DidRecalcStyle(change);
}


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

StyleRecalcChange Element::RecalcOwnStyle(StyleRecalcChange change) {
  ...
  scoped_refptr<const ComputedStyle> old_style = GetComputedStyle();

  // When propagating inherited changes, we don't need to do a full style recalc
  // if the only changed properties are independent. In this case, we can simply
  // set these directly on the ComputedStyle object.
  scoped_refptr<ComputedStyle> new_style = PropagateInheritedProperties(change);
  if (!new_style)
    new_style = StyleForLayoutObject();
  if (!new_style) {
    // here mark need to reattach
    SetNeedsReattachLayoutTree();
    return kReattach;
  }

  StyleRecalcChange local_change =
      ComputedStyle::StylePropagationDiff(old_style.get(), new_style.get());
  ...
  if (change == kReattach || local_change == kReattach) {
    SetNonAttachedStyle(new_style);
    SetNeedsReattachLayoutTree();
    return kReattach;
  }
  ...
}                                                                          

src/third_party/blink/renderer/core/dom/element.cc 

       Element类的成员函数recalcOwnStyle首先调用成员函数GetComputedStyle获得当前正处理的DOM节点的原来设置的CSS属性。由于当前正在处理的DOM节点还没有计算过CSS属性,因此前面获得的CSS属性就为空。Element类的成员函数recalcOwnStyle接下来又调用成员函数StyleForLayoutObject计算当前正在处理的DOM节点的CSS属性,这是通过解析网页内容得到的。在这种情况下调用RenderStyle类的静态成员函数stylePropagationDiff比较前面获得的两个CSS属性,会得一个值为Reattach的返回值,表示要为当前正在处理的DOM节点创建一个Layout Object。此时调用SetNonAttachedStyle(new_style)更新CSS,并调用SetNeedsReattachLayoutTree()标记需要重构layout tree (同时,document_element->RecalcStyle(change)计算children css中也会调用Node::SetNeedsReattachLayoutTree() 标记 子节点需要重构layoutTree)。并且将这个Layout Object加入到网页的Layer Tree中去。这是通过调用Element类的成员函数RebuildLayoutTree实现的。



void Element::RecalcStyle(StyleRecalcChange change) {
  ...
    if (ParentComputedStyle()) {
      // Calculate current root CSS
      change = RecalcOwnStyle(change);
    } 
  ...

  if (ShouldCallRecalcStyleForChildren(change)) {
    ...
      // claculate children CSS
      RecalcDescendantStyles(change);
    }
    UpdatePseudoElement(kPseudoIdAfter, change);
    ...
  }
  ...
}

根据我的debug,change的值等于Force等于kNoChange,ParentComputedStyle()不为NULL,调用RecalcOwnStyle(change)计算父节点的CSS属性;调用 ContainerNode::RecalcDescendantStyles,其回调Element::RecalcStyle,从而递归地计算所有子节点CSS属性,之后回到Document::UpdateStyle()再调用RebuildLayoutTree。

void ContainerNode::RecalcDescendantStyles(StyleRecalcChange change) {
  DCHECK(GetDocument().InStyleRecalc());
  DCHECK(change >= kUpdatePseudoElements || ChildNeedsStyleRecalc());
  DCHECK(!NeedsStyleRecalc());

  for (Node* child = lastChild(); child; child = child->previousSibling()) {
    if (child->IsTextNode()) {
      ToText(child)->RecalcTextStyle(change);
    } else if (child->IsElementNode()) {
      Element* element = ToElement(child);
      if (element->ShouldCallRecalcStyle(change))
        element->RecalcStyle(change);
    }
  }
}

还不清楚具体什么情况设为kForce,但还是有点混乱 

StyleRecalcChange change = kNoChange;
  if (GetStyleChangeType() >= kSubtreeStyleChange)
    change = kForce;


StyleChangeType GetStyleChangeType() const {
    return static_cast<StyleChangeType>(node_flags_ & kStyleChangeMask);
  }


2.  构建layout tree:

RebuildLayoutTree实现如下:

void Element::RebuildLayoutTree(WhitespaceAttacher& whitespace_attacher) {
  ...
  if (NeedsReattachLayoutTree()) {
    AttachContext reattach_context;
    ReattachLayoutTree(reattach_context);
    whitespace_attacher.DidReattachElement(this,
                                           reattach_context.previous_in_flow);
  } else {
    ...
  }
  ...
}

src/third_party/blink/renderer/core/dom/element.cc

Element继承自ContainerNode继承自Node,调用Node成员函数ReattachLayoutTree

void Node::ReattachLayoutTree(AttachContext& context) {
  context.performing_reattach = true;

  // We only need to detach if the node has already been through
  // attachLayoutTree().
  if (GetStyleChangeType() < kNeedsReattachStyleChange)
    DetachLayoutTree(context);
  AttachLayoutTree(context);
  DCHECK(!NeedsReattachLayoutTree());
  DCHECK(!GetNonAttachedStyle());
}

Element重写了虚函数AttachLayoutTree, 

void Element::AttachLayoutTree(AttachContext& context) {
  if (CanParticipateInFlatTree()) {
    LayoutTreeBuilderForElement builder(*this, GetNonAttachedStyle());
    builder.CreateLayoutObjectIfNeeded();

    if (ComputedStyle* style = builder.ResolvedStyle()) {
      if (!GetLayoutObject() && ShouldStoreNonLayoutObjectComputedStyle(*style))
        StoreNonLayoutObjectComputedStyle(style);
    }
  }
  ...
  AttachContext children_context(context);
  LayoutObject* layout_object = GetLayoutObject();
  ...
  // When a shadow root exists, it does the work of attaching the children.
  if (ShadowRoot* shadow_root = GetShadowRoot()) {
    if (shadow_root->NeedsAttach())
      shadow_root->AttachLayoutTree(children_context);
  }
  ContainerNode::AttachLayoutTree(children_context);
  SetNonAttachedStyle(nullptr);
  AddCallbackSelectors();
}

src/third_party/blink/renderer/core/dom/element.cc       

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

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

LayoutTreeBuilderForElement调用成员函数调用ShouldCreateLayoutObject,判断是否需要为当前正在处理的DOM节点创建一个Layout Object,从而只创建必须的Layout Object,如果不需,那么LayoutTreeBuilder类的成员函数LayoutTreeBuilde rForElement就直接返回了。

void CreateLayoutObjectIfNeeded() {
    if (ShouldCreateLayoutObject())
      CreateLayoutObject();
}


bool LayoutTreeBuilderForElement::ShouldCreateLayoutObject() const {
  if (!layout_object_parent_)
    return false;

  LayoutObject* parent_layout_object = ParentLayoutObject();
  if (!parent_layout_object)
    return false;
  if (!parent_layout_object->CanHaveChildren())
    return false;
  if (node_->IsPseudoElement() &&
      !CanHaveGeneratedChildren(*parent_layout_object)) {
    return false;
  }
  return node_->LayoutObjectIsNeeded(Style());
}


bool Element::LayoutObjectIsNeeded(const ComputedStyle& style) const {
  return style.Display() != EDisplay::kNone &&
         style.Display() != EDisplay::kContents;
}

src/third_party/blink/renderer/core/dom/element.cc

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

     回到CreateLayoutObjectIfNeeded中,假设需要为前正在处理的DOM节点创建Render Object,那么调用CreateLayoutObject,其通过LayoutTreeBuilderForElement::Style()获得当前正在处理的DOM节点的CSS属性对象,然后再以这个CSS属性对象为参数, 创建一个Render Object,接下来还做了三件事情:

       1. 将新创建的Layout Object与当前正在处理的DOM节点关联起来, 通过node_->SetLayoutObject(new_layout_object);

       2. 将用来描述当前正在处理的DOM节点的CSS属性对象设置给新创建的Layout Object,以便新创建的Layout Object后面可以根据这个CSS属性对象绘制自己,通过new_layout_object->SetStyle(&style);

       3. 获得与当前正在处理的DOM节点对应的parent Layout Object,并且将新创建的Layout Object作为这个Layout Object的子节点,从而形成一个Layout Object Tree,通过parent_layout_object->AddChild(new_layout_object, next_layout_object);

void CreateLayoutObjectIfNeeded() {
    if (ShouldCreateLayoutObject())
      CreateLayoutObject();
}

DISABLE_CFI_PERF
void LayoutTreeBuilderForElement::CreateLayoutObject() {
  ComputedStyle& style = Style();
  ...
  LayoutObject* new_layout_object = node_->CreateLayoutObject(style);
  if (!new_layout_object)
    return;

  LayoutObject* parent_layout_object = ParentLayoutObject();

  if (!parent_layout_object->IsChildAllowed(new_layout_object, style)) {
    new_layout_object->Destroy();
    return;
  }

  // Make sure the LayoutObject already knows it is going to be added to a
  // LayoutFlowThread before we set the style for the first time. Otherwise code
  // using IsInsideFlowThread() in the StyleWillChange and StyleDidChange will
  // fail.
  new_layout_object->SetIsInsideFlowThread(
      parent_layout_object->IsInsideFlowThread());

  LayoutObject* next_layout_object = NextLayoutObject();
  node_->SetLayoutObject(new_layout_object);
  new_layout_object->SetStyle(
      &style);  // SetStyle() can depend on LayoutObject() already being set.

  // Note: Adding new_layout_object instead of LayoutObject(). LayoutObject()
  // may be a child of new_layout_object.
  parent_layout_object->AddChild(new_layout_object, next_layout_object);
  if (!legacy_layout_objects.IsCollecting())
    return;
  legacy_layout_objects.AddForceLegacyAtBFCAncestor(*new_layout_object);
}

src/third_party/blink/renderer/core/dom/layout_tree_builder.cc
   接下来,我们主要分析为一个DOM节点创建一个Layout Object的过程,根据参数element描述的一个DOM节点的display属性值创建一个具体的Layout Object, 如下所示:

 LayoutObject* Element::CreateLayoutObject(const ComputedStyle& style) {
  return LayoutObject::CreateObject(this, style);
}


LayoutObject* LayoutObject::CreateObject(Element* element,
                                         const ComputedStyle& style) {
  DCHECK(IsAllowedToModifyLayoutTreeStructure(element->GetDocument()));
  ...
  switch (style.Display()) {
    case EDisplay::kNone:
    case EDisplay::kContents:
      return nullptr;
    case EDisplay::kInline:
      return new LayoutInline(element);
    case EDisplay::kBlock:
    case EDisplay::kFlowRoot:
    case EDisplay::kInlineBlock:
      return LayoutObjectFactory::CreateBlockFlow(*element, style);
    ...
    case EDisplay::kLayoutCustom:
    case EDisplay::kInlineLayoutCustom:
      return new LayoutCustom(element);
  }
}

       不管是哪一种类型的Layout Object,它们都是间接继承与LayoutBlock类,其继承于LayoutBox,其继承于LayoutBoxModelObject,其继承于LayoutObject,其继承于ImageResourceObserver和DisplayItemClient,其中LayoutBox描述的是一个CSS Box Model,如下所示,再结合content值,就可以对一个Layout Object进行绘制了:

// LayoutBox implements the full CSS box model.
//
// LayoutBoxModelObject only introduces some abstractions for LayoutInline and
// LayoutBox. The logic for the model is in LayoutBox, e.g. the storage for the
// rectangle and offset forming the CSS box (m_frameRect) and the getters for
// the different boxes.
//
// LayoutBox is also the uppermost class to support scrollbars, however the
// logic is delegated to PaintLayerScrollableArea.
// Per the CSS specification, scrollbars should "be inserted between the inner
// border edge and the outer padding edge".
// (see http://www.w3.org/TR/CSS21/visufx.html#overflow)
// Also the scrollbar width / height are removed from the content box. Taking
// the following example:
//
// <!DOCTYPE html>
// <style>
// ::-webkit-scrollbar {
//     /* Force non-overlay scrollbars */
//     width: 10px;
//     height: 20px;
// }
// </style>
// <div style="overflow:scroll; width: 100px; height: 100px">
//
// The <div>'s content box is not 100x100 as specified in the style but 90x80 as
// we remove the scrollbars from the box.
//
// The presence of scrollbars is determined by the 'overflow' property and can
// be conditioned on having layout overflow (see OverflowModel for more details
// on how we track overflow).
//
// There are 2 types of scrollbars:
// - non-overlay scrollbars take space from the content box.
// - overlay scrollbars don't and just overlay hang off from the border box,
//   potentially overlapping with the padding box's content.
// For more details on scrollbars, see PaintLayerScrollableArea.
//
//
// ***** THE BOX MODEL *****
// The CSS box model is based on a series of nested boxes:
// http://www.w3.org/TR/CSS21/box.html
//
//       |----------------------------------------------------|
//       |                                                    |
//       |                   margin-top                       |
//       |                                                    |
//       |     |-----------------------------------------|    |
//       |     |                                         |    |
//       |     |             border-top                  |    |
//       |     |                                         |    |
//       |     |    |--------------------------|----|    |    |
//       |     |    |                          |    |    |    |
//       |     |    |       padding-top        |####|    |    |
//       |     |    |                          |####|    |    |
//       |     |    |    |----------------|    |####|    |    |
//       |     |    |    |                |    |    |    |    |
//       | ML  | BL | PL |  content box   | PR | SW | BR | MR |
//       |     |    |    |                |    |    |    |    |
//       |     |    |    |----------------|    |    |    |    |
//       |     |    |                          |    |    |    |
//       |     |    |      padding-bottom      |    |    |    |
//       |     |    |--------------------------|----|    |    |
//       |     |    |                      ####|    |    |    |
//       |     |    |     scrollbar height ####| SC |    |    |
//       |     |    |                      ####|    |    |    |
//       |     |    |-------------------------------|    |    |
//       |     |                                         |    |
//       |     |           border-bottom                 |    |
//       |     |                                         |    |
//       |     |-----------------------------------------|    |
//       |                                                    |
//       |                 margin-bottom                      |
//       |                                                    |
//       |----------------------------------------------------|
//
// BL = border-left
// BR = border-right
// ML = margin-left
// MR = margin-right
// PL = padding-left
// PR = padding-right
// SC = scroll corner (contains UI for resizing (see the 'resize' property)
// SW = scrollbar width
//
// Those are just the boxes from the CSS model. Extra boxes are tracked by Blink
// (e.g. the overflows). Thus it is paramount to know which box a function is
// manipulating. Also of critical importance is the coordinate system used (see
// the COORDINATE SYSTEMS section in LayoutBoxModelObject).

src/third_party/blink/renderer/core/layout/layout_box.h 

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

void Element::AttachLayoutTree(AttachContext& context) {
  // attach layout for current elemet node_
  ...

  ContainerNode::AttachLayoutTree(children_context);
  ...
}

src/third_party/blink/renderer/core/dom/element.cc 

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

DISABLE_CFI_PERF
void ContainerNode::AttachLayoutTree(AttachContext& context) {
  for (Node* child = firstChild(); child; child = child->nextSibling()) {
    ...
    if (child->NeedsAttach())
      child->AttachLayoutTree(context);
  }

  ClearChildNeedsStyleRecalc();
  ClearChildNeedsReattachLayoutTree();
  Node::AttachLayoutTree(context);
}

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

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

猜你喜欢

转载自blog.csdn.net/tornmy/article/details/81436000