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