Mac: Add IME support with off-screen rendering (issue #973).

git-svn-id: https://chromiumembedded.googlecode.com/svn/trunk@1259 5089003a-bbd8-11dd-ad1f-f1f9622dbc98
This commit is contained in:
Marshall Greenblatt 2013-05-31 12:46:56 +00:00
parent 3f17635d3e
commit ac4c19159c
22 changed files with 942 additions and 34 deletions

View File

@ -1038,8 +1038,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.

View File

@ -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;

View File

@ -452,6 +452,27 @@ 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_

View File

@ -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;

View File

@ -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;

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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;

View File

@ -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.
// -----------------------------------------------------------------------------

View File

@ -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<CefBrowserHost> GetHost() OVERRIDE;

View File

@ -8,6 +8,10 @@
#import <Cocoa/Cocoa.h>
#import <CoreServices/CoreServices.h>
#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<CefRenderWidgetHostViewOSR*>(
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<CefRenderWidgetHostViewOSR*>(
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<CefRenderWidgetHostViewOSR*>(
GetWebContents()->GetRenderWidgetHostView());
rwhv->HandleKeyEventAfterTextInputClient(keyEvent);
}
bool CefBrowserHostImpl::PlatformViewText(const std::string& text) {
NOTIMPLEMENTED();
return false;

View File

@ -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<gfx::Rect>& character_bounds) {
}
#endif // !defined(OS_MACOSX)
void CefRenderWidgetHostViewOSR::DidUpdateBackingStore(
const gfx::Rect& scroll_rect,

View File

@ -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<gfx::Rect>& 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<CefBrowserHostImpl> get_browser_impl() const;
void set_browser_impl(CefRefPtr<CefBrowserHostImpl> 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);
};

View File

@ -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<CefTextInputClientOSRMac*>([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<gfx::Rect>& 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<gfx::Rect>& 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;
}

View File

@ -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 <Cocoa/Cocoa.h>
#include <vector>
#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<NSTextInputClient> {
@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<gfx::Rect> composition_bounds_;
// The current caret bounds.
gfx::Rect caret_rect_;
@private
// Represents the input-method attributes supported by this object.
scoped_nsobject<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.
string16 textToBeInserted_;
// Marked text which was generated by handling a key down event.
string16 markedText_;
// Underline information of the |markedText_|.
std::vector<WebKit::WebCompositionUnderline> 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_

View File

@ -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/input_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<int>(lroundf(255.0f * a)), 255)) << 24 |
std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 |
std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 |
std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255));
}
// Extract underline information from an attributed string. Mostly copied from
// third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
void ExtractUnderlines(NSAttributedString* string,
std::vector<WebKit::WebCompositionUnderline>* underlines) {
int length = [[string string] length];
int i = 0;
while (i < length) {
NSRange range;
NSDictionary* attrs = [string attributesAtIndex:i
longestEffectiveRange:&range
inRange:NSMakeRange(i, length - i)];
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 InputMsg_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

View File

@ -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

View File

@ -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<CefBrowserHostCToCpp, CefBrowserHost,

View File

@ -69,6 +69,11 @@ class CefBrowserHostCToCpp
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;
};
#endif // USING_CEF_SHARED

View File

@ -308,7 +308,9 @@ void ClientOSRHandler::SetLoading(bool isLoading) {
}
- (CefRefPtr<CefBrowser>)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<CefBrowser> 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;

View File

@ -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()'],