// 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.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/functional/concurrent_callbacks.h"
#include "base/functional/concurrent_closures.h"
#include "base/logging.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_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.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 "third_party/perfetto/include/perfetto/tracing/track.h"
#include "url/gurl.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) {
  std::move(callback).Run(status, usage, quota);
}

void DidGetUsageAndQuotaStripOverride(
    QuotaManagerImpl::UsageAndQuotaWithBreakdownCallback callback,
    blink::mojom::QuotaStatusCode status,
    int64_t usage,
    int64_t quota,
    bool is_override_enabled,
    blink::mojom::UsageBreakdownPtr usage_breakdown) {
  std::move(callback).Run(status, usage, quota, std::move(usage_breakdown));
}

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,
                               bool is_incognito) {
  base::ClampedNumeric<int64_t> clamped_total_space_bytes(total_space_bytes);
  base::ClampedNumeric<int64_t> clamped_usage_bytes(usage_bytes);

  // The flag kIncognitoStaticStorageQuota fixes the return value in incognito
  // to 10 GiB + usage, as in regular mode.
  bool enforce_static_quota_for_incognito =
      (is_incognito &&
       base::FeatureList::IsEnabled(features::kIncognitoStaticStorageQuota));
  if ((clamped_total_space_bytes >= 10 * QuotaManagerImpl::kGBytes) ||
      enforce_static_quota_for_incognito) {
    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) {
    CHECK(manager, base::NotFatalUntil::M148);
    CHECK(callback_, base::NotFatalUntil::M148);
  }

  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_);
    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,
                     base::SysInfo::DiskSpaceInfo disk_space) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

    total_space_ = static_cast<int64_t>(disk_space.total.InBytes());
    available_space_ = static_cast<int64_t>(disk_space.available.InBytes());
    std::move(callback).Run();
  }

  void OnGotUsage(base::OnceClosure callback,
                  int64_t usage,
                  blink::mojom::UsageBreakdownPtr usage_breakdown) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    CHECK_GE(usage, -1, base::NotFatalUntil::M148);
    CHECK(usage_breakdown, base::NotFatalUntil::M148);
    CHECK_GE(usage_breakdown->backgroundFetch, 0, base::NotFatalUntil::M148);
    CHECK_GE(usage_breakdown->fileSystem, 0, base::NotFatalUntil::M148);
    CHECK_GE(usage_breakdown->indexedDatabase, 0, base::NotFatalUntil::M148);
    CHECK_GE(usage_breakdown->serviceWorker, 0, base::NotFatalUntil::M148);
    CHECK_GE(usage_breakdown->serviceWorkerCache, 0, base::NotFatalUntil::M148);

    usage_ = usage;
    usage_breakdown_ = std::move(usage_breakdown);
    std::move(callback).Run();
  }

  void SetDesiredStorageKeyQuota(int64_t quota) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    CHECK_GE(quota, 0, base::NotFatalUntil::M148);

    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)) {
    CHECK(callback_, base::NotFatalUntil::M148);
    CHECK(completion_closure_, base::NotFatalUntil::M148);
  }

  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) {
    CHECK(barrier_closure, base::NotFatalUntil::M148);

    settings_ = settings;
    std::move(barrier_closure).Run();
  }

  void OnGotCapacity(base::OnceClosure barrier_closure,
                     base::SysInfo::DiskSpaceInfo disk_space) {
    CHECK(barrier_closure, base::NotFatalUntil::M148);

    total_space_ = static_cast<int64_t>(disk_space.total.InBytes());
    available_space_ = static_cast<int64_t>(disk_space.available.InBytes());
    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) {
      CHECK(!global_usage_is_complete_, base::NotFatalUntil::M148);
      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_ref<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)) {
    CHECK(manager, base::NotFatalUntil::M148);
    CHECK(callback_, base::NotFatalUntil::M148);
  }

 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();
    CHECK(tracker, base::NotFatalUntil::M148);
    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)) {
    CHECK(manager_, base::NotFatalUntil::M148);
    CHECK(callback_, base::NotFatalUntil::M148);
  }

  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_);
    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)
      : manager_(manager),
        bucket_(bucket),
        quota_client_types_(std::move(quota_client_types)),
        commit_immediately_(commit_immediately) {}

  ~BucketDataDeleter() {
    // `callback` is non-null if the deleter gets destroyed before completing.
    if (callback_) {
      std::move(callback_).Run(base::unexpected(QuotaError::kUnknownError));
    }
  }

  void Run(base::OnceCallback<void(QuotaErrorOr<mojom::BucketTableEntryPtr>)>
               callback) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    callback_ = std::move(callback);
    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_BEGIN("browsing_data",
                          "QuotaManagerImpl::BucketDataDeleter",
                          perfetto::Track(++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();
    }
  }

 private:
  void DidDeleteBucketData(int tracing_id,
                           blink::mojom::QuotaStatusCode status) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    CHECK_GT(remaining_clients_, 0u, base::NotFatalUntil::M148);
    TRACE_EVENT_END("browsing_data",
                    /*"QuotaManagerImpl::BucketDataDeleter"*/
                    perfetto::Track(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::Complete,
                         weak_factory_.GetWeakPtr()));
      return;
    }
    if (error_count_ == 0) {
      Complete(base::ok(nullptr));
    } else {
      Complete(base::unexpected(QuotaError::kUnknownError));
    }
  }

  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(std::move(result));
  }

  SEQUENCE_CHECKER(sequence_checker_);
  const raw_ref<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(QuotaErrorOr<mojom::BucketTableEntryPtr>)> callback_
      GUARDED_BY_CONTEXT(sequence_checker_);

  base::WeakPtrFactory<BucketDataDeleter> weak_factory_
      GUARDED_BY_CONTEXT(sequence_checker_){this};
};

// Deletes a set of buckets.
class QuotaManagerImpl::BucketSetDataDeleter {
 public:
  explicit BucketSetDataDeleter(QuotaManagerImpl& manager)
      : manager_(manager) {}

  ~BucketSetDataDeleter() {
    if (callback_) {
      std::move(callback_).Run(blink::mojom::QuotaStatusCode::kErrorAbort);
    }
  }

  // `completion_callback` will be run to report the status of the deletion on
  // task completion. `completion_callback` will only be called while this
  // BucketSetDataDeleter instance is alive. `completion_callback` may destroy
  // this BucketSetDataDeleter instance.
  base::OnceCallback<void(QuotaErrorOr<std::set<BucketInfo>>)> RunCallback(
      base::OnceCallback<void(blink::mojom::QuotaStatusCode)>
          completion_callback) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    callback_ = std::move(completion_callback);
    return base::BindOnce(&BucketSetDataDeleter::Run,
                          weak_factory_.GetWeakPtr());
  }

 private:
  void Run(QuotaErrorOr<std::set<BucketInfo>> buckets) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    if (buckets.has_value()) {
      buckets_ = BucketInfosToBucketLocators(*buckets);
      if (!buckets_.empty()) {
        ScheduleBucketsDeletion();
        return;
      }
    }
    Complete(/*success=*/buckets.has_value());
  }

  void ScheduleBucketsDeletion() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    for (const auto& bucket : buckets_) {
      auto bucket_deleter = std::make_unique<BucketDataDeleter>(
          *manager_, bucket, AllQuotaClientTypes(),
          /*commit_immediately=*/false);
      auto* bucket_deleter_ptr = bucket_deleter.get();
      bucket_deleters_[bucket_deleter_ptr] = std::move(bucket_deleter);
      // 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
      bucket_deleter_ptr->Run(
          base::BindOnce(&BucketSetDataDeleter::DidDeleteBucketData,
                         weak_factory_.GetWeakPtr(), bucket_deleter_ptr));
    }
  }

  void DidDeleteBucketData(BucketDataDeleter* deleter,
                           QuotaErrorOr<mojom::BucketTableEntryPtr> entry) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

    DCHECK(bucket_deleters_.contains(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_);
    // May delete `this`.
    std::move(callback_).Run(
        success ? blink::mojom::QuotaStatusCode::kOk
                : blink::mojom::QuotaStatusCode::kErrorInvalidModification);
  }

  SEQUENCE_CHECKER(sequence_checker_);
  const raw_ref<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(blink::mojom::QuotaStatusCode)> callback_
      GUARDED_BY_CONTEXT(sequence_checker_);

  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)) {
    CHECK(manager, base::NotFatalUntil::M148);
    CHECK(callback_, base::NotFatalUntil::M148);
  }

 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) {
    CHECK(database, base::NotFatalUntil::M148);
    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,
    bool report_static_storage_quota)
    : 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)),
      get_settings_function_(get_settings_function),
      special_storage_policy_(std::move(special_storage_policy)),
      get_volume_info_fn_(&base::SysInfo::AmountOfDiskSpace),
      report_static_storage_quota_(report_static_storage_quota) {
  CHECK_EQ(settings_.refresh_interval, base::TimeDelta::Max(),
           base::NotFatalUntil::M148);
  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_);

  base::TaskTraits traits{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
                          base::TaskShutdownBehavior::BLOCK_SHUTDOWN};
  if (profile_path_.empty()) {
    db_runner_ = base::ThreadPool::CreateSequencedTaskRunner(traits);
  } else {
    // Note that this path is not quite what's actually used by the database,
    // but all it needs is to be unique relative to all other Chromium features.
    db_runner_ = base::ThreadPool::CreateSequencedTaskRunnerForResource(
        traits, profile_path_.AppendASCII(QuotaDatabase::kDatabaseName));
  }
}

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_);
  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) {
              CHECK(database, base::NotFatalUntil::M148);
              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_);
  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) {
            CHECK(database, base::NotFatalUntil::M148);
            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_);
  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) {
            CHECK(database, base::NotFatalUntil::M148);
            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_);
  EnsureDatabaseOpened();

  if (db_disabled_) {
    std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
    return;
  }
  PostTaskAndReplyWithResultForDBThread(
      base::BindOnce(
          [](const BucketId bucket_id, QuotaDatabase* database) {
            CHECK(database, base::NotFatalUntil::M148);
            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_);
  EnsureDatabaseOpened();

  if (db_disabled_) {
    std::move(callback).Run(std::set<StorageKey>());
    return;
  }
  PostTaskAndReplyWithResultForDBThread(
      base::BindOnce([](QuotaDatabase* database) {
        CHECK(database, base::NotFatalUntil::M148);
        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_);
  EnsureDatabaseOpened();

  if (db_disabled_) {
    std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
    return;
  }
  PostTaskAndReplyWithResultForDBThread(
      base::BindOnce([](QuotaDatabase* database) {
        CHECK(database, base::NotFatalUntil::M148);
        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_);
  EnsureDatabaseOpened();

  if (db_disabled_) {
    std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
    return;
  }
  PostTaskAndReplyWithResultForDBThread(
      base::BindOnce(
          [](const std::string& host, QuotaDatabase* database) {
            CHECK(database, base::NotFatalUntil::M148);
            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_);
  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) {
            CHECK(database, base::NotFatalUntil::M148);
            return database->GetBucketsForStorageKey(storage_key);
          },
          storage_key),
      std::move(reply));
}

void QuotaManagerImpl::GetUsageInfo(GetUsageInfoCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(callback, base::NotFatalUntil::M148);
  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_);
  CHECK(callback, base::NotFatalUntil::M148);

  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_);
  CHECK(callback, base::NotFatalUntil::M148);

  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_);
  if (report_static_storage_quota_ && !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) {
              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,
                     bool is_incognito,
                     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, is_incognito);

                    std::move(callback).Run(status, usage, reported_quota,
                                            std::move(usage_breakdown));
                  },
                  std::move(callback), status, usage, weak_this->is_incognito_,
                  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_);
  CHECK(callback, base::NotFatalUntil::M148);
  HandleGetUsageAndQuotaRequest(storage_key, std::move(callback));
}

void QuotaManagerImpl::HandleGetUsageAndQuotaRequest(
    const StorageKey& storage_key,
    UsageAndQuotaWithBreakdownAndOverrideFlagCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(callback, base::NotFatalUntil::M148);
  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_);
  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 (report_static_storage_quota_) {
    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) {

                        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, bool is_incognito,
                                   int64_t total_space, int64_t available_space,
                                   int64_t temp_pool_size) {
                                  int64_t reported_quota =
                                      CalculateReportedQuota(total_space, usage,
                                                             is_incognito);

                                  std::move(callback).Run(status, usage,
                                                          reported_quota);
                                },
                                std::move(callback), status, usage,
                                weak_this->is_incognito_));
                      },
                      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();
  CHECK(usage_tracker, base::NotFatalUntil::M148);

  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_);
  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_);
  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) {
            CHECK(database, base::NotFatalUntil::M148);
            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_);
  EnsureDatabaseOpened();

  if (db_disabled_) {
    std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
    return;
  }
  PostTaskAndReplyWithResultForDBThread(
      base::BindOnce(
          [](BucketId bucket, const base::Time& expiration,
             QuotaDatabase* database) {
            CHECK(database, base::NotFatalUntil::M148);
            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_);
  EnsureDatabaseOpened();

  if (db_disabled_) {
    std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
    return;
  }
  PostTaskAndReplyWithResultForDBThread(
      base::BindOnce(
          [](BucketId bucket, bool persistent, QuotaDatabase* database) {
            CHECK(database, base::NotFatalUntil::M148);
            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_);
  CHECK(callback, base::NotFatalUntil::M148);
  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_);
  EnsureDatabaseOpened();

  if (host.empty() || client_types_.empty()) {
    std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk);
    return;
  }

  GetBucketsForHost(host, CreateDeleteBucketSetCallback(std::move(callback)));
}

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;
  }

  GetBucketsForStorageKey(storage_key,
                          CreateDeleteBucketSetCallback(std::move(callback)));
}

base::OnceCallback<void(QuotaErrorOr<std::set<BucketInfo>>)>
QuotaManagerImpl::CreateDeleteBucketSetCallback(StatusCallback done) {
  auto buckets_deleter = std::make_unique<BucketSetDataDeleter>(*this);
  auto* buckets_deleter_ptr = buckets_deleter.get();
  bucket_set_data_deleters_[buckets_deleter_ptr] = std::move(buckets_deleter);
  return buckets_deleter_ptr->RunCallback(
      /*completion_callback=*/
      std::move(done).Then(base::BindOnce(&QuotaManagerImpl::DidDeleteBuckets,
                                          weak_factory_.GetWeakPtr(),
                                          buckets_deleter_ptr)));
}

void QuotaManagerImpl::DidDeleteBuckets(BucketSetDataDeleter* deleter) {
  CHECK(bucket_set_data_deleters_.erase(deleter));
}

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_);
  CHECK(callback, base::NotFatalUntil::M148);

  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::unique_ptr that keeps AccumulateQuotaInternalsInfo alive, and the
  // BarrierClosure will outlive the individual closures below.
  GetStorageCapacity(base::BindOnce(
      [](base::OnceClosure barrier_callback, AccumulateQuotaInternalsInfo* info,
         base::SysInfo::DiskSpaceInfo disk_space) {
        info->total_space = disk_space.total;
        info->available_space = disk_space.available;
        std::move(barrier_callback).Run();
      },
      barrier, base::Unretained(info_ptr)));
  GetQuotaSettings(base::BindOnce(
      [](base::OnceClosure barrier_callback, AccumulateQuotaInternalsInfo* info,
         const QuotaSettings& settings) {
        info->temp_pool_size = settings.pool_size;
        std::move(barrier_callback).Run();
      },
      barrier, base::Unretained(info_ptr)));
}

void QuotaManagerImpl::FinallySendDiskAvailabilityAndTempPoolSize(
    GetDiskAvailabilityAndTempPoolSizeCallback callback,
    std::unique_ptr<AccumulateQuotaInternalsInfo> info) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_GE(info->temp_pool_size, 0);
  std::move(callback).Run(static_cast<int64_t>(info->total_space.InBytes()),
                          static_cast<int64_t>(info->available_space.InBytes()),
                          info->temp_pool_size);
}

void QuotaManagerImpl::GetStatistics(GetStatisticsCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  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_);
  CHECK(callback, base::NotFatalUntil::M148);
  EnsureDatabaseOpened();

  UsageTracker* usage_tracker = GetUsageTracker();
  CHECK(usage_tracker, base::NotFatalUntil::M148);
  usage_tracker->GetGlobalUsage(std::move(callback));
}

void QuotaManagerImpl::GetGlobalUsageForInternals(
    GetGlobalUsageForInternalsCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(callback, base::NotFatalUntil::M148);
  EnsureDatabaseOpened();

  UsageTracker* usage_tracker = GetUsageTracker();
  CHECK(usage_tracker, base::NotFatalUntil::M148);
  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();
  CHECK(usage_tracker, base::NotFatalUntil::M148);
  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();
  CHECK(usage_tracker, base::NotFatalUntil::M148);
  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_);
  EnsureDatabaseOpened();

  if (db_disabled_) {
    std::move(callback).Run(std::set<BucketLocator>());
    return;
  }

  PostTaskAndReplyWithResultForDBThread(
      base::BindOnce(
          [](base::Time begin, base::Time end, QuotaDatabase* database) {
            CHECK(database, base::NotFatalUntil::M148);
            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();
  CHECK(previous_usage_tracker, base::NotFatalUntil::M148);
  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) {
        CHECK(database, base::NotFatalUntil::M148);
        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_);
  CHECK(is_bootstrapping_database_, base::NotFatalUntil::M148);
  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_);
  CHECK(!storage_key_gatherer_, base::NotFatalUntil::M148);
  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_);
  CHECK(storage_key_gatherer_, base::NotFatalUntil::M148);
  storage_key_gatherer_.reset();

  PostTaskAndReplyWithResultForDBThread(
      base::BindOnce(
          [](std::set<StorageKey> storage_keys, QuotaDatabase* database) {
            CHECK(database, base::NotFatalUntil::M148);
            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) {
        CHECK(database, base::NotFatalUntil::M148);
        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_);
  CHECK(is_bootstrapping_database_, base::NotFatalUntil::M148);
  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_);
  CHECK(!database_.get(), base::NotFatalUntil::M148)
      << "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) {
            CHECK(database, base::NotFatalUntil::M148);
            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_);
  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) {
            CHECK(database, base::NotFatalUntil::M148);
            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_);
  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_);
  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_);

  // 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, disk_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, disk_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_);
  EnsureDatabaseOpened();

  if (db_disabled_) {
    std::move(callback).Run(base::unexpected(QuotaError::kDatabaseDisabled));
    return;
  }

  PostTaskAndReplyWithResultForDBThread(
      base::BindOnce(
          [](const BucketLocator& bucket, bool commit_immediately,
             QuotaDatabase* database) {
            CHECK(database, base::NotFatalUntil::M148);
            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()) {
    CHECK(entry.value(), base::NotFatalUntil::M148);
    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_);
  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);
  auto* bucket_deleter_ptr = bucket_deleter.get();
  bucket_data_deleters_[bucket_deleter_ptr] = std::move(bucket_deleter);
  bucket_deleter_ptr->Run(std::move(callback).Then(
      base::BindOnce(&QuotaManagerImpl::DidDeleteBucketData,
                     weak_factory_.GetWeakPtr(), bucket_deleter_ptr)));
}

void QuotaManagerImpl::DidDeleteBucketData(BucketDataDeleter* deleter) {
  CHECK(bucket_data_deleters_.erase(deleter));
}

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,
    base::SysInfo::DiskSpaceInfo disk_space) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (disk_space.available >=
      kStoragePressureThresholdRatio * disk_space.total) {
    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);
  } else {
    storage_pressure_callback_.Run(std::move(storage_key));
  }
}

void QuotaManagerImpl::SimulateStoragePressure(const url::Origin& origin_url) {
  const StorageKey key = StorageKey::CreateFirstParty(origin_url);
  if (storage_pressure_callback_.is_null()) {
    return;
  }

  storage_pressure_callback_.Run(key);
}

void QuotaManagerImpl::IsSimulateStoragePressureAvailable(
    IsSimulateStoragePressureAvailableCallback callback) {
  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_);
  CHECK_GE(quota_size.value_or(0), 0, base::NotFatalUntil::M148)
      << "negative quota override: " << quota_size.value_or(0);

  if (quota_size.has_value()) {
    CHECK_GE(next_override_handle_id_, handle_id, base::NotFatalUntil::M148);
    // 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 (!devtools_overrides_.contains(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_);
  CHECK(!is_incognito_, base::NotFatalUntil::M148);

  GetGlobalUsage(
      base::BindOnce(&QuotaManagerImpl::DidGetGlobalUsageForHistogram,
                     weak_factory_.GetWeakPtr()));
}

void QuotaManagerImpl::DidGetGlobalUsageForHistogram(int64_t usage,
                                                     int64_t unlimited_usage) {
  CHECK_GE(usage, -1, base::NotFatalUntil::M148);
  CHECK_GE(unlimited_usage, -1, base::NotFatalUntil::M148);

  GetStorageCapacity(
      base::BindOnce(&QuotaManagerImpl::DidGetStorageCapacityForHistogram,
                     weak_factory_.GetWeakPtr(), usage));
}

void QuotaManagerImpl::DidGetStorageCapacityForHistogram(
    int64_t usage,
    base::SysInfo::DiskSpaceInfo disk_space) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_GE(usage, -1, base::NotFatalUntil::M148);

  int64_t total_space = static_cast<int64_t>(disk_space.total.InBytes());
  int64_t available_space =
      static_cast<int64_t>(disk_space.available.InBytes());

  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_);
  // 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_);
  CHECK(callback, base::NotFatalUntil::M148);
  EnsureDatabaseOpened();

  // This must not be called while there's an in-flight task.
  CHECK(!is_getting_eviction_bucket_, base::NotFatalUntil::M148);
  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_);
  CHECK(done, base::NotFatalUntil::M148);
  EnsureDatabaseOpened();

  if (db_disabled_) {
    std::move(done).Run(blink::mojom::QuotaStatusCode::kUnknown);
    return;
  }

  PostTaskAndReplyWithResultForDBThread(
      base::BindOnce(
          [](SpecialStoragePolicy* policy, QuotaDatabase* database) {
            CHECK(database, base::NotFatalUntil::M148);
            return database->GetExpiredBuckets(policy);
          },
          base::RetainedRef(special_storage_policy_)),
      CreateDeleteBucketSetCallback(std::move(done)));
}

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();

  CHECK(!eviction_helper_, base::NotFatalUntil::M148);
  eviction_helper_ = std::make_unique<EvictionRoundInfoHelper>(
      *this, std::move(callback),
      base::BindOnce(&QuotaManagerImpl::DidGetEvictionRoundInfo,
                     weak_factory_.GetWeakPtr()));
  eviction_helper_->Run();
}

void QuotaManagerImpl::DidGetEvictionRoundInfo() {
  CHECK(eviction_helper_, base::NotFatalUntil::M148);
  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_);
  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) {
            CHECK(database, base::NotFatalUntil::M148);
            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_);
  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_);
  CHECK(callback, base::NotFatalUntil::M148);

  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();
  CHECK_GE(temporary_usage, -1, base::NotFatalUntil::M148);

  int64_t available_space =
      std::max(int64_t{0}, settings.pool_size - temporary_usage);
  DidGetStorageCapacity(base::SysInfo::DiskSpaceInfo{
      .total = base::ByteSize(static_cast<uint64_t>(settings.pool_size)),
      .available = base::ByteSize(static_cast<uint64_t>(available_space))});
}

void QuotaManagerImpl::DidGetStorageCapacity(
    base::SysInfo::DiskSpaceInfo disk_space) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  cached_disk_stats_for_storage_pressure_ = {base::TimeTicks::Now(),
                                             disk_space};
  storage_capacity_callbacks_.Run(disk_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
                            : std::max(int64_t{1}, quota / kTypicalBucketUsage);
  CHECK_EQ(max_buckets == 0, IsStorageUnlimited(bucket_params.storage_key));

  PostTaskAndReplyWithResultForDBThread(
      base::BindOnce(
          [](const BucketInitParams& params, int max_buckets,
             QuotaDatabase* database) {
            CHECK(database, base::NotFatalUntil::M148);
            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_);
  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_);
  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_);
  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_);
  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_);
  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_);
  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_);
  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_);
  CHECK(task, base::NotFatalUntil::M148);
  CHECK(reply, base::NotFatalUntil::M148);
  // 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_);
  CHECK(task, base::NotFatalUntil::M148);
  CHECK(reply, base::NotFatalUntil::M148);
  // 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
base::SysInfo::DiskSpaceInfo QuotaManagerImpl::CallGetVolumeInfo(
    GetVolumeInfoFn get_volume_info_fn,
    const base::FilePath& path) {
  if (!base::CreateDirectory(path)) {
    LOG(WARNING) << "Create directory failed for path" << path.value();
    return {};
  }

  std::optional<base::SysInfo::DiskSpaceInfo> disk_space =
      get_volume_info_fn(path);
  if (!disk_space) {
    LOG(WARNING) << "Unable to get volume info: " << path.value();
    return {};
  }

  if (disk_space->available > disk_space->total) {
    // On Android, `statvfs()` may return `f_bavail > f_blocks`, producing
    // `available > total`. The root cause is unknown. See crbug.com/501709109
    // and crbug.com/520534362.
    disk_space->available = disk_space->total;
  }

  base::UmaHistogramMemoryMB("Quota.TotalDiskSpace", disk_space->total);
  base::UmaHistogramMemoryMB("Quota.AvailableDiskSpace", disk_space->available);
  return *disk_space;
}

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
