分类: WINDOWS
2011-07-21 15:54:36
今天由于工作需要,需要进一步理解html5的 offline 的机制以及原理,这儿还是采用qtwebkit的代码,
同时选择qt的demo browser作为浏览器,访问我自己的一个测试页面。
CACHE MANIFEST
#The file is only for index.html
CACHE:
#NETWORK:
#*
另外注意在apache server上打开mimetype对appcache类型的选择。
这个也参看w3c的html5草案文档, http://dev.w3.org/html5/spec/offline.html#offline
另外webkit 官网有一张图,表示加载页面的几个内部对象关系
http://www.webkit.org/blog/1188/how-webkit-loads-a-web-page/
当然还需要在qt的代码中,打开offlinecache的选项。
我们先简单做一个静态分析,即webkit中这部分代码。。
它位于webkit\WebCore\loader\appcache
Manifestparser
这个比较简单,就是清单文件的解析对象。
DOMApplicationCache
??
ApplicationCacheStorage
应该是底层处理sqlite存储的一个类。
ApplicationCacheResource
ApplicationCacheHost
应该是每一个html文件,就会作为就会作为一个cachehost,是否有manifest属性貌似不影响这个对象创建,最多就是selectwithoutmanifest而已。
ApplicationCacheGroup
同属于同一个manifest文件的资源属于一个group.
ApplicationCache
貌似在内存在中管理一个组的cache
我们先运行browser,然后设置好断点,由于这是首次运行,因此访问的page都没有cache.
1.
DocumentLoader 创建并且管理了ApplicationCachehost
1)
applicationcachehost 记录了documentloader,但是不涉及lifttime
2.
尝试用documentloader加载
Documentloader会尝试询问applicationcachehost是否这个资源你哪儿有?
1)
Applicationcachehost 先判断下qtwebkit是否开启了离线部署支持.
2)
然后通过ApplicationCacheGroup的一个静态函数 ; m_mainResourceApplicationCache =
ApplicationCacheGroup::cacheForMainRequest(request, m_documentLoader); 要求Cache 此请求
3)
CacheGroup 内部判断是否是http请求且是http –get
4)
通过一个friend的全局函数cacheStorage, 尝试将request访问的url进行cache,而cachestorage
()这个函数调用,实际上返回一个静态的局部变量,表示applicationcachestorage。
5)
Cachestorage对象,尝试创建数据库文件,默认文件名为applicationcache.db; 如果文件不存在则创建文件,并且创建一系列的表。
6)
由于是首次加载,所以并不存在这样的cachegroup,也没有这样的资源,所以
m_mainResourceApplicationCache =
ApplicationCacheGroup::cacheForMainRequest(request, m_documentLoader);
这个呼叫最终得到NULL; 如果有cache的实际上,将直接尝试从db中获取。
7) 由于cache中没,所以程序继续执行,就像没有开启离线部署那样
3.
从webserver上拿到了整个index。Html,这个从httpanalyzer上也能够看到
1) 这个时候发现在收到数据的时候,applicationcachehost又得到了一次机会执行。
这个时候,实际上是检查这个httpstatus
if
(response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5)
if
(scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
return
true;
return false;
由于本次是第一次执行,所以retcode=200,因此返回false,从代码来看,如果retcode=404 之类的,将尝试从本地cache读取。
2) Webkit将给appcache一次机会自行处理收到的数据
注意这儿的char* 实际上就是收到的html数据。
4.
到了这儿,documentloder销毁,对应的applicationcachehost 销毁.这个文件的下载过程结束了,也没有appcache啥实质性的事情。由于是第一次运行,cache中没有,而index.html的manifest还没有得到解析,因此这个最初的第一阶段就是如此。
5.
继续执行,这个时候到了webkit解析html文件的时候,来看
会发现webkit在处理 标签的时候会特别检查下是否有manifest atrr这个属性。
像我们的程序就是有的,所以如上图,就让documentloader的appcachehost对象设置一个manifest。继续看。。
1)
Selectcachewithmanifest 执行一系列判断,比如离线是否开启,manifesturl是否合法,请求是否是http 且 http-get,清单文件所在的html文件的scheme是否和manifest文件的一致,最后在本次运行中发现manifest 文件需要从网络上下载.
2)
通过cachestorage(静态变量)的findOrCreateCacheGroup
来检查是否已经存在这样一个group了,按照我的理解html5貌似以manifest文件来区分group.即一个manifest构成了一个group.
3)
接下来将发现实际上group并没有创建,所以创建一个
group
= new ApplicationCacheGroup(manifestURL);
m_cacheHostSet.add(urlHostHash(manifestURL));
4)
设置appcachehost的candidategroup为这个group
5)
接下来将需要下载的资源数量+1
group->m_pendingMasterResourceLoaders.add(documentLoader);
group->m_downloadingPendingMasterResourceLoadersCount++;
6)
为这个manifest获取创建一个request
m_manifestHandle =
createResourceHandle(m_manifestURL, m_newestCache ?
m_newestCache->manifestResource() : 0); 这个过程创建的一个resourcehandle然后这个handler实际上将group的this指针作为ResourceHandleClient*
传了过去,所以当manifest下载完成后,会得到didfinishload的回调通知。
这个resourcehandle将networkreply和clienthandler关联的做法很多地方都有。
7)
继续运行,在获取manifest文件前,收到了
应该是表示index.html已经先于manifest文件加载。
8)
继续运行,这儿会收到一些内部事件护理,这儿暂且不表。。。
9)
继续运行,终于从web server上获取到了manifest 文件。
接下来applicationcachegroup的didreceiveresponse得到机会执行。
这个函数的逻辑是先判断是否是manifest的下载,像我们这个例子这儿确实是manifest的处理。如果不是下面有一个比较复杂的处理先不说。
10)
didReceiveManifestResponse
得到执行,大致进行了一些检查,如果retcode =404 or 410 则直接返回;= 304 直接返回; != 2xx , 或者类型不是cache-manifest 也返回; 否则创建一个resource, 由于这儿的retcode=200,所以
m_manifestResource = ApplicationCacheResource::create(m_manifestHandle->request().url(),
response,
ApplicationCacheResource::Manifest);
11) 接下来void
ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* data, int length, int)
得到机会执行,m_manifestResource->data()->append(data, length); 会填充数据
这儿为什么是append我有点搞不明白??
12)didFinishLoading 得到执行didFinishLoadingManifest
;
1)
本例子,将判定当前的操作是不是upgradetemp,因此直接解析manifest文件,解析后会得到masterentry,fallbackentry,networkentry;
我这儿还是不太理解fallback entry是干啥的?
2)
接下来将m_pendingMasterResourceLoaders 和新创建的applicationcache关联;
3)
接下来将expliciturl,fallbackurl,networkentry 加入队列
13)
startLoadingEntry 得到执行
1)
依次将所有pendingloader 执行finishload操作;
2)
cachegroup检查到pendingentry = 0,所以对传入的每个loader所在的url执行cache操作; void
ApplicationCache::addResource(PassRefPtr
3)
m_downloadingPendingMasterResourceLoadersCount--;
checkIfLoadIsComplete();
检查是否还有没有下载完成的masterresource,结果=0
4)
最后通过appcachestorage 写入数据库
到了这儿,一个最最简单的,首次加载完成了。
附1: 调试时候其实发现documentloader创建了2次,并且其中一次很快被析构了,实际上是qwebview首次初始化时候会用about:blank 进行初始化所以,这个documentloader是为它创建的,对本次调试没实际价值。
附2: 本实验中index.html 下载完成后finishedLoadingMainResource 并不将其放入cache,而是要等待manifest下载完成。
实验2 : 将manifest文件修改为
CACHE MANIFEST
#The file is only for index.html
CACHE:
index.html
#NETWORK:
#*
即增加了index.html作为manster entry in explicit mode, 而index,html本身又是直接ref manifest的文件,根据html5的草案,认为这个行为可做可不做。
但是从我对qtwebkit的debug来看,这个行为做了至少效率会降低。
如上图,在首次加载时候,index.html已经被下载了,然后解析manifest之后,又发起下载manifest,结果解析manifest时候发现又有一个master entry就是index.html结果只能再次发起请求要求得到index.html,所以抓包就如上图所示般index.html被搞了2.
实验3: 在实验2产生的数据基础上,关闭apache服务器,观察cache如何生效
1.
前期的过程和实验1比较类似到了MainResourceLoader::load ,从而触发ApplicationCacheHost::maybeLoadMainResource,注意这儿时候触发的request url是index.html
2.
ApplicationCacheGroup::cacheForMainRequest
在检查这个index.html时候发现数据库已经存在,且存在对应的groupid,因此直接执行loadcache,即在加载这个index.html的时候已经在这个cachegroup的所有资源从db中加载进来了
3.
ApplicationCacheHost::maybeLoadMainResource
继续检查是否cache中存在request,填充substitude数据结构,然后执行loadnow
4.
Loadnow检查到substitude 数据结构已经合法存在,因此不再从net上获取数据;而是从本地启动一个定时器模拟貌似已经从网上获取到了数据MainResourceLoader::handleDataLoadNow被呼叫
5.
模拟触发didReceiveResponse,这个就像从网上拿到数据之后的触发一样
6.
解析到manifest标签,不同的是,这个标签还是尝试从web上获取,但是会带上if-modified--since
7.
由于我们关闭了apache,所以didfailed被触发 cachegroup:: cacheUpdateFailed(); 被触发
8.
Cachegroup::stopload
9.
ApplicationCacheGroup::checkIfLoadIsComplete
进入failed处理
10. 到了这儿index.html也加载成功了,完成了离线部署
实验4: 在实验3的基础上将apache打开,但是不更新manifest
基本同实验3,无非是appcache文件从服务端拿到。
实验5: 解决一个问题,即index.html立刻跳转的问题.
function onLoad()
{
window.location = "";
}
加上上面的代码,将发现在实验5的基础上cachedb文件将无法生成了,这个就是我们目前面临的问题。
通过对代码的调试也基本上能够发现,void
ApplicationCacheGroup::disassociateDocumentLoader(DocumentLoader* loader) 这个函数在window.location切换的时候由于documentloader析构而导致被调用,而这个调用又导致了m_pendingMasterResourceLoaders 不一致,即到不了0.(这个时候还有下载项在处理)
基本认为有两个思路
1.
修改webkit的代码,比如pendingMasterResourceLoaders-1 在disassociateDocumentLoader的时候
2.
使用window.applicationcahce对象的几个事件,在第一个global页面内就对此进行处理,只要有更新且正在更新则要等待更新结束