summaryrefslogtreecommitdiffstats
path: root/webkit/appcache
diff options
context:
space:
mode:
authormichaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-10 23:29:09 +0000
committermichaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-10 23:29:09 +0000
commite0184cbde1634faa300d63c7880b5acf312f45d6 (patch)
tree69ef5ba562bbc68276fddd3279c6ca7e8e27d025 /webkit/appcache
parentb7d94558973c6c6f2776f68a4d34d20cd5303ac5 (diff)
downloadchromium_src-e0184cbde1634faa300d63c7880b5acf312f45d6.zip
chromium_src-e0184cbde1634faa300d63c7880b5acf312f45d6.tar.gz
chromium_src-e0184cbde1634faa300d63c7880b5acf312f45d6.tar.bz2
AppCache + Quota integration
* Notify the QuotaManager of accesses and modifications to the amount of storage utlized. * Implement the QuotaClient interface so the manager can query the appcache for usage and delete data. * When storing appcaches, use QuotaManager GetUsageAndQuota and respect the limit. * Remove the old and unsed support for storing per-origin quota values in the appcache DB. BUG=61676 TEST=unittests Review URL: http://codereview.chromium.org/7031065 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@88746 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/appcache')
-rw-r--r--webkit/appcache/appcache_database.cc22
-rw-r--r--webkit/appcache/appcache_database.h11
-rw-r--r--webkit/appcache/appcache_database_unittest.cc9
-rw-r--r--webkit/appcache/appcache_quota_client.cc268
-rw-r--r--webkit/appcache/appcache_quota_client.h111
-rw-r--r--webkit/appcache/appcache_quota_client_unittest.cc429
-rw-r--r--webkit/appcache/appcache_service.cc111
-rw-r--r--webkit/appcache/appcache_service.h16
-rw-r--r--webkit/appcache/appcache_service_unittest.cc109
-rw-r--r--webkit/appcache/appcache_storage.cc40
-rw-r--r--webkit/appcache/appcache_storage.h12
-rw-r--r--webkit/appcache/appcache_storage_impl.cc142
-rw-r--r--webkit/appcache/appcache_storage_impl.h3
-rw-r--r--webkit/appcache/appcache_storage_impl_unittest.cc175
-rw-r--r--webkit/appcache/appcache_storage_unittest.cc82
-rw-r--r--webkit/appcache/mock_appcache_service.cc24
-rw-r--r--webkit/appcache/mock_appcache_service.h19
-rw-r--r--webkit/appcache/mock_appcache_storage.cc13
-rw-r--r--webkit/appcache/mock_appcache_storage.h9
-rw-r--r--webkit/appcache/webkit_appcache.gypi2
20 files changed, 1541 insertions, 66 deletions
diff --git a/webkit/appcache/appcache_database.cc b/webkit/appcache/appcache_database.cc
index 0c7f1686..d754beb 100644
--- a/webkit/appcache/appcache_database.cc
+++ b/webkit/appcache/appcache_database.cc
@@ -15,7 +15,6 @@
#include "base/utf_string_conversions.h"
#include "webkit/appcache/appcache_entry.h"
#include "webkit/appcache/appcache_histograms.h"
-#include "webkit/database/quota_table.h"
// Schema -------------------------------------------------------------------
namespace {
@@ -196,12 +195,15 @@ int64 AppCacheDatabase::GetOriginUsage(const GURL& origin) {
return origin_usage;
}
-int64 AppCacheDatabase::GetOriginQuota(const GURL& origin) {
- if (!LazyOpen(false))
- return GetDefaultOriginQuota();
- int64 quota = quota_table_->GetOriginQuota(
- UTF8ToUTF16(origin.spec().c_str()));
- return (quota >= 0) ? quota : GetDefaultOriginQuota();
+bool AppCacheDatabase::GetAllOriginUsage(std::map<GURL, int64>* usage_map) {
+ std::set<GURL> origins;
+ if (!FindOriginsWithGroups(&origins))
+ return false;
+ for (std::set<GURL>::const_iterator origin = origins.begin();
+ origin != origins.end(); ++origin) {
+ (*usage_map)[*origin] = GetOriginUsage(*origin);
+ }
+ return true;
}
bool AppCacheDatabase::FindOriginsWithGroups(std::set<GURL>* origins) {
@@ -1004,7 +1006,6 @@ bool AppCacheDatabase::LazyOpen(bool create_if_needed) {
db_.reset(new sql::Connection);
meta_table_.reset(new sql::MetaTable);
- quota_table_.reset(new webkit_database::QuotaTable(db_.get()));
db_->set_error_delegate(GetErrorHandlerForAppCacheDb());
@@ -1069,10 +1070,8 @@ bool AppCacheDatabase::CreateSchema() {
if (!transaction.Begin())
return false;
- if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion) ||
- !quota_table_->Init()) {
+ if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
return false;
- }
for (int i = 0; i < kTableCount; ++i) {
std::string sql("CREATE TABLE ");
@@ -1108,7 +1107,6 @@ bool AppCacheDatabase::UpgradeSchema() {
}
void AppCacheDatabase::ResetConnectionAndTables() {
- quota_table_.reset();
meta_table_.reset();
db_.reset();
}
diff --git a/webkit/appcache/appcache_database.h b/webkit/appcache/appcache_database.h
index 4b5e099..c19b6db 100644
--- a/webkit/appcache/appcache_database.h
+++ b/webkit/appcache/appcache_database.h
@@ -5,6 +5,7 @@
#ifndef WEBKIT_APPCACHE_APPCACHE_DATABASE_H_
#define WEBKIT_APPCACHE_APPCACHE_DATABASE_H_
+#include <map>
#include <set>
#include <vector>
@@ -22,10 +23,6 @@ class Statement;
class StatementID;
}
-namespace webkit_database {
-class QuotaTable;
-}
-
namespace appcache {
class AppCacheDatabase {
@@ -86,9 +83,8 @@ class AppCacheDatabase {
void Disable();
bool is_disabled() const { return is_disabled_; }
- int64 GetDefaultOriginQuota() { return 5 * 1024 * 1024; }
int64 GetOriginUsage(const GURL& origin);
- int64 GetOriginQuota(const GURL& origin);
+ bool GetAllOriginUsage(std::map<GURL, int64>* usage_map);
bool FindOriginsWithGroups(std::set<GURL>* origins);
bool FindLastStorageIds(
@@ -198,7 +194,6 @@ class AppCacheDatabase {
FilePath db_file_path_;
scoped_ptr<sql::Connection> db_;
scoped_ptr<sql::MetaTable> meta_table_;
- scoped_ptr<webkit_database::QuotaTable> quota_table_;
bool is_disabled_;
bool is_recreating_;
@@ -210,7 +205,7 @@ class AppCacheDatabase {
FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, OnlineWhiteListRecords);
FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, ReCreate);
FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, DeletableResponseIds);
- FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, Quotas);
+ FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, OriginUsage);
DISALLOW_COPY_AND_ASSIGN(AppCacheDatabase);
};
diff --git a/webkit/appcache/appcache_database_unittest.cc b/webkit/appcache/appcache_database_unittest.cc
index c8c7e5a..4119994 100644
--- a/webkit/appcache/appcache_database_unittest.cc
+++ b/webkit/appcache/appcache_database_unittest.cc
@@ -540,7 +540,7 @@ TEST(AppCacheDatabaseTest, DeletableResponseIds) {
EXPECT_EQ(i + 5, ids[i]);
}
-TEST(AppCacheDatabaseTest, Quotas) {
+TEST(AppCacheDatabaseTest, OriginUsage) {
const GURL kManifestUrl("http://blah/manifest");
const GURL kManifestUrl2("http://blah/manifest2");
const GURL kOrigin(kManifestUrl.GetOrigin());
@@ -555,7 +555,6 @@ TEST(AppCacheDatabaseTest, Quotas) {
db.db_->set_error_delegate(error_delegate);
std::vector<AppCacheDatabase::CacheRecord> cache_records;
- EXPECT_EQ(db.GetDefaultOriginQuota(), db.GetOriginQuota(kOrigin));
EXPECT_EQ(0, db.GetOriginUsage(kOrigin));
EXPECT_TRUE(db.FindCachesForOrigin(kOrigin, &cache_records));
EXPECT_TRUE(cache_records.empty());
@@ -606,6 +605,12 @@ TEST(AppCacheDatabaseTest, Quotas) {
cache_records.clear();
EXPECT_TRUE(db.FindCachesForOrigin(kOtherOrigin, &cache_records));
EXPECT_EQ(1U, cache_records.size());
+
+ std::map<GURL, int64> usage_map;
+ EXPECT_TRUE(db.GetAllOriginUsage(&usage_map));
+ EXPECT_EQ(2U, usage_map.size());
+ EXPECT_EQ(1100, usage_map[kOrigin]);
+ EXPECT_EQ(5000, usage_map[kOtherOrigin]);
}
} // namespace appcache
diff --git a/webkit/appcache/appcache_quota_client.cc b/webkit/appcache/appcache_quota_client.cc
new file mode 100644
index 0000000..f41897d
--- /dev/null
+++ b/webkit/appcache/appcache_quota_client.cc
@@ -0,0 +1,268 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/appcache/appcache_quota_client.h"
+
+#include <algorithm>
+#include <map>
+
+#include "webkit/appcache/appcache_service.h"
+
+using quota::QuotaClient;
+
+namespace {
+quota::QuotaStatusCode NetErrorCodeToQuotaStatus(int code) {
+ if (code == net::OK)
+ return quota::kQuotaStatusOk;
+ else if (code == net::ERR_ABORTED)
+ return quota::kQuotaErrorAbort;
+ else
+ return quota::kQuotaStatusUnknown;
+}
+} // namespace
+
+namespace appcache {
+
+AppCacheQuotaClient::AppCacheQuotaClient(AppCacheService* service)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(service_delete_callback_(
+ new net::CancelableCompletionCallback<AppCacheQuotaClient>(
+ this, &AppCacheQuotaClient::DidDeleteAppCachesForOrigin))),
+ service_(service), appcache_is_ready_(false),
+ quota_manager_is_destroyed_(false) {
+}
+
+AppCacheQuotaClient::~AppCacheQuotaClient() {
+ DCHECK(pending_usage_requests_.empty());
+ DCHECK(pending_origins_requests_.empty());
+ DCHECK(pending_delete_requests_.empty());
+ DCHECK(!current_delete_request_callback_.get());
+}
+
+QuotaClient::ID AppCacheQuotaClient::id() const {
+ return kAppcache;
+}
+
+void AppCacheQuotaClient::OnQuotaManagerDestroyed() {
+ DeletePendingRequests();
+ if (current_delete_request_callback_.get()) {
+ current_delete_request_callback_.reset();
+ service_delete_callback_.release()->Cancel();
+ } else {
+ service_delete_callback_ = NULL;
+ }
+ quota_manager_is_destroyed_ = true;
+ if (!service_)
+ delete this;
+}
+
+void AppCacheQuotaClient::GetOriginUsage(
+ const GURL& origin,
+ quota::StorageType type,
+ GetUsageCallback* callback_ptr) {
+ DCHECK(callback_ptr);
+ DCHECK(!quota_manager_is_destroyed_);
+
+ scoped_ptr<GetUsageCallback> callback(callback_ptr);
+ if (!service_) {
+ callback->Run(0);
+ return;
+ }
+
+ if (!appcache_is_ready_) {
+ pending_usage_requests_.push_back(UsageRequest());
+ pending_usage_requests_.back().origin = origin;
+ pending_usage_requests_.back().type = type;
+ pending_usage_requests_.back().callback = callback.release();
+ return;
+ }
+
+ if (type == quota::kStorageTypePersistent) {
+ callback->Run(0);
+ return;
+ }
+
+ const AppCacheStorage::UsageMap* map = GetUsageMap();
+ AppCacheStorage::UsageMap::const_iterator found = map->find(origin);
+ if (found == map->end()) {
+ callback->Run(0);
+ return;
+ }
+ callback->Run(found->second);
+}
+
+void AppCacheQuotaClient::GetOriginsForType(
+ quota::StorageType type,
+ GetOriginsCallback* callback_ptr) {
+ GetOriginsHelper(type, std::string(), callback_ptr);
+}
+
+void AppCacheQuotaClient::GetOriginsForHost(
+ quota::StorageType type,
+ const std::string& host,
+ GetOriginsCallback* callback_ptr) {
+ DCHECK(!host.empty());
+ GetOriginsHelper(type, host, callback_ptr);
+}
+
+void AppCacheQuotaClient::DeleteOriginData(const GURL& origin,
+ quota::StorageType type,
+ DeletionCallback* callback_ptr) {
+ DCHECK(callback_ptr);
+ DCHECK(!quota_manager_is_destroyed_);
+
+ scoped_ptr<DeletionCallback> callback(callback_ptr);
+ if (!service_) {
+ callback->Run(quota::kQuotaErrorAbort);
+ return;
+ }
+
+ if (!appcache_is_ready_ || current_delete_request_callback_.get()) {
+ pending_delete_requests_.push_back(DeleteRequest());
+ pending_delete_requests_.back().origin = origin;
+ pending_delete_requests_.back().type = type;
+ pending_delete_requests_.back().callback = callback.release();
+ return;
+ }
+
+ current_delete_request_callback_.swap(callback);
+ if (type == quota::kStorageTypePersistent) {
+ DidDeleteAppCachesForOrigin(net::OK);
+ return;
+ }
+ service_->DeleteAppCachesForOrigin(origin, service_delete_callback_);
+}
+
+void AppCacheQuotaClient::DidDeleteAppCachesForOrigin(int rv) {
+ DCHECK(service_);
+ if (quota_manager_is_destroyed_)
+ return;
+
+ // Finish the request by calling our callers callback.
+ current_delete_request_callback_->Run(NetErrorCodeToQuotaStatus(rv));
+ current_delete_request_callback_.reset();
+ if (pending_delete_requests_.empty())
+ return;
+
+ // Start the next in the queue.
+ DeleteRequest& next_request = pending_delete_requests_.front();
+ current_delete_request_callback_.reset(next_request.callback);
+ service_->DeleteAppCachesForOrigin(next_request.origin,
+ service_delete_callback_);
+ pending_delete_requests_.pop_front();
+}
+
+void AppCacheQuotaClient::GetOriginsHelper(
+ quota::StorageType type,
+ const std::string& opt_host,
+ GetOriginsCallback* callback_ptr) {
+ DCHECK(callback_ptr);
+ DCHECK(!quota_manager_is_destroyed_);
+
+ scoped_ptr<GetOriginsCallback> callback(callback_ptr);
+ if (!service_) {
+ callback->Run(std::set<GURL>());
+ return;
+ }
+
+ if (!appcache_is_ready_) {
+ pending_origins_requests_.push_back(OriginsRequest());
+ pending_origins_requests_.back().opt_host = opt_host;
+ pending_origins_requests_.back().type = type;
+ pending_origins_requests_.back().callback = callback.release();
+ return;
+ }
+
+ if (type == quota::kStorageTypePersistent) {
+ callback->Run(std::set<GURL>());
+ return;
+ }
+
+ const AppCacheStorage::UsageMap* map = GetUsageMap();
+ std::set<GURL> origins;
+ for (AppCacheStorage::UsageMap::const_iterator iter = map->begin();
+ iter != map->end(); ++iter) {
+ if (opt_host.empty() || iter->first.host() == opt_host)
+ origins.insert(iter->first);
+ }
+ callback->Run(origins);
+}
+
+void AppCacheQuotaClient::ProcessPendingRequests() {
+ DCHECK(appcache_is_ready_);
+ while (!pending_usage_requests_.empty()) {
+ UsageRequest& request = pending_usage_requests_.front();
+ GetOriginUsage(request.origin, request.type, request.callback);
+ pending_usage_requests_.pop_front();
+ }
+ while (!pending_origins_requests_.empty()) {
+ OriginsRequest& request = pending_origins_requests_.front();
+ GetOriginsHelper(request.type, request.opt_host, request.callback);
+ pending_origins_requests_.pop_front();
+ }
+ if (!pending_delete_requests_.empty()) {
+ // Just start the first delete, others will follow upon completion.
+ DeleteRequest request = pending_delete_requests_.front();
+ pending_delete_requests_.pop_front();
+ DeleteOriginData(request.origin, request.type, request.callback);
+ }
+}
+
+void AppCacheQuotaClient::AbortPendingRequests() {
+ while (!pending_usage_requests_.empty()) {
+ pending_usage_requests_.front().callback->Run(0);
+ delete pending_usage_requests_.front().callback;
+ pending_usage_requests_.pop_front();
+ }
+ while (!pending_origins_requests_.empty()) {
+ pending_origins_requests_.front().callback->Run(std::set<GURL>());
+ delete pending_origins_requests_.front().callback;
+ pending_origins_requests_.pop_front();
+ }
+ while (!pending_delete_requests_.empty()) {
+ pending_delete_requests_.front().callback->Run(quota::kQuotaErrorAbort);
+ delete pending_delete_requests_.front().callback;
+ pending_delete_requests_.pop_front();
+ }
+}
+
+void AppCacheQuotaClient::DeletePendingRequests() {
+ while (!pending_usage_requests_.empty()) {
+ delete pending_usage_requests_.front().callback;
+ pending_usage_requests_.pop_front();
+ }
+ while (!pending_origins_requests_.empty()) {
+ delete pending_origins_requests_.front().callback;
+ pending_origins_requests_.pop_front();
+ }
+ while (!pending_delete_requests_.empty()) {
+ delete pending_delete_requests_.front().callback;
+ pending_delete_requests_.pop_front();
+ }
+}
+
+const AppCacheStorage::UsageMap* AppCacheQuotaClient::GetUsageMap() {
+ DCHECK(service_);
+ return service_->storage()->usage_map();
+}
+
+void AppCacheQuotaClient::NotifyAppCacheReady() {
+ appcache_is_ready_ = true;
+ ProcessPendingRequests();
+}
+
+void AppCacheQuotaClient::NotifyAppCacheDestroyed() {
+ service_ = NULL;
+ AbortPendingRequests();
+ if (current_delete_request_callback_.get()) {
+ current_delete_request_callback_->Run(quota::kQuotaErrorAbort);
+ current_delete_request_callback_.reset();
+ service_delete_callback_.release()->Cancel();
+ } else {
+ service_delete_callback_ = NULL;
+ }
+ if (quota_manager_is_destroyed_)
+ delete this;
+}
+
+} // namespace appcache
diff --git a/webkit/appcache/appcache_quota_client.h b/webkit/appcache/appcache_quota_client.h
new file mode 100644
index 0000000..44457793
--- /dev/null
+++ b/webkit/appcache/appcache_quota_client.h
@@ -0,0 +1,111 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_APPCACHE_APPCACHE_QUOTA_CLIENT_H_
+#define WEBKIT_APPCACHE_APPCACHE_QUOTA_CLIENT_H_
+
+#include <deque>
+#include <map>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/completion_callback.h"
+#include "webkit/appcache/appcache_storage.h"
+#include "webkit/quota/quota_client.h"
+#include "webkit/quota/quota_task.h"
+#include "webkit/quota/quota_types.h"
+
+namespace appcache {
+
+class AppCacheService;
+class AppCacheStorageImpl;
+class AppCacheQuotaClientTest;
+
+// A QuotaClient implementation to integrate the appcache service
+// with the quota management system. The QuotaClient interface is
+// used on the IO thread by the quota manager. This class deletes
+// itself when both the quota manager and the appcache service have
+// been destroyed.
+class AppCacheQuotaClient : public quota::QuotaClient {
+ public:
+ virtual ~AppCacheQuotaClient();
+
+ // QuotaClient method overrides
+ virtual ID id() const OVERRIDE;
+ virtual void OnQuotaManagerDestroyed();
+ virtual void GetOriginUsage(const GURL& origin,
+ quota::StorageType type,
+ GetUsageCallback* callback) OVERRIDE;
+ virtual void GetOriginsForType(quota::StorageType type,
+ GetOriginsCallback* callback) OVERRIDE;
+ virtual void GetOriginsForHost(quota::StorageType type,
+ const std::string& host,
+ GetOriginsCallback* callback) OVERRIDE;
+ virtual void DeleteOriginData(const GURL& origin,
+ quota::StorageType type,
+ DeletionCallback* callback) OVERRIDE;
+
+ private:
+ friend class AppCacheService; // for NotifyAppCacheIsDestroyed
+ friend class AppCacheStorageImpl; // for NotifyAppCacheIsReady
+ friend class AppCacheQuotaClientTest;
+
+ struct UsageRequest {
+ GURL origin;
+ quota::StorageType type;
+ GetUsageCallback* callback;
+ };
+ struct OriginsRequest {
+ quota::StorageType type;
+ std::string opt_host;
+ GetOriginsCallback* callback;
+ };
+ struct DeleteRequest {
+ GURL origin;
+ quota::StorageType type;
+ DeletionCallback* callback;
+ };
+ typedef std::deque<UsageRequest> UsageRequestQueue;
+ typedef std::deque<OriginsRequest> OriginsRequestQueue;
+ typedef std::deque<DeleteRequest> DeleteRequestQueue;
+
+ explicit AppCacheQuotaClient(AppCacheService* service);
+
+ void DidDeleteAppCachesForOrigin(int rv);
+ void GetOriginsHelper(quota::StorageType type,
+ const std::string& opt_host,
+ GetOriginsCallback* callback_ptr);
+ void ProcessPendingRequests();
+ void AbortPendingRequests();
+ void DeletePendingRequests();
+ const AppCacheStorage::UsageMap* GetUsageMap();
+
+ // For use by appcache internals during initialization and shutdown.
+ void NotifyAppCacheReady();
+ void NotifyAppCacheDestroyed();
+
+ // Prior to appcache service being ready, we have to queue
+ // up reqeusts and defer acting on them until we're ready.
+ UsageRequestQueue pending_usage_requests_;
+ OriginsRequestQueue pending_origins_requests_;
+ DeleteRequestQueue pending_delete_requests_;
+
+ // And once it's ready, we can only handle one delete request at a time,
+ // so we queue up additional requests while one is in already in progress.
+ scoped_ptr<DeletionCallback> current_delete_request_callback_;
+ scoped_refptr<net::CancelableCompletionCallback<AppCacheQuotaClient> >
+ service_delete_callback_;
+
+ AppCacheService* service_;
+ bool appcache_is_ready_;
+ bool quota_manager_is_destroyed_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppCacheQuotaClient);
+};
+
+} // namespace appcache
+
+#endif // WEBKIT_APPCACHE_APPCACHE_QUOTA_CLIENT_H_
diff --git a/webkit/appcache/appcache_quota_client_unittest.cc b/webkit/appcache/appcache_quota_client_unittest.cc
new file mode 100644
index 0000000..9ab1b4b
--- /dev/null
+++ b/webkit/appcache/appcache_quota_client_unittest.cc
@@ -0,0 +1,429 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <map>
+
+#include "base/memory/scoped_callback_factory.h"
+#include "base/message_loop.h"
+#include "base/message_loop_proxy.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/appcache/appcache_quota_client.h"
+#include "webkit/appcache/mock_appcache_service.h"
+
+namespace appcache {
+
+// Declared to shorten the line lengths.
+static const quota::StorageType kTemp = quota::kStorageTypeTemporary;
+static const quota::StorageType kPerm = quota::kStorageTypePersistent;
+
+// Base class for our test fixtures.
+class AppCacheQuotaClientTest : public testing::Test {
+ public:
+ const GURL kOriginA;
+ const GURL kOriginB;
+ const GURL kOriginOther;
+
+ AppCacheQuotaClientTest()
+ : kOriginA("http://host"),
+ kOriginB("http://host:8000"),
+ kOriginOther("http://other"),
+ usage_(0),
+ delete_status_(quota::kQuotaStatusUnknown),
+ num_get_origin_usage_completions_(0),
+ num_get_origins_completions_(0),
+ num_delete_origins_completions_(0),
+ callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
+ }
+
+ int64 GetOriginUsage(
+ quota::QuotaClient* client,
+ const GURL& origin,
+ quota::StorageType type) {
+ usage_ = -1;
+ AsyncGetOriginUsage(client, origin, type);
+ MessageLoop::current()->RunAllPending();
+ return usage_;
+ }
+
+ const std::set<GURL>& GetOriginsForType(
+ quota::QuotaClient* client,
+ quota::StorageType type) {
+ origins_.clear();
+ AsyncGetOriginsForType(client, type);
+ MessageLoop::current()->RunAllPending();
+ return origins_;
+ }
+
+ const std::set<GURL>& GetOriginsForHost(
+ quota::QuotaClient* client,
+ quota::StorageType type,
+ const std::string& host) {
+ origins_.clear();
+ AsyncGetOriginsForHost(client, type, host);
+ MessageLoop::current()->RunAllPending();
+ return origins_;
+ }
+
+ quota::QuotaStatusCode DeleteOriginData(
+ quota::QuotaClient* client,
+ quota::StorageType type,
+ const GURL& origin) {
+ delete_status_ = quota::kQuotaStatusUnknown;
+ AsyncDeleteOriginData(client, type, origin);
+ MessageLoop::current()->RunAllPending();
+ return delete_status_;
+ }
+
+ void AsyncGetOriginUsage(
+ quota::QuotaClient* client,
+ const GURL& origin,
+ quota::StorageType type) {
+ client->GetOriginUsage(origin, type,
+ callback_factory_.NewCallback(
+ &AppCacheQuotaClientTest::OnGetOriginUsageComplete));
+ }
+
+ void AsyncGetOriginsForType(
+ quota::QuotaClient* client,
+ quota::StorageType type) {
+ client->GetOriginsForType(type,
+ callback_factory_.NewCallback(
+ &AppCacheQuotaClientTest::OnGetOriginsComplete));
+ }
+
+ void AsyncGetOriginsForHost(
+ quota::QuotaClient* client,
+ quota::StorageType type,
+ const std::string& host) {
+ client->GetOriginsForHost(type, host,
+ callback_factory_.NewCallback(
+ &AppCacheQuotaClientTest::OnGetOriginsComplete));
+ }
+
+ void AsyncDeleteOriginData(
+ quota::QuotaClient* client,
+ quota::StorageType type,
+ const GURL& origin) {
+ client->DeleteOriginData(origin, type,
+ callback_factory_.NewCallback(
+ &AppCacheQuotaClientTest::OnDeleteOriginDataComplete));
+ }
+
+ void SetUsageMapEntry(const GURL& origin, int64 usage) {
+ mock_service_.storage()->usage_map_[origin] = usage;
+ }
+
+ AppCacheQuotaClient* CreateClient() {
+ return new AppCacheQuotaClient(&mock_service_);
+ }
+
+ void Call_NotifyAppCacheReady(AppCacheQuotaClient* client) {
+ client->NotifyAppCacheReady();
+ }
+
+ void Call_NotifyAppCacheDestroyed(AppCacheQuotaClient* client) {
+ client->NotifyAppCacheDestroyed();
+ }
+
+ void Call_OnQuotaManagerDestroyed(AppCacheQuotaClient* client) {
+ client->OnQuotaManagerDestroyed();
+ }
+
+ protected:
+ void OnGetOriginUsageComplete(int64 usage) {
+ ++num_get_origin_usage_completions_;
+ usage_ = usage;
+ }
+
+ void OnGetOriginsComplete(const std::set<GURL>& origins) {
+ ++num_get_origins_completions_;
+ origins_ = origins;
+ }
+
+ void OnDeleteOriginDataComplete(quota::QuotaStatusCode status) {
+ ++num_delete_origins_completions_;
+ delete_status_ = status;
+ }
+
+ int64 usage_;
+ std::set<GURL> origins_;
+ quota::QuotaStatusCode delete_status_;
+ int num_get_origin_usage_completions_;
+ int num_get_origins_completions_;
+ int num_delete_origins_completions_;
+ MockAppCacheService mock_service_;
+ base::ScopedCallbackFactory<AppCacheQuotaClientTest> callback_factory_;
+};
+
+
+TEST_F(AppCacheQuotaClientTest, BasicCreateDestroy) {
+ AppCacheQuotaClient* client = CreateClient();
+ Call_NotifyAppCacheReady(client);
+ Call_OnQuotaManagerDestroyed(client);
+ Call_NotifyAppCacheDestroyed(client);
+}
+
+TEST_F(AppCacheQuotaClientTest, EmptyService) {
+ AppCacheQuotaClient* client = CreateClient();
+ Call_NotifyAppCacheReady(client);
+
+ EXPECT_EQ(0, GetOriginUsage(client, kOriginA, kTemp));
+ EXPECT_EQ(0, GetOriginUsage(client, kOriginA, kPerm));
+ EXPECT_TRUE(GetOriginsForType(client, kTemp).empty());
+ EXPECT_TRUE(GetOriginsForType(client, kPerm).empty());
+ EXPECT_TRUE(GetOriginsForHost(client, kTemp, kOriginA.host()).empty());
+ EXPECT_TRUE(GetOriginsForHost(client, kPerm, kOriginA.host()).empty());
+ EXPECT_EQ(quota::kQuotaStatusOk, DeleteOriginData(client, kTemp, kOriginA));
+ EXPECT_EQ(quota::kQuotaStatusOk, DeleteOriginData(client, kPerm, kOriginA));
+
+ Call_NotifyAppCacheDestroyed(client);
+ Call_OnQuotaManagerDestroyed(client);
+}
+
+TEST_F(AppCacheQuotaClientTest, NoService) {
+ AppCacheQuotaClient* client = CreateClient();
+ Call_NotifyAppCacheReady(client);
+ Call_NotifyAppCacheDestroyed(client);
+
+ EXPECT_EQ(0, GetOriginUsage(client, kOriginA, kTemp));
+ EXPECT_EQ(0, GetOriginUsage(client, kOriginA, kPerm));
+ EXPECT_TRUE(GetOriginsForType(client, kTemp).empty());
+ EXPECT_TRUE(GetOriginsForType(client, kPerm).empty());
+ EXPECT_TRUE(GetOriginsForHost(client, kTemp, kOriginA.host()).empty());
+ EXPECT_TRUE(GetOriginsForHost(client, kPerm, kOriginA.host()).empty());
+ EXPECT_EQ(quota::kQuotaErrorAbort,
+ DeleteOriginData(client, kTemp, kOriginA));
+ EXPECT_EQ(quota::kQuotaErrorAbort,
+ DeleteOriginData(client, kPerm, kOriginA));
+
+ Call_OnQuotaManagerDestroyed(client);
+}
+
+TEST_F(AppCacheQuotaClientTest, GetOriginUsage) {
+ AppCacheQuotaClient* client = CreateClient();
+ Call_NotifyAppCacheReady(client);
+
+ SetUsageMapEntry(kOriginA, 1000);
+ EXPECT_EQ(1000, GetOriginUsage(client, kOriginA, kTemp));
+ EXPECT_EQ(0, GetOriginUsage(client, kOriginA, kPerm));
+
+ Call_NotifyAppCacheDestroyed(client);
+ Call_OnQuotaManagerDestroyed(client);
+}
+
+TEST_F(AppCacheQuotaClientTest, GetOriginsForHost) {
+ AppCacheQuotaClient* client = CreateClient();
+ Call_NotifyAppCacheReady(client);
+
+ EXPECT_EQ(kOriginA.host(), kOriginB.host());
+ EXPECT_NE(kOriginA.host(), kOriginOther.host());
+
+ std::set<GURL> origins = GetOriginsForHost(client, kTemp, kOriginA.host());
+ EXPECT_TRUE(origins.empty());
+
+ SetUsageMapEntry(kOriginA, 1000);
+ SetUsageMapEntry(kOriginB, 10);
+ SetUsageMapEntry(kOriginOther, 500);
+
+ origins = GetOriginsForHost(client, kTemp, kOriginA.host());
+ EXPECT_EQ(2ul, origins.size());
+ EXPECT_TRUE(origins.find(kOriginA) != origins.end());
+ EXPECT_TRUE(origins.find(kOriginB) != origins.end());
+
+ origins = GetOriginsForHost(client, kTemp, kOriginOther.host());
+ EXPECT_EQ(1ul, origins.size());
+ EXPECT_TRUE(origins.find(kOriginOther) != origins.end());
+
+ origins = GetOriginsForHost(client, kPerm, kOriginA.host());
+ EXPECT_TRUE(origins.empty());
+
+ Call_NotifyAppCacheDestroyed(client);
+ Call_OnQuotaManagerDestroyed(client);
+}
+
+TEST_F(AppCacheQuotaClientTest, GetOriginsForType) {
+ AppCacheQuotaClient* client = CreateClient();
+ Call_NotifyAppCacheReady(client);
+
+ EXPECT_TRUE(GetOriginsForType(client, kTemp).empty());
+ EXPECT_TRUE(GetOriginsForType(client, kPerm).empty());
+
+ SetUsageMapEntry(kOriginA, 1000);
+ SetUsageMapEntry(kOriginB, 10);
+
+ std::set<GURL> origins = GetOriginsForType(client, kTemp);
+ EXPECT_EQ(2ul, origins.size());
+ EXPECT_TRUE(origins.find(kOriginA) != origins.end());
+ EXPECT_TRUE(origins.find(kOriginB) != origins.end());
+
+ EXPECT_TRUE(GetOriginsForType(client, kPerm).empty());
+
+ Call_NotifyAppCacheDestroyed(client);
+ Call_OnQuotaManagerDestroyed(client);
+}
+
+TEST_F(AppCacheQuotaClientTest, DeleteOriginData) {
+ AppCacheQuotaClient* client = CreateClient();
+ Call_NotifyAppCacheReady(client);
+
+ // Perm deletions are short circuited in the Client and
+ // should not reach the AppCacheService.
+ EXPECT_EQ(quota::kQuotaStatusOk,
+ DeleteOriginData(client, kPerm, kOriginA));
+ EXPECT_EQ(0, mock_service_.delete_called_count());
+
+ EXPECT_EQ(quota::kQuotaStatusOk,
+ DeleteOriginData(client, kTemp, kOriginA));
+ EXPECT_EQ(1, mock_service_.delete_called_count());
+
+ mock_service_.set_mock_delete_appcaches_for_origin_result(
+ net::ERR_ABORTED);
+ EXPECT_EQ(quota::kQuotaErrorAbort,
+ DeleteOriginData(client, kTemp, kOriginA));
+ EXPECT_EQ(2, mock_service_.delete_called_count());
+
+ Call_OnQuotaManagerDestroyed(client);
+ Call_NotifyAppCacheDestroyed(client);
+}
+
+TEST_F(AppCacheQuotaClientTest, PendingRequests) {
+ AppCacheQuotaClient* client = CreateClient();
+
+ SetUsageMapEntry(kOriginA, 1000);
+ SetUsageMapEntry(kOriginB, 10);
+ SetUsageMapEntry(kOriginOther, 500);
+
+ // Queue up some reqeusts.
+ AsyncGetOriginUsage(client, kOriginA, kPerm);
+ AsyncGetOriginUsage(client, kOriginB, kTemp);
+ AsyncGetOriginsForType(client, kPerm);
+ AsyncGetOriginsForType(client, kTemp);
+ AsyncGetOriginsForHost(client, kTemp, kOriginA.host());
+ AsyncGetOriginsForHost(client, kTemp, kOriginOther.host());
+ AsyncDeleteOriginData(client, kTemp, kOriginA);
+ AsyncDeleteOriginData(client, kPerm, kOriginA);
+ AsyncDeleteOriginData(client, kTemp, kOriginB);
+
+ EXPECT_EQ(0, num_get_origin_usage_completions_);
+ EXPECT_EQ(0, num_get_origins_completions_);
+ EXPECT_EQ(0, num_delete_origins_completions_);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(0, num_get_origin_usage_completions_);
+ EXPECT_EQ(0, num_get_origins_completions_);
+ EXPECT_EQ(0, num_delete_origins_completions_);
+
+ // Pending requests should get serviced when the appcache is ready.
+ Call_NotifyAppCacheReady(client);
+ EXPECT_EQ(2, num_get_origin_usage_completions_);
+ EXPECT_EQ(4, num_get_origins_completions_);
+ EXPECT_EQ(0, num_delete_origins_completions_);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(3, num_delete_origins_completions_); // deletes are really async
+
+ // They should be serviced in order requested.
+ EXPECT_EQ(10, usage_);
+ EXPECT_EQ(1ul, origins_.size());
+ EXPECT_TRUE(origins_.find(kOriginOther) != origins_.end());
+
+ Call_NotifyAppCacheDestroyed(client);
+ Call_OnQuotaManagerDestroyed(client);
+}
+
+TEST_F(AppCacheQuotaClientTest, DestroyServiceWithPending) {
+ AppCacheQuotaClient* client = CreateClient();
+
+ SetUsageMapEntry(kOriginA, 1000);
+ SetUsageMapEntry(kOriginB, 10);
+ SetUsageMapEntry(kOriginOther, 500);
+
+ // Queue up some reqeusts prior to being ready.
+ AsyncGetOriginUsage(client, kOriginA, kPerm);
+ AsyncGetOriginUsage(client, kOriginB, kTemp);
+ AsyncGetOriginsForType(client, kPerm);
+ AsyncGetOriginsForType(client, kTemp);
+ AsyncGetOriginsForHost(client, kTemp, kOriginA.host());
+ AsyncGetOriginsForHost(client, kTemp, kOriginOther.host());
+ AsyncDeleteOriginData(client, kTemp, kOriginA);
+ AsyncDeleteOriginData(client, kPerm, kOriginA);
+ AsyncDeleteOriginData(client, kTemp, kOriginB);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(0, num_get_origin_usage_completions_);
+ EXPECT_EQ(0, num_get_origins_completions_);
+ EXPECT_EQ(0, num_delete_origins_completions_);
+
+ // Kill the service.
+ Call_NotifyAppCacheDestroyed(client);
+
+ // All should have been aborted and called completion.
+ EXPECT_EQ(2, num_get_origin_usage_completions_);
+ EXPECT_EQ(4, num_get_origins_completions_);
+ EXPECT_EQ(3, num_delete_origins_completions_);
+ EXPECT_EQ(0, usage_);
+ EXPECT_TRUE(origins_.empty());
+ EXPECT_EQ(quota::kQuotaErrorAbort, delete_status_);
+
+ Call_OnQuotaManagerDestroyed(client);
+}
+
+TEST_F(AppCacheQuotaClientTest, DestroyQuotaManagerWithPending) {
+ AppCacheQuotaClient* client = CreateClient();
+
+ SetUsageMapEntry(kOriginA, 1000);
+ SetUsageMapEntry(kOriginB, 10);
+ SetUsageMapEntry(kOriginOther, 500);
+
+ // Queue up some reqeusts prior to being ready.
+ AsyncGetOriginUsage(client, kOriginA, kPerm);
+ AsyncGetOriginUsage(client, kOriginB, kTemp);
+ AsyncGetOriginsForType(client, kPerm);
+ AsyncGetOriginsForType(client, kTemp);
+ AsyncGetOriginsForHost(client, kTemp, kOriginA.host());
+ AsyncGetOriginsForHost(client, kTemp, kOriginOther.host());
+ AsyncDeleteOriginData(client, kTemp, kOriginA);
+ AsyncDeleteOriginData(client, kPerm, kOriginA);
+ AsyncDeleteOriginData(client, kTemp, kOriginB);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(0, num_get_origin_usage_completions_);
+ EXPECT_EQ(0, num_get_origins_completions_);
+ EXPECT_EQ(0, num_delete_origins_completions_);
+
+ // Kill the quota manager.
+ Call_OnQuotaManagerDestroyed(client);
+ Call_NotifyAppCacheReady(client);
+
+ // Callbacks should be deleted and not called.
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(0, num_get_origin_usage_completions_);
+ EXPECT_EQ(0, num_get_origins_completions_);
+ EXPECT_EQ(0, num_delete_origins_completions_);
+
+ Call_NotifyAppCacheDestroyed(client);
+}
+
+TEST_F(AppCacheQuotaClientTest, DestroyWithDeleteInProgress) {
+ AppCacheQuotaClient* client = CreateClient();
+ Call_NotifyAppCacheReady(client);
+
+ // Start an async delete.
+ AsyncDeleteOriginData(client, kTemp, kOriginB);
+ EXPECT_EQ(0, num_delete_origins_completions_);
+
+ // Kill the service.
+ Call_NotifyAppCacheDestroyed(client);
+
+ // Should have been aborted.
+ EXPECT_EQ(1, num_delete_origins_completions_);
+ EXPECT_EQ(quota::kQuotaErrorAbort, delete_status_);
+
+ // A real completion callback from the service should
+ // be dropped if it comes in after NotifyAppCacheDestroyed.
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(1, num_delete_origins_completions_);
+ EXPECT_EQ(quota::kQuotaErrorAbort, delete_status_);
+
+ Call_OnQuotaManagerDestroyed(client);
+}
+
+} // namespace appcache
diff --git a/webkit/appcache/appcache_service.cc b/webkit/appcache/appcache_service.cc
index fbfd379..925e693 100644
--- a/webkit/appcache/appcache_service.cc
+++ b/webkit/appcache/appcache_service.cc
@@ -9,6 +9,7 @@
#include "base/stl_util-inl.h"
#include "webkit/appcache/appcache_backend_impl.h"
#include "webkit/appcache/appcache_entry.h"
+#include "webkit/appcache/appcache_quota_client.h"
#include "webkit/appcache/appcache_storage_impl.h"
#include "webkit/quota/quota_manager.h"
#include "webkit/quota/special_storage_policy.h"
@@ -58,7 +59,10 @@ class AppCacheService::AsyncHelper
};
void AppCacheService::AsyncHelper::Cancel() {
- CallCallback(net::ERR_ABORTED);
+ if (callback_) {
+ callback_->Run(net::ERR_ABORTED);
+ callback_ = NULL;
+ }
service_->storage()->CancelDelegateCallbacks(this);
service_ = NULL;
}
@@ -143,6 +147,95 @@ void AppCacheService::DeleteHelper::OnGroupMadeObsolete(
delete this;
}
+// DeleteOriginHelper -------
+
+class AppCacheService::DeleteOriginHelper : public AsyncHelper {
+ public:
+ DeleteOriginHelper(
+ AppCacheService* service, const GURL& origin,
+ net::CompletionCallback* callback)
+ : AsyncHelper(service, callback), origin_(origin),
+ successes_(0), failures_(0) {
+ }
+
+ virtual void Start() {
+ // We start by listing all caches, continues in OnAllInfo().
+ service_->storage()->GetAllInfo(this);
+ }
+
+ private:
+ // AppCacheStorage::Delegate methods
+ virtual void OnAllInfo(AppCacheInfoCollection* collection);
+ virtual void OnGroupLoaded(
+ appcache::AppCacheGroup* group, const GURL& manifest_url);
+ virtual void OnGroupMadeObsolete(
+ appcache::AppCacheGroup* group, bool success);
+
+ void CacheCompleted(bool success);
+
+ GURL origin_;
+ AppCacheInfoVector caches_to_delete_;
+ int successes_;
+ int failures_;
+ DISALLOW_COPY_AND_ASSIGN(DeleteOriginHelper);
+};
+
+void AppCacheService::DeleteOriginHelper::OnAllInfo(
+ AppCacheInfoCollection* collection) {
+ if (!collection) {
+ // Failed to get a listing.
+ CallCallback(net::ERR_FAILED);
+ delete this;
+ return;
+ }
+ std::map<GURL, AppCacheInfoVector>::iterator found =
+ collection->infos_by_origin.find(origin_);
+ if (found == collection->infos_by_origin.end() || found->second.empty()) {
+ // No caches for this origin.
+ CallCallback(net::OK);
+ delete this;
+ return;
+ }
+
+ // We have some caches to delete.
+ caches_to_delete_.swap(found->second);
+ successes_ = 0;
+ failures_ = 0;
+ for (AppCacheInfoVector::iterator iter = caches_to_delete_.begin();
+ iter != caches_to_delete_.end(); ++iter) {
+ service_->storage()->LoadOrCreateGroup(iter->manifest_url, this);
+ }
+}
+
+void AppCacheService::DeleteOriginHelper::OnGroupLoaded(
+ appcache::AppCacheGroup* group, const GURL& manifest_url) {
+ if (group) {
+ group->set_being_deleted(true);
+ group->CancelUpdate();
+ service_->storage()->MakeGroupObsolete(group, this);
+ } else {
+ CacheCompleted(false);
+ }
+}
+
+void AppCacheService::DeleteOriginHelper::OnGroupMadeObsolete(
+ appcache::AppCacheGroup* group, bool success) {
+ CacheCompleted(success);
+}
+
+void AppCacheService::DeleteOriginHelper::CacheCompleted(bool success) {
+ if (success)
+ ++successes_;
+ else
+ ++failures_;
+ if ((successes_ + failures_) < static_cast<int>(caches_to_delete_.size()))
+ return;
+
+ CallCallback(!failures_ ? net::OK : net::ERR_FAILED);
+ delete this;
+}
+
+
// GetInfoHelper -------
class AppCacheService::GetInfoHelper : AsyncHelper {
@@ -177,9 +270,13 @@ void AppCacheService::GetInfoHelper::OnAllInfo(
// AppCacheService -------
AppCacheService::AppCacheService(quota::QuotaManagerProxy* quota_manager_proxy)
- : appcache_policy_(NULL), quota_manager_proxy_(quota_manager_proxy),
+ : appcache_policy_(NULL), quota_client_(NULL),
+ quota_manager_proxy_(quota_manager_proxy),
request_context_(NULL) {
- // TODO(michaeln): Create and register our QuotaClient.
+ if (quota_manager_proxy_) {
+ quota_client_ = new AppCacheQuotaClient(this);
+ quota_manager_proxy_->RegisterClient(quota_client_);
+ }
}
AppCacheService::~AppCacheService() {
@@ -188,6 +285,8 @@ AppCacheService::~AppCacheService() {
pending_helpers_.end(),
std::mem_fun(&AsyncHelper::Cancel));
STLDeleteElements(&pending_helpers_);
+ if (quota_client_)
+ quota_client_->NotifyAppCacheDestroyed();
}
void AppCacheService::Initialize(const FilePath& cache_directory,
@@ -219,6 +318,12 @@ void AppCacheService::DeleteAppCacheGroup(const GURL& manifest_url,
helper->Start();
}
+void AppCacheService::DeleteAppCachesForOrigin(
+ const GURL& origin, net::CompletionCallback* callback) {
+ DeleteOriginHelper* helper = new DeleteOriginHelper(this, origin, callback);
+ helper->Start();
+}
+
void AppCacheService::set_special_storage_policy(
quota::SpecialStoragePolicy* policy) {
special_storage_policy_ = policy;
diff --git a/webkit/appcache/appcache_service.h b/webkit/appcache/appcache_service.h
index 2606a7a..4952c94 100644
--- a/webkit/appcache/appcache_service.h
+++ b/webkit/appcache/appcache_service.h
@@ -35,6 +35,7 @@ class SpecialStoragePolicy;
namespace appcache {
class AppCacheBackendImpl;
+class AppCacheQuotaClient;
class AppCachePolicy;
// Refcounted container to avoid copying the collection in callbacks.
@@ -84,6 +85,12 @@ class AppCacheService {
void DeleteAppCacheGroup(const GURL& manifest_url,
net::CompletionCallback* callback);
+ // Deletes all appcaches for the origin, 'callback' is invoked upon
+ // completion. This method always completes asynchronously.
+ // (virtual for unittesting)
+ virtual void DeleteAppCachesForOrigin(const GURL& origin,
+ net::CompletionCallback* callback);
+
// Context for use during cache updates, should only be accessed
// on the IO thread. We do NOT add a reference to the request context,
// it is the callers responsibility to ensure that the pointer
@@ -110,6 +117,10 @@ class AppCacheService {
return quota_manager_proxy_.get();
}
+ AppCacheQuotaClient* quota_client() const {
+ return quota_client_;
+ }
+
// Each child process in chrome uses a distinct backend instance.
// See chrome/browser/AppCacheDispatcherHost.
void RegisterBackend(AppCacheBackendImpl* backend_impl);
@@ -122,15 +133,20 @@ class AppCacheService {
AppCacheStorage* storage() const { return storage_.get(); }
protected:
+ friend class AppCacheStorageImplTest;
+ friend class AppCacheServiceTest;
+
class AsyncHelper;
class CanHandleOfflineHelper;
class DeleteHelper;
+ class DeleteOriginHelper;
class GetInfoHelper;
typedef std::set<AsyncHelper*> PendingAsyncHelpers;
typedef std::map<int, AppCacheBackendImpl*> BackendMap;
AppCachePolicy* appcache_policy_;
+ AppCacheQuotaClient* quota_client_;
scoped_ptr<AppCacheStorage> storage_;
scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
scoped_refptr<quota::QuotaManagerProxy> quota_manager_proxy_;
diff --git a/webkit/appcache/appcache_service_unittest.cc b/webkit/appcache/appcache_service_unittest.cc
new file mode 100644
index 0000000..2d0723b
--- /dev/null
+++ b/webkit/appcache/appcache_service_unittest.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/appcache/appcache_service.h"
+
+#include "base/message_loop.h"
+#include "net/base/completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/appcache/mock_appcache_storage.h"
+
+namespace appcache {
+
+class AppCacheServiceTest : public testing::Test {
+ public:
+ AppCacheServiceTest()
+ : kOrigin("http://hello/"),
+ service_(new AppCacheService(NULL)),
+ delete_result_(net::OK), delete_completion_count_(0),
+ ALLOW_THIS_IN_INITIALIZER_LIST(deletion_callback_(
+ this, &AppCacheServiceTest::OnDeleteAppCachesComplete)) {
+ // Setup to use mock storage.
+ service_->storage_.reset(new MockAppCacheStorage(service_.get()));
+ }
+
+ void OnDeleteAppCachesComplete(int result) {
+ delete_result_ = result;
+ ++delete_completion_count_;
+ }
+
+ MockAppCacheStorage* mock_storage() {
+ return static_cast<MockAppCacheStorage*>(service_->storage());
+ }
+
+ const GURL kOrigin;
+
+ scoped_ptr<AppCacheService> service_;
+ int delete_result_;
+ int delete_completion_count_;
+ net::CompletionCallbackImpl<AppCacheServiceTest> deletion_callback_;
+};
+
+TEST_F(AppCacheServiceTest, DeleteAppCachesForOrigin) {
+ // Without giving mock storage simiulated info, should fail.
+ service_->DeleteAppCachesForOrigin(kOrigin, &deletion_callback_);
+ EXPECT_EQ(0, delete_completion_count_);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(1, delete_completion_count_);
+ EXPECT_EQ(net::ERR_FAILED, delete_result_);
+ delete_completion_count_ = 0;
+
+ // Should succeed given an empty info collection.
+ mock_storage()->SimulateGetAllInfo(new AppCacheInfoCollection);
+ service_->DeleteAppCachesForOrigin(kOrigin, &deletion_callback_);
+ EXPECT_EQ(0, delete_completion_count_);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(1, delete_completion_count_);
+ EXPECT_EQ(net::OK, delete_result_);
+ delete_completion_count_ = 0;
+
+ scoped_refptr<AppCacheInfoCollection> info(new AppCacheInfoCollection);
+
+ // Should succeed given a non-empty info collection.
+ AppCacheInfo mock_manifest_1;
+ AppCacheInfo mock_manifest_2;
+ AppCacheInfo mock_manifest_3;
+ mock_manifest_1.manifest_url = kOrigin.Resolve("manifest1");
+ mock_manifest_2.manifest_url = kOrigin.Resolve("manifest2");
+ mock_manifest_3.manifest_url = kOrigin.Resolve("manifest3");
+ AppCacheInfoVector info_vector;
+ info_vector.push_back(mock_manifest_1);
+ info_vector.push_back(mock_manifest_2);
+ info_vector.push_back(mock_manifest_3);
+ info->infos_by_origin[kOrigin] = info_vector;
+ mock_storage()->SimulateGetAllInfo(info);
+ service_->DeleteAppCachesForOrigin(kOrigin, &deletion_callback_);
+ EXPECT_EQ(0, delete_completion_count_);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(1, delete_completion_count_);
+ EXPECT_EQ(net::OK, delete_result_);
+ delete_completion_count_ = 0;
+
+ // Should fail if storage fails to delete.
+ info->infos_by_origin[kOrigin] = info_vector;
+ mock_storage()->SimulateGetAllInfo(info);
+ mock_storage()->SimulateMakeGroupObsoleteFailure();
+ service_->DeleteAppCachesForOrigin(kOrigin, &deletion_callback_);
+ EXPECT_EQ(0, delete_completion_count_);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(1, delete_completion_count_);
+ EXPECT_EQ(net::ERR_FAILED, delete_result_);
+ delete_completion_count_ = 0;
+
+ // Should complete with abort error if the service is deleted
+ // prior to a delete completion.
+ service_->DeleteAppCachesForOrigin(kOrigin, &deletion_callback_);
+ EXPECT_EQ(0, delete_completion_count_);
+ service_.reset(); // kill it
+ EXPECT_EQ(1, delete_completion_count_);
+ EXPECT_EQ(net::ERR_ABORTED, delete_result_);
+ delete_completion_count_ = 0;
+
+ // Let any tasks lingering from the sudden deletion run and verify
+ // no other completion calls occur.
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(0, delete_completion_count_);
+}
+
+} // namespace appcache
diff --git a/webkit/appcache/appcache_storage.cc b/webkit/appcache/appcache_storage.cc
index a30088f..a4fde29 100644
--- a/webkit/appcache/appcache_storage.cc
+++ b/webkit/appcache/appcache_storage.cc
@@ -6,6 +6,9 @@
#include "base/stl_util-inl.h"
#include "webkit/appcache/appcache_response.h"
+#include "webkit/appcache/appcache_service.h"
+#include "webkit/quota/quota_client.h"
+#include "webkit/quota/quota_manager.h"
namespace appcache {
@@ -87,5 +90,42 @@ void AppCacheStorage::LoadResponseInfo(
info_load->StartIfNeeded();
}
+void AppCacheStorage::UpdateUsageMapAndNotify(
+ const GURL& origin, int64 new_usage) {
+ DCHECK_GE(new_usage, 0);
+ int64 old_usage = usage_map_[origin];
+ if (new_usage > 0)
+ usage_map_[origin] = new_usage;
+ else
+ usage_map_.erase(origin);
+ if (new_usage != old_usage && service()->quota_manager_proxy()) {
+ service()->quota_manager_proxy()->NotifyStorageModified(
+ quota::QuotaClient::kAppcache,
+ origin, quota::kStorageTypeTemporary,
+ new_usage - old_usage);
+ }
+}
+
+void AppCacheStorage::ClearUsageMapAndNotify() {
+ if (service()->quota_manager_proxy()) {
+ for (UsageMap::const_iterator iter = usage_map_.begin();
+ iter != usage_map_.end(); ++iter) {
+ service()->quota_manager_proxy()->NotifyStorageModified(
+ quota::QuotaClient::kAppcache,
+ iter->first, quota::kStorageTypeTemporary,
+ -(iter->second));
+ }
+ }
+ usage_map_.clear();
+}
+
+void AppCacheStorage::NotifyStorageAccessed(const GURL& origin) {
+ if (service()->quota_manager_proxy() &&
+ usage_map_.find(origin) != usage_map_.end())
+ service()->quota_manager_proxy()->NotifyStorageAccessed(
+ quota::QuotaClient::kAppcache,
+ origin, quota::kStorageTypeTemporary);
+}
+
} // namespace appcache
diff --git a/webkit/appcache/appcache_storage.h b/webkit/appcache/appcache_storage.h
index 5672546..9ddb7c3 100644
--- a/webkit/appcache/appcache_storage.h
+++ b/webkit/appcache/appcache_storage.h
@@ -31,6 +31,7 @@ struct HttpResponseInfoIOBuffer;
class AppCacheStorage {
public:
+ typedef std::map<GURL, int64> UsageMap;
class Delegate {
public:
@@ -180,10 +181,14 @@ class AppCacheStorage {
// The working set of object instances currently in memory.
AppCacheWorkingSet* working_set() { return &working_set_; }
+ // A map of origins to usage.
+ const UsageMap* usage_map() { return &usage_map_; }
+
// Simple ptr back to the service object that owns us.
AppCacheService* service() { return service_; }
protected:
+ friend class AppCacheQuotaClientTest;
friend class AppCacheResponseTest;
friend class AppCacheStorageTest;
@@ -280,11 +285,17 @@ class AppCacheStorage {
return ++last_response_id_;
}
+ // Helpers to query and notify the QuotaManager.
+ void UpdateUsageMapAndNotify(const GURL& origin, int64 new_usage);
+ void ClearUsageMapAndNotify();
+ void NotifyStorageAccessed(const GURL& origin);
+
// The last storage id used for different object types.
int64 last_cache_id_;
int64 last_group_id_;
int64 last_response_id_;
+ UsageMap usage_map_; // maps origin to usage
AppCacheWorkingSet working_set_;
AppCacheService* service_;
DelegateReferenceMap delegate_references_;
@@ -294,6 +305,7 @@ class AppCacheStorage {
static const int64 kUnitializedId;
FRIEND_TEST_ALL_PREFIXES(AppCacheStorageTest, DelegateReferences);
+ FRIEND_TEST_ALL_PREFIXES(AppCacheStorageTest, UsageMap);
DISALLOW_COPY_AND_ASSIGN(AppCacheStorage);
};
diff --git a/webkit/appcache/appcache_storage_impl.cc b/webkit/appcache/appcache_storage_impl.cc
index fe325a1..ef2a1b5 100644
--- a/webkit/appcache/appcache_storage_impl.cc
+++ b/webkit/appcache/appcache_storage_impl.cc
@@ -4,6 +4,8 @@
#include "webkit/appcache/appcache_storage_impl.h"
+#include <set>
+
#include "app/sql/connection.h"
#include "app/sql/transaction.h"
#include "base/file_util.h"
@@ -19,9 +21,12 @@
#include "webkit/appcache/appcache_group.h"
#include "webkit/appcache/appcache_histograms.h"
#include "webkit/appcache/appcache_policy.h"
+#include "webkit/appcache/appcache_quota_client.h"
#include "webkit/appcache/appcache_response.h"
#include "webkit/appcache/appcache_service.h"
#include "webkit/appcache/appcache_thread.h"
+#include "webkit/quota/quota_client.h"
+#include "webkit/quota/quota_manager.h"
#include "webkit/quota/special_storage_policy.h"
namespace {
@@ -33,6 +38,9 @@ void DeleteDirectory(const FilePath& path) {
namespace appcache {
+// Hard coded default when not using quota management.
+static const int kDefaultQuota = 5 * 1024 * 1024;
+
static const int kMaxDiskCacheSize = 250 * 1024 * 1024;
static const int kMaxMemDiskCacheSize = 10 * 1024 * 1024;
static const FilePath::CharType kDiskCacheDirectoryName[] =
@@ -146,14 +154,14 @@ class AppCacheStorageImpl::InitTask : public DatabaseTask {
int64 last_cache_id_;
int64 last_response_id_;
int64 last_deletable_response_rowid_;
- std::set<GURL> origins_with_groups_;
+ std::map<GURL, int64> usage_map_;
};
void AppCacheStorageImpl::InitTask::Run() {
database_->FindLastStorageIds(
&last_group_id_, &last_cache_id_, &last_response_id_,
&last_deletable_response_rowid_);
- database_->FindOriginsWithGroups(&origins_with_groups_);
+ database_->GetAllOriginUsage(&usage_map_);
}
void AppCacheStorageImpl::InitTask::RunCompleted() {
@@ -163,14 +171,16 @@ void AppCacheStorageImpl::InitTask::RunCompleted() {
storage_->last_deletable_response_rowid_ = last_deletable_response_rowid_;
if (!storage_->is_disabled()) {
- storage_->origins_with_groups_.swap(origins_with_groups_);
-
+ storage_->usage_map_.swap(usage_map_);
const int kDelayMillis = 5 * 60 * 1000; // Five minutes.
MessageLoop::current()->PostDelayedTask(FROM_HERE,
storage_->method_factory_.NewRunnableMethod(
&AppCacheStorageImpl::DelayedStartDeletingUnusedResponses),
kDelayMillis);
}
+
+ if (storage_->service()->quota_client())
+ storage_->service()->quota_client()->NotifyAppCacheReady();
}
// CloseConnectionTask -------
@@ -302,6 +312,8 @@ void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords(
cache->get()->GetEntry(*iter)->add_types(AppCacheEntry::FOREIGN);
}
+ storage_->NotifyStorageAccessed(group_record_.origin);
+
// TODO(michaeln): Maybe verify that the responses we expect to exist
// do actually exist in the disk_cache (and if not then what?)
}
@@ -395,6 +407,10 @@ class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask {
StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group,
AppCache* newest_cache);
+ void GetQuotaThenSchedule();
+ void OnQuotaCallback(
+ quota::QuotaStatusCode status, int64 usage, int64 quota);
+
virtual void Run();
virtual void RunCompleted();
virtual void CancelCompletion();
@@ -403,7 +419,8 @@ class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask {
scoped_refptr<AppCache> cache_;
bool success_;
bool would_exceed_quota_;
- int64 quota_override_;
+ int64 space_available_;
+ int64 new_origin_usage_;
std::vector<int64> newly_deletable_response_ids_;
};
@@ -411,7 +428,7 @@ AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask(
AppCacheStorageImpl* storage, AppCacheGroup* group, AppCache* newest_cache)
: StoreOrLoadTask(storage), group_(group), cache_(newest_cache),
success_(false), would_exceed_quota_(false),
- quota_override_(-1) {
+ space_available_(-1), new_origin_usage_(-1) {
group_record_.group_id = group->group_id();
group_record_.manifest_url = group->manifest_url();
group_record_.origin = group_record_.manifest_url.GetOrigin();
@@ -419,12 +436,43 @@ AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask(
group,
&cache_record_, &entry_records_, &fallback_namespace_records_,
&online_whitelist_records_);
+}
- if (storage->service()->special_storage_policy() &&
- storage->service()->special_storage_policy()->IsStorageUnlimited(
- group_record_.origin)) {
- quota_override_ = kint64max;
+void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule() {
+ quota::QuotaManager* quota_manager = NULL;
+ if (storage_->service()->quota_manager_proxy()) {
+ quota_manager =
+ storage_->service()->quota_manager_proxy()->quota_manager();
+ }
+
+ if (!quota_manager) {
+ if (storage_->service()->special_storage_policy() &&
+ storage_->service()->special_storage_policy()->IsStorageUnlimited(
+ group_record_.origin))
+ space_available_ = kint64max;
+ Schedule();
+ return;
+ }
+
+ // We have to ask the quota manager for the value.
+ AddRef(); // balanced in the OnQuotaCallback
+ storage_->pending_quota_queries_.insert(this);
+ quota_manager->GetUsageAndQuota(
+ group_record_.origin, quota::kStorageTypeTemporary,
+ NewCallback(this, &StoreGroupAndCacheTask::OnQuotaCallback));
+}
+
+void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback(
+ quota::QuotaStatusCode status, int64 usage, int64 quota) {
+ if (storage_) {
+ if (status == quota::kQuotaStatusOk)
+ space_available_ = std::max(static_cast<int64>(0), quota - usage);
+ else
+ space_available_ = 0;
+ storage_->pending_quota_queries_.erase(this);
+ Schedule();
}
+ Release(); // balanced in GetQuotaThenSchedule
}
void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
@@ -437,6 +485,8 @@ void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
if (!transaction.Begin())
return;
+ int64 old_origin_usage = database_->GetOriginUsage(group_record_.origin);
+
AppCacheDatabase::GroupRecord existing_group;
success_ = database_->FindGroup(group_record_.group_id, &existing_group);
if (!success_) {
@@ -494,11 +544,29 @@ void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
if (!success_)
return;
- int64 quota = (quota_override_ >= 0) ?
- quota_override_ :
- database_->GetOriginQuota(group_record_.origin);
+ new_origin_usage_ = database_->GetOriginUsage(group_record_.origin);
+
+ // Only check quota when the new usage exceeds the old usage.
+ if (new_origin_usage_ <= old_origin_usage) {
+ success_ = transaction.Commit();
+ return;
+ }
- if (database_->GetOriginUsage(group_record_.origin) > quota) {
+ // Use a simple hard-coded value when not using quota management.
+ if (space_available_ == -1) {
+ if (new_origin_usage_ > kDefaultQuota) {
+ would_exceed_quota_ = true;
+ success_ = false;
+ return;
+ }
+ success_ = transaction.Commit();
+ return;
+ }
+
+ // Check limits based on the space availbable given to us via the
+ // quota system.
+ int64 delta = new_origin_usage_ - old_origin_usage;
+ if (delta > space_available_) {
would_exceed_quota_ = true;
success_ = false;
return;
@@ -509,7 +577,8 @@ void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() {
if (success_) {
- storage_->origins_with_groups_.insert(group_->manifest_url().GetOrigin());
+ storage_->UpdateUsageMapAndNotify(
+ group_->manifest_url().GetOrigin(), new_origin_usage_);
if (cache_ != group_->newest_complete_cache()) {
cache_->set_complete(true);
group_->AddCache(cache_);
@@ -523,6 +592,9 @@ void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() {
would_exceed_quota_));
group_ = NULL;
cache_ = NULL;
+
+ // TODO(michaeln): if (would_exceed_quota_) what if the current usage
+ // also exceeds the quota? http://crbug.com/83968
}
void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion() {
@@ -837,15 +909,17 @@ class AppCacheStorageImpl::MakeGroupObsoleteTask : public DatabaseTask {
scoped_refptr<AppCacheGroup> group_;
int64 group_id_;
+ GURL origin_;
bool success_;
- std::set<GURL> origins_with_groups_;
+ int64 new_origin_usage_;
std::vector<int64> newly_deletable_response_ids_;
};
AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
AppCacheStorageImpl* storage, AppCacheGroup* group)
: DatabaseTask(storage), group_(group), group_id_(group->group_id()),
- success_(false) {
+ origin_(group->manifest_url().GetOrigin()),
+ success_(false), new_origin_usage_(-1) {
}
void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() {
@@ -861,10 +935,13 @@ void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() {
AppCacheDatabase::GroupRecord group_record;
if (!database_->FindGroup(group_id_, &group_record)) {
// This group doesn't exists in the database, nothing todo here.
+ new_origin_usage_ = database_->GetOriginUsage(origin_);
success_ = true;
return;
}
+ DCHECK_EQ(group_record.origin, origin_);
+
AppCacheDatabase::CacheRecord cache_record;
if (database_->FindCacheForGroup(group_id_, &cache_record)) {
database_->FindResponseIdsForCacheAsVector(cache_record.cache_id,
@@ -881,16 +958,15 @@ void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() {
success_ = database_->DeleteGroup(group_id_);
}
- success_ = success_ &&
- database_->FindOriginsWithGroups(&origins_with_groups_) &&
- transaction.Commit();
+ new_origin_usage_ = database_->GetOriginUsage(origin_);
+ success_ = success_ && transaction.Commit();
}
void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() {
if (success_) {
group_->set_obsolete(true);
if (!storage_->is_disabled()) {
- storage_->origins_with_groups_.swap(origins_with_groups_);
+ storage_->UpdateUsageMapAndNotify(origin_, new_origin_usage_);
group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
// Also remove from the working set, caches for an 'obsolete' group
@@ -970,8 +1046,11 @@ class AppCacheStorageImpl::UpdateGroupLastAccessTimeTask
: public DatabaseTask {
public:
UpdateGroupLastAccessTimeTask(
- AppCacheStorageImpl* storage, int64 group_id, base::Time time)
- : DatabaseTask(storage), group_id_(group_id), last_access_time_(time) {}
+ AppCacheStorageImpl* storage, AppCacheGroup* group, base::Time time)
+ : DatabaseTask(storage), group_id_(group->group_id()),
+ last_access_time_(time) {
+ storage->NotifyStorageAccessed(group->manifest_url().GetOrigin());
+ }
virtual void Run();
@@ -1002,6 +1081,9 @@ AppCacheStorageImpl::AppCacheStorageImpl(AppCacheService* service)
AppCacheStorageImpl::~AppCacheStorageImpl() {
STLDeleteElements(&pending_simple_tasks_);
+ std::for_each(pending_quota_queries_.begin(),
+ pending_quota_queries_.end(),
+ std::mem_fun(&DatabaseTask::CancelCompletion));
std::for_each(scheduled_database_tasks_.begin(),
scheduled_database_tasks_.end(),
std::mem_fun(&DatabaseTask::CancelCompletion));
@@ -1030,7 +1112,7 @@ void AppCacheStorageImpl::Disable() {
return;
VLOG(1) << "Disabling appcache storage.";
is_disabled_ = true;
- origins_with_groups_.clear();
+ ClearUsageMapAndNotify();
working_set()->Disable();
if (disk_cache_.get())
disk_cache_->Disable();
@@ -1058,7 +1140,7 @@ void AppCacheStorageImpl::LoadCache(int64 id, Delegate* delegate) {
if (cache->owning_group()) {
scoped_refptr<DatabaseTask> update_task(
new UpdateGroupLastAccessTimeTask(
- this, cache->owning_group()->group_id(), base::Time::Now()));
+ this, cache->owning_group(), base::Time::Now()));
update_task->Schedule();
}
return;
@@ -1087,7 +1169,7 @@ void AppCacheStorageImpl::LoadOrCreateGroup(
delegate->OnGroupLoaded(group, manifest_url);
scoped_refptr<DatabaseTask> update_task(
new UpdateGroupLastAccessTimeTask(
- this, group->group_id(), base::Time::Now()));
+ this, group, base::Time::Now()));
update_task->Schedule();
return;
}
@@ -1098,8 +1180,7 @@ void AppCacheStorageImpl::LoadOrCreateGroup(
return;
}
- if (origins_with_groups_.find(manifest_url.GetOrigin()) ==
- origins_with_groups_.end()) {
+ if (usage_map_.find(manifest_url.GetOrigin()) == usage_map_.end()) {
// No need to query the database, return a new group immediately.
scoped_refptr<AppCacheGroup> group(new AppCacheGroup(
service_, manifest_url, NewGroupId()));
@@ -1124,7 +1205,7 @@ void AppCacheStorageImpl::StoreGroupAndNewestCache(
scoped_refptr<StoreGroupAndCacheTask> task(
new StoreGroupAndCacheTask(this, group, newest_cache));
task->AddDelegate(GetOrCreateDelegateReference(delegate));
- task->Schedule();
+ task->GetQuotaThenSchedule();
}
void AppCacheStorageImpl::FindResponseForMainRequest(
@@ -1168,8 +1249,7 @@ void AppCacheStorageImpl::FindResponseForMainRequest(
}
}
- if (IsInitTaskComplete() &&
- origins_with_groups_.find(origin) == origins_with_groups_.end()) {
+ if (IsInitTaskComplete() && usage_map_.find(origin) == usage_map_.end()) {
// No need to query the database, return async'ly but without going thru
// the DB thread.
scoped_refptr<AppCacheGroup> no_group;
diff --git a/webkit/appcache/appcache_storage_impl.h b/webkit/appcache/appcache_storage_impl.h
index cd54a86..3da97db 100644
--- a/webkit/appcache/appcache_storage_impl.h
+++ b/webkit/appcache/appcache_storage_impl.h
@@ -80,6 +80,7 @@ class AppCacheStorageImpl : public AppCacheStorage {
typedef std::map<int64, CacheLoadTask*> PendingCacheLoads;
typedef std::map<GURL, GroupLoadTask*> PendingGroupLoads;
typedef std::deque<std::pair<GURL, int64> > PendingForeignMarkings;
+ typedef std::set<StoreGroupAndCacheTask*> PendingQuotaQueries;
bool IsInitTaskComplete() {
return last_cache_id_ != AppCacheStorage::kUnitializedId;
@@ -127,6 +128,7 @@ class AppCacheStorageImpl : public AppCacheStorage {
PendingCacheLoads pending_cache_loads_;
PendingGroupLoads pending_group_loads_;
PendingForeignMarkings pending_foreign_markings_;
+ PendingQuotaQueries pending_quota_queries_;
// Structures to keep track of lazy response deletion.
std::deque<int64> deletable_response_ids_;
@@ -151,7 +153,6 @@ class AppCacheStorageImpl : public AppCacheStorage {
// Used to short-circuit certain operations without having to schedule
// any tasks on the background database thread.
- std::set<GURL> origins_with_groups_;
std::deque<Task*> pending_simple_tasks_;
ScopedRunnableMethodFactory<AppCacheStorageImpl> method_factory_;
diff --git a/webkit/appcache/appcache_storage_impl_unittest.cc b/webkit/appcache/appcache_storage_impl_unittest.cc
index 1237c02..0b914f7 100644
--- a/webkit/appcache/appcache_storage_impl_unittest.cc
+++ b/webkit/appcache/appcache_storage_impl_unittest.cc
@@ -16,6 +16,7 @@
#include "webkit/appcache/appcache_policy.h"
#include "webkit/appcache/appcache_service.h"
#include "webkit/appcache/appcache_storage_impl.h"
+#include "webkit/quota/quota_manager.h"
#include "webkit/tools/test_shell/simple_appcache_system.h"
namespace appcache {
@@ -34,10 +35,17 @@ const GURL kFallbackTestUrl("http://blah/fallback_namespace/longer/test");
const GURL kOnlineNamespace("http://blah/online_namespace");
const GURL kOnlineNamespaceWithinFallback(
"http://blah/fallback_namespace/online/");
+const GURL kOrigin(kManifestUrl.GetOrigin());
const int kManifestEntryIdOffset = 100;
const int kFallbackEntryIdOffset = 1000;
+const GURL kDefaultEntryUrl("http://blah/makecacheandgroup_default_entry");
+const int kDefaultEntrySize = 10;
+const int kDefaultEntryIdOffset = 12345;
+
+const int kMockQuota = 5000;
+
// For the duration of this test case, we hijack the AppCacheThread API
// calls and implement them in terms of the io and db threads created here.
@@ -176,6 +184,80 @@ class AppCacheStorageImplTest : public testing::Test {
AppCacheStorageImplTest* test_;
};
+ class MockQuotaManager : public quota::QuotaManager {
+ public:
+ MockQuotaManager()
+ : QuotaManager(true /* is_incognito */, FilePath(),
+ io_thread->message_loop_proxy(),
+ db_thread->message_loop_proxy(),
+ NULL),
+ async_(false) {}
+
+ virtual void GetUsageAndQuota(
+ const GURL& origin, quota::StorageType type,
+ GetUsageAndQuotaCallback* callback) {
+ EXPECT_EQ(kOrigin, origin);
+ EXPECT_EQ(quota::kStorageTypeTemporary, type);
+ if (async_) {
+ MessageLoop::current()->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &MockQuotaManager::CallCallbackAndDelete,
+ callback));
+ return;
+ }
+ CallCallbackAndDelete(callback);
+ }
+
+ void CallCallbackAndDelete(GetUsageAndQuotaCallback* callback) {
+ callback->Run(quota::kQuotaStatusOk, 0, kMockQuota);
+ delete callback;
+ }
+
+ bool async_;
+ };
+
+ class MockQuotaManagerProxy : public quota::QuotaManagerProxy {
+ public:
+ MockQuotaManagerProxy()
+ : QuotaManagerProxy(NULL, NULL),
+ notify_storage_accessed_count_(0),
+ notify_storage_modified_count_(0),
+ last_delta_(0),
+ mock_manager_(new MockQuotaManager) {
+ manager_ = mock_manager_;
+ }
+
+ virtual void NotifyStorageAccessed(quota::QuotaClient::ID client_id,
+ const GURL& origin,
+ quota::StorageType type) {
+ EXPECT_EQ(quota::QuotaClient::kAppcache, client_id);
+ EXPECT_EQ(quota::kStorageTypeTemporary, type);
+ ++notify_storage_accessed_count_;
+ last_origin_ = origin;
+ }
+
+ virtual void NotifyStorageModified(quota::QuotaClient::ID client_id,
+ const GURL& origin,
+ quota::StorageType type,
+ int64 delta) {
+ EXPECT_EQ(quota::QuotaClient::kAppcache, client_id);
+ EXPECT_EQ(quota::kStorageTypeTemporary, type);
+ ++notify_storage_modified_count_;
+ last_origin_ = origin;
+ last_delta_ = delta;
+ }
+
+ // Not needed for our tests.
+ virtual void RegisterClient(quota::QuotaClient* client) {}
+ virtual void NotifyOriginInUse(const GURL& origin) {}
+ virtual void NotifyOriginNoLongerInUse(const GURL& origin) {}
+
+ int notify_storage_accessed_count_;
+ int notify_storage_modified_count_;
+ GURL last_origin_;
+ int last_delta_;
+ scoped_refptr<MockQuotaManager> mock_manager_;
+ };
+
// Helper class run a test on our io_thread. The io_thread
// is spun up once and reused for all tests.
template <class Method>
@@ -243,6 +325,8 @@ class AppCacheStorageImplTest : public testing::Test {
DCHECK(MessageLoop::current() == io_thread->message_loop());
service_.reset(new AppCacheService(NULL));
service_->Initialize(FilePath(), NULL);
+ mock_quota_manager_proxy_ = new MockQuotaManagerProxy();
+ service_->quota_manager_proxy_ = mock_quota_manager_proxy_;
delegate_.reset(new MockStorageDelegate(this));
}
@@ -252,6 +336,7 @@ class AppCacheStorageImplTest : public testing::Test {
group_ = NULL;
cache_ = NULL;
cache2_ = NULL;
+ mock_quota_manager_proxy_ = NULL;
delegate_.reset();
service_.reset();
FlushDbThreadTasks();
@@ -312,6 +397,8 @@ class AppCacheStorageImplTest : public testing::Test {
void Verify_LoadCache_Miss() {
EXPECT_EQ(111, delegate()->loaded_cache_id_);
EXPECT_FALSE(delegate()->loaded_cache_);
+ EXPECT_EQ(0, mock_quota_manager_proxy_->notify_storage_accessed_count_);
+ EXPECT_EQ(0, mock_quota_manager_proxy_->notify_storage_modified_count_);
TestFinished();
}
@@ -331,6 +418,8 @@ class AppCacheStorageImplTest : public testing::Test {
storage()->LoadCache(cache_id, delegate());
EXPECT_EQ(cache_id, delegate()->loaded_cache_id_);
EXPECT_EQ(cache.get(), delegate()->loaded_cache_.get());
+ EXPECT_EQ(0, mock_quota_manager_proxy_->notify_storage_accessed_count_);
+ EXPECT_EQ(0, mock_quota_manager_proxy_->notify_storage_modified_count_);
TestFinished();
}
@@ -354,7 +443,7 @@ class AppCacheStorageImplTest : public testing::Test {
// Since the origin has groups, storage class will have to
// consult the database and completion will be async.
- storage()->origins_with_groups_.insert(kManifestUrl.GetOrigin());
+ storage()->usage_map_[kOrigin] = kDefaultEntrySize;
storage()->LoadOrCreateGroup(kManifestUrl, delegate());
EXPECT_FALSE(delegate()->loaded_group_.get());
@@ -371,6 +460,9 @@ class AppCacheStorageImplTest : public testing::Test {
EXPECT_FALSE(database()->FindGroup(
delegate()->loaded_group_->group_id(), &record));
+ EXPECT_EQ(0, mock_quota_manager_proxy_->notify_storage_accessed_count_);
+ EXPECT_EQ(0, mock_quota_manager_proxy_->notify_storage_modified_count_);
+
TestFinished();
}
@@ -403,6 +495,9 @@ class AppCacheStorageImplTest : public testing::Test {
EXPECT_TRUE(delegate()->loaded_cache_->owning_group()->HasOneRef());
EXPECT_EQ(1, delegate()->loaded_cache_->owning_group()->group_id());
+ EXPECT_EQ(1, mock_quota_manager_proxy_->notify_storage_accessed_count_);
+ EXPECT_EQ(0, mock_quota_manager_proxy_->notify_storage_modified_count_);
+
// Drop things from the working set.
delegate()->loaded_cache_ = NULL;
EXPECT_FALSE(delegate()->loaded_group_);
@@ -420,6 +515,8 @@ class AppCacheStorageImplTest : public testing::Test {
EXPECT_TRUE(delegate()->loaded_group_->newest_complete_cache());
delegate()->loaded_groups_newest_cache_ = NULL;
EXPECT_TRUE(delegate()->loaded_group_->HasOneRef());
+ EXPECT_EQ(2, mock_quota_manager_proxy_->notify_storage_accessed_count_);
+ EXPECT_EQ(0, mock_quota_manager_proxy_->notify_storage_modified_count_);
TestFinished();
}
@@ -435,9 +532,14 @@ class AppCacheStorageImplTest : public testing::Test {
group_ = new AppCacheGroup(
service(), kManifestUrl, storage()->NewGroupId());
cache_ = new AppCache(service(), storage()->NewCacheId());
+ cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::EXPLICIT, 1,
+ kDefaultEntrySize));
// Hold a ref to the cache simulate the UpdateJob holding that ref,
// and hold a ref to the group to simulate the CacheHost holding that ref.
+ // Have the quota manager retrun asyncly for this test.
+ mock_quota_manager_proxy_->mock_manager_->async_ = true;
+
// Conduct the store test.
storage()->StoreGroupAndNewestCache(group_, cache_, delegate());
EXPECT_FALSE(delegate()->stored_group_success_);
@@ -454,6 +556,13 @@ class AppCacheStorageImplTest : public testing::Test {
AppCacheDatabase::CacheRecord cache_record;
EXPECT_TRUE(database()->FindGroup(group_->group_id(), &group_record));
EXPECT_TRUE(database()->FindCache(cache_->cache_id(), &cache_record));
+
+ // Verify quota bookkeeping
+ EXPECT_EQ(kDefaultEntrySize, storage()->usage_map_[kOrigin]);
+ EXPECT_EQ(1, mock_quota_manager_proxy_->notify_storage_modified_count_);
+ EXPECT_EQ(kOrigin, mock_quota_manager_proxy_->last_origin_);
+ EXPECT_EQ(kDefaultEntrySize, mock_quota_manager_proxy_->last_delta_);
+
TestFinished();
}
@@ -467,9 +576,12 @@ class AppCacheStorageImplTest : public testing::Test {
// Setup some preconditions. Create a group and old complete cache
// that appear to be "stored"
MakeCacheAndGroup(kManifestUrl, 1, 1, true);
+ EXPECT_EQ(kDefaultEntrySize, storage()->usage_map_[kOrigin]);
// And a newest unstored complete cache.
cache2_ = new AppCache(service(), 2);
+ cache2_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::MASTER, 1,
+ kDefaultEntrySize + 100));
// Conduct the test.
storage()->StoreGroupAndNewestCache(group_, cache2_, delegate());
@@ -490,6 +602,13 @@ class AppCacheStorageImplTest : public testing::Test {
// The old cache should have been deleted
EXPECT_FALSE(database()->FindCache(1, &cache_record));
+
+ // Verify quota bookkeeping
+ EXPECT_EQ(kDefaultEntrySize + 100, storage()->usage_map_[kOrigin]);
+ EXPECT_EQ(1, mock_quota_manager_proxy_->notify_storage_modified_count_);
+ EXPECT_EQ(kOrigin, mock_quota_manager_proxy_->last_origin_);
+ EXPECT_EQ(100, mock_quota_manager_proxy_->last_delta_);
+
TestFinished();
}
@@ -503,6 +622,7 @@ class AppCacheStorageImplTest : public testing::Test {
// Setup some preconditions. Create a group and old complete cache
// that appear to be "stored"
MakeCacheAndGroup(kManifestUrl, 1, 1, true);
+ EXPECT_EQ(kDefaultEntrySize, storage()->usage_map_[kOrigin]);
// Change the cache.
base::Time now = base::Time::Now();
@@ -530,17 +650,25 @@ class AppCacheStorageImplTest : public testing::Test {
EXPECT_EQ(1, cache_record.group_id);
EXPECT_FALSE(cache_record.online_wildcard);
EXPECT_TRUE(expected_update_time == cache_record.update_time);
- EXPECT_EQ(100, cache_record.cache_size);
+ EXPECT_EQ(100 + kDefaultEntrySize, cache_record.cache_size);
std::vector<AppCacheDatabase::EntryRecord> entry_records;
EXPECT_TRUE(database()->FindEntriesForCache(1, &entry_records));
- EXPECT_EQ(1U, entry_records.size());
+ EXPECT_EQ(2U, entry_records.size());
+ if (entry_records[0].url == kDefaultEntryUrl)
+ entry_records.erase(entry_records.begin());
EXPECT_EQ(1 , entry_records[0].cache_id);
EXPECT_EQ(kEntryUrl, entry_records[0].url);
EXPECT_EQ(AppCacheEntry::MASTER, entry_records[0].flags);
EXPECT_EQ(1, entry_records[0].response_id);
EXPECT_EQ(100, entry_records[0].response_size);
+ // Verify quota bookkeeping
+ EXPECT_EQ(100 + kDefaultEntrySize, storage()->usage_map_[kOrigin]);
+ EXPECT_EQ(1, mock_quota_manager_proxy_->notify_storage_modified_count_);
+ EXPECT_EQ(kOrigin, mock_quota_manager_proxy_->last_origin_);
+ EXPECT_EQ(100, mock_quota_manager_proxy_->last_delta_);
+
TestFinished();
}
@@ -577,6 +705,9 @@ class AppCacheStorageImplTest : public testing::Test {
EXPECT_FALSE(database()->FindGroup(group_->group_id(), &group_record));
EXPECT_FALSE(database()->FindCache(cache_->cache_id(), &cache_record));
+ EXPECT_EQ(0, mock_quota_manager_proxy_->notify_storage_accessed_count_);
+ EXPECT_EQ(0, mock_quota_manager_proxy_->notify_storage_modified_count_);
+
TestFinished();
}
@@ -590,7 +721,7 @@ class AppCacheStorageImplTest : public testing::Test {
// Setup some preconditions. Create a group and newest cache that
// appears to be "stored" and "currently in use".
MakeCacheAndGroup(kManifestUrl, 1, 1, true);
- EXPECT_FALSE(storage()->origins_with_groups_.empty());
+ EXPECT_EQ(kDefaultEntrySize, storage()->usage_map_[kOrigin]);
// Also insert some related records.
AppCacheDatabase::EntryRecord entry_record;
@@ -622,7 +753,7 @@ class AppCacheStorageImplTest : public testing::Test {
EXPECT_TRUE(delegate()->obsoleted_success_);
EXPECT_EQ(group_.get(), delegate()->obsoleted_group_.get());
EXPECT_TRUE(group_->is_obsolete());
- EXPECT_TRUE(storage()->origins_with_groups_.empty());
+ EXPECT_TRUE(storage()->usage_map_.empty());
// The cache and group have been deleted from the database.
AppCacheDatabase::GroupRecord group_record;
@@ -641,6 +772,12 @@ class AppCacheStorageImplTest : public testing::Test {
database()->FindOnlineWhiteListForCache(1, &whitelist_records);
EXPECT_TRUE(whitelist_records.empty());
+ // Verify quota bookkeeping
+ EXPECT_TRUE(storage()->usage_map_.empty());
+ EXPECT_EQ(1, mock_quota_manager_proxy_->notify_storage_modified_count_);
+ EXPECT_EQ(kOrigin, mock_quota_manager_proxy_->last_origin_);
+ EXPECT_EQ(-kDefaultEntrySize, mock_quota_manager_proxy_->last_delta_);
+
TestFinished();
}
@@ -836,7 +973,16 @@ class AppCacheStorageImplTest : public testing::Test {
std::vector<AppCacheDatabase::OnlineWhiteListRecord> whitelists;
cache_->ToDatabaseRecords(group_,
&cache_record, &entries, &fallbacks, &whitelists);
- EXPECT_TRUE(database()->InsertEntryRecords(entries));
+
+ std::vector<AppCacheDatabase::EntryRecord>::const_iterator iter =
+ entries.begin();
+ while (iter != entries.end()) {
+ // MakeCacheAndGroup has inserted the default entry record already
+ if (iter->url != kDefaultEntryUrl)
+ EXPECT_TRUE(database()->InsertEntry(&(*iter)));
+ ++iter;
+ }
+
EXPECT_TRUE(database()->InsertFallbackNameSpaceRecords(fallbacks));
EXPECT_TRUE(database()->InsertOnlineWhiteListRecords(whitelists));
if (drop_from_working_set) {
@@ -1125,8 +1271,12 @@ class AppCacheStorageImplTest : public testing::Test {
void MakeCacheAndGroup(
const GURL& manifest_url, int64 group_id, int64 cache_id,
bool add_to_database) {
+ AppCacheEntry default_entry(
+ AppCacheEntry::EXPLICIT, cache_id + kDefaultEntryIdOffset,
+ kDefaultEntrySize);
group_ = new AppCacheGroup(service(), manifest_url, group_id);
cache_ = new AppCache(service(), cache_id);
+ cache_->AddEntry(kDefaultEntryUrl, default_entry);
cache_->set_complete(true);
group_->AddCache(cache_);
if (add_to_database) {
@@ -1140,8 +1290,18 @@ class AppCacheStorageImplTest : public testing::Test {
cache_record.group_id = group_id;
cache_record.online_wildcard = false;
cache_record.update_time = kZeroTime;
+ cache_record.cache_size = kDefaultEntrySize;
EXPECT_TRUE(database()->InsertCache(&cache_record));
- storage()->origins_with_groups_.insert(manifest_url.GetOrigin());
+ AppCacheDatabase::EntryRecord entry_record;
+ entry_record.cache_id = cache_id;
+ entry_record.url = kDefaultEntryUrl;
+ entry_record.flags = default_entry.types();
+ entry_record.response_id = default_entry.response_id();
+ entry_record.response_size = default_entry.response_size();
+ EXPECT_TRUE(database()->InsertEntry(&entry_record));
+
+ storage()->usage_map_[manifest_url.GetOrigin()] =
+ default_entry.response_size();
}
}
@@ -1152,6 +1312,7 @@ class AppCacheStorageImplTest : public testing::Test {
MockAppCachePolicy policy_;
scoped_ptr<AppCacheService> service_;
scoped_ptr<MockStorageDelegate> delegate_;
+ scoped_refptr<MockQuotaManagerProxy> mock_quota_manager_proxy_;
scoped_refptr<AppCacheGroup> group_;
scoped_refptr<AppCache> cache_;
scoped_refptr<AppCache> cache2_;
diff --git a/webkit/appcache/appcache_storage_unittest.cc b/webkit/appcache/appcache_storage_unittest.cc
index 9e9e57e..82e3b99 100644
--- a/webkit/appcache/appcache_storage_unittest.cc
+++ b/webkit/appcache/appcache_storage_unittest.cc
@@ -17,6 +17,45 @@ class AppCacheStorageTest : public testing::Test {
class MockStorageDelegate : public AppCacheStorage::Delegate {
public:
};
+
+ class MockQuotaManagerProxy : public quota::QuotaManagerProxy {
+ public:
+ MockQuotaManagerProxy()
+ : QuotaManagerProxy(NULL, NULL),
+ notify_storage_accessed_count_(0),
+ notify_storage_modified_count_(0),
+ last_delta_(0) {}
+
+ virtual void NotifyStorageAccessed(quota::QuotaClient::ID client_id,
+ const GURL& origin,
+ quota::StorageType type) {
+ EXPECT_EQ(quota::QuotaClient::kAppcache, client_id);
+ EXPECT_EQ(quota::kStorageTypeTemporary, type);
+ ++notify_storage_accessed_count_;
+ last_origin_ = origin;
+ }
+
+ virtual void NotifyStorageModified(quota::QuotaClient::ID client_id,
+ const GURL& origin,
+ quota::StorageType type,
+ int64 delta) {
+ EXPECT_EQ(quota::QuotaClient::kAppcache, client_id);
+ EXPECT_EQ(quota::kStorageTypeTemporary, type);
+ ++notify_storage_modified_count_;
+ last_origin_ = origin;
+ last_delta_ = delta;
+ }
+
+ // Not needed for our tests.
+ virtual void RegisterClient(quota::QuotaClient* client) {}
+ virtual void NotifyOriginInUse(const GURL& origin) {}
+ virtual void NotifyOriginNoLongerInUse(const GURL& origin) {}
+
+ int notify_storage_accessed_count_;
+ int notify_storage_modified_count_;
+ GURL last_origin_;
+ int last_delta_;
+ };
};
TEST_F(AppCacheStorageTest, AddRemoveCache) {
@@ -107,4 +146,47 @@ TEST_F(AppCacheStorageTest, DelegateReferences) {
EXPECT_NE(delegate_reference1.get(), delegate_reference2.get());
}
+TEST_F(AppCacheStorageTest, UsageMap) {
+ const GURL kOrigin("http://origin/");
+ const GURL kOrigin2("http://origin2/");
+
+ MockAppCacheService service;
+ scoped_refptr<MockQuotaManagerProxy> mock_proxy(new MockQuotaManagerProxy);
+ service.set_quota_manager_proxy(mock_proxy);
+
+ service.storage()->UpdateUsageMapAndNotify(kOrigin, 0);
+ EXPECT_EQ(0, mock_proxy->notify_storage_modified_count_);
+
+ service.storage()->UpdateUsageMapAndNotify(kOrigin, 10);
+ EXPECT_EQ(1, mock_proxy->notify_storage_modified_count_);
+ EXPECT_EQ(10, mock_proxy->last_delta_);
+ EXPECT_EQ(kOrigin, mock_proxy->last_origin_);
+
+ service.storage()->UpdateUsageMapAndNotify(kOrigin, 100);
+ EXPECT_EQ(2, mock_proxy->notify_storage_modified_count_);
+ EXPECT_EQ(90, mock_proxy->last_delta_);
+ EXPECT_EQ(kOrigin, mock_proxy->last_origin_);
+
+ service.storage()->UpdateUsageMapAndNotify(kOrigin, 0);
+ EXPECT_EQ(3, mock_proxy->notify_storage_modified_count_);
+ EXPECT_EQ(-100, mock_proxy->last_delta_);
+ EXPECT_EQ(kOrigin, mock_proxy->last_origin_);
+
+ service.storage()->NotifyStorageAccessed(kOrigin2);
+ EXPECT_EQ(0, mock_proxy->notify_storage_accessed_count_);
+
+ service.storage()->usage_map_[kOrigin2] = 1;
+ service.storage()->NotifyStorageAccessed(kOrigin2);
+ EXPECT_EQ(1, mock_proxy->notify_storage_accessed_count_);
+ EXPECT_EQ(kOrigin2, mock_proxy->last_origin_);
+
+ service.storage()->usage_map_.clear();
+ service.storage()->usage_map_[kOrigin] = 5000;
+ service.storage()->ClearUsageMapAndNotify();
+ EXPECT_EQ(4, mock_proxy->notify_storage_modified_count_);
+ EXPECT_EQ(-5000, mock_proxy->last_delta_);
+ EXPECT_EQ(kOrigin, mock_proxy->last_origin_);
+ EXPECT_TRUE(service.storage()->usage_map_.empty());
+}
+
} // namespace appcache
diff --git a/webkit/appcache/mock_appcache_service.cc b/webkit/appcache/mock_appcache_service.cc
new file mode 100644
index 0000000..d87e4f1
--- /dev/null
+++ b/webkit/appcache/mock_appcache_service.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/appcache/mock_appcache_service.h"
+
+#include "base/message_loop.h"
+
+namespace appcache {
+
+static void DeferredCallCallback(net::CompletionCallback* callback, int rv) {
+ callback->Run(rv);
+}
+
+void MockAppCacheService::DeleteAppCachesForOrigin(
+ const GURL& origin, net::CompletionCallback* callback) {
+ ++delete_called_count_;
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ NewRunnableFunction(&DeferredCallCallback, callback,
+ mock_delete_appcaches_for_origin_result_));
+}
+
+} // namespace appcache
diff --git a/webkit/appcache/mock_appcache_service.h b/webkit/appcache/mock_appcache_service.h
index d9f327a..1679d9f 100644
--- a/webkit/appcache/mock_appcache_service.h
+++ b/webkit/appcache/mock_appcache_service.h
@@ -15,13 +15,30 @@ namespace appcache {
// For use by unit tests.
class MockAppCacheService : public AppCacheService {
public:
- MockAppCacheService() : AppCacheService(NULL) {
+ MockAppCacheService()
+ : AppCacheService(NULL),
+ mock_delete_appcaches_for_origin_result_(net::OK),
+ delete_called_count_(0) {
storage_.reset(new MockAppCacheStorage(this));
}
+ // Just returns a canned completion code without actually
+ // removing groups and caches in our mock storage instance.
+ virtual void DeleteAppCachesForOrigin(const GURL& origin,
+ net::CompletionCallback* callback);
+
void set_quota_manager_proxy(quota::QuotaManagerProxy* proxy) {
quota_manager_proxy_ = proxy;
}
+
+ void set_mock_delete_appcaches_for_origin_result(int rv) {
+ mock_delete_appcaches_for_origin_result_ = rv;
+ }
+
+ int delete_called_count() const { return delete_called_count_; }
+ private:
+ int mock_delete_appcaches_for_origin_result_;
+ int delete_called_count_;
};
} // namespace appcache
diff --git a/webkit/appcache/mock_appcache_storage.cc b/webkit/appcache/mock_appcache_storage.cc
index 98af9bf..43fcd6b 100644
--- a/webkit/appcache/mock_appcache_storage.cc
+++ b/webkit/appcache/mock_appcache_storage.cc
@@ -12,6 +12,7 @@
#include "webkit/appcache/appcache_entry.h"
#include "webkit/appcache/appcache_group.h"
#include "webkit/appcache/appcache_response.h"
+#include "webkit/appcache/appcache_service.h"
// This is a quick and easy 'mock' implementation of the storage interface
// that doesn't put anything to disk.
@@ -43,6 +44,12 @@ MockAppCacheStorage::~MockAppCacheStorage() {
STLDeleteElements(&pending_tasks_);
}
+void MockAppCacheStorage::GetAllInfo(Delegate* delegate) {
+ ScheduleTask(method_factory_.NewRunnableMethod(
+ &MockAppCacheStorage::ProcessGetAllInfo,
+ make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
+}
+
void MockAppCacheStorage::LoadCache(int64 id, Delegate* delegate) {
DCHECK(delegate);
AppCache* cache = working_set_.GetCache(id);
@@ -164,6 +171,12 @@ void MockAppCacheStorage::DeleteResponses(
}
}
+void MockAppCacheStorage::ProcessGetAllInfo(
+ scoped_refptr<DelegateReference> delegate_ref) {
+ if (delegate_ref->delegate)
+ delegate_ref->delegate->OnAllInfo(simulated_appcache_info_);
+}
+
void MockAppCacheStorage::ProcessLoadCache(
int64 id, scoped_refptr<DelegateReference> delegate_ref) {
AppCache* cache = working_set_.GetCache(id);
diff --git a/webkit/appcache/mock_appcache_storage.h b/webkit/appcache/mock_appcache_storage.h
index ea3ca1f..66ed4a7 100644
--- a/webkit/appcache/mock_appcache_storage.h
+++ b/webkit/appcache/mock_appcache_storage.h
@@ -29,7 +29,7 @@ class MockAppCacheStorage : public AppCacheStorage {
explicit MockAppCacheStorage(AppCacheService* service);
virtual ~MockAppCacheStorage();
- virtual void GetAllInfo(Delegate* delegate) {} // not implemented
+ virtual void GetAllInfo(Delegate* delegate);
virtual void LoadCache(int64 id, Delegate* delegate);
virtual void LoadOrCreateGroup(const GURL& manifest_url, Delegate* delegate);
virtual void StoreGroupAndNewestCache(
@@ -59,6 +59,7 @@ class MockAppCacheStorage : public AppCacheStorage {
typedef std::map<GURL, scoped_refptr<AppCacheGroup> > StoredGroupMap;
typedef std::set<int64> DoomedResponseIds;
+ void ProcessGetAllInfo(scoped_refptr<DelegateReference> delegate_ref);
void ProcessLoadCache(
int64 id, scoped_refptr<DelegateReference> delegate_ref);
void ProcessLoadOrCreateGroup(
@@ -145,6 +146,10 @@ class MockAppCacheStorage : public AppCacheStorage {
simulated_found_network_namespace_ = network_namespace;
}
+ void SimulateGetAllInfo(AppCacheInfoCollection* info) {
+ simulated_appcache_info_ = info;
+ }
+
StoredCacheMap stored_caches_;
StoredGroupMap stored_groups_;
DoomedResponseIds doomed_response_ids_;
@@ -163,6 +168,7 @@ class MockAppCacheStorage : public AppCacheStorage {
GURL simulated_found_fallback_url_;
GURL simulated_found_manifest_url_;
bool simulated_found_network_namespace_;
+ scoped_refptr<AppCacheInfoCollection> simulated_appcache_info_;
FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest, BasicFindMainResponse);
FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest,
@@ -178,6 +184,7 @@ class MockAppCacheStorage : public AppCacheStorage {
FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest, StoreExistingGroup);
FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest,
StoreExistingGroupExistingCache);
+ FRIEND_TEST_ALL_PREFIXES(AppCacheServiceTest, DeleteAppCachesForOrigin);
DISALLOW_COPY_AND_ASSIGN(MockAppCacheStorage);
};
diff --git a/webkit/appcache/webkit_appcache.gypi b/webkit/appcache/webkit_appcache.gypi
index 6078a21..4b610bc 100644
--- a/webkit/appcache/webkit_appcache.gypi
+++ b/webkit/appcache/webkit_appcache.gypi
@@ -36,6 +36,8 @@
'appcache_interfaces.cc',
'appcache_interfaces.h',
'appcache_policy.h',
+ 'appcache_quota_client.cc',
+ 'appcache_quota_client.h',
'appcache_request_handler.cc',
'appcache_request_handler.h',
'appcache_response.cc',