| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_element_identifiers.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_features.h" |
| #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h" |
| #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h" |
| #include "chrome/browser/ui/location_bar/location_bar.h" |
| #include "chrome/browser/ui/omnibox/omnibox_edit_model.h" |
| #include "chrome/browser/ui/omnibox/omnibox_tab_helper.h" |
| #include "chrome/browser/ui/omnibox/omnibox_view.h" |
| #include "chrome/browser/ui/toasts/api/toast_id.h" |
| #include "chrome/browser/ui/toasts/toast_controller.h" |
| #include "chrome/browser/ui/toasts/toast_features.h" |
| #include "chrome/browser/ui/toasts/toast_view.h" |
| #include "chrome/browser/ui/views/frame/app_menu_button.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/views/location_bar/star_view.h" |
| #include "chrome/test/base/interactive_test_utils.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "chrome/test/interaction/interactive_browser_test.h" |
| #include "components/omnibox/common/omnibox_features.h" |
| #include "components/plus_addresses/core/common/features.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "third_party/blink/public/mojom/frame/fullscreen.mojom.h" |
| #include "ui/base/accelerators/accelerator.h" |
| #include "ui/base/interaction/element_identifier.h" |
| #include "ui/base/interaction/interactive_test.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| #include "ui/gfx/animation/animation_test_api.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/views/bubble/bubble_dialog_delegate_view.h" |
| #include "ui/views/controls/button/button.h" |
| #include "ui/views/focus/focus_manager.h" |
| #include "ui/views/interaction/interactive_views_test.h" |
| #include "ui/views/view.h" |
| #include "ui/views/widget/widget.h" |
| |
| #if BUILDFLAG(IS_OZONE) |
| #include "ui/ozone/public/ozone_platform.h" |
| #endif |
| |
| namespace { |
| DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kFirstTab); |
| DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSecondTab); |
| |
| class OmniboxInputWaiter : public OmniboxTabHelper::Observer { |
| public: |
| explicit OmniboxInputWaiter(content::WebContents* web_contents) { |
| omnibox_helper_observer_.Observe( |
| OmniboxTabHelper::FromWebContents(web_contents)); |
| |
| run_loop_ = std::make_unique<base::RunLoop>( |
| base::RunLoop::Type::kNestableTasksAllowed); |
| } |
| ~OmniboxInputWaiter() override = default; |
| |
| void Wait() { run_loop_->Run(); } |
| |
| void OnOmniboxInputStateChanged() override {} |
| |
| void OnOmniboxInputInProgress(bool in_progress) override { |
| run_loop_->Quit(); |
| } |
| |
| void OnOmniboxFocusChanged(OmniboxFocusState state, |
| OmniboxFocusChangeReason reason) override {} |
| |
| void OnOmniboxPopupVisibilityChanged(bool popup_is_open) override {} |
| |
| private: |
| std::unique_ptr<base::RunLoop> run_loop_; |
| base::ScopedObservation<OmniboxTabHelper, OmniboxTabHelper::Observer> |
| omnibox_helper_observer_{this}; |
| }; |
| |
| DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSampleMenuItem); |
| |
| // Simplified menu model with a single item that runs `closure` on selecting it. |
| class TestMenuModel : public ui::SimpleMenuModel, |
| ui::SimpleMenuModel::Delegate { |
| public: |
| explicit TestMenuModel(base::RepeatingClosure closure) |
| : ui::SimpleMenuModel(/*delegate=*/this), closure_(std::move(closure)) { |
| AddItem(kCommandId, u"Some entry"); |
| SetElementIdentifierAt(0, kSampleMenuItem); |
| } |
| |
| // ui::SimpleMenuModel::Delegate: |
| void ExecuteCommand(int command_id, int event_flags) override { |
| ASSERT_EQ(command_id, kCommandId); |
| closure_.Run(); |
| } |
| |
| private: |
| static constexpr int kCommandId = 123; |
| |
| base::RepeatingClosure closure_; |
| }; |
| |
| } // namespace |
| |
| class ToastControllerInteractiveTest : public InteractiveBrowserTest { |
| public: |
| void SetUp() override { |
| feature_list_.InitWithFeatures( |
| {toast_features::kLinkCopiedToast, toast_features::kImageCopiedToast, |
| toast_features::kReadingListToast, |
| plus_addresses::features::kPlusAddressesEnabled}, |
| // Disable `kAiModeOmniboxEntryPoint` as it changes the focus and popup |
| // opening order of the omnibox. If it launches, updates the tests to |
| // match the new expectations. |
| {omnibox::kAiModeOmniboxEntryPoint}); |
| InteractiveBrowserTest::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| InteractiveBrowserTest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| GURL GetURL(std::string_view hostname = "example.com", |
| std::string_view path = "/title1.html") { |
| return embedded_test_server()->GetURL(hostname, path); |
| } |
| |
| ToastController* GetToastController() { |
| return browser()->browser_window_features()->toast_controller(); |
| } |
| |
| auto ShowToast(ToastParams params) { |
| return Do(base::BindOnce( |
| [](ToastController* toast_controller, ToastParams toast_params) { |
| toast_controller->MaybeShowToast(std::move(toast_params)); |
| }, |
| GetToastController(), std::move(params))); |
| } |
| |
| auto FireToastCloseTimer() { |
| return Do([=, this]() { |
| GetToastController()->GetToastCloseTimerForTesting()->FireNow(); |
| }); |
| } |
| |
| auto CheckShowingToastId(ToastId expected_id) { |
| return CheckResult( |
| [=, this]() { |
| ToastController* const toast_controller = GetToastController(); |
| std::optional<ToastId> current_toast_id = |
| toast_controller->GetCurrentToastId(); |
| return current_toast_id.value(); |
| }, |
| expected_id); |
| } |
| |
| auto AdvanceKeyboardFocus(bool reverse) { |
| return Do([this, reverse]() { |
| ASSERT_TRUE(ui_test_utils::SendKeyPressSync( |
| browser(), ui::VKEY_TAB, false, reverse, false, false)); |
| }); |
| } |
| |
| void RemoveOmniboxFocus() { |
| ui_test_utils::ClickOnView( |
| BrowserView::GetBrowserViewForBrowser(browser())->contents_web_view()); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, ShowToast) { |
| RunTestSequence( |
| ShowToast(ToastParams(ToastId::kLinkCopied)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| Check([=, this]() { return GetToastController()->IsShowingToast(); })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, ShowSameToastTwice) { |
| RunTestSequence( |
| ShowToast(ToastParams(ToastId::kLinkCopied)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| Check([=, this]() { return GetToastController()->IsShowingToast(); }), |
| ShowToast(ToastParams(ToastId::kLinkCopied)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| Check([=, this]() { return GetToastController()->IsShowingToast(); })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, PreemptToast) { |
| RunTestSequence( |
| ShowToast(ToastParams(ToastId::kLinkCopied)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| Check([=, this]() { return GetToastController()->IsShowingToast(); }), |
| ShowToast(ToastParams(ToastId::kImageCopied))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, FocusNextPane) { |
| ui::Accelerator next_pane; |
| ASSERT_TRUE(BrowserView::GetBrowserViewForBrowser(browser())->GetAccelerator( |
| IDC_FOCUS_NEXT_PANE, &next_pane)); |
| views::Widget* toast_widget = nullptr; |
| RunTestSequence( |
| ObserveState(views::test::kCurrentWidgetFocus), |
| ShowToast(ToastParams(ToastId::kAddedToReadingList)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| WithView( |
| toasts::ToastView::kToastViewId, |
| [&](toasts::ToastView* toast) { toast_widget = toast->GetWidget(); }), |
| CheckView(toasts::ToastView::kToastViewId, |
| [](toasts::ToastView* toast) { |
| return !toast->GetFocusManager()->GetFocusedView(); |
| }), |
| SendAccelerator(kBrowserViewElementId, next_pane), |
| WaitForState(views::test::kCurrentWidgetFocus, std::ref(toast_widget)), |
| CheckView(toasts::ToastView::kToastViewId, [](toasts::ToastView* toast) { |
| return toast->GetFocusManager()->GetFocusedView() == |
| toast->action_button_for_testing(); |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, ReverseFocusTraversal) { |
| ui::Accelerator next_pane; |
| ASSERT_TRUE(BrowserView::GetBrowserViewForBrowser(browser())->GetAccelerator( |
| IDC_FOCUS_NEXT_PANE, &next_pane)); |
| RunTestSequence( |
| ShowToast(ToastParams(ToastId::kAddedToReadingList)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| ActivateSurface(toasts::ToastView::kToastViewId), |
| SendAccelerator(kBrowserViewElementId, next_pane), |
| CheckView(toasts::ToastView::kToastViewId, |
| [](toasts::ToastView* toast) { |
| return toast->GetFocusManager()->GetFocusedView() == |
| toast->action_button_for_testing(); |
| }), |
| AdvanceKeyboardFocus(true), |
| #if BUILDFLAG(IS_MAC) |
| // Mac focus traversal order is slightly different from other platforms |
| CheckView(kToolbarAppMenuButtonElementId, |
| [](AppMenuButton* button) { return button->HasFocus(); }) |
| #else |
| CheckView(kBookmarkStarViewElementId, |
| [](StarView* star_view) { return star_view->HasFocus(); }) |
| #endif |
| ); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, ForwardFocusTraversal) { |
| ui::Accelerator next_pane; |
| ASSERT_TRUE(BrowserView::GetBrowserViewForBrowser(browser())->GetAccelerator( |
| IDC_FOCUS_NEXT_PANE, &next_pane)); |
| RunTestSequence( |
| ShowToast(ToastParams(ToastId::kAddedToReadingList)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| ActivateSurface(toasts::ToastView::kToastViewId), |
| SendAccelerator(kBrowserViewElementId, next_pane), |
| // Advancing focus should move into the toast close button |
| AdvanceKeyboardFocus(false), |
| CheckView(toasts::ToastView::kToastViewId, |
| [](toasts::ToastView* toast) { |
| return toast->close_button_for_testing()->HasFocus(); |
| }), |
| // Advancing focus again should move out of the toast and into the WebView |
| AdvanceKeyboardFocus(false), |
| CheckView(toasts::ToastView::kToastViewId, |
| [](toasts::ToastView* toast) { |
| return !toast->close_button_for_testing()->HasFocus(); |
| }), |
| CheckView(kBrowserViewElementId, [](BrowserView* browser_view) { |
| return browser_view->GetActiveContentsWebView()->HasFocus(); |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, |
| HideTabScopedToastOnTabChange) { |
| RunTestSequence(InstrumentTab(kFirstTab), |
| AddInstrumentedTab(kSecondTab, GetURL()), |
| SelectTab(kTabStripElementId, 0), WaitForShow(kFirstTab), |
| ShowToast(ToastParams(ToastId::kLinkCopied)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| SelectTab(kTabStripElementId, 1), |
| WaitForHide(toasts::ToastView::kToastViewId)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, |
| GlobalScopedToastStaysOnTabChange) { |
| RunTestSequence(InstrumentTab(kFirstTab), |
| AddInstrumentedTab(kSecondTab, GetURL()), |
| SelectTab(kTabStripElementId, 0), WaitForShow(kFirstTab), |
| ShowToast(ToastParams(ToastId::kNonMilestoneUpdate)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| SelectTab(kTabStripElementId, 1), |
| EnsurePresent(toasts::ToastView::kToastViewId)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, |
| HideTabScopedToastOnNavigation) { |
| RunTestSequence(InstrumentTab(kFirstTab), |
| ShowToast(ToastParams(ToastId::kLinkCopied)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| NavigateWebContents(kFirstTab, GetURL()), |
| WaitForHide(toasts::ToastView::kToastViewId)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, |
| GlobalScopedToastStaysOnNavigation) { |
| RunTestSequence(InstrumentTab(kFirstTab), |
| ShowToast(ToastParams(ToastId::kNonMilestoneUpdate)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| NavigateWebContents(kFirstTab, GetURL()), |
| EnsurePresent(toasts::ToastView::kToastViewId)); |
| } |
| |
| // Tests that setting a menu model in `ToastParams` adds a menu button to the |
| // toast that runs the menu model and that interacting with a menu element |
| // closes the toast. |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, |
| MenuButtonClickOpensMenu) { |
| ToastParams params(ToastId::kPlusAddressOverride); |
| int counter = 0; |
| params.menu_model = std::make_unique<TestMenuModel>( |
| base::BindLambdaForTesting([&counter]() { ++counter; })); |
| RunTestSequence(ShowToast(std::move(params)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| EnsurePresent(toasts::ToastView::kToastMenuButton), |
| PressButton(toasts::ToastView::kToastMenuButton), |
| WaitForShow(kSampleMenuItem), SelectMenuItem(kSampleMenuItem), |
| WaitForHide(toasts::ToastView::kToastViewId), |
| Check([&]() { return counter == 1; })); |
| } |
| |
| // Tests that attempting to close the `ToastView` does not succeed while the |
| // menu is open. If that happens, the `ToastView` is closed once the menu |
| // closes. |
| |
| // TODO(crbug.com/398296825): Flaky on Windows builds. |
| #if BUILDFLAG(IS_WIN) |
| #define MAYBE_ToastDoesNotCloseWhileMenuIsOpen \ |
| DISABLED_ToastDoesNotCloseWhileMenuIsOpen |
| #else |
| #define MAYBE_ToastDoesNotCloseWhileMenuIsOpen ToastDoesNotCloseWhileMenuIsOpen |
| #endif |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, |
| MAYBE_ToastDoesNotCloseWhileMenuIsOpen) { |
| #if BUILDFLAG(IS_OZONE) |
| if (ui::OzonePlatform::GetPlatformNameForTest() == "wayland") { |
| GTEST_SKIP() << "Flaky in Wayland due to way events are routed and bounds " |
| "are reported"; |
| } |
| #endif |
| ToastParams params(ToastId::kPlusAddressOverride); |
| params.menu_model = std::make_unique<TestMenuModel>(base::DoNothing()); |
| RunTestSequence(ShowToast(std::move(params)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| EnsurePresent(toasts::ToastView::kToastMenuButton), |
| PressButton(toasts::ToastView::kToastMenuButton), |
| WaitForShow(kSampleMenuItem), |
| EnsurePresent(toasts::ToastView::kToastViewId), |
| FireToastCloseTimer(), |
| EnsurePresent(toasts::ToastView::kToastViewId), |
| MoveMouseTo(toasts::ToastView::kToastMenuButton), |
| ClickMouse(), WaitForHide(toasts::ToastView::kToastViewId)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, |
| ToastReactToOmniboxFocus) { |
| LocationBar* const location_bar = browser()->window()->GetLocationBar(); |
| ASSERT_TRUE(location_bar); |
| OmniboxView* const omnibox_view = location_bar->GetOmniboxView(); |
| ASSERT_TRUE(omnibox_view); |
| browser()->window()->SetFocusToLocationBar(true); |
| ASSERT_FALSE(omnibox_view->model()->PopupIsOpen()); |
| |
| // Even though the omnibox is focused, the toast should still show because |
| // the omnibox doesn't have a popup and the user isn't interacting with the |
| // omnibox. |
| ToastController* const toast_controller = GetToastController(); |
| EXPECT_TRUE( |
| toast_controller->MaybeShowToast(ToastParams(ToastId::kLinkCopied))); |
| EXPECT_TRUE(toast_controller->IsShowingToast()); |
| EXPECT_TRUE(toast_controller->GetToastWidgetForTesting()->IsVisible()); |
| |
| // Omnibox should still show even when focus is removed from the omnibox. |
| RemoveOmniboxFocus(); |
| EXPECT_TRUE(toast_controller->IsShowingToast()); |
| EXPECT_TRUE(toast_controller->GetToastWidgetForTesting()->IsVisible()); |
| |
| // Focus the omnibox again should cause the toast to no longer be visible |
| // because we are focusing after the toast is already shown. |
| browser()->window()->SetFocusToLocationBar(true); |
| EXPECT_TRUE(toast_controller->IsShowingToast()); |
| EXPECT_FALSE(toast_controller->GetToastWidgetForTesting()->IsVisible()); |
| } |
| |
| // TODO(crbug.com/427355902): Flaky on Linux. |
| #if BUILDFLAG(IS_LINUX) |
| #define MAYBE_HidesWhenOmniboxPopupShows DISABLED_HidesWhenOmniboxPopupShows |
| #else |
| #define MAYBE_HidesWhenOmniboxPopupShows HidesWhenOmniboxPopupShows |
| #endif |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, |
| MAYBE_HidesWhenOmniboxPopupShows) { |
| // Even though the omnibox is focused, the toast should still show because |
| // the omnibox doesn't have a popup and the user isn't interacting with the |
| // omnibox. |
| ToastController* const toast_controller = GetToastController(); |
| EXPECT_TRUE( |
| toast_controller->MaybeShowToast(ToastParams(ToastId::kLinkCopied))); |
| EXPECT_TRUE(toast_controller->IsShowingToast()); |
| EXPECT_TRUE(toast_controller->GetToastWidgetForTesting()->IsVisible()); |
| |
| // Trigger the omnibox popup to show. |
| LocationBar* const location_bar = browser()->window()->GetLocationBar(); |
| ASSERT_TRUE(location_bar); |
| OmniboxView* const omnibox_view = location_bar->GetOmniboxView(); |
| ASSERT_TRUE(omnibox_view); |
| ASSERT_FALSE(omnibox_view->model()->PopupIsOpen()); |
| omnibox_view->OnBeforePossibleChange(); |
| omnibox_view->SetUserText(u"hello world"); |
| omnibox_view->OnAfterPossibleChange(true); |
| |
| ASSERT_TRUE(omnibox_view->model()->PopupIsOpen()); |
| |
| // The toast widget should no longer be visible because there is a popup. |
| EXPECT_TRUE(toast_controller->IsShowingToast()); |
| EXPECT_FALSE(toast_controller->GetToastWidgetForTesting()->IsVisible()); |
| |
| // Toast widget is visible again after the omnibox is no longer focused. |
| RemoveOmniboxFocus(); |
| ASSERT_FALSE(omnibox_view->model()->PopupIsOpen()); |
| EXPECT_TRUE(toast_controller->IsShowingToast()); |
| EXPECT_TRUE(toast_controller->GetToastWidgetForTesting()->IsVisible()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, |
| HidesWhenTypingInOmnibox) { |
| browser()->window()->SetFocusToLocationBar(true); |
| |
| // Even though the omnibox is focused, the toast should still show because |
| // the omnibox doesn't have a popup and the user isn't interacting with the |
| // omnibox. |
| ToastController* const toast_controller = GetToastController(); |
| EXPECT_TRUE( |
| toast_controller->MaybeShowToast(ToastParams(ToastId::kLinkCopied))); |
| EXPECT_TRUE(toast_controller->IsShowingToast()); |
| EXPECT_TRUE(toast_controller->GetToastWidgetForTesting()->IsVisible()); |
| |
| // Start typing in the omnibox. |
| auto omnibox_input_waiter = std::make_unique<OmniboxInputWaiter>( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_A, false, |
| false, false, false)); |
| omnibox_input_waiter->Wait(); |
| |
| // The toast widget should no longer be visible because we are typing. |
| EXPECT_TRUE(toast_controller->IsShowingToast()); |
| EXPECT_FALSE(toast_controller->GetToastWidgetForTesting()->IsVisible()); |
| |
| // Toast widget is visible again after the omnibox is no longer focused. |
| RemoveOmniboxFocus(); |
| EXPECT_TRUE(toast_controller->IsShowingToast()); |
| EXPECT_TRUE(toast_controller->GetToastWidgetForTesting()->IsVisible()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, RecordToastShows) { |
| base::HistogramTester histogram_tester; |
| RunTestSequence( |
| InstrumentTab(kFirstTab), AddInstrumentedTab(kSecondTab, GetURL()), |
| SelectTab(kTabStripElementId, 0), WaitForShow(kFirstTab), Do([&]() { |
| histogram_tester.ExpectBucketCount("Toast.TriggeredToShow", |
| ToastId::kLinkCopied, 0); |
| }), |
| ShowToast(ToastParams(ToastId::kLinkCopied)), |
| WaitForShow(toasts::ToastView::kToastViewId), Do([&]() { |
| histogram_tester.ExpectBucketCount("Toast.TriggeredToShow", |
| ToastId::kLinkCopied, 1); |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, |
| RecordToastDismissReason) { |
| base::HistogramTester histogram_tester; |
| RunTestSequence( |
| InstrumentTab(kFirstTab), AddInstrumentedTab(kSecondTab, GetURL()), |
| SelectTab(kTabStripElementId, 0), WaitForShow(kFirstTab), Do([&]() { |
| histogram_tester.ExpectBucketCount("Toast.LinkCopied.Dismissed", |
| toasts::ToastCloseReason::kAbort, 0); |
| }), |
| ShowToast(ToastParams(ToastId::kLinkCopied)), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| SelectTab(kTabStripElementId, 1), |
| WaitForHide(toasts::ToastView::kToastViewId), Do([&]() { |
| histogram_tester.ExpectBucketCount("Toast.LinkCopied.Dismissed", |
| toasts::ToastCloseReason::kAbort, 1); |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, |
| ToastRendersOverWebContents) { |
| #if BUILDFLAG(IS_MAC) |
| FullscreenController* const fullscreen_controller = |
| browser() |
| ->GetFeatures() |
| .exclusive_access_manager() |
| ->fullscreen_controller(); |
| fullscreen_controller->set_is_tab_fullscreen_for_testing(true); |
| #else |
| ui_test_utils::FullscreenWaiter waiter(browser(), {.tab_fullscreen = true}); |
| content::WebContents* const active_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| active_contents->GetDelegate()->EnterFullscreenModeForTab( |
| active_contents->GetPrimaryMainFrame(), {}); |
| waiter.Wait(); |
| #endif |
| |
| ToastController* const toast_controller = GetToastController(); |
| EXPECT_TRUE( |
| toast_controller->MaybeShowToast(ToastParams(ToastId::kLinkCopied))); |
| const gfx::Rect toast_bounds = |
| toast_controller->GetToastViewForTesting()->GetBoundsInScreen(); |
| const gfx::Rect web_view_bounds = |
| BrowserView::GetBrowserViewForBrowser(browser()) |
| ->GetActiveContentsWebView() |
| ->GetBoundsInScreen(); |
| EXPECT_TRUE(web_view_bounds.Contains(toast_bounds)); |
| } |
| |
| // Regression test for http://crbug.com/383898425 |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, |
| HandlesReducedAnimation) { |
| ToastController* const toast_controller = GetToastController(); |
| { |
| // Show toast while rich animations are disabled. |
| const gfx::AnimationTestApi::RenderModeResetter disable_rich_animations = |
| gfx::AnimationTestApi::SetRichAnimationRenderMode( |
| gfx::Animation::RichAnimationRenderMode::FORCE_DISABLED); |
| gfx::Animation::SetPrefersReducedMotionForTesting(true); |
| EXPECT_TRUE(gfx::Animation::PrefersReducedMotion()); |
| EXPECT_TRUE( |
| toast_controller->MaybeShowToast(ToastParams(ToastId::kLinkCopied))); |
| } |
| { |
| // Animate out toast (due to new toast showing) after enabling animations. |
| const gfx::AnimationTestApi::RenderModeResetter disable_rich_animations = |
| gfx::AnimationTestApi::SetRichAnimationRenderMode( |
| gfx::Animation::RichAnimationRenderMode::FORCE_ENABLED); |
| EXPECT_TRUE( |
| toast_controller->MaybeShowToast(ToastParams(ToastId::kLinkCopied))); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ToastControllerInteractiveTest, |
| ShowPinnedTabToastOnTabCloseViaKeyboardShortcut) { |
| ui::Accelerator close_tab_accelerator; |
| ASSERT_TRUE(BrowserView::GetBrowserViewForBrowser(browser())->GetAccelerator( |
| IDC_CLOSE_TAB, &close_tab_accelerator)); |
| |
| RunTestSequence( |
| // Add a pinned tab. |
| InstrumentTab(kFirstTab), WaitForShow(kFirstTab), |
| AddInstrumentedTab(kSecondTab, GetURL()), |
| SelectTab(kTabStripElementId, 0), |
| Do([&]() { browser()->tab_strip_model()->SetTabPinned(0, true); }), |
| // Expect that closing the tab with an accelerator will show a toast. |
| SendAccelerator(kBrowserViewElementId, close_tab_accelerator), |
| WaitForShow(toasts::ToastView::kToastViewId), |
| CheckResult([&]() { return browser()->tab_strip_model()->count(); }, 2), |
| // Expect that we can close the tab by pressing the accelerator again. |
| SendAccelerator(kBrowserViewElementId, close_tab_accelerator), |
| WaitForHide(toasts::ToastView::kToastViewId), |
| CheckResult([&]() { return browser()->tab_strip_model()->count(); }, 1)); |
| } |