Add include.
[chromium-blink-merge.git] / ui / views / cocoa / bridged_native_widget_unittest.mm
blobeb44b9f758b97f2f0c93dcc55279c8952beeb9cc
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "ui/views/cocoa/bridged_native_widget.h"
7 #import <Cocoa/Cocoa.h>
9 #import "base/mac/foundation_util.h"
10 #import "base/mac/mac_util.h"
11 #import "base/mac/sdk_forward_declarations.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/run_loop.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/strings/utf_string_conversions.h"
17 #import "testing/gtest_mac.h"
18 #import "ui/gfx/test/ui_cocoa_test_helper.h"
19 #import "ui/views/cocoa/bridged_content_view.h"
20 #include "ui/views/controls/textfield/textfield.h"
21 #include "ui/views/ime/input_method.h"
22 #include "ui/views/view.h"
23 #include "ui/views/widget/native_widget_mac.h"
24 #include "ui/views/widget/root_view.h"
25 #include "ui/views/widget/widget.h"
26 #include "ui/views/widget/widget_observer.h"
28 using base::ASCIIToUTF16;
29 using base::SysNSStringToUTF8;
30 using base::SysNSStringToUTF16;
31 using base::SysUTF8ToNSString;
33 #define EXPECT_EQ_RANGE(a, b)        \
34   EXPECT_EQ(a.location, b.location); \
35   EXPECT_EQ(a.length, b.length);
37 namespace {
39 // Empty range shortcut for readibility.
40 NSRange EmptyRange() {
41   return NSMakeRange(NSNotFound, 0);
44 }  // namespace
46 @interface NativeWidgetMacNotificationWaiter : NSObject {
47  @private
48   scoped_ptr<base::RunLoop> runLoop_;
49   base::scoped_nsobject<NSWindow> window_;
50   int enterCount_;
51   int exitCount_;
52   int targetEnterCount_;
53   int targetExitCount_;
56 @property(readonly, nonatomic) int enterCount;
57 @property(readonly, nonatomic) int exitCount;
59 // Initialize for the given window and start tracking notifications.
60 - (id)initWithWindow:(NSWindow*)window;
62 // Keep spinning a run loop until the enter and exit counts match.
63 - (void)waitForEnterCount:(int)enterCount exitCount:(int)exitCount;
65 // private:
66 // Exit the RunLoop if there is one and the counts being tracked match.
67 - (void)maybeQuitForChangedArg:(int*)changedArg;
69 - (void)onEnter:(NSNotification*)notification;
70 - (void)onExit:(NSNotification*)notification;
72 @end
74 @implementation NativeWidgetMacNotificationWaiter
76 @synthesize enterCount = enterCount_;
77 @synthesize exitCount = exitCount_;
79 - (id)initWithWindow:(NSWindow*)window {
80   if ((self = [super init])) {
81     window_.reset([window retain]);
82     NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
83     [defaultCenter addObserver:self
84                       selector:@selector(onEnter:)
85                           name:NSWindowDidEnterFullScreenNotification
86                         object:window];
87     [defaultCenter addObserver:self
88                       selector:@selector(onExit:)
89                           name:NSWindowDidExitFullScreenNotification
90                         object:window];
91   }
92   return self;
95 - (void)dealloc {
96   DCHECK(!runLoop_);
97   [[NSNotificationCenter defaultCenter] removeObserver:self];
98   [super dealloc];
101 - (void)waitForEnterCount:(int)enterCount exitCount:(int)exitCount {
102   if (enterCount_ >= enterCount && exitCount_ >= exitCount)
103     return;
105   targetEnterCount_ = enterCount;
106   targetExitCount_ = exitCount;
107   runLoop_.reset(new base::RunLoop);
108   runLoop_->Run();
109   runLoop_.reset();
112 - (void)maybeQuitForChangedArg:(int*)changedArg {
113   ++*changedArg;
114   if (!runLoop_)
115     return;
117   if (enterCount_ >= targetEnterCount_ && exitCount_ >= targetExitCount_)
118     runLoop_->Quit();
121 - (void)onEnter:(NSNotification*)notification {
122   [self maybeQuitForChangedArg:&enterCount_];
125 - (void)onExit:(NSNotification*)notification {
126   [self maybeQuitForChangedArg:&exitCount_];
129 @end
131 // Class to override -[NSWindow toggleFullScreen:] to a no-op. This simulates
132 // NSWindow's behavior when attempting to toggle fullscreen state again, when
133 // the last attempt failed but Cocoa has not yet sent
134 // windowDidFailToEnterFullScreen:.
135 @interface BridgedNativeWidgetTestFullScreenWindow : NSWindow {
136  @private
137   int ignoredToggleFullScreenCount_;
139 @property(readonly, nonatomic) int ignoredToggleFullScreenCount;
140 @end
142 @implementation BridgedNativeWidgetTestFullScreenWindow
144 @synthesize ignoredToggleFullScreenCount = ignoredToggleFullScreenCount_;
146 - (void)toggleFullScreen:(id)sender {
147   ++ignoredToggleFullScreenCount_;
150 @end
152 namespace views {
153 namespace test {
155 // Provides the |parent| argument to construct a BridgedNativeWidget.
156 class MockNativeWidgetMac : public NativeWidgetMac {
157  public:
158   MockNativeWidgetMac(Widget* delegate) : NativeWidgetMac(delegate) {}
160   // Expose a reference, so that it can be reset() independently.
161   scoped_ptr<BridgedNativeWidget>& bridge() {
162     return bridge_;
163   }
165   // internal::NativeWidgetPrivate:
166   virtual void InitNativeWidget(const Widget::InitParams& params) override {
167     ownership_ = params.ownership;
169     // Usually the bridge gets initialized here. It is skipped to run extra
170     // checks in tests, and so that a second window isn't created.
171     delegate()->OnNativeWidgetCreated(true);
172   }
174   virtual void ReorderNativeViews() override {
175     // Called via Widget::Init to set the content view. No-op in these tests.
176   }
178  private:
179   DISALLOW_COPY_AND_ASSIGN(MockNativeWidgetMac);
182 // Helper test base to construct a BridgedNativeWidget with a valid parent.
183 class BridgedNativeWidgetTestBase : public ui::CocoaTest {
184  public:
185   BridgedNativeWidgetTestBase()
186       : widget_(new Widget),
187         native_widget_mac_(new MockNativeWidgetMac(widget_.get())) {
188   }
190   scoped_ptr<BridgedNativeWidget>& bridge() {
191     return native_widget_mac_->bridge();
192   }
194   // Overridden from testing::Test:
195   virtual void SetUp() override {
196     ui::CocoaTest::SetUp();
198     Widget::InitParams params;
199     params.native_widget = native_widget_mac_;
200     // To control the lifetime without an actual window that must be closed,
201     // tests in this file need to use WIDGET_OWNS_NATIVE_WIDGET.
202     params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
203     native_widget_mac_->GetWidget()->Init(params);
204   }
206  protected:
207   scoped_ptr<Widget> widget_;
208   MockNativeWidgetMac* native_widget_mac_;  // Weak. Owned by |widget_|.
211 class BridgedNativeWidgetTest : public BridgedNativeWidgetTestBase {
212  public:
213   BridgedNativeWidgetTest();
214   virtual ~BridgedNativeWidgetTest();
216   // Install a textfield in the view hierarchy and make it the text input
217   // client.
218   void InstallTextField(const std::string& text);
220   // Returns the current text as std::string.
221   std::string GetText();
223   // testing::Test:
224   virtual void SetUp() override;
225   virtual void TearDown() override;
227  protected:
228   scoped_ptr<views::View> view_;
229   scoped_ptr<BridgedNativeWidget> bridge_;
230   BridgedContentView* ns_view_;  // Weak. Owned by bridge_.
232  private:
233   DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetTest);
236 BridgedNativeWidgetTest::BridgedNativeWidgetTest() {
239 BridgedNativeWidgetTest::~BridgedNativeWidgetTest() {
242 void BridgedNativeWidgetTest::InstallTextField(const std::string& text) {
243   Textfield* textfield = new Textfield();
244   textfield->SetText(ASCIIToUTF16(text));
245   view_->AddChildView(textfield);
246   [ns_view_ setTextInputClient:textfield];
249 std::string BridgedNativeWidgetTest::GetText() {
250   NSRange range = NSMakeRange(0, NSUIntegerMax);
251   NSAttributedString* text =
252       [ns_view_ attributedSubstringForProposedRange:range actualRange:NULL];
253   return SysNSStringToUTF8([text string]);
256 void BridgedNativeWidgetTest::SetUp() {
257   BridgedNativeWidgetTestBase::SetUp();
259   view_.reset(new views::internal::RootView(widget_.get()));
260   base::scoped_nsobject<NSWindow> window([test_window() retain]);
262   // BridgedNativeWidget expects to be initialized with a hidden (deferred)
263   // window.
264   [window orderOut:nil];
265   EXPECT_FALSE([window delegate]);
266   bridge()->Init(window, Widget::InitParams());
268   // The delegate should exist before setting the root view.
269   EXPECT_TRUE([window delegate]);
270   bridge()->SetRootView(view_.get());
271   ns_view_ = bridge()->ns_view();
273   // Pretend it has been shown via NativeWidgetMac::Show().
274   [window orderFront:nil];
275   [test_window() makePretendKeyWindowAndSetFirstResponder:bridge()->ns_view()];
278 void BridgedNativeWidgetTest::TearDown() {
279   view_.reset();
280   BridgedNativeWidgetTestBase::TearDown();
283 // The TEST_VIEW macro expects the view it's testing to have a superview. In
284 // these tests, the NSView bridge is a contentView, at the root. These mimic
285 // what TEST_VIEW usually does.
286 TEST_F(BridgedNativeWidgetTest, BridgedNativeWidgetTest_TestViewAddRemove) {
287   base::scoped_nsobject<BridgedContentView> view([bridge()->ns_view() retain]);
288   EXPECT_NSEQ([test_window() contentView], view);
289   EXPECT_NSEQ(test_window(), [view window]);
291   // The superview of a contentView is an NSNextStepFrame.
292   EXPECT_TRUE([view superview]);
293   EXPECT_TRUE([view hostedView]);
295   // Ensure the tracking area to propagate mouseMoved: events to the RootView is
296   // installed.
297   EXPECT_EQ(1u, [[view trackingAreas] count]);
299   // Destroying the C++ bridge should remove references to any C++ objects in
300   // the ObjectiveC object, and remove it from the hierarchy.
301   bridge().reset();
302   EXPECT_FALSE([view hostedView]);
303   EXPECT_FALSE([view superview]);
304   EXPECT_FALSE([view window]);
305   EXPECT_EQ(0u, [[view trackingAreas] count]);
306   EXPECT_FALSE([test_window() contentView]);
307   EXPECT_FALSE([test_window() delegate]);
310 TEST_F(BridgedNativeWidgetTest, BridgedNativeWidgetTest_TestViewDisplay) {
311   [bridge()->ns_view() display];
314 // Test that resizing the window resizes the root view appropriately.
315 TEST_F(BridgedNativeWidgetTest, ViewSizeTracksWindow) {
316   const int kTestNewWidth = 400;
317   const int kTestNewHeight = 300;
319   // |test_window()| is borderless, so these should align.
320   NSSize window_size = [test_window() frame].size;
321   EXPECT_EQ(view_->width(), static_cast<int>(window_size.width));
322   EXPECT_EQ(view_->height(), static_cast<int>(window_size.height));
324   // Make sure a resize actually occurs.
325   EXPECT_NE(kTestNewWidth, view_->width());
326   EXPECT_NE(kTestNewHeight, view_->height());
328   [test_window() setFrame:NSMakeRect(0, 0, kTestNewWidth, kTestNewHeight)
329                   display:NO];
330   EXPECT_EQ(kTestNewWidth, view_->width());
331   EXPECT_EQ(kTestNewHeight, view_->height());
334 TEST_F(BridgedNativeWidgetTest, CreateInputMethodShouldNotReturnNull) {
335   scoped_ptr<views::InputMethod> input_method(bridge()->CreateInputMethod());
336   EXPECT_TRUE(input_method);
339 TEST_F(BridgedNativeWidgetTest, GetHostInputMethodShouldNotReturnNull) {
340   EXPECT_TRUE(bridge()->GetHostInputMethod());
343 // A simpler test harness for testing initialization flows.
344 typedef BridgedNativeWidgetTestBase BridgedNativeWidgetInitTest;
346 // Test that BridgedNativeWidget remains sane if Init() is never called.
347 TEST_F(BridgedNativeWidgetInitTest, InitNotCalled) {
348   EXPECT_FALSE(bridge()->ns_view());
349   EXPECT_FALSE(bridge()->ns_window());
350   bridge().reset();
353 // Test attaching to a parent window that is not a NativeWidgetMac. When the
354 // parent is a NativeWidgetMac, that is covered in widget_unittest.cc by
355 // WidgetOwnershipTest.Ownership_ViewsNativeWidgetOwnsWidget*.
356 TEST_F(BridgedNativeWidgetInitTest, ParentWindowNotNativeWidgetMac) {
357   Widget::InitParams params;
358   params.parent = [test_window() contentView];
359   EXPECT_EQ(0u, [[test_window() childWindows] count]);
361   base::scoped_nsobject<NSWindow> child_window(
362       [[NSWindow alloc] initWithContentRect:NSMakeRect(50, 50, 400, 300)
363                                   styleMask:NSBorderlessWindowMask
364                                     backing:NSBackingStoreBuffered
365                                       defer:YES]);
366   [child_window setReleasedWhenClosed:NO];  // Owned by scoped_nsobject.
368   EXPECT_FALSE([child_window parentWindow]);
369   bridge()->Init(child_window, params);
371   EXPECT_EQ(1u, [[test_window() childWindows] count]);
372   EXPECT_EQ(test_window(), [bridge()->ns_window() parentWindow]);
373   bridge().reset();
374   EXPECT_EQ(0u, [[test_window() childWindows] count]);
377 // Test getting complete string using text input protocol.
378 TEST_F(BridgedNativeWidgetTest, TextInput_GetCompleteString) {
379   const std::string kTestString = "foo bar baz";
380   InstallTextField(kTestString);
382   NSRange range = NSMakeRange(0, kTestString.size());
383   NSRange actual_range;
384   NSAttributedString* text =
385       [ns_view_ attributedSubstringForProposedRange:range
386                                         actualRange:&actual_range];
387   EXPECT_EQ(kTestString, SysNSStringToUTF8([text string]));
388   EXPECT_EQ_RANGE(range, actual_range);
391 // Test getting middle substring using text input protocol.
392 TEST_F(BridgedNativeWidgetTest, TextInput_GetMiddleSubstring) {
393   const std::string kTestString = "foo bar baz";
394   InstallTextField(kTestString);
396   NSRange range = NSMakeRange(4, 3);
397   NSRange actual_range;
398   NSAttributedString* text =
399       [ns_view_ attributedSubstringForProposedRange:range
400                                         actualRange:&actual_range];
401   EXPECT_EQ("bar", SysNSStringToUTF8([text string]));
402   EXPECT_EQ_RANGE(range, actual_range);
405 // Test getting ending substring using text input protocol.
406 TEST_F(BridgedNativeWidgetTest, TextInput_GetEndingSubstring) {
407   const std::string kTestString = "foo bar baz";
408   InstallTextField(kTestString);
410   NSRange range = NSMakeRange(8, 100);
411   NSRange actual_range;
412   NSAttributedString* text =
413       [ns_view_ attributedSubstringForProposedRange:range
414                                         actualRange:&actual_range];
415   EXPECT_EQ("baz", SysNSStringToUTF8([text string]));
416   EXPECT_EQ(range.location, actual_range.location);
417   EXPECT_EQ(3U, actual_range.length);
420 // Test getting empty substring using text input protocol.
421 TEST_F(BridgedNativeWidgetTest, TextInput_GetEmptySubstring) {
422   const std::string kTestString = "foo bar baz";
423   InstallTextField(kTestString);
425   NSRange range = EmptyRange();
426   NSRange actual_range;
427   NSAttributedString* text =
428       [ns_view_ attributedSubstringForProposedRange:range
429                                         actualRange:&actual_range];
430   EXPECT_EQ("", SysNSStringToUTF8([text string]));
431   EXPECT_EQ_RANGE(range, actual_range);
434 // Test inserting text using text input protocol.
435 TEST_F(BridgedNativeWidgetTest, TextInput_InsertText) {
436   const std::string kTestString = "foo";
437   InstallTextField(kTestString);
439   [ns_view_ insertText:SysUTF8ToNSString(kTestString)
440       replacementRange:EmptyRange()];
441   gfx::Range range(0, kTestString.size());
442   base::string16 text;
443   EXPECT_TRUE([ns_view_ textInputClient]->GetTextFromRange(range, &text));
444   EXPECT_EQ(ASCIIToUTF16(kTestString), text);
447 // Test replacing text using text input protocol.
448 TEST_F(BridgedNativeWidgetTest, TextInput_ReplaceText) {
449   const std::string kTestString = "foo bar";
450   InstallTextField(kTestString);
452   [ns_view_ insertText:@"baz" replacementRange:NSMakeRange(4, 3)];
453   EXPECT_EQ("foo baz", GetText());
456 // Test IME composition using text input protocol.
457 TEST_F(BridgedNativeWidgetTest, TextInput_Compose) {
458   const std::string kTestString = "foo ";
459   InstallTextField(kTestString);
461   EXPECT_FALSE([ns_view_ hasMarkedText]);
462   EXPECT_EQ_RANGE(EmptyRange(), [ns_view_ markedRange]);
464   // Start composition.
465   NSString* compositionText = @"bar";
466   NSUInteger compositionLength = [compositionText length];
467   [ns_view_ setMarkedText:compositionText
468             selectedRange:NSMakeRange(0, 2)
469          replacementRange:EmptyRange()];
470   EXPECT_TRUE([ns_view_ hasMarkedText]);
471   EXPECT_EQ_RANGE(NSMakeRange(kTestString.size(), compositionLength),
472                   [ns_view_ markedRange]);
473   EXPECT_EQ_RANGE(NSMakeRange(kTestString.size(), 2), [ns_view_ selectedRange]);
475   // Confirm composition.
476   [ns_view_ unmarkText];
477   EXPECT_FALSE([ns_view_ hasMarkedText]);
478   EXPECT_EQ_RANGE(EmptyRange(), [ns_view_ markedRange]);
479   EXPECT_EQ("foo bar", GetText());
480   EXPECT_EQ_RANGE(NSMakeRange(GetText().size(), 0), [ns_view_ selectedRange]);
483 // Test moving the caret left and right using text input protocol.
484 TEST_F(BridgedNativeWidgetTest, TextInput_MoveLeftRight) {
485   InstallTextField("foo");
486   EXPECT_EQ_RANGE(NSMakeRange(3, 0), [ns_view_ selectedRange]);
488   // Move right not allowed, out of range.
489   [ns_view_ doCommandBySelector:@selector(moveRight:)];
490   EXPECT_EQ_RANGE(NSMakeRange(3, 0), [ns_view_ selectedRange]);
492   // Move left.
493   [ns_view_ doCommandBySelector:@selector(moveLeft:)];
494   EXPECT_EQ_RANGE(NSMakeRange(2, 0), [ns_view_ selectedRange]);
496   // Move right.
497   [ns_view_ doCommandBySelector:@selector(moveRight:)];
498   EXPECT_EQ_RANGE(NSMakeRange(3, 0), [ns_view_ selectedRange]);
501 // Test backward delete using text input protocol.
502 TEST_F(BridgedNativeWidgetTest, TextInput_DeleteBackward) {
503   InstallTextField("a");
504   EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]);
506   // Delete one character.
507   [ns_view_ doCommandBySelector:@selector(deleteBackward:)];
508   EXPECT_EQ("", GetText());
509   EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]);
511   // Try to delete again on an empty string.
512   [ns_view_ doCommandBySelector:@selector(deleteBackward:)];
513   EXPECT_EQ("", GetText());
514   EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]);
517 // Test forward delete using text input protocol.
518 TEST_F(BridgedNativeWidgetTest, TextInput_DeleteForward) {
519   InstallTextField("a");
520   EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]);
522   // At the end of the string, can't delete forward.
523   [ns_view_ doCommandBySelector:@selector(deleteForward:)];
524   EXPECT_EQ("a", GetText());
525   EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]);
527   // Should succeed after moving left first.
528   [ns_view_ doCommandBySelector:@selector(moveLeft:)];
529   [ns_view_ doCommandBySelector:@selector(deleteForward:)];
530   EXPECT_EQ("", GetText());
531   EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]);
534 // Tests for correct fullscreen tracking, regardless of whether it is initiated
535 // by the Widget code or elsewhere (e.g. by the user).
536 TEST_F(BridgedNativeWidgetTest, FullscreenSynchronousState) {
537   EXPECT_FALSE(widget_->IsFullscreen());
538   if (base::mac::IsOSSnowLeopard())
539     return;
541   // Allow user-initiated fullscreen changes on the Window.
542   [test_window()
543       setCollectionBehavior:[test_window() collectionBehavior] |
544                             NSWindowCollectionBehaviorFullScreenPrimary];
546   base::scoped_nsobject<NativeWidgetMacNotificationWaiter> waiter(
547       [[NativeWidgetMacNotificationWaiter alloc] initWithWindow:test_window()]);
548   const gfx::Rect restored_bounds = widget_->GetRestoredBounds();
550   // Simulate a user-initiated fullscreen. Note trying to to this again before
551   // spinning a runloop will cause Cocoa to emit text to stdio and ignore it.
552   [test_window() toggleFullScreen:nil];
553   EXPECT_TRUE(widget_->IsFullscreen());
554   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
556   // Note there's now an animation running. While that's happening, toggling the
557   // state should work as expected, but do "nothing".
558   widget_->SetFullscreen(false);
559   EXPECT_FALSE(widget_->IsFullscreen());
560   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
561   widget_->SetFullscreen(false);  // Same request - should no-op.
562   EXPECT_FALSE(widget_->IsFullscreen());
563   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
565   widget_->SetFullscreen(true);
566   EXPECT_TRUE(widget_->IsFullscreen());
567   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
569   // Always finish out of fullscreen. Otherwise there are 4 NSWindow objects
570   // that Cocoa creates which don't close themselves and will be seen by the Mac
571   // test harness on teardown. Note that the test harness will be waiting until
572   // all animations complete, since these temporary animation windows will not
573   // be removed from the window list until they do.
574   widget_->SetFullscreen(false);
575   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
577   // Now we must wait for the notifications. Since, if the widget is torn down,
578   // the NSWindowDelegate is removed, and the pending request to take out of
579   // fullscreen is lost. Since a message loop has not yet spun up in this test
580   // we can reliably say there will be one enter and one exit, despite all the
581   // toggling above.
582   base::MessageLoopForUI message_loop;
583   [waiter waitForEnterCount:1 exitCount:1];
584   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
587 // Test fullscreen without overlapping calls and without changing collection
588 // behavior on the test window.
589 TEST_F(BridgedNativeWidgetTest, FullscreenEnterAndExit) {
590   base::MessageLoopForUI message_loop;
591   base::scoped_nsobject<NativeWidgetMacNotificationWaiter> waiter(
592       [[NativeWidgetMacNotificationWaiter alloc] initWithWindow:test_window()]);
594   EXPECT_FALSE(widget_->IsFullscreen());
595   const gfx::Rect restored_bounds = widget_->GetRestoredBounds();
596   EXPECT_FALSE(restored_bounds.IsEmpty());
598   // Ensure this works without having to change collection behavior as for the
599   // test above.
600   widget_->SetFullscreen(true);
601   if (base::mac::IsOSSnowLeopard()) {
602     // On Snow Leopard, SetFullscreen() isn't implemented. But shouldn't crash.
603     EXPECT_FALSE(widget_->IsFullscreen());
604     return;
605   }
607   EXPECT_TRUE(widget_->IsFullscreen());
608   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
610   // Should be zero until the runloop spins.
611   EXPECT_EQ(0, [waiter enterCount]);
612   [waiter waitForEnterCount:1 exitCount:0];
614   // Verify it hasn't exceeded.
615   EXPECT_EQ(1, [waiter enterCount]);
616   EXPECT_EQ(0, [waiter exitCount]);
617   EXPECT_TRUE(widget_->IsFullscreen());
618   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
620   widget_->SetFullscreen(false);
621   EXPECT_FALSE(widget_->IsFullscreen());
622   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
624   [waiter waitForEnterCount:1 exitCount:1];
625   EXPECT_EQ(1, [waiter enterCount]);
626   EXPECT_EQ(1, [waiter exitCount]);
627   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
630 typedef BridgedNativeWidgetTestBase BridgedNativeWidgetSimulateFullscreenTest;
632 // Simulate the notifications that AppKit would send out if a fullscreen
633 // operation begins, and then fails and must abort. This notification sequence
634 // was determined by posting delayed tasks to toggle fullscreen state and then
635 // mashing Ctrl+Left/Right to keep OSX in a transition between Spaces to cause
636 // the fullscreen transition to fail.
637 TEST_F(BridgedNativeWidgetSimulateFullscreenTest, FailToEnterAndExit) {
638   if (base::mac::IsOSSnowLeopard())
639     return;
641   base::scoped_nsobject<NSWindow> owned_window(
642       [[BridgedNativeWidgetTestFullScreenWindow alloc]
643           initWithContentRect:NSMakeRect(50, 50, 400, 300)
644                     styleMask:NSBorderlessWindowMask
645                       backing:NSBackingStoreBuffered
646                         defer:YES]);
647   [owned_window setReleasedWhenClosed:NO];  // Owned by scoped_nsobject.
648   bridge()->Init(owned_window, Widget::InitParams());  // Transfers ownership.
650   BridgedNativeWidgetTestFullScreenWindow* window =
651       base::mac::ObjCCastStrict<BridgedNativeWidgetTestFullScreenWindow>(
652           widget_->GetNativeWindow());
653   widget_->Show();
655   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
657   EXPECT_FALSE(bridge()->target_fullscreen_state());
659   // Simulate an initial toggleFullScreen: (user- or Widget-initiated).
660   [center postNotificationName:NSWindowWillEnterFullScreenNotification
661                         object:window];
663   // On a failure, Cocoa starts by sending an unexpected *exit* fullscreen, and
664   // BridgedNativeWidget will think it's just a delayed transition and try to go
665   // back into fullscreen but get ignored by Cocoa.
666   EXPECT_EQ(0, [window ignoredToggleFullScreenCount]);
667   EXPECT_TRUE(bridge()->target_fullscreen_state());
668   [center postNotificationName:NSWindowDidExitFullScreenNotification
669                         object:window];
670   EXPECT_EQ(1, [window ignoredToggleFullScreenCount]);
671   EXPECT_FALSE(bridge()->target_fullscreen_state());
673   // Cocoa follows up with a failure message sent to the NSWindowDelegate (there
674   // is no equivalent notification for failure). Called via id so that this
675   // compiles on 10.6.
676   id window_delegate = [window delegate];
677   [window_delegate windowDidFailToEnterFullScreen:window];
678   EXPECT_FALSE(bridge()->target_fullscreen_state());
680   // Now perform a successful fullscreen operation.
681   [center postNotificationName:NSWindowWillEnterFullScreenNotification
682                         object:window];
683   EXPECT_TRUE(bridge()->target_fullscreen_state());
684   [center postNotificationName:NSWindowDidEnterFullScreenNotification
685                         object:window];
686   EXPECT_TRUE(bridge()->target_fullscreen_state());
688   // And try to get out.
689   [center postNotificationName:NSWindowWillExitFullScreenNotification
690                         object:window];
691   EXPECT_FALSE(bridge()->target_fullscreen_state());
693   // On a failure, Cocoa sends a failure message, but then just dumps the window
694   // out of fullscreen anyway (in that order).
695   [window_delegate windowDidFailToExitFullScreen:window];
696   EXPECT_FALSE(bridge()->target_fullscreen_state());
697   [center postNotificationName:NSWindowDidExitFullScreenNotification
698                         object:window];
699   EXPECT_EQ(1, [window ignoredToggleFullScreenCount]);  // No change.
700   EXPECT_FALSE(bridge()->target_fullscreen_state());
702   widget_->CloseNow();
705 }  // namespace test
706 }  // namespace views