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/mac_util.h"
10 #import "base/mac/sdk_forward_declarations.h"
11 #include "base/run_loop.h"
12 #include "ui/views/test/widget_test.h"
14 @interface NativeWidgetMacNotificationWaiter : NSObject {
16 scoped_ptr<base::RunLoop> runLoop_;
17 base::scoped_nsobject<NSWindow> window_;
20 int targetEnterCount_;
24 @property(readonly, nonatomic) int enterCount;
25 @property(readonly, nonatomic) int exitCount;
27 // Initialize for the given window and start tracking notifications.
28 - (id)initWithWindow:(NSWindow*)window;
30 // Keep spinning a run loop until the enter and exit counts match.
31 - (void)waitForEnterCount:(int)enterCount exitCount:(int)exitCount;
34 // Exit the RunLoop if there is one and the counts being tracked match.
35 - (void)maybeQuitForChangedArg:(int*)changedArg;
37 - (void)onEnter:(NSNotification*)notification;
38 - (void)onExit:(NSNotification*)notification;
42 @implementation NativeWidgetMacNotificationWaiter
44 @synthesize enterCount = enterCount_;
45 @synthesize exitCount = exitCount_;
47 - (id)initWithWindow:(NSWindow*)window {
48 if ((self = [super init])) {
49 window_.reset([window retain]);
50 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
51 [defaultCenter addObserver:self
52 selector:@selector(onEnter:)
53 name:NSWindowDidEnterFullScreenNotification
55 [defaultCenter addObserver:self
56 selector:@selector(onExit:)
57 name:NSWindowDidExitFullScreenNotification
65 [[NSNotificationCenter defaultCenter] removeObserver:self];
69 - (void)waitForEnterCount:(int)enterCount exitCount:(int)exitCount {
70 if (enterCount_ >= enterCount && exitCount_ >= exitCount)
73 targetEnterCount_ = enterCount;
74 targetExitCount_ = exitCount;
75 runLoop_.reset(new base::RunLoop);
80 - (void)maybeQuitForChangedArg:(int*)changedArg {
85 if (enterCount_ >= targetEnterCount_ && exitCount_ >= targetExitCount_)
89 - (void)onEnter:(NSNotification*)notification {
90 [self maybeQuitForChangedArg:&enterCount_];
93 - (void)onExit:(NSNotification*)notification {
94 [self maybeQuitForChangedArg:&exitCount_];
101 class BridgedNativeWidgetUITest : public test::WidgetTest {
103 BridgedNativeWidgetUITest() {}
106 void SetUp() override {
108 Widget::InitParams init_params =
109 CreateParams(Widget::InitParams::TYPE_WINDOW);
110 init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
111 widget_.reset(new Widget);
112 widget_->Init(init_params);
115 void TearDown() override {
116 // Ensures any compositor is removed before ViewsTestBase tears down the
119 WidgetTest::TearDown();
122 NSWindow* test_window() {
123 return widget_->GetNativeWindow();
127 scoped_ptr<Widget> widget_;
130 // Tests for correct fullscreen tracking, regardless of whether it is initiated
131 // by the Widget code or elsewhere (e.g. by the user).
132 TEST_F(BridgedNativeWidgetUITest, FullscreenSynchronousState) {
133 EXPECT_FALSE(widget_->IsFullscreen());
134 if (base::mac::IsOSSnowLeopard())
137 // Allow user-initiated fullscreen changes on the Window.
139 setCollectionBehavior:[test_window() collectionBehavior] |
140 NSWindowCollectionBehaviorFullScreenPrimary];
142 base::scoped_nsobject<NativeWidgetMacNotificationWaiter> waiter(
143 [[NativeWidgetMacNotificationWaiter alloc] initWithWindow:test_window()]);
144 const gfx::Rect restored_bounds = widget_->GetRestoredBounds();
146 // First show the widget. A user shouldn't be able to initiate fullscreen
147 // unless the window is visible in the first place.
150 // Simulate a user-initiated fullscreen. Note trying to to this again before
151 // spinning a runloop will cause Cocoa to emit text to stdio and ignore it.
152 [test_window() toggleFullScreen:nil];
153 EXPECT_TRUE(widget_->IsFullscreen());
154 EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
156 // Note there's now an animation running. While that's happening, toggling the
157 // state should work as expected, but do "nothing".
158 widget_->SetFullscreen(false);
159 EXPECT_FALSE(widget_->IsFullscreen());
160 EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
161 widget_->SetFullscreen(false); // Same request - should no-op.
162 EXPECT_FALSE(widget_->IsFullscreen());
163 EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
165 widget_->SetFullscreen(true);
166 EXPECT_TRUE(widget_->IsFullscreen());
167 EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
169 // Always finish out of fullscreen. Otherwise there are 4 NSWindow objects
170 // that Cocoa creates which don't close themselves and will be seen by the Mac
171 // test harness on teardown. Note that the test harness will be waiting until
172 // all animations complete, since these temporary animation windows will not
173 // be removed from the window list until they do.
174 widget_->SetFullscreen(false);
175 EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
177 // Now we must wait for the notifications. Since, if the widget is torn down,
178 // the NSWindowDelegate is removed, and the pending request to take out of
179 // fullscreen is lost. Since a message loop has not yet spun up in this test
180 // we can reliably say there will be one enter and one exit, despite all the
182 [waiter waitForEnterCount:1 exitCount:1];
183 EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
186 // Test fullscreen without overlapping calls and without changing collection
187 // behavior on the test window.
188 TEST_F(BridgedNativeWidgetUITest, FullscreenEnterAndExit) {
189 base::scoped_nsobject<NativeWidgetMacNotificationWaiter> waiter(
190 [[NativeWidgetMacNotificationWaiter alloc] initWithWindow:test_window()]);
192 EXPECT_FALSE(widget_->IsFullscreen());
193 const gfx::Rect restored_bounds = widget_->GetRestoredBounds();
194 EXPECT_FALSE(restored_bounds.IsEmpty());
196 // Ensure this works without having to change collection behavior as for the
197 // test above. Also check that making a hidden widget fullscreen shows it.
198 EXPECT_FALSE(widget_->IsVisible());
199 widget_->SetFullscreen(true);
200 EXPECT_TRUE(widget_->IsVisible());
201 if (base::mac::IsOSSnowLeopard()) {
202 // On Snow Leopard, SetFullscreen() isn't implemented. But shouldn't crash.
203 EXPECT_FALSE(widget_->IsFullscreen());
207 EXPECT_TRUE(widget_->IsFullscreen());
208 EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
210 // Should be zero until the runloop spins.
211 EXPECT_EQ(0, [waiter enterCount]);
212 [waiter waitForEnterCount:1 exitCount:0];
214 // Verify it hasn't exceeded.
215 EXPECT_EQ(1, [waiter enterCount]);
216 EXPECT_EQ(0, [waiter exitCount]);
217 EXPECT_TRUE(widget_->IsFullscreen());
218 EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
220 widget_->SetFullscreen(false);
221 EXPECT_FALSE(widget_->IsFullscreen());
222 EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
224 [waiter waitForEnterCount:1 exitCount:1];
225 EXPECT_EQ(1, [waiter enterCount]);
226 EXPECT_EQ(1, [waiter exitCount]);
227 EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());