WebKit 内核源代码分析 ( 四 )
红心地瓜( tomorrow.cyz@gmail.com)
摘要:本文介绍 WebCore中 Loader模块是如何加载资源的,分主资源和派生资源分析 loader模块的类关系。
关键词: WebKit,Loader,Network,ResouceLoader,SubresourceLoader
一、类结构及接口
Loader模块是 Network模块的客户。 Network模块提供指定资源的获取和上传功能,获取的资源可能来自网络、本地文件或者缓存。对不同 HTTP实现的适配会在 Network层完成,所以 Loader接触到的基本上是同 OS和 HTTP实现无关的Network层接口。
如上是 Loader和 NetWork之间的类关系图, ResourceHandleClient是ResourceHandle的客户,它定义一系列虚函数,这些虚函数是 ResouceHandle的回调,继承类实现这些接口。
ResouceHandleClient的接口同网络传输过程息息相关,一般为某一个网络事件对应的回调。下面是其中的一些接口。
//一般情况下,在发起网络请求前调用,可以设置特定的 Http
头部,比如 user agent等,在重定向请求的时候,也会自动调
用
void willSendRequest(ResourceHandle*, ResourceRequest&, const
ResourceResponse&)
//上传数据的时候,在 TCP wrtie事件的时候,向对方发送数据的
时候调用, loader可以根据这个回调显示上传进度。
void didSendData(ResourceHandle*, unsigned long long
/*bytesSent*/, unsigned long long /*totalBytesToBeSent*/)
//收到第一个响应包,此时至少 http的部分头部已经解析(如
status code), loader根据响应的头部信息判断请求是否成功
等。
void didReceiveResponse(ResourceHandle*,
const ResourceResponse&)
//收到 HTTP响应数据,类似 tcp的 read事件,来 http响应数据
了, Network的设计机制是来一段数据上传一段数据。
void didReceiveData(ResourceHandle*, const char*, int,
int /*lengthReceived*/)
//加载完成,数据来齐。
void didFinishLoading(ResourceHandle*, double /*finishTime*/)
//加载失败
void didFail(ResourceHandle*, const ResourceError&)
//要求用户鉴权
void didReceiveAuthenticationChallenge(ResourceHandle*,
const AuthenticationChallenge&)
WebCore把要加载的资源分成两类,一类是主资源,比如 HTML页面,或者下载项,一类是派生资源,比如 HTML页面中内嵌的图片或者脚本链接。这两类资源对于回调的处理有很大的不同,比如,同样是下载失败,主资源可能需要向用户报错,派生资源比如页面中的一张图下载失败,可能就是图不显示或者显示代替说明文字而已,不向用户报错。因此有了 MainResourceLoader和 SubresourceLoader之分。它们的公共基类 ResourceLoader则完成一些两种资源下载都需要完成的操作,比如通过回调将加载进程告知上层应用。
ResourceLoader通过 ResourceNotifier类将回调传导到FrameLoaderClient类。
主资源的加载是立刻发起的,而派生资源则可能会为了优化网络,在队列中等待(这里的立刻发起是 loader层面的,不是 Network层面的 )。ResourceScheduler这个类就是用来管理资源加载的调度。主要调度对象就是派生资源,会根据 host来影响资源加载的先后顺序。
主资源和派生资源的加载还有一个区别,主资源目前是没有缓存的,而派生资源是有缓存机制的。这里的缓存指的是 Resouce Cache,用于保存原始数据(比如CSS, JS等),以及解码过的图片数据,通过 Resource Cache可以节省网络请求和图片解码的时候。不同于 Page Cache, Page Cache存的是 DOM树和 Render树的数据结构,用来在前进后退的时候快速显示页面。
二、加载流程
下图是加载 html页面时,一个正常的加载流程。
三、主资源加载过程
1. DocumentLoader调用 MainResourceLoader::load向 loader发起请求
2. 调用 MainResourceLoader::loadNow
3. 调用 MainResourceLoader::willSendRequest
4. 调用 ResourceLoader::willSendRequest,将 callback通过ResourceNotifier传导给 FrameLoaderClient。 Client可以在回调中操作 ResourceRequest,比如设置请求头部。
5. 调用 PolicyChecker::checkNavigationPolicy过滤掉重复请求等
6. loader调用 ResourceHandle::create向 Network发起加载请求
7. 收到第一个 HTTP响应数据包 ,Network回调MainResourceLoader::didReceiveResponse,主要处理 HTTP头部。
8. 调用 PolicyChecker:: checkContentPolicy,并最终通过FrameLoaderClient的 dispatchDecidePolicyForMIMEType判断是否为下载请求(存在 "Content-Disposition"http头部)
9. 调用 MainResourceLoader::continueAfterContentPolicy,根据ResourceResponse检测是否发生错误。
10. 调用 ResourceLoader::didReceiveResponse,将 callback通过ResourceNotifier传导给 FrameLoaderClient。
11. 收到 HTTP体部数据,调用 MainResourceLoader::didReceiveData
12. 调用 ResourceLoader::didReceiveData,将 callback通过ResourceNotifier传导给 FrameLoaderClient
13. 调用 MainResourceLoader::addData
14. 调用 DocumentLoader::receivedData
15. 调用 DocumentLoader::commitLoad
16. 调用 FrameLoader::commitProvisionalLoad, FrameLoader从provisional状态跃迁到 Committed状态
17. 调用 FrameLoaderClientQt::committedLoad
18. 调用 DocumentLoader::commitData,启动 Writer对象来处理数据(DocumentWriter::setEncoding, DocumentWriter::addData)
19. 调用 DocumentWriter::addData
20. 调用 DocumentParser::appendByte
21. 调用 DecodedDataDocumentParser::appendBytes对文本编码进行解码
22. 调用 HTMLDocumentParser::append,进行 HTML解析
23. 数据来齐,调用 MainResourceLoader::didFinishLoading
24. 调用 FrameLoader::finishedLoading
25. 调用 DocumentLoader::finishedLoading
26. 调用 FrameLoader::finishedLoadingDocument,启动 writer对象接收剩余数据,重复 19-22进行解析
27. 调用 DocumentWriter::end结束接收数据(调用Document::finishParsing)
28. 调用 HTMLDocumentParser::finish
四、派生资源加载流程
在派生资源的加载中, SubresourceLoader更多起到的是一个转发的作用,通过它的 client( SubresourceLoaderClient类)来完成操作。
各个加载阶段的处理在 SubresourceLoaderClient的派生类CachedResourceRequest,Loader,IconLoader中完成。 Client会创建SubresourceLoader。
请求发起阶段, ResourceLoadScheduler负责对 SubresourceLoader进行调度。
Document类会创建 CachedResourceLoader类的对象m_cachedResourceLoader,这个类 (对象 )提供了对 Document的派生资源的访问接口 requestImage, requestCSSStyleSheet, requestUserCSSStyleSheet,requestScript, requestFont, requestXSLStyleSheet,requestLinkPrefetch。为了实现这些接口, CachedResourceLoader需要创建CachedResourceRequest对象来发起请求。
一般情况下,一个 Document拥有一个 CachedResourceLoader类实例。
MemoryCache类则对提供缓存条目的管理,可以方便地进行 add, remove,缓存淘汰等。具体的缓存条目则是通过 CachedResource类存储, MemoryCache类维护了一个 HashMap存储所有缓存条目。
HashMap <String,CachedResource> m_resources;
CachedResourceRequest依赖于 CachedResource,在 CacheResourceRequest的构造函数中,会传入 CachedResource对象作为参数。 CachedResource既存储响应体部,也存储同 cache相关的头部。在发起请求前,会检查是否有 cache的validator,在收到响应的时候,则需要更新对应的头部。 CachedResource类实现了 RFC2616中的缓存一节。实际上 CachedResource类真正完成了同网络的通信。 CachedResource类根据申请的资源类型派生出不同的子类。
CachedResource类的使用者必须是 CachedResourceClient,在这个类中维护了 CachedResourceClient类的集合 m_clients。每一个 Client通过addClient和 removeClient将自己加入到该类的 Client集合中。CachedResourceClientWalker则提供了 CachedResouceClient的一个遍历接口。当数据来齐的时候, CachedResource类会通过CachedResouceClient::notifyFinished接口通知使用者。
下图是 Image元素对应的几个类关系。
下面以 image为例分析其加载过程
1. 解析 html页面的时候,解析到 img标签,调用HTMLImageElement::create创建 HTMLImageElement对象,该对象包含HTMLImageLoader对象 m_imageLoader
2. 解析到 img的 href属性,调用ImageLoader::updateFromElementIgnoringPreviousError
3. 调用 ImageLoader::updateFromElement
4. 调用 CachedResourceLoader::requestImage
5. 调用 CachedResourceLoader::requestResource( 根据缓存的情况确定是否可以从缓存获取,或者需要 revalidate,或者需要直接从网络获取 )
6. 调用 CachedResourceLoader::loadResource
7. 根据 Resource的类型调用 createResource创建对应的 CachedResource
8. 调用 MemoryCache::add在 cache中查找是否有对应的 cache条目,如果没有创建之
9. 调用 CachedImage::load
10. 调用 CachedResource::load
11. 调用 CachedResourceLoader::load
12. 调用 CachedResourceRequest::load
13. 创建 CachedResourceRequest对象,它将作为 SubresourceLoader的client
14. 调用 ResourceLoaderScheduler::scheduleSubresourceLoad
15. 调用 SubresourceLoader::create
16. ResourceLoadScheduler::requestTimerFired
17. 调用 ResourceLoader::start
18. 调用 ResourceHandle::create发起请求
19. 收到 HTTP响应头部,调用 ResourceLoader::didReceiveResponse
20. 调用 SubresourceLoader::didiReceiveResponse
21. 调用 CachedResourceRequest::didReceiveResponse处理响应头部,特别是同缓存相关的头部,比如 304的 status code
22. 调用 ResourceLoader::didReceiveResponse
23. 收到体部数据,调用 ResourceLoader::didReceiveData
24. 调用 SubresourceLoader::didReceiveData
25. 调用 ResourceLoader::didReceiveData
26. 调用 ResourceLoader::addData将数据存储到 SharedBuffer里面
27. 调用 CachedResourceRequest::didReceiveData
28. 数据来齐 ,调用 ResourceLoader::didFinishLoading
29. 调用 SubresourceLoader::didFinishLoading
30. 调用 CachedResourceRequest::didFinishLoading
31. 调用 CachedResource::finish
32. 调用 CachedResourceLoader::loadDone
33. 调用 CachedImage::data,创建对应的 Image对象,解码