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. 4 5 #include "chrome/test/chromedriver/key_converter.h" 6 7 #include "base/format_macros.h" 8 #include "base/strings/stringprintf.h" 9 #include "base/strings/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" 13 14 namespace { 15 16 struct ModifierMaskAndKeyCode { 17 int mask; 18 ui::KeyboardCode key_code; 19 }; 20 21 const ModifierMaskAndKeyCode kModifiers[] = { 22 { kShiftKeyModifierMask, ui::VKEY_SHIFT }, 23 { kControlKeyModifierMask, ui::VKEY_CONTROL }, 24 { kAltKeyModifierMask, ui::VKEY_MENU } 25 }; 26 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[] = { 32 ui::VKEY_UNKNOWN, 33 ui::VKEY_UNKNOWN, 34 ui::VKEY_HELP, 35 ui::VKEY_BACK, 36 ui::VKEY_TAB, 37 ui::VKEY_CLEAR, 38 ui::VKEY_RETURN, 39 ui::VKEY_RETURN, 40 ui::VKEY_SHIFT, 41 ui::VKEY_CONTROL, 42 ui::VKEY_MENU, 43 ui::VKEY_PAUSE, 44 ui::VKEY_ESCAPE, 45 ui::VKEY_SPACE, 46 ui::VKEY_PRIOR, // page up 47 ui::VKEY_NEXT, // page down 48 ui::VKEY_END, 49 ui::VKEY_HOME, 50 ui::VKEY_LEFT, 51 ui::VKEY_UP, 52 ui::VKEY_RIGHT, 53 ui::VKEY_DOWN, 54 ui::VKEY_INSERT, 55 ui::VKEY_DELETE, 56 ui::VKEY_OEM_1, // semicolon 57 ui::VKEY_OEM_PLUS, // equals 58 ui::VKEY_NUMPAD0, 59 ui::VKEY_NUMPAD1, 60 ui::VKEY_NUMPAD2, 61 ui::VKEY_NUMPAD3, 62 ui::VKEY_NUMPAD4, 63 ui::VKEY_NUMPAD5, 64 ui::VKEY_NUMPAD6, 65 ui::VKEY_NUMPAD7, 66 ui::VKEY_NUMPAD8, 67 ui::VKEY_NUMPAD9, 68 ui::VKEY_MULTIPLY, 69 ui::VKEY_ADD, 70 ui::VKEY_OEM_COMMA, 71 ui::VKEY_SUBTRACT, 72 ui::VKEY_DECIMAL, 73 ui::VKEY_DIVIDE, 74 ui::VKEY_UNKNOWN, 75 ui::VKEY_UNKNOWN, 76 ui::VKEY_UNKNOWN, 77 ui::VKEY_UNKNOWN, 78 ui::VKEY_UNKNOWN, 79 ui::VKEY_UNKNOWN, 80 ui::VKEY_UNKNOWN, 81 ui::VKEY_F1, 82 ui::VKEY_F2, 83 ui::VKEY_F3, 84 ui::VKEY_F4, 85 ui::VKEY_F5, 86 ui::VKEY_F6, 87 ui::VKEY_F7, 88 ui::VKEY_F8, 89 ui::VKEY_F9, 90 ui::VKEY_F10, 91 ui::VKEY_F11, 92 ui::VKEY_F12}; 93 94 const base::char16 kWebDriverNullKey = 0xE000U; 95 const base::char16 kWebDriverShiftKey = 0xE008U; 96 const base::char16 kWebDriverControlKey = 0xE009U; 97 const base::char16 kWebDriverAltKey = 0xE00AU; 98 const base::char16 kWebDriverCommandKey = 0xE03DU; 99 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); 107 } 108 109 // Returns whether the given key is a WebDriver key modifier. 110 bool IsModifierKey(base::char16 key) { 111 switch (key) { 112 case kWebDriverShiftKey: 113 case kWebDriverControlKey: 114 case kWebDriverAltKey: 115 case kWebDriverCommandKey: 116 return true; 117 default: 118 return false; 119 } 120 } 121 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 124 // be set. 125 bool KeyCodeFromSpecialWebDriverKey(base::char16 key, 126 ui::KeyboardCode* key_code) { 127 int index = static_cast<int>(key) - 0xE000U; 128 bool is_special_key = index >= 0 && 129 index < static_cast<int>(arraysize(kSpecialWebDriverKeys)); 130 if (is_special_key) 131 *key_code = kSpecialWebDriverKeys[index]; 132 return is_special_key; 133 } 134 135 // Gets the key code associated with |key_utf16|, if it is a special shorthand 136 // key. Shorthand keys are common text equivalents for keys, such as the newline 137 // character, which is shorthand for the return key. Returns whether |key| is 138 // a shorthand key. If true, |key_code| will be set and |client_should_skip| 139 // will be set to whether the key should be skipped. 140 bool KeyCodeFromShorthandKey(base::char16 key_utf16, 141 ui::KeyboardCode* key_code, 142 bool* client_should_skip) { 143 base::string16 key_str_utf16; 144 key_str_utf16.push_back(key_utf16); 145 std::string key_str_utf8 = base::UTF16ToUTF8(key_str_utf16); 146 if (key_str_utf8.length() != 1) 147 return false; 148 bool should_skip = false; 149 char key = key_str_utf8[0]; 150 if (key == '\n') { 151 *key_code = ui::VKEY_RETURN; 152 } else if (key == '\t') { 153 *key_code = ui::VKEY_TAB; 154 } else if (key == '\b') { 155 *key_code = ui::VKEY_BACK; 156 } else if (key == ' ') { 157 *key_code = ui::VKEY_SPACE; 158 } else if (key == '\r') { 159 *key_code = ui::VKEY_UNKNOWN; 160 should_skip = true; 161 } else { 162 return false; 163 } 164 *client_should_skip = should_skip; 165 return true; 166 } 167 168 } // namespace 169 170 KeyEvent CreateKeyDownEvent(ui::KeyboardCode key_code, int modifiers) { 171 return KeyEvent( 172 kRawKeyDownEventType, modifiers, std::string(), std::string(), key_code); 173 } 174 175 KeyEvent CreateKeyUpEvent(ui::KeyboardCode key_code, int modifiers) { 176 return KeyEvent( 177 kKeyUpEventType, modifiers, std::string(), std::string(), key_code); 178 } 179 180 KeyEvent CreateCharEvent(const std::string& unmodified_text, 181 const std::string& modified_text, 182 int modifiers) { 183 return KeyEvent(kCharEventType, 184 modifiers, 185 modified_text, 186 unmodified_text, 187 ui::VKEY_UNKNOWN); 188 } 189 190 Status ConvertKeysToKeyEvents(const base::string16& client_keys, 191 bool release_modifiers, 192 int* modifiers, 193 std::list<KeyEvent>* client_key_events) { 194 std::list<KeyEvent> key_events; 195 196 base::string16 keys = client_keys; 197 // Add an implicit NULL character to the end of the input to depress all 198 // modifiers. 199 if (release_modifiers) 200 keys.push_back(kWebDriverNullKey); 201 202 int sticky_modifiers = *modifiers; 203 for (size_t i = 0; i < keys.size(); ++i) { 204 base::char16 key = keys[i]; 205 206 if (key == kWebDriverNullKey) { 207 // Release all modifier keys and clear |stick_modifiers|. 208 if (sticky_modifiers & kShiftKeyModifierMask) 209 key_events.push_back(CreateKeyUpEvent(ui::VKEY_SHIFT, 0)); 210 if (sticky_modifiers & kControlKeyModifierMask) 211 key_events.push_back(CreateKeyUpEvent(ui::VKEY_CONTROL, 0)); 212 if (sticky_modifiers & kAltKeyModifierMask) 213 key_events.push_back(CreateKeyUpEvent(ui::VKEY_MENU, 0)); 214 if (sticky_modifiers & kMetaKeyModifierMask) 215 key_events.push_back(CreateKeyUpEvent(ui::VKEY_COMMAND, 0)); 216 sticky_modifiers = 0; 217 continue; 218 } 219 if (IsModifierKey(key)) { 220 // Press or release the modifier, and adjust |sticky_modifiers|. 221 bool modifier_down = false; 222 ui::KeyboardCode key_code = ui::VKEY_UNKNOWN; 223 if (key == kWebDriverShiftKey) { 224 sticky_modifiers ^= kShiftKeyModifierMask; 225 modifier_down = (sticky_modifiers & kShiftKeyModifierMask) != 0; 226 key_code = ui::VKEY_SHIFT; 227 } else if (key == kWebDriverControlKey) { 228 sticky_modifiers ^= kControlKeyModifierMask; 229 modifier_down = (sticky_modifiers & kControlKeyModifierMask) != 0; 230 key_code = ui::VKEY_CONTROL; 231 } else if (key == kWebDriverAltKey) { 232 sticky_modifiers ^= kAltKeyModifierMask; 233 modifier_down = (sticky_modifiers & kAltKeyModifierMask) != 0; 234 key_code = ui::VKEY_MENU; 235 } else if (key == kWebDriverCommandKey) { 236 sticky_modifiers ^= kMetaKeyModifierMask; 237 modifier_down = (sticky_modifiers & kMetaKeyModifierMask) != 0; 238 key_code = ui::VKEY_COMMAND; 239 } else { 240 return Status(kUnknownError, "unknown modifier key"); 241 } 242 if (modifier_down) 243 key_events.push_back(CreateKeyDownEvent(key_code, sticky_modifiers)); 244 else 245 key_events.push_back(CreateKeyUpEvent(key_code, sticky_modifiers)); 246 continue; 247 } 248 249 ui::KeyboardCode key_code = ui::VKEY_UNKNOWN; 250 std::string unmodified_text, modified_text; 251 int all_modifiers = sticky_modifiers; 252 253 // Get the key code, text, and modifiers for the given key. 254 bool should_skip = false; 255 bool is_special_key = KeyCodeFromSpecialWebDriverKey(key, &key_code); 256 std::string error_msg; 257 if (is_special_key || 258 KeyCodeFromShorthandKey(key, &key_code, &should_skip)) { 259 if (should_skip) 260 continue; 261 if (key_code == ui::VKEY_UNKNOWN) { 262 return Status(kUnknownError, base::StringPrintf( 263 "unknown WebDriver key(%d) at string index (%" PRIuS ")", 264 static_cast<int>(key), 265 i)); 266 } 267 if (key_code == ui::VKEY_RETURN) { 268 // For some reason Chrome expects a carriage return for the return key. 269 modified_text = unmodified_text = "\r"; 270 } else if (is_special_key && !IsSpecialKeyPrintable(key_code)) { 271 // To prevent char event for special keys like DELETE. 272 modified_text = unmodified_text = std::string(); 273 } else { 274 // WebDriver assumes a numpad key should translate to the number, 275 // which requires NumLock to be on with some platforms. This isn't 276 // formally in the spec, but is expected by their tests. 277 int webdriver_modifiers = 0; 278 if (key_code >= ui::VKEY_NUMPAD0 && key_code <= ui::VKEY_NUMPAD9) 279 webdriver_modifiers = kNumLockKeyModifierMask; 280 if (!ConvertKeyCodeToText( 281 key_code, webdriver_modifiers, &unmodified_text, &error_msg)) 282 return Status(kUnknownError, error_msg); 283 if (!ConvertKeyCodeToText( 284 key_code, all_modifiers | webdriver_modifiers, &modified_text, 285 &error_msg)) 286 return Status(kUnknownError, error_msg); 287 } 288 } else { 289 int necessary_modifiers = 0; 290 ConvertCharToKeyCode(key, &key_code, &necessary_modifiers, &error_msg); 291 if (!error_msg.empty()) 292 return Status(kUnknownError, error_msg); 293 all_modifiers |= necessary_modifiers; 294 if (key_code != ui::VKEY_UNKNOWN) { 295 if (!ConvertKeyCodeToText(key_code, 0, &unmodified_text, &error_msg)) 296 return Status(kUnknownError, error_msg); 297 if (!ConvertKeyCodeToText( 298 key_code, all_modifiers, &modified_text, &error_msg)) 299 return Status(kUnknownError, error_msg); 300 if (unmodified_text.empty() || modified_text.empty()) { 301 // To prevent char event for special cases like CTRL + x (cut). 302 unmodified_text.clear(); 303 modified_text.clear(); 304 } 305 } else { 306 // Do a best effort and use the raw key we were given. 307 unmodified_text = base::UTF16ToUTF8(keys.substr(i, 1)); 308 modified_text = base::UTF16ToUTF8(keys.substr(i, 1)); 309 } 310 } 311 312 // Create the key events. 313 bool necessary_modifiers[3]; 314 for (int i = 0; i < 3; ++i) { 315 necessary_modifiers[i] = 316 all_modifiers & kModifiers[i].mask && 317 !(sticky_modifiers & kModifiers[i].mask); 318 if (necessary_modifiers[i]) { 319 key_events.push_back( 320 CreateKeyDownEvent(kModifiers[i].key_code, sticky_modifiers)); 321 } 322 } 323 324 key_events.push_back(CreateKeyDownEvent(key_code, all_modifiers)); 325 if (unmodified_text.length() || modified_text.length()) { 326 key_events.push_back( 327 CreateCharEvent(unmodified_text, modified_text, all_modifiers)); 328 } 329 key_events.push_back(CreateKeyUpEvent(key_code, all_modifiers)); 330 331 for (int i = 2; i > -1; --i) { 332 if (necessary_modifiers[i]) { 333 key_events.push_back( 334 CreateKeyUpEvent(kModifiers[i].key_code, sticky_modifiers)); 335 } 336 } 337 } 338 client_key_events->swap(key_events); 339 *modifiers = sticky_modifiers; 340 return Status(kOk); 341 } 342