Tools/ChangeLog

112014-05-09 Carlos Garcia Campos <cgarcia@igalia.com>
22
 3 REGRESSION(CMAKE): [GTK] InputMethodFilter unit test is not built
 4 https://bugs.webkit.org/show_bug.cgi?id=132686
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 Bring back InputMethodFilter test to the build and compile it into
 9 a single binary TestWebCore with the other Webcore tests. Also
 10 move it from TestWebKitAPI/gtk/ to TestWebKitAPI/Tests/WebCore/gtk/.
 11
 12 * Scripts/run-gtk-tests:
 13 (TestRunner): Remove WebCoreGtk from the list of test directories.
 14 * TestWebKitAPI/PlatformGTK.cmake:
 15 * TestWebKitAPI/Tests/WebCore/gtk/InputMethodFilter.cpp: Renamed from Tools/TestWebKitAPI/Tests/gtk/InputMethodFilter.cpp.
 16
 172014-05-09 Carlos Garcia Campos <cgarcia@igalia.com>
 18
319 REGRESSION(CMAKE): [GTK] WebKit2 C API tests are not longer built in the same binary
420 https://bugs.webkit.org/show_bug.cgi?id=132685
521

Tools/Scripts/run-gtk-tests

@@class TestTimeout(Exception):
5858 pass
5959
6060class TestRunner:
61  TEST_DIRS = [ "WebKit2Gtk", "WebKit2", "JavaScriptCore", "WTF", "WebCore", "WebCoreGtk" ]
 61 TEST_DIRS = [ "WebKit2Gtk", "WebKit2", "JavaScriptCore", "WTF", "WebCore" ]
6262
6363 SKIPPED = [
6464 SkippedTest("WebKit2Gtk/TestUIClient", "/webkit2/WebKitWebView/mouse-target", "Test times out after r150890", 117689),

Tools/TestWebKitAPI/PlatformGTK.cmake

@@add_test(TestWebKit2 ${TESTWEBKITAPI_RUNTIME_OUTPUT_DIRECTORY}/WebKit2/TestWebKi
106106set_tests_properties(TestWebKit2 PROPERTIES TIMEOUT 60)
107107set_target_properties(TestWebKit2 PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TESTWEBKITAPI_RUNTIME_OUTPUT_DIRECTORY}/WebKit2)
108108
109 # The list below works like a test expectation. Tests in the
110 # test_{webkit2_api|webcore}_BINARIES list are added to the test runner and
111 # tried on the bots on every build. Tests in test_{webkit2_api|webcore}_BINARIES
112 # are compiled and suffixed with fail and skipped from the test runner.
113 #
114 # Make sure that the tests are passing on both Debug and
115 # Release builds before adding it to test_{webkit2_api|webcore}_BINARIES.
 109set(TestWebCoreGtk_SOURCES
 110 ${WEBCORE_DIR}/platform/graphics/IntPoint.cpp
 111 ${WEBCORE_DIR}/platform/graphics/IntRect.cpp
 112 ${WEBCORE_DIR}/platform/graphics/IntSize.cpp
 113 ${WEBCORE_DIR}/platform/graphics/cairo/IntRectCairo.cpp
 114 ${WEBCORE_DIR}/platform/graphics/gtk/IntRectGtk.cpp
 115 ${WEBCORE_DIR}/platform/gtk/GtkInputMethodFilter.cpp
 116 ${TESTWEBKITAPI_DIR}/Tests/WebCore/gtk/InputMethodFilter.cpp
 117)
116118
117 set(test_webcore_BINARIES
118  LayoutUnit
119  URL
 119add_executable(TestWebCore
 120 ${test_main_SOURCES}
 121 ${TestWebCoreGtk_SOURCES}
 122 ${TESTWEBKITAPI_DIR}/TestsController.cpp
 123 ${TESTWEBKITAPI_DIR}/Tests/WebCore/LayoutUnit.cpp
 124 ${TESTWEBKITAPI_DIR}/Tests/WebCore/URL.cpp
120125)
121126
 127target_link_libraries(TestWebCore ${test_webcore_LIBRARIES})
 128add_test(TestWebCore ${TESTWEBKITAPI_RUNTIME_OUTPUT_DIRECTORY}/WebCore/TestWebCore)
 129set_tests_properties(TestWebCore PROPERTIES TIMEOUT 60)
 130set_target_properties(TestWebCore PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TESTWEBKITAPI_RUNTIME_OUTPUT_DIRECTORY}/WebCore)

Tools/TestWebKitAPI/Tests/WebCore/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 <WebCore/GtkInputMethodFilter.h>
 30#include <gdk/gdkkeysyms.h>
 31#include <gtk/gtk.h>
 32#include <wtf/gobject/GUniquePtr.h>
 33#include <wtf/gobject/GRefPtr.h>
 34#include <wtf/text/CString.h>
 35
 36using namespace WebCore;
 37
 38namespace TestWebKitAPI {
 39
 40class TestInputMethodFilter : public GtkInputMethodFilter {
 41public:
 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 multicontext.
 49 notifyFocusedIn();
 50 }
 51
 52 Vector<String>& events() { return m_events; }
 53
 54 void sendKeyEventToFilter(unsigned int gdkKeyValue, GdkEventType type, unsigned 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_display_get_device_manager(gdk_display_get_default())));
 65#endif
 66
 67 GUniqueOutPtr<GdkKeymapKey> keys;
 68 gint nKeys;
 69 if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeyValue, &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, unsigned int modifiers = 0)
 77 {
 78 sendKeyEventToFilter(gdkKeyValue, GDK_KEY_PRESS, modifiers);
 79 sendKeyEventToFilter(gdkKeyValue, GDK_KEY_RELEASE, modifiers);
 80 }
 81
 82protected:
 83 virtual bool sendSimpleKeyEvent(GdkEventKey* event, WTF::String eventString, EventFakedForComposition faked)
 84 {
 85 const char* eventType = event->type == GDK_KEY_RELEASE ? "release" : "press";
 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, ResultsToSend resultsToSend, EventFakedForComposition faked)
 96 {
 97 const char* eventType = event->type == GDK_KEY_RELEASE ? "release" : "press";
 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().data()));
 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", preedit.utf8().data(), cursorOffset));
 132 }
 133
 134private:
 135 GRefPtr<GtkWidget> m_testWindow;
 136 Vector<String> m_events;
 137};
 138
 139TEST(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'"), events[0]);
 150 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=67"), events[1]);
 151 ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=74 text='t'"), events[2]);
 152 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=74"), events[3]);
 153 ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=6b text='k'"), events[4]);
 154 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=6b"), events[5]);
 155}
 156
 157TEST(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 typical
 163 // of an actual hex entry, because they'd be transformed by the shift modifier 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 what 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_CONTROL_MASK);
 169
 170 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_U, GDK_SHIFT_MASK | GDK_CONTROL_MASK);
 171 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_0, GDK_SHIFT_MASK | GDK_CONTROL_MASK);
 172 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_0, GDK_SHIFT_MASK | GDK_CONTROL_MASK);
 173 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_F, GDK_SHIFT_MASK | GDK_CONTROL_MASK);
 174 inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_4, GDK_SHIFT_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, GDK_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
 204TEST(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='ó'"), events[3]);
 218 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=6f"), events[4]);
 219}
 220
 221typedef void (*GetPreeditStringCallback) (GtkIMContext*, gchar**, PangoAttrList**, int*);
 222static void temporaryGetPreeditStringOverride(GtkIMContext*, char** string, PangoAttrList** attrs, int* cursorPosition)
 223{
 224 *string = g_strdup("preedit of doom, bringer of cheese");
 225 *cursorPosition = 3;
 226}
 227
 228TEST(GTK, GtkInputMethodFilterContextEventsWithoutKeyEvents)
 229{
 230 TestInputMethodFilter inputMethodFilter;
 231
 232 // This is a bit of a hack to avoid mocking out the entire InputMethodContext, 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=16777215 (faked)"), events[0]);
 247 ASSERT_EQ(String("setPreedit text='preedit of doom, bringer of cheese' cursorOffset=3"), events[1]);
 248 ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=ffffff (faked)"), events[2]);
 249 ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=16777215 (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
 254static bool gSawContextReset = false;
 255typedef void (*ResetCallback) (GtkIMContext*);
 256static void temporaryResetOverride(GtkIMContext*)
 257{
 258 gSawContextReset = true;
 259}
 260
 261static 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
 270TEST(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
 290TEST(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

Tools/TestWebKitAPI/Tests/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 "GtkInputMethodFilter.h"
29 #include "WTFStringUtilities.h"
30 #include <gdk/gdkkeysyms.h>
31 #include <gtk/gtk.h>
32 #include <wtf/gobject/GUniquePtr.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 multicontext.
49  notifyFocusedIn();
50  }
51 
52  Vector<String>& events() { return m_events; }
53 
54  void sendKeyEventToFilter(unsigned int gdkKeyValue, GdkEventType type, unsigned 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_display_get_device_manager(gdk_display_get_default())));
65 #endif
66 
67  GUniqueOutPtr<GdkKeymapKey> keys;
68  gint nKeys;
69  if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeyValue, &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, unsigned 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" : "press";
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, ResultsToSend resultsToSend, EventFakedForComposition faked)
96  {
97  const char* eventType = event->type == GDK_KEY_RELEASE ? "release" : "press";
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().data()));
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", preedit.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'"), events[0]);
150  ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=67"), events[1]);
151  ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=74 text='t'"), events[2]);
152  ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=74"), events[3]);
153  ASSERT_EQ(String("sendSimpleKeyEvent type=press keycode=6b text='k'"), events[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 typical
163  // of an actual hex entry, because they'd be transformed by the shift modifier 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 what 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_CONTROL_MASK);
169 
170  inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_U, GDK_SHIFT_MASK | GDK_CONTROL_MASK);
171  inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_0, GDK_SHIFT_MASK | GDK_CONTROL_MASK);
172  inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_0, GDK_SHIFT_MASK | GDK_CONTROL_MASK);
173  inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_F, GDK_SHIFT_MASK | GDK_CONTROL_MASK);
174  inputMethodFilter.sendPressAndReleaseKeyEventPairToFilter(GDK_KEY_4, GDK_SHIFT_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, GDK_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='ó'"), events[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, PangoAttrList** 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 InputMethodContext, 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=16777215 (faked)"), events[0]);
247  ASSERT_EQ(String("setPreedit text='preedit of doom, bringer of cheese' cursorOffset=3"), events[1]);
248  ASSERT_EQ(String("sendSimpleKeyEvent type=release keycode=ffffff (faked)"), events[2]);
249  ASSERT_EQ(String("sendKeyEventWithCompositionResults type=press keycode=16777215 (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