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/gfx/test/ui_cocoa_test_helper.h"
7 #include "base/debug/debugger.h"
8 #include "base/logging.h"
9 #include "base/stl_util.h"
10 #include "base/test/mock_chrome_application_mac.h"
11 #include "base/test/test_timeouts.h"
15 // Some AppKit function leak intentionally, e.g. for caching purposes.
16 // Force those leaks here, so there can be a unique calling path, allowing
17 // to flag intentional leaks without having to suppress all calls to
18 // potentially leaky functions.
19 void NOINLINE ForceSystemLeaks() {
20 // If a test suite hasn't already initialized NSApp, register the mock one
23 mock_cr_app::RegisterMockCrApp();
25 // First NSCursor push always leaks.
26 [[NSCursor openHandCursor] push];
32 @implementation CocoaTestHelperWindow
34 - (id)initWithContentRect:(NSRect)contentRect {
35 self = [super initWithContentRect:contentRect
36 styleMask:NSBorderlessWindowMask
37 backing:NSBackingStoreBuffered
40 useDefaultConstraints_ = YES;
46 return [self initWithContentRect:NSMakeRect(0, 0, 800, 600)];
50 // Just a good place to put breakpoints when having problems with
51 // unittests and CocoaTestHelperWindow.
55 - (void)makePretendKeyWindowAndSetFirstResponder:(NSResponder*)responder {
56 EXPECT_TRUE([self makeFirstResponder:responder]);
57 [self setPretendIsKeyWindow:YES];
60 - (void)clearPretendKeyWindowAndFirstResponder {
61 [self setPretendIsKeyWindow:NO];
62 EXPECT_TRUE([self makeFirstResponder:NSApp]);
65 - (void)setPretendIsKeyWindow:(BOOL)flag {
66 pretendIsKeyWindow_ = flag;
69 - (void)setUseDefaultConstraints:(BOOL)useDefaultConstraints {
70 useDefaultConstraints_ = useDefaultConstraints;
74 return pretendIsKeyWindow_;
77 - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen {
78 if (!useDefaultConstraints_)
81 return [super constrainFrameRect:frameRect toScreen:screen];
88 CocoaTest::CocoaTest() : called_tear_down_(false), test_window_(nil) {
93 CocoaTest::~CocoaTest() {
94 // Must call CocoaTest's teardown from your overrides.
95 DCHECK(called_tear_down_);
98 void CocoaTest::Init() {
99 // Set the duration of AppKit-evaluated animations (such as frame changes)
100 // to zero for testing purposes. That way they take effect immediately.
101 [[NSAnimationContext currentContext] setDuration:0.0];
103 // The above does not affect window-resize time, such as for an
104 // attached sheet dropping in. Set that duration for the current
105 // process (this is not persisted). Empirically, the value of 0.0
108 [NSDictionary dictionaryWithObject:@"0.01" forKey:@"NSWindowResizeTime"];
109 [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
111 // Collect the list of windows that were open when the test started so
112 // that we don't wait for them to close in TearDown. Has to be done
113 // after BootstrapCocoa is called.
114 initial_windows_ = ApplicationWindows();
117 void CocoaTest::TearDown() {
118 called_tear_down_ = true;
119 // Call close on our test_window to clean it up if one was opened.
120 [test_window_ clearPretendKeyWindowAndFirstResponder];
121 [test_window_ close];
124 // Recycle the pool to clean up any stuff that was put on the
125 // autorelease pool due to window or windowcontroller closures.
128 // Some controls (NSTextFields, NSComboboxes etc) use
129 // performSelector:withDelay: to clean up drag handlers and other
130 // things (Radar 5851458 "Closing a window with a NSTextView in it
131 // should get rid of it immediately"). The event loop must be spun
132 // to get everything cleaned up correctly. It normally only takes
133 // one to two spins through the event loop to see a change.
135 // NOTE(shess): Under valgrind, -nextEventMatchingMask:* in one test
136 // needed to run twice, once taking .2 seconds, the next time .6
137 // seconds. The loop exit condition attempts to be scalable.
139 // Get the set of windows which weren't present when the test
141 std::set<NSWindow*> windows_left(WindowsLeft());
143 while (!windows_left.empty()) {
144 // Cover delayed actions by spinning the loop at least once after
146 const NSTimeInterval kCloseTimeoutSeconds =
147 TestTimeouts::action_timeout().InSecondsF();
149 // Cover chains of delayed actions by spinning the loop at least
151 const int kCloseSpins = 3;
153 // Track the set of remaining windows so that everything can be
154 // reset if progress is made.
155 std::set<NSWindow*> still_left = windows_left;
157 NSDate* start_date = [NSDate date];
158 bool one_more_time = true;
160 while (still_left.size() == windows_left.size() &&
161 (spins < kCloseSpins || one_more_time)) {
162 // Check the timeout before pumping events, so that we'll spin
163 // the loop once after the timeout.
165 ([start_date timeIntervalSinceNow] > -kCloseTimeoutSeconds);
167 // Autorelease anything thrown up by the event loop.
169 base::mac::ScopedNSAutoreleasePool pool;
171 NSEvent *next_event = [NSApp nextEventMatchingMask:NSAnyEventMask
173 inMode:NSDefaultRunLoopMode
175 [NSApp sendEvent:next_event];
176 [NSApp updateWindows];
179 // Refresh the outstanding windows.
180 still_left = WindowsLeft();
183 // If no progress is being made, log a failure and continue.
184 if (still_left.size() == windows_left.size()) {
185 // NOTE(shess): Failing this expectation means that the test
186 // opened windows which have not been fully released. Either
187 // there is a leak, or perhaps one of |kCloseTimeoutSeconds| or
188 // |kCloseSpins| needs adjustment.
189 EXPECT_EQ(0U, windows_left.size());
190 for (std::set<NSWindow*>::iterator iter = windows_left.begin();
191 iter != windows_left.end(); ++iter) {
192 const char* desc = [[*iter description] UTF8String];
193 LOG(WARNING) << "Didn't close window " << desc;
198 windows_left = still_left;
200 PlatformTest::TearDown();
203 std::set<NSWindow*> CocoaTest::ApplicationWindows() {
204 // This must NOT retain the windows it is returning.
205 std::set<NSWindow*> windows;
207 // Must create a pool here because [NSApp windows] has created an array
208 // with retains on all the windows in it.
209 base::mac::ScopedNSAutoreleasePool pool;
210 NSArray *appWindows = [NSApp windows];
211 for (NSWindow *window in appWindows) {
212 windows.insert(window);
217 std::set<NSWindow*> CocoaTest::WindowsLeft() {
218 const std::set<NSWindow*> windows(ApplicationWindows());
219 std::set<NSWindow*> windows_left =
220 base::STLSetDifference<std::set<NSWindow*> >(windows, initial_windows_);
224 CocoaTestHelperWindow* CocoaTest::test_window() {
226 test_window_ = [[CocoaTestHelperWindow alloc] init];
227 if (base::debug::BeingDebugged()) {
228 [test_window_ orderFront:nil];
230 [test_window_ orderBack:nil];