OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome/test/webdriver/webdriver_key_converter.h" | |
6 | |
7 #include "base/format_macros.h" | |
8 #include "base/logging.h" | |
9 #include "base/strings/stringprintf.h" | |
10 #include "base/strings/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" | |
15 | |
16 namespace { | |
17 | |
18 struct ModifierMaskAndKeyCode { | |
19 int mask; | |
20 ui::KeyboardCode key_code; | |
21 }; | |
22 | |
23 const ModifierMaskAndKeyCode kModifiers[] = { | |
24 { automation::kShiftKeyMask, ui::VKEY_SHIFT }, | |
25 { automation::kControlKeyMask, ui::VKEY_CONTROL }, | |
26 { automation::kAltKeyMask, ui::VKEY_MENU } | |
27 }; | |
28 | |
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/e
lement/:id/value | |
33 const ui::KeyboardCode kSpecialWebDriverKeys[] = { | |
34 ui::VKEY_UNKNOWN, | |
35 ui::VKEY_UNKNOWN, | |
36 ui::VKEY_HELP, | |
37 ui::VKEY_BACK, | |
38 ui::VKEY_TAB, | |
39 ui::VKEY_CLEAR, | |
40 ui::VKEY_RETURN, | |
41 ui::VKEY_RETURN, | |
42 ui::VKEY_SHIFT, | |
43 ui::VKEY_CONTROL, | |
44 ui::VKEY_MENU, | |
45 ui::VKEY_PAUSE, | |
46 ui::VKEY_ESCAPE, | |
47 ui::VKEY_SPACE, | |
48 ui::VKEY_PRIOR, // page up | |
49 ui::VKEY_NEXT, // page down | |
50 ui::VKEY_END, | |
51 ui::VKEY_HOME, | |
52 ui::VKEY_LEFT, | |
53 ui::VKEY_UP, | |
54 ui::VKEY_RIGHT, | |
55 ui::VKEY_DOWN, | |
56 ui::VKEY_INSERT, | |
57 ui::VKEY_DELETE, | |
58 ui::VKEY_OEM_1, // semicolon | |
59 ui::VKEY_OEM_PLUS, // equals | |
60 ui::VKEY_NUMPAD0, | |
61 ui::VKEY_NUMPAD1, | |
62 ui::VKEY_NUMPAD2, | |
63 ui::VKEY_NUMPAD3, | |
64 ui::VKEY_NUMPAD4, | |
65 ui::VKEY_NUMPAD5, | |
66 ui::VKEY_NUMPAD6, | |
67 ui::VKEY_NUMPAD7, | |
68 ui::VKEY_NUMPAD8, | |
69 ui::VKEY_NUMPAD9, | |
70 ui::VKEY_MULTIPLY, | |
71 ui::VKEY_ADD, | |
72 ui::VKEY_OEM_COMMA, | |
73 ui::VKEY_SUBTRACT, | |
74 ui::VKEY_DECIMAL, | |
75 ui::VKEY_DIVIDE, | |
76 ui::VKEY_UNKNOWN, | |
77 ui::VKEY_UNKNOWN, | |
78 ui::VKEY_UNKNOWN, | |
79 ui::VKEY_UNKNOWN, | |
80 ui::VKEY_UNKNOWN, | |
81 ui::VKEY_UNKNOWN, | |
82 ui::VKEY_UNKNOWN, | |
83 ui::VKEY_F1, | |
84 ui::VKEY_F2, | |
85 ui::VKEY_F3, | |
86 ui::VKEY_F4, | |
87 ui::VKEY_F5, | |
88 ui::VKEY_F6, | |
89 ui::VKEY_F7, | |
90 ui::VKEY_F8, | |
91 ui::VKEY_F9, | |
92 ui::VKEY_F10, | |
93 ui::VKEY_F11, | |
94 ui::VKEY_F12}; | |
95 | |
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; | |
101 | |
102 // Returns whether the given key is a WebDriver key modifier. | |
103 bool IsModifierKey(char16 key) { | |
104 switch (key) { | |
105 case kWebDriverShiftKey: | |
106 case kWebDriverControlKey: | |
107 case kWebDriverAltKey: | |
108 case kWebDriverCommandKey: | |
109 return true; | |
110 default: | |
111 return false; | |
112 } | |
113 } | |
114 | |
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 | |
117 // be set. | |
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)); | |
122 if (is_special_key) | |
123 *key_code = kSpecialWebDriverKeys[index]; | |
124 return is_special_key; | |
125 } | |
126 | |
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) | |
139 return false; | |
140 bool should_skip = false; | |
141 char key = key_str_utf8[0]; | |
142 if (key == '\n') { | |
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; | |
152 should_skip = true; | |
153 } else { | |
154 return false; | |
155 } | |
156 *client_should_skip = should_skip; | |
157 return true; | |
158 } | |
159 | |
160 } // namespace | |
161 | |
162 namespace webdriver { | |
163 | |
164 WebKeyEvent CreateKeyDownEvent(ui::KeyboardCode key_code, int modifiers) { | |
165 return WebKeyEvent(automation::kRawKeyDownType, | |
166 key_code, | |
167 std::string(), | |
168 std::string(), | |
169 modifiers); | |
170 } | |
171 | |
172 WebKeyEvent CreateKeyUpEvent(ui::KeyboardCode key_code, int modifiers) { | |
173 return WebKeyEvent(automation::kKeyUpType, | |
174 key_code, | |
175 std::string(), | |
176 std::string(), | |
177 modifiers); | |
178 } | |
179 | |
180 WebKeyEvent CreateCharEvent(const std::string& unmodified_text, | |
181 const std::string& modified_text, | |
182 int modifiers) { | |
183 return WebKeyEvent(automation::kCharType, | |
184 ui::VKEY_UNKNOWN, | |
185 unmodified_text, | |
186 modified_text, | |
187 modifiers); | |
188 } | |
189 | |
190 bool ConvertKeysToWebKeyEvents(const string16& client_keys, | |
191 const Logger& logger, | |
192 bool release_modifiers, | |
193 int* modifiers, | |
194 std::vector<WebKeyEvent>* client_key_events, | |
195 std::string* error_msg) { | |
196 std::vector<WebKeyEvent> key_events; | |
197 | |
198 string16 keys = client_keys; | |
199 // Add an implicit NULL character to the end of the input to depress all | |
200 // modifiers. | |
201 if (release_modifiers) | |
202 keys.push_back(kWebDriverNullKey); | |
203 | |
204 int sticky_modifiers = *modifiers; | |
205 for (size_t i = 0; i < keys.size(); ++i) { | |
206 char16 key = keys[i]; | |
207 | |
208 if (key == kWebDriverNullKey) { | |
209 // Release all modifier keys and clear |stick_modifiers|. | |
210 if (sticky_modifiers & automation::kShiftKeyMask) | |
211 key_events.push_back(CreateKeyUpEvent(ui::VKEY_SHIFT, 0)); | |
212 if (sticky_modifiers & automation::kControlKeyMask) | |
213 key_events.push_back(CreateKeyUpEvent(ui::VKEY_CONTROL, 0)); | |
214 if (sticky_modifiers & automation::kAltKeyMask) | |
215 key_events.push_back(CreateKeyUpEvent(ui::VKEY_MENU, 0)); | |
216 if (sticky_modifiers & automation::kMetaKeyMask) | |
217 key_events.push_back(CreateKeyUpEvent(ui::VKEY_COMMAND, 0)); | |
218 sticky_modifiers = 0; | |
219 continue; | |
220 } | |
221 if (IsModifierKey(key)) { | |
222 // Press or release the modifier, and adjust |sticky_modifiers|. | |
223 bool modifier_down = false; | |
224 ui::KeyboardCode key_code = ui::VKEY_UNKNOWN; | |
225 if (key == kWebDriverShiftKey) { | |
226 sticky_modifiers ^= automation::kShiftKeyMask; | |
227 modifier_down = sticky_modifiers & automation::kShiftKeyMask; | |
228 key_code = ui::VKEY_SHIFT; | |
229 } else if (key == kWebDriverControlKey) { | |
230 sticky_modifiers ^= automation::kControlKeyMask; | |
231 modifier_down = sticky_modifiers & automation::kControlKeyMask; | |
232 key_code = ui::VKEY_CONTROL; | |
233 } else if (key == kWebDriverAltKey) { | |
234 sticky_modifiers ^= automation::kAltKeyMask; | |
235 modifier_down = sticky_modifiers & automation::kAltKeyMask; | |
236 key_code = ui::VKEY_MENU; | |
237 } else if (key == kWebDriverCommandKey) { | |
238 sticky_modifiers ^= automation::kMetaKeyMask; | |
239 modifier_down = sticky_modifiers & automation::kMetaKeyMask; | |
240 key_code = ui::VKEY_COMMAND; | |
241 } else { | |
242 NOTREACHED(); | |
243 } | |
244 if (modifier_down) | |
245 key_events.push_back(CreateKeyDownEvent(key_code, sticky_modifiers)); | |
246 else | |
247 key_events.push_back(CreateKeyUpEvent(key_code, sticky_modifiers)); | |
248 continue; | |
249 } | |
250 | |
251 ui::KeyboardCode key_code = ui::VKEY_UNKNOWN; | |
252 std::string unmodified_text, modified_text; | |
253 int all_modifiers = sticky_modifiers; | |
254 | |
255 // Get the key code, text, and modifiers for the given key. | |
256 bool should_skip = false; | |
257 if (KeyCodeFromSpecialWebDriverKey(key, &key_code) || | |
258 KeyCodeFromShorthandKey(key, &key_code, &should_skip)) { | |
259 if (should_skip) | |
260 continue; | |
261 if (key_code == ui::VKEY_UNKNOWN) { | |
262 *error_msg = base::StringPrintf( | |
263 "Unknown WebDriver key(%d) at string index (%" PRIuS ")", | |
264 static_cast<int>(key), | |
265 i); | |
266 return false; | |
267 } | |
268 if (key_code == ui::VKEY_RETURN) { | |
269 // For some reason Chrome expects a carriage return for the return key. | |
270 modified_text = unmodified_text = "\r"; | |
271 } else { | |
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 = automation::kNumLockKeyMask; | |
278 unmodified_text = ConvertKeyCodeToText(key_code, webdriver_modifiers); | |
279 modified_text = ConvertKeyCodeToText( | |
280 key_code, | |
281 all_modifiers | webdriver_modifiers); | |
282 } | |
283 } else { | |
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 } | |
291 if (unmodified_text.empty() || modified_text.empty()) { | |
292 // Do a best effort and use the raw key we were given. | |
293 logger.Log( | |
294 kWarningLogLevel, | |
295 base::StringPrintf("No translation for key code. Code point: %d", | |
296 static_cast<int>(key))); | |
297 if (unmodified_text.empty()) | |
298 unmodified_text = UTF16ToUTF8(keys.substr(i, 1)); | |
299 if (modified_text.empty()) | |
300 modified_text = UTF16ToUTF8(keys.substr(i, 1)); | |
301 } | |
302 } | |
303 | |
304 // Create the key events. | |
305 bool necessary_modifiers[3]; | |
306 for (int i = 0; i < 3; ++i) { | |
307 necessary_modifiers[i] = | |
308 all_modifiers & kModifiers[i].mask && | |
309 !(sticky_modifiers & kModifiers[i].mask); | |
310 if (necessary_modifiers[i]) { | |
311 key_events.push_back( | |
312 CreateKeyDownEvent(kModifiers[i].key_code, sticky_modifiers)); | |
313 } | |
314 } | |
315 | |
316 key_events.push_back(CreateKeyDownEvent(key_code, all_modifiers)); | |
317 if (unmodified_text.length() || modified_text.length()) { | |
318 key_events.push_back( | |
319 CreateCharEvent(unmodified_text, modified_text, all_modifiers)); | |
320 } | |
321 key_events.push_back(CreateKeyUpEvent(key_code, all_modifiers)); | |
322 | |
323 for (int i = 2; i > -1; --i) { | |
324 if (necessary_modifiers[i]) { | |
325 key_events.push_back( | |
326 CreateKeyUpEvent(kModifiers[i].key_code, sticky_modifiers)); | |
327 } | |
328 } | |
329 } | |
330 client_key_events->swap(key_events); | |
331 *modifiers = sticky_modifiers; | |
332 return true; | |
333 } | |
334 | |
335 } // namespace webdriver | |
OLD | NEW |