|  | // Copyright 2023 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "chrome/browser/web_applications/externally_managed_app_manager.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <optional> | 
|  |  | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/functional/callback_helpers.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/test/bind.h" | 
|  | #include "base/test/test_future.h" | 
|  | #include "build/build_config.h" | 
|  | #include "chrome/browser/notifications/notification_display_service_factory.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "chrome/browser/ui/browser.h" | 
|  | #include "chrome/browser/ui/browser_commands.h" | 
|  | #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h" | 
|  | #include "chrome/browser/ui/web_applications/web_app_browsertest_base.h" | 
|  | #include "chrome/browser/web_applications/external_install_options.h" | 
|  | #include "chrome/browser/web_applications/externally_managed_app_registration_task.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/test/external_app_registration_waiter.h" | 
|  | #include "chrome/browser/web_applications/test/web_app_icon_test_utils.h" | 
|  | #include "chrome/browser/web_applications/test/web_app_install_test_utils.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_helpers.h" | 
|  | #include "chrome/browser/web_applications/web_app_icon_generator.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/test/base/in_process_browser_test.h" | 
|  | #include "components/services/app_service/public/cpp/app_types.h" | 
|  | #include "components/webapps/browser/features.h" | 
|  | #include "components/webapps/browser/install_result_code.h" | 
|  | #include "content/public/browser/service_worker_context.h" | 
|  | #include "content/public/browser/storage_partition.h" | 
|  | #include "content/public/test/browser_test.h" | 
|  | #include "net/dns/mock_host_resolver.h" | 
|  | #include "net/http/http_status_code.h" | 
|  | #include "net/test/embedded_test_server/embedded_test_server.h" | 
|  | #include "third_party/skia/include/core/SkColor.h" | 
|  | #include "url/origin.h" | 
|  |  | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | #include "base/scoped_observation.h" | 
|  | #include "base/test/run_until.h" | 
|  | #include "base/test/test_future.h" | 
|  | #include "chrome/browser/notifications/notification_display_service.h" | 
|  | #include "chrome/browser/web_applications/policy/web_app_policy_constants.h" | 
|  | #include "chrome/browser/web_applications/policy/web_app_policy_manager.h" | 
|  | #include "chrome/common/pref_names.h" | 
|  | #include "components/prefs/pref_service.h" | 
|  | #include "testing/gmock/include/gmock/gmock.h" | 
|  | #include "ui/message_center/public/cpp/notification.h" | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | using testing::_; | 
|  | using testing::AllOf; | 
|  | using testing::Eq; | 
|  | using testing::Field; | 
|  | using testing::Property; | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | namespace web_app { | 
|  |  | 
|  | class ExternallyManagedAppManagerBrowserTest : public WebAppBrowserTestBase { | 
|  | public: | 
|  | std::unique_ptr<net::test_server::HttpResponse> SimulateRedirectHandler( | 
|  | const net::test_server::HttpRequest& request) { | 
|  | if (!simulate_redirect_) { | 
|  | // Fall back to default handlers. | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | auto response = std::make_unique<net::test_server::BasicHttpResponse>(); | 
|  | if (request.GetURL().spec().find("redirected") != std::string::npos) { | 
|  | response->set_code(net::HTTP_MOVED_PERMANENTLY); | 
|  | response->set_content("Redirect successful"); | 
|  | return response; | 
|  | } | 
|  |  | 
|  | std::string destination = request.GetURL().spec() + "/redirected"; | 
|  | response->set_code(net::HTTP_TEMPORARY_REDIRECT); | 
|  | response->set_content_type("text/html"); | 
|  | response->AddCustomHeader("Location", destination); | 
|  | response->AddCustomHeader("Access-Control-Allow-Origin", "*"); | 
|  | response->set_content(base::StringPrintf( | 
|  | "<!doctype html><p>Redirecting to %s", destination.c_str())); | 
|  | return response; | 
|  | } | 
|  |  | 
|  | protected: | 
|  | void SetUpOnMainThread() override { | 
|  | WebAppBrowserTestBase::SetUpOnMainThread(); | 
|  | // Allow different origins to be handled by the embedded_test_server. | 
|  | host_resolver()->AddRule("*", "127.0.0.1"); | 
|  | test::WaitUntilWebAppProviderAndSubsystemsReady(provider()); | 
|  | } | 
|  |  | 
|  | Profile* profile() { return browser()->profile(); } | 
|  |  | 
|  | WebAppRegistrar& registrar() { return provider()->registrar_unsafe(); } | 
|  |  | 
|  | WebAppProvider* provider() { return WebAppProvider::GetForTest(profile()); } | 
|  |  | 
|  | ExternallyManagedAppManager& externally_managed_app_manager() { | 
|  | return provider()->externally_managed_app_manager(); | 
|  | } | 
|  |  | 
|  | void InstallApp(ExternalInstallOptions install_options) { | 
|  | auto result = ExternallyManagedAppManagerInstall( | 
|  | profile(), std::move(install_options)); | 
|  | result_code_ = result.code; | 
|  | } | 
|  |  | 
|  | void CheckServiceWorkerStatus(const GURL& url, | 
|  | content::ServiceWorkerCapability status) { | 
|  | std::unique_ptr<content::WebContents> web_contents = | 
|  | content::WebContents::Create( | 
|  | content::WebContents::CreateParams(profile())); | 
|  | content::StoragePartition* storage_partition = | 
|  | web_contents->GetBrowserContext()->GetStoragePartition( | 
|  | web_contents->GetSiteInstance()); | 
|  | test::CheckServiceWorkerStatus(url, storage_partition, status); | 
|  | } | 
|  |  | 
|  | std::optional<webapps::InstallResultCode> result_code_; | 
|  | bool simulate_redirect_ = false; | 
|  | }; | 
|  |  | 
|  | // Basic integration test to make sure the whole flow works. Each step in the | 
|  | // flow is unit tested separately. | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | InstallSucceeds) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | GURL url(embedded_test_server()->GetURL("/banners/manifest_test_page.html")); | 
|  | InstallApp(CreateInstallOptions(url)); | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url); | 
|  | EXPECT_TRUE(app_id.has_value()); | 
|  | EXPECT_EQ("Manifest test app", registrar().GetAppShortName(app_id.value())); | 
|  | } | 
|  |  | 
|  | // If install URL redirects, install should still succeed. | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | InstallSucceedsWithRedirect) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | GURL start_url = | 
|  | embedded_test_server()->GetURL("/banners/manifest_test_page.html"); | 
|  | GURL install_url = | 
|  | embedded_test_server()->GetURL("/server-redirect?" + start_url.spec()); | 
|  | // TODO(crbug.com/381408483): Review usage of ExternalInstallOptions in tests. | 
|  | ExternalInstallOptions install_options( | 
|  | install_url, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kInternalDefault); | 
|  | InstallApp(install_options); | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> app_id = | 
|  | registrar().LookupExternalAppId(install_url); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  | EXPECT_EQ("Manifest test app", registrar().GetAppShortName(app_id.value())); | 
|  | // Same AppID should be in the registrar using start_url from the manifest. | 
|  | EXPECT_EQ(proto::INSTALLED_WITH_OS_INTEGRATION, | 
|  | registrar().GetInstallState(app_id.value())); | 
|  | std::optional<webapps::AppId> opt_app_id = | 
|  | registrar().FindBestAppWithUrlInScope( | 
|  | start_url, | 
|  | web_app::WebAppFilter::InstalledInOperatingSystemForTesting()); | 
|  | EXPECT_TRUE(opt_app_id.has_value()); | 
|  | EXPECT_EQ(*opt_app_id, app_id); | 
|  | } | 
|  |  | 
|  | // If install URL redirects, install should still succeed. | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | InstallSucceedsWithRedirectNoManifest) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | GURL final_url = | 
|  | embedded_test_server()->GetURL("/banners/no_manifest_test_page.html"); | 
|  | GURL install_url = | 
|  | embedded_test_server()->GetURL("/server-redirect?" + final_url.spec()); | 
|  | InstallApp(CreateInstallOptions(install_url)); | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> app_id = | 
|  | registrar().LookupExternalAppId(install_url); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  | EXPECT_EQ("Web app banner test page", | 
|  | registrar().GetAppShortName(app_id.value())); | 
|  | std::optional<webapps::AppId> opt_app_id = | 
|  | registrar().FindBestAppWithUrlInScope( | 
|  | final_url, | 
|  | web_app::WebAppFilter::InstalledInOperatingSystemForTesting()); | 
|  | ASSERT_EQ(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION, | 
|  | registrar().GetInstallState(*opt_app_id)); | 
|  | ASSERT_TRUE(opt_app_id.has_value()); | 
|  | EXPECT_EQ(*opt_app_id, app_id); | 
|  | EXPECT_EQ(registrar().GetAppStartUrl(*opt_app_id), final_url); | 
|  | } | 
|  |  | 
|  | // Installing a placeholder app with shortcuts should succeed. | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | PlaceholderInstallSucceedsWithShortcuts) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | GURL final_url = embedded_test_server()->GetURL( | 
|  | "other.origin.com", "/banners/manifest_test_page.html"); | 
|  | // Add a redirect to a different origin, so a placeholder is installed. | 
|  | GURL url( | 
|  | embedded_test_server()->GetURL("/server-redirect?" + final_url.spec())); | 
|  |  | 
|  | ExternalInstallOptions options = | 
|  | CreateInstallOptions(url, ExternalInstallSource::kExternalPolicy); | 
|  | options.install_placeholder = true; | 
|  | options.add_to_applications_menu = true; | 
|  | options.add_to_desktop = true; | 
|  | InstallApp(options); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  | EXPECT_TRUE( | 
|  | registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy)); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | UpdatePlaceholderSucceedsSameAppId) { | 
|  | embedded_test_server()->RegisterRequestHandler(base::BindRepeating( | 
|  | &ExternallyManagedAppManagerBrowserTest::SimulateRedirectHandler, | 
|  | base::Unretained(this))); | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | simulate_redirect_ = true; | 
|  | GURL url = embedded_test_server()->GetURL("/banners/manifest_test_page.html"); | 
|  | ExternalInstallOptions options = | 
|  | CreateInstallOptions(url, ExternalInstallSource::kExternalPolicy); | 
|  | options.install_placeholder = true; | 
|  | options.add_to_applications_menu = true; | 
|  | options.add_to_desktop = true; | 
|  | InstallApp(options); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  | EXPECT_TRUE( | 
|  | registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy)); | 
|  |  | 
|  | simulate_redirect_ = false; | 
|  | InstallApp(options); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> final_app_id = | 
|  | registrar().LookupExternalAppId(url); | 
|  | ASSERT_TRUE(final_app_id.has_value()); | 
|  | EXPECT_FALSE(registrar().IsPlaceholderApp(final_app_id.value(), | 
|  | WebAppManagement::kPolicy)); | 
|  | EXPECT_EQ(0, registrar().CountUserInstalledApps()); | 
|  | EXPECT_EQ(1u, registrar() | 
|  | .GetExternallyInstalledApps( | 
|  | ExternalInstallSource::kExternalPolicy) | 
|  | .size()); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | InstallPlaceholderAppWindowOpen) { | 
|  | embedded_test_server()->RegisterRequestHandler(base::BindRepeating( | 
|  | &ExternallyManagedAppManagerBrowserTest::SimulateRedirectHandler, | 
|  | base::Unretained(this))); | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | simulate_redirect_ = true; | 
|  | GURL url = embedded_test_server()->GetURL("/banners/manifest_test_page.html"); | 
|  | ExternalInstallOptions options = | 
|  | CreateInstallOptions(url, ExternalInstallSource::kExternalPolicy); | 
|  | options.install_placeholder = true; | 
|  | options.add_to_applications_menu = true; | 
|  | options.add_to_desktop = true; | 
|  | InstallApp(options); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  | EXPECT_TRUE( | 
|  | registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy)); | 
|  |  | 
|  | // Open an app window so that the placeholder resolution is delayed. | 
|  | Browser* app_browser = LaunchWebAppBrowser(app_id.value()); | 
|  | EXPECT_NE(nullptr, app_browser); | 
|  | options.placeholder_resolution_behavior = | 
|  | PlaceholderResolutionBehavior::kWaitForAppWindowsClosed; | 
|  |  | 
|  | base::test::TestFuture<const GURL&, | 
|  | ExternallyManagedAppManager::InstallResult> | 
|  | install_future; | 
|  | simulate_redirect_ = false; | 
|  | provider()->externally_managed_app_manager().Install( | 
|  | std::move(options), install_future.GetCallback()); | 
|  |  | 
|  | // The callback will not be run since there is an existing app window that is | 
|  | // open. | 
|  | EXPECT_FALSE(install_future.IsReady()); | 
|  | EXPECT_TRUE( | 
|  | registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy)); | 
|  |  | 
|  | // Once the app window is closed, placeholder resolution should happen. | 
|  | chrome::CloseWindow(app_browser); | 
|  | EXPECT_TRUE(install_future.Wait()); | 
|  |  | 
|  | EXPECT_EQ( | 
|  | webapps::InstallResultCode::kSuccessNewInstall, | 
|  | install_future.Get<ExternallyManagedAppManager::InstallResult>().code); | 
|  | std::optional<webapps::AppId> final_app_id = | 
|  | registrar().LookupExternalAppId(url); | 
|  | ASSERT_TRUE(final_app_id.has_value()); | 
|  | EXPECT_FALSE(registrar().IsPlaceholderApp(final_app_id.value(), | 
|  | WebAppManagement::kPolicy)); | 
|  | EXPECT_EQ(0, registrar().CountUserInstalledApps()); | 
|  | EXPECT_EQ(1u, registrar() | 
|  | .GetExternallyInstalledApps( | 
|  | ExternalInstallSource::kExternalPolicy) | 
|  | .size()); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | PlaceholderResolutionNoAppWindow) { | 
|  | embedded_test_server()->RegisterRequestHandler(base::BindRepeating( | 
|  | &ExternallyManagedAppManagerBrowserTest::SimulateRedirectHandler, | 
|  | base::Unretained(this))); | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | simulate_redirect_ = true; | 
|  | GURL url = embedded_test_server()->GetURL("/banners/manifest_test_page.html"); | 
|  | ExternalInstallOptions options = | 
|  | CreateInstallOptions(url, ExternalInstallSource::kExternalPolicy); | 
|  | options.install_placeholder = true; | 
|  | options.add_to_applications_menu = true; | 
|  | options.add_to_desktop = true; | 
|  | InstallApp(options); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  | EXPECT_TRUE( | 
|  | registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy)); | 
|  |  | 
|  | // Since no app windows are open, placeholders are updated instantly. | 
|  | options.placeholder_resolution_behavior = | 
|  | PlaceholderResolutionBehavior::kWaitForAppWindowsClosed; | 
|  | simulate_redirect_ = false; | 
|  | InstallApp(options); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> final_app_id = | 
|  | registrar().LookupExternalAppId(url); | 
|  | ASSERT_TRUE(final_app_id.has_value()); | 
|  | EXPECT_FALSE(registrar().IsPlaceholderApp(final_app_id.value(), | 
|  | WebAppManagement::kPolicy)); | 
|  | EXPECT_EQ(0, registrar().CountUserInstalledApps()); | 
|  | EXPECT_EQ(1u, registrar() | 
|  | .GetExternallyInstalledApps( | 
|  | ExternalInstallSource::kExternalPolicy) | 
|  | .size()); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | UpdatePlaceholderSucceedsDifferentAppIdFomStartUrl) { | 
|  | embedded_test_server()->RegisterRequestHandler(base::BindRepeating( | 
|  | &ExternallyManagedAppManagerBrowserTest::SimulateRedirectHandler, | 
|  | base::Unretained(this))); | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | simulate_redirect_ = true; | 
|  | GURL install_url = embedded_test_server()->GetURL( | 
|  | "/banners/manifest_with_start_url_test_page.html"); | 
|  | ExternalInstallOptions options = | 
|  | CreateInstallOptions(install_url, ExternalInstallSource::kExternalPolicy); | 
|  | options.install_placeholder = true; | 
|  | options.add_to_applications_menu = true; | 
|  | options.add_to_desktop = true; | 
|  | InstallApp(options); | 
|  |  | 
|  | const webapps::AppId placeholder_app_id = | 
|  | GenerateAppId(std::nullopt, install_url); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> app_id = | 
|  | registrar().LookupExternalAppId(install_url); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  | EXPECT_EQ(placeholder_app_id, *app_id); | 
|  | EXPECT_TRUE(registrar().IsPlaceholderApp(*app_id, WebAppManagement::kPolicy)); | 
|  |  | 
|  | simulate_redirect_ = false; | 
|  | InstallApp(options); | 
|  |  | 
|  | GURL start_url = embedded_test_server()->GetURL( | 
|  | "/banners/different_manifest_test_page.html"); | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  |  | 
|  | const webapps::AppId new_app_id = GenerateAppId(std::nullopt, start_url); | 
|  |  | 
|  | EXPECT_NE(new_app_id, placeholder_app_id); | 
|  | EXPECT_FALSE(registrar().IsInRegistrar(placeholder_app_id)); | 
|  | EXPECT_EQ(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION, | 
|  | registrar().GetInstallState(new_app_id)); | 
|  | EXPECT_FALSE( | 
|  | registrar().IsPlaceholderApp(new_app_id, WebAppManagement::kPolicy)); | 
|  | EXPECT_EQ(0, registrar().CountUserInstalledApps()); | 
|  | EXPECT_EQ(1u, registrar() | 
|  | .GetExternallyInstalledApps( | 
|  | ExternalInstallSource::kExternalPolicy) | 
|  | .size()); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | UpdatePlaceholderSucceedsDifferentAppIdFomManifestId) { | 
|  | embedded_test_server()->RegisterRequestHandler(base::BindRepeating( | 
|  | &ExternallyManagedAppManagerBrowserTest::SimulateRedirectHandler, | 
|  | base::Unretained(this))); | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | simulate_redirect_ = true; | 
|  | GURL install_url = embedded_test_server()->GetURL( | 
|  | "/banners/manifest_with_id_test_page.html"); | 
|  | ExternalInstallOptions options = | 
|  | CreateInstallOptions(install_url, ExternalInstallSource::kExternalPolicy); | 
|  | options.install_placeholder = true; | 
|  | options.add_to_applications_menu = true; | 
|  | options.add_to_desktop = true; | 
|  | InstallApp(options); | 
|  |  | 
|  | const webapps::AppId placeholder_app_id = | 
|  | GenerateAppId(std::nullopt, install_url); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> app_id = | 
|  | registrar().LookupExternalAppId(install_url); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  | EXPECT_EQ(placeholder_app_id, *app_id); | 
|  | EXPECT_TRUE(registrar().IsPlaceholderApp(*app_id, WebAppManagement::kPolicy)); | 
|  |  | 
|  | simulate_redirect_ = false; | 
|  | InstallApp(options); | 
|  |  | 
|  | GURL start_url = embedded_test_server()->GetURL("/banners/start"); | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  |  | 
|  | const webapps::AppId new_app_id = GenerateAppId("some_id", start_url); | 
|  |  | 
|  | EXPECT_NE(new_app_id, placeholder_app_id); | 
|  | EXPECT_FALSE(registrar().IsInRegistrar(placeholder_app_id)); | 
|  | EXPECT_EQ(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION, | 
|  | registrar().GetInstallState(new_app_id)); | 
|  | EXPECT_FALSE( | 
|  | registrar().IsPlaceholderApp(new_app_id, WebAppManagement::kPolicy)); | 
|  | EXPECT_EQ(0, registrar().CountUserInstalledApps()); | 
|  | EXPECT_EQ(1u, registrar() | 
|  | .GetExternallyInstalledApps( | 
|  | ExternalInstallSource::kExternalPolicy) | 
|  | .size()); | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | // Installing a placeholder app with a custom name should succeed. | 
|  | // This feature is ChromeOS-only. | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | PlaceholderInstallSucceedsWithCustomName) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | GURL final_url = embedded_test_server()->GetURL( | 
|  | "other.origin.com", "/banners/manifest_test_page.html"); | 
|  | // Add a redirect to a different origin, so a placeholder is installed. | 
|  | GURL url( | 
|  | embedded_test_server()->GetURL("/server-redirect?" + final_url.spec())); | 
|  | const std::string CUSTOM_NAME = "CUSTOM_NAME"; | 
|  |  | 
|  | ExternalInstallOptions options = | 
|  | CreateInstallOptions(url, ExternalInstallSource::kExternalPolicy); | 
|  | options.install_placeholder = true; | 
|  | options.add_to_applications_menu = true; | 
|  | options.add_to_desktop = true; | 
|  | options.override_name = CUSTOM_NAME; | 
|  | InstallApp(options); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  | EXPECT_TRUE( | 
|  | registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy)); | 
|  | EXPECT_EQ(CUSTOM_NAME, | 
|  | registrar().GetAppById(app_id.value())->untranslated_name()); | 
|  | } | 
|  |  | 
|  | // Installing a placeholder app with a custom icon should succeed. | 
|  | // This feature is ChromeOS-only. | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | PlaceholderInstallSucceedsWithCustomIcon) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | GURL final_url = embedded_test_server()->GetURL( | 
|  | "other.origin.com", "/banners/manifest_test_page.html"); | 
|  | // Add a redirect to a different origin, so a placeholder is installed. | 
|  | GURL app_url( | 
|  | embedded_test_server()->GetURL("/server-redirect?" + final_url.spec())); | 
|  | // 192 is chosen to not be part of web_app_icon_generator.h:SizesToGenerate(). | 
|  | GURL icon_url = embedded_test_server()->GetURL("/banners/192x192-green.png"); | 
|  | const SquareSizePx kIconSize = 192; | 
|  | const SkColor kIconColor = SK_ColorGREEN; | 
|  | const auto kGeneratedSizes = SizesToGenerate(); | 
|  | EXPECT_TRUE(kGeneratedSizes.find(kIconSize) == kGeneratedSizes.end()); | 
|  |  | 
|  | ExternalInstallOptions options = | 
|  | CreateInstallOptions(app_url, ExternalInstallSource::kExternalPolicy); | 
|  | options.install_placeholder = true; | 
|  | options.add_to_applications_menu = true; | 
|  | options.add_to_desktop = true; | 
|  | options.override_icon_url = icon_url; | 
|  | InstallApp(options); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> app_id = | 
|  | registrar().LookupExternalAppId(app_url); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  | EXPECT_TRUE( | 
|  | registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy)); | 
|  | SortedSizesPx downloaded_sizes = | 
|  | registrar().GetAppTrustedIconSizesFallbackToUntrusted(app_id.value()); | 
|  | EXPECT_EQ(1u + kGeneratedSizes.size(), downloaded_sizes.size()); | 
|  | EXPECT_TRUE(downloaded_sizes.find(kIconSize) != downloaded_sizes.end()); | 
|  | EXPECT_EQ(kIconColor, | 
|  | IconManagerReadAppIconPixel(provider()->icon_manager(), | 
|  | app_id.value(), kIconSize, 0, 0)); | 
|  | } | 
|  |  | 
|  | // This RequestHandler returns HTTP_NOT_FOUND the first time a URL containing | 
|  | // |relative_url| is requested, and behaves normally in all other cases. | 
|  | std::unique_ptr<net::test_server::HttpResponse> FailFirstRequest( | 
|  | const std::string& relative_url, | 
|  | const net::test_server::HttpRequest& request) { | 
|  | static bool first_run = true; | 
|  | if (first_run && | 
|  | request.GetURL().spec().find(relative_url) != std::string::npos) { | 
|  | first_run = false; | 
|  | auto not_found_response = | 
|  | std::make_unique<net::test_server::BasicHttpResponse>(); | 
|  | not_found_response->set_code(net::HTTP_NOT_FOUND); | 
|  | return std::move(not_found_response); | 
|  | } | 
|  | // Return nullptr to use the default handlers. | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | // Installing a placeholder app with a custom icon should succeed, even we have | 
|  | // to retry fetching the icon once. | 
|  | // This feature is ChromeOS-only. | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | PlaceholderInstallSucceedsWithCustomIconAfterRetry) { | 
|  | // Fail the first time that this URL is loaded. | 
|  | std::string kIconRelativeUrl = "/banners/192x192-green.png"; | 
|  | embedded_test_server()->RegisterRequestHandler( | 
|  | base::BindRepeating(&FailFirstRequest, kIconRelativeUrl)); | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | GURL final_url = embedded_test_server()->GetURL( | 
|  | "other.origin.com", "/banners/manifest_test_page.html"); | 
|  | // Add a redirect to a different origin, so a placeholder is installed. | 
|  | GURL app_url( | 
|  | embedded_test_server()->GetURL("/server-redirect?" + final_url.spec())); | 
|  | // 192 is chosen to not be part of web_app_icon_generator.h:SizesToGenerate(). | 
|  | GURL icon_url = embedded_test_server()->GetURL(kIconRelativeUrl); | 
|  |  | 
|  | const SquareSizePx kIconSize = 192; | 
|  | const SkColor kIconColor = SK_ColorGREEN; | 
|  | const auto kGeneratedSizes = SizesToGenerate(); | 
|  | EXPECT_TRUE(kGeneratedSizes.find(kIconSize) == kGeneratedSizes.end()); | 
|  |  | 
|  | ExternalInstallOptions options = | 
|  | CreateInstallOptions(app_url, ExternalInstallSource::kExternalPolicy); | 
|  | options.install_placeholder = true; | 
|  | options.add_to_applications_menu = true; | 
|  | options.add_to_desktop = true; | 
|  | options.override_icon_url = icon_url; | 
|  | InstallApp(options); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> app_id = | 
|  | registrar().LookupExternalAppId(app_url); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  | EXPECT_TRUE( | 
|  | registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy)); | 
|  | SortedSizesPx downloaded_sizes = | 
|  | registrar().GetAppTrustedIconSizesFallbackToUntrusted(app_id.value()); | 
|  | EXPECT_EQ(1u + kGeneratedSizes.size(), downloaded_sizes.size()); | 
|  | EXPECT_TRUE(downloaded_sizes.find(kIconSize) != downloaded_sizes.end()); | 
|  | EXPECT_EQ(kIconColor, | 
|  | IconManagerReadAppIconPixel(provider()->icon_manager(), | 
|  | app_id.value(), kIconSize, 0, 0)); | 
|  | } | 
|  |  | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | // Tests that the browser doesn't crash if it gets shutdown with a pending | 
|  | // installation. | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | ShutdownWithPendingInstallation) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | ExternalInstallOptions install_options = CreateInstallOptions( | 
|  | embedded_test_server()->GetURL("/banners/manifest_test_page.html")); | 
|  |  | 
|  | // Start an installation but don't wait for it to finish. | 
|  | provider()->externally_managed_app_manager().Install( | 
|  | std::move(install_options), base::DoNothing()); | 
|  |  | 
|  | // The browser should shutdown cleanly even if there is a pending | 
|  | // installation. | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, ForceReinstall) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | std::optional<webapps::AppId> app_id; | 
|  | { | 
|  | GURL url(embedded_test_server()->GetURL( | 
|  | "/banners/" | 
|  | "manifest_test_page.html?manifest=manifest_short_name_only.json")); | 
|  | ExternalInstallOptions install_options = CreateInstallOptions(url); | 
|  | install_options.force_reinstall = true; | 
|  | InstallApp(std::move(install_options)); | 
|  |  | 
|  | app_id = registrar().FindBestAppWithUrlInScope( | 
|  | url, web_app::WebAppFilter::InstalledInOperatingSystemForTesting()); | 
|  | EXPECT_TRUE(app_id.has_value()); | 
|  | EXPECT_EQ("Manifest", registrar().GetAppShortName(app_id.value())); | 
|  | } | 
|  | { | 
|  | GURL url( | 
|  | embedded_test_server()->GetURL("/banners/manifest_test_page.html")); | 
|  | ExternalInstallOptions install_options = CreateInstallOptions(url); | 
|  | install_options.force_reinstall = true; | 
|  | InstallApp(std::move(install_options)); | 
|  |  | 
|  | std::optional<webapps::AppId> new_app_id = | 
|  | registrar().FindBestAppWithUrlInScope( | 
|  | url, web_app::WebAppFilter::InstalledInOperatingSystemForTesting()); | 
|  | EXPECT_TRUE(new_app_id.has_value()); | 
|  | EXPECT_EQ(new_app_id, app_id); | 
|  | EXPECT_EQ("Manifest test app", | 
|  | registrar().GetAppShortName(new_app_id.value())); | 
|  | } | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | PolicyAppOverridesUserInstalledApp) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | std::optional<webapps::AppId> app_id; | 
|  | { | 
|  | // Install user app | 
|  | GURL url( | 
|  | embedded_test_server()->GetURL("/banners/" | 
|  | "manifest_test_page.html")); | 
|  | auto install_info = WebAppInstallInfo::CreateWithStartUrlForTesting(url); | 
|  | install_info->title = u"Test user app"; | 
|  | app_id = test::InstallWebApp(profile(), std::move(install_info)); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  | ASSERT_TRUE(registrar().WasInstalledByUser(app_id.value())); | 
|  | ASSERT_FALSE(registrar().HasExternalApp(app_id.value())); | 
|  | ASSERT_EQ("Test user app", registrar().GetAppShortName(app_id.value())); | 
|  | } | 
|  | { | 
|  | // Install policy app | 
|  | GURL url( | 
|  | embedded_test_server()->GetURL("/banners/manifest_test_page.html")); | 
|  | std::optional<webapps::AppId> policy_app_id = | 
|  | ForceInstallWebApp(profile(), url); | 
|  | ASSERT_EQ(policy_app_id, app_id); | 
|  | ASSERT_EQ("Manifest test app", | 
|  | registrar().GetAppShortName(policy_app_id.value())); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test that adding a manifest that points to a chrome:// URL does not actually | 
|  | // install a web app that points to a chrome:// URL. | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | InstallChromeURLFails) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | GURL url(embedded_test_server()->GetURL( | 
|  | "/banners/manifest_test_page.html?manifest=manifest_chrome_url.json")); | 
|  | InstallApp(CreateInstallOptions(url)); | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  |  | 
|  | // The installer falls back to installing a web app of the original URL. | 
|  | EXPECT_EQ(url, registrar().GetAppStartUrl(app_id.value())); | 
|  | EXPECT_NE(app_id, | 
|  | registrar().FindBestAppWithUrlInScope( | 
|  | GURL("chrome://settings"), | 
|  | web_app::WebAppFilter::InstalledInOperatingSystemForTesting())); | 
|  | } | 
|  |  | 
|  | // Test that adding a web app without a manifest while using the | 
|  | // |require_manifest| flag fails. | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | RequireManifestFailsIfNoManifest) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | GURL url( | 
|  | embedded_test_server()->GetURL("/banners/no_manifest_test_page.html")); | 
|  | ExternalInstallOptions install_options = CreateInstallOptions(url); | 
|  | install_options.require_manifest = true; | 
|  | InstallApp(std::move(install_options)); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kNotValidManifestForWebApp, | 
|  | result_code_.value()); | 
|  | std::optional<webapps::AppId> id = registrar().LookupExternalAppId(url); | 
|  | ASSERT_FALSE(id.has_value()); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | RegistrationSucceeds) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | // Delay service worker registration to second load to simulate it not loading | 
|  | // during the initial install pass. | 
|  | GURL install_url(embedded_test_server()->GetURL( | 
|  | "/web_apps/service_worker_on_second_load.html")); | 
|  |  | 
|  | ExternalInstallOptions install_options = CreateInstallOptions(install_url); | 
|  | InstallApp(std::move(install_options)); | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | ExternalAppRegistrationWaiter(&externally_managed_app_manager()) | 
|  | .AwaitNextRegistration(install_url, RegistrationResultCode::kSuccess); | 
|  | CheckServiceWorkerStatus( | 
|  | install_url, | 
|  | content::ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | RegistrationAlternateUrlSucceeds) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | GURL install_url( | 
|  | embedded_test_server()->GetURL("/web_apps/no_service_worker.html")); | 
|  | GURL registration_url = | 
|  | embedded_test_server()->GetURL("/web_apps/basic.html"); | 
|  |  | 
|  | ExternalInstallOptions install_options = CreateInstallOptions(install_url); | 
|  | install_options.service_worker_registration_url = registration_url; | 
|  | InstallApp(std::move(install_options)); | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | ExternalAppRegistrationWaiter(&externally_managed_app_manager()) | 
|  | .AwaitNextRegistration(registration_url, | 
|  | RegistrationResultCode::kSuccess); | 
|  | CheckServiceWorkerStatus( | 
|  | install_url, | 
|  | content::ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | RegistrationSkipped) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | // Delay service worker registration to second load to simulate it not loading | 
|  | // during the initial install pass. | 
|  | GURL install_url(embedded_test_server()->GetURL( | 
|  | "/web_apps/service_worker_on_second_load.html")); | 
|  |  | 
|  | ExternalInstallOptions install_options = CreateInstallOptions(install_url); | 
|  | install_options.load_and_await_service_worker_registration = false; | 
|  | ExternalAppRegistrationWaiter waiter(&externally_managed_app_manager()); | 
|  | InstallApp(std::move(install_options)); | 
|  | waiter.AwaitRegistrationsComplete(); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | CheckServiceWorkerStatus(install_url, | 
|  | content::ServiceWorkerCapability::NO_SERVICE_WORKER); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | ServiceWorkerRegistrationSkippedForChromeScheme) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | GURL install_url("chrome://web-app-internals/"); | 
|  |  | 
|  | ExternalInstallOptions install_options = CreateInstallOptions(install_url); | 
|  | install_options.load_and_await_service_worker_registration = false; | 
|  | ExternalAppRegistrationWaiter waiter(&externally_managed_app_manager()); | 
|  | InstallApp(std::move(install_options)); | 
|  | waiter.AwaitRegistrationsComplete(); | 
|  |  | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | CheckServiceWorkerStatus(install_url, | 
|  | content::ServiceWorkerCapability::NO_SERVICE_WORKER); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | AlreadyRegistered) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | // Ensure service worker registered for http://embedded_test_server/web_apps/. | 
|  | // We don't need to be installing a web app here but it's convenient just to | 
|  | // await the service worker registration. | 
|  | { | 
|  | GURL install_url(embedded_test_server()->GetURL("/web_apps/basic.html")); | 
|  | ExternalInstallOptions install_options = CreateInstallOptions(install_url); | 
|  | install_options.force_reinstall = true; | 
|  | InstallApp(std::move(install_options)); | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | ExternalAppRegistrationWaiter(&externally_managed_app_manager()) | 
|  | .AwaitNextNonFailedRegistration(install_url); | 
|  | CheckServiceWorkerStatus( | 
|  | embedded_test_server()->GetURL("/web_apps/basic.html"), | 
|  | content::ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER); | 
|  | } | 
|  |  | 
|  | // With the service worker registered we install a page that doesn't register | 
|  | // a service worker to check that the existing service worker is seen by our | 
|  | // service worker registration step. | 
|  | { | 
|  | GURL install_url( | 
|  | embedded_test_server()->GetURL("/web_apps/no_service_worker.html")); | 
|  | ExternalInstallOptions install_options = CreateInstallOptions(install_url); | 
|  | install_options.force_reinstall = true; | 
|  | InstallApp(std::move(install_options)); | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | ExternalAppRegistrationWaiter(&externally_managed_app_manager()) | 
|  | .AwaitNextRegistration(install_url, | 
|  | RegistrationResultCode::kAlreadyRegistered); | 
|  | } | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | CannotFetchManifest) { | 
|  | // With a flaky network connection, clients may request an app whose manifest | 
|  | // cannot currently be retrieved. The app display mode is then assumed to be | 
|  | // 'browser'. | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | const GURL app_url(embedded_test_server()->GetURL( | 
|  | "/banners/manifest_test_page.html?manifest=does_not_exist.json")); | 
|  |  | 
|  | std::vector<ExternalInstallOptions> desired_apps_install_options; | 
|  | { | 
|  | ExternalInstallOptions install_options( | 
|  | app_url, mojom::UserDisplayMode::kStandalone, | 
|  | ExternalInstallSource::kExternalPolicy); | 
|  | install_options.add_to_applications_menu = false; | 
|  | install_options.add_to_desktop = false; | 
|  | install_options.add_to_quick_launch_bar = false; | 
|  | install_options.require_manifest = false; | 
|  | desired_apps_install_options.push_back(std::move(install_options)); | 
|  | } | 
|  |  | 
|  | base::RunLoop run_loop; | 
|  | externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | std::move(desired_apps_install_options), | 
|  | ExternalInstallSource::kExternalPolicy, | 
|  | base::BindLambdaForTesting( | 
|  | [&run_loop, &app_url]( | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> | 
|  | install_results, | 
|  | std::map<GURL, webapps::UninstallResultCode> uninstall_results) { | 
|  | EXPECT_TRUE(uninstall_results.empty()); | 
|  | EXPECT_EQ(install_results.size(), 1U); | 
|  | EXPECT_EQ(install_results[app_url].code, | 
|  | webapps::InstallResultCode::kSuccessNewInstall); | 
|  | run_loop.Quit(); | 
|  | })); | 
|  | run_loop.Run(); | 
|  |  | 
|  | std::optional<webapps::AppId> app_id = registrar().FindBestAppWithUrlInScope( | 
|  | app_url, web_app::WebAppFilter::InstalledInOperatingSystemForTesting()); | 
|  | DCHECK(app_id.has_value()); | 
|  | EXPECT_EQ(registrar().GetAppDisplayMode(*app_id), DisplayMode::kBrowser); | 
|  | EXPECT_EQ(registrar().GetAppUserDisplayMode(*app_id), | 
|  | mojom::UserDisplayMode::kStandalone); | 
|  | EXPECT_EQ(registrar().GetAppEffectiveDisplayMode(*app_id), | 
|  | DisplayMode::kMinimalUi); | 
|  | EXPECT_FALSE(registrar().GetAppThemeColor(*app_id).has_value()); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | RegistrationTimeout) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | GURL url(embedded_test_server()->GetURL( | 
|  | "/banners/manifest_no_service_worker.html")); | 
|  | CheckServiceWorkerStatus(url, | 
|  | content::ServiceWorkerCapability::NO_SERVICE_WORKER); | 
|  |  | 
|  | ExternalInstallOptions install_options = CreateInstallOptions(url); | 
|  | install_options.service_worker_registration_timeout = base::Seconds(0); | 
|  | InstallApp(std::move(install_options)); | 
|  | EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  | ExternalAppRegistrationWaiter(&externally_managed_app_manager()) | 
|  | .AwaitNextRegistration(url, RegistrationResultCode::kTimeout); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, | 
|  | ReinstallPolicyAppWithLocallyInstalledApp) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | GURL url(embedded_test_server()->GetURL("/banners/manifest_test_page.html")); | 
|  |  | 
|  | // Install user app | 
|  | auto install_info = WebAppInstallInfo::CreateWithStartUrlForTesting(url); | 
|  | install_info->title = u"Test user app"; | 
|  | webapps::AppId app_id = | 
|  | test::InstallWebApp(profile(), std::move(install_info)); | 
|  | ASSERT_TRUE(registrar().WasInstalledByUser(app_id)); | 
|  | ASSERT_FALSE(registrar().HasExternalApp(app_id)); | 
|  |  | 
|  | // Install policy app | 
|  | std::optional<webapps::AppId> policy_app_id = | 
|  | ForceInstallWebApp(profile(), url); | 
|  | ASSERT_EQ(policy_app_id, app_id); | 
|  |  | 
|  | // Uninstall policy app | 
|  | std::vector<ExternalInstallOptions> desired_apps_install_options; | 
|  | base::RunLoop run_loop; | 
|  | externally_managed_app_manager().SynchronizeInstalledApps( | 
|  | std::move(desired_apps_install_options), | 
|  | ExternalInstallSource::kExternalPolicy, | 
|  | base::BindLambdaForTesting( | 
|  | [&run_loop, &url]( | 
|  | std::map<GURL, ExternallyManagedAppManager::InstallResult> | 
|  | install_results, | 
|  | std::map<GURL, webapps::UninstallResultCode> uninstall_results) { | 
|  | EXPECT_TRUE(install_results.empty()); | 
|  | EXPECT_EQ(uninstall_results.size(), 1U); | 
|  | EXPECT_EQ(uninstall_results[url], | 
|  | webapps::UninstallResultCode::kInstallSourceRemoved); | 
|  | run_loop.Quit(); | 
|  | })); | 
|  | run_loop.Run(); | 
|  | ASSERT_FALSE(registrar().GetAppById(app_id)->IsPolicyInstalledApp()); | 
|  |  | 
|  | // Reinstall policy app | 
|  | ForceInstallWebApp(profile(), url); | 
|  | ASSERT_TRUE(registrar().GetAppById(app_id)->IsPolicyInstalledApp()); | 
|  | } | 
|  |  | 
|  | class ExternallyManagedAppManagerBrowserTestShortcut | 
|  | : public ExternallyManagedAppManagerBrowserTest, | 
|  | public testing::WithParamInterface<bool> { | 
|  | public: | 
|  | ExternallyManagedAppManagerBrowserTestShortcut() = default; | 
|  | }; | 
|  |  | 
|  | // Tests behavior when ExternalInstallOptions.install_as_shortcut is enabled | 
|  | IN_PROC_BROWSER_TEST_P(ExternallyManagedAppManagerBrowserTestShortcut, | 
|  | InstallAsShortcut) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | GURL install_url( | 
|  | embedded_test_server()->GetURL("/web_apps/different_start_url.html")); | 
|  | GURL manifest_start_url( | 
|  | embedded_test_server()->GetURL("/web_apps/basic.html")); | 
|  |  | 
|  | ExternalInstallOptions options = | 
|  | CreateInstallOptions(install_url, ExternalInstallSource::kExternalPolicy); | 
|  | options.install_as_diy = GetParam(); | 
|  |  | 
|  | InstallApp(options); | 
|  | ASSERT_EQ(webapps::InstallResultCode::kSuccessNewInstall, | 
|  | result_code_.value()); | 
|  |  | 
|  | // The main difference between a normal web app installation and a shortcut | 
|  | // creation is that in the latter the start_url field of the page's manifest | 
|  | // is ignored. Thus the installation URL is always used even when the | 
|  | // manifest tells otherwise, as in the test page used here. | 
|  |  | 
|  | const bool startUrlIsInstallUrl = | 
|  | registrar().GetAppByStartUrl(install_url) != nullptr; | 
|  | const bool startUrlFromManifest = | 
|  | registrar().GetAppByStartUrl(manifest_start_url) != nullptr; | 
|  | EXPECT_NE(startUrlIsInstallUrl, startUrlFromManifest); | 
|  |  | 
|  | EXPECT_EQ(options.install_as_diy, startUrlIsInstallUrl); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P(All, | 
|  | ExternallyManagedAppManagerBrowserTestShortcut, | 
|  | ::testing::Bool()); | 
|  |  | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | class PlaceholderUpdateRelaunchBrowserTest | 
|  | : public ExternallyManagedAppManagerBrowserTest, | 
|  | public NotificationDisplayService::Observer { | 
|  | public: | 
|  | ~PlaceholderUpdateRelaunchBrowserTest() override { | 
|  | notification_observation_.Reset(); | 
|  | } | 
|  |  | 
|  | // NotificationDisplayService::Observer: | 
|  | MOCK_METHOD(void, | 
|  | OnNotificationDisplayed, | 
|  | (const message_center::Notification&, | 
|  | const NotificationCommon::Metadata* const), | 
|  | (override)); | 
|  | MOCK_METHOD(void, | 
|  | OnNotificationClosed, | 
|  | (const std::string& notification_id), | 
|  | (override)); | 
|  |  | 
|  | void OnNotificationDisplayServiceDestroyed( | 
|  | NotificationDisplayService* service) override { | 
|  | notification_observation_.Reset(); | 
|  | } | 
|  |  | 
|  | void AddForceInstalledApp(const std::string& manifest_id, | 
|  | const std::string& app_name) { | 
|  | base::test::TestFuture<void> app_sync_future; | 
|  | provider() | 
|  | ->policy_manager() | 
|  | .SetOnAppsSynchronizedCompletedCallbackForTesting( | 
|  | app_sync_future.GetCallback()); | 
|  | PrefService* prefs = profile()->GetPrefs(); | 
|  | base::Value::List install_force_list = | 
|  | prefs->GetList(prefs::kWebAppInstallForceList).Clone(); | 
|  | install_force_list.Append( | 
|  | base::Value::Dict() | 
|  | .Set(kUrlKey, manifest_id) | 
|  | .Set(kDefaultLaunchContainerKey, kDefaultLaunchContainerWindowValue) | 
|  | .Set(kFallbackAppNameKey, app_name)); | 
|  | profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList, | 
|  | std::move(install_force_list)); | 
|  | EXPECT_TRUE(app_sync_future.Wait()); | 
|  | } | 
|  |  | 
|  | void AddPreventCloseToApp(const std::string& manifest_id, | 
|  | const std::string& run_on_os_login) { | 
|  | base::test::TestFuture<void> policy_refresh_sync_future; | 
|  | provider() | 
|  | ->policy_manager() | 
|  | .SetRefreshPolicySettingsCompletedCallbackForTesting( | 
|  | policy_refresh_sync_future.GetCallback()); | 
|  | PrefService* prefs = profile()->GetPrefs(); | 
|  | base::Value::List web_app_settings = | 
|  | prefs->GetList(prefs::kWebAppSettings).Clone(); | 
|  | web_app_settings.Append(base::Value::Dict() | 
|  | .Set(kManifestId, manifest_id) | 
|  | .Set(kRunOnOsLogin, run_on_os_login) | 
|  | .Set(kPreventClose, true)); | 
|  | prefs->SetList(prefs::kWebAppSettings, std::move(web_app_settings)); | 
|  | EXPECT_TRUE(policy_refresh_sync_future.Wait()); | 
|  | } | 
|  |  | 
|  | void WaitForNumberOfAppInstances(const webapps::AppId& app_id, | 
|  | size_t number_of_app_instances) { | 
|  | ASSERT_TRUE(base::test::RunUntil([&]() -> bool { | 
|  | return provider()->ui_manager().GetNumWindowsForApp(app_id) == | 
|  | number_of_app_instances; | 
|  | })); | 
|  | } | 
|  |  | 
|  | auto GetAllNotifications() { | 
|  | base::test::TestFuture<std::set<std::string>, bool> get_displayed_future; | 
|  | NotificationDisplayServiceFactory::GetForProfile(profile())->GetDisplayed( | 
|  | get_displayed_future.GetCallback()); | 
|  | const auto& notification_ids = get_displayed_future.Get<0>(); | 
|  | EXPECT_TRUE(get_displayed_future.Wait()); | 
|  | return notification_ids; | 
|  | } | 
|  |  | 
|  | size_t GetDisplayedNotificationsCount() { | 
|  | return GetAllNotifications().size(); | 
|  | } | 
|  |  | 
|  | void WaitUntilDisplayNotificationCount(size_t display_count) { | 
|  | ASSERT_TRUE(base::test::RunUntil([&]() -> bool { | 
|  | return GetDisplayedNotificationsCount() == display_count; | 
|  | })); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | base::ScopedObservation<NotificationDisplayService, | 
|  | PlaceholderUpdateRelaunchBrowserTest> | 
|  | notification_observation_{this}; | 
|  | }; | 
|  |  | 
|  | // TODO(b:341035409): Flaky. | 
|  | IN_PROC_BROWSER_TEST_F( | 
|  | PlaceholderUpdateRelaunchBrowserTest, | 
|  | DISABLED_UpdatePlaceholderRelaunchClosePreventedAppSucceeds) { | 
|  | notification_observation_.Observe( | 
|  | NotificationDisplayServiceFactory::GetForProfile(profile())); | 
|  |  | 
|  | embedded_test_server()->RegisterRequestHandler(base::BindRepeating( | 
|  | &ExternallyManagedAppManagerBrowserTest::SimulateRedirectHandler, | 
|  | base::Unretained(this))); | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | simulate_redirect_ = true; | 
|  | GURL install_url = embedded_test_server()->GetURL( | 
|  | "/banners/manifest_with_id_test_page.html"); | 
|  |  | 
|  | // Force install the placeholder. | 
|  | AddForceInstalledApp(install_url.spec(), /*app_name=*/"placeholder app"); | 
|  |  | 
|  | const webapps::AppId placeholder_app_id = | 
|  | GenerateAppId(std::nullopt, install_url); | 
|  |  | 
|  | // Enable prevent-close close for the placeholder. | 
|  | AddPreventCloseToApp(install_url.spec(), kRunWindowed); | 
|  |  | 
|  | std::optional<webapps::AppId> app_id = | 
|  | registrar().LookupExternalAppId(install_url); | 
|  | ASSERT_TRUE(app_id.has_value()); | 
|  | EXPECT_EQ(placeholder_app_id, *app_id); | 
|  | EXPECT_TRUE(registrar().IsPlaceholderApp(*app_id, WebAppManagement::kPolicy)); | 
|  |  | 
|  | EXPECT_CALL( | 
|  | *this, | 
|  | OnNotificationDisplayed( | 
|  | AllOf( | 
|  | Property(&message_center::Notification::id, | 
|  | Eq("web_app_relaunch_notifier:" + placeholder_app_id)), | 
|  | Property(&message_center::Notification::notifier_id, | 
|  | Field(&message_center::NotifierId::id, | 
|  | Eq("web_app_relaunch"))), | 
|  | Property(&message_center::Notification::title, | 
|  | Eq(u"Restarting and updating Manifest test app with id " | 
|  | u"specified")), | 
|  | Property( | 
|  | &message_center::Notification::message, | 
|  | Eq(u"Please wait while this application is being updated"))), | 
|  | _)) | 
|  | .Times(1); | 
|  |  | 
|  | // Launch the PWA so that the app relaunch is triggered on sync. | 
|  | ASSERT_TRUE(web_app::LaunchWebAppBrowser(profile(), placeholder_app_id, | 
|  | WindowOpenDisposition::NEW_WINDOW)); | 
|  | WaitForNumberOfAppInstances(placeholder_app_id, | 
|  | /*number_of_app_instances=*/1u); | 
|  |  | 
|  | // Resolve the redirect (placeholder can be updated now). | 
|  | simulate_redirect_ = false; | 
|  | provider()->policy_manager().RefreshPolicyInstalledAppsForTesting( | 
|  | /*allow_close_and_relaunch=*/true); | 
|  |  | 
|  | // Wait until the final version of the app is installed. | 
|  | const webapps::AppId final_app_id = GenerateAppId("some_id", install_url); | 
|  |  | 
|  | // Check that the placeholder app is indeed closed. | 
|  | WaitForNumberOfAppInstances(placeholder_app_id, | 
|  | /*number_of_app_instances=*/0u); | 
|  |  | 
|  | // Wait for the placeholder removal task to be done. | 
|  | ASSERT_FALSE(base::test::RunUntil( | 
|  | [&]() -> bool { return registrar().IsInRegistrar(placeholder_app_id); })); | 
|  |  | 
|  | // Check that the new app is launched. | 
|  | WaitForNumberOfAppInstances(final_app_id, /*number_of_app_instances=*/1u); | 
|  |  | 
|  | // Make sure that the notification got cleaned up. | 
|  | WaitUntilDisplayNotificationCount(/*display_count=*/0u); | 
|  |  | 
|  | EXPECT_NE(final_app_id, placeholder_app_id); | 
|  | EXPECT_EQ(registrar().GetInstallState(final_app_id), | 
|  | proto::InstallState::INSTALLED_WITH_OS_INTEGRATION); | 
|  | EXPECT_FALSE( | 
|  | registrar().IsPlaceholderApp(final_app_id, WebAppManagement::kPolicy)); | 
|  | EXPECT_EQ(0, registrar().CountUserInstalledApps()); | 
|  | EXPECT_EQ(1u, registrar() | 
|  | .GetExternallyInstalledApps( | 
|  | ExternalInstallSource::kExternalPolicy) | 
|  | .size()); | 
|  | } | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | }  // namespace web_app |