Home | History | Annotate | Download | only in renderer_host
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/renderer_host/gtk_im_context_wrapper.h"
      6 
      7 #include <gdk/gdk.h>
      8 #include <gdk/gdkkeysyms.h>
      9 #include <gtk/gtk.h>
     10 
     11 #include <algorithm>
     12 
     13 #include "base/logging.h"
     14 #include "base/string_util.h"
     15 #include "base/third_party/icu/icu_utf.h"
     16 #include "base/utf_string_conversions.h"
     17 #include "chrome/app/chrome_command_ids.h"
     18 #include "chrome/browser/ui/gtk/gtk_util.h"
     19 #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h"
     20 #include "chrome/common/render_messages.h"
     21 #include "content/browser/renderer_host/render_widget_host.h"
     22 #include "content/common/native_web_keyboard_event.h"
     23 #include "grit/generated_resources.h"
     24 #include "third_party/skia/include/core/SkColor.h"
     25 #include "third_party/WebKit/Source/WebKit/chromium/public/WebCompositionUnderline.h"
     26 #include "ui/base/gtk/gtk_im_context_util.h"
     27 #include "ui/base/l10n/l10n_util.h"
     28 #include "ui/gfx/gtk_util.h"
     29 #include "ui/gfx/rect.h"
     30 
     31 #if !defined(TOOLKIT_VIEWS)
     32 #include "chrome/browser/ui/gtk/menu_gtk.h"
     33 #endif
     34 
     35 namespace {
     36 // Copied from third_party/WebKit/Source/WebCore/page/EventHandler.cpp
     37 //
     38 // Match key code of composition keydown event on windows.
     39 // IE sends VK_PROCESSKEY which has value 229;
     40 //
     41 // Please refer to following documents for detals:
     42 // - Virtual-Key Codes
     43 //   http://msdn.microsoft.com/en-us/library/ms645540(VS.85).aspx
     44 // - How the IME System Works
     45 //   http://msdn.microsoft.com/en-us/library/cc194848.aspx
     46 // - ImmGetVirtualKey Function
     47 //   http://msdn.microsoft.com/en-us/library/dd318570(VS.85).aspx
     48 const int kCompositionEventKeyCode = 229;
     49 }  // namespace
     50 
     51 // ui::CompositionUnderline should be identical to
     52 // WebKit::WebCompositionUnderline, so that we can do reinterpret_cast safely.
     53 // TODO(suzhe): remove it after migrating all code in chrome to use
     54 // ui::CompositionUnderline.
     55 COMPILE_ASSERT(sizeof(ui::CompositionUnderline) ==
     56                sizeof(WebKit::WebCompositionUnderline),
     57                ui_CompositionUnderline__WebKit_WebCompositionUnderline_diff);
     58 
     59 GtkIMContextWrapper::GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view)
     60     : host_view_(host_view),
     61       context_(gtk_im_multicontext_new()),
     62       context_simple_(gtk_im_context_simple_new()),
     63       is_focused_(false),
     64       is_composing_text_(false),
     65       is_enabled_(false),
     66       is_in_key_event_handler_(false),
     67       is_composition_changed_(false),
     68       suppress_next_commit_(false),
     69       last_key_code_(0),
     70       last_key_was_up_(false),
     71       last_key_filtered_no_result_(false) {
     72   DCHECK(context_);
     73   DCHECK(context_simple_);
     74 
     75   // context_ and context_simple_ share the same callback handlers.
     76   // All data come from them are treated equally.
     77   // context_ is for full input method support.
     78   // context_simple_ is for supporting dead/compose keys when input method is
     79   // disabled by webkit, eg. in password input box.
     80   g_signal_connect(context_, "preedit_start",
     81                    G_CALLBACK(HandlePreeditStartThunk), this);
     82   g_signal_connect(context_, "preedit_end",
     83                    G_CALLBACK(HandlePreeditEndThunk), this);
     84   g_signal_connect(context_, "preedit_changed",
     85                    G_CALLBACK(HandlePreeditChangedThunk), this);
     86   g_signal_connect(context_, "commit",
     87                    G_CALLBACK(HandleCommitThunk), this);
     88 
     89   g_signal_connect(context_simple_, "preedit_start",
     90                    G_CALLBACK(HandlePreeditStartThunk), this);
     91   g_signal_connect(context_simple_, "preedit_end",
     92                    G_CALLBACK(HandlePreeditEndThunk), this);
     93   g_signal_connect(context_simple_, "preedit_changed",
     94                    G_CALLBACK(HandlePreeditChangedThunk), this);
     95   g_signal_connect(context_simple_, "commit",
     96                    G_CALLBACK(HandleCommitThunk), this);
     97 
     98   GtkWidget* widget = host_view->native_view();
     99   DCHECK(widget);
    100 
    101   g_signal_connect(widget, "realize",
    102                    G_CALLBACK(HandleHostViewRealizeThunk), this);
    103   g_signal_connect(widget, "unrealize",
    104                    G_CALLBACK(HandleHostViewUnrealizeThunk), this);
    105 
    106   // Set client window if the widget is already realized.
    107   HandleHostViewRealize(widget);
    108 }
    109 
    110 GtkIMContextWrapper::~GtkIMContextWrapper() {
    111   if (context_)
    112     g_object_unref(context_);
    113   if (context_simple_)
    114     g_object_unref(context_simple_);
    115 }
    116 
    117 void GtkIMContextWrapper::ProcessKeyEvent(GdkEventKey* event) {
    118   suppress_next_commit_ = false;
    119 
    120   // Indicates preedit-changed and commit signal handlers that we are
    121   // processing a key event.
    122   is_in_key_event_handler_ = true;
    123   // Reset this flag so that we can know if preedit is changed after
    124   // processing this key event.
    125   is_composition_changed_ = false;
    126   // Clear it so that we can know if something needs committing after
    127   // processing this key event.
    128   commit_text_.clear();
    129 
    130   // According to Document Object Model (DOM) Level 3 Events Specification
    131   // Appendix A: Keyboard events and key identifiers
    132   // http://www.w3.org/TR/DOM-Level-3-Events/keyset.html:
    133   // The event sequence would be:
    134   // 1. keydown
    135   // 2. textInput
    136   // 3. keyup
    137   //
    138   // So keydown must be sent to webkit before sending input method result,
    139   // while keyup must be sent afterwards.
    140   // However on Windows, if a keydown event has been processed by IME, its
    141   // virtual keycode will be changed to VK_PROCESSKEY(0xE5) before being sent
    142   // to application.
    143   // To emulate the windows behavior as much as possible, we need to send the
    144   // key event to the GtkIMContext object first, and decide whether or not to
    145   // send the original key event to webkit according to the result from IME.
    146   //
    147   // If IME is enabled by WebKit, this event will be dispatched to context_
    148   // to get full IME support. Otherwise it'll be dispatched to
    149   // context_simple_, so that dead/compose keys can still work.
    150   //
    151   // It sends a "commit" signal when it has a character to be inserted
    152   // even when we use a US keyboard so that we can send a Char event
    153   // (or an IME event) to the renderer in our "commit"-signal handler.
    154   // We should send a KeyDown (or a KeyUp) event before dispatching this
    155   // event to the GtkIMContext object (and send a Char event) so that WebKit
    156   // can dispatch the JavaScript events in the following order: onkeydown(),
    157   // onkeypress(), and onkeyup(). (Many JavaScript pages assume this.)
    158   gboolean filtered = false;
    159   if (is_enabled_) {
    160     filtered = gtk_im_context_filter_keypress(context_, event);
    161   } else {
    162     filtered = gtk_im_context_filter_keypress(context_simple_, event);
    163   }
    164 
    165   // Reset this flag here, as it's only used in input method callbacks.
    166   is_in_key_event_handler_ = false;
    167 
    168   NativeWebKeyboardEvent wke(event);
    169 
    170   // If the key event was handled by the input method, then we need to prevent
    171   // RenderView::UnhandledKeyboardEvent() from processing it.
    172   // Otherwise unexpected result may occur. For example if it's a
    173   // Backspace key event, the browser may go back to previous page.
    174   // We just send all keyup events to the browser to avoid breaking the
    175   // browser's MENU key function, which is actually the only keyup event
    176   // handled in the browser.
    177   if (filtered && event->type == GDK_KEY_PRESS)
    178     wke.skip_in_browser = true;
    179 
    180   const int key_code = wke.windowsKeyCode;
    181   const bool has_result = HasInputMethodResult();
    182 
    183   // Send filtered keydown event before sending IME result.
    184   // In order to workaround http://crosbug.com/6582, we only send a filtered
    185   // keydown event if it generated any input method result.
    186   if (event->type == GDK_KEY_PRESS && filtered && has_result)
    187     ProcessFilteredKeyPressEvent(&wke);
    188 
    189   // Send IME results. In most cases, it's only available if the key event
    190   // is filtered by IME. But in rare cases, an unfiltered key event may also
    191   // generate IME results.
    192   // Any IME results generated by a unfiltered key down event must be sent
    193   // before the key down event, to avoid some tricky issues. For example,
    194   // when using latin-post input method, pressing 'a' then Backspace, may
    195   // generate following events in sequence:
    196   //  1. keydown 'a' (filtered)
    197   //  2. preedit changed to "a"
    198   //  3. keyup 'a' (unfiltered)
    199   //  4. keydown Backspace (unfiltered)
    200   //  5. commit "a"
    201   //  6. preedit end
    202   //  7. keyup Backspace (unfiltered)
    203   //
    204   // In this case, the input box will be in a strange state if keydown
    205   // Backspace is sent to webkit before commit "a" and preedit end.
    206   if (has_result)
    207     ProcessInputMethodResult(event, filtered);
    208 
    209   // Send unfiltered keydown and keyup events after sending IME result.
    210   if (event->type == GDK_KEY_PRESS && !filtered) {
    211     ProcessUnfilteredKeyPressEvent(&wke);
    212   } else if (event->type == GDK_KEY_RELEASE) {
    213     // In order to workaround http://crosbug.com/6582, we need to suppress
    214     // the keyup event if corresponding keydown event was suppressed, or
    215     // the last key event was a keyup event with the same keycode.
    216     const bool suppress = (last_key_code_ == key_code) &&
    217         (last_key_was_up_ || last_key_filtered_no_result_);
    218 
    219     if (!suppress)
    220       host_view_->ForwardKeyboardEvent(wke);
    221   }
    222 
    223   last_key_code_ = key_code;
    224   last_key_was_up_ = (event->type == GDK_KEY_RELEASE);
    225   last_key_filtered_no_result_ = (filtered && !has_result);
    226 }
    227 
    228 void GtkIMContextWrapper::UpdateInputMethodState(WebKit::WebTextInputType type,
    229                                                  const gfx::Rect& caret_rect) {
    230   suppress_next_commit_ = false;
    231 
    232   // The renderer has updated its IME status.
    233   // Control the GtkIMContext object according to this status.
    234   if (!context_ || !is_focused_)
    235     return;
    236 
    237   DCHECK(!is_in_key_event_handler_);
    238 
    239   bool is_enabled = (type == WebKit::WebTextInputTypeText);
    240   if (is_enabled_ != is_enabled) {
    241     is_enabled_ = is_enabled;
    242     if (is_enabled)
    243       gtk_im_context_focus_in(context_);
    244     else
    245       gtk_im_context_focus_out(context_);
    246   }
    247 
    248   if (is_enabled) {
    249     // Updates the position of the IME candidate window.
    250     // The position sent from the renderer is a relative one, so we need to
    251     // attach the GtkIMContext object to this window before changing the
    252     // position.
    253     GdkRectangle cursor_rect(caret_rect.ToGdkRectangle());
    254     gtk_im_context_set_cursor_location(context_, &cursor_rect);
    255   }
    256 }
    257 
    258 void GtkIMContextWrapper::OnFocusIn() {
    259   if (is_focused_)
    260     return;
    261 
    262   // Tracks the focused state so that we can give focus to the
    263   // GtkIMContext object correctly later when IME is enabled by WebKit.
    264   is_focused_ = true;
    265 
    266   last_key_code_ = 0;
    267   last_key_was_up_ = false;
    268   last_key_filtered_no_result_ = false;
    269 
    270   // Notify the GtkIMContext object of this focus-in event only if IME is
    271   // enabled by WebKit.
    272   if (is_enabled_)
    273     gtk_im_context_focus_in(context_);
    274 
    275   // context_simple_ is always enabled.
    276   // Actually it doesn't care focus state at all.
    277   gtk_im_context_focus_in(context_simple_);
    278 
    279   // Enables RenderWidget's IME related events, so that we can be notified
    280   // when WebKit wants to enable or disable IME.
    281   if (host_view_->GetRenderWidgetHost())
    282     host_view_->GetRenderWidgetHost()->SetInputMethodActive(true);
    283 }
    284 
    285 void GtkIMContextWrapper::OnFocusOut() {
    286   if (!is_focused_)
    287     return;
    288 
    289   // Tracks the focused state so that we won't give focus to the
    290   // GtkIMContext object unexpectly.
    291   is_focused_ = false;
    292 
    293   // Notify the GtkIMContext object of this focus-out event only if IME is
    294   // enabled by WebKit.
    295   if (is_enabled_) {
    296     // To reset the GtkIMContext object and prevent data loss.
    297     ConfirmComposition();
    298     gtk_im_context_focus_out(context_);
    299   }
    300 
    301   // To make sure it'll be in correct state when focused in again.
    302   gtk_im_context_reset(context_simple_);
    303   gtk_im_context_focus_out(context_simple_);
    304 
    305   is_composing_text_ = false;
    306 
    307   // Disable RenderWidget's IME related events to save bandwidth.
    308   if (host_view_->GetRenderWidgetHost())
    309     host_view_->GetRenderWidgetHost()->SetInputMethodActive(false);
    310 }
    311 
    312 #if !defined(TOOLKIT_VIEWS)
    313 // Not defined for views because the views context menu doesn't
    314 // implement input methods yet.
    315 void GtkIMContextWrapper::AppendInputMethodsContextMenu(MenuGtk* menu) {
    316   gboolean show_input_method_menu = TRUE;
    317 
    318   g_object_get(gtk_widget_get_settings(GTK_WIDGET(host_view_->native_view())),
    319                "gtk-show-input-method-menu", &show_input_method_menu, NULL);
    320   if (!show_input_method_menu)
    321     return;
    322 
    323   std::string label = gfx::ConvertAcceleratorsFromWindowsStyle(
    324       l10n_util::GetStringUTF8(IDS_CONTENT_CONTEXT_INPUT_METHODS_MENU));
    325   GtkWidget* menuitem = gtk_menu_item_new_with_mnemonic(label.c_str());
    326   GtkWidget* submenu = gtk_menu_new();
    327   gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
    328   gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(context_),
    329                                        GTK_MENU_SHELL(submenu));
    330   menu->AppendSeparator();
    331   menu->AppendMenuItem(IDC_INPUT_METHODS_MENU, menuitem);
    332 }
    333 #endif
    334 
    335 void GtkIMContextWrapper::CancelComposition() {
    336   if (!is_enabled_)
    337     return;
    338 
    339   DCHECK(!is_in_key_event_handler_);
    340 
    341   // To prevent any text from being committed when resetting the |context_|;
    342   is_in_key_event_handler_ = true;
    343   suppress_next_commit_ = true;
    344 
    345   gtk_im_context_reset(context_);
    346   gtk_im_context_reset(context_simple_);
    347 
    348   if (is_focused_) {
    349     // Some input methods may not honour the reset call. Focusing out/in the
    350     // |context_| to make sure it gets reset correctly.
    351     gtk_im_context_focus_out(context_);
    352     gtk_im_context_focus_in(context_);
    353   }
    354 
    355   is_composing_text_ = false;
    356   composition_.Clear();
    357   commit_text_.clear();
    358 
    359   is_in_key_event_handler_ = false;
    360 }
    361 
    362 bool GtkIMContextWrapper::NeedCommitByForwardingCharEvent() const {
    363   // If there is no composition text and has only one character to be
    364   // committed, then the character will be send to webkit as a Char event
    365   // instead of a confirmed composition text.
    366   // It should be fine to handle BMP character only, as non-BMP characters
    367   // can always be committed as confirmed composition text.
    368   return !is_composing_text_ && commit_text_.length() == 1;
    369 }
    370 
    371 bool GtkIMContextWrapper::HasInputMethodResult() const {
    372   return commit_text_.length() || is_composition_changed_;
    373 }
    374 
    375 void GtkIMContextWrapper::ProcessFilteredKeyPressEvent(
    376     NativeWebKeyboardEvent* wke) {
    377   // If IME has filtered this event, then replace virtual key code with
    378   // VK_PROCESSKEY. See comment in ProcessKeyEvent() for details.
    379   // It's only required for keydown events.
    380   // To emulate windows behavior, when input method is enabled, if the commit
    381   // text can be emulated by a Char event, then don't do this replacement.
    382   if (!NeedCommitByForwardingCharEvent()) {
    383     wke->windowsKeyCode = kCompositionEventKeyCode;
    384     // keyidentifier must be updated accordingly, otherwise this key event may
    385     // still be processed by webkit.
    386     wke->setKeyIdentifierFromWindowsKeyCode();
    387   }
    388   host_view_->ForwardKeyboardEvent(*wke);
    389 }
    390 
    391 void GtkIMContextWrapper::ProcessUnfilteredKeyPressEvent(
    392     NativeWebKeyboardEvent* wke) {
    393   // Send keydown event as it, because it's not filtered by IME.
    394   host_view_->ForwardKeyboardEvent(*wke);
    395 
    396   // IME is disabled by WebKit or the GtkIMContext object cannot handle
    397   // this key event.
    398   // This case is caused by two reasons:
    399   // 1. The given key event is a control-key event, (e.g. return, page up,
    400   //    page down, tab, arrows, etc.) or;
    401   // 2. The given key event is not a control-key event but printable
    402   //    characters aren't assigned to the event, (e.g. alt+d, etc.)
    403   // Create a Char event manually from this key event and send it to the
    404   // renderer when this Char event contains a printable character which
    405   // should be processed by WebKit.
    406   // isSystemKey will be set to true if this key event has Alt modifier,
    407   // see WebInputEventFactory::keyboardEvent() for details.
    408   if (wke->text[0]) {
    409     wke->type = WebKit::WebInputEvent::Char;
    410     wke->skip_in_browser = true;
    411     host_view_->ForwardKeyboardEvent(*wke);
    412   }
    413 }
    414 
    415 void GtkIMContextWrapper::ProcessInputMethodResult(const GdkEventKey* event,
    416                                                    bool filtered) {
    417   RenderWidgetHost* host = host_view_->GetRenderWidgetHost();
    418   if (!host)
    419     return;
    420 
    421   bool committed = false;
    422   // We do commit before preedit change, so that we can optimize some
    423   // unnecessary preedit changes.
    424   if (commit_text_.length()) {
    425     if (filtered && NeedCommitByForwardingCharEvent()) {
    426       // Send a Char event when we input a composed character without IMEs
    427       // so that this event is to be dispatched to onkeypress() handlers,
    428       // autofill, etc.
    429       // Only commit text generated by a filtered key down event can be sent
    430       // as a Char event, because a unfiltered key down event will probably
    431       // generate another Char event.
    432       // TODO(james.su (at) gmail.com): Is it necessary to support non BMP chars
    433       // here?
    434       NativeWebKeyboardEvent char_event(commit_text_[0],
    435                                         event->state,
    436                                         base::Time::Now().ToDoubleT());
    437       char_event.skip_in_browser = true;
    438       host_view_->ForwardKeyboardEvent(char_event);
    439     } else {
    440       committed = true;
    441       // Send an IME event.
    442       // Unlike a Char event, an IME event is NOT dispatched to onkeypress()
    443       // handlers or autofill.
    444       host->ImeConfirmComposition(commit_text_);
    445       // Set this flag to false, as this composition session has been
    446       // finished.
    447       is_composing_text_ = false;
    448     }
    449   }
    450 
    451   // Send preedit text only if it's changed.
    452   // If a text has been committed, then we don't need to send the empty
    453   // preedit text again.
    454   if (is_composition_changed_) {
    455     if (composition_.text.length()) {
    456       // Another composition session has been started.
    457       is_composing_text_ = true;
    458       // TODO(suzhe): convert both renderer_host and renderer to use
    459       // ui::CompositionText.
    460       const std::vector<WebKit::WebCompositionUnderline>& underlines =
    461           reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>(
    462               composition_.underlines);
    463       host->ImeSetComposition(composition_.text, underlines,
    464                               composition_.selection.start(),
    465                               composition_.selection.end());
    466     } else if (!committed) {
    467       host->ImeCancelComposition();
    468     }
    469   }
    470 }
    471 
    472 void GtkIMContextWrapper::ConfirmComposition() {
    473   if (!is_enabled_)
    474     return;
    475 
    476   DCHECK(!is_in_key_event_handler_);
    477 
    478   if (is_composing_text_) {
    479     if (host_view_->GetRenderWidgetHost())
    480       host_view_->GetRenderWidgetHost()->ImeConfirmComposition();
    481 
    482     // Reset the input method.
    483     CancelComposition();
    484   }
    485 }
    486 
    487 void GtkIMContextWrapper::HandleCommit(const string16& text) {
    488   if (suppress_next_commit_) {
    489     suppress_next_commit_ = false;
    490     return;
    491   }
    492 
    493   // Append the text to the buffer, because commit signal might be fired
    494   // multiple times when processing a key event.
    495   commit_text_.append(text);
    496   // Nothing needs to do, if it's currently in ProcessKeyEvent()
    497   // handler, which will send commit text to webkit later. Otherwise,
    498   // we need send it here.
    499   // It's possible that commit signal is fired without a key event, for
    500   // example when user input via a voice or handwriting recognition software.
    501   // In this case, the text must be committed directly.
    502   if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) {
    503     // Workaround http://crbug.com/45478 by sending fake key down/up events.
    504     SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown);
    505     host_view_->GetRenderWidgetHost()->ImeConfirmComposition(text);
    506     SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp);
    507   }
    508 }
    509 
    510 void GtkIMContextWrapper::HandlePreeditStart() {
    511   // Ignore preedit related signals triggered by CancelComposition() method.
    512   if (suppress_next_commit_)
    513     return;
    514   is_composing_text_ = true;
    515 }
    516 
    517 void GtkIMContextWrapper::HandlePreeditChanged(const gchar* text,
    518                                                PangoAttrList* attrs,
    519                                                int cursor_position) {
    520   // Ignore preedit related signals triggered by CancelComposition() method.
    521   if (suppress_next_commit_)
    522     return;
    523 
    524   // Don't set is_composition_changed_ to false if there is no change, because
    525   // this handler might be called multiple times with the same data.
    526   is_composition_changed_ = true;
    527   composition_.Clear();
    528 
    529   ui::ExtractCompositionTextFromGtkPreedit(text, attrs, cursor_position,
    530                                            &composition_);
    531 
    532   // TODO(suzhe): due to a bug of webkit, we currently can't use selection range
    533   // with composition string. See: https://bugs.webkit.org/show_bug.cgi?id=40805
    534   composition_.selection = ui::Range(cursor_position);
    535 
    536   // In case we are using a buggy input method which doesn't fire
    537   // "preedit_start" signal.
    538   if (composition_.text.length())
    539     is_composing_text_ = true;
    540 
    541   // Nothing needs to do, if it's currently in ProcessKeyEvent()
    542   // handler, which will send preedit text to webkit later.
    543   // Otherwise, we need send it here if it's been changed.
    544   if (!is_in_key_event_handler_ && is_composing_text_ &&
    545       host_view_->GetRenderWidgetHost()) {
    546     // Workaround http://crbug.com/45478 by sending fake key down/up events.
    547     SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown);
    548     // TODO(suzhe): convert both renderer_host and renderer to use
    549     // ui::CompositionText.
    550     const std::vector<WebKit::WebCompositionUnderline>& underlines =
    551         reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>(
    552             composition_.underlines);
    553     host_view_->GetRenderWidgetHost()->ImeSetComposition(
    554         composition_.text, underlines, composition_.selection.start(),
    555         composition_.selection.end());
    556     SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp);
    557   }
    558 }
    559 
    560 void GtkIMContextWrapper::HandlePreeditEnd() {
    561   if (composition_.text.length()) {
    562     // The composition session has been finished.
    563     composition_.Clear();
    564     is_composition_changed_ = true;
    565 
    566     // If there is still a preedit text when firing "preedit-end" signal,
    567     // we need inform webkit to clear it.
    568     // It's only necessary when it's not in ProcessKeyEvent ().
    569     if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost())
    570       host_view_->GetRenderWidgetHost()->ImeCancelComposition();
    571   }
    572 
    573   // Don't set is_composing_text_ to false here, because "preedit_end"
    574   // signal may be fired before "commit" signal.
    575 }
    576 
    577 void GtkIMContextWrapper::HandleHostViewRealize(GtkWidget* widget) {
    578   // We should only set im context's client window once, because when setting
    579   // client window.im context may destroy and recreate its internal states and
    580   // objects.
    581   if (widget->window) {
    582     gtk_im_context_set_client_window(context_, widget->window);
    583     gtk_im_context_set_client_window(context_simple_, widget->window);
    584   }
    585 }
    586 
    587 void GtkIMContextWrapper::HandleHostViewUnrealize() {
    588   gtk_im_context_set_client_window(context_, NULL);
    589   gtk_im_context_set_client_window(context_simple_, NULL);
    590 }
    591 
    592 void GtkIMContextWrapper::SendFakeCompositionKeyEvent(
    593     WebKit::WebInputEvent::Type type) {
    594   NativeWebKeyboardEvent fake_event;
    595   fake_event.windowsKeyCode = kCompositionEventKeyCode;
    596   fake_event.skip_in_browser = true;
    597   fake_event.type = type;
    598   host_view_->ForwardKeyboardEvent(fake_event);
    599 }
    600 
    601 void GtkIMContextWrapper::HandleCommitThunk(
    602     GtkIMContext* context, gchar* text, GtkIMContextWrapper* self) {
    603   self->HandleCommit(UTF8ToUTF16(text));
    604 }
    605 
    606 void GtkIMContextWrapper::HandlePreeditStartThunk(
    607     GtkIMContext* context, GtkIMContextWrapper* self) {
    608   self->HandlePreeditStart();
    609 }
    610 
    611 void GtkIMContextWrapper::HandlePreeditChangedThunk(
    612     GtkIMContext* context, GtkIMContextWrapper* self) {
    613   gchar* text = NULL;
    614   PangoAttrList* attrs = NULL;
    615   gint cursor_position = 0;
    616   gtk_im_context_get_preedit_string(context, &text, &attrs, &cursor_position);
    617   self->HandlePreeditChanged(text, attrs, cursor_position);
    618   g_free(text);
    619   pango_attr_list_unref(attrs);
    620 }
    621 
    622 void GtkIMContextWrapper::HandlePreeditEndThunk(
    623     GtkIMContext* context, GtkIMContextWrapper* self) {
    624   self->HandlePreeditEnd();
    625 }
    626 
    627 void GtkIMContextWrapper::HandleHostViewRealizeThunk(
    628     GtkWidget* widget, GtkIMContextWrapper* self) {
    629   self->HandleHostViewRealize(widget);
    630 }
    631 
    632 void GtkIMContextWrapper::HandleHostViewUnrealizeThunk(
    633     GtkWidget* widget, GtkIMContextWrapper* self) {
    634   self->HandleHostViewUnrealize();
    635 }
    636