From d6b17a8fb552bc97d616dc0992f77fe7dafaec37 Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Fri, 28 Oct 2016 12:11:24 -0400 Subject: [PATCH] Standardize IME callbacks for off-screen rendering (issue #1675) --- AUTHORS.txt | 1 + BUILD.gn | 3 +- cef_paths2.gypi | 4 + include/capi/cef_browser_capi.h | 60 ++- include/capi/cef_render_handler_capi.h | 10 + include/cef_browser.h | 62 ++- include/cef_render_handler.h | 10 + include/internal/cef_linux.h | 1 - include/internal/cef_mac.h | 1 - include/internal/cef_types.h | 27 ++ include/internal/cef_types_linux.h | 1 - include/internal/cef_types_mac.h | 4 - include/internal/cef_types_win.h | 1 - include/internal/cef_types_wrappers.h | 27 ++ include/internal/cef_win.h | 1 - libcef/browser/browser_host_impl.cc | 66 ++- libcef/browser/browser_host_impl.h | 11 +- libcef/browser/browser_platform_delegate.cc | 27 +- libcef/browser/browser_platform_delegate.h | 19 +- .../osr/browser_platform_delegate_osr.cc | 34 ++ .../osr/browser_platform_delegate_osr.h | 9 + .../osr/browser_platform_delegate_osr_mac.h | 3 - .../osr/browser_platform_delegate_osr_mac.mm | 20 - .../osr/render_widget_host_view_osr.cc | 123 ++++-- .../browser/osr/render_widget_host_view_osr.h | 79 +--- .../osr/render_widget_host_view_osr_mac.mm | 232 ---------- .../browser/osr/text_input_client_osr_mac.h | 79 ---- .../browser/osr/text_input_client_osr_mac.mm | 402 ------------------ libcef_dll/cpptoc/browser_host_cpptoc.cc | 124 ++++-- libcef_dll/cpptoc/render_handler_cpptoc.cc | 42 ++ libcef_dll/ctocpp/browser_host_ctocpp.cc | 71 +++- libcef_dll/ctocpp/browser_host_ctocpp.h | 11 +- libcef_dll/ctocpp/render_handler_ctocpp.cc | 39 ++ libcef_dll/ctocpp/render_handler_ctocpp.h | 3 + .../browser/browser_window_osr_gtk.cc | 7 + .../browser/browser_window_osr_gtk.h | 4 + .../browser/browser_window_osr_mac.h | 5 + .../browser/browser_window_osr_mac.mm | 73 +++- tests/cefclient/browser/client_handler_osr.cc | 11 + tests/cefclient/browser/client_handler_osr.h | 8 + .../cefclient/browser/osr_ime_handler_win.cc | 390 +++++++++++++++++ tests/cefclient/browser/osr_ime_handler_win.h | 116 +++++ tests/cefclient/browser/osr_window_win.cc | 106 ++++- tests/cefclient/browser/osr_window_win.h | 14 + .../browser/text_input_client_osr_mac.h | 77 ++++ .../browser/text_input_client_osr_mac.mm | 343 +++++++++++++++ tests/unittests/os_rendering_unittest.cc | 211 +++++++++ tools/cef_parser.py | 3 +- 48 files changed, 1999 insertions(+), 976 deletions(-) create mode 100644 tests/cefclient/browser/osr_ime_handler_win.cc create mode 100644 tests/cefclient/browser/osr_ime_handler_win.h create mode 100644 tests/cefclient/browser/text_input_client_osr_mac.h create mode 100644 tests/cefclient/browser/text_input_client_osr_mac.mm diff --git a/AUTHORS.txt b/AUTHORS.txt index c326ef5f3..78eed1657 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -30,3 +30,4 @@ YuTeh Shen Andrei Kurushin Gonzo Berman Jakub Trzebiatowski +Nishant Kaushik diff --git a/BUILD.gn b/BUILD.gn index 2cd0add27..6275a1c2d 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -710,8 +710,6 @@ static_library("libcef_static") { "libcef/browser/osr/browser_platform_delegate_osr_mac.h", "libcef/browser/osr/browser_platform_delegate_osr_mac.mm", "libcef/browser/osr/render_widget_host_view_osr_mac.mm", - "libcef/browser/osr/text_input_client_osr_mac.mm", - "libcef/browser/osr/text_input_client_osr_mac.h", "libcef/common/util_mac.h", "libcef/common/util_mac.mm", ] @@ -1794,6 +1792,7 @@ if (is_mac) { "rpcrt4.lib", "opengl32.lib", "glu32.lib", + "imm32.lib", ] } diff --git a/cef_paths2.gypi b/cef_paths2.gypi index c5f32e206..ce6d26e04 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -248,6 +248,8 @@ 'tests/cefclient/browser/main_message_loop_multithreaded_win.h', 'tests/cefclient/browser/osr_dragdrop_win.cc', 'tests/cefclient/browser/osr_dragdrop_win.h', + 'tests/cefclient/browser/osr_ime_handler_win.cc', + 'tests/cefclient/browser/osr_ime_handler_win.h', 'tests/cefclient/browser/osr_window_win.cc', 'tests/cefclient/browser/osr_window_win.h', 'tests/cefclient/browser/resource_util_win.cc', @@ -284,6 +286,8 @@ 'tests/cefclient/browser/root_window_mac.mm', 'tests/cefclient/browser/temp_window_mac.h', 'tests/cefclient/browser/temp_window_mac.mm', + 'tests/cefclient/browser/text_input_client_osr_mac.h', + 'tests/cefclient/browser/text_input_client_osr_mac.mm', 'tests/cefclient/browser/window_test_runner_mac.h', 'tests/cefclient/browser/window_test_runner_mac.mm', 'tests/cefclient/cefclient_mac.mm', diff --git a/include/capi/cef_browser_capi.h b/include/capi/cef_browser_capi.h index bdc0ba714..8ad645cbf 100644 --- a/include/capi/cef_browser_capi.h +++ b/include/capi/cef_browser_capi.h @@ -618,24 +618,62 @@ typedef struct _cef_browser_host_t { struct _cef_browser_host_t* self, int frame_rate); /// - // Get the NSTextInputContext implementation for enabling IME on Mac when - // window rendering is disabled. + // Begins a new composition or updates the existing composition. Blink has a + // special node (a composition node) that allows the input function to change + // text without affecting other DOM nodes. |text| is the optional text that + // will be inserted into the composition node. |underlines| is an optional set + // of ranges that will be underlined in the resulting text. + // |replacement_range| is an optional range of the existing text that will be + // replaced. |selection_range| is an optional range of the resulting text that + // will be selected after insertion or replacement. The |replacement_range| + // value is only used on OS X. + // + // This function may be called multiple times as the composition changes. When + // the client is done making changes the composition should either be canceled + // or completed. To cancel the composition call ImeCancelComposition. To + // complete the composition call either ImeCommitText or + // ImeFinishComposingText. Completion is usually signaled when: + // A. The client receives a WM_IME_COMPOSITION message with a GCS_RESULTSTR + // flag (on Windows), or; + // B. The client receives a "commit" signal of GtkIMContext (on Linux), or; + // C. insertText of NSTextInput is called (on Mac). + // + // This function is only used when window rendering is disabled. /// - cef_text_input_context_t (CEF_CALLBACK *get_nstext_input_context)( - struct _cef_browser_host_t* self); + void (CEF_CALLBACK *ime_set_composition)(struct _cef_browser_host_t* self, + const cef_string_t* text, size_t underlinesCount, + cef_composition_underline_t const* underlines, + const cef_range_t* replacement_range, + const cef_range_t* selection_range); /// - // Handles a keyDown event prior to passing it through the NSTextInputClient - // machinery. + // Completes the existing composition by optionally inserting the specified + // |text| into the composition node. |replacement_range| is an optional range + // of the existing text that will be replaced. |relative_cursor_pos| is where + // the cursor will be positioned relative to the current cursor position. See + // comments on ImeSetComposition for usage. The |replacement_range| and + // |relative_cursor_pos| values are only used on OS X. This function is only + // used when window rendering is disabled. /// - void (CEF_CALLBACK *handle_key_event_before_text_input_client)( - struct _cef_browser_host_t* self, cef_event_handle_t keyEvent); + void (CEF_CALLBACK *ime_commit_text)(struct _cef_browser_host_t* self, + const cef_string_t* text, const cef_range_t* replacement_range, + int relative_cursor_pos); /// - // Performs any additional actions after NSTextInputClient handles the event. + // Completes the existing composition by applying the current composition node + // contents. If |keep_selection| is false (0) the current selection, if any, + // will be discarded. See comments on ImeSetComposition for usage. This + // function is only used when window rendering is disabled. /// - void (CEF_CALLBACK *handle_key_event_after_text_input_client)( - struct _cef_browser_host_t* self, cef_event_handle_t keyEvent); + void (CEF_CALLBACK *ime_finish_composing_text)( + struct _cef_browser_host_t* self, int keep_selection); + + /// + // Cancels the existing composition and discards the composition node contents + // without applying them. See comments on ImeSetComposition for usage. This + // function is only used when window rendering is disabled. + /// + void (CEF_CALLBACK *ime_cancel_composition)(struct _cef_browser_host_t* self); /// // Call this function when the user drags the mouse into the web view (before diff --git a/include/capi/cef_render_handler_capi.h b/include/capi/cef_render_handler_capi.h index 1a9d26529..498773f4a 100644 --- a/include/capi/cef_render_handler_capi.h +++ b/include/capi/cef_render_handler_capi.h @@ -161,6 +161,16 @@ typedef struct _cef_render_handler_t { void (CEF_CALLBACK *on_scroll_offset_changed)( struct _cef_render_handler_t* self, struct _cef_browser_t* browser, double x, double y); + + /// + // Called when the IME composition range has changed. |selected_range| is the + // range of characters that have been selected. |character_bounds| is the + // bounds of each character in view coordinates. + /// + void (CEF_CALLBACK *on_ime_composition_range_changed)( + struct _cef_render_handler_t* self, struct _cef_browser_t* browser, + const cef_range_t* selected_range, size_t character_boundsCount, + cef_rect_t const* character_bounds); } cef_render_handler_t; diff --git a/include/cef_browser.h b/include/cef_browser.h index eda660f1a..2d4d8abb7 100644 --- a/include/cef_browser.h +++ b/include/cef_browser.h @@ -669,24 +669,66 @@ class CefBrowserHost : public virtual CefBase { virtual void SetWindowlessFrameRate(int frame_rate) =0; /// - // Get the NSTextInputContext implementation for enabling IME on Mac when - // window rendering is disabled. + // Begins a new composition or updates the existing composition. Blink has a + // special node (a composition node) that allows the input method to change + // text without affecting other DOM nodes. |text| is the optional text that + // will be inserted into the composition node. |underlines| is an optional set + // of ranges that will be underlined in the resulting text. + // |replacement_range| is an optional range of the existing text that will be + // replaced. |selection_range| is an optional range of the resulting text that + // will be selected after insertion or replacement. The |replacement_range| + // value is only used on OS X. + // + // This method may be called multiple times as the composition changes. When + // the client is done making changes the composition should either be canceled + // or completed. To cancel the composition call ImeCancelComposition. To + // complete the composition call either ImeCommitText or + // ImeFinishComposingText. Completion is usually signaled when: + // A. The client receives a WM_IME_COMPOSITION message with a GCS_RESULTSTR + // flag (on Windows), or; + // B. The client receives a "commit" signal of GtkIMContext (on Linux), or; + // C. insertText of NSTextInput is called (on Mac). + // + // This method is only used when window rendering is disabled. /// - /*--cef(default_retval=NULL)--*/ - virtual CefTextInputContext GetNSTextInputContext() =0; + /*--cef(optional_param=text, optional_param=underlines)--*/ + virtual void ImeSetComposition( + const CefString& text, + const std::vector& underlines, + const CefRange& replacement_range, + const CefRange& selection_range) =0; /// - // Handles a keyDown event prior to passing it through the NSTextInputClient - // machinery. + // Completes the existing composition by optionally inserting the specified + // |text| into the composition node. |replacement_range| is an optional range + // of the existing text that will be replaced. |relative_cursor_pos| is where + // the cursor will be positioned relative to the current cursor position. See + // comments on ImeSetComposition for usage. The |replacement_range| and + // |relative_cursor_pos| values are only used on OS X. + // This method is only used when window rendering is disabled. /// - /*--cef()--*/ - virtual void HandleKeyEventBeforeTextInputClient(CefEventHandle keyEvent) =0; + /*--cef(optional_param=text)--*/ + virtual void ImeCommitText(const CefString& text, + const CefRange& replacement_range, + int relative_cursor_pos) =0; /// - // Performs any additional actions after NSTextInputClient handles the event. + // Completes the existing composition by applying the current composition node + // contents. If |keep_selection| is false the current selection, if any, will + // be discarded. See comments on ImeSetComposition for usage. + // This method is only used when window rendering is disabled. /// /*--cef()--*/ - virtual void HandleKeyEventAfterTextInputClient(CefEventHandle keyEvent) =0; + virtual void ImeFinishComposingText(bool keep_selection) =0; + + /// + // Cancels the existing composition and discards the composition node + // contents without applying them. See comments on ImeSetComposition for + // usage. + // This method is only used when window rendering is disabled. + /// + /*--cef()--*/ + virtual void ImeCancelComposition() =0; /// // Call this method when the user drags the mouse into the web view (before diff --git a/include/cef_render_handler.h b/include/cef_render_handler.h index 1b84f2f9a..0f857b07d 100644 --- a/include/cef_render_handler.h +++ b/include/cef_render_handler.h @@ -176,6 +176,16 @@ class CefRenderHandler : public virtual CefBase { virtual void OnScrollOffsetChanged(CefRefPtr browser, double x, double y) {} + + /// + // Called when the IME composition range has changed. |selected_range| is the + // range of characters that have been selected. |character_bounds| is the + // bounds of each character in view coordinates. + /// + /*--cef()--*/ + virtual void OnImeCompositionRangeChanged(CefRefPtr browser, + const CefRange& selected_range, + const RectList& character_bounds) {} }; #endif // CEF_INCLUDE_CEF_RENDER_HANDLER_H_ diff --git a/include/internal/cef_linux.h b/include/internal/cef_linux.h index 6238397a3..d9813a5b3 100644 --- a/include/internal/cef_linux.h +++ b/include/internal/cef_linux.h @@ -39,7 +39,6 @@ #define CefCursorHandle cef_cursor_handle_t #define CefEventHandle cef_event_handle_t #define CefWindowHandle cef_window_handle_t -#define CefTextInputContext cef_text_input_context_t struct CefMainArgsTraits { typedef cef_main_args_t struct_type; diff --git a/include/internal/cef_mac.h b/include/internal/cef_mac.h index 8defbfb09..420f16ada 100644 --- a/include/internal/cef_mac.h +++ b/include/internal/cef_mac.h @@ -39,7 +39,6 @@ #define CefCursorHandle cef_cursor_handle_t #define CefEventHandle cef_event_handle_t #define CefWindowHandle cef_window_handle_t -#define CefTextInputContext cef_text_input_context_t struct CefMainArgsTraits { typedef cef_main_args_t struct_type; diff --git a/include/internal/cef_types.h b/include/internal/cef_types.h index 933ea6e50..1309c5592 100644 --- a/include/internal/cef_types.h +++ b/include/internal/cef_types.h @@ -2682,6 +2682,33 @@ typedef enum { CEF_CDM_REGISTRATION_ERROR_NOT_SUPPORTED, } cef_cdm_registration_error_t; +/// +// Structure representing IME composition underline information. This is a thin +// wrapper around Blink's WebCompositionUnderline class and should be kept in +// sync with that. +/// +typedef struct _cef_composition_underline_t { + /// + // Underline character range. + /// + cef_range_t range; + + /// + // Text color. + /// + cef_color_t color; + + /// + // Background color. + /// + cef_color_t background_color; + + /// + // Set to true (1) for thick underline. + /// + int thick; +} cef_composition_underline_t; + #ifdef __cplusplus } #endif diff --git a/include/internal/cef_types_linux.h b/include/internal/cef_types_linux.h index 3fb896dd0..865a9613a 100644 --- a/include/internal/cef_types_linux.h +++ b/include/internal/cef_types_linux.h @@ -60,7 +60,6 @@ extern "C" { // thread-safe and must only be accessed on the browser process UI thread. /// CEF_EXPORT XDisplay* cef_get_xdisplay(); -#define cef_text_input_context_t void* /// // Structure representing CefExecuteProcess arguments. diff --git a/include/internal/cef_types_mac.h b/include/internal/cef_types_mac.h index 9603fc60a..8947e258d 100644 --- a/include/internal/cef_types_mac.h +++ b/include/internal/cef_types_mac.h @@ -43,22 +43,18 @@ @class NSCursor; @class NSEvent; @class NSView; -@class NSTextInputContext; #else class NSCursor; class NSEvent; struct NSView; -class NSTextInputContext; #endif #define cef_cursor_handle_t NSCursor* #define cef_event_handle_t NSEvent* #define cef_window_handle_t NSView* -#define cef_text_input_context_t NSTextInputContext* #else #define cef_cursor_handle_t void* #define cef_event_handle_t void* #define cef_window_handle_t void* -#define cef_text_input_context_t void* #endif #define kNullCursorHandle NULL diff --git a/include/internal/cef_types_win.h b/include/internal/cef_types_win.h index 27bf6053b..0c23a7e7c 100644 --- a/include/internal/cef_types_win.h +++ b/include/internal/cef_types_win.h @@ -42,7 +42,6 @@ #define cef_cursor_handle_t HCURSOR #define cef_event_handle_t MSG* #define cef_window_handle_t HWND -#define cef_text_input_context_t void* #define kNullCursorHandle NULL #define kNullEventHandle NULL diff --git a/include/internal/cef_types_wrappers.h b/include/internal/cef_types_wrappers.h index 44b49e94d..425481692 100644 --- a/include/internal/cef_types_wrappers.h +++ b/include/internal/cef_types_wrappers.h @@ -975,4 +975,31 @@ struct CefBoxLayoutSettingsTraits { /// typedef CefStructBase CefBoxLayoutSettings; +struct CefCompositionUnderlineTraits { + typedef cef_composition_underline_t struct_type; + + static inline void init(struct_type* s) { + s->range = {0, 0}; + s->color = 0; + s->background_color = 0; + s->thick = 0; + } + + static inline void clear(struct_type* s) { + } + + static inline void set(const struct_type* src, struct_type* target, + bool copy) { + target->range = src->range; + target->color = src->color; + target->background_color = src->background_color; + target->thick = src->thick; + } +}; + +/// +// Class representing IME composition underline. +/// +typedef CefStructBase CefCompositionUnderline; + #endif // CEF_INCLUDE_INTERNAL_CEF_TYPES_WRAPPERS_H_ diff --git a/include/internal/cef_win.h b/include/internal/cef_win.h index 7361a3ce4..9c20c4818 100644 --- a/include/internal/cef_win.h +++ b/include/internal/cef_win.h @@ -41,7 +41,6 @@ #define CefCursorHandle cef_cursor_handle_t #define CefEventHandle cef_event_handle_t #define CefWindowHandle cef_window_handle_t -#define CefTextInputContext cef_text_input_context_t struct CefMainArgsTraits { typedef cef_main_args_t struct_type; diff --git a/libcef/browser/browser_host_impl.cc b/libcef/browser/browser_host_impl.cc index 89b1979a1..9999b5404 100644 --- a/libcef/browser/browser_host_impl.cc +++ b/libcef/browser/browser_host_impl.cc @@ -1797,65 +1797,87 @@ void CefBrowserHostImpl::FindReply( } } -CefTextInputContext CefBrowserHostImpl::GetNSTextInputContext() { -#if defined(OS_MACOSX) +void CefBrowserHostImpl::ImeSetComposition( + const CefString& text, + const std::vector& underlines, + const CefRange& replacement_range, + const CefRange& selection_range) { if (!IsWindowless()) { NOTREACHED() << "Window rendering is not disabled"; - return nullptr; + return; } if (!CEF_CURRENTLY_ON_UIT()) { - NOTREACHED() << "Called on invalid thread"; - return nullptr; + CEF_POST_TASK(CEF_UIT, + base::Bind(&CefBrowserHostImpl::ImeSetComposition, this, text, + underlines, replacement_range, selection_range)); + return; } if (!web_contents() || !platform_delegate_) - return nullptr; + return; - return platform_delegate_->GetNSTextInputContext(); -#else - return nullptr; -#endif + platform_delegate_->ImeSetComposition(text, underlines, replacement_range, + selection_range); } -void CefBrowserHostImpl::HandleKeyEventBeforeTextInputClient( - CefEventHandle keyEvent) { -#if defined(OS_MACOSX) +void CefBrowserHostImpl::ImeCommitText(const CefString& text, + const CefRange& replacement_range, + int relative_cursor_pos) { if (!IsWindowless()) { NOTREACHED() << "Window rendering is not disabled"; return; } if (!CEF_CURRENTLY_ON_UIT()) { - NOTREACHED() << "Called on invalid thread"; + CEF_POST_TASK(CEF_UIT, + base::Bind(&CefBrowserHostImpl::ImeCommitText, this, text, + replacement_range, relative_cursor_pos)); return; } if (!web_contents() || !platform_delegate_) return; - platform_delegate_->HandleKeyEventBeforeTextInputClient(keyEvent); -#endif + platform_delegate_->ImeCommitText(text, replacement_range, + relative_cursor_pos); } -void CefBrowserHostImpl::HandleKeyEventAfterTextInputClient( - CefEventHandle keyEvent) { -#if defined(OS_MACOSX) +void CefBrowserHostImpl::ImeFinishComposingText(bool keep_selection) { if (!IsWindowless()) { NOTREACHED() << "Window rendering is not disabled"; return; } if (!CEF_CURRENTLY_ON_UIT()) { - NOTREACHED() << "Called on invalid thread"; + CEF_POST_TASK(CEF_UIT, + base::Bind(&CefBrowserHostImpl::ImeFinishComposingText, this, + keep_selection)); return; } if (!web_contents() || !platform_delegate_) return; - return platform_delegate_->HandleKeyEventAfterTextInputClient(keyEvent); -#endif + platform_delegate_->ImeFinishComposingText(keep_selection); +} + +void CefBrowserHostImpl::ImeCancelComposition() { + if (!IsWindowless()) { + NOTREACHED() << "Window rendering is not disabled"; + return; + } + + if (!CEF_CURRENTLY_ON_UIT()) { + CEF_POST_TASK(CEF_UIT, + base::Bind(&CefBrowserHostImpl::ImeCancelComposition, this)); + return; + } + + if (!web_contents() || !platform_delegate_) + return; + + platform_delegate_->ImeCancelComposition(); } void CefBrowserHostImpl::DragTargetDragEnter(CefRefPtr drag_data, diff --git a/libcef/browser/browser_host_impl.h b/libcef/browser/browser_host_impl.h index 41efbf853..71732d069 100644 --- a/libcef/browser/browser_host_impl.h +++ b/libcef/browser/browser_host_impl.h @@ -206,9 +206,14 @@ class CefBrowserHostImpl : public CefBrowserHost, void NotifyMoveOrResizeStarted() override; int GetWindowlessFrameRate() override; void SetWindowlessFrameRate(int frame_rate) override; - CefTextInputContext GetNSTextInputContext() override; - void HandleKeyEventBeforeTextInputClient(CefEventHandle keyEvent) override; - void HandleKeyEventAfterTextInputClient(CefEventHandle keyEvent) override; + void ImeSetComposition(const CefString& text, + const std::vector& underlines, + const CefRange& replacement_range, + const CefRange& selection_range) override; + void ImeCommitText(const CefString& text, const CefRange& replacement_range, + int relative_cursor_pos) override; + void ImeFinishComposingText(bool keep_selection) override; + void ImeCancelComposition() override; void DragTargetDragEnter(CefRefPtr drag_data, const CefMouseEvent& event, DragOperationsMask allowed_ops) override; diff --git a/libcef/browser/browser_platform_delegate.cc b/libcef/browser/browser_platform_delegate.cc index a4addee96..53b5deaf7 100644 --- a/libcef/browser/browser_platform_delegate.cc +++ b/libcef/browser/browser_platform_delegate.cc @@ -134,22 +134,27 @@ void CefBrowserPlatformDelegate::SetWindowlessFrameRate(int frame_rate) { NOTREACHED(); } -#if defined(OS_MACOSX) -CefTextInputContext CefBrowserPlatformDelegate::GetNSTextInputContext() { - NOTREACHED(); - return nullptr; -} - -void CefBrowserPlatformDelegate::HandleKeyEventBeforeTextInputClient( - CefEventHandle keyEvent) { +void CefBrowserPlatformDelegate::ImeSetComposition(const CefString& text, + const std::vector& underlines, + const CefRange& replacement_range, + const CefRange& selection_range) { NOTREACHED(); } -void CefBrowserPlatformDelegate::HandleKeyEventAfterTextInputClient( - CefEventHandle keyEvent) { +void CefBrowserPlatformDelegate::ImeCommitText( + const CefString& text, + const CefRange& replacement_range, + int relative_cursor_pos) { + NOTREACHED(); +} + +void CefBrowserPlatformDelegate::ImeFinishComposingText(bool keep_selection) { + NOTREACHED(); +} + +void CefBrowserPlatformDelegate::ImeCancelComposition() { NOTREACHED(); } -#endif void CefBrowserPlatformDelegate::DragTargetDragEnter( CefRefPtr drag_data, diff --git a/libcef/browser/browser_platform_delegate.h b/libcef/browser/browser_platform_delegate.h index 7d362aa4e..5b710baf3 100644 --- a/libcef/browser/browser_platform_delegate.h +++ b/libcef/browser/browser_platform_delegate.h @@ -222,13 +222,18 @@ class CefBrowserPlatformDelegate { // Set the windowless frame rate. Only used with windowless rendering. virtual void SetWindowlessFrameRate(int frame_rate); -#if defined(OS_MACOSX) - // IME-related callbacks. See documentation in CefRenderHandler. Only used - // with windowless rendering on OS X. - virtual CefTextInputContext GetNSTextInputContext(); - virtual void HandleKeyEventBeforeTextInputClient(CefEventHandle keyEvent); - virtual void HandleKeyEventAfterTextInputClient(CefEventHandle keyEvent); -#endif + // IME-related callbacks. See documentation in CefBrowser and + // CefRenderHandler. Only used with windowless rendering. + virtual void ImeSetComposition( + const CefString& text, + const std::vector& underlines, + const CefRange& replacement_range, + const CefRange& selection_range); + virtual void ImeCommitText(const CefString& text, + const CefRange& replacement_range, + int relative_cursor_pos); + virtual void ImeFinishComposingText(bool keep_selection); + virtual void ImeCancelComposition(); // Drag/drop-related callbacks. See documentation in CefRenderHandler. Only // used with windowless rendering. diff --git a/libcef/browser/osr/browser_platform_delegate_osr.cc b/libcef/browser/osr/browser_platform_delegate_osr.cc index 566be0fb4..42a2639a0 100644 --- a/libcef/browser/osr/browser_platform_delegate_osr.cc +++ b/libcef/browser/osr/browser_platform_delegate_osr.cc @@ -209,6 +209,40 @@ void CefBrowserPlatformDelegateOsr::SetWindowlessFrameRate(int frame_rate) { view->UpdateFrameRate(); } +void CefBrowserPlatformDelegateOsr::ImeSetComposition( + const CefString& text, + const std::vector& underlines, + const CefRange& replacement_range, + const CefRange& selection_range) { + CefRenderWidgetHostViewOSR* view = GetOSRHostView(); + if (view) { + view->ImeSetComposition(text, underlines, + replacement_range, selection_range); + } +} + +void CefBrowserPlatformDelegateOsr::ImeCommitText( + const CefString& text, + const CefRange& replacement_range, + int relative_cursor_pos) { + CefRenderWidgetHostViewOSR* view = GetOSRHostView(); + if (view) + view->ImeCommitText(text, replacement_range, relative_cursor_pos); +} + +void CefBrowserPlatformDelegateOsr::ImeFinishComposingText( + bool keep_selection) { + CefRenderWidgetHostViewOSR* view = GetOSRHostView(); + if (view) + view->ImeFinishComposingText(keep_selection); +} + +void CefBrowserPlatformDelegateOsr::ImeCancelComposition() { + CefRenderWidgetHostViewOSR* view = GetOSRHostView(); + if (view) + view->ImeCancelComposition(); +} + void CefBrowserPlatformDelegateOsr::DragTargetDragEnter( CefRefPtr drag_data, const CefMouseEvent& event, diff --git a/libcef/browser/osr/browser_platform_delegate_osr.h b/libcef/browser/osr/browser_platform_delegate_osr.h index 0956b6c1f..d7c8dfb4d 100644 --- a/libcef/browser/osr/browser_platform_delegate_osr.h +++ b/libcef/browser/osr/browser_platform_delegate_osr.h @@ -56,6 +56,15 @@ class CefBrowserPlatformDelegateOsr : void NotifyScreenInfoChanged() override; void Invalidate(cef_paint_element_type_t type) override; void SetWindowlessFrameRate(int frame_rate) override; + void ImeSetComposition( + const CefString& text, + const std::vector& underlines, + const CefRange& replacement_range, + const CefRange& selection_range) override; + void ImeCommitText(const CefString& text, const CefRange& replacement_range, + int relative_cursor_pos) override; + void ImeFinishComposingText(bool keep_selection) override; + void ImeCancelComposition() override; void DragTargetDragEnter(CefRefPtr drag_data, const CefMouseEvent& event, cef_drag_operations_mask_t allowed_ops) override; diff --git a/libcef/browser/osr/browser_platform_delegate_osr_mac.h b/libcef/browser/osr/browser_platform_delegate_osr_mac.h index 985acc591..84fec3c84 100644 --- a/libcef/browser/osr/browser_platform_delegate_osr_mac.h +++ b/libcef/browser/osr/browser_platform_delegate_osr_mac.h @@ -15,9 +15,6 @@ class CefBrowserPlatformDelegateOsrMac : public CefBrowserPlatformDelegateOsr { // CefBrowserPlatformDelegate methods: CefWindowHandle GetHostWindowHandle() const override; - CefTextInputContext GetNSTextInputContext() override; - void HandleKeyEventBeforeTextInputClient(CefEventHandle keyEvent) override; - void HandleKeyEventAfterTextInputClient(CefEventHandle keyEvent) override; }; #endif // CEF_LIBCEF_BROWSER_NATIVE_BROWSER_PLATFORM_DELEGATE_OSR_MAC_H_ diff --git a/libcef/browser/osr/browser_platform_delegate_osr_mac.mm b/libcef/browser/osr/browser_platform_delegate_osr_mac.mm index ed7121449..4f8c4172d 100644 --- a/libcef/browser/osr/browser_platform_delegate_osr_mac.mm +++ b/libcef/browser/osr/browser_platform_delegate_osr_mac.mm @@ -18,23 +18,3 @@ CefWindowHandle CefBrowserPlatformDelegateOsrMac::GetHostWindowHandle() const { return native_delegate_->window_info().parent_view; } -CefTextInputContext CefBrowserPlatformDelegateOsrMac::GetNSTextInputContext() { - CefRenderWidgetHostViewOSR* view = GetOSRHostView(); - if (view) - return view->GetNSTextInputContext(); - return nullptr; -} - -void CefBrowserPlatformDelegateOsrMac::HandleKeyEventBeforeTextInputClient( - CefEventHandle keyEvent) { - CefRenderWidgetHostViewOSR* view = GetOSRHostView(); - if (view) - view->HandleKeyEventBeforeTextInputClient(keyEvent); -} - -void CefBrowserPlatformDelegateOsrMac::HandleKeyEventAfterTextInputClient( - CefEventHandle keyEvent) { - CefRenderWidgetHostViewOSR* view = GetOSRHostView(); - if (view) - view->HandleKeyEventAfterTextInputClient(keyEvent); -} diff --git a/libcef/browser/osr/render_widget_host_view_osr.cc b/libcef/browser/osr/render_widget_host_view_osr.cc index 6ed77198d..13eb412e3 100644 --- a/libcef/browser/osr/render_widget_host_view_osr.cc +++ b/libcef/browser/osr/render_widget_host_view_osr.cc @@ -28,6 +28,7 @@ #include "content/browser/renderer_host/render_widget_host_delegate.h" #include "content/browser/renderer_host/render_widget_host_impl.h" #include "content/browser/renderer_host/resize_lock.h" +#include "content/common/input_messages.h" #include "content/common/view_messages.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/context_factory.h" @@ -468,9 +469,6 @@ CefRenderWidgetHostViewOSR::CefRenderWidgetHostViewOSR( is_showing_(!render_widget_host_->is_hidden()), is_destroyed_(false), is_scroll_offset_changed_pending_(false), -#if defined(OS_MACOSX) - text_input_context_osr_mac_(NULL), -#endif weak_ptr_factory_(this) { DCHECK(render_widget_host_); DCHECK(!render_widget_host_->GetView()); @@ -580,10 +578,6 @@ gfx::NativeViewAccessible return gfx::NativeViewAccessible(); } -ui::TextInputClient* CefRenderWidgetHostViewOSR::GetTextInputClient() { - return NULL; -} - void CefRenderWidgetHostViewOSR::Focus() { } @@ -828,15 +822,6 @@ void CefRenderWidgetHostViewOSR::UpdateCursor( void CefRenderWidgetHostViewOSR::SetIsLoading(bool is_loading) { } -#if !defined(OS_MACOSX) -void CefRenderWidgetHostViewOSR::TextInputStateChanged( - const content::TextInputState& params) { -} - -void CefRenderWidgetHostViewOSR::ImeCancelComposition() { -} -#endif // !defined(OS_MACOSX) - void CefRenderWidgetHostViewOSR::RenderProcessGone( base::TerminationStatus status, int error_code) { @@ -884,12 +869,6 @@ gfx::Size CefRenderWidgetHostViewOSR::GetPhysicalBackingSize() const { return gfx::ConvertSizeToPixel(scale_factor_, GetRequestedRendererSize()); } -#if !defined(OS_MACOSX) -void CefRenderWidgetHostViewOSR::SelectionBoundsChanged( - const ViewHostMsg_SelectionBounds_Params& params) { -} -#endif - void CefRenderWidgetHostViewOSR::CopyFromCompositingSurface( const gfx::Rect& src_subrect, const gfx::Size& dst_size, @@ -963,12 +942,71 @@ void CefRenderWidgetHostViewOSR::ShowDisambiguationPopup( } #endif -#if !defined(OS_MACOSX) && defined(USE_AURA) -void CefRenderWidgetHostViewOSR::ImeCompositionRangeChanged( - const gfx::Range& range, - const std::vector& character_bounds) { +void CefRenderWidgetHostViewOSR::ImeSetComposition( + const CefString& text, + const std::vector& underlines, + const CefRange& replacement_range, + const CefRange& selection_range) { + TRACE_EVENT0("libcef", "CefRenderWidgetHostViewOSR::ImeSetComposition"); + if (!render_widget_host_) + return; + + std::vector web_underlines; + web_underlines.reserve(underlines.size()); + for (const CefCompositionUnderline& line : underlines) { + web_underlines.push_back( + blink::WebCompositionUnderline(line.range.from, + line.range.to, + line.color, + line.thick ? true : false, + line.background_color)); + } + gfx::Range range(replacement_range.from, replacement_range.to); + + // Start Monitoring for composition updates before we set. + RequestImeCompositionUpdate(true); + + render_widget_host_->ImeSetComposition(text, web_underlines, range, + selection_range.from, + selection_range.to); +} + +void CefRenderWidgetHostViewOSR::ImeCommitText( + const CefString& text, + const CefRange& replacement_range, + int relative_cursor_pos) { + TRACE_EVENT0("libcef", "CefRenderWidgetHostViewOSR::ImeCommitText"); + if (!render_widget_host_) + return; + + gfx::Range range(replacement_range.from, replacement_range.to); + render_widget_host_->ImeCommitText(text, range, relative_cursor_pos); + + // Stop Monitoring for composition updates after we are done. + RequestImeCompositionUpdate(false); +} + +void CefRenderWidgetHostViewOSR::ImeFinishComposingText(bool keep_selection) { + TRACE_EVENT0("libcef", "CefRenderWidgetHostViewOSR::ImeFinishComposingText"); + if (!render_widget_host_) + return; + + render_widget_host_->ImeFinishComposingText(keep_selection); + + // Stop Monitoring for composition updates after we are done. + RequestImeCompositionUpdate(false); +} + +void CefRenderWidgetHostViewOSR::ImeCancelComposition() { + TRACE_EVENT0("libcef", "CefRenderWidgetHostViewOSR::ImeCancelComposition"); + if (!render_widget_host_) + return; + + render_widget_host_->ImeCancelComposition(); + + // Stop Monitoring for composition updates after we are done. + RequestImeCompositionUpdate(false); } -#endif void CefRenderWidgetHostViewOSR::SetNeedsBeginFrames(bool enabled) { SetFrameRate(); @@ -1516,3 +1554,34 @@ void CefRenderWidgetHostViewOSR::InvalidateInternal( copy_frame_generator_->GenerateCopyFrame(true, bounds_in_pixels); } } + +void CefRenderWidgetHostViewOSR::RequestImeCompositionUpdate( + bool start_monitoring) { + if (!render_widget_host_) + return; + render_widget_host_->Send( + new InputMsg_RequestCompositionUpdate(render_widget_host_->GetRoutingID(), + false, start_monitoring)); +} + +void CefRenderWidgetHostViewOSR::ImeCompositionRangeChanged( + const gfx::Range& range, + const std::vector& character_bounds) { + if (browser_impl_.get()) { + CefRange cef_range(range.start(), range.end()); + CefRenderHandler::RectList rcList; + + for (size_t i = 0; i < character_bounds.size(); ++i) { + rcList.push_back(CefRect(character_bounds[i].x(), character_bounds[i].y(), + character_bounds[i].width(), + character_bounds[i].height())); + } + + CefRefPtr handler = + browser_impl_->GetClient()->GetRenderHandler(); + if (handler.get()) { + handler->OnImeCompositionRangeChanged(browser_impl_->GetBrowser(), + cef_range, rcList); + } + } +} diff --git a/libcef/browser/osr/render_widget_host_view_osr.h b/libcef/browser/osr/render_widget_host_view_osr.h index 43f6881f8..8af787710 100644 --- a/libcef/browser/osr/render_widget_host_view_osr.h +++ b/libcef/browser/osr/render_widget_host_view_osr.h @@ -101,7 +101,6 @@ class CefRenderWidgetHostViewOSR gfx::Vector2dF GetLastScrollOffset() const override; gfx::NativeView GetNativeView() const override; gfx::NativeViewAccessible GetNativeViewAccessible() override; - ui::TextInputClient* GetTextInputClient() override; void Focus() override; bool HasFocus() const override; bool IsSurfaceAvailableForCopy() const override; @@ -133,23 +132,13 @@ class CefRenderWidgetHostViewOSR content::RenderWidgetHostView* reference_host_view) override; void UpdateCursor(const content::WebCursor& cursor) override; void SetIsLoading(bool is_loading) override; - void TextInputStateChanged(const content::TextInputState& params) override; - void ImeCancelComposition() override; void RenderProcessGone(base::TerminationStatus status, int error_code) override; void Destroy() override; void SetTooltipText(const base::string16& tooltip_text) override; -#if defined(OS_MACOSX) - void SelectionChanged(const base::string16& text, - size_t offset, - const gfx::Range& range) override; -#endif - gfx::Size GetRequestedRendererSize() const override; gfx::Size GetPhysicalBackingSize() const override; - void SelectionBoundsChanged( - const ViewHostMsg_SelectionBounds_Params& params) override; void CopyFromCompositingSurface( const gfx::Rect& src_subrect, const gfx::Size& dst_size, @@ -177,12 +166,9 @@ class CefRenderWidgetHostViewOSR void ShowDisambiguationPopup(const gfx::Rect& rect_pixels, const SkBitmap& zoomed_bitmap) override; #endif - -#if defined(OS_MACOSX) || defined(USE_AURA) void ImeCompositionRangeChanged( const gfx::Range& range, const std::vector& character_bounds) override; -#endif void SetNeedsBeginFrames(bool enabled) override; @@ -233,19 +219,16 @@ class CefRenderWidgetHostViewOSR return popup_type_ != blink::WebPopupTypeNone; } -#if defined(OS_MACOSX) - NSTextInputContext* GetNSTextInputContext(); - void HandleKeyEventBeforeTextInputClient(CefEventHandle keyEvent); - void HandleKeyEventAfterTextInputClient(CefEventHandle keyEvent); - - bool GetCachedFirstRectForCharacterRange(gfx::Range range, gfx::Rect* rect, - gfx::Range* actual_range) const; - - const std::string& selected_text() const { return selected_text_; } - const gfx::Range& composition_range() const { return composition_range_; } - const base::string16& selection_text() const { return selection_text_; } - size_t selection_text_offset() const { return selection_text_offset_; } -#endif // defined(OS_MACOSX) + void ImeSetComposition( + const CefString& text, + const std::vector& underlines, + const CefRange& replacement_range, + const CefRange& selection_range); + void ImeCommitText(const CefString& text, + const CefRange& replacement_range, + int relative_cursor_pos); + void ImeFinishComposingText(bool keep_selection); + void ImeCancelComposition(); void AddGuestHostView(CefRenderWidgetHostViewOSR* guest_host); void RemoveGuestHostView(CefRenderWidgetHostViewOSR* guest_host); @@ -293,30 +276,11 @@ class CefRenderWidgetHostViewOSR void InvalidateInternal(const gfx::Rect& bounds_in_pixels); + void RequestImeCompositionUpdate(bool start_monitoring); + #if defined(OS_MACOSX) friend class MacHelper; - - // Returns composition character boundary rectangle. The |range| is - // composition based range. Also stores |actual_range| which is corresponding - // to actually used range for returned rectangle. - gfx::Rect GetFirstRectForCompositionRange(const gfx::Range& range, - gfx::Range* actual_range) const; - - // Converts from given whole character range to composition oriented range. If - // the conversion failed, return gfx::Range::InvalidRange. - gfx::Range ConvertCharacterRangeToCompositionRange( - const gfx::Range& request_range) const; - - // Returns true if there is line break in |range| and stores line breaking - // point to |line_breaking_point|. The |line_break_point| is valid only if - // this function returns true. - static bool GetLineBreakIndex(const std::vector& bounds, - const gfx::Range& range, - size_t* line_break_point); - - void DestroyNSTextInputOSR(); -#endif // defined(OS_MACOSX) - +#endif void PlatformCreateCompositorWidget(); void PlatformResizeCompositorWidget(const gfx::Size& size); void PlatformDestroyCompositorWidget(); @@ -384,23 +348,6 @@ class CefRenderWidgetHostViewOSR gfx::Vector2dF last_scroll_offset_; bool is_scroll_offset_changed_pending_; -#if defined(OS_MACOSX) - NSTextInputContext* text_input_context_osr_mac_; - - // Selected text on the renderer. - std::string selected_text_; - - // The current composition character range and its bounds. - gfx::Range composition_range_; - std::vector composition_bounds_; - - // The current caret bounds. - gfx::Rect caret_rect_; - - // The current first selection bounds. - gfx::Rect first_selection_rect_; -#endif - base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(CefRenderWidgetHostViewOSR); diff --git a/libcef/browser/osr/render_widget_host_view_osr_mac.mm b/libcef/browser/osr/render_widget_host_view_osr_mac.mm index 14ae72eae..cb2cb5994 100644 --- a/libcef/browser/osr/render_widget_host_view_osr_mac.mm +++ b/libcef/browser/osr/render_widget_host_view_osr_mac.mm @@ -12,7 +12,6 @@ #import #include "libcef/browser/browser_host_impl.h" -#include "libcef/browser/osr/text_input_client_osr_mac.h" #include "base/compiler_specific.h" #include "base/strings/utf_string_conversions.h" @@ -20,17 +19,6 @@ #include "ui/accelerated_widget_mac/accelerated_widget_mac.h" #include "ui/events/latency_info.h" -namespace { - -CefTextInputClientOSRMac* GetInputClientFromContext( - const NSTextInputContext* context) { - if (!context) - return NULL; - return reinterpret_cast([context client]); -} - -} // namespace - class MacHelper : public content::BrowserCompositorMacClient, @@ -131,226 +119,6 @@ bool CefRenderWidgetHostViewOSR::IsSpeaking() const { void CefRenderWidgetHostViewOSR::StopSpeaking() { } -void CefRenderWidgetHostViewOSR::TextInputStateChanged( - const content::TextInputState& params) { - [NSApp updateWindows]; -} - -void CefRenderWidgetHostViewOSR::ImeCancelComposition() { - CefTextInputClientOSRMac* client = GetInputClientFromContext( - text_input_context_osr_mac_); - if (client) - [client cancelComposition]; -} - -void CefRenderWidgetHostViewOSR::ImeCompositionRangeChanged( - const gfx::Range& range, - const std::vector& character_bounds) { - CefTextInputClientOSRMac* client = GetInputClientFromContext( - text_input_context_osr_mac_); - if (!client) - return; - - composition_range_ = range; - composition_bounds_ = character_bounds; -} - -void CefRenderWidgetHostViewOSR::SelectionChanged( - const base::string16& text, - size_t offset, - const gfx::Range& range) { - if (range.is_empty() || text.empty()) { - selected_text_.clear(); - } else { - size_t pos = range.GetMin() - offset; - size_t n = range.length(); - - DCHECK(pos + n <= text.length()) << "The text can not fully cover range."; - if (pos >= text.length()) { - DCHECK(false) << "The text can not cover range."; - return; - } - selected_text_ = base::UTF16ToUTF8(text.substr(pos, n)); - } - - RenderWidgetHostViewBase::SelectionChanged(text, offset, range); -} - -void CefRenderWidgetHostViewOSR::SelectionBoundsChanged( - const ViewHostMsg_SelectionBounds_Params& params) { - if (params.anchor_rect == params.focus_rect) - caret_rect_ = params.anchor_rect; - first_selection_rect_ = params.anchor_rect; -} - -CefTextInputContext CefRenderWidgetHostViewOSR::GetNSTextInputContext() { - if (!text_input_context_osr_mac_) { - CefTextInputClientOSRMac* text_input_client_osr_mac = - [[CefTextInputClientOSRMac alloc] initWithRenderWidgetHostViewOSR: - this]; - - text_input_context_osr_mac_ = [[NSTextInputContext alloc] initWithClient: - text_input_client_osr_mac]; - } - - return text_input_context_osr_mac_; -} - -void CefRenderWidgetHostViewOSR::HandleKeyEventBeforeTextInputClient( - CefEventHandle keyEvent) { - CefTextInputClientOSRMac* client = GetInputClientFromContext( - text_input_context_osr_mac_); - if (client) - [client HandleKeyEventBeforeTextInputClient: keyEvent]; -} - -void CefRenderWidgetHostViewOSR::HandleKeyEventAfterTextInputClient( - CefEventHandle keyEvent) { - CefTextInputClientOSRMac* client = GetInputClientFromContext( - text_input_context_osr_mac_); - if (client) - [client HandleKeyEventAfterTextInputClient: keyEvent]; -} - -bool CefRenderWidgetHostViewOSR::GetCachedFirstRectForCharacterRange( - gfx::Range range, gfx::Rect* rect, gfx::Range* actual_range) const { - DCHECK(rect); - - const gfx::Range requested_range(range); - // If requested range is same as caret location, we can just return it. - if (selection_range_.is_empty() && requested_range == selection_range_) { - if (actual_range) - *actual_range = range; - *rect = caret_rect_; - return true; - } - - if (composition_range_.is_empty()) { - if (!selection_range_.Contains(requested_range)) - return false; - if (actual_range) - *actual_range = selection_range_; - *rect = first_selection_rect_; - return true; - } - - const gfx::Range request_range_in_composition = - ConvertCharacterRangeToCompositionRange(requested_range); - if (request_range_in_composition == gfx::Range::InvalidRange()) - return false; - - // If firstRectForCharacterRange in WebFrame is failed in renderer, - // ImeCompositionRangeChanged will be sent with empty vector. - if (composition_bounds_.empty()) - return false; - DCHECK_EQ(composition_bounds_.size(), composition_range_.length()); - - gfx::Range ui_actual_range; - *rect = GetFirstRectForCompositionRange(request_range_in_composition, - &ui_actual_range); - if (actual_range) { - *actual_range = gfx::Range( - composition_range_.start() + ui_actual_range.start(), - composition_range_.start() + ui_actual_range.end()); - } - return true; -} - -gfx::Rect CefRenderWidgetHostViewOSR::GetFirstRectForCompositionRange( - const gfx::Range& range, gfx::Range* actual_range) const { - DCHECK(actual_range); - DCHECK(!composition_bounds_.empty()); - DCHECK(range.start() <= composition_bounds_.size()); - DCHECK(range.end() <= composition_bounds_.size()); - - if (range.is_empty()) { - *actual_range = range; - if (range.start() == composition_bounds_.size()) { - return gfx::Rect(composition_bounds_[range.start() - 1].right(), - composition_bounds_[range.start() - 1].y(), - 0, - composition_bounds_[range.start() - 1].height()); - } else { - return gfx::Rect(composition_bounds_[range.start()].x(), - composition_bounds_[range.start()].y(), - 0, - composition_bounds_[range.start()].height()); - } - } - - size_t end_idx; - if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) { - end_idx = range.end(); - } - *actual_range = gfx::Range(range.start(), end_idx); - gfx::Rect rect = composition_bounds_[range.start()]; - for (size_t i = range.start() + 1; i < end_idx; ++i) { - rect.Union(composition_bounds_[i]); - } - return rect; -} - -gfx::Range CefRenderWidgetHostViewOSR::ConvertCharacterRangeToCompositionRange( - const gfx::Range& request_range) const { - if (composition_range_.is_empty()) - return gfx::Range::InvalidRange(); - - if (request_range.is_reversed()) - return gfx::Range::InvalidRange(); - - if (request_range.start() < composition_range_.start() || - request_range.start() > composition_range_.end() || - request_range.end() > composition_range_.end()) { - return gfx::Range::InvalidRange(); - } - - return gfx::Range( - request_range.start() - composition_range_.start(), - request_range.end() - composition_range_.start()); -} - -bool CefRenderWidgetHostViewOSR::GetLineBreakIndex( - const std::vector& bounds, - const gfx::Range& range, - size_t* line_break_point) { - DCHECK(line_break_point); - if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty()) - return false; - - // We can't check line breaking completely from only rectangle array. Thus we - // assume the line breaking as the next character's y offset is larger than - // a threshold. Currently the threshold is determined as minimum y offset plus - // 75% of maximum height. - const size_t loop_end_idx = - std::min(bounds.size(), static_cast(range.end())); - int max_height = 0; - int min_y_offset = std::numeric_limits::max(); - for (size_t idx = range.start(); idx < loop_end_idx; ++idx) { - max_height = std::max(max_height, bounds[idx].height()); - min_y_offset = std::min(min_y_offset, bounds[idx].y()); - } - int line_break_threshold = min_y_offset + (max_height * 3 / 4); - for (size_t idx = range.start(); idx < loop_end_idx; ++idx) { - if (bounds[idx].y() > line_break_threshold) { - *line_break_point = idx; - return true; - } - } - return false; -} - -void CefRenderWidgetHostViewOSR::DestroyNSTextInputOSR() { - CefTextInputClientOSRMac* client = GetInputClientFromContext( - text_input_context_osr_mac_); - if (client) { - [client release]; - client = NULL; - } - - [text_input_context_osr_mac_ release]; - text_input_context_osr_mac_ = NULL; -} - ui::Compositor* CefRenderWidgetHostViewOSR::GetCompositor() const { return browser_compositor_->GetCompositor(); } diff --git a/libcef/browser/osr/text_input_client_osr_mac.h b/libcef/browser/osr/text_input_client_osr_mac.h index 48ca9e77e..e69de29bb 100644 --- a/libcef/browser/osr/text_input_client_osr_mac.h +++ b/libcef/browser/osr/text_input_client_osr_mac.h @@ -1,79 +0,0 @@ -// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights -// reserved. Use of this source code is governed by a BSD-style license that -// can be found in the LICENSE file. - -#ifndef CEF_LIBCEF_BROWSER_OSR_TEXT_INPUT_CLIENT_OSR_MAC_H_ -#define CEF_LIBCEF_BROWSER_OSR_TEXT_INPUT_CLIENT_OSR_MAC_H_ -#pragma once - -#import -#include - -#include "libcef/browser/osr/render_widget_host_view_osr.h" - -#include "base/mac/scoped_nsobject.h" -#include "base/strings/string16.h" -#include "content/browser/renderer_host/render_widget_host_impl.h" -#include "content/common/edit_command.h" -#include "third_party/WebKit/public/web/WebCompositionUnderline.h" - -// Implementation for the NSTextInputClient protocol used for enabling IME on -// mac when window rendering is disabled. - -@interface CefTextInputClientOSRMac : NSObject { - @private - // Represents the input-method attributes supported by this object. - base::scoped_nsobject validAttributesForMarkedText_; - - // Indicates if we are currently handling a key down event. - BOOL handlingKeyDown_; - - // Indicates if there is any marked text. - BOOL hasMarkedText_; - - // Indicates whether there was any marked text prior to handling - // the current key event. - BOOL oldHasMarkedText_; - - // Indicates if unmarkText is called or not when handling a keyboard - // event. - BOOL unmarkTextCalled_; - - // The selected range, cached from a message sent by the renderer. - NSRange selectedRange_; - - // Text to be inserted which was generated by handling a key down event. - base::string16 textToBeInserted_; - - // Marked text which was generated by handling a key down event. - base::string16 markedText_; - - // Underline information of the |markedText_|. - std::vector underlines_; - - // Replacement range information received from |setMarkedText:|. - gfx::Range setMarkedTextReplacementRange_; - - // Indicates if doCommandBySelector method receives any edit command when - // handling a key down event. - BOOL hasEditCommands_; - - // Contains edit commands received by the -doCommandBySelector: method when - // handling a key down event, not including inserting commands, eg. insertTab, - // etc. - content::EditCommands editCommands_; - - CefRenderWidgetHostViewOSR* renderWidgetHostView_; -} - -@property(nonatomic, readonly) NSRange selectedRange; -@property(nonatomic) BOOL handlingKeyDown; - -- (id)initWithRenderWidgetHostViewOSR:(CefRenderWidgetHostViewOSR*) rwhv; -- (void)HandleKeyEventBeforeTextInputClient:(NSEvent*)keyEvent; -- (void)HandleKeyEventAfterTextInputClient:(NSEvent*)keyEvent; -- (void)cancelComposition; - -@end - -#endif // CEF_LIBCEF_BROWSER_OSR_TEXT_INPUT_CLIENT_OSR_MAC_H_ diff --git a/libcef/browser/osr/text_input_client_osr_mac.mm b/libcef/browser/osr/text_input_client_osr_mac.mm index d7a6a34e2..e69de29bb 100644 --- a/libcef/browser/osr/text_input_client_osr_mac.mm +++ b/libcef/browser/osr/text_input_client_osr_mac.mm @@ -1,402 +0,0 @@ -// Copyright (c) 2013 The Chromium Embedded Framework 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 "libcef/browser/osr/text_input_client_osr_mac.h" - -#include "libcef/browser/browser_host_impl.h" - -#include "base/numerics/safe_conversions.h" -#include "base/strings/sys_string_conversions.h" -#import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h" -#import "content/browser/renderer_host/text_input_client_mac.h" -#include "content/common/input_messages.h" - -namespace { - -// TODO(suzhe): Upstream this function. -blink::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(lroundf(255.0f * a)), 255)) << 24 | - std::max(0, std::min(static_cast(lroundf(255.0f * r)), 255)) << 16 | - std::max(0, std::min(static_cast(lroundf(255.0f * g)), 255)) << 8 | - std::max(0, std::min(static_cast(lroundf(255.0f * b)), 255)); -} - -// Extract underline information from an attributed string. Mostly copied from -// third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm -void ExtractUnderlines(NSAttributedString* string, - std::vector* 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)]; - NSNumber *style = [attrs objectForKey: NSUnderlineStyleAttributeName]; - if (style) { - blink::WebColor color = SK_ColorBLACK; - if (NSColor *colorAttr = - [attrs objectForKey:NSUnderlineColorAttributeName]) { - color = WebColorFromNSColor( - [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]); - } - underlines->push_back(blink::WebCompositionUnderline( - range.location, NSMaxRange(range), color, [style intValue] > 1, 0)); - } - i = range.location + range.length; - } -} - -} // namespace - -extern "C" { - extern NSString* NSTextInputReplacementRangeAttributeName; -} - -@implementation CefTextInputClientOSRMac - -@synthesize selectedRange = selectedRange_; -@synthesize handlingKeyDown = handlingKeyDown_; - -- (id)initWithRenderWidgetHostViewOSR:(CefRenderWidgetHostViewOSR*)rwhv { - self = [super init]; - renderWidgetHostView_ = rwhv; - - return self; -} - -- (NSArray*)validAttributesForMarkedText { - if (!validAttributesForMarkedText_) { - validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects: - NSUnderlineStyleAttributeName, - NSUnderlineColorAttributeName, - NSMarkedClauseSegmentAttributeName, - NSTextInputReplacementRangeAttributeName, - nil]); - } - return validAttributesForMarkedText_.get(); -} - -- (NSRange)markedRange { - return hasMarkedText_ ? - renderWidgetHostView_->composition_range().ToNSRange() : - NSMakeRange(NSNotFound, 0); -} - -- (BOOL)hasMarkedText { - return hasMarkedText_; -} - -- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { - BOOL isAttributedString = [aString isKindOfClass:[NSAttributedString class]]; - NSString* im_text = isAttributedString ? [aString string] : aString; - if (handlingKeyDown_) { - textToBeInserted_.append(base::SysNSStringToUTF16(im_text)); - } else { - gfx::Range replacement_range(replacementRange); - - renderWidgetHostView_->render_widget_host()->ImeCommitText( - base::SysNSStringToUTF16(im_text), replacement_range, 0); - } - - // Inserting text will delete all marked text automatically. - hasMarkedText_ = NO; -} - -- (void)doCommandBySelector:(SEL)aSelector { - // An input method calls this function to dispatch an editing command to be - // handled by this view. - if (aSelector == @selector(noop:)) - return; - std::string command([content::RenderWidgetHostViewMacEditCommandHelper:: - CommandNameForSelector(aSelector) UTF8String]); - - // If this method is called when handling a key down event, then we need to - // handle the command in the key event handler. Otherwise we can just handle - // it here. - if (handlingKeyDown_) { - hasEditCommands_ = YES; - // We ignore commands that insert characters, because this was causing - // strange behavior (e.g. tab always inserted a tab rather than moving to - // the next field on the page). - if (!base::StartsWith(command, "insert", base::CompareCase::SENSITIVE)) - editCommands_.push_back(content::EditCommand(command, "")); - } else { - renderWidgetHostView_->render_widget_host()->Send( - new InputMsg_ExecuteEditCommand( - renderWidgetHostView_->render_widget_host()->GetRoutingID(), - command, "")); - } -} - -- (void)setMarkedText:(id)aString 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. - - BOOL isAttributedString = [aString isKindOfClass:[NSAttributedString class]]; - NSString* im_text = isAttributedString ? [aString string] : aString; - int length = [im_text length]; - - // |markedRange_| will get set on a callback from ImeSetComposition(). - selectedRange_ = newSelRange; - markedText_ = base::SysNSStringToUTF16(im_text); - hasMarkedText_ = (length > 0); - underlines_.clear(); - - if (isAttributedString) { - ExtractUnderlines(aString, &underlines_); - } else { - // Use a thin black underline by default. - underlines_.push_back(blink::WebCompositionUnderline(0, length, - SK_ColorBLACK, false, 0)); - } - - // 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 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_) { - setMarkedTextReplacementRange_ = gfx::Range(replacementRange); - } else if (!handlingKeyDown_) { - renderWidgetHostView_->render_widget_host()->ImeSetComposition( - markedText_, underlines_, gfx::Range(replacementRange), - newSelRange.location, NSMaxRange(newSelRange)); - } -} - -- (void)unmarkText { - // Delete the composition node of the renderer and finish an ongoing - // composition. - // It seems an input method calls the setMarkedText method and set an empty - // 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 FinishComposingText() will be - // called in keyEvent: method. - if (!handlingKeyDown_) { - renderWidgetHostView_->render_widget_host()->ImeFinishComposingText(false); - } else { - unmarkTextCalled_ = YES; - } -} - -- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range - actualRange:(NSRangePointer)actualRange { - if (actualRange) - *actualRange = range; - - const gfx::Range requested_range(range); - if (requested_range.is_reversed()) - return nil; - - gfx::Range expected_range; - const base::string16* expected_text; - - if (!renderWidgetHostView_->composition_range().is_empty()) { - expected_text = &markedText_; - expected_range = renderWidgetHostView_->composition_range(); - } else { - expected_text = &renderWidgetHostView_->selection_text(); - size_t offset = renderWidgetHostView_->selection_text_offset(); - expected_range = gfx::Range(offset, offset + expected_text->size()); - } - - if (!expected_range.Contains(requested_range)) - return nil; - - // Gets the raw bytes to avoid unnecessary string copies for generating - // NSString. - const base::char16* bytes = - &(*expected_text)[requested_range.start() - expected_range.start()]; - // Avoid integer overflow. - base::CheckedNumeric requested_len = requested_range.length(); - requested_len *= sizeof(base::char16); - NSUInteger bytes_len = base::strict_cast( - requested_len.ValueOrDefault(0)); - base::scoped_nsobject ns_string( - [[NSString alloc] initWithBytes:bytes - length:bytes_len - encoding:NSUTF16LittleEndianStringEncoding]); - return [[[NSAttributedString alloc] initWithString:ns_string] autorelease]; -} - -- (NSRect)firstViewRectForCharacterRange:(NSRange)theRange - actualRange:(NSRangePointer)actualRange { - NSRect rect; - gfx::Rect gfxRect; - gfx::Range range(theRange); - gfx::Range actual_range; - if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange(range, - &gfxRect, &actual_range)) { - rect = content::TextInputClientMac::GetInstance()-> - GetFirstRectForRange(renderWidgetHostView_->GetRenderWidgetHost(), - range.ToNSRange()); - - if (actualRange) - *actualRange = range.ToNSRange(); - } else { - rect = NSRectFromCGRect(gfxRect.ToCGRect()); - } - - return rect; -} - -- (NSRect) screenRectFromViewRect:(NSRect)rect { - NSRect screenRect; - - int screenX, screenY; - renderWidgetHostView_->browser_impl()->GetClient()->GetRenderHandler()-> - GetScreenPoint(renderWidgetHostView_->browser_impl()->GetBrowser(), - rect.origin.x, rect.origin.y, screenX, screenY); - screenRect.origin = NSMakePoint(screenX, screenY); - screenRect.size = rect.size; - - return screenRect; -} - -- (NSRect)firstRectForCharacterRange:(NSRange)theRange - actualRange:(NSRangePointer)actualRange { - NSRect rect = [self firstViewRectForCharacterRange:theRange - actualRange:actualRange]; - - // Convert into screen coordinates for return. - rect = [self screenRectFromViewRect:rect]; - - if (rect.origin.y >= rect.size.height) - rect.origin.y -= rect.size.height; - else - rect.origin.y = 0; - - return rect; -} - -- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint { - // |thePoint| is in screen coordinates, but needs to be converted to WebKit - // coordinates (upper left origin). Scroll offsets will be taken care of in - // the renderer. - - CefRect view_rect; - renderWidgetHostView_->browser_impl()->GetClient()->GetRenderHandler()-> - GetViewRect(renderWidgetHostView_->browser_impl()->GetBrowser(), - view_rect); - - thePoint.x -= view_rect.x; - thePoint.y -= view_rect.y; - thePoint.y = view_rect.height - thePoint.y; - - NSUInteger index = content::TextInputClientMac::GetInstance()-> - GetCharacterIndexAtPoint(renderWidgetHostView_->GetRenderWidgetHost(), - gfx::Point(thePoint.x, thePoint.y)); - return index; -} - -- (void)HandleKeyEventBeforeTextInputClient:(NSEvent*)keyEvent { - DCHECK([keyEvent type] == NSKeyDown); - // Don't call this method recursively. - DCHECK(!handlingKeyDown_); - - oldHasMarkedText_ = hasMarkedText_; - handlingKeyDown_ = YES; - - // These variables might be set when handling the keyboard event. - // Clear them here so that we can know whether they have changed afterwards. - textToBeInserted_.clear(); - markedText_.clear(); - underlines_.clear(); - setMarkedTextReplacementRange_ = gfx::Range::InvalidRange(); - unmarkTextCalled_ = NO; - hasEditCommands_ = NO; - editCommands_.clear(); -} - -- (void)HandleKeyEventAfterTextInputClient:(NSEvent*)keyEvent { - handlingKeyDown_ = NO; - - // 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 FinishComposingText(). - // 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 - // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to - // prevent the browser from handling it again. - // 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. - - if (!hasMarkedText_ && !oldHasMarkedText_ && - textToBeInserted_.length() <= 1) { - content::NativeWebKeyboardEvent event(keyEvent); - if (textToBeInserted_.length() == 1) { - event.type = blink::WebInputEvent::Type::Char; - event.text[0] = textToBeInserted_[0]; - event.text[1] = 0; - } - renderWidgetHostView_->SendKeyEvent(event); - } - - BOOL textInserted = NO; - if (textToBeInserted_.length() > - ((hasMarkedText_ || oldHasMarkedText_) ? 0u : 1u)) { - renderWidgetHostView_->render_widget_host()->ImeCommitText( - textToBeInserted_, gfx::Range::InvalidRange(), 0); - textToBeInserted_ = YES; - } - - // Updates or cancels the composition. If some text has been inserted, then - // we don't need to cancel the composition explicitly. - 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. - renderWidgetHostView_->render_widget_host()->ImeSetComposition( - markedText_, underlines_, setMarkedTextReplacementRange_, - selectedRange_.location, NSMaxRange(selectedRange_)); - } else if (oldHasMarkedText_ && !hasMarkedText_ && !textInserted) { - if (unmarkTextCalled_) { - renderWidgetHostView_->render_widget_host()->ImeFinishComposingText( - false); - } else { - renderWidgetHostView_->render_widget_host()->ImeCancelComposition(); - } - } - - setMarkedTextReplacementRange_ = gfx::Range::InvalidRange(); -} - -- (void)cancelComposition { - if (!hasMarkedText_) - return; - - // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:] - // doesn't call any NSTextInput functions, such as setMarkedText or - // insertText. So, we need to send an IPC message to a renderer so it can - // delete the composition node. - // TODO(erikchen): NSInputManager is deprecated since OSX 10.6. Switch to - // NSTextInputContext. http://www.crbug.com/479010. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - NSInputManager *currentInputManager = [NSInputManager currentInputManager]; - [currentInputManager markedTextAbandoned:self]; -#pragma clang diagnostic pop - - hasMarkedText_ = NO; - // Should not call [self unmarkText] here, because it'll send unnecessary - // cancel composition IPC message to the renderer. -} - -@end diff --git a/libcef_dll/cpptoc/browser_host_cpptoc.cc b/libcef_dll/cpptoc/browser_host_cpptoc.cc index f858660df..bc8c6cef8 100644 --- a/libcef_dll/cpptoc/browser_host_cpptoc.cc +++ b/libcef_dll/cpptoc/browser_host_cpptoc.cc @@ -781,46 +781,96 @@ void CEF_CALLBACK browser_host_set_windowless_frame_rate( frame_rate); } -cef_text_input_context_t CEF_CALLBACK browser_host_get_nstext_input_context( +void CEF_CALLBACK browser_host_ime_set_composition( + struct _cef_browser_host_t* self, const cef_string_t* text, + size_t underlinesCount, cef_composition_underline_t const* underlines, + const cef_range_t* replacement_range, + const cef_range_t* selection_range) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: replacement_range; type: simple_byref_const + DCHECK(replacement_range); + if (!replacement_range) + return; + // Verify param: selection_range; type: simple_byref_const + DCHECK(selection_range); + if (!selection_range) + return; + // Unverified params: text, underlines + + // Translate param: underlines; type: simple_vec_byref_const + std::vector underlinesList; + if (underlinesCount > 0) { + for (size_t i = 0; i < underlinesCount; ++i) { + CefCompositionUnderline underlinesVal = underlines[i]; + underlinesList.push_back(underlinesVal); + } + } + // Translate param: replacement_range; type: simple_byref_const + CefRange replacement_rangeVal = replacement_range?*replacement_range:CefRange( + ); + // Translate param: selection_range; type: simple_byref_const + CefRange selection_rangeVal = selection_range?*selection_range:CefRange(); + + // Execute + CefBrowserHostCppToC::Get(self)->ImeSetComposition( + CefString(text), + underlinesList, + replacement_rangeVal, + selection_rangeVal); +} + +void CEF_CALLBACK browser_host_ime_commit_text(struct _cef_browser_host_t* self, + const cef_string_t* text, const cef_range_t* replacement_range, + int relative_cursor_pos) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: replacement_range; type: simple_byref_const + DCHECK(replacement_range); + if (!replacement_range) + return; + // Unverified params: text + + // Translate param: replacement_range; type: simple_byref_const + CefRange replacement_rangeVal = replacement_range?*replacement_range:CefRange( + ); + + // Execute + CefBrowserHostCppToC::Get(self)->ImeCommitText( + CefString(text), + replacement_rangeVal, + relative_cursor_pos); +} + +void CEF_CALLBACK browser_host_ime_finish_composing_text( + struct _cef_browser_host_t* self, int keep_selection) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + + // Execute + CefBrowserHostCppToC::Get(self)->ImeFinishComposingText( + keep_selection?true:false); +} + +void CEF_CALLBACK browser_host_ime_cancel_composition( struct _cef_browser_host_t* self) { // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING - DCHECK(self); - if (!self) - return NULL; - - // Execute - cef_text_input_context_t _retval = CefBrowserHostCppToC::Get( - self)->GetNSTextInputContext(); - - // Return type: simple - return _retval; -} - -void CEF_CALLBACK browser_host_handle_key_event_before_text_input_client( - struct _cef_browser_host_t* self, cef_event_handle_t keyEvent) { - // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING - DCHECK(self); if (!self) return; // Execute - CefBrowserHostCppToC::Get(self)->HandleKeyEventBeforeTextInputClient( - keyEvent); -} - -void CEF_CALLBACK browser_host_handle_key_event_after_text_input_client( - struct _cef_browser_host_t* self, cef_event_handle_t keyEvent) { - // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING - - DCHECK(self); - if (!self) - return; - - // Execute - CefBrowserHostCppToC::Get(self)->HandleKeyEventAfterTextInputClient( - keyEvent); + CefBrowserHostCppToC::Get(self)->ImeCancelComposition(); } void CEF_CALLBACK browser_host_drag_target_drag_enter( @@ -1008,11 +1058,11 @@ CefBrowserHostCppToC::CefBrowserHostCppToC() { browser_host_get_windowless_frame_rate; GetStruct()->set_windowless_frame_rate = browser_host_set_windowless_frame_rate; - GetStruct()->get_nstext_input_context = browser_host_get_nstext_input_context; - GetStruct()->handle_key_event_before_text_input_client = - browser_host_handle_key_event_before_text_input_client; - GetStruct()->handle_key_event_after_text_input_client = - browser_host_handle_key_event_after_text_input_client; + GetStruct()->ime_set_composition = browser_host_ime_set_composition; + GetStruct()->ime_commit_text = browser_host_ime_commit_text; + GetStruct()->ime_finish_composing_text = + browser_host_ime_finish_composing_text; + GetStruct()->ime_cancel_composition = browser_host_ime_cancel_composition; GetStruct()->drag_target_drag_enter = browser_host_drag_target_drag_enter; GetStruct()->drag_target_drag_over = browser_host_drag_target_drag_over; GetStruct()->drag_target_drag_leave = browser_host_drag_target_drag_leave; diff --git a/libcef_dll/cpptoc/render_handler_cpptoc.cc b/libcef_dll/cpptoc/render_handler_cpptoc.cc index b76fda786..0d6620567 100644 --- a/libcef_dll/cpptoc/render_handler_cpptoc.cc +++ b/libcef_dll/cpptoc/render_handler_cpptoc.cc @@ -350,6 +350,46 @@ void CEF_CALLBACK render_handler_on_scroll_offset_changed( y); } +void CEF_CALLBACK render_handler_on_ime_composition_range_changed( + struct _cef_render_handler_t* self, cef_browser_t* browser, + const cef_range_t* selected_range, size_t character_boundsCount, + cef_rect_t const* character_bounds) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return; + // Verify param: selected_range; type: simple_byref_const + DCHECK(selected_range); + if (!selected_range) + return; + // Verify param: character_bounds; type: simple_vec_byref_const + DCHECK(character_boundsCount == 0 || character_bounds); + if (character_boundsCount > 0 && !character_bounds) + return; + + // Translate param: selected_range; type: simple_byref_const + CefRange selected_rangeVal = selected_range?*selected_range:CefRange(); + // Translate param: character_bounds; type: simple_vec_byref_const + std::vector character_boundsList; + if (character_boundsCount > 0) { + for (size_t i = 0; i < character_boundsCount; ++i) { + CefRect character_boundsVal = character_bounds[i]; + character_boundsList.push_back(character_boundsVal); + } + } + + // Execute + CefRenderHandlerCppToC::Get(self)->OnImeCompositionRangeChanged( + CefBrowserCToCpp::Wrap(browser), + selected_rangeVal, + character_boundsList); +} + } // namespace @@ -368,6 +408,8 @@ CefRenderHandlerCppToC::CefRenderHandlerCppToC() { GetStruct()->update_drag_cursor = render_handler_update_drag_cursor; GetStruct()->on_scroll_offset_changed = render_handler_on_scroll_offset_changed; + GetStruct()->on_ime_composition_range_changed = + render_handler_on_ime_composition_range_changed; } template<> CefRefPtr CefCppToC& underlines, + const CefRange& replacement_range, const CefRange& selection_range) { cef_browser_host_t* _struct = GetStruct(); - if (CEF_MEMBER_MISSING(_struct, get_nstext_input_context)) - return NULL; + if (CEF_MEMBER_MISSING(_struct, ime_set_composition)) + return; // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING - // Execute - cef_text_input_context_t _retval = _struct->get_nstext_input_context(_struct); + // Unverified params: text, underlines - // Return type: simple - return _retval; + // Translate param: underlines; type: simple_vec_byref_const + const size_t underlinesCount = underlines.size(); + cef_composition_underline_t* underlinesList = NULL; + if (underlinesCount > 0) { + underlinesList = new cef_composition_underline_t[underlinesCount]; + DCHECK(underlinesList); + if (underlinesList) { + for (size_t i = 0; i < underlinesCount; ++i) { + underlinesList[i] = underlines[i]; + } + } + } + + // Execute + _struct->ime_set_composition(_struct, + text.GetStruct(), + underlinesCount, + underlinesList, + &replacement_range, + &selection_range); + + // Restore param:underlines; type: simple_vec_byref_const + if (underlinesList) + delete [] underlinesList; } -void CefBrowserHostCToCpp::HandleKeyEventBeforeTextInputClient( - CefEventHandle keyEvent) { +void CefBrowserHostCToCpp::ImeCommitText(const CefString& text, + const CefRange& replacement_range, int relative_cursor_pos) { cef_browser_host_t* _struct = GetStruct(); - if (CEF_MEMBER_MISSING(_struct, handle_key_event_before_text_input_client)) + if (CEF_MEMBER_MISSING(_struct, ime_commit_text)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Unverified params: text + + // Execute + _struct->ime_commit_text(_struct, + text.GetStruct(), + &replacement_range, + relative_cursor_pos); +} + +void CefBrowserHostCToCpp::ImeFinishComposingText(bool keep_selection) { + cef_browser_host_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, ime_finish_composing_text)) return; // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING // Execute - _struct->handle_key_event_before_text_input_client(_struct, - keyEvent); + _struct->ime_finish_composing_text(_struct, + keep_selection); } -void CefBrowserHostCToCpp::HandleKeyEventAfterTextInputClient( - CefEventHandle keyEvent) { +void CefBrowserHostCToCpp::ImeCancelComposition() { cef_browser_host_t* _struct = GetStruct(); - if (CEF_MEMBER_MISSING(_struct, handle_key_event_after_text_input_client)) + if (CEF_MEMBER_MISSING(_struct, ime_cancel_composition)) return; // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING // Execute - _struct->handle_key_event_after_text_input_client(_struct, - keyEvent); + _struct->ime_cancel_composition(_struct); } void CefBrowserHostCToCpp::DragTargetDragEnter(CefRefPtr drag_data, diff --git a/libcef_dll/ctocpp/browser_host_ctocpp.h b/libcef_dll/ctocpp/browser_host_ctocpp.h index 3b11648fc..cdec2c794 100644 --- a/libcef_dll/ctocpp/browser_host_ctocpp.h +++ b/libcef_dll/ctocpp/browser_host_ctocpp.h @@ -86,9 +86,14 @@ class CefBrowserHostCToCpp void NotifyMoveOrResizeStarted() OVERRIDE; int GetWindowlessFrameRate() OVERRIDE; void SetWindowlessFrameRate(int frame_rate) OVERRIDE; - CefTextInputContext GetNSTextInputContext() OVERRIDE; - void HandleKeyEventBeforeTextInputClient(CefEventHandle keyEvent) OVERRIDE; - void HandleKeyEventAfterTextInputClient(CefEventHandle keyEvent) OVERRIDE; + void ImeSetComposition(const CefString& text, + const std::vector& underlines, + const CefRange& replacement_range, + const CefRange& selection_range) OVERRIDE; + void ImeCommitText(const CefString& text, const CefRange& replacement_range, + int relative_cursor_pos) OVERRIDE; + void ImeFinishComposingText(bool keep_selection) OVERRIDE; + void ImeCancelComposition() OVERRIDE; void DragTargetDragEnter(CefRefPtr drag_data, const CefMouseEvent& event, DragOperationsMask allowed_ops) OVERRIDE; void DragTargetDragOver(const CefMouseEvent& event, diff --git a/libcef_dll/ctocpp/render_handler_ctocpp.cc b/libcef_dll/ctocpp/render_handler_ctocpp.cc index 8738c0df8..1a7ad9b24 100644 --- a/libcef_dll/ctocpp/render_handler_ctocpp.cc +++ b/libcef_dll/ctocpp/render_handler_ctocpp.cc @@ -283,6 +283,45 @@ void CefRenderHandlerCToCpp::OnScrollOffsetChanged( y); } +void CefRenderHandlerCToCpp::OnImeCompositionRangeChanged( + CefRefPtr browser, const CefRange& selected_range, + const RectList& character_bounds) { + cef_render_handler_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_ime_composition_range_changed)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return; + + // Translate param: character_bounds; type: simple_vec_byref_const + const size_t character_boundsCount = character_bounds.size(); + cef_rect_t* character_boundsList = NULL; + if (character_boundsCount > 0) { + character_boundsList = new cef_rect_t[character_boundsCount]; + DCHECK(character_boundsList); + if (character_boundsList) { + for (size_t i = 0; i < character_boundsCount; ++i) { + character_boundsList[i] = character_bounds[i]; + } + } + } + + // Execute + _struct->on_ime_composition_range_changed(_struct, + CefBrowserCppToC::Wrap(browser), + &selected_range, + character_boundsCount, + character_boundsList); + + // Restore param:character_bounds; type: simple_vec_byref_const + if (character_boundsList) + delete [] character_boundsList; +} + // CONSTRUCTOR - Do not edit by hand. diff --git a/libcef_dll/ctocpp/render_handler_ctocpp.h b/libcef_dll/ctocpp/render_handler_ctocpp.h index 3276b4a3b..31b9d3789 100644 --- a/libcef_dll/ctocpp/render_handler_ctocpp.h +++ b/libcef_dll/ctocpp/render_handler_ctocpp.h @@ -51,6 +51,9 @@ class CefRenderHandlerCToCpp DragOperation operation) override; void OnScrollOffsetChanged(CefRefPtr browser, double x, double y) override; + void OnImeCompositionRangeChanged(CefRefPtr browser, + const CefRange& selected_range, + const RectList& character_bounds) override; }; #endif // BUILDING_CEF_SHARED diff --git a/tests/cefclient/browser/browser_window_osr_gtk.cc b/tests/cefclient/browser/browser_window_osr_gtk.cc index b2d43d6ff..40f5f3259 100644 --- a/tests/cefclient/browser/browser_window_osr_gtk.cc +++ b/tests/cefclient/browser/browser_window_osr_gtk.cc @@ -1204,6 +1204,13 @@ void BrowserWindowOsrGtk::UpdateDragCursor( CEF_REQUIRE_UI_THREAD(); } +void BrowserWindowOsrGtk::OnImeCompositionRangeChanged( + CefRefPtr browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& character_bounds) { + CEF_REQUIRE_UI_THREAD(); +} + void BrowserWindowOsrGtk::Create(ClientWindowHandle parent_handle) { REQUIRE_MAIN_THREAD(); DCHECK(!glarea_); diff --git a/tests/cefclient/browser/browser_window_osr_gtk.h b/tests/cefclient/browser/browser_window_osr_gtk.h index c6a2d491e..62e3773cc 100644 --- a/tests/cefclient/browser/browser_window_osr_gtk.h +++ b/tests/cefclient/browser/browser_window_osr_gtk.h @@ -76,6 +76,10 @@ class BrowserWindowOsrGtk : public BrowserWindow, int x, int y) OVERRIDE; void UpdateDragCursor(CefRefPtr browser, CefRenderHandler::DragOperation operation) OVERRIDE; + void OnImeCompositionRangeChanged( + CefRefPtr browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& character_bounds) OVERRIDE; private: // Create the GTK GlArea. diff --git a/tests/cefclient/browser/browser_window_osr_mac.h b/tests/cefclient/browser/browser_window_osr_mac.h index 52f4e514c..708141366 100644 --- a/tests/cefclient/browser/browser_window_osr_mac.h +++ b/tests/cefclient/browser/browser_window_osr_mac.h @@ -9,6 +9,7 @@ #include "cefclient/browser/browser_window.h" #include "cefclient/browser/client_handler_osr.h" #include "cefclient/browser/osr_renderer.h" +#include "cefclient/browser/text_input_client_osr_mac.h" namespace client { @@ -77,6 +78,10 @@ class BrowserWindowOsrMac : public BrowserWindow, int x, int y) OVERRIDE; void UpdateDragCursor(CefRefPtr browser, CefRenderHandler::DragOperation operation) OVERRIDE; + void OnImeCompositionRangeChanged( + CefRefPtr browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& character_bounds) OVERRIDE; private: // Create the NSView. diff --git a/tests/cefclient/browser/browser_window_osr_mac.mm b/tests/cefclient/browser/browser_window_osr_mac.mm index 271f2afa7..694d9a07e 100644 --- a/tests/cefclient/browser/browser_window_osr_mac.mm +++ b/tests/cefclient/browser/browser_window_osr_mac.mm @@ -14,6 +14,18 @@ #include "cefclient/browser/bytes_write_handler.h" #include "cefclient/browser/geometry_util.h" #include "cefclient/browser/main_message_loop.h" +#include "cefclient/browser/text_input_client_osr_mac.h" + +namespace { + +CefTextInputClientOSRMac* GetInputClientFromContext( + const NSTextInputContext* context) { + if (!context) + return NULL; + return reinterpret_cast([context client]); +} + +} // namespace @interface BrowserOpenGLView : NSOpenGLView { @@ -29,13 +41,16 @@ float device_scale_factor_; - // Drag and drop + // Drag and drop. CefRefPtr current_drag_data_; NSDragOperation current_drag_op_; NSDragOperation current_allowed_ops_; NSPasteboard* pasteboard_; CFStringRef fileUTI_; + // For intreacting with IME. + NSTextInputContext* text_input_context_osr_mac_; + // Event monitor for scroll wheel end event. id endWheelMonitor_; } @@ -81,7 +96,8 @@ - (NSPoint)convertPointToBackingInternal:(NSPoint)aPoint; - (NSRect)convertRectFromBackingInternal:(NSRect)aRect; - (NSRect)convertRectToBackingInternal:(NSRect)aRect; - +- (void)ChangeCompositionRange:(CefRange)range + character_bounds:(const CefRenderHandler::RectList&) character_bounds; @end @@ -315,18 +331,25 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) { - (void)keyDown:(NSEvent*)event { CefRefPtr browser = [self getBrowser]; - if (!browser.get()) + if (!browser.get() || !text_input_context_osr_mac_) return; if ([event type] != NSFlagsChanged) { - browser->GetHost()->HandleKeyEventBeforeTextInputClient(event); + CefTextInputClientOSRMac* client = GetInputClientFromContext( + text_input_context_osr_mac_); - // The return value of this method seems to always be set to YES, - // thus we ignore it and ask the host view whether IME is active - // or not. - [[self inputContext] handleEvent:event]; + if (client) { + [client HandleKeyEventBeforeTextInputClient:event]; - browser->GetHost()->HandleKeyEventAfterTextInputClient(event); + // The return value of this method seems to always be set to YES, thus we + // ignore it and ask the host view whether IME is active or not. + [text_input_context_osr_mac_ handleEvent:event]; + + CefKeyEvent keyEvent; + [self getKeyEvent:keyEvent forEvent:event]; + + [client HandleKeyEventAfterTextInputClient:keyEvent]; + } } } @@ -502,10 +525,15 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) { } - (NSTextInputContext*)inputContext { - CefRefPtr browser = [self getBrowser]; - if (browser.get()) - return browser->GetHost()->GetNSTextInputContext(); - return NULL; + if (!text_input_context_osr_mac_) { + CefTextInputClientOSRMac* text_input_client = + [[CefTextInputClientOSRMac alloc] initWithBrowser:[self getBrowser]]; + + text_input_context_osr_mac_ = [[NSTextInputContext alloc] initWithClient: + text_input_client]; + } + + return text_input_context_osr_mac_; } - (void)getMouseEvent:(CefMouseEvent&)mouseEvent forEvent:(NSEvent*)event { @@ -1098,6 +1126,13 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) { return [self convertRectToBacking:aRect]; } +- (void)ChangeCompositionRange:(CefRange)range + character_bounds:(const CefRenderHandler::RectList&) bounds { + CefTextInputClientOSRMac* client = + GetInputClientFromContext(text_input_context_osr_mac_); + if (client) + [client ChangeCompositionRange: range character_bounds:bounds]; +} @end @@ -1437,6 +1472,18 @@ void BrowserWindowOsrMac::UpdateDragCursor( [GLView(nsview_) setCurrentDragOp:operation]; } +void BrowserWindowOsrMac::OnImeCompositionRangeChanged( + CefRefPtr browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& bounds) { + CEF_REQUIRE_UI_THREAD(); + + if (nsview_) { + [GLView(nsview_) ChangeCompositionRange:selection_range + character_bounds:bounds]; + } +} + void BrowserWindowOsrMac::Create(ClientWindowHandle parent_handle, const CefRect& rect) { REQUIRE_MAIN_THREAD(); diff --git a/tests/cefclient/browser/client_handler_osr.cc b/tests/cefclient/browser/client_handler_osr.cc index 5bbc0522b..b78965802 100644 --- a/tests/cefclient/browser/client_handler_osr.cc +++ b/tests/cefclient/browser/client_handler_osr.cc @@ -135,4 +135,15 @@ void ClientHandlerOsr::UpdateDragCursor(CefRefPtr browser, osr_delegate_->UpdateDragCursor(browser, operation); } +void ClientHandlerOsr::OnImeCompositionRangeChanged( + CefRefPtr browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& character_bounds) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return; + osr_delegate_->OnImeCompositionRangeChanged(browser, selection_range, + character_bounds); +} + } // namespace client diff --git a/tests/cefclient/browser/client_handler_osr.h b/tests/cefclient/browser/client_handler_osr.h index 1e32c1b96..0f73a32b2 100644 --- a/tests/cefclient/browser/client_handler_osr.h +++ b/tests/cefclient/browser/client_handler_osr.h @@ -56,6 +56,10 @@ class ClientHandlerOsr : public ClientHandler, virtual void UpdateDragCursor( CefRefPtr browser, CefRenderHandler::DragOperation operation) = 0; + virtual void OnImeCompositionRangeChanged( + CefRefPtr browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& character_bounds) = 0; protected: virtual ~OsrDelegate() {} @@ -109,6 +113,10 @@ class ClientHandlerOsr : public ClientHandler, int x, int y) OVERRIDE; void UpdateDragCursor(CefRefPtr browser, CefRenderHandler::DragOperation operation) OVERRIDE; + void OnImeCompositionRangeChanged( + CefRefPtr browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& character_bounds) OVERRIDE; private: // Only accessed on the UI thread. diff --git a/tests/cefclient/browser/osr_ime_handler_win.cc b/tests/cefclient/browser/osr_ime_handler_win.cc new file mode 100644 index 000000000..8624e30b8 --- /dev/null +++ b/tests/cefclient/browser/osr_ime_handler_win.cc @@ -0,0 +1,390 @@ +// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright +// 2013 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. + +// Implementation based on ui/base/ime/win/imm32_manager.cc from Chromium. + +#include +#include + +#include "include/base/cef_build.h" +#include "cefclient/browser/geometry_util.h" +#include "cefclient/browser/main_message_loop.h" +#include "cefclient/browser/resource.h" +#include "cefclient/browser/util_win.h" +#include "cefclient/browser/osr_ime_handler_win.h" + +#define ColorUNDERLINE 0xFF000000 // Black SkColor value for underline, + // same as Blink. +#define ColorBKCOLOR 0x00000000 // White SkColor value for background, + // same as Blink. + +namespace client { + +namespace { + +// Determines whether or not the given attribute represents a selection +bool IsSelectionAttribute(char attribute) { + return (attribute == ATTR_TARGET_CONVERTED || + attribute == ATTR_TARGET_NOTCONVERTED); +} + +// Helper function for OsrImeHandlerWin::GetCompositionInfo() method, +// to get the target range that's selected by the user in the current +// composition string. +void GetCompositionSelectionRange(HIMC imc, int* target_start, + int* target_end) { + int attribute_size = ::ImmGetCompositionString(imc, GCS_COMPATTR, NULL, 0); + if (attribute_size > 0) { + int start = 0; + int end = 0; + std::vector attribute_data(attribute_size); + + ::ImmGetCompositionString(imc, GCS_COMPATTR, &attribute_data[0], + attribute_size); + for (start = 0; start < attribute_size; ++start) { + if (IsSelectionAttribute(attribute_data[start])) + break; + } + for (end = start; end < attribute_size; ++end) { + if (!IsSelectionAttribute(attribute_data[end])) + break; + } + + *target_start = start; + *target_end = end; + } +} + +// Helper function for OsrImeHandlerWin::GetCompositionInfo() method, to get +// underlines information of the current composition string. +void GetCompositionUnderlines( + HIMC imc, + int target_start, + int target_end, + std::vector &underlines) { + int clause_size = ::ImmGetCompositionString(imc, GCS_COMPCLAUSE, NULL, 0); + int clause_length = clause_size / sizeof(uint32); + if (clause_length) { + std::vector clause_data(clause_length); + + ::ImmGetCompositionString(imc, GCS_COMPCLAUSE, + &clause_data[0], clause_size); + for (int i = 0; i < clause_length - 1; ++i) { + cef_composition_underline_t underline; + underline.range.from = clause_data[i]; + underline.range.to = clause_data[i + 1]; + underline.color = ColorUNDERLINE; + underline.background_color = ColorBKCOLOR; + underline.thick = 0; + + // Use thick underline for the target clause. + if (underline.range.from >= target_start && + underline.range.to <= target_end) { + underline.thick = 1; + } + underlines.push_back(underline); + } + } +} + +} // namespace + +OsrImeHandlerWin::OsrImeHandlerWin(HWND hwnd) + : ime_status_(false), + hwnd_(hwnd), + input_language_id_(LANG_USER_DEFAULT), + is_composing_(false), + cursor_index_(-1), + system_caret_(false) { + ime_rect_ = { -1, -1, 0, 0 }; +} + +OsrImeHandlerWin::~OsrImeHandlerWin() { + DestroyImeWindow(); +} + +void OsrImeHandlerWin::SetInputLanguage() { + // Retrieve the current input language from the system's keyboard layout. + // Using GetKeyboardLayoutName instead of GetKeyboardLayout, because + // the language from GetKeyboardLayout is the language under where the + // keyboard layout is installed. And the language from GetKeyboardLayoutName + // indicates the language of the keyboard layout itself. + // See crbug.com/344834. + WCHAR keyboard_layout[KL_NAMELENGTH]; + if (::GetKeyboardLayoutNameW(keyboard_layout)) { + input_language_id_ = + static_cast(_wtoi(&keyboard_layout[KL_NAMELENGTH >> 1])); + } else { + input_language_id_ = 0x0409; // Fallback to en-US. + } +} + +void OsrImeHandlerWin::CreateImeWindow() { + // Chinese/Japanese IMEs somehow ignore function calls to + // ::ImmSetCandidateWindow(), and use the position of the current system + // caret instead -::GetCaretPos(). + // Therefore, we create a temporary system caret for Chinese IMEs and use + // it during this input context. + // Since some third-party Japanese IME also uses ::GetCaretPos() to determine + // their window position, we also create a caret for Japanese IMEs. + if (PRIMARYLANGID(input_language_id_) == LANG_CHINESE || + PRIMARYLANGID(input_language_id_) == LANG_JAPANESE) { + if (!system_caret_) { + if (::CreateCaret(hwnd_, NULL, 1, 1)) + system_caret_ = true; + } + } +} + +void OsrImeHandlerWin::DestroyImeWindow() { + // Destroy the system caret if we have created for this IME input context. + if (system_caret_) { + ::DestroyCaret(); + system_caret_ = false; + } +} + +void OsrImeHandlerWin::MoveImeWindow() { + // Does nothing when the target window has no input focus. + if (GetFocus() != hwnd_) + return; + + CefRect rc = ime_rect_; + int location = cursor_index_; + + // If location is not specified fall back to the composition range start. + if (location == -1) + location = composition_range_.from; + + // Offset location by the composition range start if required. + if (location >= composition_range_.from) + location -= composition_range_.from; + + if (location < static_cast(composition_bounds_.size())) + rc = composition_bounds_[location]; + else + return; + + HIMC imc = ::ImmGetContext(hwnd_); + if (imc) { + const int kCaretMargin = 1; + if (PRIMARYLANGID(input_language_id_) == LANG_CHINESE) { + // Chinese IMEs ignore function calls to ::ImmSetCandidateWindow() + // when a user disables TSF (Text Service Framework) and CUAS (Cicero + // Unaware Application Support). + // On the other hand, when a user enables TSF and CUAS, Chinese IMEs + // ignore the position of the current system caret and use the + // parameters given to ::ImmSetCandidateWindow() with its 'dwStyle' + // parameter CFS_CANDIDATEPOS. + // Therefore, we do not only call ::ImmSetCandidateWindow() but also + // set the positions of the temporary system caret if it exists. + CANDIDATEFORM candidate_position = { + 0, CFS_CANDIDATEPOS, { rc.x, rc.y }, { 0, 0, 0, 0 } + }; + ::ImmSetCandidateWindow(imc, &candidate_position); + } + if (system_caret_) { + switch (PRIMARYLANGID(input_language_id_)) { + case LANG_JAPANESE: + ::SetCaretPos(rc.x, rc.y + rc.height); + break; + default: + ::SetCaretPos(rc.x, rc.y); + break; + } + } + + if (PRIMARYLANGID(input_language_id_) == LANG_KOREAN) { + // Korean IMEs require the lower-left corner of the caret to move their + // candidate windows. + rc.y += kCaretMargin; + } + + // Japanese IMEs and Korean IMEs also use the rectangle given to + // ::ImmSetCandidateWindow() with its 'dwStyle' parameter CFS_EXCLUDE + // Therefore, we also set this parameter here. + CANDIDATEFORM exclude_rectangle = { + 0, CFS_EXCLUDE, { rc.x, rc.y }, + { rc.x, rc.y, rc.x + rc.width, rc.y + rc.height } + }; + ::ImmSetCandidateWindow(imc, &exclude_rectangle); + + ::ImmReleaseContext(hwnd_, imc); + } +} + +void OsrImeHandlerWin::CleanupComposition() { + // Notify the IMM attached to the given window to complete the ongoing + // composition (when given window is de-activated while composing and + // re-activated) and reset the composition status. + if (is_composing_) { + HIMC imc = ::ImmGetContext(hwnd_); + if (imc) { + ::ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); + ::ImmReleaseContext(hwnd_, imc); + } + ResetComposition(); + } +} + +void OsrImeHandlerWin::ResetComposition() { + // Reset the composition status. + is_composing_ = false; + cursor_index_ = -1; +} + + +void OsrImeHandlerWin::GetCompositionInfo( + HIMC imc, + LPARAM lparam, + CefString &composition_text, + std::vector &underlines, + int& composition_start) { + // We only care about GCS_COMPATTR, GCS_COMPCLAUSE and GCS_CURSORPOS, and + // convert them into underlines and selection range respectively. + underlines.clear(); + + int length = static_cast(composition_text.length()); + + // Find out the range selected by the user. + int target_start = length; + int target_end = length; + if (lparam & GCS_COMPATTR) + GetCompositionSelectionRange(imc, &target_start, &target_end); + + // Retrieve the selection range information. If CS_NOMOVECARET is specified + // it means the cursor should not be moved and we therefore 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 in 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) && (lparam & GCS_CURSORPOS)) { + // IMM32 does not support non-zero-width selection in a composition. So + // always use the caret position as selection range. + int cursor = ::ImmGetCompositionString(imc, GCS_CURSORPOS, NULL, 0); + composition_start = cursor; + } else { + composition_start = 0; + } + + // Retrieve the clause segmentations and convert them to underlines. + if (lparam & GCS_COMPCLAUSE) + GetCompositionUnderlines(imc, target_start, target_end, underlines); + + // Set default underlines in case there is no clause information. + if (!underlines.size()) { + CefCompositionUnderline underline; + underline.color = ColorUNDERLINE; + underline.background_color = ColorBKCOLOR; + if (target_start > 0) { + underline.range.from = 0; + underline.range.to = target_start; + underline.thick = 0; + underlines.push_back(underline); + } + if (target_end > target_start) { + underline.range.from = target_start; + underline.range.to = target_end; + underline.thick = 1; + underlines.push_back(underline); + } + if (target_end < length) { + underline.range.from = target_end; + underline.range.to = length; + underline.thick = 0; + underlines.push_back(underline); + } + } +} + +bool OsrImeHandlerWin::GetString(HIMC imc, WPARAM lparam, int type, + CefString& result) { + if (!(lparam & type)) + return false; + LONG string_size = ::ImmGetCompositionString(imc, type, NULL, 0); + if (string_size <= 0) + return false; + + // For trailing NULL - ImmGetCompositionString excludes that. + string_size += sizeof(WCHAR); + + std::vector buffer(string_size); + ::ImmGetCompositionString(imc, type, &buffer[0], string_size); + result.FromWString(&buffer[0]); + return true; +} + +bool OsrImeHandlerWin::GetResult(LPARAM lparam, CefString& result) { + bool ret = false; + HIMC imc = ::ImmGetContext(hwnd_); + if (imc) { + ret = GetString(imc, lparam, GCS_RESULTSTR, result); + ::ImmReleaseContext(hwnd_, imc); + } + return ret; +} + +bool OsrImeHandlerWin::GetComposition( + LPARAM lparam, + CefString &composition_text, + std::vector &underlines, + int& composition_start) { + bool ret = false; + HIMC imc = ::ImmGetContext(hwnd_); + if (imc) { + // Copy the composition string to the CompositionText object. + ret = GetString(imc, lparam, GCS_COMPSTR, composition_text); + + if (ret) { + // Retrieve the composition underlines and selection range information. + GetCompositionInfo(imc, lparam, composition_text, underlines, + composition_start); + + // Mark that there is an ongoing composition. + is_composing_ = true; + } + + ::ImmReleaseContext(hwnd_, imc); + } + return ret; +} + +void OsrImeHandlerWin::DisableIME() { + CleanupComposition(); + ::ImmAssociateContextEx(hwnd_, NULL, 0); +} + +void OsrImeHandlerWin::CancelIME() { + if (is_composing_) { + HIMC imc = ::ImmGetContext(hwnd_); + if (imc) { + ::ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, 0); + ::ImmReleaseContext(hwnd_, imc); + } + ResetComposition(); + } +} + +void OsrImeHandlerWin::EnableIME() { + // Load the default IME context. + ::ImmAssociateContextEx(hwnd_, NULL, IACE_DEFAULT); +} + +void OsrImeHandlerWin::UpdateCaretPosition(int index) { + // Save the caret position. + cursor_index_ = index; + // Move the IME window. + MoveImeWindow(); +} + +void OsrImeHandlerWin::ChangeCompositionRange( + const CefRange& selection_range, + const std::vector& bounds) { + composition_range_ = selection_range; + composition_bounds_ = bounds; + MoveImeWindow(); +} + +} // namespace client diff --git a/tests/cefclient/browser/osr_ime_handler_win.h b/tests/cefclient/browser/osr_ime_handler_win.h new file mode 100644 index 000000000..ed43ad208 --- /dev/null +++ b/tests/cefclient/browser/osr_ime_handler_win.h @@ -0,0 +1,116 @@ +// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright +// 2013 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. + +#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_IME_HANDLER_WIN_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_IME_HANDLER_WIN_H_ +#pragma once + +#include +#include + +#include "include/internal/cef_types_wrappers.h" + +namespace client { + +// Handles IME for the native parent window that hosts an off-screen browser. +// This object is only accessed on the CEF UI thread. +class OsrImeHandlerWin { + public: + explicit OsrImeHandlerWin(HWND hwnd); + virtual ~OsrImeHandlerWin(); + + // Retrieves whether or not there is an ongoing composition. + bool is_composing() const { return is_composing_; } + + // Retrieves the input language from Windows and update it. + void SetInputLanguage(); + + // Creates the IME caret windows if required. + void CreateImeWindow(); + + // Destroys the IME caret windows. + void DestroyImeWindow(); + + // Cleans up the all resources attached to the given IMM32Manager object, and + // reset its composition status. + void CleanupComposition(); + + // Resets the composition status and cancels the ongoing composition. + void ResetComposition(); + + // Retrieves a composition result of the ongoing composition if it exists. + bool GetResult(LPARAM lparam, CefString& result); + + // Retrieves the current composition status of the ongoing composition. + // Includes composition text, underline information and selection range in the + // composition text. IMM32 does not support char selection. + bool GetComposition(LPARAM lparam, CefString &composition_text, + std::vector &underlines, + int& composition_start); + + // Enables the IME attached to the given window. + virtual void EnableIME(); + + // Disables the IME attached to the given window. + virtual void DisableIME(); + + // Cancels an ongoing composition of the IME. + virtual void CancelIME(); + + // Updates the IME caret position of the given window. + void UpdateCaretPosition(int index); + + // Updates the composition range. |selected_range| is the range of characters + // that have been selected. |character_bounds| is the bounds of each character + // in view device coordinates. + void ChangeCompositionRange(const CefRange& selection_range, + const std::vector& character_bounds); + + // Updates the position of the IME windows. + void MoveImeWindow(); + + private: + // Retrieves the composition information. + void GetCompositionInfo(HIMC imm_context, LPARAM lparam, + CefString &composition_text, + std::vector& underlines, + int& composition_start); + + // Retrieves a string from the IMM. + bool GetString(HIMC imm_context, WPARAM lparam, int type, CefString& result); + + // Represents whether or not there is an ongoing composition. + bool is_composing_; + + // The current composition character range and its bounds. + std::vector composition_bounds_; + + // This value represents whether or not the current input context has IMEs. + bool ime_status_; + + // The current input Language ID retrieved from Windows - + // used for processing language-specific operations in IME. + LANGID input_language_id_; + + // Represents whether or not the current input context has created a system + // caret to set the position of its IME candidate window. + bool system_caret_; + + // The rectangle of the input caret retrieved from a renderer process. + CefRect ime_rect_; + + // The current cursor index in composition string. + int cursor_index_; + + // The composition range in the string. This may be used to determine the + // offset in composition bounds. + CefRange composition_range_; + + // Hwnd associated with this instance. + HWND hwnd_; +}; + +} // namespace client + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_OSR_IME_HANDLER_WIN_H_ diff --git a/tests/cefclient/browser/osr_window_win.cc b/tests/cefclient/browser/osr_window_win.cc index 90801a51c..fdab749cd 100644 --- a/tests/cefclient/browser/osr_window_win.cc +++ b/tests/cefclient/browser/osr_window_win.cc @@ -11,6 +11,7 @@ #include "cefclient/browser/main_message_loop.h" #include "cefclient/browser/resource.h" #include "cefclient/browser/util_win.h" +#include "cefclient/browser/osr_ime_handler_win.h" namespace client { @@ -55,7 +56,6 @@ OsrWindowWin::OsrWindowWin(Delegate* delegate, hwnd_(NULL), hdc_(NULL), hrc_(NULL), - client_rect_(), device_scale_factor_(client::GetDeviceScaleFactor()), painting_popup_(false), render_task_pending_(false), @@ -255,6 +255,8 @@ void OsrWindowWin::Create(HWND parent_hwnd, const RECT& rect) { DCHECK_EQ(register_res, S_OK); #endif + ime_handler_.reset(new OsrImeHandlerWin(hwnd_)); + // Notify the window owner. NotifyNativeWindowCreated(hwnd_); } @@ -273,6 +275,7 @@ void OsrWindowWin::Destroy() { // Destroy the native window. ::DestroyWindow(hwnd_); + ime_handler_.reset(); hwnd_ = NULL; } @@ -390,6 +393,74 @@ void OsrWindowWin::RegisterOsrClass(HINSTANCE hInstance, RegisterClassEx(&wcex); } +void OsrWindowWin::OnIMESetContext(UINT message, WPARAM wParam, LPARAM lParam) { + // We handle the IME Composition Window ourselves (but let the IME Candidates + // Window be handled by IME through DefWindowProc()), so clear the + // ISC_SHOWUICOMPOSITIONWINDOW flag: + lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW; + ::DefWindowProc(hwnd_, message, wParam, lParam); + + // Create Caret Window if required + if (ime_handler_) { + ime_handler_->CreateImeWindow(); + ime_handler_->MoveImeWindow(); + } +} + +void OsrWindowWin::OnIMEStartComposition() { + if (ime_handler_) { + ime_handler_->CreateImeWindow(); + ime_handler_->MoveImeWindow(); + ime_handler_->ResetComposition(); + } +} + +void OsrWindowWin::OnIMEComposition(UINT message, WPARAM wParam, + LPARAM lParam) { + if (browser_ && ime_handler_) { + CefString cTextStr; + if (ime_handler_->GetResult(lParam, cTextStr)) { + // Send the text to the browser. The |replacement_range| and + // |relative_cursor_pos| params are not used on Windows, so provide + // default invalid values. + browser_->GetHost()->ImeCommitText(cTextStr, + CefRange(UINT32_MAX, UINT32_MAX), 0); + ime_handler_->ResetComposition(); + // Continue reading the composition string - Japanese IMEs send both + // GCS_RESULTSTR and GCS_COMPSTR. + } + + std::vector underlines; + int composition_start = 0; + + if (ime_handler_->GetComposition(lParam, cTextStr, underlines, + composition_start)) { + // Send the composition string to the browser. The |replacement_range| + // param is not used on Windows, so provide a default invalid value. + browser_->GetHost()->ImeSetComposition(cTextStr, underlines, + CefRange(UINT32_MAX, UINT32_MAX), + CefRange(composition_start, + static_cast(composition_start + cTextStr.length()))); + + // Update the Candidate Window position. The cursor is at the end so + // subtract 1. This is safe because IMM32 does not support non-zero-width + // in a composition. Also, negative values are safely ignored in + // MoveImeWindow + ime_handler_->UpdateCaretPosition(composition_start - 1); + } else { + OnIMECancelCompositionEvent(); + } + } +} + +void OsrWindowWin::OnIMECancelCompositionEvent() { + if (browser_ && ime_handler_) { + browser_->GetHost()->ImeCancelComposition(); + ime_handler_->ResetComposition(); + ime_handler_->DestroyImeWindow(); + } +} + // static LRESULT CALLBACK OsrWindowWin::OsrWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { @@ -399,7 +470,22 @@ LRESULT CALLBACK OsrWindowWin::OsrWndProc(HWND hWnd, UINT message, if (!self) return DefWindowProc(hWnd, message, wParam, lParam); + // We want to handle IME events before the OS does any default handling. switch (message) { + case WM_IME_SETCONTEXT: + self->OnIMESetContext(message, wParam, lParam); + return 0; + case WM_IME_STARTCOMPOSITION: + self->OnIMEStartComposition(); + return 0; + case WM_IME_COMPOSITION: + self->OnIMEComposition(message, wParam, lParam); + return 0; + case WM_IME_ENDCOMPOSITION: + self->OnIMECancelCompositionEvent(); + // Let WTL call::DefWindowProc() and release its resources. + break; + case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: @@ -909,6 +995,24 @@ void OsrWindowWin::UpdateDragCursor( #endif } +void OsrWindowWin::OnImeCompositionRangeChanged( + CefRefPtr browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& character_bounds) { + CEF_REQUIRE_UI_THREAD(); + + if (ime_handler_) { + // Convert from view coordinates to device coordinates. + CefRenderHandler::RectList device_bounds; + CefRenderHandler::RectList::const_iterator it = character_bounds.begin(); + for (; it != character_bounds.end(); ++it) { + device_bounds.push_back(LogicalToDevice(*it, device_scale_factor_)); + } + + ime_handler_->ChangeCompositionRange(selection_range, device_bounds); + } +} + #if defined(CEF_USE_ATL) CefBrowserHost::DragOperationsMask diff --git a/tests/cefclient/browser/osr_window_win.h b/tests/cefclient/browser/osr_window_win.h index 398846783..b93c96baa 100644 --- a/tests/cefclient/browser/osr_window_win.h +++ b/tests/cefclient/browser/osr_window_win.h @@ -16,6 +16,8 @@ namespace client { +class OsrImeHandlerWin; + // Represents the native parent window for an off-screen browser. This object // must live on the CEF UI thread in order to handle CefRenderHandler callbacks. // The methods of this class are thread-safe unless otherwise indicated. @@ -94,6 +96,11 @@ class OsrWindowWin : void OnPaint(); bool OnEraseBkgnd(); + void OnIMESetContext(UINT message, WPARAM wParam, LPARAM lParam); + void OnIMEStartComposition(); + void OnIMEComposition(UINT message, WPARAM wParam, LPARAM lParam); + void OnIMECancelCompositionEvent(); + // Manage popup bounds. bool IsOverPopupWidget(int x, int y) const; int GetPopupXOffset() const; @@ -133,6 +140,10 @@ class OsrWindowWin : int x, int y) OVERRIDE; void UpdateDragCursor(CefRefPtr browser, CefRenderHandler::DragOperation operation) OVERRIDE; + void OnImeCompositionRangeChanged( + CefRefPtr browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& character_bounds) OVERRIDE; #if defined(CEF_USE_ATL) // OsrDragEvents methods. @@ -156,6 +167,9 @@ class OsrWindowWin : HDC hdc_; HGLRC hrc_; + // Class that encapsulates IMM32 APIs and controls IMEs attached to a window. + scoped_ptr ime_handler_; + RECT client_rect_; float device_scale_factor_; diff --git a/tests/cefclient/browser/text_input_client_osr_mac.h b/tests/cefclient/browser/text_input_client_osr_mac.h new file mode 100644 index 000000000..d35b3062a --- /dev/null +++ b/tests/cefclient/browser/text_input_client_osr_mac.h @@ -0,0 +1,77 @@ +// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright +// 2013 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. + +#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_TEXT_INPUT_CLIENT_OSR_MAC_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_TEXT_INPUT_CLIENT_OSR_MAC_H_ +#pragma once + +#import +#include +#include + +#include "include/cef_browser.h" +#include "include/cef_render_handler.h" + +// Implementation for the NSTextInputClient protocol used for enabling IME on +// mac when window rendering is disabled. + +@interface CefTextInputClientOSRMac : NSObject { + @private + + // The range of current marked text inside the whole content of the DOM node + // being edited. + NSRange markedRange_; + + // The current composition character range and its bounds. + CefRange composition_range_; + std::vector composition_bounds_; + + // Represents the input-method attributes supported by this object. + NSArray* validAttributesForMarkedText_; + + // Indicates if we are currently handling a key down event. + BOOL handlingKeyDown_; + + // Indicates if there is any marked text. + BOOL hasMarkedText_; + + // Indicates whether there was any marked text prior to handling + // the current key event. + BOOL oldHasMarkedText_; + + // Indicates if unmarkText is called or not when handling a keyboard + // event. + BOOL unmarkTextCalled_; + + // The selected range, cached from a message sent by the renderer. + NSRange selectedRange_; + + // Text to be inserted which was generated by handling a key down event. + std::string textToBeInserted_; + + // Marked text which was generated by handling a key down event. + CefString markedText_; + + // Underline information of the |markedText_|. + std::vector underlines_; + + // Replacement range information received from |setMarkedText:|. + CefRange setMarkedTextReplacementRange_; + + CefRefPtr browser_; +} + +@property(nonatomic, readonly) NSRange selectedRange; +@property(nonatomic) BOOL handlingKeyDown; + +- (id)initWithBrowser:(CefRefPtr)browser; +- (void)HandleKeyEventBeforeTextInputClient:(NSEvent*)keyEvent; +- (void)HandleKeyEventAfterTextInputClient:(CefKeyEvent)keyEvent; +- (void)ChangeCompositionRange:(CefRange)range + character_bounds:(const CefRenderHandler::RectList&)bounds; +- (void)cancelComposition; + +@end + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_OSR_TEXT_INPUT_CLIENT_OSR_MAC_H_ diff --git a/tests/cefclient/browser/text_input_client_osr_mac.mm b/tests/cefclient/browser/text_input_client_osr_mac.mm new file mode 100644 index 000000000..735f290f9 --- /dev/null +++ b/tests/cefclient/browser/text_input_client_osr_mac.mm @@ -0,0 +1,343 @@ +// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright +// 2013 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. + +// Implementation based on +// content/browser/renderer_host/render_widget_host_view_mac.mm from Chromium. + +#include "text_input_client_osr_mac.h" +#include "include/cef_client.h" + +#define ColorBLACK 0xFF000000 // Same as Blink SKColor. + +namespace { + +// TODO(suzhe): Upstream this function. +cef_color_t CefColorFromNSColor(NSColor *color) { + CGFloat r, g, b, a; + [color getRed:&r green:&g blue:&b alpha:&a]; + + return + std::max(0, std::min(static_cast(lroundf(255.0f * a)), 255)) << 24 | + std::max(0, std::min(static_cast(lroundf(255.0f * r)), 255)) << 16 | + std::max(0, std::min(static_cast(lroundf(255.0f * g)), 255)) << 8 | + std::max(0, std::min(static_cast(lroundf(255.0f * b)), 255)); +} + +// Extract underline information from an attributed string. Mostly copied from +// third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm +void ExtractUnderlines(NSAttributedString* string, + std::vector* 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)]; + NSNumber *style = [attrs objectForKey: NSUnderlineStyleAttributeName]; + if (style) { + cef_color_t color = ColorBLACK; + if (NSColor *colorAttr = + [attrs objectForKey:NSUnderlineColorAttributeName]) { + color = CefColorFromNSColor( + [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]); + } + cef_composition_underline_t line = { + {range.location, NSMaxRange(range)}, color, 0, [style intValue] > 1 + }; + underlines->push_back(line); + } + i = range.location + range.length; + } +} + +} // namespace + +extern "C" { +extern NSString* NSTextInputReplacementRangeAttributeName; +} + +@implementation CefTextInputClientOSRMac + +@synthesize selectedRange = selectedRange_; +@synthesize handlingKeyDown = handlingKeyDown_; + +- (id)initWithBrowser:(CefRefPtr)browser { + self = [super init]; + browser_ = browser; + return self; +} + +- (NSArray*)validAttributesForMarkedText { + if (!validAttributesForMarkedText_) { + validAttributesForMarkedText_ = [[NSArray alloc] initWithObjects: + NSUnderlineStyleAttributeName, + NSUnderlineColorAttributeName, + NSMarkedClauseSegmentAttributeName, + NSTextInputReplacementRangeAttributeName, + nil]; + } + return validAttributesForMarkedText_; +} + +- (NSRange)markedRange { + return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0); +} + +- (BOOL)hasMarkedText { + return hasMarkedText_; +} + +- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { + BOOL isAttributedString = [aString isKindOfClass:[NSAttributedString class]]; + NSString* im_text = isAttributedString ? [aString string] : aString; + if (handlingKeyDown_) { + textToBeInserted_.append([im_text UTF8String]); + } else { + cef_range_t range = { + replacementRange.location, + NSMaxRange(replacementRange) + }; + browser_->GetHost()->ImeCommitText([im_text UTF8String], range, 0); + } + + // Inserting text will delete all marked text automatically. + hasMarkedText_ = NO; +} + +- (void)doCommandBySelector:(SEL)aSelector { + // An input method calls this function to dispatch an editing command to be + // handled by this view. +} + +- (void)setMarkedText:(id)aString + selectedRange:(NSRange)newSelRange + replacementRange:(NSRange)replacementRange { + // An input method has updated the composition string. We send the given text + // and range to the browser so it can update the composition node of Blink. + + BOOL isAttributedString = [aString isKindOfClass:[NSAttributedString class]]; + NSString* im_text = isAttributedString ? [aString string] : aString; + int length = [im_text length]; + + // |markedRange_| will get set in a callback from ImeSetComposition(). + selectedRange_ = newSelRange; + markedText_ = [im_text UTF8String]; + hasMarkedText_ = (length > 0); + underlines_.clear(); + + if (isAttributedString) { + ExtractUnderlines(aString, &underlines_); + } else { + // Use a thin black underline by default. + cef_composition_underline_t line = { + {0, length}, ColorBLACK, 0, false + }; + underlines_.push_back(line); + } + + // If we are handling a key down event then ImeSetComposition() will be + // called from the keyEvent: method. + // Input methods of Mac use setMarkedText calls with empty text to cancel an + // ongoing composition. Our input method backend will automatically cancel an + // ongoing composition when we send empty text. + if (handlingKeyDown_) { + setMarkedTextReplacementRange_ = { + replacementRange.location, + NSMaxRange(replacementRange) + }; + } else if (!handlingKeyDown_) { + CefRange replacement_range(replacementRange.location, + NSMaxRange(replacementRange)); + CefRange selection_range(newSelRange.location, NSMaxRange(newSelRange)); + + browser_->GetHost()->ImeSetComposition( + markedText_, underlines_, replacement_range, selection_range); + } +} + +- (void)unmarkText { + // Delete the composition node of the browser and finish an ongoing + // composition. + // It seems that, instead of calling this method, an input method will call + // the setMarkedText method with empty text to cancel ongoing composition. + // Implement this method even though we don't expect it to be called. + hasMarkedText_ = NO; + markedText_.clear(); + underlines_.clear(); + + // If we are handling a key down event then ImeFinishComposingText() will be + // called from the keyEvent: method. + if (!handlingKeyDown_) + browser_->GetHost()->ImeFinishComposingText(false); + else + unmarkTextCalled_ = YES; +} + +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range + actualRange:(NSRangePointer)actualRange { + // Modify the attributed string if required. + // Not implemented here as we do not want to control the IME window view. + return nil; +} + +- (NSRect)firstViewRectForCharacterRange:(NSRange)theRange + actualRange:(NSRangePointer)actualRange { + NSRect rect; + + NSUInteger location = theRange.location; + + // If location is not specified fall back to the composition range start. + if (location == NSNotFound) + location = markedRange_.location; + + // Offset location by the composition range start if required. + if (location >= markedRange_.location) + location -= markedRange_.location; + + if(location < composition_bounds_.size()) { + const CefRect& rc = composition_bounds_[location]; + rect = NSMakeRect(rc.x, rc.y, rc.width, rc.height); + } + + if (actualRange) + *actualRange = NSMakeRange(location, theRange.length); + + return rect; +} + +- (NSRect)screenRectFromViewRect:(NSRect)rect { + NSRect screenRect; + + int screenX, screenY; + browser_->GetHost()->GetClient()->GetRenderHandler()->GetScreenPoint( + browser_, rect.origin.x, rect.origin.y, screenX, screenY); + screenRect.origin = NSMakePoint(screenX, screenY); + screenRect.size = rect.size; + + return screenRect; +} + +- (NSRect)firstRectForCharacterRange:(NSRange)theRange + actualRange:(NSRangePointer)actualRange { + NSRect rect = [self firstViewRectForCharacterRange:theRange + actualRange:actualRange]; + + // Convert into screen coordinates for return. + rect = [self screenRectFromViewRect:rect]; + + if (rect.origin.y >= rect.size.height) + rect.origin.y -= rect.size.height; + else + rect.origin.y = 0; + + return rect; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint { + return NSNotFound; +} + +- (void)HandleKeyEventBeforeTextInputClient:(NSEvent*)keyEvent { + DCHECK([keyEvent type] == NSKeyDown); + // Don't call this method recursively. + DCHECK(!handlingKeyDown_); + + oldHasMarkedText_ = hasMarkedText_; + handlingKeyDown_ = YES; + + // These variables might be set when handling the keyboard event. + // Clear them here so that we can know whether they have changed afterwards. + textToBeInserted_.clear(); + markedText_.clear(); + underlines_.clear(); + setMarkedTextReplacementRange_ = CefRange(UINT32_MAX, UINT32_MAX); + unmarkTextCalled_ = NO; +} + +- (void)HandleKeyEventAfterTextInputClient:(CefKeyEvent)keyEvent { + handlingKeyDown_ = NO; + + // Send keypress and/or composition related events. + // 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. + + // If the text to be inserted only contains 1 character then we can just send + // a keypress event. + if (!hasMarkedText_ && !oldHasMarkedText_ && + textToBeInserted_.length() <= 1) { + keyEvent.type = KEYEVENT_KEYDOWN; + + browser_->GetHost()->SendKeyEvent(keyEvent); + + // Don't send a CHAR event for non-char keys like arrows, function keys and + // clear. + if (keyEvent.modifiers & (EVENTFLAG_IS_KEY_PAD)) { + if(keyEvent.native_key_code == 71) + return; + } + + keyEvent.type = KEYEVENT_CHAR; + browser_->GetHost()->SendKeyEvent(keyEvent); + } + + // If the text to be inserted contains multiple characters then send the text + // to the browser using ImeCommitText(). + BOOL textInserted = NO; + if (textToBeInserted_.length() > + ((hasMarkedText_ || oldHasMarkedText_) ? 0u : 1u)) { + browser_->GetHost()->ImeCommitText( + textToBeInserted_, CefRange(UINT32_MAX, UINT32_MAX), 0); + textToBeInserted_.clear(); + } + + // Update or cancel the composition. If some text has been inserted then we + // don't need to explicitly cancel the composition. + if (hasMarkedText_ && markedText_.length()) { + // Update the composition by sending marked text to the browser. + // |selectedRange_| is the range being selected inside the marked text. + browser_->GetHost()->ImeSetComposition( + markedText_, underlines_, setMarkedTextReplacementRange_, + CefRange(selectedRange_.location, NSMaxRange(selectedRange_))); + } else if (oldHasMarkedText_ && !hasMarkedText_ && !textInserted) { + // There was no marked text or inserted text. Complete or cancel the + // composition. + if (unmarkTextCalled_) + browser_->GetHost()->ImeFinishComposingText(false); + else + browser_->GetHost()->ImeCancelComposition(); + } + + setMarkedTextReplacementRange_ = CefRange(UINT32_MAX, UINT32_MAX); +} + +- (void)ChangeCompositionRange:(CefRange)range + character_bounds:(const CefRenderHandler::RectList&) bounds { + composition_range_ = range; + markedRange_ = NSMakeRange(range.from, range.to - range.from); + composition_bounds_ = bounds; +} + +- (void)cancelComposition { + if (!hasMarkedText_) + return; + + // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:] + // doesn't call any NSTextInput functions, such as setMarkedText or + // insertText. + // TODO(erikchen): NSInputManager is deprecated since OSX 10.6. Switch to + // NSTextInputContext. http://www.crbug.com/479010. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + NSInputManager* currentInputManager = [NSInputManager currentInputManager]; + [currentInputManager markedTextAbandoned:self]; +#pragma clang diagnostic pop + + hasMarkedText_ = NO; + // Should not call [self unmarkText] here because it'll send unnecessary + // cancel composition messages to the browser. +} + +@end diff --git a/tests/unittests/os_rendering_unittest.cc b/tests/unittests/os_rendering_unittest.cc index 2729cec22..6f088da39 100644 --- a/tests/unittests/os_rendering_unittest.cc +++ b/tests/unittests/os_rendering_unittest.cc @@ -173,6 +173,14 @@ enum OSRTestType { OSR_TEST_DRAG_DROP_UPDATE_CURSOR, // dropping element inside drop region will move the element OSR_TEST_DRAG_DROP_DROP, + // IMESetComposition will update the composition range + OSR_TEST_IME_SET_COMPOSITION, + // IMECommitText inserts the specified text + OSR_TEST_IME_COMMIT_TEXT, + // IMEFinishComposition will commit the text present composition text + OSR_TEST_IME_FINISH_COMPOSITION, + // IMECancelComposition will update the composition range + OSR_TEST_IME_CANCEL_COMPOSITION, // Define the range for popup tests. OSR_TEST_POPUP_FIRST = OSR_TEST_POPUP_PAINT, @@ -224,6 +232,27 @@ class OSRTestHandler : public RoutingTestHandler, frame->GetURL().ToString().c_str()); DestroySucceededTestSoon(); } break; + case OSR_TEST_IME_COMMIT_TEXT: { + const std::string& expected_url = + std::string(kTestUrl) + "?k=osrimecommit"; + EXPECT_STREQ(expected_url.c_str(), + frame->GetURL().ToString().c_str()); + DestroySucceededTestSoon(); + } break; + case OSR_TEST_IME_FINISH_COMPOSITION: { + const std::string& expected_url = + std::string(kTestUrl) + "?k=" + kKeyTestWord; + EXPECT_STREQ(expected_url.c_str(), + frame->GetURL().ToString().c_str()); + DestroySucceededTestSoon(); + } break; + case OSR_TEST_IME_CANCEL_COMPOSITION: { + const std::string& expected_url = + std::string(kTestUrl) + "?k="; + EXPECT_STREQ(expected_url.c_str(), + frame->GetURL().ToString().c_str()); + DestroySucceededTestSoon(); + } break; default: // Intentionally left blank break; @@ -717,6 +746,169 @@ class OSRTestHandler : public RoutingTestHandler, } } break; + case OSR_TEST_IME_COMMIT_TEXT: + { + // trigger the IME Set Composition event + if (StartTest()) { + // click inside edit box so that text could be entered + CefMouseEvent mouse_event; + mouse_event.x = MiddleX(kEditBoxRect); + mouse_event.y = MiddleY(kEditBoxRect); + mouse_event.modifiers = 0; + browser->GetHost()->SendMouseClickEvent( + mouse_event, MBT_LEFT, false, 1); + browser->GetHost()->SendMouseClickEvent( + mouse_event, MBT_LEFT, true, 1); + + size_t word_length = strlen(kKeyTestWord); + // Add some input keys to edit box + for (size_t i = 0; i < word_length; ++i) { +#if defined(OS_WIN) + SendKeyEvent(browser, kKeyTestWord[i]); +#elif defined(OS_MACOSX) + SendKeyEvent(browser, kKeyTestCodes[i]); +#elif defined(OS_LINUX) + SendKeyEvent(browser, kNativeKeyTestCodes[i], kKeyTestCodes[i]); +#else +#error "Unsupported platform" +#endif + } + // This text should be honored instead of 'ka' added via key events + CefString markedText("osrimecommit"); + + CefRange range(0, markedText.length()); + browser->GetHost()->ImeCommitText(markedText, range, 0); + + // click button to navigate + mouse_event.x = MiddleX(kNavigateButtonRect); + mouse_event.y = MiddleY(kNavigateButtonRect); + browser->GetHost()->SendMouseClickEvent( + mouse_event, MBT_LEFT, false, 1); + browser->GetHost()->SendMouseClickEvent( + mouse_event, MBT_LEFT, true, 1); + } + } + break; + case OSR_TEST_IME_FINISH_COMPOSITION: + { + // trigger the IME Set Composition event + if (StartTest()) { + // click inside edit box so that text could be entered + CefMouseEvent mouse_event; + mouse_event.x = MiddleX(kEditBoxRect); + mouse_event.y = MiddleY(kEditBoxRect); + mouse_event.modifiers = 0; + browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT, + false, 1); + browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT, + true, 1); + + size_t word_length = strlen(kKeyTestWord); + // Add some input keys to edit box + for (size_t i = 0; i < word_length; ++i) { +#if defined(OS_WIN) + SendKeyEvent(browser, kKeyTestWord[i]); +#elif defined(OS_MACOSX) + SendKeyEvent(browser, kKeyTestCodes[i]); +#elif defined(OS_LINUX) + SendKeyEvent(browser, kNativeKeyTestCodes[i], kKeyTestCodes[i]); +#else +#error "Unsupported platform" +#endif + } + + // Finish Composition should set the existing composition + browser->GetHost()->ImeFinishComposingText(true); + + // click button to navigate + mouse_event.x = MiddleX(kNavigateButtonRect); + mouse_event.y = MiddleY(kNavigateButtonRect); + browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT, + false, 1); + browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT, + true, 1); + } + } + break; + case OSR_TEST_IME_CANCEL_COMPOSITION: + { + // trigger the IME Set Composition event + if (StartTest()) { + // click inside edit box so that text could be entered + CefMouseEvent mouse_event; + mouse_event.x = MiddleX(kEditBoxRect); + mouse_event.y = MiddleY(kEditBoxRect); + mouse_event.modifiers = 0; + browser->GetHost()->SendMouseClickEvent( + mouse_event, MBT_LEFT, false, 1); + browser->GetHost()->SendMouseClickEvent( + mouse_event, MBT_LEFT, true, 1); + // Add some input keys to edit box + CefString markedText("か"); + std::vector underlines; + + // Use a thin black underline by default. + cef_range_t range = {0, markedText.length()}; + cef_composition_underline_t line = { + range, 0xFF000000, 0, false + }; + underlines.push_back(line); + + CefRange replacement_range(0, markedText.length()); + CefRange selection_range(0,markedText.length()); + + // Composition should be updated + browser->GetHost()->ImeSetComposition(markedText, underlines, + replacement_range,selection_range); + + // CancelComposition should clean up the edit text + browser->GetHost()->ImeCancelComposition(); + + // click button to navigate and verify + mouse_event.x = MiddleX(kNavigateButtonRect); + mouse_event.y = MiddleY(kNavigateButtonRect); + browser->GetHost()->SendMouseClickEvent( + mouse_event, MBT_LEFT, false, 1); + browser->GetHost()->SendMouseClickEvent( + mouse_event, MBT_LEFT, true, 1); + } + } + break; + case OSR_TEST_IME_SET_COMPOSITION: + { + // trigger the IME Set Composition event + if (StartTest()) { + // click inside edit box so that text could be entered + CefMouseEvent mouse_event; + mouse_event.x = MiddleX(kEditBoxRect); + mouse_event.y = MiddleY(kEditBoxRect); + mouse_event.modifiers = 0; + browser->GetHost()->SendMouseClickEvent( + mouse_event, MBT_LEFT, false, 1); + browser->GetHost()->SendMouseClickEvent( + mouse_event, MBT_LEFT, true, 1); + + // Now set some intermediate text composition + CefString markedText("か"); + std::vector underlines; + + // Use a thin black underline by default. + cef_range_t range = {0, markedText.length()}; + cef_composition_underline_t line = { + range, 0xFF000000, 0, false + }; + underlines.push_back(line); + + CefRange replacement_range(0, markedText.length()); + CefRange selection_range(0,markedText.length()); + + // This should update composition range and + // trigger the compositionRangeChanged callback + browser->GetHost()->ImeSetComposition(markedText, underlines, + replacement_range,selection_range); + } + } + break; default: break; } @@ -750,6 +942,17 @@ class OSRTestHandler : public RoutingTestHandler, } } + void OnImeCompositionRangeChanged(CefRefPtr browser, + const CefRange& range, + const CefRenderHandler::RectList& bounds) override { + if (test_type_ == OSR_TEST_IME_SET_COMPOSITION && started()) { + EXPECT_EQ(range.from, 0); + EXPECT_EQ(range.to, 1); + EXPECT_EQ(1U, bounds.size()); + DestroySucceededTestSoon(); + } + } + bool StartDragging(CefRefPtr browser, CefRefPtr drag_data, CefRenderHandler::DragOperationsMask allowed_ops, @@ -1078,3 +1281,11 @@ OSR_TEST(DragDropUpdateCursor, OSR_TEST_DRAG_DROP_UPDATE_CURSOR, 1.0f); OSR_TEST(DragDropUpdateCursor2x, OSR_TEST_DRAG_DROP_UPDATE_CURSOR, 2.0f); OSR_TEST(DragDropDropElement, OSR_TEST_DRAG_DROP_DROP, 1.0f); OSR_TEST(DragDropDropElement2x, OSR_TEST_DRAG_DROP_DROP, 2.0f); +OSR_TEST(IMESetComposition, OSR_TEST_IME_SET_COMPOSITION, 1.0f); +OSR_TEST(IMESetComposition2x, OSR_TEST_IME_SET_COMPOSITION, 2.0f); +OSR_TEST(IMECommitText, OSR_TEST_IME_COMMIT_TEXT, 1.0f); +OSR_TEST(IMECommitText2x, OSR_TEST_IME_COMMIT_TEXT, 2.0f); +OSR_TEST(IMEFinishComposition, OSR_TEST_IME_FINISH_COMPOSITION, 1.0f); +OSR_TEST(IMEFinishComposition2x, OSR_TEST_IME_FINISH_COMPOSITION, 2.0f); +OSR_TEST(IMECancelComposition, OSR_TEST_IME_CANCEL_COMPOSITION, 1.0f); +OSR_TEST(IMECancelComposition2x, OSR_TEST_IME_CANCEL_COMPOSITION, 2.0f); diff --git a/tools/cef_parser.py b/tools/cef_parser.py index 8d354c882..6495401a1 100644 --- a/tools/cef_parser.py +++ b/tools/cef_parser.py @@ -416,9 +416,10 @@ _simpletypes = { 'cef_json_parser_error_t': ['cef_json_parser_error_t', 'JSON_NO_ERROR'], 'cef_plugin_policy_t': ['cef_plugin_policy_t', 'PLUGIN_POLICY_ALLOW'], 'CefCursorHandle' : ['cef_cursor_handle_t', 'kNullCursorHandle'], + 'CefCompositionUnderline' : ['cef_composition_underline_t', + 'CefCompositionUnderline()'], 'CefEventHandle' : ['cef_event_handle_t', 'kNullEventHandle'], 'CefWindowHandle' : ['cef_window_handle_t', 'kNullWindowHandle'], - 'CefTextInputContext' : ['cef_text_input_context_t' ,'NULL'], 'CefPoint' : ['cef_point_t', 'CefPoint()'], 'CefRect' : ['cef_rect_t', 'CefRect()'], 'CefSize' : ['cef_size_t', 'CefSize()'],