[GTK] Ensure generated events have given modifiers GDK understands
[WebKit-https.git] / Tools / WebKitTestRunner / gtk / EventSenderProxyGtk.cpp
1 /*
2  * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3  * Copyright (C) 2009 Zan Dobersek <zandobersek@gmail.com>
4  * Copyright (C) 2009 Holger Hans Peter Freyther
5  * Copyright (C) 2010 Igalia S.L.
6  * Copyright (c) 2010 Motorola Mobility, Inc.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1.  Redistributions of source code must retain the above copyright
13  *     notice, this list of conditions and the following disclaimer.
14  * 2.  Redistributions in binary form must reproduce the above copyright
15  *     notice, this list of conditions and the following disclaimer in the
16  *     documentation and/or other materials provided with the distribution.
17  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
18  *     its contributors may be used to endorse or promote products derived
19  *     from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
22  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
25  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
28  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "config.h"
34 #include "EventSenderProxy.h"
35
36 #include "PlatformWebView.h"
37 #include "TestController.h"
38 #include <gdk/gdkkeysyms.h>
39 #include <gtk/gtk.h>
40 #include <wtf/StdLibExtras.h>
41 #include <wtf/gobject/GOwnPtr.h>
42 #include <wtf/text/WTFString.h>
43
44 namespace WTR {
45
46 // WebCore and layout tests assume this value
47 static const float pixelsPerScrollTick = 40;
48
49 // Key event location code defined in DOM Level 3.
50 enum KeyLocationCode {
51     DOMKeyLocationStandard      = 0x00,
52     DOMKeyLocationLeft          = 0x01,
53     DOMKeyLocationRight         = 0x02,
54     DOMKeyLocationNumpad        = 0x03
55 };
56
57
58 struct WTREventQueueItem {
59     GdkEvent* event;
60     gulong delay;
61
62     WTREventQueueItem()
63         : event(0)
64         , delay(0)
65     {
66     }
67     WTREventQueueItem(GdkEvent* event, gulong delay)
68         : event(event)
69         , delay(delay)
70     {
71     }
72 };
73
74 EventSenderProxy::EventSenderProxy(TestController* testController)
75     : m_testController(testController)
76     , m_time(0)
77     , m_leftMouseButtonDown(false)
78     , m_clickCount(0)
79     , m_clickTime(0)
80     , m_clickButton(kWKEventMouseButtonNoButton)
81     , m_mouseButtonCurrentlyDown(0)
82 {
83 }
84
85 EventSenderProxy::~EventSenderProxy()
86 {
87 }
88
89 static guint getMouseButtonModifiers(int gdkButton)
90 {
91     if (gdkButton == 1)
92         return GDK_BUTTON1_MASK;
93     if (gdkButton == 2)
94         return GDK_BUTTON2_MASK;
95     if (gdkButton == 3)
96         return GDK_BUTTON3_MASK;
97     return 0;
98 }
99
100 static unsigned eventSenderButtonToGDKButton(unsigned button)
101 {
102     int mouseButton = 3;
103     if (button <= 2)
104         mouseButton = button + 1;
105     // fast/events/mouse-click-events expects the 4th button to be treated as the middle button.
106     else if (button == 3)
107         mouseButton = 2;
108
109     return mouseButton;
110 }
111
112 static guint webkitModifiersToGDKModifiers(WKEventModifiers wkModifiers)
113 {
114     guint modifiers = 0;
115
116     if (wkModifiers & kWKEventModifiersControlKey)
117         modifiers |= GDK_CONTROL_MASK;
118     if (wkModifiers & kWKEventModifiersShiftKey)
119         modifiers |= GDK_SHIFT_MASK;
120     if (wkModifiers & kWKEventModifiersAltKey)
121         modifiers |= GDK_MOD1_MASK;
122     if (wkModifiers & kWKEventModifiersMetaKey)
123         modifiers |= GDK_META_MASK;
124
125     return modifiers;
126 }
127
128 GdkEvent* EventSenderProxy::createMouseButtonEvent(GdkEventType eventType, unsigned button, WKEventModifiers modifiers)
129 {
130     GdkEvent* mouseEvent = gdk_event_new(eventType);
131
132     mouseEvent->button.button = eventSenderButtonToGDKButton(button);
133     mouseEvent->button.x = m_position.x;
134     mouseEvent->button.y = m_position.y;
135     mouseEvent->button.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
136     g_object_ref(mouseEvent->button.window);
137     gdk_event_set_device(mouseEvent, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(mouseEvent->button.window))));
138     mouseEvent->button.state = webkitModifiersToGDKModifiers(modifiers) | getMouseButtonModifiers(mouseEvent->button.button);
139     mouseEvent->button.time = GDK_CURRENT_TIME;
140     mouseEvent->button.axes = 0;
141
142     int xRoot, yRoot;
143     gdk_window_get_root_coords(mouseEvent->button.window, m_position.x, m_position.y, &xRoot, &yRoot);
144     mouseEvent->button.x_root = xRoot;
145     mouseEvent->button.y_root = yRoot;
146
147     return mouseEvent;
148 }
149
150 void EventSenderProxy::updateClickCountForButton(int button)
151 {
152     if (m_time - m_clickTime < 1 && m_position == m_clickPosition && button == m_clickButton) {
153         ++m_clickCount;
154         m_clickTime = m_time;
155         return;
156     }
157
158     m_clickCount = 1;
159     m_clickTime = m_time;
160     m_clickPosition = m_position;
161     m_clickButton = button;
162 }
163
164 static void dispatchEvent(GdkEvent* event)
165 {
166     gtk_main_do_event(event);
167     gdk_event_free(event);
168 }
169
170 void EventSenderProxy::replaySavedEvents()
171 {
172     while (!m_eventQueue.isEmpty()) {
173         WTREventQueueItem item = m_eventQueue.takeFirst();
174         if (item.delay)
175             g_usleep(item.delay * 1000);
176
177         dispatchEvent(item.event);
178     }
179 }
180
181 void EventSenderProxy::sendOrQueueEvent(GdkEvent* event)
182 {
183     if (m_eventQueue.isEmpty() || !m_eventQueue.last().delay) {
184         dispatchEvent(event);
185         return;
186     }
187
188     m_eventQueue.last().event = event;
189     replaySavedEvents();
190 }
191
192 int getGDKKeySymForKeyRef(WKStringRef keyRef, unsigned location, guint* modifiers)
193 {
194     if (location == DOMKeyLocationNumpad) {
195         if (WKStringIsEqualToUTF8CString(keyRef, "leftArrow"))
196             return GDK_KEY_KP_Left;
197         if (WKStringIsEqualToUTF8CString(keyRef, "rightArror"))
198             return GDK_KEY_KP_Right;
199         if (WKStringIsEqualToUTF8CString(keyRef, "upArrow"))
200             return GDK_KEY_KP_Up;
201         if (WKStringIsEqualToUTF8CString(keyRef, "downArrow"))
202             return GDK_KEY_KP_Down;
203         if (WKStringIsEqualToUTF8CString(keyRef, "pageUp"))
204             return GDK_KEY_KP_Page_Up;
205         if (WKStringIsEqualToUTF8CString(keyRef, "pageDown"))
206             return GDK_KEY_KP_Page_Down;
207         if (WKStringIsEqualToUTF8CString(keyRef, "home"))
208             return GDK_KEY_KP_Home;
209         if (WKStringIsEqualToUTF8CString(keyRef, "end"))
210             return GDK_KEY_KP_End;
211         if (WKStringIsEqualToUTF8CString(keyRef, "insert"))
212             return GDK_KEY_KP_Insert;
213         if (WKStringIsEqualToUTF8CString(keyRef, "delete"))
214             return GDK_KEY_KP_Delete;
215
216         return GDK_KEY_VoidSymbol;
217     }
218
219     if (WKStringIsEqualToUTF8CString(keyRef, "leftArrow"))
220         return GDK_KEY_Left;
221     if (WKStringIsEqualToUTF8CString(keyRef, "rightArrow"))
222         return GDK_KEY_Right;
223     if (WKStringIsEqualToUTF8CString(keyRef, "upArrow"))
224         return GDK_KEY_Up;
225     if (WKStringIsEqualToUTF8CString(keyRef, "downArrow"))
226         return GDK_KEY_Down;
227     if (WKStringIsEqualToUTF8CString(keyRef, "pageUp"))
228         return GDK_KEY_Page_Up;
229     if (WKStringIsEqualToUTF8CString(keyRef, "pageDown"))
230         return GDK_KEY_Page_Down;
231     if (WKStringIsEqualToUTF8CString(keyRef, "home"))
232         return GDK_KEY_Home;
233     if (WKStringIsEqualToUTF8CString(keyRef, "end"))
234         return GDK_KEY_End;
235     if (WKStringIsEqualToUTF8CString(keyRef, "insert"))
236         return GDK_KEY_Insert;
237     if (WKStringIsEqualToUTF8CString(keyRef, "delete"))
238         return GDK_KEY_Delete;
239     if (WKStringIsEqualToUTF8CString(keyRef, "printScreen"))
240         return GDK_KEY_Print;
241     if (WKStringIsEqualToUTF8CString(keyRef, "menu"))
242         return GDK_KEY_Menu;
243     if (WKStringIsEqualToUTF8CString(keyRef, "F1"))
244         return GDK_KEY_F1;
245     if (WKStringIsEqualToUTF8CString(keyRef, "F2"))
246         return GDK_KEY_F2;
247     if (WKStringIsEqualToUTF8CString(keyRef, "F3"))
248         return GDK_KEY_F3;
249     if (WKStringIsEqualToUTF8CString(keyRef, "F4"))
250         return GDK_KEY_F4;
251     if (WKStringIsEqualToUTF8CString(keyRef, "F5"))
252         return GDK_KEY_F5;
253     if (WKStringIsEqualToUTF8CString(keyRef, "F6"))
254         return GDK_KEY_F6;
255     if (WKStringIsEqualToUTF8CString(keyRef, "F7"))
256         return GDK_KEY_F7;
257     if (WKStringIsEqualToUTF8CString(keyRef, "F8"))
258         return GDK_KEY_F8;
259     if (WKStringIsEqualToUTF8CString(keyRef, "F9"))
260         return GDK_KEY_F9;
261     if (WKStringIsEqualToUTF8CString(keyRef, "F10"))
262         return GDK_KEY_F10;
263     if (WKStringIsEqualToUTF8CString(keyRef, "F11"))
264         return GDK_KEY_F11;
265     if (WKStringIsEqualToUTF8CString(keyRef, "F12"))
266         return GDK_KEY_F12;
267
268     size_t bufferSize = WKStringGetMaximumUTF8CStringSize(keyRef);
269     auto buffer = std::make_unique<char[]>(bufferSize);
270     WKStringGetUTF8CString(keyRef, buffer.get(), bufferSize);
271     char charCode = buffer.get()[0];
272
273     if (charCode == '\n' || charCode == '\r')
274         return GDK_KEY_Return;
275     if (charCode == '\t')
276         return GDK_KEY_Tab;
277     if (charCode == '\x8')
278         return GDK_KEY_BackSpace;
279
280     if (WTF::isASCIIUpper(charCode))
281         *modifiers |= GDK_SHIFT_MASK;
282
283     return gdk_unicode_to_keyval(static_cast<guint32>(buffer.get()[0]));
284 }
285
286 void EventSenderProxy::keyDown(WKStringRef keyRef, WKEventModifiers wkModifiers, unsigned location)
287 {
288     guint modifiers = webkitModifiersToGDKModifiers(wkModifiers);
289     int gdkKeySym = getGDKKeySymForKeyRef(keyRef, location, &modifiers);
290
291     GdkEvent* pressEvent = gdk_event_new(GDK_KEY_PRESS);
292     pressEvent->key.keyval = gdkKeySym;
293     pressEvent->key.state = webkitModifiersToGDKModifiers(modifiers);
294     pressEvent->key.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformWindow()));
295     g_object_ref(pressEvent->key.window);
296     gdk_event_set_device(pressEvent, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(pressEvent->key.window))));
297
298     GOwnPtr<GdkKeymapKey> keys;
299     gint nKeys;
300     if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeySym, &keys.outPtr(), &nKeys))
301         pressEvent->key.hardware_keycode = keys.get()[0].keycode;
302
303     GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
304     dispatchEvent(pressEvent);
305     releaseEvent->key.type = GDK_KEY_RELEASE;
306     dispatchEvent(releaseEvent);
307 }
308
309 void EventSenderProxy::mouseDown(unsigned button, WKEventModifiers wkModifiers)
310 {
311     // If the same mouse button is already in the down position don't
312     // send another event as it may confuse Xvfb.
313     unsigned gdkButton = eventSenderButtonToGDKButton(button);
314     if (m_mouseButtonCurrentlyDown == gdkButton)
315         return;
316
317     m_mouseButtonCurrentlyDown = gdkButton;
318
319     // Normally GDK will send both GDK_BUTTON_PRESS and GDK_2BUTTON_PRESS for
320     // the second button press during double-clicks. WebKit GTK+ selectively
321     // ignores the first GDK_BUTTON_PRESS of that pair using gdk_event_peek.
322     // Since our events aren't ever going onto the GDK event queue, WebKit won't
323     // be able to filter out the first GDK_BUTTON_PRESS, so we just don't send
324     // it here. Eventually this code should probably figure out a way to get all
325     // appropriate events onto the event queue and this work-around should be
326     // removed.
327     updateClickCountForButton(button);
328
329     GdkEventType eventType;
330     if (m_clickCount == 2)
331         eventType = GDK_2BUTTON_PRESS;
332     else if (m_clickCount == 3)
333         eventType = GDK_3BUTTON_PRESS;
334     else
335         eventType = GDK_BUTTON_PRESS;
336
337     GdkEvent* event = createMouseButtonEvent(eventType, button, wkModifiers);
338     sendOrQueueEvent(event);
339 }
340
341 void EventSenderProxy::mouseUp(unsigned button, WKEventModifiers wkModifiers)
342 {
343     m_clickButton = kWKEventMouseButtonNoButton;
344     GdkEvent* event = createMouseButtonEvent(GDK_BUTTON_RELEASE, button, wkModifiers);
345     sendOrQueueEvent(event);
346
347     if (m_mouseButtonCurrentlyDown == event->button.button)
348         m_mouseButtonCurrentlyDown = 0;
349     m_clickPosition = m_position;
350     m_clickTime = GDK_CURRENT_TIME;
351 }
352
353 void EventSenderProxy::mouseMoveTo(double x, double y)
354 {
355     m_position.x = x;
356     m_position.y = y;
357
358     GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY);
359     event->motion.x = m_position.x;
360     event->motion.y = m_position.y;
361
362     event->motion.time = GDK_CURRENT_TIME;
363     event->motion.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
364     g_object_ref(event->motion.window);
365     gdk_event_set_device(event, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(event->motion.window))));
366     event->motion.state = 0 | getMouseButtonModifiers(m_mouseButtonCurrentlyDown);
367     event->motion.axes = 0;
368
369     int xRoot, yRoot;
370     gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView())), m_position.x, m_position.y , &xRoot, &yRoot);
371     event->motion.x_root = xRoot;
372     event->motion.y_root = yRoot;
373
374     sendOrQueueEvent(event);
375 }
376
377 void EventSenderProxy::mouseScrollBy(int horizontal, int vertical)
378 {
379     // Copy behaviour of Qt and EFL - just return in case of (0,0) mouse scroll
380     if (!horizontal && !vertical)
381         return;
382
383     GdkEvent* event = gdk_event_new(GDK_SCROLL);
384     event->scroll.x = m_position.x;
385     event->scroll.y = m_position.y;
386     event->scroll.time = GDK_CURRENT_TIME;
387     event->scroll.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
388     g_object_ref(event->scroll.window);
389     gdk_event_set_device(event, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(event->scroll.window))));
390
391     // For more than one tick in a scroll, we need smooth scroll event
392     if ((horizontal && vertical) || horizontal > 1 || horizontal < -1 || vertical > 1 || vertical < -1) {
393         event->scroll.direction = GDK_SCROLL_SMOOTH;
394         event->scroll.delta_x = -horizontal;
395         event->scroll.delta_y = -vertical;
396
397         sendOrQueueEvent(event);
398         return;
399     }
400
401     if (horizontal < 0)
402         event->scroll.direction = GDK_SCROLL_RIGHT;
403     else if (horizontal > 0)
404         event->scroll.direction = GDK_SCROLL_LEFT;
405     else if (vertical < 0)
406         event->scroll.direction = GDK_SCROLL_DOWN;
407     else if (vertical > 0)
408         event->scroll.direction = GDK_SCROLL_UP;
409     else
410         g_assert_not_reached();
411
412     sendOrQueueEvent(event);
413 }
414
415 void EventSenderProxy::continuousMouseScrollBy(int horizontal, int vertical, bool paged)
416 {
417     // Gtk+ does not support paged scroll events.
418     g_return_if_fail(!paged);
419
420     GdkEvent* event = gdk_event_new(GDK_SCROLL);
421     event->scroll.x = m_position.x;
422     event->scroll.y = m_position.y;
423     event->scroll.time = GDK_CURRENT_TIME;
424     event->scroll.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
425     g_object_ref(event->scroll.window);
426     gdk_event_set_device(event, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(event->scroll.window))));
427
428     event->scroll.direction = GDK_SCROLL_SMOOTH;
429     event->scroll.delta_x = -horizontal / pixelsPerScrollTick;
430     event->scroll.delta_y = -vertical / pixelsPerScrollTick;
431
432     sendOrQueueEvent(event);
433 }
434
435 void EventSenderProxy::leapForward(int milliseconds)
436 {
437     if (m_eventQueue.isEmpty())
438         m_eventQueue.append(WTREventQueueItem());
439
440     m_eventQueue.last().delay = milliseconds;
441     m_time += milliseconds / 1000.0;
442 }
443
444 } // namespace WTR