Popular sites on the NTP: Favicon improvements
[chromium-blink-merge.git] / ui / views / widget / native_widget_mac_unittest.mm
blobf97fcb0ce1d07ed6a7dde632531d6d4c9bb326a2
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/foundation_util.h"
10 #import "base/mac/scoped_nsobject.h"
11 #import "base/mac/scoped_objc_class_swizzler.h"
12 #include "base/run_loop.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "base/test/test_timeouts.h"
16 #import "testing/gtest_mac.h"
17 #include "third_party/skia/include/core/SkBitmap.h"
18 #include "third_party/skia/include/core/SkCanvas.h"
19 #import "ui/base/cocoa/constrained_window/constrained_window_animation.h"
20 #import "ui/base/cocoa/window_size_constants.h"
21 #import "ui/events/test/cocoa_test_event_utils.h"
22 #include "ui/events/test/event_generator.h"
23 #import "ui/gfx/mac/coordinate_conversion.h"
24 #import "ui/views/cocoa/bridged_native_widget.h"
25 #import "ui/views/cocoa/native_widget_mac_nswindow.h"
26 #include "ui/views/controls/button/label_button.h"
27 #include "ui/views/controls/label.h"
28 #include "ui/views/native_cursor.h"
29 #include "ui/views/test/test_widget_observer.h"
30 #include "ui/views/test/widget_test.h"
31 #include "ui/views/widget/native_widget_private.h"
32 #include "ui/views/window/dialog_delegate.h"
34 // Donates an implementation of -[NSAnimation stopAnimation] which calls the
35 // original implementation, then quits a nested run loop.
36 @interface TestStopAnimationWaiter : NSObject
37 @end
39 @interface ConstrainedWindowAnimationBase (TestingAPI)
40 - (void)setWindowStateForEnd;
41 @end
43 @interface NSWindow (PrivateAPI)
44 - (BOOL)_isTitleHidden;
45 @end
47 // Test NSWindow that provides hooks via method overrides to verify behavior.
48 @interface NativeWidetMacTestWindow : NativeWidgetMacNSWindow {
49  @private
50   int invalidateShadowCount_;
52 @property(readonly, nonatomic) int invalidateShadowCount;
53 @end
55 namespace views {
56 namespace test {
58 // BridgedNativeWidget friend to access private members.
59 class BridgedNativeWidgetTestApi {
60  public:
61   explicit BridgedNativeWidgetTestApi(NSWindow* window) {
62     bridge_ = NativeWidgetMac::GetBridgeForNativeWindow(window);
63   }
65   // Simulate a frame swap from the compositor. Assumes scale factor of 1.0f.
66   void SimulateFrameSwap(const gfx::Size& size) {
67     const float kScaleFactor = 1.0f;
68     SkBitmap bitmap;
69     bitmap.allocN32Pixels(size.width(), size.height());
70     SkCanvas canvas(bitmap);
71     bridge_->compositor_widget_->GotSoftwareFrame(kScaleFactor, &canvas);
72     std::vector<ui::LatencyInfo> latency_info;
73     bridge_->AcceleratedWidgetSwapCompleted(latency_info);
74   }
76  private:
77   BridgedNativeWidget* bridge_;
79   DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetTestApi);
82 // Custom native_widget to create a NativeWidgetMacTestWindow.
83 class TestWindowNativeWidgetMac : public NativeWidgetMac {
84  public:
85   explicit TestWindowNativeWidgetMac(Widget* delegate)
86       : NativeWidgetMac(delegate) {}
88  protected:
89   // NativeWidgetMac:
90   gfx::NativeWindow CreateNSWindow(const Widget::InitParams& params) override {
91     NSUInteger style_mask = NSBorderlessWindowMask;
92     if (params.type == Widget::InitParams::TYPE_WINDOW) {
93       style_mask = NSTexturedBackgroundWindowMask | NSTitledWindowMask |
94                    NSClosableWindowMask | NSMiniaturizableWindowMask |
95                    NSResizableWindowMask;
96     }
97     return [[[NativeWidetMacTestWindow alloc]
98         initWithContentRect:ui::kWindowSizeDeterminedLater
99                   styleMask:style_mask
100                     backing:NSBackingStoreBuffered
101                       defer:NO] autorelease];
102   }
104  private:
105   DISALLOW_COPY_AND_ASSIGN(TestWindowNativeWidgetMac);
108 // Tests for parts of NativeWidgetMac not covered by BridgedNativeWidget, which
109 // need access to Cocoa APIs.
110 class NativeWidgetMacTest : public WidgetTest {
111  public:
112   NativeWidgetMacTest() {}
114   // The content size of NSWindows made by MakeNativeParent().
115   NSRect ParentRect() const { return NSMakeRect(100, 100, 300, 200); }
117   // Make a native NSWindow to use as a parent.
118   NSWindow* MakeNativeParent() {
119     native_parent_.reset(
120         [[NSWindow alloc] initWithContentRect:ParentRect()
121                                     styleMask:NSBorderlessWindowMask
122                                       backing:NSBackingStoreBuffered
123                                         defer:NO]);
124     [native_parent_ setReleasedWhenClosed:NO];  // Owned by scoped_nsobject.
125     [native_parent_ makeKeyAndOrderFront:nil];
126     return native_parent_;
127   }
129   // Create a Widget backed by the NativeWidetMacTestWindow NSWindow subclass.
130   Widget* CreateWidgetWithTestWindow(Widget::InitParams params,
131                                      NativeWidetMacTestWindow** window) {
132     Widget* widget = new Widget;
133     params.native_widget = new TestWindowNativeWidgetMac(widget);
134     widget->Init(params);
135     widget->Show();
136     *window = base::mac::ObjCCastStrict<NativeWidetMacTestWindow>(
137         widget->GetNativeWindow());
138     EXPECT_TRUE(*window);
139     return widget;
140   }
142  private:
143   base::scoped_nsobject<NSWindow> native_parent_;
145   DISALLOW_COPY_AND_ASSIGN(NativeWidgetMacTest);
148 class WidgetChangeObserver : public TestWidgetObserver {
149  public:
150   WidgetChangeObserver(Widget* widget) : TestWidgetObserver(widget) {}
152   void WaitForVisibleCounts(int gained, int lost) {
153     if (gained_visible_count_ >= gained && lost_visible_count_ >= lost)
154       return;
156     target_gained_visible_count_ = gained;
157     target_lost_visible_count_ = lost;
159     base::RunLoop run_loop;
160     run_loop_ = &run_loop;
161     base::MessageLoop::current()->task_runner()->PostDelayedTask(
162         FROM_HERE, run_loop.QuitClosure(), TestTimeouts::action_timeout());
163     run_loop.Run();
164     run_loop_ = nullptr;
165   }
167   int gained_visible_count() const { return gained_visible_count_; }
168   int lost_visible_count() const { return lost_visible_count_; }
170  private:
171   // WidgetObserver:
172   void OnWidgetVisibilityChanged(Widget* widget,
173                                  bool visible) override {
174     ++(visible ? gained_visible_count_ : lost_visible_count_);
175     if (run_loop_ && gained_visible_count_ >= target_gained_visible_count_ &&
176         lost_visible_count_ >= target_lost_visible_count_)
177       run_loop_->Quit();
178   }
180   int gained_visible_count_ = 0;
181   int lost_visible_count_ = 0;
182   int target_gained_visible_count_ = 0;
183   int target_lost_visible_count_ = 0;
184   base::RunLoop* run_loop_ = nullptr;
186   DISALLOW_COPY_AND_ASSIGN(WidgetChangeObserver);
189 // Test visibility states triggered externally.
190 TEST_F(NativeWidgetMacTest, HideAndShowExternally) {
191   Widget* widget = CreateTopLevelPlatformWidget();
192   NSWindow* ns_window = widget->GetNativeWindow();
193   WidgetChangeObserver observer(widget);
195   // Should initially be hidden.
196   EXPECT_FALSE(widget->IsVisible());
197   EXPECT_FALSE([ns_window isVisible]);
198   EXPECT_EQ(0, observer.gained_visible_count());
199   EXPECT_EQ(0, observer.lost_visible_count());
201   widget->Show();
202   EXPECT_TRUE(widget->IsVisible());
203   EXPECT_TRUE([ns_window isVisible]);
204   EXPECT_EQ(1, observer.gained_visible_count());
205   EXPECT_EQ(0, observer.lost_visible_count());
207   widget->Hide();
208   EXPECT_FALSE(widget->IsVisible());
209   EXPECT_FALSE([ns_window isVisible]);
210   EXPECT_EQ(1, observer.gained_visible_count());
211   EXPECT_EQ(1, observer.lost_visible_count());
213   widget->Show();
214   EXPECT_TRUE(widget->IsVisible());
215   EXPECT_TRUE([ns_window isVisible]);
216   EXPECT_EQ(2, observer.gained_visible_count());
217   EXPECT_EQ(1, observer.lost_visible_count());
219   // Test when hiding individual windows.
220   [ns_window orderOut:nil];
221   EXPECT_FALSE(widget->IsVisible());
222   EXPECT_FALSE([ns_window isVisible]);
223   EXPECT_EQ(2, observer.gained_visible_count());
224   EXPECT_EQ(2, observer.lost_visible_count());
226   [ns_window orderFront:nil];
227   EXPECT_TRUE(widget->IsVisible());
228   EXPECT_TRUE([ns_window isVisible]);
229   EXPECT_EQ(3, observer.gained_visible_count());
230   EXPECT_EQ(2, observer.lost_visible_count());
232   // Test when hiding the entire application. This doesn't send an orderOut:
233   // to the NSWindow.
234   [NSApp hide:nil];
235   // When the activation policy is NSApplicationActivationPolicyRegular, the
236   // calls via NSApp are asynchronous, and the run loop needs to be flushed.
237   // With NSApplicationActivationPolicyProhibited, the following
238   // WaitForVisibleCounts calls are superfluous, but don't hurt.
239   observer.WaitForVisibleCounts(3, 3);
240   EXPECT_FALSE(widget->IsVisible());
241   EXPECT_FALSE([ns_window isVisible]);
242   EXPECT_EQ(3, observer.gained_visible_count());
243   EXPECT_EQ(3, observer.lost_visible_count());
245   [NSApp unhideWithoutActivation];
246   observer.WaitForVisibleCounts(4, 3);
247   EXPECT_TRUE(widget->IsVisible());
248   EXPECT_TRUE([ns_window isVisible]);
249   EXPECT_EQ(4, observer.gained_visible_count());
250   EXPECT_EQ(3, observer.lost_visible_count());
252   // Hide again to test unhiding with an activation.
253   [NSApp hide:nil];
254   observer.WaitForVisibleCounts(4, 4);
255   EXPECT_EQ(4, observer.lost_visible_count());
256   [NSApp unhide:nil];
257   observer.WaitForVisibleCounts(5, 4);
258   EXPECT_EQ(5, observer.gained_visible_count());
260   // Hide again to test makeKeyAndOrderFront:.
261   [ns_window orderOut:nil];
262   EXPECT_FALSE(widget->IsVisible());
263   EXPECT_FALSE([ns_window isVisible]);
264   EXPECT_EQ(5, observer.gained_visible_count());
265   EXPECT_EQ(5, observer.lost_visible_count());
267   [ns_window makeKeyAndOrderFront:nil];
268   EXPECT_TRUE(widget->IsVisible());
269   EXPECT_TRUE([ns_window isVisible]);
270   EXPECT_EQ(6, observer.gained_visible_count());
271   EXPECT_EQ(5, observer.lost_visible_count());
273   // No change when closing.
274   widget->CloseNow();
275   EXPECT_EQ(5, observer.lost_visible_count());
276   EXPECT_EQ(6, observer.gained_visible_count());
279 // A view that counts calls to OnPaint().
280 class PaintCountView : public View {
281  public:
282   PaintCountView() : paint_count_(0) {
283     SetBounds(0, 0, 100, 100);
284   }
286   // View:
287   void OnPaint(gfx::Canvas* canvas) override {
288     EXPECT_TRUE(GetWidget()->IsVisible());
289     ++paint_count_;
290   }
292   int paint_count() { return paint_count_; }
294  private:
295   int paint_count_;
297   DISALLOW_COPY_AND_ASSIGN(PaintCountView);
300 // Test minimized states triggered externally, implied visibility and restored
301 // bounds whilst minimized.
302 TEST_F(NativeWidgetMacTest, MiniaturizeExternally) {
303   Widget* widget = new Widget;
304   Widget::InitParams init_params(Widget::InitParams::TYPE_WINDOW);
305   widget->Init(init_params);
307   PaintCountView* view = new PaintCountView();
308   widget->GetContentsView()->AddChildView(view);
309   NSWindow* ns_window = widget->GetNativeWindow();
310   WidgetChangeObserver observer(widget);
312   widget->SetBounds(gfx::Rect(100, 100, 300, 300));
314   EXPECT_TRUE(view->IsDrawn());
315   EXPECT_EQ(0, view->paint_count());
316   widget->Show();
317   base::RunLoop().RunUntilIdle();
319   EXPECT_EQ(1, observer.gained_visible_count());
320   EXPECT_EQ(0, observer.lost_visible_count());
321   const gfx::Rect restored_bounds = widget->GetRestoredBounds();
322   EXPECT_FALSE(restored_bounds.IsEmpty());
323   EXPECT_FALSE(widget->IsMinimized());
324   EXPECT_TRUE(widget->IsVisible());
326   // Showing should paint.
327   EXPECT_EQ(1, view->paint_count());
329   // First try performMiniaturize:, which requires a minimize button. Note that
330   // Cocoa just blocks the UI thread during the animation, so no need to do
331   // anything fancy to wait for it finish.
332   [ns_window performMiniaturize:nil];
333   base::RunLoop().RunUntilIdle();
335   EXPECT_TRUE(widget->IsMinimized());
336   EXPECT_FALSE(widget->IsVisible());  // Minimizing also makes things invisible.
337   EXPECT_EQ(1, observer.gained_visible_count());
338   EXPECT_EQ(1, observer.lost_visible_count());
339   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
341   // No repaint when minimizing. But note that this is partly due to not calling
342   // [NSView setNeedsDisplay:YES] on the content view. The superview, which is
343   // an NSThemeFrame, would repaint |view| if we had, because the miniaturize
344   // button is highlighted for performMiniaturize.
345   EXPECT_EQ(1, view->paint_count());
347   [ns_window deminiaturize:nil];
348   base::RunLoop().RunUntilIdle();
350   EXPECT_FALSE(widget->IsMinimized());
351   EXPECT_TRUE(widget->IsVisible());
352   EXPECT_EQ(2, observer.gained_visible_count());
353   EXPECT_EQ(1, observer.lost_visible_count());
354   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
356   EXPECT_EQ(2, view->paint_count());  // A single paint when deminiaturizing.
357   EXPECT_FALSE([ns_window isMiniaturized]);
359   widget->Minimize();
360   base::RunLoop().RunUntilIdle();
362   EXPECT_TRUE(widget->IsMinimized());
363   EXPECT_TRUE([ns_window isMiniaturized]);
364   EXPECT_EQ(2, observer.gained_visible_count());
365   EXPECT_EQ(2, observer.lost_visible_count());
366   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
367   EXPECT_EQ(2, view->paint_count());  // No paint when miniaturizing.
369   widget->Restore();  // If miniaturized, should deminiaturize.
370   base::RunLoop().RunUntilIdle();
372   EXPECT_FALSE(widget->IsMinimized());
373   EXPECT_FALSE([ns_window isMiniaturized]);
374   EXPECT_EQ(3, observer.gained_visible_count());
375   EXPECT_EQ(2, observer.lost_visible_count());
376   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
377   EXPECT_EQ(3, view->paint_count());
379   widget->Restore();  // If not miniaturized, does nothing.
380   base::RunLoop().RunUntilIdle();
382   EXPECT_FALSE(widget->IsMinimized());
383   EXPECT_FALSE([ns_window isMiniaturized]);
384   EXPECT_EQ(3, observer.gained_visible_count());
385   EXPECT_EQ(2, observer.lost_visible_count());
386   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
387   EXPECT_EQ(3, view->paint_count());
389   widget->CloseNow();
391   // Create a widget without a minimize button.
392   widget = CreateTopLevelFramelessPlatformWidget();
393   ns_window = widget->GetNativeWindow();
394   widget->SetBounds(gfx::Rect(100, 100, 300, 300));
395   widget->Show();
396   EXPECT_FALSE(widget->IsMinimized());
398   // This should fail, since performMiniaturize: requires a minimize button.
399   [ns_window performMiniaturize:nil];
400   EXPECT_FALSE(widget->IsMinimized());
402   // But this should work.
403   widget->Minimize();
404   EXPECT_TRUE(widget->IsMinimized());
406   // Test closing while minimized.
407   widget->CloseNow();
410 // Simple view for the SetCursor test that overrides View::GetCursor().
411 class CursorView : public View {
412  public:
413   CursorView(int x, NSCursor* cursor) : cursor_(cursor) {
414     SetBounds(x, 0, 100, 300);
415   }
417   // View:
418   gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override {
419     return cursor_;
420   }
422  private:
423   NSCursor* cursor_;
425   DISALLOW_COPY_AND_ASSIGN(CursorView);
428 // Test for Widget::SetCursor(). There is no Widget::GetCursor(), so this uses
429 // -[NSCursor currentCursor] to validate expectations. Note that currentCursor
430 // is just "the top cursor on the application's cursor stack.", which is why it
431 // is safe to use this in a non-interactive UI test with the EventGenerator.
432 TEST_F(NativeWidgetMacTest, SetCursor) {
433   NSCursor* arrow = [NSCursor arrowCursor];
434   NSCursor* hand = GetNativeHandCursor();
435   NSCursor* ibeam = GetNativeIBeamCursor();
437   Widget* widget = CreateTopLevelPlatformWidget();
438   widget->SetBounds(gfx::Rect(0, 0, 300, 300));
439   widget->GetContentsView()->AddChildView(new CursorView(0, hand));
440   widget->GetContentsView()->AddChildView(new CursorView(100, ibeam));
441   widget->Show();
443   // Events used to simulate tracking rectangle updates. These are not passed to
444   // toolkit-views, so it only matters whether they are inside or outside the
445   // content area.
446   NSEvent* event_in_content = cocoa_test_event_utils::MouseEventAtPoint(
447       NSMakePoint(100, 100), NSMouseMoved, 0);
448   NSEvent* event_out_of_content = cocoa_test_event_utils::MouseEventAtPoint(
449       NSMakePoint(-50, -50), NSMouseMoved, 0);
451   EXPECT_NE(arrow, hand);
452   EXPECT_NE(arrow, ibeam);
454   // At the start of the test, the cursor stack should be empty.
455   EXPECT_FALSE([NSCursor currentCursor]);
457   // Use an event generator to ask views code to set the cursor. However, note
458   // that this does not cause Cocoa to generate tracking rectangle updates.
459   ui::test::EventGenerator event_generator(GetContext(),
460                                            widget->GetNativeWindow());
462   // Move the mouse over the first view, then simulate a tracking rectangle
463   // update.
464   event_generator.MoveMouseTo(gfx::Point(50, 50));
465   [widget->GetNativeWindow() cursorUpdate:event_in_content];
466   EXPECT_EQ(hand, [NSCursor currentCursor]);
468   // A tracking rectangle update not in the content area should forward to
469   // the native NSWindow implementation, which sets the arrow cursor.
470   [widget->GetNativeWindow() cursorUpdate:event_out_of_content];
471   EXPECT_EQ(arrow, [NSCursor currentCursor]);
473   // Now move to the second view.
474   event_generator.MoveMouseTo(gfx::Point(150, 50));
475   [widget->GetNativeWindow() cursorUpdate:event_in_content];
476   EXPECT_EQ(ibeam, [NSCursor currentCursor]);
478   // Moving to the third view (but remaining in the content area) should also
479   // forward to the native NSWindow implementation.
480   event_generator.MoveMouseTo(gfx::Point(250, 50));
481   [widget->GetNativeWindow() cursorUpdate:event_in_content];
482   EXPECT_EQ(arrow, [NSCursor currentCursor]);
484   widget->CloseNow();
487 // Tests that an accessibility request from the system makes its way through to
488 // a views::Label filling the window.
489 TEST_F(NativeWidgetMacTest, AccessibilityIntegration) {
490   Widget* widget = CreateTopLevelPlatformWidget();
491   gfx::Rect screen_rect(50, 50, 100, 100);
492   widget->SetBounds(screen_rect);
494   const base::string16 test_string = base::ASCIIToUTF16("Green");
495   views::Label* label = new views::Label(test_string);
496   label->SetBounds(0, 0, 100, 100);
497   widget->GetContentsView()->AddChildView(label);
498   widget->Show();
500   // Accessibility hit tests come in Cocoa screen coordinates.
501   NSRect nsrect = gfx::ScreenRectToNSRect(screen_rect);
502   NSPoint midpoint = NSMakePoint(NSMidX(nsrect), NSMidY(nsrect));
504   id hit = [widget->GetNativeWindow() accessibilityHitTest:midpoint];
505   id title = [hit accessibilityAttributeValue:NSAccessibilityTitleAttribute];
506   EXPECT_NSEQ(title, @"Green");
508   widget->CloseNow();
511 // Tests creating a views::Widget parented off a native NSWindow.
512 TEST_F(NativeWidgetMacTest, NonWidgetParent) {
513   NSWindow* native_parent = MakeNativeParent();
515   base::scoped_nsobject<NSView> anchor_view(
516       [[NSView alloc] initWithFrame:[[native_parent contentView] bounds]]);
517   [[native_parent contentView] addSubview:anchor_view];
519   // Note: Don't use WidgetTest::CreateChildPlatformWidget because that makes
520   // windows of TYPE_CONTROL which are automatically made visible. But still
521   // mark it as a child to test window positioning.
522   Widget* child = new Widget;
523   Widget::InitParams init_params;
524   init_params.parent = anchor_view;
525   init_params.child = true;
526   child->Init(init_params);
528   TestWidgetObserver child_observer(child);
530   // GetTopLevelNativeWidget() only goes as far as there exists a Widget (i.e.
531   // must stop at |child|.
532   internal::NativeWidgetPrivate* top_level_widget =
533       internal::NativeWidgetPrivate::GetTopLevelNativeWidget(
534           child->GetNativeView());
535   EXPECT_EQ(child, top_level_widget->GetWidget());
537   // To verify the parent, we need to use NativeWidgetMac APIs.
538   BridgedNativeWidget* bridged_native_widget =
539       NativeWidgetMac::GetBridgeForNativeWindow(child->GetNativeWindow());
540   EXPECT_EQ(native_parent, bridged_native_widget->parent()->GetNSWindow());
542   child->SetBounds(gfx::Rect(50, 50, 200, 100));
543   EXPECT_FALSE(child->IsVisible());
544   EXPECT_EQ(0u, [[native_parent childWindows] count]);
546   child->Show();
547   EXPECT_TRUE(child->IsVisible());
548   EXPECT_EQ(1u, [[native_parent childWindows] count]);
549   EXPECT_EQ(child->GetNativeWindow(),
550             [[native_parent childWindows] objectAtIndex:0]);
551   EXPECT_EQ(native_parent, [child->GetNativeWindow() parentWindow]);
553   // Child should be positioned on screen relative to the parent, but note we
554   // positioned the parent in Cocoa coordinates, so we need to convert.
555   gfx::Point parent_origin = gfx::ScreenRectFromNSRect(ParentRect()).origin();
556   EXPECT_EQ(gfx::Rect(150, parent_origin.y() + 50, 200, 100),
557             child->GetWindowBoundsInScreen());
559   // Removing the anchor_view from its view hierarchy is permitted. This should
560   // not break the relationship between the two windows.
561   [anchor_view removeFromSuperview];
562   anchor_view.reset();
563   EXPECT_EQ(native_parent, bridged_native_widget->parent()->GetNSWindow());
565   // Closing the parent should close and destroy the child.
566   EXPECT_FALSE(child_observer.widget_closed());
567   [native_parent close];
568   EXPECT_TRUE(child_observer.widget_closed());
570   EXPECT_EQ(0u, [[native_parent childWindows] count]);
573 // Use Native APIs to query the tooltip text that would be shown once the
574 // tooltip delay had elapsed.
575 base::string16 TooltipTextForWidget(Widget* widget) {
576   // For Mac, the actual location doesn't matter, since there is only one native
577   // view and it fills the window. This just assumes the window is at least big
578   // big enough for a constant coordinate to be within it.
579   NSPoint point = NSMakePoint(30, 30);
580   NSView* view = [widget->GetNativeView() hitTest:point];
581   NSString* text =
582       [view view:view stringForToolTip:0 point:point userData:nullptr];
583   return base::SysNSStringToUTF16(text);
586 // Tests tooltips. The test doesn't wait for tooltips to appear. That is, the
587 // test assumes Cocoa calls stringForToolTip: at appropriate times and that,
588 // when a tooltip is already visible, changing it causes an update. These were
589 // tested manually by inserting a base::RunLoop.Run().
590 TEST_F(NativeWidgetMacTest, Tooltips) {
591   Widget* widget = CreateTopLevelPlatformWidget();
592   gfx::Rect screen_rect(50, 50, 100, 100);
593   widget->SetBounds(screen_rect);
595   const base::string16 tooltip_back = base::ASCIIToUTF16("Back");
596   const base::string16 tooltip_front = base::ASCIIToUTF16("Front");
597   const base::string16 long_tooltip(2000, 'W');
599   // Create a nested layout to test corner cases.
600   LabelButton* back = new LabelButton(nullptr, base::string16());
601   back->SetBounds(10, 10, 80, 80);
602   widget->GetContentsView()->AddChildView(back);
603   widget->Show();
605   ui::test::EventGenerator event_generator(GetContext(),
606                                            widget->GetNativeWindow());
608   // Initially, there should be no tooltip.
609   event_generator.MoveMouseTo(gfx::Point(50, 50));
610   EXPECT_TRUE(TooltipTextForWidget(widget).empty());
612   // Create a new button for the "front", and set the tooltip, but don't add it
613   // to the view hierarchy yet.
614   LabelButton* front = new LabelButton(nullptr, base::string16());
615   front->SetBounds(20, 20, 40, 40);
616   front->SetTooltipText(tooltip_front);
618   // Changing the tooltip text shouldn't require an additional mousemove to take
619   // effect.
620   EXPECT_TRUE(TooltipTextForWidget(widget).empty());
621   back->SetTooltipText(tooltip_back);
622   EXPECT_EQ(tooltip_back, TooltipTextForWidget(widget));
624   // Adding a new view under the mouse should also take immediate effect.
625   back->AddChildView(front);
626   EXPECT_EQ(tooltip_front, TooltipTextForWidget(widget));
628   // A long tooltip will be wrapped by Cocoa, but the full string should appear.
629   // Note that render widget hosts clip at 1024 to prevent DOS, but in toolkit-
630   // views the UI is more trusted.
631   front->SetTooltipText(long_tooltip);
632   EXPECT_EQ(long_tooltip, TooltipTextForWidget(widget));
634   // Move the mouse to a different view - tooltip should change.
635   event_generator.MoveMouseTo(gfx::Point(15, 15));
636   EXPECT_EQ(tooltip_back, TooltipTextForWidget(widget));
638   // Move the mouse off of any view, tooltip should clear.
639   event_generator.MoveMouseTo(gfx::Point(5, 5));
640   EXPECT_TRUE(TooltipTextForWidget(widget).empty());
642   widget->CloseNow();
645 namespace {
647 // Delegate to make Widgets of MODAL_TYPE_CHILD.
648 class ChildModalDialogDelegate : public DialogDelegateView {
649  public:
650   ChildModalDialogDelegate() {}
652   // WidgetDelegate:
653   ui::ModalType GetModalType() const override { return ui::MODAL_TYPE_CHILD; }
655  private:
656   DISALLOW_COPY_AND_ASSIGN(ChildModalDialogDelegate);
659 // While in scope, waits for a call to a swizzled objective C method, then quits
660 // a nested run loop.
661 class ScopedSwizzleWaiter {
662  public:
663   explicit ScopedSwizzleWaiter(Class target)
664       : swizzler_(target,
665                   [TestStopAnimationWaiter class],
666                   @selector(setWindowStateForEnd)) {
667     DCHECK(!instance_);
668     instance_ = this;
669   }
671   ~ScopedSwizzleWaiter() { instance_ = nullptr; }
673   static IMP GetMethodAndMarkCalled() {
674     return instance_->GetMethodInternal();
675   }
677   void WaitForMethod() {
678     if (method_called_)
679       return;
681     base::RunLoop run_loop;
682     base::MessageLoop::current()->task_runner()->PostDelayedTask(
683         FROM_HERE, run_loop.QuitClosure(), TestTimeouts::action_timeout());
684     run_loop_ = &run_loop;
685     run_loop.Run();
686     run_loop_ = nullptr;
687   }
689   bool method_called() const { return method_called_; }
691  private:
692   IMP GetMethodInternal() {
693     DCHECK(!method_called_);
694     method_called_ = true;
695     if (run_loop_)
696       run_loop_->Quit();
697     return swizzler_.GetOriginalImplementation();
698   }
700   static ScopedSwizzleWaiter* instance_;
702   base::mac::ScopedObjCClassSwizzler swizzler_;
703   base::RunLoop* run_loop_ = nullptr;
704   bool method_called_ = false;
706   DISALLOW_COPY_AND_ASSIGN(ScopedSwizzleWaiter);
709 ScopedSwizzleWaiter* ScopedSwizzleWaiter::instance_ = nullptr;
711 // Shows a modal widget and waits for the show animation to complete. Waiting is
712 // not compulsory (calling Close() while animating the show will cancel the show
713 // animation). However, testing with overlapping swizzlers is tricky.
714 Widget* ShowChildModalWidgetAndWait(NSWindow* native_parent) {
715   Widget* modal_dialog_widget = views::DialogDelegate::CreateDialogWidget(
716       new ChildModalDialogDelegate, nullptr, [native_parent contentView]);
718   modal_dialog_widget->SetBounds(gfx::Rect(50, 50, 200, 150));
719   EXPECT_FALSE(modal_dialog_widget->IsVisible());
720   ScopedSwizzleWaiter show_waiter([ConstrainedWindowAnimationShow class]);
722   modal_dialog_widget->Show();
723   // Visible immediately (although it animates from transparent).
724   EXPECT_TRUE(modal_dialog_widget->IsVisible());
726   // Run the animation.
727   show_waiter.WaitForMethod();
728   EXPECT_TRUE(modal_dialog_widget->IsVisible());
729   EXPECT_TRUE(show_waiter.method_called());
730   return modal_dialog_widget;
733 }  // namespace
735 // Tests object lifetime for the show/hide animations used for child-modal
736 // windows. Parents the dialog off a native parent window (not a views::Widget).
737 TEST_F(NativeWidgetMacTest, NativeWindowChildModalShowHide) {
738   NSWindow* native_parent = MakeNativeParent();
739   {
740     Widget* modal_dialog_widget = ShowChildModalWidgetAndWait(native_parent);
741     TestWidgetObserver widget_observer(modal_dialog_widget);
743     ScopedSwizzleWaiter hide_waiter([ConstrainedWindowAnimationHide class]);
744     EXPECT_TRUE(modal_dialog_widget->IsVisible());
745     EXPECT_FALSE(widget_observer.widget_closed());
747     // Widget::Close() is always asynchronous, so we can check that the widget
748     // is initially visible, but then it's destroyed.
749     modal_dialog_widget->Close();
750     EXPECT_TRUE(modal_dialog_widget->IsVisible());
751     EXPECT_FALSE(hide_waiter.method_called());
752     EXPECT_FALSE(widget_observer.widget_closed());
754     // Wait for a hide to finish.
755     hide_waiter.WaitForMethod();
756     EXPECT_TRUE(hide_waiter.method_called());
758     // The animation finishing should also mean it has closed the window.
759     EXPECT_TRUE(widget_observer.widget_closed());
760   }
762   {
763     // Make a new dialog to test another lifetime flow.
764     Widget* modal_dialog_widget = ShowChildModalWidgetAndWait(native_parent);
765     TestWidgetObserver widget_observer(modal_dialog_widget);
767     // Start an asynchronous close as above.
768     ScopedSwizzleWaiter hide_waiter([ConstrainedWindowAnimationHide class]);
769     modal_dialog_widget->Close();
770     EXPECT_FALSE(widget_observer.widget_closed());
771     EXPECT_FALSE(hide_waiter.method_called());
773     // Now close the _parent_ window to force a synchronous close of the child.
774     [native_parent close];
776     // Widget is destroyed immediately. No longer paints, but the animation is
777     // still running.
778     EXPECT_TRUE(widget_observer.widget_closed());
779     EXPECT_FALSE(hide_waiter.method_called());
781     // Wait for the hide again. It will call close on its retained copy of the
782     // child NSWindow, but that's fine since all the C++ objects are detached.
783     hide_waiter.WaitForMethod();
784     EXPECT_TRUE(hide_waiter.method_called());
785   }
788 // Test calls to Widget::ReparentNativeView() that result in a no-op on Mac.
789 // Tests with both native and non-native parents.
790 TEST_F(NativeWidgetMacTest, NoopReparentNativeView) {
791   NSWindow* parent = MakeNativeParent();
792   Widget* dialog = views::DialogDelegate::CreateDialogWidget(
793       new DialogDelegateView, nullptr, [parent contentView]);
794   BridgedNativeWidget* bridge =
795       NativeWidgetMac::GetBridgeForNativeWindow(dialog->GetNativeWindow());
797   EXPECT_EQ(bridge->parent()->GetNSWindow(), parent);
798   Widget::ReparentNativeView(dialog->GetNativeView(), [parent contentView]);
799   EXPECT_EQ(bridge->parent()->GetNSWindow(), parent);
801   [parent close];
803   Widget* parent_widget = CreateNativeDesktopWidget();
804   parent = parent_widget->GetNativeWindow();
805   dialog = views::DialogDelegate::CreateDialogWidget(
806       new DialogDelegateView, nullptr, [parent contentView]);
807   bridge = NativeWidgetMac::GetBridgeForNativeWindow(dialog->GetNativeWindow());
809   EXPECT_EQ(bridge->parent()->GetNSWindow(), parent);
810   Widget::ReparentNativeView(dialog->GetNativeView(), [parent contentView]);
811   EXPECT_EQ(bridge->parent()->GetNSWindow(), parent);
813   parent_widget->CloseNow();
816 // Tests Cocoa properties that should be given to particular widget types.
817 TEST_F(NativeWidgetMacTest, NativeProperties) {
818   // Create a regular widget (TYPE_WINDOW).
819   Widget* regular_widget = CreateNativeDesktopWidget();
820   EXPECT_TRUE([regular_widget->GetNativeWindow() canBecomeKeyWindow]);
821   EXPECT_TRUE([regular_widget->GetNativeWindow() canBecomeMainWindow]);
823   // Disabling activation should prevent key and main status.
824   regular_widget->widget_delegate()->set_can_activate(false);
825   EXPECT_FALSE([regular_widget->GetNativeWindow() canBecomeKeyWindow]);
826   EXPECT_FALSE([regular_widget->GetNativeWindow() canBecomeMainWindow]);
828   // Create a dialog widget (also TYPE_WINDOW), but with a DialogDelegate.
829   Widget* dialog_widget = views::DialogDelegate::CreateDialogWidget(
830       new ChildModalDialogDelegate, nullptr, regular_widget->GetNativeView());
831   EXPECT_TRUE([dialog_widget->GetNativeWindow() canBecomeKeyWindow]);
832   // Dialogs shouldn't take main status away from their parent.
833   EXPECT_FALSE([dialog_widget->GetNativeWindow() canBecomeMainWindow]);
835   regular_widget->CloseNow();
838 NSData* WindowContentsAsTIFF(NSWindow* window) {
839   NSView* frame_view = [[window contentView] superview];
840   EXPECT_TRUE(frame_view);
842   // Inset to mask off left and right edges which vary in HighDPI.
843   NSRect bounds = NSInsetRect([frame_view bounds], 4, 0);
845   // On 10.6, the grippy changes appearance slightly when painted the second
846   // time in a textured window. Since this test cares about the window title,
847   // cut off the bottom of the window.
848   bounds.size.height -= 40;
849   bounds.origin.y += 40;
851   NSBitmapImageRep* bitmap =
852       [frame_view bitmapImageRepForCachingDisplayInRect:bounds];
853   EXPECT_TRUE(bitmap);
855   [frame_view cacheDisplayInRect:bounds toBitmapImageRep:bitmap];
856   NSData* tiff = [bitmap TIFFRepresentation];
857   EXPECT_TRUE(tiff);
858   return tiff;
861 class CustomTitleWidgetDelegate : public WidgetDelegate {
862  public:
863   CustomTitleWidgetDelegate(Widget* widget)
864       : widget_(widget), should_show_title_(true) {}
866   void set_title(const base::string16& title) { title_ = title; }
867   void set_should_show_title(bool show) { should_show_title_ = show; }
869   // WidgetDelegate:
870   base::string16 GetWindowTitle() const override { return title_; }
871   bool ShouldShowWindowTitle() const override { return should_show_title_; }
872   Widget* GetWidget() override { return widget_; };
873   const Widget* GetWidget() const override { return widget_; };
875  private:
876   Widget* widget_;
877   base::string16 title_;
878   bool should_show_title_;
880   DISALLOW_COPY_AND_ASSIGN(CustomTitleWidgetDelegate);
883 // Test that undocumented title-hiding API we're using does the job.
884 TEST_F(NativeWidgetMacTest, DoesHideTitle) {
885   // Same as CreateTopLevelPlatformWidget but with a custom delegate.
886   Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
887   Widget* widget = new Widget;
888   params.native_widget = new NativeWidgetCapture(widget);
889   CustomTitleWidgetDelegate delegate(widget);
890   params.delegate = &delegate;
891   params.bounds = gfx::Rect(0, 0, 800, 600);
892   widget->Init(params);
893   widget->Show();
895   NSWindow* ns_window = widget->GetNativeWindow();
896   // Disable color correction so we can read unmodified values from the bitmap.
897   [ns_window setColorSpace:[NSColorSpace sRGBColorSpace]];
899   EXPECT_EQ(base::string16(), delegate.GetWindowTitle());
900   EXPECT_NSEQ(@"", [ns_window title]);
901   NSData* empty_title_data = WindowContentsAsTIFF(ns_window);
903   delegate.set_title(base::ASCIIToUTF16("This is a title"));
904   widget->UpdateWindowTitle();
905   NSData* this_title_data = WindowContentsAsTIFF(ns_window);
907   // The default window with a title should look different from the
908   // window with an empty title.
909   EXPECT_FALSE([empty_title_data isEqualToData:this_title_data]);
911   delegate.set_should_show_title(false);
912   delegate.set_title(base::ASCIIToUTF16("This is another title"));
913   widget->UpdateWindowTitle();
914   NSData* hidden_title_data = WindowContentsAsTIFF(ns_window);
916   // With our magic setting, the window with a title should look the
917   // same as the window with an empty title.
918   EXPECT_TRUE([ns_window _isTitleHidden]);
919   EXPECT_TRUE([empty_title_data isEqualToData:hidden_title_data]);
921   widget->CloseNow();
924 // Test calls to invalidate the shadow when composited frames arrive.
925 TEST_F(NativeWidgetMacTest, InvalidateShadow) {
926   NativeWidetMacTestWindow* window;
927   const gfx::Rect rect(0, 0, 100, 200);
928   Widget::InitParams init_params =
929       CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
930   init_params.bounds = rect;
931   Widget* widget = CreateWidgetWithTestWindow(init_params, &window);
933   // Simulate the initial paint.
934   BridgedNativeWidgetTestApi(window).SimulateFrameSwap(rect.size());
936   // Default is an opaque window, so shadow doesn't need to be invalidated.
937   EXPECT_EQ(0, [window invalidateShadowCount]);
938   widget->CloseNow();
940   init_params.opacity = Widget::InitParams::TRANSLUCENT_WINDOW;
941   widget = CreateWidgetWithTestWindow(init_params, &window);
942   BridgedNativeWidgetTestApi test_api(window);
944   // First paint on a translucent window needs to invalidate the shadow. Once.
945   EXPECT_EQ(0, [window invalidateShadowCount]);
946   test_api.SimulateFrameSwap(rect.size());
947   EXPECT_EQ(1, [window invalidateShadowCount]);
948   test_api.SimulateFrameSwap(rect.size());
949   EXPECT_EQ(1, [window invalidateShadowCount]);
951   // Resizing the window also needs to trigger a shadow invalidation.
952   [window setContentSize:NSMakeSize(123, 456)];
953   // A "late" frame swap at the old size should do nothing.
954   test_api.SimulateFrameSwap(rect.size());
955   EXPECT_EQ(1, [window invalidateShadowCount]);
957   test_api.SimulateFrameSwap(gfx::Size(123, 456));
958   EXPECT_EQ(2, [window invalidateShadowCount]);
959   test_api.SimulateFrameSwap(gfx::Size(123, 456));
960   EXPECT_EQ(2, [window invalidateShadowCount]);
962   widget->CloseNow();
965 }  // namespace test
966 }  // namespace views
968 @implementation TestStopAnimationWaiter
969 - (void)setWindowStateForEnd {
970   views::test::ScopedSwizzleWaiter::GetMethodAndMarkCalled()(self, _cmd);
972 @end
974 @implementation NativeWidetMacTestWindow
976 @synthesize invalidateShadowCount = invalidateShadowCount_;
978 - (void)invalidateShadow {
979   ++invalidateShadowCount_;
980   [super invalidateShadow];
983 @end