[GTK] [WK2] Handle wheel event with delta zero
[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 <wtf/OwnArrayPtr.h>
39 #include <wtf/PassOwnArrayPtr.h>
40 #include <gdk/gdkkeysyms.h>
41 #include <gtk/gtk.h>
42 #include <wtf/gobject/GOwnPtr.h>
43 #include <wtf/text/WTFString.h>
44
45 namespace WTR {
46
47 // WebCore and layout tests assume this value
48 static const float pixelsPerScrollTick = 40;
49
50 // Key event location code defined in DOM Level 3.
51 enum KeyLocationCode {
52     DOMKeyLocationStandard      = 0x00,
53     DOMKeyLocationLeft          = 0x01,
54     DOMKeyLocationRight         = 0x02,
55     DOMKeyLocationNumpad        = 0x03
56 };
57
58
59 struct WTREventQueueItem {
60     GdkEvent* event;
61     gulong delay;
62
63     WTREventQueueItem()
64         : event(0)
65         , delay(0)
66     {
67     }
68     WTREventQueueItem(GdkEvent* event, gulong delay)
69         : event(event)
70         , delay(delay)
71     {
72     }
73 };
74
75 EventSenderProxy::EventSenderProxy(TestController* testController)
76     : m_testController(testController)
77     , m_time(0)
78     , m_leftMouseButtonDown(false)
79     , m_clickCount(0)
80     , m_clickTime(0)
81     , m_clickButton(kWKEventMouseButtonNoButton)
82     , m_mouseButtonCurrentlyDown(0)
83 {
84 }
85
86 EventSenderProxy::~EventSenderProxy()
87 {
88 }
89
90 static guint getMouseButtonModifiers(int gdkButton)
91 {
92     if (gdkButton == 1)
93         return GDK_BUTTON1_MASK;
94     if (gdkButton == 2)
95         return GDK_BUTTON2_MASK;
96     if (gdkButton == 3)
97         return GDK_BUTTON3_MASK;
98     return 0;
99 }
100
101 static unsigned eventSenderButtonToGDKButton(unsigned button)
102 {
103     int mouseButton = 3;
104     if (button <= 2)
105         mouseButton = button + 1;
106     // fast/events/mouse-click-events expects the 4th button to be treated as the middle button.
107     else if (button == 3)
108         mouseButton = 2;
109
110     return mouseButton;
111 }
112
113 GdkEvent* EventSenderProxy::createMouseButtonEvent(GdkEventType eventType, unsigned button, WKEventModifiers modifiers)
114 {
115     GdkEvent* mouseEvent = gdk_event_new(eventType);
116
117     mouseEvent->button.button = eventSenderButtonToGDKButton(button);
118     mouseEvent->button.x = m_position.x;
119     mouseEvent->button.y = m_position.y;
120     mouseEvent->button.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
121     g_object_ref(mouseEvent->button.window);
122     gdk_event_set_device(mouseEvent, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(mouseEvent->button.window))));
123     mouseEvent->button.state = modifiers | getMouseButtonModifiers(mouseEvent->button.button);
124     mouseEvent->button.time = GDK_CURRENT_TIME;
125     mouseEvent->button.axes = 0;
126
127     int xRoot, yRoot;
128     gdk_window_get_root_coords(mouseEvent->button.window, m_position.x, m_position.y, &xRoot, &yRoot);
129     mouseEvent->button.x_root = xRoot;
130     mouseEvent->button.y_root = yRoot;
131
132     return mouseEvent;
133 }
134
135 void EventSenderProxy::updateClickCountForButton(int button)
136 {
137     if (m_time - m_clickTime < 1 && m_position == m_clickPosition && button == m_clickButton) {
138         ++m_clickCount;
139         m_clickTime = m_time;
140         return;
141     }
142
143     m_clickCount = 1;
144     m_clickTime = m_time;
145     m_clickPosition = m_position;
146     m_clickButton = button;
147 }
148
149 static void dispatchEvent(GdkEvent* event)
150 {
151     gtk_main_do_event(event);
152     gdk_event_free(event);
153 }
154
155 void EventSenderProxy::replaySavedEvents()
156 {
157     while (!m_eventQueue.isEmpty()) {
158         WTREventQueueItem item = m_eventQueue.takeFirst();
159         if (item.delay)
160             g_usleep(item.delay * 1000);
161
162         dispatchEvent(item.event);
163     }
164 }
165
166 void EventSenderProxy::sendOrQueueEvent(GdkEvent* event)
167 {
168     if (m_eventQueue.isEmpty() || !m_eventQueue.last().delay) {
169         dispatchEvent(event);
170         return;
171     }
172
173     m_eventQueue.last().event = event;
174     replaySavedEvents();
175 }
176
177 static guint webkitModifiersToGDKModifiers(WKEventModifiers wkModifiers)
178 {
179     guint modifiers = 0;
180
181     if (wkModifiers & kWKEventModifiersControlKey)
182         modifiers |= GDK_CONTROL_MASK;
183     if (wkModifiers & kWKEventModifiersShiftKey)
184         modifiers |= GDK_SHIFT_MASK;
185     if (wkModifiers & kWKEventModifiersAltKey)
186         modifiers |= GDK_MOD1_MASK;
187     if (wkModifiers & kWKEventModifiersMetaKey)
188         modifiers |= GDK_META_MASK;
189
190     return modifiers;
191 }
192
193 int getGDKKeySymForKeyRef(WKStringRef keyRef, unsigned location, guint* modifiers)
194 {
195     if (location == DOMKeyLocationNumpad) {
196         if (WKStringIsEqualToUTF8CString(keyRef, "leftArrow"))
197             return GDK_KEY_KP_Left;
198         if (WKStringIsEqualToUTF8CString(keyRef, "rightArror"))
199             return GDK_KEY_KP_Right;
200         if (WKStringIsEqualToUTF8CString(keyRef, "upArrow"))
201             return GDK_KEY_KP_Up;
202         if (WKStringIsEqualToUTF8CString(keyRef, "downArrow"))
203             return GDK_KEY_KP_Down;
204         if (WKStringIsEqualToUTF8CString(keyRef, "pageUp"))
205             return GDK_KEY_KP_Page_Up;
206         if (WKStringIsEqualToUTF8CString(keyRef, "pageDown"))
207             return GDK_KEY_KP_Page_Down;
208         if (WKStringIsEqualToUTF8CString(keyRef, "home"))
209             return GDK_KEY_KP_Home;
210         if (WKStringIsEqualToUTF8CString(keyRef, "end"))
211             return GDK_KEY_KP_End;
212         if (WKStringIsEqualToUTF8CString(keyRef, "insert"))
213             return GDK_KEY_KP_Insert;
214         if (WKStringIsEqualToUTF8CString(keyRef, "delete"))
215             return GDK_KEY_KP_Delete;
216
217         return GDK_KEY_VoidSymbol;
218     }
219
220     if (WKStringIsEqualToUTF8CString(keyRef, "leftArrow"))
221         return GDK_KEY_Left;
222     if (WKStringIsEqualToUTF8CString(keyRef, "rightArrow"))
223         return GDK_KEY_Right;
224     if (WKStringIsEqualToUTF8CString(keyRef, "upArrow"))
225         return GDK_KEY_Up;
226     if (WKStringIsEqualToUTF8CString(keyRef, "downArrow"))
227         return GDK_KEY_Down;
228     if (WKStringIsEqualToUTF8CString(keyRef, "pageUp"))
229         return GDK_KEY_Page_Up;
230     if (WKStringIsEqualToUTF8CString(keyRef, "pageDown"))
231         return GDK_KEY_Page_Down;
232     if (WKStringIsEqualToUTF8CString(keyRef, "home"))
233         return GDK_KEY_Home;
234     if (WKStringIsEqualToUTF8CString(keyRef, "end"))
235         return GDK_KEY_End;
236     if (WKStringIsEqualToUTF8CString(keyRef, "insert"))
237         return GDK_KEY_Insert;
238     if (WKStringIsEqualToUTF8CString(keyRef, "delete"))
239         return GDK_KEY_Delete;
240     if (WKStringIsEqualToUTF8CString(keyRef, "printScreen"))
241         return GDK_KEY_Print;
242     if (WKStringIsEqualToUTF8CString(keyRef, "menu"))
243         return GDK_KEY_Menu;
244     if (WKStringIsEqualToUTF8CString(keyRef, "F1"))
245         return GDK_KEY_F1;
246     if (WKStringIsEqualToUTF8CString(keyRef, "F2"))
247         return GDK_KEY_F2;
248     if (WKStringIsEqualToUTF8CString(keyRef, "F3"))
249         return GDK_KEY_F3;
250     if (WKStringIsEqualToUTF8CString(keyRef, "F4"))
251         return GDK_KEY_F4;
252     if (WKStringIsEqualToUTF8CString(keyRef, "F5"))
253         return GDK_KEY_F5;
254     if (WKStringIsEqualToUTF8CString(keyRef, "F6"))
255         return GDK_KEY_F6;
256     if (WKStringIsEqualToUTF8CString(keyRef, "F7"))
257         return GDK_KEY_F7;
258     if (WKStringIsEqualToUTF8CString(keyRef, "F8"))
259         return GDK_KEY_F8;
260     if (WKStringIsEqualToUTF8CString(keyRef, "F9"))
261         return GDK_KEY_F9;
262     if (WKStringIsEqualToUTF8CString(keyRef, "F10"))
263         return GDK_KEY_F10;
264     if (WKStringIsEqualToUTF8CString(keyRef, "F11"))
265         return GDK_KEY_F11;
266     if (WKStringIsEqualToUTF8CString(keyRef, "F12"))
267         return GDK_KEY_F12;
268
269     size_t bufferSize = WKStringGetMaximumUTF8CStringSize(keyRef);
270     OwnArrayPtr<char> buffer = adoptArrayPtr(new char[bufferSize]);
271     WKStringGetUTF8CString(keyRef, buffer.get(), bufferSize);
272     char charCode = buffer.get()[0];
273
274     if (charCode == '\n' || charCode == '\r')
275         return GDK_KEY_Return;
276     if (charCode == '\t')
277         return GDK_KEY_Tab;
278     if (charCode == '\x8')
279         return GDK_KEY_BackSpace;
280
281     if (WTF::isASCIIUpper(charCode))
282         *modifiers |= GDK_SHIFT_MASK;
283
284     return gdk_unicode_to_keyval(static_cast<guint32>(buffer.get()[0]));
285 }
286
287 void EventSenderProxy::keyDown(WKStringRef keyRef, WKEventModifiers wkModifiers, unsigned location)
288 {
289     guint modifiers = webkitModifiersToGDKModifiers(wkModifiers);
290     int gdkKeySym = getGDKKeySymForKeyRef(keyRef, location, &modifiers);
291
292     GdkEvent* pressEvent = gdk_event_new(GDK_KEY_PRESS);
293     pressEvent->key.keyval = gdkKeySym;
294     pressEvent->key.state = modifiers;
295     pressEvent->key.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformWindow()));
296     g_object_ref(pressEvent->key.window);
297     gdk_event_set_device(pressEvent, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(pressEvent->key.window))));
298
299     GOwnPtr<GdkKeymapKey> keys;
300     gint nKeys;
301     if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeySym, &keys.outPtr(), &nKeys))
302         pressEvent->key.hardware_keycode = keys.get()[0].keycode;
303
304     GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
305     dispatchEvent(pressEvent);
306     releaseEvent->key.type = GDK_KEY_RELEASE;
307     dispatchEvent(releaseEvent);
308 }
309
310 void EventSenderProxy::mouseDown(unsigned button, WKEventModifiers wkModifiers)
311 {
312     // If the same mouse button is already in the down position don't
313     // send another event as it may confuse Xvfb.
314     unsigned gdkButton = eventSenderButtonToGDKButton(button);
315     if (m_mouseButtonCurrentlyDown == gdkButton)
316         return;
317
318     m_mouseButtonCurrentlyDown = gdkButton;
319
320     // Normally GDK will send both GDK_BUTTON_PRESS and GDK_2BUTTON_PRESS for
321     // the second button press during double-clicks. WebKit GTK+ selectively
322     // ignores the first GDK_BUTTON_PRESS of that pair using gdk_event_peek.
323     // Since our events aren't ever going onto the GDK event queue, WebKit won't
324     // be able to filter out the first GDK_BUTTON_PRESS, so we just don't send
325     // it here. Eventually this code should probably figure out a way to get all
326     // appropriate events onto the event queue and this work-around should be
327     // removed.
328     updateClickCountForButton(button);
329
330     GdkEventType eventType;
331     if (m_clickCount == 2)
332         eventType = GDK_2BUTTON_PRESS;
333     else if (m_clickCount == 3)
334         eventType = GDK_3BUTTON_PRESS;
335     else
336         eventType = GDK_BUTTON_PRESS;
337
338     GdkEvent* event = createMouseButtonEvent(eventType, button, wkModifiers);
339     sendOrQueueEvent(event);
340 }
341
342 void EventSenderProxy::mouseUp(unsigned button, WKEventModifiers wkModifiers)
343 {
344     m_clickButton = kWKEventMouseButtonNoButton;
345     GdkEvent* event = createMouseButtonEvent(GDK_BUTTON_RELEASE, button, wkModifiers);
346     sendOrQueueEvent(event);
347
348     if (m_mouseButtonCurrentlyDown == event->button.button)
349         m_mouseButtonCurrentlyDown = 0;
350     m_clickPosition = m_position;
351     m_clickTime = GDK_CURRENT_TIME;
352 }
353
354 void EventSenderProxy::mouseMoveTo(double x, double y)
355 {
356     m_position.x = x;
357     m_position.y = y;
358
359     GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY);
360     event->motion.x = m_position.x;
361     event->motion.y = m_position.y;
362
363     event->motion.time = GDK_CURRENT_TIME;
364     event->motion.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
365     g_object_ref(event->motion.window);
366     gdk_event_set_device(event, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(event->motion.window))));
367     event->motion.state = 0 | getMouseButtonModifiers(m_mouseButtonCurrentlyDown);
368     event->motion.axes = 0;
369
370     int xRoot, yRoot;
371     gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView())), m_position.x, m_position.y , &xRoot, &yRoot);
372     event->motion.x_root = xRoot;
373     event->motion.y_root = yRoot;
374
375     sendOrQueueEvent(event);
376 }
377
378 void EventSenderProxy::mouseScrollBy(int horizontal, int vertical)
379 {
380     // Copy behaviour of Qt and EFL - just return in case of (0,0) mouse scroll
381     if (!horizontal && !vertical)
382         return;
383
384     GdkEvent* event = gdk_event_new(GDK_SCROLL);
385     event->scroll.x = m_position.x;
386     event->scroll.y = m_position.y;
387     event->scroll.time = GDK_CURRENT_TIME;
388     event->scroll.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
389     g_object_ref(event->scroll.window);
390     gdk_event_set_device(event, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(event->scroll.window))));
391
392     // For more than one tick in a scroll, we need smooth scroll event
393     if ((horizontal && vertical) || horizontal > 1 || horizontal < -1 || vertical > 1 || vertical < -1) {
394         event->scroll.direction = GDK_SCROLL_SMOOTH;
395         event->scroll.delta_x = -horizontal;
396         event->scroll.delta_y = -vertical;
397
398         sendOrQueueEvent(event);
399         return;
400     }
401
402     if (horizontal < 0)
403         event->scroll.direction = GDK_SCROLL_RIGHT;
404     else if (horizontal > 0)
405         event->scroll.direction = GDK_SCROLL_LEFT;
406     else if (vertical < 0)
407         event->scroll.direction = GDK_SCROLL_DOWN;
408     else if (vertical > 0)
409         event->scroll.direction = GDK_SCROLL_UP;
410     else
411         g_assert_not_reached();
412
413     sendOrQueueEvent(event);
414 }
415
416 void EventSenderProxy::continuousMouseScrollBy(int horizontal, int vertical, bool paged)
417 {
418     // Gtk+ does not support paged scroll events.
419     g_return_if_fail(!paged);
420
421     GdkEvent* event = gdk_event_new(GDK_SCROLL);
422     event->scroll.x = m_position.x;
423     event->scroll.y = m_position.y;
424     event->scroll.time = GDK_CURRENT_TIME;
425     event->scroll.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
426     g_object_ref(event->scroll.window);
427     gdk_event_set_device(event, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(event->scroll.window))));
428
429     event->scroll.direction = GDK_SCROLL_SMOOTH;
430     event->scroll.delta_x = -horizontal / pixelsPerScrollTick;
431     event->scroll.delta_y = -vertical / pixelsPerScrollTick;
432
433     sendOrQueueEvent(event);
434 }
435
436 void EventSenderProxy::leapForward(int milliseconds)
437 {
438     if (m_eventQueue.isEmpty())
439         m_eventQueue.append(WTREventQueueItem());
440
441     m_eventQueue.last().delay = milliseconds;
442     m_time += milliseconds / 1000.0;
443 }
444
445 } // namespace WTR