Chromium网页URL加载过程分析

2016-01 from–https://blog.csdn.net/Luoshengyang/article/details/50527574

2014-06 from–https://www.cnblogs.com/fangkm/p/3784660.html

2018-12 from–https://blog.csdn.net/camike/article/details/85230513

2017-03 from–https://blog.csdn.net/u012155923/article/details/66973857?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

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

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       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进程接收类型为FrameMsg_Navigate的IPC消息开始分析网页URL的加载过程。Render进程是通过RenderFrameImpl类的成员函数OnMessageReceived接收类型为FrameMsg_Navigate的IPC消息的,如下所示:

 

  1. bool RenderFrameImpl::OnMessageReceived(const IPC::Message& msg) {
  2. ……
  3. bool handled = true;
  4. IPC_BEGIN_MESSAGE_MAP(RenderFrameImpl, msg)
  5. IPC_MESSAGE_HANDLER(FrameMsg_Navigate, OnNavigate)
  6. ……
  7. IPC_END_MESSAGE_MAP()
  8. return handled;
  9. }

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

 

RenderFrameImpl类的成员函数OnMessageReceived将类型为FrameMsg_Navigate的IPC消息分发给另外一个成员函数OnNavigate处理,后者的实现如下所示:

 

  1. void RenderFrameImpl::OnNavigate(const FrameMsg_Navigate_Params& params) {
  2. ……
  3. bool is_reload = RenderViewImpl::IsReload(params);
  4. ……
  5. WebFrame* frame = frame_;
  6. ……
  7. if (is_reload) {
  8. ……
  9. } else if (params.page_state.IsValid()) {
  10. ……
  11. } else if (!params.base_url_for_data_url.is_empty()) {
  12. ……
  13. } else {
  14. // Navigate to the given URL.
  15. WebURLRequest request(params.url);
  16. ……
  17. frame->loadRequest(request);
  18. ……
  19. }
  20. ……
  21. }

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

从前面Chromium Frame Tree创建过程分析一文可以知道,RenderFrameImpl类的成员变量frame_指向的是一个WebLocalFrameImpl对象。如果当前正在处理的RenderFrameImpl对象还没有加载过URL,并且当前要加载的URL不为空,RenderFrameImpl类的成员函数OnNavigate会调用成员变量frame_指向的WebLocalFrameImpl对象的成员函数loadRequest加载指定的URL。

WebLocalFrameImpl类的成员函数loadRequest的实现如下所示:

 

  1. void WebLocalFrameImpl::loadRequest(const WebURLRequest& request)
  2. {
  3. ……
  4. const ResourceRequest& resourceRequest = request.toResourceRequest();
  5. if (resourceRequest.url().protocolIs(“javascript”)) {
  6. loadJavaScriptURL(resourceRequest.url());
  7. return;
  8. }
  9. frame()->loader().load(FrameLoadRequest(0, resourceRequest));
  10. }

这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebLocalFrameImpl.cpp中。

 

如果参数request描述的URL指定的协议是”javascript”,那么表示要加载的是一段JavaScript。这时候WebLocalFrameImpl类的成员函数loadRequest会调用另外一个成员函数loadJavaScriptURL加载这段JavaScript。

在其它情况下,WebLocalFrameImpl类的成员函数loadRequest首先调用成员函数frame获得成员变量m_frame描述的一个LocalFrame对象,接着又调用这个LocalFrame对象的成员函数loader获得其成员变量m_loader描述的一个FrameLoader对象。有了这个FrameLoader对象之后,就调用它的成员函数load加载参数request描述的URL。

WebLocalFrameImpl类的成员变量m_frame描述的LocalFrame对象和LocalFrame类的成员变量m_loader描述的FrameLoader对象的创建过程,可以参考前面Chromium Frame Tree创建过程分析一文。接下来我们继续分析FrameLoader类的成员函数load的实现,如下所示:

 

  1. void FrameLoader::load(const FrameLoadRequest& passedRequest)
  2. {
  3. ……
  4. FrameLoadRequest request(passedRequest);
  5. ……
  6. FrameLoadType newLoadType = determineFrameLoadType(request);
  7. NavigationAction action(request.resourceRequest(), newLoadType, request.formState(), request.triggeringEvent());
  8. ……
  9. loadWithNavigationAction(action, newLoadType, request.formState(), request.substituteData(), request.clientRedirect());
  10. ……
  11. }

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

 

FrameLoader类的成员函数load主要是调用另外一个成员函数loadWithNavigationAction加载参数passedRequest描述的URL。

FrameLoader类的成员函数loadWithNavigationAction的实现如下所示:

 

  1. void FrameLoader::loadWithNavigationAction(const NavigationAction& action, FrameLoadType type, PassRefPtrWillBeRawPtr formState, const SubstituteData& substituteData, ClientRedirectPolicy clientRedirect, const AtomicString& overrideEncoding)
  2. {
  3. ……
  4. const ResourceRequest& request = action.resourceRequest();
  5. ……
  6. m_policyDocumentLoader = client()->createDocumentLoader(m_frame, request, substituteData.isValid() ? substituteData : defaultSubstituteDataForURL(request.url()));
  7. ……
  8. m_provisionalDocumentLoader = m_policyDocumentLoader.release();
  9. ……
  10. m_provisionalDocumentLoader->startLoadingMainResource();
  11. }

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

 

FrameLoader类的成员函数loadWithNavigationAction首先调用成员函数client获得一个FrameLoaderClientImpl对象,接着再调用这个FrameLoaderClientImpl对象的成员函数createDocumentLoader为参数action描述的URL创建了一个WebDataSourceImpl对象,并且保存在成员变量m_policyDocumentLoader中。关于FrameLoader类的成员函数client和FrameLoaderClientImpl类的成员函数createDocumentLoader的实现,可以参考前面Chromium Frame Tree创建过程分析一文。

FrameLoader类的成员函数loadWithNavigationAction接下来又将成员变量m_policyDocumentLoader描述的WebDataSourceImpl对象转移到另外一个成员变量m_provisionalDocumentLoader中,最后调用这个WebDataSourceImpl对象的成员函数startLoadingMainResource加载参数action描述的URL。

WebDataSourceImpl类的成员函数startLoadingMainResource是从父类DocumentLoader继承下来的,它的实现如下所示:

 

  1. void DocumentLoader::startLoadingMainResource()
  2. {
  3. ……
  4. FetchRequest cachedResourceRequest(request, FetchInitiatorTypeNames::document, mainResourceLoadOptions);
  5. m_mainResource = m_fetcher->fetchMainResource(cachedResourceRequest, m_substituteData);
  6. ……
  7. m_mainResource->addClient(this);
  8. ……
  9. }

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

 

从前面Chromium Frame Tree创建过程分析一文可以知道,DocumentLoader类的成员变量m_fetcher描述的是一个ResourceFetcher对象,DocumentLoader类的成员函数startLoadingMainResource调用这个ResourceFetcher对象的成员函数fetchMainResource请求加载本地变量cachedResourceRequest描述的资源。这个资源描述的即为上一步指定要加载的URL。

ResourceFetcher类的成员函数fetchMainResource执行结束后,会返回一个RawResource对象。这个RawResource对象保存在WebDataSourceImpl类的成员变量m_mainResource中。这个RawResource对象描述的是一个异步加载的资源,DocumentLoader类的成员startLoadingMainResource调用它的成员函数addClient将当前正在处理的DocumentLoader对象添加到它的内部去,用来获得异步加载的资源数据,也就是本地变量cachedResourceRequest描述的URL对应的网页内容。

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

 

  1. void Resource::addClient(ResourceClient* client)
  2. {
  3. if (addClientToSet(client))
  4. didAddClient(client);
  5. }

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

 

Resource类的成员函数addClient调用另外一个成员函数addClientToSet将参数client描述的一个DocumentLoader对象保存在内部,如下所示:

 

  1. bool Resource::addClientToSet(ResourceClient* client)
  2. {
  3. ……
  4. m_clients.add(client);
  5. return true;
  6. }

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

 

Resource类的成员函数addClientToSet将参数client描述的一个DocumentLoader保存在成员变量m_clients描述的一个Hash Set中,以便当前正在处理的Resource对象描述的网页内容从Web服务器下载回来的时候,可以交给它处理。

接下来我们继续分析WebDataSourceImpl类的成员函数startLoadingMainResource调用成员变量m_fetcher描述的ResourceFetcher对象的成员函数fetchMainResource加载本地变量cachedResourceRequest描述的URL的过程,如下所示:

 

  1. ResourcePtr ResourceFetcher::fetchMainResource(FetchRequest& request, const SubstituteData& substituteData)
  2. {
  3. ……
  4. return toRawResource(requestResource(Resource::MainResource, request));
  5. }

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

 

ResourceFetcher类的成员函数fetchMainResource调用另外一个成员函数requestResource加载参数request描述的URL。ResourceFetcher类的成员函数requestResource会返回一个RawResource对象给调用者,即ResourceFetcher类的成员函数fetchMainResource。后者又会将这个RawResource对象返回给它的调用者。

ResourceFetcher类的成员函数requestResource的实现如下所示:

 

  1. ResourcePtr ResourceFetcher::requestResource(Resource::Type type, FetchRequest& request)
  2. {
  3. ……
  4. KURL url = request.resourceRequest().url();
  5. ……
  6. const RevalidationPolicy policy = determineRevalidationPolicy(type, request.mutableResourceRequest(), request.forPreload(), resource.get(), request.defer(), request.options());
  7. switch (policy) {
  8. ……
  9. case Load:
  10. resource = createResourceForLoading(type, request, request.charset());
  11. break;
  12. …..
  13. }
  14. ……
  15. if (resourceNeedsLoad(resource.get(), request, policy)) {
  16. ……
  17. if (!m_documentLoader || !m_documentLoader->scheduleArchiveLoad(resource.get(), request.resourceRequest()))
  18. resource->load(this, request.options());
  19. ……
  20. }
  21. ……
  22. return resource;
  23. }

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

 

ResourceFetcher类的成员函数requestResource首先调用成员函数createResourceForLoading为参数request描述的URL创建一个RawResource对象,如下所示:

 

  1. ResourcePtr ResourceFetcher::createResourceForLoading(Resource::Type type, FetchRequest& request, const String& charset)
  2. {
  3. ……
  4. ResourcePtr resource = createResource(type, request.resourceRequest(), charset);
  5. ……
  6. return resource;
  7. }

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

 

ResourceFetcher类的成员函数createResourceForLoading调用函数createResource根据参数type和request创建一个RawResource对象,如下所示:

 

  1. static Resource* createResource(Resource::Type type, const ResourceRequest& request, const String& charset)
  2. {
  3. switch (type) {
  4. ……
  5. case Resource::MainResource:
  6. case Resource::Raw:
  7. case Resource::TextTrack:
  8. case Resource::Media:
  9. return new RawResource(request, type);
  10. ……
  11. }
  12. ……
  13. return 0;
  14. }

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

 

从前面的调用过程可以知道,参数type的值等于Resource::MainResource,因此函数createResource创建的是一个RawResource对象。

回到ResourceFetcher类的成员函数requestResource中,它调用成员函数createResourceForLoading为参数request描述的URL创建了一个RawResource对象之后,接下来又调用成员函数resourceNeedsLoad判断该URL是否需要进行加载。如果需要进行加载,那么ResourceFetcher类的成员函数requestResource又会调用成员变量m_documentLoader描述的一个DocumentLoader对象的成员函数scheduleArchiveLoad判断要加载的URL描述的是否是一个存档文件。如果不是,那么就会调用前面创建的RawResource对象的成员函数load从Web服务器下载对应的网页内容。

我们假设request描述的URL需要进行加载,并且不是一个存档文件,因此接下来我们继续分析RawResource类的成员函数load的实现。RawResource类的成员函数load是从父类Resource继承下来的,它的实现如下所示:

 

  1. void Resource::load(ResourceFetcher* fetcher, const ResourceLoaderOptions& options)
  2. {
  3. ……
  4. ResourceRequest request(m_resourceRequest);
  5. ……
  6. m_loader = ResourceLoader::create(fetcher, this, request, options);
  7. m_loader->start();
  8. }

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

 

Resource类的成员变量m_resourceRequest描述的是要加载的URL,Resource类的成员函数load首先调用ResourceLoader类的静态成员函数create为其创建一个ResourceLoader对象,如下所示:

 

  1. PassRefPtr ResourceLoader::create(ResourceLoaderHost* host, Resource* resource, const ResourceRequest& request, const ResourceLoaderOptions& options)
  2. {
  3. RefPtr loader(adoptRef(new ResourceLoader(host, resource, options)));
  4. loader->init(request);
  5. return loader.release();
  6. }

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

 

从这里可以看到,ResourceLoader类的静态成员函数create创建的是一个ResourceLoader对象。这个ResourceLoader对象经过初始化之后,会返回给调用者。

回到Resource类的成员函数load中,它为要加载的URL创建了一个ResourceLoader对象之后,会调用这个ResourceLoader对象的成员函数start开始加载要加载的URL,如下所示:

 

  1. void ResourceLoader::start()
  2. {
  3. ……
  4. m_loader = adoptPtr(blink::Platform::current()->createURLLoader());
  5. ……
  6. blink::WrappedResourceRequest wrappedRequest(m_request);
  7. m_loader->loadAsynchronously(wrappedRequest, this);
  8. }

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

 

ResourceLoader类的成员函数start首先调用由Chromium的Content模块实现的一个blink::Platform接口的成员函数createURLLoader创建一个WebURLLoaderImpl对象,接着再调用这个WebURLLoaderImpl对象的成员函数loadAsynchronously对象成员变量m_request描述的URL进行异步加载。

Chromium的Content模块的BlinkPlatformImpl类实现了blink::Platform接口,它的成员函数createURLLoader的实现如下所示:

 

  1. WebURLLoader* BlinkPlatformImpl::createURLLoader() {
  2. return new WebURLLoaderImpl;
  3. }

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

 

从这里可以看到,BlinkPlatformImpl类的成员函数createURLLoader创建的是一个WebURLLoaderImpl对象。这个WebURLLoaderImpl对象会返回给调用者。

接下来我们继续分析WebURLLoaderImpl类的成员函数loadAsynchronously异步加载一个URL的过程,如下所示:

 

  1. void WebURLLoaderImpl::loadAsynchronously(const WebURLRequest& request,
  2. WebURLLoaderClient* client) {
  3. ……
  4. context_->set_client(client);
  5. context_->Start(request, NULL);
  6. }

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

 

从前面的调用过程可以知道,参数client描述的是一个ResourceLoader对象。这个ResourceLoader对象会保存在WebURLLoaderImpl类的成员变量content_描述的一个WebURLLoaderImpl::Context对象的内部。这是通过调用WebURLLoaderImpl::Context类的成员函数set_client实现的,如下所示:

 

  1. class WebURLLoaderImpl::Context : public base::RefCounted,
  2. public RequestPeer {
  3. public:
  4. ……
  5. void set_client(WebURLLoaderClient* client) { client_ = client; }
  6. private:
  7. ……
  8. WebURLLoaderClient* client_;
  9. ……
  10. };

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

 

WebURLLoaderImpl::Context类的成员函数set_client将参数client描述的ResourceLoader对象保存在成员变量client_中。

回到WebURLLoaderImpl类的成员函数loadAsynchronously中,它接下来会继续调用成员变量content_描述的一个WebURLLoaderImpl::Context对象的成员函数Start加载参数request描述的URL,如下所示:

 

  1. void WebURLLoaderImpl::Context::Start(const WebURLRequest& request,
  2. SyncLoadResponse* sync_load_response) {
  3. ……
  4. GURL url = request.url();
  5. ……
  6. RequestInfo request_info;
  7. ……
  8. request_info.url = url;
  9. ……
  10. bridge_.reset(ChildThread::current()->resource_dispatcher()->CreateBridge(
  11. request_info));
  12. ……
  13. if (bridge_->Start(this)) {
  14. AddRef(); // Balanced in OnCompletedRequest
  15. } else {
  16. bridge_.reset();
  17. }
  18. }

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

 

WebURLLoaderImpl::Context类的成员函数Start首先调用当前Render进程的一个ChildThread单例的成员函数resource_dispatcher获得一个ResourceDispatcher对象,如下所示:

 

  1. class CONTENT_EXPORT ChildThread
  2. : public IPC::Listener,
  3. public IPC::Sender,
  4. public NON_EXPORTED_BASE(mojo::ServiceProvider) {
  5. public:
  6. ……
  7. ResourceDispatcher* resource_dispatcher() const {
  8. return resource_dispatcher_.get();
  9. }
  10. ……
  11. private:
  12. ……
  13. // Handles resource loads for this process.
  14. scoped_ptr resource_dispatcher_;
  15. ……
  16. };

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

 

ChildThread类的成员函数resource_dispatcher返回的是成员变量resource_dispatcher_描述的一个ResourceDispatcher对象。

回到WebURLLoaderImpl::Context类的成员函数Start中,它获得了一个ResourceDispatcher对象之后,接着调用这个ResourceDispatcher对象的成员函数CreateBridge创建一个IPCResourceLoaderBridge对象,如下所示:

 

  1. ResourceLoaderBridge* ResourceDispatcher::CreateBridge(
  2. const RequestInfo& request_info) {
  3. return new IPCResourceLoaderBridge(this, request_info);
  4. }

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

 

从这里可以看到,ResourceDispatcher类的成员函数CreateBridge创建的是一个IPCResourceLoaderBridge对象,并且会将这个IPCResourceLoaderBridge对象返回给调用者。

回到WebURLLoaderImpl::Context类的成员函数Start中,它获得了一个IPCResourceLoaderBridge对象之后,接着调用这个IPCResourceLoaderBridge对象的成员函数Start加载参数request描述的URL,如下所示:

 

  1. bool IPCResourceLoaderBridge::Start(RequestPeer* peer) {
  2. ……
  3. // generate the request ID, and append it to the message
  4. request_id_ = dispatcher_->AddPendingRequest(peer,
  5. request_.resource_type,
  6. request_.origin_pid,
  7. frame_origin_,
  8. request_.url,
  9. request_.download_to_file);
  10. return dispatcher_->message_sender()->Send(
  11. new ResourceHostMsg_RequestResource(routing_id_, request_id_, request_));
  12. }

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

 

IPCResourceLoaderBridge类的成员变量dispatcher_描述的是一个ResourceDispatcher对象,IPCResourceLoaderBridge类的成员函数Start首先调用这个ResourceDispatcher对象的成员函数AddPendingRequest将参数peer描述的一个WebURLLoaderImpl::Context对象保存在内部,如下所示:

 

  1. int ResourceDispatcher::AddPendingRequest(RequestPeer* callback,
  2. ResourceType::Type resource_type,
  3. int origin_pid,
  4. const GURL& frame_origin,
  5. const GURL& request_url,
  6. bool download_to_file) {
  7. // Compute a unique request_id for this renderer process.
  8. int id = MakeRequestID();
  9. pending_requests_[id] = PendingRequestInfo(callback,
  10. resource_type,
  11. origin_pid,
  12. frame_origin,
  13. request_url,
  14. download_to_file);
  15. return id;
  16. }

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

 

ResourceDispatcher类的成员函数AddPendingRequest首先调用成员函数MakeRequestID生成一个Request ID,接着将参数callback描述的一个WebURLLoaderImpl::Context对象封装在一个PendingRequestInfo对象中,并且以上述Request ID为键值,将这个PendingRequestInfo对象保存在成员变量pending_requests_描述的一个Hash Map中。

回到IPCResourceLoaderBridge类的成员函数Start中,它接下来调用成员变量dispatcher_描述的ResourceDispatcher对象的成员函数message_sender获得一个IPC::Sender对象,并且通过这个IPC::Sender对象向Browser进程发送一个类型为ResourceHostMsg_RequestResource的IPC消息,用来请求Browser进程下载成员变量request_描述的URL对应的网页的内容。

在Browser进程中,类型为ResourceHostMsg_RequestResource的IPC消息是由ResourceDispatcherHostImpl类的成员函数OnMessageReceived进行接收的,如下所示:

 

  1. bool ResourceDispatcherHostImpl::OnMessageReceived(
  2. const IPC::Message& message,
  3. ResourceMessageFilter* filter) {
  4. ……
  5. bool handled = true;
  6. IPC_BEGIN_MESSAGE_MAP(ResourceDispatcherHostImpl, message)
  7. IPC_MESSAGE_HANDLER(ResourceHostMsg_RequestResource, OnRequestResource)
  8. ……
  9. IPC_MESSAGE_UNHANDLED(handled = false)
  10. IPC_END_MESSAGE_MAP()
  11. ……
  12. }

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

 

ResourceDispatcherHostImpl类的成员函数OnMessageReceived将类型为ResourceHostMsg_RequestResource的IPC消息分发给另外一个成员函数OnRequestResource处理,后者的实现如下所示:

 

  1. void ResourceDispatcherHostImpl::OnRequestResource(
  2. int routing_id,
  3. int request_id,
  4. const ResourceHostMsg_Request& request_data) {
  5. BeginRequest(request_id, request_data, NULL, routing_id);
  6. }

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

ResourceDispatcherHostImpl类的成员函数OnRequestResource调用另外一个成员函数BeginRequest开始下载参数request_data描述的URL对应的网页内容,后者的实现如下所示:

 

  1. void ResourceDispatcherHostImpl::BeginRequest(
  2. int request_id,
  3. const ResourceHostMsg_Request& request_data,
  4. IPC::Message* sync_result, // only valid for sync
  5. int route_id) {
  6. ……
  7. // Construct the request.
  8. net::CookieStore* cookie_store =
  9. GetContentClient()->browser()->OverrideCookieStoreForRenderProcess(
  10. child_id);
  11. scoped_ptr new_request;
  12. new_request = request_context->CreateRequest(
  13. request_data.url, request_data.priority, NULL, cookie_store);
  14. ……
  15. scoped_ptr handler(
  16. CreateResourceHandler(
  17. new_request.get(),
  18. request_data, sync_result, route_id, process_type, child_id,
  19. resource_context));
  20. if (handler)
  21. BeginRequestInternal(new_request.Pass(), handler.Pass());
  22. }

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

 

ResourceDispatcherHostImpl类的成员函数BeginRequest首先从参数request_data取出要下载网页内容的URL,接着又将该URL封装在一个URLRequest对象中。

ResourceDispatcherHostImpl类的成员函数BeginRequest接下来又调用另外一个成员函数CreateResourceHandler创建了一个AsyncResourceHandler对象。这个AsyncResourceHandler对象用来异步接收和处理从Web服务器下载回来的网页内容。

ResourceDispatcherHostImpl类的成员函数CreateResourceHandler的实现如下所示:

 

  1. scoped_ptr ResourceDispatcherHostImpl::CreateResourceHandler(
  2. net::URLRequest* request,
  3. const ResourceHostMsg_Request& request_data,
  4. IPC::Message* sync_result,
  5. int route_id,
  6. int process_type,
  7. int child_id,
  8. ResourceContext* resource_context) {
  9. // Construct the IPC resource handler.
  10. scoped_ptr handler;
  11. if (sync_result) {
  12. ……
  13. handler.reset(new SyncResourceHandler(request, sync_result, this));
  14. } else {
  15. handler.reset(new AsyncResourceHandler(request, this));
  16. // The RedirectToFileResourceHandler depends on being next in the chain.
  17. if (request_data.download_to_file) {
  18. handler.reset(
  19. new RedirectToFileResourceHandler(handler.Pass(), request));
  20. }
  21. }
  22. ……
  23. // Install a CrossSiteResourceHandler for all main frame requests. This will
  24. // let us check whether a transfer is required and pause for the unload
  25. // handler either if so or if a cross-process navigation is already under way.
  26. bool is_swappable_navigation =
  27. request_data.resource_type == ResourceType::MAIN_FRAME;
  28. // If we are using –site-per-process, install it for subframes as well.
  29. if (!is_swappable_navigation &&
  30. CommandLine::ForCurrentProcess()->HasSwitch(switches::kSitePerProcess)) {
  31. is_swappable_navigation =
  32. request_data.resource_type == ResourceType::SUB_FRAME;
  33. }
  34. if (is_swappable_navigation && process_type == PROCESS_TYPE_RENDERER)
  35. handler.reset(new CrossSiteResourceHandler(handler.Pass(), request));
  36. // Insert a buffered event handler before the actual one.
  37. handler.reset(
  38. new BufferedResourceHandler(handler.Pass(), this, request));
  39. ……
  40. handler.reset(
  41. new ThrottlingResourceHandler(handler.Pass(), request, throttles.Pass()));
  42. return handler.Pass();
  43. }

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

 

从前面的调用过程可以知道,参数sync_result的值等于NULL,因此ResourceDispatcherHostImpl类的成员函数CreateResourceHandler首先创建了一个AsyncResourceHandler对象,保存在本地变量handler中,表示要通过异步方式下载参数request描述的URL。

接下来ResourceDispatcherHostImpl类的成员函数CreateResourceHandler又会根据情况创建其它的Handler对象。这些Handler对象会依次连接在一起。其中,后面创建的Handler对象位于前面创建的Handler对象的前面。下载回来的网页内容将依次被这些Handler对象处理。这意味着下载回来的网页内容最后会被最先创建的AsyncResourceHandler对象进行处理。为了简单起见,后面我们只分析这个AsyncResourceHandler对象处理下载回来的网页内容的过程,也就是假设ResourceDispatcherHostImpl类的成员函数CreateResourceHandler返回给调用者的是一个AsyncResourceHandler对象。

回到ResourceDispatcherHostImpl类的成员函数BeginRequest中,它最后调用另外一个成员函数BeginRequestInternal下载本地变量new_request描述的URL对应的网页内容,如下所示:

 

  1. void ResourceDispatcherHostImpl::BeginRequestInternal(
  2. scoped_ptr request,
  3. scoped_ptr handler) {
  4. ……
  5. ResourceRequestInfoImpl* info =
  6. ResourceRequestInfoImpl::ForRequest(request.get());
  7. ……
  8. linked_ptr loader(
  9. new ResourceLoader(request.Pass(), handler.Pass(), this));
  10. …..
  11. StartLoading(info, loader);
  12. }

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

 

ResourceDispatcherHostImpl类的成员函数BeginRequestInternal将参数request描述的URL和参数handler描述的AsyncResourceHandler对象封装在一个ResourceLoader对象后,调用另外一个成员函数StartLoading开始加载参数request描述的URL。

ResourceDispatcherHostImpl类的成员函数StartLoading的实现如下所示:

 

  1. void ResourceDispatcherHostImpl::StartLoading(
  2. ResourceRequestInfoImpl* info,
  3. const linked_ptr& loader) {
  4. ……
  5. loader->StartRequest();
  6. }

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

 

ResourceDispatcherHostImpl类的成员函数StartLoading主要是调用参数loader描述的ResourceLoader对象的成员函数StartRequest开始加载其内部封装的URL。

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

 

  1. void ResourceLoader::StartRequest() {
  2. ……
  3. // Give the handler a chance to delay the URLRequest from being started.
  4. bool defer_start = false;
  5. if (!handler_->OnWillStart(request_->url(), &defer_start)) {
  6. Cancel();
  7. return;
  8. }
  9. if (defer_start) {
  10. deferred_stage_ = DEFERRED_START;
  11. } else {
  12. StartRequestInternal();
  13. }
  14. }

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

 

ResourceLoader类的成员变量handler_描述的便是前面我们假设ResourceDispatcherHostImpl类的成员函数CreateResourceHandler返回的AsyncResourceHandler对象。ResourceLoader类的成员函数StartRequest调用这个AsyncResourceHandler对象的成员函数OnWillStart询问是要取消、延迟、还是马上下载当前正在处理的ResourceLoader对象封装的URL对应的网页内容。

我们假设是第三种情况,这时候ResourceLoader类的成员函数StartRequest就会马上调用另外一个成员函数StartRequestInternal下载当前正在处理的ResourceLoader对象封装的URL对应的网页内容。

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

 

  1. void ResourceLoader::StartRequestInternal() {
  2. ……
  3. request_->Start();
  4. ……
  5. }

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

 

ResourceLoader类的成员变量request_描述的是前面在ResourceDispatcherHostImpl类的成员函数BeginRequest中创建的一个URLRequest对象。这个URLRequest对象封装了要下载的URL。ResourceLoader类的成员函数StartRequestInternal通过调用这个URLRequest对象的成员函数Start就可以启动下载网页的过程了。

URLRequest类是Chromium在Net模块中提供的一个类,用来执行具体的网络操作,也就是根据约定的协议请求Web服务器返回指定URL对应的网页的内容。这个过程我们留给读者自行分析。

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

 

  1. void ResourceLoader::OnResponseStarted(net::URLRequest* unused) {
  2. ……
  3. if (request_->status().is_success()) {
  4. StartReading(false); // Read the first chunk.
  5. }
  6. ……
  7. }

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

 

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

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

 

  1. void ResourceLoader::StartReading(bool is_continuation) {
  2. int bytes_read = 0;
  3. ReadMore(&bytes_read);
  4. ……
  5. if (!is_continuation || bytes_read <= 0) {
  6. OnReadCompleted(request_.get(), bytes_read);
  7. } else {
  8. // Else, trigger OnReadCompleted asynchronously to avoid starving the IO
  9. // thread in case the URLRequest can provide data synchronously.
  10. base::MessageLoop::current()->PostTask(
  11. FROM_HERE,
  12. base::Bind(&ResourceLoader::OnReadCompleted,
  13. weak_ptr_factory_.GetWeakPtr(),
  14. request_.get(),
  15. bytes_read));
  16. }
  17. }

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

 

ResourceLoader类的成员函数StartReading调用成员函数ReadMore读取Web服务器返回来的数据,读出来的数据大小保存在本地变量bytes_read中。

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

 

  1. void ResourceLoader::ReadMore(int* bytes_read) {
  2. ……
  3. scoped_refptr buf;
  4. int buf_size;
  5. if (!handler_->OnWillRead(&buf, &buf_size, -1)) {
  6. Cancel();
  7. return;
  8. }
  9. ……
  10. request_->Read(buf.get(), buf_size, bytes_read);
  11. ……
  12. }

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

 

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

AsyncResourceHandler对象的成员函数OnWillRead的实现如下所示:

 

  1. bool AsyncResourceHandler::OnWillRead(scoped_refptr* buf,
  2. int* buf_size,
  3. int min_size) {
  4. ……
  5. if (!EnsureResourceBufferIsInitialized())
  6. return false;
  7. ……
  8. char* memory = buffer_->Allocate(&allocation_size_);
  9. …..
  10. *buf = new DependentIOBuffer(buffer_.get(), memory);
  11. *buf_size = allocation_size_;
  12. ……
  13. return true;
  14. }

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

 

AsyncResourceHandler对象的成员函数OnWillRead首先调用成员函数EnsureResourceBufferIsInitialized确保成员变量buffer_指向了一块共享内存,然后再从这块共享内存中分配一块大小等于成员变量allocation_size_的值的缓冲区,用来返回给调用者保存从Web服务器返回来的数据。

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

 

  1. bool AsyncResourceHandler::EnsureResourceBufferIsInitialized() {
  2. if (buffer_.get() && buffer_->IsInitialized())
  3. return true;
  4. ……
  5. buffer_ = new ResourceBuffer();
  6. return buffer_->Initialize(kBufferSize,
  7. kMinAllocationSize,
  8. kMaxAllocationSize);
  9. }

这个函数定义在文件external/chromium_org/content/browser/loader/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进程处理。

这一步执行完成后,回到ResourceLoader类的成员函数StartReading中,如果没有读出数据(表明数据已经下载完毕),或者参数is_continuation的值等于false(表示读出来的是第一个数据块),那么ResourceLoader类的成员函数StartReading就会调用成员函数OnReadCompleted马上进行下一步处理。其余情况下,为了避免当前(网络)线程被阻塞,ResourceLoader类的成员函数StartReading并不会马上调用成员函数OnReadCompleted处理读出来的数据,而是延后一个消息处理,也就是等ResourceLoader类的成员函数StartReading返回到Chromium的Net模块之后再作处理。

接下来我们继续分析ResourceLoader类的成员函数OnReadCompleted的实现,如下所示:

 

  1. void ResourceLoader::OnReadCompleted(net::URLRequest* unused, int bytes_read) {
  2. ……
  3. CompleteRead(bytes_read);
  4. ……
  5. if (bytes_read > 0) {
  6. StartReading(true); // Read the next chunk.
  7. } else {
  8. // URLRequest reported an EOF. Call ResponseCompleted.
  9. DCHECK_EQ(0, bytes_read);
  10. ResponseCompleted();
  11. }
  12. }

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

 

ResourceLoader类的成员函数OnReadCompleted首先调用成员函数CompleteRead处理当前读出来的数据,数据的大小由参数bytes_read描述。如果当前读出来的数据的大小大于0,那么就表示数据还没读完,这时候就需要调用前面分析的成员函数StartReading继续进行读取。注意,这时候传递成员函数StartReading的参数为true,表示不是第一次读取Web服务器返回来的数据。

另一方面,如果当前读出来的数据的大小小于等于0,那么就说明Web服务器已经把所有的数据都返回来了,这时候ResourceLoader类的成员函数OnReadCompleted就调用另外一个成员函数ResponseCompleted结束读取数据。

接下来我们继续分析ResourceLoader类的成员函数CompleteRead的实现,以便了解Browser进程将下载回来的网页内容返回给Render进程处理的过程,如下所示:

 

  1. void ResourceLoader::CompleteRead(int bytes_read) {
  2. ……
  3. bool defer = false;
  4. if (!handler_->OnReadCompleted(bytes_read, &defer)) {
  5. Cancel();
  6. }
  7. ……
  8. }

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

 

ResourceLoader类的成员函数CompleteRead将读取出来的数据交给成员变量handler_描述的一个AsyncResourceHandler对象处理,这是通过调用它的成员函数OnReadCompleted实现的。

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

 

  1. bool AsyncResourceHandler::OnReadCompleted(int bytes_read, bool* defer) {
  2. ……
  3. if (!sent_first_data_msg_) {
  4. base::SharedMemoryHandle handle;
  5. int size;
  6. if (!buffer_->ShareToProcess(filter->PeerHandle(), &handle, &size))
  7. return false;
  8. filter->Send(new ResourceMsg_SetDataBuffer(
  9. GetRequestID(), handle, size, filter->peer_pid()));
  10. sent_first_data_msg_ = true;
  11. }
  12. int data_offset = buffer_->GetLastAllocationOffset();
  13. int64_t current_transfer_size = request()->GetTotalReceivedBytes();
  14. int encoded_data_length = current_transfer_size – reported_transfer_size_;
  15. reported_transfer_size_ = current_transfer_size;
  16. filter->Send(new ResourceMsg_DataReceived(
  17. GetRequestID(), data_offset, bytes_read, encoded_data_length));
  18. ……
  19. }

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

 

当AsyncResourceHandler类的成员变量sent_first_data_msg_的值等于false的时候,表示当前正在处理的AsyncResourceHandler对象还没有向Render进程返回过从Web服务器下载回来的网页内容。这时候AsyncResourceHandler类的成员函数OnReadCompleted首先要向Render进程发送一个类型为ResourceMsg_SetDataBuffer的IPC消息。这个IPC消息会将AsyncResourceHandler类的成员变量buffer_描述的共享内存传递给Render进程,以便Render进程接下来可以通过这块共享内存读取从Web服务器下载回来的网页内容。

最后,AsyncResourceHandler类的成员函数OnReadCompleted再向Render进程发送一个类型为ResourceMsg_DataReceived的IPC消息。这个IPC消息告诉Render进程从前面所描述的共享内存的什么位置开始读取多少数据。有了这些数据之后,Render进程就可以构建网页的DOM Tree了。

接下来我们就继续分析Render进程接收和处理类型为ResourceMsg_SetDataBuffer和ResourceMsg_DataReceived的IPC消息的过程。

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

 

  1. void ResourceDispatcher::DispatchMessage(const IPC::Message& message) {
  2. IPC_BEGIN_MESSAGE_MAP(ResourceDispatcher, message)
  3. ……
  4. IPC_MESSAGE_HANDLER(ResourceMsg_SetDataBuffer, OnSetDataBuffer)
  5. IPC_MESSAGE_HANDLER(ResourceMsg_DataReceived, OnReceivedData)
  6. ……
  7. IPC_END_MESSAGE_MAP()
  8. }

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

 

从这里可以看到,ResourceDispatcher类的成员函数DispatchMessage把类型为ResourceMsg_SetDataBuffer的IPC消息分发给成员函数OnSetDataBuffer处理,把类型为ResourceMsg_DataReceived的IPC消息分发给成员函数OnReceivedData处理。

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

 

  1. void ResourceDispatcher::OnSetDataBuffer(int request_id,
  2. base::SharedMemoryHandle shm_handle,
  3. int shm_size,
  4. base::ProcessId renderer_pid) {
  5. ……
  6. PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
  7. ……
  8. request_info->buffer.reset(
  9. new base::SharedMemory(shm_handle, true)); // read only
  10. bool ok = request_info->buffer->Map(shm_size);
  11. ……
  12. request_info->buffer_size = shm_size;
  13. }

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

从前面的分析可以知道,Render进程在请求Browser进程下载指定URL对应的网页内容之前,会创建一个PendingRequestInfo对象。这个PendingRequestInfo对象以一个Request ID为键值保存在ResourceDispatcher类的内部。这个Request ID即为参数request_id描述的Request ID。因此,ResourceDispatcher类的成员函数OnSetDataBuffer可以通过参数request_id获得一个PendingRequestInfo对象。有了这个PendingRequestInfo对象之后,ResourceDispatcher类的成员函数OnSetDataBuffer就根据参数shm_handle描述的句柄创建一个ShareMemory对象,保存在它的成员变量buffer中。

ResourceDispatcher类的成员函数OnSetDataBuffer最后调用上述ShareMemory对象的成员函数Map即可将Browser进程传递过来的共享内存映射到当前进程的地址空间来,这样以后就可以直接从这块共享内存读出Browser进程下载回来的网页内容。

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

 

  1. void ResourceDispatcher::OnReceivedData(int request_id,
  2. int data_offset,
  3. int data_length,
  4. int encoded_data_length) {
  5. ……
  6. PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
  7. ……
  8. if (request_info && data_length > 0) {
  9. ……
  10. linked_ptr retain_buffer(request_info->buffer);
  11. ……
  12. const char* data_start = static_cast<char*>(request_info->buffer->memory());
  13. ……
  14. const char* data_ptr = data_start data_offset;
  15. ……
  16. // Check whether this response data is compliant with our cross-site
  17. // document blocking policy. We only do this for the first packet.
  18. std::string alternative_data;
  19. if (request_info->site_isolation_metadata.get()) {
  20. request_info->blocked_response =
  21. SiteIsolationPolicy::ShouldBlockResponse(
  22. request_info->site_isolation_metadata, data_ptr, data_length,
  23. &alternative_data);
  24. request_info->site_isolation_metadata.reset();
  25. // When the response is blocked we may have any alternative data to
  26. // send to the renderer. When |alternative_data| is zero-sized, we do not
  27. // call peer\’s callback.
  28. if (request_info->blocked_response && !alternative_data.empty()) {
  29. data_ptr = alternative_data.data();
  30. data_length = alternative_data.size();
  31. encoded_data_length = alternative_data.size();
  32. }
  33. }
  34. if (!request_info->blocked_response || !alternative_data.empty()) {
  35. if (request_info->threaded_data_provider) {
  36. request_info->threaded_data_provider->OnReceivedDataOnForegroundThread(
  37. data_ptr, data_length, encoded_data_length);
  38. // A threaded data provider will take care of its own ACKing, as the
  39. // data may be processed later on another thread.
  40. send_ack = false;
  41. } else {
  42. request_info->peer->OnReceivedData(
  43. data_ptr, data_length, encoded_data_length);
  44. }
  45. }
  46. ……
  47. }
  48. ……
  49. }

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

 

ResourceDispatcher类的成员函数OnReceivedData首先获得参数request_id对应的一个PendingRequestInfo对象,保存在本地变量request_info中。有了这个PendingRequestInfo对象之后,就可以根据参数data_offset和data_length从它的成员变量buffer描述的共享内存中获得Browser进程下载回来的网页内容。

如果这是一个跨站(cross-site)请求下载回来的内容,ResourceDispatcher类的成员函数OnReceivedData会调用SiteIsolationPolicy类的静态成员函数ShouldBlockResponse根据Cross-Site Document Blocking Policy决定是否需要阻止下载回来的内容在当前Render进程中加载。关于Chromium的Cross-Site Document Blocking Policy,可以参考Site IsolationBlocking Cross-Site Documents for Site Isolation这两篇文章。

如果SiteIsolationPolicy类的静态成员函数ShouldBlockResponse表明要阻止下载回来的内容在当前Render进程中加载,那么本地变量request_info指向的PendingRequestInfo对象的成员变量blocked_response的值就会等于true。这时候如果SiteIsolationPolicy类的静态成员函数ShouldBlockResponse还返回了Alternative Data,那么这个Alternative Data就会替换下载回来的网页内容交给WebKit处理。

如果SiteIsolationPolicy类的静态成员函数ShouldBlockResponse没有阻止下载回来的内容在当前Render进程中加载,或者阻止的同时也提供了Alternative Data,那么ResourceDispatcher类的成员函数OnReceivedData接下来继续判断本地变量request_info指向的PendingRequestInfo对象的成员变量threaded_data_provider是否指向了一个ThreadedDataProvider对象。如果指向了一个ThreadedDataProvider对象,那么ResourceDispatcher类的成员函数OnReceivedData会将下载回来的网页内容交给这个ThreadedDataProvider对象的成员函数OnReceivedDataOnForegroundThread处理。否则的话,下载回来的网页内容将会交给本地变量request_info指向的PendingRequestInfo对象的成员变量peer描述的一个WebURLLoaderImpl::Context对象的成员函数OnReceivedData处理。

WebKit在请求Chromium的Content模块下载指定URL对应的网页内容时,可以指定将下载回来的网页内容交给一个后台线程进行接收和解析,这时候本地变量request_info指向的PendingRequestInfo对象的成员变量threaded_data_provider就会指向一个ThreadedDataProvider对象。这个ThreadedDataProvider对象就会将下载回来的网页内容交给一个后台线程接收和解析。我们不考虑这种情况,因此接下来我们继续分析WebURLLoaderImpl::Context类的成员函数OnReceivedData的实现,如下所示:

 

  1. void WebURLLoaderImpl::Context::OnReceivedData(const char* data,
  2. int data_length,
  3. int encoded_data_length) {
  4. ……
  5. if (ftp_listing_delegate_) {
  6. // The FTP listing delegate will make the appropriate calls to
  7. // client_->didReceiveData and client_->didReceiveResponse.
  8. ftp_listing_delegate_->OnReceivedData(data, data_length);
  9. } else if (multipart_delegate_) {
  10. // The multipart delegate will make the appropriate calls to
  11. // client_->didReceiveData and client_->didReceiveResponse.
  12. multipart_delegate_->OnReceivedData(data, data_length, encoded_data_length);
  13. } else {
  14. client_->didReceiveData(loader_, data, data_length, encoded_data_length);
  15. }
  16. }

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

当从Web服务器返回来的网页内容的MIME类型为“text/vnd.chromium.ftp-dir”时,WebURLLoaderImpl::Context类的成员变量ftp_listing_delegate_指向一个FtpDirectoryListingResponseDelegate对象。这时候从Web服务器返回来的网页内容是一些FTP目录,上述FtpDirectoryListingResponseDelegate对象对这些网页内容进行一些排版处理后,再交给WebKit处理,也就是ResourceLoader类的成员变量client_描述的一个ResourceLoader对象处理。

当从Web服务器返回来的网页内容的MIME类型为“multipart/x-mixed-replace”时,WebURLLoaderImpl::Context类的成员变量multipart_delegate_指向一个MultipartResponseDelegate对象。这时候从Web服务器返回来的网页内容包含若干个数据块,每一个数据块都有单独的MIME类型,并且它们之间通过一个Boundary String。上述MultipartResponseDelegate对象根据Boundary String解析出每一数据块之后,再交给WebKit处理,也就是ResourceLoader类的成员变量client_描述的一个ResourceLoader对象处理。

在其余情况下,WebURLLoaderImpl::Context类的成员函数OnReceivedData直接把Web服务器返回来的网页内容交给WebKit处理,也就是调用ResourceLoader类的成员变量client_描述的一个ResourceLoader对象的成员函数didReceiveData进行处理。

至此,我们就分析完成Chromium下载指定URL对应的网页内容的过程了。下载回来的网页内容将由WebKit进行处理,也就是由ResourceLoader类的成员函数didReceiveData进行处理。这个处理过程即为网页内容的解析过程,解析后就会得到一棵DOM Tree。有了DOM Tree之后,接下来就可以对下载回来的网页内容进行渲染了。在接下来的一篇文章中,我们再详细分析WebKit根据网页内容生成DOM Tree的过程,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

 

 

 

本文探讨一下chromium中加载URL的流程,具体来说是从地址栏输入URL地址到通过URLRequest类请求http流的过程。

为避免繁琐, URL请求过程中的NavigationController类和WebContents类姑且略过,直接从RenderViewHostImpl::Navigate方法下手,该方法通过ViewMsg_Navigate消息,将URL请求信息发送到render进程。Render进程中通过RenderViewImpl类的OnNavigate方法响应该请求,

该方法将URL请求信息封装成一个WebURLRequest对象,调用WebKit层WebFrameImpl对象的loadRequest方法。这里有必要简单介绍下几个关键类之间的关系:

RenderViewImpl派生自RenderView类,是Render进程中网页展示部分,对应于browser进程中的RenderViewHostImpl类,负责与browser进程进行IPC消息的通讯、对网页中的诸多行为进行响应。

RenderViewImpl维护一个WebView对象,两者通过一个map对象一一映射。

在WebView接口的实现类中,有一个Page成员,表示整个页面, Page类中又有一个或多个Frame对象,至少有一个主Frame。WebFrame接口应该是Page中主Frame的public访问代理,其关联操作发生在WebViewImpl类的initializeMainFrame方法里。

言归正传,WebFrameImpl的loadRequest方法最终会将请求转给FrameLoader的load方法,

FrameLoader是Frame的加载器。FrameLoader会将该请求委派给内部DocumentLoader成员的startLoadingMainResource方法来处理,再往下探讨之前,先把类之间的结构关系梳理一下:

DocumentLoader类:做为FrameLoader的成员,负责Document的加载。

ResourceFetcher类:资源提取器,相当于Resource的工厂类,负责创建不同的资源类型

的Resource封装。比如Image资源对应的是ImageResource封装类,

                    Script对应的是ScriptResource封装类。

在这里DocumentLoader调用ResourceFetcher的fetchMainResource方法加载Document资源, 创建的封装类为RawResource,创建完成后调用Resource的load方法,内部创建ResourceLoader类来执行具体的加载行为。

分析到ResourceLoader类,方有拨开云雾见日月的感觉,该类内部维护一个WebURLLoader接口,顾名思义,这才是真正执行URL加载的地方,不过只是个抽象接口,隶属WebKit的public接口部分,与平台相关,chromium对此接口有自己的实现,位于webkit_glue层 的WebURLLoaderImpl实现WebURLLoader接口,相关类结构如下:

IPCResourceLoaderBridge将URL请求任务封装成ResourceHostMsg_RequestResource

消息发送到browser进程,由browser进程请求URL数据。

自此,URL请求消息由browser进程抛到render进程,之后绕一大圈又回到browser进程执行任务, 路由过程中将页面的框架搭建起来, 之后由browser进程的URLRequest请求到的网络数据发送到render进程所做的解析操作都是对页面框架的添砖加瓦。

接下来分析一下browser进程对render进程抛送过来的URL请求的处理。

在ResourceDispatcherHostImpl类的OnRequestResource方法对render进程发送过来的ResourceHostMsg_RequestResource消息进行响应,相关类结构如下:

每收到一个URL请求, ResourceDispatcherHostImpl会创建一个ResourceLoader对象来承接URL的加载任务, ResourceLoader封装URLRequest类的使用,并将请求过程中的相关事件和数据分派到ResourceHandler链中。ResourceHandler链中的AsyncResourceHandler类负责将请求事件和数据封装消息发送到render进程中。Render进程ResourceDispatcher类来响应这些消息。

Chromium源码实在庞大,很多流程以前都看过,没做记录很快就没了印象,还是需要把学习的成果总结出来已做备忘。

 

 

chromium内核代码一直在更新,最近又有了大动作。尤其是IPC通信部分,因为性能问题,传统的IPC已经被弃用,虽然不是完全舍弃,但除了严重依赖于时序关系的Navigate相关消息外,其他的所有IPC::Channel都被替换成了mojom。这就导致以前的FrameMsg_Navigate、ResourceHostMsg_RequestSource等IPC消息在chromium代码中不再可见了。因为大体的流程没有改变,有兴趣的可以回顾一下老罗的文章,这里只是从头梳理一下chromium69版本内核代码加载一个url的整个过程,以及该过程中涉及到的一些重要的类和方法。

(个人理解得不够深刻,如有问题,请留言指教,不胜感激)

用户在地址栏输入一个URL,浏览器加载该URL的过程,被称为“Navigate”,所以该过程中会有很多类和方法名中,包含有该单词,如有遇到,可以认为该类或方法很可能和URL加载过程相关。

第一阶段(发送请求):

1. WebContents

在浏览器中代表一个页面的实体,它是一个抽象类,实现于WebContentsImpl。每个WebContents对象都会有一个NavigationController,它管理着URL的前进/后退列表,负责加载URL到WebContents中。所以,浏览器在导航URL的时候,会首先创建一个WebContentsImpl对象,调用其GetController()方法,获取到其NavigationController成员。获取到与WebContents关联的NavigationController后,浏览器一般会调用它的LoadURLWithParams()方法进行目标URL的加载工作。

2. NavigationController

每个WebContents里都关联着一个NavigationController对象,且每个NavigationController也只被关联在一个WebContents中,二者一一对应。NavigationController也是一个抽象类,它的实现类的类名,也是在后面加个”Impl”,即NavigationControllerImpl。当其成员函数LoadURLWithParams()方法被调用后,它会在进行简单的url判断后,调用成员函数NavigateWithoutEntry()继续处理。该函数实现里主要做了三件事:
1. 确定目标URL所要加载在FrameTree中的哪个node上,即得到一个FrameTreeNode对象;
2. 创建一个NavigationEntry对象,用于组建一个NavigationRequest;
3. 输送NavigationRequest到目标FrameTreeNode的NavigatorImpl对象的Navigate()方法中。

3. Navigator

该类负责在一棵FrameTree的节点中执行URL导航操作,可以被同一棵FrameTree上的多个FrameTreeNode所共享,但不能被多棵FrameTree的子节点所共享。该类是一个抽象类,实现类为NavigatorImpl。Navigate()方法中,先判断当前指定的FrameTreeNode所代表的网页中是否有悬挂的BeforeUnload事件处理器需要执行,如果有,则先执行BeforeUnload事件处理程序,稍后派发NavigationRequest到FrameTreeNode;如果没有,则立即派发。派发形式如下:
1. 调用FrameTreeNode的CreateNavigationRequest()方法,将NavigationRequest对象存储;
2. 调用FrameTreeNode中NavigationRequest对象的BeginNavigation()方法进行加载。
BeforeUnload事件的判断处理,是在第一步和第二步中间。

4. NavigationRequest

该类存在于UI线程,确保URL请求会在IO线程中的ResourceDispatcherHost中执行,描述UI线程和IO线程之间的交互。该类先对目标URL进行了内容安全策略检查,以及注册了各种NavigationThrottles对目标URL进行审批,最终会创建一个NavigationURLLoader对象。

5. NavigationURLLoader

该类实现类NavigationURLLoaderImpl构造函数中,进行线程调度,在IO线程中执行StartWithoutNetworkService()方法,并在该方法中调用ThrottlingURLLoader::CreateLoaderAndStart()方法创建了一个ThrottlingURLLoader对象。

6. ThrottlingURLLoader

该类继承于network::mojom::URLLoaderClient,可以进行IPC通信,且Render进程和Browser进程都有其实例化对象。类名中带着”Throttling”的,且和URL加载相关的类,通常都会根据某些自定义规则,对网络数据进行拦截过滤处理,就像一个瓶塞一样。它的CreateLoaderAndStart()方法,创建完自身的一个实例对象后,调用其Start()方法。Start方法接收一个SharedURLLoaderFactory类实例(该实例是在NavigationURLLoaderImpl的StartWithNetworkService()方法中创建的),并调用SharedURLLoaderFactory实例的CreateLoaderAndStart()。

7. SingleRequestURLLoaderFactory

该类继承于network::SharedURLLoaderFactory,而SharedURLLoaderFactory又继承于mojom::URLLoaderFactory。URLLoaderFactory这一系列的近亲类(比如WebUIURLLoaderFactory、FileURLLoaderFactory、CORSURLLoaderFactory等),都可以创建一个mojom::URLLoader对象,既可以跨进程加载url并得到返回数据,又可以同进程加载url,该性质来自于mojom的调用机制。
SingleRequestURLLoaderFactory的CreateLoaderAndStart方法中执行回调函数,调用堆栈返回到了URLLoaderRequestController::CreateNonNetworkServiceURLLoader(),该方法调用ResourceDispatcherHostImpl类的BeginNavigationRequest()。

8. ResourceDispatcherHostImpl

ResourceDispatcher和ResourseDispatcherHost分别是Render进程和Borwser进程进行资源分发的接口类。
在BeginNavigationRequest()方法中创建了一个URLRequest对象,在BeginRequestInternal()方法中创建了一个ResourceLoader对象,然后在StartLoading()方法中,调用ResourceLoader对象的StartRequest()方法开始加载请求。

9. ResourceLoader

该类集中接收转发URLRequest、SSLErrorHandler、SSLClientAuthHandler、ResourceHandler相关的事件。
该类StartRequestInternal()方法中,直接调用了URLRequest对象的Start()方法,至此结束了Navigate()的第一个过程。

 

第二阶段(数据响应):

网络模块获取到响应头数据,数据流向及处理方法。

ResourceLoader类的ResponseCompleted()方法被调用,然后通过ResourceLoader成员变量handler的OnResponseCompleted()方法向上传递数据。主要的handler类有MimeSniffingResourceHandler、CrossSiteDocumentResourceHandler、InterceptingResourceHandler、MojoAsyncResourceHandler等,各个handler都可以对数据进行截获处理,最终NavigationRequest类的OnResponseStarted()方法被调用。该方法最终调用到RenderFrameHostImpl::CommitNavigation(),RenderFrameHostImpl发送了一个IPC消息到Render进程。

 

第三阶段(Render进程发起主要资源(一般指html文件)网络请求):

1. RenderFrameImpl

CommitNavigation()函数除了携带response_header、request_params等基本信息,还有mojom通信相关接口url_loader_client_endpoints和Browser进程目前所支持的URLLoaderFactory列表subresource_loader_factories。关于mojom接口的绑定过程,参考Converting Legacy Chrome IPC To Mojo一文,这里不详细赘述。参数subresource_loader_factories是一个Bundle,包裹着从Browser进程传递过来的各种URLLoaderFactory,前面说过,URLLoaderFactory可以跨进程进行资源请求,而不同的URLLoaderFactory用来请求不同scheme的资源。比如WebUIURLLoaderFactory用来请求浏览器内置页面,url格式一般类似于chrome://page;再比如FileSystemURLLoaderFactory用来请求本地资源,url格式类似于file:///C:\\test.txt。进行网络请求的时候,Render进程去factory列表里根据url的scheme里查找对应的URLLoaderFactory,调用它的CreateLoaderAndStart()方法进行资源请求。
RenderFrameImpl类的CommitNavigation()方法被mojom消息调起,它根据消息携带的header信息、request参数信息以及url_loader_client_endpoints创建一个WebURLRequest对象,并调用成员变量frame_的CommitNavigation()将其传递过去。RenderFrameImpl的成员变量frame_指向了WebLocalFrameImpl,WebLocalFrameImpl接收到WebURLRequest对象后,将其转换成FrameLoadRequest类型,然后调用传递给FrameLoader类的CommitNavigation()函数。

2. FrameLoader

CommitNavigation()接收到FrameLoadRequest后,直接调用了StartLoad()函数,在StartLoad()函数中,创建并用FrameLoadRequest参数初始化了一个DocumentLoader对象,然后调起DocumentLoader对象的StartLoading()方法。

3. DocumentLoader

StartLoading()函数准备好request、fetcher等参数,调用RawResource类的静态方法FetchMainResource()去请求主要资源。

4. RawResource

FetchMainResource()函数,根据Resoure::kMainResource类型去创建一个ResourceFactory对象,同FetchParameters对象一起作为参数,调起参数列表中fetcher的RequestResource()函数。

5. ResourceFetcher

RequestResource()函数创建Resource对象,调用StartLoad()方法。StartLoad()方法创建一个ResourceLoader对象,调用loader的Start()方法。

6. ResourceLoader

Start()方法调用ResourceLoaderScheduler::Request(),最终回调到ResourceLoader::StartWith()方法中。ResourceLoader的StartWith()调用WebURLLoaderImpl类的LoadAsynchronously()方法。

7. WebURLLoaderImpl

LoadAsynchronously()通过自己的成员变量context_,对request进行加载。最终从WebURLLoaderImpl::Context::Start()方法中调用了ResourceDispatcher类的StartAsync()方法。

8. ResourceDispatcher

调用ThrottlingURLLoader类的CreateLoaderAndStart()方法。

9. ThrolltingURLLoader

Start()函数接收一个SharedURLLoaderFactory和一个ResourceRequest参数,调用factory的CreateLoaderAndStart()方法。

10. ChildURLLoaderFactoryBundle

CreateLoaderAndStart()方法被调起,参数包含network::ResourceRequest和一个network::mojom::URLLoaderRequest对象,根据request.url获取对应的URLLoaderFactory,然后调用该factory的CreateLoaderAndStart()发送跨进程IPC消息到Browser Process。

 

第四阶段(请求主要资源):

Browser进程接收CreateLoaderAndStart()方法的跨进程调用的位置,是在ResourceMessageFilter类的同名方法CreateLoaderAndStart()。 该类有一个成员变量url_loader_factory_,指向CORSURLLoaderFactory,且该类的CreateLoaderAndStart()方法被调用。最终传递到URLLoaderFactoryImpl::CreateLoaderAndStart(),该方法获取全局的ResourceDispatcherHostImpl实例,调用其OnRequestResourceWithMojo(),代替以前的ResourceHostMsg_RequestResource消息。ResourceDispatcherHostImpl接收来自Render进程的网络请求相关参数,调用OnRequestResourceInternal()方法开始对该请求进行加载。过程同第一阶段相同。

当数据请求有结果时,同样是几个Handler类的OnReadCompleted()方法最先被调用,然后通过network::mojom::URLLoaderClientProxy类的OnStartLoadingResponseBody()方法将数据结果跨进程通知回Render进程。

第五阶段(接收处理主要资源,发起子资源请求):

同名方法OnStartLoadingResponseBody()被调用,分别经过以下几个类,最终到达HTMLTreeBuilder:
URLResponseBodyConsumer::OnReadable()
WebURLLoaderImpl::RequestPeerImpl::OnReceivedData()
WebURLLoaderImpl::Context::OnReceivedData()
ResourceLoader::DidReceviedData()
RawResource::AppendData()
DocumentLoader::DataReceived()   ::ProcessData()   ::CommitData()  ::InstallNewDocument()
HTMLDocumentParser::AppendBytes()  ::PumpPendingSpeculations()  ::ProcessTokenizedChunkFromBackgroundParser()
HTMLTreeBuilder::ConstructTree()

在构建DOM树的时候,如果发现一个子节点需要加载资源,比如css文件。则HTMLDocumentParser类的DocumentElementAvailable()方法会被调用,然后调用自身的资源预加载器preloader_的TakeAndPreload()对资源进行加载。之后的调用过程如下:
HTMLResourcePreloader::Preload()
PreloadRequest::Start()
DocumentLoader::StartPreload()
CSSStyleSheetResource::Fetch()
ResourceFetcher::RequestResource()
过程同上…..
ChildURLLoaderFactoryBundle::CreateLoaderAndStart()

 

 

以下是堆栈调用的一个大概过程:

  1. content::NavigationControllerImpl::LoadURLWithParams()
  2. content::NavigationControllerImpl::NavigateWithoutEntry()
  3. content::NavigatorImpl::Navigate()
  4. content::NavigationRequest::BeginNavigation()
  5. content::NavigationHandleImpl::WillStartRequest()
  6. content::NavigationRequest::OnStartCehcksComplete()
  7. content::NavigationURLLoader::Create()
  8. content::NavigationURLLoaderImpl::NavigationURLLoaderImpl()
  9. content::NavigationURLLoaderImpl::StartWithoutNetworkService()
  10. content::ThrottlingURLLoader::CreateLoaderAndStart()
  11. content::ThrottlingURLLoader::Start()
  12. content::ThrottlingURLLoader::StartNow()
  13. content::SingleRequestURLLoaderFactory::CreateLoaderAndStart()
  14. content::SingleRequestURLLoaderFactory::HandleRequest()
  15. content::NavigationURLLoaderImpl::URLLoaderRequestController::CreateNonNetworkServiceURLLoader();
  16. content::ResourceDispatcherHostImpl::BeginNavigationRequest()
  17. content::ResourceDispatcherHostImpl::BeginNavigationRequestInternal()   –>  content::ResourceLoader::ResourceLoader()
  18. content::ResourceDispatcherHostImpl::StartLoading()
  19. content::ResourceLoader::StartRequest()
  20. content::ResourceLoader::ScopedDeferral::~ScopedDeferral()   //判断状态
  21. content::ResourceLoader::Resume()
  22. content::ResourceLoader::StartRequestInternal()
  23. net::URLRequest::Start()
  24. content::NavigationURLLoaderImpl::OnReceiveResponse()
  25. content::NavigationRequest::OnResponseStarted()
  26. content::NavigationHandleImpl::WillProcessResponse()
  27. content::NavigationRequest::OnWillProcessResponseChecksComplete()
  28. content::NavigationRequest::CommitNavigation()
  29. content::RenderFrameHostImpl::CommitNavigation()
  30. content::mojom::FrameNavigationControlProxy::CommitNavigation()         // Send IPC Message To Render Process
  31. —————–Render Process————————————-
  32. ………
  33. content::RenderFrameImpl::CommitNavigation()
  34. blink::WebLocalFrameImpl::CommitNavigation()
  35. blink::FrameLoader::CommitNavigation()
  36. blink::FrameLoader::StartLoad()
  37. blink::DocumentLoader::StartLoading()
  38. blink::RawResource::FetchMainResource()
  39. blink::ResourceFetcher::RequestResource()
  40. blink::ResourceFetcher::StartLoad()
  41. blink::ResourceLoader::Start()
  42. blink::ResourceLoaderScheduler::Request()
  43. blink::ResourceLoaderScheduler::Run()
  44. blink::ResourceLoader::Run()
  45. blink::ResourceLoader::StartWith(blink::ResourceRequest& request)
  46. content::WebURLLoaderImpl::LoadAsynchronously()
  47. content::WebURLLoaderImpl::Context::Start()
  48. content::ResourceDispatcher::StartAsync()
  49. content::ThrottlingURLLoader::CreateLoaderAndStart()
  50. content::ThrottlingURLLoader::Start()
  51. content::ThrottlingURLLoader::StartNow()
  52. content::ChildURLLoaderFactoryBundle::CreateLoaderAndStart()
  53. network::mojom::URLLoaderFactoryProxy::CreateLoaderAndStart()
  54. ————————————————-Browser Process—————–
  55. content::ResourceMessageFilter::CreateLoaderAndStart()
  56. network::cors::CORSURLLoaderFactory::CreateLoaderAndStart()
  57. content::URLLoaderFactoryImpl::CreateLoaderAndStart()
  58. content::ResourceDispatcherHostImpl::OnRequestResourceWithMojo()
  59. content::ResourceDispatcherHostImpl::OnRequestResourceInternal()
  60. content::ResourceDispatcherHostImpl::BeginRequest()
  61. content::ResourceDispatcherHostImpl::StartLoading()
  62. content::ResourceLoader::StartRequest()
  63. content::ResourceLoader::ScopedDeferral::~ScopedDeferral()   //判断状态
  64. content::ResourceLoader::Resume()
  65. content::ResourceLoader::StartRequestInternal()
  66. net::URLRequest::Start()
  67. content::LayeredResourceHandler::OnReadCompleted()
  68. content::InterceptingResourceHandler::OnReadCompleted()
  69. maxthon::MxResourceSnifferHandler::OnReadCompleted()
  70. content::MojoAsyncResourceHandler::OnReadCompleted()
  71. network::mojom::URLLoaderClientProxy::OnStartLoadingResponseBody()
  72. ———————–Renderer Process————————————-
  73. content::URLLoaderClientImpl::OnStartLoadingResponseBody()
  74. content::URLResponseBodyConsumer::OnReadable()
  75. content::WebURLLoaderImpl::RequestPeerImpl::OnReceivedData()
  76. content::WebURLLoaderImpl::Context::OnReceivedData()
  77. blink::ResourceLoader::DidReceiveData()
  78. blink::RawResource::AppendData()
  79. blink::Resource::AppendData()
  80. blink::DocumentLoader::DataReceived()
  81. blink::DocumentLoader::ProcessData()
  82. blink::DocumentLoader::CommitData()
  83.     blink::HTMLDocumentParser::AppendBytes()
  84.     blink::DocumentLoader::CommitNavigation()
  85. blink::DocumentLoader::InstallNewDocument()
  86. blink::HTMLDocumentParser::PumpPendingSpeculations()
  87. blink::HTMLDocumentParser::ProcessTokenizedChunkFromBackgroundParser()
  88. blink::HTMLTreeBuilder::ConstructTree()
  89. blink::HTMLTreeBuilder::ProcessToken()
  90. blink::HTMLTreeBuilder::ProcessStartTag()
  91. blink::HTMLConstructionSite::InsertHTMLHtmlStartTagBeforeHTML()
  92. blink::HTMLHtmlElement::InsertedByParser()
  93. blink::HTMLDocumentParser::DocumentElementAvailable()
  94. blink::ResourcePreloader::TakeAndPreload()
  95. blink::HTMLResourcePreloader::Preload()
  96. blink::PreloadRequest::Start()
  97. blink::DocumentLoader::StartPreload()
  98. blink::CSSStyleSheetResource::Fetch()
  99. blink::ResourceFetcher::RequestResource()
  100. blink::ResourceFetcher::StartLoad()
  101. blink::ResourceLoader::Start()
  102. blink::ResourceLoaderScheduler::Request()
  103. blink::ResourceLoaderScheduler::Run()
  104. blink::ResourceLoader::Run()
  105. blink::ResourceLoader::StartWith(blink::ResourceRequest& request)
  106. content::WebURLLoaderImpl::LoadAsynchronously()
  107. content::WebURLLoaderImpl::Context::Start()
  108. content::ResourceDispatcher::StartAsync()
  109. content::ThrottlingURLLoader::CreateLoaderAndStart()
  110. content::ThrottlingURLLoader::Start()
  111. content::ThrottlingURLLoader::StartNow()
  112. content::ChildURLLoaderFactoryBundle::CreateLoaderAndStart(n)
  113. network::mojom::URLLoaderFactoryProxy::CreateLoaderAndStart()

 

今天先列一个大纲,详细叙述需要慢慢填充,望大家谅解!

 

 

 

 

 

 

上一节我们介绍了Chrome对于DNS协议的解析,今天我们继续介绍一个更为大家所熟知的协议,HTTP协议。
HTTP协议是一种很常见的协议,在chromium网络库中,对HTTP的解析主要是分为两部分,一部分是去缓存数据的获取,另外一部分则是重新加载网络资源。简单点说就是当我们在浏览器中输入 http://www.bytedance.net 的时候,优先去浏览器中的缓存数据查找是否存在相应的资源,如果存在则直接加在缓存中的数据,否则就需要建立TCP连接,进行网络数据的加载。对于刚才举的那个例子,要看在chrome中是否已经有缓存数据存在,只需要在浏览器中输入 chrome://cache/ 即可查看:

这里写图片描述

和DNS一样,在net网络库中的HTTP源码文件多达上百个,一个个去看效率低并且也学不到什么有用的东西,还是像上次一样先找出其主流程,分析关键函数,了解整个过程的来龙去脉,最后才是逐个击破。

可以把HTTP的资源加载看作是浏览器解析一个用户指定的URL的过程(实际上URL不仅仅包含http,还包含https、ftp等等协议),而整个chrome的net库对外的一个大的接口函数就是UrlRequest,顾名思义,这个函数代表一个URL请求,net库就是要根据这个请求,快速准确地返回相应的数据,这个函数的文件位于 /net/url_request。 当我们在浏览器中输入 http://www.bytedance.net 时,通过URL解析,得到其scheme是http,这个时候就会调用到HTTP模块进行相应的处理,关于这一部分的详细过程,后续我会再找机会详细分析URL的解析过程。

当HTTP模块收到这个URL解析请求时,就像我们最先提到的那样,会先去调用HttpCache::Transaction::Start 这个方法,这个函数的部分代码如下:

int HttpCache::Transaction::Start(const HttpRequestInfo* request,
                                  const CompletionCallback& callback,
                                  const BoundNetLog& net_log) {
  …
  next_state_ = STATE_GET_BACKEND;
  int rv = DoLoop(OK);
  // Setting this here allows us to check for the existence of a callback_ to
  // determine if we are still inside Start.
  if (rv == ERR_IO_PENDING) {
    callback_ = tracked_objects::ScopedTracker::TrackCallback(
        FROM_HERE_WITH_EXPLICIT_FUNCTION(
            "422516 HttpCache::Transaction::Start"),
        callback);
  }
  return rv;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

可以看到这部分代码主要是启动了一个状态机,并且制定了初始状态STATE_GET_BACKEND 和 结束状态 ERR_IO_PENDING。关于这个结束状态,是为了防止状态机阻塞后不能及时结束而导致程序无法继续执行,因此当状态码为ERR_IO_PENDING,则直接跳出该状态机循环。启动这个状态机是为了从缓存中获取我们想要的数据,这部分的流程分支有很多,光状态机的state就有40种之多,作者也将一些流程分支都整理了出来(这里只贴了一部分,想看完整版的可以去http_cache_transaction.cc 这个文件中看)

这里写图片描述

大概看了下,好家伙,有14种状态,这还只是作者列出来的,一些异常情况还未考虑进来,但是从根本上来看,大致可以分为两种情况:

情况一就是Cached entry,这部分状态就代表我们从缓存数据中找到了想要的资源,直接拿过来用就可以了,不需要再去进行网络请求。
情况二就是Not-cached entry,这部分状态代表我们并没有从缓存中找到想要的数据,因此我们需要调用 SendRequest 来实现网络加载资源的请求,如果调用成功,即我们通过网络请求获取了想要的资源,除了将资源返回给用户之外,还需要调用CacheWriteData 来更新缓存中的数据,这样等下次访问时,就可以直接在cache命中。

再来看一下读取缓存数据的流程:
首先是状态变更:GetBackend* -> InitEntry -> OpenEntry* -> AddToEntry* -> CacheReadResponse* -> CacheDispatchValidation
用文字解读一下就是,初始化Entry,打开这个Entry,根据URL获取Entry数据,读取数据并且构造响应消息体。

这里写图片描述

这里的cache_key_实际上就是URL,通过这个值便可以唯一检索到缓存中的数据。

以上是HttpCache::Transaction状态机的实现概述,简单点说就是以URL为key值在缓存中查找数据,如果匹配成功则直接返回,如果不成功则需要发送网络请求,这就涉及到我们接下来要介绍的HttpNetworkTransaction了。
当我们需要调用网络加载所需资源时,就要把状态机状态置为STATE_SEND_REQUEST,此时就会调用到下面这个函数:

这里写图片描述

我们注意到这个函数最后是调用了network_trans中的Start方法,此时便会调用到
int HttpNetworkTransaction::Start 这个函数里来,同样的,在该函数中也新启动了一个状态机,并且将初始状态设置为 STATE_NOTIFY_BEFORE_CREATE_STREAM :

这里写图片描述

NetworkTransaction层的状态机状态码有以下几种,比之前的cache层要少很多
这里写图片描述

顺着这个状态码整理NetworkTransaction层的大致流程如下:
DoCreateStream —> DoInitStream —> DoGenerateProxyAuthToken —> DoGenerateServerAuthToken —> DoInitRequestBody —> DoBuildRequest —> DoSendRequest —> DoReadHeaders —> DoReadBody
用文字解释一下就是:创建strem,初始化参数,建立连接,初始化请求主体,发送请求,并且读取相应的返回信息,最后根据需要处理连接。

这里要提一下的是DoSendRequest方法,它实际上调用的是HttpBasicStream::SendRequest的方法,而HttpBasicStream则调用HttpStreamParser中的SendRequest方法来实现,其大致思路就是创建连接,构造消息体,调用tcp socket进行消息头和数据的传输,最终将结果返回给NetworkTransaction层,有兴趣的同学可以去看一下代码的具体实现,这部分代码位于 http_stream_parser.cc 文件中。

介绍完NetworkTransaction层的网络传输层以后,还会做一个动作,那就是将得到的数据写入到cache中去,这个我们在之前已经提到过,这里就不再赘述了。

关于HTTP的解析就先介绍到这里,简单总结一下,对于一个URL请求,当解析为HTTP请求时,先去cache层中查找,如果找到了,则构造数据并返回;如果没找到,再去调用network层发起网络请求,通过调用底层的tcp socket实现数据的传输,当完成加载以后,再将其写入到cache层中。当然,这只是最常见的一种流程,关于HTTP还有许多需要探索和学习的地方,这些都留在以后的章节吧。

 

 

 

 

 

 

 

 

 

 

 

 

 

上一篇
下一篇