Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(111)

Side by Side Diff: chrome/browser/automation/ui_controls_mac.mm

Issue 2986004: [Mac]Port browser_keyevents_browsertest.cc and browser_focus_uitest.cc to Mac. (Closed) Base URL: http://src.chromium.org/git/chromium.git
Patch Set: Enable BrowserFocusTest and BrowserKeyEventsTests on Mac. Created 10 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/browser/automation/ui_controls_linux.cc ('k') | chrome/browser/browser_focus_uitest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2010 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 "chrome/browser/automation/ui_controls.h" 5 #include "chrome/browser/automation/ui_controls.h"
6 6
7 #import <Cocoa/Cocoa.h> 7 #import <Cocoa/Cocoa.h>
8 #include <mach/mach_time.h> 8 #include <mach/mach_time.h>
9 #include <vector>
9 10
11 #include "base/keyboard_code_conversion_mac.h"
10 #include "base/message_loop.h" 12 #include "base/message_loop.h"
13 #include "chrome/browser/automation/ui_controls_internal.h"
11 #include "chrome/browser/chrome_thread.h" 14 #include "chrome/browser/chrome_thread.h"
12 15
13 // Implementation details: We use [NSApplication sendEvent:] instead 16 // Implementation details: We use [NSApplication sendEvent:] instead
14 // of [NSApplication postEvent:atStart:] so that the event gets sent 17 // of [NSApplication postEvent:atStart:] so that the event gets sent
15 // immediately. This lets us run the post-event task right 18 // immediately. This lets us run the post-event task right
16 // immediately as well. Unfortunately I cannot subclass NSEvent (it's 19 // immediately as well. Unfortunately I cannot subclass NSEvent (it's
17 // probably a class cluster) to allow other easy answers. For 20 // probably a class cluster) to allow other easy answers. For
18 // example, if I could subclass NSEvent, I could run the Task in it's 21 // example, if I could subclass NSEvent, I could run the Task in it's
19 // dealloc routine (which necessarily happens after the event is 22 // dealloc routine (which necessarily happens after the event is
20 // dispatched). Unlike Linux, Mac does not have message loop 23 // dispatched). Unlike Linux, Mac does not have message loop
21 // observer/notification. Unlike windows, I cannot post non-events 24 // observer/notification. Unlike windows, I cannot post non-events
22 // into the event queue. (I can post other kinds of tasks but can't 25 // into the event queue. (I can post other kinds of tasks but can't
23 // guarantee their order with regards to events). 26 // guarantee their order with regards to events).
24 27
28 // But [NSApplication sendEvent:] causes a problem when sending mouse click
29 // events. Because in order to handle mouse drag, when processing a mouse
30 // click event, the application may want to retrieve the next event
31 // synchronously by calling NSApplication's nextEventMatchingMask method.
32 // In this case, [NSApplication sendEvent:] causes deadlock.
33 // So we need to use [NSApplication postEvent:atStart:] for mouse click
34 // events. In order to notify the caller correctly after all events has been
35 // processed, we setup a task to watch for the event queue time to time and
36 // notify the caller as soon as there is no event in the queue.
37 //
38 // TODO(suzhe):
39 // 1. Investigate why using [NSApplication postEvent:atStart:] for keyboard
40 // events causes BrowserKeyEventsTest.CommandKeyEvents to fail.
41 // See http://crbug.com/49270
42 // 2. On OSX 10.6, [NSEvent addLocalMonitorForEventsMatchingMask:handler:] may
43 // be used, so that we don't need to poll the event queue time to time.
44
25 namespace { 45 namespace {
26 46
27 // From 47 // From
28 // http://stackoverflow.com/questions/1597383/cgeventtimestamp-to-nsdate 48 // http://stackoverflow.com/questions/1597383/cgeventtimestamp-to-nsdate
29 // Which credits Apple sample code for this routine. 49 // Which credits Apple sample code for this routine.
30 uint64_t UpTimeInNanoseconds(void) { 50 uint64_t UpTimeInNanoseconds(void) {
31 uint64_t time; 51 uint64_t time;
32 uint64_t timeNano; 52 uint64_t timeNano;
33 static mach_timebase_info_data_t sTimebaseInfo; 53 static mach_timebase_info_data_t sTimebaseInfo;
34 54
(...skipping 11 matching lines...) Expand all
46 66
47 // This could overflow; for testing needs we probably don't care. 67 // This could overflow; for testing needs we probably don't care.
48 timeNano = time * sTimebaseInfo.numer / sTimebaseInfo.denom; 68 timeNano = time * sTimebaseInfo.numer / sTimebaseInfo.denom;
49 return timeNano; 69 return timeNano;
50 } 70 }
51 71
52 NSTimeInterval TimeIntervalSinceSystemStartup() { 72 NSTimeInterval TimeIntervalSinceSystemStartup() {
53 return UpTimeInNanoseconds() / 1000000000.0; 73 return UpTimeInNanoseconds() / 1000000000.0;
54 } 74 }
55 75
76 // Creates and returns an autoreleased key event.
77 NSEvent* SynthesizeKeyEvent(NSWindow* window,
78 bool keyDown,
79 base::KeyboardCode keycode,
80 NSUInteger flags) {
81 unichar character;
82 unichar characterIgnoringModifiers;
83 int macKeycode = base::MacKeyCodeForWindowsKeyCode(
84 keycode, flags, &character, &characterIgnoringModifiers);
85
86 if (macKeycode < 0)
87 return nil;
88
89 NSString* charactersIgnoringModifiers =
90 [[[NSString alloc] initWithCharacters:&characterIgnoringModifiers
91 length:1]
92 autorelease];
93 NSString* characters =
94 [[[NSString alloc] initWithCharacters:&character length:1] autorelease];
95
96 NSEventType type = (keyDown ? NSKeyDown : NSKeyUp);
97
98 // Modifier keys generate NSFlagsChanged event rather than
99 // NSKeyDown/NSKeyUp events.
100 if (keycode == base::VKEY_CONTROL || keycode == base::VKEY_SHIFT ||
101 keycode == base::VKEY_MENU || keycode == base::VKEY_COMMAND)
102 type = NSFlagsChanged;
103
104 // For events other than mouse moved, [event locationInWindow] is
105 // UNDEFINED if the event is not NSMouseMoved. Thus, the (0,0)
106 // location should be fine.
107 NSEvent* event =
108 [NSEvent keyEventWithType:type
109 location:NSMakePoint(0, 0)
110 modifierFlags:flags
111 timestamp:TimeIntervalSinceSystemStartup()
112 windowNumber:[window windowNumber]
113 context:nil
114 characters:characters
115 charactersIgnoringModifiers:charactersIgnoringModifiers
116 isARepeat:NO
117 keyCode:(unsigned short)macKeycode];
118
119 return event;
120 }
121
122 // Creates the proper sequence of autoreleased key events for a key down + up.
123 void SynthesizeKeyEventsSequence(NSWindow* window,
124 base::KeyboardCode keycode,
125 bool control,
126 bool shift,
127 bool alt,
128 bool command,
129 std::vector<NSEvent*>* events) {
130 NSEvent* event = nil;
131 NSUInteger flags = 0;
132 if (control) {
133 flags |= NSControlKeyMask;
134 event = SynthesizeKeyEvent(window, true, base::VKEY_CONTROL, flags);
135 DCHECK(event);
136 events->push_back(event);
137 }
138 if (shift) {
139 flags |= NSShiftKeyMask;
140 event = SynthesizeKeyEvent(window, true, base::VKEY_SHIFT, flags);
141 DCHECK(event);
142 events->push_back(event);
143 }
144 if (alt) {
145 flags |= NSAlternateKeyMask;
146 event = SynthesizeKeyEvent(window, true, base::VKEY_MENU, flags);
147 DCHECK(event);
148 events->push_back(event);
149 }
150 if (command) {
151 flags |= NSCommandKeyMask;
152 event = SynthesizeKeyEvent(window, true, base::VKEY_COMMAND, flags);
153 DCHECK(event);
154 events->push_back(event);
155 }
156
157 event = SynthesizeKeyEvent(window, true, keycode, flags);
158 DCHECK(event);
159 events->push_back(event);
160 event = SynthesizeKeyEvent(window, false, keycode, flags);
161 DCHECK(event);
162 events->push_back(event);
163
164 if (command) {
165 flags &= ~NSCommandKeyMask;
166 event = SynthesizeKeyEvent(window, false, base::VKEY_COMMAND, flags);
167 DCHECK(event);
168 events->push_back(event);
169 }
170 if (alt) {
171 flags &= ~NSAlternateKeyMask;
172 event = SynthesizeKeyEvent(window, false, base::VKEY_MENU, flags);
173 DCHECK(event);
174 events->push_back(event);
175 }
176 if (shift) {
177 flags &= ~NSShiftKeyMask;
178 event = SynthesizeKeyEvent(window, false, base::VKEY_SHIFT, flags);
179 DCHECK(event);
180 events->push_back(event);
181 }
182 if (control) {
183 flags &= ~NSControlKeyMask;
184 event = SynthesizeKeyEvent(window, false, base::VKEY_CONTROL, flags);
185 DCHECK(event);
186 events->push_back(event);
187 }
188 }
189
190 // A task class to watch for the event queue. The specific task will be fired
191 // when there is no more event in the queue.
192 class EventQueueWatcher : public Task {
193 public:
194 EventQueueWatcher(Task* task) : task_(task) {}
195
196 virtual ~EventQueueWatcher() {}
197
198 virtual void Run() {
199 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
200 untilDate:nil
201 inMode:NSDefaultRunLoopMode
202 dequeue:NO];
203 // If there is still event in the queue, then we need to check again.
204 if (event)
205 MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task_));
206 else
207 MessageLoop::current()->PostTask(FROM_HERE, task_);
208 }
209
210 private:
211 Task* task_;
212 };
213
214 // Stores the current mouse location on the screen. So that we can use it
215 // when firing keyboard and mouse click events.
216 NSPoint g_mouse_location = { 0, 0 };
217
56 } // anonymous namespace 218 } // anonymous namespace
57 219
58 220
59 namespace ui_controls { 221 namespace ui_controls {
60 222
61 bool SendKeyPress(gfx::NativeWindow window, 223 bool SendKeyPress(gfx::NativeWindow window,
62 base::KeyboardCode key, 224 base::KeyboardCode key,
63 bool control, 225 bool control,
64 bool shift, 226 bool shift,
65 bool alt, 227 bool alt,
66 bool command) { 228 bool command) {
67 return SendKeyPressNotifyWhenDone(window, key, 229 return SendKeyPressNotifyWhenDone(window, key,
68 control, shift, alt, command, 230 control, shift, alt, command,
69 NULL); 231 NULL);
70 } 232 }
71 233
72 // Win and Linux implement a SendKeyPress() this as a 234 // Win and Linux implement a SendKeyPress() this as a
73 // SendKeyPressAndRelease(), so we should as well (despite the name). 235 // SendKeyPressAndRelease(), so we should as well (despite the name).
74 //
75 // TODO(jrg): handle "characters" better (e.g. apply shift?)
76 bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, 236 bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window,
77 base::KeyboardCode key, 237 base::KeyboardCode key,
78 bool control, 238 bool control,
79 bool shift, 239 bool shift,
80 bool alt, 240 bool alt,
81 bool command, 241 bool command,
82 Task* task) { 242 Task* task) {
83 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); 243 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
84 NSUInteger flags = 0;
85 if (control)
86 flags |= NSControlKeyMask;
87 if (shift)
88 flags |= NSShiftKeyMask;
89 if (alt)
90 flags |= NSAlternateKeyMask;
91 if (command)
92 flags |= NSCommandKeyMask;
93 unsigned char keycode = key;
94 NSString* charactersIgnoringModifiers = [[[NSString alloc]
95 initWithBytes:&keycode
96 length:1
97 encoding:NSUTF8StringEncoding]
98 autorelease];
99 NSString* characters = charactersIgnoringModifiers;
100 244
101 // For events other than mouse moved, [event locationInWindow] is 245 std::vector<NSEvent*> events;
102 // UNDEFINED if the event is not NSMouseMoved. Thus, the (0,0) 246 SynthesizeKeyEventsSequence(
103 // locaiton should be fine. 247 window, key, control, shift, alt, command, &events);
104 // First a key down... 248
105 NSEvent* event = 249 // TODO(suzhe): Using [NSApplication postEvent:atStart:] here causes
106 [NSEvent keyEventWithType:NSKeyDown 250 // BrowserKeyEventsTest.CommandKeyEvents to fail. See http://crbug.com/49270
107 location:NSMakePoint(0,0) 251 // But using [NSApplication sendEvent:] should be safe for keyboard events,
108 modifierFlags:flags 252 // because until now, no code wants to retrieve the next event when handling
109 timestamp:TimeIntervalSinceSystemStartup() 253 // a keyboard event.
110 windowNumber:[window windowNumber] 254 for (std::vector<NSEvent*>::iterator iter = events.begin();
111 context:nil 255 iter != events.end(); ++iter)
112 characters:characters 256 [[NSApplication sharedApplication] sendEvent:*iter];
113 charactersIgnoringModifiers:charactersIgnoringModifiers
114 isARepeat:NO
115 keyCode:key];
116 [[NSApplication sharedApplication] sendEvent:event];
117 // Then a key up.
118 event =
119 [NSEvent keyEventWithType:NSKeyUp
120 location:NSMakePoint(0,0)
121 modifierFlags:flags
122 timestamp:TimeIntervalSinceSystemStartup()
123 windowNumber:[window windowNumber]
124 context:nil
125 characters:characters
126 charactersIgnoringModifiers:charactersIgnoringModifiers
127 isARepeat:NO
128 keyCode:key];
129 [[NSApplication sharedApplication] sendEvent:event];
130 257
131 if (task) 258 if (task)
132 MessageLoop::current()->PostTask(FROM_HERE, task); 259 MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task));
260
133 return true; 261 return true;
134 } 262 }
135 263
136 bool SendMouseMove(long x, long y) { 264 bool SendMouseMove(long x, long y) {
137 return SendMouseMoveNotifyWhenDone(x, y, NULL); 265 return SendMouseMoveNotifyWhenDone(x, y, NULL);
138 } 266 }
139 267
140 // Input position is in screen coordinates. However, NSMouseMoved 268 // Input position is in screen coordinates. However, NSMouseMoved
141 // events require them window-relative, so we adjust. We *DO* flip 269 // events require them window-relative, so we adjust. We *DO* flip
142 // the coordinate space, so input events can be the same for all 270 // the coordinate space, so input events can be the same for all
143 // platforms. E.g. (0,0) is upper-left. 271 // platforms. E.g. (0,0) is upper-left.
144 bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { 272 bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) {
145 NSWindow* window = [[NSApplication sharedApplication] keyWindow]; 273 NSWindow* window = [[NSApplication sharedApplication] keyWindow];
146 CGFloat screenHeight = [[NSScreen mainScreen] frame].size.height; 274 CGFloat screenHeight = [[NSScreen mainScreen] frame].size.height;
147 NSPoint pointInWindow = NSMakePoint(x, screenHeight - y); // flip! 275 g_mouse_location = NSMakePoint(x, screenHeight - y); // flip!
276 NSPoint pointInWindow = g_mouse_location;
148 if (window) 277 if (window)
149 pointInWindow = [window convertScreenToBase:pointInWindow]; 278 pointInWindow = [window convertScreenToBase:pointInWindow];
150 NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); 279 NSTimeInterval timestamp = TimeIntervalSinceSystemStartup();
151 280
152 NSEvent* event = 281 NSEvent* event =
153 [NSEvent mouseEventWithType:NSMouseMoved 282 [NSEvent mouseEventWithType:NSMouseMoved
154 location:pointInWindow 283 location:pointInWindow
155 modifierFlags:0 284 modifierFlags:0
156 timestamp:timestamp 285 timestamp:timestamp
157 windowNumber:[window windowNumber] 286 windowNumber:[window windowNumber]
158 context:nil 287 context:nil
159 eventNumber:0 288 eventNumber:0
160 clickCount:0 289 clickCount:0
161 pressure:0.0]; 290 pressure:0.0];
162 [[NSApplication sharedApplication] postEvent:event atStart:NO]; 291 [[NSApplication sharedApplication] postEvent:event atStart:NO];
292
163 if (task) 293 if (task)
164 MessageLoop::current()->PostTask(FROM_HERE, task); 294 MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task));
295
165 return true; 296 return true;
166 } 297 }
167 298
168 bool SendMouseEvents(MouseButton type, int state) { 299 bool SendMouseEvents(MouseButton type, int state) {
169 return SendMouseEventsNotifyWhenDone(type, state, NULL); 300 return SendMouseEventsNotifyWhenDone(type, state, NULL);
170 } 301 }
171 302
172 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { 303 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) {
173 // On windows it appears state can be (UP|DOWN). It is unclear if 304 // On windows it appears state can be (UP|DOWN). It is unclear if
174 // that'll happen here but prepare for it just in case. 305 // that'll happen here but prepare for it just in case.
175 if (state == (UP|DOWN)) { 306 if (state == (UP|DOWN)) {
176 return (SendMouseEventsNotifyWhenDone(type, DOWN, NULL) && 307 return (SendMouseEventsNotifyWhenDone(type, DOWN, NULL) &&
177 SendMouseEventsNotifyWhenDone(type, UP, task)); 308 SendMouseEventsNotifyWhenDone(type, UP, task));
178 } 309 }
179
180 NSEventType etype = 0; 310 NSEventType etype = 0;
181 if (type == LEFT) { 311 if (type == LEFT) {
182 if (state == UP) { 312 if (state == UP) {
183 etype = NSLeftMouseUp; 313 etype = NSLeftMouseUp;
184 } else { 314 } else {
185 etype = NSLeftMouseDown; 315 etype = NSLeftMouseDown;
186 } 316 }
187 } else if (type == MIDDLE) { 317 } else if (type == MIDDLE) {
188 if (state == UP) { 318 if (state == UP) {
189 etype = NSOtherMouseUp; 319 etype = NSOtherMouseUp;
190 } else { 320 } else {
191 etype = NSOtherMouseDown; 321 etype = NSOtherMouseDown;
192 } 322 }
193 } else if (type == RIGHT) { 323 } else if (type == RIGHT) {
194 if (state == UP) { 324 if (state == UP) {
195 etype = NSRightMouseUp; 325 etype = NSRightMouseUp;
196 } else { 326 } else {
197 etype = NSRightMouseDown; 327 etype = NSRightMouseDown;
198 } 328 }
199 } else { 329 } else {
200 return false; 330 return false;
201 } 331 }
202 NSWindow* window = [[NSApplication sharedApplication] keyWindow]; 332 NSWindow* window = [[NSApplication sharedApplication] keyWindow];
203 NSPoint location = [NSEvent mouseLocation]; 333 NSPoint pointInWindow = g_mouse_location;
204 NSPoint pointInWindow = location;
205 if (window) 334 if (window)
206 pointInWindow = [window convertScreenToBase:pointInWindow]; 335 pointInWindow = [window convertScreenToBase:pointInWindow];
207 336
208 NSEvent* event = 337 NSEvent* event =
209 [NSEvent mouseEventWithType:etype 338 [NSEvent mouseEventWithType:etype
210 location:pointInWindow 339 location:pointInWindow
211 modifierFlags:0 340 modifierFlags:0
212 timestamp:TimeIntervalSinceSystemStartup() 341 timestamp:TimeIntervalSinceSystemStartup()
213 windowNumber:[window windowNumber] 342 windowNumber:[window windowNumber]
214 context:nil 343 context:nil
215 eventNumber:0 344 eventNumber:0
216 clickCount:0 345 clickCount:1
217 pressure:0.0]; 346 pressure:(state == DOWN ? 1.0 : 0.0 )];
218 [[NSApplication sharedApplication] sendEvent:event]; 347 [[NSApplication sharedApplication] postEvent:event atStart:NO];
348
219 if (task) 349 if (task)
220 MessageLoop::current()->PostTask(FROM_HERE, task); 350 MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task));
351
221 return true; 352 return true;
222 } 353 }
223 354
224 bool SendMouseClick(MouseButton type) { 355 bool SendMouseClick(MouseButton type) {
225 return SendMouseEventsNotifyWhenDone(type, UP|DOWN, NULL); 356 return SendMouseEventsNotifyWhenDone(type, UP|DOWN, NULL);
226 } 357 }
227 358
228 // This appears to only be used by a function in test/ui_test_utils.h:
229 // ui_test_utils::ClickOnView(). That is not implemented on Mac, so
230 // we don't need to implement MoveMouseToCenterAndPress(). I've
231 // suggested an implementation of ClickOnView() which would call Cocoa
232 // directly and not need this indirection, so this may not be needed,
233 // ever.
234 void MoveMouseToCenterAndPress( 359 void MoveMouseToCenterAndPress(
235 NSWindow* window, 360 NSView* view,
236 MouseButton button, 361 MouseButton button,
237 int state, 362 int state,
238 Task* task) { 363 Task* task) {
239 NOTIMPLEMENTED(); 364 DCHECK(view);
365 NSWindow* window = [view window];
366 DCHECK(window);
367 NSScreen* screen = [window screen];
368 DCHECK(screen);
369
370 // Converts the center position of the view into the coordinates accepted
371 // by SendMouseMoveNotifyWhenDone() method.
372 NSRect bounds = [view bounds];
373 NSPoint center = NSMakePoint(NSMidX(bounds), NSMidY(bounds));
374 center = [view convertPoint:center toView:nil];
375 center = [window convertBaseToScreen:center];
376 center = NSMakePoint(center.x, [screen frame].size.height - center.y);
377
378 SendMouseMoveNotifyWhenDone(center.x, center.y,
379 new ClickTask(button, state, task));
240 } 380 }
241 381
242 } // ui_controls 382 } // ui_controls
OLDNEW
« no previous file with comments | « chrome/browser/automation/ui_controls_linux.cc ('k') | chrome/browser/browser_focus_uitest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698