ApplicationCacheGroup.cpp [plain text]
#include "config.h"
#include "ApplicationCacheGroup.h"
#if ENABLE(OFFLINE_WEB_APPLICATIONS)
#include "ApplicationCache.h"
#include "ApplicationCacheResource.h"
#include "ApplicationCacheStorage.h"
#include "DocumentLoader.h"
#include "DOMApplicationCache.h"
#include "DOMWindow.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "MainResourceLoader.h"
#include "ManifestParser.h"
#include "Page.h"
#include "Settings.h"
#include <wtf/HashMap.h>
namespace WebCore {
const unsigned maximumCacheSize = 5 * 1024 * 1024;
ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL, bool isCopy)
: m_manifestURL(manifestURL)
, m_updateStatus(Idle)
, m_frame(0)
, m_storageID(0)
, m_isObsolete(false)
, m_completionType(None)
, m_isCopy(isCopy)
, m_loadedSize(0)
{
}
ApplicationCacheGroup::~ApplicationCacheGroup()
{
if (m_isCopy) {
ASSERT(m_newestCache);
ASSERT(m_caches.size() == 1);
ASSERT(m_caches.contains(m_newestCache.get()));
ASSERT(!m_cacheBeingUpdated);
ASSERT(m_associatedDocumentLoaders.isEmpty());
ASSERT(m_pendingMasterResourceLoaders.isEmpty());
ASSERT(m_newestCache->group() == this);
return;
}
ASSERT(!m_newestCache);
ASSERT(m_caches.isEmpty());
stopLoading();
cacheStorage().cacheGroupDestroyed(this);
}
ApplicationCache* ApplicationCacheGroup::cacheForMainRequest(const ResourceRequest& request, DocumentLoader*)
{
if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
return 0;
if (ApplicationCacheGroup* group = cacheStorage().cacheGroupForURL(request.url())) {
ASSERT(group->newestCache());
ASSERT(!group->isObsolete());
return group->newestCache();
}
return 0;
}
ApplicationCache* ApplicationCacheGroup::fallbackCacheForMainRequest(const ResourceRequest& request, DocumentLoader*)
{
if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
return 0;
if (ApplicationCacheGroup* group = cacheStorage().fallbackCacheGroupForURL(request.url())) {
ASSERT(group->newestCache());
ASSERT(!group->isObsolete());
return group->newestCache();
}
return 0;
}
void ApplicationCacheGroup::selectCache(Frame* frame, const KURL& manifestURL)
{
ASSERT(frame && frame->page());
if (!frame->settings()->offlineWebApplicationCacheEnabled())
return;
DocumentLoader* documentLoader = frame->loader()->documentLoader();
ASSERT(!documentLoader->applicationCache());
if (manifestURL.isNull()) {
selectCacheWithoutManifestURL(frame);
return;
}
ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache();
if (mainResourceCache) {
if (manifestURL == mainResourceCache->group()->m_manifestURL) {
mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext);
} else {
ApplicationCacheResource* resource = mainResourceCache->resourceForURL(documentLoader->url());
bool inStorage = resource->storageID();
resource->addType(ApplicationCacheResource::Foreign);
if (inStorage)
cacheStorage().storeUpdatedType(resource, mainResourceCache);
frame->loader()->scheduleLocationChange(documentLoader->url(), frame->loader()->referrer(), true);
}
return;
}
const ResourceRequest& request = frame->loader()->activeDocumentLoader()->request();
if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
return;
if (!protocolHostAndPortAreEqual(manifestURL, request.url()))
return;
ApplicationCacheGroup* group = cacheStorage().findOrCreateCacheGroup(manifestURL);
documentLoader->setCandidateApplicationCacheGroup(group);
group->m_pendingMasterResourceLoaders.add(documentLoader);
ASSERT(!group->m_cacheBeingUpdated || group->m_updateStatus != Idle);
group->update(frame, ApplicationCacheUpdateWithBrowsingContext);
}
void ApplicationCacheGroup::selectCacheWithoutManifestURL(Frame* frame)
{
if (!frame->settings()->offlineWebApplicationCacheEnabled())
return;
DocumentLoader* documentLoader = frame->loader()->documentLoader();
ASSERT(!documentLoader->applicationCache());
ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache();
if (mainResourceCache) {
mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext);
}
}
void ApplicationCacheGroup::finishedLoadingMainResource(DocumentLoader* loader)
{
ASSERT(m_pendingMasterResourceLoaders.contains(loader));
ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
const KURL& url = loader->url();
switch (m_completionType) {
case None:
return;
case NoUpdate:
ASSERT(!m_cacheBeingUpdated);
associateDocumentLoaderWithCache(loader, m_newestCache.get());
if (ApplicationCacheResource* resource = m_newestCache->resourceForURL(url)) {
if (!(resource->type() & ApplicationCacheResource::Implicit)) {
resource->addType(ApplicationCacheResource::Implicit);
ASSERT(!resource->storageID());
}
} else
m_newestCache->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Implicit, loader->mainResourceData()));
break;
case Failure:
ASSERT(!m_cacheBeingUpdated); loader->setApplicationCache(0); m_associatedDocumentLoaders.remove(loader);
postListenerTask(&DOMApplicationCache::callErrorListener, loader);
break;
case Completed:
ASSERT(m_associatedDocumentLoaders.contains(loader));
if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) {
if (!(resource->type() & ApplicationCacheResource::Implicit)) {
resource->addType(ApplicationCacheResource::Implicit);
ASSERT(!resource->storageID());
}
} else
m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Implicit, loader->mainResourceData()));
break;
}
m_pendingMasterResourceLoaders.remove(loader);
checkIfLoadIsComplete();
}
void ApplicationCacheGroup::failedLoadingMainResource(DocumentLoader* loader)
{
ASSERT(m_pendingMasterResourceLoaders.contains(loader));
ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
switch (m_completionType) {
case None:
return;
case NoUpdate:
ASSERT(!m_cacheBeingUpdated);
postListenerTask(&DOMApplicationCache::callErrorListener, loader);
break;
case Failure:
ASSERT(!m_cacheBeingUpdated); ASSERT(!loader->applicationCache() || loader->applicationCache() == m_cacheBeingUpdated);
loader->setApplicationCache(0); m_associatedDocumentLoaders.remove(loader);
postListenerTask(&DOMApplicationCache::callErrorListener, loader);
break;
case Completed:
ASSERT(m_associatedDocumentLoaders.contains(loader));
ASSERT(loader->applicationCache() == m_cacheBeingUpdated);
ASSERT(!loader->candidateApplicationCacheGroup());
m_associatedDocumentLoaders.remove(loader);
loader->setApplicationCache(0);
postListenerTask(&DOMApplicationCache::callErrorListener, loader);
break;
}
m_pendingMasterResourceLoaders.remove(loader);
checkIfLoadIsComplete();
}
void ApplicationCacheGroup::stopLoading()
{
if (m_manifestHandle) {
ASSERT(!m_currentHandle);
m_manifestHandle->setClient(0);
m_manifestHandle->cancel();
m_manifestHandle = 0;
}
if (m_currentHandle) {
ASSERT(!m_manifestHandle);
ASSERT(m_cacheBeingUpdated);
m_currentHandle->setClient(0);
m_currentHandle->cancel();
m_currentHandle = 0;
}
m_cacheBeingUpdated = 0;
m_pendingEntries.clear();
}
void ApplicationCacheGroup::disassociateDocumentLoader(DocumentLoader* loader)
{
HashSet<DocumentLoader*>::iterator it = m_associatedDocumentLoaders.find(loader);
if (it != m_associatedDocumentLoaders.end())
m_associatedDocumentLoaders.remove(it);
m_pendingMasterResourceLoaders.remove(loader);
loader->setApplicationCache(0);
if (!m_associatedDocumentLoaders.isEmpty() || !m_pendingMasterResourceLoaders.isEmpty())
return;
if (m_caches.isEmpty()) {
ASSERT(!m_newestCache);
delete this;
return;
}
ASSERT(m_caches.contains(m_newestCache.get()));
m_newestCache.release();
}
void ApplicationCacheGroup::cacheDestroyed(ApplicationCache* cache)
{
if (!m_caches.contains(cache))
return;
m_caches.remove(cache);
if (m_caches.isEmpty()) {
ASSERT(m_associatedDocumentLoaders.isEmpty());
ASSERT(m_pendingMasterResourceLoaders.isEmpty());
delete this;
}
}
void ApplicationCacheGroup::setNewestCache(PassRefPtr<ApplicationCache> newestCache)
{
ASSERT(!m_caches.contains(newestCache.get()));
m_newestCache = newestCache;
m_caches.add(m_newestCache.get());
m_newestCache->setGroup(this);
}
void ApplicationCacheGroup::makeObsolete()
{
if (isObsolete())
return;
m_isObsolete = true;
cacheStorage().cacheGroupMadeObsolete(this);
ASSERT(!m_storageID);
}
void ApplicationCacheGroup::update(Frame* frame, ApplicationCacheUpdateOption updateOption)
{
if (m_updateStatus == Checking || m_updateStatus == Downloading) {
if (updateOption == ApplicationCacheUpdateWithBrowsingContext) {
postListenerTask(&DOMApplicationCache::callCheckingListener, frame->loader()->documentLoader());
if (m_updateStatus == Downloading)
postListenerTask(&DOMApplicationCache::callDownloadingListener, frame->loader()->documentLoader());
}
return;
}
ASSERT(!m_frame);
m_frame = frame;
m_updateStatus = Checking;
postListenerTask(&DOMApplicationCache::callCheckingListener, m_associatedDocumentLoaders);
if (!m_newestCache) {
ASSERT(updateOption == ApplicationCacheUpdateWithBrowsingContext);
postListenerTask(&DOMApplicationCache::callCheckingListener, frame->loader()->documentLoader());
}
ASSERT(!m_manifestHandle);
ASSERT(!m_manifestResource);
ASSERT(m_completionType == None);
ResourceRequest request(m_manifestURL);
m_frame->loader()->applyUserAgent(request);
m_manifestHandle = ResourceHandle::create(request, this, m_frame, false, true, false);
}
void ApplicationCacheGroup::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response)
{
if (handle == m_manifestHandle) {
didReceiveManifestResponse(response);
return;
}
ASSERT(handle == m_currentHandle);
const KURL& url = handle->request().url();
ASSERT(!m_currentResource);
ASSERT(m_pendingEntries.contains(url));
unsigned type = m_pendingEntries.get(url);
if (!m_newestCache)
ASSERT(!(type & ApplicationCacheResource::Implicit));
if (response.httpStatusCode() / 100 != 2 || response.url() != m_currentHandle->request().url()) {
if ((type & ApplicationCacheResource::Explicit) || (type & ApplicationCacheResource::Fallback)) {
cacheUpdateFailed();
} else if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) {
m_currentHandle->cancel();
m_currentHandle = 0;
m_pendingEntries.remove(handle->request().url());
startLoadingEntry();
} else {
ASSERT(m_newestCache);
ApplicationCacheResource* resource = m_newestCache->resourceForURL(handle->request().url());
ASSERT(resource);
m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(handle->request().url(), resource->response(), resource->type(), resource->data()));
m_currentHandle->cancel();
m_currentHandle = 0;
startLoadingEntry();
}
return;
}
m_currentResource = ApplicationCacheResource::create(url, response, type);
}
void ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* data, int length, int)
{
if (handle == m_manifestHandle) {
didReceiveManifestData(data, length);
return;
}
ASSERT(handle == m_currentHandle);
ASSERT(m_currentResource);
m_currentResource->data()->append(data, length);
m_loadedSize += length;
}
void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle)
{
if (handle == m_manifestHandle) {
didFinishLoadingManifest();
return;
}
if (m_loadedSize > maximumCacheSize) {
cacheUpdateFailed();
return;
}
ASSERT(m_currentHandle == handle);
ASSERT(m_pendingEntries.contains(handle->request().url()));
m_pendingEntries.remove(handle->request().url());
ASSERT(m_cacheBeingUpdated);
m_cacheBeingUpdated->addResource(m_currentResource.release());
m_currentHandle = 0;
startLoadingEntry();
}
void ApplicationCacheGroup::didFail(ResourceHandle* handle, const ResourceError&)
{
if (handle == m_manifestHandle) {
cacheUpdateFailed();
return;
}
unsigned type = m_currentResource ? m_currentResource->type() : m_pendingEntries.get(handle->request().url());
ASSERT(!m_currentResource || !m_pendingEntries.contains(handle->request().url()));
m_currentResource = 0;
m_pendingEntries.remove(handle->request().url());
if ((type & ApplicationCacheResource::Explicit) || (type & ApplicationCacheResource::Fallback)) {
cacheUpdateFailed();
} else {
ASSERT(m_newestCache);
ApplicationCacheResource* resource = m_newestCache->resourceForURL(handle->request().url());
ASSERT(resource);
m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(handle->request().url(), resource->response(), resource->type(), resource->data()));
startLoadingEntry();
}
}
void ApplicationCacheGroup::didReceiveManifestResponse(const ResourceResponse& response)
{
ASSERT(!m_manifestResource);
ASSERT(m_manifestHandle);
if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) {
manifestNotFound();
return;
}
if (response.httpStatusCode() / 100 != 2 || response.url() != m_manifestHandle->request().url() || !equalIgnoringCase(response.mimeType(), "text/cache-manifest")) {
cacheUpdateFailed();
return;
}
m_manifestResource = ApplicationCacheResource::create(m_manifestHandle->request().url(), response,
ApplicationCacheResource::Manifest);
}
void ApplicationCacheGroup::didReceiveManifestData(const char* data, int length)
{
ASSERT(m_manifestResource);
m_manifestResource->data()->append(data, length);
}
void ApplicationCacheGroup::didFinishLoadingManifest()
{
if (!m_manifestResource) {
cacheUpdateFailed();
return;
}
bool isUpgradeAttempt = m_newestCache;
m_manifestHandle = 0;
if (isUpgradeAttempt) {
ApplicationCacheResource* newestManifest = m_newestCache->manifestResource();
ASSERT(newestManifest);
if (newestManifest->data()->size() == m_manifestResource->data()->size() &&
!memcmp(newestManifest->data()->data(), m_manifestResource->data()->data(), newestManifest->data()->size())) {
m_completionType = NoUpdate;
m_manifestResource = 0;
deliverDelayedMainResources();
return;
}
}
Manifest manifest;
if (!parseManifest(m_manifestURL, m_manifestResource->data()->data(), m_manifestResource->data()->size(), manifest)) {
cacheUpdateFailed();
return;
}
ASSERT(!m_cacheBeingUpdated);
m_cacheBeingUpdated = ApplicationCache::create();
m_cacheBeingUpdated->setGroup(this);
HashSet<DocumentLoader*>::const_iterator masterEnd = m_pendingMasterResourceLoaders.end();
for (HashSet<DocumentLoader*>::const_iterator iter = m_pendingMasterResourceLoaders.begin(); iter != masterEnd; ++iter)
associateDocumentLoaderWithCache(*iter, m_cacheBeingUpdated.get());
m_updateStatus = Downloading;
postListenerTask(&DOMApplicationCache::callDownloadingListener, m_associatedDocumentLoaders);
ASSERT(m_pendingEntries.isEmpty());
if (isUpgradeAttempt) {
ApplicationCache::ResourceMap::const_iterator end = m_newestCache->end();
for (ApplicationCache::ResourceMap::const_iterator it = m_newestCache->begin(); it != end; ++it) {
unsigned type = it->second->type();
if (type & (ApplicationCacheResource::Implicit | ApplicationCacheResource::Dynamic))
addEntry(it->first, type);
}
}
HashSet<String>::const_iterator end = manifest.explicitURLs.end();
for (HashSet<String>::const_iterator it = manifest.explicitURLs.begin(); it != end; ++it)
addEntry(*it, ApplicationCacheResource::Explicit);
size_t fallbackCount = manifest.fallbackURLs.size();
for (size_t i = 0; i < fallbackCount; ++i)
addEntry(manifest.fallbackURLs[i].second, ApplicationCacheResource::Fallback);
m_cacheBeingUpdated->setOnlineWhitelist(manifest.onlineWhitelistedURLs);
m_cacheBeingUpdated->setFallbackURLs(manifest.fallbackURLs);
startLoadingEntry();
}
void ApplicationCacheGroup::cacheUpdateFailed()
{
stopLoading();
m_manifestResource = 0;
m_completionType = Failure;
deliverDelayedMainResources();
}
void ApplicationCacheGroup::manifestNotFound()
{
makeObsolete();
postListenerTask(&DOMApplicationCache::callObsoleteListener, m_associatedDocumentLoaders);
postListenerTask(&DOMApplicationCache::callErrorListener, m_pendingMasterResourceLoaders);
stopLoading();
ASSERT(m_pendingEntries.isEmpty());
m_manifestResource = 0;
while (!m_pendingMasterResourceLoaders.isEmpty()) {
HashSet<DocumentLoader*>::iterator it = m_pendingMasterResourceLoaders.begin();
ASSERT((*it)->candidateApplicationCacheGroup() == this);
ASSERT(!(*it)->applicationCache());
(*it)->setCandidateApplicationCacheGroup(0);
m_pendingMasterResourceLoaders.remove(it);
}
m_updateStatus = Idle;
m_frame = 0;
if (m_caches.isEmpty()) {
ASSERT(m_associatedDocumentLoaders.isEmpty());
ASSERT(!m_cacheBeingUpdated);
delete this;
}
}
void ApplicationCacheGroup::checkIfLoadIsComplete()
{
if (m_manifestHandle || !m_pendingEntries.isEmpty() || !m_pendingMasterResourceLoaders.isEmpty())
return;
bool isUpgradeAttempt = m_newestCache;
switch (m_completionType) {
case None:
ASSERT_NOT_REACHED();
return;
case NoUpdate:
ASSERT(isUpgradeAttempt);
ASSERT(!m_cacheBeingUpdated);
ASSERT(m_storageID);
postListenerTask(&DOMApplicationCache::callNoUpdateListener, m_associatedDocumentLoaders);
break;
case Failure:
ASSERT(!m_cacheBeingUpdated);
postListenerTask(&DOMApplicationCache::callErrorListener, m_associatedDocumentLoaders);
if (m_caches.isEmpty()) {
ASSERT(m_associatedDocumentLoaders.isEmpty());
delete this;
return;
}
break;
case Completed: {
ASSERT(m_cacheBeingUpdated);
m_cacheBeingUpdated->setManifestResource(m_manifestResource.release());
RefPtr<ApplicationCache> oldNewestCache = (m_newestCache == m_cacheBeingUpdated) ? 0 : m_newestCache;
setNewestCache(m_cacheBeingUpdated.release());
cacheStorage().storeNewestCache(this);
if (oldNewestCache)
cacheStorage().remove(oldNewestCache.get());
postListenerTask(isUpgradeAttempt ? &DOMApplicationCache::callUpdateReadyListener : &DOMApplicationCache::callCachedListener, m_associatedDocumentLoaders);
break;
}
}
m_completionType = None;
m_updateStatus = Idle;
m_frame = 0;
m_loadedSize = 0;
}
void ApplicationCacheGroup::startLoadingEntry()
{
ASSERT(m_cacheBeingUpdated);
if (m_pendingEntries.isEmpty()) {
m_completionType = Completed;
deliverDelayedMainResources();
return;
}
EntryMap::const_iterator it = m_pendingEntries.begin();
postListenerTask(&DOMApplicationCache::callProgressListener, m_associatedDocumentLoaders);
ASSERT(!m_currentHandle);
ResourceRequest request(it->first);
m_frame->loader()->applyUserAgent(request);
m_currentHandle = ResourceHandle::create(request, this, m_frame, false, true, false);
}
void ApplicationCacheGroup::deliverDelayedMainResources()
{
Vector<DocumentLoader*> loaders;
copyToVector(m_pendingMasterResourceLoaders, loaders);
size_t count = loaders.size();
for (size_t i = 0; i != count; ++i) {
DocumentLoader* loader = loaders[i];
if (loader->isLoadingMainResource())
continue;
const ResourceError& error = loader->mainDocumentError();
if (error.isNull())
finishedLoadingMainResource(loader);
else
failedLoadingMainResource(loader);
}
if (!count)
checkIfLoadIsComplete();
}
void ApplicationCacheGroup::addEntry(const String& url, unsigned type)
{
ASSERT(m_cacheBeingUpdated);
if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) {
ASSERT(resource->type() & ApplicationCacheResource::Implicit);
ASSERT(!m_frame->loader()->documentLoader()->isLoadingMainResource());
resource->addType(type);
return;
}
ASSERT(m_manifestResource);
if (m_manifestResource->url() == url) {
m_manifestResource->addType(type);
return;
}
pair<EntryMap::iterator, bool> result = m_pendingEntries.add(url, type);
if (!result.second)
result.first->second |= type;
}
void ApplicationCacheGroup::associateDocumentLoaderWithCache(DocumentLoader* loader, ApplicationCache* cache)
{
if (!m_newestCache && !m_cacheBeingUpdated)
m_newestCache = cache;
ASSERT(!m_isObsolete);
loader->setApplicationCache(cache);
ASSERT(!m_associatedDocumentLoaders.contains(loader));
m_associatedDocumentLoaders.add(loader);
}
class CallCacheListenerTask : public ScriptExecutionContext::Task {
typedef void (DOMApplicationCache::*ListenerFunction)();
public:
static PassRefPtr<CallCacheListenerTask> create(ListenerFunction listenerFunction)
{
return adoptRef(new CallCacheListenerTask(listenerFunction));
}
virtual void performTask(ScriptExecutionContext* context)
{
ASSERT(context->isDocument());
if (DOMWindow* window = static_cast<Document*>(context)->domWindow()) {
if (DOMApplicationCache* domCache = window->optionalApplicationCache())
(domCache->*m_listenerFunction)();
}
}
private:
CallCacheListenerTask(ListenerFunction listenerFunction)
: m_listenerFunction(listenerFunction)
{
}
ListenerFunction m_listenerFunction;
};
void ApplicationCacheGroup::postListenerTask(ListenerFunction listenerFunction, const HashSet<DocumentLoader*>& loaderSet)
{
HashSet<DocumentLoader*>::const_iterator loaderSetEnd = loaderSet.end();
for (HashSet<DocumentLoader*>::const_iterator iter = loaderSet.begin(); iter != loaderSetEnd; ++iter)
postListenerTask(listenerFunction, *iter);
}
void ApplicationCacheGroup::postListenerTask(ListenerFunction listenerFunction, DocumentLoader* loader)
{
Frame* frame = loader->frame();
if (!frame)
return;
ASSERT(frame->loader()->documentLoader() == loader);
frame->document()->postTask(CallCacheListenerTask::create(listenerFunction));
}
void ApplicationCacheGroup::clearStorageID()
{
m_storageID = 0;
HashSet<ApplicationCache*>::const_iterator end = m_caches.end();
for (HashSet<ApplicationCache*>::const_iterator it = m_caches.begin(); it != end; ++it)
(*it)->clearStorageID();
}
}
#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)