// Copyright 2013 The Chromium Authors
// 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 <array>

#include "base/format_macros.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversion_utils.h"
#include "base/strings/utf_string_conversions.h"
#include "base/third_party/icu/icu_utf.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;
};

constexpr auto kModifiers = std::to_array<ModifierMaskAndKeyCode>({
    {kShiftKeyModifierMask, ui::VKEY_SHIFT},
    {kControlKeyModifierMask, ui::VKEY_CONTROL},
    {kAltKeyModifierMask, ui::VKEY_MENU},
    {kMetaKeyModifierMask, ui::VKEY_COMMAND},
});

// 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.
constexpr auto kSpecialWebDriverKeys = std::to_array<ui::KeyboardCode>({
    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 char16_t kWebDriverNullKey = u'\uE000';
const char16_t kWebDriverShiftKey = u'\uE008';
const char16_t kWebDriverControlKey = u'\uE009';
const char16_t kWebDriverAltKey = u'\uE00A';
const char16_t kWebDriverCommandKey = u'\uE03D';
const char16_t kWebDriverRightShiftKey = u'\uE050';
const char16_t kWebDriverRightControlKey = u'\uE051';
const char16_t kWebDriverRightAltKey = u'\uE052';
const char16_t kWebDriverRightCommandKey = u'\uE053';

// 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(char16_t 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(char16_t key, ui::KeyboardCode* key_code) {
  int index = static_cast<int>(key) - 0xE000U;
  bool is_special_key =
      index >= 0 && index < static_cast<int>(std::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(char16_t key_utf16,
                             ui::KeyboardCode* key_code,
                             bool* client_should_skip) {
  std::u16string 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;
constexpr auto kNormalisedKeyValue = std::to_array<const char*>({
    "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 {
  char16_t key;
  char16_t 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 std::u16string& client_keys,
                              bool release_modifiers,
                              int* modifiers,
                              std::vector<KeyEvent>* client_key_events) {
  std::vector<KeyEvent> key_events;

  std::u16string 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) {
    char16_t key = keys[i];

    if (CBU16_IS_TRAIL(key)) {
      return Status(kUnknownError, "invalid surrogate pair in input");
    }

    if (CBU16_IS_LEAD(key)) {
      if (i + 1 >= keys.size() || !CBU16_IS_TRAIL(keys[i + 1])) {
        return Status(kUnknownError, "invalid surrogate pair in input");
      }
      std::string text = base::UTF16ToUTF8(keys.substr(i, 2));
      KeyEventBuilder builder;
      builder.SetModifiers(sticky_modifiers)
          ->SetText(text, text)
          ->SetKeyCode(ui::VKEY_UNKNOWN)
          ->Generate(&key_events);
      ++i;
      continue;
    }

    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.
    constexpr int number_modifiers = std::size(kModifiers);
    std::array<bool, number_modifiers> necessary_modifiers;
    for (int j = 0; j < number_modifiers; ++j) {
      necessary_modifiers[j] = all_modifiers & kModifiers[j].mask &&
                               !(sticky_modifiers & kModifiers[j].mask);
      if (necessary_modifiers[j]) {
        KeyEventBuilder builder;
        key_events.push_back(builder.SetType(kRawKeyDownEventType)
                                 ->SetKeyCode(kModifiers[j].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 j = 2; j > -1; --j) {
      if (necessary_modifiers[j]) {
        KeyEventBuilder builder;
        key_events.push_back(builder.SetType(kKeyUpEventType)
                                 ->SetKeyCode(kModifiers[j].key_code)
                                 ->SetModifiers(sticky_modifiers)
                                 ->Build());
      }
    }
  }
  client_key_events->swap(key_events);
  *modifiers = sticky_modifiers;
  return Status(kOk);
}

Status ConvertKeyActionToKeyEvent(const base::DictValue& action_object,
                                  base::DictValue& input_state,
                                  bool is_key_down,
                                  std::vector<KeyEvent>* key_events) {
  const std::string* raw_key = action_object.FindString("value");
  if (!raw_key)
    return Status(kUnknownError, "missing 'value'");

  size_t char_index = 0;
  base_icu::UChar32 code_point;
  base::ReadUnicodeCharacter(*raw_key, &char_index, &code_point);

  std::string key;
  if (code_point >= kNormalisedKeyValueBase &&
      code_point < base_icu::UChar32{kNormalisedKeyValueBase +
                                     std::size(kNormalisedKeyValue)}) {
    key = kNormalisedKeyValue[code_point - kNormalisedKeyValueBase];
  }
  if (key.size() == 0)
    key = *raw_key;

  base::DictValue* pressed = input_state.FindDict("pressed");
  if (!pressed)
    return Status(kUnknownError, "missing 'pressed'");
  bool already_pressed = pressed->contains(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;
      }
    }
  }

  std::optional<int> maybe_modifiers = input_state.FindInt("modifiers");
  if (!maybe_modifiers)
    return Status(kUnknownError, "missing 'modifiers'");

  int modifiers = *maybe_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.Set("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->Set(key, true);
  else
    pressed->Remove(key);

  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);
}
