diff options
-rw-r--r-- | base/base.gypi | 2 | ||||
-rw-r--r-- | base/keyboard_code_conversion_gtk.cc | 7 | ||||
-rw-r--r-- | base/keyboard_code_conversion_mac.h | 34 | ||||
-rw-r--r-- | base/keyboard_code_conversion_mac.mm | 293 | ||||
-rw-r--r-- | chrome/browser/automation/ui_controls.h | 4 | ||||
-rw-r--r-- | chrome/browser/automation/ui_controls_internal.h | 40 | ||||
-rw-r--r-- | chrome/browser/automation/ui_controls_linux.cc | 22 | ||||
-rw-r--r-- | chrome/browser/automation/ui_controls_mac.mm | 270 | ||||
-rw-r--r-- | chrome/browser/browser_focus_uitest.cc | 102 | ||||
-rw-r--r-- | chrome/browser/browser_keyevents_browsertest.cc | 485 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 1 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 4 | ||||
-rw-r--r-- | chrome/test/data/keyevents_test.html | 25 | ||||
-rw-r--r-- | chrome/test/interactive_ui/interactive_ui_tests.gypi | 2 | ||||
-rw-r--r-- | chrome/test/ui_test_utils.h | 7 | ||||
-rw-r--r-- | chrome/test/ui_test_utils_linux.cc | 8 | ||||
-rw-r--r-- | chrome/test/ui_test_utils_mac.cc | 31 | ||||
-rw-r--r-- | chrome/test/ui_test_utils_mac.mm | 66 | ||||
-rw-r--r-- | chrome/test/ui_test_utils_win.cc | 12 |
19 files changed, 1121 insertions, 294 deletions
diff --git a/base/base.gypi b/base/base.gypi index ff05d2a..2553ab5 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -421,6 +421,8 @@ 'json/string_escape.h', 'keyboard_code_conversion_gtk.cc', 'keyboard_code_conversion_gtk.h', + 'keyboard_code_conversion_mac.mm', + 'keyboard_code_conversion_mac.h', 'keyboard_codes.h', 'keyboard_codes_win.h', 'keyboard_codes_posix.h', diff --git a/base/keyboard_code_conversion_gtk.cc b/base/keyboard_code_conversion_gtk.cc index 91de891..9e6bfd1 100644 --- a/base/keyboard_code_conversion_gtk.cc +++ b/base/keyboard_code_conversion_gtk.cc @@ -33,9 +33,6 @@ // WindowsKeyCodeForGdkKeyCode is copied from platform/gtk/KeyEventGtk.cpp -#ifndef BASE_KEYBOARD_CODE_CONVERSION_GTK_H_ -#define BASE_KEYBOARD_CODE_CONVERSION_GTK_H_ - #include "base/keyboard_code_conversion_gtk.h" #include <gdk/gdkkeysyms.h> @@ -615,6 +612,4 @@ int GdkKeyCodeForWindowsKeyCode(base::KeyboardCode keycode, bool shift) { } } -} // namespace - -#endif // BASE_KEYBOARD_CODE_CONVERSION_GTK_H_ +} // namespace base diff --git a/base/keyboard_code_conversion_mac.h b/base/keyboard_code_conversion_mac.h new file mode 100644 index 0000000..9db6e47 --- /dev/null +++ b/base/keyboard_code_conversion_mac.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef BASE_KEYBOARD_CODE_CONVERSION_MAC_H_ +#define BASE_KEYBOARD_CODE_CONVERSION_MAC_H_ + +#import <Cocoa/Cocoa.h> +#include "basictypes.h" +#include "base/keyboard_codes_posix.h" + +namespace base { + +// We use windows virtual keycodes throughout our keyboard event related code, +// including unit tests. But Mac uses a different set of virtual keycodes. +// This function converts a windows virtual keycode into Mac's virtual key code +// and corresponding unicode character. |flags| is the modifiers mask such +// as NSControlKeyMask, NSShiftKeyMask, etc. +// When success, the corresponding Mac's virtual key code will be returned. +// The corresponding unicode character will be stored in |character|, and the +// corresponding unicode character ignoring the modifiers will be stored in +// |characterIgnoringModifiers|. +// -1 will be returned if the keycode can't be converted. +// This function is mainly for simulating keyboard events in unit tests. +// See third_party/WebKit/WebKit/chromium/src/mac/WebInputEventFactory.mm for +// reverse conversion. +int MacKeyCodeForWindowsKeyCode(KeyboardCode keycode, + NSUInteger flags, + unichar* character, + unichar* characterIgnoringModifiers); + +} // namespace base + +#endif // BASE_KEYBOARD_CODE_CONVERSION_MAC_H_ diff --git a/base/keyboard_code_conversion_mac.mm b/base/keyboard_code_conversion_mac.mm new file mode 100644 index 0000000..79c1b19 --- /dev/null +++ b/base/keyboard_code_conversion_mac.mm @@ -0,0 +1,293 @@ +// 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. + +#import "base/keyboard_code_conversion_mac.h" + +#include <algorithm> +#import <Carbon/Carbon.h> + +#include "base/logging.h" + +namespace base { + +namespace { + +// A struct to hold a Windows keycode to Mac virtual keycode mapping. +struct KeyCodeMap { + KeyboardCode keycode; + int macKeycode; + unichar characterIgnoringModifiers; +}; + +// Customized less operator for using std::lower_bound() on a KeyCodeMap array. +bool operator<(const KeyCodeMap& a, const KeyCodeMap& b) { + return a.keycode < b.keycode; +} + +// This array must keep sorted ascending according to the value of |keycode|, +// so that we can binary search it. +// TODO(suzhe): This map is not complete, missing entries have macKeycode == -1. +const KeyCodeMap kKeyCodesMap[] = { + { VKEY_BACK /* 0x08 */, kVK_Delete, kBackspaceCharCode }, + { VKEY_TAB /* 0x09 */, kVK_Tab, kTabCharCode }, + { VKEY_CLEAR /* 0x0C */, kVK_ANSI_KeypadClear, kClearCharCode }, + { VKEY_RETURN /* 0x0D */, kVK_Return, kReturnCharCode }, + { VKEY_SHIFT /* 0x10 */, kVK_Shift, 0 }, + { VKEY_CONTROL /* 0x11 */, kVK_Control, 0 }, + { VKEY_MENU /* 0x12 */, kVK_Option, 0 }, + { VKEY_PAUSE /* 0x13 */, -1, NSPauseFunctionKey }, + { VKEY_CAPITAL /* 0x14 */, kVK_CapsLock, 0 }, + { VKEY_KANA /* 0x15 */, kVK_JIS_Kana, 0 }, + { VKEY_HANGUL /* 0x15 */, -1, 0 }, + { VKEY_JUNJA /* 0x17 */, -1, 0 }, + { VKEY_FINAL /* 0x18 */, -1, 0 }, + { VKEY_HANJA /* 0x19 */, -1, 0 }, + { VKEY_KANJI /* 0x19 */, -1, 0 }, + { VKEY_ESCAPE /* 0x1B */, kVK_Escape, kEscapeCharCode }, + { VKEY_CONVERT /* 0x1C */, -1, 0 }, + { VKEY_NONCONVERT /* 0x1D */, -1, 0 }, + { VKEY_ACCEPT /* 0x1E */, -1, 0 }, + { VKEY_MODECHANGE /* 0x1F */, -1, 0 }, + { VKEY_SPACE /* 0x20 */, kVK_Space, kSpaceCharCode }, + { VKEY_PRIOR /* 0x21 */, kVK_PageUp, NSPageUpFunctionKey }, + { VKEY_NEXT /* 0x22 */, kVK_PageDown, NSPageDownFunctionKey }, + { VKEY_END /* 0x23 */, kVK_End, NSEndFunctionKey }, + { VKEY_HOME /* 0x24 */, kVK_Home, NSHomeFunctionKey }, + { VKEY_LEFT /* 0x25 */, kVK_LeftArrow, NSLeftArrowFunctionKey }, + { VKEY_UP /* 0x26 */, kVK_UpArrow, NSUpArrowFunctionKey }, + { VKEY_RIGHT /* 0x27 */, kVK_RightArrow, NSRightArrowFunctionKey }, + { VKEY_DOWN /* 0x28 */, kVK_DownArrow, NSDownArrowFunctionKey }, + { VKEY_SELECT /* 0x29 */, -1, 0 }, + { VKEY_PRINT /* 0x2A */, -1, NSPrintFunctionKey }, + { VKEY_EXECUTE /* 0x2B */, -1, NSExecuteFunctionKey }, + { VKEY_SNAPSHOT /* 0x2C */, -1, NSPrintScreenFunctionKey }, + { VKEY_INSERT /* 0x2D */, -1, NSInsertFunctionKey }, + { VKEY_DELETE /* 0x2E */, kVK_ForwardDelete, kDeleteCharCode }, + { VKEY_HELP /* 0x2F */, kVK_Help, kHelpCharCode }, + { VKEY_0 /* 0x30 */, kVK_ANSI_0, '0' }, + { VKEY_1 /* 0x31 */, kVK_ANSI_1, '1' }, + { VKEY_2 /* 0x32 */, kVK_ANSI_2, '2' }, + { VKEY_3 /* 0x33 */, kVK_ANSI_3, '3' }, + { VKEY_4 /* 0x34 */, kVK_ANSI_4, '4' }, + { VKEY_5 /* 0x35 */, kVK_ANSI_5, '5' }, + { VKEY_6 /* 0x36 */, kVK_ANSI_6, '6' }, + { VKEY_7 /* 0x37 */, kVK_ANSI_7, '7' }, + { VKEY_8 /* 0x38 */, kVK_ANSI_8, '8' }, + { VKEY_9 /* 0x39 */, kVK_ANSI_9, '9' }, + { VKEY_A /* 0x41 */, kVK_ANSI_A, 'a' }, + { VKEY_B /* 0x42 */, kVK_ANSI_B, 'b' }, + { VKEY_C /* 0x43 */, kVK_ANSI_C, 'c' }, + { VKEY_D /* 0x44 */, kVK_ANSI_D, 'd' }, + { VKEY_E /* 0x45 */, kVK_ANSI_E, 'e' }, + { VKEY_F /* 0x46 */, kVK_ANSI_F, 'f' }, + { VKEY_G /* 0x47 */, kVK_ANSI_G, 'g' }, + { VKEY_H /* 0x48 */, kVK_ANSI_H, 'h' }, + { VKEY_I /* 0x49 */, kVK_ANSI_I, 'i' }, + { VKEY_J /* 0x4A */, kVK_ANSI_J, 'j' }, + { VKEY_K /* 0x4B */, kVK_ANSI_K, 'k' }, + { VKEY_L /* 0x4C */, kVK_ANSI_L, 'l' }, + { VKEY_M /* 0x4D */, kVK_ANSI_M, 'm' }, + { VKEY_N /* 0x4E */, kVK_ANSI_N, 'n' }, + { VKEY_O /* 0x4F */, kVK_ANSI_O, 'o' }, + { VKEY_P /* 0x50 */, kVK_ANSI_P, 'p' }, + { VKEY_Q /* 0x51 */, kVK_ANSI_Q, 'q' }, + { VKEY_R /* 0x52 */, kVK_ANSI_R, 'r' }, + { VKEY_S /* 0x53 */, kVK_ANSI_S, 's' }, + { VKEY_T /* 0x54 */, kVK_ANSI_T, 't' }, + { VKEY_U /* 0x55 */, kVK_ANSI_U, 'u' }, + { VKEY_V /* 0x56 */, kVK_ANSI_V, 'v' }, + { VKEY_W /* 0x57 */, kVK_ANSI_W, 'w' }, + { VKEY_X /* 0x58 */, kVK_ANSI_X, 'x' }, + { VKEY_Y /* 0x59 */, kVK_ANSI_Y, 'y' }, + { VKEY_Z /* 0x5A */, kVK_ANSI_Z, 'z' }, + { VKEY_LWIN /* 0x5B */, kVK_Command, 0 }, + { VKEY_RWIN /* 0x5C */, 0x36, 0 }, + { VKEY_APPS /* 0x5D */, 0x36, 0 }, + { VKEY_SLEEP /* 0x5F */, -1, 0 }, + { VKEY_NUMPAD0 /* 0x60 */, kVK_ANSI_Keypad0, '0' }, + { VKEY_NUMPAD1 /* 0x61 */, kVK_ANSI_Keypad1, '1' }, + { VKEY_NUMPAD2 /* 0x62 */, kVK_ANSI_Keypad2, '2' }, + { VKEY_NUMPAD3 /* 0x63 */, kVK_ANSI_Keypad3, '3' }, + { VKEY_NUMPAD4 /* 0x64 */, kVK_ANSI_Keypad4, '4' }, + { VKEY_NUMPAD5 /* 0x65 */, kVK_ANSI_Keypad5, '5' }, + { VKEY_NUMPAD6 /* 0x66 */, kVK_ANSI_Keypad6, '6' }, + { VKEY_NUMPAD7 /* 0x67 */, kVK_ANSI_Keypad7, '7' }, + { VKEY_NUMPAD8 /* 0x68 */, kVK_ANSI_Keypad8, '8' }, + { VKEY_NUMPAD9 /* 0x69 */, kVK_ANSI_Keypad9, '9' }, + { VKEY_MULTIPLY /* 0x6A */, kVK_ANSI_KeypadMultiply, '*' }, + { VKEY_ADD /* 0x6B */, kVK_ANSI_KeypadPlus, '+' }, + { VKEY_SEPARATOR /* 0x6C */, -1, 0 }, + { VKEY_SUBTRACT /* 0x6D */, kVK_ANSI_KeypadMinus, '-' }, + { VKEY_DECIMAL /* 0x6E */, kVK_ANSI_KeypadDecimal, '.' }, + { VKEY_DIVIDE /* 0x6F */, kVK_ANSI_KeypadDivide, '/' }, + { VKEY_F1 /* 0x70 */, kVK_F1, NSF1FunctionKey }, + { VKEY_F2 /* 0x71 */, kVK_F2, NSF2FunctionKey }, + { VKEY_F3 /* 0x72 */, kVK_F3, NSF3FunctionKey }, + { VKEY_F4 /* 0x73 */, kVK_F4, NSF4FunctionKey }, + { VKEY_F5 /* 0x74 */, kVK_F5, NSF5FunctionKey }, + { VKEY_F6 /* 0x75 */, kVK_F6, NSF6FunctionKey }, + { VKEY_F7 /* 0x76 */, kVK_F7, NSF7FunctionKey }, + { VKEY_F8 /* 0x77 */, kVK_F8, NSF8FunctionKey }, + { VKEY_F9 /* 0x78 */, kVK_F9, NSF9FunctionKey }, + { VKEY_F10 /* 0x79 */, kVK_F10, NSF10FunctionKey }, + { VKEY_F11 /* 0x7A */, kVK_F11, NSF11FunctionKey }, + { VKEY_F12 /* 0x7B */, kVK_F12, NSF12FunctionKey }, + { VKEY_F13 /* 0x7C */, kVK_F13, NSF13FunctionKey }, + { VKEY_F14 /* 0x7D */, kVK_F14, NSF14FunctionKey }, + { VKEY_F15 /* 0x7E */, kVK_F15, NSF15FunctionKey }, + { VKEY_F16 /* 0x7F */, kVK_F16, NSF16FunctionKey }, + { VKEY_F17 /* 0x80 */, kVK_F17, NSF17FunctionKey }, + { VKEY_F18 /* 0x81 */, kVK_F18, NSF18FunctionKey }, + { VKEY_F19 /* 0x82 */, kVK_F19, NSF19FunctionKey }, + { VKEY_F20 /* 0x83 */, kVK_F20, NSF20FunctionKey }, + { VKEY_F21 /* 0x84 */, -1, NSF21FunctionKey }, + { VKEY_F22 /* 0x85 */, -1, NSF22FunctionKey }, + { VKEY_F23 /* 0x86 */, -1, NSF23FunctionKey }, + { VKEY_F24 /* 0x87 */, -1, NSF24FunctionKey }, + { VKEY_NUMLOCK /* 0x90 */, -1, 0 }, + { VKEY_SCROLL /* 0x91 */, -1, NSScrollLockFunctionKey }, + { VKEY_LSHIFT /* 0xA0 */, kVK_Shift, 0 }, + { VKEY_RSHIFT /* 0xA1 */, kVK_Shift, 0 }, + { VKEY_LCONTROL /* 0xA2 */, kVK_Control, 0 }, + { VKEY_RCONTROL /* 0xA3 */, kVK_Control, 0 }, + { VKEY_LMENU /* 0xA4 */, -1, 0 }, + { VKEY_RMENU /* 0xA5 */, -1, 0 }, + { VKEY_BROWSER_BACK /* 0xA6 */, -1, 0 }, + { VKEY_BROWSER_FORWARD /* 0xA7 */, -1, 0 }, + { VKEY_BROWSER_REFRESH /* 0xA8 */, -1, 0 }, + { VKEY_BROWSER_STOP /* 0xA9 */, -1, 0 }, + { VKEY_BROWSER_SEARCH /* 0xAA */, -1, 0 }, + { VKEY_BROWSER_FAVORITES /* 0xAB */, -1, 0 }, + { VKEY_BROWSER_HOME /* 0xAC */, -1, 0 }, + { VKEY_VOLUME_MUTE /* 0xAD */, -1, 0 }, + { VKEY_VOLUME_DOWN /* 0xAE */, -1, 0 }, + { VKEY_VOLUME_UP /* 0xAF */, -1, 0 }, + { VKEY_MEDIA_NEXT_TRACK /* 0xB0 */, -1, 0 }, + { VKEY_MEDIA_PREV_TRACK /* 0xB1 */, -1, 0 }, + { VKEY_MEDIA_STOP /* 0xB2 */, -1, 0 }, + { VKEY_MEDIA_PLAY_PAUSE /* 0xB3 */, -1, 0 }, + { VKEY_MEDIA_LAUNCH_MAIL /* 0xB4 */, -1, 0 }, + { VKEY_MEDIA_LAUNCH_MEDIA_SELECT /* 0xB5 */, -1, 0 }, + { VKEY_MEDIA_LAUNCH_APP1 /* 0xB6 */, -1, 0 }, + { VKEY_MEDIA_LAUNCH_APP2 /* 0xB7 */, -1, 0 }, + { VKEY_OEM_1 /* 0xBA */, kVK_ANSI_Semicolon, ';' }, + { VKEY_OEM_PLUS /* 0xBB */, kVK_ANSI_Equal, '=' }, + { VKEY_OEM_COMMA /* 0xBC */, kVK_ANSI_Comma, ',' }, + { VKEY_OEM_MINUS /* 0xBD */, kVK_ANSI_Minus, '-' }, + { VKEY_OEM_PERIOD /* 0xBE */, kVK_ANSI_Period, '.' }, + { VKEY_OEM_2 /* 0xBF */, kVK_ANSI_Slash, '/' }, + { VKEY_OEM_3 /* 0xC0 */, kVK_ANSI_Grave, '`' }, + { VKEY_OEM_4 /* 0xDB */, kVK_ANSI_LeftBracket, '[' }, + { VKEY_OEM_5 /* 0xDC */, kVK_ANSI_Backslash, '\\' }, + { VKEY_OEM_6 /* 0xDD */, kVK_ANSI_RightBracket, ']' }, + { VKEY_OEM_7 /* 0xDE */, kVK_ANSI_Quote, '\'' }, + { VKEY_OEM_8 /* 0xDF */, -1, 0 }, + { VKEY_OEM_102 /* 0xE2 */, -1, 0 }, + { VKEY_PROCESSKEY /* 0xE5 */, -1, 0 }, + { VKEY_PACKET /* 0xE7 */, -1, 0 }, + { VKEY_ATTN /* 0xF6 */, -1, 0 }, + { VKEY_CRSEL /* 0xF7 */, -1, 0 }, + { VKEY_EXSEL /* 0xF8 */, -1, 0 }, + { VKEY_EREOF /* 0xF9 */, -1, 0 }, + { VKEY_PLAY /* 0xFA */, -1, 0 }, + { VKEY_ZOOM /* 0xFB */, -1, 0 }, + { VKEY_NONAME /* 0xFC */, -1, 0 }, + { VKEY_PA1 /* 0xFD */, -1, 0 }, + { VKEY_OEM_CLEAR /* 0xFE */, kVK_ANSI_KeypadClear, kClearCharCode } +}; + +// A convenient array for getting symbol characters on the number keys. +const char kShiftCharsForNumberKeys[] = ")!@#$%^&*("; + +} // anonymous namespace + +int MacKeyCodeForWindowsKeyCode(KeyboardCode keycode, + NSUInteger flags, + unichar* character, + unichar* characterIgnoringModifiers) { + KeyCodeMap from; + from.keycode = keycode; + + const KeyCodeMap* ptr = std::lower_bound( + kKeyCodesMap, kKeyCodesMap + arraysize(kKeyCodesMap), from); + + if (ptr >= kKeyCodesMap + arraysize(kKeyCodesMap) || + ptr->keycode != keycode || ptr->macKeycode == -1) { + NOTREACHED() << "Unsupported keycode:" << keycode; + return -1; + } + + int macKeycode = ptr->macKeycode; + if (characterIgnoringModifiers) + *characterIgnoringModifiers = ptr->characterIgnoringModifiers; + + if (!character) + return macKeycode; + + *character = ptr->characterIgnoringModifiers; + + // Fill in |character| according to flags. + if (flags & NSShiftKeyMask) { + if (keycode >= VKEY_0 && keycode <= VKEY_9) { + *character = kShiftCharsForNumberKeys[keycode - VKEY_0]; + } else if (keycode >= VKEY_A && keycode <= VKEY_Z) { + *character = 'A' + (keycode - VKEY_A); + } else { + switch (macKeycode) { + case kVK_ANSI_Grave: + *character = '~'; + break; + case kVK_ANSI_Minus: + *character = '_'; + break; + case kVK_ANSI_Equal: + *character = '+'; + break; + case kVK_ANSI_LeftBracket: + *character = '{'; + break; + case kVK_ANSI_RightBracket: + *character = '}'; + break; + case kVK_ANSI_Backslash: + *character = '|'; + break; + case kVK_ANSI_Semicolon: + *character = ':'; + break; + case kVK_ANSI_Quote: + *character = '\"'; + break; + case kVK_ANSI_Comma: + *character = '<'; + break; + case kVK_ANSI_Period: + *character = '>'; + break; + case kVK_ANSI_Slash: + *character = '?'; + break; + default: + break; + } + } + } + + // Control characters. + if (flags & NSControlKeyMask) { + if (keycode >= VKEY_A && keycode <= VKEY_Z) + *character = 1 + keycode - VKEY_A; + else if (macKeycode == kVK_ANSI_LeftBracket) + *character = 27; + else if (macKeycode == kVK_ANSI_Backslash) + *character = 28; + else if (macKeycode == kVK_ANSI_RightBracket) + *character = 29; + } + + // TODO(suzhe): Support characters for Option key bindings. + return macKeycode; +} + +} // namespace base diff --git a/chrome/browser/automation/ui_controls.h b/chrome/browser/automation/ui_controls.h index 9c47f8d..63ba677 100644 --- a/chrome/browser/automation/ui_controls.h +++ b/chrome/browser/automation/ui_controls.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// 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. @@ -90,7 +90,7 @@ void MoveMouseToCenterAndPress( #elif defined(TOOLKIT_GTK) GtkWidget* widget, #elif defined(OS_MACOSX) - NSWindow* window, + NSView* view, #endif MouseButton button, int state, diff --git a/chrome/browser/automation/ui_controls_internal.h b/chrome/browser/automation/ui_controls_internal.h new file mode 100644 index 0000000..88631c9 --- /dev/null +++ b/chrome/browser/automation/ui_controls_internal.h @@ -0,0 +1,40 @@ +// 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. + +#ifndef CHROME_BROWSER_AUTOMATION_UI_CONTROLS_INTERNAL_H_ +#define CHROME_BROWSER_AUTOMATION_UI_CONTROLS_INTERNAL_H_ + +#include "base/message_loop.h" +#include "chrome/browser/automation/ui_controls.h" + +namespace ui_controls { + +// A utility class to send a mouse click event in a task. +// It's shared by ui_controls_linux.cc and ui_controls_mac.cc +class ClickTask : public Task { + public: + // A |followup| Task can be specified to notify the caller when the mouse + // click event is sent. If can be NULL if the caller does not care about it. + ClickTask(MouseButton button, int state, Task* followup) + : button_(button), state_(state), followup_(followup) { + } + + virtual ~ClickTask() {} + + virtual void Run() { + if (followup_) + SendMouseEventsNotifyWhenDone(button_, state_, followup_); + else + SendMouseEvents(button_, state_); + } + + private: + MouseButton button_; + int state_; + Task* followup_; +}; + +} // ui_controls + +#endif // CHROME_BROWSER_AUTOMATION_UI_CONTROLS_INTERNAL_H_ diff --git a/chrome/browser/automation/ui_controls_linux.cc b/chrome/browser/automation/ui_controls_linux.cc index c9dca78..4c19b7d 100644 --- a/chrome/browser/automation/ui_controls_linux.cc +++ b/chrome/browser/automation/ui_controls_linux.cc @@ -12,6 +12,7 @@ #include "base/keyboard_code_conversion_gtk.h" #include "base/logging.h" #include "base/message_loop.h" +#include "chrome/browser/automation/ui_controls_internal.h" #include "chrome/browser/gtk/gtk_util.h" #include "chrome/test/automation/automation_constants.h" @@ -62,27 +63,6 @@ class EventWaiter : public MessageLoopForUI::Observer { int count_; }; -class ClickTask : public Task { - public: - ClickTask(ui_controls::MouseButton button, int state, Task* followup) - : button_(button), state_(state), followup_(followup) { - } - - virtual ~ClickTask() {} - - virtual void Run() { - if (followup_) - ui_controls::SendMouseEventsNotifyWhenDone(button_, state_, followup_); - else - ui_controls::SendMouseEvents(button_, state_); - } - - private: - ui_controls::MouseButton button_; - int state_; - Task* followup_; -}; - void FakeAMouseMotionEvent(gint x, gint y) { GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY); diff --git a/chrome/browser/automation/ui_controls_mac.mm b/chrome/browser/automation/ui_controls_mac.mm index 715135c1..d95dbc2 100644 --- a/chrome/browser/automation/ui_controls_mac.mm +++ b/chrome/browser/automation/ui_controls_mac.mm @@ -6,8 +6,11 @@ #import <Cocoa/Cocoa.h> #include <mach/mach_time.h> +#include <vector> +#include "base/keyboard_code_conversion_mac.h" #include "base/message_loop.h" +#include "chrome/browser/automation/ui_controls_internal.h" #include "chrome/browser/chrome_thread.h" // Implementation details: We use [NSApplication sendEvent:] instead @@ -22,6 +25,23 @@ // into the event queue. (I can post other kinds of tasks but can't // guarantee their order with regards to events). +// But [NSApplication sendEvent:] causes a problem when sending mouse click +// events. Because in order to handle mouse drag, when processing a mouse +// click event, the application may want to retrieve the next event +// synchronously by calling NSApplication's nextEventMatchingMask method. +// In this case, [NSApplication sendEvent:] causes deadlock. +// So we need to use [NSApplication postEvent:atStart:] for mouse click +// events. In order to notify the caller correctly after all events has been +// processed, we setup a task to watch for the event queue time to time and +// notify the caller as soon as there is no event in the queue. +// +// TODO(suzhe): +// 1. Investigate why using [NSApplication postEvent:atStart:] for keyboard +// events causes BrowserKeyEventsTest.CommandKeyEvents to fail. +// See http://crbug.com/49270 +// 2. On OSX 10.6, [NSEvent addLocalMonitorForEventsMatchingMask:handler:] may +// be used, so that we don't need to poll the event queue time to time. + namespace { // From @@ -53,6 +73,148 @@ NSTimeInterval TimeIntervalSinceSystemStartup() { return UpTimeInNanoseconds() / 1000000000.0; } +// Creates and returns an autoreleased key event. +NSEvent* SynthesizeKeyEvent(NSWindow* window, + bool keyDown, + base::KeyboardCode keycode, + NSUInteger flags) { + unichar character; + unichar characterIgnoringModifiers; + int macKeycode = base::MacKeyCodeForWindowsKeyCode( + keycode, flags, &character, &characterIgnoringModifiers); + + if (macKeycode < 0) + return nil; + + NSString* charactersIgnoringModifiers = + [[[NSString alloc] initWithCharacters:&characterIgnoringModifiers + length:1] + autorelease]; + NSString* characters = + [[[NSString alloc] initWithCharacters:&character length:1] autorelease]; + + NSEventType type = (keyDown ? NSKeyDown : NSKeyUp); + + // Modifier keys generate NSFlagsChanged event rather than + // NSKeyDown/NSKeyUp events. + if (keycode == base::VKEY_CONTROL || keycode == base::VKEY_SHIFT || + keycode == base::VKEY_MENU || keycode == base::VKEY_COMMAND) + type = NSFlagsChanged; + + // For events other than mouse moved, [event locationInWindow] is + // UNDEFINED if the event is not NSMouseMoved. Thus, the (0,0) + // location should be fine. + NSEvent* event = + [NSEvent keyEventWithType:type + location:NSMakePoint(0, 0) + modifierFlags:flags + timestamp:TimeIntervalSinceSystemStartup() + windowNumber:[window windowNumber] + context:nil + characters:characters + charactersIgnoringModifiers:charactersIgnoringModifiers + isARepeat:NO + keyCode:(unsigned short)macKeycode]; + + return event; +} + +// Creates the proper sequence of autoreleased key events for a key down + up. +void SynthesizeKeyEventsSequence(NSWindow* window, + base::KeyboardCode keycode, + bool control, + bool shift, + bool alt, + bool command, + std::vector<NSEvent*>* events) { + NSEvent* event = nil; + NSUInteger flags = 0; + if (control) { + flags |= NSControlKeyMask; + event = SynthesizeKeyEvent(window, true, base::VKEY_CONTROL, flags); + DCHECK(event); + events->push_back(event); + } + if (shift) { + flags |= NSShiftKeyMask; + event = SynthesizeKeyEvent(window, true, base::VKEY_SHIFT, flags); + DCHECK(event); + events->push_back(event); + } + if (alt) { + flags |= NSAlternateKeyMask; + event = SynthesizeKeyEvent(window, true, base::VKEY_MENU, flags); + DCHECK(event); + events->push_back(event); + } + if (command) { + flags |= NSCommandKeyMask; + event = SynthesizeKeyEvent(window, true, base::VKEY_COMMAND, flags); + DCHECK(event); + events->push_back(event); + } + + event = SynthesizeKeyEvent(window, true, keycode, flags); + DCHECK(event); + events->push_back(event); + event = SynthesizeKeyEvent(window, false, keycode, flags); + DCHECK(event); + events->push_back(event); + + if (command) { + flags &= ~NSCommandKeyMask; + event = SynthesizeKeyEvent(window, false, base::VKEY_COMMAND, flags); + DCHECK(event); + events->push_back(event); + } + if (alt) { + flags &= ~NSAlternateKeyMask; + event = SynthesizeKeyEvent(window, false, base::VKEY_MENU, flags); + DCHECK(event); + events->push_back(event); + } + if (shift) { + flags &= ~NSShiftKeyMask; + event = SynthesizeKeyEvent(window, false, base::VKEY_SHIFT, flags); + DCHECK(event); + events->push_back(event); + } + if (control) { + flags &= ~NSControlKeyMask; + event = SynthesizeKeyEvent(window, false, base::VKEY_CONTROL, flags); + DCHECK(event); + events->push_back(event); + } +} + +// A task class to watch for the event queue. The specific task will be fired +// when there is no more event in the queue. +class EventQueueWatcher : public Task { + public: + EventQueueWatcher(Task* task) : task_(task) {} + + virtual ~EventQueueWatcher() {} + + virtual void Run() { + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:nil + inMode:NSDefaultRunLoopMode + dequeue:NO]; + // If there is still event in the queue, then we need to check again. + if (event) + MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task_)); + else + MessageLoop::current()->PostTask(FROM_HERE, task_); + } + + private: + Task* task_; +}; + +// Stores the current mouse location on the screen. So that we can use it +// when firing keyboard and mouse click events. +NSPoint g_mouse_location = { 0, 0 }; + } // anonymous namespace @@ -71,8 +233,6 @@ bool SendKeyPress(gfx::NativeWindow window, // 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, @@ -81,55 +241,23 @@ bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, 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]; + std::vector<NSEvent*> events; + SynthesizeKeyEventsSequence( + window, key, control, shift, alt, command, &events); + + // TODO(suzhe): Using [NSApplication postEvent:atStart:] here causes + // BrowserKeyEventsTest.CommandKeyEvents to fail. See http://crbug.com/49270 + // But using [NSApplication sendEvent:] should be safe for keyboard events, + // because until now, no code wants to retrieve the next event when handling + // a keyboard event. + for (std::vector<NSEvent*>::iterator iter = events.begin(); + iter != events.end(); ++iter) + [[NSApplication sharedApplication] sendEvent:*iter]; if (task) - MessageLoop::current()->PostTask(FROM_HERE, task); + MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task)); + return true; } @@ -144,7 +272,8 @@ bool SendMouseMove(long x, long y) { 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! + g_mouse_location = NSMakePoint(x, screenHeight - y); // flip! + NSPoint pointInWindow = g_mouse_location; if (window) pointInWindow = [window convertScreenToBase:pointInWindow]; NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); @@ -160,8 +289,10 @@ bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { clickCount:0 pressure:0.0]; [[NSApplication sharedApplication] postEvent:event atStart:NO]; + if (task) - MessageLoop::current()->PostTask(FROM_HERE, task); + MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task)); + return true; } @@ -176,7 +307,6 @@ bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { return (SendMouseEventsNotifyWhenDone(type, DOWN, NULL) && SendMouseEventsNotifyWhenDone(type, UP, task)); } - NSEventType etype = 0; if (type == LEFT) { if (state == UP) { @@ -200,8 +330,7 @@ bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { return false; } NSWindow* window = [[NSApplication sharedApplication] keyWindow]; - NSPoint location = [NSEvent mouseLocation]; - NSPoint pointInWindow = location; + NSPoint pointInWindow = g_mouse_location; if (window) pointInWindow = [window convertScreenToBase:pointInWindow]; @@ -213,11 +342,13 @@ bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { windowNumber:[window windowNumber] context:nil eventNumber:0 - clickCount:0 - pressure:0.0]; - [[NSApplication sharedApplication] sendEvent:event]; + clickCount:1 + pressure:(state == DOWN ? 1.0 : 0.0 )]; + [[NSApplication sharedApplication] postEvent:event atStart:NO]; + if (task) - MessageLoop::current()->PostTask(FROM_HERE, task); + MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task)); + return true; } @@ -225,18 +356,27 @@ 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, + NSView* view, MouseButton button, int state, Task* task) { - NOTIMPLEMENTED(); + DCHECK(view); + NSWindow* window = [view window]; + DCHECK(window); + NSScreen* screen = [window screen]; + DCHECK(screen); + + // Converts the center position of the view into the coordinates accepted + // by SendMouseMoveNotifyWhenDone() method. + NSRect bounds = [view bounds]; + NSPoint center = NSMakePoint(NSMidX(bounds), NSMidY(bounds)); + center = [view convertPoint:center toView:nil]; + center = [window convertBaseToScreen:center]; + center = NSMakePoint(center.x, [screen frame].size.height - center.y); + + SendMouseMoveNotifyWhenDone(center.x, center.y, + new ClickTask(button, state, task)); } } // ui_controls diff --git a/chrome/browser/browser_focus_uitest.cc b/chrome/browser/browser_focus_uitest.cc index de13199..7513625 100644 --- a/chrome/browser/browser_focus_uitest.cc +++ b/chrome/browser/browser_focus_uitest.cc @@ -5,6 +5,7 @@ #include "build/build_config.h" #include "base/format_macros.h" +#include "base/logging.h" #include "base/message_loop.h" #include "base/ref_counted.h" #include "chrome/browser/automation/ui_controls.h" @@ -19,9 +20,12 @@ #include "chrome/common/chrome_paths.h" #include "chrome/test/in_process_browser_test.h" #include "chrome/test/ui_test_utils.h" + +#if defined(TOOLKIT_VIEWS) || defined(OS_WIN) #include "views/focus/focus_manager.h" #include "views/view.h" #include "views/window/window.h" +#endif #if defined(TOOLKIT_VIEWS) #include "chrome/browser/views/frame/browser_view.h" @@ -34,12 +38,20 @@ #endif #if defined(OS_LINUX) +#define MAYBE_FocusTraversal FocusTraversal // For some reason we hit an external DNS lookup in this test in Linux but not // on Windows. TODO(estade): investigate. #define MAYBE_FocusTraversalOnInterstitial DISABLED_FocusTraversalOnInterstitial // TODO(jcampan): http://crbug.com/23683 #define MAYBE_TabsRememberFocusFindInPage FAILS_TabsRememberFocusFindInPage -#else +#elif defined(OS_MACOSX) +// TODO(suzhe): http://crbug.com/49738 (following two tests) +#define MAYBE_FocusTraversal FAILS_FocusTraversal +#define MAYBE_FocusTraversalOnInterstitial FAILS_FocusTraversalOnInterstitial +// TODO(suzhe): http://crbug.com/49737 +#define MAYBE_TabsRememberFocusFindInPage FAILS_TabsRememberFocusFindInPage +#elif defined(OS_WIN) +#define MAYBE_FocusTraversal FocusTraversal #define MAYBE_FocusTraversalOnInterstitial FocusTraversalOnInterstitial #define MAYBE_TabsRememberFocusFindInPage TabsRememberFocusFindInPage #endif @@ -70,28 +82,9 @@ class BrowserFocusTest : public InProcessBrowserTest { ui_test_utils::ClickOnView(browser(), vid); } - static void HideNativeWindow(gfx::NativeWindow window) { -#if defined(OS_WIN) - // TODO(jcampan): retrieve the WidgetWin and show/hide on it instead of - // using Windows API. - ::ShowWindow(window, SW_HIDE); -#elif defined(TOOLKIT_USES_GTK) - gtk_widget_hide(GTK_WIDGET(window)); -#else - NOTIMPLEMENTED(); -#endif - } - - static void ShowNativeWindow(gfx::NativeWindow window) { -#if defined(OS_WIN) - // TODO(jcampan): retrieve the WidgetWin and show/hide on it instead of - // using Windows API. - ::ShowWindow(window, SW_SHOW); -#elif defined(TOOLKIT_USES_GTK) - gtk_widget_hide(GTK_WIDGET(window)); -#else - NOTIMPLEMENTED(); -#endif + void BringBrowserWindowToFront() { + ui_test_utils::ShowAndFocusNativeWindow( + browser()->window()->GetNativeHandle()); } }; @@ -170,7 +163,8 @@ class TestInterstitialPage : public InterstitialPage { } // namespace IN_PROC_BROWSER_TEST_F(BrowserFocusTest, ClickingMovesFocus) { -#if defined(USE_X11) + BringBrowserWindowToFront(); +#if defined(USE_X11) || defined(OS_MACOSX) // It seems we have to wait a little bit for the widgets to spin up before // we can start clicking on them. MessageLoop::current()->PostDelayedTask(FROM_HERE, @@ -189,7 +183,9 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, ClickingMovesFocus) { } IN_PROC_BROWSER_TEST_F(BrowserFocusTest, BrowsersRememberFocus) { + BringBrowserWindowToFront(); HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); // First we navigate to our test page. GURL url = server->TestServerPage(kSimplePage); @@ -200,15 +196,15 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, BrowsersRememberFocus) { // The focus should be on the Tab contents. ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER_FOCUS_VIEW)); // Now hide the window, show it again, the focus should not have changed. - HideNativeWindow(window); - ShowNativeWindow(window); + ui_test_utils::HideNativeWindow(window); + ui_test_utils::ShowAndFocusNativeWindow(window); ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER_FOCUS_VIEW)); browser()->FocusLocationBar(); ASSERT_TRUE(IsViewFocused(VIEW_ID_LOCATION_BAR)); // Hide the window, show it again, the focus should not have changed. - HideNativeWindow(window); - ShowNativeWindow(window); + ui_test_utils::HideNativeWindow(window); + ui_test_utils::ShowAndFocusNativeWindow(window); ASSERT_TRUE(IsViewFocused(VIEW_ID_LOCATION_BAR)); // The rest of this test does not make sense on Linux because the behavior @@ -252,7 +248,9 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, BrowsersRememberFocus) { // Tabs remember focus. IN_PROC_BROWSER_TEST_F(BrowserFocusTest, TabsRememberFocus) { + BringBrowserWindowToFront(); HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); // First we navigate to our test page. GURL url = server->TestServerPage(kSimplePage); @@ -326,7 +324,9 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, TabsRememberFocus) { // Tabs remember focus with find-in-page box. IN_PROC_BROWSER_TEST_F(BrowserFocusTest, MAYBE_TabsRememberFocusFindInPage) { + BringBrowserWindowToFront(); HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); // First we navigate to our test page. GURL url = server->TestServerPage(kSimplePage); @@ -364,7 +364,9 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, MAYBE_TabsRememberFocusFindInPage) { // Background window does not steal focus. IN_PROC_BROWSER_TEST_F(BrowserFocusTest, BackgroundBrowserDontStealFocus) { + BringBrowserWindowToFront(); HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); // First we navigate to our test page. GURL url = server->TestServerPage(kSimplePage); @@ -393,6 +395,10 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, BackgroundBrowserDontStealFocus) { #elif defined(OS_WIN) focused_browser = browser(); unfocused_browser = browser2; +#elif defined(OS_MACOSX) + // On Mac, the newly created window always gets the focus. + focused_browser = browser2; + unfocused_browser = browser(); #endif GURL steal_focus_url = server->TestServerPage(kStealFocusPage); @@ -415,7 +421,9 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, BackgroundBrowserDontStealFocus) { // Page cannot steal focus when focus is on location bar. IN_PROC_BROWSER_TEST_F(BrowserFocusTest, LocationBarLockFocus) { + BringBrowserWindowToFront(); HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); // Open the page that steals focus. GURL url = server->TestServerPage(kStealFocusPage); @@ -435,8 +443,10 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, LocationBarLockFocus) { // focus has changed in the page. The notification in the renderer may change // at which point this test would fail (see comment in // RenderWidget::didFocus()). -IN_PROC_BROWSER_TEST_F(BrowserFocusTest, FocusTraversal) { +IN_PROC_BROWSER_TEST_F(BrowserFocusTest, MAYBE_FocusTraversal) { + BringBrowserWindowToFront(); HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); // First we navigate to our test page. GURL url = server->TestServerPage(kTypicalPage); @@ -528,7 +538,9 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, FocusTraversal) { // Focus traversal while an interstitial is showing. IN_PROC_BROWSER_TEST_F(BrowserFocusTest, MAYBE_FocusTraversalOnInterstitial) { + BringBrowserWindowToFront(); HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); // First we navigate to our test page. GURL url = server->TestServerPage(kSimplePage); @@ -618,7 +630,9 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, MAYBE_FocusTraversalOnInterstitial) { // Focus stays on page with interstitials. IN_PROC_BROWSER_TEST_F(BrowserFocusTest, InterstitialFocus) { + BringBrowserWindowToFront(); HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); // First we navigate to our test page. GURL url = server->TestServerPage(kSimplePage); @@ -653,7 +667,9 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, InterstitialFocus) { // Make sure Find box can request focus, even when it is already open. IN_PROC_BROWSER_TEST_F(BrowserFocusTest, FindFocusTest) { + BringBrowserWindowToFront(); HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); // Open some page (any page that doesn't steal focus). GURL url = server->TestServerPage(kTypicalPage); @@ -661,10 +677,17 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, FindFocusTest) { gfx::NativeWindow window = browser()->window()->GetNativeHandle(); +#if defined(OS_MACOSX) + // Press Cmd+F, which will make the Find box open and request focus. + ui_controls::SendKeyPressNotifyWhenDone(window, base::VKEY_F, false, + false, false, true, + new MessageLoop::QuitTask()); +#else // Press Ctrl+F, which will make the Find box open and request focus. ui_controls::SendKeyPressNotifyWhenDone(window, base::VKEY_F, true, false, false, false, new MessageLoop::QuitTask()); +#endif ui_test_utils::RunMessageLoop(); // Ideally, we wouldn't sleep here and instead would intercept the @@ -682,9 +705,15 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, FindFocusTest) { ASSERT_TRUE(IsViewFocused(VIEW_ID_LOCATION_BAR)); // Now press Ctrl+F again and focus should move to the Find box. +#if defined(OS_MACOSX) + ui_controls::SendKeyPressNotifyWhenDone(window, base::VKEY_F, false, + false, false, true, + new MessageLoop::QuitTask()); +#else ui_controls::SendKeyPressNotifyWhenDone(window, base::VKEY_F, true, false, false, false, new MessageLoop::QuitTask()); +#endif ui_test_utils::RunMessageLoop(); ASSERT_TRUE(IsViewFocused(VIEW_ID_FIND_IN_PAGE_TEXT_FIELD)); @@ -693,9 +722,15 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, FindFocusTest) { ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER_FOCUS_VIEW)); // Now press Ctrl+F again and focus should move to the Find box. - ui_controls::SendKeyPressNotifyWhenDone(window, base::VKEY_F, true, false, - false, false, +#if defined(OS_MACOSX) + ui_controls::SendKeyPressNotifyWhenDone(window, base::VKEY_F, false, + false, false, true, + new MessageLoop::QuitTask()); +#else + ui_controls::SendKeyPressNotifyWhenDone(window, base::VKEY_F, true, + false, false, false, new MessageLoop::QuitTask()); +#endif ui_test_utils::RunMessageLoop(); // See remark above on why we wait. @@ -708,6 +743,7 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, FindFocusTest) { // Makes sure the focus is in the right location when opening the different // types of tabs. IN_PROC_BROWSER_TEST_F(BrowserFocusTest, TabInitialFocus) { + BringBrowserWindowToFront(); // Open the history tab, focus should be on the tab contents. browser()->ShowHistoryTab(); @@ -732,7 +768,9 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, TabInitialFocus) { // Tests that focus goes where expected when using reload. IN_PROC_BROWSER_TEST_F(BrowserFocusTest, FocusOnReload) { + BringBrowserWindowToFront(); HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); // Open the new tab, reload. browser()->NewTab(); @@ -765,7 +803,9 @@ IN_PROC_BROWSER_TEST_F(BrowserFocusTest, FocusOnReload) { // Tests that focus goes where expected when using reload on a crashed tab. IN_PROC_BROWSER_TEST_F(BrowserFocusTest, MAYBE_FocusOnReloadCrashedTab) { + BringBrowserWindowToFront(); HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); // Open a regular page, crash, reload. ui_test_utils::NavigateToURL(browser(), server->TestServerPage(kSimplePage)); diff --git a/chrome/browser/browser_keyevents_browsertest.cc b/chrome/browser/browser_keyevents_browsertest.cc index 0b6e8fb..d3aae77 100644 --- a/chrome/browser/browser_keyevents_browsertest.cc +++ b/chrome/browser/browser_keyevents_browsertest.cc @@ -41,8 +41,11 @@ const wchar_t kSetFocusedElementJS[] = const wchar_t kGetTextBoxValueJS[] = L"window.domAutomationController.send(" L"document.getElementById('%ls').value);"; +const wchar_t kSetTextBoxValueJS[] = + L"window.domAutomationController.send(" + L"document.getElementById('%ls').value = '%ls');"; const wchar_t kStartTestJS[] = - L"window.domAutomationController.send(startTest());"; + L"window.domAutomationController.send(startTest(%d));"; // Maximum lenght of the result array in KeyEventTestData structure. const size_t kMaxResultLength = 10; @@ -51,10 +54,10 @@ const size_t kMaxResultLength = 10; // Each keyboard event may generate multiple result strings representing // the result of keydown, keypress, keyup and textInput events. // For keydown, keypress and keyup events, the format of the result string is: -// <type> <keyCode> <charCode> <ctrlKey> <shiftKey> <altKey> +// <type> <keyCode> <charCode> <ctrlKey> <shiftKey> <altKey> <commandKey> // where <type> may be 'D' (keydown), 'P' (keypress) or 'U' (keyup). -// <ctrlKey>, <shiftKey> and <altKey> are boolean value indicating the state of -// corresponding modifier key. +// <ctrlKey>, <shiftKey> <altKey> and <commandKey> are boolean value indicating +// the state of corresponding modifier key. // For textInput event, the format is: T <text>, where <text> is the text to be // input. // Please refer to chrome/test/data/keyevents_test.html for details. @@ -63,6 +66,7 @@ struct KeyEventTestData { bool ctrl; bool shift; bool alt; + bool command; bool suppress_keydown; bool suppress_keypress; @@ -131,12 +135,21 @@ class BrowserKeyEventsTest : public InProcessBrowserTest { ASSERT_TRUE(*native_window); } - void SendKey(base::KeyboardCode key, bool control, bool shift, bool alt) { + void BringBrowserWindowToFront() { + gfx::NativeWindow window = NULL; + ASSERT_NO_FATAL_FAILURE(GetNativeWindow(&window)); + ui_test_utils::ShowAndFocusNativeWindow(window); + } + + void SendKey(base::KeyboardCode key, + bool control, + bool shift, + bool alt, + bool command) { gfx::NativeWindow window = NULL; ASSERT_NO_FATAL_FAILURE(GetNativeWindow(&window)); EXPECT_TRUE(ui_controls::SendKeyPressNotifyWhenDone( - window, key, control, shift, alt, - false /* command, */, + window, key, control, shift, alt, command, new MessageLoop::QuitTask())); ui_test_utils::RunMessageLoop(); } @@ -238,12 +251,24 @@ class BrowserKeyEventsTest : public InProcessBrowserTest { ASSERT_EQ(WideToUTF8(value), actual); } - void StartTest(int tab_index) { + void SetTextBoxValue(int tab_index, const wchar_t* id, + const wchar_t* value) { + ASSERT_LT(tab_index, browser()->tab_count()); + std::string actual; + ASSERT_TRUE(ui_test_utils::ExecuteJavaScriptAndExtractString( + browser()->GetTabContentsAt(tab_index)->render_view_host(), + L"", + StringPrintf(kSetTextBoxValueJS, id, value), + &actual)); + ASSERT_EQ(WideToUTF8(value), actual); + } + + void StartTest(int tab_index, int result_length) { ASSERT_LT(tab_index, browser()->tab_count()); bool actual; ASSERT_TRUE(ui_test_utils::ExecuteJavaScriptAndExtractBool( browser()->GetTabContentsAt(tab_index)->render_view_host(), - L"", kStartTestJS, &actual)); + L"", StringPrintf(kStartTestJS, result_length), &actual)); ASSERT_TRUE(actual); } @@ -253,7 +278,7 @@ class BrowserKeyEventsTest : public InProcessBrowserTest { // Inform our testing web page that we are about to start testing a key // event. - ASSERT_NO_FATAL_FAILURE(StartTest(tab_index)); + ASSERT_NO_FATAL_FAILURE(StartTest(tab_index, test.result_length)); ASSERT_NO_FATAL_FAILURE(SuppressEvents( tab_index, test.suppress_keydown, test.suppress_keypress, test.suppress_keyup, test.suppress_textinput)); @@ -264,7 +289,8 @@ class BrowserKeyEventsTest : public InProcessBrowserTest { TestFinishObserver finish_observer( browser()->GetTabContentsAt(tab_index)->render_view_host()); - ASSERT_NO_FATAL_FAILURE(SendKey(test.key, test.ctrl, test.shift, test.alt)); + ASSERT_NO_FATAL_FAILURE( + SendKey(test.key, test.ctrl, test.shift, test.alt, test.command)); ASSERT_TRUE(finish_observer.WaitForFinish()); ASSERT_NO_FATAL_FAILURE(CheckResult( tab_index, test.result_length, test.result)); @@ -272,10 +298,10 @@ class BrowserKeyEventsTest : public InProcessBrowserTest { std::string GetTestDataDescription(const KeyEventTestData& data) { std::string desc = StringPrintf( - " VKEY:0x%02x, ctrl:%d, shift:%d, alt:%d\n" + " VKEY:0x%02x, ctrl:%d, shift:%d, alt:%d, command:%d\n" " Suppress: keydown:%d, keypress:%d, keyup:%d, textInput:%d\n" " Expected results(%d):\n", - data.key, data.ctrl, data.shift, data.alt, + data.key, data.ctrl, data.shift, data.alt, data.command, data.suppress_keydown, data.suppress_keypress, data.suppress_keyup, data.suppress_textinput, data.result_length); for (int i = 0; i < data.result_length; ++i) { @@ -289,69 +315,70 @@ class BrowserKeyEventsTest : public InProcessBrowserTest { } // namespace -// Flaky since r51395. See crbug.com/48671. -IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, FLAKY_NormalKeyEvents) { +IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, NormalKeyEvents) { static const KeyEventTestData kTestNoInput[] = { // a - { base::VKEY_A, false, false, false, + { base::VKEY_A, false, false, false, false, false, false, false, false, 3, - { "D 65 0 false false false", - "P 97 97 false false false", - "U 65 0 false false false" } }, + { "D 65 0 false false false false", + "P 97 97 false false false false", + "U 65 0 false false false false" } }, // shift-a - { base::VKEY_A, false, true, false, + { base::VKEY_A, false, true, false, false, false, false, false, false, 5, - { "D 16 0 false true false", - "D 65 0 false true false", - "P 65 65 false true false", - "U 65 0 false true false", - "U 16 0 false true false" } }, + { "D 16 0 false true false false", + "D 65 0 false true false false", + "P 65 65 false true false false", + "U 65 0 false true false false", + "U 16 0 false true false false" } }, // a, suppress keydown - { base::VKEY_A, false, false, false, + { base::VKEY_A, false, false, false, false, true, false, false, false, 2, - { "D 65 0 false false false", - "U 65 0 false false false" } }, + { "D 65 0 false false false false", + "U 65 0 false false false false" } }, }; static const KeyEventTestData kTestWithInput[] = { // a - { base::VKEY_A, false, false, false, + { base::VKEY_A, false, false, false, false, false, false, false, false, 4, - { "D 65 0 false false false", - "P 97 97 false false false", + { "D 65 0 false false false false", + "P 97 97 false false false false", "T a", - "U 65 0 false false false" } }, + "U 65 0 false false false false" } }, // shift-a - { base::VKEY_A, false, true, false, + { base::VKEY_A, false, true, false, false, false, false, false, false, 6, - { "D 16 0 false true false", - "D 65 0 false true false", - "P 65 65 false true false", + { "D 16 0 false true false false", + "D 65 0 false true false false", + "P 65 65 false true false false", "T A", - "U 65 0 false true false", - "U 16 0 false true false" } }, + "U 65 0 false true false false", + "U 16 0 false true false false" } }, // a, suppress keydown - { base::VKEY_A, false, false, false, + { base::VKEY_A, false, false, false, false, true, false, false, false, 2, - { "D 65 0 false false false", - "U 65 0 false false false" } }, + { "D 65 0 false false false false", + "U 65 0 false false false false" } }, // a, suppress keypress - { base::VKEY_A, false, false, false, + { base::VKEY_A, false, false, false, false, false, true, false, false, 3, - { "D 65 0 false false false", - "P 97 97 false false false", - "U 65 0 false false false" } }, + { "D 65 0 false false false false", + "P 97 97 false false false false", + "U 65 0 false false false false" } }, // a, suppress textInput - { base::VKEY_A, false, false, false, + { base::VKEY_A, false, false, false, false, false, false, false, true, 4, - { "D 65 0 false false false", - "P 97 97 false false false", + { "D 65 0 false false false false", + "P 97 97 false false false false", "T a", - "U 65 0 false false false" } }, + "U 65 0 false false false false" } }, }; HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); + BringBrowserWindowToFront(); GURL url = server->TestServerPage(kTestingPage); ui_test_utils::NavigateToURL(browser(), url); @@ -365,67 +392,79 @@ IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, FLAKY_NormalKeyEvents) { << GetTestDataDescription(kTestNoInput[i]); } + // Input in normal text box. ASSERT_NO_FATAL_FAILURE(SetFocusedElement(tab_index, L"A")); for (size_t i = 0; i < arraysize(kTestWithInput); ++i) { EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestWithInput[i])) - << "kTestWithInput[" << i << "] failed:\n" + << "kTestWithInput[" << i << "] in text box failed:\n" << GetTestDataDescription(kTestWithInput[i]); } - EXPECT_NO_FATAL_FAILURE(CheckTextBoxValue(tab_index, L"A", L"aA")); + + // Input in password box. + ASSERT_NO_FATAL_FAILURE(SetFocusedElement(tab_index, L"B")); + for (size_t i = 0; i < arraysize(kTestWithInput); ++i) { + EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestWithInput[i])) + << "kTestWithInput[" << i << "] in password box failed:\n" + << GetTestDataDescription(kTestWithInput[i]); + } + EXPECT_NO_FATAL_FAILURE(CheckTextBoxValue(tab_index, L"B", L"aA")); } +#if defined(OS_WIN) || defined(OS_LINUX) IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, CtrlKeyEvents) { static const KeyEventTestData kTestCtrlF = { - base::VKEY_F, true, false, false, + base::VKEY_F, true, false, false, false, false, false, false, false, 2, - { "D 17 0 true false false", - "D 70 0 true false false" } + { "D 17 0 true false false false", + "D 70 0 true false false false" } }; static const KeyEventTestData kTestCtrlFSuppressKeyDown = { - base::VKEY_F, true, false, false, + base::VKEY_F, true, false, false, false, true, false, false, false, 4, - { "D 17 0 true false false", - "D 70 0 true false false", - "U 70 0 true false false", - "U 17 0 true false false" } + { "D 17 0 true false false false", + "D 70 0 true false false false", + "U 70 0 true false false false", + "U 17 0 true false false false" } }; // Ctrl+Z doesn't bind to any accelerators, which then should generate a // keypress event with charCode=26. static const KeyEventTestData kTestCtrlZ = { - base::VKEY_Z, true, false, false, + base::VKEY_Z, true, false, false, false, false, false, false, false, 5, - { "D 17 0 true false false", - "D 90 0 true false false", - "P 26 26 true false false", - "U 90 0 true false false", - "U 17 0 true false false" } + { "D 17 0 true false false false", + "D 90 0 true false false false", + "P 26 26 true false false false", + "U 90 0 true false false false", + "U 17 0 true false false false" } }; static const KeyEventTestData kTestCtrlZSuppressKeyDown = { - base::VKEY_Z, true, false, false, + base::VKEY_Z, true, false, false, false, true, false, false, false, 4, - { "D 17 0 true false false", - "D 90 0 true false false", - "U 90 0 true false false", - "U 17 0 true false false" } + { "D 17 0 true false false false", + "D 90 0 true false false false", + "U 90 0 true false false false", + "U 17 0 true false false false" } }; // Ctrl+Enter shall generate a keypress event with charCode=10 (LF). static const KeyEventTestData kTestCtrlEnter = { - base::VKEY_RETURN, true, false, false, + base::VKEY_RETURN, true, false, false, false, false, false, false, false, 5, - { "D 17 0 true false false", - "D 13 0 true false false", - "P 10 10 true false false", - "U 13 0 true false false", - "U 17 0 true false false" } + { "D 17 0 true false false false", + "D 13 0 true false false false", + "P 10 10 true false false false", + "U 13 0 true false false false", + "U 17 0 true false false false" } }; HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); + BringBrowserWindowToFront(); GURL url = server->TestServerPage(kTestingPage); ui_test_utils::NavigateToURL(browser(), url); @@ -438,7 +477,8 @@ IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, CtrlKeyEvents) { EXPECT_TRUE(IsViewFocused(VIEW_ID_FIND_IN_PAGE_TEXT_FIELD)); // Press Escape to close the Find box and move the focus back to the web page. - ASSERT_NO_FATAL_FAILURE(SendKey(base::VKEY_ESCAPE, false, false, false)); + ASSERT_NO_FATAL_FAILURE( + SendKey(base::VKEY_ESCAPE, false, false, false, false)); ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER_FOCUS_VIEW)); // Press Ctrl+F with keydown suppressed shall not open the find box. @@ -449,6 +489,49 @@ IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, CtrlKeyEvents) { EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestCtrlZSuppressKeyDown)); EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestCtrlEnter)); } +#elif defined(OS_MACOSX) +IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, CommandKeyEvents) { + static const KeyEventTestData kTestCmdF = { + base::VKEY_F, false, false, false, true, + false, false, false, false, 2, + { "D 91 0 false false false true", + "D 70 0 false false false true" } + }; + + // On Mac we don't send key up events when command modifier is down. + static const KeyEventTestData kTestCmdFSuppressKeyDown = { + base::VKEY_F, false, false, false, true, + true, false, false, false, 3, + { "D 91 0 false false false true", + "D 70 0 false false false true", + "U 91 0 false false false true" } + }; + + HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); + + BringBrowserWindowToFront(); + GURL url = server->TestServerPage(kTestingPage); + ui_test_utils::NavigateToURL(browser(), url); + + ASSERT_NO_FATAL_FAILURE(ClickOnView(VIEW_ID_TAB_CONTAINER)); + ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER_FOCUS_VIEW)); + + int tab_index = browser()->selected_index(); + // Press Cmd+F, which will make the Find box open and request focus. + EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestCmdF)); + EXPECT_TRUE(IsViewFocused(VIEW_ID_FIND_IN_PAGE_TEXT_FIELD)); + + // Press Escape to close the Find box and move the focus back to the web page. + ASSERT_NO_FATAL_FAILURE( + SendKey(base::VKEY_ESCAPE, false, false, false, false)); + ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER_FOCUS_VIEW)); + + // Press Cmd+F with keydown suppressed shall not open the find box. + EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestCmdFSuppressKeyDown)); + ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER_FOCUS_VIEW)); +} +#endif #if defined(TOOLKIT_VIEWS) && defined(OS_LINUX) // See http://crbug.com/40037 for details. @@ -458,42 +541,80 @@ IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, CtrlKeyEvents) { #endif IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, MAYBE_AccessKeys) { - static const KeyEventTestData kTestAltA = { - base::VKEY_A, false, false, true, +#if defined(OS_MACOSX) + // On Mac, access keys use ctrl+alt modifiers. + static const KeyEventTestData kTestAccessA = { + base::VKEY_A, true, false, true, false, + false, false, false, false, 6, + { "D 17 0 true false false false", + "D 18 0 true false true false", + "D 65 0 true false true false", + "U 65 0 true false true false", + "U 18 0 true false true false", + "U 17 0 true false false false" } + }; + + static const KeyEventTestData kTestAccessDSuppress = { + base::VKEY_D, true, false, true, false, + true, true, true, false, 6, + { "D 17 0 true false false false", + "D 18 0 true false true false", + "D 68 0 true false true false", + "U 68 0 true false true false", + "U 18 0 true false true false", + "U 17 0 true false false false" } + }; + + static const KeyEventTestData kTestAccess1 = { + base::VKEY_1, true, false, true, false, + false, false, false, false, 6, + { "D 17 0 true false false false", + "D 18 0 true false true false", + "D 49 0 true false true false", + "U 49 0 true false true false", + "U 18 0 true false true false", + "U 17 0 true false false false" } + }; +#else + static const KeyEventTestData kTestAccessA = { + base::VKEY_A, false, false, true, false, false, false, false, false, 4, - { "D 18 0 false false true", - "D 65 0 false false true", - "U 65 0 false false true", - "U 18 0 false false true" } + { "D 18 0 false false true false", + "D 65 0 false false true false", + "U 65 0 false false true false", + "U 18 0 false false true false" } }; - static const KeyEventTestData kTestAltD = { - base::VKEY_D, false, false, true, + static const KeyEventTestData kTestAccessD = { + base::VKEY_D, false, false, true, false, false, false, false, false, 2, - { "D 18 0 false false true", - "D 68 0 false false true" } + { "D 18 0 false false true false", + "D 68 0 false false true false" } }; - static const KeyEventTestData kTestAltDSuppress = { - base::VKEY_D, false, false, true, + static const KeyEventTestData kTestAccessDSuppress = { + base::VKEY_D, false, false, true, false, true, true, true, false, 4, - { "D 18 0 false false true", - "D 68 0 false false true", - "U 68 0 false false true", - "U 18 0 false false true" } + { "D 18 0 false false true false", + "D 68 0 false false true false", + "U 68 0 false false true false", + "U 18 0 false false true false" } }; - static const KeyEventTestData kTestAlt1 = { - base::VKEY_1, false, false, true, + static const KeyEventTestData kTestAccess1 = { + base::VKEY_1, false, false, true, false, false, false, false, false, 4, - { "D 18 0 false false true", - "D 49 0 false false true", - "U 49 0 false false true", - "U 18 0 false false true" } + { "D 18 0 false false true false", + "D 49 0 false false true false", + "U 49 0 false false true false", + "U 18 0 false false true false" } }; +#endif HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); + BringBrowserWindowToFront(); GURL url = server->TestServerPage(kTestingPage); ui_test_utils::NavigateToURL(browser(), url); @@ -505,15 +626,17 @@ IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, MAYBE_AccessKeys) { // Make sure no element is focused. EXPECT_NO_FATAL_FAILURE(CheckFocusedElement(tab_index, L"")); // Alt+A should focus the element with accesskey = "A". - EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestAltA)); + EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestAccessA)); EXPECT_NO_FATAL_FAILURE(CheckFocusedElement(tab_index, L"A")); // Blur the focused element. EXPECT_NO_FATAL_FAILURE(SetFocusedElement(tab_index, L"")); // Make sure no element is focused. EXPECT_NO_FATAL_FAILURE(CheckFocusedElement(tab_index, L"")); + +#if !defined(OS_MACOSX) // Alt+D should move the focus to the location entry. - EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestAltD)); + EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestAccessD)); EXPECT_TRUE(IsViewFocused(VIEW_ID_LOCATION_BAR)); // No element should be focused, as Alt+D was handled by the browser. EXPECT_NO_FATAL_FAILURE(CheckFocusedElement(tab_index, L"")); @@ -524,11 +647,13 @@ IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, MAYBE_AccessKeys) { // Make sure no element is focused. EXPECT_NO_FATAL_FAILURE(CheckFocusedElement(tab_index, L"")); +#endif + // If the keydown event is suppressed, then Alt+D should be handled as an // accesskey rather than an accelerator key. Activation of an accesskey is not // a part of the default action of the key event, so it should not be // suppressed at all. - EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestAltDSuppress)); + EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestAccessDSuppress)); ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER_FOCUS_VIEW)); EXPECT_NO_FATAL_FAILURE(CheckFocusedElement(tab_index, L"D")); @@ -536,30 +661,32 @@ IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, MAYBE_AccessKeys) { EXPECT_NO_FATAL_FAILURE(SetFocusedElement(tab_index, L"")); // Make sure no element is focused. EXPECT_NO_FATAL_FAILURE(CheckFocusedElement(tab_index, L"")); - EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestAlt1)); + EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestAccess1)); #if defined(TOOLKIT_GTK) // On GTK, alt-0..9 are assigned as tab selection accelerators, so they can // not be used as accesskeys. EXPECT_NO_FATAL_FAILURE(CheckFocusedElement(tab_index, L"")); -#elif defined(OS_WIN) +#else EXPECT_NO_FATAL_FAILURE(CheckFocusedElement(tab_index, L"1")); #endif } IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, ReservedAccelerators) { HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); + BringBrowserWindowToFront(); GURL url = server->TestServerPage(kTestingPage); ui_test_utils::NavigateToURL(browser(), url); ASSERT_NO_FATAL_FAILURE(ClickOnView(VIEW_ID_TAB_CONTAINER)); ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER_FOCUS_VIEW)); -#if defined(OS_WIN) +#if defined(OS_WIN) || defined(TOOLKIT_VIEWS) static const KeyEventTestData kTestCtrlT = { - base::VKEY_T, true, false, false, + base::VKEY_T, true, false, false, false, true, false, false, false, 1, - { "D 17 0 true false false" } + { "D 17 0 true false false false" } }; ASSERT_EQ(1, browser()->tab_count()); @@ -576,46 +703,68 @@ IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, ReservedAccelerators) { // Reserved accelerators can't be suppressed. ASSERT_NO_FATAL_FAILURE(SuppressAllEvents(0, true)); // Press Ctrl+W, which will close the tab. - ASSERT_NO_FATAL_FAILURE(SendKey(base::VKEY_W, true, false, false)); + ASSERT_NO_FATAL_FAILURE(SendKey(base::VKEY_W, true, false, false, false)); EXPECT_EQ(1, browser()->tab_count()); +#elif defined(OS_MACOSX) + static const KeyEventTestData kTestCmdT = { + base::VKEY_T, false, false, false, true, + true, false, false, false, 1, + { "D 91 0 false false false true" } + }; + + ASSERT_EQ(1, browser()->tab_count()); + // Press Cmd+T, which will open a new tab. + EXPECT_NO_FATAL_FAILURE(TestKeyEvent(0, kTestCmdT)); + EXPECT_EQ(2, browser()->tab_count()); + browser()->SelectNumberedTab(0); + ASSERT_EQ(0, browser()->selected_index()); + + int result_length; + ASSERT_NO_FATAL_FAILURE(GetResultLength(0, &result_length)); + EXPECT_EQ(1, result_length); + // Reserved accelerators can't be suppressed. + ASSERT_NO_FATAL_FAILURE(SuppressAllEvents(0, true)); + // Press Cmd+W, which will close the tab. + ASSERT_NO_FATAL_FAILURE(SendKey(base::VKEY_W, false, false, false, true)); + EXPECT_EQ(1, browser()->tab_count()); #elif defined(TOOLKIT_GTK) // Ctrl-[a-z] are not treated as reserved accelerators on GTK. static const KeyEventTestData kTestCtrlT = { - base::VKEY_T, true, false, false, + base::VKEY_T, true, false, false, false, false, false, false, false, 2, - { "D 17 0 true false false", - "D 84 0 true false false" } + { "D 17 0 true false false false", + "D 84 0 true false false false" } }; static const KeyEventTestData kTestCtrlPageDown = { - base::VKEY_NEXT, true, false, false, + base::VKEY_NEXT, true, false, false, false, true, false, false, false, 1, - { "D 17 0 true false false" } + { "D 17 0 true false false false" } }; static const KeyEventTestData kTestCtrlTab = { - base::VKEY_TAB, true, false, false, + base::VKEY_TAB, true, false, false, false, true, false, false, false, 1, - { "D 17 0 true false false" } + { "D 17 0 true false false false" } }; static const KeyEventTestData kTestCtrlTBlocked = { - base::VKEY_T, true, false, false, + base::VKEY_T, true, false, false, false, true, false, false, false, 4, - { "D 17 0 true false false", - "D 84 0 true false false", - "U 84 0 true false false", - "U 17 0 true false false" } + { "D 17 0 true false false false", + "D 84 0 true false false false", + "U 84 0 true false false false", + "U 17 0 true false false false" } }; static const KeyEventTestData kTestCtrlWBlocked = { - base::VKEY_W, true, false, false, + base::VKEY_W, true, false, false, false, true, false, false, false, 4, - { "D 17 0 true false false", - "D 87 0 true false false", - "U 87 0 true false false", - "U 17 0 true false false" } + { "D 17 0 true false false false", + "D 87 0 true false false false", + "U 87 0 true false false false", + "U 17 0 true false false false" } }; ASSERT_EQ(1, browser()->tab_count()); @@ -647,7 +796,91 @@ IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, ReservedAccelerators) { // Ctrl+F4 to close the tab. ASSERT_NO_FATAL_FAILURE(SuppressAllEvents(0, true)); - ASSERT_NO_FATAL_FAILURE(SendKey(base::VKEY_F4, true, false, false)); + ASSERT_NO_FATAL_FAILURE( SendKey(base::VKEY_F4, true, false, false, false)); ASSERT_EQ(1, browser()->tab_count()); #endif } + +#if defined(OS_MACOSX) +IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, EditorKeyBindings) { + static const KeyEventTestData kTestCtrlA = { + base::VKEY_A, true, false, false, false, + false, false, false, false, 4, + { "D 17 0 true false false false", + "D 65 0 true false false false", + "U 65 0 true false false false", + "U 17 0 true false false false" } + }; + + static const KeyEventTestData kTestCtrlF = { + base::VKEY_F, true, false, false, false, + false, false, false, false, 4, + { "D 17 0 true false false false", + "D 70 0 true false false false", + "U 70 0 true false false false", + "U 17 0 true false false false" } + }; + + static const KeyEventTestData kTestCtrlK = { + base::VKEY_K, true, false, false, false, + false, false, false, false, 4, + { "D 17 0 true false false false", + "D 75 0 true false false false", + "U 75 0 true false false false", + "U 17 0 true false false false" } + }; + + HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); + + BringBrowserWindowToFront(); + GURL url = server->TestServerPage(kTestingPage); + ui_test_utils::NavigateToURL(browser(), url); + + ASSERT_NO_FATAL_FAILURE(ClickOnView(VIEW_ID_TAB_CONTAINER)); + ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER_FOCUS_VIEW)); + + int tab_index = browser()->selected_index(); + ASSERT_NO_FATAL_FAILURE(SetFocusedElement(tab_index, L"A")); + ASSERT_NO_FATAL_FAILURE(SetTextBoxValue(tab_index, L"A", L"Hello")); + // Move the caret to the beginning of the line. + EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestCtrlA)); + // Forward one character + EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestCtrlF)); + // Delete to the end of the line. + EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestCtrlK)); + EXPECT_NO_FATAL_FAILURE(CheckTextBoxValue(tab_index, L"A", L"H")); +} +#endif + +IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, PageUpDownKeys) { + static const KeyEventTestData kTestPageUp = { + base::VKEY_PRIOR, false, false, false, false, + false, false, false, false, 2, + { "D 33 0 false false false false", + "U 33 0 false false false false" } + }; + + static const KeyEventTestData kTestPageDown = { + base::VKEY_NEXT, false, false, false, false, + false, false, false, false, 2, + { "D 34 0 false false false false", + "U 34 0 false false false false" } + }; + + HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); + + BringBrowserWindowToFront(); + GURL url = server->TestServerPage(kTestingPage); + ui_test_utils::NavigateToURL(browser(), url); + + ASSERT_NO_FATAL_FAILURE(ClickOnView(VIEW_ID_TAB_CONTAINER)); + ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER_FOCUS_VIEW)); + + int tab_index = browser()->selected_index(); + ASSERT_NO_FATAL_FAILURE(SetFocusedElement(tab_index, L"A")); + EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestPageUp)); + EXPECT_NO_FATAL_FAILURE(TestKeyEvent(tab_index, kTestPageDown)); + EXPECT_NO_FATAL_FAILURE(CheckTextBoxValue(tab_index, L"A", L"")); +} diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 1e6b896..6e280d4 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -222,6 +222,7 @@ 'browser/automation/automation_window_tracker.h', 'browser/automation/extension_port_container.cc', 'browser/automation/extension_port_container.h', + 'browser/automation/ui_controls_internal.h', 'browser/automation/ui_controls_linux.cc', 'browser/automation/ui_controls_mac.mm', 'browser/automation/ui_controls_win.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index da22578..d2207d4 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -124,7 +124,7 @@ 'test/ui_test_utils.cc', 'test/ui_test_utils.h', 'test/ui_test_utils_linux.cc', - 'test/ui_test_utils_mac.cc', + 'test/ui_test_utils_mac.mm', 'test/ui_test_utils_win.cc', ], 'conditions': [ @@ -2164,7 +2164,7 @@ 'test/test_notification_tracker.h', 'test/testing_browser_process.h', 'test/ui_test_utils_linux.cc', - 'test/ui_test_utils_mac.cc', + 'test/ui_test_utils_mac.mm', 'test/ui_test_utils_win.cc', 'test/data/resource.h', 'test/data/resource.rc', diff --git a/chrome/test/data/keyevents_test.html b/chrome/test/data/keyevents_test.html index 140fad0..88ba7a8 100644 --- a/chrome/test/data/keyevents_test.html +++ b/chrome/test/data/keyevents_test.html @@ -11,7 +11,10 @@ var keyEventResult = []; var focusedElement = ""; var lastFocusedElement = ""; var testStarted = false; -var keyEventCount = 0; +var expectedEventCount = 0; +var eventCount = 0; +var keyDownCount = 0; +var keyUpCount = 0; function init() { document.addEventListener("keydown", handleEvent, false); @@ -31,10 +34,11 @@ function setDefaultAction(type, value) { return defaultActions[type]; } -function startTest() { +function startTest(count) { if (!testStarted) { clearResult(); testStarted = true; + expectedEventCount = count; log("Start test."); return true; } @@ -72,17 +76,19 @@ function handleEvent(e) { result += (evt.keyCode + ' ' + evt.charCode + ' ' + (keyId == 'Control' ? true : evt.ctrlKey) + ' ' + (keyId == 'Shift' ? true : evt.shiftKey) + ' ' + - (keyId == 'Alt' ? true : evt.altKey)); + (keyId == 'Alt' ? true : evt.altKey) + ' ' + + (keyId == 'Meta' ? true : evt.metaKey)); } keyEventResult.push(result); log(result); if (testStarted) { + ++eventCount; if (evt.type == "keydown") { - ++keyEventCount; + ++keyDownCount; } else if (evt.type == "keyup") { - --keyEventCount; - if (keyEventCount == 0) + ++keyUpCount; + if (keyDownCount == keyUpCount || (eventCount >= expectedEventCount)) finishTest(); } } @@ -102,7 +108,10 @@ function handleWindowBlur() { function clearResult() { keyEventResult = []; testStarted = false; - keyEventCount = 0; + expectedEventCount = 0; + eventCount = 0; + keyDownCount = 0; + keyUpCount = 0; document.getElementById('log').innerHTML = ""; return true; } @@ -157,7 +166,7 @@ function onClick(element) { onfocus="onFocus(this)" onblur="onBlur(this)"/> <input type="text" id="A" accesskey="A" onfocus="onFocus(this)" onblur="onBlur(this)"/> - <input type="text" id="B" accesskey="B" + <input type="password" id="B" accesskey="B" onfocus="onFocus(this)" onblur="onBlur(this)"/> <button id="clear" accesskey='C' onclick="clearResult()">Clear</button> <p id="log"></p> diff --git a/chrome/test/interactive_ui/interactive_ui_tests.gypi b/chrome/test/interactive_ui/interactive_ui_tests.gypi index 4a6c841..33529d5 100644 --- a/chrome/test/interactive_ui/interactive_ui_tests.gypi +++ b/chrome/test/interactive_ui/interactive_ui_tests.gypi @@ -88,8 +88,6 @@ 'sources!': [ # TODO(port) '<(DEPTH)/chrome/browser/autocomplete/autocomplete_edit_view_browsertest.cc', - '<(DEPTH)/chrome/browser/browser_focus_uitest.cc', - '<(DEPTH)/chrome/browser/browser_keyevents_browsertest.cc', '<(DEPTH)/chrome/browser/debugger/devtools_sanity_unittest.cc', '<(DEPTH)/chrome/browser/views/bookmark_bar_view_test.cc', '<(DEPTH)/chrome/browser/views/find_bar_host_interactive_uitest.cc', diff --git a/chrome/test/ui_test_utils.h b/chrome/test/ui_test_utils.h index 5949f51..8173348 100644 --- a/chrome/test/ui_test_utils.h +++ b/chrome/test/ui_test_utils.h @@ -10,6 +10,7 @@ #include <string> #include <set> +#include "gfx/native_widget_types.h" #include "base/basictypes.h" #include "base/message_loop.h" #include "base/scoped_temp_dir.h" @@ -417,6 +418,12 @@ class WindowedNotificationObserverWithDetails DISALLOW_COPY_AND_ASSIGN(WindowedNotificationObserverWithDetails); }; +// Hide a native window. +void HideNativeWindow(gfx::NativeWindow window); + +// Show and focus a native window. +void ShowAndFocusNativeWindow(gfx::NativeWindow window); + } // namespace ui_test_utils #endif // CHROME_TEST_UI_TEST_UTILS_H_ diff --git a/chrome/test/ui_test_utils_linux.cc b/chrome/test/ui_test_utils_linux.cc index 5878086..e9d29dc 100644 --- a/chrome/test/ui_test_utils_linux.cc +++ b/chrome/test/ui_test_utils_linux.cc @@ -85,4 +85,12 @@ void ClickOnView(const Browser* browser, ViewID vid) { RunMessageLoop(); } +void HideNativeWindow(gfx::NativeWindow window) { + gtk_widget_hide(GTK_WIDGET(window)); +} + +void ShowAndFocusNativeWindow(gfx::NativeWindow window) { + gtk_window_present(GTK_WINDOW(window)); +} + } // namespace ui_test_utils diff --git a/chrome/test/ui_test_utils_mac.cc b/chrome/test/ui_test_utils_mac.cc deleted file mode 100644 index c763a10..0000000 --- a/chrome/test/ui_test_utils_mac.cc +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2009 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/test/ui_test_utils.h" - -#include "base/logging.h" - -namespace ui_test_utils { - -// Details on why these are unimplemented: ViewIDs are defined in -// chrome/browser/view_ids.h. For Cocoa (unlike Views GTK) we don't -// associate ViewIDs with NSViews. -// -// Here's an idea on how to implement. -// - associate the correct ViewID with an NSView on construction (the most work) -// - create a mapping table, such as chrome/browser/gtk/view_id_util.h -// - IsViewFocused() then becomes -// [browser->window()->GetNativeHandle() firstResponder] -// - ClickOnView() becomes a normal NSMouseDown event forge at the right coords - -bool IsViewFocused(const Browser* browser, ViewID vid) { - NOTIMPLEMENTED(); - return false; -} - -void ClickOnView(const Browser* browser, ViewID vid) { - NOTIMPLEMENTED(); -} - -} // namespace ui_test_utils diff --git a/chrome/test/ui_test_utils_mac.mm b/chrome/test/ui_test_utils_mac.mm new file mode 100644 index 0000000..6e3689b --- /dev/null +++ b/chrome/test/ui_test_utils_mac.mm @@ -0,0 +1,66 @@ +// 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/test/ui_test_utils.h" + +#include <Carbon/Carbon.h> +#import <Cocoa/Cocoa.h> + +#include "base/logging.h" +#include "base/message_loop.h" +#include "chrome/browser/automation/ui_controls.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_window.h" +#import "chrome/browser/cocoa/view_id_util.h" + +namespace ui_test_utils { + +bool IsViewFocused(const Browser* browser, ViewID vid) { + NSWindow* window = browser->window()->GetNativeHandle(); + DCHECK(window); + NSView* view = view_id_util::GetView(window, vid); + if (!view) + return false; + + NSResponder* firstResponder = [window firstResponder]; + if (firstResponder == static_cast<NSResponder*>(view)) + return true; + + // Handle the special case of focusing a TextField. + if ([firstResponder isKindOfClass:[NSTextView class]]) { + NSView* delegate = [(NSTextView*)firstResponder delegate]; + if (delegate == view) + return true; + } + + return false; +} + +void ClickOnView(const Browser* browser, ViewID vid) { + NSWindow* window = browser->window()->GetNativeHandle(); + DCHECK(window); + NSView* view = view_id_util::GetView(window, vid); + DCHECK(view); + ui_controls::MoveMouseToCenterAndPress( + view, + ui_controls::LEFT, + ui_controls::DOWN | ui_controls::UP, + new MessageLoop::QuitTask()); + RunMessageLoop(); +} + +void HideNativeWindow(gfx::NativeWindow window) { + [window orderOut:nil]; +} + +void ShowAndFocusNativeWindow(gfx::NativeWindow window) { + // Make sure an unbundled program can get the input focus. + ProcessSerialNumber psn = { 0, kCurrentProcess }; + TransformProcessType(&psn,kProcessTransformToForegroundApplication); + SetFrontProcess(&psn); + + [window makeKeyAndOrderFront:nil]; +} + +} // namespace ui_test_utils diff --git a/chrome/test/ui_test_utils_win.cc b/chrome/test/ui_test_utils_win.cc index 22fa003..8c45ee3 100644 --- a/chrome/test/ui_test_utils_win.cc +++ b/chrome/test/ui_test_utils_win.cc @@ -39,4 +39,16 @@ void ClickOnView(const Browser* browser, ViewID vid) { RunMessageLoop(); } +void HideNativeWindow(gfx::NativeWindow window) { + // TODO(jcampan): retrieve the WidgetWin and show/hide on it instead of + // using Windows API. + ::ShowWindow(window, SW_HIDE); +} + +void ShowAndFocusNativeWindow(gfx::NativeWindow window) { + // TODO(jcampan): retrieve the WidgetWin and show/hide on it instead of + // using Windows API. + ::ShowWindow(window, SW_SHOW); +} + } // namespace ui_test_utils |