1/* 2 * Copyright (C) 2009, 2010 Martin Robinson <mrobinson@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 31typedef struct { 32 char* page; 33 char* text; 34 gboolean shouldBeHandled; 35} TestInfo; 36 37typedef struct { 38 GtkWidget* window; 39 WebKitWebView* webView; 40 GMainLoop* loop; 41 TestInfo* info; 42} KeyEventFixture; 43 44TestInfo* 45test_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 57void 58test_info_destroy(TestInfo* info) 59{ 60 g_free(info->page); 61 g_free(info->text); 62 g_slice_free(TestInfo, info); 63} 64 65static 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 75static 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 82static 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 91static 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 103static 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 119gboolean 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 128static 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 144static 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 149static 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 179static 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 209static void test_ime(KeyEventFixture* fixture, gconstpointer data) 210{ 211 setup_keyevent_test(fixture, data, G_CALLBACK(test_ime_load_status_cb)); 212} 213 214static 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 226static 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 248static 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) 254static 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 298static 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 304int 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 396int 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