OLD | NEW |
---|---|
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ui/base/test/ui_controls.h" | 5 #include "ui/base/test/ui_controls.h" |
6 | 6 |
7 #import <Cocoa/Cocoa.h> | 7 #import <Cocoa/Cocoa.h> |
8 #include <vector> | 8 #include <vector> |
9 | 9 |
10 #include "base/bind.h" | 10 #include "base/bind.h" |
11 #include "base/callback.h" | 11 #include "base/callback.h" |
12 #import "base/mac/foundation_util.h" | |
13 #import "base/mac/scoped_objc_class_swizzler.h" | |
12 #include "base/message_loop/message_loop.h" | 14 #include "base/message_loop/message_loop.h" |
13 #include "ui/base/cocoa/cocoa_base_utils.h" | 15 #include "ui/base/cocoa/cocoa_base_utils.h" |
14 #include "ui/events/keycodes/keyboard_code_conversion_mac.h" | 16 #include "ui/events/keycodes/keyboard_code_conversion_mac.h" |
15 #import "ui/events/test/cocoa_test_event_utils.h" | 17 #import "ui/events/test/cocoa_test_event_utils.h" |
18 #include "ui/gfx/geometry/point.h" | |
19 #import "ui/gfx/mac/coordinate_conversion.h" | |
16 | 20 |
17 // Implementation details: We use [NSApplication sendEvent:] instead | 21 // Implementation details: We use [NSApplication sendEvent:] instead |
18 // of [NSApplication postEvent:atStart:] so that the event gets sent | 22 // of [NSApplication postEvent:atStart:] so that the event gets sent |
19 // immediately. This lets us run the post-event task right | 23 // immediately. This lets us run the post-event task right |
20 // immediately as well. Unfortunately I cannot subclass NSEvent (it's | 24 // immediately as well. Unfortunately I cannot subclass NSEvent (it's |
21 // probably a class cluster) to allow other easy answers. For | 25 // probably a class cluster) to allow other easy answers. For |
22 // example, if I could subclass NSEvent, I could run the Task in it's | 26 // example, if I could subclass NSEvent, I could run the Task in it's |
23 // dealloc routine (which necessarily happens after the event is | 27 // dealloc routine (which necessarily happens after the event is |
24 // dispatched). Unlike Linux, Mac does not have message loop | 28 // dispatched). Unlike Linux, Mac does not have message loop |
25 // observer/notification. Unlike windows, I cannot post non-events | 29 // observer/notification. Unlike windows, I cannot post non-events |
(...skipping 19 matching lines...) Expand all Loading... | |
45 | 49 |
46 using cocoa_test_event_utils::SynthesizeKeyEvent; | 50 using cocoa_test_event_utils::SynthesizeKeyEvent; |
47 using cocoa_test_event_utils::TimeIntervalSinceSystemStartup; | 51 using cocoa_test_event_utils::TimeIntervalSinceSystemStartup; |
48 | 52 |
49 namespace { | 53 namespace { |
50 | 54 |
51 // Stores the current mouse location on the screen. So that we can use it | 55 // Stores the current mouse location on the screen. So that we can use it |
52 // when firing keyboard and mouse click events. | 56 // when firing keyboard and mouse click events. |
53 NSPoint g_mouse_location = { 0, 0 }; | 57 NSPoint g_mouse_location = { 0, 0 }; |
54 | 58 |
59 // Stores the current pressed mouse buttons. Indexed by | |
60 // ui_controls::MouseButton. | |
61 bool g_mouse_button_down[3] = {false, false, false}; | |
62 | |
55 bool g_ui_controls_enabled = false; | 63 bool g_ui_controls_enabled = false; |
56 | 64 |
57 // Creates the proper sequence of autoreleased key events for a key down + up. | 65 // Creates the proper sequence of autoreleased key events for a key down + up. |
58 void SynthesizeKeyEventsSequence(NSWindow* window, | 66 void SynthesizeKeyEventsSequence(NSWindow* window, |
59 ui::KeyboardCode keycode, | 67 ui::KeyboardCode keycode, |
60 bool control, | 68 bool control, |
61 bool shift, | 69 bool shift, |
62 bool alt, | 70 bool alt, |
63 bool command, | 71 bool command, |
64 std::vector<NSEvent*>* events) { | 72 std::vector<NSEvent*>* events) { |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
118 flags &= ~NSControlKeyMask; | 126 flags &= ~NSControlKeyMask; |
119 event = SynthesizeKeyEvent(window, false, ui::VKEY_CONTROL, flags); | 127 event = SynthesizeKeyEvent(window, false, ui::VKEY_CONTROL, flags); |
120 DCHECK(event); | 128 DCHECK(event); |
121 events->push_back(event); | 129 events->push_back(event); |
122 } | 130 } |
123 } | 131 } |
124 | 132 |
125 // A helper function to watch for the event queue. The specific task will be | 133 // A helper function to watch for the event queue. The specific task will be |
126 // fired when there is no more event in the queue. | 134 // fired when there is no more event in the queue. |
127 void EventQueueWatcher(const base::Closure& task) { | 135 void EventQueueWatcher(const base::Closure& task) { |
136 DCHECK_EQ(dispatch_get_current_queue(), dispatch_get_main_queue()) | |
tapted
2016/06/01 11:29:55
This DCHECK can probably go since we're not using
themblsha
2016/06/03 17:42:08
Done.
| |
137 << "It should be run on the UI thread, as otherwise it will always " | |
138 "report there are no pending events"; | |
128 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask | 139 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask |
129 untilDate:nil | 140 untilDate:nil |
130 inMode:NSDefaultRunLoopMode | 141 inMode:NSDefaultRunLoopMode |
131 dequeue:NO]; | 142 dequeue:NO]; |
132 // If there is still event in the queue, then we need to check again. | 143 // If there is still event in the queue, then we need to check again. |
133 if (event) { | 144 if (event) { |
134 base::MessageLoop::current()->PostTask( | 145 base::MessageLoop::current()->PostTask( |
135 FROM_HERE, | 146 FROM_HERE, |
136 base::Bind(&EventQueueWatcher, task)); | 147 base::Bind(&EventQueueWatcher, task)); |
137 } else { | 148 } else { |
(...skipping 28 matching lines...) Expand all Loading... | |
166 return window; | 177 return window; |
167 } | 178 } |
168 | 179 |
169 // Note that -[NSApplication orderedWindows] won't include NSPanels. If a test | 180 // Note that -[NSApplication orderedWindows] won't include NSPanels. If a test |
170 // uses those, it will need to handle that itself. | 181 // uses those, it will need to handle that itself. |
171 return nil; | 182 return nil; |
172 } | 183 } |
173 | 184 |
174 } // namespace | 185 } // namespace |
175 | 186 |
187 // Donates testing implementations of NSEvent methods. | |
188 @interface FakeNSEventTestingDonor : NSObject | |
189 @end | |
190 | |
191 @implementation FakeNSEventTestingDonor | |
192 + (NSPoint)mouseLocation { | |
193 return g_mouse_location; | |
194 } | |
195 | |
196 + (NSUInteger)pressedMouseButtons { | |
197 NSUInteger result = 0; | |
198 const int buttons[3] = { | |
199 ui_controls::LEFT, ui_controls::RIGHT, ui_controls::MIDDLE}; | |
200 for (unsigned int i = 0; i < arraysize(buttons); ++i) { | |
tapted
2016/06/01 11:29:55
nit: unsigned int -> size_t (or just `int`, but th
themblsha
2016/06/03 17:42:08
Done.
| |
201 if (g_mouse_button_down[buttons[i]]) | |
202 result |= (1 << i); | |
203 } | |
204 return result; | |
205 } | |
206 @end | |
207 | |
208 // Donates testing implementations of NSWindow methods. | |
209 @interface FakeNSWindowTestingDonor : NSObject | |
210 @end | |
211 | |
212 @implementation FakeNSWindowTestingDonor | |
213 - (NSPoint)mouseLocationOutsideOfEventStream { | |
tapted
2016/06/01 11:29:55
Is anything being tested that requires this? (remo
themblsha
2016/06/03 17:42:08
It's widely used in Chromium, and without this swi
tapted
2016/06/06 07:12:36
`git grep mouseLocationOutsideOfEventStream` shows
themblsha
2016/06/06 17:20:27
Done.
| |
214 NSWindow* window = base::mac::ObjCCastStrict<NSWindow>(self); | |
215 return ui::ConvertPointFromWindowToScreen(window, g_mouse_location); | |
216 } | |
217 @end | |
218 | |
219 namespace { | |
tapted
2016/06/01 11:29:55
nit: blank line after
themblsha
2016/06/03 17:42:08
Done.
| |
220 class NSEventSwizzler { | |
tapted
2016/06/01 11:29:55
needs a comment
themblsha
2016/06/03 17:42:08
Done.
| |
221 public: | |
222 static void Install() { | |
223 static NSEventSwizzler* swizzler = nullptr; | |
tapted
2016/06/01 11:29:55
I'm unsure about this -- the swizzler is "Scoped"
themblsha
2016/06/03 17:42:08
In tests the entire browser state gets reconstruct
tapted
2016/06/06 07:12:36
Not always -- there is `--single-process-tests` wh
themblsha
2016/06/06 17:20:27
If it's only installed in EnableUIControls() and n
| |
224 if (!swizzler) { | |
225 swizzler = new NSEventSwizzler(); | |
226 } | |
227 } | |
228 | |
229 protected: | |
230 NSEventSwizzler() | |
231 : mouse_location_swizzler_(new base::mac::ScopedObjCClassSwizzler( | |
232 [NSEvent class], | |
233 [FakeNSEventTestingDonor class], | |
234 @selector(mouseLocation))), | |
235 pressed_mouse_buttons_swizzler_(new base::mac::ScopedObjCClassSwizzler( | |
236 [NSEvent class], | |
237 [FakeNSEventTestingDonor class], | |
238 @selector(pressedMouseButtons))), | |
239 mouse_location_outside_of_event_stream_swizzler_( | |
240 new base::mac::ScopedObjCClassSwizzler( | |
241 [NSWindow class], | |
242 [FakeNSWindowTestingDonor class], | |
243 @selector(mouseLocationOutsideOfEventStream))) {} | |
244 | |
245 private: | |
246 std::unique_ptr<base::mac::ScopedObjCClassSwizzler> mouse_location_swizzler_; | |
247 std::unique_ptr<base::mac::ScopedObjCClassSwizzler> | |
248 pressed_mouse_buttons_swizzler_; | |
249 std::unique_ptr<base::mac::ScopedObjCClassSwizzler> | |
250 mouse_location_outside_of_event_stream_swizzler_; | |
251 }; | |
tapted
2016/06/01 11:29:55
nit: DISALLOW_COPY_AND_ASSIGN(..)
themblsha
2016/06/03 17:42:08
Done.
| |
252 } // namespace | |
tapted
2016/06/01 11:29:55
nit: blank line before
themblsha
2016/06/03 17:42:08
Done.
| |
253 | |
176 namespace ui_controls { | 254 namespace ui_controls { |
177 | 255 |
178 void EnableUIControls() { | 256 void EnableUIControls() { |
179 g_ui_controls_enabled = true; | 257 g_ui_controls_enabled = true; |
180 } | 258 } |
181 | 259 |
182 bool IsUIControlsEnabled() { | 260 bool IsUIControlsEnabled() { |
183 return g_ui_controls_enabled; | 261 return g_ui_controls_enabled; |
184 } | 262 } |
185 | 263 |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
232 CHECK(g_ui_controls_enabled); | 310 CHECK(g_ui_controls_enabled); |
233 return SendMouseMoveNotifyWhenDone(x, y, base::Closure()); | 311 return SendMouseMoveNotifyWhenDone(x, y, base::Closure()); |
234 } | 312 } |
235 | 313 |
236 // Input position is in screen coordinates. However, NSMouseMoved | 314 // Input position is in screen coordinates. However, NSMouseMoved |
237 // events require them window-relative, so we adjust. We *DO* flip | 315 // events require them window-relative, so we adjust. We *DO* flip |
238 // the coordinate space, so input events can be the same for all | 316 // the coordinate space, so input events can be the same for all |
239 // platforms. E.g. (0,0) is upper-left. | 317 // platforms. E.g. (0,0) is upper-left. |
240 bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) { | 318 bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) { |
241 CHECK(g_ui_controls_enabled); | 319 CHECK(g_ui_controls_enabled); |
242 CGFloat screenHeight = | 320 g_mouse_location = gfx::ScreenPointToNSPoint(gfx::Point(x, y)); // flip! |
243 [[[NSScreen screens] firstObject] frame].size.height; | 321 NSEventSwizzler::Install(); |
244 g_mouse_location = NSMakePoint(x, screenHeight - y); // flip! | |
245 | 322 |
246 NSWindow* window = WindowAtCurrentMouseLocation(); | 323 NSWindow* window = WindowAtCurrentMouseLocation(); |
247 | 324 |
248 NSPoint pointInWindow = g_mouse_location; | 325 NSPoint pointInWindow = g_mouse_location; |
249 if (window) | 326 if (window) |
250 pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); | 327 pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); |
251 NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); | 328 NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); |
252 | 329 |
330 NSEventType event_type = NSMouseMoved; | |
331 if (g_mouse_button_down[LEFT]) { | |
332 event_type = NSLeftMouseDragged; | |
333 } else if (g_mouse_button_down[RIGHT]) { | |
334 event_type = NSRightMouseDragged; | |
335 } else if (g_mouse_button_down[MIDDLE]) { | |
336 event_type = NSOtherMouseDragged; | |
337 } | |
338 | |
253 NSEvent* event = | 339 NSEvent* event = |
254 [NSEvent mouseEventWithType:NSMouseMoved | 340 [NSEvent mouseEventWithType:event_type |
255 location:pointInWindow | 341 location:pointInWindow |
256 modifierFlags:0 | 342 modifierFlags:0 |
257 timestamp:timestamp | 343 timestamp:timestamp |
258 windowNumber:[window windowNumber] | 344 windowNumber:[window windowNumber] |
259 context:nil | 345 context:nil |
260 eventNumber:0 | 346 eventNumber:0 |
261 clickCount:0 | 347 clickCount:(event_type == NSMouseMoved ? 0 : 1) |
tapted
2016/06/01 11:29:55
outer () parens not required, same below
themblsha
2016/06/03 17:42:08
Done. Although there are parens in SendMouseEvents
| |
262 pressure:0.0]; | 348 pressure:(event_type == NSMouseMoved ? 0.0 : 1.0)]; |
263 [[NSApplication sharedApplication] postEvent:event atStart:NO]; | 349 [[NSApplication sharedApplication] postEvent:event atStart:NO]; |
264 | 350 |
265 if (!task.is_null()) { | 351 if (!task.is_null()) { |
266 base::MessageLoop::current()->PostTask( | 352 base::MessageLoop::current()->PostTask( |
267 FROM_HERE, base::Bind(&EventQueueWatcher, task)); | 353 FROM_HERE, base::Bind(&EventQueueWatcher, task)); |
268 } | 354 } |
269 | 355 |
270 return true; | 356 return true; |
271 } | 357 } |
272 | 358 |
273 bool SendMouseEvents(MouseButton type, int state) { | 359 bool SendMouseEvents(MouseButton type, int state) { |
274 CHECK(g_ui_controls_enabled); | 360 CHECK(g_ui_controls_enabled); |
275 return SendMouseEventsNotifyWhenDone(type, state, base::Closure()); | 361 return SendMouseEventsNotifyWhenDone(type, state, base::Closure()); |
276 } | 362 } |
277 | 363 |
278 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, | 364 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, |
279 const base::Closure& task) { | 365 const base::Closure& task) { |
280 CHECK(g_ui_controls_enabled); | 366 CHECK(g_ui_controls_enabled); |
281 // On windows it appears state can be (UP|DOWN). It is unclear if | 367 // On windows it appears state can be (UP|DOWN). It is unclear if |
282 // that'll happen here but prepare for it just in case. | 368 // that'll happen here but prepare for it just in case. |
283 if (state == (UP|DOWN)) { | 369 if (state == (UP|DOWN)) { |
284 return (SendMouseEventsNotifyWhenDone(type, DOWN, base::Closure()) && | 370 return (SendMouseEventsNotifyWhenDone(type, DOWN, base::Closure()) && |
285 SendMouseEventsNotifyWhenDone(type, UP, task)); | 371 SendMouseEventsNotifyWhenDone(type, UP, task)); |
286 } | 372 } |
287 NSEventType etype = NSLeftMouseDown; | 373 NSEventType event_type = NSLeftMouseDown; |
288 if (type == LEFT) { | 374 if (type == LEFT) { |
289 if (state == UP) { | 375 if (state == UP) { |
290 etype = NSLeftMouseUp; | 376 event_type = NSLeftMouseUp; |
291 } else { | 377 } else { |
292 etype = NSLeftMouseDown; | 378 event_type = NSLeftMouseDown; |
293 } | 379 } |
294 } else if (type == MIDDLE) { | 380 } else if (type == MIDDLE) { |
295 if (state == UP) { | 381 if (state == UP) { |
296 etype = NSOtherMouseUp; | 382 event_type = NSOtherMouseUp; |
297 } else { | 383 } else { |
298 etype = NSOtherMouseDown; | 384 event_type = NSOtherMouseDown; |
299 } | 385 } |
300 } else if (type == RIGHT) { | 386 } else if (type == RIGHT) { |
301 if (state == UP) { | 387 if (state == UP) { |
302 etype = NSRightMouseUp; | 388 event_type = NSRightMouseUp; |
303 } else { | 389 } else { |
304 etype = NSRightMouseDown; | 390 event_type = NSRightMouseDown; |
305 } | 391 } |
306 } else { | 392 } else { |
393 NOTREACHED(); | |
307 return false; | 394 return false; |
308 } | 395 } |
396 g_mouse_button_down[type] = state == DOWN; | |
397 | |
309 NSWindow* window = WindowAtCurrentMouseLocation(); | 398 NSWindow* window = WindowAtCurrentMouseLocation(); |
310 NSPoint pointInWindow = g_mouse_location; | 399 NSPoint pointInWindow = g_mouse_location; |
311 if (window) | 400 if (window) |
312 pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); | 401 pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); |
313 | 402 |
403 NSEventSwizzler::Install(); | |
404 | |
314 NSEvent* event = | 405 NSEvent* event = |
315 [NSEvent mouseEventWithType:etype | 406 [NSEvent mouseEventWithType:event_type |
316 location:pointInWindow | 407 location:pointInWindow |
317 modifierFlags:0 | 408 modifierFlags:0 |
318 timestamp:TimeIntervalSinceSystemStartup() | 409 timestamp:TimeIntervalSinceSystemStartup() |
319 windowNumber:[window windowNumber] | 410 windowNumber:[window windowNumber] |
320 context:nil | 411 context:nil |
321 eventNumber:0 | 412 eventNumber:0 |
322 clickCount:1 | 413 clickCount:1 |
323 pressure:(state == DOWN ? 1.0 : 0.0 )]; | 414 pressure:(state == DOWN ? 1.0 : 0.0 )]; |
324 [[NSApplication sharedApplication] postEvent:event atStart:NO]; | 415 [[NSApplication sharedApplication] postEvent:event atStart:NO]; |
325 | 416 |
(...skipping 13 matching lines...) Expand all Loading... | |
339 void RunClosureAfterAllPendingUIEvents(const base::Closure& closure) { | 430 void RunClosureAfterAllPendingUIEvents(const base::Closure& closure) { |
340 base::MessageLoop::current()->PostTask( | 431 base::MessageLoop::current()->PostTask( |
341 FROM_HERE, base::Bind(&EventQueueWatcher, closure)); | 432 FROM_HERE, base::Bind(&EventQueueWatcher, closure)); |
342 } | 433 } |
343 | 434 |
344 bool IsFullKeyboardAccessEnabled() { | 435 bool IsFullKeyboardAccessEnabled() { |
345 return [NSApp isFullKeyboardAccessEnabled]; | 436 return [NSApp isFullKeyboardAccessEnabled]; |
346 } | 437 } |
347 | 438 |
348 } // namespace ui_controls | 439 } // namespace ui_controls |
OLD | NEW |