summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/loader/appcache
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2011-05-06 11:45:16 +0100
committerSteve Block <steveblock@google.com>2011-05-12 13:44:10 +0100
commitcad810f21b803229eb11403f9209855525a25d57 (patch)
tree29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/loader/appcache
parent121b0cf4517156d0ac5111caf9830c51b69bae8f (diff)
downloadexternal_webkit-cad810f21b803229eb11403f9209855525a25d57.zip
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/loader/appcache')
-rw-r--r--Source/WebCore/loader/appcache/ApplicationCache.cpp204
-rw-r--r--Source/WebCore/loader/appcache/ApplicationCache.h116
-rw-r--r--Source/WebCore/loader/appcache/ApplicationCacheGroup.cpp1181
-rw-r--r--Source/WebCore/loader/appcache/ApplicationCacheGroup.h217
-rw-r--r--Source/WebCore/loader/appcache/ApplicationCacheHost.cpp466
-rw-r--r--Source/WebCore/loader/appcache/ApplicationCacheHost.h208
-rw-r--r--Source/WebCore/loader/appcache/ApplicationCacheResource.cpp90
-rw-r--r--Source/WebCore/loader/appcache/ApplicationCacheResource.h75
-rw-r--r--Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp1306
-rw-r--r--Source/WebCore/loader/appcache/ApplicationCacheStorage.h147
-rw-r--r--Source/WebCore/loader/appcache/DOMApplicationCache.cpp130
-rw-r--r--Source/WebCore/loader/appcache/DOMApplicationCache.h98
-rw-r--r--Source/WebCore/loader/appcache/DOMApplicationCache.idl69
-rw-r--r--Source/WebCore/loader/appcache/ManifestParser.cpp188
-rw-r--r--Source/WebCore/loader/appcache/ManifestParser.h50
15 files changed, 4545 insertions, 0 deletions
diff --git a/Source/WebCore/loader/appcache/ApplicationCache.cpp b/Source/WebCore/loader/appcache/ApplicationCache.cpp
new file mode 100644
index 0000000..2a93765
--- /dev/null
+++ b/Source/WebCore/loader/appcache/ApplicationCache.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2008 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 "ApplicationCache.h"
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "ApplicationCacheGroup.h"
+#include "ApplicationCacheResource.h"
+#include "ApplicationCacheStorage.h"
+#include "ResourceRequest.h"
+#include <wtf/text/CString.h>
+#include <stdio.h>
+
+namespace WebCore {
+
+ApplicationCache::ApplicationCache()
+ : m_group(0)
+ , m_manifest(0)
+ , m_estimatedSizeInStorage(0)
+ , m_storageID(0)
+{
+}
+
+ApplicationCache::~ApplicationCache()
+{
+ if (m_group && !m_group->isCopy())
+ m_group->cacheDestroyed(this);
+}
+
+void ApplicationCache::setGroup(ApplicationCacheGroup* group)
+{
+ ASSERT(!m_group || group == m_group);
+ m_group = group;
+}
+
+bool ApplicationCache::isComplete() const
+{
+ return !m_group->cacheIsBeingUpdated(this);
+}
+
+void ApplicationCache::setManifestResource(PassRefPtr<ApplicationCacheResource> manifest)
+{
+ ASSERT(manifest);
+ ASSERT(!m_manifest);
+ ASSERT(manifest->type() & ApplicationCacheResource::Manifest);
+
+ m_manifest = manifest.get();
+
+ addResource(manifest);
+}
+
+void ApplicationCache::addResource(PassRefPtr<ApplicationCacheResource> resource)
+{
+ ASSERT(resource);
+
+ const String& url = resource->url();
+
+ ASSERT(!m_resources.contains(url));
+
+ if (m_storageID) {
+ ASSERT(!resource->storageID());
+ ASSERT(resource->type() & ApplicationCacheResource::Master);
+
+ // Add the resource to the storage.
+ cacheStorage().store(resource.get(), this);
+ }
+
+ m_estimatedSizeInStorage += resource->estimatedSizeInStorage();
+
+ m_resources.set(url, resource);
+}
+
+unsigned ApplicationCache::removeResource(const String& url)
+{
+ HashMap<String, RefPtr<ApplicationCacheResource> >::iterator it = m_resources.find(url);
+ if (it == m_resources.end())
+ return 0;
+
+ // The resource exists, get its type so we can return it.
+ unsigned type = it->second->type();
+
+ m_resources.remove(it);
+
+ m_estimatedSizeInStorage -= it->second->estimatedSizeInStorage();
+
+ return type;
+}
+
+ApplicationCacheResource* ApplicationCache::resourceForURL(const String& url)
+{
+ ASSERT(!KURL(ParsedURLString, url).hasFragmentIdentifier());
+ return m_resources.get(url).get();
+}
+
+bool ApplicationCache::requestIsHTTPOrHTTPSGet(const ResourceRequest& request)
+{
+ if (!request.url().protocolInHTTPFamily())
+ return false;
+
+ if (!equalIgnoringCase(request.httpMethod(), "GET"))
+ return false;
+
+ return true;
+}
+
+ApplicationCacheResource* ApplicationCache::resourceForRequest(const ResourceRequest& request)
+{
+ // We only care about HTTP/HTTPS GET requests.
+ if (!requestIsHTTPOrHTTPSGet(request))
+ return 0;
+
+ KURL url(request.url());
+ if (url.hasFragmentIdentifier())
+ url.removeFragmentIdentifier();
+
+ return resourceForURL(url);
+}
+
+void ApplicationCache::setOnlineWhitelist(const Vector<KURL>& onlineWhitelist)
+{
+ ASSERT(m_onlineWhitelist.isEmpty());
+ m_onlineWhitelist = onlineWhitelist;
+}
+
+bool ApplicationCache::isURLInOnlineWhitelist(const KURL& url)
+{
+ if (m_allowAllNetworkRequests)
+ return true;
+
+ size_t whitelistSize = m_onlineWhitelist.size();
+ for (size_t i = 0; i < whitelistSize; ++i) {
+ if (protocolHostAndPortAreEqual(url, m_onlineWhitelist[i]) && url.string().startsWith(m_onlineWhitelist[i].string()))
+ return true;
+ }
+ return false;
+}
+
+void ApplicationCache::setFallbackURLs(const FallbackURLVector& fallbackURLs)
+{
+ ASSERT(m_fallbackURLs.isEmpty());
+ m_fallbackURLs = fallbackURLs;
+}
+
+bool ApplicationCache::urlMatchesFallbackNamespace(const KURL& url, KURL* fallbackURL)
+{
+ size_t fallbackCount = m_fallbackURLs.size();
+ for (size_t i = 0; i < fallbackCount; ++i) {
+ if (protocolHostAndPortAreEqual(url, m_fallbackURLs[i].first) && url.string().startsWith(m_fallbackURLs[i].first.string())) {
+ if (fallbackURL)
+ *fallbackURL = m_fallbackURLs[i].second;
+ return true;
+ }
+ }
+ return false;
+}
+
+void ApplicationCache::clearStorageID()
+{
+ m_storageID = 0;
+
+ ResourceMap::const_iterator end = m_resources.end();
+ for (ResourceMap::const_iterator it = m_resources.begin(); it != end; ++it)
+ it->second->clearStorageID();
+}
+
+#ifndef NDEBUG
+void ApplicationCache::dump()
+{
+ HashMap<String, RefPtr<ApplicationCacheResource> >::const_iterator end = m_resources.end();
+
+ for (HashMap<String, RefPtr<ApplicationCacheResource> >::const_iterator it = m_resources.begin(); it != end; ++it) {
+ printf("%s ", it->first.ascii().data());
+ ApplicationCacheResource::dumpType(it->second->type());
+ }
+}
+#endif
+
+}
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
diff --git a/Source/WebCore/loader/appcache/ApplicationCache.h b/Source/WebCore/loader/appcache/ApplicationCache.h
new file mode 100644
index 0000000..f073499
--- /dev/null
+++ b/Source/WebCore/loader/appcache/ApplicationCache.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+#ifndef ApplicationCache_h
+#define ApplicationCache_h
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "PlatformString.h"
+#include <wtf/HashMap.h>
+#include <wtf/HashSet.h>
+#include <wtf/PassRefPtr.h>
+#include <wtf/RefCounted.h>
+#include <wtf/text/StringHash.h>
+
+namespace WebCore {
+
+class ApplicationCacheGroup;
+class ApplicationCacheResource;
+class DocumentLoader;
+class KURL;
+class ResourceRequest;
+
+typedef Vector<std::pair<KURL, KURL> > FallbackURLVector;
+
+class ApplicationCache : public RefCounted<ApplicationCache> {
+public:
+ static PassRefPtr<ApplicationCache> create() { return adoptRef(new ApplicationCache); }
+ ~ApplicationCache();
+
+ void addResource(PassRefPtr<ApplicationCacheResource> resource);
+ unsigned removeResource(const String& url);
+
+ void setManifestResource(PassRefPtr<ApplicationCacheResource> manifest);
+ ApplicationCacheResource* manifestResource() const { return m_manifest; }
+
+ void setGroup(ApplicationCacheGroup*);
+ ApplicationCacheGroup* group() const { return m_group; }
+
+ bool isComplete() const;
+
+ ApplicationCacheResource* resourceForRequest(const ResourceRequest&);
+ ApplicationCacheResource* resourceForURL(const String& url);
+
+ void setAllowsAllNetworkRequests(bool value) { m_allowAllNetworkRequests = value; }
+ bool allowsAllNetworkRequests() const { return m_allowAllNetworkRequests; }
+ void setOnlineWhitelist(const Vector<KURL>& onlineWhitelist);
+ const Vector<KURL>& onlineWhitelist() const { return m_onlineWhitelist; }
+ bool isURLInOnlineWhitelist(const KURL&); // There is an entry in online whitelist that has the same origin as the resource's URL and that is a prefix match for the resource's URL.
+
+ void setFallbackURLs(const FallbackURLVector&);
+ const FallbackURLVector& fallbackURLs() const { return m_fallbackURLs; }
+ bool urlMatchesFallbackNamespace(const KURL&, KURL* fallbackURL = 0);
+
+#ifndef NDEBUG
+ void dump();
+#endif
+
+ typedef HashMap<String, RefPtr<ApplicationCacheResource> > ResourceMap;
+ ResourceMap::const_iterator begin() const { return m_resources.begin(); }
+ ResourceMap::const_iterator end() const { return m_resources.end(); }
+
+ void setStorageID(unsigned storageID) { m_storageID = storageID; }
+ unsigned storageID() const { return m_storageID; }
+ void clearStorageID();
+
+ static bool requestIsHTTPOrHTTPSGet(const ResourceRequest&);
+
+ int64_t estimatedSizeInStorage() const { return m_estimatedSizeInStorage; }
+
+private:
+ ApplicationCache();
+
+ ApplicationCacheGroup* m_group;
+ ResourceMap m_resources;
+ ApplicationCacheResource* m_manifest;
+
+ bool m_allowAllNetworkRequests;
+ Vector<KURL> m_onlineWhitelist;
+ FallbackURLVector m_fallbackURLs;
+
+ // The total size of the resources belonging to this Application Cache instance.
+ // This is an estimation of the size this Application Cache occupies in the
+ // database file.
+ int64_t m_estimatedSizeInStorage;
+
+ unsigned m_storageID;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#endif // ApplicationCache_h
diff --git a/Source/WebCore/loader/appcache/ApplicationCacheGroup.cpp b/Source/WebCore/loader/appcache/ApplicationCacheGroup.cpp
new file mode 100644
index 0000000..6454b90
--- /dev/null
+++ b/Source/WebCore/loader/appcache/ApplicationCacheGroup.cpp
@@ -0,0 +1,1181 @@
+/*
+ * 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 "FrameLoaderClient.h"
+#include "MainResourceLoader.h"
+#include "ManifestParser.h"
+#include "Page.h"
+#include "SecurityOrigin.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_origin(SecurityOrigin::create(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)
+ , m_loadedSize(0)
+ , m_availableSpaceInQuota(ApplicationCacheStorage::unknownQuota())
+ , m_originQuotaReached(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) {
+ // The cache may have gotten obsoleted after we've loaded from it, but before we parsed the document and saw cache manifest.
+ if (mainResourceCache->group()->isObsolete())
+ return;
+ 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 resourceURL(documentLoader->responseURL());
+ if (resourceURL.hasFragmentIdentifier())
+ resourceURL.removeFragmentIdentifier();
+ ApplicationCacheResource* resource = mainResourceCache->resourceForURL(resourceURL);
+ 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->navigationScheduler()->scheduleLocationChange(frame->document()->securityOrigin(), 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;
+ }
+}
+
+void ApplicationCacheGroup::stopLoadingInFrame(Frame* frame)
+{
+ if (frame != m_frame)
+ return;
+
+ stopLoading();
+}
+
+#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(m_frame->loader()->networkingContext(), request, this, 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, m_frame->loader()->documentLoader(), 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);
+
+ m_loadedSize += length;
+}
+
+void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle, double finishTime)
+{
+#if ENABLE(INSPECTOR)
+ if (Page* page = m_frame->page())
+ page->inspectorController()->didFinishLoading(m_currentResourceIdentifier, finishTime);
+#endif
+
+ if (handle == m_manifestHandle) {
+ didFinishLoadingManifest();
+ return;
+ }
+
+ // After finishing the loading of any resource, we check if it will
+ // fit in our last known quota limit.
+ if (m_availableSpaceInQuota == ApplicationCacheStorage::unknownQuota()) {
+ // Failed to determine what is left in the quota. Fallback to allowing anything.
+ if (!cacheStorage().remainingSizeForOriginExcludingCache(m_origin.get(), m_newestCache.get(), m_availableSpaceInQuota))
+ m_availableSpaceInQuota = ApplicationCacheStorage::noQuota();
+ }
+
+ // Check each resource, as it loads, to see if it would fit in our
+ // idea of the available quota space.
+ if (m_availableSpaceInQuota < m_loadedSize) {
+ m_currentResource = 0;
+ cacheUpdateFailedDueToOriginQuota();
+ 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::didReachOriginQuota(PassRefPtr<Frame> frame)
+{
+ // Inform the client the origin quota has been reached,
+ // they may decide to increase the quota.
+ frame->page()->chrome()->client()->reachedApplicationCacheOriginQuota(m_origin.get());
+}
+
+void ApplicationCacheGroup::cacheUpdateFailed()
+{
+ stopLoading();
+ m_manifestResource = 0;
+
+ // Wait for master resource loads to finish.
+ m_completionType = Failure;
+ deliverDelayedMainResources();
+}
+
+void ApplicationCacheGroup::cacheUpdateFailedDueToOriginQuota()
+{
+ if (!m_originQuotaReached) {
+ m_originQuotaReached = true;
+ scheduleReachedOriginQuotaCallback();
+ }
+
+ cacheUpdateFailed();
+}
+
+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);
+ }
+
+ ApplicationCacheStorage::FailureReason failureReason;
+ RefPtr<ApplicationCache> oldNewestCache = (m_newestCache == m_cacheBeingUpdated) ? 0 : m_newestCache;
+ setNewestCache(m_cacheBeingUpdated.release());
+ if (cacheStorage().storeNewestCache(this, oldNewestCache.get(), failureReason)) {
+ // 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);
+ // It is clear that the origin quota was not reached, so clear the flag if it was set.
+ m_originQuotaReached = false;
+ } else {
+ if (failureReason == ApplicationCacheStorage::OriginQuotaReached) {
+ // We ran out of space for this origin. Roll back to previous state.
+ if (oldNewestCache)
+ setNewestCache(oldNewestCache.release());
+ cacheUpdateFailedDueToOriginQuota();
+ return;
+ }
+
+ if (failureReason == ApplicationCacheStorage::TotalQuotaReached && !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;
+ }
+
+ // 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_loadedSize = 0;
+ m_availableSpaceInQuota = ApplicationCacheStorage::unknownQuota();
+ 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.
+}
+
+void ApplicationCacheGroup::scheduleReachedOriginQuotaCallback()
+{
+ // FIXME: it might be nice to run this asynchronously, because there is no return value to wait for.
+ didReachOriginQuota(m_frame);
+}
+
+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)
diff --git a/Source/WebCore/loader/appcache/ApplicationCacheGroup.h b/Source/WebCore/loader/appcache/ApplicationCacheGroup.h
new file mode 100644
index 0000000..29d0749
--- /dev/null
+++ b/Source/WebCore/loader/appcache/ApplicationCacheGroup.h
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+#ifndef ApplicationCacheGroup_h
+#define ApplicationCacheGroup_h
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "DOMApplicationCache.h"
+#include "KURL.h"
+#include "PlatformString.h"
+#include "ResourceHandle.h"
+#include "ResourceHandleClient.h"
+#include "SharedBuffer.h"
+
+#include <wtf/Noncopyable.h>
+#include <wtf/HashMap.h>
+#include <wtf/HashSet.h>
+
+namespace WebCore {
+
+class ApplicationCache;
+class ApplicationCacheResource;
+class Document;
+class DocumentLoader;
+class Frame;
+class SecurityOrigin;
+
+enum ApplicationCacheUpdateOption {
+ ApplicationCacheUpdateWithBrowsingContext,
+ ApplicationCacheUpdateWithoutBrowsingContext
+};
+
+class ApplicationCacheGroup : public Noncopyable, ResourceHandleClient {
+public:
+ ApplicationCacheGroup(const KURL& manifestURL, bool isCopy = false);
+ ~ApplicationCacheGroup();
+
+ enum UpdateStatus { Idle, Checking, Downloading };
+
+ static ApplicationCache* cacheForMainRequest(const ResourceRequest&, DocumentLoader*);
+ static ApplicationCache* fallbackCacheForMainRequest(const ResourceRequest&, DocumentLoader*);
+
+ static void selectCache(Frame*, const KURL& manifestURL);
+ static void selectCacheWithoutManifestURL(Frame*);
+
+ const KURL& manifestURL() const { return m_manifestURL; }
+ const SecurityOrigin* origin() const { return m_origin.get(); }
+ UpdateStatus updateStatus() const { return m_updateStatus; }
+ void setUpdateStatus(UpdateStatus status);
+
+ void setStorageID(unsigned storageID) { m_storageID = storageID; }
+ unsigned storageID() const { return m_storageID; }
+ void clearStorageID();
+
+ void update(Frame*, ApplicationCacheUpdateOption); // FIXME: Frame should not be needed when updating without browsing context.
+ void cacheDestroyed(ApplicationCache*);
+
+ bool cacheIsBeingUpdated(const ApplicationCache* cache) const { return cache == m_cacheBeingUpdated; }
+
+ void stopLoadingInFrame(Frame*);
+
+ ApplicationCache* newestCache() const { return m_newestCache.get(); }
+ void setNewestCache(PassRefPtr<ApplicationCache>);
+
+ void makeObsolete();
+ bool isObsolete() const { return m_isObsolete; }
+
+ void finishedLoadingMainResource(DocumentLoader*);
+ void failedLoadingMainResource(DocumentLoader*);
+
+ void disassociateDocumentLoader(DocumentLoader*);
+
+ bool isCopy() const { return m_isCopy; }
+
+private:
+ static void postListenerTask(ApplicationCacheHost::EventID id, const HashSet<DocumentLoader*>& set) { postListenerTask(id, 0, 0, set); }
+ static void postListenerTask(ApplicationCacheHost::EventID id, DocumentLoader* loader) { postListenerTask(id, 0, 0, loader); }
+ static void postListenerTask(ApplicationCacheHost::EventID, int progressTotal, int progressDone, const HashSet<DocumentLoader*>&);
+ static void postListenerTask(ApplicationCacheHost::EventID, int progressTotal, int progressDone, DocumentLoader*);
+
+ void scheduleReachedMaxAppCacheSizeCallback();
+ void scheduleReachedOriginQuotaCallback();
+
+ PassRefPtr<ResourceHandle> createResourceHandle(const KURL&, ApplicationCacheResource* newestCachedResource);
+
+ // For normal resource loading, WebKit client is asked about each resource individually. Since application cache does not belong to any particular document,
+ // the existing client callback cannot be used, so assume that any client that enables application cache also wants it to use credential storage.
+ virtual bool shouldUseCredentialStorage(ResourceHandle*) { return true; }
+
+#if ENABLE(INSPECTOR)
+ virtual void willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse&);
+#endif
+ virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&);
+ virtual void didReceiveData(ResourceHandle*, const char*, int length, int lengthReceived);
+ virtual void didFinishLoading(ResourceHandle*, double finishTime);
+ virtual void didFail(ResourceHandle*, const ResourceError&);
+
+ void didReceiveManifestResponse(const ResourceResponse&);
+ void didReceiveManifestData(const char*, int);
+ void didFinishLoadingManifest();
+ void didReachMaxAppCacheSize();
+ void didReachOriginQuota(PassRefPtr<Frame> frame);
+
+ void startLoadingEntry();
+ void deliverDelayedMainResources();
+ void checkIfLoadIsComplete();
+ void cacheUpdateFailed();
+ void cacheUpdateFailedDueToOriginQuota();
+ void manifestNotFound();
+
+ void addEntry(const String&, unsigned type);
+
+ void associateDocumentLoaderWithCache(DocumentLoader*, ApplicationCache*);
+
+ void stopLoading();
+
+ KURL m_manifestURL;
+ RefPtr<SecurityOrigin> m_origin;
+ UpdateStatus m_updateStatus;
+
+ // This is the newest complete cache in the group.
+ RefPtr<ApplicationCache> m_newestCache;
+
+ // All complete caches in this cache group.
+ HashSet<ApplicationCache*> m_caches;
+
+ // The cache being updated (if any). Note that cache updating does not immediately create a new
+ // ApplicationCache object, so this may be null even when update status is not Idle.
+ RefPtr<ApplicationCache> m_cacheBeingUpdated;
+
+ // List of pending master entries, used during the update process to ensure that new master entries are cached.
+ HashSet<DocumentLoader*> m_pendingMasterResourceLoaders;
+ // How many of the above pending master entries have not yet finished downloading.
+ int m_downloadingPendingMasterResourceLoadersCount;
+
+ // These are all the document loaders that are associated with a cache in this group.
+ HashSet<DocumentLoader*> m_associatedDocumentLoaders;
+
+ // The URLs and types of pending cache entries.
+ typedef HashMap<String, unsigned> EntryMap;
+ EntryMap m_pendingEntries;
+
+ // The total number of items to be processed to update the cache group and the number that have been done.
+ int m_progressTotal;
+ int m_progressDone;
+
+ // Frame used for fetching resources when updating.
+ // FIXME: An update started by a particular frame should not stop if it is destroyed, but there are other frames associated with the same cache group.
+ Frame* m_frame;
+
+ // An obsolete cache group is never stored, but the opposite is not true - storing may fail for multiple reasons, such as exceeding disk quota.
+ unsigned m_storageID;
+ bool m_isObsolete;
+
+ // During update, this is used to handle asynchronously arriving results.
+ enum CompletionType {
+ None,
+ NoUpdate,
+ Failure,
+ Completed
+ };
+ CompletionType m_completionType;
+
+ // Whether this cache group is a copy that's only used for transferring the cache to another file.
+ bool m_isCopy;
+
+ // This flag is set immediately after the ChromeClient::reachedMaxAppCacheSize() callback is invoked as a result of the storage layer failing to save a cache
+ // due to reaching the maximum size of the application cache database file. This flag is used by ApplicationCacheGroup::checkIfLoadIsComplete() to decide
+ // the course of action in case of this failure (i.e. call the ChromeClient callback or run the failure steps).
+ bool m_calledReachedMaxAppCacheSize;
+
+ RefPtr<ResourceHandle> m_currentHandle;
+ RefPtr<ApplicationCacheResource> m_currentResource;
+
+#if ENABLE(INSPECTOR)
+ unsigned long m_currentResourceIdentifier;
+#endif
+
+ RefPtr<ApplicationCacheResource> m_manifestResource;
+ RefPtr<ResourceHandle> m_manifestHandle;
+
+ int64_t m_loadedSize;
+ int64_t m_availableSpaceInQuota;
+ bool m_originQuotaReached;
+
+ friend class ChromeClientCallbackTimer;
+ friend class OriginQuotaReachedCallbackTimer;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#endif // ApplicationCacheGroup_h
diff --git a/Source/WebCore/loader/appcache/ApplicationCacheHost.cpp b/Source/WebCore/loader/appcache/ApplicationCacheHost.cpp
new file mode 100644
index 0000000..d5707cf
--- /dev/null
+++ b/Source/WebCore/loader/appcache/ApplicationCacheHost.cpp
@@ -0,0 +1,466 @@
+/*
+ * 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
+ * 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 "ApplicationCacheHost.h"
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "ApplicationCache.h"
+#include "ApplicationCacheGroup.h"
+#include "ApplicationCacheResource.h"
+#include "DocumentLoader.h"
+#include "DOMApplicationCache.h"
+#include "Frame.h"
+#include "FrameLoader.h"
+#include "FrameLoaderClient.h"
+#include "MainResourceLoader.h"
+#include "ProgressEvent.h"
+#include "ResourceLoader.h"
+#include "ResourceRequest.h"
+#include "Settings.h"
+
+namespace WebCore {
+
+ApplicationCacheHost::ApplicationCacheHost(DocumentLoader* documentLoader)
+ : m_domApplicationCache(0)
+ , m_documentLoader(documentLoader)
+ , m_defersEvents(true)
+ , m_candidateApplicationCacheGroup(0)
+{
+ ASSERT(m_documentLoader);
+}
+
+ApplicationCacheHost::~ApplicationCacheHost()
+{
+ ASSERT(!m_applicationCache || !m_candidateApplicationCacheGroup || m_applicationCache->group() == m_candidateApplicationCacheGroup);
+
+ if (m_applicationCache)
+ m_applicationCache->group()->disassociateDocumentLoader(m_documentLoader);
+ else if (m_candidateApplicationCacheGroup)
+ m_candidateApplicationCacheGroup->disassociateDocumentLoader(m_documentLoader);
+}
+
+void ApplicationCacheHost::selectCacheWithoutManifest()
+{
+ ApplicationCacheGroup::selectCacheWithoutManifestURL(m_documentLoader->frame());
+}
+
+void ApplicationCacheHost::selectCacheWithManifest(const KURL& manifestURL)
+{
+ ApplicationCacheGroup::selectCache(m_documentLoader->frame(), manifestURL);
+}
+
+void ApplicationCacheHost::maybeLoadMainResource(ResourceRequest& request, SubstituteData& substituteData)
+{
+ // Check if this request should be loaded from the application cache
+ if (!substituteData.isValid() && isApplicationCacheEnabled()) {
+ ASSERT(!m_mainResourceApplicationCache);
+
+ m_mainResourceApplicationCache = ApplicationCacheGroup::cacheForMainRequest(request, m_documentLoader);
+
+ if (m_mainResourceApplicationCache) {
+ // Get the resource from the application cache. By definition, cacheForMainRequest() returns a cache that contains the resource.
+ ApplicationCacheResource* resource = m_mainResourceApplicationCache->resourceForRequest(request);
+ substituteData = SubstituteData(resource->data(),
+ resource->response().mimeType(),
+ resource->response().textEncodingName(), KURL());
+ }
+ }
+}
+
+void ApplicationCacheHost::maybeLoadMainResourceForRedirect(ResourceRequest& request, SubstituteData& substituteData)
+{
+ ASSERT(status() == UNCACHED);
+ maybeLoadMainResource(request, substituteData);
+}
+
+bool ApplicationCacheHost::maybeLoadFallbackForMainResponse(const ResourceRequest& request, const ResourceResponse& r)
+{
+ if (r.httpStatusCode() / 100 == 4 || r.httpStatusCode() / 100 == 5) {
+ ASSERT(!m_mainResourceApplicationCache);
+ if (isApplicationCacheEnabled()) {
+ m_mainResourceApplicationCache = ApplicationCacheGroup::fallbackCacheForMainRequest(request, documentLoader());
+
+ if (scheduleLoadFallbackResourceFromApplicationCache(documentLoader()->mainResourceLoader(), m_mainResourceApplicationCache.get()))
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ApplicationCacheHost::maybeLoadFallbackForMainError(const ResourceRequest& request, const ResourceError& error)
+{
+ if (!error.isCancellation()) {
+ ASSERT(!m_mainResourceApplicationCache);
+ if (isApplicationCacheEnabled()) {
+ m_mainResourceApplicationCache = ApplicationCacheGroup::fallbackCacheForMainRequest(request, m_documentLoader);
+
+ if (scheduleLoadFallbackResourceFromApplicationCache(documentLoader()->mainResourceLoader(), m_mainResourceApplicationCache.get()))
+ return true;
+ }
+ }
+ return false;
+}
+
+void ApplicationCacheHost::mainResourceDataReceived(const char*, int, long long, bool)
+{
+ // This method is here to facilitate alternate implemetations of this interface by the host browser.
+}
+
+void ApplicationCacheHost::failedLoadingMainResource()
+{
+ ApplicationCacheGroup* group = m_candidateApplicationCacheGroup;
+ if (!group && m_applicationCache) {
+ if (mainResourceApplicationCache()) {
+ // Even when the main resource is being loaded from an application cache, loading can fail if aborted.
+ return;
+ }
+ group = m_applicationCache->group();
+ }
+
+ if (group)
+ group->failedLoadingMainResource(m_documentLoader);
+}
+
+void ApplicationCacheHost::finishedLoadingMainResource()
+{
+ ApplicationCacheGroup* group = candidateApplicationCacheGroup();
+ if (!group && applicationCache() && !mainResourceApplicationCache())
+ group = applicationCache()->group();
+
+ if (group)
+ group->finishedLoadingMainResource(m_documentLoader);
+}
+
+bool ApplicationCacheHost::maybeLoadResource(ResourceLoader* loader, ResourceRequest& request, const KURL& originalURL)
+{
+ if (!isApplicationCacheEnabled())
+ return false;
+
+ if (request.url() != originalURL)
+ return false;
+
+ ApplicationCacheResource* resource;
+ if (!shouldLoadResourceFromApplicationCache(request, resource))
+ return false;
+
+ m_documentLoader->m_pendingSubstituteResources.set(loader, resource);
+ m_documentLoader->deliverSubstituteResourcesAfterDelay();
+
+ return true;
+}
+
+bool ApplicationCacheHost::maybeLoadFallbackForRedirect(ResourceLoader* resourceLoader, ResourceRequest& request, const ResourceResponse& redirectResponse)
+{
+ if (!redirectResponse.isNull() && !protocolHostAndPortAreEqual(request.url(), redirectResponse.url()))
+ if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
+ return true;
+ return false;
+}
+
+bool ApplicationCacheHost::maybeLoadFallbackForResponse(ResourceLoader* resourceLoader, const ResourceResponse& response)
+{
+ if (response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5)
+ if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
+ return true;
+ return false;
+}
+
+bool ApplicationCacheHost::maybeLoadFallbackForError(ResourceLoader* resourceLoader, const ResourceError& error)
+{
+ if (!error.isCancellation())
+ if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
+ return true;
+ return false;
+}
+
+bool ApplicationCacheHost::maybeLoadSynchronously(ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data)
+{
+ ApplicationCacheResource* resource;
+ if (shouldLoadResourceFromApplicationCache(request, resource)) {
+ if (resource) {
+ response = resource->response();
+ data.append(resource->data()->data(), resource->data()->size());
+ } else {
+ error = documentLoader()->frameLoader()->client()->cannotShowURLError(request);
+ }
+ return true;
+ }
+ return false;
+}
+
+void ApplicationCacheHost::maybeLoadFallbackSynchronously(const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data)
+{
+ // If normal loading results in a redirect to a resource with another origin (indicative of a captive portal), or a 4xx or 5xx status code or equivalent,
+ // or if there were network errors (but not if the user canceled the download), then instead get, from the cache, the resource of the fallback entry
+ // corresponding to the matched namespace.
+ if ((!error.isNull() && !error.isCancellation())
+ || response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5
+ || !protocolHostAndPortAreEqual(request.url(), response.url())) {
+ ApplicationCacheResource* resource;
+ if (getApplicationCacheFallbackResource(request, resource)) {
+ response = resource->response();
+ data.clear();
+ data.append(resource->data()->data(), resource->data()->size());
+ }
+ }
+}
+
+bool ApplicationCacheHost::canCacheInPageCache() const
+{
+ return !applicationCache() && !candidateApplicationCacheGroup();
+}
+
+void ApplicationCacheHost::setDOMApplicationCache(DOMApplicationCache* domApplicationCache)
+{
+ ASSERT(!m_domApplicationCache || !domApplicationCache);
+ m_domApplicationCache = domApplicationCache;
+}
+
+void ApplicationCacheHost::notifyDOMApplicationCache(EventID id, int total, int done)
+{
+ if (m_defersEvents) {
+ // Event dispatching is deferred until document.onload has fired.
+ m_deferredEvents.append(DeferredEvent(id, total, done));
+ return;
+ }
+ dispatchDOMEvent(id, total, done);
+}
+
+void ApplicationCacheHost::stopLoadingInFrame(Frame* frame)
+{
+ ASSERT(!m_applicationCache || !m_candidateApplicationCacheGroup || m_applicationCache->group() == m_candidateApplicationCacheGroup);
+
+ if (m_candidateApplicationCacheGroup)
+ m_candidateApplicationCacheGroup->stopLoadingInFrame(frame);
+ else if (m_applicationCache)
+ m_applicationCache->group()->stopLoadingInFrame(frame);
+}
+
+void ApplicationCacheHost::stopDeferringEvents()
+{
+ RefPtr<DocumentLoader> protect(documentLoader());
+ for (unsigned i = 0; i < m_deferredEvents.size(); ++i) {
+ const DeferredEvent& deferred = m_deferredEvents[i];
+ dispatchDOMEvent(deferred.eventID, deferred.progressTotal, deferred.progressDone);
+ }
+ m_deferredEvents.clear();
+ m_defersEvents = false;
+}
+
+#if ENABLE(INSPECTOR)
+void ApplicationCacheHost::fillResourceList(ResourceInfoList* resources)
+{
+ ApplicationCache* cache = applicationCache();
+ if (!cache || !cache->isComplete())
+ return;
+
+ ApplicationCache::ResourceMap::const_iterator end = cache->end();
+ for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) {
+ RefPtr<ApplicationCacheResource> resource = it->second;
+ unsigned type = resource->type();
+ bool isMaster = type & ApplicationCacheResource::Master;
+ bool isManifest = type & ApplicationCacheResource::Manifest;
+ bool isExplicit = type & ApplicationCacheResource::Explicit;
+ bool isForeign = type & ApplicationCacheResource::Foreign;
+ bool isFallback = type & ApplicationCacheResource::Fallback;
+ resources->append(ResourceInfo(resource->url(), isMaster, isManifest, isFallback, isForeign, isExplicit, resource->estimatedSizeInStorage()));
+ }
+}
+
+ApplicationCacheHost::CacheInfo ApplicationCacheHost::applicationCacheInfo()
+{
+ ApplicationCache* cache = applicationCache();
+ if (!cache || !cache->isComplete())
+ return CacheInfo(KURL(), 0, 0, 0);
+
+ // FIXME: Add "Creation Time" and "Update Time" to Application Caches.
+ return CacheInfo(cache->manifestResource()->url(), 0, 0, cache->estimatedSizeInStorage());
+}
+#endif
+
+void ApplicationCacheHost::dispatchDOMEvent(EventID id, int total, int done)
+{
+ if (m_domApplicationCache) {
+ const AtomicString& eventType = DOMApplicationCache::toEventType(id);
+ ExceptionCode ec = 0;
+ RefPtr<Event> event;
+ if (id == PROGRESS_EVENT)
+ event = ProgressEvent::create(eventType, true, done, total);
+ else
+ event = Event::create(eventType, false, false);
+ m_domApplicationCache->dispatchEvent(event, ec);
+ ASSERT(!ec);
+ }
+}
+
+void ApplicationCacheHost::setCandidateApplicationCacheGroup(ApplicationCacheGroup* group)
+{
+ ASSERT(!m_applicationCache);
+ m_candidateApplicationCacheGroup = group;
+}
+
+void ApplicationCacheHost::setApplicationCache(PassRefPtr<ApplicationCache> applicationCache)
+{
+ if (m_candidateApplicationCacheGroup) {
+ ASSERT(!m_applicationCache);
+ m_candidateApplicationCacheGroup = 0;
+ }
+
+ m_applicationCache = applicationCache;
+}
+
+bool ApplicationCacheHost::shouldLoadResourceFromApplicationCache(const ResourceRequest& request, ApplicationCacheResource*& resource)
+{
+ ApplicationCache* cache = applicationCache();
+ if (!cache || !cache->isComplete())
+ return false;
+
+ // If the resource is not to be fetched using the HTTP GET mechanism or equivalent, or if its URL has a different
+ // <scheme> component than the application cache's manifest, then fetch the resource normally.
+ if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request) || !equalIgnoringCase(request.url().protocol(), cache->manifestResource()->url().protocol()))
+ return false;
+
+ // If the resource's URL is an master entry, the manifest, an explicit entry, or a fallback entry
+ // in the application cache, then get the resource from the cache (instead of fetching it).
+ resource = cache->resourceForURL(request.url());
+
+ // Resources that match fallback namespaces or online whitelist entries are fetched from the network,
+ // unless they are also cached.
+ if (!resource && (cache->urlMatchesFallbackNamespace(request.url()) || cache->isURLInOnlineWhitelist(request.url())))
+ return false;
+
+ // Resources that are not present in the manifest will always fail to load (at least, after the
+ // cache has been primed the first time), making the testing of offline applications simpler.
+ return true;
+}
+
+bool ApplicationCacheHost::getApplicationCacheFallbackResource(const ResourceRequest& request, ApplicationCacheResource*& resource, ApplicationCache* cache)
+{
+ if (!cache) {
+ cache = applicationCache();
+ if (!cache)
+ return false;
+ }
+ if (!cache->isComplete())
+ return false;
+
+ // If the resource is not a HTTP/HTTPS GET, then abort
+ if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
+ return false;
+
+ KURL fallbackURL;
+ if (!cache->urlMatchesFallbackNamespace(request.url(), &fallbackURL))
+ return false;
+
+ resource = cache->resourceForURL(fallbackURL);
+ ASSERT(resource);
+
+ return true;
+}
+
+bool ApplicationCacheHost::scheduleLoadFallbackResourceFromApplicationCache(ResourceLoader* loader, ApplicationCache* cache)
+{
+ if (!isApplicationCacheEnabled())
+ return false;
+
+ ApplicationCacheResource* resource;
+ if (!getApplicationCacheFallbackResource(loader->request(), resource, cache))
+ return false;
+
+ m_documentLoader->m_pendingSubstituteResources.set(loader, resource);
+ m_documentLoader->deliverSubstituteResourcesAfterDelay();
+
+ loader->handle()->cancel();
+
+ return true;
+}
+
+ApplicationCacheHost::Status ApplicationCacheHost::status() const
+{
+ ApplicationCache* cache = applicationCache();
+ if (!cache)
+ return UNCACHED;
+
+ switch (cache->group()->updateStatus()) {
+ case ApplicationCacheGroup::Checking:
+ return CHECKING;
+ case ApplicationCacheGroup::Downloading:
+ return DOWNLOADING;
+ case ApplicationCacheGroup::Idle: {
+ if (cache->group()->isObsolete())
+ return OBSOLETE;
+ if (cache != cache->group()->newestCache())
+ return UPDATEREADY;
+ return IDLE;
+ }
+ }
+
+ ASSERT_NOT_REACHED();
+ return UNCACHED;
+}
+
+bool ApplicationCacheHost::update()
+{
+ ApplicationCache* cache = applicationCache();
+ if (!cache)
+ return false;
+ cache->group()->update(m_documentLoader->frame(), ApplicationCacheUpdateWithoutBrowsingContext);
+ return true;
+}
+
+bool ApplicationCacheHost::swapCache()
+{
+ ApplicationCache* cache = applicationCache();
+ if (!cache)
+ return false;
+
+ // If the group of application caches to which cache belongs has the lifecycle status obsolete, unassociate document from cache.
+ if (cache->group()->isObsolete()) {
+ cache->group()->disassociateDocumentLoader(m_documentLoader);
+ return true;
+ }
+
+ // If there is no newer cache, raise an INVALID_STATE_ERR exception.
+ ApplicationCache* newestCache = cache->group()->newestCache();
+ if (cache == newestCache)
+ return false;
+
+ ASSERT(cache->group() == newestCache->group());
+ setApplicationCache(newestCache);
+
+ return true;
+}
+
+bool ApplicationCacheHost::isApplicationCacheEnabled()
+{
+ return m_documentLoader->frame()->settings()
+ && m_documentLoader->frame()->settings()->offlineWebApplicationCacheEnabled();
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
diff --git a/Source/WebCore/loader/appcache/ApplicationCacheHost.h b/Source/WebCore/loader/appcache/ApplicationCacheHost.h
new file mode 100644
index 0000000..8ac5357
--- /dev/null
+++ b/Source/WebCore/loader/appcache/ApplicationCacheHost.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2009, Google 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:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 THE COPYRIGHT
+ * OWNER 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.
+ */
+
+#ifndef ApplicationCacheHost_h
+#define ApplicationCacheHost_h
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "KURL.h"
+#include <wtf/Deque.h>
+#include <wtf/OwnPtr.h>
+#include <wtf/PassRefPtr.h>
+#include <wtf/RefPtr.h>
+#include <wtf/Vector.h>
+
+namespace WebCore {
+ class DOMApplicationCache;
+ class DocumentLoader;
+ class Frame;
+ class ResourceLoader;
+ class ResourceError;
+ class ResourceRequest;
+ class ResourceResponse;
+ class SubstituteData;
+#if PLATFORM(CHROMIUM)
+ class ApplicationCacheHostInternal;
+#else
+ class ApplicationCache;
+ class ApplicationCacheGroup;
+ class ApplicationCacheResource;
+ class ApplicationCacheStorage;
+#endif
+
+ class ApplicationCacheHost : public Noncopyable {
+ public:
+ // The Status numeric values are specified in the HTML5 spec.
+ enum Status {
+ UNCACHED = 0,
+ IDLE = 1,
+ CHECKING = 2,
+ DOWNLOADING = 3,
+ UPDATEREADY = 4,
+ OBSOLETE = 5
+ };
+
+ enum EventID {
+ CHECKING_EVENT = 0,
+ ERROR_EVENT,
+ NOUPDATE_EVENT,
+ DOWNLOADING_EVENT,
+ PROGRESS_EVENT,
+ UPDATEREADY_EVENT,
+ CACHED_EVENT,
+ OBSOLETE_EVENT // Must remain the last value, this is used to size arrays.
+ };
+
+#if ENABLE(INSPECTOR)
+ struct CacheInfo {
+ CacheInfo(const KURL& manifest, double creationTime, double updateTime, long long size)
+ : m_manifest(manifest)
+ , m_creationTime(creationTime)
+ , m_updateTime(updateTime)
+ , m_size(size) { }
+ KURL m_manifest;
+ double m_creationTime;
+ double m_updateTime;
+ long long m_size;
+ };
+
+ struct ResourceInfo {
+ ResourceInfo(const KURL& resource, bool isMaster, bool isManifest, bool isFallback, bool isForeign, bool isExplicit, long long size)
+ : m_resource(resource)
+ , m_isMaster(isMaster)
+ , m_isManifest(isManifest)
+ , m_isFallback(isFallback)
+ , m_isForeign(isForeign)
+ , m_isExplicit(isExplicit)
+ , m_size(size) { }
+ KURL m_resource;
+ bool m_isMaster;
+ bool m_isManifest;
+ bool m_isFallback;
+ bool m_isForeign;
+ bool m_isExplicit;
+ long long m_size;
+ };
+
+ typedef Vector<ResourceInfo> ResourceInfoList;
+#endif
+
+ ApplicationCacheHost(DocumentLoader*);
+ ~ApplicationCacheHost();
+
+ void selectCacheWithoutManifest();
+ void selectCacheWithManifest(const KURL& manifestURL);
+
+ void maybeLoadMainResource(ResourceRequest&, SubstituteData&);
+ void maybeLoadMainResourceForRedirect(ResourceRequest&, SubstituteData&);
+ bool maybeLoadFallbackForMainResponse(const ResourceRequest&, const ResourceResponse&);
+ bool maybeLoadFallbackForMainError(const ResourceRequest&, const ResourceError&);
+ void mainResourceDataReceived(const char* data, int length, long long lengthReceived, bool allAtOnce);
+ void finishedLoadingMainResource();
+ void failedLoadingMainResource();
+
+ bool maybeLoadResource(ResourceLoader*, ResourceRequest&, const KURL& originalURL);
+ bool maybeLoadFallbackForRedirect(ResourceLoader*, ResourceRequest&, const ResourceResponse&);
+ bool maybeLoadFallbackForResponse(ResourceLoader*, const ResourceResponse&);
+ bool maybeLoadFallbackForError(ResourceLoader*, const ResourceError&);
+
+ bool maybeLoadSynchronously(ResourceRequest&, ResourceError&, ResourceResponse&, Vector<char>& data);
+ void maybeLoadFallbackSynchronously(const ResourceRequest&, ResourceError&, ResourceResponse&, Vector<char>& data);
+
+ bool canCacheInPageCache() const;
+
+ Status status() const;
+ bool update();
+ bool swapCache();
+
+ void setDOMApplicationCache(DOMApplicationCache*);
+ void notifyDOMApplicationCache(EventID, int progressTotal, int progressDone);
+
+ void stopLoadingInFrame(Frame*);
+
+ void stopDeferringEvents(); // Also raises the events that have been queued up.
+
+#if ENABLE(INSPECTOR)
+ void fillResourceList(ResourceInfoList*);
+ CacheInfo applicationCacheInfo();
+#endif
+
+ private:
+ bool isApplicationCacheEnabled();
+ DocumentLoader* documentLoader() const { return m_documentLoader; }
+
+ struct DeferredEvent {
+ EventID eventID;
+ int progressTotal;
+ int progressDone;
+ DeferredEvent(EventID id, int total, int done) : eventID(id), progressTotal(total), progressDone(done) { }
+ };
+
+ DOMApplicationCache* m_domApplicationCache;
+ DocumentLoader* m_documentLoader;
+ bool m_defersEvents; // Events are deferred until after document onload.
+ Vector<DeferredEvent> m_deferredEvents;
+
+ void dispatchDOMEvent(EventID, int progressTotal, int progressDone);
+
+#if PLATFORM(CHROMIUM)
+ friend class ApplicationCacheHostInternal;
+ OwnPtr<ApplicationCacheHostInternal> m_internal;
+#else
+ friend class ApplicationCacheGroup;
+ friend class ApplicationCacheStorage;
+
+ bool scheduleLoadFallbackResourceFromApplicationCache(ResourceLoader*, ApplicationCache* = 0);
+ bool shouldLoadResourceFromApplicationCache(const ResourceRequest&, ApplicationCacheResource*&);
+ bool getApplicationCacheFallbackResource(const ResourceRequest&, ApplicationCacheResource*&, ApplicationCache* = 0);
+ void setCandidateApplicationCacheGroup(ApplicationCacheGroup* group);
+ ApplicationCacheGroup* candidateApplicationCacheGroup() const { return m_candidateApplicationCacheGroup; }
+ void setApplicationCache(PassRefPtr<ApplicationCache> applicationCache);
+ ApplicationCache* applicationCache() const { return m_applicationCache.get(); }
+ ApplicationCache* mainResourceApplicationCache() const { return m_mainResourceApplicationCache.get(); }
+
+
+ // The application cache that the document loader is associated with (if any).
+ RefPtr<ApplicationCache> m_applicationCache;
+
+ // Before an application cache has finished loading, this will be the candidate application
+ // group that the document loader is associated with.
+ ApplicationCacheGroup* m_candidateApplicationCacheGroup;
+
+ // This is the application cache the main resource was loaded from (if any).
+ RefPtr<ApplicationCache> m_mainResourceApplicationCache;
+#endif
+ };
+
+} // namespace WebCore
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
+#endif // ApplicationCacheHost_h
diff --git a/Source/WebCore/loader/appcache/ApplicationCacheResource.cpp b/Source/WebCore/loader/appcache/ApplicationCacheResource.cpp
new file mode 100644
index 0000000..03c5c83
--- /dev/null
+++ b/Source/WebCore/loader/appcache/ApplicationCacheResource.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2008 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 "ApplicationCacheResource.h"
+#include <stdio.h>
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+namespace WebCore {
+
+ApplicationCacheResource::ApplicationCacheResource(const KURL& url, const ResourceResponse& response, unsigned type, PassRefPtr<SharedBuffer> data)
+ : SubstituteResource(url, response, data)
+ , m_type(type)
+ , m_storageID(0)
+ , m_estimatedSizeInStorage(0)
+{
+}
+
+void ApplicationCacheResource::addType(unsigned type)
+{
+ // Caller should take care of storing the new type in database.
+ m_type |= type;
+}
+
+int64_t ApplicationCacheResource::estimatedSizeInStorage()
+{
+ if (m_estimatedSizeInStorage)
+ return m_estimatedSizeInStorage;
+
+ if (data())
+ m_estimatedSizeInStorage = data()->size();
+
+ HTTPHeaderMap::const_iterator end = response().httpHeaderFields().end();
+ for (HTTPHeaderMap::const_iterator it = response().httpHeaderFields().begin(); it != end; ++it)
+ m_estimatedSizeInStorage += (it->first.length() + it->second.length() + 2) * sizeof(UChar);
+
+ m_estimatedSizeInStorage += url().string().length() * sizeof(UChar);
+ m_estimatedSizeInStorage += sizeof(int); // response().m_httpStatusCode
+ m_estimatedSizeInStorage += response().url().string().length() * sizeof(UChar);
+ m_estimatedSizeInStorage += sizeof(unsigned); // dataId
+ m_estimatedSizeInStorage += response().mimeType().length() * sizeof(UChar);
+ m_estimatedSizeInStorage += response().textEncodingName().length() * sizeof(UChar);
+
+ return m_estimatedSizeInStorage;
+}
+
+#ifndef NDEBUG
+void ApplicationCacheResource::dumpType(unsigned type)
+{
+ if (type & Master)
+ printf("master ");
+ if (type & Manifest)
+ printf("manifest ");
+ if (type & Explicit)
+ printf("explicit ");
+ if (type & Foreign)
+ printf("foreign ");
+ if (type & Fallback)
+ printf("fallback ");
+
+ printf("\n");
+}
+#endif
+
+} // namespace WebCore
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
diff --git a/Source/WebCore/loader/appcache/ApplicationCacheResource.h b/Source/WebCore/loader/appcache/ApplicationCacheResource.h
new file mode 100644
index 0000000..2ca7846
--- /dev/null
+++ b/Source/WebCore/loader/appcache/ApplicationCacheResource.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+#ifndef ApplicationCacheResource_h
+#define ApplicationCacheResource_h
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "SubstituteResource.h"
+
+namespace WebCore {
+
+class ApplicationCacheResource : public SubstituteResource {
+public:
+ enum Type {
+ Master = 1 << 0,
+ Manifest = 1 << 1,
+ Explicit = 1 << 2,
+ Foreign = 1 << 3,
+ Fallback = 1 << 4
+ };
+
+ static PassRefPtr<ApplicationCacheResource> create(const KURL& url, const ResourceResponse& response, unsigned type, PassRefPtr<SharedBuffer> buffer = SharedBuffer::create())
+ {
+ ASSERT(!url.hasFragmentIdentifier());
+ return adoptRef(new ApplicationCacheResource(url, response, type, buffer));
+ }
+
+ unsigned type() const { return m_type; }
+ void addType(unsigned type);
+
+ void setStorageID(unsigned storageID) { m_storageID = storageID; }
+ unsigned storageID() const { return m_storageID; }
+ void clearStorageID() { m_storageID = 0; }
+ int64_t estimatedSizeInStorage();
+
+#ifndef NDEBUG
+ static void dumpType(unsigned type);
+#endif
+
+private:
+ ApplicationCacheResource(const KURL& url, const ResourceResponse& response, unsigned type, PassRefPtr<SharedBuffer> buffer);
+
+ unsigned m_type;
+ unsigned m_storageID;
+ int64_t m_estimatedSizeInStorage;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#endif // ApplicationCacheResource_h
diff --git a/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp b/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp
new file mode 100644
index 0000000..7b20775
--- /dev/null
+++ b/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp
@@ -0,0 +1,1306 @@
+/*
+ * 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 "ApplicationCacheStorage.h"
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "ApplicationCache.h"
+#include "ApplicationCacheGroup.h"
+#include "ApplicationCacheHost.h"
+#include "ApplicationCacheResource.h"
+#include "FileSystem.h"
+#include "KURL.h"
+#include "SQLiteStatement.h"
+#include "SQLiteTransaction.h"
+#include "SecurityOrigin.h"
+#include <wtf/text/CString.h>
+#include <wtf/StdLibExtras.h>
+#include <wtf/StringExtras.h>
+
+using namespace std;
+
+namespace WebCore {
+
+template <class T>
+class StorageIDJournal {
+public:
+ ~StorageIDJournal()
+ {
+ size_t size = m_records.size();
+ for (size_t i = 0; i < size; ++i)
+ m_records[i].restore();
+ }
+
+ void add(T* resource, unsigned storageID)
+ {
+ m_records.append(Record(resource, storageID));
+ }
+
+ void commit()
+ {
+ m_records.clear();
+ }
+
+private:
+ class Record {
+ public:
+ Record() : m_resource(0), m_storageID(0) { }
+ Record(T* resource, unsigned storageID) : m_resource(resource), m_storageID(storageID) { }
+
+ void restore()
+ {
+ m_resource->setStorageID(m_storageID);
+ }
+
+ private:
+ T* m_resource;
+ unsigned m_storageID;
+ };
+
+ Vector<Record> m_records;
+};
+
+static unsigned urlHostHash(const KURL& url)
+{
+ unsigned hostStart = url.hostStart();
+ unsigned hostEnd = url.hostEnd();
+
+ return AlreadyHashed::avoidDeletedValue(StringImpl::computeHash(url.string().characters() + hostStart, hostEnd - hostStart));
+}
+
+ApplicationCacheGroup* ApplicationCacheStorage::loadCacheGroup(const KURL& manifestURL)
+{
+ openDatabase(false);
+ if (!m_database.isOpen())
+ return 0;
+
+ SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL AND manifestURL=?");
+ if (statement.prepare() != SQLResultOk)
+ return 0;
+
+ statement.bindText(1, manifestURL);
+
+ int result = statement.step();
+ if (result == SQLResultDone)
+ return 0;
+
+ if (result != SQLResultRow) {
+ LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg());
+ return 0;
+ }
+
+ unsigned newestCacheStorageID = static_cast<unsigned>(statement.getColumnInt64(2));
+
+ RefPtr<ApplicationCache> cache = loadCache(newestCacheStorageID);
+ if (!cache)
+ return 0;
+
+ ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL);
+
+ group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0)));
+ group->setNewestCache(cache.release());
+
+ return group;
+}
+
+ApplicationCacheGroup* ApplicationCacheStorage::findOrCreateCacheGroup(const KURL& manifestURL)
+{
+ ASSERT(!manifestURL.hasFragmentIdentifier());
+
+ std::pair<CacheGroupMap::iterator, bool> result = m_cachesInMemory.add(manifestURL, 0);
+
+ if (!result.second) {
+ ASSERT(result.first->second);
+ return result.first->second;
+ }
+
+ // Look up the group in the database
+ ApplicationCacheGroup* group = loadCacheGroup(manifestURL);
+
+ // If the group was not found we need to create it
+ if (!group) {
+ group = new ApplicationCacheGroup(manifestURL);
+ m_cacheHostSet.add(urlHostHash(manifestURL));
+ }
+
+ result.first->second = group;
+
+ return group;
+}
+
+void ApplicationCacheStorage::loadManifestHostHashes()
+{
+ static bool hasLoadedHashes = false;
+
+ if (hasLoadedHashes)
+ return;
+
+ // We set this flag to true before the database has been opened
+ // to avoid trying to open the database over and over if it doesn't exist.
+ hasLoadedHashes = true;
+
+ openDatabase(false);
+ if (!m_database.isOpen())
+ return;
+
+ // Fetch the host hashes.
+ SQLiteStatement statement(m_database, "SELECT manifestHostHash FROM CacheGroups");
+ if (statement.prepare() != SQLResultOk)
+ return;
+
+ while (statement.step() == SQLResultRow)
+ m_cacheHostSet.add(static_cast<unsigned>(statement.getColumnInt64(0)));
+}
+
+ApplicationCacheGroup* ApplicationCacheStorage::cacheGroupForURL(const KURL& url)
+{
+ ASSERT(!url.hasFragmentIdentifier());
+
+ loadManifestHostHashes();
+
+ // Hash the host name and see if there's a manifest with the same host.
+ if (!m_cacheHostSet.contains(urlHostHash(url)))
+ return 0;
+
+ // Check if a cache already exists in memory.
+ CacheGroupMap::const_iterator end = m_cachesInMemory.end();
+ for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) {
+ ApplicationCacheGroup* group = it->second;
+
+ ASSERT(!group->isObsolete());
+
+ if (!protocolHostAndPortAreEqual(url, group->manifestURL()))
+ continue;
+
+ if (ApplicationCache* cache = group->newestCache()) {
+ ApplicationCacheResource* resource = cache->resourceForURL(url);
+ if (!resource)
+ continue;
+ if (resource->type() & ApplicationCacheResource::Foreign)
+ continue;
+ return group;
+ }
+ }
+
+ if (!m_database.isOpen())
+ return 0;
+
+ // Check the database. Look for all cache groups with a newest cache.
+ SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL");
+ if (statement.prepare() != SQLResultOk)
+ return 0;
+
+ int result;
+ while ((result = statement.step()) == SQLResultRow) {
+ KURL manifestURL = KURL(ParsedURLString, statement.getColumnText(1));
+
+ if (m_cachesInMemory.contains(manifestURL))
+ continue;
+
+ if (!protocolHostAndPortAreEqual(url, manifestURL))
+ continue;
+
+ // We found a cache group that matches. Now check if the newest cache has a resource with
+ // a matching URL.
+ unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2));
+ RefPtr<ApplicationCache> cache = loadCache(newestCacheID);
+ if (!cache)
+ continue;
+
+ ApplicationCacheResource* resource = cache->resourceForURL(url);
+ if (!resource)
+ continue;
+ if (resource->type() & ApplicationCacheResource::Foreign)
+ continue;
+
+ ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL);
+
+ group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0)));
+ group->setNewestCache(cache.release());
+
+ m_cachesInMemory.set(group->manifestURL(), group);
+
+ return group;
+ }
+
+ if (result != SQLResultDone)
+ LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg());
+
+ return 0;
+}
+
+ApplicationCacheGroup* ApplicationCacheStorage::fallbackCacheGroupForURL(const KURL& url)
+{
+ ASSERT(!url.hasFragmentIdentifier());
+
+ // Check if an appropriate cache already exists in memory.
+ CacheGroupMap::const_iterator end = m_cachesInMemory.end();
+ for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) {
+ ApplicationCacheGroup* group = it->second;
+
+ ASSERT(!group->isObsolete());
+
+ if (ApplicationCache* cache = group->newestCache()) {
+ KURL fallbackURL;
+ if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL))
+ continue;
+ if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign)
+ continue;
+ return group;
+ }
+ }
+
+ if (!m_database.isOpen())
+ return 0;
+
+ // Check the database. Look for all cache groups with a newest cache.
+ SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL");
+ if (statement.prepare() != SQLResultOk)
+ return 0;
+
+ int result;
+ while ((result = statement.step()) == SQLResultRow) {
+ KURL manifestURL = KURL(ParsedURLString, statement.getColumnText(1));
+
+ if (m_cachesInMemory.contains(manifestURL))
+ continue;
+
+ // Fallback namespaces always have the same origin as manifest URL, so we can avoid loading caches that cannot match.
+ if (!protocolHostAndPortAreEqual(url, manifestURL))
+ continue;
+
+ // We found a cache group that matches. Now check if the newest cache has a resource with
+ // a matching fallback namespace.
+ unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2));
+ RefPtr<ApplicationCache> cache = loadCache(newestCacheID);
+
+ KURL fallbackURL;
+ if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL))
+ continue;
+ if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign)
+ continue;
+
+ ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL);
+
+ group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0)));
+ group->setNewestCache(cache.release());
+
+ m_cachesInMemory.set(group->manifestURL(), group);
+
+ return group;
+ }
+
+ if (result != SQLResultDone)
+ LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg());
+
+ return 0;
+}
+
+void ApplicationCacheStorage::cacheGroupDestroyed(ApplicationCacheGroup* group)
+{
+ if (group->isObsolete()) {
+ ASSERT(!group->storageID());
+ ASSERT(m_cachesInMemory.get(group->manifestURL()) != group);
+ return;
+ }
+
+ ASSERT(m_cachesInMemory.get(group->manifestURL()) == group);
+
+ m_cachesInMemory.remove(group->manifestURL());
+
+ // If the cache group is half-created, we don't want it in the saved set (as it is not stored in database).
+ if (!group->storageID())
+ m_cacheHostSet.remove(urlHostHash(group->manifestURL()));
+}
+
+void ApplicationCacheStorage::cacheGroupMadeObsolete(ApplicationCacheGroup* group)
+{
+ ASSERT(m_cachesInMemory.get(group->manifestURL()) == group);
+ ASSERT(m_cacheHostSet.contains(urlHostHash(group->manifestURL())));
+
+ if (ApplicationCache* newestCache = group->newestCache())
+ remove(newestCache);
+
+ m_cachesInMemory.remove(group->manifestURL());
+ m_cacheHostSet.remove(urlHostHash(group->manifestURL()));
+}
+
+void ApplicationCacheStorage::setCacheDirectory(const String& cacheDirectory)
+{
+ ASSERT(m_cacheDirectory.isNull());
+ ASSERT(!cacheDirectory.isNull());
+
+ m_cacheDirectory = cacheDirectory;
+}
+
+const String& ApplicationCacheStorage::cacheDirectory() const
+{
+ return m_cacheDirectory;
+}
+
+void ApplicationCacheStorage::setMaximumSize(int64_t size)
+{
+ m_maximumSize = size;
+}
+
+int64_t ApplicationCacheStorage::maximumSize() const
+{
+ return m_maximumSize;
+}
+
+bool ApplicationCacheStorage::isMaximumSizeReached() const
+{
+ return m_isMaximumSizeReached;
+}
+
+int64_t ApplicationCacheStorage::spaceNeeded(int64_t cacheToSave)
+{
+ int64_t spaceNeeded = 0;
+ long long fileSize = 0;
+ if (!getFileSize(m_cacheFile, fileSize))
+ return 0;
+
+ int64_t currentSize = fileSize;
+
+ // Determine the amount of free space we have available.
+ int64_t totalAvailableSize = 0;
+ if (m_maximumSize < currentSize) {
+ // The max size is smaller than the actual size of the app cache file.
+ // This can happen if the client previously imposed a larger max size
+ // value and the app cache file has already grown beyond the current
+ // max size value.
+ // The amount of free space is just the amount of free space inside
+ // the database file. Note that this is always 0 if SQLite is compiled
+ // with AUTO_VACUUM = 1.
+ totalAvailableSize = m_database.freeSpaceSize();
+ } else {
+ // The max size is the same or larger than the current size.
+ // The amount of free space available is the amount of free space
+ // inside the database file plus the amount we can grow until we hit
+ // the max size.
+ totalAvailableSize = (m_maximumSize - currentSize) + m_database.freeSpaceSize();
+ }
+
+ // The space needed to be freed in order to accommodate the failed cache is
+ // the size of the failed cache minus any already available free space.
+ spaceNeeded = cacheToSave - totalAvailableSize;
+ // The space needed value must be positive (or else the total already
+ // available free space would be larger than the size of the failed cache and
+ // saving of the cache should have never failed).
+ ASSERT(spaceNeeded);
+ return spaceNeeded;
+}
+
+void ApplicationCacheStorage::setDefaultOriginQuota(int64_t quota)
+{
+ m_defaultOriginQuota = quota;
+}
+
+bool ApplicationCacheStorage::quotaForOrigin(const SecurityOrigin* origin, int64_t& quota)
+{
+ // If an Origin record doesn't exist, then the COUNT will be 0 and quota will be 0.
+ // Using the count to determine if a record existed or not is a safe way to determine
+ // if a quota of 0 is real, from the record, or from null.
+ SQLiteStatement statement(m_database, "SELECT COUNT(quota), quota FROM Origins WHERE origin=?");
+ if (statement.prepare() != SQLResultOk)
+ return false;
+
+ statement.bindText(1, origin->databaseIdentifier());
+ int result = statement.step();
+
+ // Return the quota, or if it was null the default.
+ if (result == SQLResultRow) {
+ bool wasNoRecord = statement.getColumnInt64(0) == 0;
+ quota = wasNoRecord ? m_defaultOriginQuota : statement.getColumnInt64(1);
+ return true;
+ }
+
+ LOG_ERROR("Could not get the quota of an origin, error \"%s\"", m_database.lastErrorMsg());
+ return false;
+}
+
+bool ApplicationCacheStorage::usageForOrigin(const SecurityOrigin* origin, int64_t& usage)
+{
+ // If an Origins record doesn't exist, then the SUM will be null,
+ // which will become 0, as expected, when converting to a number.
+ SQLiteStatement statement(m_database, "SELECT SUM(Caches.size)"
+ " FROM CacheGroups"
+ " INNER JOIN Origins ON CacheGroups.origin = Origins.origin"
+ " INNER JOIN Caches ON CacheGroups.id = Caches.cacheGroup"
+ " WHERE Origins.origin=?");
+ if (statement.prepare() != SQLResultOk)
+ return false;
+
+ statement.bindText(1, origin->databaseIdentifier());
+ int result = statement.step();
+
+ if (result == SQLResultRow) {
+ usage = statement.getColumnInt64(0);
+ return true;
+ }
+
+ LOG_ERROR("Could not get the quota of an origin, error \"%s\"", m_database.lastErrorMsg());
+ return false;
+}
+
+bool ApplicationCacheStorage::remainingSizeForOriginExcludingCache(const SecurityOrigin* origin, ApplicationCache* cache, int64_t& remainingSize)
+{
+ openDatabase(false);
+ if (!m_database.isOpen())
+ return false;
+
+ // Remaining size = total origin quota - size of all caches with origin excluding the provided cache.
+ // Keep track of the number of caches so we can tell if the result was a calculation or not.
+ const char* query;
+ int64_t excludingCacheIdentifier = cache ? cache->storageID() : 0;
+ if (excludingCacheIdentifier != 0) {
+ query = "SELECT COUNT(Caches.size), Origins.quota - SUM(Caches.size)"
+ " FROM CacheGroups"
+ " INNER JOIN Origins ON CacheGroups.origin = Origins.origin"
+ " INNER JOIN Caches ON CacheGroups.id = Caches.cacheGroup"
+ " WHERE Origins.origin=?"
+ " AND Caches.id!=?";
+ } else {
+ query = "SELECT COUNT(Caches.size), Origins.quota - SUM(Caches.size)"
+ " FROM CacheGroups"
+ " INNER JOIN Origins ON CacheGroups.origin = Origins.origin"
+ " INNER JOIN Caches ON CacheGroups.id = Caches.cacheGroup"
+ " WHERE Origins.origin=?";
+ }
+
+ SQLiteStatement statement(m_database, query);
+ if (statement.prepare() != SQLResultOk)
+ return false;
+
+ statement.bindText(1, origin->databaseIdentifier());
+ if (excludingCacheIdentifier != 0)
+ statement.bindInt64(2, excludingCacheIdentifier);
+ int result = statement.step();
+
+ // If the count was 0 that then we have to query the origin table directly
+ // for its quota. Otherwise we can use the calculated value.
+ if (result == SQLResultRow) {
+ int64_t numberOfCaches = statement.getColumnInt64(0);
+ if (numberOfCaches == 0)
+ quotaForOrigin(origin, remainingSize);
+ else
+ remainingSize = statement.getColumnInt64(1);
+ return true;
+ }
+
+ LOG_ERROR("Could not get the remaining size of an origin's quota, error \"%s\"", m_database.lastErrorMsg());
+ return false;
+}
+
+bool ApplicationCacheStorage::storeUpdatedQuotaForOrigin(const SecurityOrigin* origin, int64_t quota)
+{
+ openDatabase(true);
+ if (!m_database.isOpen())
+ return false;
+
+ if (!ensureOriginRecord(origin))
+ return false;
+
+ SQLiteStatement updateStatement(m_database, "UPDATE Origins SET quota=? WHERE origin=?");
+ if (updateStatement.prepare() != SQLResultOk)
+ return false;
+
+ updateStatement.bindInt64(1, quota);
+ updateStatement.bindText(2, origin->databaseIdentifier());
+
+ return executeStatement(updateStatement);
+}
+
+bool ApplicationCacheStorage::executeSQLCommand(const String& sql)
+{
+ ASSERT(m_database.isOpen());
+
+ bool result = m_database.executeCommand(sql);
+ if (!result)
+ LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"",
+ sql.utf8().data(), m_database.lastErrorMsg());
+
+ return result;
+}
+
+// Update the schemaVersion when the schema of any the Application Cache
+// SQLite tables changes. This allows the database to be rebuilt when
+// a new, incompatible change has been introduced to the database schema.
+static const int schemaVersion = 6;
+
+void ApplicationCacheStorage::verifySchemaVersion()
+{
+ int version = SQLiteStatement(m_database, "PRAGMA user_version").getColumnInt(0);
+ if (version == schemaVersion)
+ return;
+
+ m_database.clearAllTables();
+
+ // Update user version.
+ SQLiteTransaction setDatabaseVersion(m_database);
+ setDatabaseVersion.begin();
+
+ char userVersionSQL[32];
+ int unusedNumBytes = snprintf(userVersionSQL, sizeof(userVersionSQL), "PRAGMA user_version=%d", schemaVersion);
+ ASSERT_UNUSED(unusedNumBytes, static_cast<int>(sizeof(userVersionSQL)) >= unusedNumBytes);
+
+ SQLiteStatement statement(m_database, userVersionSQL);
+ if (statement.prepare() != SQLResultOk)
+ return;
+
+ executeStatement(statement);
+ setDatabaseVersion.commit();
+}
+
+void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist)
+{
+ if (m_database.isOpen())
+ return;
+
+ // The cache directory should never be null, but if it for some weird reason is we bail out.
+ if (m_cacheDirectory.isNull())
+ return;
+
+ m_cacheFile = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db");
+ if (!createIfDoesNotExist && !fileExists(m_cacheFile))
+ return;
+
+ makeAllDirectories(m_cacheDirectory);
+ m_database.open(m_cacheFile);
+
+ if (!m_database.isOpen())
+ return;
+
+ verifySchemaVersion();
+
+ // Create tables
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheGroups (id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "manifestHostHash INTEGER NOT NULL ON CONFLICT FAIL, manifestURL TEXT UNIQUE ON CONFLICT FAIL, newestCache INTEGER, origin TEXT)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS Caches (id INTEGER PRIMARY KEY AUTOINCREMENT, cacheGroup INTEGER, size INTEGER)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheWhitelistURLs (url TEXT NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheAllowsAllNetworkRequests (wildcard INTEGER NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS FallbackURLs (namespace TEXT NOT NULL ON CONFLICT FAIL, fallbackURL TEXT NOT NULL ON CONFLICT FAIL, "
+ "cache INTEGER NOT NULL ON CONFLICT FAIL)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheEntries (cache INTEGER NOT NULL ON CONFLICT FAIL, type INTEGER, resource INTEGER NOT NULL)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL ON CONFLICT FAIL, "
+ "statusCode INTEGER NOT NULL, responseURL TEXT NOT NULL, mimeType TEXT, textEncodingName TEXT, headers TEXT, data INTEGER NOT NULL ON CONFLICT FAIL)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResourceData (id INTEGER PRIMARY KEY AUTOINCREMENT, data BLOB)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS Origins (origin TEXT UNIQUE ON CONFLICT IGNORE, quota INTEGER NOT NULL ON CONFLICT FAIL)");
+
+ // When a cache is deleted, all its entries and its whitelist should be deleted.
+ executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheDeleted AFTER DELETE ON Caches"
+ " FOR EACH ROW BEGIN"
+ " DELETE FROM CacheEntries WHERE cache = OLD.id;"
+ " DELETE FROM CacheWhitelistURLs WHERE cache = OLD.id;"
+ " DELETE FROM CacheAllowsAllNetworkRequests WHERE cache = OLD.id;"
+ " DELETE FROM FallbackURLs WHERE cache = OLD.id;"
+ " END");
+
+ // When a cache entry is deleted, its resource should also be deleted.
+ executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheEntryDeleted AFTER DELETE ON CacheEntries"
+ " FOR EACH ROW BEGIN"
+ " DELETE FROM CacheResources WHERE id = OLD.resource;"
+ " END");
+
+ // When a cache resource is deleted, its data blob should also be deleted.
+ executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDeleted AFTER DELETE ON CacheResources"
+ " FOR EACH ROW BEGIN"
+ " DELETE FROM CacheResourceData WHERE id = OLD.data;"
+ " END");
+}
+
+bool ApplicationCacheStorage::executeStatement(SQLiteStatement& statement)
+{
+ bool result = statement.executeCommand();
+ if (!result)
+ LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"",
+ statement.query().utf8().data(), m_database.lastErrorMsg());
+
+ return result;
+}
+
+bool ApplicationCacheStorage::store(ApplicationCacheGroup* group, GroupStorageIDJournal* journal)
+{
+ ASSERT(group->storageID() == 0);
+ ASSERT(journal);
+
+ SQLiteStatement statement(m_database, "INSERT INTO CacheGroups (manifestHostHash, manifestURL, origin) VALUES (?, ?, ?)");
+ if (statement.prepare() != SQLResultOk)
+ return false;
+
+ statement.bindInt64(1, urlHostHash(group->manifestURL()));
+ statement.bindText(2, group->manifestURL());
+ statement.bindText(3, group->origin()->databaseIdentifier());
+
+ if (!executeStatement(statement))
+ return false;
+
+ unsigned groupStorageID = static_cast<unsigned>(m_database.lastInsertRowID());
+
+ if (!ensureOriginRecord(group->origin()))
+ return false;
+
+ group->setStorageID(groupStorageID);
+ journal->add(group, 0);
+ return true;
+}
+
+bool ApplicationCacheStorage::store(ApplicationCache* cache, ResourceStorageIDJournal* storageIDJournal)
+{
+ ASSERT(cache->storageID() == 0);
+ ASSERT(cache->group()->storageID() != 0);
+ ASSERT(storageIDJournal);
+
+ SQLiteStatement statement(m_database, "INSERT INTO Caches (cacheGroup, size) VALUES (?, ?)");
+ if (statement.prepare() != SQLResultOk)
+ return false;
+
+ statement.bindInt64(1, cache->group()->storageID());
+ statement.bindInt64(2, cache->estimatedSizeInStorage());
+
+ if (!executeStatement(statement))
+ return false;
+
+ unsigned cacheStorageID = static_cast<unsigned>(m_database.lastInsertRowID());
+
+ // Store all resources
+ {
+ ApplicationCache::ResourceMap::const_iterator end = cache->end();
+ for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) {
+ unsigned oldStorageID = it->second->storageID();
+ if (!store(it->second.get(), cacheStorageID))
+ return false;
+
+ // Storing the resource succeeded. Log its old storageID in case
+ // it needs to be restored later.
+ storageIDJournal->add(it->second.get(), oldStorageID);
+ }
+ }
+
+ // Store the online whitelist
+ const Vector<KURL>& onlineWhitelist = cache->onlineWhitelist();
+ {
+ size_t whitelistSize = onlineWhitelist.size();
+ for (size_t i = 0; i < whitelistSize; ++i) {
+ SQLiteStatement statement(m_database, "INSERT INTO CacheWhitelistURLs (url, cache) VALUES (?, ?)");
+ statement.prepare();
+
+ statement.bindText(1, onlineWhitelist[i]);
+ statement.bindInt64(2, cacheStorageID);
+
+ if (!executeStatement(statement))
+ return false;
+ }
+ }
+
+ // Store online whitelist wildcard flag.
+ {
+ SQLiteStatement statement(m_database, "INSERT INTO CacheAllowsAllNetworkRequests (wildcard, cache) VALUES (?, ?)");
+ statement.prepare();
+
+ statement.bindInt64(1, cache->allowsAllNetworkRequests());
+ statement.bindInt64(2, cacheStorageID);
+
+ if (!executeStatement(statement))
+ return false;
+ }
+
+ // Store fallback URLs.
+ const FallbackURLVector& fallbackURLs = cache->fallbackURLs();
+ {
+ size_t fallbackCount = fallbackURLs.size();
+ for (size_t i = 0; i < fallbackCount; ++i) {
+ SQLiteStatement statement(m_database, "INSERT INTO FallbackURLs (namespace, fallbackURL, cache) VALUES (?, ?, ?)");
+ statement.prepare();
+
+ statement.bindText(1, fallbackURLs[i].first);
+ statement.bindText(2, fallbackURLs[i].second);
+ statement.bindInt64(3, cacheStorageID);
+
+ if (!executeStatement(statement))
+ return false;
+ }
+ }
+
+ cache->setStorageID(cacheStorageID);
+ return true;
+}
+
+bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, unsigned cacheStorageID)
+{
+ ASSERT(cacheStorageID);
+ ASSERT(!resource->storageID());
+
+ openDatabase(true);
+
+ // openDatabase(true) could still fail, for example when cacheStorage is full or no longer available.
+ if (!m_database.isOpen())
+ return false;
+
+ // First, insert the data
+ SQLiteStatement dataStatement(m_database, "INSERT INTO CacheResourceData (data) VALUES (?)");
+ if (dataStatement.prepare() != SQLResultOk)
+ return false;
+
+ if (resource->data()->size())
+ dataStatement.bindBlob(1, resource->data()->data(), resource->data()->size());
+
+ if (!dataStatement.executeCommand())
+ return false;
+
+ unsigned dataId = static_cast<unsigned>(m_database.lastInsertRowID());
+
+ // Then, insert the resource
+
+ // Serialize the headers
+ Vector<UChar> stringBuilder;
+
+ HTTPHeaderMap::const_iterator end = resource->response().httpHeaderFields().end();
+ for (HTTPHeaderMap::const_iterator it = resource->response().httpHeaderFields().begin(); it!= end; ++it) {
+ stringBuilder.append(it->first.characters(), it->first.length());
+ stringBuilder.append((UChar)':');
+ stringBuilder.append(it->second.characters(), it->second.length());
+ stringBuilder.append((UChar)'\n');
+ }
+
+ String headers = String::adopt(stringBuilder);
+
+ SQLiteStatement resourceStatement(m_database, "INSERT INTO CacheResources (url, statusCode, responseURL, headers, data, mimeType, textEncodingName) VALUES (?, ?, ?, ?, ?, ?, ?)");
+ if (resourceStatement.prepare() != SQLResultOk)
+ return false;
+
+ // The same ApplicationCacheResource are used in ApplicationCacheResource::size()
+ // to calculate the approximate size of an ApplicationCacheResource object. If
+ // you change the code below, please also change ApplicationCacheResource::size().
+ resourceStatement.bindText(1, resource->url());
+ resourceStatement.bindInt64(2, resource->response().httpStatusCode());
+ resourceStatement.bindText(3, resource->response().url());
+ resourceStatement.bindText(4, headers);
+ resourceStatement.bindInt64(5, dataId);
+ resourceStatement.bindText(6, resource->response().mimeType());
+ resourceStatement.bindText(7, resource->response().textEncodingName());
+
+ if (!executeStatement(resourceStatement))
+ return false;
+
+ unsigned resourceId = static_cast<unsigned>(m_database.lastInsertRowID());
+
+ // Finally, insert the cache entry
+ SQLiteStatement entryStatement(m_database, "INSERT INTO CacheEntries (cache, type, resource) VALUES (?, ?, ?)");
+ if (entryStatement.prepare() != SQLResultOk)
+ return false;
+
+ entryStatement.bindInt64(1, cacheStorageID);
+ entryStatement.bindInt64(2, resource->type());
+ entryStatement.bindInt64(3, resourceId);
+
+ if (!executeStatement(entryStatement))
+ return false;
+
+ resource->setStorageID(resourceId);
+ return true;
+}
+
+bool ApplicationCacheStorage::storeUpdatedType(ApplicationCacheResource* resource, ApplicationCache* cache)
+{
+ ASSERT_UNUSED(cache, cache->storageID());
+ ASSERT(resource->storageID());
+
+ // First, insert the data
+ SQLiteStatement entryStatement(m_database, "UPDATE CacheEntries SET type=? WHERE resource=?");
+ if (entryStatement.prepare() != SQLResultOk)
+ return false;
+
+ entryStatement.bindInt64(1, resource->type());
+ entryStatement.bindInt64(2, resource->storageID());
+
+ return executeStatement(entryStatement);
+}
+
+bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache)
+{
+ ASSERT(cache->storageID());
+
+ openDatabase(true);
+
+ if (!m_database.isOpen())
+ return false;
+
+ m_isMaximumSizeReached = false;
+ m_database.setMaximumSize(m_maximumSize);
+
+ SQLiteTransaction storeResourceTransaction(m_database);
+ storeResourceTransaction.begin();
+
+ if (!store(resource, cache->storageID())) {
+ checkForMaxSizeReached();
+ return false;
+ }
+
+ // A resource was added to the cache. Update the total data size for the cache.
+ SQLiteStatement sizeUpdateStatement(m_database, "UPDATE Caches SET size=size+? WHERE id=?");
+ if (sizeUpdateStatement.prepare() != SQLResultOk)
+ return false;
+
+ sizeUpdateStatement.bindInt64(1, resource->estimatedSizeInStorage());
+ sizeUpdateStatement.bindInt64(2, cache->storageID());
+
+ if (!executeStatement(sizeUpdateStatement))
+ return false;
+
+ storeResourceTransaction.commit();
+ return true;
+}
+
+bool ApplicationCacheStorage::ensureOriginRecord(const SecurityOrigin* origin)
+{
+ SQLiteStatement insertOriginStatement(m_database, "INSERT INTO Origins (origin, quota) VALUES (?, ?)");
+ if (insertOriginStatement.prepare() != SQLResultOk)
+ return false;
+
+ insertOriginStatement.bindText(1, origin->databaseIdentifier());
+ insertOriginStatement.bindInt64(2, m_defaultOriginQuota);
+ if (!executeStatement(insertOriginStatement))
+ return false;
+
+ return true;
+}
+
+bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group, ApplicationCache* oldCache, FailureReason& failureReason)
+{
+ openDatabase(true);
+
+ if (!m_database.isOpen())
+ return false;
+
+ m_isMaximumSizeReached = false;
+ m_database.setMaximumSize(m_maximumSize);
+
+ SQLiteTransaction storeCacheTransaction(m_database);
+
+ storeCacheTransaction.begin();
+
+ // Check if this would reach the per-origin quota.
+ int64_t remainingSpaceInOrigin;
+ if (remainingSizeForOriginExcludingCache(group->origin(), oldCache, remainingSpaceInOrigin)) {
+ if (remainingSpaceInOrigin < group->newestCache()->estimatedSizeInStorage()) {
+ failureReason = OriginQuotaReached;
+ return false;
+ }
+ }
+
+ GroupStorageIDJournal groupStorageIDJournal;
+ if (!group->storageID()) {
+ // Store the group
+ if (!store(group, &groupStorageIDJournal)) {
+ checkForMaxSizeReached();
+ failureReason = isMaximumSizeReached() ? TotalQuotaReached : DiskOrOperationFailure;
+ return false;
+ }
+ }
+
+ ASSERT(group->newestCache());
+ ASSERT(!group->isObsolete());
+ ASSERT(!group->newestCache()->storageID());
+
+ // Log the storageID changes to the in-memory resource objects. The journal
+ // object will roll them back automatically in case a database operation
+ // fails and this method returns early.
+ ResourceStorageIDJournal resourceStorageIDJournal;
+
+ // Store the newest cache
+ if (!store(group->newestCache(), &resourceStorageIDJournal)) {
+ checkForMaxSizeReached();
+ failureReason = isMaximumSizeReached() ? TotalQuotaReached : DiskOrOperationFailure;
+ return false;
+ }
+
+ // Update the newest cache in the group.
+
+ SQLiteStatement statement(m_database, "UPDATE CacheGroups SET newestCache=? WHERE id=?");
+ if (statement.prepare() != SQLResultOk) {
+ failureReason = DiskOrOperationFailure;
+ return false;
+ }
+
+ statement.bindInt64(1, group->newestCache()->storageID());
+ statement.bindInt64(2, group->storageID());
+
+ if (!executeStatement(statement)) {
+ failureReason = DiskOrOperationFailure;
+ return false;
+ }
+
+ groupStorageIDJournal.commit();
+ resourceStorageIDJournal.commit();
+ storeCacheTransaction.commit();
+ return true;
+}
+
+bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group)
+{
+ // Ignore the reason for failing, just attempt the store.
+ FailureReason ignoredFailureReason;
+ return storeNewestCache(group, 0, ignoredFailureReason);
+}
+
+static inline void parseHeader(const UChar* header, size_t headerLength, ResourceResponse& response)
+{
+ size_t pos = find(header, headerLength, ':');
+ ASSERT(pos != notFound);
+
+ AtomicString headerName = AtomicString(header, pos);
+ String headerValue = String(header + pos + 1, headerLength - pos - 1);
+
+ response.setHTTPHeaderField(headerName, headerValue);
+}
+
+static inline void parseHeaders(const String& headers, ResourceResponse& response)
+{
+ unsigned startPos = 0;
+ size_t endPos;
+ while ((endPos = headers.find('\n', startPos)) != notFound) {
+ ASSERT(startPos != endPos);
+
+ parseHeader(headers.characters() + startPos, endPos - startPos, response);
+
+ startPos = endPos + 1;
+ }
+
+ if (startPos != headers.length())
+ parseHeader(headers.characters(), headers.length(), response);
+}
+
+PassRefPtr<ApplicationCache> ApplicationCacheStorage::loadCache(unsigned storageID)
+{
+ SQLiteStatement cacheStatement(m_database,
+ "SELECT url, type, mimeType, textEncodingName, headers, CacheResourceData.data FROM CacheEntries INNER JOIN CacheResources ON CacheEntries.resource=CacheResources.id "
+ "INNER JOIN CacheResourceData ON CacheResourceData.id=CacheResources.data WHERE CacheEntries.cache=?");
+ if (cacheStatement.prepare() != SQLResultOk) {
+ LOG_ERROR("Could not prepare cache statement, error \"%s\"", m_database.lastErrorMsg());
+ return 0;
+ }
+
+ cacheStatement.bindInt64(1, storageID);
+
+ RefPtr<ApplicationCache> cache = ApplicationCache::create();
+
+ int result;
+ while ((result = cacheStatement.step()) == SQLResultRow) {
+ KURL url(ParsedURLString, cacheStatement.getColumnText(0));
+
+ unsigned type = static_cast<unsigned>(cacheStatement.getColumnInt64(1));
+
+ Vector<char> blob;
+ cacheStatement.getColumnBlobAsVector(5, blob);
+
+ RefPtr<SharedBuffer> data = SharedBuffer::adoptVector(blob);
+
+ String mimeType = cacheStatement.getColumnText(2);
+ String textEncodingName = cacheStatement.getColumnText(3);
+
+ ResourceResponse response(url, mimeType, data->size(), textEncodingName, "");
+
+ String headers = cacheStatement.getColumnText(4);
+ parseHeaders(headers, response);
+
+ RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, response, type, data.release());
+
+ if (type & ApplicationCacheResource::Manifest)
+ cache->setManifestResource(resource.release());
+ else
+ cache->addResource(resource.release());
+ }
+
+ if (result != SQLResultDone)
+ LOG_ERROR("Could not load cache resources, error \"%s\"", m_database.lastErrorMsg());
+
+ // Load the online whitelist
+ SQLiteStatement whitelistStatement(m_database, "SELECT url FROM CacheWhitelistURLs WHERE cache=?");
+ if (whitelistStatement.prepare() != SQLResultOk)
+ return 0;
+ whitelistStatement.bindInt64(1, storageID);
+
+ Vector<KURL> whitelist;
+ while ((result = whitelistStatement.step()) == SQLResultRow)
+ whitelist.append(KURL(ParsedURLString, whitelistStatement.getColumnText(0)));
+
+ if (result != SQLResultDone)
+ LOG_ERROR("Could not load cache online whitelist, error \"%s\"", m_database.lastErrorMsg());
+
+ cache->setOnlineWhitelist(whitelist);
+
+ // Load online whitelist wildcard flag.
+ SQLiteStatement whitelistWildcardStatement(m_database, "SELECT wildcard FROM CacheAllowsAllNetworkRequests WHERE cache=?");
+ if (whitelistWildcardStatement.prepare() != SQLResultOk)
+ return 0;
+ whitelistWildcardStatement.bindInt64(1, storageID);
+
+ result = whitelistWildcardStatement.step();
+ if (result != SQLResultRow)
+ LOG_ERROR("Could not load cache online whitelist wildcard flag, error \"%s\"", m_database.lastErrorMsg());
+
+ cache->setAllowsAllNetworkRequests(whitelistWildcardStatement.getColumnInt64(0));
+
+ if (whitelistWildcardStatement.step() != SQLResultDone)
+ LOG_ERROR("Too many rows for online whitelist wildcard flag");
+
+ // Load fallback URLs.
+ SQLiteStatement fallbackStatement(m_database, "SELECT namespace, fallbackURL FROM FallbackURLs WHERE cache=?");
+ if (fallbackStatement.prepare() != SQLResultOk)
+ return 0;
+ fallbackStatement.bindInt64(1, storageID);
+
+ FallbackURLVector fallbackURLs;
+ while ((result = fallbackStatement.step()) == SQLResultRow)
+ fallbackURLs.append(make_pair(KURL(ParsedURLString, fallbackStatement.getColumnText(0)), KURL(ParsedURLString, fallbackStatement.getColumnText(1))));
+
+ if (result != SQLResultDone)
+ LOG_ERROR("Could not load fallback URLs, error \"%s\"", m_database.lastErrorMsg());
+
+ cache->setFallbackURLs(fallbackURLs);
+
+ cache->setStorageID(storageID);
+
+ return cache.release();
+}
+
+void ApplicationCacheStorage::remove(ApplicationCache* cache)
+{
+ if (!cache->storageID())
+ return;
+
+ openDatabase(false);
+ if (!m_database.isOpen())
+ return;
+
+ ASSERT(cache->group());
+ ASSERT(cache->group()->storageID());
+
+ // All associated data will be deleted by database triggers.
+ SQLiteStatement statement(m_database, "DELETE FROM Caches WHERE id=?");
+ if (statement.prepare() != SQLResultOk)
+ return;
+
+ statement.bindInt64(1, cache->storageID());
+ executeStatement(statement);
+
+ cache->clearStorageID();
+
+ if (cache->group()->newestCache() == cache) {
+ // Currently, there are no triggers on the cache group, which is why the cache had to be removed separately above.
+ SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?");
+ if (groupStatement.prepare() != SQLResultOk)
+ return;
+
+ groupStatement.bindInt64(1, cache->group()->storageID());
+ executeStatement(groupStatement);
+
+ cache->group()->clearStorageID();
+ }
+}
+
+void ApplicationCacheStorage::empty()
+{
+ openDatabase(false);
+
+ if (!m_database.isOpen())
+ return;
+
+ // Clear cache groups, caches, cache resources, and origins.
+ executeSQLCommand("DELETE FROM CacheGroups");
+ executeSQLCommand("DELETE FROM Caches");
+ executeSQLCommand("DELETE FROM Origins");
+
+ // Clear the storage IDs for the caches in memory.
+ // The caches will still work, but cached resources will not be saved to disk
+ // until a cache update process has been initiated.
+ CacheGroupMap::const_iterator end = m_cachesInMemory.end();
+ for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it)
+ it->second->clearStorageID();
+}
+
+bool ApplicationCacheStorage::storeCopyOfCache(const String& cacheDirectory, ApplicationCacheHost* cacheHost)
+{
+ ApplicationCache* cache = cacheHost->applicationCache();
+ if (!cache)
+ return true;
+
+ // Create a new cache.
+ RefPtr<ApplicationCache> cacheCopy = ApplicationCache::create();
+
+ cacheCopy->setOnlineWhitelist(cache->onlineWhitelist());
+ cacheCopy->setFallbackURLs(cache->fallbackURLs());
+
+ // Traverse the cache and add copies of all resources.
+ ApplicationCache::ResourceMap::const_iterator end = cache->end();
+ for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) {
+ ApplicationCacheResource* resource = it->second.get();
+
+ RefPtr<ApplicationCacheResource> resourceCopy = ApplicationCacheResource::create(resource->url(), resource->response(), resource->type(), resource->data());
+
+ cacheCopy->addResource(resourceCopy.release());
+ }
+
+ // Now create a new cache group.
+ OwnPtr<ApplicationCacheGroup> groupCopy(adoptPtr(new ApplicationCacheGroup(cache->group()->manifestURL(), true)));
+
+ groupCopy->setNewestCache(cacheCopy);
+
+ ApplicationCacheStorage copyStorage;
+ copyStorage.setCacheDirectory(cacheDirectory);
+
+ // Empty the cache in case something was there before.
+ copyStorage.empty();
+
+ return copyStorage.storeNewestCache(groupCopy.get());
+}
+
+bool ApplicationCacheStorage::manifestURLs(Vector<KURL>* urls)
+{
+ ASSERT(urls);
+ openDatabase(false);
+ if (!m_database.isOpen())
+ return false;
+
+ SQLiteStatement selectURLs(m_database, "SELECT manifestURL FROM CacheGroups");
+
+ if (selectURLs.prepare() != SQLResultOk)
+ return false;
+
+ while (selectURLs.step() == SQLResultRow)
+ urls->append(KURL(ParsedURLString, selectURLs.getColumnText(0)));
+
+ return true;
+}
+
+bool ApplicationCacheStorage::cacheGroupSize(const String& manifestURL, int64_t* size)
+{
+ ASSERT(size);
+ openDatabase(false);
+ if (!m_database.isOpen())
+ return false;
+
+ SQLiteStatement statement(m_database, "SELECT sum(Caches.size) FROM Caches INNER JOIN CacheGroups ON Caches.cacheGroup=CacheGroups.id WHERE CacheGroups.manifestURL=?");
+ if (statement.prepare() != SQLResultOk)
+ return false;
+
+ statement.bindText(1, manifestURL);
+
+ int result = statement.step();
+ if (result == SQLResultDone)
+ return false;
+
+ if (result != SQLResultRow) {
+ LOG_ERROR("Could not get the size of the cache group, error \"%s\"", m_database.lastErrorMsg());
+ return false;
+ }
+
+ *size = statement.getColumnInt64(0);
+ return true;
+}
+
+bool ApplicationCacheStorage::deleteCacheGroup(const String& manifestURL)
+{
+ SQLiteTransaction deleteTransaction(m_database);
+ // Check to see if the group is in memory.
+ ApplicationCacheGroup* group = m_cachesInMemory.get(manifestURL);
+ if (group)
+ cacheGroupMadeObsolete(group);
+ else {
+ // The cache group is not in memory, so remove it from the disk.
+ openDatabase(false);
+ if (!m_database.isOpen())
+ return false;
+
+ SQLiteStatement idStatement(m_database, "SELECT id FROM CacheGroups WHERE manifestURL=?");
+ if (idStatement.prepare() != SQLResultOk)
+ return false;
+
+ idStatement.bindText(1, manifestURL);
+
+ int result = idStatement.step();
+ if (result == SQLResultDone)
+ return false;
+
+ if (result != SQLResultRow) {
+ LOG_ERROR("Could not load cache group id, error \"%s\"", m_database.lastErrorMsg());
+ return false;
+ }
+
+ int64_t groupId = idStatement.getColumnInt64(0);
+
+ SQLiteStatement cacheStatement(m_database, "DELETE FROM Caches WHERE cacheGroup=?");
+ if (cacheStatement.prepare() != SQLResultOk)
+ return false;
+
+ SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?");
+ if (groupStatement.prepare() != SQLResultOk)
+ return false;
+
+ cacheStatement.bindInt64(1, groupId);
+ executeStatement(cacheStatement);
+ groupStatement.bindInt64(1, groupId);
+ executeStatement(groupStatement);
+ }
+
+ deleteTransaction.commit();
+ return true;
+}
+
+void ApplicationCacheStorage::vacuumDatabaseFile()
+{
+ openDatabase(false);
+ if (!m_database.isOpen())
+ return;
+
+ m_database.runVacuumCommand();
+}
+
+void ApplicationCacheStorage::checkForMaxSizeReached()
+{
+ if (m_database.lastError() == SQLResultFull)
+ m_isMaximumSizeReached = true;
+}
+
+ApplicationCacheStorage::ApplicationCacheStorage()
+ : m_maximumSize(ApplicationCacheStorage::noQuota())
+ , m_isMaximumSizeReached(false)
+ , m_defaultOriginQuota(ApplicationCacheStorage::noQuota())
+{
+}
+
+ApplicationCacheStorage& cacheStorage()
+{
+ DEFINE_STATIC_LOCAL(ApplicationCacheStorage, storage, ());
+
+ return storage;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
diff --git a/Source/WebCore/loader/appcache/ApplicationCacheStorage.h b/Source/WebCore/loader/appcache/ApplicationCacheStorage.h
new file mode 100644
index 0000000..7db34e6
--- /dev/null
+++ b/Source/WebCore/loader/appcache/ApplicationCacheStorage.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2008, 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.
+ */
+
+#ifndef ApplicationCacheStorage_h
+#define ApplicationCacheStorage_h
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "PlatformString.h"
+#include "SQLiteDatabase.h"
+#include <wtf/HashCountedSet.h>
+#include <wtf/text/StringHash.h>
+
+namespace WebCore {
+
+class ApplicationCache;
+class ApplicationCacheGroup;
+class ApplicationCacheHost;
+class ApplicationCacheResource;
+class KURL;
+template <class T>
+class StorageIDJournal;
+class SecurityOrigin;
+
+class ApplicationCacheStorage : public Noncopyable {
+public:
+ enum FailureReason {
+ OriginQuotaReached,
+ TotalQuotaReached,
+ DiskOrOperationFailure
+ };
+
+ void setCacheDirectory(const String&);
+ const String& cacheDirectory() const;
+
+ void setMaximumSize(int64_t size);
+ int64_t maximumSize() const;
+ bool isMaximumSizeReached() const;
+ int64_t spaceNeeded(int64_t cacheToSave);
+
+ int64_t defaultOriginQuota() const { return m_defaultOriginQuota; }
+ void setDefaultOriginQuota(int64_t quota);
+ bool usageForOrigin(const SecurityOrigin*, int64_t& usage);
+ bool quotaForOrigin(const SecurityOrigin*, int64_t& quota);
+ bool remainingSizeForOriginExcludingCache(const SecurityOrigin*, ApplicationCache*, int64_t& remainingSize);
+ bool storeUpdatedQuotaForOrigin(const SecurityOrigin*, int64_t quota);
+
+ ApplicationCacheGroup* cacheGroupForURL(const KURL&); // Cache to load a main resource from.
+ ApplicationCacheGroup* fallbackCacheGroupForURL(const KURL&); // Cache that has a fallback entry to load a main resource from if normal loading fails.
+
+ ApplicationCacheGroup* findOrCreateCacheGroup(const KURL& manifestURL);
+ void cacheGroupDestroyed(ApplicationCacheGroup*);
+ void cacheGroupMadeObsolete(ApplicationCacheGroup*);
+
+ bool storeNewestCache(ApplicationCacheGroup*, ApplicationCache* oldCache, FailureReason& failureReason);
+ bool storeNewestCache(ApplicationCacheGroup*); // Updates the cache group, but doesn't remove old cache.
+ bool store(ApplicationCacheResource*, ApplicationCache*);
+ bool storeUpdatedType(ApplicationCacheResource*, ApplicationCache*);
+
+ // Removes the group if the cache to be removed is the newest one (so, storeNewestCache() needs to be called beforehand when updating).
+ void remove(ApplicationCache*);
+
+ void empty();
+
+ static bool storeCopyOfCache(const String& cacheDirectory, ApplicationCacheHost*);
+
+ bool manifestURLs(Vector<KURL>* urls);
+ bool cacheGroupSize(const String& manifestURL, int64_t* size);
+ bool deleteCacheGroup(const String& manifestURL);
+ void vacuumDatabaseFile();
+
+ static int64_t unknownQuota() { return -1; }
+ static int64_t noQuota() { return std::numeric_limits<int64_t>::max(); }
+private:
+ ApplicationCacheStorage();
+ PassRefPtr<ApplicationCache> loadCache(unsigned storageID);
+ ApplicationCacheGroup* loadCacheGroup(const KURL& manifestURL);
+
+ typedef StorageIDJournal<ApplicationCacheResource> ResourceStorageIDJournal;
+ typedef StorageIDJournal<ApplicationCacheGroup> GroupStorageIDJournal;
+
+ bool store(ApplicationCacheGroup*, GroupStorageIDJournal*);
+ bool store(ApplicationCache*, ResourceStorageIDJournal*);
+ bool store(ApplicationCacheResource*, unsigned cacheStorageID);
+
+ bool ensureOriginRecord(const SecurityOrigin*);
+
+ void loadManifestHostHashes();
+
+ void verifySchemaVersion();
+
+ void openDatabase(bool createIfDoesNotExist);
+
+ bool executeStatement(SQLiteStatement&);
+ bool executeSQLCommand(const String&);
+
+ void checkForMaxSizeReached();
+
+ String m_cacheDirectory;
+ String m_cacheFile;
+
+ int64_t m_maximumSize;
+ bool m_isMaximumSizeReached;
+
+ int64_t m_defaultOriginQuota;
+
+ SQLiteDatabase m_database;
+
+ // In order to quickly determine if a given resource exists in an application cache,
+ // we keep a hash set of the hosts of the manifest URLs of all non-obsolete cache groups.
+ HashCountedSet<unsigned, AlreadyHashed> m_cacheHostSet;
+
+ typedef HashMap<String, ApplicationCacheGroup*> CacheGroupMap;
+ CacheGroupMap m_cachesInMemory; // Excludes obsolete cache groups.
+
+ friend ApplicationCacheStorage& cacheStorage();
+};
+
+ApplicationCacheStorage& cacheStorage();
+
+} // namespace WebCore
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#endif // ApplicationCacheStorage_h
diff --git a/Source/WebCore/loader/appcache/DOMApplicationCache.cpp b/Source/WebCore/loader/appcache/DOMApplicationCache.cpp
new file mode 100644
index 0000000..b9297b1
--- /dev/null
+++ b/Source/WebCore/loader/appcache/DOMApplicationCache.cpp
@@ -0,0 +1,130 @@
+/*
+ * 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
+ * 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 "DOMApplicationCache.h"
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "ApplicationCacheHost.h"
+#include "DocumentLoader.h"
+#include "Event.h"
+#include "EventException.h"
+#include "EventListener.h"
+#include "EventNames.h"
+#include "Frame.h"
+#include "FrameLoader.h"
+
+namespace WebCore {
+
+DOMApplicationCache::DOMApplicationCache(Frame* frame)
+ : m_frame(frame)
+{
+ ApplicationCacheHost* cacheHost = applicationCacheHost();
+ if (cacheHost)
+ cacheHost->setDOMApplicationCache(this);
+}
+
+void DOMApplicationCache::disconnectFrame()
+{
+ ApplicationCacheHost* cacheHost = applicationCacheHost();
+ if (cacheHost)
+ cacheHost->setDOMApplicationCache(0);
+ m_frame = 0;
+}
+
+ApplicationCacheHost* DOMApplicationCache::applicationCacheHost() const
+{
+ if (!m_frame || !m_frame->loader()->documentLoader())
+ return 0;
+ return m_frame->loader()->documentLoader()->applicationCacheHost();
+}
+
+unsigned short DOMApplicationCache::status() const
+{
+ ApplicationCacheHost* cacheHost = applicationCacheHost();
+ if (!cacheHost)
+ return ApplicationCacheHost::UNCACHED;
+ return cacheHost->status();
+}
+
+void DOMApplicationCache::update(ExceptionCode& ec)
+{
+ ApplicationCacheHost* cacheHost = applicationCacheHost();
+ if (!cacheHost || !cacheHost->update())
+ ec = INVALID_STATE_ERR;
+}
+
+void DOMApplicationCache::swapCache(ExceptionCode& ec)
+{
+ ApplicationCacheHost* cacheHost = applicationCacheHost();
+ if (!cacheHost || !cacheHost->swapCache())
+ ec = INVALID_STATE_ERR;
+}
+
+ScriptExecutionContext* DOMApplicationCache::scriptExecutionContext() const
+{
+ if (m_frame)
+ return m_frame->document();
+ return 0;
+}
+
+const AtomicString& DOMApplicationCache::toEventType(ApplicationCacheHost::EventID id)
+{
+ switch (id) {
+ case ApplicationCacheHost::CHECKING_EVENT:
+ return eventNames().checkingEvent;
+ case ApplicationCacheHost::ERROR_EVENT:
+ return eventNames().errorEvent;
+ case ApplicationCacheHost::NOUPDATE_EVENT:
+ return eventNames().noupdateEvent;
+ case ApplicationCacheHost::DOWNLOADING_EVENT:
+ return eventNames().downloadingEvent;
+ case ApplicationCacheHost::PROGRESS_EVENT:
+ return eventNames().progressEvent;
+ case ApplicationCacheHost::UPDATEREADY_EVENT:
+ return eventNames().updatereadyEvent;
+ case ApplicationCacheHost::CACHED_EVENT:
+ return eventNames().cachedEvent;
+ case ApplicationCacheHost::OBSOLETE_EVENT:
+ return eventNames().obsoleteEvent;
+ }
+ ASSERT_NOT_REACHED();
+ return eventNames().errorEvent;
+}
+
+EventTargetData* DOMApplicationCache::eventTargetData()
+{
+ return &m_eventTargetData;
+}
+
+EventTargetData* DOMApplicationCache::ensureEventTargetData()
+{
+ return &m_eventTargetData;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
diff --git a/Source/WebCore/loader/appcache/DOMApplicationCache.h b/Source/WebCore/loader/appcache/DOMApplicationCache.h
new file mode 100644
index 0000000..2a806fa
--- /dev/null
+++ b/Source/WebCore/loader/appcache/DOMApplicationCache.h
@@ -0,0 +1,98 @@
+/*
+ * 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
+ * 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.
+ */
+
+#ifndef DOMApplicationCache_h
+#define DOMApplicationCache_h
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "ApplicationCacheHost.h"
+#include "EventListener.h"
+#include "EventNames.h"
+#include "EventTarget.h"
+#include <wtf/Forward.h>
+#include <wtf/HashMap.h>
+#include <wtf/PassRefPtr.h>
+#include <wtf/RefCounted.h>
+#include <wtf/Vector.h>
+#include <wtf/text/AtomicStringHash.h>
+
+namespace WebCore {
+
+class Frame;
+class KURL;
+
+class DOMApplicationCache : public RefCounted<DOMApplicationCache>, public EventTarget {
+public:
+ static PassRefPtr<DOMApplicationCache> create(Frame* frame) { return adoptRef(new DOMApplicationCache(frame)); }
+ ~DOMApplicationCache() { ASSERT(!m_frame); }
+
+ Frame* frame() const { return m_frame; }
+ void disconnectFrame();
+
+ unsigned short status() const;
+ void update(ExceptionCode&);
+ void swapCache(ExceptionCode&);
+
+ // EventTarget impl
+
+ using RefCounted<DOMApplicationCache>::ref;
+ using RefCounted<DOMApplicationCache>::deref;
+
+ // Explicitly named attribute event listener helpers
+
+ DEFINE_ATTRIBUTE_EVENT_LISTENER(checking);
+ DEFINE_ATTRIBUTE_EVENT_LISTENER(error);
+ DEFINE_ATTRIBUTE_EVENT_LISTENER(noupdate);
+ DEFINE_ATTRIBUTE_EVENT_LISTENER(downloading);
+ DEFINE_ATTRIBUTE_EVENT_LISTENER(progress);
+ DEFINE_ATTRIBUTE_EVENT_LISTENER(updateready);
+ DEFINE_ATTRIBUTE_EVENT_LISTENER(cached);
+ DEFINE_ATTRIBUTE_EVENT_LISTENER(obsolete);
+
+ virtual ScriptExecutionContext* scriptExecutionContext() const;
+ DOMApplicationCache* toDOMApplicationCache() { return this; }
+
+ static const AtomicString& toEventType(ApplicationCacheHost::EventID id);
+
+private:
+ DOMApplicationCache(Frame*);
+
+ virtual void refEventTarget() { ref(); }
+ virtual void derefEventTarget() { deref(); }
+ virtual EventTargetData* eventTargetData();
+ virtual EventTargetData* ensureEventTargetData();
+
+ ApplicationCacheHost* applicationCacheHost() const;
+
+ Frame* m_frame;
+ EventTargetData m_eventTargetData;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#endif // DOMApplicationCache_h
diff --git a/Source/WebCore/loader/appcache/DOMApplicationCache.idl b/Source/WebCore/loader/appcache/DOMApplicationCache.idl
new file mode 100644
index 0000000..9113ffa
--- /dev/null
+++ b/Source/WebCore/loader/appcache/DOMApplicationCache.idl
@@ -0,0 +1,69 @@
+/*
+ * 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
+ * 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.
+ */
+
+module offline {
+
+ interface [
+ Conditional=OFFLINE_WEB_APPLICATIONS,
+ EventTarget,
+ OmitConstructor,
+ DontCheckEnums
+ ] DOMApplicationCache {
+ // update status
+ const unsigned short UNCACHED = 0;
+ const unsigned short IDLE = 1;
+ const unsigned short CHECKING = 2;
+ const unsigned short DOWNLOADING = 3;
+ const unsigned short UPDATEREADY = 4;
+ const unsigned short OBSOLETE = 5;
+ readonly attribute unsigned short status;
+
+ void update()
+ raises(DOMException);
+ void swapCache()
+ raises(DOMException);
+
+ // events
+ attribute EventListener onchecking;
+ attribute EventListener onerror;
+ attribute EventListener onnoupdate;
+ attribute EventListener ondownloading;
+ attribute EventListener onprogress;
+ attribute EventListener onupdateready;
+ attribute EventListener oncached;
+ attribute EventListener onobsolete;
+
+ // EventTarget interface
+ void addEventListener(in DOMString type,
+ in EventListener listener,
+ in boolean useCapture);
+ void removeEventListener(in DOMString type,
+ in EventListener listener,
+ in boolean useCapture);
+ boolean dispatchEvent(in Event evt)
+ raises(EventException);
+ };
+
+}
diff --git a/Source/WebCore/loader/appcache/ManifestParser.cpp b/Source/WebCore/loader/appcache/ManifestParser.cpp
new file mode 100644
index 0000000..f58a55d
--- /dev/null
+++ b/Source/WebCore/loader/appcache/ManifestParser.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2008 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 "ManifestParser.h"
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "CharacterNames.h"
+#include "KURL.h"
+#include "TextResourceDecoder.h"
+
+using namespace std;
+
+namespace WebCore {
+
+enum Mode { Explicit, Fallback, OnlineWhitelist, Unknown };
+
+bool parseManifest(const KURL& manifestURL, const char* data, int length, Manifest& manifest)
+{
+ ASSERT(manifest.explicitURLs.isEmpty());
+ ASSERT(manifest.onlineWhitelistedURLs.isEmpty());
+ ASSERT(manifest.fallbackURLs.isEmpty());
+ manifest.allowAllNetworkRequests = false;
+
+ Mode mode = Explicit;
+
+ RefPtr<TextResourceDecoder> decoder = TextResourceDecoder::create("text/cache-manifest", "UTF-8");
+ String s = decoder->decode(data, length);
+ s += decoder->flush();
+
+ // Look for the magic signature: "^\xFEFF?CACHE MANIFEST[ \t]?" (the BOM is removed by TextResourceDecoder).
+ // Example: "CACHE MANIFEST #comment" is a valid signature.
+ // Example: "CACHE MANIFEST;V2" is not.
+ if (!s.startsWith("CACHE MANIFEST"))
+ return false;
+
+ const UChar* end = s.characters() + s.length();
+ const UChar* p = s.characters() + 14; // "CACHE MANIFEST" is 14 characters.
+
+ if (p < end && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r')
+ return false;
+
+ // Skip to the end of the line.
+ while (p < end && *p != '\r' && *p != '\n')
+ p++;
+
+ while (1) {
+ // Skip whitespace
+ while (p < end && (*p == '\n' || *p == '\r' || *p == ' ' || *p == '\t'))
+ p++;
+
+ if (p == end)
+ break;
+
+ const UChar* lineStart = p;
+
+ // Find the end of the line
+ while (p < end && *p != '\r' && *p != '\n')
+ p++;
+
+ // Check if we have a comment
+ if (*lineStart == '#')
+ continue;
+
+ // Get rid of trailing whitespace
+ const UChar* tmp = p - 1;
+ while (tmp > lineStart && (*tmp == ' ' || *tmp == '\t'))
+ tmp--;
+
+ String line(lineStart, tmp - lineStart + 1);
+
+ if (line == "CACHE:")
+ mode = Explicit;
+ else if (line == "FALLBACK:")
+ mode = Fallback;
+ else if (line == "NETWORK:")
+ mode = OnlineWhitelist;
+ else if (line.endsWith(":"))
+ mode = Unknown;
+ else if (mode == Unknown)
+ continue;
+ else if (mode == Explicit || mode == OnlineWhitelist) {
+ const UChar* p = line.characters();
+ const UChar* lineEnd = p + line.length();
+
+ // Look for whitespace separating the URL from subsequent ignored tokens.
+ while (p < lineEnd && *p != '\t' && *p != ' ')
+ p++;
+
+ if (mode == OnlineWhitelist && p - line.characters() == 1 && *line.characters() == '*') {
+ // Wildcard was found.
+ manifest.allowAllNetworkRequests = true;
+ continue;
+ }
+
+ KURL url(manifestURL, String(line.characters(), p - line.characters()));
+
+ if (!url.isValid())
+ continue;
+
+ if (url.hasFragmentIdentifier())
+ url.removeFragmentIdentifier();
+
+ if (!equalIgnoringCase(url.protocol(), manifestURL.protocol()))
+ continue;
+
+ if (mode == Explicit && manifestURL.protocolIs("https") && !protocolHostAndPortAreEqual(manifestURL, url))
+ continue;
+
+ if (mode == Explicit)
+ manifest.explicitURLs.add(url.string());
+ else
+ manifest.onlineWhitelistedURLs.append(url);
+
+ } else if (mode == Fallback) {
+ const UChar* p = line.characters();
+ const UChar* lineEnd = p + line.length();
+
+ // Look for whitespace separating the two URLs
+ while (p < lineEnd && *p != '\t' && *p != ' ')
+ p++;
+
+ if (p == lineEnd) {
+ // There was no whitespace separating the URLs.
+ continue;
+ }
+
+ KURL namespaceURL(manifestURL, String(line.characters(), p - line.characters()));
+ if (!namespaceURL.isValid())
+ continue;
+ if (namespaceURL.hasFragmentIdentifier())
+ namespaceURL.removeFragmentIdentifier();
+
+ if (!protocolHostAndPortAreEqual(manifestURL, namespaceURL))
+ continue;
+
+ // Skip whitespace separating fallback namespace from URL.
+ while (p < lineEnd && (*p == '\t' || *p == ' '))
+ p++;
+
+ // Look for whitespace separating the URL from subsequent ignored tokens.
+ const UChar* fallbackStart = p;
+ while (p < lineEnd && *p != '\t' && *p != ' ')
+ p++;
+
+ KURL fallbackURL(manifestURL, String(fallbackStart, p - fallbackStart));
+ if (!fallbackURL.isValid())
+ continue;
+ if (fallbackURL.hasFragmentIdentifier())
+ fallbackURL.removeFragmentIdentifier();
+
+ if (!protocolHostAndPortAreEqual(manifestURL, fallbackURL))
+ continue;
+
+ manifest.fallbackURLs.append(make_pair(namespaceURL, fallbackURL));
+ } else
+ ASSERT_NOT_REACHED();
+ }
+
+ return true;
+}
+
+}
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
diff --git a/Source/WebCore/loader/appcache/ManifestParser.h b/Source/WebCore/loader/appcache/ManifestParser.h
new file mode 100644
index 0000000..f0369ee
--- /dev/null
+++ b/Source/WebCore/loader/appcache/ManifestParser.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+#ifndef ManifestParser_h
+#define ManifestParser_h
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "ApplicationCache.h"
+
+namespace WebCore {
+
+ class KURL;
+
+ struct Manifest {
+ Vector<KURL> onlineWhitelistedURLs;
+ HashSet<String> explicitURLs;
+ FallbackURLVector fallbackURLs;
+ bool allowAllNetworkRequests; // Wildcard found in NETWORK section.
+ };
+
+ bool parseManifest(const KURL& manifestURL, const char* data, int length, Manifest&);
+
+}
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#endif // ManifestParser_h