// Copyright (c) 2012 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/quota_manager.h" #include #include #include #include "base/bind.h" #include "base/callback.h" #include "base/command_line.h" #include "base/file_path.h" #include "base/message_loop_proxy.h" #include "base/metrics/histogram.h" #include "base/string_number_conversions.h" #include "base/sys_info.h" #include "base/time.h" #include "net/base/net_util.h" #include "webkit/quota/quota_database.h" #include "webkit/quota/quota_temporary_storage_evictor.h" #include "webkit/quota/quota_types.h" #include "webkit/quota/usage_tracker.h" #define UMA_HISTOGRAM_MBYTES(name, sample) \ UMA_HISTOGRAM_CUSTOM_COUNTS( \ (name), static_cast((sample) / kMBytes), \ 1, 10 * 1024 * 1024 /* 10TB */, 100) namespace quota { namespace { const int64 kMBytes = 1024 * 1024; const int kMinutesInMilliSeconds = 60 * 1000; const int64 kIncognitoDefaultTemporaryQuota = 50 * kMBytes; const int64 kReportHistogramInterval = 60 * 60 * 1000; // 1 hour const double kTemporaryQuotaRatioToAvail = 0.5; // 50% void CountOriginType(const std::set& origins, SpecialStoragePolicy* policy, size_t* protected_origins, size_t* unlimited_origins) { DCHECK(protected_origins); DCHECK(unlimited_origins); *protected_origins = 0; *unlimited_origins = 0; if (!policy) return; for (std::set::const_iterator itr = origins.begin(); itr != origins.end(); ++itr) { if (policy->IsStorageProtected(*itr)) ++*protected_origins; if (policy->IsStorageUnlimited(*itr)) ++*unlimited_origins; } } } // anonymous namespace const int QuotaManager::kPerHostTemporaryPortion = 5; // 20% const char QuotaManager::kDatabaseName[] = "QuotaManager"; const int QuotaManager::kThresholdOfErrorsToBeBlacklisted = 3; const int QuotaManager::kEvictionIntervalInMilliSeconds = 30 * kMinutesInMilliSeconds; // Callback translators. void CallGetUsageAndQuotaCallback( const QuotaManager::GetUsageAndQuotaCallback& callback, bool unlimited, QuotaStatusCode status, const QuotaAndUsage& quota_and_usage) { int64 usage = unlimited ? quota_and_usage.unlimited_usage : quota_and_usage.usage; int64 quota = unlimited ? kint64max : quota_and_usage.quota; callback.Run(status, usage, quota); } void CallQuotaCallback( const QuotaCallback& callback, StorageType type, QuotaStatusCode status, const QuotaAndUsage& quota_and_usage) { callback.Run(status, type, quota_and_usage.quota); } // This class is for posting GetUsage/GetQuota tasks, gathering // results and dispatching GetAndQuota callbacks. // This class is self-destructed. class QuotaManager::UsageAndQuotaDispatcherTask : public QuotaTask { public: typedef UsageAndQuotaDispatcherCallback Callback; typedef std::deque CallbackList; static UsageAndQuotaDispatcherTask* Create( QuotaManager* manager, bool global, const HostAndType& host_and_type); // Returns true if it is the first call for this task; which means // the caller needs to call Start(). bool AddCallback(const Callback& callback) { callbacks_.push_back(callback); return (callbacks_.size() == 1); } void DidGetGlobalUsage(StorageType type, int64 usage, int64 unlimited_usage) { DCHECK_EQ(this->type(), type); DCHECK_GE(usage, unlimited_usage); if (quota_status_ == kQuotaStatusUnknown) quota_status_ = kQuotaStatusOk; global_usage_ = usage; global_unlimited_usage_ = unlimited_usage; CheckCompleted(); } void DidGetHostUsage(const std::string& host, StorageType type, int64 usage) { DCHECK_EQ(this->host(), host); DCHECK_EQ(this->type(), type); host_usage_ = usage; CheckCompleted(); } void DidGetHostQuota(QuotaStatusCode status, const std::string& host, StorageType type, int64 host_quota) { DCHECK_EQ(this->host(), host); DCHECK_EQ(this->type(), type); if (quota_status_ == kQuotaStatusUnknown || quota_status_ == kQuotaStatusOk) quota_status_ = status; host_quota_ = host_quota; CheckCompleted(); } void DidGetAvailableSpace(QuotaStatusCode status, int64 space) { DCHECK_GE(space, 0); if (quota_status_ == kQuotaStatusUnknown || quota_status_ == kQuotaStatusOk) quota_status_ = status; available_space_ = space; CheckCompleted(); } bool IsStartable() const { return !started_ && !callbacks_.empty(); } protected: UsageAndQuotaDispatcherTask( QuotaManager* manager, const HostAndType& host_and_type) : QuotaTask(manager), host_and_type_(host_and_type), started_(false), host_quota_(-1), global_usage_(-1), global_unlimited_usage_(-1), host_usage_(-1), available_space_(-1), quota_status_(kQuotaStatusUnknown), waiting_callbacks_(1), weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {} virtual ~UsageAndQuotaDispatcherTask() {} // Subclasses must implement them. virtual void RunBody() = 0; virtual void DispatchCallbacks() = 0; virtual void Run() OVERRIDE { DCHECK(!started_); started_ = true; RunBody(); // We initialize waiting_callbacks to 1 so that we won't run // the completion callback until here even some of the callbacks // are dispatched synchronously. CheckCompleted(); } virtual void Aborted() OVERRIDE { CallCallbacksAndClear(kQuotaErrorAbort, 0, 0, 0, 0); DeleteSoon(); } virtual void Completed() OVERRIDE { DeleteSoon(); } void CallCallbacksAndClear( QuotaStatusCode status, int64 usage, int64 unlimited_usage, int64 quota, int64 available_space) { QuotaAndUsage qau = { usage, unlimited_usage, quota, available_space }; for (CallbackList::iterator iter = callbacks_.begin(); iter != callbacks_.end(); ++iter) { (*iter).Run(status, qau); } callbacks_.clear(); } QuotaManager* manager() const { return static_cast(observer()); } std::string host() const { return host_and_type_.first; } virtual StorageType type() const { return host_and_type_.second; } int64 host_quota() const { return host_quota_; } int64 global_usage() const { return global_usage_; } int64 global_unlimited_usage() const { return global_unlimited_usage_; } int64 host_usage() const { return host_usage_; } int64 available_space() const { return available_space_; } QuotaStatusCode quota_status() const { return quota_status_; } CallbackList& callbacks() { return callbacks_; } // The main logic that determines the temporary global quota. int64 temporary_global_quota() const { DCHECK_EQ(type(), kStorageTypeTemporary); DCHECK(manager()); DCHECK_GE(global_usage(), global_unlimited_usage()); if (manager()->temporary_quota_override_ > 0) { // If the user has specified an explicit temporary quota, use the value. return manager()->temporary_quota_override_; } int64 limited_usage = global_usage() - global_unlimited_usage(); int64 avail_space = available_space(); if (avail_space < kint64max - limited_usage) { // We basically calculate the temporary quota by // [available_space + space_used_for_temp] * kTempQuotaRatio, // but make sure we'll have no overflow. avail_space += limited_usage; } return avail_space * kTemporaryQuotaRatioToAvail; } // Subclasses must call following methods to create a new 'waitable' // callback, which decrements waiting_callbacks when it is called. GlobalUsageCallback NewWaitableGlobalUsageCallback() { ++waiting_callbacks_; return base::Bind(&UsageAndQuotaDispatcherTask::DidGetGlobalUsage, weak_factory_.GetWeakPtr()); } HostUsageCallback NewWaitableHostUsageCallback() { ++waiting_callbacks_; return base::Bind(&UsageAndQuotaDispatcherTask::DidGetHostUsage, weak_factory_.GetWeakPtr()); } HostQuotaCallback NewWaitableHostQuotaCallback() { ++waiting_callbacks_; return base::Bind(&UsageAndQuotaDispatcherTask::DidGetHostQuota, weak_factory_.GetWeakPtr()); } AvailableSpaceCallback NewWaitableAvailableSpaceCallback() { ++waiting_callbacks_; return base::Bind(&UsageAndQuotaDispatcherTask::DidGetAvailableSpace, weak_factory_.GetWeakPtr()); } private: void CheckCompleted() { if (--waiting_callbacks_ <= 0) { DispatchCallbacks(); DCHECK(callbacks_.empty()); UsageAndQuotaDispatcherTaskMap& dispatcher_map = manager()->usage_and_quota_dispatchers_; DCHECK(dispatcher_map.find(host_and_type_) != dispatcher_map.end()); dispatcher_map.erase(host_and_type_); CallCompleted(); } } const std::string host_; const HostAndType host_and_type_; bool started_; int64 host_quota_; int64 global_usage_; int64 global_unlimited_usage_; int64 host_usage_; int64 available_space_; QuotaStatusCode quota_status_; CallbackList callbacks_; int waiting_callbacks_; base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(UsageAndQuotaDispatcherTask); }; class QuotaManager::GetUsageInfoTask : public QuotaTask { private: typedef QuotaManager::GetUsageInfoTask self_type; public: GetUsageInfoTask( QuotaManager* manager, const GetUsageInfoCallback& callback) : QuotaTask(manager), callback_(callback), weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { } protected: virtual void Run() OVERRIDE { remaining_trackers_ = 2; // This will populate cached hosts and usage info. manager()->GetUsageTracker(kStorageTypeTemporary)->GetGlobalUsage( base::Bind(&GetUsageInfoTask::DidGetGlobalUsage, weak_factory_.GetWeakPtr())); manager()->GetUsageTracker(kStorageTypePersistent)->GetGlobalUsage( base::Bind(&GetUsageInfoTask::DidGetGlobalUsage, weak_factory_.GetWeakPtr())); } virtual void Completed() OVERRIDE { callback_.Run(entries_); DeleteSoon(); } virtual void Aborted() OVERRIDE { callback_.Run(UsageInfoEntries()); DeleteSoon(); } private: void AddEntries(StorageType type, UsageTracker* tracker) { std::map host_usage; tracker->GetCachedHostsUsage(&host_usage); for (std::map::const_iterator iter = host_usage.begin(); iter != host_usage.end(); ++iter) { entries_.push_back(UsageInfo(iter->first, type, iter->second)); } if (--remaining_trackers_ == 0) CallCompleted(); } void DidGetGlobalUsage(StorageType type, int64, int64) { AddEntries(type, manager()->GetUsageTracker(type)); } QuotaManager* manager() const { return static_cast(observer()); } GetUsageInfoCallback callback_; UsageInfoEntries entries_; base::WeakPtrFactory weak_factory_; int remaining_trackers_; DISALLOW_COPY_AND_ASSIGN(GetUsageInfoTask); }; class QuotaManager::UsageAndQuotaDispatcherTaskForTemporary : public QuotaManager::UsageAndQuotaDispatcherTask { public: UsageAndQuotaDispatcherTaskForTemporary( QuotaManager* manager, const HostAndType& host_and_type) : UsageAndQuotaDispatcherTask(manager, host_and_type) {} protected: virtual void RunBody() OVERRIDE { manager()->temporary_usage_tracker_->GetGlobalUsage( NewWaitableGlobalUsageCallback()); manager()->temporary_usage_tracker_->GetHostUsage( host(), NewWaitableHostUsageCallback()); manager()->GetAvailableSpace(NewWaitableAvailableSpaceCallback()); } virtual void DispatchCallbacks() OVERRIDE { // Allow an individual host to utilize a fraction of the total // pool available for temp storage. int64 host_quota = temporary_global_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 > temporary_global_quota()) host_quota = std::min(host_quota, host_usage()); CallCallbacksAndClear(quota_status(), host_usage(), host_usage(), host_quota, available_space()); } }; class QuotaManager::UsageAndQuotaDispatcherTaskForPersistent : public QuotaManager::UsageAndQuotaDispatcherTask { public: UsageAndQuotaDispatcherTaskForPersistent( QuotaManager* manager, const HostAndType& host_and_type) : UsageAndQuotaDispatcherTask(manager, host_and_type) {} protected: virtual void RunBody() OVERRIDE { manager()->persistent_usage_tracker_->GetHostUsage( host(), NewWaitableHostUsageCallback()); manager()->GetPersistentHostQuota( host(), NewWaitableHostQuotaCallback()); } virtual void DispatchCallbacks() OVERRIDE { CallCallbacksAndClear(quota_status(), host_usage(), host_usage(), host_quota(), available_space()); } }; class QuotaManager::UsageAndQuotaDispatcherTaskForTemporaryGlobal : public QuotaManager::UsageAndQuotaDispatcherTask { public: UsageAndQuotaDispatcherTaskForTemporaryGlobal( QuotaManager* manager, const HostAndType& host_and_type) : UsageAndQuotaDispatcherTask(manager, host_and_type) {} protected: virtual void RunBody() OVERRIDE { manager()->temporary_usage_tracker_->GetGlobalUsage( NewWaitableGlobalUsageCallback()); manager()->GetAvailableSpace(NewWaitableAvailableSpaceCallback()); } virtual void DispatchCallbacks() OVERRIDE { CallCallbacksAndClear(quota_status(), global_usage(), global_unlimited_usage(), temporary_global_quota(), available_space()); } virtual StorageType type() const { return kStorageTypeTemporary; } }; // static QuotaManager::UsageAndQuotaDispatcherTask* QuotaManager::UsageAndQuotaDispatcherTask::Create( QuotaManager* manager, bool global, const QuotaManager::HostAndType& host_and_type) { if (global) return new UsageAndQuotaDispatcherTaskForTemporaryGlobal( manager, host_and_type); switch (host_and_type.second) { case kStorageTypeTemporary: return new UsageAndQuotaDispatcherTaskForTemporary( manager, host_and_type); case kStorageTypePersistent: return new UsageAndQuotaDispatcherTaskForPersistent( manager, host_and_type); default: NOTREACHED(); } return NULL; } class QuotaManager::OriginDataDeleter : public QuotaTask { public: OriginDataDeleter(QuotaManager* manager, const GURL& origin, StorageType type, int quota_client_mask, const StatusCallback& callback) : QuotaTask(manager), origin_(origin), type_(type), quota_client_mask_(quota_client_mask), error_count_(0), remaining_clients_(-1), skipped_clients_(0), callback_(callback), weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {} protected: virtual void Run() OVERRIDE { error_count_ = 0; remaining_clients_ = manager()->clients_.size(); for (QuotaClientList::iterator iter = manager()->clients_.begin(); iter != manager()->clients_.end(); ++iter) { if (quota_client_mask_ & (*iter)->id()) { (*iter)->DeleteOriginData( origin_, type_, base::Bind(&OriginDataDeleter::DidDeleteOriginData, weak_factory_.GetWeakPtr())); } else { ++skipped_clients_; if (--remaining_clients_ == 0) CallCompleted(); } } } virtual void Completed() OVERRIDE { if (error_count_ == 0) { // Only remove the entire origin if we didn't skip any client types. if (skipped_clients_ == 0) manager()->DeleteOriginFromDatabase(origin_, type_); callback_.Run(kQuotaStatusOk); } else { callback_.Run(kQuotaErrorInvalidModification); } DeleteSoon(); } virtual void Aborted() OVERRIDE { callback_.Run(kQuotaErrorAbort); DeleteSoon(); } void DidDeleteOriginData(QuotaStatusCode status) { DCHECK_GT(remaining_clients_, 0); if (status != kQuotaStatusOk) ++error_count_; if (--remaining_clients_ == 0) CallCompleted(); } QuotaManager* manager() const { return static_cast(observer()); } GURL origin_; StorageType type_; int quota_client_mask_; int error_count_; int remaining_clients_; int skipped_clients_; StatusCallback callback_; base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(OriginDataDeleter); }; class QuotaManager::HostDataDeleter : public QuotaTask { public: HostDataDeleter(QuotaManager* manager, const std::string& host, StorageType type, int quota_client_mask, const StatusCallback& callback) : QuotaTask(manager), host_(host), type_(type), quota_client_mask_(quota_client_mask), error_count_(0), remaining_clients_(-1), remaining_deleters_(-1), callback_(callback), weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {} protected: virtual void Run() OVERRIDE { error_count_ = 0; remaining_clients_ = manager()->clients_.size(); for (QuotaClientList::iterator iter = manager()->clients_.begin(); iter != manager()->clients_.end(); ++iter) { (*iter)->GetOriginsForHost( type_, host_, base::Bind(&HostDataDeleter::DidGetOriginsForHost, weak_factory_.GetWeakPtr())); } } virtual void Completed() OVERRIDE { if (error_count_ == 0) { callback_.Run(kQuotaStatusOk); } else { callback_.Run(kQuotaErrorInvalidModification); } DeleteSoon(); } virtual void Aborted() OVERRIDE { callback_.Run(kQuotaErrorAbort); DeleteSoon(); } void DidGetOriginsForHost(const std::set& origins, StorageType type) { DCHECK_GT(remaining_clients_, 0); origins_.insert(origins.begin(), origins.end()); if (--remaining_clients_ == 0) { if (!origins_.empty()) ScheduleOriginsDeletion(); else CallCompleted(); } } void ScheduleOriginsDeletion() { remaining_deleters_ = origins_.size(); for (std::set::const_iterator p = origins_.begin(); p != origins_.end(); ++p) { OriginDataDeleter* deleter = new OriginDataDeleter( manager(), *p, type_, quota_client_mask_, base::Bind(&HostDataDeleter::DidDeleteOriginData, weak_factory_.GetWeakPtr())); deleter->Start(); } } void DidDeleteOriginData(QuotaStatusCode status) { DCHECK_GT(remaining_deleters_, 0); if (status != kQuotaStatusOk) ++error_count_; if (--remaining_deleters_ == 0) CallCompleted(); } QuotaManager* manager() const { return static_cast(observer()); } std::string host_; StorageType type_; int quota_client_mask_; std::set origins_; int error_count_; int remaining_clients_; int remaining_deleters_; StatusCallback callback_; base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(HostDataDeleter); }; class QuotaManager::DatabaseTaskBase : public QuotaThreadTask { public: explicit DatabaseTaskBase(QuotaManager* manager) : QuotaThreadTask(manager, manager->db_thread_), manager_(manager), database_(manager->database_.get()), db_disabled_(false) { DCHECK(database_); } protected: virtual void DatabaseTaskCompleted() = 0; virtual void Completed() OVERRIDE { manager_->db_disabled_ = db_disabled_; DatabaseTaskCompleted(); } bool db_disabled() const { return db_disabled_; } void set_db_disabled(bool db_disabled) { db_disabled_ = db_disabled; } QuotaManager* manager() const { return manager_; } QuotaDatabase* database() const { return database_; } private: QuotaManager* manager_; QuotaDatabase* database_; bool db_disabled_; }; class QuotaManager::InitializeTask : public QuotaManager::DatabaseTaskBase { public: InitializeTask(QuotaManager* manager) : DatabaseTaskBase(manager), temporary_quota_override_(-1), desired_available_space_(-1) { } protected: virtual void RunOnTargetThread() OVERRIDE { // See if we have overriding temporary quota configuration. database()->GetQuotaConfigValue(QuotaDatabase::kTemporaryQuotaOverrideKey, &temporary_quota_override_); database()->GetQuotaConfigValue(QuotaDatabase::kDesiredAvailableSpaceKey, &desired_available_space_); } virtual void DatabaseTaskCompleted() OVERRIDE { manager()->temporary_quota_override_ = temporary_quota_override_; manager()->desired_available_space_ = desired_available_space_; manager()->temporary_quota_initialized_ = true; manager()->DidRunInitializeTask(); } private: int64 temporary_quota_override_; int64 desired_available_space_; }; class QuotaManager::UpdateTemporaryQuotaOverrideTask : public QuotaManager::DatabaseTaskBase { public: UpdateTemporaryQuotaOverrideTask( QuotaManager* manager, int64 new_quota, const QuotaCallback& callback) : DatabaseTaskBase(manager), new_quota_(new_quota), callback_(callback) {} protected: virtual void RunOnTargetThread() OVERRIDE { if (!database()->SetQuotaConfigValue( QuotaDatabase::kTemporaryQuotaOverrideKey, new_quota_)) { set_db_disabled(true); new_quota_ = -1; return; } } virtual void DatabaseTaskCompleted() OVERRIDE { if (!db_disabled()) { manager()->temporary_quota_override_ = new_quota_; CallCallback(kQuotaStatusOk, kStorageTypeTemporary, new_quota_); } else { CallCallback(kQuotaErrorInvalidAccess, kStorageTypeTemporary, new_quota_); } } private: void CallCallback(QuotaStatusCode status, StorageType type, int64 quota) { if (!callback_.is_null()) { callback_.Run(status, type, quota); callback_.Reset(); } } int64 new_quota_; QuotaCallback callback_; }; class QuotaManager::GetPersistentHostQuotaTask : public QuotaManager::DatabaseTaskBase { public: GetPersistentHostQuotaTask( QuotaManager* manager, const std::string& host, const HostQuotaCallback& callback) : DatabaseTaskBase(manager), host_(host), quota_(-1), callback_(callback) { } protected: virtual void RunOnTargetThread() OVERRIDE { if (!database()->GetHostQuota(host_, kStorageTypePersistent, "a_)) quota_ = 0; } virtual void DatabaseTaskCompleted() OVERRIDE { callback_.Run(kQuotaStatusOk, host_, kStorageTypePersistent, quota_); } private: std::string host_; int64 quota_; HostQuotaCallback callback_; }; class QuotaManager::UpdatePersistentHostQuotaTask : public QuotaManager::DatabaseTaskBase { public: UpdatePersistentHostQuotaTask( QuotaManager* manager, const std::string& host, int64 new_quota, const HostQuotaCallback& callback) : DatabaseTaskBase(manager), host_(host), new_quota_(new_quota), callback_(callback) { DCHECK_GE(new_quota_, 0); } protected: virtual void RunOnTargetThread() OVERRIDE { if (!database()->SetHostQuota(host_, kStorageTypePersistent, new_quota_)) { set_db_disabled(true); new_quota_ = 0; } } virtual void DatabaseTaskCompleted() OVERRIDE { callback_.Run(db_disabled() ? kQuotaErrorInvalidAccess : kQuotaStatusOk, host_, kStorageTypePersistent, new_quota_); } virtual void Aborted() OVERRIDE { callback_.Reset(); } private: std::string host_; int64 new_quota_; HostQuotaCallback callback_; }; class QuotaManager::GetLRUOriginTask : public QuotaManager::DatabaseTaskBase { public: GetLRUOriginTask( QuotaManager* manager, StorageType type, const std::map& origins_in_use, const std::map& origins_in_error, const GetLRUOriginCallback& callback) : DatabaseTaskBase(manager), type_(type), callback_(callback), special_storage_policy_(manager->special_storage_policy_) { for (std::map::const_iterator p = origins_in_use.begin(); p != origins_in_use.end(); ++p) { if (p->second > 0) exceptions_.insert(p->first); } for (std::map::const_iterator p = origins_in_error.begin(); p != origins_in_error.end(); ++p) { if (p->second > QuotaManager::kThresholdOfErrorsToBeBlacklisted) exceptions_.insert(p->first); } } protected: virtual void RunOnTargetThread() OVERRIDE { database()->GetLRUOrigin( type_, exceptions_, special_storage_policy_, &url_); } virtual void DatabaseTaskCompleted() OVERRIDE { callback_.Run(url_); } virtual void Aborted() OVERRIDE { callback_.Reset(); } private: StorageType type_; std::set exceptions_; GetLRUOriginCallback callback_; scoped_refptr special_storage_policy_; GURL url_; }; class QuotaManager::DeleteOriginInfo : public QuotaManager::DatabaseTaskBase { public: DeleteOriginInfo( QuotaManager* manager, const GURL& origin, StorageType type) : DatabaseTaskBase(manager), origin_(origin), type_(type) {} protected: virtual void RunOnTargetThread() OVERRIDE { if (!database()->DeleteOriginInfo(origin_, type_)) { set_db_disabled(true); } } virtual void DatabaseTaskCompleted() OVERRIDE {} private: GURL origin_; StorageType type_; }; class QuotaManager::InitializeTemporaryOriginsInfoTask : public QuotaManager::DatabaseTaskBase { public: InitializeTemporaryOriginsInfoTask( QuotaManager* manager, UsageTracker* temporary_usage_tracker) : DatabaseTaskBase(manager), has_registered_origins_(false) { DCHECK(temporary_usage_tracker); temporary_usage_tracker->GetCachedOrigins(&origins_); } protected: virtual void RunOnTargetThread() OVERRIDE { if (!database()->IsOriginDatabaseBootstrapped()) { // Register existing origins with 0 last time access. if (!database()->RegisterInitialOriginInfo( origins_, kStorageTypeTemporary)) { set_db_disabled(true); } else { has_registered_origins_ = true; database()->SetOriginDatabaseBootstrapped(true); } } } virtual void DatabaseTaskCompleted() OVERRIDE { if (has_registered_origins_) manager()->StartEviction(); } private: std::set origins_; bool has_registered_origins_; }; class QuotaManager::AvailableSpaceQueryTask : public QuotaThreadTask { public: AvailableSpaceQueryTask( QuotaManager* manager, const AvailableSpaceCallback& callback) : QuotaThreadTask(manager, manager->db_thread_), profile_path_(manager->profile_path_), space_(-1), callback_(callback) { } virtual ~AvailableSpaceQueryTask() {} protected: virtual void RunOnTargetThread() OVERRIDE { space_ = base::SysInfo::AmountOfFreeDiskSpace(profile_path_); } virtual void Aborted() OVERRIDE { callback_.Reset(); } virtual void Completed() OVERRIDE { callback_.Run(kQuotaStatusOk, space_); } private: FilePath profile_path_; int64 space_; AvailableSpaceCallback callback_; }; class QuotaManager::UpdateAccessTimeTask : public QuotaManager::DatabaseTaskBase { public: UpdateAccessTimeTask( QuotaManager* manager, const GURL& origin, StorageType type, base::Time accessed_time) : DatabaseTaskBase(manager), origin_(origin), type_(type), accessed_time_(accessed_time) {} protected: virtual void RunOnTargetThread() OVERRIDE { if (!database()->SetOriginLastAccessTime(origin_, type_, accessed_time_)) { set_db_disabled(true); } } virtual void DatabaseTaskCompleted() OVERRIDE {} private: GURL origin_; StorageType type_; base::Time accessed_time_; }; class QuotaManager::UpdateModifiedTimeTask : public QuotaManager::DatabaseTaskBase { public: UpdateModifiedTimeTask( QuotaManager* manager, const GURL& origin, StorageType type, base::Time modified_time) : DatabaseTaskBase(manager), origin_(origin), type_(type), modified_time_(modified_time) {} protected: virtual void RunOnTargetThread() OVERRIDE { if (!database()->SetOriginLastModifiedTime( origin_, type_, modified_time_)) { set_db_disabled(true); } } virtual void DatabaseTaskCompleted() OVERRIDE {} private: GURL origin_; StorageType type_; base::Time modified_time_; }; class QuotaManager::GetModifiedSinceTask : public QuotaManager::DatabaseTaskBase { public: GetModifiedSinceTask( QuotaManager* manager, StorageType type, base::Time modified_since, GetOriginsCallback callback) : DatabaseTaskBase(manager), type_(type), modified_since_(modified_since), callback_(callback) {} protected: virtual void RunOnTargetThread() OVERRIDE { if (!database()->GetOriginsModifiedSince( type_, &origins_, modified_since_)) { set_db_disabled(true); } } virtual void DatabaseTaskCompleted() OVERRIDE { callback_.Run(origins_, type_); } virtual void Aborted() OVERRIDE { callback_.Run(std::set(), type_); } private: StorageType type_; base::Time modified_since_; std::set origins_; GetOriginsCallback callback_; }; class QuotaManager::DumpQuotaTableTask : public QuotaManager::DatabaseTaskBase { private: typedef QuotaManager::DumpQuotaTableTask self_type; typedef QuotaManager::DumpQuotaTableCallback Callback; typedef QuotaManager::QuotaTableEntry TableEntry; typedef QuotaManager::QuotaTableEntries TableEntries; typedef QuotaDatabase::QuotaTableCallback TableCallback; public: DumpQuotaTableTask( QuotaManager* manager, const Callback& callback) : DatabaseTaskBase(manager), callback_(callback) { } protected: virtual void RunOnTargetThread() OVERRIDE { if (!database()->DumpQuotaTable( new TableCallback( base::Bind(&self_type::AppendEntry, this)))) set_db_disabled(true); } virtual void Aborted() OVERRIDE { callback_.Run(TableEntries()); } virtual void DatabaseTaskCompleted() OVERRIDE { callback_.Run(entries_); } private: bool AppendEntry(const TableEntry& entry) { entries_.push_back(entry); return true; } Callback callback_; TableEntries entries_; }; class QuotaManager::DumpOriginInfoTableTask : public QuotaManager::DatabaseTaskBase { private: typedef QuotaManager::DumpOriginInfoTableTask self_type; typedef QuotaManager::DumpOriginInfoTableCallback Callback; typedef QuotaManager::OriginInfoTableEntry TableEntry; typedef QuotaManager::OriginInfoTableEntries TableEntries; typedef QuotaDatabase::OriginInfoTableCallback TableCallback; public: DumpOriginInfoTableTask( QuotaManager* manager, const Callback& callback) : DatabaseTaskBase(manager), callback_(callback) { } protected: virtual void RunOnTargetThread() OVERRIDE { if (!database()->DumpOriginInfoTable( new TableCallback( base::Bind(&self_type::AppendEntry, this)))) set_db_disabled(true); } virtual void Aborted() OVERRIDE { callback_.Run(TableEntries()); } virtual void DatabaseTaskCompleted() OVERRIDE { callback_.Run(entries_); } private: bool AppendEntry(const TableEntry& entry) { entries_.push_back(entry); return true; } Callback callback_; TableEntries entries_; }; // QuotaManager --------------------------------------------------------------- QuotaManager::QuotaManager(bool is_incognito, const FilePath& profile_path, base::MessageLoopProxy* io_thread, base::MessageLoopProxy* db_thread, SpecialStoragePolicy* special_storage_policy) : is_incognito_(is_incognito), profile_path_(profile_path), proxy_(new QuotaManagerProxy( ALLOW_THIS_IN_INITIALIZER_LIST(this), io_thread)), db_disabled_(false), eviction_disabled_(false), io_thread_(io_thread), db_thread_(db_thread), temporary_quota_initialized_(false), temporary_quota_override_(-1), desired_available_space_(-1), special_storage_policy_(special_storage_policy), weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { } QuotaManager::~QuotaManager() { DCHECK(io_thread_->BelongsToCurrentThread()); proxy_->manager_ = NULL; std::for_each(clients_.begin(), clients_.end(), std::mem_fun(&QuotaClient::OnQuotaManagerDestroyed)); if (database_.get()) db_thread_->DeleteSoon(FROM_HERE, database_.release()); } void QuotaManager::GetUsageInfo(const GetUsageInfoCallback& callback) { LazyInitialize(); GetUsageInfoTask* get_usage_info = new GetUsageInfoTask(this, callback); get_usage_info->Start(); } void QuotaManager::GetUsageAndQuota( const GURL& origin, StorageType type, const GetUsageAndQuotaCallback& callback) { GetUsageAndQuotaInternal(origin, type, false /* global */, base::Bind(&CallGetUsageAndQuotaCallback, callback, IsStorageUnlimited(origin))); } void QuotaManager::GetAvailableSpace(const AvailableSpaceCallback& callback) { if (is_incognito_) { callback.Run(kQuotaStatusOk, kIncognitoDefaultTemporaryQuota); return; } make_scoped_refptr(new AvailableSpaceQueryTask(this, callback))->Start(); } void QuotaManager::GetTemporaryGlobalQuota(const QuotaCallback& callback) { if (temporary_quota_override_ > 0) { callback.Run(kQuotaStatusOk, kStorageTypeTemporary, temporary_quota_override_); return; } GetUsageAndQuotaInternal( GURL(), kStorageTypeTemporary, true /* global */, base::Bind(&CallQuotaCallback, callback, kStorageTypeTemporary)); } void QuotaManager::SetTemporaryGlobalOverrideQuota( int64 new_quota, const QuotaCallback& callback) { LazyInitialize(); if (new_quota < 0) { if (!callback.is_null()) callback.Run(kQuotaErrorInvalidModification, kStorageTypeTemporary, -1); return; } if (db_disabled_) { if (callback.is_null()) callback.Run(kQuotaErrorInvalidAccess, kStorageTypeTemporary, -1); return; } make_scoped_refptr(new UpdateTemporaryQuotaOverrideTask( this, new_quota, callback))->Start(); } void QuotaManager::GetPersistentHostQuota(const std::string& host, const HostQuotaCallback& callback) { LazyInitialize(); if (host.empty()) { // This could happen if we are called on file:///. // TODO(kinuko) We may want to respect --allow-file-access-from-files // command line switch. callback.Run(kQuotaStatusOk, host, kStorageTypePersistent, 0); return; } scoped_refptr task( new GetPersistentHostQuotaTask(this, host, callback)); task->Start(); } void QuotaManager::SetPersistentHostQuota(const std::string& host, int64 new_quota, const HostQuotaCallback& callback) { LazyInitialize(); if (host.empty()) { // This could happen if we are called on file:///. callback.Run(kQuotaErrorNotSupported, host, kStorageTypePersistent, 0); return; } if (new_quota < 0) { callback.Run(kQuotaErrorInvalidModification, host, kStorageTypePersistent, -1); return; } if (!db_disabled_) { scoped_refptr task( new UpdatePersistentHostQuotaTask( this, host, new_quota, callback)); task->Start(); } else { callback.Run(kQuotaErrorInvalidAccess, host, kStorageTypePersistent, -1); } } void QuotaManager::GetGlobalUsage(StorageType type, const GlobalUsageCallback& callback) { LazyInitialize(); GetUsageTracker(type)->GetGlobalUsage(callback); } void QuotaManager::GetHostUsage(const std::string& host, StorageType type, const HostUsageCallback& callback) { LazyInitialize(); GetUsageTracker(type)->GetHostUsage(host, callback); } void QuotaManager::GetStatistics( std::map* statistics) { DCHECK(statistics); if (temporary_storage_evictor_.get()) { std::map stats; temporary_storage_evictor_->GetStatistics(&stats); for (std::map::iterator p = stats.begin(); p != stats.end(); ++p) (*statistics)[p->first] = base::Int64ToString(p->second); } } void QuotaManager::GetOriginsModifiedSince(StorageType type, base::Time modified_since, const GetOriginsCallback& callback) { LazyInitialize(); make_scoped_refptr(new GetModifiedSinceTask( this, type, modified_since, callback))->Start(); } void QuotaManager::LazyInitialize() { DCHECK(io_thread_->BelongsToCurrentThread()); if (database_.get()) { // Initialization seems to be done already. return; } // Use an empty path to open an in-memory only databse for incognito. database_.reset(new QuotaDatabase(is_incognito_ ? FilePath() : profile_path_.AppendASCII(kDatabaseName))); temporary_usage_tracker_.reset( new UsageTracker(clients_, kStorageTypeTemporary, special_storage_policy_)); persistent_usage_tracker_.reset( new UsageTracker(clients_, kStorageTypePersistent, special_storage_policy_)); make_scoped_refptr(new InitializeTask(this))->Start(); } void QuotaManager::RegisterClient(QuotaClient* client) { DCHECK(io_thread_->BelongsToCurrentThread()); DCHECK(!database_.get()); clients_.push_back(client); } void QuotaManager::NotifyStorageAccessed( QuotaClient::ID client_id, const GURL& origin, StorageType type) { NotifyStorageAccessedInternal(client_id, origin, type, base::Time::Now()); } void QuotaManager::NotifyStorageModified( QuotaClient::ID client_id, const GURL& origin, StorageType type, int64 delta) { NotifyStorageModifiedInternal(client_id, origin, type, delta, base::Time::Now()); } void QuotaManager::NotifyOriginInUse(const GURL& origin) { DCHECK(io_thread_->BelongsToCurrentThread()); origins_in_use_[origin]++; } void QuotaManager::NotifyOriginNoLongerInUse(const GURL& origin) { DCHECK(io_thread_->BelongsToCurrentThread()); DCHECK(IsOriginInUse(origin)); int& count = origins_in_use_[origin]; if (--count == 0) origins_in_use_.erase(origin); } void QuotaManager::DeleteOriginData( const GURL& origin, StorageType type, int quota_client_mask, const StatusCallback& callback) { LazyInitialize(); if (origin.is_empty() || clients_.empty()) { callback.Run(kQuotaStatusOk); return; } OriginDataDeleter* deleter = new OriginDataDeleter(this, origin, type, quota_client_mask, callback); deleter->Start(); } void QuotaManager::DeleteHostData(const std::string& host, StorageType type, int quota_client_mask, const StatusCallback& callback) { LazyInitialize(); if (host.empty() || clients_.empty()) { callback.Run(kQuotaStatusOk); return; } HostDataDeleter* deleter = new HostDataDeleter(this, host, type, quota_client_mask, callback); deleter->Start(); } bool QuotaManager::ResetUsageTracker(StorageType type) { switch (type) { case kStorageTypeTemporary: if (temporary_usage_tracker_->IsWorking()) return false; temporary_usage_tracker_.reset( new UsageTracker(clients_, kStorageTypeTemporary, special_storage_policy_)); return true; case kStorageTypePersistent: if (persistent_usage_tracker_->IsWorking()) return false; persistent_usage_tracker_.reset( new UsageTracker(clients_, kStorageTypePersistent, special_storage_policy_)); return true; default: NOTREACHED(); } return true; } UsageTracker* QuotaManager::GetUsageTracker(StorageType type) const { switch (type) { case kStorageTypeTemporary: return temporary_usage_tracker_.get(); case kStorageTypePersistent: return persistent_usage_tracker_.get(); default: NOTREACHED(); } return NULL; } void QuotaManager::GetCachedOrigins( StorageType type, std::set* origins) { DCHECK(origins); LazyInitialize(); switch (type) { case kStorageTypeTemporary: DCHECK(temporary_usage_tracker_.get()); temporary_usage_tracker_->GetCachedOrigins(origins); return; case kStorageTypePersistent: DCHECK(persistent_usage_tracker_.get()); persistent_usage_tracker_->GetCachedOrigins(origins); return; default: NOTREACHED(); } } void QuotaManager::NotifyStorageAccessedInternal( QuotaClient::ID client_id, const GURL& origin, StorageType type, base::Time accessed_time) { LazyInitialize(); if (type == kStorageTypeTemporary && !lru_origin_callback_.is_null()) { // Record the accessed origins while GetLRUOrigin task is runing // to filter out them from eviction. access_notified_origins_.insert(origin); } if (db_disabled_) return; make_scoped_refptr(new UpdateAccessTimeTask( this, origin, type, accessed_time))->Start(); } void QuotaManager::NotifyStorageModifiedInternal( QuotaClient::ID client_id, const GURL& origin, StorageType type, int64 delta, base::Time modified_time) { LazyInitialize(); GetUsageTracker(type)->UpdateUsageCache(client_id, origin, delta); make_scoped_refptr(new UpdateModifiedTimeTask( this, origin, type, modified_time))->Start(); } void QuotaManager::GetUsageAndQuotaInternal( const GURL& origin, StorageType type, bool global, const UsageAndQuotaDispatcherCallback& callback) { LazyInitialize(); StorageType requested_type = type; if (type == kStorageTypeUnknown) { // Quota only supports temporary/persistent types. callback.Run(kQuotaErrorNotSupported, QuotaAndUsage()); return; } // Special internal type for querying global usage and quota. const int kStorageTypeTemporaryGlobal = kStorageTypeTemporary + 100; if (global) { DCHECK_EQ(kStorageTypeTemporary, type); type = static_cast(kStorageTypeTemporaryGlobal); } std::string host = net::GetHostOrSpecFromURL(origin); HostAndType host_and_type = std::make_pair(host, type); UsageAndQuotaDispatcherTaskMap::iterator found = usage_and_quota_dispatchers_.find(host_and_type); if (found == usage_and_quota_dispatchers_.end()) { UsageAndQuotaDispatcherTask* dispatcher = UsageAndQuotaDispatcherTask::Create(this, global, host_and_type); found = usage_and_quota_dispatchers_.insert( std::make_pair(host_and_type, dispatcher)).first; } // Start the dispatcher if it is the first one and temporary_quota_override // is already initialized iff the requested type is temporary. // (The first dispatcher task for temporary will be kicked in // DidRunInitializeTask if temporary_quota_initialized_ is false here.) if (found->second->AddCallback(callback) && (requested_type != kStorageTypeTemporary || temporary_quota_initialized_)) { found->second->Start(); } } void QuotaManager::DumpQuotaTable(const DumpQuotaTableCallback& callback) { make_scoped_refptr(new DumpQuotaTableTask(this, callback))->Start(); } void QuotaManager::DumpOriginInfoTable( const DumpOriginInfoTableCallback& callback) { make_scoped_refptr(new DumpOriginInfoTableTask(this, callback))->Start(); } void QuotaManager::DeleteOriginFromDatabase( const GURL& origin, StorageType type) { LazyInitialize(); if (db_disabled_) return; scoped_refptr task = new DeleteOriginInfo(this, origin, type); task->Start(); } void QuotaManager::GetLRUOrigin( StorageType type, const GetLRUOriginCallback& callback) { LazyInitialize(); // This must not be called while there's an in-flight task. DCHECK(lru_origin_callback_.is_null()); lru_origin_callback_ = callback; if (db_disabled_) { lru_origin_callback_.Run(GURL()); lru_origin_callback_.Reset(); return; } scoped_refptr task(new GetLRUOriginTask( this, type, origins_in_use_, origins_in_error_, base::Bind(&QuotaManager::DidGetDatabaseLRUOrigin, weak_factory_.GetWeakPtr()))); task->Start(); } void QuotaManager::DidOriginDataEvicted(QuotaStatusCode status) { DCHECK(io_thread_->BelongsToCurrentThread()); // We only try evict origins that are not in use, so basically // deletion attempt for eviction should not fail. Let's record // the origin if we get error and exclude it from future eviction // if the error happens consistently (> kThresholdOfErrorsToBeBlacklisted). if (status != kQuotaStatusOk) origins_in_error_[eviction_context_.evicted_origin]++; eviction_context_.evict_origin_data_callback.Run(status); eviction_context_.evict_origin_data_callback.Reset(); } void QuotaManager::EvictOriginData( const GURL& origin, StorageType type, const EvictOriginDataCallback& callback) { DCHECK(io_thread_->BelongsToCurrentThread()); DCHECK_EQ(type, kStorageTypeTemporary); eviction_context_.evicted_origin = origin; eviction_context_.evicted_type = type; eviction_context_.evict_origin_data_callback = callback; DeleteOriginData(origin, type, QuotaClient::kAllClientsMask, base::Bind(&QuotaManager::DidOriginDataEvicted, weak_factory_.GetWeakPtr())); } void QuotaManager::GetUsageAndQuotaForEviction( const GetUsageAndQuotaForEvictionCallback& callback) { DCHECK(io_thread_->BelongsToCurrentThread()); GetUsageAndQuotaInternal( GURL(), kStorageTypeTemporary, true /* global */, callback); } void QuotaManager::StartEviction() { DCHECK(!temporary_storage_evictor_.get()); temporary_storage_evictor_.reset(new QuotaTemporaryStorageEvictor( this, kEvictionIntervalInMilliSeconds)); if (desired_available_space_ >= 0) temporary_storage_evictor_->set_min_available_disk_space_to_start_eviction( desired_available_space_); temporary_storage_evictor_->Start(); } void QuotaManager::ReportHistogram() { GetGlobalUsage(kStorageTypeTemporary, base::Bind( &QuotaManager::DidGetTemporaryGlobalUsageForHistogram, weak_factory_.GetWeakPtr())); GetGlobalUsage(kStorageTypePersistent, base::Bind( &QuotaManager::DidGetPersistentGlobalUsageForHistogram, weak_factory_.GetWeakPtr())); } void QuotaManager::DidGetTemporaryGlobalUsageForHistogram( StorageType type, int64 usage, int64 unlimited_usage) { UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfTemporaryStorage", usage); std::set origins; GetCachedOrigins(type, &origins); size_t num_origins = origins.size(); size_t protected_origins = 0; size_t unlimited_origins = 0; CountOriginType(origins, special_storage_policy_, &protected_origins, &unlimited_origins); UMA_HISTOGRAM_COUNTS("Quota.NumberOfTemporaryStorageOrigins", num_origins); UMA_HISTOGRAM_COUNTS("Quota.NumberOfProtectedTemporaryStorageOrigins", protected_origins); UMA_HISTOGRAM_COUNTS("Quota.NumberOfUnlimitedTemporaryStorageOrigins", unlimited_origins); } void QuotaManager::DidGetPersistentGlobalUsageForHistogram( StorageType type, int64 usage, int64 unlimited_usage) { UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfPersistentStorage", usage); std::set origins; GetCachedOrigins(type, &origins); size_t num_origins = origins.size(); size_t protected_origins = 0; size_t unlimited_origins = 0; CountOriginType(origins, special_storage_policy_, &protected_origins, &unlimited_origins); UMA_HISTOGRAM_COUNTS("Quota.NumberOfPersistentStorageOrigins", num_origins); UMA_HISTOGRAM_COUNTS("Quota.NumberOfProtectedPersistentStorageOrigins", protected_origins); UMA_HISTOGRAM_COUNTS("Quota.NumberOfUnlimitedPersistentStorageOrigins", unlimited_origins); } void QuotaManager::DidRunInitializeTask() { histogram_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds( kReportHistogramInterval), this, &QuotaManager::ReportHistogram); DCHECK(temporary_quota_initialized_); // Kick the tasks that have been waiting for the // temporary_quota_initialized_ to be initialized (if there're any). for (UsageAndQuotaDispatcherTaskMap::iterator iter = usage_and_quota_dispatchers_.begin(); iter != usage_and_quota_dispatchers_.end(); ++iter) { if (iter->second->IsStartable()) iter->second->Start(); } // Kick the first GetTemporaryGlobalQuota. This internally fetches (and // caches) the usage of all origins and checks the available disk space. GetTemporaryGlobalQuota( base::Bind(&QuotaManager::DidGetInitialTemporaryGlobalQuota, weak_factory_.GetWeakPtr())); } void QuotaManager::DidGetInitialTemporaryGlobalQuota( QuotaStatusCode status, StorageType type, int64 quota_unused) { DCHECK_EQ(type, kStorageTypeTemporary); if (eviction_disabled_) return; // This will call the StartEviction() when initial origin registration // is completed. make_scoped_refptr(new InitializeTemporaryOriginsInfoTask( this, temporary_usage_tracker_.get()))->Start(); } void QuotaManager::DidGetDatabaseLRUOrigin(const GURL& origin) { // Make sure the returned origin is (still) not in the origin_in_use_ set // and has not been accessed since we posted the task. if (origins_in_use_.find(origin) != origins_in_use_.end() || access_notified_origins_.find(origin) != access_notified_origins_.end()) lru_origin_callback_.Run(GURL()); else lru_origin_callback_.Run(origin); access_notified_origins_.clear(); lru_origin_callback_.Reset(); } void QuotaManager::DeleteOnCorrectThread() const { if (!io_thread_->BelongsToCurrentThread()) { io_thread_->DeleteSoon(FROM_HERE, this); return; } delete this; } // QuotaManagerProxy ---------------------------------------------------------- void QuotaManagerProxy::RegisterClient(QuotaClient* client) { if (!io_thread_->BelongsToCurrentThread()) { io_thread_->PostTask( FROM_HERE, base::Bind(&QuotaManagerProxy::RegisterClient, this, client)); return; } if (manager_) manager_->RegisterClient(client); else client->OnQuotaManagerDestroyed(); } void QuotaManagerProxy::NotifyStorageAccessed( QuotaClient::ID client_id, const GURL& origin, StorageType type) { if (!io_thread_->BelongsToCurrentThread()) { io_thread_->PostTask( FROM_HERE, base::Bind(&QuotaManagerProxy::NotifyStorageAccessed, this, client_id, origin, type)); return; } if (manager_) manager_->NotifyStorageAccessed(client_id, origin, type); } void QuotaManagerProxy::NotifyStorageModified( QuotaClient::ID client_id, const GURL& origin, StorageType type, int64 delta) { if (!io_thread_->BelongsToCurrentThread()) { io_thread_->PostTask( FROM_HERE, base::Bind(&QuotaManagerProxy::NotifyStorageModified, this, client_id, origin, type, delta)); return; } if (manager_) manager_->NotifyStorageModified(client_id, origin, type, delta); } void QuotaManagerProxy::NotifyOriginInUse( const GURL& origin) { if (!io_thread_->BelongsToCurrentThread()) { io_thread_->PostTask( FROM_HERE, base::Bind(&QuotaManagerProxy::NotifyOriginInUse, this, origin)); return; } if (manager_) manager_->NotifyOriginInUse(origin); } void QuotaManagerProxy::NotifyOriginNoLongerInUse( const GURL& origin) { if (!io_thread_->BelongsToCurrentThread()) { io_thread_->PostTask( FROM_HERE, base::Bind(&QuotaManagerProxy::NotifyOriginNoLongerInUse, this, origin)); return; } if (manager_) manager_->NotifyOriginNoLongerInUse(origin); } QuotaManager* QuotaManagerProxy::quota_manager() const { DCHECK(!io_thread_ || io_thread_->BelongsToCurrentThread()); return manager_; } QuotaManagerProxy::QuotaManagerProxy( QuotaManager* manager, base::MessageLoopProxy* io_thread) : manager_(manager), io_thread_(io_thread) { } QuotaManagerProxy::~QuotaManagerProxy() { } } // namespace quota