diff --git a/cef3/cef.gyp b/cef3/cef.gyp index c37f17545..9c282ae37 100644 --- a/cef3/cef.gyp +++ b/cef3/cef.gyp @@ -1042,8 +1042,11 @@ 'libcef/browser/javascript_dialog_mac.mm', 'libcef/browser/menu_creator_runner_mac.h', 'libcef/browser/menu_creator_runner_mac.mm', + 'libcef/browser/render_widget_host_view_osr_mac.mm', 'libcef/browser/render_widget_host_view_osr.cc', 'libcef/browser/render_widget_host_view_osr.h', + 'libcef/browser/text_input_client_osr_mac.mm', + 'libcef/browser/text_input_client_osr_mac.h', 'libcef/browser/web_contents_view_osr.cc', 'libcef/browser/web_contents_view_osr.h', # Include sources for context menu implementation. diff --git a/cef3/include/capi/cef_browser_capi.h b/cef3/include/capi/cef_browser_capi.h index 84908e5f6..b970eca90 100644 --- a/cef3/include/capi/cef_browser_capi.h +++ b/cef3/include/capi/cef_browser_capi.h @@ -407,6 +407,26 @@ typedef struct _cef_browser_host_t { /// void (CEF_CALLBACK *send_capture_lost_event)( struct _cef_browser_host_t* self); + + /// + // Get the NSTextInputContext implementation for enabling IME on Mac when + // window rendering is disabled. + /// + cef_text_input_context_t (CEF_CALLBACK *get_nstext_input_context)( + struct _cef_browser_host_t* self); + + /// + // Handles a keyDown event prior to passing it through the NSTextInputClient + // machinery. + /// + void (CEF_CALLBACK *handle_key_event_before_text_input_client)( + struct _cef_browser_host_t* self, cef_event_handle_t keyEvent); + + /// + // Performs any additional actions after NSTextInputClient handles the event. + /// + void (CEF_CALLBACK *handle_key_event_after_text_input_client)( + struct _cef_browser_host_t* self, cef_event_handle_t keyEvent); } cef_browser_host_t; diff --git a/cef3/include/cef_browser.h b/cef3/include/cef_browser.h index c445a7de4..4817ce632 100644 --- a/cef3/include/cef_browser.h +++ b/cef3/include/cef_browser.h @@ -452,6 +452,26 @@ class CefBrowserHost : public virtual CefBase { /// /*--cef()--*/ virtual void SendCaptureLostEvent() =0; + + /// + // Get the NSTextInputContext implementation for enabling IME on Mac when + // window rendering is disabled. + /// + /*--cef(default_retval=NULL)--*/ + virtual CefTextInputContext GetNSTextInputContext() =0; + + /// + // Handles a keyDown event prior to passing it through the NSTextInputClient + // machinery. + /// + /*--cef()--*/ + virtual void HandleKeyEventBeforeTextInputClient(CefEventHandle keyEvent) =0; + + /// + // Performs any additional actions after NSTextInputClient handles the event. + /// + /*--cef()--*/ + virtual void HandleKeyEventAfterTextInputClient(CefEventHandle keyEvent) =0; }; #endif // CEF_INCLUDE_CEF_BROWSER_H_ diff --git a/cef3/include/internal/cef_linux.h b/cef3/include/internal/cef_linux.h index c7974bec2..2a50f24eb 100644 --- a/cef3/include/internal/cef_linux.h +++ b/cef3/include/internal/cef_linux.h @@ -72,6 +72,7 @@ class CefCriticalSection { #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/cef3/include/internal/cef_mac.h b/cef3/include/internal/cef_mac.h index f4060b2f3..352a5c2b5 100644 --- a/cef3/include/internal/cef_mac.h +++ b/cef3/include/internal/cef_mac.h @@ -72,6 +72,7 @@ class CefCriticalSection { #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/cef3/include/internal/cef_types_linux.h b/cef3/include/internal/cef_types_linux.h index ebb57becd..db30527b6 100644 --- a/cef3/include/internal/cef_types_linux.h +++ b/cef3/include/internal/cef_types_linux.h @@ -46,6 +46,7 @@ extern "C" { #define cef_cursor_handle_t void* #define cef_event_handle_t GdkEvent* #define cef_window_handle_t GtkWidget* +#define cef_text_input_context_t void* /// // Structure representing CefExecuteProcess arguments. diff --git a/cef3/include/internal/cef_types_mac.h b/cef3/include/internal/cef_types_mac.h index d2ea2540b..7dd997f94 100644 --- a/cef3/include/internal/cef_types_mac.h +++ b/cef3/include/internal/cef_types_mac.h @@ -43,18 +43,22 @@ @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 #ifdef __cplusplus diff --git a/cef3/include/internal/cef_types_win.h b/cef3/include/internal/cef_types_win.h index fbaaaaf59..8c4693cd6 100644 --- a/cef3/include/internal/cef_types_win.h +++ b/cef3/include/internal/cef_types_win.h @@ -46,6 +46,7 @@ extern "C" { #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* /// // Structure representing CefExecuteProcess arguments. diff --git a/cef3/include/internal/cef_win.h b/cef3/include/internal/cef_win.h index d9caad443..29e26c456 100644 --- a/cef3/include/internal/cef_win.h +++ b/cef3/include/internal/cef_win.h @@ -71,6 +71,7 @@ class CefCriticalSection { #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/cef3/libcef/browser/browser_host_impl.cc b/cef3/libcef/browser/browser_host_impl.cc index e61481b31..6c88c019a 100644 --- a/cef3/libcef/browser/browser_host_impl.cc +++ b/cef3/libcef/browser/browser_host_impl.cc @@ -1453,6 +1453,22 @@ void CefBrowserHostImpl::RunFileChooser( callback)); } +#if !defined(OS_MACOSX) +CefTextInputContext CefBrowserHostImpl::GetNSTextInputContext() { + NOTREACHED(); + return NULL; +} + +void CefBrowserHostImpl::HandleKeyEventBeforeTextInputClient( + CefEventHandle keyEvent) { + NOTREACHED(); +} + +void CefBrowserHostImpl::HandleKeyEventAfterTextInputClient( + CefEventHandle keyEvent) { + NOTREACHED(); +} +#endif // !defined(OS_MACOSX) // content::WebContentsDelegate methods. // ----------------------------------------------------------------------------- diff --git a/cef3/libcef/browser/browser_host_impl.h b/cef3/libcef/browser/browser_host_impl.h index 929348050..1d3d375f1 100644 --- a/cef3/libcef/browser/browser_host_impl.h +++ b/cef3/libcef/browser/browser_host_impl.h @@ -143,6 +143,11 @@ class CefBrowserHostImpl : public CefBrowserHost, int deltaX, int deltaY) OVERRIDE; virtual void SendFocusEvent(bool setFocus) OVERRIDE; virtual void SendCaptureLostEvent() OVERRIDE; + virtual CefTextInputContext GetNSTextInputContext() OVERRIDE; + virtual void HandleKeyEventBeforeTextInputClient(CefEventHandle keyEvent) + OVERRIDE; + virtual void HandleKeyEventAfterTextInputClient(CefEventHandle keyEvent) + OVERRIDE; // CefBrowser methods. virtual CefRefPtr GetHost() OVERRIDE; diff --git a/cef3/libcef/browser/browser_host_impl_mac.mm b/cef3/libcef/browser/browser_host_impl_mac.mm index 32a9caead..b574afd46 100644 --- a/cef3/libcef/browser/browser_host_impl_mac.mm +++ b/cef3/libcef/browser/browser_host_impl_mac.mm @@ -8,6 +8,10 @@ #import #import +#include "libcef/browser/render_widget_host_view_osr.h" +#include "libcef/browser/text_input_client_osr_mac.h" +#include "libcef/browser/thread_util.h" + #include "base/file_util.h" #include "base/mac/mac_util.h" #include "base/string_util.h" @@ -252,6 +256,59 @@ bool RunSaveFileDialog(const content::FileChooserParams& params, } // namespace +CefTextInputContext CefBrowserHostImpl::GetNSTextInputContext() { + if (!IsWindowRenderingDisabled()) { + NOTREACHED() << "Window rendering is not disabled"; + return NULL; + } + + if (!CEF_CURRENTLY_ON_UIT()) { + NOTREACHED() << "Called on invalid thread"; + return NULL; + } + + CefRenderWidgetHostViewOSR* rwhv = static_cast( + GetWebContents()->GetRenderWidgetHostView()); + + return rwhv->GetNSTextInputContext(); +} + +void CefBrowserHostImpl::HandleKeyEventBeforeTextInputClient( + CefEventHandle keyEvent) { + if (!IsWindowRenderingDisabled()) { + NOTREACHED() << "Window rendering is not disabled"; + return; + } + + if (!CEF_CURRENTLY_ON_UIT()) { + NOTREACHED() << "Called on invalid thread"; + return; + } + + CefRenderWidgetHostViewOSR* rwhv = static_cast( + GetWebContents()->GetRenderWidgetHostView()); + + rwhv->HandleKeyEventBeforeTextInputClient(keyEvent); +} + +void CefBrowserHostImpl::HandleKeyEventAfterTextInputClient( + CefEventHandle keyEvent) { + if (!IsWindowRenderingDisabled()) { + NOTREACHED() << "Window rendering is not disabled"; + return; + } + + if (!CEF_CURRENTLY_ON_UIT()) { + NOTREACHED() << "Called on invalid thread"; + return; + } + + CefRenderWidgetHostViewOSR* rwhv = static_cast( + GetWebContents()->GetRenderWidgetHostView()); + + rwhv->HandleKeyEventAfterTextInputClient(keyEvent); +} + bool CefBrowserHostImpl::PlatformViewText(const std::string& text) { NOTIMPLEMENTED(); return false; diff --git a/cef3/libcef/browser/render_widget_host_view_osr.cc b/cef3/libcef/browser/render_widget_host_view_osr.cc index 6ac10c7a7..cd0f853ac 100644 --- a/cef3/libcef/browser/render_widget_host_view_osr.cc +++ b/cef3/libcef/browser/render_widget_host_view_osr.cc @@ -52,7 +52,11 @@ CefRenderWidgetHostViewOSR::CefRenderWidgetHostViewOSR( render_widget_host_(content::RenderWidgetHostImpl::From(widget)), parent_host_view_(NULL), popup_host_view_(NULL), - about_to_validate_and_paint_(false) { + about_to_validate_and_paint_(false) +#if defined(OS_MACOSX) + , text_input_context_osr_mac_(NULL) +#endif + { DCHECK(render_widget_host_); render_widget_host_->SetView(this); @@ -204,6 +208,7 @@ void CefRenderWidgetHostViewOSR::UpdateCursor(const WebCursor& cursor) { void CefRenderWidgetHostViewOSR::SetIsLoading(bool is_loading) { } +#if !defined(OS_MACOSX) void CefRenderWidgetHostViewOSR::TextInputStateChanged( const ViewHostMsg_TextInputState_Params& params) { } @@ -215,6 +220,7 @@ void CefRenderWidgetHostViewOSR::ImeCompositionRangeChanged( const ui::Range& range, const std::vector& character_bounds) { } +#endif // !defined(OS_MACOSX) void CefRenderWidgetHostViewOSR::DidUpdateBackingStore( const gfx::Rect& scroll_rect, diff --git a/cef3/libcef/browser/render_widget_host_view_osr.h b/cef3/libcef/browser/render_widget_host_view_osr.h index 3c99023dd..2d92b1cc5 100644 --- a/cef3/libcef/browser/render_widget_host_view_osr.h +++ b/cef3/libcef/browser/render_widget_host_view_osr.h @@ -42,6 +42,37 @@ class CefWebContentsViewOSR; /////////////////////////////////////////////////////////////////////////////// class CefRenderWidgetHostViewOSR : public content::RenderWidgetHostViewBase { +#if defined(OS_MACOSX) + public: + NSTextInputContext* GetNSTextInputContext(); + void HandleKeyEventBeforeTextInputClient(CefEventHandle keyEvent); + void HandleKeyEventAfterTextInputClient(CefEventHandle keyEvent); + + bool GetCachedFirstRectForCharacterRange(ui::Range range, gfx::Rect* rect, + ui::Range* actual_range) const; + + private: + // 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 ui::Range& range, + ui::Range* actual_range) const; + + // Converts from given whole character range to composition oriented range. If + // the conversion failed, return ui::Range::InvalidRange. + ui::Range ConvertCharacterRangeToCompositionRange( + const ui::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 ui::Range& range, + size_t* line_break_point); + + void DestroyNSTextInputOSR(); +#endif // defined(OS_MACOSX) + public: // RenderWidgetHostView methods. virtual void InitAsChild(gfx::NativeView parent_view) OVERRIDE; @@ -170,6 +201,8 @@ class CefRenderWidgetHostViewOSR : public content::RenderWidgetHostViewBase { void NotifyHideWidget(); void NotifySizeWidget(); + content::RenderWidgetHostImpl* get_render_widget_host_impl() const + { return render_widget_host_; } CefRefPtr get_browser_impl() const; void set_browser_impl(CefRefPtr browser); void set_popup_host_view(CefRenderWidgetHostViewOSR* popup_view); @@ -205,6 +238,10 @@ class CefRenderWidgetHostViewOSR : public content::RenderWidgetHostViewBase { gfx::Rect popup_position_; +#if defined(OS_MACOSX) + NSTextInputContext* text_input_context_osr_mac_; +#endif // defined(OS_MACOSX) + DISALLOW_COPY_AND_ASSIGN(CefRenderWidgetHostViewOSR); }; diff --git a/cef3/libcef/browser/render_widget_host_view_osr_mac.mm b/cef3/libcef/browser/render_widget_host_view_osr_mac.mm new file mode 100644 index 000000000..bf186a4ce --- /dev/null +++ b/cef3/libcef/browser/render_widget_host_view_osr_mac.mm @@ -0,0 +1,207 @@ +// Copyright (c) 2013 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "libcef/browser/render_widget_host_view_osr.h" +#include "libcef/browser/text_input_client_osr_mac.h" + +static CefTextInputClientOSRMac* GetInputClientFromContext( + const NSTextInputContext* context) { + if (!context) + return NULL; + return reinterpret_cast([context client]); +} + +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]; +} + +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; +} + +void CefRenderWidgetHostViewOSR::ImeCompositionRangeChanged( + const ui::Range& range, + const std::vector& character_bounds) { + CefTextInputClientOSRMac* client = GetInputClientFromContext( + text_input_context_osr_mac_); + if (!client) + return; + + client->markedRange_ = range.ToNSRange(); + client->composition_range_ = range; + client->composition_bounds_ = character_bounds; +} + +void CefRenderWidgetHostViewOSR::ImeCancelComposition() { + CefTextInputClientOSRMac* client = GetInputClientFromContext( + text_input_context_osr_mac_); + if (client) + [client cancelComposition]; +} + +void CefRenderWidgetHostViewOSR::TextInputStateChanged( + const ViewHostMsg_TextInputState_Params& params) { + [NSApp updateWindows]; +} + +bool CefRenderWidgetHostViewOSR::GetLineBreakIndex( + const std::vector& bounds, + const ui::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(), range.end()); + int max_height = 0; + int min_y_offset = kint32max; + 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; +} + +gfx::Rect CefRenderWidgetHostViewOSR::GetFirstRectForCompositionRange( + const ui::Range& range, ui::Range* actual_range) const { + CefTextInputClientOSRMac* client = GetInputClientFromContext( + text_input_context_osr_mac_); + + DCHECK(client); + DCHECK(actual_range); + DCHECK(!client->composition_bounds_.empty()); + DCHECK_LE(range.start(), client->composition_bounds_.size()); + DCHECK_LE(range.end(), client->composition_bounds_.size()); + + if (range.is_empty()) { + *actual_range = range; + if (range.start() == client->composition_bounds_.size()) { + return gfx::Rect(client->composition_bounds_[range.start() - 1].right(), + client->composition_bounds_[range.start() - 1].y(), + 0, + client->composition_bounds_[range.start() - 1].height()); + } else { + return gfx::Rect(client->composition_bounds_[range.start()].x(), + client->composition_bounds_[range.start()].y(), + 0, + client->composition_bounds_[range.start()].height()); + } + } + + size_t end_idx; + if (!GetLineBreakIndex(client->composition_bounds_, + range, &end_idx)) { + end_idx = range.end(); + } + *actual_range = ui::Range(range.start(), end_idx); + gfx::Rect rect = client->composition_bounds_[range.start()]; + for (size_t i = range.start() + 1; i < end_idx; ++i) { + rect.Union(client->composition_bounds_[i]); + } + return rect; +} + +ui::Range CefRenderWidgetHostViewOSR::ConvertCharacterRangeToCompositionRange( + const ui::Range& request_range) const { + CefTextInputClientOSRMac* client = GetInputClientFromContext( + text_input_context_osr_mac_); + DCHECK(client); + + if (client->composition_range_.is_empty()) + return ui::Range::InvalidRange(); + + if (request_range.is_reversed()) + return ui::Range::InvalidRange(); + + if (request_range.start() < client->composition_range_.start() + || request_range.start() > client->composition_range_.end() + || request_range.end() > client->composition_range_.end()) + return ui::Range::InvalidRange(); + + return ui::Range(request_range.start() - client->composition_range_.start(), + request_range.end() - client->composition_range_.start()); +} + +bool CefRenderWidgetHostViewOSR::GetCachedFirstRectForCharacterRange( + ui::Range range, gfx::Rect* rect, ui::Range* actual_range) const { + DCHECK(rect); + CefTextInputClientOSRMac* client = GetInputClientFromContext( + text_input_context_osr_mac_); + + // If requested range is same as caret location, we can just return it. + if (selection_range_.is_empty() && ui::Range(range) == selection_range_) { + if (actual_range) + *actual_range = range; + *rect = client->caret_rect_; + return true; + } + + const ui::Range request_range_in_composition = + ConvertCharacterRangeToCompositionRange(ui::Range(range)); + if (request_range_in_composition == ui::Range::InvalidRange()) + return false; + + // If firstRectForCharacterRange in WebFrame is failed in renderer, + // ImeCompositionRangeChanged will be sent with empty vector. + if (client->composition_bounds_.empty()) + return false; + + DCHECK_EQ(client->composition_bounds_.size(), + client->composition_range_.length()); + + ui::Range ui_actual_range; + *rect = GetFirstRectForCompositionRange(request_range_in_composition, + &ui_actual_range); + if (actual_range) { + *actual_range = ui::Range( + client->composition_range_.start() + ui_actual_range.start(), + client->composition_range_.start() + ui_actual_range.end()).ToNSRange(); + } + return true; +} diff --git a/cef3/libcef/browser/text_input_client_osr_mac.h b/cef3/libcef/browser/text_input_client_osr_mac.h new file mode 100644 index 000000000..3258dbf30 --- /dev/null +++ b/cef3/libcef/browser/text_input_client_osr_mac.h @@ -0,0 +1,89 @@ +// 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_TEXT_INPUT_CLIENT_OSR_MAC_H_ +#define CEF_LIBCEF_BROWSER_TEXT_INPUT_CLIENT_OSR_MAC_H_ +#pragma once + +#import +#include + +#include "libcef/browser/render_widget_host_view_osr.h" + +#include "base/memory/scoped_nsobject.h" +#include "base/string16.h" +#include "content/browser/renderer_host/render_widget_host_impl.h" +#include "content/common/edit_command.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebCompositionUnderline.h" + +// Implementation for the NSTextInputClient protocol used for enabling IME on +// mac when window rendering is disabled. + +@interface CefTextInputClientOSRMac : NSObject { + + @public + // 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. + ui::Range composition_range_; + std::vector composition_bounds_; + + // The current caret bounds. + gfx::Rect caret_rect_; + + @private + // Represents the input-method attributes supported by this object. + 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. + string16 textToBeInserted_; + + // Marked text which was generated by handling a key down event. + string16 markedText_; + + // Underline information of the |markedText_|. + std::vector underlines_; + + // 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_TEXT_INPUT_CLIENT_OSR_MAC_H_ diff --git a/cef3/libcef/browser/text_input_client_osr_mac.mm b/cef3/libcef/browser/text_input_client_osr_mac.mm new file mode 100644 index 000000000..e67f397e6 --- /dev/null +++ b/cef3/libcef/browser/text_input_client_osr_mac.mm @@ -0,0 +1,363 @@ +// 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/text_input_client_osr_mac.h" +#include "libcef/browser/browser_host_impl.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/view_messages.h" + +namespace { + +// Maximum number of characters we allow in a tooltip. +const size_t kMaxTooltipLength = 1024; + +// TODO(suzhe): Upstream this function. +WebKit::WebColor WebColorFromNSColor(NSColor *color) { + CGFloat r, g, b, a; + [color getRed:&r green:&g blue:&b alpha:&a]; + + return + std::max(0, std::min(static_cast(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) { + WebKit::WebColor color = SK_ColorBLACK; + if (NSColor *colorAttr = + [attrs objectForKey:NSUnderlineColorAttributeName]) { + color = WebColorFromNSColor( + [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]); + } + underlines->push_back(WebKit::WebCompositionUnderline( + range.location, NSMaxRange(range), color, [style intValue] > 1)); + } + i = range.location + range.length; + } +} + +} // 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_ ? 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(base::SysNSStringToUTF16(im_text)); + } else { + ui::Range replacement_range(replacementRange); + + renderWidgetHostView_->get_render_widget_host_impl()->ImeConfirmComposition( + base::SysNSStringToUTF16(im_text), replacement_range); + } + + // 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 (!StartsWithASCII(command, "insert", false)) + editCommands_.push_back(content::EditCommand(command, "")); + } else { + renderWidgetHostView_->get_render_widget_host_impl()->Send( + new ViewMsg_ExecuteEditCommand( + renderWidgetHostView_->get_render_widget_host_impl()-> + 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(WebKit::WebCompositionUnderline(0, length, + SK_ColorBLACK, false)); + } + + // If we are handling a key down event, then SetComposition() will be + // called in keyEvent: method. + // Input methods of Mac use setMarkedText calls with an empty text to cancel + // an ongoing composition. So, we should check whether or not the given text + // is empty to update the input method state. (Our input method backend can + // automatically cancels an ongoing composition when we send an empty text. + // So, it is OK to send an empty text to the renderer.) + if (!handlingKeyDown_) { + renderWidgetHostView_->get_render_widget_host_impl()->ImeSetComposition( + markedText_, underlines_, 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 ConfirmComposition() will be + // called in keyEvent: method. + if (!handlingKeyDown_) { + renderWidgetHostView_->get_render_widget_host_impl()-> + ImeConfirmComposition(); + } else { + unmarkTextCalled_ = YES; + } +} + +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range + actualRange:(NSRangePointer)actualRange { + if (actualRange) + *actualRange = range; + NSAttributedString* str = content::TextInputClientMac::GetInstance()-> + GetAttributedSubstringFromRange( + renderWidgetHostView_->GetRenderWidgetHost(), range); + return str; +} + +- (NSRect)firstViewRectForCharacterRange:(NSRange)theRange + actualRange:(NSRangePointer)actualRange { + NSRect rect; + gfx::Rect gfxRect; + ui::Range range(theRange); + ui::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_->get_browser_impl()->GetClient()->GetRenderHandler()-> + GetScreenPoint(renderWidgetHostView_->get_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_->get_browser_impl()->GetClient()->GetRenderHandler()-> + GetViewRect(renderWidgetHostView_->get_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(); + 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 ConfirmComposition(). + // Otherwise, if the text to be inserted only contains 1 character, then we + // can just send a keypress event which is fabricated by changing the type of + // the keydown event, so that we can retain all necessary informations, such + // 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 = WebKit::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_->get_render_widget_host_impl()->ImeConfirmComposition( + textToBeInserted_); + 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_->get_render_widget_host_impl()->ImeSetComposition( + markedText_, underlines_, selectedRange_.location, + NSMaxRange(selectedRange_)); + } else if (oldHasMarkedText_ && !hasMarkedText_ && !textInserted) { + if (unmarkTextCalled_) { + renderWidgetHostView_->get_render_widget_host_impl()-> + ImeConfirmComposition(); + } else { + renderWidgetHostView_->get_render_widget_host_impl()-> + ImeCancelComposition(); + } + } +} + +- (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. + NSInputManager *currentInputManager = [NSInputManager currentInputManager]; + [currentInputManager markedTextAbandoned:self]; + + hasMarkedText_ = NO; + // Should not call [self unmarkText] here, because it'll send unnecessary + // cancel composition IPC message to the renderer. +} + +@end diff --git a/cef3/libcef_dll/cpptoc/browser_host_cpptoc.cc b/cef3/libcef_dll/cpptoc/browser_host_cpptoc.cc index d4dbd5bab..9e212f18e 100644 --- a/cef3/libcef_dll/cpptoc/browser_host_cpptoc.cc +++ b/cef3/libcef_dll/cpptoc/browser_host_cpptoc.cc @@ -503,6 +503,48 @@ void CEF_CALLBACK browser_host_send_capture_lost_event( CefBrowserHostCppToC::Get(self)->SendCaptureLostEvent(); } +cef_text_input_context_t CEF_CALLBACK browser_host_get_nstext_input_context( + 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); +} + // CONSTRUCTOR - Do not edit by hand. @@ -540,6 +582,12 @@ CefBrowserHostCppToC::CefBrowserHostCppToC(CefBrowserHost* cls) struct_.struct_.send_focus_event = browser_host_send_focus_event; struct_.struct_.send_capture_lost_event = browser_host_send_capture_lost_event; + struct_.struct_.get_nstext_input_context = + browser_host_get_nstext_input_context; + struct_.struct_.handle_key_event_before_text_input_client = + browser_host_handle_key_event_before_text_input_client; + struct_.struct_.handle_key_event_after_text_input_client = + browser_host_handle_key_event_after_text_input_client; } #ifndef NDEBUG diff --git a/cef3/libcef_dll/ctocpp/browser_host_ctocpp.cc b/cef3/libcef_dll/ctocpp/browser_host_ctocpp.cc index b5a0fabbc..2d9b22ed5 100644 --- a/cef3/libcef_dll/ctocpp/browser_host_ctocpp.cc +++ b/cef3/libcef_dll/ctocpp/browser_host_ctocpp.cc @@ -387,6 +387,43 @@ void CefBrowserHostCToCpp::SendCaptureLostEvent() { struct_->send_capture_lost_event(struct_); } +CefTextInputContext CefBrowserHostCToCpp::GetNSTextInputContext() { + if (CEF_MEMBER_MISSING(struct_, get_nstext_input_context)) + return NULL; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + cef_text_input_context_t _retval = struct_->get_nstext_input_context(struct_); + + // Return type: simple + return _retval; +} + +void CefBrowserHostCToCpp::HandleKeyEventBeforeTextInputClient( + CefEventHandle keyEvent) { + if (CEF_MEMBER_MISSING(struct_, handle_key_event_before_text_input_client)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + struct_->handle_key_event_before_text_input_client(struct_, + keyEvent); +} + +void CefBrowserHostCToCpp::HandleKeyEventAfterTextInputClient( + CefEventHandle keyEvent) { + if (CEF_MEMBER_MISSING(struct_, handle_key_event_after_text_input_client)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + struct_->handle_key_event_after_text_input_client(struct_, + keyEvent); +} + #ifndef NDEBUG template<> long CefCToCpp)getBrowser { - return browser_provider_->GetBrowser(); + if (browser_provider_) + return browser_provider_->GetBrowser(); + return NULL; } - (void)setFrame:(NSRect)frameRect { @@ -434,41 +436,16 @@ void ClientOSRHandler::SetLoading(bool isLoading) { if (!browser) return; - CefKeyEvent keyEvent; - [self getKeyEvent:keyEvent forEvent:event]; + if ([event type] != NSFlagsChanged) { + browser->GetHost()->HandleKeyEventBeforeTextInputClient(event); - keyEvent.type = KEYEVENT_KEYDOWN; - browser->GetHost()->SendKeyEvent(keyEvent); + // 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 ([event modifierFlags] & (NSNumericPadKeyMask | NSFunctionKeyMask)) { - // Don't send a Char event for non-char keys like arrows, function keys and - // clear. - switch (keyEvent.native_key_code) { - case 81: // = - case 75: // / - case 67: // * - case 78: // - - case 69: // + - case 76: // Enter - case 65: // . - case 82: // 0 - case 83: // 1 - case 84: // 2 - case 85: // 3 - case 86: // 4 - case 87: // 5 - case 88: // 6 - case 89: // 7 - case 91: // 8 - case 92: // 9 - break; - default: - return; - } + browser->GetHost()->HandleKeyEventAfterTextInputClient(event); } - - keyEvent.type = KEYEVENT_CHAR; - browser->GetHost()->SendKeyEvent(keyEvent); } - (void)keyUp:(NSEvent *)event { @@ -611,6 +588,13 @@ void ClientOSRHandler::SetLoading(bool isLoading) { keyEvent.modifiers = [self getModifiersForEvent:event]; } +- (NSTextInputContext*)inputContext { + CefRefPtr browser = [self getBrowser]; + if (browser) + return browser->GetHost()->GetNSTextInputContext(); + return NULL; +} + - (void)getMouseEvent:(CefMouseEvent&)mouseEvent forEvent:(NSEvent*)event { NSPoint point = [self getClickPointForEvent:event]; mouseEvent.x = point.x; diff --git a/cef3/tools/cef_parser.py b/cef3/tools/cef_parser.py index a6a275e99..e8e8820b8 100644 --- a/cef3/tools/cef_parser.py +++ b/cef3/tools/cef_parser.py @@ -376,6 +376,7 @@ _simpletypes = { 'CefCursorHandle' : ['cef_cursor_handle_t', 'NULL'], 'CefEventHandle' : ['cef_event_handle_t', 'NULL'], 'CefWindowHandle' : ['cef_window_handle_t', 'NULL'], + 'CefTextInputContext' : ['cef_text_input_context_t' ,'NULL'], 'CefRect' : ['cef_rect_t', 'CefRect()'], 'CefThreadId' : ['cef_thread_id_t', 'TID_UI'], 'CefTime' : ['cef_time_t', 'CefTime()'],