Standardize IME callbacks for off-screen rendering (issue #1675)

This commit is contained in:
Marshall Greenblatt
2016-10-28 12:11:24 -04:00
parent e69de63b15
commit d6b17a8fb5
48 changed files with 1999 additions and 976 deletions

View File

@@ -1204,6 +1204,13 @@ void BrowserWindowOsrGtk::UpdateDragCursor(
CEF_REQUIRE_UI_THREAD();
}
void BrowserWindowOsrGtk::OnImeCompositionRangeChanged(
CefRefPtr<CefBrowser> 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_);

View File

@@ -76,6 +76,10 @@ class BrowserWindowOsrGtk : public BrowserWindow,
int x, int y) OVERRIDE;
void UpdateDragCursor(CefRefPtr<CefBrowser> browser,
CefRenderHandler::DragOperation operation) OVERRIDE;
void OnImeCompositionRangeChanged(
CefRefPtr<CefBrowser> browser,
const CefRange& selection_range,
const CefRenderHandler::RectList& character_bounds) OVERRIDE;
private:
// Create the GTK GlArea.

View File

@@ -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<CefBrowser> browser,
CefRenderHandler::DragOperation operation) OVERRIDE;
void OnImeCompositionRangeChanged(
CefRefPtr<CefBrowser> browser,
const CefRange& selection_range,
const CefRenderHandler::RectList& character_bounds) OVERRIDE;
private:
// Create the NSView.

View File

@@ -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<CefTextInputClientOSRMac*>([context client]);
}
} // namespace
@interface BrowserOpenGLView
: NSOpenGLView <NSDraggingSource, NSDraggingDestination> {
@@ -29,13 +41,16 @@
float device_scale_factor_;
// Drag and drop
// Drag and drop.
CefRefPtr<CefDragData> 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<CefBrowser> 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<CefBrowser> 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<CefBrowser> 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();

View File

@@ -135,4 +135,15 @@ void ClientHandlerOsr::UpdateDragCursor(CefRefPtr<CefBrowser> browser,
osr_delegate_->UpdateDragCursor(browser, operation);
}
void ClientHandlerOsr::OnImeCompositionRangeChanged(
CefRefPtr<CefBrowser> 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

View File

@@ -56,6 +56,10 @@ class ClientHandlerOsr : public ClientHandler,
virtual void UpdateDragCursor(
CefRefPtr<CefBrowser> browser,
CefRenderHandler::DragOperation operation) = 0;
virtual void OnImeCompositionRangeChanged(
CefRefPtr<CefBrowser> 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<CefBrowser> browser,
CefRenderHandler::DragOperation operation) OVERRIDE;
void OnImeCompositionRangeChanged(
CefRefPtr<CefBrowser> browser,
const CefRange& selection_range,
const CefRenderHandler::RectList& character_bounds) OVERRIDE;
private:
// Only accessed on the UI thread.

View File

@@ -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 <windowsx.h>
#include <msctf.h>
#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<char> 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<CefCompositionUnderline> &underlines) {
int clause_size = ::ImmGetCompositionString(imc, GCS_COMPCLAUSE, NULL, 0);
int clause_length = clause_size / sizeof(uint32);
if (clause_length) {
std::vector<uint32> 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<LANGID>(_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<int>(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<CefCompositionUnderline> &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<int>(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<wchar_t> 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<CefCompositionUnderline> &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<CefRect>& bounds) {
composition_range_ = selection_range;
composition_bounds_ = bounds;
MoveImeWindow();
}
} // namespace client

View File

@@ -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 <windows.h>
#include <vector>
#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<CefCompositionUnderline> &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<CefRect>& 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<CefCompositionUnderline>& 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<CefRect> 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_

View File

@@ -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<CefCompositionUnderline> 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<int>(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<CefBrowser> 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

View File

@@ -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<CefBrowser> browser,
CefRenderHandler::DragOperation operation) OVERRIDE;
void OnImeCompositionRangeChanged(
CefRefPtr<CefBrowser> 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<OsrImeHandlerWin> ime_handler_;
RECT client_rect_;
float device_scale_factor_;

View File

@@ -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 <Cocoa/Cocoa.h>
#include <vector>
#include <string>
#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<NSTextInputClient> {
@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<CefRect> 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<CefCompositionUnderline> underlines_;
// Replacement range information received from |setMarkedText:|.
CefRange setMarkedTextReplacementRange_;
CefRefPtr<CefBrowser> browser_;
}
@property(nonatomic, readonly) NSRange selectedRange;
@property(nonatomic) BOOL handlingKeyDown;
- (id)initWithBrowser:(CefRefPtr<CefBrowser>)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_

View File

@@ -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<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<CefCompositionUnderline>* 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<CefBrowser>)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

View File

@@ -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<CefCompositionUnderline> 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<CefCompositionUnderline> 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<CefBrowser> 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<CefBrowser> browser,
CefRefPtr<CefDragData> 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);