1 // Copyright 2015 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/base/test/scoped_fake_nswindow_fullscreen.h"
7 #import <Cocoa/Cocoa.h>
10 #import "base/mac/foundation_util.h"
11 #import "base/mac/mac_util.h"
12 #import "base/mac/scoped_nsobject.h"
13 #import "base/mac/scoped_objc_class_swizzler.h"
14 #import "base/mac/sdk_forward_declarations.h"
15 #include "base/message_loop/message_loop.h"
17 // This method exists on NSWindowDelegate on 10.7+.
18 // To build on 10.6, we just need to declare it somewhere. We'll test
19 // -[NSObject respondsToSelector] before calling it.
20 #if !defined(MAC_OS_X_VERSION_10_7) || \
21 MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
22 @protocol NSWindowDelegateLion
23 - (NSSize)window:(NSWindow*)window
24 willUseFullScreenContentSize:(NSSize)proposedSize;
28 // Donates a testing implementation of [NSWindow toggleFullScreen:].
29 @interface ToggleFullscreenDonorForWindow : NSObject
34 ui::test::ScopedFakeNSWindowFullscreen::Impl* g_fake_fullscreen_impl = nullptr;
41 class ScopedFakeNSWindowFullscreen::Impl {
44 : toggle_fullscreen_swizzler_([NSWindow class],
45 [ToggleFullscreenDonorForWindow class],
46 @selector(toggleFullScreen:)) {}
49 // If there's a pending transition, it means there's a task in the queue to
50 // complete it, referencing |this|.
51 DCHECK(!is_in_transition_);
54 void ToggleFullscreenForWindow(NSWindow* window) {
55 DCHECK(!is_in_transition_);
57 StartEnterFullscreen(window);
58 } else if (window_ == window) {
59 StartExitFullscreen();
61 // Another window is fullscreen.
66 void StartEnterFullscreen(NSWindow* window) {
67 // If the window cannot go fullscreen, do nothing.
68 if (!([window collectionBehavior] &
69 NSWindowCollectionBehaviorFullScreenPrimary)) {
73 // This cannot be id<NSWindowDelegate> because on 10.6 it won't have
74 // window:willUseFullScreenContentSize:.
75 id delegate = [window delegate];
77 // Nothing is currently fullscreen. Make this window fullscreen.
79 is_in_transition_ = true;
80 frame_before_fullscreen_ = [window frame];
81 NSSize fullscreen_content_size =
82 [window contentRectForFrameRect:[[window screen] frame]].size;
83 if ([delegate respondsToSelector:@selector(window:
84 willUseFullScreenContentSize:)]) {
85 fullscreen_content_size = [delegate window:window
86 willUseFullScreenContentSize:fullscreen_content_size];
88 [[NSNotificationCenter defaultCenter]
89 postNotificationName:NSWindowWillEnterFullScreenNotification
91 base::MessageLoopForUI::current()->PostTask(
92 FROM_HERE, base::Bind(&Impl::FinishEnterFullscreen,
93 base::Unretained(this), fullscreen_content_size));
96 void FinishEnterFullscreen(NSSize fullscreen_content_size) {
97 // The frame should not have changed during the transition.
98 DCHECK(NSEqualRects(frame_before_fullscreen_, [window_ frame]));
99 // Style mask must be set first because -[NSWindow frame] may be different
100 // depending on NSFullScreenWindowMask.
101 [window_ setStyleMask:[window_ styleMask] | NSFullScreenWindowMask];
102 // The origin doesn't matter, NSFullScreenWindowMask means the origin will
104 NSRect target_fullscreen_frame = [window_
105 frameRectForContentRect:NSMakeRect(0, 0, fullscreen_content_size.width,
106 fullscreen_content_size.height)];
107 [window_ setFrame:target_fullscreen_frame display:YES animate:NO];
108 [[NSNotificationCenter defaultCenter]
109 postNotificationName:NSWindowDidEnterFullScreenNotification
111 // Store the actual frame because we check against it when exiting.
112 frame_during_fullscreen_ = [window_ frame];
113 is_in_transition_ = false;
116 void StartExitFullscreen() {
117 is_in_transition_ = true;
118 [[NSNotificationCenter defaultCenter]
119 postNotificationName:NSWindowWillExitFullScreenNotification
122 base::MessageLoopForUI::current()->PostTask(
124 base::Bind(&Impl::FinishExitFullscreen, base::Unretained(this)));
127 void FinishExitFullscreen() {
128 // The bounds may have changed during the transition. Check for this before
129 // setting the style mask because -[NSWindow frame] may be different
130 // depending on NSFullScreenWindowMask.
131 bool no_frame_change_during_fullscreen =
132 NSEqualRects(frame_during_fullscreen_, [window_ frame]);
133 [window_ setStyleMask:[window_ styleMask] & ~NSFullScreenWindowMask];
134 // Set the original frame after setting the style mask.
135 if (no_frame_change_during_fullscreen)
136 [window_ setFrame:frame_before_fullscreen_ display:YES animate:NO];
137 [[NSNotificationCenter defaultCenter]
138 postNotificationName:NSWindowDidExitFullScreenNotification
141 is_in_transition_ = false;
145 base::mac::ScopedObjCClassSwizzler toggle_fullscreen_swizzler_;
147 // The currently fullscreen window.
148 NSWindow* window_ = nil;
149 NSRect frame_before_fullscreen_;
150 NSRect frame_during_fullscreen_;
151 bool is_in_transition_ = false;
153 DISALLOW_COPY_AND_ASSIGN(Impl);
156 ScopedFakeNSWindowFullscreen::ScopedFakeNSWindowFullscreen() {
157 // -[NSWindow toggleFullScreen:] does not exist on 10.6, so do nothing.
158 if (base::mac::IsOSSnowLeopard())
161 DCHECK(!g_fake_fullscreen_impl);
162 impl_.reset(new Impl);
163 g_fake_fullscreen_impl = impl_.get();
166 ScopedFakeNSWindowFullscreen::~ScopedFakeNSWindowFullscreen() {
167 g_fake_fullscreen_impl = nullptr;
173 @implementation ToggleFullscreenDonorForWindow
175 - (void)toggleFullScreen:(id)sender {
176 NSWindow* window = base::mac::ObjCCastStrict<NSWindow>(self);
177 g_fake_fullscreen_impl->ToggleFullscreenForWindow(window);