网页url加载

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

Chromium在Browser进程中为网页创建了一个Frame Tree之后,会将网页的URL发送给Render进程进行加载。Render进程接收到网页URL加载请求之后,会做一些必要的初始化工作,然后请求Browser进程下载网页的内容。Browser进程一边下载网页内容,一边又通过共享内存将网页内容传递给Render进程解析,也就是创建DOM Tree。

Render进程之所以要请求Browser进程下载网页的内容,是因为Render进程没有网络访问权限。出于安全考虑,Chromium将Render进程启动在一个受限环境中,使得Render进程没有网络访问权限。那为什么不是Browser进程主动下载好网页内容再交给Render进程解析呢?

       这是因为Render进程是通过WebKit加载网页URL的,WebKit不关心自己所在的进程是否有网络访问权限,它通过特定的接口访问网络。这个特定接口由WebKit的使用者,也就是Render进程中的Content模块实现。Content模块在实现这个接口的时候,会通过IPC请求Browser进程下载网络的内容。这种设计方式使得WebKit可以灵活地使用:既可以在有网络访问权限的进程中使用,也可以在没有网络访问权限的进程中使用,并且使用方式是统一的。

       从前面Chromium Frame Tree创建过程分析一文可以知道,Browser进程中为要加载的网页创建了一个Frame Tree之后,会向Render进程发送一个类型为FrameMsg_Navigate的IPC消息。Render进程接收到这个IPC消息之后,处理流程如图1所示:

                                                                                        图1 网页URL加载过程

       Render进程执行了一些初始化工作之后,就向Browser进程发送一个类型为ResourceHostMsg_RequestResource的IPC消息。Browser进程收到这个IPC消息之后,就会通过HTTP协议请求Web服务器将网页的内容返回来。请求得到响应后,Browser进程就会创建一块共享内存,并且通过一个类型为ResourceMsg_SetDataBuffer的IPC消息将这块共享内存传递给Render进程的。

       以后每当下载到新的网页内容,Browser进程就会将它们写入到前面创建的共享内存中去,并且发送Render进程发送一个类型为ResourceMsg_DataReceived的IPC消息。Render进程接收到这个IPC消息之后,就会从共享内存中读出Browser进程写入的内容,并且进行解析,也就是创建一个DOM Tree。这个过程一直持续到网页内容下载完成为止。

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

// IPC::Listener -------------------------------------------------------------

bool RenderViewImpl::OnMessageReceived(const IPC::Message& message) {
  WebFrame* main_frame = webview() ? webview()->MainFrame() : nullptr;
  if (main_frame) {
    GURL active_url;
    if (main_frame->IsWebLocalFrame())
      active_url = main_frame->ToWebLocalFrame()->GetDocument().Url();
    GetContentClient()->SetActiveURL(
        active_url, main_frame->Top()->GetSecurityOrigin().ToString().Utf8());
  }

  for (auto& observer : observers_) {
    if (observer.OnMessageReceived(message))
      return true;
  }

  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(RenderViewImpl, message)
    IPC_MESSAGE_HANDLER(ViewMsg_SetPageScale, OnSetPageScale)
    ...
    IPC_MESSAGE_HANDLER(PageMsg_SetPageFrozen, SetPageFrozen)
    ...
  IPC_END_MESSAGE_MAP()

  return handled;
}

src/content/renderer/render_view_impl.cc

... Missing render--> blink --> browser --> open URL ...

 Web服务器响应了请求之后,Chromium的Net模块会调用ResourceLoader类的成员函数OnResponseStarted,它的实现如下所示:

void ResourceLoader::OnResponseStarted(net::URLRequest* unused, int net_error) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
               "ResourceLoader::OnResponseStarted");
  DCHECK_EQ(request_.get(), unused);

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

  if (net_error != net::OK) {
    ResponseCompleted();
    return;
  }

  CompleteResponseStarted();
}

src/content/browser/loader/resource_loader.cc

ResourceLoader类的成员函数OnResponseStarted检查Web服务器的响应是否成功,例如Web服务器是否根据HTTP协议返回了200响应。如果成功的话,那么接下来就会调用另外一个成员函数StartReading读出第一块数据。

void ResourceLoader::CompleteResponseStarted() {
  ResourceRequestInfoImpl* info = GetRequestInfo();
  scoped_refptr<network::ResourceResponse> response =
      new network::ResourceResponse();
  PopulateResourceResponse(info, request_.get(), response.get(),
                           raw_request_headers_, raw_response_headers_.get());
  raw_request_headers_ = net::HttpRawRequestHeaders();
  raw_response_headers_ = nullptr;

  delegate_->DidReceiveResponse(this, response.get());

  // For back-forward navigations, record metrics.
  // TODO(clamy): Remove once we understand the root cause behind the regression
  // of PLT for b/f navigations in PlzNavigate.
  if ((info->GetPageTransition() & ui::PAGE_TRANSITION_FORWARD_BACK) &&
      IsResourceTypeFrame(info->GetResourceType()) &&
      request_->url().SchemeIsHTTPOrHTTPS()) {
    UMA_HISTOGRAM_BOOLEAN("Navigation.BackForward.WasCached",
                          request_->was_cached());
  }

  read_deferral_start_time_ = base::TimeTicks::Now();
  // Using a ScopedDeferral here would result in calling ReadMore(true) on sync
  // success. Calling PrepareToReadMore(false) here instead allows small
  // responses to be handled completely synchronously, if no ResourceHandler
  // defers handling of the response.
  deferred_stage_ = DEFERRED_SYNC;
  handler_->OnResponseStarted(response.get(),
                              std::make_unique<Controller>(this));
  if (is_deferred()) {
    deferred_stage_ = DEFERRED_READ;
  } else {
    PrepareToReadMore(false);
  }
}

src/content/browser/loader/resource_loader.cc

ResourceLoader类的成员函数ReadMore首先调用成员变量handler_描述的一个AsyncResourceHandler对象的成员函数OnWillRead获取一个Buffer。这个Buffer用来保存从Web服务器返回来的数据。这些数据可以通过调用ResourceLoader类的成员变量reqeust_描述的一个URLRequest对象的成员函数Read获得。 

       AsyncResourceHandler对象的成员函数OnWillRead的实现如下所示:src/content/browser/loader/mojo_async_resource_handler.cc

void MojoAsyncResourceHandler::OnWillRead(
    scoped_refptr<net::IOBuffer>* buf,
    int* buf_size,
    std::unique_ptr<ResourceController> controller) {
  // |buffer_| is set to nullptr on successful read completion (Except for the
  // final 0-byte read, so this DCHECK will also catch OnWillRead being called
  // after OnReadCompelted(0)).
  DCHECK(!buffer_);
  DCHECK_EQ(0u, buffer_offset_);

  ...

  bool defer = false;
  if (!AllocateWriterIOBuffer(&buffer_, &defer)) {
    controller->CancelWithError(net::ERR_INSUFFICIENT_RESOURCES);
    return;
  }

  ...

  // The first call to OnWillRead must return a buffer of at least
  // kMinAllocationSize. If the Mojo buffer is too small, need to allocate an
  // intermediary buffer.
  if (first_call && static_cast<size_t>(buffer_->size()) < kMinAllocationSize) {
    ...
    DCHECK(!is_using_io_buffer_not_from_writer_);
    is_using_io_buffer_not_from_writer_ = true;
    buffer_ = new net::IOBufferWithSize(kMinAllocationSize);
  }

  *buf = buffer_;
  *buf_size = buffer_->size();
  controller->Resume();
}

src/content/browser/loader/mojo_async_resource_handler.cc 

AsyncResourceHandler类的成员函数EnsureResourceBufferIsInitialized首先检查成员变量buffer_是否指向了一个ResourceBuffer对象,并且这个ResourceBuffer对象描述的共享内存是否已经创建。

      如果AsyncResourceHandler类的成员变量buffer_还没有指向一个ResourceBuffer对象,或者指向了一个ResourceBuffer对象,但是这个ResourceBuffer对象描述的共享内存还没有创建,那么AsyncResourceHandler类的成员函数EnsureResourceBufferIsInitialized就会创建一个ResourceBuffer对象保存在成员变量buffer_中,并且调用这个ResourceBuffer对象的成员函数Initialize创建一块大小为kBufferSize的共享内存。这块共享内存每次可以分配出来的缓冲区最小值为kMinAllocationSize,最大值为kMaxAllocationSize。

       在Android平台上,调用ResourceBuffer类的成员函数Initialize创建的共享内存实际上是匿名共享内存。匿名共享内存可以通过Binder机制在两个进程之间进行共享。这一点可以参考前面Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析一文。这样Browser进程就可以通过这块匿名共享内存将下载回来的网页内容传递给Render进程处理。

browser下载完成,通知renderer,

void ResourceLoader::ReadMore(bool handle_result_async) {
  ...

  if (!handle_result_async || result <= 0) {
    OnReadCompleted(request_.get(), result); //读取不成功
  } else {
    // Else, trigger OnReadCompleted asynchronously to avoid starving the IO
    // thread in case the URLRequest can provide data synchronously.
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE,
        base::BindOnce(&ResourceLoader::OnReadCompleted,
                       weak_ptr_factory_.GetWeakPtr(), request_.get(), result));
  }
}

下载不成功

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

renderer响应接受browser下载的内容

void URLLoaderClientImpl::OnReceiveResponse(
    const network::ResourceResponseHead& response_head) {
  has_received_response_ = true;
  if (NeedsStoringMessage()) {
    StoreAndDispatch(
        std::make_unique<DeferredOnReceiveResponse>(response_head));
  } else {
    resource_dispatcher_->OnReceivedResponse(request_id_, response_head);
  }
}

src/content/renderer/loader/url_loader_client_impl.cc

下载回来的网页内容将由WebKit进行处理,也就是由ResourceLoader类的成员函数didReceiveData进行处理。这个处理过程即为网页内容的解析过程,解析后就会得到一棵DOM Tree。有了DOM Tree之后,接下来就可以对下载回来的网页内容进行渲染了。在接下来的一篇文章中,我们再详细分析WebKit根据网页内容生成DOM Tree的过程,

异步开始构建DOM TREE

void ResourceLoader::CompleteResponseStarted() {
  ...

  delegate_->DidReceiveResponse(this, response.get());

  ...
}

src/content/browser/loader/resource_loader.cc 

猜你喜欢

转载自blog.csdn.net/tornmy/article/details/81429846
今日推荐