Home | History | Annotate | Download | only in chromedriver
      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 char16 kWebDriverNullKey = 0xE000U;
     95 const char16 kWebDriverShiftKey = 0xE008U;
     96 const char16 kWebDriverControlKey = 0xE009U;
     97 const char16 kWebDriverAltKey = 0xE00AU;
     98 const 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(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(char16 key, ui::KeyboardCode* key_code) {
    126   int index = static_cast<int>(key) - 0xE000U;
    127   bool is_special_key = index >= 0 &&
    128       index < static_cast<int>(arraysize(kSpecialWebDriverKeys));
    129   if (is_special_key)
    130     *key_code = kSpecialWebDriverKeys[index];
    131   return is_special_key;
    132 }
    133 
    134 // Gets the key code associated with |key_utf16|, if it is a special shorthand
    135 // key. Shorthand keys are common text equivalents for keys, such as the newline
    136 // character, which is shorthand for the return key. Returns whether |key| is
    137 // a shorthand key. If true, |key_code| will be set and |client_should_skip|
    138 // will be set to whether the key should be skipped.
    139 bool KeyCodeFromShorthandKey(char16 key_utf16,
    140                              ui::KeyboardCode* key_code,
    141                              bool* client_should_skip) {
    142   string16 key_str_utf16;
    143   key_str_utf16.push_back(key_utf16);
    144   std::string key_str_utf8 = UTF16ToUTF8(key_str_utf16);
    145   if (key_str_utf8.length() != 1)
    146     return false;
    147   bool should_skip = false;
    148   char key = key_str_utf8[0];
    149   if (key == '\n') {
    150     *key_code = ui::VKEY_RETURN;
    151   } else if (key == '\t') {
    152     *key_code = ui::VKEY_TAB;
    153   } else if (key == '\b') {
    154     *key_code = ui::VKEY_BACK;
    155   } else if (key == ' ') {
    156     *key_code = ui::VKEY_SPACE;
    157   } else if (key == '\r') {
    158     *key_code = ui::VKEY_UNKNOWN;
    159     should_skip = true;
    160   } else {
    161     return false;
    162   }
    163   *client_should_skip = should_skip;
    164   return true;
    165 }
    166 
    167 }  // namespace
    168 
    169 KeyEvent CreateKeyDownEvent(ui::KeyboardCode key_code, int modifiers) {
    170   return KeyEvent(
    171       kRawKeyDownEventType, modifiers, std::string(), std::string(), key_code);
    172 }
    173 
    174 KeyEvent CreateKeyUpEvent(ui::KeyboardCode key_code, int modifiers) {
    175   return KeyEvent(
    176       kKeyUpEventType, modifiers, std::string(), std::string(), key_code);
    177 }
    178 
    179 KeyEvent CreateCharEvent(const std::string& unmodified_text,
    180                          const std::string& modified_text,
    181                          int modifiers) {
    182   return KeyEvent(kCharEventType,
    183                   modifiers,
    184                   modified_text,
    185                   unmodified_text,
    186                   ui::VKEY_UNKNOWN);
    187 }
    188 
    189 Status ConvertKeysToKeyEvents(const string16& client_keys,
    190                               bool release_modifiers,
    191                               int* modifiers,
    192                               std::list<KeyEvent>* client_key_events) {
    193   std::list<KeyEvent> key_events;
    194 
    195   string16 keys = client_keys;
    196   // Add an implicit NULL character to the end of the input to depress all
    197   // modifiers.
    198   if (release_modifiers)
    199     keys.push_back(kWebDriverNullKey);
    200 
    201   int sticky_modifiers = *modifiers;
    202   for (size_t i = 0; i < keys.size(); ++i) {
    203     char16 key = keys[i];
    204 
    205     if (key == kWebDriverNullKey) {
    206       // Release all modifier keys and clear |stick_modifiers|.
    207       if (sticky_modifiers & kShiftKeyModifierMask)
    208         key_events.push_back(CreateKeyUpEvent(ui::VKEY_SHIFT, 0));
    209       if (sticky_modifiers & kControlKeyModifierMask)
    210         key_events.push_back(CreateKeyUpEvent(ui::VKEY_CONTROL, 0));
    211       if (sticky_modifiers & kAltKeyModifierMask)
    212         key_events.push_back(CreateKeyUpEvent(ui::VKEY_MENU, 0));
    213       if (sticky_modifiers & kMetaKeyModifierMask)
    214         key_events.push_back(CreateKeyUpEvent(ui::VKEY_COMMAND, 0));
    215       sticky_modifiers = 0;
    216       continue;
    217     }
    218     if (IsModifierKey(key)) {
    219       // Press or release the modifier, and adjust |sticky_modifiers|.
    220       bool modifier_down = false;
    221       ui::KeyboardCode key_code = ui::VKEY_UNKNOWN;
    222       if (key == kWebDriverShiftKey) {
    223         sticky_modifiers ^= kShiftKeyModifierMask;
    224         modifier_down = (sticky_modifiers & kShiftKeyModifierMask) != 0;
    225         key_code = ui::VKEY_SHIFT;
    226       } else if (key == kWebDriverControlKey) {
    227         sticky_modifiers ^= kControlKeyModifierMask;
    228         modifier_down = (sticky_modifiers & kControlKeyModifierMask) != 0;
    229         key_code = ui::VKEY_CONTROL;
    230       } else if (key == kWebDriverAltKey) {
    231         sticky_modifiers ^= kAltKeyModifierMask;
    232         modifier_down = (sticky_modifiers & kAltKeyModifierMask) != 0;
    233         key_code = ui::VKEY_MENU;
    234       } else if (key == kWebDriverCommandKey) {
    235         sticky_modifiers ^= kMetaKeyModifierMask;
    236         modifier_down = (sticky_modifiers & kMetaKeyModifierMask) != 0;
    237         key_code = ui::VKEY_COMMAND;
    238       } else {
    239         return Status(kUnknownError, "unknown modifier key");
    240       }
    241       if (modifier_down)
    242         key_events.push_back(CreateKeyDownEvent(key_code, sticky_modifiers));
    243       else
    244         key_events.push_back(CreateKeyUpEvent(key_code, sticky_modifiers));
    245       continue;
    246     }
    247 
    248     ui::KeyboardCode key_code = ui::VKEY_UNKNOWN;
    249     std::string unmodified_text, modified_text;
    250     int all_modifiers = sticky_modifiers;
    251 
    252     // Get the key code, text, and modifiers for the given key.
    253     bool should_skip = false;
    254     bool is_special_key = KeyCodeFromSpecialWebDriverKey(key, &key_code);
    255     std::string error_msg;
    256     if (is_special_key ||
    257         KeyCodeFromShorthandKey(key, &key_code, &should_skip)) {
    258       if (should_skip)
    259         continue;
    260       if (key_code == ui::VKEY_UNKNOWN) {
    261         return Status(kUnknownError, base::StringPrintf(
    262             "unknown WebDriver key(%d) at string index (%" PRIuS ")",
    263             static_cast<int>(key),
    264             i));
    265       }
    266       if (key_code == ui::VKEY_RETURN) {
    267         // For some reason Chrome expects a carriage return for the return key.
    268         modified_text = unmodified_text = "\r";
    269       } else if (is_special_key && !IsSpecialKeyPrintable(key_code)) {
    270         // To prevent char event for special keys like DELETE.
    271         modified_text = unmodified_text = std::string();
    272       } else {
    273         // WebDriver assumes a numpad key should translate to the number,
    274         // which requires NumLock to be on with some platforms. This isn't
    275         // formally in the spec, but is expected by their tests.
    276         int webdriver_modifiers = 0;
    277         if (key_code >= ui::VKEY_NUMPAD0 && key_code <= ui::VKEY_NUMPAD9)
    278           webdriver_modifiers = kNumLockKeyModifierMask;
    279         if (!ConvertKeyCodeToText(
    280             key_code, webdriver_modifiers, &unmodified_text, &error_msg))
    281           return Status(kUnknownError, error_msg);
    282         if (!ConvertKeyCodeToText(
    283             key_code, all_modifiers | webdriver_modifiers, &modified_text,
    284             &error_msg))
    285           return Status(kUnknownError, error_msg);
    286       }
    287     } else {
    288       int necessary_modifiers = 0;
    289       ConvertCharToKeyCode(key, &key_code, &necessary_modifiers, &error_msg);
    290       if (!error_msg.empty())
    291         return Status(kUnknownError, error_msg);
    292       all_modifiers |= necessary_modifiers;
    293       if (key_code != ui::VKEY_UNKNOWN) {
    294         if (!ConvertKeyCodeToText(key_code, 0, &unmodified_text, &error_msg))
    295           return Status(kUnknownError, error_msg);
    296         if (!ConvertKeyCodeToText(
    297             key_code, all_modifiers, &modified_text, &error_msg))
    298           return Status(kUnknownError, error_msg);
    299         if (unmodified_text.empty() || modified_text.empty()) {
    300           // To prevent char event for special cases like CTRL + x (cut).
    301           unmodified_text.clear();
    302           modified_text.clear();
    303         }
    304       } else {
    305         // Do a best effort and use the raw key we were given.
    306         unmodified_text = UTF16ToUTF8(keys.substr(i, 1));
    307         modified_text = UTF16ToUTF8(keys.substr(i, 1));
    308       }
    309     }
    310 
    311     // Create the key events.
    312     bool necessary_modifiers[3];
    313     for (int i = 0; i < 3; ++i) {
    314       necessary_modifiers[i] =
    315           all_modifiers & kModifiers[i].mask &&
    316           !(sticky_modifiers & kModifiers[i].mask);
    317       if (necessary_modifiers[i]) {
    318         key_events.push_back(
    319             CreateKeyDownEvent(kModifiers[i].key_code, sticky_modifiers));
    320       }
    321     }
    322 
    323     key_events.push_back(CreateKeyDownEvent(key_code, all_modifiers));
    324     if (unmodified_text.length() || modified_text.length()) {
    325       key_events.push_back(
    326           CreateCharEvent(unmodified_text, modified_text, all_modifiers));
    327     }
    328     key_events.push_back(CreateKeyUpEvent(key_code, all_modifiers));
    329 
    330     for (int i = 2; i > -1; --i) {
    331       if (necessary_modifiers[i]) {
    332         key_events.push_back(
    333             CreateKeyUpEvent(kModifiers[i].key_code, sticky_modifiers));
    334       }
    335     }
    336   }
    337   client_key_events->swap(key_events);
    338   *modifiers = sticky_modifiers;
    339   return Status(kOk);
    340 }
    341