[GTK][Wayland] Crash when gdk_keymap_get_entries_for_keyval returns TRUE but n_keys=0
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKit / gtk / InputMethodFilter.cpp
1 /*
2  * Copyright (C) 2012 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
28 #include "WTFStringUtilities.h"
29 #include <WebKit/InputMethodFilter.h>
30 #include <gdk/gdkkeysyms.h>
31 #include <gtk/gtk.h>
32 #include <wtf/glib/GRefPtr.h>
33 #include <wtf/glib/GUniquePtr.h>
34 #include <wtf/text/CString.h>
35
36 using namespace WebKit;
37
38 namespace TestWebKitAPI {
39
40 class TestInputMethodFilter : public InputMethodFilter {
41 public:
42     TestInputMethodFilter()
43         : m_testWindow(gtk_window_new(GTK_WINDOW_POPUP))
44     {
45         setTestingMode(true);
46
47         gtk_widget_show(m_testWindow);
48         gtk_im_context_set_client_window(context(), gtk_widget_get_window(m_testWindow));
49
50         setEnabled(true);
51     }
52
53     ~TestInputMethodFilter()
54     {
55         gtk_widget_destroy(m_testWindow);
56     }
57
58     void sendKeyEventToFilter(unsigned gdkKeyValue, GdkEventType type, unsigned modifiers = 0)
59     {
60         GdkEvent* event = gdk_event_new(type);
61         event->key.keyval = gdkKeyValue;
62         event->key.state = modifiers;
63         event->key.window = gtk_widget_get_window(m_testWindow);
64         event->key.time = GDK_CURRENT_TIME;
65         g_object_ref(event->key.window);
66         gdk_event_set_device(event, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_display_get_default())));
67
68         GUniqueOutPtr<GdkKeymapKey> keys;
69         gint nKeys;
70         if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeyValue, &keys.outPtr(), &nKeys) && nKeys)
71             event->key.hardware_keycode = keys.get()[0].keycode;
72
73         filterKeyEvent(&event->key);
74         gdk_event_free(event);
75     }
76
77     void sendPressAndReleaseKeyEventPairToFilter(unsigned gdkKeyValue, unsigned modifiers = 0)
78     {
79         sendKeyEventToFilter(gdkKeyValue, GDK_KEY_PRESS, modifiers);
80         sendKeyEventToFilter(gdkKeyValue, GDK_KEY_RELEASE, modifiers);
81     }
82
83 private:
84     GtkWidget* m_testWindow;
85 };
86
87 TEST(WebKit2, InputMethodFilterSimple)
88 {
89     TestInputMethodFilter inputMethodFilter;
90     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_g);
91     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_t);
92     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_k);
93
94     const Vector<String>& events = inputMethodFilter.events();
95
96     ASSERT_EQ(6, events.size());
97     ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=67 text='g'"), events[0]);
98     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=67"), events[1]);
99     ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=74 text='t'"), events[2]);
100     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=74"), events[3]);
101     ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=6b text='k'"), events[4]);
102     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=6b"), events[5]);
103 }
104
105 TEST(WebKit2, InputMethodFilterUnicodeSequence)
106 {
107     TestInputMethodFilter inputMethodFilter;
108
109     // This is simple unicode hex entry of the characters, u, 0, 0, f, 4 pressed with
110     // the shift and controls keys held down. In reality, these values are not typical
111     // of an actual hex entry, because they'd be transformed by the shift modifier according
112     // to the keyboard layout. For instance, on a US keyboard a 0 with the shift key pressed
113     // is a right parenthesis. Using these values prevents having to work out what the
114     // transformed characters are based on the current keyboard layout.
115     inputMethodFilter.sendKeyEventToFilter(GDK_KEY_Control_L, GDK_KEY_PRESS);
116     inputMethodFilter.sendKeyEventToFilter(GDK_KEY_Shift_L, GDK_KEY_PRESS, GDK_CONTROL_MASK);
117
118     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_U, GDK_SHIFT_MASK | GDK_CONTROL_MASK);
119     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_0, GDK_SHIFT_MASK | GDK_CONTROL_MASK);
120     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_0, GDK_SHIFT_MASK | GDK_CONTROL_MASK);
121     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_F, GDK_SHIFT_MASK | GDK_CONTROL_MASK);
122     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_4, GDK_SHIFT_MASK | GDK_CONTROL_MASK);
123
124     inputMethodFilter.sendKeyEventToFilter(GDK_KEY_Shift_L, GDK_KEY_RELEASE, GDK_CONTROL_MASK | GDK_SHIFT_MASK);
125     inputMethodFilter.sendKeyEventToFilter(GDK_KEY_Control_L, GDK_KEY_RELEASE, GDK_CONTROL_MASK);
126
127     const Vector<String>& events = inputMethodFilter.events();
128     ASSERT_EQ(21, events.size());
129     ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=ffe3"), events[0]);
130     ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=ffe1"), events[1]);
131     ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=85"), events[2]);
132     ASSERT_EQ(String("setPreedit text='u' cursorOffset=1"), events[3]);
133     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=55"), events[4]);
134     ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=48"), events[5]);
135     ASSERT_EQ(String("setPreedit text='u0' cursorOffset=2"), events[6]);
136     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=30"), events[7]);
137     ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=48"), events[8]);
138     ASSERT_EQ(String("setPreedit text='u00' cursorOffset=3"), events[9]);
139     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=30"), events[10]);
140     ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=70"), events[11]);
141     ASSERT_EQ(String("setPreedit text='u00F' cursorOffset=4"), events[12]);
142     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=46"), events[13]);
143     ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=52"), events[14]);
144     ASSERT_EQ(String("setPreedit text='u00F4' cursorOffset=5"), events[15]);
145     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=34"), events[16]);
146     ASSERT_EQ(String("confirmComposition 'ô'"), events[17]);
147     ASSERT_EQ(String("setPreedit text='' cursorOffset=0"), events[18]);
148     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=ffe1"), events[19]);
149     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=ffe3"), events[20]);
150 }
151
152 TEST(WebKit2, InputMethodFilterComposeKey)
153 {
154     TestInputMethodFilter inputMethodFilter;
155
156     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_Multi_key);
157     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_apostrophe);
158     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_o);
159
160     const Vector<String>& events = inputMethodFilter.events();
161     ASSERT_EQ(5, events.size());
162     ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=39"), events[0]);
163     ASSERT_EQ(String("setPreedit text='' cursorOffset=0"), events[1]);
164     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=27"), events[2]);
165     ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=6f text='ó'"), events[3]);
166     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=6f"), events[4]);
167 }
168
169 typedef void (*GetPreeditStringCallback) (GtkIMContext*, gchar**, PangoAttrList**, int*);
170 static void temporaryGetPreeditStringOverride(GtkIMContext*, char** string, PangoAttrList** attrs, int* cursorPosition)
171 {
172     *string = g_strdup("preedit of doom, bringer of cheese");
173     *cursorPosition = 3;
174 }
175
176 TEST(WebKit2, InputMethodFilterContextEventsWithoutKeyEvents)
177 {
178     TestInputMethodFilter inputMethodFilter;
179
180     // This is a bit of a hack to avoid mocking out the entire InputMethodContext, by
181     // simply replacing the get_preedit_string virtual method for the length of this test.
182     GtkIMContext* context = inputMethodFilter.context();
183     GtkIMContextClass* contextClass = GTK_IM_CONTEXT_GET_CLASS(context);
184     GetPreeditStringCallback previousCallback = contextClass->get_preedit_string;
185     contextClass->get_preedit_string = temporaryGetPreeditStringOverride;
186
187     g_signal_emit_by_name(context, "preedit-changed");
188     g_signal_emit_by_name(context, "commit", "commit text");
189
190     contextClass->get_preedit_string = previousCallback;
191
192     const Vector<String>& events = inputMethodFilter.events();
193     ASSERT_EQ(6, events.size());
194     ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=16777215 (faked)"), events[0]);
195     ASSERT_EQ(String("setPreedit text='preedit of doom, bringer of cheese' cursorOffset=3"), events[1]);
196     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=ffffff (faked)"), events[2]);
197     ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=16777215 (faked)"), events[3]);
198     ASSERT_EQ(String("confirmComposition 'commit text'"), events[4]);
199     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=ffffff (faked)"), events[5]);
200 }
201
202 static bool gSawContextReset = false;
203 typedef void (*ResetCallback) (GtkIMContext*);
204 static void temporaryResetOverride(GtkIMContext*)
205 {
206     gSawContextReset = true;
207 }
208
209 static void verifyCanceledComposition(const Vector<String>& events)
210 {
211     ASSERT_EQ(3, events.size());
212     ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=39"), events[0]);
213     ASSERT_EQ(String("setPreedit text='' cursorOffset=0"), events[1]);
214     ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=27"), events[2]);
215     ASSERT(gSawContextReset);
216 }
217
218 TEST(WebKit2, InputMethodFilterContextFocusOutDuringOngoingComposition)
219 {
220     TestInputMethodFilter inputMethodFilter;
221
222     // See comment above about this technique.
223     GtkIMContext* context = inputMethodFilter.context();
224     GtkIMContextClass* contextClass = GTK_IM_CONTEXT_GET_CLASS(context);
225     ResetCallback previousCallback = contextClass->reset;
226     contextClass->reset = temporaryResetOverride;
227
228     gSawContextReset = false;
229     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_Multi_key);
230     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_apostrophe);
231     inputMethodFilter.notifyFocusedOut();
232
233     verifyCanceledComposition(inputMethodFilter.events());
234
235     contextClass->reset = previousCallback;
236 }
237
238 TEST(WebKit2, InputMethodFilterContextMouseClickDuringOngoingComposition)
239 {
240     TestInputMethodFilter inputMethodFilter;
241
242     // See comment above about this technique.
243     GtkIMContext* context = inputMethodFilter.context();
244     GtkIMContextClass* contextClass = GTK_IM_CONTEXT_GET_CLASS(context);
245     ResetCallback previousCallback = contextClass->reset;
246     contextClass->reset = temporaryResetOverride;
247
248     gSawContextReset = false;
249     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_Multi_key);
250     inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_apostrophe);
251     inputMethodFilter.notifyMouseButtonPress();
252
253     verifyCanceledComposition(inputMethodFilter.events());
254
255     contextClass->reset = previousCallback;
256 }
257
258 } // namespace TestWebKitAPI