1 // Copyright (c) 2012 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/ui_cocoa_test_helper.h"
7 #include "base/debug/debugger.h"
8 #include "base/logging.h"
9 #include "base/test/test_timeouts.h"
11 @implementation CocoaTestHelperWindow
13 - (id)initWithContentRect:(NSRect)contentRect {
14 return [self initWithContentRect:contentRect
15 styleMask:NSBorderlessWindowMask
16 backing:NSBackingStoreBuffered
21 return [self initWithContentRect:NSMakeRect(0, 0, 800, 600)];
25 // Just a good place to put breakpoints when having problems with
26 // unittests and CocoaTestHelperWindow.
30 - (void)makePretendKeyWindowAndSetFirstResponder:(NSResponder*)responder {
31 EXPECT_TRUE([self makeFirstResponder:responder]);
32 [self setPretendIsKeyWindow:YES];
35 - (void)clearPretendKeyWindowAndFirstResponder {
36 [self setPretendIsKeyWindow:NO];
37 EXPECT_TRUE([self makeFirstResponder:NSApp]);
40 - (void)setPretendIsKeyWindow:(BOOL)flag {
41 pretendIsKeyWindow_ = flag;
45 return pretendIsKeyWindow_;
52 CocoaTest::CocoaTest() : called_tear_down_(false), test_window_(nil) {
56 CocoaTest::~CocoaTest() {
57 // Must call CocoaTest's teardown from your overrides.
58 DCHECK(called_tear_down_);
61 void CocoaTest::Init() {
62 // Set the duration of AppKit-evaluated animations (such as frame changes)
63 // to zero for testing purposes. That way they take effect immediately.
64 [[NSAnimationContext currentContext] setDuration:0.0];
66 // The above does not affect window-resize time, such as for an
67 // attached sheet dropping in. Set that duration for the current
68 // process (this is not persisted). Empirically, the value of 0.0
71 [NSDictionary dictionaryWithObject:@"0.01" forKey:@"NSWindowResizeTime"];
72 [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
74 // Collect the list of windows that were open when the test started so
75 // that we don't wait for them to close in TearDown. Has to be done
76 // after BootstrapCocoa is called.
77 initial_windows_ = ApplicationWindows();
80 void CocoaTest::TearDown() {
81 called_tear_down_ = true;
82 // Call close on our test_window to clean it up if one was opened.
83 [test_window_ clearPretendKeyWindowAndFirstResponder];
87 // Recycle the pool to clean up any stuff that was put on the
88 // autorelease pool due to window or windowcontroller closures.
91 // Some controls (NSTextFields, NSComboboxes etc) use
92 // performSelector:withDelay: to clean up drag handlers and other
93 // things (Radar 5851458 "Closing a window with a NSTextView in it
94 // should get rid of it immediately"). The event loop must be spun
95 // to get everything cleaned up correctly. It normally only takes
96 // one to two spins through the event loop to see a change.
98 // NOTE(shess): Under valgrind, -nextEventMatchingMask:* in one test
99 // needed to run twice, once taking .2 seconds, the next time .6
100 // seconds. The loop exit condition attempts to be scalable.
102 // Get the set of windows which weren't present when the test
104 std::set<NSWindow*> windows_left(WindowsLeft());
106 while (!windows_left.empty()) {
107 // Cover delayed actions by spinning the loop at least once after
109 const NSTimeInterval kCloseTimeoutSeconds =
110 TestTimeouts::action_timeout().InSecondsF();
112 // Cover chains of delayed actions by spinning the loop at least
114 const int kCloseSpins = 3;
116 // Track the set of remaining windows so that everything can be
117 // reset if progress is made.
118 std::set<NSWindow*> still_left = windows_left;
120 NSDate* start_date = [NSDate date];
121 bool one_more_time = true;
123 while (still_left.size() == windows_left.size() &&
124 (spins < kCloseSpins || one_more_time)) {
125 // Check the timeout before pumping events, so that we'll spin
126 // the loop once after the timeout.
128 ([start_date timeIntervalSinceNow] > -kCloseTimeoutSeconds);
130 // Autorelease anything thrown up by the event loop.
132 base::mac::ScopedNSAutoreleasePool pool;
134 NSEvent *next_event = [NSApp nextEventMatchingMask:NSAnyEventMask
136 inMode:NSDefaultRunLoopMode
138 [NSApp sendEvent:next_event];
139 [NSApp updateWindows];
142 // Refresh the outstanding windows.
143 still_left = WindowsLeft();
146 // If no progress is being made, log a failure and continue.
147 if (still_left.size() == windows_left.size()) {
148 // NOTE(shess): Failing this expectation means that the test
149 // opened windows which have not been fully released. Either
150 // there is a leak, or perhaps one of |kCloseTimeoutSeconds| or
151 // |kCloseSpins| needs adjustment.
152 EXPECT_EQ(0U, windows_left.size());
153 for (std::set<NSWindow*>::iterator iter = windows_left.begin();
154 iter != windows_left.end(); ++iter) {
155 const char* desc = [[*iter description] UTF8String];
156 LOG(WARNING) << "Didn't close window " << desc;
161 windows_left = still_left;
163 PlatformTest::TearDown();
166 std::set<NSWindow*> CocoaTest::ApplicationWindows() {
167 // This must NOT retain the windows it is returning.
168 std::set<NSWindow*> windows;
170 // Must create a pool here because [NSApp windows] has created an array
171 // with retains on all the windows in it.
172 base::mac::ScopedNSAutoreleasePool pool;
173 NSArray *appWindows = [NSApp windows];
174 for (NSWindow *window in appWindows) {
175 windows.insert(window);
180 std::set<NSWindow*> CocoaTest::WindowsLeft() {
181 const std::set<NSWindow*> windows(ApplicationWindows());
182 std::set<NSWindow*> windows_left;
183 std::set_difference(windows.begin(), windows.end(),
184 initial_windows_.begin(), initial_windows_.end(),
185 std::inserter(windows_left, windows_left.begin()));
189 CocoaTestHelperWindow* CocoaTest::test_window() {
191 test_window_ = [[CocoaTestHelperWindow alloc] init];
192 if (base::debug::BeingDebugged()) {
193 [test_window_ orderFront:nil];
195 [test_window_ orderBack:nil];