2009-10-01 Martin Robinson <martin.james.robinson@gmail.com>
authoreric@webkit.org <eric@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 1 Oct 2009 09:02:30 +0000 (09:02 +0000)
committereric@webkit.org <eric@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 1 Oct 2009 09:02:30 +0000 (09:02 +0000)
        Reviewed by Xan Lopez.

        [GTK] GtkIMContext filtering interferes with DOM key events
        https://bugs.webkit.org/show_bug.cgi?id=28733

        Add new key event test ensuring that IME keypresses are handled.

        * GNUmakefile.am:
2009-10-01  Martin Robinson  <martin.james.robinson@gmail.com>

        Reviewed by Xan Lopez.

        [GTK] GtkIMContext filtering interferes with DOM key events
        https://bugs.webkit.org/show_bug.cgi?id=28733

        Re-enable skipped tests which were previously failing.

        * platform/gtk/Skipped:
2009-10-01  Martin Robinson  <martin.james.robinson@gmail.com>

        Reviewed by Xan Lopez.

        [GTK] GtkIMContext filtering interferes with DOM key events
        https://bugs.webkit.org/show_bug.cgi?id=28733

        Ensure that keyboard events filtered by the GtkIMContext still create
        the proper DOM events.

        No tests added. Instead previously skipped tests have been enabled.

        * platform/gtk/KeyEventGtk.cpp:
        (WebCore::keyIdentifierForGdkKeyCode):
        (WebCore::singleCharacterString):
2009-10-01  Martin Robinson  <martin.james.robinson@gmail.com>

        Reviewed by Xan Lopez.

        [GTK] GtkIMContext filtering interferes with DOM key events
        https://bugs.webkit.org/show_bug.cgi?id=28733

        Ensure that keyboard events filtered by the GtkIMContext still create
        the proper DOM events.

        * WebCoreSupport/EditorClientGtk.cpp:
        (WebKit::clearPendingIMData):
        (WebKit::imContextCommitted):
        (WebKit::imContextPreeditChanged):
        (WebKit::EditorClient::shouldBeginEditing):
        (WebKit::EditorClient::shouldEndEditing):
        (WebKit::interpretEditorCommandKeyEvent):
        (WebKit::handleCaretBrowsingKeyboardEvent):
        (WebKit::EditorClient::handleKeyboardEvent):
        (WebKit::EditorClient::handleInputMethodKeydown):
        * tests/testkeyevents.c: Added.
        (test_info_new):
        (test_info_destroy):
        (key_event_fixture_setup):
        (key_event_fixture_teardown):
        (key_press_event_cb):
        (key_release_event_cb):
        (load_status_cb):
        (map_event_cb):
        (test_keypress):
        (main):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@48964 268f45cc-cd09-0410-ab3c-d52691b4dbfc

ChangeLog
GNUmakefile.am
LayoutTests/ChangeLog
LayoutTests/platform/gtk/Skipped
WebCore/ChangeLog
WebCore/platform/gtk/KeyEventGtk.cpp
WebKit/gtk/ChangeLog
WebKit/gtk/WebCoreSupport/EditorClientGtk.cpp
WebKit/gtk/tests/testkeyevents.c [new file with mode: 0644]

index 96fb0a0..d6c67b8 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2009-10-01  Martin Robinson  <martin.james.robinson@gmail.com>
+
+        Reviewed by Xan Lopez.
+
+        [GTK] GtkIMContext filtering interferes with DOM key events
+        https://bugs.webkit.org/show_bug.cgi?id=28733
+
+        Add new key event test ensuring that IME keypresses are handled.
+
+        * GNUmakefile.am:
+
 2009-10-01  Philippe Normand  <pnormand@igalia.com>
 
         Reviewed by Xan Lopez.
index 40c791a..5aca0e6 100644 (file)
@@ -581,7 +581,8 @@ TEST_PROGS += Programs/unittests/testhttpbackend \
        Programs/unittests/testhittestresult \
        Programs/unittests/testwebsettings \
        Programs/unittests/testwebresource \
-       Programs/unittests/testwebdatasource
+       Programs/unittests/testwebdatasource \
+       Programs/unittests/testkeyevents
 
 # Add additional tests here
 Programs_unittests_testhttpbackend_SOURCES = WebKit/gtk/tests/testhttpbackend.c
@@ -651,6 +652,11 @@ Programs_unittests_testhittestresult_SOURCES = WebKit/gtk/tests/testhittestresul
 Programs_unittests_testhittestresult_CFLAGS = $(webkit_tests_cflags)
 Programs_unittests_testhittestresult_LDADD = $(webkit_tests_ldadd)
 
+Programs_unittests_testkeyevents_SOURCES = WebKit/gtk/tests/testkeyevents.c
+Programs_unittests_testkeyevents_CFLAGS = $(webkit_tests_cflags)
+Programs_unittests_testkeyevents_LDADD = $(webkit_tests_ldadd)
+Programs_unittests_testkeyevents_LDFLAGS = $(webkit_tests_ldflags)
+
 # Autogenerated sources
 BUILT_SOURCES := \
        $(javascriptcore_built_sources) \
index 9eceb78..b696ee1 100644 (file)
@@ -1,3 +1,14 @@
+2009-10-01  Martin Robinson  <martin.james.robinson@gmail.com>
+
+        Reviewed by Xan Lopez.
+
+        [GTK] GtkIMContext filtering interferes with DOM key events
+        https://bugs.webkit.org/show_bug.cgi?id=28733
+
+        Re-enable skipped tests which were previously failing.
+
+        * platform/gtk/Skipped:
+
 2009-10-01  Philippe Normand  <pnormand@igalia.com>
 
         Reviewed by Xan Lopez.
index ef65b41..11ba645 100644 (file)
@@ -1594,11 +1594,6 @@ fast/events/dblclick-addEventListener.html
 fast/events/drag-in-frames.html
 fast/events/frame-tab-focus.html
 fast/events/js-keyboard-event-creation.html
-fast/events/key-events-in-input-button.html
-fast/events/key-events-in-input-text.html
-fast/events/keydown-keypress-focus-change.html
-fast/events/keydown-keypress-preventDefault.html
-fast/events/keypress-focus-change.html
 fast/events/keypress-insert-tab.html
 fast/events/mouse-click-events.html
 fast/events/mouseclick-target-and-positioning.html
@@ -1618,7 +1613,6 @@ fast/events/pointer-events-2.html
 fast/events/popup-blocking-click-in-iframe.html
 fast/events/right-click-focus.html
 fast/events/scrollbar-double-click.html
-fast/events/special-key-events-in-input-text.html
 fast/events/stop-load-in-unload-handler-using-document-write.html
 fast/events/stop-load-in-unload-handler-using-window-stop.html
 fast/events/tabindex-focus-blur-all.html
@@ -5553,7 +5547,6 @@ editing/pasteboard/files-during-page-drags.html
 editing/selection/extend-selection-after-double-click.html
 fast/events/drag-to-navigate.html
 fast/events/prevent-drag-to-navigate.html
-fast/events/keydown-function-keys.html
 fast/forms/slider-delete-while-dragging-thumb.html
 fast/events/tab-focus-anchor.html
 http/tests/local/drag-over-remote-content.html
index fa571a7..87bc9c2 100644 (file)
@@ -1,3 +1,19 @@
+2009-10-01  Martin Robinson  <martin.james.robinson@gmail.com>
+
+        Reviewed by Xan Lopez.
+
+        [GTK] GtkIMContext filtering interferes with DOM key events
+        https://bugs.webkit.org/show_bug.cgi?id=28733
+
+        Ensure that keyboard events filtered by the GtkIMContext still create
+        the proper DOM events.
+
+        No tests added. Instead previously skipped tests have been enabled.
+
+        * platform/gtk/KeyEventGtk.cpp:
+        (WebCore::keyIdentifierForGdkKeyCode):
+        (WebCore::singleCharacterString):
+
 2009-10-01  Philippe Normand  <pnormand@igalia.com>
 
         Reviewed by Xan Lopez.
index 5875547..4186c2f 100644 (file)
@@ -136,6 +136,8 @@ static String keyIdentifierForGdkKeyCode(guint keyCode)
             // Standard says that DEL becomes U+007F.
         case GDK_Delete:
             return "U+007F";
+        case GDK_BackSpace:
+            return "U+0008";
         case GDK_ISO_Left_Tab:
         case GDK_3270_BackTab:
         case GDK_Tab:
@@ -503,6 +505,8 @@ static String singleCharacterString(guint val)
         case GDK_KP_Enter:
         case GDK_Return:
             return String("\r");
+        case GDK_BackSpace:
+            return String("\x8");
         default:
             gunichar c = gdk_keyval_to_unicode(val);
             glong nwc;
index 4265988..d6a7fd2 100644 (file)
@@ -1,3 +1,35 @@
+2009-10-01  Martin Robinson  <martin.james.robinson@gmail.com>
+
+        Reviewed by Xan Lopez.
+
+        [GTK] GtkIMContext filtering interferes with DOM key events
+        https://bugs.webkit.org/show_bug.cgi?id=28733
+
+        Ensure that keyboard events filtered by the GtkIMContext still create
+        the proper DOM events.
+
+        * WebCoreSupport/EditorClientGtk.cpp:
+        (WebKit::clearPendingIMData):
+        (WebKit::imContextCommitted):
+        (WebKit::imContextPreeditChanged):
+        (WebKit::EditorClient::shouldBeginEditing):
+        (WebKit::EditorClient::shouldEndEditing):
+        (WebKit::interpretEditorCommandKeyEvent):
+        (WebKit::handleCaretBrowsingKeyboardEvent):
+        (WebKit::EditorClient::handleKeyboardEvent):
+        (WebKit::EditorClient::handleInputMethodKeydown):
+        * tests/testkeyevents.c: Added.
+        (test_info_new):
+        (test_info_destroy):
+        (key_event_fixture_setup):
+        (key_event_fixture_teardown):
+        (key_press_event_cb):
+        (key_release_event_cb):
+        (load_status_cb):
+        (map_event_cb):
+        (test_keypress):
+        (main):
+
 2009-09-29  Xan Lopez  <xlopez@igalia.com>
 
         Reviewed by Gustavo Noronha.
index 71a7c1a..b8ba5ad 100644 (file)
@@ -49,39 +49,32 @@ using namespace WebCore;
 
 namespace WebKit {
 
+static gchar* pendingComposition = 0;
+static gchar* pendingPreedit = 0;
+
+static void clearPendingIMData()
+{
+    g_free(pendingComposition);
+    pendingComposition = 0;
+    g_free(pendingPreedit);
+    pendingPreedit = 0;
+}
 static void imContextCommitted(GtkIMContext* context, const gchar* str, EditorClient* client)
 {
-    Frame* targetFrame = core(client->m_webView)->focusController()->focusedOrMainFrame();
+    ASSERT(!pendingComposition);
 
-    if (!targetFrame || !targetFrame->editor()->canEdit())
-        return;
-
-    Editor* editor = targetFrame->editor();
-
-    String commitString = String::fromUTF8(str);
-    editor->confirmComposition(commitString);
+    // This signal will fire during a keydown event. We want the contents of the
+    // field to change right before the keyup event, so we wait until then to actually
+    // commit this composition.
+    pendingComposition = g_strdup(str);
 }
 
 static void imContextPreeditChanged(GtkIMContext* context, EditorClient* client)
 {
-    Frame* frame = core(client->m_webView)->focusController()->focusedOrMainFrame();
-    Editor* editor = frame->editor();
+    ASSERT(!pendingPreedit);
 
-    gchar* preedit = NULL;
-    gint cursorPos = 0;
     // We ignore the provided PangoAttrList for now.
-    gtk_im_context_get_preedit_string(context, &preedit, NULL, &cursorPos);
-    String preeditString = String::fromUTF8(preedit);
-    g_free(preedit);
-
-    // setComposition() will replace the user selection if passed an empty
-    // preedit. We don't want this to happen.
-    if (preeditString.isEmpty() && !editor->hasComposition())
-        return;
-
-    Vector<CompositionUnderline> underlines;
-    underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
-    editor->setComposition(preeditString, underlines, cursorPos, 0);
+    gtk_im_context_get_preedit_string(context, &pendingPreedit, NULL, NULL);
 }
 
 void EditorClient::setInputMethodState(bool active)
@@ -136,12 +129,16 @@ int EditorClient::spellCheckerDocumentTag()
 
 bool EditorClient::shouldBeginEditing(WebCore::Range*)
 {
+    clearPendingIMData();
+
     notImplemented();
     return true;
 }
 
 bool EditorClient::shouldEndEditing(WebCore::Range*)
 {
+    clearPendingIMData();
+
     notImplemented();
     return true;
 }
@@ -421,7 +418,7 @@ static const KeyPressEntry keyPressEntries[] = {
     { '\r',   AltKey | ShiftKey,  "InsertNewline"                               },
 };
 
-static const char* interpretKeyEvent(const KeyboardEvent* evt)
+static const char* interpretEditorCommandKeyEvent(const KeyboardEvent* evt)
 {
     ASSERT(evt->type() == eventNames().keydownEvent || evt->type() == eventNames().keypressEvent);
 
@@ -456,74 +453,117 @@ static const char* interpretKeyEvent(const KeyboardEvent* evt)
     return mapKey ? keyPressCommandsMap->get(mapKey) : 0;
 }
 
-static bool handleEditingKeyboardEvent(KeyboardEvent* evt)
+static bool handleCaretBrowsingKeyboardEvent(Frame* frame, const PlatformKeyboardEvent* keyEvent)
+{
+    switch (keyEvent->windowsVirtualKeyCode()) {
+        case VK_LEFT:
+            frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
+                    SelectionController::LEFT,
+                    keyEvent->ctrlKey() ? WordGranularity : CharacterGranularity,
+                    true);
+            return true;
+        case VK_RIGHT:
+            frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
+                    SelectionController::RIGHT,
+                    keyEvent->ctrlKey() ? WordGranularity : CharacterGranularity,
+                    true);
+            return true;
+        case VK_UP:
+            frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
+                    SelectionController::BACKWARD,
+                    keyEvent->ctrlKey() ? ParagraphGranularity : LineGranularity,
+                    true);
+            return true;
+        case VK_DOWN:
+            frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
+                    SelectionController::FORWARD,
+                    keyEvent->ctrlKey() ? ParagraphGranularity : LineGranularity,
+                    true);
+            return true;
+        default:
+            return false; // Not a caret browswing keystroke, so continue processing.
+    }
+}
+
+void EditorClient::handleKeyboardEvent(KeyboardEvent* event)
 {
-    Node* node = evt->target()->toNode();
+    Node* node = event->target()->toNode();
     ASSERT(node);
     Frame* frame = node->document()->frame();
     ASSERT(frame);
 
-    const PlatformKeyboardEvent* keyEvent = evt->keyEvent();
-    if (!keyEvent)
-        return false;
+    const PlatformKeyboardEvent* platformEvent = event->keyEvent();
+    if (!platformEvent)
+        return;
 
     bool caretBrowsing = frame->settings()->caretBrowsingEnabled();
-    if (caretBrowsing) {
-        switch (keyEvent->windowsVirtualKeyCode()) {
-            case VK_LEFT:
-                frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
-                        SelectionController::LEFT,
-                        keyEvent->ctrlKey() ? WordGranularity : CharacterGranularity,
-                        true);
-                return true;
-            case VK_RIGHT:
-                frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
-                        SelectionController::RIGHT,
-                        keyEvent->ctrlKey() ? WordGranularity : CharacterGranularity,
-                        true);
-                return true;
-            case VK_UP:
-                frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
-                        SelectionController::BACKWARD,
-                        keyEvent->ctrlKey() ? ParagraphGranularity : LineGranularity,
-                        true);
-                return true;
-            case VK_DOWN:
-                frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
-                        SelectionController::FORWARD,
-                        keyEvent->ctrlKey() ? ParagraphGranularity : LineGranularity,
-                        true);
-                return true;
-        }
+    if (caretBrowsing && handleCaretBrowsingKeyboardEvent(frame, platformEvent)) {
+        // This was a caret browsing key event, so prevent it from bubbling up to the DOM.
+        event->setDefaultHandled();
+        return;
     }
 
-    Editor::Command command = frame->editor()->command(interpretKeyEvent(evt));
+    // Don't allow editor commands or text insertion for nodes that cannot edit.
+    if (!frame->editor()->canEdit())
+        return;
 
-    if (keyEvent->type() == PlatformKeyboardEvent::RawKeyDown) {
-        // WebKit doesn't have enough information about mode to decide how commands that just insert text if executed via Editor should be treated,
-        // so we leave it upon WebCore to either handle them immediately (e.g. Tab that changes focus) or let a keypress event be generated
-        // (e.g. Tab that inserts a Tab character, or Enter).
-        return !command.isTextInsertion() && command.execute(evt);
+    const gchar* editorCommandString = interpretEditorCommandKeyEvent(event);
+    if (editorCommandString) {
+        Editor::Command command = frame->editor()->command(editorCommandString);
+
+        // On editor commands from key down events, we only want to let the event bubble up to
+        // the DOM if it inserts text. If it doesn't insert text (e.g. Tab that changes focus)
+        // we just want WebKit to handle it immediately without a DOM event.
+        if (platformEvent->type() == PlatformKeyboardEvent::RawKeyDown) {
+            if (!command.isTextInsertion() && command.execute(event))
+                event->setDefaultHandled();
+
+            return;
+        } else if (command.execute(event)) {
+            event->setDefaultHandled();
+            return;
+        }
     }
 
-    if (command.execute(evt))
-        return true;
+    // This is just a normal text insertion, so wait to execute the insertion
+    // until a keypress event happens. This will ensure that the insertion will not
+    // be reflected in the contents of the field until the keyup DOM event.
+    if (event->type() == eventNames().keypressEvent) {
 
-    // Don't insert null or control characters as they can result in unexpected behaviour
-    if (evt->charCode() < ' ')
-        return false;
+        if (pendingComposition) {
+            String compositionString = String::fromUTF8(pendingComposition);
+            frame->editor()->confirmComposition(compositionString);
 
-    // Don't insert anything if a modifier is pressed
-    if (keyEvent->ctrlKey() || keyEvent->altKey())
-        return false;
+            clearPendingIMData();
+            event->setDefaultHandled();
 
-    return frame->editor()->insertText(evt->keyEvent()->text(), evt);
-}
+        } else if (pendingPreedit) {
+            String preeditString = String::fromUTF8(pendingPreedit);
 
-void EditorClient::handleKeyboardEvent(KeyboardEvent* event)
-{
-    if (handleEditingKeyboardEvent(event))
-        event->setDefaultHandled();
+            // Don't use an empty preedit as it will destroy the current
+            // selection, even if the composition is cancelled or fails later on.
+            if (!preeditString.isEmpty()) {
+                Vector<CompositionUnderline> underlines;
+                underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
+                frame->editor()->setComposition(preeditString, underlines, 0, 0);
+            }
+
+            clearPendingIMData();
+            event->setDefaultHandled();
+
+        } else {
+            // Don't insert null or control characters as they can result in unexpected behaviour
+            if (event->charCode() < ' ')
+                return;
+
+            // Don't insert anything if a modifier is pressed
+            if (platformEvent->ctrlKey() || platformEvent->altKey())
+                return;
+
+            if (frame->editor()->insertText(platformEvent->text(), event))
+                event->setDefaultHandled();
+        }
+    }
 }
 
 void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
@@ -532,10 +572,10 @@ void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
     if (!targetFrame || !targetFrame->editor()->canEdit())
         return;
 
+    // TODO: We need to decide which filtered keystrokes should be treated as IM
+    // events and which should not.
     WebKitWebViewPrivate* priv = m_webView->priv;
-    // TODO: Dispatch IE-compatible text input events for IM events.
-    if (gtk_im_context_filter_keypress(priv->imContext, event->keyEvent()->gdkEventKey()))
-        event->setDefaultHandled();
+    gtk_im_context_filter_keypress(priv->imContext, event->keyEvent()->gdkEventKey());
 }
 
 EditorClient::EditorClient(WebKitWebView* webView)
diff --git a/WebKit/gtk/tests/testkeyevents.c b/WebKit/gtk/tests/testkeyevents.c
new file mode 100644 (file)
index 0000000..ee7728c
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2009 Martin Robinson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2,1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <glib/gstdio.h>
+#include <webkit/webkit.h>
+
+#if GTK_CHECK_VERSION(2, 14, 0)
+
+typedef struct {
+  char* page;
+  gboolean shouldBeHandled;
+} TestInfo;
+
+typedef struct {
+    GtkWidget* window;
+    WebKitWebView* webView;
+    GMainLoop* loop;
+    TestInfo* info;
+} KeyEventFixture;
+
+TestInfo*
+test_info_new(const char* page, gboolean shouldBeHandled)
+{
+    TestInfo* info;
+
+    info = g_slice_new(TestInfo);
+    info->page = g_strdup(page);
+    info->shouldBeHandled = shouldBeHandled;
+
+    return info;
+}
+
+void
+test_info_destroy(TestInfo* info)
+{
+    g_free(info->page);
+    g_slice_free(TestInfo, info);
+}
+
+static void key_event_fixture_setup(KeyEventFixture* fixture, gconstpointer data)
+{
+    fixture->loop = g_main_loop_new(NULL, TRUE);
+
+    fixture->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    fixture->webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
+
+    gtk_container_add(GTK_CONTAINER(fixture->window), GTK_WIDGET(fixture->webView));
+}
+
+static void key_event_fixture_teardown(KeyEventFixture* fixture, gconstpointer data)
+{
+    gtk_widget_destroy(fixture->window);
+    g_main_loop_unref(fixture->loop);
+    test_info_destroy(fixture->info);
+}
+
+static gboolean key_press_event_cb(WebKitWebView* webView, GdkEvent* event, gpointer data)
+{
+    KeyEventFixture* fixture = (KeyEventFixture*)data;
+    gboolean handled = GTK_WIDGET_GET_CLASS(fixture->webView)->key_press_event(GTK_WIDGET(fixture->webView), &event->key);
+    g_assert_cmpint(handled, ==, fixture->info->shouldBeHandled);
+
+    return FALSE;
+}
+
+
+static gboolean key_release_event_cb(WebKitWebView* webView, GdkEvent* event, gpointer data)
+{
+    // WebCore never seems to mark keyup events as handled.
+    KeyEventFixture* fixture = (KeyEventFixture*)data;
+    gboolean handled = GTK_WIDGET_GET_CLASS(fixture->webView)->key_press_event(GTK_WIDGET(fixture->webView), &event->key);
+    g_assert(!handled);
+
+    g_main_loop_quit(fixture->loop);
+
+    return FALSE;
+}
+
+static void load_status_cb(WebKitWebView* webView, GParamSpec* spec, gpointer data)
+{
+    KeyEventFixture* fixture = (KeyEventFixture*)data;
+    WebKitLoadStatus status = webkit_web_view_get_load_status(webView);
+    if (status == WEBKIT_LOAD_FINISHED) {
+        gtk_test_widget_send_key(GTK_WIDGET(fixture->webView),
+                                 gdk_unicode_to_keyval('a'), 0);
+    }
+
+}
+
+gboolean map_event_cb(GtkWidget *widget, GdkEvent* event, gpointer data)
+{
+    gtk_widget_grab_focus(widget);
+    KeyEventFixture* fixture = (KeyEventFixture*)data;
+
+    g_signal_connect(fixture->webView, "key-press-event",
+                     G_CALLBACK(key_press_event_cb), fixture);
+    g_signal_connect(fixture->webView, "key-release-event",
+                     G_CALLBACK(key_release_event_cb), fixture);
+
+    g_signal_connect(fixture->webView, "notify::load-status",
+                     G_CALLBACK(load_status_cb), fixture);
+
+    webkit_web_view_load_string(fixture->webView, fixture->info->page,
+                                "text/html", "utf-8", "file://");
+
+    return FALSE;
+}
+
+static void test_keypress(KeyEventFixture* fixture, gconstpointer data)
+{
+    fixture->info = (TestInfo*)data;
+
+    g_signal_connect(fixture->window, "map-event",
+                     G_CALLBACK(map_event_cb), fixture);
+
+    gtk_widget_show(fixture->window);
+    gtk_widget_show(GTK_WIDGET(fixture->webView));
+    gtk_window_present(GTK_WINDOW(fixture->window));
+
+    g_main_loop_run(fixture->loop);
+
+}
+
+int main(int argc, char** argv)
+{
+    g_thread_init(NULL);
+    gtk_test_init(&argc, &argv, NULL);
+
+    g_test_bug_base("https://bugs.webkit.org/");
+
+    g_test_add("/webkit/keyevent/textfield", KeyEventFixture,
+               test_info_new("<html><body><input id=\"in\" type=\"text\">"
+                             "<script>document.getElementById('in').focus();"
+                             "</script></body></html>", TRUE),
+               key_event_fixture_setup,
+               test_keypress,
+               key_event_fixture_teardown);
+
+    g_test_add("/webkit/keyevent/buttons", KeyEventFixture,
+               test_info_new("<html><body><input id=\"in\" type=\"button\">"
+                             "<script>document.getElementById('in').focus();"
+                             "</script></body></html>", FALSE),
+               key_event_fixture_setup,
+               test_keypress,
+               key_event_fixture_teardown);
+
+    g_test_add("/webkit/keyevent/link", KeyEventFixture,
+               test_info_new("<html><body><a href=\"http://www.gnome.org\" id=\"in\">"
+                             "LINKY MCLINKERSON</a><script>"
+                             "document.getElementById('in').focus();</script>"
+                             "</body></html>", FALSE),
+               key_event_fixture_setup,
+               test_keypress,
+               key_event_fixture_teardown);
+
+    return g_test_run();
+}
+
+#else
+
+int main(int argc, char** argv)
+{
+    g_critical("You will need at least GTK+ 2.14.0 to run the unit tests.");
+    return 0;
+}
+
+#endif