Changeset 116114 in webkit


Ignore:
Timestamp:
May 4, 2012 9:49:39 AM (12 years ago)
Author:
Martin Robinson
Message:

[GTK] Rework IME handling to fix bugs and prepare for WebKit2
https://bugs.webkit.org/show_bug.cgi?id=84556

Reviewed by Gustavo Noronha Silva.

Source/WebCore:

No new tests. This change is already covered by a suite of keyboard
handling unit tests in WebKitGTK+. There are some changes in behavior,
but they are difficult to test without mocking out an entire GtkIMContext.

Add a struct, CompositionResults, which is used by PlatformKeyboardEvent
to package composition information with a keyboard event. Also add some logic
to PlatformKeyboardEvent to give the right information when it has composition
results.

  • GNUmakefile.list.am: Added new sources to the list.
  • platform/PlatformKeyboardEvent.h: Added a new CompositionResults member,

getter, and argument to the constructor.

  • platform/gtk/CompositionResults.h: Added.
  • platform/gtk/GtkInputMethodFilter.cpp: Added.
  • platform/gtk/GtkInputMethodFilter.h: Added.
  • platform/gtk/PlatformKeyboardEventGtk.cpp:

(WebCore::PlatformKeyboardEvent::windowsKeyCodeForGdkKeyCode): When
the key value is void return the VK_PROCESS keycode, which is the keycode
that web content expects with keystrokes that trigger composition events.
(WebCore::eventTypeForGdkKeyEvent): Abstract out this helper.
(WebCore::modifiersForGdkKeyEvent): Abstract out this helper.
(WebCore::PlatformKeyboardEvent::PlatformKeyboardEvent): When a PlatformKeyEvent
has composition results, use VK_PROCESS as the keycode for this event.
(WebCore::PlatformKeyboardEvent::disambiguateKeyDownEvent): When this event is
transformed into a Char event, the PlatformKeyboardEvent used for DOM keypress
events, and it has composition results clear the text members. This forces the
EventHandler code to drop the keypress event. Platform events that change the
composition states do not have corresponding keypress DOM events (only keydown
and keyup events), so this is necessary to ensure web compatibility.

Source/WebKit/gtk:

Rework input method handling logic into a class called GtkInputMethodFilter.
This filter now runs before WebCore event handling, allowing the code to more
easily fake simple compositions that should be seen as keystrokes. We can also
filter keypresses that should not go to web content at all, such as key up events
related to key down events that were filtered.

Also added is a WebViewInputMethodFilter which is a concrete implementation of
GtkInputMethodFilter. This class contains logic for actually sending events to
WebCore. In WebKit2 an implementation of GtkInputMethodFilter will send events
across the IPC channel.

  • GNUmakefile.am: Add new files to the source list.
  • WebCoreSupport/ContextMenuClientGtk.cpp:

(WebKit::inputMethodsMenuItem): Access the input method context via the filter.

  • WebCoreSupport/EditorClientGtk.cpp: Remove the tricky logic of input method

events from this class, because it's now in the GtkInputMethodFilter.
(WebKit::EditorClient::setInputMethodState): Call into the filter.
(WebKit::EditorClient::shouldBeginEditing): We no longer need to update the composition here.
This is handled by the focus in and focus out logic in the filter.
(WebKit::EditorClient::shouldEndEditing): Ditto.
(WebKit::EditorClient::respondToChangedSelection): Call into the filter now.
(WebKit::EditorClient::handleInputMethodKeyboardEvent): Added this helper which executes
any pending composition confirmation or preedit update actions as the default action of
the keydown event.
(WebKit::EditorClient::handleKeyboardEvent): Call handleInputMethodKeyboardEvent to do
any pending composition action.
(WebKit::EditorClient::handleInputMethodKeydown): Remove all the logic from this method.
Keys are filtered before they are sent to WebCore now and the actual action of input method
events happens in the keydown default action to increase compatibility with other browsers.
(WebKit::EditorClient::EditorClient): Remove context signal management.
(WebKit::EditorClient::~EditorClient): Ditto.

  • WebCoreSupport/EditorClientGtk.h:

(EditorClient): No longer has some members that tracked IME status.

  • WebCoreSupport/WebViewInputMethodFilter.cpp: Added.
  • WebCoreSupport/WebViewInputMethodFilter.h: Added.
  • webkit/webkitwebview.cpp:

(webkit_web_view_get_property): Get the context from the filter now.
(webkit_web_view_key_press_event): Just send events straight to the filter.
The filter will decide whether or not to send them to WebCore.
(webkit_web_view_key_release_event): Ditto.
(webkit_web_view_button_press_event): Use the filter to handle button press
events related to IME.
(webkit_web_view_focus_in_event): Notify the filter now.
(webkit_web_view_focus_out_event): Ditto.
(webkit_web_view_realize): The filter takes care of listening for realize now.
(webkit_web_view_init): Set the WebView widget on the filter.

  • webkit/webkitwebviewprivate.h: Change the GtkIMContext member to be a GtkInputMethodFilter member.
Location:
trunk/Source
Files:
5 added
11 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r116113 r116114  
     12012-05-03  Martin Robinson  <mrobinson@igalia.com>
     2
     3        [GTK] Rework IME handling to fix bugs and prepare for WebKit2
     4        https://bugs.webkit.org/show_bug.cgi?id=84556
     5
     6        Reviewed by Gustavo Noronha Silva.
     7
     8        No new tests. This change is already covered by a suite of keyboard
     9        handling unit tests in WebKitGTK+. There are some changes in behavior,
     10        but they are difficult to test without mocking out an entire GtkIMContext.
     11
     12        Add a struct, CompositionResults, which is used by PlatformKeyboardEvent
     13        to package composition information with a keyboard event. Also add some logic
     14        to PlatformKeyboardEvent to give the right information when it has composition
     15        results.
     16
     17        * GNUmakefile.list.am: Added new sources to the list.
     18        * platform/PlatformKeyboardEvent.h:  Added a new CompositionResults member,
     19        getter, and argument to the constructor.
     20        * platform/gtk/CompositionResults.h: Added.
     21        * platform/gtk/GtkInputMethodFilter.cpp: Added.
     22        * platform/gtk/GtkInputMethodFilter.h: Added.
     23        * platform/gtk/PlatformKeyboardEventGtk.cpp:
     24        (WebCore::PlatformKeyboardEvent::windowsKeyCodeForGdkKeyCode): When
     25        the key value is void return the VK_PROCESS keycode, which is the keycode
     26        that web content expects with keystrokes that trigger composition events.
     27        (WebCore::eventTypeForGdkKeyEvent): Abstract out this helper.
     28        (WebCore::modifiersForGdkKeyEvent): Abstract out this helper.
     29        (WebCore::PlatformKeyboardEvent::PlatformKeyboardEvent): When a PlatformKeyEvent
     30        has composition results, use VK_PROCESS as the keycode for this event.
     31        (WebCore::PlatformKeyboardEvent::disambiguateKeyDownEvent): When this event is
     32        transformed into a Char event, the PlatformKeyboardEvent used for DOM keypress
     33        events, and it has composition results clear the text members. This forces the
     34        EventHandler code to drop the keypress event. Platform events that change the
     35        composition states do not have corresponding keypress DOM events (only keydown
     36        and keyup events), so this is necessary to ensure web compatibility.
     37
    1382012-05-04  Jochen Eisinger  <jochen@chromium.org>
    239
  • trunk/Source/WebCore/GNUmakefile.list.am

    r115943 r116114  
    47104710        Source/WebCore/platform/gtk/GtkDragAndDropHelper.cpp \
    47114711        Source/WebCore/platform/gtk/GtkDragAndDropHelper.h \
     4712        Source/WebCore/platform/gtk/GtkInputMethodFilter.cpp \
     4713        Source/WebCore/platform/gtk/GtkInputMethodFilter.h \
    47124714        Source/WebCore/platform/gtk/GtkUtilities.cpp \
    47134715        Source/WebCore/platform/gtk/GtkUtilities.h \
  • trunk/Source/WebCore/platform/PlatformKeyboardEvent.h

    r111191 r116114  
    4444#if PLATFORM(GTK)
    4545typedef struct _GdkEventKey GdkEventKey;
     46#include "CompositionResults.h"
    4647#endif
    4748
     
    152153
    153154#if PLATFORM(GTK)
    154         PlatformKeyboardEvent(GdkEventKey*);
    155         GdkEventKey* gdkEventKey() const;
     155        PlatformKeyboardEvent(GdkEventKey*, const CompositionResults&);
     156        GdkEventKey* gdkEventKey() const { return m_gdkEventKey; }
     157        const CompositionResults& compositionResults() const { return m_compositionResults; }
    156158
    157159        // Used by WebKit2
     
    201203#if PLATFORM(GTK)
    202204        GdkEventKey* m_gdkEventKey;
     205        CompositionResults m_compositionResults;
    203206#endif
    204207#if PLATFORM(QT)
  • trunk/Source/WebCore/platform/gtk/PlatformKeyboardEventGtk.cpp

    r103643 r116114  
    511511        case GDK_F24:
    512512            return VK_F1 + (keycode - GDK_F1);
    513 
     513        case GDK_KEY_VoidSymbol:
     514            return VK_PROCESSKEY;
    514515        default:
    515516            return 0;
     
    546547}
    547548
     549static PlatformEvent::Type eventTypeForGdkKeyEvent(GdkEventKey* event)
     550{
     551    return event->type == GDK_KEY_RELEASE ? PlatformEvent::KeyUp : PlatformEvent::KeyDown;
     552}
     553
     554static PlatformEvent::Modifiers modifiersForGdkKeyEvent(GdkEventKey* event)
     555{
     556    unsigned int modifiers = 0;
     557    if (event->state & GDK_SHIFT_MASK || event->keyval == GDK_3270_BackTab)
     558        modifiers |= PlatformEvent::ShiftKey;
     559    if (event->state & GDK_CONTROL_MASK)
     560        modifiers |= PlatformEvent::CtrlKey;
     561    if (event->state & GDK_MOD1_MASK)
     562        modifiers |= PlatformEvent::AltKey;
     563    if (event->state & GDK_META_MASK)
     564        modifiers |= PlatformEvent::MetaKey;
     565    return static_cast<PlatformEvent::Modifiers>(modifiers);
     566}
     567
    548568// Keep this in sync with the other platform event constructors
    549 // TODO: m_gdkEventKey should be refcounted
    550 PlatformKeyboardEvent::PlatformKeyboardEvent(GdkEventKey* event)
    551     : PlatformEvent((event->type == GDK_KEY_RELEASE) ? PlatformEvent::KeyUp : PlatformEvent::KeyDown, (event->state & GDK_SHIFT_MASK) || (event->keyval == GDK_3270_BackTab), event->state & GDK_CONTROL_MASK, event->state & GDK_MOD1_MASK, event->state & GDK_META_MASK, currentTime())
    552     , m_text(singleCharacterString(event->keyval))
    553     , m_unmodifiedText(singleCharacterString(event->keyval))
     569PlatformKeyboardEvent::PlatformKeyboardEvent(GdkEventKey* event, const CompositionResults& compositionResults)
     570    : PlatformEvent(eventTypeForGdkKeyEvent(event), modifiersForGdkKeyEvent(event), currentTime())
     571    , m_text(compositionResults.simpleString.length() ? compositionResults.simpleString : singleCharacterString(event->keyval))
     572    , m_unmodifiedText(m_text)
    554573    , m_keyIdentifier(keyIdentifierForGdkKeyCode(event->keyval))
    555574    , m_windowsVirtualKeyCode(windowsKeyCodeForGdkKeyCode(event->keyval))
     
    560579    , m_isSystemKey(false)
    561580    , m_gdkEventKey(event)
    562 {
     581    , m_compositionResults(compositionResults)
     582{
     583    // To match the behavior of IE, we return VK_PROCESSKEY for keys that triggered composition results.
     584    if (compositionResults.compositionUpdated())
     585        m_windowsVirtualKeyCode = VK_PROCESSKEY;
    563586}
    564587
     
    573596
    574597    if (type == PlatformEvent::RawKeyDown) {
     598        m_text = String();
     599        m_unmodifiedText = String();
     600    } else if (type == PlatformEvent::Char && m_compositionResults.compositionUpdated()) {
     601        // Having empty text, prevents this Char (which is a DOM keypress) event
     602        // from going to the DOM. Keys that trigger composition events should not
     603        // fire keypress.
    575604        m_text = String();
    576605        m_unmodifiedText = String();
     
    597626}
    598627
    599 GdkEventKey* PlatformKeyboardEvent::gdkEventKey() const
    600 {
    601     return m_gdkEventKey;
    602 }
    603 
    604 }
     628}
  • trunk/Source/WebKit/gtk/ChangeLog

    r115998 r116114  
     12012-05-03  Martin Robinson  <mrobinson@igalia.com>
     2
     3        [GTK] Rework IME handling to fix bugs and prepare for WebKit2
     4        https://bugs.webkit.org/show_bug.cgi?id=84556
     5
     6        Reviewed by Gustavo Noronha Silva.
     7
     8        Rework input method handling logic into a class called GtkInputMethodFilter.
     9        This filter now runs before WebCore event handling, allowing the code to more
     10        easily fake simple compositions that should be seen as keystrokes. We can also
     11        filter keypresses that should not go to web content at all, such as key up events
     12        related to key down events that were filtered.
     13
     14        Also added is a WebViewInputMethodFilter which is a concrete implementation of
     15        GtkInputMethodFilter. This class contains logic for actually sending events to
     16        WebCore. In WebKit2 an implementation of GtkInputMethodFilter will send events
     17        across the IPC channel.
     18
     19        * GNUmakefile.am: Add new files to the source list.
     20        * WebCoreSupport/ContextMenuClientGtk.cpp:
     21        (WebKit::inputMethodsMenuItem): Access the input method context via the filter.
     22        * WebCoreSupport/EditorClientGtk.cpp: Remove the tricky logic of input method
     23        events from this class, because it's now in the GtkInputMethodFilter.
     24        (WebKit::EditorClient::setInputMethodState): Call into the filter.
     25        (WebKit::EditorClient::shouldBeginEditing): We no longer need to update the composition here.
     26        This is handled by the focus in and focus out logic in the filter.
     27        (WebKit::EditorClient::shouldEndEditing): Ditto.
     28        (WebKit::EditorClient::respondToChangedSelection): Call into the filter now.
     29        (WebKit::EditorClient::handleInputMethodKeyboardEvent): Added this helper which executes
     30        any pending composition confirmation or preedit update actions as the default action of
     31        the keydown event.
     32        (WebKit::EditorClient::handleKeyboardEvent): Call handleInputMethodKeyboardEvent to do
     33        any pending composition action.
     34        (WebKit::EditorClient::handleInputMethodKeydown): Remove all the logic from this method.
     35        Keys are filtered before they are sent to WebCore now and the actual action of input method
     36        events happens in the keydown default action to increase compatibility with other browsers.
     37        (WebKit::EditorClient::EditorClient): Remove context signal management.
     38        (WebKit::EditorClient::~EditorClient): Ditto.
     39        * WebCoreSupport/EditorClientGtk.h:
     40        (EditorClient): No longer has some members that tracked IME status.
     41        * WebCoreSupport/WebViewInputMethodFilter.cpp: Added.
     42        * WebCoreSupport/WebViewInputMethodFilter.h: Added.
     43        * webkit/webkitwebview.cpp:
     44        (webkit_web_view_get_property): Get the context from the filter now.
     45        (webkit_web_view_key_press_event): Just send events straight to the filter.
     46        The filter will decide whether or not to send them to WebCore.
     47        (webkit_web_view_key_release_event): Ditto.
     48        (webkit_web_view_button_press_event): Use the filter to handle button press
     49        events related to IME.
     50        (webkit_web_view_focus_in_event): Notify the filter now.
     51        (webkit_web_view_focus_out_event): Ditto.
     52        (webkit_web_view_realize): The filter takes care of listening for realize now.
     53        (webkit_web_view_init): Set the WebView widget on the filter.
     54        * webkit/webkitwebviewprivate.h: Change the GtkIMContext member to be a GtkInputMethodFilter member.
     55
    1562012-05-03  Fady Samuel  <fsamuel@chromium.org>
    257
  • trunk/Source/WebKit/gtk/GNUmakefile.am

    r114865 r116114  
    213213        Source/WebKit/gtk/WebCoreSupport/UserMediaClientGtk.cpp \
    214214        Source/WebKit/gtk/WebCoreSupport/UserMediaClientGtk.h \
     215        Source/WebKit/gtk/WebCoreSupport/WebViewInputMethodFilter.cpp \
     216        Source/WebKit/gtk/WebCoreSupport/WebViewInputMethodFilter.h \
    215217        Source/WebKit/gtk/webkit/webkitapplicationcache.cpp \
    216218        Source/WebKit/gtk/webkit/webkitdownload.cpp \
  • trunk/Source/WebKit/gtk/WebCoreSupport/ContextMenuClientGtk.cpp

    r113147 r116114  
    6565    WebKitWebViewPrivate* priv = webView->priv;
    6666    ContextMenu imContextMenu;
    67     gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(priv->imContext.get()), GTK_MENU_SHELL(imContextMenu.platformDescription()));
     67    gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(priv->imFilter.context()), GTK_MENU_SHELL(imContextMenu.platformDescription()));
    6868
    6969    ContextMenuItem menuItem(ActionType, ContextMenuItemTagInputMethods, contextMenuItemTagInputMethods(), &imContextMenu);
  • trunk/Source/WebKit/gtk/WebCoreSupport/EditorClientGtk.cpp

    r114865 r116114  
    6363namespace WebKit {
    6464
    65 static void imContextCommitted(GtkIMContext* context, const gchar* compositionString, EditorClient* client)
    66 {
    67     Frame* frame = core(static_cast<WebKitWebView*>(client->webView()))->focusController()->focusedOrMainFrame();
    68     if (!frame || !frame->editor()->canEdit())
    69         return;
    70 
    71     // If this signal fires during a keydown event when we are not in the middle
    72     // of a composition, then treat this 'commit' as a normal key event and just
    73     // change the editable area right before the keypress event.
    74     if (client->treatContextCommitAsKeyEvent()) {
    75         client->updatePendingComposition(compositionString);
    76         return;
    77     }
    78 
    79     // If this signal fires during a mousepress event when we are in the middle
    80     // of a composition, skip this 'commit' because the composition is already confirmed.
    81     if (client->preventNextCompositionCommit())
    82         return;
    83  
    84     frame->editor()->confirmComposition(String::fromUTF8(compositionString));
    85     client->clearPendingComposition();
    86 }
    87 
    88 static void imContextPreeditChanged(GtkIMContext* context, EditorClient* client)
    89 {
    90     Frame* frame = core(static_cast<WebKitWebView*>(client->webView()))->focusController()->focusedOrMainFrame();
    91     if (!frame || !frame->editor()->canEdit())
    92         return;
    93 
    94     // We ignore the provided PangoAttrList for now.
    95     GOwnPtr<gchar> newPreedit(0);
    96     gtk_im_context_get_preedit_string(context, &newPreedit.outPtr(), 0, 0);
    97 
    98     String preeditString = String::fromUTF8(newPreedit.get());
    99     Vector<CompositionUnderline> underlines;
    100     underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
    101     frame->editor()->setComposition(preeditString, underlines, 0, 0);
    102 }
    103 
    104 
    105 void EditorClient::updatePendingComposition(const gchar* newComposition)
    106 {
    107     // The IMContext may signal more than one completed composition in a row,
    108     // in which case we want to append them, rather than overwrite the old one.
    109     if (!m_pendingComposition)
    110         m_pendingComposition.set(g_strdup(newComposition));
    111     else
    112         m_pendingComposition.set(g_strconcat(m_pendingComposition.get(), newComposition, NULL));
    113 }
    114 
    11565void EditorClient::willSetInputMethodState()
    11666{
     
    11969void EditorClient::setInputMethodState(bool active)
    12070{
    121     WebKitWebViewPrivate* priv = m_webView->priv;
    122 
    123     if (active)
    124         gtk_im_context_focus_in(priv->imContext.get());
    125     else
    126         gtk_im_context_focus_out(priv->imContext.get());
     71    m_webView->priv->imFilter.setEnabled(active);
    12772}
    12873
     
    182127bool EditorClient::shouldBeginEditing(WebCore::Range* range)
    183128{
    184     clearPendingComposition();
    185 
    186129    gboolean accept = TRUE;
    187130    GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range)));
     
    192135bool EditorClient::shouldEndEditing(WebCore::Range* range)
    193136{
    194     clearPendingComposition();
    195 
    196137    gboolean accept = TRUE;
    197138    GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range)));
     
    318259        return;
    319260
    320     if (frame->editor()->ignoreCompositionSelectionChange())
    321         return;
    322 
    323261#if PLATFORM(X11)
    324262    setSelectionPrimaryClipboardIfNeeded(m_webView);
    325263#endif
    326264
    327     if (!frame->editor()->hasComposition())
     265    if (m_updatingComposition || !frame->editor()->hasComposition() || frame->editor()->ignoreCompositionSelectionChange())
    328266        return;
    329267
    330268    unsigned start;
    331269    unsigned end;
    332     WebKitWebViewPrivate* priv = m_webView->priv;
    333 
    334     if (!frame->editor()->getCompositionSelection(start, end)) {
    335         // gtk_im_context_reset() clears the composition for us.
    336         gtk_im_context_reset(priv->imContext.get());
    337         frame->editor()->cancelComposition();
    338     }
     270    if (!frame->editor()->getCompositionSelection(start, end))
     271        m_webView->priv->imFilter.resetContext();
    339272}
    340273
     
    483416
    484417    m_pendingEditorCommands.clear();
    485 
    486     // If we successfully completed all editor commands, then
    487     // this signals a canceling of the composition.
    488     if (success)
    489         clearPendingComposition();
    490 
    491418    return success;
     419}
     420
     421bool EditorClient::handleInputMethodKeyboardEvent(KeyboardEvent* event)
     422{
     423    if (event->type() != eventNames().keydownEvent)
     424        return false;
     425
     426    const PlatformKeyboardEvent* platformEvent = event->keyEvent();
     427    if (!platformEvent)
     428        return false;
     429
     430    Frame* frame = core(m_webView)->focusController()->focusedOrMainFrame();
     431    if (!frame || !frame->editor()->canEdit())
     432        return false;
     433
     434    const CompositionResults& compositionResults = platformEvent->compositionResults();
     435    if (!compositionResults.compositionUpdated())
     436        return false;
     437
     438    m_updatingComposition = true;
     439
     440    // This won't prevent a keypress event alone, but PlatformKeyboardEvent returns
     441    // an empty string when there are composition results. That prevents the delivery
     442    // of keypress, which is the behavior we want for composition events. See
     443    // EventHandler::keyEvent.
     444    event->preventDefault();
     445    ASSERT(platformEvent->string().isNull());
     446
     447    if (!compositionResults.confirmedComposition.isNull())
     448        frame->editor()->confirmComposition(compositionResults.confirmedComposition);
     449
     450    String preedit = compositionResults.preedit;
     451    if (!preedit.isNull()) {
     452        Vector<CompositionUnderline> underlines;
     453        underlines.append(CompositionUnderline(0, preedit.length(), Color(1, 1, 1), false));
     454        frame->editor()->setComposition(preedit, underlines, compositionResults.preeditCursorOffset, compositionResults.preeditCursorOffset);
     455    }
     456
     457    m_updatingComposition = false;
     458    return true;
    492459}
    493460
     
    501468    const PlatformKeyboardEvent* platformEvent = event->keyEvent();
    502469    if (!platformEvent)
     470        return;
     471
     472    if (handleInputMethodKeyboardEvent(event))
    503473        return;
    504474
     
    532502    // until a keypress event happens. This will ensure that the insertion will not
    533503    // be reflected in the contents of the field until the keyup DOM event.
    534     if (event->type() == eventNames().keypressEvent) {
    535 
    536         // If we have a pending composition at this point, it happened while
    537         // filtering a keypress, so we treat it as a normal text insertion.
    538         // This will also ensure that if the keypress event handler changed the
    539         // currently focused node, the text is still inserted into the original
    540         // node (insertText() has this logic, but confirmComposition() does not).
    541         if (m_pendingComposition) {
    542             frame->editor()->insertText(String::fromUTF8(m_pendingComposition.get()), event);
    543             clearPendingComposition();
    544             event->setDefaultHandled();
    545 
    546         } else {
    547             // Don't insert null or control characters as they can result in unexpected behaviour
    548             if (event->charCode() < ' ')
    549                 return;
    550 
    551             // Don't insert anything if a modifier is pressed
    552             if (platformEvent->ctrlKey() || platformEvent->altKey())
    553                 return;
    554 
    555             if (frame->editor()->insertText(platformEvent->text(), event))
    556                 event->setDefaultHandled();
    557         }
    558     }
     504    if (event->type() != eventNames().keypressEvent)
     505        return;
     506
     507    // Don't insert null or control characters as they can result in unexpected behaviour
     508    if (event->charCode() < ' ')
     509        return;
     510
     511    // Don't insert anything if a modifier is pressed
     512    if (platformEvent->ctrlKey() || platformEvent->altKey())
     513        return;
     514
     515    if (frame->editor()->insertText(platformEvent->text(), event))
     516        event->setDefaultHandled();
    559517}
    560518
    561519void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
    562520{
    563     Frame* targetFrame = core(m_webView)->focusController()->focusedOrMainFrame();
    564     if (!targetFrame || !targetFrame->editor()->canEdit())
    565         return;
    566 
    567     WebKitWebViewPrivate* priv = m_webView->priv;
    568 
    569     m_preventNextCompositionCommit = false;
    570 
    571     // Some IM contexts (e.g. 'simple') will act as if they filter every
    572     // keystroke and just issue a 'commit' signal during handling. In situations
    573     // where the 'commit' signal happens during filtering and there is no active
    574     // composition, act as if the keystroke was not filtered. The one exception to
    575     // this is when the keyval parameter of the GdkKeyEvent is 0, which is often
    576     // a key event sent by the IM context for committing the current composition.
    577 
    578     // Here is a typical sequence of events for the 'simple' context:
    579     // 1. GDK key press event -> webkit_web_view_key_press_event
    580     // 2. Keydown event -> EditorClient::handleInputMethodKeydown
    581     //     gtk_im_context_filter_keypress returns true, but there is a pending
    582     //     composition so event->preventDefault is not called (below).
    583     // 3. Keydown event bubbles through the DOM
    584     // 4. Keydown event -> EditorClient::handleKeyboardEvent
    585     //     No action taken.
    586     // 4. GDK key release event -> webkit_web_view_key_release_event
    587     // 5. gtk_im_context_filter_keypress is called on the release event.
    588     //     Simple does not filter most key releases, so the event continues.
    589     // 6. Keypress event bubbles through the DOM.
    590     // 7. Keypress event -> EditorClient::handleKeyboardEvent
    591     //     pending composition is inserted.
    592     // 8. Keyup event bubbles through the DOM.
    593     // 9. Keyup event -> EditorClient::handleKeyboardEvent
    594     //     No action taken.
    595 
    596     // There are two situations where we do filter the keystroke:
    597     // 1. The IMContext instructed us to filter and we have no pending composition.
    598     // 2. The IMContext did not instruct us to filter, but the keystroke caused a
    599     //    composition in progress to finish. It seems that sometimes SCIM will finish
    600     //    a composition and not mark the keystroke as filtered.
    601     m_treatContextCommitAsKeyEvent = (!targetFrame->editor()->hasComposition())
    602          && event->keyEvent()->gdkEventKey()->keyval;
    603     clearPendingComposition();
    604     if ((gtk_im_context_filter_keypress(priv->imContext.get(), event->keyEvent()->gdkEventKey()) && !m_pendingComposition)
    605         || (!m_treatContextCommitAsKeyEvent && !targetFrame->editor()->hasComposition()))
    606         event->preventDefault();
    607 
    608     m_treatContextCommitAsKeyEvent = false;
    609 }
    610 
    611 void EditorClient::handleInputMethodMousePress()
    612 {
    613     Frame* targetFrame = core(m_webView)->focusController()->focusedOrMainFrame();
    614 
    615     if (!targetFrame || !targetFrame->editor()->canEdit())
    616         return;
    617 
    618     WebKitWebViewPrivate* priv = m_webView->priv;
    619 
    620     // When a mouse press fires, the commit signal happens during a composition.
    621     // In this case, if the focused node is changed, the commit signal happens in a diffrent node.
    622     // Therefore, we need to confirm the current compositon and ignore the next commit signal.
    623     GOwnPtr<gchar> newPreedit(0);
    624     gtk_im_context_get_preedit_string(priv->imContext.get(), &newPreedit.outPtr(), 0, 0);
    625    
    626     if (g_utf8_strlen(newPreedit.get(), -1)) {
    627         targetFrame->editor()->confirmComposition();
    628         m_preventNextCompositionCommit = true;
    629         gtk_im_context_reset(priv->imContext.get());
    630     }
     521    // Input method results are handled in handleKeyboardEvent, so that we can wait
     522    // to trigger composition updates until after the keydown event handler. This better
     523    // matches other browsers.
    631524}
    632525
     
    637530#endif
    638531    , m_webView(webView)
    639     , m_preventNextCompositionCommit(false)
    640     , m_treatContextCommitAsKeyEvent(false)
    641532    , m_smartInsertDeleteEnabled(false)
    642 {
    643     WebKitWebViewPrivate* priv = m_webView->priv;
    644     g_signal_connect(priv->imContext.get(), "commit", G_CALLBACK(imContextCommitted), this);
    645     g_signal_connect(priv->imContext.get(), "preedit-changed", G_CALLBACK(imContextPreeditChanged), this);
     533    , m_updatingComposition(false)
     534{
    646535}
    647536
    648537EditorClient::~EditorClient()
    649538{
    650     WebKitWebViewPrivate* priv = m_webView->priv;
    651     g_signal_handlers_disconnect_by_func(priv->imContext.get(), (gpointer)imContextCommitted, this);
    652     g_signal_handlers_disconnect_by_func(priv->imContext.get(), (gpointer)imContextPreeditChanged, this);
    653539}
    654540
  • trunk/Source/WebKit/gtk/WebCoreSupport/EditorClientGtk.h

    r110865 r116114  
    6666        ~EditorClient();
    6767        WebKitWebView* webView() { return m_webView; }
    68         bool treatContextCommitAsKeyEvent() { return m_treatContextCommitAsKeyEvent; }
    69         bool preventNextCompositionCommit() { return m_preventNextCompositionCommit; }
    70         void clearPendingComposition() { m_pendingComposition.set(0); }
    71         bool hasPendingComposition() { return m_pendingComposition; }
    7268        void addPendingEditorCommand(const char* command) { m_pendingEditorCommands.append(command); }
    73         void updatePendingComposition(const char*);
    7469        void generateEditorCommands(const WebCore::KeyboardEvent*);
    7570        bool executePendingEditorCommands(WebCore::Frame*, bool);
     
    120115        virtual void handleKeyboardEvent(WebCore::KeyboardEvent*);
    121116        virtual void handleInputMethodKeydown(WebCore::KeyboardEvent*);
    122         virtual void handleInputMethodMousePress();
    123117
    124118        virtual void textFieldDidBeginEditing(WebCore::Element*);
     
    141135
    142136    private:
     137        bool handleInputMethodKeyboardEvent(WebCore::KeyboardEvent*);
     138
    143139#if ENABLE(SPELLCHECK)
    144140        TextCheckerClientGtk m_textCheckerClient;
     
    147143#endif
    148144        WebKitWebView* m_webView;
    149         bool m_preventNextCompositionCommit;
    150         bool m_treatContextCommitAsKeyEvent;
    151         GOwnPtr<gchar> m_pendingComposition;
    152 
    153145        WebCore::KeyBindingTranslator m_keyBindingTranslator;
    154146        Vector<WTF::String> m_pendingEditorCommands;
    155 
    156147        bool m_smartInsertDeleteEnabled;
     148        bool m_updatingComposition;
    157149    };
    158150}
  • trunk/Source/WebKit/gtk/webkit/webkitwebview.cpp

    r115672 r116114  
    274274static void webkit_web_view_set_window_features(WebKitWebView* webView, WebKitWebWindowFeatures* webWindowFeatures);
    275275
    276 static GtkIMContext* webkit_web_view_get_im_context(WebKitWebView*);
    277 
    278276#if ENABLE(CONTEXT_MENUS)
    279277static void PopupMenuPositionFunc(GtkMenu* menu, gint *x, gint *y, gboolean *pushIn, gpointer userData)
     
    582580        break;
    583581    case PROP_IM_CONTEXT:
    584         g_value_set_object(value, webkit_web_view_get_im_context(webView));
     582        g_value_set_object(value, webView->priv->imFilter.context());
    585583        break;
    586584    case PROP_VIEW_MODE:
     
    711709static gboolean webkit_web_view_key_press_event(GtkWidget* widget, GdkEventKey* event)
    712710{
    713     WebKitWebView* webView = WEBKIT_WEB_VIEW(widget);
    714 
    715     Frame* frame = core(webView)->focusController()->focusedOrMainFrame();
    716     PlatformKeyboardEvent keyboardEvent(event);
    717 
    718     if (!frame->view())
    719         return FALSE;
    720 
    721     if (frame->eventHandler()->keyEvent(keyboardEvent))
     711    if (WEBKIT_WEB_VIEW(widget)->priv->imFilter.filterKeyEvent(event))
    722712        return TRUE;
    723 
    724     /* Chain up to our parent class for binding activation */
    725713    return GTK_WIDGET_CLASS(webkit_web_view_parent_class)->key_press_event(widget, event);
    726714}
     
    728716static gboolean webkit_web_view_key_release_event(GtkWidget* widget, GdkEventKey* event)
    729717{
    730     WebKitWebView* webView = WEBKIT_WEB_VIEW(widget);
    731 
    732     // GTK+ IM contexts often require us to filter key release events, which
    733     // WebCore does not do by default, so we filter the event here. We only block
    734     // the event if we don't have a pending composition, because that means we
    735     // are using a context like 'simple' which marks every keystroke as filtered.
    736     WebKit::EditorClient* client = static_cast<WebKit::EditorClient*>(core(webView)->editorClient());
    737     if (gtk_im_context_filter_keypress(webView->priv->imContext.get(), event) && !client->hasPendingComposition())
     718    if (WEBKIT_WEB_VIEW(widget)->priv->imFilter.filterKeyEvent(event))
    738719        return TRUE;
    739 
    740     Frame* frame = core(webView)->focusController()->focusedOrMainFrame();
    741     if (!frame->view())
    742         return FALSE;
    743 
    744     PlatformKeyboardEvent keyboardEvent(event);
    745     if (frame->eventHandler()->keyEvent(keyboardEvent))
    746         return TRUE;
    747 
    748     /* Chain up to our parent class for binding activation */
    749720    return GTK_WIDGET_CLASS(webkit_web_view_parent_class)->key_release_event(widget, event);
    750721}
     
    774745        return FALSE;
    775746
     747    priv->imFilter.notifyMouseButtonPress();
    776748    gboolean result = frame->eventHandler()->handleMousePressEvent(platformEvent);
    777     // Handle the IM context when a mouse press fires
    778     static_cast<WebKit::EditorClient*>(core(webView)->editorClient())->handleInputMethodMousePress();
    779749
    780750#if PLATFORM(X11)
     
    965935    // http://bugs.webkit.org/show_bug.cgi?id=16910
    966936    GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
    967     if (widgetIsOnscreenToplevelWindow(toplevel) && gtk_window_has_toplevel_focus(GTK_WINDOW(toplevel))) {
    968         WebKitWebView* webView = WEBKIT_WEB_VIEW(widget);
    969         FocusController* focusController = core(webView)->focusController();
    970 
    971         focusController->setActive(true);
    972 
    973         if (focusController->focusedFrame())
    974             focusController->setFocused(true);
    975         else
    976             focusController->setFocusedFrame(core(webView)->mainFrame());
    977 
    978         if (focusController->focusedFrame()->editor()->canEdit())
    979             gtk_im_context_focus_in(webView->priv->imContext.get());
    980     }
     937    if (!widgetIsOnscreenToplevelWindow(toplevel) || !gtk_window_has_toplevel_focus(GTK_WINDOW(toplevel)))
     938        return GTK_WIDGET_CLASS(webkit_web_view_parent_class)->focus_in_event(widget, event);
     939
     940    WebKitWebView* webView = WEBKIT_WEB_VIEW(widget);
     941    FocusController* focusController = core(webView)->focusController();
     942
     943    focusController->setActive(true);
     944    if (focusController->focusedFrame())
     945        focusController->setFocused(true);
     946    else
     947        focusController->setFocusedFrame(core(webView)->mainFrame());
     948
     949    if (focusController->focusedFrame()->editor()->canEdit())
     950        webView->priv->imFilter.notifyFocusedIn();
    981951    return GTK_WIDGET_CLASS(webkit_web_view_parent_class)->focus_in_event(widget, event);
    982952}
     
    988958    // We may hit this code while destroying the widget, and we might
    989959    // no longer have a page, then.
    990     Page* page = core(webView);
    991     if (page) {
     960    if (Page* page = core(webView)) {
    992961        page->focusController()->setActive(false);
    993962        page->focusController()->setFocused(false);
    994963    }
    995964
    996     if (webView->priv->imContext)
    997         gtk_im_context_focus_out(webView->priv->imContext.get());
    998 
     965    webView->priv->imFilter.notifyFocusedOut();
    999966    return GTK_WIDGET_CLASS(webkit_web_view_parent_class)->focus_out_event(widget, event);
    1000967}
     
    1002969static void webkit_web_view_realize(GtkWidget* widget)
    1003970{
    1004     WebKitWebViewPrivate* priv = WEBKIT_WEB_VIEW(widget)->priv;
    1005 
    1006971    gtk_widget_set_realized(widget, TRUE);
    1007972
     
    10591024    gtk_style_context_set_background(gtk_widget_get_style_context(widget), window);
    10601025#endif
    1061 
    1062     gtk_im_context_set_client_window(priv->imContext.get(), window);
    10631026}
    10641027
     
    16411604}
    16421605#endif
    1643 
    1644 static GtkIMContext* webkit_web_view_get_im_context(WebKitWebView* webView)
    1645 {
    1646     g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);
    1647     return GTK_IM_CONTEXT(webView->priv->imContext.get());
    1648 }
    16491606
    16501607static void webkit_web_view_class_init(WebKitWebViewClass* webViewClass)
     
    35653522    new (priv) WebKitWebViewPrivate();
    35663523
    3567     priv->imContext = adoptGRef(gtk_im_multicontext_new());
     3524    priv->imFilter.setWebView(webView);
    35683525
    35693526    Page::PageClients pageClients;
  • trunk/Source/WebKit/gtk/webkit/webkitwebviewprivate.h

    r111696 r116114  
    3030#include "Page.h"
    3131#include "ResourceHandle.h"
     32#include "WebViewInputMethodFilter.h"
    3233#include "WidgetBackingStore.h"
    3334#include <webkit/webkitwebview.h>
     
    6061
    6162    HashSet<GtkWidget*> children;
    62     GRefPtr<GtkIMContext> imContext;
     63    WebKit::WebViewInputMethodFilter imFilter;
    6364
    6465    gboolean transparent;
Note: See TracChangeset for help on using the changeset viewer.