1 /* 2 * Copyright (C) 2009, 2010 Martin Robinson <mrobinson (at) webkit.org> 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2,1 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Library General Public License for more details. 13 * 14 * You should have received a copy of the GNU Library General Public License 15 * along with this library; see the file COPYING.LIB. If not, write to 16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17 * Boston, MA 02110-1301, USA. 18 */ 19 20 #include <errno.h> 21 #include <unistd.h> 22 #include <string.h> 23 #include <glib/gstdio.h> 24 #include <webkit/webkit.h> 25 #include <JavaScriptCore/JSStringRef.h> 26 #include <JavaScriptCore/JSContextRef.h> 27 28 29 #if GTK_CHECK_VERSION(2, 14, 0) 30 31 typedef struct { 32 char* page; 33 char* text; 34 gboolean shouldBeHandled; 35 } TestInfo; 36 37 typedef struct { 38 GtkWidget* window; 39 WebKitWebView* webView; 40 GMainLoop* loop; 41 TestInfo* info; 42 } KeyEventFixture; 43 44 TestInfo* 45 test_info_new(const char* page, gboolean shouldBeHandled) 46 { 47 TestInfo* info; 48 49 info = g_slice_new(TestInfo); 50 info->page = g_strdup(page); 51 info->shouldBeHandled = shouldBeHandled; 52 info->text = 0; 53 54 return info; 55 } 56 57 void 58 test_info_destroy(TestInfo* info) 59 { 60 g_free(info->page); 61 g_free(info->text); 62 g_slice_free(TestInfo, info); 63 } 64 65 static void key_event_fixture_setup(KeyEventFixture* fixture, gconstpointer data) 66 { 67 fixture->loop = g_main_loop_new(NULL, TRUE); 68 69 fixture->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 70 fixture->webView = WEBKIT_WEB_VIEW(webkit_web_view_new()); 71 72 gtk_container_add(GTK_CONTAINER(fixture->window), GTK_WIDGET(fixture->webView)); 73 } 74 75 static void key_event_fixture_teardown(KeyEventFixture* fixture, gconstpointer data) 76 { 77 gtk_widget_destroy(fixture->window); 78 g_main_loop_unref(fixture->loop); 79 test_info_destroy(fixture->info); 80 } 81 82 static gboolean key_press_event_cb(WebKitWebView* webView, GdkEvent* event, gpointer data) 83 { 84 KeyEventFixture* fixture = (KeyEventFixture*)data; 85 gboolean handled = GTK_WIDGET_GET_CLASS(fixture->webView)->key_press_event(GTK_WIDGET(fixture->webView), &event->key); 86 g_assert_cmpint(handled, ==, fixture->info->shouldBeHandled); 87 88 return FALSE; 89 } 90 91 static gboolean key_release_event_cb(WebKitWebView* webView, GdkEvent* event, gpointer data) 92 { 93 // WebCore never seems to mark keyup events as handled. 94 KeyEventFixture* fixture = (KeyEventFixture*)data; 95 gboolean handled = GTK_WIDGET_GET_CLASS(fixture->webView)->key_press_event(GTK_WIDGET(fixture->webView), &event->key); 96 g_assert(!handled); 97 98 g_main_loop_quit(fixture->loop); 99 100 return FALSE; 101 } 102 103 static void test_keypress_events_load_status_cb(WebKitWebView* webView, GParamSpec* spec, gpointer data) 104 { 105 KeyEventFixture* fixture = (KeyEventFixture*)data; 106 WebKitLoadStatus status = webkit_web_view_get_load_status(webView); 107 if (status == WEBKIT_LOAD_FINISHED) { 108 g_signal_connect(fixture->webView, "key-press-event", 109 G_CALLBACK(key_press_event_cb), fixture); 110 g_signal_connect(fixture->webView, "key-release-event", 111 G_CALLBACK(key_release_event_cb), fixture); 112 if (!gtk_test_widget_send_key(GTK_WIDGET(fixture->webView), 113 gdk_unicode_to_keyval('a'), 0)) 114 g_assert_not_reached(); 115 } 116 117 } 118 119 gboolean map_event_cb(GtkWidget *widget, GdkEvent* event, gpointer data) 120 { 121 gtk_widget_grab_focus(widget); 122 KeyEventFixture* fixture = (KeyEventFixture*)data; 123 webkit_web_view_load_string(fixture->webView, fixture->info->page, 124 "text/html", "utf-8", "file://"); 125 return FALSE; 126 } 127 128 static void setup_keyevent_test(KeyEventFixture* fixture, gconstpointer data, GCallback load_event_callback) 129 { 130 fixture->info = (TestInfo*)data; 131 g_signal_connect(fixture->window, "map-event", 132 G_CALLBACK(map_event_cb), fixture); 133 134 gtk_widget_show(fixture->window); 135 gtk_widget_show(GTK_WIDGET(fixture->webView)); 136 gtk_window_present(GTK_WINDOW(fixture->window)); 137 138 g_signal_connect(fixture->webView, "notify::load-status", 139 load_event_callback, fixture); 140 141 g_main_loop_run(fixture->loop); 142 } 143 144 static void test_keypress_events(KeyEventFixture* fixture, gconstpointer data) 145 { 146 setup_keyevent_test(fixture, data, G_CALLBACK(test_keypress_events_load_status_cb)); 147 } 148 149 static gboolean element_text_equal_to(JSContextRef context, const gchar* text) 150 { 151 JSStringRef scriptString = JSStringCreateWithUTF8CString( 152 "window.document.getElementById(\"in\").value;"); 153 JSValueRef value = JSEvaluateScript(context, scriptString, 0, 0, 0, 0); 154 JSStringRelease(scriptString); 155 156 // If the value isn't a string, the element is probably a div 157 // so grab the innerText instead. 158 if (!JSValueIsString(context, value)) { 159 JSStringRef scriptString = JSStringCreateWithUTF8CString( 160 "window.document.getElementById(\"in\").innerText;"); 161 value = JSEvaluateScript(context, scriptString, 0, 0, 0, 0); 162 JSStringRelease(scriptString); 163 } 164 165 g_assert(JSValueIsString(context, value)); 166 JSStringRef inputString = JSValueToStringCopy(context, value, 0); 167 g_assert(inputString); 168 169 gint size = JSStringGetMaximumUTF8CStringSize(inputString); 170 gchar* cString = g_malloc(size); 171 JSStringGetUTF8CString(inputString, cString, size); 172 JSStringRelease(inputString); 173 174 gboolean result = g_utf8_collate(cString, text) == 0; 175 g_free(cString); 176 return result; 177 } 178 179 static void test_ime_load_status_cb(WebKitWebView* webView, GParamSpec* spec, gpointer data) 180 { 181 KeyEventFixture* fixture = (KeyEventFixture*)data; 182 WebKitLoadStatus status = webkit_web_view_get_load_status(webView); 183 if (status != WEBKIT_LOAD_FINISHED) 184 return; 185 186 JSGlobalContextRef context = webkit_web_frame_get_global_context( 187 webkit_web_view_get_main_frame(webView)); 188 g_assert(context); 189 190 GtkIMContext* imContext = 0; 191 g_object_get(webView, "im-context", &imContext, NULL); 192 g_assert(imContext); 193 194 // Test that commits that happen outside of key events 195 // change the text field immediately. This closely replicates 196 // the behavior of SCIM. 197 g_assert(element_text_equal_to(context, "")); 198 g_signal_emit_by_name(imContext, "commit", "a"); 199 g_assert(element_text_equal_to(context, "a")); 200 g_signal_emit_by_name(imContext, "commit", "b"); 201 g_assert(element_text_equal_to(context, "ab")); 202 g_signal_emit_by_name(imContext, "commit", "c"); 203 g_assert(element_text_equal_to(context, "abc")); 204 205 g_object_unref(imContext); 206 g_main_loop_quit(fixture->loop); 207 } 208 209 static void test_ime(KeyEventFixture* fixture, gconstpointer data) 210 { 211 setup_keyevent_test(fixture, data, G_CALLBACK(test_ime_load_status_cb)); 212 } 213 214 static gboolean verify_contents(gpointer data) 215 { 216 KeyEventFixture* fixture = (KeyEventFixture*)data; 217 JSGlobalContextRef context = webkit_web_frame_get_global_context( 218 webkit_web_view_get_main_frame(fixture->webView)); 219 g_assert(context); 220 221 g_assert(element_text_equal_to(context, fixture->info->text)); 222 g_main_loop_quit(fixture->loop); 223 return FALSE; 224 } 225 226 static void test_blocking_load_status_cb(WebKitWebView* webView, GParamSpec* spec, gpointer data) 227 { 228 KeyEventFixture* fixture = (KeyEventFixture*)data; 229 WebKitLoadStatus status = webkit_web_view_get_load_status(webView); 230 if (status != WEBKIT_LOAD_FINISHED) 231 return; 232 233 // The first keypress event should not modify the field. 234 fixture->info->text = g_strdup("bc"); 235 if (!gtk_test_widget_send_key(GTK_WIDGET(fixture->webView), 236 gdk_unicode_to_keyval('a'), 0)) 237 g_assert_not_reached(); 238 if (!gtk_test_widget_send_key(GTK_WIDGET(fixture->webView), 239 gdk_unicode_to_keyval('b'), 0)) 240 g_assert_not_reached(); 241 if (!gtk_test_widget_send_key(GTK_WIDGET(fixture->webView), 242 gdk_unicode_to_keyval('c'), 0)) 243 g_assert_not_reached(); 244 245 g_idle_add(verify_contents, fixture); 246 } 247 248 static void test_blocking(KeyEventFixture* fixture, gconstpointer data) 249 { 250 setup_keyevent_test(fixture, data, G_CALLBACK(test_blocking_load_status_cb)); 251 } 252 253 #if defined(GDK_WINDOWING_X11) && GTK_CHECK_VERSION(2, 16, 0) 254 static void test_xim_load_status_cb(WebKitWebView* webView, GParamSpec* spec, gpointer data) 255 { 256 KeyEventFixture* fixture = (KeyEventFixture*)data; 257 WebKitLoadStatus status = webkit_web_view_get_load_status(webView); 258 if (status != WEBKIT_LOAD_FINISHED) 259 return; 260 261 GtkIMContext* imContext = 0; 262 g_object_get(webView, "im-context", &imContext, NULL); 263 g_assert(imContext); 264 265 gchar* originalId = g_strdup(gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(imContext))); 266 gtk_im_multicontext_set_context_id(GTK_IM_MULTICONTEXT(imContext), "xim"); 267 268 // Test that commits that happen outside of key events 269 // change the text field immediately. This closely replicates 270 // the behavior of SCIM. 271 fixture->info->text = g_strdup("debian"); 272 if (!gtk_test_widget_send_key(GTK_WIDGET(fixture->webView), 273 gdk_unicode_to_keyval('d'), 0)) 274 g_assert_not_reached(); 275 if (!gtk_test_widget_send_key(GTK_WIDGET(fixture->webView), 276 gdk_unicode_to_keyval('e'), 0)) 277 g_assert_not_reached(); 278 if (!gtk_test_widget_send_key(GTK_WIDGET(fixture->webView), 279 gdk_unicode_to_keyval('b'), 0)) 280 g_assert_not_reached(); 281 if (!gtk_test_widget_send_key(GTK_WIDGET(fixture->webView), 282 gdk_unicode_to_keyval('i'), 0)) 283 g_assert_not_reached(); 284 if (!gtk_test_widget_send_key(GTK_WIDGET(fixture->webView), 285 gdk_unicode_to_keyval('a'), 0)) 286 g_assert_not_reached(); 287 if (!gtk_test_widget_send_key(GTK_WIDGET(fixture->webView), 288 gdk_unicode_to_keyval('n'), 0)) 289 g_assert_not_reached(); 290 291 gtk_im_multicontext_set_context_id(GTK_IM_MULTICONTEXT(imContext), originalId); 292 g_free(originalId); 293 g_object_unref(imContext); 294 295 g_idle_add(verify_contents, fixture); 296 } 297 298 static void test_xim(KeyEventFixture* fixture, gconstpointer data) 299 { 300 setup_keyevent_test(fixture, data, G_CALLBACK(test_xim_load_status_cb)); 301 } 302 #endif 303 304 int main(int argc, char** argv) 305 { 306 g_thread_init(NULL); 307 gtk_test_init(&argc, &argv, NULL); 308 309 g_test_bug_base("https://bugs.webkit.org/"); 310 311 312 // We'll test input on a slew of different node types. Key events to 313 // text inputs and editable divs should be marked as handled. Key events 314 // to buttons and links should not. 315 const char* textinput_html = "<html><body><input id=\"in\" type=\"text\">" 316 "<script>document.getElementById('in').focus();</script></body></html>"; 317 const char* button_html = "<html><body><input id=\"in\" type=\"button\">" 318 "<script>document.getElementById('in').focus();</script></body></html>"; 319 const char* link_html = "<html><body><a href=\"http://www.gnome.org\" id=\"in\">" 320 "LINKY MCLINKERSON</a><script>document.getElementById('in').focus();</script>" 321 "</body></html>"; 322 const char* div_html = "<html><body><div id=\"in\" contenteditable=\"true\">" 323 "<script>document.getElementById('in').focus();</script></body></html>"; 324 325 // These are similar to the blocks above, but they should block the first 326 // keypress modifying the editable node. 327 const char* textinput_html_blocking = "<html><body>" 328 "<input id=\"in\" type=\"text\" " 329 "onkeypress=\"if (first) {event.preventDefault();first=false;}\">" 330 "<script>first = true;\ndocument.getElementById('in').focus();</script>\n" 331 "</script></body></html>"; 332 const char* div_html_blocking = "<html><body>" 333 "<div id=\"in\" contenteditable=\"true\" " 334 "onkeypress=\"if (first) {event.preventDefault();first=false;}\">" 335 "<script>first = true; document.getElementById('in').focus();</script>\n" 336 "</script></body></html>"; 337 338 g_test_add("/webkit/keyevents/event-textinput", KeyEventFixture, 339 test_info_new(textinput_html, TRUE), 340 key_event_fixture_setup, 341 test_keypress_events, 342 key_event_fixture_teardown); 343 g_test_add("/webkit/keyevents/event-buttons", KeyEventFixture, 344 test_info_new(button_html, FALSE), 345 key_event_fixture_setup, 346 test_keypress_events, 347 key_event_fixture_teardown); 348 g_test_add("/webkit/keyevents/event-link", KeyEventFixture, 349 test_info_new(link_html, FALSE), 350 key_event_fixture_setup, 351 test_keypress_events, 352 key_event_fixture_teardown); 353 g_test_add("/webkit/keyevent/event-div", KeyEventFixture, 354 test_info_new(div_html, TRUE), 355 key_event_fixture_setup, 356 test_keypress_events, 357 key_event_fixture_teardown); 358 g_test_add("/webkit/keyevent/ime-textinput", KeyEventFixture, 359 test_info_new(textinput_html, TRUE), 360 key_event_fixture_setup, 361 test_ime, 362 key_event_fixture_teardown); 363 g_test_add("/webkit/keyevent/ime-div", KeyEventFixture, 364 test_info_new(div_html, TRUE), 365 key_event_fixture_setup, 366 test_ime, 367 key_event_fixture_teardown); 368 g_test_add("/webkit/keyevent/block-textinput", KeyEventFixture, 369 test_info_new(textinput_html_blocking, TRUE), 370 key_event_fixture_setup, 371 test_blocking, 372 key_event_fixture_teardown); 373 g_test_add("/webkit/keyevent/block-div", KeyEventFixture, 374 test_info_new(div_html_blocking, TRUE), 375 key_event_fixture_setup, 376 test_blocking, 377 key_event_fixture_teardown); 378 #if defined(GDK_WINDOWING_X11) && GTK_CHECK_VERSION(2, 16, 0) 379 g_test_add("/webkit/keyevent/xim-textinput", KeyEventFixture, 380 test_info_new(textinput_html, TRUE), 381 key_event_fixture_setup, 382 test_xim, 383 key_event_fixture_teardown); 384 g_test_add("/webkit/keyevent/xim-div", KeyEventFixture, 385 test_info_new(div_html, TRUE), 386 key_event_fixture_setup, 387 test_xim, 388 key_event_fixture_teardown); 389 #endif 390 391 return g_test_run(); 392 } 393 394 #else 395 396 int main(int argc, char** argv) 397 { 398 g_critical("You will need at least GTK+ 2.14.0 to run the unit tests."); 399 return 0; 400 } 401 402 #endif 403