站在老罗的肩膀上:https://blog.youkuaiyun.com/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());
...
}