1// Copyright 2017 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "ui/base/cocoa/bubble_closer.h" 6 7#include "base/bind.h" 8#import "ui/base/test/cocoa_helper.h" 9#import "ui/base/test/menu_test_observer.h" 10#import "ui/events/test/cocoa_test_event_utils.h" 11 12namespace ui { 13 14class BubbleCloserTest : public CocoaTest { 15 public: 16 enum Button { LEFT, RIGHT }; 17 enum InOrOut { INSIDE, OUTSIDE }; 18 19 BubbleCloserTest() = default; 20 21 void SetUp() override { 22 CocoaTest::SetUp(); 23 bubble_window_.reset([[NSWindow alloc] 24 initWithContentRect:NSMakeRect(100, 100, 320, 200) 25 styleMask:NSBorderlessWindowMask 26 backing:NSBackingStoreBuffered 27 defer:NO]); 28 [bubble_window_ setReleasedWhenClosed:NO]; // Owned by scoped_nsobject. 29 [bubble_window_ makeKeyAndOrderFront:nil]; 30 bubble_closer_ = std::make_unique<BubbleCloser>( 31 bubble_window_, 32 base::BindRepeating([](int* i) { *i += 1; }, &click_outside_count_)); 33 } 34 35 void TearDown() override { 36 [bubble_window_ close]; 37 bubble_closer_ = nullptr; 38 bubble_window_.reset(); 39 CocoaTest::TearDown(); 40 } 41 42 void SendClick(Button left_or_right, InOrOut in_or_out) { 43 NSWindow* window = in_or_out == INSIDE ? bubble_window_ : test_window(); 44 NSEvent* event = 45 left_or_right == LEFT 46 ? cocoa_test_event_utils::LeftMouseDownAtPointInWindow( 47 NSMakePoint(10, 10), window) 48 : cocoa_test_event_utils::RightMouseDownAtPointInWindow( 49 NSMakePoint(10, 10), window); 50 [NSApp sendEvent:event]; 51 } 52 53 void ResetCloser() { bubble_closer_ = nullptr; } 54 55 protected: 56 int click_outside_count() const { return click_outside_count_; } 57 NSWindow* bubble_window() { return bubble_window_; } 58 bool IsCloserReset() const { return !bubble_closer_; } 59 60 private: 61 base::scoped_nsobject<NSWindow> bubble_window_; 62 std::unique_ptr<BubbleCloser> bubble_closer_; 63 int click_outside_count_ = 0; 64 65 DISALLOW_COPY_AND_ASSIGN(BubbleCloserTest); 66}; 67 68// Test for lifetime issues around NSEvent monitors. 69TEST_F(BubbleCloserTest, SecondBubbleCloser) { 70 auto resetter = [](BubbleCloserTest* me) { me->ResetCloser(); }; 71 auto deleter = std::make_unique<BubbleCloser>( 72 bubble_window(), base::BindRepeating(resetter, this)); 73 SendClick(LEFT, OUTSIDE); 74 75 // The order is non-deterministic, so click_outside_count() may not change. 76 // But either way, the closer should have been reset. 77 EXPECT_TRUE(IsCloserReset()); 78} 79 80// Test that clicking outside the window fires the callback and clicking inside 81// does not. 82TEST_F(BubbleCloserTest, ClickInsideAndOut) { 83 EXPECT_EQ(0, click_outside_count()); 84 SendClick(LEFT, OUTSIDE); 85 EXPECT_EQ(1, click_outside_count()); 86 SendClick(RIGHT, OUTSIDE); 87 EXPECT_EQ(2, click_outside_count()); 88 SendClick(LEFT, INSIDE); 89 EXPECT_EQ(2, click_outside_count()); 90 SendClick(RIGHT, INSIDE); 91 EXPECT_EQ(2, click_outside_count()); 92} 93 94// Test that right-clicking the window to display a context menu works. 95TEST_F(BubbleCloserTest, RightClickOutsideClosesWithContextMenu) { 96 base::scoped_nsobject<NSMenu> context_menu( 97 [[NSMenu alloc] initWithTitle:@""]); 98 [context_menu addItemWithTitle:@"ContextMenuTest" 99 action:nil 100 keyEquivalent:@""]; 101 102 // Set the menu as the contextual menu of contentView of test_window(). 103 [[test_window() contentView] setMenu:context_menu]; 104 105 base::scoped_nsobject<MenuTestObserver> menu_observer( 106 [[MenuTestObserver alloc] initWithMenu:context_menu]); 107 [menu_observer setCloseAfterOpening:YES]; 108 [menu_observer setOpenCallback:^(MenuTestObserver* observer) { 109 // Verify click is seen when contextual menu is open. 110 EXPECT_TRUE([observer isOpen]); 111 EXPECT_EQ(1, click_outside_count()); 112 }]; 113 114 EXPECT_FALSE([menu_observer isOpen]); 115 EXPECT_FALSE([menu_observer didOpen]); 116 117 EXPECT_EQ(0, click_outside_count()); 118 119 // RightMouseDown in test_window() would close the bubble window and then 120 // display the contextual menu. 121 SendClick(RIGHT, OUTSIDE); 122 123 // When we got here, menu has already run its RunLoop. 124 EXPECT_EQ(1, click_outside_count()); 125 126 EXPECT_FALSE([menu_observer isOpen]); 127 EXPECT_TRUE([menu_observer didOpen]); 128} 129 130} // namespace ui 131