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