summaryrefslogtreecommitdiffstats
path: root/chrome/browser/automation/ui_controls_mac.mm
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/automation/ui_controls_mac.mm')
-rw-r--r--chrome/browser/automation/ui_controls_mac.mm242
1 files changed, 242 insertions, 0 deletions
diff --git a/chrome/browser/automation/ui_controls_mac.mm b/chrome/browser/automation/ui_controls_mac.mm
new file mode 100644
index 0000000..715135c
--- /dev/null
+++ b/chrome/browser/automation/ui_controls_mac.mm
@@ -0,0 +1,242 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/automation/ui_controls.h"
+
+#import <Cocoa/Cocoa.h>
+#include <mach/mach_time.h>
+
+#include "base/message_loop.h"
+#include "chrome/browser/chrome_thread.h"
+
+// Implementation details: We use [NSApplication sendEvent:] instead
+// of [NSApplication postEvent:atStart:] so that the event gets sent
+// immediately. This lets us run the post-event task right
+// immediately as well. Unfortunately I cannot subclass NSEvent (it's
+// probably a class cluster) to allow other easy answers. For
+// example, if I could subclass NSEvent, I could run the Task in it's
+// dealloc routine (which necessarily happens after the event is
+// dispatched). Unlike Linux, Mac does not have message loop
+// observer/notification. Unlike windows, I cannot post non-events
+// into the event queue. (I can post other kinds of tasks but can't
+// guarantee their order with regards to events).
+
+namespace {
+
+// From
+// http://stackoverflow.com/questions/1597383/cgeventtimestamp-to-nsdate
+// Which credits Apple sample code for this routine.
+uint64_t UpTimeInNanoseconds(void) {
+ uint64_t time;
+ uint64_t timeNano;
+ static mach_timebase_info_data_t sTimebaseInfo;
+
+ time = mach_absolute_time();
+
+ // Convert to nanoseconds.
+
+ // If this is the first time we've run, get the timebase.
+ // We can use denom == 0 to indicate that sTimebaseInfo is
+ // uninitialised because it makes no sense to have a zero
+ // denominator is a fraction.
+ if (sTimebaseInfo.denom == 0) {
+ (void) mach_timebase_info(&sTimebaseInfo);
+ }
+
+ // This could overflow; for testing needs we probably don't care.
+ timeNano = time * sTimebaseInfo.numer / sTimebaseInfo.denom;
+ return timeNano;
+}
+
+NSTimeInterval TimeIntervalSinceSystemStartup() {
+ return UpTimeInNanoseconds() / 1000000000.0;
+}
+
+} // anonymous namespace
+
+
+namespace ui_controls {
+
+bool SendKeyPress(gfx::NativeWindow window,
+ base::KeyboardCode key,
+ bool control,
+ bool shift,
+ bool alt,
+ bool command) {
+ return SendKeyPressNotifyWhenDone(window, key,
+ control, shift, alt, command,
+ NULL);
+}
+
+// Win and Linux implement a SendKeyPress() this as a
+// SendKeyPressAndRelease(), so we should as well (despite the name).
+//
+// TODO(jrg): handle "characters" better (e.g. apply shift?)
+bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window,
+ base::KeyboardCode key,
+ bool control,
+ bool shift,
+ bool alt,
+ bool command,
+ Task* task) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ NSUInteger flags = 0;
+ if (control)
+ flags |= NSControlKeyMask;
+ if (shift)
+ flags |= NSShiftKeyMask;
+ if (alt)
+ flags |= NSAlternateKeyMask;
+ if (command)
+ flags |= NSCommandKeyMask;
+ unsigned char keycode = key;
+ NSString* charactersIgnoringModifiers = [[[NSString alloc]
+ initWithBytes:&keycode
+ length:1
+ encoding:NSUTF8StringEncoding]
+ autorelease];
+ NSString* characters = charactersIgnoringModifiers;
+
+ // For events other than mouse moved, [event locationInWindow] is
+ // UNDEFINED if the event is not NSMouseMoved. Thus, the (0,0)
+ // locaiton should be fine.
+ // First a key down...
+ NSEvent* event =
+ [NSEvent keyEventWithType:NSKeyDown
+ location:NSMakePoint(0,0)
+ modifierFlags:flags
+ timestamp:TimeIntervalSinceSystemStartup()
+ windowNumber:[window windowNumber]
+ context:nil
+ characters:characters
+ charactersIgnoringModifiers:charactersIgnoringModifiers
+ isARepeat:NO
+ keyCode:key];
+ [[NSApplication sharedApplication] sendEvent:event];
+ // Then a key up.
+ event =
+ [NSEvent keyEventWithType:NSKeyUp
+ location:NSMakePoint(0,0)
+ modifierFlags:flags
+ timestamp:TimeIntervalSinceSystemStartup()
+ windowNumber:[window windowNumber]
+ context:nil
+ characters:characters
+ charactersIgnoringModifiers:charactersIgnoringModifiers
+ isARepeat:NO
+ keyCode:key];
+ [[NSApplication sharedApplication] sendEvent:event];
+
+ if (task)
+ MessageLoop::current()->PostTask(FROM_HERE, task);
+ return true;
+}
+
+bool SendMouseMove(long x, long y) {
+ return SendMouseMoveNotifyWhenDone(x, y, NULL);
+}
+
+// Input position is in screen coordinates. However, NSMouseMoved
+// events require them window-relative, so we adjust. We *DO* flip
+// the coordinate space, so input events can be the same for all
+// platforms. E.g. (0,0) is upper-left.
+bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) {
+ NSWindow* window = [[NSApplication sharedApplication] keyWindow];
+ CGFloat screenHeight = [[NSScreen mainScreen] frame].size.height;
+ NSPoint pointInWindow = NSMakePoint(x, screenHeight - y); // flip!
+ if (window)
+ pointInWindow = [window convertScreenToBase:pointInWindow];
+ NSTimeInterval timestamp = TimeIntervalSinceSystemStartup();
+
+ NSEvent* event =
+ [NSEvent mouseEventWithType:NSMouseMoved
+ location:pointInWindow
+ modifierFlags:0
+ timestamp:timestamp
+ windowNumber:[window windowNumber]
+ context:nil
+ eventNumber:0
+ clickCount:0
+ pressure:0.0];
+ [[NSApplication sharedApplication] postEvent:event atStart:NO];
+ if (task)
+ MessageLoop::current()->PostTask(FROM_HERE, task);
+ return true;
+}
+
+bool SendMouseEvents(MouseButton type, int state) {
+ return SendMouseEventsNotifyWhenDone(type, state, NULL);
+}
+
+bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) {
+ // On windows it appears state can be (UP|DOWN). It is unclear if
+ // that'll happen here but prepare for it just in case.
+ if (state == (UP|DOWN)) {
+ return (SendMouseEventsNotifyWhenDone(type, DOWN, NULL) &&
+ SendMouseEventsNotifyWhenDone(type, UP, task));
+ }
+
+ NSEventType etype = 0;
+ if (type == LEFT) {
+ if (state == UP) {
+ etype = NSLeftMouseUp;
+ } else {
+ etype = NSLeftMouseDown;
+ }
+ } else if (type == MIDDLE) {
+ if (state == UP) {
+ etype = NSOtherMouseUp;
+ } else {
+ etype = NSOtherMouseDown;
+ }
+ } else if (type == RIGHT) {
+ if (state == UP) {
+ etype = NSRightMouseUp;
+ } else {
+ etype = NSRightMouseDown;
+ }
+ } else {
+ return false;
+ }
+ NSWindow* window = [[NSApplication sharedApplication] keyWindow];
+ NSPoint location = [NSEvent mouseLocation];
+ NSPoint pointInWindow = location;
+ if (window)
+ pointInWindow = [window convertScreenToBase:pointInWindow];
+
+ NSEvent* event =
+ [NSEvent mouseEventWithType:etype
+ location:pointInWindow
+ modifierFlags:0
+ timestamp:TimeIntervalSinceSystemStartup()
+ windowNumber:[window windowNumber]
+ context:nil
+ eventNumber:0
+ clickCount:0
+ pressure:0.0];
+ [[NSApplication sharedApplication] sendEvent:event];
+ if (task)
+ MessageLoop::current()->PostTask(FROM_HERE, task);
+ return true;
+}
+
+bool SendMouseClick(MouseButton type) {
+ return SendMouseEventsNotifyWhenDone(type, UP|DOWN, NULL);
+}
+
+// This appears to only be used by a function in test/ui_test_utils.h:
+// ui_test_utils::ClickOnView(). That is not implemented on Mac, so
+// we don't need to implement MoveMouseToCenterAndPress(). I've
+// suggested an implementation of ClickOnView() which would call Cocoa
+// directly and not need this indirection, so this may not be needed,
+// ever.
+void MoveMouseToCenterAndPress(
+ NSWindow* window,
+ MouseButton button,
+ int state,
+ Task* task) {
+ NOTIMPLEMENTED();
+}
+
+} // ui_controls