summaryrefslogtreecommitdiffstats
path: root/chrome/browser/renderer_host
diff options
context:
space:
mode:
authorhbono@chromium.org <hbono@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-23 05:35:02 +0000
committerhbono@chromium.org <hbono@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-23 05:35:02 +0000
commitf338a0ed8c0490fad58fa47799c98909e8750978 (patch)
treebb27f6d22afaeca65f41fd6b226fe32812193e7d /chrome/browser/renderer_host
parentabe7a89488d132d650aff0846ccd9a0b83d4a1f1 (diff)
downloadchromium_src-f338a0ed8c0490fad58fa47799c98909e8750978.zip
chromium_src-f338a0ed8c0490fad58fa47799c98909e8750978.tar.gz
chromium_src-f338a0ed8c0490fad58fa47799c98909e8750978.tar.bz2
Integrating GtkIMContext into the RenderWidgetHostViewGtk class.This change implements signal handers of the GtkIMContext object to support IMEs and dead-keys. Also, to improve compatibility with Windows Chrome, this change emulates IPC messages sent on Windows when we input characters and fixes Issue 13604 as well as Issue 10953 and 11226. Even though I notice we need more work for fixing edge cases (e.g. disabling IMEs on a password input) on Linux, I think this is the good starting point. (Supporting edge-cases requires complicated code and it makes hard to review.)
BUG=10953 "IME support" BUG=11226 "Dead keys and accents input not working" BUG=13604 "Hotkeys not working in non-us keyboard layout" TEST=Open a web page which contains an <input> form (e.g. <http://www.google.com/>), type a '[{' key and an 'A' key on a Canadian-French keyboard, and see a Latin character "U+00E2" is displayed in the <input> form. TEST=Open a web page which contains an <input> form (e.g. <http://www.google.com/>), enable an Chinese Pinyin IME, type a 'W' key, type an 'O' key, and see a Chinese character is displayed in the <input> form. TEST=Change the keyboard layout to Hebrew (or Russian), open a web page which contains an <input> form, input some characters in the <input> form, type control+a, and see the text in the <input> form is selected. Review URL: http://codereview.chromium.org/126118 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19009 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/renderer_host')
-rw-r--r--chrome/browser/renderer_host/render_widget_host.cc22
-rw-r--r--chrome/browser/renderer_host/render_widget_host.h39
-rw-r--r--chrome/browser/renderer_host/render_widget_host_view_gtk.cc119
-rw-r--r--chrome/browser/renderer_host/render_widget_host_view_gtk.h23
4 files changed, 201 insertions, 2 deletions
diff --git a/chrome/browser/renderer_host/render_widget_host.cc b/chrome/browser/renderer_host/render_widget_host.cc
index 51d2b3b..fd734bb 100644
--- a/chrome/browser/renderer_host/render_widget_host.cc
+++ b/chrome/browser/renderer_host/render_widget_host.cc
@@ -424,6 +424,28 @@ void RenderWidgetHost::NotifyTextDirection() {
}
}
+void RenderWidgetHost::ImeSetInputMode(bool activate) {
+ Send(new ViewMsg_ImeSetInputMode(routing_id(), activate));
+}
+
+void RenderWidgetHost::ImeSetComposition(const std::wstring& ime_string,
+ int cursor_position,
+ int target_start,
+ int target_end) {
+ Send(new ViewMsg_ImeSetComposition(routing_id(), 0, cursor_position,
+ target_start, target_end, ime_string));
+}
+
+void RenderWidgetHost::ImeConfirmComposition(const std::wstring& ime_string) {
+ Send(new ViewMsg_ImeSetComposition(routing_id(), 1, -1, -1, -1, ime_string));
+}
+
+void RenderWidgetHost::ImeCancelComposition() {
+ std::wstring empty_string;
+ Send(new ViewMsg_ImeSetComposition(routing_id(), -1, -1, -1, -1,
+ empty_string));
+}
+
gfx::Rect RenderWidgetHost::GetRootWindowResizerRect() const {
return gfx::Rect();
}
diff --git a/chrome/browser/renderer_host/render_widget_host.h b/chrome/browser/renderer_host/render_widget_host.h
index c5913ef..aab4645 100644
--- a/chrome/browser/renderer_host/render_widget_host.h
+++ b/chrome/browser/renderer_host/render_widget_host.h
@@ -279,6 +279,45 @@ class RenderWidgetHost : public IPC::Channel::Listener {
void CancelUpdateTextDirection();
void NotifyTextDirection();
+ // Notifies the renderer whether or not the IME attached to this process is
+ // activated.
+ // When the IME is activated, a renderer process sends IPC messages to notify
+ // the status of its composition node. (This message is mainly used for
+ // notifying the position of the input cursor so that the browser can
+ // display IME windows under the cursor.)
+ void ImeSetInputMode(bool activate);
+
+ // Update the composition node of the renderer (or WebKit).
+ // WebKit has a special node (a composition node) for IMEs to change its text
+ // without affecting any other DOM nodes. When the IME (attached to the
+ // browser) updates its text, the browser sends IPC messages to update the
+ // composition node of the renderer.
+ // (Read the comments of each function for its detail.)
+
+ // Sets the text of the composition node.
+ // This function can also update the cursor position and mark the specified
+ // range in the composition node.
+ // A browser should call this function:
+ // * when it receives a WM_IME_COMPOSITION message with a GCS_COMPSTR flag
+ // (on Windows);
+ // * when it receives a "preedit_changed" signal of GtkIMContext (on Linux);
+ // * when markedText of NSTextInput is called (on Mac).
+ void ImeSetComposition(const std::wstring& ime_string,
+ int cursor_position,
+ int target_start,
+ int target_end);
+
+ // Finishes an ongoing composition with the specified text.
+ // A browser should call this function:
+ // * when it receives a WM_IME_COMPOSITION message with a GCS_RESULTSTR flag
+ // (on Windows);
+ // * when it receives a "commit" signal of GtkIMContext (on Linux);
+ // * when insertText of NSTextInput is called (on Mac).
+ void ImeConfirmComposition(const std::wstring& ime_string);
+
+ // Cancels an ongoing composition.
+ void ImeCancelComposition();
+
// This is for derived classes to give us access to the resizer rect.
// And to also expose it to the RenderWidgetHostView.
virtual gfx::Rect GetRootWindowResizerRect() const;
diff --git a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc
index 73b60c9..5d42ff8 100644
--- a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc
+++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc
@@ -71,6 +71,17 @@ class RenderWidgetHostViewGtkWidget {
g_signal_connect(widget, "scroll-event",
G_CALLBACK(MouseScrollEvent), host_view);
+ // Create a GtkIMContext instance and attach its signal handlers.
+ host_view->im_context_ = gtk_im_multicontext_new();
+ g_signal_connect(host_view->im_context_, "preedit_start",
+ G_CALLBACK(InputMethodPreeditStart), host_view);
+ g_signal_connect(host_view->im_context_, "preedit_end",
+ G_CALLBACK(InputMethodPreeditEnd), host_view);
+ g_signal_connect(host_view->im_context_, "preedit_changed",
+ G_CALLBACK(InputMethodPreeditChanged), host_view);
+ g_signal_connect(host_view->im_context_, "commit",
+ G_CALLBACK(InputMethodCommit), host_view);
+
GtkTargetList* target_list = gtk_target_list_new(NULL, 0);
gtk_target_list_add_text_targets(target_list, 0);
gint num_targets = 0;
@@ -109,6 +120,19 @@ class RenderWidgetHostViewGtkWidget {
NativeWebKeyboardEvent wke(event);
host_view->GetRenderWidgetHost()->ForwardKeyboardEvent(wke);
}
+
+ // Dispatch this event to the GtkIMContext object.
+ // It sends a "commit" signal when it has a character to be inserted
+ // even when we use a US keyboard so that we can send a Char event
+ // (or an IME event) to the renderer in our "commit"-signal handler.
+ // We should send a KeyDown (or a KeyUp) event before dispatching this
+ // event to the GtkIMContext object (and send a Char event) so that WebKit
+ // can dispatch the JavaScript events in the following order: onkeydown(),
+ // onkeypress(), and onkeyup(). (Many JavaScript pages assume this.)
+ // TODO(hbono): we should not dispatch a key event when the input focus
+ // is in a password input?
+ gtk_im_context_filter_keypress(host_view->im_context_, event);
+
// We return TRUE because we did handle the event. If it turns out webkit
// can't handle the event, we'll deal with it in
// RenderView::UnhandledKeyboardEvent().
@@ -215,6 +239,68 @@ class RenderWidgetHostViewGtkWidget {
return FALSE;
}
+ static void InputMethodCommit(GtkIMContext* im_context,
+ gchar* text,
+ RenderWidgetHostViewGtk* host_view) {
+ std::wstring im_text = UTF8ToWide(text);
+ if (!host_view->im_is_composing_cjk_text_ && im_text.length() == 1) {
+ // Send a Char event when we input a composed character without IMEs so
+ // that this event is to be dispatched to onkeypress() handlers,
+ // autofill, etc.
+ ForwardCharEvent(host_view, im_text[0]);
+ } else {
+ // Send an IME event.
+ // Unlike a Char event, an IME event is NOT dispatched to onkeypress()
+ // handlers or autofill.
+ host_view->GetRenderWidgetHost()->ImeConfirmComposition(im_text);
+ }
+ }
+
+ static void InputMethodPreeditStart(GtkIMContext* im_context,
+ RenderWidgetHostViewGtk* host_view) {
+ // Start monitoring IME events of the renderer.
+ // TODO(hbono): a renderer sends these IME events not only for sending the
+ // caret position, but also for enabling/disabling IMEs. If we need to
+ // enable/disable IMEs, we should move this code to a better place.
+ // (This signal handler is called only when an IME is enabled. So, once
+ // we disable an IME, we cannot receive any IME events from the renderer,
+ // i.e. we cannot re-enable the IME any longer.)
+ host_view->GetRenderWidgetHost()->ImeSetInputMode(true);
+ host_view->im_is_composing_cjk_text_ = true;
+ }
+
+ static void InputMethodPreeditEnd(GtkIMContext* im_context,
+ RenderWidgetHostViewGtk* host_view) {
+ // End monitoring IME events.
+ host_view->GetRenderWidgetHost()->ImeSetInputMode(false);
+ host_view->im_is_composing_cjk_text_ = false;
+ }
+
+ static void InputMethodPreeditChanged(GtkIMContext* im_context,
+ RenderWidgetHostViewGtk* host_view) {
+ // Send an IME event to update the composition node of the renderer.
+ // TODO(hbono): an IME intercepts all key events while composing a text,
+ // i.e. we cannot receive any GDK_KEY_PRESS (or GDK_KEY_UP) events.
+ // Should we send pseudo KeyDown (and KeyUp) events to emulate Windows?
+ gchar* preedit_text = NULL;
+ gint cursor_position = 0;
+ gtk_im_context_get_preedit_string(im_context, &preedit_text, NULL,
+ &cursor_position);
+ host_view->GetRenderWidgetHost()->ImeSetComposition(
+ UTF8ToWide(preedit_text), cursor_position, -1, -1);
+ g_free(preedit_text);
+ }
+
+ static void ForwardCharEvent(RenderWidgetHostViewGtk* host_view,
+ wchar_t im_character) {
+ if (!im_character)
+ return;
+
+ NativeWebKeyboardEvent char_event(im_character,
+ base::Time::Now().ToDoubleT());
+ host_view->GetRenderWidgetHost()->ForwardKeyboardEvent(char_event);
+ }
+
DISALLOW_IMPLICIT_CONSTRUCTORS(RenderWidgetHostViewGtkWidget);
};
@@ -232,11 +318,15 @@ RenderWidgetHostViewGtk::RenderWidgetHostViewGtk(RenderWidgetHost* widget_host)
is_showing_context_menu_(false),
parent_host_view_(NULL),
parent_(NULL),
- is_popup_first_mouse_release_(true) {
+ is_popup_first_mouse_release_(true),
+ im_context_(NULL),
+ im_is_composing_cjk_text_(false) {
host_->set_view(this);
}
RenderWidgetHostViewGtk::~RenderWidgetHostViewGtk() {
+ if (im_context_)
+ g_object_unref(im_context_);
view_.Destroy();
}
@@ -396,7 +486,32 @@ void RenderWidgetHostViewGtk::SetIsLoading(bool is_loading) {
void RenderWidgetHostViewGtk::IMEUpdateStatus(int control,
const gfx::Rect& caret_rect) {
- NOTIMPLEMENTED();
+ // The renderer has updated its IME status.
+ // Control the GtkIMContext object according to this status.
+ if (!im_context_)
+ return;
+
+ if (control == IME_DISABLE) {
+ // TODO(hbono): this code just resets the GtkIMContext object and
+ // detaches it from this window. Should we prevent sending key events to
+ // the GtkIMContext object (or unref it) when we disable IMEs?
+ gtk_im_context_reset(im_context_);
+ gtk_im_context_set_client_window(im_context_, NULL);
+ gtk_im_context_set_cursor_location(im_context_, NULL);
+ } else {
+ // TODO(hbono): we should finish (not reset) an ongoing composition
+ // when |control| is IME_COMPLETE_COMPOSITION.
+
+ // Attach the GtkIMContext object to this window.
+ gtk_im_context_set_client_window(im_context_, view_.get()->window);
+
+ // Updates the position of the IME candidate window.
+ // The position sent from the renderer is a relative one, so we need to
+ // attach the GtkIMContext object to this window before changing the
+ // position.
+ GdkRectangle cursor_rect(caret_rect.ToGdkRectangle());
+ gtk_im_context_set_cursor_location(im_context_, &cursor_rect);
+ }
}
void RenderWidgetHostViewGtk::DidPaintRect(const gfx::Rect& rect) {
diff --git a/chrome/browser/renderer_host/render_widget_host_view_gtk.h b/chrome/browser/renderer_host/render_widget_host_view_gtk.h
index af0ac47..e727e08 100644
--- a/chrome/browser/renderer_host/render_widget_host_view_gtk.h
+++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.h
@@ -18,6 +18,7 @@ class RenderWidgetHost;
typedef struct _GtkClipboard GtkClipboard;
typedef struct _GtkSelectionData GtkSelectionData;
+typedef struct _GtkIMContext GtkIMContext;
// -----------------------------------------------------------------------------
// See comments in render_widget_host_view.h about this class and its members.
@@ -109,6 +110,28 @@ class RenderWidgetHostViewGtk : public RenderWidgetHostView {
// We ignore the first mouse release on popups. This allows the popup to
// stay open.
bool is_popup_first_mouse_release_;
+
+ // The GtkIMContext object.
+ // In terms of the DOM event specification Appendix A
+ // <http://www.w3.org/TR/DOM-Level-3-Events/keyset.html>,
+ // GTK uses a GtkIMContext object for the following two purposes:
+ // 1. Composing Latin characters (A.1.2), and;
+ // 2. Composing CJK characters with an IME (A.1.3).
+ // Many JavaScript pages assume composed Latin characters are dispatched to
+ // their onkeypress() handlers but not dispatched CJK characters composed
+ // with an IME. To emulate this behavior, we should monitor the status of
+ // this GtkIMContext object and prevent sending Char events when a
+ // GtkIMContext object sends a "commit" signal with the CJK characters
+ // composed by an IME.
+ GtkIMContext* im_context_;
+
+ // Whether or not the above GtkIMContext is composing a CJK text with an IME.
+ // The GtkIMContext object sends a "preedit_start" before it starts composing
+ // a CJK text and a "preedit_end" signal after it finishes composing it.
+ // On the other hand, the GtkIMContext object doesn't send them when
+ // composing Latin texts. So, we monitor the above signals to check whether
+ // or not the GtkIMContext object is composing a CJK text.
+ bool im_is_composing_cjk_text_;
};
#endif // CHROME_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GTK_H_