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