diff options
author | suzhe@chromium.org <suzhe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-23 17:53:04 +0000 |
---|---|---|
committer | suzhe@chromium.org <suzhe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-23 17:53:04 +0000 |
commit | fa7b1dc866e2a5af457602e015ebc84ab05c75b9 (patch) | |
tree | e0226343303b9925c10deba260ed00e7f91aa8dd | |
parent | 31825d4399cf8c44f86f828f5662c885c85e8b49 (diff) | |
download | chromium_src-fa7b1dc866e2a5af457602e015ebc84ab05c75b9.zip chromium_src-fa7b1dc866e2a5af457602e015ebc84ab05c75b9.tar.gz chromium_src-fa7b1dc866e2a5af457602e015ebc84ab05c75b9.tar.bz2 |
Improve input method support.
This CL fixes following issues:
BUG=23219 IME should be disabled in password box.
BUG=41876 Chinese IME is still active when current focus is not a text input control
BUG=44529 Clause segmentation information of composition text is not honored when using CJK input methods.
BUG=46326 Clicking during a composition cancels it
TEST=See individual bug report.
This CL is blocked on webkit bug: https://bugs.webkit.org/show_bug.cgi?id=40608
Review URL: http://codereview.chromium.org/2824015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@50622 0039d316-1c4b-4281-b951-d872f2087c98
24 files changed, 1163 insertions, 736 deletions
diff --git a/chrome/browser/ime_input.cc b/chrome/browser/ime_input.cc index 7897a66..edb49f8 100644 --- a/chrome/browser/ime_input.cc +++ b/chrome/browser/ime_input.cc @@ -4,8 +4,10 @@ #include "chrome/browser/ime_input.h" +#include "base/basictypes.h" #include "base/scoped_ptr.h" #include "base/string_util.h" +#include "third_party/skia/include/core/SkColor.h" // "imm32.lib" is required by IMM32 APIs used in this file. // NOTE(hbono): To comply with a comment from Darin, I have added @@ -15,6 +17,84 @@ /////////////////////////////////////////////////////////////////////////////// // ImeInput +namespace { + +// Determines whether or not the given attribute represents a target +// (a.k.a. a selection). +bool IsTargetAttribute(char attribute) { + return (attribute == ATTR_TARGET_CONVERTED || + attribute == ATTR_TARGET_NOTCONVERTED); +} + +// Helper function for ImeInput::GetCompositionInfo() method, to get the target +// range that's selected by the user in the current composition string. +void GetCompositionTargetRange(HIMC imm_context, int* target_start, + int* target_end) { + int attribute_size = ::ImmGetCompositionString(imm_context, GCS_COMPATTR, + NULL, 0); + if (attribute_size > 0) { + int start = 0; + int end = 0; + scoped_array<char> attribute_data(new char[attribute_size]); + if (attribute_data.get()) { + ::ImmGetCompositionString(imm_context, GCS_COMPATTR, + attribute_data.get(), attribute_size); + for (start = 0; start < attribute_size; ++start) { + if (IsTargetAttribute(attribute_data[start])) + break; + } + for (end = start; end < attribute_size; ++end) { + if (!IsTargetAttribute(attribute_data[end])) + break; + } + if (start == attribute_size) { + // This composition clause does not contain any target clauses, + // i.e. this clauses is an input clause. + // We treat the whole composition as a target clause. + start = 0; + end = attribute_size; + } + } + *target_start = start; + *target_end = end; + } +} + +// Helper function for ImeInput::GetCompositionInfo() method, to get underlines +// information of the current composition string. +void GetCompositionUnderlines( + HIMC imm_context, + int target_start, + int target_end, + std::vector<WebKit::WebCompositionUnderline>* underlines) { + int clause_size = ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, + NULL, 0); + int clause_length = clause_size / sizeof(uint32); + if (clause_length) { + scoped_array<uint32> clause_data(new uint32[clause_length]); + if (clause_data.get()) { + ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, + clause_data.get(), clause_size); + for (int i = 0; i < clause_length - 1; ++i) { + WebKit::WebCompositionUnderline underline; + underline.startOffset = clause_data[i]; + underline.endOffset = clause_data[i+1]; + underline.color = SK_ColorBLACK; + underline.thick = false; + + // Use thick underline for the target clause. + if (underline.startOffset >= static_cast<unsigned>(target_start) && + underline.endOffset <= static_cast<unsigned>(target_end)) { + underline.thick = true; + } + underlines->push_back(underline); + } + } + } +} + +} // namespace + ImeInput::ImeInput() : ime_status_(false), input_language_id_(LANG_USER_DEFAULT), @@ -168,65 +248,64 @@ void ImeInput::CompleteComposition(HWND window_handle, HIMC imm_context) { } } -void ImeInput::GetCaret(HIMC imm_context, LPARAM lparam, - ImeComposition* composition) { - // This operation is optional and language-dependent because the caret - // style is depended on the language, e.g.: - // * Korean IMEs: the caret is a blinking block, - // (It contains only one hangul character); - // * Chinese IMEs: the caret is a blinking line, - // (i.e. they do not need to retrieve the target selection); - // * Japanese IMEs: the caret is a selection (or underlined) block, - // (which can contain one or more Japanese characters). - int target_start = -1; - int target_end = -1; - switch (PRIMARYLANGID(input_language_id_)) { - case LANG_KOREAN: - if (lparam & CS_NOMOVECARET) { - target_start = 0; - target_end = 1; +void ImeInput::GetCompositionInfo(HIMC imm_context, LPARAM lparam, + ImeComposition* composition) { + // We only care about GCS_COMPATTR, GCS_COMPCLAUSE and GCS_CURSORPOS, and + // convert them into underlines and selection range respectively. + composition->underlines.clear(); + + int length = static_cast<int>(composition->ime_string.length()); + + // Retrieve the selection range information. If CS_NOMOVECARET is specified, + // that means the cursor should not be moved, then we just place the caret at + // the beginning of the composition string. Otherwise we should honour the + // GCS_CURSORPOS value if it's available. + // TODO(suzhe): due to a bug of webkit, we currently can't use selection range + // with composition string. See: https://bugs.webkit.org/show_bug.cgi?id=40805 + if (lparam & CS_NOMOVECARET) { + composition->selection_start = composition->selection_end = 0; + } else if (lparam & GCS_CURSORPOS) { + composition->selection_start = composition->selection_end = + ::ImmGetCompositionString(imm_context, GCS_CURSORPOS, NULL, 0); + } else { + composition->selection_start = composition->selection_end = length; + } + + // Find out the range selected by the user. + int target_start = 0; + int target_end = 0; + if (lparam & GCS_COMPATTR) + GetCompositionTargetRange(imm_context, &target_start, &target_end); + + // Retrieve the clause segmentations and convert them to underlines. + if (lparam & GCS_COMPCLAUSE) { + GetCompositionUnderlines(imm_context, target_start, target_end, + &composition->underlines); + } + + // Set default underlines in case there is no clause information. + if (!composition->underlines.size()) { + WebKit::WebCompositionUnderline underline; + underline.color = SK_ColorBLACK; + if (target_start > 0) { + underline.startOffset = 0; + underline.endOffset = target_start; + underline.thick = false; + composition->underlines.push_back(underline); } - break; - case LANG_CHINESE: - break; - case LANG_JAPANESE: - // For Japanese IMEs, the robustest way to retrieve the caret - // is scanning the attribute of the latest composition string and - // retrieving the begining and the end of the target clause, i.e. - // a clause being converted. - if (lparam & GCS_COMPATTR) { - int attribute_size = ::ImmGetCompositionString(imm_context, - GCS_COMPATTR, - NULL, 0); - if (attribute_size > 0) { - scoped_array<char> attribute_data(new char[attribute_size]); - if (attribute_data.get()) { - ::ImmGetCompositionString(imm_context, GCS_COMPATTR, - attribute_data.get(), attribute_size); - for (target_start = 0; target_start < attribute_size; - ++target_start) { - if (IsTargetAttribute(attribute_data[target_start])) - break; - } - for (target_end = target_start; target_end < attribute_size; - ++target_end) { - if (!IsTargetAttribute(attribute_data[target_end])) - break; - } - if (target_start == attribute_size) { - // This composition clause does not contain any target clauses, - // i.e. this clauses is an input clause. - // We treat whole this clause as a target clause. - target_end = target_start; - target_start = 0; - } - } - } + if (target_end > target_start) { + underline.startOffset = target_start; + underline.endOffset = target_end; + underline.thick = true; + composition->underlines.push_back(underline); + } + if (target_end < length) { + underline.startOffset = target_end; + underline.endOffset = length; + underline.thick = false; + composition->underlines.push_back(underline); } - break; } - composition->target_start = target_start; - composition->target_end = target_end; } bool ImeInput::GetString(HIMC imm_context, WPARAM lparam, int type, @@ -259,9 +338,8 @@ bool ImeInput::GetResult(HWND window_handle, LPARAM lparam, result = GetString(imm_context, lparam, GCS_RESULTSTR, composition); // Reset all the other parameters because a result string does not // have composition attributes. - composition->cursor_position = -1; - composition->target_start = -1; - composition->target_end = -1; + composition->selection_start = 0; + composition->selection_end = 0; ::ImmReleaseContext(window_handle, imm_context); } return result; @@ -288,16 +366,8 @@ bool ImeInput::GetComposition(HWND window_handle, LPARAM lparam, composition->ime_string[0] = 0xFF3F; } - // Retrieve the cursor position in the IME composition. - int cursor_position = ::ImmGetCompositionString(imm_context, - GCS_CURSORPOS, NULL, 0); - composition->cursor_position = cursor_position; - composition->target_start = -1; - composition->target_end = -1; - - // Retrieve the target selection and Update the ImeComposition - // object. - GetCaret(imm_context, lparam, composition); + // Retrieve the composition underlines and selection range information. + GetCompositionInfo(imm_context, lparam, composition); // Mark that there is an ongoing composition. is_composing_ = true; @@ -328,34 +398,26 @@ void ImeInput::CancelIME(HWND window_handle) { } } -void ImeInput::EnableIME(HWND window_handle, - const gfx::Rect& caret_rect, - bool complete) { +void ImeInput::EnableIME(HWND window_handle) { // Load the default IME context. // NOTE(hbono) // IMM ignores this call if the IME context is loaded. Therefore, we do // not have to check whether or not the IME context is loaded. ::ImmAssociateContextEx(window_handle, NULL, IACE_DEFAULT); - // Complete the ongoing composition and move the IME windows. - HIMC imm_context = ::ImmGetContext(window_handle); - if (imm_context) { - if (complete) { - // A renderer process have moved its input focus to another edit - // control when there is an ongoing composition, e.g. a user has - // clicked a mouse button and selected another edit control while - // composing a text. - // For this case, we have to complete the ongoing composition and - // hide the IME windows BEFORE MOVING THEM. - CompleteComposition(window_handle, imm_context); - } - // Save the caret position, and Update the position of the IME window. - // This update is used for moving an IME window when a renderer process - // resize/moves the input caret. - if (caret_rect.x() >= 0 && caret_rect.y() >= 0) { - caret_rect_.SetRect(caret_rect.x(), caret_rect.y(), caret_rect.width(), - caret_rect.height()); +} + +void ImeInput::UpdateCaretRect(HWND window_handle, + const gfx::Rect& caret_rect) { + // Save the caret position, and Update the position of the IME window. + // This update is used for moving an IME window when a renderer process + // resize/moves the input caret. + if (caret_rect_ != caret_rect) { + caret_rect_ = caret_rect; + // Move the IME windows. + HIMC imm_context = ::ImmGetContext(window_handle); + if (imm_context) { MoveImeWindow(window_handle, imm_context); + ::ImmReleaseContext(window_handle, imm_context); } - ::ImmReleaseContext(window_handle, imm_context); } } diff --git a/chrome/browser/ime_input.h b/chrome/browser/ime_input.h index 45fe21f..184343b 100644 --- a/chrome/browser/ime_input.h +++ b/chrome/browser/ime_input.h @@ -8,9 +8,11 @@ #include <windows.h> #include <string> +#include <vector> #include "base/basictypes.h" #include "gfx/rect.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCompositionUnderline.h" // This header file defines a struct and a class used for encapsulating IMM32 // APIs, controls IMEs attached to a window, and enables the 'on-the-spot' @@ -53,14 +55,14 @@ // This struct represents the status of an ongoing composition. struct ImeComposition { - // Represents the cursor position in the IME composition. - int cursor_position; + // Represents the start position of the selection range in the IME + // composition. + int selection_start; - // Represents the position of the beginning of the selection - int target_start; - - // Represents the position of the end of the selection - int target_end; + // Represents the end position of the selection range in the IME composition. + // If |selection_start| and |selection_end| are equal, then it represents the + // cursor position. + int selection_end; // Represents the type of the string in the 'ime_string' parameter. // Its possible values and description are listed bwlow: @@ -72,6 +74,9 @@ struct ImeComposition { // Represents the string retrieved from IME (Input Method Editor) std::wstring ime_string; + + // Contains the underline information of the composition string. + std::vector<WebKit::WebCompositionUnderline> underlines; }; // This class controls the IMM (Input Method Manager) through IMM32 APIs and @@ -104,13 +109,13 @@ class ImeInput { // The given input language does not have IMEs. bool SetInputLanguage(); - // Create the IME windows, and allocate required resources for them. + // Creates the IME windows, and allocate required resources for them. // Parameters // * window_handle [in] (HWND) // Represents the window handle of the caller. void CreateImeWindow(HWND window_handle); - // Update the style of the IME windows. + // Updates the style of the IME windows. // Parameters // * window_handle [in] (HWND) // Represents the window handle of the caller. @@ -131,26 +136,26 @@ class ImeInput { void SetImeWindowStyle(HWND window_handle, UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled); - // Destroy the IME windows and all the resources attached to them. + // Destroys the IME windows and all the resources attached to them. // Parameters // * window_handle [in] (HWND) // Represents the window handle of the caller. void DestroyImeWindow(HWND window_handle); - // Update the position of the IME windows. + // Updates the position of the IME windows. // Parameters // * window_handle [in] (HWND) // Represents the window handle of the caller. void UpdateImeWindow(HWND window_handle); - // Clean up the all resources attached to the given ImeInput object, and + // Cleans up the all resources attached to the given ImeInput object, and // reset its composition status. // Parameters // * window_handle [in] (HWND) // Represents the window handle of the caller. void CleanupComposition(HWND window_handle); - // Reset the composition status. + // Resets the composition status. // Cancel the ongoing composition if it exists. // NOTE(hbono): This method does not release the allocated resources. // Parameters @@ -158,7 +163,7 @@ class ImeInput { // Represents the window handle of the caller. void ResetComposition(HWND window_handle); - // Retrieve a composition result of the ongoing composition if it exists. + // Retrieves a composition result of the ongoing composition if it exists. // Parameters // * window_handle [in] (HWND) // Represents the window handle of the caller. @@ -180,7 +185,7 @@ class ImeInput { bool GetResult(HWND window_handle, LPARAM lparam, ImeComposition* composition); - // Retrieve the current composition status of the ongoing composition. + // Retrieves the current composition status of the ongoing composition. // Parameters // * window_handle [in] (HWND) // Represents the window handle of the caller. @@ -202,17 +207,11 @@ class ImeInput { bool GetComposition(HWND window_handle, LPARAM lparam, ImeComposition* composition); - // Enable the IME attached to the given window, i.e. allows user-input + // Enables the IME attached to the given window, i.e. allows user-input // events to be dispatched to the IME. - // In Chrome, this function is used when: - // * a renderer process moves its input focus to another edit control, or; - // * a renrerer process moves the position of the focused edit control. // Parameters // * window_handle [in] (HWND) // Represents the window handle of the caller. - // * caret_rect [in] (const gfx::Rect&) - // Represent the rectangle of the input caret. - // This rectangle is used for controlling the positions of IME windows. // * complete [in] (bool) // Represents whether or not to complete the ongoing composition. // + true @@ -222,11 +221,9 @@ class ImeInput { // + false // Just move the IME windows of the ongoing composition to the given // position without finishing it. - void EnableIME(HWND window_handle, - const gfx::Rect& caret_rect, - bool complete); + void EnableIME(HWND window_handle); - // Disable the IME attached to the given window, i.e. prohibits any user-input + // Disables the IME attached to the given window, i.e. prohibits any user-input // events from being dispatched to the IME. // In Chrome, this function is used when: // * a renreder process sets its input focus to a password input. @@ -241,25 +238,27 @@ class ImeInput { // Represents the window handle of the caller. void CancelIME(HWND window_handle); + // Updates the caret position of the given window. + // Parameters + // * window_handle [in] (HWND) + // Represents the window handle of the caller. + // * caret_rect [in] (const gfx::Rect&) + // Represent the rectangle of the input caret. + // This rectangle is used for controlling the positions of IME windows. + void UpdateCaretRect(HWND window_handle, const gfx::Rect& caret_rect); + protected: - // Determines whether or not the given attribute represents a target - // (a.k.a. a selection). - bool IsTargetAttribute(char attribute) const { - return (attribute == ATTR_TARGET_CONVERTED || - attribute == ATTR_TARGET_NOTCONVERTED); - } - - // Retrieve the target area. - void GetCaret(HIMC imm_context, LPARAM lparam, - ImeComposition* composition); - - // Update the position of the IME windows. + // Retrieves the composition information. + void GetCompositionInfo(HIMC imm_context, LPARAM lparam, + ImeComposition* composition); + + // Updates the position of the IME windows. void MoveImeWindow(HWND window_handle, HIMC imm_context); - // Complete the ongoing composition if it exists. + // Completes the ongoing composition if it exists. void CompleteComposition(HWND window_handle, HIMC imm_context); - // Retrieve a string from the IMM. + // Retrieves a string from the IMM. bool GetString(HIMC imm_context, WPARAM lparam, int type, ImeComposition* composition); diff --git a/chrome/browser/renderer_host/gtk_im_context_wrapper.cc b/chrome/browser/renderer_host/gtk_im_context_wrapper.cc index 3badcae..94173e2 100644 --- a/chrome/browser/renderer_host/gtk_im_context_wrapper.cc +++ b/chrome/browser/renderer_host/gtk_im_context_wrapper.cc @@ -7,10 +7,12 @@ #include <gdk/gdk.h> #include <gdk/gdkkeysyms.h> #include <gtk/gtk.h> +#include <algorithm> #include "app/l10n_util.h" #include "base/logging.h" #include "base/string_util.h" +#include "base/third_party/icu/icu_utf.h" #include "chrome/app/chrome_dll_resource.h" #include "chrome/browser/gtk/gtk_util.h" #include "chrome/browser/gtk/menu_gtk.h" @@ -20,6 +22,7 @@ #include "chrome/common/render_messages.h" #include "gfx/rect.h" #include "grit/generated_resources.h" +#include "third_party/skia/include/core/SkColor.h" GtkIMContextWrapper::GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view) : host_view_(host_view), @@ -29,7 +32,8 @@ GtkIMContextWrapper::GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view) is_composing_text_(false), is_enabled_(false), is_in_key_event_handler_(false), - preedit_cursor_position_(0), + preedit_selection_start_(0), + preedit_selection_end_(0), is_preedit_changed_(false) { DCHECK(context_); DCHECK(context_simple_); @@ -122,6 +126,9 @@ void GtkIMContextWrapper::ProcessKeyEvent(GdkEventKey* event) { filtered = gtk_im_context_filter_keypress(context_simple_, event); } + // Reset this flag here, as it's only used in input method callbacks. + is_in_key_event_handler_ = false; + NativeWebKeyboardEvent wke(event); // Send filtered keydown event before sending IME result. @@ -152,13 +159,10 @@ void GtkIMContextWrapper::ProcessKeyEvent(GdkEventKey* event) { ProcessUnfilteredKeyPressEvent(&wke); else if (event->type == GDK_KEY_RELEASE) host_view_->ForwardKeyboardEvent(wke); - - // End of key event processing. - is_in_key_event_handler_ = false; } -void GtkIMContextWrapper::UpdateStatus(int control, - const gfx::Rect& caret_rect) { +void GtkIMContextWrapper::UpdateInputMethodState(WebKit::WebTextInputType type, + const gfx::Rect& caret_rect) { // The renderer has updated its IME status. // Control the GtkIMContext object according to this status. if (!context_ || !is_focused_) @@ -166,37 +170,16 @@ void GtkIMContextWrapper::UpdateStatus(int control, DCHECK(!is_in_key_event_handler_); - // TODO(james.su@gmail.com): Following code causes a side effect: - // When trying to move cursor from one text input box to another while - // composition text is still not confirmed, following CompleteComposition() - // calls will prevent the cursor from moving outside the first input box. - if (control == IME_DISABLE) { - if (is_enabled_) { - CompleteComposition(); - gtk_im_context_reset(context_simple_); - gtk_im_context_focus_out(context_); - is_enabled_ = false; - } - } else if (control == IME_CANCEL_COMPOSITION) { - preedit_text_.clear(); - preedit_cursor_position_ = 0; - gtk_im_context_reset(context_simple_); - gtk_im_context_reset(context_); - } else { - // Enable the GtkIMContext object if it's not enabled yet. - if (!is_enabled_) { - // Reset context_simple_ to its initial state, in case it's currently - // in middle of a composition session inside a password box. - gtk_im_context_reset(context_simple_); + bool is_enabled = (type == WebKit::WebTextInputTypeText); + if (is_enabled_ != is_enabled) { + is_enabled_ = is_enabled; + if (is_enabled) gtk_im_context_focus_in(context_); - // It might be true when switching from a password box in middle of a - // composition session. - is_composing_text_ = false; - is_enabled_ = true; - } else if (control == IME_COMPLETE_COMPOSITION) { - CompleteComposition(); - } + else + gtk_im_context_focus_out(context_); + } + if (is_enabled) { // 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 @@ -225,7 +208,7 @@ void GtkIMContextWrapper::OnFocusIn() { // Enables RenderWidget's IME related events, so that we can be notified // when WebKit wants to enable or disable IME. - host_view_->GetRenderWidgetHost()->ImeSetInputMode(true); + host_view_->GetRenderWidgetHost()->SetInputMethodActive(true); } void GtkIMContextWrapper::OnFocusOut() { @@ -240,7 +223,7 @@ void GtkIMContextWrapper::OnFocusOut() { // enabled by WebKit. if (is_enabled_) { // To reset the GtkIMContext object and prevent data loss. - CompleteComposition(); + ConfirmComposition(); gtk_im_context_focus_out(context_); } @@ -248,13 +231,10 @@ void GtkIMContextWrapper::OnFocusOut() { gtk_im_context_reset(context_simple_); gtk_im_context_focus_out(context_simple_); - // Reset stored IME status. is_composing_text_ = false; - preedit_text_.clear(); - preedit_cursor_position_ = 0; // Disable RenderWidget's IME related events to save bandwidth. - host_view_->GetRenderWidgetHost()->ImeSetInputMode(false); + host_view_->GetRenderWidgetHost()->SetInputMethodActive(false); } void GtkIMContextWrapper::AppendInputMethodsContextMenu(MenuGtk* menu) { @@ -269,6 +249,31 @@ void GtkIMContextWrapper::AppendInputMethodsContextMenu(MenuGtk* menu) { menu->AppendMenuItem(IDC_INPUT_METHODS_MENU, menuitem); } +void GtkIMContextWrapper::CancelComposition() { + if (!is_enabled_) + return; + + DCHECK(!is_in_key_event_handler_); + + // To prevent any text from being committed when resetting the |context_|; + is_in_key_event_handler_ = true; + + gtk_im_context_reset(context_); + gtk_im_context_reset(context_simple_); + + // Some input methods may not honour the reset call. Focusing out/in the + // |context_| to make sure it gets reset correctly. + gtk_im_context_focus_out(context_); + gtk_im_context_focus_in(context_); + + is_composing_text_ = false; + preedit_text_.clear(); + preedit_underlines_.clear(); + commit_text_.clear(); + + is_in_key_event_handler_ = false; +} + bool GtkIMContextWrapper::NeedCommitByForwardingCharEvent() { // If there is no composition text and has only one character to be // committed, then the character will be send to webkit as a Char event @@ -374,43 +379,28 @@ void GtkIMContextWrapper::ProcessInputMethodResult(const GdkEventKey* event, // preedit text again. if (is_preedit_changed_) { if (preedit_text_.length()) { - host->ImeSetComposition(preedit_text_, preedit_cursor_position_, - -1, -1); + // Another composition session has been started. + is_composing_text_ = true; + host->ImeSetComposition(preedit_text_, preedit_underlines_, + preedit_selection_start_, preedit_selection_end_); } else if (!committed) { host->ImeCancelComposition(); } } } -void GtkIMContextWrapper::CompleteComposition() { +void GtkIMContextWrapper::ConfirmComposition() { if (!is_enabled_) return; - // If WebKit requires to complete current composition, then we need commit - // existing preedit text and reset the GtkIMContext object. - - // Backup existing preedit text to avoid it's being cleared when resetting - // the GtkIMContext object. - string16 old_preedit_text = preedit_text_; - - // Clear it so that we can know if anything is committed by following - // line. - commit_text_.clear(); + DCHECK(!is_in_key_event_handler_); - // Resetting the GtkIMContext. Input method may commit something at this - // point. In this case, we shall not commit the preedit text again. - gtk_im_context_reset(context_); + if (is_composing_text_) { + host_view_->GetRenderWidgetHost()->ImeConfirmComposition(); - // If nothing was committed by above line, then commit stored preedit text - // to prevent data loss. - if (old_preedit_text.length() && commit_text_.length() == 0) { - host_view_->GetRenderWidgetHost()->ImeConfirmComposition( - old_preedit_text); + // Reset the input method. + CancelComposition(); } - - is_composing_text_ = false; - preedit_text_.clear(); - preedit_cursor_position_ = 0; } void GtkIMContextWrapper::HandleCommit(const string16& text) { @@ -423,46 +413,41 @@ void GtkIMContextWrapper::HandleCommit(const string16& text) { // It's possible that commit signal is fired without a key event, for // example when user input via a voice or handwriting recognition software. // In this case, the text must be committed directly. - if (!is_in_key_event_handler_) { + if (!is_in_key_event_handler_) host_view_->GetRenderWidgetHost()->ImeConfirmComposition(text); - } } void GtkIMContextWrapper::HandlePreeditStart() { is_composing_text_ = true; } -void GtkIMContextWrapper::HandlePreeditChanged(const string16& text, +void GtkIMContextWrapper::HandlePreeditChanged(const gchar* text, + PangoAttrList* attrs, int cursor_position) { - bool changed = false; - // If preedit text or cursor position is not changed since last time, - // then it's not necessary to update it again. - // Preedit text is always stored, so that we can commit it when webkit - // requires. // Don't set is_preedit_changed_ to false if there is no change, because // this handler might be called multiple times with the same data. - if (cursor_position != preedit_cursor_position_ || text != preedit_text_) { - preedit_text_ = text; - preedit_cursor_position_ = cursor_position; - is_preedit_changed_ = true; - changed = true; - } + is_preedit_changed_ = true; + preedit_text_.clear(); + preedit_underlines_.clear(); + preedit_selection_start_ = 0; + preedit_selection_end_ = 0; + + ExtractCompositionInfo(text, attrs, cursor_position, &preedit_text_, + &preedit_underlines_, &preedit_selection_start_, + &preedit_selection_end_); // In case we are using a buggy input method which doesn't fire // "preedit_start" signal. - if (text.length()) + if (preedit_text_.length()) is_composing_text_ = true; // Nothing needs to do, if it's currently in ProcessKeyEvent() // handler, which will send preedit text to webkit later. // Otherwise, we need send it here if it's been changed. - if (!is_in_key_event_handler_ && changed) { - if (text.length()) { - host_view_->GetRenderWidgetHost()->ImeSetComposition( - text, cursor_position, -1, -1); - } else { - host_view_->GetRenderWidgetHost()->ImeCancelComposition(); - } + if (!is_in_key_event_handler_) { + host_view_->GetRenderWidgetHost()->ImeSetComposition( + preedit_text_, preedit_underlines_, preedit_selection_start_, + preedit_selection_end_); } } @@ -471,7 +456,7 @@ void GtkIMContextWrapper::HandlePreeditEnd() { if (preedit_text_.length()) { // The composition session has been finished. preedit_text_.clear(); - preedit_cursor_position_ = 0; + preedit_underlines_.clear(); is_preedit_changed_ = true; changed = true; } @@ -479,9 +464,8 @@ void GtkIMContextWrapper::HandlePreeditEnd() { // If there is still a preedit text when firing "preedit-end" signal, // we need inform webkit to clear it. // It's only necessary when it's not in ProcessKeyEvent (). - if (!is_in_key_event_handler_ && changed) { + if (!is_in_key_event_handler_ && changed) host_view_->GetRenderWidgetHost()->ImeCancelComposition(); - } // Don't set is_composing_text_ to false here, because "preedit_end" // signal may be fired before "commit" signal. @@ -515,10 +499,12 @@ void GtkIMContextWrapper::HandlePreeditStartThunk( void GtkIMContextWrapper::HandlePreeditChangedThunk( GtkIMContext* context, GtkIMContextWrapper* self) { gchar* text = NULL; + PangoAttrList* attrs = NULL; gint cursor_position = 0; - gtk_im_context_get_preedit_string(context, &text, NULL, &cursor_position); - self->HandlePreeditChanged(UTF8ToUTF16(text), cursor_position); + gtk_im_context_get_preedit_string(context, &text, &attrs, &cursor_position); + self->HandlePreeditChanged(text, attrs, cursor_position); g_free(text); + pango_attr_list_unref(attrs); } void GtkIMContextWrapper::HandlePreeditEndThunk( @@ -535,3 +521,97 @@ void GtkIMContextWrapper::HandleHostViewUnrealizeThunk( GtkWidget* widget, GtkIMContextWrapper* self) { self->HandleHostViewUnrealize(); } + +void GtkIMContextWrapper::ExtractCompositionInfo( + const gchar* utf8_text, + PangoAttrList* attrs, + int cursor_position, + string16* utf16_text, + std::vector<WebKit::WebCompositionUnderline>* underlines, + int* selection_start, + int* selection_end) { + *utf16_text = UTF8ToUTF16(utf8_text); + + if (utf16_text->empty()) + return; + + // Gtk/Pango uses character index for cursor position and byte index for + // attribute range, but we use char16 offset for them. So we need to do + // conversion here. + std::vector<int> char16_offsets; + int length = static_cast<int>(utf16_text->length()); + for (int offset = 0; offset < length; ++offset) { + char16_offsets.push_back(offset); + if (CBU16_IS_SURROGATE((*utf16_text)[offset])) + ++offset; + } + + // The text length in Unicode characters. + int char_length = static_cast<int>(char16_offsets.size()); + // Make sure we can convert the value of |char_length| as well. + char16_offsets.push_back(length); + + int cursor_offset = + char16_offsets[std::max(0, std::min(char_length, cursor_position))]; + + // TODO(suzhe): due to a bug of webkit, we currently can't use selection range + // with composition string. See: https://bugs.webkit.org/show_bug.cgi?id=40805 + *selection_start = *selection_end = cursor_offset; + + if (attrs) { + int utf8_length = strlen(utf8_text); + PangoAttrIterator* iter = pango_attr_list_get_iterator(attrs); + + // We only care about underline and background attributes and convert + // background attribute into selection if possible. + do { + gint start, end; + pango_attr_iterator_range(iter, &start, &end); + + start = std::min(start, utf8_length); + end = std::min(end, utf8_length); + if (start >= end) + continue; + + start = g_utf8_pointer_to_offset(utf8_text, utf8_text + start); + end = g_utf8_pointer_to_offset(utf8_text, utf8_text + end); + + // Double check, in case |utf8_text| is not a valid utf-8 string. + start = std::min(start, char_length); + end = std::min(end, char_length); + if (start >= end) + continue; + + PangoAttribute* background_attr = + pango_attr_iterator_get(iter, PANGO_ATTR_BACKGROUND); + PangoAttribute* underline_attr = + pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE); + + if (background_attr || underline_attr) { + // Use a black thin underline by default. + WebKit::WebCompositionUnderline underline( + char16_offsets[start], char16_offsets[end], SK_ColorBLACK, false); + + // Always use thick underline for a range with background color, which + // is usually the selection range. + if (background_attr) + underline.thick = true; + if (underline_attr) { + int type = reinterpret_cast<PangoAttrInt*>(underline_attr)->value; + if (type == PANGO_UNDERLINE_DOUBLE) + underline.thick = true; + else if (type == PANGO_UNDERLINE_ERROR) + underline.color = SK_ColorRED; + } + underlines->push_back(underline); + } + } while (pango_attr_iterator_next(iter)); + pango_attr_iterator_destroy(iter); + } + + // Use a black thin underline by default. + if (underlines->empty()) { + underlines->push_back( + WebKit::WebCompositionUnderline(0, length, SK_ColorBLACK, false)); + } +} diff --git a/chrome/browser/renderer_host/gtk_im_context_wrapper.h b/chrome/browser/renderer_host/gtk_im_context_wrapper.h index ef0ac18..c33c290 100644 --- a/chrome/browser/renderer_host/gtk_im_context_wrapper.h +++ b/chrome/browser/renderer_host/gtk_im_context_wrapper.h @@ -6,9 +6,14 @@ #define CHROME_BROWSER_RENDERER_HOST_GTK_IM_CONTEXT_WRAPPER_H_ #include <gdk/gdk.h> +#include <pango/pango-attributes.h> +#include <vector> #include "base/basictypes.h" #include "base/string16.h" +#include "testing/gtest/include/gtest/gtest_prod.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCompositionUnderline.h" +#include "third_party/WebKit/WebKit/chromium/public/WebTextInputType.h" namespace gfx { class Rect; @@ -39,14 +44,22 @@ class GtkIMContextWrapper { // Processes a gdk key event received by |host_view|. void ProcessKeyEvent(GdkEventKey* event); - // Updates IME status and caret position. - void UpdateStatus(int control, const gfx::Rect& caret_rect); + void UpdateInputMethodState(WebKit::WebTextInputType type, + const gfx::Rect& caret_rect); void OnFocusIn(); void OnFocusOut(); void AppendInputMethodsContextMenu(MenuGtk* menu); + void CancelComposition(); + + void ConfirmComposition(); + private: + // For unit tests. + class GtkIMContextWrapperTest; + FRIEND_TEST(GtkIMContextWrapperTest, ExtractCompositionInfo); + // Check if a text needs commit by forwarding a char event instead of // by confirming as a composition text. bool NeedCommitByForwardingCharEvent(); @@ -58,8 +71,6 @@ class GtkIMContextWrapper { // |filtered| indicates if the key event was filtered by the input method. void ProcessInputMethodResult(const GdkEventKey* event, bool filtered); - void CompleteComposition(); - // Real code of "commit" signal handler. void HandleCommit(const string16& text); @@ -67,7 +78,9 @@ class GtkIMContextWrapper { void HandlePreeditStart(); // Real code of "preedit-changed" signal handler. - void HandlePreeditChanged(const string16& text, int cursor_position); + void HandlePreeditChanged(const gchar* text, + PangoAttrList* attrs, + int cursor_position); // Real code of "preedit-end" signal handler. void HandlePreeditEnd(); @@ -96,6 +109,17 @@ class GtkIMContextWrapper { static void HandleHostViewUnrealizeThunk(GtkWidget* widget, GtkIMContextWrapper* self); + // Extracts composition underlines, selection range and utf-16 text from given + // utf-8 text, pango attributes and cursor position. + static void ExtractCompositionInfo( + const gchar* utf8_text, + PangoAttrList* attrs, + int cursor_position, + string16* utf16_text, + std::vector<WebKit::WebCompositionUnderline>* underlines, + int* selection_start, + int* selection_end); + // The parent object. RenderWidgetHostViewGtk* host_view_; @@ -139,12 +163,6 @@ class GtkIMContextWrapper { bool is_composing_text_; // Whether or not the IME is enabled. - // This flag is actually controlled by RenderWidget. - // It shall be set to false when an ImeUpdateStatus message with control == - // IME_DISABLE is received, and shall be set to true if control == - // IME_COMPLETE_COMPOSITION or IME_MOVE_WINDOWS. - // When this flag is false, keyboard events shall be dispatched directly - // instead of sending to context_. bool is_enabled_; // Whether or not it's currently running inside key event handler. @@ -154,13 +172,15 @@ class GtkIMContextWrapper { bool is_in_key_event_handler_; // Stores a copy of the most recent preedit text retrieved from context_. - // When an ImeUpdateStatus message with control == IME_COMPLETE_COMPOSITION - // is received, this stored preedit text (if not empty) shall be committed, - // and context_ shall be reset. string16 preedit_text_; - // Stores the cursor position in the stored preedit text. - int preedit_cursor_position_; + // Stores the selection range in the stored preedit text. + int preedit_selection_start_; + int preedit_selection_end_; + + // Stores composition underlines computed from the pango attributes of the + // most recent preedit text. + std::vector<WebKit::WebCompositionUnderline> preedit_underlines_; // Whether or not the preedit has been changed since last key event. bool is_preedit_changed_; diff --git a/chrome/browser/renderer_host/gtk_im_context_wrapper_unittest.cc b/chrome/browser/renderer_host/gtk_im_context_wrapper_unittest.cc new file mode 100644 index 0000000..5b8fcb5 --- /dev/null +++ b/chrome/browser/renderer_host/gtk_im_context_wrapper_unittest.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "chrome/browser/renderer_host/gtk_im_context_wrapper.h" + +#include <string> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkColor.h" + +namespace { + +struct AttributeInfo { + int type; + int value; + int start_offset; + int end_offset; +}; + +struct Underline { + unsigned startOffset; + unsigned endOffset; + uint32 color; + bool thick; +}; + +struct TestData { + const char* text; + const AttributeInfo attrs[10]; + const Underline underlines[10]; +}; + +const TestData kTestData[] = { + // Normal case + { "One Two Three", + { { PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_SINGLE, 0, 3 }, + { PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_DOUBLE, 4, 7 }, + { PANGO_ATTR_BACKGROUND, 0, 4, 7 }, + { PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_SINGLE, 8, 13 }, + { 0, 0, 0, 0 } }, + { { 0, 3, SK_ColorBLACK, false }, + { 4, 7, SK_ColorBLACK, true }, + { 8, 13, SK_ColorBLACK, false }, + { 0, 0, 0, false } } + }, + + // Offset overflow. + { "One Two Three", + { { PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_SINGLE, 0, 3 }, + { PANGO_ATTR_BACKGROUND, 0, 4, 7 }, + { PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_SINGLE, 8, 20 }, + { 0, 0, 0, 0 } }, + { { 0, 3, SK_ColorBLACK, false }, + { 4, 7, SK_ColorBLACK, true }, + { 8, 13, SK_ColorBLACK, false }, + { 0, 0, 0, false} } + }, + + // Error underline. + { "One Two Three", + { { PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_SINGLE, 0, 3 }, + { PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_ERROR, 4, 7 }, + { PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_SINGLE, 8, 13 }, + { 0, 0, 0, 0 } }, + { { 0, 3, SK_ColorBLACK, false }, + { 4, 7, SK_ColorRED, false }, + { 8, 13, SK_ColorBLACK, false }, + { 0, 0, 0, false} } + }, + + // Default underline. + { "One Two Three", + { { 0, 0, 0, 0 } }, + { { 0, 13, SK_ColorBLACK, false }, + { 0, 0, 0, false } } + }, + + // Unicode, including non-BMP characters: "123你好𠀀𠀁一丁 456" + { "123\xE4\xBD\xA0\xE5\xA5\xBD\xF0\xA0\x80\x80\xF0\xA0\x80\x81\xE4\xB8\x80\xE4\xB8\x81 456", + { { PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_SINGLE, 0, 3 }, + { PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_SINGLE, 3, 5 }, + { PANGO_ATTR_BACKGROUND, 0, 5, 7 }, + { PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_SINGLE, 7, 13 }, + { 0, 0, 0, 0 } }, + { { 0, 3, SK_ColorBLACK, false }, + { 3, 5, SK_ColorBLACK, false }, + { 5, 9, SK_ColorBLACK, true }, + { 9, 15, SK_ColorBLACK, false }, + { 0, 0, 0, false } } + }, +}; + +void CompareUnderline(const Underline& a, + const WebKit::WebCompositionUnderline& b) { + EXPECT_EQ(a.startOffset, b.startOffset); + EXPECT_EQ(a.endOffset, b.endOffset); + EXPECT_EQ(a.color, b.color); + EXPECT_EQ(a.thick, b.thick); +} + +class GtkIMContextWrapperTest : public testing::Test { +}; + +} // namespace + +TEST(GtkIMContextWrapperTest, ExtractCompositionInfo) { + for (size_t i = 0; i < arraysize(kTestData); ++i) { + const char* text = kTestData[i].text; + const AttributeInfo* attrs = kTestData[i].attrs; + SCOPED_TRACE(testing::Message() << "Testing:" << i + << " text:" << text); + + PangoAttrList* pango_attrs = pango_attr_list_new(); + for (size_t a = 0; attrs[a].type; ++a) { + PangoAttribute* pango_attr = NULL; + switch (attrs[a].type) { + case PANGO_ATTR_UNDERLINE: + pango_attr = pango_attr_underline_new( + static_cast<PangoUnderline>(attrs[a].value)); + break; + case PANGO_ATTR_BACKGROUND: + pango_attr = pango_attr_background_new(0, 0, 0); + break; + default: + NOTREACHED(); + } + pango_attr->start_index = + g_utf8_offset_to_pointer(text, attrs[a].start_offset) - text; + pango_attr->end_index = + g_utf8_offset_to_pointer(text, attrs[a].end_offset) - text; + pango_attr_list_insert(pango_attrs, pango_attr); + } + + string16 utf16_text; + std::vector<WebKit::WebCompositionUnderline> results; + int selection_start; + int selection_end; + + GtkIMContextWrapper::ExtractCompositionInfo(text, pango_attrs, 0, + &utf16_text, &results, &selection_start, &selection_end); + + const Underline* underlines = kTestData[i].underlines; + for (size_t u = 0; underlines[u].color && u < results.size(); ++u) { + SCOPED_TRACE(testing::Message() << "Underline:" << u); + CompareUnderline(underlines[u], results[u]); + } + + pango_attr_list_unref(pango_attrs); + } +} diff --git a/chrome/browser/renderer_host/render_widget_host.cc b/chrome/browser/renderer_host/render_widget_host.cc index e84a051..1920101 100644 --- a/chrome/browser/renderer_host/render_widget_host.cc +++ b/chrome/browser/renderer_host/render_widget_host.cc @@ -148,7 +148,10 @@ void RenderWidgetHost::OnMessageReceived(const IPC::Message &msg) { IPC_MESSAGE_HANDLER(ViewHostMsg_Focus, OnMsgFocus) IPC_MESSAGE_HANDLER(ViewHostMsg_Blur, OnMsgBlur) IPC_MESSAGE_HANDLER(ViewHostMsg_SetCursor, OnMsgSetCursor) - IPC_MESSAGE_HANDLER(ViewHostMsg_ImeUpdateStatus, OnMsgImeUpdateStatus) + IPC_MESSAGE_HANDLER(ViewHostMsg_ImeUpdateTextInputState, + OnMsgImeUpdateTextInputState) + IPC_MESSAGE_HANDLER(ViewHostMsg_ImeCancelComposition, + OnMsgImeCancelComposition) IPC_MESSAGE_HANDLER(ViewHostMsg_GpuRenderingActivated, OnMsgGpuRenderingActivated) #if defined(OS_LINUX) @@ -601,30 +604,32 @@ void RenderWidgetHost::NotifyTextDirection() { } } -void RenderWidgetHost::ImeSetInputMode(bool activate) { - Send(new ViewMsg_ImeSetInputMode(routing_id(), activate)); +void RenderWidgetHost::SetInputMethodActive(bool activate) { + Send(new ViewMsg_SetInputMethodActive(routing_id(), activate)); } -void RenderWidgetHost::ImeSetComposition(const string16& ime_string, - int cursor_position, - int target_start, - int target_end) { - Send(new ViewMsg_ImeSetComposition(routing_id(), - WebKit::WebCompositionCommandSet, - cursor_position, target_start, target_end, - ime_string)); +void RenderWidgetHost::ImeSetComposition( + const string16& text, + const std::vector<WebKit::WebCompositionUnderline>& underlines, + int selection_start, + int selection_end) { + Send(new ViewMsg_ImeSetComposition( + routing_id(), text, underlines, selection_start, selection_end)); } -void RenderWidgetHost::ImeConfirmComposition(const string16& ime_string) { +void RenderWidgetHost::ImeConfirmComposition(const string16& text) { Send(new ViewMsg_ImeSetComposition(routing_id(), - WebKit::WebCompositionCommandConfirm, - -1, -1, -1, ime_string)); + text, std::vector<WebKit::WebCompositionUnderline>(), 0, 0)); + Send(new ViewMsg_ImeConfirmComposition(routing_id())); +} + +void RenderWidgetHost::ImeConfirmComposition() { + Send(new ViewMsg_ImeConfirmComposition(routing_id())); } void RenderWidgetHost::ImeCancelComposition() { - Send(new ViewMsg_ImeSetComposition(routing_id(), - WebKit::WebCompositionCommandDiscard, - -1, -1, -1, string16())); + Send(new ViewMsg_ImeSetComposition(routing_id(), string16(), + std::vector<WebKit::WebCompositionUnderline>(), 0, 0)); } gfx::Rect RenderWidgetHost::GetRootWindowResizerRect() const { @@ -888,11 +893,16 @@ void RenderWidgetHost::OnMsgSetCursor(const WebCursor& cursor) { view_->UpdateCursor(cursor); } -void RenderWidgetHost::OnMsgImeUpdateStatus(int control, - const gfx::Rect& caret_rect) { - if (view_) { - view_->IMEUpdateStatus(control, caret_rect); - } +void RenderWidgetHost::OnMsgImeUpdateTextInputState( + WebKit::WebTextInputType type, + const gfx::Rect& caret_rect) { + if (view_) + view_->ImeUpdateTextInputState(type, caret_rect); +} + +void RenderWidgetHost::OnMsgImeCancelComposition() { + if (view_) + view_->ImeCancelComposition(); } void RenderWidgetHost::OnMsgGpuRenderingActivated(bool activated) { diff --git a/chrome/browser/renderer_host/render_widget_host.h b/chrome/browser/renderer_host/render_widget_host.h index f4aa8df..c7e3cd6 100644 --- a/chrome/browser/renderer_host/render_widget_host.h +++ b/chrome/browser/renderer_host/render_widget_host.h @@ -22,7 +22,9 @@ #include "gfx/size.h" #include "ipc/ipc_channel.h" #include "ipc/ipc_channel_handle.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCompositionUnderline.h" #include "third_party/WebKit/WebKit/chromium/public/WebTextDirection.h" +#include "third_party/WebKit/WebKit/chromium/public/WebTextInputType.h" namespace gfx { class Rect; @@ -316,19 +318,19 @@ 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); + // Notifies the renderer whether or not the input method attached to this + // process is activated. + // When the input method 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 input method windows under the cursor.) + void SetInputMethodActive(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. + // WebKit has a special node (a composition node) for input method to change + // its text without affecting any other DOM nodes. When the input method + // (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. @@ -339,10 +341,11 @@ class RenderWidgetHost : public IPC::Channel::Listener, // (on Windows); // * when it receives a "preedit_changed" signal of GtkIMContext (on Linux); // * when markedText of NSTextInput is called (on Mac). - void ImeSetComposition(const string16& ime_string, - int cursor_position, - int target_start, - int target_end); + void ImeSetComposition( + const string16& text, + const std::vector<WebKit::WebCompositionUnderline>& underlines, + int selection_start, + int selection_end); // Finishes an ongoing composition with the specified text. // A browser should call this function: @@ -350,7 +353,11 @@ class RenderWidgetHost : public IPC::Channel::Listener, // (on Windows); // * when it receives a "commit" signal of GtkIMContext (on Linux); // * when insertText of NSTextInput is called (on Mac). - void ImeConfirmComposition(const string16& ime_string); + void ImeConfirmComposition(const string16& text); + + // Finishes an ongoing composition with the composition text set by last + // SetComposition() call. + void ImeConfirmComposition(); // Cancels an ongoing composition. void ImeCancelComposition(); @@ -480,9 +487,9 @@ class RenderWidgetHost : public IPC::Channel::Listener, void OnMsgBlur(); void OnMsgSetCursor(const WebCursor& cursor); - // Using int instead of ViewHostMsg_ImeControl for control's type to avoid - // having to bring in render_messages.h in a header file. - void OnMsgImeUpdateStatus(int control, const gfx::Rect& caret_rect); + void OnMsgImeUpdateTextInputState(WebKit::WebTextInputType type, + const gfx::Rect& caret_rect); + void OnMsgImeCancelComposition(); void OnMsgGpuRenderingActivated(bool activated); diff --git a/chrome/browser/renderer_host/render_widget_host_view.h b/chrome/browser/renderer_host/render_widget_host_view.h index d9e7bae..9a20af0 100644 --- a/chrome/browser/renderer_host/render_widget_host_view.h +++ b/chrome/browser/renderer_host/render_widget_host_view.h @@ -14,6 +14,7 @@ #include "gfx/native_widget_types.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/WebKit/WebKit/chromium/public/WebPopupType.h" +#include "third_party/WebKit/WebKit/chromium/public/WebTextInputType.h" #include "webkit/glue/plugins/webplugin.h" #include "webkit/glue/webaccessibility.h" @@ -109,8 +110,12 @@ class RenderWidgetHostView { // Indicates whether the page has finished loading. virtual void SetIsLoading(bool is_loading) = 0; - // Enable or disable IME for the view. - virtual void IMEUpdateStatus(int control, const gfx::Rect& caret_rect) = 0; + // Updates the state of the input method attached to the view. + virtual void ImeUpdateTextInputState(WebKit::WebTextInputType type, + const gfx::Rect& caret_rect) = 0; + + // Cancel the ongoing composition of the input method attached to the view. + virtual void ImeCancelComposition() = 0; // Informs the view that a portion of the widget's backing store was scrolled // and/or painted. The view should ensure this gets copied to the screen. 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 c3ce693..542dcf6 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc +++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc @@ -230,6 +230,10 @@ class RenderWidgetHostViewGtkWidget { if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS) return FALSE; + // Confirm existing composition text on mouse click events, to make sure + // the input caret won't be moved with an ongoing composition session. + host_view->im_context_->ConfirmComposition(); + // We want to translate the coordinates of events that do not originate // from this widget to be relative to the top left of the widget. GtkWidget* event_widget = gtk_get_event_widget( @@ -650,9 +654,14 @@ void RenderWidgetHostViewGtk::SetIsLoading(bool is_loading) { ShowCurrentCursor(); } -void RenderWidgetHostViewGtk::IMEUpdateStatus(int control, - const gfx::Rect& caret_rect) { - im_context_->UpdateStatus(control, caret_rect); +void RenderWidgetHostViewGtk::ImeUpdateTextInputState( + WebKit::WebTextInputType type, + const gfx::Rect& caret_rect) { + im_context_->UpdateInputMethodState(type, caret_rect); +} + +void RenderWidgetHostViewGtk::ImeCancelComposition() { + im_context_->CancelComposition(); } void RenderWidgetHostViewGtk::DidUpdateBackingStore( 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 fa5ff74..f33cd55 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_gtk.h +++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.h @@ -65,7 +65,9 @@ class RenderWidgetHostViewGtk : public RenderWidgetHostView { virtual gfx::Rect GetViewBounds() const; virtual void UpdateCursor(const WebCursor& cursor); virtual void SetIsLoading(bool is_loading); - virtual void IMEUpdateStatus(int control, const gfx::Rect& caret_rect); + virtual void ImeUpdateTextInputState(WebKit::WebTextInputType type, + const gfx::Rect& caret_rect); + virtual void ImeCancelComposition(); virtual void DidUpdateBackingStore( const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy, const std::vector<gfx::Rect>& copy_rects); diff --git a/chrome/browser/renderer_host/render_widget_host_view_mac.h b/chrome/browser/renderer_host/render_widget_host_view_mac.h index d12458c..0ed5a3b 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_mac.h +++ b/chrome/browser/renderer_host/render_widget_host_view_mac.h @@ -15,6 +15,7 @@ #include "chrome/browser/cocoa/base_view.h" #include "chrome/browser/renderer_host/accelerated_surface_container_manager_mac.h" #include "chrome/browser/renderer_host/render_widget_host_view.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCompositionUnderline.h" #include "webkit/glue/webcursor.h" #include "webkit/glue/webmenuitem.h" @@ -32,7 +33,8 @@ class RWHVMEditCommandHelper; // when it's removed from the view system. @interface RenderWidgetHostViewCocoa - : BaseView <RenderWidgetHostViewMacOwner, NSTextInput, NSChangeSpelling> { + : BaseView <RenderWidgetHostViewMacOwner, NSTextInputClient, + NSChangeSpelling> { @private scoped_ptr<RenderWidgetHostViewMac> renderWidgetHostView_; BOOL canBeKeyView_; @@ -79,6 +81,10 @@ class RWHVMEditCommandHelper; // Indicates if there is any marked text. BOOL hasMarkedText_; + // Indicates if unmarkText is called or not when handling a keyboard + // event. + BOOL unmarkTextCalled_; + // The range of current marked text inside the whole content of the DOM node // being edited. // TODO(suzhe): This is currently a fake value, as we do not support accessing @@ -95,8 +101,11 @@ class RWHVMEditCommandHelper; // Text to be inserted which was generated by handling a key down event. string16 textToBeInserted_; - // New marked text which was generated by handling a key down event. - string16 newMarkedText_; + // Marked text which was generated by handling a key down event. + string16 markedText_; + + // Underline information of the |markedText_|. + std::vector<WebKit::WebCompositionUnderline> underlines_; } @property(assign, nonatomic) NSRect caretRect; @@ -119,6 +128,8 @@ class RWHVMEditCommandHelper; - (void)renderWidgetHostWasResized; // Cancel ongoing composition (abandon the marked text). - (void)cancelComposition; +// Confirm ongoing composition. +- (void)confirmComposition; @end @@ -166,7 +177,9 @@ class RenderWidgetHostViewMac : public RenderWidgetHostView { virtual gfx::Rect GetViewBounds() const; virtual void UpdateCursor(const WebCursor& cursor); virtual void SetIsLoading(bool is_loading); - virtual void IMEUpdateStatus(int control, const gfx::Rect& caret_rect); + virtual void ImeUpdateTextInputState(WebKit::WebTextInputType state, + const gfx::Rect& caret_rect); + virtual void ImeCancelComposition(); virtual void DidUpdateBackingStore( const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy, const std::vector<gfx::Rect>& copy_rects); @@ -214,6 +227,8 @@ class RenderWidgetHostViewMac : public RenderWidgetHostView { void set_parent_view(NSView* parent_view) { parent_view_ = parent_view; } + void SetTextInputActive(bool active); + // These member variables should be private, but the associated ObjC class // needs access to them and can't be made a friend. @@ -243,6 +258,9 @@ class RenderWidgetHostViewMac : public RenderWidgetHostView { // The time it took after this view was selected for it to be fully painted. base::TimeTicks tab_switch_paint_time_; + // Current text input type. + WebKit::WebTextInputType text_input_type_; + private: // Updates the display cursor to the current cursor if the cursor is over this // render view. diff --git a/chrome/browser/renderer_host/render_widget_host_view_mac.mm b/chrome/browser/renderer_host/render_widget_host_view_mac.mm index bf30c38..fa1fb81 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_mac.mm +++ b/chrome/browser/renderer_host/render_widget_host_view_mac.mm @@ -9,6 +9,7 @@ #include "app/surface/io_surface_support_mac.h" #import "base/chrome_application_mac.h" #include "base/histogram.h" +#include "base/logging.h" #import "base/scoped_nsobject.h" #include "base/string_util.h" #include "base/sys_string_conversions.h" @@ -25,6 +26,7 @@ #include "chrome/common/plugin_messages.h" #include "chrome/common/render_messages.h" #include "skia/ext/platform_canvas.h" +#include "third_party/skia/include/core/SkColor.h" #include "third_party/WebKit/WebKit/chromium/public/mac/WebInputEventFactory.h" #include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" #include "webkit/glue/webmenurunner_mac.h" @@ -51,13 +53,77 @@ static inline int ToWebKitModifiers(NSUInteger flags) { - (void)attachPluginLayer; @end +// This API was published since 10.6. Provide the declaration so it can be +// // called below when building with the 10.5 SDK. +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 +@class NSTextInputContext; +@interface NSResponder (AppKitDetails) +- (NSTextInputContext *)inputContext; +@end +#endif + namespace { // Maximum number of characters we allow in a tooltip. const size_t kMaxTooltipLength = 1024; +// TODO(suzhe): Upstream this function. +WebKit::WebColor WebColorFromNSColor(NSColor *color) { + CGFloat r, g, b, a; + [color getRed:&r green:&g blue:&b alpha:&a]; + + return + std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 | + std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 | + std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 | + std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255)); +} + +// Extract underline information from an attributed string. +// Mostly copied from third_party/WebKit/WebKit/mac/WebView/WebHTMLView.mm +void ExtractUnderlines( + NSAttributedString* string, + std::vector<WebKit::WebCompositionUnderline>* underlines) { + int length = [[string string] length]; + int i = 0; + while (i < length) { + NSRange range; + NSDictionary* attrs = [string attributesAtIndex:i + longestEffectiveRange:&range + inRange:NSMakeRange(i, length - i)]; + if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) { + WebKit::WebColor color = SK_ColorBLACK; + if (NSColor *colorAttr = + [attrs objectForKey:NSUnderlineColorAttributeName]) { + color = WebColorFromNSColor( + [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]); + } + underlines->push_back(WebKit::WebCompositionUnderline( + range.location, NSMaxRange(range), color, [style intValue] > 1)); + } + i = range.location + range.length; + } +} + +// EnablePasswordInput() and DisablePasswordInput() are copied from +// enableSecureTextInput() and disableSecureTextInput() functions in +// third_party/WebKit/WebCore/platform/SecureTextInput.cpp +// But we don't call EnableSecureEventInput() and DisableSecureEventInput() +// here, because they are already called in webkit and they are system wide +// functions. +void EnablePasswordInput() { + CFArrayRef inputSources = TISCreateASCIICapableInputSourceList(); + TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag, + sizeof(CFArrayRef), &inputSources); + CFRelease(inputSources); } +void DisablePasswordInput() { + TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag); +} + +} // namespace + // AcceleratedPluginLayer ------------------------------------------------------ // This subclass of CAOpenGLLayer hosts the output of accelerated plugins on @@ -131,6 +197,7 @@ RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget) : render_widget_host_(widget), about_to_validate_and_paint_(false), call_set_needs_display_in_rect_pending_(false), + text_input_type_(WebKit::WebTextInputTypeNone), is_loading_(false), is_hidden_(false), is_popup_menu_(false), @@ -314,22 +381,27 @@ void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) { UpdateCursorIfOverSelf(); } -void RenderWidgetHostViewMac::IMEUpdateStatus(int control, - const gfx::Rect& caret_rect) { - // Reset the IME state and finish an ongoing composition in the renderer. - if (control == IME_DISABLE || control == IME_COMPLETE_COMPOSITION || - control == IME_CANCEL_COMPOSITION) { - [cocoa_view_ cancelComposition]; +void RenderWidgetHostViewMac::ImeUpdateTextInputState( + WebKit::WebTextInputType type, + const gfx::Rect& caret_rect) { + if (text_input_type_ != type) { + text_input_type_ = type; + if (HasFocus()) + SetTextInputActive(true); } // We need to convert the coordinate of the cursor rectangle sent from the - // renderer and save it. Our IME backend uses a coordinate system whose - // origin is the upper-left corner of this view. On the other hand, Cocoa - // uses a coordinate system whose origin is the lower-left corner of this - // view. So, we convert the cursor rectangle and save it. + // renderer and save it. Our input method backend uses a coordinate system + // whose origin is the upper-left corner of this view. On the other hand, + // Cocoa uses a coordinate system whose origin is the lower-left corner of + // this view. So, we convert the cursor rectangle and save it. [cocoa_view_ setCaretRect:[cocoa_view_ RectToNSRect:caret_rect]]; } +void RenderWidgetHostViewMac::ImeCancelComposition() { + [cocoa_view_ cancelComposition]; +} + void RenderWidgetHostViewMac::DidUpdateBackingStore( const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy, const std::vector<gfx::Rect>& copy_rects) { @@ -669,6 +741,8 @@ gfx::Rect RenderWidgetHostViewMac::GetRootWindowRect() { void RenderWidgetHostViewMac::SetActive(bool active) { if (render_widget_host_) render_widget_host_->SetActive(active); + if (HasFocus()) + SetTextInputActive(active); } void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) { @@ -701,6 +775,18 @@ bool RenderWidgetHostViewMac::ContainsNativeView( return false; } +void RenderWidgetHostViewMac::SetTextInputActive(bool active) { + if (active) { + if (text_input_type_ == WebKit::WebTextInputTypePassword) + EnablePasswordInput(); + else + DisablePasswordInput(); + } else { + if (text_input_type_ == WebKit::WebTextInputTypePassword) + DisablePasswordInput(); + } +} + // EditCommandMatcher --------------------------------------------------------- // This class is used to capture the shortcuts that a given key event maps to. @@ -791,20 +877,28 @@ bool RenderWidgetHostViewMac::ContainsNativeView( // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel // the popup anyway, so we're OK. + NSEventType type = [theEvent type]; + if (type == NSLeftMouseDown) + hasOpenMouseDown_ = YES; + else if (type == NSLeftMouseUp) + hasOpenMouseDown_ = NO; + + // TODO(suzhe): We should send mouse events to the input method first if it + // wants to handle them. But it won't work without implementing method + // - (NSUInteger)characterIndexForPoint:. + // See: http://code.google.com/p/chromium/issues/detail?id=47141 + // Instead of sending mouse events to the input method first, we now just + // simply confirm all ongoing composition here. + if (type == NSLeftMouseDown || type == NSRightMouseDown || + type == NSOtherMouseDown) { + [self confirmComposition]; + } + const WebMouseEvent& event = WebInputEventFactory::mouseEvent(theEvent, self); + if (renderWidgetHostView_->render_widget_host_) renderWidgetHostView_->render_widget_host_->ForwardMouseEvent(event); - - if ([theEvent type] == NSLeftMouseDown) { - [self cancelComposition]; - - hasOpenMouseDown_ = YES; - } - - if ([theEvent type] == NSLeftMouseUp) { - hasOpenMouseDown_ = NO; - } } - (BOOL)performKeyEquivalent:(NSEvent*)theEvent { @@ -899,7 +993,9 @@ bool RenderWidgetHostViewMac::ContainsNativeView( // These two variables might be set when handling the keyboard event. // Clear them here so that we can know whether they have changed afterwards. textToBeInserted_.clear(); - newMarkedText_.clear(); + markedText_.clear(); + underlines_.clear(); + unmarkTextCalled_ = NO; // Sends key down events to input method first, then we can decide what should // be done according to input method's feedback. @@ -936,7 +1032,7 @@ bool RenderWidgetHostViewMac::ContainsNativeView( // Then send keypress and/or composition related events. // If there was a marked text or the text to be inserted is longer than 1 - // character, then we send the text by calling ImeConfirmComposition(). + // character, then we send the text by calling ConfirmComposition(). // Otherwise, if the text to be inserted only contains 1 character, then we // can just send a keypress event which is fabricated by changing the type of // the keydown event, so that we can retain all necessary informations, such @@ -945,7 +1041,6 @@ bool RenderWidgetHostViewMac::ContainsNativeView( // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only // handle BMP characters here, as we can always insert non-BMP characters as // text. - const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask; BOOL textInserted = NO; if (textToBeInserted_.length() > (oldHasMarkedText ? 0 : 1)) { widgetHost->ImeConfirmComposition(textToBeInserted_); @@ -956,13 +1051,11 @@ bool RenderWidgetHostViewMac::ContainsNativeView( event.text[1] = 0; event.skip_in_browser = true; widgetHost->ForwardKeyboardEvent(event); - } else if (([theEvent modifierFlags] & kCtrlCmdKeyMask) && + } else if (!hasMarkedText_ && !oldHasMarkedText && [[theEvent characters] length] > 0) { // We don't get insertText: calls if ctrl is down and not even a keyDown: - // call if cmd is down, so synthesize a keypress event for these cases. - // Note that this makes our behavior deviate from the windows and linux - // versions of chrome (however, see http://crbug.com/13891 ), but it makes - // us behave similar to how Safari behaves. + // call if cmd is down, or in password input mode, so synthesize a keypress + // event for these cases. event.type = WebKit::WebInputEvent::Char; event.skip_in_browser = true; widgetHost->ForwardKeyboardEvent(event); @@ -970,18 +1063,19 @@ bool RenderWidgetHostViewMac::ContainsNativeView( // Updates or cancels the composition. If some text has been inserted, then // we don't need to cancel the composition explicitly. - if (hasMarkedText_ && newMarkedText_.length()) { + if (hasMarkedText_ && markedText_.length()) { // Sends the updated marked text to the renderer so it can update the // composition node in WebKit. // When marked text is available, |selectedRange_| will be the range being - // selected inside the marked text. We put the cursor at the beginning of - // the selected range. - widgetHost->ImeSetComposition(newMarkedText_, - selectedRange_.location, - selectedRange_.location, - NSMaxRange(selectedRange_)); + // selected inside the marked text. + widgetHost->ImeSetComposition(markedText_, underlines_, + selectedRange_.location, + NSMaxRange(selectedRange_)); } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) { - widgetHost->ImeCancelComposition(); + if (unmarkTextCalled_) + widgetHost->ImeConfirmComposition(); + else + widgetHost->ImeCancelComposition(); } // Possibly autohide the cursor. @@ -1183,19 +1277,34 @@ bool RenderWidgetHostViewMac::ContainsNativeView( return NO; renderWidgetHostView_->render_widget_host_->Focus(); - renderWidgetHostView_->render_widget_host_->ImeSetInputMode(true); + renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true); + renderWidgetHostView_->SetTextInputActive(true); + + // Cancel any onging composition text which was left before we lost focus. + // TODO(suzhe): We should do it in -resignFirstResponder: method, but + // somehow that method won't be called when switching among different tabs. + // See http://crbug.com/47209 + [self cancelComposition]; + return YES; } - (BOOL)resignFirstResponder { + renderWidgetHostView_->SetTextInputActive(false); if (!renderWidgetHostView_->render_widget_host_) return YES; if (closeOnDeactivate_) renderWidgetHostView_->KillSelf(); - renderWidgetHostView_->render_widget_host_->ImeSetInputMode(false); + renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false); renderWidgetHostView_->render_widget_host_->Blur(); + + // We should cancel any onging composition whenever RWH's Blur() method gets + // called, because in this case, webkit will confirm the ongoing composition + // internally. + [self cancelComposition]; + return YES; } @@ -1469,7 +1578,7 @@ static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE; return [[toolTip_ copy] autorelease]; } -// Below is our NSTextInput implementation. +// Below is our NSTextInputClient implementation. // // When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following // functions to process this event. @@ -1509,9 +1618,9 @@ static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE; // // This needs many sync IPC messages sent between a browser and a renderer for // each key event, which would probably result in key-typing jank. -// To avoid this problem, this implementation processes key events (and IME -// events) totally in a browser process and sends asynchronous input events, -// almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a +// To avoid this problem, this implementation processes key events (and input +// method events) totally in a browser process and sends asynchronous input +// events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a // renderer process. // // [RenderWidgetHostViewMac keyEvent] (browser) -> @@ -1546,18 +1655,28 @@ extern NSString *NSTextInputReplacementRangeAttributeName; return NSNotFound; } -- (NSRect)firstRectForCharacterRange:(NSRange)theRange { +- (NSRect)firstRectForCharacterRange:(NSRange)theRange + actualRange:(NSRangePointer)actualRange { // An input method requests a cursor rectangle to display its candidate // window. // Calculate the screen coordinate of the cursor rectangle saved in - // RenderWidgetHostViewMac::IMEUpdateStatus() and send it to the IME. + // RenderWidgetHostViewMac::ImeUpdateTextInputState() and send it to the + // input method. // Since this window may be moved since we receive the cursor rectangle last - // time we sent the cursor rectangle to the IME, so we should map from the - // view coordinate to the screen coordinate every time when an IME need it. + // time we sent the cursor rectangle to the input method, so we should map + // from the view coordinate to the screen coordinate every time when an input + // method need it. NSRect resultRect = [self convertRect:caretRect_ toView:nil]; NSWindow* window = [self window]; if (window) resultRect.origin = [window convertBaseToScreen:resultRect.origin]; + + // If marked text is available, then we actually return the rect of the + // selected range within the marked text. Otherwise, we actually can't get + // the rect of an arbitrary range in the web content, so just return the + // caret rect instead and don't touch actualRange at all. + if (actualRange && hasMarkedText_) + *actualRange = selectedRange_; return resultRect; } @@ -1576,7 +1695,8 @@ extern NSString *NSTextInputReplacementRangeAttributeName; return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0); } -- (NSAttributedString *)attributedSubstringFromRange:(NSRange)nsRange { +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range + actualRange:(NSRangePointer)actualRange { // TODO(hbono): Even though many input method works without implementing // this method, we need to save a copy of the string in the setMarkedText // method and create a NSAttributedString with the given range. @@ -1588,6 +1708,20 @@ extern NSString *NSTextInputReplacementRangeAttributeName; return reinterpret_cast<NSInteger>(self); } +// Each RenderWidgetHostViewCocoa has its own input context, but we return +// nil when the caret is in non-editable content or password box to avoid +// making input methods do their work. +- (NSTextInputContext *)inputContext +{ + switch(renderWidgetHostView_->text_input_type_) { + case WebKit::WebTextInputTypeNone: + case WebKit::WebTextInputTypePassword: + return nil; + default: + return [super inputContext]; + } +} + - (BOOL)hasMarkedText { // An input method calls this function to figure out whether or not an // application is really composing a text. If it is composing, it calls @@ -1605,37 +1739,53 @@ extern NSString *NSTextInputReplacementRangeAttributeName; // text when it cancels an ongoing composition, i.e. I have never seen an // input method calls this method. hasMarkedText_ = NO; + markedText_.clear(); + underlines_.clear(); - // If we are handling a key down event, then ImeCancelComposition() will be + // If we are handling a key down event, then ConfirmComposition() will be // called in keyEvent: method. if (!handlingKeyDown_) - renderWidgetHostView_->render_widget_host_->ImeCancelComposition(); + renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(); + else + unmarkTextCalled_ = YES; } -- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange { +- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange + replacementRange:(NSRange)replacementRange { // An input method updates the composition string. // We send the given text and range to the renderer so it can update the // composition node of WebKit. + // TODO(suzhe): It's hard for us to support replacementRange without accessing + // the full web content. BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; NSString* im_text = isAttributedString ? [string string] : string; int length = [im_text length]; markedRange_ = NSMakeRange(0, length); selectedRange_ = newSelRange; - newMarkedText_ = base::SysNSStringToUTF16(im_text); + markedText_ = base::SysNSStringToUTF16(im_text); hasMarkedText_ = (length > 0); - // If we are handling a key down event, then ImeSetComposition() will be + underlines_.clear(); + if (isAttributedString) { + ExtractUnderlines(string, &underlines_); + } else { + // Use a thin black underline by default. + underlines_.push_back( + WebKit::WebCompositionUnderline(0, length, SK_ColorBLACK, false)); + } + + // If we are handling a key down event, then SetComposition() will be // called in keyEvent: method. // Input methods of Mac use setMarkedText calls with an empty text to cancel // an ongoing composition. So, we should check whether or not the given text - // is empty to update the IME state. (Our IME backend can automatically - // cancels an ongoing composition when we send an empty text. So, it is OK - // to send an empty text to the renderer.) + // is empty to update the input method state. (Our input method backend can + // automatically cancels an ongoing composition when we send an empty text. + // So, it is OK to send an empty text to the renderer.) if (!handlingKeyDown_) { renderWidgetHostView_->render_widget_host_->ImeSetComposition( - newMarkedText_, newSelRange.location, newSelRange.location, - NSMaxRange(newSelRange)); + markedText_, underlines_, + newSelRange.location, NSMaxRange(newSelRange)); } } @@ -1662,7 +1812,7 @@ extern NSString *NSTextInputReplacementRangeAttributeName; } } -- (void)insertText:(id)string { +- (void)insertText:(id)string replacementRange:(NSRange)replacementRange { // An input method has characters to be inserted. // Same as Linux, Mac calls this method not only: // * when an input method finishs composing text, but also; @@ -1671,11 +1821,13 @@ extern NSString *NSTextInputReplacementRangeAttributeName; // a Char event so it is dispatched to an onkeypress() event handler of // JavaScript. // On the other hand, when we are using input methods, we should send the - // given characters as an IME event and prevent the characters from being - // dispatched to onkeypress() event handlers. + // given characters as an input method event and prevent the characters from + // being dispatched to onkeypress() event handlers. // Text inserting might be initiated by other source instead of keyboard // events, such as the Characters dialog. In this case the text should be - // sent as an IME event as well. + // sent as an input method event as well. + // TODO(suzhe): It's hard for us to support replacementRange without accessing + // the full web content. BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; NSString* im_text = isAttributedString ? [string string] : string; if (handlingKeyDown_) { @@ -1823,7 +1975,19 @@ extern NSString *NSTextInputReplacementRangeAttributeName; NSInputManager *currentInputManager = [NSInputManager currentInputManager]; [currentInputManager markedTextAbandoned:self]; - [self unmarkText]; + hasMarkedText_ = NO; + // Should not call [self unmarkText] here, because it'll send unnecessary + // cancel composition IPC message to the renderer. +} + +- (void)confirmComposition { + if (!hasMarkedText_) + return; + + if (renderWidgetHostView_->render_widget_host_) + renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(); + + [self cancelComposition]; } @end diff --git a/chrome/browser/renderer_host/render_widget_host_view_win.cc b/chrome/browser/renderer_host/render_widget_host_view_win.cc index 0905f8e..5090b38 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_win.cc +++ b/chrome/browser/renderer_host/render_widget_host_view_win.cc @@ -281,7 +281,8 @@ RenderWidgetHostViewWin::RenderWidgetHostViewWin(RenderWidgetHost* widget) shutdown_factory_(this), parent_hwnd_(NULL), is_loading_(false), - visually_deemphasized_(false) { + visually_deemphasized_(false), + text_input_type_(WebKit::WebTextInputTypeNone) { render_widget_host_->set_view(this); registrar_.Add(this, NotificationType::RENDERER_PROCESS_TERMINATED, @@ -612,16 +613,24 @@ void RenderWidgetHostViewWin::SetIsLoading(bool is_loading) { UpdateCursorIfOverSelf(); } -void RenderWidgetHostViewWin::IMEUpdateStatus(int control, - const gfx::Rect& caret_rect) { - if (control == IME_DISABLE) { - ime_input_.DisableIME(m_hWnd); - } else if (control == IME_CANCEL_COMPOSITION) { - ime_input_.CancelIME(m_hWnd); - } else { - ime_input_.EnableIME(m_hWnd, caret_rect, - control == IME_COMPLETE_COMPOSITION); +void RenderWidgetHostViewWin::ImeUpdateTextInputState( + WebKit::WebTextInputType type, + const gfx::Rect& caret_rect) { + if (text_input_type_ != type) { + text_input_type_ = type; + if (type == WebKit::WebTextInputTypeText) + ime_input_.EnableIME(m_hWnd); + else + ime_input_.DisableIME(m_hWnd); } + + // Only update caret position if the input method is enabled. + if (type == WebKit::WebTextInputTypeText) + ime_input_.UpdateCaretRect(m_hWnd, caret_rect); +} + +void RenderWidgetHostViewWin::ImeCancelComposition() { + ime_input_.CancelIME(m_hWnd); } BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lparam) { @@ -1095,8 +1104,8 @@ void RenderWidgetHostViewWin::OnInputLangChange(DWORD character_set, // successfully (because Action 1 shows ime_status = !ime_notification_.) bool ime_status = ime_input_.SetInputLanguage(); if (ime_status != ime_notification_) { - if (Send(new ViewMsg_ImeSetInputMode(render_widget_host_->routing_id(), - ime_status))) { + if (render_widget_host_) { + render_widget_host_->SetInputMethodActive(ime_status); ime_notification_ = ime_status; } } @@ -1148,8 +1157,8 @@ LRESULT RenderWidgetHostViewWin::OnImeSetContext( // Therefore, we just start/stop status messages according to the activation // status of this application without checks. bool activated = (wparam == TRUE); - if (Send(new ViewMsg_ImeSetInputMode( - render_widget_host_->routing_id(), activated))) { + if (render_widget_host_) { + render_widget_host_->SetInputMethodActive(activated); ime_notification_ = activated; } @@ -1188,12 +1197,7 @@ LRESULT RenderWidgetHostViewWin::OnImeComposition( // and send it to a renderer process. ImeComposition composition; if (ime_input_.GetResult(m_hWnd, lparam, &composition)) { - Send(new ViewMsg_ImeSetComposition(render_widget_host_->routing_id(), - WebKit::WebCompositionCommandConfirm, - composition.cursor_position, - composition.target_start, - composition.target_end, - composition.ime_string)); + render_widget_host_->ImeConfirmComposition(composition.ime_string); ime_input_.ResetComposition(m_hWnd); // Fall though and try reading the composition string. // Japanese IMEs send a message containing both GCS_RESULTSTR and @@ -1203,12 +1207,9 @@ LRESULT RenderWidgetHostViewWin::OnImeComposition( // Retrieve the composition string and its attributes of the ongoing // composition and send it to a renderer process. if (ime_input_.GetComposition(m_hWnd, lparam, &composition)) { - Send(new ViewMsg_ImeSetComposition(render_widget_host_->routing_id(), - WebKit::WebCompositionCommandSet, - composition.cursor_position, - composition.target_start, - composition.target_end, - composition.ime_string)); + render_widget_host_->ImeSetComposition( + composition.ime_string, composition.underlines, + composition.selection_start, composition.selection_end); } // We have to prevent WTL from calling ::DefWindowProc() because we do not // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages. @@ -1226,10 +1227,7 @@ LRESULT RenderWidgetHostViewWin::OnImeEndComposition( // i.e. the ongoing composition has been canceled. // We need to reset the composition status both of the ImeInput object and // of the renderer process. - std::wstring empty_string; - Send(new ViewMsg_ImeSetComposition(render_widget_host_->routing_id(), - WebKit::WebCompositionCommandDiscard, - -1, -1, -1, empty_string)); + render_widget_host_->ImeCancelComposition(); ime_input_.ResetComposition(m_hWnd); } ime_input_.DestroyImeWindow(m_hWnd); @@ -1282,9 +1280,13 @@ LRESULT RenderWidgetHostViewWin::OnMouseEvent(UINT message, WPARAM wparam, switch (message) { case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + // Finish the ongoing composition whenever a mouse click happens. + // It matches IE's behavior. + ime_input_.CleanupComposition(m_hWnd); + // Fall through. case WM_MOUSEMOVE: - case WM_MOUSELEAVE: - case WM_RBUTTONDOWN: { + case WM_MOUSELEAVE: { // Give the TabContents first crack at the message. It may want to // prevent forwarding to the renderer if some higher level browser // functionality is invoked. @@ -1303,12 +1305,6 @@ LRESULT RenderWidgetHostViewWin::OnMouseEvent(UINT message, WPARAM wparam, return 1; } } - - // WebKit does not update its IME status when a user clicks a mouse button - // to change the input focus onto a popup menu. As a workaround, we finish - // an ongoing composition every time when we click a left button. - if (message == WM_LBUTTONDOWN) - ime_input_.CleanupComposition(m_hWnd); } ForwardMouseEventToRenderer(message, wparam, lparam); diff --git a/chrome/browser/renderer_host/render_widget_host_view_win.h b/chrome/browser/renderer_host/render_widget_host_view_win.h index fe524cd7..e0bc712 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_win.h +++ b/chrome/browser/renderer_host/render_widget_host_view_win.h @@ -135,7 +135,9 @@ class RenderWidgetHostViewWin virtual gfx::Rect GetViewBounds() const; virtual void UpdateCursor(const WebCursor& cursor); virtual void SetIsLoading(bool is_loading); - virtual void IMEUpdateStatus(int control, const gfx::Rect& caret_rect); + virtual void ImeUpdateTextInputState(WebKit::WebTextInputType type, + const gfx::Rect& caret_rect); + virtual void ImeCancelComposition(); virtual void DidUpdateBackingStore( const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy, const std::vector<gfx::Rect>& copy_rects); @@ -336,6 +338,10 @@ class RenderWidgetHostViewWin // Registrar so we can listen to RENDERER_PROCESS_TERMINATED events. NotificationRegistrar registrar_; + // Stores the current text input type received by ImeUpdateTextInputState() + // method. + WebKit::WebTextInputType text_input_type_; + DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewWin); }; diff --git a/chrome/browser/renderer_host/test/test_render_view_host.h b/chrome/browser/renderer_host/test/test_render_view_host.h index bda5712..c3c5109 100644 --- a/chrome/browser/renderer_host/test/test_render_view_host.h +++ b/chrome/browser/renderer_host/test/test_render_view_host.h @@ -59,7 +59,9 @@ class TestRenderWidgetHostView : public RenderWidgetHostView { virtual void SetIsLoading(bool is_loading) {} virtual void UpdateCursor(const WebCursor& cursor) {} virtual void UpdateCursorIfOverSelf() {} - virtual void IMEUpdateStatus(int control, const gfx::Rect& caret_rect) {} + virtual void ImeUpdateTextInputState(WebKit::WebTextInputType state, + const gfx::Rect& caret_rect) {} + virtual void ImeCancelComposition() {} virtual void DidUpdateBackingStore( const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy, const std::vector<gfx::Rect>& rects) {} diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 03b41b2..2c003f6 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -892,6 +892,7 @@ 'browser/process_info_snapshot_mac_unittest.cc', 'browser/profile_manager_unittest.cc', 'browser/renderer_host/audio_renderer_host_unittest.cc', + 'browser/renderer_host/gtk_im_context_wrapper_unittest.cc', 'browser/renderer_host/render_widget_host_unittest.cc', 'browser/renderer_host/resource_dispatcher_host_unittest.cc', 'browser/renderer_host/resource_queue_unittest.cc', diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h index f5a3376..2b757e9 100644 --- a/chrome/common/render_messages.h +++ b/chrome/common/render_messages.h @@ -443,14 +443,6 @@ struct ViewHostMsg_DidPrintPage_Params { double actual_shrink; }; -// The first parameter for the ViewHostMsg_ImeUpdateStatus message. -enum ViewHostMsg_ImeControl { - IME_DISABLE = 0, - IME_MOVE_WINDOWS, - IME_COMPLETE_COMPOSITION, - IME_CANCEL_COMPOSITION, -}; - // Parameters for creating an audio output stream. struct ViewHostMsg_Audio_CreateStream_Params { // Format request for the stream. @@ -761,40 +753,6 @@ struct ParamTraits<ResourceType::Type> { } }; -template <> -struct ParamTraits<ViewHostMsg_ImeControl> { - typedef ViewHostMsg_ImeControl param_type; - static void Write(Message* m, const param_type& p) { - m->WriteInt(p); - } - static bool Read(const Message* m, void** iter, param_type* p) { - int type; - if (!m->ReadInt(iter, &type)) - return false; - *p = static_cast<ViewHostMsg_ImeControl>(type); - return true; - } - static void Log(const param_type& p, std::wstring* l) { - std::wstring control; - switch (p) { - case IME_DISABLE: - control = L"IME_DISABLE"; - break; - case IME_MOVE_WINDOWS: - control = L"IME_MOVE_WINDOWS"; - break; - case IME_COMPLETE_COMPOSITION: - control = L"IME_COMPLETE_COMPOSITION"; - break; - default: - control = L"UNKNOWN"; - break; - } - - LogParam(control, l); - } -}; - // Traits for ViewMsg_Navigate_Params structure to pack/unpack. template <> struct ParamTraits<ViewMsg_Navigate_Params> { diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h index df043e7..53c33a9 100644 --- a/chrome/common/render_messages_internal.h +++ b/chrome/common/render_messages_internal.h @@ -456,43 +456,30 @@ IPC_BEGIN_MESSAGES(View) std::string /* property_name */, std::string /* property_value_json */) - // This message starts/stop monitoring the status of the focused edit - // control of a renderer process. + // This message starts/stop monitoring the input method status of the focused + // edit control of a renderer process. // Parameters // * is_active (bool) - // Represents whether or not the IME is active in a browser process. + // Indicates if an input method is active in the browser process. // The possible actions when a renderer process receives this message are // listed below: // Value Action - // true Start sending IPC messages, ViewHostMsg_ImeUpdateStatus - // to notify the status of the focused edit control. - // false Stop sending IPC messages, ViewHostMsg_ImeUpdateStatus. - IPC_MESSAGE_ROUTED1(ViewMsg_ImeSetInputMode, + // true Start sending IPC message ViewHostMsg_ImeUpdateTextInputState + // to notify the input method status of the focused edit control. + // false Stop sending IPC message ViewHostMsg_ImeUpdateTextInputState. + IPC_MESSAGE_ROUTED1(ViewMsg_SetInputMethodActive, bool /* is_active */) - // This message sends a string being composed with IME. - // Parameters - // * string_type (int) - // Represents the type of the 'ime_string' parameter. - // Its possible values and description are listed below: - // Value Description - // -1 The parameter is not used. - // 1 The parameter represents a result string. - // 0 The parameter represents a composition string. - // * cursor_position (int) - // Represents the position of the cursor - // * target_start (int) - // Represents the position of the beginning of the selection - // * target_end (int) - // Represents the position of the end of the selection - // * ime_string (std::wstring) - // Represents the string retrieved from IME (Input Method Editor) - IPC_MESSAGE_ROUTED5(ViewMsg_ImeSetComposition, - WebKit::WebCompositionCommand, /* command */ - int, /* cursor_position */ - int, /* target_start */ - int, /* target_end */ - string16 /* ime_string */ ) + // This message sends a string being composed with an input method. + IPC_MESSAGE_ROUTED4( + ViewMsg_ImeSetComposition, + string16, /* text */ + std::vector<WebKit::WebCompositionUnderline>, /* underlines */ + int, /* selectiont_start */ + int /* selection_end */) + + // This message confirms an ongoing composition. + IPC_MESSAGE_ROUTED0(ViewMsg_ImeConfirmComposition) // This passes a set of webkit preferences down to the renderer. IPC_MESSAGE_ROUTED1(ViewMsg_UpdateWebPreferences, WebPreferences) @@ -1524,45 +1511,14 @@ IPC_BEGIN_MESSAGES(ViewHost) GURL /* url of OS description document */, bool /* autodetected */) - // required for synchronizing IME windows. - // Parameters - // * control (ViewHostMsg_ImeControl) - // It specifies the code for controlling the IME attached to - // the browser process. This parameter should be one of the values - // listed below. - // + IME_DISABLE - // Deactivate the IME attached to a browser process. - // This code is typically used for notifying a renderer process - // moves its input focus to a password input. A browser process - // finishes the current composition and deactivate IME. - // If a renderer process sets its input focus to another edit - // control which is not a password input, it needs to re-activate - // IME, it has to send another message with this code IME_MOVE_WINDOWS - // and set the new caret position. - // + IME_MOVE_WINDOWS - // Activate the IME attached to a browser process and set the position - // of its IME windows. - // This code is typically used for the following cases: - // - Notifying a renderer process moves the caret position of the - // focused edit control, or; - // - Notifying a renderer process moves its input focus from a - // password input to an editable control which is NOT a password - // input. - // A renderer process also has to set caret_rect and - // specify the new caret rectangle. - // + IME_COMPLETE_COMPOSITION - // Finish the current composition. - // This code is used for notifying a renderer process moves its - // input focus from an editable control being composed to another one - // which is NOT a password input. A browser process closes its IME - // windows without changing the activation status of its IME, i.e. it - // keeps activating its IME. - // * caret_rect (gfx::Rect) - // They specify the rectangle of the input caret. - IPC_MESSAGE_ROUTED2(ViewHostMsg_ImeUpdateStatus, - ViewHostMsg_ImeControl, /* control */ + // requires for updating text input state. + IPC_MESSAGE_ROUTED2(ViewHostMsg_ImeUpdateTextInputState, + WebKit::WebTextInputType, /* text_input_type */ gfx::Rect /* caret_rect */) + // requires for cancelling an ongoing input method composition. + IPC_MESSAGE_ROUTED0(ViewHostMsg_ImeCancelComposition) + // Tells the browser that the renderer is done calculating the number of // rendered pages according to the specified settings. IPC_MESSAGE_ROUTED2(ViewHostMsg_DidGetPrintedPagesCount, diff --git a/chrome/common/webkit_param_traits.h b/chrome/common/webkit_param_traits.h index 270c2f7..e277e38 100644 --- a/chrome/common/webkit_param_traits.h +++ b/chrome/common/webkit_param_traits.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this +// Copyright (c) 2010 The Chromium Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // @@ -25,7 +25,7 @@ #include "ipc/ipc_message_utils.h" #include "third_party/WebKit/WebKit/chromium/public/WebCache.h" -#include "third_party/WebKit/WebKit/chromium/public/WebCompositionCommand.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCompositionUnderline.h" #include "third_party/WebKit/WebKit/chromium/public/WebConsoleMessage.h" #include "third_party/WebKit/WebKit/chromium/public/WebContextMenuData.h" #include "third_party/WebKit/WebKit/chromium/public/WebDragOperation.h" @@ -35,6 +35,7 @@ #include "third_party/WebKit/WebKit/chromium/public/WebPopupType.h" #include "third_party/WebKit/WebKit/chromium/public/WebScreenInfo.h" #include "third_party/WebKit/WebKit/chromium/public/WebTextDirection.h" +#include "third_party/WebKit/WebKit/chromium/public/WebTextInputType.h" namespace IPC { @@ -101,24 +102,6 @@ struct ParamTraits<WebKit::WebScreenInfo> { }; template <> -struct ParamTraits<WebKit::WebCompositionCommand> { - typedef WebKit::WebCompositionCommand param_type; - static void Write(Message* m, const param_type& p) { - WriteParam(m, static_cast<int>(p)); - } - static bool Read(const Message* m, void** iter, param_type* r) { - int value; - if (!ReadParam(m, iter, &value)) - return false; - *r = static_cast<param_type>(value); - return true; - } - static void Log(const param_type& p, std::wstring* l) { - LogParam(static_cast<int>(p), l); - } -}; - -template <> struct ParamTraits<WebKit::WebConsoleMessage::Level> { typedef WebKit::WebConsoleMessage::Level param_type; static void Write(Message* m, const param_type& p) { @@ -391,6 +374,69 @@ template <> } }; +template <> +struct ParamTraits<WebKit::WebCompositionUnderline> { + typedef WebKit::WebCompositionUnderline param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.startOffset); + WriteParam(m, p.endOffset); + WriteParam(m, p.color); + WriteParam(m, p.thick); + } + static bool Read(const Message* m, void** iter, param_type* p) { + return + ReadParam(m, iter, &p->startOffset) && + ReadParam(m, iter, &p->endOffset) && + ReadParam(m, iter, &p->color) && + ReadParam(m, iter, &p->thick); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(L"("); + LogParam(p.startOffset, l); + l->append(L","); + LogParam(p.endOffset, l); + l->append(L":"); + LogParam(p.color, l); + l->append(L":"); + LogParam(p.thick, l); + l->append(L")"); + } +}; + +template <> +struct ParamTraits<WebKit::WebTextInputType> { + typedef WebKit::WebTextInputType param_type; + static void Write(Message* m, const param_type& p) { + m->WriteInt(p); + } + static bool Read(const Message* m, void** iter, param_type* p) { + int type; + if (!m->ReadInt(iter, &type)) + return false; + *p = static_cast<param_type>(type); + return true; + } + static void Log(const param_type& p, std::wstring* l) { + std::wstring control; + switch (p) { + case WebKit::WebTextInputTypeNone: + control = L"WebKit::WebTextInputTypeNone"; + break; + case WebKit::WebTextInputTypeText: + control = L"WebKit::WebTextInputTypeText"; + break; + case WebKit::WebTextInputTypePassword: + control = L"WebKit::WebTextInputTypePassword"; + break; + default: + NOTIMPLEMENTED(); + control = L"UNKNOWN"; + break; + } + LogParam(control, l); + } +}; + } // namespace IPC #endif // CHROME_COMMON_WEBKIT_PARAM_TRAITS_H_ diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc index 19d9904..6b95e29 100644 --- a/chrome/renderer/render_view.cc +++ b/chrome/renderer/render_view.cc @@ -1760,16 +1760,6 @@ bool RenderView::isSelectTrailingWhitespaceEnabled() { #endif } -void RenderView::setInputMethodEnabled(bool enabled) { - // Save the updated IME status and mark the input focus has been updated. - // The IME status is to be sent to a browser process next time when - // the input caret is rendered. - if (!ime_control_busy_) { - ime_control_updated_ = true; - ime_control_new_state_ = enabled; - } -} - void RenderView::didChangeSelection(bool is_empty_selection) { #if defined(USE_X11) if (!handling_input_event_) diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h index 1defef1..35db956 100644 --- a/chrome/renderer/render_view.h +++ b/chrome/renderer/render_view.h @@ -326,7 +326,6 @@ class RenderView : public RenderWidget, virtual void didStopLoading(); virtual bool isSmartInsertDeleteEnabled(); virtual bool isSelectTrailingWhitespaceEnabled(); - virtual void setInputMethodEnabled(bool enabled); virtual void didChangeSelection(bool is_selection_empty); virtual void didExecuteCommand(const WebKit::WebString& command_name); virtual bool handleCurrentKeyboardEvent(); diff --git a/chrome/renderer/render_view_unittest.cc b/chrome/renderer/render_view_unittest.cc index b07f2b6..5f34637 100644 --- a/chrome/renderer/render_view_unittest.cc +++ b/chrome/renderer/render_view_unittest.cc @@ -24,7 +24,6 @@ #include "webkit/glue/form_data.h" #include "webkit/glue/form_field.h" -using WebKit::WebCompositionCommand; using WebKit::WebDocument; using WebKit::WebFrame; using WebKit::WebInputElement; @@ -34,19 +33,6 @@ using WebKit::WebURLError; using webkit_glue::FormData; using webkit_glue::FormField; -static WebCompositionCommand ToCompositionCommand(int string_type) { - switch (string_type) { - default: - NOTREACHED(); - case -1: - return WebKit::WebCompositionCommandDiscard; - case 0: - return WebKit::WebCompositionCommandSet; - case 1: - return WebKit::WebCompositionCommandConfirm; - } -} - // Test that we get form state change notifications when input fields change. TEST_F(RenderViewTest, OnNavStateChanged) { // Don't want any delay for form state sync changes. This will still post a @@ -73,7 +59,7 @@ TEST_F(RenderViewTest, OnNavStateChanged) { // changes. TEST_F(RenderViewTest, OnImeStateChanged) { // Enable our IME backend code. - view_->OnImeSetInputMode(true); + view_->OnSetInputMethodActive(true); // Load an HTML page consisting of two input fields. view_->set_send_content_state_immediately(true); @@ -97,13 +83,13 @@ TEST_F(RenderViewTest, OnImeStateChanged) { // Update the IME status and verify if our IME backend sends an IPC message // to activate IMEs. - view_->UpdateIME(); + view_->UpdateInputMethod(); const IPC::Message* msg = render_thread_.sink().GetMessageAt(0); EXPECT_TRUE(msg != NULL); - EXPECT_EQ(ViewHostMsg_ImeUpdateStatus::ID, msg->type()); - ViewHostMsg_ImeUpdateStatus::Param params; - ViewHostMsg_ImeUpdateStatus::Read(msg, ¶ms); - EXPECT_EQ(params.a, IME_COMPLETE_COMPOSITION); + EXPECT_EQ(ViewHostMsg_ImeUpdateTextInputState::ID, msg->type()); + ViewHostMsg_ImeUpdateTextInputState::Param params; + ViewHostMsg_ImeUpdateTextInputState::Read(msg, ¶ms); + EXPECT_EQ(params.a, WebKit::WebTextInputTypeText); EXPECT_TRUE(params.b.x() > 0 && params.b.y() > 0); // Move the input focus to the second <input> element, where we should @@ -114,12 +100,12 @@ TEST_F(RenderViewTest, OnImeStateChanged) { // Update the IME status and verify if our IME backend sends an IPC message // to de-activate IMEs. - view_->UpdateIME(); + view_->UpdateInputMethod(); msg = render_thread_.sink().GetMessageAt(0); EXPECT_TRUE(msg != NULL); - EXPECT_EQ(ViewHostMsg_ImeUpdateStatus::ID, msg->type()); - ViewHostMsg_ImeUpdateStatus::Read(msg, ¶ms); - EXPECT_EQ(params.a, IME_DISABLE); + EXPECT_EQ(ViewHostMsg_ImeUpdateTextInputState::ID, msg->type()); + ViewHostMsg_ImeUpdateTextInputState::Read(msg, ¶ms); + EXPECT_EQ(params.a, WebKit::WebTextInputTypePassword); } } @@ -140,60 +126,59 @@ TEST_F(RenderViewTest, ImeComposition) { IME_SETINPUTMODE, IME_SETFOCUS, IME_SETCOMPOSITION, + IME_CONFIRMCOMPOSITION, + IME_CANCELCOMPOSITION }; struct ImeMessage { ImeCommand command; bool enable; - int string_type; - int cursor_position; - int target_start; - int target_end; + int selection_start; + int selection_end; const wchar_t* ime_string; const wchar_t* result; }; static const ImeMessage kImeMessages[] = { // Scenario 1: input a Chinese word with Microsoft IME (on Vista). - {IME_INITIALIZE, true, 0, 0, 0, 0, NULL, NULL}, - {IME_SETINPUTMODE, true, 0, 0, 0, 0, NULL, NULL}, - {IME_SETFOCUS, true, 0, 0, 0, 0, NULL, NULL}, - {IME_SETCOMPOSITION, false, 0, 1, -1, -1, L"n", L"n"}, - {IME_SETCOMPOSITION, false, 0, 2, -1, -1, L"ni", L"ni"}, - {IME_SETCOMPOSITION, false, 0, 3, -1, -1, L"nih", L"nih"}, - {IME_SETCOMPOSITION, false, 0, 4, -1, -1, L"niha", L"niha"}, - {IME_SETCOMPOSITION, false, 0, 5, -1, -1, L"nihao", L"nihao"}, - {IME_SETCOMPOSITION, false, 0, 2, -1, -1, L"\x4F60\x597D", L"\x4F60\x597D"}, - {IME_SETCOMPOSITION, false, 1, -1, -1, -1, L"\x4F60\x597D", - L"\x4F60\x597D"}, - {IME_SETCOMPOSITION, false, -1, -1, -1, -1, L"", L"\x4F60\x597D"}, + {IME_INITIALIZE, true, 0, 0, NULL, NULL}, + {IME_SETINPUTMODE, true, 0, 0, NULL, NULL}, + {IME_SETFOCUS, true, 0, 0, NULL, NULL}, + {IME_SETCOMPOSITION, false, 1, 1, L"n", L"n"}, + {IME_SETCOMPOSITION, false, 2, 2, L"ni", L"ni"}, + {IME_SETCOMPOSITION, false, 3, 3, L"nih", L"nih"}, + {IME_SETCOMPOSITION, false, 4, 4, L"niha", L"niha"}, + {IME_SETCOMPOSITION, false, 5, 5, L"nihao", L"nihao"}, + {IME_SETCOMPOSITION, false, 2, 2, L"\x4F60\x597D", L"\x4F60\x597D"}, + {IME_CONFIRMCOMPOSITION, false, -1, -1, NULL, L"\x4F60\x597D"}, + {IME_CANCELCOMPOSITION, false, -1, -1, L"", L"\x4F60\x597D"}, // Scenario 2: input a Japanese word with Microsoft IME (on Vista). - {IME_INITIALIZE, true, 0, 0, 0, 0, NULL, NULL}, - {IME_SETINPUTMODE, true, 0, 0, 0, 0, NULL, NULL}, - {IME_SETFOCUS, true, 0, 0, 0, 0, NULL, NULL}, - {IME_SETCOMPOSITION, false, 0, 1, 0, 1, L"\xFF4B", L"\xFF4B"}, - {IME_SETCOMPOSITION, false, 0, 1, 0, 1, L"\x304B", L"\x304B"}, - {IME_SETCOMPOSITION, false, 0, 2, 0, 2, L"\x304B\xFF4E", L"\x304B\xFF4E"}, - {IME_SETCOMPOSITION, false, 0, 3, 0, 3, L"\x304B\x3093\xFF4A", + {IME_INITIALIZE, true, 0, 0, NULL, NULL}, + {IME_SETINPUTMODE, true, 0, 0, NULL, NULL}, + {IME_SETFOCUS, true, 0, 0, NULL, NULL}, + {IME_SETCOMPOSITION, false, 0, 1, L"\xFF4B", L"\xFF4B"}, + {IME_SETCOMPOSITION, false, 0, 1, L"\x304B", L"\x304B"}, + {IME_SETCOMPOSITION, false, 0, 2, L"\x304B\xFF4E", L"\x304B\xFF4E"}, + {IME_SETCOMPOSITION, false, 0, 3, L"\x304B\x3093\xFF4A", L"\x304B\x3093\xFF4A"}, - {IME_SETCOMPOSITION, false, 0, 3, 0, 3, L"\x304B\x3093\x3058", + {IME_SETCOMPOSITION, false, 0, 3, L"\x304B\x3093\x3058", L"\x304B\x3093\x3058"}, - {IME_SETCOMPOSITION, false, 0, 0, 0, 2, L"\x611F\x3058", L"\x611F\x3058"}, - {IME_SETCOMPOSITION, false, 0, 0, 0, 2, L"\x6F22\x5B57", L"\x6F22\x5B57"}, - {IME_SETCOMPOSITION, false, 1, -1, -1, -1, L"\x6F22\x5B57", - L"\x6F22\x5B57"}, - {IME_SETCOMPOSITION, false, -1, -1, -1, -1, L"", L"\x6F22\x5B57"}, + {IME_SETCOMPOSITION, false, 0, 2, L"\x611F\x3058", L"\x611F\x3058"}, + {IME_SETCOMPOSITION, false, 0, 2, L"\x6F22\x5B57", L"\x6F22\x5B57"}, + {IME_CONFIRMCOMPOSITION, false, -1, -1, NULL, L"\x6F22\x5B57"}, + {IME_CANCELCOMPOSITION, false, -1, -1, L"", L"\x6F22\x5B57"}, // Scenario 3: input a Korean word with Microsot IME (on Vista). - {IME_INITIALIZE, true, 0, 0, 0, 0, NULL, NULL}, - {IME_SETINPUTMODE, true, 0, 0, 0, 0, NULL, NULL}, - {IME_SETFOCUS, true, 0, 0, 0, 0, NULL, NULL}, - {IME_SETCOMPOSITION, false, 0, 0, 0, 1, L"\x3147", L"\x3147"}, - {IME_SETCOMPOSITION, false, 0, 0, 0, 1, L"\xC544", L"\xC544"}, - {IME_SETCOMPOSITION, false, 0, 0, 0, 1, L"\xC548", L"\xC548"}, - {IME_SETCOMPOSITION, false, 1, -1, -1, -1, L"\xC548", L"\xC548"}, - {IME_SETCOMPOSITION, false, 0, 0, 0, 1, L"\x3134", L"\xC548\x3134"}, - {IME_SETCOMPOSITION, false, 0, 0, 0, 1, L"\xB140", L"\xC548\xB140"}, - {IME_SETCOMPOSITION, false, 0, 0, 0, 1, L"\xB155", L"\xC548\xB155"}, - {IME_SETCOMPOSITION, false, -1, -1, -1, -1, L"", L"\xC548"}, - {IME_SETCOMPOSITION, false, 1, -1, -1, -1, L"\xB155", L"\xC548\xB155"}, + {IME_INITIALIZE, true, 0, 0, NULL, NULL}, + {IME_SETINPUTMODE, true, 0, 0, NULL, NULL}, + {IME_SETFOCUS, true, 0, 0, NULL, NULL}, + {IME_SETCOMPOSITION, false, 0, 1, L"\x3147", L"\x3147"}, + {IME_SETCOMPOSITION, false, 0, 1, L"\xC544", L"\xC544"}, + {IME_SETCOMPOSITION, false, 0, 1, L"\xC548", L"\xC548"}, + {IME_CONFIRMCOMPOSITION, false, -1, -1, NULL, L"\xC548"}, + {IME_SETCOMPOSITION, false, 0, 1, L"\x3134", L"\xC548\x3134"}, + {IME_SETCOMPOSITION, false, 0, 1, L"\xB140", L"\xC548\xB140"}, + {IME_SETCOMPOSITION, false, 0, 1, L"\xB155", L"\xC548\xB155"}, + {IME_CANCELCOMPOSITION, false, -1, -1, L"", L"\xC548"}, + {IME_SETCOMPOSITION, false, 0, 1, L"\xB155", L"\xC548\xB155"}, + {IME_CONFIRMCOMPOSITION, false, -1, -1, NULL, L"\xC548\xB155"}, }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kImeMessages); i++) { @@ -203,7 +188,7 @@ TEST_F(RenderViewTest, ImeComposition) { // Load an HTML page consisting of a content-editable <div> element, // and move the input focus to the <div> element, where we can use // IMEs. - view_->OnImeSetInputMode(ime_message->enable); + view_->OnSetInputMethodActive(ime_message->enable); view_->set_send_content_state_immediately(true); LoadHTML("<html>" "<head>" @@ -217,7 +202,7 @@ TEST_F(RenderViewTest, ImeComposition) { case IME_SETINPUTMODE: // Activate (or deactivate) our IME back-end. - view_->OnImeSetInputMode(ime_message->enable); + view_->OnSetInputMethodActive(ime_message->enable); break; case IME_SETFOCUS: @@ -227,17 +212,26 @@ TEST_F(RenderViewTest, ImeComposition) { case IME_SETCOMPOSITION: view_->OnImeSetComposition( - ToCompositionCommand(ime_message->string_type), - ime_message->cursor_position, - ime_message->target_start, - ime_message->target_end, - WideToUTF16Hack(ime_message->ime_string)); + WideToUTF16Hack(ime_message->ime_string), + std::vector<WebKit::WebCompositionUnderline>(), + ime_message->selection_start, + ime_message->selection_end); + break; + + case IME_CONFIRMCOMPOSITION: + view_->OnImeConfirmComposition(); + break; + + case IME_CANCELCOMPOSITION: + view_->OnImeSetComposition(string16(), + std::vector<WebKit::WebCompositionUnderline>(), + 0, 0); break; } // Update the status of our IME back-end. // TODO(hbono): we should verify messages to be sent from the back-end. - view_->UpdateIME(); + view_->UpdateInputMethod(); ProcessPendingMessages(); render_thread_.sink().ClearMessages(); diff --git a/chrome/renderer/render_widget.cc b/chrome/renderer/render_widget.cc index 37ea5a4..2366f78 100644 --- a/chrome/renderer/render_widget.cc +++ b/chrome/renderer/render_widget.cc @@ -34,7 +34,7 @@ #include "third_party/WebKit/WebKit/chromium/public/WebWidget.h" -using WebKit::WebCompositionCommand; +using WebKit::WebCompositionUnderline; using WebKit::WebCursorInfo; using WebKit::WebInputEvent; using WebKit::WebNavigationPolicy; @@ -44,6 +44,8 @@ using WebKit::WebRect; using WebKit::WebScreenInfo; using WebKit::WebSize; using WebKit::WebTextDirection; +using WebKit::WebTextInputType; +using WebKit::WebVector; RenderWidget::RenderWidget(RenderThreadBase* render_thread, WebKit::WebPopupType popup_type) @@ -61,13 +63,8 @@ RenderWidget::RenderWidget(RenderThreadBase* render_thread, has_focus_(false), handling_input_event_(false), closing_(false), - ime_is_active_(false), - ime_control_enable_ime_(true), - ime_control_x_(-1), - ime_control_y_(-1), - ime_control_new_state_(false), - ime_control_updated_(false), - ime_control_busy_(false), + input_method_is_active_(false), + text_input_type_(WebKit::WebTextInputTypeNone), popup_type_(popup_type), pending_window_rect_count_(0), suppress_next_char_events_(false), @@ -149,8 +146,9 @@ IPC_DEFINE_MESSAGE_MAP(RenderWidget) IPC_MESSAGE_HANDLER(ViewMsg_HandleInputEvent, OnHandleInputEvent) IPC_MESSAGE_HANDLER(ViewMsg_MouseCaptureLost, OnMouseCaptureLost) IPC_MESSAGE_HANDLER(ViewMsg_SetFocus, OnSetFocus) - IPC_MESSAGE_HANDLER(ViewMsg_ImeSetInputMode, OnImeSetInputMode) + IPC_MESSAGE_HANDLER(ViewMsg_SetInputMethodActive, OnSetInputMethodActive) IPC_MESSAGE_HANDLER(ViewMsg_ImeSetComposition, OnImeSetComposition) + IPC_MESSAGE_HANDLER(ViewMsg_ImeConfirmComposition, OnImeConfirmComposition) IPC_MESSAGE_HANDLER(ViewMsg_PaintAtSize, OnMsgPaintAtSize) IPC_MESSAGE_HANDLER(ViewMsg_Repaint, OnMsgRepaint) IPC_MESSAGE_HANDLER(ViewMsg_SetTextDirection, OnSetTextDirection) @@ -359,13 +357,6 @@ void RenderWidget::OnSetFocus(bool enable) { has_focus_ = enable; if (webwidget_) webwidget_->setFocus(enable); - if (enable) { - // Force to retrieve the state of the focused widget to determine if we - // should activate IMEs next time when this process calls the UpdateIME() - // function. - ime_control_updated_ = true; - ime_control_new_state_ = true; - } } void RenderWidget::ClearFocus() { @@ -531,7 +522,7 @@ void RenderWidget::DoDeferredUpdate() { Send(new ViewHostMsg_UpdateRect(routing_id_, params)); next_paint_flags_ = 0; - UpdateIME(); + UpdateInputMethod(); // Let derived classes know we've painted. DidInitiatePaint(); @@ -721,30 +712,32 @@ WebRect RenderWidget::windowResizerRect() { return resizer_rect_; } -void RenderWidget::OnImeSetInputMode(bool is_active) { +void RenderWidget::OnSetInputMethodActive(bool is_active) { // To prevent this renderer process from sending unnecessary IPC messages to // a browser process, we permit the renderer process to send IPC messages - // only during the IME attached to the browser process is active. - ime_is_active_ = is_active; + // only during the input method attached to the browser process is active. + input_method_is_active_ = is_active; } -void RenderWidget::OnImeSetComposition(WebCompositionCommand command, - int cursor_position, - int target_start, int target_end, - const string16& ime_string) { +void RenderWidget::OnImeSetComposition( + const string16& text, + const std::vector<WebCompositionUnderline>& underlines, + int selection_start, int selection_end) { if (!webwidget_) return; - ime_control_busy_ = true; - if (!webwidget_->handleCompositionEvent(command, cursor_position, - target_start, target_end, ime_string) && - command == WebKit::WebCompositionCommandSet) { - // If the composition event can't be handled while we were trying to update - // the composition, let the browser process know so it can update it's - // state. - Send(new ViewHostMsg_ImeUpdateStatus(routing_id(), IME_CANCEL_COMPOSITION, - WebRect())); + if (!webwidget_->setComposition( + text, WebVector<WebCompositionUnderline>(underlines), + selection_start, selection_end)) { + // If we failed to set the composition text, then we need to let the browser + // process to cancel the input method's ongoing composition session, to make + // sure we are in a consistent state. + Send(new ViewHostMsg_ImeCancelComposition(routing_id())); } - ime_control_busy_ = false; +} + +void RenderWidget::OnImeConfirmComposition() { + if (webwidget_) + webwidget_->confirmComposition(); } // This message causes the renderer to render an image of the @@ -872,75 +865,26 @@ void RenderWidget::set_next_paint_is_repaint_ack() { next_paint_flags_ |= ViewHostMsg_UpdateRect_Flags::IS_REPAINT_ACK; } -void RenderWidget::UpdateIME() { - // If a browser process does not have IMEs, its IMEs are not active, or there - // are not any attached widgets. - // a renderer process does not have to retrieve information of the focused - // control or send notification messages to a browser process. - if (!ime_is_active_) { +void RenderWidget::UpdateInputMethod() { + if (!input_method_is_active_) return; + + WebTextInputType new_type = WebKit::WebTextInputTypeNone; + WebRect new_caret_bounds; + + if (webwidget_) { + new_type = webwidget_->textInputType(); + new_caret_bounds = webwidget_->caretOrSelectionBounds(); } - // Retrieve the caret position from the focused widget and verify we should - // enabled IMEs attached to the browser process. - bool enable_ime = false; - WebRect caret_rect; - if (!webwidget_ || - !webwidget_->queryCompositionStatus(&enable_ime, &caret_rect)) { - // There are not any editable widgets attached to this process. - // We should disable the IME to prevent it from sending CJK strings to - // non-editable widgets. - ime_control_updated_ = true; - ime_control_new_state_ = false; - } - if (ime_control_new_state_ != enable_ime) { - ime_control_updated_ = true; - ime_control_new_state_ = enable_ime; - } - if (ime_control_updated_) { - // The input focus has been changed. - // Compare the current state with the updated state and choose actions. - if (ime_control_enable_ime_) { - if (ime_control_new_state_) { - // Case 1: a text input -> another text input - // Complete the current composition and notify the caret position. - Send(new ViewHostMsg_ImeUpdateStatus(routing_id(), - IME_COMPLETE_COMPOSITION, - caret_rect)); - } else { - // Case 2: a text input -> a password input (or a static control) - // Complete the current composition and disable the IME. - Send(new ViewHostMsg_ImeUpdateStatus(routing_id(), IME_DISABLE, - caret_rect)); - } - } else { - if (ime_control_new_state_) { - // Case 3: a password input (or a static control) -> a text input - // Enable the IME and notify the caret position. - Send(new ViewHostMsg_ImeUpdateStatus(routing_id(), - IME_COMPLETE_COMPOSITION, - caret_rect)); - } else { - // Case 4: a password input (or a static contol) -> another password - // input (or another static control). - // The IME has been already disabled and we don't have to do anything. - } - } - } else { - // The input focus is not changed. - // Notify the caret position to a browser process only if it is changed. - if (ime_control_enable_ime_) { - if (caret_rect.x != ime_control_x_ || - caret_rect.y != ime_control_y_) { - Send(new ViewHostMsg_ImeUpdateStatus(routing_id(), IME_MOVE_WINDOWS, - caret_rect)); - } - } + + // Only sends text input type and caret bounds to the browser process if they + // are changed. + if (text_input_type_ != new_type || caret_bounds_ != new_caret_bounds) { + text_input_type_ = new_type; + caret_bounds_ = new_caret_bounds; + Send(new ViewHostMsg_ImeUpdateTextInputState( + routing_id(), new_type, new_caret_bounds)); } - // Save the updated IME status to prevent from sending the same IPC messages. - ime_control_updated_ = false; - ime_control_enable_ime_ = ime_control_new_state_; - ime_control_x_ = caret_rect.x; - ime_control_y_ = caret_rect.y; } WebScreenInfo RenderWidget::screenInfo() { @@ -949,6 +893,20 @@ WebScreenInfo RenderWidget::screenInfo() { return results; } +void RenderWidget::resetInputMethod() { + if (!input_method_is_active_) + return; + + // If the last text input type is not None, then we should finish any + // ongoing composition regardless of the new text input type. + if (text_input_type_ != WebKit::WebTextInputTypeNone) { + // If a composition text exists, then we need to let the browser process + // to cancel the input method's ongoing composition session. + if (webwidget_->confirmComposition()) + Send(new ViewHostMsg_ImeCancelComposition(routing_id())); + } +} + void RenderWidget::SchedulePluginMove( const webkit_glue::WebPluginGeometry& move) { size_t i = 0; diff --git a/chrome/renderer/render_widget.h b/chrome/renderer/render_widget.h index d2c8347..d602fec4 100644 --- a/chrome/renderer/render_widget.h +++ b/chrome/renderer/render_widget.h @@ -19,10 +19,11 @@ #include "gfx/size.h" #include "ipc/ipc_channel.h" #include "skia/ext/platform_canvas.h" -#include "third_party/WebKit/WebKit/chromium/public/WebCompositionCommand.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCompositionUnderline.h" #include "third_party/WebKit/WebKit/chromium/public/WebPopupType.h" #include "third_party/WebKit/WebKit/chromium/public/WebRect.h" #include "third_party/WebKit/WebKit/chromium/public/WebTextDirection.h" +#include "third_party/WebKit/WebKit/chromium/public/WebTextInputType.h" #include "third_party/WebKit/WebKit/chromium/public/WebWidgetClient.h" #include "third_party/skia/include/core/SkBitmap.h" #include "webkit/glue/webcursor.h" @@ -92,6 +93,7 @@ class RenderWidget : public IPC::Channel::Listener, virtual WebKit::WebRect windowResizerRect(); virtual WebKit::WebRect rootWindowRect(); virtual WebKit::WebScreenInfo screenInfo(); + virtual void resetInputMethod(); // Called when a plugin is moved. These events are queued up and sent with // the next paint or scroll message to the host. @@ -156,11 +158,13 @@ class RenderWidget : public IPC::Channel::Listener, void OnHandleInputEvent(const IPC::Message& message); void OnMouseCaptureLost(); virtual void OnSetFocus(bool enable); - void OnImeSetInputMode(bool is_active); - void OnImeSetComposition(WebKit::WebCompositionCommand command, - int cursor_position, - int target_start, int target_end, - const string16& ime_string); + void OnSetInputMethodActive(bool is_active); + void OnImeSetComposition( + const string16& text, + const std::vector<WebKit::WebCompositionUnderline>& underlines, + int selection_start, + int selection_end); + void OnImeConfirmComposition(); void OnMsgPaintAtSize(const TransportDIB::Handle& dib_id, const gfx::Size& page_size, const gfx::Size& desired_size); @@ -192,12 +196,9 @@ class RenderWidget : public IPC::Channel::Listener, void set_next_paint_is_restore_ack(); void set_next_paint_is_repaint_ack(); - // Called when a renderer process moves an input focus or updates the - // position of its caret. - // This function compares them with the previous values, and send them to - // the browser process only if they are updated. - // The browser process moves IME windows and context. - void UpdateIME(); + // Checks if the input method state and caret position have been changed. + // If they are changed, the new value will be sent to the browser process. + void UpdateInputMethod(); // Tells the renderer it does not have focus. Used to prevent us from getting // the focus on our own when the browser did not focus us. @@ -288,24 +289,14 @@ class RenderWidget : public IPC::Channel::Listener, // be sent, except for a Close. bool closing_; - // Represents whether or not the IME of a browser process is active. - bool ime_is_active_; - - // Represents the status of the selected edit control sent to a browser - // process last time. - // When a renderer process finishes rendering a region, it retrieves: - // * The identifier of the selected edit control; - // * Whether or not the selected edit control requires IME, and; - // * The position of the caret (or cursor). - // If the above values is updated, a renderer process sends an IPC message - // to a browser process. A browser process uses these values to - // activate/deactivate IME and set the position of IME windows. - bool ime_control_enable_ime_; - int ime_control_x_; - int ime_control_y_; - bool ime_control_new_state_; - bool ime_control_updated_; - bool ime_control_busy_; + // Indicates if an input method is active in the browser process. + bool input_method_is_active_; + + // Stores the current text input type of |webwidget_|. + WebKit::WebTextInputType text_input_type_; + + // Stores the current caret bounds of input focus. + WebKit::WebRect caret_bounds_; // The kind of popup this widget represents, NONE if not a popup. WebKit::WebPopupType popup_type_; |