| /* |
| * Copyright (C) 2006, 2007 Apple, Inc. All rights reserved. |
| * Copyright (C) 2012 Google, Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/editing/editing_behavior.h" |
| |
| #include "build/build_config.h" |
| #include "third_party/blink/public/common/input/web_input_event.h" |
| #include "third_party/blink/public/web/web_settings.h" |
| #include "third_party/blink/renderer/core/events/keyboard_event.h" |
| #include "third_party/blink/renderer/platform/keyboard_codes.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| // |
| // The below code was adapted from the WebKit file webview.cpp |
| // |
| |
| const unsigned kCtrlKey = WebInputEvent::kControlKey; |
| const unsigned kAltKey = WebInputEvent::kAltKey; |
| const unsigned kShiftKey = WebInputEvent::kShiftKey; |
| const unsigned kMetaKey = WebInputEvent::kMetaKey; |
| #if BUILDFLAG(IS_MAC) |
| // Aliases for the generic key defintions to make kbd shortcuts definitions more |
| // readable on OS X. |
| const unsigned kOptionKey = kAltKey; |
| |
| // Do not use this constant for anything but cursor movement commands. Keys |
| // with cmd set have their |isSystemKey| bit set, so chances are the shortcut |
| // will not be executed. Another, less important, reason is that shortcuts |
| // defined in the layoutObject do not blink the menu item that they triggered. |
| // See http://crbug.com/25856 and the bugs linked from there for details. |
| const unsigned kCommandKey = kMetaKey; |
| #endif |
| |
| // Keys with special meaning. These will be delegated to the editor using |
| // the execCommand() method |
| struct KeyboardCodeKeyDownEntry { |
| unsigned virtual_key; |
| unsigned modifiers; |
| const char* name; |
| }; |
| |
| struct KeyboardCodeKeyPressEntry { |
| unsigned char_code; |
| unsigned modifiers; |
| const char* name; |
| }; |
| |
| // DomKey has a broader range than KeyboardCode, we need DomKey to handle some |
| // special keys. |
| // Note: We cannot use DomKey for printable keys since it may vary based on |
| // locale. |
| struct DomKeyKeyDownEntry { |
| const char* key; |
| unsigned modifiers; |
| const char* name; |
| }; |
| |
| #if BUILDFLAG(IS_MAC) |
| #define OPTION_OR_CTRL_KEY kOptionKey |
| #else |
| #define OPTION_OR_CTRL_KEY kCtrlKey |
| #endif |
| |
| // Key bindings with command key on Mac and alt key on other platforms are |
| // marked as system key events and will be ignored (with the exception |
| // of Command-B and Command-I) so they shouldn't be added here. |
| const KeyboardCodeKeyDownEntry kKeyboardCodeKeyDownEntries[] = { |
| {VKEY_LEFT, 0, "MoveLeft"}, |
| {VKEY_LEFT, kShiftKey, "MoveLeftAndModifySelection"}, |
| {VKEY_LEFT, OPTION_OR_CTRL_KEY, "MoveWordLeft"}, |
| {VKEY_LEFT, OPTION_OR_CTRL_KEY | kShiftKey, |
| "MoveWordLeftAndModifySelection"}, |
| {VKEY_RIGHT, 0, "MoveRight"}, |
| {VKEY_RIGHT, kShiftKey, "MoveRightAndModifySelection"}, |
| {VKEY_RIGHT, OPTION_OR_CTRL_KEY, "MoveWordRight"}, |
| {VKEY_RIGHT, OPTION_OR_CTRL_KEY | kShiftKey, |
| "MoveWordRightAndModifySelection"}, |
| {VKEY_UP, 0, "MoveUp"}, |
| {VKEY_UP, kShiftKey, "MoveUpAndModifySelection"}, |
| {VKEY_PRIOR, kShiftKey, "MovePageUpAndModifySelection"}, |
| {VKEY_DOWN, 0, "MoveDown"}, |
| {VKEY_DOWN, kShiftKey, "MoveDownAndModifySelection"}, |
| {VKEY_NEXT, kShiftKey, "MovePageDownAndModifySelection"}, |
| {VKEY_UP, OPTION_OR_CTRL_KEY, "MoveParagraphBackward"}, |
| {VKEY_DOWN, OPTION_OR_CTRL_KEY, "MoveParagraphForward"}, |
| #if !BUILDFLAG(IS_MAC) |
| {VKEY_UP, kCtrlKey | kShiftKey, "MoveParagraphBackwardAndModifySelection"}, |
| {VKEY_DOWN, kCtrlKey | kShiftKey, "MoveParagraphForwardAndModifySelection"}, |
| {VKEY_PRIOR, 0, "MovePageUp"}, |
| {VKEY_NEXT, 0, "MovePageDown"}, |
| #endif |
| {VKEY_HOME, 0, "MoveToBeginningOfLine"}, |
| {VKEY_HOME, kShiftKey, "MoveToBeginningOfLineAndModifySelection"}, |
| #if BUILDFLAG(IS_MAC) |
| {VKEY_PRIOR, kOptionKey, "MovePageUp"}, |
| {VKEY_NEXT, kOptionKey, "MovePageDown"}, |
| #endif |
| #if !BUILDFLAG(IS_MAC) |
| {VKEY_HOME, kCtrlKey, "MoveToBeginningOfDocument"}, |
| {VKEY_HOME, kCtrlKey | kShiftKey, |
| "MoveToBeginningOfDocumentAndModifySelection"}, |
| #endif |
| {VKEY_END, 0, "MoveToEndOfLine"}, |
| {VKEY_END, kShiftKey, "MoveToEndOfLineAndModifySelection"}, |
| #if !BUILDFLAG(IS_MAC) |
| {VKEY_END, kCtrlKey, "MoveToEndOfDocument"}, |
| {VKEY_END, kCtrlKey | kShiftKey, "MoveToEndOfDocumentAndModifySelection"}, |
| #endif |
| {VKEY_BACK, 0, "DeleteBackward"}, |
| {VKEY_BACK, kShiftKey, "DeleteBackward"}, |
| {VKEY_DELETE, 0, "DeleteForward"}, |
| {VKEY_BACK, OPTION_OR_CTRL_KEY, "DeleteWordBackward"}, |
| {VKEY_DELETE, OPTION_OR_CTRL_KEY, "DeleteWordForward"}, |
| #if BUILDFLAG(IS_MAC) |
| {'B', kCommandKey, "ToggleBold"}, |
| {'I', kCommandKey, "ToggleItalic"}, |
| {'U', kCommandKey, "ToggleUnderline"}, |
| #else |
| {'B', kCtrlKey, "ToggleBold"}, |
| {'I', kCtrlKey, "ToggleItalic"}, |
| {'U', kCtrlKey, "ToggleUnderline"}, |
| #endif |
| {VKEY_ESCAPE, 0, "Cancel"}, |
| {VKEY_OEM_PERIOD, kCtrlKey, "Cancel"}, |
| {VKEY_TAB, 0, "InsertTab"}, |
| {VKEY_TAB, kShiftKey, "InsertBacktab"}, |
| {VKEY_RETURN, 0, "InsertNewline"}, |
| {VKEY_RETURN, kCtrlKey, "InsertNewline"}, |
| {VKEY_RETURN, kAltKey, "InsertNewline"}, |
| {VKEY_RETURN, kAltKey | kShiftKey, "InsertNewline"}, |
| {VKEY_RETURN, kShiftKey, "InsertLineBreak"}, |
| {VKEY_INSERT, kCtrlKey, "Copy"}, |
| {VKEY_INSERT, kShiftKey, "Paste"}, |
| {VKEY_DELETE, kShiftKey, "Cut"}, |
| #if !BUILDFLAG(IS_MAC) |
| // On OS X, we pipe these back to the browser, so that it can do menu item |
| // blinking. |
| {'C', kCtrlKey, "Copy"}, |
| {'V', kCtrlKey, "Paste"}, |
| {'V', kCtrlKey | kShiftKey, "PasteAndMatchStyle"}, |
| {'X', kCtrlKey, "Cut"}, |
| {'A', kCtrlKey, "SelectAll"}, |
| {'Z', kCtrlKey, "Undo"}, |
| {'Z', kCtrlKey | kShiftKey, "Redo"}, |
| {'Y', kCtrlKey, "Redo"}, |
| #endif |
| #if BUILDFLAG(IS_WIN) |
| {VKEY_BACK, kAltKey, "Undo"}, |
| {VKEY_BACK, kAltKey | kShiftKey, "Redo"}, |
| #endif |
| {VKEY_INSERT, 0, "OverWrite"}, |
| #if BUILDFLAG(IS_ANDROID) |
| {VKEY_BACK, kAltKey, "DeleteToBeginningOfLine"}, |
| #endif |
| }; |
| |
| const KeyboardCodeKeyPressEntry kKeyboardCodeKeyPressEntries[] = { |
| {'\t', 0, "InsertTab"}, |
| {'\t', kShiftKey, "InsertBacktab"}, |
| {'\r', 0, "InsertNewline"}, |
| {'\r', kShiftKey, "InsertLineBreak"}, |
| }; |
| |
| const DomKeyKeyDownEntry kDomKeyKeyDownEntries[] = { |
| {"Copy", 0, "Copy"}, |
| {"Cut", 0, "Cut"}, |
| {"Paste", 0, "Paste"}, |
| }; |
| |
| #undef OPTION_OR_CTRL_KEY |
| |
| const char* LookupCommandNameFromDomKeyKeyDown(const String& key, |
| unsigned modifiers) { |
| // This table is not likely to grow, so sequential search is fine here. |
| for (const auto& entry : kDomKeyKeyDownEntries) { |
| if (key == entry.key && modifiers == entry.modifiers) |
| return entry.name; |
| } |
| return nullptr; |
| } |
| |
| const int kVkeyForwardChar = VKEY_RIGHT; |
| const int kVkeyBackwardChar = VKEY_LEFT; |
| const int kVkeyNextLine = VKEY_DOWN; |
| const int kVkeyPreviousLine = VKEY_UP; |
| |
| // Each of the following arrays contains logical behaviors in kVerticalRl, |
| // kVerticalLr, kSidewaysRl, and kSidewaysLr. |
| const int kPhysicalLeftToLogical[] = {kVkeyNextLine, kVkeyPreviousLine, |
| kVkeyNextLine, kVkeyPreviousLine}; |
| const int kPhysicalRightToLogical[] = {kVkeyPreviousLine, kVkeyNextLine, |
| kVkeyPreviousLine, kVkeyNextLine}; |
| const int kPhysicalUpToLogical[] = {kVkeyBackwardChar, kVkeyBackwardChar, |
| kVkeyForwardChar, kVkeyForwardChar}; |
| const int kPhysicalDownToLogical[] = {kVkeyForwardChar, kVkeyForwardChar, |
| kVkeyBackwardChar, kVkeyBackwardChar}; |
| |
| int TransposeArrowKey(int key_code, WritingMode writing_mode) { |
| if (writing_mode == WritingMode::kHorizontalTb) { |
| return key_code; |
| } |
| DCHECK_EQ(1, static_cast<uint8_t>(WritingMode::kVerticalRl)); |
| DCHECK_EQ(2, static_cast<uint8_t>(WritingMode::kVerticalLr)); |
| DCHECK_EQ(3, static_cast<uint8_t>(WritingMode::kSidewaysRl)); |
| DCHECK_EQ(4, static_cast<uint8_t>(WritingMode::kSidewaysLr)); |
| DCHECK_EQ(4, static_cast<uint8_t>(WritingMode::kMaxWritingMode)); |
| unsigned index = static_cast<uint8_t>(writing_mode) - 1; |
| switch (key_code) { |
| case VKEY_LEFT: |
| CHECK_LT(index, std::size(kPhysicalLeftToLogical)); |
| return kPhysicalLeftToLogical[index]; |
| case VKEY_RIGHT: |
| CHECK_LT(index, std::size(kPhysicalRightToLogical)); |
| return kPhysicalRightToLogical[index]; |
| case VKEY_UP: |
| CHECK_LT(index, std::size(kPhysicalUpToLogical)); |
| return kPhysicalUpToLogical[index]; |
| case VKEY_DOWN: |
| CHECK_LT(index, std::size(kPhysicalDownToLogical)); |
| return kPhysicalDownToLogical[index]; |
| } |
| return key_code; |
| } |
| |
| } // anonymous namespace |
| |
| const char* EditingBehavior::InterpretKeyEvent(const KeyboardEvent& event, |
| WritingMode writing_mode) const { |
| const WebKeyboardEvent* key_event = event.KeyEvent(); |
| if (!key_event) |
| return ""; |
| |
| static HashMap<int, const char*>* key_down_commands_map = nullptr; |
| static HashMap<int, const char*>* key_press_commands_map = nullptr; |
| |
| if (!key_down_commands_map) { |
| key_down_commands_map = new HashMap<int, const char*>; |
| key_press_commands_map = new HashMap<int, const char*>; |
| |
| for (const auto& entry : kKeyboardCodeKeyDownEntries) { |
| key_down_commands_map->Set(entry.modifiers << 16 | entry.virtual_key, |
| entry.name); |
| } |
| |
| for (const auto& entry : kKeyboardCodeKeyPressEntries) { |
| key_press_commands_map->Set(entry.modifiers << 16 | entry.char_code, |
| entry.name); |
| } |
| } |
| |
| unsigned modifiers = |
| key_event->GetModifiers() & (kShiftKey | kAltKey | kCtrlKey | kMetaKey); |
| |
| auto FindName = [=](HashMap<int, const char*>* map, int code) -> const char* { |
| int map_key = modifiers << 16 | code; |
| if (!map_key) |
| return nullptr; |
| auto it = map->find(map_key); |
| if (it == map->end()) |
| return nullptr; |
| DCHECK(it->value); |
| return it->value; |
| }; |
| |
| if (key_event->GetType() == WebInputEvent::Type::kRawKeyDown) { |
| const char* name = |
| FindName(key_down_commands_map, |
| TransposeArrowKey(event.keyCode(), writing_mode)); |
| return name ? name |
| : LookupCommandNameFromDomKeyKeyDown(event.key(), modifiers); |
| } |
| return FindName(key_press_commands_map, event.charCode()); |
| } |
| |
| bool EditingBehavior::ShouldInsertCharacter(const KeyboardEvent& event) const { |
| if (event.KeyEvent()->text[1] != 0) |
| return true; |
| |
| // On Gtk/Linux, it emits key events with ASCII text and ctrl on for ctrl-<x>. |
| // In Webkit, EditorClient::handleKeyboardEvent in |
| // WebKit/gtk/WebCoreSupport/EditorClientGtk.cpp drop such events. |
| // On Mac, it emits key events with ASCII text and meta on for Command-<x>. |
| // These key events should not emit text insert event. |
| // Alt key would be used to insert alternative character, so we should let |
| // through. Also note that Ctrl-Alt combination equals to AltGr key which is |
| // also used to insert alternative character. |
| // http://code.google.com/p/chromium/issues/detail?id=10846 |
| // Windows sets both alt and meta are on when "Alt" key pressed. |
| // http://code.google.com/p/chromium/issues/detail?id=2215 |
| // Also, we should not rely on an assumption that keyboards don't |
| // send ASCII characters when pressing a control key on Windows, |
| // which may be configured to do it so by user. |
| // See also http://en.wikipedia.org/wiki/Keyboard_Layout |
| // FIXME(ukai): investigate more detail for various keyboard layout. |
| UChar ch = event.KeyEvent()->text[0U]; |
| |
| // Don't insert null or control characters as they can result in |
| // unexpected behaviour |
| if (ch < ' ') |
| return false; |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| // According to XKB map no keyboard combinations with ctrl key are mapped to |
| // printable characters, however we need the filter as the DomKey/text could |
| // contain printable characters. |
| if (event.ctrlKey()) |
| return false; |
| #elif !BUILDFLAG(IS_WIN) |
| // Don't insert ASCII character if ctrl w/o alt or meta is on. |
| // On Mac, we should ignore events when meta is on (Command-<x>). |
| if (ch < 0x80) { |
| if (event.ctrlKey() && !event.altKey()) |
| return false; |
| #if BUILDFLAG(IS_MAC) |
| if (event.metaKey()) |
| return false; |
| #endif |
| } |
| #endif |
| |
| return true; |
| } |
| |
| } // namespace blink |