1/*
2 * Copyright (C) 2011 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public License
6 * as published by the Free Software Foundation; either version 2 of
7 * the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free
16 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301 USA
18 */
19
20#include "config.h"
21#include "WebEditorClient.h"
22
23#include <WebCore/Document.h>
24#include <WebCore/Editor.h>
25#include <WebCore/EventNames.h>
26#include <WebCore/Frame.h>
27#include <WebCore/KeyboardEvent.h>
28#include <WebCore/Pasteboard.h>
29#include <WebCore/PlatformKeyboardEvent.h>
30#include <WebCore/markup.h>
31#include <wtf/Variant.h>
32#include <wtf/glib/GRefPtr.h>
33
34namespace WebKit {
35using namespace WebCore;
36
37bool WebEditorClient::handleGtkEditorCommand(Frame& frame, const String& command, bool allowTextInsertion)
38{
39 if (command == "GtkInsertEmoji"_s) {
40 if (!allowTextInsertion)
41 return false;
42 m_page->showEmojiPicker(frame);
43 return true;
44 }
45
46 return false;
47}
48
49bool WebEditorClient::executePendingEditorCommands(Frame& frame, const Vector<WTF::String>& pendingEditorCommands, bool allowTextInsertion)
50{
51 Vector<Variant<Editor::Command, String>> commands;
52 for (auto& commandString : pendingEditorCommands) {
53 if (commandString.startsWith("Gtk"))
54 commands.append(commandString);
55 else {
56 Editor::Command command = frame.editor().command(commandString);
57 if (command.isTextInsertion() && !allowTextInsertion)
58 return false;
59
60 commands.append(WTFMove(command));
61 }
62 }
63
64 for (auto& commandVariant : commands) {
65 if (WTF::holds_alternative<String>(commandVariant)) {
66 if (!handleGtkEditorCommand(frame, WTF::get<String>(commandVariant), allowTextInsertion))
67 return false;
68 } else {
69 auto& command = WTF::get<Editor::Command>(commandVariant);
70 if (!command.execute())
71 return false;
72 }
73 }
74
75 return true;
76}
77
78void WebEditorClient::handleKeyboardEvent(KeyboardEvent& event)
79{
80 auto* platformEvent = event.underlyingPlatformEvent();
81 if (!platformEvent)
82 return;
83
84 // If this was an IME event don't do anything.
85 if (platformEvent->handledByInputMethod())
86 return;
87
88 ASSERT(event.target());
89 auto* frame = downcast<Node>(event.target())->document().frame();
90 ASSERT(frame);
91
92 const Vector<String> pendingEditorCommands = platformEvent->commands();
93 if (!pendingEditorCommands.isEmpty()) {
94
95 // During RawKeyDown events if an editor command will insert text, defer
96 // the insertion until the keypress event. We want keydown to bubble up
97 // through the DOM first.
98 if (platformEvent->type() == PlatformEvent::RawKeyDown) {
99 if (executePendingEditorCommands(*frame, pendingEditorCommands, false))
100 event.setDefaultHandled();
101
102 return;
103 }
104
105 // Only allow text insertion commands if the current node is editable.
106 if (executePendingEditorCommands(*frame, pendingEditorCommands, frame->editor().canEdit())) {
107 event.setDefaultHandled();
108 return;
109 }
110 }
111
112 // Don't allow text insertion for nodes that cannot edit.
113 if (!frame->editor().canEdit())
114 return;
115
116 // This is just a normal text insertion, so wait to execute the insertion
117 // until a keypress event happens. This will ensure that the insertion will not
118 // be reflected in the contents of the field until the keyup DOM event.
119 if (event.type() != eventNames().keypressEvent)
120 return;
121
122 // Don't insert null or control characters as they can result in unexpected behaviour
123 if (event.charCode() < ' ')
124 return;
125
126 // Don't insert anything if a modifier is pressed
127 if (platformEvent->controlKey() || platformEvent->altKey())
128 return;
129
130 if (frame->editor().insertText(platformEvent->text(), &event))
131 event.setDefaultHandled();
132}
133
134void WebEditorClient::handleInputMethodKeydown(KeyboardEvent& event)
135{
136 auto* platformEvent = event.underlyingPlatformEvent();
137 if (platformEvent && platformEvent->handledByInputMethod())
138 event.setDefaultHandled();
139}
140
141void WebEditorClient::updateGlobalSelection(Frame* frame)
142{
143 if (!frame->selection().isRange())
144 return;
145 RefPtr<Range> range = frame->selection().toNormalizedRange();
146 if (!range)
147 return;
148
149 PasteboardWebContent pasteboardContent;
150 pasteboardContent.canSmartCopyOrDelete = false;
151 pasteboardContent.text = range->text();
152 pasteboardContent.markup = serializePreservingVisualAppearance(frame->selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy);
153 Pasteboard::createForGlobalSelection()->write(pasteboardContent);
154}
155
156bool WebEditorClient::shouldShowUnicodeMenu()
157{
158 return true;
159}
160
161}
162