1/*
2 * Copyright (C) 2017 Igalia S.L.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "WebAutomationSession.h"
28
29#include "WebAutomationSessionMacros.h"
30#include "WebPageProxy.h"
31#include <WebCore/GtkUtilities.h>
32#include <gtk/gtk.h>
33
34namespace WebKit {
35using namespace WebCore;
36
37static unsigned modifiersToEventState(OptionSet<WebEvent::Modifier> modifiers)
38{
39 unsigned state = 0;
40 if (modifiers.contains(WebEvent::Modifier::ControlKey))
41 state |= GDK_CONTROL_MASK;
42 if (modifiers.contains(WebEvent::Modifier::ShiftKey))
43 state |= GDK_SHIFT_MASK;
44 if (modifiers.contains(WebEvent::Modifier::AltKey))
45 state |= GDK_META_MASK;
46 if (modifiers.contains(WebEvent::Modifier::CapsLockKey))
47 state |= GDK_LOCK_MASK;
48 return state;
49}
50
51static unsigned mouseButtonToGdkButton(WebMouseEvent::Button button)
52{
53 switch (button) {
54 case WebMouseEvent::NoButton:
55 case WebMouseEvent::LeftButton:
56 return GDK_BUTTON_PRIMARY;
57 case WebMouseEvent::MiddleButton:
58 return GDK_BUTTON_MIDDLE;
59 case WebMouseEvent::RightButton:
60 return GDK_BUTTON_SECONDARY;
61 }
62 return GDK_BUTTON_PRIMARY;
63}
64
65static void doMouseEvent(GdkEventType type, GtkWidget* widget, const WebCore::IntPoint& location, unsigned button, unsigned state)
66{
67 ASSERT(type == GDK_BUTTON_PRESS || type == GDK_BUTTON_RELEASE);
68
69 GUniquePtr<GdkEvent> event(gdk_event_new(type));
70 event->button.window = gtk_widget_get_window(widget);
71 g_object_ref(event->button.window);
72 event->button.time = GDK_CURRENT_TIME;
73 event->button.x = location.x();
74 event->button.y = location.y();
75 event->button.axes = 0;
76 event->button.state = state;
77 event->button.button = button;
78#if GTK_CHECK_VERSION(3, 20, 0)
79 event->button.device = gdk_seat_get_pointer(gdk_display_get_default_seat(gtk_widget_get_display(widget)));
80#else
81 event->button.device = gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gtk_widget_get_display(widget)));
82#endif
83 int xRoot, yRoot;
84 gdk_window_get_root_coords(gtk_widget_get_window(widget), location.x(), location.y(), &xRoot, &yRoot);
85 event->button.x_root = xRoot;
86 event->button.y_root = yRoot;
87 gtk_main_do_event(event.get());
88}
89
90static void doMotionEvent(GtkWidget* widget, const WebCore::IntPoint& location, unsigned state)
91{
92 GUniquePtr<GdkEvent> event(gdk_event_new(GDK_MOTION_NOTIFY));
93 event->motion.window = gtk_widget_get_window(widget);
94 g_object_ref(event->motion.window);
95 event->motion.time = GDK_CURRENT_TIME;
96 event->motion.x = location.x();
97 event->motion.y = location.y();
98 event->motion.axes = 0;
99 event->motion.state = state;
100#if GTK_CHECK_VERSION(3, 20, 0)
101 event->motion.device = gdk_seat_get_pointer(gdk_display_get_default_seat(gtk_widget_get_display(widget)));
102#else
103 event->motion.device = gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gtk_widget_get_display(widget)));
104#endif
105 int xRoot, yRoot;
106 gdk_window_get_root_coords(gtk_widget_get_window(widget), location.x(), location.y(), &xRoot, &yRoot);
107 event->motion.x_root = xRoot;
108 event->motion.y_root = yRoot;
109 gtk_main_do_event(event.get());
110}
111
112void WebAutomationSession::platformSimulateMouseInteraction(WebPageProxy& page, MouseInteraction interaction, WebMouseEvent::Button button, const WebCore::IntPoint& locationInView, OptionSet<WebEvent::Modifier> keyModifiers)
113{
114 unsigned gdkButton = mouseButtonToGdkButton(button);
115 auto modifier = stateModifierForGdkButton(gdkButton);
116 unsigned state = modifiersToEventState(keyModifiers) | m_currentModifiers;
117
118 switch (interaction) {
119 case MouseInteraction::Move:
120 doMotionEvent(page.viewWidget(), locationInView, state);
121 break;
122 case MouseInteraction::Down:
123 doMouseEvent(GDK_BUTTON_PRESS, page.viewWidget(), locationInView, gdkButton, state);
124 m_currentModifiers |= modifier;
125 break;
126 case MouseInteraction::Up:
127 doMouseEvent(GDK_BUTTON_RELEASE, page.viewWidget(), locationInView, gdkButton, state);
128 m_currentModifiers &= ~modifier;
129 break;
130 case MouseInteraction::SingleClick:
131 doMouseEvent(GDK_BUTTON_PRESS, page.viewWidget(), locationInView, gdkButton, state);
132 doMouseEvent(GDK_BUTTON_RELEASE, page.viewWidget(), locationInView, gdkButton, state | modifier);
133 break;
134 case MouseInteraction::DoubleClick:
135 doMouseEvent(GDK_BUTTON_PRESS, page.viewWidget(), locationInView, gdkButton, state);
136 doMouseEvent(GDK_BUTTON_RELEASE, page.viewWidget(), locationInView, gdkButton, state | modifier);
137 doMouseEvent(GDK_BUTTON_PRESS, page.viewWidget(), locationInView, gdkButton, state);
138 doMouseEvent(GDK_BUTTON_RELEASE, page.viewWidget(), locationInView, gdkButton, state | modifier);
139 break;
140 }
141}
142
143static void doKeyStrokeEvent(GdkEventType type, GtkWidget* widget, unsigned keyVal, unsigned state, bool doReleaseAfterPress = false)
144{
145 ASSERT(type == GDK_KEY_PRESS || type == GDK_KEY_RELEASE);
146
147 GUniquePtr<GdkEvent> event(gdk_event_new(type));
148 event->key.keyval = keyVal;
149
150 event->key.time = GDK_CURRENT_TIME;
151 event->key.window = gtk_widget_get_window(widget);
152 g_object_ref(event->key.window);
153
154#if GTK_CHECK_VERSION(3, 20, 0)
155 gdk_event_set_device(event.get(), gdk_seat_get_pointer(gdk_display_get_default_seat(gtk_widget_get_display(widget))));
156#else
157 gdk_event_set_device(event.get(), gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gtk_widget_get_display(widget))));
158#endif
159 event->key.state = state;
160
161 // When synthesizing an event, an invalid hardware_keycode value can cause it to be badly processed by GTK+.
162 GUniqueOutPtr<GdkKeymapKey> keys;
163 int keysCount;
164 if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), keyVal, &keys.outPtr(), &keysCount) && keysCount)
165 event->key.hardware_keycode = keys.get()[0].keycode;
166
167 gtk_main_do_event(event.get());
168 if (doReleaseAfterPress) {
169 ASSERT(type == GDK_KEY_PRESS);
170 event->key.type = GDK_KEY_RELEASE;
171 gtk_main_do_event(event.get());
172 }
173}
174
175static int keyCodeForVirtualKey(Inspector::Protocol::Automation::VirtualKey key)
176{
177 switch (key) {
178 case Inspector::Protocol::Automation::VirtualKey::Shift:
179 return GDK_KEY_Shift_R;
180 case Inspector::Protocol::Automation::VirtualKey::Control:
181 return GDK_KEY_Control_R;
182 case Inspector::Protocol::Automation::VirtualKey::Alternate:
183 return GDK_KEY_Alt_L;
184 case Inspector::Protocol::Automation::VirtualKey::Meta:
185 return GDK_KEY_Meta_R;
186 case Inspector::Protocol::Automation::VirtualKey::Command:
187 return GDK_KEY_Execute;
188 case Inspector::Protocol::Automation::VirtualKey::Help:
189 return GDK_KEY_Help;
190 case Inspector::Protocol::Automation::VirtualKey::Backspace:
191 return GDK_KEY_BackSpace;
192 case Inspector::Protocol::Automation::VirtualKey::Tab:
193 return GDK_KEY_Tab;
194 case Inspector::Protocol::Automation::VirtualKey::Clear:
195 return GDK_KEY_Clear;
196 case Inspector::Protocol::Automation::VirtualKey::Enter:
197 return GDK_KEY_Return;
198 case Inspector::Protocol::Automation::VirtualKey::Pause:
199 return GDK_KEY_Pause;
200 case Inspector::Protocol::Automation::VirtualKey::Cancel:
201 return GDK_KEY_Cancel;
202 case Inspector::Protocol::Automation::VirtualKey::Escape:
203 return GDK_KEY_Escape;
204 case Inspector::Protocol::Automation::VirtualKey::PageUp:
205 return GDK_KEY_Page_Up;
206 case Inspector::Protocol::Automation::VirtualKey::PageDown:
207 return GDK_KEY_Page_Down;
208 case Inspector::Protocol::Automation::VirtualKey::End:
209 return GDK_KEY_End;
210 case Inspector::Protocol::Automation::VirtualKey::Home:
211 return GDK_KEY_Home;
212 case Inspector::Protocol::Automation::VirtualKey::LeftArrow:
213 return GDK_KEY_Left;
214 case Inspector::Protocol::Automation::VirtualKey::UpArrow:
215 return GDK_KEY_Up;
216 case Inspector::Protocol::Automation::VirtualKey::RightArrow:
217 return GDK_KEY_Right;
218 case Inspector::Protocol::Automation::VirtualKey::DownArrow:
219 return GDK_KEY_Down;
220 case Inspector::Protocol::Automation::VirtualKey::Insert:
221 return GDK_KEY_Insert;
222 case Inspector::Protocol::Automation::VirtualKey::Delete:
223 return GDK_KEY_Delete;
224 case Inspector::Protocol::Automation::VirtualKey::Space:
225 return GDK_KEY_space;
226 case Inspector::Protocol::Automation::VirtualKey::Semicolon:
227 return GDK_KEY_semicolon;
228 case Inspector::Protocol::Automation::VirtualKey::Equals:
229 return GDK_KEY_equal;
230 case Inspector::Protocol::Automation::VirtualKey::Return:
231 return GDK_KEY_Return;
232 case Inspector::Protocol::Automation::VirtualKey::NumberPad0:
233 return GDK_KEY_KP_0;
234 case Inspector::Protocol::Automation::VirtualKey::NumberPad1:
235 return GDK_KEY_KP_1;
236 case Inspector::Protocol::Automation::VirtualKey::NumberPad2:
237 return GDK_KEY_KP_2;
238 case Inspector::Protocol::Automation::VirtualKey::NumberPad3:
239 return GDK_KEY_KP_3;
240 case Inspector::Protocol::Automation::VirtualKey::NumberPad4:
241 return GDK_KEY_KP_4;
242 case Inspector::Protocol::Automation::VirtualKey::NumberPad5:
243 return GDK_KEY_KP_5;
244 case Inspector::Protocol::Automation::VirtualKey::NumberPad6:
245 return GDK_KEY_KP_6;
246 case Inspector::Protocol::Automation::VirtualKey::NumberPad7:
247 return GDK_KEY_KP_7;
248 case Inspector::Protocol::Automation::VirtualKey::NumberPad8:
249 return GDK_KEY_KP_8;
250 case Inspector::Protocol::Automation::VirtualKey::NumberPad9:
251 return GDK_KEY_KP_9;
252 case Inspector::Protocol::Automation::VirtualKey::NumberPadMultiply:
253 return GDK_KEY_KP_Multiply;
254 case Inspector::Protocol::Automation::VirtualKey::NumberPadAdd:
255 return GDK_KEY_KP_Add;
256 case Inspector::Protocol::Automation::VirtualKey::NumberPadSubtract:
257 return GDK_KEY_KP_Subtract;
258 case Inspector::Protocol::Automation::VirtualKey::NumberPadSeparator:
259 return GDK_KEY_KP_Separator;
260 case Inspector::Protocol::Automation::VirtualKey::NumberPadDecimal:
261 return GDK_KEY_KP_Decimal;
262 case Inspector::Protocol::Automation::VirtualKey::NumberPadDivide:
263 return GDK_KEY_KP_Divide;
264 case Inspector::Protocol::Automation::VirtualKey::Function1:
265 return GDK_KEY_F1;
266 case Inspector::Protocol::Automation::VirtualKey::Function2:
267 return GDK_KEY_F2;
268 case Inspector::Protocol::Automation::VirtualKey::Function3:
269 return GDK_KEY_F3;
270 case Inspector::Protocol::Automation::VirtualKey::Function4:
271 return GDK_KEY_F4;
272 case Inspector::Protocol::Automation::VirtualKey::Function5:
273 return GDK_KEY_F5;
274 case Inspector::Protocol::Automation::VirtualKey::Function6:
275 return GDK_KEY_F6;
276 case Inspector::Protocol::Automation::VirtualKey::Function7:
277 return GDK_KEY_F7;
278 case Inspector::Protocol::Automation::VirtualKey::Function8:
279 return GDK_KEY_F8;
280 case Inspector::Protocol::Automation::VirtualKey::Function9:
281 return GDK_KEY_F9;
282 case Inspector::Protocol::Automation::VirtualKey::Function10:
283 return GDK_KEY_F10;
284 case Inspector::Protocol::Automation::VirtualKey::Function11:
285 return GDK_KEY_F11;
286 case Inspector::Protocol::Automation::VirtualKey::Function12:
287 return GDK_KEY_F12;
288 }
289
290 ASSERT_NOT_REACHED();
291 return 0;
292}
293
294static unsigned modifiersForKeyCode(unsigned keyCode)
295{
296 switch (keyCode) {
297 case GDK_KEY_Shift_R:
298 return GDK_SHIFT_MASK;
299 case GDK_KEY_Control_R:
300 return GDK_CONTROL_MASK;
301 case GDK_KEY_Alt_L:
302 return GDK_MOD1_MASK;
303 case GDK_KEY_Meta_R:
304 return GDK_META_MASK;
305 }
306 return 0;
307}
308
309void WebAutomationSession::platformSimulateKeyboardInteraction(WebPageProxy& page, KeyboardInteraction interaction, WTF::Variant<VirtualKey, CharKey>&& key)
310{
311 unsigned keyCode;
312 WTF::switchOn(key,
313 [&] (VirtualKey virtualKey) {
314 keyCode = keyCodeForVirtualKey(virtualKey);
315 },
316 [&] (CharKey charKey) {
317 keyCode = gdk_unicode_to_keyval(g_utf8_get_char(&charKey));
318 }
319 );
320 unsigned modifiers = modifiersForKeyCode(keyCode);
321
322 switch (interaction) {
323 case KeyboardInteraction::KeyPress:
324 m_currentModifiers |= modifiers;
325 doKeyStrokeEvent(GDK_KEY_PRESS, page.viewWidget(), keyCode, m_currentModifiers);
326 break;
327 case KeyboardInteraction::KeyRelease:
328 m_currentModifiers &= ~modifiers;
329 doKeyStrokeEvent(GDK_KEY_RELEASE, page.viewWidget(), keyCode, m_currentModifiers);
330 break;
331 case KeyboardInteraction::InsertByKey:
332 doKeyStrokeEvent(GDK_KEY_PRESS, page.viewWidget(), keyCode, m_currentModifiers, true);
333 break;
334 }
335}
336
337void WebAutomationSession::platformSimulateKeySequence(WebPageProxy& page, const String& keySequence)
338{
339 CString keySequenceUTF8 = keySequence.utf8();
340 const char* p = keySequenceUTF8.data();
341 do {
342 doKeyStrokeEvent(GDK_KEY_PRESS, page.viewWidget(), gdk_unicode_to_keyval(g_utf8_get_char(p)), m_currentModifiers, true);
343 p = g_utf8_next_char(p);
344 } while (*p);
345}
346
347} // namespace WebKit
348