diff options
Diffstat (limited to 'WebCore')
| -rw-r--r-- | WebCore/WebCore.base.exp | 2 | ||||
| -rw-r--r-- | WebCore/loader/EmptyClients.h | 4 | ||||
| -rw-r--r-- | WebCore/loader/appcache/ApplicationCache.cpp | 9 | ||||
| -rw-r--r-- | WebCore/loader/appcache/ApplicationCache.h | 7 | ||||
| -rw-r--r-- | WebCore/loader/appcache/ApplicationCacheGroup.cpp | 112 | ||||
| -rw-r--r-- | WebCore/loader/appcache/ApplicationCacheGroup.h | 9 | ||||
| -rw-r--r-- | WebCore/loader/appcache/ApplicationCacheResource.cpp | 23 | ||||
| -rw-r--r-- | WebCore/loader/appcache/ApplicationCacheResource.h | 2 | ||||
| -rw-r--r-- | WebCore/loader/appcache/ApplicationCacheStorage.cpp | 240 | ||||
| -rw-r--r-- | WebCore/loader/appcache/ApplicationCacheStorage.h | 30 | ||||
| -rw-r--r-- | WebCore/page/ChromeClient.h | 9 | ||||
| -rw-r--r-- | WebCore/platform/sql/SQLiteDatabase.cpp | 12 | ||||
| -rw-r--r-- | WebCore/platform/sql/SQLiteDatabase.h | 3 | 
13 files changed, 407 insertions, 55 deletions
| diff --git a/WebCore/WebCore.base.exp b/WebCore/WebCore.base.exp index 1ce477e..6bcc46e 100644 --- a/WebCore/WebCore.base.exp +++ b/WebCore/WebCore.base.exp @@ -437,6 +437,8 @@ __ZN7WebCore22externalRepresentationEPNS_12RenderObjectE  __ZN7WebCore23ApplicationCacheStorage16storeCopyOfCacheERKNS_6StringEPNS_16ApplicationCacheE  __ZN7WebCore23ApplicationCacheStorage17setCacheDirectoryERKNS_6StringE  __ZN7WebCore23ApplicationCacheStorage5emptyEv +__ZN7WebCore23ApplicationCacheStorage14setMaximumSizeEx +__ZN7WebCore23ApplicationCacheStorage18vacuumDatabaseFileEv  __ZN7WebCore23ReplaceSelectionCommandC1EPNS_8DocumentEN3WTF10PassRefPtrINS_16DocumentFragmentEEEbbbbbNS_10EditActionE  __ZN7WebCore23createFragmentFromNodesEPNS_8DocumentERKN3WTF6VectorIPNS_4NodeELm0EEE  __ZN7WebCore24BinaryPropertyListWriter17writePropertyListEv diff --git a/WebCore/loader/EmptyClients.h b/WebCore/loader/EmptyClients.h index f6916ef..f93a878 100644 --- a/WebCore/loader/EmptyClients.h +++ b/WebCore/loader/EmptyClients.h @@ -133,6 +133,10 @@ public:      virtual void exceededDatabaseQuota(Frame*, const String&) { }  #endif +#if ENABLE(OFFLINE_WEB_APPLICATIONS) +    virtual void reachedMaxAppCacheSize(int64_t) { } +#endif +      virtual void runOpenPanel(Frame*, PassRefPtr<FileChooser>) { }      virtual void formStateDidChange(const Node*) { } diff --git a/WebCore/loader/appcache/ApplicationCache.cpp b/WebCore/loader/appcache/ApplicationCache.cpp index 42f5b6a..3c29d68 100644 --- a/WebCore/loader/appcache/ApplicationCache.cpp +++ b/WebCore/loader/appcache/ApplicationCache.cpp @@ -39,6 +39,7 @@ namespace WebCore {  ApplicationCache::ApplicationCache()      : m_group(0)      , m_manifest(0) +    , m_estimatedSizeInStorage(0)      , m_storageID(0)  {  } @@ -86,7 +87,9 @@ void ApplicationCache::addResource(PassRefPtr<ApplicationCacheResource> resource          // Add the resource to the storage.          cacheStorage().store(resource.get(), this);      } -     + +    m_estimatedSizeInStorage += resource->estimatedSizeInStorage(); +      m_resources.set(url, resource);  } @@ -100,7 +103,9 @@ unsigned ApplicationCache::removeResource(const String& url)      unsigned type = it->second->type();      m_resources.remove(it); -     + +    m_estimatedSizeInStorage -= it->second->estimatedSizeInStorage(); +      return type;  }     diff --git a/WebCore/loader/appcache/ApplicationCache.h b/WebCore/loader/appcache/ApplicationCache.h index afdab27..4566471 100644 --- a/WebCore/loader/appcache/ApplicationCache.h +++ b/WebCore/loader/appcache/ApplicationCache.h @@ -93,6 +93,8 @@ public:      static bool requestIsHTTPOrHTTPSGet(const ResourceRequest&); +    int64_t estimatedSizeInStorage() const { return m_estimatedSizeInStorage; } +  private:      ApplicationCache(); @@ -106,6 +108,11 @@ private:      // While an update is in progress, changes in dynamic entries are queued for later execution.      Vector<std::pair<KURL, bool> > m_pendingDynamicEntryActions; +    // 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;  }; diff --git a/WebCore/loader/appcache/ApplicationCacheGroup.cpp b/WebCore/loader/appcache/ApplicationCacheGroup.cpp index 9f2f6a4..735e3a3 100644 --- a/WebCore/loader/appcache/ApplicationCacheGroup.cpp +++ b/WebCore/loader/appcache/ApplicationCacheGroup.cpp @@ -31,6 +31,7 @@  #include "ApplicationCache.h"  #include "ApplicationCacheResource.h"  #include "ApplicationCacheStorage.h" +#include "ChromeClient.h"  #include "DocumentLoader.h"  #include "DOMApplicationCache.h"  #include "DOMWindow.h" @@ -53,6 +54,7 @@ ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL, bool isCop      , m_isObsolete(false)      , m_completionType(None)      , m_isCopy(isCopy) +    , m_calledReachedMaxAppCacheSize(false)  {  } @@ -650,6 +652,15 @@ void ApplicationCacheGroup::didFinishLoadingManifest()      startLoadingEntry();  } +void ApplicationCacheGroup::didReachMaxAppCacheSize() +{ +    ASSERT(m_frame); +    ASSERT(m_cacheBeingUpdated); +    m_frame->page()->chrome()->client()->reachedMaxAppCacheSize(cacheStorage().spaceNeeded(m_cacheBeingUpdated->estimatedSizeInStorage())); +    m_calledReachedMaxAppCacheSize = true; +    checkIfLoadIsComplete(); +} +  void ApplicationCacheGroup::cacheUpdateFailed()  {      stopLoading(); @@ -728,7 +739,15 @@ void ApplicationCacheGroup::checkIfLoadIsComplete()          // FIXME: Fetch the resource from manifest URL again, and check whether it is identical to the one used for update (in case the application was upgraded server-side in the meanwhile). (<rdar://problem/6467625>)          ASSERT(m_cacheBeingUpdated); -        m_cacheBeingUpdated->setManifestResource(m_manifestResource.release()); +        if (m_manifestResource) +            m_cacheBeingUpdated->setManifestResource(m_manifestResource.release()); +        else { +            // We can get here as a result of retrying the Complete step, following +            // a failure of the cache storage to save the newest cache due to hitting +            // the maximum size. In such a case, m_manifestResource may be 0, as +            // the manifest was already set on the newest cache object. +            ASSERT(cacheStorage().isMaximumSizeReached() && m_calledReachedMaxAppCacheSize); +        }          RefPtr<ApplicationCache> oldNewestCache = (m_newestCache == m_cacheBeingUpdated) ? 0 : m_newestCache; @@ -739,29 +758,42 @@ void ApplicationCacheGroup::checkIfLoadIsComplete()                  cacheStorage().remove(oldNewestCache.get());
              // Fire the success events.
              postListenerTask(isUpgradeAttempt ? &DOMApplicationCache::callUpdateReadyListener : &DOMApplicationCache::callCachedListener, m_associatedDocumentLoaders);
 -        } 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(&DOMApplicationCache::callErrorListener, 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;
 +        } else { +            if (cacheStorage().isMaximumSizeReached() && !m_calledReachedMaxAppCacheSize) { +                // We ran out of space. All the changes in the cache storage have +                // been rolled back. We roll back to the previous state in here, +                // as well, call the chrome client asynchronously and retry to +                // save the new cache. +                // Save a reference to the new cache. +                m_cacheBeingUpdated = m_newestCache.release(); +                if (oldNewestCache) { +                    // Reinstate the oldNewestCache. +                    setNewestCache(oldNewestCache.release()); +                } +                scheduleReachedMaxAppCacheSizeCallback(); +                return; +            } else { +                // Run the "cache failure steps" +                // Fire the error events to all pending master entries, as well any other cache hosts +                // currently associated with a cache in this group. +                postListenerTask(&DOMApplicationCache::callErrorListener, 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; @@ -773,6 +805,7 @@ void ApplicationCacheGroup::checkIfLoadIsComplete()      m_completionType = None;      m_updateStatus = Idle;      m_frame = 0; +    m_calledReachedMaxAppCacheSize = false;  }  void ApplicationCacheGroup::startLoadingEntry() @@ -855,7 +888,34 @@ void ApplicationCacheGroup::associateDocumentLoaderWithCache(DocumentLoader* loa      ASSERT(!m_associatedDocumentLoaders.contains(loader));      m_associatedDocumentLoaders.add(loader);  } -  + +class ChromeClientCallbackTimer: public TimerBase { +public: +    ChromeClientCallbackTimer(ApplicationCacheGroup* cacheGroup) +        : m_cacheGroup(cacheGroup) +    { +    } + +private: +    virtual void fired() +    { +        m_cacheGroup->didReachMaxAppCacheSize(); +        delete this; +    } +    // Note that there is no need to use a RefPtr here. The ApplicationCacheGroup instance is guaranteed +    // to be alive when the timer fires since invoking the ChromeClient callback is part of its normal +    // update machinery and nothing can yet cause it to get deleted. +    ApplicationCacheGroup* m_cacheGroup; +}; + +void ApplicationCacheGroup::scheduleReachedMaxAppCacheSizeCallback() +{ +    ASSERT(isMainThread()); +    ChromeClientCallbackTimer* timer = new ChromeClientCallbackTimer(this); +    timer->startOneShot(0); +    // The timer will delete itself once it fires. +} +  class CallCacheListenerTask : public ScriptExecutionContext::Task {      typedef void (DOMApplicationCache::*ListenerFunction)();  public: @@ -908,7 +968,7 @@ void ApplicationCacheGroup::clearStorageID()      for (HashSet<ApplicationCache*>::const_iterator it = m_caches.begin(); it != end; ++it)          (*it)->clearStorageID();  } -     +  } diff --git a/WebCore/loader/appcache/ApplicationCacheGroup.h b/WebCore/loader/appcache/ApplicationCacheGroup.h index 063fb3b..281dadf 100644 --- a/WebCore/loader/appcache/ApplicationCacheGroup.h +++ b/WebCore/loader/appcache/ApplicationCacheGroup.h @@ -95,6 +95,7 @@ private:      static void postListenerTask(ListenerFunction, const HashSet<DocumentLoader*>&);      static void postListenerTask(ListenerFunction, const Vector<RefPtr<DocumentLoader> >& loaders);      static void postListenerTask(ListenerFunction, DocumentLoader*); +    void scheduleReachedMaxAppCacheSizeCallback();      PassRefPtr<ResourceHandle> createResourceHandle(const KURL&, ApplicationCacheResource* newestCachedResource); @@ -106,6 +107,7 @@ private:      void didReceiveManifestResponse(const ResourceResponse&);      void didReceiveManifestData(const char*, int);      void didFinishLoadingManifest(); +    void didReachMaxAppCacheSize();      void startLoadingEntry();      void deliverDelayedMainResources(); @@ -163,12 +165,19 @@ private:      // 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;      RefPtr<ApplicationCacheResource> m_manifestResource;      RefPtr<ResourceHandle> m_manifestHandle; + +    friend class ChromeClientCallbackTimer;  };  } // namespace WebCore diff --git a/WebCore/loader/appcache/ApplicationCacheResource.cpp b/WebCore/loader/appcache/ApplicationCacheResource.cpp index 7c1241b..90e65ad 100644 --- a/WebCore/loader/appcache/ApplicationCacheResource.cpp +++ b/WebCore/loader/appcache/ApplicationCacheResource.cpp @@ -35,6 +35,7 @@ ApplicationCacheResource::ApplicationCacheResource(const KURL& url, const Resour      : SubstituteResource(url, response, data)      , m_type(type)      , m_storageID(0) +    , m_estimatedSizeInStorage(0)  {  } @@ -44,6 +45,28 @@ void ApplicationCacheResource::addType(unsigned type)      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)  { diff --git a/WebCore/loader/appcache/ApplicationCacheResource.h b/WebCore/loader/appcache/ApplicationCacheResource.h index 28d8280..53fdd1d 100644 --- a/WebCore/loader/appcache/ApplicationCacheResource.h +++ b/WebCore/loader/appcache/ApplicationCacheResource.h @@ -54,6 +54,7 @@ public:      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); @@ -64,6 +65,7 @@ private:      unsigned m_type;      unsigned m_storageID; +    int64_t m_estimatedSizeInStorage;  };  } // namespace WebCore diff --git a/WebCore/loader/appcache/ApplicationCacheStorage.cpp b/WebCore/loader/appcache/ApplicationCacheStorage.cpp index 1c59581..a1dad15 100644 --- a/WebCore/loader/appcache/ApplicationCacheStorage.cpp +++ b/WebCore/loader/appcache/ApplicationCacheStorage.cpp @@ -43,16 +43,17 @@ using namespace std;  namespace WebCore { -class ResourceStorageIDJournal { +template <class T> +class StorageIDJournal {  public:   -    ~ResourceStorageIDJournal() +    ~StorageIDJournal()      {          size_t size = m_records.size();          for (size_t i = 0; i < size; ++i)              m_records[i].restore();      } -    void add(ApplicationCacheResource* resource, unsigned storageID) +    void add(T* resource, unsigned storageID)      {          m_records.append(Record(resource, storageID));      } @@ -66,7 +67,7 @@ private:      class Record {      public:          Record() : m_resource(0), m_storageID(0) { } -        Record(ApplicationCacheResource* resource, unsigned storageID) : m_resource(resource), m_storageID(storageID) { } +        Record(T* resource, unsigned storageID) : m_resource(resource), m_storageID(storageID) { }          void restore()          { @@ -74,7 +75,7 @@ private:          }      private: -        ApplicationCacheResource* m_resource; +        T* m_resource;          unsigned m_storageID;      }; @@ -223,6 +224,8 @@ ApplicationCacheGroup* ApplicationCacheStorage::cacheGroupForURL(const KURL& url          // 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) @@ -353,6 +356,56 @@ 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; +    int64_t currentSize = 0; +    if (!getFileSize(m_cacheFile, currentSize)) +        return 0; + +    // 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 accomodate 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; +}  bool ApplicationCacheStorage::executeSQLCommand(const String& sql)  { @@ -366,7 +419,7 @@ bool ApplicationCacheStorage::executeSQLCommand(const String& sql)      return result;  } -static const int schemaVersion = 3; +static const int schemaVersion = 4;  void ApplicationCacheStorage::verifySchemaVersion()  { @@ -400,13 +453,13 @@ void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist)      // The cache directory should never be null, but if it for some weird reason is we bail out.      if (m_cacheDirectory.isNull())          return; -     -    String applicationCachePath = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db"); -    if (!createIfDoesNotExist && !fileExists(applicationCachePath)) + +    m_cacheFile = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db"); +    if (!createIfDoesNotExist && !fileExists(m_cacheFile))          return;      makeAllDirectories(m_cacheDirectory); -    m_database.open(applicationCachePath); +    m_database.open(m_cacheFile);      if (!m_database.isOpen())          return; @@ -416,7 +469,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)"); -    executeSQLCommand("CREATE TABLE IF NOT EXISTS Caches (id INTEGER PRIMARY KEY AUTOINCREMENT, cacheGroup INTEGER)"); +    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 FallbackURLs (namespace TEXT NOT NULL ON CONFLICT FAIL, fallbackURL TEXT NOT NULL ON CONFLICT FAIL, "                        "cache INTEGER NOT NULL ON CONFLICT FAIL)"); @@ -456,9 +509,10 @@ bool ApplicationCacheStorage::executeStatement(SQLiteStatement& statement)      return result;  }     -bool ApplicationCacheStorage::store(ApplicationCacheGroup* group) +bool ApplicationCacheStorage::store(ApplicationCacheGroup* group, GroupStorageIDJournal* journal)  {      ASSERT(group->storageID() == 0); +    ASSERT(journal);      SQLiteStatement statement(m_database, "INSERT INTO CacheGroups (manifestHostHash, manifestURL) VALUES (?, ?)");      if (statement.prepare() != SQLResultOk) @@ -471,6 +525,7 @@ bool ApplicationCacheStorage::store(ApplicationCacheGroup* group)          return false;      group->setStorageID(static_cast<unsigned>(m_database.lastInsertRowID())); +    journal->add(group, 0);      return true;  }     @@ -480,11 +535,12 @@ bool ApplicationCacheStorage::store(ApplicationCache* cache, ResourceStorageIDJo      ASSERT(cache->group()->storageID() != 0);      ASSERT(storageIDJournal); -    SQLiteStatement statement(m_database, "INSERT INTO Caches (cacheGroup) VALUES (?)"); +    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; @@ -581,6 +637,9 @@ bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, unsigned      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()); @@ -629,33 +688,56 @@ bool ApplicationCacheStorage::storeUpdatedType(ApplicationCacheResource* resourc      return executeStatement(entryStatement);  } -void ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache) +bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache)  {      ASSERT(cache->storageID());      openDatabase(true); +    m_isMaximumSizeReached = false; +    m_database.setMaximumSize(m_maximumSize); +      SQLiteTransaction storeResourceTransaction(m_database);      storeResourceTransaction.begin(); -    if (!store(resource, cache->storageID())) -        return; +    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::storeNewestCache(ApplicationCacheGroup* group)  {      openDatabase(true); -     + +    m_isMaximumSizeReached = false; +    m_database.setMaximumSize(m_maximumSize); +      SQLiteTransaction storeCacheTransaction(m_database);      storeCacheTransaction.begin(); -     + +    GroupStorageIDJournal groupStorageIDJournal;      if (!group->storageID()) {          // Store the group -        if (!store(group)) +        if (!store(group, &groupStorageIDJournal)) { +            checkForMaxSizeReached();              return false; +        }      }      ASSERT(group->newestCache()); @@ -665,11 +747,13 @@ bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group)      // 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 storageIDJournal; +    ResourceStorageIDJournal resourceStorageIDJournal;      // Store the newest cache -    if (!store(group->newestCache(), &storageIDJournal)) +    if (!store(group->newestCache(), &resourceStorageIDJournal)) { +        checkForMaxSizeReached();          return false; +    }      // Update the newest cache in the group. @@ -683,7 +767,8 @@ bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group)      if (!executeStatement(statement))          return false; -    storageIDJournal.commit(); +    groupStorageIDJournal.commit(); +    resourceStorageIDJournal.commit();      storeCacheTransaction.commit();      return true;  } @@ -879,7 +964,116 @@ bool ApplicationCacheStorage::storeCopyOfCache(const String& cacheDirectory, App      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(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() +{ +    m_database.runVacuumCommand(); +} + +void ApplicationCacheStorage::checkForMaxSizeReached() +{ +    if (m_database.lastError() == SQLResultFull) +        m_isMaximumSizeReached = true; +} + +ApplicationCacheStorage::ApplicationCacheStorage()  +    : m_maximumSize(INT_MAX) +    , m_isMaximumSizeReached(false) +{ +} +  ApplicationCacheStorage& cacheStorage()  {      DEFINE_STATIC_LOCAL(ApplicationCacheStorage, storage, ()); diff --git a/WebCore/loader/appcache/ApplicationCacheStorage.h b/WebCore/loader/appcache/ApplicationCacheStorage.h index b13b596..c6d687e 100644 --- a/WebCore/loader/appcache/ApplicationCacheStorage.h +++ b/WebCore/loader/appcache/ApplicationCacheStorage.h @@ -40,13 +40,19 @@ class ApplicationCache;  class ApplicationCacheGroup;  class ApplicationCacheResource;  class KURL; -class ResourceStorageIDJournal; -     +template <class T> +class StorageIDJournal; +  class ApplicationCacheStorage {  public:      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); +      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. @@ -55,7 +61,7 @@ public:      void cacheGroupMadeObsolete(ApplicationCacheGroup*);      bool storeNewestCache(ApplicationCacheGroup*); // Updates the cache group, but doesn't remove old cache. -    void store(ApplicationCacheResource*, ApplicationCache*); +    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). @@ -65,11 +71,19 @@ public:      static bool storeCopyOfCache(const String& cacheDirectory, ApplicationCache*); +    bool manifestURLs(Vector<KURL>* urls); +    bool cacheGroupSize(const String& manifestURL, int64_t* size); +    bool deleteCacheGroup(const String& manifestURL); +    void vacuumDatabaseFile();  private: +    ApplicationCacheStorage();      PassRefPtr<ApplicationCache> loadCache(unsigned storageID);      ApplicationCacheGroup* loadCacheGroup(const KURL& manifestURL); -    bool store(ApplicationCacheGroup*); +    typedef StorageIDJournal<ApplicationCacheResource> ResourceStorageIDJournal; +    typedef StorageIDJournal<ApplicationCacheGroup> GroupStorageIDJournal; + +    bool store(ApplicationCacheGroup*, GroupStorageIDJournal*);      bool store(ApplicationCache*, ResourceStorageIDJournal*);      bool store(ApplicationCacheResource*, unsigned cacheStorageID); @@ -81,8 +95,14 @@ private:      bool executeStatement(SQLiteStatement&);      bool executeSQLCommand(const String&); + +    void checkForMaxSizeReached();      String m_cacheDirectory; +    String m_cacheFile; + +    int64_t m_maximumSize; +    bool m_isMaximumSizeReached;      SQLiteDatabase m_database; @@ -92,6 +112,8 @@ private:      typedef HashMap<String, ApplicationCacheGroup*> CacheGroupMap;      CacheGroupMap m_cachesInMemory; // Excludes obsolete cache groups. + +    friend ApplicationCacheStorage& cacheStorage();  };  ApplicationCacheStorage& cacheStorage(); diff --git a/WebCore/page/ChromeClient.h b/WebCore/page/ChromeClient.h index e155754..46f37f3 100644 --- a/WebCore/page/ChromeClient.h +++ b/WebCore/page/ChromeClient.h @@ -139,6 +139,15 @@ namespace WebCore {          virtual void exceededDatabaseQuota(Frame*, const String& databaseName) = 0;  #endif +#if ENABLE(OFFLINE_WEB_APPLICATIONS) +        // Callback invoked when the application cache fails to save a cache object +        // because storing it would grow the database file past its defined maximum +        // size or past the amount of free space on the device.  +        // The chrome client would need to take some action such as evicting some +        // old caches. +        virtual void reachedMaxAppCacheSize(int64_t spaceNeeded) = 0; +#endif +  #if ENABLE(DASHBOARD_SUPPORT)          virtual void dashboardRegionsChanged();  #endif diff --git a/WebCore/platform/sql/SQLiteDatabase.cpp b/WebCore/platform/sql/SQLiteDatabase.cpp index 702cf02..e16b04e 100644 --- a/WebCore/platform/sql/SQLiteDatabase.cpp +++ b/WebCore/platform/sql/SQLiteDatabase.cpp @@ -151,6 +151,18 @@ int SQLiteDatabase::pageSize()      return m_pageSize;  } +int64_t SQLiteDatabase::freeSpaceSize() +{ +    MutexLocker locker(m_authorizerLock); +    enableAuthorizer(false); +    // Note: freelist_count was added in SQLite 3.4.1. +    SQLiteStatement statement(*this, "PRAGMA freelist_count"); +    int64_t size = statement.getColumnInt64(0) * pageSize(); + +    enableAuthorizer(true); +    return size; +} +  void SQLiteDatabase::setSynchronous(SynchronousPragma sync)  {      executeCommand(String::format("PRAGMA synchronous = %i", sync)); diff --git a/WebCore/platform/sql/SQLiteDatabase.h b/WebCore/platform/sql/SQLiteDatabase.h index 76700dc..d313435 100644 --- a/WebCore/platform/sql/SQLiteDatabase.h +++ b/WebCore/platform/sql/SQLiteDatabase.h @@ -83,6 +83,9 @@ public:      int64_t maximumSize();      void setMaximumSize(int64_t); +    // Gets the number of unused bytes in the database file. +    int64_t freeSpaceSize(); +      // The SQLite SYNCHRONOUS pragma can be either FULL, NORMAL, or OFF      // FULL - Any writing calls to the DB block until the data is actually on the disk surface      // NORMAL - SQLite pauses at some critical moments when writing, but much less than FULL | 
