diff options
Diffstat (limited to 'WebCore/loader')
-rw-r--r-- | WebCore/loader/EmptyClients.h | 38 | ||||
-rw-r--r-- | WebCore/loader/HistoryController.cpp | 48 | ||||
-rw-r--r-- | WebCore/loader/SubframeLoader.cpp | 4 | ||||
-rw-r--r-- | WebCore/loader/appcache/ApplicationCache.h | 1 | ||||
-rw-r--r-- | WebCore/loader/appcache/ApplicationCacheGroup.cpp | 134 | ||||
-rw-r--r-- | WebCore/loader/appcache/ApplicationCacheGroup.h | 21 | ||||
-rw-r--r-- | WebCore/loader/appcache/ApplicationCacheStorage.cpp | 187 | ||||
-rw-r--r-- | WebCore/loader/appcache/ApplicationCacheStorage.h | 26 |
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, |