diff options
Diffstat (limited to 'WebCore/loader/appcache/ApplicationCacheGroup.cpp')
-rw-r--r-- | WebCore/loader/appcache/ApplicationCacheGroup.cpp | 702 |
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() |