| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/test/chromedriver/key_converter.h" |
| |
| #include <stddef.h> |
| |
| #include "base/format_macros.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversion_utils.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/test/chromedriver/chrome/status.h" |
| #include "chrome/test/chromedriver/chrome/ui_events.h" |
| #include "chrome/test/chromedriver/keycode_text_conversion.h" |
| |
| namespace { |
| |
| struct ModifierMaskAndKeyCode { |
| int mask; |
| ui::KeyboardCode key_code; |
| }; |
| |
| const ModifierMaskAndKeyCode kModifiers[] = { |
| { kShiftKeyModifierMask, ui::VKEY_SHIFT }, |
| { kControlKeyModifierMask, ui::VKEY_CONTROL }, |
| { kAltKeyModifierMask, ui::VKEY_MENU } |
| }; |
| |
| // Ordered list of all the key codes corresponding to special WebDriver keys. |
| // These keys are "special" in the sense that their code points are defined by |
| // the W3C spec (https://w3c.github.io/webdriver/#dfn-normalised-key-value), |
| // and are in the Unicode Private Use Area. All other keys have their code |
| // points defined by the Unicode standard. |
| const ui::KeyboardCode kSpecialWebDriverKeys[] = { |
| ui::VKEY_UNKNOWN, // \uE000 |
| ui::VKEY_CANCEL, // \uE001 |
| ui::VKEY_HELP, |
| ui::VKEY_BACK, |
| ui::VKEY_TAB, |
| ui::VKEY_CLEAR, |
| ui::VKEY_RETURN, |
| ui::VKEY_RETURN, |
| ui::VKEY_SHIFT, |
| ui::VKEY_CONTROL, |
| ui::VKEY_MENU, |
| ui::VKEY_PAUSE, |
| ui::VKEY_ESCAPE, |
| ui::VKEY_SPACE, |
| ui::VKEY_PRIOR, // page up |
| ui::VKEY_NEXT, // page down |
| ui::VKEY_END, // \uE010 |
| ui::VKEY_HOME, |
| ui::VKEY_LEFT, |
| ui::VKEY_UP, |
| ui::VKEY_RIGHT, |
| ui::VKEY_DOWN, |
| ui::VKEY_INSERT, |
| ui::VKEY_DELETE, |
| ui::VKEY_OEM_1, // semicolon |
| ui::VKEY_OEM_PLUS, // equals |
| ui::VKEY_NUMPAD0, |
| ui::VKEY_NUMPAD1, |
| ui::VKEY_NUMPAD2, |
| ui::VKEY_NUMPAD3, |
| ui::VKEY_NUMPAD4, |
| ui::VKEY_NUMPAD5, |
| ui::VKEY_NUMPAD6, // \uE020 |
| ui::VKEY_NUMPAD7, |
| ui::VKEY_NUMPAD8, |
| ui::VKEY_NUMPAD9, |
| ui::VKEY_MULTIPLY, |
| ui::VKEY_ADD, |
| ui::VKEY_OEM_COMMA, |
| ui::VKEY_SUBTRACT, |
| ui::VKEY_DECIMAL, |
| ui::VKEY_DIVIDE, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, // \uE030 |
| ui::VKEY_F1, |
| ui::VKEY_F2, |
| ui::VKEY_F3, |
| ui::VKEY_F4, |
| ui::VKEY_F5, |
| ui::VKEY_F6, |
| ui::VKEY_F7, |
| ui::VKEY_F8, |
| ui::VKEY_F9, |
| ui::VKEY_F10, |
| ui::VKEY_F11, |
| ui::VKEY_F12, |
| ui::VKEY_LWIN, // meta |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_DBE_DBCSCHAR, // \uE040 ZenkakuHankaku |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_UNKNOWN, |
| ui::VKEY_RSHIFT, // \uE050 |
| ui::VKEY_RCONTROL, |
| ui::VKEY_RMENU, |
| ui::VKEY_RWIN, // meta |
| ui::VKEY_PRIOR, // page up |
| ui::VKEY_NEXT, // page down |
| ui::VKEY_END, |
| ui::VKEY_HOME, |
| ui::VKEY_LEFT, |
| ui::VKEY_UP, |
| ui::VKEY_RIGHT, |
| ui::VKEY_DOWN, |
| ui::VKEY_INSERT, |
| ui::VKEY_DELETE, |
| }; |
| |
| const base::char16 kWebDriverNullKey = 0xE000U; |
| const base::char16 kWebDriverShiftKey = 0xE008U; |
| const base::char16 kWebDriverControlKey = 0xE009U; |
| const base::char16 kWebDriverAltKey = 0xE00AU; |
| const base::char16 kWebDriverCommandKey = 0xE03DU; |
| const base::char16 kWebDriverRightShiftKey = 0xE050U; |
| const base::char16 kWebDriverRightControlKey = 0xE051U; |
| const base::char16 kWebDriverRightAltKey = 0xE052U; |
| const base::char16 kWebDriverRightCommandKey = 0xE053U; |
| |
| // Returns whether the given key code has a corresponding printable char. |
| // Notice: The given key code should be a special WebDriver key code. |
| bool IsSpecialKeyPrintable(ui::KeyboardCode key_code) { |
| return key_code == ui::VKEY_TAB || key_code == ui::VKEY_SPACE || |
| key_code == ui::VKEY_OEM_1 || key_code == ui::VKEY_OEM_PLUS || |
| key_code == ui::VKEY_OEM_COMMA || |
| (key_code >= ui::VKEY_NUMPAD0 && key_code <= ui::VKEY_DIVIDE); |
| } |
| |
| // Returns whether the given key is a WebDriver key modifier. |
| bool IsModifierKey(base::char16 key) { |
| switch (key) { |
| case kWebDriverShiftKey: |
| case kWebDriverControlKey: |
| case kWebDriverAltKey: |
| case kWebDriverCommandKey: |
| case kWebDriverRightShiftKey: |
| case kWebDriverRightControlKey: |
| case kWebDriverRightAltKey: |
| case kWebDriverRightCommandKey: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| // Gets the key code associated with |key|, if it is a special WebDriver key. |
| // Returns whether |key| is a special WebDriver key. If true, |key_code| will |
| // be set. |
| bool KeyCodeFromSpecialWebDriverKey(base::char16 key, |
| ui::KeyboardCode* key_code) { |
| int index = static_cast<int>(key) - 0xE000U; |
| bool is_special_key = |
| index >= 0 && index < static_cast<int>(base::size(kSpecialWebDriverKeys)); |
| if (is_special_key) |
| *key_code = kSpecialWebDriverKeys[index]; |
| return is_special_key; |
| } |
| |
| // Gets the key code associated with |key_utf16|, if it is a special shorthand |
| // key. Shorthand keys are common text equivalents for keys, such as the newline |
| // character, which is shorthand for the return key. Returns whether |key| is |
| // a shorthand key. If true, |key_code| will be set and |client_should_skip| |
| // will be set to whether the key should be skipped. |
| bool KeyCodeFromShorthandKey(base::char16 key_utf16, |
| ui::KeyboardCode* key_code, |
| bool* client_should_skip) { |
| base::string16 key_str_utf16; |
| key_str_utf16.push_back(key_utf16); |
| std::string key_str_utf8 = base::UTF16ToUTF8(key_str_utf16); |
| if (key_str_utf8.length() != 1) |
| return false; |
| bool should_skip = false; |
| char key = key_str_utf8[0]; |
| if (key == '\n') { |
| *key_code = ui::VKEY_RETURN; |
| } else if (key == '\t') { |
| *key_code = ui::VKEY_TAB; |
| } else if (key == '\b') { |
| *key_code = ui::VKEY_BACK; |
| } else if (key == ' ') { |
| *key_code = ui::VKEY_SPACE; |
| } else if (key == '\r') { |
| *key_code = ui::VKEY_UNKNOWN; |
| should_skip = true; |
| } else { |
| return false; |
| } |
| *client_should_skip = should_skip; |
| return true; |
| } |
| |
| // The "normalised key value" table from W3C spec |
| // (https://w3c.github.io/webdriver/#dfn-normalised-key-value). |
| // The code point starts at \uE000 and must increase by 1 with each row, |
| // with placeholders (empty strings) used for unassigned code points. |
| const int kNormalisedKeyValueBase = 0xE000; |
| const char* const kNormalisedKeyValue[] = { |
| "Unidentified", // \uE000 |
| "Cancel", // \uE001 |
| "Help", // \uE002 |
| "Backspace", // \uE003 |
| "Tab", // \uE004 |
| "Clear", // \uE005 |
| "Return", // \uE006 |
| "Enter", // \uE007 |
| "Shift", // \uE008 |
| "Control", // \uE009 |
| "Alt", // \uE00A |
| "Pause", // \uE00B |
| "Escape", // \uE00C |
| " ", // \uE00D |
| "PageUp", // \uE00E |
| "PageDown", // \uE00F |
| "End", // \uE010 |
| "Home", // \uE011 |
| "ArrowLeft", // \uE012 |
| "ArrowUp", // \uE013 |
| "ArrowRight", // \uE014 |
| "ArrowDown", // \uE015 |
| "Insert", // \uE016 |
| "Delete", // \uE017 |
| ";", // \uE018 |
| "=", // \uE019 |
| "0", // \uE01A |
| "1", // \uE01B |
| "2", // \uE01C |
| "3", // \uE01D |
| "4", // \uE01E |
| "5", // \uE01F |
| "6", // \uE020 |
| "7", // \uE021 |
| "8", // \uE022 |
| "9", // \uE023 |
| "*", // \uE024 |
| "+", // \uE025 |
| ",", // \uE026 |
| "-", // \uE027 |
| ".", // \uE028 |
| "/", // \uE029 |
| "", |
| "", |
| "", |
| "", |
| "", |
| "", |
| "", |
| "F1", // \uE031 |
| "F2", // \uE032 |
| "F3", // \uE033 |
| "F4", // \uE034 |
| "F5", // \uE035 |
| "F6", // \uE036 |
| "F7", // \uE037 |
| "F8", // \uE038 |
| "F9", // \uE039 |
| "F10", // \uE03A |
| "F11", // \uE03B |
| "F12", // \uE03C |
| "Meta", // \uE03D |
| "", |
| "", |
| "ZenkakuHankaku", // \uE040 |
| "", |
| "", |
| "", |
| "", |
| "", |
| "", |
| "", |
| "", |
| "", |
| "", |
| "", |
| "", |
| "", |
| "", |
| "", |
| "Shift", // \uE050 |
| "Control", // \uE051 |
| "Alt", // \uE052 |
| "Meta", // \uE053 |
| "PageUp", // \uE054 |
| "PageDown", // \uE055 |
| "End", // \uE056 |
| "Home", // \uE057 |
| "ArrowLeft", // \uE058 |
| "ArrowUp", // \uE059 |
| "ArrowRight", // \uE05A |
| "ArrowDown", // \uE05B |
| "Insert", // \uE05C |
| "Delete", // \uE05D |
| }; |
| |
| // The "code for key" table (https://w3c.github.io/webdriver/#dfn-code), |
| // with the following modifications: |
| // * Fixed some inconsistencies in the original table. |
| // See https://github.com/w3c/webdriver/pull/1384. |
| // * Replaced "OSLeft" and "OSRight" with "MetaLeft" and "MetaRight", to be |
| // compatible with Chrome. |
| // TODO(johnchen@chromium.org): Find a better way to handle this. |
| const struct { |
| base::char16 key; |
| base::char16 alternate_key; |
| std::string code; |
| } kCodeForKey[] = { |
| {'`', '~', "Backquote"}, |
| {'\\', '|', "Backslash"}, |
| {0xE003, 0, "Backspace"}, |
| {'[', '{', "BracketLeft"}, |
| {']', '}', "BracketRight"}, |
| {',', '<', "Comma"}, |
| {'0', ')', "Digit0"}, |
| {'1', '!', "Digit1"}, |
| {'2', '@', "Digit2"}, |
| {'3', '#', "Digit3"}, |
| {'4', '$', "Digit4"}, |
| {'5', '%', "Digit5"}, |
| {'6', '^', "Digit6"}, |
| {'7', '&', "Digit7"}, |
| {'8', '*', "Digit8"}, |
| {'9', '(', "Digit9"}, |
| {'=', '+', "Equal"}, |
| {'<', '>', "IntlBackslash"}, |
| {'a', 'A', "KeyA"}, |
| {'b', 'B', "KeyB"}, |
| {'c', 'C', "KeyC"}, |
| {'d', 'D', "KeyD"}, |
| {'e', 'E', "KeyE"}, |
| {'f', 'F', "KeyF"}, |
| {'g', 'G', "KeyG"}, |
| {'h', 'H', "KeyH"}, |
| {'i', 'I', "KeyI"}, |
| {'j', 'J', "KeyJ"}, |
| {'k', 'K', "KeyK"}, |
| {'l', 'L', "KeyL"}, |
| {'m', 'M', "KeyM"}, |
| {'n', 'N', "KeyN"}, |
| {'o', 'O', "KeyO"}, |
| {'p', 'P', "KeyP"}, |
| {'q', 'Q', "KeyQ"}, |
| {'r', 'R', "KeyR"}, |
| {'s', 'S', "KeyS"}, |
| {'t', 'T', "KeyT"}, |
| {'u', 'U', "KeyU"}, |
| {'v', 'V', "KeyV"}, |
| {'w', 'W', "KeyW"}, |
| {'x', 'X', "KeyX"}, |
| {'y', 'Y', "KeyY"}, |
| {'z', 'Z', "KeyZ"}, |
| {'-', '_', "Minus"}, |
| {'.', '>', "Period"}, |
| {'\'', '"', "Quote"}, |
| {';', ':', "Semicolon"}, |
| {'/', '?', "Slash"}, |
| {0xE00A, 0, "AltLeft"}, |
| {0xE052, 0, "AltRight"}, |
| {0xE009, 0, "ControlLeft"}, |
| {0xE051, 0, "ControlRight"}, |
| {0xE006, 0, "Enter"}, |
| {0xE03D, 0, "MetaLeft"}, |
| {0xE053, 0, "MetaRight"}, |
| {0xE008, 0, "ShiftLeft"}, |
| {0xE050, 0, "ShiftRight"}, |
| {' ', 0xE00D, "Space"}, |
| {0xE004, 0, "Tab"}, |
| {0xE017, 0, "Delete"}, |
| {0xE010, 0, "End"}, |
| {0xE002, 0, "Help"}, |
| {0xE011, 0, "Home"}, |
| {0xE016, 0, "Insert"}, |
| {0xE00F, 0, "PageDown"}, |
| {0xE00E, 0, "PageUp"}, |
| {0xE015, 0, "ArrowDown"}, |
| {0xE012, 0, "ArrowLeft"}, |
| {0xE014, 0, "ArrowRight"}, |
| {0xE013, 0, "ArrowUp"}, |
| {0xE00C, 0, "Escape"}, |
| {0xE031, 0, "F1"}, |
| {0xE032, 0, "F2"}, |
| {0xE033, 0, "F3"}, |
| {0xE034, 0, "F4"}, |
| {0xE035, 0, "F5"}, |
| {0xE036, 0, "F6"}, |
| {0xE037, 0, "F7"}, |
| {0xE038, 0, "F8"}, |
| {0xE039, 0, "F9"}, |
| {0xE03A, 0, "F10"}, |
| {0xE03B, 0, "F11"}, |
| {0xE03C, 0, "F12"}, |
| {0xE01A, 0xE05C, "Numpad0"}, |
| {0xE01B, 0xE056, "Numpad1"}, |
| {0xE01C, 0xE05B, "Numpad2"}, |
| {0xE01D, 0xE055, "Numpad3"}, |
| {0xE01E, 0xE058, "Numpad4"}, |
| {0xE01F, 0, "Numpad5"}, |
| {0xE020, 0xE05A, "Numpad6"}, |
| {0xE021, 0xE057, "Numpad7"}, |
| {0xE022, 0xE059, "Numpad8"}, |
| {0xE023, 0xE054, "Numpad9"}, |
| {0xE025, 0, "NumpadAdd"}, |
| {0xE026, 0, "NumpadComma"}, |
| {0xE028, 0xE05D, "NumpadDecimal"}, |
| {0xE029, 0, "NumpadDivide"}, |
| {0xE007, 0, "NumpadEnter"}, |
| {0xE024, 0, "NumpadMultiply"}, |
| {0xE027, 0, "NumpadSubtract"}, |
| }; |
| |
| // The "key location for key" table from W3C spec |
| // (https://w3c.github.io/webdriver/#dfn-key-location). For simplicity, it is |
| // implemented as a few 'if' statements, instead of as a true table. |
| int GetKeyLocation(uint32_t code_point) { |
| if (code_point >= 0xe007 && code_point <= 0xe00a) |
| return 1; |
| if (code_point >= 0xe01a && code_point <= 0xe029) |
| return 3; |
| if (code_point == 0xe03d) |
| return 1; |
| if (code_point >= 0xe050 && code_point <= 0xe053) |
| return 2; |
| if (code_point >= 0xe054 && code_point <= 0xe05d) |
| return 3; |
| return 0; |
| } |
| |
| } // namespace |
| |
| Status ConvertKeysToKeyEvents(const base::string16& client_keys, |
| bool release_modifiers, |
| int* modifiers, |
| std::list<KeyEvent>* client_key_events) { |
| std::list<KeyEvent> key_events; |
| |
| base::string16 keys = client_keys; |
| // Add an implicit NULL character to the end of the input to depress all |
| // modifiers. |
| if (release_modifiers) |
| keys.push_back(kWebDriverNullKey); |
| |
| int sticky_modifiers = *modifiers; |
| for (size_t i = 0; i < keys.size(); ++i) { |
| base::char16 key = keys[i]; |
| |
| if (key == kWebDriverNullKey) { |
| // Release all modifier keys and clear |stick_modifiers|. |
| KeyEventBuilder builder; |
| builder.SetType(kKeyUpEventType); |
| if (sticky_modifiers & kShiftKeyModifierMask) |
| key_events.push_back(builder.SetKeyCode(ui::VKEY_SHIFT)->Build()); |
| if (sticky_modifiers & kControlKeyModifierMask) |
| key_events.push_back(builder.SetKeyCode(ui::VKEY_CONTROL)->Build()); |
| if (sticky_modifiers & kAltKeyModifierMask) |
| key_events.push_back(builder.SetKeyCode(ui::VKEY_MENU)->Build()); |
| if (sticky_modifiers & kMetaKeyModifierMask) |
| key_events.push_back(builder.SetKeyCode(ui::VKEY_COMMAND)->Build()); |
| sticky_modifiers = 0; |
| continue; |
| } |
| if (IsModifierKey(key)) { |
| // Press or release the modifier, and adjust |sticky_modifiers|. |
| bool modifier_down = false; |
| ui::KeyboardCode key_code = ui::VKEY_UNKNOWN; |
| if (key == kWebDriverShiftKey || key == kWebDriverRightShiftKey) { |
| sticky_modifiers ^= kShiftKeyModifierMask; |
| modifier_down = (sticky_modifiers & kShiftKeyModifierMask) != 0; |
| key_code = ui::VKEY_SHIFT; |
| } else if (key == kWebDriverControlKey || |
| key == kWebDriverRightControlKey) { |
| sticky_modifiers ^= kControlKeyModifierMask; |
| modifier_down = (sticky_modifiers & kControlKeyModifierMask) != 0; |
| key_code = ui::VKEY_CONTROL; |
| } else if (key == kWebDriverAltKey || key == kWebDriverRightAltKey) { |
| sticky_modifiers ^= kAltKeyModifierMask; |
| modifier_down = (sticky_modifiers & kAltKeyModifierMask) != 0; |
| key_code = ui::VKEY_MENU; |
| } else if (key == kWebDriverCommandKey || |
| key == kWebDriverRightCommandKey) { |
| sticky_modifiers ^= kMetaKeyModifierMask; |
| modifier_down = (sticky_modifiers & kMetaKeyModifierMask) != 0; |
| key_code = ui::VKEY_COMMAND; |
| } else { |
| return Status(kUnknownError, "unknown modifier key"); |
| } |
| KeyEventBuilder builder; |
| if (modifier_down) |
| builder.SetType(kRawKeyDownEventType); |
| else |
| builder.SetType(kKeyUpEventType); |
| key_events.push_back(builder.SetKeyCode(key_code) |
| ->SetModifiers(sticky_modifiers) |
| ->Build()); |
| continue; |
| } |
| |
| ui::KeyboardCode key_code = ui::VKEY_UNKNOWN; |
| std::string unmodified_text, modified_text; |
| int all_modifiers = sticky_modifiers; |
| |
| // Get the key code, text, and modifiers for the given key. |
| bool should_skip = false; |
| bool is_special_key = KeyCodeFromSpecialWebDriverKey(key, &key_code); |
| std::string error_msg; |
| if (is_special_key || |
| KeyCodeFromShorthandKey(key, &key_code, &should_skip)) { |
| if (should_skip) |
| continue; |
| if (key_code == ui::VKEY_UNKNOWN) { |
| return Status(kUnknownError, base::StringPrintf( |
| "unknown WebDriver key(%d) at string index (%" PRIuS ")", |
| static_cast<int>(key), |
| i)); |
| } |
| if (key_code == ui::VKEY_RETURN) { |
| // For some reason Chrome expects a carriage return for the return key. |
| modified_text = unmodified_text = "\r"; |
| } else if (is_special_key && !IsSpecialKeyPrintable(key_code)) { |
| // To prevent char event for special keys like DELETE. |
| modified_text = unmodified_text = std::string(); |
| } else { |
| // WebDriver assumes a numpad key should translate to the number, |
| // which requires NumLock to be on with some platforms. This isn't |
| // formally in the spec, but is expected by their tests. |
| int webdriver_modifiers = 0; |
| if (key_code >= ui::VKEY_NUMPAD0 && key_code <= ui::VKEY_NUMPAD9) |
| webdriver_modifiers = kNumLockKeyModifierMask; |
| if (!ConvertKeyCodeToText( |
| key_code, webdriver_modifiers, &unmodified_text, &error_msg)) |
| return Status(kUnknownError, error_msg); |
| if (!ConvertKeyCodeToText( |
| key_code, all_modifiers | webdriver_modifiers, &modified_text, |
| &error_msg)) |
| return Status(kUnknownError, error_msg); |
| } |
| } else { |
| int necessary_modifiers = 0; |
| ConvertCharToKeyCode(key, &key_code, &necessary_modifiers, &error_msg); |
| if (!error_msg.empty()) |
| return Status(kUnknownError, error_msg); |
| all_modifiers |= necessary_modifiers; |
| if (key_code != ui::VKEY_UNKNOWN) { |
| if (!ConvertKeyCodeToText(key_code, 0, &unmodified_text, &error_msg)) |
| return Status(kUnknownError, error_msg); |
| if (!ConvertKeyCodeToText( |
| key_code, all_modifiers, &modified_text, &error_msg)) |
| return Status(kUnknownError, error_msg); |
| if (unmodified_text.empty() || modified_text.empty()) { |
| // To prevent char event for special cases like CTRL + x (cut). |
| unmodified_text.clear(); |
| modified_text.clear(); |
| } |
| } else { |
| // Do a best effort and use the raw key we were given. |
| unmodified_text = base::UTF16ToUTF8(keys.substr(i, 1)); |
| modified_text = base::UTF16ToUTF8(keys.substr(i, 1)); |
| } |
| } |
| |
| // Create the key events. |
| bool necessary_modifiers[3]; |
| for (int i = 0; i < 3; ++i) { |
| necessary_modifiers[i] = |
| all_modifiers & kModifiers[i].mask && |
| !(sticky_modifiers & kModifiers[i].mask); |
| if (necessary_modifiers[i]) { |
| KeyEventBuilder builder; |
| key_events.push_back(builder.SetType(kRawKeyDownEventType) |
| ->SetKeyCode(kModifiers[i].key_code) |
| ->SetModifiers(sticky_modifiers) |
| ->Build()); |
| } |
| } |
| |
| KeyEventBuilder builder; |
| builder.SetModifiers(all_modifiers) |
| ->SetText(unmodified_text, modified_text) |
| ->SetKeyCode(key_code) |
| ->Generate(&key_events); |
| |
| for (int i = 2; i > -1; --i) { |
| if (necessary_modifiers[i]) { |
| KeyEventBuilder builder; |
| key_events.push_back(builder.SetType(kKeyUpEventType) |
| ->SetKeyCode(kModifiers[i].key_code) |
| ->SetModifiers(sticky_modifiers) |
| ->Build()); |
| } |
| } |
| } |
| client_key_events->swap(key_events); |
| *modifiers = sticky_modifiers; |
| return Status(kOk); |
| } |
| |
| Status ConvertKeyActionToKeyEvent(const base::DictionaryValue* action_object, |
| base::DictionaryValue* input_state, |
| bool is_key_down, |
| std::list<KeyEvent>* key_events) { |
| std::string raw_key; |
| if (!action_object->GetString("value", &raw_key)) |
| return Status(kUnknownError, "missing 'value'"); |
| |
| int32_t char_index = 0; |
| uint32_t code_point; |
| base::ReadUnicodeCharacter(raw_key.c_str(), raw_key.size(), &char_index, |
| &code_point); |
| |
| std::string key; |
| if (code_point >= kNormalisedKeyValueBase && |
| code_point < kNormalisedKeyValueBase + base::size(kNormalisedKeyValue)) { |
| key = kNormalisedKeyValue[code_point - kNormalisedKeyValueBase]; |
| } |
| if (key.size() == 0) |
| key = raw_key; |
| |
| base::DictionaryValue* pressed; |
| if (!input_state->GetDictionary("pressed", &pressed)) |
| return Status(kUnknownError, "missing 'pressed'"); |
| bool already_pressed = pressed->HasKey(key); |
| if (!is_key_down && !already_pressed) |
| return Status(kOk); |
| |
| std::string code; |
| if (code_point != 0) { |
| for (auto& mapping : kCodeForKey) { |
| if (mapping.key == code_point || mapping.alternate_key == code_point) { |
| code = mapping.code; |
| break; |
| } |
| } |
| } |
| |
| int modifiers; |
| if (!input_state->GetInteger("modifiers", &modifiers)) |
| return Status(kUnknownError, "missing 'modifiers'"); |
| |
| bool is_modifier_key = false; |
| bool is_special_key = false; |
| bool should_skip = false; |
| std::string unmodified_text, modified_text; |
| ui::KeyboardCode key_code = ui::VKEY_UNKNOWN; |
| std::string error_msg; |
| |
| is_modifier_key = IsModifierKey(code_point); |
| if (!is_modifier_key) |
| is_special_key = KeyCodeFromSpecialWebDriverKey(code_point, &key_code); |
| |
| if (is_modifier_key) { |
| int updated_modifier; |
| if (code_point == kWebDriverShiftKey) { |
| updated_modifier = kShiftKeyModifierMask; |
| key_code = ui::VKEY_SHIFT; |
| } else if (code_point == kWebDriverRightShiftKey) { |
| updated_modifier = kShiftKeyModifierMask; |
| key_code = ui::VKEY_RSHIFT; |
| } else if (code_point == kWebDriverControlKey) { |
| updated_modifier = kControlKeyModifierMask; |
| key_code = ui::VKEY_CONTROL; |
| } else if (code_point == kWebDriverRightControlKey) { |
| updated_modifier = kControlKeyModifierMask; |
| key_code = ui::VKEY_RCONTROL; |
| } else if (code_point == kWebDriverAltKey) { |
| updated_modifier = kAltKeyModifierMask; |
| key_code = ui::VKEY_MENU; |
| } else if (code_point == kWebDriverRightAltKey) { |
| updated_modifier = kAltKeyModifierMask; |
| key_code = ui::VKEY_RMENU; |
| } else if (code_point == kWebDriverCommandKey) { |
| updated_modifier = kMetaKeyModifierMask; |
| key_code = ui::VKEY_COMMAND; |
| } else if (code_point == kWebDriverRightCommandKey) { |
| updated_modifier = kMetaKeyModifierMask; |
| key_code = ui::VKEY_RWIN; |
| } else { |
| return Status(kUnknownError, "unknown modifier key"); |
| } |
| |
| if (is_key_down) |
| modifiers |= updated_modifier; |
| else |
| modifiers &= ~updated_modifier; |
| |
| input_state->SetInteger("modifiers", modifiers); |
| } else if (is_special_key || |
| KeyCodeFromShorthandKey(code_point, &key_code, &should_skip)) { |
| if (should_skip) |
| return Status(kOk); |
| if (key_code == ui::VKEY_RETURN) { |
| // For some reason Chrome expects a carriage return for the return key. |
| modified_text = unmodified_text = "\r"; |
| } else if (is_special_key && !IsSpecialKeyPrintable(key_code)) { |
| // To prevent char event for special keys like DELETE. |
| modified_text = unmodified_text = std::string(); |
| } else { |
| // WebDriver assumes a numpad key should translate to the number, |
| // which requires NumLock to be on with some platforms. This isn't |
| // formally in the spec, but is expected by their tests. |
| int webdriver_modifiers = 0; |
| if (key_code >= ui::VKEY_NUMPAD0 && key_code <= ui::VKEY_NUMPAD9) |
| webdriver_modifiers = kNumLockKeyModifierMask; |
| if (!ConvertKeyCodeToText(key_code, webdriver_modifiers, &unmodified_text, |
| &error_msg)) |
| return Status(kUnknownError, error_msg); |
| if (!ConvertKeyCodeToText(key_code, modifiers | webdriver_modifiers, |
| &modified_text, &error_msg)) |
| return Status(kUnknownError, error_msg); |
| } |
| } else { |
| int necessary_modifiers = 0; |
| ConvertCharToKeyCode(code_point, &key_code, &necessary_modifiers, |
| &error_msg); |
| if (!error_msg.empty()) |
| return Status(kUnknownError, error_msg); |
| if (key_code != ui::VKEY_UNKNOWN) { |
| modifiers |= necessary_modifiers; |
| if (!ConvertKeyCodeToText(key_code, 0, &unmodified_text, &error_msg)) |
| return Status(kUnknownError, error_msg); |
| if (!ConvertKeyCodeToText(key_code, modifiers, &modified_text, |
| &error_msg)) |
| return Status(kUnknownError, error_msg); |
| if (unmodified_text.empty() || modified_text.empty()) { |
| // To prevent char event for special cases like CTRL + x (cut). |
| unmodified_text.clear(); |
| modified_text.clear(); |
| } |
| } else { |
| // Do a best effort and use the raw key we were given. |
| unmodified_text = raw_key; |
| modified_text = raw_key; |
| } |
| } |
| |
| if (is_key_down) |
| pressed->SetBoolean(key, true); |
| else |
| pressed->Remove(key, nullptr); |
| |
| KeyEventBuilder builder; |
| builder.SetKeyCode(key_code) |
| ->SetModifiers(modifiers) |
| ->SetLocation(GetKeyLocation(code_point)) |
| ->SetDefaultKey(key) |
| ->SetCode(code) |
| ->SetIsFromAction(); |
| if (!is_modifier_key) |
| builder.SetText(unmodified_text, modified_text); |
| if (is_key_down) { |
| key_events->push_back(builder.SetType(kKeyDownEventType)->Build()); |
| } else { |
| key_events->push_back(builder.SetType(kKeyUpEventType)->Build()); |
| } |
| |
| return Status(kOk); |
| } |