| 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 |