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