| // Copyright 2025 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/ui/autofill/bubble_manager_impl.h" |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "base/memory/raw_ref.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "chrome/browser/ui/autofill/bubble_controller_base.h" |
| #include "chrome/browser/ui/autofill/bubble_manager.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/tabs/public/mock_tab_interface.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/test_web_contents_factory.h" |
| #include "content/public/test/web_contents_tester.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace autofill { |
| |
| using ::testing::_; |
| using ::testing::InSequence; |
| using ::testing::Return; |
| |
| class FakeTabInterface : public tabs::MockTabInterface { |
| public: |
| ~FakeTabInterface() override = default; |
| explicit FakeTabInterface(TestingProfile* testing_profile); |
| base::CallbackListSubscription RegisterDidActivate( |
| base::RepeatingCallback<void(TabInterface*)> cb) override; |
| base::CallbackListSubscription RegisterWillDeactivate( |
| base::RepeatingCallback<void(TabInterface*)> cb) override; |
| content::WebContents* GetContents() const override { return web_contents_; } |
| void Activate(); |
| void Deactivate(); |
| |
| private: |
| // Only created if a non-null profile is provided. |
| std::unique_ptr<content::TestWebContentsFactory> web_contents_factory_; |
| // Owned by `web_contents_factory_`. |
| raw_ptr<content::WebContents> web_contents_; |
| bool is_activated_ = false; |
| base::RepeatingCallbackList<void(TabInterface*)> activation_callbacks_; |
| base::RepeatingCallbackList<void(TabInterface*)> deactivation_callbacks_; |
| }; |
| |
| FakeTabInterface::FakeTabInterface(TestingProfile* testing_profile) { |
| if (testing_profile) { |
| web_contents_factory_ = std::make_unique<content::TestWebContentsFactory>(); |
| web_contents_ = web_contents_factory_->CreateWebContents(testing_profile); |
| } |
| } |
| |
| base::CallbackListSubscription FakeTabInterface::RegisterDidActivate( |
| base::RepeatingCallback<void(TabInterface*)> cb) { |
| return activation_callbacks_.Add(cb); |
| } |
| |
| base::CallbackListSubscription FakeTabInterface::RegisterWillDeactivate( |
| base::RepeatingCallback<void(TabInterface*)> cb) { |
| return deactivation_callbacks_.Add(cb); |
| } |
| |
| void FakeTabInterface::Activate() { |
| is_activated_ = true; |
| activation_callbacks_.Notify(this); |
| } |
| |
| void FakeTabInterface::Deactivate() { |
| is_activated_ = false; |
| deactivation_callbacks_.Notify(this); |
| } |
| |
| // A mock implementation of BubbleControllerBase to use in tests. |
| class MockBubbleController : public BubbleControllerBase { |
| public: |
| MockBubbleController() { |
| // Default behavior for mock methods. |
| ON_CALL(*this, ShowBubble).WillByDefault([this]() { is_shown_ = true; }); |
| ON_CALL(*this, HideBubble).WillByDefault([this]() { is_shown_ = false; }); |
| ON_CALL(*this, IsShowingBubble).WillByDefault([this]() { |
| return is_shown_; |
| }); |
| ON_CALL(*this, GetBubbleControllerBaseWeakPtr).WillByDefault([this]() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| }); |
| } |
| |
| MOCK_METHOD(void, ShowBubble, (), (override)); |
| MOCK_METHOD(void, HideBubble, (), (override)); |
| MOCK_METHOD(BubbleType, GetBubbleType, (), (const, override)); |
| MOCK_METHOD(bool, IsShowingBubble, (), (const, override)); |
| MOCK_METHOD(bool, IsMouseHovered, (), (const, override)); |
| MOCK_METHOD(base::WeakPtr<BubbleControllerBase>, |
| GetBubbleControllerBaseWeakPtr, |
| (), |
| (override)); |
| |
| void SetBubbleType(BubbleType type) { |
| ON_CALL(*this, GetBubbleType).WillByDefault(Return(type)); |
| } |
| |
| private: |
| bool is_shown_ = false; |
| base::WeakPtrFactory<BubbleControllerBase> weak_ptr_factory_{this}; |
| }; |
| |
| class BubbleManagerImplTest : public ::testing::Test { |
| public: |
| BubbleManagerImplTest() |
| : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| |
| // Creates and returns a new `MockBubbleController` with `bubble_type`. |
| std::unique_ptr<MockBubbleController> CreateController( |
| BubbleType bubble_type) { |
| std::unique_ptr<MockBubbleController> controller = |
| std::make_unique<MockBubbleController>(); |
| controller->SetBubbleType(bubble_type); |
| return controller; |
| } |
| |
| protected: |
| void SetUp() override { |
| Test::SetUp(); |
| |
| tab_interface_ = std::make_unique<FakeTabInterface>(&profile_); |
| bubble_manager_ = std::make_unique<BubbleManagerImpl>(tab_interface_.get()); |
| tab_interface_->Activate(); |
| } |
| |
| void FastForwardBy(base::TimeDelta time) { |
| task_environment_.FastForwardBy(time); |
| } |
| |
| BubbleManagerImpl& bubble_manager() const { return *bubble_manager_; } |
| |
| FakeTabInterface* tab_interface() const { return tab_interface_.get(); } |
| |
| TestingProfile* profile() { return &profile_; } |
| |
| base::HistogramTester histogram_tester_; |
| |
| private: |
| content::BrowserTaskEnvironment task_environment_; |
| TestingProfile profile_; |
| std::unique_ptr<FakeTabInterface> tab_interface_; |
| std::unique_ptr<BubbleManagerImpl> bubble_manager_; |
| }; |
| |
| // Test that requesting a bubble when none is active shows it immediately. |
| TEST_F(BubbleManagerImplTest, RequestShow_NoActiveBubble_ShowsImmediately) { |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| |
| EXPECT_CALL(*address_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/false); |
| EXPECT_TRUE(address_controller->IsShowingBubble()); |
| |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.RequestShow", |
| BubbleType::kSaveUpdateAddress, 1); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.Show.NoActiveBubble", |
| BubbleType::kSaveUpdateAddress, 1); |
| histogram_tester_.ExpectTotalCount("Autofill.Bubble.RequestShow.ForceShow", |
| 0); |
| } |
| |
| // Test that a higher-priority bubble preempts a lower-priority one. |
| TEST_F(BubbleManagerImplTest, RequestShow_HigherPriority_PreemptsActive) { |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| std::unique_ptr<MockBubbleController> card_controller = |
| CreateController(BubbleType::kSaveUpdateCard); |
| |
| EXPECT_CALL(*address_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/false); |
| ASSERT_TRUE(address_controller->IsShowingBubble()); |
| |
| { |
| InSequence sequence; |
| EXPECT_CALL(*address_controller, HideBubble()); |
| EXPECT_CALL(*card_controller, ShowBubble()); |
| } |
| |
| bubble_manager().RequestShowController(*card_controller, |
| /*force_show=*/false); |
| EXPECT_FALSE(address_controller->IsShowingBubble()); |
| EXPECT_TRUE(card_controller->IsShowingBubble()); |
| |
| histogram_tester_.ExpectBucketCount("Autofill.Bubble.RequestShow", |
| BubbleType::kSaveUpdateAddress, 1); |
| histogram_tester_.ExpectBucketCount("Autofill.Bubble.RequestShow", |
| BubbleType::kSaveUpdateCard, 1); |
| histogram_tester_.ExpectTotalCount("Autofill.Bubble.RequestShow", 2); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.Show.NoActiveBubble", |
| BubbleType::kSaveUpdateAddress, 1); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.Show.Preemption", |
| BubbleType::kSaveUpdateCard, 1); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.WasPreempted", |
| BubbleType::kSaveUpdateAddress, 1); |
| } |
| |
| // Test that hiding the active bubble shows the next highest-priority one from |
| // the queue. |
| TEST_F(BubbleManagerImplTest, HideActiveBubble_WithPendingRequest_ShowsNext) { |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| std::unique_ptr<MockBubbleController> card_controller = |
| CreateController(BubbleType::kSaveUpdateCard); |
| |
| // Show card bubble, then queue address bubble. |
| EXPECT_CALL(*card_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*card_controller, |
| /*force_show=*/false); |
| ASSERT_TRUE(card_controller->IsShowingBubble()); |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/false); |
| ASSERT_FALSE(address_controller->IsShowingBubble()); |
| |
| // When the active (card) bubble is hidden, the address bubble should be shown |
| // from the queue. |
| EXPECT_CALL(*address_controller, ShowBubble()); |
| |
| // Hide the card bubble. |
| bubble_manager().OnBubbleHiddenByController(*card_controller); |
| |
| // The state of the card controller should now be false. |
| card_controller->HideBubble(); |
| |
| EXPECT_FALSE(card_controller->IsShowingBubble()); |
| EXPECT_TRUE(address_controller->IsShowingBubble()); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.Queue.ShownFromQueue", |
| BubbleType::kSaveUpdateAddress, 1); |
| } |
| |
| // Tests that when a high priority bubble is shown, the lower priority bubbles |
| // from the queue do not get shown. |
| TEST_F(BubbleManagerImplTest, |
| HideBubbleDuringPreemption_DoesNotShowNextFromQueue) { |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| std::unique_ptr<MockBubbleController> card_controller = |
| CreateController(BubbleType::kSaveUpdateCard); |
| std::unique_ptr<MockBubbleController> password_controller = |
| CreateController(BubbleType::kPassword); |
| |
| // Show card bubble and then queue address bubble. |
| EXPECT_CALL(*card_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*card_controller, |
| /*force_show=*/false); |
| ASSERT_TRUE(card_controller->IsShowingBubble()); |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/false); |
| ASSERT_FALSE(address_controller->IsShowingBubble()); |
| |
| // Request a high-priority password bubble. This will preempt the active |
| // card bubble. |
| { |
| InSequence sequence; |
| EXPECT_CALL(*card_controller, HideBubble()); |
| EXPECT_CALL(*password_controller, ShowBubble()); |
| } |
| |
| // Ensure that the queued address bubble is never shown during this process. |
| EXPECT_CALL(*address_controller, ShowBubble()).Times(0); |
| bubble_manager().RequestShowController(*password_controller, |
| /*force_show=*/false); |
| |
| // The password bubble is now active, and the other two are not. |
| EXPECT_TRUE(password_controller->IsShowingBubble()); |
| EXPECT_FALSE(address_controller->IsShowingBubble()); |
| EXPECT_FALSE(card_controller->IsShowingBubble()); |
| } |
| |
| // Test that a lower-priority bubble is queued if a higher-priority one is |
| // active. |
| TEST_F(BubbleManagerImplTest, RequestShow_LowerPriority_QueuesRequest) { |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| std::unique_ptr<MockBubbleController> card_controller = |
| CreateController(BubbleType::kSaveUpdateCard); |
| |
| EXPECT_CALL(*card_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*card_controller, |
| /*force_show=*/false); |
| ASSERT_TRUE(card_controller->IsShowingBubble()); |
| |
| EXPECT_CALL(*address_controller, ShowBubble()).Times(0); |
| EXPECT_CALL(*card_controller, HideBubble()).Times(0); |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/false); |
| |
| EXPECT_TRUE(card_controller->IsShowingBubble()); |
| EXPECT_FALSE(address_controller->IsShowingBubble()); |
| histogram_tester_.ExpectUniqueSample( |
| "Autofill.Bubble.Queue.AddedDueToActiveBubble", |
| BubbleType::kSaveUpdateAddress, 1); |
| } |
| |
| // Test that a bubble with a preempt-same-type policy replaces an existing one. |
| TEST_F(BubbleManagerImplTest, |
| RequestShow_SameTypeWithPreemptPolicy_PreemptsActive) { |
| std::unique_ptr<MockBubbleController> password_controller_1 = |
| CreateController(BubbleType::kPassword); |
| std::unique_ptr<MockBubbleController> password_controller_2 = |
| CreateController(BubbleType::kPassword); |
| |
| // Show first password bubble and then replace it. |
| EXPECT_CALL(*password_controller_1, ShowBubble()); |
| bubble_manager().RequestShowController(*password_controller_1, |
| /*force_show=*/false); |
| ASSERT_TRUE(password_controller_1->IsShowingBubble()); |
| |
| { |
| InSequence sequence; |
| EXPECT_CALL(*password_controller_1, HideBubble()); |
| EXPECT_CALL(*password_controller_2, ShowBubble()); |
| } |
| |
| bubble_manager().RequestShowController(*password_controller_2, |
| /*force_show=*/false); |
| EXPECT_FALSE(password_controller_1->IsShowingBubble()); |
| EXPECT_TRUE(password_controller_2->IsShowingBubble()); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.WasPreempted", |
| BubbleType::kPassword, 1); |
| } |
| |
| // Test that a pending request is ignored if a request of the same type |
| // exists and the timeout has not been reached for bubbles without the preempt |
| // policy. |
| TEST_F(BubbleManagerImplTest, AddToQueue_DuplicateType_IgnoredBeforeTimeout) { |
| std::unique_ptr<MockBubbleController> password_controller = |
| CreateController(BubbleType::kPassword); |
| std::unique_ptr<MockBubbleController> address_controller_1 = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| std::unique_ptr<MockBubbleController> address_controller_2 = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| |
| // Show password bubbles and couple of address bubbles back-to-back. |
| EXPECT_CALL(*password_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*password_controller, |
| /*force_show=*/false); |
| bubble_manager().RequestShowController(*address_controller_1, |
| /*force_show=*/false); |
| bubble_manager().RequestShowController(*address_controller_2, |
| /*force_show=*/false); |
| |
| bubble_manager().OnBubbleHiddenByController(*password_controller); |
| |
| EXPECT_TRUE(address_controller_1->IsShowingBubble()); |
| EXPECT_FALSE(address_controller_2->IsShowingBubble()); |
| |
| // Ensure `address_controller_2` is never shown. |
| bubble_manager().OnBubbleHiddenByController(*address_controller_1); |
| EXPECT_FALSE(address_controller_2->IsShowingBubble()); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.Queue.Discarded", |
| BubbleType::kSaveUpdateAddress, 1); |
| } |
| |
| // Test that a pending request replaces an older one of the same type after |
| // timeout for bubbles without the preempt policy. |
| TEST_F(BubbleManagerImplTest, AddToQueue_DuplicateType_ReplacedAfterTimeout) { |
| std::unique_ptr<MockBubbleController> password_controller = |
| CreateController(BubbleType::kPassword); |
| std::unique_ptr<MockBubbleController> address_controller_1 = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| std::unique_ptr<MockBubbleController> address_controller_2 = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| |
| // Show password bubbles and address bubbles. Then add another address bubble |
| // after 3601 seconds. |
| EXPECT_CALL(*password_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*password_controller, |
| /*force_show=*/false); |
| bubble_manager().RequestShowController(*address_controller_1, |
| /*force_show=*/false); |
| |
| FastForwardBy(base::Seconds(3601)); |
| bubble_manager().RequestShowController(*address_controller_2, |
| /*force_show=*/false); |
| |
| bubble_manager().OnBubbleHiddenByController(*password_controller); |
| |
| EXPECT_FALSE(address_controller_1->IsShowingBubble()); |
| EXPECT_TRUE(address_controller_2->IsShowingBubble()); |
| |
| // Ensure `address_controller_1` is never shown. |
| bubble_manager().OnBubbleHiddenByController(*address_controller_2); |
| EXPECT_FALSE(address_controller_1->IsShowingBubble()); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.Queue.TimedOut", |
| BubbleType::kSaveUpdateAddress, 1); |
| } |
| |
| // Test that a new bubble with a preempt policy always replaces a pending one. |
| TEST_F(BubbleManagerImplTest, |
| AddToQueue_DuplicateTypeWithPreemptPolicy_ReplacesImmediately) { |
| std::unique_ptr<MockBubbleController> password_controller_1 = |
| CreateController(BubbleType::kPassword); |
| std::unique_ptr<MockBubbleController> password_controller_2 = |
| CreateController(BubbleType::kPassword); |
| // It has higher priority than password bubbles. |
| std::unique_ptr<MockBubbleController> filled_card_controller = |
| CreateController(BubbleType::kFilledCardInformation); |
| |
| // Filled card controller is shown and then password controllers are added to |
| // the queue. |
| EXPECT_CALL(*filled_card_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*filled_card_controller, |
| /*force_show=*/false); |
| bubble_manager().RequestShowController(*password_controller_1, |
| /*force_show=*/false); |
| bubble_manager().RequestShowController(*password_controller_2, |
| /*force_show=*/false); |
| |
| bubble_manager().OnBubbleHiddenByController(*filled_card_controller); |
| EXPECT_FALSE(password_controller_1->IsShowingBubble()); |
| EXPECT_TRUE(password_controller_2->IsShowingBubble()); |
| |
| // Ensure `password_controller_1` is never shown. |
| bubble_manager().OnBubbleHiddenByController(*password_controller_2); |
| EXPECT_FALSE(password_controller_1->IsShowingBubble()); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.Queue.Replaced", |
| BubbleType::kPassword, 1); |
| } |
| |
| // Test that a higher-priority bubble does NOT preempt a lower-priority one if |
| // the mouse is hovered. |
| TEST_F(BubbleManagerImplTest, |
| RequestShow_HigherPriority_DoesNotPreemptIfHovered) { |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| std::unique_ptr<MockBubbleController> card_controller = |
| CreateController(BubbleType::kSaveUpdateCard); |
| |
| EXPECT_CALL(*address_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/false); |
| ASSERT_TRUE(address_controller->IsShowingBubble()); |
| |
| // Simulate mouse hover. |
| ON_CALL(*address_controller, IsMouseHovered).WillByDefault(Return(true)); |
| |
| // Card bubble should not be shown, address bubble should not be hidden. |
| EXPECT_CALL(*card_controller, ShowBubble()).Times(0); |
| EXPECT_CALL(*address_controller, HideBubble()).Times(0); |
| bubble_manager().RequestShowController(*card_controller, |
| /*force_show=*/false); |
| |
| EXPECT_TRUE(address_controller->IsShowingBubble()); |
| EXPECT_FALSE(card_controller->IsShowingBubble()); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.Queue.AddedDueToHover", |
| BubbleType::kSaveUpdateCard, 1); |
| } |
| |
| // Test that HasPendingBubble returns false when no bubble is pending. |
| TEST_F(BubbleManagerImplTest, HasPendingBubble_NoBubble_ReturnsFalse) { |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| EXPECT_FALSE(bubble_manager().HasPendingBubbleOfSameType( |
| address_controller->GetBubbleType())); |
| } |
| |
| // Test that HasPendingBubble returns true for a valid, non-expired pending |
| // bubble. |
| TEST_F(BubbleManagerImplTest, HasPendingBubble_BubblePending_ReturnsTrue) { |
| std::unique_ptr<MockBubbleController> password_controller = |
| CreateController(BubbleType::kPassword); |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| std::unique_ptr<MockBubbleController> card_controller = |
| CreateController(BubbleType::kSaveUpdateCard); |
| |
| // Show a high-priority bubble to ensure the next one is queued. |
| EXPECT_CALL(*password_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*password_controller, |
| /*force_show=*/false); |
| |
| // Queue the address bubble. |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/false); |
| |
| // Check that the address bubble is correctly reported as pending. |
| EXPECT_TRUE(bubble_manager().HasPendingBubbleOfSameType( |
| address_controller->GetBubbleType())); |
| // Check that a bubble type not in the queue returns false. |
| EXPECT_FALSE(bubble_manager().HasPendingBubbleOfSameType( |
| card_controller->GetBubbleType())); |
| } |
| |
| // Test that HasPendingBubble returns false for a timed-out bubble. |
| TEST_F(BubbleManagerImplTest, |
| HasPendingBubble_BubblePending_TimedOut_ReturnsFalse) { |
| std::unique_ptr<MockBubbleController> password_controller = |
| CreateController(BubbleType::kPassword); |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| |
| // Show a high-priority bubble. |
| EXPECT_CALL(*password_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*password_controller, |
| /*force_show=*/false); |
| |
| // Queue the address bubble and confirm it's pending. |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/false); |
| ASSERT_TRUE(bubble_manager().HasPendingBubbleOfSameType( |
| address_controller->GetBubbleType())); |
| |
| // Fast forward time past the timeout. |
| FastForwardBy(base::Seconds(3601)); |
| |
| // Now, checking for the bubble should return false because it has timed out. |
| EXPECT_FALSE(bubble_manager().HasPendingBubbleOfSameType( |
| address_controller->GetBubbleType())); |
| } |
| |
| // Test that a bubble is timed out from the queue. |
| TEST_F(BubbleManagerImplTest, ProcessPendingBubbles_TimedOut) { |
| std::unique_ptr<MockBubbleController> password_controller = |
| CreateController(BubbleType::kPassword); |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| |
| // Show a high-priority bubble. |
| EXPECT_CALL(*password_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*password_controller, |
| /*force_show=*/false); |
| |
| // Queue the address bubble and confirm it's pending. |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/false); |
| ASSERT_TRUE(bubble_manager().HasPendingBubbleOfSameType( |
| address_controller->GetBubbleType())); |
| |
| // Fast forward time past the timeout. |
| FastForwardBy(base::Seconds(3601)); |
| |
| // Hiding the current bubble will trigger processing the pending bubbles. |
| // The address bubble should be timed out and not shown. |
| EXPECT_CALL(*address_controller, ShowBubble()).Times(0); |
| bubble_manager().OnBubbleHiddenByController(*password_controller); |
| |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.Queue.TimedOut", |
| BubbleType::kSaveUpdateAddress, 1); |
| } |
| |
| // Test that a force_show request preempts a higher-priority active bubble. |
| TEST_F(BubbleManagerImplTest, RequestShow_ForceShow_PreemptsActiveBubble) { |
| std::unique_ptr<MockBubbleController> card_controller = |
| CreateController(BubbleType::kSaveUpdateCard); |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| |
| // Show the high-priority bubble first. |
| EXPECT_CALL(*card_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*card_controller, |
| /*force_show=*/false); |
| ASSERT_TRUE(card_controller->IsShowingBubble()); |
| |
| // Expect the active bubble to be hidden and the new one shown. |
| { |
| InSequence sequence; |
| EXPECT_CALL(*card_controller, HideBubble()); |
| EXPECT_CALL(*address_controller, ShowBubble()); |
| } |
| |
| // Force show the lower-priority bubble. It should preempt the active one. |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/true); |
| EXPECT_FALSE(card_controller->IsShowingBubble()); |
| EXPECT_TRUE(address_controller->IsShowingBubble()); |
| |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.RequestShow.ForceShow", |
| BubbleType::kSaveUpdateAddress, 1); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.WasPreempted", |
| BubbleType::kSaveUpdateCard, 1); |
| } |
| |
| // Test that the active bubble is hidden and queued when the tab visibility |
| // changes to hidden. |
| TEST_F(BubbleManagerImplTest, OnVisibilityChanged_Hidden_HidesAndQueuesBubble) { |
| std::unique_ptr<MockBubbleController> card_controller = |
| CreateController(BubbleType::kSaveUpdateCard); |
| |
| // Show a bubble, making it active. |
| EXPECT_CALL(*card_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*card_controller, |
| /*force_show=*/false); |
| ASSERT_TRUE(card_controller->IsShowingBubble()); |
| |
| // Expect the bubble to be hidden when visibility changes. |
| EXPECT_CALL(*card_controller, HideBubble()); |
| |
| // Simulate the tab becoming hidden. |
| tab_interface()->Deactivate(); |
| |
| // The bubble should no longer be considered "showing". |
| EXPECT_FALSE(card_controller->IsShowingBubble()); |
| |
| // To verify it was queued, we can simulate the tab becoming visible again. |
| EXPECT_CALL(*card_controller, ShowBubble()); |
| tab_interface()->Activate(); |
| EXPECT_TRUE(card_controller->IsShowingBubble()); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.HideDueToTabHide", |
| BubbleType::kSaveUpdateCard, 1); |
| } |
| |
| // Test that a pending bubble is shown when the tab visibility changes to |
| // visible. |
| TEST_F(BubbleManagerImplTest, OnVisibilityChanged_Visible_ShowsPendingBubble) { |
| std::unique_ptr<MockBubbleController> card_controller = |
| CreateController(BubbleType::kSaveUpdateCard); |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| |
| // Show a high-priority bubble, then queue a lower-priority one. |
| EXPECT_CALL(*card_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*card_controller, |
| /*force_show=*/false); |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/false); |
| ASSERT_TRUE(card_controller->IsShowingBubble()); |
| ASSERT_FALSE(address_controller->IsShowingBubble()); |
| |
| // Hide the active bubble and simulate the tab becoming hidden. |
| // This leaves the address bubble in the queue. |
| EXPECT_CALL(*card_controller, HideBubble()); |
| tab_interface()->Deactivate(); |
| ASSERT_FALSE(card_controller->IsShowingBubble()); |
| |
| // When the tab becomes visible again, the card bubble should show again as |
| // it has higher priority. |
| EXPECT_CALL(*card_controller, ShowBubble()); |
| tab_interface()->Activate(); |
| |
| EXPECT_TRUE(card_controller->IsShowingBubble()); |
| } |
| |
| // Test that no action is taken when the tab becomes hidden and there is no |
| // active bubble. |
| TEST_F(BubbleManagerImplTest, OnVisibilityChanged_Hidden_NoActiveBubble) { |
| // Ensure no mock calls are expected on any controllers. |
| // We can create a controller but never show it. |
| std::unique_ptr<MockBubbleController> card_controller = |
| CreateController(BubbleType::kSaveUpdateCard); |
| EXPECT_CALL(*card_controller, HideBubble()).Times(0); |
| |
| // Simulate the tab becoming hidden. The test will fail if any unexpected |
| // methods are called. |
| tab_interface()->Deactivate(); |
| histogram_tester_.ExpectTotalCount("Autofill.Bubble.HideDueToTabHide", 0); |
| } |
| |
| // Test that `force_show` preempts an active bubble regardless of priority or |
| // hover state. |
| TEST_F(BubbleManagerImplTest, |
| RequestShow_ForceShow_PreemptsRegardlessOfPriority) { |
| std::unique_ptr<MockBubbleController> card_controller = |
| CreateController(BubbleType::kSaveUpdateCard); |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| |
| // Show the higher-priority card bubble. |
| EXPECT_CALL(*card_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*card_controller, |
| /*force_show=*/false); |
| ASSERT_TRUE(card_controller->IsShowingBubble()); |
| |
| // Simulate hovering to ensure `force_show` bypasses it. |
| ON_CALL(*card_controller, IsMouseHovered).WillByDefault(Return(true)); |
| { |
| InSequence sequence; |
| EXPECT_CALL(*card_controller, HideBubble()); |
| EXPECT_CALL(*address_controller, ShowBubble()); |
| } |
| |
| // Force show the lower-priority address bubble. |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/true); |
| EXPECT_FALSE(card_controller->IsShowingBubble()); |
| EXPECT_TRUE(address_controller->IsShowingBubble()); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.RequestShow.ForceShow", |
| BubbleType::kSaveUpdateAddress, 1); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.WasPreempted", |
| BubbleType::kSaveUpdateCard, 1); |
| } |
| |
| // Test that the active bubble is hidden and queued when the tab is deactivated. |
| TEST_F(BubbleManagerImplTest, TabDeactivated_ActiveBubbleIsQueuedAndHidden) { |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| EXPECT_CALL(*address_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/false); |
| ASSERT_TRUE(address_controller->IsShowingBubble()); |
| |
| // Deactivating the tab should hide the bubble. |
| EXPECT_CALL(*address_controller, HideBubble()); |
| tab_interface()->Deactivate(); |
| EXPECT_FALSE(address_controller->IsShowingBubble()); |
| |
| // When the tab is reactivated, the bubble should be shown again from the |
| // queue. |
| EXPECT_CALL(*address_controller, ShowBubble()); |
| tab_interface()->Activate(); |
| EXPECT_TRUE(address_controller->IsShowingBubble()); |
| histogram_tester_.ExpectUniqueSample("Autofill.Bubble.HideDueToTabHide", |
| BubbleType::kSaveUpdateAddress, 1); |
| } |
| |
| // Test that `ProcessPendingBubbles` cleans up stale pointers from the queue. |
| TEST_F(BubbleManagerImplTest, ProcessPendingBubbles_CleansUpStaleControllers) { |
| auto card_controller = CreateController(BubbleType::kSaveUpdateCard); |
| auto address_controller = CreateController(BubbleType::kSaveUpdateAddress); |
| |
| EXPECT_CALL(*card_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*card_controller, |
| /*force_show=*/false); |
| |
| // Queue the address bubble. |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/false); |
| EXPECT_TRUE(card_controller->IsShowingBubble()); |
| EXPECT_FALSE(address_controller->IsShowingBubble()); |
| |
| // Destroy the address controller, invalidating its weak pointer. |
| address_controller.reset(); |
| bubble_manager().OnBubbleHiddenByController(*card_controller); |
| } |
| |
| // Test that the time a bubble spends in the queue is logged correctly. |
| TEST_F(BubbleManagerImplTest, HideActiveBubble_LogsTimeInQueue) { |
| std::unique_ptr<MockBubbleController> address_controller = |
| CreateController(BubbleType::kSaveUpdateAddress); |
| std::unique_ptr<MockBubbleController> card_controller = |
| CreateController(BubbleType::kSaveUpdateCard); |
| |
| // Show card bubble, then queue address bubble. |
| EXPECT_CALL(*card_controller, ShowBubble()); |
| bubble_manager().RequestShowController(*card_controller, |
| /*force_show=*/false); |
| bubble_manager().RequestShowController(*address_controller, |
| /*force_show=*/false); |
| |
| // Advance time by 5 seconds. |
| FastForwardBy(base::Seconds(5)); |
| |
| // Hide the active bubble, which should trigger the queued bubble to show. |
| EXPECT_CALL(*address_controller, ShowBubble()); |
| bubble_manager().OnBubbleHiddenByController(*card_controller); |
| histogram_tester_.ExpectUniqueTimeSample( |
| "Autofill.Bubble.Queue.TimeInQueue.SaveUpdateAddress", base::Seconds(5), |
| 1); |
| } |
| |
| } // namespace autofill |