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