From 65cacb74ec0ba259fc27875d2b1cbac1345ce806 Mon Sep 17 00:00:00 2001 From: Nathan Memmott Date: Wed, 15 Mar 2023 23:29:19 +0000 Subject: [PATCH] Add a QuotaManager observer Adds a QuotaManager observer interface which can be implemented to listen for quota manager changes. Currently only listens for buckets being created, updated, or deleted. A mojom version of the interface has also been added for observers that exist on separate sequences/processes. Bug: 1406017 Change-Id: I4d36c74b0be890aa1257ec0127da7b6a603d92e4 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4193281 Reviewed-by: Will Harris Reviewed-by: Evan Stade Commit-Queue: Nathan Memmott Reviewed-by: Ayu Ishii Cr-Commit-Position: refs/heads/main@{#1117829} --- .../services/storage/public/mojom/BUILD.gn | 1 + .../storage/public/mojom/buckets/BUILD.gn | 13 +- .../public/mojom/buckets/bucket_info.mojom | 25 +++ .../mojom/buckets/bucket_info_mojom_traits.cc | 33 ++++ .../mojom/buckets/bucket_info_mojom_traits.h | 50 +++++ .../bucket_info_mojom_traits_unittest.cc | 48 +++++ content/browser/resources/quota/BUILD.gn | 11 +- storage/browser/quota/BUILD.gn | 11 +- storage/browser/quota/quota_database.cc | 3 +- .../browser/quota/quota_database_unittest.cc | 8 +- storage/browser/quota/quota_internals.mojom | 13 +- storage/browser/quota/quota_manager_impl.cc | 177 +++++++++++++----- storage/browser/quota/quota_manager_impl.h | 20 +- .../quota/quota_manager_observer.mojom | 18 ++ storage/browser/quota/quota_manager_proxy.cc | 29 ++- storage/browser/quota/quota_manager_proxy.h | 4 + .../browser/quota/quota_manager_unittest.cc | 166 +++++++++++++++- .../mojom/buckets/bucket_manager_host.mojom | 8 +- .../public/mojom/quota/quota_types.mojom | 7 + 19 files changed, 552 insertions(+), 93 deletions(-) create mode 100644 components/services/storage/public/mojom/buckets/bucket_info.mojom create mode 100644 components/services/storage/public/mojom/buckets/bucket_info_mojom_traits.cc create mode 100644 components/services/storage/public/mojom/buckets/bucket_info_mojom_traits.h create mode 100644 components/services/storage/public/mojom/buckets/bucket_info_mojom_traits_unittest.cc create mode 100644 storage/browser/quota/quota_manager_observer.mojom diff --git a/components/services/storage/public/mojom/BUILD.gn b/components/services/storage/public/mojom/BUILD.gn index e8e369f540e24..a56dc309a9df3 100644 --- a/components/services/storage/public/mojom/BUILD.gn +++ b/components/services/storage/public/mojom/BUILD.gn @@ -64,6 +64,7 @@ source_set("tests") { testonly = true sources = [ "buckets/bucket_id_mojom_traits_unittest.cc", + "buckets/bucket_info_mojom_traits_unittest.cc", "buckets/bucket_locator_mojom_traits_unittest.cc", ] deps = [ diff --git a/components/services/storage/public/mojom/buckets/BUILD.gn b/components/services/storage/public/mojom/buckets/BUILD.gn index 1f2b1fa40605d..0ae2ea82b6ebd 100644 --- a/components/services/storage/public/mojom/buckets/BUILD.gn +++ b/components/services/storage/public/mojom/buckets/BUILD.gn @@ -7,6 +7,7 @@ import("//mojo/public/tools/bindings/mojom.gni") mojom("buckets") { sources = [ "bucket_id.mojom", + "bucket_info.mojom", "bucket_locator.mojom", ] public_deps = [ @@ -28,6 +29,10 @@ mojom("buckets") { nullable_is_same_type = true copyable_pass_by_value = true }, + { + mojom = "storage.mojom.BucketInfo" + cpp = "::storage::BucketInfo" + }, { mojom = "storage.mojom.BucketLocator" cpp = "::storage::BucketLocator" @@ -35,14 +40,18 @@ mojom("buckets") { ] traits_headers = [ "bucket_id_mojom_traits.h", + "bucket_info_mojom_traits.h", "bucket_locator_mojom_traits.h", ] traits_sources = [ "bucket_id_mojom_traits.cc", + "bucket_info_mojom_traits.cc", "bucket_locator_mojom_traits.cc", ] - traits_public_deps = - [ "//components/services/storage/public/cpp/buckets" ] + traits_public_deps = [ + "//components/services/storage/public/cpp/buckets", + "//third_party/blink/public/mojom/storage_key", + ] }, ] diff --git a/components/services/storage/public/mojom/buckets/bucket_info.mojom b/components/services/storage/public/mojom/buckets/bucket_info.mojom new file mode 100644 index 0000000000000..306abb4f566dc --- /dev/null +++ b/components/services/storage/public/mojom/buckets/bucket_info.mojom @@ -0,0 +1,25 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module storage.mojom; + +import "mojo/public/mojom/base/time.mojom"; +import "third_party/blink/public/mojom/storage_key/storage_key.mojom"; +import "third_party/blink/public/mojom/quota/quota_types.mojom"; + +// Snapshot of a bucket's information in the quota database. +// +// Properties that can be updated by the Storage Buckets API, like +// `expiration` and `quota`, may get out of sync with the database. The +// database is the source of truth. +struct BucketInfo { + int64 id; + blink.mojom.StorageKey storage_key; + blink.mojom.StorageType type; + string name; + mojo_base.mojom.Time expiration; + uint64 quota; + bool persistent; + blink.mojom.BucketDurability durability; +}; diff --git a/components/services/storage/public/mojom/buckets/bucket_info_mojom_traits.cc b/components/services/storage/public/mojom/buckets/bucket_info_mojom_traits.cc new file mode 100644 index 0000000000000..3de3d12caa9df --- /dev/null +++ b/components/services/storage/public/mojom/buckets/bucket_info_mojom_traits.cc @@ -0,0 +1,33 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/services/storage/public/mojom/buckets/bucket_info_mojom_traits.h" + +#include "third_party/blink/public/common/storage_key/storage_key_mojom_traits.h" + +namespace mojo { + +// static +bool StructTraits:: + Read(storage::mojom::BucketInfoDataView data, storage::BucketInfo* out) { + blink::StorageKey storage_key; + if (!data.ReadStorageKey(&storage_key)) { + return false; + } + std::string name; + if (!data.ReadName(&name)) { + return false; + } + base::Time expiration; + if (!data.ReadExpiration(&expiration)) { + return false; + } + + *out = storage::BucketInfo(storage::BucketId(data.id()), storage_key, + data.type(), name, expiration, data.quota(), + data.persistent(), data.durability()); + return true; +} + +} // namespace mojo diff --git a/components/services/storage/public/mojom/buckets/bucket_info_mojom_traits.h b/components/services/storage/public/mojom/buckets/bucket_info_mojom_traits.h new file mode 100644 index 0000000000000..8cfdff91cbd76 --- /dev/null +++ b/components/services/storage/public/mojom/buckets/bucket_info_mojom_traits.h @@ -0,0 +1,50 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SERVICES_STORAGE_PUBLIC_MOJOM_BUCKETS_BUCKET_INFO_MOJOM_TRAITS_H_ +#define COMPONENTS_SERVICES_STORAGE_PUBLIC_MOJOM_BUCKETS_BUCKET_INFO_MOJOM_TRAITS_H_ + +#include "components/services/storage/public/cpp/buckets/bucket_info.h" +#include "components/services/storage/public/mojom/buckets/bucket_info.mojom-shared.h" +#include "mojo/public/cpp/bindings/struct_traits.h" + +namespace mojo { + +template <> +class StructTraits { + public: + static int64_t id(const storage::BucketInfo& bucket) { + return bucket.id.value(); + } + static const blink::StorageKey& storage_key( + const storage::BucketInfo& bucket) { + return bucket.storage_key; + } + static blink::mojom::StorageType type(const storage::BucketInfo& bucket) { + return bucket.type; + } + static base::StringPiece name(const storage::BucketInfo& bucket) { + return base::StringPiece(bucket.name.c_str(), bucket.name.length()); + } + static const base::Time& expiration(const storage::BucketInfo& bucket) { + return bucket.expiration; + } + static uint64_t quota(const storage::BucketInfo& bucket) { + return bucket.quota; + } + static bool persistent(const storage::BucketInfo& bucket) { + return bucket.persistent; + } + static blink::mojom::BucketDurability durability( + const storage::BucketInfo& bucket) { + return bucket.durability; + } + + static bool Read(storage::mojom::BucketInfoDataView data, + storage::BucketInfo* out); +}; + +} // namespace mojo + +#endif // COMPONENTS_SERVICES_STORAGE_PUBLIC_MOJOM_BUCKETS_BUCKET_INFO_MOJOM_TRAITS_H_ diff --git a/components/services/storage/public/mojom/buckets/bucket_info_mojom_traits_unittest.cc b/components/services/storage/public/mojom/buckets/bucket_info_mojom_traits_unittest.cc new file mode 100644 index 0000000000000..8a2c95c33caf5 --- /dev/null +++ b/components/services/storage/public/mojom/buckets/bucket_info_mojom_traits_unittest.cc @@ -0,0 +1,48 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/services/storage/public/mojom/buckets/bucket_info_mojom_traits.h" + +#include "components/services/storage/public/cpp/buckets/bucket_id.h" +#include "components/services/storage/public/cpp/buckets/bucket_info.h" +#include "components/services/storage/public/cpp/buckets/constants.h" +#include "components/services/storage/public/mojom/buckets/bucket_info.mojom.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/common/storage_key/storage_key.h" + +namespace storage { +namespace { + +TEST(BucketInfoMojomTraitsTest, SerializeAndDeserialize) { + BucketInfo test_keys[] = { + BucketInfo( + BucketId(1), + blink::StorageKey::CreateFromStringForTesting("http://example/"), + blink::mojom::StorageType::kTemporary, "default", base::Time(), 0, + true, blink::mojom::BucketDurability::kRelaxed), + BucketInfo( + BucketId(123), + blink::StorageKey::CreateFromStringForTesting("http://google.com/"), + blink::mojom::StorageType::kTemporary, "inbox", + base::Time::Now() + base::Days(1), 100, true, + blink::mojom::BucketDurability::kStrict), + BucketInfo( + BucketId(1000), + blink::StorageKey::CreateFromStringForTesting("http://test.com/"), + blink::mojom::StorageType::kTemporary, "drafts", + base::Time::Now() - base::Days(1), 1234, false, + blink::mojom::BucketDurability::kStrict), + }; + + for (auto& original : test_keys) { + BucketInfo copied; + EXPECT_TRUE(mojo::test::SerializeAndDeserialize(original, + copied)); + EXPECT_EQ(original, copied); + } +} + +} // namespace +} // namespace storage diff --git a/content/browser/resources/quota/BUILD.gn b/content/browser/resources/quota/BUILD.gn index ee5179de2bbbf..6c275152bf0fb 100644 --- a/content/browser/resources/quota/BUILD.gn +++ b/content/browser/resources/quota/BUILD.gn @@ -17,9 +17,14 @@ build_webui("build") { "quota_internals_browser_proxy.ts", ] - mojo_files_deps = [ "//storage/browser/quota:mojo_bindings_ts__generator" ] - mojo_files = - [ "$root_gen_dir/storage/browser/quota/quota_internals.mojom-webui.ts" ] + mojo_files_deps = [ + "//storage/browser/quota:mojo_bindings_ts__generator", + "//third_party/blink/public/mojom/quota:quota_ts__generator", + ] + mojo_files = [ + "$root_gen_dir/storage/browser/quota/quota_internals.mojom-webui.ts", + "$root_gen_dir/third_party/blink/public/mojom/quota/quota_types.mojom-webui.ts", + ] ts_tsconfig_base = "tsconfig_base.json" ts_deps = [ diff --git a/storage/browser/quota/BUILD.gn b/storage/browser/quota/BUILD.gn index 06989a81c490f..e360eafff49d6 100644 --- a/storage/browser/quota/BUILD.gn +++ b/storage/browser/quota/BUILD.gn @@ -5,11 +5,18 @@ import("//mojo/public/tools/bindings/mojom.gni") mojom("mojo_bindings") { - sources = [ "quota_internals.mojom" ] + sources = [ + "quota_internals.mojom", + "quota_manager_observer.mojom", + ] webui_module_path = "/" use_typescript_sources = true - public_deps = [ "//url/mojom:url_mojom_origin" ] + public_deps = [ + "//components/services/storage/public/mojom/buckets", + "//third_party/blink/public/mojom/quota", + "//url/mojom:url_mojom_origin", + ] disable_variants = true diff --git a/storage/browser/quota/quota_database.cc b/storage/browser/quota/quota_database.cc index 2309692e47ae6..f8643e5fbc383 100644 --- a/storage/browser/quota/quota_database.cc +++ b/storage/browser/quota/quota_database.cc @@ -115,8 +115,7 @@ mojom::BucketTableEntryPtr BucketTableEntryFromSqlStatement( mojom::BucketTableEntryPtr entry = mojom::BucketTableEntry::New(); entry->bucket_id = statement.ColumnInt64(0); entry->storage_key = statement.ColumnString(1); - entry->type = - static_cast(statement.ColumnInt(2)); + entry->type = static_cast(statement.ColumnInt(2)); entry->name = statement.ColumnString(3); entry->use_count = statement.ColumnInt(4); entry->last_accessed = statement.ColumnTime(5); diff --git a/storage/browser/quota/quota_database_unittest.cc b/storage/browser/quota/quota_database_unittest.cc index 2a7adc0d668db..0d7c407211acb 100644 --- a/storage/browser/quota/quota_database_unittest.cc +++ b/storage/browser/quota/quota_database_unittest.cc @@ -51,10 +51,10 @@ static const blink::mojom::StorageType kTemp = static const blink::mojom::StorageType kSync = blink::mojom::StorageType::kSyncable; -static const storage::mojom::StorageType kStorageTemp = - storage::mojom::StorageType::kTemporary; -static const storage::mojom::StorageType kStorageSync = - storage::mojom::StorageType::kSyncable; +static const blink::mojom::StorageType kStorageTemp = + blink::mojom::StorageType::kTemporary; +static const blink::mojom::StorageType kStorageSync = + blink::mojom::StorageType::kSyncable; static constexpr char kDatabaseName[] = "QuotaManager"; diff --git a/storage/browser/quota/quota_internals.mojom b/storage/browser/quota/quota_internals.mojom index 6362fbf2ab2d2..88806d6e410ff 100644 --- a/storage/browser/quota/quota_internals.mojom +++ b/storage/browser/quota/quota_internals.mojom @@ -6,12 +6,13 @@ module storage.mojom; import "mojo/public/mojom/base/time.mojom"; import "url/mojom/origin.mojom"; +import "third_party/blink/public/mojom/quota/quota_types.mojom"; // Represents a single Storage Bucket entry. struct BucketTableEntry { int64 bucket_id; string storage_key; - StorageType type; + blink.mojom.StorageType type; string name; int64 usage = -1; int64 use_count; @@ -19,14 +20,6 @@ struct BucketTableEntry { mojo_base.mojom.Time last_modified; }; -// Represents the Storage Type for a given host. -// This is a subset of blink::mojom::StorageType. -enum StorageType { - kTemporary = 0, - // kPersistent = 1, DEPRECATED - kSyncable = 2, -}; - // Interface for controlling Quota Internals. // Hosted on "chrome://quota-internals" for WebUI content::QuotaInternalsUI. interface QuotaInternalsHandler { @@ -49,7 +42,7 @@ interface QuotaInternalsHandler { RetrieveBucketsTable() => (array entries); // Returns the global usage and unlimited usage for a given storage type. - GetGlobalUsageForInternals(StorageType storage_type) => + GetGlobalUsageForInternals(blink.mojom.StorageType storage_type) => (int64 usage, int64 unlimited_usage); // Returns whether simulate storage pressure is available. diff --git a/storage/browser/quota/quota_manager_impl.cc b/storage/browser/quota/quota_manager_impl.cc index 8a332ab15db0c..a7cf23f4f4c18 100644 --- a/storage/browser/quota/quota_manager_impl.cc +++ b/storage/browser/quota/quota_manager_impl.cc @@ -48,18 +48,22 @@ #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 "storage/browser/quota/client_usage_tracker.h" #include "storage/browser/quota/quota_availability.h" #include "storage/browser/quota/quota_callbacks.h" #include "storage/browser/quota/quota_client_type.h" #include "storage/browser/quota/quota_features.h" #include "storage/browser/quota/quota_macros.h" +#include "storage/browser/quota/quota_manager_observer.mojom-forward.h" +#include "storage/browser/quota/quota_manager_observer.mojom.h" #include "storage/browser/quota/quota_manager_proxy.h" #include "storage/browser/quota/quota_override_handle.h" #include "storage/browser/quota/quota_temporary_storage_evictor.h" #include "storage/browser/quota/usage_tracker.h" #include "third_party/blink/public/common/storage_key/storage_key.h" #include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h" +#include "third_party/blink/public/mojom/storage_key/storage_key.mojom.h" #include "url/origin.h" using ::blink::StorageKey; @@ -95,15 +99,6 @@ bool IsSupportedIncognitoType(StorageType type) { return type == StorageType::kTemporary; } -StorageType GetBlinkStorageType(storage::mojom::StorageType type) { - switch (type) { - case storage::mojom::StorageType::kTemporary: - return StorageType::kTemporary; - case storage::mojom::StorageType::kSyncable: - return StorageType::kSyncable; - } -} - void DidGetUsageAndQuotaStripBreakdown( QuotaManagerImpl::UsageAndQuotaCallback callback, blink::mojom::QuotaStatusCode status, @@ -235,20 +230,23 @@ class QuotaManagerImpl::UsageAndQuotaInfoGatherer : public QuotaTask { int64_t quota = desired_storage_key_quota_; absl::optional quota_override_size = manager()->GetQuotaOverrideForStorageKey(storage_key_); - if (quota_override_size) + 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) + 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) + if (quota > temp_pool_free_space) { quota = available_space_ + usage_; + } } std::move(callback_).Run(blink::mojom::QuotaStatusCode::kOk, usage_, quota, @@ -505,8 +503,9 @@ class QuotaManagerImpl::GetUsageInfoTask : public QuotaTask { entries_.emplace_back(host_usage_pair.first, type, host_usage_pair.second); } - if (--remaining_trackers_ == 0) + if (--remaining_trackers_ == 0) { CallCompleted(); + } } void DidGetGlobalUsage(StorageType type, int64_t, int64_t) { @@ -542,8 +541,9 @@ class QuotaManagerImpl::StorageKeyGathererTask { #endif // DCHECK_IS_ON() size_t client_call_count = 0; - for (const auto& client_and_type : manager_->client_types_) + for (const auto& client_and_type : manager_->client_types_) { client_call_count += client_and_type.second.size(); + } // Registered clients can be empty in tests. if (!client_call_count) { @@ -685,8 +685,9 @@ class QuotaManagerImpl::BucketDataDeleter { } } - if (remaining_clients_ == 0) + if (remaining_clients_ == 0) { FinishDeletion(); + } } bool completed() { @@ -702,11 +703,13 @@ class QuotaManagerImpl::BucketDataDeleter { TRACE_EVENT_NESTABLE_ASYNC_END0( "browsing_data", "QuotaManagerImpl::BucketDataDeleter", tracing_id); - if (status != blink::mojom::QuotaStatusCode::kOk) + if (status != blink::mojom::QuotaStatusCode::kOk) { ++error_count_; + } - if (--remaining_clients_ == 0) + if (--remaining_clients_ == 0) { FinishDeletion(); + } } void FinishDeletion() { @@ -857,8 +860,9 @@ class QuotaManagerImpl::BucketSetDataDeleter { ++error_count_; } - if (bucket_deleters_.empty()) + if (bucket_deleters_.empty()) { Complete(/*success=*/error_count_ == 0); + } } void Complete(bool success) { @@ -1085,7 +1089,8 @@ void QuotaManagerImpl::GetOrCreateBucketDeprecated( }, bucket_params, storage_type), base::BindOnce(&QuotaManagerImpl::DidGetBucket, - weak_factory_.GetWeakPtr(), std::move(callback))); + weak_factory_.GetWeakPtr(), + /*notify_update_bucket=*/true, std::move(callback))); } void QuotaManagerImpl::CreateBucketForTesting( @@ -1111,7 +1116,8 @@ void QuotaManagerImpl::CreateBucketForTesting( }, storage_key, bucket_name, storage_type), base::BindOnce(&QuotaManagerImpl::DidGetBucket, - weak_factory_.GetWeakPtr(), std::move(callback))); + weak_factory_.GetWeakPtr(), /*notify_update_bucket=*/true, + std::move(callback))); } void QuotaManagerImpl::GetBucketForTesting( @@ -1136,7 +1142,8 @@ void QuotaManagerImpl::GetBucketForTesting( }, storage_key, bucket_name, type), base::BindOnce(&QuotaManagerImpl::DidGetBucket, - weak_factory_.GetWeakPtr(), std::move(callback))); + weak_factory_.GetWeakPtr(), /*notify_update_bucket=*/false, + std::move(callback))); } void QuotaManagerImpl::GetBucketById( @@ -1158,7 +1165,8 @@ void QuotaManagerImpl::GetBucketById( }, bucket_id), base::BindOnce(&QuotaManagerImpl::DidGetBucket, - weak_factory_.GetWeakPtr(), std::move(callback))); + weak_factory_.GetWeakPtr(), /*notify_update_bucket=*/false, + std::move(callback))); } void QuotaManagerImpl::GetStorageKeysForType(blink::mojom::StorageType type, @@ -1441,7 +1449,8 @@ void QuotaManagerImpl::UpdateBucketExpiration( }, bucket, expiration), base::BindOnce(&QuotaManagerImpl::DidGetBucket, - weak_factory_.GetWeakPtr(), std::move(callback))); + weak_factory_.GetWeakPtr(), + /*notify_update_bucket=*/true, std::move(callback))); } void QuotaManagerImpl::UpdateBucketPersistence( @@ -1464,7 +1473,8 @@ void QuotaManagerImpl::UpdateBucketPersistence( }, bucket, persistent), base::BindOnce(&QuotaManagerImpl::DidGetBucket, - weak_factory_.GetWeakPtr(), std::move(callback))); + weak_factory_.GetWeakPtr(), + /*notify_update_bucket=*/true, std::move(callback))); } void QuotaManagerImpl::PerformStorageCleanup( @@ -1509,8 +1519,9 @@ void QuotaManagerImpl::DidDeleteBuckets( DCHECK(deleter); DCHECK(deleter->completed()); - if (quota_manager) + if (quota_manager) { quota_manager->bucket_set_data_deleters_.erase(deleter); + } std::move(callback).Run(status_code); } @@ -1612,14 +1623,13 @@ void QuotaManagerImpl::GetGlobalUsage(StorageType type, } void QuotaManagerImpl::GetGlobalUsageForInternals( - storage::mojom::StorageType storage_type, + blink::mojom::StorageType storage_type, GetGlobalUsageForInternalsCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(callback); EnsureDatabaseOpened(); - StorageType type = GetBlinkStorageType(storage_type); - UsageTracker* usage_tracker = GetUsageTracker(type); + UsageTracker* usage_tracker = GetUsageTracker(storage_type); DCHECK(usage_tracker); usage_tracker->GetGlobalUsage(std::move(callback)); } @@ -1654,10 +1664,12 @@ bool QuotaManagerImpl::IsStorageUnlimited(const StorageKey& storage_key, // For syncable storage we should always enforce quota (since the // quota must be capped by the server limit). - if (type == StorageType::kSyncable) + if (type == StorageType::kSyncable) { return false; - if (type == StorageType::kDeprecatedQuotaNotManaged) + } + if (type == StorageType::kDeprecatedQuotaNotManaged) { return true; + } return special_storage_policy_.get() && special_storage_policy_->IsStorageUnlimited( storage_key.origin().GetURL()); @@ -1714,8 +1726,9 @@ bool QuotaManagerImpl::ResetUsageTracker(StorageType type) { UsageTracker* previous_usage_tracker = GetUsageTracker(type); DCHECK(previous_usage_tracker); - if (previous_usage_tracker->IsWorking()) + if (previous_usage_tracker->IsWorking()) { return false; + } auto usage_tracker = std::make_unique( this, client_types_[type], type, special_storage_policy_.get()); @@ -1739,8 +1752,9 @@ QuotaManagerImpl::~QuotaManagerImpl() { proxy_->InvalidateQuotaManagerImpl(base::PassKey()); - if (database_) + if (database_) { db_runner_->DeleteSoon(FROM_HERE, std::move(database_)); + } } QuotaManagerImpl::EvictionContext::EvictionContext() = default; @@ -1776,8 +1790,9 @@ void QuotaManagerImpl::EnsureDatabaseOpened() { &QuotaManagerImpl::ReportHistogram); } - if (bootstrap_disabled_for_testing_) + if (bootstrap_disabled_for_testing_) { return; + } is_bootstrapping_database_ = true; db_runner_->PostTaskAndReplyWithResult( @@ -1859,8 +1874,9 @@ void QuotaManagerImpl::DidSetDatabaseBootstrapped(QuotaError error) { } void QuotaManagerImpl::RunDatabaseCallbacks() { - for (auto& callback : database_callbacks_) + for (auto& callback : database_callbacks_) { std::move(callback).Run(); + } database_callbacks_.clear(); } @@ -1875,8 +1891,9 @@ void QuotaManagerImpl::RegisterClient( clients_for_ownership_.emplace_back(std::move(client)); mojom::QuotaClient* client_ptr = clients_for_ownership_.back().get(); - for (blink::mojom::StorageType storage_type : storage_types) + for (blink::mojom::StorageType storage_type : storage_types) { client_types_[storage_type].insert({client_ptr, client_type}); + } } UsageTracker* QuotaManagerImpl::GetUsageTracker(StorageType type) const { @@ -1905,8 +1922,9 @@ void QuotaManagerImpl::NotifyBucketAccessed(const BucketLocator& bucket, access_notified_buckets_.insert(bucket); } - if (db_disabled_) + if (db_disabled_) { return; + } PostTaskAndReplyWithResultForDBThread( base::BindOnce( [](BucketLocator bucket, base::Time accessed_time, @@ -1995,8 +2013,9 @@ void QuotaManagerImpl::RetrieveBucketUsageForBucketTable( StorageType type = static_cast(entry->type); // TODO(crbug.com/1175113): Change to DCHECK once persistent type is removed // from QuotaDatabase. - if (!IsSupportedType(type)) + if (!IsSupportedType(type)) { continue; + } absl::optional storage_key = StorageKey::Deserialize(entry->storage_key); @@ -2033,8 +2052,9 @@ void QuotaManagerImpl::AddBucketTableEntry( void QuotaManagerImpl::StartEviction() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (eviction_disabled_) + if (eviction_disabled_) { return; + } if (!temporary_storage_evictor_) { temporary_storage_evictor_ = std::make_unique( this, kEvictionIntervalInMilliSeconds); @@ -2069,7 +2089,8 @@ void QuotaManagerImpl::DeleteBucketFromDatabase( return result; }, bucket, commit_immediately), - std::move(callback)); + base::BindOnce(&QuotaManagerImpl::OnBucketDeleted, + weak_factory_.GetWeakPtr(), std::move(callback))); } void QuotaManagerImpl::DidEvictBucketData( @@ -2130,8 +2151,9 @@ void QuotaManagerImpl::DidDeleteBucketData( DCHECK(deleter); DCHECK(deleter->completed()); - if (quota_manager) + if (quota_manager) { quota_manager->bucket_data_deleters_.erase(deleter); + } std::move(callback).Run(std::move(result)); } @@ -2158,8 +2180,9 @@ void QuotaManagerImpl::MaybeRunStoragePressureCallback( // TODO(https://crbug.com/1059560): Figure out what 0 total_space means // and how to handle the storage pressure callback in these cases. - if (total_space == 0) + if (total_space == 0) { return; + } if (!storage_pressure_callback_) { // Quota will hold onto a storage pressure notification if no storage @@ -2355,20 +2378,24 @@ void QuotaManagerImpl::DidDumpBucketTableForHistogram( GetUsageTracker(StorageType::kTemporary)->GetCachedStorageKeysUsage(); base::Time now = QuotaDatabase::GetNow(); for (const auto& info : entries) { - if (info->type != storage::mojom::StorageType::kTemporary) + if (info->type != blink::mojom::StorageType::kTemporary) { continue; + } absl::optional storage_key = StorageKey::Deserialize(info->storage_key); - if (!storage_key.has_value()) + if (!storage_key.has_value()) { continue; + } - if (storage_key.value().nonce().has_value()) + if (storage_key.value().nonce().has_value()) { nonce_count += 1; + } auto it = usage_map.find(*storage_key); - if (it == usage_map.end() || it->second == 0) + if (it == usage_map.end() || it->second == 0) { continue; + } base::TimeDelta age = now - std::max(info->last_accessed, info->last_modified); @@ -2387,8 +2414,9 @@ std::set QuotaManagerImpl::GetEvictionBucketExceptions() { std::set exceptions; for (const auto& p : buckets_in_error_) { - if (p.second > QuotaManagerImpl::kThresholdOfErrorsToBeDenylisted) + if (p.second > QuotaManagerImpl::kThresholdOfErrorsToBeDenylisted) { exceptions.insert(p.first); + } } return exceptions; @@ -2546,8 +2574,9 @@ void QuotaManagerImpl::GetQuotaSettings(QuotaSettingsCallback callback) { return; } - if (!settings_callbacks_.Add(std::move(callback))) + 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. @@ -2578,8 +2607,9 @@ void QuotaManagerImpl::GetStorageCapacity(StorageCapacityCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(callback); - if (!storage_capacity_callbacks_.Add(std::move(callback))) + if (!storage_capacity_callbacks_.Add(std::move(callback))) { return; + } if (is_incognito_) { GetQuotaSettings( base::BindOnce(&QuotaManagerImpl::ContinueIncognitoGetStorageCapacity, @@ -2629,13 +2659,15 @@ void QuotaManagerImpl::DidDatabaseWork(bool success, bool is_bootstrap_work) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); base::UmaHistogramBoolean("Quota.QuotaDatabaseResultSuccess", success); - if (success) + if (success) { return; + } // Ignore any errors that happen while a new bootstrap attempt is already in // progress or queued. - if (is_bootstrapping_database_) + if (is_bootstrapping_database_) { return; + } db_error_count_++; @@ -2686,6 +2718,39 @@ void QuotaManagerImpl::OnComplete(QuotaError result) { DidDatabaseWork(result != QuotaError::kDatabaseError); } +void QuotaManagerImpl::NotifyUpdatedBucket( + const QuotaErrorOr& 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)> callback, + QuotaErrorOr result) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (result.has_value()) { + const mojom::BucketTableEntryPtr& entry = result.value(); + StorageType type = static_cast(entry->type); + if (IsSupportedType(type)) { + auto storage_key = blink::StorageKey::Deserialize(entry->storage_key); + if (storage_key) { + storage::BucketLocator bucket_locator( + BucketId(entry->bucket_id), storage_key.value(), type, + 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)> callback, @@ -2710,11 +2775,16 @@ void QuotaManagerImpl::DidGetQuotaSettingsForBucketCreation( } void QuotaManagerImpl::DidGetBucket( + bool notify_update_bucket, base::OnceCallback)> callback, QuotaErrorOr result) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(callback); + if (notify_update_bucket) { + NotifyUpdatedBucket(result); + } + DidDatabaseWork(result.has_value() || result.error() != QuotaError::kDatabaseError); std::move(callback).Run(std::move(result)); @@ -2740,6 +2810,7 @@ void QuotaManagerImpl::DidGetBucketCheckExpiration( return; } + NotifyUpdatedBucket(result); std::move(callback).Run(std::move(result)); } @@ -3003,4 +3074,10 @@ QuotaAvailability QuotaManagerImpl::GetVolumeInfo(const base::FilePath& path) { base::SysInfo::AmountOfFreeDiskSpace(path)); } +void QuotaManagerImpl::AddObserver( + mojo::PendingRemote observer) { + // `MojoQuotaManagerObserver` is self owned and deletes itself when its remote + // is disconnected. + observers_.Add(std::move(observer)); +} } // namespace storage diff --git a/storage/browser/quota/quota_manager_impl.h b/storage/browser/quota/quota_manager_impl.h index ffef5e8bd241b..9598b8674ecf3 100644 --- a/storage/browser/quota/quota_manager_impl.h +++ b/storage/browser/quota/quota_manager_impl.h @@ -21,6 +21,8 @@ #include "base/memory/ref_counted.h" #include "base/memory/ref_counted_delete_on_sequence.h" #include "base/memory/weak_ptr.h" +#include "base/observer_list_types.h" +#include "base/scoped_observation_traits.h" #include "base/sequence_checker.h" #include "base/time/time.h" #include "base/timer/timer.h" @@ -32,11 +34,13 @@ #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/receiver_set.h" #include "mojo/public/cpp/bindings/remote.h" +#include "mojo/public/cpp/bindings/remote_set.h" #include "storage/browser/quota/quota_availability.h" #include "storage/browser/quota/quota_callbacks.h" #include "storage/browser/quota/quota_client_type.h" #include "storage/browser/quota/quota_database.h" #include "storage/browser/quota/quota_internals.mojom.h" +#include "storage/browser/quota/quota_manager_observer.mojom.h" #include "storage/browser/quota/quota_settings.h" #include "storage/browser/quota/quota_task.h" #include "storage/browser/quota/special_storage_policy.h" @@ -380,7 +384,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl void GetStatistics(GetStatisticsCallback callback) override; void RetrieveBucketsTable(RetrieveBucketsTableCallback callback) override; void GetGlobalUsageForInternals( - storage::mojom::StorageType storage_type, + blink::mojom::StorageType storage_type, GetGlobalUsageForInternalsCallback callback) override; // Used from quota-internals page to test behavior of the storage pressure // callback. @@ -489,6 +493,9 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl bool is_db_disabled_for_testing() { return db_disabled_; } + void AddObserver( + mojo::PendingRemote observer); + protected: ~QuotaManagerImpl() override; void SetQuotaChangeCallbackForTesting( @@ -671,11 +678,18 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl void DidRazeForReBootstrap(QuotaError raze_and_reopen_result); void OnComplete(QuotaError result); + void NotifyUpdatedBucket(const QuotaErrorOr& result); + void OnBucketDeleted( + base::OnceCallback)> + callback, + QuotaErrorOr result); + void DidGetQuotaSettingsForBucketCreation( const BucketInitParams& bucket_params, base::OnceCallback)> callback, const QuotaSettings& settings); - void DidGetBucket(base::OnceCallback)> callback, + void DidGetBucket(bool notify_update_bucket, + base::OnceCallback)> callback, QuotaErrorOr result); void DidGetBucketCheckExpiration( const BucketInitParams& params, @@ -831,6 +845,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl bucket_data_deleters_; std::unique_ptr storage_key_gatherer_; + mojo::RemoteSet observers_; + SEQUENCE_CHECKER(sequence_checker_); base::WeakPtrFactory weak_factory_{this}; diff --git a/storage/browser/quota/quota_manager_observer.mojom b/storage/browser/quota/quota_manager_observer.mojom new file mode 100644 index 0000000000000..896f30343d035 --- /dev/null +++ b/storage/browser/quota/quota_manager_observer.mojom @@ -0,0 +1,18 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module storage.mojom; + +import "components/services/storage/public/mojom/buckets/bucket_info.mojom"; +import "components/services/storage/public/mojom/buckets/bucket_locator.mojom"; + +// Interface for an observer of the QuotaManager. QuotaManager calls these +// functions to notify the observer of events. This is used by Devtools. +interface QuotaManagerObserver { + // Called when a bucket is created or updated. + OnCreateOrUpdateBucket(BucketInfo bucket_info); + + // Called when a bucket is deleted. + OnDeleteBucket(BucketLocator bucket_locator); +}; diff --git a/storage/browser/quota/quota_manager_proxy.cc b/storage/browser/quota/quota_manager_proxy.cc index 6480f2b6686a7..4a05270255ad9 100644 --- a/storage/browser/quota/quota_manager_proxy.cc +++ b/storage/browser/quota/quota_manager_proxy.cc @@ -87,8 +87,9 @@ void QuotaManagerProxy::BindInternalsHandler( return; } DCHECK_CALLED_ON_VALID_SEQUENCE(quota_manager_impl_sequence_checker_); - if (quota_manager_impl_) + if (quota_manager_impl_) { quota_manager_impl_->BindInternalsHandler(std::move(receiver)); + } } void QuotaManagerProxy::UpdateOrCreateBucket( @@ -409,8 +410,9 @@ void QuotaManagerProxy::NotifyBucketAccessed(const BucketLocator& bucket, } DCHECK_CALLED_ON_VALID_SEQUENCE(quota_manager_impl_sequence_checker_); - if (quota_manager_impl_) + if (quota_manager_impl_) { quota_manager_impl_->NotifyBucketAccessed(bucket, access_time); + } } void QuotaManagerProxy::NotifyBucketModified( @@ -454,8 +456,9 @@ void QuotaManagerProxy::NotifyWriteFailed(const StorageKey& storage_key) { } DCHECK_CALLED_ON_VALID_SEQUENCE(quota_manager_impl_sequence_checker_); - if (quota_manager_impl_) + if (quota_manager_impl_) { quota_manager_impl_->NotifyWriteFailed(storage_key); + } } void QuotaManagerProxy::SetUsageCacheEnabled(QuotaClientType client_id, @@ -604,8 +607,9 @@ void QuotaManagerProxy::WithdrawOverridesForHandle(int handle_id) { } DCHECK_CALLED_ON_VALID_SEQUENCE(quota_manager_impl_sequence_checker_); - if (quota_manager_impl_) + if (quota_manager_impl_) { quota_manager_impl_->WithdrawOverridesForHandle(handle_id); + } } void QuotaManagerProxy::GetOverrideHandleId( @@ -638,4 +642,21 @@ void QuotaManagerProxy::InvalidateQuotaManagerImpl( quota_manager_impl_ = nullptr; } +void QuotaManagerProxy::AddObserver( + mojo::PendingRemote observer) { + if (!quota_manager_impl_task_runner_->RunsTasksInCurrentSequence()) { + quota_manager_impl_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&QuotaManagerProxy::AddObserver, this, + std::move(observer))); + return; + } + + DCHECK_CALLED_ON_VALID_SEQUENCE(quota_manager_impl_sequence_checker_); + if (!quota_manager_impl_) { + return; + } + + quota_manager_impl_->AddObserver(std::move(observer)); +} + } // namespace storage diff --git a/storage/browser/quota/quota_manager_proxy.h b/storage/browser/quota/quota_manager_proxy.h index 794ab339f5f41..2a2bc4fe9f023 100644 --- a/storage/browser/quota/quota_manager_proxy.h +++ b/storage/browser/quota/quota_manager_proxy.h @@ -241,6 +241,10 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerProxy // This method may only be called on the QuotaManagerImpl sequence. void InvalidateQuotaManagerImpl(base::PassKey); + // Adds an observer which is notified of changes to the QuotaManager. + void AddObserver( + mojo::PendingRemote observer); + protected: friend class base::RefCountedThreadSafe; friend class base::DeleteHelper; diff --git a/storage/browser/quota/quota_manager_unittest.cc b/storage/browser/quota/quota_manager_unittest.cc index 0d8189bf4013d..7aa9f44b58d01 100644 --- a/storage/browser/quota/quota_manager_unittest.cc +++ b/storage/browser/quota/quota_manager_unittest.cc @@ -33,6 +33,7 @@ #include "base/test/task_environment.h" #include "base/test/test_future.h" #include "base/time/time.h" +#include "components/services/storage/public/cpp/buckets/bucket_info.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" @@ -51,6 +52,7 @@ #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/storage_key/storage_key.h" +#include "third_party/blink/public/mojom/buckets/bucket_manager_host.mojom-shared.h" #include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h" #include "url/gurl.h" @@ -66,10 +68,10 @@ namespace { const StorageType kTemp = StorageType::kTemporary; const StorageType kSync = StorageType::kSyncable; -const storage::mojom::StorageType kStorageTemp = - storage::mojom::StorageType::kTemporary; -const storage::mojom::StorageType kStorageSync = - storage::mojom::StorageType::kSyncable; +const blink::mojom::StorageType kStorageTemp = + blink::mojom::StorageType::kTemporary; +const blink::mojom::StorageType kStorageSync = + blink::mojom::StorageType::kSyncable; // Values in bytes. const int64_t kAvailableSpaceForApp = 13377331U; @@ -575,13 +577,74 @@ class QuotaManagerImplTest : public testing::Test { } const QuotaSettings& settings() const { return settings_; } + void SetupQuotaManagerObserver() { + quota_manager_observer_run_loop_ = std::make_unique(); + quota_manager_observer_test_ = + std::make_unique(weak_factory_.GetWeakPtr()); + } + + void RunUntilObserverNotifies() { + quota_manager_observer_run_loop_->Run(); + quota_manager_observer_run_loop_ = std::make_unique(); + } + protected: + enum ObserverNotifyType { + kCreateOrUpdate, + kDelete, + }; + struct ObserverNotification { + explicit ObserverNotification(BucketInfo bucket) + : type(ObserverNotifyType::kCreateOrUpdate), bucket_info(bucket) {} + explicit ObserverNotification(BucketLocator locator) + : type(ObserverNotifyType::kDelete), bucket_locator(locator) {} + ObserverNotifyType type; + absl::optional bucket_info; + absl::optional bucket_locator; + }; + base::test::ScopedFeatureList scoped_feature_list_; base::test::TaskEnvironment task_environment_; base::ScopedTempDir data_dir_; scoped_refptr quota_manager_impl_; + std::vector observer_notifications_; private: + class QuotaManagerObserverTest : storage::mojom::QuotaManagerObserver { + public: + explicit QuotaManagerObserverTest(base::WeakPtr owner) + : owner_(owner) { + owner_->quota_manager_impl_->AddObserver( + receiver_.BindNewPipeAndPassRemote()); + } + + QuotaManagerObserverTest(const QuotaManagerObserverTest&) = delete; + QuotaManagerObserverTest& operator=(const QuotaManagerObserverTest&) = + delete; + + ~QuotaManagerObserverTest() override = default; + + void OnCreateOrUpdateBucket( + const storage::BucketInfo& bucket_info) override { + owner_->observer_notifications_.emplace_back(bucket_info); + QuitRunLoop(); + } + + void OnDeleteBucket(const storage::BucketLocator& bucket_locator) override { + owner_->observer_notifications_.emplace_back(bucket_locator); + QuitRunLoop(); + } + + private: + void QuitRunLoop() { + if (owner_->quota_manager_observer_run_loop_) { + owner_->quota_manager_observer_run_loop_->Quit(); + } + } + base::WeakPtr owner_; + mojo::Receiver receiver_{this}; + }; + base::Time IncrementMockTime() { ++mock_time_counter_; return base::Time::FromDoubleT(mock_time_counter_ * 10.0); @@ -596,6 +659,8 @@ class QuotaManagerImplTest : public testing::Test { int64_t available_space_; absl::optional eviction_bucket_; QuotaSettings settings_; + std::unique_ptr quota_manager_observer_test_; + std::unique_ptr quota_manager_observer_run_loop_; int additional_callback_count_; @@ -2221,8 +2286,9 @@ TEST_F(QuotaManagerImplTest, EvictBucketDataWithDeletionError) { ToStorageKey("http://foo.com/"), kSync) .usage); - for (const ClientBucketData& data : kData) + for (const ClientBucketData& data : kData) { NotifyDefaultBucketAccessed(ToStorageKey(data.origin), data.type); + } task_environment_.RunUntilIdle(); auto bucket = @@ -2395,8 +2461,9 @@ TEST_F(QuotaManagerImplTest, DeleteHostDataMultiple) { const BucketTableEntries& entries = DumpBucketTable(); for (const auto& entry : entries) { - if (entry->type != kStorageTemp) + if (entry->type != kStorageTemp) { continue; + } absl::optional storage_key = StorageKey::Deserialize(entry->storage_key); @@ -2491,8 +2558,9 @@ TEST_F(QuotaManagerImplTest, DeleteHostDataMultipleClientsDifferentTypes) { const BucketTableEntries& entries = DumpBucketTable(); for (const auto& entry : entries) { - if (entry->type != kStorageSync) + if (entry->type != kStorageSync) { continue; + } absl::optional storage_key = StorageKey::Deserialize(entry->storage_key); @@ -3524,4 +3592,88 @@ TEST_F(QuotaManagerImplTest, SimulateStoragePressure_Incognito) { EXPECT_FALSE(callback_ran); } +TEST_F(QuotaManagerImplTest, + QuotaManagerObserver_NotifiedOnAddedChangedAndDeleted) { + auto clock = std::make_unique(); + QuotaDatabase::SetClockForTesting(clock.get()); + clock->SetNow(base::Time::Now()); + + SetupQuotaManagerObserver(); + + BucketInitParams params(ToStorageKey("http://a.com/"), "bucket_a"); + + // Create bucket. + auto bucket = UpdateOrCreateBucket(params); + RunUntilObserverNotifies(); + + ASSERT_TRUE(bucket.has_value()); + ASSERT_EQ(observer_notifications_.size(), 1U); + ObserverNotification notification = observer_notifications_[0]; + ASSERT_EQ(notification.type, kCreateOrUpdate); + ASSERT_EQ(notification.bucket_info, bucket.value()); + observer_notifications_.clear(); + + params.persistent = true; + params.expiration = clock->Now() + base::Days(1); + + // Update bucket. + auto updated_bucket = UpdateOrCreateBucket(params); + RunUntilObserverNotifies(); + + ASSERT_TRUE(updated_bucket.has_value()); + ASSERT_EQ(observer_notifications_.size(), 1U); + notification = observer_notifications_[0]; + ASSERT_EQ(notification.type, kCreateOrUpdate); + EXPECT_EQ(notification.bucket_info, updated_bucket.value()); + EXPECT_EQ(notification.bucket_info->persistent, params.persistent); + EXPECT_EQ(notification.bucket_info->expiration, params.expiration); + observer_notifications_.clear(); + + // Delete bucket. + auto status = + DeleteBucketData(bucket->ToBucketLocator(), AllQuotaClientTypes()); + RunUntilObserverNotifies(); + + ASSERT_EQ(status, QuotaStatusCode::kOk); + ASSERT_EQ(observer_notifications_.size(), 1U); + notification = observer_notifications_[0]; + ASSERT_EQ(notification.type, kDelete); + EXPECT_EQ(notification.bucket_locator, updated_bucket->ToBucketLocator()); + + QuotaDatabase::SetClockForTesting(nullptr); +} + +TEST_F(QuotaManagerImplTest, QuotaManagerObserver_NotifiedOnExpired) { + auto clock = std::make_unique(); + QuotaDatabase::SetClockForTesting(clock.get()); + clock->SetNow(base::Time::Now()); + + SetupQuotaManagerObserver(); + + BucketInitParams params(ToStorageKey("http://a.com/"), "bucket_a"); + params.expiration = clock->Now() + base::Days(5); + + auto bucket = UpdateOrCreateBucket(params); + RunUntilObserverNotifies(); + + ASSERT_TRUE(bucket.has_value()); + ASSERT_EQ(observer_notifications_.size(), 1U); + ObserverNotification notification = observer_notifications_[0]; + ASSERT_EQ(notification.type, kCreateOrUpdate); + ASSERT_EQ(notification.bucket_info, bucket.value()); + observer_notifications_.clear(); + + clock->Advance(base::Days(20)); + base::test::TestFuture future; + quota_manager_impl_->EvictExpiredBuckets(future.GetCallback()); + EXPECT_EQ(QuotaStatusCode::kOk, future.Get()); + + EXPECT_FALSE(GetBucketById(bucket->id).has_value()); + ASSERT_EQ(observer_notifications_.size(), 1U); + notification = observer_notifications_[0]; + ASSERT_EQ(notification.type, kDelete); + EXPECT_EQ(notification.bucket_locator, bucket->ToBucketLocator()); + + QuotaDatabase::SetClockForTesting(nullptr); +} } // namespace storage diff --git a/third_party/blink/public/mojom/buckets/bucket_manager_host.mojom b/third_party/blink/public/mojom/buckets/bucket_manager_host.mojom index 6256050900456..890a852d54a78 100644 --- a/third_party/blink/public/mojom/buckets/bucket_manager_host.mojom +++ b/third_party/blink/public/mojom/buckets/bucket_manager_host.mojom @@ -10,18 +10,12 @@ import "third_party/blink/public/mojom/file_system_access/file_system_access_dir import "third_party/blink/public/mojom/file_system_access/file_system_access_error.mojom"; import "third_party/blink/public/mojom/indexeddb/indexeddb.mojom"; import "third_party/blink/public/mojom/locks/lock_manager.mojom"; +import "third_party/blink/public/mojom/quota/quota_types.mojom"; // Implementation of the proposed "Storage Buckets API". // // Proposal: https://github.com/WICG/storage-buckets -// The durability policy to apply to a single StorageBucket. The values are -// persisted to the quota DB and must not be changed. -enum BucketDurability { - kRelaxed = 0, - kStrict = 1, -}; - enum BucketError { kUnknown = 0, kQuotaExceeded = 1, diff --git a/third_party/blink/public/mojom/quota/quota_types.mojom b/third_party/blink/public/mojom/quota/quota_types.mojom index f7e075f257ba6..eb255256df098 100644 --- a/third_party/blink/public/mojom/quota/quota_types.mojom +++ b/third_party/blink/public/mojom/quota/quota_types.mojom @@ -20,6 +20,13 @@ enum StorageType { kUnknown = 4, }; +// The durability policy to apply to a single StorageBucket. The values are +// persisted to the quota DB and must not be changed. +enum BucketDurability { + kRelaxed = 0, + kStrict = 1, +}; + // These values need to match core/dom/exception_code.h. enum QuotaStatusCode { kOk = 0,