| // 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/banners/app_banner_manager_desktop.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/auto_reset.h" |
| #include "base/functional/callback.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "chrome/browser/banners/app_banner_manager_browsertest_base.h" |
| #include "chrome/browser/banners/test_app_banner_manager_desktop.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/actions/chrome_action_id.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_command_controller.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h" |
| #include "chrome/browser/ui/web_applications/web_app_dialog_utils.h" |
| #include "chrome/browser/ui/web_applications/web_app_dialogs.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.h" |
| #include "chrome/browser/web_applications/web_app_helpers.h" |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/password_manager/content/common/web_ui_constants.h" |
| #include "components/password_manager/core/common/password_manager_features.h" |
| #include "components/webapps/browser/banners/app_banner_metrics.h" |
| #include "components/webapps/browser/banners/app_banner_settings_helper.h" |
| #include "components/webapps/browser/install_result_code.h" |
| #include "components/webapps/browser/uninstall_result_code.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "extensions/common/extension.h" |
| |
| namespace webapps { |
| |
| using State = AppBannerManager::State; |
| |
| class AppBannerManagerDesktopBrowserTest |
| : public AppBannerManagerBrowserTestBase { |
| public: |
| AppBannerManagerDesktopBrowserTest() |
| : auto_accept_pwa_install_confirmation_( |
| web_app::SetAutoAcceptPWAInstallConfirmationForTesting()) {} |
| |
| void SetUp() override { |
| TestAppBannerManagerDesktop::SetUp(); |
| AppBannerManagerBrowserTestBase::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| AppBannerManagerBrowserTestBase::SetUpOnMainThread(); |
| } |
| |
| AppBannerManagerDesktopBrowserTest( |
| const AppBannerManagerDesktopBrowserTest&) = delete; |
| AppBannerManagerDesktopBrowserTest& operator=( |
| const AppBannerManagerDesktopBrowserTest&) = delete; |
| |
| private: |
| base::AutoReset<bool> auto_accept_pwa_install_confirmation_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTest, |
| WebAppBannerResolvesUserChoice) { |
| base::HistogramTester tester; |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| auto* manager = TestAppBannerManagerDesktop::FromWebContents(web_contents); |
| |
| { |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), GetBannerURLWithAction("stash_event"))); |
| run_loop.Run(); |
| EXPECT_EQ(State::PENDING_PROMPT_NOT_CANCELED, manager->state()); |
| } |
| |
| { |
| // Trigger the installation prompt and wait for installation to occur. |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| ExecuteScript(web_contents, "callStashedPrompt();", |
| true /* with_gesture */); |
| run_loop.Run(); |
| EXPECT_EQ(State::COMPLETE, manager->state()); |
| } |
| |
| // Ensure that the userChoice promise resolves. |
| const std::u16string title = u"Got userChoice: accepted"; |
| content::TitleWatcher watcher(web_contents, title); |
| EXPECT_EQ(title, watcher.WaitAndGetTitle()); |
| |
| tester.ExpectUniqueSample(kInstallDisplayModeHistogram, |
| blink::mojom::DisplayMode::kStandalone, 1); |
| } |
| |
| // TODO(crbug.com/40637899): Flakes on most platforms. |
| IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTest, |
| DISABLED_WebAppBannerFiresAppInstalled) { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| auto* manager = TestAppBannerManagerDesktop::FromWebContents(web_contents); |
| |
| { |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), GetBannerURLWithAction("verify_appinstalled_stash_event"))); |
| run_loop.Run(); |
| EXPECT_EQ(State::PENDING_PROMPT_NOT_CANCELED, manager->state()); |
| } |
| |
| { |
| // Trigger the installation prompt and wait for installation to occur. |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| |
| const GURL url = GetBannerURL(); |
| bool callback_called = false; |
| |
| web_app::SetInstalledCallbackForTesting( |
| base::BindLambdaForTesting([&](const webapps::AppId& installed_app_id, |
| webapps::InstallResultCode code) { |
| EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, code); |
| EXPECT_EQ(installed_app_id, |
| web_app::GenerateAppId(/*manifest_id=*/std::nullopt, url)); |
| callback_called = true; |
| })); |
| |
| ExecuteScript(web_contents, "callStashedPrompt();", |
| true /* with_gesture */); |
| |
| run_loop.Run(); |
| |
| EXPECT_EQ(State::COMPLETE, manager->state()); |
| EXPECT_TRUE(callback_called); |
| } |
| |
| // Ensure that the appinstalled event fires. |
| const std::u16string title = u"Got appinstalled: listener, attr"; |
| content::TitleWatcher watcher(web_contents, title); |
| EXPECT_EQ(title, watcher.WaitAndGetTitle()); |
| } |
| |
| // TODO(crbug.com/40817384): Flaky failures. |
| #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) |
| #define MAYBE_DestroyWebContents DISABLED_DestroyWebContents |
| #else |
| #define MAYBE_DestroyWebContents DestroyWebContents |
| #endif |
| IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTest, |
| MAYBE_DestroyWebContents) { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| auto* manager = TestAppBannerManagerDesktop::FromWebContents(web_contents); |
| |
| { |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), GetBannerURLWithAction("stash_event"))); |
| run_loop.Run(); |
| EXPECT_EQ(State::PENDING_PROMPT_NOT_CANCELED, manager->state()); |
| } |
| |
| { |
| // Trigger the installation and wait for termination to occur. |
| base::RunLoop run_loop; |
| bool callback_called = false; |
| |
| web_app::SetInstalledCallbackForTesting( |
| base::BindLambdaForTesting([&](const webapps::AppId& installed_app_id, |
| webapps::InstallResultCode code) { |
| EXPECT_EQ(webapps::InstallResultCode::kWebContentsDestroyed, code); |
| callback_called = true; |
| run_loop.Quit(); |
| })); |
| |
| ExecuteScript(web_contents, "callStashedPrompt();", |
| true /* with_gesture */); |
| |
| // Closing WebContents destroys WebContents and AppBannerManager. |
| browser()->tab_strip_model()->CloseWebContentsAt( |
| browser()->tab_strip_model()->active_index(), 0); |
| manager = nullptr; |
| web_contents = nullptr; |
| |
| run_loop.Run(); |
| EXPECT_TRUE(callback_called); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTest, |
| InstallPromptAfterUserMenuInstall) { |
| base::HistogramTester tester; |
| |
| TestAppBannerManagerDesktop* manager = |
| TestAppBannerManagerDesktop::FromWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| |
| { |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), GetBannerURLWithManifestAndQuery("/banners/minimal-ui.json", |
| "action", "stash_event"))); |
| run_loop.Run(); |
| EXPECT_EQ(State::PENDING_PROMPT_NOT_CANCELED, manager->state()); |
| } |
| |
| // Install the app via the menu instead of the banner. |
| browser()->command_controller()->ExecuteCommand(IDC_INSTALL_PWA); |
| manager->AwaitAppInstall(); |
| |
| EXPECT_FALSE(manager->IsPromptAvailableForTesting()); |
| |
| tester.ExpectUniqueSample(kInstallDisplayModeHistogram, |
| blink::mojom::DisplayMode::kMinimalUi, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTest, |
| InstallPromptAfterUserOmniboxInstall) { |
| base::HistogramTester tester; |
| |
| TestAppBannerManagerDesktop* manager = |
| TestAppBannerManagerDesktop::FromWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| |
| { |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), GetBannerURLWithManifestAndQuery("/banners/fullscreen.json", |
| "action", "stash_event"))); |
| run_loop.Run(); |
| EXPECT_EQ(State::PENDING_PROMPT_NOT_CANCELED, manager->state()); |
| } |
| |
| // Install the app via the menu instead of the banner. |
| if (IsPageActionMigrated(PageActionIconType::kPwaInstall)) { |
| actions::ActionManager::Get().FindAction(kActionInstallPwa)->InvokeAction(); |
| } else { |
| browser()->window()->ExecutePageActionIconForTesting( |
| PageActionIconType::kPwaInstall); |
| } |
| |
| manager->AwaitAppInstall(); |
| |
| EXPECT_FALSE(manager->IsPromptAvailableForTesting()); |
| |
| tester.ExpectUniqueSample(kInstallDisplayModeHistogram, |
| blink::mojom::DisplayMode::kFullscreen, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTest, |
| PolicyAppInstalled_Prompt) { |
| TestAppBannerManagerDesktop* manager = |
| TestAppBannerManagerDesktop::FromWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| |
| // Install web app by policy. |
| web_app::ExternalInstallOptions options = |
| web_app::CreateInstallOptions(GetBannerURL()); |
| options.install_source = web_app::ExternalInstallSource::kExternalPolicy; |
| options.user_display_mode = web_app::mojom::UserDisplayMode::kBrowser; |
| web_app::ExternallyManagedAppManagerInstall(browser()->profile(), options); |
| |
| // Run promotability check. |
| { |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetBannerURL())); |
| run_loop.Run(); |
| EXPECT_EQ(State::PENDING_PROMPT_NOT_CANCELED, manager->state()); |
| } |
| |
| EXPECT_EQ(InstallableWebAppCheckResult::kYes_Promotable, |
| manager->GetInstallableWebAppCheckResult()); |
| EXPECT_TRUE(manager->IsPromptAvailableForTesting()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTest, |
| PolicyAppUninstalled_Prompt) { |
| TestAppBannerManagerDesktop* manager = |
| TestAppBannerManagerDesktop::FromWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| Profile* profile = browser()->profile(); |
| |
| // Install web app by policy. |
| web_app::ExternalInstallOptions options = |
| web_app::CreateInstallOptions(GetBannerURL()); |
| options.install_source = web_app::ExternalInstallSource::kExternalPolicy; |
| options.user_display_mode = web_app::mojom::UserDisplayMode::kBrowser; |
| web_app::ExternallyManagedAppManagerInstall(profile, options); |
| |
| // Uninstall web app by policy by synchronizing with an empty |
| // `ExternalInstallOptions` vector. |
| { |
| base::RunLoop run_loop; |
| web_app::WebAppProvider::GetForTest(profile) |
| ->externally_managed_app_manager() |
| .SynchronizeInstalledApps( |
| {}, web_app::ExternalInstallSource::kExternalPolicy, |
| base::BindLambdaForTesting( |
| [&](std::map<GURL /*install_url*/, |
| web_app::ExternallyManagedAppManagerInstallResult> |
| install_results, |
| std::map<GURL /*install_url*/, webapps::UninstallResultCode> |
| uninstall_results) { |
| EXPECT_TRUE(install_results.empty()); |
| ASSERT_TRUE( |
| base::Contains(uninstall_results, GetBannerURL())); |
| EXPECT_EQ(webapps::UninstallResultCode::kAppRemoved, |
| uninstall_results[GetBannerURL()]); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| // Run promotability check. |
| { |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetBannerURL())); |
| run_loop.Run(); |
| EXPECT_EQ(State::PENDING_PROMPT_NOT_CANCELED, manager->state()); |
| } |
| |
| EXPECT_EQ(InstallableWebAppCheckResult::kYes_Promotable, |
| manager->GetInstallableWebAppCheckResult()); |
| EXPECT_TRUE(manager->IsPromptAvailableForTesting()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTest, |
| InstallPromptAfterUserMenuInstall_DisplayOverride) { |
| base::HistogramTester tester; |
| |
| TestAppBannerManagerDesktop* manager = |
| TestAppBannerManagerDesktop::FromWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| |
| { |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), GetBannerURLWithManifestAndQuery( |
| "/banners/manifest_display_override.json", "action", |
| "stash_event"))); |
| run_loop.Run(); |
| EXPECT_EQ(State::PENDING_PROMPT_NOT_CANCELED, manager->state()); |
| } |
| |
| // Install the app via the menu instead of the banner. |
| browser()->command_controller()->ExecuteCommand(IDC_INSTALL_PWA); |
| manager->AwaitAppInstall(); |
| |
| EXPECT_FALSE(manager->IsPromptAvailableForTesting()); |
| |
| tester.ExpectUniqueSample(kInstallDisplayModeHistogram, |
| blink::mojom::DisplayMode::kMinimalUi, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTest, |
| WebAppBannerResolvesUserChoice_DisplayOverride) { |
| base::HistogramTester tester; |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| auto* manager = TestAppBannerManagerDesktop::FromWebContents(web_contents); |
| |
| { |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| GetBannerURLWithManifestAndQuery( |
| "/banners/manifest_display_override_display_is_browser.json", |
| "action", "stash_event"))); |
| run_loop.Run(); |
| EXPECT_EQ(State::PENDING_PROMPT_NOT_CANCELED, manager->state()); |
| } |
| |
| { |
| // Trigger the installation prompt and wait for installation to occur. |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| ExecuteScript(web_contents, "callStashedPrompt();", |
| true /* with_gesture */); |
| run_loop.Run(); |
| EXPECT_EQ(State::COMPLETE, manager->state()); |
| } |
| |
| // Ensure that the userChoice promise resolves. |
| const std::u16string title = u"Got userChoice: accepted"; |
| content::TitleWatcher watcher(web_contents, title); |
| EXPECT_EQ(title, watcher.WaitAndGetTitle()); |
| |
| tester.ExpectUniqueSample(kInstallDisplayModeHistogram, |
| blink::mojom::DisplayMode::kStandalone, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTest, |
| PolicyAppInstalled_Prompt_DisplayOverride) { |
| TestAppBannerManagerDesktop* manager = |
| TestAppBannerManagerDesktop::FromWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| |
| // Install web app by policy. |
| web_app::ExternalInstallOptions options = |
| web_app::CreateInstallOptions(GetBannerURLWithManifest( |
| "/banners/manifest_display_override_contains_browser.json")); |
| options.install_source = web_app::ExternalInstallSource::kExternalPolicy; |
| options.user_display_mode = web_app::mojom::UserDisplayMode::kBrowser; |
| web_app::ExternallyManagedAppManagerInstall(browser()->profile(), options); |
| |
| // Run promotability check. |
| { |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetBannerURL())); |
| run_loop.Run(); |
| EXPECT_EQ(State::PENDING_PROMPT_NOT_CANCELED, manager->state()); |
| } |
| |
| EXPECT_EQ(InstallableWebAppCheckResult::kYes_Promotable, |
| manager->GetInstallableWebAppCheckResult()); |
| EXPECT_TRUE(manager->IsPromptAvailableForTesting()); |
| } |
| |
| class AppBannerManagerDesktopBrowserTestForPasswordManagerPage |
| : public AppBannerManagerDesktopBrowserTest { |
| public: |
| void SetUp() override { |
| TestAppBannerManagerDesktop::SetUp(); |
| AppBannerManagerBrowserTestBase::SetUp(); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTestForPasswordManagerPage, |
| WebUiPasswordManagerApp) { |
| TestAppBannerManagerDesktop* manager = |
| TestAppBannerManagerDesktop::FromWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| |
| // Simulate loading a PasswordManager page. |
| { |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| GURL(base::StrCat( |
| {"chrome://", password_manager::kChromeUIPasswordManagerHost})))); |
| run_loop.Run(); |
| } |
| |
| EXPECT_EQ(InstallableWebAppCheckResult::kYes_Promotable, |
| manager->GetInstallableWebAppCheckResult()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTest, |
| PipelineRunsAfterStop) { |
| TestAppBannerManagerDesktop* manager = |
| TestAppBannerManagerDesktop::FromWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| |
| { |
| base::RunLoop run_loop; |
| manager->PrepareDone(run_loop.QuitClosure()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| embedded_test_server()->GetURL("/web_apps/stop-loading-early.html"))); |
| run_loop.Run(); |
| } |
| |
| EXPECT_EQ(InstallableWebAppCheckResult::kYes_Promotable, |
| manager->GetInstallableWebAppCheckResult()) |
| << manager->debug_log(); |
| } |
| |
| } // namespace webapps |