--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/loader/appcache/ApplicationCacheGroup.cpp Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,1111 @@
+/*
+ * Copyright (C) 2008, 2009, 2010 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "ApplicationCacheGroup.h"
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "ApplicationCache.h"
+#include "ApplicationCacheHost.h"
+#include "ApplicationCacheResource.h"
+#include "ApplicationCacheStorage.h"
+#include "Chrome.h"
+#include "ChromeClient.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>
+
+#if ENABLE(INSPECTOR)
+#include "InspectorApplicationCacheAgent.h"
+#include "InspectorController.h"
+#include "ProgressTracker.h"
+#else
+#include <wtf/UnusedParam.h>
+#endif
+
+namespace WebCore {
+
+ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL, bool isCopy)
+ : m_manifestURL(manifestURL)
+ , m_updateStatus(Idle)
+ , m_downloadingPendingMasterResourceLoadersCount(0)
+ , m_progressTotal(0)
+ , m_progressDone(0)
+ , m_frame(0)
+ , m_storageID(0)
+ , m_isObsolete(false)
+ , m_completionType(None)
+ , m_isCopy(isCopy)
+ , m_calledReachedMaxAppCacheSize(false)
+{
+}
+
+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;
+
+ KURL url(request.url());
+ if (url.hasFragmentIdentifier())
+ url.removeFragmentIdentifier();
+
+ if (ApplicationCacheGroup* group = cacheStorage().cacheGroupForURL(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;
+
+ KURL url(request.url());
+ if (url.hasFragmentIdentifier())
+ url.removeFragmentIdentifier();
+
+ if (ApplicationCacheGroup* group = cacheStorage().fallbackCacheGroupForURL(url)) {
+ ASSERT(group->newestCache());
+ ASSERT(!group->isObsolete());
+
+ return group->newestCache();
+ }
+
+ return 0;
+}
+
+void ApplicationCacheGroup::selectCache(Frame* frame, const KURL& passedManifestURL)
+{
+ ASSERT(frame && frame->page());
+
+ if (!frame->settings()->offlineWebApplicationCacheEnabled())
+ return;
+
+ DocumentLoader* documentLoader = frame->loader()->documentLoader();
+ ASSERT(!documentLoader->applicationCacheHost()->applicationCache());
+
+ if (passedManifestURL.isNull()) {
+ selectCacheWithoutManifestURL(frame);
+ return;
+ }
+
+ KURL manifestURL(passedManifestURL);
+ if (manifestURL.hasFragmentIdentifier())
+ manifestURL.removeFragmentIdentifier();
+
+ ApplicationCache* mainResourceCache = documentLoader->applicationCacheHost()->mainResourceApplicationCache();
+
+ if (mainResourceCache) {
+ if (manifestURL == mainResourceCache->group()->m_manifestURL) {
+ mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
+ mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext);
+ } else {
+ // The main resource was loaded from cache, so the cache must have an entry for it. Mark it as foreign.
+ KURL documentURL(documentLoader->url());
+ if (documentURL.hasFragmentIdentifier())
+ documentURL.removeFragmentIdentifier();
+ ApplicationCacheResource* resource = mainResourceCache->resourceForURL(documentURL);
+ 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->redirectScheduler()->scheduleLocationChange(documentLoader->url(), frame->loader()->referrer(), true);
+ }
+
+ return;
+ }
+
+ // 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))
+ return;
+
+ // Check that the resource URL has the same scheme/host/port as the manifest URL.
+ if (!protocolHostAndPortAreEqual(manifestURL, request.url()))
+ return;
+
+ // Don't change anything on disk if private browsing is enabled.
+ if (!frame->settings() || frame->settings()->privateBrowsingEnabled()) {
+ postListenerTask(ApplicationCacheHost::CHECKING_EVENT, documentLoader);
+ postListenerTask(ApplicationCacheHost::ERROR_EVENT, documentLoader);
+ return;
+ }
+
+ ApplicationCacheGroup* group = cacheStorage().findOrCreateCacheGroup(manifestURL);
+
+ documentLoader->applicationCacheHost()->setCandidateApplicationCacheGroup(group);
+ group->m_pendingMasterResourceLoaders.add(documentLoader);
+ group->m_downloadingPendingMasterResourceLoadersCount++;
+
+ 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->applicationCacheHost()->applicationCache());
+
+ ApplicationCache* mainResourceCache = documentLoader->applicationCacheHost()->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());
+ KURL url = loader->url();
+ if (url.hasFragmentIdentifier())
+ url.removeFragmentIdentifier();
+
+ 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::Master)) {
+ resource->addType(ApplicationCacheResource::Master);
+ ASSERT(!resource->storageID());
+ }
+ } else
+ m_newestCache->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Master, 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->applicationCacheHost()->setApplicationCache(0); // Will unset candidate, too.
+ m_associatedDocumentLoaders.remove(loader);
+ postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
+ break;
+ case Completed:
+ ASSERT(m_associatedDocumentLoaders.contains(loader));
+
+ if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) {
+ if (!(resource->type() & ApplicationCacheResource::Master)) {
+ resource->addType(ApplicationCacheResource::Master);
+ ASSERT(!resource->storageID());
+ }
+ } else
+ m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Master, loader->mainResourceData()));
+ // The "cached" event will be posted to all associated documents once update is complete.
+ break;
+ }
+
+ m_downloadingPendingMasterResourceLoadersCount--;
+ checkIfLoadIsComplete();
+}
+
+void ApplicationCacheGroup::failedLoadingMainResource(DocumentLoader* loader)
+{
+ ASSERT(m_pendingMasterResourceLoaders.contains(loader));
+ ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
+
+ 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(ApplicationCacheHost::ERROR_EVENT, loader);
+
+ break;
+ case Failure:
+ // Cache update failed, too.
+ ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading().
+ ASSERT(!loader->applicationCacheHost()->applicationCache() || loader->applicationCacheHost()->applicationCache() == m_cacheBeingUpdated);
+
+ loader->applicationCacheHost()->setApplicationCache(0); // Will unset candidate, too.
+ m_associatedDocumentLoaders.remove(loader);
+ postListenerTask(ApplicationCacheHost::ERROR_EVENT, 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->applicationCacheHost()->applicationCache() == m_cacheBeingUpdated);
+ ASSERT(!loader->applicationCacheHost()->candidateApplicationCacheGroup());
+ m_associatedDocumentLoaders.remove(loader);
+ loader->applicationCacheHost()->setApplicationCache(0);
+
+ postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
+
+ break;
+ }
+
+ m_downloadingPendingMasterResourceLoadersCount--;
+ 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->applicationCacheHost()->setApplicationCache(0); // Will set candidate to 0, too.
+
+ if (!m_associatedDocumentLoaders.isEmpty() || !m_pendingMasterResourceLoaders.isEmpty())
+ return;
+
+ 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;
+ }
+
+ 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)
+{
+ if (!m_caches.contains(cache))
+ return;
+
+ m_caches.remove(cache);
+
+ if (m_caches.isEmpty()) {
+ ASSERT(m_associatedDocumentLoaders.isEmpty());
+ ASSERT(m_pendingMasterResourceLoaders.isEmpty());
+ delete this;
+ }
+}
+
+#if ENABLE(INSPECTOR)
+static void inspectorUpdateApplicationCacheStatus(Frame* frame)
+{
+ if (!frame)
+ return;
+
+ if (Page *page = frame->page()) {
+ if (InspectorApplicationCacheAgent* applicationCacheAgent = page->inspectorController()->applicationCacheAgent()) {
+ ApplicationCacheHost::Status status = frame->loader()->documentLoader()->applicationCacheHost()->status();
+ applicationCacheAgent->updateApplicationCacheStatus(status);
+ }
+ }
+}
+#endif
+
+void ApplicationCacheGroup::setNewestCache(PassRefPtr<ApplicationCache> newestCache)
+{
+ m_newestCache = newestCache;
+
+ m_caches.add(m_newestCache.get());
+ m_newestCache->setGroup(this);
+#if ENABLE(INSPECTOR)
+ inspectorUpdateApplicationCacheStatus(m_frame);
+#endif
+}
+
+void ApplicationCacheGroup::makeObsolete()
+{
+ if (isObsolete())
+ return;
+
+ m_isObsolete = true;
+ cacheStorage().cacheGroupMadeObsolete(this);
+ ASSERT(!m_storageID);
+#if ENABLE(INSPECTOR)
+ inspectorUpdateApplicationCacheStatus(m_frame);
+#endif
+}
+
+void ApplicationCacheGroup::update(Frame* frame, ApplicationCacheUpdateOption updateOption)
+{
+ if (m_updateStatus == Checking || m_updateStatus == Downloading) {
+ if (updateOption == ApplicationCacheUpdateWithBrowsingContext) {
+ postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader());
+ if (m_updateStatus == Downloading)
+ postListenerTask(ApplicationCacheHost::DOWNLOADING_EVENT, frame->loader()->documentLoader());
+ }
+ return;
+ }
+
+ // Don't change anything on disk if private browsing is enabled.
+ if (!frame->settings() || frame->settings()->privateBrowsingEnabled()) {
+ ASSERT(m_pendingMasterResourceLoaders.isEmpty());
+ ASSERT(m_pendingEntries.isEmpty());
+ ASSERT(!m_cacheBeingUpdated);
+ postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader());
+ postListenerTask(ApplicationCacheHost::NOUPDATE_EVENT, frame->loader()->documentLoader());
+ return;
+ }
+
+ ASSERT(!m_frame);
+ m_frame = frame;
+
+ setUpdateStatus(Checking);
+
+ postListenerTask(ApplicationCacheHost::CHECKING_EVENT, m_associatedDocumentLoaders);
+ if (!m_newestCache) {
+ ASSERT(updateOption == ApplicationCacheUpdateWithBrowsingContext);
+ postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader());
+ }
+
+ ASSERT(!m_manifestHandle);
+ ASSERT(!m_manifestResource);
+ ASSERT(m_completionType == None);
+
+ // FIXME: Handle defer loading
+ m_manifestHandle = createResourceHandle(m_manifestURL, m_newestCache ? m_newestCache->manifestResource() : 0);
+}
+
+PassRefPtr<ResourceHandle> ApplicationCacheGroup::createResourceHandle(const KURL& url, ApplicationCacheResource* newestCachedResource)
+{
+ ResourceRequest request(url);
+ m_frame->loader()->applyUserAgent(request);
+ request.setHTTPHeaderField("Cache-Control", "max-age=0");
+
+ if (newestCachedResource) {
+ const String& lastModified = newestCachedResource->response().httpHeaderField("Last-Modified");
+ const String& eTag = newestCachedResource->response().httpHeaderField("ETag");
+ if (!lastModified.isEmpty() || !eTag.isEmpty()) {
+ if (!lastModified.isEmpty())
+ request.setHTTPHeaderField("If-Modified-Since", lastModified);
+ if (!eTag.isEmpty())
+ request.setHTTPHeaderField("If-None-Match", eTag);
+ }
+ }
+
+ RefPtr<ResourceHandle> handle = ResourceHandle::create(request, this, m_frame, false, true);
+#if ENABLE(INSPECTOR)
+ // Because willSendRequest only gets called during redirects, we initialize
+ // the identifier and the first willSendRequest here.
+ m_currentResourceIdentifier = m_frame->page()->progress()->createUniqueIdentifier();
+ if (Page* page = m_frame->page()) {
+ InspectorController* inspectorController = page->inspectorController();
+ inspectorController->identifierForInitialRequest(m_currentResourceIdentifier, m_frame->loader()->documentLoader(), handle->firstRequest());
+ ResourceResponse redirectResponse = ResourceResponse();
+ inspectorController->willSendRequest(m_currentResourceIdentifier, request, redirectResponse);
+ }
+#endif
+ return handle;
+}
+
+#if ENABLE(INSPECTOR)
+void ApplicationCacheGroup::willSendRequest(ResourceHandle*, ResourceRequest& request, const ResourceResponse& redirectResponse)
+{
+ // This only gets called by ResourceHandleMac if there is a redirect.
+ if (Page* page = m_frame->page())
+ page->inspectorController()->willSendRequest(m_currentResourceIdentifier, request, redirectResponse);
+}
+#endif
+
+void ApplicationCacheGroup::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response)
+{
+#if ENABLE(INSPECTOR)
+ if (Page* page = m_frame->page()) {
+ if (handle == m_manifestHandle) {
+ if (InspectorApplicationCacheAgent* applicationCacheAgent = page->inspectorController()->applicationCacheAgent())
+ applicationCacheAgent->didReceiveManifestResponse(m_currentResourceIdentifier, response);
+ } else
+ page->inspectorController()->didReceiveResponse(m_currentResourceIdentifier, response);
+ }
+#endif
+
+ if (handle == m_manifestHandle) {
+ didReceiveManifestResponse(response);
+ return;
+ }
+
+ ASSERT(handle == m_currentHandle);
+
+ KURL url(handle->firstRequest().url());
+ if (url.hasFragmentIdentifier())
+ url.removeFragmentIdentifier();
+
+ ASSERT(!m_currentResource);
+ ASSERT(m_pendingEntries.contains(url));
+
+ unsigned type = m_pendingEntries.get(url);
+
+ // If this is an initial cache attempt, we should not get master resources delivered here.
+ if (!m_newestCache)
+ ASSERT(!(type & ApplicationCacheResource::Master));
+
+ if (m_newestCache && response.httpStatusCode() == 304) { // Not modified.
+ ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(url);
+ if (newestCachedResource) {
+ m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data()));
+ m_pendingEntries.remove(m_currentHandle->firstRequest().url());
+ m_currentHandle->cancel();
+ m_currentHandle = 0;
+ // Load the next resource, if any.
+ startLoadingEntry();
+ return;
+ }
+ // The server could return 304 for an unconditional request - in this case, we handle the response as a normal error.
+ }
+
+ if (response.httpStatusCode() / 100 != 2 || response.url() != m_currentHandle->firstRequest().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(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* newestCachedResource = m_newestCache->resourceForURL(handle->firstRequest().url());
+ ASSERT(newestCachedResource);
+ m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data()));
+ m_pendingEntries.remove(m_currentHandle->firstRequest().url());
+ m_currentHandle->cancel();
+ m_currentHandle = 0;
+ // Load the next resource, if any.
+ startLoadingEntry();
+ }
+ return;
+ }
+
+ m_currentResource = ApplicationCacheResource::create(url, response, type);
+}
+
+void ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* data, int length, int lengthReceived)
+{
+#if ENABLE(INSPECTOR)
+ if (Page* page = m_frame->page())
+ page->inspectorController()->didReceiveContentLength(m_currentResourceIdentifier, lengthReceived);
+#else
+ UNUSED_PARAM(lengthReceived);
+#endif
+
+ if (handle == m_manifestHandle) {
+ didReceiveManifestData(data, length);
+ return;
+ }
+
+ ASSERT(handle == m_currentHandle);
+
+ ASSERT(m_currentResource);
+ m_currentResource->data()->append(data, length);
+}
+
+void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle)
+{
+#if ENABLE(INSPECTOR)
+ if (Page* page = m_frame->page())
+ page->inspectorController()->didFinishLoading(m_currentResourceIdentifier);
+#endif
+
+ if (handle == m_manifestHandle) {
+ didFinishLoadingManifest();
+ return;
+ }
+
+ ASSERT(m_currentHandle == handle);
+ ASSERT(m_pendingEntries.contains(handle->firstRequest().url()));
+
+ m_pendingEntries.remove(handle->firstRequest().url());
+
+ ASSERT(m_cacheBeingUpdated);
+
+ m_cacheBeingUpdated->addResource(m_currentResource.release());
+ m_currentHandle = 0;
+
+ // Load the next resource, if any.
+ startLoadingEntry();
+}
+
+void ApplicationCacheGroup::didFail(ResourceHandle* handle, const ResourceError& error)
+{
+#if ENABLE(INSPECTOR)
+ if (Page* page = m_frame->page())
+ page->inspectorController()->didFailLoading(m_currentResourceIdentifier, error);
+#else
+ UNUSED_PARAM(error);
+#endif
+
+ if (handle == m_manifestHandle) {
+ cacheUpdateFailed();
+ return;
+ }
+
+ unsigned type = m_currentResource ? m_currentResource->type() : m_pendingEntries.get(handle->firstRequest().url());
+ KURL url(handle->firstRequest().url());
+ if (url.hasFragmentIdentifier())
+ url.removeFragmentIdentifier();
+
+ ASSERT(!m_currentResource || !m_pendingEntries.contains(url));
+ m_currentResource = 0;
+ m_pendingEntries.remove(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* newestCachedResource = m_newestCache->resourceForURL(url);
+ ASSERT(newestCachedResource);
+ m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data()));
+ // Load the next resource, if any.
+ 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() == 304)
+ return;
+
+ if (response.httpStatusCode() / 100 != 2 || response.url() != m_manifestHandle->firstRequest().url() || !equalIgnoringCase(response.mimeType(), "text/cache-manifest")) {
+ cacheUpdateFailed();
+ return;
+ }
+
+ m_manifestResource = ApplicationCacheResource::create(m_manifestHandle->firstRequest().url(), response, ApplicationCacheResource::Manifest);
+}
+
+void ApplicationCacheGroup::didReceiveManifestData(const char* data, int length)
+{
+ if (m_manifestResource)
+ m_manifestResource->data()->append(data, length);
+}
+
+void ApplicationCacheGroup::didFinishLoadingManifest()
+{
+ bool isUpgradeAttempt = m_newestCache;
+
+ if (!isUpgradeAttempt && !m_manifestResource) {
+ // The server returned 304 Not Modified even though we didn't send a conditional request.
+ cacheUpdateFailed();
+ return;
+ }
+
+ m_manifestHandle = 0;
+
+ // Check if the manifest was not modified.
+ if (isUpgradeAttempt) {
+ ApplicationCacheResource* newestManifest = m_newestCache->manifestResource();
+ ASSERT(newestManifest);
+
+ if (!m_manifestResource || // The resource will be null if HTTP response was 304 Not Modified.
+ (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());
+
+ // We have the manifest, now download the resources.
+ setUpdateStatus(Downloading);
+
+ postListenerTask(ApplicationCacheHost::DOWNLOADING_EVENT, 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::Master)
+ 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);
+ m_cacheBeingUpdated->setAllowsAllNetworkRequests(manifest.allowAllNetworkRequests);
+
+ m_progressTotal = m_pendingEntries.size();
+ m_progressDone = 0;
+
+ startLoadingEntry();
+}
+
+void ApplicationCacheGroup::didReachMaxAppCacheSize()
+{
+ ASSERT(m_frame);
+ ASSERT(m_cacheBeingUpdated);
+ m_frame->page()->chrome()->client()->reachedMaxAppCacheSize(cacheStorage().spaceNeeded(m_cacheBeingUpdated->estimatedSizeInStorage()));
+ m_calledReachedMaxAppCacheSize = true;
+ checkIfLoadIsComplete();
+}
+
+void ApplicationCacheGroup::cacheUpdateFailed()
+{
+ stopLoading();
+ m_manifestResource = 0;
+
+ // Wait for master resource loads to finish.
+ m_completionType = Failure;
+ deliverDelayedMainResources();
+}
+
+void ApplicationCacheGroup::manifestNotFound()
+{
+ makeObsolete();
+
+ postListenerTask(ApplicationCacheHost::OBSOLETE_EVENT, m_associatedDocumentLoaders);
+ postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_pendingMasterResourceLoaders);
+
+ stopLoading();
+
+ ASSERT(m_pendingEntries.isEmpty());
+ m_manifestResource = 0;
+
+ while (!m_pendingMasterResourceLoaders.isEmpty()) {
+ HashSet<DocumentLoader*>::iterator it = m_pendingMasterResourceLoaders.begin();
+
+ ASSERT((*it)->applicationCacheHost()->candidateApplicationCacheGroup() == this);
+ ASSERT(!(*it)->applicationCacheHost()->applicationCache());
+ (*it)->applicationCacheHost()->setCandidateApplicationCacheGroup(0);
+ m_pendingMasterResourceLoaders.remove(it);
+ }
+
+ m_downloadingPendingMasterResourceLoadersCount = 0;
+ setUpdateStatus(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_downloadingPendingMasterResourceLoadersCount)
+ return;
+
+ // We're done, all resources have finished downloading (successfully or not).
+
+ bool isUpgradeAttempt = m_newestCache;
+
+ switch (m_completionType) {
+ case None:
+ ASSERT_NOT_REACHED();
+ return;
+ case NoUpdate:
+ ASSERT(isUpgradeAttempt);
+ ASSERT(!m_cacheBeingUpdated);
+
+ // The storage could have been manually emptied by the user.
+ if (!m_storageID)
+ cacheStorage().storeNewestCache(this);
+
+ postListenerTask(ApplicationCacheHost::NOUPDATE_EVENT, m_associatedDocumentLoaders);
+ break;
+ case Failure:
+ ASSERT(!m_cacheBeingUpdated);
+ postListenerTask(ApplicationCacheHost::ERROR_EVENT, 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);
+ if (m_manifestResource)
+ m_cacheBeingUpdated->setManifestResource(m_manifestResource.release());
+ else {
+ // We can get here as a result of retrying the Complete step, following
+ // a failure of the cache storage to save the newest cache due to hitting
+ // the maximum size. In such a case, m_manifestResource may be 0, as
+ // the manifest was already set on the newest cache object.
+ ASSERT(cacheStorage().isMaximumSizeReached() && m_calledReachedMaxAppCacheSize);
+ }
+
+ RefPtr<ApplicationCache> oldNewestCache = (m_newestCache == m_cacheBeingUpdated) ? 0 : m_newestCache;
+
+ setNewestCache(m_cacheBeingUpdated.release());
+ if (cacheStorage().storeNewestCache(this)) {
+ // New cache stored, now remove the old cache.
+ if (oldNewestCache)
+ cacheStorage().remove(oldNewestCache.get());
+
+ // Fire the final progress event.
+ ASSERT(m_progressDone == m_progressTotal);
+ postListenerTask(ApplicationCacheHost::PROGRESS_EVENT, m_progressTotal, m_progressDone, m_associatedDocumentLoaders);
+
+ // Fire the success event.
+ postListenerTask(isUpgradeAttempt ? ApplicationCacheHost::UPDATEREADY_EVENT : ApplicationCacheHost::CACHED_EVENT, m_associatedDocumentLoaders);
+ } else {
+ if (cacheStorage().isMaximumSizeReached() && !m_calledReachedMaxAppCacheSize) {
+ // We ran out of space. All the changes in the cache storage have
+ // been rolled back. We roll back to the previous state in here,
+ // as well, call the chrome client asynchronously and retry to
+ // save the new cache.
+
+ // Save a reference to the new cache.
+ m_cacheBeingUpdated = m_newestCache.release();
+ if (oldNewestCache) {
+ // Reinstate the oldNewestCache.
+ setNewestCache(oldNewestCache.release());
+ }
+ scheduleReachedMaxAppCacheSizeCallback();
+ return;
+ } else {
+ // Run the "cache failure steps"
+ // Fire the error events to all pending master entries, as well any other cache hosts
+ // currently associated with a cache in this group.
+ postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_associatedDocumentLoaders);
+ // Disassociate the pending master entries from the failed new cache. Note that
+ // all other loaders in the m_associatedDocumentLoaders are still associated with
+ // some other cache in this group. They are not associated with the failed new cache.
+
+ // 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)
+ disassociateDocumentLoader(loaders[i]); // This can delete this group.
+
+ // Reinstate the oldNewestCache, if there was one.
+ if (oldNewestCache) {
+ // This will discard the failed new cache.
+ setNewestCache(oldNewestCache.release());
+ } else {
+ // We must have been deleted by the last call to disassociateDocumentLoader().
+ return;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ // Empty cache group's list of pending master entries.
+ m_pendingMasterResourceLoaders.clear();
+ m_completionType = None;
+ setUpdateStatus(Idle);
+ m_frame = 0;
+ m_calledReachedMaxAppCacheSize = false;
+}
+
+void ApplicationCacheGroup::startLoadingEntry()
+{
+ ASSERT(m_cacheBeingUpdated);
+
+ if (m_pendingEntries.isEmpty()) {
+ m_completionType = Completed;
+ deliverDelayedMainResources();
+ return;
+ }
+
+ EntryMap::const_iterator it = m_pendingEntries.begin();
+
+ postListenerTask(ApplicationCacheHost::PROGRESS_EVENT, m_progressTotal, m_progressDone, m_associatedDocumentLoaders);
+ m_progressDone++;
+
+ ASSERT(!m_currentHandle);
+
+ m_currentHandle = createResourceHandle(KURL(ParsedURLString, it->first), m_newestCache ? m_newestCache->resourceForURL(it->first) : 0);
+}
+
+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);
+ ASSERT(!KURL(ParsedURLString, url).hasFragmentIdentifier());
+
+ // Don't add the URL if we already have an master 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::Master);
+ ASSERT(!m_frame->loader()->documentLoader()->isLoadingMainResource());
+
+ resource->addType(type);
+ return;
+ }
+
+ // Don't add the URL if it's the same as the manifest URL.
+ 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 teardown started already, revive the group.
+ if (!m_newestCache && !m_cacheBeingUpdated)
+ m_newestCache = cache;
+
+ ASSERT(!m_isObsolete);
+
+ loader->applicationCacheHost()->setApplicationCache(cache);
+
+ ASSERT(!m_associatedDocumentLoaders.contains(loader));
+ m_associatedDocumentLoaders.add(loader);
+}
+
+class ChromeClientCallbackTimer: public TimerBase {
+public:
+ ChromeClientCallbackTimer(ApplicationCacheGroup* cacheGroup)
+ : m_cacheGroup(cacheGroup)
+ {
+ }
+
+private:
+ virtual void fired()
+ {
+ m_cacheGroup->didReachMaxAppCacheSize();
+ delete this;
+ }
+ // Note that there is no need to use a RefPtr here. The ApplicationCacheGroup instance is guaranteed
+ // to be alive when the timer fires since invoking the ChromeClient callback is part of its normal
+ // update machinery and nothing can yet cause it to get deleted.
+ ApplicationCacheGroup* m_cacheGroup;
+};
+
+void ApplicationCacheGroup::scheduleReachedMaxAppCacheSizeCallback()
+{
+ ASSERT(isMainThread());
+ ChromeClientCallbackTimer* timer = new ChromeClientCallbackTimer(this);
+ timer->startOneShot(0);
+ // The timer will delete itself once it fires.
+}
+
+class CallCacheListenerTask : public ScriptExecutionContext::Task {
+public:
+ static PassOwnPtr<CallCacheListenerTask> create(PassRefPtr<DocumentLoader> loader, ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone)
+ {
+ return adoptPtr(new CallCacheListenerTask(loader, eventID, progressTotal, progressDone));
+ }
+
+ virtual void performTask(ScriptExecutionContext* context)
+ {
+
+ ASSERT_UNUSED(context, context->isDocument());
+ Frame* frame = m_documentLoader->frame();
+ if (!frame)
+ return;
+
+ ASSERT(frame->loader()->documentLoader() == m_documentLoader.get());
+
+ m_documentLoader->applicationCacheHost()->notifyDOMApplicationCache(m_eventID, m_progressTotal, m_progressDone);
+ }
+
+private:
+ CallCacheListenerTask(PassRefPtr<DocumentLoader> loader, ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone)
+ : m_documentLoader(loader)
+ , m_eventID(eventID)
+ , m_progressTotal(progressTotal)
+ , m_progressDone(progressDone)
+ {
+ }
+
+ RefPtr<DocumentLoader> m_documentLoader;
+ ApplicationCacheHost::EventID m_eventID;
+ int m_progressTotal;
+ int m_progressDone;
+};
+
+void ApplicationCacheGroup::postListenerTask(ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone, const HashSet<DocumentLoader*>& loaderSet)
+{
+ HashSet<DocumentLoader*>::const_iterator loaderSetEnd = loaderSet.end();
+ for (HashSet<DocumentLoader*>::const_iterator iter = loaderSet.begin(); iter != loaderSetEnd; ++iter)
+ postListenerTask(eventID, progressTotal, progressDone, *iter);
+}
+
+void ApplicationCacheGroup::postListenerTask(ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone, DocumentLoader* loader)
+{
+ Frame* frame = loader->frame();
+ if (!frame)
+ return;
+
+ ASSERT(frame->loader()->documentLoader() == loader);
+
+ frame->document()->postTask(CallCacheListenerTask::create(loader, eventID, progressTotal, progressDone));
+}
+
+void ApplicationCacheGroup::setUpdateStatus(UpdateStatus status)
+{
+ m_updateStatus = status;
+#if ENABLE(INSPECTOR)
+ inspectorUpdateApplicationCacheStatus(m_frame);
+#endif
+}
+
+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)