Implements a mechanism that limit the growth of the application cache
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 @@
 __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 @@
     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 @@
 ApplicationCache::ApplicationCache()
     : m_group(0)
     , m_manifest(0)
+    , m_estimatedSizeInStorage(0)
     , m_storageID(0)
 {
 }
@@ -86,7 +87,9 @@
         // 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 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 @@
     
     static bool requestIsHTTPOrHTTPSGet(const ResourceRequest&);
 
+    int64_t estimatedSizeInStorage() const { return m_estimatedSizeInStorage; }
+
 private:
     ApplicationCache();
     
@@ -106,6 +108,11 @@
     // 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 @@
     , m_isObsolete(false)
     , m_completionType(None)
     , m_isCopy(isCopy)
+    , m_calledReachedMaxAppCacheSize(false)
 {
 }
 
@@ -650,6 +652,15 @@
     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 @@
         // 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 @@
                 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 @@
     m_completionType = None;
     m_updateStatus = Idle;
     m_frame = 0;
+    m_calledReachedMaxAppCacheSize = false;
 }
 
 void ApplicationCacheGroup::startLoadingEntry()
@@ -855,7 +888,34 @@
     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 @@
     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 @@
     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 @@
     void didReceiveManifestResponse(const ResourceResponse&);
     void didReceiveManifestData(const char*, int);
     void didFinishLoadingManifest();
+    void didReachMaxAppCacheSize();
     
     void startLoadingEntry();
     void deliverDelayedMainResources();
@@ -163,12 +165,19 @@
 
     // 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 @@
     : SubstituteResource(url, response, data)
     , m_type(type)
     , m_storageID(0)
+    , m_estimatedSizeInStorage(0)
 {
 }
 
@@ -44,6 +45,28 @@
     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 @@
     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 @@
 
     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 @@
 
 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 @@
     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:
-        ApplicationCacheResource* m_resource;
+        T* m_resource;
         unsigned m_storageID;
     };
 
@@ -223,6 +224,8 @@
         // 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 @@
     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 @@
     return result;
 }
 
-static const int schemaVersion = 3;
+static const int schemaVersion = 4;
     
 void ApplicationCacheStorage::verifySchemaVersion()
 {
@@ -400,13 +453,13 @@
     // 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 @@
     // 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 @@
     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 @@
         return false;
 
     group->setStorageID(static_cast<unsigned>(m_database.lastInsertRowID()));
+    journal->add(group, 0);
     return true;
 }    
 
@@ -480,11 +535,12 @@
     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 @@
     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 @@
     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 @@
     // 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 @@
     if (!executeStatement(statement))
         return false;
     
-    storageIDJournal.commit();
+    groupStorageIDJournal.commit();
+    resourceStorageIDJournal.commit();
     storeCacheTransaction.commit();
     return true;
 }
@@ -879,7 +964,116 @@
     
     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 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 @@
     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 @@
     
     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 @@
     
     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 @@
     
     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 @@
         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 @@
     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 @@
     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
diff --git a/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp b/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp
index 39bc004..71d9f59 100644
--- a/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp
+++ b/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp
@@ -311,6 +311,13 @@
     }
 }
 #endif
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+void ChromeClientAndroid::reachedMaxAppCacheSize(int64_t spaceNeeded)
+{
+    // FIXME: Free some space.
+    notImplemented();
+}
+#endif
 
 void ChromeClientAndroid::requestGeolocationPermissionForFrame(Frame*, Geolocation*) { notImplemented(); }
 void ChromeClientAndroid::runOpenPanel(Frame*, PassRefPtr<FileChooser>) { notImplemented(); }
diff --git a/WebKit/android/WebCoreSupport/ChromeClientAndroid.h b/WebKit/android/WebCoreSupport/ChromeClientAndroid.h
index 966d5c7..93426b8 100644
--- a/WebKit/android/WebCoreSupport/ChromeClientAndroid.h
+++ b/WebKit/android/WebCoreSupport/ChromeClientAndroid.h
@@ -111,6 +111,9 @@
 #if ENABLE(DATABASE)
         virtual void exceededDatabaseQuota(Frame*, const String&);
 #endif
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+        virtual void reachedMaxAppCacheSize(int64_t spaceNeeded);
+#endif
         virtual void requestGeolocationPermissionForFrame(Frame*, Geolocation*);
         virtual void runOpenPanel(Frame*, PassRefPtr<FileChooser>);
         virtual bool setCursor(PlatformCursorHandle);
diff --git a/WebKit/gtk/WebCoreSupport/ChromeClientGtk.cpp b/WebKit/gtk/WebCoreSupport/ChromeClientGtk.cpp
index 892be74..7125305 100644
--- a/WebKit/gtk/WebCoreSupport/ChromeClientGtk.cpp
+++ b/WebKit/gtk/WebCoreSupport/ChromeClientGtk.cpp
@@ -418,6 +418,14 @@
 }
 #endif
 
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+void ChromeClient::reachedMaxAppCacheSize(int64_t spaceNeeded)
+{
+    // FIXME: Free some space.
+    notImplemented();
+}
+#endif
+
 void ChromeClient::runOpenPanel(Frame*, PassRefPtr<FileChooser> prpFileChooser)
 {
     RefPtr<FileChooser> chooser = prpFileChooser;
diff --git a/WebKit/gtk/WebCoreSupport/ChromeClientGtk.h b/WebKit/gtk/WebCoreSupport/ChromeClientGtk.h
index fc0ea8a..bac9940 100644
--- a/WebKit/gtk/WebCoreSupport/ChromeClientGtk.h
+++ b/WebKit/gtk/WebCoreSupport/ChromeClientGtk.h
@@ -99,6 +99,9 @@
 #if ENABLE(DATABASE)
         virtual void exceededDatabaseQuota(WebCore::Frame*, const WebCore::String&);
 #endif
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+        virtual void reachedMaxAppCacheSize(int64_t spaceNeeded);
+#endif
         virtual void runOpenPanel(WebCore::Frame*, PassRefPtr<WebCore::FileChooser>);
 
         virtual void formStateDidChange(const WebCore::Node*) { }
diff --git a/WebKit/mac/WebCoreSupport/WebChromeClient.h b/WebKit/mac/WebCoreSupport/WebChromeClient.h
index 6c3d71e..6974cb1 100644
--- a/WebKit/mac/WebCoreSupport/WebChromeClient.h
+++ b/WebKit/mac/WebCoreSupport/WebChromeClient.h
@@ -107,6 +107,9 @@
 #if ENABLE(DATABASE)
     virtual void exceededDatabaseQuota(WebCore::Frame*, const WebCore::String& databaseName);
 #endif
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+    virtual void reachedMaxAppCacheSize(int64_t spaceNeeded);
+#endif
     virtual void populateVisitedLinks();
 
 #if ENABLE(DASHBOARD_SUPPORT)
diff --git a/WebKit/mac/WebCoreSupport/WebChromeClient.mm b/WebKit/mac/WebCoreSupport/WebChromeClient.mm
index 18c73e9..2ca86d1 100644
--- a/WebKit/mac/WebCoreSupport/WebChromeClient.mm
+++ b/WebKit/mac/WebCoreSupport/WebChromeClient.mm
@@ -517,6 +517,13 @@
     END_BLOCK_OBJC_EXCEPTIONS;
 }
 #endif
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+void WebChromeClient::reachedMaxAppCacheSize(int64_t spaceNeeded)
+{
+    // FIXME: Free some space.
+}
+#endif
     
 void WebChromeClient::populateVisitedLinks()
 {
diff --git a/WebKit/mac/WebKit.exp b/WebKit/mac/WebKit.exp
index d166894..cb26943 100644
--- a/WebKit/mac/WebKit.exp
+++ b/WebKit/mac/WebKit.exp
@@ -1,3 +1,4 @@
+.objc_class_name_WebApplicationCache
 .objc_class_name_WebArchive
 .objc_class_name_WebBackForwardList
 .objc_class_name_WebCache
diff --git a/WebKit/qt/WebCoreSupport/ChromeClientQt.cpp b/WebKit/qt/WebCoreSupport/ChromeClientQt.cpp
index 5df554b..5199a5a 100644
--- a/WebKit/qt/WebCoreSupport/ChromeClientQt.cpp
+++ b/WebKit/qt/WebCoreSupport/ChromeClientQt.cpp
@@ -399,6 +399,14 @@
 }
 #endif
 
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+void ChromeClientQt::reachedMaxAppCacheSize(int64_t spaceNeeded)
+{
+    // FIXME: Free some space.
+    notImplemented();
+}
+#endif
+
 void ChromeClientQt::runOpenPanel(Frame* frame, PassRefPtr<FileChooser> prpFileChooser)
 {
     RefPtr<FileChooser> fileChooser = prpFileChooser;
diff --git a/WebKit/qt/WebCoreSupport/ChromeClientQt.h b/WebKit/qt/WebCoreSupport/ChromeClientQt.h
index 77c56fc..7922000 100644
--- a/WebKit/qt/WebCoreSupport/ChromeClientQt.h
+++ b/WebKit/qt/WebCoreSupport/ChromeClientQt.h
@@ -116,6 +116,9 @@
 #if ENABLE(DATABASE)
         virtual void exceededDatabaseQuota(Frame*, const String&);
 #endif
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+        virtual void reachedMaxAppCacheSize(int64_t spaceNeeded);
+#endif
         virtual void runOpenPanel(Frame*, PassRefPtr<FileChooser>);
 
         virtual void formStateDidChange(const Node*) { }
diff --git a/WebKit/win/WebCoreSupport/WebChromeClient.cpp b/WebKit/win/WebCoreSupport/WebChromeClient.cpp
index 45b07cf..1f0c09c 100644
--- a/WebKit/win/WebCoreSupport/WebChromeClient.cpp
+++ b/WebKit/win/WebCoreSupport/WebChromeClient.cpp
@@ -543,6 +543,15 @@
 }
 #endif
 
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+#include "ApplicationCacheStorage.h"
+void WebChromeClient::reachedMaxAppCacheSize(int64_t spaceNeeded)
+{
+    // FIXME: Free some space.
+    notImplemented();
+}
+#endif
+
 void WebChromeClient::populateVisitedLinks()
 {
     WebHistory* history = WebHistory::sharedHistory();
diff --git a/WebKit/win/WebCoreSupport/WebChromeClient.h b/WebKit/win/WebCoreSupport/WebChromeClient.h
index 44c6107..4aa7422 100644
--- a/WebKit/win/WebCoreSupport/WebChromeClient.h
+++ b/WebKit/win/WebCoreSupport/WebChromeClient.h
@@ -109,6 +109,10 @@
     virtual void exceededDatabaseQuota(WebCore::Frame*, const WebCore::String&);
 #endif
 
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+    virtual void reachedMaxAppCacheSize(int64_t spaceNeeded);
+#endif
+
     virtual void populateVisitedLinks();
 
     virtual bool paintCustomScrollbar(WebCore::GraphicsContext*, const WebCore::FloatRect&, WebCore::ScrollbarControlSize, 
diff --git a/WebKit/wx/WebKitSupport/ChromeClientWx.cpp b/WebKit/wx/WebKitSupport/ChromeClientWx.cpp
index 6dfe7a4..411f795 100644
--- a/WebKit/wx/WebKitSupport/ChromeClientWx.cpp
+++ b/WebKit/wx/WebKitSupport/ChromeClientWx.cpp
@@ -355,6 +355,13 @@
 }
 #endif
 
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+void ChromeClientWx::reachedMaxAppCacheSize(int64_t spaceNeeded)
+{
+    notImplemented();
+}
+#endif
+
 void ChromeClientWx::scroll(const IntSize&, const IntRect&, const IntRect&)
 {
     notImplemented();
diff --git a/WebKit/wx/WebKitSupport/ChromeClientWx.h b/WebKit/wx/WebKitSupport/ChromeClientWx.h
index df1fdd8..d7f4152 100644
--- a/WebKit/wx/WebKitSupport/ChromeClientWx.h
+++ b/WebKit/wx/WebKitSupport/ChromeClientWx.h
@@ -113,6 +113,11 @@
 #if ENABLE(DATABASE)
     virtual void exceededDatabaseQuota(Frame*, const String&);
 #endif
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+    virtual void reachedMaxAppCacheSize(int64_t spaceNeeded);
+#endif
+
     virtual void runOpenPanel(Frame*, PassRefPtr<FileChooser>);
 
     virtual void formStateDidChange(const Node*) { }
diff --git a/WebKitTools/DumpRenderTree/LayoutTestController.cpp b/WebKitTools/DumpRenderTree/LayoutTestController.cpp
index a78d115..2a06c4f 100644
--- a/WebKitTools/DumpRenderTree/LayoutTestController.cpp
+++ b/WebKitTools/DumpRenderTree/LayoutTestController.cpp
@@ -460,6 +460,22 @@
     return JSValueMakeUndefined(context);
 }
 
+static JSValueRef setAppCacheMaximumSizeCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+    // Has mac implementation
+    if (argumentCount < 1)
+        return JSValueMakeUndefined(context);
+
+    LayoutTestController* controller = static_cast<LayoutTestController*>(JSObjectGetPrivate(thisObject));
+
+    double size = JSValueToNumber(context, arguments[0], NULL);
+    if (!isnan(size))
+        controller->setAppCacheMaximumSize(static_cast<unsigned long long>(size));
+        
+    return JSValueMakeUndefined(context);
+
+}
+
 static JSValueRef setAuthorAndUserStylesEnabledCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
 {
     // Has mac & windows implementation
@@ -840,6 +856,7 @@
         { "repaintSweepHorizontally", repaintSweepHorizontallyCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "setAcceptsEditing", setAcceptsEditingCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "setAuthorAndUserStylesEnabled", setAuthorAndUserStylesEnabledCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
+        { "setAppCacheMaximumSize", setAppCacheMaximumSizeCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, 
         { "setCallCloseOnWebViews", setCallCloseOnWebViewsCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "setCanOpenWindows", setCanOpenWindowsCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "setCloseRemainingWindowsWhenComplete", setCloseRemainingWindowsWhenCompleteCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
diff --git a/WebKitTools/DumpRenderTree/LayoutTestController.h b/WebKitTools/DumpRenderTree/LayoutTestController.h
index 2de7f72..0f41923 100644
--- a/WebKitTools/DumpRenderTree/LayoutTestController.h
+++ b/WebKitTools/DumpRenderTree/LayoutTestController.h
@@ -58,6 +58,7 @@
     void queueReload();
     void queueScript(JSStringRef url);
     void setAcceptsEditing(bool acceptsEditing);
+    void setAppCacheMaximumSize(unsigned long long quota);
     void setAuthorAndUserStylesEnabled(bool);
     void setCustomPolicyDelegate(bool setDelegate, bool permissive);
     void setDatabaseQuota(unsigned long long quota);
diff --git a/WebKitTools/DumpRenderTree/gtk/LayoutTestControllerGtk.cpp b/WebKitTools/DumpRenderTree/gtk/LayoutTestControllerGtk.cpp
index ca3bc36..5c2bae7 100644
--- a/WebKitTools/DumpRenderTree/gtk/LayoutTestControllerGtk.cpp
+++ b/WebKitTools/DumpRenderTree/gtk/LayoutTestControllerGtk.cpp
@@ -303,6 +303,11 @@
     // FIXME: implement
 }
 
+void LayoutTestController::setAppCacheMaximumSize(unsigned long long size)
+{
+    // FIXME: implement
+}
+
 bool LayoutTestController::pauseAnimationAtTimeOnElementWithId(JSStringRef animationName, double time, JSStringRef elementId)
 {    
     gchar* name = JSStringCopyUTF8CString(animationName);
diff --git a/WebKitTools/DumpRenderTree/mac/LayoutTestControllerMac.mm b/WebKitTools/DumpRenderTree/mac/LayoutTestControllerMac.mm
index c80c78f..3aa1f2c 100644
--- a/WebKitTools/DumpRenderTree/mac/LayoutTestControllerMac.mm
+++ b/WebKitTools/DumpRenderTree/mac/LayoutTestControllerMac.mm
@@ -39,6 +39,7 @@
 #import <JavaScriptCore/JSStringRefCF.h>
 #import <WebKit/DOMDocument.h>
 #import <WebKit/DOMElement.h>
+#import <WebKit/WebApplicationCache.h>
 #import <WebKit/WebBackForwardList.h>
 #import <WebKit/WebDatabaseManagerPrivate.h>
 #import <WebKit/WebDataSource.h>
@@ -210,6 +211,11 @@
     [(EditingDelegate *)[[mainFrame webView] editingDelegate] setAcceptsEditing:newAcceptsEditing];
 }
 
+void LayoutTestController::setAppCacheMaximumSize(unsigned long long size)
+{
+    [WebApplicationCache setMaximumSize:size];
+}
+
 void LayoutTestController::setAuthorAndUserStylesEnabled(bool flag)
 {
     [[[mainFrame webView] preferences] setAuthorAndUserStylesEnabled:flag];
diff --git a/WebKitTools/DumpRenderTree/win/LayoutTestControllerWin.cpp b/WebKitTools/DumpRenderTree/win/LayoutTestControllerWin.cpp
index ec5139b..56d0a80 100644
--- a/WebKitTools/DumpRenderTree/win/LayoutTestControllerWin.cpp
+++ b/WebKitTools/DumpRenderTree/win/LayoutTestControllerWin.cpp
@@ -681,6 +681,11 @@
     printf("ERROR: LayoutTestController::setDatabaseQuota() not implemented\n");
 }
 
+void LayoutTestController::setAppCacheMaximumSize(unsigned long long size)
+{
+    printf("ERROR: LayoutTestController::setAppCacheMaximumSize() not implemented\n");
+}
+
 bool LayoutTestController::pauseAnimationAtTimeOnElementWithId(JSStringRef animationName, double time, JSStringRef elementId)
 {
     COMPtr<IDOMDocument> document;