Chromium Code Reviews| 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 protected: | |
|
tapted
2016/06/07 00:49:14
nit: private: (and remove private: below)
themblsha
2016/06/07 12:11:11
Done.
| |
| 220 MockNSEventClassMethods() | |
| 221 : mouse_location_swizzler_(new base::mac::ScopedObjCClassSwizzler( | |
| 222 [NSEvent class], | |
| 223 [FakeNSEventTestingDonor class], | |
| 224 @selector(mouseLocation))), | |
| 225 pressed_mouse_buttons_swizzler_(new base::mac::ScopedObjCClassSwizzler( | |
| 226 [NSEvent class], | |
| 227 [FakeNSEventTestingDonor class], | |
| 228 @selector(pressedMouseButtons))) {} | |
| 229 | |
| 230 private: | |
| 231 std::unique_ptr<base::mac::ScopedObjCClassSwizzler> mouse_location_swizzler_; | |
|
tapted
2016/06/07 00:49:14
Ah, sorry - these should be regular members, not u
themblsha
2016/06/07 12:11:11
Done.
| |
| 232 std::unique_ptr<base::mac::ScopedObjCClassSwizzler> | |
| 233 pressed_mouse_buttons_swizzler_; | |
| 234 | |
| 235 DISALLOW_COPY_AND_ASSIGN(MockNSEventClassMethods); | |
| 236 }; | |
| 237 | |
| 238 } // namespace | |
| 239 | |
| 176 namespace ui_controls { | 240 namespace ui_controls { |
| 177 | 241 |
| 178 void EnableUIControls() { | 242 void EnableUIControls() { |
| 179 g_ui_controls_enabled = true; | 243 g_ui_controls_enabled = true; |
| 244 MockNSEventClassMethods::Init(); | |
| 180 } | 245 } |
| 181 | 246 |
| 182 bool IsUIControlsEnabled() { | 247 bool IsUIControlsEnabled() { |
| 183 return g_ui_controls_enabled; | 248 return g_ui_controls_enabled; |
| 184 } | 249 } |
| 185 | 250 |
| 186 bool SendKeyPress(gfx::NativeWindow window, | 251 bool SendKeyPress(gfx::NativeWindow window, |
| 187 ui::KeyboardCode key, | 252 ui::KeyboardCode key, |
| 188 bool control, | 253 bool control, |
| 189 bool shift, | 254 bool shift, |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 232 CHECK(g_ui_controls_enabled); | 297 CHECK(g_ui_controls_enabled); |
| 233 return SendMouseMoveNotifyWhenDone(x, y, base::Closure()); | 298 return SendMouseMoveNotifyWhenDone(x, y, base::Closure()); |
| 234 } | 299 } |
| 235 | 300 |
| 236 // Input position is in screen coordinates. However, NSMouseMoved | 301 // Input position is in screen coordinates. However, NSMouseMoved |
| 237 // events require them window-relative, so we adjust. We *DO* flip | 302 // events require them window-relative, so we adjust. We *DO* flip |
| 238 // the coordinate space, so input events can be the same for all | 303 // the coordinate space, so input events can be the same for all |
| 239 // platforms. E.g. (0,0) is upper-left. | 304 // platforms. E.g. (0,0) is upper-left. |
| 240 bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) { | 305 bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) { |
| 241 CHECK(g_ui_controls_enabled); | 306 CHECK(g_ui_controls_enabled); |
| 242 CGFloat screenHeight = | 307 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 | 308 |
| 246 NSWindow* window = WindowAtCurrentMouseLocation(); | 309 NSWindow* window = WindowAtCurrentMouseLocation(); |
| 247 | 310 |
| 248 NSPoint pointInWindow = g_mouse_location; | 311 NSPoint pointInWindow = g_mouse_location; |
| 249 if (window) | 312 if (window) |
| 250 pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); | 313 pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); |
| 251 NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); | 314 NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); |
| 252 | 315 |
| 316 NSEventType event_type = NSMouseMoved; | |
| 317 if (g_mouse_button_down[LEFT]) { | |
| 318 event_type = NSLeftMouseDragged; | |
| 319 } else if (g_mouse_button_down[RIGHT]) { | |
| 320 event_type = NSRightMouseDragged; | |
| 321 } else if (g_mouse_button_down[MIDDLE]) { | |
| 322 event_type = NSOtherMouseDragged; | |
| 323 } | |
| 324 | |
| 253 NSEvent* event = | 325 NSEvent* event = |
| 254 [NSEvent mouseEventWithType:NSMouseMoved | 326 [NSEvent mouseEventWithType:event_type |
| 255 location:pointInWindow | 327 location:pointInWindow |
| 256 modifierFlags:0 | 328 modifierFlags:0 |
| 257 timestamp:timestamp | 329 timestamp:timestamp |
| 258 windowNumber:[window windowNumber] | 330 windowNumber:[window windowNumber] |
| 259 context:nil | 331 context:nil |
| 260 eventNumber:0 | 332 eventNumber:0 |
| 261 clickCount:0 | 333 clickCount:event_type == NSMouseMoved ? 0 : 1 |
| 262 pressure:0.0]; | 334 pressure:event_type == NSMouseMoved ? 0.0 : 1.0]; |
| 263 [[NSApplication sharedApplication] postEvent:event atStart:NO]; | 335 [[NSApplication sharedApplication] postEvent:event atStart:NO]; |
| 264 | 336 |
| 265 if (!task.is_null()) { | 337 if (!task.is_null()) { |
| 266 base::MessageLoop::current()->PostTask( | 338 base::MessageLoop::current()->PostTask( |
| 267 FROM_HERE, base::Bind(&EventQueueWatcher, task)); | 339 FROM_HERE, base::Bind(&EventQueueWatcher, task)); |
| 268 } | 340 } |
| 269 | 341 |
| 270 return true; | 342 return true; |
| 271 } | 343 } |
| 272 | 344 |
| 273 bool SendMouseEvents(MouseButton type, int state) { | 345 bool SendMouseEvents(MouseButton type, int state) { |
| 274 CHECK(g_ui_controls_enabled); | 346 CHECK(g_ui_controls_enabled); |
| 275 return SendMouseEventsNotifyWhenDone(type, state, base::Closure()); | 347 return SendMouseEventsNotifyWhenDone(type, state, base::Closure()); |
| 276 } | 348 } |
| 277 | 349 |
| 278 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, | 350 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, |
| 279 const base::Closure& task) { | 351 const base::Closure& task) { |
| 280 CHECK(g_ui_controls_enabled); | 352 CHECK(g_ui_controls_enabled); |
| 281 // On windows it appears state can be (UP|DOWN). It is unclear if | 353 // 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. | 354 // that'll happen here but prepare for it just in case. |
| 283 if (state == (UP|DOWN)) { | 355 if (state == (UP|DOWN)) { |
| 284 return (SendMouseEventsNotifyWhenDone(type, DOWN, base::Closure()) && | 356 return (SendMouseEventsNotifyWhenDone(type, DOWN, base::Closure()) && |
| 285 SendMouseEventsNotifyWhenDone(type, UP, task)); | 357 SendMouseEventsNotifyWhenDone(type, UP, task)); |
| 286 } | 358 } |
| 287 NSEventType etype = NSLeftMouseDown; | 359 NSEventType event_type = NSLeftMouseDown; |
| 288 if (type == LEFT) { | 360 if (type == LEFT) { |
| 289 if (state == UP) { | 361 if (state == UP) { |
| 290 etype = NSLeftMouseUp; | 362 event_type = NSLeftMouseUp; |
| 291 } else { | 363 } else { |
| 292 etype = NSLeftMouseDown; | 364 event_type = NSLeftMouseDown; |
| 293 } | 365 } |
| 294 } else if (type == MIDDLE) { | 366 } else if (type == MIDDLE) { |
| 295 if (state == UP) { | 367 if (state == UP) { |
| 296 etype = NSOtherMouseUp; | 368 event_type = NSOtherMouseUp; |
| 297 } else { | 369 } else { |
| 298 etype = NSOtherMouseDown; | 370 event_type = NSOtherMouseDown; |
| 299 } | 371 } |
| 300 } else if (type == RIGHT) { | 372 } else if (type == RIGHT) { |
| 301 if (state == UP) { | 373 if (state == UP) { |
| 302 etype = NSRightMouseUp; | 374 event_type = NSRightMouseUp; |
| 303 } else { | 375 } else { |
| 304 etype = NSRightMouseDown; | 376 event_type = NSRightMouseDown; |
| 305 } | 377 } |
| 306 } else { | 378 } else { |
| 379 NOTREACHED(); | |
| 307 return false; | 380 return false; |
| 308 } | 381 } |
| 382 g_mouse_button_down[type] = state == DOWN; | |
| 383 | |
| 309 NSWindow* window = WindowAtCurrentMouseLocation(); | 384 NSWindow* window = WindowAtCurrentMouseLocation(); |
| 310 NSPoint pointInWindow = g_mouse_location; | 385 NSPoint pointInWindow = g_mouse_location; |
| 311 if (window) | 386 if (window) |
| 312 pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); | 387 pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); |
| 313 | 388 |
| 314 NSEvent* event = | 389 NSEvent* event = |
| 315 [NSEvent mouseEventWithType:etype | 390 [NSEvent mouseEventWithType:event_type |
| 316 location:pointInWindow | 391 location:pointInWindow |
| 317 modifierFlags:0 | 392 modifierFlags:0 |
| 318 timestamp:TimeIntervalSinceSystemStartup() | 393 timestamp:TimeIntervalSinceSystemStartup() |
| 319 windowNumber:[window windowNumber] | 394 windowNumber:[window windowNumber] |
| 320 context:nil | 395 context:nil |
| 321 eventNumber:0 | 396 eventNumber:0 |
| 322 clickCount:1 | 397 clickCount:1 |
| 323 pressure:(state == DOWN ? 1.0 : 0.0 )]; | 398 pressure:state == DOWN ? 1.0 : 0.0]; |
| 324 [[NSApplication sharedApplication] postEvent:event atStart:NO]; | 399 [[NSApplication sharedApplication] postEvent:event atStart:NO]; |
| 325 | 400 |
| 326 if (!task.is_null()) { | 401 if (!task.is_null()) { |
| 327 base::MessageLoop::current()->PostTask( | 402 base::MessageLoop::current()->PostTask( |
| 328 FROM_HERE, base::Bind(&EventQueueWatcher, task)); | 403 FROM_HERE, base::Bind(&EventQueueWatcher, task)); |
| 329 } | 404 } |
| 330 | 405 |
| 331 return true; | 406 return true; |
| 332 } | 407 } |
| 333 | 408 |
| 334 bool SendMouseClick(MouseButton type) { | 409 bool SendMouseClick(MouseButton type) { |
| 335 CHECK(g_ui_controls_enabled); | 410 CHECK(g_ui_controls_enabled); |
| 336 return SendMouseEventsNotifyWhenDone(type, UP|DOWN, base::Closure()); | 411 return SendMouseEventsNotifyWhenDone(type, UP|DOWN, base::Closure()); |
| 337 } | 412 } |
| 338 | 413 |
| 339 void RunClosureAfterAllPendingUIEvents(const base::Closure& closure) { | 414 void RunClosureAfterAllPendingUIEvents(const base::Closure& closure) { |
| 340 base::MessageLoop::current()->PostTask( | 415 base::MessageLoop::current()->PostTask( |
| 341 FROM_HERE, base::Bind(&EventQueueWatcher, closure)); | 416 FROM_HERE, base::Bind(&EventQueueWatcher, closure)); |
| 342 } | 417 } |
| 343 | 418 |
| 344 bool IsFullKeyboardAccessEnabled() { | 419 bool IsFullKeyboardAccessEnabled() { |
| 345 return [NSApp isFullKeyboardAccessEnabled]; | 420 return [NSApp isFullKeyboardAccessEnabled]; |
| 346 } | 421 } |
| 347 | 422 |
| 348 } // namespace ui_controls | 423 } // namespace ui_controls |
| OLD | NEW |