Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / ui / views / widget / native_widget_mac_unittest.mm
blobec3d4c9be9748be88b63664240e4d7e77a0cfab6
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 #import "base/mac/scoped_nsobject.h"
10 #include "base/run_loop.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/strings/sys_string_conversions.h"
13 #import "testing/gtest_mac.h"
14 #import "ui/events/test/cocoa_test_event_utils.h"
15 #include "ui/events/test/event_generator.h"
16 #import "ui/gfx/mac/coordinate_conversion.h"
17 #import "ui/views/cocoa/bridged_native_widget.h"
18 #include "ui/views/controls/button/label_button.h"
19 #include "ui/views/controls/label.h"
20 #include "ui/views/native_cursor.h"
21 #include "ui/views/test/test_widget_observer.h"
22 #include "ui/views/test/widget_test.h"
23 #include "ui/views/widget/native_widget_private.h"
25 namespace views {
26 namespace test {
28 // Tests for parts of NativeWidgetMac not covered by BridgedNativeWidget, which
29 // need access to Cocoa APIs.
30 typedef WidgetTest NativeWidgetMacTest;
32 class WidgetChangeObserver : public TestWidgetObserver {
33  public:
34   WidgetChangeObserver(Widget* widget)
35       : TestWidgetObserver(widget),
36         gained_visible_count_(0),
37         lost_visible_count_(0) {}
39   int gained_visible_count() const { return gained_visible_count_; }
40   int lost_visible_count() const { return lost_visible_count_; }
42  private:
43   // WidgetObserver:
44   void OnWidgetVisibilityChanged(Widget* widget,
45                                  bool visible) override {
46     ++(visible ? gained_visible_count_ : lost_visible_count_);
47   }
49   int gained_visible_count_;
50   int lost_visible_count_;
52   DISALLOW_COPY_AND_ASSIGN(WidgetChangeObserver);
55 // Test visibility states triggered externally.
56 TEST_F(NativeWidgetMacTest, HideAndShowExternally) {
57   Widget* widget = CreateTopLevelPlatformWidget();
58   NSWindow* ns_window = widget->GetNativeWindow();
59   WidgetChangeObserver observer(widget);
61   // Should initially be hidden.
62   EXPECT_FALSE(widget->IsVisible());
63   EXPECT_FALSE([ns_window isVisible]);
64   EXPECT_EQ(0, observer.gained_visible_count());
65   EXPECT_EQ(0, observer.lost_visible_count());
67   widget->Show();
68   EXPECT_TRUE(widget->IsVisible());
69   EXPECT_TRUE([ns_window isVisible]);
70   EXPECT_EQ(1, observer.gained_visible_count());
71   EXPECT_EQ(0, observer.lost_visible_count());
73   widget->Hide();
74   EXPECT_FALSE(widget->IsVisible());
75   EXPECT_FALSE([ns_window isVisible]);
76   EXPECT_EQ(1, observer.gained_visible_count());
77   EXPECT_EQ(1, observer.lost_visible_count());
79   widget->Show();
80   EXPECT_TRUE(widget->IsVisible());
81   EXPECT_TRUE([ns_window isVisible]);
82   EXPECT_EQ(2, observer.gained_visible_count());
83   EXPECT_EQ(1, observer.lost_visible_count());
85   // Test when hiding individual windows.
86   [ns_window orderOut:nil];
87   EXPECT_FALSE(widget->IsVisible());
88   EXPECT_FALSE([ns_window isVisible]);
89   EXPECT_EQ(2, observer.gained_visible_count());
90   EXPECT_EQ(2, observer.lost_visible_count());
92   [ns_window orderFront:nil];
93   EXPECT_TRUE(widget->IsVisible());
94   EXPECT_TRUE([ns_window isVisible]);
95   EXPECT_EQ(3, observer.gained_visible_count());
96   EXPECT_EQ(2, observer.lost_visible_count());
98   // Test when hiding the entire application. This doesn't send an orderOut:
99   // to the NSWindow.
100   [NSApp hide:nil];
101   // When the activation policy is NSApplicationActivationPolicyRegular, the
102   // calls via NSApp are asynchronous, and the run loop needs to be flushed.
103   // With NSApplicationActivationPolicyProhibited, the following RunUntilIdle
104   // calls are superfluous, but don't hurt.
105   base::RunLoop().RunUntilIdle();
106   EXPECT_FALSE(widget->IsVisible());
107   EXPECT_FALSE([ns_window isVisible]);
108   EXPECT_EQ(3, observer.gained_visible_count());
109   EXPECT_EQ(3, observer.lost_visible_count());
111   [NSApp unhideWithoutActivation];
112   base::RunLoop().RunUntilIdle();
113   EXPECT_TRUE(widget->IsVisible());
114   EXPECT_TRUE([ns_window isVisible]);
115   EXPECT_EQ(4, observer.gained_visible_count());
116   EXPECT_EQ(3, observer.lost_visible_count());
118   // Hide again to test unhiding with an activation.
119   [NSApp hide:nil];
120   base::RunLoop().RunUntilIdle();
121   EXPECT_EQ(4, observer.lost_visible_count());
122   [NSApp unhide:nil];
123   base::RunLoop().RunUntilIdle();
124   EXPECT_EQ(5, observer.gained_visible_count());
126   // Hide again to test makeKeyAndOrderFront:.
127   [ns_window orderOut:nil];
128   EXPECT_FALSE(widget->IsVisible());
129   EXPECT_FALSE([ns_window isVisible]);
130   EXPECT_EQ(5, observer.gained_visible_count());
131   EXPECT_EQ(5, observer.lost_visible_count());
133   [ns_window makeKeyAndOrderFront:nil];
134   EXPECT_TRUE(widget->IsVisible());
135   EXPECT_TRUE([ns_window isVisible]);
136   EXPECT_EQ(6, observer.gained_visible_count());
137   EXPECT_EQ(5, observer.lost_visible_count());
139   // No change when closing.
140   widget->CloseNow();
141   EXPECT_EQ(5, observer.lost_visible_count());
142   EXPECT_EQ(6, observer.gained_visible_count());
145 // A view that counts calls to OnPaint().
146 class PaintCountView : public View {
147  public:
148   PaintCountView() : paint_count_(0) {
149     SetBounds(0, 0, 100, 100);
150   }
152   // View:
153   void OnPaint(gfx::Canvas* canvas) override {
154     EXPECT_TRUE(GetWidget()->IsVisible());
155     ++paint_count_;
156   }
158   int paint_count() { return paint_count_; }
160  private:
161   int paint_count_;
163   DISALLOW_COPY_AND_ASSIGN(PaintCountView);
166 // Test minimized states triggered externally, implied visibility and restored
167 // bounds whilst minimized.
168 TEST_F(NativeWidgetMacTest, MiniaturizeExternally) {
169   Widget* widget = new Widget;
170   Widget::InitParams init_params(Widget::InitParams::TYPE_WINDOW);
171   // Make the layer not drawn, so that calls to paint can be observed
172   // synchronously.
173   init_params.layer_type = ui::LAYER_NOT_DRAWN;
174   widget->Init(init_params);
176   PaintCountView* view = new PaintCountView();
177   widget->GetContentsView()->AddChildView(view);
178   NSWindow* ns_window = widget->GetNativeWindow();
179   WidgetChangeObserver observer(widget);
181   widget->SetBounds(gfx::Rect(100, 100, 300, 300));
183   EXPECT_TRUE(view->IsDrawn());
184   EXPECT_EQ(0, view->paint_count());
185   widget->Show();
187   EXPECT_EQ(1, observer.gained_visible_count());
188   EXPECT_EQ(0, observer.lost_visible_count());
189   const gfx::Rect restored_bounds = widget->GetRestoredBounds();
190   EXPECT_FALSE(restored_bounds.IsEmpty());
191   EXPECT_FALSE(widget->IsMinimized());
192   EXPECT_TRUE(widget->IsVisible());
194   // Showing should paint.
195   EXPECT_EQ(1, view->paint_count());
197   // First try performMiniaturize:, which requires a minimize button. Note that
198   // Cocoa just blocks the UI thread during the animation, so no need to do
199   // anything fancy to wait for it finish.
200   [ns_window performMiniaturize:nil];
202   EXPECT_TRUE(widget->IsMinimized());
203   EXPECT_FALSE(widget->IsVisible());  // Minimizing also makes things invisible.
204   EXPECT_EQ(1, observer.gained_visible_count());
205   EXPECT_EQ(1, observer.lost_visible_count());
206   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
208   // No repaint when minimizing. But note that this is partly due to not calling
209   // [NSView setNeedsDisplay:YES] on the content view. The superview, which is
210   // an NSThemeFrame, would repaint |view| if we had, because the miniaturize
211   // button is highlighted for performMiniaturize.
212   EXPECT_EQ(1, view->paint_count());
214   [ns_window deminiaturize:nil];
216   EXPECT_FALSE(widget->IsMinimized());
217   EXPECT_TRUE(widget->IsVisible());
218   EXPECT_EQ(2, observer.gained_visible_count());
219   EXPECT_EQ(1, observer.lost_visible_count());
220   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
222   EXPECT_EQ(2, view->paint_count());  // A single paint when deminiaturizing.
223   EXPECT_FALSE([ns_window isMiniaturized]);
225   widget->Minimize();
227   EXPECT_TRUE(widget->IsMinimized());
228   EXPECT_TRUE([ns_window isMiniaturized]);
229   EXPECT_EQ(2, observer.gained_visible_count());
230   EXPECT_EQ(2, observer.lost_visible_count());
231   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
232   EXPECT_EQ(2, view->paint_count());  // No paint when miniaturizing.
234   widget->Restore();  // If miniaturized, should deminiaturize.
236   EXPECT_FALSE(widget->IsMinimized());
237   EXPECT_FALSE([ns_window isMiniaturized]);
238   EXPECT_EQ(3, observer.gained_visible_count());
239   EXPECT_EQ(2, observer.lost_visible_count());
240   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
241   EXPECT_EQ(3, view->paint_count());
243   widget->Restore();  // If not miniaturized, does nothing.
245   EXPECT_FALSE(widget->IsMinimized());
246   EXPECT_FALSE([ns_window isMiniaturized]);
247   EXPECT_EQ(3, observer.gained_visible_count());
248   EXPECT_EQ(2, observer.lost_visible_count());
249   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
250   EXPECT_EQ(3, view->paint_count());
252   widget->CloseNow();
254   // Create a widget without a minimize button.
255   widget = CreateTopLevelFramelessPlatformWidget();
256   ns_window = widget->GetNativeWindow();
257   widget->SetBounds(gfx::Rect(100, 100, 300, 300));
258   widget->Show();
259   EXPECT_FALSE(widget->IsMinimized());
261   // This should fail, since performMiniaturize: requires a minimize button.
262   [ns_window performMiniaturize:nil];
263   EXPECT_FALSE(widget->IsMinimized());
265   // But this should work.
266   widget->Minimize();
267   EXPECT_TRUE(widget->IsMinimized());
269   // Test closing while minimized.
270   widget->CloseNow();
273 // Simple view for the SetCursor test that overrides View::GetCursor().
274 class CursorView : public View {
275  public:
276   CursorView(int x, NSCursor* cursor) : cursor_(cursor) {
277     SetBounds(x, 0, 100, 300);
278   }
280   // View:
281   gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override {
282     return cursor_;
283   }
285  private:
286   NSCursor* cursor_;
288   DISALLOW_COPY_AND_ASSIGN(CursorView);
291 // Test for Widget::SetCursor(). There is no Widget::GetCursor(), so this uses
292 // -[NSCursor currentCursor] to validate expectations. Note that currentCursor
293 // is just "the top cursor on the application's cursor stack.", which is why it
294 // is safe to use this in a non-interactive UI test with the EventGenerator.
295 TEST_F(NativeWidgetMacTest, SetCursor) {
296   NSCursor* arrow = [NSCursor arrowCursor];
297   NSCursor* hand = GetNativeHandCursor();
298   NSCursor* ibeam = GetNativeIBeamCursor();
300   Widget* widget = CreateTopLevelPlatformWidget();
301   widget->SetBounds(gfx::Rect(0, 0, 300, 300));
302   widget->GetContentsView()->AddChildView(new CursorView(0, hand));
303   widget->GetContentsView()->AddChildView(new CursorView(100, ibeam));
304   widget->Show();
306   // Events used to simulate tracking rectangle updates. These are not passed to
307   // toolkit-views, so it only matters whether they are inside or outside the
308   // content area.
309   NSEvent* event_in_content = cocoa_test_event_utils::MouseEventAtPoint(
310       NSMakePoint(100, 100), NSMouseMoved, 0);
311   NSEvent* event_out_of_content = cocoa_test_event_utils::MouseEventAtPoint(
312       NSMakePoint(-50, -50), NSMouseMoved, 0);
314   EXPECT_NE(arrow, hand);
315   EXPECT_NE(arrow, ibeam);
317   // At the start of the test, the cursor stack should be empty.
318   EXPECT_FALSE([NSCursor currentCursor]);
320   // Use an event generator to ask views code to set the cursor. However, note
321   // that this does not cause Cocoa to generate tracking rectangle updates.
322   ui::test::EventGenerator event_generator(GetContext(),
323                                            widget->GetNativeWindow());
325   // Move the mouse over the first view, then simulate a tracking rectangle
326   // update.
327   event_generator.MoveMouseTo(gfx::Point(50, 50));
328   [widget->GetNativeWindow() cursorUpdate:event_in_content];
329   EXPECT_EQ(hand, [NSCursor currentCursor]);
331   // A tracking rectangle update not in the content area should forward to
332   // the native NSWindow implementation, which sets the arrow cursor.
333   [widget->GetNativeWindow() cursorUpdate:event_out_of_content];
334   EXPECT_EQ(arrow, [NSCursor currentCursor]);
336   // Now move to the second view.
337   event_generator.MoveMouseTo(gfx::Point(150, 50));
338   [widget->GetNativeWindow() cursorUpdate:event_in_content];
339   EXPECT_EQ(ibeam, [NSCursor currentCursor]);
341   // Moving to the third view (but remaining in the content area) should also
342   // forward to the native NSWindow implementation.
343   event_generator.MoveMouseTo(gfx::Point(250, 50));
344   [widget->GetNativeWindow() cursorUpdate:event_in_content];
345   EXPECT_EQ(arrow, [NSCursor currentCursor]);
347   widget->CloseNow();
350 // Tests that an accessibility request from the system makes its way through to
351 // a views::Label filling the window.
352 TEST_F(NativeWidgetMacTest, AccessibilityIntegration) {
353   Widget* widget = CreateTopLevelPlatformWidget();
354   gfx::Rect screen_rect(50, 50, 100, 100);
355   widget->SetBounds(screen_rect);
357   const base::string16 test_string = base::ASCIIToUTF16("Green");
358   views::Label* label = new views::Label(test_string);
359   label->SetBounds(0, 0, 100, 100);
360   widget->GetContentsView()->AddChildView(label);
361   widget->Show();
363   // Accessibility hit tests come in Cocoa screen coordinates.
364   NSRect nsrect = gfx::ScreenRectToNSRect(screen_rect);
365   NSPoint midpoint = NSMakePoint(NSMidX(nsrect), NSMidY(nsrect));
367   id hit = [widget->GetNativeWindow() accessibilityHitTest:midpoint];
368   id title = [hit accessibilityAttributeValue:NSAccessibilityTitleAttribute];
369   EXPECT_NSEQ(title, @"Green");
372 // Tests creating a views::Widget parented off a native NSWindow.
373 TEST_F(NativeWidgetMacTest, NonWidgetParent) {
374   NSRect parent_nsrect = NSMakeRect(100, 100, 300, 200);
375   base::scoped_nsobject<NSWindow> native_parent(
376       [[NSWindow alloc] initWithContentRect:parent_nsrect
377                                   styleMask:NSBorderlessWindowMask
378                                     backing:NSBackingStoreBuffered
379                                       defer:NO]);
380   [native_parent setReleasedWhenClosed:NO];  // Owned by scoped_nsobject.
381   [native_parent makeKeyAndOrderFront:nil];
383   // Note: Don't use WidgetTest::CreateChildPlatformWidget because that makes
384   // windows of TYPE_CONTROL which are automatically made visible. But still
385   // mark it as a child to test window positioning.
386   Widget* child = new Widget;
387   Widget::InitParams init_params;
388   init_params.parent = [native_parent contentView];
389   init_params.child = true;
390   child->Init(init_params);
392   TestWidgetObserver child_observer(child);
394   // GetTopLevelNativeWidget() only goes as far as there exists a Widget (i.e.
395   // must stop at |child|.
396   internal::NativeWidgetPrivate* top_level_widget =
397       internal::NativeWidgetPrivate::GetTopLevelNativeWidget(
398           child->GetNativeView());
399   EXPECT_EQ(child, top_level_widget->GetWidget());
401   // To verify the parent, we need to use NativeWidgetMac APIs.
402   BridgedNativeWidget* bridged_native_widget =
403       NativeWidgetMac::GetBridgeForNativeWindow(child->GetNativeWindow());
404   EXPECT_EQ(native_parent, bridged_native_widget->parent()->GetNSWindow());
406   child->SetBounds(gfx::Rect(50, 50, 200, 100));
407   EXPECT_FALSE(child->IsVisible());
408   EXPECT_EQ(0u, [[native_parent childWindows] count]);
410   child->Show();
411   EXPECT_TRUE(child->IsVisible());
412   EXPECT_EQ(1u, [[native_parent childWindows] count]);
413   EXPECT_EQ(child->GetNativeWindow(),
414             [[native_parent childWindows] objectAtIndex:0]);
415   EXPECT_EQ(native_parent, [child->GetNativeWindow() parentWindow]);
417   // Child should be positioned on screen relative to the parent, but note we
418   // positioned the parent in Cooca coordinates, so we need to convert.
419   gfx::Point parent_origin = gfx::ScreenRectFromNSRect(parent_nsrect).origin();
420   EXPECT_EQ(gfx::Rect(150, parent_origin.y() + 50, 200, 100),
421             child->GetWindowBoundsInScreen());
423   // Closing the parent should close and destroy the child.
424   EXPECT_FALSE(child_observer.widget_closed());
425   [native_parent close];
426   EXPECT_TRUE(child_observer.widget_closed());
428   EXPECT_EQ(0u, [[native_parent childWindows] count]);
431 // Use Native APIs to query the tooltip text that would be shown once the
432 // tooltip delay had elapsed.
433 base::string16 TooltipTextForWidget(Widget* widget) {
434   // For Mac, the actual location doesn't matter, since there is only one native
435   // view and it fills the window. This just assumes the window is at least big
436   // big enough for a constant coordinate to be within it.
437   NSPoint point = NSMakePoint(30, 30);
438   NSView* view = [widget->GetNativeView() hitTest:point];
439   NSString* text =
440       [view view:view stringForToolTip:0 point:point userData:nullptr];
441   return base::SysNSStringToUTF16(text);
444 // Tests tooltips. The test doesn't wait for tooltips to appear. That is, the
445 // test assumes Cocoa calls stringForToolTip: at appropriate times and that,
446 // when a tooltip is already visible, changing it causes an update. These were
447 // tested manually by inserting a base::RunLoop.Run().
448 TEST_F(NativeWidgetMacTest, Tooltips) {
449   Widget* widget = CreateTopLevelPlatformWidget();
450   gfx::Rect screen_rect(50, 50, 100, 100);
451   widget->SetBounds(screen_rect);
453   const base::string16 tooltip_back = base::ASCIIToUTF16("Back");
454   const base::string16 tooltip_front = base::ASCIIToUTF16("Front");
455   const base::string16 long_tooltip(2000, 'W');
457   // Create a nested layout to test corner cases.
458   LabelButton* back = new LabelButton(nullptr, base::string16());
459   back->SetBounds(10, 10, 80, 80);
460   widget->GetContentsView()->AddChildView(back);
461   widget->Show();
463   ui::test::EventGenerator event_generator(GetContext(),
464                                            widget->GetNativeWindow());
466   // Initially, there should be no tooltip.
467   event_generator.MoveMouseTo(gfx::Point(50, 50));
468   EXPECT_TRUE(TooltipTextForWidget(widget).empty());
470   // Create a new button for the "front", and set the tooltip, but don't add it
471   // to the view hierarchy yet.
472   LabelButton* front = new LabelButton(nullptr, base::string16());
473   front->SetBounds(20, 20, 40, 40);
474   front->SetTooltipText(tooltip_front);
476   // Changing the tooltip text shouldn't require an additional mousemove to take
477   // effect.
478   EXPECT_TRUE(TooltipTextForWidget(widget).empty());
479   back->SetTooltipText(tooltip_back);
480   EXPECT_EQ(tooltip_back, TooltipTextForWidget(widget));
482   // Adding a new view under the mouse should also take immediate effect.
483   back->AddChildView(front);
484   EXPECT_EQ(tooltip_front, TooltipTextForWidget(widget));
486   // A long tooltip will be wrapped by Cocoa, but the full string should appear.
487   // Note that render widget hosts clip at 1024 to prevent DOS, but in toolkit-
488   // views the UI is more trusted.
489   front->SetTooltipText(long_tooltip);
490   EXPECT_EQ(long_tooltip, TooltipTextForWidget(widget));
492   // Move the mouse to a different view - tooltip should change.
493   event_generator.MoveMouseTo(gfx::Point(15, 15));
494   EXPECT_EQ(tooltip_back, TooltipTextForWidget(widget));
496   // Move the mouse off of any view, tooltip should clear.
497   event_generator.MoveMouseTo(gfx::Point(5, 5));
498   EXPECT_TRUE(TooltipTextForWidget(widget).empty());
501 }  // namespace test
502 }  // namespace views