summaryrefslogtreecommitdiffstats
path: root/WebCore/loader
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/loader')
-rw-r--r--WebCore/loader/EmptyClients.h38
-rw-r--r--WebCore/loader/HistoryController.cpp48
-rw-r--r--WebCore/loader/SubframeLoader.cpp4
-rw-r--r--WebCore/loader/appcache/ApplicationCache.h1
-rw-r--r--WebCore/loader/appcache/ApplicationCacheGroup.cpp134
-rw-r--r--WebCore/loader/appcache/ApplicationCacheGroup.h21
-rw-r--r--WebCore/loader/appcache/ApplicationCacheStorage.cpp187
-rw-r--r--WebCore/loader/appcache/ApplicationCacheStorage.h26
8 files changed, 398 insertions, 61 deletions
diff --git a/WebCore/loader/EmptyClients.h b/WebCore/loader/EmptyClients.h
index aab30fc..8043b21 100644
--- a/WebCore/loader/EmptyClients.h
+++ b/WebCore/loader/EmptyClients.h
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2006 Eric Seidel (eric@webkit.org)
* Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -28,8 +29,8 @@
#define EmptyClients_h
#include "ChromeClient.h"
-#include "ContextMenuClient.h"
#include "Console.h"
+#include "ContextMenuClient.h"
#include "DocumentLoader.h"
#include "DragClient.h"
#include "EditCommand.h"
@@ -39,7 +40,13 @@
#include "FrameLoaderClient.h"
#include "InspectorClient.h"
#include "PluginHalterClient.h"
+#include "PopupMenu.h"
#include "ResourceError.h"
+#include "SearchPopupMenu.h"
+
+#if USE(GLES2_RENDERING)
+#include "GLES2Context.h"
+#endif
/*
This file holds empty Client stubs for use by WebCore.
@@ -56,6 +63,25 @@
namespace WebCore {
+class EmptyPopupMenu : public PopupMenu {
+public:
+ virtual void show(const IntRect&, FrameView*, int) {}
+ virtual void hide() {}
+ virtual void updateFromElement() {}
+ virtual void disconnectClient() {}
+};
+
+class EmptySearchPopupMenu : public SearchPopupMenu {
+public:
+ virtual PopupMenu* popupMenu() { return m_popup.get(); }
+ virtual void saveRecentSearches(const AtomicString&, const Vector<String>&) {}
+ virtual void loadRecentSearches(const AtomicString&, Vector<String>&) {}
+ virtual bool enabled() { return false; }
+
+private:
+ RefPtr<EmptyPopupMenu> m_popup;
+};
+
class EmptyChromeClient : public ChromeClient {
public:
virtual ~EmptyChromeClient() { }
@@ -112,6 +138,10 @@ public:
virtual bool runJavaScriptPrompt(Frame*, const String&, const String&, String&) { return false; }
virtual bool shouldInterruptJavaScript() { return false; }
+ virtual bool selectItemWritingDirectionIsNatural() { return false; }
+ virtual PassRefPtr<PopupMenu> createPopupMenu(PopupMenuClient*) const { return adoptRef(new EmptyPopupMenu()); }
+ virtual PassRefPtr<SearchPopupMenu> createSearchPopupMenu(PopupMenuClient*) const { return adoptRef(new EmptySearchPopupMenu()); }
+
virtual void setStatusbarText(const String&) { }
virtual bool tabsToLinks() const { return false; }
@@ -141,6 +171,7 @@ public:
#if ENABLE(OFFLINE_WEB_APPLICATIONS)
virtual void reachedMaxAppCacheSize(int64_t) { }
+ virtual void reachedApplicationCacheOriginQuota(SecurityOrigin*) { }
#endif
#if ENABLE(NOTIFICATIONS)
@@ -170,6 +201,11 @@ public:
virtual void scheduleCompositingLayerSync() {};
#endif
+#if USE(GLES2_RENDERING)
+ virtual PassOwnPtr<GLES2Context> getOnscreenGLES2Context() { return 0; }
+ virtual PassOwnPtr<GLES2Context> getOffscreenGLES2Context() { return 0; }
+#endif
+
#if PLATFORM(WIN)
virtual void setLastSetCursorToCurrentCursor() { }
#endif
diff --git a/WebCore/loader/HistoryController.cpp b/WebCore/loader/HistoryController.cpp
index 5ccdf72..144faa5 100644
--- a/WebCore/loader/HistoryController.cpp
+++ b/WebCore/loader/HistoryController.cpp
@@ -48,8 +48,22 @@
#include "Settings.h"
#include <wtf/text/CString.h>
+#if USE(PLATFORM_STRATEGIES)
+#include "PlatformStrategies.h"
+#include "VisitedLinkStrategy.h"
+#endif
+
namespace WebCore {
+static inline void addVisitedLink(Page* page, const KURL& url)
+{
+#if USE(PLATFORM_STRATEGIES)
+ platformStrategies()->visitedLinkStrategy()->addVisitedLink(page, visitedLinkHash(url.string().characters(), url.string().length()));
+#else
+ page->group().addVisitedLink(url);
+#endif
+}
+
HistoryController::HistoryController(Frame* frame)
: m_frame(frame)
{
@@ -290,7 +304,7 @@ void HistoryController::updateForStandardLoad(HistoryUpdateType updateType)
if (!historyURL.isEmpty() && !needPrivacy) {
if (Page* page = m_frame->page())
- page->group().addVisitedLink(historyURL);
+ addVisitedLink(page, historyURL);
if (!frameLoader->documentLoader()->didCreateGlobalHistoryEntry() && frameLoader->documentLoader()->unreachableURL().isEmpty() && !frameLoader->url().isEmpty())
frameLoader->client()->updateGlobalHistoryRedirectLinks();
@@ -334,7 +348,7 @@ void HistoryController::updateForRedirectWithLockedBackForwardList()
if (!historyURL.isEmpty() && !needPrivacy) {
if (Page* page = m_frame->page())
- page->group().addVisitedLink(historyURL);
+ addVisitedLink(page, historyURL);
if (!m_frame->loader()->documentLoader()->didCreateGlobalHistoryEntry() && m_frame->loader()->documentLoader()->unreachableURL().isEmpty() && !m_frame->loader()->url().isEmpty())
m_frame->loader()->client()->updateGlobalHistoryRedirectLinks();
@@ -361,7 +375,7 @@ void HistoryController::updateForClientRedirect()
if (!historyURL.isEmpty() && !needPrivacy) {
if (Page* page = m_frame->page())
- page->group().addVisitedLink(historyURL);
+ addVisitedLink(page, historyURL);
}
}
@@ -399,7 +413,7 @@ void HistoryController::updateForSameDocumentNavigation()
if (!page)
return;
- page->group().addVisitedLink(m_frame->loader()->url());
+ addVisitedLink(page, m_frame->loader()->url());
}
void HistoryController::updateForFrameLoadCompleted()
@@ -619,9 +633,9 @@ void HistoryController::updateBackForwardListClippedAtTarget(bool doClip)
frameLoader->checkDidPerformFirstNavigation();
- RefPtr<HistoryItem> item = frameLoader->history()->createItemTree(m_frame, doClip);
- LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", item.get(), m_frame->loader()->documentLoader()->url().string().ascii().data());
- page->backForwardList()->addItem(item);
+ RefPtr<HistoryItem> topItem = frameLoader->history()->createItemTree(m_frame, doClip);
+ LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", topItem.get(), m_frame->loader()->documentLoader()->url().string().ascii().data());
+ page->backForwardList()->addItem(topItem.release());
}
void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
@@ -633,15 +647,21 @@ void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject,
ASSERT(page);
// Get a HistoryItem tree for the current frame tree.
- RefPtr<HistoryItem> item = createItemTree(m_frame, false);
- ASSERT(item->isTargetItem());
+ RefPtr<HistoryItem> topItem = page->mainFrame()->loader()->history()->createItemTree(m_frame, false);
- // Override data in the target item to reflect the pushState() arguments.
- item->setTitle(title);
- item->setStateObject(stateObject);
- item->setURLString(urlString);
+ // Override data in the current item (created by createItemTree) to reflect
+ // the pushState() arguments.
+ m_currentItem->setTitle(title);
+ m_currentItem->setStateObject(stateObject);
+ m_currentItem->setURLString(urlString);
+
+ // Create a null state object for the previous HistoryItem so that we will
+ // generate a popstate event when navigating back to it.
+ // FIXME: http://webkit.org/b/41372 implies that we shouldn't need this.
+ if (!m_previousItem->stateObject())
+ m_previousItem->setStateObject(SerializedScriptValue::create());
- page->backForwardList()->pushStateItem(item.release());
+ page->backForwardList()->addItem(topItem.release());
}
void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
diff --git a/WebCore/loader/SubframeLoader.cpp b/WebCore/loader/SubframeLoader.cpp
index 2821a50..e7dafa1 100644
--- a/WebCore/loader/SubframeLoader.cpp
+++ b/WebCore/loader/SubframeLoader.cpp
@@ -226,7 +226,9 @@ PassRefPtr<Widget> SubframeLoader::createJavaAppletWidget(const IntSize& size, H
baseURLString = m_frame->document()->baseURL().string();
KURL baseURL = completeURL(baseURLString);
- RefPtr<Widget> widget = m_frame->loader()->client()->createJavaAppletWidget(size, element, baseURL, paramNames, paramValues);
+ RefPtr<Widget> widget;
+ if (allowPlugins(AboutToInstantiatePlugin))
+ widget = m_frame->loader()->client()->createJavaAppletWidget(size, element, baseURL, paramNames, paramValues);
if (!widget)
return 0;
diff --git a/WebCore/loader/appcache/ApplicationCache.h b/WebCore/loader/appcache/ApplicationCache.h
index 08e2dd3..d6e15ed 100644
--- a/WebCore/loader/appcache/ApplicationCache.h
+++ b/WebCore/loader/appcache/ApplicationCache.h
@@ -41,7 +41,6 @@ class ApplicationCacheGroup;
class ApplicationCacheResource;
class DocumentLoader;
class KURL;
-
class ResourceRequest;
typedef Vector<std::pair<KURL, KURL> > FallbackURLVector;
diff --git a/WebCore/loader/appcache/ApplicationCacheGroup.cpp b/WebCore/loader/appcache/ApplicationCacheGroup.cpp
index 9b05e0d..395d9ad 100644
--- a/WebCore/loader/appcache/ApplicationCacheGroup.cpp
+++ b/WebCore/loader/appcache/ApplicationCacheGroup.cpp
@@ -42,6 +42,7 @@
#include "MainResourceLoader.h"
#include "ManifestParser.h"
#include "Page.h"
+#include "SecurityOrigin.h"
#include "Settings.h"
#include <wtf/HashMap.h>
@@ -57,6 +58,7 @@ 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)
@@ -67,6 +69,9 @@ ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL, bool isCop
, m_completionType(None)
, m_isCopy(isCopy)
, m_calledReachedMaxAppCacheSize(false)
+ , m_loadedSize(0)
+ , m_availableSpaceInQuota(ApplicationCacheStorage::unknownQuota())
+ , m_originQuotaReached(false)
{
}
@@ -592,6 +597,8 @@ void ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* d
ASSERT(m_currentResource);
m_currentResource->data()->append(data, length);
+
+ m_loadedSize += length;
}
void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle)
@@ -605,7 +612,23 @@ void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle)
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()));
@@ -770,6 +793,13 @@ void ApplicationCacheGroup::didReachMaxAppCacheSize()
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();
@@ -779,6 +809,16 @@ void ApplicationCacheGroup::cacheUpdateFailed()
m_completionType = Failure;
deliverDelayedMainResources();
}
+
+void ApplicationCacheGroup::cacheUpdateFailedDueToOriginQuota()
+{
+ if (!m_originQuotaReached) {
+ m_originQuotaReached = true;
+ scheduleReachedOriginQuotaCallback();
+ }
+
+ cacheUpdateFailed();
+}
void ApplicationCacheGroup::manifestNotFound()
{
@@ -858,10 +898,10 @@ void ApplicationCacheGroup::checkIfLoadIsComplete()
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)) {
+ if (cacheStorage().storeNewestCache(this, oldNewestCache.get(), failureReason)) {
// New cache stored, now remove the old cache.
if (oldNewestCache)
cacheStorage().remove(oldNewestCache.get());
@@ -872,8 +912,18 @@ void ApplicationCacheGroup::checkIfLoadIsComplete()
// 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 (cacheStorage().isMaximumSizeReached() && !m_calledReachedMaxAppCacheSize) {
+ 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
@@ -887,30 +937,30 @@ void ApplicationCacheGroup::checkIfLoadIsComplete()
}
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 {
- // 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;
- }
+ // We must have been deleted by the last call to disassociateDocumentLoader().
+ return;
}
}
break;
@@ -922,6 +972,8 @@ void ApplicationCacheGroup::checkIfLoadIsComplete()
m_completionType = None;
setUpdateStatus(Idle);
m_frame = 0;
+ m_loadedSize = 0;
+ m_availableSpaceInQuota = ApplicationCacheStorage::unknownQuota();
m_calledReachedMaxAppCacheSize = false;
}
@@ -1027,6 +1079,25 @@ private:
ApplicationCacheGroup* m_cacheGroup;
};
+class OriginQuotaReachedCallbackTimer: public TimerBase {
+public:
+ OriginQuotaReachedCallbackTimer(ApplicationCacheGroup* cacheGroup, Frame* frame)
+ : m_cacheGroup(cacheGroup)
+ , m_frame(frame)
+ {
+ }
+
+private:
+ virtual void fired()
+ {
+ m_cacheGroup->didReachOriginQuota(m_frame.release());
+ delete this;
+ }
+
+ ApplicationCacheGroup* m_cacheGroup;
+ RefPtr<Frame> m_frame;
+};
+
void ApplicationCacheGroup::scheduleReachedMaxAppCacheSizeCallback()
{
ASSERT(isMainThread());
@@ -1035,6 +1106,15 @@ void ApplicationCacheGroup::scheduleReachedMaxAppCacheSizeCallback()
// The timer will delete itself once it fires.
}
+void ApplicationCacheGroup::scheduleReachedOriginQuotaCallback()
+{
+ ASSERT(isMainThread());
+ RefPtr<Frame> frameProtector = m_frame;
+ OriginQuotaReachedCallbackTimer* timer = new OriginQuotaReachedCallbackTimer(this, frameProtector.get());
+ timer->startOneShot(0);
+ // The timer will delete itself once it fires.
+}
+
class CallCacheListenerTask : public ScriptExecutionContext::Task {
public:
static PassOwnPtr<CallCacheListenerTask> create(PassRefPtr<DocumentLoader> loader, ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone)
diff --git a/WebCore/loader/appcache/ApplicationCacheGroup.h b/WebCore/loader/appcache/ApplicationCacheGroup.h
index 9b8a6ab..b5cdf7b 100644
--- a/WebCore/loader/appcache/ApplicationCacheGroup.h
+++ b/WebCore/loader/appcache/ApplicationCacheGroup.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved.
+ * 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
@@ -28,10 +28,6 @@
#if ENABLE(OFFLINE_WEB_APPLICATIONS)
-#include <wtf/Noncopyable.h>
-#include <wtf/HashMap.h>
-#include <wtf/HashSet.h>
-
#include "DOMApplicationCache.h"
#include "KURL.h"
#include "PlatformString.h"
@@ -39,6 +35,10 @@
#include "ResourceHandleClient.h"
#include "SharedBuffer.h"
+#include <wtf/Noncopyable.h>
+#include <wtf/HashMap.h>
+#include <wtf/HashSet.h>
+
namespace WebCore {
class ApplicationCache;
@@ -46,6 +46,7 @@ class ApplicationCacheResource;
class Document;
class DocumentLoader;
class Frame;
+class SecurityOrigin;
enum ApplicationCacheUpdateOption {
ApplicationCacheUpdateWithBrowsingContext,
@@ -66,6 +67,7 @@ public:
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);
@@ -98,6 +100,7 @@ private:
static void postListenerTask(ApplicationCacheHost::EventID, int progressTotal, int progressDone, DocumentLoader*);
void scheduleReachedMaxAppCacheSizeCallback();
+ void scheduleReachedOriginQuotaCallback();
PassRefPtr<ResourceHandle> createResourceHandle(const KURL&, ApplicationCacheResource* newestCachedResource);
@@ -117,11 +120,13 @@ private:
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);
@@ -131,6 +136,7 @@ private:
void stopLoading();
KURL m_manifestURL;
+ RefPtr<SecurityOrigin> m_origin;
UpdateStatus m_updateStatus;
// This is the newest complete cache in the group.
@@ -194,7 +200,12 @@ private:
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
diff --git a/WebCore/loader/appcache/ApplicationCacheStorage.cpp b/WebCore/loader/appcache/ApplicationCacheStorage.cpp
index 9a11307..ec83911 100644
--- a/WebCore/loader/appcache/ApplicationCacheStorage.cpp
+++ b/WebCore/loader/appcache/ApplicationCacheStorage.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved.
+ * 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
@@ -29,13 +29,14 @@
#if ENABLE(OFFLINE_WEB_APPLICATIONS)
#include "ApplicationCache.h"
-#include "ApplicationCacheHost.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>
@@ -414,6 +415,126 @@ int64_t ApplicationCacheStorage::spaceNeeded(int64_t cacheToSave)
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(false);
+ 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());
@@ -426,7 +547,10 @@ bool ApplicationCacheStorage::executeSQLCommand(const String& sql)
return result;
}
-static const int schemaVersion = 5;
+// 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()
{
@@ -475,7 +599,7 @@ void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist)
// 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)");
+ "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)");
@@ -485,6 +609,7 @@ void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist)
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"
@@ -523,16 +648,20 @@ bool ApplicationCacheStorage::store(ApplicationCacheGroup* group, GroupStorageID
ASSERT(group->storageID() == 0);
ASSERT(journal);
- SQLiteStatement statement(m_database, "INSERT INTO CacheGroups (manifestHostHash, manifestURL) VALUES (?, ?)");
+ 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;
+ if (!ensureOriginRecord(group->origin()))
+ return false;
+
group->setStorageID(static_cast<unsigned>(m_database.lastInsertRowID()));
journal->add(group, 0);
return true;
@@ -745,7 +874,21 @@ bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, Applicat
return true;
}
-bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group)
+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);
@@ -759,11 +902,21 @@ bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group)
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;
}
}
@@ -780,20 +933,25 @@ bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group)
// 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)
+ if (statement.prepare() != SQLResultOk) {
+ failureReason = DiskOrOperationFailure;
return false;
+ }
statement.bindInt64(1, group->newestCache()->storageID());
statement.bindInt64(2, group->storageID());
- if (!executeStatement(statement))
+ if (!executeStatement(statement)) {
+ failureReason = DiskOrOperationFailure;
return false;
+ }
groupStorageIDJournal.commit();
resourceStorageIDJournal.commit();
@@ -801,6 +959,13 @@ bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group)
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)
{
int pos = find(header, headerLength, ':');
@@ -964,9 +1129,10 @@ void ApplicationCacheStorage::empty()
if (!m_database.isOpen())
return;
- // Clear cache groups, caches and cache resources.
+ // 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
@@ -1120,8 +1286,9 @@ void ApplicationCacheStorage::checkForMaxSizeReached()
}
ApplicationCacheStorage::ApplicationCacheStorage()
- : m_maximumSize(INT_MAX)
+ : m_maximumSize(ApplicationCacheStorage::noQuota())
, m_isMaximumSizeReached(false)
+ , m_defaultOriginQuota(ApplicationCacheStorage::noQuota())
{
}
diff --git a/WebCore/loader/appcache/ApplicationCacheStorage.h b/WebCore/loader/appcache/ApplicationCacheStorage.h
index aaa5c9c..c990fa7 100644
--- a/WebCore/loader/appcache/ApplicationCacheStorage.h
+++ b/WebCore/loader/appcache/ApplicationCacheStorage.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ * 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
@@ -37,15 +37,22 @@
namespace WebCore {
class ApplicationCache;
-class ApplicationCacheHost;
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;
@@ -54,6 +61,13 @@ public:
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.
@@ -61,6 +75,7 @@ public:
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*);
@@ -76,6 +91,9 @@ public:
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);
@@ -88,6 +106,8 @@ private:
bool store(ApplicationCache*, ResourceStorageIDJournal*);
bool store(ApplicationCacheResource*, unsigned cacheStorageID);
+ bool ensureOriginRecord(const SecurityOrigin*);
+
void loadManifestHostHashes();
void verifySchemaVersion();
@@ -105,6 +125,8 @@ private:
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,