summaryrefslogtreecommitdiffstats
path: root/WebCore/loader/appcache/ApplicationCacheGroup.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/loader/appcache/ApplicationCacheGroup.cpp')
-rw-r--r--WebCore/loader/appcache/ApplicationCacheGroup.cpp702
1 files changed, 413 insertions, 289 deletions
diff --git a/WebCore/loader/appcache/ApplicationCacheGroup.cpp b/WebCore/loader/appcache/ApplicationCacheGroup.cpp
index ff26767..2367e79 100644
--- a/WebCore/loader/appcache/ApplicationCacheGroup.cpp
+++ b/WebCore/loader/appcache/ApplicationCacheGroup.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -46,10 +46,11 @@ namespace WebCore {
ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL, bool isCopy)
: m_manifestURL(manifestURL)
- , m_status(Idle)
- , m_savedNewestCachePointer(0)
+ , m_updateStatus(Idle)
, m_frame(0)
, m_storageID(0)
+ , m_isObsolete(false)
+ , m_completionType(None)
, m_isCopy(isCopy)
{
}
@@ -62,7 +63,7 @@ ApplicationCacheGroup::~ApplicationCacheGroup()
ASSERT(m_caches.contains(m_newestCache.get()));
ASSERT(!m_cacheBeingUpdated);
ASSERT(m_associatedDocumentLoaders.isEmpty());
- ASSERT(m_cacheCandidates.isEmpty());
+ ASSERT(m_pendingMasterResourceLoaders.isEmpty());
ASSERT(m_newestCache->group() == this);
return;
@@ -76,18 +77,14 @@ ApplicationCacheGroup::~ApplicationCacheGroup()
cacheStorage().cacheGroupDestroyed(this);
}
-ApplicationCache* ApplicationCacheGroup::cacheForMainRequest(const ResourceRequest& request, DocumentLoader* loader)
+ApplicationCache* ApplicationCacheGroup::cacheForMainRequest(const ResourceRequest& request, DocumentLoader*)
{
if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
return 0;
-
- ASSERT(loader->frame());
- ASSERT(loader->frame()->page());
- if (loader->frame() != loader->frame()->page()->mainFrame())
- return 0;
if (ApplicationCacheGroup* group = cacheStorage().cacheGroupForURL(request.url())) {
ASSERT(group->newestCache());
+ ASSERT(!group->isObsolete());
return group->newestCache();
}
@@ -95,6 +92,21 @@ ApplicationCache* ApplicationCacheGroup::cacheForMainRequest(const ResourceReque
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());
@@ -112,28 +124,22 @@ void ApplicationCacheGroup::selectCache(Frame* frame, const KURL& manifestURL)
ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache();
- // Check if the main resource is being loaded as part of navigation of the main frame
- bool isMainFrame = frame->page()->mainFrame() == frame;
-
- if (!isMainFrame) {
- if (mainResourceCache && manifestURL != mainResourceCache->group()->manifestURL()) {
- ApplicationCacheResource* resource = mainResourceCache->resourceForURL(documentLoader->originalURL());
- ASSERT(resource);
-
- resource->addType(ApplicationCacheResource::Foreign);
- }
-
- return;
- }
-
if (mainResourceCache) {
if (manifestURL == mainResourceCache->group()->m_manifestURL) {
mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
- mainResourceCache->group()->update(frame);
+ mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext);
} else {
- // FIXME: If the resource being loaded was loaded from an application cache and the URI of
- // that application cache's manifest is not the same as the manifest URI with which the algorithm was invoked
- // then we should "undo" the navigation.
+ // The main resource was loaded from cache, so the cache must have an entry for it. Mark it as foreign.
+ ApplicationCacheResource* resource = mainResourceCache->resourceForURL(documentLoader->url());
+ bool inStorage = resource->storageID();
+ resource->addType(ApplicationCacheResource::Foreign);
+ if (inStorage)
+ cacheStorage().storeUpdatedType(resource, mainResourceCache);
+
+ // Restart the current navigation from the top of the navigation algorithm, undoing any changes that were made
+ // as part of the initial load.
+ // The navigation will not result in the same resource being loaded, because "foreign" entries are never picked during navigation.
+ frame->loader()->scheduleLocationChange(documentLoader->url(), frame->loader()->referrer(), true);
}
return;
@@ -142,53 +148,20 @@ void ApplicationCacheGroup::selectCache(Frame* frame, const KURL& manifestURL)
// The resource was loaded from the network, check if it is a HTTP/HTTPS GET.
const ResourceRequest& request = frame->loader()->activeDocumentLoader()->request();
- if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request)) {
- selectCacheWithoutManifestURL(frame);
+ if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
return;
- }
// Check that the resource URL has the same scheme/host/port as the manifest URL.
- if (!protocolHostAndPortAreEqual(manifestURL, request.url())) {
- selectCacheWithoutManifestURL(frame);
+ if (!protocolHostAndPortAreEqual(manifestURL, request.url()))
return;
- }
ApplicationCacheGroup* group = cacheStorage().findOrCreateCacheGroup(manifestURL);
-
- if (ApplicationCache* cache = group->newestCache()) {
- ASSERT(cache->manifestResource());
-
- group->associateDocumentLoaderWithCache(frame->loader()->documentLoader(), cache);
-
- if (!frame->loader()->documentLoader()->isLoadingMainResource())
- group->finishedLoadingMainResource(frame->loader()->documentLoader());
-
- group->update(frame);
- } else {
- bool isUpdating = group->m_cacheBeingUpdated;
-
- if (!isUpdating)
- group->m_cacheBeingUpdated = ApplicationCache::create();
- documentLoader->setCandidateApplicationCacheGroup(group);
- group->m_cacheCandidates.add(documentLoader);
-
- const KURL& url = frame->loader()->documentLoader()->originalURL();
-
- unsigned type = 0;
-
- // If the resource has already been downloaded, remove it so that it will be replaced with the implicit resource
- if (isUpdating)
- type = group->m_cacheBeingUpdated->removeResource(url);
-
- // Add the main resource URL as an implicit entry.
- group->addEntry(url, type | ApplicationCacheResource::Implicit);
-
- if (!frame->loader()->documentLoader()->isLoadingMainResource())
- group->finishedLoadingMainResource(frame->loader()->documentLoader());
-
- if (!isUpdating)
- group->update(frame);
- }
+
+ 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)
@@ -200,54 +173,110 @@ void ApplicationCacheGroup::selectCacheWithoutManifestURL(Frame* frame)
ASSERT(!documentLoader->applicationCache());
ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache();
- bool isMainFrame = frame->page()->mainFrame() == frame;
- if (isMainFrame && mainResourceCache) {
+ if (mainResourceCache) {
mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
- mainResourceCache->group()->update(frame);
+ 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->originalURL();
-
- if (ApplicationCache* cache = loader->applicationCache()) {
- RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Implicit, loader->mainResourceData());
- cache->addResource(resource.release());
-
- if (!m_cacheBeingUpdated)
- return;
- }
-
- ASSERT(m_pendingEntries.contains(url));
-
- EntryMap::iterator it = m_pendingEntries.find(url);
- ASSERT(it->second & ApplicationCacheResource::Implicit);
- RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, loader->response(), it->second, loader->mainResourceData());
+ switch (m_completionType) {
+ case None:
+ // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later.
+ 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:
+ // Cache update has been a failure, so there is no reason to keep the document associated with the incomplete cache
+ // (its main resource was not cached yet, so it is likely that the application changed significantly server-side).
+ ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading().
+ loader->setApplicationCache(0); // Will unset candidate, too.
+ 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()));
+ // The "cached" event will be posted to all associated documents once update is complete.
+ break;
+ }
- ASSERT(m_cacheBeingUpdated);
- m_cacheBeingUpdated->addResource(resource.release());
-
- m_pendingEntries.remove(it);
-
+ m_pendingMasterResourceLoaders.remove(loader);
checkIfLoadIsComplete();
}
void ApplicationCacheGroup::failedLoadingMainResource(DocumentLoader* loader)
{
- ASSERT(m_cacheCandidates.contains(loader) || m_associatedDocumentLoaders.contains(loader));
+ ASSERT(m_pendingMasterResourceLoaders.contains(loader));
+ ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
- // Note that cacheUpdateFailed() can cause the cache group to be deleted.
- cacheUpdateFailed();
+ switch (m_completionType) {
+ case None:
+ // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later.
+ return;
+ case NoUpdate:
+ ASSERT(!m_cacheBeingUpdated);
+
+ // The manifest didn't change, and we have a relevant cache - but the main resource download failed mid-way, so it cannot be stored to the cache,
+ // and the loader does not get associated to it. If there are other main resources being downloaded for this cache group, they may still succeed.
+ postListenerTask(&DOMApplicationCache::callErrorListener, loader);
+
+ break;
+ case Failure:
+ // Cache update failed, too.
+ ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading().
+ ASSERT(!loader->applicationCache() || loader->applicationCache() == m_cacheBeingUpdated);
+
+ loader->setApplicationCache(0); // Will unset candidate, too.
+ m_associatedDocumentLoaders.remove(loader);
+ postListenerTask(&DOMApplicationCache::callErrorListener, loader);
+ break;
+ case Completed:
+ // The cache manifest didn't list this main resource, and all cache entries were already updated successfully - but the main resource failed to load,
+ // so it cannot be stored to the cache. If there are other main resources being downloaded for this cache group, they may still succeed.
+ 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);
- ASSERT(!m_cacheBeingUpdated);
m_manifestHandle->setClient(0);
m_manifestHandle->cancel();
@@ -264,91 +293,102 @@ void ApplicationCacheGroup::stopLoading()
}
m_cacheBeingUpdated = 0;
+ m_pendingEntries.clear();
}
-void ApplicationCacheGroup::documentLoaderDestroyed(DocumentLoader* loader)
+void ApplicationCacheGroup::disassociateDocumentLoader(DocumentLoader* loader)
{
HashSet<DocumentLoader*>::iterator it = m_associatedDocumentLoaders.find(loader);
-
- if (it != m_associatedDocumentLoaders.end()) {
- ASSERT(!m_cacheCandidates.contains(loader));
-
+ if (it != m_associatedDocumentLoaders.end())
m_associatedDocumentLoaders.remove(it);
- } else {
- ASSERT(m_cacheCandidates.contains(loader));
- m_cacheCandidates.remove(loader);
- }
-
- if (!m_associatedDocumentLoaders.isEmpty() || !m_cacheCandidates.isEmpty())
+
+ m_pendingMasterResourceLoaders.remove(loader);
+
+ loader->setApplicationCache(0); // Will set candidate to 0, too.
+
+ if (!m_associatedDocumentLoaders.isEmpty() || !m_pendingMasterResourceLoaders.isEmpty())
return;
-
- // We should only have the newest cache remaining, or there is an initial cache attempt in progress.
- ASSERT(m_caches.size() == 1 || m_cacheBeingUpdated);
-
- // If a cache update is in progress, stop it.
- if (m_caches.size() == 1) {
- ASSERT(m_caches.contains(m_newestCache.get()));
-
- // Release our reference to the newest cache.
- m_savedNewestCachePointer = m_newestCache.get();
-
- // This could cause us to be deleted.
- m_newestCache = 0;
-
+
+ if (m_caches.isEmpty()) {
+ // There is an initial cache attempt in progress.
+ ASSERT(!m_newestCache);
+ // Delete ourselves, causing the cache attempt to be stopped.
+ delete this;
return;
}
-
- // There is an initial cache attempt in progress
- ASSERT(m_cacheBeingUpdated);
- ASSERT(m_caches.size() == 0);
-
- // Delete ourselves, causing the cache attempt to be stopped.
- delete this;
-}
+
+ ASSERT(m_caches.contains(m_newestCache.get()));
+
+ // Release our reference to the newest cache. This could cause us to be deleted.
+ // Any ongoing updates will be stopped from destructor.
+ m_newestCache.release();
+}
void ApplicationCacheGroup::cacheDestroyed(ApplicationCache* cache)
{
- ASSERT(m_caches.contains(cache));
+ if (!m_caches.contains(cache))
+ return;
m_caches.remove(cache);
-
- if (cache != m_savedNewestCachePointer)
- cacheStorage().remove(cache);
- if (m_caches.isEmpty())
+ if (m_caches.isEmpty()) {
+ ASSERT(m_associatedDocumentLoaders.isEmpty());
+ ASSERT(m_pendingMasterResourceLoaders.isEmpty());
delete this;
+ }
}
void ApplicationCacheGroup::setNewestCache(PassRefPtr<ApplicationCache> newestCache)
-{
- ASSERT(!m_newestCache);
+{
ASSERT(!m_caches.contains(newestCache.get()));
- ASSERT(!newestCache->group());
-
- m_newestCache = newestCache;
+
+ m_newestCache = newestCache;
+
m_caches.add(m_newestCache.get());
m_newestCache->setGroup(this);
}
-void ApplicationCacheGroup::update(Frame* frame)
+void ApplicationCacheGroup::makeObsolete()
{
- if (m_status == Checking || m_status == Downloading)
+ 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_status = Checking;
+ m_updateStatus = Checking;
- callListenersOnAssociatedDocuments(&DOMApplicationCache::callCheckingListener);
+ 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);
+
// FIXME: Handle defer loading
ResourceRequest request(m_manifestURL);
m_frame->loader()->applyUserAgent(request);
+ // FIXME: Should ask to revalidate from origin.
m_manifestHandle = ResourceHandle::create(request, this, m_frame, false, true, false);
}
@@ -361,14 +401,7 @@ void ApplicationCacheGroup::didReceiveResponse(ResourceHandle* handle, const Res
}
ASSERT(handle == m_currentHandle);
-
- int statusCode = response.httpStatusCode() / 100;
- if (statusCode == 4 || statusCode == 5) {
- // Note that cacheUpdateFailed() can cause the cache group to be deleted.
- cacheUpdateFailed();
- return;
- }
-
+
const KURL& url = handle->request().url();
ASSERT(!m_currentResource);
@@ -379,11 +412,37 @@ void ApplicationCacheGroup::didReceiveResponse(ResourceHandle* handle, const Res
// If this is an initial cache attempt, we should not get implicit resources delivered here.
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)) {
+ // Note that cacheUpdateFailed() can cause the cache group to be deleted.
+ cacheUpdateFailed();
+ } else if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) {
+ // Skip this resource. It is dropped from the cache.
+ m_currentHandle->cancel();
+ m_currentHandle = 0;
+ m_pendingEntries.remove(handle->request().url());
+ // Load the next resource, if any.
+ startLoadingEntry();
+ } else {
+ // Copy the resource and its metadata from the newest application cache in cache group whose completeness flag is complete, and act
+ // as if that was the fetched resource, ignoring the resource obtained from the network.
+ 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()));
+ // Load the next resource, if any.
+ 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 lengthReceived)
+void ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* data, int length, int)
{
if (handle == m_manifestHandle) {
didReceiveManifestData(data, length);
@@ -413,38 +472,53 @@ void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle)
m_cacheBeingUpdated->addResource(m_currentResource.release());
m_currentHandle = 0;
- // Load the next file.
- if (!m_pendingEntries.isEmpty()) {
- startLoadingEntry();
- return;
- }
-
- checkIfLoadIsComplete();
+ // Load the next resource, if any.
+ startLoadingEntry();
}
void ApplicationCacheGroup::didFail(ResourceHandle* handle, const ResourceError&)
{
if (handle == m_manifestHandle) {
- didFailToLoadManifest();
+ cacheUpdateFailed();
return;
}
-
- // Note that cacheUpdateFailed() can cause the cache group to be deleted.
- cacheUpdateFailed();
+
+ 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)) {
+ // Note that cacheUpdateFailed() can cause the cache group to be deleted.
+ cacheUpdateFailed();
+ } else {
+ // Copy the resource and its metadata from the newest application cache in cache group whose completeness flag is complete, and act
+ // as if that was the fetched resource, ignoring the resource obtained from the network.
+ 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()));
+ // Load the next resource, if any.
+ startLoadingEntry();
+ }
}
void ApplicationCacheGroup::didReceiveManifestResponse(const ResourceResponse& response)
{
- int statusCode = response.httpStatusCode() / 100;
+ ASSERT(!m_manifestResource);
+ ASSERT(m_manifestHandle);
- if (statusCode == 4 || statusCode == 5 ||
- !equalIgnoringCase(response.mimeType(), "text/cache-manifest")) {
- didFailToLoadManifest();
+ if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) {
+ manifestNotFound();
return;
}
-
- ASSERT(!m_manifestResource);
- ASSERT(m_manifestHandle);
+
+ 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);
}
@@ -458,7 +532,7 @@ void ApplicationCacheGroup::didReceiveManifestData(const char* data, int length)
void ApplicationCacheGroup::didFinishLoadingManifest()
{
if (!m_manifestResource) {
- didFailToLoadManifest();
+ cacheUpdateFailed();
return;
}
@@ -473,47 +547,41 @@ void ApplicationCacheGroup::didFinishLoadingManifest()
if (newestManifest->data()->size() == m_manifestResource->data()->size() &&
!memcmp(newestManifest->data()->data(), m_manifestResource->data()->data(), newestManifest->data()->size())) {
-
- callListenersOnAssociatedDocuments(&DOMApplicationCache::callNoUpdateListener);
-
- m_status = Idle;
- m_frame = 0;
+
+ m_completionType = NoUpdate;
m_manifestResource = 0;
+ deliverDelayedMainResources();
+
return;
}
}
Manifest manifest;
if (!parseManifest(m_manifestURL, m_manifestResource->data()->data(), m_manifestResource->data()->size(), manifest)) {
- didFailToLoadManifest();
+ cacheUpdateFailed();
return;
}
-
- // FIXME: Add the opportunistic caching namespaces and their fallbacks.
-
+
+ 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());
+
// We have the manifest, now download the resources.
- m_status = Downloading;
+ m_updateStatus = Downloading;
- callListenersOnAssociatedDocuments(&DOMApplicationCache::callDownloadingListener);
+ postListenerTask(&DOMApplicationCache::callDownloadingListener, m_associatedDocumentLoaders);
+
+ ASSERT(m_pendingEntries.isEmpty());
-#ifndef NDEBUG
- // We should only have implicit entries.
- {
- EntryMap::const_iterator end = m_pendingEntries.end();
- for (EntryMap::const_iterator it = m_pendingEntries.begin(); it != end; ++it)
- ASSERT(it->second & ApplicationCacheResource::Implicit);
- }
-#endif
-
if (isUpgradeAttempt) {
- ASSERT(!m_cacheBeingUpdated);
-
- m_cacheBeingUpdated = ApplicationCache::create();
-
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::Opportunistic | ApplicationCacheResource::Implicit | ApplicationCacheResource::Dynamic))
+ if (type & (ApplicationCacheResource::Implicit | ApplicationCacheResource::Dynamic))
addEntry(it->first, type);
}
}
@@ -521,8 +589,13 @@ void ApplicationCacheGroup::didFinishLoadingManifest()
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();
}
@@ -530,82 +603,94 @@ void ApplicationCacheGroup::didFinishLoadingManifest()
void ApplicationCacheGroup::cacheUpdateFailed()
{
stopLoading();
-
- callListenersOnAssociatedDocuments(&DOMApplicationCache::callErrorListener);
+ m_manifestResource = 0;
- m_pendingEntries.clear();
+ // Wait for master resource loads to finish.
+ 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_cacheCandidates.isEmpty()) {
- HashSet<DocumentLoader*>::iterator it = m_cacheCandidates.begin();
+ while (!m_pendingMasterResourceLoaders.isEmpty()) {
+ HashSet<DocumentLoader*>::iterator it = m_pendingMasterResourceLoaders.begin();
ASSERT((*it)->candidateApplicationCacheGroup() == this);
+ ASSERT(!(*it)->applicationCache());
(*it)->setCandidateApplicationCacheGroup(0);
- m_cacheCandidates.remove(it);
+ m_pendingMasterResourceLoaders.remove(it);
}
-
- m_status = Idle;
+
+ m_updateStatus = Idle;
m_frame = 0;
- // If there are no associated caches, delete ourselves
- if (m_associatedDocumentLoaders.isEmpty())
+ if (m_caches.isEmpty()) {
+ ASSERT(m_associatedDocumentLoaders.isEmpty());
+ ASSERT(!m_cacheBeingUpdated);
delete this;
-}
-
-
-void ApplicationCacheGroup::didFailToLoadManifest()
-{
- // Note that cacheUpdateFailed() can cause the cache group to be deleted.
- cacheUpdateFailed();
+ }
}
void ApplicationCacheGroup::checkIfLoadIsComplete()
{
- ASSERT(m_cacheBeingUpdated);
-
- if (m_manifestHandle)
+ if (m_manifestHandle || !m_pendingEntries.isEmpty() || !m_pendingMasterResourceLoaders.isEmpty())
return;
- if (!m_pendingEntries.isEmpty())
- return;
-
- // We're done
+ // We're done, all resources have finished downloading (successfully or not).
+
bool isUpgradeAttempt = m_newestCache;
-
- m_cacheBeingUpdated->setManifestResource(m_manifestResource.release());
-
- m_status = Idle;
- m_frame = 0;
-
- Vector<RefPtr<DocumentLoader> > documentLoaders;
-
- if (isUpgradeAttempt) {
- ASSERT(m_cacheCandidates.isEmpty());
-
- copyToVector(m_associatedDocumentLoaders, documentLoaders);
- } else {
- while (!m_cacheCandidates.isEmpty()) {
- HashSet<DocumentLoader*>::iterator it = m_cacheCandidates.begin();
-
- DocumentLoader* loader = *it;
- ASSERT(!loader->applicationCache());
- ASSERT(loader->candidateApplicationCacheGroup() == this);
-
- associateDocumentLoaderWithCache(loader, m_cacheBeingUpdated.get());
-
- documentLoaders.append(loader);
- m_cacheCandidates.remove(it);
+ 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: {
+ // FIXME: Fetch the resource from manifest URL again, and check whether it is identical to the one used for update (in case the application was upgraded server-side in the meanwhile). (<rdar://problem/6467625>)
+
+ 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;
}
-
- setNewestCache(m_cacheBeingUpdated.release());
-
- // Store the cache
- cacheStorage().storeNewestCache(this);
-
- callListeners(isUpgradeAttempt ? &DOMApplicationCache::callUpdateReadyListener : &DOMApplicationCache::callCachedListener,
- documentLoaders);
+ }
+
+ m_completionType = None;
+ m_updateStatus = Idle;
+ m_frame = 0;
}
void ApplicationCacheGroup::startLoadingEntry()
@@ -613,27 +698,14 @@ void ApplicationCacheGroup::startLoadingEntry()
ASSERT(m_cacheBeingUpdated);
if (m_pendingEntries.isEmpty()) {
- checkIfLoadIsComplete();
+ m_completionType = Completed;
+ deliverDelayedMainResources();
return;
}
EntryMap::const_iterator it = m_pendingEntries.begin();
- // If this is an initial cache attempt, we do not want to fetch any implicit entries,
- // since those are fed to us by the normal loader machinery.
- if (!m_newestCache) {
- // Get the first URL in the entry table that is not implicit
- EntryMap::const_iterator end = m_pendingEntries.end();
-
- while (it->second & ApplicationCacheResource::Implicit) {
- ++it;
-
- if (it == end)
- return;
- }
- }
-
- callListenersOnAssociatedDocuments(&DOMApplicationCache::callProgressListener);
+ postListenerTask(&DOMApplicationCache::callProgressListener, m_associatedDocumentLoaders);
// FIXME: If this is an upgrade attempt, the newest cache should be used as an HTTP cache.
@@ -641,24 +713,49 @@ void ApplicationCacheGroup::startLoadingEntry()
ResourceRequest request(it->first);
m_frame->loader()->applyUserAgent(request);
+ // FIXME: Should ask to revalidate from origin.
m_currentHandle = ResourceHandle::create(request, this, m_frame, false, true, false);
}
+void ApplicationCacheGroup::deliverDelayedMainResources()
+{
+ // Need to copy loaders, because the cache group may be destroyed at the end of iteration.
+ 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);
// Don't add the URL if we already have an implicit resource in the cache
+ // (i.e., the main resource finished loading before the manifest).
if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) {
ASSERT(resource->type() & ApplicationCacheResource::Implicit);
+ ASSERT(!m_frame->loader()->documentLoader()->isLoadingMainResource());
resource->addType(type);
return;
}
// Don't add the URL if it's the same as the manifest URL.
- if (m_manifestResource && m_manifestResource->url() == url) {
+ ASSERT(m_manifestResource);
+ if (m_manifestResource->url() == url) {
m_manifestResource->addType(type);
return;
}
@@ -671,33 +768,60 @@ void ApplicationCacheGroup::addEntry(const String& url, unsigned type)
void ApplicationCacheGroup::associateDocumentLoaderWithCache(DocumentLoader* loader, ApplicationCache* cache)
{
+ // If teardown started already, revive the group.
+ if (!m_newestCache && !m_cacheBeingUpdated)
+ m_newestCache = cache;
+
+ ASSERT(!m_isObsolete);
+
loader->setApplicationCache(cache);
-
+
ASSERT(!m_associatedDocumentLoaders.contains(loader));
m_associatedDocumentLoaders.add(loader);
}
-void ApplicationCacheGroup::callListenersOnAssociatedDocuments(ListenerFunction listenerFunction)
-{
- Vector<RefPtr<DocumentLoader> > loaders;
- copyToVector(m_associatedDocumentLoaders, loaders);
+class CallCacheListenerTask : public ScriptExecutionContext::Task {
+ typedef void (DOMApplicationCache::*ListenerFunction)();
+public:
+ static PassRefPtr<CallCacheListenerTask> create(ListenerFunction listenerFunction)
+ {
+ return adoptRef(new CallCacheListenerTask(listenerFunction));
+ }
- callListeners(listenerFunction, loaders);
+ 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::callListeners(ListenerFunction listenerFunction, const Vector<RefPtr<DocumentLoader> >& loaders)
+
+void ApplicationCacheGroup::postListenerTask(ListenerFunction listenerFunction, DocumentLoader* loader)
{
- for (unsigned i = 0; i < loaders.size(); i++) {
- Frame* frame = loaders[i]->frame();
- if (!frame)
- continue;
-
- ASSERT(frame->loader()->documentLoader() == loaders[i]);
- DOMWindow* window = frame->domWindow();
-
- if (DOMApplicationCache* domCache = window->optionalApplicationCache())
- (domCache->*listenerFunction)();
- }
+ Frame* frame = loader->frame();
+ if (!frame)
+ return;
+
+ ASSERT(frame->loader()->documentLoader() == loader);
+
+ frame->document()->postTask(CallCacheListenerTask::create(listenerFunction));
}
void ApplicationCacheGroup::clearStorageID()