diff options
author | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-10 23:29:09 +0000 |
---|---|---|
committer | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-10 23:29:09 +0000 |
commit | e0184cbde1634faa300d63c7880b5acf312f45d6 (patch) | |
tree | 69ef5ba562bbc68276fddd3279c6ca7e8e27d025 /webkit/appcache | |
parent | b7d94558973c6c6f2776f68a4d34d20cd5303ac5 (diff) | |
download | chromium_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')
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', |