| // Copyright 2018 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 "chrome/browser/web_applications/externally_managed_app_manager.h" |
| |
| #include <algorithm> |
| #include <sstream> |
| #include <vector> |
| |
| #include "base/callback_helpers.h" |
| #include "base/containers/flat_map.h" |
| #include "base/containers/flat_set.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/web_applications/externally_installed_web_app_prefs.h" |
| #include "chrome/browser/web_applications/test/fake_externally_managed_app_manager.h" |
| #include "chrome/browser/web_applications/test/fake_web_app_registry_controller.h" |
| #include "chrome/browser/web_applications/test/web_app_test.h" |
| #include "chrome/browser/web_applications/test/web_app_test_utils.h" |
| #include "chrome/browser/web_applications/user_display_mode.h" |
| #include "chrome/browser/web_applications/web_app.h" |
| #include "chrome/browser/web_applications/web_app_helpers.h" |
| #include "chrome/browser/web_applications/web_app_registrar.h" |
| #include "chrome/common/chrome_features.h" |
| #include "components/webapps/browser/install_result_code.h" |
| |
| namespace web_app { |
| |
| class ExternallyManagedAppManagerTest |
| : public WebAppTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| ExternallyManagedAppManagerTest() { |
| bool enable_migration = GetParam(); |
| if (enable_migration) { |
| scoped_feature_list_.InitWithFeatures( |
| {features::kUseWebAppDBInsteadOfExternalPrefs}, {}); |
| } else { |
| scoped_feature_list_.InitWithFeatures( |
| {}, {features::kUseWebAppDBInsteadOfExternalPrefs}); |
| } |
| } |
| |
| protected: |
| void SetUp() override { |
| WebAppTest::SetUp(); |
| fake_registry_controller_ = |
| std::make_unique<FakeWebAppRegistryController>(); |
| controller().SetUp(profile()); |
| |
| externally_installed_app_prefs_ = |
| std::make_unique<ExternallyInstalledWebAppPrefs>(profile()->GetPrefs()); |
| externally_managed_app_manager_ = |
| std::make_unique<FakeExternallyManagedAppManager>(profile()); |
| |
| externally_managed_app_manager().SetSubsystems(&app_registrar(), nullptr, |
| nullptr, nullptr, nullptr); |
| externally_managed_app_manager().SetHandleInstallRequestCallback( |
| base::BindLambdaForTesting( |
| [this](const ExternalInstallOptions& install_options) |
| -> ExternallyManagedAppManager::InstallResult { |
| const GURL& install_url = install_options.install_url; |
| if (!app_registrar().GetAppById(GenerateAppId( |
| /*manifest_id=*/absl::nullopt, install_url))) { |
| std::unique_ptr<WebApp> web_app = |
| test::CreateWebApp(install_url, WebAppManagement::kDefault); |
| web_app->AddInstallURLToManagementExternalConfigMap( |
| WebAppManagement::kDefault, install_url); |
| controller().RegisterApp(std::move(web_app)); |
| |
| externally_installed_app_prefs().Insert( |
| install_url, |
| GenerateAppId(/*manifest_id=*/absl::nullopt, install_url), |
| install_options.install_source); |
| ++deduped_install_count_; |
| } |
| return ExternallyManagedAppManager::InstallResult( |
| webapps::InstallResultCode::kSuccessNewInstall); |
| })); |
| externally_managed_app_manager().SetHandleUninstallRequestCallback( |
| base::BindLambdaForTesting( |
| [this](const GURL& app_url, |
| ExternalInstallSource install_source) -> bool { |
| absl::optional<AppId> app_id = |
| app_registrar().LookupExternalAppId(app_url); |
| if (app_id) { |
| controller().UnregisterApp(*app_id); |
| deduped_uninstall_count_++; |
| } |
| return true; |
| })); |
| |
| controller().Init(); |
| } |
| |
| void DestroyExternallyManagedAppManager() { |
| externally_managed_app_manager_.reset(); |
| } |
| |
| void Sync(const std::vector<GURL>& urls) { |
| ResetCounts(); |
| |
| std::vector<ExternalInstallOptions> install_options_list; |
| install_options_list.reserve(urls.size()); |
| for (const auto& url : urls) { |
| install_options_list.emplace_back( |
| url, UserDisplayMode::kStandalone, |
| ExternalInstallSource::kInternalDefault); |
| } |
| |
| base::RunLoop run_loop; |
| externally_managed_app_manager().SynchronizeInstalledApps( |
| std::move(install_options_list), |
| ExternalInstallSource::kInternalDefault, |
| base::BindLambdaForTesting( |
| [&run_loop, urls]( |
| std::map<GURL, ExternallyManagedAppManager::InstallResult> |
| install_results, |
| std::map<GURL, bool> uninstall_results) { run_loop.Quit(); })); |
| // Wait for SynchronizeInstalledApps to finish. |
| run_loop.Run(); |
| } |
| |
| void Expect(int deduped_install_count, |
| int deduped_uninstall_count, |
| const std::vector<GURL>& installed_app_urls) { |
| EXPECT_EQ(deduped_install_count, deduped_install_count_); |
| EXPECT_EQ(deduped_uninstall_count, deduped_uninstall_count_); |
| base::flat_map<AppId, base::flat_set<GURL>> apps = |
| app_registrar().GetExternallyInstalledApps( |
| ExternalInstallSource::kInternalDefault); |
| std::vector<GURL> urls; |
| for (const auto& it : apps) { |
| std::copy(it.second.begin(), it.second.end(), std::back_inserter(urls)); |
| } |
| |
| std::sort(urls.begin(), urls.end()); |
| EXPECT_EQ(installed_app_urls, urls); |
| } |
| |
| void ResetCounts() { |
| deduped_install_count_ = 0; |
| deduped_uninstall_count_ = 0; |
| } |
| |
| FakeWebAppRegistryController& controller() { |
| return *fake_registry_controller_; |
| } |
| |
| WebAppRegistrar& app_registrar() { return controller().registrar(); } |
| |
| ExternallyInstalledWebAppPrefs& externally_installed_app_prefs() { |
| return *externally_installed_app_prefs_; |
| } |
| |
| FakeExternallyManagedAppManager& externally_managed_app_manager() { |
| return *externally_managed_app_manager_; |
| } |
| |
| private: |
| int deduped_install_count_ = 0; |
| int deduped_uninstall_count_ = 0; |
| |
| std::unique_ptr<FakeWebAppRegistryController> fake_registry_controller_; |
| std::unique_ptr<ExternallyInstalledWebAppPrefs> |
| externally_installed_app_prefs_; |
| std::unique_ptr<FakeExternallyManagedAppManager> |
| externally_managed_app_manager_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Test that destroying ExternallyManagedAppManager during a synchronize call |
| // that installs an app doesn't crash. Regression test for |
| // https://crbug.com/962808 |
| TEST_P(ExternallyManagedAppManagerTest, DestroyDuringInstallInSynchronize) { |
| std::vector<ExternalInstallOptions> install_options_list; |
| install_options_list.emplace_back(GURL("https://foo.example"), |
| UserDisplayMode::kStandalone, |
| ExternalInstallSource::kInternalDefault); |
| install_options_list.emplace_back(GURL("https://bar.example"), |
| UserDisplayMode::kStandalone, |
| ExternalInstallSource::kInternalDefault); |
| |
| externally_managed_app_manager().SynchronizeInstalledApps( |
| std::move(install_options_list), ExternalInstallSource::kInternalDefault, |
| // ExternallyManagedAppManager gives no guarantees about whether its |
| // pending callbacks will be run or not when it gets destroyed. |
| base::DoNothing()); |
| DestroyExternallyManagedAppManager(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Test that destroying ExternallyManagedAppManager during a synchronize call |
| // that uninstalls an app doesn't crash. Regression test for |
| // https://crbug.com/962808 |
| TEST_P(ExternallyManagedAppManagerTest, DestroyDuringUninstallInSynchronize) { |
| // Install an app that will be uninstalled next. |
| { |
| std::vector<ExternalInstallOptions> install_options_list; |
| install_options_list.emplace_back(GURL("https://foo.example"), |
| UserDisplayMode::kStandalone, |
| ExternalInstallSource::kInternalDefault); |
| base::RunLoop run_loop; |
| externally_managed_app_manager().SynchronizeInstalledApps( |
| std::move(install_options_list), |
| ExternalInstallSource::kInternalDefault, |
| base::BindLambdaForTesting( |
| [&](std::map<GURL, ExternallyManagedAppManager::InstallResult> |
| install_results, |
| std::map<GURL, bool> uninstall_results) { run_loop.Quit(); })); |
| run_loop.Run(); |
| } |
| |
| externally_managed_app_manager().SynchronizeInstalledApps( |
| std::vector<ExternalInstallOptions>(), |
| ExternalInstallSource::kInternalDefault, |
| // ExternallyManagedAppManager gives no guarantees about whether its |
| // pending callbacks will be run or not when it gets destroyed. |
| base::DoNothing()); |
| DestroyExternallyManagedAppManager(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_P(ExternallyManagedAppManagerTest, SynchronizeInstalledApps) { |
| GURL a("https://a.example.com/"); |
| GURL b("https://b.example.com/"); |
| GURL c("https://c.example.com/"); |
| GURL d("https://d.example.com/"); |
| GURL e("https://e.example.com/"); |
| |
| Sync(std::vector<GURL>{a, b, d}); |
| Expect(3, 0, std::vector<GURL>{a, b, d}); |
| |
| Sync(std::vector<GURL>{b, e}); |
| Expect(1, 2, std::vector<GURL>{b, e}); |
| |
| Sync(std::vector<GURL>{e}); |
| Expect(0, 1, std::vector<GURL>{e}); |
| |
| Sync(std::vector<GURL>{c}); |
| Expect(1, 1, std::vector<GURL>{c}); |
| |
| Sync(std::vector<GURL>{e, a, d}); |
| Expect(3, 1, std::vector<GURL>{a, d, e}); |
| |
| Sync(std::vector<GURL>{c, a, b, d, e}); |
| Expect(2, 0, std::vector<GURL>{a, b, c, d, e}); |
| |
| Sync(std::vector<GURL>{}); |
| Expect(0, 5, std::vector<GURL>{}); |
| |
| // The remaining code tests duplicate inputs. |
| |
| Sync(std::vector<GURL>{b, a, b, c}); |
| Expect(3, 0, std::vector<GURL>{a, b, c}); |
| |
| Sync(std::vector<GURL>{e, a, e, e, e, a}); |
| Expect(1, 2, std::vector<GURL>{a, e}); |
| |
| Sync(std::vector<GURL>{b, c, d}); |
| Expect(3, 2, std::vector<GURL>{b, c, d}); |
| |
| Sync(std::vector<GURL>{a, a, a, a, a, a}); |
| Expect(1, 3, std::vector<GURL>{a}); |
| |
| Sync(std::vector<GURL>{}); |
| Expect(0, 1, std::vector<GURL>{}); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| using ExternallyManagedAppManagerTestAndroidSMS = |
| ExternallyManagedAppManagerTest; |
| // This test verifies that AndroidSMS is not uninstalled during the Syncing |
| // process. |
| TEST_P(ExternallyManagedAppManagerTestAndroidSMS, |
| SynchronizeAppsAndroidSMSTest) { |
| GURL android_sms_url1( |
| "https://messages-web.sandbox.google.com/web/authentication"); |
| GURL android_sms_url2("https://messages.google.com/web/authentication"); |
| GURL extra_url("https://extra.com/"); |
| |
| // Install all URLs first. |
| Sync(std::vector<GURL>{android_sms_url1, android_sms_url2, extra_url}); |
| Expect(/*deduped_install_count=*/3, /*deduped_uninstall_count=*/0, |
| std::vector<GURL>{extra_url, android_sms_url1, android_sms_url2}); |
| |
| // Assume that extra_url is the only URL desired. |
| // install_count = 0 as no new installs happen. |
| // uninstall_count = 0 as android sms URLs does not get uninstalled. |
| // Both android SMS URLs remain. |
| Sync(std::vector<GURL>{extra_url}); |
| Expect(/*deduped_install_count=*/0, /*deduped_uninstall_count=*/0, |
| std::vector<GURL>{extra_url, android_sms_url1, android_sms_url2}); |
| |
| // Assume that android_sms_url1 is only required. |
| // install_count = 0 as no new installs happen. |
| // uninstall_count = 1 as extra.com gets uninstalled. |
| // Both android SMS URLs remain. |
| Sync(std::vector<GURL>{android_sms_url1}); |
| Expect(/*deduped_install_count=*/0, /*deduped_uninstall_count=*/1, |
| std::vector<GURL>{android_sms_url1, android_sms_url2}); |
| |
| // Assume that no URL is required. |
| // install_count = 0 as no new installs happen. |
| // uninstall_count = 0 as android sms URLs does not get uninstalled. |
| // Both android SMS URLs remain. |
| Sync(std::vector<GURL>{}); |
| Expect(/*deduped_install_count=*/0, /*deduped_uninstall_count=*/0, |
| std::vector<GURL>{android_sms_url1, android_sms_url2}); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| ExternallyManagedAppManagerTestAndroidSMS, |
| ::testing::Values(false)); |
| |
| #endif |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| ExternallyManagedAppManagerTest, |
| ::testing::Bool()); |
| |
| } // namespace web_app |