| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/web_applications/web_app_sync_bridge.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #include "base/barrier_closure.h" |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/notreached.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/test_future.h" |
| #include "chrome/browser/web_applications/mojom/user_display_mode.mojom-shared.h" |
| #include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h" |
| #include "chrome/browser/web_applications/os_integration/os_integration_manager.h" |
| #include "chrome/browser/web_applications/proto/web_app_install_state.pb.h" |
| #include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h" |
| #include "chrome/browser/web_applications/test/fake_web_app_database_factory.h" |
| #include "chrome/browser/web_applications/test/fake_web_app_provider.h" |
| #include "chrome/browser/web_applications/test/web_app_install_test_utils.h" |
| #include "chrome/browser/web_applications/test/web_app_sync_test_utils.h" |
| #include "chrome/browser/web_applications/test/web_app_test.h" |
| #include "chrome/browser/web_applications/test/web_app_test_observers.h" |
| #include "chrome/browser/web_applications/test/web_app_test_utils.h" |
| #include "chrome/browser/web_applications/web_app.h" |
| #include "chrome/browser/web_applications/web_app_command_scheduler.h" |
| #include "chrome/browser/web_applications/web_app_constants.h" |
| #include "chrome/browser/web_applications/web_app_helpers.h" |
| #include "chrome/browser/web_applications/web_app_install_manager.h" |
| #include "chrome/browser/web_applications/web_app_registry_update.h" |
| #include "chrome/browser/web_applications/web_app_utils.h" |
| #include "components/sync/model/data_batch.h" |
| #include "components/sync/model/entity_change.h" |
| #include "components/sync/protocol/entity_data.h" |
| #include "components/sync/protocol/web_app_specifics.pb.h" |
| #include "components/sync/test/mock_data_type_local_change_processor.h" |
| #include "components/webapps/browser/install_result_code.h" |
| #include "components/webapps/browser/installable/installable_metrics.h" |
| #include "components/webapps/browser/uninstall_result_code.h" |
| #include "components/webapps/common/web_app_id.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| |
| namespace web_app { |
| |
| namespace { |
| |
| using testing::_; |
| |
| using AppsList = std::vector<std::unique_ptr<WebApp>>; |
| |
| using mojom::UserDisplayMode; |
| using sync_pb::WebAppSpecifics_UserDisplayMode; |
| using sync_pb::WebAppSpecifics_UserDisplayMode_BROWSER; |
| using sync_pb::WebAppSpecifics_UserDisplayMode_STANDALONE; |
| using sync_pb::WebAppSpecifics_UserDisplayMode_TABBED; |
| using sync_pb::WebAppSpecifics_UserDisplayMode_UNSPECIFIED; |
| |
| void RemoveWebAppFromAppsList(AppsList* apps_list, |
| const webapps::AppId& app_id) { |
| std::erase_if(*apps_list, [app_id](const std::unique_ptr<WebApp>& app) { |
| return app->app_id() == app_id; |
| }); |
| } |
| |
| bool IsSyncDataEqualIfApplied(const WebApp& expected_app, |
| std::unique_ptr<WebApp> app_to_apply_sync_data, |
| const syncer::EntityData& entity_data) { |
| if (!entity_data.specifics.has_web_app()) |
| return false; |
| |
| auto& web_app_specifics = entity_data.specifics.web_app(); |
| const GURL sync_start_url(web_app_specifics.start_url()); |
| webapps::ManifestId sync_manifest_id = GenerateManifestId( |
| web_app_specifics.relative_manifest_id(), sync_start_url); |
| if (expected_app.app_id() != GenerateAppIdFromManifestId(sync_manifest_id)) { |
| return false; |
| } |
| |
| // ApplySyncDataToApp does not set the start_url or manifest_id. |
| app_to_apply_sync_data->SetStartUrl(sync_start_url); |
| app_to_apply_sync_data->SetManifestId(sync_manifest_id); |
| |
| // ApplySyncDataToApp enforces kSync source on |app_to_apply_sync_data|. |
| ApplySyncDataToApp(entity_data.specifics.web_app(), |
| app_to_apply_sync_data.get()); |
| app_to_apply_sync_data->SetName(entity_data.name); |
| |
| // Remove OS integration state, as this is never applied with the sync system. |
| WebApp expected_app_copy = WebApp(expected_app); |
| expected_app_copy.SetCurrentOsIntegrationStates( |
| proto::WebAppOsIntegrationState()); |
| |
| WebApp app_applied_sync_data_copy = WebApp(*app_to_apply_sync_data); |
| app_applied_sync_data_copy.SetCurrentOsIntegrationStates( |
| proto::WebAppOsIntegrationState()); |
| |
| return expected_app_copy == app_applied_sync_data_copy; |
| } |
| |
| bool IsSyncDataEqual(const WebApp& expected_app, |
| const syncer::EntityData& entity_data) { |
| auto app_to_apply_sync_data = std::make_unique<WebApp>(expected_app.app_id()); |
| return IsSyncDataEqualIfApplied( |
| expected_app, std::move(app_to_apply_sync_data), entity_data); |
| } |
| |
| bool RegistryContainsSyncDataBatchChanges( |
| const Registry& registry, |
| std::unique_ptr<syncer::DataBatch> data_batch) { |
| if (!data_batch || !data_batch->HasNext()) |
| return registry.empty(); |
| |
| while (data_batch->HasNext()) { |
| syncer::KeyAndData key_and_data = data_batch->Next(); |
| auto web_app_iter = registry.find(key_and_data.first); |
| if (web_app_iter == registry.end()) { |
| LOG(ERROR) << "App not found in registry: " << key_and_data.first; |
| return false; |
| } |
| |
| if (!IsSyncDataEqual(*web_app_iter->second, *key_and_data.second)) |
| return false; |
| } |
| return true; |
| } |
| |
| std::unique_ptr<WebApp> CreateWebAppWithSyncOnlyFields( |
| const std::string& url, |
| std::optional<std::string> relative_manifest_id = std::nullopt) { |
| const GURL start_url(url); |
| const webapps::AppId app_id = GenerateAppId(relative_manifest_id, start_url); |
| |
| auto web_app = std::make_unique<WebApp>(app_id); |
| web_app->AddSource(WebAppManagement::kSync); |
| web_app->SetStartUrl(start_url); |
| web_app->SetName("Name"); |
| web_app->SetUserDisplayMode(UserDisplayMode::kStandalone); |
| return web_app; |
| } |
| |
| AppsList CreateAppsList(const std::string& base_url, int num_apps) { |
| AppsList apps_list; |
| |
| for (int i = 0; i < num_apps; ++i) { |
| apps_list.push_back( |
| CreateWebAppWithSyncOnlyFields(base_url + base::NumberToString(i))); |
| } |
| |
| return apps_list; |
| } |
| |
| void InsertAppIntoRegistry(Registry* registry, std::unique_ptr<WebApp> app) { |
| webapps::AppId app_id = app->app_id(); |
| ASSERT_FALSE(base::Contains(*registry, app_id)); |
| registry->emplace(std::move(app_id), std::move(app)); |
| } |
| |
| void InsertAppsListIntoRegistry(Registry* registry, const AppsList& apps_list) { |
| for (const std::unique_ptr<WebApp>& app : apps_list) |
| registry->emplace(app->app_id(), std::make_unique<WebApp>(*app)); |
| } |
| |
| void ConvertAppToEntityChange(const WebApp& app, |
| syncer::EntityChange::ChangeType change_type, |
| syncer::EntityChangeList* sync_data_list) { |
| std::unique_ptr<syncer::EntityChange> entity_change; |
| |
| switch (change_type) { |
| case syncer::EntityChange::ACTION_ADD: |
| entity_change = syncer::EntityChange::CreateAdd( |
| app.app_id(), std::move(*CreateSyncEntityData(app))); |
| break; |
| case syncer::EntityChange::ACTION_UPDATE: |
| entity_change = syncer::EntityChange::CreateUpdate( |
| app.app_id(), std::move(*CreateSyncEntityData(app))); |
| break; |
| case syncer::EntityChange::ACTION_DELETE: |
| entity_change = syncer::EntityChange::CreateDelete(app.app_id()); |
| break; |
| } |
| |
| sync_data_list->push_back(std::move(entity_change)); |
| } |
| |
| void ConvertAppsListToEntityChangeList( |
| const AppsList& apps_list, |
| syncer::EntityChangeList* sync_data_list) { |
| for (auto& app : apps_list) { |
| ConvertAppToEntityChange(*app, syncer::EntityChange::ACTION_ADD, |
| sync_data_list); |
| } |
| } |
| |
| // Returns true if the app converted from entity_data exists in the apps_list. |
| bool RemoveEntityDataAppFromAppsList(const std::string& storage_key, |
| const syncer::EntityData& entity_data, |
| AppsList* apps_list) { |
| for (auto& app : *apps_list) { |
| if (app->app_id() == storage_key) { |
| if (!IsSyncDataEqual(*app, entity_data)) |
| return false; |
| |
| RemoveWebAppFromAppsList(apps_list, storage_key); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void RunCallbacksOnInstall( |
| const std::vector<WebApp*>& apps, |
| const WebAppSyncBridge::RepeatingInstallCallback& callback, |
| webapps::InstallResultCode code) { |
| for (WebApp* app : apps) |
| callback.Run(app->app_id(), code); |
| } |
| |
| syncer::EntityChangeList ToEntityChageList( |
| const webapps::AppId& app_id, |
| const sync_pb::WebAppSpecifics& sync_proto, |
| WebAppSyncBridge& sync_bridge) { |
| syncer::EntityChangeList entity_change_list; |
| syncer::EntityData entity_data; |
| *entity_data.specifics.mutable_web_app() = sync_proto; |
| CHECK(sync_bridge.IsEntityDataValid(entity_data)); |
| std::string storage_key = sync_bridge.GetClientTag(entity_data); |
| DCHECK_EQ(storage_key, app_id); |
| entity_change_list.push_back( |
| syncer::EntityChange::CreateUpdate(storage_key, std::move(entity_data))); |
| return entity_change_list; |
| } |
| |
| } // namespace |
| |
| // TODO(dmurph): Replace these tests with tests in SingleClientWebAppsSyncTest, |
| // which can fake out the sync server and allow the WebAppProvider system to run |
| // in full w/o mocks. |
| class WebAppSyncBridgeTest : public WebAppTest { |
| public: |
| void StartWebAppProvider() { |
| test::AwaitStartWebAppProviderAndSubsystems(profile()); |
| } |
| |
| void StartWebAppProviderFromAppList(const AppsList& apps_list) { |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, apps_list); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| } |
| |
| void MergeFullSyncData(const AppsList& merged_apps) { |
| syncer::EntityChangeList entity_data_list; |
| ConvertAppsListToEntityChangeList(merged_apps, &entity_data_list); |
| EXPECT_CALL(processor(), Put).Times(0); |
| EXPECT_CALL(processor(), Delete).Times(0); |
| sync_bridge().MergeFullSyncData(sync_bridge().CreateMetadataChangeList(), |
| std::move(entity_data_list)); |
| } |
| |
| bool IsDatabaseRegistryEqualToRegistrar(bool exclude_current_os_integration) { |
| Registry registry = database_factory().ReadRegistry(); |
| return IsRegistryEqual(registrar_registry(), registry, |
| exclude_current_os_integration); |
| } |
| |
| void SetSyncInstallCallbackFailureIfCalled() { |
| sync_bridge().SetInstallWebAppsAfterSyncCallbackForTesting( |
| base::BindLambdaForTesting( |
| [&](std::vector<WebApp*> apps_to_install, |
| WebAppSyncBridge::RepeatingInstallCallback callback) { |
| ADD_FAILURE(); |
| })); |
| |
| sync_bridge().SetUninstallFromSyncCallbackForTesting( |
| base::BindLambdaForTesting( |
| [&](const std::vector<webapps::AppId>& apps_to_uninstall, |
| WebAppSyncBridge::RepeatingUninstallCallback callback) { |
| ADD_FAILURE(); |
| })); |
| } |
| |
| protected: |
| syncer::MockDataTypeLocalChangeProcessor& processor() { |
| return fake_provider().processor(); |
| } |
| FakeWebAppDatabaseFactory& database_factory() { |
| return static_cast<FakeWebAppDatabaseFactory&>( |
| fake_provider().GetDatabaseFactory()); |
| } |
| |
| WebAppRegistrar& registrar() { return fake_provider().registrar_unsafe(); } |
| |
| WebAppSyncBridge& sync_bridge() { |
| return fake_provider().sync_bridge_unsafe(); |
| } |
| |
| WebAppInstallManager& install_manager() { |
| return fake_provider().install_manager(); |
| } |
| |
| const Registry& registrar_registry() { |
| return fake_provider().registrar_unsafe().registry_for_testing(); |
| } |
| }; |
| |
| // Tests that the WebAppSyncBridge correctly reports data from the |
| // WebAppDatabase. |
| TEST_F(WebAppSyncBridgeTest, GetData) { |
| Registry registry; |
| |
| std::unique_ptr<WebApp> synced_app1 = |
| CreateWebAppWithSyncOnlyFields("https://example.com/app1/"); |
| { |
| sync_pb::WebAppSpecifics sync_data; |
| sync_data.set_name("Sync Name"); |
| sync_data.set_theme_color(SK_ColorCYAN); |
| synced_app1->SetSyncProto(std::move(sync_data)); |
| } |
| InsertAppIntoRegistry(®istry, std::move(synced_app1)); |
| |
| std::unique_ptr<WebApp> synced_app2 = |
| CreateWebAppWithSyncOnlyFields("https://example.com/app2/"); |
| // sync_fallback_data is empty for this app. |
| InsertAppIntoRegistry(®istry, std::move(synced_app2)); |
| |
| std::unique_ptr<WebApp> policy_app = test::CreateWebApp( |
| GURL("https://example.org/"), WebAppManagement::kPolicy); |
| InsertAppIntoRegistry(®istry, std::move(policy_app)); |
| |
| database_factory().WriteRegistry(registry); |
| |
| EXPECT_CALL(processor(), ModelReadyToSync(_)).Times(1); |
| StartWebAppProvider(); |
| |
| { |
| WebAppSyncBridge::StorageKeyList storage_keys; |
| // Add an unknown key to test this is handled gracefully. |
| storage_keys.push_back("unknown"); |
| for (const Registry::value_type& id_and_web_app : registry) |
| storage_keys.push_back(id_and_web_app.first); |
| |
| EXPECT_TRUE(RegistryContainsSyncDataBatchChanges( |
| registry, sync_bridge().GetDataForCommit(std::move(storage_keys)))); |
| } |
| |
| EXPECT_TRUE(RegistryContainsSyncDataBatchChanges( |
| registry, sync_bridge().GetAllDataForDebugging())); |
| } |
| |
| // Tests that the client & storage tags are correct for entity data. |
| TEST_F(WebAppSyncBridgeTest, Identities) { |
| std::unique_ptr<WebApp> app = |
| CreateWebAppWithSyncOnlyFields("https://example.com/"); |
| std::unique_ptr<syncer::EntityData> entity_data = CreateSyncEntityData(*app); |
| |
| EXPECT_EQ(app->app_id(), sync_bridge().GetClientTag(*entity_data)); |
| EXPECT_EQ(app->app_id(), sync_bridge().GetStorageKey(*entity_data)); |
| } |
| |
| // Test that a empty local data results in no changes sent to the sync system. |
| TEST_F(WebAppSyncBridgeTest, MergeFullSyncData_LocalSetAndServerSetAreEmpty) { |
| StartWebAppProvider(); |
| |
| syncer::EntityChangeList sync_data_list; |
| |
| EXPECT_CALL(processor(), Put).Times(0); |
| |
| sync_bridge().MergeFullSyncData(sync_bridge().CreateMetadataChangeList(), |
| std::move(sync_data_list)); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, MergeFullSyncData_LocalSetEqualsServerSet) { |
| AppsList apps = CreateAppsList("https://example.com/", 10); |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| |
| // The incoming list of apps from the sync server. |
| syncer::EntityChangeList sync_data_list; |
| ConvertAppsListToEntityChangeList(apps, &sync_data_list); |
| |
| // The local app state is the same as the server state, so no changes should |
| // be sent. |
| EXPECT_CALL(processor(), Put).Times(0); |
| |
| sync_bridge().MergeFullSyncData(sync_bridge().CreateMetadataChangeList(), |
| std::move(sync_data_list)); |
| |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, MergeFullSyncData_LocalSetGreaterThanServerSet) { |
| AppsList local_and_server_apps = CreateAppsList("https://example.com/", 10); |
| AppsList expected_local_apps_to_upload = |
| CreateAppsList("https://example.org/", 10); |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, local_and_server_apps); |
| InsertAppsListIntoRegistry(®istry, expected_local_apps_to_upload); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| |
| auto metadata_change_list = sync_bridge().CreateMetadataChangeList(); |
| syncer::MetadataChangeList* metadata_ptr = metadata_change_list.get(); |
| |
| syncer::EntityChangeList sync_data_list; |
| ConvertAppsListToEntityChangeList(local_and_server_apps, &sync_data_list); |
| |
| // MergeFullSyncData below should send |expected_local_apps_to_upload| to the |
| // processor() to upload to USS. |
| base::RunLoop run_loop; |
| ON_CALL(processor(), Put) |
| .WillByDefault([&](const std::string& storage_key, |
| std::unique_ptr<syncer::EntityData> entity_data, |
| syncer::MetadataChangeList* metadata) { |
| EXPECT_EQ(metadata_ptr, metadata); |
| EXPECT_TRUE(RemoveEntityDataAppFromAppsList( |
| storage_key, *entity_data, &expected_local_apps_to_upload)); |
| if (expected_local_apps_to_upload.empty()) |
| run_loop.Quit(); |
| }); |
| |
| sync_bridge().MergeFullSyncData(std::move(metadata_change_list), |
| std::move(sync_data_list)); |
| run_loop.Run(); |
| |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, MergeFullSyncData_LocalSetLessThanServerSet) { |
| AppsList local_and_server_apps = CreateAppsList("https://example.com/", 10); |
| AppsList expected_apps_to_install = |
| CreateAppsList("https://example.org/", 10); |
| // These fields are not synced, these are just expected values. |
| for (std::unique_ptr<WebApp>& expected_app_to_install : |
| expected_apps_to_install) { |
| expected_app_to_install->SetInstallState( |
| AreAppsLocallyInstalledBySync() |
| ? proto::InstallState::INSTALLED_WITH_OS_INTEGRATION |
| : proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE); |
| expected_app_to_install->SetIsFromSyncAndPendingInstallation(true); |
| } |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, local_and_server_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| |
| syncer::EntityChangeList sync_data_list; |
| ConvertAppsListToEntityChangeList(expected_apps_to_install, &sync_data_list); |
| ConvertAppsListToEntityChangeList(local_and_server_apps, &sync_data_list); |
| |
| EXPECT_CALL(processor(), Put).Times(0); |
| |
| base::RunLoop run_loop; |
| // This is called after apps are installed from sync in MergeFullSyncData() |
| // below. |
| sync_bridge().SetInstallWebAppsAfterSyncCallbackForTesting( |
| base::BindLambdaForTesting( |
| [&](std::vector<WebApp*> apps_to_install, |
| WebAppSyncBridge::RepeatingInstallCallback callback) { |
| for (WebApp* app_to_install : apps_to_install) { |
| // The app must be registered. |
| EXPECT_TRUE(registrar().GetAppById(app_to_install->app_id())); |
| // Add the app copy to the expected registry. |
| registry.emplace(app_to_install->app_id(), |
| std::make_unique<WebApp>(*app_to_install)); |
| |
| // Find the app in expected_apps_to_install set and remove the |
| // entry. |
| bool found = false; |
| for (const std::unique_ptr<WebApp>& expected_app_to_install : |
| expected_apps_to_install) { |
| if (expected_app_to_install->app_id() == |
| app_to_install->app_id()) { |
| EXPECT_EQ(*expected_app_to_install, *app_to_install); |
| RemoveWebAppFromAppsList(&expected_apps_to_install, |
| expected_app_to_install->app_id()); |
| found = true; |
| break; |
| } |
| } |
| EXPECT_TRUE(found); |
| } |
| |
| EXPECT_TRUE(expected_apps_to_install.empty()); |
| |
| RunCallbacksOnInstall( |
| apps_to_install, callback, |
| webapps::InstallResultCode::kSuccessNewInstall); |
| run_loop.Quit(); |
| })); |
| |
| sync_bridge().MergeFullSyncData(sync_bridge().CreateMetadataChangeList(), |
| std::move(sync_data_list)); |
| run_loop.Run(); |
| |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, ApplyIncrementalSyncChanges_EmptyEntityChanges) { |
| AppsList merged_apps = CreateAppsList("https://example.com/", 10); |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, merged_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| MergeFullSyncData(merged_apps); |
| |
| syncer::EntityChangeList entity_changes; |
| EXPECT_CALL(processor(), Put).Times(0); |
| EXPECT_CALL(processor(), Delete).Times(0); |
| SetSyncInstallCallbackFailureIfCalled(); |
| |
| sync_bridge().ApplyIncrementalSyncChanges( |
| sync_bridge().CreateMetadataChangeList(), std::move(entity_changes)); |
| |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, ApplyIncrementalSyncChanges_AddUpdateDelete) { |
| // 20 initial apps with DisplayMode::kStandalone user display mode. |
| AppsList merged_apps = CreateAppsList("https://example.com/", 20); |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, merged_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| MergeFullSyncData(merged_apps); |
| |
| syncer::EntityChangeList entity_changes; |
| |
| for (std::unique_ptr<WebApp>& app_to_add : |
| CreateAppsList("https://example.org/", 10)) { |
| app_to_add->SetInstallState( |
| AreAppsLocallyInstalledBySync() |
| ? proto::InstallState::INSTALLED_WITH_OS_INTEGRATION |
| : proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE); |
| app_to_add->SetIsFromSyncAndPendingInstallation(true); |
| |
| ConvertAppToEntityChange(*app_to_add, syncer::EntityChange::ACTION_ADD, |
| &entity_changes); |
| } |
| |
| // Update first 5 initial apps. |
| for (int i = 0; i < 5; ++i) { |
| auto app_to_update = std::make_unique<WebApp>(*merged_apps[i]); |
| // Update user display mode field. |
| app_to_update->SetUserDisplayMode(UserDisplayMode::kBrowser); |
| ConvertAppToEntityChange( |
| *app_to_update, syncer::EntityChange::ACTION_UPDATE, &entity_changes); |
| // Override the app in the expected registry. |
| registry[app_to_update->app_id()] = std::move(app_to_update); |
| } |
| |
| // Delete next 5 initial apps. Leave the rest unchanged. |
| for (int i = 5; i < 10; ++i) { |
| const WebApp& app_to_delete = *merged_apps[i]; |
| ConvertAppToEntityChange(app_to_delete, syncer::EntityChange::ACTION_DELETE, |
| &entity_changes); |
| } |
| |
| // There should be no changes sent to USS in the next |
| // ApplyIncrementalSyncChanges() operation. |
| EXPECT_CALL(processor(), Put).Times(0); |
| EXPECT_CALL(processor(), Delete).Times(0); |
| |
| base::RunLoop run_loop; |
| base::RepeatingClosure barrier_closure = |
| base::BarrierClosure(2, run_loop.QuitClosure()); |
| |
| sync_bridge().SetInstallWebAppsAfterSyncCallbackForTesting( |
| base::BindLambdaForTesting( |
| [&](std::vector<WebApp*> apps_to_install, |
| WebAppSyncBridge::RepeatingInstallCallback callback) { |
| for (WebApp* app_to_install : apps_to_install) { |
| // The app must be registered. |
| EXPECT_TRUE(registrar().GetAppById(app_to_install->app_id())); |
| // Add the app copy to the expected registry. |
| registry.emplace(app_to_install->app_id(), |
| std::make_unique<WebApp>(*app_to_install)); |
| } |
| |
| RunCallbacksOnInstall( |
| apps_to_install, callback, |
| webapps::InstallResultCode::kSuccessNewInstall); |
| barrier_closure.Run(); |
| })); |
| |
| sync_bridge().SetUninstallFromSyncCallbackForTesting( |
| base::BindLambdaForTesting( |
| [&](const std::vector<webapps::AppId>& apps_to_uninstall, |
| WebAppSyncBridge::RepeatingUninstallCallback callback) { |
| EXPECT_EQ(5ul, apps_to_uninstall.size()); |
| for (const webapps::AppId& app_to_uninstall : apps_to_uninstall) { |
| // The app must be registered. |
| const WebApp* app = registrar().GetAppById(app_to_uninstall); |
| // Sync expects that the apps are deleted by the delegate. |
| EXPECT_TRUE(app); |
| EXPECT_TRUE(app->is_uninstalling()); |
| EXPECT_TRUE(app->GetSources().empty()); |
| registry.erase(app_to_uninstall); |
| { |
| ScopedRegistryUpdate update = sync_bridge().BeginUpdate(); |
| update->DeleteApp(app_to_uninstall); |
| } |
| callback.Run(app_to_uninstall, |
| webapps::UninstallResultCode::kAppRemoved); |
| } |
| |
| barrier_closure.Run(); |
| })); |
| |
| sync_bridge().ApplyIncrementalSyncChanges( |
| sync_bridge().CreateMetadataChangeList(), std::move(entity_changes)); |
| run_loop.Run(); |
| |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, |
| ApplyIncrementalSyncChanges_DeleteHappensExternally) { |
| // 5 initial apps. |
| AppsList merged_apps = CreateAppsList("https://example.com/", 5); |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, merged_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| MergeFullSyncData(merged_apps); |
| |
| syncer::EntityChangeList entity_changes; |
| |
| // Delete next 5 initial apps. Leave the rest unchanged. |
| for (int i = 0; i < 5; ++i) { |
| const WebApp& app_to_delete = *merged_apps[i]; |
| ConvertAppToEntityChange(app_to_delete, syncer::EntityChange::ACTION_DELETE, |
| &entity_changes); |
| } |
| |
| // There should be no changes sent to USS in the next |
| // ApplyIncrementalSyncChanges() operation. |
| EXPECT_CALL(processor(), Put).Times(0); |
| EXPECT_CALL(processor(), Delete).Times(0); |
| |
| base::RunLoop run_loop; |
| std::vector<webapps::AppId> to_uninstall; |
| WebAppSyncBridge::RepeatingUninstallCallback uninstall_complete_callback; |
| |
| sync_bridge().SetUninstallFromSyncCallbackForTesting( |
| base::BindLambdaForTesting( |
| [&](const std::vector<webapps::AppId>& apps_to_uninstall, |
| WebAppSyncBridge::RepeatingUninstallCallback callback) { |
| to_uninstall = apps_to_uninstall; |
| uninstall_complete_callback = callback; |
| run_loop.Quit(); |
| })); |
| |
| sync_bridge().ApplyIncrementalSyncChanges( |
| sync_bridge().CreateMetadataChangeList(), std::move(entity_changes)); |
| run_loop.Run(); |
| |
| EXPECT_EQ(5ul, to_uninstall.size()); |
| |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| for (const webapps::AppId& app_to_uninstall : to_uninstall) { |
| const WebApp* app = registrar().GetAppById(app_to_uninstall); |
| EXPECT_TRUE(app); |
| EXPECT_TRUE(app->is_uninstalling()); |
| EXPECT_TRUE(app->GetSources().empty()); |
| } |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, ApplyIncrementalSyncChanges_UpdateOnly) { |
| AppsList merged_apps = CreateAppsList("https://example.com/", 10); |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, merged_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| MergeFullSyncData(merged_apps); |
| |
| syncer::EntityChangeList entity_changes; |
| |
| // Update last 5 initial apps. |
| for (int i = 5; i < 10; ++i) { |
| auto app_to_update = std::make_unique<WebApp>(*merged_apps[i]); |
| app_to_update->SetUserDisplayMode(UserDisplayMode::kStandalone); |
| |
| sync_pb::WebAppSpecifics sync_data = app_to_update->sync_proto(); |
| sync_data.set_name("Sync Name"); |
| sync_data.set_theme_color(SK_ColorYELLOW); |
| app_to_update->SetSyncProto(std::move(sync_data)); |
| |
| ConvertAppToEntityChange( |
| *app_to_update, syncer::EntityChange::ACTION_UPDATE, &entity_changes); |
| // Override the app in the expected registry. |
| registry[app_to_update->app_id()] = std::move(app_to_update); |
| } |
| |
| EXPECT_CALL(processor(), Put).Times(0); |
| EXPECT_CALL(processor(), Delete).Times(0); |
| |
| // No installs or uninstalls are made here, only app updates. |
| SetSyncInstallCallbackFailureIfCalled(); |
| |
| sync_bridge().ApplyIncrementalSyncChanges( |
| sync_bridge().CreateMetadataChangeList(), std::move(entity_changes)); |
| |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, |
| ApplyIncrementalSyncChanges_AddSyncAppsWithOverlappingPolicyApps) { |
| AppsList policy_apps; |
| for (int i = 0; i < 10; ++i) { |
| std::unique_ptr<WebApp> policy_app = test::CreateWebApp( |
| GURL("https://example.com/" + base::NumberToString(i)), |
| WebAppManagement::kPolicy); |
| policy_apps.push_back(std::move(policy_app)); |
| } |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, policy_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| |
| syncer::EntityChangeList entity_changes; |
| |
| // Install 5 kSync apps over existing kPolicy apps. Leave the rest unchanged. |
| for (int i = 0; i < 5; ++i) { |
| const WebApp& app_to_install = *policy_apps[i]; |
| ConvertAppToEntityChange(app_to_install, syncer::EntityChange::ACTION_ADD, |
| &entity_changes); |
| } |
| |
| EXPECT_CALL(processor(), Put).Times(0); |
| EXPECT_CALL(processor(), Delete).Times(0); |
| SetSyncInstallCallbackFailureIfCalled(); |
| |
| sync_bridge().ApplyIncrementalSyncChanges( |
| sync_bridge().CreateMetadataChangeList(), std::move(entity_changes)); |
| |
| // Modify the registry with the results that we expect. |
| for (int i = 0; i < 5; ++i) { |
| std::unique_ptr<WebApp>& expected_sync_and_policy_app = |
| registry[policy_apps[i]->app_id()]; |
| expected_sync_and_policy_app->AddSource(WebAppManagement::kSync); |
| } |
| |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, |
| ApplyIncrementalSyncChanges_UpdateSyncAppsWithOverlappingPolicyApps) { |
| AppsList policy_and_sync_apps; |
| for (int i = 0; i < 10; ++i) { |
| std::unique_ptr<WebApp> policy_and_sync_app = test::CreateWebApp( |
| GURL("https://example.com/" + base::NumberToString(i))); |
| policy_and_sync_app->AddSource(WebAppManagement::kPolicy); |
| policy_and_sync_app->AddSource(WebAppManagement::kSync); |
| policy_and_sync_apps.push_back(std::move(policy_and_sync_app)); |
| } |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, policy_and_sync_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| MergeFullSyncData(policy_and_sync_apps); |
| |
| syncer::EntityChangeList entity_changes; |
| |
| // Update first 5 kSync apps which are shared with kPolicy. Leave the rest |
| // unchanged. |
| AppsList apps_to_update; |
| for (int i = 0; i < 5; ++i) { |
| auto app_to_update = std::make_unique<WebApp>(*policy_and_sync_apps[i]); |
| app_to_update->SetUserDisplayMode(UserDisplayMode::kBrowser); |
| |
| sync_pb::WebAppSpecifics sync_data = app_to_update->sync_proto(); |
| sync_data.set_name("Updated Sync Name"); |
| sync_data.set_theme_color(SK_ColorWHITE); |
| app_to_update->SetSyncProto(std::move(sync_data)); |
| |
| ConvertAppToEntityChange( |
| *app_to_update, syncer::EntityChange::ACTION_UPDATE, &entity_changes); |
| apps_to_update.push_back(std::move(app_to_update)); |
| } |
| |
| EXPECT_CALL(processor(), Put).Times(0); |
| EXPECT_CALL(processor(), Delete).Times(0); |
| SetSyncInstallCallbackFailureIfCalled(); |
| |
| sync_bridge().ApplyIncrementalSyncChanges( |
| sync_bridge().CreateMetadataChangeList(), std::move(entity_changes)); |
| |
| // Modify the registry with the results that we expect. |
| for (int i = 0; i < 5; ++i) |
| registry[policy_and_sync_apps[i]->app_id()] = std::move(apps_to_update[i]); |
| |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| // Tests that if a policy app is installed, and that app is also in 'sync' and |
| // is uninstalled through sync, then it should remain on the system as a policy |
| // app. |
| TEST_F(WebAppSyncBridgeTest, |
| ApplyIncrementalSyncChanges_DeleteSyncAppsWithOverlappingPolicyApps) { |
| AppsList policy_and_sync_apps; |
| for (int i = 0; i < 10; ++i) { |
| std::unique_ptr<WebApp> policy_and_sync_app = test::CreateWebApp( |
| GURL("https://example.com/" + base::NumberToString(i))); |
| policy_and_sync_app->AddSource(WebAppManagement::kPolicy); |
| policy_and_sync_app->AddSource(WebAppManagement::kSync); |
| policy_and_sync_apps.push_back(std::move(policy_and_sync_app)); |
| } |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, policy_and_sync_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| MergeFullSyncData(policy_and_sync_apps); |
| |
| syncer::EntityChangeList entity_changes; |
| |
| // Uninstall 5 kSync apps which are shared with kPolicy. Leave the rest |
| // unchanged. |
| for (int i = 0; i < 5; ++i) { |
| const WebApp& app_to_uninstall = *policy_and_sync_apps[i]; |
| ConvertAppToEntityChange( |
| app_to_uninstall, syncer::EntityChange::ACTION_DELETE, &entity_changes); |
| } |
| |
| EXPECT_CALL(processor(), Put).Times(0); |
| EXPECT_CALL(processor(), Delete).Times(0); |
| SetSyncInstallCallbackFailureIfCalled(); |
| |
| sync_bridge().ApplyIncrementalSyncChanges( |
| sync_bridge().CreateMetadataChangeList(), std::move(entity_changes)); |
| |
| // Modify the registry with the results that we expect. |
| for (int i = 0; i < 5; ++i) { |
| std::unique_ptr<WebApp>& expected_policy_app = |
| registry[policy_and_sync_apps[i]->app_id()]; |
| expected_policy_app->RemoveSource(WebAppManagement::kSync); |
| } |
| |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| // Commits local data (e.g. installed web apps) before sync is hooked up. This |
| // tests that the web apps are correctly sent to USS after MergeFullSyncData is |
| // called. |
| TEST_F(WebAppSyncBridgeTest, CommitUpdate_CommitWhileNotTrackingMetadata) { |
| EXPECT_CALL(processor(), ModelReadyToSync(_)).Times(1); |
| StartWebAppProvider(); |
| |
| AppsList sync_apps = CreateAppsList("https://example.com/", 10); |
| Registry expected_registry; |
| InsertAppsListIntoRegistry(&expected_registry, sync_apps); |
| |
| EXPECT_CALL(processor(), Put).Times(0); |
| EXPECT_CALL(processor(), Delete).Times(0); |
| EXPECT_CALL(processor(), IsTrackingMetadata()) |
| .WillOnce(testing::Return(false)); |
| |
| base::test::TestFuture<bool> future; |
| { |
| ScopedRegistryUpdate update = |
| sync_bridge().BeginUpdate(future.GetCallback()); |
| |
| for (const std::unique_ptr<WebApp>& app : sync_apps) { |
| update->CreateApp(std::make_unique<WebApp>(*app)); |
| } |
| } |
| EXPECT_TRUE(future.Take()); |
| testing::Mock::VerifyAndClear(&processor()); |
| |
| // Do MergeFullSyncData next. |
| base::RunLoop run_loop; |
| ON_CALL(processor(), Put) |
| .WillByDefault([&](const std::string& storage_key, |
| std::unique_ptr<syncer::EntityData> entity_data, |
| syncer::MetadataChangeList* metadata) { |
| EXPECT_TRUE(RemoveEntityDataAppFromAppsList(storage_key, *entity_data, |
| &sync_apps)); |
| if (sync_apps.empty()) |
| run_loop.Quit(); |
| }); |
| |
| EXPECT_CALL(processor(), Delete).Times(0); |
| EXPECT_CALL(processor(), IsTrackingMetadata()) |
| .WillOnce(testing::Return(true)); |
| |
| sync_bridge().MergeFullSyncData(sync_bridge().CreateMetadataChangeList(), |
| syncer::EntityChangeList{}); |
| run_loop.Run(); |
| |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), expected_registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, CommitUpdate_CreateSyncApp) { |
| StartWebAppProvider(); |
| |
| AppsList sync_apps = CreateAppsList("https://example.com/", 10); |
| Registry expected_registry; |
| InsertAppsListIntoRegistry(&expected_registry, sync_apps); |
| |
| ON_CALL(processor(), Put) |
| .WillByDefault([&](const std::string& storage_key, |
| std::unique_ptr<syncer::EntityData> entity_data, |
| syncer::MetadataChangeList* metadata) { |
| ASSERT_TRUE(base::Contains(expected_registry, storage_key)); |
| const std::unique_ptr<WebApp>& expected_app = |
| expected_registry.at(storage_key); |
| EXPECT_TRUE(IsSyncDataEqual(*expected_app, *entity_data)); |
| RemoveWebAppFromAppsList(&sync_apps, storage_key); |
| }); |
| EXPECT_CALL(processor(), Delete).Times(0); |
| EXPECT_CALL(processor(), IsTrackingMetadata()) |
| .WillOnce(testing::Return(true)); |
| |
| base::test::TestFuture<bool> future; |
| { |
| ScopedRegistryUpdate update = |
| sync_bridge().BeginUpdate(future.GetCallback()); |
| |
| for (const std::unique_ptr<WebApp>& app : sync_apps) { |
| update->CreateApp(std::make_unique<WebApp>(*app)); |
| } |
| } |
| EXPECT_TRUE(future.Take()); |
| |
| EXPECT_TRUE(sync_apps.empty()); |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), expected_registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, CommitUpdate_UpdateSyncApp) { |
| AppsList sync_apps = CreateAppsList("https://example.com/", 10); |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, sync_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| |
| ON_CALL(processor(), Put) |
| .WillByDefault([&](const std::string& storage_key, |
| std::unique_ptr<syncer::EntityData> entity_data, |
| syncer::MetadataChangeList* metadata) { |
| ASSERT_TRUE(base::Contains(registry, storage_key)); |
| const std::unique_ptr<WebApp>& expected_app = registry.at(storage_key); |
| EXPECT_TRUE(IsSyncDataEqual(*expected_app, *entity_data)); |
| RemoveWebAppFromAppsList(&sync_apps, storage_key); |
| }); |
| EXPECT_CALL(processor(), Delete).Times(0); |
| |
| base::test::TestFuture<bool> future; |
| { |
| ScopedRegistryUpdate update = |
| sync_bridge().BeginUpdate(future.GetCallback()); |
| |
| for (const std::unique_ptr<WebApp>& app : sync_apps) { |
| // Obtain a writeable handle. |
| WebApp* sync_app = update->UpdateApp(app->app_id()); |
| |
| sync_pb::WebAppSpecifics sync_data; |
| sync_data.set_name("Updated Sync Name"); |
| sync_data.set_theme_color(SK_ColorBLACK); |
| sync_app->SetSyncProto(std::move(sync_data)); |
| sync_app->SetUserDisplayMode(UserDisplayMode::kBrowser); |
| |
| // Override the app in the expected registry. |
| registry[sync_app->app_id()] = std::make_unique<WebApp>(*sync_app); |
| } |
| } |
| EXPECT_TRUE(future.Take()); |
| |
| EXPECT_TRUE(sync_apps.empty()); |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, CommitUpdate_DeleteSyncApp) { |
| const int kNumApps = 5; |
| AppsList sync_apps = CreateAppsList("https://example.com/", kNumApps); |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, sync_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| |
| // Put() is called kNumApps times, since UninstallWebApp() calls Put() to |
| // update the `is_uninstalling` field. |
| EXPECT_CALL(processor(), Put).Times(kNumApps); |
| ON_CALL(processor(), Delete) |
| .WillByDefault([&](const std::string& storage_key, |
| const syncer::DeletionOrigin& origin, |
| syncer::MetadataChangeList* metadata) { |
| EXPECT_TRUE(base::Contains(registry, storage_key)); |
| RemoveWebAppFromAppsList(&sync_apps, storage_key); |
| // Delete the app in the expected registry. |
| registry.erase(storage_key); |
| }); |
| |
| base::test::TestFuture<const std::optional<std::string>&> final_callback; |
| fake_provider().scheduler().UninstallAllUserInstalledWebApps( |
| webapps::WebappUninstallSource::kSync, final_callback.GetCallback()); |
| EXPECT_TRUE(final_callback.Wait()); |
| |
| EXPECT_TRUE(sync_apps.empty()); |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, |
| CommitUpdate_CreateSyncAppWithOverlappingPolicyApp) { |
| AppsList policy_apps; |
| for (int i = 0; i < 10; ++i) { |
| std::unique_ptr<WebApp> policy_app = test::CreateWebApp( |
| GURL("https://example.com/" + base::NumberToString(i)), |
| WebAppManagement::kPolicy); |
| policy_apps.push_back(std::move(policy_app)); |
| } |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, policy_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| |
| ON_CALL(processor(), Put) |
| .WillByDefault([&](const std::string& storage_key, |
| std::unique_ptr<syncer::EntityData> entity_data, |
| syncer::MetadataChangeList* metadata) { |
| ASSERT_TRUE(base::Contains(registry, storage_key)); |
| const WebApp& expected_app = *registry.at(storage_key); |
| |
| // kPolicy and Name is the difference for the sync "view". Add them to |
| // make operator== work. |
| std::unique_ptr<WebApp> entity_data_app = |
| std::make_unique<WebApp>(expected_app.app_id()); |
| entity_data_app->AddSource(WebAppManagement::kPolicy); |
| entity_data_app->SetName("Name"); |
| entity_data_app->SetInstallState( |
| proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION); |
| |
| EXPECT_TRUE(IsSyncDataEqualIfApplied( |
| expected_app, std::move(entity_data_app), *entity_data)); |
| |
| RemoveWebAppFromAppsList(&policy_apps, storage_key); |
| }); |
| EXPECT_CALL(processor(), Delete).Times(0); |
| |
| base::test::TestFuture<bool> future; |
| { |
| ScopedRegistryUpdate update = |
| sync_bridge().BeginUpdate(future.GetCallback()); |
| |
| for (int i = 0; i < 10; ++i) { |
| WebApp* app_to_update = update->UpdateApp(policy_apps[i]->app_id()); |
| |
| // Add kSync source to first 5 apps. Modify the rest 5 apps locally. |
| if (i < 5) { |
| app_to_update->AddSource(WebAppManagement::kSync); |
| } else { |
| app_to_update->SetDescription("Local policy app"); |
| } |
| |
| // Override the app in the expected registry. |
| registry[app_to_update->app_id()] = |
| std::make_unique<WebApp>(*app_to_update); |
| } |
| } |
| EXPECT_TRUE(future.Take()); |
| |
| EXPECT_EQ(5u, policy_apps.size()); |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, |
| CommitUpdate_DeleteSyncAppWithOverlappingPolicyApp) { |
| AppsList policy_and_sync_apps; |
| for (int i = 0; i < 10; ++i) { |
| std::unique_ptr<WebApp> policy_and_sync_app = test::CreateWebApp( |
| GURL("https://example.com/" + base::NumberToString(i))); |
| policy_and_sync_app->AddSource(WebAppManagement::kPolicy); |
| policy_and_sync_app->AddSource(WebAppManagement::kSync); |
| policy_and_sync_apps.push_back(std::move(policy_and_sync_app)); |
| } |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, policy_and_sync_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| |
| ON_CALL(processor(), Put) |
| .WillByDefault([&](const std::string& storage_key, |
| std::unique_ptr<syncer::EntityData> entity_data, |
| syncer::MetadataChangeList* metadata) { |
| // Local changes to synced apps cause excessive |Put|. |
| // See TODO in WebAppSyncBridge::UpdateSync. |
| RemoveWebAppFromAppsList(&policy_and_sync_apps, storage_key); |
| }); |
| ON_CALL(processor(), Delete) |
| .WillByDefault([&](const std::string& storage_key, |
| const syncer::DeletionOrigin& origin, |
| syncer::MetadataChangeList* metadata) { |
| ASSERT_TRUE(base::Contains(registry, storage_key)); |
| RemoveWebAppFromAppsList(&policy_and_sync_apps, storage_key); |
| }); |
| |
| base::test::TestFuture<bool> future; |
| { |
| ScopedRegistryUpdate update = |
| sync_bridge().BeginUpdate(future.GetCallback()); |
| |
| for (int i = 0; i < 10; ++i) { |
| WebApp* app_to_update = |
| update->UpdateApp(policy_and_sync_apps[i]->app_id()); |
| |
| // Remove kSync source from first 5 apps. Modify the rest 5 apps locally. |
| if (i < 5) { |
| app_to_update->RemoveSource(WebAppManagement::kSync); |
| } else { |
| app_to_update->SetDescription("Local policy app"); |
| } |
| |
| // Override the app in the expected registry. |
| registry[app_to_update->app_id()] = |
| std::make_unique<WebApp>(*app_to_update); |
| } |
| } |
| EXPECT_TRUE(future.Take()); |
| |
| EXPECT_TRUE(policy_and_sync_apps.empty()); |
| EXPECT_TRUE(IsRegistryEqual(registrar_registry(), registry, |
| /*exclude_current_os_integration=*/true)); |
| EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar( |
| /*exclude_current_os_integration=*/true)); |
| } |
| |
| // Test that any apps that are still pending install from sync (or, |
| // |is_from_sync_and_pending_installation|) are continued to be installed when |
| // the bridge initializes. |
| TEST_F(WebAppSyncBridgeTest, InstallAppsFromSyncAndPendingInstallation) { |
| AppsList apps_in_sync_install = CreateAppsList("https://example.com/", 10); |
| for (std::unique_ptr<WebApp>& app : apps_in_sync_install) { |
| app->SetInstallState( |
| AreAppsLocallyInstalledBySync() |
| ? proto::InstallState::INSTALLED_WITH_OS_INTEGRATION |
| : proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE); |
| app->SetIsFromSyncAndPendingInstallation(true); |
| } |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, apps_in_sync_install); |
| database_factory().WriteRegistry(registry); |
| |
| base::RunLoop run_loop; |
| sync_bridge().SetInstallWebAppsAfterSyncCallbackForTesting( |
| base::BindLambdaForTesting( |
| [&](std::vector<WebApp*> apps_to_install, |
| WebAppSyncBridge::RepeatingInstallCallback callback) { |
| for (WebApp* app_to_install : apps_to_install) { |
| // The app must be registered. |
| EXPECT_TRUE(registrar().GetAppById(app_to_install->app_id())); |
| RemoveWebAppFromAppsList(&apps_in_sync_install, |
| app_to_install->app_id()); |
| } |
| |
| EXPECT_TRUE(apps_in_sync_install.empty()); |
| RunCallbacksOnInstall( |
| apps_to_install, callback, |
| webapps::InstallResultCode::kSuccessNewInstall); |
| run_loop.Quit(); |
| })); |
| |
| StartWebAppProvider(); |
| |
| run_loop.Run(); |
| } |
| |
| // Tests that non user installable apps can also be removed by the |
| // WebAppSyncBridge during system startup, if `is_uninstalling` is set to true. |
| // Test for crbug.com/335253048, by using kSystem to mock that behavior. Since |
| // System Web Apps are only on Ash chrome, kPolicy is used instead on Lacro |
| TEST_F(WebAppSyncBridgeTest, CanDeleteNonUserInstallableApps) { |
| AppsList system_apps; |
| |
| // This app should be uninstalled, since the `is_uninstalling` field is set. |
| std::unique_ptr<WebApp> app1 = |
| test::CreateWebApp(GURL("https://example.com/app1")); |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| app1->AddSource(WebAppManagement::kPolicy); |
| #else |
| app1->AddSource(WebAppManagement::kSystem); |
| #endif // BUILDFLAG(IS_CHROMEOS_LACROS) |
| app1->SetIsUninstalling(/*is_uninstalling=*/true); |
| const webapps::AppId app_id1 = app1->app_id(); |
| system_apps.push_back(std::move(app1)); |
| |
| // This app will not be uninstalled. |
| std::unique_ptr<WebApp> app2 = |
| test::CreateWebApp(GURL("https://example.com/app2")); |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| app2->AddSource(WebAppManagement::kPolicy); |
| #else |
| app2->AddSource(WebAppManagement::kSystem); |
| #endif // BUILDFLAG(IS_CHROMEOS_LACROS) |
| const webapps::AppId app_id2 = app2->app_id(); |
| system_apps.push_back(std::move(app2)); |
| |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, system_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| |
| EXPECT_FALSE(registrar().IsInstalled(app_id1)); |
| EXPECT_TRUE(registrar().IsInstalled(app_id2)); |
| } |
| |
| // Tests that OnWebAppsWillBeUpdatedFromSync observer notification is called |
| // properly. |
| TEST_F(WebAppSyncBridgeTest, |
| ApplyIncrementalSyncChanges_OnWebAppsWillBeUpdatedFromSync) { |
| AppsList initial_registry_apps = CreateAppsList("https://example.com/", 10); |
| for (std::unique_ptr<WebApp>& app : initial_registry_apps) |
| app->SetUserDisplayMode(UserDisplayMode::kBrowser); |
| StartWebAppProviderFromAppList(initial_registry_apps); |
| |
| WebAppTestRegistryObserverAdapter observer{®istrar()}; |
| base::RunLoop run_loop; |
| |
| observer.SetWebAppWillBeUpdatedFromSyncDelegate(base::BindLambdaForTesting( |
| [&](const std::vector<const WebApp*>& new_apps_state) { |
| EXPECT_EQ(5u, new_apps_state.size()); |
| |
| for (const WebApp* new_app_state : new_apps_state) { |
| const WebApp* old_app_state = |
| registrar().GetAppById(new_app_state->app_id()); |
| EXPECT_NE(*old_app_state, *new_app_state); |
| |
| EXPECT_EQ(old_app_state->user_display_mode(), |
| UserDisplayMode::kBrowser); |
| EXPECT_EQ(new_app_state->user_display_mode(), |
| UserDisplayMode::kStandalone); |
| |
| // new and old states must be equal if diff fixed: |
| auto old_app_state_no_diff = std::make_unique<WebApp>(*old_app_state); |
| old_app_state_no_diff->SetUserDisplayMode( |
| UserDisplayMode::kStandalone); |
| EXPECT_EQ(*old_app_state_no_diff, *new_app_state); |
| |
| RemoveWebAppFromAppsList(&initial_registry_apps, |
| new_app_state->app_id()); |
| } |
| |
| run_loop.Quit(); |
| })); |
| |
| AppsList apps_server_state; |
| |
| // Update first 5 apps: change user_display_mode field only. |
| for (int i = 0; i < 5; ++i) { |
| auto app_server_state = std::make_unique<WebApp>(*initial_registry_apps[i]); |
| app_server_state->SetUserDisplayMode(UserDisplayMode::kStandalone); |
| apps_server_state.push_back(std::move(app_server_state)); |
| } |
| |
| sync_bridge_test_utils::UpdateApps(sync_bridge(), apps_server_state); |
| |
| run_loop.Run(); |
| |
| // 5 other apps left unchanged: |
| EXPECT_EQ(5u, initial_registry_apps.size()); |
| for (int i = 0; i < 5; ++i) { |
| EXPECT_EQ(UserDisplayMode::kBrowser, |
| initial_registry_apps[i]->user_display_mode()); |
| } |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, RetryIncompleteUninstalls) { |
| AppsList initial_registry_apps = CreateAppsList("https://example.com/", 5); |
| std::vector<webapps::AppId> initial_app_ids; |
| for (std::unique_ptr<WebApp>& app : initial_registry_apps) { |
| app->SetUserDisplayMode(UserDisplayMode::kBrowser); |
| app->SetIsUninstalling(true); |
| initial_app_ids.push_back(app->app_id()); |
| } |
| |
| SetSyncInstallCallbackFailureIfCalled(); |
| |
| base::RunLoop run_loop; |
| sync_bridge().SetRetryIncompleteUninstallsCallbackForTesting( |
| base::BindLambdaForTesting( |
| [&](const base::flat_set<webapps::AppId>& apps_to_uninstall) { |
| EXPECT_EQ(apps_to_uninstall.size(), 5ul); |
| EXPECT_THAT(apps_to_uninstall, ::testing::UnorderedElementsAreArray( |
| apps_to_uninstall)); |
| run_loop.Quit(); |
| })); |
| |
| StartWebAppProviderFromAppList(initial_registry_apps); |
| |
| run_loop.Run(); |
| } |
| |
| syncer::EntityData CreateSyncEntityData( |
| const std::string& name, |
| const sync_pb::EntitySpecifics& specifics) { |
| syncer::EntityData entity_data; |
| entity_data.name = name; |
| entity_data.specifics = specifics; |
| return entity_data; |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, InvalidSyncData) { |
| StartWebAppProvider(); |
| |
| base::HistogramTester histogram_tester; |
| |
| GURL url("https://example.com/start"); |
| const std::string app_id = |
| GenerateAppId(/*manifest_id_path=*/std::nullopt, url); |
| sync_pb::EntitySpecifics entity_specifics; |
| entity_specifics.mutable_web_app()->set_name(app_id); |
| |
| // No start url. |
| EXPECT_FALSE(sync_bridge().IsEntityDataValid( |
| CreateSyncEntityData(app_id, entity_specifics))); |
| |
| // Invalid start url. |
| entity_specifics.mutable_web_app()->set_start_url(""); |
| EXPECT_FALSE(sync_bridge().IsEntityDataValid( |
| CreateSyncEntityData(app_id, entity_specifics))); |
| |
| // Invalid manifest id. |
| entity_specifics.mutable_web_app()->set_start_url("about:blank"); |
| entity_specifics.mutable_web_app()->set_relative_manifest_id(""); |
| EXPECT_FALSE(sync_bridge().IsEntityDataValid( |
| CreateSyncEntityData(app_id, entity_specifics))); |
| |
| EXPECT_THAT(histogram_tester.GetAllSamples("WebApp.Sync.InvalidEntity"), |
| base::BucketsAre( |
| base::Bucket(StorageKeyParseResult::kNoStartUrl, 1), |
| base::Bucket(StorageKeyParseResult::kInvalidStartUrl, 1), |
| base::Bucket(StorageKeyParseResult::kInvalidManifestId, 1))); |
| } |
| |
| // Test that a serialized proto with an unrecognized new field can successfully |
| // sync install in the current version and preserves the field value. |
| TEST_F(WebAppSyncBridgeTest, SpecificsProtoWithNewFieldPreserved) { |
| // Serialized in M125 by modifying web_app_specifics.proto: |
| // +optional string test_new_field = 5372767; |
| // Then: |
| // const char kAppName[] = "Test name"; |
| // sync_pb::WebAppSpecifics sync_proto; |
| // sync_proto.set_start_url(kStartUrl); |
| // sync_proto.set_name(kAppName); |
| // sync_proto.set_user_display_mode_default( |
| // sync_pb::WebAppSpecifics::BROWSER); |
| // sync_proto.set_test_new_field("hello"); |
| // sync_proto.SerializeAsString(); |
| const char kStartUrl[] = "https://example.com/launchurl"; |
| const std::string serialized_proto = { |
| 10, 29, 104, 116, 116, 112, 115, 58, 47, 47, 101, 120, 97, 109, |
| 112, 108, 101, 46, 99, 111, 109, 47, 108, 97, 117, 110, 99, 104, |
| 117, 114, 108, 18, 9, 84, 101, 115, 116, 32, 110, 97, 109, 101, |
| 24, 1, -6, -75, -65, 20, 5, 104, 101, 108, 108, 111}; |
| const GURL start_url = GURL(kStartUrl); |
| const webapps::AppId app_id = |
| GenerateAppId(/*manifest_id_path=*/std::nullopt, start_url); |
| |
| // Parse the proto. |
| sync_pb::WebAppSpecifics sync_proto; |
| bool parsed = sync_proto.ParseFromString(serialized_proto); |
| |
| // Sanity check the proto was parsed. |
| ASSERT_TRUE(parsed); |
| EXPECT_EQ(kStartUrl, sync_proto.start_url()); |
| |
| StartWebAppProvider(); |
| |
| // Listen for sync installs. |
| WebAppTestInstallObserver install_observer(profile()); |
| install_observer.BeginListening(); |
| |
| EXPECT_FALSE(sync_bridge().ApplyIncrementalSyncChanges( |
| sync_bridge().CreateMetadataChangeList(), |
| ToEntityChageList(app_id, sync_proto, sync_bridge()))); |
| |
| // Await sync install. |
| EXPECT_EQ(install_observer.Wait(), app_id); |
| |
| const WebApp* app = fake_provider().registrar_unsafe().GetAppById(app_id); |
| ASSERT_TRUE(app); |
| |
| // Clear the fields added due to normalizing the proto in `SetSyncProto` and |
| // `ApplySyncDataToApp`. |
| sync_pb::WebAppSpecifics result_proto = app->sync_proto(); |
| result_proto.clear_relative_manifest_id(); |
| #if BUILDFLAG(IS_CHROMEOS) |
| result_proto.clear_user_display_mode_cros(); |
| #endif |
| |
| // Check that the sync proto retained its value, including the unknown field. |
| EXPECT_EQ(result_proto.SerializeAsString(), serialized_proto); |
| } |
| |
| TEST_F(WebAppSyncBridgeTest, MigratePartiallyInstalledToCorrectStatus) { |
| AppsList initial_registry_apps = CreateAppsList("https://example.com/", 10); |
| for (auto& app : initial_registry_apps) { |
| app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION); |
| } |
| Registry registry; |
| InsertAppsListIntoRegistry(®istry, initial_registry_apps); |
| database_factory().WriteRegistry(registry); |
| StartWebAppProvider(); |
| |
| for (const webapps::AppId& app_id : registrar().GetAppIds()) { |
| EXPECT_EQ(registrar().GetAppById(app_id)->install_state(), |
| proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION); |
| } |
| } |
| |
| namespace { |
| using UserDisplayModeSplitParam = std::tuple< |
| std::optional<WebAppSpecifics_UserDisplayMode> /*sync_cros_udm*/, |
| std::optional<WebAppSpecifics_UserDisplayMode> /*sync_non_cros_udm*/, |
| std::optional<UserDisplayMode> /*installed_udm*/, |
| std::optional< |
| WebAppSpecifics_UserDisplayMode> /*local_other_platform_udm*/>; |
| |
| constexpr std::optional<WebAppSpecifics_UserDisplayMode> |
| kSyncUserDisplayModes[]{std::nullopt, |
| WebAppSpecifics_UserDisplayMode_UNSPECIFIED, |
| WebAppSpecifics_UserDisplayMode_BROWSER, |
| WebAppSpecifics_UserDisplayMode_STANDALONE}; |
| |
| constexpr std::optional<UserDisplayMode> kInstalledUserDisplayModes[]{ |
| std::nullopt, UserDisplayMode::kBrowser, UserDisplayMode::kStandalone}; |
| |
| std::string ToString(std::optional<WebAppSpecifics_UserDisplayMode> udm) { |
| if (!udm.has_value()) { |
| return "absent"; |
| } |
| switch (udm.value()) { |
| case WebAppSpecifics_UserDisplayMode_UNSPECIFIED: |
| return "unspecified"; |
| case WebAppSpecifics_UserDisplayMode_BROWSER: |
| return "browser"; |
| case WebAppSpecifics_UserDisplayMode_STANDALONE: |
| return "standalone"; |
| case WebAppSpecifics_UserDisplayMode_TABBED: |
| NOTREACHED(); |
| } |
| } |
| |
| std::string ToString(std::optional<UserDisplayMode> udm) { |
| if (!udm.has_value()) { |
| return "absent"; |
| } |
| switch (udm.value()) { |
| case UserDisplayMode::kBrowser: |
| return "browser"; |
| case UserDisplayMode::kStandalone: |
| return "standalone"; |
| case UserDisplayMode::kTabbed: |
| NOTREACHED(); |
| } |
| } |
| } // namespace |
| |
| class WebAppSyncBridgeTest_UserDisplayModeSplit |
| : public WebAppTest, |
| public testing::WithParamInterface<UserDisplayModeSplitParam> { |
| public: |
| static std::string ParamToString( |
| testing::TestParamInfo<UserDisplayModeSplitParam> param) { |
| return base::StrCat({ |
| "SyncCrosUdm_", |
| ToString(std::get<0>(param.param)), |
| "_SyncNonCrosUdm_", |
| ToString(std::get<1>(param.param)), |
| "_InstalledUdm_", |
| ToString(std::get<2>(param.param)), |
| "_OtherPlatformUdm_", |
| ToString(std::get<3>(param.param)), |
| }); |
| } |
| |
| WebAppSyncBridgeTest_UserDisplayModeSplit() { |
| #if BUILDFLAG(IS_CHROMEOS) |
| scoped_feature_list_.InitWithFeatureStates({ |
| // UDM mitigations mess with the installed local state, disable them so |
| // the state matches the intention of the test. |
| {kUserDisplayModeSyncBrowserMitigation, false}, |
| {kUserDisplayModeSyncStandaloneMitigation, false}, |
| }); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| } |
| |
| ~WebAppSyncBridgeTest_UserDisplayModeSplit() override = default; |
| |
| std::optional<WebAppSpecifics_UserDisplayMode> sync_cros_udm() const { |
| return std::get<0>(GetParam()); |
| } |
| std::optional<WebAppSpecifics_UserDisplayMode> sync_non_cros_udm() const { |
| return std::get<1>(GetParam()); |
| } |
| // UDM for the current platform. Absent means it's not locally installed. |
| std::optional<UserDisplayMode> installed_udm() const { |
| return std::get<2>(GetParam()); |
| } |
| // UDM stored locally for the other platform. |
| std::optional<WebAppSpecifics_UserDisplayMode> local_other_platform_udm() |
| const { |
| return std::get<3>(GetParam()); |
| } |
| |
| bool IsChromeOs() const { |
| #if BUILDFLAG(IS_CHROMEOS) |
| return true; |
| #else |
| return false; |
| #endif |
| } |
| |
| std::optional<WebAppSpecifics_UserDisplayMode> sync_current_platform_udm() |
| const { |
| return IsChromeOs() ? sync_cros_udm() : sync_non_cros_udm(); |
| } |
| std::optional<WebAppSpecifics_UserDisplayMode> sync_other_platform_udm() |
| const { |
| return IsChromeOs() ? sync_non_cros_udm() : sync_cros_udm(); |
| } |
| |
| bool installed_before_sync() const { return installed_udm().has_value(); } |
| |
| WebAppProvider& provider() { return *WebAppProvider::GetForTest(profile()); } |
| WebAppSyncBridge& sync_bridge() { return provider().sync_bridge_unsafe(); } |
| |
| UserDisplayMode ToMojomUdmFallbackToStandalone( |
| std::optional<WebAppSpecifics_UserDisplayMode> sync_udm) { |
| if (!sync_udm.has_value()) { |
| return mojom::UserDisplayMode::kStandalone; |
| } |
| return ToMojomUserDisplayMode(sync_udm.value()); |
| } |
| |
| protected: |
| void SetUp() override { |
| WebAppTest::SetUp(); |
| test::AwaitStartWebAppProviderAndSubsystems(profile()); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| TEST_P(WebAppSyncBridgeTest_UserDisplayModeSplit, SyncUpdateToUserDisplayMode) { |
| GURL start_url = GURL("https://example.com/app"); |
| webapps::AppId app_id = |
| GenerateAppId(/*manifest_id_path=*/std::nullopt, start_url); |
| |
| // Install an app. |
| if (installed_before_sync()) { |
| auto info = WebAppInstallInfo::CreateWithStartUrlForTesting(start_url); |
| info->scope = start_url; |
| info->title = u"Basic web app"; |
| info->description = u"Test description"; |
| info->user_display_mode = installed_udm().value(); |
| |
| webapps::AppId installed_app_id = |
| test::InstallWebApp(profile(), std::move(info)); |
| DCHECK_EQ(installed_app_id, app_id); |
| |
| // Set the local value of the other platform's UDM. |
| if (local_other_platform_udm()) { |
| ScopedRegistryUpdate update = sync_bridge().BeginUpdate(); |
| WebApp* web_app = update->UpdateApp(app_id); |
| DCHECK(web_app); |
| sync_pb::WebAppSpecifics sync_proto = web_app->sync_proto(); |
| |
| if (IsChromeOs()) { |
| sync_proto.set_user_display_mode_default( |
| local_other_platform_udm().value()); |
| } else { |
| sync_proto.set_user_display_mode_cros( |
| local_other_platform_udm().value()); |
| } |
| web_app->SetSyncProto(std::move(sync_proto)); |
| } |
| } |
| |
| // Listen for sync installs. |
| WebAppTestInstallObserver install_observer(profile()); |
| install_observer.BeginListening(); |
| |
| // Update web app in sync profile. |
| sync_pb::WebAppSpecifics sync_proto; |
| sync_proto.set_start_url(start_url.spec()); |
| sync_proto.set_scope(start_url.spec()); |
| sync_proto.set_relative_manifest_id("app"); |
| sync_proto.set_name("Basic web app"); |
| if (sync_cros_udm()) { |
| sync_proto.set_user_display_mode_cros(sync_cros_udm().value()); |
| } |
| if (sync_non_cros_udm()) { |
| sync_proto.set_user_display_mode_default(sync_non_cros_udm().value()); |
| } |
| |
| EXPECT_FALSE(sync_bridge().ApplyIncrementalSyncChanges( |
| sync_bridge().CreateMetadataChangeList(), |
| ToEntityChageList(app_id, sync_proto, sync_bridge()))); |
| |
| // Await sync install. |
| if (!installed_before_sync()) { |
| EXPECT_EQ(install_observer.Wait(), app_id); |
| } |
| |
| const WebApp* app = provider().registrar_unsafe().GetAppById(app_id); |
| ASSERT_TRUE(app); |
| |
| if (sync_current_platform_udm()) { |
| // If UDM is set for the current platform in sync, it should be used. |
| EXPECT_EQ(app->user_display_mode(), |
| ToMojomUdmFallbackToStandalone(sync_current_platform_udm())); |
| } else if (installed_before_sync()) { |
| // Otherwise we should preserve a local UDM value if available. |
| EXPECT_EQ(app->user_display_mode(), installed_udm()); |
| } else { |
| if (IsChromeOs()) { |
| // CrOS should populate a UDM value from non-CrOS, falling back to |
| // standalone. |
| EXPECT_EQ(app->user_display_mode(), |
| ToMojomUdmFallbackToStandalone(sync_other_platform_udm())); |
| } else { |
| // Non-CrOS should fall back to standalone. |
| EXPECT_EQ(app->user_display_mode(), UserDisplayMode::kStandalone); |
| } |
| } |
| |
| std::optional<sync_pb::WebAppSpecifics::UserDisplayMode> |
| app_this_platform_udm; |
| std::optional<sync_pb::WebAppSpecifics::UserDisplayMode> |
| app_other_platform_udm; |
| if (IsChromeOs()) { |
| if (app->sync_proto().has_user_display_mode_cros()) { |
| app_this_platform_udm = app->sync_proto().user_display_mode_cros(); |
| } |
| if (app->sync_proto().has_user_display_mode_default()) { |
| app_other_platform_udm = app->sync_proto().user_display_mode_default(); |
| } |
| } else { |
| if (app->sync_proto().has_user_display_mode_default()) { |
| app_this_platform_udm = app->sync_proto().user_display_mode_default(); |
| } |
| if (app->sync_proto().has_user_display_mode_cros()) { |
| app_other_platform_udm = app->sync_proto().user_display_mode_cros(); |
| } |
| } |
| EXPECT_EQ(app->user_display_mode(), |
| ToMojomUdmFallbackToStandalone(app_this_platform_udm)); |
| |
| if (sync_other_platform_udm()) { |
| // If UDM is set for the other platform in sync, it should be preserved |
| // (though Unspecified values currently become standalone). |
| EXPECT_EQ(app_other_platform_udm, sync_other_platform_udm()); |
| } else if (installed_before_sync()) { |
| // Otherwise, if installed, we should preserve a local UDM value (or unset). |
| EXPECT_EQ(app_other_platform_udm, local_other_platform_udm()); |
| } else { |
| // Otherwise, it should remain unset. |
| EXPECT_EQ(app_other_platform_udm, std::nullopt); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| /*no prefix*/, |
| WebAppSyncBridgeTest_UserDisplayModeSplit, |
| testing::Combine( |
| /*sync_cros_udm=*/testing::ValuesIn(kSyncUserDisplayModes), |
| /*sync_non_cros_udm=*/testing::ValuesIn(kSyncUserDisplayModes), |
| /*installed_udm=*/testing::ValuesIn(kInstalledUserDisplayModes), |
| /*local_other_platform_udm=*/ |
| testing::ValuesIn(kSyncUserDisplayModes)), |
| WebAppSyncBridgeTest_UserDisplayModeSplit::ParamToString); |
| |
| } // namespace web_app |