| // Copyright 2013 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 "storage/browser/quota/quota_manager_impl.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <functional> |
| #include <limits> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/barrier_closure.h" |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/files/file_util.h" |
| #include "base/macros.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/rand_util.h" |
| #include "base/sequence_checker.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/system/sys_info.h" |
| #include "base/task/post_task.h" |
| #include "base/task/thread_pool.h" |
| #include "base/task_runner_util.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/types/pass_key.h" |
| #include "components/services/storage/public/mojom/quota_client.mojom.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "storage/browser/quota/client_usage_tracker.h" |
| #include "storage/browser/quota/mojo_quota_client_wrapper.h" |
| #include "storage/browser/quota/quota_client_type.h" |
| #include "storage/browser/quota/quota_features.h" |
| #include "storage/browser/quota/quota_macros.h" |
| #include "storage/browser/quota/quota_manager_proxy.h" |
| #include "storage/browser/quota/quota_override_handle.h" |
| #include "storage/browser/quota/quota_temporary_storage_evictor.h" |
| #include "storage/browser/quota/usage_tracker.h" |
| #include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h" |
| |
| using blink::mojom::StorageType; |
| |
| namespace storage { |
| |
| namespace { |
| |
| constexpr int64_t kReportHistogramInterval = 60 * 60 * 1000; // 1 hour |
| |
| // Take action on write errors if there is <= 2% disk space |
| // available. |
| constexpr double kStoragePressureThresholdRatio = 0.02; |
| |
| // Limit how frequently QuotaManagerImpl polls for free disk space when |
| // only using that information to identify storage pressure. |
| constexpr base::TimeDelta kStoragePressureCheckDiskStatsInterval = |
| base::TimeDelta::FromMinutes(5); |
| |
| // Modifies a given value by a uniformly random amount from |
| // -percent to +percent. |
| int64_t RandomizeByPercent(int64_t value, int percent) { |
| double random_percent = (base::RandDouble() - 0.5) * percent * 2; |
| return value * (1 + (random_percent / 100.0)); |
| } |
| } // namespace |
| |
| // Heuristics: assuming average cloud server allows a few Gigs storage |
| // on the server side and the storage needs to be shared for user data |
| // and by multiple apps. |
| int64_t QuotaManagerImpl::kSyncableStorageDefaultHostQuota = 500 * kMBytes; |
| |
| namespace { |
| |
| bool IsSupportedType(StorageType type) { |
| return type == StorageType::kTemporary || type == StorageType::kPersistent || |
| type == StorageType::kSyncable; |
| } |
| |
| bool IsSupportedIncognitoType(StorageType type) { |
| return type == StorageType::kTemporary || type == StorageType::kPersistent; |
| } |
| |
| bool GetPersistentHostQuotaOnDBThread(const std::string& host, |
| int64_t* quota, |
| QuotaDatabase* database) { |
| DCHECK(database); |
| database->GetHostQuota(host, StorageType::kPersistent, quota); |
| return true; |
| } |
| |
| bool SetPersistentHostQuotaOnDBThread(const std::string& host, |
| int64_t* new_quota, |
| QuotaDatabase* database) { |
| DCHECK(database); |
| if (database->SetHostQuota(host, StorageType::kPersistent, *new_quota)) |
| return true; |
| *new_quota = 0; |
| return false; |
| } |
| |
| bool GetLRUOriginOnDBThread(StorageType type, |
| const std::set<url::Origin>& exceptions, |
| SpecialStoragePolicy* policy, |
| base::Optional<url::Origin>* origin, |
| QuotaDatabase* database) { |
| DCHECK(database); |
| database->GetLRUOrigin(type, exceptions, policy, origin); |
| return true; |
| } |
| |
| bool DeleteOriginInfoOnDBThread(const url::Origin& origin, |
| StorageType type, |
| bool is_eviction, |
| QuotaDatabase* database) { |
| DCHECK(database); |
| |
| base::Time now = base::Time::Now(); |
| |
| if (is_eviction) { |
| QuotaDatabase::OriginInfoTableEntry entry; |
| database->GetOriginInfo(origin, type, &entry); |
| UMA_HISTOGRAM_COUNTS_1M( |
| QuotaManagerImpl::kEvictedOriginAccessedCountHistogram, |
| entry.used_count); |
| UMA_HISTOGRAM_COUNTS_1000( |
| QuotaManagerImpl::kEvictedOriginDaysSinceAccessHistogram, |
| (now - entry.last_access_time).InDays()); |
| } |
| |
| if (!database->DeleteOriginInfo(origin, type)) |
| return false; |
| |
| // If the deletion is not due to an eviction, delete the entry in the eviction |
| // table as well due to privacy concerns. |
| if (!is_eviction) |
| return database->DeleteOriginLastEvictionTime(origin, type); |
| |
| base::Time last_eviction_time; |
| database->GetOriginLastEvictionTime(origin, type, &last_eviction_time); |
| |
| if (last_eviction_time != base::Time()) { |
| UMA_HISTOGRAM_COUNTS_1000( |
| QuotaManagerImpl::kDaysBetweenRepeatedOriginEvictionsHistogram, |
| (now - last_eviction_time).InDays()); |
| } |
| |
| return database->SetOriginLastEvictionTime(origin, type, now); |
| } |
| |
| bool BootstrapDatabaseOnDBThread(std::set<url::Origin> origins, |
| QuotaDatabase* database) { |
| DCHECK(database); |
| if (database->IsOriginDatabaseBootstrapped()) |
| return true; |
| |
| // Register existing origins with 0 last time access. |
| if (database->RegisterInitialOriginInfo(origins, StorageType::kTemporary)) { |
| database->SetOriginDatabaseBootstrapped(true); |
| return true; |
| } |
| return false; |
| } |
| |
| bool UpdateAccessTimeOnDBThread(const url::Origin& origin, |
| StorageType type, |
| base::Time accessed_time, |
| QuotaDatabase* database) { |
| DCHECK(database); |
| return database->SetOriginLastAccessTime(origin, type, accessed_time); |
| } |
| |
| bool UpdateModifiedTimeOnDBThread(const url::Origin& origin, |
| StorageType type, |
| base::Time modified_time, |
| QuotaDatabase* database) { |
| DCHECK(database); |
| return database->SetOriginLastModifiedTime(origin, type, modified_time); |
| } |
| |
| void DidGetUsageAndQuotaStripBreakdown( |
| QuotaManagerImpl::UsageAndQuotaCallback callback, |
| blink::mojom::QuotaStatusCode status, |
| int64_t usage, |
| int64_t quota, |
| blink::mojom::UsageBreakdownPtr usage_breakdown) { |
| std::move(callback).Run(status, usage, quota); |
| } |
| |
| void DidGetUsageAndQuotaStripOverride( |
| QuotaManagerImpl::UsageAndQuotaWithBreakdownCallback callback, |
| blink::mojom::QuotaStatusCode status, |
| int64_t usage, |
| int64_t quota, |
| bool is_override_enabled, |
| blink::mojom::UsageBreakdownPtr usage_breakdown) { |
| std::move(callback).Run(status, usage, quota, std::move(usage_breakdown)); |
| } |
| |
| } // namespace |
| |
| constexpr int64_t QuotaManagerImpl::kGBytes; |
| constexpr int64_t QuotaManagerImpl::kNoLimit; |
| constexpr int64_t QuotaManagerImpl::kPerHostPersistentQuotaLimit; |
| constexpr int QuotaManagerImpl::kEvictionIntervalInMilliSeconds; |
| constexpr int QuotaManagerImpl::kThresholdOfErrorsToBeDenylisted; |
| constexpr int QuotaManagerImpl::kThresholdRandomizationPercent; |
| constexpr char QuotaManagerImpl::kDatabaseName[]; |
| constexpr char QuotaManagerImpl::kDaysBetweenRepeatedOriginEvictionsHistogram[]; |
| constexpr char QuotaManagerImpl::kEvictedOriginAccessedCountHistogram[]; |
| constexpr char QuotaManagerImpl::kEvictedOriginDaysSinceAccessHistogram[]; |
| |
| QuotaManagerImpl::QuotaOverride::QuotaOverride() = default; |
| QuotaManagerImpl::QuotaOverride::~QuotaOverride() = default; |
| |
| class QuotaManagerImpl::UsageAndQuotaInfoGatherer : public QuotaTask { |
| public: |
| UsageAndQuotaInfoGatherer(QuotaManagerImpl* manager, |
| const url::Origin& origin, |
| StorageType type, |
| bool is_unlimited, |
| bool is_session_only, |
| bool is_incognito, |
| base::Optional<int64_t> quota_override_size, |
| UsageAndQuotaForDevtoolsCallback callback) |
| : QuotaTask(manager), |
| origin_(origin), |
| callback_(std::move(callback)), |
| type_(type), |
| is_unlimited_(is_unlimited), |
| is_session_only_(is_session_only), |
| is_incognito_(is_incognito), |
| is_override_enabled_(quota_override_size.has_value()), |
| quota_override_size_(quota_override_size) {} |
| |
| protected: |
| void Run() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Start the async process of gathering the info we need. |
| // Gather 4 pieces of info before computing an answer: |
| // settings, device_storage_capacity, host_usage, and host_quota. |
| base::RepeatingClosure barrier = base::BarrierClosure( |
| 4, base::BindOnce(&UsageAndQuotaInfoGatherer::OnBarrierComplete, |
| weak_factory_.GetWeakPtr())); |
| |
| const std::string& host = origin_.host(); |
| |
| manager()->GetQuotaSettings( |
| base::BindOnce(&UsageAndQuotaInfoGatherer::OnGotSettings, |
| weak_factory_.GetWeakPtr(), barrier)); |
| manager()->GetStorageCapacity( |
| base::BindOnce(&UsageAndQuotaInfoGatherer::OnGotCapacity, |
| weak_factory_.GetWeakPtr(), barrier)); |
| manager()->GetHostUsageWithBreakdown( |
| host, type_, |
| base::BindOnce(&UsageAndQuotaInfoGatherer::OnGotHostUsage, |
| weak_factory_.GetWeakPtr(), barrier)); |
| |
| // Determine host_quota differently depending on type. |
| if (is_unlimited_) { |
| SetDesiredHostQuota(barrier, blink::mojom::QuotaStatusCode::kOk, |
| kNoLimit); |
| } else if (type_ == StorageType::kSyncable) { |
| SetDesiredHostQuota(barrier, blink::mojom::QuotaStatusCode::kOk, |
| kSyncableStorageDefaultHostQuota); |
| } else if (type_ == StorageType::kPersistent) { |
| manager()->GetPersistentHostQuota( |
| host, base::BindOnce(&UsageAndQuotaInfoGatherer::SetDesiredHostQuota, |
| weak_factory_.GetWeakPtr(), barrier)); |
| } else { |
| DCHECK_EQ(StorageType::kTemporary, type_); |
| // For temporary storage, OnGotSettings will set the host quota. |
| } |
| } |
| |
| void Aborted() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| weak_factory_.InvalidateWeakPtrs(); |
| std::move(callback_).Run(blink::mojom::QuotaStatusCode::kErrorAbort, |
| /*usage=*/0, |
| /*quota=*/0, |
| /*is_override_enabled=*/false, |
| /*usage_breakdown=*/nullptr); |
| DeleteSoon(); |
| } |
| |
| void Completed() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| weak_factory_.InvalidateWeakPtrs(); |
| |
| int64_t host_quota = quota_override_size_.has_value() |
| ? quota_override_size_.value() |
| : desired_host_quota_; |
| int64_t temp_pool_free_space = |
| std::max(static_cast<int64_t>(0), |
| available_space_ - settings_.must_remain_available); |
| |
| // Constrain the desired |host_quota| to something that fits. |
| if (host_quota > temp_pool_free_space) { |
| if (is_unlimited_) { |
| host_quota = available_space_ + host_usage_; |
| } |
| } |
| |
| std::move(callback_).Run(blink::mojom::QuotaStatusCode::kOk, host_usage_, |
| host_quota, is_override_enabled_, |
| std::move(host_usage_breakdown_)); |
| if (type_ == StorageType::kTemporary && !is_incognito_ && |
| !is_unlimited_) { |
| UMA_HISTOGRAM_MBYTES("Quota.QuotaForOrigin", host_quota); |
| UMA_HISTOGRAM_MBYTES("Quota.UsageByOrigin", host_usage_); |
| if (host_quota > 0) { |
| UMA_HISTOGRAM_PERCENTAGE("Quota.PercentUsedByOrigin", |
| std::min(100, static_cast<int>((host_usage_ * 100) / host_quota))); |
| } |
| } |
| DeleteSoon(); |
| } |
| |
| private: |
| QuotaManagerImpl* manager() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return static_cast<QuotaManagerImpl*>(observer()); |
| } |
| |
| void OnGotSettings(base::RepeatingClosure barrier_closure, |
| const QuotaSettings& settings) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| settings_ = settings; |
| barrier_closure.Run(); |
| if (type_ == StorageType::kTemporary && !is_unlimited_) { |
| int64_t host_quota = is_session_only_ |
| ? settings.session_only_per_host_quota |
| : settings.per_host_quota; |
| SetDesiredHostQuota(barrier_closure, blink::mojom::QuotaStatusCode::kOk, |
| host_quota); |
| } |
| } |
| |
| void OnGotCapacity(base::OnceClosure barrier_closure, |
| int64_t total_space, |
| int64_t available_space) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| total_space_ = total_space; |
| available_space_ = available_space; |
| std::move(barrier_closure).Run(); |
| } |
| |
| void OnGotHostUsage(base::OnceClosure barrier_closure, |
| int64_t usage, |
| blink::mojom::UsageBreakdownPtr usage_breakdown) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| host_usage_ = usage; |
| host_usage_breakdown_ = std::move(usage_breakdown); |
| std::move(barrier_closure).Run(); |
| } |
| |
| void SetDesiredHostQuota(base::OnceClosure barrier_closure, |
| blink::mojom::QuotaStatusCode status, |
| int64_t quota) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| desired_host_quota_ = quota; |
| std::move(barrier_closure).Run(); |
| } |
| |
| void OnBarrierComplete() { CallCompleted(); } |
| |
| const url::Origin origin_; |
| QuotaManagerImpl::UsageAndQuotaForDevtoolsCallback callback_; |
| const StorageType type_; |
| const bool is_unlimited_; |
| const bool is_session_only_; |
| const bool is_incognito_; |
| int64_t available_space_ = 0; |
| int64_t total_space_ = 0; |
| int64_t desired_host_quota_ = 0; |
| int64_t host_usage_ = 0; |
| const bool is_override_enabled_; |
| base::Optional<int64_t> quota_override_size_; |
| blink::mojom::UsageBreakdownPtr host_usage_breakdown_; |
| QuotaSettings settings_; |
| SEQUENCE_CHECKER(sequence_checker_); |
| |
| // Weak pointers are used to support cancelling work. |
| base::WeakPtrFactory<UsageAndQuotaInfoGatherer> weak_factory_{this}; |
| }; |
| |
| class QuotaManagerImpl::EvictionRoundInfoHelper : public QuotaTask { |
| public: |
| EvictionRoundInfoHelper(QuotaManagerImpl* manager, |
| EvictionRoundInfoCallback callback) |
| : QuotaTask(manager), callback_(std::move(callback)) {} |
| |
| protected: |
| void Run() override { |
| // Gather 2 pieces of info before deciding if we need to get GlobalUsage: |
| // settings and device_storage_capacity. |
| base::RepeatingClosure barrier = base::BarrierClosure( |
| 2, base::BindOnce(&EvictionRoundInfoHelper::OnBarrierComplete, |
| weak_factory_.GetWeakPtr())); |
| |
| manager()->GetQuotaSettings( |
| base::BindOnce(&EvictionRoundInfoHelper::OnGotSettings, |
| weak_factory_.GetWeakPtr(), barrier)); |
| manager()->GetStorageCapacity( |
| base::BindOnce(&EvictionRoundInfoHelper::OnGotCapacity, |
| weak_factory_.GetWeakPtr(), barrier)); |
| } |
| |
| void Aborted() override { |
| weak_factory_.InvalidateWeakPtrs(); |
| std::move(callback_).Run(blink::mojom::QuotaStatusCode::kErrorAbort, |
| QuotaSettings(), 0, 0, 0, false); |
| DeleteSoon(); |
| } |
| |
| void Completed() override { |
| weak_factory_.InvalidateWeakPtrs(); |
| std::move(callback_).Run(blink::mojom::QuotaStatusCode::kOk, settings_, |
| available_space_, total_space_, global_usage_, |
| global_usage_is_complete_); |
| DeleteSoon(); |
| } |
| |
| private: |
| QuotaManagerImpl* manager() const { |
| return static_cast<QuotaManagerImpl*>(observer()); |
| } |
| |
| void OnGotSettings(base::OnceClosure barrier_closure, |
| const QuotaSettings& settings) { |
| settings_ = settings; |
| std::move(barrier_closure).Run(); |
| } |
| |
| void OnGotCapacity(base::OnceClosure barrier_closure, |
| int64_t total_space, |
| int64_t available_space) { |
| total_space_ = total_space; |
| available_space_ = available_space; |
| std::move(barrier_closure).Run(); |
| } |
| |
| void OnBarrierComplete() { |
| // Avoid computing the full current_usage when there's no pressure. |
| int64_t consumed_space = total_space_ - available_space_; |
| if (consumed_space < settings_.pool_size && |
| available_space_ > settings_.should_remain_available) { |
| DCHECK(!global_usage_is_complete_); |
| global_usage_ = |
| manager()->GetUsageTracker(StorageType::kTemporary)->GetCachedUsage(); |
| CallCompleted(); |
| return; |
| } |
| manager()->GetGlobalUsage( |
| StorageType::kTemporary, |
| base::BindOnce(&EvictionRoundInfoHelper::OnGotGlobalUsage, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void OnGotGlobalUsage(int64_t usage, int64_t unlimited_usage) { |
| global_usage_ = std::max(INT64_C(0), usage - unlimited_usage); |
| global_usage_is_complete_ = true; |
| CallCompleted(); |
| } |
| |
| EvictionRoundInfoCallback callback_; |
| QuotaSettings settings_; |
| int64_t available_space_ = 0; |
| int64_t total_space_ = 0; |
| int64_t global_usage_ = 0; |
| bool global_usage_is_complete_ = false; |
| base::WeakPtrFactory<EvictionRoundInfoHelper> weak_factory_{this}; |
| }; |
| |
| class QuotaManagerImpl::GetUsageInfoTask : public QuotaTask { |
| public: |
| GetUsageInfoTask(QuotaManagerImpl* manager, GetUsageInfoCallback callback) |
| : QuotaTask(manager), callback_(std::move(callback)) {} |
| |
| protected: |
| void Run() override { |
| remaining_trackers_ = 3; |
| // This will populate cached hosts and usage info. |
| manager() |
| ->GetUsageTracker(StorageType::kTemporary) |
| ->GetGlobalUsage(base::BindOnce(&GetUsageInfoTask::DidGetGlobalUsage, |
| weak_factory_.GetWeakPtr(), |
| StorageType::kTemporary)); |
| manager() |
| ->GetUsageTracker(StorageType::kPersistent) |
| ->GetGlobalUsage(base::BindOnce(&GetUsageInfoTask::DidGetGlobalUsage, |
| weak_factory_.GetWeakPtr(), |
| StorageType::kPersistent)); |
| manager() |
| ->GetUsageTracker(StorageType::kSyncable) |
| ->GetGlobalUsage(base::BindOnce(&GetUsageInfoTask::DidGetGlobalUsage, |
| weak_factory_.GetWeakPtr(), |
| StorageType::kSyncable)); |
| } |
| |
| void Completed() override { |
| std::move(callback_).Run(std::move(entries_)); |
| DeleteSoon(); |
| } |
| |
| void Aborted() override { |
| std::move(callback_).Run(UsageInfoEntries()); |
| DeleteSoon(); |
| } |
| |
| private: |
| void AddEntries(StorageType type, UsageTracker* tracker) { |
| std::map<std::string, int64_t> host_usage = tracker->GetCachedHostsUsage(); |
| for (const auto& host_usage_pair : host_usage) { |
| entries_.emplace_back(host_usage_pair.first, type, |
| host_usage_pair.second); |
| } |
| if (--remaining_trackers_ == 0) |
| CallCompleted(); |
| } |
| |
| void DidGetGlobalUsage(StorageType type, int64_t, int64_t) { |
| DCHECK(manager()->GetUsageTracker(type)); |
| AddEntries(type, manager()->GetUsageTracker(type)); |
| } |
| |
| QuotaManagerImpl* manager() const { |
| return static_cast<QuotaManagerImpl*>(observer()); |
| } |
| |
| GetUsageInfoCallback callback_; |
| UsageInfoEntries entries_; |
| int remaining_trackers_; |
| base::WeakPtrFactory<GetUsageInfoTask> weak_factory_{this}; |
| }; |
| |
| class QuotaManagerImpl::OriginDataDeleter : public QuotaTask { |
| public: |
| OriginDataDeleter(QuotaManagerImpl* manager, |
| const url::Origin& origin, |
| StorageType type, |
| QuotaClientTypes quota_client_types, |
| bool is_eviction, |
| StatusCallback callback) |
| : QuotaTask(manager), |
| origin_(origin), |
| type_(type), |
| quota_client_types_(std::move(quota_client_types)), |
| error_count_(0), |
| remaining_clients_(0), |
| skipped_clients_(0), |
| is_eviction_(is_eviction), |
| callback_(std::move(callback)) {} |
| |
| protected: |
| void Run() override { |
| DCHECK(manager()->client_types_.contains(type_)); |
| remaining_clients_ = manager()->client_types_[type_].size(); |
| |
| for (const auto& client_and_type : manager()->client_types_[type_]) { |
| QuotaClient* client = client_and_type.first; |
| QuotaClientType client_type = client_and_type.second; |
| if (quota_client_types_.contains(client_type)) { |
| static int tracing_id = 0; |
| TRACE_EVENT_ASYNC_BEGIN2("browsing_data", |
| "QuotaManagerImpl::OriginDataDeleter", |
| ++tracing_id, "client_type", client_type, |
| "origin", origin_.Serialize()); |
| client->DeleteOriginData( |
| origin_, type_, |
| base::BindOnce(&OriginDataDeleter::DidDeleteOriginData, |
| weak_factory_.GetWeakPtr(), tracing_id)); |
| } else { |
| ++skipped_clients_; |
| --remaining_clients_; |
| } |
| } |
| |
| if (remaining_clients_ == 0) |
| CallCompleted(); |
| } |
| |
| 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_, is_eviction_); |
| std::move(callback_).Run(blink::mojom::QuotaStatusCode::kOk); |
| } else { |
| std::move(callback_).Run( |
| blink::mojom::QuotaStatusCode::kErrorInvalidModification); |
| } |
| DeleteSoon(); |
| } |
| |
| void Aborted() override { |
| std::move(callback_).Run(blink::mojom::QuotaStatusCode::kErrorAbort); |
| DeleteSoon(); |
| } |
| |
| private: |
| void DidDeleteOriginData(int tracing_id, |
| blink::mojom::QuotaStatusCode status) { |
| DCHECK_GT(remaining_clients_, 0U); |
| TRACE_EVENT_ASYNC_END0("browsing_data", |
| "QuotaManagerImpl::OriginDataDeleter", tracing_id); |
| |
| if (status != blink::mojom::QuotaStatusCode::kOk) |
| ++error_count_; |
| |
| if (--remaining_clients_ == 0) |
| CallCompleted(); |
| } |
| |
| QuotaManagerImpl* manager() const { |
| return static_cast<QuotaManagerImpl*>(observer()); |
| } |
| |
| const url::Origin origin_; |
| const StorageType type_; |
| const QuotaClientTypes quota_client_types_; |
| int error_count_; |
| size_t remaining_clients_; |
| int skipped_clients_; |
| const bool is_eviction_; |
| StatusCallback callback_; |
| |
| base::WeakPtrFactory<OriginDataDeleter> weak_factory_{this}; |
| }; |
| |
| class QuotaManagerImpl::HostDataDeleter : public QuotaTask { |
| public: |
| HostDataDeleter(QuotaManagerImpl* manager, |
| const std::string& host, |
| StorageType type, |
| QuotaClientTypes quota_client_types, |
| StatusCallback callback) |
| : QuotaTask(manager), |
| host_(host), |
| type_(type), |
| quota_client_types_(std::move(quota_client_types)), |
| error_count_(0), |
| remaining_clients_(0), |
| remaining_deleters_(0), |
| callback_(std::move(callback)) {} |
| |
| protected: |
| void Run() override { |
| DCHECK(manager()->client_types_.contains(type_)); |
| remaining_clients_ = manager()->client_types_[type_].size(); |
| |
| for (const auto& client_and_type : manager()->client_types_[type_]) { |
| client_and_type.first->GetOriginsForHost( |
| type_, host_, |
| base::BindOnce(&HostDataDeleter::DidGetOriginsForHost, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void Completed() override { |
| if (error_count_ == 0) { |
| std::move(callback_).Run(blink::mojom::QuotaStatusCode::kOk); |
| } else { |
| std::move(callback_).Run( |
| blink::mojom::QuotaStatusCode::kErrorInvalidModification); |
| } |
| DeleteSoon(); |
| } |
| |
| void Aborted() override { |
| std::move(callback_).Run(blink::mojom::QuotaStatusCode::kErrorAbort); |
| DeleteSoon(); |
| } |
| |
| private: |
| void DidGetOriginsForHost(const std::vector<url::Origin>& origins) { |
| DCHECK_GT(remaining_clients_, 0U); |
| |
| for (const auto& origin : origins) |
| origins_.insert(origin); |
| |
| if (--remaining_clients_ == 0) { |
| if (!origins_.empty()) |
| ScheduleOriginsDeletion(); |
| else |
| CallCompleted(); |
| } |
| } |
| |
| void ScheduleOriginsDeletion() { |
| remaining_deleters_ = origins_.size(); |
| for (const auto& origin : origins_) { |
| OriginDataDeleter* deleter = new OriginDataDeleter( |
| manager(), origin, type_, std::move(quota_client_types_), false, |
| base::BindOnce(&HostDataDeleter::DidDeleteOriginData, |
| weak_factory_.GetWeakPtr())); |
| deleter->Start(); |
| } |
| } |
| |
| void DidDeleteOriginData(blink::mojom::QuotaStatusCode status) { |
| DCHECK_GT(remaining_deleters_, 0U); |
| |
| if (status != blink::mojom::QuotaStatusCode::kOk) |
| ++error_count_; |
| |
| if (--remaining_deleters_ == 0) |
| CallCompleted(); |
| } |
| |
| QuotaManagerImpl* manager() const { |
| return static_cast<QuotaManagerImpl*>(observer()); |
| } |
| |
| const std::string host_; |
| const StorageType type_; |
| const QuotaClientTypes quota_client_types_; |
| std::set<url::Origin> origins_; |
| int error_count_; |
| size_t remaining_clients_; |
| size_t remaining_deleters_; |
| StatusCallback callback_; |
| |
| base::WeakPtrFactory<HostDataDeleter> weak_factory_{this}; |
| }; |
| |
| class QuotaManagerImpl::StorageCleanupHelper : public QuotaTask { |
| public: |
| StorageCleanupHelper(QuotaManagerImpl* manager, |
| StorageType type, |
| QuotaClientTypes quota_client_types, |
| base::OnceClosure callback) |
| : QuotaTask(manager), |
| type_(type), |
| quota_client_types_(std::move(quota_client_types)), |
| callback_(std::move(callback)) { |
| DCHECK(manager->client_types_.contains(type_)); |
| } |
| |
| protected: |
| void Run() override { |
| DCHECK(manager()->client_types_.contains(type_)); |
| base::RepeatingClosure barrier = base::BarrierClosure( |
| manager()->client_types_[type_].size(), |
| base::BindOnce(&StorageCleanupHelper::CallCompleted, |
| weak_factory_.GetWeakPtr())); |
| |
| // This may synchronously trigger |callback_| at the end of the for loop, |
| // make sure we do nothing after this block. |
| for (const auto& client_and_type : manager()->client_types_[type_]) { |
| QuotaClient* client = client_and_type.first; |
| QuotaClientType client_type = client_and_type.second; |
| if (quota_client_types_.contains(client_type)) { |
| client->PerformStorageCleanup(type_, barrier); |
| } else { |
| barrier.Run(); |
| } |
| } |
| } |
| |
| void Aborted() override { |
| weak_factory_.InvalidateWeakPtrs(); |
| std::move(callback_).Run(); |
| DeleteSoon(); |
| } |
| |
| void Completed() override { |
| weak_factory_.InvalidateWeakPtrs(); |
| std::move(callback_).Run(); |
| DeleteSoon(); |
| } |
| |
| private: |
| QuotaManagerImpl* manager() const { |
| return static_cast<QuotaManagerImpl*>(observer()); |
| } |
| |
| const StorageType type_; |
| const QuotaClientTypes quota_client_types_; |
| base::OnceClosure callback_; |
| base::WeakPtrFactory<StorageCleanupHelper> weak_factory_{this}; |
| }; |
| |
| // Fetch origins that have been modified since the specified time. This is used |
| // to clear data for origins that have been modified within the user specified |
| // time frame. |
| // |
| // This class is granted ownership of itself when it is passed to |
| // DidGetModifiedBetween() via base::Owned(). When the closure for said |
| // function goes out of scope, the object is deleted. This is a thread-safe |
| // class. |
| class QuotaManagerImpl::GetModifiedSinceHelper { |
| public: |
| bool GetModifiedBetweenOnDBThread(StorageType type, |
| base::Time begin, |
| base::Time end, |
| QuotaDatabase* database) { |
| DCHECK(database); |
| return database->GetOriginsModifiedBetween(type, &origins_, begin, end); |
| } |
| |
| void DidGetModifiedBetween(const base::WeakPtr<QuotaManagerImpl>& manager, |
| GetOriginsCallback callback, |
| StorageType type, |
| bool success) { |
| if (!manager) { |
| // The operation was aborted. |
| std::move(callback).Run(std::set<url::Origin>(), type); |
| return; |
| } |
| manager->DidDatabaseWork(success); |
| std::move(callback).Run(origins_, type); |
| } |
| |
| private: |
| std::set<url::Origin> origins_; |
| }; |
| |
| // Gather origin info table for quota-internals page. |
| // |
| // This class is granted ownership of itself when it is passed to |
| // DidDumpQuotaTable() via base::Owned(). When the closure for said function |
| // goes out of scope, the object is deleted. |
| // This class is not thread-safe because there can be a race when entries_ is |
| // modified. |
| class QuotaManagerImpl::DumpQuotaTableHelper { |
| public: |
| bool DumpQuotaTableOnDBThread(QuotaDatabase* database) { |
| DCHECK(database); |
| return database->DumpQuotaTable(base::BindRepeating( |
| &DumpQuotaTableHelper::AppendEntry, base::Unretained(this))); |
| } |
| |
| void DidDumpQuotaTable(const base::WeakPtr<QuotaManagerImpl>& manager, |
| DumpQuotaTableCallback callback, |
| bool success) { |
| if (!manager) { |
| // The operation was aborted. |
| std::move(callback).Run(QuotaTableEntries()); |
| return; |
| } |
| manager->DidDatabaseWork(success); |
| std::move(callback).Run(entries_); |
| } |
| |
| private: |
| bool AppendEntry(const QuotaTableEntry& entry) { |
| entries_.push_back(entry); |
| return true; |
| } |
| |
| QuotaTableEntries entries_; |
| }; |
| |
| // Gather origin info table for quota-internals page. |
| // |
| // This class is granted ownership of itself when it is passed to |
| // DidDumpQuotaTable() via base::Owned(). When the closure for said function |
| // goes out of scope, the object is deleted. |
| // This class is not thread-safe because there can be races when entries_ is |
| // modified. |
| class QuotaManagerImpl::DumpOriginInfoTableHelper { |
| public: |
| bool DumpOriginInfoTableOnDBThread(QuotaDatabase* database) { |
| DCHECK(database); |
| return database->DumpOriginInfoTable(base::BindRepeating( |
| &DumpOriginInfoTableHelper::AppendEntry, base::Unretained(this))); |
| } |
| |
| void DidDumpOriginInfoTable(const base::WeakPtr<QuotaManagerImpl>& manager, |
| DumpOriginInfoTableCallback callback, |
| bool success) { |
| if (!manager) { |
| // The operation was aborted. |
| std::move(callback).Run(OriginInfoTableEntries()); |
| return; |
| } |
| manager->DidDatabaseWork(success); |
| std::move(callback).Run(entries_); |
| } |
| |
| private: |
| bool AppendEntry(const OriginInfoTableEntry& entry) { |
| entries_.push_back(entry); |
| return true; |
| } |
| |
| OriginInfoTableEntries entries_; |
| }; |
| |
| // QuotaManagerImpl ----------------------------------------------------------- |
| |
| QuotaManagerImpl::QuotaManagerImpl( |
| bool is_incognito, |
| const base::FilePath& profile_path, |
| scoped_refptr<base::SingleThreadTaskRunner> io_thread, |
| base::RepeatingClosure quota_change_callback, |
| scoped_refptr<SpecialStoragePolicy> special_storage_policy, |
| const GetQuotaSettingsFunc& get_settings_function) |
| : RefCountedDeleteOnSequence<QuotaManagerImpl>(io_thread), |
| is_incognito_(is_incognito), |
| profile_path_(profile_path), |
| proxy_(base::MakeRefCounted<QuotaManagerProxy>(this, io_thread)), |
| db_disabled_(false), |
| eviction_disabled_(false), |
| io_thread_(std::move(io_thread)), |
| db_runner_(base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_VISIBLE, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN})), |
| get_settings_function_(get_settings_function), |
| quota_change_callback_(std::move(quota_change_callback)), |
| is_getting_eviction_origin_(false), |
| special_storage_policy_(std::move(special_storage_policy)), |
| get_volume_info_fn_(&QuotaManagerImpl::GetVolumeInfo) { |
| DCHECK_EQ(settings_.refresh_interval, base::TimeDelta::Max()); |
| if (!get_settings_function.is_null()) { |
| // Reset the interval to ensure we use the get_settings_function |
| // the first times settings_ is needed. |
| settings_.refresh_interval = base::TimeDelta(); |
| get_settings_task_runner_ = base::ThreadTaskRunnerHandle::Get(); |
| } |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| void QuotaManagerImpl::SetQuotaSettings(const QuotaSettings& settings) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| settings_ = settings; |
| settings_timestamp_ = base::TimeTicks::Now(); |
| } |
| |
| void QuotaManagerImpl::GetUsageInfo(GetUsageInfoCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| GetUsageInfoTask* get_usage_info = |
| new GetUsageInfoTask(this, std::move(callback)); |
| get_usage_info->Start(); |
| } |
| |
| void QuotaManagerImpl::GetUsageAndQuotaForWebApps( |
| const url::Origin& origin, |
| StorageType type, |
| UsageAndQuotaCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| GetUsageAndQuotaWithBreakdown( |
| origin, type, |
| base::BindOnce(&DidGetUsageAndQuotaStripBreakdown, std::move(callback))); |
| } |
| |
| void QuotaManagerImpl::GetUsageAndQuotaWithBreakdown( |
| const url::Origin& origin, |
| StorageType type, |
| UsageAndQuotaWithBreakdownCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| GetUsageAndQuotaForDevtools( |
| origin, type, |
| base::BindOnce(&DidGetUsageAndQuotaStripOverride, std::move(callback))); |
| } |
| |
| void QuotaManagerImpl::GetUsageAndQuotaForDevtools( |
| const url::Origin& origin, |
| StorageType type, |
| UsageAndQuotaForDevtoolsCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!IsSupportedType(type) || |
| (is_incognito_ && !IsSupportedIncognitoType(type))) { |
| std::move(callback).Run(blink::mojom::QuotaStatusCode::kErrorNotSupported, |
| /*usage=*/0, |
| /*quota=*/0, |
| /*is_override_enabled=*/false, |
| /*usage_breakdown=*/nullptr); |
| return; |
| } |
| LazyInitialize(); |
| |
| bool is_session_only = |
| type == StorageType::kTemporary && special_storage_policy_ && |
| special_storage_policy_->IsStorageSessionOnly(origin.GetURL()); |
| |
| base::Optional<int64_t> quota_override = GetQuotaOverrideForOrigin(origin); |
| |
| UsageAndQuotaInfoGatherer* helper = new UsageAndQuotaInfoGatherer( |
| this, origin, type, IsStorageUnlimited(origin, type), is_session_only, |
| is_incognito_, quota_override, std::move(callback)); |
| helper->Start(); |
| } |
| |
| void QuotaManagerImpl::GetUsageAndQuota(const url::Origin& origin, |
| StorageType type, |
| UsageAndQuotaCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (IsStorageUnlimited(origin, type)) { |
| // TODO(michaeln): This seems like a non-obvious odd behavior, probably for |
| // apps/extensions, but it would be good to eliminate this special case. |
| std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk, 0, kNoLimit); |
| return; |
| } |
| |
| if (!IsSupportedType(type) || |
| (is_incognito_ && !IsSupportedIncognitoType(type))) { |
| std::move(callback).Run( |
| /*status*/ blink::mojom::QuotaStatusCode::kErrorNotSupported, |
| /*usage*/ 0, |
| /*quota*/ 0); |
| return; |
| } |
| LazyInitialize(); |
| |
| bool is_session_only = |
| type == StorageType::kTemporary && special_storage_policy_ && |
| special_storage_policy_->IsStorageSessionOnly(origin.GetURL()); |
| |
| base::Optional<int64_t> quota_override = GetQuotaOverrideForOrigin(origin); |
| |
| UsageAndQuotaInfoGatherer* helper = new UsageAndQuotaInfoGatherer( |
| this, origin, type, IsStorageUnlimited(origin, type), is_session_only, |
| is_incognito_, quota_override, |
| base::BindOnce(&DidGetUsageAndQuotaStripOverride, |
| base::BindOnce(&DidGetUsageAndQuotaStripBreakdown, |
| std::move(callback)))); |
| helper->Start(); |
| } |
| |
| void QuotaManagerImpl::NotifyWriteFailed(const url::Origin& origin) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| auto age_of_disk_stats = base::TimeTicks::Now() - |
| std::get<0>(cached_disk_stats_for_storage_pressure_); |
| |
| // Avoid polling for free disk space if disk stats have been recently |
| // queried. |
| if (age_of_disk_stats < kStoragePressureCheckDiskStatsInterval) { |
| int64_t total_space = std::get<1>(cached_disk_stats_for_storage_pressure_); |
| int64_t available_space = |
| std::get<2>(cached_disk_stats_for_storage_pressure_); |
| MaybeRunStoragePressureCallback(origin, total_space, available_space); |
| } |
| |
| GetStorageCapacity( |
| base::BindOnce(&QuotaManagerImpl::MaybeRunStoragePressureCallback, |
| weak_factory_.GetWeakPtr(), origin)); |
| } |
| |
| void QuotaManagerImpl::NotifyOriginInUse(const url::Origin& origin) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(io_thread_->BelongsToCurrentThread()); |
| origins_in_use_[origin]++; |
| } |
| |
| void QuotaManagerImpl::NotifyOriginNoLongerInUse(const url::Origin& origin) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(io_thread_->BelongsToCurrentThread()); |
| DCHECK(IsOriginInUse(origin)); |
| int& count = origins_in_use_[origin]; |
| if (--count == 0) |
| origins_in_use_.erase(origin); |
| } |
| |
| void QuotaManagerImpl::SetUsageCacheEnabled(QuotaClientType client_id, |
| const url::Origin& origin, |
| StorageType type, |
| bool enabled) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| DCHECK(GetUsageTracker(type)); |
| GetUsageTracker(type)->SetUsageCacheEnabled(client_id, origin, enabled); |
| } |
| |
| void QuotaManagerImpl::DeleteOriginData(const url::Origin& origin, |
| StorageType type, |
| QuotaClientTypes quota_client_types, |
| StatusCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DeleteOriginDataInternal(origin, type, std::move(quota_client_types), false, |
| std::move(callback)); |
| } |
| |
| void QuotaManagerImpl::PerformStorageCleanup( |
| StorageType type, |
| QuotaClientTypes quota_client_types, |
| base::OnceClosure callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| StorageCleanupHelper* deleter = new StorageCleanupHelper( |
| this, type, std::move(quota_client_types), std::move(callback)); |
| deleter->Start(); |
| } |
| |
| void QuotaManagerImpl::DeleteHostData(const std::string& host, |
| StorageType type, |
| QuotaClientTypes quota_client_types, |
| StatusCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| |
| DCHECK(client_types_.contains(type)); |
| if (host.empty() || client_types_[type].empty()) { |
| std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk); |
| return; |
| } |
| |
| HostDataDeleter* deleter = new HostDataDeleter( |
| this, host, type, std::move(quota_client_types), std::move(callback)); |
| deleter->Start(); |
| } |
| |
| void QuotaManagerImpl::GetPersistentHostQuota(const std::string& host, |
| QuotaCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 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. |
| std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk, 0); |
| return; |
| } |
| |
| if (!persistent_host_quota_callbacks_.Add(host, std::move(callback))) |
| return; |
| |
| int64_t* quota_ptr = new int64_t(0); |
| PostTaskAndReplyWithResultForDBThread( |
| FROM_HERE, |
| base::BindOnce(&GetPersistentHostQuotaOnDBThread, host, |
| base::Unretained(quota_ptr)), |
| base::BindOnce(&QuotaManagerImpl::DidGetPersistentHostQuota, |
| weak_factory_.GetWeakPtr(), host, base::Owned(quota_ptr))); |
| } |
| |
| void QuotaManagerImpl::SetPersistentHostQuota(const std::string& host, |
| int64_t new_quota, |
| QuotaCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| if (host.empty()) { |
| // This could happen if we are called on file:///. |
| std::move(callback).Run(blink::mojom::QuotaStatusCode::kErrorNotSupported, |
| 0); |
| return; |
| } |
| |
| if (new_quota < 0) { |
| std::move(callback).Run( |
| blink::mojom::QuotaStatusCode::kErrorInvalidModification, -1); |
| return; |
| } |
| |
| // Cap the requested size at the per-host quota limit. |
| new_quota = std::min(new_quota, kPerHostPersistentQuotaLimit); |
| |
| if (db_disabled_) { |
| std::move(callback).Run(blink::mojom::QuotaStatusCode::kErrorInvalidAccess, |
| -1); |
| return; |
| } |
| |
| int64_t* new_quota_ptr = new int64_t(new_quota); |
| PostTaskAndReplyWithResultForDBThread( |
| FROM_HERE, |
| base::BindOnce(&SetPersistentHostQuotaOnDBThread, host, |
| base::Unretained(new_quota_ptr)), |
| base::BindOnce(&QuotaManagerImpl::DidSetPersistentHostQuota, |
| weak_factory_.GetWeakPtr(), host, std::move(callback), |
| base::Owned(new_quota_ptr))); |
| } |
| |
| void QuotaManagerImpl::GetGlobalUsage(StorageType type, |
| GlobalUsageCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| DCHECK(GetUsageTracker(type)); |
| GetUsageTracker(type)->GetGlobalUsage(std::move(callback)); |
| } |
| |
| void QuotaManagerImpl::GetHostUsageWithBreakdown( |
| const std::string& host, |
| StorageType type, |
| UsageWithBreakdownCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| DCHECK(GetUsageTracker(type)); |
| GetUsageTracker(type)->GetHostUsageWithBreakdown(host, std::move(callback)); |
| } |
| |
| std::map<std::string, std::string> QuotaManagerImpl::GetStatistics() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| std::map<std::string, std::string> statistics; |
| if (temporary_storage_evictor_) { |
| std::map<std::string, int64_t> stats; |
| temporary_storage_evictor_->GetStatistics(&stats); |
| for (const auto& origin_usage_pair : stats) { |
| statistics[origin_usage_pair.first] = |
| base::NumberToString(origin_usage_pair.second); |
| } |
| } |
| return statistics; |
| } |
| |
| bool QuotaManagerImpl::IsStorageUnlimited(const url::Origin& origin, |
| StorageType type) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // For syncable storage we should always enforce quota (since the |
| // quota must be capped by the server limit). |
| if (type == StorageType::kSyncable) |
| return false; |
| if (type == StorageType::kQuotaNotManaged) |
| return true; |
| return special_storage_policy_.get() && |
| special_storage_policy_->IsStorageUnlimited(origin.GetURL()); |
| } |
| |
| void QuotaManagerImpl::GetOriginsModifiedBetween(StorageType type, |
| base::Time begin, |
| base::Time end, |
| GetOriginsCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| GetModifiedSinceHelper* helper = new GetModifiedSinceHelper; |
| PostTaskAndReplyWithResultForDBThread( |
| FROM_HERE, |
| base::BindOnce(&GetModifiedSinceHelper::GetModifiedBetweenOnDBThread, |
| base::Unretained(helper), type, begin, end), |
| base::BindOnce(&GetModifiedSinceHelper::DidGetModifiedBetween, |
| base::Owned(helper), weak_factory_.GetWeakPtr(), |
| std::move(callback), type)); |
| } |
| |
| bool QuotaManagerImpl::ResetUsageTracker(StorageType type) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(GetUsageTracker(type)); |
| if (GetUsageTracker(type)->IsWorking()) |
| return false; |
| |
| auto usage_tracker = std::make_unique<UsageTracker>( |
| client_types_[type], type, special_storage_policy_.get()); |
| switch (type) { |
| case StorageType::kTemporary: |
| temporary_usage_tracker_ = std::move(usage_tracker); |
| return true; |
| case StorageType::kPersistent: |
| persistent_usage_tracker_ = std::move(usage_tracker); |
| return true; |
| case StorageType::kSyncable: |
| syncable_usage_tracker_ = std::move(usage_tracker); |
| return true; |
| default: |
| NOTREACHED(); |
| } |
| return true; |
| } |
| |
| QuotaManagerImpl::~QuotaManagerImpl() { |
| proxy_->InvalidateQuotaManagerImpl(base::PassKey<QuotaManagerImpl>()); |
| |
| // Iterating over `legacy_clients_for_ownership_` is correct here because we |
| // want to call OnQuotaManagerDestroyed() once per QuotaClient. |
| for (const auto& client : legacy_clients_for_ownership_) |
| client->OnQuotaManagerDestroyed(); |
| |
| if (database_) |
| db_runner_->DeleteSoon(FROM_HERE, database_.release()); |
| } |
| |
| QuotaManagerImpl::EvictionContext::EvictionContext() |
| : evicted_type(StorageType::kUnknown) {} |
| |
| QuotaManagerImpl::EvictionContext::~EvictionContext() = default; |
| |
| void QuotaManagerImpl::LazyInitialize() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(io_thread_->BelongsToCurrentThread()); |
| if (database_) { |
| // Already initialized. |
| return; |
| } |
| |
| // Use an empty path to open an in-memory only database for incognito. |
| database_ = std::make_unique<QuotaDatabase>( |
| is_incognito_ ? base::FilePath() |
| : profile_path_.AppendASCII(kDatabaseName)); |
| |
| temporary_usage_tracker_ = std::make_unique<UsageTracker>( |
| client_types_[StorageType::kTemporary], StorageType::kTemporary, |
| special_storage_policy_.get()); |
| persistent_usage_tracker_ = std::make_unique<UsageTracker>( |
| client_types_[StorageType::kPersistent], StorageType::kPersistent, |
| special_storage_policy_.get()); |
| syncable_usage_tracker_ = std::make_unique<UsageTracker>( |
| client_types_[StorageType::kSyncable], StorageType::kSyncable, |
| special_storage_policy_.get()); |
| |
| if (!is_incognito_) { |
| histogram_timer_.Start( |
| FROM_HERE, base::TimeDelta::FromMilliseconds(kReportHistogramInterval), |
| this, &QuotaManagerImpl::ReportHistogram); |
| } |
| |
| base::PostTaskAndReplyWithResult( |
| db_runner_.get(), FROM_HERE, |
| base::BindOnce(&QuotaDatabase::IsOriginDatabaseBootstrapped, |
| base::Unretained(database_.get())), |
| base::BindOnce(&QuotaManagerImpl::FinishLazyInitialize, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void QuotaManagerImpl::FinishLazyInitialize(bool is_database_bootstrapped) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| is_database_bootstrapped_ = is_database_bootstrapped; |
| StartEviction(); |
| } |
| |
| void QuotaManagerImpl::BootstrapDatabaseForEviction( |
| GetOriginCallback did_get_origin_callback, |
| int64_t usage, |
| int64_t unlimited_usage) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // The usage cache should be fully populated now so we can |
| // seed the database with origins we know about. |
| std::set<url::Origin> origins = temporary_usage_tracker_->GetCachedOrigins(); |
| PostTaskAndReplyWithResultForDBThread( |
| FROM_HERE, |
| base::BindOnce(&BootstrapDatabaseOnDBThread, std::move(origins)), |
| base::BindOnce(&QuotaManagerImpl::DidBootstrapDatabase, |
| weak_factory_.GetWeakPtr(), |
| std::move(did_get_origin_callback))); |
| } |
| |
| void QuotaManagerImpl::DidBootstrapDatabase( |
| GetOriginCallback did_get_origin_callback, |
| bool success) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| is_database_bootstrapped_ = success; |
| DidDatabaseWork(success); |
| GetLRUOrigin(StorageType::kTemporary, std::move(did_get_origin_callback)); |
| } |
| |
| void QuotaManagerImpl::RegisterClient( |
| mojo::PendingRemote<mojom::QuotaClient> client, |
| QuotaClientType client_type, |
| const std::vector<blink::mojom::StorageType>& storage_types) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!database_.get()) |
| << "All clients must be registered before the database is initialized"; |
| |
| clients_for_ownership_.emplace_back(std::move(client)); |
| mojom::QuotaClient* client_ptr = clients_for_ownership_.back().get(); |
| |
| // TODO(crbug.com/1163009): Remove this block after all QuotaClients have been |
| // mojofied. |
| legacy_clients_for_ownership_.push_back( |
| base::MakeRefCounted<MojoQuotaClientWrapper>(client_ptr)); |
| QuotaClient* legacy_client_ptr = legacy_clients_for_ownership_.back().get(); |
| |
| // TODO(crbug.com/1163009): Use client_ptr instead of legacy_client_ptr after |
| // all QuotaClients have been mojofied. |
| for (blink::mojom::StorageType storage_type : storage_types) |
| client_types_[storage_type].insert({legacy_client_ptr, client_type}); |
| } |
| |
| void QuotaManagerImpl::RegisterLegacyClient( |
| scoped_refptr<QuotaClient> client, |
| QuotaClientType client_type, |
| const std::vector<blink::mojom::StorageType>& storage_types) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!database_.get()) |
| << "All clients must be registered before the database is initialized"; |
| DCHECK(client.get()); |
| |
| for (blink::mojom::StorageType storage_type : storage_types) |
| client_types_[storage_type].insert({client.get(), client_type}); |
| legacy_clients_for_ownership_.push_back(std::move(client)); |
| } |
| |
| UsageTracker* QuotaManagerImpl::GetUsageTracker(StorageType type) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| switch (type) { |
| case StorageType::kTemporary: |
| return temporary_usage_tracker_.get(); |
| case StorageType::kPersistent: |
| return persistent_usage_tracker_.get(); |
| case StorageType::kSyncable: |
| return syncable_usage_tracker_.get(); |
| case StorageType::kQuotaNotManaged: |
| return nullptr; |
| case StorageType::kUnknown: |
| NOTREACHED(); |
| } |
| return nullptr; |
| } |
| |
| std::set<url::Origin> QuotaManagerImpl::GetCachedOrigins(StorageType type) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| DCHECK(GetUsageTracker(type)); |
| return GetUsageTracker(type)->GetCachedOrigins(); |
| } |
| |
| void QuotaManagerImpl::NotifyStorageAccessed(const url::Origin& origin, |
| StorageType type, |
| base::Time access_time) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| if (type == StorageType::kTemporary && is_getting_eviction_origin_) { |
| // Record the accessed origins while GetLRUOrigin task is runing |
| // to filter out them from eviction. |
| access_notified_origins_.insert(origin); |
| } |
| |
| if (db_disabled_) |
| return; |
| PostTaskAndReplyWithResultForDBThread( |
| FROM_HERE, |
| base::BindOnce(&UpdateAccessTimeOnDBThread, origin, type, access_time), |
| base::BindOnce(&QuotaManagerImpl::DidDatabaseWork, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void QuotaManagerImpl::NotifyStorageModified(QuotaClientType client_id, |
| const url::Origin& origin, |
| StorageType type, |
| int64_t delta, |
| base::Time modification_time, |
| base::OnceClosure callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| DCHECK(GetUsageTracker(type)); |
| GetUsageTracker(type)->UpdateUsageCache(client_id, origin, delta); |
| |
| if (callback) |
| std::move(callback).Run(); |
| |
| if (db_disabled_) |
| return; |
| |
| PostTaskAndReplyWithResultForDBThread( |
| FROM_HERE, |
| base::BindOnce(&UpdateModifiedTimeOnDBThread, origin, type, |
| modification_time), |
| base::BindOnce(&QuotaManagerImpl::DidDatabaseWork, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void QuotaManagerImpl::DumpQuotaTable(DumpQuotaTableCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DumpQuotaTableHelper* helper = new DumpQuotaTableHelper; |
| PostTaskAndReplyWithResultForDBThread( |
| FROM_HERE, |
| base::BindOnce(&DumpQuotaTableHelper::DumpQuotaTableOnDBThread, |
| base::Unretained(helper)), |
| base::BindOnce(&DumpQuotaTableHelper::DidDumpQuotaTable, |
| base::Owned(helper), weak_factory_.GetWeakPtr(), |
| std::move(callback))); |
| } |
| |
| void QuotaManagerImpl::DumpOriginInfoTable( |
| DumpOriginInfoTableCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DumpOriginInfoTableHelper* helper = new DumpOriginInfoTableHelper; |
| PostTaskAndReplyWithResultForDBThread( |
| FROM_HERE, |
| base::BindOnce(&DumpOriginInfoTableHelper::DumpOriginInfoTableOnDBThread, |
| base::Unretained(helper)), |
| base::BindOnce(&DumpOriginInfoTableHelper::DidDumpOriginInfoTable, |
| base::Owned(helper), weak_factory_.GetWeakPtr(), |
| std::move(callback))); |
| } |
| |
| void QuotaManagerImpl::StartEviction() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!temporary_storage_evictor_.get()); |
| if (eviction_disabled_) |
| return; |
| temporary_storage_evictor_ = std::make_unique<QuotaTemporaryStorageEvictor>( |
| this, kEvictionIntervalInMilliSeconds); |
| temporary_storage_evictor_->Start(); |
| } |
| |
| void QuotaManagerImpl::DeleteOriginFromDatabase(const url::Origin& origin, |
| StorageType type, |
| bool is_eviction) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| if (db_disabled_) |
| return; |
| |
| PostTaskAndReplyWithResultForDBThread( |
| FROM_HERE, |
| base::BindOnce(&DeleteOriginInfoOnDBThread, origin, type, is_eviction), |
| base::BindOnce(&QuotaManagerImpl::DidDatabaseWork, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void QuotaManagerImpl::DidOriginDataEvicted( |
| blink::mojom::QuotaStatusCode status) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 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 (> kThresholdOfErrorsToBeDenylisted). |
| if (status != blink::mojom::QuotaStatusCode::kOk) |
| origins_in_error_[eviction_context_.evicted_origin]++; |
| |
| std::move(eviction_context_.evict_origin_data_callback).Run(status); |
| } |
| |
| void QuotaManagerImpl::DeleteOriginDataInternal( |
| const url::Origin& origin, |
| StorageType type, |
| QuotaClientTypes quota_client_types, |
| bool is_eviction, |
| StatusCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| |
| OriginDataDeleter* deleter = |
| new OriginDataDeleter(this, origin, type, std::move(quota_client_types), |
| is_eviction, std::move(callback)); |
| deleter->Start(); |
| } |
| |
| void QuotaManagerImpl::MaybeRunStoragePressureCallback( |
| const url::Origin& origin, |
| int64_t total_space, |
| int64_t available_space) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // TODO(https://crbug.com/1059560): Figure out what 0 total_space means |
| // and how to handle the storage pressure callback in these cases. |
| if (total_space == 0) |
| return; |
| |
| if (!storage_pressure_callback_) { |
| // Quota will hold onto a storage pressure notification if no storage |
| // pressure callback is set. |
| origin_for_pending_storage_pressure_callback_ = std::move(origin); |
| return; |
| } |
| |
| if (available_space < kStoragePressureThresholdRatio * total_space) { |
| storage_pressure_callback_.Run(std::move(origin)); |
| } |
| } |
| |
| void QuotaManagerImpl::SimulateStoragePressure(const url::Origin origin) { |
| storage_pressure_callback_.Run(origin); |
| } |
| |
| void QuotaManagerImpl::DetermineStoragePressure(int64_t total_space, |
| int64_t free_space) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!base::FeatureList::IsEnabled(features::kStoragePressureEvent)) { |
| return; |
| } |
| int64_t threshold_bytes = |
| RandomizeByPercent(kGBytes, kThresholdRandomizationPercent); |
| int64_t threshold = RandomizeByPercent( |
| static_cast<int64_t>(total_space * |
| (kThresholdRandomizationPercent / 100.0)), |
| kThresholdRandomizationPercent); |
| threshold = std::min(threshold_bytes, threshold); |
| if (free_space < threshold && !quota_change_callback_.is_null()) { |
| quota_change_callback_.Run(); |
| } |
| } |
| |
| void QuotaManagerImpl::SetStoragePressureCallback( |
| base::RepeatingCallback<void(url::Origin)> storage_pressure_callback) { |
| storage_pressure_callback_ = storage_pressure_callback; |
| if (origin_for_pending_storage_pressure_callback_.has_value()) { |
| storage_pressure_callback_.Run( |
| std::move(origin_for_pending_storage_pressure_callback_.value())); |
| origin_for_pending_storage_pressure_callback_ = base::nullopt; |
| } |
| } |
| |
| int QuotaManagerImpl::GetOverrideHandleId() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return ++next_override_handle_id_; |
| } |
| |
| void QuotaManagerImpl::OverrideQuotaForOrigin( |
| int handle_id, |
| const url::Origin& origin, |
| base::Optional<int64_t> quota_size) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (quota_size.has_value()) { |
| DCHECK_GE(next_override_handle_id_, handle_id); |
| // Bracket notation is safe here because we want to construct a new |
| // QuotaOverride in the case that one does not exist for origin. |
| devtools_overrides_[origin].active_override_session_ids.insert(handle_id); |
| devtools_overrides_[origin].quota_size = quota_size.value(); |
| } else { |
| devtools_overrides_.erase(origin); |
| } |
| } |
| |
| void QuotaManagerImpl::WithdrawOverridesForHandle(int handle_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| std::vector<url::Origin> origins_to_clear; |
| for (auto& devtools_override : devtools_overrides_) { |
| auto& quota_override = devtools_override.second; |
| auto& origin = devtools_override.first; |
| |
| quota_override.active_override_session_ids.erase(handle_id); |
| |
| if (!quota_override.active_override_session_ids.size()) { |
| origins_to_clear.push_back(origin); |
| } |
| } |
| |
| for (auto& origin : origins_to_clear) { |
| devtools_overrides_.erase(origin); |
| } |
| } |
| |
| base::Optional<int64_t> QuotaManagerImpl::GetQuotaOverrideForOrigin( |
| const url::Origin& origin) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!base::Contains(devtools_overrides_, origin)) { |
| return base::nullopt; |
| } |
| return devtools_overrides_[origin].quota_size; |
| } |
| |
| void QuotaManagerImpl::SetQuotaChangeCallbackForTesting( |
| base::RepeatingClosure storage_pressure_event_callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| quota_change_callback_ = std::move(storage_pressure_event_callback); |
| } |
| |
| void QuotaManagerImpl::ReportHistogram() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!is_incognito_); |
| GetGlobalUsage( |
| StorageType::kTemporary, |
| base::BindOnce(&QuotaManagerImpl::DidGetTemporaryGlobalUsageForHistogram, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void QuotaManagerImpl::DidGetTemporaryGlobalUsageForHistogram( |
| int64_t usage, |
| int64_t unlimited_usage) { |
| GetStorageCapacity( |
| base::BindOnce(&QuotaManagerImpl::DidGetStorageCapacityForHistogram, |
| weak_factory_.GetWeakPtr(), usage)); |
| } |
| |
| void QuotaManagerImpl::DidGetStorageCapacityForHistogram( |
| int64_t usage, |
| int64_t total_space, |
| int64_t available_space) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfTemporaryStorage", usage); |
| if (total_space > 0) { |
| UMA_HISTOGRAM_PERCENTAGE("Quota.PercentUsedForTemporaryStorage2", |
| static_cast<int>((usage * 100) / total_space)); |
| UMA_HISTOGRAM_MBYTES("Quota.AvailableDiskSpace2", available_space); |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Quota.PercentDiskAvailable2", |
| std::min(100, static_cast<int>((available_space * 100 / total_space)))); |
| } |
| |
| GetGlobalUsage( |
| StorageType::kPersistent, |
| base::BindOnce(&QuotaManagerImpl::DidGetPersistentGlobalUsageForHistogram, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void QuotaManagerImpl::DidGetPersistentGlobalUsageForHistogram( |
| int64_t usage, |
| int64_t unlimited_usage) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfPersistentStorage", usage); |
| |
| // We DumpOriginInfoTable last to ensure the trackers caches are loaded. |
| DumpOriginInfoTable( |
| base::BindOnce(&QuotaManagerImpl::DidDumpOriginInfoTableForHistogram, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void QuotaManagerImpl::DidDumpOriginInfoTableForHistogram( |
| const OriginInfoTableEntries& entries) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| std::map<url::Origin, int64_t> usage_map = |
| GetUsageTracker(StorageType::kTemporary)->GetCachedOriginsUsage(); |
| base::Time now = base::Time::Now(); |
| for (const auto& info : entries) { |
| if (info.type != StorageType::kTemporary) |
| continue; |
| |
| // Ignore stale database entries. If there is no map entry, the origin's |
| // data has been deleted. |
| auto it = usage_map.find(info.origin); |
| if (it == usage_map.end() || it->second == 0) |
| continue; |
| |
| base::TimeDelta age = now - std::max(info.last_access_time, |
| info.last_modified_time); |
| UMA_HISTOGRAM_COUNTS_1000("Quota.AgeOfOriginInDays", age.InDays()); |
| |
| int64_t kilobytes = std::max(it->second / INT64_C(1024), INT64_C(1)); |
| base::Histogram::FactoryGet( |
| "Quota.AgeOfDataInDays", 1, 1000, 50, |
| base::HistogramBase::kUmaTargetedHistogramFlag)-> |
| AddCount(age.InDays(), |
| base::saturated_cast<int>(kilobytes)); |
| } |
| } |
| |
| std::set<url::Origin> QuotaManagerImpl::GetEvictionOriginExceptions() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| std::set<url::Origin> exceptions; |
| for (const auto& p : origins_in_use_) { |
| if (p.second > 0) |
| exceptions.insert(p.first); |
| } |
| |
| for (const auto& p : origins_in_error_) { |
| if (p.second > QuotaManagerImpl::kThresholdOfErrorsToBeDenylisted) |
| exceptions.insert(p.first); |
| } |
| |
| return exceptions; |
| } |
| |
| void QuotaManagerImpl::DidGetEvictionOrigin( |
| GetOriginCallback callback, |
| const base::Optional<url::Origin>& origin) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Make sure the returned origin is (still) not in the origin_in_use_ set |
| // and has not been accessed since we posted the task. |
| DCHECK(!origin.has_value() || !origin->GetURL().is_empty()); |
| if (origin.has_value() && |
| (base::Contains(origins_in_use_, *origin) || |
| base::Contains(access_notified_origins_, *origin))) { |
| std::move(callback).Run(base::nullopt); |
| } else { |
| std::move(callback).Run(origin); |
| } |
| access_notified_origins_.clear(); |
| |
| is_getting_eviction_origin_ = false; |
| } |
| |
| void QuotaManagerImpl::GetEvictionOrigin(StorageType type, |
| int64_t global_quota, |
| GetOriginCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| // This must not be called while there's an in-flight task. |
| DCHECK(!is_getting_eviction_origin_); |
| is_getting_eviction_origin_ = true; |
| |
| auto did_get_origin_callback = |
| base::BindOnce(&QuotaManagerImpl::DidGetEvictionOrigin, |
| weak_factory_.GetWeakPtr(), std::move(callback)); |
| |
| if (!is_database_bootstrapped_ && !eviction_disabled_) { |
| // Once bootstrapped, GetLRUOrigin will be called. |
| GetGlobalUsage( |
| StorageType::kTemporary, |
| base::BindOnce(&QuotaManagerImpl::BootstrapDatabaseForEviction, |
| weak_factory_.GetWeakPtr(), |
| std::move(did_get_origin_callback))); |
| return; |
| } |
| |
| GetLRUOrigin(type, std::move(did_get_origin_callback)); |
| } |
| |
| void QuotaManagerImpl::EvictOriginData(const url::Origin& origin, |
| StorageType type, |
| StatusCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(io_thread_->BelongsToCurrentThread()); |
| DCHECK_EQ(type, StorageType::kTemporary); |
| |
| eviction_context_.evicted_origin = origin; |
| eviction_context_.evicted_type = type; |
| eviction_context_.evict_origin_data_callback = std::move(callback); |
| |
| DeleteOriginDataInternal( |
| origin, type, AllQuotaClientTypes(), true, |
| base::BindOnce(&QuotaManagerImpl::DidOriginDataEvicted, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void QuotaManagerImpl::GetEvictionRoundInfo( |
| EvictionRoundInfoCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(io_thread_->BelongsToCurrentThread()); |
| LazyInitialize(); |
| EvictionRoundInfoHelper* helper = |
| new EvictionRoundInfoHelper(this, std::move(callback)); |
| helper->Start(); |
| } |
| |
| void QuotaManagerImpl::GetLRUOrigin(StorageType type, |
| GetOriginCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| LazyInitialize(); |
| // This must not be called while there's an in-flight task. |
| DCHECK(lru_origin_callback_.is_null()); |
| lru_origin_callback_ = std::move(callback); |
| if (db_disabled_) { |
| std::move(lru_origin_callback_).Run(base::nullopt); |
| return; |
| } |
| |
| auto origin = std::make_unique<base::Optional<url::Origin>>(); |
| auto* origin_ptr = origin.get(); |
| PostTaskAndReplyWithResultForDBThread( |
| FROM_HERE, |
| base::BindOnce(&GetLRUOriginOnDBThread, type, |
| GetEvictionOriginExceptions(), |
| base::RetainedRef(special_storage_policy_), |
| base::Unretained(origin_ptr)), |
| base::BindOnce(&QuotaManagerImpl::DidGetLRUOrigin, |
| weak_factory_.GetWeakPtr(), std::move(origin))); |
| } |
| |
| void QuotaManagerImpl::DidGetPersistentHostQuota(const std::string& host, |
| const int64_t* quota, |
| bool success) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DidDatabaseWork(success); |
| persistent_host_quota_callbacks_.Run( |
| host, blink::mojom::QuotaStatusCode::kOk, |
| std::min(*quota, kPerHostPersistentQuotaLimit)); |
| } |
| |
| void QuotaManagerImpl::DidSetPersistentHostQuota(const std::string& host, |
| QuotaCallback callback, |
| const int64_t* new_quota, |
| bool success) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DidDatabaseWork(success); |
| std::move(callback).Run( |
| success ? blink::mojom::QuotaStatusCode::kOk |
| : blink::mojom::QuotaStatusCode::kErrorInvalidAccess, |
| *new_quota); |
| } |
| |
| void QuotaManagerImpl::DidGetLRUOrigin( |
| std::unique_ptr<base::Optional<url::Origin>> origin, |
| bool success) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DidDatabaseWork(success); |
| |
| std::move(lru_origin_callback_).Run(*origin); |
| } |
| |
| namespace { |
| void DidGetSettingsThreadAdapter(base::TaskRunner* task_runner, |
| OptionalQuotaSettingsCallback callback, |
| base::Optional<QuotaSettings> settings) { |
| task_runner->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), std::move(settings))); |
| } |
| } // namespace |
| |
| void QuotaManagerImpl::GetQuotaSettings(QuotaSettingsCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (base::TimeTicks::Now() - settings_timestamp_ < |
| settings_.refresh_interval) { |
| std::move(callback).Run(settings_); |
| return; |
| } |
| |
| if (!settings_callbacks_.Add(std::move(callback))) |
| return; |
| |
| // We invoke our clients GetQuotaSettingsFunc on the |
| // UI thread and plumb the resulting value back to this thread. |
| get_settings_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| get_settings_function_, |
| base::BindOnce(&DidGetSettingsThreadAdapter, |
| base::RetainedRef(base::ThreadTaskRunnerHandle::Get()), |
| base::BindOnce(&QuotaManagerImpl::DidGetSettings, |
| weak_factory_.GetWeakPtr())))); |
| } |
| |
| void QuotaManagerImpl::DidGetSettings(base::Optional<QuotaSettings> settings) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!settings) { |
| settings = settings_; |
| settings->refresh_interval = base::TimeDelta::FromMinutes(1); |
| } |
| SetQuotaSettings(*settings); |
| settings_callbacks_.Run(*settings); |
| UMA_HISTOGRAM_MBYTES("Quota.GlobalTemporaryPoolSize", settings->pool_size); |
| LOG_IF(WARNING, settings->pool_size == 0) |
| << "No storage quota provided in QuotaSettings."; |
| } |
| |
| void QuotaManagerImpl::GetStorageCapacity(StorageCapacityCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!storage_capacity_callbacks_.Add(std::move(callback))) |
| return; |
| if (is_incognito_) { |
| GetQuotaSettings( |
| base::BindOnce(&QuotaManagerImpl::ContinueIncognitoGetStorageCapacity, |
| weak_factory_.GetWeakPtr())); |
| return; |
| } |
| base::PostTaskAndReplyWithResult( |
| db_runner_.get(), FROM_HERE, |
| base::BindOnce(&QuotaManagerImpl::CallGetVolumeInfo, get_volume_info_fn_, |
| profile_path_), |
| base::BindOnce(&QuotaManagerImpl::DidGetStorageCapacity, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void QuotaManagerImpl::ContinueIncognitoGetStorageCapacity( |
| const QuotaSettings& settings) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| int64_t current_usage = |
| GetUsageTracker(StorageType::kTemporary)->GetCachedUsage(); |
| current_usage += GetUsageTracker(StorageType::kPersistent)->GetCachedUsage(); |
| int64_t available_space = |
| std::max(INT64_C(0), settings.pool_size - current_usage); |
| DidGetStorageCapacity(std::make_tuple(settings.pool_size, available_space)); |
| } |
| |
| void QuotaManagerImpl::DidGetStorageCapacity( |
| const std::tuple<int64_t, int64_t>& total_and_available) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| int64_t total_space = std::get<0>(total_and_available); |
| int64_t available_space = std::get<1>(total_and_available); |
| cached_disk_stats_for_storage_pressure_ = |
| std::make_tuple(base::TimeTicks::Now(), total_space, available_space); |
| storage_capacity_callbacks_.Run(total_space, available_space); |
| DetermineStoragePressure(total_space, available_space); |
| } |
| |
| void QuotaManagerImpl::DidDatabaseWork(bool success) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| db_disabled_ = !success; |
| } |
| |
| void QuotaManagerImpl::PostTaskAndReplyWithResultForDBThread( |
| const base::Location& from_here, |
| base::OnceCallback<bool(QuotaDatabase*)> task, |
| base::OnceCallback<void(bool)> reply) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Deleting manager will post another task to DB sequence to delete |
| // |database_|, therefore we can be sure that database_ is alive when this |
| // task runs. |
| base::PostTaskAndReplyWithResult( |
| db_runner_.get(), from_here, |
| base::BindOnce(std::move(task), base::Unretained(database_.get())), |
| std::move(reply)); |
| } |
| |
| // static |
| std::tuple<int64_t, int64_t> QuotaManagerImpl::CallGetVolumeInfo( |
| GetVolumeInfoFn get_volume_info_fn, |
| const base::FilePath& path) { |
| if (!base::CreateDirectory(path)) { |
| LOG(WARNING) << "Create directory failed for path" << path.value(); |
| return std::make_tuple<int64_t, int64_t>(0, 0); |
| } |
| int64_t total; |
| int64_t available; |
| std::tie(total, available) = get_volume_info_fn(path); |
| if (total < 0 || available < 0) { |
| LOG(WARNING) << "Unable to get volume info: " << path.value(); |
| return std::make_tuple<int64_t, int64_t>(0, 0); |
| } |
| UMA_HISTOGRAM_MBYTES("Quota.TotalDiskSpace", total); |
| UMA_HISTOGRAM_MBYTES("Quota.AvailableDiskSpace", available); |
| if (total > 0) { |
| UMA_HISTOGRAM_PERCENTAGE("Quota.PercentDiskAvailable", |
| std::min(100, static_cast<int>((available * 100) / total))); |
| } |
| return std::make_tuple(total, available); |
| } |
| |
| // static |
| std::tuple<int64_t, int64_t> QuotaManagerImpl::GetVolumeInfo( |
| const base::FilePath& path) { |
| return std::make_tuple(base::SysInfo::AmountOfTotalDiskSpace(path), |
| base::SysInfo::AmountOfFreeDiskSpace(path)); |
| } |
| |
| } // namespace storage |