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

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

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

Powered by Google App Engine
This is Rietveld 408576698