NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / ui / base / test / ui_controls_mac.mm
blob86345405bfe5b744f4f53b234fbe7b3f5b87b5c5
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.
5 #include "ui/base/test/ui_controls.h"
7 #import <Cocoa/Cocoa.h>
8 #include <mach/mach_time.h>
9 #include <vector>
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"
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).
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.
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.
46 namespace {
48 // From
49 // http://stackoverflow.com/questions/1597383/cgeventtimestamp-to-nsdate
50 // Which credits Apple sample code for this routine.
51 uint64_t UpTimeInNanoseconds(void) {
52   uint64_t time;
53   uint64_t timeNano;
54   static mach_timebase_info_data_t sTimebaseInfo;
56   time = mach_absolute_time();
58   // Convert to nanoseconds.
60   // If this is the first time we've run, get the timebase.
61   // We can use denom == 0 to indicate that sTimebaseInfo is
62   // uninitialised because it makes no sense to have a zero
63   // denominator is a fraction.
64   if (sTimebaseInfo.denom == 0) {
65     (void) mach_timebase_info(&sTimebaseInfo);
66   }
68   // This could overflow; for testing needs we probably don't care.
69   timeNano = time * sTimebaseInfo.numer / sTimebaseInfo.denom;
70   return timeNano;
73 NSTimeInterval TimeIntervalSinceSystemStartup() {
74   return UpTimeInNanoseconds() / 1000000000.0;
77 // Creates and returns an autoreleased key event.
78 NSEvent* SynthesizeKeyEvent(NSWindow* window,
79                             bool keyDown,
80                             ui::KeyboardCode keycode,
81                             NSUInteger flags) {
82   unichar character;
83   unichar characterIgnoringModifiers;
84   int macKeycode = ui::MacKeyCodeForWindowsKeyCode(
85       keycode, flags, &character, &characterIgnoringModifiers);
87   if (macKeycode < 0)
88     return nil;
90   NSString* charactersIgnoringModifiers =
91       [[[NSString alloc] initWithCharacters:&characterIgnoringModifiers
92                                      length:1]
93         autorelease];
94   NSString* characters =
95       [[[NSString alloc] initWithCharacters:&character length:1] autorelease];
97   NSEventType type = (keyDown ? NSKeyDown : NSKeyUp);
99   // Modifier keys generate NSFlagsChanged event rather than
100   // NSKeyDown/NSKeyUp events.
101   if (keycode == ui::VKEY_CONTROL || keycode == ui::VKEY_SHIFT ||
102       keycode == ui::VKEY_MENU || keycode == ui::VKEY_COMMAND)
103     type = NSFlagsChanged;
105   // For events other than mouse moved, [event locationInWindow] is
106   // UNDEFINED if the event is not NSMouseMoved.  Thus, the (0,0)
107   // location should be fine.
108   NSEvent* event =
109       [NSEvent keyEventWithType:type
110                        location:NSZeroPoint
111                   modifierFlags:flags
112                       timestamp:TimeIntervalSinceSystemStartup()
113                    windowNumber:[window windowNumber]
114                         context:nil
115                      characters:characters
116     charactersIgnoringModifiers:charactersIgnoringModifiers
117                       isARepeat:NO
118                         keyCode:(unsigned short)macKeycode];
120   return event;
123 // Creates the proper sequence of autoreleased key events for a key down + up.
124 void SynthesizeKeyEventsSequence(NSWindow* window,
125                                  ui::KeyboardCode keycode,
126                                  bool control,
127                                  bool shift,
128                                  bool alt,
129                                  bool command,
130                                  std::vector<NSEvent*>* events) {
131   NSEvent* event = nil;
132   NSUInteger flags = 0;
133   if (control) {
134     flags |= NSControlKeyMask;
135     event = SynthesizeKeyEvent(window, true, ui::VKEY_CONTROL, flags);
136     DCHECK(event);
137     events->push_back(event);
138   }
139   if (shift) {
140     flags |= NSShiftKeyMask;
141     event = SynthesizeKeyEvent(window, true, ui::VKEY_SHIFT, flags);
142     DCHECK(event);
143     events->push_back(event);
144   }
145   if (alt) {
146     flags |= NSAlternateKeyMask;
147     event = SynthesizeKeyEvent(window, true, ui::VKEY_MENU, flags);
148     DCHECK(event);
149     events->push_back(event);
150   }
151   if (command) {
152     flags |= NSCommandKeyMask;
153     event = SynthesizeKeyEvent(window, true, ui::VKEY_COMMAND, flags);
154     DCHECK(event);
155     events->push_back(event);
156   }
158   event = SynthesizeKeyEvent(window, true, keycode, flags);
159   DCHECK(event);
160   events->push_back(event);
161   event = SynthesizeKeyEvent(window, false, keycode, flags);
162   DCHECK(event);
163   events->push_back(event);
165   if (command) {
166     flags &= ~NSCommandKeyMask;
167     event = SynthesizeKeyEvent(window, false, ui::VKEY_COMMAND, flags);
168     DCHECK(event);
169     events->push_back(event);
170   }
171   if (alt) {
172     flags &= ~NSAlternateKeyMask;
173     event = SynthesizeKeyEvent(window, false, ui::VKEY_MENU, flags);
174     DCHECK(event);
175     events->push_back(event);
176   }
177   if (shift) {
178     flags &= ~NSShiftKeyMask;
179     event = SynthesizeKeyEvent(window, false, ui::VKEY_SHIFT, flags);
180     DCHECK(event);
181     events->push_back(event);
182   }
183   if (control) {
184     flags &= ~NSControlKeyMask;
185     event = SynthesizeKeyEvent(window, false, ui::VKEY_CONTROL, flags);
186     DCHECK(event);
187     events->push_back(event);
188   }
191 // A helper function to watch for the event queue. The specific task will be
192 // fired when there is no more event in the queue.
193 void EventQueueWatcher(const base::Closure& task) {
194   NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
195                                       untilDate:nil
196                                          inMode:NSDefaultRunLoopMode
197                                         dequeue:NO];
198   // If there is still event in the queue, then we need to check again.
199   if (event) {
200     base::MessageLoop::current()->PostTask(
201         FROM_HERE,
202         base::Bind(&EventQueueWatcher, task));
203   } else {
204     base::MessageLoop::current()->PostTask(FROM_HERE, task);
205   }
208 // Stores the current mouse location on the screen. So that we can use it
209 // when firing keyboard and mouse click events.
210 NSPoint g_mouse_location = { 0, 0 };
212 bool g_ui_controls_enabled = false;
214 }  // namespace
216 namespace ui_controls {
218 void EnableUIControls() {
219   g_ui_controls_enabled = true;
222 bool SendKeyPress(gfx::NativeWindow window,
223                   ui::KeyboardCode key,
224                   bool control,
225                   bool shift,
226                   bool alt,
227                   bool command) {
228   CHECK(g_ui_controls_enabled);
229   return SendKeyPressNotifyWhenDone(window, key,
230                                     control, shift, alt, command,
231                                     base::Closure());
234 // Win and Linux implement a SendKeyPress() this as a
235 // SendKeyPressAndRelease(), so we should as well (despite the name).
236 bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window,
237                                 ui::KeyboardCode key,
238                                 bool control,
239                                 bool shift,
240                                 bool alt,
241                                 bool command,
242                                 const base::Closure& task) {
243   CHECK(g_ui_controls_enabled);
244   DCHECK(base::MessageLoopForUI::IsCurrent());
246   std::vector<NSEvent*> events;
247   SynthesizeKeyEventsSequence(
248       window, key, control, shift, alt, command, &events);
250   // TODO(suzhe): Using [NSApplication postEvent:atStart:] here causes
251   // BrowserKeyEventsTest.CommandKeyEvents to fail. See http://crbug.com/49270
252   // But using [NSApplication sendEvent:] should be safe for keyboard events,
253   // because until now, no code wants to retrieve the next event when handling
254   // a keyboard event.
255   for (std::vector<NSEvent*>::iterator iter = events.begin();
256        iter != events.end(); ++iter)
257     [[NSApplication sharedApplication] sendEvent:*iter];
259   if (!task.is_null()) {
260     base::MessageLoop::current()->PostTask(
261         FROM_HERE, base::Bind(&EventQueueWatcher, task));
262   }
264   return true;
267 bool SendMouseMove(long x, long y) {
268   CHECK(g_ui_controls_enabled);
269   return SendMouseMoveNotifyWhenDone(x, y, base::Closure());
272 // Input position is in screen coordinates.  However, NSMouseMoved
273 // events require them window-relative, so we adjust.  We *DO* flip
274 // the coordinate space, so input events can be the same for all
275 // platforms.  E.g. (0,0) is upper-left.
276 bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) {
277   CHECK(g_ui_controls_enabled);
278   NSWindow* window = [[NSApplication sharedApplication] keyWindow];
279   CGFloat screenHeight =
280     [[[NSScreen screens] objectAtIndex:0] frame].size.height;
281   g_mouse_location = NSMakePoint(x, screenHeight - y);  // flip!
282   NSPoint pointInWindow = g_mouse_location;
283   if (window)
284     pointInWindow = [window convertScreenToBase:pointInWindow];
285   NSTimeInterval timestamp = TimeIntervalSinceSystemStartup();
287   NSEvent* event =
288       [NSEvent mouseEventWithType:NSMouseMoved
289                          location:pointInWindow
290                     modifierFlags:0
291                         timestamp:timestamp
292                      windowNumber:[window windowNumber]
293                           context:nil
294                       eventNumber:0
295                        clickCount:0
296                          pressure:0.0];
297   [[NSApplication sharedApplication] postEvent:event atStart:NO];
299   if (!task.is_null()) {
300     base::MessageLoop::current()->PostTask(
301         FROM_HERE, base::Bind(&EventQueueWatcher, task));
302   }
304   return true;
307 bool SendMouseEvents(MouseButton type, int state) {
308   CHECK(g_ui_controls_enabled);
309   return SendMouseEventsNotifyWhenDone(type, state, base::Closure());
312 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state,
313                                    const base::Closure& task) {
314   CHECK(g_ui_controls_enabled);
315   // On windows it appears state can be (UP|DOWN).  It is unclear if
316   // that'll happen here but prepare for it just in case.
317   if (state == (UP|DOWN)) {
318     return (SendMouseEventsNotifyWhenDone(type, DOWN, base::Closure()) &&
319             SendMouseEventsNotifyWhenDone(type, UP, task));
320   }
321   NSEventType etype = 0;
322   if (type == LEFT) {
323     if (state == UP) {
324       etype = NSLeftMouseUp;
325     } else {
326       etype = NSLeftMouseDown;
327     }
328   } else if (type == MIDDLE) {
329     if (state == UP) {
330       etype = NSOtherMouseUp;
331     } else {
332       etype = NSOtherMouseDown;
333     }
334   } else if (type == RIGHT) {
335     if (state == UP) {
336       etype = NSRightMouseUp;
337     } else {
338       etype = NSRightMouseDown;
339     }
340   } else {
341     return false;
342   }
343   NSWindow* window = [[NSApplication sharedApplication] keyWindow];
344   NSPoint pointInWindow = g_mouse_location;
345   if (window)
346     pointInWindow = [window convertScreenToBase:pointInWindow];
348   NSEvent* event =
349       [NSEvent mouseEventWithType:etype
350                          location:pointInWindow
351                     modifierFlags:0
352                         timestamp:TimeIntervalSinceSystemStartup()
353                      windowNumber:[window windowNumber]
354                           context:nil
355                       eventNumber:0
356                        clickCount:1
357                          pressure:(state == DOWN ? 1.0 : 0.0 )];
358   [[NSApplication sharedApplication] postEvent:event atStart:NO];
360   if (!task.is_null()) {
361     base::MessageLoop::current()->PostTask(
362         FROM_HERE, base::Bind(&EventQueueWatcher, task));
363   }
365   return true;
368 bool SendMouseClick(MouseButton type) {
369   CHECK(g_ui_controls_enabled);
370   return SendMouseEventsNotifyWhenDone(type, UP|DOWN, base::Closure());
373 }  // namespace ui_controls