Update V8 to version 4.7.57.
[chromium-blink-merge.git] / ui / views / widget / native_widget_mac_unittest.mm
blob9cbc7f93279313471cb68ac1813243cdd6f318d6
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 NativeWidgetMacTestWindow : 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   NativeWidgetMacNSWindow* CreateNSWindow(
91       const Widget::InitParams& params) override {
92     NSUInteger style_mask = NSBorderlessWindowMask;
93     if (params.type == Widget::InitParams::TYPE_WINDOW) {
94       style_mask = NSTexturedBackgroundWindowMask | NSTitledWindowMask |
95                    NSClosableWindowMask | NSMiniaturizableWindowMask |
96                    NSResizableWindowMask;
97     }
98     return [[[NativeWidgetMacTestWindow alloc]
99         initWithContentRect:ui::kWindowSizeDeterminedLater
100                   styleMask:style_mask
101                     backing:NSBackingStoreBuffered
102                       defer:NO] autorelease];
103   }
105  private:
106   DISALLOW_COPY_AND_ASSIGN(TestWindowNativeWidgetMac);
109 // Tests for parts of NativeWidgetMac not covered by BridgedNativeWidget, which
110 // need access to Cocoa APIs.
111 class NativeWidgetMacTest : public WidgetTest {
112  public:
113   NativeWidgetMacTest() {}
115   // The content size of NSWindows made by MakeNativeParent().
116   NSRect ParentRect() const { return NSMakeRect(100, 100, 300, 200); }
118   // Make a native NSWindow to use as a parent.
119   NSWindow* MakeNativeParent() {
120     native_parent_.reset(
121         [[NSWindow alloc] initWithContentRect:ParentRect()
122                                     styleMask:NSBorderlessWindowMask
123                                       backing:NSBackingStoreBuffered
124                                         defer:NO]);
125     [native_parent_ setReleasedWhenClosed:NO];  // Owned by scoped_nsobject.
126     [native_parent_ makeKeyAndOrderFront:nil];
127     return native_parent_;
128   }
130   // Create a Widget backed by the NativeWidgetMacTestWindow NSWindow subclass.
131   Widget* CreateWidgetWithTestWindow(Widget::InitParams params,
132                                      NativeWidgetMacTestWindow** window) {
133     Widget* widget = new Widget;
134     params.native_widget = new TestWindowNativeWidgetMac(widget);
135     widget->Init(params);
136     widget->Show();
137     *window = base::mac::ObjCCastStrict<NativeWidgetMacTestWindow>(
138         widget->GetNativeWindow());
139     EXPECT_TRUE(*window);
140     return widget;
141   }
143  private:
144   base::scoped_nsobject<NSWindow> native_parent_;
146   DISALLOW_COPY_AND_ASSIGN(NativeWidgetMacTest);
149 class WidgetChangeObserver : public TestWidgetObserver {
150  public:
151   WidgetChangeObserver(Widget* widget) : TestWidgetObserver(widget) {}
153   void WaitForVisibleCounts(int gained, int lost) {
154     if (gained_visible_count_ >= gained && lost_visible_count_ >= lost)
155       return;
157     target_gained_visible_count_ = gained;
158     target_lost_visible_count_ = lost;
160     base::RunLoop run_loop;
161     run_loop_ = &run_loop;
162     base::MessageLoop::current()->task_runner()->PostDelayedTask(
163         FROM_HERE, run_loop.QuitClosure(), TestTimeouts::action_timeout());
164     run_loop.Run();
165     run_loop_ = nullptr;
166   }
168   int gained_visible_count() const { return gained_visible_count_; }
169   int lost_visible_count() const { return lost_visible_count_; }
171  private:
172   // WidgetObserver:
173   void OnWidgetVisibilityChanged(Widget* widget,
174                                  bool visible) override {
175     ++(visible ? gained_visible_count_ : lost_visible_count_);
176     if (run_loop_ && gained_visible_count_ >= target_gained_visible_count_ &&
177         lost_visible_count_ >= target_lost_visible_count_)
178       run_loop_->Quit();
179   }
181   int gained_visible_count_ = 0;
182   int lost_visible_count_ = 0;
183   int target_gained_visible_count_ = 0;
184   int target_lost_visible_count_ = 0;
185   base::RunLoop* run_loop_ = nullptr;
187   DISALLOW_COPY_AND_ASSIGN(WidgetChangeObserver);
190 // Test visibility states triggered externally.
191 TEST_F(NativeWidgetMacTest, HideAndShowExternally) {
192   Widget* widget = CreateTopLevelPlatformWidget();
193   NSWindow* ns_window = widget->GetNativeWindow();
194   WidgetChangeObserver observer(widget);
196   // Should initially be hidden.
197   EXPECT_FALSE(widget->IsVisible());
198   EXPECT_FALSE([ns_window isVisible]);
199   EXPECT_EQ(0, observer.gained_visible_count());
200   EXPECT_EQ(0, observer.lost_visible_count());
202   widget->Show();
203   EXPECT_TRUE(widget->IsVisible());
204   EXPECT_TRUE([ns_window isVisible]);
205   EXPECT_EQ(1, observer.gained_visible_count());
206   EXPECT_EQ(0, observer.lost_visible_count());
208   widget->Hide();
209   EXPECT_FALSE(widget->IsVisible());
210   EXPECT_FALSE([ns_window isVisible]);
211   EXPECT_EQ(1, observer.gained_visible_count());
212   EXPECT_EQ(1, observer.lost_visible_count());
214   widget->Show();
215   EXPECT_TRUE(widget->IsVisible());
216   EXPECT_TRUE([ns_window isVisible]);
217   EXPECT_EQ(2, observer.gained_visible_count());
218   EXPECT_EQ(1, observer.lost_visible_count());
220   // Test when hiding individual windows.
221   [ns_window orderOut:nil];
222   EXPECT_FALSE(widget->IsVisible());
223   EXPECT_FALSE([ns_window isVisible]);
224   EXPECT_EQ(2, observer.gained_visible_count());
225   EXPECT_EQ(2, observer.lost_visible_count());
227   [ns_window orderFront:nil];
228   EXPECT_TRUE(widget->IsVisible());
229   EXPECT_TRUE([ns_window isVisible]);
230   EXPECT_EQ(3, observer.gained_visible_count());
231   EXPECT_EQ(2, observer.lost_visible_count());
233   // Test when hiding the entire application. This doesn't send an orderOut:
234   // to the NSWindow.
235   [NSApp hide:nil];
236   // When the activation policy is NSApplicationActivationPolicyRegular, the
237   // calls via NSApp are asynchronous, and the run loop needs to be flushed.
238   // With NSApplicationActivationPolicyProhibited, the following
239   // WaitForVisibleCounts calls are superfluous, but don't hurt.
240   observer.WaitForVisibleCounts(3, 3);
241   EXPECT_FALSE(widget->IsVisible());
242   EXPECT_FALSE([ns_window isVisible]);
243   EXPECT_EQ(3, observer.gained_visible_count());
244   EXPECT_EQ(3, observer.lost_visible_count());
246   [NSApp unhideWithoutActivation];
247   observer.WaitForVisibleCounts(4, 3);
248   EXPECT_TRUE(widget->IsVisible());
249   EXPECT_TRUE([ns_window isVisible]);
250   EXPECT_EQ(4, observer.gained_visible_count());
251   EXPECT_EQ(3, observer.lost_visible_count());
253   // Hide again to test unhiding with an activation.
254   [NSApp hide:nil];
255   observer.WaitForVisibleCounts(4, 4);
256   EXPECT_EQ(4, observer.lost_visible_count());
257   [NSApp unhide:nil];
258   observer.WaitForVisibleCounts(5, 4);
259   EXPECT_EQ(5, observer.gained_visible_count());
261   // Hide again to test makeKeyAndOrderFront:.
262   [ns_window orderOut:nil];
263   EXPECT_FALSE(widget->IsVisible());
264   EXPECT_FALSE([ns_window isVisible]);
265   EXPECT_EQ(5, observer.gained_visible_count());
266   EXPECT_EQ(5, observer.lost_visible_count());
268   [ns_window makeKeyAndOrderFront:nil];
269   EXPECT_TRUE(widget->IsVisible());
270   EXPECT_TRUE([ns_window isVisible]);
271   EXPECT_EQ(6, observer.gained_visible_count());
272   EXPECT_EQ(5, observer.lost_visible_count());
274   // No change when closing.
275   widget->CloseNow();
276   EXPECT_EQ(5, observer.lost_visible_count());
277   EXPECT_EQ(6, observer.gained_visible_count());
280 // A view that counts calls to OnPaint().
281 class PaintCountView : public View {
282  public:
283   PaintCountView() { SetBounds(0, 0, 100, 100); }
285   // View:
286   void OnPaint(gfx::Canvas* canvas) override {
287     EXPECT_TRUE(GetWidget()->IsVisible());
288     ++paint_count_;
289     if (run_loop_ && paint_count_ == target_paint_count_)
290       run_loop_->Quit();
291   }
293   void WaitForPaintCount(int target) {
294     if (paint_count_ == target)
295       return;
297     target_paint_count_ = target;
298     base::RunLoop run_loop;
299     run_loop_ = &run_loop;
300     run_loop.Run();
301     run_loop_ = nullptr;
302   }
304   int paint_count() { return paint_count_; }
306  private:
307   int paint_count_ = 0;
308   int target_paint_count_ = 0;
309   base::RunLoop* run_loop_ = nullptr;
311   DISALLOW_COPY_AND_ASSIGN(PaintCountView);
314 // Test minimized states triggered externally, implied visibility and restored
315 // bounds whilst minimized.
316 TEST_F(NativeWidgetMacTest, MiniaturizeExternally) {
317   Widget* widget = new Widget;
318   Widget::InitParams init_params(Widget::InitParams::TYPE_WINDOW);
319   widget->Init(init_params);
321   PaintCountView* view = new PaintCountView();
322   widget->GetContentsView()->AddChildView(view);
323   NSWindow* ns_window = widget->GetNativeWindow();
324   WidgetChangeObserver observer(widget);
326   widget->SetBounds(gfx::Rect(100, 100, 300, 300));
328   EXPECT_TRUE(view->IsDrawn());
329   EXPECT_EQ(0, view->paint_count());
330   widget->Show();
331   base::RunLoop().RunUntilIdle();
333   EXPECT_EQ(1, observer.gained_visible_count());
334   EXPECT_EQ(0, observer.lost_visible_count());
335   const gfx::Rect restored_bounds = widget->GetRestoredBounds();
336   EXPECT_FALSE(restored_bounds.IsEmpty());
337   EXPECT_FALSE(widget->IsMinimized());
338   EXPECT_TRUE(widget->IsVisible());
340   // Showing should paint.
341   view->WaitForPaintCount(1);
343   // First try performMiniaturize:, which requires a minimize button. Note that
344   // Cocoa just blocks the UI thread during the animation, so no need to do
345   // anything fancy to wait for it finish.
346   [ns_window performMiniaturize:nil];
347   base::RunLoop().RunUntilIdle();
349   EXPECT_TRUE(widget->IsMinimized());
350   EXPECT_FALSE(widget->IsVisible());  // Minimizing also makes things invisible.
351   EXPECT_EQ(1, observer.gained_visible_count());
352   EXPECT_EQ(1, observer.lost_visible_count());
353   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
355   // No repaint when minimizing. But note that this is partly due to not calling
356   // [NSView setNeedsDisplay:YES] on the content view. The superview, which is
357   // an NSThemeFrame, would repaint |view| if we had, because the miniaturize
358   // button is highlighted for performMiniaturize.
359   EXPECT_EQ(1, view->paint_count());
361   [ns_window deminiaturize:nil];
362   base::RunLoop().RunUntilIdle();
364   EXPECT_FALSE(widget->IsMinimized());
365   EXPECT_TRUE(widget->IsVisible());
366   EXPECT_EQ(2, observer.gained_visible_count());
367   EXPECT_EQ(1, observer.lost_visible_count());
368   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
370   view->WaitForPaintCount(2);  // A single paint when deminiaturizing.
371   EXPECT_FALSE([ns_window isMiniaturized]);
373   widget->Minimize();
374   base::RunLoop().RunUntilIdle();
376   EXPECT_TRUE(widget->IsMinimized());
377   EXPECT_TRUE([ns_window isMiniaturized]);
378   EXPECT_EQ(2, observer.gained_visible_count());
379   EXPECT_EQ(2, observer.lost_visible_count());
380   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
381   EXPECT_EQ(2, view->paint_count());  // No paint when miniaturizing.
383   widget->Restore();  // If miniaturized, should deminiaturize.
384   base::RunLoop().RunUntilIdle();
386   EXPECT_FALSE(widget->IsMinimized());
387   EXPECT_FALSE([ns_window isMiniaturized]);
388   EXPECT_EQ(3, observer.gained_visible_count());
389   EXPECT_EQ(2, observer.lost_visible_count());
390   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
391   view->WaitForPaintCount(3);
393   widget->Restore();  // If not miniaturized, does nothing.
394   base::RunLoop().RunUntilIdle();
396   EXPECT_FALSE(widget->IsMinimized());
397   EXPECT_FALSE([ns_window isMiniaturized]);
398   EXPECT_EQ(3, observer.gained_visible_count());
399   EXPECT_EQ(2, observer.lost_visible_count());
400   EXPECT_EQ(restored_bounds, widget->GetRestoredBounds());
401   EXPECT_EQ(3, view->paint_count());
403   widget->CloseNow();
405   // Create a widget without a minimize button.
406   widget = CreateTopLevelFramelessPlatformWidget();
407   ns_window = widget->GetNativeWindow();
408   widget->SetBounds(gfx::Rect(100, 100, 300, 300));
409   widget->Show();
410   EXPECT_FALSE(widget->IsMinimized());
412   // This should fail, since performMiniaturize: requires a minimize button.
413   [ns_window performMiniaturize:nil];
414   EXPECT_FALSE(widget->IsMinimized());
416   // But this should work.
417   widget->Minimize();
418   EXPECT_TRUE(widget->IsMinimized());
420   // Test closing while minimized.
421   widget->CloseNow();
424 // Simple view for the SetCursor test that overrides View::GetCursor().
425 class CursorView : public View {
426  public:
427   CursorView(int x, NSCursor* cursor) : cursor_(cursor) {
428     SetBounds(x, 0, 100, 300);
429   }
431   // View:
432   gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override {
433     return cursor_;
434   }
436  private:
437   NSCursor* cursor_;
439   DISALLOW_COPY_AND_ASSIGN(CursorView);
442 // Test for Widget::SetCursor(). There is no Widget::GetCursor(), so this uses
443 // -[NSCursor currentCursor] to validate expectations. Note that currentCursor
444 // is just "the top cursor on the application's cursor stack.", which is why it
445 // is safe to use this in a non-interactive UI test with the EventGenerator.
446 TEST_F(NativeWidgetMacTest, SetCursor) {
447   NSCursor* arrow = [NSCursor arrowCursor];
448   NSCursor* hand = GetNativeHandCursor();
449   NSCursor* ibeam = GetNativeIBeamCursor();
451   Widget* widget = CreateTopLevelPlatformWidget();
452   widget->SetBounds(gfx::Rect(0, 0, 300, 300));
453   widget->GetContentsView()->AddChildView(new CursorView(0, hand));
454   widget->GetContentsView()->AddChildView(new CursorView(100, ibeam));
455   widget->Show();
457   // Events used to simulate tracking rectangle updates. These are not passed to
458   // toolkit-views, so it only matters whether they are inside or outside the
459   // content area.
460   NSEvent* event_in_content = cocoa_test_event_utils::MouseEventAtPoint(
461       NSMakePoint(100, 100), NSMouseMoved, 0);
462   NSEvent* event_out_of_content = cocoa_test_event_utils::MouseEventAtPoint(
463       NSMakePoint(-50, -50), NSMouseMoved, 0);
465   EXPECT_NE(arrow, hand);
466   EXPECT_NE(arrow, ibeam);
468   // At the start of the test, the cursor stack should be empty.
469   EXPECT_FALSE([NSCursor currentCursor]);
471   // Use an event generator to ask views code to set the cursor. However, note
472   // that this does not cause Cocoa to generate tracking rectangle updates.
473   ui::test::EventGenerator event_generator(GetContext(),
474                                            widget->GetNativeWindow());
476   // Move the mouse over the first view, then simulate a tracking rectangle
477   // update.
478   event_generator.MoveMouseTo(gfx::Point(50, 50));
479   [widget->GetNativeWindow() cursorUpdate:event_in_content];
480   EXPECT_EQ(hand, [NSCursor currentCursor]);
482   // A tracking rectangle update not in the content area should forward to
483   // the native NSWindow implementation, which sets the arrow cursor.
484   [widget->GetNativeWindow() cursorUpdate:event_out_of_content];
485   EXPECT_EQ(arrow, [NSCursor currentCursor]);
487   // Now move to the second view.
488   event_generator.MoveMouseTo(gfx::Point(150, 50));
489   [widget->GetNativeWindow() cursorUpdate:event_in_content];
490   EXPECT_EQ(ibeam, [NSCursor currentCursor]);
492   // Moving to the third view (but remaining in the content area) should also
493   // forward to the native NSWindow implementation.
494   event_generator.MoveMouseTo(gfx::Point(250, 50));
495   [widget->GetNativeWindow() cursorUpdate:event_in_content];
496   EXPECT_EQ(arrow, [NSCursor currentCursor]);
498   widget->CloseNow();
501 // Tests that an accessibility request from the system makes its way through to
502 // a views::Label filling the window.
503 TEST_F(NativeWidgetMacTest, AccessibilityIntegration) {
504   Widget* widget = CreateTopLevelPlatformWidget();
505   gfx::Rect screen_rect(50, 50, 100, 100);
506   widget->SetBounds(screen_rect);
508   const base::string16 test_string = base::ASCIIToUTF16("Green");
509   views::Label* label = new views::Label(test_string);
510   label->SetBounds(0, 0, 100, 100);
511   widget->GetContentsView()->AddChildView(label);
512   widget->Show();
514   // Accessibility hit tests come in Cocoa screen coordinates.
515   NSRect nsrect = gfx::ScreenRectToNSRect(screen_rect);
516   NSPoint midpoint = NSMakePoint(NSMidX(nsrect), NSMidY(nsrect));
518   id hit = [widget->GetNativeWindow() accessibilityHitTest:midpoint];
519   id title = [hit accessibilityAttributeValue:NSAccessibilityTitleAttribute];
520   EXPECT_NSEQ(title, @"Green");
522   widget->CloseNow();
525 // Tests creating a views::Widget parented off a native NSWindow.
526 TEST_F(NativeWidgetMacTest, NonWidgetParent) {
527   NSWindow* native_parent = MakeNativeParent();
529   base::scoped_nsobject<NSView> anchor_view(
530       [[NSView alloc] initWithFrame:[[native_parent contentView] bounds]]);
531   [[native_parent contentView] addSubview:anchor_view];
533   // Note: Don't use WidgetTest::CreateChildPlatformWidget because that makes
534   // windows of TYPE_CONTROL which are automatically made visible. But still
535   // mark it as a child to test window positioning.
536   Widget* child = new Widget;
537   Widget::InitParams init_params;
538   init_params.parent = anchor_view;
539   init_params.child = true;
540   child->Init(init_params);
542   TestWidgetObserver child_observer(child);
544   // GetTopLevelNativeWidget() only goes as far as there exists a Widget (i.e.
545   // must stop at |child|.
546   internal::NativeWidgetPrivate* top_level_widget =
547       internal::NativeWidgetPrivate::GetTopLevelNativeWidget(
548           child->GetNativeView());
549   EXPECT_EQ(child, top_level_widget->GetWidget());
551   // To verify the parent, we need to use NativeWidgetMac APIs.
552   BridgedNativeWidget* bridged_native_widget =
553       NativeWidgetMac::GetBridgeForNativeWindow(child->GetNativeWindow());
554   EXPECT_EQ(native_parent, bridged_native_widget->parent()->GetNSWindow());
556   child->SetBounds(gfx::Rect(50, 50, 200, 100));
557   EXPECT_FALSE(child->IsVisible());
558   EXPECT_EQ(0u, [[native_parent childWindows] count]);
560   child->Show();
561   EXPECT_TRUE(child->IsVisible());
562   EXPECT_EQ(1u, [[native_parent childWindows] count]);
563   EXPECT_EQ(child->GetNativeWindow(),
564             [[native_parent childWindows] objectAtIndex:0]);
565   EXPECT_EQ(native_parent, [child->GetNativeWindow() parentWindow]);
567   // Child should be positioned on screen relative to the parent, but note we
568   // positioned the parent in Cocoa coordinates, so we need to convert.
569   gfx::Point parent_origin = gfx::ScreenRectFromNSRect(ParentRect()).origin();
570   EXPECT_EQ(gfx::Rect(150, parent_origin.y() + 50, 200, 100),
571             child->GetWindowBoundsInScreen());
573   // Removing the anchor_view from its view hierarchy is permitted. This should
574   // not break the relationship between the two windows.
575   [anchor_view removeFromSuperview];
576   anchor_view.reset();
577   EXPECT_EQ(native_parent, bridged_native_widget->parent()->GetNSWindow());
579   // Closing the parent should close and destroy the child.
580   EXPECT_FALSE(child_observer.widget_closed());
581   [native_parent close];
582   EXPECT_TRUE(child_observer.widget_closed());
584   EXPECT_EQ(0u, [[native_parent childWindows] count]);
587 // Use Native APIs to query the tooltip text that would be shown once the
588 // tooltip delay had elapsed.
589 base::string16 TooltipTextForWidget(Widget* widget) {
590   // For Mac, the actual location doesn't matter, since there is only one native
591   // view and it fills the window. This just assumes the window is at least big
592   // big enough for a constant coordinate to be within it.
593   NSPoint point = NSMakePoint(30, 30);
594   NSView* view = [widget->GetNativeView() hitTest:point];
595   NSString* text =
596       [view view:view stringForToolTip:0 point:point userData:nullptr];
597   return base::SysNSStringToUTF16(text);
600 // Tests tooltips. The test doesn't wait for tooltips to appear. That is, the
601 // test assumes Cocoa calls stringForToolTip: at appropriate times and that,
602 // when a tooltip is already visible, changing it causes an update. These were
603 // tested manually by inserting a base::RunLoop.Run().
604 TEST_F(NativeWidgetMacTest, Tooltips) {
605   Widget* widget = CreateTopLevelPlatformWidget();
606   gfx::Rect screen_rect(50, 50, 100, 100);
607   widget->SetBounds(screen_rect);
609   const base::string16 tooltip_back = base::ASCIIToUTF16("Back");
610   const base::string16 tooltip_front = base::ASCIIToUTF16("Front");
611   const base::string16 long_tooltip(2000, 'W');
613   // Create a nested layout to test corner cases.
614   LabelButton* back = new LabelButton(nullptr, base::string16());
615   back->SetBounds(10, 10, 80, 80);
616   widget->GetContentsView()->AddChildView(back);
617   widget->Show();
619   ui::test::EventGenerator event_generator(GetContext(),
620                                            widget->GetNativeWindow());
622   // Initially, there should be no tooltip.
623   event_generator.MoveMouseTo(gfx::Point(50, 50));
624   EXPECT_TRUE(TooltipTextForWidget(widget).empty());
626   // Create a new button for the "front", and set the tooltip, but don't add it
627   // to the view hierarchy yet.
628   LabelButton* front = new LabelButton(nullptr, base::string16());
629   front->SetBounds(20, 20, 40, 40);
630   front->SetTooltipText(tooltip_front);
632   // Changing the tooltip text shouldn't require an additional mousemove to take
633   // effect.
634   EXPECT_TRUE(TooltipTextForWidget(widget).empty());
635   back->SetTooltipText(tooltip_back);
636   EXPECT_EQ(tooltip_back, TooltipTextForWidget(widget));
638   // Adding a new view under the mouse should also take immediate effect.
639   back->AddChildView(front);
640   EXPECT_EQ(tooltip_front, TooltipTextForWidget(widget));
642   // A long tooltip will be wrapped by Cocoa, but the full string should appear.
643   // Note that render widget hosts clip at 1024 to prevent DOS, but in toolkit-
644   // views the UI is more trusted.
645   front->SetTooltipText(long_tooltip);
646   EXPECT_EQ(long_tooltip, TooltipTextForWidget(widget));
648   // Move the mouse to a different view - tooltip should change.
649   event_generator.MoveMouseTo(gfx::Point(15, 15));
650   EXPECT_EQ(tooltip_back, TooltipTextForWidget(widget));
652   // Move the mouse off of any view, tooltip should clear.
653   event_generator.MoveMouseTo(gfx::Point(5, 5));
654   EXPECT_TRUE(TooltipTextForWidget(widget).empty());
656   widget->CloseNow();
659 namespace {
661 // Delegate to make Widgets of MODAL_TYPE_CHILD.
662 class ChildModalDialogDelegate : public DialogDelegateView {
663  public:
664   ChildModalDialogDelegate() {}
666   // WidgetDelegate:
667   ui::ModalType GetModalType() const override { return ui::MODAL_TYPE_CHILD; }
669  private:
670   DISALLOW_COPY_AND_ASSIGN(ChildModalDialogDelegate);
673 // While in scope, waits for a call to a swizzled objective C method, then quits
674 // a nested run loop.
675 class ScopedSwizzleWaiter {
676  public:
677   explicit ScopedSwizzleWaiter(Class target)
678       : swizzler_(target,
679                   [TestStopAnimationWaiter class],
680                   @selector(setWindowStateForEnd)) {
681     DCHECK(!instance_);
682     instance_ = this;
683   }
685   ~ScopedSwizzleWaiter() { instance_ = nullptr; }
687   static IMP GetMethodAndMarkCalled() {
688     return instance_->GetMethodInternal();
689   }
691   void WaitForMethod() {
692     if (method_called_)
693       return;
695     base::RunLoop run_loop;
696     base::MessageLoop::current()->task_runner()->PostDelayedTask(
697         FROM_HERE, run_loop.QuitClosure(), TestTimeouts::action_timeout());
698     run_loop_ = &run_loop;
699     run_loop.Run();
700     run_loop_ = nullptr;
701   }
703   bool method_called() const { return method_called_; }
705  private:
706   IMP GetMethodInternal() {
707     DCHECK(!method_called_);
708     method_called_ = true;
709     if (run_loop_)
710       run_loop_->Quit();
711     return swizzler_.GetOriginalImplementation();
712   }
714   static ScopedSwizzleWaiter* instance_;
716   base::mac::ScopedObjCClassSwizzler swizzler_;
717   base::RunLoop* run_loop_ = nullptr;
718   bool method_called_ = false;
720   DISALLOW_COPY_AND_ASSIGN(ScopedSwizzleWaiter);
723 ScopedSwizzleWaiter* ScopedSwizzleWaiter::instance_ = nullptr;
725 // Shows a modal widget and waits for the show animation to complete. Waiting is
726 // not compulsory (calling Close() while animating the show will cancel the show
727 // animation). However, testing with overlapping swizzlers is tricky.
728 Widget* ShowChildModalWidgetAndWait(NSWindow* native_parent) {
729   Widget* modal_dialog_widget = views::DialogDelegate::CreateDialogWidget(
730       new ChildModalDialogDelegate, nullptr, [native_parent contentView]);
732   modal_dialog_widget->SetBounds(gfx::Rect(50, 50, 200, 150));
733   EXPECT_FALSE(modal_dialog_widget->IsVisible());
734   ScopedSwizzleWaiter show_waiter([ConstrainedWindowAnimationShow class]);
736   modal_dialog_widget->Show();
737   // Visible immediately (although it animates from transparent).
738   EXPECT_TRUE(modal_dialog_widget->IsVisible());
740   // Run the animation.
741   show_waiter.WaitForMethod();
742   EXPECT_TRUE(modal_dialog_widget->IsVisible());
743   EXPECT_TRUE(show_waiter.method_called());
744   return modal_dialog_widget;
747 }  // namespace
749 // Tests object lifetime for the show/hide animations used for child-modal
750 // windows. Parents the dialog off a native parent window (not a views::Widget).
751 TEST_F(NativeWidgetMacTest, NativeWindowChildModalShowHide) {
752   NSWindow* native_parent = MakeNativeParent();
753   {
754     Widget* modal_dialog_widget = ShowChildModalWidgetAndWait(native_parent);
755     TestWidgetObserver widget_observer(modal_dialog_widget);
757     ScopedSwizzleWaiter hide_waiter([ConstrainedWindowAnimationHide class]);
758     EXPECT_TRUE(modal_dialog_widget->IsVisible());
759     EXPECT_FALSE(widget_observer.widget_closed());
761     // Widget::Close() is always asynchronous, so we can check that the widget
762     // is initially visible, but then it's destroyed.
763     modal_dialog_widget->Close();
764     EXPECT_TRUE(modal_dialog_widget->IsVisible());
765     EXPECT_FALSE(hide_waiter.method_called());
766     EXPECT_FALSE(widget_observer.widget_closed());
768     // Wait for a hide to finish.
769     hide_waiter.WaitForMethod();
770     EXPECT_TRUE(hide_waiter.method_called());
772     // The animation finishing should also mean it has closed the window.
773     EXPECT_TRUE(widget_observer.widget_closed());
774   }
776   {
777     // Make a new dialog to test another lifetime flow.
778     Widget* modal_dialog_widget = ShowChildModalWidgetAndWait(native_parent);
779     TestWidgetObserver widget_observer(modal_dialog_widget);
781     // Start an asynchronous close as above.
782     ScopedSwizzleWaiter hide_waiter([ConstrainedWindowAnimationHide class]);
783     modal_dialog_widget->Close();
784     EXPECT_FALSE(widget_observer.widget_closed());
785     EXPECT_FALSE(hide_waiter.method_called());
787     // Now close the _parent_ window to force a synchronous close of the child.
788     [native_parent close];
790     // Widget is destroyed immediately. No longer paints, but the animation is
791     // still running.
792     EXPECT_TRUE(widget_observer.widget_closed());
793     EXPECT_FALSE(hide_waiter.method_called());
795     // Wait for the hide again. It will call close on its retained copy of the
796     // child NSWindow, but that's fine since all the C++ objects are detached.
797     hide_waiter.WaitForMethod();
798     EXPECT_TRUE(hide_waiter.method_called());
799   }
802 // Test calls to Widget::ReparentNativeView() that result in a no-op on Mac.
803 // Tests with both native and non-native parents.
804 TEST_F(NativeWidgetMacTest, NoopReparentNativeView) {
805   NSWindow* parent = MakeNativeParent();
806   Widget* dialog = views::DialogDelegate::CreateDialogWidget(
807       new DialogDelegateView, nullptr, [parent contentView]);
808   BridgedNativeWidget* bridge =
809       NativeWidgetMac::GetBridgeForNativeWindow(dialog->GetNativeWindow());
811   EXPECT_EQ(bridge->parent()->GetNSWindow(), parent);
812   Widget::ReparentNativeView(dialog->GetNativeView(), [parent contentView]);
813   EXPECT_EQ(bridge->parent()->GetNSWindow(), parent);
815   [parent close];
817   Widget* parent_widget = CreateNativeDesktopWidget();
818   parent = parent_widget->GetNativeWindow();
819   dialog = views::DialogDelegate::CreateDialogWidget(
820       new DialogDelegateView, nullptr, [parent contentView]);
821   bridge = NativeWidgetMac::GetBridgeForNativeWindow(dialog->GetNativeWindow());
823   EXPECT_EQ(bridge->parent()->GetNSWindow(), parent);
824   Widget::ReparentNativeView(dialog->GetNativeView(), [parent contentView]);
825   EXPECT_EQ(bridge->parent()->GetNSWindow(), parent);
827   parent_widget->CloseNow();
830 // Tests Cocoa properties that should be given to particular widget types.
831 TEST_F(NativeWidgetMacTest, NativeProperties) {
832   // Create a regular widget (TYPE_WINDOW).
833   Widget* regular_widget = CreateNativeDesktopWidget();
834   EXPECT_TRUE([regular_widget->GetNativeWindow() canBecomeKeyWindow]);
835   EXPECT_TRUE([regular_widget->GetNativeWindow() canBecomeMainWindow]);
837   // Disabling activation should prevent key and main status.
838   regular_widget->widget_delegate()->set_can_activate(false);
839   EXPECT_FALSE([regular_widget->GetNativeWindow() canBecomeKeyWindow]);
840   EXPECT_FALSE([regular_widget->GetNativeWindow() canBecomeMainWindow]);
842   // Create a dialog widget (also TYPE_WINDOW), but with a DialogDelegate.
843   Widget* dialog_widget = views::DialogDelegate::CreateDialogWidget(
844       new ChildModalDialogDelegate, nullptr, regular_widget->GetNativeView());
845   EXPECT_TRUE([dialog_widget->GetNativeWindow() canBecomeKeyWindow]);
846   // Dialogs shouldn't take main status away from their parent.
847   EXPECT_FALSE([dialog_widget->GetNativeWindow() canBecomeMainWindow]);
849   regular_widget->CloseNow();
852 NSData* WindowContentsAsTIFF(NSWindow* window) {
853   NSView* frame_view = [[window contentView] superview];
854   EXPECT_TRUE(frame_view);
856   // Inset to mask off left and right edges which vary in HighDPI.
857   NSRect bounds = NSInsetRect([frame_view bounds], 4, 0);
859   // On 10.6, the grippy changes appearance slightly when painted the second
860   // time in a textured window. Since this test cares about the window title,
861   // cut off the bottom of the window.
862   bounds.size.height -= 40;
863   bounds.origin.y += 40;
865   NSBitmapImageRep* bitmap =
866       [frame_view bitmapImageRepForCachingDisplayInRect:bounds];
867   EXPECT_TRUE(bitmap);
869   [frame_view cacheDisplayInRect:bounds toBitmapImageRep:bitmap];
870   NSData* tiff = [bitmap TIFFRepresentation];
871   EXPECT_TRUE(tiff);
872   return tiff;
875 class CustomTitleWidgetDelegate : public WidgetDelegate {
876  public:
877   CustomTitleWidgetDelegate(Widget* widget)
878       : widget_(widget), should_show_title_(true) {}
880   void set_title(const base::string16& title) { title_ = title; }
881   void set_should_show_title(bool show) { should_show_title_ = show; }
883   // WidgetDelegate:
884   base::string16 GetWindowTitle() const override { return title_; }
885   bool ShouldShowWindowTitle() const override { return should_show_title_; }
886   Widget* GetWidget() override { return widget_; };
887   const Widget* GetWidget() const override { return widget_; };
889  private:
890   Widget* widget_;
891   base::string16 title_;
892   bool should_show_title_;
894   DISALLOW_COPY_AND_ASSIGN(CustomTitleWidgetDelegate);
897 // Test that undocumented title-hiding API we're using does the job.
898 TEST_F(NativeWidgetMacTest, DoesHideTitle) {
899   // Same as CreateTopLevelPlatformWidget but with a custom delegate.
900   Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
901   Widget* widget = new Widget;
902   params.native_widget = new NativeWidgetCapture(widget);
903   CustomTitleWidgetDelegate delegate(widget);
904   params.delegate = &delegate;
905   params.bounds = gfx::Rect(0, 0, 800, 600);
906   widget->Init(params);
907   widget->Show();
909   NSWindow* ns_window = widget->GetNativeWindow();
910   // Disable color correction so we can read unmodified values from the bitmap.
911   [ns_window setColorSpace:[NSColorSpace sRGBColorSpace]];
913   EXPECT_EQ(base::string16(), delegate.GetWindowTitle());
914   EXPECT_NSEQ(@"", [ns_window title]);
915   NSData* empty_title_data = WindowContentsAsTIFF(ns_window);
917   delegate.set_title(base::ASCIIToUTF16("This is a title"));
918   widget->UpdateWindowTitle();
919   NSData* this_title_data = WindowContentsAsTIFF(ns_window);
921   // The default window with a title should look different from the
922   // window with an empty title.
923   EXPECT_FALSE([empty_title_data isEqualToData:this_title_data]);
925   delegate.set_should_show_title(false);
926   delegate.set_title(base::ASCIIToUTF16("This is another title"));
927   widget->UpdateWindowTitle();
928   NSData* hidden_title_data = WindowContentsAsTIFF(ns_window);
930   // With our magic setting, the window with a title should look the
931   // same as the window with an empty title.
932   EXPECT_TRUE([ns_window _isTitleHidden]);
933   EXPECT_TRUE([empty_title_data isEqualToData:hidden_title_data]);
935   widget->CloseNow();
938 // Test calls to invalidate the shadow when composited frames arrive.
939 TEST_F(NativeWidgetMacTest, InvalidateShadow) {
940   NativeWidgetMacTestWindow* window;
941   const gfx::Rect rect(0, 0, 100, 200);
942   Widget::InitParams init_params =
943       CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
944   init_params.bounds = rect;
945   Widget* widget = CreateWidgetWithTestWindow(init_params, &window);
947   // Simulate the initial paint.
948   BridgedNativeWidgetTestApi(window).SimulateFrameSwap(rect.size());
950   // Default is an opaque window, so shadow doesn't need to be invalidated.
951   EXPECT_EQ(0, [window invalidateShadowCount]);
952   widget->CloseNow();
954   init_params.opacity = Widget::InitParams::TRANSLUCENT_WINDOW;
955   widget = CreateWidgetWithTestWindow(init_params, &window);
956   BridgedNativeWidgetTestApi test_api(window);
958   // First paint on a translucent window needs to invalidate the shadow. Once.
959   EXPECT_EQ(0, [window invalidateShadowCount]);
960   test_api.SimulateFrameSwap(rect.size());
961   EXPECT_EQ(1, [window invalidateShadowCount]);
962   test_api.SimulateFrameSwap(rect.size());
963   EXPECT_EQ(1, [window invalidateShadowCount]);
965   // Resizing the window also needs to trigger a shadow invalidation.
966   [window setContentSize:NSMakeSize(123, 456)];
967   // A "late" frame swap at the old size should do nothing.
968   test_api.SimulateFrameSwap(rect.size());
969   EXPECT_EQ(1, [window invalidateShadowCount]);
971   test_api.SimulateFrameSwap(gfx::Size(123, 456));
972   EXPECT_EQ(2, [window invalidateShadowCount]);
973   test_api.SimulateFrameSwap(gfx::Size(123, 456));
974   EXPECT_EQ(2, [window invalidateShadowCount]);
976   widget->CloseNow();
979 // Test the expected result of GetWorkAreaBoundsInScreen().
980 TEST_F(NativeWidgetMacTest, GetWorkAreaBoundsInScreen) {
981   Widget widget;
982   Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
983   params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
985   // This is relative to the top-left of the primary screen, so unless the bot's
986   // display is smaller than 400x300, the window will be wholly contained there.
987   params.bounds = gfx::Rect(100, 100, 300, 200);
988   widget.Init(params);
989   widget.Show();
990   NSRect expected = [[[NSScreen screens] objectAtIndex:0] visibleFrame];
991   NSRect actual = gfx::ScreenRectToNSRect(widget.GetWorkAreaBoundsInScreen());
992   EXPECT_FALSE(NSIsEmptyRect(actual));
993   EXPECT_NSEQ(expected, actual);
995   [widget.GetNativeWindow() close];
996   actual = gfx::ScreenRectToNSRect(widget.GetWorkAreaBoundsInScreen());
997   EXPECT_TRUE(NSIsEmptyRect(actual));
1000 }  // namespace test
1001 }  // namespace views
1003 @implementation TestStopAnimationWaiter
1004 - (void)setWindowStateForEnd {
1005   views::test::ScopedSwizzleWaiter::GetMethodAndMarkCalled()(self, _cmd);
1007 @end
1009 @implementation NativeWidgetMacTestWindow
1011 @synthesize invalidateShadowCount = invalidateShadowCount_;
1013 - (void)invalidateShadow {
1014   ++invalidateShadowCount_;
1015   [super invalidateShadow];
1018 @end