• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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