gecko-dev/widget/windows/IMMHandler.cpp
Srujana Peddinti b44e48bff4 Bug 1259660 - Moved mozilla::WidgetMosueEventBase::buttonType in MouseEvents.h to mozilla::MouseButton in EventForwards.h, and mozilla::WidgetMouseEventBase::buttonsFlag to mozilla::MouseButtonsFlag r=masayuki
Moved mozilla::WidgetMosueEventBase::buttonType in MouseEvents.h to mozilla::MouseButton in EventForwards.h, and mozilla::WidgetMouseEventBase::buttonsFlag to mozilla::MouseButtonsFlag so that any referer in header files do not need to include MouseEvents.h only for referring them. Instead, they just need to include EventForwards.h. Now when MouseEvents.h is changed, the rebuild speed becomes faster.

Differential Revision: https://phabricator.services.mozilla.com/D25325

--HG--
extra : moz-landing-system : lando
2019-04-21 20:13:34 +00:00

2694 lines
100 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sts=2 sw=2 et cin: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/Logging.h"
#include "IMMHandler.h"
#include "nsWindow.h"
#include "nsWindowDefs.h"
#include "WinIMEHandler.h"
#include "WinUtils.h"
#include "KeyboardLayout.h"
#include <algorithm>
#include "mozilla/CheckedInt.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/TextEvents.h"
#include "mozilla/WindowsVersion.h"
#ifndef IME_PROP_ACCEPT_WIDE_VKEY
# define IME_PROP_ACCEPT_WIDE_VKEY 0x20
#endif
//-------------------------------------------------------------------------
//
// from
// http://download.microsoft.com/download/6/0/9/60908e9e-d2c1-47db-98f6-216af76a235f/msime.h
// The document for this has been removed from MSDN...
//
//-------------------------------------------------------------------------
#define RWM_MOUSE TEXT("MSIMEMouseOperation")
#define IMEMOUSE_NONE 0x00 // no mouse button was pushed
#define IMEMOUSE_LDOWN 0x01
#define IMEMOUSE_RDOWN 0x02
#define IMEMOUSE_MDOWN 0x04
#define IMEMOUSE_WUP 0x10 // wheel up
#define IMEMOUSE_WDOWN 0x20 // wheel down
static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
static void HandleSeparator(nsACString& aDesc) {
if (!aDesc.IsEmpty()) {
aDesc.AppendLiteral(" | ");
}
}
class GetIMEGeneralPropertyName : public nsAutoCString {
public:
explicit GetIMEGeneralPropertyName(DWORD aFlags) {
if (!aFlags) {
AppendLiteral("no flags");
return;
}
if (aFlags & IME_PROP_AT_CARET) {
AppendLiteral("IME_PROP_AT_CARET");
}
if (aFlags & IME_PROP_SPECIAL_UI) {
HandleSeparator(*this);
AppendLiteral("IME_PROP_SPECIAL_UI");
}
if (aFlags & IME_PROP_CANDLIST_START_FROM_1) {
HandleSeparator(*this);
AppendLiteral("IME_PROP_CANDLIST_START_FROM_1");
}
if (aFlags & IME_PROP_UNICODE) {
HandleSeparator(*this);
AppendLiteral("IME_PROP_UNICODE");
}
if (aFlags & IME_PROP_COMPLETE_ON_UNSELECT) {
HandleSeparator(*this);
AppendLiteral("IME_PROP_COMPLETE_ON_UNSELECT");
}
if (aFlags & IME_PROP_ACCEPT_WIDE_VKEY) {
HandleSeparator(*this);
AppendLiteral("IME_PROP_ACCEPT_WIDE_VKEY");
}
}
virtual ~GetIMEGeneralPropertyName() {}
};
class GetIMEUIPropertyName : public nsAutoCString {
public:
explicit GetIMEUIPropertyName(DWORD aFlags) {
if (!aFlags) {
AppendLiteral("no flags");
return;
}
if (aFlags & UI_CAP_2700) {
AppendLiteral("UI_CAP_2700");
}
if (aFlags & UI_CAP_ROT90) {
HandleSeparator(*this);
AppendLiteral("UI_CAP_ROT90");
}
if (aFlags & UI_CAP_ROTANY) {
HandleSeparator(*this);
AppendLiteral("UI_CAP_ROTANY");
}
}
virtual ~GetIMEUIPropertyName() {}
};
class GetWritingModeName : public nsAutoCString {
public:
explicit GetWritingModeName(const WritingMode& aWritingMode) {
if (!aWritingMode.IsVertical()) {
AssignLiteral("Horizontal");
return;
}
if (aWritingMode.IsVerticalLR()) {
AssignLiteral("Vertical (LR)");
return;
}
AssignLiteral("Vertical (RL)");
}
virtual ~GetWritingModeName() {}
};
class GetReconvertStringLog : public nsAutoCString {
public:
explicit GetReconvertStringLog(RECONVERTSTRING* aReconv) {
AssignLiteral("{ dwSize=");
AppendInt(static_cast<uint32_t>(aReconv->dwSize));
AppendLiteral(", dwVersion=");
AppendInt(static_cast<uint32_t>(aReconv->dwVersion));
AppendLiteral(", dwStrLen=");
AppendInt(static_cast<uint32_t>(aReconv->dwStrLen));
AppendLiteral(", dwStrOffset=");
AppendInt(static_cast<uint32_t>(aReconv->dwStrOffset));
AppendLiteral(", dwCompStrLen=");
AppendInt(static_cast<uint32_t>(aReconv->dwCompStrLen));
AppendLiteral(", dwCompStrOffset=");
AppendInt(static_cast<uint32_t>(aReconv->dwCompStrOffset));
AppendLiteral(", dwTargetStrLen=");
AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrLen));
AppendLiteral(", dwTargetStrOffset=");
AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrOffset));
AppendLiteral(", result str=\"");
if (aReconv->dwStrLen) {
char16_t* strStart = reinterpret_cast<char16_t*>(
reinterpret_cast<char*>(aReconv) + aReconv->dwStrOffset);
nsDependentString str(strStart, aReconv->dwStrLen);
Append(NS_ConvertUTF16toUTF8(str));
}
AppendLiteral("\" }");
}
virtual ~GetReconvertStringLog() {}
};
namespace mozilla {
namespace widget {
static IMMHandler* gIMMHandler = nullptr;
LazyLogModule gIMMLog("nsIMM32HandlerWidgets");
/******************************************************************************
* IMEContext
******************************************************************************/
IMEContext::IMEContext(HWND aWnd) : mWnd(aWnd), mIMC(::ImmGetContext(aWnd)) {}
IMEContext::IMEContext(nsWindowBase* aWindowBase)
: mWnd(aWindowBase->GetWindowHandle()),
mIMC(::ImmGetContext(aWindowBase->GetWindowHandle())) {}
void IMEContext::Init(HWND aWnd) {
Clear();
mWnd = aWnd;
mIMC = ::ImmGetContext(mWnd);
}
void IMEContext::Init(nsWindowBase* aWindowBase) {
Init(aWindowBase->GetWindowHandle());
}
void IMEContext::Clear() {
if (mWnd && mIMC) {
::ImmReleaseContext(mWnd, mIMC);
}
mWnd = nullptr;
mIMC = nullptr;
}
/******************************************************************************
* IMMHandler
******************************************************************************/
static UINT sWM_MSIME_MOUSE = 0; // mouse message for MSIME 98/2000
WritingMode IMMHandler::sWritingModeOfCompositionFont;
nsString IMMHandler::sIMEName;
UINT IMMHandler::sCodePage = 0;
DWORD IMMHandler::sIMEProperty = 0;
DWORD IMMHandler::sIMEUIProperty = 0;
bool IMMHandler::sAssumeVerticalWritingModeNotSupported = false;
bool IMMHandler::sHasFocus = false;
#define IMPL_IS_IME_ACTIVE(aReadableName, aActualName) \
bool IMMHandler::Is##aReadableName##Active() { \
return sIMEName.Equals(aActualName); \
}
IMPL_IS_IME_ACTIVE(ATOK2006, u"ATOK 2006")
IMPL_IS_IME_ACTIVE(ATOK2007, u"ATOK 2007")
IMPL_IS_IME_ACTIVE(ATOK2008, u"ATOK 2008")
IMPL_IS_IME_ACTIVE(ATOK2009, u"ATOK 2009")
IMPL_IS_IME_ACTIVE(ATOK2010, u"ATOK 2010")
// NOTE: Even on Windows for en-US, the name of Google Japanese Input is
// written in Japanese.
IMPL_IS_IME_ACTIVE(GoogleJapaneseInput,
u"Google \x65E5\x672C\x8A9E\x5165\x529B "
u"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB")
IMPL_IS_IME_ACTIVE(Japanist2003, u"Japanist 2003")
#undef IMPL_IS_IME_ACTIVE
// static
bool IMMHandler::IsActiveIMEInBlockList() {
if (sIMEName.IsEmpty()) {
return false;
}
#ifdef _WIN64
// ATOK started to be TIP of TSF since 2011. Older than it, i.e., ATOK 2010
// and earlier have a lot of problems even for daily use. Perhaps, the
// reason is Win 8 has a lot of changes around IMM-IME support and TSF,
// and ATOK 2010 is released earlier than Win 8.
// ATOK 2006 crashes while converting a word with candidate window.
// ATOK 2007 doesn't paint and resize suggest window and candidate window
// correctly (showing white window or too big window).
// ATOK 2008 and ATOK 2009 crash when user just opens their open state.
// ATOK 2010 isn't installable newly on Win 7 or later, but we have a lot of
// crash reports.
if (IsWin8OrLater() &&
(IsATOK2006Active() || IsATOK2007Active() || IsATOK2008Active() ||
IsATOK2009Active() || IsATOK2010Active())) {
return true;
}
#endif // #ifdef _WIN64
return false;
}
// static
void IMMHandler::EnsureHandlerInstance() {
if (!gIMMHandler) {
gIMMHandler = new IMMHandler();
}
}
// static
void IMMHandler::Initialize() {
if (!sWM_MSIME_MOUSE) {
sWM_MSIME_MOUSE = ::RegisterWindowMessage(RWM_MOUSE);
}
sAssumeVerticalWritingModeNotSupported = Preferences::GetBool(
"intl.imm.vertical_writing.always_assume_not_supported", false);
InitKeyboardLayout(nullptr, ::GetKeyboardLayout(0));
}
// static
void IMMHandler::Terminate() {
if (!gIMMHandler) return;
delete gIMMHandler;
gIMMHandler = nullptr;
}
// static
bool IMMHandler::IsComposingOnOurEditor() {
return gIMMHandler && gIMMHandler->mIsComposing;
}
// static
bool IMMHandler::IsComposingOnPlugin() {
return gIMMHandler && gIMMHandler->mIsComposingOnPlugin;
}
// static
bool IMMHandler::IsComposingWindow(nsWindow* aWindow) {
return gIMMHandler && gIMMHandler->mComposingWindow == aWindow;
}
// static
bool IMMHandler::IsTopLevelWindowOfComposition(nsWindow* aWindow) {
if (!gIMMHandler || !gIMMHandler->mComposingWindow) {
return false;
}
HWND wnd = gIMMHandler->mComposingWindow->GetWindowHandle();
return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle();
}
// static
bool IMMHandler::ShouldDrawCompositionStringOurselves() {
// If current IME has special UI or its composition window should not
// positioned to caret position, we should now draw composition string
// ourselves.
return !(sIMEProperty & IME_PROP_SPECIAL_UI) &&
(sIMEProperty & IME_PROP_AT_CARET);
}
// static
bool IMMHandler::IsVerticalWritingSupported() {
// Even if IME claims that they support vertical writing mode but it may not
// support vertical writing mode for its candidate window.
if (sAssumeVerticalWritingModeNotSupported) {
return false;
}
// Google Japanese Input doesn't support vertical writing mode. We should
// return false if it's active IME.
if (IsGoogleJapaneseInputActive()) {
return false;
}
return !!(sIMEUIProperty & (UI_CAP_2700 | UI_CAP_ROT90 | UI_CAP_ROTANY));
}
// static
void IMMHandler::InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout) {
UINT IMENameLength = ::ImmGetDescriptionW(aKeyboardLayout, nullptr, 0);
if (IMENameLength) {
// Add room for the terminating null character
sIMEName.SetLength(++IMENameLength);
IMENameLength =
::ImmGetDescriptionW(aKeyboardLayout, sIMEName.get(), IMENameLength);
// Adjust the length to ignore the terminating null character
sIMEName.SetLength(IMENameLength);
} else {
sIMEName.Truncate();
}
WORD langID = LOWORD(aKeyboardLayout);
::GetLocaleInfoW(MAKELCID(langID, SORT_DEFAULT),
LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
(PWSTR)&sCodePage, sizeof(sCodePage) / sizeof(WCHAR));
sIMEProperty = ::ImmGetProperty(aKeyboardLayout, IGP_PROPERTY);
sIMEUIProperty = ::ImmGetProperty(aKeyboardLayout, IGP_UI);
// If active IME is a TIP of TSF, we cannot retrieve the name with IMM32 API.
// For hacking some bugs of some TIP, we should set an IME name from the
// pref.
if (sCodePage == 932 && sIMEName.IsEmpty()) {
Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as",
sIMEName);
}
// Whether the IME supports vertical writing mode might be changed or
// some IMEs may need specific font for their UI. Therefore, we should
// update composition font forcibly here.
if (aWindow) {
MaybeAdjustCompositionFont(aWindow, sWritingModeOfCompositionFont, true);
}
MOZ_LOG(gIMMLog, LogLevel::Info,
("InitKeyboardLayout, aKeyboardLayout=%08x (\"%s\"), sCodePage=%lu, "
"sIMEProperty=%s, sIMEUIProperty=%s",
aKeyboardLayout, NS_ConvertUTF16toUTF8(sIMEName).get(), sCodePage,
GetIMEGeneralPropertyName(sIMEProperty).get(),
GetIMEUIPropertyName(sIMEUIProperty).get()));
}
// static
UINT IMMHandler::GetKeyboardCodePage() { return sCodePage; }
// static
IMENotificationRequests IMMHandler::GetIMENotificationRequests() {
return IMENotificationRequests(
IMENotificationRequests::NOTIFY_POSITION_CHANGE |
IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR);
}
// used for checking the lParam of WM_IME_COMPOSITION
#define IS_COMPOSING_LPARAM(lParam) \
((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS))
#define IS_COMMITTING_LPARAM(lParam) ((lParam)&GCS_RESULTSTR)
// Some IMEs (e.g., the standard IME for Korean) don't have caret position,
// then, we should not set caret position to compositionchange event.
#define NO_IME_CARET -1
IMMHandler::IMMHandler()
: mComposingWindow(nullptr),
mCursorPosition(NO_IME_CARET),
mCompositionStart(0),
mIsComposing(false),
mIsComposingOnPlugin(false) {
MOZ_LOG(gIMMLog, LogLevel::Debug, ("IMMHandler is created"));
}
IMMHandler::~IMMHandler() {
if (mIsComposing) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("~IMMHandler, ERROR, the instance is still composing"));
}
MOZ_LOG(gIMMLog, LogLevel::Debug, ("IMMHandler is destroyed"));
}
nsresult IMMHandler::EnsureClauseArray(int32_t aCount) {
NS_ENSURE_ARG_MIN(aCount, 0);
mClauseArray.SetCapacity(aCount + 32);
return NS_OK;
}
nsresult IMMHandler::EnsureAttributeArray(int32_t aCount) {
NS_ENSURE_ARG_MIN(aCount, 0);
mAttributeArray.SetCapacity(aCount + 64);
return NS_OK;
}
// static
void IMMHandler::CommitComposition(nsWindow* aWindow, bool aForce) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("CommitComposition, aForce=%s, aWindow=%p, hWnd=%08x, "
"mComposingWindow=%p%s",
GetBoolName(aForce), aWindow, aWindow->GetWindowHandle(),
gIMMHandler ? gIMMHandler->mComposingWindow : nullptr,
gIMMHandler && gIMMHandler->mComposingWindow
? IsComposingOnOurEditor() ? " (composing on editor)"
: " (composing on plug-in)"
: ""));
if (!aForce && !IsComposingWindow(aWindow)) {
return;
}
IMEContext context(aWindow);
bool associated = context.AssociateDefaultContext();
MOZ_LOG(gIMMLog, LogLevel::Info,
("CommitComposition, associated=%s", GetBoolName(associated)));
if (context.IsValid()) {
::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0);
}
if (associated) {
context.Disassociate();
}
}
// static
void IMMHandler::CancelComposition(nsWindow* aWindow, bool aForce) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("CancelComposition, aForce=%s, aWindow=%p, hWnd=%08x, "
"mComposingWindow=%p%s",
GetBoolName(aForce), aWindow, aWindow->GetWindowHandle(),
gIMMHandler ? gIMMHandler->mComposingWindow : nullptr,
gIMMHandler && gIMMHandler->mComposingWindow
? IsComposingOnOurEditor() ? " (composing on editor)"
: " (composing on plug-in)"
: ""));
if (!aForce && !IsComposingWindow(aWindow)) {
return;
}
IMEContext context(aWindow);
bool associated = context.AssociateDefaultContext();
MOZ_LOG(gIMMLog, LogLevel::Info,
("CancelComposition, associated=%s", GetBoolName(associated)));
if (context.IsValid()) {
::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0);
}
if (associated) {
context.Disassociate();
}
}
// static
void IMMHandler::OnFocusChange(bool aFocus, nsWindow* aWindow) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnFocusChange(aFocus=%s, aWindow=%p), sHasFocus=%s, "
"IsComposingWindow(aWindow)=%s, aWindow->Destroyed()=%s",
GetBoolName(aFocus), aWindow, GetBoolName(sHasFocus),
GetBoolName(IsComposingWindow(aWindow)),
GetBoolName(aWindow->Destroyed())));
if (!aFocus) {
IMEHandler::MaybeDestroyNativeCaret();
if (IsComposingWindow(aWindow) && aWindow->Destroyed()) {
CancelComposition(aWindow);
}
}
if (gIMMHandler) {
gIMMHandler->mSelection.Clear();
}
sHasFocus = aFocus;
}
// static
void IMMHandler::OnUpdateComposition(nsWindow* aWindow) {
if (!gIMMHandler) {
return;
}
if (aWindow->PluginHasFocus()) {
return;
}
IMEContext context(aWindow);
gIMMHandler->SetIMERelatedWindowsPos(aWindow, context);
}
// static
void IMMHandler::OnSelectionChange(nsWindow* aWindow,
const IMENotification& aIMENotification,
bool aIsIMMActive) {
if (!aIMENotification.mSelectionChangeData.mCausedByComposition &&
aIsIMMActive) {
MaybeAdjustCompositionFont(
aWindow, aIMENotification.mSelectionChangeData.GetWritingMode());
}
// MaybeAdjustCompositionFont() may create gIMMHandler. So, check it
// after a call of MaybeAdjustCompositionFont().
if (gIMMHandler) {
gIMMHandler->mSelection.Update(aIMENotification);
}
}
// static
void IMMHandler::MaybeAdjustCompositionFont(nsWindow* aWindow,
const WritingMode& aWritingMode,
bool aForceUpdate) {
switch (sCodePage) {
case 932: // Japanese Shift-JIS
case 936: // Simlified Chinese GBK
case 949: // Korean
case 950: // Traditional Chinese Big5
EnsureHandlerInstance();
break;
default:
// If there is no instance of nsIMM32Hander, we shouldn't waste footprint.
if (!gIMMHandler) {
return;
}
}
// Like Navi-Bar of ATOK, some IMEs may require proper composition font even
// before sending WM_IME_STARTCOMPOSITION.
IMEContext context(aWindow);
gIMMHandler->AdjustCompositionFont(aWindow, context, aWritingMode,
aForceUpdate);
}
// static
bool IMMHandler::ProcessInputLangChangeMessage(nsWindow* aWindow, WPARAM wParam,
LPARAM lParam,
MSGResult& aResult) {
aResult.mResult = 0;
aResult.mConsumed = false;
// We don't need to create the instance of the handler here.
if (gIMMHandler) {
gIMMHandler->OnInputLangChange(aWindow, wParam, lParam, aResult);
}
InitKeyboardLayout(aWindow, reinterpret_cast<HKL>(lParam));
// We can release the instance here, because the instance may be never
// used. E.g., the new keyboard layout may not use IME, or it may use TSF.
Terminate();
// Don't return as "processed", the messages should be processed on nsWindow
// too.
return false;
}
// static
bool IMMHandler::ProcessMessage(nsWindow* aWindow, UINT msg, WPARAM& wParam,
LPARAM& lParam, MSGResult& aResult) {
// XXX We store the composing window in mComposingWindow. If IME messages are
// sent to different window, we should commit the old transaction. And also
// if the new window handle is not focused, probably, we should not start
// the composition, however, such case should not be, it's just bad scenario.
// When a plug-in has focus, we should dispatch the IME events to
// the plug-in at first.
if (aWindow->PluginHasFocus()) {
bool ret = false;
if (ProcessMessageForPlugin(aWindow, msg, wParam, lParam, ret, aResult)) {
return ret;
}
}
aResult.mResult = 0;
switch (msg) {
case WM_INPUTLANGCHANGE:
return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult);
case WM_IME_STARTCOMPOSITION:
EnsureHandlerInstance();
return gIMMHandler->OnIMEStartComposition(aWindow, aResult);
case WM_IME_COMPOSITION:
EnsureHandlerInstance();
return gIMMHandler->OnIMEComposition(aWindow, wParam, lParam, aResult);
case WM_IME_ENDCOMPOSITION:
EnsureHandlerInstance();
return gIMMHandler->OnIMEEndComposition(aWindow, aResult);
case WM_IME_CHAR:
return OnIMEChar(aWindow, wParam, lParam, aResult);
case WM_IME_NOTIFY:
return OnIMENotify(aWindow, wParam, lParam, aResult);
case WM_IME_REQUEST:
EnsureHandlerInstance();
return gIMMHandler->OnIMERequest(aWindow, wParam, lParam, aResult);
case WM_IME_SELECT:
return OnIMESelect(aWindow, wParam, lParam, aResult);
case WM_IME_SETCONTEXT:
return OnIMESetContext(aWindow, wParam, lParam, aResult);
case WM_KEYDOWN:
return OnKeyDownEvent(aWindow, wParam, lParam, aResult);
case WM_CHAR:
if (!gIMMHandler) {
return false;
}
return gIMMHandler->OnChar(aWindow, wParam, lParam, aResult);
default:
return false;
};
}
// static
bool IMMHandler::ProcessMessageForPlugin(nsWindow* aWindow, UINT msg,
WPARAM& wParam, LPARAM& lParam,
bool& aRet, MSGResult& aResult) {
aResult.mResult = 0;
aResult.mConsumed = false;
switch (msg) {
case WM_INPUTLANGCHANGEREQUEST:
case WM_INPUTLANGCHANGE:
aWindow->DispatchPluginEvent(msg, wParam, lParam, false);
aRet = ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult);
return true;
case WM_IME_CHAR:
EnsureHandlerInstance();
aRet = gIMMHandler->OnIMECharOnPlugin(aWindow, wParam, lParam, aResult);
return true;
case WM_IME_SETCONTEXT:
aRet = OnIMESetContextOnPlugin(aWindow, wParam, lParam, aResult);
return true;
case WM_CHAR:
if (!gIMMHandler) {
return true;
}
aRet = gIMMHandler->OnCharOnPlugin(aWindow, wParam, lParam, aResult);
return true;
case WM_IME_COMPOSITIONFULL:
case WM_IME_CONTROL:
case WM_IME_KEYDOWN:
case WM_IME_KEYUP:
case WM_IME_SELECT:
aResult.mConsumed =
aWindow->DispatchPluginEvent(msg, wParam, lParam, false);
aRet = true;
return true;
case WM_IME_REQUEST:
// Our plugin implementation is alwasy OOP. So WM_IME_REQUEST doesn't
// allow that parameter is pointer and shouldn't handle into Gecko.
aRet = false;
return true;
}
return false;
}
/****************************************************************************
* message handlers
****************************************************************************/
void IMMHandler::OnInputLangChange(nsWindow* aWindow, WPARAM wParam,
LPARAM lParam, MSGResult& aResult) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnInputLangChange, hWnd=%08x, wParam=%08x, lParam=%08x",
aWindow->GetWindowHandle(), wParam, lParam));
aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
NS_ASSERTION(!mIsComposing, "ResetInputState failed");
if (mIsComposing) {
HandleEndComposition(aWindow);
}
aResult.mConsumed = false;
}
bool IMMHandler::OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMEStartComposition, hWnd=%08x, mIsComposing=%s",
aWindow->GetWindowHandle(), GetBoolName(mIsComposing)));
aResult.mConsumed = ShouldDrawCompositionStringOurselves();
if (mIsComposing) {
NS_WARNING("Composition has been already started");
return true;
}
IMEContext context(aWindow);
HandleStartComposition(aWindow, context);
return true;
}
bool IMMHandler::OnIMEComposition(nsWindow* aWindow, WPARAM wParam,
LPARAM lParam, MSGResult& aResult) {
MOZ_LOG(
gIMMLog, LogLevel::Info,
("OnIMEComposition, hWnd=%08x, lParam=%08x, mIsComposing=%s, "
"GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, "
"GCS_CURSORPOS=%s,",
aWindow->GetWindowHandle(), lParam, GetBoolName(mIsComposing),
GetBoolName(lParam & GCS_RESULTSTR), GetBoolName(lParam & GCS_COMPSTR),
GetBoolName(lParam & GCS_COMPATTR), GetBoolName(lParam & GCS_COMPCLAUSE),
GetBoolName(lParam & GCS_CURSORPOS)));
IMEContext context(aWindow);
aResult.mConsumed = HandleComposition(aWindow, context, lParam);
return true;
}
bool IMMHandler::OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMEEndComposition, hWnd=%08x, mIsComposing=%s",
aWindow->GetWindowHandle(), GetBoolName(mIsComposing)));
aResult.mConsumed = ShouldDrawCompositionStringOurselves();
if (!mIsComposing) {
return true;
}
// Korean IME posts WM_IME_ENDCOMPOSITION first when we hit space during
// composition. Then, we should ignore the message and commit the composition
// string at following WM_IME_COMPOSITION.
MSG compositionMsg;
if (WinUtils::PeekMessage(&compositionMsg, aWindow->GetWindowHandle(),
WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION,
PM_NOREMOVE) &&
compositionMsg.message == WM_IME_COMPOSITION &&
IS_COMMITTING_LPARAM(compositionMsg.lParam)) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMEEndComposition, WM_IME_ENDCOMPOSITION is followed by "
"WM_IME_COMPOSITION, ignoring the message..."));
return true;
}
// Otherwise, e.g., ChangJie doesn't post WM_IME_COMPOSITION before
// WM_IME_ENDCOMPOSITION when composition string becomes empty.
// Then, we should dispatch a compositionupdate event, a compositionchange
// event and a compositionend event.
// XXX Shouldn't we dispatch the compositionchange event with actual or
// latest composition string?
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMEEndComposition, mCompositionString=\"%s\"%s",
NS_ConvertUTF16toUTF8(mCompositionString).get(),
mCompositionString.IsEmpty() ? "" : ", but canceling it..."));
HandleEndComposition(aWindow, &EmptyString());
return true;
}
// static
bool IMMHandler::OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
MSGResult& aResult) {
MOZ_LOG(
gIMMLog, LogLevel::Info,
("OnIMEChar, hWnd=%08x, char=%08x", aWindow->GetWindowHandle(), wParam));
// We don't need to fire any compositionchange events from here. This method
// will be called when the composition string of the current IME is not drawn
// by us and some characters are committed. In that case, the committed
// string was processed in nsWindow::OnIMEComposition already.
// We need to consume the message so that Windows don't send two WM_CHAR msgs
aResult.mConsumed = true;
return true;
}
// static
bool IMMHandler::OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMECompositionFull, hWnd=%08x", aWindow->GetWindowHandle()));
// not implement yet
aResult.mConsumed = false;
return true;
}
// static
bool IMMHandler::OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
MSGResult& aResult) {
switch (wParam) {
case IMN_CHANGECANDIDATE:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_CHANGECANDIDATE, lParam=%08x",
aWindow->GetWindowHandle(), lParam));
break;
case IMN_CLOSECANDIDATE:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_CLOSECANDIDATE, lParam=%08x",
aWindow->GetWindowHandle(), lParam));
break;
case IMN_CLOSESTATUSWINDOW:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_CLOSESTATUSWINDOW",
aWindow->GetWindowHandle()));
break;
case IMN_GUIDELINE:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_GUIDELINE",
aWindow->GetWindowHandle()));
break;
case IMN_OPENCANDIDATE:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_OPENCANDIDATE, lParam=%08x",
aWindow->GetWindowHandle(), lParam));
break;
case IMN_OPENSTATUSWINDOW:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_OPENSTATUSWINDOW",
aWindow->GetWindowHandle()));
break;
case IMN_SETCANDIDATEPOS:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_SETCANDIDATEPOS, lParam=%08x",
aWindow->GetWindowHandle(), lParam));
break;
case IMN_SETCOMPOSITIONFONT:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONFONT",
aWindow->GetWindowHandle()));
break;
case IMN_SETCOMPOSITIONWINDOW:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONWINDOW",
aWindow->GetWindowHandle()));
break;
case IMN_SETCONVERSIONMODE:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_SETCONVERSIONMODE",
aWindow->GetWindowHandle()));
break;
case IMN_SETOPENSTATUS:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_SETOPENSTATUS",
aWindow->GetWindowHandle()));
break;
case IMN_SETSENTENCEMODE:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_SETSENTENCEMODE",
aWindow->GetWindowHandle()));
break;
case IMN_SETSTATUSWINDOWPOS:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_SETSTATUSWINDOWPOS",
aWindow->GetWindowHandle()));
break;
case IMN_PRIVATE:
MOZ_LOG(
gIMMLog, LogLevel::Info,
("OnIMENotify, hWnd=%08x, IMN_PRIVATE", aWindow->GetWindowHandle()));
break;
}
// not implement yet
aResult.mConsumed = false;
return true;
}
bool IMMHandler::OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
MSGResult& aResult) {
switch (wParam) {
case IMR_RECONVERTSTRING:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMERequest, hWnd=%08x, IMR_RECONVERTSTRING",
aWindow->GetWindowHandle()));
aResult.mConsumed = HandleReconvert(aWindow, lParam, &aResult.mResult);
return true;
case IMR_QUERYCHARPOSITION:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMERequest, hWnd=%08x, IMR_QUERYCHARPOSITION",
aWindow->GetWindowHandle()));
aResult.mConsumed =
HandleQueryCharPosition(aWindow, lParam, &aResult.mResult);
return true;
case IMR_DOCUMENTFEED:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMERequest, hWnd=%08x, IMR_DOCUMENTFEED",
aWindow->GetWindowHandle()));
aResult.mConsumed = HandleDocumentFeed(aWindow, lParam, &aResult.mResult);
return true;
default:
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMERequest, hWnd=%08x, wParam=%08x",
aWindow->GetWindowHandle(), wParam));
aResult.mConsumed = false;
return true;
}
}
// static
bool IMMHandler::OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
MSGResult& aResult) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMESelect, hWnd=%08x, wParam=%08x, lParam=%08x",
aWindow->GetWindowHandle(), wParam, lParam));
// not implement yet
aResult.mConsumed = false;
return true;
}
// static
bool IMMHandler::OnIMESetContext(nsWindow* aWindow, WPARAM wParam,
LPARAM lParam, MSGResult& aResult) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMESetContext, hWnd=%08x, %s, lParam=%08x",
aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam));
aResult.mConsumed = false;
// NOTE: If the aWindow is top level window of the composing window because
// when a window on deactive window gets focus, WM_IME_SETCONTEXT (wParam is
// TRUE) is sent to the top level window first. After that,
// WM_IME_SETCONTEXT (wParam is FALSE) is sent to the top level window.
// Finally, WM_IME_SETCONTEXT (wParam is TRUE) is sent to the focused window.
// The top level window never becomes composing window, so, we can ignore
// the WM_IME_SETCONTEXT on the top level window.
if (IsTopLevelWindowOfComposition(aWindow)) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMESetContext, hWnd=%08x is top level window"));
return true;
}
// When IME context is activating on another window,
// we should commit the old composition on the old window.
bool cancelComposition = false;
if (wParam && gIMMHandler) {
cancelComposition = gIMMHandler->CommitCompositionOnPreviousWindow(aWindow);
}
if (wParam && (lParam & ISC_SHOWUICOMPOSITIONWINDOW) &&
ShouldDrawCompositionStringOurselves()) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMESetContext, ISC_SHOWUICOMPOSITIONWINDOW is removed"));
lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
}
// We should sent WM_IME_SETCONTEXT to the DefWndProc here because the
// ancestor windows shouldn't receive this message. If they receive the
// message, we cannot know whether which window is the target of the message.
aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(),
WM_IME_SETCONTEXT, wParam, lParam);
// Cancel composition on the new window if we committed our composition on
// another window.
if (cancelComposition) {
CancelComposition(aWindow, true);
}
aResult.mConsumed = true;
return true;
}
bool IMMHandler::OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
MSGResult& aResult) {
// The return value must be same as aResult.mConsumed because only when we
// consume the message, the caller shouldn't do anything anymore but
// otherwise, the caller should handle the message.
aResult.mConsumed = false;
if (IsIMECharRecordsEmpty()) {
return aResult.mConsumed;
}
WPARAM recWParam;
LPARAM recLParam;
DequeueIMECharRecords(recWParam, recLParam);
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnChar, aWindow=%p, wParam=%08x, lParam=%08x, "
"recorded: wParam=%08x, lParam=%08x",
aWindow->GetWindowHandle(), wParam, lParam, recWParam, recLParam));
// If an unexpected char message comes, we should reset the records,
// of course, this shouldn't happen.
if (recWParam != wParam || recLParam != lParam) {
ResetIMECharRecords();
return aResult.mConsumed;
}
// Eat the char message which is caused by WM_IME_CHAR because we should
// have processed the IME messages, so, this message could be come from
// a windowless plug-in.
aResult.mConsumed = true;
return aResult.mConsumed;
}
/****************************************************************************
* message handlers for plug-in
****************************************************************************/
void IMMHandler::OnIMEStartCompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
LPARAM lParam) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMEStartCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s",
aWindow->GetWindowHandle(), GetBoolName(mIsComposingOnPlugin)));
mIsComposingOnPlugin = true;
mDispatcher = GetTextEventDispatcherFor(aWindow);
mComposingWindow = aWindow;
IMEContext context(aWindow);
SetIMERelatedWindowsPosOnPlugin(aWindow, context);
// On widnowless plugin, we should assume that the focused editor is always
// in horizontal writing mode.
AdjustCompositionFont(aWindow, context, WritingMode());
}
void IMMHandler::OnIMECompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
LPARAM lParam) {
MOZ_LOG(
gIMMLog, LogLevel::Info,
("OnIMECompositionOnPlugin, hWnd=%08x, lParam=%08x, "
"mIsComposingOnPlugin=%s, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, "
"GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s",
aWindow->GetWindowHandle(), lParam, GetBoolName(mIsComposingOnPlugin),
GetBoolName(lParam & GCS_RESULTSTR), GetBoolName(lParam & GCS_COMPSTR),
GetBoolName(lParam & GCS_COMPATTR), GetBoolName(lParam & GCS_COMPCLAUSE),
GetBoolName(lParam & GCS_CURSORPOS)));
// We should end composition if there is a committed string.
if (IS_COMMITTING_LPARAM(lParam)) {
mIsComposingOnPlugin = false;
mComposingWindow = nullptr;
mDispatcher = nullptr;
return;
}
// Continue composition if there is still a string being composed.
if (IS_COMPOSING_LPARAM(lParam)) {
mIsComposingOnPlugin = true;
mDispatcher = GetTextEventDispatcherFor(aWindow);
mComposingWindow = aWindow;
IMEContext context(aWindow);
SetIMERelatedWindowsPosOnPlugin(aWindow, context);
}
}
void IMMHandler::OnIMEEndCompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
LPARAM lParam) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMEEndCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s",
aWindow->GetWindowHandle(), GetBoolName(mIsComposingOnPlugin)));
mIsComposingOnPlugin = false;
mComposingWindow = nullptr;
mDispatcher = nullptr;
IMEHandler::MaybeDestroyNativeCaret();
}
bool IMMHandler::OnIMECharOnPlugin(nsWindow* aWindow, WPARAM wParam,
LPARAM lParam, MSGResult& aResult) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMECharOnPlugin, hWnd=%08x, char=%08x, scancode=%08x",
aWindow->GetWindowHandle(), wParam, lParam));
aResult.mConsumed =
aWindow->DispatchPluginEvent(WM_IME_CHAR, wParam, lParam, true);
if (!aResult.mConsumed) {
// Record the WM_CHAR messages which are going to be coming.
EnsureHandlerInstance();
EnqueueIMECharRecords(wParam, lParam);
}
return true;
}
// static
bool IMMHandler::OnIMESetContextOnPlugin(nsWindow* aWindow, WPARAM wParam,
LPARAM lParam, MSGResult& aResult) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnIMESetContextOnPlugin, hWnd=%08x, %s, lParam=%08x",
aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam));
// If the IME context becomes active on a plug-in, we should commit
// our composition. And also we should cancel the composition on new
// window. Note that if IsTopLevelWindowOfComposition(aWindow) returns
// true, we should ignore the message here, see the comment in
// OnIMESetContext() for the detail.
if (wParam && gIMMHandler && !IsTopLevelWindowOfComposition(aWindow)) {
if (gIMMHandler->CommitCompositionOnPreviousWindow(aWindow)) {
CancelComposition(aWindow);
}
}
// Dispatch message to the plug-in.
// XXX When a windowless plug-in gets focus, we should send
// WM_IME_SETCONTEXT
aWindow->DispatchPluginEvent(WM_IME_SETCONTEXT, wParam, lParam, false);
// We should send WM_IME_SETCONTEXT to the DefWndProc here. It shouldn't
// be received on ancestor windows, see OnIMESetContext() for the detail.
aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(),
WM_IME_SETCONTEXT, wParam, lParam);
// Don't synchronously dispatch the pending events when we receive
// WM_IME_SETCONTEXT because we get it during plugin destruction.
// (bug 491848)
aResult.mConsumed = true;
return true;
}
bool IMMHandler::OnCharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
MSGResult& aResult) {
NS_WARNING("OnCharOnPlugin");
if (mIsComposing) {
aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
return true;
}
// We should never consume char message on windowless plugin.
aResult.mConsumed = false;
if (IsIMECharRecordsEmpty()) {
return false;
}
WPARAM recWParam;
LPARAM recLParam;
DequeueIMECharRecords(recWParam, recLParam);
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnCharOnPlugin, aWindow=%p, wParam=%08x, lParam=%08x, "
"recorded: wParam=%08x, lParam=%08x",
aWindow->GetWindowHandle(), wParam, lParam, recWParam, recLParam));
// If an unexpected char message comes, we should reset the records,
// of course, this shouldn't happen.
if (recWParam != wParam || recLParam != lParam) {
ResetIMECharRecords();
}
// WM_CHAR on plug-in is always handled by nsWindow.
return false;
}
/****************************************************************************
* others
****************************************************************************/
TextEventDispatcher* IMMHandler::GetTextEventDispatcherFor(nsWindow* aWindow) {
return aWindow == mComposingWindow && mDispatcher
? mDispatcher.get()
: aWindow->GetTextEventDispatcher();
}
void IMMHandler::HandleStartComposition(nsWindow* aWindow,
const IMEContext& aContext) {
MOZ_ASSERT(!mIsComposing,
"HandleStartComposition is called but mIsComposing is TRUE");
Selection& selection = GetSelection();
if (!selection.EnsureValidSelection(aWindow)) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleStartComposition, FAILED, due to "
"Selection::EnsureValidSelection() failure"));
return;
}
AdjustCompositionFont(aWindow, aContext, selection.mWritingMode);
mCompositionStart = selection.mOffset;
mCursorPosition = NO_IME_CARET;
RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
nsresult rv = dispatcher->BeginNativeInputTransaction();
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleStartComposition, FAILED due to "
"TextEventDispatcher::BeginNativeInputTransaction() failure"));
return;
}
WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
nsEventStatus status;
rv = dispatcher->StartComposition(status, &eventTime);
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleStartComposition, FAILED, due to "
"TextEventDispatcher::StartComposition() failure"));
return;
}
mIsComposing = true;
mComposingWindow = aWindow;
mDispatcher = dispatcher;
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleStartComposition, START composition, mCompositionStart=%ld",
mCompositionStart));
}
bool IMMHandler::HandleComposition(nsWindow* aWindow,
const IMEContext& aContext, LPARAM lParam) {
// for bug #60050
// MS-IME 95/97/98/2000 may send WM_IME_COMPOSITION with non-conversion
// mode before it send WM_IME_STARTCOMPOSITION.
// However, ATOK sends a WM_IME_COMPOSITION before WM_IME_STARTCOMPOSITION,
// and if we access ATOK via some APIs, ATOK will sometimes fail to
// initialize its state. If WM_IME_STARTCOMPOSITION is already in the
// message queue, we should ignore the strange WM_IME_COMPOSITION message and
// skip to the next. So, we should look for next composition message
// (WM_IME_STARTCOMPOSITION or WM_IME_ENDCOMPOSITION or WM_IME_COMPOSITION),
// and if it's WM_IME_STARTCOMPOSITION, and one more next composition message
// is WM_IME_COMPOSITION, current IME is ATOK, probably. Otherwise, we
// should start composition forcibly.
if (!mIsComposing) {
MSG msg1, msg2;
HWND wnd = aWindow->GetWindowHandle();
if (WinUtils::PeekMessage(&msg1, wnd, WM_IME_STARTCOMPOSITION,
WM_IME_COMPOSITION, PM_NOREMOVE) &&
msg1.message == WM_IME_STARTCOMPOSITION &&
WinUtils::PeekMessage(&msg2, wnd, WM_IME_ENDCOMPOSITION,
WM_IME_COMPOSITION, PM_NOREMOVE) &&
msg2.message == WM_IME_COMPOSITION) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleComposition, Ignores due to find a "
"WM_IME_STARTCOMPOSITION"));
return ShouldDrawCompositionStringOurselves();
}
}
bool startCompositionMessageHasBeenSent = mIsComposing;
//
// This catches a fixed result
//
if (IS_COMMITTING_LPARAM(lParam)) {
if (!mIsComposing) {
HandleStartComposition(aWindow, aContext);
}
GetCompositionString(aContext, GCS_RESULTSTR, mCompositionString);
MOZ_LOG(gIMMLog, LogLevel::Info, ("HandleComposition, GCS_RESULTSTR"));
HandleEndComposition(aWindow, &mCompositionString);
if (!IS_COMPOSING_LPARAM(lParam)) {
return ShouldDrawCompositionStringOurselves();
}
}
//
// This provides us with a composition string
//
if (!mIsComposing) {
HandleStartComposition(aWindow, aContext);
}
//--------------------------------------------------------
// 1. Get GCS_COMPSTR
//--------------------------------------------------------
MOZ_LOG(gIMMLog, LogLevel::Info, ("HandleComposition, GCS_COMPSTR"));
nsAutoString previousCompositionString(mCompositionString);
GetCompositionString(aContext, GCS_COMPSTR, mCompositionString);
if (!IS_COMPOSING_LPARAM(lParam)) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleComposition, lParam doesn't indicate composing, "
"mCompositionString=\"%s\", previousCompositionString=\"%s\"",
NS_ConvertUTF16toUTF8(mCompositionString).get(),
NS_ConvertUTF16toUTF8(previousCompositionString).get()));
// If composition string isn't changed, we can trust the lParam.
// So, we need to do nothing.
if (previousCompositionString == mCompositionString) {
return ShouldDrawCompositionStringOurselves();
}
// IME may send WM_IME_COMPOSITION without composing lParam values
// when composition string becomes empty (e.g., using Backspace key).
// If composition string is empty, we should dispatch a compositionchange
// event with empty string and clear the clause information.
if (mCompositionString.IsEmpty()) {
mClauseArray.Clear();
mAttributeArray.Clear();
mCursorPosition = 0;
DispatchCompositionChangeEvent(aWindow, aContext);
return ShouldDrawCompositionStringOurselves();
}
// Otherwise, we cannot trust the lParam value. We might need to
// dispatch compositionchange event with the latest composition string
// information.
}
// See https://bugzilla.mozilla.org/show_bug.cgi?id=296339
if (mCompositionString.IsEmpty() && !startCompositionMessageHasBeenSent) {
// In this case, maybe, the sender is MSPinYin. That sends *only*
// WM_IME_COMPOSITION with GCS_COMP* and GCS_RESULT* when
// user inputted the Chinese full stop. So, that doesn't send
// WM_IME_STARTCOMPOSITION and WM_IME_ENDCOMPOSITION.
// If WM_IME_STARTCOMPOSITION was not sent and the composition
// string is null (it indicates the composition transaction ended),
// WM_IME_ENDCOMPOSITION may not be sent. If so, we cannot run
// HandleEndComposition() in other place.
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleComposition, Aborting GCS_COMPSTR"));
HandleEndComposition(aWindow);
return IS_COMMITTING_LPARAM(lParam);
}
//--------------------------------------------------------
// 2. Get GCS_COMPCLAUSE
//--------------------------------------------------------
long clauseArrayLength =
::ImmGetCompositionStringW(aContext.get(), GCS_COMPCLAUSE, nullptr, 0);
clauseArrayLength /= sizeof(uint32_t);
if (clauseArrayLength > 0) {
nsresult rv = EnsureClauseArray(clauseArrayLength);
NS_ENSURE_SUCCESS(rv, false);
// Intelligent ABC IME (Simplified Chinese IME, the code page is 936)
// will crash in ImmGetCompositionStringW for GCS_COMPCLAUSE (bug 424663).
// See comment 35 of the bug for the detail. Therefore, we should use A
// API for it, however, we should not kill Unicode support on all IMEs.
bool useA_API = !(sIMEProperty & IME_PROP_UNICODE);
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleComposition, GCS_COMPCLAUSE, useA_API=%s",
useA_API ? "TRUE" : "FALSE"));
long clauseArrayLength2 =
useA_API ? ::ImmGetCompositionStringA(
aContext.get(), GCS_COMPCLAUSE, mClauseArray.Elements(),
mClauseArray.Capacity() * sizeof(uint32_t))
: ::ImmGetCompositionStringW(
aContext.get(), GCS_COMPCLAUSE, mClauseArray.Elements(),
mClauseArray.Capacity() * sizeof(uint32_t));
clauseArrayLength2 /= sizeof(uint32_t);
if (clauseArrayLength != clauseArrayLength2) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleComposition, GCS_COMPCLAUSE, clauseArrayLength=%ld but "
"clauseArrayLength2=%ld",
clauseArrayLength, clauseArrayLength2));
if (clauseArrayLength > clauseArrayLength2)
clauseArrayLength = clauseArrayLength2;
}
if (useA_API && clauseArrayLength > 0) {
// Convert each values of sIMECompClauseArray. The values mean offset of
// the clauses in ANSI string. But we need the values in Unicode string.
nsAutoCString compANSIStr;
if (ConvertToANSIString(mCompositionString, GetKeyboardCodePage(),
compANSIStr)) {
uint32_t maxlen = compANSIStr.Length();
mClauseArray.SetLength(clauseArrayLength);
mClauseArray[0] = 0; // first value must be 0
for (int32_t i = 1; i < clauseArrayLength; i++) {
uint32_t len = std::min(mClauseArray[i], maxlen);
mClauseArray[i] =
::MultiByteToWideChar(GetKeyboardCodePage(), MB_PRECOMPOSED,
(LPCSTR)compANSIStr.get(), len, nullptr, 0);
}
}
}
}
// compClauseArrayLength may be negative. I.e., ImmGetCompositionStringW
// may return an error code.
mClauseArray.SetLength(std::max<long>(0, clauseArrayLength));
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleComposition, GCS_COMPCLAUSE, mClauseLength=%ld",
mClauseArray.Length()));
//--------------------------------------------------------
// 3. Get GCS_COMPATTR
//--------------------------------------------------------
// This provides us with the attribute string necessary
// for doing hiliting
long attrArrayLength =
::ImmGetCompositionStringW(aContext.get(), GCS_COMPATTR, nullptr, 0);
attrArrayLength /= sizeof(uint8_t);
if (attrArrayLength > 0) {
nsresult rv = EnsureAttributeArray(attrArrayLength);
NS_ENSURE_SUCCESS(rv, false);
attrArrayLength = ::ImmGetCompositionStringW(
aContext.get(), GCS_COMPATTR, mAttributeArray.Elements(),
mAttributeArray.Capacity() * sizeof(uint8_t));
}
// attrStrLen may be negative. I.e., ImmGetCompositionStringW may return an
// error code.
mAttributeArray.SetLength(std::max<long>(0, attrArrayLength));
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleComposition, GCS_COMPATTR, mAttributeLength=%ld",
mAttributeArray.Length()));
//--------------------------------------------------------
// 4. Get GCS_CURSOPOS
//--------------------------------------------------------
// Some IMEs (e.g., the standard IME for Korean) don't have caret position.
if (lParam & GCS_CURSORPOS) {
mCursorPosition =
::ImmGetCompositionStringW(aContext.get(), GCS_CURSORPOS, nullptr, 0);
if (mCursorPosition < 0) {
mCursorPosition = NO_IME_CARET; // The result is error
}
} else {
mCursorPosition = NO_IME_CARET;
}
NS_ASSERTION(mCursorPosition <= (long)mCompositionString.Length(),
"illegal pos");
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleComposition, GCS_CURSORPOS, mCursorPosition=%d",
mCursorPosition));
//--------------------------------------------------------
// 5. Send the compositionchange event
//--------------------------------------------------------
DispatchCompositionChangeEvent(aWindow, aContext);
return ShouldDrawCompositionStringOurselves();
}
void IMMHandler::HandleEndComposition(nsWindow* aWindow,
const nsAString* aCommitString) {
MOZ_ASSERT(mIsComposing,
"HandleEndComposition is called but mIsComposing is FALSE");
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleEndComposition(aWindow=0x%p, aCommitString=0x%p (\"%s\"))",
aWindow, aCommitString,
aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : ""));
IMEHandler::MaybeDestroyNativeCaret();
RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
nsresult rv = dispatcher->BeginNativeInputTransaction();
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleEndComposition, FAILED due to "
"TextEventDispatcher::BeginNativeInputTransaction() failure"));
return;
}
WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
nsEventStatus status;
rv = dispatcher->CommitComposition(status, aCommitString, &eventTime);
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleStartComposition, FAILED, due to "
"TextEventDispatcher::CommitComposition() failure"));
return;
}
mIsComposing = false;
// XXX aWindow and mComposingWindow are always same??
mComposingWindow = nullptr;
mDispatcher = nullptr;
}
bool IMMHandler::HandleReconvert(nsWindow* aWindow, LPARAM lParam,
LRESULT* oResult) {
*oResult = 0;
RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
Selection& selection = GetSelection();
if (!selection.EnsureValidSelection(aWindow)) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleReconvert, FAILED, due to "
"Selection::EnsureValidSelection() failure"));
return false;
}
uint32_t len = selection.Length();
uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
if (!pReconv) {
// Return need size to reconvert.
if (len == 0) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleReconvert, There are not selected text"));
return false;
}
*oResult = needSize;
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleReconvert, succeeded, result=%ld", *oResult));
return true;
}
if (pReconv->dwSize < needSize) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleReconvert, FAILED, pReconv->dwSize=%ld, needSize=%ld",
pReconv->dwSize, needSize));
return false;
}
*oResult = needSize;
// Fill reconvert struct
pReconv->dwVersion = 0;
pReconv->dwStrLen = len;
pReconv->dwStrOffset = sizeof(RECONVERTSTRING);
pReconv->dwCompStrLen = len;
pReconv->dwCompStrOffset = 0;
pReconv->dwTargetStrLen = len;
pReconv->dwTargetStrOffset = 0;
::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)),
selection.mString.get(), len * sizeof(WCHAR));
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleReconvert, SUCCEEDED, pReconv=%s, result=%ld",
GetReconvertStringLog(pReconv).get(), *oResult));
return true;
}
bool IMMHandler::HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam,
LRESULT* oResult) {
uint32_t len = mIsComposing ? mCompositionString.Length() : 0;
*oResult = false;
IMECHARPOSITION* pCharPosition = reinterpret_cast<IMECHARPOSITION*>(lParam);
if (!pCharPosition) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleQueryCharPosition, FAILED, due to pCharPosition is null"));
return false;
}
if (pCharPosition->dwSize < sizeof(IMECHARPOSITION)) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleReconvert, FAILED, pCharPosition->dwSize=%ld, "
"sizeof(IMECHARPOSITION)=%ld",
pCharPosition->dwSize, sizeof(IMECHARPOSITION)));
return false;
}
if (::GetFocus() != aWindow->GetWindowHandle()) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleReconvert, FAILED, ::GetFocus()=%08x, OurWindowHandle=%08x",
::GetFocus(), aWindow->GetWindowHandle()));
return false;
}
if (pCharPosition->dwCharPos > len) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleQueryCharPosition, FAILED, pCharPosition->dwCharPos=%ld, "
"len=%ld",
pCharPosition->dwCharPos, len));
return false;
}
LayoutDeviceIntRect r;
bool ret =
GetCharacterRectOfSelectedTextAt(aWindow, pCharPosition->dwCharPos, r);
NS_ENSURE_TRUE(ret, false);
LayoutDeviceIntRect screenRect;
// We always need top level window that is owner window of the popup window
// even if the content of the popup window has focus.
ResolveIMECaretPos(aWindow->GetTopLevelWindow(false), r, nullptr, screenRect);
// XXX This might need to check writing mode. However, MSDN doesn't explain
// how to set the values in vertical writing mode. Additionally, IME
// doesn't work well with top-left of the character (this is explicitly
// documented) and its horizontal width. So, it might be better to set
// top-right corner of the character and horizontal width, but we're not
// sure if it doesn't cause any problems with a lot of IMEs...
pCharPosition->pt.x = screenRect.X();
pCharPosition->pt.y = screenRect.Y();
pCharPosition->cLineHeight = r.Height();
WidgetQueryContentEvent editorRect(true, eQueryEditorRect, aWindow);
aWindow->InitEvent(editorRect);
DispatchEvent(aWindow, editorRect);
if (NS_WARN_IF(!editorRect.mSucceeded)) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleQueryCharPosition, eQueryEditorRect failed"));
::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument);
} else {
LayoutDeviceIntRect editorRectInWindow = editorRect.mReply.mRect;
nsWindow* window =
editorRect.mReply.mFocusedWidget
? static_cast<nsWindow*>(editorRect.mReply.mFocusedWidget)
: aWindow;
LayoutDeviceIntRect editorRectInScreen;
ResolveIMECaretPos(window, editorRectInWindow, nullptr, editorRectInScreen);
::SetRect(&pCharPosition->rcDocument, editorRectInScreen.X(),
editorRectInScreen.Y(), editorRectInScreen.XMost(),
editorRectInScreen.YMost());
}
*oResult = TRUE;
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleQueryCharPosition, SUCCEEDED, pCharPosition={ pt={ x=%d, "
"y=%d }, cLineHeight=%d, rcDocument={ left=%d, top=%d, right=%d, "
"bottom=%d } }",
pCharPosition->pt.x, pCharPosition->pt.y, pCharPosition->cLineHeight,
pCharPosition->rcDocument.left, pCharPosition->rcDocument.top,
pCharPosition->rcDocument.right, pCharPosition->rcDocument.bottom));
return true;
}
bool IMMHandler::HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam,
LRESULT* oResult) {
*oResult = 0;
RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
LayoutDeviceIntPoint point(0, 0);
bool hasCompositionString =
mIsComposing && ShouldDrawCompositionStringOurselves();
int32_t targetOffset, targetLength;
if (!hasCompositionString) {
Selection& selection = GetSelection();
if (!selection.EnsureValidSelection(aWindow)) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleDocumentFeed, FAILED, due to "
"Selection::EnsureValidSelection() failure"));
return false;
}
targetOffset = int32_t(selection.mOffset);
targetLength = int32_t(selection.Length());
} else {
targetOffset = int32_t(mCompositionStart);
targetLength = int32_t(mCompositionString.Length());
}
// XXX nsString::Find and nsString::RFind take int32_t for offset, so,
// we cannot support this message when the current offset is larger than
// INT32_MAX.
if (targetOffset < 0 || targetLength < 0 || targetOffset + targetLength < 0) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleDocumentFeed, FAILED, due to the selection is out of "
"range"));
return false;
}
// Get all contents of the focused editor.
WidgetQueryContentEvent textContent(true, eQueryTextContent, aWindow);
textContent.InitForQueryTextContent(0, UINT32_MAX);
aWindow->InitEvent(textContent, &point);
DispatchEvent(aWindow, textContent);
if (!textContent.mSucceeded) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleDocumentFeed, FAILED, due to eQueryTextContent failure"));
return false;
}
nsAutoString str(textContent.mReply.mString);
if (targetOffset > int32_t(str.Length())) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleDocumentFeed, FAILED, due to the caret offset is invalid"));
return false;
}
// Get the focused paragraph, we decide that it starts from the previous CRLF
// (or start of the editor) to the next one (or the end of the editor).
int32_t paragraphStart = str.RFind("\n", false, targetOffset, -1) + 1;
int32_t paragraphEnd = str.Find("\r", false, targetOffset + targetLength, -1);
if (paragraphEnd < 0) {
paragraphEnd = str.Length();
}
nsDependentSubstring paragraph(str, paragraphStart,
paragraphEnd - paragraphStart);
uint32_t len = paragraph.Length();
uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
if (!pReconv) {
*oResult = needSize;
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleDocumentFeed, succeeded, result=%ld", *oResult));
return true;
}
if (pReconv->dwSize < needSize) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleDocumentFeed, FAILED, pReconv->dwSize=%ld, needSize=%ld",
pReconv->dwSize, needSize));
return false;
}
// Fill reconvert struct
pReconv->dwVersion = 0;
pReconv->dwStrLen = len;
pReconv->dwStrOffset = sizeof(RECONVERTSTRING);
if (hasCompositionString) {
pReconv->dwCompStrLen = targetLength;
pReconv->dwCompStrOffset = (targetOffset - paragraphStart) * sizeof(WCHAR);
// Set composition target clause information
uint32_t offset, length;
if (!GetTargetClauseRange(&offset, &length)) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("HandleDocumentFeed, FAILED, due to GetTargetClauseRange() "
"failure"));
return false;
}
pReconv->dwTargetStrLen = length;
pReconv->dwTargetStrOffset = (offset - paragraphStart) * sizeof(WCHAR);
} else {
pReconv->dwTargetStrLen = targetLength;
pReconv->dwTargetStrOffset =
(targetOffset - paragraphStart) * sizeof(WCHAR);
// There is no composition string, so, the length is zero but we should
// set the cursor offset to the composition str offset.
pReconv->dwCompStrLen = 0;
pReconv->dwCompStrOffset = pReconv->dwTargetStrOffset;
}
*oResult = needSize;
::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)),
paragraph.BeginReading(), len * sizeof(WCHAR));
MOZ_LOG(gIMMLog, LogLevel::Info,
("HandleDocumentFeed, SUCCEEDED, pReconv=%s, result=%ld",
GetReconvertStringLog(pReconv).get(), *oResult));
return true;
}
bool IMMHandler::CommitCompositionOnPreviousWindow(nsWindow* aWindow) {
if (!mComposingWindow || mComposingWindow == aWindow) {
return false;
}
MOZ_LOG(gIMMLog, LogLevel::Info,
("CommitCompositionOnPreviousWindow, mIsComposing=%s",
GetBoolName(mIsComposing)));
// If we have composition, we should dispatch composition events internally.
if (mIsComposing) {
IMEContext context(mComposingWindow);
NS_ASSERTION(context.IsValid(), "IME context must be valid");
HandleEndComposition(mComposingWindow);
return true;
}
return false;
}
static TextRangeType PlatformToNSAttr(uint8_t aAttr) {
switch (aAttr) {
case ATTR_INPUT_ERROR:
// case ATTR_FIXEDCONVERTED:
case ATTR_INPUT:
return TextRangeType::eRawClause;
case ATTR_CONVERTED:
return TextRangeType::eConvertedClause;
case ATTR_TARGET_NOTCONVERTED:
return TextRangeType::eSelectedRawClause;
case ATTR_TARGET_CONVERTED:
return TextRangeType::eSelectedClause;
default:
NS_ASSERTION(false, "unknown attribute");
return TextRangeType::eCaret;
}
}
// static
void IMMHandler::DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent) {
MOZ_LOG(
gIMMLog, LogLevel::Info,
("DispatchEvent(aWindow=0x%p, aEvent={ mMessage=%s }, "
"aWindow->Destroyed()=%s",
aWindow, ToChar(aEvent.mMessage), GetBoolName(aWindow->Destroyed())));
if (aWindow->Destroyed()) {
return;
}
aWindow->DispatchWindowEvent(&aEvent);
}
void IMMHandler::DispatchCompositionChangeEvent(nsWindow* aWindow,
const IMEContext& aContext) {
NS_ASSERTION(mIsComposing, "conflict state");
MOZ_LOG(gIMMLog, LogLevel::Info, ("DispatchCompositionChangeEvent"));
// If we don't need to draw composition string ourselves, we don't need to
// fire compositionchange event during composing.
if (!ShouldDrawCompositionStringOurselves()) {
// But we need to adjust composition window pos and native caret pos, here.
SetIMERelatedWindowsPos(aWindow, aContext);
return;
}
RefPtr<nsWindow> kungFuDeathGrip(aWindow);
RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
nsresult rv = dispatcher->BeginNativeInputTransaction();
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("DispatchCompositionChangeEvent, FAILED due to "
"TextEventDispatcher::BeginNativeInputTransaction() failure"));
return;
}
// NOTE: Calling SetIMERelatedWindowsPos() from this method will be failure
// in e10s mode. compositionchange event will notify this of
// NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED, then
// SetIMERelatedWindowsPos() will be called.
// XXX Sogou (Simplified Chinese IME) returns contradictory values:
// The cursor position is actual cursor position. However, other values
// (composition string and attributes) are empty.
if (mCompositionString.IsEmpty()) {
// Don't append clause information if composition string is empty.
} else if (mClauseArray.IsEmpty()) {
// Some IMEs don't return clause array information, then, we assume that
// all characters in the composition string are in one clause.
MOZ_LOG(gIMMLog, LogLevel::Info,
("DispatchCompositionChangeEvent, mClauseArray.Length()=0"));
rv = dispatcher->SetPendingComposition(mCompositionString, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("DispatchCompositionChangeEvent, FAILED due to"
"TextEventDispatcher::SetPendingComposition() failure"));
return;
}
} else {
// iterate over the attributes
rv = dispatcher->SetPendingCompositionString(mCompositionString);
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("DispatchCompositionChangeEvent, FAILED due to"
"TextEventDispatcher::SetPendingCompositionString() failure"));
return;
}
uint32_t lastOffset = 0;
for (uint32_t i = 0; i < mClauseArray.Length() - 1; i++) {
uint32_t current = mClauseArray[i + 1];
if (current > mCompositionString.Length()) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("DispatchCompositionChangeEvent, mClauseArray[%ld]=%lu. "
"This is larger than mCompositionString.Length()=%lu",
i + 1, current, mCompositionString.Length()));
current = int32_t(mCompositionString.Length());
}
uint32_t length = current - lastOffset;
if (NS_WARN_IF(lastOffset >= mAttributeArray.Length())) {
MOZ_LOG(
gIMMLog, LogLevel::Error,
("DispatchCompositionChangeEvent, FAILED due to invalid data of "
"mClauseArray or mAttributeArray"));
return;
}
TextRangeType textRangeType =
PlatformToNSAttr(mAttributeArray[lastOffset]);
rv = dispatcher->AppendClauseToPendingComposition(length, textRangeType);
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("DispatchCompositionChangeEvent, FAILED due to"
"TextEventDispatcher::AppendClauseToPendingComposition() "
"failure"));
return;
}
lastOffset = current;
MOZ_LOG(gIMMLog, LogLevel::Info,
("DispatchCompositionChangeEvent, index=%ld, rangeType=%s, "
"range length=%lu",
i, ToChar(textRangeType), length));
}
}
if (mCursorPosition == NO_IME_CARET) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("DispatchCompositionChangeEvent, no caret"));
} else {
uint32_t cursor = static_cast<uint32_t>(mCursorPosition);
if (cursor > mCompositionString.Length()) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("CreateTextRangeArray, mCursorPosition=%ld. "
"This is larger than mCompositionString.Length()=%lu",
mCursorPosition, mCompositionString.Length()));
cursor = mCompositionString.Length();
}
// If caret is in the target clause, the target clause will be painted as
// normal selection range. Since caret shouldn't be in selection range on
// Windows, we shouldn't append caret range in such case.
const TextRangeArray* clauses = dispatcher->GetPendingCompositionClauses();
const TextRange* targetClause =
clauses ? clauses->GetTargetClause() : nullptr;
if (targetClause && cursor >= targetClause->mStartOffset &&
cursor <= targetClause->mEndOffset) {
// Forget the caret position specified by IME since Gecko's caret position
// will be at the end of composition string.
mCursorPosition = NO_IME_CARET;
MOZ_LOG(gIMMLog, LogLevel::Info,
("CreateTextRangeArray, no caret due to it's in the target "
"clause, now, mCursorPosition is NO_IME_CARET"));
}
if (mCursorPosition != NO_IME_CARET) {
rv = dispatcher->SetCaretInPendingComposition(cursor, 0);
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(
gIMMLog, LogLevel::Error,
("DispatchCompositionChangeEvent, FAILED due to"
"TextEventDispatcher::SetCaretInPendingComposition() failure"));
return;
}
}
}
WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
nsEventStatus status;
rv = dispatcher->FlushPendingComposition(status, &eventTime);
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("DispatchCompositionChangeEvent, FAILED due to"
"TextEventDispatcher::FlushPendingComposition() failure"));
return;
}
}
void IMMHandler::GetCompositionString(const IMEContext& aContext, DWORD aIndex,
nsAString& aCompositionString) const {
aCompositionString.Truncate();
// Retrieve the size of the required output buffer.
long lRtn = ::ImmGetCompositionStringW(aContext.get(), aIndex, nullptr, 0);
if (lRtn < 0 || !aCompositionString.SetLength((lRtn / sizeof(WCHAR)) + 1,
mozilla::fallible)) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("GetCompositionString, FAILED, due to OOM"));
return; // Error or out of memory.
}
// Actually retrieve the composition string information.
lRtn = ::ImmGetCompositionStringW(aContext.get(), aIndex,
(LPVOID)aCompositionString.BeginWriting(),
lRtn + sizeof(WCHAR));
aCompositionString.SetLength(lRtn / sizeof(WCHAR));
MOZ_LOG(gIMMLog, LogLevel::Info,
("GetCompositionString, succeeded, aCompositionString=\"%s\"",
NS_ConvertUTF16toUTF8(aCompositionString).get()));
}
bool IMMHandler::GetTargetClauseRange(uint32_t* aOffset, uint32_t* aLength) {
NS_ENSURE_TRUE(aOffset, false);
NS_ENSURE_TRUE(mIsComposing, false);
NS_ENSURE_TRUE(ShouldDrawCompositionStringOurselves(), false);
bool found = false;
*aOffset = mCompositionStart;
for (uint32_t i = 0; i < mAttributeArray.Length(); i++) {
if (mAttributeArray[i] == ATTR_TARGET_NOTCONVERTED ||
mAttributeArray[i] == ATTR_TARGET_CONVERTED) {
*aOffset = mCompositionStart + i;
found = true;
break;
}
}
if (!aLength) {
return true;
}
if (!found) {
// The all composition string is targetted when there is no ATTR_TARGET_*
// clause. E.g., there is only ATTR_INPUT
*aLength = mCompositionString.Length();
return true;
}
uint32_t offsetInComposition = *aOffset - mCompositionStart;
*aLength = mCompositionString.Length() - offsetInComposition;
for (uint32_t i = offsetInComposition; i < mAttributeArray.Length(); i++) {
if (mAttributeArray[i] != ATTR_TARGET_NOTCONVERTED &&
mAttributeArray[i] != ATTR_TARGET_CONVERTED) {
*aLength = i - offsetInComposition;
break;
}
}
return true;
}
bool IMMHandler::ConvertToANSIString(const nsString& aStr, UINT aCodePage,
nsACString& aANSIStr) {
int len = ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(),
aStr.Length(), nullptr, 0, nullptr, nullptr);
NS_ENSURE_TRUE(len >= 0, false);
if (!aANSIStr.SetLength(len, mozilla::fallible)) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("ConvertToANSIString, FAILED, due to OOM"));
return false;
}
::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(),
(LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr);
return true;
}
bool IMMHandler::GetCharacterRectOfSelectedTextAt(
nsWindow* aWindow, uint32_t aOffset, LayoutDeviceIntRect& aCharRect,
WritingMode* aWritingMode) {
LayoutDeviceIntPoint point(0, 0);
Selection& selection = GetSelection();
if (!selection.EnsureValidSelection(aWindow)) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("GetCharacterRectOfSelectedTextAt, FAILED, due to "
"Selection::EnsureValidSelection() failure"));
return false;
}
// If the offset is larger than the end of composition string or selected
// string, we should return false since such case must be a bug of the caller
// or the active IME. If it's an IME's bug, we need to set targetLength to
// aOffset.
uint32_t targetLength =
mIsComposing ? mCompositionString.Length() : selection.Length();
if (NS_WARN_IF(aOffset > targetLength)) {
MOZ_LOG(
gIMMLog, LogLevel::Error,
("GetCharacterRectOfSelectedTextAt, FAILED, due to "
"aOffset is too large (aOffset=%u, targetLength=%u, mIsComposing=%s)",
aOffset, targetLength, GetBoolName(mIsComposing)));
return false;
}
// If there is caret, we might be able to use caret rect.
uint32_t caretOffset = UINT32_MAX;
// There is a caret only when the normal selection is collapsed.
if (selection.Collapsed()) {
if (mIsComposing) {
// If it's composing, mCursorPosition is the offset to caret in
// the composition string.
if (mCursorPosition != NO_IME_CARET) {
MOZ_ASSERT(mCursorPosition >= 0);
caretOffset = mCursorPosition;
} else if (!ShouldDrawCompositionStringOurselves() ||
mCompositionString.IsEmpty()) {
// Otherwise, if there is no composition string, we should assume that
// there is a caret at the start of composition string.
caretOffset = 0;
}
} else {
// If there is no composition, the selection offset is the caret offset.
caretOffset = 0;
}
}
// If there is a caret and retrieving offset is same as the caret offset,
// we should use the caret rect.
if (aOffset != caretOffset) {
WidgetQueryContentEvent charRect(true, eQueryTextRect, aWindow);
WidgetQueryContentEvent::Options options;
options.mRelativeToInsertionPoint = true;
charRect.InitForQueryTextRect(aOffset, 1, options);
aWindow->InitEvent(charRect, &point);
DispatchEvent(aWindow, charRect);
if (charRect.mSucceeded) {
aCharRect = charRect.mReply.mRect;
if (aWritingMode) {
*aWritingMode = charRect.GetWritingMode();
}
MOZ_LOG(gIMMLog, LogLevel::Debug,
("GetCharacterRectOfSelectedTextAt, Succeeded, aOffset=%u, "
"aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
"charRect.GetWritingMode()=%s",
aOffset, aCharRect.X(), aCharRect.Y(), aCharRect.Width(),
aCharRect.Height(),
GetWritingModeName(charRect.GetWritingMode()).get()));
return true;
}
}
return GetCaretRect(aWindow, aCharRect, aWritingMode);
}
bool IMMHandler::GetCaretRect(nsWindow* aWindow,
LayoutDeviceIntRect& aCaretRect,
WritingMode* aWritingMode) {
LayoutDeviceIntPoint point(0, 0);
WidgetQueryContentEvent caretRect(true, eQueryCaretRect, aWindow);
WidgetQueryContentEvent::Options options;
options.mRelativeToInsertionPoint = true;
caretRect.InitForQueryCaretRect(0, options);
aWindow->InitEvent(caretRect, &point);
DispatchEvent(aWindow, caretRect);
if (!caretRect.mSucceeded) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("GetCaretRect, FAILED, due to eQueryCaretRect failure"));
return false;
}
aCaretRect = caretRect.mReply.mRect;
if (aWritingMode) {
*aWritingMode = caretRect.GetWritingMode();
}
MOZ_LOG(
gIMMLog, LogLevel::Info,
("GetCaretRect, SUCCEEDED, "
"aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
"caretRect.GetWritingMode()=%s",
aCaretRect.X(), aCaretRect.Y(), aCaretRect.Width(), aCaretRect.Height(),
GetWritingModeName(caretRect.GetWritingMode()).get()));
return true;
}
bool IMMHandler::SetIMERelatedWindowsPos(nsWindow* aWindow,
const IMEContext& aContext) {
// Get first character rect of current a normal selected text or a composing
// string.
WritingMode writingMode;
LayoutDeviceIntRect firstSelectedCharRectRelativeToWindow;
bool ret = GetCharacterRectOfSelectedTextAt(
aWindow, 0, firstSelectedCharRectRelativeToWindow, &writingMode);
NS_ENSURE_TRUE(ret, false);
nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
LayoutDeviceIntRect firstSelectedCharRect;
ResolveIMECaretPos(toplevelWindow, firstSelectedCharRectRelativeToWindow,
aWindow, firstSelectedCharRect);
// Set native caret size/position to our caret. Some IMEs honor it. E.g.,
// "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified
// Chinese) on XP. But if a11y module is handling native caret, we shouldn't
// touch it.
if (!IMEHandler::IsA11yHandlingNativeCaret()) {
LayoutDeviceIntRect caretRect(firstSelectedCharRect),
caretRectRelativeToWindow;
if (GetCaretRect(aWindow, caretRectRelativeToWindow)) {
ResolveIMECaretPos(toplevelWindow, caretRectRelativeToWindow, aWindow,
caretRect);
} else {
NS_WARNING("failed to get caret rect");
caretRect.SetWidth(1);
}
IMEHandler::CreateNativeCaret(aWindow, caretRect);
}
if (ShouldDrawCompositionStringOurselves()) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("SetIMERelatedWindowsPos, Set candidate window"));
// Get a rect of first character in current target in composition string.
LayoutDeviceIntRect firstTargetCharRect, lastTargetCharRect;
if (mIsComposing && !mCompositionString.IsEmpty()) {
// If there are no targetted selection, we should use it's first character
// rect instead.
uint32_t offset, length;
if (!GetTargetClauseRange(&offset, &length)) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("SetIMERelatedWindowsPos, FAILED, due to "
"GetTargetClauseRange() failure"));
return false;
}
ret =
GetCharacterRectOfSelectedTextAt(aWindow, offset - mCompositionStart,
firstTargetCharRect, &writingMode);
NS_ENSURE_TRUE(ret, false);
if (length) {
ret = GetCharacterRectOfSelectedTextAt(
aWindow, offset + length - 1 - mCompositionStart,
lastTargetCharRect);
NS_ENSURE_TRUE(ret, false);
} else {
lastTargetCharRect = firstTargetCharRect;
}
} else {
// If there are no composition string, we should use a first character
// rect.
ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, firstTargetCharRect,
&writingMode);
NS_ENSURE_TRUE(ret, false);
lastTargetCharRect = firstTargetCharRect;
}
ResolveIMECaretPos(toplevelWindow, firstTargetCharRect, aWindow,
firstTargetCharRect);
ResolveIMECaretPos(toplevelWindow, lastTargetCharRect, aWindow,
lastTargetCharRect);
LayoutDeviceIntRect targetClauseRect;
targetClauseRect.UnionRect(firstTargetCharRect, lastTargetCharRect);
// Move the candidate window to proper position from the target clause as
// far as possible.
CANDIDATEFORM candForm;
candForm.dwIndex = 0;
if (!writingMode.IsVertical() || IsVerticalWritingSupported()) {
candForm.dwStyle = CFS_EXCLUDE;
// Candidate window shouldn't overlap the target clause in any writing
// mode.
candForm.rcArea.left = targetClauseRect.X();
candForm.rcArea.right = targetClauseRect.XMost();
candForm.rcArea.top = targetClauseRect.Y();
candForm.rcArea.bottom = targetClauseRect.YMost();
if (!writingMode.IsVertical()) {
// In horizontal layout, current point of interest should be top-left
// of the first character.
candForm.ptCurrentPos.x = firstTargetCharRect.X();
candForm.ptCurrentPos.y = firstTargetCharRect.Y();
} else if (writingMode.IsVerticalRL()) {
// In vertical layout (RL), candidate window should be positioned right
// side of target clause. However, we don't set vertical writing font
// to the IME. Therefore, the candidate window may be positioned
// bottom-left of target clause rect with these information.
candForm.ptCurrentPos.x = targetClauseRect.X();
candForm.ptCurrentPos.y = targetClauseRect.Y();
} else {
MOZ_ASSERT(writingMode.IsVerticalLR(), "Did we miss some causes?");
// In vertical layout (LR), candidate window should be poisitioned left
// side of target clause. Although, we don't set vertical writing font
// to the IME, the candidate window may be positioned bottom-right of
// the target clause rect with these information.
candForm.ptCurrentPos.x = targetClauseRect.XMost();
candForm.ptCurrentPos.y = targetClauseRect.Y();
}
} else {
// If vertical writing is not supported by IME, let's set candidate
// window position to the bottom-left of the target clause because
// the position must be the safest position to prevent the candidate
// window to overlap with the target clause.
candForm.dwStyle = CFS_CANDIDATEPOS;
candForm.ptCurrentPos.x = targetClauseRect.X();
candForm.ptCurrentPos.y = targetClauseRect.YMost();
}
MOZ_LOG(gIMMLog, LogLevel::Info,
("SetIMERelatedWindowsPos, Calling ImmSetCandidateWindow()... "
"ptCurrentPos={ x=%d, y=%d }, "
"rcArea={ left=%d, top=%d, right=%d, bottom=%d }, "
"writingMode=%s",
candForm.ptCurrentPos.x, candForm.ptCurrentPos.y,
candForm.rcArea.left, candForm.rcArea.top, candForm.rcArea.right,
candForm.rcArea.bottom, GetWritingModeName(writingMode).get()));
::ImmSetCandidateWindow(aContext.get(), &candForm);
} else {
MOZ_LOG(gIMMLog, LogLevel::Info,
("SetIMERelatedWindowsPos, Set composition window"));
// Move the composition window to caret position (if selected some
// characters, we should use first character rect of them).
// And in this mode, IME adjusts the candidate window position
// automatically. So, we don't need to set it.
COMPOSITIONFORM compForm;
compForm.dwStyle = CFS_POINT;
compForm.ptCurrentPos.x = !writingMode.IsVerticalLR()
? firstSelectedCharRect.X()
: firstSelectedCharRect.XMost();
compForm.ptCurrentPos.y = firstSelectedCharRect.Y();
::ImmSetCompositionWindow(aContext.get(), &compForm);
}
return true;
}
void IMMHandler::SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow,
const IMEContext& aContext) {
WidgetQueryContentEvent editorRectEvent(true, eQueryEditorRect, aWindow);
aWindow->InitEvent(editorRectEvent);
DispatchEvent(aWindow, editorRectEvent);
if (!editorRectEvent.mSucceeded) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("SetIMERelatedWindowsPosOnPlugin, "
"FAILED, due to eQueryEditorRect failure"));
return;
}
// Clip the plugin rect by the client rect of the window because composition
// window needs to be specified the position in the client area.
nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
LayoutDeviceIntRect pluginRectInScreen =
editorRectEvent.mReply.mRect + toplevelWindow->WidgetToScreenOffset();
LayoutDeviceIntRect winRectInScreen = aWindow->GetClientBounds();
// composition window cannot be positioned on the edge of client area.
winRectInScreen.SizeTo(winRectInScreen.Width() - 1,
winRectInScreen.Height() - 1);
LayoutDeviceIntRect clippedPluginRect;
clippedPluginRect.MoveTo(
std::min(std::max(pluginRectInScreen.X(), winRectInScreen.X()),
winRectInScreen.XMost()),
std::min(std::max(pluginRectInScreen.Y(), winRectInScreen.Y()),
winRectInScreen.YMost()));
int32_t xMost = std::min(pluginRectInScreen.XMost(), winRectInScreen.XMost());
int32_t yMost = std::min(pluginRectInScreen.YMost(), winRectInScreen.YMost());
clippedPluginRect.SizeTo(std::max(0, xMost - clippedPluginRect.X()),
std::max(0, yMost - clippedPluginRect.Y()));
clippedPluginRect -= aWindow->WidgetToScreenOffset();
// Cover the plugin with native caret. This prevents IME's window and plugin
// overlap. But if a11y modules is handling native caret, we shouldn't touch
// it.
if (!IMEHandler::IsA11yHandlingNativeCaret()) {
IMEHandler::CreateNativeCaret(aWindow, clippedPluginRect);
}
// Set the composition window to bottom-left of the clipped plugin.
// As far as we know, there is no IME for RTL language. Therefore, this code
// must not need to take care of RTL environment.
COMPOSITIONFORM compForm;
compForm.dwStyle = CFS_POINT;
compForm.ptCurrentPos.x = clippedPluginRect.BottomLeft().x;
compForm.ptCurrentPos.y = clippedPluginRect.BottomLeft().y;
if (!::ImmSetCompositionWindow(aContext.get(), &compForm)) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("SetIMERelatedWindowsPosOnPlugin, "
"FAILED, due to ::ImmSetCompositionWindow() failure"));
return;
}
}
void IMMHandler::ResolveIMECaretPos(nsIWidget* aReferenceWidget,
LayoutDeviceIntRect& aCursorRect,
nsIWidget* aNewOriginWidget,
LayoutDeviceIntRect& aOutRect) {
aOutRect = aCursorRect;
if (aReferenceWidget == aNewOriginWidget) return;
if (aReferenceWidget)
aOutRect.MoveBy(aReferenceWidget->WidgetToScreenOffset());
if (aNewOriginWidget)
aOutRect.MoveBy(-aNewOriginWidget->WidgetToScreenOffset());
}
static void SetHorizontalFontToLogFont(const nsAString& aFontFace,
LOGFONTW& aLogFont) {
aLogFont.lfEscapement = aLogFont.lfOrientation = 0;
if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 1)) {
memcpy(aLogFont.lfFaceName, L"System", sizeof(L"System"));
return;
}
memcpy(aLogFont.lfFaceName, aFontFace.BeginReading(),
aFontFace.Length() * sizeof(wchar_t));
aLogFont.lfFaceName[aFontFace.Length()] = 0;
}
static void SetVerticalFontToLogFont(const nsAString& aFontFace,
LOGFONTW& aLogFont) {
aLogFont.lfEscapement = aLogFont.lfOrientation = 2700;
if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 2)) {
memcpy(aLogFont.lfFaceName, L"@System", sizeof(L"@System"));
return;
}
aLogFont.lfFaceName[0] = '@';
memcpy(&aLogFont.lfFaceName[1], aFontFace.BeginReading(),
aFontFace.Length() * sizeof(wchar_t));
aLogFont.lfFaceName[aFontFace.Length() + 1] = 0;
}
void IMMHandler::AdjustCompositionFont(nsWindow* aWindow,
const IMEContext& aContext,
const WritingMode& aWritingMode,
bool aForceUpdate) {
// An instance of IMMHandler is destroyed when active IME is changed.
// Therefore, we need to store the information which are set to the IM
// context to static variables since IM context is never recreated.
static bool sCompositionFontsInitialized = false;
static nsString sCompositionFont;
static bool sCompositionFontPrefDone = false;
if (!sCompositionFontPrefDone) {
sCompositionFontPrefDone = true;
Preferences::GetString("intl.imm.composition_font", sCompositionFont);
}
// If composition font is customized by pref, we need to modify the
// composition font of the IME context at first time even if the writing mode
// is horizontal.
bool setCompositionFontForcibly =
aForceUpdate ||
(!sCompositionFontsInitialized && !sCompositionFont.IsEmpty());
static WritingMode sCurrentWritingMode;
static nsString sCurrentIMEName;
if (!setCompositionFontForcibly &&
sWritingModeOfCompositionFont == aWritingMode &&
sCurrentIMEName == sIMEName) {
// Nothing to do if writing mode isn't being changed.
return;
}
// Decide composition fonts for both horizontal writing mode and vertical
// writing mode. If the font isn't specified by the pref, use default
// font which is already set to the IM context. And also in vertical writing
// mode, insert '@' to the start of the font.
if (!sCompositionFontsInitialized) {
sCompositionFontsInitialized = true;
// sCompositionFontH must not start with '@' and its length is less than
// LF_FACESIZE since it needs to end with null terminating character.
if (sCompositionFont.IsEmpty() ||
sCompositionFont.Length() > LF_FACESIZE - 1 ||
sCompositionFont[0] == '@') {
LOGFONTW defaultLogFont;
if (NS_WARN_IF(
!::ImmGetCompositionFont(aContext.get(), &defaultLogFont))) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("AdjustCompositionFont, ::ImmGetCompositionFont() failed"));
sCompositionFont.AssignLiteral("System");
} else {
// The font face is typically, "System".
sCompositionFont.Assign(defaultLogFont.lfFaceName);
}
}
MOZ_LOG(gIMMLog, LogLevel::Info,
("AdjustCompositionFont, sCompositionFont=\"%s\" is initialized",
NS_ConvertUTF16toUTF8(sCompositionFont).get()));
}
static nsString sCompositionFontForJapanist2003;
if (IsJapanist2003Active() && sCompositionFontForJapanist2003.IsEmpty()) {
const char* kCompositionFontForJapanist2003 =
"intl.imm.composition_font.japanist_2003";
Preferences::GetString(kCompositionFontForJapanist2003,
sCompositionFontForJapanist2003);
// If the font name is not specified properly, let's use
// "MS PGothic" instead.
if (sCompositionFontForJapanist2003.IsEmpty() ||
sCompositionFontForJapanist2003.Length() > LF_FACESIZE - 2 ||
sCompositionFontForJapanist2003[0] == '@') {
sCompositionFontForJapanist2003.AssignLiteral("MS PGothic");
}
}
sWritingModeOfCompositionFont = aWritingMode;
sCurrentIMEName = sIMEName;
LOGFONTW logFont;
memset(&logFont, 0, sizeof(logFont));
if (!::ImmGetCompositionFont(aContext.get(), &logFont)) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("AdjustCompositionFont, ::ImmGetCompositionFont() failed"));
logFont.lfFaceName[0] = 0;
}
// Need to reset some information which should be recomputed with new font.
logFont.lfWidth = 0;
logFont.lfWeight = FW_DONTCARE;
logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
logFont.lfPitchAndFamily = DEFAULT_PITCH;
if (!aWindow->PluginHasFocus() && aWritingMode.IsVertical() &&
IsVerticalWritingSupported()) {
SetVerticalFontToLogFont(IsJapanist2003Active()
? sCompositionFontForJapanist2003
: sCompositionFont,
logFont);
} else {
SetHorizontalFontToLogFont(IsJapanist2003Active()
? sCompositionFontForJapanist2003
: sCompositionFont,
logFont);
}
MOZ_LOG(gIMMLog, LogLevel::Warning,
("AdjustCompositionFont, calling ::ImmSetCompositionFont(\"%s\")",
NS_ConvertUTF16toUTF8(nsDependentString(logFont.lfFaceName)).get()));
::ImmSetCompositionFontW(aContext.get(), &logFont);
}
// static
nsresult IMMHandler::OnMouseButtonEvent(
nsWindow* aWindow, const IMENotification& aIMENotification) {
// We don't need to create the instance of the handler here.
if (!gIMMHandler) {
return NS_OK;
}
if (!sWM_MSIME_MOUSE || !IsComposingOnOurEditor() ||
!ShouldDrawCompositionStringOurselves()) {
return NS_OK;
}
// We need to handle only mousedown event.
if (aIMENotification.mMouseButtonEventData.mEventMessage != eMouseDown) {
return NS_OK;
}
// If the character under the cursor is not in the composition string,
// we don't need to notify IME of it.
uint32_t compositionStart = gIMMHandler->mCompositionStart;
uint32_t compositionEnd =
compositionStart + gIMMHandler->mCompositionString.Length();
if (aIMENotification.mMouseButtonEventData.mOffset < compositionStart ||
aIMENotification.mMouseButtonEventData.mOffset >= compositionEnd) {
return NS_OK;
}
BYTE button;
switch (aIMENotification.mMouseButtonEventData.mButton) {
case MouseButton::eLeft:
button = IMEMOUSE_LDOWN;
break;
case MouseButton::eMiddle:
button = IMEMOUSE_MDOWN;
break;
case MouseButton::eRight:
button = IMEMOUSE_RDOWN;
break;
default:
return NS_OK;
}
// calcurate positioning and offset
// char : JCH1|JCH2|JCH3
// offset: 0011 1122 2233
// positioning: 2301 2301 2301
nsIntPoint cursorPos =
aIMENotification.mMouseButtonEventData.mCursorPos.AsIntPoint();
nsIntRect charRect =
aIMENotification.mMouseButtonEventData.mCharRect.AsIntRect();
int32_t cursorXInChar = cursorPos.x - charRect.X();
// The event might hit to zero-width character, see bug 694913.
// The reason might be:
// * There are some zero-width characters are actually.
// * font-size is specified zero.
// But nobody reproduced this bug actually...
// We should assume that user clicked on right most of the zero-width
// character in such case.
int positioning = 1;
if (charRect.Width() > 0) {
positioning = cursorXInChar * 4 / charRect.Width();
positioning = (positioning + 2) % 4;
}
int offset =
aIMENotification.mMouseButtonEventData.mOffset - compositionStart;
if (positioning < 2) {
offset++;
}
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnMouseButtonEvent, x,y=%ld,%ld, offset=%ld, positioning=%ld",
cursorPos.x, cursorPos.y, offset, positioning));
// send MS_MSIME_MOUSE message to default IME window.
HWND imeWnd = ::ImmGetDefaultIMEWnd(aWindow->GetWindowHandle());
IMEContext context(aWindow);
if (::SendMessageW(imeWnd, sWM_MSIME_MOUSE,
MAKELONG(MAKEWORD(button, positioning), offset),
(LPARAM)context.get()) == 1) {
return NS_SUCCESS_EVENT_CONSUMED;
}
return NS_OK;
}
// static
bool IMMHandler::OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
MSGResult& aResult) {
MOZ_LOG(gIMMLog, LogLevel::Info,
("OnKeyDownEvent, hWnd=%08x, wParam=%08x, lParam=%08x",
aWindow->GetWindowHandle(), wParam, lParam));
aResult.mConsumed = false;
switch (wParam) {
case VK_TAB:
case VK_PRIOR:
case VK_NEXT:
case VK_END:
case VK_HOME:
case VK_LEFT:
case VK_UP:
case VK_RIGHT:
case VK_DOWN:
case VK_RETURN:
// If IME didn't process the key message (the virtual key code wasn't
// converted to VK_PROCESSKEY), and the virtual key code event causes
// moving caret or editing text with keeping composing state, we should
// cancel the composition here because we cannot support moving
// composition string with DOM events (IE also cancels the composition
// in same cases). Then, this event will be dispatched.
if (IsComposingOnOurEditor()) {
// NOTE: We don't need to cancel the composition on another window.
CancelComposition(aWindow, false);
}
return false;
default:
return false;
}
}
// static
void IMMHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm) {
// Hack for ATOK 2011 - 2016 (Japanese IME). They refer native caret
// position at deciding candidate window position. Note that we cannot
// check active IME since TIPs are wrapped and hidden by CUAS.
if (aWindow->PluginHasFocus()) {
// We cannot retrieve proper character height from plugin. Therefore,
// we should assume that the caret height is always 20px since if less than
// this height, candidate window may overlap with composition string when
// there is no enough space under composition string to show candidate
// window.
static const int32_t kCaretHeight = 20;
// If a11y module is handling native caret, we shouldn't touch it.
// However, it does not work well with Flash Player with IME. So, we
// need to keep overriding caret position here.
LayoutDeviceIntRect caretRect(aForm->ptCurrentPos.x,
aForm->ptCurrentPos.y - kCaretHeight, 1,
kCaretHeight);
IMEHandler::CreateNativeCaret(aWindow, caretRect);
}
IMEContext context(aWindow);
::ImmSetCandidateWindow(context.get(), aForm);
}
// staitc
void IMMHandler::DefaultProcOfPluginEvent(nsWindow* aWindow,
const NPEvent* aEvent) {
switch (aEvent->event) {
case WM_IME_STARTCOMPOSITION:
EnsureHandlerInstance();
gIMMHandler->OnIMEStartCompositionOnPlugin(aWindow, aEvent->wParam,
aEvent->lParam);
break;
case WM_IME_COMPOSITION:
if (gIMMHandler) {
gIMMHandler->OnIMECompositionOnPlugin(aWindow, aEvent->wParam,
aEvent->lParam);
}
break;
case WM_IME_ENDCOMPOSITION:
if (gIMMHandler) {
gIMMHandler->OnIMEEndCompositionOnPlugin(aWindow, aEvent->wParam,
aEvent->lParam);
}
break;
}
}
/******************************************************************************
* IMMHandler::Selection
******************************************************************************/
bool IMMHandler::Selection::IsValid() const {
if (!mIsValid || NS_WARN_IF(mOffset == UINT32_MAX)) {
return false;
}
CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(mOffset) + Length();
return endOffset.isValid();
}
bool IMMHandler::Selection::Update(const IMENotification& aIMENotification) {
mOffset = aIMENotification.mSelectionChangeData.mOffset;
mString = aIMENotification.mSelectionChangeData.String();
mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
mIsValid = true;
MOZ_LOG(gIMMLog, LogLevel::Info,
("Selection::Update, aIMENotification={ mSelectionChangeData={ "
"mOffset=%u, mLength=%u, GetWritingMode()=%s } }",
mOffset, mString.Length(), GetWritingModeName(mWritingMode).get()));
if (!IsValid()) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("Selection::Update, FAILED, due to invalid range"));
Clear();
return false;
}
return true;
}
bool IMMHandler::Selection::Init(nsWindow* aWindow) {
Clear();
WidgetQueryContentEvent selection(true, eQuerySelectedText, aWindow);
LayoutDeviceIntPoint point(0, 0);
aWindow->InitEvent(selection, &point);
DispatchEvent(aWindow, selection);
if (NS_WARN_IF(!selection.mSucceeded)) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("Selection::Init, FAILED, due to eQuerySelectedText failure"));
return false;
}
// If the window is destroyed during querying selected text, we shouldn't
// do anymore.
if (aWindow->Destroyed()) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("Selection::Init, FAILED, due to the widget destroyed"));
return false;
}
mOffset = selection.mReply.mOffset;
mString = selection.mReply.mString;
mWritingMode = selection.GetWritingMode();
mIsValid = true;
MOZ_LOG(gIMMLog, LogLevel::Info,
("Selection::Init, selection={ mReply={ mOffset=%u, "
"mString.Length()=%u, mWritingMode=%s } }",
mOffset, mString.Length(), GetWritingModeName(mWritingMode).get()));
if (!IsValid()) {
MOZ_LOG(gIMMLog, LogLevel::Error,
("Selection::Init, FAILED, due to invalid range"));
Clear();
return false;
}
return true;
}
bool IMMHandler::Selection::EnsureValidSelection(nsWindow* aWindow) {
if (IsValid()) {
return true;
}
return Init(aWindow);
}
} // namespace widget
} // namespace mozilla