// 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 "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, scoped_ptr delegate, BubbleReference* chained_bubble) : manager_(manager), delegate_(delegate.Pass()), chained_bubble_(chained_bubble), closed_(false) { EXPECT_CALL(*this, ShouldClose(testing::_)).WillOnce(testing::Return(true)); } ~ChainShowBubbleDelegate() override { EXPECT_TRUE(closed_); } void DidClose() override { MockBubbleDelegate::DidClose(); BubbleReference ref = manager_->ShowBubble(delegate_.Pass()); if (chained_bubble_) *chained_bubble_ = ref; closed_ = true; } private: BubbleManager* manager_; scoped_ptr 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() 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 BubbleManagerTest : public testing::Test { public: BubbleManagerTest(); ~BubbleManagerTest() override {} void SetUp() override; void TearDown() override; protected: scoped_ptr manager_; private: DISALLOW_COPY_AND_ASSIGN(BubbleManagerTest); }; BubbleManagerTest::BubbleManagerTest() {} void BubbleManagerTest::SetUp() { testing::Test::SetUp(); manager_.reset(new BubbleManager); } void BubbleManagerTest::TearDown() { manager_.reset(); testing::Test::TearDown(); } TEST_F(BubbleManagerTest, ManagerShowsBubbleUi) { scoped_ptr 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(delegate.Pass()); } TEST_F(BubbleManagerTest, ManagerUpdatesBubbleUiAnchor) { scoped_ptr 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(delegate.Pass()); 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, 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(make_scoped_ptr(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(make_scoped_ptr(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); scoped_ptr 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()).Times(0); manager_->ShowBubble(make_scoped_ptr(new ChainShowBubbleDelegate( manager_.get(), chained_delegate.Pass(), 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) { scoped_ptr 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(closing_bubble.Pass()); 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) { scoped_ptr 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(closing_bubble.Pass()); 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) { scoped_ptr 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(closing_bubble.Pass()); 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