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/test/test_timeouts.h" 10 11 @implementation CocoaTestHelperWindow 12 13 - (id)initWithContentRect:(NSRect)contentRect { 14 return [self initWithContentRect:contentRect 15 styleMask:NSBorderlessWindowMask 16 backing:NSBackingStoreBuffered 17 defer:NO]; 18 } 19 20 - (id)init { 21 return [self initWithContentRect:NSMakeRect(0, 0, 800, 600)]; 22 } 23 24 - (void)dealloc { 25 // Just a good place to put breakpoints when having problems with 26 // unittests and CocoaTestHelperWindow. 27 [super dealloc]; 28 } 29 30 - (void)makePretendKeyWindowAndSetFirstResponder:(NSResponder*)responder { 31 EXPECT_TRUE([self makeFirstResponder:responder]); 32 [self setPretendIsKeyWindow:YES]; 33 } 34 35 - (void)clearPretendKeyWindowAndFirstResponder { 36 [self setPretendIsKeyWindow:NO]; 37 EXPECT_TRUE([self makeFirstResponder:NSApp]); 38 } 39 40 - (void)setPretendIsKeyWindow:(BOOL)flag { 41 pretendIsKeyWindow_ = flag; 42 } 43 44 - (BOOL)isKeyWindow { 45 return pretendIsKeyWindow_; 46 } 47 48 @end 49 50 namespace ui { 51 52 CocoaTest::CocoaTest() : called_tear_down_(false), test_window_(nil) { 53 Init(); 54 } 55 56 CocoaTest::~CocoaTest() { 57 // Must call CocoaTest's teardown from your overrides. 58 DCHECK(called_tear_down_); 59 } 60 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]; 65 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 69 // is ignored. 70 NSDictionary* dict = 71 [NSDictionary dictionaryWithObject:@"0.01" forKey:@"NSWindowResizeTime"]; 72 [[NSUserDefaults standardUserDefaults] registerDefaults:dict]; 73 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(); 78 } 79 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]; 84 [test_window_ close]; 85 test_window_ = nil; 86 87 // Recycle the pool to clean up any stuff that was put on the 88 // autorelease pool due to window or windowcontroller closures. 89 pool_.Recycle(); 90 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. 97 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. 101 102 // Get the set of windows which weren't present when the test 103 // started. 104 std::set<NSWindow*> windows_left(WindowsLeft()); 105 106 while (!windows_left.empty()) { 107 // Cover delayed actions by spinning the loop at least once after 108 // this timeout. 109 const NSTimeInterval kCloseTimeoutSeconds = 110 TestTimeouts::action_timeout().InSecondsF(); 111 112 // Cover chains of delayed actions by spinning the loop at least 113 // this many times. 114 const int kCloseSpins = 3; 115 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; 119 120 NSDate* start_date = [NSDate date]; 121 bool one_more_time = true; 122 int spins = 0; 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. 127 one_more_time = 128 ([start_date timeIntervalSinceNow] > -kCloseTimeoutSeconds); 129 130 // Autorelease anything thrown up by the event loop. 131 { 132 base::mac::ScopedNSAutoreleasePool pool; 133 ++spins; 134 NSEvent *next_event = [NSApp nextEventMatchingMask:NSAnyEventMask 135 untilDate:nil 136 inMode:NSDefaultRunLoopMode 137 dequeue:YES]; 138 [NSApp sendEvent:next_event]; 139 [NSApp updateWindows]; 140 } 141 142 // Refresh the outstanding windows. 143 still_left = WindowsLeft(); 144 } 145 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; 157 } 158 break; 159 } 160 161 windows_left = still_left; 162 } 163 PlatformTest::TearDown(); 164 } 165 166 std::set<NSWindow*> CocoaTest::ApplicationWindows() { 167 // This must NOT retain the windows it is returning. 168 std::set<NSWindow*> windows; 169 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); 176 } 177 return windows; 178 } 179 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())); 186 return windows_left; 187 } 188 189 CocoaTestHelperWindow* CocoaTest::test_window() { 190 if (!test_window_) { 191 test_window_ = [[CocoaTestHelperWindow alloc] init]; 192 if (base::debug::BeingDebugged()) { 193 [test_window_ orderFront:nil]; 194 } else { 195 [test_window_ orderBack:nil]; 196 } 197 } 198 return test_window_; 199 } 200 201 } // namespace ui 202