1 // Copyright (c) 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 "chrome/test/chromedriver/key_converter.h"
7 #include "base/format_macros.h"
8 #include "base/stringprintf.h"
9 #include "base/utf_string_conversions.h"
10 #include "chrome/test/chromedriver/chrome/status.h"
11 #include "chrome/test/chromedriver/chrome/ui_events.h"
12 #include "chrome/test/chromedriver/keycode_text_conversion.h"
16 struct ModifierMaskAndKeyCode
{
18 ui::KeyboardCode key_code
;
21 const ModifierMaskAndKeyCode kModifiers
[] = {
22 { kShiftKeyModifierMask
, ui::VKEY_SHIFT
},
23 { kControlKeyModifierMask
, ui::VKEY_CONTROL
},
24 { kAltKeyModifierMask
, ui::VKEY_MENU
}
27 // TODO(kkania): Use this in KeyMap.
28 // Ordered list of all the key codes corresponding to special WebDriver keys.
29 // These WebDriver keys are defined in the Unicode Private Use Area.
30 // http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/value
31 const ui::KeyboardCode kSpecialWebDriverKeys
[] = {
46 ui::VKEY_PRIOR
, // page up
47 ui::VKEY_NEXT
, // page down
56 ui::VKEY_OEM_1
, // semicolon
57 ui::VKEY_OEM_PLUS
, // equals
94 const char16 kWebDriverNullKey
= 0xE000U
;
95 const char16 kWebDriverShiftKey
= 0xE008U
;
96 const char16 kWebDriverControlKey
= 0xE009U
;
97 const char16 kWebDriverAltKey
= 0xE00AU
;
98 const char16 kWebDriverCommandKey
= 0xE03DU
;
100 // Returns whether the given key code has a corresponding printable char.
101 // Notice: The given key code should be a special WebDriver key code.
102 bool IsSpecialKeyPrintable(ui::KeyboardCode key_code
) {
103 return key_code
== ui::VKEY_TAB
|| key_code
== ui::VKEY_SPACE
||
104 key_code
== ui::VKEY_OEM_1
|| key_code
== ui::VKEY_OEM_PLUS
||
105 key_code
== ui::VKEY_OEM_COMMA
||
106 (key_code
>= ui::VKEY_NUMPAD0
&& key_code
<= ui::VKEY_DIVIDE
);
109 // Returns whether the given key is a WebDriver key modifier.
110 bool IsModifierKey(char16 key
) {
112 case kWebDriverShiftKey
:
113 case kWebDriverControlKey
:
114 case kWebDriverAltKey
:
115 case kWebDriverCommandKey
:
122 // Gets the key code associated with |key|, if it is a special WebDriver key.
123 // Returns whether |key| is a special WebDriver key. If true, |key_code| will
125 bool KeyCodeFromSpecialWebDriverKey(char16 key
, ui::KeyboardCode
* key_code
) {
126 int index
= static_cast<int>(key
) - 0xE000U
;
127 bool is_special_key
= index
>= 0 &&
128 index
< static_cast<int>(arraysize(kSpecialWebDriverKeys
));
130 *key_code
= kSpecialWebDriverKeys
[index
];
131 return is_special_key
;
134 // Gets the key code associated with |key_utf16|, if it is a special shorthand
135 // key. Shorthand keys are common text equivalents for keys, such as the newline
136 // character, which is shorthand for the return key. Returns whether |key| is
137 // a shorthand key. If true, |key_code| will be set and |client_should_skip|
138 // will be set to whether the key should be skipped.
139 bool KeyCodeFromShorthandKey(char16 key_utf16
,
140 ui::KeyboardCode
* key_code
,
141 bool* client_should_skip
) {
142 string16 key_str_utf16
;
143 key_str_utf16
.push_back(key_utf16
);
144 std::string key_str_utf8
= UTF16ToUTF8(key_str_utf16
);
145 if (key_str_utf8
.length() != 1)
147 bool should_skip
= false;
148 char key
= key_str_utf8
[0];
150 *key_code
= ui::VKEY_RETURN
;
151 } else if (key
== '\t') {
152 *key_code
= ui::VKEY_TAB
;
153 } else if (key
== '\b') {
154 *key_code
= ui::VKEY_BACK
;
155 } else if (key
== ' ') {
156 *key_code
= ui::VKEY_SPACE
;
157 } else if (key
== '\r') {
158 *key_code
= ui::VKEY_UNKNOWN
;
163 *client_should_skip
= should_skip
;
169 KeyEvent
CreateKeyDownEvent(ui::KeyboardCode key_code
, int modifiers
) {
171 kRawKeyDownEventType
, modifiers
, std::string(), std::string(), key_code
);
174 KeyEvent
CreateKeyUpEvent(ui::KeyboardCode key_code
, int modifiers
) {
176 kKeyUpEventType
, modifiers
, std::string(), std::string(), key_code
);
179 KeyEvent
CreateCharEvent(const std::string
& unmodified_text
,
180 const std::string
& modified_text
,
182 return KeyEvent(kCharEventType
,
189 Status
ConvertKeysToKeyEvents(const string16
& client_keys
,
190 bool release_modifiers
,
192 std::list
<KeyEvent
>* client_key_events
) {
193 std::list
<KeyEvent
> key_events
;
195 string16 keys
= client_keys
;
196 // Add an implicit NULL character to the end of the input to depress all
198 if (release_modifiers
)
199 keys
.push_back(kWebDriverNullKey
);
201 int sticky_modifiers
= *modifiers
;
202 for (size_t i
= 0; i
< keys
.size(); ++i
) {
203 char16 key
= keys
[i
];
205 if (key
== kWebDriverNullKey
) {
206 // Release all modifier keys and clear |stick_modifiers|.
207 if (sticky_modifiers
& kShiftKeyModifierMask
)
208 key_events
.push_back(CreateKeyUpEvent(ui::VKEY_SHIFT
, 0));
209 if (sticky_modifiers
& kControlKeyModifierMask
)
210 key_events
.push_back(CreateKeyUpEvent(ui::VKEY_CONTROL
, 0));
211 if (sticky_modifiers
& kAltKeyModifierMask
)
212 key_events
.push_back(CreateKeyUpEvent(ui::VKEY_MENU
, 0));
213 if (sticky_modifiers
& kMetaKeyModifierMask
)
214 key_events
.push_back(CreateKeyUpEvent(ui::VKEY_COMMAND
, 0));
215 sticky_modifiers
= 0;
218 if (IsModifierKey(key
)) {
219 // Press or release the modifier, and adjust |sticky_modifiers|.
220 bool modifier_down
= false;
221 ui::KeyboardCode key_code
= ui::VKEY_UNKNOWN
;
222 if (key
== kWebDriverShiftKey
) {
223 sticky_modifiers
^= kShiftKeyModifierMask
;
224 modifier_down
= (sticky_modifiers
& kShiftKeyModifierMask
) != 0;
225 key_code
= ui::VKEY_SHIFT
;
226 } else if (key
== kWebDriverControlKey
) {
227 sticky_modifiers
^= kControlKeyModifierMask
;
228 modifier_down
= (sticky_modifiers
& kControlKeyModifierMask
) != 0;
229 key_code
= ui::VKEY_CONTROL
;
230 } else if (key
== kWebDriverAltKey
) {
231 sticky_modifiers
^= kAltKeyModifierMask
;
232 modifier_down
= (sticky_modifiers
& kAltKeyModifierMask
) != 0;
233 key_code
= ui::VKEY_MENU
;
234 } else if (key
== kWebDriverCommandKey
) {
235 sticky_modifiers
^= kMetaKeyModifierMask
;
236 modifier_down
= (sticky_modifiers
& kMetaKeyModifierMask
) != 0;
237 key_code
= ui::VKEY_COMMAND
;
239 return Status(kUnknownError
, "unknown modifier key");
242 key_events
.push_back(CreateKeyDownEvent(key_code
, sticky_modifiers
));
244 key_events
.push_back(CreateKeyUpEvent(key_code
, sticky_modifiers
));
248 ui::KeyboardCode key_code
= ui::VKEY_UNKNOWN
;
249 std::string unmodified_text
, modified_text
;
250 int all_modifiers
= sticky_modifiers
;
252 // Get the key code, text, and modifiers for the given key.
253 bool should_skip
= false;
254 bool is_special_key
= KeyCodeFromSpecialWebDriverKey(key
, &key_code
);
255 if (is_special_key
||
256 KeyCodeFromShorthandKey(key
, &key_code
, &should_skip
)) {
259 if (key_code
== ui::VKEY_UNKNOWN
) {
260 return Status(kUnknownError
, base::StringPrintf(
261 "unknown WebDriver key(%d) at string index (%" PRIuS
")",
262 static_cast<int>(key
),
265 if (key_code
== ui::VKEY_RETURN
) {
266 // For some reason Chrome expects a carriage return for the return key.
267 modified_text
= unmodified_text
= "\r";
268 } else if (is_special_key
&& !IsSpecialKeyPrintable(key_code
)) {
269 // To prevent char event for special keys like DELETE.
270 modified_text
= unmodified_text
= std::string();
272 // WebDriver assumes a numpad key should translate to the number,
273 // which requires NumLock to be on with some platforms. This isn't
274 // formally in the spec, but is expected by their tests.
275 int webdriver_modifiers
= 0;
276 if (key_code
>= ui::VKEY_NUMPAD0
&& key_code
<= ui::VKEY_NUMPAD9
)
277 webdriver_modifiers
= kNumLockKeyModifierMask
;
278 unmodified_text
= ConvertKeyCodeToText(key_code
, webdriver_modifiers
);
279 modified_text
= ConvertKeyCodeToText(
281 all_modifiers
| webdriver_modifiers
);
284 int necessary_modifiers
= 0;
285 ConvertCharToKeyCode(key
, &key_code
, &necessary_modifiers
);
286 all_modifiers
|= necessary_modifiers
;
287 if (key_code
!= ui::VKEY_UNKNOWN
) {
288 unmodified_text
= ConvertKeyCodeToText(key_code
, 0);
289 modified_text
= ConvertKeyCodeToText(key_code
, all_modifiers
);
290 if (unmodified_text
.empty() || modified_text
.empty()) {
291 // To prevent char event for special cases like CTRL + x (cut).
292 unmodified_text
.clear();
293 modified_text
.clear();
296 // Do a best effort and use the raw key we were given.
297 unmodified_text
= UTF16ToUTF8(keys
.substr(i
, 1));
298 modified_text
= UTF16ToUTF8(keys
.substr(i
, 1));
302 // Create the key events.
303 bool necessary_modifiers
[3];
304 for (int i
= 0; i
< 3; ++i
) {
305 necessary_modifiers
[i
] =
306 all_modifiers
& kModifiers
[i
].mask
&&
307 !(sticky_modifiers
& kModifiers
[i
].mask
);
308 if (necessary_modifiers
[i
]) {
309 key_events
.push_back(
310 CreateKeyDownEvent(kModifiers
[i
].key_code
, sticky_modifiers
));
314 key_events
.push_back(CreateKeyDownEvent(key_code
, all_modifiers
));
315 if (unmodified_text
.length() || modified_text
.length()) {
316 key_events
.push_back(
317 CreateCharEvent(unmodified_text
, modified_text
, all_modifiers
));
319 key_events
.push_back(CreateKeyUpEvent(key_code
, all_modifiers
));
321 for (int i
= 2; i
> -1; --i
) {
322 if (necessary_modifiers
[i
]) {
323 key_events
.push_back(
324 CreateKeyUpEvent(kModifiers
[i
].key_code
, sticky_modifiers
));
328 client_key_events
->swap(key_events
);
329 *modifiers
= sticky_modifiers
;