| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/bubble/bubble_manager.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "components/bubble/bubble_controller.h" |
| #include "components/bubble/bubble_manager_mocks.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| // A "show chain" happens when a bubble decides to show another bubble on close. |
| // All bubbles must be iterated to handle a close event. If a bubble shows |
| // another bubble while it's being closed, the iterator can get messed up. |
| class ChainShowBubbleDelegate : public MockBubbleDelegate { |
| public: |
| // |chained_bubble| can be nullptr if not interested in getting a reference to |
| // the chained bubble. |
| ChainShowBubbleDelegate(BubbleManager* manager, |
| std::unique_ptr<BubbleDelegate> delegate, |
| BubbleReference* chained_bubble) |
| : manager_(manager), |
| delegate_(std::move(delegate)), |
| chained_bubble_(chained_bubble), |
| closed_(false) { |
| EXPECT_CALL(*this, ShouldClose(testing::_)).WillOnce(testing::Return(true)); |
| } |
| |
| ~ChainShowBubbleDelegate() override { EXPECT_TRUE(closed_); } |
| |
| void DidClose(BubbleCloseReason reason) override { |
| MockBubbleDelegate::DidClose(reason); |
| BubbleReference ref = manager_->ShowBubble(std::move(delegate_)); |
| if (chained_bubble_) |
| *chained_bubble_ = ref; |
| closed_ = true; |
| } |
| |
| private: |
| BubbleManager* manager_; |
| std::unique_ptr<BubbleDelegate> delegate_; |
| BubbleReference* chained_bubble_; |
| bool closed_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ChainShowBubbleDelegate); |
| }; |
| |
| // A "close chain" happens when a close event is received while another close |
| // event is in progress. Ex: Closing the BubbleUi will hide the bubble, causing |
| // it to lose focus, which causes another close event. This test simulates this |
| // by sending a close event during the |DidClose| method of a BubbleDelegate. |
| // Similarly to the show chain, a close chain can mess up the iterator. |
| class ChainCloseBubbleDelegate : public MockBubbleDelegate { |
| public: |
| ChainCloseBubbleDelegate(BubbleManager* manager) : manager_(manager) {} |
| |
| ~ChainCloseBubbleDelegate() override {} |
| |
| void DidClose(BubbleCloseReason reason) override { |
| manager_->CloseAllBubbles(BUBBLE_CLOSE_FOCUS_LOST); |
| } |
| |
| private: |
| BubbleManager* manager_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ChainCloseBubbleDelegate); |
| }; |
| |
| class MockBubbleManagerObserver : public BubbleManager::BubbleManagerObserver { |
| public: |
| MockBubbleManagerObserver() {} |
| ~MockBubbleManagerObserver() override {} |
| |
| MOCK_METHOD1(OnBubbleNeverShown, void(BubbleReference)); |
| MOCK_METHOD2(OnBubbleClosed, void(BubbleReference, BubbleCloseReason)); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockBubbleManagerObserver); |
| }; |
| |
| class BubbleManagerSubclass : public BubbleManager { |
| public: |
| using BubbleManager::CloseBubblesOwnedBy; |
| }; |
| |
| class BubbleManagerTest : public testing::Test { |
| public: |
| BubbleManagerTest(); |
| ~BubbleManagerTest() override {} |
| |
| void SetUp() override; |
| void TearDown() override; |
| |
| protected: |
| std::unique_ptr<BubbleManagerSubclass> manager_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(BubbleManagerTest); |
| }; |
| |
| BubbleManagerTest::BubbleManagerTest() {} |
| |
| void BubbleManagerTest::SetUp() { |
| testing::Test::SetUp(); |
| manager_.reset(new BubbleManagerSubclass); |
| } |
| |
| void BubbleManagerTest::TearDown() { |
| manager_.reset(); |
| testing::Test::TearDown(); |
| } |
| |
| TEST_F(BubbleManagerTest, ManagerShowsBubbleUi) { |
| std::unique_ptr<MockBubbleDelegate> delegate = MockBubbleDelegate::Default(); |
| |
| MockBubbleUi* bubble_ui = delegate->bubble_ui(); |
| EXPECT_CALL(*bubble_ui, Destroyed()); |
| EXPECT_CALL(*bubble_ui, Show(testing::_)); |
| EXPECT_CALL(*bubble_ui, Close()); |
| EXPECT_CALL(*bubble_ui, UpdateAnchorPosition()).Times(0); |
| |
| manager_->ShowBubble(std::move(delegate)); |
| } |
| |
| TEST_F(BubbleManagerTest, ManagerUpdatesBubbleUiAnchor) { |
| std::unique_ptr<MockBubbleDelegate> delegate = MockBubbleDelegate::Default(); |
| |
| MockBubbleUi* bubble_ui = delegate->bubble_ui(); |
| EXPECT_CALL(*bubble_ui, Destroyed()); |
| EXPECT_CALL(*bubble_ui, Show(testing::_)); |
| EXPECT_CALL(*bubble_ui, Close()); |
| EXPECT_CALL(*bubble_ui, UpdateAnchorPosition()); |
| |
| manager_->ShowBubble(std::move(delegate)); |
| manager_->UpdateAllBubbleAnchors(); |
| } |
| |
| TEST_F(BubbleManagerTest, CloseOnReferenceInvalidatesReference) { |
| BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default()); |
| |
| ASSERT_TRUE(ref->CloseBubble(BUBBLE_CLOSE_FOCUS_LOST)); |
| |
| ASSERT_FALSE(ref); |
| } |
| |
| TEST_F(BubbleManagerTest, CloseOnStubbornReferenceDoesNotInvalidate) { |
| BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Stubborn()); |
| |
| ASSERT_FALSE(ref->CloseBubble(BUBBLE_CLOSE_FOCUS_LOST)); |
| |
| ASSERT_TRUE(ref); |
| } |
| |
| TEST_F(BubbleManagerTest, CloseInvalidatesReference) { |
| BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default()); |
| |
| ASSERT_TRUE(manager_->CloseBubble(ref, BUBBLE_CLOSE_FOCUS_LOST)); |
| |
| ASSERT_FALSE(ref); |
| } |
| |
| TEST_F(BubbleManagerTest, CloseAllInvalidatesReference) { |
| BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default()); |
| |
| manager_->CloseAllBubbles(BUBBLE_CLOSE_FOCUS_LOST); |
| |
| ASSERT_FALSE(ref); |
| } |
| |
| TEST_F(BubbleManagerTest, DestroyInvalidatesReference) { |
| BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default()); |
| |
| manager_.reset(); |
| |
| ASSERT_FALSE(ref); |
| } |
| |
| TEST_F(BubbleManagerTest, CloseInvalidatesStubbornReference) { |
| BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Stubborn()); |
| |
| ASSERT_TRUE(manager_->CloseBubble(ref, BUBBLE_CLOSE_FORCED)); |
| |
| ASSERT_FALSE(ref); |
| } |
| |
| TEST_F(BubbleManagerTest, CloseAllInvalidatesStubbornReference) { |
| BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Stubborn()); |
| |
| manager_->CloseAllBubbles(BUBBLE_CLOSE_FORCED); |
| |
| ASSERT_FALSE(ref); |
| } |
| |
| TEST_F(BubbleManagerTest, DestroyInvalidatesStubbornReference) { |
| BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Stubborn()); |
| |
| manager_.reset(); |
| |
| ASSERT_FALSE(ref); |
| } |
| |
| TEST_F(BubbleManagerTest, CloseDoesNotInvalidateStubbornReference) { |
| BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Stubborn()); |
| |
| ASSERT_FALSE(manager_->CloseBubble(ref, BUBBLE_CLOSE_FOCUS_LOST)); |
| |
| ASSERT_TRUE(ref); |
| } |
| |
| TEST_F(BubbleManagerTest, CloseAllDoesNotInvalidateStubbornReference) { |
| BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Stubborn()); |
| |
| manager_->CloseAllBubbles(BUBBLE_CLOSE_FOCUS_LOST); |
| |
| ASSERT_TRUE(ref); |
| } |
| |
| TEST_F(BubbleManagerTest, CloseAllInvalidatesMixAppropriately) { |
| BubbleReference stubborn_ref1 = |
| manager_->ShowBubble(MockBubbleDelegate::Stubborn()); |
| BubbleReference normal_ref1 = |
| manager_->ShowBubble(MockBubbleDelegate::Default()); |
| BubbleReference stubborn_ref2 = |
| manager_->ShowBubble(MockBubbleDelegate::Stubborn()); |
| BubbleReference normal_ref2 = |
| manager_->ShowBubble(MockBubbleDelegate::Default()); |
| BubbleReference stubborn_ref3 = |
| manager_->ShowBubble(MockBubbleDelegate::Stubborn()); |
| BubbleReference normal_ref3 = |
| manager_->ShowBubble(MockBubbleDelegate::Default()); |
| |
| manager_->CloseAllBubbles(BUBBLE_CLOSE_FOCUS_LOST); |
| |
| ASSERT_TRUE(stubborn_ref1); |
| ASSERT_TRUE(stubborn_ref2); |
| ASSERT_TRUE(stubborn_ref3); |
| ASSERT_FALSE(normal_ref1); |
| ASSERT_FALSE(normal_ref2); |
| ASSERT_FALSE(normal_ref3); |
| } |
| |
| TEST_F(BubbleManagerTest, CloseBubbleShouldOnlylCloseSelf) { |
| BubbleReference ref1 = manager_->ShowBubble(MockBubbleDelegate::Default()); |
| BubbleReference ref2 = manager_->ShowBubble(MockBubbleDelegate::Default()); |
| BubbleReference ref3 = manager_->ShowBubble(MockBubbleDelegate::Default()); |
| |
| EXPECT_TRUE(ref1); |
| EXPECT_TRUE(ref2); |
| EXPECT_TRUE(ref3); |
| |
| ref2->CloseBubble(BUBBLE_CLOSE_FOCUS_LOST); |
| |
| EXPECT_TRUE(ref1); |
| EXPECT_FALSE(ref2); |
| EXPECT_TRUE(ref3); |
| } |
| |
| TEST_F(BubbleManagerTest, CloseOwnedByShouldLeaveUnowned) { |
| std::unique_ptr<MockBubbleDelegate> delegate1 = MockBubbleDelegate::Default(); |
| std::unique_ptr<MockBubbleDelegate> delegate2 = MockBubbleDelegate::Default(); |
| std::unique_ptr<MockBubbleDelegate> delegate3 = MockBubbleDelegate::Default(); |
| MockBubbleDelegate& delegate1_ref = *delegate1; |
| MockBubbleDelegate& delegate2_ref = *delegate2; |
| MockBubbleDelegate& delegate3_ref = *delegate3; |
| BubbleReference ref1 = manager_->ShowBubble(std::move(delegate1)); |
| BubbleReference ref2 = manager_->ShowBubble(std::move(delegate2)); |
| BubbleReference ref3 = manager_->ShowBubble(std::move(delegate3)); |
| |
| // These pointers are only compared for equality, not dereferenced. |
| const content::RenderFrameHost* const frame1 = |
| reinterpret_cast<const content::RenderFrameHost*>(&ref1); |
| const content::RenderFrameHost* const frame2 = |
| reinterpret_cast<const content::RenderFrameHost*>(&ref2); |
| |
| EXPECT_CALL(delegate1_ref, OwningFrame()) |
| .WillRepeatedly(testing::Return(frame1)); |
| EXPECT_CALL(delegate2_ref, OwningFrame()) |
| .WillRepeatedly(testing::Return(frame2)); |
| EXPECT_CALL(delegate3_ref, OwningFrame()) |
| .WillRepeatedly(testing::Return(nullptr)); |
| EXPECT_CALL(delegate1_ref, ShouldClose(BUBBLE_CLOSE_FRAME_DESTROYED)) |
| .WillOnce(testing::Return(true)); |
| |
| manager_->CloseBubblesOwnedBy(frame1); |
| EXPECT_FALSE(ref1); |
| EXPECT_TRUE(ref2); |
| EXPECT_TRUE(ref3); |
| } |
| |
| TEST_F(BubbleManagerTest, UpdateAllShouldWorkWithoutBubbles) { |
| // Manager shouldn't crash if bubbles have never been added. |
| manager_->UpdateAllBubbleAnchors(); |
| |
| // Add a bubble and close it. |
| BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default()); |
| ASSERT_TRUE(manager_->CloseBubble(ref, BUBBLE_CLOSE_FORCED)); |
| |
| // Bubble should NOT get an update event because it's already closed. |
| manager_->UpdateAllBubbleAnchors(); |
| } |
| |
| TEST_F(BubbleManagerTest, CloseAllShouldWorkWithoutBubbles) { |
| // Manager shouldn't crash if bubbles have never been added. |
| manager_->CloseAllBubbles(BUBBLE_CLOSE_FOCUS_LOST); |
| |
| // Add a bubble and close it. |
| BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default()); |
| ASSERT_TRUE(manager_->CloseBubble(ref, BUBBLE_CLOSE_FORCED)); |
| |
| // Bubble should NOT get a close event because it's already closed. |
| manager_->CloseAllBubbles(BUBBLE_CLOSE_FOCUS_LOST); |
| } |
| |
| // This test validates that it's possible to show another bubble when |
| // |CloseBubble| is called. |
| TEST_F(BubbleManagerTest, AllowBubbleChainingOnClose) { |
| BubbleReference chained_bubble; |
| BubbleReference ref = |
| manager_->ShowBubble(base::WrapUnique(new ChainShowBubbleDelegate( |
| manager_.get(), MockBubbleDelegate::Default(), &chained_bubble))); |
| ASSERT_FALSE(chained_bubble); // Bubble not yet visible. |
| ASSERT_TRUE(manager_->CloseBubble(ref, BUBBLE_CLOSE_FORCED)); |
| ASSERT_TRUE(chained_bubble); // Bubble is now visible. |
| } |
| |
| // This test validates that it's possible to show another bubble when |
| // |CloseAllBubbles| is called. |
| TEST_F(BubbleManagerTest, AllowBubbleChainingOnCloseAll) { |
| BubbleReference chained_bubble; |
| BubbleReference ref = |
| manager_->ShowBubble(base::WrapUnique(new ChainShowBubbleDelegate( |
| manager_.get(), MockBubbleDelegate::Default(), &chained_bubble))); |
| ASSERT_FALSE(chained_bubble); // Bubble not yet visible. |
| manager_->CloseAllBubbles(BUBBLE_CLOSE_FORCED); |
| ASSERT_TRUE(chained_bubble); // Bubble is now visible. |
| } |
| |
| // This test validates that a show chain will not happen in the destructor. |
| // While chaining is during the normal life span of the manager, it should NOT |
| // happen when the manager is being destroyed. |
| TEST_F(BubbleManagerTest, BubblesDoNotChainOnDestroy) { |
| MockBubbleManagerObserver metrics; |
| // |chained_delegate| should never be shown. |
| EXPECT_CALL(metrics, OnBubbleNeverShown(testing::_)); |
| // The ChainShowBubbleDelegate should be closed when the manager is destroyed. |
| EXPECT_CALL(metrics, OnBubbleClosed(testing::_, BUBBLE_CLOSE_FORCED)); |
| manager_->AddBubbleManagerObserver(&metrics); |
| |
| std::unique_ptr<MockBubbleDelegate> chained_delegate(new MockBubbleDelegate); |
| EXPECT_CALL(*chained_delegate->bubble_ui(), Show(testing::_)).Times(0); |
| EXPECT_CALL(*chained_delegate, ShouldClose(testing::_)).Times(0); |
| EXPECT_CALL(*chained_delegate, DidClose(testing::_)).Times(0); |
| |
| manager_->ShowBubble(base::WrapUnique(new ChainShowBubbleDelegate( |
| manager_.get(), std::move(chained_delegate), nullptr))); |
| manager_.reset(); |
| } |
| |
| TEST_F(BubbleManagerTest, BubbleCloseReasonIsCalled) { |
| MockBubbleManagerObserver metrics; |
| EXPECT_CALL(metrics, OnBubbleNeverShown(testing::_)).Times(0); |
| EXPECT_CALL(metrics, OnBubbleClosed(testing::_, BUBBLE_CLOSE_ACCEPTED)); |
| manager_->AddBubbleManagerObserver(&metrics); |
| |
| BubbleReference ref = manager_->ShowBubble(MockBubbleDelegate::Default()); |
| ref->CloseBubble(BUBBLE_CLOSE_ACCEPTED); |
| |
| // Destroy to verify no events are sent to |metrics| in destructor. |
| manager_.reset(); |
| } |
| |
| // In a close chain, it should be possible for the bubble in the second close |
| // event to close. |
| TEST_F(BubbleManagerTest, BubbleCloseChainCloseClose) { |
| std::unique_ptr<ChainCloseBubbleDelegate> closing_bubble( |
| new ChainCloseBubbleDelegate(manager_.get())); |
| EXPECT_CALL(*closing_bubble, ShouldClose(testing::_)) |
| .WillOnce(testing::Return(true)); |
| |
| BubbleReference other_bubble_ref = |
| manager_->ShowBubble(MockBubbleDelegate::Default()); |
| |
| BubbleReference closing_bubble_ref = |
| manager_->ShowBubble(std::move(closing_bubble)); |
| |
| EXPECT_TRUE(other_bubble_ref); |
| EXPECT_TRUE(closing_bubble_ref); |
| |
| closing_bubble_ref->CloseBubble(BUBBLE_CLOSE_ACCEPTED); |
| |
| EXPECT_FALSE(other_bubble_ref); |
| EXPECT_FALSE(closing_bubble_ref); |
| } |
| |
| // In a close chain, it should be possible for the bubble in the second close |
| // event to remain open because close is a request. |
| TEST_F(BubbleManagerTest, BubbleCloseChainCloseNoClose) { |
| std::unique_ptr<ChainCloseBubbleDelegate> closing_bubble( |
| new ChainCloseBubbleDelegate(manager_.get())); |
| EXPECT_CALL(*closing_bubble, ShouldClose(testing::_)) |
| .WillOnce(testing::Return(true)); |
| |
| BubbleReference other_bubble_ref = |
| manager_->ShowBubble(MockBubbleDelegate::Stubborn()); |
| |
| BubbleReference closing_bubble_ref = |
| manager_->ShowBubble(std::move(closing_bubble)); |
| |
| EXPECT_TRUE(other_bubble_ref); |
| EXPECT_TRUE(closing_bubble_ref); |
| |
| closing_bubble_ref->CloseBubble(BUBBLE_CLOSE_ACCEPTED); |
| |
| EXPECT_TRUE(other_bubble_ref); |
| EXPECT_FALSE(closing_bubble_ref); |
| } |
| |
| // This test is a sanity check. |closing_bubble| will attempt to close all other |
| // bubbles if it's closed, but it doesn't want to close. Sending a close request |
| // should keep it open without starting a close chain. |
| TEST_F(BubbleManagerTest, BubbleCloseChainNoCloseNoClose) { |
| std::unique_ptr<ChainCloseBubbleDelegate> closing_bubble( |
| new ChainCloseBubbleDelegate(manager_.get())); |
| EXPECT_CALL(*closing_bubble, ShouldClose(testing::_)) |
| .WillRepeatedly(testing::Return(false)); |
| |
| BubbleReference other_bubble_ref = |
| manager_->ShowBubble(MockBubbleDelegate::Default()); |
| |
| BubbleReference closing_bubble_ref = |
| manager_->ShowBubble(std::move(closing_bubble)); |
| |
| EXPECT_TRUE(other_bubble_ref); |
| EXPECT_TRUE(closing_bubble_ref); |
| |
| closing_bubble_ref->CloseBubble(BUBBLE_CLOSE_ACCEPTED); |
| |
| EXPECT_TRUE(other_bubble_ref); |
| EXPECT_TRUE(closing_bubble_ref); |
| } |
| |
| } // namespace |