blob: 762f88d138f9b01098e9ed91822c4349bc60b193 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// 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 <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <utility>
#include "base/barrier_callback.h"
#include "base/barrier_closure.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/functional/concurrent_callbacks.h"
#include "base/functional/concurrent_closures.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/not_fatal_until.h"
#include "base/numerics/clamped_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/rand_util.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/system/sys_info.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/thread_annotations.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/types/pass_key.h"
#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
#include "components/services/storage/public/cpp/buckets/constants.h"
#include "components/services/storage/public/mojom/quota_client.mojom.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "sql/error_delegate_util.h"
#include "sql/sqlite_result_code.h"
#include "sql/sqlite_result_code_values.h"
#include "storage/browser/quota/client_usage_tracker.h"
#include "storage/browser/quota/quota_availability.h"
#include "storage/browser/quota/quota_callbacks.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_observer.mojom-forward.h"
#include "storage/browser/quota/quota_manager_observer.mojom.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/storage_directory_util.h"
#include "storage/browser/quota/usage_tracker.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h"
#include "third_party/blink/public/mojom/storage_key/storage_key.mojom.h"
#include "url/origin.h"
using ::blink::StorageKey;
namespace storage {
namespace {
// These values are used in UMA, so the list should be append-only.
enum class DatabaseDisabledReason {
kRegisterStorageKeyFailed = 0,
kSetIsBootstrappedFailed = 1,
kRazeFailed = 2,
kMaxValue = kRazeFailed,
};
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::Minutes(5);
// The path where media license data is persisted on disk, relative to the path
// for the respective storage bucket.
constexpr base::FilePath::CharType kMediaLicenseDirectory[] =
FILE_PATH_LITERAL("Media Licenses");
void ReportDatabaseDisabledReason(DatabaseDisabledReason reason) {
base::UmaHistogramEnumeration("Quota.QuotaDatabaseDisabled", reason);
}
void DidGetUsageAndQuotaStripBreakdown(
QuotaManagerImpl::UsageAndQuotaCallback callback,
blink::mojom::QuotaStatusCode status,
int64_t usage,
int64_t quota,
blink::mojom::UsageBreakdownPtr usage_breakdown) {
DCHECK(callback);
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) {
DCHECK(callback);
std::move(callback).Run(status, usage, quota, std::move(usage_breakdown));
}
base::FilePath CreateMediaLicenseBucketPath(const base::FilePath& profile_path,
const BucketLocator& bucket) {
base::FilePath bucket_directory = CreateBucketPath(profile_path, bucket);
return bucket_directory.Append(kMediaLicenseDirectory);
}
int64_t CalculateReportedQuota(int64_t total_space_bytes, int64_t usage_bytes) {
base::ClampedNumeric<int64_t> clamped_total_space_bytes(total_space_bytes);
base::ClampedNumeric<int64_t> clamped_usage_bytes(usage_bytes);
if (clamped_total_space_bytes >= 10 * QuotaManagerImpl::kGBytes) {
return clamped_usage_bytes + 10 * QuotaManagerImpl::kGBytes;
}
// Generally, we report a quota value of 10 GiB. However, if there is less
// than 10 GiB of disk space, the quota value we report is disk space rounded
// up to the nearest 1 GiB.
base::ClampedNumeric<int64_t> rounded_total_space_gib =
(clamped_total_space_bytes / QuotaManagerImpl::kGBytes) +
(clamped_total_space_bytes % QuotaManagerImpl::kGBytes != 0);
return clamped_usage_bytes +
rounded_total_space_gib * QuotaManagerImpl::kGBytes;
}
} // 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::kSyncableStorageDefaultStorageKeyQuota =
500 * kMBytes;
QuotaManagerImpl::QuotaOverride::QuotaOverride() = default;
QuotaManagerImpl::QuotaOverride::~QuotaOverride() = default;
class QuotaManagerImpl::UsageAndQuotaInfoGatherer : public QuotaTask {
public:
UsageAndQuotaInfoGatherer(
QuotaManagerImpl* manager,
const StorageKey& storage_key,
bool is_incognito,
UsageAndQuotaWithBreakdownAndOverrideFlagCallback callback)
: QuotaTask(manager),
storage_key_(storage_key),
callback_(std::move(callback)),
is_unlimited_(manager->IsStorageUnlimited(storage_key_)),
is_incognito_(is_incognito) {
DCHECK(manager);
DCHECK(callback_);
}
UsageAndQuotaInfoGatherer(
QuotaManagerImpl* manager,
const BucketInfo& bucket_info,
bool is_incognito,
UsageAndQuotaWithBreakdownAndOverrideFlagCallback callback)
: UsageAndQuotaInfoGatherer(manager,
bucket_info.storage_key,
is_incognito,
std::move(callback)) {
bucket_info_ = bucket_info;
}
protected:
void Run() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Start the async process of gathering the info we need.
// Gather info before computing an answer:
// settings, host_usage, storage_key_quota and device_storage_capacity if
// unlimited.
base::ConcurrentClosures concurrent;
manager()->GetQuotaSettings(
base::BindOnce(&UsageAndQuotaInfoGatherer::OnGotSettings,
weak_factory_.GetWeakPtr(), concurrent.CreateClosure()));
if (bucket_info_) {
manager()->GetBucketUsageWithBreakdown(
bucket_info_->ToBucketLocator(),
base::BindOnce(&UsageAndQuotaInfoGatherer::OnGotUsage,
weak_factory_.GetWeakPtr(),
concurrent.CreateClosure()));
} else {
manager()->GetStorageKeyUsageWithBreakdown(
storage_key_, base::BindOnce(&UsageAndQuotaInfoGatherer::OnGotUsage,
weak_factory_.GetWeakPtr(),
concurrent.CreateClosure()));
}
// Determine storage_key_quota differently depending on type.
if (is_unlimited_) {
SetDesiredStorageKeyQuota(kNoLimit);
manager()->GetStorageCapacity(base::BindOnce(
&UsageAndQuotaInfoGatherer::OnGotCapacity, weak_factory_.GetWeakPtr(),
concurrent.CreateClosure()));
} else {
// For limited storage, OnGotSettings will set the host quota.
}
std::move(concurrent)
.Done(base::BindOnce(&UsageAndQuotaInfoGatherer::CallCompleted,
weak_factory_.GetWeakPtr()));
}
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 quota = desired_storage_key_quota_;
std::optional<int64_t> quota_override_size =
manager()->GetQuotaOverrideForStorageKey(storage_key_);
if (quota_override_size) {
quota = *quota_override_size;
}
// For an individual bucket, the quota is the minimum of the requested quota
// and the StorageKey quota.
if (bucket_info_ && bucket_info_->quota > 0) {
quota = std::min(quota, bucket_info_->quota);
}
if (is_unlimited_) {
int64_t temp_pool_free_space =
available_space_ - settings_.must_remain_available;
// Constrain the desired quota to something that fits.
if (quota > temp_pool_free_space) {
quota = available_space_ + usage_;
}
}
std::move(callback_).Run(usage_ >= 0
? blink::mojom::QuotaStatusCode::kOk
: blink::mojom::QuotaStatusCode::kUnknown,
usage_, quota, quota_override_size.has_value(),
std::move(usage_breakdown_));
if (!is_incognito_ && !is_unlimited_ && !bucket_info_) {
UMA_HISTOGRAM_MBYTES("Quota.QuotaForOrigin", quota);
UMA_HISTOGRAM_MBYTES("Quota.UsageByOrigin", usage_);
if (quota > 0) {
UMA_HISTOGRAM_PERCENTAGE(
"Quota.PercentUsedByOrigin",
std::min(100, static_cast<int>((usage_ * 100) / quota)));
}
}
DeleteSoon();
}
private:
QuotaManagerImpl* manager() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return static_cast<QuotaManagerImpl*>(observer());
}
void OnGotSettings(base::OnceClosure callback,
const QuotaSettings& settings) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
settings_ = settings;
const int64_t quota =
manager()->GetQuotaForStorageKey(storage_key_, settings);
if (quota != kNoLimit) {
SetDesiredStorageKeyQuota(quota);
}
std::move(callback).Run();
}
void OnGotCapacity(base::OnceClosure callback,
int64_t total_space,
int64_t available_space) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
DCHECK_GE(total_space, 0);
DCHECK_GE(available_space, 0);
total_space_ = total_space;
available_space_ = available_space;
std::move(callback).Run();
}
void OnGotUsage(base::OnceClosure callback,
int64_t usage,
blink::mojom::UsageBreakdownPtr usage_breakdown) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
DCHECK_GE(usage, -1);
DCHECK(usage_breakdown);
DCHECK_GE(usage_breakdown->backgroundFetch, 0);
DCHECK_GE(usage_breakdown->fileSystem, 0);
DCHECK_GE(usage_breakdown->indexedDatabase, 0);
DCHECK_GE(usage_breakdown->serviceWorker, 0);
DCHECK_GE(usage_breakdown->serviceWorkerCache, 0);
DCHECK_GE(usage_breakdown->webSql, 0);
usage_ = usage;
usage_breakdown_ = std::move(usage_breakdown);
std::move(callback).Run();
}
void SetDesiredStorageKeyQuota(int64_t quota) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GE(quota, 0);
desired_storage_key_quota_ = quota;
}
// These fields are passed at construction time.
const StorageKey storage_key_;
// Non-null iff usage info is to be gathered for an individual bucket. If
// null, usage is gathered for all buckets in the given host/StorageKey.
std::optional<BucketInfo> bucket_info_;
QuotaManagerImpl::UsageAndQuotaWithBreakdownAndOverrideFlagCallback callback_;
const bool is_unlimited_;
const bool is_incognito_;
// Fields retrieved while running.
int64_t available_space_ = 0;
int64_t total_space_ = 0;
int64_t desired_storage_key_quota_ = 0;
int64_t usage_ = 0;
blink::mojom::UsageBreakdownPtr 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:
// `callback` is called when the helper successfully retrieves quota settings
// and capacity data. `completion_closure` is called after data has been
// retrieved and `callback` has been run and to clean up itself and delete
// this EvictionRoundInfoHelper instance.
EvictionRoundInfoHelper(QuotaManagerImpl* manager,
EvictionRoundInfoCallback callback,
base::OnceClosure completion_closure)
: manager_(manager),
callback_(std::move(callback)),
completion_closure_(std::move(completion_closure)) {
DCHECK(manager_);
DCHECK(callback_);
DCHECK(completion_closure_);
}
void Run() {
#if DCHECK_IS_ON()
DCHECK(!run_called_) << __func__ << " already called";
run_called_ = true;
#endif // DCHECK_IS_ON()
// 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));
}
private:
void Completed() {
#if DCHECK_IS_ON()
DCHECK(!completed_called_) << __func__ << " already called";
completed_called_ = true;
#endif // DCHECK_IS_ON()
std::move(callback_).Run(blink::mojom::QuotaStatusCode::kOk, settings_,
available_space_, total_space_, global_usage_,
global_usage_is_complete_);
// May delete `this`.
std::move(completion_closure_).Run();
}
void OnGotSettings(base::OnceClosure barrier_closure,
const QuotaSettings& settings) {
DCHECK(barrier_closure);
settings_ = settings;
std::move(barrier_closure).Run();
}
void OnGotCapacity(base::OnceClosure barrier_closure,
int64_t total_space,
int64_t available_space) {
DCHECK(barrier_closure);
DCHECK_GE(total_space, 0);
DCHECK_GE(available_space, 0);
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()->GetCachedUsage();
// `this` may be deleted during this Complete() call.
Completed();
return;
}
manager_->GetGlobalUsage(
base::BindOnce(&EvictionRoundInfoHelper::OnGotGlobalUsage,
weak_factory_.GetWeakPtr()));
}
void OnGotGlobalUsage(int64_t usage, int64_t unlimited_usage) {
global_usage_ = std::max(int64_t{0}, usage - unlimited_usage);
global_usage_is_complete_ = true;
// `this` may be deleted during this Complete() call.
Completed();
}
const raw_ptr<QuotaManagerImpl> manager_;
EvictionRoundInfoCallback callback_;
base::OnceClosure completion_closure_;
QuotaSettings settings_;
int64_t available_space_ = 0;
int64_t total_space_ = 0;
int64_t global_usage_ = 0;
bool global_usage_is_complete_ = false;
#if DCHECK_IS_ON()
bool run_called_ = false;
bool completed_called_ = false;
#endif // DCHECK_IS_ON()
base::WeakPtrFactory<EvictionRoundInfoHelper> weak_factory_{this};
};
class QuotaManagerImpl::GetUsageInfoTask : public QuotaTask {
public:
GetUsageInfoTask(QuotaManagerImpl* manager, GetUsageInfoCallback callback)
: QuotaTask(manager), callback_(std::move(callback)) {
DCHECK(manager);
DCHECK(callback_);
}
protected:
void Run() override {
// This will populate cached hosts and usage info.
manager()->GetUsageTracker()->GetGlobalUsage(base::BindOnce(
&GetUsageInfoTask::DidGetGlobalUsage, weak_factory_.GetWeakPtr()));
}
void Completed() override {
std::move(callback_).Run(std::move(entries_));
DeleteSoon();
}
private:
void AddEntries(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, host_usage_pair.second);
}
CallCompleted();
}
void DidGetGlobalUsage(int64_t, int64_t) {
UsageTracker* tracker = manager()->GetUsageTracker();
DCHECK(tracker);
AddEntries(*tracker);
}
QuotaManagerImpl* manager() const {
return static_cast<QuotaManagerImpl*>(observer());
}
GetUsageInfoCallback callback_;
UsageInfoEntries entries_;
base::WeakPtrFactory<GetUsageInfoTask> weak_factory_{this};
};
class QuotaManagerImpl::StorageKeyGathererTask {
public:
StorageKeyGathererTask(
QuotaManagerImpl* manager,
base::OnceCallback<void(std::set<StorageKey>)> callback)
: manager_(manager), callback_(std::move(callback)) {
DCHECK(manager_);
DCHECK(callback_);
}
void Run() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
DCHECK(!run_called_) << __func__ << " already called";
run_called_ = true;
#endif // DCHECK_IS_ON()
base::ConcurrentClosures concurrent;
for (const auto& client_and_type : manager_->client_types_) {
client_and_type.first->GetDefaultStorageKeys(base::BindOnce(
&StorageKeyGathererTask::DidGetStorageKeys,
weak_factory_.GetWeakPtr(), concurrent.CreateClosure()));
}
std::move(concurrent)
.Done(
base::BindOnce(&QuotaManagerImpl::StorageKeyGathererTask::Completed,
weak_factory_.GetWeakPtr()));
}
private:
void DidGetStorageKeys(base::OnceClosure callback,
const std::vector<StorageKey>& storage_keys) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
storage_keys_.insert(storage_keys.begin(), storage_keys.end());
std::move(callback).Run();
}
void Completed() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
DCHECK(!completed_called_) << __func__ << " already called";
completed_called_ = true;
#endif // DCHECK_IS_ON()
// Deletes `this`.
std::move(callback_).Run(storage_keys_);
}
SEQUENCE_CHECKER(sequence_checker_);
raw_ptr<QuotaManagerImpl> manager_ GUARDED_BY_CONTEXT(sequence_checker_);
base::OnceCallback<void(std::set<StorageKey>)> callback_
GUARDED_BY_CONTEXT(sequence_checker_);
std::set<StorageKey> storage_keys_ GUARDED_BY_CONTEXT(sequence_checker_);
#if DCHECK_IS_ON()
bool run_called_ = false;
bool completed_called_ = false;
#endif // DCHECK_IS_ON()
base::WeakPtrFactory<StorageKeyGathererTask> weak_factory_
GUARDED_BY_CONTEXT(sequence_checker_){this};
};
// Calls QuotaClients in `quota_client_types` for the `bucket` to delete bucket
// data. Will delete bucket entries from the QuotaDatabase if bucket data has
// been successfully deleted from all registered QuotaClient.
// This is currently only for the default bucket. If a non-default bucket is to
// be deleted, it will immediately complete the task since non-default bucket
// usage is not being tracked by QuotaClients yet.
class QuotaManagerImpl::BucketDataDeleter {
public:
// `callback` will be run to report the status of the deletion on task
// completion. `callback` will only be called while this BucketDataDeleter
// instance is alive. `callback` may destroy this BucketDataDeleter instance.
BucketDataDeleter(
QuotaManagerImpl* manager,
const BucketLocator& bucket,
QuotaClientTypes quota_client_types,
bool commit_immediately,
base::OnceCallback<void(BucketDataDeleter*,
QuotaErrorOr<mojom::BucketTableEntryPtr>)>
callback)
: manager_(manager),
bucket_(bucket),
quota_client_types_(std::move(quota_client_types)),
commit_immediately_(commit_immediately),
callback_(std::move(callback)) {
DCHECK(manager_);
// TODO(crbug.com/40058632): Convert back into DCHECKs once issue is
// resolved.
CHECK(callback_);
}
~BucketDataDeleter() {
// `callback` is non-null if the deleter gets destroyed before completing.
if (callback_) {
std::move(callback_).Run(this,
base::unexpected(QuotaError::kUnknownError));
}
}
void Run() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
// TODO(crbug.com/40058632): Convert back into DCHECK once issue is
// resolved.
CHECK(!run_called_) << __func__ << " already called";
run_called_ = true;
#endif // DCHECK_IS_ON()
remaining_clients_ = manager_->client_types_.size();
UsageTracker* usage_tracker = manager_->GetUsageTracker();
for (const auto& client_and_type : manager_->client_types_) {
mojom::QuotaClient* client = client_and_type.first;
QuotaClientType client_type = client_and_type.second;
if (quota_client_types_.contains(client_type)) {
// Delete cached usage.
usage_tracker->DeleteBucketCache(client_type, bucket_);
static int tracing_id = 0;
std::string bucket_params = base::StrCat(
{"storage_key: ", bucket_.storage_key.Serialize(),
", is_default: ", bucket_.is_default ? "true" : "false",
", id: ", base::NumberToString(bucket_.id.value())});
TRACE_EVENT_NESTABLE_ASYNC_BEGIN2(
"browsing_data", "QuotaManagerImpl::BucketDataDeleter",
++tracing_id, "client_type", client_type, "bucket", bucket_params);
client->DeleteBucketData(
bucket_, base::BindOnce(&BucketDataDeleter::DidDeleteBucketData,
weak_factory_.GetWeakPtr(), tracing_id));
} else {
++skipped_clients_;
--remaining_clients_;
}
}
if (remaining_clients_ == 0) {
FinishDeletion();
}
}
bool completed() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return !callback_;
}
private:
void DidDeleteBucketData(int tracing_id,
blink::mojom::QuotaStatusCode status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GT(remaining_clients_, 0u);
TRACE_EVENT_NESTABLE_ASYNC_END0(
"browsing_data", "QuotaManagerImpl::BucketDataDeleter", tracing_id);
if (status != blink::mojom::QuotaStatusCode::kOk) {
++error_count_;
}
if (--remaining_clients_ == 0) {
FinishDeletion();
}
}
void FinishDeletion() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(crbug.com/40058632): Convert back into DCHECKs once issue is
// resolved.
CHECK_EQ(remaining_clients_, 0u);
CHECK(callback_) << __func__ << " called after Complete";
// Only remove the bucket from the database if we didn't skip any client
// types.
if (skipped_clients_ == 0 && error_count_ == 0) {
manager_->DeleteBucketFromDatabase(
bucket_, commit_immediately_,
base::BindOnce(&BucketDataDeleter::DidDeleteBucketFromDatabase,
weak_factory_.GetWeakPtr()));
return;
}
if (error_count_ == 0) {
Complete(base::ok(nullptr));
} else {
Complete(base::unexpected(QuotaError::kUnknownError));
}
}
void DidDeleteBucketFromDatabase(
QuotaErrorOr<mojom::BucketTableEntryPtr> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Complete(std::move(result));
}
void Complete(QuotaErrorOr<mojom::BucketTableEntryPtr> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(crbug.com/40058632): Convert back into DCHECKs once issue is
// resolved.
CHECK_EQ(remaining_clients_, 0u);
CHECK(callback_);
// May delete `this`.
std::move(callback_).Run(this, std::move(result));
}
SEQUENCE_CHECKER(sequence_checker_);
const raw_ptr<QuotaManagerImpl> manager_
GUARDED_BY_CONTEXT(sequence_checker_);
const BucketLocator bucket_;
const QuotaClientTypes quota_client_types_;
// Whether the update to the database should be committed immediately (if not,
// it will be scheduled to be committed as part of a batch).
const bool commit_immediately_;
int error_count_ GUARDED_BY_CONTEXT(sequence_checker_) = 0;
size_t remaining_clients_ GUARDED_BY_CONTEXT(sequence_checker_) = 0;
int skipped_clients_ GUARDED_BY_CONTEXT(sequence_checker_) = 0;
// Running the callback may delete this instance.
base::OnceCallback<void(BucketDataDeleter*,
QuotaErrorOr<mojom::BucketTableEntryPtr>)>
callback_ GUARDED_BY_CONTEXT(sequence_checker_);
#if DCHECK_IS_ON()
bool run_called_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
#endif // DCHECK_IS_ON()
base::WeakPtrFactory<BucketDataDeleter> weak_factory_
GUARDED_BY_CONTEXT(sequence_checker_){this};
};
// Deletes a set of buckets.
class QuotaManagerImpl::BucketSetDataDeleter {
public:
// `callback` will be run to report the status of the deletion on task
// completion. `callback` will only be called while this BucketSetDataDeleter
// instance is alive. `callback` may destroy this BucketSetDataDeleter
// instance.
BucketSetDataDeleter(
QuotaManagerImpl* manager,
base::OnceCallback<void(BucketSetDataDeleter*,
blink::mojom::QuotaStatusCode)> callback)
: manager_(manager), callback_(std::move(callback)) {
DCHECK(manager_);
DCHECK(callback_);
}
~BucketSetDataDeleter() {
if (callback_) {
std::move(callback_).Run(this,
blink::mojom::QuotaStatusCode::kErrorAbort);
}
}
base::OnceCallback<void(QuotaErrorOr<std::set<BucketInfo>>)>
GetBucketDeletionCallback() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
DCHECK(!started_) << __func__ << " already called";
started_ = true;
#endif // DCHECK_IS_ON()
return base::BindOnce(&BucketSetDataDeleter::DidGetBuckets,
weak_factory_.GetWeakPtr());
}
bool completed() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return !callback_;
}
private:
void DidGetBuckets(QuotaErrorOr<std::set<BucketInfo>> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (result.has_value()) {
buckets_ = BucketInfosToBucketLocators(result.value());
if (!buckets_.empty()) {
ScheduleBucketsDeletion();
return;
}
}
Complete(/*success=*/result.has_value());
}
void ScheduleBucketsDeletion() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (const auto& bucket : buckets_) {
// Weak pointer is used here because the callback will be invoked when
// `bucket_deleters_` is being destroyed during `BucketSetDataDeleter`
// destruction. In this case, we don't need `DidDeleteBucketData` to be
// called. See crbug.com/373754859
auto bucket_deleter = std::make_unique<BucketDataDeleter>(
manager_, bucket, AllQuotaClientTypes(), /*commit_immediately=*/false,
base::BindOnce(&BucketSetDataDeleter::DidDeleteBucketData,
weak_factory_.GetWeakPtr()));
auto* bucket_deleter_ptr = bucket_deleter.get();
bucket_deleters_[bucket_deleter_ptr] = std::move(bucket_deleter);
bucket_deleter_ptr->Run();
}
}
void DidDeleteBucketData(BucketDataDeleter* deleter,
QuotaErrorOr<mojom::BucketTableEntryPtr> entry) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(deleter->completed());
DCHECK(base::Contains(bucket_deleters_, deleter));
bucket_deleters_.erase(deleter);
if (!entry.has_value()) {
++error_count_;
}
if (bucket_deleters_.empty()) {
Complete(/*success=*/error_count_ == 0);
}
}
void Complete(bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback_);
// May delete `this`.
std::move(callback_).Run(
this, success
? blink::mojom::QuotaStatusCode::kOk
: blink::mojom::QuotaStatusCode::kErrorInvalidModification);
}
SEQUENCE_CHECKER(sequence_checker_);
const raw_ptr<QuotaManagerImpl> manager_
GUARDED_BY_CONTEXT(sequence_checker_);
std::map<BucketDataDeleter*, std::unique_ptr<BucketDataDeleter>>
bucket_deleters_;
std::set<BucketLocator> buckets_ GUARDED_BY_CONTEXT(sequence_checker_);
int error_count_ GUARDED_BY_CONTEXT(sequence_checker_) = 0;
base::OnceCallback<void(BucketSetDataDeleter*, blink::mojom::QuotaStatusCode)>
callback_ GUARDED_BY_CONTEXT(sequence_checker_);
#if DCHECK_IS_ON()
bool started_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
#endif // DCHECK_IS_ON()
base::WeakPtrFactory<BucketSetDataDeleter> weak_factory_{this};
};
class QuotaManagerImpl::StorageCleanupHelper : public QuotaTask {
public:
StorageCleanupHelper(QuotaManagerImpl* manager,
QuotaClientTypes quota_client_types,
base::OnceClosure callback)
: QuotaTask(manager),
quota_client_types_(std::move(quota_client_types)),
callback_(std::move(callback)) {
DCHECK(manager);
DCHECK(callback_);
}
protected:
void Run() override {
base::ConcurrentClosures concurrent;
for (const auto& client_and_type : manager()->client_types_) {
mojom::QuotaClient* client = client_and_type.first;
QuotaClientType client_type = client_and_type.second;
if (quota_client_types_.contains(client_type)) {
client->PerformStorageCleanup(concurrent.CreateClosure());
}
}
std::move(concurrent)
.Done(base::BindOnce(&StorageCleanupHelper::CallCompleted,
weak_factory_.GetWeakPtr()));
}
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 QuotaClientTypes quota_client_types_;
base::OnceClosure callback_;
base::WeakPtrFactory<StorageCleanupHelper> weak_factory_{this};
};
// Gather storage key info table for quota-internals page.
//
// This class is granted ownership of itself when it is passed to
// DidDumpBucketTable() 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::DumpBucketTableHelper {
public:
QuotaError DumpBucketTableOnDBThread(QuotaDatabase* database) {
DCHECK(database);
return database->DumpBucketTable(base::BindRepeating(
&DumpBucketTableHelper::AppendEntry, base::Unretained(this)));
}
void DidDumpBucketTable(const base::WeakPtr<QuotaManagerImpl>& manager,
DumpBucketTableCallback callback,
QuotaError error) {
if (!manager) {
// The operation was aborted.
std::move(callback).Run(BucketTableEntries());
return;
}
std::move(callback).Run(std::move(entries_));
}
private:
bool AppendEntry(mojom::BucketTableEntryPtr entry) {
entries_.push_back(std::move(entry));
return true;
}
BucketTableEntries entries_;
};
// QuotaManagerImpl -----------------------------------------------------------
QuotaManagerImpl::QuotaManagerImpl(
bool is_incognito,
const base::FilePath& profile_path,
scoped_refptr<base::SingleThreadTaskRunner> io_thread,
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,
profile_path)),
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),
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::SingleThreadTaskRunner::GetCurrentDefault();
}
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::UpdateOrCreateBucket(
const BucketInitParams& bucket_params,
base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
return;
}
if (!bucket_params.expiration.is_null() &&
(bucket_params.expiration <= QuotaDatabase::GetNow())) {
std::move(callback).Run(base::unexpected(QuotaError::kInvalidExpiration));
return;
}
last_opened_bucket_site_ = bucket_params.storage_key;
// The default bucket skips the quota check.
if (bucket_params.name == kDefaultBucketName) {
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](const BucketInitParams& params, QuotaDatabase* database) {
DCHECK(database);
return database->UpdateOrCreateBucket(params,
/*max_bucket_count=*/0);
},
bucket_params),
base::BindOnce(&QuotaManagerImpl::DidGetBucketCheckExpiration,
weak_factory_.GetWeakPtr(), bucket_params,
std::move(callback)));
return;
}
GetQuotaSettings(
base::BindOnce(&QuotaManagerImpl::DidGetQuotaSettingsForBucketCreation,
weak_factory_.GetWeakPtr(), std::move(bucket_params),
std::move(callback)));
}
void QuotaManagerImpl::CreateBucketForTesting(
const StorageKey& storage_key,
const std::string& bucket_name,
base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
return;
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](const StorageKey& storage_key, const std::string& bucket_name,
QuotaDatabase* database) {
DCHECK(database);
return database->CreateBucketForTesting( // IN-TEST
storage_key, bucket_name);
},
storage_key, bucket_name),
base::BindOnce(&QuotaManagerImpl::DidGetBucket,
weak_factory_.GetWeakPtr(), /*notify_update_bucket=*/true,
std::move(callback)));
}
void QuotaManagerImpl::GetBucketByNameUnsafe(
const StorageKey& storage_key,
const std::string& bucket_name,
base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
return;
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](const StorageKey& storage_key, const std::string& bucket_name,
QuotaDatabase* database) {
DCHECK(database);
return database->GetBucket(storage_key, bucket_name);
},
storage_key, bucket_name),
base::BindOnce(&QuotaManagerImpl::DidGetBucket,
weak_factory_.GetWeakPtr(), /*notify_update_bucket=*/false,
std::move(callback)));
}
void QuotaManagerImpl::GetBucketById(
const BucketId& bucket_id,
base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
return;
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](const BucketId bucket_id, QuotaDatabase* database) {
DCHECK(database);
return database->GetBucketById(bucket_id);
},
bucket_id),
base::BindOnce(&QuotaManagerImpl::DidGetBucket,
weak_factory_.GetWeakPtr(), /*notify_update_bucket=*/false,
std::move(callback)));
}
void QuotaManagerImpl::GetAllStorageKeys(GetStorageKeysCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(std::set<StorageKey>());
return;
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce([](QuotaDatabase* database) {
DCHECK(database);
return database->GetAllStorageKeys();
}),
base::BindOnce(&QuotaManagerImpl::DidGetStorageKeys,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void QuotaManagerImpl::GetAllBuckets(
base::OnceCallback<void(QuotaErrorOr<std::set<BucketInfo>>)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
return;
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce([](QuotaDatabase* database) {
DCHECK(database);
return database->GetAllBuckets();
}),
base::BindOnce(&QuotaManagerImpl::DidGetBuckets,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void QuotaManagerImpl::GetBucketsForHost(
const std::string& host,
base::OnceCallback<void(QuotaErrorOr<std::set<BucketInfo>>)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
return;
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](const std::string& host, QuotaDatabase* database) {
DCHECK(database);
return database->GetBucketsForHost(host);
},
host),
base::BindOnce(&QuotaManagerImpl::DidGetBuckets,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void QuotaManagerImpl::GetBucketsForStorageKey(
const StorageKey& storage_key,
base::OnceCallback<void(QuotaErrorOr<std::set<BucketInfo>>)> callback,
bool delete_expired) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
return;
}
base::OnceCallback<void(QuotaErrorOr<std::set<BucketInfo>>)> reply;
if (delete_expired) {
reply = base::BindOnce(&QuotaManagerImpl::DidGetBucketsCheckExpiration,
weak_factory_.GetWeakPtr(), std::move(callback));
} else {
reply = base::BindOnce(&QuotaManagerImpl::DidGetBuckets,
weak_factory_.GetWeakPtr(), std::move(callback));
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](const StorageKey& storage_key, QuotaDatabase* database) {
DCHECK(database);
return database->GetBucketsForStorageKey(storage_key);
},
storage_key),
std::move(reply));
}
void QuotaManagerImpl::GetUsageInfo(GetUsageInfoCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
GetUsageInfoTask* get_usage_info =
new GetUsageInfoTask(this, std::move(callback));
get_usage_info->Start();
}
void QuotaManagerImpl::GetUsageAndQuotaForWebApps(
const StorageKey& storage_key,
UsageAndQuotaCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
GetUsageAndQuotaWithBreakdown(
storage_key,
base::BindOnce(&DidGetUsageAndQuotaStripBreakdown, std::move(callback)));
}
void QuotaManagerImpl::GetUsageAndQuotaWithBreakdown(
const StorageKey& storage_key,
UsageAndQuotaWithBreakdownCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
HandleGetUsageAndQuotaRequest(
storage_key,
base::BindOnce(&DidGetUsageAndQuotaStripOverride, std::move(callback)));
}
void QuotaManagerImpl::GetUsageAndReportedQuotaWithBreakdown(
const StorageKey& storage_key,
UsageAndQuotaWithBreakdownCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
if (base::FeatureList::IsEnabled(storage::features::kStaticStorageQuota) &&
!IsStorageUnlimited(storage_key)) {
HandleGetUsageAndQuotaRequest(
storage_key,
base::BindOnce(
[](base::WeakPtr<QuotaManagerImpl> weak_this,
UsageAndQuotaWithBreakdownCallback callback,
blink::mojom::QuotaStatusCode status, int64_t usage,
int64_t quota, bool is_override_enabled,
blink::mojom::UsageBreakdownPtr usage_breakdown) {
DCHECK(callback);
if (!weak_this) {
std::move(callback).Run(blink::mojom::QuotaStatusCode::kUnknown,
0, 0, std::move(usage_breakdown));
return;
}
if (status != blink::mojom::QuotaStatusCode::kOk) {
std::move(callback).Run(status, 0, 0,
std::move(usage_breakdown));
return;
}
weak_this->GetDiskAvailabilityAndTempPoolSize(base::BindOnce(
[](UsageAndQuotaWithBreakdownCallback callback,
blink::mojom::QuotaStatusCode status, int64_t usage,
blink::mojom::UsageBreakdownPtr usage_breakdown,
int64_t total_space, int64_t available_space,
int64_t temp_pool_size) {
int64_t reported_quota =
CalculateReportedQuota(total_space, usage);
std::move(callback).Run(status, usage, reported_quota,
std::move(usage_breakdown));
},
std::move(callback), status, usage,
std::move(usage_breakdown)));
},
weak_factory_.GetWeakPtr(), std::move(callback)));
return;
}
HandleGetUsageAndQuotaRequest(
storage_key,
base::BindOnce(&DidGetUsageAndQuotaStripOverride, std::move(callback)));
}
void QuotaManagerImpl::GetUsageAndQuotaForDevtools(
const StorageKey& storage_key,
UsageAndQuotaWithBreakdownAndOverrideFlagCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
HandleGetUsageAndQuotaRequest(storage_key, std::move(callback));
}
void QuotaManagerImpl::HandleGetUsageAndQuotaRequest(
const StorageKey& storage_key,
UsageAndQuotaWithBreakdownAndOverrideFlagCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
UsageAndQuotaInfoGatherer* helper = new UsageAndQuotaInfoGatherer(
this, storage_key, is_incognito_, std::move(callback));
helper->Start();
}
void QuotaManagerImpl::GetUsageAndQuota(const StorageKey& storage_key,
UsageAndQuotaCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
if (IsStorageUnlimited(storage_key)) {
// 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;
}
GetUsageAndQuotaWithBreakdown(
storage_key,
base::BindOnce(&DidGetUsageAndQuotaStripBreakdown, std::move(callback)));
}
void QuotaManagerImpl::GetBucketUsageAndQuota(BucketId id,
UsageAndQuotaCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
GetBucketById(
id, base::BindOnce(&QuotaManagerImpl::DidGetBucketForUsageAndQuota,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void QuotaManagerImpl::GetBucketUsageAndReportedQuota(
BucketId id,
UsageAndQuotaCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (base::FeatureList::IsEnabled(storage::features::kStaticStorageQuota)) {
GetBucketById(
id,
base::BindOnce(
[](base::WeakPtr<QuotaManagerImpl> weak_this,
UsageAndQuotaCallback callback,
QuotaErrorOr<BucketInfo> result) {
if (!weak_this || !result.has_value()) {
std::move(callback).Run(blink::mojom::QuotaStatusCode::kUnknown,
0, 0);
return;
}
const BucketInfo& bucket = result.value();
bool is_storage_unlimited =
weak_this->IsStorageUnlimited(bucket.storage_key);
UsageAndQuotaInfoGatherer* helper = new UsageAndQuotaInfoGatherer(
weak_this.get(), bucket, weak_this->is_incognito_,
base::BindOnce(
[](base::WeakPtr<QuotaManagerImpl> weak_this,
UsageAndQuotaCallback callback,
const BucketInfo& bucket, bool is_storage_unlimited,
blink::mojom::QuotaStatusCode status, int64_t usage,
int64_t quota, bool is_override_enabled,
blink::mojom::UsageBreakdownPtr usage_breakdown) {
DCHECK(callback);
if (!weak_this) {
std::move(callback).Run(
blink::mojom::QuotaStatusCode::kUnknown, 0, 0);
return;
}
// If storage is unlimited, return the real quota value.
if (is_storage_unlimited) {
std::move(callback).Run(status, usage, quota);
return;
}
// If there was a requested bucket quota, return that
// value regardless of whether it was capped at the
// StorageKey quota or not.
if (bucket.quota > 0) {
std::move(callback).Run(status, usage, bucket.quota);
return;
}
if (status != blink::mojom::QuotaStatusCode::kOk) {
std::move(callback).Run(status, 0, 0);
return;
}
weak_this->GetDiskAvailabilityAndTempPoolSize(
base::BindOnce(
[](UsageAndQuotaCallback callback,
blink::mojom::QuotaStatusCode status,
int64_t usage, int64_t total_space,
int64_t available_space,
int64_t temp_pool_size) {
int64_t reported_quota =
CalculateReportedQuota(total_space,
usage);
std::move(callback).Run(status, usage,
reported_quota);
},
std::move(callback), status, usage));
},
weak_this, std::move(callback), bucket,
is_storage_unlimited));
helper->Start();
},
weak_factory_.GetWeakPtr(), std::move(callback)));
return;
}
GetBucketUsageAndQuota(id, std::move(callback));
}
void QuotaManagerImpl::GetBucketSpaceRemaining(
const BucketLocator& bucket,
base::OnceCallback<void(QuotaErrorOr<int64_t>)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// ConcurrentCallbacks is run once with each space restriction --- the
// StorageKey usage/quota and the bucket's usage/quota (if it exists). The
// final value is the more restrictive of the two.
auto aggregator = base::BindOnce(
[](base::OnceCallback<void(QuotaErrorOr<int64_t>)> final_space_remaining,
std::vector<int64_t> space_checks) {
int64_t space_left =
*std::min_element(space_checks.begin(), space_checks.end());
if (space_left == std::numeric_limits<int64_t>::min()) {
std::move(final_space_remaining)
.Run(base::unexpected(QuotaError::kUnknownError));
} else {
std::move(final_space_remaining).Run(space_left);
}
},
std::move(callback));
base::ConcurrentCallbacks<int64_t> concurrent;
// Translates a UsageAndQuota result into a single number for the aggregator.
auto on_got_usage =
[](base::OnceCallback<void(int64_t)> report_space_remaining,
blink::mojom::QuotaStatusCode code, int64_t usage, int64_t quota) {
// Report the amount of allocated space remaining, or min() for an
// error, or max() if there's no limit.
int64_t leftover_space = 0;
if (code != blink::mojom::QuotaStatusCode::kOk) {
leftover_space = std::numeric_limits<int64_t>::min();
} else if (quota == 0) {
leftover_space = kNoLimit;
} else {
leftover_space = quota - usage;
}
std::move(report_space_remaining).Run(leftover_space);
};
// Check the usage for the whole StorageKey.
GetUsageAndQuota(bucket.storage_key,
base::BindOnce(on_got_usage, concurrent.CreateCallback()));
// If this is the default bucket, we're done. Otherwise, additionally check
// the usage of the specific bucket against its quota.
if (!bucket.is_default) {
GetBucketUsageAndQuota(
bucket.id, base::BindOnce(on_got_usage, concurrent.CreateCallback()));
}
std::move(concurrent).Done(std::move(aggregator));
}
void QuotaManagerImpl::OnClientWriteFailed(const StorageKey& storage_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
OnFullDiskError(storage_key);
}
void QuotaManagerImpl::SetUsageCacheEnabled(QuotaClientType client_id,
const StorageKey& storage_key,
bool enabled) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureDatabaseOpened();
UsageTracker* usage_tracker = GetUsageTracker();
DCHECK(usage_tracker);
usage_tracker->SetUsageCacheEnabled(client_id, storage_key, enabled);
}
void QuotaManagerImpl::DeleteBucketData(const BucketLocator& bucket,
QuotaClientTypes quota_client_types,
StatusCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
auto result_callback = base::BindOnce(
[](StatusCallback callback,
QuotaErrorOr<mojom::BucketTableEntryPtr> result) {
std::move(callback).Run(result.has_value()
? blink::mojom::QuotaStatusCode::kOk
: blink::mojom::QuotaStatusCode::kUnknown);
},
std::move(callback));
DeleteBucketDataInternal(bucket, std::move(quota_client_types),
std::move(result_callback));
}
void QuotaManagerImpl::FindAndDeleteBucketData(const StorageKey& storage_key,
const std::string& bucket_name,
StatusCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(blink::mojom::QuotaStatusCode::kErrorInvalidAccess);
return;
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](const StorageKey& storage_key, const std::string& bucket_name,
QuotaDatabase* database) {
DCHECK(database);
return database->GetBucket(storage_key, bucket_name);
},
storage_key, bucket_name),
base::BindOnce(&QuotaManagerImpl::DidGetBucketForDeletion,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void QuotaManagerImpl::UpdateBucketExpiration(
BucketId bucket,
const base::Time& expiration,
base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
return;
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](BucketId bucket, const base::Time& expiration,
QuotaDatabase* database) {
DCHECK(database);
return database->UpdateBucketExpiration(bucket, expiration);
},
bucket, expiration),
base::BindOnce(&QuotaManagerImpl::DidGetBucket,
weak_factory_.GetWeakPtr(),
/*notify_update_bucket=*/true, std::move(callback)));
}
void QuotaManagerImpl::UpdateBucketPersistence(
BucketId bucket,
bool persistent,
base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
return;
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](BucketId bucket, bool persistent, QuotaDatabase* database) {
DCHECK(database);
return database->UpdateBucketPersistence(bucket, persistent);
},
bucket, persistent),
base::BindOnce(&QuotaManagerImpl::DidGetBucket,
weak_factory_.GetWeakPtr(),
/*notify_update_bucket=*/true, std::move(callback)));
}
void QuotaManagerImpl::PerformStorageCleanup(
QuotaClientTypes quota_client_types,
base::OnceClosure callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
StorageCleanupHelper* deleter = new StorageCleanupHelper(
this, std::move(quota_client_types), std::move(callback));
deleter->Start();
}
void QuotaManagerImpl::DeleteHostData(const std::string& host,
StatusCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (host.empty() || client_types_.empty()) {
std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk);
return;
}
auto buckets_deleter = std::make_unique<BucketSetDataDeleter>(
this, base::BindOnce(&QuotaManagerImpl::DidDeleteBuckets,
weak_factory_.GetWeakPtr(), std::move(callback)));
auto* buckets_deleter_ptr = buckets_deleter.get();
bucket_set_data_deleters_[buckets_deleter_ptr] = std::move(buckets_deleter);
GetBucketsForHost(host, buckets_deleter_ptr->GetBucketDeletionCallback());
}
void QuotaManagerImpl::DeleteStorageKeyData(
const blink::StorageKey& storage_key,
StatusCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureDatabaseOpened();
if (client_types_.empty()) {
std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk);
return;
}
auto buckets_deleter = std::make_unique<BucketSetDataDeleter>(
this, base::BindOnce(&QuotaManagerImpl::DidDeleteBuckets,
weak_factory_.GetWeakPtr(), std::move(callback)));
auto* buckets_deleter_ptr = buckets_deleter.get();
bucket_set_data_deleters_[buckets_deleter_ptr] = std::move(buckets_deleter);
GetBucketsForStorageKey(storage_key,
buckets_deleter_ptr->GetBucketDeletionCallback());
}
// static
void QuotaManagerImpl::DidDeleteBuckets(
base::WeakPtr<QuotaManagerImpl> quota_manager,
StatusCallback callback,
BucketSetDataDeleter* deleter,
blink::mojom::QuotaStatusCode status_code) {
DCHECK(callback);
DCHECK(deleter);
DCHECK(deleter->completed());
if (quota_manager) {
quota_manager->bucket_set_data_deleters_.erase(deleter);
}
std::move(callback).Run(status_code);
}
void QuotaManagerImpl::BindInternalsHandler(
mojo::PendingReceiver<mojom::QuotaInternalsHandler> receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
internals_handlers_receivers_.Add(this, std::move(receiver));
}
void QuotaManagerImpl::GetDiskAvailabilityAndTempPoolSize(
GetDiskAvailabilityAndTempPoolSizeCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
auto info = std::make_unique<AccumulateQuotaInternalsInfo>();
auto* info_ptr = info.get();
base::RepeatingClosure barrier = base::BarrierClosure(
2, base::BindOnce(
&QuotaManagerImpl::FinallySendDiskAvailabilityAndTempPoolSize,
weak_factory_.GetWeakPtr(), std::move(callback), std::move(info)));
// base::Unretained usage is safe here because BarrierClosure holds
// the std::unque_ptr that keeps AccumulateQuotaInternalsInfo alive, and the
// BarrierClosure will outlive the UpdateQuotaInternalsDiskAvailability
// and UpdateQuotaInternalsTempPoolSpace closures.
GetStorageCapacity(base::BindOnce(
&QuotaManagerImpl::UpdateQuotaInternalsDiskAvailability,
weak_factory_.GetWeakPtr(), barrier, base::Unretained(info_ptr)));
GetQuotaSettings(base::BindOnce(
&QuotaManagerImpl::UpdateQuotaInternalsTempPoolSpace,
weak_factory_.GetWeakPtr(), barrier, base::Unretained(info_ptr)));
}
void QuotaManagerImpl::UpdateQuotaInternalsDiskAvailability(
base::OnceClosure barrier_callback,
AccumulateQuotaInternalsInfo* info,
int64_t total_space,
int64_t available_space) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GE(total_space, 0);
DCHECK_GE(total_space, available_space);
info->total_space = total_space;
info->available_space = available_space;
std::move(barrier_callback).Run();
}
void QuotaManagerImpl::UpdateQuotaInternalsTempPoolSpace(
base::OnceClosure barrier_callback,
AccumulateQuotaInternalsInfo* info,
const QuotaSettings& settings) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GE(settings.pool_size, 0);
info->temp_pool_size = settings.pool_size;
std::move(barrier_callback).Run();
}
void QuotaManagerImpl::FinallySendDiskAvailabilityAndTempPoolSize(
GetDiskAvailabilityAndTempPoolSizeCallback callback,
std::unique_ptr<AccumulateQuotaInternalsInfo> info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GE(info->total_space, 0);
DCHECK_GE(info->total_space, info->available_space);
DCHECK_GE(info->temp_pool_size, 0);
std::move(callback).Run(info->total_space, info->available_space,
info->temp_pool_size);
}
void QuotaManagerImpl::GetStatistics(GetStatisticsCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
base::flat_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& storage_key_usage_pair : stats) {
statistics[storage_key_usage_pair.first] =
base::NumberToString(storage_key_usage_pair.second);
}
}
std::move(callback).Run(statistics);
}
void QuotaManagerImpl::GetGlobalUsage(UsageCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
UsageTracker* usage_tracker = GetUsageTracker();
DCHECK(usage_tracker);
usage_tracker->GetGlobalUsage(std::move(callback));
}
void QuotaManagerImpl::GetGlobalUsageForInternals(
GetGlobalUsageForInternalsCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
UsageTracker* usage_tracker = GetUsageTracker();
DCHECK(usage_tracker);
usage_tracker->GetGlobalUsage(std::move(callback));
}
void QuotaManagerImpl::GetStorageKeyUsageWithBreakdown(
const blink::StorageKey& storage_key,
UsageWithBreakdownCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureDatabaseOpened();
UsageTracker* usage_tracker = GetUsageTracker();
DCHECK(usage_tracker);
usage_tracker->GetStorageKeyUsageWithBreakdown(storage_key,
std::move(callback));
}
void QuotaManagerImpl::GetBucketUsageWithBreakdown(
const BucketLocator& bucket,
UsageWithBreakdownCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureDatabaseOpened();
UsageTracker* usage_tracker = GetUsageTracker();
DCHECK(usage_tracker);
usage_tracker->GetBucketUsageWithBreakdown(bucket, std::move(callback));
}
bool QuotaManagerImpl::IsStorageUnlimited(const StorageKey& storage_key) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return special_storage_policy_.get() &&
special_storage_policy_->IsStorageUnlimited(
storage_key.origin().GetURL());
}
int64_t QuotaManagerImpl::GetQuotaForStorageKey(
const StorageKey& storage_key,
const QuotaSettings& settings) const {
if (IsStorageUnlimited(storage_key)) {
return kNoLimit;
}
if (special_storage_policy_ && special_storage_policy_->IsStorageSessionOnly(
storage_key.origin().GetURL())) {
return settings.session_only_per_storage_key_quota;
}
return settings.per_storage_key_quota;
}
void QuotaManagerImpl::GetBucketsModifiedBetween(base::Time begin,
base::Time end,
GetBucketsCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(std::set<BucketLocator>());
return;
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](base::Time begin, base::Time end, QuotaDatabase* database) {
DCHECK(database);
return database->GetBucketsModifiedBetween(begin, end);
},
begin, end),
base::BindOnce(&QuotaManagerImpl::DidGetModifiedBetween,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
bool QuotaManagerImpl::ResetUsageTracker() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UsageTracker* previous_usage_tracker = GetUsageTracker();
DCHECK(previous_usage_tracker);
if (previous_usage_tracker->IsWorking()) {
return false;
}
usage_tracker_ = std::make_unique<UsageTracker>(
this, client_types_, special_storage_policy_.get());
return true;
}
QuotaManagerImpl::~QuotaManagerImpl() {
// Delete this now because otherwise it may call back into `this` after the
// `sequence_checker_` has been destroyed.
temporary_storage_evictor_.reset();
proxy_->InvalidateQuotaManagerImpl(base::PassKey<QuotaManagerImpl>());
if (database_) {
db_runner_->DeleteSoon(FROM_HERE, std::move(database_));
}
}
void QuotaManagerImpl::EnsureDatabaseOpened() {
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_);
database_->SetDbErrorCallback(
base::BindPostTaskToCurrentDefault(base::BindRepeating(
&QuotaManagerImpl::OnDbError, weak_factory_.GetWeakPtr())));
usage_tracker_ = std::make_unique<UsageTracker>(
this, client_types_, special_storage_policy_.get());
if (!is_incognito_) {
histogram_timer_.Start(FROM_HERE,
base::Milliseconds(kReportHistogramInterval), this,
&QuotaManagerImpl::ReportHistogram);
}
if (bootstrap_disabled_for_testing_) {
return;
}
MaybeBootstrapDatabase();
MaybeRemoveMediaLicenseDatabases();
}
void QuotaManagerImpl::MaybeRemoveMediaLicenseDatabases() {
db_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&QuotaDatabase::IsMediaLicenseDatabaseRemoved,
base::Unretained(database_.get())),
base::BindOnce(&QuotaManagerImpl::DidGetMediaLicenseDatabaseRemovalFlag,
weak_factory_.GetWeakPtr()));
}
void QuotaManagerImpl::DidGetMediaLicenseDatabaseRemovalFlag(
bool is_media_license_database_removed) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!is_media_license_database_removed) {
RemoveMediaLicenseDatabases();
}
}
void QuotaManagerImpl::RemoveMediaLicenseDatabases() {
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&QuotaManagerImpl::DeleteMediaLicenseDatabase,
weak_factory_.GetWeakPtr()),
kMinutesAfterStartupToBeginMediaLicenseDatabaseDeletion);
PostTaskAndReplyWithResultForDBThread(
base::BindOnce([](QuotaDatabase* database) {
DCHECK(database);
return database->SetIsMediaLicenseDatabaseRemoved(true);
}),
base::DoNothing(), FROM_HERE);
}
void QuotaManagerImpl::MaybeBootstrapDatabase() {
is_bootstrapping_database_ = true;
db_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&QuotaDatabase::IsBootstrapped,
base::Unretained(database_.get())),
base::BindOnce(&QuotaManagerImpl::DidGetBootstrapFlag,
weak_factory_.GetWeakPtr()));
}
void QuotaManagerImpl::DidGetBootstrapFlag(bool is_database_bootstrapped) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(is_bootstrapping_database_);
if (!is_database_bootstrapped) {
BootstrapDatabase();
return;
}
is_bootstrapping_database_ = false;
RunDatabaseCallbacks();
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&QuotaManagerImpl::StartEviction,
weak_factory_.GetWeakPtr()),
kMinutesAfterStartupToBeginEviction);
}
void QuotaManagerImpl::BootstrapDatabase() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!storage_key_gatherer_);
storage_key_gatherer_ = std::make_unique<StorageKeyGathererTask>(
this, base::BindOnce(&QuotaManagerImpl::DidGetStorageKeysForBootstrap,
weak_factory_.GetWeakPtr()));
storage_key_gatherer_->Run();
}
void QuotaManagerImpl::DidGetStorageKeysForBootstrap(
std::set<StorageKey> storage_keys) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(storage_key_gatherer_);
storage_key_gatherer_.reset();
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](std::set<StorageKey> storage_keys, QuotaDatabase* database) {
DCHECK(database);
return database->RegisterInitialStorageKeyInfo(
std::move(storage_keys));
},
std::move(storage_keys)),
base::BindOnce(&QuotaManagerImpl::DidBootstrapDatabase,
weak_factory_.GetWeakPtr()),
FROM_HERE,
/*is_bootstrap_task=*/true);
}
void QuotaManagerImpl::DidBootstrapDatabase(QuotaError error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (error == QuotaError::kDatabaseError) {
// If we got an error during bootstrapping there is no point in
// trying again. Disable the database instead.
db_disabled_ = true;
ReportDatabaseDisabledReason(
DatabaseDisabledReason::kRegisterStorageKeyFailed);
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce([](QuotaDatabase* database) {
DCHECK(database);
return database->SetIsBootstrapped(true);
}),
base::BindOnce(&QuotaManagerImpl::DidSetDatabaseBootstrapped,
weak_factory_.GetWeakPtr()),
FROM_HERE,
/*is_bootstrap_task=*/true);
}
void QuotaManagerImpl::DidSetDatabaseBootstrapped(QuotaError error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(is_bootstrapping_database_);
is_bootstrapping_database_ = false;
if (error == QuotaError::kDatabaseError) {
// If we got an error during bootstrapping there is no point in
// trying again. Disable the database instead.
db_disabled_ = true;
ReportDatabaseDisabledReason(
DatabaseDisabledReason::kSetIsBootstrappedFailed);
}
RunDatabaseCallbacks();
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&QuotaManagerImpl::StartEviction,
weak_factory_.GetWeakPtr()),
kMinutesAfterStartupToBeginEviction);
// Schedule the MediaLicenseDatabase deletion task.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&QuotaManagerImpl::DeleteMediaLicenseDatabase,
weak_factory_.GetWeakPtr()),
kMinutesAfterStartupToBeginMediaLicenseDatabaseDeletion);
}
void QuotaManagerImpl::RunDatabaseCallbacks() {
for (auto& callback : database_callbacks_) {
std::move(callback).Run();
}
database_callbacks_.clear();
}
void QuotaManagerImpl::RegisterClient(
mojo::PendingRemote<mojom::QuotaClient> client,
QuotaClientType client_type) {
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();
client_types_.insert({client_ptr, client_type});
}
UsageTracker* QuotaManagerImpl::GetUsageTracker() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return usage_tracker_.get();
}
void QuotaManagerImpl::NotifyBucketAccessed(const BucketLocator& bucket,
base::Time access_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureDatabaseOpened();
if (is_getting_eviction_bucket_) {
// Record the accessed buckets while GetLRUStorageKey task is running
// to filter out them from eviction.
access_notified_buckets_.insert(bucket);
}
if (db_disabled_) {
return;
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](BucketLocator bucket, base::Time accessed_time,
QuotaDatabase* database) {
DCHECK(database);
if (bucket.is_default) {
return database->SetStorageKeyLastAccessTime(bucket.storage_key,
accessed_time);
} else {
return database->SetBucketLastAccessTime(bucket.id,
accessed_time);
}
},
bucket, access_time),
base::DoNothing());
}
void QuotaManagerImpl::NotifyBucketModified(QuotaClientType client_id,
const BucketLocator& bucket,
std::optional<int64_t> delta,
base::Time modification_time,
base::OnceClosure callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
GetUsageTracker()->UpdateBucketUsageCache(client_id, bucket, delta);
// Return once usage cache is updated for callers waiting for quota changes to
// be reflected before querying for usage.
std::move(callback).Run();
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](BucketLocator bucket, base::Time modification_time,
QuotaDatabase* database) {
DCHECK(database);
BucketId id = bucket.id;
if (!id) {
CHECK(bucket.is_default);
QuotaErrorOr<BucketInfo> result =
database->GetBucket(bucket.storage_key, kDefaultBucketName);
if (!result.has_value()) {
return QuotaError::kNotFound;
}
id = result->id;
}
return database->SetBucketLastModifiedTime(id, modification_time);
},
bucket, modification_time),
base::DoNothing());
}
void QuotaManagerImpl::DumpBucketTable(DumpBucketTableCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
if (db_disabled_ || !database_) {
std::move(callback).Run(BucketTableEntries());
return;
}
DumpBucketTableHelper* helper = new DumpBucketTableHelper;
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(&DumpBucketTableHelper::DumpBucketTableOnDBThread,
base::Unretained(helper)),
base::BindOnce(&DumpBucketTableHelper::DidDumpBucketTable,
base::Owned(helper), weak_factory_.GetWeakPtr(),
std::move(callback)));
}
void QuotaManagerImpl::RetrieveBucketsTable(
RetrieveBucketsTableCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
if (db_disabled_) {
std::move(callback).Run({});
return;
}
DumpBucketTable(
base::BindOnce(&QuotaManagerImpl::RetrieveBucketUsageForBucketTable,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void QuotaManagerImpl::RetrieveBucketUsageForBucketTable(
RetrieveBucketsTableCallback callback,
BucketTableEntries entries) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::ConcurrentCallbacks<mojom::BucketTableEntryPtr> concurrent;
for (auto& entry : entries) {
std::optional<StorageKey> storage_key =
StorageKey::Deserialize(entry->storage_key);
// If the serialization format changes keys may not deserialize.
if (!storage_key) {
continue;
}
BucketId bucket_id = BucketId(entry->bucket_id);
BucketLocator bucket_locator =
BucketLocator(bucket_id, std::move(storage_key).value(),
entry->name == kDefaultBucketName);
GetBucketUsageWithBreakdown(
bucket_locator,
base::BindOnce(&QuotaManagerImpl::AddBucketTableEntry,
weak_factory_.GetWeakPtr(), std::move(entry),
concurrent.CreateCallback()));
}
std::move(concurrent).Done(std::move(callback));
}
void QuotaManagerImpl::AddBucketTableEntry(
mojom::BucketTableEntryPtr entry,
base::OnceCallback<void(mojom::BucketTableEntryPtr)> callback,
int64_t usage,
blink::mojom::UsageBreakdownPtr bucket_usage_breakdown) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
entry->usage = usage;
std::move(callback).Run(std::move(entry));
}
void QuotaManagerImpl::OnDbError(int error_code) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
sql::UmaHistogramSqliteResult("Quota.QuotaDatabaseError", error_code);
// Start the storage eviction routine on a full disk error.
if (static_cast<sql::SqliteErrorCode>(error_code) ==
sql::SqliteErrorCode::kFullDisk) {
OnFullDiskError(std::nullopt);
return;
}
if (!sql::IsErrorCatastrophic(error_code)) {
return;
}
// Db will be set to disabled after a bootstrapping failure.
if (db_disabled_) {
return;
}
// Ignore any errors that happen while a new bootstrap attempt is already in
// progress or queued.
if (is_bootstrapping_database_) {
return;
}
if (bootstrap_disabled_for_testing_) {
db_disabled_ = true;
return;
}
// Start another bootstrapping process. Pause eviction while bootstrapping
// is in progress. When bootstrapping finishes a new Evictor will be
// created.
is_bootstrapping_database_ = true;
temporary_storage_evictor_ = nullptr;
// Wipe the database before triggering another bootstrap.
db_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&QuotaDatabase::RecoverOrRaze,
base::Unretained(database_.get()), error_code),
base::BindOnce(&QuotaManagerImpl::DidRecoverOrRazeForReBootstrap,
weak_factory_.GetWeakPtr()));
}
void QuotaManagerImpl::OnFullDiskError(std::optional<StorageKey> storage_key) {
if ((base::TimeTicks::Now() - last_full_disk_eviction_time_) >
base::Minutes(15)) {
last_full_disk_eviction_time_ = base::TimeTicks::Now();
StartEviction();
}
// We may already be evicting, either due to the above or just by chance. In
// either case, do nothing more for now.
if (temporary_storage_evictor_ && temporary_storage_evictor_->in_round()) {
return;
}
if (storage_key) {
NotifyWriteFailed(*storage_key);
} else if (last_opened_bucket_site_) {
NotifyWriteFailed(*last_opened_bucket_site_);
}
}
void QuotaManagerImpl::NotifyWriteFailed(const blink::StorageKey& storage_key) {
auto [time_of_last_stats, total_space, available_space] =
cached_disk_stats_for_storage_pressure_;
auto age_of_disk_stats = base::TimeTicks::Now() - time_of_last_stats;
// Avoid polling for free disk space if disk stats have been recently
// queried.
if (age_of_disk_stats < kStoragePressureCheckDiskStatsInterval) {
MaybeRunStoragePressureCallback(storage_key, total_space, available_space);
return;
}
GetStorageCapacity(
base::BindOnce(&QuotaManagerImpl::MaybeRunStoragePressureCallback,
weak_factory_.GetWeakPtr(), storage_key));
}
void QuotaManagerImpl::StartEviction() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (eviction_disabled_) {
return;
}
if (!temporary_storage_evictor_) {
temporary_storage_evictor_ =
std::make_unique<QuotaTemporaryStorageEvictor>(this, kEvictionInterval);
}
temporary_storage_evictor_->Start();
}
void QuotaManagerImpl::DeleteMediaLicenseDatabase() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
GetBucketsModifiedBetween(
base::Time::Min(), base::Time::Max(),
base::BindOnce(&QuotaManagerImpl::DidGetBucketsForMediaLicenseDeletion,
weak_factory_.GetWeakPtr()));
}
void QuotaManagerImpl::DidGetBucketsForMediaLicenseDeletion(
const std::set<BucketLocator>& buckets) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<base::FilePath> media_license_dir_paths;
for (const BucketLocator& bucket : buckets) {
if (bucket.storage_key.IsFirstPartyContext()) {
media_license_dir_paths.push_back(
CreateMediaLicenseBucketPath(profile_path_, bucket));
}
}
db_runner_->PostTask(FROM_HERE,
base::BindOnce(
[](std::vector<base::FilePath> file_paths) {
for (base::FilePath& path : file_paths) {
base::DeletePathRecursively(path);
}
},
std::move(media_license_dir_paths)));
}
void QuotaManagerImpl::DeleteBucketFromDatabase(
const BucketLocator& bucket,
bool commit_immediately,
base::OnceCallback<void(QuotaErrorOr<mojom::BucketTableEntryPtr>)>
callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
return;
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](const BucketLocator& bucket, bool commit_immediately,
QuotaDatabase* database) {
DCHECK(database);
auto result = database->DeleteBucketData(bucket);
if (commit_immediately && result.has_value()) {
database->CommitNow();
}
return result;
},
bucket, commit_immediately),
base::BindOnce(&QuotaManagerImpl::OnBucketDeleted,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void QuotaManagerImpl::DidEvictBucketData(
BucketId evicted_bucket_id,
base::RepeatingCallback<void(bool)> barrier,
QuotaErrorOr<mojom::BucketTableEntryPtr> entry) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(io_thread_->BelongsToCurrentThread());
if (entry.has_value()) {
DCHECK(entry.value());
base::Time now = QuotaDatabase::GetNow();
base::UmaHistogramCounts1M(
QuotaManagerImpl::kEvictedBucketAccessedCountHistogram,
entry.value()->use_count);
base::UmaHistogramCounts1000(
QuotaManagerImpl::kEvictedBucketDaysSinceAccessHistogram,
(now - entry.value()->last_accessed).InDays());
barrier.Run(true);
} else {
// We only try to evict buckets that are not in use, so basically deletion
// attempt for eviction should not fail. Let's record the bucket if we get
// an error and exclude it from future eviction if the error happens
// consistently (> kThresholdOfErrorsToBeDenylisted).
buckets_in_error_[evicted_bucket_id]++;
barrier.Run(false);
}
}
void QuotaManagerImpl::DeleteBucketDataInternal(
const BucketLocator& bucket,
QuotaClientTypes quota_client_types,
base::OnceCallback<void(QuotaErrorOr<mojom::BucketTableEntryPtr>)>
callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
return;
}
auto bucket_deleter = std::make_unique<BucketDataDeleter>(
this, bucket, std::move(quota_client_types), /*commit_immediately=*/true,
base::BindOnce(&QuotaManagerImpl::DidDeleteBucketData,
weak_factory_.GetWeakPtr(), std::move(callback)));
auto* bucket_deleter_ptr = bucket_deleter.get();
bucket_data_deleters_[bucket_deleter_ptr] = std::move(bucket_deleter);
bucket_deleter_ptr->Run();
}
// static
void QuotaManagerImpl::DidDeleteBucketData(
base::WeakPtr<QuotaManagerImpl> quota_manager,
base::OnceCallback<void(QuotaErrorOr<mojom::BucketTableEntryPtr>)> callback,
BucketDataDeleter* deleter,
QuotaErrorOr<mojom::BucketTableEntryPtr> result) {
DCHECK(callback);
DCHECK(deleter);
DCHECK(deleter->completed());
if (quota_manager) {
quota_manager->bucket_data_deleters_.erase(deleter);
}
std::move(callback).Run(std::move(result));
}
void QuotaManagerImpl::DidDeleteBucketForRecreation(
const BucketInitParams& params,
base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback,
BucketInfo bucket_info,
QuotaErrorOr<mojom::BucketTableEntryPtr> result) {
if (result.has_value()) {
UpdateOrCreateBucket(params, std::move(callback));
} else {
std::move(callback).Run(base::unexpected(QuotaError::kDatabaseError));
}
}
void QuotaManagerImpl::MaybeRunStoragePressureCallback(
const StorageKey& storage_key,
int64_t total_space,
int64_t available_space) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GE(total_space, 0);
DCHECK_GE(available_space, 0);
// TODO(crbug.com/40121667): 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.
storage_key_for_pending_storage_pressure_callback_ = std::move(storage_key);
return;
}
if (available_space < kStoragePressureThresholdRatio * total_space) {
storage_pressure_callback_.Run(std::move(storage_key));
}
}
void QuotaManagerImpl::SimulateStoragePressure(const url::Origin& origin_url) {
const StorageKey key = StorageKey::CreateFirstParty(origin_url);
// In Incognito, since no data is stored on disk, storage pressure should be
// ignored.
DCHECK_EQ(is_incognito_, storage_pressure_callback_.is_null());
if (storage_pressure_callback_.is_null()) {
return;
}
storage_pressure_callback_.Run(key);
}
void QuotaManagerImpl::IsSimulateStoragePressureAvailable(
IsSimulateStoragePressureAvailableCallback callback) {
// We assume this is only the case in incognito. If it changes, update this.
DCHECK_EQ(is_incognito_, storage_pressure_callback_.is_null());
std::move(callback).Run(!storage_pressure_callback_.is_null());
}
void QuotaManagerImpl::SetStoragePressureCallback(
base::RepeatingCallback<void(const StorageKey&)>
storage_pressure_callback) {
storage_pressure_callback_ = storage_pressure_callback;
if (storage_key_for_pending_storage_pressure_callback_.has_value()) {
storage_pressure_callback_.Run(
std::move(storage_key_for_pending_storage_pressure_callback_.value()));
storage_key_for_pending_storage_pressure_callback_ = std::nullopt;
}
}
int QuotaManagerImpl::GetOverrideHandleId() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return ++next_override_handle_id_;
}
void QuotaManagerImpl::OverrideQuotaForStorageKey(
int handle_id,
const StorageKey& storage_key,
std::optional<int64_t> quota_size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GE(quota_size.value_or(0), 0)
<< "negative quota override: " << quota_size.value_or(0);
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 storage key.
devtools_overrides_[storage_key].active_override_session_ids.insert(
handle_id);
devtools_overrides_[storage_key].quota_size = quota_size.value();
} else {
devtools_overrides_.erase(storage_key);
}
}
void QuotaManagerImpl::WithdrawOverridesForHandle(int handle_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<StorageKey> storage_keys_to_clear;
for (auto& devtools_override : devtools_overrides_) {
auto& quota_override = devtools_override.second;
auto& storage_key = devtools_override.first;
quota_override.active_override_session_ids.erase(handle_id);
if (!quota_override.active_override_session_ids.size()) {
storage_keys_to_clear.push_back(storage_key);
}
}
for (auto& storage_key : storage_keys_to_clear) {
devtools_overrides_.erase(storage_key);
}
}
std::optional<int64_t> QuotaManagerImpl::GetQuotaOverrideForStorageKey(
const StorageKey& storage_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!base::Contains(devtools_overrides_, storage_key)) {
return std::nullopt;
}
return devtools_overrides_[storage_key].quota_size;
}
void QuotaManagerImpl::CorruptDatabaseForTesting(
base::OnceCallback<void(const base::FilePath&)> corrupter,
base::OnceCallback<void(QuotaError)> callback) {
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](base::OnceCallback<void(const base::FilePath&)> corrupter,
QuotaDatabase* database) {
return database->CorruptForTesting( // IN-TEST
std::move(corrupter));
},
std::move(corrupter)),
std::move(callback));
}
void QuotaManagerImpl::ReportHistogram() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!is_incognito_);
GetGlobalUsage(
base::BindOnce(&QuotaManagerImpl::DidGetGlobalUsageForHistogram,
weak_factory_.GetWeakPtr()));
}
void QuotaManagerImpl::DidGetGlobalUsageForHistogram(int64_t usage,
int64_t unlimited_usage) {
DCHECK_GE(usage, -1);
DCHECK_GE(unlimited_usage, -1);
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_);
DCHECK_GE(usage, -1);
DCHECK_GE(total_space, 0);
DCHECK_GE(available_space, 0);
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))));
}
// We DumpBucketTable last to ensure the trackers caches are loaded.
DumpBucketTable(
base::BindOnce(&QuotaManagerImpl::DidDumpBucketTableForHistogram,
weak_factory_.GetWeakPtr()));
}
void QuotaManagerImpl::DidDumpBucketTableForHistogram(
BucketTableEntries entries) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::UmaHistogramCounts100000("Quota.TotalBucketCount", entries.size());
std::map<StorageKey, int64_t> usage_map =
GetUsageTracker()->GetCachedStorageKeysUsage();
base::Time now = QuotaDatabase::GetNow();
for (const auto& info : entries) {
std::optional<StorageKey> storage_key =
StorageKey::Deserialize(info->storage_key);
if (!storage_key.has_value()) {
continue;
}
auto it = usage_map.find(*storage_key);
if (it == usage_map.end() || it->second == 0) {
continue;
}
base::TimeDelta age =
now - std::max(info->last_accessed, info->last_modified);
base::UmaHistogramCounts1000("Quota.AgeOfOriginInDays", age.InDays());
int64_t kilobytes = std::max(it->second / int64_t{1024}, int64_t{1});
base::Histogram::FactoryGet("Quota.AgeOfDataInDays", 1, 1000, 50,
base::HistogramBase::kUmaTargetedHistogramFlag)
->AddCount(age.InDays(), base::saturated_cast<int>(kilobytes));
}
}
std::set<BucketId> QuotaManagerImpl::GetEvictionBucketExceptions() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::set<BucketId> exceptions;
for (const auto& p : buckets_in_error_) {
if (p.second > QuotaManagerImpl::kThresholdOfErrorsToBeDenylisted) {
exceptions.insert(p.first);
}
}
return exceptions;
}
void QuotaManagerImpl::DidGetEvictionBuckets(
GetBucketsCallback callback,
const std::set<BucketLocator>& buckets) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
// Filter out buckets that were accessed while getting eviction buckets.
auto bucket_wasnt_accessed =
[this](const BucketLocator& to_be_evicted_bucket) {
return !std::count_if(
access_notified_buckets_.begin(), access_notified_buckets_.end(),
[&to_be_evicted_bucket](const BucketLocator& accessed_bucket) {
return to_be_evicted_bucket.IsEquivalentTo(accessed_bucket);
});
};
std::set<BucketLocator> bucket_copies;
std::copy_if(buckets.begin(), buckets.end(),
std::inserter(bucket_copies, bucket_copies.end()),
bucket_wasnt_accessed);
std::move(callback).Run(bucket_copies);
access_notified_buckets_.clear();
is_getting_eviction_bucket_ = false;
}
void QuotaManagerImpl::GetEvictionBuckets(int64_t target_usage,
GetBucketsCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
// This must not be called while there's an in-flight task.
DCHECK(!is_getting_eviction_bucket_);
is_getting_eviction_bucket_ = true;
// The usage map should have been cached recently due to
// `GetEvictionRoundInfo()`.
std::map<BucketLocator, int64_t> usage_map =
GetUsageTracker()->GetCachedBucketsUsage();
GetBucketsForEvictionFromDatabase(
target_usage, std::move(usage_map),
base::BindOnce(&QuotaManagerImpl::DidGetEvictionBuckets,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void QuotaManagerImpl::EvictExpiredBuckets(StatusCallback done) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(done);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(done).Run(blink::mojom::QuotaStatusCode::kUnknown);
return;
}
auto buckets_deleter = std::make_unique<BucketSetDataDeleter>(
this, base::BindOnce(&QuotaManagerImpl::DidDeleteBuckets,
weak_factory_.GetWeakPtr(), std::move(done)));
auto* buckets_deleter_ptr = buckets_deleter.get();
bucket_set_data_deleters_[buckets_deleter_ptr] = std::move(buckets_deleter);
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](SpecialStoragePolicy* policy, QuotaDatabase* database) {
DCHECK(database);
return database->GetExpiredBuckets(policy);
},
base::RetainedRef(special_storage_policy_)),
buckets_deleter_ptr->GetBucketDeletionCallback());
}
void QuotaManagerImpl::EvictBucketData(
const std::set<BucketLocator>& buckets,
base::OnceCallback<void(int)> on_eviction_done) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(io_thread_->BelongsToCurrentThread());
auto barrier = base::BarrierCallback<bool>(
buckets.size(), base::BindOnce(
[](base::OnceCallback<void(int)> on_eviction_done,
std::vector<bool> results) {
const int evicted_count = std::count(
results.begin(), results.end(), true);
std::move(on_eviction_done).Run(evicted_count);
},
std::move(on_eviction_done)));
for (const auto& bucket : buckets) {
DeleteBucketDataInternal(
bucket, AllQuotaClientTypes(),
base::BindOnce(&QuotaManagerImpl::DidEvictBucketData,
weak_factory_.GetWeakPtr(), bucket.id, barrier));
}
}
void QuotaManagerImpl::GetEvictionRoundInfo(
EvictionRoundInfoCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(io_thread_->BelongsToCurrentThread());
DCHECK(callback);
EnsureDatabaseOpened();
DCHECK(!eviction_helper_);
eviction_helper_ = std::make_unique<EvictionRoundInfoHelper>(
this, std::move(callback),
base::BindOnce(&QuotaManagerImpl::DidGetEvictionRoundInfo,
weak_factory_.GetWeakPtr()));
eviction_helper_->Run();
}
void QuotaManagerImpl::DidGetEvictionRoundInfo() {
DCHECK(eviction_helper_);
eviction_helper_.reset();
}
void QuotaManagerImpl::GetBucketsForEvictionFromDatabase(
int64_t target_usage,
std::map<BucketLocator, int64_t> usage_map,
GetBucketsCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
EnsureDatabaseOpened();
if (db_disabled_) {
std::move(callback).Run({});
return;
}
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](int64_t target_usage, std::map<BucketLocator, int64_t> usage_map,
const std::set<BucketId>& bucket_exceptions,
SpecialStoragePolicy* policy, QuotaDatabase* database) {
DCHECK(database);
return database->GetBucketsForEviction(target_usage, usage_map,
bucket_exceptions, policy);
},
target_usage, std::move(usage_map), GetEvictionBucketExceptions(),
base::RetainedRef(special_storage_policy_)),
base::BindOnce(&QuotaManagerImpl::DidGetBucketsForEvictionFromDatabase,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void QuotaManagerImpl::DidGetBucketsForEvictionFromDatabase(
GetBucketsCallback callback,
QuotaErrorOr<std::set<BucketLocator>> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (result.has_value()) {
std::move(callback).Run(result.value());
} else {
std::move(callback).Run({});
}
}
void QuotaManagerImpl::GetQuotaSettings(QuotaSettingsCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
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::BindPostTask(base::SingleThreadTaskRunner::GetCurrentDefault(),
base::BindOnce(&QuotaManagerImpl::DidGetSettings,
weak_factory_.GetWeakPtr()))));
}
void QuotaManagerImpl::DidGetSettings(std::optional<QuotaSettings> settings) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!settings) {
settings = settings_;
settings->refresh_interval = base::Minutes(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_);
DCHECK(callback);
if (!storage_capacity_callbacks_.Add(std::move(callback))) {
return;
}
if (is_incognito_) {
GetQuotaSettings(
base::BindOnce(&QuotaManagerImpl::ContinueIncognitoGetStorageCapacity,
weak_factory_.GetWeakPtr()));
return;
}
db_runner_->PostTaskAndReplyWithResult(
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_);
auto* temporary_usage_tracker = GetUsageTracker();
int64_t temporary_usage = temporary_usage_tracker == nullptr
? 0
: temporary_usage_tracker->GetCachedUsage();
DCHECK_GE(temporary_usage, -1);
int64_t available_space =
std::max(int64_t{0}, settings.pool_size - temporary_usage);
DidGetStorageCapacity(QuotaAvailability(settings.pool_size, available_space));
}
void QuotaManagerImpl::DidGetStorageCapacity(
const QuotaAvailability& quota_usage) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
int64_t total_space = quota_usage.total;
DCHECK_GE(total_space, 0);
int64_t available_space = quota_usage.available;
DCHECK_GE(available_space, 0);
cached_disk_stats_for_storage_pressure_ =
std::make_tuple(base::TimeTicks::Now(), total_space, available_space);
storage_capacity_callbacks_.Run(total_space, available_space);
}
void QuotaManagerImpl::DidRecoverOrRazeForReBootstrap(bool success) {
if (success) {
MaybeBootstrapDatabase();
return;
}
// Deleting the database failed. Disable the database and hope we'll recover
// after Chrome restarts instead.
db_disabled_ = true;
ReportDatabaseDisabledReason(DatabaseDisabledReason::kRazeFailed);
is_bootstrapping_database_ = false;
RunDatabaseCallbacks();
// No reason to restart eviction here. Without a working database there is
// nothing to evict.
}
void QuotaManagerImpl::NotifyUpdatedBucket(
const QuotaErrorOr<BucketInfo>& result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!result.has_value()) {
return;
}
for (auto& observer : observers_) {
observer->OnCreateOrUpdateBucket(result.value());
}
}
void QuotaManagerImpl::OnBucketDeleted(
base::OnceCallback<void(QuotaErrorOr<mojom::BucketTableEntryPtr>)> callback,
QuotaErrorOr<mojom::BucketTableEntryPtr> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (result.has_value()) {
const mojom::BucketTableEntryPtr& entry = result.value();
auto storage_key = blink::StorageKey::Deserialize(entry->storage_key);
if (storage_key) {
storage::BucketLocator bucket_locator(BucketId(entry->bucket_id),
storage_key.value(),
entry->name == kDefaultBucketName);
for (auto& observer : observers_) {
observer->OnDeleteBucket(bucket_locator);
}
}
}
std::move(callback).Run(std::move(result));
}
void QuotaManagerImpl::DidGetQuotaSettingsForBucketCreation(
const BucketInitParams& bucket_params,
base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback,
const QuotaSettings& settings) {
const int64_t quota =
GetQuotaForStorageKey(bucket_params.storage_key, settings);
int64_t max_buckets = (quota == kNoLimit) ? 0 : (quota / kTypicalBucketUsage);
DCHECK_EQ(max_buckets == 0, IsStorageUnlimited(bucket_params.storage_key));
PostTaskAndReplyWithResultForDBThread(
base::BindOnce(
[](const BucketInitParams& params, int max_buckets,
QuotaDatabase* database) {
DCHECK(database);
return database->UpdateOrCreateBucket(params, max_buckets);
},
bucket_params, max_buckets),
base::BindOnce(&QuotaManagerImpl::DidGetBucketCheckExpiration,
weak_factory_.GetWeakPtr(), bucket_params,
std::move(callback)));
}
void QuotaManagerImpl::DidGetBucket(
bool notify_update_bucket,
base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback,
QuotaErrorOr<BucketInfo> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
if (notify_update_bucket) {
NotifyUpdatedBucket(result);
}
std::move(callback).Run(std::move(result));
}
void QuotaManagerImpl::DidGetBucketCheckExpiration(
const BucketInitParams& params,
base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback,
QuotaErrorOr<BucketInfo> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
if (result.has_value() && !result->expiration.is_null() &&
result->expiration <= QuotaDatabase::GetNow()) {
DeleteBucketDataInternal(
result->ToBucketLocator(), AllQuotaClientTypes(),
base::BindOnce(&QuotaManagerImpl::DidDeleteBucketForRecreation,
weak_factory_.GetWeakPtr(), params, std::move(callback),
result.value()));
return;
}
NotifyUpdatedBucket(result);
std::move(callback).Run(std::move(result));
}
void QuotaManagerImpl::DidGetBucketForDeletion(
StatusCallback callback,
QuotaErrorOr<BucketInfo> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
if (!result.has_value()) {
// Return QuotaStatusCode::kOk if bucket not found. No work needed.
std::move(callback).Run(result.error() == QuotaError::kNotFound
? blink::mojom::QuotaStatusCode::kOk
: blink::mojom::QuotaStatusCode::kUnknown);
return;
}
auto result_callback = base::BindOnce(
[](StatusCallback callback,
QuotaErrorOr<mojom::BucketTableEntryPtr> result) {
std::move(callback).Run(result.has_value()
? blink::mojom::QuotaStatusCode::kOk
: blink::mojom::QuotaStatusCode::kUnknown);
},
std::move(callback));
DeleteBucketDataInternal(result->ToBucketLocator(), AllQuotaClientTypes(),
std::move(result_callback));
return;
}
void QuotaManagerImpl::DidGetBucketForUsageAndQuota(
UsageAndQuotaCallback callback,
QuotaErrorOr<BucketInfo> result) {
if (!result.has_value()) {
std::move(callback).Run(blink::mojom::QuotaStatusCode::kUnknown, 0, 0);
return;
}
UsageAndQuotaInfoGatherer* helper = new UsageAndQuotaInfoGatherer(
this, result.value(), is_incognito_,
base::BindOnce(&DidGetUsageAndQuotaStripOverride,
base::BindOnce(&DidGetUsageAndQuotaStripBreakdown,
std::move(callback))));
helper->Start();
}
void QuotaManagerImpl::DidGetStorageKeys(
GetStorageKeysCallback callback,
QuotaErrorOr<std::set<StorageKey>> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
std::move(callback).Run(result.value_or(std::set<StorageKey>()));
}
void QuotaManagerImpl::DidGetBuckets(
base::OnceCallback<void(QuotaErrorOr<std::set<BucketInfo>>)> callback,
QuotaErrorOr<std::set<BucketInfo>> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
std::move(callback).Run(std::move(result));
}
void QuotaManagerImpl::DidGetBucketsCheckExpiration(
base::OnceCallback<void(QuotaErrorOr<std::set<BucketInfo>>)> callback,
QuotaErrorOr<std::set<BucketInfo>> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
if (!result.has_value()) {
std::move(callback).Run(std::move(result));
return;
}
std::set<BucketInfo> kept_buckets;
std::set<BucketInfo> buckets_to_delete;
for (const BucketInfo& bucket : result.value()) {
if (!bucket.expiration.is_null() &&
bucket.expiration <= QuotaDatabase::GetNow()) {
buckets_to_delete.insert(bucket);
} else {
kept_buckets.insert(bucket);
}
}
if (buckets_to_delete.empty()) {
std::move(callback).Run(kept_buckets);
return;
}
base::RepeatingClosure barrier =
base::BarrierClosure(buckets_to_delete.size(),
base::BindOnce(std::move(callback), kept_buckets));
for (const BucketInfo& bucket : buckets_to_delete) {
DeleteBucketDataInternal(
bucket.ToBucketLocator(), AllQuotaClientTypes(),
base::IgnoreArgs<QuotaErrorOr<mojom::BucketTableEntryPtr>>(barrier));
}
}
void QuotaManagerImpl::DidGetModifiedBetween(
GetBucketsCallback callback,
QuotaErrorOr<std::set<BucketLocator>> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
std::move(callback).Run(result.value_or(std::set<BucketLocator>()));
}
template <typename ValueType>
void QuotaManagerImpl::PostTaskAndReplyWithResultForDBThread(
base::OnceCallback<QuotaErrorOr<ValueType>(QuotaDatabase*)> task,
base::OnceCallback<void(QuotaErrorOr<ValueType>)> reply,
const base::Location& from_here,
bool is_bootstrap_task) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(task);
DCHECK(reply);
// 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.
if (!is_bootstrap_task && is_bootstrapping_database_) {
database_callbacks_.push_back(base::BindOnce(
&QuotaManagerImpl::PostTaskAndReplyWithResultForDBThread<ValueType>,
weak_factory_.GetWeakPtr(), std::move(task), std::move(reply),
from_here, is_bootstrap_task));
return;
}
db_runner_->PostTaskAndReplyWithResult(
from_here,
base::BindOnce(std::move(task), base::Unretained(database_.get())),
std::move(reply));
}
void QuotaManagerImpl::PostTaskAndReplyWithResultForDBThread(
base::OnceCallback<QuotaError(QuotaDatabase*)> task,
base::OnceCallback<void(QuotaError)> reply,
const base::Location& from_here,
bool is_bootstrap_task) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(task);
DCHECK(reply);
// 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.
if (!is_bootstrap_task && is_bootstrapping_database_) {
database_callbacks_.push_back(base::BindOnce(
static_cast<void (QuotaManagerImpl::*)(
base::OnceCallback<QuotaError(QuotaDatabase*)>,
base::OnceCallback<void(QuotaError)>, const base::Location&, bool)>(
&QuotaManagerImpl::PostTaskAndReplyWithResultForDBThread),
weak_factory_.GetWeakPtr(), std::move(task), std::move(reply),
from_here, is_bootstrap_task));
return;
}
db_runner_->PostTaskAndReplyWithResult(
from_here,
base::BindOnce(std::move(task), base::Unretained(database_.get())),
std::move(reply));
}
// static
QuotaAvailability 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 QuotaAvailability(0, 0);
}
const QuotaAvailability quotaAvailability = get_volume_info_fn(path);
const auto total = quotaAvailability.total;
const auto available = quotaAvailability.available;
if (total < 0 || available < 0) {
LOG(WARNING) << "Unable to get volume info: " << path.value();
return QuotaAvailability(0, 0);
}
DCHECK_GE(total, 0);
DCHECK_GE(available, 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 QuotaAvailability(total, available);
}
// static
QuotaAvailability QuotaManagerImpl::GetVolumeInfo(const base::FilePath& path) {
return QuotaAvailability(base::SysInfo::AmountOfTotalDiskSpace(path),
base::SysInfo::AmountOfFreeDiskSpace(path));
}
void QuotaManagerImpl::AddObserver(
mojo::PendingRemote<storage::mojom::QuotaManagerObserver> observer) {
// `MojoQuotaManagerObserver` is self owned and deletes itself when its remote
// is disconnected.
observers_.Add(std::move(observer));
}
} // namespace storage