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 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
166 return window; | 174 return window; |
167 } | 175 } |
168 | 176 |
169 // Note that -[NSApplication orderedWindows] won't include NSPanels. If a test | 177 // Note that -[NSApplication orderedWindows] won't include NSPanels. If a test |
170 // uses those, it will need to handle that itself. | 178 // uses those, it will need to handle that itself. |
171 return nil; | 179 return nil; |
172 } | 180 } |
173 | 181 |
174 } // namespace | 182 } // namespace |
175 | 183 |
| 184 // Donates testing implementations of NSEvent methods. |
| 185 @interface FakeNSEventTestingDonor : NSObject |
| 186 @end |
| 187 |
| 188 @implementation FakeNSEventTestingDonor |
| 189 + (NSPoint)mouseLocation { |
| 190 return g_mouse_location; |
| 191 } |
| 192 |
| 193 + (NSUInteger)pressedMouseButtons { |
| 194 NSUInteger result = 0; |
| 195 const int buttons[3] = { |
| 196 ui_controls::LEFT, ui_controls::RIGHT, ui_controls::MIDDLE}; |
| 197 for (size_t i = 0; i < arraysize(buttons); ++i) { |
| 198 if (g_mouse_button_down[buttons[i]]) |
| 199 result |= (1 << i); |
| 200 } |
| 201 return result; |
| 202 } |
| 203 @end |
| 204 |
| 205 namespace { |
| 206 |
| 207 // Swizzles several Cocoa functions that are used to directly get mouse state, |
| 208 // so that they will return the current simulated mouse position and pressed |
| 209 // mouse buttons. |
| 210 class MockNSEventClassMethods { |
| 211 public: |
| 212 static void Init() { |
| 213 static MockNSEventClassMethods* swizzler = nullptr; |
| 214 if (!swizzler) { |
| 215 swizzler = new MockNSEventClassMethods(); |
| 216 } |
| 217 } |
| 218 |
| 219 private: |
| 220 MockNSEventClassMethods() |
| 221 : mouse_location_swizzler_([NSEvent class], |
| 222 [FakeNSEventTestingDonor class], |
| 223 @selector(mouseLocation)), |
| 224 pressed_mouse_buttons_swizzler_([NSEvent class], |
| 225 [FakeNSEventTestingDonor class], |
| 226 @selector(pressedMouseButtons)) {} |
| 227 |
| 228 base::mac::ScopedObjCClassSwizzler mouse_location_swizzler_; |
| 229 base::mac::ScopedObjCClassSwizzler pressed_mouse_buttons_swizzler_; |
| 230 |
| 231 DISALLOW_COPY_AND_ASSIGN(MockNSEventClassMethods); |
| 232 }; |
| 233 |
| 234 } // namespace |
| 235 |
176 namespace ui_controls { | 236 namespace ui_controls { |
177 | 237 |
178 void EnableUIControls() { | 238 void EnableUIControls() { |
179 g_ui_controls_enabled = true; | 239 g_ui_controls_enabled = true; |
| 240 MockNSEventClassMethods::Init(); |
180 } | 241 } |
181 | 242 |
182 bool IsUIControlsEnabled() { | 243 bool IsUIControlsEnabled() { |
183 return g_ui_controls_enabled; | 244 return g_ui_controls_enabled; |
184 } | 245 } |
185 | 246 |
186 bool SendKeyPress(gfx::NativeWindow window, | 247 bool SendKeyPress(gfx::NativeWindow window, |
187 ui::KeyboardCode key, | 248 ui::KeyboardCode key, |
188 bool control, | 249 bool control, |
189 bool shift, | 250 bool shift, |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
232 CHECK(g_ui_controls_enabled); | 293 CHECK(g_ui_controls_enabled); |
233 return SendMouseMoveNotifyWhenDone(x, y, base::Closure()); | 294 return SendMouseMoveNotifyWhenDone(x, y, base::Closure()); |
234 } | 295 } |
235 | 296 |
236 // Input position is in screen coordinates. However, NSMouseMoved | 297 // Input position is in screen coordinates. However, NSMouseMoved |
237 // events require them window-relative, so we adjust. We *DO* flip | 298 // events require them window-relative, so we adjust. We *DO* flip |
238 // the coordinate space, so input events can be the same for all | 299 // the coordinate space, so input events can be the same for all |
239 // platforms. E.g. (0,0) is upper-left. | 300 // platforms. E.g. (0,0) is upper-left. |
240 bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) { | 301 bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) { |
241 CHECK(g_ui_controls_enabled); | 302 CHECK(g_ui_controls_enabled); |
242 CGFloat screenHeight = | 303 g_mouse_location = gfx::ScreenPointToNSPoint(gfx::Point(x, y)); // flip! |
243 [[[NSScreen screens] firstObject] frame].size.height; | |
244 g_mouse_location = NSMakePoint(x, screenHeight - y); // flip! | |
245 | 304 |
246 NSWindow* window = WindowAtCurrentMouseLocation(); | 305 NSWindow* window = WindowAtCurrentMouseLocation(); |
247 | 306 |
248 NSPoint pointInWindow = g_mouse_location; | 307 NSPoint pointInWindow = g_mouse_location; |
249 if (window) | 308 if (window) |
250 pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); | 309 pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); |
251 NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); | 310 NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); |
252 | 311 |
| 312 NSEventType event_type = NSMouseMoved; |
| 313 if (g_mouse_button_down[LEFT]) { |
| 314 event_type = NSLeftMouseDragged; |
| 315 } else if (g_mouse_button_down[RIGHT]) { |
| 316 event_type = NSRightMouseDragged; |
| 317 } else if (g_mouse_button_down[MIDDLE]) { |
| 318 event_type = NSOtherMouseDragged; |
| 319 } |
| 320 |
253 NSEvent* event = | 321 NSEvent* event = |
254 [NSEvent mouseEventWithType:NSMouseMoved | 322 [NSEvent mouseEventWithType:event_type |
255 location:pointInWindow | 323 location:pointInWindow |
256 modifierFlags:0 | 324 modifierFlags:0 |
257 timestamp:timestamp | 325 timestamp:timestamp |
258 windowNumber:[window windowNumber] | 326 windowNumber:[window windowNumber] |
259 context:nil | 327 context:nil |
260 eventNumber:0 | 328 eventNumber:0 |
261 clickCount:0 | 329 clickCount:event_type == NSMouseMoved ? 0 : 1 |
262 pressure:0.0]; | 330 pressure:event_type == NSMouseMoved ? 0.0 : 1.0]; |
263 [[NSApplication sharedApplication] postEvent:event atStart:NO]; | 331 [[NSApplication sharedApplication] postEvent:event atStart:NO]; |
264 | 332 |
265 if (!task.is_null()) { | 333 if (!task.is_null()) { |
266 base::MessageLoop::current()->PostTask( | 334 base::MessageLoop::current()->PostTask( |
267 FROM_HERE, base::Bind(&EventQueueWatcher, task)); | 335 FROM_HERE, base::Bind(&EventQueueWatcher, task)); |
268 } | 336 } |
269 | 337 |
270 return true; | 338 return true; |
271 } | 339 } |
272 | 340 |
273 bool SendMouseEvents(MouseButton type, int state) { | 341 bool SendMouseEvents(MouseButton type, int state) { |
274 CHECK(g_ui_controls_enabled); | 342 CHECK(g_ui_controls_enabled); |
275 return SendMouseEventsNotifyWhenDone(type, state, base::Closure()); | 343 return SendMouseEventsNotifyWhenDone(type, state, base::Closure()); |
276 } | 344 } |
277 | 345 |
278 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, | 346 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, |
279 const base::Closure& task) { | 347 const base::Closure& task) { |
280 CHECK(g_ui_controls_enabled); | 348 CHECK(g_ui_controls_enabled); |
281 // On windows it appears state can be (UP|DOWN). It is unclear if | 349 // 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. | 350 // that'll happen here but prepare for it just in case. |
283 if (state == (UP|DOWN)) { | 351 if (state == (UP|DOWN)) { |
284 return (SendMouseEventsNotifyWhenDone(type, DOWN, base::Closure()) && | 352 return (SendMouseEventsNotifyWhenDone(type, DOWN, base::Closure()) && |
285 SendMouseEventsNotifyWhenDone(type, UP, task)); | 353 SendMouseEventsNotifyWhenDone(type, UP, task)); |
286 } | 354 } |
287 NSEventType etype = NSLeftMouseDown; | 355 NSEventType event_type = NSLeftMouseDown; |
288 if (type == LEFT) { | 356 if (type == LEFT) { |
289 if (state == UP) { | 357 if (state == UP) { |
290 etype = NSLeftMouseUp; | 358 event_type = NSLeftMouseUp; |
291 } else { | 359 } else { |
292 etype = NSLeftMouseDown; | 360 event_type = NSLeftMouseDown; |
293 } | 361 } |
294 } else if (type == MIDDLE) { | 362 } else if (type == MIDDLE) { |
295 if (state == UP) { | 363 if (state == UP) { |
296 etype = NSOtherMouseUp; | 364 event_type = NSOtherMouseUp; |
297 } else { | 365 } else { |
298 etype = NSOtherMouseDown; | 366 event_type = NSOtherMouseDown; |
299 } | 367 } |
300 } else if (type == RIGHT) { | 368 } else if (type == RIGHT) { |
301 if (state == UP) { | 369 if (state == UP) { |
302 etype = NSRightMouseUp; | 370 event_type = NSRightMouseUp; |
303 } else { | 371 } else { |
304 etype = NSRightMouseDown; | 372 event_type = NSRightMouseDown; |
305 } | 373 } |
306 } else { | 374 } else { |
| 375 NOTREACHED(); |
307 return false; | 376 return false; |
308 } | 377 } |
| 378 g_mouse_button_down[type] = state == DOWN; |
| 379 |
309 NSWindow* window = WindowAtCurrentMouseLocation(); | 380 NSWindow* window = WindowAtCurrentMouseLocation(); |
310 NSPoint pointInWindow = g_mouse_location; | 381 NSPoint pointInWindow = g_mouse_location; |
311 if (window) | 382 if (window) |
312 pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); | 383 pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); |
313 | 384 |
314 NSEvent* event = | 385 NSEvent* event = |
315 [NSEvent mouseEventWithType:etype | 386 [NSEvent mouseEventWithType:event_type |
316 location:pointInWindow | 387 location:pointInWindow |
317 modifierFlags:0 | 388 modifierFlags:0 |
318 timestamp:TimeIntervalSinceSystemStartup() | 389 timestamp:TimeIntervalSinceSystemStartup() |
319 windowNumber:[window windowNumber] | 390 windowNumber:[window windowNumber] |
320 context:nil | 391 context:nil |
321 eventNumber:0 | 392 eventNumber:0 |
322 clickCount:1 | 393 clickCount:1 |
323 pressure:(state == DOWN ? 1.0 : 0.0 )]; | 394 pressure:state == DOWN ? 1.0 : 0.0]; |
324 [[NSApplication sharedApplication] postEvent:event atStart:NO]; | 395 [[NSApplication sharedApplication] postEvent:event atStart:NO]; |
325 | 396 |
326 if (!task.is_null()) { | 397 if (!task.is_null()) { |
327 base::MessageLoop::current()->PostTask( | 398 base::MessageLoop::current()->PostTask( |
328 FROM_HERE, base::Bind(&EventQueueWatcher, task)); | 399 FROM_HERE, base::Bind(&EventQueueWatcher, task)); |
329 } | 400 } |
330 | 401 |
331 return true; | 402 return true; |
332 } | 403 } |
333 | 404 |
334 bool SendMouseClick(MouseButton type) { | 405 bool SendMouseClick(MouseButton type) { |
335 CHECK(g_ui_controls_enabled); | 406 CHECK(g_ui_controls_enabled); |
336 return SendMouseEventsNotifyWhenDone(type, UP|DOWN, base::Closure()); | 407 return SendMouseEventsNotifyWhenDone(type, UP|DOWN, base::Closure()); |
337 } | 408 } |
338 | 409 |
339 void RunClosureAfterAllPendingUIEvents(const base::Closure& closure) { | 410 void RunClosureAfterAllPendingUIEvents(const base::Closure& closure) { |
340 base::MessageLoop::current()->PostTask( | 411 base::MessageLoop::current()->PostTask( |
341 FROM_HERE, base::Bind(&EventQueueWatcher, closure)); | 412 FROM_HERE, base::Bind(&EventQueueWatcher, closure)); |
342 } | 413 } |
343 | 414 |
344 bool IsFullKeyboardAccessEnabled() { | 415 bool IsFullKeyboardAccessEnabled() { |
345 return [NSApp isFullKeyboardAccessEnabled]; | 416 return [NSApp isFullKeyboardAccessEnabled]; |
346 } | 417 } |
347 | 418 |
348 } // namespace ui_controls | 419 } // namespace ui_controls |
OLD | NEW |