| // Copyright 2015 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/webdriver/keyboard.h" |
| |
| #include <limits> |
| |
| #include "base/i18n/char_iterator.h" |
| #include "cobalt/base/token.h" |
| #include "cobalt/base/tokens.h" |
| #include "cobalt/dom/keycode.h" |
| |
| using cobalt::dom::KeyboardEvent; |
| |
| namespace cobalt { |
| namespace webdriver { |
| namespace { |
| |
| typedef KeyboardEvent::KeyLocationCode KeyLocationCode; |
| |
| // The following utf-8 code points could be provided as "keys" sent to |
| // WebDriver, and should be mapped to the corresponding keyboard code. |
| enum SpecialKey { |
| kFirstSpecialKey = 0xE000, |
| kSpecialKey_Null = kFirstSpecialKey, |
| kSpecialKey_Cancel, |
| kSpecialKey_Help, |
| kSpecialKey_Backspace, |
| kSpecialKey_Tab, |
| kSpecialKey_Clear, |
| kSpecialKey_Return, |
| kSpecialKey_Enter, |
| kSpecialKey_Shift, |
| kSpecialKey_Ctrl, |
| kSpecialKey_Alt, |
| kSpecialKey_Pause, |
| kSpecialKey_Escape, |
| kSpecialKey_Space, |
| kSpecialKey_Pageup, |
| kSpecialKey_Pagedown, |
| kSpecialKey_End, |
| kSpecialKey_Home, |
| kSpecialKey_LeftArrow, |
| kSpecialKey_UpArrow, |
| kSpecialKey_RightArrow, |
| kSpecialKey_DownArrow, |
| kSpecialKey_Insert, |
| kSpecialKey_Delete, |
| kSpecialKey_Semicolon, |
| kSpecialKey_Equals, |
| kSpecialKey_Numpad0, |
| kSpecialKey_Numpad1, |
| kSpecialKey_Numpad2, |
| kSpecialKey_Numpad3, |
| kSpecialKey_Numpad4, |
| kSpecialKey_Numpad5, |
| kSpecialKey_Numpad6, |
| kSpecialKey_Numpad7, |
| kSpecialKey_Numpad8, |
| kSpecialKey_Numpad9, |
| kSpecialKey_Multiply, |
| kSpecialKey_Add, |
| kSpecialKey_Separator, |
| kSpecialKey_Subtract, |
| kSpecialKey_Decimal, |
| kSpecialKey_Divide, // = 0xE029 |
| // 0xE02A to 0xE030 are not mapped to anything. |
| kSpecialKey_F1 = 0xE031, // |
| kSpecialKey_F2, |
| kSpecialKey_F3, |
| kSpecialKey_F4, |
| kSpecialKey_F5, |
| kSpecialKey_F6, |
| kSpecialKey_F7, |
| kSpecialKey_F8, |
| kSpecialKey_F9, |
| kSpecialKey_F10, |
| kSpecialKey_F11, |
| kSpecialKey_F12, |
| kSpecialKey_Meta, |
| kLastSpecialKey = kSpecialKey_Meta |
| }; |
| // Assert the expected values. |
| COMPILE_ASSERT(kSpecialKey_Divide == 0xE029, MissingAnEnum); |
| COMPILE_ASSERT(kLastSpecialKey == 0xE03D, MissingAnEnum); |
| |
| // Mapping from a special keycode to virtual keycode. Subtract kFirstSpecialKey |
| // from the integer value of the WebDriver keycode and index into this table. |
| const int32 special_keycode_mapping[] = { |
| dom::keycode::kUnknown, // kSpecialKey_NULL |
| dom::keycode::kCancel, // kSpecialKey_Cancel, |
| dom::keycode::kHelp, // kSpecialKey_Help |
| dom::keycode::kBack, // kSpecialKey_Backspace, |
| dom::keycode::kTab, // kSpecialKey_Tab |
| dom::keycode::kClear, // kSpecialKey_Clear |
| dom::keycode::kReturn, // kSpecialKey_Return |
| dom::keycode::kReturn, // kSpecialKey_Enter (on numeric keypad) |
| dom::keycode::kShift, // kSpecialKey_Shift |
| dom::keycode::kControl, // kSpecialKey_Control |
| dom::keycode::kMenu, // kSpecialKey_Alt |
| dom::keycode::kPause, // kSpecialKey_Pause |
| dom::keycode::kEscape, // kSpecialKey_Escape |
| dom::keycode::kSpace, // kSpecialKey_Space |
| dom::keycode::kPrior, // kSpecialKey_Pageup, |
| dom::keycode::kNext, // kSpecialKey_Pagedown, |
| dom::keycode::kEnd, // kSpecialKey_End |
| dom::keycode::kHome, // kSpecialKey_Home |
| dom::keycode::kLeft, // kSpecialKey_LeftArrow, |
| dom::keycode::kUp, // kSpecialKey_UpArrow, |
| dom::keycode::kRight, // kSpecialKey_RightArrow, |
| dom::keycode::kDown, // kSpecialKey_DownArrow, |
| dom::keycode::kInsert, // kSpecialKey_Insert |
| dom::keycode::kDelete, // kSpecialKey_Delete |
| dom::keycode::kOem1, // kSpecialKey_Semicolon, |
| dom::keycode::kOemPlus, // kSpecialKey_Equals (on numeric keypad) |
| dom::keycode::kNumpad0, // kSpecialKey_Numpad0 |
| dom::keycode::kNumpad1, // kSpecialKey_Numpad1 |
| dom::keycode::kNumpad2, // kSpecialKey_Numpad2 |
| dom::keycode::kNumpad3, // kSpecialKey_Numpad3 |
| dom::keycode::kNumpad4, // kSpecialKey_Numpad4 |
| dom::keycode::kNumpad5, // kSpecialKey_Numpad5 |
| dom::keycode::kNumpad6, // kSpecialKey_Numpad6 |
| dom::keycode::kNumpad7, // kSpecialKey_Numpad7 |
| dom::keycode::kNumpad8, // kSpecialKey_Numpad8 |
| dom::keycode::kNumpad9, // kSpecialKey_Numpad9 |
| dom::keycode::kMultiply, // kSpecialKey_Multiply, |
| dom::keycode::kAdd, // kSpecialKey_Add |
| dom::keycode::kSeparator, // kSpecialKey_Separator |
| dom::keycode::kSubtract, // kSpecialKey_Subtract |
| dom::keycode::kDecimal, // kSpecialKey_Decimal |
| dom::keycode::kDivide, // kSpecialKey_Divide = 0xE029 |
| dom::keycode::kUnknown, // 0xE02A |
| dom::keycode::kUnknown, // 0xE02B |
| dom::keycode::kUnknown, // 0xE02C |
| dom::keycode::kUnknown, // 0xE02D |
| dom::keycode::kUnknown, // 0xE02E |
| dom::keycode::kUnknown, // 0xE02F |
| dom::keycode::kUnknown, // 0xE030 |
| dom::keycode::kF1, // kSpecialKey_F1 = 0xE031 |
| dom::keycode::kF2, // kSpecialKey_F2 |
| dom::keycode::kF3, // kSpecialKey_F3 |
| dom::keycode::kF4, // kSpecialKey_F4 |
| dom::keycode::kF5, // kSpecialKey_F5 |
| dom::keycode::kF6, // kSpecialKey_F6 |
| dom::keycode::kF7, // kSpecialKey_F7 |
| dom::keycode::kF8, // kSpecialKey_F8 |
| dom::keycode::kF9, // kSpecialKey_F9 |
| dom::keycode::kF10, // kSpecialKey_F10 |
| dom::keycode::kF11, // kSpecialKey_F11 |
| dom::keycode::kF12, // kSpecialKey_F12 |
| dom::keycode::kLwin, // kSpecialKey_Meta |
| }; |
| |
| // Check that the mapping is the expected size. |
| const int kLargestMappingIndex = kLastSpecialKey - kFirstSpecialKey; |
| COMPILE_ASSERT(arraysize(special_keycode_mapping) == kLargestMappingIndex + 1, |
| IncorrectMappingTable); |
| |
| // Translate a utf8 character to a keycode. Characters that would require the |
| // shift key to be pressed are lower-cased or translated to their corresponding |
| // non-shifted special character. |
| // This is based on a standard US keyboard. |
| int CharacterToKeyCode(char character) { |
| if (character >= '0' && character <= '9') { |
| return character; |
| } |
| if (character >= 'a' && character <= 'z') { |
| return character - 32; |
| } |
| if (character >= 'A' && character <= 'Z') { |
| return character; |
| } |
| |
| switch (character) { |
| case ' ': |
| return dom::keycode::kSpace; |
| case ')': |
| return dom::keycode::k0; |
| case '!': |
| return dom::keycode::k1; |
| case '@': |
| return dom::keycode::k2; |
| case '#': |
| return dom::keycode::k3; |
| case '$': |
| return dom::keycode::k4; |
| case '%': |
| return dom::keycode::k5; |
| case '^': |
| return dom::keycode::k6; |
| case '&': |
| return dom::keycode::k7; |
| case '*': |
| return dom::keycode::k8; |
| case '(': |
| return dom::keycode::k9; |
| |
| case ':': |
| case ';': |
| return dom::keycode::kOem1; |
| case '+': |
| case '=': |
| return dom::keycode::kOemPlus; |
| case '<': |
| case ',': |
| return dom::keycode::kOemComma; |
| case '_': |
| case '-': |
| return dom::keycode::kOemMinus; |
| case '>': |
| case '.': |
| return dom::keycode::kOemPeriod; |
| case '?': |
| case '/': |
| return dom::keycode::kOem2; |
| case '~': |
| case '`': |
| return dom::keycode::kOem3; |
| case '{': |
| case '[': |
| return dom::keycode::kOem4; |
| case '|': |
| case '\\': |
| return dom::keycode::kOem5; |
| case '}': |
| case ']': |
| return dom::keycode::kOem6; |
| case '"': |
| case '\'': |
| return dom::keycode::kOem7; |
| } |
| NOTREACHED(); |
| return 0; |
| } |
| |
| // Returns true iff this utf8 codepoint corresponds to a WebDriver special key. |
| bool IsSpecialKey(int webdriver_key) { |
| return webdriver_key >= kFirstSpecialKey && webdriver_key < kLastSpecialKey; |
| } |
| |
| bool IsModifierKey(int webdriver_key) { |
| return webdriver_key == kSpecialKey_Alt || |
| webdriver_key == kSpecialKey_Shift || |
| webdriver_key == kSpecialKey_Ctrl; |
| } |
| |
| // Returns true if typing the utf8 character on a standard US keyboard would |
| // require the shift modifier to be pressed. |
| bool CharacterRequiresShift(char character) { |
| DCHECK_NE(0, character); |
| const char kSpecialsRequiringShift[] = ")!@#$%%^&*(:+<_>?~{|}\"}"; |
| if (character >= 'A' && character <= 'Z') { |
| return true; |
| } |
| return (strchr(kSpecialsRequiringShift, character) != NULL); |
| } |
| |
| // Returns the keycode that corresponds to this WebDriver special key. |
| int32 GetSpecialKeycode(int32 webdriver_key) { |
| DCHECK(IsSpecialKey(webdriver_key)); |
| int index = webdriver_key - kFirstSpecialKey; |
| DCHECK_GE(index, 0); |
| DCHECK_LT(index, arraysize(special_keycode_mapping)); |
| return special_keycode_mapping[index]; |
| } |
| |
| // Returns the location code for the key. |
| KeyLocationCode GetSpecialKeyLocation(int32 webdriver_key) { |
| DCHECK(IsSpecialKey(webdriver_key)); |
| if ((webdriver_key >= kSpecialKey_Equals && |
| webdriver_key <= kSpecialKey_Divide) || |
| webdriver_key == kSpecialKey_Enter || |
| webdriver_key == kSpecialKey_Equals) { |
| return KeyboardEvent::kDomKeyLocationNumpad; |
| } |
| if (webdriver_key == kSpecialKey_Shift || webdriver_key == kSpecialKey_Ctrl || |
| webdriver_key == kSpecialKey_Alt || webdriver_key == kSpecialKey_Meta) { |
| // Choose all of these are the left ones. WebDriver doesn't distinguish |
| // between left and right modifiers. |
| return KeyboardEvent::kDomKeyLocationLeft; |
| } |
| // Other keys do not have a special location. |
| return KeyboardEvent::kDomKeyLocationStandard; |
| } |
| |
| class KeyTranslator { |
| public: |
| explicit KeyTranslator(Keyboard::KeyboardEventVector* event_vector) |
| : shift_pressed_(false), |
| ctrl_pressed_(false), |
| alt_pressed_(false), |
| event_vector_(event_vector) {} |
| |
| // Runs the sendKeys() algorithm to translate utf8 code points into key |
| // press events. |
| // https://www.w3.org/TR/webdriver/#sendkeys |
| void Translate(const std::string& utf8_keys) { |
| base::i18n::UTF8CharIterator utf8_iterator(&utf8_keys); |
| while (!utf8_iterator.end()) { |
| int32 webdriver_key = utf8_iterator.get(); |
| utf8_iterator.Advance(); |
| if (webdriver_key == kSpecialKey_Null) { |
| // If it's a NULL key, release the modifiers. |
| ReleaseModifiers(); |
| DCHECK(!shift_pressed_); |
| DCHECK(!alt_pressed_); |
| DCHECK(!ctrl_pressed_); |
| } else if (IsModifierKey(webdriver_key)) { |
| // Else if it's a modifier, toggle the modifier state. |
| ToggleModifier(webdriver_key); |
| } else if (IsSpecialKey(webdriver_key)) { |
| // Else if it's a WebDriver special key, translate to key_code and |
| // send key events. |
| int32 key_code = GetSpecialKeycode(webdriver_key); |
| int32 char_code = 0; |
| KeyLocationCode location = GetSpecialKeyLocation(webdriver_key); |
| AddKeyDownEvent(key_code, char_code, location); |
| AddKeyUpEvent(key_code, char_code, location); |
| } else { |
| DCHECK_GE(webdriver_key, 0); |
| DCHECK_LT(webdriver_key, std::numeric_limits<char>::max()); |
| const char character = static_cast<char>(webdriver_key); |
| int key_code = CharacterToKeyCode(character); |
| KeyLocationCode location = KeyboardEvent::kDomKeyLocationStandard; |
| if (CharacterRequiresShift(character)) { |
| // Handle Upper-case characters. Press and release Shift if it's not |
| // being held already. |
| bool shift_was_pressed = shift_pressed_; |
| if (!shift_was_pressed) { |
| ToggleModifier(kSpecialKey_Shift); |
| } |
| int32 char_code = KeyboardEvent::KeyCodeToCharCodeWithShift(key_code); |
| DCHECK_EQ(char_code, character); |
| |
| AddKeyDownEvent(key_code, char_code, location); |
| AddKeyPressEvent(key_code, char_code, location); |
| AddKeyUpEvent(key_code, char_code, location); |
| |
| if (!shift_was_pressed) { |
| ToggleModifier(kSpecialKey_Shift); |
| } |
| } else { |
| // Handle lower-case characters. If shift is pressed, convert the |
| // character to uppercase. |
| int32 char_code = KeyboardEvent::KeyCodeToCharCodeNoShift(key_code); |
| DCHECK_EQ(char_code, character); |
| if (shift_pressed_) { |
| char_code = KeyboardEvent::KeyCodeToCharCodeWithShift(key_code); |
| } |
| AddKeyDownEvent(key_code, char_code, location); |
| AddKeyPressEvent(key_code, char_code, location); |
| AddKeyUpEvent(key_code, char_code, location); |
| } |
| } |
| } |
| } |
| |
| void ReleaseModifiers() { |
| if (alt_pressed_) { |
| ToggleModifier(kSpecialKey_Alt); |
| } |
| if (ctrl_pressed_) { |
| ToggleModifier(kSpecialKey_Ctrl); |
| } |
| if (shift_pressed_) { |
| ToggleModifier(kSpecialKey_Shift); |
| } |
| } |
| |
| private: |
| void ToggleModifier(int webdriver_key) { |
| DCHECK(IsModifierKey(webdriver_key)); |
| bool do_keyup = false; |
| if (webdriver_key == kSpecialKey_Alt) { |
| do_keyup = alt_pressed_; |
| alt_pressed_ = !alt_pressed_; |
| } else if (webdriver_key == kSpecialKey_Shift) { |
| do_keyup = shift_pressed_; |
| shift_pressed_ = !shift_pressed_; |
| } else if (webdriver_key == kSpecialKey_Ctrl) { |
| do_keyup = ctrl_pressed_; |
| ctrl_pressed_ = !ctrl_pressed_; |
| } else { |
| NOTREACHED(); |
| } |
| DCHECK(IsSpecialKey(webdriver_key)); |
| int32 char_code = 0; |
| int32 key_code = GetSpecialKeycode(webdriver_key); |
| KeyLocationCode location = GetSpecialKeyLocation(webdriver_key); |
| if (do_keyup) { |
| AddKeyUpEvent(key_code, char_code, location); |
| } else { |
| AddKeyDownEvent(key_code, char_code, location); |
| } |
| } |
| |
| void AddKeyDownEvent(int key_code, int char_code, KeyLocationCode location) { |
| AddKeyEvent(base::Tokens::keydown(), key_code, char_code, location); |
| } |
| |
| void AddKeyPressEvent(int key_code, int char_code, KeyLocationCode location) { |
| AddKeyEvent(base::Tokens::keypress(), key_code, char_code, location); |
| } |
| |
| void AddKeyUpEvent(int key_code, int char_code, KeyLocationCode location) { |
| AddKeyEvent(base::Tokens::keyup(), key_code, char_code, location); |
| } |
| |
| void AddKeyEvent(base::Token type, int key_code, int char_code, |
| KeyLocationCode location) { |
| dom::KeyboardEventInit event; |
| event.set_location(location); |
| event.set_shift_key(shift_pressed_); |
| event.set_ctrl_key(ctrl_pressed_); |
| event.set_alt_key(alt_pressed_); |
| event.set_key_code(key_code); |
| event.set_char_code(char_code); |
| event.set_repeat(false); |
| event_vector_->push_back(std::make_pair(type, event)); |
| } |
| |
| bool shift_pressed_; |
| bool ctrl_pressed_; |
| bool alt_pressed_; |
| Keyboard::KeyboardEventVector* event_vector_; |
| }; |
| |
| } // namespace |
| |
| void Keyboard::TranslateToKeyEvents(const std::string& utf8_keys, |
| TerminationBehaviour termination_behaviour, |
| KeyboardEventVector* out_events) { |
| KeyTranslator translator(out_events); |
| translator.Translate(utf8_keys); |
| if (termination_behaviour == kReleaseModifiers) { |
| translator.ReleaseModifiers(); |
| } |
| } |
| |
| } // namespace webdriver |
| } // namespace cobalt |