OLD | NEW |
| (Empty) |
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 "GtkInputMethodFilter.h" | |
29 #include "WTFStringUtilities.h" | |
30 #include <gdk/gdkkeysyms.h> | |
31 #include <gtk/gtk.h> | |
32 #include <wtf/gobject/GOwnPtr.h> | |
33 #include <wtf/gobject/GRefPtr.h> | |
34 #include <wtf/text/CString.h> | |
35 | |
36 using namespace WebCore; | |
37 | |
38 namespace TestWebKitAPI { | |
39 | |
40 class TestInputMethodFilter : public GtkInputMethodFilter { | |
41 public: | |
42 TestInputMethodFilter() | |
43 : m_testWindow(gtk_window_new(GTK_WINDOW_POPUP)) | |
44 { | |
45 gtk_widget_show(m_testWindow.get()); | |
46 setWidget(m_testWindow.get()); | |
47 | |
48 // Focus in is necessary to activate the default input method in the mul
ticontext. | |
49 notifyFocusedIn(); | |
50 } | |
51 | |
52 Vector<String>& events() { return m_events; } | |
53 | |
54 void sendKeyEventToFilter(unsigned int gdkKeyValue, GdkEventType type, unsig
ned int modifiers = 0) | |
55 { | |
56 GdkEvent* event = gdk_event_new(type); | |
57 event->key.keyval = gdkKeyValue; | |
58 event->key.state = modifiers; | |
59 event->key.window = gtk_widget_get_window(m_testWindow.get()); | |
60 event->key.time = GDK_CURRENT_TIME; | |
61 g_object_ref(event->key.window); | |
62 | |
63 #ifndef GTK_API_VERSION_2 | |
64 gdk_event_set_device(event, gdk_device_manager_get_client_pointer(gdk_di
splay_get_device_manager(gdk_display_get_default()))); | |
65 #endif | |
66 | |
67 GOwnPtr<GdkKeymapKey> keys; | |
68 gint nKeys; | |
69 if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeyVa
lue, &keys.outPtr(), &nKeys)) | |
70 event->key.hardware_keycode = keys.get()[0].keycode; | |
71 | |
72 filterKeyEvent(&event->key); | |
73 gdk_event_free(event); | |
74 } | |
75 | |
76 void sendPressAndReleaseKeyEventPairToFilter(unsigned int gdkKeyValue, unsig
ned int modifiers = 0) | |
77 { | |
78 sendKeyEventToFilter(gdkKeyValue, GDK_KEY_PRESS, modifiers); | |
79 sendKeyEventToFilter(gdkKeyValue, GDK_KEY_RELEASE, modifiers); | |
80 } | |
81 | |
82 protected: | |
83 virtual bool sendSimpleKeyEvent(GdkEventKey* event, WTF::String eventString,
EventFakedForComposition faked) | |
84 { | |
85 const char* eventType = event->type == GDK_KEY_RELEASE ? "release" : "pr
ess"; | |
86 const char* fakedString = faked == EventFaked ? " (faked)" : ""; | |
87 if (!eventString.isNull()) | |
88 m_events.append(String::format("sendSimpleKeyEvent type=%s keycode=%
x text='%s'%s", eventType, event->keyval, eventString.utf8().data(), fakedString
)); | |
89 else | |
90 m_events.append(String::format("sendSimpleKeyEvent type=%s keycode=%
x%s", eventType, event->keyval, fakedString)); | |
91 | |
92 return true; | |
93 } | |
94 | |
95 virtual bool sendKeyEventWithCompositionResults(GdkEventKey* event, ResultsT
oSend resultsToSend, EventFakedForComposition faked) | |
96 { | |
97 const char* eventType = event->type == GDK_KEY_RELEASE ? "release" : "pr
ess"; | |
98 const char* fakedString = faked == EventFaked ? " (faked)" : ""; | |
99 m_events.append(String::format("sendKeyEventWithCompositionResults type=
%s keycode=%u%s", eventType, event->keyval, fakedString)); | |
100 | |
101 if (resultsToSend & Composition && !m_confirmedComposition.isNull()) | |
102 confirmCompositionText(m_confirmedComposition); | |
103 if (resultsToSend & Preedit && !m_preedit.isNull()) | |
104 setPreedit(m_preedit, m_cursorOffset); | |
105 | |
106 return true; | |
107 } | |
108 | |
109 virtual bool canEdit() | |
110 { | |
111 return true; | |
112 } | |
113 | |
114 virtual void confirmCompositionText(String text) | |
115 { | |
116 m_events.append(String::format("confirmComposition '%s'", text.utf8().da
ta())); | |
117 } | |
118 | |
119 virtual void confirmCurrentComposition() | |
120 { | |
121 m_events.append(String("confirmCurrentcomposition")); | |
122 } | |
123 | |
124 virtual void cancelCurrentComposition() | |
125 { | |
126 m_events.append(String("cancelCurrentComposition")); | |
127 } | |
128 | |
129 virtual void setPreedit(String preedit, int cursorOffset) | |
130 { | |
131 m_events.append(String::format("setPreedit text='%s' cursorOffset=%i", p
reedit.utf8().data(), cursorOffset)); | |
132 } | |
133 | |
134 private: | |
135 GRefPtr<GtkWidget> m_testWindow; | |
136 Vector<String> m_events; | |
137 }; | |
138 | |
139 TEST(GTK, GtkInputMethodFilterSimple) | |
140 { | |
141 TestInputMethodFilter inputMethodFilter; | |
142 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_g); | |
143 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_t); | |
144 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_k); | |
145 | |
146 const Vector<String>& events = inputMethodFilter.events(); | |
147 | |
148 ASSERT_EQ(6, events.size()); | |
149 ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=67 text='g'"), event
s[0]); | |
150 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=67"), events[1]); | |
151 ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=74 text='t'"), event
s[2]); | |
152 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=74"), events[3]); | |
153 ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=6b text='k'"), event
s[4]); | |
154 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=6b"), events[5]); | |
155 } | |
156 | |
157 TEST(GTK, GtkInputMethodFilterUnicodeSequence) | |
158 { | |
159 TestInputMethodFilter inputMethodFilter; | |
160 | |
161 // This is simple unicode hex entry of the characters, u, 0, 0, f, 4 pressed
with | |
162 // the shift and controls keys held down. In reality, these values are not t
ypical | |
163 // of an actual hex entry, because they'd be transformed by the shift modifi
er according | |
164 // to the keyboard layout. For instance, on a US keyboard a 0 with the shift
key pressed | |
165 // is a right parenthesis. Using these values prevents having to work out wh
at the | |
166 // transformed characters are based on the current keyboard layout. | |
167 inputMethodFilter.sendKeyEventToFilter(GDK_KEY_Control_L, GDK_KEY_PRESS); | |
168 inputMethodFilter.sendKeyEventToFilter(GDK_KEY_Shift_L, GDK_KEY_PRESS, GDK_C
ONTROL_MASK); | |
169 | |
170 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_U, GDK_SHI
FT_MASK | GDK_CONTROL_MASK); | |
171 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_0, GDK_SHI
FT_MASK | GDK_CONTROL_MASK); | |
172 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_0, GDK_SHI
FT_MASK | GDK_CONTROL_MASK); | |
173 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_F, GDK_SHI
FT_MASK | GDK_CONTROL_MASK); | |
174 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_4, GDK_SHI
FT_MASK | GDK_CONTROL_MASK); | |
175 | |
176 inputMethodFilter.sendKeyEventToFilter(GDK_KEY_Shift_L, GDK_KEY_RELEASE, GDK
_CONTROL_MASK | GDK_SHIFT_MASK); | |
177 inputMethodFilter.sendKeyEventToFilter(GDK_KEY_Control_L, GDK_KEY_RELEASE, G
DK_CONTROL_MASK); | |
178 | |
179 const Vector<String>& events = inputMethodFilter.events(); | |
180 ASSERT_EQ(21, events.size()); | |
181 ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=ffe3"), events[0]); | |
182 ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=ffe1"), events[1]); | |
183 ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=85")
, events[2]); | |
184 ASSERT_EQ(String("setPreedit text='u' cursorOffset=1"), events[3]); | |
185 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=55"), events[4]); | |
186 ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=48")
, events[5]); | |
187 ASSERT_EQ(String("setPreedit text='u0' cursorOffset=2"), events[6]); | |
188 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=30"), events[7]); | |
189 ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=48")
, events[8]); | |
190 ASSERT_EQ(String("setPreedit text='u00' cursorOffset=3"), events[9]); | |
191 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=30"), events[10]); | |
192 ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=70")
, events[11]); | |
193 ASSERT_EQ(String("setPreedit text='u00F' cursorOffset=4"), events[12]); | |
194 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=46"), events[13]); | |
195 ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=52")
, events[14]); | |
196 ASSERT_EQ(String("setPreedit text='u00F4' cursorOffset=5"), events[15]); | |
197 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=34"), events[16]); | |
198 ASSERT_EQ(String("confirmComposition 'ô'"), events[17]); | |
199 ASSERT_EQ(String("setPreedit text='' cursorOffset=0"), events[18]); | |
200 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=ffe1"), events[19]
); | |
201 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=ffe3"), events[20]
); | |
202 } | |
203 | |
204 TEST(GTK, GtkInputMethodFilterComposeKey) | |
205 { | |
206 TestInputMethodFilter inputMethodFilter; | |
207 | |
208 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_Multi_key)
; | |
209 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_apostrophe
); | |
210 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_o); | |
211 | |
212 const Vector<String>& events = inputMethodFilter.events(); | |
213 ASSERT_EQ(5, events.size()); | |
214 ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=39")
, events[0]); | |
215 ASSERT_EQ(String("setPreedit text='' cursorOffset=0"), events[1]); | |
216 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=27"), events[2]); | |
217 ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=6f text='ó'"), event
s[3]); | |
218 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=6f"), events[4]); | |
219 } | |
220 | |
221 typedef void (*GetPreeditStringCallback) (GtkIMContext*, gchar**, PangoAttrList*
*, int*); | |
222 static void temporaryGetPreeditStringOverride(GtkIMContext*, char** string, Pang
oAttrList** attrs, int* cursorPosition) | |
223 { | |
224 *string = g_strdup("preedit of doom, bringer of cheese"); | |
225 *cursorPosition = 3; | |
226 } | |
227 | |
228 TEST(GTK, GtkInputMethodFilterContextEventsWithoutKeyEvents) | |
229 { | |
230 TestInputMethodFilter inputMethodFilter; | |
231 | |
232 // This is a bit of a hack to avoid mocking out the entire InputMethodContex
t, by | |
233 // simply replacing the get_preedit_string virtual method for the length of
this test. | |
234 GtkIMContext* context = inputMethodFilter.context(); | |
235 GtkIMContextClass* contextClass = GTK_IM_CONTEXT_GET_CLASS(context); | |
236 GetPreeditStringCallback previousCallback = contextClass->get_preedit_string
; | |
237 contextClass->get_preedit_string = temporaryGetPreeditStringOverride; | |
238 | |
239 g_signal_emit_by_name(context, "preedit-changed"); | |
240 g_signal_emit_by_name(context, "commit", "commit text"); | |
241 | |
242 contextClass->get_preedit_string = previousCallback; | |
243 | |
244 const Vector<String>& events = inputMethodFilter.events(); | |
245 ASSERT_EQ(6, events.size()); | |
246 ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=1677
7215 (faked)"), events[0]); | |
247 ASSERT_EQ(String("setPreedit text='preedit of doom, bringer of cheese' curso
rOffset=3"), events[1]); | |
248 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=ffffff (faked)"),
events[2]); | |
249 ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=1677
7215 (faked)"), events[3]); | |
250 ASSERT_EQ(String("confirmComposition 'commit text'"), events[4]); | |
251 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=ffffff (faked)"),
events[5]); | |
252 } | |
253 | |
254 static bool gSawContextReset = false; | |
255 typedef void (*ResetCallback) (GtkIMContext*); | |
256 static void temporaryResetOverride(GtkIMContext*) | |
257 { | |
258 gSawContextReset = true; | |
259 } | |
260 | |
261 static void verifyCanceledComposition(const Vector<String>& events) | |
262 { | |
263 ASSERT_EQ(3, events.size()); | |
264 ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=39")
, events[0]); | |
265 ASSERT_EQ(String("setPreedit text='' cursorOffset=0"), events[1]); | |
266 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=27"), events[2]); | |
267 ASSERT(gSawContextReset); | |
268 } | |
269 | |
270 TEST(GTK, GtkInputMethodFilterContextFocusOutDuringOngoingComposition) | |
271 { | |
272 TestInputMethodFilter inputMethodFilter; | |
273 | |
274 // See comment above about this technique. | |
275 GtkIMContext* context = inputMethodFilter.context(); | |
276 GtkIMContextClass* contextClass = GTK_IM_CONTEXT_GET_CLASS(context); | |
277 ResetCallback previousCallback = contextClass->reset; | |
278 contextClass->reset = temporaryResetOverride; | |
279 | |
280 gSawContextReset = false; | |
281 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_Multi_key)
; | |
282 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_apostrophe
); | |
283 inputMethodFilter.notifyFocusedOut(); | |
284 | |
285 verifyCanceledComposition(inputMethodFilter.events()); | |
286 | |
287 contextClass->reset = previousCallback; | |
288 } | |
289 | |
290 TEST(GTK, GtkInputMethodFilterContextMouseClickDuringOngoingComposition) | |
291 { | |
292 TestInputMethodFilter inputMethodFilter; | |
293 | |
294 // See comment above about this technique. | |
295 GtkIMContext* context = inputMethodFilter.context(); | |
296 GtkIMContextClass* contextClass = GTK_IM_CONTEXT_GET_CLASS(context); | |
297 ResetCallback previousCallback = contextClass->reset; | |
298 contextClass->reset = temporaryResetOverride; | |
299 | |
300 gSawContextReset = false; | |
301 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_Multi_key)
; | |
302 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_apostrophe
); | |
303 inputMethodFilter.notifyMouseButtonPress(); | |
304 | |
305 verifyCanceledComposition(inputMethodFilter.events()); | |
306 | |
307 contextClass->reset = previousCallback; | |
308 } | |
309 | |
310 } // namespace TestWebKitAPI | |
OLD | NEW |