Make castv2 performance test work.
[chromium-blink-merge.git] / ui / views / widget / native_widget_mac_unittest.mm
blobccc1e4c48c56de2886e7939469c70ea04e32ed59
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/widget/native_widget_mac.h"
7 #import <Cocoa/Cocoa.h>
9 #include "base/run_loop.h"
10 #include "base/strings/utf_string_conversions.h"
11 #import "testing/gtest_mac.h"
12 #import "ui/events/test/cocoa_test_event_utils.h"
13 #include "ui/events/test/event_generator.h"
14 #import "ui/gfx/mac/coordinate_conversion.h"
15 #include "ui/views/controls/label.h"
16 #include "ui/views/native_cursor.h"
17 #include "ui/views/test/test_widget_observer.h"
18 #include "ui/views/test/widget_test.h"
20 namespace views {
21 namespace test {
23 // Tests for parts of NativeWidgetMac not covered by BridgedNativeWidget, which
24 // need access to Cocoa APIs.
25 typedef WidgetTest NativeWidgetMacTest;
27 class WidgetChangeObserver : public TestWidgetObserver {
28  public:
29   WidgetChangeObserver(Widget* widget)
30       : TestWidgetObserver(widget),
31         gained_visible_count_(0),
32         lost_visible_count_(0) {}
34   int gained_visible_count() const { return gained_visible_count_; }
35   int lost_visible_count() const { return lost_visible_count_; }
37  private:
38   // WidgetObserver:
39   void OnWidgetVisibilityChanged(Widget* widget,
40                                  bool visible) override {
41     ++(visible ? gained_visible_count_ : lost_visible_count_);
42   }
44   int gained_visible_count_;
45   int lost_visible_count_;
47   DISALLOW_COPY_AND_ASSIGN(WidgetChangeObserver);
50 // Test visibility states triggered externally.
51 TEST_F(NativeWidgetMacTest, HideAndShowExternally) {
52   Widget* widget = CreateTopLevelPlatformWidget();
53   NSWindow* ns_window = widget->GetNativeWindow();
54   WidgetChangeObserver observer(widget);
56   // Should initially be hidden.
57   EXPECT_FALSE(widget->IsVisible());
58   EXPECT_FALSE([ns_window isVisible]);
59   EXPECT_EQ(0, observer.gained_visible_count());
60   EXPECT_EQ(0, observer.lost_visible_count());
62   widget->Show();
63   EXPECT_TRUE(widget->IsVisible());
64   EXPECT_TRUE([ns_window isVisible]);
65   EXPECT_EQ(1, observer.gained_visible_count());
66   EXPECT_EQ(0, observer.lost_visible_count());
68   widget->Hide();
69   EXPECT_FALSE(widget->IsVisible());
70   EXPECT_FALSE([ns_window isVisible]);
71   EXPECT_EQ(1, observer.gained_visible_count());
72   EXPECT_EQ(1, observer.lost_visible_count());
74   widget->Show();
75   EXPECT_TRUE(widget->IsVisible());
76   EXPECT_TRUE([ns_window isVisible]);
77   EXPECT_EQ(2, observer.gained_visible_count());
78   EXPECT_EQ(1, observer.lost_visible_count());
80   // Test when hiding individual windows.
81   [ns_window orderOut:nil];
82   EXPECT_FALSE(widget->IsVisible());
83   EXPECT_FALSE([ns_window isVisible]);
84   EXPECT_EQ(2, observer.gained_visible_count());
85   EXPECT_EQ(2, observer.lost_visible_count());
87   [ns_window orderFront:nil];
88   EXPECT_TRUE(widget->IsVisible());
89   EXPECT_TRUE([ns_window isVisible]);
90   EXPECT_EQ(3, observer.gained_visible_count());
91   EXPECT_EQ(2, observer.lost_visible_count());
93   // Test when hiding the entire application. This doesn't send an orderOut:
94   // to the NSWindow.
95   [NSApp hide:nil];
96   // When the activation policy is NSApplicationActivationPolicyRegular, the
97   // calls via NSApp are asynchronous, and the run loop needs to be flushed.
98   // With NSApplicationActivationPolicyProhibited, the following RunUntilIdle
99   // calls are superfluous, but don't hurt.
100   base::RunLoop().RunUntilIdle();
101   EXPECT_FALSE(widget->IsVisible());
102   EXPECT_FALSE([ns_window isVisible]);
103   EXPECT_EQ(3, observer.gained_visible_count());
104   EXPECT_EQ(3, observer.lost_visible_count());
106   [NSApp unhideWithoutActivation];
107   base::RunLoop().RunUntilIdle();
108   EXPECT_TRUE(widget->IsVisible());
109   EXPECT_TRUE([ns_window isVisible]);
110   EXPECT_EQ(4, observer.gained_visible_count());
111   EXPECT_EQ(3, observer.lost_visible_count());
113   // Hide again to test unhiding with an activation.
114   [NSApp hide:nil];
115   base::RunLoop().RunUntilIdle();
116   EXPECT_EQ(4, observer.lost_visible_count());
117   [NSApp unhide:nil];
118   base::RunLoop().RunUntilIdle();
119   EXPECT_EQ(5, observer.gained_visible_count());
121   // Hide again to test makeKeyAndOrderFront:.
122   [ns_window orderOut:nil];
123   EXPECT_FALSE(widget->IsVisible());
124   EXPECT_FALSE([ns_window isVisible]);
125   EXPECT_EQ(5, observer.gained_visible_count());
126   EXPECT_EQ(5, observer.lost_visible_count());
128   [ns_window makeKeyAndOrderFront:nil];
129   EXPECT_TRUE(widget->IsVisible());
130   EXPECT_TRUE([ns_window isVisible]);
131   EXPECT_EQ(6, observer.gained_visible_count());
132   EXPECT_EQ(5, observer.lost_visible_count());
134   // No change when closing.
135   widget->CloseNow();
136   EXPECT_EQ(5, observer.lost_visible_count());
137   EXPECT_EQ(6, observer.gained_visible_count());
140 // A view that counts calls to OnPaint().
141 class PaintCountView : public View {
142  public:
143   PaintCountView() : paint_count_(0) {
144     SetBounds(0, 0, 100, 100);
145   }
147   // View:
148   void OnPaint(gfx::Canvas* canvas) override {
149     EXPECT_TRUE(GetWidget()->IsVisible());
150     ++paint_count_;
151   }
153   int paint_count() { return paint_count_; }
155  private:
156   int paint_count_;
158   DISALLOW_COPY_AND_ASSIGN(PaintCountView);
161 // Test minimized states triggered externally, implied visibility and restored
162 // bounds whilst minimized.
163 TEST_F(NativeWidgetMacTest, MiniaturizeExternally) {
164   Widget* widget = new Widget;
165   Widget::InitParams init_params(Widget::InitParams::TYPE_WINDOW);
166   // Make the layer not drawn, so that calls to paint can be observed
167   // synchronously.
168   init_params.layer_type = ui::LAYER_NOT_DRAWN;
169   widget->Init(init_params);
171   PaintCountView* view = new PaintCountView();
172   widget->GetContentsView()->AddChildView(view);
173   NSWindow* ns_window = widget->GetNativeWindow();
174   WidgetChangeObserver observer(widget);
176   widget->SetBounds(gfx::Rect(100, 100, 300, 300));
178   EXPECT_TRUE(view->IsDrawn());
179   EXPECT_EQ(0, view->paint_count());
180   widget->Show();
182   EXPECT_EQ(1, observer.gained_visible_count());
183   EXPECT_EQ(0, observer.lost_visible_count());
184   const gfx::Rect restored_bounds = widget->GetRestoredBounds();
185   EXPECT_FALSE(restored_bounds.IsEmpty());
186   EXPECT_FALSE(widget->IsMinimized());
187   EXPECT_TRUE(widget->IsVisible());
189   // Showing should paint.
190   EXPECT_EQ(1, view->paint_count());
192   // First try performMiniaturize:, which requires a minimize button. Note that
193   // Cocoa just blocks the UI thread during the animation, so no need to do
194   // anything fancy to wait for it finish.
195   [ns_window performMiniaturize:nil];
197   EXPECT_TRUE(widget->IsMinimized());
198   EXPECT_FALSE(widget->IsVisible());  // Minimizing also makes things invisible.
199   EXPECT_EQ(1, observer.gained_visible_count());
200   EXPECT_EQ(1, observer.lost_visible_count());
201   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
203   // No repaint when minimizing. But note that this is partly due to not calling
204   // [NSView setNeedsDisplay:YES] on the content view. The superview, which is
205   // an NSThemeFrame, would repaint |view| if we had, because the miniaturize
206   // button is highlighted for performMiniaturize.
207   EXPECT_EQ(1, view->paint_count());
209   [ns_window deminiaturize:nil];
211   EXPECT_FALSE(widget->IsMinimized());
212   EXPECT_TRUE(widget->IsVisible());
213   EXPECT_EQ(2, observer.gained_visible_count());
214   EXPECT_EQ(1, observer.lost_visible_count());
215   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
217   EXPECT_EQ(2, view->paint_count());  // A single paint when deminiaturizing.
218   EXPECT_FALSE([ns_window isMiniaturized]);
220   widget->Minimize();
222   EXPECT_TRUE(widget->IsMinimized());
223   EXPECT_TRUE([ns_window isMiniaturized]);
224   EXPECT_EQ(2, observer.gained_visible_count());
225   EXPECT_EQ(2, observer.lost_visible_count());
226   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
227   EXPECT_EQ(2, view->paint_count());  // No paint when miniaturizing.
229   widget->Restore();  // If miniaturized, should deminiaturize.
231   EXPECT_FALSE(widget->IsMinimized());
232   EXPECT_FALSE([ns_window isMiniaturized]);
233   EXPECT_EQ(3, observer.gained_visible_count());
234   EXPECT_EQ(2, observer.lost_visible_count());
235   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
236   EXPECT_EQ(3, view->paint_count());
238   widget->Restore();  // If not miniaturized, does nothing.
240   EXPECT_FALSE(widget->IsMinimized());
241   EXPECT_FALSE([ns_window isMiniaturized]);
242   EXPECT_EQ(3, observer.gained_visible_count());
243   EXPECT_EQ(2, observer.lost_visible_count());
244   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
245   EXPECT_EQ(3, view->paint_count());
247   widget->CloseNow();
249   // Create a widget without a minimize button.
250   widget = CreateTopLevelFramelessPlatformWidget();
251   ns_window = widget->GetNativeWindow();
252   widget->SetBounds(gfx::Rect(100, 100, 300, 300));
253   widget->Show();
254   EXPECT_FALSE(widget->IsMinimized());
256   // This should fail, since performMiniaturize: requires a minimize button.
257   [ns_window performMiniaturize:nil];
258   EXPECT_FALSE(widget->IsMinimized());
260   // But this should work.
261   widget->Minimize();
262   EXPECT_TRUE(widget->IsMinimized());
264   // Test closing while minimized.
265   widget->CloseNow();
268 // Simple view for the SetCursor test that overrides View::GetCursor().
269 class CursorView : public View {
270  public:
271   CursorView(int x, NSCursor* cursor) : cursor_(cursor) {
272     SetBounds(x, 0, 100, 300);
273   }
275   // View:
276   gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override {
277     return cursor_;
278   }
280  private:
281   NSCursor* cursor_;
283   DISALLOW_COPY_AND_ASSIGN(CursorView);
286 // Test for Widget::SetCursor(). There is no Widget::GetCursor(), so this uses
287 // -[NSCursor currentCursor] to validate expectations. Note that currentCursor
288 // is just "the top cursor on the application's cursor stack.", which is why it
289 // is safe to use this in a non-interactive UI test with the EventGenerator.
290 TEST_F(NativeWidgetMacTest, SetCursor) {
291   NSCursor* arrow = [NSCursor arrowCursor];
292   NSCursor* hand = GetNativeHandCursor();
293   NSCursor* ibeam = GetNativeIBeamCursor();
295   Widget* widget = CreateTopLevelPlatformWidget();
296   widget->SetBounds(gfx::Rect(0, 0, 300, 300));
297   widget->GetContentsView()->AddChildView(new CursorView(0, hand));
298   widget->GetContentsView()->AddChildView(new CursorView(100, ibeam));
299   widget->Show();
301   // Events used to simulate tracking rectangle updates. These are not passed to
302   // toolkit-views, so it only matters whether they are inside or outside the
303   // content area.
304   NSEvent* event_in_content = cocoa_test_event_utils::MouseEventAtPoint(
305       NSMakePoint(100, 100), NSMouseMoved, 0);
306   NSEvent* event_out_of_content = cocoa_test_event_utils::MouseEventAtPoint(
307       NSMakePoint(-50, -50), NSMouseMoved, 0);
309   EXPECT_NE(arrow, hand);
310   EXPECT_NE(arrow, ibeam);
312   // At the start of the test, the cursor stack should be empty.
313   EXPECT_FALSE([NSCursor currentCursor]);
315   // Use an event generator to ask views code to set the cursor. However, note
316   // that this does not cause Cocoa to generate tracking rectangle updates.
317   ui::test::EventGenerator event_generator(GetContext(),
318                                            widget->GetNativeWindow());
320   // Move the mouse over the first view, then simulate a tracking rectangle
321   // update.
322   event_generator.MoveMouseTo(gfx::Point(50, 50));
323   [widget->GetNativeWindow() cursorUpdate:event_in_content];
324   EXPECT_EQ(hand, [NSCursor currentCursor]);
326   // A tracking rectangle update not in the content area should forward to
327   // the native NSWindow implementation, which sets the arrow cursor.
328   [widget->GetNativeWindow() cursorUpdate:event_out_of_content];
329   EXPECT_EQ(arrow, [NSCursor currentCursor]);
331   // Now move to the second view.
332   event_generator.MoveMouseTo(gfx::Point(150, 50));
333   [widget->GetNativeWindow() cursorUpdate:event_in_content];
334   EXPECT_EQ(ibeam, [NSCursor currentCursor]);
336   // Moving to the third view (but remaining in the content area) should also
337   // forward to the native NSWindow implementation.
338   event_generator.MoveMouseTo(gfx::Point(250, 50));
339   [widget->GetNativeWindow() cursorUpdate:event_in_content];
340   EXPECT_EQ(arrow, [NSCursor currentCursor]);
342   widget->CloseNow();
345 // Tests that an accessibility request from the system makes its way through to
346 // a views::Label filling the window.
347 TEST_F(NativeWidgetMacTest, AccessibilityIntegration) {
348   Widget* widget = CreateTopLevelPlatformWidget();
349   gfx::Rect screen_rect(50, 50, 100, 100);
350   widget->SetBounds(screen_rect);
352   const base::string16 test_string = base::ASCIIToUTF16("Green");
353   views::Label* label = new views::Label(test_string);
354   label->SetBounds(0, 0, 100, 100);
355   widget->GetContentsView()->AddChildView(label);
356   widget->Show();
358   // Accessibility hit tests come in Cocoa screen coordinates.
359   NSRect nsrect = gfx::ScreenRectToNSRect(screen_rect);
360   NSPoint midpoint = NSMakePoint(NSMidX(nsrect), NSMidY(nsrect));
362   id hit = [widget->GetNativeWindow() accessibilityHitTest:midpoint];
363   id title = [hit accessibilityAttributeValue:NSAccessibilityTitleAttribute];
364   EXPECT_NSEQ(title, @"Green");
367 }  // namespace test
368 }  // namespace views