blob: b05b865c0064f1bf24a35fd2a75ab1dad995092c [file] [log] [blame]
/*
* Copyright (C) 2014 Igalia S.L.
*
* 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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 "config.h"
#include "WebEditorClient.h"
#include <WebCore/Document.h>
#include <WebCore/Editor.h>
#include <WebCore/EventNames.h>
#include <WebCore/Frame.h>
#include <WebCore/KeyboardEvent.h>
#include <WebCore/Node.h>
#include <WebCore/PlatformKeyboardEvent.h>
#include <WebCore/WindowsKeyboardCodes.h>
#include <wtf/NeverDestroyed.h>
using namespace WebCore;
namespace WebKit {
// The idea for the array/map below comes from Blink's EditingBehavior.cpp.
static const unsigned CtrlKey = 1 << 0;
static const unsigned AltKey = 1 << 1;
static const unsigned ShiftKey = 1 << 2;
static const unsigned MetaKey = 1 << 3;
// Keys with special meaning. These will be delegated to the editor using
// the execCommand() method
struct KeyDownEntry {
unsigned virtualKey;
unsigned modifiers;
const char* name;
};
struct KeyPressEntry {
unsigned charCode;
unsigned modifiers;
const char* name;
};
static const KeyDownEntry keyDownEntries[] = {
{ VK_LEFT, 0, "MoveLeft" },
{ VK_LEFT, ShiftKey, "MoveLeftAndModifySelection" },
{ VK_LEFT, CtrlKey, "MoveWordLeft" },
{ VK_LEFT, CtrlKey | ShiftKey,
"MoveWordLeftAndModifySelection" },
{ VK_RIGHT, 0, "MoveRight" },
{ VK_RIGHT, ShiftKey, "MoveRightAndModifySelection" },
{ VK_RIGHT, CtrlKey, "MoveWordRight" },
{ VK_RIGHT, CtrlKey | ShiftKey, "MoveWordRightAndModifySelection" },
{ VK_UP, 0, "MoveUp" },
{ VK_UP, ShiftKey, "MoveUpAndModifySelection" },
{ VK_PRIOR, ShiftKey, "MovePageUpAndModifySelection" },
{ VK_DOWN, 0, "MoveDown" },
{ VK_DOWN, ShiftKey, "MoveDownAndModifySelection" },
{ VK_NEXT, ShiftKey, "MovePageDownAndModifySelection" },
{ VK_UP, CtrlKey | ShiftKey, "MoveParagraphBackwardAndModifySelection" },
{ VK_DOWN, CtrlKey | ShiftKey, "MoveParagraphForwardAndModifySelection" },
{ VK_PRIOR, 0, "MovePageUp" },
{ VK_NEXT, 0, "MovePageDown" },
{ VK_HOME, 0, "MoveToBeginningOfLine" },
{ VK_HOME, ShiftKey,
"MoveToBeginningOfLineAndModifySelection" },
{ VK_HOME, CtrlKey, "MoveToBeginningOfDocument" },
{ VK_HOME, CtrlKey | ShiftKey,
"MoveToBeginningOfDocumentAndModifySelection" },
{ VK_END, 0, "MoveToEndOfLine" },
{ VK_END, ShiftKey, "MoveToEndOfLineAndModifySelection" },
{ VK_END, CtrlKey, "MoveToEndOfDocument" },
{ VK_END, CtrlKey | ShiftKey,
"MoveToEndOfDocumentAndModifySelection" },
{ VK_BACK, 0, "DeleteBackward" },
{ VK_BACK, ShiftKey, "DeleteBackward" },
{ VK_DELETE, 0, "DeleteForward" },
{ VK_BACK, CtrlKey, "DeleteWordBackward" },
{ VK_DELETE, CtrlKey, "DeleteWordForward" },
{ 'B', CtrlKey, "ToggleBold" },
{ 'I', CtrlKey, "ToggleItalic" },
{ 'U', CtrlKey, "ToggleUnderline" },
{ VK_ESCAPE, 0, "Cancel" },
{ VK_OEM_PERIOD, CtrlKey, "Cancel" },
{ VK_TAB, 0, "InsertTab" },
{ VK_TAB, ShiftKey, "InsertBacktab" },
{ VK_RETURN, 0, "InsertNewline" },
{ VK_RETURN, CtrlKey, "InsertNewline" },
{ VK_RETURN, AltKey, "InsertNewline" },
{ VK_RETURN, AltKey | ShiftKey, "InsertNewline" },
{ VK_RETURN, ShiftKey, "InsertLineBreak" },
// These probably need handling somewhere else so do not execute them. The
// 'Cut' command is removing text so let's avoid losing the user losing data
// until we implement clipboard support wherever it should be.
{ VK_INSERT, CtrlKey, "Copy" },
{ VK_INSERT, ShiftKey, "Paste" },
{ VK_DELETE, ShiftKey, "Cut" },
{ 'C', CtrlKey, "Copy" },
{ 'V', CtrlKey, "Paste" },
{ 'V', CtrlKey | ShiftKey, "PasteAndMatchStyle" },
{ 'X', CtrlKey, "Cut" },
{ 'A', CtrlKey, "SelectAll" },
{ 'Z', CtrlKey, "Undo" },
{ 'Z', CtrlKey | ShiftKey, "Redo" },
{ 'Y', CtrlKey, "Redo" },
{ VK_INSERT, 0, "OverWrite" },
};
static const KeyPressEntry keyPressEntries[] = {
{ '\t', 0, "InsertTab" },
{ '\t', ShiftKey, "InsertBacktab" },
{ '\r', 0, "InsertNewline" },
{ '\r', CtrlKey, "InsertNewline" },
{ '\r', ShiftKey, "InsertLineBreak" },
{ '\r', AltKey, "InsertNewline" },
{ '\r', AltKey | ShiftKey, "InsertNewline" },
};
static const char* interpretKeyEvent(const KeyboardEvent& event)
{
static NeverDestroyed<HashMap<int, const char*>> keyDownCommandsMap;
static NeverDestroyed<HashMap<int, const char*>> keyPressCommandsMap;
if (keyDownCommandsMap.get().isEmpty()) {
for (unsigned i = 0; i < WTF_ARRAY_LENGTH(keyDownEntries); i++)
keyDownCommandsMap.get().set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name);
for (unsigned i = 0; i < WTF_ARRAY_LENGTH(keyPressEntries); i++)
keyPressCommandsMap.get().set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name);
}
unsigned modifiers = 0;
if (event.shiftKey())
modifiers |= ShiftKey;
if (event.altKey())
modifiers |= AltKey;
if (event.ctrlKey())
modifiers |= CtrlKey;
if (event.metaKey())
modifiers |= MetaKey;
if (event.type() == eventNames().keydownEvent) {
int mapKey = modifiers << 16 | event.keyCode();
return mapKey ? keyDownCommandsMap.get().get(mapKey) : nullptr;
}
int mapKey = modifiers << 16 | event.charCode();
return mapKey ? keyPressCommandsMap.get().get(mapKey) : nullptr;
}
static void handleKeyPress(Frame& frame, KeyboardEvent& event, const PlatformKeyboardEvent& platformEvent)
{
String commandName = interpretKeyEvent(event);
if (!commandName.isEmpty()) {
frame.editor().command(commandName).execute();
event.setDefaultHandled();
return;
}
// Don't insert null or control characters as they can result in unexpected behaviour
if (event.charCode() < ' ')
return;
// Don't insert anything if a modifier is pressed and it has not been handled yet
if (platformEvent.ctrlKey() || platformEvent.altKey())
return;
if (frame.editor().insertText(platformEvent.text(), &event))
event.setDefaultHandled();
}
static void handleKeyDown(Frame& frame, KeyboardEvent& event, const PlatformKeyboardEvent&)
{
String commandName = interpretKeyEvent(event);
if (commandName.isEmpty())
return;
// We shouldn't insert text through the editor. Let WebCore decide
// how to handle that (say, Tab, which could also be used to
// change focus).
Editor::Command command = frame.editor().command(commandName);
if (command.isTextInsertion())
return;
command.execute();
event.setDefaultHandled();
}
void WebEditorClient::handleKeyboardEvent(WebCore::KeyboardEvent* event)
{
ASSERT(event->target());
auto* frame = downcast<Node>(event->target())->document().frame();
ASSERT(frame);
// FIXME: Reorder the checks in a more sensible way.
auto* platformEvent = event->underlyingPlatformEvent();
if (!platformEvent)
return;
// If this was an IME event don't do anything.
if (platformEvent->windowsVirtualKeyCode() == VK_PROCESSKEY)
return;
// Don't allow text insertion for nodes that cannot edit.
if (!frame->editor().canEdit())
return;
// This is just a normal text insertion, so wait to execute the insertion
// until a keypress event happens. This will ensure that the insertion will not
// be reflected in the contents of the field until the keyup DOM event.
if (event->type() == eventNames().keypressEvent)
return handleKeyPress(*frame, *event, *platformEvent);
if (event->type() == eventNames().keydownEvent)
return handleKeyDown(*frame, *event, *platformEvent);
}
void WebEditorClient::handleInputMethodKeydown(WebCore::KeyboardEvent* event)
{
auto* platformEvent = event->underlyingPlatformEvent();
if (platformEvent && platformEvent->windowsVirtualKeyCode() == VK_PROCESSKEY)
event->preventDefault();
}
} // namespace WebKit