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 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