|  | // Copyright 2018 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/externally_managed_app_manager.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <iterator> | 
|  | #include <optional> | 
|  | #include <sstream> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/containers/contains.h" | 
|  | #include "base/containers/flat_map.h" | 
|  | #include "base/containers/flat_set.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/functional/callback_helpers.h" | 
|  | #include "base/memory/raw_ptr.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/test/bind.h" | 
|  | #include "base/test/test_future.h" | 
|  | #include "chrome/browser/apps/app_service/app_service_proxy.h" | 
|  | #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" | 
|  | #include "chrome/browser/apps/app_service/publishers/app_publisher.h" | 
|  | #include "chrome/browser/web_applications/external_install_options.h" | 
|  | #include "chrome/browser/web_applications/externally_managed_app_manager.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/policy/web_app_policy_manager.h" | 
|  | #include "chrome/browser/web_applications/test/fake_web_app_provider.h" | 
|  | #include "chrome/browser/web_applications/test/fake_web_contents_manager.h" | 
|  | #include "chrome/browser/web_applications/test/web_app_install_test_utils.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/web_app.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_management_type.h" | 
|  | #include "chrome/browser/web_applications/web_app_provider.h" | 
|  | #include "chrome/browser/web_applications/web_app_registrar.h" | 
|  | #include "chrome/browser/web_applications/web_app_registry_update.h" | 
|  | #include "chrome/browser/web_applications/web_app_sync_bridge.h" | 
|  | #include "chrome/common/chrome_features.h" | 
|  | #include "components/services/app_service/public/cpp/app_types.h" | 
|  | #include "components/webapps/browser/install_result_code.h" | 
|  | #include "components/webapps/browser/web_contents/web_app_url_loader.h" | 
|  | #include "components/webapps/common/web_page_metadata.mojom.h" | 
|  | #include "testing/gmock/include/gmock/gmock.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h" | 
|  | #include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h" | 
|  | #include "url/gurl.h" | 
|  | #include "url/origin.h" | 
|  |  | 
|  | namespace web_app { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | int GetInstallCountsFromResult( | 
|  | const std::map<GURL, ExternallyManagedAppManager::InstallResult>& | 
|  | install_results, | 
|  | bool include_duplicates = false) { | 
|  | int count = 0; | 
|  | for (const auto& result : install_results) { | 
|  | if (result.second.code == webapps::InstallResultCode::kSuccessNewInstall || | 
|  | (include_duplicates && | 
|  | result.second.code == | 
|  | webapps::InstallResultCode::kSuccessAlreadyInstalled)) { | 
|  | count++; | 
|  | } | 
|  | } | 
|  | return count; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class ExternallyManagedAppManagerTest : public WebAppTest { | 
|  | public: | 
|  | ExternallyManagedAppManagerTest() = default; | 
|  |  | 
|  | protected: | 
|  | void SetUp() override { | 
|  | WebAppTest::SetUp(); | 
|  | provider_ = web_app::FakeWebAppProvider::Get(profile()); | 
|  | web_app::test::AwaitStartWebAppProviderAndSubsystems(profile()); | 
|  | } | 
|  |  | 
|  | void ForceSystemShutdown() { provider_->Shutdown(); } | 
|  |  | 
|  | void Sync(const std::vector<GURL>& urls, bool include_duplicates = false) { | 
|  | ResetCounts(); | 
|  |  | 
|  | std::vector<ExternalInstallOptions> install_options_list; | 
|  | install_options_list.reserve(urls.size()); | 
|  | for (const auto& url : urls) { | 
|  | install_options_list.emplace_back( | 
|  | url, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kInternalDefault); | 
|  | } | 
|  |  | 
|  | base::RunLoop run_loop; | 
|  | provider().externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | std::move(install_options_list), | 
|  | ExternalInstallSource::kInternalDefault, | 
|  | base::BindLambdaForTesting( | 
|  | [&](std::map<GURL, ExternallyManagedAppManager::InstallResult> | 
|  | install_results, | 
|  | std::map<GURL, webapps::UninstallResultCode> | 
|  | uninstall_results) { | 
|  | install_count_ = GetInstallCountsFromResult(install_results, | 
|  | include_duplicates); | 
|  | uninstall_count_ = uninstall_results.size(); | 
|  | run_loop.Quit(); | 
|  | })); | 
|  | // Wait for SynchronizeInstalledApps to finish. | 
|  | run_loop.Run(); | 
|  | } | 
|  |  | 
|  | void Expect(int install_count, | 
|  | int uninstall_count, | 
|  | const std::vector<GURL>& installed_app_urls) { | 
|  | EXPECT_EQ(install_count, install_count_); | 
|  | EXPECT_EQ(uninstall_count, uninstall_count_); | 
|  | base::flat_map<webapps::AppId, base::flat_set<GURL>> apps = | 
|  | app_registrar().GetExternallyInstalledApps( | 
|  | ExternalInstallSource::kInternalDefault); | 
|  | std::vector<GURL> urls; | 
|  | for (const auto& it : apps) { | 
|  | std::ranges::copy(it.second, std::back_inserter(urls)); | 
|  | } | 
|  |  | 
|  | std::sort(urls.begin(), urls.end()); | 
|  | EXPECT_EQ(installed_app_urls, urls); | 
|  | } | 
|  |  | 
|  | void ResetCounts() { | 
|  | install_count_ = 0; | 
|  | uninstall_count_ = 0; | 
|  | } | 
|  |  | 
|  | WebAppProvider& provider() { return *provider_; } | 
|  |  | 
|  | WebAppRegistrar& app_registrar() { return provider().registrar_unsafe(); } | 
|  |  | 
|  | private: | 
|  | int install_count_ = 0; | 
|  | int uninstall_count_ = 0; | 
|  |  | 
|  | raw_ptr<FakeWebAppProvider, DanglingUntriaged> provider_ = nullptr; | 
|  | }; | 
|  |  | 
|  | // Test that destroying ExternallyManagedAppManager during a synchronize call | 
|  | // that installs an app doesn't crash. Regression test for | 
|  | // https://crbug.com/962808 | 
|  | TEST_F(ExternallyManagedAppManagerTest, DestroyDuringInstallInSynchronize) { | 
|  | std::vector<ExternalInstallOptions> install_options_list; | 
|  | install_options_list.emplace_back(GURL("https://foo.example"), | 
|  | mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kInternalDefault); | 
|  | install_options_list.emplace_back(GURL("https://bar.example"), | 
|  | mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kInternalDefault); | 
|  |  | 
|  | provider().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()); | 
|  | ForceSystemShutdown(); | 
|  | 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_F(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"), | 
|  | mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kInternalDefault); | 
|  | base::RunLoop run_loop; | 
|  | provider().externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | std::move(install_options_list), | 
|  | ExternalInstallSource::kInternalDefault, | 
|  | base::BindLambdaForTesting( | 
|  | [&](std::map<GURL, ExternallyManagedAppManager::InstallResult> | 
|  | install_results, | 
|  | std::map<GURL, webapps::UninstallResultCode> | 
|  | uninstall_results) { run_loop.Quit(); })); | 
|  | run_loop.Run(); | 
|  | } | 
|  |  | 
|  | provider().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()); | 
|  | ForceSystemShutdown(); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | TEST_F(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/"); | 
|  |  | 
|  | FakeWebContentsManager& web_contents_manager = | 
|  | static_cast<FakeWebContentsManager&>(provider().web_contents_manager()); | 
|  | web_contents_manager.CreateBasicInstallPageState(a, a, a); | 
|  | web_contents_manager.CreateBasicInstallPageState(b, b, b); | 
|  | web_contents_manager.CreateBasicInstallPageState(c, c, c); | 
|  | web_contents_manager.CreateBasicInstallPageState(d, d, d); | 
|  | web_contents_manager.CreateBasicInstallPageState(e, e, e); | 
|  |  | 
|  | 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}, /*include_duplicates=*/true); | 
|  | Expect(3, 0, std::vector<GURL>{a, b, c}); | 
|  |  | 
|  | Sync(std::vector<GURL>{e, a, e, e, e, a}, /*include_duplicates=*/true); | 
|  | Expect(2, 2, std::vector<GURL>{a, e}); | 
|  |  | 
|  | Sync(std::vector<GURL>{b, c, d}, /*include_duplicates=*/true); | 
|  | Expect(3, 2, std::vector<GURL>{b, c, d}); | 
|  |  | 
|  | Sync(std::vector<GURL>{a, a, a, a, a, a}, /*include_duplicates=*/true); | 
|  | Expect(1, 3, std::vector<GURL>{a}); | 
|  |  | 
|  | Sync(std::vector<GURL>{}, /*include_duplicates=*/true); | 
|  | Expect(0, 1, std::vector<GURL>{}); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | using ::testing::ElementsAre; | 
|  | using ::testing::Eq; | 
|  | using ::testing::IsEmpty; | 
|  | using ::testing::UnorderedElementsAre; | 
|  |  | 
|  | std::unique_ptr<WebAppInstallInfo> GetWebAppInstallInfo(const GURL& url) { | 
|  | std::unique_ptr<WebAppInstallInfo> info = | 
|  | WebAppInstallInfo::CreateWithStartUrlForTesting(url); | 
|  | info->scope = url.GetWithoutFilename(); | 
|  | info->title = u"Web App"; | 
|  | return info; | 
|  | } | 
|  |  | 
|  | // Test harness that keep the system as real as possible. | 
|  | class ExternallyAppManagerTest : public WebAppTest { | 
|  | public: | 
|  | using InstallResults = std::map<GURL /*install_url*/, | 
|  | ExternallyManagedAppManager::InstallResult>; | 
|  | using UninstallResults = | 
|  | std::map<GURL /*install_url*/, webapps::UninstallResultCode>; | 
|  | using SynchronizeFuture = | 
|  | base::test::TestFuture<InstallResults, UninstallResults>; | 
|  | using InstallNowFuture = | 
|  | base::test::TestFuture<const GURL&, | 
|  | ExternallyManagedAppManager::InstallResult>; | 
|  |  | 
|  | ExternallyAppManagerTest() = default; | 
|  |  | 
|  | void SetUp() override { | 
|  | WebAppTest::SetUp(); | 
|  | // TODO(http://b/278922549): Disable the external management apps so we | 
|  | // don't compete with the policy app manager for our installs / | 
|  | // synchronization. | 
|  | web_app::test::AwaitStartWebAppProviderAndSubsystems(profile()); | 
|  | } | 
|  |  | 
|  | std::vector<ExternalInstallOptions> CreateExternalInstallOptionsFromTemplate( | 
|  | std::vector<GURL> install_urls, | 
|  | ExternalInstallSource source, | 
|  | std::optional<ExternalInstallOptions> template_options = std::nullopt) { | 
|  | std::vector<ExternalInstallOptions> output; | 
|  | std::ranges::transform( | 
|  | install_urls, std::back_inserter(output), | 
|  | [source, &template_options](const GURL& install_url) { | 
|  | ExternalInstallOptions options = template_options.value_or( | 
|  | ExternalInstallOptions(install_url, std::nullopt, source)); | 
|  | options.install_url = install_url; | 
|  | options.install_source = source; | 
|  | return options; | 
|  | }); | 
|  | return output; | 
|  | } | 
|  |  | 
|  | ExternalInstallOptions CreateExternalInstallOptionsWithAppInfo( | 
|  | GURL install_url, | 
|  | ExternalInstallSource source) { | 
|  | ExternalInstallOptions options(install_url, | 
|  | mojom::UserDisplayMode::kBrowser, source); | 
|  | options.app_info_factory = | 
|  | base::BindRepeating(&GetWebAppInstallInfo, install_url); | 
|  | return options; | 
|  | } | 
|  |  | 
|  | WebAppProvider& provider() { return *WebAppProvider::GetForTest(profile()); } | 
|  |  | 
|  | WebAppRegistrar& app_registrar() { return provider().registrar_unsafe(); } | 
|  |  | 
|  | ExternallyManagedAppManager& external_manager() { | 
|  | return provider().externally_managed_app_manager(); | 
|  | } | 
|  |  | 
|  | FakeWebContentsManager& web_contents_manager() { | 
|  | return static_cast<FakeWebContentsManager&>( | 
|  | provider().web_contents_manager()); | 
|  | } | 
|  | }; | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, NoNetworkNoPlaceholder) { | 
|  | const GURL kInstallUrl = GURL("https://www.example.com/install_url.html"); | 
|  |  | 
|  | // Not populating the `FakeWebContentsManager` means it treats the network as | 
|  | // non-functional / not available. | 
|  |  | 
|  | SynchronizeFuture result; | 
|  | external_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl}, ExternalInstallSource::kExternalPolicy), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  |  | 
|  | // Empty uninstall results. | 
|  | EXPECT_THAT(result.Get<UninstallResults>(), IsEmpty()); | 
|  |  | 
|  | // Install should have failed. | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results = | 
|  | result.Get<InstallResults>(); | 
|  | EXPECT_THAT(install_results, | 
|  | ElementsAre(std::make_pair( | 
|  | kInstallUrl, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kInstallURLLoadFailed)))); | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, SimpleInstall) { | 
|  | const GURL kStartUrl = GURL("https://www.example.com/index.html"); | 
|  | const GURL kInstallUrl = | 
|  | GURL("https://www.example.com/nested/install_url.html"); | 
|  | const GURL kManifestUrl = GURL("https://www.example.com/manifest.json"); | 
|  |  | 
|  | webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl, kStartUrl); | 
|  |  | 
|  | SynchronizeFuture result; | 
|  | external_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl}, ExternalInstallSource::kExternalPolicy), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  |  | 
|  | // Empty uninstall results. | 
|  | EXPECT_THAT(result.Get<UninstallResults>(), IsEmpty()); | 
|  |  | 
|  | // Install should succeed. | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results = | 
|  | result.Get<InstallResults>(); | 
|  | EXPECT_THAT( | 
|  | install_results, | 
|  | ElementsAre(std::make_pair( | 
|  | kInstallUrl, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)))); | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots. | 
|  | #if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | #define MAYBE_SimpleInstallWebAppInfo DISABLED_SimpleInstallWebAppInfo | 
|  | #else | 
|  | #define MAYBE_SimpleInstallWebAppInfo SimpleInstallWebAppInfo | 
|  | #endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | TEST_F(ExternallyAppManagerTest, MAYBE_SimpleInstallWebAppInfo) { | 
|  | const GURL kStartUrl = GURL("https://www.example.com/index.html"); | 
|  | const GURL kInstallUrl = | 
|  | GURL("https://www.example.com/nested/install_url.html"); | 
|  | const GURL kManifestUrl = GURL("https://www.example.com/manifest.json"); | 
|  |  | 
|  | webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl, kStartUrl); | 
|  |  | 
|  | InstallNowFuture future; | 
|  | external_manager().InstallNow( | 
|  | CreateExternalInstallOptionsWithAppInfo( | 
|  | kInstallUrl, ExternalInstallSource::kExternalPolicy), | 
|  | future.GetCallback()); | 
|  | ASSERT_TRUE(future.Wait()); | 
|  |  | 
|  | // Install should succeed. | 
|  | ExternallyManagedAppManager::InstallResult result = | 
|  | future.Get<ExternallyManagedAppManager::InstallResult>(); | 
|  | EXPECT_EQ(result, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)); | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots. | 
|  | #if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | #define MAYBE_MultipleInstallWebAppInfoInstallUrl \ | 
|  | DISABLED_MultipleInstallWebAppInfoInstallUrl | 
|  | #else | 
|  | #define MAYBE_MultipleInstallWebAppInfoInstallUrl \ | 
|  | MultipleInstallWebAppInfoInstallUrl | 
|  | #endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | TEST_F(ExternallyAppManagerTest, MAYBE_MultipleInstallWebAppInfoInstallUrl) { | 
|  | const GURL kStartUrl1 = GURL("https://www.example1.com/index.html"); | 
|  | const GURL kInstallUrl1 = | 
|  | GURL("https://www.example1.com/nested/install_url.html"); | 
|  | const GURL kManifestUrl1 = GURL("https://www.example1.com/manifest.json"); | 
|  |  | 
|  | const GURL kStartUrl2 = GURL("https://www.example2.com/index.html"); | 
|  | const GURL kInstallUrl2 = | 
|  | GURL("https://www.example2.com/nested/install_url.html"); | 
|  | const GURL kManifestUrl2 = GURL("https://www.example2.com/manifest.json"); | 
|  |  | 
|  | webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl1, kManifestUrl1, kStartUrl1); | 
|  |  | 
|  | webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl2, kManifestUrl2, kStartUrl2); | 
|  |  | 
|  | InstallNowFuture future1; | 
|  | InstallNowFuture future2; | 
|  | external_manager().InstallNow( | 
|  | CreateExternalInstallOptionsWithAppInfo( | 
|  | kInstallUrl1, ExternalInstallSource::kExternalDefault), | 
|  | future1.GetCallback()); | 
|  | external_manager().InstallNow( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl2}, ExternalInstallSource::kExternalPolicy)[0], | 
|  | future2.GetCallback()); | 
|  | ASSERT_TRUE(future1.Wait()); | 
|  | ASSERT_TRUE(future2.Wait()); | 
|  |  | 
|  | // Both installs should succeed. | 
|  | ExternallyManagedAppManager::InstallResult result1 = | 
|  | future1.Get<ExternallyManagedAppManager::InstallResult>(); | 
|  | ExternallyManagedAppManager::InstallResult result2 = | 
|  | future2.Get<ExternallyManagedAppManager::InstallResult>(); | 
|  |  | 
|  | EXPECT_EQ(result1, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id1)); | 
|  | EXPECT_EQ(result2, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id2)); | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots. | 
|  | #if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | #define MAYBE_InstallWebAppInfoTwiceAlreadyInstalled \ | 
|  | DISABLED_InstallWebAppInfoTwiceAlreadyInstalled | 
|  | #else | 
|  | #define MAYBE_InstallWebAppInfoTwiceAlreadyInstalled \ | 
|  | InstallWebAppInfoTwiceAlreadyInstalled | 
|  | #endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | TEST_F(ExternallyAppManagerTest, MAYBE_InstallWebAppInfoTwiceAlreadyInstalled) { | 
|  | const GURL kStartUrl = GURL("https://www.example.com/index.html"); | 
|  | const GURL kInstallUrl = | 
|  | GURL("https://www.example.com/nested/install_url.html"); | 
|  | const GURL kManifestUrl = GURL("https://www.example.com/manifest.json"); | 
|  |  | 
|  | webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl, kStartUrl); | 
|  |  | 
|  | InstallNowFuture future1; | 
|  | InstallNowFuture future2; | 
|  | external_manager().Install( | 
|  | CreateExternalInstallOptionsWithAppInfo( | 
|  | kInstallUrl, ExternalInstallSource::kExternalDefault), | 
|  | future1.GetCallback()); | 
|  | external_manager().Install( | 
|  | CreateExternalInstallOptionsWithAppInfo( | 
|  | kInstallUrl, ExternalInstallSource::kExternalDefault), | 
|  | future2.GetCallback()); | 
|  | ASSERT_TRUE(future1.Wait()); | 
|  | ASSERT_TRUE(future2.Wait()); | 
|  |  | 
|  | // Both installs should succeed, with the 2nd one returning a | 
|  | // `kSuccessAlreadyInstalled` result. | 
|  | ExternallyManagedAppManager::InstallResult result1 = | 
|  | future1.Get<ExternallyManagedAppManager::InstallResult>(); | 
|  | ExternallyManagedAppManager::InstallResult result2 = | 
|  | future2.Get<ExternallyManagedAppManager::InstallResult>(); | 
|  |  | 
|  | EXPECT_EQ(result1, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)); | 
|  | EXPECT_EQ(result2, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessAlreadyInstalled, app_id)); | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots. | 
|  | #if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | #define MAYBE_InstallOptionsTwiceForReinstall \ | 
|  | DISABLED_InstallOptionsTwiceForReinstall | 
|  | #else | 
|  | #define MAYBE_InstallOptionsTwiceForReinstall InstallOptionsTwiceForReinstall | 
|  | #endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | TEST_F(ExternallyAppManagerTest, MAYBE_InstallOptionsTwiceForReinstall) { | 
|  | const GURL kStartUrl = GURL("https://www.example.com/index.html"); | 
|  | const GURL kInstallUrl = | 
|  | GURL("https://www.example.com/nested/install_url.html"); | 
|  | const GURL kManifestUrl = GURL("https://www.example.com/manifest.json"); | 
|  |  | 
|  | webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl, kStartUrl); | 
|  |  | 
|  | ExternalInstallOptions options = CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl}, ExternalInstallSource::kExternalPolicy)[0]; | 
|  | options.force_reinstall = true; | 
|  |  | 
|  | InstallNowFuture future1; | 
|  | InstallNowFuture future2; | 
|  | external_manager().Install(options, future1.GetCallback()); | 
|  | external_manager().Install(options, future2.GetCallback()); | 
|  | ASSERT_TRUE(future1.Wait()); | 
|  | ASSERT_TRUE(future2.Wait()); | 
|  |  | 
|  | // Both installs should succeed, with the 2nd one returning a | 
|  | // `kSuccessNewInstall` result because of the `force_reinstall` flag. | 
|  | ExternallyManagedAppManager::InstallResult result1 = | 
|  | future1.Get<ExternallyManagedAppManager::InstallResult>(); | 
|  | ExternallyManagedAppManager::InstallResult result2 = | 
|  | future2.Get<ExternallyManagedAppManager::InstallResult>(); | 
|  |  | 
|  | EXPECT_EQ(result1, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)); | 
|  | EXPECT_EQ(result2, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)); | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots. | 
|  | #if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | #define MAYBE_InstallReentrantCallback DISABLED_InstallReentrantCallback | 
|  | #else | 
|  | #define MAYBE_InstallReentrantCallback InstallReentrantCallback | 
|  | #endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | TEST_F(ExternallyAppManagerTest, MAYBE_InstallReentrantCallback) { | 
|  | const GURL kStartUrl1 = GURL("https://www.example1.com/index.html"); | 
|  | const GURL kInstallUrl1 = | 
|  | GURL("https://www.example1.com/nested/install_url.html"); | 
|  | const GURL kManifestUrl1 = GURL("https://www.example1.com/manifest.json"); | 
|  |  | 
|  | const GURL kStartUrl2 = GURL("https://www.example2.com/index.html"); | 
|  | const GURL kInstallUrl2 = | 
|  | GURL("https://www.example2.com/nested/install_url.html"); | 
|  | const GURL kManifestUrl2 = GURL("https://www.example2.com/manifest.json"); | 
|  |  | 
|  | webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl1, kManifestUrl1, kStartUrl1); | 
|  |  | 
|  | webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl2, kManifestUrl2, kStartUrl2); | 
|  |  | 
|  | InstallNowFuture inside_future; | 
|  | ExternallyManagedAppManager::InstallResult external_result; | 
|  | external_manager().InstallNow( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl1}, ExternalInstallSource::kExternalPolicy)[0], | 
|  | base::BindLambdaForTesting( | 
|  | [&](const GURL& url, | 
|  | ExternallyManagedAppManager::InstallResult result) { | 
|  | // Verify the first installation has succeeded. | 
|  | EXPECT_EQ( | 
|  | result, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id1)); | 
|  | external_manager().InstallNow( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl2}, ExternalInstallSource::kExternalPolicy)[0], | 
|  | inside_future.GetCallback()); | 
|  | })); | 
|  | ASSERT_TRUE(inside_future.Wait()); | 
|  |  | 
|  | // The intermediary install inside the callback should succeed. | 
|  | ExternallyManagedAppManager::InstallResult result = | 
|  | inside_future.Get<ExternallyManagedAppManager::InstallResult>(); | 
|  | EXPECT_EQ(result, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id2)); | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots. | 
|  | #if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | #define MAYBE_MultipleAppsInstallSerial DISABLED_MultipleAppsInstallSerial | 
|  | #else | 
|  | #define MAYBE_MultipleAppsInstallSerial MultipleAppsInstallSerial | 
|  | #endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | TEST_F(ExternallyAppManagerTest, MAYBE_MultipleAppsInstallSerial) { | 
|  | const GURL kStartUrl1 = GURL("https://www.example.com/index1.html"); | 
|  | const GURL kInstallUrl1 = | 
|  | GURL("https://www.example.com/nested/install_url1.html"); | 
|  | const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json"); | 
|  |  | 
|  | webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl1, kManifestUrl1, kStartUrl1); | 
|  |  | 
|  | SynchronizeFuture result1; | 
|  | external_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl1}, ExternalInstallSource::kExternalPolicy), | 
|  | ExternalInstallSource::kExternalPolicy, result1.GetCallback()); | 
|  | ASSERT_TRUE(result1.Wait()); | 
|  |  | 
|  | // Empty uninstall results. | 
|  | EXPECT_THAT(result1.Get<UninstallResults>(), IsEmpty()); | 
|  |  | 
|  | // Install should succeed. | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results1 = | 
|  | result1.Get<InstallResults>(); | 
|  | EXPECT_THAT( | 
|  | install_results1, | 
|  | ElementsAre(std::make_pair( | 
|  | kInstallUrl1, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id1)))); | 
|  |  | 
|  | const GURL kStartUrl2 = GURL("https://www.example2.com/index.html"); | 
|  | const GURL kInstallUrl2 = | 
|  | GURL("https://www.example2.com/nested/install_url.html"); | 
|  | const GURL kManifestUrl2 = GURL("https://www.example2.com/manifest.json"); | 
|  |  | 
|  | webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl2, kManifestUrl2, kStartUrl2); | 
|  |  | 
|  | SynchronizeFuture result2; | 
|  | external_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl2}, ExternalInstallSource::kExternalPolicy), | 
|  | ExternalInstallSource::kExternalPolicy, result2.GetCallback()); | 
|  | ASSERT_TRUE(result2.Wait()); | 
|  |  | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results2 = | 
|  | result2.Get<InstallResults>(); | 
|  | EXPECT_THAT( | 
|  | install_results2, | 
|  | ElementsAre(std::make_pair( | 
|  | kInstallUrl2, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id2)))); | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots. | 
|  | #if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | #define MAYBE_PlaceholderAppWindowsClosed DISABLED_PlaceholderAppWindowsClosed | 
|  | #else | 
|  | #define MAYBE_PlaceholderAppWindowsClosed PlaceholderAppWindowsClosed | 
|  | #endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) | 
|  | TEST_F(ExternallyAppManagerTest, MAYBE_PlaceholderAppWindowsClosed) { | 
|  | const GURL kInstallUrl = GURL("https://www.example.com/install_url.html"); | 
|  | ExternalInstallOptions template_options( | 
|  | kInstallUrl, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kExternalPolicy); | 
|  | template_options.install_placeholder = true; | 
|  |  | 
|  | SynchronizeFuture result; | 
|  | external_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl}, ExternalInstallSource::kExternalPolicy, | 
|  | template_options), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  |  | 
|  | // The webapps::AppId should be created from the install url. | 
|  | webapps::AppId app_id = | 
|  | GenerateAppId(/*manifest_id_path=*/std::nullopt, kInstallUrl); | 
|  |  | 
|  | // Install should succeed. | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results = | 
|  | result.Get<InstallResults>(); | 
|  | EXPECT_THAT( | 
|  | install_results, | 
|  | ElementsAre(std::make_pair( | 
|  | kInstallUrl, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)))); | 
|  |  | 
|  | const WebApp* app = provider().registrar_unsafe().GetAppById(app_id); | 
|  |  | 
|  | ASSERT_TRUE(app); | 
|  | EXPECT_THAT(app->management_to_external_config_map(), | 
|  | ElementsAre(std::make_pair(WebAppManagement::kPolicy, | 
|  | WebApp::ExternalManagementConfig( | 
|  | /*is_placeholder=*/true, | 
|  | /*install_urls=*/{kInstallUrl}, | 
|  | /*additional_policy_ids=*/{})))); | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, TwoInstallUrlsSameApp) { | 
|  | const GURL kStartUrl = GURL("https://www.example.com/index.html"); | 
|  | const GURL kInstallUrl1 = | 
|  | GURL("https://www.example.com/nested/install_url.html"); | 
|  | const GURL kInstallUrl2 = | 
|  | GURL("https://www.example.com/nested/install_url2.html"); | 
|  | const GURL kManifestUrl = GURL("https://www.example.com/manifest.json"); | 
|  |  | 
|  | webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl1, kManifestUrl, kStartUrl); | 
|  | webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl2, kManifestUrl, kStartUrl); | 
|  | EXPECT_EQ(app_id, app_id2); | 
|  |  | 
|  | SynchronizeFuture result; | 
|  | external_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl1, kInstallUrl2}, ExternalInstallSource::kExternalPolicy), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  |  | 
|  | // Empty uninstall results. | 
|  | EXPECT_THAT(result.Get<UninstallResults>(), IsEmpty()); | 
|  |  | 
|  | // Installs should have both succeeded. | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results = | 
|  | result.Get<InstallResults>(); | 
|  | EXPECT_THAT( | 
|  | install_results, | 
|  | UnorderedElementsAre( | 
|  | std::make_pair( | 
|  | kInstallUrl1, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)), | 
|  | std::make_pair( | 
|  | kInstallUrl2, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)))); | 
|  |  | 
|  | EXPECT_EQ(app_registrar().GetAppIds().size(), 1ul); | 
|  | const WebApp* app = app_registrar().GetAppById(app_id); | 
|  | ASSERT_TRUE(app); | 
|  | EXPECT_THAT(app->management_to_external_config_map(), | 
|  | ElementsAre(std::make_pair( | 
|  | WebAppManagement::kPolicy, | 
|  | WebApp::ExternalManagementConfig( | 
|  | /*is_placeholder=*/false, | 
|  | /*install_urls=*/{kInstallUrl1, kInstallUrl2}, | 
|  | /*additional_policy_ids=*/{})))); | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, RemovingInstallUrlsFromSource) { | 
|  | const GURL kStartUrl = GURL("https://www.example.com/index.html"); | 
|  | const GURL kInstallUrl1 = | 
|  | GURL("https://www.example.com/nested/install_url.html"); | 
|  | const GURL kInstallUrl2 = | 
|  | GURL("https://www.example.com/nested/install_url2.html"); | 
|  | const GURL kManifestUrl = GURL("https://www.example.com/manifest.json"); | 
|  |  | 
|  | webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl1, kManifestUrl, kStartUrl); | 
|  | webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl2, kManifestUrl, kStartUrl); | 
|  | EXPECT_EQ(app_id, app_id2); | 
|  |  | 
|  | // Synchronize with 2 install URLs. | 
|  | { | 
|  | SynchronizeFuture result; | 
|  | provider().externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl1, kInstallUrl2}, | 
|  | ExternalInstallSource::kExternalPolicy), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  |  | 
|  | // Empty uninstall results. | 
|  | EXPECT_THAT(result.Get<UninstallResults>(), IsEmpty()); | 
|  |  | 
|  | // Installs should have both succeeded. | 
|  | EXPECT_THAT( | 
|  | result.Get<InstallResults>(), | 
|  | UnorderedElementsAre( | 
|  | std::make_pair( | 
|  | kInstallUrl1, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)), | 
|  | std::make_pair( | 
|  | kInstallUrl2, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)))); | 
|  |  | 
|  | EXPECT_EQ(app_registrar().GetAppIds().size(), 1ul); | 
|  | const WebApp* app = app_registrar().GetAppById(app_id); | 
|  | ASSERT_TRUE(app); | 
|  | EXPECT_THAT(app->management_to_external_config_map(), | 
|  | ElementsAre(std::make_pair( | 
|  | WebAppManagement::kPolicy, | 
|  | WebApp::ExternalManagementConfig( | 
|  | /*is_placeholder=*/false, | 
|  | /*install_urls=*/{kInstallUrl1, kInstallUrl2}, | 
|  | /*additional_policy_ids=*/{})))); | 
|  | } | 
|  |  | 
|  | // Synchronize with 1 install URL. | 
|  | { | 
|  | SynchronizeFuture result; | 
|  | provider().externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl1}, ExternalInstallSource::kExternalPolicy), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  |  | 
|  | // Empty install results. | 
|  | EXPECT_THAT(result.Get<InstallResults>(), | 
|  | UnorderedElementsAre(std::make_pair( | 
|  | kInstallUrl1, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessAlreadyInstalled, | 
|  | app_id)))); | 
|  |  | 
|  | // One install URL uninstalled. | 
|  | EXPECT_THAT( | 
|  | result.Get<UninstallResults>(), | 
|  | UnorderedElementsAre(std::make_pair( | 
|  | kInstallUrl2, webapps::UninstallResultCode::kInstallUrlRemoved))); | 
|  |  | 
|  | EXPECT_EQ(app_registrar().GetAppIds().size(), 1ul); | 
|  | const WebApp* app = app_registrar().GetAppById(app_id); | 
|  | ASSERT_TRUE(app); | 
|  | EXPECT_THAT(app->management_to_external_config_map(), | 
|  | ElementsAre(std::make_pair(WebAppManagement::kPolicy, | 
|  | WebApp::ExternalManagementConfig( | 
|  | /*is_placeholder=*/false, | 
|  | /*install_urls=*/{kInstallUrl1}, | 
|  | /*additional_policy_ids=*/{})))); | 
|  | } | 
|  |  | 
|  | // Synchronize with 0 install URLs. | 
|  | { | 
|  | SynchronizeFuture result; | 
|  | provider().externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {}, ExternalInstallSource::kExternalPolicy), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  |  | 
|  | // Empty install results. | 
|  | EXPECT_THAT(result.Get<InstallResults>(), IsEmpty()); | 
|  |  | 
|  | // One install URL uninstalled. | 
|  | EXPECT_THAT(result.Get<UninstallResults>(), | 
|  | UnorderedElementsAre(std::make_pair( | 
|  | kInstallUrl1, webapps::UninstallResultCode::kAppRemoved))); | 
|  |  | 
|  | // App should be cleaned up. | 
|  | EXPECT_EQ(app_registrar().GetAppIds().size(), 0ul); | 
|  | const WebApp* app = app_registrar().GetAppById(app_id); | 
|  | ASSERT_FALSE(app); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, InstallUrlChanges) { | 
|  | const GURL kStartUrl = GURL("https://www.example.com/index.html"); | 
|  | const GURL kInstallUrl = | 
|  | GURL("https://www.example.com/nested/install_url.html"); | 
|  | const GURL kInstallUrl2 = | 
|  | GURL("https://www.example.com/nested/install_url2.html"); | 
|  | const GURL kManifestUrl = GURL("https://www.example.com/manifest.json"); | 
|  |  | 
|  | webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl, kStartUrl); | 
|  | webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl2, kManifestUrl, kStartUrl); | 
|  | EXPECT_EQ(app_id, app_id2); | 
|  |  | 
|  | // First synchronize will install the app. | 
|  | { | 
|  | SynchronizeFuture result; | 
|  | external_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl}, ExternalInstallSource::kExternalPolicy), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  |  | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results = | 
|  | result.Get<InstallResults>(); | 
|  | EXPECT_THAT( | 
|  | install_results, | 
|  | ElementsAre(std::make_pair( | 
|  | kInstallUrl, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)))); | 
|  | } | 
|  |  | 
|  | // Second synchronize with a different install url should succeed and update | 
|  | // the install urls correctly. | 
|  | { | 
|  | SynchronizeFuture result; | 
|  | external_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl2}, ExternalInstallSource::kExternalPolicy), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results = | 
|  | result.Get<InstallResults>(); | 
|  | EXPECT_THAT( | 
|  | install_results, | 
|  | ElementsAre(std::make_pair( | 
|  | kInstallUrl2, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)))); | 
|  |  | 
|  | ASSERT_THAT(result.Get<UninstallResults>(), | 
|  | testing::UnorderedElementsAre(std::make_pair( | 
|  | kInstallUrl, webapps::UninstallResultCode::kAppRemoved))); | 
|  | } | 
|  |  | 
|  | const WebApp* app = provider().registrar_unsafe().GetAppById(app_id); | 
|  | ASSERT_TRUE(app); | 
|  | EXPECT_THAT(app->management_to_external_config_map(), | 
|  | ElementsAre(std::make_pair(WebAppManagement::kPolicy, | 
|  | WebApp::ExternalManagementConfig( | 
|  | /*is_placeholder=*/false, | 
|  | /*install_urls=*/{kInstallUrl2}, | 
|  | /*additional_policy_ids=*/{})))); | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, PolicyAppOverridesUserInstalledApp) { | 
|  | const GURL kStartUrl = GURL("https://www.example.com/index.html"); | 
|  | const GURL kInstallUrl = | 
|  | GURL("https://www.example.com/nested/install_url.html"); | 
|  | const GURL kManifestUrl = GURL("https://www.example.com/manifest.json"); | 
|  |  | 
|  | webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl, kStartUrl); | 
|  |  | 
|  | { | 
|  | // Install user app | 
|  | auto& install_page_state = | 
|  | web_contents_manager().GetOrCreatePageState(kInstallUrl); | 
|  | install_page_state.manifest_before_default_processing->short_name = | 
|  | u"Test user app"; | 
|  |  | 
|  | auto install_info = | 
|  | WebAppInstallInfo::CreateWithStartUrlForTesting(kStartUrl); | 
|  | install_info->title = u"Test user app"; | 
|  | std::optional<webapps::AppId> user_app_id = | 
|  | test::InstallWebApp(profile(), std::move(install_info)); | 
|  |  | 
|  | ASSERT_TRUE(user_app_id.has_value()); | 
|  | ASSERT_EQ(user_app_id.value(), app_id); | 
|  | ASSERT_TRUE(app_registrar().WasInstalledByUser(app_id)); | 
|  | ASSERT_FALSE(app_registrar().HasExternalApp(app_id)); | 
|  | ASSERT_EQ("Test user app", app_registrar().GetAppShortName(app_id)); | 
|  | } | 
|  | { | 
|  | // Install policy app | 
|  | auto& install_page_state = | 
|  | web_contents_manager().GetOrCreatePageState(kInstallUrl); | 
|  | install_page_state.manifest_before_default_processing->name = | 
|  | u"Test policy app"; | 
|  | install_page_state.manifest_before_default_processing->short_name = | 
|  | u"Test policy app"; | 
|  |  | 
|  | SynchronizeFuture result; | 
|  | provider().externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl}, ExternalInstallSource::kExternalPolicy), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results = | 
|  | result.Get<InstallResults>(); | 
|  | EXPECT_THAT( | 
|  | install_results, | 
|  | ElementsAre(std::make_pair( | 
|  | kInstallUrl, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)))); | 
|  | ASSERT_EQ("Test policy app", app_registrar().GetAppShortName(app_id)); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, NoNetworkWithPlaceholder) { | 
|  | const GURL kInstallUrl = GURL("https://www.example.com/install_url.html"); | 
|  | ExternalInstallOptions template_options( | 
|  | kInstallUrl, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kExternalPolicy); | 
|  | template_options.install_placeholder = true; | 
|  |  | 
|  | SynchronizeFuture result; | 
|  | external_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl}, ExternalInstallSource::kExternalPolicy, | 
|  | template_options), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  |  | 
|  | // The webapps::AppId should be created from the install url. | 
|  | webapps::AppId app_id = | 
|  | GenerateAppId(/*manifest_id=*/std::nullopt, kInstallUrl); | 
|  |  | 
|  | // Install should succeed. | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results = | 
|  | result.Get<InstallResults>(); | 
|  | EXPECT_THAT( | 
|  | install_results, | 
|  | ElementsAre(std::make_pair( | 
|  | kInstallUrl, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)))); | 
|  |  | 
|  | const WebApp* app = provider().registrar_unsafe().GetAppById(app_id); | 
|  |  | 
|  | ASSERT_TRUE(app); | 
|  | EXPECT_THAT(app->management_to_external_config_map(), | 
|  | ElementsAre(std::make_pair(WebAppManagement::kPolicy, | 
|  | WebApp::ExternalManagementConfig( | 
|  | /*is_placeholder=*/true, | 
|  | /*install_urls=*/{kInstallUrl}, | 
|  | /*additional_policy_ids=*/{})))); | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, RedirectInstallUrlPlaceholder) { | 
|  | const GURL kInstallUrl = GURL("https://www.example.com/install_url.html"); | 
|  | const GURL kRedirectToUrl = | 
|  | GURL("https://www.otherorigin.com/redirected.html"); | 
|  | ExternalInstallOptions template_options( | 
|  | kInstallUrl, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kExternalPolicy); | 
|  | template_options.install_placeholder = true; | 
|  |  | 
|  | // Verify that a redirection causes a placeholder app to be installed. | 
|  |  | 
|  | auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl); | 
|  | page_state.redirection_url = kRedirectToUrl; | 
|  |  | 
|  | SynchronizeFuture result; | 
|  | external_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl}, ExternalInstallSource::kExternalPolicy, | 
|  | template_options), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  |  | 
|  | // The webapps::AppId should be created from teh install url. | 
|  | webapps::AppId app_id = | 
|  | GenerateAppId(/*manifest_id=*/std::nullopt, kInstallUrl); | 
|  |  | 
|  | // Install should succeed. | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results = | 
|  | result.Get<InstallResults>(); | 
|  | EXPECT_THAT( | 
|  | install_results, | 
|  | ElementsAre(std::make_pair( | 
|  | kInstallUrl, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, app_id)))); | 
|  |  | 
|  | const WebApp* app = provider().registrar_unsafe().GetAppById(app_id); | 
|  | ASSERT_TRUE(app); | 
|  | EXPECT_THAT(app->management_to_external_config_map(), | 
|  | ElementsAre(std::make_pair(WebAppManagement::kPolicy, | 
|  | WebApp::ExternalManagementConfig( | 
|  | /*is_placeholder=*/true, | 
|  | /*install_urls=*/{kInstallUrl}, | 
|  | /*additional_policy_ids=*/{})))); | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, PlaceholderResolvedFromSynchronize) { | 
|  | const GURL kInstallUrl = GURL("https://www.example.com/install_url.html"); | 
|  | const GURL kRedirectToUrl = | 
|  | GURL("https://www.otherorigin.com/redirected.html"); | 
|  | const GURL kStartUrl = GURL("https://www.example.com/index.html"); | 
|  | const GURL kManifestUrl = GURL("https://www.example.com/manifest.json"); | 
|  |  | 
|  | ExternalInstallOptions template_options( | 
|  | kInstallUrl, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kExternalPolicy); | 
|  | template_options.install_placeholder = true; | 
|  |  | 
|  | auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl); | 
|  | page_state.redirection_url = kRedirectToUrl; | 
|  | { | 
|  | SynchronizeFuture result; | 
|  | provider().externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl}, ExternalInstallSource::kExternalPolicy, | 
|  | template_options), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  | } | 
|  |  | 
|  | webapps::AppId placeholder_app_id = | 
|  | GenerateAppId(/*manifest_id=*/std::nullopt, kInstallUrl); | 
|  |  | 
|  | auto app_ids = provider().registrar_unsafe().GetAppIds(); | 
|  | EXPECT_THAT(app_ids, ElementsAre(placeholder_app_id)); | 
|  |  | 
|  | // Replace the redirect with an app that resolves. | 
|  | webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl, kStartUrl); | 
|  |  | 
|  | // The placeholder app should be uninstalled & the real one installed. | 
|  | { | 
|  | SynchronizeFuture result; | 
|  | provider().externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl}, ExternalInstallSource::kExternalPolicy, | 
|  | template_options), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  | } | 
|  |  | 
|  | app_ids = provider().registrar_unsafe().GetAppIds(); | 
|  |  | 
|  | EXPECT_THAT(app_ids, ElementsAre(app_id)); | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, PlaceholderResolvedFromInstallNow) { | 
|  | const GURL kInstallUrl = GURL("https://www.example.com/install_url.html"); | 
|  | const GURL kRedirectToUrl = | 
|  | GURL("https://www.otherorigin.com/redirected.html"); | 
|  | const GURL kStartUrl = GURL("https://www.example.com/index.html"); | 
|  | const GURL kManifestUrl = GURL("https://www.example.com/manifest.json"); | 
|  |  | 
|  | ExternalInstallOptions template_options( | 
|  | kInstallUrl, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kExternalPolicy); | 
|  | template_options.install_placeholder = true; | 
|  |  | 
|  | auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl); | 
|  | page_state.redirection_url = kRedirectToUrl; | 
|  | { | 
|  | SynchronizeFuture result; | 
|  | provider().externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl}, ExternalInstallSource::kExternalPolicy, | 
|  | template_options), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  | } | 
|  |  | 
|  | webapps::AppId placeholder_app_id = | 
|  | GenerateAppId(/*manifest_id=*/std::nullopt, kInstallUrl); | 
|  |  | 
|  | auto app_ids = provider().registrar_unsafe().GetAppIds(); | 
|  | EXPECT_THAT(app_ids, ElementsAre(placeholder_app_id)); | 
|  |  | 
|  | // Replace the redirect with an app that resolves. | 
|  | webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl, kStartUrl); | 
|  |  | 
|  | ExternalInstallOptions options = template_options; | 
|  | options.install_url = kInstallUrl; | 
|  | options.placeholder_resolution_behavior = | 
|  | PlaceholderResolutionBehavior::kClose; | 
|  | InstallNowFuture install_future; | 
|  | provider().externally_managed_app_manager().InstallNow( | 
|  | std::move(options), install_future.GetCallback()); | 
|  | ASSERT_TRUE(install_future.Wait()); | 
|  |  | 
|  | app_ids = provider().registrar_unsafe().GetAppIds(); | 
|  | EXPECT_THAT(app_ids, ElementsAre(app_id)); | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, TwoAppsSameInstallUrlSameSourceInstallNow) { | 
|  | const GURL kInstallUrl = GURL("https://www.example.com/install_url.html"); | 
|  | const GURL kStartUrl1 = GURL("https://www.example.com/index1.html"); | 
|  | const GURL kStartUrl2 = GURL("https://www.example.com/index2.html"); | 
|  | const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json"); | 
|  | const GURL kManifestUrl2 = GURL("https://www.example.com/manifest2.json"); | 
|  |  | 
|  | ExternalInstallOptions template_options( | 
|  | kInstallUrl, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kExternalPolicy); | 
|  |  | 
|  | webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl1, kStartUrl1); | 
|  |  | 
|  | { | 
|  | ExternalInstallOptions options = template_options; | 
|  | options.install_url = kInstallUrl; | 
|  | base::test::TestFuture<const GURL&, | 
|  | ExternallyManagedAppManager::InstallResult> | 
|  | install_future; | 
|  | provider().externally_managed_app_manager().InstallNow( | 
|  | std::move(options), install_future.GetCallback()); | 
|  | ASSERT_TRUE(install_future.Wait()); | 
|  | } | 
|  |  | 
|  | auto app_ids = provider().registrar_unsafe().GetAppIds(); | 
|  | EXPECT_THAT(app_ids, ElementsAre(app_id1)); | 
|  |  | 
|  | webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl2, kStartUrl2); | 
|  |  | 
|  | { | 
|  | ExternalInstallOptions options = template_options; | 
|  | options.install_url = kInstallUrl; | 
|  | InstallNowFuture install_future; | 
|  | provider().externally_managed_app_manager().InstallNow( | 
|  | std::move(options), install_future.GetCallback()); | 
|  | ASSERT_TRUE(install_future.Wait()); | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/40264854): This keeps the original app, but perhaps | 
|  | // should install app_id2. | 
|  | app_ids = provider().registrar_unsafe().GetAppIds(); | 
|  | EXPECT_THAT(app_ids, ElementsAre(app_id1)); | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, TwoAppsSameInstallUrlTwoSourcesInstallNow) { | 
|  | const GURL kInstallUrl = GURL("https://www.example.com/install_url.html"); | 
|  | const GURL kStartUrl1 = GURL("https://www.example.com/index1.html"); | 
|  | const GURL kStartUrl2 = GURL("https://www.example.com/index2.html"); | 
|  | const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json"); | 
|  | const GURL kManifestUrl2 = GURL("https://www.example.com/manifest2.json"); | 
|  |  | 
|  | ExternalInstallOptions template_options( | 
|  | kInstallUrl, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kExternalPolicy); | 
|  |  | 
|  | webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl1, kStartUrl1); | 
|  |  | 
|  | { | 
|  | ExternalInstallOptions options = template_options; | 
|  | options.install_url = kInstallUrl; | 
|  | base::test::TestFuture<const GURL&, | 
|  | ExternallyManagedAppManager::InstallResult> | 
|  | install_future; | 
|  | provider().externally_managed_app_manager().InstallNow( | 
|  | std::move(options), install_future.GetCallback()); | 
|  | ASSERT_TRUE(install_future.Wait()); | 
|  | } | 
|  |  | 
|  | auto app_ids = provider().registrar_unsafe().GetAppIds(); | 
|  | EXPECT_THAT(app_ids, ElementsAre(app_id1)); | 
|  |  | 
|  | webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl2, kStartUrl2); | 
|  |  | 
|  | { | 
|  | ExternalInstallOptions options = template_options; | 
|  | options.install_url = kInstallUrl; | 
|  | options.install_source = ExternalInstallSource::kInternalDefault; | 
|  | InstallNowFuture install_future; | 
|  | provider().externally_managed_app_manager().InstallNow( | 
|  | std::move(options), install_future.GetCallback()); | 
|  | ASSERT_TRUE(install_future.Wait()); | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/40264854): Currently, this keeps the original app, | 
|  | // but we should eventually resolve all apps to app_id2. | 
|  | app_ids = provider().registrar_unsafe().GetAppIds(); | 
|  | EXPECT_THAT(app_ids, ElementsAre(app_id1)); | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, TwoAppsSameInstallUrlTwoSourcesSynchronize) { | 
|  | const GURL kInstallUrl = GURL("https://www.example.com/install_url.html"); | 
|  | const GURL kStartUrl1 = GURL("https://www.example.com/index1.html"); | 
|  | const GURL kStartUrl2 = GURL("https://www.example.com/index2.html"); | 
|  | const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json"); | 
|  | const GURL kManifestUrl2 = GURL("https://www.example.com/manifest2.json"); | 
|  |  | 
|  | ExternalInstallOptions template_options( | 
|  | kInstallUrl, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kExternalPolicy); | 
|  |  | 
|  | webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl1, kStartUrl1); | 
|  |  | 
|  | { | 
|  | SynchronizeFuture result; | 
|  | provider().externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl}, ExternalInstallSource::kExternalPolicy, | 
|  | template_options), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  | } | 
|  |  | 
|  | auto app_ids = provider().registrar_unsafe().GetAppIds(); | 
|  | EXPECT_THAT(app_ids, ElementsAre(app_id1)); | 
|  |  | 
|  | webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl, kManifestUrl2, kStartUrl2); | 
|  |  | 
|  | { | 
|  | SynchronizeFuture result; | 
|  | provider().externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl}, ExternalInstallSource::kInternalDefault, | 
|  | template_options), | 
|  | ExternalInstallSource::kInternalDefault, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/40264854): Currently this resolves to app_id1, but | 
|  | // should probably eventually resolve to app_id2. | 
|  | app_ids = provider().registrar_unsafe().GetAppIds(); | 
|  | EXPECT_THAT(app_ids, UnorderedElementsAre(app_id1)); | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, PlaceholderFixedBySecondInstallUrlInstallNow) { | 
|  | const GURL kInstallUrl1 = GURL("https://www.example.com/install_url1.html"); | 
|  | const GURL kInstallUrl2 = GURL("https://www.example.com/install_url2.html"); | 
|  | const GURL kManifestUrl = GURL("https://www.example.com/manifest1.json"); | 
|  |  | 
|  | ExternalInstallOptions template_options( | 
|  | kInstallUrl1, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kExternalPolicy); | 
|  | template_options.install_placeholder = true; | 
|  |  | 
|  | // The first install creates a placeholder at kInstallUrl1, as that redirects. | 
|  | // The second install doesn't redirect, and the app at kInstallUrl2 points to | 
|  | // kInstallUrl1 as it's start_url (basically - it's identity conflicts with | 
|  | // the placeholder app's default identity). | 
|  |  | 
|  | auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl1); | 
|  | page_state.redirection_url = | 
|  | GURL("https://www.otherorigin.com/redirect.html"); | 
|  |  | 
|  | webapps::AppId app_at_install_url = | 
|  | web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl2, kManifestUrl, kInstallUrl1); | 
|  |  | 
|  | { | 
|  | ExternalInstallOptions options = template_options; | 
|  | options.install_url = kInstallUrl1; | 
|  | InstallNowFuture install_future; | 
|  | provider().externally_managed_app_manager().InstallNow( | 
|  | std::move(options), install_future.GetCallback()); | 
|  | ASSERT_TRUE(install_future.Wait()); | 
|  | EXPECT_THAT( | 
|  | install_future.Get(), | 
|  | Eq(std::make_tuple(kInstallUrl1, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, | 
|  | app_at_install_url)))); | 
|  | } | 
|  |  | 
|  | { | 
|  | ExternalInstallOptions options = template_options; | 
|  | options.install_url = kInstallUrl2; | 
|  | base::test::TestFuture<const GURL&, | 
|  | ExternallyManagedAppManager::InstallResult> | 
|  | install_future; | 
|  | provider().externally_managed_app_manager().InstallNow( | 
|  | std::move(options), install_future.GetCallback()); | 
|  | ASSERT_TRUE(install_future.Wait()); | 
|  | EXPECT_THAT( | 
|  | install_future.Get(), | 
|  | Eq(std::make_tuple(kInstallUrl2, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, | 
|  | app_at_install_url)))); | 
|  | } | 
|  |  | 
|  | // The current implementation records placeholder information per-source, so | 
|  | // when the second install succeeds, it overrides the `is_placeholder` to | 
|  | // `false`, and thus the first install is considered fully installed now too. | 
|  | // This is in contrast to the behavior if the two installs are different | 
|  | // sources, which will (correctly?) evaluate the manifest served by the | 
|  | // install url of the first install, which could be a different app identity. | 
|  | const WebApp* app = | 
|  | provider().registrar_unsafe().GetAppById(app_at_install_url); | 
|  | ASSERT_TRUE(app); | 
|  | EXPECT_THAT(app->management_to_external_config_map(), | 
|  | ElementsAre(std::make_pair( | 
|  | WebAppManagement::kPolicy, | 
|  | WebApp::ExternalManagementConfig( | 
|  | /*is_placeholder=*/false, | 
|  | /*install_urls=*/{kInstallUrl1, kInstallUrl2}, | 
|  | /*additional_policy_ids=*/{})))); | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, | 
|  | PlaceholderFixedBySecondInstallUrlSynchronize) { | 
|  | const GURL kInstallUrl1 = GURL("https://www.example.com/install_url1.html"); | 
|  | const GURL kInstallUrl2 = GURL("https://www.example.com/install_url2.html"); | 
|  | const GURL kManifestUrl = GURL("https://www.example.com/manifest1.json"); | 
|  |  | 
|  | // The first install creates a placeholder at kInstallUrl1, as that redirects. | 
|  | // The second install doesn't redirect, and the app at kInstallUrl2 points to | 
|  | // kInstallUrl1 as it's start_url (basically - it's identity conflicts with | 
|  | // the placeholder app's default identity). | 
|  |  | 
|  | ExternalInstallOptions template_options( | 
|  | kInstallUrl1, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kExternalPolicy); | 
|  | template_options.install_placeholder = true; | 
|  |  | 
|  | auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl1); | 
|  | page_state.redirection_url = | 
|  | GURL("https://www.otherorigin.com/redirect.html"); | 
|  |  | 
|  | webapps::AppId app_at_install_url = | 
|  | web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl2, kManifestUrl, /*start_url=*/kInstallUrl1); | 
|  |  | 
|  | SynchronizeFuture result; | 
|  | provider().externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | CreateExternalInstallOptionsFromTemplate( | 
|  | {kInstallUrl1, kInstallUrl2}, ExternalInstallSource::kExternalPolicy, | 
|  | template_options), | 
|  | ExternalInstallSource::kExternalPolicy, result.GetCallback()); | 
|  | ASSERT_TRUE(result.Wait()); | 
|  |  | 
|  | // Install should succeed. | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results = | 
|  | result.Get<InstallResults>(); | 
|  | EXPECT_THAT( | 
|  | install_results, | 
|  | UnorderedElementsAre( | 
|  | std::make_pair(kInstallUrl1, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, | 
|  | app_at_install_url)), | 
|  | std::make_pair(kInstallUrl2, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, | 
|  | app_at_install_url)))); | 
|  |  | 
|  | // The current implementation records placeholder information per-source, so | 
|  | // when the second install succeeds, it overrides the `is_placeholder` to | 
|  | // `false`, and thus the first install is considered fully installed now too. | 
|  | const WebApp* app = | 
|  | provider().registrar_unsafe().GetAppById(app_at_install_url); | 
|  | ASSERT_TRUE(app); | 
|  | EXPECT_THAT(app->management_to_external_config_map(), | 
|  | ElementsAre(std::make_pair( | 
|  | WebAppManagement::kPolicy, | 
|  | WebApp::ExternalManagementConfig( | 
|  | /*is_placeholder=*/false, | 
|  | /*install_urls=*/{kInstallUrl1, kInstallUrl2}, | 
|  | /*additional_policy_ids=*/{})))); | 
|  | } | 
|  |  | 
|  | TEST_F(ExternallyAppManagerTest, PlaceholderFullInstallConflictCanUpdate) { | 
|  | // This test exists to test what happens when a placeholder install conflicts | 
|  | // with a another install from a different source, and then resolves to a | 
|  | // separate app entirely after the placeholder state is fixed. | 
|  |  | 
|  | // Phase 1 configuration with redirect: | 
|  | // - kInstallUrl1 will redirect & generate a placeholder | 
|  | // - kInstallUrl2 will serve a manifest with kInstallUrl1 as the start_url | 
|  |  | 
|  | // Phase 2 configuration - redirect removed | 
|  | // - kInstallUrl1 will serve a manifest with kStartUrl as the start_url. | 
|  |  | 
|  | // What happens when resolving the placeholder at kInstallUrl1? Currently this | 
|  | // updates so that kStartUrl1 is installed. | 
|  |  | 
|  | const GURL kInstallUrl1 = GURL("https://www.example.com/install_url1.html"); | 
|  | const GURL kInstallUrl2 = GURL("https://www.example.com/install_url2.html"); | 
|  | const GURL kStartUrl1 = GURL("https://www.example.com/index1.html"); | 
|  | const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json"); | 
|  | const GURL kManifestUrl2 = GURL("https://www.example.com/manifest2.json"); | 
|  |  | 
|  | ExternalInstallOptions template_options( | 
|  | kInstallUrl1, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kExternalPolicy); | 
|  | template_options.install_placeholder = true; | 
|  |  | 
|  | // Phase 1 state | 
|  | auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl1); | 
|  | page_state.redirection_url = | 
|  | GURL("https://www.otherorigin.com/redirect.html"); | 
|  |  | 
|  | webapps::AppId app_at_install_url = | 
|  | web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl2, kManifestUrl1, kInstallUrl1); | 
|  |  | 
|  | { | 
|  | ExternalInstallOptions options = template_options; | 
|  | options.install_url = kInstallUrl1; | 
|  | base::test::TestFuture<const GURL&, | 
|  | ExternallyManagedAppManager::InstallResult> | 
|  | install_future; | 
|  | provider().externally_managed_app_manager().InstallNow( | 
|  | std::move(options), install_future.GetCallback()); | 
|  | ASSERT_TRUE(install_future.Wait()); | 
|  | EXPECT_THAT( | 
|  | install_future.Get(), | 
|  | Eq(std::make_tuple(kInstallUrl1, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, | 
|  | app_at_install_url)))); | 
|  | } | 
|  |  | 
|  | { | 
|  | ExternalInstallOptions options(kInstallUrl2, | 
|  | mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kInternalDefault); | 
|  | base::test::TestFuture<const GURL&, | 
|  | ExternallyManagedAppManager::InstallResult> | 
|  | install_future; | 
|  | provider().externally_managed_app_manager().InstallNow( | 
|  | std::move(options), install_future.GetCallback()); | 
|  | ASSERT_TRUE(install_future.Wait()); | 
|  | EXPECT_THAT( | 
|  | install_future.Get(), | 
|  | Eq(std::make_tuple(kInstallUrl2, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, | 
|  | app_at_install_url)))); | 
|  | } | 
|  |  | 
|  | const WebApp* app = | 
|  | provider().registrar_unsafe().GetAppById(app_at_install_url); | 
|  | ASSERT_TRUE(app); | 
|  | EXPECT_THAT( | 
|  | app->management_to_external_config_map(), | 
|  | UnorderedElementsAre(std::make_pair(WebAppManagement::kPolicy, | 
|  | WebApp::ExternalManagementConfig( | 
|  | /*is_placeholder=*/true, | 
|  | /*install_urls=*/{kInstallUrl1}, | 
|  | /*additional_policy_ids=*/{})), | 
|  | std::make_pair(WebAppManagement::kDefault, | 
|  | WebApp::ExternalManagementConfig( | 
|  | /*is_placeholder=*/false, | 
|  | /*install_urls=*/{kInstallUrl2}, | 
|  | /*additional_policy_ids=*/{})))); | 
|  | // Phase 2 - undo the redirection, and point to start url. | 
|  | webapps::AppId app_at_start_url = | 
|  | web_contents_manager().CreateBasicInstallPageState( | 
|  | kInstallUrl1, kManifestUrl2, kStartUrl1); | 
|  |  | 
|  | { | 
|  | ExternalInstallOptions options = template_options; | 
|  | options.install_url = kInstallUrl1; | 
|  | base::test::TestFuture<const GURL&, | 
|  | ExternallyManagedAppManager::InstallResult> | 
|  | install_future; | 
|  | provider().externally_managed_app_manager().InstallNow( | 
|  | std::move(options), install_future.GetCallback()); | 
|  | ASSERT_TRUE(install_future.Wait()); | 
|  |  | 
|  | EXPECT_THAT( | 
|  | install_future.Get(), | 
|  | Eq(std::make_tuple(kInstallUrl1, | 
|  | ExternallyManagedAppManager::InstallResult( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, | 
|  | app_at_start_url)))); | 
|  | } | 
|  |  | 
|  | // This is the current behavior, but it could change if we decide that | 
|  | // 'placeholder' is a per-app state instead of a per-app-and-source state. | 
|  | auto app_ids = provider().registrar_unsafe().GetAppIds(); | 
|  | EXPECT_THAT(app_ids, | 
|  | UnorderedElementsAre(app_at_install_url, app_at_start_url)); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace web_app |