diff options
author | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-29 21:14:05 +0000 |
---|---|---|
committer | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-29 21:14:05 +0000 |
commit | 62bf213f70f3fa3ebbe2eaa9353ca9ac9f8264fb (patch) | |
tree | 9040e07832c545e0abb5754da126f0b314afb17f /webkit/quota | |
parent | fcfa89d8845754ceb5680805b5726de002a030be (diff) | |
download | chromium_src-62bf213f70f3fa3ebbe2eaa9353ca9ac9f8264fb.zip chromium_src-62bf213f70f3fa3ebbe2eaa9353ca9ac9f8264fb.tar.gz chromium_src-62bf213f70f3fa3ebbe2eaa9353ca9ac9f8264fb.tar.bz2 |
Retain support for 'UnlimitedStorage' in the QuotaManager.
Track unlimited_usage in parallel with total global_usage. The latter value is the sum of all usage by any origin (same as before). The new value is the amount of the total that is attributable to origins with the unlimited storage permission.
Widened the GlobalUsageCallback to take two int64 params, one for the total global_usage and the other for the unlimited_usage.
Modified the eviction logic to exempt origins with the unlimited storage right from eviction.
Modified the behavior of QuotaManager::GetUsageAndQuota(origin) method to return kint64max for unlimited origins. Also modified the return value in the limited case to be 10% of the total limit for temp storage, unless the system is over the total limit in which case they're capped at their current usage level.
// Allow an individual host to utilize no more than 10% of the total
// pool available for temp storage.
int64 host_quota = quota() / 10;
// If total temp usage is over-budget, stop letting new data in until we reclaim space.
limited_global_usage = global_usage() - unlimted_global_usage();
if (limited_global_usage > quota())
host_quota = std::min(host_quota, host_usage());
return host_quota;
At this time, the total limit for temp storage is 1G, that gives each site 100M to work with (no prompts involved).
BUG=61676
TEST=unittests
Review URL: http://codereview.chromium.org/6962005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@87207 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/quota')
-rw-r--r-- | webkit/quota/mock_special_storage_policy.cc | 21 | ||||
-rw-r--r-- | webkit/quota/mock_special_storage_policy.h | 48 | ||||
-rw-r--r-- | webkit/quota/mock_storage_client.cc | 25 | ||||
-rw-r--r-- | webkit/quota/mock_storage_client.h | 14 | ||||
-rw-r--r-- | webkit/quota/quota_database.cc | 13 | ||||
-rw-r--r-- | webkit/quota/quota_database.h | 8 | ||||
-rw-r--r-- | webkit/quota/quota_database_unittest.cc | 32 | ||||
-rw-r--r-- | webkit/quota/quota_manager.cc | 150 | ||||
-rw-r--r-- | webkit/quota/quota_manager.h | 37 | ||||
-rw-r--r-- | webkit/quota/quota_manager_unittest.cc | 200 | ||||
-rw-r--r-- | webkit/quota/quota_temporary_storage_evictor.cc | 6 | ||||
-rw-r--r-- | webkit/quota/quota_temporary_storage_evictor.h | 1 | ||||
-rw-r--r-- | webkit/quota/quota_temporary_storage_evictor_unittest.cc | 22 | ||||
-rw-r--r-- | webkit/quota/quota_types.h | 3 | ||||
-rw-r--r-- | webkit/quota/usage_tracker.cc | 63 | ||||
-rw-r--r-- | webkit/quota/usage_tracker.h | 27 |
16 files changed, 531 insertions, 139 deletions
diff --git a/webkit/quota/mock_special_storage_policy.cc b/webkit/quota/mock_special_storage_policy.cc new file mode 100644 index 0000000..9559d8f --- /dev/null +++ b/webkit/quota/mock_special_storage_policy.cc @@ -0,0 +1,21 @@ +// 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/quota/mock_special_storage_policy.h" + +namespace quota { + +bool MockSpecialStoragePolicy::IsStorageProtected(const GURL& origin) { + return protected_.find(origin) != protected_.end(); +} + +bool MockSpecialStoragePolicy::IsStorageUnlimited(const GURL& origin) { + return unlimited_.find(origin) != unlimited_.end(); +} + +bool MockSpecialStoragePolicy::IsFileHandler(const std::string& extension_id) { + return file_handlers_.find(extension_id) != file_handlers_.end(); +} + +} // namespace quota diff --git a/webkit/quota/mock_special_storage_policy.h b/webkit/quota/mock_special_storage_policy.h new file mode 100644 index 0000000..9a819a9 --- /dev/null +++ b/webkit/quota/mock_special_storage_policy.h @@ -0,0 +1,48 @@ +// 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_QUOTA_MOCK_SPECIAL_STORAGE_POLICY_H_ +#define WEBKIT_QUOTA_MOCK_SPECIAL_STORAGE_POLICY_H_ + +#include <set> +#include <string> +#include "googleurl/src/gurl.h" +#include "webkit/quota/special_storage_policy.h" + +namespace quota { + +class MockSpecialStoragePolicy : public quota::SpecialStoragePolicy { + public: + MockSpecialStoragePolicy() {} + + virtual bool IsStorageProtected(const GURL& origin); + virtual bool IsStorageUnlimited(const GURL& origin); + virtual bool IsFileHandler(const std::string& extension_id); + + void AddProtected(const GURL& origin) { + protected_.insert(origin); + } + + void AddUnlimited(const GURL& origin) { + unlimited_.insert(origin); + } + + void AddFileHandler(const std::string& id) { + file_handlers_.insert(id); + } + + void Reset() { + protected_.clear(); + unlimited_.clear(); + file_handlers_.clear(); + } + + private: + std::set<GURL> protected_; + std::set<GURL> unlimited_; + std::set<std::string> file_handlers_; +}; +} // namespace quota + +#endif // WEBKIT_QUOTA_MOCK_SPECIAL_STORAGE_POLICY_H_ diff --git a/webkit/quota/mock_storage_client.cc b/webkit/quota/mock_storage_client.cc index e6578cd..89499ce 100644 --- a/webkit/quota/mock_storage_client.cc +++ b/webkit/quota/mock_storage_client.cc @@ -42,10 +42,16 @@ class MockStorageClientIDSequencer { } // anonymous namespace -MockStorageClient::MockStorageClient(QuotaManagerProxy* quota_manager_proxy) +MockStorageClient::MockStorageClient( + QuotaManagerProxy* quota_manager_proxy, + const MockOriginData* mock_data, size_t mock_data_size) : quota_manager_proxy_(quota_manager_proxy), id_(MockStorageClientIDSequencer::GetInstance()->NextMockID()), runnable_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { + for (size_t i = 0; i < mock_data_size; ++i) { + origin_data_[make_pair(GURL(mock_data[i].origin), mock_data[i].type)] = + mock_data[i].usage; + } } MockStorageClient::~MockStorageClient() { @@ -56,21 +62,20 @@ MockStorageClient::~MockStorageClient() { deletion_callbacks_.begin(), deletion_callbacks_.end()); } -void MockStorageClient::AddMockOriginData( +void MockStorageClient::AddOriginAndNotify( const GURL& origin_url, StorageType type, int64 size) { + DCHECK(origin_data_.find(make_pair(origin_url, type)) == origin_data_.end()); + DCHECK_GE(size, 0); origin_data_[make_pair(origin_url, type)] = size; + quota_manager_proxy_->NotifyStorageModified(id(), origin_url, type, size); } -void MockStorageClient::ModifyMockOriginDataSize( +void MockStorageClient::ModifyOriginAndNotify( const GURL& origin_url, StorageType type, int64 delta) { OriginDataMap::iterator find = origin_data_.find(make_pair(origin_url, type)); - if (find == origin_data_.end()) { - DCHECK_GE(delta, 0); - AddMockOriginData(origin_url, type, delta); - } else { - find->second += delta; - DCHECK_GE(find->second, 0); - } + DCHECK(find != origin_data_.end()); + find->second += delta; + DCHECK_GE(find->second, 0); // TODO(tzik): Check quota to prevent usage exceed quota_manager_proxy_->NotifyStorageModified(id(), origin_url, type, delta); diff --git a/webkit/quota/mock_storage_client.h b/webkit/quota/mock_storage_client.h index 6d8def2..51c6985 100644 --- a/webkit/quota/mock_storage_client.h +++ b/webkit/quota/mock_storage_client.h @@ -18,15 +18,23 @@ namespace quota { class QuotaManagerProxy; +struct MockOriginData { + const char* origin; + StorageType type; + int64 usage; +}; + // Mock storage class for testing. class MockStorageClient : public QuotaClient { public: - explicit MockStorageClient(QuotaManagerProxy* quota_manager_proxy); + MockStorageClient(QuotaManagerProxy* quota_manager_proxy, + const MockOriginData* mock_data, size_t mock_data_size); virtual ~MockStorageClient(); // To add or modify mock data in this client. - void AddMockOriginData(const GURL& origin_url, StorageType type, int64 size); - void ModifyMockOriginDataSize( + void AddOriginAndNotify( + const GURL& origin_url, StorageType type, int64 size); + void ModifyOriginAndNotify( const GURL& origin_url, StorageType type, int64 delta); // QuotaClient methods. diff --git a/webkit/quota/quota_database.cc b/webkit/quota/quota_database.cc index 9aa9f3e..592a039 100644 --- a/webkit/quota/quota_database.cc +++ b/webkit/quota/quota_database.cc @@ -15,6 +15,7 @@ #include "base/file_util.h" #include "base/time.h" #include "googleurl/src/gurl.h" +#include "webkit/quota/special_storage_policy.h" namespace { @@ -297,6 +298,7 @@ bool QuotaDatabase::SetGlobalQuota(StorageType type, int64 quota) { bool QuotaDatabase::GetLRUOrigin( StorageType type, const std::set<GURL>& exceptions, + SpecialStoragePolicy* special_storage_policy, GURL* origin) { DCHECK(origin); if (!LazyOpen(false)) @@ -313,10 +315,13 @@ bool QuotaDatabase::GetLRUOrigin( while (statement.Step()) { GURL url(statement.ColumnString(0)); - if (exceptions.find(url) == exceptions.end()) { - *origin = url; - return true; - } + if (exceptions.find(url) != exceptions.end()) + continue; + if (special_storage_policy && + special_storage_policy->IsStorageUnlimited(url)) + continue; + *origin = url; + return true; } *origin = GURL(); diff --git a/webkit/quota/quota_database.h b/webkit/quota/quota_database.h index dc433d6..981bb94 100644 --- a/webkit/quota/quota_database.h +++ b/webkit/quota/quota_database.h @@ -28,6 +28,8 @@ class GURL; namespace quota { +class SpecialStoragePolicy; + // All the methods of this class must run on the DB thread. class QuotaDatabase { public: @@ -55,11 +57,13 @@ class QuotaDatabase { bool GetGlobalQuota(StorageType type, int64* quota); bool SetGlobalQuota(StorageType type, int64 quota); - // Sets |origin| the least recently used origin of origins not included in - // |exceptions|. It returns false when it failed in accessing the database. + // Sets |origin| to the least recently used origin of origins not included + // in |exceptions| and not granted the special unlimited storage right. + // It returns false when it failed in accessing the database. // |origin| is set to empty when there is no matching origin. bool GetLRUOrigin(StorageType type, const std::set<GURL>& exceptions, + SpecialStoragePolicy* special_storage_policy, GURL* origin); // Returns false if SetOriginDatabaseBootstrapped has never diff --git a/webkit/quota/quota_database_unittest.cc b/webkit/quota/quota_database_unittest.cc index bbb0006..f126f31 100644 --- a/webkit/quota/quota_database_unittest.cc +++ b/webkit/quota/quota_database_unittest.cc @@ -15,6 +15,7 @@ #include "base/scoped_temp_dir.h" #include "googleurl/src/gurl.h" #include "testing/gtest/include/gtest/gtest.h" +#include "webkit/quota/mock_special_storage_policy.h" #include "webkit/quota/quota_database.h" namespace { @@ -168,7 +169,8 @@ class QuotaDatabaseTest : public testing::Test { std::set<GURL> exceptions; GURL origin; - EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, &origin)); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); EXPECT_TRUE(origin.is_empty()); const GURL kOrigin1("http://a/"); @@ -188,19 +190,33 @@ class QuotaDatabaseTest : public testing::Test { EXPECT_TRUE(db.SetOriginLastAccessTime( kOrigin4, kStorageTypePersistent, base::Time::FromInternalValue(40))); - EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, &origin)); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); EXPECT_EQ(kOrigin1.spec(), origin.spec()); + // Test that unlimited origins are exluded from eviction, but + // protected origins are not excluded. + scoped_refptr<MockSpecialStoragePolicy> policy( + new MockSpecialStoragePolicy); + policy->AddUnlimited(kOrigin1); + policy->AddProtected(kOrigin2); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + policy, &origin)); + EXPECT_EQ(kOrigin2.spec(), origin.spec()); + exceptions.insert(kOrigin1); - EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, &origin)); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); EXPECT_EQ(kOrigin2.spec(), origin.spec()); exceptions.insert(kOrigin2); - EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, &origin)); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); EXPECT_EQ(kOrigin3.spec(), origin.spec()); exceptions.insert(kOrigin3); - EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, &origin)); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); EXPECT_TRUE(origin.is_empty()); EXPECT_TRUE(db.SetOriginLastAccessTime( @@ -211,12 +227,14 @@ class QuotaDatabaseTest : public testing::Test { // Querying again to see if the deletion has worked. exceptions.clear(); - EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, &origin)); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); EXPECT_EQ(kOrigin2.spec(), origin.spec()); exceptions.insert(kOrigin1); exceptions.insert(kOrigin2); - EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, &origin)); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); EXPECT_TRUE(origin.is_empty()); } diff --git a/webkit/quota/quota_manager.cc b/webkit/quota/quota_manager.cc index c8f1d63..741a9b2 100644 --- a/webkit/quota/quota_manager.cc +++ b/webkit/quota/quota_manager.cc @@ -64,10 +64,12 @@ const int64 MBytes = 1024 * 1024; // (e.g. larger for desktop etc) and may want to have them in preferences. const int64 QuotaManager::kTemporaryStorageQuotaDefaultSize = 50 * MBytes; const int64 QuotaManager::kTemporaryStorageQuotaMaxSize = 1 * 1024 * MBytes; -const char QuotaManager::kDatabaseName[] = "QuotaManager"; - const int64 QuotaManager::kIncognitoDefaultTemporaryQuota = 50 * MBytes; +const int QuotaManager::kPerHostTemporaryPortion = 5; // 20% + +const char QuotaManager::kDatabaseName[] = "QuotaManager"; + // This class is for posting GetUsage/GetQuota tasks, gathering // results and dispatching GetAndQuota callbacks. // This class is self-destructed. @@ -80,14 +82,20 @@ class QuotaManager::UsageAndQuotaDispatcherTask : public QuotaTask { // Returns true if it is the first call for this task; which means // the caller needs to call Start(). - bool AddCallback(GetUsageAndQuotaCallback* callback) { - callbacks_.push_back(callback); - return (callbacks_.size() == 1); + bool AddCallback(GetUsageAndQuotaCallback* callback, bool unlimited) { + if (unlimited) + unlimited_callbacks_.push_back(callback); + else + callbacks_.push_back(callback); + return (callbacks_.size() + unlimited_callbacks_.size() == 1); } - void DidGetGlobalUsage(StorageType type, int64 usage) { + void DidGetGlobalUsage(StorageType type, int64 usage, + int64 unlimited_usage) { DCHECK_EQ(type_, type); + DCHECK_GE(usage, unlimited_usage); global_usage_ = usage; + global_unlimited_usage_ = unlimited_usage; CheckCompleted(); } @@ -130,6 +138,7 @@ class QuotaManager::UsageAndQuotaDispatcherTask : public QuotaTask { type_(type), quota_(-1), global_usage_(-1), + global_unlimited_usage_(-1), host_usage_(-1), quota_status_(kQuotaStatusUnknown), waiting_callbacks_(1), @@ -137,11 +146,13 @@ class QuotaManager::UsageAndQuotaDispatcherTask : public QuotaTask { virtual ~UsageAndQuotaDispatcherTask() { STLDeleteContainerPointers(callbacks_.begin(), callbacks_.end()); + STLDeleteContainerPointers(unlimited_callbacks_.begin(), + unlimited_callbacks_.end()); } // Subclasses must implement them. virtual void RunBody() = 0; - virtual void DispatchCallback(GetUsageAndQuotaCallback* callback) = 0; + virtual void DispatchCallbacks() = 0; virtual void Run() OVERRIDE { RunBody(); @@ -152,13 +163,8 @@ class QuotaManager::UsageAndQuotaDispatcherTask : public QuotaTask { } virtual void Aborted() OVERRIDE { - for (CallbackList::iterator iter = callbacks_.begin(); - iter != callbacks_.end(); - ++iter) { - (*iter)->Run(kQuotaErrorAbort, 0, 0); - delete *iter; - } - callbacks_.clear(); + CallCallbacksAndClear(&callbacks_, kQuotaErrorAbort, 0, 0); + CallCallbacksAndClear(&unlimited_callbacks_, kQuotaErrorAbort, 0, 0); DeleteSoon(); } @@ -166,6 +172,17 @@ class QuotaManager::UsageAndQuotaDispatcherTask : public QuotaTask { DeleteSoon(); } + void CallCallbacksAndClear( + CallbackList* callbacks, QuotaStatusCode status, + int64 usage, int64 quota) { + for (CallbackList::iterator iter = callbacks->begin(); + iter != callbacks->end(); ++iter) { + (*iter)->Run(status, usage, quota); + delete *iter; + } + callbacks->clear(); + } + QuotaManager* manager() const { return static_cast<QuotaManager*>(observer()); } @@ -174,12 +191,15 @@ class QuotaManager::UsageAndQuotaDispatcherTask : public QuotaTask { StorageType type() const { return type_; } int64 quota() const { return quota_; } int64 global_usage() const { return global_usage_; } + int64 global_unlimited_usage() const { return global_unlimited_usage_; } int64 host_usage() const { return host_usage_; } QuotaStatusCode quota_status() const { return quota_status_; } + CallbackList& callbacks() { return callbacks_; } + CallbackList& unlimited_callbacks() { return unlimited_callbacks_; } // Subclasses must call following methods to create a new 'waitable' // callback, which decrements waiting_callbacks when it is called. - UsageCallback* NewWaitableGlobalUsageCallback() { + GlobalUsageCallback* NewWaitableGlobalUsageCallback() { ++waiting_callbacks_; return callback_factory_.NewCallback( &UsageAndQuotaDispatcherTask::DidGetGlobalUsage); @@ -203,14 +223,10 @@ class QuotaManager::UsageAndQuotaDispatcherTask : public QuotaTask { private: void CheckCompleted() { if (--waiting_callbacks_ <= 0) { - // Dispatches callbacks. - for (CallbackList::iterator iter = callbacks_.begin(); - iter != callbacks_.end(); - ++iter) { - DispatchCallback(*iter); - delete *iter; - } - callbacks_.clear(); + DispatchCallbacks(); + DCHECK(callbacks_.empty()); + DCHECK(unlimited_callbacks_.empty()); + UsageAndQuotaDispatcherTaskMap& dispatcher_map = manager()->usage_and_quota_dispatchers_; DCHECK(dispatcher_map.find(std::make_pair(host_, type_)) != @@ -224,9 +240,11 @@ class QuotaManager::UsageAndQuotaDispatcherTask : public QuotaTask { const StorageType type_; int64 quota_; int64 global_usage_; + int64 global_unlimited_usage_; int64 host_usage_; QuotaStatusCode quota_status_; CallbackList callbacks_; + CallbackList unlimited_callbacks_; int waiting_callbacks_; ScopedCallbackFactory<UsageAndQuotaDispatcherTask> callback_factory_; @@ -249,12 +267,31 @@ class QuotaManager::UsageAndQuotaDispatcherTaskForTemporary manager()->GetTemporaryGlobalQuota(NewWaitableGlobalQuotaCallback()); } - virtual void DispatchCallback(GetUsageAndQuotaCallback* callback) OVERRIDE { - // TODO(kinuko): For now it returns pessimistic quota. Change this - // to return {usage, quota - nonevictable_usage} once eviction is - // supported. - int64 other_usage = global_usage() - host_usage(); - callback->Run(quota_status(), host_usage(), quota() - other_usage); + virtual void DispatchCallbacks() OVERRIDE { + // Due to a mismatch of models between same-origin (required by stds) vs + // per-host (as this info is viewed in the UI) vs the extension systems + // notion of webextents (the basis of granting unlimited rights), + // we can end up with both 'unlimited' and 'limited' callbacks to invoke. + // Should be rare, but it can happen. + + CallCallbacksAndClear(&unlimited_callbacks(), quota_status(), + host_usage(), kint64max); + + if (!callbacks().empty()) { + // Allow an individual host to utilize a fraction of the total + // pool available for temp storage. + int64 host_quota = quota() / kPerHostTemporaryPortion; + + // But if total temp usage is over-budget, stop letting new data in + // until we reclaim space. + DCHECK_GE(global_usage(), global_unlimited_usage()); + int64 limited_global_usage = global_usage() - global_unlimited_usage(); + if (limited_global_usage > quota()) + host_quota = std::min(host_quota, host_usage()); + + CallCallbacksAndClear(&callbacks(), quota_status(), + host_usage(), host_quota); + } } }; @@ -273,8 +310,11 @@ class QuotaManager::UsageAndQuotaDispatcherTaskForPersistent host(), NewWaitableHostQuotaCallback()); } - virtual void DispatchCallback(GetUsageAndQuotaCallback* callback) OVERRIDE { - callback->Run(quota_status(), host_usage(), quota()); + virtual void DispatchCallbacks() OVERRIDE { + CallCallbacksAndClear(&callbacks(), quota_status(), + host_usage(), quota()); + CallCallbacksAndClear(&unlimited_callbacks(), quota_status(), + host_usage(), kint64max); } }; @@ -471,6 +511,11 @@ class QuotaManager::PersistentHostQuotaUpdateTask callback_->Run(db_disabled() ? kQuotaErrorInvalidAccess : kQuotaStatusOk, host_, kStorageTypePersistent, new_quota_); } + + virtual void Aborted() OVERRIDE { + callback_.reset(); + } + private: std::string host_; int64 new_quota_; @@ -489,7 +534,8 @@ class QuotaManager::GetLRUOriginTask GetLRUOriginCallback *callback) : DatabaseTaskBase(manager, database, db_message_loop), type_(type), - callback_(callback) { + callback_(callback), + special_storage_policy_(manager->special_storage_policy_) { for (std::map<GURL, int>::const_iterator p = origins_in_use.begin(); p != origins_in_use.end(); ++p) { @@ -500,7 +546,8 @@ class QuotaManager::GetLRUOriginTask protected: virtual void RunOnTargetThread() OVERRIDE { - if (!database()->GetLRUOrigin(type_, exceptions_, &url_)) + if (!database()->GetLRUOrigin(type_, exceptions_, + special_storage_policy_, &url_)) set_db_disabled(true); } @@ -508,10 +555,15 @@ class QuotaManager::GetLRUOriginTask callback_->Run(url_); } + virtual void Aborted() OVERRIDE { + callback_.reset(); + } + private: StorageType type_; std::set<GURL> exceptions_; scoped_ptr<GetLRUOriginCallback> callback_; + scoped_refptr<SpecialStoragePolicy> special_storage_policy_; GURL url_; }; @@ -731,7 +783,8 @@ class QuotaManager::DumpLastAccessTimeTableTask QuotaManager::QuotaManager(bool is_incognito, const FilePath& profile_path, base::MessageLoopProxy* io_thread, - base::MessageLoopProxy* db_thread) + base::MessageLoopProxy* db_thread, + SpecialStoragePolicy* special_storage_policy) : is_incognito_(is_incognito), profile_path_(profile_path), proxy_(new QuotaManagerProxy( @@ -741,6 +794,7 @@ QuotaManager::QuotaManager(bool is_incognito, db_thread_(db_thread), need_initialize_origins_(false), temporary_global_quota_(-1), + special_storage_policy_(special_storage_policy), callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { } @@ -774,8 +828,10 @@ void QuotaManager::GetUsageAndQuota( found = usage_and_quota_dispatchers_.insert( std::make_pair(std::make_pair(host, type), dispatcher)).first; } - if (found->second->AddCallback(callback.release())) + if (found->second->AddCallback( + callback.release(), IsStorageUnlimited(origin))) { found->second->Start(); + } } void QuotaManager::RequestQuota( @@ -864,7 +920,7 @@ void QuotaManager::SetPersistentHostQuota(const std::string& host, void QuotaManager::GetGlobalUsage( StorageType type, - UsageCallback* callback) { + GlobalUsageCallback* callback) { LazyInitialize(); GetUsageTracker(type)->GetGlobalUsage(callback); } @@ -887,9 +943,11 @@ void QuotaManager::LazyInitialize() { profile_path_.AppendASCII(kDatabaseName))); temporary_usage_tracker_.reset( - new UsageTracker(clients_, kStorageTypeTemporary)); + new UsageTracker(clients_, kStorageTypeTemporary, + special_storage_policy_)); persistent_usage_tracker_.reset( - new UsageTracker(clients_, kStorageTypePersistent)); + new UsageTracker(clients_, kStorageTypePersistent, + special_storage_policy_)); scoped_refptr<InitializeTask> task( new InitializeTask(this, database_.get(), db_thread_, @@ -1070,7 +1128,9 @@ void QuotaManager::DidGetAvailableSpaceForEviction( QuotaStatusCode status, int64 available_space) { eviction_context_.get_usage_and_quota_callback->Run(status, - eviction_context_.usage, eviction_context_.quota, available_space); + eviction_context_.usage, + eviction_context_.unlimited_usage, + eviction_context_.quota, available_space); eviction_context_.get_usage_and_quota_callback.reset(); } @@ -1080,8 +1140,8 @@ void QuotaManager::DidGetGlobalQuotaForEviction( int64 quota) { DCHECK_EQ(type, kStorageTypeTemporary); if (status != kQuotaStatusOk) { - eviction_context_.get_usage_and_quota_callback->Run(status, - eviction_context_.usage, quota, 0); + eviction_context_.get_usage_and_quota_callback->Run( + status, 0, 0, 0, 0); eviction_context_.get_usage_and_quota_callback.reset(); return; } @@ -1091,10 +1151,12 @@ void QuotaManager::DidGetGlobalQuotaForEviction( NewCallback(&QuotaManager::DidGetAvailableSpaceForEviction)); } -void QuotaManager::DidGetGlobalUsageForEviction(StorageType type, - int64 usage) { +void QuotaManager::DidGetGlobalUsageForEviction( + StorageType type, int64 usage, int64 unlimited_usage) { DCHECK_EQ(type, kStorageTypeTemporary); + DCHECK_GE(usage, unlimited_usage); eviction_context_.usage = usage; + eviction_context_.unlimited_usage = unlimited_usage; GetTemporaryGlobalQuota(callback_factory_. NewCallback(&QuotaManager::DidGetGlobalQuotaForEviction)); } @@ -1142,7 +1204,7 @@ void QuotaManager::DidInitializeTemporaryGlobalQuota(int64 quota) { } void QuotaManager::DidRunInitialGetTemporaryGlobalUsage( - StorageType type, int64 usage_unused) { + StorageType type, int64 usage_unused, int64 unlimited_usage_unused) { DCHECK_EQ(type, kStorageTypeTemporary); // This will call the StartEviction() when initial origin registration // is completed. diff --git a/webkit/quota/quota_manager.h b/webkit/quota/quota_manager.h index 7d0cd66..ee279ae 100644 --- a/webkit/quota/quota_manager.h +++ b/webkit/quota/quota_manager.h @@ -23,6 +23,7 @@ #include "webkit/quota/quota_client.h" #include "webkit/quota/quota_task.h" #include "webkit/quota/quota_types.h" +#include "webkit/quota/special_storage_policy.h" namespace base { class MessageLoopProxy; @@ -44,8 +45,9 @@ class QuotaEvictionHandler { typedef Callback1<const GURL&>::Type GetLRUOriginCallback; typedef Callback1<QuotaStatusCode>::Type EvictOriginDataCallback; - typedef Callback4<QuotaStatusCode, + typedef Callback5<QuotaStatusCode, int64 /* usage */, + int64 /* unlimited_usage */, int64 /* quota */, int64 /* physical_available */ >::Type GetUsageAndQuotaForEvictionCallback; @@ -82,7 +84,8 @@ class QuotaManager : public QuotaTaskObserver, QuotaManager(bool is_incognito, const FilePath& profile_path, base::MessageLoopProxy* io_thread, - base::MessageLoopProxy* db_thread); + base::MessageLoopProxy* db_thread, + SpecialStoragePolicy* special_storage_policy); virtual ~QuotaManager(); @@ -134,17 +137,26 @@ class QuotaManager : public QuotaTaskObserver, void SetPersistentHostQuota(const std::string& host, int64 new_quota, HostQuotaCallback* callback); - - void GetGlobalUsage(StorageType type, UsageCallback* callback); + void GetGlobalUsage(StorageType type, GlobalUsageCallback* callback); void GetHostUsage(const std::string& host, StorageType type, HostUsageCallback* callback); + bool IsStorageUnlimited(const GURL& origin) const { + return special_storage_policy_.get() && + special_storage_policy_->IsStorageUnlimited(origin); + } + + // Used to determine the total size of the temp pool. static const int64 kTemporaryStorageQuotaDefaultSize; static const int64 kTemporaryStorageQuotaMaxSize; - static const char kDatabaseName[]; - static const int64 kIncognitoDefaultTemporaryQuota; + // Determines the portion of the temp pool that can be + // utilized by a single host (ie. 5 for 20%). + static const int kPerHostTemporaryPortion; + + static const char kDatabaseName[]; + private: class DatabaseTaskBase; class InitializeTask; @@ -179,6 +191,7 @@ class QuotaManager : public QuotaTaskObserver, num_evicted_clients(0), num_eviction_error(0), usage(0), + unlimited_usage(0), quota(0) {} virtual ~EvictionContext() {} @@ -190,6 +203,7 @@ class QuotaManager : public QuotaTaskObserver, scoped_ptr<GetUsageAndQuotaForEvictionCallback> get_usage_and_quota_callback; int64 usage; + int64 unlimited_usage; int64 quota; }; @@ -233,7 +247,8 @@ class QuotaManager : public QuotaTaskObserver, QuotaStatusCode status, StorageType type, int64 quota); - void DidGetGlobalUsageForEviction(StorageType type, int64 usage); + void DidGetGlobalUsageForEviction(StorageType type, int64 usage, + int64 unlimited_usage); // QuotaEvictionHandler. virtual void GetLRUOrigin( @@ -247,7 +262,8 @@ class QuotaManager : public QuotaTaskObserver, GetUsageAndQuotaForEvictionCallback* callback) OVERRIDE; void DidInitializeTemporaryGlobalQuota(int64 quota); - void DidRunInitialGetTemporaryGlobalUsage(StorageType type, int64 usage); + void DidRunInitialGetTemporaryGlobalUsage(StorageType type, int64 usage, + int64 unlimited_usage); void DidGetDatabaseLRUOrigin(const GURL& origin); void DeleteOnCorrectThread() const; @@ -269,6 +285,8 @@ class QuotaManager : public QuotaTaskObserver, scoped_ptr<UsageTracker> temporary_usage_tracker_; scoped_ptr<UsageTracker> persistent_usage_tracker_; + // TODO(michaeln): Need a way to clear the cache, drop and + // reinstantiate the trackers when they're not handling requests. EvictionContext eviction_context_; @@ -280,6 +298,8 @@ class QuotaManager : public QuotaTaskObserver, // Map from origin to count. std::map<GURL, int> origins_in_use_; + scoped_refptr<SpecialStoragePolicy> special_storage_policy_; + base::ScopedCallbackFactory<QuotaManager> callback_factory_; DISALLOW_COPY_AND_ASSIGN(QuotaManager); @@ -323,7 +343,6 @@ class QuotaManagerProxy DISALLOW_COPY_AND_ASSIGN(QuotaManagerProxy); }; - } // namespace quota #endif // WEBKIT_QUOTA_QUOTA_MANAGER_H_ diff --git a/webkit/quota/quota_manager_unittest.cc b/webkit/quota/quota_manager_unittest.cc index 6898f1a..e3611c5 100644 --- a/webkit/quota/quota_manager_unittest.cc +++ b/webkit/quota/quota_manager_unittest.cc @@ -17,6 +17,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebStorageQuotaError.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebStorageQuotaType.h" +#include "webkit/quota/mock_special_storage_policy.h" #include "webkit/quota/mock_storage_client.h" #include "webkit/quota/quota_database.h" #include "webkit/quota/quota_manager.h" @@ -27,14 +28,6 @@ using WebKit::WebStorageQuotaType; namespace quota { -namespace { -struct MockOriginData { - const char* origin; - StorageType type; - int64 usage; -}; -} // anonymous namespace - class QuotaManagerTest : public testing::Test { protected: typedef QuotaManager::QuotaTableEntry QuotaTableEntry; @@ -49,11 +42,13 @@ class QuotaManagerTest : public testing::Test { void SetUp() { ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + mock_special_storage_policy_ = new MockSpecialStoragePolicy; quota_manager_ = new QuotaManager( false /* is_incognito */, data_dir_.path(), MessageLoopProxy::CreateForCurrentThread(), - MessageLoopProxy::CreateForCurrentThread()); + MessageLoopProxy::CreateForCurrentThread(), + mock_special_storage_policy_); additional_callback_count_ = 0; } @@ -66,13 +61,8 @@ class QuotaManagerTest : public testing::Test { protected: MockStorageClient* CreateClient( const MockOriginData* mock_data, size_t mock_data_size) { - MockStorageClient* client = new MockStorageClient(quota_manager_->proxy()); - for (size_t i = 0; i < mock_data_size; ++i) { - client->AddMockOriginData(GURL(mock_data[i].origin), - mock_data[i].type, - mock_data[i].usage); - } - return client; + return new MockStorageClient(quota_manager_->proxy(), + mock_data, mock_data_size); } void RegisterClient(MockStorageClient* client) { @@ -127,6 +117,7 @@ class QuotaManagerTest : public testing::Test { void GetGlobalUsage(StorageType type) { type_ = kStorageTypeUnknown; usage_ = -1; + unlimited_usage_ = -1; quota_manager_->GetGlobalUsage(type, callback_factory_.NewCallback( &QuotaManagerTest::DidGetGlobalUsage)); @@ -176,6 +167,7 @@ class QuotaManagerTest : public testing::Test { void GetUsageAndQuotaForEviction() { quota_status_ = kQuotaStatusUnknown; usage_ = -1; + unlimited_usage_ = -1; quota_ = -1; available_space_ = -1; quota_manager_->GetUsageAndQuotaForEviction( @@ -257,9 +249,11 @@ class QuotaManagerTest : public testing::Test { quota_ = quota; } - void DidGetGlobalUsage(StorageType type, int64 usage) { + void DidGetGlobalUsage(StorageType type, int64 usage, + int64 unlimited_usage) { type_ = type; usage_ = usage; + unlimited_usage_ = unlimited_usage; } void DidGetHostUsage(const std::string& host, @@ -279,9 +273,10 @@ class QuotaManagerTest : public testing::Test { } void DidGetUsageAndQuotaForEviction(QuotaStatusCode status, - int64 usage, int64 quota, int64 available_space) { + int64 usage, int64 unlimited_usage, int64 quota, int64 available_space) { quota_status_ = status; usage_ = usage; + unlimited_usage_ = unlimited_usage; quota_ = quota; available_space_ = available_space; } @@ -312,10 +307,15 @@ class QuotaManagerTest : public testing::Test { quota_manager_ = quota_manager; } + MockSpecialStoragePolicy* mock_special_storage_policy() const { + return mock_special_storage_policy_.get(); + } + QuotaStatusCode status() const { return quota_status_; } const std::string& host() const { return host_; } StorageType type() const { return type_; } int64 usage() const { return usage_; } + int64 unlimited_usage() const { return unlimited_usage_; } int64 quota() const { return quota_; } int64 available_space() const { return available_space_; } const GURL& lru_origin() const { return lru_origin_; } @@ -330,11 +330,13 @@ class QuotaManagerTest : public testing::Test { base::ScopedCallbackFactory<QuotaManagerTest> callback_factory_; scoped_refptr<QuotaManager> quota_manager_; + scoped_refptr<MockSpecialStoragePolicy> mock_special_storage_policy_; QuotaStatusCode quota_status_; std::string host_; StorageType type_; int64 usage_; + int64 unlimited_usage_; int64 quota_; int64 available_space_; GURL lru_origin_; @@ -371,7 +373,7 @@ TEST_F(QuotaManagerTest, GetUsageAndQuota_Simple) { MessageLoop::current()->RunAllPending(); EXPECT_EQ(kQuotaStatusOk, status()); EXPECT_EQ(0, usage()); - EXPECT_EQ(quota_returned_for_foo - 10, quota()); + EXPECT_EQ(quota_returned_for_foo, quota()); } TEST_F(QuotaManagerTest, GetUsage_NoClient) { @@ -396,10 +398,12 @@ TEST_F(QuotaManagerTest, GetUsage_NoClient) { GetGlobalUsage(kStorageTypeTemporary); MessageLoop::current()->RunAllPending(); EXPECT_EQ(0, usage()); + EXPECT_EQ(0, unlimited_usage()); GetGlobalUsage(kStorageTypePersistent); MessageLoop::current()->RunAllPending(); EXPECT_EQ(0, usage()); + EXPECT_EQ(0, unlimited_usage()); } TEST_F(QuotaManagerTest, GetUsage_EmptyClient) { @@ -425,10 +429,12 @@ TEST_F(QuotaManagerTest, GetUsage_EmptyClient) { GetGlobalUsage(kStorageTypeTemporary); MessageLoop::current()->RunAllPending(); EXPECT_EQ(0, usage()); + EXPECT_EQ(0, unlimited_usage()); GetGlobalUsage(kStorageTypePersistent); MessageLoop::current()->RunAllPending(); EXPECT_EQ(0, usage()); + EXPECT_EQ(0, unlimited_usage()); } TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_MultiOrigins) { @@ -454,14 +460,17 @@ TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_MultiOrigins) { EXPECT_EQ(kQuotaStatusOk, status()); EXPECT_EQ(10 + 20, usage()); - // The returned quota must be equal to (global_quota - other_origins_usage). - EXPECT_EQ(100 - (5 + 7 + 30), quota()); + const int kPerHostQuota = 100 / QuotaManager::kPerHostTemporaryPortion; + + // The host's quota should be its full portion of the global quota + // since global usage is under the global quota. + EXPECT_EQ(kPerHostQuota, quota()); GetUsageAndQuota(GURL("http://bar.com/"), kStorageTypeTemporary); MessageLoop::current()->RunAllPending(); EXPECT_EQ(kQuotaStatusOk, status()); EXPECT_EQ(5 + 7, usage()); - EXPECT_EQ(100 - (10 + 20 + 30), quota()); + EXPECT_EQ(kPerHostQuota, quota()); } TEST_F(QuotaManagerTest, GetUsage_MultipleClients) { @@ -469,11 +478,14 @@ TEST_F(QuotaManagerTest, GetUsage_MultipleClients) { { "http://foo.com/", kStorageTypeTemporary, 10 }, { "http://bar.com/", kStorageTypeTemporary, 20 }, { "http://bar.com/", kStorageTypePersistent, 50 }, + { "http://unlimited/", kStorageTypePersistent, 1 }, }; static const MockOriginData kData2[] = { { "https://foo.com/", kStorageTypeTemporary, 30 }, { "http://example.com/", kStorageTypePersistent, 40 }, + { "http://unlimited/", kStorageTypeTemporary, 1 }, }; + mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/")); RegisterClient(CreateClient(kData1, ARRAYSIZE_UNSAFE(kData1))); RegisterClient(CreateClient(kData2, ARRAYSIZE_UNSAFE(kData2))); @@ -487,15 +499,29 @@ TEST_F(QuotaManagerTest, GetUsage_MultipleClients) { EXPECT_EQ(kQuotaStatusOk, status()); EXPECT_EQ(50, usage()); + GetUsageAndQuota(GURL("http://unlimited/"), kStorageTypeTemporary); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(1, usage()); + EXPECT_EQ(kint64max, quota()); + + GetUsageAndQuota(GURL("http://unlimited/"), kStorageTypePersistent); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(1, usage()); + EXPECT_EQ(kint64max, quota()); + GetGlobalUsage(kStorageTypeTemporary); MessageLoop::current()->RunAllPending(); EXPECT_EQ(kQuotaStatusOk, status()); - EXPECT_EQ(10 + 20 + 30, usage()); + EXPECT_EQ(10 + 20 + 30 + 1, usage()); + EXPECT_EQ(1, unlimited_usage()); GetGlobalUsage(kStorageTypePersistent); MessageLoop::current()->RunAllPending(); EXPECT_EQ(kQuotaStatusOk, status()); - EXPECT_EQ(40 + 50, usage()); + EXPECT_EQ(40 + 50 + 1, usage()); + EXPECT_EQ(1, unlimited_usage()); } void QuotaManagerTest::GetUsage_WithModifyTestBody(const StorageType type) { @@ -511,11 +537,9 @@ void QuotaManagerTest::GetUsage_WithModifyTestBody(const StorageType type) { EXPECT_EQ(kQuotaStatusOk, status()); EXPECT_EQ(10 + 20, usage()); - client->ModifyMockOriginDataSize(GURL("http://foo.com/"), type, 30); - client->ModifyMockOriginDataSize(GURL("http://foo.com:1/"), type, -5); - - // Modifying (adding) a new origin for an existing host. - client->ModifyMockOriginDataSize(GURL("https://foo.com/"), type, 1); + client->ModifyOriginAndNotify(GURL("http://foo.com/"), type, 30); + client->ModifyOriginAndNotify(GURL("http://foo.com:1/"), type, -5); + client->AddOriginAndNotify(GURL("https://foo.com/"), type, 1); GetUsageAndQuota(GURL("http://foo.com/"), type); MessageLoop::current()->RunAllPending(); @@ -523,8 +547,7 @@ void QuotaManagerTest::GetUsage_WithModifyTestBody(const StorageType type) { EXPECT_EQ(10 + 20 + 30 - 5 + 1, usage()); int foo_usage = usage(); - // Modifying (adding) a new origin. - client->ModifyMockOriginDataSize(GURL("http://bar.com/"), type, 40); + client->AddOriginAndNotify(GURL("http://bar.com/"), type, 40); GetUsageAndQuota(GURL("http://bar.com/"), type); MessageLoop::current()->RunAllPending(); EXPECT_EQ(kQuotaStatusOk, status()); @@ -533,6 +556,7 @@ void QuotaManagerTest::GetUsage_WithModifyTestBody(const StorageType type) { GetGlobalUsage(type); MessageLoop::current()->RunAllPending(); EXPECT_EQ(foo_usage + 40, usage()); + EXPECT_EQ(0, unlimited_usage()); } TEST_F(QuotaManagerTest, GetTemporaryUsage_WithModify) { @@ -550,13 +574,15 @@ TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_WithAdditionalTasks) { SetTemporaryGlobalQuota(100); MessageLoop::current()->RunAllPending(); + const int kPerHostQuota = 100 / QuotaManager::kPerHostTemporaryPortion; + GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary); GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary); GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary); MessageLoop::current()->RunAllPending(); EXPECT_EQ(kQuotaStatusOk, status()); EXPECT_EQ(10 + 20, usage()); - EXPECT_EQ(100 - 13, quota()); + EXPECT_EQ(kPerHostQuota, quota()); set_additional_callback_count(0); RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"), @@ -567,7 +593,7 @@ TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_WithAdditionalTasks) { MessageLoop::current()->RunAllPending(); EXPECT_EQ(kQuotaStatusOk, status()); EXPECT_EQ(10 + 20, usage()); - EXPECT_EQ(100 - 13, quota()); + EXPECT_EQ(kPerHostQuota, quota()); EXPECT_EQ(2, additional_callback_count()); } @@ -595,6 +621,98 @@ TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_NukeManager) { EXPECT_EQ(kQuotaErrorAbort, status()); } +TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_Overbudget) { + static const MockOriginData kData[] = { + { "http://usage1/", kStorageTypeTemporary, 1 }, + { "http://usage10/", kStorageTypeTemporary, 10 }, + { "http://usage200/", kStorageTypeTemporary, 200 }, + }; + RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData))); + SetTemporaryGlobalQuota(100); + MessageLoop::current()->RunAllPending(); + + const int kPerHostQuota = 100 / QuotaManager::kPerHostTemporaryPortion; + + GetUsageAndQuota(GURL("http://usage1/"), kStorageTypeTemporary); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(1, usage()); + EXPECT_EQ(1, quota()); // should be clamped to our current usage + + GetUsageAndQuota(GURL("http://usage10/"), kStorageTypeTemporary); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10, usage()); + EXPECT_EQ(10, quota()); + + GetUsageAndQuota(GURL("http://usage200/"), kStorageTypeTemporary); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(200, usage()); + EXPECT_EQ(kPerHostQuota, quota()); // should be clamped to the nominal quota +} + +TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_Unlimited) { + static const MockOriginData kData[] = { + { "http://usage10/", kStorageTypeTemporary, 10 }, + { "http://usage50/", kStorageTypeTemporary, 50 }, + { "http://unlimited/", kStorageTypeTemporary, 4000 }, + }; + mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/")); + MockStorageClient* client = CreateClient(kData, ARRAYSIZE_UNSAFE(kData)); + RegisterClient(client); + + // Test when not overbugdet. + SetTemporaryGlobalQuota(1000); + MessageLoop::current()->RunAllPending(); + + const int kPerHostQuotaFor1000 = + 1000 / QuotaManager::kPerHostTemporaryPortion; + + GetUsageAndQuota(GURL("http://usage10/"), kStorageTypeTemporary); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10, usage()); + EXPECT_EQ(kPerHostQuotaFor1000, quota()); + + GetUsageAndQuota(GURL("http://usage50/"), kStorageTypeTemporary); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(50, usage()); + EXPECT_EQ(kPerHostQuotaFor1000, quota()); + + GetUsageAndQuota(GURL("http://unlimited/"), kStorageTypeTemporary); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(4000, usage()); + EXPECT_EQ(kint64max, quota()); + + // Test when overbugdet. + SetTemporaryGlobalQuota(100); + MessageLoop::current()->RunAllPending(); + + const int kPerHostQuotaFor100 = + 100 / QuotaManager::kPerHostTemporaryPortion; + + GetUsageAndQuota(GURL("http://usage10/"), kStorageTypeTemporary); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10, usage()); + EXPECT_EQ(kPerHostQuotaFor100, quota()); + + GetUsageAndQuota(GURL("http://usage50/"), kStorageTypeTemporary); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(50, usage()); + EXPECT_EQ(kPerHostQuotaFor100, quota()); + + GetUsageAndQuota(GURL("http://unlimited/"), kStorageTypeTemporary); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(4000, usage()); + EXPECT_EQ(kint64max, quota()); +} + TEST_F(QuotaManagerTest, OriginInUse) { const GURL kFooOrigin("http://foo.com/"); const GURL kBarOrigin("http://bar.com/"); @@ -749,10 +867,12 @@ TEST_F(QuotaManagerTest, GetUsage_Simple) { GetGlobalUsage(kStorageTypePersistent); MessageLoop::current()->RunAllPending(); EXPECT_EQ(usage(), 1 + 20 + 600000); + EXPECT_EQ(0, unlimited_usage()); GetGlobalUsage(kStorageTypeTemporary); MessageLoop::current()->RunAllPending(); EXPECT_EQ(usage(), 300 + 4000 + 50000 + 7000000); + EXPECT_EQ(0, unlimited_usage()); GetHostUsage("foo.com", kStorageTypePersistent); MessageLoop::current()->RunAllPending(); @@ -780,30 +900,34 @@ TEST_F(QuotaManagerTest, GetUsage_WithModification) { GetGlobalUsage(kStorageTypePersistent); MessageLoop::current()->RunAllPending(); EXPECT_EQ(usage(), 1 + 20 + 600000); + EXPECT_EQ(0, unlimited_usage()); - client->ModifyMockOriginDataSize( + client->ModifyOriginAndNotify( GURL("http://foo.com/"), kStorageTypePersistent, 80000000); GetGlobalUsage(kStorageTypePersistent); MessageLoop::current()->RunAllPending(); EXPECT_EQ(usage(), 1 + 20 + 600000 + 80000000); + EXPECT_EQ(0, unlimited_usage()); GetGlobalUsage(kStorageTypeTemporary); MessageLoop::current()->RunAllPending(); EXPECT_EQ(usage(), 300 + 4000 + 50000 + 7000000); + EXPECT_EQ(0, unlimited_usage()); - client->ModifyMockOriginDataSize( + client->ModifyOriginAndNotify( GURL("http://foo.com/"), kStorageTypeTemporary, 1); GetGlobalUsage(kStorageTypeTemporary); MessageLoop::current()->RunAllPending(); EXPECT_EQ(usage(), 300 + 4000 + 50000 + 7000000 + 1); + EXPECT_EQ(0, unlimited_usage()); GetHostUsage("buz.com", kStorageTypeTemporary); MessageLoop::current()->RunAllPending(); EXPECT_EQ(usage(), 4000 + 50000); - client->ModifyMockOriginDataSize( + client->ModifyOriginAndNotify( GURL("http://buz.com/"), kStorageTypeTemporary, 900000000); GetHostUsage("buz.com", kStorageTypeTemporary); @@ -910,9 +1034,10 @@ TEST_F(QuotaManagerTest, GetUsageAndQuotaForEviction) { { "http://foo.com/", kStorageTypeTemporary, 1 }, { "http://foo.com:1/", kStorageTypeTemporary, 20 }, { "http://foo.com/", kStorageTypePersistent, 300 }, - { "http://bar.com/", kStorageTypeTemporary, 4000 }, + { "http://unlimited/", kStorageTypeTemporary, 4000 }, }; + mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/")); MockStorageClient* client = CreateClient(kData, ARRAYSIZE_UNSAFE(kData)); RegisterClient(client); @@ -923,6 +1048,7 @@ TEST_F(QuotaManagerTest, GetUsageAndQuotaForEviction) { MessageLoop::current()->RunAllPending(); EXPECT_EQ(kQuotaStatusOk, status()); EXPECT_EQ(4021, usage()); + EXPECT_EQ(4000, unlimited_usage()); EXPECT_EQ(10000000, quota()); EXPECT_LE(0, available_space()); } diff --git a/webkit/quota/quota_temporary_storage_evictor.cc b/webkit/quota/quota_temporary_storage_evictor.cc index 2c77056..1df3436 100644 --- a/webkit/quota/quota_temporary_storage_evictor.cc +++ b/webkit/quota/quota_temporary_storage_evictor.cc @@ -53,14 +53,20 @@ void QuotaTemporaryStorageEvictor::ConsiderEviction() { void QuotaTemporaryStorageEvictor::OnGotUsageAndQuotaForEviction( QuotaStatusCode status, int64 usage, + int64 unlimited_usage, int64 quota, int64 available_disk_space) { DCHECK(io_thread_->BelongsToCurrentThread()); + DCHECK_GE(usage, unlimited_usage); // unlimited_usage is a subset of usage + + usage -= unlimited_usage; if (status == kQuotaStatusOk && (usage > quota * kUsageRatioToStartEviction || min_available_disk_space_to_start_eviction_ > available_disk_space)) { // Space is getting tight. Get the least recently used origin and continue. + // TODO(michaeln): if the reason for eviction is low physical disk space, + // make 'unlimited' origins subject to eviction too. quota_eviction_handler_->GetLRUOrigin(kStorageTypeTemporary, callback_factory_.NewCallback( &QuotaTemporaryStorageEvictor::OnGotLRUOrigin)); diff --git a/webkit/quota/quota_temporary_storage_evictor.h b/webkit/quota/quota_temporary_storage_evictor.h index ef92795..6a7c97e1 100644 --- a/webkit/quota/quota_temporary_storage_evictor.h +++ b/webkit/quota/quota_temporary_storage_evictor.h @@ -43,6 +43,7 @@ class QuotaTemporaryStorageEvictor { void OnGotUsageAndQuotaForEviction( QuotaStatusCode status, int64 usage, + int64 unlimited_usage, int64 quota, int64 available_disk_space); void OnGotLRUOrigin(const GURL& origin); diff --git a/webkit/quota/quota_temporary_storage_evictor_unittest.cc b/webkit/quota/quota_temporary_storage_evictor_unittest.cc index 6345c64..ed17752 100644 --- a/webkit/quota/quota_temporary_storage_evictor_unittest.cc +++ b/webkit/quota/quota_temporary_storage_evictor_unittest.cc @@ -25,6 +25,7 @@ class MockQuotaEvictionHandler : public quota::QuotaEvictionHandler { public: MockQuotaEvictionHandler(QuotaTemporaryStorageEvictorTest *test) : quota_(0), + unlimited_usage_(0), available_space_(0), test_(test) {} @@ -43,7 +44,8 @@ class MockQuotaEvictionHandler : public quota::QuotaEvictionHandler { GetUsageAndQuotaForEvictionCallback* callback) OVERRIDE { if (task_for_get_usage_and_quota_.get()) task_for_get_usage_and_quota_->Run(); - callback->Run(quota::kQuotaStatusOk, GetUsage(), quota_, available_space_); + callback->Run(quota::kQuotaStatusOk, GetUsage(), unlimited_usage_, + quota_, available_space_); delete callback; } @@ -69,6 +71,9 @@ class MockQuotaEvictionHandler : public quota::QuotaEvictionHandler { void set_quota(int64 quota) { quota_ = quota; } + void set_unlimited_usage(int64 usage) { + unlimited_usage_ = usage; + } void set_available_space(int64 available_space) { available_space_ = available_space; } @@ -107,6 +112,7 @@ class MockQuotaEvictionHandler : public quota::QuotaEvictionHandler { } int64 quota_; + int64 unlimited_usage_; int64 available_space_; std::list<GURL> origin_order_; std::map<GURL, int64> origins_; @@ -290,4 +296,18 @@ TEST_F(QuotaTemporaryStorageEvictorTest, DiskSpaceEvictionTest) { EXPECT_EQ(150 + 300, quota_eviction_handler()->GetUsage()); } +TEST_F(QuotaTemporaryStorageEvictorTest, UnlimitedExclusionEvictionTest) { + quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 3000); + quota_eviction_handler()->AddOrigin(GURL("http://www.y.com"), 200); + quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 500000); + quota_eviction_handler()->set_unlimited_usage(500000); + quota_eviction_handler()->set_quota(5000); + quota_eviction_handler()->set_available_space(1000000000); + EXPECT_EQ(3000 + 200 + 500000, quota_eviction_handler()->GetUsage()); + temporary_storage_evictor()->Start(); + MessageLoop::current()->RunAllPending(); + // Nothing should have been evicted. + EXPECT_EQ(3000 + 200 + 500000, quota_eviction_handler()->GetUsage()); +} + } // namespace quota diff --git a/webkit/quota/quota_types.h b/webkit/quota/quota_types.h index cadbcf6..449e717 100644 --- a/webkit/quota/quota_types.h +++ b/webkit/quota/quota_types.h @@ -36,6 +36,7 @@ enum QuotaStatusCode { // Common callback types that are used throughout in the quota module. typedef Callback2<StorageType, int64>::Type UsageCallback; +typedef Callback3<StorageType, int64, int64>::Type GlobalUsageCallback; typedef Callback3<QuotaStatusCode, StorageType, int64>::Type QuotaCallback; @@ -138,6 +139,8 @@ class CallbackQueue4 : public CallbackQueueBase<CallbackType4> { typedef CallbackQueue2<UsageCallback*, StorageType, int64> UsageCallbackQueue; +typedef CallbackQueue3<GlobalUsageCallback*, + StorageType, int64, int64> GlobalUsageCallbackQueue; typedef CallbackQueue3<QuotaCallback*, QuotaStatusCode, StorageType, int64> QuotaCallbackQueue; diff --git a/webkit/quota/usage_tracker.cc b/webkit/quota/usage_tracker.cc index 164a360..33d5e23 100644 --- a/webkit/quota/usage_tracker.cc +++ b/webkit/quota/usage_tracker.cc @@ -11,6 +11,7 @@ #include "base/message_loop_proxy.h" #include "base/stl_util-inl.h" #include "net/base/net_util.h" +#include "webkit/quota/special_storage_policy.h" namespace quota { @@ -81,8 +82,7 @@ class ClientUsageTracker::GatherUsageTaskBase : public QuotaTask { // order as we dispatched GetOriginUsage calls. DCHECK(original_message_loop()->BelongsToCurrentThread()); DCHECK(!pending_origins_.empty()); - origin_usage_map_.insert(std::make_pair( - pending_origins_.front(), usage)); + origin_usage_map_[pending_origins_.front()] = usage; pending_origins_.pop_front(); if (pending_origins_.empty()) { // We're done. @@ -173,14 +173,16 @@ class ClientUsageTracker::GatherHostUsageTask // UsageTracker ---------------------------------------------------------- -UsageTracker::UsageTracker(const QuotaClientList& clients, StorageType type) +UsageTracker::UsageTracker(const QuotaClientList& clients, StorageType type, + SpecialStoragePolicy* special_storage_policy) : type_(type), callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { for (QuotaClientList::const_iterator iter = clients.begin(); iter != clients.end(); ++iter) { client_tracker_map_.insert(std::make_pair( - (*iter)->id(), new ClientUsageTracker(this, *iter, type))); + (*iter)->id(), + new ClientUsageTracker(this, *iter, type, special_storage_policy))); } } @@ -195,10 +197,10 @@ ClientUsageTracker* UsageTracker::GetClientTracker(QuotaClient::ID client_id) { return NULL; } -void UsageTracker::GetGlobalUsage(UsageCallback* callback) { +void UsageTracker::GetGlobalUsage(GlobalUsageCallback* callback) { if (client_tracker_map_.size() == 0) { // No clients registered. - callback->Run(type_, 0); + callback->Run(type_, 0, 0); delete callback; return; } @@ -207,6 +209,7 @@ void UsageTracker::GetGlobalUsage(UsageCallback* callback) { // usage information. global_usage_.pending_clients = client_tracker_map_.size(); global_usage_.usage = 0; + global_usage_.unlimited_usage = 0; for (ClientTrackerMap::iterator iter = client_tracker_map_.begin(); iter != client_tracker_map_.end(); ++iter) { @@ -256,13 +259,26 @@ void UsageTracker::GetCachedOrigins(std::set<GURL>* origins) const { } void UsageTracker::DidGetClientGlobalUsage(StorageType type, - int64 usage) { + int64 usage, + int64 unlimited_usage) { DCHECK_EQ(type, type_); global_usage_.usage += usage; + global_usage_.unlimited_usage += unlimited_usage; if (--global_usage_.pending_clients == 0) { + // Defend against confusing inputs from clients. + if (global_usage_.usage < 0) + global_usage_.usage = 0; + // TODO(michaeln): The unlimited number is not trustworthy, it + // can get out of whack when apps are installed or uninstalled. + if (global_usage_.unlimited_usage > global_usage_.usage) + global_usage_.unlimited_usage = global_usage_.usage; + else if (global_usage_.unlimited_usage < 0) + global_usage_.unlimited_usage = 0; + // All the clients have returned their usage data. Dispatches the // pending callbacks. - global_usage_callbacks_.Run(type, global_usage_.usage); + global_usage_callbacks_.Run(type, global_usage_.usage, + global_usage_.unlimited_usage); } } @@ -273,6 +289,9 @@ void UsageTracker::DidGetClientHostUsage(const std::string& host, TrackingInfo& info = outstanding_host_usage_[host]; info.usage += usage; if (--info.pending_clients == 0) { + // Defend against confusing inputs from clients. + if (info.usage < 0) + info.usage = 0; // All the clients have returned their usage data. Dispatches the // pending callbacks. host_usage_callbacks_.Run(host, host, type, info.usage); @@ -283,13 +302,16 @@ void UsageTracker::DidGetClientHostUsage(const std::string& host, // ClientUsageTracker ---------------------------------------------------- ClientUsageTracker::ClientUsageTracker( - UsageTracker* tracker, QuotaClient* client, StorageType type) + UsageTracker* tracker, QuotaClient* client, StorageType type, + SpecialStoragePolicy* special_storage_policy) : tracker_(tracker), client_(client), type_(type), global_usage_(0), + global_unlimited_usage_(0), global_usage_retrieved_(false), - global_usage_task_(NULL) { + global_usage_task_(NULL), + special_storage_policy_(special_storage_policy) { DCHECK(tracker_); DCHECK(client_); } @@ -297,9 +319,9 @@ ClientUsageTracker::ClientUsageTracker( ClientUsageTracker::~ClientUsageTracker() { } -void ClientUsageTracker::GetGlobalUsage(UsageCallback* callback) { +void ClientUsageTracker::GetGlobalUsage(GlobalUsageCallback* callback) { if (global_usage_retrieved_) { - callback->Run(type_, global_usage_); + callback->Run(type_, global_usage_, global_unlimited_usage_); delete callback; return; } @@ -344,6 +366,8 @@ void ClientUsageTracker::UpdateUsageCache( if (cached_origins_.find(origin) != cached_origins_.end()) { host_usage_map_[host] += delta; global_usage_ += delta; + if (IsStorageUnlimited(origin)) + global_unlimited_usage_ += delta; DCHECK_GE(host_usage_map_[host], 0); DCHECK_GE(global_usage_, 0); return; @@ -354,6 +378,8 @@ void ClientUsageTracker::UpdateUsageCache( cached_origins_.insert(origin); host_usage_map_[host] += delta; global_usage_ += delta; + if (IsStorageUnlimited(origin)) + global_unlimited_usage_ += delta; DCHECK_GE(host_usage_map_[host], 0); DCHECK_GE(global_usage_, 0); return; @@ -363,6 +389,8 @@ void ClientUsageTracker::UpdateUsageCache( if (global_usage_task_ && global_usage_task_->IsOriginDone(origin)) { host_usage_map_[host] += delta; global_usage_ += delta; + if (IsStorageUnlimited(origin)) + global_unlimited_usage_ += delta; DCHECK_GE(host_usage_map_[host], 0); DCHECK_GE(global_usage_, 0); return; @@ -387,6 +415,8 @@ void ClientUsageTracker::DidGetGlobalUsage( ++iter) { if (cached_origins_.insert(iter->first).second) { global_usage_ += iter->second; + if (IsStorageUnlimited(iter->first)) + global_unlimited_usage_ += iter->second; std::string host = net::GetHostOrSpecFromURL(iter->first); host_usage_map_[host] += iter->second; DCHECK_GE(host_usage_map_[host], 0); @@ -396,7 +426,7 @@ void ClientUsageTracker::DidGetGlobalUsage( // Dispatches the global usage callback. DCHECK(global_usage_callback_.HasCallbacks()); - global_usage_callback_.Run(type_, global_usage_); + global_usage_callback_.Run(type_, global_usage_, global_unlimited_usage_); // Dispatches host usage callbacks. for (HostUsageCallbackMap::iterator iter = host_usage_callbacks_.Begin(); @@ -422,6 +452,8 @@ void ClientUsageTracker::DidGetHostUsage( ++iter) { if (cached_origins_.insert(iter->first).second) { global_usage_ += iter->second; + if (IsStorageUnlimited(iter->first)) + global_unlimited_usage_ += iter->second; host_usage_map_[host] += iter->second; DCHECK_GE(host_usage_map_[host], 0); DCHECK_GE(global_usage_, 0); @@ -432,4 +464,9 @@ void ClientUsageTracker::DidGetHostUsage( host_usage_callbacks_.Run(host, host, type_, host_usage_map_[host]); } +bool ClientUsageTracker::IsStorageUnlimited(const GURL& origin) const { + return special_storage_policy_.get() && + special_storage_policy_->IsStorageUnlimited(origin); +} + } // namespace quota diff --git a/webkit/quota/usage_tracker.h b/webkit/quota/usage_tracker.h index 084842c..cced53d 100644 --- a/webkit/quota/usage_tracker.h +++ b/webkit/quota/usage_tracker.h @@ -23,19 +23,21 @@ namespace quota { class ClientUsageTracker; +class SpecialStoragePolicy; // A helper class that gathers and tracks the amount of data stored in // all quota clients. // An instance of this class is created per storage type. class UsageTracker : public QuotaTaskObserver { public: - UsageTracker(const QuotaClientList& clients, StorageType type); + UsageTracker(const QuotaClientList& clients, StorageType type, + SpecialStoragePolicy* special_storage_policy); virtual ~UsageTracker(); StorageType type() const { return type_; } ClientUsageTracker* GetClientTracker(QuotaClient::ID client_id); - void GetGlobalUsage(UsageCallback* callback); + void GetGlobalUsage(GlobalUsageCallback* callback); void GetHostUsage(const std::string& host, HostUsageCallback* callback); void UpdateUsageCache(QuotaClient::ID client_id, const GURL& origin, @@ -45,15 +47,17 @@ class UsageTracker : public QuotaTaskObserver { private: struct TrackingInfo { - TrackingInfo() : pending_clients(0), usage(0) {} + TrackingInfo() : pending_clients(0), usage(0), unlimited_usage(0) {} int pending_clients; int64 usage; + int64 unlimited_usage; }; typedef std::map<QuotaClient::ID, ClientUsageTracker*> ClientTrackerMap; friend class ClientUsageTracker; - void DidGetClientGlobalUsage(StorageType type, int64 usage); + void DidGetClientGlobalUsage(StorageType type, int64 usage, + int64 unlimited_usage); void DidGetClientHostUsage(const std::string& host, StorageType type, int64 usage); @@ -63,7 +67,7 @@ class UsageTracker : public QuotaTaskObserver { TrackingInfo global_usage_; std::map<std::string, TrackingInfo> outstanding_host_usage_; - UsageCallbackQueue global_usage_callbacks_; + GlobalUsageCallbackQueue global_usage_callbacks_; HostUsageCallbackMap host_usage_callbacks_; base::ScopedCallbackFactory<UsageTracker> callback_factory_; @@ -74,12 +78,13 @@ class UsageTracker : public QuotaTaskObserver { // usage data. An instance of this class is created per client. class ClientUsageTracker { public: - ClientUsageTracker(UsageTracker* tracking_info, + ClientUsageTracker(UsageTracker* tracker, QuotaClient* client, - StorageType type); + StorageType type, + SpecialStoragePolicy* special_storage_policy); ~ClientUsageTracker(); - void GetGlobalUsage(UsageCallback* callback); + void GetGlobalUsage(GlobalUsageCallback* callback); void GetHostUsage(const std::string& host, HostUsageCallback* callback); void DetermineOriginsToGetUsage(const std::set<GURL>& origins, std::set<GURL>* origins_to_process); @@ -95,6 +100,7 @@ class ClientUsageTracker { void DidGetGlobalUsage(const std::map<GURL, int64>& origin_usage_map); void DidGetHostUsage(const std::string& host, const std::map<GURL, int64>& origin_usage_map); + bool IsStorageUnlimited(const GURL& origin) const; UsageTracker* tracker_; QuotaClient* client_; @@ -102,14 +108,17 @@ class ClientUsageTracker { std::set<GURL> cached_origins_; int64 global_usage_; + int64 global_unlimited_usage_; bool global_usage_retrieved_; GatherGlobalUsageTask* global_usage_task_; - UsageCallbackQueue global_usage_callback_; + GlobalUsageCallbackQueue global_usage_callback_; std::map<std::string, int64> host_usage_map_; std::map<std::string, GatherHostUsageTask*> host_usage_tasks_; HostUsageCallbackMap host_usage_callbacks_; + scoped_refptr<SpecialStoragePolicy> special_storage_policy_; + DISALLOW_COPY_AND_ASSIGN(ClientUsageTracker); }; |