chrome/browser/extensions: Remove use of MessageLoopProxy and deprecated MessageLoop...
[chromium-blink-merge.git] / ui / views / cocoa / bridged_native_widget_interactive_uitest.mm
blobf7c4329f176d9cfec32e52da974ed8ed3528e85d
1 // Copyright 2014 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.
5 #import "ui/views/cocoa/bridged_native_widget.h"
7 #import <Cocoa/Cocoa.h>
9 #import "base/mac/mac_util.h"
10 #import "base/mac/sdk_forward_declarations.h"
11 #import "ui/base/test/nswindow_fullscreen_notification_waiter.h"
12 #include "ui/base/hit_test.h"
13 #import "ui/base/test/windowed_nsnotification_observer.h"
14 #import "ui/events/test/cocoa_test_event_utils.h"
15 #include "ui/views/test/widget_test.h"
16 #include "ui/views/window/native_frame_view.h"
18 namespace views {
19 namespace test {
21 class BridgedNativeWidgetUITest : public test::WidgetTest {
22  public:
23   BridgedNativeWidgetUITest() {}
25   // testing::Test:
26   void SetUp() override {
27     WidgetTest::SetUp();
28     Widget::InitParams init_params =
29         CreateParams(Widget::InitParams::TYPE_WINDOW);
30     init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
31     widget_.reset(new Widget);
32     widget_->Init(init_params);
33   }
35   void TearDown() override {
36     // Ensures any compositor is removed before ViewsTestBase tears down the
37     // ContextFactory.
38     widget_.reset();
39     WidgetTest::TearDown();
40   }
42   NSWindow* test_window() {
43     return widget_->GetNativeWindow();
44   }
46  protected:
47   scoped_ptr<Widget> widget_;
50 // Tests for correct fullscreen tracking, regardless of whether it is initiated
51 // by the Widget code or elsewhere (e.g. by the user).
52 TEST_F(BridgedNativeWidgetUITest, FullscreenSynchronousState) {
53   EXPECT_FALSE(widget_->IsFullscreen());
54   if (base::mac::IsOSSnowLeopard())
55     return;
57   // Allow user-initiated fullscreen changes on the Window.
58   [test_window()
59       setCollectionBehavior:[test_window() collectionBehavior] |
60                             NSWindowCollectionBehaviorFullScreenPrimary];
62   base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter(
63       [[NSWindowFullscreenNotificationWaiter alloc]
64           initWithWindow:test_window()]);
65   const gfx::Rect restored_bounds = widget_->GetRestoredBounds();
67   // First show the widget. A user shouldn't be able to initiate fullscreen
68   // unless the window is visible in the first place.
69   widget_->Show();
71   // Simulate a user-initiated fullscreen. Note trying to to this again before
72   // spinning a runloop will cause Cocoa to emit text to stdio and ignore it.
73   [test_window() toggleFullScreen:nil];
74   EXPECT_TRUE(widget_->IsFullscreen());
75   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
77   // Note there's now an animation running. While that's happening, toggling the
78   // state should work as expected, but do "nothing".
79   widget_->SetFullscreen(false);
80   EXPECT_FALSE(widget_->IsFullscreen());
81   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
82   widget_->SetFullscreen(false);  // Same request - should no-op.
83   EXPECT_FALSE(widget_->IsFullscreen());
84   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
86   widget_->SetFullscreen(true);
87   EXPECT_TRUE(widget_->IsFullscreen());
88   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
90   // Always finish out of fullscreen. Otherwise there are 4 NSWindow objects
91   // that Cocoa creates which don't close themselves and will be seen by the Mac
92   // test harness on teardown. Note that the test harness will be waiting until
93   // all animations complete, since these temporary animation windows will not
94   // be removed from the window list until they do.
95   widget_->SetFullscreen(false);
96   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
98   // Now we must wait for the notifications. Since, if the widget is torn down,
99   // the NSWindowDelegate is removed, and the pending request to take out of
100   // fullscreen is lost. Since a message loop has not yet spun up in this test
101   // we can reliably say there will be one enter and one exit, despite all the
102   // toggling above.
103   [waiter waitForEnterCount:1 exitCount:1];
104   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
107 // Test fullscreen without overlapping calls and without changing collection
108 // behavior on the test window.
109 TEST_F(BridgedNativeWidgetUITest, FullscreenEnterAndExit) {
110   base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter(
111       [[NSWindowFullscreenNotificationWaiter alloc]
112           initWithWindow:test_window()]);
114   EXPECT_FALSE(widget_->IsFullscreen());
115   const gfx::Rect restored_bounds = widget_->GetRestoredBounds();
116   EXPECT_FALSE(restored_bounds.IsEmpty());
118   // Ensure this works without having to change collection behavior as for the
119   // test above. Also check that making a hidden widget fullscreen shows it.
120   EXPECT_FALSE(widget_->IsVisible());
121   widget_->SetFullscreen(true);
122   EXPECT_TRUE(widget_->IsVisible());
123   if (base::mac::IsOSSnowLeopard()) {
124     // On Snow Leopard, SetFullscreen() isn't implemented. But shouldn't crash.
125     EXPECT_FALSE(widget_->IsFullscreen());
126     return;
127   }
129   EXPECT_TRUE(widget_->IsFullscreen());
130   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
132   // Should be zero until the runloop spins.
133   EXPECT_EQ(0, [waiter enterCount]);
134   [waiter waitForEnterCount:1 exitCount:0];
136   // Verify it hasn't exceeded.
137   EXPECT_EQ(1, [waiter enterCount]);
138   EXPECT_EQ(0, [waiter exitCount]);
139   EXPECT_TRUE(widget_->IsFullscreen());
140   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
142   widget_->SetFullscreen(false);
143   EXPECT_FALSE(widget_->IsFullscreen());
144   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
146   [waiter waitForEnterCount:1 exitCount:1];
147   EXPECT_EQ(1, [waiter enterCount]);
148   EXPECT_EQ(1, [waiter exitCount]);
149   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
152 namespace {
154 // This is used to wait for reposted events to be seen. We can't just use
155 // RunPendingMessages() because CGEventPost might not be synchronous.
156 class HitTestBridgedNativeWidget : public BridgedNativeWidget {
157  public:
158   explicit HitTestBridgedNativeWidget(NativeWidgetMac* widget)
159       : BridgedNativeWidget(widget) {}
161   // BridgedNativeWidget:
162   bool ShouldRepostPendingLeftMouseDown(NSPoint location_in_window) override {
163     bool draggable_before = [ns_view() mouseDownCanMoveWindow];
164     bool should_repost = BridgedNativeWidget::ShouldRepostPendingLeftMouseDown(
165         location_in_window);
166     bool draggable_after = [ns_view() mouseDownCanMoveWindow];
168     if (run_loop_ && draggable_before && !draggable_after)
169       run_loop_->Quit();
171     return should_repost;
172   }
174   void WaitForIsDraggableChange() {
175     base::RunLoop run_loop;
176     run_loop_ = &run_loop;
177     run_loop.Run();
178     run_loop_ = nullptr;
179   }
181  private:
182   base::RunLoop* run_loop_;
184   DISALLOW_COPY_AND_ASSIGN(HitTestBridgedNativeWidget);
187 // This is used to return a customized result to NonClientHitTest.
188 class HitTestNonClientFrameView : public NativeFrameView {
189  public:
190   explicit HitTestNonClientFrameView(Widget* widget)
191       : NativeFrameView(widget), hit_test_result_(HTNOWHERE) {}
193   // NonClientFrameView overrides:
194   int NonClientHitTest(const gfx::Point& point) override {
195     return hit_test_result_;
196   }
198   void set_hit_test_result(int component) { hit_test_result_ = component; }
200  private:
201   int hit_test_result_;
203   DISALLOW_COPY_AND_ASSIGN(HitTestNonClientFrameView);
206 void WaitForEvent(NSUInteger mask) {
207   // Pointer because the handler block captures local variables by copying.
208   base::RunLoop run_loop;
209   base::RunLoop* run_loop_ref = &run_loop;
210   id monitor = [NSEvent
211       addLocalMonitorForEventsMatchingMask:mask
212                                    handler:^NSEvent*(NSEvent* ns_event) {
213                                      run_loop_ref->Quit();
214                                      return ns_event;
215                                    }];
216   run_loop.Run();
217   [NSEvent removeMonitor:monitor];
220 }  // namespace
222 // This is used to inject test versions of NativeFrameView and
223 // BridgedNativeWidget.
224 class HitTestNativeWidgetMac : public NativeWidgetMac {
225  public:
226   HitTestNativeWidgetMac(internal::NativeWidgetDelegate* delegate,
227                          NativeFrameView* native_frame_view)
228       : NativeWidgetMac(delegate), native_frame_view_(native_frame_view) {
229     NativeWidgetMac::bridge_.reset(new HitTestBridgedNativeWidget(this));
230   }
232   HitTestBridgedNativeWidget* bridge() {
233     return static_cast<HitTestBridgedNativeWidget*>(
234         NativeWidgetMac::bridge_.get());
235   }
237   // internal::NativeWidgetPrivate:
238   NonClientFrameView* CreateNonClientFrameView() override {
239     return native_frame_view_;
240   }
242  private:
243   // Owned by Widget.
244   NativeFrameView* native_frame_view_;
246   DISALLOW_COPY_AND_ASSIGN(HitTestNativeWidgetMac);
249 TEST_F(BridgedNativeWidgetUITest, HitTest) {
250   Widget widget;
251   HitTestNonClientFrameView* frame_view =
252       new HitTestNonClientFrameView(&widget);
253   test::HitTestNativeWidgetMac* native_widget =
254       new test::HitTestNativeWidgetMac(&widget, frame_view);
255   Widget::InitParams init_params =
256       CreateParams(Widget::InitParams::TYPE_WINDOW);
257   init_params.native_widget = native_widget;
258   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
259   init_params.bounds = gfx::Rect(100, 200, 400, 300);
260   widget.Init(init_params);
261   widget.Show();
263   // Dragging the window should work.
264   frame_view->set_hit_test_result(HTCAPTION);
265   {
266     EXPECT_EQ(100, [widget.GetNativeWindow() frame].origin.x);
268     NSEvent* mouse_down = cocoa_test_event_utils::LeftMouseDownAtPointInWindow(
269         NSMakePoint(10, 10), widget.GetNativeWindow());
270     CGEventPost(kCGSessionEventTap, [mouse_down CGEvent]);
271     native_widget->bridge()->WaitForIsDraggableChange();
273     base::scoped_nsobject<WindowedNSNotificationObserver> ns_observer(
274         [[WindowedNSNotificationObserver alloc]
275             initForNotification:NSWindowDidMoveNotification]);
276     NSEvent* mouse_drag = cocoa_test_event_utils::MouseEventAtPointInWindow(
277         NSMakePoint(110, 110), NSLeftMouseDragged, widget.GetNativeWindow(), 0);
278     CGEventPost(kCGSessionEventTap, [mouse_drag CGEvent]);
279     [ns_observer wait];
280     EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x);
282     NSEvent* mouse_up = cocoa_test_event_utils::MouseEventAtPointInWindow(
283         NSMakePoint(10, 10), NSLeftMouseUp, widget.GetNativeWindow(), 0);
284     CGEventPost(kCGSessionEventTap, [mouse_up CGEvent]);
285     WaitForEvent(NSLeftMouseUpMask);
286     EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x);
287   }
289   // Mouse-downs on the window controls should not be intercepted.
290   {
291     EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x);
293     base::scoped_nsobject<WindowedNSNotificationObserver> ns_observer(
294         [[WindowedNSNotificationObserver alloc]
295             initForNotification:NSWindowDidMiniaturizeNotification]);
297     // Position this on the minimize button.
298     NSEvent* mouse_down = cocoa_test_event_utils::LeftMouseDownAtPointInWindow(
299         NSMakePoint(30, 290), widget.GetNativeWindow());
300     CGEventPost(kCGSessionEventTap, [mouse_down CGEvent]);
302     NSEvent* mouse_up = cocoa_test_event_utils::MouseEventAtPointInWindow(
303         NSMakePoint(30, 290), NSLeftMouseUp, widget.GetNativeWindow(), 0);
304     EXPECT_FALSE([widget.GetNativeWindow() isMiniaturized]);
305     CGEventPost(kCGSessionEventTap, [mouse_up CGEvent]);
306     [ns_observer wait];
307     EXPECT_TRUE([widget.GetNativeWindow() isMiniaturized]);
308     [widget.GetNativeWindow() deminiaturize:nil];
310     // Position unchanged.
311     EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x);
312   }
314   // Non-draggable areas should do nothing.
315   frame_view->set_hit_test_result(HTCLIENT);
316   {
317     EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x);
319     NSEvent* mouse_down = cocoa_test_event_utils::LeftMouseDownAtPointInWindow(
320         NSMakePoint(10, 10), widget.GetNativeWindow());
321     CGEventPost(kCGSessionEventTap, [mouse_down CGEvent]);
322     WaitForEvent(NSLeftMouseDownMask);
324     NSEvent* mouse_drag = cocoa_test_event_utils::MouseEventAtPointInWindow(
325         NSMakePoint(110, 110), NSLeftMouseDragged, widget.GetNativeWindow(), 0);
326     CGEventPost(kCGSessionEventTap, [mouse_drag CGEvent]);
327     WaitForEvent(NSLeftMouseDraggedMask);
328     EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x);
330     NSEvent* mouse_up = cocoa_test_event_utils::MouseEventAtPointInWindow(
331         NSMakePoint(110, 110), NSLeftMouseUp, widget.GetNativeWindow(), 0);
332     CGEventPost(kCGSessionEventTap, [mouse_up CGEvent]);
333     WaitForEvent(NSLeftMouseUpMask);
334     EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x);
335   }
338 }  // namespace test
339 }  // namespace views