| // 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 <algorithm> |
| #include <tuple> |
| |
| #include "base/test/metrics/user_action_tester.h" |
| #include "base/test/test_future.h" |
| #include "chrome/browser/apps/app_service/app_registry_cache_waiter.h" |
| #include "chrome/browser/apps/link_capturing/enable_link_capturing_infobar_delegate.h" |
| #include "chrome/browser/apps/link_capturing/intent_picker_info.h" |
| #include "chrome/browser/apps/link_capturing/link_capturing_feature_test_support.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_commands.h" |
| #include "chrome/browser/ui/browser_tabstrip.h" |
| #include "chrome/browser/ui/views/web_apps/web_app_link_capturing_test_utils.h" |
| #include "chrome/browser/ui/web_applications/app_browser_controller.h" |
| #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h" |
| #include "chrome/browser/ui/web_applications/test/web_app_navigation_browsertest.h" |
| #include "chrome/browser/web_applications/web_app_command_manager.h" |
| #include "chrome/browser/web_applications/web_app_command_scheduler.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/common/chrome_features.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/infobars/content/content_infobar_manager.h" |
| #include "components/infobars/core/confirm_infobar_delegate.h" |
| #include "components/infobars/core/infobar.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/views/controls/button/button.h" |
| #include "ui/views/test/button_test_api.h" |
| #include "ui/views/widget/any_widget_observer.h" |
| #include "url/gurl.h" |
| #include "url/url_constants.h" |
| |
| namespace web_app { |
| namespace { |
| |
| content::WebContents* GetActiveWebContents(Browser* browser) { |
| return browser->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| class EnableLinkCapturingInfobarBrowserTest |
| : public WebAppNavigationBrowserTest, |
| public testing::WithParamInterface< |
| apps::test::LinkCapturingFeatureVersion> { |
| public: |
| EnableLinkCapturingInfobarBrowserTest() { |
| feature_list_.InitWithFeaturesAndParameters( |
| apps::test::GetFeaturesToEnableLinkCapturingUX(GetParam()), {}); |
| } |
| |
| bool LinkCapturingEnabledByDefault() { |
| return GetParam() == apps::test::LinkCapturingFeatureVersion::kV2DefaultOn; |
| } |
| |
| // Returns [app_id, in_scope_url] |
| std::tuple<webapps::AppId, GURL> InstallTestApp() { |
| GURL start_url = embedded_test_server()->GetURL("/web_apps/basic.html"); |
| GURL in_scope_url = embedded_test_server()->GetURL("/web_apps/page1.html"); |
| |
| webapps::AppId app_id = |
| InstallWebAppFromPageAndCloseAppBrowser(browser(), start_url); |
| apps::AppReadinessWaiter(profile(), app_id).Await(); |
| return {app_id, in_scope_url}; |
| } |
| |
| // Returns [outer app_id, inner app_id, inner app in_scope_url] |
| std::tuple<webapps::AppId, webapps::AppId, GURL> |
| InstallOuterAppAndInnerApp() { |
| GURL outer_start_url = |
| embedded_test_server()->GetURL("/web_apps/nesting/index.html"); |
| GURL inner_start_url = |
| embedded_test_server()->GetURL("/web_apps/nesting/nested/index.html"); |
| GURL inner_in_scope_url = |
| embedded_test_server()->GetURL("/web_apps/nesting/nested/page1.html"); |
| |
| // The inner app must be installed first so that it is installable. |
| webapps::AppId inner_app_id = |
| InstallWebAppFromPageAndCloseAppBrowser(browser(), inner_start_url); |
| apps::AppReadinessWaiter(profile(), inner_app_id).Await(); |
| webapps::AppId outer_app_id = |
| InstallWebAppFromPageAndCloseAppBrowser(browser(), outer_start_url); |
| apps::AppReadinessWaiter(profile(), outer_app_id).Await(); |
| return {outer_app_id, inner_app_id, inner_in_scope_url}; |
| } |
| |
| // Calling `NavigateViaLinkClick()` with `LinkTarget::BLANK` ensures that a |
| // new top level browsing context is always created, to allow navigation |
| // capturing to happen. |
| void NavigateViaLinkClick(Browser* browser, |
| const GURL& url, |
| LinkTarget link_target = LinkTarget::BLANK) { |
| ClickLinkAndWait(GetActiveWebContents(browser), url, link_target, |
| std::string()); |
| } |
| |
| infobars::InfoBar* GetLinkCapturingInfoBar(Browser* browser) { |
| return GetLinkCapturingInfoBar(GetActiveWebContents(browser)); |
| } |
| |
| infobars::InfoBar* GetLinkCapturingInfoBar( |
| content::WebContents* web_contents) { |
| return apps::EnableLinkCapturingInfoBarDelegate::FindInfoBar(web_contents); |
| } |
| |
| void TurnOnLinkCapturing(webapps::AppId app_id) { |
| base::test::TestFuture<void> preference_set; |
| provider().scheduler().SetAppCapturesSupportedLinksDisableOverlapping( |
| app_id, /*set_to_preferred=*/true, preference_set.GetCallback()); |
| ASSERT_TRUE(preference_set.Wait()); |
| } |
| |
| void TurnOffLinkCapturing(webapps::AppId app_id) { |
| base::test::TestFuture<void> preference_set; |
| provider().scheduler().SetAppCapturesSupportedLinksDisableOverlapping( |
| app_id, /*set_to_preferred=*/false, preference_set.GetCallback()); |
| ASSERT_TRUE(preference_set.Wait()); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(EnableLinkCapturingInfobarBrowserTest, |
| InfoBarShowsOnIntentPickerLaunch) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const auto [app_id, in_scope_url] = InstallTestApp(); |
| |
| // The infobar shows up only when link capturing is not enabled for an app. |
| // For default-on behavior, switch off link capturing manually. |
| if (LinkCapturingEnabledByDefault()) { |
| TurnOffLinkCapturing(app_id); |
| } |
| |
| NavigateViaLinkClick(browser(), in_scope_url); |
| |
| ui_test_utils::BrowserCreatedObserver browser_created_observer; |
| ASSERT_TRUE(web_app::ClickIntentPickerChip(browser())); |
| Browser* app_browser = browser_created_observer.Wait(); |
| ASSERT_TRUE(app_browser); |
| |
| EXPECT_NE(GetLinkCapturingInfoBar(app_browser), nullptr); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(EnableLinkCapturingInfobarBrowserTest, |
| EnableLinkCapturingThroughInfoBar) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const auto [app_id, in_scope_url] = InstallTestApp(); |
| |
| // The infobar shows up only when link capturing is not enabled for an app. |
| // For default-on behavior, switch off link capturing manually. |
| if (LinkCapturingEnabledByDefault()) { |
| TurnOffLinkCapturing(app_id); |
| } |
| |
| NavigateViaLinkClick(browser(), in_scope_url); |
| |
| Browser* app_browser; |
| { |
| ui_test_utils::BrowserCreatedObserver browser_created_observer; |
| ASSERT_TRUE(web_app::ClickIntentPickerChip(browser())); |
| app_browser = browser_created_observer.Wait(); |
| ASSERT_TRUE(app_browser); |
| } |
| infobars::InfoBar* infobar = GetLinkCapturingInfoBar(app_browser); |
| ASSERT_TRUE(infobar); |
| |
| base::UserActionTester user_action_tester; |
| // Because there is no testing utility for info bars, manually accept & |
| // remove. |
| EXPECT_TRUE( |
| static_cast<ConfirmInfoBarDelegate*>(infobar->delegate())->Accept()); |
| infobar->RemoveSelf(); |
| provider().command_manager().AwaitAllCommandsCompleteForTesting(); |
| |
| EXPECT_EQ( |
| 1, user_action_tester.GetActionCount("LinkCapturingAcceptedFromInfoBar")); |
| |
| EXPECT_EQ(app_id, |
| provider().registrar_unsafe().FindAppThatCapturesLinksInScope( |
| in_scope_url)); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(EnableLinkCapturingInfobarBrowserTest, |
| InfoBarNotShownOnLinkCapturingEnabled) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const auto [app_id, in_scope_url] = InstallTestApp(); |
| |
| // The infobar shows up only when link capturing is not enabled for an app. |
| // For default-on behavior, switch off link capturing manually. |
| if (!LinkCapturingEnabledByDefault()) { |
| TurnOnLinkCapturing(app_id); |
| } |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), in_scope_url)); |
| |
| ui_test_utils::BrowserCreatedObserver browser_created_observer; |
| // If there is only 1 app installed that captures the document URL, and that |
| // app is also set to capture links by default, the link should open in the |
| // PWA automatically on clicking the intent chip without going through the |
| // intent picker bubble. |
| ASSERT_TRUE(web_app::ClickIntentPickerChip(browser())); |
| Browser* app_browser = browser_created_observer.Wait(); |
| ASSERT_TRUE(app_browser); |
| |
| EXPECT_EQ(GetLinkCapturingInfoBar(app_browser), nullptr); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(EnableLinkCapturingInfobarBrowserTest, |
| RecordUserActionCancelled) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const auto [app_id, in_scope_url] = InstallTestApp(); |
| |
| // The infobar shows up only when link capturing is not enabled for an app. |
| // For default-on behavior, switch off link capturing manually. |
| if (LinkCapturingEnabledByDefault()) { |
| TurnOffLinkCapturing(app_id); |
| } |
| |
| NavigateViaLinkClick(browser(), in_scope_url); |
| |
| ui_test_utils::BrowserCreatedObserver browser_created_observer; |
| ASSERT_TRUE(web_app::ClickIntentPickerChip(browser())); |
| Browser* app_browser = browser_created_observer.Wait(); |
| ASSERT_TRUE(app_browser); |
| |
| infobars::InfoBar* infobar = GetLinkCapturingInfoBar(app_browser); |
| ASSERT_TRUE(infobar); |
| |
| base::UserActionTester user_action_tester; |
| // Because there is no testing utility for info bars, manually cancel & |
| // remove. |
| EXPECT_TRUE( |
| static_cast<ConfirmInfoBarDelegate*>(infobar->delegate())->Cancel()); |
| infobar->RemoveSelf(); |
| provider().command_manager().AwaitAllCommandsCompleteForTesting(); |
| |
| EXPECT_EQ(1, user_action_tester.GetActionCount( |
| "LinkCapturingCancelledFromInfoBar")); |
| |
| EXPECT_EQ(std::nullopt, |
| provider().registrar_unsafe().FindAppThatCapturesLinksInScope( |
| in_scope_url)); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(EnableLinkCapturingInfobarBrowserTest, |
| RecordUserActionIgnored) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const auto [app_id, in_scope_url] = InstallTestApp(); |
| |
| // The infobar shows up only when link capturing is not enabled for an app. |
| // For default-on behavior, switch off link capturing manually. |
| if (LinkCapturingEnabledByDefault()) { |
| TurnOffLinkCapturing(app_id); |
| } |
| |
| NavigateViaLinkClick(browser(), in_scope_url); |
| |
| ui_test_utils::BrowserCreatedObserver browser_created_observer; |
| ASSERT_TRUE(web_app::ClickIntentPickerChip(browser())); |
| Browser* app_browser = browser_created_observer.Wait(); |
| ASSERT_TRUE(app_browser); |
| |
| infobars::InfoBar* infobar = GetLinkCapturingInfoBar(app_browser); |
| ASSERT_TRUE(infobar); |
| |
| base::UserActionTester user_action_tester; |
| |
| CloseBrowserSynchronously(app_browser); |
| provider().command_manager().AwaitAllCommandsCompleteForTesting(); |
| |
| EXPECT_EQ( |
| 1, user_action_tester.GetActionCount("LinkCapturingIgnoredFromInfoBar")); |
| |
| EXPECT_EQ(std::nullopt, |
| provider().registrar_unsafe().FindAppThatCapturesLinksInScope( |
| in_scope_url)); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(EnableLinkCapturingInfobarBrowserTest, AppLaunched) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const auto [app_id, in_scope_url] = InstallTestApp(); |
| |
| // The infobar shows up only when link capturing is not enabled for an app. |
| // For default-on behavior, switch off link capturing manually. |
| if (LinkCapturingEnabledByDefault()) { |
| TurnOffLinkCapturing(app_id); |
| } |
| |
| NavigateViaLinkClick(browser(), in_scope_url); |
| |
| Browser* app_browser; |
| { |
| ui_test_utils::BrowserCreatedObserver browser_created_observer; |
| ASSERT_TRUE(web_app::ClickIntentPickerChip(browser())); |
| app_browser = browser_created_observer.Wait(); |
| ASSERT_TRUE(app_browser); |
| } |
| |
| infobars::InfoBar* infobar = GetLinkCapturingInfoBar(app_browser); |
| ASSERT_TRUE(infobar); |
| EXPECT_TRUE( |
| static_cast<ConfirmInfoBarDelegate*>(infobar->delegate())->Accept()); |
| // Because there is no testing utility for info bars, manually remove. |
| infobar->RemoveSelf(); |
| provider().command_manager().AwaitAllCommandsCompleteForTesting(); |
| |
| CloseBrowserSynchronously(app_browser); |
| provider().command_manager().AwaitAllCommandsCompleteForTesting(); |
| |
| content::TestNavigationObserver observer = |
| content::TestNavigationObserver(GURL(url::kAboutBlankURL)); |
| observer.StartWatchingNewWebContents(); |
| chrome::AddTabAt(browser(), GURL(url::kAboutBlankURL), /*index=*/-1, |
| /*foreground=*/true); |
| observer.Wait(); |
| { |
| ui_test_utils::BrowserCreatedObserver browser_created_observer; |
| NavigateViaLinkClick(browser(), in_scope_url); |
| app_browser = browser_created_observer.Wait(); |
| ASSERT_TRUE(app_browser); |
| EXPECT_TRUE(AppBrowserController::IsForWebApp(app_browser, app_id)); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_P(EnableLinkCapturingInfobarBrowserTest, BarRemoved) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const auto [app_id, in_scope_url] = InstallTestApp(); |
| |
| // The infobar shows up only when link capturing is not enabled for an app. |
| // For default-on behavior, switch off link capturing manually. |
| if (LinkCapturingEnabledByDefault()) { |
| TurnOffLinkCapturing(app_id); |
| } |
| |
| NavigateViaLinkClick(browser(), in_scope_url); |
| |
| ui_test_utils::BrowserCreatedObserver browser_created_observer; |
| ASSERT_TRUE(web_app::ClickIntentPickerChip(browser())); |
| Browser* app_browser = browser_created_observer.Wait(); |
| ASSERT_TRUE(app_browser); |
| |
| // The web_contents here is moving to `browser()`, and `app_browser` will be |
| // invalidated, so grab a WeakPtr here. |
| base::WeakPtr<content::WebContents> web_contents = |
| GetActiveWebContents(app_browser)->GetWeakPtr(); |
| |
| EXPECT_TRUE(GetLinkCapturingInfoBar(web_contents.get())); |
| |
| // Note: this will close & invalidate app_browser. |
| Browser* tabbed_browser = chrome::OpenInChrome(app_browser); |
| |
| ASSERT_TRUE(web_contents); |
| EXPECT_EQ(web_contents.get(), GetActiveWebContents(tabbed_browser)); |
| EXPECT_FALSE(GetLinkCapturingInfoBar(web_contents.get())); |
| } |
| |
| // TODO(crbug.com/40936015): Flaky on all platforms. |
| IN_PROC_BROWSER_TEST_P(EnableLinkCapturingInfobarBrowserTest, |
| DISABLED_InfoBarHiddenAfterDismissals) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const auto [app_id, in_scope_url] = InstallTestApp(); |
| |
| // The infobar shows up only when link capturing is not enabled for an app. |
| // For default-on behavior, switch off link capturing manually. |
| if (LinkCapturingEnabledByDefault()) { |
| TurnOffLinkCapturing(app_id); |
| } |
| |
| // Dismiss the infobar twice. |
| for (int i = 0; i < 2; ++i) { |
| NavigateViaLinkClick(browser(), in_scope_url); |
| |
| ui_test_utils::BrowserCreatedObserver browser_created_observer; |
| ASSERT_TRUE(web_app::ClickIntentPickerChip(browser())); |
| Browser* app_browser = browser_created_observer.Wait(); |
| ASSERT_TRUE(app_browser); |
| |
| infobars::InfoBar* infobar = GetLinkCapturingInfoBar(app_browser); |
| ASSERT_TRUE(infobar); |
| |
| base::UserActionTester user_action_tester; |
| EXPECT_TRUE( |
| static_cast<ConfirmInfoBarDelegate*>(infobar->delegate())->Cancel()); |
| // Because there is no testing utility for info bars, manually remove. |
| infobar->RemoveSelf(); |
| provider().command_manager().AwaitAllCommandsCompleteForTesting(); |
| |
| EXPECT_EQ(1, user_action_tester.GetActionCount( |
| "LinkCapturingCancelledFromInfoBar")); |
| |
| EXPECT_EQ(std::nullopt, |
| provider().registrar_unsafe().FindAppThatCapturesLinksInScope( |
| in_scope_url)); |
| CloseBrowserSynchronously(app_browser); |
| } |
| |
| // Now, the infobar will not show up. |
| NavigateViaLinkClick(browser(), in_scope_url); |
| |
| ui_test_utils::BrowserCreatedObserver browser_created_observer; |
| ASSERT_TRUE(web_app::ClickIntentPickerChip(browser())); |
| Browser* app_browser = browser_created_observer.Wait(); |
| |
| infobars::InfoBar* infobar = GetLinkCapturingInfoBar(app_browser); |
| EXPECT_FALSE(infobar); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(EnableLinkCapturingInfobarBrowserTest, |
| OuterAppNoInfoBar) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const auto [outer_app_id, inner_app_id, inner_app_scoped_url] = |
| InstallOuterAppAndInnerApp(); |
| |
| // The infobar shows up only when link capturing is not enabled for an app. |
| // For default-on behavior, switch off link capturing manually. |
| if (LinkCapturingEnabledByDefault()) { |
| TurnOffLinkCapturing(outer_app_id); |
| TurnOffLinkCapturing(inner_app_id); |
| } |
| |
| NavigateViaLinkClick(browser(), inner_app_scoped_url); |
| EXPECT_TRUE(web_app::ClickIntentPickerAndWaitForBubble(browser())); |
| |
| // The app list is currently not deterministically ordered, so find the |
| // correct item and select that. |
| const auto& app_infos = |
| web_app::intent_picker_bubble()->app_info_for_testing(); |
| auto it = std::ranges::find(app_infos, outer_app_id, |
| &apps::IntentPickerAppInfo::launch_name); |
| ASSERT_NE(it, app_infos.end()); |
| size_t index = it - app_infos.begin(); |
| |
| ui_test_utils::BrowserCreatedObserver browser_created_observer; |
| views::test::ButtonTestApi test_api( |
| web_app::GetIntentPickerButtonAtIndex(index)); |
| test_api.NotifyClick(ui::MouseEvent( |
| ui::EventType::kMousePressed, gfx::Point(), gfx::Point(), |
| base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); |
| web_app::intent_picker_bubble()->AcceptDialog(); |
| |
| Browser* app_browser = browser_created_observer.Wait(); |
| ASSERT_TRUE(app_browser); |
| EXPECT_TRUE(AppBrowserController::IsWebApp(app_browser)); |
| EXPECT_TRUE(AppBrowserController::IsForWebApp(app_browser, outer_app_id)); |
| EXPECT_FALSE(GetLinkCapturingInfoBar(app_browser)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| EnableLinkCapturingInfobarBrowserTest, |
| testing::Values(apps::test::LinkCapturingFeatureVersion::kV2DefaultOff, |
| apps::test::LinkCapturingFeatureVersion::kV2DefaultOn), |
| apps::test::LinkCapturingVersionToString); |
| |
| } // namespace |
| } // namespace web_app |