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/element/: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 336