1 // Copyright (c) 2012 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/webdriver/webdriver_key_converter.h"
7 #include "base/format_macros.h"
8 #include "base/logging.h"
9 #include "base/stringprintf.h"
10 #include "base/utf_string_conversions.h"
11 #include "chrome/common/automation_constants.h"
12 #include "chrome/test/automation/automation_json_requests.h"
13 #include "chrome/test/webdriver/keycode_text_conversion.h"
14 #include "chrome/test/webdriver/webdriver_logging.h"
18 struct ModifierMaskAndKeyCode
{
20 ui::KeyboardCode key_code
;
23 const ModifierMaskAndKeyCode kModifiers
[] = {
24 { automation::kShiftKeyMask
, ui::VKEY_SHIFT
},
25 { automation::kControlKeyMask
, ui::VKEY_CONTROL
},
26 { automation::kAltKeyMask
, ui::VKEY_MENU
}
29 // TODO(kkania): Use this in KeyMap.
30 // Ordered list of all the key codes corresponding to special WebDriver keys.
31 // These WebDriver keys are defined in the Unicode Private Use Area.
32 // http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/value
33 const ui::KeyboardCode kSpecialWebDriverKeys
[] = {
48 ui::VKEY_PRIOR
, // page up
49 ui::VKEY_NEXT
, // page down
58 ui::VKEY_OEM_1
, // semicolon
59 ui::VKEY_OEM_PLUS
, // equals
96 const char16 kWebDriverNullKey
= 0xE000U
;
97 const char16 kWebDriverShiftKey
= 0xE008U
;
98 const char16 kWebDriverControlKey
= 0xE009U
;
99 const char16 kWebDriverAltKey
= 0xE00AU
;
100 const char16 kWebDriverCommandKey
= 0xE03DU
;
102 // Returns whether the given key is a WebDriver key modifier.
103 bool IsModifierKey(char16 key
) {
105 case kWebDriverShiftKey
:
106 case kWebDriverControlKey
:
107 case kWebDriverAltKey
:
108 case kWebDriverCommandKey
:
115 // Gets the key code associated with |key|, if it is a special WebDriver key.
116 // Returns whether |key| is a special WebDriver key. If true, |key_code| will
118 bool KeyCodeFromSpecialWebDriverKey(char16 key
, ui::KeyboardCode
* key_code
) {
119 int index
= static_cast<int>(key
) - 0xE000U
;
120 bool is_special_key
= index
>= 0 &&
121 index
< static_cast<int>(arraysize(kSpecialWebDriverKeys
));
123 *key_code
= kSpecialWebDriverKeys
[index
];
124 return is_special_key
;
127 // Gets the key code associated with |key|, if it is a special shorthand key.
128 // Shorthand keys are common text equivalents for keys, such as the newline
129 // character, which is shorthand for the return key. Returns whether |key| is
130 // a shorthand key. If true, |key_code| will be set and |client_should_skip|
131 // will be set to whether the key should be skipped.
132 bool KeyCodeFromShorthandKey(char16 key_utf16
,
133 ui::KeyboardCode
* key_code
,
134 bool* client_should_skip
) {
135 string16 key_str_utf16
;
136 key_str_utf16
.push_back(key_utf16
);
137 std::string key_str_utf8
= UTF16ToUTF8(key_str_utf16
);
138 if (key_str_utf8
.length() != 1)
140 bool should_skip
= false;
141 char key
= key_str_utf8
[0];
143 *key_code
= ui::VKEY_RETURN
;
144 } else if (key
== '\t') {
145 *key_code
= ui::VKEY_TAB
;
146 } else if (key
== '\b') {
147 *key_code
= ui::VKEY_BACK
;
148 } else if (key
== ' ') {
149 *key_code
= ui::VKEY_SPACE
;
150 } else if (key
== '\r') {
151 *key_code
= ui::VKEY_UNKNOWN
;
156 *client_should_skip
= should_skip
;
162 namespace webdriver
{
164 WebKeyEvent
CreateKeyDownEvent(ui::KeyboardCode key_code
, int modifiers
) {
165 return WebKeyEvent(automation::kRawKeyDownType
, key_code
, "", "", modifiers
);
168 WebKeyEvent
CreateKeyUpEvent(ui::KeyboardCode key_code
, int modifiers
) {
169 return WebKeyEvent(automation::kKeyUpType
, key_code
, "", "", modifiers
);
172 WebKeyEvent
CreateCharEvent(const std::string
& unmodified_text
,
173 const std::string
& modified_text
,
175 return WebKeyEvent(automation::kCharType
,
182 bool ConvertKeysToWebKeyEvents(const string16
& client_keys
,
183 const Logger
& logger
,
184 bool release_modifiers
,
186 std::vector
<WebKeyEvent
>* client_key_events
,
187 std::string
* error_msg
) {
188 std::vector
<WebKeyEvent
> key_events
;
190 string16 keys
= client_keys
;
191 // Add an implicit NULL character to the end of the input to depress all
193 if (release_modifiers
)
194 keys
.push_back(kWebDriverNullKey
);
196 int sticky_modifiers
= *modifiers
;
197 for (size_t i
= 0; i
< keys
.size(); ++i
) {
198 char16 key
= keys
[i
];
200 if (key
== kWebDriverNullKey
) {
201 // Release all modifier keys and clear |stick_modifiers|.
202 if (sticky_modifiers
& automation::kShiftKeyMask
)
203 key_events
.push_back(CreateKeyUpEvent(ui::VKEY_SHIFT
, 0));
204 if (sticky_modifiers
& automation::kControlKeyMask
)
205 key_events
.push_back(CreateKeyUpEvent(ui::VKEY_CONTROL
, 0));
206 if (sticky_modifiers
& automation::kAltKeyMask
)
207 key_events
.push_back(CreateKeyUpEvent(ui::VKEY_MENU
, 0));
208 if (sticky_modifiers
& automation::kMetaKeyMask
)
209 key_events
.push_back(CreateKeyUpEvent(ui::VKEY_COMMAND
, 0));
210 sticky_modifiers
= 0;
213 if (IsModifierKey(key
)) {
214 // Press or release the modifier, and adjust |sticky_modifiers|.
215 bool modifier_down
= false;
216 ui::KeyboardCode key_code
= ui::VKEY_UNKNOWN
;
217 if (key
== kWebDriverShiftKey
) {
218 sticky_modifiers
^= automation::kShiftKeyMask
;
219 modifier_down
= sticky_modifiers
& automation::kShiftKeyMask
;
220 key_code
= ui::VKEY_SHIFT
;
221 } else if (key
== kWebDriverControlKey
) {
222 sticky_modifiers
^= automation::kControlKeyMask
;
223 modifier_down
= sticky_modifiers
& automation::kControlKeyMask
;
224 key_code
= ui::VKEY_CONTROL
;
225 } else if (key
== kWebDriverAltKey
) {
226 sticky_modifiers
^= automation::kAltKeyMask
;
227 modifier_down
= sticky_modifiers
& automation::kAltKeyMask
;
228 key_code
= ui::VKEY_MENU
;
229 } else if (key
== kWebDriverCommandKey
) {
230 sticky_modifiers
^= automation::kMetaKeyMask
;
231 modifier_down
= sticky_modifiers
& automation::kMetaKeyMask
;
232 key_code
= ui::VKEY_COMMAND
;
237 key_events
.push_back(CreateKeyDownEvent(key_code
, sticky_modifiers
));
239 key_events
.push_back(CreateKeyUpEvent(key_code
, sticky_modifiers
));
243 ui::KeyboardCode key_code
= ui::VKEY_UNKNOWN
;
244 std::string unmodified_text
, modified_text
;
245 int all_modifiers
= sticky_modifiers
;
247 // Get the key code, text, and modifiers for the given key.
248 bool should_skip
= false;
249 if (KeyCodeFromSpecialWebDriverKey(key
, &key_code
) ||
250 KeyCodeFromShorthandKey(key
, &key_code
, &should_skip
)) {
253 if (key_code
== ui::VKEY_UNKNOWN
) {
254 *error_msg
= StringPrintf(
255 "Unknown WebDriver key(%d) at string index (%" PRIuS
")",
256 static_cast<int>(key
),
260 if (key_code
== ui::VKEY_RETURN
) {
261 // For some reason Chrome expects a carriage return for the return key.
262 modified_text
= unmodified_text
= "\r";
264 // WebDriver assumes a numpad key should translate to the number,
265 // which requires NumLock to be on with some platforms. This isn't
266 // formally in the spec, but is expected by their tests.
267 int webdriver_modifiers
= 0;
268 if (key_code
>= ui::VKEY_NUMPAD0
&& key_code
<= ui::VKEY_NUMPAD9
)
269 webdriver_modifiers
= automation::kNumLockKeyMask
;
270 unmodified_text
= ConvertKeyCodeToText(key_code
, webdriver_modifiers
);
271 modified_text
= ConvertKeyCodeToText(
273 all_modifiers
| webdriver_modifiers
);
276 int necessary_modifiers
= 0;
277 ConvertCharToKeyCode(key
, &key_code
, &necessary_modifiers
);
278 all_modifiers
|= necessary_modifiers
;
279 if (key_code
!= ui::VKEY_UNKNOWN
) {
280 unmodified_text
= ConvertKeyCodeToText(key_code
, 0);
281 modified_text
= ConvertKeyCodeToText(key_code
, all_modifiers
);
283 if (unmodified_text
.empty() || modified_text
.empty()) {
284 // Do a best effort and use the raw key we were given.
287 base::StringPrintf("No translation for key code. Code point: %d",
288 static_cast<int>(key
)));
289 if (unmodified_text
.empty())
290 unmodified_text
= UTF16ToUTF8(keys
.substr(i
, 1));
291 if (modified_text
.empty())
292 modified_text
= UTF16ToUTF8(keys
.substr(i
, 1));
296 // Create the key events.
297 bool necessary_modifiers
[3];
298 for (int i
= 0; i
< 3; ++i
) {
299 necessary_modifiers
[i
] =
300 all_modifiers
& kModifiers
[i
].mask
&&
301 !(sticky_modifiers
& kModifiers
[i
].mask
);
302 if (necessary_modifiers
[i
]) {
303 key_events
.push_back(
304 CreateKeyDownEvent(kModifiers
[i
].key_code
, sticky_modifiers
));
308 key_events
.push_back(CreateKeyDownEvent(key_code
, all_modifiers
));
309 if (unmodified_text
.length() || modified_text
.length()) {
310 key_events
.push_back(
311 CreateCharEvent(unmodified_text
, modified_text
, all_modifiers
));
313 key_events
.push_back(CreateKeyUpEvent(key_code
, all_modifiers
));
315 for (int i
= 2; i
> -1; --i
) {
316 if (necessary_modifiers
[i
]) {
317 key_events
.push_back(
318 CreateKeyUpEvent(kModifiers
[i
].key_code
, sticky_modifiers
));
322 client_key_events
->swap(key_events
);
323 *modifiers
= sticky_modifiers
;
327 } // namespace webdriver