diff options
Diffstat (limited to 'chromium/components/services')
126 files changed, 9417 insertions, 771 deletions
diff --git a/chromium/components/services/app_service/BUILD.gn b/chromium/components/services/app_service/BUILD.gn index a860306ee5b..d6e390233a2 100644 --- a/chromium/components/services/app_service/BUILD.gn +++ b/chromium/components/services/app_service/BUILD.gn @@ -35,6 +35,7 @@ source_set("unit_tests") { "//components/services/app_service/public/cpp:intents", "//components/services/app_service/public/cpp:preferred_apps", "//components/services/app_service/public/cpp:publisher", + "//components/services/app_service/public/cpp:run_on_os_login", "//components/services/app_service/public/cpp:test_support", "//content/test:test_support", "//testing/gtest", diff --git a/chromium/components/services/app_service/app_service_mojom_impl.cc b/chromium/components/services/app_service/app_service_mojom_impl.cc index dab153dcd2f..23623e84c2b 100644 --- a/chromium/components/services/app_service/app_service_mojom_impl.cc +++ b/chromium/components/services/app_service/app_service_mojom_impl.cc @@ -539,6 +539,17 @@ void AppServiceMojomImpl::SetWindowMode(apps::mojom::AppType app_type, iter->second->SetWindowMode(app_id, window_mode); } +void AppServiceMojomImpl::SetRunOnOsLoginMode( + apps::mojom::AppType app_type, + const std::string& app_id, + apps::mojom::RunOnOsLoginMode run_on_os_login_mode) { + auto iter = publishers_.find(app_type); + if (iter == publishers_.end()) { + return; + } + iter->second->SetRunOnOsLoginMode(app_id, run_on_os_login_mode); +} + PreferredAppsList& AppServiceMojomImpl::GetPreferredAppsForTesting() { return preferred_apps_; } diff --git a/chromium/components/services/app_service/app_service_mojom_impl.h b/chromium/components/services/app_service/app_service_mojom_impl.h index 32a402deb8e..c58a2ea5d4d 100644 --- a/chromium/components/services/app_service/app_service_mojom_impl.h +++ b/chromium/components/services/app_service/app_service_mojom_impl.h @@ -121,6 +121,10 @@ class AppServiceMojomImpl : public apps::mojom::AppService { void SetWindowMode(apps::mojom::AppType app_type, const std::string& app_id, apps::mojom::WindowMode window_mode) override; + void SetRunOnOsLoginMode( + apps::mojom::AppType app_type, + const std::string& app_id, + apps::mojom::RunOnOsLoginMode run_on_os_login_mode) override; // Retern the preferred_apps_ for testing. PreferredAppsList& GetPreferredAppsForTesting(); diff --git a/chromium/components/services/app_service/public/cpp/BUILD.gn b/chromium/components/services/app_service/public/cpp/BUILD.gn index 504e7576c47..b461dd8b633 100644 --- a/chromium/components/services/app_service/public/cpp/BUILD.gn +++ b/chromium/components/services/app_service/public/cpp/BUILD.gn @@ -49,13 +49,20 @@ component("app_types") { sources = [ "app_types.cc", "app_types.h", + "intent_filter.cc", + "intent_filter.h", + "permission.cc", + "permission.h", ] defines = [ "IS_APP_TYPES_IMPL" ] public_deps = [ "//components/services/app_service/public/mojom" ] - deps = [ ":icon_types" ] + deps = [ + ":icon_types", + ":run_on_os_login", + ] } component("app_update") { @@ -73,6 +80,9 @@ component("app_update") { "app_update.h", "capability_access_update.cc", "capability_access_update.h", + "features.cc", + "features.h", + "macros.h", ] defines = [ "IS_APP_UPDATE_IMPL" ] @@ -86,6 +96,7 @@ component("app_update") { ":app_types", ":icon_types", ":intents", + ":run_on_os_login", "//ui/gfx", ] } @@ -173,6 +184,18 @@ component("icon_types") { deps = [ "//ui/gfx" ] } +component("run_on_os_login") { + output_name = "LOGIN_MODE" + sources = [ + "run_on_os_login_types.cc", + "run_on_os_login_types.h", + ] + + defines = [ "IS_LOGIN_MODE_IMPL" ] + + public_deps = [ "//components/services/app_service/public/mojom" ] +} + source_set("intents") { sources = [ "intent_constants.cc", @@ -184,6 +207,7 @@ source_set("intents") { ] deps = [ + ":app_types", "//base", "//components/services/app_service/public/mojom", "//third_party/blink/public/common", @@ -295,6 +319,7 @@ source_set("unit_tests") { ":intents", ":preferred_apps", ":publisher", + ":run_on_os_login", ":test_support", ":types", "//content/test:test_support", diff --git a/chromium/components/services/app_service/public/cpp/app_registry_cache.cc b/chromium/components/services/app_service/public/cpp/app_registry_cache.cc index c0effb42c76..ef505a87806 100644 --- a/chromium/components/services/app_service/public/cpp/app_registry_cache.cc +++ b/chromium/components/services/app_service/public/cpp/app_registry_cache.cc @@ -5,17 +5,12 @@ #include "components/services/app_service/public/cpp/app_registry_cache.h" #include "base/containers/contains.h" +#include "components/services/app_service/public/cpp/features.h" #include <utility> namespace apps { -namespace { - -constexpr uint32_t kUAFSentinelInitial = 0xabcd1234; - -} // namespace - AppRegistryCache::Observer::Observer(AppRegistryCache* cache) { Observe(cache); } @@ -42,15 +37,13 @@ void AppRegistryCache::Observer::Observe(AppRegistryCache* cache) { } } -AppRegistryCache::AppRegistryCache() - : account_id_(EmptyAccountId()), uaf_sentinel_(kUAFSentinelInitial) {} +AppRegistryCache::AppRegistryCache() : account_id_(EmptyAccountId()) {} AppRegistryCache::~AppRegistryCache() { for (auto& obs : observers_) { obs.OnAppRegistryCacheWillBeDestroyed(this); } DCHECK(observers_.empty()); - uaf_sentinel_ = 0; } void AppRegistryCache::AddObserver(Observer* observer) { @@ -69,8 +62,8 @@ void AppRegistryCache::OnApps(std::vector<apps::mojom::AppPtr> deltas, if (should_notify_initialized) { DCHECK_NE(apps::mojom::AppType::kUnknown, app_type); - if (initialized_app_types_.find(app_type) == initialized_app_types_.end()) { - in_progress_initialized_app_types_.insert(app_type); + if (!IsAppTypeInitialized(ConvertMojomAppTypToAppType(app_type))) { + in_progress_initialized_mojom_app_types_.insert(app_type); } } @@ -90,15 +83,17 @@ void AppRegistryCache::OnApps(std::vector<apps::mojom::AppPtr> deltas, OnAppTypeInitialized(); } -void AppRegistryCache::OnApps(std::vector<std::unique_ptr<App>> deltas, +void AppRegistryCache::OnApps(std::vector<AppPtr> deltas, apps::AppType app_type, bool should_notify_initialized) { DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); - // TODO(crbug.com/1253250): Modify in_progress_initialized_app_types_ based on - // the `App` struct when the `App` struct replaces the mojom `App` struct for - // all publishers, to prevent OnAppTypeInitialized to be called twice for both - // the mojom struct and non-mojom struct. + if (should_notify_initialized) { + DCHECK_NE(apps::AppType::kUnknown, app_type); + if (!IsAppTypeInitialized(app_type)) { + in_progress_initialized_app_types_.insert(app_type); + } + } if (!deltas_in_progress_.empty()) { std::move(deltas.begin(), deltas.end(), @@ -108,15 +103,15 @@ void AppRegistryCache::OnApps(std::vector<std::unique_ptr<App>> deltas, DoOnApps(std::move(deltas)); while (!deltas_pending_.empty()) { - std::vector<std::unique_ptr<App>> pending; + std::vector<AppPtr> pending; pending.swap(deltas_pending_); DoOnApps(std::move(pending)); } - // TODO(crbug.com/1253250): Modify and add OnAppTypeInitialized for the `App` - // struct when the `App` struct replaces the mojom `App` struct for all - // publishers, to prevent OnAppTypeInitialized to be called twice for both the - // mojom struct and non-mojom struct. + if (base::FeatureList::IsEnabled( + kAppServiceOnAppTypeInitializedWithoutMojom)) { + OnAppTypeInitialized(); + } } void AppRegistryCache::DoOnApps(std::vector<apps::mojom::AppPtr> deltas) { @@ -179,7 +174,7 @@ void AppRegistryCache::DoOnApps(std::vector<apps::mojom::AppPtr> deltas) { mojom_deltas_in_progress_.clear(); } -void AppRegistryCache::DoOnApps(std::vector<std::unique_ptr<App>> deltas) { +void AppRegistryCache::DoOnApps(std::vector<AppPtr> deltas) { // Merge any deltas elements that have the same app_id. If an observer's // OnAppUpdate calls back into this AppRegistryCache then we can therefore // present a single delta for any given app_id. @@ -242,30 +237,68 @@ void AppRegistryCache::SetAccountId(const AccountId& account_id) { account_id_ = account_id; } -const std::set<apps::mojom::AppType>& AppRegistryCache::GetInitializedAppTypes() - const { +const std::set<AppType>& AppRegistryCache::InitializedAppTypes() const { return initialized_app_types_; } -bool AppRegistryCache::IsAppTypeInitialized( - apps::mojom::AppType app_type) const { +bool AppRegistryCache::IsAppTypeInitialized(apps::AppType app_type) const { return base::Contains(initialized_app_types_, app_type); } +void AppRegistryCache::OnMojomAppTypeInitialized() { + if (in_progress_initialized_mojom_app_types_.empty()) { + return; + } + + auto in_progress_initialized_app_types = + in_progress_initialized_mojom_app_types_; + in_progress_initialized_mojom_app_types_.clear(); + + for (auto app_type : in_progress_initialized_app_types) { + for (auto& obs : observers_) { + obs.OnAppTypeInitialized(ConvertMojomAppTypToAppType(app_type)); + } + initialized_app_types_.insert(ConvertMojomAppTypToAppType(app_type)); + } +} + void AppRegistryCache::OnAppTypeInitialized() { - CHECK_EQ(kUAFSentinelInitial, uaf_sentinel_); - if (in_progress_initialized_app_types_.empty()) { + if (!base::FeatureList::IsEnabled( + kAppServiceOnAppTypeInitializedWithoutMojom)) { + OnMojomAppTypeInitialized(); + return; + } + + // Check both the non mojom and mojom initialized status. Only when they are + // not initialized, call OnAppTypeInitialized to notify observers, because + // observers might use the non mojom or mojom App struct. + // + // TODO(crbug.com/1253250): Remove the mojom initialized checking when all + // observers use the non mojom App struct only. + if (in_progress_initialized_mojom_app_types_.empty() || + in_progress_initialized_app_types_.empty()) { return; } - auto in_progress_initialized_app_types = in_progress_initialized_app_types_; - in_progress_initialized_app_types_.clear(); + // In observer's OnAppTypeInitialized callback, `OnApp` might be call to + // update the app, then this OnAppTypeInitialized might be called again. So we + // need to check the initialized `app_type` first, and remove it from + // `in_progress_initialized_app_types_` to prevent the dead loop. + std::set<AppType> in_progress_initialized_app_types; + for (auto app_type : in_progress_initialized_app_types_) { + if (base::Contains(in_progress_initialized_mojom_app_types_, + ConvertAppTypeToMojomAppType(app_type))) { + in_progress_initialized_app_types.insert(app_type); + } + } for (auto app_type : in_progress_initialized_app_types) { + auto mojom_app_type = ConvertAppTypeToMojomAppType(app_type); + in_progress_initialized_app_types_.erase(app_type); + in_progress_initialized_mojom_app_types_.erase(mojom_app_type); for (auto& obs : observers_) { obs.OnAppTypeInitialized(app_type); } - CHECK_EQ(kUAFSentinelInitial, uaf_sentinel_); initialized_app_types_.insert(app_type); } } diff --git a/chromium/components/services/app_service/public/cpp/app_registry_cache.h b/chromium/components/services/app_service/public/cpp/app_registry_cache.h index 799a93a8af1..14f52557977 100644 --- a/chromium/components/services/app_service/public/cpp/app_registry_cache.h +++ b/chromium/components/services/app_service/public/cpp/app_registry_cache.h @@ -53,9 +53,9 @@ class COMPONENT_EXPORT(APP_UPDATE) AppRegistryCache { // Called when the publisher for |app_type| has finished initiating apps. // Note that this will not be called for app types initialized prior to this // observer being registered. Observers should call - // AppRegistryCache::GetInitializedAppTypes() at the time of starting + // AppRegistryCache::InitializedAppTypes() at the time of starting // observation to get a set of the app types which have been initialized. - virtual void OnAppTypeInitialized(apps::mojom::AppType app_type) {} + virtual void OnAppTypeInitialized(apps::AppType app_type) {} // Called when the AppRegistryCache object (the thing that this observer // observes) will be destroyed. In response, the observer, |this|, should @@ -115,7 +115,7 @@ class COMPONENT_EXPORT(APP_UPDATE) AppRegistryCache { void OnApps(std::vector<apps::mojom::AppPtr> deltas, apps::mojom::AppType app_type, bool should_notify_initialized); - void OnApps(std::vector<std::unique_ptr<App>> deltas, + void OnApps(std::vector<AppPtr> deltas, apps::AppType app_type, bool should_notify_initialized); @@ -240,26 +240,26 @@ class COMPONENT_EXPORT(APP_UPDATE) AppRegistryCache { } // Returns the set of app types that have so far been initialized. - const std::set<apps::mojom::AppType>& GetInitializedAppTypes() const; + const std::set<AppType>& InitializedAppTypes() const; - bool IsAppTypeInitialized(apps::mojom::AppType app_type) const; + bool IsAppTypeInitialized(AppType app_type) const; private: friend class AppRegistryCacheTest; friend class PublisherTest; void DoOnApps(std::vector<apps::mojom::AppPtr> deltas); - void DoOnApps(std::vector<std::unique_ptr<App>> deltas); + void DoOnApps(std::vector<AppPtr> deltas); - // NOINLINE should force this function to appear on the stack in crash dumps. - // https://crbug.com/1237267. - void NOINLINE OnAppTypeInitialized(); + void OnMojomAppTypeInitialized(); + + void OnAppTypeInitialized(); base::ObserverList<Observer> observers_; // Maps from app_id to the latest state: the "sum" of all previous deltas. std::map<std::string, apps::mojom::AppPtr> mojom_states_; - std::map<std::string, std::unique_ptr<App>> states_; + std::map<std::string, AppPtr> states_; // Track the deltas being processed or are about to be processed by OnApps. // They are separate to manage the "notification and merging might be delayed @@ -280,23 +280,20 @@ class COMPONENT_EXPORT(APP_UPDATE) AppRegistryCache { std::map<std::string, apps::mojom::App*> mojom_deltas_in_progress_; std::vector<apps::mojom::AppPtr> mojom_deltas_pending_; std::map<std::string, App*> deltas_in_progress_; - std::vector<std::unique_ptr<App>> deltas_pending_; + std::vector<AppPtr> deltas_pending_; // Saves app types which will finish initialization, and OnAppTypeInitialized // will be called to notify observers. - std::set<apps::mojom::AppType> in_progress_initialized_app_types_; + std::set<apps::mojom::AppType> in_progress_initialized_mojom_app_types_; + std::set<AppType> in_progress_initialized_app_types_; // Saves app types which have finished initialization, and // OnAppTypeInitialized has be called to notify observers. - std::set<apps::mojom::AppType> initialized_app_types_; + std::set<AppType> initialized_app_types_; AccountId account_id_; SEQUENCE_CHECKER(my_sequence_checker_); - - // A sentinel value checking for a UAF in https://crbug.com/1237267. Should be - // removed after https://crbug.com/1237267 is fixed. - uint32_t uaf_sentinel_; }; } // namespace apps diff --git a/chromium/components/services/app_service/public/cpp/app_registry_cache_mojom_unittest.cc b/chromium/components/services/app_service/public/cpp/app_registry_cache_mojom_unittest.cc index 1e52d6ad846..04612f78995 100644 --- a/chromium/components/services/app_service/public/cpp/app_registry_cache_mojom_unittest.cc +++ b/chromium/components/services/app_service/public/cpp/app_registry_cache_mojom_unittest.cc @@ -5,7 +5,10 @@ #include <map> #include "base/memory/raw_ptr.h" +#include "base/test/scoped_feature_list.h" #include "components/services/app_service/public/cpp/app_registry_cache.h" +#include "components/services/app_service/public/cpp/app_types.h" +#include "components/services/app_service/public/cpp/features.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -30,6 +33,11 @@ MATCHER_P(HasAppId, app_id, "Has the correct app id") { class AppRegistryCacheMojomTest : public testing::Test, public apps::AppRegistryCache::Observer { protected: + AppRegistryCacheMojomTest() { + scoped_feature_list_.InitAndDisableFeature( + apps::kAppServiceOnAppTypeInitializedWithoutMojom); + } + static apps::mojom::AppPtr MakeApp( const char* app_id, const char* name, @@ -68,7 +76,7 @@ class AppRegistryCacheMojomTest : public testing::Test, updated_names_.insert(update.Name()); } - void OnAppTypeInitialized(apps::mojom::AppType app_type) override { + void OnAppTypeInitialized(apps::AppType app_type) override { app_type_ = app_type; } @@ -78,17 +86,18 @@ class AppRegistryCacheMojomTest : public testing::Test, NOTREACHED(); } - void SetAppType(apps::mojom::AppType app_type) { app_type_ = app_type; } + void SetAppType(apps::AppType app_type) { app_type_ = app_type; } const AccountId& account_id() const { return account_id_; } - apps::mojom::AppType app_type() const { return app_type_; } + apps::AppType app_type() const { return app_type_; } int num_freshly_installed_ = 0; std::set<std::string> updated_ids_; std::set<std::string> updated_names_; AccountId account_id_ = AccountId::FromUserEmail("test@gmail.com"); - apps::mojom::AppType app_type_ = apps::mojom::AppType::kUnknown; + apps::AppType app_type_ = apps::AppType::kUnknown; + base::test::ScopedFeatureList scoped_feature_list_; }; // Responds to a cache's OnAppUpdate to call back into the cache, checking that @@ -126,7 +135,7 @@ class RecursiveObserver : public apps::AppRegistryCache::Observer { int NumAppsSeenOnAppUpdate() { return num_apps_seen_on_app_update_; } - apps::mojom::AppType app_type() const { return app_type_; } + apps::AppType app_type() const { return app_type_; } protected: // apps::AppRegistryCache::Observer overrides. @@ -199,7 +208,7 @@ class RecursiveObserver : public apps::AppRegistryCache::Observer { num_apps_seen_on_app_update_++; } - void OnAppTypeInitialized(apps::mojom::AppType app_type) override { + void OnAppTypeInitialized(apps::AppType app_type) override { app_type_ = app_type; } @@ -222,7 +231,7 @@ class RecursiveObserver : public apps::AppRegistryCache::Observer { int expected_num_apps_; int num_apps_seen_on_app_update_; AccountId account_id_ = AccountId::FromUserEmail("test@gmail.com"); - apps::mojom::AppType app_type_ = apps::mojom::AppType::kUnknown; + apps::AppType app_type_ = apps::AppType::kUnknown; // Records previously seen app names, keyed by app_id's, so we can check // that, for these tests, a given app's name is always increasing (in string @@ -261,7 +270,7 @@ class InitializedObserver : public apps::AppRegistryCache::Observer { updated_ids_.insert(update.AppId()); } - void OnAppTypeInitialized(apps::mojom::AppType app_type) override { + void OnAppTypeInitialized(apps::AppType app_type) override { app_type_ = app_type; ++count_; app_count_ = updated_ids_.size(); @@ -272,9 +281,9 @@ class InitializedObserver : public apps::AppRegistryCache::Observer { Observe(nullptr); } - void SetAppType(apps::mojom::AppType app_type) { app_type_ = app_type; } + void SetAppType(apps::AppType app_type) { app_type_ = app_type; } - apps::mojom::AppType app_type() const { return app_type_; } + apps::AppType app_type() const { return app_type_; } int count() const { return count_; } @@ -282,7 +291,7 @@ class InitializedObserver : public apps::AppRegistryCache::Observer { private: std::set<std::string> updated_ids_; - apps::mojom::AppType app_type_ = apps::mojom::AppType::kUnknown; + apps::AppType app_type_ = apps::AppType::kUnknown; int count_ = 0; int app_count_ = 0; }; @@ -410,10 +419,10 @@ TEST_F(AppRegistryCacheMojomTest, Observer) { EXPECT_NE(updated_ids_.end(), updated_ids_.find("a")); EXPECT_NE(updated_ids_.end(), updated_ids_.find("c")); EXPECT_NE(updated_ids_.end(), updated_ids_.find("e")); - EXPECT_EQ(apps::mojom::AppType::kArc, app_type()); - EXPECT_TRUE(cache.IsAppTypeInitialized(apps::mojom::AppType::kArc)); + EXPECT_EQ(apps::AppType::kArc, app_type()); + EXPECT_TRUE(cache.IsAppTypeInitialized(apps::AppType::kArc)); - SetAppType(apps::mojom::AppType::kUnknown); + SetAppType(apps::AppType::kUnknown); num_freshly_installed_ = 0; updated_ids_.clear(); deltas.clear(); @@ -426,7 +435,7 @@ TEST_F(AppRegistryCacheMojomTest, Observer) { EXPECT_EQ(2u, updated_ids_.size()); EXPECT_NE(updated_ids_.end(), updated_ids_.find("b")); EXPECT_NE(updated_ids_.end(), updated_ids_.find("c")); - EXPECT_EQ(apps::mojom::AppType::kUnknown, app_type()); + EXPECT_EQ(apps::AppType::kUnknown, app_type()); cache.RemoveObserver(this); @@ -439,8 +448,8 @@ TEST_F(AppRegistryCacheMojomTest, Observer) { EXPECT_EQ(0, num_freshly_installed_); EXPECT_EQ(0u, updated_ids_.size()); - EXPECT_EQ(apps::mojom::AppType::kUnknown, app_type()); - EXPECT_TRUE(cache.IsAppTypeInitialized(apps::mojom::AppType::kArc)); + EXPECT_EQ(apps::AppType::kUnknown, app_type()); + EXPECT_TRUE(cache.IsAppTypeInitialized(apps::AppType::kArc)); } TEST_F(AppRegistryCacheMojomTest, Recursive) { @@ -473,8 +482,8 @@ TEST_F(AppRegistryCacheMojomTest, Recursive) { cache.OnApps(std::move(deltas), apps::mojom::AppType::kUnknown, false /* should_notify_initialized */); EXPECT_EQ(1, observer.NumAppsSeenOnAppUpdate()); - EXPECT_EQ(apps::mojom::AppType::kArc, observer.app_type()); - EXPECT_TRUE(cache.IsAppTypeInitialized(apps::mojom::AppType::kArc)); + EXPECT_EQ(apps::AppType::kArc, observer.app_type()); + EXPECT_TRUE(cache.IsAppTypeInitialized(apps::AppType::kArc)); } TEST_F(AppRegistryCacheMojomTest, SuperRecursive) { @@ -521,8 +530,8 @@ TEST_F(AppRegistryCacheMojomTest, SuperRecursive) { EXPECT_EQ("avocado", GetName(cache, "a")); EXPECT_EQ("boysenberry", GetName(cache, "b")); EXPECT_EQ("coconut", GetName(cache, "c")); - EXPECT_EQ(apps::mojom::AppType::kArc, observer.app_type()); - EXPECT_TRUE(cache.IsAppTypeInitialized(apps::mojom::AppType::kArc)); + EXPECT_EQ(apps::AppType::kArc, observer.app_type()); + EXPECT_TRUE(cache.IsAppTypeInitialized(apps::AppType::kArc)); } TEST_F(AppRegistryCacheMojomTest, OnAppTypeInitialized) { @@ -541,16 +550,16 @@ TEST_F(AppRegistryCacheMojomTest, OnAppTypeInitialized) { cache.OnApps(std::move(deltas), apps::mojom::AppType::kArc, true /* should_notify_initialized */); - EXPECT_EQ(apps::mojom::AppType::kArc, observer1.app_type()); + EXPECT_EQ(apps::AppType::kArc, observer1.app_type()); EXPECT_EQ(1, observer1.count()); EXPECT_EQ(3, observer1.app_count()); - EXPECT_EQ(1u, cache.GetInitializedAppTypes().size()); - EXPECT_TRUE(cache.IsAppTypeInitialized(apps::mojom::AppType::kArc)); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(apps::AppType::kArc)); // New observers should not have OnAppTypeInitialized called. InitializedObserver observer2(&cache); - EXPECT_EQ(apps::mojom::AppType::kUnknown, observer2.app_type()); + EXPECT_EQ(apps::AppType::kUnknown, observer2.app_type()); EXPECT_EQ(0, observer2.count()); - EXPECT_EQ(1u, cache.GetInitializedAppTypes().size()); - EXPECT_TRUE(cache.IsAppTypeInitialized(apps::mojom::AppType::kArc)); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(apps::AppType::kArc)); } diff --git a/chromium/components/services/app_service/public/cpp/app_registry_cache_unittest.cc b/chromium/components/services/app_service/public/cpp/app_registry_cache_unittest.cc index c6c81461ff4..ebaa29ce7fb 100644 --- a/chromium/components/services/app_service/public/cpp/app_registry_cache_unittest.cc +++ b/chromium/components/services/app_service/public/cpp/app_registry_cache_unittest.cc @@ -2,29 +2,112 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "components/services/app_service/public/cpp/app_registry_cache.h" + #include <map> +#include <set> #include <utility> #include <vector> -#include "components/services/app_service/public/cpp/app_registry_cache.h" +#include "base/containers/contains.h" +#include "base/test/scoped_feature_list.h" +#include "components/services/app_service/public/cpp/app_types.h" +#include "components/services/app_service/public/cpp/features.h" #include "components/services/app_service/public/cpp/types_util.h" #include "testing/gtest/include/gtest/gtest.h" namespace apps { -class AppRegistryCacheTest : public testing::Test { +namespace { + +apps::AppPtr MakeApp(const char* app_id, + const char* name, + apps::AppType app_type = apps::AppType::kArc, + apps::Readiness readiness = apps::Readiness::kUnknown, + uint64_t timeline = 0) { + auto app = std::make_unique<apps::App>(app_type, app_id); + app->readiness = readiness; + app->name = name; + app->icon_key = + apps::IconKey(timeline, /*resource_id=*/0, /*icon_effects=*/0); + return app; +} + +apps::mojom::AppPtr MakeMojomApp( + const char* app_id, + const char* name, + apps::mojom::AppType app_type = apps::mojom::AppType::kArc) { + apps::mojom::AppPtr app = apps::mojom::App::New(); + app->app_type = app_type; + app->app_id = app_id; + app->name = name; + return app; +} + +// InitializedObserver is used to test the OnAppTypeInitialized interface for +// AppRegistryCache::Observer. +class InitializedObserver : public apps::AppRegistryCache::Observer { public: - std::unique_ptr<App> MakeApp(const char* app_id, - const char* name, - Readiness readiness = Readiness::kUnknown, - uint64_t timeline = 0) { - std::unique_ptr<App> app = std::make_unique<App>(AppType::kArc, app_id); - app->readiness = readiness; - app->name = name; - app->icon_key = IconKey(timeline, /*resource_id=*/0, /*icon_effects=*/0); - return app; + explicit InitializedObserver(apps::AppRegistryCache* cache) { + cache_ = cache; + Observe(cache); + } + + ~InitializedObserver() override = default; + + // apps::AppRegistryCache::Observer overrides. + void OnAppUpdate(const apps::AppUpdate& update) override { + updated_ids_.insert(update.AppId()); + } + + void UpdateApps() { + std::vector<AppPtr> deltas; + deltas.push_back(MakeApp("n", "noodle", AppType::kArc)); + deltas.push_back(MakeApp("s", "salmon", AppType::kChromeApp)); + cache_->OnApps(std::move(deltas), AppType::kUnknown, + false /* should_notify_initialized */); + + std::vector<apps::mojom::AppPtr> mojom_deltas; + mojom_deltas.push_back( + MakeMojomApp("n", "noodle", apps::mojom::AppType::kArc)); + mojom_deltas.push_back( + MakeMojomApp("s", "salmon", apps::mojom::AppType::kChromeApp)); + cache_->OnApps(std::move(mojom_deltas), apps::mojom::AppType::kUnknown, + false /* should_notify_initialized */); + } + + void OnAppTypeInitialized(apps::AppType app_type) override { + app_types_.insert(app_type); + ++initialized_app_type_count_; + app_count_at_initialization_ = updated_ids_.size(); + UpdateApps(); + } + + void OnAppRegistryCacheWillBeDestroyed( + apps::AppRegistryCache* cache) override { + Observe(nullptr); + } + + std::set<apps::AppType> app_types() const { return app_types_; } + + int initialized_app_type_count() const { return initialized_app_type_count_; } + + int app_count_at_initialization() const { + return app_count_at_initialization_; } + private: + std::set<std::string> updated_ids_; + std::set<apps::AppType> app_types_; + int initialized_app_type_count_ = 0; + int app_count_at_initialization_ = 0; + apps::AppRegistryCache* cache_ = nullptr; +}; + +} // namespace + +class AppRegistryCacheTest : public testing::Test { + public: void CallForAllApps(AppRegistryCache& cache) { cache.ForAllApps([this](const AppUpdate& update) { OnAppUpdate(update); }); } @@ -68,16 +151,30 @@ class AppRegistryCacheTest : public testing::Test { updated_names_.clear(); } + void DisableOnAppTypeInitializedFlag() { + scoped_feature_list_.InitAndDisableFeature( + kAppServiceOnAppTypeInitializedWithoutMojom); + } + + void EnableOnAppTypeInitializedFlag() { + scoped_feature_list_.InitAndEnableFeature( + kAppServiceOnAppTypeInitializedWithoutMojom); + } + std::set<std::string> updated_ids_; std::set<std::string> updated_names_; + + private: + base::test::ScopedFeatureList scoped_feature_list_; }; TEST_F(AppRegistryCacheTest, OnApps) { AppRegistryCache cache; - std::vector<std::unique_ptr<App>> deltas; + std::vector<AppPtr> deltas; deltas.push_back(MakeApp("a", "apple")); - deltas.push_back(MakeApp("b", "banana", Readiness::kReady)); - deltas.push_back(MakeApp("c", "cherry", Readiness::kDisabledByPolicy, + deltas.push_back(MakeApp("b", "banana", AppType::kArc, Readiness::kReady)); + deltas.push_back(MakeApp("c", "cherry", AppType::kArc, + Readiness::kDisabledByPolicy, /*timeline=*/10)); cache.OnApps(std::move(deltas), AppType::kUnknown, false /* should_notify_initialized */); @@ -98,7 +195,7 @@ TEST_F(AppRegistryCacheTest, OnApps) { Clear(); deltas.clear(); - deltas.push_back(MakeApp("a", "apricot", Readiness::kReady)); + deltas.push_back(MakeApp("a", "apricot", AppType::kArc, Readiness::kReady)); deltas.push_back(MakeApp("d", "durian")); cache.OnApps(std::move(deltas), AppType::kUnknown, false /* should_notify_initialized */); @@ -121,8 +218,8 @@ TEST_F(AppRegistryCacheTest, OnApps) { TEST_F(AppRegistryCacheTest, Removed) { AppRegistryCache cache; - std::vector<std::unique_ptr<App>> apps; - apps.push_back(MakeApp("app", "app", Readiness::kReady)); + std::vector<AppPtr> apps; + apps.push_back(MakeApp("app", "app", AppType::kArc, Readiness::kReady)); cache.OnApps(std::move(apps), AppType::kUnknown, false /* should_notify_initialized */); @@ -135,8 +232,9 @@ TEST_F(AppRegistryCacheTest, Removed) { // Uninstall the app, then remove it. apps.clear(); - apps.push_back(MakeApp("app", "app", Readiness::kUninstalledByUser)); - apps.push_back(MakeApp("app", "app", Readiness::kRemoved)); + apps.push_back( + MakeApp("app", "app", AppType::kArc, Readiness::kUninstalledByUser)); + apps.push_back(MakeApp("app", "app", AppType::kArc, Readiness::kRemoved)); cache.OnApps(std::move(apps), AppType::kUnknown, false /* should_notify_initialized */); @@ -148,4 +246,549 @@ TEST_F(AppRegistryCacheTest, Removed) { Clear(); } +// Verify the OnAppTypeInitialized callback when OnApps is called for the non +// mojom App type first, with the disabled flag. +TEST_F(AppRegistryCacheTest, + OnAppTypeInitializedWithDisableFlagNonMojomUpdateFirst) { + DisableOnAppTypeInitializedFlag(); + + AppRegistryCache cache; + InitializedObserver observer1(&cache); + + std::vector<AppPtr> deltas1; + deltas1.push_back(MakeApp("a", "avocado")); + deltas1.push_back(MakeApp("c", "cucumber")); + cache.OnApps(std::move(deltas1), AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the non mojom Apps are + // added. + EXPECT_TRUE(observer1.app_types().empty()); + EXPECT_EQ(0, observer1.initialized_app_type_count()); + EXPECT_EQ(0, observer1.app_count_at_initialization()); + EXPECT_TRUE(cache.InitializedAppTypes().empty()); + EXPECT_FALSE(cache.IsAppTypeInitialized(AppType::kArc)); + + std::vector<apps::mojom::AppPtr> mojom_deltas1; + mojom_deltas1.push_back(MakeMojomApp("a", "avocado")); + mojom_deltas1.push_back(MakeMojomApp("c", "cucumber")); + cache.OnApps(std::move(mojom_deltas1), apps::mojom::AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is called when the mojom Apps are added. + EXPECT_TRUE(base::Contains(observer1.app_types(), apps::AppType::kArc)); + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(2, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(AppType::kArc)); + + std::vector<AppPtr> deltas2; + deltas2.push_back(MakeApp("d", "durian")); + cache.OnApps(std::move(deltas2), AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the non mojom Apps are + // added. + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(2, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + + std::vector<apps::mojom::AppPtr> mojom_deltas2; + mojom_deltas2.push_back(MakeMojomApp("d", "durian")); + cache.OnApps(std::move(mojom_deltas2), apps::mojom::AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the mojom Apps are + // initialized again. + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(2, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + + // Verify the new observers should not have OnAppTypeInitialized called. + InitializedObserver observer2(&cache); + EXPECT_TRUE(observer2.app_types().empty()); + EXPECT_EQ(0, observer2.initialized_app_type_count()); + EXPECT_EQ(0, observer2.app_count_at_initialization()); +} + +// Verify the OnAppTypeInitialized callback when OnApps is called for the mojom +// App type first, with the disabled flag. +TEST_F(AppRegistryCacheTest, + OnAppTypeInitializedWithDisableFlagMojomUpdateFirst) { + DisableOnAppTypeInitializedFlag(); + + AppRegistryCache cache; + InitializedObserver observer1(&cache); + + std::vector<apps::mojom::AppPtr> mojom_deltas1; + mojom_deltas1.push_back(MakeMojomApp("a", "avocado")); + mojom_deltas1.push_back(MakeMojomApp("c", "cucumber")); + cache.OnApps(std::move(mojom_deltas1), apps::mojom::AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is called when the mojom Apps are added. + EXPECT_TRUE(base::Contains(observer1.app_types(), apps::AppType::kArc)); + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(2, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(AppType::kArc)); + + std::vector<AppPtr> deltas1; + deltas1.push_back(MakeApp("a", "avocado")); + deltas1.push_back(MakeApp("c", "cucumber")); + cache.OnApps(std::move(deltas1), AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the non mojom Apps are + // added. + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(2, observer1.app_count_at_initialization()); + + std::vector<apps::mojom::AppPtr> mojom_deltas2; + mojom_deltas2.push_back(MakeMojomApp("d", "durian")); + cache.OnApps(std::move(mojom_deltas2), apps::mojom::AppType::kArc, + true /* should_notify_initialized */); + + std::vector<AppPtr> deltas2; + deltas2.push_back(MakeApp("d", "durian")); + cache.OnApps(std::move(deltas2), AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the Apps are added. + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(2, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + + // Verify the new observers should not have OnAppTypeInitialized called. + InitializedObserver observer2(&cache); + EXPECT_TRUE(observer2.app_types().empty()); + EXPECT_EQ(0, observer2.initialized_app_type_count()); + EXPECT_EQ(0, observer2.app_count_at_initialization()); +} + +// Verify the OnAppTypeInitialized callback when OnApps is called for multiple +// App types, with the disabled flag. +TEST_F(AppRegistryCacheTest, + OnAppTypeInitializedWithDisableFlagMultipleAppTypes) { + DisableOnAppTypeInitializedFlag(); + + AppRegistryCache cache; + InitializedObserver observer1(&cache); + + std::vector<AppPtr> deltas1; + deltas1.push_back(MakeApp("a", "avocado")); + deltas1.push_back(MakeApp("c", "cucumber")); + cache.OnApps(std::move(deltas1), AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the non mojom Apps are + // added. + EXPECT_TRUE(observer1.app_types().empty()); + EXPECT_EQ(0, observer1.initialized_app_type_count()); + EXPECT_EQ(0, observer1.app_count_at_initialization()); + EXPECT_TRUE(cache.InitializedAppTypes().empty()); + + std::vector<apps::mojom::AppPtr> mojom_deltas1; + mojom_deltas1.push_back(MakeMojomApp("a", "avocado")); + mojom_deltas1.push_back(MakeMojomApp("c", "cucumber")); + cache.OnApps(std::move(mojom_deltas1), apps::mojom::AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is called when the mojom Apps are added. + EXPECT_TRUE(base::Contains(observer1.app_types(), apps::AppType::kArc)); + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(2, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(AppType::kArc)); + + std::vector<apps::mojom::AppPtr> mojom_deltas2; + mojom_deltas2.push_back( + MakeMojomApp("d", "durian", apps::mojom::AppType::kChromeApp)); + cache.OnApps(std::move(mojom_deltas2), apps::mojom::AppType::kChromeApp, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is called when the mojom Apps are added. + EXPECT_EQ(2u, observer1.app_types().size()); + EXPECT_TRUE(base::Contains(observer1.app_types(), apps::AppType::kChromeApp)); + EXPECT_EQ(2, observer1.initialized_app_type_count()); + EXPECT_EQ(5, observer1.app_count_at_initialization()); + EXPECT_EQ(2u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(AppType::kChromeApp)); + + std::vector<AppPtr> deltas2; + deltas2.push_back(MakeApp("d", "durian", AppType::kChromeApp)); + cache.OnApps(std::move(deltas2), AppType::kChromeApp, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the non mojom Apps are + // added. + EXPECT_EQ(2, observer1.initialized_app_type_count()); + EXPECT_EQ(5, observer1.app_count_at_initialization()); + EXPECT_EQ(2u, cache.InitializedAppTypes().size()); + + // Verify the new observers should not have OnAppTypeInitialized called. + InitializedObserver observer2(&cache); + EXPECT_TRUE(observer2.app_types().empty()); + EXPECT_EQ(0, observer2.initialized_app_type_count()); + EXPECT_EQ(0, observer2.app_count_at_initialization()); +} + +// Verify the OnAppTypeInitialized callback when OnApps is called for empty apps +// vector, with the disabled flag. +TEST_F(AppRegistryCacheTest, OnAppTypeInitializedWithDisableFlagEmptyUpdate) { + DisableOnAppTypeInitializedFlag(); + + AppRegistryCache cache; + InitializedObserver observer1(&cache); + + std::vector<AppPtr> deltas1; + cache.OnApps(std::move(deltas1), AppType::kStandaloneBrowserChromeApp, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the non mojom Apps are + // initialized. + EXPECT_TRUE(observer1.app_types().empty()); + EXPECT_EQ(0, observer1.initialized_app_type_count()); + EXPECT_EQ(0, observer1.app_count_at_initialization()); + EXPECT_TRUE(cache.InitializedAppTypes().empty()); + EXPECT_FALSE( + cache.IsAppTypeInitialized(AppType::kStandaloneBrowserChromeApp)); + + std::vector<apps::mojom::AppPtr> mojom_deltas1; + cache.OnApps(std::move(mojom_deltas1), + apps::mojom::AppType::kStandaloneBrowserChromeApp, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is called when the mojom Apps are initialized. + EXPECT_TRUE(base::Contains(observer1.app_types(), + apps::AppType::kStandaloneBrowserChromeApp)); + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(0, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(AppType::kStandaloneBrowserChromeApp)); + + std::vector<AppPtr> deltas2; + deltas2.push_back(MakeApp("d", "durian")); + cache.OnApps(std::move(deltas2), AppType::kStandaloneBrowserChromeApp, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the non mojom Apps are + // initialized again. + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(0, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + + std::vector<apps::mojom::AppPtr> mojom_deltas2; + mojom_deltas2.push_back(MakeMojomApp("d", "durian")); + cache.OnApps(std::move(mojom_deltas2), + apps::mojom::AppType::kStandaloneBrowserChromeApp, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the mojom Apps are + // initialized again. + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(0, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + + std::vector<apps::mojom::AppPtr> mojom_deltas3; + cache.OnApps(std::move(mojom_deltas3), apps::mojom::AppType::kRemote, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is called when the mojom Apps are initialized. + EXPECT_EQ(2u, observer1.app_types().size()); + EXPECT_TRUE(base::Contains(observer1.app_types(), apps::AppType::kRemote)); + EXPECT_EQ(2, observer1.initialized_app_type_count()); + EXPECT_EQ(2u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(AppType::kRemote)); + + std::vector<AppPtr> deltas3; + cache.OnApps(std::move(deltas3), AppType::kRemote, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the non mojom Apps are + // initialized. + EXPECT_EQ(2, observer1.initialized_app_type_count()); + EXPECT_EQ(2u, cache.InitializedAppTypes().size()); + + // Verify the new observers should not have OnAppTypeInitialized called. + InitializedObserver observer2(&cache); + EXPECT_TRUE(observer2.app_types().empty()); + EXPECT_EQ(0, observer2.initialized_app_type_count()); + EXPECT_EQ(0, observer2.app_count_at_initialization()); +} + +// Verify the OnAppTypeInitialized callback when OnApps is called for the non +// mojom App type first, with the enabled flag. +TEST_F(AppRegistryCacheTest, + OnAppTypeInitializedWithEnableFlagNonMojomUpdateFirst) { + EnableOnAppTypeInitializedFlag(); + + AppRegistryCache cache; + InitializedObserver observer1(&cache); + + std::vector<AppPtr> deltas1; + deltas1.push_back(MakeApp("a", "avocado")); + deltas1.push_back(MakeApp("c", "cucumber")); + cache.OnApps(std::move(deltas1), AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the non mojom Apps are + // added. + EXPECT_TRUE(observer1.app_types().empty()); + EXPECT_EQ(0, observer1.initialized_app_type_count()); + EXPECT_EQ(0, observer1.app_count_at_initialization()); + EXPECT_EQ(0u, cache.InitializedAppTypes().size()); + EXPECT_FALSE(cache.IsAppTypeInitialized(AppType::kArc)); + + std::vector<apps::mojom::AppPtr> mojom_deltas1; + mojom_deltas1.push_back(MakeMojomApp("a", "avocado")); + mojom_deltas1.push_back(MakeMojomApp("c", "cucumber")); + cache.OnApps(std::move(mojom_deltas1), apps::mojom::AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is called when both the non mojom and mojom + // Apps are added. + EXPECT_TRUE(base::Contains(observer1.app_types(), AppType::kArc)); + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(2, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(AppType::kArc)); + + std::vector<AppPtr> deltas2; + deltas2.push_back(MakeApp("d", "durian")); + cache.OnApps(std::move(deltas2), AppType::kArc, + true /* should_notify_initialized */); + + std::vector<apps::mojom::AppPtr> mojom_deltas2; + mojom_deltas2.push_back(MakeMojomApp("d", "durian")); + cache.OnApps(std::move(mojom_deltas2), apps::mojom::AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when more Apps are + // added. + EXPECT_TRUE(base::Contains(observer1.app_types(), AppType::kArc)); + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(2, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + + // Verify the new observers should not have OnAppTypeInitialized called. + InitializedObserver observer2(&cache); + EXPECT_TRUE(observer2.app_types().empty()); + EXPECT_EQ(0, observer2.initialized_app_type_count()); + EXPECT_EQ(0, observer2.app_count_at_initialization()); +} + +// Verify the OnAppTypeInitialized callback when OnApps is called for the mojom +// App type first, with the enabled flag. +TEST_F(AppRegistryCacheTest, + OnAppTypeInitializedWithEnableFlagMojomUpdateFirst) { + EnableOnAppTypeInitializedFlag(); + + AppRegistryCache cache; + InitializedObserver observer1(&cache); + + std::vector<apps::mojom::AppPtr> mojom_deltas1; + mojom_deltas1.push_back(MakeMojomApp("a", "avocado")); + mojom_deltas1.push_back(MakeMojomApp("c", "cucumber")); + cache.OnApps(std::move(mojom_deltas1), apps::mojom::AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the mojom Apps are added. + EXPECT_TRUE(observer1.app_types().empty()); + EXPECT_EQ(0, observer1.initialized_app_type_count()); + EXPECT_EQ(0, observer1.app_count_at_initialization()); + EXPECT_EQ(0u, cache.InitializedAppTypes().size()); + EXPECT_FALSE(cache.IsAppTypeInitialized(AppType::kArc)); + + std::vector<AppPtr> deltas1; + deltas1.push_back(MakeApp("a", "avocado")); + deltas1.push_back(MakeApp("c", "cucumber")); + cache.OnApps(std::move(deltas1), AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is called when both the non mojom and mojom + // Apps are added. + EXPECT_TRUE(base::Contains(observer1.app_types(), AppType::kArc)); + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(2, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(AppType::kArc)); + + std::vector<apps::mojom::AppPtr> mojom_deltas2; + mojom_deltas2.push_back(MakeMojomApp("d", "durian")); + cache.OnApps(std::move(mojom_deltas2), apps::mojom::AppType::kArc, + true /* should_notify_initialized */); + + std::vector<AppPtr> deltas2; + deltas2.push_back(MakeApp("d", "durian")); + cache.OnApps(std::move(deltas2), AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the Apps are added. + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(2, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + + // Verify the new observers should not have OnAppTypeInitialized called. + InitializedObserver observer2(&cache); + EXPECT_TRUE(observer2.app_types().empty()); + EXPECT_EQ(0, observer2.initialized_app_type_count()); + EXPECT_EQ(0, observer2.app_count_at_initialization()); +} + +// Verify the OnAppTypeInitialized callback when OnApps is called for multiple +// App types, with the enabled flag. +TEST_F(AppRegistryCacheTest, + OnAppTypeInitializedWithEnableFlagMultipleAppTypes) { + EnableOnAppTypeInitializedFlag(); + + AppRegistryCache cache; + InitializedObserver observer1(&cache); + + std::vector<AppPtr> deltas1; + deltas1.push_back(MakeApp("a", "avocado")); + deltas1.push_back(MakeApp("c", "cucumber")); + cache.OnApps(std::move(deltas1), AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the non mojom Apps are + // added. + EXPECT_TRUE(observer1.app_types().empty()); + EXPECT_EQ(0, observer1.initialized_app_type_count()); + EXPECT_EQ(0, observer1.app_count_at_initialization()); + EXPECT_TRUE(cache.InitializedAppTypes().empty()); + + std::vector<apps::mojom::AppPtr> mojom_deltas1; + mojom_deltas1.push_back(MakeMojomApp("a", "avocado")); + mojom_deltas1.push_back(MakeMojomApp("c", "cucumber")); + cache.OnApps(std::move(mojom_deltas1), apps::mojom::AppType::kArc, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is called when both the non mojom and mojom + // Apps are added. + EXPECT_TRUE(base::Contains(observer1.app_types(), AppType::kArc)); + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(2, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(AppType::kArc)); + + std::vector<apps::mojom::AppPtr> mojom_deltas2; + mojom_deltas2.push_back( + MakeMojomApp("d", "durian", apps::mojom::AppType::kChromeApp)); + cache.OnApps(std::move(mojom_deltas2), apps::mojom::AppType::kChromeApp, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the mojom Apps are added. + EXPECT_EQ(1u, observer1.app_types().size()); + EXPECT_FALSE(base::Contains(observer1.app_types(), AppType::kChromeApp)); + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(2, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + EXPECT_FALSE(cache.IsAppTypeInitialized(AppType::kChromeApp)); + + std::vector<AppPtr> deltas2; + deltas2.push_back(MakeApp("d", "durian", AppType::kChromeApp)); + cache.OnApps(std::move(deltas2), AppType::kChromeApp, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is called when both the non mojom and mojom + // Apps are added. + EXPECT_EQ(2u, observer1.app_types().size()); + EXPECT_TRUE(base::Contains(observer1.app_types(), AppType::kChromeApp)); + EXPECT_EQ(2, observer1.initialized_app_type_count()); + EXPECT_EQ(5, observer1.app_count_at_initialization()); + EXPECT_EQ(2u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(AppType::kChromeApp)); + + // Verify the new observers should not have OnAppTypeInitialized called. + InitializedObserver observer2(&cache); + EXPECT_TRUE(observer2.app_types().empty()); + EXPECT_EQ(0, observer2.initialized_app_type_count()); + EXPECT_EQ(0, observer2.app_count_at_initialization()); +} + +// Verify the OnAppTypeInitialized callback when OnApps is called for empty apps +// vector, with the enabled flag. +TEST_F(AppRegistryCacheTest, OnAppTypeInitializedWithEnableFlagEmptyUpdate) { + EnableOnAppTypeInitializedFlag(); + + AppRegistryCache cache; + InitializedObserver observer1(&cache); + + std::vector<AppPtr> deltas1; + cache.OnApps(std::move(deltas1), AppType::kStandaloneBrowserChromeApp, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the non mojom Apps are + // initialized. + EXPECT_TRUE(observer1.app_types().empty()); + EXPECT_EQ(0, observer1.initialized_app_type_count()); + EXPECT_EQ(0, observer1.app_count_at_initialization()); + EXPECT_TRUE(cache.InitializedAppTypes().empty()); + + std::vector<apps::mojom::AppPtr> mojom_deltas1; + cache.OnApps(std::move(mojom_deltas1), + apps::mojom::AppType::kStandaloneBrowserChromeApp, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is called when both the mojom and non mojom + // Apps are initialized. + EXPECT_TRUE(base::Contains(observer1.app_types(), + AppType::kStandaloneBrowserChromeApp)); + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(0, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(AppType::kStandaloneBrowserChromeApp)); + + std::vector<AppPtr> deltas2; + deltas2.push_back(MakeApp("d", "durian")); + cache.OnApps(std::move(deltas2), AppType::kStandaloneBrowserChromeApp, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the non mojom Apps are + // initialized again. + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(0, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + + std::vector<apps::mojom::AppPtr> mojom_deltas2; + mojom_deltas2.push_back(MakeMojomApp("d", "durian")); + cache.OnApps(std::move(mojom_deltas2), + apps::mojom::AppType::kStandaloneBrowserChromeApp, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the mojom Apps are + // initialized again. + EXPECT_EQ(1, observer1.initialized_app_type_count()); + EXPECT_EQ(0, observer1.app_count_at_initialization()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + + std::vector<apps::mojom::AppPtr> mojom_deltas3; + cache.OnApps(std::move(mojom_deltas3), apps::mojom::AppType::kRemote, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is not called when the mojom Apps are + // initialized. + EXPECT_EQ(1u, observer1.app_types().size()); + EXPECT_EQ(1u, cache.InitializedAppTypes().size()); + + std::vector<AppPtr> deltas3; + cache.OnApps(std::move(deltas3), AppType::kRemote, + true /* should_notify_initialized */); + + // Verify OnAppTypeInitialized is called when both the mojom and non mojom + // Apps are initialized. + EXPECT_EQ(2u, observer1.app_types().size()); + EXPECT_TRUE(base::Contains(observer1.app_types(), AppType::kRemote)); + EXPECT_EQ(2, observer1.initialized_app_type_count()); + EXPECT_EQ(2u, cache.InitializedAppTypes().size()); + EXPECT_TRUE(cache.IsAppTypeInitialized(AppType::kRemote)); + + // Verify the new observers should not have OnAppTypeInitialized called. + InitializedObserver observer2(&cache); + EXPECT_TRUE(observer2.app_types().empty()); + EXPECT_EQ(0, observer2.initialized_app_type_count()); + EXPECT_EQ(0, observer2.app_count_at_initialization()); +} + } // namespace apps diff --git a/chromium/components/services/app_service/public/cpp/app_types.cc b/chromium/components/services/app_service/public/cpp/app_types.cc index 4a6d3ed29c9..abacfc3c8a1 100644 --- a/chromium/components/services/app_service/public/cpp/app_types.cc +++ b/chromium/components/services/app_service/public/cpp/app_types.cc @@ -11,8 +11,8 @@ App::App(AppType app_type, const std::string& app_id) App::~App() = default; -std::unique_ptr<App> App::Clone() const { - std::unique_ptr<App> app = std::make_unique<App>(app_type, app_id); +AppPtr App::Clone() const { + auto app = std::make_unique<App>(app_type, app_id); app->readiness = readiness; app->name = name; @@ -20,11 +20,39 @@ std::unique_ptr<App> App::Clone() const { app->publisher_id = publisher_id; app->description = description; app->version = version; + app->additional_search_terms = additional_search_terms; if (icon_key.has_value()) { app->icon_key = apps::IconKey(icon_key->timeline, icon_key->resource_id, icon_key->icon_effects); } + + app->last_launch_time = last_launch_time; + app->install_time = install_time; + app->permissions = ClonePermissions(permissions); + app->install_reason = install_reason; + app->install_source = install_source; + app->policy_id = policy_id; + app->is_platform_app = is_platform_app; + app->recommendable = recommendable; + app->searchable = searchable; + app->show_in_launcher = show_in_launcher; + app->show_in_shelf = show_in_shelf; + app->show_in_search = show_in_search; + app->show_in_management = show_in_management; + app->handles_intents = handles_intents; + app->allow_uninstall = allow_uninstall; + app->has_badge = has_badge; + app->paused = paused; + app->intent_filters = CloneIntentFilters(intent_filters); + app->resize_locked = resize_locked; + app->window_mode = window_mode; + + if (run_on_os_login.has_value()) { + app->run_on_os_login = apps::RunOnOsLogin(run_on_os_login->login_mode, + run_on_os_login->is_managed); + } + return app; } @@ -61,6 +89,39 @@ AppType ConvertMojomAppTypToAppType(apps::mojom::AppType mojom_app_type) { } } +mojom::AppType ConvertAppTypeToMojomAppType(AppType app_type) { + switch (app_type) { + case AppType::kUnknown: + return apps::mojom::AppType::kUnknown; + case AppType::kArc: + return apps::mojom::AppType::kArc; + case AppType::kBuiltIn: + return apps::mojom::AppType::kBuiltIn; + case AppType::kCrostini: + return apps::mojom::AppType::kCrostini; + case AppType::kChromeApp: + return apps::mojom::AppType::kChromeApp; + case AppType::kWeb: + return apps::mojom::AppType::kWeb; + case AppType::kMacOs: + return apps::mojom::AppType::kMacOs; + case AppType::kPluginVm: + return apps::mojom::AppType::kPluginVm; + case AppType::kStandaloneBrowser: + return apps::mojom::AppType::kStandaloneBrowser; + case AppType::kRemote: + return apps::mojom::AppType::kRemote; + case AppType::kBorealis: + return apps::mojom::AppType::kBorealis; + case AppType::kSystemWeb: + return apps::mojom::AppType::kSystemWeb; + case AppType::kStandaloneBrowserChromeApp: + return apps::mojom::AppType::kStandaloneBrowserChromeApp; + case AppType::kExtension: + return apps::mojom::AppType::kExtension; + } +} + Readiness ConvertMojomReadinessToReadiness( apps::mojom::Readiness mojom_readiness) { switch (mojom_readiness) { @@ -85,10 +146,157 @@ Readiness ConvertMojomReadinessToReadiness( } } -std::unique_ptr<App> ConvertMojomAppToApp( - const apps::mojom::AppPtr& mojom_app) { +apps::mojom::Readiness ConvertReadinessToMojomReadiness(Readiness readiness) { + switch (readiness) { + case Readiness::kUnknown: + return apps::mojom::Readiness::kUnknown; + case Readiness::kReady: + return apps::mojom::Readiness::kReady; + case Readiness::kDisabledByBlocklist: + return apps::mojom::Readiness::kDisabledByBlocklist; + case Readiness::kDisabledByPolicy: + return apps::mojom::Readiness::kDisabledByPolicy; + case Readiness::kDisabledByUser: + return apps::mojom::Readiness::kDisabledByUser; + case Readiness::kTerminated: + return apps::mojom::Readiness::kTerminated; + case Readiness::kUninstalledByUser: + return apps::mojom::Readiness::kUninstalledByUser; + case Readiness::kRemoved: + return apps::mojom::Readiness::kRemoved; + case Readiness::kUninstalledByMigration: + return apps::mojom::Readiness::kUninstalledByMigration; + } +} + +InstallReason ConvertMojomInstallReasonToInstallReason( + apps::mojom::InstallReason mojom_install_reason) { + switch (mojom_install_reason) { + case apps::mojom::InstallReason::kUnknown: + return InstallReason::kUnknown; + case apps::mojom::InstallReason::kSystem: + return InstallReason::kSystem; + case apps::mojom::InstallReason::kPolicy: + return InstallReason::kPolicy; + case apps::mojom::InstallReason::kOem: + return InstallReason::kOem; + case apps::mojom::InstallReason::kDefault: + return InstallReason::kDefault; + case apps::mojom::InstallReason::kSync: + return InstallReason::kSync; + case apps::mojom::InstallReason::kUser: + return InstallReason::kUser; + case apps::mojom::InstallReason::kSubApp: + return InstallReason::kSubApp; + } +} + +apps::mojom::InstallReason ConvertInstallReasonToMojomInstallReason( + InstallReason install_reason) { + switch (install_reason) { + case InstallReason::kUnknown: + return apps::mojom::InstallReason::kUnknown; + case InstallReason::kSystem: + return apps::mojom::InstallReason::kSystem; + case InstallReason::kPolicy: + return apps::mojom::InstallReason::kPolicy; + case InstallReason::kOem: + return apps::mojom::InstallReason::kOem; + case InstallReason::kDefault: + return apps::mojom::InstallReason::kDefault; + case InstallReason::kSync: + return apps::mojom::InstallReason::kSync; + case InstallReason::kUser: + return apps::mojom::InstallReason::kUser; + case InstallReason::kSubApp: + return apps::mojom::InstallReason::kSubApp; + } +} + +InstallSource ConvertMojomInstallSourceToInstallSource( + apps::mojom::InstallSource mojom_install_source) { + switch (mojom_install_source) { + case apps::mojom::InstallSource::kUnknown: + return InstallSource::kUnknown; + case apps::mojom::InstallSource::kSystem: + return InstallSource::kSystem; + case apps::mojom::InstallSource::kSync: + return InstallSource::kSync; + case apps::mojom::InstallSource::kPlayStore: + return InstallSource::kPlayStore; + case apps::mojom::InstallSource::kChromeWebStore: + return InstallSource::kChromeWebStore; + case apps::mojom::InstallSource::kBrowser: + return InstallSource::kBrowser; + } +} + +apps::mojom::InstallSource ConvertInstallSourceToMojomInstallSource( + InstallSource install_source) { + switch (install_source) { + case InstallSource::kUnknown: + return apps::mojom::InstallSource::kUnknown; + case InstallSource::kSystem: + return apps::mojom::InstallSource::kSystem; + case InstallSource::kSync: + return apps::mojom::InstallSource::kSync; + case InstallSource::kPlayStore: + return apps::mojom::InstallSource::kPlayStore; + case InstallSource::kChromeWebStore: + return apps::mojom::InstallSource::kChromeWebStore; + case InstallSource::kBrowser: + return apps::mojom::InstallSource::kBrowser; + } +} + +WindowMode ConvertMojomWindowModeToWindowMode( + apps::mojom::WindowMode mojom_window_mode) { + switch (mojom_window_mode) { + case apps::mojom::WindowMode::kUnknown: + return WindowMode::kUnknown; + case apps::mojom::WindowMode::kWindow: + return WindowMode::kWindow; + case apps::mojom::WindowMode::kBrowser: + return WindowMode::kBrowser; + case apps::mojom::WindowMode::kTabbedWindow: + return WindowMode::kTabbedWindow; + } +} + +apps::mojom::WindowMode ConvertWindowModeToMojomWindowMode( + WindowMode window_mode) { + switch (window_mode) { + case WindowMode::kUnknown: + return apps::mojom::WindowMode::kUnknown; + case WindowMode::kWindow: + return apps::mojom::WindowMode::kWindow; + case WindowMode::kBrowser: + return apps::mojom::WindowMode::kBrowser; + case WindowMode::kTabbedWindow: + return apps::mojom::WindowMode::kTabbedWindow; + } +} + +absl::optional<bool> GetOptionalBool( + const apps::mojom::OptionalBool& mojom_optional_bool) { + absl::optional<bool> optional_bool; + if (mojom_optional_bool != apps::mojom::OptionalBool::kUnknown) { + optional_bool = mojom_optional_bool == apps::mojom::OptionalBool::kTrue; + } + return optional_bool; +} + +apps::mojom::OptionalBool GetMojomOptionalBool( + const absl::optional<bool>& optional_bool) { + return optional_bool.has_value() + ? (optional_bool.value() ? apps::mojom::OptionalBool::kTrue + : apps::mojom::OptionalBool::kFalse) + : apps::mojom::OptionalBool::kUnknown; +} + +AppPtr ConvertMojomAppToApp(const apps::mojom::AppPtr& mojom_app) { DCHECK(mojom_app); - std::unique_ptr<App> app = std::make_unique<App>( + auto app = std::make_unique<App>( ConvertMojomAppTypToAppType(mojom_app->app_type), mojom_app->app_id); app->readiness = ConvertMojomReadinessToReadiness(mojom_app->readiness); @@ -97,13 +305,121 @@ std::unique_ptr<App> ConvertMojomAppToApp( app->publisher_id = mojom_app->publisher_id; app->description = mojom_app->description; app->version = mojom_app->version; + app->additional_search_terms = mojom_app->additional_search_terms; if (mojom_app->icon_key) { app->icon_key = apps::IconKey(mojom_app->icon_key->timeline, mojom_app->icon_key->resource_id, mojom_app->icon_key->icon_effects); } + + app->last_launch_time = mojom_app->last_launch_time; + app->install_time = mojom_app->install_time; + + for (const auto& mojom_permission : mojom_app->permissions) { + auto permission = ConvertMojomPermissionToPermission(mojom_permission); + if (permission) { + app->permissions.push_back(std::move(permission)); + } + } + + app->install_reason = + ConvertMojomInstallReasonToInstallReason(mojom_app->install_reason); + app->install_source = + ConvertMojomInstallSourceToInstallSource(mojom_app->install_source); + + app->policy_id = mojom_app->policy_id; + + app->is_platform_app = GetOptionalBool(mojom_app->is_platform_app); + app->recommendable = GetOptionalBool(mojom_app->recommendable); + app->searchable = GetOptionalBool(mojom_app->searchable); + app->show_in_launcher = GetOptionalBool(mojom_app->show_in_launcher); + app->show_in_shelf = GetOptionalBool(mojom_app->show_in_shelf); + app->show_in_search = GetOptionalBool(mojom_app->show_in_search); + app->show_in_management = GetOptionalBool(mojom_app->show_in_management); + app->handles_intents = GetOptionalBool(mojom_app->handles_intents); + app->allow_uninstall = GetOptionalBool(mojom_app->allow_uninstall); + app->has_badge = GetOptionalBool(mojom_app->has_badge); + app->paused = GetOptionalBool(mojom_app->paused); + + for (const auto& mojom_intent_filter : mojom_app->intent_filters) { + auto intent_filter = + ConvertMojomIntentFilterToIntentFilter(mojom_intent_filter); + if (intent_filter) { + app->intent_filters.push_back(std::move(intent_filter)); + } + } + + app->resize_locked = GetOptionalBool(mojom_app->resize_locked); + app->window_mode = ConvertMojomWindowModeToWindowMode(mojom_app->window_mode); + if (mojom_app->run_on_os_login) { + app->run_on_os_login = + apps::RunOnOsLogin(ConvertMojomRunOnOsLoginModeToRunOnOsLoginMode( + mojom_app->run_on_os_login->login_mode), + mojom_app->run_on_os_login->is_managed); + } + return app; } +apps::mojom::AppPtr ConvertAppToMojomApp(const AppPtr& app) { + auto mojom_app = apps::mojom::App::New(); + mojom_app->app_type = ConvertAppTypeToMojomAppType(app->app_type); + mojom_app->app_id = app->app_id; + mojom_app->readiness = ConvertReadinessToMojomReadiness(app->readiness); + mojom_app->name = app->name; + mojom_app->short_name = app->short_name; + mojom_app->publisher_id = app->publisher_id; + mojom_app->description = app->description; + mojom_app->version = app->version; + mojom_app->additional_search_terms = app->additional_search_terms; + + if (app->icon_key.has_value()) { + mojom_app->icon_key = ConvertIconKeyToMojomIconKey(app->icon_key.value()); + } + + mojom_app->last_launch_time = app->last_launch_time; + mojom_app->install_time = app->install_time; + + for (const auto& permission : app->permissions) { + if (permission) { + mojom_app->permissions.push_back( + ConvertPermissionToMojomPermission(permission)); + } + } + + mojom_app->install_reason = + ConvertInstallReasonToMojomInstallReason(app->install_reason); + mojom_app->install_source = + ConvertInstallSourceToMojomInstallSource(app->install_source); + mojom_app->policy_id = app->policy_id; + mojom_app->is_platform_app = GetMojomOptionalBool(app->is_platform_app); + mojom_app->recommendable = GetMojomOptionalBool(app->recommendable); + mojom_app->searchable = GetMojomOptionalBool(app->searchable); + mojom_app->show_in_launcher = GetMojomOptionalBool(app->show_in_launcher); + mojom_app->show_in_shelf = GetMojomOptionalBool(app->show_in_shelf); + mojom_app->show_in_search = GetMojomOptionalBool(app->show_in_search); + mojom_app->show_in_management = GetMojomOptionalBool(app->show_in_management); + mojom_app->handles_intents = GetMojomOptionalBool(app->handles_intents); + mojom_app->allow_uninstall = GetMojomOptionalBool(app->allow_uninstall); + mojom_app->has_badge = GetMojomOptionalBool(app->has_badge); + mojom_app->paused = GetMojomOptionalBool(app->paused); + + for (const auto& intent_filter : app->intent_filters) { + if (intent_filter) { + mojom_app->intent_filters.push_back( + ConvertIntentFilterToMojomIntentFilter(intent_filter)); + } + } + + mojom_app->resize_locked = GetMojomOptionalBool(app->resize_locked); + mojom_app->window_mode = ConvertWindowModeToMojomWindowMode(app->window_mode); + + if (app->run_on_os_login.has_value()) { + mojom_app->run_on_os_login = + ConvertRunOnOsLoginToMojomRunOnOsLogin(app->run_on_os_login.value()); + } + return mojom_app; +} + } // namespace apps diff --git a/chromium/components/services/app_service/public/cpp/app_types.h b/chromium/components/services/app_service/public/cpp/app_types.h index d22de50352a..0e7fdc2ac43 100644 --- a/chromium/components/services/app_service/public/cpp/app_types.h +++ b/chromium/components/services/app_service/public/cpp/app_types.h @@ -7,8 +7,14 @@ #include <string> #include <utility> +#include <vector> +#include "base/component_export.h" +#include "base/time/time.h" #include "components/services/app_service/public/cpp/icon_types.h" +#include "components/services/app_service/public/cpp/intent_filter.h" +#include "components/services/app_service/public/cpp/permission.h" +#include "components/services/app_service/public/cpp/run_on_os_login_types.h" #include "components/services/app_service/public/mojom/types.mojom.h" #include "third_party/abseil-cpp/absl/types/optional.h" @@ -54,6 +60,55 @@ enum class Readiness { kMaxValue = kUninstalledByMigration, }; +// How the app was installed. +// This should be kept in sync with histograms.xml, and InstallReason in +// enums.xml. +// Note the enumeration is used in UMA histogram so entries should not be +// re-ordered or removed. New entries should be added at the bottom. +enum class InstallReason { + kUnknown = 0, + kSystem, // Installed with the system and is considered a part of the OS. + kPolicy, // Installed by policy. + kOem, // Installed by an OEM. + kDefault, // Preinstalled by default, but is not considered a system app. + kSync, // Installed by sync. + kUser, // Installed by user action. + kSubApp, // Installed by the SubApp API call. + + // Add any new values above this one, and update kMaxValue to the highest + // enumerator value. + kMaxValue = kSubApp, +}; + +// Where the app was installed from. +// This should be kept in sync with histograms.xml, and InstallSource in +// enums.xml. +// Note the enumeration is used in UMA histogram so entries should not be +// re-ordered or removed. New entries should be added at the bottom. +enum class InstallSource { + kUnknown = 0, + kSystem, // Installed as part of Chrome OS. + kSync, // Installed from sync. + kPlayStore, // Installed from Play store. + kChromeWebStore, // Installed from Chrome web store. + kBrowser, // Installed from browser. + + // Add any new values above this one, and update kMaxValue to the highest + // enumerator value. + kMaxValue = kBrowser, +}; + +// The window mode that each app will open in. +enum class WindowMode { + kUnknown = 0, + // Opens in a standalone window + kWindow, + // Opens in the default web browser + kBrowser, + // Opens in a tabbed app window + kTabbedWindow, +}; + struct COMPONENT_EXPORT(APP_TYPES) App { App(AppType app_type, const std::string& app_id); @@ -78,26 +133,130 @@ struct COMPONENT_EXPORT(APP_TYPES) App { absl::optional<std::string> description; absl::optional<std::string> version; + std::vector<std::string> additional_search_terms; absl::optional<IconKey> icon_key; - // TODO(crbug.com/1253250): Add other App struct fields. + absl::optional<base::Time> last_launch_time; + absl::optional<base::Time> install_time; + + // This vector must be treated atomically, if there is a permission + // change, the publisher must send through the entire list of permissions. + // Should contain no duplicate IDs. + // If empty during updates, Subscriber can assume no changes. + // There is no guarantee that this is sorted by any criteria. + Permissions permissions; + + // Whether the app was installed by sync, policy or as a default app. + InstallReason install_reason = InstallReason::kUnknown; + + // Where the app was installed from, e.g. from Play Store, from Chrome Web + // Store, etc. + InstallSource install_source = InstallSource::kUnknown; + + // An optional ID used for policy to identify the app. + // For web apps, it contains the install URL. + absl::optional<std::string> policy_id; + + // Whether the app is an extensions::Extensions where is_platform_app() + // returns true. + absl::optional<bool> is_platform_app; + + absl::optional<bool> recommendable; + absl::optional<bool> searchable; + absl::optional<bool> show_in_launcher; + absl::optional<bool> show_in_shelf; + absl::optional<bool> show_in_search; + absl::optional<bool> show_in_management; + + // True if the app is able to handle intents and should be shown in intent + // surfaces. + absl::optional<bool> handles_intents; + + // Whether the app publisher allows the app to be uninstalled. + absl::optional<bool> allow_uninstall; + + // Whether the app icon should add the notification badging. + absl::optional<bool> has_badge; + + // Paused apps cannot be launched, and any running apps that become paused + // will be stopped. This is independent of whether or not the app is ready to + // be launched (defined by the Readiness field). + absl::optional<bool> paused; + + // This vector stores all the intent filters defined in this app. Each + // intent filter defines a matching criteria for whether an intent can + // be handled by this app. One app can have multiple intent filters. + IntentFilters intent_filters; + + // Whether the app can be free resized. If this is true, various resizing + // operations will be restricted. + absl::optional<bool> resize_locked; + + // Whether the app's display mode is in the browser or otherwise. + WindowMode window_mode = WindowMode::kUnknown; + + // Whether the app runs on os login in a new window or not. + absl::optional<RunOnOsLogin> run_on_os_login; // When adding new fields to the App type, the `Clone` function and the // `AppUpdate` class should also be updated. }; +using AppPtr = std::unique_ptr<App>; + // TODO(crbug.com/1253250): Remove these functions after migrating to non-mojo // AppService. COMPONENT_EXPORT(APP_TYPES) AppType ConvertMojomAppTypToAppType(apps::mojom::AppType mojom_app_type); COMPONENT_EXPORT(APP_TYPES) +mojom::AppType ConvertAppTypeToMojomAppType(AppType mojom_app_type); + +COMPONENT_EXPORT(APP_TYPES) Readiness ConvertMojomReadinessToReadiness( apps::mojom::Readiness mojom_readiness); COMPONENT_EXPORT(APP_TYPES) -std::unique_ptr<App> ConvertMojomAppToApp(const apps::mojom::AppPtr& mojom_app); +apps::mojom::Readiness ConvertReadinessToMojomReadiness(Readiness readiness); + +COMPONENT_EXPORT(APP_TYPES) +InstallReason ConvertMojomInstallReasonToInstallReason( + apps::mojom::InstallReason mojom_install_reason); + +COMPONENT_EXPORT(APP_TYPES) +apps::mojom::InstallReason ConvertInstallReasonToMojomInstallReason( + InstallReason install_reason); + +COMPONENT_EXPORT(APP_TYPES) +InstallSource ConvertMojomInstallSourceToInstallSource( + apps::mojom::InstallSource mojom_install_source); + +COMPONENT_EXPORT(APP_TYPES) +apps::mojom::InstallSource ConvertInstallSourceToMojomInstallSource( + InstallSource install_source); + +COMPONENT_EXPORT(APP_TYPES) +WindowMode ConvertMojomWindowModeToWindowMode( + apps::mojom::WindowMode mojom_window_mode); + +COMPONENT_EXPORT(APP_TYPES) +apps::mojom::WindowMode ConvertWindowModeToMojomWindowMode( + WindowMode mojom_window_mode); + +COMPONENT_EXPORT(APP_TYPES) +absl::optional<bool> GetOptionalBool( + const apps::mojom::OptionalBool& mojom_optional_bool); + +COMPONENT_EXPORT(APP_TYPES) +absl::optional<bool> GetMojomOptionalBool( + const apps::mojom::OptionalBool& mojom_optional_bool); + +COMPONENT_EXPORT(APP_TYPES) +AppPtr ConvertMojomAppToApp(const apps::mojom::AppPtr& mojom_app); + +COMPONENT_EXPORT(APP_TYPES) +apps::mojom::AppPtr ConvertAppToMojomApp(const AppPtr& app); } // namespace apps diff --git a/chromium/components/services/app_service/public/cpp/app_update.cc b/chromium/components/services/app_service/public/cpp/app_update.cc index 71a696f89cf..2950815e592 100644 --- a/chromium/components/services/app_service/public/cpp/app_update.cc +++ b/chromium/components/services/app_service/public/cpp/app_update.cc @@ -9,6 +9,7 @@ #include "base/time/time.h" #include "components/services/app_service/public/cpp/icon_types.h" #include "components/services/app_service/public/cpp/intent_filter_util.h" +#include "components/services/app_service/public/cpp/macros.h" namespace { @@ -39,6 +40,11 @@ absl::optional<apps::IconKey> CloneIconKey(const apps::IconKey& icon_key) { icon_key.icon_effects); } +absl::optional<apps::RunOnOsLogin> CloneRunOnOsLogin( + const apps::RunOnOsLogin& login_data) { + return apps::RunOnOsLogin(login_data.login_mode, login_data.is_managed); +} + } // namespace namespace apps { @@ -99,7 +105,7 @@ void AppUpdate::Merge(apps::mojom::App* state, const apps::mojom::App* delta) { DCHECK(state->permissions.empty() || (delta->permissions.size() == state->permissions.size())); state->permissions.clear(); - ClonePermissions(delta->permissions, &state->permissions); + ::ClonePermissions(delta->permissions, &state->permissions); } if (delta->install_reason != apps::mojom::InstallReason::kUnknown) { state->install_reason = delta->install_reason; @@ -145,7 +151,7 @@ void AppUpdate::Merge(apps::mojom::App* state, const apps::mojom::App* delta) { } if (!delta->intent_filters.empty()) { state->intent_filters.clear(); - CloneIntentFilters(delta->intent_filters, &state->intent_filters); + ::CloneIntentFilters(delta->intent_filters, &state->intent_filters); } if (delta->resize_locked != apps::mojom::OptionalBool::kUnknown) { state->resize_locked = delta->resize_locked; @@ -153,6 +159,9 @@ void AppUpdate::Merge(apps::mojom::App* state, const apps::mojom::App* delta) { if (delta->window_mode != apps::mojom::WindowMode::kUnknown) { state->window_mode = delta->window_mode; } + if (!delta->run_on_os_login.is_null()) { + state->run_on_os_login = delta->run_on_os_login.Clone(); + } // When adding new fields to the App Mojo type, this function should also be // updated. @@ -175,28 +184,59 @@ void AppUpdate::Merge(App* state, const App* delta) { DCHECK_NE(state->readiness, Readiness::kRemoved); DCHECK_NE(delta->readiness, Readiness::kRemoved); - if (delta->readiness != apps::Readiness::kUnknown) { - state->readiness = delta->readiness; - } - if (delta->name.has_value()) { - state->name = delta->name; - } - if (delta->short_name.has_value()) { - state->short_name = delta->short_name; - } - if (delta->publisher_id.has_value()) { - state->publisher_id = delta->publisher_id; - } - if (delta->description.has_value()) { - state->description = delta->description; - } - if (delta->version.has_value()) { - state->version = delta->version; + SET_ENUM_VALUE(readiness, apps::Readiness::kUnknown); + SET_OPTIONAL_VALUE(name) + SET_OPTIONAL_VALUE(short_name) + SET_OPTIONAL_VALUE(publisher_id) + SET_OPTIONAL_VALUE(description) + SET_OPTIONAL_VALUE(version) + + if (!delta->additional_search_terms.empty()) { + state->additional_search_terms.clear(); + state->additional_search_terms = delta->additional_search_terms; } + if (delta->icon_key.has_value()) { state->icon_key = CloneIconKey(delta->icon_key.value()); } + SET_OPTIONAL_VALUE(last_launch_time); + SET_OPTIONAL_VALUE(install_time); + + if (!delta->permissions.empty()) { + DCHECK(state->permissions.empty() || + (delta->permissions.size() == state->permissions.size())); + state->permissions.clear(); + state->permissions = ClonePermissions(delta->permissions); + } + + SET_ENUM_VALUE(install_reason, InstallReason::kUnknown); + SET_ENUM_VALUE(install_source, InstallSource::kUnknown); + SET_OPTIONAL_VALUE(policy_id); + SET_OPTIONAL_VALUE(is_platform_app); + SET_OPTIONAL_VALUE(recommendable); + SET_OPTIONAL_VALUE(searchable); + SET_OPTIONAL_VALUE(show_in_launcher); + SET_OPTIONAL_VALUE(show_in_shelf); + SET_OPTIONAL_VALUE(show_in_search); + SET_OPTIONAL_VALUE(show_in_management); + SET_OPTIONAL_VALUE(handles_intents); + SET_OPTIONAL_VALUE(allow_uninstall); + SET_OPTIONAL_VALUE(has_badge); + SET_OPTIONAL_VALUE(paused); + + if (!delta->intent_filters.empty()) { + state->intent_filters.clear(); + state->intent_filters = CloneIntentFilters(delta->intent_filters); + } + + SET_OPTIONAL_VALUE(resize_locked) + SET_ENUM_VALUE(window_mode, WindowMode::kUnknown) + + if (delta->run_on_os_login.has_value()) { + state->run_on_os_login = CloneRunOnOsLogin(delta->run_on_os_login.value()); + } + // When adding new fields to the App type, this function should also be // updated. } @@ -260,14 +300,7 @@ apps::mojom::Readiness AppUpdate::PriorReadiness() const { } apps::Readiness AppUpdate::GetReadiness() const { - if (delta_ && (delta_->readiness != apps::Readiness::kUnknown)) { - return delta_->readiness; - } - if (state_) { - return state_->readiness; - } - return apps::Readiness::kUnknown; -} + GET_VALUE_WITH_DEFAULT_VALUE(readiness, apps::Readiness::kUnknown)} apps::Readiness AppUpdate::GetPriorReadiness() const { return state_ ? state_->readiness : apps::Readiness::kUnknown; @@ -291,13 +324,7 @@ const std::string& AppUpdate::Name() const { } const std::string& AppUpdate::GetName() const { - if (delta_ && delta_->name.has_value()) { - return delta_->name.value(); - } - if (state_ && state_->name.has_value()) { - return state_->name.value(); - } - return base::EmptyString(); + GET_VALUE_WITH_FALLBACK(name, base::EmptyString()) } bool AppUpdate::NameChanged() const { @@ -316,13 +343,7 @@ const std::string& AppUpdate::ShortName() const { } const std::string& AppUpdate::GetShortName() const { - if (delta_ && delta_->short_name.has_value()) { - return delta_->short_name.value(); - } - if (state_ && state_->short_name.has_value()) { - return state_->short_name.value(); - } - return base::EmptyString(); + GET_VALUE_WITH_FALLBACK(short_name, base::EmptyString()) } bool AppUpdate::ShortNameChanged() const { @@ -342,13 +363,7 @@ const std::string& AppUpdate::PublisherId() const { } const std::string& AppUpdate::GetPublisherId() const { - if (delta_ && delta_->publisher_id.has_value()) { - return delta_->publisher_id.value(); - } - if (state_ && state_->publisher_id.has_value()) { - return state_->publisher_id.value(); - } - return base::EmptyString(); + GET_VALUE_WITH_FALLBACK(publisher_id, base::EmptyString()) } bool AppUpdate::PublisherIdChanged() const { @@ -368,13 +383,7 @@ const std::string& AppUpdate::Description() const { } const std::string& AppUpdate::GetDescription() const { - if (delta_ && delta_->description.has_value()) { - return delta_->description.value(); - } - if (state_ && state_->description.has_value()) { - return state_->description.value(); - } - return base::EmptyString(); + GET_VALUE_WITH_FALLBACK(description, base::EmptyString()) } bool AppUpdate::DescriptionChanged() const { @@ -394,13 +403,7 @@ const std::string& AppUpdate::Version() const { } const std::string& AppUpdate::GetVersion() const { - if (delta_ && delta_->version.has_value()) { - return delta_->version.value(); - } - if (state_ && state_->version.has_value()) { - return state_->version.value(); - } - return base::EmptyString(); + GET_VALUE_WITH_FALLBACK(version, base::EmptyString()) } bool AppUpdate::VersionChanged() const { @@ -422,6 +425,11 @@ std::vector<std::string> AppUpdate::AdditionalSearchTerms() const { return additional_search_terms; } +std::vector<std::string> AppUpdate::GetAdditionalSearchTerms() const { + GET_VALUE_WITH_CHECK_AND_DEFAULT_RETURN(additional_search_terms, empty, + std::vector<std::string>{}) +} + bool AppUpdate::AdditionalSearchTermsChanged() const { return mojom_delta_ && !mojom_delta_->additional_search_terms.empty() && (!mojom_state_ || (mojom_delta_->additional_search_terms != @@ -464,6 +472,10 @@ base::Time AppUpdate::LastLaunchTime() const { return base::Time(); } +base::Time AppUpdate::GetLastLaunchTime() const { + GET_VALUE_WITH_FALLBACK(last_launch_time, base::Time()) +} + bool AppUpdate::LastLaunchTimeChanged() const { return mojom_delta_ && mojom_delta_->last_launch_time.has_value() && (!mojom_state_ || @@ -480,6 +492,10 @@ base::Time AppUpdate::InstallTime() const { return base::Time(); } +base::Time AppUpdate::GetInstallTime() const { + GET_VALUE_WITH_FALLBACK(install_time, base::Time()) +} + bool AppUpdate::InstallTimeChanged() const { return mojom_delta_ && mojom_delta_->install_time.has_value() && (!mojom_state_ || @@ -490,9 +506,21 @@ std::vector<apps::mojom::PermissionPtr> AppUpdate::Permissions() const { std::vector<apps::mojom::PermissionPtr> permissions; if (mojom_delta_ && !mojom_delta_->permissions.empty()) { - ClonePermissions(mojom_delta_->permissions, &permissions); + ::ClonePermissions(mojom_delta_->permissions, &permissions); } else if (mojom_state_ && !mojom_state_->permissions.empty()) { - ClonePermissions(mojom_state_->permissions, &permissions); + ::ClonePermissions(mojom_state_->permissions, &permissions); + } + + return permissions; +} + +apps::Permissions AppUpdate::GetPermissions() const { + apps::Permissions permissions; + + if (delta_ && !delta_->permissions.empty()) { + permissions = ClonePermissions(delta_->permissions); + } else if (state_ && !state_->permissions.empty()) { + permissions = ClonePermissions(state_->permissions); } return permissions; @@ -515,6 +543,10 @@ apps::mojom::InstallReason AppUpdate::InstallReason() const { return apps::mojom::InstallReason::kUnknown; } +apps::InstallReason AppUpdate::GetInstallReason() const { + GET_VALUE_WITH_DEFAULT_VALUE(install_reason, InstallReason::kUnknown) +} + bool AppUpdate::InstallReasonChanged() const { return mojom_delta_ && (mojom_delta_->install_reason != @@ -534,6 +566,10 @@ apps::mojom::InstallSource AppUpdate::InstallSource() const { return apps::mojom::InstallSource::kUnknown; } +apps::InstallSource AppUpdate::GetInstallSource() const { + GET_VALUE_WITH_DEFAULT_VALUE(install_source, InstallSource::kUnknown) +} + bool AppUpdate::InstallSourceChanged() const { return mojom_delta_ && (mojom_delta_->install_source != @@ -552,6 +588,10 @@ const std::string& AppUpdate::PolicyId() const { return base::EmptyString(); } +const std::string& AppUpdate::GetPolicyId() const { + GET_VALUE_WITH_FALLBACK(policy_id, base::EmptyString()) +} + bool AppUpdate::PolicyIdChanged() const { return mojom_delta_ && mojom_delta_->policy_id.has_value() && (!mojom_state_ || @@ -583,6 +623,10 @@ apps::mojom::OptionalBool AppUpdate::IsPlatformApp() const { return apps::mojom::OptionalBool::kUnknown; } +absl::optional<bool> AppUpdate::GetIsPlatformApp() const { + GET_VALUE_WITH_FALLBACK(is_platform_app, absl::nullopt) +} + bool AppUpdate::IsPlatformAppChanged() const { return mojom_delta_ && (mojom_delta_->is_platform_app != @@ -602,6 +646,10 @@ apps::mojom::OptionalBool AppUpdate::Recommendable() const { return apps::mojom::OptionalBool::kUnknown; } +absl::optional<bool> AppUpdate::GetRecommendable() const { + GET_VALUE_WITH_FALLBACK(recommendable, absl::nullopt) +} + bool AppUpdate::RecommendableChanged() const { return mojom_delta_ && (mojom_delta_->recommendable != apps::mojom::OptionalBool::kUnknown) && @@ -620,6 +668,10 @@ apps::mojom::OptionalBool AppUpdate::Searchable() const { return apps::mojom::OptionalBool::kUnknown; } +absl::optional<bool> AppUpdate::GetSearchable() const { + GET_VALUE_WITH_FALLBACK(searchable, absl::nullopt) +} + bool AppUpdate::SearchableChanged() const { return mojom_delta_ && (mojom_delta_->searchable != apps::mojom::OptionalBool::kUnknown) && @@ -638,6 +690,10 @@ apps::mojom::OptionalBool AppUpdate::ShowInLauncher() const { return apps::mojom::OptionalBool::kUnknown; } +absl::optional<bool> AppUpdate::GetShowInLauncher() const { + GET_VALUE_WITH_FALLBACK(show_in_launcher, absl::nullopt) +} + bool AppUpdate::ShowInLauncherChanged() const { return mojom_delta_ && (mojom_delta_->show_in_launcher != @@ -657,6 +713,10 @@ apps::mojom::OptionalBool AppUpdate::ShowInShelf() const { return apps::mojom::OptionalBool::kUnknown; } +absl::optional<bool> AppUpdate::GetShowInShelf() const { + GET_VALUE_WITH_FALLBACK(show_in_shelf, absl::nullopt) +} + bool AppUpdate::ShowInShelfChanged() const { return mojom_delta_ && (mojom_delta_->show_in_shelf != apps::mojom::OptionalBool::kUnknown) && @@ -675,6 +735,10 @@ apps::mojom::OptionalBool AppUpdate::ShowInSearch() const { return apps::mojom::OptionalBool::kUnknown; } +absl::optional<bool> AppUpdate::GetShowInSearch() const { + GET_VALUE_WITH_FALLBACK(show_in_search, absl::nullopt) +} + bool AppUpdate::ShowInSearchChanged() const { return mojom_delta_ && (mojom_delta_->show_in_search != @@ -694,6 +758,10 @@ apps::mojom::OptionalBool AppUpdate::ShowInManagement() const { return apps::mojom::OptionalBool::kUnknown; } +absl::optional<bool> AppUpdate::GetShowInManagement() const { + GET_VALUE_WITH_FALLBACK(show_in_management, absl::nullopt) +} + bool AppUpdate::ShowInManagementChanged() const { return mojom_delta_ && (mojom_delta_->show_in_management != @@ -713,6 +781,10 @@ apps::mojom::OptionalBool AppUpdate::HandlesIntents() const { return apps::mojom::OptionalBool::kUnknown; } +absl::optional<bool> AppUpdate::GetHandlesIntents() const { + GET_VALUE_WITH_FALLBACK(handles_intents, absl::nullopt) +} + bool AppUpdate::HandlesIntentsChanged() const { return mojom_delta_ && (mojom_delta_->handles_intents != @@ -732,6 +804,10 @@ apps::mojom::OptionalBool AppUpdate::AllowUninstall() const { return apps::mojom::OptionalBool::kUnknown; } +absl::optional<bool> AppUpdate::GetAllowUninstall() const { + GET_VALUE_WITH_FALLBACK(allow_uninstall, absl::nullopt) +} + bool AppUpdate::AllowUninstallChanged() const { return mojom_delta_ && (mojom_delta_->allow_uninstall != @@ -751,6 +827,10 @@ apps::mojom::OptionalBool AppUpdate::HasBadge() const { return apps::mojom::OptionalBool::kUnknown; } +absl::optional<bool> AppUpdate::GetHasBadge() const { + GET_VALUE_WITH_FALLBACK(has_badge, absl::nullopt); +} + bool AppUpdate::HasBadgeChanged() const { return mojom_delta_ && (mojom_delta_->has_badge != apps::mojom::OptionalBool::kUnknown) && @@ -769,6 +849,10 @@ apps::mojom::OptionalBool AppUpdate::Paused() const { return apps::mojom::OptionalBool::kUnknown; } +absl::optional<bool> AppUpdate::GetPaused() const { + GET_VALUE_WITH_FALLBACK(paused, absl::nullopt); +} + bool AppUpdate::PausedChanged() const { return mojom_delta_ && (mojom_delta_->paused != apps::mojom::OptionalBool::kUnknown) && @@ -779,9 +863,21 @@ std::vector<apps::mojom::IntentFilterPtr> AppUpdate::IntentFilters() const { std::vector<apps::mojom::IntentFilterPtr> intent_filters; if (mojom_delta_ && !mojom_delta_->intent_filters.empty()) { - CloneIntentFilters(mojom_delta_->intent_filters, &intent_filters); + ::CloneIntentFilters(mojom_delta_->intent_filters, &intent_filters); } else if (mojom_state_ && !mojom_state_->intent_filters.empty()) { - CloneIntentFilters(mojom_state_->intent_filters, &intent_filters); + ::CloneIntentFilters(mojom_state_->intent_filters, &intent_filters); + } + + return intent_filters; +} + +apps::IntentFilters AppUpdate::GetIntentFilters() const { + apps::IntentFilters intent_filters; + + if (delta_ && !delta_->intent_filters.empty()) { + intent_filters = CloneIntentFilters(delta_->intent_filters); + } else if (state_ && !state_->intent_filters.empty()) { + intent_filters = CloneIntentFilters(state_->intent_filters); } return intent_filters; @@ -803,6 +899,10 @@ apps::mojom::OptionalBool AppUpdate::ResizeLocked() const { return apps::mojom::OptionalBool::kUnknown; } +absl::optional<bool> AppUpdate::GetResizeLocked() const { + GET_VALUE_WITH_FALLBACK(resize_locked, absl::nullopt); +} + bool AppUpdate::ResizeLockedChanged() const { return mojom_delta_ && (mojom_delta_->resize_locked != apps::mojom::OptionalBool::kUnknown) && @@ -821,6 +921,10 @@ apps::mojom::WindowMode AppUpdate::WindowMode() const { return apps::mojom::WindowMode::kUnknown; } +apps::WindowMode AppUpdate::GetWindowMode() const { + GET_VALUE_WITH_DEFAULT_VALUE(window_mode, WindowMode::kUnknown) +} + bool AppUpdate::WindowModeChanged() const { return mojom_delta_ && (mojom_delta_->window_mode != apps::mojom::WindowMode::kUnknown) && @@ -828,6 +932,32 @@ bool AppUpdate::WindowModeChanged() const { (mojom_delta_->window_mode != mojom_state_->window_mode)); } +apps::mojom::RunOnOsLoginPtr AppUpdate::RunOnOsLogin() const { + if (mojom_delta_ && !mojom_delta_->run_on_os_login.is_null()) { + return mojom_delta_->run_on_os_login.Clone(); + } + if (mojom_state_ && !mojom_state_->run_on_os_login.is_null()) { + return mojom_state_->run_on_os_login.Clone(); + } + return apps::mojom::RunOnOsLoginPtr(); +} + +absl::optional<apps::RunOnOsLogin> AppUpdate::GetRunOnOsLogin() const { + if (delta_ && delta_->run_on_os_login.has_value()) { + return CloneRunOnOsLogin(delta_->run_on_os_login.value()); + } + if (state_ && state_->run_on_os_login.has_value()) { + return CloneRunOnOsLogin(state_->run_on_os_login.value()); + } + return absl::nullopt; +} + +bool AppUpdate::RunOnOsLoginChanged() const { + return mojom_delta_ && !mojom_delta_->run_on_os_login.is_null() && + (!mojom_state_ || + !mojom_delta_->run_on_os_login.Equals(mojom_state_->run_on_os_login)); +} + const ::AccountId& AppUpdate::AccountId() const { return account_id_; } @@ -885,6 +1015,9 @@ std::ostream& operator<<(std::ostream& out, const AppUpdate& app) { out << "ResizeLocked: " << app.ResizeLocked() << std::endl; out << "WindowMode: " << app.WindowMode() << std::endl; + if (app.RunOnOsLogin()) { + out << "RunOnOsLoginMode: " << app.RunOnOsLogin()->login_mode << std::endl; + } return out; } diff --git a/chromium/components/services/app_service/public/cpp/app_update.h b/chromium/components/services/app_service/public/cpp/app_update.h index 9070c6b3aca..4c6ccc87bc6 100644 --- a/chromium/components/services/app_service/public/cpp/app_update.h +++ b/chromium/components/services/app_service/public/cpp/app_update.h @@ -13,12 +13,15 @@ #include "base/memory/raw_ptr.h" #include "components/account_id/account_id.h" #include "components/services/app_service/public/cpp/app_types.h" +#include "components/services/app_service/public/cpp/intent_filter.h" +#include "components/services/app_service/public/cpp/permission.h" #include "components/services/app_service/public/mojom/types.mojom.h" #include "third_party/abseil-cpp/absl/types/optional.h" namespace apps { struct IconKey; +struct RunOnOsLogin; // Wraps two apps::mojom::AppPtr's, a prior state and a delta on top of that // state. The state is conceptually the "sum" of all of the previous deltas, @@ -108,6 +111,7 @@ class COMPONENT_EXPORT(APP_UPDATE) AppUpdate { bool VersionChanged() const; std::vector<std::string> AdditionalSearchTerms() const; + std::vector<std::string> GetAdditionalSearchTerms() const; bool AdditionalSearchTermsChanged() const; apps::mojom::IconKeyPtr IconKey() const; @@ -115,69 +119,93 @@ class COMPONENT_EXPORT(APP_UPDATE) AppUpdate { bool IconKeyChanged() const; base::Time LastLaunchTime() const; + base::Time GetLastLaunchTime() const; bool LastLaunchTimeChanged() const; base::Time InstallTime() const; + base::Time GetInstallTime() const; bool InstallTimeChanged() const; std::vector<apps::mojom::PermissionPtr> Permissions() const; + apps::Permissions GetPermissions() const; bool PermissionsChanged() const; apps::mojom::InstallReason InstallReason() const; + apps::InstallReason GetInstallReason() const; bool InstallReasonChanged() const; apps::mojom::InstallSource InstallSource() const; + apps::InstallSource GetInstallSource() const; bool InstallSourceChanged() const; // An optional ID used for policy to identify the app. // For web apps, it contains the install URL. const std::string& PolicyId() const; + const std::string& GetPolicyId() const; bool PolicyIdChanged() const; apps::mojom::OptionalBool InstalledInternally() const; apps::mojom::OptionalBool IsPlatformApp() const; + absl::optional<bool> GetIsPlatformApp() const; bool IsPlatformAppChanged() const; apps::mojom::OptionalBool Recommendable() const; + absl::optional<bool> GetRecommendable() const; bool RecommendableChanged() const; apps::mojom::OptionalBool Searchable() const; + absl::optional<bool> GetSearchable() const; bool SearchableChanged() const; apps::mojom::OptionalBool ShowInLauncher() const; + absl::optional<bool> GetShowInLauncher() const; bool ShowInLauncherChanged() const; apps::mojom::OptionalBool ShowInShelf() const; + absl::optional<bool> GetShowInShelf() const; bool ShowInShelfChanged() const; apps::mojom::OptionalBool ShowInSearch() const; + absl::optional<bool> GetShowInSearch() const; bool ShowInSearchChanged() const; apps::mojom::OptionalBool ShowInManagement() const; + absl::optional<bool> GetShowInManagement() const; bool ShowInManagementChanged() const; apps::mojom::OptionalBool HandlesIntents() const; + absl::optional<bool> GetHandlesIntents() const; bool HandlesIntentsChanged() const; apps::mojom::OptionalBool AllowUninstall() const; + absl::optional<bool> GetAllowUninstall() const; bool AllowUninstallChanged() const; apps::mojom::OptionalBool HasBadge() const; + absl::optional<bool> GetHasBadge() const; bool HasBadgeChanged() const; apps::mojom::OptionalBool Paused() const; + absl::optional<bool> GetPaused() const; bool PausedChanged() const; std::vector<apps::mojom::IntentFilterPtr> IntentFilters() const; + apps::IntentFilters GetIntentFilters() const; bool IntentFiltersChanged() const; apps::mojom::OptionalBool ResizeLocked() const; + absl::optional<bool> GetResizeLocked() const; bool ResizeLockedChanged() const; apps::mojom::WindowMode WindowMode() const; + apps::WindowMode GetWindowMode() const; bool WindowModeChanged() const; + apps::mojom::RunOnOsLoginPtr RunOnOsLogin() const; + absl::optional<apps::RunOnOsLogin> GetRunOnOsLogin() const; + bool RunOnOsLoginChanged() const; + const ::AccountId& AccountId() const; private: diff --git a/chromium/components/services/app_service/public/cpp/app_update_mojom_unittest.cc b/chromium/components/services/app_service/public/cpp/app_update_mojom_unittest.cc index 87c933f790a..3e2bf019056 100644 --- a/chromium/components/services/app_service/public/cpp/app_update_mojom_unittest.cc +++ b/chromium/components/services/app_service/public/cpp/app_update_mojom_unittest.cc @@ -2,8 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "components/services/app_service/public/cpp/app_types.h" #include "components/services/app_service/public/cpp/app_update.h" +#include "components/services/app_service/public/cpp/icon_types.h" +#include "components/services/app_service/public/cpp/intent_filter.h" #include "components/services/app_service/public/cpp/intent_filter_util.h" +#include "components/services/app_service/public/cpp/permission.h" #include "testing/gtest/include/gtest/gtest.h" namespace { @@ -100,6 +104,9 @@ class AppUpdateMojomTest : public testing::Test { apps::mojom::WindowMode expect_window_mode_; bool expect_window_mode_changed_; + apps::mojom::RunOnOsLoginPtr expect_run_on_os_login_; + bool expect_run_on_os_login_changed_; + AccountId account_id_ = AccountId::FromUserEmail("test@gmail.com"); apps::mojom::PermissionPtr MakePermission( @@ -141,6 +148,7 @@ class AppUpdateMojomTest : public testing::Test { expect_intent_filters_changed_ = false; expect_resize_locked_changed_ = false; expect_window_mode_changed_ = false; + expect_run_on_os_login_changed_ = false; } void CheckExpects(const apps::AppUpdate& u) { @@ -230,6 +238,9 @@ class AppUpdateMojomTest : public testing::Test { EXPECT_EQ(expect_window_mode_, u.WindowMode()); EXPECT_EQ(expect_window_mode_changed_, u.WindowModeChanged()); + EXPECT_EQ(expect_run_on_os_login_, u.RunOnOsLogin()); + EXPECT_EQ(expect_run_on_os_login_changed_, u.RunOnOsLoginChanged()); + EXPECT_EQ(account_id_, u.AccountId()); } @@ -269,6 +280,7 @@ class AppUpdateMojomTest : public testing::Test { expect_intent_filters_.clear(); expect_resize_locked_ = apps::mojom::OptionalBool::kUnknown; expect_window_mode_ = apps::mojom::WindowMode::kUnknown; + expect_run_on_os_login_ = nullptr; ExpectNoChange(); CheckExpects(u); @@ -939,6 +951,32 @@ class AppUpdateMojomTest : public testing::Test { ExpectNoChange(); CheckExpects(u); } + + // RunOnOsLogin tests. + + if (state) { + auto runOnOsLoginTestPtr = apps::mojom::RunOnOsLogin::New( + apps::mojom::RunOnOsLoginMode::kNotRun, false); + state->run_on_os_login = runOnOsLoginTestPtr.Clone(); + expect_run_on_os_login_ = runOnOsLoginTestPtr.Clone(); + expect_run_on_os_login_changed_ = false; + CheckExpects(u); + } + + if (delta) { + auto runOnOsLoginTestPtr = apps::mojom::RunOnOsLogin::New( + apps::mojom::RunOnOsLoginMode::kWindowed, false); + delta->run_on_os_login = runOnOsLoginTestPtr.Clone(); + expect_run_on_os_login_ = runOnOsLoginTestPtr.Clone(); + expect_run_on_os_login_changed_ = true; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + ExpectNoChange(); + CheckExpects(u); + } } }; @@ -969,3 +1007,168 @@ TEST_F(AppUpdateMojomTest, BothAreNonNull) { TestAppUpdate(state.get(), delta.get()); } + +TEST_F(AppUpdateMojomTest, AppConvert) { + apps::mojom::AppPtr input = apps::mojom::App::New(); + input->app_type = apps::mojom::AppType::kWeb; + input->app_id = "abcdefg"; + input->readiness = apps::mojom::Readiness::kReady; + input->name = "lacros test name"; + input->short_name = "lacros test name"; + input->publisher_id = "publisher_id"; + input->description = "description"; + input->version = "version"; + input->additional_search_terms = {"1", "2"}; + + auto icon_key = apps::mojom::IconKey::New(); + icon_key->timeline = 1; + icon_key->icon_effects = 2; + input->icon_key = std::move(icon_key); + + input->last_launch_time = base::Time() + base::Days(1); + input->install_time = base::Time() + base::Days(2); + + input->install_reason = apps::mojom::InstallReason::kUser; + input->policy_id = "https://app.site/alpha"; + input->is_platform_app = apps::mojom::OptionalBool::kFalse; + input->recommendable = apps::mojom::OptionalBool::kTrue; + input->searchable = apps::mojom::OptionalBool::kTrue; + input->paused = apps::mojom::OptionalBool::kFalse; + input->show_in_launcher = apps::mojom::OptionalBool::kTrue; + input->show_in_shelf = apps::mojom::OptionalBool::kTrue; + input->show_in_search = apps::mojom::OptionalBool::kTrue; + input->show_in_management = apps::mojom::OptionalBool::kTrue; + input->has_badge = apps::mojom::OptionalBool::kUnknown; + input->paused = apps::mojom::OptionalBool::kFalse; + + auto intent_filter = apps::mojom::IntentFilter::New(); + apps_util::AddSingleValueCondition( + apps::mojom::ConditionType::kScheme, "https", + apps::mojom::PatternMatchType::kNone, intent_filter); + intent_filter->activity_name = "activity_name"; + intent_filter->activity_label = "activity_label"; + input->intent_filters.push_back(std::move(intent_filter)); + + input->window_mode = apps::mojom::WindowMode::kWindow; + + auto permission = apps::mojom::Permission::New(); + permission->permission_type = apps::mojom::PermissionType::kCamera; + permission->value = apps::mojom::PermissionValue::New(); + permission->value->set_bool_value(true); + permission->is_managed = true; + input->permissions.push_back(std::move(permission)); + + input->allow_uninstall = apps::mojom::OptionalBool::kTrue; + input->handles_intents = apps::mojom::OptionalBool::kTrue; + + auto output = apps::ConvertMojomAppToApp(input); + + EXPECT_EQ(output->app_type, apps::AppType::kWeb); + EXPECT_EQ(output->app_id, "abcdefg"); + EXPECT_EQ(output->readiness, apps::Readiness::kReady); + EXPECT_EQ(output->name, "lacros test name"); + EXPECT_EQ(output->short_name, "lacros test name"); + EXPECT_EQ(output->publisher_id, "publisher_id"); + EXPECT_EQ(output->description, "description"); + EXPECT_EQ(output->version, "version"); + EXPECT_EQ(output->additional_search_terms, input->additional_search_terms); + + EXPECT_EQ(output->icon_key->timeline, 1U); + EXPECT_EQ(output->icon_key->icon_effects, 2U); + + EXPECT_EQ(output->last_launch_time, base::Time() + base::Days(1)); + EXPECT_EQ(output->install_time, base::Time() + base::Days(2)); + + EXPECT_EQ(output->install_reason, apps::InstallReason::kUser); + EXPECT_EQ(output->policy_id, "https://app.site/alpha"); + EXPECT_FALSE(output->is_platform_app.value()); + EXPECT_TRUE(output->recommendable.value()); + EXPECT_TRUE(output->searchable.value()); + EXPECT_FALSE(output->paused.value()); + EXPECT_TRUE(output->show_in_launcher.value()); + EXPECT_TRUE(output->show_in_shelf.value()); + EXPECT_TRUE(output->show_in_search.value()); + EXPECT_TRUE(output->show_in_management.value()); + EXPECT_FALSE(output->has_badge.has_value()); + EXPECT_FALSE(output->paused.value()); + + ASSERT_EQ(output->intent_filters.size(), 1U); + auto& filter = output->intent_filters[0]; + ASSERT_EQ(filter->conditions.size(), 1U); + auto& condition = filter->conditions[0]; + EXPECT_EQ(condition->condition_type, apps::ConditionType::kScheme); + ASSERT_EQ(condition->condition_values.size(), 1U); + EXPECT_EQ(condition->condition_values[0]->value, "https"); + EXPECT_EQ(condition->condition_values[0]->match_type, + apps::PatternMatchType::kNone); + EXPECT_EQ(filter->activity_name, "activity_name"); + EXPECT_EQ(filter->activity_label, "activity_label"); + + EXPECT_EQ(output->window_mode, apps::WindowMode::kWindow); + + ASSERT_EQ(output->permissions.size(), 1U); + auto& out_permission = output->permissions[0]; + EXPECT_EQ(out_permission->permission_type, apps::PermissionType::kCamera); + ASSERT_TRUE(out_permission->value->bool_value.has_value()); + EXPECT_TRUE(out_permission->value->bool_value.value()); + EXPECT_TRUE(out_permission->is_managed); + + EXPECT_TRUE(output->allow_uninstall.value()); + EXPECT_TRUE(output->handles_intents.value()); + + auto mojom_app = apps::ConvertAppToMojomApp(output); + + EXPECT_EQ(mojom_app->app_type, apps::mojom::AppType::kWeb); + EXPECT_EQ(mojom_app->app_id, "abcdefg"); + EXPECT_EQ(mojom_app->readiness, apps::mojom::Readiness::kReady); + EXPECT_EQ(mojom_app->name, "lacros test name"); + EXPECT_EQ(mojom_app->short_name, "lacros test name"); + EXPECT_EQ(mojom_app->publisher_id, "publisher_id"); + EXPECT_EQ(mojom_app->description, "description"); + EXPECT_EQ(mojom_app->version, "version"); + EXPECT_EQ(mojom_app->additional_search_terms, input->additional_search_terms); + + EXPECT_EQ(mojom_app->icon_key->timeline, 1U); + EXPECT_EQ(mojom_app->icon_key->icon_effects, 2U); + + EXPECT_EQ(mojom_app->last_launch_time, base::Time() + base::Days(1)); + EXPECT_EQ(mojom_app->install_time, base::Time() + base::Days(2)); + + EXPECT_EQ(mojom_app->install_reason, apps::mojom::InstallReason::kUser); + EXPECT_EQ(mojom_app->policy_id, "https://app.site/alpha"); + EXPECT_EQ(mojom_app->recommendable, apps::mojom::OptionalBool::kTrue); + EXPECT_EQ(mojom_app->searchable, apps::mojom::OptionalBool::kTrue); + EXPECT_EQ(mojom_app->paused, apps::mojom::OptionalBool::kFalse); + EXPECT_EQ(mojom_app->show_in_launcher, apps::mojom::OptionalBool::kTrue); + EXPECT_EQ(mojom_app->show_in_shelf, apps::mojom::OptionalBool::kTrue); + EXPECT_EQ(mojom_app->show_in_search, apps::mojom::OptionalBool::kTrue); + EXPECT_EQ(mojom_app->show_in_management, apps::mojom::OptionalBool::kTrue); + EXPECT_EQ(mojom_app->has_badge, apps::mojom::OptionalBool::kUnknown); + EXPECT_EQ(mojom_app->paused, apps::mojom::OptionalBool::kFalse); + + ASSERT_EQ(mojom_app->intent_filters.size(), 1U); + auto& mojom_filter = mojom_app->intent_filters[0]; + ASSERT_EQ(mojom_filter->conditions.size(), 1U); + auto& mojom_condition = mojom_filter->conditions[0]; + EXPECT_EQ(mojom_condition->condition_type, + apps::mojom::ConditionType::kScheme); + ASSERT_EQ(mojom_condition->condition_values.size(), 1U); + EXPECT_EQ(mojom_condition->condition_values[0]->value, "https"); + EXPECT_EQ(mojom_condition->condition_values[0]->match_type, + apps::mojom::PatternMatchType::kNone); + EXPECT_EQ(mojom_filter->activity_name, "activity_name"); + EXPECT_EQ(mojom_filter->activity_label, "activity_label"); + + EXPECT_EQ(mojom_app->window_mode, apps::mojom::WindowMode::kWindow); + + ASSERT_EQ(mojom_app->permissions.size(), 1U); + auto& mojom_permission = mojom_app->permissions[0]; + EXPECT_EQ(mojom_permission->permission_type, + apps::mojom::PermissionType::kCamera); + ASSERT_TRUE(mojom_permission->value->is_bool_value()); + EXPECT_TRUE(mojom_permission->value->get_bool_value()); + EXPECT_TRUE(mojom_permission->is_managed); + + EXPECT_EQ(mojom_app->allow_uninstall, apps::mojom::OptionalBool::kTrue); + EXPECT_EQ(mojom_app->handles_intents, apps::mojom::OptionalBool::kTrue); +} diff --git a/chromium/components/services/app_service/public/cpp/app_update_unittest.cc b/chromium/components/services/app_service/public/cpp/app_update_unittest.cc index 00f4613d62f..a969a8d995d 100644 --- a/chromium/components/services/app_service/public/cpp/app_update_unittest.cc +++ b/chromium/components/services/app_service/public/cpp/app_update_unittest.cc @@ -3,20 +3,69 @@ // found in the LICENSE file. #include "components/services/app_service/public/cpp/app_update.h" -#include "components/services/app_service/public/cpp/intent_filter_util.h" + +#include "components/services/app_service/public/cpp/app_update.h" +#include "components/services/app_service/public/cpp/intent_filter.h" +#include "components/services/app_service/public/cpp/permission.h" +#include "components/services/app_service/public/cpp/run_on_os_login_types.h" #include "testing/gtest/include/gtest/gtest.h" +namespace apps { + namespace { -const apps::AppType app_type = apps::AppType::kArc; +const AppType app_type = AppType::kArc; const char app_id[] = "abcdefgh"; const char test_name_0[] = "Inigo Montoya"; const char test_name_1[] = "Dread Pirate Roberts"; + +PermissionPtr MakePermission(PermissionType permission_type, + TriState tri_state, + bool is_managed) { + return std::make_unique<Permission>( + permission_type, std::make_unique<PermissionValue>(tri_state), + is_managed); +} + +PermissionPtr MakePermission(PermissionType permission_type, + bool bool_value, + bool is_managed) { + return std::make_unique<Permission>( + permission_type, std::make_unique<PermissionValue>(bool_value), + is_managed); +} + +bool IsEqual(const Permissions& source, const Permissions& target) { + if (source.size() != target.size()) { + return false; + } + + for (int i = 0; i < static_cast<int>(source.size()); i++) { + if (*source[i] != *target[i]) { + return false; + } + } + return true; +} + +bool IsEqual(const IntentFilters& source, const IntentFilters& target) { + if (source.size() != target.size()) { + return false; + } + + for (int i = 0; i < static_cast<int>(source.size()); i++) { + if (*source[i] != *target[i]) { + return false; + } + } + return true; +} + } // namespace class AppUpdateTest : public testing::Test { protected: - apps::Readiness expect_readiness_; - apps::Readiness expect_prior_readiness_; + Readiness expect_readiness_; + Readiness expect_prior_readiness_; std::string expect_name_; bool expect_name_changed_; @@ -29,11 +78,55 @@ class AppUpdateTest : public testing::Test { std::string expect_version_; - absl::optional<apps::IconKey> expect_icon_key_; + std::vector<std::string> expect_additional_search_terms_; + + absl::optional<IconKey> expect_icon_key_; + + base::Time expect_last_launch_time_; + + base::Time expect_install_time_; + + Permissions expect_permissions_; + + InstallReason expect_install_reason_; + + InstallSource expect_install_source_; + + std::string expect_policy_id_; + + absl::optional<bool> expect_is_platform_app_; + + absl::optional<bool> expect_recommendable_; + + absl::optional<bool> expect_searchable_; + + absl::optional<bool> expect_show_in_launcher_; + + absl::optional<bool> expect_show_in_shelf_; + + absl::optional<bool> expect_show_in_search_; + + absl::optional<bool> expect_show_in_management_; + + absl::optional<bool> expect_handles_intents_; + + absl::optional<bool> expect_allow_uninstall_; + + absl::optional<bool> expect_has_badge_; + + absl::optional<bool> expect_paused_; + + IntentFilters expect_intent_filters_; + + absl::optional<bool> expect_resize_locked_; + + WindowMode expect_window_mode_; + + absl::optional<RunOnOsLogin> expect_run_on_os_login_; AccountId account_id_ = AccountId::FromUserEmail("test@gmail.com"); - void CheckExpects(const apps::AppUpdate& u) { + void CheckExpects(const AppUpdate& u) { EXPECT_EQ(expect_readiness_, u.GetReadiness()); EXPECT_EQ(expect_prior_readiness_, u.GetPriorReadiness()); @@ -47,6 +140,8 @@ class AppUpdateTest : public testing::Test { EXPECT_EQ(expect_version_, u.GetVersion()); + EXPECT_EQ(expect_additional_search_terms_, u.GetAdditionalSearchTerms()); + if (expect_icon_key_.has_value()) { ASSERT_TRUE(u.GetIconKey().has_value()); EXPECT_EQ(expect_icon_key_.value(), u.GetIconKey().value()); @@ -54,23 +149,88 @@ class AppUpdateTest : public testing::Test { ASSERT_FALSE(u.GetIconKey().has_value()); } + EXPECT_EQ(expect_last_launch_time_, u.GetLastLaunchTime()); + + EXPECT_EQ(expect_install_time_, u.GetInstallTime()); + + EXPECT_TRUE(IsEqual(expect_permissions_, u.GetPermissions())); + + EXPECT_EQ(expect_install_reason_, u.GetInstallReason()); + + EXPECT_EQ(expect_install_source_, u.GetInstallSource()); + + EXPECT_EQ(expect_policy_id_, u.GetPolicyId()); + + EXPECT_EQ(expect_is_platform_app_, u.GetIsPlatformApp()); + + EXPECT_EQ(expect_recommendable_, u.GetRecommendable()); + + EXPECT_EQ(expect_searchable_, u.GetSearchable()); + + EXPECT_EQ(expect_show_in_launcher_, u.GetShowInLauncher()); + + EXPECT_EQ(expect_show_in_shelf_, u.GetShowInShelf()); + + EXPECT_EQ(expect_show_in_search_, u.GetShowInSearch()); + + EXPECT_EQ(expect_show_in_management_, u.GetShowInManagement()); + + EXPECT_EQ(expect_handles_intents_, u.GetHandlesIntents()); + + EXPECT_EQ(expect_has_badge_, u.GetHasBadge()); + + EXPECT_EQ(expect_paused_, u.GetPaused()); + + EXPECT_TRUE(IsEqual(expect_intent_filters_, u.GetIntentFilters())); + + EXPECT_EQ(expect_resize_locked_, u.GetResizeLocked()); + + EXPECT_EQ(expect_window_mode_, u.GetWindowMode()); + if (expect_run_on_os_login_.has_value()) { + ASSERT_TRUE(u.GetRunOnOsLogin().has_value()); + EXPECT_EQ(expect_run_on_os_login_.value(), u.GetRunOnOsLogin().value()); + } else { + ASSERT_FALSE(u.GetRunOnOsLogin().has_value()); + } + EXPECT_EQ(account_id_, u.AccountId()); } - void TestAppUpdate(apps::App* state, apps::App* delta) { - apps::AppUpdate u(state, delta, account_id_); + void TestAppUpdate(App* state, App* delta) { + AppUpdate u(state, delta, account_id_); EXPECT_EQ(app_type, u.GetAppType()); EXPECT_EQ(app_id, u.GetAppId()); - expect_readiness_ = apps::Readiness::kUnknown; - expect_prior_readiness_ = apps::Readiness::kUnknown; + expect_readiness_ = Readiness::kUnknown; + expect_prior_readiness_ = Readiness::kUnknown; expect_name_ = ""; expect_short_name_ = ""; expect_publisher_id_ = ""; expect_description_ = ""; expect_version_ = ""; + expect_additional_search_terms_.clear(); expect_icon_key_ = absl::nullopt; + expect_last_launch_time_ = base::Time(); + expect_install_time_ = base::Time(); + expect_permissions_.clear(); + expect_install_reason_ = InstallReason::kUnknown; + expect_install_source_ = InstallSource::kUnknown; + expect_policy_id_ = ""; + expect_is_platform_app_ = absl::nullopt; + expect_recommendable_ = absl::nullopt; + expect_searchable_ = absl::nullopt; + expect_show_in_launcher_ = absl::nullopt; + expect_show_in_shelf_ = absl::nullopt; + expect_show_in_search_ = absl::nullopt; + expect_show_in_management_ = absl::nullopt; + expect_handles_intents_ = absl::nullopt; + expect_has_badge_ = absl::nullopt; + expect_paused_ = absl::nullopt; + expect_intent_filters_.clear(); + expect_resize_locked_ = absl::nullopt; + expect_window_mode_ = WindowMode::kUnknown; + expect_run_on_os_login_ = absl::nullopt; CheckExpects(u); if (delta) { @@ -88,8 +248,8 @@ class AppUpdateTest : public testing::Test { } if (delta) { - delta->readiness = apps::Readiness::kReady; - expect_readiness_ = apps::Readiness::kReady; + delta->readiness = Readiness::kReady; + expect_readiness_ = Readiness::kReady; CheckExpects(u); delta->name = absl::nullopt; @@ -99,14 +259,16 @@ class AppUpdateTest : public testing::Test { } if (state) { - apps::AppUpdate::Merge(state, delta); + AppUpdate::Merge(state, delta); expect_prior_readiness_ = state->readiness; + EXPECT_EQ(expect_name_, state->name); + EXPECT_EQ(expect_readiness_, state->readiness); CheckExpects(u); } if (delta) { - delta->readiness = apps::Readiness::kDisabledByPolicy; - expect_readiness_ = apps::Readiness::kDisabledByPolicy; + delta->readiness = Readiness::kDisabledByPolicy; + expect_readiness_ = Readiness::kDisabledByPolicy; delta->name = test_name_1; expect_name_ = test_name_1; expect_name_changed_ = true; @@ -128,8 +290,9 @@ class AppUpdateTest : public testing::Test { } if (state) { - apps::AppUpdate::Merge(state, delta); + AppUpdate::Merge(state, delta); expect_prior_readiness_ = state->readiness; + EXPECT_EQ(expect_short_name_, state->short_name); CheckExpects(u); } @@ -148,7 +311,8 @@ class AppUpdateTest : public testing::Test { } if (state) { - apps::AppUpdate::Merge(state, delta); + AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_publisher_id_, state->publisher_id); CheckExpects(u); } @@ -167,7 +331,8 @@ class AppUpdateTest : public testing::Test { } if (state) { - apps::AppUpdate::Merge(state, delta); + AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_description_, state->description); CheckExpects(u); } @@ -186,43 +351,548 @@ class AppUpdateTest : public testing::Test { } if (state) { + AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_version_, state->version); + CheckExpects(u); + } + + // AdditionalSearchTerms tests. + + if (state) { + state->additional_search_terms.push_back("cat"); + state->additional_search_terms.push_back("dog"); + expect_additional_search_terms_.push_back("cat"); + expect_additional_search_terms_.push_back("dog"); + CheckExpects(u); + } + + if (delta) { + expect_additional_search_terms_.clear(); + delta->additional_search_terms.push_back("horse"); + delta->additional_search_terms.push_back("mouse"); + expect_additional_search_terms_.push_back("horse"); + expect_additional_search_terms_.push_back("mouse"); + CheckExpects(u); + } + + if (state) { apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_additional_search_terms_, + state->additional_search_terms); CheckExpects(u); } // IconKey tests. if (state) { - state->icon_key = apps::IconKey(100, 0, 0); - expect_icon_key_ = apps::IconKey(100, 0, 0); + state->icon_key = IconKey(100, 0, 0); + expect_icon_key_ = IconKey(100, 0, 0); + CheckExpects(u); + } + + if (delta) { + delta->icon_key = IconKey(200, 0, 0); + expect_icon_key_ = IconKey(200, 0, 0); + CheckExpects(u); + } + + if (state) { + AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_icon_key_.value(), state->icon_key.value()); + CheckExpects(u); + } + + // LastLaunchTime tests. + + if (state) { + state->last_launch_time = base::Time::FromDoubleT(1000.0); + expect_last_launch_time_ = base::Time::FromDoubleT(1000.0); CheckExpects(u); } if (delta) { - delta->icon_key = apps::IconKey(200, 0, 0); - expect_icon_key_ = apps::IconKey(200, 0, 0); + delta->last_launch_time = base::Time::FromDoubleT(1001.0); + expect_last_launch_time_ = base::Time::FromDoubleT(1001.0); CheckExpects(u); } if (state) { apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_last_launch_time_, state->last_launch_time); + CheckExpects(u); + } + + // InstallTime tests. + + if (state) { + state->install_time = base::Time::FromDoubleT(2000.0); + expect_install_time_ = base::Time::FromDoubleT(2000.0); + CheckExpects(u); + } + + if (delta) { + delta->install_time = base::Time::FromDoubleT(2001.0); + expect_install_time_ = base::Time::FromDoubleT(2001.0); + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_install_time_, state->install_time); + CheckExpects(u); + } + + // Permission tests. + + if (state) { + auto p0 = MakePermission(PermissionType::kLocation, TriState::kAllow, + /*is_managed=*/true); + auto p1 = MakePermission(PermissionType::kNotifications, TriState::kBlock, + /*is_managed=*/false); + state->permissions.push_back(p0->Clone()); + state->permissions.push_back(p1->Clone()); + expect_permissions_.push_back(p0->Clone()); + expect_permissions_.push_back(p1->Clone()); + CheckExpects(u); + } + + if (delta) { + expect_permissions_.clear(); + auto p0 = MakePermission(PermissionType::kNotifications, + /*bool_value=*/true, + /*is_managed=*/false); + auto p1 = MakePermission(PermissionType::kLocation, + /*bool_value=*/false, + /*is_managed=*/true); + delta->permissions.push_back(p0->Clone()); + delta->permissions.push_back(p1->Clone()); + expect_permissions_.push_back(p0->Clone()); + expect_permissions_.push_back(p1->Clone()); + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_TRUE(IsEqual(expect_permissions_, state->permissions)); + CheckExpects(u); + } + + // InstallReason tests. + + if (state) { + state->install_reason = InstallReason::kUser; + expect_install_reason_ = InstallReason::kUser; + CheckExpects(u); + } + + if (delta) { + delta->install_reason = InstallReason::kPolicy; + expect_install_reason_ = InstallReason::kPolicy; + CheckExpects(u); + } + + if (state) { + AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_install_reason_, state->install_reason); + CheckExpects(u); + } + + // InstallSource tests. + + if (state) { + state->install_source = InstallSource::kPlayStore; + expect_install_source_ = InstallSource::kPlayStore; + CheckExpects(u); + } + + if (delta) { + delta->install_source = InstallSource::kSync; + expect_install_source_ = InstallSource::kSync; + CheckExpects(u); + } + + if (state) { + AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_install_source_, state->install_source); + CheckExpects(u); + } + + // PolicyId tests. + + if (state) { + state->policy_id = "https://app.site/alpha"; + expect_policy_id_ = "https://app.site/alpha"; + CheckExpects(u); + } + + if (delta) { + delta->policy_id = "https://app.site/delta"; + expect_policy_id_ = "https://app.site/delta"; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_policy_id_, state->policy_id); + CheckExpects(u); + } + + // IsPlatformApp tests. + + if (state) { + state->is_platform_app = false; + expect_is_platform_app_ = false; + CheckExpects(u); + } + + if (delta) { + delta->is_platform_app = true; + expect_is_platform_app_ = true; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_is_platform_app_, state->is_platform_app); + CheckExpects(u); + } + + // Recommendable tests. + + if (state) { + state->recommendable = false; + expect_recommendable_ = false; + CheckExpects(u); + } + + if (delta) { + delta->recommendable = true; + expect_recommendable_ = true; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_recommendable_, state->recommendable); + CheckExpects(u); + } + + // Searchable tests. + + if (state) { + state->searchable = false; + expect_searchable_ = false; + CheckExpects(u); + } + + if (delta) { + delta->searchable = true; + expect_searchable_ = true; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_searchable_, state->searchable); + CheckExpects(u); + } + + // ShowInLauncher tests. + + if (state) { + state->show_in_launcher = false; + expect_show_in_launcher_ = false; + CheckExpects(u); + } + + if (delta) { + delta->show_in_launcher = true; + expect_show_in_launcher_ = true; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_show_in_launcher_, state->show_in_launcher); + CheckExpects(u); + } + + // ShowInShelf tests. + + if (state) { + state->show_in_shelf = false; + expect_show_in_shelf_ = false; + CheckExpects(u); + } + + if (delta) { + delta->show_in_shelf = true; + expect_show_in_shelf_ = true; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_show_in_shelf_, state->show_in_shelf); + CheckExpects(u); + } + + // ShowInSearch tests. + + if (state) { + state->show_in_search = false; + expect_show_in_search_ = false; + CheckExpects(u); + } + + if (delta) { + delta->show_in_search = true; + expect_show_in_search_ = true; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_show_in_search_, state->show_in_search); + CheckExpects(u); + } + + // ShowInManagement tests. + + if (state) { + state->show_in_management = false; + expect_show_in_management_ = false; + CheckExpects(u); + } + + if (delta) { + delta->show_in_management = true; + expect_show_in_management_ = true; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_show_in_management_, state->show_in_management); + CheckExpects(u); + } + + // HandlesIntents tests. + + if (state) { + state->handles_intents = false; + expect_handles_intents_ = false; + CheckExpects(u); + } + + if (delta) { + delta->handles_intents = true; + expect_handles_intents_ = true; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_handles_intents_, state->handles_intents); + CheckExpects(u); + } + + // AllowUninstall tests + + if (state) { + state->allow_uninstall = false; + expect_allow_uninstall_ = false; + CheckExpects(u); + } + + if (delta) { + delta->allow_uninstall = true; + expect_allow_uninstall_ = true; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_allow_uninstall_, state->allow_uninstall); + CheckExpects(u); + } + + // HasBadge tests. + + if (state) { + state->has_badge = false; + expect_has_badge_ = false; + CheckExpects(u); + } + + if (delta) { + delta->has_badge = true; + expect_has_badge_ = true; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_has_badge_, state->has_badge); + CheckExpects(u); + } + + // Pause tests. + + if (state) { + state->paused = false; + expect_paused_ = false; + CheckExpects(u); + } + + if (delta) { + delta->paused = true; + expect_paused_ = true; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_paused_, state->paused); + CheckExpects(u); + } + + // Intent Filter tests. + + if (state) { + IntentFilterPtr intent_filter = std::make_unique<IntentFilter>(); + + ConditionValues scheme_condition_values; + scheme_condition_values.push_back( + std::make_unique<ConditionValue>("https", PatternMatchType::kNone)); + ConditionPtr scheme_condition = std::make_unique<Condition>( + ConditionType::kScheme, std::move(scheme_condition_values)); + + ConditionValues host_condition_values; + host_condition_values.push_back(std::make_unique<ConditionValue>( + "www.google.com", PatternMatchType::kNone)); + auto host_condition = std::make_unique<Condition>( + ConditionType::kHost, std::move(host_condition_values)); + + intent_filter->conditions.push_back(std::move(scheme_condition)); + intent_filter->conditions.push_back(std::move(host_condition)); + + state->intent_filters.push_back(intent_filter->Clone()); + expect_intent_filters_.push_back(intent_filter->Clone()); + CheckExpects(u); + } + + if (delta) { + expect_intent_filters_.clear(); + + IntentFilterPtr intent_filter = std::make_unique<IntentFilter>(); + + ConditionValues scheme_condition_values; + scheme_condition_values.push_back( + std::make_unique<ConditionValue>("https", PatternMatchType::kNone)); + ConditionPtr scheme_condition = std::make_unique<Condition>( + ConditionType::kScheme, std::move(scheme_condition_values)); + intent_filter->conditions.push_back(scheme_condition->Clone()); + + ConditionValues host_condition_values; + host_condition_values.push_back(std::make_unique<ConditionValue>( + "www.abc.com", PatternMatchType::kNone)); + auto host_condition = std::make_unique<Condition>( + ConditionType::kHost, std::move(host_condition_values)); + intent_filter->conditions.push_back(host_condition->Clone()); + + intent_filter->conditions.push_back(std::move(scheme_condition)); + intent_filter->conditions.push_back(std::move(host_condition)); + + delta->intent_filters.push_back(intent_filter->Clone()); + expect_intent_filters_.push_back(intent_filter->Clone()); + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_TRUE(IsEqual(expect_intent_filters_, state->intent_filters)); + CheckExpects(u); + } + + // ResizeLocked tests. + + if (state) { + state->resize_locked = false; + expect_resize_locked_ = false; + CheckExpects(u); + } + + if (delta) { + delta->resize_locked = true; + expect_resize_locked_ = true; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_resize_locked_, state->resize_locked); + CheckExpects(u); + } + + // WindowMode tests. + + if (state) { + state->window_mode = WindowMode::kBrowser; + expect_window_mode_ = WindowMode::kBrowser; + CheckExpects(u); + } + + if (delta) { + delta->window_mode = WindowMode::kWindow; + expect_window_mode_ = WindowMode::kWindow; + CheckExpects(u); + } + + if (state) { + apps::AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_window_mode_, state->window_mode); + CheckExpects(u); + } + + // RunOnOsLogin tests. + + if (state) { + state->run_on_os_login = RunOnOsLogin(RunOnOsLoginMode::kNotRun, false); + expect_run_on_os_login_ = RunOnOsLogin(RunOnOsLoginMode::kNotRun, false); + CheckExpects(u); + } + + if (delta) { + delta->run_on_os_login = RunOnOsLogin(RunOnOsLoginMode::kWindowed, false); + expect_run_on_os_login_ = + RunOnOsLogin(RunOnOsLoginMode::kWindowed, false); + CheckExpects(u); + } + + if (state) { + AppUpdate::Merge(state, delta); + EXPECT_EQ(expect_run_on_os_login_.value(), + state->run_on_os_login.value()); CheckExpects(u); } } }; TEST_F(AppUpdateTest, StateIsNonNull) { - apps::App state(app_type, app_id); + App state(app_type, app_id); TestAppUpdate(&state, nullptr); } TEST_F(AppUpdateTest, DeltaIsNonNull) { - apps::App delta(app_type, app_id); + App delta(app_type, app_id); TestAppUpdate(nullptr, &delta); } TEST_F(AppUpdateTest, BothAreNonNull) { - apps::App state(app_type, app_id); - apps::App delta(app_type, app_id); + App state(app_type, app_id); + App delta(app_type, app_id); TestAppUpdate(&state, &delta); } + +} // namespace apps diff --git a/chromium/components/services/app_service/public/cpp/browser_app_instance_update.h b/chromium/components/services/app_service/public/cpp/browser_app_instance_update.h index 37f7c25c493..dfaa7dd3783 100644 --- a/chromium/components/services/app_service/public/cpp/browser_app_instance_update.h +++ b/chromium/components/services/app_service/public/cpp/browser_app_instance_update.h @@ -27,8 +27,10 @@ struct BrowserAppInstanceUpdate { std::string app_id; std::string window_id; std::string title; - bool is_browser_active; - bool is_web_contents_active; + bool is_browser_active = false; + bool is_web_contents_active = false; + uint32_t browser_session_id = 0; + uint32_t restored_browser_session_id = 0; }; } // namespace apps diff --git a/chromium/components/services/app_service/public/cpp/browser_window_instance_update.h b/chromium/components/services/app_service/public/cpp/browser_window_instance_update.h index 4f0d3c1b504..83402c01707 100644 --- a/chromium/components/services/app_service/public/cpp/browser_window_instance_update.h +++ b/chromium/components/services/app_service/public/cpp/browser_window_instance_update.h @@ -12,7 +12,9 @@ namespace apps { struct BrowserWindowInstanceUpdate { base::UnguessableToken id; std::string window_id; - bool is_active; + bool is_active = false; + uint32_t browser_session_id = 0; + uint32_t restored_browser_session_id = 0; }; } // namespace apps diff --git a/chromium/components/services/app_service/public/cpp/features.cc b/chromium/components/services/app_service/public/cpp/features.cc new file mode 100644 index 00000000000..af71190a101 --- /dev/null +++ b/chromium/components/services/app_service/public/cpp/features.cc @@ -0,0 +1,16 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/services/app_service/public/cpp/features.h" + +namespace apps { + +const base::Feature kAppServiceOnAppTypeInitializedWithoutMojom{ + "AppServiceOnAppTypeInitializedWithoutMojom", + base::FEATURE_DISABLED_BY_DEFAULT}; + +const base::Feature kAppServiceOnAppUpdateWithoutMojom{ + "AppServiceOnAppUpdateWithoutMojom", base::FEATURE_DISABLED_BY_DEFAULT}; + +} // namespace apps diff --git a/chromium/components/services/app_service/public/cpp/features.h b/chromium/components/services/app_service/public/cpp/features.h new file mode 100644 index 00000000000..dcc3bbc7c41 --- /dev/null +++ b/chromium/components/services/app_service/public/cpp/features.h @@ -0,0 +1,19 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_FEATURES_H_ +#define COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_FEATURES_H_ + +#include "base/component_export.h" +#include "base/feature_list.h" + +namespace apps { + +COMPONENT_EXPORT(APP_UPDATE) +extern const base::Feature kAppServiceOnAppTypeInitializedWithoutMojom; +extern const base::Feature kAppServiceOnAppUpdateWithoutMojom; + +} // namespace apps + +#endif // COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_FEATURES_H_ diff --git a/chromium/components/services/app_service/public/cpp/icon_loader.h b/chromium/components/services/app_service/public/cpp/icon_loader.h index 8da5708cc0e..37f7e89b49d 100644 --- a/chromium/components/services/app_service/public/cpp/icon_loader.h +++ b/chromium/components/services/app_service/public/cpp/icon_loader.h @@ -58,7 +58,8 @@ class IconLoader { virtual absl::optional<IconKey> GetIconKey(const std::string& app_id); // This can return nullptr, meaning that the IconLoader does not track when - // the icon is no longer actively used by the caller. + // the icon is no longer actively used by the caller. `callback` may be + // dispatched synchronously if it's possible to quickly return a result. virtual std::unique_ptr<Releaser> LoadIconFromIconKey( AppType app_type, const std::string& app_id, @@ -69,7 +70,8 @@ class IconLoader { apps::LoadIconCallback callback) = 0; // Convenience method that calls "LoadIconFromIconKey(app_type, app_id, - // GetIconKey(app_id), etc)". + // GetIconKey(app_id), etc)". `callback` may be dispatched synchronously if + // it's possible to quickly return a result. std::unique_ptr<Releaser> LoadIcon(AppType app_type, const std::string& app_id, const IconType& icon_type, diff --git a/chromium/components/services/app_service/public/cpp/icon_types.h b/chromium/components/services/app_service/public/cpp/icon_types.h index 5391b835c4f..7d89bdd2b22 100644 --- a/chromium/components/services/app_service/public/cpp/icon_types.h +++ b/chromium/components/services/app_service/public/cpp/icon_types.h @@ -7,6 +7,7 @@ #include <vector> +#include "base/component_export.h" #include "components/services/app_service/public/mojom/types.mojom.h" #include "ui/gfx/image/image_skia.h" @@ -60,6 +61,8 @@ struct COMPONENT_EXPORT(ICON_TYPES) IconKey { // components/services/app_service/public/cpp/icon_loader.* }; +using IconKeyPtr = std::unique_ptr<IconKey>; + enum class IconType { // Sentinel value used in error cases. kUnknown, diff --git a/chromium/components/services/app_service/public/cpp/instance_update.cc b/chromium/components/services/app_service/public/cpp/instance_update.cc index 028b19cb5e5..1bc5e1b53c2 100644 --- a/chromium/components/services/app_service/public/cpp/instance_update.cc +++ b/chromium/components/services/app_service/public/cpp/instance_update.cc @@ -125,7 +125,8 @@ bool InstanceUpdate::WindowChanged() const { } const std::string& InstanceUpdate::LaunchId() const { - GET_VALUE_WITH_CHECK_AND_DEFAULT_RETURN(LaunchId, empty, base::EmptyString()); + GET_VALUE_WITH_CHECK_AND_DEFAULT_RETURN(LaunchId(), empty, + base::EmptyString()); } bool InstanceUpdate::LaunchIdChanged() const { @@ -133,7 +134,7 @@ bool InstanceUpdate::LaunchIdChanged() const { } InstanceState InstanceUpdate::State() const { - GET_VALUE_WITH_DEFAULT_VALUE(State, InstanceState::kUnknown); + GET_VALUE_WITH_DEFAULT_VALUE(State(), InstanceState::kUnknown); } bool InstanceUpdate::StateChanged() const { @@ -141,7 +142,7 @@ bool InstanceUpdate::StateChanged() const { } base::Time InstanceUpdate::LastUpdatedTime() const { - GET_VALUE_WITH_CHECK_AND_DEFAULT_RETURN(LastUpdatedTime, is_null, + GET_VALUE_WITH_CHECK_AND_DEFAULT_RETURN(LastUpdatedTime(), is_null, base::Time()); } diff --git a/chromium/components/services/app_service/public/cpp/intent_filter.cc b/chromium/components/services/app_service/public/cpp/intent_filter.cc new file mode 100644 index 00000000000..c58fbbac959 --- /dev/null +++ b/chromium/components/services/app_service/public/cpp/intent_filter.cc @@ -0,0 +1,318 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/services/app_service/public/cpp/intent_filter.h" + +namespace apps { + +ConditionValue::ConditionValue(const std::string& value, + PatternMatchType match_type) + : value(value), match_type(match_type) {} + +ConditionValue::~ConditionValue() = default; + +bool ConditionValue::operator==(const ConditionValue& other) const { + return value == other.value && match_type == other.match_type; +} + +bool ConditionValue::operator!=(const ConditionValue& other) const { + return !(*this == other); +} + +Condition::Condition(ConditionType condition_type, + ConditionValues condition_values) + : condition_type(condition_type), + condition_values(std::move(condition_values)) {} + +Condition::~Condition() = default; + +bool Condition::operator==(const Condition& other) const { + if (condition_values.size() != other.condition_values.size()) { + return false; + } + + for (int i = 0; i < static_cast<int>(condition_values.size()); i++) { + if (*condition_values[i] != *other.condition_values[i]) { + return false; + } + } + + return condition_type == other.condition_type; +} + +bool Condition::operator!=(const Condition& other) const { + return !(*this == other); +} + +ConditionPtr Condition::Clone() const { + ConditionValues values; + for (const auto& condition_value : condition_values) { + values.push_back(std::make_unique<ConditionValue>( + condition_value->value, condition_value->match_type)); + } + + return std::make_unique<Condition>(condition_type, std::move(values)); +} + +IntentFilter::IntentFilter() = default; +IntentFilter::~IntentFilter() = default; + +bool IntentFilter::operator==(const IntentFilter& other) const { + if (conditions.size() != other.conditions.size()) { + return false; + } + + for (int i = 0; i < static_cast<int>(conditions.size()); i++) { + if (*conditions[i] != *other.conditions[i]) { + return false; + } + } + + return activity_name == other.activity_name && + activity_label == other.activity_label; +} + +bool IntentFilter::operator!=(const IntentFilter& other) const { + return !(*this == other); +} + +IntentFilterPtr IntentFilter::Clone() const { + IntentFilterPtr intent_filter = std::make_unique<IntentFilter>(); + + for (const auto& condition : conditions) { + intent_filter->conditions.push_back(condition->Clone()); + } + + if (activity_name.has_value()) + intent_filter->activity_name = activity_name.value(); + + if (activity_label.has_value()) + intent_filter->activity_label = activity_label.value(); + + return intent_filter; +} + +IntentFilters CloneIntentFilters(const IntentFilters& intent_filters) { + IntentFilters ret; + for (const auto& intent_filter : intent_filters) { + ret.push_back(intent_filter->Clone()); + } + return ret; +} + +ConditionType ConvertMojomConditionTypeToConditionType( + const apps::mojom::ConditionType& mojom_condition_type) { + switch (mojom_condition_type) { + case apps::mojom::ConditionType::kScheme: + return ConditionType::kScheme; + case apps::mojom::ConditionType::kHost: + return ConditionType::kHost; + case apps::mojom::ConditionType::kPattern: + return ConditionType::kPattern; + case apps::mojom::ConditionType::kAction: + return ConditionType::kAction; + case apps::mojom::ConditionType::kMimeType: + return ConditionType::kMimeType; + case apps::mojom::ConditionType::kFile: + return ConditionType::kFile; + } +} + +apps::mojom::ConditionType ConvertConditionTypeToMojomConditionType( + const ConditionType& condition_type) { + switch (condition_type) { + case ConditionType::kScheme: + return apps::mojom::ConditionType::kScheme; + case ConditionType::kHost: + return apps::mojom::ConditionType::kHost; + case ConditionType::kPattern: + return apps::mojom::ConditionType::kPattern; + case ConditionType::kAction: + return apps::mojom::ConditionType::kAction; + case ConditionType::kMimeType: + return apps::mojom::ConditionType::kMimeType; + case ConditionType::kFile: + return apps::mojom::ConditionType::kFile; + } +} + +PatternMatchType ConvertMojomPatternMatchTypeToPatternMatchType( + const apps::mojom::PatternMatchType& mojom_pattern_match_type) { + switch (mojom_pattern_match_type) { + case apps::mojom::PatternMatchType::kNone: + return PatternMatchType::kNone; + case apps::mojom::PatternMatchType::kLiteral: + return PatternMatchType::kLiteral; + case apps::mojom::PatternMatchType::kPrefix: + return PatternMatchType::kPrefix; + case apps::mojom::PatternMatchType::kGlob: + return PatternMatchType::kGlob; + case apps::mojom::PatternMatchType::kMimeType: + return PatternMatchType::kMimeType; + case apps::mojom::PatternMatchType::kFileExtension: + return PatternMatchType::kFileExtension; + case apps::mojom::PatternMatchType::kIsDirectory: + return PatternMatchType::kIsDirectory; + } +} + +apps::mojom::PatternMatchType ConvertPatternMatchTypeToMojomPatternMatchType( + const PatternMatchType& pattern_match_type) { + switch (pattern_match_type) { + case PatternMatchType::kNone: + return apps::mojom::PatternMatchType::kNone; + case PatternMatchType::kLiteral: + return apps::mojom::PatternMatchType::kLiteral; + case PatternMatchType::kPrefix: + return apps::mojom::PatternMatchType::kPrefix; + case PatternMatchType::kGlob: + return apps::mojom::PatternMatchType::kGlob; + case PatternMatchType::kMimeType: + return apps::mojom::PatternMatchType::kMimeType; + case PatternMatchType::kFileExtension: + return apps::mojom::PatternMatchType::kFileExtension; + case PatternMatchType::kIsDirectory: + return apps::mojom::PatternMatchType::kIsDirectory; + } +} + +ConditionValuePtr ConvertMojomConditionValueToConditionValue( + const apps::mojom::ConditionValuePtr& mojom_condition_value) { + if (!mojom_condition_value) { + return nullptr; + } + + ConditionValuePtr condition_value = std::make_unique<ConditionValue>( + mojom_condition_value->value, + ConvertMojomPatternMatchTypeToPatternMatchType( + mojom_condition_value->match_type)); + return condition_value; +} + +apps::mojom::ConditionValuePtr ConvertConditionValueToMojomConditionValue( + const ConditionValuePtr& condition_value) { + auto mojom_condition_value = apps::mojom::ConditionValue::New(); + if (!condition_value) { + return mojom_condition_value; + } + + mojom_condition_value->value = condition_value->value; + mojom_condition_value->match_type = + ConvertPatternMatchTypeToMojomPatternMatchType( + condition_value->match_type); + return mojom_condition_value; +} + +ConditionPtr ConvertMojomConditionToCondition( + const apps::mojom::ConditionPtr& mojom_condition) { + if (!mojom_condition) { + return nullptr; + } + + ConditionValues values; + for (const auto& condition_value : mojom_condition->condition_values) { + values.push_back( + ConvertMojomConditionValueToConditionValue(condition_value)); + } + return std::make_unique<Condition>( + ConvertMojomConditionTypeToConditionType(mojom_condition->condition_type), + std::move(values)); +} + +apps::mojom::ConditionPtr ConvertConditionToMojomCondition( + const ConditionPtr& condition) { + auto mojom_condition = apps::mojom::Condition::New(); + if (!condition) { + return mojom_condition; + } + + mojom_condition->condition_type = + ConvertConditionTypeToMojomConditionType(condition->condition_type); + + for (const auto& condition_value : condition->condition_values) { + if (condition_value) { + mojom_condition->condition_values.push_back( + ConvertConditionValueToMojomConditionValue(condition_value)); + } + } + return mojom_condition; +} + +IntentFilterPtr ConvertMojomIntentFilterToIntentFilter( + const apps::mojom::IntentFilterPtr& mojom_intent_filter) { + if (!mojom_intent_filter) { + return nullptr; + } + + IntentFilterPtr intent_filter = std::make_unique<IntentFilter>(); + for (const auto& condition : mojom_intent_filter->conditions) { + if (condition) { + intent_filter->conditions.push_back( + ConvertMojomConditionToCondition(condition)); + } + } + + if (mojom_intent_filter->activity_name.has_value()) + intent_filter->activity_name = mojom_intent_filter->activity_name.value(); + + if (mojom_intent_filter->activity_label.has_value()) + intent_filter->activity_label = mojom_intent_filter->activity_label.value(); + + return intent_filter; +} + +apps::mojom::IntentFilterPtr ConvertIntentFilterToMojomIntentFilter( + const IntentFilterPtr& intent_filter) { + auto mojom_intent_filter = apps::mojom::IntentFilter::New(); + if (!intent_filter) { + return mojom_intent_filter; + } + + for (const auto& condition : intent_filter->conditions) { + if (condition) { + mojom_intent_filter->conditions.push_back( + ConvertConditionToMojomCondition(condition)); + } + } + + mojom_intent_filter->activity_name = intent_filter->activity_name; + mojom_intent_filter->activity_label = intent_filter->activity_label; + return mojom_intent_filter; +} + +base::flat_map<std::string, std::vector<apps::mojom::IntentFilterPtr>> +ConvertIntentFiltersToMojomIntentFilters( + const base::flat_map<std::string, apps::IntentFilters>& intent_filter) { + base::flat_map<std::string, std::vector<apps::mojom::IntentFilterPtr>> ret; + for (const auto& it : intent_filter) { + std::vector<apps::mojom::IntentFilterPtr> mojom_filters; + for (const auto& filter_it : it.second) { + if (filter_it) { + mojom_filters.push_back( + ConvertIntentFilterToMojomIntentFilter(filter_it)); + } + } + ret[it.first] = std::move(mojom_filters); + } + return ret; +} + +base::flat_map<std::string, apps::IntentFilters> +ConvertMojomIntentFiltersToIntentFilters( + const base::flat_map<std::string, + std::vector<apps::mojom::IntentFilterPtr>>& + mojom_intent_filter) { + base::flat_map<std::string, apps::IntentFilters> ret; + for (const auto& it : mojom_intent_filter) { + apps::IntentFilters filters; + for (const auto& filter_it : it.second) { + if (filter_it) + filters.push_back(ConvertMojomIntentFilterToIntentFilter(filter_it)); + } + ret[it.first] = std::move(filters); + } + return ret; +} + +} // namespace apps diff --git a/chromium/components/services/app_service/public/cpp/intent_filter.h b/chromium/components/services/app_service/public/cpp/intent_filter.h new file mode 100644 index 00000000000..7b1ba9bf476 --- /dev/null +++ b/chromium/components/services/app_service/public/cpp/intent_filter.h @@ -0,0 +1,167 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_INTENT_FILTER_H_ +#define COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_INTENT_FILTER_H_ + +#include <string> +#include <utility> +#include <vector> + +#include "base/component_export.h" +#include "base/containers/flat_map.h" +#include "components/services/app_service/public/mojom/types.mojom.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace apps { + +// The intent filter matching condition types. +enum class ConditionType { + kScheme, // Matches the URL scheme (e.g. https, tel). + kHost, // Matches the URL host (e.g. www.google.com). + kPattern, // Matches the URL pattern (e.g. /abc/*). + kAction, // Matches the action type (e.g. view, send). + kMimeType, // Matches the top-level mime type (e.g. text/plain). + kFile, // Matches against all files. +}; + +// The pattern match type for intent filter pattern condition. +enum class PatternMatchType { + kNone = 0, + kLiteral, + kPrefix, + kGlob, + kMimeType, + kFileExtension, + kIsDirectory, +}; + +// For pattern type of condition, the value match will be based on the pattern +// match type. If the match_type is kNone, then an exact match with the value +// will be required. +struct COMPONENT_EXPORT(APP_TYPES) ConditionValue { + ConditionValue(const std::string& value, PatternMatchType match_type); + ConditionValue(const ConditionValue&) = delete; + ConditionValue& operator=(const ConditionValue&) = delete; + ~ConditionValue(); + + bool operator==(const ConditionValue& other) const; + bool operator!=(const ConditionValue& other) const; + + std::string value; + PatternMatchType match_type; // This will be None for non pattern conditions. +}; + +using ConditionValuePtr = std::unique_ptr<ConditionValue>; +using ConditionValues = std::vector<ConditionValuePtr>; + +// The condition for an intent filter. It matches if the intent contains this +// condition type and the corresponding value matches with any of the +// condition_values. +struct COMPONENT_EXPORT(APP_TYPES) Condition { + Condition(ConditionType condition_type, ConditionValues condition_values); + Condition(const Condition&) = delete; + Condition& operator=(const Condition&) = delete; + ~Condition(); + + bool operator==(const Condition& other) const; + bool operator!=(const Condition& other) const; + + std::unique_ptr<Condition> Clone() const; + + ConditionType condition_type; + ConditionValues condition_values; +}; + +using ConditionPtr = std::unique_ptr<Condition>; +using Conditions = std::vector<ConditionPtr>; + +// An intent filter is defined by an app, and contains a list of conditions that +// an intent needs to match. If all conditions match, then this intent filter +// matches against an intent. +struct COMPONENT_EXPORT(APP_TYPES) IntentFilter { + IntentFilter(); + IntentFilter(const IntentFilter&) = delete; + IntentFilter& operator=(const IntentFilter&) = delete; + ~IntentFilter(); + + bool operator==(const IntentFilter& other) const; + bool operator!=(const IntentFilter& other) const; + + std::unique_ptr<IntentFilter> Clone() const; + + Conditions conditions; + + // Activity which registered this filter. We only fill this field for ARC + // share intent filters and Web App file_handlers. + absl::optional<std::string> activity_name; + + // The label shown to the user for this activity. + absl::optional<std::string> activity_label; +}; + +using IntentFilterPtr = std::unique_ptr<IntentFilter>; +using IntentFilters = std::vector<IntentFilterPtr>; + +// Creates a deep copy of `intent_filters`. +COMPONENT_EXPORT(APP_TYPES) +IntentFilters CloneIntentFilters(const IntentFilters& intent_filters); + +// TODO(crbug.com/1253250): Remove these functions after migrating to non-mojo +// AppService. +COMPONENT_EXPORT(APP_TYPES) +ConditionType ConvertMojomConditionTypeToConditionType( + const apps::mojom::ConditionType& mojom_condition_type); + +COMPONENT_EXPORT(APP_TYPES) +apps::mojom::ConditionType ConvertConditionTypeToMojomConditionType( + const ConditionType& condition_type); + +COMPONENT_EXPORT(APP_TYPES) +PatternMatchType ConvertMojomPatternMatchTypeToPatternMatchType( + const apps::mojom::PatternMatchType& mojom_pattern_match_type); + +COMPONENT_EXPORT(APP_TYPES) +apps::mojom::PatternMatchType ConvertPatternMatchTypeToMojomPatternMatchType( + const PatternMatchType& pattern_match_type); + +COMPONENT_EXPORT(APP_TYPES) +ConditionValuePtr ConvertMojomConditionValueToConditionValue( + const apps::mojom::ConditionValuePtr& mojom_condition_value); + +COMPONENT_EXPORT(APP_TYPES) +apps::mojom::ConditionValuePtr ConvertConditionValueToMojomConditionValue( + const ConditionValuePtr& condition_value); + +COMPONENT_EXPORT(APP_TYPES) +ConditionPtr ConvertMojomConditionToCondition( + const apps::mojom::ConditionPtr& mojom_condition); + +COMPONENT_EXPORT(APP_TYPES) +apps::mojom::ConditionPtr ConvertConditionToMojomCondition( + const ConditionPtr& condition); + +COMPONENT_EXPORT(APP_TYPES) +IntentFilterPtr ConvertMojomIntentFilterToIntentFilter( + const apps::mojom::IntentFilterPtr& mojom_intent_filter); + +COMPONENT_EXPORT(APP_TYPES) +apps::mojom::IntentFilterPtr ConvertIntentFilterToMojomIntentFilter( + const IntentFilterPtr& intent_filter); + +COMPONENT_EXPORT(APP_TYPES) +base::flat_map<std::string, std::vector<apps::mojom::IntentFilterPtr>> +ConvertIntentFiltersToMojomIntentFilters( + const base::flat_map<std::string, apps::IntentFilters>& intent_filter); + +COMPONENT_EXPORT(APP_TYPES) +base::flat_map<std::string, apps::IntentFilters> +ConvertMojomIntentFiltersToIntentFilters( + const base::flat_map<std::string, + std::vector<apps::mojom::IntentFilterPtr>>& + mojom_intent_filter); + +} // namespace apps + +#endif // COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_INTENT_FILTER_H_ diff --git a/chromium/components/services/app_service/public/cpp/intent_filter_util.cc b/chromium/components/services/app_service/public/cpp/intent_filter_util.cc index ca849f67af7..811cb6fb04c 100644 --- a/chromium/components/services/app_service/public/cpp/intent_filter_util.cc +++ b/chromium/components/services/app_service/public/cpp/intent_filter_util.cc @@ -84,6 +84,17 @@ apps::mojom::ConditionPtr MakeCondition( return condition; } +void AddSingleValueCondition(apps::ConditionType condition_type, + const std::string& value, + apps::PatternMatchType pattern_match_type, + apps::IntentFilterPtr& intent_filter) { + apps::ConditionValues condition_values; + condition_values.push_back( + std::make_unique<apps::ConditionValue>(value, pattern_match_type)); + intent_filter->conditions.push_back(std::make_unique<apps::Condition>( + condition_type, std::move(condition_values))); +} + void AddSingleValueCondition(apps::mojom::ConditionType condition_type, const std::string& value, apps::mojom::PatternMatchType pattern_match_type, diff --git a/chromium/components/services/app_service/public/cpp/intent_filter_util.h b/chromium/components/services/app_service/public/cpp/intent_filter_util.h index 8624079f7a6..e4ec59576ad 100644 --- a/chromium/components/services/app_service/public/cpp/intent_filter_util.h +++ b/chromium/components/services/app_service/public/cpp/intent_filter_util.h @@ -9,6 +9,7 @@ #include <string> +#include "components/services/app_service/public/cpp/intent_filter.h" #include "components/services/app_service/public/mojom/types.mojom.h" #include "url/gurl.h" @@ -47,6 +48,12 @@ apps::mojom::ConditionPtr MakeCondition( // Creates condition that only contain one value and add the condition to // the intent filter. +void AddSingleValueCondition(apps::ConditionType condition_type, + const std::string& value, + apps::PatternMatchType pattern_match_type, + apps::IntentFilterPtr& intent_filter); + +// TODO(crbug.com/1253250): Remove after migrating to non-mojo AppService. void AddSingleValueCondition(apps::mojom::ConditionType condition_type, const std::string& value, apps::mojom::PatternMatchType pattern_match_type, diff --git a/chromium/components/services/app_service/public/cpp/intent_filter_util_unittest.cc b/chromium/components/services/app_service/public/cpp/intent_filter_util_unittest.cc index 190ad7d3570..bb6e1140105 100644 --- a/chromium/components/services/app_service/public/cpp/intent_filter_util_unittest.cc +++ b/chromium/components/services/app_service/public/cpp/intent_filter_util_unittest.cc @@ -5,6 +5,7 @@ #include "components/services/app_service/public/cpp/intent_filter_util.h" #include "base/values.h" +#include "components/services/app_service/public/cpp/intent_filter.h" #include "components/services/app_service/public/cpp/intent_test_util.h" #include "components/services/app_service/public/cpp/intent_util.h" #include "testing/gtest/include/gtest/gtest.h" @@ -370,3 +371,108 @@ TEST_F(IntentFilterUtilTest, PatternGlobAndLiteralOverlap) { ASSERT_FALSE(apps_util::FiltersHaveOverlap(literal_pattern_filter2, glob_pattern_filter)); } + +TEST_F(IntentFilterUtilTest, IntentFiltersConvert) { + base::flat_map<std::string, std::vector<apps::IntentFilterPtr>> filters; + + auto intent_filter1 = std::make_unique<apps::IntentFilter>(); + apps_util::AddSingleValueCondition(apps::ConditionType::kScheme, "1", + apps::PatternMatchType::kNone, + intent_filter1); + filters["1"].push_back(std::move(intent_filter1)); + + auto intent_filter2 = std::make_unique<apps::IntentFilter>(); + apps_util::AddSingleValueCondition(apps::ConditionType::kHost, "2", + apps::PatternMatchType::kLiteral, + intent_filter2); + apps_util::AddSingleValueCondition(apps::ConditionType::kPattern, "3", + apps::PatternMatchType::kPrefix, + intent_filter2); + filters["1"].push_back(std::move(intent_filter2)); + + apps::IntentFilters intent_filters2; + auto intent_filter3 = std::make_unique<apps::IntentFilter>(); + apps_util::AddSingleValueCondition(apps::ConditionType::kAction, "4", + apps::PatternMatchType::kGlob, + intent_filter3); + apps_util::AddSingleValueCondition(apps::ConditionType::kMimeType, "5", + apps::PatternMatchType::kMimeType, + intent_filter3); + filters["2"].push_back(std::move(intent_filter3)); + + auto intent_filter4 = std::make_unique<apps::IntentFilter>(); + apps_util::AddSingleValueCondition(apps::ConditionType::kFile, "6", + apps::PatternMatchType::kMimeType, + intent_filter4); + apps_util::AddSingleValueCondition(apps::ConditionType::kFile, "7", + apps::PatternMatchType::kFileExtension, + intent_filter4); + filters["2"].push_back(std::move(intent_filter4)); + + auto output = apps::ConvertMojomIntentFiltersToIntentFilters( + apps::ConvertIntentFiltersToMojomIntentFilters(filters)); + + ASSERT_EQ(output.size(), 2U); + EXPECT_EQ(*filters["1"][0], *output["1"][0]); + EXPECT_EQ(*filters["1"][1], *output["1"][1]); + + EXPECT_EQ(*filters["2"][0], *output["2"][0]); + EXPECT_EQ(*filters["2"][1], *output["2"][1]); + + { + auto& condition = output["1"][0]->conditions[0]; + EXPECT_EQ(condition->condition_type, apps::ConditionType::kScheme); + ASSERT_EQ(condition->condition_values.size(), 1U); + EXPECT_EQ(condition->condition_values[0]->match_type, + apps::PatternMatchType::kNone); + EXPECT_EQ(condition->condition_values[0]->value, "1"); + } + { + auto& condition = output["1"][1]->conditions[0]; + EXPECT_EQ(condition->condition_type, apps::ConditionType::kHost); + ASSERT_EQ(condition->condition_values.size(), 1U); + EXPECT_EQ(condition->condition_values[0]->match_type, + apps::PatternMatchType::kLiteral); + EXPECT_EQ(condition->condition_values[0]->value, "2"); + } + { + auto& condition = output["1"][1]->conditions[1]; + EXPECT_EQ(condition->condition_type, apps::ConditionType::kPattern); + ASSERT_EQ(condition->condition_values.size(), 1U); + EXPECT_EQ(condition->condition_values[0]->match_type, + apps::PatternMatchType::kPrefix); + EXPECT_EQ(condition->condition_values[0]->value, "3"); + } + { + auto& condition = output["2"][0]->conditions[0]; + EXPECT_EQ(condition->condition_type, apps::ConditionType::kAction); + ASSERT_EQ(condition->condition_values.size(), 1U); + EXPECT_EQ(condition->condition_values[0]->match_type, + apps::PatternMatchType::kGlob); + EXPECT_EQ(condition->condition_values[0]->value, "4"); + } + { + auto& condition = output["2"][0]->conditions[1]; + EXPECT_EQ(condition->condition_type, apps::ConditionType::kMimeType); + ASSERT_EQ(condition->condition_values.size(), 1U); + EXPECT_EQ(condition->condition_values[0]->match_type, + apps::PatternMatchType::kMimeType); + EXPECT_EQ(condition->condition_values[0]->value, "5"); + } + { + auto& condition = output["2"][1]->conditions[0]; + EXPECT_EQ(condition->condition_type, apps::ConditionType::kFile); + ASSERT_EQ(condition->condition_values.size(), 1U); + EXPECT_EQ(condition->condition_values[0]->match_type, + apps::PatternMatchType::kMimeType); + EXPECT_EQ(condition->condition_values[0]->value, "6"); + } + { + auto& condition = output["2"][1]->conditions[1]; + EXPECT_EQ(condition->condition_type, apps::ConditionType::kFile); + ASSERT_EQ(condition->condition_values.size(), 1U); + EXPECT_EQ(condition->condition_values[0]->match_type, + apps::PatternMatchType::kFileExtension); + EXPECT_EQ(condition->condition_values[0]->value, "7"); + } +} diff --git a/chromium/components/services/app_service/public/cpp/intent_util.cc b/chromium/components/services/app_service/public/cpp/intent_util.cc index d08a4483572..57d426c8753 100644 --- a/chromium/components/services/app_service/public/cpp/intent_util.cc +++ b/chromium/components/services/app_service/public/cpp/intent_util.cc @@ -610,22 +610,20 @@ base::Value ConvertIntentToValue(const apps::mojom::IntentPtr& intent) { absl::optional<std::string> GetStringValueFromDict( const base::DictionaryValue& dict, const std::string& key_name) { - if (!dict.HasKey(key_name)) + const base::Value* value = dict.FindKey(key_name); + if (!value) return absl::nullopt; - const std::string* value = dict.FindStringKey(key_name); - if (!value || value->empty()) + const std::string* string_value = value->GetIfString(); + if (!string_value || string_value->empty()) return absl::nullopt; - return *value; + return *string_value; } apps::mojom::OptionalBool GetBoolValueFromDict( const base::DictionaryValue& dict, const std::string& key_name) { - if (!dict.HasKey(key_name)) - return apps::mojom::OptionalBool::kUnknown; - absl::optional<bool> value = dict.FindBoolKey(key_name); if (!value.has_value()) return apps::mojom::OptionalBool::kUnknown; @@ -636,9 +634,6 @@ apps::mojom::OptionalBool GetBoolValueFromDict( absl::optional<GURL> GetGurlValueFromDict(const base::DictionaryValue& dict, const std::string& key_name) { - if (!dict.HasKey(key_name)) - return absl::nullopt; - const std::string* url_spec = dict.FindStringKey(key_name); if (!url_spec) return absl::nullopt; @@ -653,15 +648,12 @@ absl::optional<GURL> GetGurlValueFromDict(const base::DictionaryValue& dict, absl::optional<std::vector<apps::mojom::IntentFilePtr>> GetFilesFromDict( const base::DictionaryValue& dict, const std::string& key_name) { - if (!dict.HasKey(key_name)) - return absl::nullopt; - const base::Value* value = dict.FindListKey(key_name); - if (!value || !value->is_list() || value->GetList().empty()) + if (!value || !value->is_list() || value->GetListDeprecated().empty()) return absl::nullopt; std::vector<apps::mojom::IntentFilePtr> files; - for (const auto& item : value->GetList()) { + for (const auto& item : value->GetListDeprecated()) { GURL url(item.GetString()); if (url.is_valid()) { auto file = apps::mojom::IntentFile::New(); @@ -675,15 +667,12 @@ absl::optional<std::vector<apps::mojom::IntentFilePtr>> GetFilesFromDict( absl::optional<std::vector<std::string>> GetCategoriesFromDict( const base::DictionaryValue& dict, const std::string& key_name) { - if (!dict.HasKey(key_name)) - return absl::nullopt; - const base::Value* value = dict.FindListKey(key_name); - if (!value || !value->is_list() || value->GetList().empty()) + if (!value || !value->is_list() || value->GetListDeprecated().empty()) return absl::nullopt; std::vector<std::string> categories; - for (const auto& item : value->GetList()) + for (const auto& item : value->GetListDeprecated()) categories.push_back(item.GetString()); return categories; @@ -692,9 +681,6 @@ absl::optional<std::vector<std::string>> GetCategoriesFromDict( absl::optional<base::flat_map<std::string, std::string>> GetExtrasFromDict( const base::DictionaryValue& dict, const std::string& key_name) { - if (!dict.HasKey(key_name)) - return absl::nullopt; - const base::Value* value = dict.FindDictKey(key_name); if (!value || !value->is_dict()) return absl::nullopt; diff --git a/chromium/components/services/app_service/public/cpp/macros.h b/chromium/components/services/app_service/public/cpp/macros.h index 9cef7b39213..6e7d30d9844 100644 --- a/chromium/components/services/app_service/public/cpp/macros.h +++ b/chromium/components/services/app_service/public/cpp/macros.h @@ -7,6 +7,16 @@ namespace apps { +#define SET_OPTIONAL_VALUE(VALUE) \ + if (delta->VALUE.has_value()) { \ + state->VALUE = delta->VALUE; \ + } + +#define SET_ENUM_VALUE(VALUE, DEFAULT_VALUE) \ + if (delta->VALUE != DEFAULT_VALUE) { \ + state->VALUE = delta->VALUE; \ + } + #define GET_VALUE(VALUE) \ if (delta_ && delta_->VALUE()) { \ return delta_->VALUE(); \ @@ -20,12 +30,21 @@ namespace apps { return delta_ && delta_->VALUE() && \ (!state_ || (delta_->VALUE() != state_->VALUE())); +#define GET_VALUE_WITH_FALLBACK(VALUE, FALLBACK_VALUE) \ + if (delta_ && delta_->VALUE.has_value()) { \ + return delta_->VALUE.value(); \ + } \ + if (state_ && state_->VALUE.has_value()) { \ + return state_->VALUE.value(); \ + } \ + return FALLBACK_VALUE; + #define GET_VALUE_WITH_DEFAULT_VALUE(VALUE, DEFAULT_VALUE) \ - if (delta_ && delta_->VALUE() != (DEFAULT_VALUE)) { \ - return delta_->VALUE(); \ + if (delta_ && delta_->VALUE != (DEFAULT_VALUE)) { \ + return delta_->VALUE; \ } \ if (state_) { \ - return state_->VALUE(); \ + return state_->VALUE; \ } \ return DEFAULT_VALUE; @@ -34,11 +53,11 @@ namespace apps { (!state_ || (delta_->VALUE() != state_->VALUE())); #define GET_VALUE_WITH_CHECK_AND_DEFAULT_RETURN(VALUE, CHECK, DEFAULT_RETURN) \ - if (delta_ && !delta_->VALUE().CHECK()) { \ - return delta_->VALUE(); \ + if (delta_ && !delta_->VALUE.CHECK()) { \ + return delta_->VALUE; \ } \ - if (state_ && !state_->VALUE().CHECK()) { \ - return state_->VALUE(); \ + if (state_ && !state_->VALUE.CHECK()) { \ + return state_->VALUE; \ } \ return DEFAULT_RETURN; diff --git a/chromium/components/services/app_service/public/cpp/permission.cc b/chromium/components/services/app_service/public/cpp/permission.cc new file mode 100644 index 00000000000..7e91f5ab680 --- /dev/null +++ b/chromium/components/services/app_service/public/cpp/permission.cc @@ -0,0 +1,213 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/services/app_service/public/cpp/permission.h" + +namespace apps { + +PermissionValue::PermissionValue(bool bool_value) : bool_value(bool_value) {} + +PermissionValue::PermissionValue(TriState tristate_value) + : tristate_value(tristate_value) {} + +PermissionValue::~PermissionValue() = default; + +bool PermissionValue::operator==(const PermissionValue& other) const { + if (tristate_value.has_value() && other.tristate_value.has_value()) { + return tristate_value.value() == other.tristate_value.value(); + } + + if (bool_value.has_value() && other.bool_value.has_value()) { + return bool_value.value() == other.bool_value.value(); + } + + return false; +} + +std::unique_ptr<PermissionValue> PermissionValue::Clone() const { + if (tristate_value.has_value()) { + return std::make_unique<PermissionValue>(tristate_value.value()); + } + + if (bool_value.has_value()) { + return std::make_unique<PermissionValue>(bool_value.value()); + } + + return nullptr; +} + +bool PermissionValue::IsPermissionEnabled() { + if (tristate_value.has_value()) { + return tristate_value.value() == TriState::kAllow; + } else if (bool_value.has_value()) { + return bool_value.value(); + } + return false; +} + +Permission::Permission(PermissionType permission_type, + PermissionValuePtr value, + bool is_managed) + : permission_type(permission_type), + value(std::move(value)), + is_managed(is_managed) {} + +Permission::~Permission() = default; + +bool Permission::operator==(const Permission& other) const { + return permission_type == other.permission_type && + ((!value && !other.value) || (*value == *other.value)) && + is_managed == other.is_managed; +} + +bool Permission::operator!=(const Permission& other) const { + return !(*this == other); +} + +PermissionPtr Permission::Clone() const { + if (!value) { + return nullptr; + } + + return std::make_unique<Permission>(permission_type, value->Clone(), + is_managed); +} + +Permissions ClonePermissions(const Permissions& source_permissions) { + Permissions permissions; + for (const auto& permission : source_permissions) { + permissions.push_back(permission->Clone()); + } + return permissions; +} + +PermissionType ConvertMojomPermissionTypeToPermissionType( + apps::mojom::PermissionType mojom_permission_type) { + switch (mojom_permission_type) { + case apps::mojom::PermissionType::kUnknown: + return PermissionType::kUnknown; + case apps::mojom::PermissionType::kCamera: + return PermissionType::kCamera; + case apps::mojom::PermissionType::kLocation: + return PermissionType::kLocation; + case apps::mojom::PermissionType::kMicrophone: + return PermissionType::kMicrophone; + case apps::mojom::PermissionType::kNotifications: + return PermissionType::kNotifications; + case apps::mojom::PermissionType::kContacts: + return PermissionType::kContacts; + case apps::mojom::PermissionType::kStorage: + return PermissionType::kStorage; + case apps::mojom::PermissionType::kPrinting: + return PermissionType::kPrinting; + } +} + +apps::mojom::PermissionType ConvertPermissionTypeToMojomPermissionType( + PermissionType permission_type) { + switch (permission_type) { + case PermissionType::kUnknown: + return apps::mojom::PermissionType::kUnknown; + case PermissionType::kCamera: + return apps::mojom::PermissionType::kCamera; + case PermissionType::kLocation: + return apps::mojom::PermissionType::kLocation; + case PermissionType::kMicrophone: + return apps::mojom::PermissionType::kMicrophone; + case PermissionType::kNotifications: + return apps::mojom::PermissionType::kNotifications; + case PermissionType::kContacts: + return apps::mojom::PermissionType::kContacts; + case PermissionType::kStorage: + return apps::mojom::PermissionType::kStorage; + case PermissionType::kPrinting: + return apps::mojom::PermissionType::kPrinting; + } +} + +TriState ConvertMojomTriStateToTriState(apps::mojom::TriState mojom_tri_state) { + switch (mojom_tri_state) { + case apps::mojom::TriState::kAllow: + return TriState::kAllow; + case apps::mojom::TriState::kBlock: + return TriState::kBlock; + case apps::mojom::TriState::kAsk: + return TriState::kAsk; + } +} + +apps::mojom::TriState ConvertTriStateToMojomTriState(TriState tri_state) { + switch (tri_state) { + case TriState::kAllow: + return apps::mojom::TriState::kAllow; + case TriState::kBlock: + return apps::mojom::TriState::kBlock; + case TriState::kAsk: + return apps::mojom::TriState::kAsk; + } +} + +PermissionValuePtr ConvertMojomPermissionValueToPermissionValue( + const apps::mojom::PermissionValuePtr& mojom_permission_value) { + if (!mojom_permission_value) { + return nullptr; + } + + if (mojom_permission_value->is_tristate_value()) { + return std::make_unique<PermissionValue>(ConvertMojomTriStateToTriState( + mojom_permission_value->get_tristate_value())); + } else if (mojom_permission_value->is_bool_value()) { + return std::make_unique<PermissionValue>( + mojom_permission_value->get_bool_value()); + } + return nullptr; +} + +apps::mojom::PermissionValuePtr ConvertPermissionValueToMojomPermissionValue( + const PermissionValuePtr& permission_value) { + auto mojom_permission_value = apps::mojom::PermissionValue::New(); + if (!permission_value) { + return mojom_permission_value; + } + + if (permission_value->bool_value.has_value()) { + mojom_permission_value->set_bool_value( + permission_value->bool_value.value()); + } + if (permission_value->tristate_value.has_value()) { + mojom_permission_value->set_tristate_value(ConvertTriStateToMojomTriState( + permission_value->tristate_value.value())); + } + return mojom_permission_value; +} + +PermissionPtr ConvertMojomPermissionToPermission( + const apps::mojom::PermissionPtr& mojom_permission) { + if (!mojom_permission) { + return nullptr; + } + + return std::make_unique<Permission>( + ConvertMojomPermissionTypeToPermissionType( + mojom_permission->permission_type), + ConvertMojomPermissionValueToPermissionValue(mojom_permission->value), + mojom_permission->is_managed); +} + +apps::mojom::PermissionPtr ConvertPermissionToMojomPermission( + const PermissionPtr& permission) { + auto mojom_permission = apps::mojom::Permission::New(); + if (!permission) { + return mojom_permission; + } + + mojom_permission->permission_type = + ConvertPermissionTypeToMojomPermissionType(permission->permission_type); + mojom_permission->value = + ConvertPermissionValueToMojomPermissionValue(permission->value); + mojom_permission->is_managed = permission->is_managed; + return mojom_permission; +} + +} // namespace apps diff --git a/chromium/components/services/app_service/public/cpp/permission.h b/chromium/components/services/app_service/public/cpp/permission.h new file mode 100644 index 00000000000..1483cf52b8a --- /dev/null +++ b/chromium/components/services/app_service/public/cpp/permission.h @@ -0,0 +1,117 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_PERMISSION_H_ +#define COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_PERMISSION_H_ + +#include <utility> +#include <vector> + +#include "base/component_export.h" +#include "components/services/app_service/public/mojom/types.mojom.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace apps { + +// The types of permissions in App Service. +enum class PermissionType { + kUnknown = 0, + kCamera = 1, + kLocation = 2, + kMicrophone = 3, + kNotifications = 4, + kContacts = 5, + kStorage = 6, + kPrinting = 7, +}; + +enum class TriState { + kAllow, + kBlock, + kAsk, +}; + +// The permission value could be a TriState or a bool +struct COMPONENT_EXPORT(APP_TYPES) PermissionValue { + explicit PermissionValue(bool bool_value); + explicit PermissionValue(TriState tristate_value); + PermissionValue(const PermissionValue&) = delete; + PermissionValue& operator=(const PermissionValue&) = delete; + ~PermissionValue(); + + bool operator==(const PermissionValue& other) const; + + std::unique_ptr<PermissionValue> Clone() const; + + // Checks whether this is equal to permission enabled. If it is TriState, only + // Allow represent permission enabled. + bool IsPermissionEnabled(); + + absl::optional<bool> bool_value; + absl::optional<TriState> tristate_value; +}; + +using PermissionValuePtr = std::unique_ptr<PermissionValue>; + +struct COMPONENT_EXPORT(APP_TYPES) Permission { + Permission(PermissionType permission_type, + PermissionValuePtr value, + bool is_managed); + Permission(const Permission&) = delete; + Permission& operator=(const Permission&) = delete; + ~Permission(); + + bool operator==(const Permission& other) const; + bool operator!=(const Permission& other) const; + + std::unique_ptr<Permission> Clone() const; + + PermissionType permission_type; + std::unique_ptr<PermissionValue> value; + // If the permission is managed by an enterprise policy. + bool is_managed; +}; + +using PermissionPtr = std::unique_ptr<Permission>; +using Permissions = std::vector<PermissionPtr>; + +// Creates a deep copy of `source_permissions`. +COMPONENT_EXPORT(APP_TYPES) +Permissions ClonePermissions(const Permissions& source_permissions); + +// TODO(crbug.com/1253250): Remove these functions after migrating to non-mojo +// AppService. +COMPONENT_EXPORT(APP_TYPES) +PermissionType ConvertMojomPermissionTypeToPermissionType( + apps::mojom::PermissionType mojom_permission_type); + +COMPONENT_EXPORT(APP_TYPES) +apps::mojom::PermissionType ConvertPermissionTypeToMojomPermissionType( + PermissionType permission_type); + +COMPONENT_EXPORT(APP_TYPES) +TriState ConvertMojomTriStateToTriState(apps::mojom::TriState mojom_tri_state); + +COMPONENT_EXPORT(APP_TYPES) +apps::mojom::TriState ConvertTriStateToMojomTriState(TriState tri_state); + +COMPONENT_EXPORT(APP_TYPES) +PermissionValuePtr ConvertMojomPermissionValueToPermissionValue( + const apps::mojom::PermissionValuePtr& mojom_permission_value); + +COMPONENT_EXPORT(APP_TYPES) +apps::mojom::PermissionValuePtr ConvertPermissionValueToMojomPermissionValue( + const PermissionValuePtr& permission_value); + +COMPONENT_EXPORT(APP_TYPES) +PermissionPtr ConvertMojomPermissionToPermission( + const apps::mojom::PermissionPtr& mojom_permission); + +COMPONENT_EXPORT(APP_TYPES) +apps::mojom::PermissionPtr ConvertPermissionToMojomPermission( + const PermissionPtr& permission); + +} // namespace apps + +#endif // COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_PERMISSION_H_ diff --git a/chromium/components/services/app_service/public/cpp/preferred_apps_converter.cc b/chromium/components/services/app_service/public/cpp/preferred_apps_converter.cc index 0114a7944e2..e83d2165923 100644 --- a/chromium/components/services/app_service/public/cpp/preferred_apps_converter.cc +++ b/chromium/components/services/app_service/public/cpp/preferred_apps_converter.cc @@ -84,7 +84,7 @@ apps::mojom::ConditionPtr ParseValueToCondition(const base::Value& value) { << apps::kConditionValuesKey << "\" key with list value."; return nullptr; } - for (auto& condition_value : condition_values->GetList()) { + for (auto& condition_value : condition_values->GetListDeprecated()) { auto parsed_condition_value = ParseValueToConditionValue(condition_value); if (!parsed_condition_value) { DVLOG(0) << "Fail to parse condition. Cannot parse condition values"; @@ -102,7 +102,7 @@ apps::mojom::IntentFilterPtr ParseValueToIntentFilter( return nullptr; } auto intent_filter = apps::mojom::IntentFilter::New(); - for (auto& condition : value->GetList()) { + for (auto& condition : value->GetListDeprecated()) { auto parsed_condition = ParseValueToCondition(condition); if (!parsed_condition) { DVLOG(0) << "Fail to parse intent filter. Cannot parse conditions."; @@ -160,7 +160,7 @@ PreferredAppsList::PreferredApps ParseValueToPreferredApps( } PreferredAppsList::PreferredApps preferred_apps; - for (auto& entry : preferred_apps_list->GetList()) { + for (auto& entry : preferred_apps_list->GetListDeprecated()) { auto* app_id = entry.FindStringKey(kAppIdKey); if (!app_id) { DVLOG(0) << "Fail to parse condition value. Cannot find \"" diff --git a/chromium/components/services/app_service/public/cpp/preferred_apps_converter_unittest.cc b/chromium/components/services/app_service/public/cpp/preferred_apps_converter_unittest.cc index acc8599f795..b40d83862db 100644 --- a/chromium/components/services/app_service/public/cpp/preferred_apps_converter_unittest.cc +++ b/chromium/components/services/app_service/public/cpp/preferred_apps_converter_unittest.cc @@ -33,20 +33,21 @@ TEST_F(PreferredAppsConverterTest, ConvertSimpleEntry) { auto* converted_preferred_apps = converted_value.FindKey(apps::kPreferredAppsKey); // Check that each entry is correct. - ASSERT_EQ(1u, converted_preferred_apps->GetList().size()); - auto& entry = converted_preferred_apps->GetList()[0]; + ASSERT_EQ(1u, converted_preferred_apps->GetListDeprecated().size()); + auto& entry = converted_preferred_apps->GetListDeprecated()[0]; EXPECT_EQ(kAppId1, *entry.FindStringKey(apps::kAppIdKey)); auto* converted_intent_filter = entry.FindKey(apps::kIntentFilterKey); ASSERT_EQ(intent_filter->conditions.size(), - converted_intent_filter->GetList().size()); + converted_intent_filter->GetListDeprecated().size()); for (size_t i = 0; i < intent_filter->conditions.size(); i++) { auto& condition = intent_filter->conditions[i]; - auto& converted_condition = converted_intent_filter->GetList()[i]; + auto& converted_condition = converted_intent_filter->GetListDeprecated()[i]; auto& condition_values = condition->condition_values; auto converted_condition_values = - converted_condition.FindKey(apps::kConditionValuesKey)->GetList(); + converted_condition.FindKey(apps::kConditionValuesKey) + ->GetListDeprecated(); EXPECT_EQ(static_cast<int>(condition->condition_type), converted_condition.FindIntKey(apps::kConditionTypeKey)); diff --git a/chromium/components/services/app_service/public/cpp/publisher_base.cc b/chromium/components/services/app_service/public/cpp/publisher_base.cc index 2fb5657d845..9f1d5aa4e28 100644 --- a/chromium/components/services/app_service/public/cpp/publisher_base.cc +++ b/chromium/components/services/app_service/public/cpp/publisher_base.cc @@ -181,4 +181,10 @@ void PublisherBase::SetWindowMode(const std::string& app_id, NOTIMPLEMENTED(); } +void PublisherBase::SetRunOnOsLoginMode( + const std::string& app_id, + apps::mojom::RunOnOsLoginMode run_on_os_login_mode) { + NOTIMPLEMENTED(); +} + } // namespace apps diff --git a/chromium/components/services/app_service/public/cpp/publisher_base.h b/chromium/components/services/app_service/public/cpp/publisher_base.h index 20f8955123a..d5e4951902a 100644 --- a/chromium/components/services/app_service/public/cpp/publisher_base.h +++ b/chromium/components/services/app_service/public/cpp/publisher_base.h @@ -101,6 +101,9 @@ class PublisherBase : public apps::mojom::Publisher { apps::mojom::OptionalBool locked) override; void SetWindowMode(const std::string& app_id, apps::mojom::WindowMode window_mode) override; + void SetRunOnOsLoginMode( + const std::string& app_id, + apps::mojom::RunOnOsLoginMode run_on_os_login_mode) override; mojo::Receiver<apps::mojom::Publisher> receiver_{this}; }; diff --git a/chromium/components/services/app_service/public/cpp/run_on_os_login_types.cc b/chromium/components/services/app_service/public/cpp/run_on_os_login_types.cc new file mode 100644 index 00000000000..0cf4bacdd45 --- /dev/null +++ b/chromium/components/services/app_service/public/cpp/run_on_os_login_types.cc @@ -0,0 +1,62 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/services/app_service/public/cpp/run_on_os_login_types.h" + +namespace apps { + +RunOnOsLogin::RunOnOsLogin() = default; + +RunOnOsLogin::RunOnOsLogin(RunOnOsLoginMode login_mode, bool is_managed) + : login_mode(login_mode), is_managed(is_managed) {} + +RunOnOsLogin::~RunOnOsLogin() = default; + +bool RunOnOsLogin::operator==(const RunOnOsLogin& other) const { + return login_mode == other.login_mode && is_managed == other.is_managed; +} + +apps::mojom::RunOnOsLoginPtr ConvertRunOnOsLoginToMojomRunOnOsLogin( + const RunOnOsLogin& run_on_os_login) { + auto run_on_os_login_mojom = apps::mojom::RunOnOsLogin::New(); + run_on_os_login_mojom->login_mode = + ConvertRunOnOsLoginModeToMojomRunOnOsLoginMode( + run_on_os_login.login_mode); + run_on_os_login_mojom->is_managed = run_on_os_login.is_managed; + return run_on_os_login_mojom; +} + +std::unique_ptr<RunOnOsLogin> ConvertMojomRunOnOsLoginToRunOnOsLogin( + const apps::mojom::RunOnOsLoginPtr& run_on_os_login) { + DCHECK(run_on_os_login); + return std::make_unique<RunOnOsLogin>( + ConvertMojomRunOnOsLoginModeToRunOnOsLoginMode( + run_on_os_login->login_mode), + run_on_os_login->is_managed); +} + +apps::mojom::RunOnOsLoginMode ConvertRunOnOsLoginModeToMojomRunOnOsLoginMode( + RunOnOsLoginMode login_mode) { + switch (login_mode) { + case RunOnOsLoginMode::kUnknown: + return apps::mojom::RunOnOsLoginMode::kUnknown; + case RunOnOsLoginMode::kNotRun: + return apps::mojom::RunOnOsLoginMode::kNotRun; + case RunOnOsLoginMode::kWindowed: + return apps::mojom::RunOnOsLoginMode::kWindowed; + } +} + +RunOnOsLoginMode ConvertMojomRunOnOsLoginModeToRunOnOsLoginMode( + apps::mojom::RunOnOsLoginMode login_mode) { + switch (login_mode) { + case apps::mojom::RunOnOsLoginMode::kUnknown: + return RunOnOsLoginMode::kUnknown; + case apps::mojom::RunOnOsLoginMode::kNotRun: + return RunOnOsLoginMode::kNotRun; + case apps::mojom::RunOnOsLoginMode::kWindowed: + return RunOnOsLoginMode::kWindowed; + } +} +} // namespace apps
\ No newline at end of file diff --git a/chromium/components/services/app_service/public/cpp/run_on_os_login_types.h b/chromium/components/services/app_service/public/cpp/run_on_os_login_types.h new file mode 100644 index 00000000000..fccf6d8a239 --- /dev/null +++ b/chromium/components/services/app_service/public/cpp/run_on_os_login_types.h @@ -0,0 +1,64 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_RUN_ON_OS_LOGIN_TYPES_H_ +#define COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_RUN_ON_OS_LOGIN_TYPES_H_ + +#include <vector> + +#include "base/component_export.h" +#include "components/services/app_service/public/mojom/types.mojom.h" + +namespace apps { + +enum class RunOnOsLoginMode { + // kUnknown to be used for app_update.cc. + kUnknown, + // App won't run on OS Login. + kNotRun, + // App runs in windowed mode on OS Login. + kWindowed, +}; + +struct COMPONENT_EXPORT(LOGIN_MODE) RunOnOsLogin { + RunOnOsLogin(); + RunOnOsLogin(RunOnOsLoginMode login_mode, bool is_managed); + + RunOnOsLogin(const RunOnOsLogin&) = delete; + RunOnOsLogin& operator=(const RunOnOsLogin&) = delete; + RunOnOsLogin(RunOnOsLogin&&) = default; + RunOnOsLogin& operator=(RunOnOsLogin&&) = default; + + bool operator==(const RunOnOsLogin& other) const; + + ~RunOnOsLogin(); + + // RunOnOsLoginMode struct to be used + // to verify if the mode is set by policy + // or not. + RunOnOsLoginMode login_mode; + // If the run on os login mode is policy + // controlled or not. + bool is_managed; +}; + +COMPONENT_EXPORT(LOGIN_MODE) +apps::mojom::RunOnOsLoginPtr ConvertRunOnOsLoginToMojomRunOnOsLogin( + const RunOnOsLogin& run_on_os_login); + +COMPONENT_EXPORT(LOGIN_MODE) +std::unique_ptr<RunOnOsLogin> ConvertMojomRunOnOsLoginToRunOnOsLogin( + const apps::mojom::RunOnOsLoginPtr& run_on_os_login); + +COMPONENT_EXPORT(LOGIN_MODE) +apps::mojom::RunOnOsLoginMode ConvertRunOnOsLoginModeToMojomRunOnOsLoginMode( + RunOnOsLoginMode login_mode); + +COMPONENT_EXPORT(LOGIN_MODE) +RunOnOsLoginMode ConvertMojomRunOnOsLoginModeToRunOnOsLoginMode( + apps::mojom::RunOnOsLoginMode login_mode); + +} // namespace apps + +#endif // COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_RUN_ON_OS_LOGIN_TYPES_H_
\ No newline at end of file diff --git a/chromium/components/services/app_service/public/mojom/BUILD.gn b/chromium/components/services/app_service/public/mojom/BUILD.gn index b2a043d80dd..76718d3985c 100644 --- a/chromium/components/services/app_service/public/mojom/BUILD.gn +++ b/chromium/components/services/app_service/public/mojom/BUILD.gn @@ -6,13 +6,13 @@ import("//mojo/public/tools/bindings/mojom.gni") mojom("types") { sources = [ "types.mojom" ] + webui_module_path = "/" public_deps = [ "//mojo/public/mojom/base", "//skia/public/mojom", "//ui/gfx/geometry/mojom", "//ui/gfx/image/mojom", - "//ui/gfx/image/mojom", "//ui/gfx/mojom", "//ui/gfx/range/mojom", "//url/mojom:url_mojom_gurl", @@ -21,6 +21,7 @@ mojom("types") { mojom("mojom") { sources = [ "app_service.mojom" ] + webui_module_path = "/" public_deps = [ ":types" ] } diff --git a/chromium/components/services/app_service/public/mojom/app_service.mojom b/chromium/components/services/app_service/public/mojom/app_service.mojom index 7fcc26c101b..199129ed973 100644 --- a/chromium/components/services/app_service/public/mojom/app_service.mojom +++ b/chromium/components/services/app_service/public/mojom/app_service.mojom @@ -13,6 +13,9 @@ import "components/services/app_service/public/mojom/types.mojom"; // app's name and icon) that are satisfied by the appropriate provider. // // See components/services/app_service/README.md. +// +// Mojom AppService is DEPRECATED. When adding new interfaces, use AppPublisher +// in chrome/browser/apps/app_service/publishers/app_publisher.h. interface AppService { // Called by a publisher of apps to register itself and its apps with the App // Service. @@ -166,6 +169,12 @@ interface AppService { AppType app_type, string app_id, WindowMode window_mode); + + // Set the mode for the app to be run on os login identified by |app_id|. + SetRunOnOsLoginMode( + AppType app_type, + string app_id, + RunOnOsLoginMode run_on_os_login_mode); }; interface Publisher { @@ -333,6 +342,12 @@ interface Publisher { SetWindowMode( string app_id, WindowMode window_mode); + + // Set the mode to run on OS Login for the app identified by |app_id|. + // Implemented if the publisher supports setting this mode for OS Login, + // otherwise should do nothing. + SetRunOnOsLoginMode(string app_id, + RunOnOsLoginMode run_on_os_login_mode); }; // Subscriber works as a proxy, to receive a stream of apps from publishers, diff --git a/chromium/components/services/app_service/public/mojom/types.mojom b/chromium/components/services/app_service/public/mojom/types.mojom index b419afc1580..626bb17c7c9 100644 --- a/chromium/components/services/app_service/public/mojom/types.mojom +++ b/chromium/components/services/app_service/public/mojom/types.mojom @@ -91,7 +91,11 @@ struct App { // Whether the app's display mode is in the browser or otherwise. WindowMode window_mode; - // When adding new fields, also update the Merge method and other helpers in + // Whether the app runs on os login. + RunOnOsLogin? run_on_os_login; + // When adding new fields, also update the App struct in + // components/services/app_service/public/cpp/app_types.* and the Merge method + // and other helpers in // components/services/app_service/public/cpp/app_update.* }; @@ -533,3 +537,24 @@ enum WindowMode { // Opens in a tabbed app window kTabbedWindow, }; + +// The RunOnOsLoginModes must be kept in sync +// with RunOnOsLoginMode in +// chrome/browser/web_applications/web_app_constants.h +enum RunOnOsLoginMode { + kUnknown = 0, + // App won't run on OS Login. + kNotRun, + // App will run in windowed mode on OS Login. + kWindowed, +}; + +// RunOnOsLoginMode struct to be used +// to verify if the mode is set by policy +// or not. +struct RunOnOsLogin { + RunOnOsLoginMode login_mode; + // If the run on os login mode is policy + // controlled or not. + bool is_managed; +}; diff --git a/chromium/components/services/filesystem/directory_impl.cc b/chromium/components/services/filesystem/directory_impl.cc index 82b49267de7..e484cc0eb29 100644 --- a/chromium/components/services/filesystem/directory_impl.cc +++ b/chromium/components/services/filesystem/directory_impl.cc @@ -259,7 +259,7 @@ void DirectoryImpl::IsWritable(const std::string& raw_path, void DirectoryImpl::Flush(FlushCallback callback) { // On Windows no need to sync directories. Their metadata will be updated when // files are created, without an explicit sync. -#if !defined(OS_WIN) +#if !BUILDFLAG(IS_WIN) base::File file(directory_path_, base::File::FLAG_OPEN | base::File::FLAG_READ); if (!file.IsValid()) { diff --git a/chromium/components/services/filesystem/file_impl.cc b/chromium/components/services/filesystem/file_impl.cc index 29e51005498..a8417ea7367 100644 --- a/chromium/components/services/filesystem/file_impl.cc +++ b/chromium/components/services/filesystem/file_impl.cc @@ -65,7 +65,7 @@ bool FileImpl::IsValid() const { return file_.IsValid(); } -#if !defined(OS_FUCHSIA) +#if !BUILDFLAG(IS_FUCHSIA) base::File::Error FileImpl::RawLockFile() { return file_.Lock(base::File::LockMode::kExclusive); } @@ -73,7 +73,7 @@ base::File::Error FileImpl::RawLockFile() { base::File::Error FileImpl::RawUnlockFile() { return file_.Unlock(); } -#endif // !OS_FUCHSIA +#endif // !BUILDFLAG(IS_FUCHSIA) void FileImpl::Close(CloseCallback callback) { if (!file_.IsValid()) { @@ -143,7 +143,7 @@ void FileImpl::Write(const std::vector<uint8_t>& bytes_to_write, // Who knows what |write()| would return if the size is that big (and it // actually wrote that much). if (bytes_to_write.size() > -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) static_cast<size_t>(std::numeric_limits<int>::max())) { #else static_cast<size_t>(std::numeric_limits<ssize_t>::max())) { diff --git a/chromium/components/services/filesystem/file_impl.h b/chromium/components/services/filesystem/file_impl.h index 4a3d4a0f70e..8ae90799d29 100644 --- a/chromium/components/services/filesystem/file_impl.h +++ b/chromium/components/services/filesystem/file_impl.h @@ -41,12 +41,12 @@ class FileImpl : public mojom::File { // Returns whether the underlying file handle is valid. bool IsValid() const; -#if !defined(OS_FUCHSIA) +#if !BUILDFLAG(IS_FUCHSIA) // Attempts to perform the native operating system's locking operations on // the internal mojom::File handle. Not supported on Fuchsia. base::File::Error RawLockFile(); base::File::Error RawUnlockFile(); -#endif // !OS_FUCHSIA +#endif // !BUILDFLAG(IS_FUCHSIA) const base::FilePath& path() const { return path_; } diff --git a/chromium/components/services/filesystem/lock_table.cc b/chromium/components/services/filesystem/lock_table.cc index a3402a9ef54..e1bdf6b71fa 100644 --- a/chromium/components/services/filesystem/lock_table.cc +++ b/chromium/components/services/filesystem/lock_table.cc @@ -23,7 +23,7 @@ base::File::Error LockTable::LockFile(FileImpl* file) { return base::File::FILE_ERROR_FAILED; } -#if !defined(OS_FUCHSIA) +#if !BUILDFLAG(IS_FUCHSIA) // Fuchsia doesn't provide a file locking mechanism, so file locks work only // within a single process. File locking is used only by LevelDB which stores // all files in the profile directory and normally there shouldn't be more @@ -36,7 +36,7 @@ base::File::Error LockTable::LockFile(FileImpl* file) { // Locking failed for some reason. return lock_err; } -#endif // !OS_FUCHSIA +#endif // !BUILDFLAG(IS_FUCHSIA) locked_files_.insert(file->path()); return base::File::FILE_OK; @@ -45,14 +45,14 @@ base::File::Error LockTable::LockFile(FileImpl* file) { base::File::Error LockTable::UnlockFile(FileImpl* file) { auto it = locked_files_.find(file->path()); if (it != locked_files_.end()) { -#if !defined(OS_FUCHSIA) +#if !BUILDFLAG(IS_FUCHSIA) base::File::Error lock_err = file->RawUnlockFile(); if (lock_err != base::File::FILE_OK) { // TODO(erg): When can we fail to release a lock? NOTREACHED(); return lock_err; } -#endif // !OS_FUCHSIA +#endif // !BUILDFLAG(IS_FUCHSIA) locked_files_.erase(it); } diff --git a/chromium/components/services/filesystem/util.cc b/chromium/components/services/filesystem/util.cc index 1aef24d61b7..ddfdc58bfdb 100644 --- a/chromium/components/services/filesystem/util.cc +++ b/chromium/components/services/filesystem/util.cc @@ -15,7 +15,7 @@ #include "base/strings/string_util.h" #include "build/build_config.h" -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) #include "base/strings/utf_string_conversions.h" #endif @@ -97,9 +97,9 @@ base::File::Error ValidatePath(const std::string& raw_path, if (!base::IsStringUTF8(raw_path)) return base::File::Error::FILE_ERROR_INVALID_OPERATION; -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) base::FilePath::StringType path = base::UTF8ToWide(raw_path); -#elif defined(OS_POSIX) || defined(OS_FUCHSIA) +#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) base::FilePath::StringType path = raw_path; #endif diff --git a/chromium/components/services/font/BUILD.gn b/chromium/components/services/font/BUILD.gn index b065131e035..1d4965449a5 100644 --- a/chromium/components/services/font/BUILD.gn +++ b/chromium/components/services/font/BUILD.gn @@ -16,6 +16,7 @@ source_set("lib") { deps = [ "//base", + "//build:chromeos_buildflags", "//components/services/font/public/mojom", "//mojo/public/cpp/bindings", "//mojo/public/cpp/system", diff --git a/chromium/components/services/font/font_service_app.cc b/chromium/components/services/font/font_service_app.cc index 4bbeebcb52c..32adf6f3c5c 100644 --- a/chromium/components/services/font/font_service_app.cc +++ b/chromium/components/services/font/font_service_app.cc @@ -8,10 +8,12 @@ #include "base/bind.h" #include "base/command_line.h" +#include "base/feature_list.h" #include "base/files/file.h" #include "base/files/file_path.h" #include "base/trace_event/trace_event.h" #include "build/build_config.h" +#include "build/chromeos_buildflags.h" #include "components/services/font/fontconfig_matching.h" #include "mojo/public/cpp/system/platform_handle.h" #include "ppapi/buildflags/buildflags.h" @@ -77,11 +79,24 @@ font_service::mojom::RenderStyleSwitch ConvertSubpixelRendering( return font_service::mojom::RenderStyleSwitch::NO_PREFERENCE; } +// A feature that controls whether we use a cache for font family matching. +const base::Feature kCacheFontFamilyMatching { + "CacheFontFamilyMatching", +#if BUILDFLAG(IS_CHROMEOS_LACROS) + base::FEATURE_ENABLED_BY_DEFAULT +#else + base::FEATURE_DISABLED_BY_DEFAULT +#endif +}; + +// The maximum number of entries to keep in the font family matching cache. +constexpr int kCacheFontFamilyMaxSize = 10000; + } // namespace namespace font_service { -FontServiceApp::FontServiceApp() = default; +FontServiceApp::FontServiceApp() : match_cache_(kCacheFontFamilyMaxSize) {} FontServiceApp::~FontServiceApp() = default; @@ -100,37 +115,59 @@ void FontServiceApp::MatchFamilyName(const std::string& family_name, SkFontStyle result_style; SkFontConfigInterface* fc = SkFontConfigInterface::GetSingletonDirectInterface(); - const bool r = fc->matchFamilyName( - family_name.data(), - SkFontStyle(requested_style->weight, requested_style->width, - static_cast<SkFontStyle::Slant>(requested_style->slant)), - &result_identity, &result_family, &result_style); - - if (!r) { - mojom::TypefaceStylePtr style(mojom::TypefaceStyle::New()); + SkFontStyle font_style( + requested_style->weight, requested_style->width, + static_cast<SkFontStyle::Slant>(requested_style->slant)); + + MatchCacheKey key; + if (base::FeatureList::IsEnabled(kCacheFontFamilyMatching)) { + key.family_name = family_name; + key.font_style = font_style; + auto it = match_cache_.Get(key); + if (it != match_cache_.end()) { + std::move(callback).Run( + it->second.identity ? it->second.identity.Clone() : nullptr, + it->second.family_name, it->second.style.Clone()); + return; + } + } + const bool r = + fc->matchFamilyName(family_name.data(), font_style, &result_identity, + &result_family, &result_style); + + mojom::FontIdentityPtr identity = nullptr; + mojom::TypefaceStylePtr style(mojom::TypefaceStyle::New()); + std::string result_family_cppstring = result_family.c_str(); + + if (r) { + // Stash away the returned path, so we can give it an ID (index) + // which will later be given to us in a request to open the file. + base::FilePath path(result_identity.fString.c_str()); + size_t index = FindOrAddPath(path); + + identity = mojom::FontIdentity::New(); + identity->id = static_cast<uint32_t>(index); + identity->ttc_index = result_identity.fTTCIndex; + identity->filepath = path; + + style->weight = result_style.weight(); + style->width = result_style.width(); + style->slant = static_cast<mojom::TypefaceSlant>(result_style.slant()); + } else { style->weight = SkFontStyle().weight(); style->width = SkFontStyle().width(); style->slant = static_cast<mojom::TypefaceSlant>(SkFontStyle().slant()); - std::move(callback).Run(nullptr, "", std::move(style)); - return; } - // Stash away the returned path, so we can give it an ID (index) - // which will later be given to us in a request to open the file. - base::FilePath path(result_identity.fString.c_str()); - size_t index = FindOrAddPath(path); - - mojom::FontIdentityPtr identity(mojom::FontIdentity::New()); - identity->id = static_cast<uint32_t>(index); - identity->ttc_index = result_identity.fTTCIndex; - identity->filepath = path; - - mojom::TypefaceStylePtr style(mojom::TypefaceStyle::New()); - style->weight = result_style.weight(); - style->width = result_style.width(); - style->slant = static_cast<mojom::TypefaceSlant>(result_style.slant()); + if (base::FeatureList::IsEnabled(kCacheFontFamilyMatching)) { + MatchCacheValue value; + value.family_name = result_family_cppstring; + value.identity = identity ? identity.Clone() : nullptr; + value.style = style.Clone(); + match_cache_.Put(key, std::move(value)); + } - std::move(callback).Run(std::move(identity), result_family.c_str(), + std::move(callback).Run(std::move(identity), result_family_cppstring, std::move(style)); } @@ -256,4 +293,8 @@ size_t FontServiceApp::FindOrAddPath(const base::FilePath& path) { return count; } +FontServiceApp::MatchCacheValue::MatchCacheValue() = default; +FontServiceApp::MatchCacheValue::~MatchCacheValue() = default; +FontServiceApp::MatchCacheValue::MatchCacheValue(MatchCacheValue&&) = default; + } // namespace font_service diff --git a/chromium/components/services/font/font_service_app.h b/chromium/components/services/font/font_service_app.h index 949551b6949..2b2aac722a4 100644 --- a/chromium/components/services/font/font_service_app.h +++ b/chromium/components/services/font/font_service_app.h @@ -6,14 +6,18 @@ #define COMPONENTS_SERVICES_FONT_FONT_SERVICE_APP_H_ #include <stdint.h> +#include <tuple> #include <vector> +#include "base/containers/lru_cache.h" #include "base/files/file_path.h" #include "components/services/font/public/mojom/font_service.mojom.h" #include "mojo/public/cpp/bindings/receiver_set.h" +#include "third_party/skia/include/core/SkFontStyle.h" namespace font_service { +// This class is instantiated in the browser process. class FontServiceApp : public mojom::FontService { public: FontServiceApp(); @@ -58,6 +62,35 @@ class FontServiceApp : public mojom::FontService { // We don't want to leak paths to our callers; we thus enumerate the paths of // fonts. std::vector<base::FilePath> paths_; + + // On some platforms, font matching is very expensive, ranging from 2-30+ms. + // We keep a cache of results to speed this up. + struct MatchCacheKey { + std::string family_name; + SkFontStyle font_style; + bool operator==(const MatchCacheKey& other) const { + return family_name == other.family_name && font_style == other.font_style; + } + }; + struct MatchCacheKeyCompare { + bool operator()(const MatchCacheKey& lhs, const MatchCacheKey& rhs) const { + return std::make_tuple(lhs.family_name, lhs.font_style.weight(), + lhs.font_style.width(), lhs.font_style.slant()) < + std::make_tuple(rhs.family_name, rhs.font_style.weight(), + rhs.font_style.width(), rhs.font_style.slant()); + } + }; + struct MatchCacheValue { + MatchCacheValue(); + ~MatchCacheValue(); + MatchCacheValue(MatchCacheValue&&); + std::string family_name; + mojom::FontIdentityPtr identity; + mojom::TypefaceStylePtr style; + }; + + base::LRUCache<MatchCacheKey, MatchCacheValue, MatchCacheKeyCompare> + match_cache_; }; } // namespace font_service diff --git a/chromium/components/services/heap_profiling/heap_profiling_service.cc b/chromium/components/services/heap_profiling/heap_profiling_service.cc index ede6ca04444..b21009e1106 100644 --- a/chromium/components/services/heap_profiling/heap_profiling_service.cc +++ b/chromium/components/services/heap_profiling/heap_profiling_service.cc @@ -9,7 +9,6 @@ #include "base/bind.h" #include "base/memory/weak_ptr.h" -#include "base/no_destructor.h" #include "base/task/post_task.h" #include "base/task/thread_pool.h" #include "components/services/heap_profiling/connection_manager.h" diff --git a/chromium/components/services/heap_profiling/json_exporter_unittest.cc b/chromium/components/services/heap_profiling/json_exporter_unittest.cc index e8a17017bb4..f6ba7ceb574 100644 --- a/chromium/components/services/heap_profiling/json_exporter_unittest.cc +++ b/chromium/components/services/heap_profiling/json_exporter_unittest.cc @@ -37,7 +37,7 @@ const base::Value* FindFirstRegionWithAnyName( if (!found_regions) return nullptr; - for (const base::Value& cur : found_regions->GetList()) { + for (const base::Value& cur : found_regions->GetListDeprecated()) { const base::Value* found_name = cur.FindKeyOfType("mf", base::Value::Type::STRING); if (!found_name) @@ -51,7 +51,7 @@ const base::Value* FindFirstRegionWithAnyName( // Looks up a given string id from the string table. Returns -1 if not found. int GetIdFromStringTable(const base::Value* strings, const char* text) { - for (const auto& string : strings->GetList()) { + for (const auto& string : strings->GetListDeprecated()) { const base::Value* string_id = string.FindKeyOfType("id", base::Value::Type::INTEGER); const base::Value* string_text = @@ -66,7 +66,7 @@ int GetIdFromStringTable(const base::Value* strings, const char* text) { // Looks up a given string from the string table. Returns empty string if not // found. std::string GetStringFromStringTable(const base::Value* strings, int sid) { - for (const auto& string : strings->GetList()) { + for (const auto& string : strings->GetListDeprecated()) { const base::Value* string_id = string.FindKeyOfType("id", base::Value::Type::INTEGER); if (string_id->GetInt() == sid) { @@ -81,7 +81,7 @@ std::string GetStringFromStringTable(const base::Value* strings, int sid) { } int GetNodeWithNameID(const base::Value* nodes, int sid) { - for (const auto& node : nodes->GetList()) { + for (const auto& node : nodes->GetListDeprecated()) { const base::Value* node_id = node.FindKeyOfType("id", base::Value::Type::INTEGER); const base::Value* node_name_sid = @@ -95,7 +95,7 @@ int GetNodeWithNameID(const base::Value* nodes, int sid) { int GetOffsetForBacktraceID(const base::Value* nodes, int id) { int offset = 0; - for (const auto& node : nodes->GetList()) { + for (const auto& node : nodes->GetListDeprecated()) { if (node.GetInt() == id) return offset; offset++; @@ -104,7 +104,7 @@ int GetOffsetForBacktraceID(const base::Value* nodes, int id) { } bool IsBacktraceInList(const base::Value* backtraces, int id, int parent) { - for (const auto& backtrace : backtraces->GetList()) { + for (const auto& backtrace : backtraces->GetListDeprecated()) { const base::Value* backtrace_id = backtrace.FindKeyOfType("id", base::Value::Type::INTEGER); if (backtrace_id == nullptr) @@ -192,7 +192,7 @@ TEST(ProfilingJsonExporterTest, Simple) { ASSERT_TRUE(strings); // Validate the strings table. - EXPECT_EQ(5u, strings->GetList().size()); + EXPECT_EQ(5u, strings->GetListDeprecated().size()); int sid_unknown = GetIdFromStringTable(strings, "[unknown]"); int sid_1234 = GetIdFromStringTable(strings, "pc:1234"); int sid_5678 = GetIdFromStringTable(strings, "pc:5678"); @@ -210,7 +210,7 @@ TEST(ProfilingJsonExporterTest, Simple) { // [1] => address: 5678 parent: 0 // [2] => address: 9012 parent: 0 // [3] => address: 9013 parent: 2 - EXPECT_EQ(4u, nodes->GetList().size()); + EXPECT_EQ(4u, nodes->GetListDeprecated().size()); int id0 = GetNodeWithNameID(nodes, sid_1234); int id1 = GetNodeWithNameID(nodes, sid_5678); int id2 = GetNodeWithNameID(nodes, sid_9012); @@ -241,9 +241,9 @@ TEST(ProfilingJsonExporterTest, Simple) { // Counts should be a list of two items, a 1 and a 2. The two matching 20-byte // allocations should be coalesced to produce the 2. - EXPECT_EQ(2u, counts->GetList().size()); - EXPECT_EQ(2u, types->GetList().size()); - EXPECT_EQ(2u, sizes->GetList().size()); + EXPECT_EQ(2u, counts->GetListDeprecated().size()); + EXPECT_EQ(2u, types->GetListDeprecated().size()); + EXPECT_EQ(2u, sizes->GetListDeprecated().size()); int node1 = GetOffsetForBacktraceID(backtraces, id1); int node3 = GetOffsetForBacktraceID(backtraces, id3); @@ -251,16 +251,16 @@ TEST(ProfilingJsonExporterTest, Simple) { EXPECT_NE(-1, node3); // Validate node allocated with |stack1|. - EXPECT_EQ(2, counts->GetList()[node1].GetInt()); - EXPECT_EQ(0, types->GetList()[node1].GetInt()); - EXPECT_EQ(40, sizes->GetList()[node1].GetInt()); - EXPECT_EQ(id1, backtraces->GetList()[node1].GetInt()); + EXPECT_EQ(2, counts->GetListDeprecated()[node1].GetInt()); + EXPECT_EQ(0, types->GetListDeprecated()[node1].GetInt()); + EXPECT_EQ(40, sizes->GetListDeprecated()[node1].GetInt()); + EXPECT_EQ(id1, backtraces->GetListDeprecated()[node1].GetInt()); // Validate node allocated with |stack2|. - EXPECT_EQ(2, counts->GetList()[node3].GetInt()); - EXPECT_EQ(0, types->GetList()[node3].GetInt()); - EXPECT_EQ(44, sizes->GetList()[node3].GetInt()); - EXPECT_EQ(id3, backtraces->GetList()[node3].GetInt()); + EXPECT_EQ(2, counts->GetListDeprecated()[node3].GetInt()); + EXPECT_EQ(0, types->GetListDeprecated()[node3].GetInt()); + EXPECT_EQ(44, sizes->GetListDeprecated()[node3].GetInt()); + EXPECT_EQ(id3, backtraces->GetListDeprecated()[node3].GetInt()); // Validate that the partition alloc one got through. counts = heaps_v2->FindPath({"allocators", "partition_alloc", "counts"}); @@ -274,9 +274,9 @@ TEST(ProfilingJsonExporterTest, Simple) { ASSERT_TRUE(backtraces); // There should just be one entry for the partition_alloc allocation. - EXPECT_EQ(1u, counts->GetList().size()); - EXPECT_EQ(1u, types->GetList().size()); - EXPECT_EQ(1u, sizes->GetList().size()); + EXPECT_EQ(1u, counts->GetListDeprecated().size()); + EXPECT_EQ(1u, types->GetListDeprecated().size()); + EXPECT_EQ(1u, sizes->GetListDeprecated().size()); } // GetProcessMemoryMaps iterates through every memory region, making allocations @@ -354,8 +354,8 @@ TEST(ProfilingJsonExporterTest, Context) { heaps_v2->FindPath({"allocators", "partition_alloc", "types"}); ASSERT_TRUE(types); - const auto& counts_list = counts->GetList(); - const auto& types_list = types->GetList(); + const auto& counts_list = counts->GetListDeprecated(); + const auto& types_list = types->GetListDeprecated(); // There should be three allocations, two coalesced ones, one with unique // context, and one with no context. @@ -369,7 +369,7 @@ TEST(ProfilingJsonExporterTest, Context) { // Reconstruct the map from type id to string. std::map<int, std::string> type_to_string; - for (const auto& type : types_map->GetList()) { + for (const auto& type : types_map->GetListDeprecated()) { const base::Value* id = type.FindKeyOfType("id", base::Value::Type::INTEGER); ASSERT_TRUE(id); @@ -447,8 +447,8 @@ TEST(ProfilingJsonExporterTest, LargeAllocation) { const base::Value* malloc = parsed_json.value->FindPath({"heaps_v2", "allocators", "malloc"}); const base::Value* malloc_sizes = malloc->FindKey("sizes"); - EXPECT_EQ(1u, malloc_sizes->GetList().size()); - EXPECT_EQ(0x9876543210ul, malloc_sizes->GetList()[0].GetDouble()); + EXPECT_EQ(1u, malloc_sizes->GetListDeprecated().size()); + EXPECT_EQ(0x9876543210ul, malloc_sizes->GetListDeprecated()[0].GetDouble()); } #endif diff --git a/chromium/components/services/heap_profiling/public/cpp/heap_profiling_trace_source.cc b/chromium/components/services/heap_profiling/public/cpp/heap_profiling_trace_source.cc index 31391d6b0d5..1860fe83ded 100644 --- a/chromium/components/services/heap_profiling/public/cpp/heap_profiling_trace_source.cc +++ b/chromium/components/services/heap_profiling/public/cpp/heap_profiling_trace_source.cc @@ -4,6 +4,7 @@ #include "components/services/heap_profiling/public/cpp/heap_profiling_trace_source.h" +#include "base/no_destructor.h" #include "base/profiler/frame.h" #include "base/profiler/module_cache.h" #include "base/trace_event/trace_event.h" diff --git a/chromium/components/services/heap_profiling/public/cpp/profiling_client.cc b/chromium/components/services/heap_profiling/public/cpp/profiling_client.cc index 72d2d2016be..964890d3773 100644 --- a/chromium/components/services/heap_profiling/public/cpp/profiling_client.cc +++ b/chromium/components/services/heap_profiling/public/cpp/profiling_client.cc @@ -21,16 +21,16 @@ #include "base/trace_event/memory_dump_manager.h" #include "build/build_config.h" -#if !defined(OS_IOS) +#if !BUILDFLAG(IS_IOS) #include "components/services/heap_profiling/public/cpp/heap_profiling_trace_source.h" #endif -#if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) && \ +#if BUILDFLAG(IS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) && \ defined(OFFICIAL_BUILD) #include "base/trace_event/cfi_backtrace_android.h" #endif -#if defined(OS_APPLE) +#if BUILDFLAG(IS_APPLE) #include "base/allocator/allocator_interception_mac.h" #endif @@ -51,14 +51,14 @@ void ProfilingClient::StartProfiling(mojom::ProfilingParamsPtr params, started_profiling_ = true; base::trace_event::MallocDumpProvider::GetInstance()->DisableMetrics(); -#if defined(OS_APPLE) +#if BUILDFLAG(IS_APPLE) // On macOS, this call is necessary to shim malloc zones that were created // after startup. This cannot be done during shim initialization because the // task scheduler has not yet been initialized. base::allocator::PeriodicallyShimNewMallocZones(); #endif -#if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) && \ +#if BUILDFLAG(IS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) && \ defined(OFFICIAL_BUILD) // On Android the unwinder initialization requires file reading before // initializing shim. So, post task on background thread. @@ -78,7 +78,7 @@ void ProfilingClient::StartProfiling(mojom::ProfilingParamsPtr params, StartProfilingInternal(std::move(params), std::move(callback)); #endif -#if !defined(OS_IOS) +#if !BUILDFLAG(IS_IOS) // Create trace source so that it registers itself to the tracing system. HeapProfilingTraceSource::GetInstance(); #endif @@ -215,7 +215,7 @@ void ProfilingClient::AddHeapProfileToTrace( std::vector<base::SamplingHeapProfiler::Sample> samples = profiler->GetSamples(/*profile_id=*/0); -#if !defined(OS_IOS) +#if !BUILDFLAG(IS_IOS) bool success = HeapProfilingTraceSource::GetInstance()->AddToTraceIfEnabled(samples); #else diff --git a/chromium/components/services/paint_preview_compositor/paint_preview_compositor_collection_impl.cc b/chromium/components/services/paint_preview_compositor/paint_preview_compositor_collection_impl.cc index cfae6933194..7b54280f3c3 100644 --- a/chromium/components/services/paint_preview_compositor/paint_preview_compositor_collection_impl.cc +++ b/chromium/components/services/paint_preview_compositor/paint_preview_compositor_collection_impl.cc @@ -17,9 +17,9 @@ #include "third_party/skia/include/core/SkGraphics.h" #include "third_party/skia/include/ports/SkFontConfigInterface.h" -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) #include "content/public/child/dwrite_font_proxy_init_win.h" -#elif defined(OS_LINUX) || defined(OS_CHROMEOS) +#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) #include "components/services/font/public/cpp/font_loader.h" #endif @@ -48,7 +48,7 @@ PaintPreviewCompositorCollectionImpl::PaintPreviewCompositorCollectionImpl( // Adapted from content::InitializeSkia(). // TODO(crbug/1199857): Tune these limits. constexpr int kMB = 1024 * 1024; -#if defined(OS_ANDROID) +#if BUILDFLAG(IS_ANDROID) SkGraphics::SetFontCacheLimit(base::SysInfo::IsLowEndDevice() ? kMB : 8 * kMB); SkGraphics::SetResourceCacheTotalByteLimit( @@ -56,15 +56,15 @@ PaintPreviewCompositorCollectionImpl::PaintPreviewCompositorCollectionImpl( SkGraphics::SetResourceCacheSingleAllocationByteLimit(16 * kMB); #else SkGraphics::SetResourceCacheSingleAllocationByteLimit(64 * kMB); -#endif // defined(OS_ANDROID) +#endif // BUILDFLAG(IS_ANDROID) if (!initialize_environment_) return; // Initialize font access for Skia. -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) content::InitializeDWriteFontProxy(); -#elif defined(OS_LINUX) || defined(OS_CHROMEOS) +#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) mojo::PendingRemote<font_service::mojom::FontService> font_service; content::UtilityThread::Get()->BindHostReceiver( font_service.InitWithNewPipeAndPassReceiver()); @@ -87,7 +87,7 @@ PaintPreviewCompositorCollectionImpl::PaintPreviewCompositorCollectionImpl( base::BindOnce([] { SkFontMgr::RefDefault(); })); // Sanity check that fonts are working. -#if defined(OS_LINUX) || defined(OS_CHROMEOS) +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) // No WebSandbox is provided on Linux so the local fonts aren't accessible. // This is fine since since the subsetted fonts are provided in the SkPicture. // However, we still need to check that the SkFontMgr starts as it is used by @@ -100,7 +100,7 @@ PaintPreviewCompositorCollectionImpl::PaintPreviewCompositorCollectionImpl( PaintPreviewCompositorCollectionImpl::~PaintPreviewCompositorCollectionImpl() { g_in_shutdown_key.Set("true"); -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) content::UninitializeDWriteFontProxy(); #endif } diff --git a/chromium/components/services/paint_preview_compositor/paint_preview_compositor_collection_impl.h b/chromium/components/services/paint_preview_compositor/paint_preview_compositor_collection_impl.h index 7c09acbe61e..a7f8fe5d35b 100644 --- a/chromium/components/services/paint_preview_compositor/paint_preview_compositor_collection_impl.h +++ b/chromium/components/services/paint_preview_compositor/paint_preview_compositor_collection_impl.h @@ -21,7 +21,7 @@ #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/receiver.h" -#if defined(OS_LINUX) || defined(OS_CHROMEOS) +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) #include "components/services/font/public/cpp/font_loader.h" #include "third_party/skia/include/core/SkRefCnt.h" #endif @@ -73,7 +73,7 @@ class PaintPreviewCompositorCollectionImpl std::unique_ptr<PaintPreviewCompositorImpl>> compositors_; -#if defined(OS_LINUX) || defined(OS_CHROMEOS) +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) sk_sp<font_service::FontLoader> font_loader_; #endif diff --git a/chromium/components/services/patch/content/patch_service.cc b/chromium/components/services/patch/content/patch_service.cc index 1c8ff4fc446..d384c3cb559 100644 --- a/chromium/components/services/patch/content/patch_service.cc +++ b/chromium/components/services/patch/content/patch_service.cc @@ -4,7 +4,6 @@ #include "components/services/patch/content/patch_service.h" -#include "base/no_destructor.h" #include "components/services/patch/public/mojom/file_patcher.mojom.h" #include "components/strings/grit/components_strings.h" #include "content/public/browser/service_process_host.h" diff --git a/chromium/components/services/print_compositor/print_compositor_impl.cc b/chromium/components/services/print_compositor/print_compositor_impl.cc index 8f6f77381fb..e3739baf49f 100644 --- a/chromium/components/services/print_compositor/print_compositor_impl.cc +++ b/chromium/components/services/print_compositor/print_compositor_impl.cc @@ -29,12 +29,12 @@ #include "third_party/skia/src/utils/SkMultiPictureDocument.h" #include "ui/accessibility/ax_tree_update.h" -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) #include "content/public/child/dwrite_font_proxy_init_win.h" -#elif defined(OS_APPLE) +#elif BUILDFLAG(IS_APPLE) #include "third_party/blink/public/platform/platform.h" #include "third_party/skia/include/core/SkFontMgr.h" -#elif defined(OS_POSIX) && !defined(OS_ANDROID) +#elif BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID) #include "third_party/blink/public/platform/platform.h" #endif @@ -51,7 +51,7 @@ PrintCompositorImpl::PrintCompositorImpl( if (!initialize_environment) return; -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) // Initialize direct write font proxy so skia can use it. content::InitializeDWriteFontProxy(); #endif @@ -60,7 +60,7 @@ PrintCompositorImpl::PrintCompositorImpl( SkGraphics::SetImageGeneratorFromEncodedDataFactory( blink::WebImageGenerator::CreateAsSkImageGenerator); -#if defined(OS_POSIX) && !defined(OS_ANDROID) +#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID) content::UtilityThread::Get()->EnsureBlinkInitializedWithSandboxSupport(); // Check that we have sandbox support on this platform. DCHECK(blink::Platform::Current()->GetSandboxSupport()); @@ -68,7 +68,7 @@ PrintCompositorImpl::PrintCompositorImpl( content::UtilityThread::Get()->EnsureBlinkInitialized(); #endif -#if defined(OS_APPLE) +#if BUILDFLAG(IS_APPLE) // Check that font access is granted. // This doesn't do comprehensive tests to make sure fonts can work properly. // It is just a quick and simple check to catch things like improper sandbox @@ -78,7 +78,7 @@ PrintCompositorImpl::PrintCompositorImpl( } PrintCompositorImpl::~PrintCompositorImpl() { -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) content::UninitializeDWriteFontProxy(); #endif } diff --git a/chromium/components/services/quarantine/BUILD.gn b/chromium/components/services/quarantine/BUILD.gn index 4411ebbb4a4..d8d69e696ba 100644 --- a/chromium/components/services/quarantine/BUILD.gn +++ b/chromium/components/services/quarantine/BUILD.gn @@ -27,7 +27,6 @@ static_library("quarantine") { if (is_win) { sources += [ "quarantine_win.cc" ] - deps += [ "//components/services/quarantine/public/cpp:features" ] } if (is_mac) { @@ -136,7 +135,6 @@ source_set("unit_tests") { if (is_win) { sources += [ "quarantine_win_unittest.cc" ] - deps += [ "//components/services/quarantine/public/cpp:features" ] } if (is_mac) { diff --git a/chromium/components/services/quarantine/OWNERS b/chromium/components/services/quarantine/OWNERS index 15eb1078310..2a42cbefb8f 100644 --- a/chromium/components/services/quarantine/OWNERS +++ b/chromium/components/services/quarantine/OWNERS @@ -1,2 +1 @@ -asanka@chromium.org wfh@chromium.org diff --git a/chromium/components/services/quarantine/README.md b/chromium/components/services/quarantine/README.md index 8caf099c5e2..b23584c478e 100644 --- a/chromium/components/services/quarantine/README.md +++ b/chromium/components/services/quarantine/README.md @@ -1,6 +1,4 @@ Quarantine service to scan/mark a downloaded file with a mark-of-the-web. -The service will run in browser process except for Windows -with kOutOfProcessQuarantine flag set, where will run in a utility process - -TODO: Implement, add call sites, and refactor components/download/quarantine. +The service will run in browser process except for Windows, where will run in a +utility process diff --git a/chromium/components/services/quarantine/public/cpp/BUILD.gn b/chromium/components/services/quarantine/public/cpp/BUILD.gn deleted file mode 100644 index 40fab13c662..00000000000 --- a/chromium/components/services/quarantine/public/cpp/BUILD.gn +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2019 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import("//mojo/public/tools/bindings/mojom.gni") - -if (is_win) { - source_set("features") { - sources = [ - "quarantine_features_win.cc", - "quarantine_features_win.h", - ] - - public_deps = [ "//base" ] - } -} diff --git a/chromium/components/services/quarantine/public/cpp/quarantine_features_win.cc b/chromium/components/services/quarantine/public/cpp/quarantine_features_win.cc deleted file mode 100644 index 47bf5a470f0..00000000000 --- a/chromium/components/services/quarantine/public/cpp/quarantine_features_win.cc +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "components/services/quarantine/public/cpp/quarantine_features_win.h" - -namespace quarantine { - -// This feature controls whether the quarantine service should run in -// the browser process or a new utility process. -// Unused until quarantine service is fully implemented. -const base::Feature kOutOfProcessQuarantine{"OutOfProcessQuarantine", - base::FEATURE_ENABLED_BY_DEFAULT}; - -} // namespace quarantine diff --git a/chromium/components/services/quarantine/public/cpp/quarantine_features_win.h b/chromium/components/services/quarantine/public/cpp/quarantine_features_win.h deleted file mode 100644 index b97fd1e6535..00000000000 --- a/chromium/components/services/quarantine/public/cpp/quarantine_features_win.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_SERVICES_QUARANTINE_PUBLIC_CPP_QUARANTINE_FEATURES_WIN_H_ -#define COMPONENTS_SERVICES_QUARANTINE_PUBLIC_CPP_QUARANTINE_FEATURES_WIN_H_ - -#include "base/feature_list.h" - -namespace quarantine { - -extern const base::Feature kOutOfProcessQuarantine; - -} // namespace quarantine - -#endif // COMPONENTS_SERVICES_QUARANTINE_PUBLIC_CPP_QUARANTINE_FEATURES_WIN_H_ diff --git a/chromium/components/services/quarantine/quarantine.cc b/chromium/components/services/quarantine/quarantine.cc index 83e907cbfb4..84215bc6fa0 100644 --- a/chromium/components/services/quarantine/quarantine.cc +++ b/chromium/components/services/quarantine/quarantine.cc @@ -6,7 +6,7 @@ #include "build/build_config.h" -#if !defined(OS_WIN) && !defined(OS_APPLE) && !defined(OS_CHROMEOS) +#if !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_CHROMEOS) namespace quarantine { diff --git a/chromium/components/services/quarantine/quarantine.h b/chromium/components/services/quarantine/quarantine.h index 949a17921ba..be1b49a881b 100644 --- a/chromium/components/services/quarantine/quarantine.h +++ b/chromium/components/services/quarantine/quarantine.h @@ -73,7 +73,7 @@ void QuarantineFile(const base::FilePath& file, const std::string& client_guid, mojom::Quarantine::QuarantineFileCallback callback); -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) QuarantineFileResult SetInternetZoneIdentifierDirectly( const base::FilePath& full_path, const GURL& source_url, diff --git a/chromium/components/services/quarantine/quarantine_impl.cc b/chromium/components/services/quarantine/quarantine_impl.cc index 5fcd99151c8..63339384418 100644 --- a/chromium/components/services/quarantine/quarantine_impl.cc +++ b/chromium/components/services/quarantine/quarantine_impl.cc @@ -7,6 +7,7 @@ #include "base/bind.h" #include "base/task/post_task.h" #include "base/task/thread_pool.h" +#include "build/build_config.h" #include "components/services/quarantine/quarantine.h" namespace quarantine { @@ -35,16 +36,16 @@ void QuarantineImpl::QuarantineFile( const GURL& referrer_url, const std::string& client_guid, mojom::Quarantine::QuarantineFileCallback callback) { -#if defined(OS_MAC) +#if BUILDFLAG(IS_MAC) // On Mac posting to a new task runner to do the potentially blocking // quarantine work. scoped_refptr<base::TaskRunner> task_runner = base::ThreadPool::CreateTaskRunner( {base::MayBlock(), base::TaskPriority::USER_VISIBLE}); -#else // OS_MAC +#else // BUILDFLAG(IS_MAC) scoped_refptr<base::TaskRunner> task_runner = base::ThreadTaskRunnerHandle::Get(); -#endif // OS_MAC +#endif // BUILDFLAG(IS_MAC) task_runner->PostTask( FROM_HERE, base::BindOnce( diff --git a/chromium/components/services/quarantine/quarantine_impl.h b/chromium/components/services/quarantine/quarantine_impl.h index 877414bb3c1..87dbc72b11a 100644 --- a/chromium/components/services/quarantine/quarantine_impl.h +++ b/chromium/components/services/quarantine/quarantine_impl.h @@ -12,9 +12,9 @@ #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/receiver.h" -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) #include "base/win/scoped_com_initializer.h" -#endif // OS_WIN +#endif // BUILDFLAG(IS_WIN) namespace quarantine { @@ -39,10 +39,10 @@ class QuarantineImpl : public mojom::Quarantine { private: mojo::Receiver<mojom::Quarantine> receiver_{this}; -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) base::win::ScopedCOMInitializer com_initializer_{ base::win::ScopedCOMInitializer::Uninitialization::kBlockPremature}; -#endif // OS_WIN +#endif // BUILDFLAG(IS_WIN) }; } // namespace quarantine diff --git a/chromium/components/services/quarantine/quarantine_unittest.cc b/chromium/components/services/quarantine/quarantine_unittest.cc index 052430ab8f9..4701858bcb7 100644 --- a/chromium/components/services/quarantine/quarantine_unittest.cc +++ b/chromium/components/services/quarantine/quarantine_unittest.cc @@ -18,7 +18,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) #include "base/win/scoped_com_initializer.h" #endif @@ -39,7 +39,7 @@ void CheckQuarantineResult(QuarantineFileResult result, class QuarantineTest : public testing::Test { public: void SetUp() override { -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) ASSERT_TRUE(com_initializer_.Succeeded()); #endif ASSERT_TRUE(test_dir_.CreateUniqueTempDir()); @@ -54,7 +54,7 @@ class QuarantineTest : public testing::Test { } private: -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) base::win::ScopedCOMInitializer com_initializer_; #endif base::test::SingleThreadTaskEnvironment task_environment_; diff --git a/chromium/components/services/quarantine/quarantine_win.cc b/chromium/components/services/quarantine/quarantine_win.cc index e4cd1da3d70..010bb335d5e 100644 --- a/chromium/components/services/quarantine/quarantine_win.cc +++ b/chromium/components/services/quarantine/quarantine_win.cc @@ -29,7 +29,6 @@ #include "base/win/windows_version.h" #include "components/services/quarantine/common.h" #include "components/services/quarantine/common_win.h" -#include "components/services/quarantine/public/cpp/quarantine_features_win.h" #include "url/gurl.h" namespace quarantine { diff --git a/chromium/components/services/quarantine/quarantine_win_unittest.cc b/chromium/components/services/quarantine/quarantine_win_unittest.cc index 3c38a6c2220..12540ae6899 100644 --- a/chromium/components/services/quarantine/quarantine_win_unittest.cc +++ b/chromium/components/services/quarantine/quarantine_win_unittest.cc @@ -19,7 +19,6 @@ #include "base/win/scoped_com_initializer.h" #include "base/win/win_util.h" #include "base/win/windows_version.h" -#include "components/services/quarantine/public/cpp/quarantine_features_win.h" #include "components/services/quarantine/quarantine.h" #include "components/services/quarantine/test_support.h" #include "net/base/filename_util.h" diff --git a/chromium/components/services/quarantine/test_support.cc b/chromium/components/services/quarantine/test_support.cc index 1bd7bc3b4f4..a2a09fbee61 100644 --- a/chromium/components/services/quarantine/test_support.cc +++ b/chromium/components/services/quarantine/test_support.cc @@ -6,7 +6,7 @@ #include "build/build_config.h" -#if !defined(OS_WIN) && !defined(OS_APPLE) +#if !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_APPLE) namespace quarantine { diff --git a/chromium/components/services/storage/BUILD.gn b/chromium/components/services/storage/BUILD.gn index 007edf013a8..00ed503c1d1 100644 --- a/chromium/components/services/storage/BUILD.gn +++ b/chromium/components/services/storage/BUILD.gn @@ -77,6 +77,12 @@ source_set("storage") { "service_worker/service_worker_storage.h", "service_worker/service_worker_storage_control_impl.cc", "service_worker/service_worker_storage_control_impl.h", + "shared_storage/async_shared_storage_database.cc", + "shared_storage/async_shared_storage_database.h", + "shared_storage/shared_storage_database.cc", + "shared_storage/shared_storage_database.h", + "shared_storage/shared_storage_options.cc", + "shared_storage/shared_storage_options.h", "storage_service_impl.cc", "storage_service_impl.h", ] @@ -90,6 +96,7 @@ source_set("storage") { "//components/services/storage/service_worker:service_worker_proto", "//mojo/public/cpp/bindings", "//sql", + "//third_party/abseil-cpp:absl", "//third_party/blink/public/common", "//third_party/leveldatabase", "//url", @@ -101,6 +108,7 @@ source_set("storage") { "//net", "//services/network/public/cpp", "//services/network/public/mojom", + "//storage/browser", "//storage/common", ] } @@ -136,6 +144,18 @@ component("test_api_stubs") { defines = [ "IS_STORAGE_SERVICE_TEST_API_STUBS_IMPL" ] } +bundle_data("tests_bundle_data") { + visibility = [ ":tests" ] + testonly = true + sources = [ + "//components/test/data/storage/shared_storage.v0.init_too_old.sql", + "//components/test/data/storage/shared_storage.v1.init_too_new.sql", + "//components/test/data/storage/shared_storage.v1.sql", + ] + outputs = [ "{{bundle_resources_dir}}/" + + "{{source_root_relative_dir}}/{{source_file_part}}" ] +} + source_set("tests") { testonly = true @@ -164,12 +184,15 @@ source_set("tests") { "service_worker/service_worker_storage_test_utils.cc", "service_worker/service_worker_storage_test_utils.h", "service_worker/service_worker_storage_unittest.cc", + "shared_storage/async_shared_storage_database_unittest.cc", + "shared_storage/shared_storage_database_unittest.cc", "storage_service_impl_unittest.cc", ] deps = [ ":storage", ":test_support", + ":tests_bundle_data", "//base", "//base/test:test_support", "//components/services/storage/public/cpp", @@ -180,8 +203,13 @@ source_set("tests") { "//mojo/public/cpp/system", "//net", "//net:test_support", + "//sql", + "//sql:test_support", + "//storage/browser:browser", + "//storage/browser:test_support", "//testing/gmock", "//testing/gtest", + "//third_party/sqlite", ] data = [ "//components/services/storage/test_data/" ] @@ -201,11 +229,14 @@ source_set("test_support") { "indexed_db/leveldb/mock_level_db.h", "indexed_db/scopes/leveldb_scopes_test_utils.cc", "indexed_db/scopes/leveldb_scopes_test_utils.h", + "shared_storage/shared_storage_test_utils.cc", + "shared_storage/shared_storage_test_utils.h", ] deps = [ ":storage", "//base/test:test_support", + "//sql:test_support", "//testing/gmock", "//testing/gtest", "//third_party/leveldatabase", diff --git a/chromium/components/services/storage/DEPS b/chromium/components/services/storage/DEPS index fadaaf00f3c..8c47aae7a79 100644 --- a/chromium/components/services/storage/DEPS +++ b/chromium/components/services/storage/DEPS @@ -4,4 +4,6 @@ include_rules = [ "+third_party/blink/public/mojom", "+third_party/leveldatabase", "+sql", + "+storage/browser/quota/special_storage_policy.h", + "+storage/browser/test/mock_special_storage_policy.h", ] diff --git a/chromium/components/services/storage/dom_storage/local_storage_impl.cc b/chromium/components/services/storage/dom_storage/local_storage_impl.cc index 749ba0bb361..82a90a61105 100644 --- a/chromium/components/services/storage/dom_storage/local_storage_impl.cc +++ b/chromium/components/services/storage/dom_storage/local_storage_impl.cc @@ -79,7 +79,7 @@ const int kCommitErrorThreshold = 8; // Limits on the cache size and number of areas in memory, over which the areas // are purged. -#if defined(OS_ANDROID) +#if BUILDFLAG(IS_ANDROID) const unsigned kMaxLocalStorageAreaCount = 10; const size_t kMaxLocalStorageCacheSize = 2 * 1024 * 1024; #else @@ -213,7 +213,7 @@ class LocalStorageImpl::StorageAreaHolder final options.default_commit_delay = kCommitDefaultDelaySecs; options.max_bytes_per_hour = kMaxBytesPerHour; options.max_commits_per_hour = kMaxCommitsPerHour; -#if defined(OS_ANDROID) +#if BUILDFLAG(IS_ANDROID) options.cache_mode = StorageAreaImpl::CacheMode::KEYS_ONLY_WHEN_POSSIBLE; #else options.cache_mode = StorageAreaImpl::CacheMode::KEYS_AND_VALUES; diff --git a/chromium/components/services/storage/dom_storage/local_storage_impl_unittest.cc b/chromium/components/services/storage/dom_storage/local_storage_impl_unittest.cc index 89ddf64633f..3b617b6302c 100644 --- a/chromium/components/services/storage/dom_storage/local_storage_impl_unittest.cc +++ b/chromium/components/services/storage/dom_storage/local_storage_impl_unittest.cc @@ -4,13 +4,14 @@ #include "components/services/storage/dom_storage/local_storage_impl.h" +#include <tuple> + #include "base/bind.h" #include "base/callback_helpers.h" #include "base/containers/span.h" #include "base/files/file_enumerator.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" -#include "base/ignore_result.h" #include "base/run_loop.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" @@ -256,7 +257,7 @@ class LocalStorageImplTest : public testing::Test { base::RunLoop run_loop; std::vector<blink::mojom::KeyValuePtr> data; mojo::PendingRemote<blink::mojom::StorageAreaObserver> unused_observer; - ignore_result(unused_observer.InitWithNewPipeAndPassReceiver()); + std::ignore = unused_observer.InitWithNewPipeAndPassReceiver(); area->GetAll(std::move(unused_observer), test::MakeGetAllCallback(run_loop.QuitClosure(), &data)); run_loop.Run(); diff --git a/chromium/components/services/storage/dom_storage/session_storage_data_map_unittest.cc b/chromium/components/services/storage/dom_storage/session_storage_data_map_unittest.cc index b798967fd47..5d3362c5473 100644 --- a/chromium/components/services/storage/dom_storage/session_storage_data_map_unittest.cc +++ b/chromium/components/services/storage/dom_storage/session_storage_data_map_unittest.cc @@ -5,11 +5,11 @@ #include "components/services/storage/dom_storage/session_storage_data_map.h" #include <map> +#include <tuple> #include <vector> #include "base/bind.h" #include "base/containers/span.h" -#include "base/ignore_result.h" #include "base/memory/ptr_util.h" #include "base/run_loop.h" #include "base/task/post_task.h" @@ -41,7 +41,7 @@ base::span<const uint8_t> MakeBytes(base::StringPiece str) { mojo::PendingRemote<blink::mojom::StorageAreaObserver> MakeStubObserver() { mojo::PendingRemote<blink::mojom::StorageAreaObserver> observer; - ignore_result(observer.InitWithNewPipeAndPassReceiver()); + std::ignore = observer.InitWithNewPipeAndPassReceiver(); return observer; } diff --git a/chromium/components/services/storage/dom_storage/session_storage_impl.cc b/chromium/components/services/storage/dom_storage/session_storage_impl.cc index 70bd2345f7c..80aecec0563 100644 --- a/chromium/components/services/storage/dom_storage/session_storage_impl.cc +++ b/chromium/components/services/storage/dom_storage/session_storage_impl.cc @@ -41,7 +41,7 @@ const int kSessionStorageCommitErrorThreshold = 8; // Limits on the cache size and number of areas in memory, over which the areas // are purged. -#if defined(OS_ANDROID) +#if BUILDFLAG(IS_ANDROID) const unsigned kMaxSessionStorageAreaCount = 10; const size_t kMaxSessionStorageCacheSize = 2 * 1024 * 1024; #else diff --git a/chromium/components/services/storage/dom_storage/storage_area_impl_unittest.cc b/chromium/components/services/storage/dom_storage/storage_area_impl_unittest.cc index 502c7b980bc..299d5380ab0 100644 --- a/chromium/components/services/storage/dom_storage/storage_area_impl_unittest.cc +++ b/chromium/components/services/storage/dom_storage/storage_area_impl_unittest.cc @@ -7,13 +7,13 @@ #include <list> #include <memory> #include <string> +#include <tuple> #include <vector> #include "base/atomic_ref_count.h" #include "base/bind.h" #include "base/callback_helpers.h" #include "base/containers/span.h" -#include "base/ignore_result.h" #include "base/memory/ptr_util.h" #include "base/run_loop.h" #include "base/strings/string_number_conversions.h" @@ -903,7 +903,7 @@ TEST_F(StorageAreaImplTest, GetAllWhenCacheOnlyKeys) { MakeSuccessCallback(barrier.AddClosure(), &put_result1)); mojo::PendingRemote<blink::mojom::StorageAreaObserver> unused_observer; - ignore_result(unused_observer.InitWithNewPipeAndPassReceiver()); + std::ignore = unused_observer.InitWithNewPipeAndPassReceiver(); storage_area()->GetAll(std::move(unused_observer), MakeGetAllCallback(barrier.AddClosure(), &data)); storage_area()->Put( @@ -974,7 +974,7 @@ TEST_F(StorageAreaImplTest, GetAllAfterSetCacheMode) { upgrade_loop.Run(); mojo::PendingRemote<blink::mojom::StorageAreaObserver> unused_observer; - ignore_result(unused_observer.InitWithNewPipeAndPassReceiver()); + std::ignore = unused_observer.InitWithNewPipeAndPassReceiver(); storage_area()->GetAll( std::move(unused_observer), MakeGetAllCallback(upgrade_loop.QuitClosure(), &data)); diff --git a/chromium/components/services/storage/dom_storage/testing_legacy_session_storage_database.cc b/chromium/components/services/storage/dom_storage/testing_legacy_session_storage_database.cc index 29cd8d1609f..ecd979bc3e1 100644 --- a/chromium/components/services/storage/dom_storage/testing_legacy_session_storage_database.cc +++ b/chromium/components/services/storage/dom_storage/testing_legacy_session_storage_database.cc @@ -499,7 +499,7 @@ leveldb::Status TestingLegacySessionStorageDatabase::TryToOpen( options.block_cache = leveldb_chrome::GetSharedWebBlockCache(); std::string db_name = file_path_.AsUTF8Unsafe(); -#if defined(OS_ANDROID) +#if BUILDFLAG(IS_ANDROID) // On Android there is no support for session storage restoring, and since // the restoring code is responsible for database cleanup, we must manually // delete the old database here before we open it. diff --git a/chromium/components/services/storage/indexed_db/leveldb/leveldb_factory.cc b/chromium/components/services/storage/indexed_db/leveldb/leveldb_factory.cc index c4082447fb4..dd3646d6e61 100644 --- a/chromium/components/services/storage/indexed_db/leveldb/leveldb_factory.cc +++ b/chromium/components/services/storage/indexed_db/leveldb/leveldb_factory.cc @@ -50,16 +50,13 @@ std::tuple<scoped_refptr<LevelDBState>, DefaultLevelDBFactory::OpenLevelDBState(const base::FilePath& file_name, bool create_if_missing, size_t write_buffer_size) { - leveldb::Status status; - std::unique_ptr<leveldb::DB> db; - if (file_name.empty()) { if (!create_if_missing) return {nullptr, leveldb::Status::NotFound("", ""), false}; std::unique_ptr<leveldb::Env> in_memory_env = leveldb_chrome::NewMemEnv(in_memory_db_name_, options_.env); - std::tie(db, status) = OpenInMemoryDB(in_memory_env.get()); + auto [db, status] = OpenInMemoryDB(in_memory_env.get()); if (UNLIKELY(!status.ok())) { LOG(ERROR) << "Failed to open in-memory LevelDB database: " << status.ToString(); @@ -73,7 +70,7 @@ DefaultLevelDBFactory::OpenLevelDBState(const base::FilePath& file_name, } // ChromiumEnv assumes UTF8, converts back to FilePath before using. - std::tie(db, status) = + auto [db, status] = OpenDB(file_name.AsUTF8Unsafe(), create_if_missing, write_buffer_size); if (UNLIKELY(!status.ok())) { if (!create_if_missing && status.IsInvalidArgument()) diff --git a/chromium/components/services/storage/indexed_db/scopes/leveldb_scope.h b/chromium/components/services/storage/indexed_db/scopes/leveldb_scope.h index 26f70a76dfc..13f5d1723f6 100644 --- a/chromium/components/services/storage/indexed_db/scopes/leveldb_scope.h +++ b/chromium/components/services/storage/indexed_db/scopes/leveldb_scope.h @@ -13,7 +13,6 @@ #include "base/callback.h" #include "base/check_op.h" -#include "base/compiler_specific.h" #include "base/containers/flat_map.h" #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.h" @@ -81,19 +80,19 @@ class LevelDBScope { return scope_id_; } - leveldb::Status Put(const leveldb::Slice& key, - const leveldb::Slice& value) WARN_UNUSED_RESULT; - leveldb::Status Delete(const leveldb::Slice& key) WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status Put(const leveldb::Slice& key, + const leveldb::Slice& value); + [[nodiscard]] leveldb::Status Delete(const leveldb::Slice& key); // Deletes the range. |begin| is always inclusive. See // |LevelDBScopeDeletionMode| for the different types of range deletion. - leveldb::Status DeleteRange(const leveldb::Slice& begin, - const leveldb::Slice& end, - LevelDBScopeDeletionMode mode) WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status DeleteRange(const leveldb::Slice& begin, + const leveldb::Slice& end, + LevelDBScopeDeletionMode mode); // Submits pending changes & the undo log to LevelDB. Required to be able to // read any keys that have been submitted to |Put|, |Delete|, or // |DeleteRange|. - leveldb::Status WriteChangesAndUndoLog() WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status WriteChangesAndUndoLog(); // In the case of LevelDBScopes being in the mode // TaskRunnerMode::kUseCurrentSequence, rollbacks happen synchronously. The @@ -148,8 +147,7 @@ class LevelDBScope { // status & the mode of this scope. The caller (LevelDBScopes) is expected to // queue up a cleanup task if the mode is kUndoLogOnDisk. This instance should // not be used after this call. - std::pair<leveldb::Status, Mode> Commit(bool sync_on_commit) - WARN_UNUSED_RESULT; + [[nodiscard]] std::pair<leveldb::Status, Mode> Commit(bool sync_on_commit); // Submits pending changes & the undo log to LevelDB. Required to be able to // read any keys that have been submitted to Put, Delete, or @@ -181,7 +179,7 @@ class LevelDBScope { bool CanSkipWritingUndoEntry(const leveldb::Slice& key); void AddCommitPoint(); - leveldb::Status WriteBufferBatch(bool sync) WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status WriteBufferBatch(bool sync); #if DCHECK_IS_ON() std::vector<std::pair<std::string, std::string>> deferred_delete_ranges_; diff --git a/chromium/components/services/storage/indexed_db/scopes/leveldb_scopes.cc b/chromium/components/services/storage/indexed_db/scopes/leveldb_scopes.cc index e45257801f0..e2d1299ba13 100644 --- a/chromium/components/services/storage/indexed_db/scopes/leveldb_scopes.cc +++ b/chromium/components/services/storage/indexed_db/scopes/leveldb_scopes.cc @@ -111,9 +111,7 @@ leveldb::Status LevelDBScopes::Initialize() { for (; iterator->Valid() && iterator->key().starts_with(prefix_key); iterator->Next()) { // Parse the key & value. - int64_t scope_id; - bool success; - std::tie(success, scope_id) = leveldb_scopes::ParseScopeMetadataId( + auto [success, scope_id] = leveldb_scopes::ParseScopeMetadataId( iterator->key(), metadata_key_prefix_); if (UNLIKELY(!success)) { return leveldb::Status::Corruption(base::StrCat( @@ -274,9 +272,7 @@ leveldb::Status LevelDBScopes::Commit(std::unique_ptr<LevelDBScope> scope, DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(recovery_finished_); DCHECK(cleanup_runner_); - LevelDBScope::Mode scopes_mode; - leveldb::Status s; - std::tie(s, scopes_mode) = scope->Commit(sync_on_commit); + auto [status, scopes_mode] = scope->Commit(sync_on_commit); if (scopes_mode == LevelDBScope::Mode::kUndoLogOnDisk) { auto task = std::make_unique<CleanupScopeTask>( level_db_, metadata_key_prefix_, scope->scope_id(), @@ -288,7 +284,7 @@ leveldb::Status LevelDBScopes::Commit(std::unique_ptr<LevelDBScope> scope, base::BindOnce(&LevelDBScopes::OnCleanupTaskResult, weak_factory_.GetWeakPtr(), std::move(on_complete))); } - return s; + return status; } leveldb::Status LevelDBScopes::Rollback(int64_t scope_id, diff --git a/chromium/components/services/storage/indexed_db/scopes/leveldb_scopes_tasks.h b/chromium/components/services/storage/indexed_db/scopes/leveldb_scopes_tasks.h index 97c02f337c9..f8fca7817c0 100644 --- a/chromium/components/services/storage/indexed_db/scopes/leveldb_scopes_tasks.h +++ b/chromium/components/services/storage/indexed_db/scopes/leveldb_scopes_tasks.h @@ -9,7 +9,6 @@ #include <vector> #include "base/callback.h" -#include "base/compiler_specific.h" #include "base/memory/scoped_refptr.h" #include "base/sequence_checker.h" #include "components/services/storage/indexed_db/leveldb/leveldb_state.h" @@ -38,18 +37,18 @@ class LevelDBScopesTask { protected: // Submits the in-progress WriteBatch to LevelDB, no matter what size the // batch is. - leveldb::Status SubmitWriteBatch(const leveldb::WriteOptions& options) - WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status SubmitWriteBatch( + const leveldb::WriteOptions& options); // Submits thein-progress WriteBatch to LevelDB only if the approximate size // of the batch is > |max_write_batch_size_|. - leveldb::Status MaybeSubmitWriteBatch(const leveldb::WriteOptions& options) - WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status MaybeSubmitWriteBatch( + const leveldb::WriteOptions& options); - leveldb::Status DeleteRange(leveldb::Slice range_start, - leveldb::Slice range_end, - const leveldb::ReadOptions& read_options, - const leveldb::WriteOptions& write_options) - WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status DeleteRange( + leveldb::Slice range_start, + leveldb::Slice range_end, + const leveldb::ReadOptions& read_options, + const leveldb::WriteOptions& write_options); SEQUENCE_CHECKER(sequence_checker_); @@ -79,16 +78,16 @@ class CleanupScopeTask : private LevelDBScopesTask { size_t max_write_batch_size_bytes); ~CleanupScopeTask(); - leveldb::Status Run() WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status Run(); private: - leveldb::Status ExecuteAndDeleteCleanupTasks( + [[nodiscard]] leveldb::Status ExecuteAndDeleteCleanupTasks( const leveldb::ReadOptions& read_options, - const leveldb::WriteOptions& write_options) WARN_UNUSED_RESULT; - leveldb::Status DeletePrefixedRange( + const leveldb::WriteOptions& write_options); + [[nodiscard]] leveldb::Status DeletePrefixedRange( leveldb::Slice prefix, const leveldb::ReadOptions& read_options, - const leveldb::WriteOptions& write_options) WARN_UNUSED_RESULT; + const leveldb::WriteOptions& write_options); const std::vector<uint8_t> metadata_prefix_; const int64_t scope_number_; @@ -109,7 +108,7 @@ class RevertScopeTask : private LevelDBScopesTask { size_t max_write_batch_size_bytes); ~RevertScopeTask(); - leveldb::Status Run() WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status Run(); private: const std::vector<uint8_t> metadata_prefix_; diff --git a/chromium/components/services/storage/indexed_db/scopes/leveldb_scopes_test_utils.cc b/chromium/components/services/storage/indexed_db/scopes/leveldb_scopes_test_utils.cc index 9486bf3a1b0..cf380e68105 100644 --- a/chromium/components/services/storage/indexed_db/scopes/leveldb_scopes_test_utils.cc +++ b/chromium/components/services/storage/indexed_db/scopes/leveldb_scopes_test_utils.cc @@ -101,9 +101,7 @@ void LevelDBScopesTestBase::SetUpBreakableDB( TearDown(); ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); - leveldb::Status status; - std::unique_ptr<leveldb::DB> temp_real_db; - std::tie(temp_real_db, status) = + auto [temp_real_db, status] = leveldb_factory_->OpenDB(temp_directory_.GetPath().AsUTF8Unsafe(), /*create_if_missing=*/true, kWriteBufferSize); ASSERT_TRUE(status.ok()); @@ -124,10 +122,8 @@ void LevelDBScopesTestBase::SetUpFlakyDB( if (leveldb_) TearDown(); ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); - leveldb::Status status; - std::unique_ptr<leveldb::DB> temp_db; - std::tie(temp_db, status) = + auto [temp_db, status] = leveldb_factory_->OpenDB(temp_directory_.GetPath().AsUTF8Unsafe(), /*create_if_missing=*/true, kWriteBufferSize); ASSERT_TRUE(status.ok()); diff --git a/chromium/components/services/storage/indexed_db/scopes/varint_coding.h b/chromium/components/services/storage/indexed_db/scopes/varint_coding.h index 640061229cb..eca8f086a92 100644 --- a/chromium/components/services/storage/indexed_db/scopes/varint_coding.h +++ b/chromium/components/services/storage/indexed_db/scopes/varint_coding.h @@ -22,7 +22,7 @@ void EncodeVarInt(int64_t from, std::string* into); // Decodes a varint from the given string piece into the given int64_t. Returns // if the string had a valid varint (where a byte was found with it's top bit // set). This function does NOT check to see if move than 64 bits were read. -WARN_UNUSED_RESULT bool DecodeVarInt(base::StringPiece* from, int64_t* into); +[[nodiscard]] bool DecodeVarInt(base::StringPiece* from, int64_t* into); } // namespace content diff --git a/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_database.cc b/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_database.cc index 0c8aa710d02..473a0b1d85a 100644 --- a/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_database.cc +++ b/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_database.cc @@ -53,7 +53,7 @@ namespace { // // Sync writes are necessary on Windows for quota calculations; POSIX // calculates file sizes correctly even when not synced to disk. -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) const bool kSyncWrites = true; #else // TODO(dgrogan): Either remove the #if block or change this back to false. diff --git a/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_iterator.cc b/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_iterator.cc index c9d2b0a5bb0..8b2932c34dd 100644 --- a/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_iterator.cc +++ b/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_iterator.cc @@ -113,11 +113,9 @@ leveldb::Status TransactionalLevelDBIterator::Next() { CheckState(); bool iterator_is_loaded = (iterator_ != nullptr); - std::string key_before_eviction; - leveldb::Status s; - std::tie(key_before_eviction, s) = WillUseDBIterator(/*perform_seek=*/true); - if (!s.ok()) - return s; + auto [key_before_eviction, status] = WillUseDBIterator(/*perform_seek=*/true); + if (!status.ok()) + return status; DCHECK(iterator_); // Exit early if not valid. diff --git a/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_iterator.h b/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_iterator.h index aa497672c5d..293ce558002 100644 --- a/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_iterator.h +++ b/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_iterator.h @@ -72,16 +72,16 @@ class TransactionalLevelDBIterator { enum class Direction { kNext, kPrev }; enum class IteratorState { kActive, kEvictedAndValid, kEvictedAndInvalid }; - leveldb::Status WrappedIteratorStatus() WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status WrappedIteratorStatus(); // Notifies the database of iterator usage and recreates iterator if needed. // If the iterator was previously evicted, this method returns the key that // was used, the status of reloading the iterator. - std::tuple<std::string, leveldb::Status> WillUseDBIterator(bool perform_seek) - WARN_UNUSED_RESULT; + [[nodiscard]] std::tuple<std::string, leveldb::Status> WillUseDBIterator( + bool perform_seek); // If this method fails, then iterator_ will be nullptr. - leveldb::Status ReloadIterator() WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status ReloadIterator(); void NextPastScopesMetadata(); void PrevPastScopesMetadata(); diff --git a/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_transaction.h b/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_transaction.h index a3a5c752fcc..2c4652fc481 100644 --- a/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_transaction.h +++ b/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_transaction.h @@ -10,7 +10,6 @@ #include <string> #include "base/callback.h" -#include "base/compiler_specific.h" #include "base/containers/flat_set.h" #include "base/gtest_prod_util.h" #include "base/memory/raw_ptr.h" @@ -50,24 +49,24 @@ class TransactionalLevelDBTransaction TransactionalLevelDBTransaction& operator=( const TransactionalLevelDBTransaction&) = delete; - leveldb::Status Put(const base::StringPiece& key, - std::string* value) WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status Put(const base::StringPiece& key, + std::string* value); - leveldb::Status Remove(const base::StringPiece& key) WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status Remove(const base::StringPiece& key); - leveldb::Status RemoveRange(const base::StringPiece& begin, - const base::StringPiece& end, - LevelDBScopeDeletionMode deletion_mode) - WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status RemoveRange( + const base::StringPiece& begin, + const base::StringPiece& end, + LevelDBScopeDeletionMode deletion_mode); - virtual leveldb::Status Get(const base::StringPiece& key, - std::string* value, - bool* found) WARN_UNUSED_RESULT; - virtual leveldb::Status Commit(bool sync_on_commit) WARN_UNUSED_RESULT; + [[nodiscard]] virtual leveldb::Status Get(const base::StringPiece& key, + std::string* value, + bool* found); + [[nodiscard]] virtual leveldb::Status Commit(bool sync_on_commit); // If the underlying scopes system is in single-sequence mode, then this // method will return the result of the rollback task. - leveldb::Status Rollback() WARN_UNUSED_RESULT; + [[nodiscard]] leveldb::Status Rollback(); // The returned iterator must be destroyed before the destruction of this // transaction. This may return null, if it does, status will explain why. diff --git a/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_transaction_unittest.cc b/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_transaction_unittest.cc index 644beeed548..f2602dae6a6 100644 --- a/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_transaction_unittest.cc +++ b/chromium/components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_transaction_unittest.cc @@ -15,7 +15,6 @@ #include "base/files/file.h" #include "base/files/file_path.h" -#include "base/no_destructor.h" #include "base/strings/string_piece.h" #include "base/test/bind.h" #include "base/threading/sequenced_task_runner_handle.h" diff --git a/chromium/components/services/storage/partition_impl.cc b/chromium/components/services/storage/partition_impl.cc index 88d5a877127..d77cb004a94 100644 --- a/chromium/components/services/storage/partition_impl.cc +++ b/chromium/components/services/storage/partition_impl.cc @@ -88,7 +88,7 @@ void PartitionImpl::BindSessionStorageControl( {base::MayBlock(), base::WithBaseSyncPrimitives(), base::TaskShutdownBehavior::BLOCK_SHUTDOWN}), base::SequencedTaskRunnerHandle::Get(), -#if defined(OS_ANDROID) +#if BUILDFLAG(IS_ANDROID) // On Android there is no support for session storage restoring, and since // the restoring code is responsible for database cleanup, we must // manually delete the old database here before we open a new one. diff --git a/chromium/components/services/storage/public/cpp/BUILD.gn b/chromium/components/services/storage/public/cpp/BUILD.gn index 3b259d0b6cf..867ebf97832 100644 --- a/chromium/components/services/storage/public/cpp/BUILD.gn +++ b/chromium/components/services/storage/public/cpp/BUILD.gn @@ -10,7 +10,6 @@ component("cpp") { "constants.h", "quota_client_callback_wrapper.h", "quota_error_or.h", - "storage_key_quota_client.h", ] sources = [ diff --git a/chromium/components/services/storage/public/cpp/constants.cc b/chromium/components/services/storage/public/cpp/constants.cc index c79decd3187..0ca6aa5ba1f 100644 --- a/chromium/components/services/storage/public/cpp/constants.cc +++ b/chromium/components/services/storage/public/cpp/constants.cc @@ -24,4 +24,8 @@ const char kLocalStorageLeveldbName[] = "leveldb"; const base::FilePath::CharType kServiceWorkerDirectory[] = FILE_PATH_LITERAL("Service Worker"); +// The file name of the database storing media license data. +const base::FilePath::CharType kMediaLicenseDatabaseFileName[] = + FILE_PATH_LITERAL("Media Licenses.db"); + } // namespace storage diff --git a/chromium/components/services/storage/public/cpp/constants.h b/chromium/components/services/storage/public/cpp/constants.h index 3897d6b73b4..b2cdcf60758 100644 --- a/chromium/components/services/storage/public/cpp/constants.h +++ b/chromium/components/services/storage/public/cpp/constants.h @@ -22,6 +22,9 @@ extern const char kLocalStorageLeveldbName[]; COMPONENT_EXPORT(STORAGE_SERVICE_PUBLIC) extern const base::FilePath::CharType kServiceWorkerDirectory[]; +COMPONENT_EXPORT(STORAGE_SERVICE_PUBLIC) +extern const base::FilePath::CharType kMediaLicenseDatabaseFileName[]; + } // namespace storage #endif // COMPONENTS_SERVICES_STORAGE_PUBLIC_CPP_CONSTANTS_H_ diff --git a/chromium/components/services/storage/public/cpp/filesystem/filesystem_impl.cc b/chromium/components/services/storage/public/cpp/filesystem/filesystem_impl.cc index fab8aa8f523..fbe4ab5b280 100644 --- a/chromium/components/services/storage/public/cpp/filesystem/filesystem_impl.cc +++ b/chromium/components/services/storage/public/cpp/filesystem/filesystem_impl.cc @@ -19,7 +19,7 @@ #include "build/build_config.h" #include "mojo/public/cpp/bindings/self_owned_receiver.h" -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) #include <windows.h> #endif @@ -81,7 +81,7 @@ class FileLockImpl : public mojom::FileLock { return; } -#if defined(OS_FUCHSIA) +#if BUILDFLAG(IS_FUCHSIA) std::move(callback).Run(base::File::FILE_OK); #else std::move(callback).Run(file_.Unlock()); @@ -124,12 +124,11 @@ void FilesystemImpl::GetEntries(const base::FilePath& path, // Fix up the absolute paths to be relative to |path|. std::vector<base::FilePath> entries; - std::vector<base::FilePath::StringType> root_components; - full_path.GetComponents(&root_components); + std::vector<base::FilePath::StringType> root_components = + full_path.GetComponents(); const size_t num_components_to_strip = root_components.size(); for (const auto& entry : result.value()) { - std::vector<base::FilePath::StringType> components; - entry.GetComponents(&components); + std::vector<base::FilePath::StringType> components = entry.GetComponents(); base::FilePath relative_path; for (size_t i = num_components_to_strip; i < components.size(); ++i) relative_path = relative_path.Append(components[i]); @@ -288,7 +287,7 @@ base::FileErrorOr<base::File> FilesystemImpl::LockFileLocal( if (!GetLockTable().AddLock(path)) return base::File::FILE_ERROR_IN_USE; -#if !defined(OS_FUCHSIA) +#if !BUILDFLAG(IS_FUCHSIA) base::File::Error error = file.Lock(base::File::LockMode::kExclusive); if (error != base::File::FILE_OK) return error; @@ -306,7 +305,7 @@ void FilesystemImpl::UnlockFileLocal(const base::FilePath& path) { mojom::PathAccessInfoPtr FilesystemImpl::GetPathAccessLocal( const base::FilePath& path) { mojom::PathAccessInfoPtr info; -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) uint32_t attributes = ::GetFileAttributes(path.value().c_str()); if (attributes != INVALID_FILE_ATTRIBUTES) { info = mojom::PathAccessInfo::New(); @@ -314,7 +313,7 @@ mojom::PathAccessInfoPtr FilesystemImpl::GetPathAccessLocal( if ((attributes & FILE_ATTRIBUTE_READONLY) == 0) info->can_write = true; } -#elif defined(OS_POSIX) || defined(OS_FUCHSIA) +#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) const char* const c_path = path.value().c_str(); if (!access(c_path, F_OK)) { info = mojom::PathAccessInfo::New(); diff --git a/chromium/components/services/storage/public/cpp/filesystem/filesystem_proxy.cc b/chromium/components/services/storage/public/cpp/filesystem/filesystem_proxy.cc index 67a934a7c40..c2d7895529f 100644 --- a/chromium/components/services/storage/public/cpp/filesystem/filesystem_proxy.cc +++ b/chromium/components/services/storage/public/cpp/filesystem/filesystem_proxy.cc @@ -23,12 +23,6 @@ namespace storage { namespace { -size_t GetNumPathComponents(const base::FilePath& path) { - std::vector<base::FilePath::StringType> components; - path.GetComponents(&components); - return components.size(); -} - class LocalFileLockImpl : public FilesystemProxy::FileLock { public: LocalFileLockImpl(base::FilePath path, base::File lock) @@ -41,7 +35,7 @@ class LocalFileLockImpl : public FilesystemProxy::FileLock { // FilesystemProxy::FileLock implementation: base::File::Error Release() override { base::File::Error error = base::File::FILE_OK; -#if !defined(OS_FUCHSIA) +#if !BUILDFLAG(IS_FUCHSIA) error = lock_.Unlock(); #endif lock_.Close(); @@ -89,7 +83,7 @@ FilesystemProxy::FilesystemProxy( mojo::PendingRemote<mojom::Directory> directory, scoped_refptr<base::SequencedTaskRunner> ipc_task_runner) : root_(root), - num_root_components_(GetNumPathComponents(root_)), + num_root_components_(root_.GetComponents().size()), remote_directory_(std::move(directory), ipc_task_runner) { DCHECK(root_.IsAbsolute()); } @@ -380,8 +374,7 @@ base::FilePath FilesystemProxy::MakeRelative(const base::FilePath& path) const { return base::FilePath(); // Absolute paths need to be rebased onto |root_|. - std::vector<base::FilePath::StringType> components; - path.GetComponents(&components); + std::vector<base::FilePath::StringType> components = path.GetComponents(); base::FilePath relative_path; for (size_t i = num_root_components_; i < components.size(); ++i) relative_path = relative_path.Append(components[i]); diff --git a/chromium/components/services/storage/public/cpp/quota_client_callback_wrapper.cc b/chromium/components/services/storage/public/cpp/quota_client_callback_wrapper.cc index 883a17d9f81..32256f4166b 100644 --- a/chromium/components/services/storage/public/cpp/quota_client_callback_wrapper.cc +++ b/chromium/components/services/storage/public/cpp/quota_client_callback_wrapper.cc @@ -8,6 +8,7 @@ #include "base/check_op.h" #include "base/sequence_checker.h" +#include "components/services/storage/public/cpp/buckets/bucket_locator.h" #include "components/services/storage/public/mojom/quota_client.mojom.h" #include "mojo/public/cpp/bindings/callback_helpers.h" #include "third_party/blink/public/common/storage_key/storage_key.h" @@ -26,14 +27,13 @@ QuotaClientCallbackWrapper::~QuotaClientCallbackWrapper() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); } -void QuotaClientCallbackWrapper::GetStorageKeyUsage( - const blink::StorageKey& storage_key, - blink::mojom::StorageType type, - GetStorageKeyUsageCallback callback) { +void QuotaClientCallbackWrapper::GetBucketUsage( + const BucketLocator& bucket, + GetBucketUsageCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - wrapped_client_->GetStorageKeyUsage( - storage_key, type, + wrapped_client_->GetBucketUsage( + bucket, mojo::WrapCallbackWithDefaultInvokeIfNotRun(std::move(callback), 0)); } @@ -47,26 +47,13 @@ void QuotaClientCallbackWrapper::GetStorageKeysForType( std::move(callback), std::vector<blink::StorageKey>())); } -void QuotaClientCallbackWrapper::GetStorageKeysForHost( - blink::mojom::StorageType type, - const std::string& host, - GetStorageKeysForHostCallback callback) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - - wrapped_client_->GetStorageKeysForHost( - type, host, - mojo::WrapCallbackWithDefaultInvokeIfNotRun( - std::move(callback), std::vector<blink::StorageKey>())); -} - -void QuotaClientCallbackWrapper::DeleteStorageKeyData( - const blink::StorageKey& storage_key, - blink::mojom::StorageType type, - DeleteStorageKeyDataCallback callback) { +void QuotaClientCallbackWrapper::DeleteBucketData( + const BucketLocator& bucket, + DeleteBucketDataCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - wrapped_client_->DeleteStorageKeyData( - storage_key, type, + wrapped_client_->DeleteBucketData( + bucket, mojo::WrapCallbackWithDefaultInvokeIfNotRun( std::move(callback), blink::mojom::QuotaStatusCode::kErrorAbort)); } diff --git a/chromium/components/services/storage/public/cpp/quota_client_callback_wrapper.h b/chromium/components/services/storage/public/cpp/quota_client_callback_wrapper.h index f964baf1528..5935e56d02a 100644 --- a/chromium/components/services/storage/public/cpp/quota_client_callback_wrapper.h +++ b/chromium/components/services/storage/public/cpp/quota_client_callback_wrapper.h @@ -10,12 +10,10 @@ #include "base/thread_annotations.h" #include "components/services/storage/public/mojom/quota_client.mojom.h" -namespace blink { -class StorageKey; -} // namespace blink - namespace storage { +struct BucketLocator; + // Stopgap for QuotaClients in systems with an unclear ownership graph. // // Implements the QuotaClient interface by proxying to a "real" implementation. @@ -54,17 +52,12 @@ class COMPONENT_EXPORT(STORAGE_SERVICE_PUBLIC) QuotaClientCallbackWrapper ~QuotaClientCallbackWrapper() override; // mojom::QuotaClient. - void GetStorageKeyUsage(const blink::StorageKey& storage_key, - blink::mojom::StorageType type, - GetStorageKeyUsageCallback callback) override; + void GetBucketUsage(const BucketLocator& bucket, + GetBucketUsageCallback callback) override; void GetStorageKeysForType(blink::mojom::StorageType type, GetStorageKeysForTypeCallback callback) override; - void GetStorageKeysForHost(blink::mojom::StorageType type, - const std::string& host, - GetStorageKeysForHostCallback callback) override; - void DeleteStorageKeyData(const blink::StorageKey& storage_key, - blink::mojom::StorageType type, - DeleteStorageKeyDataCallback callback) override; + void DeleteBucketData(const BucketLocator& bucket, + DeleteBucketDataCallback callback) override; void PerformStorageCleanup(blink::mojom::StorageType type, PerformStorageCleanupCallback callback) override; diff --git a/chromium/components/services/storage/public/cpp/storage_key_quota_client.h b/chromium/components/services/storage/public/cpp/storage_key_quota_client.h deleted file mode 100644 index 7c0ac11520c..00000000000 --- a/chromium/components/services/storage/public/cpp/storage_key_quota_client.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_SERVICES_STORAGE_PUBLIC_CPP_STORAGE_KEY_QUOTA_CLIENT_H_ -#define COMPONENTS_SERVICES_STORAGE_PUBLIC_CPP_STORAGE_KEY_QUOTA_CLIENT_H_ - -#include "components/services/storage/public/mojom/quota_client.mojom.h" - -namespace storage { - -// Interface for storage key based QuotaClient to be inherited by quota managed -// storage APIs that have not implemented Storage Buckets support yet. As -// Storage APIs migrate their implementation to support Storage Buckets, they -// should use the bucket base QuotaClient to be added as part of -// crbug.com/1199417. -// -// TODO(crbug.com/1214066): Once all storage API's have migrated off storage key -// based QuotaClient. This class should be removed and all API's should inherit -// from bucket based QuotaClient. -class COMPONENT_EXPORT(STORAGE_SERVICE_PUBLIC) StorageKeyQuotaClient - : public mojom::QuotaClient { - protected: - ~StorageKeyQuotaClient() override = default; -}; - -} // namespace storage - -#endif // COMPONENTS_SERVICES_STORAGE_PUBLIC_CPP_STORAGE_KEY_QUOTA_CLIENT_H_ diff --git a/chromium/components/services/storage/public/mojom/quota_client.mojom b/chromium/components/services/storage/public/mojom/quota_client.mojom index e64fb788eab..52f9aea8cde 100644 --- a/chromium/components/services/storage/public/mojom/quota_client.mojom +++ b/chromium/components/services/storage/public/mojom/quota_client.mojom @@ -6,6 +6,7 @@ module storage.mojom; import "third_party/blink/public/mojom/quota/quota_types.mojom"; import "third_party/blink/public/mojom/storage_key/storage_key.mojom"; +import "components/services/storage/public/mojom/buckets/bucket_locator.mojom"; // Interface between each storage API and the quota manager. // @@ -17,25 +18,24 @@ import "third_party/blink/public/mojom/storage_key/storage_key.mojom"; // currently lives in the browser process, and will eventually move to the // Storage Service process. interface QuotaClient { - // Returns the amount of data stored in the storage specified by `storage_key` - // and `type`. - GetStorageKeyUsage(blink.mojom.StorageKey storage_key, - blink.mojom.StorageType type) - => (int64 usage); + // Returns the amount of data stored in the storage specified by `bucket`. + // The quota manager should not assume that 0 `usage` means the + // storage API has no record of the `bucket`'s existence. + // DeleteBucketData() is the only way to guarantee that storage APIs erase + // all tracks of a `bucket`. + GetBucketUsage(BucketLocator bucket) => (int64 usage); - // Returns a list of storage keys that have data in the `type` storage. - GetStorageKeysForType(blink.mojom.StorageType type) - => (array<blink.mojom.StorageKey> storage_keys); - - // Returns a list of storage keys that match the `host` and have data in the + // Returns a list of storage keys that have data in the default bucket for // `type` storage. - GetStorageKeysForHost(blink.mojom.StorageType type, string host) + // + // This method is currently used to bootstrap the buckets table in the quota + // database with data produced by old code. No other uses should be added. + // We're planning to remove this around 2024. + GetStorageKeysForType(blink.mojom.StorageType type) => (array<blink.mojom.StorageKey> storage_keys); - // Returns after all data belonging to `storage_key` in the `type` storage - // has been deleted. - DeleteStorageKeyData(blink.mojom.StorageKey storage_key, - blink.mojom.StorageType type) + // Returns after all data belonging to `bucket` has been deleted. + DeleteBucketData(BucketLocator bucket) => (blink.mojom.QuotaStatusCode status); // An opportunity to perform a cleanup step after major deletions. diff --git a/chromium/components/services/storage/service_worker/service_worker_database.cc b/chromium/components/services/storage/service_worker/service_worker_database.cc index c539b19c5c8..dbf4ba5d433 100644 --- a/chromium/components/services/storage/service_worker/service_worker_database.cc +++ b/chromium/components/services/storage/service_worker/service_worker_database.cc @@ -19,6 +19,7 @@ #include "components/services/storage/filesystem_proxy_factory.h" #include "components/services/storage/service_worker/service_worker_database.pb.h" #include "third_party/abseil-cpp/absl/types/optional.h" +#include "third_party/blink/public/common/storage_key/storage_key.h" #include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom.h" #include "third_party/leveldatabase/env_chromium.h" #include "third_party/leveldatabase/leveldb_chrome.h" @@ -63,7 +64,8 @@ // be updated in the future to avoid a migration. // TODO(crbug.com/1199077): Update name during a migration to Version 3. // key: "REG:" + <StorageKey 'key'.origin> + "/" + [ "^0" + <StorageKey -// `key`.top_level_site> ] + '\x00' + <int64_t 'registration_id'> +// `key`.top_level_site> + "^3" + <StorageKey `key`.ancestor_chain_bit> ] + +// '\x00' + <int64_t 'registration_id'> // - or - // key: "REG:" + <StorageKey 'key'.origin> + "/" + "^1" + <StorageKey // 'nonce'.High64Bits> + "^2" + <StorageKey 'nonce'.Low64Bits> + '\x00' + @@ -95,7 +97,7 @@ // TODO(crbug.com/1199077): Update name during a migration to Version 3. // key: "REGID_TO_ORIGIN:" + <int64_t 'registration_id'> // value: <StorageKey 'key'.origin> + "/" + [ "^0" + <StorageKey -// `key`.top_level_site> ] +// `key`.top_level_site> + "^3" + <StorageKey `key`.ancestor_chain_bit>] // - or - // value: <StorageKey 'key'.origin> + "/" + "^1" + <StorageKey // 'nonce'.High64Bits> + "^2" + <StorageKey 'nonce'.Low64Bits> @@ -115,45 +117,6 @@ // OBSOLETE: https://crbug.com/788604 // key: "INITDATA_FOREIGN_FETCH_ORIGIN:" + <GURL 'origin'> // value: <empty> -namespace { - -// Returns true if the registration key string is partitioned by top-level site -// but storage partitioning is currently disabled. Returns false if the key -// string contains a serialized nonce. -bool ShouldSkipKeyDueToPartitioning(const std::string& reg_key_string) { - // Don't skip anything if storage partitioning is enabled. - if (blink::StorageKey::IsThirdPartyStoragePartitioningEnabled()) - return false; - - // TODO(crbug.com/1246549) : This currently counts carets to tell the - // difference between nonce and top-level site schemes. When the ancestor bit - // is implemented this will need to be modified to handle that case (since it - // will also use 2 carets). - int number_of_carets = - std::count(reg_key_string.begin(), reg_key_string.end(), '^'); - - switch (number_of_carets) { - case 2: { - // Don't skip if a nonce serialization scheme is found. - return false; - } - case 1: { - // Do skip if partitioning is disabled and we detect a top-level site - // serialization scheme. - return true; - } - case 0: { - // Don't skip for a 1p context key. - return false; - } - default: { - NOTREACHED(); - return true; - } - } -} - -} // namespace namespace storage { @@ -431,7 +394,7 @@ ServiceWorkerDatabase::GetStorageKeysWithRegistrations( service_worker_internals::kUniqueOriginKey, &key_str)) break; - if (ShouldSkipKeyDueToPartitioning(key_str)) + if (blink::StorageKey::ShouldSkipKeyDueToPartitioning(key_str)) continue; absl::optional<blink::StorageKey> key = @@ -623,7 +586,7 @@ ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetAllRegistrations( // Get only the sub-string before the separator. std::string reg_key_string = prefix_string.substr(0, separator_pos); - if (ShouldSkipKeyDueToPartitioning(reg_key_string)) + if (blink::StorageKey::ShouldSkipKeyDueToPartitioning(reg_key_string)) continue; absl::optional<blink::StorageKey> key = @@ -700,7 +663,7 @@ ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistrationStorageKey( // If storage partitioning is disabled we shouldn't have any handles to // registration IDs associated with partitioned entries. - DCHECK(!ShouldSkipKeyDueToPartitioning(value)); + DCHECK(!blink::StorageKey::ShouldSkipKeyDueToPartitioning(value)); absl::optional<blink::StorageKey> parsed = blink::StorageKey::Deserialize(value); diff --git a/chromium/components/services/storage/service_worker/service_worker_storage.cc b/chromium/components/services/storage/service_worker/service_worker_storage.cc index 59a32d7e89a..3dbac654f20 100644 --- a/chromium/components/services/storage/service_worker/service_worker_storage.cc +++ b/chromium/components/services/storage/service_worker/service_worker_storage.cc @@ -20,6 +20,7 @@ #include "base/task/sequenced_task_runner.h" #include "base/task/task_runner_util.h" #include "base/task/thread_pool.h" +#include "base/time/time.h" #include "base/trace_event/trace_event.h" #include "components/services/storage/public/cpp/constants.h" #include "components/services/storage/service_worker/service_worker_disk_cache.h" @@ -1580,6 +1581,8 @@ void ServiceWorkerStorage::ReadInitialDataFromDB( ServiceWorkerDatabase* database, scoped_refptr<base::SequencedTaskRunner> original_task_runner, InitializeCallback callback) { + base::TimeTicks now = base::TimeTicks::Now(); + DCHECK(database); std::unique_ptr<ServiceWorkerStorage::InitialData> data( new ServiceWorkerStorage::InitialData()); @@ -1604,6 +1607,10 @@ void ServiceWorkerStorage::ReadInitialDataFromDB( original_task_runner->PostTask( FROM_HERE, base::BindOnce(std::move(callback), std::move(data), status)); + + base::UmaHistogramMediumTimes( + "ServiceWorker.Storage.ReadInitialDataFromDB.Time", + base::TimeTicks::Now() - now); } void ServiceWorkerStorage::DeleteRegistrationFromDB( diff --git a/chromium/components/services/storage/service_worker/service_worker_storage_control_impl.cc b/chromium/components/services/storage/service_worker/service_worker_storage_control_impl.cc index d5d1ccb754f..9001b00dc84 100644 --- a/chromium/components/services/storage/service_worker/service_worker_storage_control_impl.cc +++ b/chromium/components/services/storage/service_worker/service_worker_storage_control_impl.cc @@ -5,6 +5,7 @@ #include "components/services/storage/service_worker/service_worker_storage_control_impl.h" #include "base/containers/contains.h" +#include "base/debug/alias.h" #include "components/services/storage/service_worker/service_worker_resource_ops.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/receiver_set.h" @@ -483,6 +484,10 @@ ServiceWorkerStorageControlImpl::CreateLiveVersionReferenceRemote( reference->Add(remote_reference.InitWithNewPipeAndPassReceiver()); live_versions_[version_id] = std::move(reference); } else { + // TODO(https://crbug.com/1277263): Remove the following CHECK() once the + // cause is identified. + base::debug::Alias(&version_id); + CHECK(it->second.get()) << "Invalid version id: " << version_id; it->second->Add(remote_reference.InitWithNewPipeAndPassReceiver()); } return remote_reference; diff --git a/chromium/components/services/storage/service_worker/service_worker_storage_control_impl_unittest.cc b/chromium/components/services/storage/service_worker/service_worker_storage_control_impl_unittest.cc index 4ba4aaa8093..a17552bdbee 100644 --- a/chromium/components/services/storage/service_worker/service_worker_storage_control_impl_unittest.cc +++ b/chromium/components/services/storage/service_worker/service_worker_storage_control_impl_unittest.cc @@ -88,6 +88,11 @@ struct GetUserDataForAllRegistrationsResult { std::vector<mojom::ServiceWorkerUserDataPtr> values; }; +struct GetUsageForStorageKeyResult { + DatabaseStatus status; + int64_t usage; +}; + ReadResponseHeadResult ReadResponseHead( mojom::ServiceWorkerResourceReader* reader) { ReadResponseHeadResult result; @@ -570,6 +575,21 @@ class ServiceWorkerStorageControlImplTest : public testing::Test { return return_value; } + GetUsageForStorageKeyResult GetUsageForStorageKey( + const blink::StorageKey& key) { + GetUsageForStorageKeyResult result; + base::RunLoop loop; + storage()->GetUsageForStorageKey( + key, + base::BindLambdaForTesting([&](DatabaseStatus status, int64_t usage) { + result.status = status; + result.usage = usage; + loop.Quit(); + })); + loop.Run(); + return result; + } + // Creates a registration with the given resource records. RegistrationData CreateRegistrationData( int64_t registration_id, @@ -1423,6 +1443,92 @@ TEST_F(ServiceWorkerStorageControlImplTest, EXPECT_EQ(result.values.size(), 0UL); } +// Tests that getting usage for a storage key works at different stages of +// registration resource update. +TEST_F(ServiceWorkerStorageControlImplTest, GetUsageForStorageKey) { + const GURL kScope("https://www.example.com/"); + const blink::StorageKey kKey(url::Origin::Create(kScope)); + const GURL kScriptUrl("https://www.example.com/sw.js"); + const GURL kImportedScriptUrl("https://www.example.com/imported.js"); + + LazyInitializeForTest(); + + // No data has written yet for a given storage key. + { + GetUsageForStorageKeyResult result = GetUsageForStorageKey(kKey); + ASSERT_EQ(result.status, DatabaseStatus::kOk); + ASSERT_EQ(result.usage, 0); + } + + // Preparation: Create a registration with two resources. These aren't written + // to storage yet. + std::vector<ResourceRecord> resources; + const int64_t resource_id1 = GetNewResourceId(); + const std::string resource_data1 = "main script data"; + resources.push_back(mojom::ServiceWorkerResourceRecord::New( + resource_id1, kScriptUrl, resource_data1.size())); + + const int64_t resource_id2 = GetNewResourceId(); + const std::string resource_data2 = "imported script data"; + resources.push_back(mojom::ServiceWorkerResourceRecord::New( + resource_id2, kImportedScriptUrl, resource_data2.size())); + + const int64_t registration_id = GetNewRegistrationId(); + const int64_t version_id = GetNewVersionId().version_id; + RegistrationData registration_data = CreateRegistrationData( + registration_id, version_id, kScope, kKey, kScriptUrl, resources); + + // Put these resources ids on the uncommitted list in storage. + DatabaseStatus status; + status = StoreUncommittedResourceId(resource_id1); + ASSERT_EQ(status, DatabaseStatus::kOk); + status = StoreUncommittedResourceId(resource_id2); + ASSERT_EQ(status, DatabaseStatus::kOk); + + { + GetUsageForStorageKeyResult result = GetUsageForStorageKey(kKey); + ASSERT_EQ(result.status, DatabaseStatus::kOk); + ASSERT_EQ(result.usage, 0) + << "Resources that aren't stored with the registration " + << "don't use quota."; + } + + // Write responses and the registration data. + { + int result = WriteResource(resource_id1, resource_data1); + ASSERT_GT(result, 0); + } + { + int result = WriteResource(resource_id2, resource_data2); + ASSERT_GT(result, 0); + } + status = + StoreRegistration(std::move(registration_data), std::move(resources)); + ASSERT_EQ(status, DatabaseStatus::kOk); + + // Expect the storage usage for resources stored. + { + GetUsageForStorageKeyResult result = GetUsageForStorageKey(kKey); + ASSERT_EQ(result.status, DatabaseStatus::kOk); + const int64_t expected_usage = + resource_data1.size() + resource_data2.size(); + ASSERT_EQ(result.usage, expected_usage); + } + + // Delete the registration. + { + DeleteRegistrationResult result = DeleteRegistration(registration_id, kKey); + ASSERT_EQ(result.status, DatabaseStatus::kOk); + } + + // Expect no storage usage. + { + GetUsageForStorageKeyResult result = GetUsageForStorageKey(kKey); + ASSERT_EQ(result.status, DatabaseStatus::kOk); + ASSERT_EQ(result.usage, 0); + } +} + // Tests that apply policy updates work. TEST_F(ServiceWorkerStorageControlImplTest, ApplyPolicyUpdates) { const GURL kScope1("https://foo.example.com/"); diff --git a/chromium/components/services/storage/service_worker/service_worker_storage_unittest.cc b/chromium/components/services/storage/service_worker/service_worker_storage_unittest.cc index be27d1faa0e..8b325727c14 100644 --- a/chromium/components/services/storage/service_worker/service_worker_storage_unittest.cc +++ b/chromium/components/services/storage/service_worker/service_worker_storage_unittest.cc @@ -913,7 +913,7 @@ TEST_F(ServiceWorkerStorageDiskTest, DeleteAndStartOver_OpenedFileExists) { base::BindOnce(&DatabaseStatusCallback, run_loop.QuitClosure(), &status)); run_loop.Run(); -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) // On Windows, deleting the directory containing an opened file should fail. EXPECT_EQ(ServiceWorkerDatabase::Status::kErrorIOError, *status); EXPECT_TRUE(storage()->IsDisabled()); diff --git a/chromium/components/services/storage/shared_storage/DIR_METADATA b/chromium/components/services/storage/shared_storage/DIR_METADATA new file mode 100644 index 00000000000..95ccec4a089 --- /dev/null +++ b/chromium/components/services/storage/shared_storage/DIR_METADATA @@ -0,0 +1,12 @@ +# Metadata information for this directory. +# +# For more information on DIR_METADATA files, see: +# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md +# +# For the schema of this file, see Metadata message: +# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto + +monorail { + component: "Blink>Storage>SharedStorage" +} +team_email: "chrome-ads-core@google.com" diff --git a/chromium/components/services/storage/shared_storage/OWNERS b/chromium/components/services/storage/shared_storage/OWNERS new file mode 100644 index 00000000000..6ab87565cd2 --- /dev/null +++ b/chromium/components/services/storage/shared_storage/OWNERS @@ -0,0 +1,6 @@ +# Primary: +cammie@chromium.org + +# Secondary: +yaoxia@chromium.org +jkarlin@chromium.org diff --git a/chromium/components/services/storage/shared_storage/async_shared_storage_database.cc b/chromium/components/services/storage/shared_storage/async_shared_storage_database.cc new file mode 100644 index 00000000000..3a6d9f438e6 --- /dev/null +++ b/chromium/components/services/storage/shared_storage/async_shared_storage_database.cc @@ -0,0 +1,196 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/services/storage/shared_storage/async_shared_storage_database.h" + +#include <inttypes.h> + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "base/files/file_util.h" +#include "base/time/time.h" +#include "components/services/storage/public/mojom/storage_usage_info.mojom.h" +#include "components/services/storage/shared_storage/shared_storage_options.h" +#include "storage/browser/quota/special_storage_policy.h" +#include "url/origin.h" + +namespace storage { + +// static +std::unique_ptr<AsyncSharedStorageDatabase> AsyncSharedStorageDatabase::Create( + base::FilePath db_path, + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy, + std::unique_ptr<SharedStorageDatabaseOptions> options) { + return absl::WrapUnique(new AsyncSharedStorageDatabase( + std::move(db_path), std::move(blocking_task_runner), + std::move(special_storage_policy), std::move(options))); +} + +AsyncSharedStorageDatabase::AsyncSharedStorageDatabase( + base::FilePath db_path, + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy, + std::unique_ptr<SharedStorageDatabaseOptions> options) + : database_(base::SequenceBound<SharedStorageDatabase>( + std::move(blocking_task_runner), + std::move(db_path), + std::move(special_storage_policy), + std::move(options))) {} + +AsyncSharedStorageDatabase::~AsyncSharedStorageDatabase() = default; + +void AsyncSharedStorageDatabase::Destroy( + base::OnceCallback<void(bool)> callback) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::Destroy) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::TrimMemory(base::OnceClosure callback) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::TrimMemory) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::Get( + url::Origin context_origin, + std::u16string key, + base::OnceCallback<void(GetResult)> callback) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::Get) + .WithArgs(std::move(context_origin), std::move(key)) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::Set( + url::Origin context_origin, + std::u16string key, + std::u16string value, + base::OnceCallback<void(OperationResult)> callback, + SetBehavior behavior) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::Set) + .WithArgs(std::move(context_origin), std::move(key), std::move(value), + behavior) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::Append( + url::Origin context_origin, + std::u16string key, + std::u16string value, + base::OnceCallback<void(OperationResult)> callback) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::Append) + .WithArgs(std::move(context_origin), std::move(key), std::move(value)) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::Delete( + url::Origin context_origin, + std::u16string key, + base::OnceCallback<void(OperationResult)> callback) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::Delete) + .WithArgs(std::move(context_origin), std::move(key)) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::Clear( + url::Origin context_origin, + base::OnceCallback<void(OperationResult)> callback) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::Clear) + .WithArgs(std::move(context_origin)) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::Length( + url::Origin context_origin, + base::OnceCallback<void(int)> callback) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::Length) + .WithArgs(std::move(context_origin)) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::Key( + url::Origin context_origin, + int index, + base::OnceCallback<void(GetResult)> callback) { + DCHECK_GE(index, 0); + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::Key) + .WithArgs(std::move(context_origin), static_cast<uint64_t>(index)) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::PurgeMatchingOrigins( + OriginMatcherFunction origin_matcher, + base::Time begin, + base::Time end, + base::OnceCallback<void(OperationResult)> callback, + bool perform_storage_cleanup) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::PurgeMatchingOrigins) + .WithArgs(std::move(origin_matcher), begin, end, perform_storage_cleanup) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::PurgeStaleOrigins( + base::TimeDelta window_to_be_deemed_active, + base::OnceCallback<void(OperationResult)> callback) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::PurgeStaleOrigins) + .WithArgs(window_to_be_deemed_active) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::FetchOrigins( + base::OnceCallback<void(std::vector<mojom::StorageUsageInfoPtr>)> + callback) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::FetchOrigins) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::IsOpenForTesting( + base::OnceCallback<void(bool)> callback) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::IsOpenForTesting) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::DBStatusForTesting( + base::OnceCallback<void(InitStatus)> callback) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::DBStatusForTesting) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::OverrideLastUsedTimeForTesting( + url::Origin context_origin, + base::Time override_last_used_time, + base::OnceCallback<void(bool)> callback) { + DCHECK(database_); + database_.AsyncCall(&SharedStorageDatabase::OverrideLastUsedTimeForTesting) + .WithArgs(std::move(context_origin), override_last_used_time) + .Then(std::move(callback)); +} + +void AsyncSharedStorageDatabase::OverrideSpecialStoragePolicyForTesting( + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy, + base::OnceCallback<void(bool)> callback) { + DCHECK(database_); + database_ + .AsyncCall(&SharedStorageDatabase::OverrideSpecialStoragePolicyForTesting) + .WithArgs(std::move(special_storage_policy)) + .Then(std::move(callback)); +} + +} // namespace storage diff --git a/chromium/components/services/storage/shared_storage/async_shared_storage_database.h b/chromium/components/services/storage/shared_storage/async_shared_storage_database.h new file mode 100644 index 00000000000..1771c4ff3d8 --- /dev/null +++ b/chromium/components/services/storage/shared_storage/async_shared_storage_database.h @@ -0,0 +1,237 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SERVICES_STORAGE_SHARED_STORAGE_ASYNC_SHARED_STORAGE_DATABASE_H_ +#define COMPONENTS_SERVICES_STORAGE_SHARED_STORAGE_ASYNC_SHARED_STORAGE_DATABASE_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "base/memory/scoped_refptr.h" +#include "base/task/sequenced_task_runner.h" +#include "base/threading/sequence_bound.h" +#include "components/services/storage/shared_storage/shared_storage_database.h" + +namespace base { +class FilePath; +class Time; +class TimeDelta; +} // namespace base + +namespace url { +class Origin; +} // namespace url + +namespace storage { +struct SharedStorageDatabaseOptions; +class SpecialStoragePolicy; + +// A wrapper around SharedStorageDatabase which makes the operations +// asynchronous. +class AsyncSharedStorageDatabase { + public: + using InitStatus = SharedStorageDatabase::InitStatus; + using SetBehavior = SharedStorageDatabase::SetBehavior; + using OperationResult = SharedStorageDatabase::OperationResult; + using GetResult = SharedStorageDatabase::GetResult; + + // A callback type to check if a given origin matches a storage policy. + // Can be passed empty/null where used, which means the origin will always + // match. + using OriginMatcherFunction = SharedStorageDatabase::OriginMatcherFunction; + + // Creates an `AsyncSharedStorageDatabase` instance. If `db_path` is empty, + // creates a temporary, in-memory database; otherwise creates a persistent + // database within a filesystem directory given by `db_path`, which must be an + // absolute path. If file-backed, the database may or may not already exist at + // `db_path`, and if it doesn't, it will be created. + // + // The instance will be bound to and perform all operations on + // `blocking_task_runner`, which must support blocking operations. + static std::unique_ptr<AsyncSharedStorageDatabase> Create( + base::FilePath db_path, + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy, + std::unique_ptr<SharedStorageDatabaseOptions> options); + + AsyncSharedStorageDatabase(const AsyncSharedStorageDatabase&) = delete; + AsyncSharedStorageDatabase& operator=(const AsyncSharedStorageDatabase&) = + delete; + AsyncSharedStorageDatabase(const AsyncSharedStorageDatabase&&) = delete; + AsyncSharedStorageDatabase& operator=(const AsyncSharedStorageDatabase&&) = + delete; + + ~AsyncSharedStorageDatabase(); + + base::SequenceBound<SharedStorageDatabase>& + GetSequenceBoundDatabaseForTesting() { + return database_; + } + + // Destroys the database. + // + // If filebacked, deletes the persistent database within the filesystem + // directory. + // + // It is OK to call `Destroy()` regardless of whether database initialization + // was successful. + void Destroy(base::OnceCallback<void(bool)> callback); + + // `TrimMemory()`, `Get()`, `Set()`, `Append()`, `Delete()`, `Clear()`, + // `Length()`, `Key()`, `PurgeMatchingOrigins()`, `PurgeStaleOrigins()`, and + // `FetchOrigins()` are all async versions of the corresponding methods in + // `storage::SharedStorageDatabase`, with the modification that `Set()` and + // `Append()` take a boolean callback to indicate that a value was set or + // appended, rather than a long integer callback with the row number for the + // next available row. + // + // It is OK to call these async methods even if the database has failed to + // initialize, as there is an alternate code path to handle this case that + // skips accessing `database_` (as it will be null) and hence performing the + // intending operation, logs the occurrence of the missing database to UMA, + // and runs the callback with a trivial instance of its expected result type). + + // Releases all non-essential memory associated with this database connection. + // `callback` runs once the operation is finished. + void TrimMemory(base::OnceClosure callback); + + // Retrieves the `value` for `context_origin` and `key`. `callback` is called + // with a struct bundling a string `value` in its data field if one is found, + // `absl::nullopt` otherwise, and a OperationResult to indicate whether the + // transaction was free of database errors. + // + // `key` must be of length at most + // `SharedStorageDatabaseOptions::max_string_length`, with the burden on the + // caller to handle errors for strings that exceed this length. + void Get(url::Origin context_origin, + std::u16string key, + base::OnceCallback<void(GetResult)> callback); + + // Sets an entry for `context_origin` and `key` to have `value`. + // If `behavior` is `kIgnoreIfPresent` and an entry already exists for + // `context_origin` and `key`, then the database table is not modified. + // The parameter of `callback` reports whether or not any entry is added, the + // request is ignored, or if there is an error. + // + // `key` and `value` must be each of length at most + // `SharedStorageDatabaseOptions::max_string_length`, with the burden on the + // caller to handle errors for strings that exceed this length. Moreover, if + // the length retrieved by `Length(context_origin, callback)` equals + // `SharedStorageDatabaseOptions::max_entries_per_origin_`, `Set()` will fail + // and the table will not be modified. + void Set(url::Origin context_origin, + std::u16string key, + std::u16string value, + base::OnceCallback<void(OperationResult)> callback, + SetBehavior behavior = SetBehavior::kDefault); + + // Appends `value` to the end of the current `value` for `context_origin` and + // `key`, if `key` exists. If `key` does not exist, creates an entry for `key` + // with value `value`. The parameter of `callback` reports whether or not any + // entry is added or modified or if there is an error. + // + // `key` and `value` must be each of length at most + // `SharedStorageDatabaseOptions::max_string_length`, with the burden on the + // caller to handle errors for strings that exceed this length. Moreover, if + // the length of the string obtained by concatening the current `script_value` + // (if one exists) and `value` exceeds + // `SharedStorageDatabaseOptions::max_string_length`, or if the length + // retrieved by `Length(context_origin, callback)` equals + // `SharedStorageDatabaseOptions::max_entries_per_origin_`, `Append()` will + // fail and the database table will not be modified. + void Append(url::Origin context_origin, + std::u16string key, + std::u16string value, + base::OnceCallback<void(OperationResult)> callback); + + // Deletes the entry for `context_origin` and `key`. The parameter of + // `callback` reports whether the deletion is successful. + // + // `key` must be of length at most + // `SharedStorageDatabaseOptions::max_string_length`, with the burden on the + // caller to handle errors for strings that exceed this length. + void Delete(url::Origin context_origin, + std::u16string key, + base::OnceCallback<void(OperationResult)> callback); + + // Clears all entries for `context_origin`. The parameter of `callback` + // reports whether the operation is successful. + void Clear(url::Origin context_origin, + base::OnceCallback<void(OperationResult)> callback); + + // The parameter of `callback` reports the number of entries for + // `context_origin`, 0 if there are none, or -1 on operation failure. + void Length(url::Origin context_origin, + base::OnceCallback<void(int)> callback); + + // If a list of all the keys for `context_origin` are taken in lexicographic + // order, retrieves the `key` at `index` of the list and calls `callback` with + // a struct bundling it as a parameter (along with a OperationResult to + // indicate whether the transaction was free of database errors); otherwise + // calls `callback` with `absl::nullopt` in the data field of the struct. + // `index` must be non-negative. + // + // TODO(crbug.com/1247861): Replace with an async iterator. + void Key(url::Origin context_origin, + int index, + base::OnceCallback<void(GetResult)> callback); + + // Clears all origins that match `origin_matcher` run on the owning + // StoragePartition's `SpecialStoragePolicy` and have `last_used_time` between + // the times `begin` and `end`. If `perform_storage_cleanup` is true, vacuums + // the database afterwards. The parameter of `callback` reports whether the + // transaction was successful. + // + // Note that `origin_matcher` is accessed on a different sequence than where + // it was created. + void PurgeMatchingOrigins(OriginMatcherFunction origin_matcher, + base::Time begin, + base::Time end, + base::OnceCallback<void(OperationResult)> callback, + bool perform_storage_cleanup = false); + + // Clear all entries for all origins whose `last_read_time` falls before + // `base::Time::Now() - window_to_be_deemed_active`. + void PurgeStaleOrigins(base::TimeDelta window_to_be_deemed_active, + base::OnceCallback<void(OperationResult)> callback); + + // Fetches a vector of `mojom::StorageUsageInfoPtr`, with one + // `mojom::StorageUsageInfoPtr` for each origin currently using shared storage + // in this profile. + void FetchOrigins( + base::OnceCallback<void(std::vector<mojom::StorageUsageInfoPtr>)> + callback); + + // Asynchronously determines whether the database is open. Useful for testing. + void IsOpenForTesting(base::OnceCallback<void(bool)> callback); + + // Asynchronously determines the database `InitStatus`. Useful for testing. + void DBStatusForTesting(base::OnceCallback<void(InitStatus)> callback); + + // Changes `last_used_time` to `override_last_used_time` for `context_origin`. + void OverrideLastUsedTimeForTesting(url::Origin context_origin, + base::Time override_last_used_time, + base::OnceCallback<void(bool)> callback); + + // Overrides the `SpecialStoragePolicy` for tests. + void OverrideSpecialStoragePolicyForTesting( + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy, + base::OnceCallback<void(bool)> callback); + + private: + // Instances should be obtained from the `Create()` factory method. + AsyncSharedStorageDatabase( + base::FilePath db_path, + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy, + std::unique_ptr<SharedStorageDatabaseOptions> options); + + base::SequenceBound<SharedStorageDatabase> database_; +}; + +} // namespace storage + +#endif // COMPONENTS_SERVICES_STORAGE_SHARED_STORAGE_ASYNC_SHARED_STORAGE_DATABASE_H_ diff --git a/chromium/components/services/storage/shared_storage/async_shared_storage_database_unittest.cc b/chromium/components/services/storage/shared_storage/async_shared_storage_database_unittest.cc new file mode 100644 index 00000000000..fdf3041a271 --- /dev/null +++ b/chromium/components/services/storage/shared_storage/async_shared_storage_database_unittest.cc @@ -0,0 +1,1648 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/services/storage/shared_storage/async_shared_storage_database.h" + +#include <memory> +#include <queue> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/files/file.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_refptr.h" +#include "base/run_loop.h" +#include "base/strings/strcat.h" +#include "base/task/sequenced_task_runner.h" +#include "base/task/thread_pool.h" +#include "base/test/bind.h" +#include "base/test/scoped_feature_list.h" +#include "base/test/task_environment.h" +#include "base/test/test_future.h" +#include "base/threading/sequence_bound.h" +#include "base/threading/thread.h" +#include "components/services/storage/public/mojom/storage_usage_info.mojom.h" +#include "components/services/storage/shared_storage/shared_storage_database.h" +#include "components/services/storage/shared_storage/shared_storage_options.h" +#include "components/services/storage/shared_storage/shared_storage_test_utils.h" +#include "sql/database.h" +#include "storage/browser/quota/special_storage_policy.h" +#include "storage/browser/test/mock_special_storage_policy.h" +#include "testing/gmock/include/gmock/gmock-matchers.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/common/features.h" +#include "url/gurl.h" +#include "url/origin.h" + +namespace storage { + +namespace { + +using ::testing::ElementsAre; +using InitStatus = SharedStorageDatabase::InitStatus; +using SetBehavior = SharedStorageDatabase::SetBehavior; +using OperationResult = SharedStorageDatabase::OperationResult; +using GetResult = SharedStorageDatabase::GetResult; +using DBOperation = TestDatabaseOperationReceiver::DBOperation; +using Type = DBOperation::Type; + +const int kMaxEntriesPerOrigin = 5; +const int kMaxStringLength = 100; + +} // namespace + +class AsyncSharedStorageDatabaseTest : public testing::Test { + public: + AsyncSharedStorageDatabaseTest() + : task_runner_(base::ThreadPool::CreateSequencedTaskRunner( + {base::MayBlock(), base::WithBaseSyncPrimitives(), + base::TaskShutdownBehavior::BLOCK_SHUTDOWN})), + special_storage_policy_( + base::MakeRefCounted<MockSpecialStoragePolicy>()), + receiver_(std::make_unique<TestDatabaseOperationReceiver>()) {} + + ~AsyncSharedStorageDatabaseTest() override = default; + + void SetUp() override { + InitSharedStorageFeature(); + + // Get a temporary directory for the test DB files. + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + file_name_ = temp_dir_.GetPath().AppendASCII("TestSharedStorage.db"); + } + + void TearDown() override { + task_environment_.RunUntilIdle(); + + // It's not strictly necessary as part of test cleanup, but here we test + // the code path that force razes the database, and, if file-backed, + // force deletes the database file. + EXPECT_TRUE(DestroySync()); + EXPECT_FALSE(base::PathExists(file_name_)); + EXPECT_TRUE(temp_dir_.Delete()); + } + + virtual void InitSharedStorageFeature() { + scoped_feature_list_.InitAndEnableFeatureWithParameters( + {blink::features::kSharedStorageAPI}, + {{"MaxSharedStorageInitTries", "1"}}); + } + + // Initialize an async shared storage database instance from the SQL file at + // `relative_file_path` in the "storage/" subdirectory of test data. + void LoadFromFileSync(const char* relative_file_path) { + DCHECK(!file_name_.empty()); + + ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, relative_file_path)); + + async_database_ = AsyncSharedStorageDatabase::Create( + file_name_, task_runner_, special_storage_policy_, + SharedStorageOptions::Create()->GetDatabaseOptions()); + } + + void CreateSync(const base::FilePath& db_path, + std::unique_ptr<SharedStorageDatabaseOptions> options) { + async_database_ = AsyncSharedStorageDatabase::Create( + db_path, task_runner_, special_storage_policy_, std::move(options)); + } + + void Destroy(bool* out_success) { + DCHECK(out_success); + DCHECK(async_database_); + DCHECK(receiver_); + + DBOperation operation(Type::DB_DESTROY); + auto callback = + receiver_->MakeBoolCallback(std::move(operation), out_success); + async_database_->Destroy(std::move(callback)); + } + + bool DestroySync() { + if (!async_database_) + return true; + + base::test::TestFuture<bool> future; + async_database_->Destroy(future.GetCallback()); + EXPECT_TRUE(future.Wait()); + return future.Get(); + } + + bool is_finished() const { + DCHECK(receiver_); + return receiver_->is_finished(); + } + + void SetExpectedOperationList(std::queue<DBOperation> expected_operations) { + DCHECK(receiver_); + receiver_->set_expected_operations(std::move(expected_operations)); + } + + void WaitForOperations() { + DCHECK(receiver_); + receiver_->WaitForOperations(); + } + + void VerifySharedStorageTablesAndColumnsSync() { + DCHECK(async_database_); + + auto task = base::BindOnce([](SharedStorageDatabase* db) -> bool { + auto* sql_db = db->db(); + EXPECT_TRUE(sql_db); + VerifySharedStorageTablesAndColumns(*sql_db); + return true; + }); + + base::test::TestFuture<bool> future; + + auto wrapped_task = base::BindOnce( + [](base::OnceCallback<bool(SharedStorageDatabase*)> task, + base::OnceCallback<void(bool)> callback, + scoped_refptr<base::SequencedTaskRunner> callback_task_runner, + SharedStorageDatabase* db) { + callback_task_runner->PostTask( + FROM_HERE, + base::BindOnce(std::move(callback), std::move(task).Run(db))); + }, + std::move(task), future.GetCallback(), + base::SequencedTaskRunnerHandle::Get()); + + async_database_->GetSequenceBoundDatabaseForTesting() + .PostTaskWithThisObject(std::move(wrapped_task)); + + EXPECT_TRUE(future.Wait()); + EXPECT_TRUE(future.Get()); + } + + void IsOpen(bool* out_boolean) { + DCHECK(out_boolean); + DCHECK(async_database_); + DCHECK(receiver_); + + DBOperation operation(Type::DB_IS_OPEN); + auto callback = + receiver_->MakeBoolCallback(std::move(operation), out_boolean); + async_database_->IsOpenForTesting(std::move(callback)); + } + + bool IsOpenSync() { + DCHECK(async_database_); + + base::test::TestFuture<bool> future; + async_database_->IsOpenForTesting(future.GetCallback()); + EXPECT_TRUE(future.Wait()); + return future.Get(); + } + + void DBStatus(InitStatus* out_status) { + DCHECK(out_status); + DCHECK(async_database_); + DCHECK(receiver_); + + DBOperation operation(Type::DB_STATUS); + auto callback = + receiver_->MakeStatusCallback(std::move(operation), out_status); + async_database_->DBStatusForTesting(std::move(callback)); + } + + InitStatus DBStatusSync() { + DCHECK(async_database_); + + base::test::TestFuture<InitStatus> future; + async_database_->DBStatusForTesting(future.GetCallback()); + EXPECT_TRUE(future.Wait()); + return future.Get(); + } + + void TrimMemory() { + DCHECK(async_database_); + DCHECK(receiver_); + + DBOperation operation(Type::DB_TRIM_MEMORY); + auto callback = receiver_->MakeOnceClosure(std::move(operation)); + async_database_->TrimMemory(std::move(callback)); + } + + void OverrideLastUsedTime(url::Origin context_origin, + base::Time override_last_used_time, + bool* out_success) { + DCHECK(out_success); + DCHECK(async_database_); + DCHECK(receiver_); + + DBOperation operation(Type::DB_OVERRIDE_TIME, context_origin, + {TestDatabaseOperationReceiver::SerializeTime( + override_last_used_time)}); + auto callback = + receiver_->MakeBoolCallback(std::move(operation), out_success); + async_database_->OverrideLastUsedTimeForTesting(std::move(context_origin), + override_last_used_time, + std::move(callback)); + } + + void Get(url::Origin context_origin, + std::u16string key, + GetResult* out_value) { + DCHECK(out_value); + DCHECK(async_database_); + DCHECK(receiver_); + + DBOperation operation(Type::DB_GET, context_origin, {key}); + auto callback = + receiver_->MakeGetResultCallback(std::move(operation), out_value); + async_database_->Get(std::move(context_origin), std::move(key), + std::move(callback)); + } + + GetResult GetSync(url::Origin context_origin, std::u16string key) { + DCHECK(async_database_); + + base::test::TestFuture<GetResult> future; + async_database_->Get(std::move(context_origin), std::move(key), + future.GetCallback()); + EXPECT_TRUE(future.Wait()); + return future.Get(); + } + + void Set(url::Origin context_origin, + std::u16string key, + std::u16string value, + OperationResult* out_result, + SetBehavior behavior = SetBehavior::kDefault) { + DCHECK(out_result); + DCHECK(async_database_); + DCHECK(receiver_); + + *out_result = OperationResult::kSqlError; + DBOperation operation( + Type::DB_SET, context_origin, + {key, value, base::NumberToString16(static_cast<int>(behavior))}); + auto callback = receiver_->MakeOperationResultCallback(std::move(operation), + out_result); + async_database_->Set(std::move(context_origin), std::move(key), + std::move(value), std::move(callback), behavior); + } + + OperationResult SetSync(url::Origin context_origin, + std::u16string key, + std::u16string value, + SetBehavior behavior = SetBehavior::kDefault) { + DCHECK(async_database_); + + base::test::TestFuture<OperationResult> future; + async_database_->Set(std::move(context_origin), std::move(key), + std::move(value), future.GetCallback(), behavior); + EXPECT_TRUE(future.Wait()); + return future.Get(); + } + + void Append(url::Origin context_origin, + std::u16string key, + std::u16string value, + OperationResult* out_result) { + DCHECK(out_result); + DCHECK(async_database_); + DCHECK(receiver_); + + *out_result = OperationResult::kSqlError; + DBOperation operation(Type::DB_APPEND, context_origin, {key, value}); + auto callback = receiver_->MakeOperationResultCallback(std::move(operation), + out_result); + async_database_->Append(std::move(context_origin), std::move(key), + std::move(value), std::move(callback)); + } + + OperationResult AppendSync(url::Origin context_origin, + std::u16string key, + std::u16string value) { + DCHECK(async_database_); + + base::test::TestFuture<OperationResult> future; + async_database_->Append(std::move(context_origin), std::move(key), + std::move(value), future.GetCallback()); + EXPECT_TRUE(future.Wait()); + return future.Get(); + } + + void Delete(url::Origin context_origin, + std::u16string key, + OperationResult* out_result) { + DCHECK(out_result); + DCHECK(async_database_); + DCHECK(receiver_); + + *out_result = OperationResult::kSqlError; + DBOperation operation(Type::DB_DELETE, context_origin, {key}); + auto callback = receiver_->MakeOperationResultCallback(std::move(operation), + out_result); + async_database_->Delete(std::move(context_origin), std::move(key), + std::move(callback)); + } + + OperationResult DeleteSync(url::Origin context_origin, std::u16string key) { + DCHECK(async_database_); + + base::test::TestFuture<OperationResult> future; + async_database_->Delete(std::move(context_origin), std::move(key), + future.GetCallback()); + EXPECT_TRUE(future.Wait()); + return future.Get(); + } + + void Length(url::Origin context_origin, int* out_length) { + DCHECK(out_length); + DCHECK(async_database_); + DCHECK(receiver_); + + *out_length = -1; + DBOperation operation(Type::DB_LENGTH, context_origin); + auto callback = + receiver_->MakeIntCallback(std::move(operation), out_length); + async_database_->Length(std::move(context_origin), std::move(callback)); + } + + int LengthSync(url::Origin context_origin) { + DCHECK(async_database_); + + base::test::TestFuture<int> future; + async_database_->Length(std::move(context_origin), future.GetCallback()); + EXPECT_TRUE(future.Wait()); + return future.Get(); + } + + void Key(url::Origin context_origin, int index, GetResult* out_key) { + DCHECK(out_key); + DCHECK(async_database_); + DCHECK(receiver_); + + DBOperation operation(Type::DB_KEY, context_origin, + {base::NumberToString16(index)}); + auto callback = + receiver_->MakeGetResultCallback(std::move(operation), out_key); + async_database_->Key(std::move(context_origin), index, std::move(callback)); + } + + GetResult KeySync(url::Origin context_origin, int index) { + DCHECK(async_database_); + + base::test::TestFuture<GetResult> future; + async_database_->Key(std::move(context_origin), index, + future.GetCallback()); + EXPECT_TRUE(future.Wait()); + return future.Get(); + } + + void Clear(url::Origin context_origin, OperationResult* out_result) { + DCHECK(out_result); + DCHECK(async_database_); + DCHECK(receiver_); + + *out_result = OperationResult::kSqlError; + DBOperation operation(Type::DB_CLEAR, context_origin); + auto callback = receiver_->MakeOperationResultCallback(std::move(operation), + out_result); + async_database_->Clear(std::move(context_origin), std::move(callback)); + } + + OperationResult ClearSync(url::Origin context_origin) { + DCHECK(async_database_); + + base::test::TestFuture<OperationResult> future; + async_database_->Clear(std::move(context_origin), future.GetCallback()); + EXPECT_TRUE(future.Wait()); + return future.Get(); + } + + void FetchOrigins(std::vector<mojom::StorageUsageInfoPtr>* out_result) { + DCHECK(out_result); + DCHECK(async_database_); + DCHECK(receiver_); + + DBOperation operation(Type::DB_FETCH_ORIGINS); + auto callback = + receiver_->MakeInfosCallback(std::move(operation), out_result); + async_database_->FetchOrigins(std::move(callback)); + } + + std::vector<mojom::StorageUsageInfoPtr> FetchOriginsSync() { + DCHECK(async_database_); + + base::test::TestFuture<std::vector<mojom::StorageUsageInfoPtr>> future; + async_database_->FetchOrigins(future.GetCallback()); + EXPECT_TRUE(future.Wait()); + return future.Take(); + } + + void PurgeMatchingOrigins(OriginMatcherFunctionUtility* matcher_utility, + size_t matcher_id, + base::Time begin, + base::Time end, + OperationResult* out_result, + bool perform_storage_cleanup = false) { + DCHECK(out_result); + DCHECK(async_database_); + DCHECK(receiver_); + DCHECK(matcher_utility); + DCHECK(!(*matcher_utility).is_empty()); + DCHECK_LT(matcher_id, (*matcher_utility).size()); + + std::vector<std::u16string> params( + {base::NumberToString16(matcher_id), + TestDatabaseOperationReceiver::SerializeTime(begin), + TestDatabaseOperationReceiver::SerializeTime(end), + TestDatabaseOperationReceiver::SerializeBool( + perform_storage_cleanup)}); + DBOperation operation(Type::DB_PURGE_MATCHING, std::move(params)); + auto callback = receiver_->MakeOperationResultCallback(std::move(operation), + out_result); + async_database_->PurgeMatchingOrigins( + matcher_utility->TakeMatcherFunctionForId(matcher_id), begin, end, + std::move(callback), perform_storage_cleanup); + } + + void PurgeStaleOrigins(base::TimeDelta window_to_be_deemed_active, + OperationResult* out_result) { + DCHECK(out_result); + DCHECK(async_database_); + DCHECK(receiver_); + + DBOperation operation(Type::DB_PURGE_STALE, + {TestDatabaseOperationReceiver::SerializeTimeDelta( + window_to_be_deemed_active)}); + auto callback = receiver_->MakeOperationResultCallback(std::move(operation), + out_result); + async_database_->PurgeStaleOrigins(window_to_be_deemed_active, + std::move(callback)); + } + + protected: + base::test::TaskEnvironment task_environment_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + scoped_refptr<storage::MockSpecialStoragePolicy> special_storage_policy_; + std::unique_ptr<AsyncSharedStorageDatabase> async_database_; + std::unique_ptr<TestDatabaseOperationReceiver> receiver_; + base::test::ScopedFeatureList scoped_feature_list_; + base::ScopedTempDir temp_dir_; + base::FilePath file_name_; +}; + +// Test loading version 1 database. +TEST_F(AsyncSharedStorageDatabaseTest, Version1_LoadFromFile) { + LoadFromFileSync("shared_storage.v1.sql"); + ASSERT_TRUE(async_database_); + + url::Origin google_com = url::Origin::Create(GURL("http://google.com/")); + EXPECT_EQ(GetSync(google_com, u"key1").data, u"value1"); + EXPECT_EQ(GetSync(google_com, u"key2").data, u"value2"); + + // Because the SQL database is lazy-initialized, wait to verify tables and + // columns until after the first call to `GetSync()`. + VerifySharedStorageTablesAndColumnsSync(); + + std::vector<url::Origin> origins; + for (const auto& info : FetchOriginsSync()) + origins.push_back(info->origin); + EXPECT_THAT( + origins, + ElementsAre(url::Origin::Create(GURL("http://abc.xyz")), + url::Origin::Create(GURL("http://chromium.org")), google_com, + url::Origin::Create(GURL("http://google.org")), + url::Origin::Create(GURL("http://growwithgoogle.com")), + url::Origin::Create(GURL("http://gv.com")), + url::Origin::Create(GURL("http://waymo.com")), + url::Origin::Create(GURL("http://withgoogle.com")), + url::Origin::Create(GURL("http://youtube.com")))); +} + +TEST_F(AsyncSharedStorageDatabaseTest, Version1_DestroyTooNew) { + // Initialization should fail, since the last compatible version number + // is too high. + LoadFromFileSync("shared_storage.v1.init_too_new.sql"); + ASSERT_TRUE(async_database_); + + // Call an operation so that the database will attempt to be lazy-initialized. + EXPECT_EQ( + OperationResult::kInitFailure, + SetSync(url::Origin::Create(GURL("http://www.a.com")), u"key", u"value")); + ASSERT_FALSE(IsOpenSync()); + EXPECT_EQ(InitStatus::kTooNew, DBStatusSync()); + + // Test that it is still OK to `Destroy()` the database. + EXPECT_TRUE(DestroySync()); +} + +TEST_F(AsyncSharedStorageDatabaseTest, Version0_DestroyTooOld) { + // Initialization should fail, since the current version number + // is too low and we're forcing there not to be a retry attempt. + LoadFromFileSync("shared_storage.v0.init_too_old.sql"); + ASSERT_TRUE(async_database_); + + // Call an operation so that the database will attempt to be lazy-initialized. + EXPECT_EQ( + OperationResult::kInitFailure, + SetSync(url::Origin::Create(GURL("http://www.a.com")), u"key", u"value")); + ASSERT_FALSE(IsOpenSync()); + EXPECT_EQ(InitStatus::kTooOld, DBStatusSync()); + + // Test that it is still OK to `Destroy()` the database. + EXPECT_TRUE(DestroySync()); +} + +class AsyncSharedStorageDatabaseParamTest + : public AsyncSharedStorageDatabaseTest, + public testing::WithParamInterface<SharedStorageWrappedBool> { + public: + void SetUp() override { + AsyncSharedStorageDatabaseTest::SetUp(); + + auto options = SharedStorageOptions::Create()->GetDatabaseOptions(); + + if (GetParam().in_memory_only) + CreateSync(base::FilePath(), std::move(options)); + else + CreateSync(file_name_, std::move(options)); + } + + void TearDown() override { + if (!GetParam().in_memory_only) { + // `TearDown()` will call `DestroySync()`. First verify that the file + // exists, so that when the we verify after destruction in `TearDown()` + // that the file no longer exists, we know that `Destroy()` was indeed + // successful. + EXPECT_TRUE(base::PathExists(file_name_)); + } + + AsyncSharedStorageDatabaseTest::TearDown(); + } + + void InitSharedStorageFeature() override { + scoped_feature_list_.InitAndEnableFeatureWithParameters( + {blink::features::kSharedStorageAPI}, + {{"MaxSharedStorageEntriesPerOrigin", + base::NumberToString(kMaxEntriesPerOrigin)}, + {"MaxSharedStorageStringLength", + base::NumberToString(kMaxStringLength)}}); + } +}; + +INSTANTIATE_TEST_SUITE_P(All, + AsyncSharedStorageDatabaseParamTest, + testing::ValuesIn(GetSharedStorageWrappedBools()), + testing::PrintToStringParamName()); + +// Operations are tested more thoroughly in shared_storage_database_unittest.cc. +TEST_P(AsyncSharedStorageDatabaseParamTest, SyncOperations) { + url::Origin kOrigin1 = url::Origin::Create(GURL("http://www.example1.test")); + EXPECT_EQ(OperationResult::kSet, SetSync(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(GetSync(kOrigin1, u"key1").data, u"value1"); + + EXPECT_EQ(OperationResult::kSet, SetSync(kOrigin1, u"key1", u"value2")); + EXPECT_EQ(GetSync(kOrigin1, u"key1").data, u"value2"); + + EXPECT_EQ(OperationResult::kSet, SetSync(kOrigin1, u"key2", u"value1")); + EXPECT_EQ(GetSync(kOrigin1, u"key2").data, u"value1"); + EXPECT_EQ(2, LengthSync(kOrigin1)); + + EXPECT_EQ(OperationResult::kSuccess, DeleteSync(kOrigin1, u"key1")); + EXPECT_FALSE(GetSync(kOrigin1, u"key1").data); + EXPECT_EQ(1, LengthSync(kOrigin1)); + + EXPECT_EQ(OperationResult::kSet, AppendSync(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(GetSync(kOrigin1, u"key1").data, u"value1"); + EXPECT_EQ(2, LengthSync(kOrigin1)); + + EXPECT_EQ(OperationResult::kSet, AppendSync(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(GetSync(kOrigin1, u"key1").data, + base::StrCat({u"value1", u"value1"})); + EXPECT_EQ(2, LengthSync(kOrigin1)); + + EXPECT_EQ(KeySync(kOrigin1, 0).data, u"key1"); + EXPECT_EQ(KeySync(kOrigin1, 1).data, u"key2"); + + EXPECT_EQ(OperationResult::kSuccess, ClearSync(kOrigin1)); + EXPECT_EQ(0, LengthSync(kOrigin1)); +} + +// Verifies that the async operations are executed in order and without races. +TEST_P(AsyncSharedStorageDatabaseParamTest, AsyncOperations) { + url::Origin kOrigin1 = url::Origin::Create(GURL("http://www.example1.test")); + + std::queue<DBOperation> operation_list( + {{Type::DB_SET, + kOrigin1, + {u"key1", u"value1", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)}}, + {Type::DB_GET, kOrigin1, {u"key1"}}, + {Type::DB_SET, + kOrigin1, + {u"key1", u"value2", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)}}, + {Type::DB_GET, kOrigin1, {u"key1"}}, + {Type::DB_SET, + kOrigin1, + {u"key2", u"value1", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)}}, + {Type::DB_GET, kOrigin1, {u"key2"}}, + {Type::DB_LENGTH, kOrigin1}, + {Type::DB_DELETE, kOrigin1, {u"key1"}}, + {Type::DB_LENGTH, kOrigin1}, + {Type::DB_APPEND, kOrigin1, {u"key1", u"value1"}}, + {Type::DB_GET, kOrigin1, {u"key1"}}, + {Type::DB_LENGTH, kOrigin1}, + {Type::DB_APPEND, kOrigin1, {u"key1", u"value1"}}, + {Type::DB_GET, kOrigin1, {u"key1"}}, + {Type::DB_LENGTH, kOrigin1}, + {Type::DB_KEY, kOrigin1, {base::NumberToString16(0)}}, + {Type::DB_KEY, kOrigin1, {base::NumberToString16(1)}}, + {Type::DB_CLEAR, kOrigin1}, + {Type::DB_LENGTH, kOrigin1}}); + + SetExpectedOperationList(std::move(operation_list)); + + OperationResult result1 = OperationResult::kSqlError; + Set(kOrigin1, u"key1", u"value1", &result1); + GetResult value1; + Get(kOrigin1, u"key1", &value1); + OperationResult result2 = OperationResult::kSqlError; + Set(kOrigin1, u"key1", u"value2", &result2); + GetResult value2; + Get(kOrigin1, u"key1", &value2); + OperationResult result3 = OperationResult::kSqlError; + Set(kOrigin1, u"key2", u"value1", &result3); + GetResult value3; + Get(kOrigin1, u"key2", &value3); + int length1 = -1; + Length(kOrigin1, &length1); + + OperationResult result4 = OperationResult::kSqlError; + Delete(kOrigin1, u"key1", &result4); + int length2 = -1; + Length(kOrigin1, &length2); + + OperationResult result5 = OperationResult::kSqlError; + Append(kOrigin1, u"key1", u"value1", &result5); + GetResult value4; + Get(kOrigin1, u"key1", &value4); + int length3 = -1; + Length(kOrigin1, &length3); + + OperationResult result6 = OperationResult::kSqlError; + Append(kOrigin1, u"key1", u"value1", &result6); + GetResult value5; + Get(kOrigin1, u"key1", &value5); + int length4 = -1; + Length(kOrigin1, &length4); + + GetResult key1; + Key(kOrigin1, 0, &key1); + GetResult key2; + Key(kOrigin1, 1, &key2); + + OperationResult result7 = OperationResult::kSqlError; + Clear(kOrigin1, &result7); + int length5 = -1; + Length(kOrigin1, &length5); + + WaitForOperations(); + EXPECT_TRUE(is_finished()); + + EXPECT_EQ(OperationResult::kSet, result1); + EXPECT_EQ(value1.data, u"value1"); + EXPECT_EQ(OperationResult::kSet, result2); + EXPECT_EQ(value2.data, u"value2"); + EXPECT_EQ(OperationResult::kSet, result3); + EXPECT_EQ(value3.data, u"value1"); + EXPECT_EQ(2, length1); + + EXPECT_EQ(OperationResult::kSuccess, result4); + EXPECT_EQ(1, length2); + + EXPECT_EQ(OperationResult::kSet, result5); + EXPECT_EQ(value4.data, u"value1"); + EXPECT_EQ(2, length3); + + EXPECT_EQ(OperationResult::kSet, result6); + EXPECT_EQ(value5.data, u"value1value1"); + EXPECT_EQ(2, length4); + + EXPECT_EQ(key1.data, u"key1"); + EXPECT_EQ(key2.data, u"key2"); + + EXPECT_EQ(OperationResult::kSuccess, result7); + EXPECT_EQ(0, length5); +} + +TEST_P(AsyncSharedStorageDatabaseParamTest, + LazyInit_IgnoreForGet_CreateForSet) { + url::Origin kOrigin1 = url::Origin::Create(GURL("http://www.example1.test")); + + std::queue<DBOperation> operation_list; + operation_list.push(DBOperation(Type::DB_IS_OPEN)); + operation_list.push(DBOperation(Type::DB_STATUS)); + operation_list.push(DBOperation(Type::DB_GET, kOrigin1, {u"key1"})); + operation_list.push(DBOperation(Type::DB_IS_OPEN)); + operation_list.push(DBOperation(Type::DB_STATUS)); + operation_list.push( + DBOperation(Type::DB_SET, kOrigin1, + {u"key1", u"value1", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push(DBOperation(Type::DB_IS_OPEN)); + operation_list.push(DBOperation(Type::DB_STATUS)); + operation_list.push(DBOperation(Type::DB_GET, kOrigin1, {u"key1"})); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin1)); + + SetExpectedOperationList(std::move(operation_list)); + + bool open1 = false; + IsOpen(&open1); + InitStatus status1 = InitStatus::kUnattempted; + DBStatus(&status1); + + // Test that we can successfully call `Get()` on a nonexistent key before the + // database is initialized. + GetResult value1; + Get(kOrigin1, u"key1", &value1); + bool open2 = false; + IsOpen(&open2); + InitStatus status2 = InitStatus::kUnattempted; + DBStatus(&status2); + + // Call an operation that initializes the database. + OperationResult result1 = OperationResult::kSqlError; + Set(kOrigin1, u"key1", u"value1", &result1); + bool open3 = false; + IsOpen(&open3); + InitStatus status3 = InitStatus::kUnattempted; + DBStatus(&status3); + + GetResult value2; + Get(kOrigin1, u"key1", &value2); + int length1 = -1; + Length(kOrigin1, &length1); + + WaitForOperations(); + EXPECT_TRUE(is_finished()); + + EXPECT_FALSE(open1); + EXPECT_EQ(InitStatus::kUnattempted, status1); + + EXPECT_FALSE(value1.data); + EXPECT_EQ(OperationResult::kSuccess, value1.result); + EXPECT_EQ(!GetParam().in_memory_only, open2); + EXPECT_EQ(InitStatus::kUnattempted, status2); + + EXPECT_EQ(OperationResult::kSet, result1); + EXPECT_TRUE(open3); + EXPECT_EQ(InitStatus::kSuccess, status3); + + EXPECT_EQ(value2.data, u"value1"); + EXPECT_EQ(OperationResult::kSuccess, value2.result); + EXPECT_EQ(1, length1); +} + +TEST_P(AsyncSharedStorageDatabaseParamTest, + LazyInit_IgnoreForDelete_CreateForAppend) { + url::Origin kOrigin1 = url::Origin::Create(GURL("http://www.example1.test")); + + std::queue<DBOperation> operation_list; + operation_list.push(DBOperation(Type::DB_IS_OPEN)); + operation_list.push(DBOperation(Type::DB_STATUS)); + operation_list.push(DBOperation(Type::DB_DELETE, kOrigin1, {u"key1"})); + operation_list.push(DBOperation(Type::DB_IS_OPEN)); + operation_list.push(DBOperation(Type::DB_STATUS)); + operation_list.push( + DBOperation(Type::DB_APPEND, kOrigin1, {u"key1", u"value1"})); + operation_list.push(DBOperation(Type::DB_IS_OPEN)); + operation_list.push(DBOperation(Type::DB_STATUS)); + operation_list.push(DBOperation(Type::DB_DELETE, kOrigin1, {u"key2"})); + + SetExpectedOperationList(std::move(operation_list)); + + bool open1 = false; + IsOpen(&open1); + InitStatus status1 = InitStatus::kUnattempted; + DBStatus(&status1); + + // Test that we can successfully call `Delete()` on a nonexistent key before + // the database is initialized. + OperationResult result1 = OperationResult::kSqlError; + Delete(kOrigin1, u"key1", &result1); + bool open2 = false; + IsOpen(&open2); + InitStatus status2 = InitStatus::kUnattempted; + DBStatus(&status2); + + // Call an operation that initializes the database. + OperationResult result2 = OperationResult::kSqlError; + Append(kOrigin1, u"key1", u"value1", &result2); + bool open3 = false; + IsOpen(&open3); + InitStatus status3 = InitStatus::kUnattempted; + DBStatus(&status3); + + // Test that we can successfully call `Delete()` on a nonexistent key after + // the database is initialized. + OperationResult result3 = OperationResult::kSqlError; + Delete(kOrigin1, u"key2", &result3); + + WaitForOperations(); + EXPECT_TRUE(is_finished()); + + EXPECT_FALSE(open1); + EXPECT_EQ(InitStatus::kUnattempted, status1); + + EXPECT_EQ(OperationResult::kSuccess, result1); + EXPECT_EQ(!GetParam().in_memory_only, open2); + EXPECT_EQ(InitStatus::kUnattempted, status2); + + EXPECT_EQ(OperationResult::kSet, result2); + EXPECT_TRUE(open3); + EXPECT_EQ(InitStatus::kSuccess, status3); + + EXPECT_EQ(OperationResult::kSuccess, result3); +} + +TEST_P(AsyncSharedStorageDatabaseParamTest, + LazyInit_IgnoreForClear_CreateForAppend) { + url::Origin kOrigin1 = url::Origin::Create(GURL("http://www.example1.test")); + url::Origin kOrigin2 = url::Origin::Create(GURL("http://www.example2.test")); + + std::queue<DBOperation> operation_list; + operation_list.push(DBOperation(Type::DB_IS_OPEN)); + operation_list.push(DBOperation(Type::DB_STATUS)); + operation_list.push(DBOperation(Type::DB_CLEAR, kOrigin1)); + operation_list.push(DBOperation(Type::DB_IS_OPEN)); + operation_list.push(DBOperation(Type::DB_STATUS)); + operation_list.push( + DBOperation(Type::DB_APPEND, kOrigin1, {u"key1", u"value1"})); + operation_list.push(DBOperation(Type::DB_IS_OPEN)); + operation_list.push(DBOperation(Type::DB_STATUS)); + operation_list.push(DBOperation(Type::DB_CLEAR, kOrigin2)); + + SetExpectedOperationList(std::move(operation_list)); + + bool open1 = false; + IsOpen(&open1); + InitStatus status1 = InitStatus::kUnattempted; + DBStatus(&status1); + + // Test that we can successfully call `Clear()` on a nonexistent origin before + // the database is initialized. + OperationResult result1 = OperationResult::kSqlError; + Clear(kOrigin1, &result1); + bool open2 = false; + IsOpen(&open2); + InitStatus status2 = InitStatus::kUnattempted; + DBStatus(&status2); + + // Call an operation that initializes the database. + OperationResult result2 = OperationResult::kSqlError; + Append(kOrigin1, u"key1", u"value1", &result2); + bool open3 = false; + IsOpen(&open3); + InitStatus status3 = InitStatus::kUnattempted; + DBStatus(&status3); + + // Test that we can successfully call `Clear()` on a nonexistent origin after + // the database is initialized. + OperationResult result3 = OperationResult::kSqlError; + Clear(kOrigin2, &result3); + + WaitForOperations(); + EXPECT_TRUE(is_finished()); + + EXPECT_FALSE(open1); + EXPECT_EQ(InitStatus::kUnattempted, status1); + + EXPECT_EQ(OperationResult::kSuccess, result1); + EXPECT_EQ(!GetParam().in_memory_only, open2); + EXPECT_EQ(InitStatus::kUnattempted, status2); + + EXPECT_EQ(OperationResult::kSet, result2); + EXPECT_TRUE(open3); + EXPECT_EQ(InitStatus::kSuccess, status3); + + EXPECT_EQ(OperationResult::kSuccess, result3); +} + +TEST_P(AsyncSharedStorageDatabaseParamTest, PurgeStaleOrigins) { + url::Origin kOrigin1 = url::Origin::Create(GURL("http://www.example1.test")); + url::Origin kOrigin2 = url::Origin::Create(GURL("http://www.example2.test")); + url::Origin kOrigin3 = url::Origin::Create(GURL("http://www.example3.test")); + url::Origin kOrigin4 = url::Origin::Create(GURL("http://www.example4.test")); + + std::queue<DBOperation> operation_list; + operation_list.push(DBOperation(Type::DB_FETCH_ORIGINS)); + operation_list.push(DBOperation(Type::DB_IS_OPEN)); + operation_list.push(DBOperation(Type::DB_STATUS)); + + operation_list.push(DBOperation( + Type::DB_PURGE_STALE, + {TestDatabaseOperationReceiver::SerializeTimeDelta(base::Days(1))})); + + operation_list.push( + DBOperation(Type::DB_SET, kOrigin1, + {u"key1", u"value1", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push(DBOperation(Type::DB_IS_OPEN)); + operation_list.push(DBOperation(Type::DB_STATUS)); + + operation_list.push( + DBOperation(Type::DB_SET, kOrigin1, + {u"key2", u"value2", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin1)); + + operation_list.push( + DBOperation(Type::DB_SET, kOrigin2, + {u"key1", u"value1", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin2)); + + operation_list.push( + DBOperation(Type::DB_SET, kOrigin3, + {u"key1", u"value1", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push( + DBOperation(Type::DB_SET, kOrigin3, + {u"key2", u"value2", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push( + DBOperation(Type::DB_SET, kOrigin3, + {u"key3", u"value3", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin3)); + + operation_list.push( + DBOperation(Type::DB_SET, kOrigin4, + {u"key1", u"value1", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push( + DBOperation(Type::DB_SET, kOrigin4, + {u"key2", u"value2", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push( + DBOperation(Type::DB_SET, kOrigin4, + {u"key3", u"value3", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push( + DBOperation(Type::DB_SET, kOrigin4, + {u"key4", u"value4", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin4)); + + operation_list.push(DBOperation(Type::DB_FETCH_ORIGINS)); + + base::Time override_time1 = base::Time::Now() - base::Days(2); + operation_list.push(DBOperation( + Type::DB_OVERRIDE_TIME, kOrigin1, + {TestDatabaseOperationReceiver::SerializeTime(override_time1)})); + operation_list.push(DBOperation( + Type::DB_PURGE_STALE, + {TestDatabaseOperationReceiver::SerializeTimeDelta(base::Days(1))})); + + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin1)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin2)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin3)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin4)); + operation_list.push(DBOperation(Type::DB_FETCH_ORIGINS)); + + base::Time override_time2 = base::Time::Now() - base::Hours(2); + operation_list.push(DBOperation( + Type::DB_OVERRIDE_TIME, kOrigin3, + {TestDatabaseOperationReceiver::SerializeTime(override_time2)})); + operation_list.push(DBOperation( + Type::DB_PURGE_STALE, + {TestDatabaseOperationReceiver::SerializeTimeDelta(base::Hours(1))})); + + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin1)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin2)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin3)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin4)); + + operation_list.push(DBOperation(Type::DB_TRIM_MEMORY)); + operation_list.push(DBOperation(Type::DB_FETCH_ORIGINS)); + + operation_list.push(DBOperation(Type::DB_GET, kOrigin2, {u"key1"})); + operation_list.push(DBOperation(Type::DB_GET, kOrigin4, {u"key1"})); + operation_list.push(DBOperation(Type::DB_GET, kOrigin4, {u"key2"})); + operation_list.push(DBOperation(Type::DB_GET, kOrigin4, {u"key3"})); + operation_list.push(DBOperation(Type::DB_GET, kOrigin4, {u"key4"})); + + SetExpectedOperationList(std::move(operation_list)); + + // Check that origin list is initially empty due to the database not being + // initialized. + std::vector<mojom::StorageUsageInfoPtr> infos1; + FetchOrigins(&infos1); + + // Check that calling `PurgeStaleOrigins()` on the uninitialized database + // doesn't give an error. + bool open1 = false; + IsOpen(&open1); + InitStatus status1 = InitStatus::kUnattempted; + DBStatus(&status1); + + OperationResult result1 = OperationResult::kSqlError; + PurgeStaleOrigins(base::Days(1), &result1); + + OperationResult result2 = OperationResult::kSqlError; + Set(kOrigin1, u"key1", u"value1", &result2); + + bool open2 = false; + IsOpen(&open2); + InitStatus status2 = InitStatus::kUnattempted; + DBStatus(&status2); + + OperationResult result3 = OperationResult::kSqlError; + Set(kOrigin1, u"key2", u"value2", &result3); + int length1 = -1; + Length(kOrigin1, &length1); + + OperationResult result4 = OperationResult::kSqlError; + Set(kOrigin2, u"key1", u"value1", &result4); + int length2 = -1; + Length(kOrigin2, &length2); + + OperationResult result5 = OperationResult::kSqlError; + Set(kOrigin3, u"key1", u"value1", &result5); + OperationResult result6 = OperationResult::kSqlError; + Set(kOrigin3, u"key2", u"value2", &result6); + OperationResult result7 = OperationResult::kSqlError; + Set(kOrigin3, u"key3", u"value3", &result7); + int length3 = -1; + Length(kOrigin3, &length3); + + OperationResult result8 = OperationResult::kSqlError; + Set(kOrigin4, u"key1", u"value1", &result8); + OperationResult result9 = OperationResult::kSqlError; + Set(kOrigin4, u"key2", u"value2", &result9); + OperationResult result10 = OperationResult::kSqlError; + Set(kOrigin4, u"key3", u"value3", &result10); + OperationResult result11 = OperationResult::kSqlError; + Set(kOrigin4, u"key4", u"value4", &result11); + int length4 = -1; + Length(kOrigin4, &length4); + + std::vector<mojom::StorageUsageInfoPtr> infos2; + FetchOrigins(&infos2); + + bool success1 = false; + OverrideLastUsedTime(kOrigin1, override_time1, &success1); + + OperationResult result12 = OperationResult::kSqlError; + PurgeStaleOrigins(base::Days(1), &result12); + + int length5 = -1; + Length(kOrigin1, &length5); + int length6 = -1; + Length(kOrigin2, &length6); + int length7 = -1; + Length(kOrigin3, &length7); + int length8 = -1; + Length(kOrigin4, &length8); + + std::vector<mojom::StorageUsageInfoPtr> infos3; + FetchOrigins(&infos3); + + bool success2 = false; + OverrideLastUsedTime(kOrigin3, override_time2, &success2); + + OperationResult result13 = OperationResult::kSqlError; + PurgeStaleOrigins(base::Hours(1), &result13); + + int length9 = -1; + Length(kOrigin1, &length9); + int length10 = -1; + Length(kOrigin2, &length10); + int length11 = -1; + Length(kOrigin3, &length11); + int length12 = -1; + Length(kOrigin4, &length12); + + TrimMemory(); + + std::vector<mojom::StorageUsageInfoPtr> infos4; + FetchOrigins(&infos4); + + GetResult value1; + Get(kOrigin2, u"key1", &value1); + GetResult value2; + Get(kOrigin4, u"key1", &value2); + GetResult value3; + Get(kOrigin4, u"key2", &value3); + GetResult value4; + Get(kOrigin4, u"key3", &value4); + GetResult value5; + Get(kOrigin4, u"key4", &value5); + + WaitForOperations(); + EXPECT_TRUE(is_finished()); + + // Database is not yet initialized. `FetchOrigins()` returns an empty vector. + EXPECT_TRUE(infos1.empty()); + EXPECT_EQ(!GetParam().in_memory_only, open1); + EXPECT_EQ(InitStatus::kUnattempted, status1); + + // No error from calling `PurgeStaleOrigins()` on an uninitialized + // database. + EXPECT_EQ(OperationResult::kSuccess, result1); + + // The call to `Set()` initializes the database. + EXPECT_EQ(OperationResult::kSet, result2); + EXPECT_TRUE(open2); + EXPECT_EQ(InitStatus::kSuccess, status2); + + EXPECT_EQ(OperationResult::kSet, result3); + EXPECT_EQ(2, length1); + + EXPECT_EQ(OperationResult::kSet, result4); + EXPECT_EQ(1, length2); + + EXPECT_EQ(OperationResult::kSet, result5); + EXPECT_EQ(OperationResult::kSet, result6); + EXPECT_EQ(OperationResult::kSet, result7); + EXPECT_EQ(3, length3); + + EXPECT_EQ(OperationResult::kSet, result8); + EXPECT_EQ(OperationResult::kSet, result9); + EXPECT_EQ(OperationResult::kSet, result10); + EXPECT_EQ(OperationResult::kSet, result11); + EXPECT_EQ(4, length4); + + std::vector<url::Origin> origins; + for (const auto& info : infos2) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin1, kOrigin2, kOrigin3, kOrigin4)); + + EXPECT_TRUE(success1); + EXPECT_EQ(OperationResult::kSuccess, result12); + + // `kOrigin1` is cleared. The other origins are not. + EXPECT_EQ(0, length5); + EXPECT_EQ(1, length6); + EXPECT_EQ(3, length7); + EXPECT_EQ(4, length8); + + origins.clear(); + for (const auto& info : infos3) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin2, kOrigin3, kOrigin4)); + + EXPECT_TRUE(success2); + EXPECT_EQ(OperationResult::kSuccess, result13); + + // `kOrigin3` is cleared. The other remaining ones are not. + EXPECT_EQ(0, length9); + EXPECT_EQ(1, length10); + EXPECT_EQ(0, length11); + EXPECT_EQ(4, length12); + + origins.clear(); + for (const auto& info : infos4) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin2, kOrigin4)); + + // Database is still intact after trimming memory. + EXPECT_EQ(value1.data, u"value1"); + EXPECT_EQ(OperationResult::kSuccess, value1.result); + EXPECT_EQ(value2.data, u"value1"); + EXPECT_EQ(OperationResult::kSuccess, value2.result); + EXPECT_EQ(value3.data, u"value2"); + EXPECT_EQ(OperationResult::kSuccess, value3.result); + EXPECT_EQ(value4.data, u"value3"); + EXPECT_EQ(OperationResult::kSuccess, value4.result); + EXPECT_EQ(value5.data, u"value4"); + EXPECT_EQ(OperationResult::kSuccess, value5.result); +} + +class AsyncSharedStorageDatabasePurgeMatchingOriginsParamTest + : public AsyncSharedStorageDatabaseTest, + public testing::WithParamInterface<PurgeMatchingOriginsParams> { + public: + void SetUp() override { + AsyncSharedStorageDatabaseTest::SetUp(); + + auto options = SharedStorageOptions::Create()->GetDatabaseOptions(); + + if (GetParam().in_memory_only) + CreateSync(base::FilePath(), std::move(options)); + else + CreateSync(file_name_, std::move(options)); + } + + void InitSharedStorageFeature() override { + scoped_feature_list_.InitAndEnableFeatureWithParameters( + {blink::features::kSharedStorageAPI}, + {{"MaxSharedStorageEntriesPerOrigin", + base::NumberToString(kMaxEntriesPerOrigin)}, + {"MaxSharedStorageStringLength", + base::NumberToString(kMaxStringLength)}}); + } +}; + +INSTANTIATE_TEST_SUITE_P( + All, + AsyncSharedStorageDatabasePurgeMatchingOriginsParamTest, + testing::ValuesIn(GetPurgeMatchingOriginsParams()), + testing::PrintToStringParamName()); + +TEST_P(AsyncSharedStorageDatabasePurgeMatchingOriginsParamTest, + SinceThreshold) { + url::Origin kOrigin1 = url::Origin::Create(GURL("http://www.example1.test")); + url::Origin kOrigin2 = url::Origin::Create(GURL("http://www.example2.test")); + url::Origin kOrigin3 = url::Origin::Create(GURL("http://www.example3.test")); + url::Origin kOrigin4 = url::Origin::Create(GURL("http://www.example4.test")); + url::Origin kOrigin5 = url::Origin::Create(GURL("http://www.example5.test")); + + std::queue<DBOperation> operation_list; + operation_list.push(DBOperation(Type::DB_FETCH_ORIGINS)); + operation_list.push(DBOperation(Type::DB_IS_OPEN)); + operation_list.push(DBOperation(Type::DB_STATUS)); + + base::Time threshold1 = base::Time::Now(); + OriginMatcherFunctionUtility matcher_utility; + size_t matcher_id1 = matcher_utility.RegisterMatcherFunction({kOrigin1}); + + operation_list.push(DBOperation( + Type::DB_PURGE_MATCHING, + {base::NumberToString16(matcher_id1), + TestDatabaseOperationReceiver::SerializeTime(threshold1), + TestDatabaseOperationReceiver::SerializeTime(base::Time::Max()), + TestDatabaseOperationReceiver::SerializeBool( + GetParam().perform_storage_cleanup)})); + + operation_list.push( + DBOperation(Type::DB_SET, kOrigin1, + {u"key1", u"value1", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push(DBOperation(Type::DB_IS_OPEN)); + operation_list.push(DBOperation(Type::DB_STATUS)); + + operation_list.push( + DBOperation(Type::DB_SET, kOrigin1, + {u"key2", u"value2", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin1)); + + operation_list.push( + DBOperation(Type::DB_SET, kOrigin2, + {u"key1", u"value1", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin2)); + + operation_list.push( + DBOperation(Type::DB_SET, kOrigin3, + {u"key1", u"value1", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push( + DBOperation(Type::DB_SET, kOrigin3, + {u"key2", u"value2", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push( + DBOperation(Type::DB_SET, kOrigin3, + {u"key3", u"value3", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin3)); + + operation_list.push( + DBOperation(Type::DB_SET, kOrigin4, + {u"key1", u"value1", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push( + DBOperation(Type::DB_SET, kOrigin4, + {u"key2", u"value2", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push( + DBOperation(Type::DB_SET, kOrigin4, + {u"key3", u"value3", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push( + DBOperation(Type::DB_SET, kOrigin4, + {u"key4", u"value4", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin4)); + + operation_list.push( + DBOperation(Type::DB_SET, kOrigin5, + {u"key1", u"value1", + TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior::kDefault)})); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin5)); + + operation_list.push(DBOperation(Type::DB_FETCH_ORIGINS)); + + base::Time threshold2 = base::Time::Now() + base::Seconds(100); + base::Time override_time1 = threshold2 + base::Milliseconds(5); + operation_list.push(DBOperation( + Type::DB_OVERRIDE_TIME, kOrigin1, + {TestDatabaseOperationReceiver::SerializeTime(override_time1)})); + + size_t matcher_id2 = + matcher_utility.RegisterMatcherFunction({kOrigin1, kOrigin2, kOrigin5}); + operation_list.push(DBOperation( + Type::DB_PURGE_MATCHING, + {base::NumberToString16(matcher_id2), + TestDatabaseOperationReceiver::SerializeTime(threshold2), + TestDatabaseOperationReceiver::SerializeTime(base::Time::Max()), + TestDatabaseOperationReceiver::SerializeBool( + GetParam().perform_storage_cleanup)})); + + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin1)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin2)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin3)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin4)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin5)); + + operation_list.push(DBOperation(Type::DB_FETCH_ORIGINS)); + + base::Time threshold3 = base::Time::Now() + base::Seconds(200); + operation_list.push( + DBOperation(Type::DB_OVERRIDE_TIME, kOrigin3, + {TestDatabaseOperationReceiver::SerializeTime(threshold3)})); + + base::Time threshold4 = threshold3 + base::Seconds(100); + operation_list.push( + DBOperation(Type::DB_OVERRIDE_TIME, kOrigin5, + {TestDatabaseOperationReceiver::SerializeTime(threshold4)})); + + size_t matcher_id3 = matcher_utility.RegisterMatcherFunction( + {kOrigin2, kOrigin3, kOrigin4, kOrigin5}); + operation_list.push( + DBOperation(Type::DB_PURGE_MATCHING, + {base::NumberToString16(matcher_id3), + TestDatabaseOperationReceiver::SerializeTime(threshold3), + TestDatabaseOperationReceiver::SerializeTime(threshold4), + TestDatabaseOperationReceiver::SerializeBool( + GetParam().perform_storage_cleanup)})); + + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin1)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin2)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin3)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin4)); + operation_list.push(DBOperation(Type::DB_LENGTH, kOrigin5)); + + operation_list.push(DBOperation(Type::DB_TRIM_MEMORY)); + + operation_list.push(DBOperation(Type::DB_FETCH_ORIGINS)); + + operation_list.push(DBOperation(Type::DB_GET, kOrigin2, {u"key1"})); + operation_list.push(DBOperation(Type::DB_GET, kOrigin4, {u"key1"})); + operation_list.push(DBOperation(Type::DB_GET, kOrigin4, {u"key2"})); + operation_list.push(DBOperation(Type::DB_GET, kOrigin4, {u"key3"})); + operation_list.push(DBOperation(Type::DB_GET, kOrigin4, {u"key4"})); + + operation_list.push(DBOperation(Type::DB_DESTROY)); + + SetExpectedOperationList(std::move(operation_list)); + + // Check that origin list is initially empty due to the database not being + // initialized. + std::vector<mojom::StorageUsageInfoPtr> infos1; + FetchOrigins(&infos1); + + // Check that calling `PurgeMatchingOrigins()` on the uninitialized database + // doesn't give an error. + bool open1 = false; + IsOpen(&open1); + InitStatus status1 = InitStatus::kUnattempted; + DBStatus(&status1); + + OperationResult result1 = OperationResult::kSqlError; + PurgeMatchingOrigins(&matcher_utility, matcher_id1, threshold1, + base::Time::Max(), &result1, + GetParam().perform_storage_cleanup); + + OperationResult result2 = OperationResult::kSqlError; + Set(kOrigin1, u"key1", u"value1", &result2); + + bool open2 = false; + IsOpen(&open2); + InitStatus status2 = InitStatus::kUnattempted; + DBStatus(&status2); + + OperationResult result3 = OperationResult::kSqlError; + Set(kOrigin1, u"key2", u"value2", &result3); + int length1 = -1; + Length(kOrigin1, &length1); + + OperationResult result4 = OperationResult::kSqlError; + Set(kOrigin2, u"key1", u"value1", &result4); + int length2 = -1; + Length(kOrigin2, &length2); + + OperationResult result5 = OperationResult::kSqlError; + Set(kOrigin3, u"key1", u"value1", &result5); + OperationResult result6 = OperationResult::kSqlError; + Set(kOrigin3, u"key2", u"value2", &result6); + OperationResult result7 = OperationResult::kSqlError; + Set(kOrigin3, u"key3", u"value3", &result7); + int length3 = -1; + Length(kOrigin3, &length3); + + OperationResult result8 = OperationResult::kSqlError; + Set(kOrigin4, u"key1", u"value1", &result8); + OperationResult result9 = OperationResult::kSqlError; + Set(kOrigin4, u"key2", u"value2", &result9); + OperationResult result10 = OperationResult::kSqlError; + Set(kOrigin4, u"key3", u"value3", &result10); + OperationResult result11 = OperationResult::kSqlError; + Set(kOrigin4, u"key4", u"value4", &result11); + int length4 = -1; + Length(kOrigin4, &length4); + + OperationResult result12 = OperationResult::kSqlError; + Set(kOrigin5, u"key1", u"value1", &result12); + int length5 = -1; + Length(kOrigin5, &length5); + + std::vector<mojom::StorageUsageInfoPtr> infos2; + FetchOrigins(&infos2); + + bool success1 = false; + OverrideLastUsedTime(kOrigin1, override_time1, &success1); + + // Verify that the only match we get is for `kOrigin1`, whose `last_used_time` + // is between the time parameters. + OperationResult result13 = OperationResult::kSqlError; + PurgeMatchingOrigins(&matcher_utility, matcher_id2, threshold2, + base::Time::Max(), &result13, + GetParam().perform_storage_cleanup); + + int length6 = -1; + Length(kOrigin1, &length6); + int length7 = -1; + Length(kOrigin2, &length7); + int length8 = -1; + Length(kOrigin3, &length8); + int length9 = -1; + Length(kOrigin4, &length9); + int length10 = -1; + Length(kOrigin5, &length10); + + std::vector<mojom::StorageUsageInfoPtr> infos3; + FetchOrigins(&infos3); + + bool success2 = false; + OverrideLastUsedTime(kOrigin3, threshold3, &success2); + bool success3 = false; + OverrideLastUsedTime(kOrigin5, threshold4, &success3); + + // Verify that we still get matches for `kOrigin3`, whose `last_used_time` is + // exactly at the `begin` time, as well as for `kOrigin5`, whose + // `last_used_time` is exactly at the `end` time. + OperationResult result14 = OperationResult::kSqlError; + PurgeMatchingOrigins(&matcher_utility, matcher_id3, threshold3, threshold4, + &result14, GetParam().perform_storage_cleanup); + + int length11 = -1; + Length(kOrigin1, &length11); + int length12 = -1; + Length(kOrigin2, &length12); + int length13 = -1; + Length(kOrigin3, &length13); + int length14 = -1; + Length(kOrigin4, &length14); + int length15 = -1; + Length(kOrigin5, &length15); + + TrimMemory(); + + std::vector<mojom::StorageUsageInfoPtr> infos4; + FetchOrigins(&infos4); + + GetResult value1; + Get(kOrigin2, u"key1", &value1); + GetResult value2; + Get(kOrigin4, u"key1", &value2); + GetResult value3; + Get(kOrigin4, u"key2", &value3); + GetResult value4; + Get(kOrigin4, u"key3", &value4); + GetResult value5; + Get(kOrigin4, u"key4", &value5); + + bool success4 = false; + Destroy(&success4); + + WaitForOperations(); + EXPECT_TRUE(is_finished()); + + // Database is not yet initialized. `FetchOrigins()` returns an empty vector. + EXPECT_TRUE(infos1.empty()); + EXPECT_EQ(!GetParam().in_memory_only, open1); + EXPECT_EQ(InitStatus::kUnattempted, status1); + + // No error from calling `PurgeMatchingOrigins()` on an uninitialized + // database. + EXPECT_EQ(OperationResult::kSuccess, result1); + + // The call to `Set()` initializes the database. + EXPECT_EQ(OperationResult::kSet, result2); + EXPECT_TRUE(open2); + EXPECT_EQ(InitStatus::kSuccess, status2); + + EXPECT_EQ(OperationResult::kSet, result3); + EXPECT_EQ(2, length1); + + EXPECT_EQ(OperationResult::kSet, result4); + EXPECT_EQ(1, length2); + + EXPECT_EQ(OperationResult::kSet, result5); + EXPECT_EQ(OperationResult::kSet, result6); + EXPECT_EQ(OperationResult::kSet, result7); + EXPECT_EQ(3, length3); + + EXPECT_EQ(OperationResult::kSet, result8); + EXPECT_EQ(OperationResult::kSet, result9); + EXPECT_EQ(OperationResult::kSet, result10); + EXPECT_EQ(OperationResult::kSet, result11); + EXPECT_EQ(4, length4); + + EXPECT_EQ(OperationResult::kSet, result12); + EXPECT_EQ(1, length5); + + std::vector<url::Origin> origins; + for (const auto& info : infos2) + origins.push_back(info->origin); + EXPECT_THAT(origins, + ElementsAre(kOrigin1, kOrigin2, kOrigin3, kOrigin4, kOrigin5)); + + EXPECT_TRUE(success1); + EXPECT_EQ(OperationResult::kSuccess, result13); + + // `kOrigin1` is cleared. The other origins are not. + EXPECT_EQ(0, length6); + EXPECT_EQ(1, length7); + EXPECT_EQ(3, length8); + EXPECT_EQ(4, length9); + EXPECT_EQ(1, length10); + + origins.clear(); + for (const auto& info : infos3) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin2, kOrigin3, kOrigin4, kOrigin5)); + + EXPECT_TRUE(success2); + EXPECT_TRUE(success3); + + EXPECT_EQ(OperationResult::kSuccess, result14); + + // `kOrigin3` and `kOrigin5` are cleared. The others weren't modified within + // the given time period. + EXPECT_EQ(0, length11); + EXPECT_EQ(1, length12); + EXPECT_EQ(0, length13); + EXPECT_EQ(4, length14); + EXPECT_EQ(0, length15); + + origins.clear(); + for (const auto& info : infos4) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin2, kOrigin4)); + + // Database is still intact after trimming memory (and possibly performing + // storage cleanup). + EXPECT_EQ(value1.data, u"value1"); + EXPECT_EQ(OperationResult::kSuccess, value1.result); + EXPECT_EQ(value2.data, u"value1"); + EXPECT_EQ(OperationResult::kSuccess, value2.result); + EXPECT_EQ(value3.data, u"value2"); + EXPECT_EQ(OperationResult::kSuccess, value3.result); + EXPECT_EQ(value4.data, u"value3"); + EXPECT_EQ(OperationResult::kSuccess, value4.result); + EXPECT_EQ(value5.data, u"value4"); + EXPECT_EQ(OperationResult::kSuccess, value5.result); + + EXPECT_TRUE(success4); +} + +} // namespace storage diff --git a/chromium/components/services/storage/shared_storage/shared_storage_database.cc b/chromium/components/services/storage/shared_storage/shared_storage_database.cc new file mode 100644 index 00000000000..923717a5467 --- /dev/null +++ b/chromium/components/services/storage/shared_storage/shared_storage_database.cc @@ -0,0 +1,900 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/services/storage/shared_storage/shared_storage_database.h" + +#include <inttypes.h> + +#include <algorithm> +#include <memory> +#include <string> +#include <vector> + +#include "base/files/file_util.h" +#include "base/metrics/histogram_functions.h" +#include "base/metrics/histogram_macros.h" +#include "base/time/default_clock.h" +#include "base/time/time.h" +#include "components/services/storage/public/mojom/storage_usage_info.mojom.h" +#include "components/services/storage/shared_storage/shared_storage_options.h" +#include "sql/error_delegate_util.h" +#include "sql/statement.h" +#include "sql/transaction.h" +#include "storage/browser/quota/special_storage_policy.h" +#include "third_party/blink/public/common/features.h" +#include "url/gurl.h" +#include "url/origin.h" + +namespace storage { + +// Because each entry is a key-value pair, and both keys and values are +// std::u16strings and bounded by `max_string_length_`, the total bytes used per +// entry is at most 2 * 2 * `max_string_length_`. +const int kSharedStorageEntryTotalBytesMultiplier = 4; + +namespace { + +// Version number of the database. +const int kCurrentVersionNumber = 1; + +[[nodiscard]] std::string SerializeOrigin(const url::Origin& origin) { + DCHECK(!origin.opaque()); + DCHECK_NE(url::kFileScheme, origin.scheme()); + return origin.Serialize(); +} + +[[nodiscard]] bool InitSchema(sql::Database& db) { + static constexpr char kValuesMappingSql[] = + "CREATE TABLE IF NOT EXISTS values_mapping(" + "context_origin TEXT NOT NULL," + "key TEXT NOT NULL," + "value TEXT," + "PRIMARY KEY(context_origin,key)) WITHOUT ROWID"; + if (!db.Execute(kValuesMappingSql)) + return false; + + static constexpr char kPerOriginMappingSql[] = + "CREATE TABLE IF NOT EXISTS per_origin_mapping(" + "context_origin TEXT NOT NULL PRIMARY KEY," + "last_used_time INTEGER NOT NULL," + "length INTEGER NOT NULL) WITHOUT ROWID"; + if (!db.Execute(kPerOriginMappingSql)) + return false; + + static constexpr char kLastUsedTimeIndexSql[] = + "CREATE INDEX IF NOT EXISTS per_origin_mapping_last_used_time_idx " + "ON per_origin_mapping(last_used_time)"; + if (!db.Execute(kLastUsedTimeIndexSql)) + return false; + + return true; +} + +} // namespace + +SharedStorageDatabase::GetResult::GetResult() = default; + +SharedStorageDatabase::GetResult::GetResult(const GetResult&) = default; + +SharedStorageDatabase::GetResult::GetResult(GetResult&&) = default; + +SharedStorageDatabase::GetResult::~GetResult() = default; + +SharedStorageDatabase::GetResult& SharedStorageDatabase::GetResult::operator=( + const GetResult&) = default; + +SharedStorageDatabase::GetResult& SharedStorageDatabase::GetResult::operator=( + GetResult&&) = default; + +SharedStorageDatabase::SharedStorageDatabase( + base::FilePath db_path, + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy, + std::unique_ptr<SharedStorageDatabaseOptions> options) + : db_({// Run the database in exclusive mode. Nobody else should be + // accessing the database while we're running, and this will give + // somewhat improved perf. + .exclusive_locking = true, + .page_size = options->max_page_size, + .cache_size = options->max_cache_size}), + db_path_(std::move(db_path)), + special_storage_policy_(std::move(special_storage_policy)), + max_entries_per_origin_(int64_t{options->max_entries_per_origin}), + clock_(base::DefaultClock::GetInstance()) { + DCHECK(db_path_.empty() || db_path_.IsAbsolute()); + DCHECK_GT(max_entries_per_origin_, 0); + DCHECK_GT(options->max_init_tries, 0); + DCHECK_GT(options->max_string_length, 0); + max_string_length_ = static_cast<size_t>(options->max_string_length); + max_init_tries_ = static_cast<size_t>(options->max_init_tries); + db_file_status_ = db_path_.empty() ? DBFileStatus::kNoPreexistingFile + : DBFileStatus::kNotChecked; +} + +SharedStorageDatabase::~SharedStorageDatabase() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +bool SharedStorageDatabase::Destroy() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (db_.is_open() && !db_.RazeAndClose()) + return false; + + // The file already doesn't exist. + if (db_path_.empty()) + return true; + + return base::DeleteFile(db_path_); +} + +void SharedStorageDatabase::TrimMemory() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK_EQ(InitStatus::kSuccess, db_status_); + db_.TrimMemory(); +} + +SharedStorageDatabase::GetResult SharedStorageDatabase::Get( + url::Origin context_origin, + std::u16string key) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK_LE(key.size(), max_string_length_); + GetResult result; + + if (LazyInit(DBCreationPolicy::kIgnoreIfAbsent) != InitStatus::kSuccess) { + // We do not return an error if the database doesn't exist, but only if it + // pre-exists on disk and yet fails to initialize. + if (db_status_ == InitStatus::kUnattempted) + result.result = OperationResult::kSuccess; + else + result.result = OperationResult::kInitFailure; + + return result; + } + + // In theory, there ought to be at most one entry found. But we make no + // assumption about the state of the disk. In the rare case that multiple + // entries are found, we return only the value from the first entry found. + static constexpr char kSelectSql[] = + "SELECT value FROM values_mapping " + "WHERE context_origin=? AND key=? " + "LIMIT 1"; + + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSelectSql)); + std::string origin_str(SerializeOrigin(context_origin)); + statement.BindString(0, origin_str); + statement.BindString16(1, key); + + if (statement.Step()) + result.data = statement.ColumnString16(0); + if (!statement.Succeeded()) + return result; + + if (UpdateLastUsedTime(origin_str)) + result.result = OperationResult::kSuccess; + + return result; +} + +SharedStorageDatabase::OperationResult SharedStorageDatabase::Set( + url::Origin context_origin, + std::u16string key, + std::u16string value, + SetBehavior behavior) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(!key.empty()); + DCHECK_LE(key.size(), max_string_length_); + DCHECK_LE(value.size(), max_string_length_); + + if (LazyInit(DBCreationPolicy::kCreateIfAbsent) != InitStatus::kSuccess) + return OperationResult::kInitFailure; + + sql::Transaction transaction(&db_); + if (!transaction.Begin()) + return OperationResult::kSqlError; + + std::string origin_str(SerializeOrigin(context_origin)); + if (HasEntryFor(origin_str, key)) { + if (behavior == SharedStorageDatabase::SetBehavior::kIgnoreIfPresent) { + // If we are in a nested transaction, we need to commit, even though we + // haven't made any changes, so that the failure to set in this case + // isn't seen as an error (as then the entire stack of transactions + // will be rolled back and the next transaction within the parent + // transaction will fail to begin). + if (db_.transaction_nesting()) + transaction.Commit(); + return OperationResult::kIgnored; + } + + if (Delete(context_origin, key) != OperationResult::kSuccess) + return OperationResult::kSqlError; + } else if (!HasCapacity(origin_str)) { + return OperationResult::kNoCapacity; + } + + if (!InsertIntoValuesMapping(origin_str, key, value)) + return OperationResult::kSqlError; + + if (!UpdateLength(origin_str, /*delta=*/1)) + return OperationResult::kSqlError; + + if (!transaction.Commit()) + return OperationResult::kSqlError; + + return OperationResult::kSet; +} + +SharedStorageDatabase::OperationResult SharedStorageDatabase::Append( + url::Origin context_origin, + std::u16string key, + std::u16string tail_value) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(!key.empty()); + DCHECK_LE(key.size(), max_string_length_); + DCHECK_LE(tail_value.size(), max_string_length_); + + if (LazyInit(DBCreationPolicy::kCreateIfAbsent) != InitStatus::kSuccess) + return OperationResult::kInitFailure; + + sql::Transaction transaction(&db_); + if (!transaction.Begin()) + return OperationResult::kSqlError; + + GetResult get_result = Get(context_origin, key); + if (get_result.result != OperationResult::kSuccess) + return OperationResult::kSqlError; + + std::u16string new_value; + std::string origin_str(SerializeOrigin(context_origin)); + + if (get_result.data) { + new_value = std::move(*get_result.data); + new_value.append(tail_value); + + if (new_value.size() > max_string_length_) + return OperationResult::kInvalidAppend; + + if (Delete(context_origin, key) != OperationResult::kSuccess) + return OperationResult::kSqlError; + } else { + new_value = std::move(tail_value); + + if (!HasCapacity(origin_str)) + return OperationResult::kNoCapacity; + } + + if (!InsertIntoValuesMapping(origin_str, key, new_value)) + return OperationResult::kSqlError; + + if (!UpdateLength(origin_str, /*delta=*/1)) + return OperationResult::kSqlError; + + if (!transaction.Commit()) + return OperationResult::kSqlError; + + return OperationResult::kSet; +} + +SharedStorageDatabase::OperationResult SharedStorageDatabase::Delete( + url::Origin context_origin, + std::u16string key) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK_LE(key.size(), max_string_length_); + + if (LazyInit(DBCreationPolicy::kIgnoreIfAbsent) != InitStatus::kSuccess) { + // We do not return an error if the database doesn't exist, but only if it + // pre-exists on disk and yet fails to initialize. + if (db_status_ == InitStatus::kUnattempted) + return OperationResult::kSuccess; + else + return OperationResult::kInitFailure; + } + + std::string origin_str(SerializeOrigin(context_origin)); + if (!HasEntryFor(origin_str, key)) + return OperationResult::kSuccess; + + sql::Transaction transaction(&db_); + if (!transaction.Begin()) + return OperationResult::kSqlError; + + static constexpr char kDeleteSql[] = + "DELETE FROM values_mapping " + "WHERE context_origin=? AND key=?"; + + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kDeleteSql)); + statement.BindString(0, origin_str); + statement.BindString16(1, key); + + if (!statement.Run()) + return OperationResult::kSqlError; + + if (!UpdateLength(origin_str, /*delta=*/-1)) + return OperationResult::kSqlError; + + if (!transaction.Commit()) + return OperationResult::kSqlError; + return OperationResult::kSuccess; +} + +SharedStorageDatabase::OperationResult SharedStorageDatabase::Clear( + url::Origin context_origin) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (LazyInit(DBCreationPolicy::kIgnoreIfAbsent) != InitStatus::kSuccess) { + // We do not return an error if the database doesn't exist, but only if it + // pre-exists on disk and yet fails to initialize. + if (db_status_ == InitStatus::kUnattempted) + return OperationResult::kSuccess; + else + return OperationResult::kInitFailure; + } + + if (!Purge(SerializeOrigin(context_origin))) + return OperationResult::kSqlError; + return OperationResult::kSuccess; +} + +int64_t SharedStorageDatabase::Length(url::Origin context_origin) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (LazyInit(DBCreationPolicy::kIgnoreIfAbsent) != InitStatus::kSuccess) { + // We do not return -1 (to signifiy an error) if the database doesn't exist, + // but only if it pre-exists on disk and yet fails to initialize. + if (db_status_ == InitStatus::kUnattempted) + return 0L; + else + return -1; + } + + std::string origin_str(SerializeOrigin(context_origin)); + int64_t length = NumEntries(origin_str); + if (!length) + return 0L; + + if (!UpdateLastUsedTime(origin_str)) + return -1; + + return length; +} + +SharedStorageDatabase::GetResult SharedStorageDatabase::Key( + url::Origin context_origin, + uint64_t index) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + GetResult result; + + if (LazyInit(DBCreationPolicy::kIgnoreIfAbsent) != InitStatus::kSuccess) { + // We do not return an error if the database doesn't exist, but only if it + // pre-exists on disk and yet fails to initialize. + if (db_status_ == InitStatus::kUnattempted) + result.result = OperationResult::kSuccess; + else + result.result = OperationResult::kInitFailure; + return result; + } + + static constexpr char kSelectSql[] = + "SELECT key FROM values_mapping " + "WHERE context_origin=? " + "ORDER BY key"; + + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSelectSql)); + std::string origin_str(SerializeOrigin(context_origin)); + statement.BindString(0, origin_str); + + uint64_t current_index = 0UL; + + while (statement.Step()) { + if (!statement.Succeeded()) + return result; + if (current_index == index) { + result.data = statement.ColumnString16(0); + break; + } + + current_index++; + } + + if (UpdateLastUsedTime(origin_str)) + result.result = OperationResult::kSuccess; + + return result; +} + +SharedStorageDatabase::OperationResult +SharedStorageDatabase::PurgeMatchingOrigins( + OriginMatcherFunction origin_matcher, + base::Time begin, + base::Time end, + bool perform_storage_cleanup) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK_LE(begin, end); + + if (LazyInit(DBCreationPolicy::kIgnoreIfAbsent) != InitStatus::kSuccess) { + // We do not return an error if the database doesn't exist, but only if it + // pre-exists on disk and yet fails to initialize. + if (db_status_ == InitStatus::kUnattempted) + return OperationResult::kSuccess; + else + return OperationResult::kInitFailure; + } + + static constexpr char kSelectSql[] = + "SELECT context_origin FROM per_origin_mapping " + "WHERE last_used_time BETWEEN ? AND ? " + "ORDER BY last_used_time"; + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSelectSql)); + statement.BindTime(0, begin); + statement.BindTime(1, end); + + std::vector<std::string> origins; + + while (statement.Step()) + origins.push_back(statement.ColumnString(0)); + + if (!statement.Succeeded()) + return OperationResult::kSqlError; + + if (origins.empty()) + return OperationResult::kSuccess; + + sql::Transaction transaction(&db_); + if (!transaction.Begin()) + return OperationResult::kSqlError; + + for (const auto& origin : origins) { + if (origin_matcher && !origin_matcher.Run(url::Origin::Create(GURL(origin)), + special_storage_policy_.get())) { + continue; + } + + if (!Purge(origin)) + return OperationResult::kSqlError; + } + + if (!transaction.Commit()) + return OperationResult::kSqlError; + + if (perform_storage_cleanup && !Vacuum()) + return OperationResult::kSqlError; + + return OperationResult::kSuccess; +} + +SharedStorageDatabase::OperationResult SharedStorageDatabase::PurgeStaleOrigins( + base::TimeDelta window_to_be_deemed_active) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK_GT(window_to_be_deemed_active, base::TimeDelta()); + + if (LazyInit(DBCreationPolicy::kIgnoreIfAbsent) != InitStatus::kSuccess) { + // We do not return an error if the database doesn't exist, but only if it + // pre-exists on disk and yet fails to initialize. + if (db_status_ == InitStatus::kUnattempted) + return OperationResult::kSuccess; + else + return OperationResult::kInitFailure; + } + + base::Time threshold = clock_->Now() - window_to_be_deemed_active; + + static constexpr char kSelectSql[] = + "SELECT context_origin FROM per_origin_mapping " + "WHERE last_used_time<? " + "ORDER BY last_used_time"; + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSelectSql)); + statement.BindTime(0, threshold); + + std::vector<std::string> stale_origins; + + while (statement.Step()) + stale_origins.push_back(statement.ColumnString(0)); + + if (!statement.Succeeded()) + return OperationResult::kSqlError; + + if (stale_origins.empty()) + return OperationResult::kSuccess; + + sql::Transaction transaction(&db_); + if (!transaction.Begin()) + return OperationResult::kSqlError; + + for (const auto& origin : stale_origins) { + if (!Purge(origin)) + return OperationResult::kSqlError; + } + + if (!transaction.Commit()) + return OperationResult::kSqlError; + return OperationResult::kSuccess; +} + +std::vector<mojom::StorageUsageInfoPtr> SharedStorageDatabase::FetchOrigins() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (LazyInit(DBCreationPolicy::kIgnoreIfAbsent) != InitStatus::kSuccess) + return {}; + + static constexpr char kSelectSql[] = + "SELECT context_origin,last_used_time,length FROM per_origin_mapping " + "ORDER BY context_origin"; + + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSelectSql)); + std::vector<mojom::StorageUsageInfoPtr> fetched_origin_infos; + + while (statement.Step()) { + fetched_origin_infos.emplace_back(mojom::StorageUsageInfo::New( + url::Origin::Create(GURL(statement.ColumnString(0))), + statement.ColumnInt64(2) * kSharedStorageEntryTotalBytesMultiplier * + max_string_length_, + statement.ColumnTime(1))); + } + + if (!statement.Succeeded()) + return {}; + + return fetched_origin_infos; +} + +bool SharedStorageDatabase::IsOpenForTesting() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return db_.is_open(); +} + +SharedStorageDatabase::InitStatus SharedStorageDatabase::DBStatusForTesting() + const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return db_status_; +} + +bool SharedStorageDatabase::OverrideLastUsedTimeForTesting( + url::Origin context_origin, + base::Time override_last_used_time) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (LazyInit(DBCreationPolicy::kIgnoreIfAbsent) != InitStatus::kSuccess) + return false; + + return SetLastUsedTime(SerializeOrigin(context_origin), + override_last_used_time); +} + +void SharedStorageDatabase::OverrideClockForTesting(base::Clock* clock) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(clock); + clock_ = clock; +} + +bool SharedStorageDatabase::OverrideSpecialStoragePolicyForTesting( + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + special_storage_policy_ = std::move(special_storage_policy); + return true; +} + +SharedStorageDatabase::InitStatus SharedStorageDatabase::LazyInit( + DBCreationPolicy policy) { + // Early return in case of previous failure, to prevent an unbounded + // number of re-attempts. + if (db_status_ != InitStatus::kUnattempted) + return db_status_; + + if (policy == DBCreationPolicy::kIgnoreIfAbsent && !DBExists()) + return InitStatus::kUnattempted; + + for (size_t i = 0; i < max_init_tries_; ++i) { + db_status_ = InitImpl(); + if (db_status_ == InitStatus::kSuccess) + return db_status_; + + meta_table_.Reset(); + db_.Close(); + } + + return db_status_; +} + +bool SharedStorageDatabase::DBExists() { + DCHECK_EQ(InitStatus::kUnattempted, db_status_); + + if (db_file_status_ == DBFileStatus::kNoPreexistingFile) + return false; + + // The in-memory case is included in `DBFileStatus::kNoPreexistingFile`. + DCHECK(!db_path_.empty()); + + // We do not expect `DBExists()` to be called in the case where + // `db_file_status_ == DBFileStatus::kPreexistingFile`, as then + // `db_status_ != InitStatus::kUnattempted`, which would force an early return + // in `LazyInit()`. + DCHECK_EQ(DBFileStatus::kNotChecked, db_file_status_); + + // The histogram tag must be set before opening. + db_.set_histogram_tag("SharedStorage"); + + if (!db_.Open(db_path_)) { + db_file_status_ = DBFileStatus::kNoPreexistingFile; + return false; + } + + static const char kSelectSql[] = + "SELECT COUNT(*) FROM sqlite_schema WHERE type=?"; + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSelectSql)); + statement.BindCString(0, "table"); + + if (!statement.Step() || statement.ColumnInt(0) == 0) { + db_file_status_ = DBFileStatus::kNoPreexistingFile; + return false; + } + + db_file_status_ = DBFileStatus::kPreexistingFile; + return true; +} + +bool SharedStorageDatabase::OpenDatabase() { + // If the database is open, the histogram tag will have already been set in + // `DBExists()`, since it must be set before opening. + if (!db_.is_open()) + db_.set_histogram_tag("SharedStorage"); + + // base::Unretained is safe here because this SharedStorageDatabase owns + // the sql::Database instance that stores and uses the callback. So, + // `this` is guaranteed to outlive the callback. + db_.set_error_callback(base::BindRepeating( + &SharedStorageDatabase::DatabaseErrorCallback, base::Unretained(this))); + + if (!db_path_.empty()) { + if (!db_.is_open() && !db_.Open(db_path_)) + return false; + + db_.Preload(); + } else { + if (!db_.OpenInMemory()) + return false; + } + + return true; +} + +void SharedStorageDatabase::DatabaseErrorCallback(int extended_error, + sql::Statement* stmt) { + base::UmaHistogramSparse("Storage.SharedStorage.Database.Error", + extended_error); + + if (sql::IsErrorCatastrophic(extended_error)) { + bool success = Destroy(); + UMA_HISTOGRAM_BOOLEAN("Storage.SharedStorage.Database.Destruction", + success); + if (!success) { + DLOG(FATAL) << "Database destruction failed after catastrophic error:\n" + << db_.GetErrorMessage(); + } + } + + // The default handling is to assert on debug and to ignore on release. + if (!sql::Database::IsExpectedSqliteError(extended_error)) + DLOG(FATAL) << db_.GetErrorMessage(); +} + +SharedStorageDatabase::InitStatus SharedStorageDatabase::InitImpl() { + if (!OpenDatabase()) + return InitStatus::kError; + + // Database should now be open. + DCHECK(db_.is_open()); + + // Scope initialization in a transaction so we can't be partially initialized. + sql::Transaction transaction(&db_); + if (!transaction.Begin()) { + LOG(WARNING) << "Shared storage database begin initialization failed."; + db_.RazeAndClose(); + return InitStatus::kError; + } + + // Create the tables. + if (!meta_table_.Init(&db_, kCurrentVersionNumber, kCurrentVersionNumber) || + !InitSchema(db_)) { + return InitStatus::kError; + } + + if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) { + LOG(WARNING) << "Shared storage database is too new."; + return InitStatus::kTooNew; + } + + int cur_version = meta_table_.GetVersionNumber(); + + if (cur_version < kCurrentVersionNumber) { + LOG(WARNING) << "Shared storage database is too old to be compatible."; + db_.RazeAndClose(); + return InitStatus::kTooOld; + } + + // The initialization is complete. + if (!transaction.Commit()) { + LOG(WARNING) << "Shared storage database initialization commit failed."; + db_.RazeAndClose(); + return InitStatus::kError; + } + + return InitStatus::kSuccess; +} + +bool SharedStorageDatabase::Vacuum() { + DCHECK_EQ(InitStatus::kSuccess, db_status_); + DCHECK_EQ(0, db_.transaction_nesting()) + << "Can not have a transaction when vacuuming."; + return db_.Execute("VACUUM"); +} + +bool SharedStorageDatabase::Purge(const std::string& context_origin) { + sql::Transaction transaction(&db_); + if (!transaction.Begin()) + return false; + + static constexpr char kDeleteSql[] = + "DELETE FROM values_mapping " + "WHERE context_origin=?"; + + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kDeleteSql)); + statement.BindString(0, context_origin); + + if (!statement.Run()) + return false; + + if (!DeleteFromPerOriginMapping(context_origin)) + return false; + + return transaction.Commit(); +} + +int64_t SharedStorageDatabase::NumEntries(const std::string& context_origin) { + // In theory, there ought to be at most one entry found. But we make no + // assumption about the state of the disk. In the rare case that multiple + // entries are found, we return only the `length` from the first entry found. + static constexpr char kSelectSql[] = + "SELECT length FROM per_origin_mapping " + "WHERE context_origin=? " + "LIMIT 1"; + + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSelectSql)); + statement.BindString(0, context_origin); + + int64_t length = 0; + if (statement.Step()) + length = statement.ColumnInt64(0); + + return length; +} + +bool SharedStorageDatabase::HasEntryFor(const std::string& context_origin, + const std::u16string& key) { + static constexpr char kSelectSql[] = + "SELECT 1 FROM values_mapping " + "WHERE context_origin=? AND key=? " + "LIMIT 1"; + + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSelectSql)); + statement.BindString(0, context_origin); + statement.BindString16(1, key); + + return statement.Step(); +} + +bool SharedStorageDatabase::SetLastUsedTime(const std::string& context_origin, + base::Time new_last_used_time) { + int64_t length = NumEntries(context_origin); + + // If length is zero, no need to delete, and don't insert the origin into the + // `per_origin_mapping`. + if (!length) + return true; + + sql::Transaction transaction(&db_); + if (!transaction.Begin()) + return false; + + if (!DeleteFromPerOriginMapping(context_origin)) + return false; + + if (!InsertIntoPerOriginMapping(context_origin, new_last_used_time, length)) + return false; + + return transaction.Commit(); +} + +bool SharedStorageDatabase::UpdateLastUsedTime( + const std::string& context_origin) { + return SetLastUsedTime(context_origin, clock_->Now()); +} + +bool SharedStorageDatabase::UpdateLength(const std::string& context_origin, + int64_t delta, + bool should_update_time) { + // In theory, there ought to be at most one entry found. But we make no + // assumption about the state of the disk. In the rare case that multiple + // entries are found, we retrieve only the `length` (and possibly the `time`) + // from the first entry found. + static constexpr char kSelectSql[] = + "SELECT length,last_used_time FROM per_origin_mapping " + "WHERE context_origin=? " + "LIMIT 1"; + + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSelectSql)); + statement.BindString(0, context_origin); + int64_t length = 0; + base::Time time = clock_->Now(); + + if (statement.Step()) { + length = statement.ColumnInt64(0); + if (!should_update_time) + time = statement.ColumnTime(1); + } + + sql::Transaction transaction(&db_); + if (!transaction.Begin()) + return false; + + if (!DeleteFromPerOriginMapping(context_origin)) + return false; + + // If the new length is zero, then don't re-insert the origin into the + // `per_origin_mapping`. + if (length + delta == 0L) + return transaction.Commit(); + + if (!InsertIntoPerOriginMapping(context_origin, time, length + delta)) + return false; + + return transaction.Commit(); +} + +bool SharedStorageDatabase::InsertIntoValuesMapping( + const std::string& context_origin, + const std::u16string& key, + const std::u16string& value) { + static constexpr char kInsertSql[] = + "INSERT INTO values_mapping(context_origin,key,value)" + "VALUES(?,?,?)"; + + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kInsertSql)); + statement.BindString(0, context_origin); + statement.BindString16(1, key); + statement.BindString16(2, value); + + return statement.Run(); +} + +bool SharedStorageDatabase::DeleteFromPerOriginMapping( + const std::string& context_origin) { + static constexpr char kDeleteSql[] = + "DELETE FROM per_origin_mapping " + "WHERE context_origin=?"; + + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kDeleteSql)); + statement.BindString(0, context_origin); + + return statement.Run(); +} + +bool SharedStorageDatabase::InsertIntoPerOriginMapping( + const std::string& context_origin, + base::Time last_used_time, + uint64_t length) { + static constexpr char kInsertSql[] = + "INSERT INTO per_origin_mapping(context_origin,last_used_time,length)" + "VALUES(?,?,?)"; + + sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kInsertSql)); + statement.BindString(0, context_origin); + statement.BindTime(1, last_used_time); + statement.BindInt64(2, static_cast<int64_t>(length)); + + return statement.Run(); +} + +bool SharedStorageDatabase::HasCapacity(const std::string& context_origin) { + return NumEntries(context_origin) < max_entries_per_origin_; +} + +} // namespace storage diff --git a/chromium/components/services/storage/shared_storage/shared_storage_database.h b/chromium/components/services/storage/shared_storage/shared_storage_database.h new file mode 100644 index 00000000000..413396a9efa --- /dev/null +++ b/chromium/components/services/storage/shared_storage/shared_storage_database.h @@ -0,0 +1,388 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SERVICES_STORAGE_SHARED_STORAGE_SHARED_STORAGE_DATABASE_H_ +#define COMPONENTS_SERVICES_STORAGE_SHARED_STORAGE_SHARED_STORAGE_DATABASE_H_ + +#include <inttypes.h> + +#include <memory> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/memory/raw_ptr.h" +#include "base/memory/scoped_refptr.h" +#include "base/sequence_checker.h" +#include "base/thread_annotations.h" +#include "base/threading/sequence_bound.h" +#include "base/time/clock.h" +#include "components/services/storage/public/mojom/storage_usage_info.mojom-forward.h" +#include "sql/database.h" +#include "sql/meta_table.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace base { +class FilePath; +class Time; +class TimeDelta; +} // namespace base + +namespace sql { +class Statement; +} + +namespace url { +class Origin; +} // namespace url + +namespace storage { +struct SharedStorageDatabaseOptions; +class SpecialStoragePolicy; + +// Multiplier for determining the padded total size in bytes that an origin +// is using. +extern const int kSharedStorageEntryTotalBytesMultiplier; + +// Wraps its own `sql::Database` instance on behalf of the Shared Storage +// backend implementation. This object is not sequence-safe and must be +// instantiated on a sequence which allows use of blocking file operations. +class SharedStorageDatabase { + public: + // A callback type to check if a given origin matches a storage policy. + // Can be passed empty/null where used, which means the origin will always + // match. + using OriginMatcherFunction = + base::RepeatingCallback<bool(const url::Origin&, SpecialStoragePolicy*)>; + + enum class InitStatus { + kUnattempted = + 0, // Status if `LazyInit()` has not yet been called or if `LazyInit()` + // has early returned due to `DBCreationPolicy::kIgnoreIfAbsent`. + kSuccess = 1, // Status if `LazyInit()` was successful. + kError = 2, // Status if `LazyInit()` failed and a more specific error + // wasn't diagnosed. + kTooNew = 3, // Status if `LazyInit()` failed due to a compatible version + // number being too high. + kTooOld = 4, // Status if `LazyInit()` failed due to a version number being + // too low. + }; + + enum class DBFileStatus { + kNotChecked = 0, // Status if DB is file-backed and there hasn't been an + // attempt to open the SQL database for the given FilePath + // to see if it exists and contains data. + kNoPreexistingFile = + 1, // Status if the DB is in-memory or if the DB is file-backed but the + // attempt to open it was unsuccessful or any pre-existing file + // contained no data. + kPreexistingFile = + 2, // Status if there was a pre-existing file containing at least one + // table that we were able to successfully open. + }; + + enum class SetBehavior { + kDefault = 0, // Sets entry regardless of whether one previously exists. + kIgnoreIfPresent = 1, // Does not set an entry if one previously exists. + }; + + enum class OperationResult { + kSuccess = 0, // Result if a non-setting operation is successful. + kSet = 1, // Result if value is set. + kIgnored = 2, // Result if value was present and ignored; no error. + kSqlError = 3, // Result if there is a SQL database error. + kInitFailure = 4, // Result if database initialization failed and a + // database is required. + kNoCapacity = 5, // Result if there was insufficient capacity for the + // requesting origin. + kInvalidAppend = 6, // Result if the length of the value after appending + // would exceed the maximum allowed length. + }; + + // Bundles a retrieved string from the database along with a field indicating + // whether the transaction was free of SQL errors. + struct GetResult { + absl::optional<std::u16string> data; + OperationResult result = OperationResult::kSqlError; + GetResult(); + GetResult(const GetResult&); + GetResult(GetResult&&); + ~GetResult(); + GetResult& operator=(const GetResult&); + GetResult& operator=(GetResult&&); + }; + + // When `db_path` is empty, the database will be opened in memory only. + SharedStorageDatabase( + base::FilePath db_path, + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy, + std::unique_ptr<SharedStorageDatabaseOptions> options); + + SharedStorageDatabase(const SharedStorageDatabase&) = delete; + SharedStorageDatabase(const SharedStorageDatabase&&) = delete; + + ~SharedStorageDatabase(); + + SharedStorageDatabase& operator=(const SharedStorageDatabase&) = delete; + SharedStorageDatabase& operator=(const SharedStorageDatabase&&) = delete; + + // Deletes the database and returns whether the operation was successful. + // + // It is OK to call `Destroy()` regardless of whether `Init()` was successful. + [[nodiscard]] bool Destroy(); + + // Returns a pointer to the database containing the actual data. + [[nodiscard]] sql::Database* db() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return &db_; + } + + // Releases all non-essential memory associated with this database connection. + void TrimMemory(); + + // Retrieves the `entry` for `context_origin` and `key`. Returns a + // struct containing a `value` string if one is found, `absl::nullopt` + // otherwise, with a bool `success` indicating whether the transaction was + // free of errors. + // + // Note that `key` is assumed to be of length at most + // `max_string_length_`, with the burden on the caller to handle errors for + // strings that exceed this length. + [[nodiscard]] GetResult Get(url::Origin context_origin, std::u16string key); + + // Sets an entry for `context_origin` and `key` to have `value`. + // If `behavior` is `kIgnoreIfPresent` and an entry already exists for + // `context_origin` and `key`, then the table is not modified. + // Returns an enum indicating whether or not a new entry is added, the request + // is ignored, or if there is an error. + // + // Note that `key` and `value` assumed to be each of length at + // most `max_string_length_`, with the burden on the caller to handle errors + // for strings that exceed this length. Moreover, if `Length(context_origin)` + // equals `max_entries_per_origin_`, `Set()` will return a value of + // `OperationResult::kNoCapacity` and the table will not be modified. + [[nodiscard]] OperationResult Set( + url::Origin context_origin, + std::u16string key, + std::u16string value, + SetBehavior behavior = SetBehavior::kDefault); + + // Appends `tail_value` to the end of the current `value` + // for `context_origin` and `key`, if `key` exists. If + // `key` does not exist, creates an entry for `key` with value + // `tail_value`. Returns an enum indicating whether or not an entry is + // added/modified or if there is an error. + // + // Note that `key` and `value` are assumed to be each of length + // at most `max_string_length_`, with the burden on the caller to handle + // errors for strings that exceed this length. Moreover, if the length of the + // string obtained by concatening the current `value` (if one exists) + // and `tail_value` exceeds `max_string_length_`, or if + // `Length(context_origin)` equals `max_entries_per_origin_`, `Append()` will + // return a value of `OperationResult::kNoCapacity` and the table will not be + // modified. + [[nodiscard]] OperationResult Append(url::Origin context_origin, + std::u16string key, + std::u16string tail_value); + + // Deletes the entry for `context_origin` and `key`. Returns + // whether the deletion is successful. + // + // Note that `key` is assumed to be of length at most + // `max_string_length_`, with the burden on the caller to handle errors for + // strings that exceed this length. + [[nodiscard]] OperationResult Delete(url::Origin context_origin, + std::u16string key); + + // Clears all entries for `context_origin`. Returns whether the operation is + // successful. + [[nodiscard]] OperationResult Clear(url::Origin context_origin); + + // Returns the number of entries for `context_origin` in the database, or -1 + // on error. Note that this call will update the origin's `last_used_time`. + // TODO(crbug.com/1277662): Consider renaming to something more descriptive. + [[nodiscard]] int64_t Length(url::Origin context_origin); + + // If a list of all the keys for `context_origin` are taken in lexicographic + // order, retrieves the `key` at `index` of the list and sets it as + // data in the returned struct; otherwise the struct holds `absl::nullopt` if + // no such `key` exists. The `GetResult` struct also has a bool + // `success` indicating whether the transaction was free of errors. + // + // TODO(crbug.com/1247861): Replace with an async iterator. + [[nodiscard]] GetResult Key(url::Origin context_origin, uint64_t index); + + // Clears all origins that match `origin_matcher` run on the owning + // StoragePartition's `SpecialStoragePolicy` and have `last_used_time` between + // the times `begin` and `end`. If `perform_storage_cleanup` is true, vacuums + // the database afterwards. Returns whether the transaction was successful. + [[nodiscard]] OperationResult PurgeMatchingOrigins( + OriginMatcherFunction origin_matcher, + base::Time begin, + base::Time end, + bool perform_storage_cleanup = false); + + // Clear all entries for all origins whose `last_read_time` falls before + // `base::Time::Now() - window_to_be_deemed_active`. Returns whether the + // transaction was successful. + [[nodiscard]] OperationResult PurgeStaleOrigins( + base::TimeDelta window_to_be_deemed_active); + + // Fetches a vector of `mojom::StorageUsageInfoPtr`, with one + // `mojom::StorageUsageInfoPtr` for each origin currently using shared storage + // in this profile. + [[nodiscard]] std::vector<mojom::StorageUsageInfoPtr> FetchOrigins(); + + // Returns whether the SQLite database is open. + [[nodiscard]] bool IsOpenForTesting() const; + + // Returns the `db_status_` for tests. + [[nodiscard]] InitStatus DBStatusForTesting() const; + + // Changes `last_used_time` to `override_last_used_time` for `context_origin`. + [[nodiscard]] bool OverrideLastUsedTimeForTesting( + url::Origin context_origin, + base::Time override_last_used_time); + + // Overrides the clock used to check the time. + void OverrideClockForTesting(base::Clock* clock); + + // Overrides the `SpecialStoragePolicy` for tests. Returns true. + [[nodiscard]] bool OverrideSpecialStoragePolicyForTesting( + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy); + + private: + // Policy to tell `LazyInit()` whether or not to create a new database if a + // pre-existing on-disk database is not found. + enum class DBCreationPolicy { + kIgnoreIfAbsent = 0, + kCreateIfAbsent = 1, + }; + + // Called at the start of each public operation, and initializes the database + // if it isn't already initialized (unless there is no pre-existing on-disk + // database to initialize and `policy` is + // `DBCreationPolicy::kIgnoreIfAbsent`). + [[nodiscard]] InitStatus LazyInit(DBCreationPolicy policy) + VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Determines whether or not an uninitialized DB already exists on disk. + [[nodiscard]] bool DBExists() VALID_CONTEXT_REQUIRED(sequence_checker_); + + // If `db_path_` is empty, opens a temporary database in memory; otherwise + // opens a persistent database with the absolute path `db_path`, creating the + // file if it does not yet exist. Returns whether opening was successful. + [[nodiscard]] bool OpenDatabase() VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Callback for database errors. Schedules a call to Destroy() if the + // error is catastrophic. + void DatabaseErrorCallback(int extended_error, sql::Statement* stmt) + VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Helper function to implement internals of `Init()`. This allows + // Init() to retry in case of failure, since some failures run + // recovery code. + [[nodiscard]] InitStatus InitImpl() VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Vacuums the database. This will cause sqlite to defragment and collect + // unused space in the file. It can be VERY SLOW. Returns whether the + // operation was successful. + [[nodiscard]] bool Vacuum() VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Clears all entries for `context_origin`. Returns whether deletion is + // successful. Not named `Clear()` to distinguish it from the public method + // called via `SequenceBound::AsyncCall()`. + [[nodiscard]] bool Purge(const std::string& context_origin) + VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Returns the number of entries for `context_origin`, i.e. the `length`. + // Not named `Length()` to distinguish it from the public method called via + // `SequenceBound::AsyncCall()`. + [[nodiscard]] int64_t NumEntries(const std::string& context_origin) + VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Returns whether an entry exists for `context_origin` and `key`. + [[nodiscard]] bool HasEntryFor(const std::string& context_origin, + const std::u16string& key) + VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Sets `last_used_time` to `new_last_used_time` for `context_origin`. + [[nodiscard]] bool SetLastUsedTime(const std::string& context_origin, + base::Time new_last_used_time) + VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Updates `last_used_time` to `base::Time::Now()` for `context_origin`. + [[nodiscard]] bool UpdateLastUsedTime(const std::string& context_origin) + VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Updates `length` by `delta` for `context_origin`. If `should_update_time` + // is true, also updates `last_used_time` to `base::Time::Now()`. + [[nodiscard]] bool UpdateLength(const std::string& context_origin, + int64_t delta, + bool should_update_time = true) + VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Inserts a triple for `(context_origin,key,value)` into + // `values_mapping`. + [[nodiscard]] bool InsertIntoValuesMapping(const std::string& context_origin, + const std::u16string& key, + const std::u16string& value) + VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Deletes the row for `context_origin` from `per_origin_mapping`. + [[nodiscard]] bool DeleteFromPerOriginMapping( + const std::string& context_origin) + VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Inserts the triple for `(context_origin, last_used_time, length)` into + // `per_origin_mapping`. + [[nodiscard]] bool InsertIntoPerOriginMapping( + const std::string& context_origin, + base::Time last_used_time, + uint64_t length) VALID_CONTEXT_REQUIRED(sequence_checker_); + + // Returns whether the `length` for `context_origin` is less than + // `max_entries_per_origin_`. + [[nodiscard]] bool HasCapacity(const std::string& context_origin) + VALID_CONTEXT_REQUIRED(sequence_checker_); + + // The database containing the actual data. + sql::Database db_ GUARDED_BY_CONTEXT(sequence_checker_); + + // Contains the version information. + sql::MetaTable meta_table_ GUARDED_BY_CONTEXT(sequence_checker_); + + // Initialization status of `db_`. + GUARDED_BY_CONTEXT(sequence_checker_) + InitStatus db_status_ = InitStatus::kUnattempted; + + // Only set to true if `DBExists() + DBFileStatus db_file_status_ GUARDED_BY_CONTEXT(sequence_checker_); + + // The path to the database, if file-backed. + base::FilePath db_path_ GUARDED_BY_CONTEXT(sequence_checker_); + + // The owning partition's storage policy. + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy_ + GUARDED_BY_CONTEXT(sequence_checker_); + + // The maximum allowed number of entries per origin. + const int64_t max_entries_per_origin_ GUARDED_BY_CONTEXT(sequence_checker_); + + // The maximum size of a string input from any origin's script. Applies + // separately to both script keys and script values. + size_t max_string_length_ GUARDED_BY_CONTEXT(sequence_checker_); + + // Maxmium number of times that SQL database attempts to initialize. + size_t max_init_tries_ GUARDED_BY_CONTEXT(sequence_checker_); + + // Clock used to determine current time. Can be overridden in tests. + raw_ptr<base::Clock> clock_ GUARDED_BY_CONTEXT(sequence_checker_); + + SEQUENCE_CHECKER(sequence_checker_); +}; + +} // namespace storage + +#endif // COMPONENTS_SERVICES_STORAGE_SHARED_STORAGE_SHARED_STORAGE_DATABASE_H_ diff --git a/chromium/components/services/storage/shared_storage/shared_storage_database_unittest.cc b/chromium/components/services/storage/shared_storage/shared_storage_database_unittest.cc new file mode 100644 index 00000000000..364bead255c --- /dev/null +++ b/chromium/components/services/storage/shared_storage/shared_storage_database_unittest.cc @@ -0,0 +1,835 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/services/storage/shared_storage/shared_storage_database.h" + +#include <algorithm> +#include <memory> +#include <string> +#include <vector> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_refptr.h" +#include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/test/scoped_feature_list.h" +#include "base/test/simple_test_clock.h" +#include "base/test/task_environment.h" +#include "base/time/time.h" +#include "components/services/storage/public/mojom/storage_usage_info.mojom.h" +#include "components/services/storage/shared_storage/shared_storage_options.h" +#include "components/services/storage/shared_storage/shared_storage_test_utils.h" +#include "sql/database.h" +#include "storage/browser/quota/special_storage_policy.h" +#include "storage/browser/test/mock_special_storage_policy.h" +#include "testing/gmock/include/gmock/gmock-matchers.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/common/features.h" +#include "url/gurl.h" +#include "url/origin.h" + +namespace storage { + +namespace { + +using ::testing::ElementsAre; +using OriginMatcherFunction = SharedStorageDatabase::OriginMatcherFunction; +using InitStatus = SharedStorageDatabase::InitStatus; +using SetBehavior = SharedStorageDatabase::SetBehavior; +using OperationResult = SharedStorageDatabase::OperationResult; +using GetResult = SharedStorageDatabase::GetResult; + +const int kMaxEntriesPerOrigin = 5; +const int kMaxStringLength = 100; + +} // namespace + +class SharedStorageDatabaseTest : public testing::Test { + public: + SharedStorageDatabaseTest() { + special_storage_policy_ = base::MakeRefCounted<MockSpecialStoragePolicy>(); + } + + ~SharedStorageDatabaseTest() override = default; + + void SetUp() override { + InitSharedStorageFeature(); + + // Get a temporary directory for the test DB files. + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + file_name_ = temp_dir_.GetPath().AppendASCII("TestSharedStorage.db"); + } + + void TearDown() override { + db_.reset(); + EXPECT_TRUE(temp_dir_.Delete()); + } + + // Initialize a shared storage database instance from the SQL file at + // `relative_file_path` in the "storage/" subdirectory of test data. + std::unique_ptr<SharedStorageDatabase> LoadFromFile( + const char* relative_file_path) { + if (!CreateDatabaseFromSQL(file_name_, relative_file_path)) { + ADD_FAILURE() << "Failed loading " << relative_file_path; + return nullptr; + } + + return std::make_unique<SharedStorageDatabase>( + file_name_, special_storage_policy_, + SharedStorageOptions::Create()->GetDatabaseOptions()); + } + + sql::Database* SqlDB() { return db_ ? db_->db() : nullptr; } + + virtual void InitSharedStorageFeature() { + scoped_feature_list_.InitAndEnableFeatureWithParameters( + {blink::features::kSharedStorageAPI}, + {{"MaxSharedStorageInitTries", "1"}}); + } + + protected: + base::ScopedTempDir temp_dir_; + base::FilePath file_name_; + scoped_refptr<storage::MockSpecialStoragePolicy> special_storage_policy_; + std::unique_ptr<SharedStorageDatabase> db_; + base::test::ScopedFeatureList scoped_feature_list_; + base::SimpleTestClock clock_; + + private: + base::test::SingleThreadTaskEnvironment task_environment_; +}; + +// Test loading version 1 database. +TEST_F(SharedStorageDatabaseTest, Version1_LoadFromFile) { + db_ = LoadFromFile("shared_storage.v1.sql"); + ASSERT_TRUE(db_); + + url::Origin google_com = url::Origin::Create(GURL("http://google.com/")); + EXPECT_EQ(db_->Get(google_com, u"key1").data, u"value1"); + EXPECT_EQ(db_->Get(google_com, u"key2").data, u"value2"); + + // Because the SQL database is lazy-initialized, wait to verify tables and + // columns until after the first call to `Get()`. + ASSERT_TRUE(SqlDB()); + VerifySharedStorageTablesAndColumns(*SqlDB()); + + url::Origin youtube_com = url::Origin::Create(GURL("http://youtube.com/")); + EXPECT_EQ(1L, db_->Length(youtube_com)); + + url::Origin chromium_org = url::Origin::Create(GURL("http://chromium.org/")); + EXPECT_EQ(db_->Get(chromium_org, u"a").data, u""); + EXPECT_EQ(db_->Key(chromium_org, 2UL).data, u"c"); + + url::Origin google_org = url::Origin::Create(GURL("http://google.org/")); + EXPECT_EQ( + db_->Get(google_org, u"1").data, + u"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffff"); + EXPECT_EQ(db_->Get(google_org, + u"ffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + .data, + u"k"); + + std::vector<url::Origin> origins; + for (const auto& info : db_->FetchOrigins()) + origins.push_back(info->origin); + EXPECT_THAT( + origins, + ElementsAre( + url::Origin::Create(GURL("http://abc.xyz")), chromium_org, google_com, + google_org, url::Origin::Create(GURL("http://growwithgoogle.com")), + url::Origin::Create(GURL("http://gv.com")), + url::Origin::Create(GURL("http://waymo.com")), + url::Origin::Create(GURL("http://withgoogle.com")), youtube_com)); + + EXPECT_TRUE(db_->Destroy()); +} + +TEST_F(SharedStorageDatabaseTest, Version1_DestroyTooNew) { + // Initialization should fail, since the last compatible version number + // is too high. + db_ = LoadFromFile("shared_storage.v1.init_too_new.sql"); + ASSERT_TRUE(db_); + ASSERT_TRUE(SqlDB()); + + // Call an operation so that the database will attempt to be lazy-initialized. + const url::Origin kOrigin = url::Origin::Create(GURL("http://www.a.com")); + EXPECT_EQ(OperationResult::kInitFailure, db_->Set(kOrigin, u"key", u"value")); + ASSERT_FALSE(db_->IsOpenForTesting()); + EXPECT_EQ(InitStatus::kTooNew, db_->DBStatusForTesting()); + + // Test that other operations likewise fail, in order to exercise these code + // paths. + EXPECT_EQ(OperationResult::kInitFailure, db_->Get(kOrigin, u"key").result); + EXPECT_EQ(OperationResult::kInitFailure, + db_->Append(kOrigin, u"key", u"value")); + EXPECT_EQ(OperationResult::kInitFailure, db_->Delete(kOrigin, u"key")); + EXPECT_EQ(OperationResult::kInitFailure, db_->Clear(kOrigin)); + EXPECT_EQ(-1, db_->Length(kOrigin)); + EXPECT_EQ(OperationResult::kInitFailure, db_->Key(kOrigin, 0).result); + EXPECT_EQ(OperationResult::kInitFailure, + db_->PurgeMatchingOrigins(OriginMatcherFunction(), + base::Time::Min(), base::Time::Max(), + /*perform_storage_cleanup=*/false)); + EXPECT_EQ(OperationResult::kInitFailure, + db_->PurgeStaleOrigins(base::Seconds(1))); + + // Test that it is still OK to Destroy() the database. + EXPECT_TRUE(db_->Destroy()); +} + +TEST_F(SharedStorageDatabaseTest, Version0_DestroyTooOld) { + // Initialization should fail, since the current version number + // is too low and we're forcing there not to be a retry attempt. + db_ = LoadFromFile("shared_storage.v0.init_too_old.sql"); + ASSERT_TRUE(db_); + ASSERT_TRUE(SqlDB()); + + // Call an operation so that the database will attempt to be lazy-initialized. + EXPECT_EQ(OperationResult::kInitFailure, + db_->Set(url::Origin::Create(GURL("http://www.a.com")), u"key", + u"value")); + ASSERT_FALSE(db_->IsOpenForTesting()); + EXPECT_EQ(InitStatus::kTooOld, db_->DBStatusForTesting()); + + // Test that it is still OK to Destroy() the database. + EXPECT_TRUE(db_->Destroy()); +} + +class SharedStorageDatabaseParamTest + : public SharedStorageDatabaseTest, + public testing::WithParamInterface<SharedStorageWrappedBool> { + public: + void SetUp() override { + SharedStorageDatabaseTest::SetUp(); + + auto options = SharedStorageOptions::Create()->GetDatabaseOptions(); + base::FilePath db_path = + (GetParam().in_memory_only) ? base::FilePath() : file_name_; + db_ = std::make_unique<SharedStorageDatabase>( + db_path, special_storage_policy_, std::move(options)); + db_->OverrideClockForTesting(&clock_); + } + + void InitSharedStorageFeature() override { + scoped_feature_list_.InitAndEnableFeatureWithParameters( + {blink::features::kSharedStorageAPI}, + {{"MaxSharedStorageEntriesPerOrigin", + base::NumberToString(kMaxEntriesPerOrigin)}, + {"MaxSharedStorageStringLength", + base::NumberToString(kMaxStringLength)}}); + } +}; + +INSTANTIATE_TEST_SUITE_P(All, + SharedStorageDatabaseParamTest, + testing::ValuesIn(GetSharedStorageWrappedBools()), + testing::PrintToStringParamName()); + +TEST_P(SharedStorageDatabaseParamTest, BasicOperations) { + const url::Origin kOrigin1 = + url::Origin::Create(GURL("http://www.example1.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(db_->Get(kOrigin1, u"key1").data, u"value1"); + + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key1", u"value2")); + EXPECT_EQ(db_->Get(kOrigin1, u"key1").data, u"value2"); + + EXPECT_EQ(OperationResult::kSuccess, db_->Delete(kOrigin1, u"key1")); + EXPECT_FALSE(db_->Get(kOrigin1, u"key1").data); + + // Check that trying to retrieve the empty key doesn't give an error, even + // though the input is invalid and no value is found. + GetResult result = db_->Get(kOrigin1, u""); + EXPECT_EQ(OperationResult::kSuccess, result.result); + EXPECT_FALSE(result.data); + + // Check that trying to delete the empty key doesn't give an error, even + // though the input is invalid and no value is found to delete. + EXPECT_EQ(OperationResult::kSuccess, db_->Delete(kOrigin1, u"")); +} + +TEST_P(SharedStorageDatabaseParamTest, IgnoreIfPresent) { + const url::Origin kOrigin1 = + url::Origin::Create(GURL("http://www.example1.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(db_->Get(kOrigin1, u"key1").data, u"value1"); + + // The database does not set a new value for "key1", but retains the + // previously set value "value1" because `behavior` is `kIgnoreIfPresent`. + EXPECT_EQ(OperationResult::kIgnored, + db_->Set(kOrigin1, u"key1", u"value2", + /*behavior=*/SetBehavior::kIgnoreIfPresent)); + EXPECT_EQ(db_->Get(kOrigin1, u"key1").data, u"value1"); + + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key2", u"value1")); + EXPECT_EQ(db_->Get(kOrigin1, u"key2").data, u"value1"); + + // Having `behavior` set to `kDefault` makes `Set()` override any previous + // value. + EXPECT_EQ(OperationResult::kSet, + db_->Set(kOrigin1, u"key2", u"value2", + /*behavior=*/SetBehavior::kDefault)); + EXPECT_EQ(db_->Get(kOrigin1, u"key2").data, u"value2"); + + const url::Origin kOrigin2 = + url::Origin::Create(GURL("http://www.example2.test")); + + // If no previous value exists, it makes no difference whether + // `behavior` is set to `kDefault` or `kIgnoreIfPresent`. + EXPECT_EQ(OperationResult::kSet, + db_->Set(kOrigin2, u"key1", u"value1", + /*behavior=*/SetBehavior::kIgnoreIfPresent)); + EXPECT_EQ(db_->Get(kOrigin2, u"key1").data, u"value1"); + + EXPECT_EQ(OperationResult::kSet, + db_->Set(kOrigin2, u"key2", u"value2", + /*behavior=*/SetBehavior::kDefault)); + EXPECT_EQ(db_->Get(kOrigin2, u"key2").data, u"value2"); +} + +TEST_P(SharedStorageDatabaseParamTest, Append) { + const url::Origin kOrigin1 = + url::Origin::Create(GURL("http://www.example1.test")); + EXPECT_EQ(OperationResult::kSet, db_->Append(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(db_->Get(kOrigin1, u"key1").data, u"value1"); + + EXPECT_EQ(OperationResult::kSet, db_->Append(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(db_->Get(kOrigin1, u"key1").data, u"value1value1"); + + EXPECT_EQ(OperationResult::kSet, db_->Append(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(db_->Get(kOrigin1, u"key1").data, u"value1value1value1"); +} + +TEST_P(SharedStorageDatabaseParamTest, Length) { + const url::Origin kOrigin1 = + url::Origin::Create(GURL("http://www.example1.test")); + EXPECT_EQ(0L, db_->Length(kOrigin1)); + + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(1L, db_->Length(kOrigin1)); + + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key2", u"value2")); + EXPECT_EQ(2L, db_->Length(kOrigin1)); + + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key2", u"value3")); + EXPECT_EQ(2L, db_->Length(kOrigin1)); + + const url::Origin kOrigin2 = + url::Origin::Create(GURL("http://www.example2.test")); + EXPECT_EQ(0L, db_->Length(kOrigin2)); + + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin2, u"key1", u"value1")); + EXPECT_EQ(1L, db_->Length(kOrigin2)); + EXPECT_EQ(2L, db_->Length(kOrigin1)); + + EXPECT_EQ(OperationResult::kSuccess, db_->Delete(kOrigin2, u"key1")); + EXPECT_EQ(0L, db_->Length(kOrigin2)); + EXPECT_EQ(2L, db_->Length(kOrigin1)); + + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key3", u"value3")); + EXPECT_EQ(3L, db_->Length(kOrigin1)); + EXPECT_EQ(0L, db_->Length(kOrigin2)); +} + +TEST_P(SharedStorageDatabaseParamTest, Key) { + const url::Origin kOrigin1 = + url::Origin::Create(GURL("http://www.example1.test")); + EXPECT_FALSE(db_->Key(kOrigin1, 0UL).data); + + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key2", u"value2")); + EXPECT_EQ(db_->Key(kOrigin1, 0UL).data, u"key1"); + EXPECT_EQ(db_->Key(kOrigin1, 1UL).data, u"key2"); + EXPECT_FALSE(db_->Key(kOrigin1, 2UL).data); + + const url::Origin kOrigin2 = + url::Origin::Create(GURL("http://www.example2.test")); + EXPECT_FALSE(db_->Key(kOrigin2, 0UL).data); + + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin2, u"key2", u"value2")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin2, u"key1", u"value1")); + EXPECT_EQ(db_->Key(kOrigin2, 0UL).data, u"key1"); + EXPECT_EQ(db_->Key(kOrigin2, 1UL).data, u"key2"); + + EXPECT_EQ(OperationResult::kSuccess, db_->Delete(kOrigin2, u"key2")); + EXPECT_EQ(db_->Key(kOrigin2, 0UL).data, u"key1"); + + // There is no longer a key at this index. + EXPECT_FALSE(db_->Key(kOrigin2, 1UL).data); +} + +TEST_P(SharedStorageDatabaseParamTest, Clear) { + const url::Origin kOrigin1 = + url::Origin::Create(GURL("http://www.example1.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key2", u"value2")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key3", u"value3")); + EXPECT_EQ(3L, db_->Length(kOrigin1)); + + const url::Origin kOrigin2 = + url::Origin::Create(GURL("http://www.example2.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin2, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin2, u"key2", u"value2")); + EXPECT_EQ(2L, db_->Length(kOrigin2)); + + EXPECT_EQ(OperationResult::kSuccess, db_->Clear(kOrigin1)); + EXPECT_EQ(0L, db_->Length(kOrigin1)); + EXPECT_EQ(2L, db_->Length(kOrigin2)); + + EXPECT_EQ(OperationResult::kSuccess, db_->Clear(kOrigin2)); + EXPECT_EQ(0L, db_->Length(kOrigin2)); +} + +TEST_P(SharedStorageDatabaseParamTest, FetchOrigins) { + EXPECT_TRUE(db_->FetchOrigins().empty()); + + const url::Origin kOrigin1 = + url::Origin::Create(GURL("http://www.example1.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key2", u"value2")); + EXPECT_EQ(2L, db_->Length(kOrigin1)); + + const url::Origin kOrigin2 = + url::Origin::Create(GURL("http://www.example2.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin2, u"key1", u"value1")); + EXPECT_EQ(1L, db_->Length(kOrigin2)); + + const url::Origin kOrigin3 = + url::Origin::Create(GURL("http://www.example3.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin3, u"key1", u"value1")); + EXPECT_EQ(1L, db_->Length(kOrigin3)); + + const url::Origin kOrigin4 = + url::Origin::Create(GURL("http://www.example4.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin4, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin4, u"key2", u"value2")); + EXPECT_EQ(2L, db_->Length(kOrigin4)); + + std::vector<url::Origin> origins; + for (const auto& info : db_->FetchOrigins()) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin1, kOrigin2, kOrigin3, kOrigin4)); + + EXPECT_EQ(OperationResult::kSuccess, db_->Clear(kOrigin1)); + EXPECT_EQ(0L, db_->Length(kOrigin1)); + + EXPECT_EQ(OperationResult::kSuccess, db_->Delete(kOrigin2, u"key1")); + EXPECT_EQ(0L, db_->Length(kOrigin2)); + + origins.clear(); + EXPECT_TRUE(origins.empty()); + for (const auto& info : db_->FetchOrigins()) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin3, kOrigin4)); +} + +class SharedStorageDatabasePurgeMatchingOriginsParamTest + : public SharedStorageDatabaseTest, + public testing::WithParamInterface<PurgeMatchingOriginsParams> { + public: + void SetUp() override { + SharedStorageDatabaseTest::SetUp(); + + auto options = SharedStorageOptions::Create()->GetDatabaseOptions(); + base::FilePath db_path = + (GetParam().in_memory_only) ? base::FilePath() : file_name_; + db_ = std::make_unique<SharedStorageDatabase>( + db_path, special_storage_policy_, std::move(options)); + db_->OverrideClockForTesting(&clock_); + } + + void InitSharedStorageFeature() override { + scoped_feature_list_.InitAndEnableFeatureWithParameters( + {blink::features::kSharedStorageAPI}, + {{"MaxSharedStorageEntriesPerOrigin", + base::NumberToString(kMaxEntriesPerOrigin)}, + {"MaxSharedStorageStringLength", + base::NumberToString(kMaxStringLength)}}); + } +}; + +INSTANTIATE_TEST_SUITE_P(All, + SharedStorageDatabasePurgeMatchingOriginsParamTest, + testing::ValuesIn(GetPurgeMatchingOriginsParams()), + testing::PrintToStringParamName()); + +TEST_P(SharedStorageDatabasePurgeMatchingOriginsParamTest, AllTime) { + EXPECT_TRUE(db_->FetchOrigins().empty()); + + const url::Origin kOrigin1 = + url::Origin::Create(GURL("http://www.example1.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key2", u"value2")); + EXPECT_EQ(2L, db_->Length(kOrigin1)); + + const url::Origin kOrigin2 = + url::Origin::Create(GURL("http://www.example2.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin2, u"key1", u"value1")); + EXPECT_EQ(1L, db_->Length(kOrigin2)); + + const url::Origin kOrigin3 = + url::Origin::Create(GURL("http://www.example3.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin3, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin3, u"key2", u"value2")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin3, u"key3", u"value3")); + EXPECT_EQ(3L, db_->Length(kOrigin3)); + + std::vector<url::Origin> origins; + for (const auto& info : db_->FetchOrigins()) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin1, kOrigin2, kOrigin3)); + + EXPECT_EQ( + OperationResult::kSuccess, + db_->PurgeMatchingOrigins( + OriginMatcherFunctionUtility::MakeMatcherFunction({kOrigin1}), + base::Time(), base::Time::Max(), GetParam().perform_storage_cleanup)); + + // `kOrigin1` is cleared. The other origins are not. + EXPECT_EQ(0L, db_->Length(kOrigin1)); + EXPECT_EQ(1L, db_->Length(kOrigin2)); + EXPECT_EQ(3L, db_->Length(kOrigin3)); + + origins.clear(); + for (const auto& info : db_->FetchOrigins()) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin2, kOrigin3)); + + EXPECT_EQ( + OperationResult::kSuccess, + db_->PurgeMatchingOrigins( + OriginMatcherFunctionUtility::MakeMatcherFunction( + {kOrigin2, kOrigin3}), + base::Time(), base::Time::Max(), GetParam().perform_storage_cleanup)); + + // All three origins should be cleared. + EXPECT_EQ(0L, db_->Length(kOrigin1)); + EXPECT_EQ(0L, db_->Length(kOrigin2)); + EXPECT_EQ(0L, db_->Length(kOrigin3)); + + EXPECT_TRUE(db_->FetchOrigins().empty()); + + // There is no error from trying to clear an origin that isn't in the + // database. + EXPECT_EQ( + OperationResult::kSuccess, + db_->PurgeMatchingOrigins( + OriginMatcherFunctionUtility::MakeMatcherFunction( + {"http://www.example4.test"}), + base::Time(), base::Time::Max(), GetParam().perform_storage_cleanup)); +} + +TEST_P(SharedStorageDatabasePurgeMatchingOriginsParamTest, SinceThreshold) { + EXPECT_TRUE(db_->FetchOrigins().empty()); + + const url::Origin kOrigin1 = + url::Origin::Create(GURL("http://www.example1.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key2", u"value2")); + EXPECT_EQ(2L, db_->Length(kOrigin1)); + + const url::Origin kOrigin2 = + url::Origin::Create(GURL("http://www.example2.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin2, u"key1", u"value1")); + EXPECT_EQ(1L, db_->Length(kOrigin2)); + + const url::Origin kOrigin3 = + url::Origin::Create(GURL("http://www.example3.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin3, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin3, u"key2", u"value2")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin3, u"key3", u"value3")); + EXPECT_EQ(3L, db_->Length(kOrigin3)); + + const url::Origin kOrigin4 = + url::Origin::Create(GURL("http://www.example4.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin4, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin4, u"key2", u"value2")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin4, u"key3", u"value3")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin4, u"key4", u"value4")); + EXPECT_EQ(4L, db_->Length(kOrigin4)); + + clock_.SetNow(base::Time::Now()); + clock_.Advance(base::Milliseconds(50)); + + // Time threshold that will be used as a starting point for deletion. + base::Time threshold = clock_.Now(); + + std::vector<url::Origin> origins; + for (const auto& info : db_->FetchOrigins()) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin1, kOrigin2, kOrigin3, kOrigin4)); + + // Read from `kOrigin1`. + EXPECT_EQ(db_->Get(kOrigin1, u"key1").data, u"value1"); + + EXPECT_EQ( + OperationResult::kSuccess, + db_->PurgeMatchingOrigins( + OriginMatcherFunctionUtility::MakeMatcherFunction( + {kOrigin1, kOrigin2}), + threshold, base::Time::Max(), GetParam().perform_storage_cleanup)); + + // `kOrigin1` is cleared. The other origins are not. + EXPECT_EQ(0L, db_->Length(kOrigin1)); + EXPECT_EQ(1L, db_->Length(kOrigin2)); + EXPECT_EQ(3L, db_->Length(kOrigin3)); + EXPECT_EQ(4L, db_->Length(kOrigin4)); + + clock_.Advance(base::Milliseconds(50)); + + // Time threshold that will be used as a starting point for deletion. + threshold = clock_.Now(); + + // Write to `kOrigin3`. + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin3, u"key4", u"value4")); + EXPECT_EQ(4L, db_->Length(kOrigin3)); + + origins.clear(); + for (const auto& info : db_->FetchOrigins()) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin2, kOrigin3, kOrigin4)); + + EXPECT_EQ( + OperationResult::kSuccess, + db_->PurgeMatchingOrigins( + OriginMatcherFunctionUtility::MakeMatcherFunction( + {kOrigin2, kOrigin3, kOrigin4}), + threshold, base::Time::Max(), GetParam().perform_storage_cleanup)); + + // `kOrigin3` is cleared. The others weren't modified within the given time + // period. + EXPECT_EQ(0L, db_->Length(kOrigin1)); + EXPECT_EQ(1L, db_->Length(kOrigin2)); + EXPECT_EQ(0L, db_->Length(kOrigin3)); + EXPECT_EQ(4L, db_->Length(kOrigin4)); + + origins.clear(); + for (const auto& info : db_->FetchOrigins()) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin2, kOrigin4)); + + // There is no error from trying to clear an origin that isn't in the + // database. + EXPECT_EQ( + OperationResult::kSuccess, + db_->PurgeMatchingOrigins( + OriginMatcherFunctionUtility::MakeMatcherFunction( + {"http://www.example5.test"}), + threshold, base::Time::Max(), GetParam().perform_storage_cleanup)); +} + +TEST_P(SharedStorageDatabaseParamTest, PurgeStaleOrigins) { + EXPECT_TRUE(db_->FetchOrigins().empty()); + + const url::Origin kOrigin1 = + url::Origin::Create(GURL("http://www.example1.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key2", u"value2")); + EXPECT_EQ(2L, db_->Length(kOrigin1)); + EXPECT_EQ(db_->Get(kOrigin1, u"key1").data, u"value1"); + EXPECT_EQ(db_->Get(kOrigin1, u"key2").data, u"value2"); + + const url::Origin kOrigin2 = + url::Origin::Create(GURL("http://www.example2.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin2, u"key1", u"value1")); + EXPECT_EQ(1L, db_->Length(kOrigin2)); + EXPECT_EQ(db_->Get(kOrigin2, u"key1").data, u"value1"); + + const url::Origin kOrigin3 = + url::Origin::Create(GURL("http://www.example3.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin3, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin3, u"key2", u"value2")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin3, u"key3", u"value3")); + EXPECT_EQ(3L, db_->Length(kOrigin3)); + + const url::Origin kOrigin4 = + url::Origin::Create(GURL("http://www.example4.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin4, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin4, u"key2", u"value2")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin4, u"key3", u"value3")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin4, u"key4", u"value4")); + EXPECT_EQ(4L, db_->Length(kOrigin4)); + + std::vector<url::Origin> origins; + for (const auto& info : db_->FetchOrigins()) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin1, kOrigin2, kOrigin3, kOrigin4)); + + clock_.SetNow(base::Time::Now()); + clock_.Advance(base::Milliseconds(50)); + + // Time threshold after which an origin must be read from or written to in + // order to be considered active. + base::Time threshold = clock_.Now(); + clock_.Advance(base::Milliseconds(50)); + + // Read from `kOrigin1`. + EXPECT_EQ(db_->Get(kOrigin1, u"key1").data, u"value1"); + + // Write to `kOrigin3`. + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin3, u"key4", u"value4")); + EXPECT_EQ(4L, db_->Length(kOrigin3)); + + EXPECT_EQ(OperationResult::kSuccess, + db_->PurgeStaleOrigins(clock_.Now() - threshold)); + + // `kOrigin1` was active. + EXPECT_EQ(2L, db_->Length(kOrigin1)); + + // `kOrigin2` was inactive. + EXPECT_EQ(0L, db_->Length(kOrigin2)); + + // `kOrigin3` was active. + EXPECT_EQ(4L, db_->Length(kOrigin3)); + + // `kOrigin4` was inactive. + EXPECT_EQ(0L, db_->Length(kOrigin4)); + + origins.clear(); + for (const auto& info : db_->FetchOrigins()) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin1, kOrigin3)); +} + +TEST_P(SharedStorageDatabaseParamTest, TrimMemory) { + EXPECT_TRUE(db_->FetchOrigins().empty()); + + const url::Origin kOrigin1 = + url::Origin::Create(GURL("http://www.example1.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key2", u"value2")); + EXPECT_EQ(2L, db_->Length(kOrigin1)); + + const url::Origin kOrigin2 = + url::Origin::Create(GURL("http://www.example2.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin2, u"key1", u"value1")); + EXPECT_EQ(1L, db_->Length(kOrigin2)); + + const url::Origin kOrigin3 = + url::Origin::Create(GURL("http://www.example3.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin3, u"key1", u"value1")); + EXPECT_EQ(1L, db_->Length(kOrigin3)); + + const url::Origin kOrigin4 = + url::Origin::Create(GURL("http://www.example4.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin4, u"key1", u"value1")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin4, u"key2", u"value2")); + EXPECT_EQ(2L, db_->Length(kOrigin4)); + + std::vector<url::Origin> origins; + for (const auto& info : db_->FetchOrigins()) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin1, kOrigin2, kOrigin3, kOrigin4)); + + EXPECT_EQ(OperationResult::kSuccess, db_->Clear(kOrigin1)); + EXPECT_EQ(0L, db_->Length(kOrigin1)); + + EXPECT_EQ(OperationResult::kSuccess, db_->Delete(kOrigin2, u"key1")); + EXPECT_EQ(0L, db_->Length(kOrigin2)); + + origins.clear(); + for (const auto& info : db_->FetchOrigins()) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin3, kOrigin4)); + + // Release nonessential memory. + db_->TrimMemory(); + + // Check that the database is still intact. + origins.clear(); + for (const auto& info : db_->FetchOrigins()) + origins.push_back(info->origin); + EXPECT_THAT(origins, ElementsAre(kOrigin3, kOrigin4)); + + EXPECT_EQ(1L, db_->Length(kOrigin3)); + EXPECT_EQ(2L, db_->Length(kOrigin4)); + + EXPECT_EQ(db_->Get(kOrigin3, u"key1").data, u"value1"); + EXPECT_EQ(db_->Get(kOrigin4, u"key1").data, u"value1"); + EXPECT_EQ(db_->Get(kOrigin4, u"key2").data, u"value2"); +} + +TEST_P(SharedStorageDatabaseParamTest, MaxEntriesPerOrigin) { + const url::Origin kOrigin1 = + url::Origin::Create(GURL("http://www.example1.test")); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key1", u"value1")); + EXPECT_EQ(1L, db_->Length(kOrigin1)); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key2", u"value2")); + EXPECT_EQ(2L, db_->Length(kOrigin1)); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key3", u"value3")); + EXPECT_EQ(3L, db_->Length(kOrigin1)); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key4", u"value4")); + EXPECT_EQ(4L, db_->Length(kOrigin1)); + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key5", u"value5")); + EXPECT_EQ(5L, db_->Length(kOrigin1)); + + // `kOrigin1` should have hit capacity, and hence this value will not be set. + EXPECT_EQ(OperationResult::kNoCapacity, + db_->Set(kOrigin1, u"key6", u"value6")); + + EXPECT_EQ(5L, db_->Length(kOrigin1)); + EXPECT_EQ(OperationResult::kSuccess, db_->Delete(kOrigin1, u"key5")); + EXPECT_EQ(4L, db_->Length(kOrigin1)); + + // There should now be capacity and the value will be set. + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key6", u"value6")); + EXPECT_EQ(5L, db_->Length(kOrigin1)); +} + +TEST_P(SharedStorageDatabaseParamTest, MaxStringLength) { + const url::Origin kOrigin1 = + url::Origin::Create(GURL("http://www.example1.test")); + const std::u16string kLongString(kMaxStringLength, u'g'); + + // This value has the maximum allowed length. + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, u"key1", kLongString)); + EXPECT_EQ(1L, db_->Length(kOrigin1)); + + // Appending to the value would exceed the allowed length and so won't + // succeed. + EXPECT_EQ(OperationResult::kInvalidAppend, + db_->Append(kOrigin1, u"key1", u"h")); + + EXPECT_EQ(1L, db_->Length(kOrigin1)); + + // This key has the maximum allowed length. + EXPECT_EQ(OperationResult::kSet, db_->Set(kOrigin1, kLongString, u"value1")); + EXPECT_EQ(2L, db_->Length(kOrigin1)); +} + +} // namespace storage diff --git a/chromium/components/services/storage/shared_storage/shared_storage_options.cc b/chromium/components/services/storage/shared_storage/shared_storage_options.cc new file mode 100644 index 00000000000..fcbb0343a98 --- /dev/null +++ b/chromium/components/services/storage/shared_storage/shared_storage_options.cc @@ -0,0 +1,80 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/services/storage/shared_storage/shared_storage_options.h" + +#include "base/bits.h" +#include "third_party/blink/public/common/features.h" + +namespace storage { + +namespace { + +bool IsValidPageSize(int page_size) { + if (page_size < 512 || page_size > 65536) + return false; + return base::bits::IsPowerOfTwo(page_size); +} + +} // namespace + +// static +std::unique_ptr<SharedStorageOptions> SharedStorageOptions::Create() { + return std::make_unique<SharedStorageOptions>( + blink::features::kMaxSharedStoragePageSize.Get(), + blink::features::kMaxSharedStorageCacheSize.Get(), + blink::features::kMaxSharedStorageEntriesPerOrigin.Get(), + blink::features::kMaxSharedStorageStringLength.Get(), + blink::features::kMaxSharedStorageInitTries.Get(), + blink::features::kMaxSharedStorageConsecutiveOperationErrorsAllowed.Get(), + blink::features::kSharedStorageStaleOriginPurgeInitialInterval.Get(), + blink::features::kSharedStorageStaleOriginPurgeRecurringInterval.Get(), + blink::features::kSharedStorageOriginStalenessThreshold.Get()); +} + +SharedStorageOptions::SharedStorageOptions( + int max_page_size, + int max_cache_size, + int max_entries_per_origin, + int max_string_length, + int max_init_tries, + int max_allowed_consecutive_errors, + base::TimeDelta stale_origin_purge_initial_interval, + base::TimeDelta stale_origin_purge_recurring_interval, + base::TimeDelta origin_staleness_threshold) + : max_page_size(max_page_size), + max_cache_size(max_cache_size), + max_entries_per_origin(max_entries_per_origin), + max_string_length(max_string_length), + max_init_tries(max_init_tries), + max_allowed_consecutive_errors(max_allowed_consecutive_errors), + stale_origin_purge_initial_interval(stale_origin_purge_initial_interval), + stale_origin_purge_recurring_interval( + stale_origin_purge_recurring_interval), + origin_staleness_threshold(origin_staleness_threshold) { + DCHECK(IsValidPageSize(max_page_size)); +} + +std::unique_ptr<SharedStorageDatabaseOptions> +SharedStorageOptions::GetDatabaseOptions() { + return std::make_unique<SharedStorageDatabaseOptions>( + max_page_size, max_cache_size, max_entries_per_origin, max_string_length, + max_init_tries); +} + +SharedStorageDatabaseOptions::SharedStorageDatabaseOptions( + int max_page_size, + int max_cache_size, + int max_entries_per_origin, + int max_string_length, + int max_init_tries) + : max_page_size(max_page_size), + max_cache_size(max_cache_size), + max_entries_per_origin(max_entries_per_origin), + max_string_length(max_string_length), + max_init_tries(max_init_tries) { + DCHECK(IsValidPageSize(max_page_size)); +} + +} // namespace storage diff --git a/chromium/components/services/storage/shared_storage/shared_storage_options.h b/chromium/components/services/storage/shared_storage/shared_storage_options.h new file mode 100644 index 00000000000..751662e24a2 --- /dev/null +++ b/chromium/components/services/storage/shared_storage/shared_storage_options.h @@ -0,0 +1,104 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SERVICES_STORAGE_SHARED_STORAGE_SHARED_STORAGE_OPTIONS_H_ +#define COMPONENTS_SERVICES_STORAGE_SHARED_STORAGE_SHARED_STORAGE_OPTIONS_H_ + +#include <memory> + +#include "base/time/time.h" + +namespace storage { + +struct SharedStorageDatabaseOptions; + +// Bundles Finch-configurable constants for the `SharedStorageManager`, +// `AsyncSharedStorageDatabase`, and `SharedStorageDatabase` classes. +struct SharedStorageOptions { + // The static `Create()` method accesses field trial params to populate one or + // more attributes, and so must be called on the main thread. + static std::unique_ptr<SharedStorageOptions> Create(); + + SharedStorageOptions(int max_page_size, + int max_cache_size, + int max_entries_per_origin, + int max_string_length, + int max_init_tries, + int max_allowed_consecutive_errors, + base::TimeDelta stale_origin_purge_initial_interval, + base::TimeDelta stale_origin_purge_recurring_interval, + base::TimeDelta origin_staleness_threshold); + + // Creates a pointer to a smaller bundle of just the constants that need to + // be forwarded to `AsyncSharedStorageDatabase` and `SharedStorageDatabase`. + std::unique_ptr<SharedStorageDatabaseOptions> GetDatabaseOptions(); + + // The max size of a database page, in bytes. Must be a power of 2 between + // 512 and 65536 inclusive. + const int max_page_size; + + // The max size of the database cache, in pages. + const int max_cache_size; + + // The maximum number of entries allowed per origin. + const int max_entries_per_origin; + + // The maximum allowed string length for each script key or script value. + const int max_string_length; + + // The maximum number of times that `SharedStorageDatabase` will try to + // initialize the SQL database. + const int max_init_tries; + + // Maximum number of consecutive operation errors allowed before the database + // is deleted and recreated. + const int max_allowed_consecutive_errors; + + // The initial interval at which stale origins are purged. + const base::TimeDelta stale_origin_purge_initial_interval; + + // The recurring interval at which stale origins are purged. May differ from + // the initial interval. + const base::TimeDelta stale_origin_purge_recurring_interval; + + // The amount of time that an origin needs to be inactive in order for it to + // be deemed stale. + const base::TimeDelta origin_staleness_threshold; +}; + +// Bundles Finch-configurable constants for the `AsyncSharedStorageDatabase` +// and `SharedStorageDatabase` classes. This smaller class is separate from the +// larger `SharedStorageOptions` (which has the ability to create an instance of +// `SharedStorageDatabaseOptions` from a subset of its members) so that the +// smaller `SharedStorageDatabaseOptions` bundle can be read on an alternate +// thread while the larger class's bundle can continue to be accessed on the +// main thread. +struct SharedStorageDatabaseOptions { + SharedStorageDatabaseOptions(int max_page_size, + int max_cache_size, + int max_entries_per_origin, + int max_string_length, + int max_init_tries); + + // The max size of a database page, in bytes. Must be a power of 2 between + // 512 and 65536 inclusive. + const int max_page_size; + + // The max size of the database cache, in pages. + const int max_cache_size; + + // The maximum number of entries allowed per origin. + const int max_entries_per_origin; + + // The maximum allowed string length for each script key or script value. + const int max_string_length; + + // The maximum number of times that `SharedStorageDatabase` will try to + // initialize the SQL database. + const int max_init_tries; +}; + +} // namespace storage + +#endif // COMPONENTS_SERVICES_STORAGE_SHARED_STORAGE_SHARED_STORAGE_OPTIONS_H_ diff --git a/chromium/components/services/storage/shared_storage/shared_storage_test_utils.cc b/chromium/components/services/storage/shared_storage/shared_storage_test_utils.cc new file mode 100644 index 00000000000..2c13bbf283a --- /dev/null +++ b/chromium/components/services/storage/shared_storage/shared_storage_test_utils.cc @@ -0,0 +1,369 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/services/storage/shared_storage/shared_storage_test_utils.h" + +#include <queue> +#include <string> +#include <utility> +#include <vector> + +#include "base/containers/contains.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/strings/strcat.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "components/services/storage/public/mojom/storage_usage_info.mojom.h" +#include "sql/database.h" +#include "sql/test/test_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace storage { + +TestDatabaseOperationReceiver::DBOperation::DBOperation(Type type) + : type(type) { + DCHECK(type == Type::DB_IS_OPEN || type == Type::DB_STATUS || + type == Type::DB_DESTROY || type == Type::DB_TRIM_MEMORY || + type == Type::DB_FETCH_ORIGINS); +} + +TestDatabaseOperationReceiver::DBOperation::DBOperation(Type type, + url::Origin origin) + : type(type), origin(std::move(origin)) { + DCHECK(type == Type::DB_LENGTH || type == Type::DB_CLEAR); +} + +TestDatabaseOperationReceiver::DBOperation::DBOperation( + Type type, + url::Origin origin, + std::vector<std::u16string> params) + : type(type), origin(std::move(origin)), params(std::move(params)) { + DCHECK(type == Type::DB_GET || type == Type::DB_SET || + type == Type::DB_APPEND || type == Type::DB_DELETE || + type == Type::DB_KEY || type == Type::DB_OVERRIDE_TIME); +} + +TestDatabaseOperationReceiver::DBOperation::DBOperation( + Type type, + std::vector<std::u16string> params) + : type(type), params(std::move(params)) { + DCHECK(type == Type::DB_PURGE_MATCHING || type == Type::DB_PURGE_STALE); +} + +TestDatabaseOperationReceiver::DBOperation::~DBOperation() = default; + +TestDatabaseOperationReceiver::DBOperation::DBOperation(const DBOperation&) = + default; + +bool TestDatabaseOperationReceiver::DBOperation::operator==( + const DBOperation& operation) const { + if (type != operation.type || params != operation.params) + return false; + + if (origin.opaque() && operation.origin.opaque()) + return true; + + return origin == operation.origin; +} + +bool TestDatabaseOperationReceiver::DBOperation::operator!=( + const DBOperation& operation) const { + if (type != operation.type || params != operation.params) + return true; + + if (origin.opaque() && operation.origin.opaque()) + return false; + + return origin != operation.origin; +} + +std::string TestDatabaseOperationReceiver::DBOperation::Serialize() const { + std::string serialization( + base::StrCat({"type: ", base::NumberToString(static_cast<int>(type)), + "; origin: ", origin.Serialize(), "; params: {"})); + for (int i = 0; i < static_cast<int>(params.size()) - 1; i++) { + serialization = + base::StrCat({serialization, base::UTF16ToUTF8(params[i]), ","}); + } + serialization = params.empty() + ? base::StrCat({serialization, "}"}) + : base::StrCat({serialization, + base::UTF16ToUTF8(params.back()), "}"}); + return serialization; +} + +TestDatabaseOperationReceiver::TestDatabaseOperationReceiver() = default; + +TestDatabaseOperationReceiver::~TestDatabaseOperationReceiver() = default; + +// static +std::u16string TestDatabaseOperationReceiver::SerializeTime(base::Time time) { + return SerializeTimeDelta(time.ToDeltaSinceWindowsEpoch()); +} + +// static + +std::u16string TestDatabaseOperationReceiver::SerializeTimeDelta( + base::TimeDelta delta) { + return base::StrCat({base::NumberToString16(delta.InMicroseconds()), u"us"}); +} + +// static +std::u16string TestDatabaseOperationReceiver::SerializeBool(bool b) { + return b ? u"true" : u"false"; +} + +// static +std::u16string TestDatabaseOperationReceiver::SerializeSetBehavior( + SetBehavior behavior) { + return base::NumberToString16(static_cast<int>(behavior)); +} + +void TestDatabaseOperationReceiver::WaitForOperations() { + finished_ = false; + loop_.Run(); + if (expected_operations_.empty()) + Finish(); +} + +void TestDatabaseOperationReceiver::GetResultCallbackBase( + const DBOperation& current_operation, + GetResult* out_result, + GetResult result) { + DCHECK(out_result); + *out_result = std::move(result); + + if (ExpectationsMet(current_operation) && loop_.running()) + Finish(); +} + +base::OnceCallback<void(GetResult)> +TestDatabaseOperationReceiver::MakeGetResultCallback( + const DBOperation& current_operation, + GetResult* out_result) { + return base::BindOnce(&TestDatabaseOperationReceiver::GetResultCallbackBase, + base::Unretained(this), current_operation, out_result); +} + +void TestDatabaseOperationReceiver::OperationResultCallbackBase( + const DBOperation& current_operation, + OperationResult* out_result, + OperationResult result) { + DCHECK(out_result); + *out_result = result; + + if (ExpectationsMet(current_operation) && loop_.running()) + Finish(); +} + +base::OnceCallback<void(OperationResult)> +TestDatabaseOperationReceiver::MakeOperationResultCallback( + const DBOperation& current_operation, + OperationResult* out_result) { + return base::BindOnce( + &TestDatabaseOperationReceiver::OperationResultCallbackBase, + base::Unretained(this), current_operation, out_result); +} + +void TestDatabaseOperationReceiver::IntCallbackBase( + const DBOperation& current_operation, + int* out_length, + int length) { + DCHECK(out_length); + *out_length = length; + + if (ExpectationsMet(current_operation) && loop_.running()) + Finish(); +} + +base::OnceCallback<void(int)> TestDatabaseOperationReceiver::MakeIntCallback( + const DBOperation& current_operation, + int* out_length) { + return base::BindOnce(&TestDatabaseOperationReceiver::IntCallbackBase, + base::Unretained(this), current_operation, out_length); +} + +void TestDatabaseOperationReceiver::BoolCallbackBase( + const DBOperation& current_operation, + bool* out_boolean, + bool boolean) { + DCHECK(out_boolean); + *out_boolean = boolean; + + if (ExpectationsMet(current_operation) && loop_.running()) + Finish(); +} + +base::OnceCallback<void(bool)> TestDatabaseOperationReceiver::MakeBoolCallback( + const DBOperation& current_operation, + bool* out_boolean) { + return base::BindOnce(&TestDatabaseOperationReceiver::BoolCallbackBase, + base::Unretained(this), current_operation, out_boolean); +} + +void TestDatabaseOperationReceiver::StatusCallbackBase( + const DBOperation& current_operation, + InitStatus* out_status, + InitStatus status) { + DCHECK(out_status); + *out_status = status; + + if (ExpectationsMet(current_operation) && loop_.running()) + Finish(); +} + +base::OnceCallback<void(InitStatus)> +TestDatabaseOperationReceiver::MakeStatusCallback( + const DBOperation& current_operation, + InitStatus* out_status) { + return base::BindOnce(&TestDatabaseOperationReceiver::StatusCallbackBase, + base::Unretained(this), current_operation, out_status); +} + +void TestDatabaseOperationReceiver::InfosCallbackBase( + const DBOperation& current_operation, + std::vector<mojom::StorageUsageInfoPtr>* out_infos, + std::vector<mojom::StorageUsageInfoPtr> infos) { + DCHECK(out_infos); + *out_infos = std::move(infos); + + if (ExpectationsMet(current_operation) && loop_.running()) + Finish(); +} + +base::OnceCallback<void(std::vector<mojom::StorageUsageInfoPtr>)> +TestDatabaseOperationReceiver::MakeInfosCallback( + const DBOperation& current_operation, + std::vector<mojom::StorageUsageInfoPtr>* out_infos) { + return base::BindOnce(&TestDatabaseOperationReceiver::InfosCallbackBase, + base::Unretained(this), current_operation, out_infos); +} + +void TestDatabaseOperationReceiver::OnceClosureBase( + const DBOperation& current_operation) { + if (ExpectationsMet(current_operation) && loop_.running()) + Finish(); +} + +base::OnceClosure TestDatabaseOperationReceiver::MakeOnceClosure( + const DBOperation& current_operation) { + return base::BindOnce(&TestDatabaseOperationReceiver::OnceClosureBase, + base::Unretained(this), current_operation); +} + +bool TestDatabaseOperationReceiver::ExpectationsMet( + const DBOperation& current_operation) { + EXPECT_FALSE(expected_operations_.empty()); + + if (expected_operations_.empty()) + return false; + + EXPECT_EQ(expected_operations_.front(), current_operation) + << "expected operation: " << expected_operations_.front().Serialize() + << std::endl + << "actual operation: " << current_operation.Serialize() << std::endl; + + if (expected_operations_.front() != current_operation) { + return false; + } else { + expected_operations_.pop(); + return expected_operations_.empty(); + } +} + +void TestDatabaseOperationReceiver::Finish() { + finished_ = true; + loop_.Quit(); +} + +OriginMatcherFunctionUtility::OriginMatcherFunctionUtility() = default; +OriginMatcherFunctionUtility::~OriginMatcherFunctionUtility() = default; + +OriginMatcherFunction OriginMatcherFunctionUtility::MakeMatcherFunction( + std::vector<url::Origin> origins_to_match) { + return base::BindRepeating( + [](std::vector<url::Origin> origins_to_match, const url::Origin& origin, + SpecialStoragePolicy* policy) { + return base::Contains(origins_to_match, origin); + }, + origins_to_match); +} + +OriginMatcherFunction OriginMatcherFunctionUtility::MakeMatcherFunction( + std::vector<std::string> origin_strs_to_match) { + std::vector<url::Origin> origins_to_match; + for (const auto& str : origin_strs_to_match) + origins_to_match.push_back(url::Origin::Create(GURL(str))); + return MakeMatcherFunction(origins_to_match); +} + +size_t OriginMatcherFunctionUtility::RegisterMatcherFunction( + std::vector<url::Origin> origins_to_match) { + matcher_table_.emplace_back(MakeMatcherFunction(origins_to_match)); + return matcher_table_.size() - 1; +} + +OriginMatcherFunction OriginMatcherFunctionUtility::TakeMatcherFunctionForId( + size_t id) { + DCHECK_LT(id, matcher_table_.size()); + return std::move(matcher_table_[id]); +} + +std::vector<SharedStorageWrappedBool> GetSharedStorageWrappedBools() { + return std::vector<SharedStorageWrappedBool>({{true}, {false}}); +} + +std::string PrintToString(const SharedStorageWrappedBool& b) { + return b.in_memory_only ? "InMemoryOnly" : "FileBacked"; +} + +std::vector<PurgeMatchingOriginsParams> GetPurgeMatchingOriginsParams() { + return std::vector<PurgeMatchingOriginsParams>( + {{true, true}, {true, false}, {false, true}, {false, false}}); +} + +std::string PrintToString(const PurgeMatchingOriginsParams& p) { + return base::StrCat({(p.in_memory_only ? "InMemoryOnly" : "FileBacked"), + "_With", (p.perform_storage_cleanup ? "" : "out"), + "Cleanup"}); +} + +void VerifySharedStorageTablesAndColumns(sql::Database& db) { + // `meta`, `values_mapping`, and `per_origin_mapping`. + EXPECT_EQ(3u, sql::test::CountSQLTables(&db)); + + // Implicit index on `meta` and `per_origin_mapping_last_used_time_idx`. + EXPECT_EQ(2u, sql::test::CountSQLIndices(&db)); + + // `key` and `value`. + EXPECT_EQ(2u, sql::test::CountTableColumns(&db, "meta")); + + // `context_origin`, `script_key`, and `script_value`. + EXPECT_EQ(3u, sql::test::CountTableColumns(&db, "values_mapping")); + + // `context_origin`, `last_used_time`, and `length`. + EXPECT_EQ(3u, sql::test::CountTableColumns(&db, "per_origin_mapping")); +} + +bool GetTestDataSharedStorageDir(base::FilePath* dir) { + if (!base::PathService::Get(base::DIR_SOURCE_ROOT, dir)) + return false; + *dir = dir->AppendASCII("components"); + *dir = dir->AppendASCII("test"); + *dir = dir->AppendASCII("data"); + *dir = dir->AppendASCII("storage"); + return true; +} + +bool CreateDatabaseFromSQL(const base::FilePath& db_path, + const char* ascii_path) { + base::FilePath dir; + if (!GetTestDataSharedStorageDir(&dir)) + return false; + return sql::test::CreateDatabaseFromSQL(db_path, dir.AppendASCII(ascii_path)); +} + +} // namespace storage diff --git a/chromium/components/services/storage/shared_storage/shared_storage_test_utils.h b/chromium/components/services/storage/shared_storage/shared_storage_test_utils.h new file mode 100644 index 00000000000..b518d06321f --- /dev/null +++ b/chromium/components/services/storage/shared_storage/shared_storage_test_utils.h @@ -0,0 +1,210 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SERVICES_STORAGE_SHARED_STORAGE_SHARED_STORAGE_TEST_UTILS_H_ +#define COMPONENTS_SERVICES_STORAGE_SHARED_STORAGE_SHARED_STORAGE_TEST_UTILS_H_ + +#include <queue> +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "components/services/storage/public/mojom/storage_usage_info.mojom-forward.h" +#include "components/services/storage/shared_storage/shared_storage_database.h" +#include "url/origin.h" + +namespace base { +class FilePath; +} // namespace base + +namespace sql { +class Database; +} // namespace sql + +namespace storage { + +using OriginMatcherFunction = SharedStorageDatabase::OriginMatcherFunction; +using InitStatus = SharedStorageDatabase::InitStatus; +using SetBehavior = SharedStorageDatabase::SetBehavior; +using OperationResult = SharedStorageDatabase::OperationResult; +using GetResult = SharedStorageDatabase::GetResult; + +// Helper class for testing async operations, accessible here for unit tests +// of both `AsyncSharedStorageDatabase` and `SharedStorageManager`. +class TestDatabaseOperationReceiver { + public: + struct DBOperation { + enum class Type { + DB_DESTROY = 0, + DB_TRIM_MEMORY = 1, + DB_GET = 2, + DB_SET = 3, + DB_APPEND = 4, + DB_DELETE = 5, + DB_CLEAR = 6, + DB_LENGTH = 7, + DB_KEY = 8, + DB_PURGE_MATCHING = 9, + DB_PURGE_STALE = 10, + DB_FETCH_ORIGINS = 11, + DB_IS_OPEN = 12, + DB_STATUS = 13, + DB_OVERRIDE_TIME = 14, + } type; + url::Origin origin; + std::vector<std::u16string> params; + explicit DBOperation(Type type); + DBOperation(Type type, url::Origin origin); + DBOperation(Type type, + url::Origin origin, + std::vector<std::u16string> params); + DBOperation(Type type, std::vector<std::u16string> params); + DBOperation(const DBOperation&); + ~DBOperation(); + bool operator==(const DBOperation& operation) const; + bool operator!=(const DBOperation& operation) const; + std::string Serialize() const; + }; + + TestDatabaseOperationReceiver(); + + ~TestDatabaseOperationReceiver(); + + // For serializing parameters to insert into `params` when creating a + // `DBOperation` struct. + static std::u16string SerializeTime(base::Time time); + static std::u16string SerializeTimeDelta(base::TimeDelta delta); + static std::u16string SerializeBool(bool b); + static std::u16string SerializeSetBehavior(SetBehavior behavior); + + bool is_finished() const { return finished_; } + + void set_expected_operations(std::queue<DBOperation> expected_operations) { + expected_operations_ = std::move(expected_operations); + } + + void WaitForOperations(); + + void GetResultCallbackBase(const DBOperation& current_operation, + GetResult* out_result, + GetResult result); + base::OnceCallback<void(GetResult)> MakeGetResultCallback( + const DBOperation& current_operation, + GetResult* out_result); + + void OperationResultCallbackBase(const DBOperation& current_operation, + OperationResult* out_result, + OperationResult result); + base::OnceCallback<void(OperationResult)> MakeOperationResultCallback( + const DBOperation& current_operation, + OperationResult* out_result); + + void IntCallbackBase(const DBOperation& current_operation, + int* out_length, + int length); + base::OnceCallback<void(int)> MakeIntCallback( + const DBOperation& current_operation, + int* out_length); + + void BoolCallbackBase(const DBOperation& current_operation, + bool* out_boolean, + bool boolean); + base::OnceCallback<void(bool)> MakeBoolCallback( + const DBOperation& current_operation, + bool* out_boolean); + + void StatusCallbackBase(const DBOperation& current_operation, + InitStatus* out_status, + InitStatus status); + base::OnceCallback<void(InitStatus)> MakeStatusCallback( + const DBOperation& current_operation, + InitStatus* out_status); + + void InfosCallbackBase(const DBOperation& current_operation, + std::vector<mojom::StorageUsageInfoPtr>* out_infos, + std::vector<mojom::StorageUsageInfoPtr> infos); + base::OnceCallback<void(std::vector<mojom::StorageUsageInfoPtr>)> + MakeInfosCallback(const DBOperation& current_operation, + std::vector<mojom::StorageUsageInfoPtr>* out_infos); + + void OnceClosureBase(const DBOperation& current_operation); + base::OnceClosure MakeOnceClosure(const DBOperation& current_operation); + + private: + bool ExpectationsMet(const DBOperation& current_operation); + void Finish(); + + base::RunLoop loop_; + bool finished_ = true; + std::queue<DBOperation> expected_operations_; +}; + +class OriginMatcherFunctionUtility { + public: + OriginMatcherFunctionUtility(); + ~OriginMatcherFunctionUtility(); + + [[nodiscard]] static OriginMatcherFunction MakeMatcherFunction( + std::vector<url::Origin> origins_to_match); + + [[nodiscard]] static OriginMatcherFunction MakeMatcherFunction( + std::vector<std::string> origin_strs_to_match); + + [[nodiscard]] size_t RegisterMatcherFunction( + std::vector<url::Origin> origins_to_match); + + [[nodiscard]] OriginMatcherFunction TakeMatcherFunctionForId(size_t id); + + [[nodiscard]] bool is_empty() const { return matcher_table_.empty(); } + + [[nodiscard]] size_t size() const { return matcher_table_.size(); } + + private: + std::vector<OriginMatcherFunction> matcher_table_; +}; + +// Wraps a bool indicating if the database is in memory only, +// for the purpose of customizing a `PrintToString()` method below, which will +// be used in the parameterized test names via +// `testing::PrintToStringParamName()`. +struct SharedStorageWrappedBool { + bool in_memory_only; +}; + +// Wraps bools indicating if the database is in memory only and if storage +// cleanup should be performed after purging, for the purpose of customizing a +// `PrintToString()` method below, which will be used in the parameterized test +// names via `testing::PrintToStringParamName()`. +struct PurgeMatchingOriginsParams { + bool in_memory_only; + bool perform_storage_cleanup; +}; + +[[nodiscard]] std::vector<SharedStorageWrappedBool> +GetSharedStorageWrappedBools(); + +// Used by `testing::PrintToStringParamName()`. +[[nodiscard]] std::string PrintToString(const SharedStorageWrappedBool& b); + +[[nodiscard]] std::vector<PurgeMatchingOriginsParams> +GetPurgeMatchingOriginsParams(); + +// Used by testing::PrintToStringParamName(). +[[nodiscard]] std::string PrintToString(const PurgeMatchingOriginsParams& p); + +// Verify that the up-to-date SQL Shared Storage database has the expected +// tables and columns. Functional tests only check whether the things which +// should be there are, but do not check if extraneous items are +// present. Any extraneous items have the potential to interact +// negatively with future schema changes. +void VerifySharedStorageTablesAndColumns(sql::Database& db); + +[[nodiscard]] bool GetTestDataSharedStorageDir(base::FilePath* dir); + +[[nodiscard]] bool CreateDatabaseFromSQL(const base::FilePath& db_path, + const char* ascii_path); + +} // namespace storage + +#endif // COMPONENTS_SERVICES_STORAGE_SHARED_STORAGE_SHARED_STORAGE_TEST_UTILS_H_ diff --git a/chromium/components/services/storage/storage_service_impl.cc b/chromium/components/services/storage/storage_service_impl.cc index d119afafeb4..d848fb3c480 100644 --- a/chromium/components/services/storage/storage_service_impl.cc +++ b/chromium/components/services/storage/storage_service_impl.cc @@ -25,7 +25,7 @@ namespace { // We don't use out-of-process Storage Service on Android, so we can avoid // pulling all the related code (including Directory mojom) into the build. -#if !defined(OS_ANDROID) +#if !BUILDFLAG(IS_ANDROID) // The name under which we register our own sandboxed VFS instance when running // out-of-process. constexpr char kVfsName[] = "storage_service"; @@ -61,7 +61,7 @@ void StorageServiceImpl::EnableAggressiveDomStorageFlushing() { StorageAreaImpl::EnableAggressiveCommitDelay(); } -#if !defined(OS_ANDROID) +#if !BUILDFLAG(IS_ANDROID) void StorageServiceImpl::SetDataDirectory( const base::FilePath& path, mojo::PendingRemote<mojom::Directory> directory) { @@ -91,7 +91,7 @@ void StorageServiceImpl::SetDataDirectory( kVfsName, std::make_unique<SandboxedVfsDelegate>(CreateFilesystemProxy()), /*make_default=*/true); } -#endif // !defined(OS_ANDROID) +#endif // !BUILDFLAG(IS_ANDROID) void StorageServiceImpl::BindPartition( const absl::optional<base::FilePath>& path, @@ -132,7 +132,7 @@ void StorageServiceImpl::RemovePartition(PartitionImpl* partition) { partitions_.erase(iter); } -#if !defined(OS_ANDROID) +#if !BUILDFLAG(IS_ANDROID) void StorageServiceImpl::BindDataDirectoryReceiver( mojo::PendingReceiver<mojom::Directory> receiver) { DCHECK(remote_data_directory_.is_bound()); diff --git a/chromium/components/services/storage/storage_service_impl.h b/chromium/components/services/storage/storage_service_impl.h index 76838adc62c..e02fc94c2f8 100644 --- a/chromium/components/services/storage/storage_service_impl.h +++ b/chromium/components/services/storage/storage_service_impl.h @@ -45,7 +45,7 @@ class StorageServiceImpl : public mojom::StorageService { // mojom::StorageService implementation: void EnableAggressiveDomStorageFlushing() override; -#if !defined(OS_ANDROID) +#if !BUILDFLAG(IS_ANDROID) void SetDataDirectory( const base::FilePath& path, mojo::PendingRemote<mojom::Directory> directory) override; @@ -60,7 +60,7 @@ class StorageServiceImpl : public mojom::StorageService { // Removes a partition from the set of tracked partitions. void RemovePartition(PartitionImpl* partition); -#if !defined(OS_ANDROID) +#if !BUILDFLAG(IS_ANDROID) // Binds a Directory receiver to the same remote implementation to which // |remote_data_directory_| is bound. It is invalid to call this when // |remote_data_directory_| is unbound. @@ -71,7 +71,7 @@ class StorageServiceImpl : public mojom::StorageService { const mojo::Receiver<mojom::StorageService> receiver_; const scoped_refptr<base::SequencedTaskRunner> io_task_runner_; -#if !defined(OS_ANDROID) +#if !BUILDFLAG(IS_ANDROID) // If bound, the service will assume it should not perform certain filesystem // operations directly and will instead go through this interface. base::FilePath remote_data_directory_path_; diff --git a/chromium/components/services/unzip/BUILD.gn b/chromium/components/services/unzip/BUILD.gn index d2648aecb43..a21e47cabef 100644 --- a/chromium/components/services/unzip/BUILD.gn +++ b/chromium/components/services/unzip/BUILD.gn @@ -11,6 +11,7 @@ source_set("lib") { deps = [ "//base", "//mojo/public/cpp/bindings", + "//third_party/ced", "//third_party/zlib/google:zip", ] @@ -45,6 +46,21 @@ bundle_data("unit_tests_bundle_data") { visibility = [ ":unit_tests" ] testonly = true sources = [ + "//components/test/data/unzip_service/SJIS 00.zip", + "//components/test/data/unzip_service/SJIS 01.zip", + "//components/test/data/unzip_service/SJIS 02.zip", + "//components/test/data/unzip_service/SJIS 03.zip", + "//components/test/data/unzip_service/SJIS 04.zip", + "//components/test/data/unzip_service/SJIS 05.zip", + "//components/test/data/unzip_service/SJIS 06.zip", + "//components/test/data/unzip_service/SJIS 07.zip", + "//components/test/data/unzip_service/SJIS 08.zip", + "//components/test/data/unzip_service/SJIS 09.zip", + "//components/test/data/unzip_service/SJIS 10.zip", + "//components/test/data/unzip_service/SJIS 11.zip", + "//components/test/data/unzip_service/SJIS 12.zip", + "//components/test/data/unzip_service/SJIS 13.zip", + "//components/test/data/unzip_service/UTF8 (Bug 903664).zip", "//components/test/data/unzip_service/bad_archive.zip", "//components/test/data/unzip_service/good_archive.zip", ] diff --git a/chromium/components/services/unzip/DEPS b/chromium/components/services/unzip/DEPS index c182c3ca877..c84c5757b14 100644 --- a/chromium/components/services/unzip/DEPS +++ b/chromium/components/services/unzip/DEPS @@ -1,5 +1,6 @@ include_rules = [ "+components/services/filesystem", "+mojo/public", + "+third_party/ced", "+third_party/zlib/google", ] diff --git a/chromium/components/services/unzip/OWNERS b/chromium/components/services/unzip/OWNERS index ec81839d0ad..76d860e1f41 100644 --- a/chromium/components/services/unzip/OWNERS +++ b/chromium/components/services/unzip/OWNERS @@ -1,2 +1,5 @@ +adanilo@chromium.org +fdegros@chromium.org +noel@chromium.org sorin@chromium.org waffles@chromium.org diff --git a/chromium/components/services/unzip/public/cpp/unzip.cc b/chromium/components/services/unzip/public/cpp/unzip.cc index b2b929ca7c1..d487f1f2393 100644 --- a/chromium/components/services/unzip/public/cpp/unzip.cc +++ b/chromium/components/services/unzip/public/cpp/unzip.cc @@ -11,12 +11,15 @@ #include "base/callback.h" #include "base/files/file.h" #include "base/files/file_path.h" +#include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_refptr.h" +#include "base/metrics/histogram_functions.h" #include "base/task/post_task.h" #include "base/task/sequenced_task_runner.h" #include "base/task/thread_pool.h" #include "base/threading/sequenced_task_runner_handle.h" +#include "base/time/time.h" #include "components/services/filesystem/directory_impl.h" #include "components/services/filesystem/lock_table.h" #include "components/services/unzip/public/mojom/unzipper.mojom.h" @@ -25,9 +28,12 @@ #include "mojo/public/cpp/bindings/self_owned_receiver.h" namespace unzip { - namespace { +std::string Redact(const base::FilePath& path) { + return LOG_IS_ON(INFO) ? "'" + path.AsUTF8Unsafe() + "'" : "(redacted)"; +} + class UnzipFilter : public unzip::mojom::UnzipFilter { public: UnzipFilter(mojo::PendingReceiver<unzip::mojom::UnzipFilter> receiver, @@ -72,6 +78,8 @@ class UnzipParams : public base::RefCounted<UnzipParams> { callback_task_runner_->PostTask( FROM_HERE, base::BindOnce(std::move(callback_), result)); } + + unzipper_.reset(); } void set_unzip_filter(std::unique_ptr<UnzipFilter> filter) { @@ -94,12 +102,52 @@ class UnzipParams : public base::RefCounted<UnzipParams> { scoped_refptr<base::SequencedTaskRunner> background_task_runner_keep_alive_; }; -void UnzipDone(scoped_refptr<UnzipParams> params, bool success) { - params->InvokeCallback(success); - params->unzipper().reset(); -} +class DetectEncodingParams : public base::RefCounted<DetectEncodingParams> { + public: + DetectEncodingParams( + mojo::PendingRemote<mojom::Unzipper> unzipper, + DetectEncodingCallback callback, + scoped_refptr<base::SequencedTaskRunner> callback_task_runner, + scoped_refptr<base::SequencedTaskRunner> background_task_runner) + : unzipper_(std::move(unzipper)), + callback_(std::move(callback)), + callback_task_runner_(std::move(callback_task_runner)), + background_task_runner_(std::move(background_task_runner)) {} + + DetectEncodingParams(const DetectEncodingParams&) = delete; + DetectEncodingParams& operator=(const DetectEncodingParams&) = delete; + + mojo::Remote<mojom::Unzipper>& unzipper() { return unzipper_; } + + void InvokeCallback(int encoding) { + if (callback_) { + base::UmaHistogramEnumeration("Unzipper.DetectEncoding.Result", + static_cast<Encoding>(encoding), + Encoding::NUM_ENCODINGS); + base::UmaHistogramTimes("Unzipper.DetectEncoding.Time", + base::TimeTicks::Now() - start_time_); + callback_task_runner_->PostTask( + FROM_HERE, base::BindOnce(std::move(callback_), + static_cast<Encoding>(encoding))); + } + + unzipper_.reset(); + } + + private: + friend class base::RefCounted<DetectEncodingParams>; + + ~DetectEncodingParams() = default; + + // The Remote is stored so it does not get deleted before the callback runs. + mojo::Remote<mojom::Unzipper> unzipper_; + DetectEncodingCallback callback_; + scoped_refptr<base::SequencedTaskRunner> callback_task_runner_; + scoped_refptr<base::SequencedTaskRunner> background_task_runner_; + const base::TimeTicks start_time_ = base::TimeTicks::Now(); +}; -void DoUnzipWithFilter( +void DoUnzip( mojo::PendingRemote<mojom::Unzipper> unzipper, const base::FilePath& zip_path, const base::FilePath& output_dir, @@ -129,22 +177,50 @@ void DoUnzipWithFilter( background_task_runner_keep_alive); unzip_params->unzipper().set_disconnect_handler( - base::BindOnce(&UnzipDone, unzip_params, false)); - - if (filter_callback.is_null()) { - unzip_params->unzipper()->Unzip(std::move(zip_file), - std::move(directory_remote), - base::BindOnce(&UnzipDone, unzip_params)); - return; - } + base::BindOnce(&UnzipParams::InvokeCallback, unzip_params, false)); mojo::PendingRemote<unzip::mojom::UnzipFilter> unzip_filter_remote; - unzip_params->set_unzip_filter(std::make_unique<UnzipFilter>( - unzip_filter_remote.InitWithNewPipeAndPassReceiver(), filter_callback)); + if (filter_callback) { + unzip_params->set_unzip_filter(std::make_unique<UnzipFilter>( + unzip_filter_remote.InitWithNewPipeAndPassReceiver(), + std::move(filter_callback))); + } - unzip_params->unzipper()->UnzipWithFilter( + unzip_params->unzipper()->Unzip( std::move(zip_file), std::move(directory_remote), - std::move(unzip_filter_remote), base::BindOnce(&UnzipDone, unzip_params)); + std::move(unzip_filter_remote), + base::BindOnce(&UnzipParams::InvokeCallback, unzip_params)); +} + +void DoDetectEncoding( + mojo::PendingRemote<mojom::Unzipper> unzipper, + const base::FilePath& zip_path, + DetectEncodingCallback result_callback, + scoped_refptr<base::SequencedTaskRunner> callback_task_runner, + scoped_refptr<base::SequencedTaskRunner> background_task_runner) { + base::File zip_file(zip_path, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!zip_file.IsValid()) { + LOG(ERROR) << "Cannot open ZIP archive " << Redact(zip_path) << ": " + << base::File::ErrorToString(zip_file.error_details()); + callback_task_runner->PostTask( + FROM_HERE, + base::BindOnce(std::move(result_callback), UNKNOWN_ENCODING)); + return; + } + + // |result_callback| is shared between the connection error handler and the + // DetectEncoding call using a refcounted DetectEncodingParams object that + // owns |result_callback|. + auto params = base::MakeRefCounted<DetectEncodingParams>( + std::move(unzipper), std::move(result_callback), + std::move(callback_task_runner), std::move(background_task_runner)); + + params->unzipper().set_disconnect_handler(base::BindOnce( + &DetectEncodingParams::InvokeCallback, params, UNKNOWN_ENCODING)); + + params->unzipper()->DetectEncoding( + std::move(zip_file), + base::BindOnce(&DetectEncodingParams::InvokeCallback, params)); } } // namespace @@ -170,10 +246,23 @@ void UnzipWithFilter(mojo::PendingRemote<mojom::Unzipper> unzipper, base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}); background_task_runner->PostTask( FROM_HERE, - base::BindOnce(&DoUnzipWithFilter, std::move(unzipper), zip_path, - output_dir, base::SequencedTaskRunnerHandle::Get(), - filter_callback, std::move(result_callback), - background_task_runner)); + base::BindOnce(&DoUnzip, std::move(unzipper), zip_path, output_dir, + base::SequencedTaskRunnerHandle::Get(), filter_callback, + std::move(result_callback), background_task_runner)); +} + +void DetectEncoding(mojo::PendingRemote<mojom::Unzipper> unzipper, + const base::FilePath& zip_path, + DetectEncodingCallback result_callback) { + scoped_refptr<base::SequencedTaskRunner> background_task_runner = + base::ThreadPool::CreateSequencedTaskRunner( + {base::TaskPriority::USER_VISIBLE, base::MayBlock(), + base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}); + background_task_runner->PostTask( + FROM_HERE, base::BindOnce(&DoDetectEncoding, std::move(unzipper), + zip_path, std::move(result_callback), + base::SequencedTaskRunnerHandle::Get(), + background_task_runner)); } } // namespace unzip diff --git a/chromium/components/services/unzip/public/cpp/unzip.h b/chromium/components/services/unzip/public/cpp/unzip.h index e62a4ac524d..1f4bbb0f203 100644 --- a/chromium/components/services/unzip/public/cpp/unzip.h +++ b/chromium/components/services/unzip/public/cpp/unzip.h @@ -8,6 +8,7 @@ #include "base/callback_forward.h" #include "components/services/unzip/public/mojom/unzipper.mojom.h" #include "mojo/public/cpp/bindings/pending_remote.h" +#include "third_party/ced/src/util/encodings/encodings.h" namespace base { class FilePath; @@ -32,6 +33,11 @@ void UnzipWithFilter(mojo::PendingRemote<mojom::Unzipper> unzipper, UnzipFilterCallback filter_callback, UnzipCallback result_callback); +using DetectEncodingCallback = base::OnceCallback<void(Encoding)>; +void DetectEncoding(mojo::PendingRemote<mojom::Unzipper> unzipper, + const base::FilePath& zip_file, + DetectEncodingCallback result_callback); + } // namespace unzip #endif // COMPONENTS_SERVICES_UNZIP_PUBLIC_CPP_UNZIP_H_ diff --git a/chromium/components/services/unzip/public/cpp/unzip_unittest.cc b/chromium/components/services/unzip/public/cpp/unzip_unittest.cc index ccb9053207a..658301c4b44 100644 --- a/chromium/components/services/unzip/public/cpp/unzip_unittest.cc +++ b/chromium/components/services/unzip/public/cpp/unzip_unittest.cc @@ -4,33 +4,33 @@ #include "components/services/unzip/public/cpp/unzip.h" +#include <utility> + #include "base/base_paths.h" -#include "base/bind.h" #include "base/files/file_enumerator.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/path_service.h" #include "base/run_loop.h" +#include "base/test/bind.h" #include "base/test/task_environment.h" #include "components/services/unzip/unzipper_impl.h" #include "mojo/public/cpp/bindings/receiver_set.h" #include "testing/gtest/include/gtest/gtest.h" namespace unzip { - namespace { -// Note: this method has to return void for the ASSERTION_* to compile. -void GetArchivePath(const base::FilePath::StringPieceType& archive_name, - base::FilePath* path) { - ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, path)); - *path = path->Append(FILE_PATH_LITERAL("components")) - .Append(FILE_PATH_LITERAL("test")) - .Append(FILE_PATH_LITERAL("data")) - .Append(FILE_PATH_LITERAL("unzip_service")) - .Append(archive_name); - ASSERT_TRUE(base::PathExists(*path)); +base::FilePath GetArchivePath( + const base::FilePath::StringPieceType archive_name) { + base::FilePath path; + EXPECT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &path)); + return path.Append(FILE_PATH_LITERAL("components")) + .Append(FILE_PATH_LITERAL("test")) + .Append(FILE_PATH_LITERAL("data")) + .Append(FILE_PATH_LITERAL("unzip_service")) + .Append(archive_name); } // Sets the number of files under |dir| in |file_count| and if @@ -63,37 +63,44 @@ class UnzipTest : public testing::Test { ~UnzipTest() override = default; // Unzips |zip_file| into |output_dir| and returns true if the unzip was - // successful. + // successful. Only extract files for which |filter_callback| returns true, if + // it is provided. bool DoUnzip(const base::FilePath& zip_file, - const base::FilePath& output_dir) { - return DoUnzipWithFilter(zip_file, output_dir, UnzipFilterCallback()); + const base::FilePath& output_dir, + UnzipFilterCallback filter_callback = {}) { + mojo::PendingRemote<mojom::Unzipper> unzipper; + receivers_.Add(&unzipper_, unzipper.InitWithNewPipeAndPassReceiver()); + + base::RunLoop run_loop; + bool result = false; + + UnzipCallback result_callback = + base::BindLambdaForTesting([&](const bool success) { + result = success; + run_loop.QuitClosure().Run(); + }); + + UnzipWithFilter(std::move(unzipper), zip_file, output_dir, + std::move(filter_callback), std::move(result_callback)); + + run_loop.Run(); + return result; } - // Same as DoUnzip() but only extract files for which |filter_callback| - // returns true. - bool DoUnzipWithFilter(const base::FilePath& zip_file, - const base::FilePath& output_dir, - UnzipFilterCallback filter_callback) { + Encoding DoDetectEncoding(const base::FilePath& zip_file) { mojo::PendingRemote<mojom::Unzipper> unzipper; receivers_.Add(&unzipper_, unzipper.InitWithNewPipeAndPassReceiver()); base::RunLoop run_loop; - bool result = false; + Encoding result = UNKNOWN_ENCODING; - UnzipCallback result_callback = base::BindOnce( - [](base::OnceClosure quit_closure, bool* out_result, bool result) { - *out_result = result; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &result); - - if (filter_callback) { - UnzipWithFilter(std::move(unzipper), zip_file, output_dir, - std::move(filter_callback), std::move(result_callback)); - } else { - Unzip(std::move(unzipper), zip_file, output_dir, - std::move(result_callback)); - } + DetectEncodingCallback result_callback = + base::BindLambdaForTesting([&](const Encoding encoding) { + result = encoding; + run_loop.QuitClosure().Run(); + }); + + DetectEncoding(std::move(unzipper), zip_file, std::move(result_callback)); run_loop.Run(); return result; } @@ -114,9 +121,8 @@ class UnzipTest : public testing::Test { }; TEST_F(UnzipTest, UnzipBadArchive) { - base::FilePath bad_archive; - GetArchivePath(FILE_PATH_LITERAL("bad_archive.zip"), &bad_archive); - EXPECT_FALSE(DoUnzip(bad_archive, unzip_dir_)); + EXPECT_FALSE(DoUnzip(GetArchivePath(FILE_PATH_LITERAL("bad_archive.zip")), + unzip_dir_)); // No files should have been extracted. int64_t file_count = -1; @@ -125,9 +131,8 @@ TEST_F(UnzipTest, UnzipBadArchive) { } TEST_F(UnzipTest, UnzipGoodArchive) { - base::FilePath archive; - GetArchivePath(FILE_PATH_LITERAL("good_archive.zip"), &archive); - EXPECT_TRUE(DoUnzip(archive, unzip_dir_)); + EXPECT_TRUE(DoUnzip(GetArchivePath(FILE_PATH_LITERAL("good_archive.zip")), + unzip_dir_)); // Sanity check that the right number of files have been extracted and that // they are not empty. @@ -139,13 +144,11 @@ TEST_F(UnzipTest, UnzipGoodArchive) { } TEST_F(UnzipTest, UnzipWithFilter) { - base::FilePath archive; - GetArchivePath(FILE_PATH_LITERAL("good_archive.zip"), &archive); - EXPECT_TRUE(DoUnzipWithFilter( - archive, unzip_dir_, - base::BindRepeating([](const base::FilePath& path) -> bool { - return path.MatchesExtension(FILE_PATH_LITERAL(".txt")); - }))); + EXPECT_TRUE(DoUnzip(GetArchivePath(FILE_PATH_LITERAL("good_archive.zip")), + unzip_dir_, + base::BindRepeating([](const base::FilePath& path) { + return path.MatchesExtension(FILE_PATH_LITERAL(".txt")); + }))); // It should only have kept the 2 text files from the archive. int64_t file_count = -1; @@ -155,5 +158,51 @@ TEST_F(UnzipTest, UnzipWithFilter) { EXPECT_FALSE(some_files_empty); } +TEST_F(UnzipTest, DetectEncodingAbsentArchive) { + EXPECT_EQ(UNKNOWN_ENCODING, DoDetectEncoding(GetArchivePath( + FILE_PATH_LITERAL("absent_archive.zip")))); +} + +TEST_F(UnzipTest, DetectEncodingBadArchive) { + EXPECT_EQ( + UNKNOWN_ENCODING, + DoDetectEncoding(GetArchivePath(FILE_PATH_LITERAL("bad_archive.zip")))); +} + +TEST_F(UnzipTest, DetectEncodingAscii) { + EXPECT_EQ( + Encoding::ASCII_7BIT, + DoDetectEncoding(GetArchivePath(FILE_PATH_LITERAL("good_archive.zip")))); +} + +// See https://crbug.com/903664 +TEST_F(UnzipTest, DetectEncodingUtf8) { + EXPECT_EQ(Encoding::UTF8, DoDetectEncoding(GetArchivePath( + FILE_PATH_LITERAL("UTF8 (Bug 903664).zip")))); +} + +// See https://crbug.com/1287893 +TEST_F(UnzipTest, DetectEncodingSjis) { + for (const base::FilePath::StringPieceType name : { + FILE_PATH_LITERAL("SJIS 00.zip"), + FILE_PATH_LITERAL("SJIS 01.zip"), + FILE_PATH_LITERAL("SJIS 02.zip"), + FILE_PATH_LITERAL("SJIS 03.zip"), + FILE_PATH_LITERAL("SJIS 04.zip"), + FILE_PATH_LITERAL("SJIS 05.zip"), + FILE_PATH_LITERAL("SJIS 06.zip"), + FILE_PATH_LITERAL("SJIS 07.zip"), + FILE_PATH_LITERAL("SJIS 08.zip"), + FILE_PATH_LITERAL("SJIS 09.zip"), + FILE_PATH_LITERAL("SJIS 10.zip"), + FILE_PATH_LITERAL("SJIS 11.zip"), + FILE_PATH_LITERAL("SJIS 12.zip"), + FILE_PATH_LITERAL("SJIS 13.zip"), + }) { + EXPECT_EQ(Encoding::JAPANESE_SHIFT_JIS, + DoDetectEncoding(GetArchivePath(name))); + } +} + } // namespace } // namespace unzip diff --git a/chromium/components/services/unzip/public/mojom/unzipper.mojom b/chromium/components/services/unzip/public/mojom/unzipper.mojom index 00273934dd9..a16f891cd85 100644 --- a/chromium/components/services/unzip/public/mojom/unzipper.mojom +++ b/chromium/components/services/unzip/public/mojom/unzipper.mojom @@ -20,14 +20,17 @@ interface UnzipFilter { interface Unzipper { // Unzip |zip_file| into |output_dir|. // Returns true on success, false otherwise. - Unzip(mojo_base.mojom.ReadOnlyFile zip_file, - pending_remote<filesystem.mojom.Directory> output_dir) - => (bool result); - - // Same as |unzip| but only includes the files for which |filter| returns - // true. Note that this incurs one IPC for each file of the archive. - UnzipWithFilter( + // If provided, |filter| is called for each entry of the archive (which incurs + // one IPC for each entry) and only the entries for which it returns true are + // extracted. + Unzip( mojo_base.mojom.ReadOnlyFile zip_file, pending_remote<filesystem.mojom.Directory> output_dir, - pending_remote<UnzipFilter> filter) => (bool result); + pending_remote<UnzipFilter>? filter) => (bool result); + + // Detects the encoding of filenames stored in the ZIP archive. + // Returns an Encoding as defined in + // third_party/ced/src/util/encodings/encodings.pb.h + // or UNKNOWN_ENCODING in case of error. + DetectEncoding(mojo_base.mojom.ReadOnlyFile zip_file) => (int32 encoding); }; diff --git a/chromium/components/services/unzip/unzipper_impl.cc b/chromium/components/services/unzip/unzipper_impl.cc index b710ab8b2bf..10e1a68bfef 100644 --- a/chromium/components/services/unzip/unzipper_impl.cc +++ b/chromium/components/services/unzip/unzipper_impl.cc @@ -9,10 +9,13 @@ #include "base/bind.h" #include "base/compiler_specific.h" +#include "base/files/file.h" +#include "base/logging.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "components/services/filesystem/public/mojom/directory.mojom.h" #include "mojo/public/cpp/bindings/remote.h" +#include "third_party/ced/src/compact_enc_det/compact_enc_det.h" #include "third_party/zlib/google/zip.h" #include "third_party/zlib/google/zip_reader.h" @@ -20,24 +23,8 @@ namespace unzip { namespace { -// A writer delegate that reports errors instead of writing. -class DudWriterDelegate : public zip::WriterDelegate { - public: - DudWriterDelegate() {} - - DudWriterDelegate(const DudWriterDelegate&) = delete; - DudWriterDelegate& operator=(const DudWriterDelegate&) = delete; - - ~DudWriterDelegate() override {} - - // WriterDelegate methods: - bool PrepareOutput() override { return false; } - bool WriteBytes(const char* data, int num_bytes) override { return false; } - void SetTimeModified(const base::Time& time) override {} -}; - std::string PathToMojoString(const base::FilePath& path) { -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) return base::WideToUTF8(path.value()); #else return path.value(); @@ -64,7 +51,7 @@ std::unique_ptr<zip::WriterDelegate> MakeFileWriterDelegateNoParent( filesystem::mojom::kFlagWriteAttributes, &err, file.get()) || err != base::File::Error::FILE_OK) { - return std::make_unique<DudWriterDelegate>(); + return nullptr; } return std::make_unique<zip::FileWriterDelegate>(std::move(file)); } @@ -80,22 +67,51 @@ std::unique_ptr<zip::WriterDelegate> MakeFileWriterDelegate( parent.BindNewPipeAndPassReceiver(), filesystem::mojom::kFlagOpenAlways, &err) || err != base::File::Error::FILE_OK) { - return std::make_unique<DudWriterDelegate>(); + return nullptr; } return MakeFileWriterDelegateNoParent(parent.get(), path.BaseName()); } -bool FilterNoFiles(const base::FilePath& unused) { - return true; -} - -bool FilterWithFilterRemote(mojom::UnzipFilter* filter, - const base::FilePath& path) { +bool Filter(const mojo::Remote<mojom::UnzipFilter>& filter, + const base::FilePath& path) { bool result = false; filter->ShouldUnzipFile(path, &result); return result; } +// Reads the given ZIP archive, and returns all the filenames concatenated +// together in one long string capped at ~100KB, without any separator, and in +// the encoding used by the ZIP archive itself. Returns an empty string if the +// ZIP cannot be read. +std::string GetRawFileNamesFromZip(const base::File& zip_file) { + std::string result; + + // Open ZIP archive for reading. + zip::ZipReader reader; + if (!reader.OpenFromPlatformFile(zip_file.GetPlatformFile())) { + LOG(ERROR) << "Cannot decode ZIP archive"; + return result; + } + + // Reserve a ~100KB buffer. + result.reserve(100000); + + // Iterate over file entries of the ZIP archive. + while (const zip::ZipReader::Entry* const entry = reader.Next()) { + const std::string& path = entry->path_in_original_encoding; + + // Stop if we have enough data in |result|. + if (path.size() > (result.capacity() - result.size())) + break; + + // Accumulate data in |result|. + result += path; + } + + LOG_IF(ERROR, result.empty()) << "Cannot extract filenames from ZIP archive"; + return result; +} + } // namespace UnzipperImpl::UnzipperImpl() = default; @@ -108,38 +124,53 @@ UnzipperImpl::~UnzipperImpl() = default; void UnzipperImpl::Unzip( base::File zip_file, mojo::PendingRemote<filesystem::mojom::Directory> output_dir_remote, + mojo::PendingRemote<mojom::UnzipFilter> filter_remote, UnzipCallback callback) { DCHECK(zip_file.IsValid()); mojo::Remote<filesystem::mojom::Directory> output_dir( std::move(output_dir_remote)); - std::move(callback).Run(zip::UnzipWithFilterAndWriters( - zip_file.GetPlatformFile(), - base::BindRepeating(&MakeFileWriterDelegate, output_dir.get()), - base::BindRepeating(&CreateDirectory, output_dir.get()), - base::BindRepeating(&FilterNoFiles), /*log_skipped_files=*/false)); + zip::FilterCallback filter_cb; + if (filter_remote) { + filter_cb = base::BindRepeating( + &Filter, mojo::Remote<mojom::UnzipFilter>(std::move(filter_remote))); + } + + std::move(callback).Run( + zip::Unzip(zip_file.GetPlatformFile(), + base::BindRepeating(&MakeFileWriterDelegate, output_dir.get()), + base::BindRepeating(&CreateDirectory, output_dir.get()), + {.filter = std::move(filter_cb)})); } -void UnzipperImpl::UnzipWithFilter( - base::File zip_file, - mojo::PendingRemote<filesystem::mojom::Directory> output_dir_remote, - mojo::PendingRemote<mojom::UnzipFilter> filter_remote, - UnzipCallback callback) { +void UnzipperImpl::DetectEncoding(base::File zip_file, + DetectEncodingCallback callback) { DCHECK(zip_file.IsValid()); - mojo::Remote<filesystem::mojom::Directory> output_dir( - std::move(output_dir_remote)); - mojo::Remote<mojom::UnzipFilter> filter(std::move(filter_remote)); - - // Note that we pass a pointer to |filter| below, as it is a repeating - // callback and transferring its value would cause the callback to fail when - // called more than once. It is safe to pass a pointer as - // UnzipWithFilterAndWriters is synchronous, so |filter| won't be used when - // the method returns. - std::move(callback).Run(zip::UnzipWithFilterAndWriters( - zip_file.GetPlatformFile(), - base::BindRepeating(&MakeFileWriterDelegate, output_dir.get()), - base::BindRepeating(&CreateDirectory, output_dir.get()), - base::BindRepeating(&FilterWithFilterRemote, filter.get()), - /*log_skipped_files=*/false)); + + // Accumulate raw filenames. + const std::string all_names = GetRawFileNamesFromZip(zip_file); + if (all_names.empty()) { + std::move(callback).Run(UNKNOWN_ENCODING); + return; + } + + // Detect encoding. + int consumed_bytes = 0; + bool is_reliable = false; + const Encoding encoding = CompactEncDet::DetectEncoding( + all_names.data(), all_names.size(), nullptr, nullptr, nullptr, + UNKNOWN_ENCODING, UNKNOWN_LANGUAGE, + CompactEncDet::QUERY_CORPUS, // Plain text + true, // Exclude 7-bit encodings + &consumed_bytes, &is_reliable); + + VLOG(1) << "Detected encoding: " << MimeEncodingName(encoding) << " (" + << encoding << "), reliable: " << is_reliable + << ", consumed bytes: " << consumed_bytes; + + LOG_IF(ERROR, encoding == UNKNOWN_ENCODING) + << "Cannot detect encoding of filenames in ZIP archive"; + + std::move(callback).Run(encoding); } } // namespace unzip diff --git a/chromium/components/services/unzip/unzipper_impl.h b/chromium/components/services/unzip/unzipper_impl.h index 330b716f910..6503523312a 100644 --- a/chromium/components/services/unzip/unzipper_impl.h +++ b/chromium/components/services/unzip/unzipper_impl.h @@ -32,13 +32,11 @@ class UnzipperImpl : public mojom::Unzipper { void Unzip( base::File zip_file, mojo::PendingRemote<filesystem::mojom::Directory> output_dir_remote, + mojo::PendingRemote<mojom::UnzipFilter> filter_remote, UnzipCallback callback) override; - void UnzipWithFilter( - base::File zip_file, - mojo::PendingRemote<filesystem::mojom::Directory> output_dir_remote, - mojo::PendingRemote<mojom::UnzipFilter> filter_remote, - UnzipWithFilterCallback callback) override; + void DetectEncoding(base::File zip_file, + DetectEncodingCallback callback) override; mojo::Receiver<mojom::Unzipper> receiver_{this}; }; |