1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sts=2 sw=2 et cin: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/Logging.h"
9 #include "IMMHandler.h"
11 #include "nsWindowDefs.h"
12 #include "WinIMEHandler.h"
14 #include "KeyboardLayout.h"
17 #include "mozilla/CheckedInt.h"
18 #include "mozilla/MiscEvents.h"
19 #include "mozilla/TextEvents.h"
20 #include "mozilla/ToString.h"
21 #include "mozilla/WindowsVersion.h"
23 #ifndef IME_PROP_ACCEPT_WIDE_VKEY
24 # define IME_PROP_ACCEPT_WIDE_VKEY 0x20
27 //-------------------------------------------------------------------------
30 // http://download.microsoft.com/download/6/0/9/60908e9e-d2c1-47db-98f6-216af76a235f/msime.h
31 // The document for this has been removed from MSDN...
33 //-------------------------------------------------------------------------
35 #define RWM_MOUSE TEXT("MSIMEMouseOperation")
37 #define IMEMOUSE_NONE 0x00 // no mouse button was pushed
38 #define IMEMOUSE_LDOWN 0x01
39 #define IMEMOUSE_RDOWN 0x02
40 #define IMEMOUSE_MDOWN 0x04
41 #define IMEMOUSE_WUP 0x10 // wheel up
42 #define IMEMOUSE_WDOWN 0x20 // wheel down
44 // For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync`
45 // rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
47 // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
48 extern mozilla::LazyLogModule gIMELog
;
50 static const char* GetBoolName(bool aBool
) { return aBool
? "true" : "false"; }
52 static void HandleSeparator(nsACString
& aDesc
) {
53 if (!aDesc
.IsEmpty()) {
54 aDesc
.AppendLiteral(" | ");
58 class GetIMEGeneralPropertyName
: public nsAutoCString
{
60 explicit GetIMEGeneralPropertyName(DWORD aFlags
) {
62 AppendLiteral("no flags");
65 if (aFlags
& IME_PROP_AT_CARET
) {
66 AppendLiteral("IME_PROP_AT_CARET");
68 if (aFlags
& IME_PROP_SPECIAL_UI
) {
69 HandleSeparator(*this);
70 AppendLiteral("IME_PROP_SPECIAL_UI");
72 if (aFlags
& IME_PROP_CANDLIST_START_FROM_1
) {
73 HandleSeparator(*this);
74 AppendLiteral("IME_PROP_CANDLIST_START_FROM_1");
76 if (aFlags
& IME_PROP_UNICODE
) {
77 HandleSeparator(*this);
78 AppendLiteral("IME_PROP_UNICODE");
80 if (aFlags
& IME_PROP_COMPLETE_ON_UNSELECT
) {
81 HandleSeparator(*this);
82 AppendLiteral("IME_PROP_COMPLETE_ON_UNSELECT");
84 if (aFlags
& IME_PROP_ACCEPT_WIDE_VKEY
) {
85 HandleSeparator(*this);
86 AppendLiteral("IME_PROP_ACCEPT_WIDE_VKEY");
89 virtual ~GetIMEGeneralPropertyName() {}
92 class GetIMEUIPropertyName
: public nsAutoCString
{
94 explicit GetIMEUIPropertyName(DWORD aFlags
) {
96 AppendLiteral("no flags");
99 if (aFlags
& UI_CAP_2700
) {
100 AppendLiteral("UI_CAP_2700");
102 if (aFlags
& UI_CAP_ROT90
) {
103 HandleSeparator(*this);
104 AppendLiteral("UI_CAP_ROT90");
106 if (aFlags
& UI_CAP_ROTANY
) {
107 HandleSeparator(*this);
108 AppendLiteral("UI_CAP_ROTANY");
111 virtual ~GetIMEUIPropertyName() {}
114 class GetReconvertStringLog
: public nsAutoCString
{
116 explicit GetReconvertStringLog(RECONVERTSTRING
* aReconv
) {
117 AssignLiteral("{ dwSize=");
118 AppendInt(static_cast<uint32_t>(aReconv
->dwSize
));
119 AppendLiteral(", dwVersion=");
120 AppendInt(static_cast<uint32_t>(aReconv
->dwVersion
));
121 AppendLiteral(", dwStrLen=");
122 AppendInt(static_cast<uint32_t>(aReconv
->dwStrLen
));
123 AppendLiteral(", dwStrOffset=");
124 AppendInt(static_cast<uint32_t>(aReconv
->dwStrOffset
));
125 AppendLiteral(", dwCompStrLen=");
126 AppendInt(static_cast<uint32_t>(aReconv
->dwCompStrLen
));
127 AppendLiteral(", dwCompStrOffset=");
128 AppendInt(static_cast<uint32_t>(aReconv
->dwCompStrOffset
));
129 AppendLiteral(", dwTargetStrLen=");
130 AppendInt(static_cast<uint32_t>(aReconv
->dwTargetStrLen
));
131 AppendLiteral(", dwTargetStrOffset=");
132 AppendInt(static_cast<uint32_t>(aReconv
->dwTargetStrOffset
));
133 AppendLiteral(", result str=\"");
134 if (aReconv
->dwStrLen
) {
135 char16_t
* strStart
= reinterpret_cast<char16_t
*>(
136 reinterpret_cast<char*>(aReconv
) + aReconv
->dwStrOffset
);
137 nsDependentString
str(strStart
, aReconv
->dwStrLen
);
138 Append(NS_ConvertUTF16toUTF8(str
));
140 AppendLiteral("\" }");
142 virtual ~GetReconvertStringLog() {}
148 static IMMHandler
* gIMMHandler
= nullptr;
150 /******************************************************************************
152 ******************************************************************************/
154 IMEContext::IMEContext(HWND aWnd
) : mWnd(aWnd
), mIMC(::ImmGetContext(aWnd
)) {}
156 IMEContext::IMEContext(nsWindow
* aWindowBase
)
157 : mWnd(aWindowBase
->GetWindowHandle()),
158 mIMC(::ImmGetContext(aWindowBase
->GetWindowHandle())) {}
160 void IMEContext::Init(HWND aWnd
) {
163 mIMC
= ::ImmGetContext(mWnd
);
166 void IMEContext::Init(nsWindow
* aWindowBase
) {
167 Init(aWindowBase
->GetWindowHandle());
170 void IMEContext::Clear() {
172 ::ImmReleaseContext(mWnd
, mIMC
);
178 /******************************************************************************
180 ******************************************************************************/
182 static UINT sWM_MSIME_MOUSE
= 0; // mouse message for MSIME 98/2000
184 WritingMode
IMMHandler::sWritingModeOfCompositionFont
;
185 nsString
IMMHandler::sIMEName
;
186 UINT
IMMHandler::sCodePage
= 0;
187 DWORD
IMMHandler::sIMEProperty
= 0;
188 DWORD
IMMHandler::sIMEUIProperty
= 0;
189 bool IMMHandler::sAssumeVerticalWritingModeNotSupported
= false;
190 bool IMMHandler::sHasFocus
= false;
192 #define IMPL_IS_IME_ACTIVE(aReadableName, aActualName) \
193 bool IMMHandler::Is##aReadableName##Active() { \
194 return sIMEName.Equals(aActualName); \
197 IMPL_IS_IME_ACTIVE(ATOK2006
, u
"ATOK 2006")
198 IMPL_IS_IME_ACTIVE(ATOK2007
, u
"ATOK 2007")
199 IMPL_IS_IME_ACTIVE(ATOK2008
, u
"ATOK 2008")
200 IMPL_IS_IME_ACTIVE(ATOK2009
, u
"ATOK 2009")
201 IMPL_IS_IME_ACTIVE(ATOK2010
, u
"ATOK 2010")
202 // NOTE: Even on Windows for en-US, the name of Google Japanese Input is
203 // written in Japanese.
204 IMPL_IS_IME_ACTIVE(GoogleJapaneseInput
,
205 u
"Google \x65E5\x672C\x8A9E\x5165\x529B "
206 u
"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB")
207 IMPL_IS_IME_ACTIVE(Japanist2003
, u
"Japanist 2003")
209 #undef IMPL_IS_IME_ACTIVE
212 bool IMMHandler::IsActiveIMEInBlockList() {
213 if (sIMEName
.IsEmpty()) {
217 // ATOK started to be TIP of TSF since 2011. Older than it, i.e., ATOK 2010
218 // and earlier have a lot of problems even for daily use. Perhaps, the
219 // reason is Win 8 has a lot of changes around IMM-IME support and TSF,
220 // and ATOK 2010 is released earlier than Win 8.
221 // ATOK 2006 crashes while converting a word with candidate window.
222 // ATOK 2007 doesn't paint and resize suggest window and candidate window
223 // correctly (showing white window or too big window).
224 // ATOK 2008 and ATOK 2009 crash when user just opens their open state.
225 // ATOK 2010 isn't installable newly on Win 7 or later, but we have a lot of
227 if (IsWin8OrLater() &&
228 (IsATOK2006Active() || IsATOK2007Active() || IsATOK2008Active() ||
229 IsATOK2009Active() || IsATOK2010Active())) {
232 #endif // #ifdef _WIN64
237 void IMMHandler::EnsureHandlerInstance() {
239 gIMMHandler
= new IMMHandler();
244 void IMMHandler::Initialize() {
245 if (!sWM_MSIME_MOUSE
) {
246 sWM_MSIME_MOUSE
= ::RegisterWindowMessage(RWM_MOUSE
);
248 sAssumeVerticalWritingModeNotSupported
= Preferences::GetBool(
249 "intl.imm.vertical_writing.always_assume_not_supported", false);
250 InitKeyboardLayout(nullptr, ::GetKeyboardLayout(0));
254 void IMMHandler::Terminate() {
255 if (!gIMMHandler
) return;
257 gIMMHandler
= nullptr;
261 bool IMMHandler::IsComposingOnOurEditor() {
262 return gIMMHandler
&& gIMMHandler
->mIsComposing
;
266 bool IMMHandler::IsComposingWindow(nsWindow
* aWindow
) {
267 return gIMMHandler
&& gIMMHandler
->mComposingWindow
== aWindow
;
271 bool IMMHandler::IsTopLevelWindowOfComposition(nsWindow
* aWindow
) {
272 if (!gIMMHandler
|| !gIMMHandler
->mComposingWindow
) {
275 HWND wnd
= gIMMHandler
->mComposingWindow
->GetWindowHandle();
276 return WinUtils::GetTopLevelHWND(wnd
, true) == aWindow
->GetWindowHandle();
280 bool IMMHandler::ShouldDrawCompositionStringOurselves() {
281 // If current IME has special UI or its composition window should not
282 // positioned to caret position, we should now draw composition string
284 return !(sIMEProperty
& IME_PROP_SPECIAL_UI
) &&
285 (sIMEProperty
& IME_PROP_AT_CARET
);
289 bool IMMHandler::IsVerticalWritingSupported() {
290 // Even if IME claims that they support vertical writing mode but it may not
291 // support vertical writing mode for its candidate window.
292 if (sAssumeVerticalWritingModeNotSupported
) {
295 // Google Japanese Input doesn't support vertical writing mode. We should
296 // return false if it's active IME.
297 if (IsGoogleJapaneseInputActive()) {
300 return !!(sIMEUIProperty
& (UI_CAP_2700
| UI_CAP_ROT90
| UI_CAP_ROTANY
));
304 void IMMHandler::InitKeyboardLayout(nsWindow
* aWindow
, HKL aKeyboardLayout
) {
305 UINT IMENameLength
= ::ImmGetDescriptionW(aKeyboardLayout
, nullptr, 0);
307 // Add room for the terminating null character
308 sIMEName
.SetLength(++IMENameLength
);
310 ::ImmGetDescriptionW(aKeyboardLayout
, sIMEName
.get(), IMENameLength
);
311 // Adjust the length to ignore the terminating null character
312 sIMEName
.SetLength(IMENameLength
);
317 WORD langID
= LOWORD(aKeyboardLayout
);
318 ::GetLocaleInfoW(MAKELCID(langID
, SORT_DEFAULT
),
319 LOCALE_IDEFAULTANSICODEPAGE
| LOCALE_RETURN_NUMBER
,
320 (PWSTR
)&sCodePage
, sizeof(sCodePage
) / sizeof(WCHAR
));
321 sIMEProperty
= ::ImmGetProperty(aKeyboardLayout
, IGP_PROPERTY
);
322 sIMEUIProperty
= ::ImmGetProperty(aKeyboardLayout
, IGP_UI
);
324 // If active IME is a TIP of TSF, we cannot retrieve the name with IMM32 API.
325 // For hacking some bugs of some TIP, we should set an IME name from the
327 if (sCodePage
== 932 && sIMEName
.IsEmpty()) {
328 Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as",
332 // Whether the IME supports vertical writing mode might be changed or
333 // some IMEs may need specific font for their UI. Therefore, we should
334 // update composition font forcibly here.
336 MaybeAdjustCompositionFont(aWindow
, sWritingModeOfCompositionFont
, true);
339 MOZ_LOG(gIMELog
, LogLevel::Info
,
340 ("IMMHandler::InitKeyboardLayout, aKeyboardLayout=%08x (\"%s\"), "
341 "sCodePage=%lu, sIMEProperty=%s, sIMEUIProperty=%s",
342 aKeyboardLayout
, NS_ConvertUTF16toUTF8(sIMEName
).get(), sCodePage
,
343 GetIMEGeneralPropertyName(sIMEProperty
).get(),
344 GetIMEUIPropertyName(sIMEUIProperty
).get()));
348 UINT
IMMHandler::GetKeyboardCodePage() { return sCodePage
; }
351 IMENotificationRequests
IMMHandler::GetIMENotificationRequests() {
352 return IMENotificationRequests(
353 IMENotificationRequests::NOTIFY_POSITION_CHANGE
|
354 IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR
);
357 // used for checking the lParam of WM_IME_COMPOSITION
358 #define IS_COMPOSING_LPARAM(lParam) \
359 ((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS))
360 #define IS_COMMITTING_LPARAM(lParam) ((lParam)&GCS_RESULTSTR)
361 // Some IMEs (e.g., the standard IME for Korean) don't have caret position,
362 // then, we should not set caret position to compositionchange event.
363 #define NO_IME_CARET -1
365 IMMHandler::IMMHandler()
366 : mComposingWindow(nullptr),
367 mCursorPosition(NO_IME_CARET
),
368 mCompositionStart(0),
369 mIsComposing(false) {
370 MOZ_LOG(gIMELog
, LogLevel::Debug
, ("IMMHandler::IMMHandler is created"));
373 IMMHandler::~IMMHandler() {
376 gIMELog
, LogLevel::Error
,
377 (" IMMHandler::~IMMHandler, ERROR, the instance is still composing"));
379 MOZ_LOG(gIMELog
, LogLevel::Debug
, ("IMMHandler::IMMHandler is destroyed"));
382 nsresult
IMMHandler::EnsureClauseArray(int32_t aCount
) {
383 NS_ENSURE_ARG_MIN(aCount
, 0);
384 mClauseArray
.SetCapacity(aCount
+ 32);
388 nsresult
IMMHandler::EnsureAttributeArray(int32_t aCount
) {
389 NS_ENSURE_ARG_MIN(aCount
, 0);
390 mAttributeArray
.SetCapacity(aCount
+ 64);
395 void IMMHandler::CommitComposition(nsWindow
* aWindow
, bool aForce
) {
396 MOZ_LOG(gIMELog
, LogLevel::Info
,
397 ("IMMHandler::CommitComposition, aForce=%s, aWindow=%p, hWnd=%08x, "
398 "mComposingWindow=%p%s",
399 GetBoolName(aForce
), aWindow
, aWindow
->GetWindowHandle(),
400 gIMMHandler
? gIMMHandler
->mComposingWindow
: nullptr,
401 gIMMHandler
&& gIMMHandler
->mComposingWindow
402 ? IsComposingOnOurEditor() ? " (composing on editor)"
403 : " (composing on plug-in)"
405 if (!aForce
&& !IsComposingWindow(aWindow
)) {
409 IMEContext
context(aWindow
);
410 bool associated
= context
.AssociateDefaultContext();
411 MOZ_LOG(gIMELog
, LogLevel::Info
,
412 (" IMMHandler::CommitComposition, associated=%s",
413 GetBoolName(associated
)));
415 if (context
.IsValid()) {
416 ::ImmNotifyIME(context
.get(), NI_COMPOSITIONSTR
, CPS_COMPLETE
, 0);
417 ::ImmNotifyIME(context
.get(), NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
421 context
.Disassociate();
426 void IMMHandler::CancelComposition(nsWindow
* aWindow
, bool aForce
) {
427 MOZ_LOG(gIMELog
, LogLevel::Info
,
428 ("IMMHandler::CancelComposition, aForce=%s, aWindow=%p, hWnd=%08x, "
429 "mComposingWindow=%p%s",
430 GetBoolName(aForce
), aWindow
, aWindow
->GetWindowHandle(),
431 gIMMHandler
? gIMMHandler
->mComposingWindow
: nullptr,
432 gIMMHandler
&& gIMMHandler
->mComposingWindow
433 ? IsComposingOnOurEditor() ? " (composing on editor)"
434 : " (composing on plug-in)"
436 if (!aForce
&& !IsComposingWindow(aWindow
)) {
440 IMEContext
context(aWindow
);
441 bool associated
= context
.AssociateDefaultContext();
442 MOZ_LOG(gIMELog
, LogLevel::Info
,
443 (" IMMHandler::CancelComposition, associated=%s",
444 GetBoolName(associated
)));
446 if (context
.IsValid()) {
447 ::ImmNotifyIME(context
.get(), NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
451 context
.Disassociate();
456 void IMMHandler::OnFocusChange(bool aFocus
, nsWindow
* aWindow
) {
457 MOZ_LOG(gIMELog
, LogLevel::Info
,
458 ("IMMHandler::OnFocusChange(aFocus=%s, aWindow=%p), sHasFocus=%s, "
459 "IsComposingWindow(aWindow)=%s, aWindow->Destroyed()=%s",
460 GetBoolName(aFocus
), aWindow
, GetBoolName(sHasFocus
),
461 GetBoolName(IsComposingWindow(aWindow
)),
462 GetBoolName(aWindow
->Destroyed())));
465 IMEHandler::MaybeDestroyNativeCaret();
466 if (IsComposingWindow(aWindow
) && aWindow
->Destroyed()) {
467 CancelComposition(aWindow
);
471 gIMMHandler
->mContentSelection
.reset();
477 void IMMHandler::OnUpdateComposition(nsWindow
* aWindow
) {
482 IMEContext
context(aWindow
);
483 gIMMHandler
->SetIMERelatedWindowsPos(aWindow
, context
);
487 void IMMHandler::OnSelectionChange(nsWindow
* aWindow
,
488 const IMENotification
& aIMENotification
,
490 if (!aIMENotification
.mSelectionChangeData
.mCausedByComposition
&&
492 MaybeAdjustCompositionFont(
493 aWindow
, aIMENotification
.mSelectionChangeData
.GetWritingMode());
495 // MaybeAdjustCompositionFont() may create gIMMHandler. So, check it
496 // after a call of MaybeAdjustCompositionFont().
498 gIMMHandler
->mContentSelection
=
499 Some(ContentSelection(aIMENotification
.mSelectionChangeData
));
504 void IMMHandler::MaybeAdjustCompositionFont(nsWindow
* aWindow
,
505 const WritingMode
& aWritingMode
,
508 case 932: // Japanese Shift-JIS
509 case 936: // Simlified Chinese GBK
511 case 950: // Traditional Chinese Big5
512 EnsureHandlerInstance();
515 // If there is no instance of nsIMM32Hander, we shouldn't waste footprint.
521 // Like Navi-Bar of ATOK, some IMEs may require proper composition font even
522 // before sending WM_IME_STARTCOMPOSITION.
523 IMEContext
context(aWindow
);
524 gIMMHandler
->AdjustCompositionFont(aWindow
, context
, aWritingMode
,
529 bool IMMHandler::ProcessInputLangChangeMessage(nsWindow
* aWindow
, WPARAM wParam
,
531 MSGResult
& aResult
) {
533 aResult
.mConsumed
= false;
534 // We don't need to create the instance of the handler here.
536 gIMMHandler
->OnInputLangChange(aWindow
, wParam
, lParam
, aResult
);
538 InitKeyboardLayout(aWindow
, reinterpret_cast<HKL
>(lParam
));
539 // We can release the instance here, because the instance may be never
540 // used. E.g., the new keyboard layout may not use IME, or it may use TSF.
542 // Don't return as "processed", the messages should be processed on nsWindow
548 bool IMMHandler::ProcessMessage(nsWindow
* aWindow
, UINT msg
, WPARAM
& wParam
,
549 LPARAM
& lParam
, MSGResult
& aResult
) {
550 // XXX We store the composing window in mComposingWindow. If IME messages are
551 // sent to different window, we should commit the old transaction. And also
552 // if the new window handle is not focused, probably, we should not start
553 // the composition, however, such case should not be, it's just bad scenario.
557 case WM_INPUTLANGCHANGE
:
558 return ProcessInputLangChangeMessage(aWindow
, wParam
, lParam
, aResult
);
559 case WM_IME_STARTCOMPOSITION
:
560 EnsureHandlerInstance();
561 return gIMMHandler
->OnIMEStartComposition(aWindow
, aResult
);
562 case WM_IME_COMPOSITION
:
563 EnsureHandlerInstance();
564 return gIMMHandler
->OnIMEComposition(aWindow
, wParam
, lParam
, aResult
);
565 case WM_IME_ENDCOMPOSITION
:
566 EnsureHandlerInstance();
567 return gIMMHandler
->OnIMEEndComposition(aWindow
, aResult
);
569 return OnIMEChar(aWindow
, wParam
, lParam
, aResult
);
571 return OnIMENotify(aWindow
, wParam
, lParam
, aResult
);
573 EnsureHandlerInstance();
574 return gIMMHandler
->OnIMERequest(aWindow
, wParam
, lParam
, aResult
);
576 return OnIMESelect(aWindow
, wParam
, lParam
, aResult
);
577 case WM_IME_SETCONTEXT
:
578 return OnIMESetContext(aWindow
, wParam
, lParam
, aResult
);
580 return OnKeyDownEvent(aWindow
, wParam
, lParam
, aResult
);
585 return gIMMHandler
->OnChar(aWindow
, wParam
, lParam
, aResult
);
591 /****************************************************************************
593 ****************************************************************************/
595 void IMMHandler::OnInputLangChange(nsWindow
* aWindow
, WPARAM wParam
,
596 LPARAM lParam
, MSGResult
& aResult
) {
597 MOZ_LOG(gIMELog
, LogLevel::Info
,
598 ("IMMHandler::OnInputLangChange, hWnd=%08x, wParam=%08x, lParam=%08x",
599 aWindow
->GetWindowHandle(), wParam
, lParam
));
601 aWindow
->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION
);
602 NS_ASSERTION(!mIsComposing
, "ResetInputState failed");
605 HandleEndComposition(aWindow
);
608 aResult
.mConsumed
= false;
611 bool IMMHandler::OnIMEStartComposition(nsWindow
* aWindow
, MSGResult
& aResult
) {
612 MOZ_LOG(gIMELog
, LogLevel::Info
,
613 ("IMMHandler::OnIMEStartComposition, hWnd=%08x, mIsComposing=%s",
614 aWindow
->GetWindowHandle(), GetBoolName(mIsComposing
)));
615 aResult
.mConsumed
= ShouldDrawCompositionStringOurselves();
617 NS_WARNING("Composition has been already started");
621 IMEContext
context(aWindow
);
622 HandleStartComposition(aWindow
, context
);
626 bool IMMHandler::OnIMEComposition(nsWindow
* aWindow
, WPARAM wParam
,
627 LPARAM lParam
, MSGResult
& aResult
) {
629 gIMELog
, LogLevel::Info
,
630 ("IMMHandler::OnIMEComposition, hWnd=%08x, lParam=%08x, mIsComposing=%s, "
631 "GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, "
633 aWindow
->GetWindowHandle(), lParam
, GetBoolName(mIsComposing
),
634 GetBoolName(lParam
& GCS_RESULTSTR
), GetBoolName(lParam
& GCS_COMPSTR
),
635 GetBoolName(lParam
& GCS_COMPATTR
), GetBoolName(lParam
& GCS_COMPCLAUSE
),
636 GetBoolName(lParam
& GCS_CURSORPOS
)));
638 IMEContext
context(aWindow
);
639 aResult
.mConsumed
= HandleComposition(aWindow
, context
, lParam
);
643 bool IMMHandler::OnIMEEndComposition(nsWindow
* aWindow
, MSGResult
& aResult
) {
644 MOZ_LOG(gIMELog
, LogLevel::Info
,
645 ("IMMHandler::OnIMEEndComposition, hWnd=%08x, mIsComposing=%s",
646 aWindow
->GetWindowHandle(), GetBoolName(mIsComposing
)));
648 aResult
.mConsumed
= ShouldDrawCompositionStringOurselves();
653 // Korean IME posts WM_IME_ENDCOMPOSITION first when we hit space during
654 // composition. Then, we should ignore the message and commit the composition
655 // string at following WM_IME_COMPOSITION.
657 if (WinUtils::PeekMessage(&compositionMsg
, aWindow
->GetWindowHandle(),
658 WM_IME_STARTCOMPOSITION
, WM_IME_COMPOSITION
,
660 compositionMsg
.message
== WM_IME_COMPOSITION
&&
661 IS_COMMITTING_LPARAM(compositionMsg
.lParam
)) {
662 MOZ_LOG(gIMELog
, LogLevel::Info
,
663 (" IMMHandler::OnIMEEndComposition, WM_IME_ENDCOMPOSITION is "
664 "followed by WM_IME_COMPOSITION, ignoring the message..."));
668 // Otherwise, e.g., ChangJie doesn't post WM_IME_COMPOSITION before
669 // WM_IME_ENDCOMPOSITION when composition string becomes empty.
670 // Then, we should dispatch a compositionupdate event, a compositionchange
671 // event and a compositionend event.
672 // XXX Shouldn't we dispatch the compositionchange event with actual or
673 // latest composition string?
674 MOZ_LOG(gIMELog
, LogLevel::Info
,
675 (" IMMHandler::OnIMEEndComposition, mCompositionString=\"%s\"%s",
676 NS_ConvertUTF16toUTF8(mCompositionString
).get(),
677 mCompositionString
.IsEmpty() ? "" : ", but canceling it..."));
679 HandleEndComposition(aWindow
, &EmptyString());
685 bool IMMHandler::OnIMEChar(nsWindow
* aWindow
, WPARAM wParam
, LPARAM lParam
,
686 MSGResult
& aResult
) {
687 MOZ_LOG(gIMELog
, LogLevel::Info
,
688 ("IMMHandler::OnIMEChar, hWnd=%08x, char=%08x",
689 aWindow
->GetWindowHandle(), wParam
));
691 // We don't need to fire any compositionchange events from here. This method
692 // will be called when the composition string of the current IME is not drawn
693 // by us and some characters are committed. In that case, the committed
694 // string was processed in nsWindow::OnIMEComposition already.
696 // We need to consume the message so that Windows don't send two WM_CHAR msgs
697 aResult
.mConsumed
= true;
702 bool IMMHandler::OnIMECompositionFull(nsWindow
* aWindow
, MSGResult
& aResult
) {
703 MOZ_LOG(gIMELog
, LogLevel::Info
,
704 ("IMMHandler::OnIMECompositionFull, hWnd=%08x",
705 aWindow
->GetWindowHandle()));
708 aResult
.mConsumed
= false;
713 bool IMMHandler::OnIMENotify(nsWindow
* aWindow
, WPARAM wParam
, LPARAM lParam
,
714 MSGResult
& aResult
) {
716 case IMN_CHANGECANDIDATE
:
717 MOZ_LOG(gIMELog
, LogLevel::Info
,
718 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_CHANGECANDIDATE, "
720 aWindow
->GetWindowHandle(), lParam
));
722 case IMN_CLOSECANDIDATE
:
723 MOZ_LOG(gIMELog
, LogLevel::Info
,
724 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_CLOSECANDIDATE, "
726 aWindow
->GetWindowHandle(), lParam
));
728 case IMN_CLOSESTATUSWINDOW
:
729 MOZ_LOG(gIMELog
, LogLevel::Info
,
730 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_CLOSESTATUSWINDOW",
731 aWindow
->GetWindowHandle()));
734 MOZ_LOG(gIMELog
, LogLevel::Info
,
735 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_GUIDELINE",
736 aWindow
->GetWindowHandle()));
738 case IMN_OPENCANDIDATE
:
740 gIMELog
, LogLevel::Info
,
741 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_OPENCANDIDATE, lParam=%08x",
742 aWindow
->GetWindowHandle(), lParam
));
744 case IMN_OPENSTATUSWINDOW
:
745 MOZ_LOG(gIMELog
, LogLevel::Info
,
746 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_OPENSTATUSWINDOW",
747 aWindow
->GetWindowHandle()));
749 case IMN_SETCANDIDATEPOS
:
750 MOZ_LOG(gIMELog
, LogLevel::Info
,
751 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETCANDIDATEPOS, "
753 aWindow
->GetWindowHandle(), lParam
));
755 case IMN_SETCOMPOSITIONFONT
:
756 MOZ_LOG(gIMELog
, LogLevel::Info
,
757 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONFONT",
758 aWindow
->GetWindowHandle()));
760 case IMN_SETCOMPOSITIONWINDOW
:
761 MOZ_LOG(gIMELog
, LogLevel::Info
,
762 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONWINDOW",
763 aWindow
->GetWindowHandle()));
765 case IMN_SETCONVERSIONMODE
:
766 MOZ_LOG(gIMELog
, LogLevel::Info
,
767 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETCONVERSIONMODE",
768 aWindow
->GetWindowHandle()));
770 case IMN_SETOPENSTATUS
:
771 MOZ_LOG(gIMELog
, LogLevel::Info
,
772 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETOPENSTATUS",
773 aWindow
->GetWindowHandle()));
775 case IMN_SETSENTENCEMODE
:
776 MOZ_LOG(gIMELog
, LogLevel::Info
,
777 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETSENTENCEMODE",
778 aWindow
->GetWindowHandle()));
780 case IMN_SETSTATUSWINDOWPOS
:
781 MOZ_LOG(gIMELog
, LogLevel::Info
,
782 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETSTATUSWINDOWPOS",
783 aWindow
->GetWindowHandle()));
786 MOZ_LOG(gIMELog
, LogLevel::Info
,
787 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_PRIVATE",
788 aWindow
->GetWindowHandle()));
793 aResult
.mConsumed
= false;
797 bool IMMHandler::OnIMERequest(nsWindow
* aWindow
, WPARAM wParam
, LPARAM lParam
,
798 MSGResult
& aResult
) {
800 case IMR_RECONVERTSTRING
:
801 MOZ_LOG(gIMELog
, LogLevel::Info
,
802 ("IMMHandler::OnIMERequest, hWnd=%08x, IMR_RECONVERTSTRING",
803 aWindow
->GetWindowHandle()));
804 aResult
.mConsumed
= HandleReconvert(aWindow
, lParam
, &aResult
.mResult
);
806 case IMR_QUERYCHARPOSITION
:
807 MOZ_LOG(gIMELog
, LogLevel::Info
,
808 ("IMMHandler::OnIMERequest, hWnd=%08x, IMR_QUERYCHARPOSITION",
809 aWindow
->GetWindowHandle()));
811 HandleQueryCharPosition(aWindow
, lParam
, &aResult
.mResult
);
813 case IMR_DOCUMENTFEED
:
814 MOZ_LOG(gIMELog
, LogLevel::Info
,
815 ("IMMHandler::OnIMERequest, hWnd=%08x, IMR_DOCUMENTFEED",
816 aWindow
->GetWindowHandle()));
817 aResult
.mConsumed
= HandleDocumentFeed(aWindow
, lParam
, &aResult
.mResult
);
820 MOZ_LOG(gIMELog
, LogLevel::Info
,
821 ("IMMHandler::OnIMERequest, hWnd=%08x, wParam=%08x",
822 aWindow
->GetWindowHandle(), wParam
));
823 aResult
.mConsumed
= false;
829 bool IMMHandler::OnIMESelect(nsWindow
* aWindow
, WPARAM wParam
, LPARAM lParam
,
830 MSGResult
& aResult
) {
831 MOZ_LOG(gIMELog
, LogLevel::Info
,
832 ("IMMHandler::OnIMESelect, hWnd=%08x, wParam=%08x, lParam=%08x",
833 aWindow
->GetWindowHandle(), wParam
, lParam
));
836 aResult
.mConsumed
= false;
841 bool IMMHandler::OnIMESetContext(nsWindow
* aWindow
, WPARAM wParam
,
842 LPARAM lParam
, MSGResult
& aResult
) {
843 MOZ_LOG(gIMELog
, LogLevel::Info
,
844 ("IMMHandler::OnIMESetContext, hWnd=%08x, %s, lParam=%08x",
845 aWindow
->GetWindowHandle(), wParam
? "Active" : "Deactive", lParam
));
847 aResult
.mConsumed
= false;
849 // NOTE: If the aWindow is top level window of the composing window because
850 // when a window on deactive window gets focus, WM_IME_SETCONTEXT (wParam is
851 // TRUE) is sent to the top level window first. After that,
852 // WM_IME_SETCONTEXT (wParam is FALSE) is sent to the top level window.
853 // Finally, WM_IME_SETCONTEXT (wParam is TRUE) is sent to the focused window.
854 // The top level window never becomes composing window, so, we can ignore
855 // the WM_IME_SETCONTEXT on the top level window.
856 if (IsTopLevelWindowOfComposition(aWindow
)) {
857 MOZ_LOG(gIMELog
, LogLevel::Info
,
858 (" IMMHandler::OnIMESetContext, hWnd=%08x is top level window"));
862 // When IME context is activating on another window,
863 // we should commit the old composition on the old window.
864 bool cancelComposition
= false;
865 if (wParam
&& gIMMHandler
) {
866 cancelComposition
= gIMMHandler
->CommitCompositionOnPreviousWindow(aWindow
);
869 if (wParam
&& (lParam
& ISC_SHOWUICOMPOSITIONWINDOW
) &&
870 ShouldDrawCompositionStringOurselves()) {
871 MOZ_LOG(gIMELog
, LogLevel::Info
,
872 (" IMMHandler::OnIMESetContext, ISC_SHOWUICOMPOSITIONWINDOW is "
874 lParam
&= ~ISC_SHOWUICOMPOSITIONWINDOW
;
877 // We should sent WM_IME_SETCONTEXT to the DefWndProc here because the
878 // ancestor windows shouldn't receive this message. If they receive the
879 // message, we cannot know whether which window is the target of the message.
880 aResult
.mResult
= ::DefWindowProc(aWindow
->GetWindowHandle(),
881 WM_IME_SETCONTEXT
, wParam
, lParam
);
883 // Cancel composition on the new window if we committed our composition on
885 if (cancelComposition
) {
886 CancelComposition(aWindow
, true);
889 aResult
.mConsumed
= true;
893 bool IMMHandler::OnChar(nsWindow
* aWindow
, WPARAM wParam
, LPARAM lParam
,
894 MSGResult
& aResult
) {
895 // The return value must be same as aResult.mConsumed because only when we
896 // consume the message, the caller shouldn't do anything anymore but
897 // otherwise, the caller should handle the message.
898 aResult
.mConsumed
= false;
899 if (IsIMECharRecordsEmpty()) {
900 return aResult
.mConsumed
;
904 DequeueIMECharRecords(recWParam
, recLParam
);
905 MOZ_LOG(gIMELog
, LogLevel::Info
,
906 ("IMMHandler::OnChar, aWindow=%p, wParam=%08x, lParam=%08x, "
907 "recorded: wParam=%08x, lParam=%08x",
908 aWindow
->GetWindowHandle(), wParam
, lParam
, recWParam
, recLParam
));
909 // If an unexpected char message comes, we should reset the records,
910 // of course, this shouldn't happen.
911 if (recWParam
!= wParam
|| recLParam
!= lParam
) {
912 ResetIMECharRecords();
913 return aResult
.mConsumed
;
915 // Eat the char message which is caused by WM_IME_CHAR because we should
916 // have processed the IME messages, so, this message could be come from
917 // a windowless plug-in.
918 aResult
.mConsumed
= true;
919 return aResult
.mConsumed
;
922 /****************************************************************************
924 ****************************************************************************/
926 TextEventDispatcher
* IMMHandler::GetTextEventDispatcherFor(nsWindow
* aWindow
) {
927 return aWindow
== mComposingWindow
&& mDispatcher
929 : aWindow
->GetTextEventDispatcher();
932 void IMMHandler::HandleStartComposition(nsWindow
* aWindow
,
933 const IMEContext
& aContext
) {
934 MOZ_ASSERT(!mIsComposing
,
935 "HandleStartComposition is called but mIsComposing is TRUE");
937 const Maybe
<ContentSelection
>& contentSelection
=
938 GetContentSelectionWithQueryIfNothing(aWindow
);
939 if (contentSelection
.isNothing()) {
940 MOZ_LOG(gIMELog
, LogLevel::Error
,
941 (" IMMHandler::HandleStartComposition, FAILED, due to "
942 "Selection::GetContentSelectionWithQueryIfNothing() failure"));
945 if (!contentSelection
->HasRange()) {
946 MOZ_LOG(gIMELog
, LogLevel::Error
,
947 (" IMMHandler::HandleStartComposition, FAILED, due to "
948 "there is no selection"));
952 AdjustCompositionFont(aWindow
, aContext
, contentSelection
->WritingModeRef());
954 mCompositionStart
= contentSelection
->OffsetAndDataRef().StartOffset();
955 mCursorPosition
= NO_IME_CARET
;
957 RefPtr
<TextEventDispatcher
> dispatcher
= GetTextEventDispatcherFor(aWindow
);
958 nsresult rv
= dispatcher
->BeginNativeInputTransaction();
959 if (NS_WARN_IF(NS_FAILED(rv
))) {
960 MOZ_LOG(gIMELog
, LogLevel::Error
,
961 (" IMMHandler::HandleStartComposition, FAILED due to "
962 "TextEventDispatcher::BeginNativeInputTransaction() failure"));
965 WidgetEventTime eventTime
= aWindow
->CurrentMessageWidgetEventTime();
966 nsEventStatus status
;
967 rv
= dispatcher
->StartComposition(status
, &eventTime
);
968 if (NS_WARN_IF(NS_FAILED(rv
))) {
969 MOZ_LOG(gIMELog
, LogLevel::Error
,
970 (" IMMHandler::HandleStartComposition, FAILED, due to "
971 "TextEventDispatcher::StartComposition() failure"));
976 mComposingWindow
= aWindow
;
977 mDispatcher
= dispatcher
;
979 MOZ_LOG(gIMELog
, LogLevel::Info
,
980 ("IMMHandler::HandleStartComposition, START composition, "
981 "mCompositionStart=%ld",
985 bool IMMHandler::HandleComposition(nsWindow
* aWindow
,
986 const IMEContext
& aContext
, LPARAM lParam
) {
988 // MS-IME 95/97/98/2000 may send WM_IME_COMPOSITION with non-conversion
989 // mode before it send WM_IME_STARTCOMPOSITION.
990 // However, ATOK sends a WM_IME_COMPOSITION before WM_IME_STARTCOMPOSITION,
991 // and if we access ATOK via some APIs, ATOK will sometimes fail to
992 // initialize its state. If WM_IME_STARTCOMPOSITION is already in the
993 // message queue, we should ignore the strange WM_IME_COMPOSITION message and
994 // skip to the next. So, we should look for next composition message
995 // (WM_IME_STARTCOMPOSITION or WM_IME_ENDCOMPOSITION or WM_IME_COMPOSITION),
996 // and if it's WM_IME_STARTCOMPOSITION, and one more next composition message
997 // is WM_IME_COMPOSITION, current IME is ATOK, probably. Otherwise, we
998 // should start composition forcibly.
1001 HWND wnd
= aWindow
->GetWindowHandle();
1002 if (WinUtils::PeekMessage(&msg1
, wnd
, WM_IME_STARTCOMPOSITION
,
1003 WM_IME_COMPOSITION
, PM_NOREMOVE
) &&
1004 msg1
.message
== WM_IME_STARTCOMPOSITION
&&
1005 WinUtils::PeekMessage(&msg2
, wnd
, WM_IME_ENDCOMPOSITION
,
1006 WM_IME_COMPOSITION
, PM_NOREMOVE
) &&
1007 msg2
.message
== WM_IME_COMPOSITION
) {
1008 MOZ_LOG(gIMELog
, LogLevel::Info
,
1009 ("IMMHandler::HandleComposition, Ignores due to find a "
1010 "WM_IME_STARTCOMPOSITION"));
1011 return ShouldDrawCompositionStringOurselves();
1015 bool startCompositionMessageHasBeenSent
= mIsComposing
;
1018 // This catches a fixed result
1020 if (IS_COMMITTING_LPARAM(lParam
)) {
1021 if (!mIsComposing
) {
1022 HandleStartComposition(aWindow
, aContext
);
1025 GetCompositionString(aContext
, GCS_RESULTSTR
, mCompositionString
);
1027 MOZ_LOG(gIMELog
, LogLevel::Info
,
1028 ("IMMHandler::HandleComposition, GCS_RESULTSTR"));
1030 HandleEndComposition(aWindow
, &mCompositionString
);
1032 if (!IS_COMPOSING_LPARAM(lParam
)) {
1033 return ShouldDrawCompositionStringOurselves();
1038 // This provides us with a composition string
1040 if (!mIsComposing
) {
1041 HandleStartComposition(aWindow
, aContext
);
1044 //--------------------------------------------------------
1045 // 1. Get GCS_COMPSTR
1046 //--------------------------------------------------------
1047 MOZ_LOG(gIMELog
, LogLevel::Info
,
1048 ("IMMHandler::HandleComposition, GCS_COMPSTR"));
1050 nsAutoString
previousCompositionString(mCompositionString
);
1051 GetCompositionString(aContext
, GCS_COMPSTR
, mCompositionString
);
1053 if (!IS_COMPOSING_LPARAM(lParam
)) {
1055 gIMELog
, LogLevel::Info
,
1056 (" IMMHandler::HandleComposition, lParam doesn't indicate composing, "
1057 "mCompositionString=\"%s\", previousCompositionString=\"%s\"",
1058 NS_ConvertUTF16toUTF8(mCompositionString
).get(),
1059 NS_ConvertUTF16toUTF8(previousCompositionString
).get()));
1061 // If composition string isn't changed, we can trust the lParam.
1062 // So, we need to do nothing.
1063 if (previousCompositionString
== mCompositionString
) {
1064 return ShouldDrawCompositionStringOurselves();
1067 // IME may send WM_IME_COMPOSITION without composing lParam values
1068 // when composition string becomes empty (e.g., using Backspace key).
1069 // If composition string is empty, we should dispatch a compositionchange
1070 // event with empty string and clear the clause information.
1071 if (mCompositionString
.IsEmpty()) {
1072 mClauseArray
.Clear();
1073 mAttributeArray
.Clear();
1074 mCursorPosition
= 0;
1075 DispatchCompositionChangeEvent(aWindow
, aContext
);
1076 return ShouldDrawCompositionStringOurselves();
1079 // Otherwise, we cannot trust the lParam value. We might need to
1080 // dispatch compositionchange event with the latest composition string
1084 // See https://bugzilla.mozilla.org/show_bug.cgi?id=296339
1085 if (mCompositionString
.IsEmpty() && !startCompositionMessageHasBeenSent
) {
1086 // In this case, maybe, the sender is MSPinYin. That sends *only*
1087 // WM_IME_COMPOSITION with GCS_COMP* and GCS_RESULT* when
1088 // user inputted the Chinese full stop. So, that doesn't send
1089 // WM_IME_STARTCOMPOSITION and WM_IME_ENDCOMPOSITION.
1090 // If WM_IME_STARTCOMPOSITION was not sent and the composition
1091 // string is null (it indicates the composition transaction ended),
1092 // WM_IME_ENDCOMPOSITION may not be sent. If so, we cannot run
1093 // HandleEndComposition() in other place.
1094 MOZ_LOG(gIMELog
, LogLevel::Info
,
1095 (" IMMHandler::HandleComposition, Aborting GCS_COMPSTR"));
1096 HandleEndComposition(aWindow
);
1097 return IS_COMMITTING_LPARAM(lParam
);
1100 //--------------------------------------------------------
1101 // 2. Get GCS_COMPCLAUSE
1102 //--------------------------------------------------------
1103 long clauseArrayLength
=
1104 ::ImmGetCompositionStringW(aContext
.get(), GCS_COMPCLAUSE
, nullptr, 0);
1105 clauseArrayLength
/= sizeof(uint32_t);
1107 if (clauseArrayLength
> 0) {
1108 nsresult rv
= EnsureClauseArray(clauseArrayLength
);
1109 NS_ENSURE_SUCCESS(rv
, false);
1111 // Intelligent ABC IME (Simplified Chinese IME, the code page is 936)
1112 // will crash in ImmGetCompositionStringW for GCS_COMPCLAUSE (bug 424663).
1113 // See comment 35 of the bug for the detail. Therefore, we should use A
1114 // API for it, however, we should not kill Unicode support on all IMEs.
1115 bool useA_API
= !(sIMEProperty
& IME_PROP_UNICODE
);
1117 MOZ_LOG(gIMELog
, LogLevel::Info
,
1118 (" IMMHandler::HandleComposition, GCS_COMPCLAUSE, useA_API=%s",
1119 useA_API
? "TRUE" : "FALSE"));
1121 long clauseArrayLength2
=
1122 useA_API
? ::ImmGetCompositionStringA(
1123 aContext
.get(), GCS_COMPCLAUSE
, mClauseArray
.Elements(),
1124 mClauseArray
.Capacity() * sizeof(uint32_t))
1125 : ::ImmGetCompositionStringW(
1126 aContext
.get(), GCS_COMPCLAUSE
, mClauseArray
.Elements(),
1127 mClauseArray
.Capacity() * sizeof(uint32_t));
1128 clauseArrayLength2
/= sizeof(uint32_t);
1130 if (clauseArrayLength
!= clauseArrayLength2
) {
1131 MOZ_LOG(gIMELog
, LogLevel::Info
,
1132 (" IMMHandler::HandleComposition, GCS_COMPCLAUSE, "
1133 "clauseArrayLength=%ld but clauseArrayLength2=%ld",
1134 clauseArrayLength
, clauseArrayLength2
));
1135 if (clauseArrayLength
> clauseArrayLength2
)
1136 clauseArrayLength
= clauseArrayLength2
;
1139 if (useA_API
&& clauseArrayLength
> 0) {
1140 // Convert each values of sIMECompClauseArray. The values mean offset of
1141 // the clauses in ANSI string. But we need the values in Unicode string.
1142 nsAutoCString compANSIStr
;
1143 if (ConvertToANSIString(mCompositionString
, GetKeyboardCodePage(),
1145 uint32_t maxlen
= compANSIStr
.Length();
1146 mClauseArray
.SetLength(clauseArrayLength
);
1147 mClauseArray
[0] = 0; // first value must be 0
1148 for (int32_t i
= 1; i
< clauseArrayLength
; i
++) {
1149 uint32_t len
= std::min(mClauseArray
[i
], maxlen
);
1151 ::MultiByteToWideChar(GetKeyboardCodePage(), MB_PRECOMPOSED
,
1152 (LPCSTR
)compANSIStr
.get(), len
, nullptr, 0);
1157 // compClauseArrayLength may be negative. I.e., ImmGetCompositionStringW
1158 // may return an error code.
1159 mClauseArray
.SetLength(std::max
<long>(0, clauseArrayLength
));
1161 MOZ_LOG(gIMELog
, LogLevel::Info
,
1162 (" IMMHandler::HandleComposition, GCS_COMPCLAUSE, mClauseLength=%ld",
1163 mClauseArray
.Length()));
1165 //--------------------------------------------------------
1166 // 3. Get GCS_COMPATTR
1167 //--------------------------------------------------------
1168 // This provides us with the attribute string necessary
1169 // for doing hiliting
1170 long attrArrayLength
=
1171 ::ImmGetCompositionStringW(aContext
.get(), GCS_COMPATTR
, nullptr, 0);
1172 attrArrayLength
/= sizeof(uint8_t);
1174 if (attrArrayLength
> 0) {
1175 nsresult rv
= EnsureAttributeArray(attrArrayLength
);
1176 NS_ENSURE_SUCCESS(rv
, false);
1177 attrArrayLength
= ::ImmGetCompositionStringW(
1178 aContext
.get(), GCS_COMPATTR
, mAttributeArray
.Elements(),
1179 mAttributeArray
.Capacity() * sizeof(uint8_t));
1182 // attrStrLen may be negative. I.e., ImmGetCompositionStringW may return an
1184 mAttributeArray
.SetLength(std::max
<long>(0, attrArrayLength
));
1187 gIMELog
, LogLevel::Info
,
1188 (" IMMHandler::HandleComposition, GCS_COMPATTR, mAttributeLength=%ld",
1189 mAttributeArray
.Length()));
1191 //--------------------------------------------------------
1192 // 4. Get GCS_CURSOPOS
1193 //--------------------------------------------------------
1194 // Some IMEs (e.g., the standard IME for Korean) don't have caret position.
1195 if (lParam
& GCS_CURSORPOS
) {
1197 ::ImmGetCompositionStringW(aContext
.get(), GCS_CURSORPOS
, nullptr, 0);
1198 if (mCursorPosition
< 0) {
1199 mCursorPosition
= NO_IME_CARET
; // The result is error
1202 mCursorPosition
= NO_IME_CARET
;
1205 NS_ASSERTION(mCursorPosition
<= (long)mCompositionString
.Length(),
1208 MOZ_LOG(gIMELog
, LogLevel::Info
,
1209 (" IMMHandler::HandleComposition, GCS_CURSORPOS, mCursorPosition=%d",
1212 //--------------------------------------------------------
1213 // 5. Send the compositionchange event
1214 //--------------------------------------------------------
1215 DispatchCompositionChangeEvent(aWindow
, aContext
);
1217 return ShouldDrawCompositionStringOurselves();
1220 void IMMHandler::HandleEndComposition(nsWindow
* aWindow
,
1221 const nsAString
* aCommitString
) {
1222 MOZ_ASSERT(mIsComposing
,
1223 "HandleEndComposition is called but mIsComposing is FALSE");
1225 MOZ_LOG(gIMELog
, LogLevel::Info
,
1226 ("IMMHandler::HandleEndComposition(aWindow=0x%p, aCommitString=0x%p "
1228 aWindow
, aCommitString
,
1229 aCommitString
? NS_ConvertUTF16toUTF8(*aCommitString
).get() : ""));
1231 IMEHandler::MaybeDestroyNativeCaret();
1233 RefPtr
<TextEventDispatcher
> dispatcher
= GetTextEventDispatcherFor(aWindow
);
1234 nsresult rv
= dispatcher
->BeginNativeInputTransaction();
1235 if (NS_WARN_IF(NS_FAILED(rv
))) {
1236 MOZ_LOG(gIMELog
, LogLevel::Error
,
1237 (" IMMHandler::HandleEndComposition, FAILED due to "
1238 "TextEventDispatcher::BeginNativeInputTransaction() failure"));
1241 WidgetEventTime eventTime
= aWindow
->CurrentMessageWidgetEventTime();
1242 nsEventStatus status
;
1243 rv
= dispatcher
->CommitComposition(status
, aCommitString
, &eventTime
);
1244 if (NS_WARN_IF(NS_FAILED(rv
))) {
1245 MOZ_LOG(gIMELog
, LogLevel::Error
,
1246 (" IMMHandler::HandleStartComposition, FAILED, due to "
1247 "TextEventDispatcher::CommitComposition() failure"));
1250 mIsComposing
= false;
1251 // XXX aWindow and mComposingWindow are always same??
1252 mComposingWindow
= nullptr;
1253 mDispatcher
= nullptr;
1256 bool IMMHandler::HandleReconvert(nsWindow
* aWindow
, LPARAM lParam
,
1259 RECONVERTSTRING
* pReconv
= reinterpret_cast<RECONVERTSTRING
*>(lParam
);
1261 const Maybe
<ContentSelection
>& contentSelection
=
1262 GetContentSelectionWithQueryIfNothing(aWindow
);
1263 if (contentSelection
.isNothing()) {
1264 MOZ_LOG(gIMELog
, LogLevel::Error
,
1265 ("IMMHandler::HandleReconvert, FAILED, due to "
1266 "Selection::GetContentSelectionWithQueryIfNothing() failure"));
1270 const uint32_t len
= contentSelection
->HasRange()
1271 ? contentSelection
->OffsetAndDataRef().Length()
1273 uint32_t needSize
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
1276 // Return need size to reconvert.
1278 MOZ_LOG(gIMELog
, LogLevel::Error
,
1279 ("IMMHandler::HandleReconvert, There are not selected text"));
1282 *oResult
= needSize
;
1283 MOZ_LOG(gIMELog
, LogLevel::Info
,
1284 ("IMMHandler::HandleReconvert, succeeded, result=%ld", *oResult
));
1288 if (pReconv
->dwSize
< needSize
) {
1289 MOZ_LOG(gIMELog
, LogLevel::Info
,
1290 ("IMMHandler::HandleReconvert, FAILED, pReconv->dwSize=%ld, "
1292 pReconv
->dwSize
, needSize
));
1296 *oResult
= needSize
;
1298 // Fill reconvert struct
1299 pReconv
->dwVersion
= 0;
1300 pReconv
->dwStrLen
= len
;
1301 pReconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
1302 pReconv
->dwCompStrLen
= len
;
1303 pReconv
->dwCompStrOffset
= 0;
1304 pReconv
->dwTargetStrLen
= len
;
1305 pReconv
->dwTargetStrOffset
= 0;
1308 ::CopyMemory(reinterpret_cast<LPVOID
>(lParam
+ sizeof(RECONVERTSTRING
)),
1309 contentSelection
->OffsetAndDataRef().DataRef().get(),
1310 len
* sizeof(WCHAR
));
1313 MOZ_LOG(gIMELog
, LogLevel::Info
,
1314 ("IMMHandler::HandleReconvert, SUCCEEDED, pReconv=%s, result=%ld",
1315 GetReconvertStringLog(pReconv
).get(), *oResult
));
1320 bool IMMHandler::HandleQueryCharPosition(nsWindow
* aWindow
, LPARAM lParam
,
1322 uint32_t len
= mIsComposing
? mCompositionString
.Length() : 0;
1324 IMECHARPOSITION
* pCharPosition
= reinterpret_cast<IMECHARPOSITION
*>(lParam
);
1325 if (!pCharPosition
) {
1326 MOZ_LOG(gIMELog
, LogLevel::Error
,
1327 ("IMMHandler::HandleQueryCharPosition, FAILED, due to "
1328 "pCharPosition is null"));
1331 if (pCharPosition
->dwSize
< sizeof(IMECHARPOSITION
)) {
1332 MOZ_LOG(gIMELog
, LogLevel::Error
,
1333 ("IMMHandler::HandleReconvert, FAILED, pCharPosition->dwSize=%ld, "
1334 "sizeof(IMECHARPOSITION)=%ld",
1335 pCharPosition
->dwSize
, sizeof(IMECHARPOSITION
)));
1338 if (::GetFocus() != aWindow
->GetWindowHandle()) {
1339 MOZ_LOG(gIMELog
, LogLevel::Error
,
1340 ("IMMHandler::HandleReconvert, FAILED, ::GetFocus()=%08x, "
1341 "OurWindowHandle=%08x",
1342 ::GetFocus(), aWindow
->GetWindowHandle()));
1345 if (pCharPosition
->dwCharPos
> len
) {
1346 MOZ_LOG(gIMELog
, LogLevel::Error
,
1347 ("IMMHandler::HandleQueryCharPosition, FAILED, "
1348 "pCharPosition->dwCharPos=%ld, len=%ld",
1349 pCharPosition
->dwCharPos
, len
));
1353 LayoutDeviceIntRect r
;
1355 GetCharacterRectOfSelectedTextAt(aWindow
, pCharPosition
->dwCharPos
, r
);
1356 NS_ENSURE_TRUE(ret
, false);
1358 LayoutDeviceIntRect screenRect
;
1359 // We always need top level window that is owner window of the popup window
1360 // even if the content of the popup window has focus.
1361 ResolveIMECaretPos(aWindow
->GetTopLevelWindow(false), r
, nullptr, screenRect
);
1363 // XXX This might need to check writing mode. However, MSDN doesn't explain
1364 // how to set the values in vertical writing mode. Additionally, IME
1365 // doesn't work well with top-left of the character (this is explicitly
1366 // documented) and its horizontal width. So, it might be better to set
1367 // top-right corner of the character and horizontal width, but we're not
1368 // sure if it doesn't cause any problems with a lot of IMEs...
1369 pCharPosition
->pt
.x
= screenRect
.X();
1370 pCharPosition
->pt
.y
= screenRect
.Y();
1372 pCharPosition
->cLineHeight
= r
.Height();
1374 WidgetQueryContentEvent
queryEditorRectEvent(true, eQueryEditorRect
, aWindow
);
1375 aWindow
->InitEvent(queryEditorRectEvent
);
1376 DispatchEvent(aWindow
, queryEditorRectEvent
);
1377 if (NS_WARN_IF(queryEditorRectEvent
.Failed())) {
1378 MOZ_LOG(gIMELog
, LogLevel::Error
,
1379 (" IMMHandler::HandleQueryCharPosition, eQueryEditorRect failed"));
1380 ::GetWindowRect(aWindow
->GetWindowHandle(), &pCharPosition
->rcDocument
);
1382 LayoutDeviceIntRect editorRectInWindow
= queryEditorRectEvent
.mReply
->mRect
;
1383 nsWindow
* window
= !!queryEditorRectEvent
.mReply
->mFocusedWidget
1384 ? static_cast<nsWindow
*>(
1385 queryEditorRectEvent
.mReply
->mFocusedWidget
)
1387 LayoutDeviceIntRect editorRectInScreen
;
1388 ResolveIMECaretPos(window
, editorRectInWindow
, nullptr, editorRectInScreen
);
1389 ::SetRect(&pCharPosition
->rcDocument
, editorRectInScreen
.X(),
1390 editorRectInScreen
.Y(), editorRectInScreen
.XMost(),
1391 editorRectInScreen
.YMost());
1396 MOZ_LOG(gIMELog
, LogLevel::Info
,
1397 ("IMMHandler::HandleQueryCharPosition, SUCCEEDED, pCharPosition={ "
1398 "pt={ x=%d, y=%d }, cLineHeight=%d, rcDocument={ left=%d, top=%d, "
1399 "right=%d, bottom=%d } }",
1400 pCharPosition
->pt
.x
, pCharPosition
->pt
.y
, pCharPosition
->cLineHeight
,
1401 pCharPosition
->rcDocument
.left
, pCharPosition
->rcDocument
.top
,
1402 pCharPosition
->rcDocument
.right
, pCharPosition
->rcDocument
.bottom
));
1406 bool IMMHandler::HandleDocumentFeed(nsWindow
* aWindow
, LPARAM lParam
,
1409 RECONVERTSTRING
* pReconv
= reinterpret_cast<RECONVERTSTRING
*>(lParam
);
1411 LayoutDeviceIntPoint
point(0, 0);
1413 bool hasCompositionString
=
1414 mIsComposing
&& ShouldDrawCompositionStringOurselves();
1416 int32_t targetOffset
, targetLength
;
1417 if (!hasCompositionString
) {
1418 const Maybe
<ContentSelection
>& contentSelection
=
1419 GetContentSelectionWithQueryIfNothing(aWindow
);
1420 if (contentSelection
.isNothing()) {
1421 MOZ_LOG(gIMELog
, LogLevel::Error
,
1422 ("IMMHandler::HandleDocumentFeed, FAILED, due to "
1423 "Selection::GetContentSelectionWithQueryIfNothing() failure"));
1426 if (contentSelection
->HasRange()) {
1427 targetOffset
= static_cast<int32_t>(
1428 contentSelection
->OffsetAndDataRef().StartOffset());
1430 static_cast<int32_t>(contentSelection
->OffsetAndDataRef().Length());
1432 // If there is no selection range, let's return all text in the editor.
1434 targetLength
= INT32_MAX
;
1437 targetOffset
= int32_t(mCompositionStart
);
1438 targetLength
= int32_t(mCompositionString
.Length());
1441 // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
1442 // we cannot support this message when the current offset is larger than
1444 if (targetOffset
< 0 || targetLength
< 0 || targetOffset
+ targetLength
< 0) {
1445 MOZ_LOG(gIMELog
, LogLevel::Error
,
1446 ("IMMHandler::HandleDocumentFeed, FAILED, "
1447 "due to the selection is out of range"));
1451 // Get all contents of the focused editor.
1452 WidgetQueryContentEvent
queryTextContentEvent(true, eQueryTextContent
,
1454 queryTextContentEvent
.InitForQueryTextContent(0, UINT32_MAX
);
1455 aWindow
->InitEvent(queryTextContentEvent
, &point
);
1456 DispatchEvent(aWindow
, queryTextContentEvent
);
1457 if (NS_WARN_IF(queryTextContentEvent
.Failed())) {
1458 MOZ_LOG(gIMELog
, LogLevel::Error
,
1459 ("IMMHandler::HandleDocumentFeed, FAILED, "
1460 "due to eQueryTextContent failure"));
1464 nsAutoString
str(queryTextContentEvent
.mReply
->DataRef());
1465 if (targetOffset
> static_cast<int32_t>(str
.Length())) {
1466 MOZ_LOG(gIMELog
, LogLevel::Error
,
1467 (" IMMHandler::HandleDocumentFeed, FAILED, "
1468 "due to the caret offset is invalid"));
1472 // Get the focused paragraph, we decide that it starts from the previous CRLF
1473 // (or start of the editor) to the next one (or the end of the editor).
1474 int32_t paragraphStart
= str
.RFind("\n", false, targetOffset
, -1) + 1;
1475 int32_t paragraphEnd
= str
.Find("\r", false, targetOffset
+ targetLength
, -1);
1476 if (paragraphEnd
< 0) {
1477 paragraphEnd
= str
.Length();
1479 nsDependentSubstring
paragraph(str
, paragraphStart
,
1480 paragraphEnd
- paragraphStart
);
1482 uint32_t len
= paragraph
.Length();
1483 uint32_t needSize
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
1486 *oResult
= needSize
;
1488 gIMELog
, LogLevel::Info
,
1489 ("IMMHandler::HandleDocumentFeed, succeeded, result=%ld", *oResult
));
1493 if (pReconv
->dwSize
< needSize
) {
1494 MOZ_LOG(gIMELog
, LogLevel::Error
,
1495 ("IMMHandler::HandleDocumentFeed, FAILED, "
1496 "pReconv->dwSize=%ld, needSize=%ld",
1497 pReconv
->dwSize
, needSize
));
1501 // Fill reconvert struct
1502 pReconv
->dwVersion
= 0;
1503 pReconv
->dwStrLen
= len
;
1504 pReconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
1505 if (hasCompositionString
) {
1506 pReconv
->dwCompStrLen
= targetLength
;
1507 pReconv
->dwCompStrOffset
= (targetOffset
- paragraphStart
) * sizeof(WCHAR
);
1508 // Set composition target clause information
1509 uint32_t offset
, length
;
1510 if (!GetTargetClauseRange(&offset
, &length
)) {
1511 MOZ_LOG(gIMELog
, LogLevel::Error
,
1512 ("IMMHandler::HandleDocumentFeed, FAILED, "
1513 "due to IMMHandler::GetTargetClauseRange() failure"));
1516 pReconv
->dwTargetStrLen
= length
;
1517 pReconv
->dwTargetStrOffset
= (offset
- paragraphStart
) * sizeof(WCHAR
);
1519 pReconv
->dwTargetStrLen
= targetLength
;
1520 pReconv
->dwTargetStrOffset
=
1521 (targetOffset
- paragraphStart
) * sizeof(WCHAR
);
1522 // There is no composition string, so, the length is zero but we should
1523 // set the cursor offset to the composition str offset.
1524 pReconv
->dwCompStrLen
= 0;
1525 pReconv
->dwCompStrOffset
= pReconv
->dwTargetStrOffset
;
1528 *oResult
= needSize
;
1529 ::CopyMemory(reinterpret_cast<LPVOID
>(lParam
+ sizeof(RECONVERTSTRING
)),
1530 paragraph
.BeginReading(), len
* sizeof(WCHAR
));
1532 MOZ_LOG(gIMELog
, LogLevel::Info
,
1533 ("IMMHandler::HandleDocumentFeed, SUCCEEDED, pReconv=%s, result=%ld",
1534 GetReconvertStringLog(pReconv
).get(), *oResult
));
1539 bool IMMHandler::CommitCompositionOnPreviousWindow(nsWindow
* aWindow
) {
1540 if (!mComposingWindow
|| mComposingWindow
== aWindow
) {
1544 MOZ_LOG(gIMELog
, LogLevel::Info
,
1545 ("IMMHandler::CommitCompositionOnPreviousWindow, mIsComposing=%s",
1546 GetBoolName(mIsComposing
)));
1548 // If we have composition, we should dispatch composition events internally.
1550 IMEContext
context(mComposingWindow
);
1551 NS_ASSERTION(context
.IsValid(), "IME context must be valid");
1553 HandleEndComposition(mComposingWindow
);
1560 static TextRangeType
PlatformToNSAttr(uint8_t aAttr
) {
1562 case ATTR_INPUT_ERROR
:
1563 // case ATTR_FIXEDCONVERTED:
1565 return TextRangeType::eRawClause
;
1566 case ATTR_CONVERTED
:
1567 return TextRangeType::eConvertedClause
;
1568 case ATTR_TARGET_NOTCONVERTED
:
1569 return TextRangeType::eSelectedRawClause
;
1570 case ATTR_TARGET_CONVERTED
:
1571 return TextRangeType::eSelectedClause
;
1573 NS_ASSERTION(false, "unknown attribute");
1574 return TextRangeType::eCaret
;
1579 void IMMHandler::DispatchEvent(nsWindow
* aWindow
, WidgetGUIEvent
& aEvent
) {
1581 gIMELog
, LogLevel::Info
,
1582 ("IMMHandler::DispatchEvent(aWindow=0x%p, aEvent={ mMessage=%s }, "
1583 "aWindow->Destroyed()=%s",
1584 aWindow
, ToChar(aEvent
.mMessage
), GetBoolName(aWindow
->Destroyed())));
1586 if (aWindow
->Destroyed()) {
1590 aWindow
->DispatchWindowEvent(aEvent
);
1593 void IMMHandler::DispatchCompositionChangeEvent(nsWindow
* aWindow
,
1594 const IMEContext
& aContext
) {
1595 NS_ASSERTION(mIsComposing
, "conflict state");
1596 MOZ_LOG(gIMELog
, LogLevel::Info
,
1597 ("IMMHandler::DispatchCompositionChangeEvent"));
1599 // If we don't need to draw composition string ourselves, we don't need to
1600 // fire compositionchange event during composing.
1601 if (!ShouldDrawCompositionStringOurselves()) {
1602 // But we need to adjust composition window pos and native caret pos, here.
1603 SetIMERelatedWindowsPos(aWindow
, aContext
);
1607 RefPtr
<nsWindow
> kungFuDeathGrip(aWindow
);
1608 RefPtr
<TextEventDispatcher
> dispatcher
= GetTextEventDispatcherFor(aWindow
);
1609 nsresult rv
= dispatcher
->BeginNativeInputTransaction();
1610 if (NS_WARN_IF(NS_FAILED(rv
))) {
1611 MOZ_LOG(gIMELog
, LogLevel::Error
,
1612 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to "
1613 "TextEventDispatcher::BeginNativeInputTransaction() failure"));
1617 // NOTE: Calling SetIMERelatedWindowsPos() from this method will be failure
1618 // in e10s mode. compositionchange event will notify this of
1619 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED, then
1620 // SetIMERelatedWindowsPos() will be called.
1622 // XXX Sogou (Simplified Chinese IME) returns contradictory values:
1623 // The cursor position is actual cursor position. However, other values
1624 // (composition string and attributes) are empty.
1626 if (mCompositionString
.IsEmpty()) {
1627 // Don't append clause information if composition string is empty.
1628 } else if (mClauseArray
.IsEmpty()) {
1629 // Some IMEs don't return clause array information, then, we assume that
1630 // all characters in the composition string are in one clause.
1631 MOZ_LOG(gIMELog
, LogLevel::Info
,
1632 (" IMMHandler::DispatchCompositionChangeEvent, "
1633 "mClauseArray.Length()=0"));
1634 rv
= dispatcher
->SetPendingComposition(mCompositionString
, nullptr);
1635 if (NS_WARN_IF(NS_FAILED(rv
))) {
1636 MOZ_LOG(gIMELog
, LogLevel::Error
,
1637 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
1638 "TextEventDispatcher::SetPendingComposition() failure"));
1642 // iterate over the attributes
1643 rv
= dispatcher
->SetPendingCompositionString(mCompositionString
);
1644 if (NS_WARN_IF(NS_FAILED(rv
))) {
1645 MOZ_LOG(gIMELog
, LogLevel::Error
,
1646 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
1647 "TextEventDispatcher::SetPendingCompositionString() failure"));
1650 uint32_t lastOffset
= 0;
1651 for (uint32_t i
= 0; i
< mClauseArray
.Length() - 1; i
++) {
1652 uint32_t current
= mClauseArray
[i
+ 1];
1653 if (current
> mCompositionString
.Length()) {
1654 MOZ_LOG(gIMELog
, LogLevel::Info
,
1655 (" IMMHandler::DispatchCompositionChangeEvent, "
1656 "mClauseArray[%ld]=%lu. "
1657 "This is larger than mCompositionString.Length()=%lu",
1658 i
+ 1, current
, mCompositionString
.Length()));
1659 current
= int32_t(mCompositionString
.Length());
1662 uint32_t length
= current
- lastOffset
;
1663 if (NS_WARN_IF(lastOffset
>= mAttributeArray
.Length())) {
1664 MOZ_LOG(gIMELog
, LogLevel::Error
,
1665 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to "
1666 "invalid data of mClauseArray or mAttributeArray"));
1669 TextRangeType textRangeType
=
1670 PlatformToNSAttr(mAttributeArray
[lastOffset
]);
1671 rv
= dispatcher
->AppendClauseToPendingComposition(length
, textRangeType
);
1672 if (NS_WARN_IF(NS_FAILED(rv
))) {
1673 MOZ_LOG(gIMELog
, LogLevel::Error
,
1674 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
1675 "TextEventDispatcher::AppendClauseToPendingComposition() "
1680 lastOffset
= current
;
1682 MOZ_LOG(gIMELog
, LogLevel::Info
,
1683 (" IMMHandler::DispatchCompositionChangeEvent, index=%ld, "
1684 "rangeType=%s, range length=%lu",
1685 i
, ToChar(textRangeType
), length
));
1689 if (mCursorPosition
== NO_IME_CARET
) {
1690 MOZ_LOG(gIMELog
, LogLevel::Info
,
1691 (" IMMHandler::DispatchCompositionChangeEvent, no caret"));
1693 uint32_t cursor
= static_cast<uint32_t>(mCursorPosition
);
1694 if (cursor
> mCompositionString
.Length()) {
1695 MOZ_LOG(gIMELog
, LogLevel::Info
,
1696 (" IMMHandler::CreateTextRangeArray, mCursorPosition=%ld. "
1697 "This is larger than mCompositionString.Length()=%lu",
1698 mCursorPosition
, mCompositionString
.Length()));
1699 cursor
= mCompositionString
.Length();
1702 // If caret is in the target clause, the target clause will be painted as
1703 // normal selection range. Since caret shouldn't be in selection range on
1704 // Windows, we shouldn't append caret range in such case.
1705 const TextRangeArray
* clauses
= dispatcher
->GetPendingCompositionClauses();
1706 const TextRange
* targetClause
=
1707 clauses
? clauses
->GetTargetClause() : nullptr;
1708 if (targetClause
&& cursor
>= targetClause
->mStartOffset
&&
1709 cursor
<= targetClause
->mEndOffset
) {
1710 // Forget the caret position specified by IME since Gecko's caret position
1711 // will be at the end of composition string.
1712 mCursorPosition
= NO_IME_CARET
;
1713 MOZ_LOG(gIMELog
, LogLevel::Info
,
1714 (" IMMHandler::CreateTextRangeArray, no caret due to it's in "
1715 "the target clause, now, mCursorPosition is NO_IME_CARET"));
1718 if (mCursorPosition
!= NO_IME_CARET
) {
1719 rv
= dispatcher
->SetCaretInPendingComposition(cursor
, 0);
1720 if (NS_WARN_IF(NS_FAILED(rv
))) {
1722 gIMELog
, LogLevel::Error
,
1723 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
1724 "TextEventDispatcher::SetCaretInPendingComposition() failure"));
1730 WidgetEventTime eventTime
= aWindow
->CurrentMessageWidgetEventTime();
1731 nsEventStatus status
;
1732 rv
= dispatcher
->FlushPendingComposition(status
, &eventTime
);
1733 if (NS_WARN_IF(NS_FAILED(rv
))) {
1734 MOZ_LOG(gIMELog
, LogLevel::Error
,
1735 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
1736 "TextEventDispatcher::FlushPendingComposition() failure"));
1741 void IMMHandler::GetCompositionString(const IMEContext
& aContext
, DWORD aIndex
,
1742 nsAString
& aCompositionString
) const {
1743 aCompositionString
.Truncate();
1745 // Retrieve the size of the required output buffer.
1746 long lRtn
= ::ImmGetCompositionStringW(aContext
.get(), aIndex
, nullptr, 0);
1747 if (lRtn
< 0 || !aCompositionString
.SetLength((lRtn
/ sizeof(WCHAR
)) + 1,
1748 mozilla::fallible
)) {
1749 MOZ_LOG(gIMELog
, LogLevel::Error
,
1750 ("IMMHandler::GetCompositionString, FAILED, due to OOM"));
1751 return; // Error or out of memory.
1754 // Actually retrieve the composition string information.
1755 lRtn
= ::ImmGetCompositionStringW(aContext
.get(), aIndex
,
1756 (LPVOID
)aCompositionString
.BeginWriting(),
1757 lRtn
+ sizeof(WCHAR
));
1758 aCompositionString
.SetLength(lRtn
/ sizeof(WCHAR
));
1761 gIMELog
, LogLevel::Info
,
1762 ("IMMHandler::GetCompositionString, succeeded, aCompositionString=\"%s\"",
1763 NS_ConvertUTF16toUTF8(aCompositionString
).get()));
1766 bool IMMHandler::GetTargetClauseRange(uint32_t* aOffset
, uint32_t* aLength
) {
1767 NS_ENSURE_TRUE(aOffset
, false);
1768 NS_ENSURE_TRUE(mIsComposing
, false);
1769 NS_ENSURE_TRUE(ShouldDrawCompositionStringOurselves(), false);
1772 *aOffset
= mCompositionStart
;
1773 for (uint32_t i
= 0; i
< mAttributeArray
.Length(); i
++) {
1774 if (mAttributeArray
[i
] == ATTR_TARGET_NOTCONVERTED
||
1775 mAttributeArray
[i
] == ATTR_TARGET_CONVERTED
) {
1776 *aOffset
= mCompositionStart
+ i
;
1787 // The all composition string is targetted when there is no ATTR_TARGET_*
1788 // clause. E.g., there is only ATTR_INPUT
1789 *aLength
= mCompositionString
.Length();
1793 uint32_t offsetInComposition
= *aOffset
- mCompositionStart
;
1794 *aLength
= mCompositionString
.Length() - offsetInComposition
;
1795 for (uint32_t i
= offsetInComposition
; i
< mAttributeArray
.Length(); i
++) {
1796 if (mAttributeArray
[i
] != ATTR_TARGET_NOTCONVERTED
&&
1797 mAttributeArray
[i
] != ATTR_TARGET_CONVERTED
) {
1798 *aLength
= i
- offsetInComposition
;
1805 bool IMMHandler::ConvertToANSIString(const nsString
& aStr
, UINT aCodePage
,
1806 nsACString
& aANSIStr
) {
1807 int len
= ::WideCharToMultiByte(aCodePage
, 0, (LPCWSTR
)aStr
.get(),
1808 aStr
.Length(), nullptr, 0, nullptr, nullptr);
1809 NS_ENSURE_TRUE(len
>= 0, false);
1811 if (!aANSIStr
.SetLength(len
, mozilla::fallible
)) {
1812 MOZ_LOG(gIMELog
, LogLevel::Error
,
1813 ("IMMHandler::ConvertToANSIString, FAILED, due to OOM"));
1816 ::WideCharToMultiByte(aCodePage
, 0, (LPCWSTR
)aStr
.get(), aStr
.Length(),
1817 (LPSTR
)aANSIStr
.BeginWriting(), len
, nullptr, nullptr);
1821 bool IMMHandler::GetCharacterRectOfSelectedTextAt(
1822 nsWindow
* aWindow
, uint32_t aOffset
, LayoutDeviceIntRect
& aCharRect
,
1823 WritingMode
* aWritingMode
) {
1824 LayoutDeviceIntPoint
point(0, 0);
1826 const Maybe
<ContentSelection
>& contentSelection
=
1827 GetContentSelectionWithQueryIfNothing(aWindow
);
1828 if (contentSelection
.isNothing()) {
1829 MOZ_LOG(gIMELog
, LogLevel::Error
,
1830 ("IMMHandler::GetCharacterRectOfSelectedTextAt, FAILED, due to "
1831 "Selection::GetContentSelectionWithQueryIfNothing() failure"));
1835 // If there is neither a selection range nor composition string, cannot return
1836 // character rect, of course.
1837 if (!contentSelection
->HasRange() && !mIsComposing
) {
1838 MOZ_LOG(gIMELog
, LogLevel::Warning
,
1839 ("IMMHandler::GetCharacterRectOfSelectedTextAt, FAILED, due to "
1840 "there is neither a selection range nor composition string"));
1844 // If the offset is larger than the end of composition string or selected
1845 // string, we should return false since such case must be a bug of the caller
1846 // or the active IME. If it's an IME's bug, we need to set targetLength to
1848 const uint32_t targetLength
=
1849 mIsComposing
? mCompositionString
.Length()
1850 : contentSelection
->OffsetAndDataRef().Length();
1851 if (NS_WARN_IF(aOffset
> targetLength
)) {
1853 gIMELog
, LogLevel::Error
,
1854 ("IMMHandler::GetCharacterRectOfSelectedTextAt, FAILED, due to "
1855 "aOffset is too large (aOffset=%u, targetLength=%u, mIsComposing=%s)",
1856 aOffset
, targetLength
, GetBoolName(mIsComposing
)));
1860 // If there is caret, we might be able to use caret rect.
1861 uint32_t caretOffset
= UINT32_MAX
;
1862 // There is a caret only when the normal selection is collapsed.
1863 if (contentSelection
.isNothing() ||
1864 contentSelection
->OffsetAndDataRef().IsDataEmpty()) {
1866 // If it's composing, mCursorPosition is the offset to caret in
1867 // the composition string.
1868 if (mCursorPosition
!= NO_IME_CARET
) {
1869 MOZ_ASSERT(mCursorPosition
>= 0);
1870 caretOffset
= mCursorPosition
;
1871 } else if (!ShouldDrawCompositionStringOurselves() ||
1872 mCompositionString
.IsEmpty()) {
1873 // Otherwise, if there is no composition string, we should assume that
1874 // there is a caret at the start of composition string.
1878 // If there is no composition, the selection offset is the caret offset.
1883 // If there is a caret and retrieving offset is same as the caret offset,
1884 // we should use the caret rect.
1885 if (aOffset
!= caretOffset
) {
1886 WidgetQueryContentEvent
queryTextRectEvent(true, eQueryTextRect
, aWindow
);
1887 WidgetQueryContentEvent::Options options
;
1888 options
.mRelativeToInsertionPoint
= true;
1889 queryTextRectEvent
.InitForQueryTextRect(aOffset
, 1, options
);
1890 aWindow
->InitEvent(queryTextRectEvent
, &point
);
1891 DispatchEvent(aWindow
, queryTextRectEvent
);
1892 if (queryTextRectEvent
.Succeeded()) {
1893 aCharRect
= queryTextRectEvent
.mReply
->mRect
;
1895 *aWritingMode
= queryTextRectEvent
.mReply
->WritingModeRef();
1898 gIMELog
, LogLevel::Debug
,
1899 ("IMMHandler::GetCharacterRectOfSelectedTextAt, Succeeded, "
1900 "aOffset=%u, aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
1901 "queryTextRectEvent={ mReply=%s }",
1902 aOffset
, aCharRect
.X(), aCharRect
.Y(), aCharRect
.Width(),
1903 aCharRect
.Height(), ToString(queryTextRectEvent
.mReply
).c_str()));
1908 return GetCaretRect(aWindow
, aCharRect
, aWritingMode
);
1911 bool IMMHandler::GetCaretRect(nsWindow
* aWindow
,
1912 LayoutDeviceIntRect
& aCaretRect
,
1913 WritingMode
* aWritingMode
) {
1914 LayoutDeviceIntPoint
point(0, 0);
1916 WidgetQueryContentEvent
queryCaretRectEvent(true, eQueryCaretRect
, aWindow
);
1917 WidgetQueryContentEvent::Options options
;
1918 options
.mRelativeToInsertionPoint
= true;
1919 queryCaretRectEvent
.InitForQueryCaretRect(0, options
);
1920 aWindow
->InitEvent(queryCaretRectEvent
, &point
);
1921 DispatchEvent(aWindow
, queryCaretRectEvent
);
1922 if (queryCaretRectEvent
.Failed()) {
1924 gIMELog
, LogLevel::Info
,
1925 ("IMMHandler::GetCaretRect, FAILED, due to eQueryCaretRect failure"));
1928 aCaretRect
= queryCaretRectEvent
.mReply
->mRect
;
1930 *aWritingMode
= queryCaretRectEvent
.mReply
->WritingModeRef();
1932 MOZ_LOG(gIMELog
, LogLevel::Info
,
1933 ("IMMHandler::GetCaretRect, SUCCEEDED, "
1934 "aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
1935 "queryCaretRectEvent={ mReply=%s }",
1936 aCaretRect
.X(), aCaretRect
.Y(), aCaretRect
.Width(),
1937 aCaretRect
.Height(), ToString(queryCaretRectEvent
.mReply
).c_str()));
1941 bool IMMHandler::SetIMERelatedWindowsPos(nsWindow
* aWindow
,
1942 const IMEContext
& aContext
) {
1943 // Get first character rect of current a normal selected text or a composing
1945 WritingMode writingMode
;
1946 LayoutDeviceIntRect firstSelectedCharRectRelativeToWindow
;
1947 bool ret
= GetCharacterRectOfSelectedTextAt(
1948 aWindow
, 0, firstSelectedCharRectRelativeToWindow
, &writingMode
);
1949 NS_ENSURE_TRUE(ret
, false);
1950 nsWindow
* toplevelWindow
= aWindow
->GetTopLevelWindow(false);
1951 LayoutDeviceIntRect firstSelectedCharRect
;
1952 ResolveIMECaretPos(toplevelWindow
, firstSelectedCharRectRelativeToWindow
,
1953 aWindow
, firstSelectedCharRect
);
1955 // Set native caret size/position to our caret. Some IMEs honor it. E.g.,
1956 // "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified
1957 // Chinese) on XP. But if a11y module is handling native caret, we shouldn't
1959 if (!IMEHandler::IsA11yHandlingNativeCaret()) {
1960 LayoutDeviceIntRect
caretRect(firstSelectedCharRect
),
1961 caretRectRelativeToWindow
;
1962 if (GetCaretRect(aWindow
, caretRectRelativeToWindow
)) {
1963 ResolveIMECaretPos(toplevelWindow
, caretRectRelativeToWindow
, aWindow
,
1966 NS_WARNING("failed to get caret rect");
1967 caretRect
.SetWidth(1);
1969 IMEHandler::CreateNativeCaret(aWindow
, caretRect
);
1972 if (ShouldDrawCompositionStringOurselves()) {
1973 MOZ_LOG(gIMELog
, LogLevel::Info
,
1974 ("IMMHandler::SetIMERelatedWindowsPos, Set candidate window"));
1976 // Get a rect of first character in current target in composition string.
1977 LayoutDeviceIntRect firstTargetCharRect
, lastTargetCharRect
;
1978 if (mIsComposing
&& !mCompositionString
.IsEmpty()) {
1979 // If there are no targetted selection, we should use it's first character
1981 uint32_t offset
, length
;
1982 if (!GetTargetClauseRange(&offset
, &length
)) {
1983 MOZ_LOG(gIMELog
, LogLevel::Error
,
1984 (" IMMHandler::SetIMERelatedWindowsPos, FAILED, due to "
1985 "GetTargetClauseRange() failure"));
1989 GetCharacterRectOfSelectedTextAt(aWindow
, offset
- mCompositionStart
,
1990 firstTargetCharRect
, &writingMode
);
1991 NS_ENSURE_TRUE(ret
, false);
1993 ret
= GetCharacterRectOfSelectedTextAt(
1994 aWindow
, offset
+ length
- 1 - mCompositionStart
,
1995 lastTargetCharRect
);
1996 NS_ENSURE_TRUE(ret
, false);
1998 lastTargetCharRect
= firstTargetCharRect
;
2001 // If there are no composition string, we should use a first character
2003 ret
= GetCharacterRectOfSelectedTextAt(aWindow
, 0, firstTargetCharRect
,
2005 NS_ENSURE_TRUE(ret
, false);
2006 lastTargetCharRect
= firstTargetCharRect
;
2008 ResolveIMECaretPos(toplevelWindow
, firstTargetCharRect
, aWindow
,
2009 firstTargetCharRect
);
2010 ResolveIMECaretPos(toplevelWindow
, lastTargetCharRect
, aWindow
,
2011 lastTargetCharRect
);
2012 LayoutDeviceIntRect targetClauseRect
;
2013 targetClauseRect
.UnionRect(firstTargetCharRect
, lastTargetCharRect
);
2015 // Move the candidate window to proper position from the target clause as
2017 CANDIDATEFORM candForm
;
2018 candForm
.dwIndex
= 0;
2019 if (!writingMode
.IsVertical() || IsVerticalWritingSupported()) {
2020 candForm
.dwStyle
= CFS_EXCLUDE
;
2021 // Candidate window shouldn't overlap the target clause in any writing
2023 candForm
.rcArea
.left
= targetClauseRect
.X();
2024 candForm
.rcArea
.right
= targetClauseRect
.XMost();
2025 candForm
.rcArea
.top
= targetClauseRect
.Y();
2026 candForm
.rcArea
.bottom
= targetClauseRect
.YMost();
2027 if (!writingMode
.IsVertical()) {
2028 // In horizontal layout, current point of interest should be top-left
2029 // of the first character.
2030 candForm
.ptCurrentPos
.x
= firstTargetCharRect
.X();
2031 candForm
.ptCurrentPos
.y
= firstTargetCharRect
.Y();
2032 } else if (writingMode
.IsVerticalRL()) {
2033 // In vertical layout (RL), candidate window should be positioned right
2034 // side of target clause. However, we don't set vertical writing font
2035 // to the IME. Therefore, the candidate window may be positioned
2036 // bottom-left of target clause rect with these information.
2037 candForm
.ptCurrentPos
.x
= targetClauseRect
.X();
2038 candForm
.ptCurrentPos
.y
= targetClauseRect
.Y();
2040 MOZ_ASSERT(writingMode
.IsVerticalLR(), "Did we miss some causes?");
2041 // In vertical layout (LR), candidate window should be poisitioned left
2042 // side of target clause. Although, we don't set vertical writing font
2043 // to the IME, the candidate window may be positioned bottom-right of
2044 // the target clause rect with these information.
2045 candForm
.ptCurrentPos
.x
= targetClauseRect
.XMost();
2046 candForm
.ptCurrentPos
.y
= targetClauseRect
.Y();
2049 // If vertical writing is not supported by IME, let's set candidate
2050 // window position to the bottom-left of the target clause because
2051 // the position must be the safest position to prevent the candidate
2052 // window to overlap with the target clause.
2053 candForm
.dwStyle
= CFS_CANDIDATEPOS
;
2054 candForm
.ptCurrentPos
.x
= targetClauseRect
.X();
2055 candForm
.ptCurrentPos
.y
= targetClauseRect
.YMost();
2057 MOZ_LOG(gIMELog
, LogLevel::Info
,
2058 (" IMMHandler::SetIMERelatedWindowsPos, Calling "
2059 "ImmSetCandidateWindow()... ptCurrentPos={ x=%d, y=%d }, "
2060 "rcArea={ left=%d, top=%d, right=%d, bottom=%d }, "
2062 candForm
.ptCurrentPos
.x
, candForm
.ptCurrentPos
.y
,
2063 candForm
.rcArea
.left
, candForm
.rcArea
.top
, candForm
.rcArea
.right
,
2064 candForm
.rcArea
.bottom
, ToString(writingMode
).c_str()));
2065 ::ImmSetCandidateWindow(aContext
.get(), &candForm
);
2067 MOZ_LOG(gIMELog
, LogLevel::Info
,
2068 ("IMMHandler::SetIMERelatedWindowsPos, Set composition window"));
2070 // Move the composition window to caret position (if selected some
2071 // characters, we should use first character rect of them).
2072 // And in this mode, IME adjusts the candidate window position
2073 // automatically. So, we don't need to set it.
2074 COMPOSITIONFORM compForm
;
2075 compForm
.dwStyle
= CFS_POINT
;
2076 compForm
.ptCurrentPos
.x
= !writingMode
.IsVerticalLR()
2077 ? firstSelectedCharRect
.X()
2078 : firstSelectedCharRect
.XMost();
2079 compForm
.ptCurrentPos
.y
= firstSelectedCharRect
.Y();
2080 ::ImmSetCompositionWindow(aContext
.get(), &compForm
);
2086 void IMMHandler::ResolveIMECaretPos(nsIWidget
* aReferenceWidget
,
2087 LayoutDeviceIntRect
& aCursorRect
,
2088 nsIWidget
* aNewOriginWidget
,
2089 LayoutDeviceIntRect
& aOutRect
) {
2090 aOutRect
= aCursorRect
;
2092 if (aReferenceWidget
== aNewOriginWidget
) return;
2094 if (aReferenceWidget
)
2095 aOutRect
.MoveBy(aReferenceWidget
->WidgetToScreenOffset());
2097 if (aNewOriginWidget
)
2098 aOutRect
.MoveBy(-aNewOriginWidget
->WidgetToScreenOffset());
2101 static void SetHorizontalFontToLogFont(const nsAString
& aFontFace
,
2102 LOGFONTW
& aLogFont
) {
2103 aLogFont
.lfEscapement
= aLogFont
.lfOrientation
= 0;
2104 if (NS_WARN_IF(aFontFace
.Length() > LF_FACESIZE
- 1)) {
2105 memcpy(aLogFont
.lfFaceName
, L
"System", sizeof(L
"System"));
2108 memcpy(aLogFont
.lfFaceName
, aFontFace
.BeginReading(),
2109 aFontFace
.Length() * sizeof(wchar_t));
2110 aLogFont
.lfFaceName
[aFontFace
.Length()] = 0;
2113 static void SetVerticalFontToLogFont(const nsAString
& aFontFace
,
2114 LOGFONTW
& aLogFont
) {
2115 aLogFont
.lfEscapement
= aLogFont
.lfOrientation
= 2700;
2116 if (NS_WARN_IF(aFontFace
.Length() > LF_FACESIZE
- 2)) {
2117 memcpy(aLogFont
.lfFaceName
, L
"@System", sizeof(L
"@System"));
2120 aLogFont
.lfFaceName
[0] = '@';
2121 memcpy(&aLogFont
.lfFaceName
[1], aFontFace
.BeginReading(),
2122 aFontFace
.Length() * sizeof(wchar_t));
2123 aLogFont
.lfFaceName
[aFontFace
.Length() + 1] = 0;
2126 void IMMHandler::AdjustCompositionFont(nsWindow
* aWindow
,
2127 const IMEContext
& aContext
,
2128 const WritingMode
& aWritingMode
,
2129 bool aForceUpdate
) {
2130 // An instance of IMMHandler is destroyed when active IME is changed.
2131 // Therefore, we need to store the information which are set to the IM
2132 // context to static variables since IM context is never recreated.
2133 static bool sCompositionFontsInitialized
= false;
2134 static nsString sCompositionFont
;
2135 static bool sCompositionFontPrefDone
= false;
2136 if (!sCompositionFontPrefDone
) {
2137 sCompositionFontPrefDone
= true;
2138 Preferences::GetString("intl.imm.composition_font", sCompositionFont
);
2141 // If composition font is customized by pref, we need to modify the
2142 // composition font of the IME context at first time even if the writing mode
2144 bool setCompositionFontForcibly
=
2146 (!sCompositionFontsInitialized
&& !sCompositionFont
.IsEmpty());
2148 static WritingMode sCurrentWritingMode
;
2149 static nsString sCurrentIMEName
;
2150 if (!setCompositionFontForcibly
&&
2151 sWritingModeOfCompositionFont
== aWritingMode
&&
2152 sCurrentIMEName
== sIMEName
) {
2153 // Nothing to do if writing mode isn't being changed.
2157 // Decide composition fonts for both horizontal writing mode and vertical
2158 // writing mode. If the font isn't specified by the pref, use default
2159 // font which is already set to the IM context. And also in vertical writing
2160 // mode, insert '@' to the start of the font.
2161 if (!sCompositionFontsInitialized
) {
2162 sCompositionFontsInitialized
= true;
2163 // sCompositionFontH must not start with '@' and its length is less than
2164 // LF_FACESIZE since it needs to end with null terminating character.
2165 if (sCompositionFont
.IsEmpty() ||
2166 sCompositionFont
.Length() > LF_FACESIZE
- 1 ||
2167 sCompositionFont
[0] == '@') {
2168 LOGFONTW defaultLogFont
;
2170 !::ImmGetCompositionFont(aContext
.get(), &defaultLogFont
))) {
2172 gIMELog
, LogLevel::Error
,
2173 (" IMMHandler::AdjustCompositionFont, ::ImmGetCompositionFont() "
2175 sCompositionFont
.AssignLiteral("System");
2177 // The font face is typically, "System".
2178 sCompositionFont
.Assign(defaultLogFont
.lfFaceName
);
2182 MOZ_LOG(gIMELog
, LogLevel::Info
,
2183 (" IMMHandler::AdjustCompositionFont, sCompositionFont=\"%s\" is "
2185 NS_ConvertUTF16toUTF8(sCompositionFont
).get()));
2188 static nsString sCompositionFontForJapanist2003
;
2189 if (IsJapanist2003Active() && sCompositionFontForJapanist2003
.IsEmpty()) {
2190 const char* kCompositionFontForJapanist2003
=
2191 "intl.imm.composition_font.japanist_2003";
2192 Preferences::GetString(kCompositionFontForJapanist2003
,
2193 sCompositionFontForJapanist2003
);
2194 // If the font name is not specified properly, let's use
2195 // "MS PGothic" instead.
2196 if (sCompositionFontForJapanist2003
.IsEmpty() ||
2197 sCompositionFontForJapanist2003
.Length() > LF_FACESIZE
- 2 ||
2198 sCompositionFontForJapanist2003
[0] == '@') {
2199 sCompositionFontForJapanist2003
.AssignLiteral("MS PGothic");
2203 sWritingModeOfCompositionFont
= aWritingMode
;
2204 sCurrentIMEName
= sIMEName
;
2207 memset(&logFont
, 0, sizeof(logFont
));
2208 if (!::ImmGetCompositionFont(aContext
.get(), &logFont
)) {
2209 MOZ_LOG(gIMELog
, LogLevel::Error
,
2210 (" IMMHandler::AdjustCompositionFont, ::ImmGetCompositionFont() "
2212 logFont
.lfFaceName
[0] = 0;
2214 // Need to reset some information which should be recomputed with new font.
2215 logFont
.lfWidth
= 0;
2216 logFont
.lfWeight
= FW_DONTCARE
;
2217 logFont
.lfOutPrecision
= OUT_DEFAULT_PRECIS
;
2218 logFont
.lfClipPrecision
= CLIP_DEFAULT_PRECIS
;
2219 logFont
.lfPitchAndFamily
= DEFAULT_PITCH
;
2221 if (aWritingMode
.IsVertical() && IsVerticalWritingSupported()) {
2222 SetVerticalFontToLogFont(IsJapanist2003Active()
2223 ? sCompositionFontForJapanist2003
2227 SetHorizontalFontToLogFont(IsJapanist2003Active()
2228 ? sCompositionFontForJapanist2003
2232 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2233 (" IMMHandler::AdjustCompositionFont, calling "
2234 "::ImmSetCompositionFont(\"%s\")",
2235 NS_ConvertUTF16toUTF8(nsDependentString(logFont
.lfFaceName
)).get()));
2236 ::ImmSetCompositionFontW(aContext
.get(), &logFont
);
2240 nsresult
IMMHandler::OnMouseButtonEvent(
2241 nsWindow
* aWindow
, const IMENotification
& aIMENotification
) {
2242 // We don't need to create the instance of the handler here.
2247 if (!sWM_MSIME_MOUSE
|| !IsComposingOnOurEditor() ||
2248 !ShouldDrawCompositionStringOurselves()) {
2252 // We need to handle only mousedown event.
2253 if (aIMENotification
.mMouseButtonEventData
.mEventMessage
!= eMouseDown
) {
2257 // If the character under the cursor is not in the composition string,
2258 // we don't need to notify IME of it.
2259 uint32_t compositionStart
= gIMMHandler
->mCompositionStart
;
2260 uint32_t compositionEnd
=
2261 compositionStart
+ gIMMHandler
->mCompositionString
.Length();
2262 if (aIMENotification
.mMouseButtonEventData
.mOffset
< compositionStart
||
2263 aIMENotification
.mMouseButtonEventData
.mOffset
>= compositionEnd
) {
2268 switch (aIMENotification
.mMouseButtonEventData
.mButton
) {
2269 case MouseButton::ePrimary
:
2270 button
= IMEMOUSE_LDOWN
;
2272 case MouseButton::eMiddle
:
2273 button
= IMEMOUSE_MDOWN
;
2275 case MouseButton::eSecondary
:
2276 button
= IMEMOUSE_RDOWN
;
2282 // calcurate positioning and offset
2283 // char : JCH1|JCH2|JCH3
2284 // offset: 0011 1122 2233
2285 // positioning: 2301 2301 2301
2286 LayoutDeviceIntPoint cursorPos
=
2287 aIMENotification
.mMouseButtonEventData
.mCursorPos
;
2288 LayoutDeviceIntRect charRect
=
2289 aIMENotification
.mMouseButtonEventData
.mCharRect
;
2290 int32_t cursorXInChar
= cursorPos
.x
- charRect
.X();
2291 // The event might hit to zero-width character, see bug 694913.
2292 // The reason might be:
2293 // * There are some zero-width characters are actually.
2294 // * font-size is specified zero.
2295 // But nobody reproduced this bug actually...
2296 // We should assume that user clicked on right most of the zero-width
2297 // character in such case.
2298 int positioning
= 1;
2299 if (charRect
.Width() > 0) {
2300 positioning
= cursorXInChar
* 4 / charRect
.Width();
2301 positioning
= (positioning
+ 2) % 4;
2305 aIMENotification
.mMouseButtonEventData
.mOffset
- compositionStart
;
2306 if (positioning
< 2) {
2310 MOZ_LOG(gIMELog
, LogLevel::Info
,
2311 ("IMMHandler::OnMouseButtonEvent, x,y=%ld,%ld, offset=%ld, "
2313 cursorPos
.x
, cursorPos
.y
, offset
, positioning
));
2315 // send MS_MSIME_MOUSE message to default IME window.
2316 HWND imeWnd
= ::ImmGetDefaultIMEWnd(aWindow
->GetWindowHandle());
2317 IMEContext
context(aWindow
);
2318 if (::SendMessageW(imeWnd
, sWM_MSIME_MOUSE
,
2319 MAKELONG(MAKEWORD(button
, positioning
), offset
),
2320 (LPARAM
)context
.get()) == 1) {
2321 return NS_SUCCESS_EVENT_CONSUMED
;
2327 bool IMMHandler::OnKeyDownEvent(nsWindow
* aWindow
, WPARAM wParam
, LPARAM lParam
,
2328 MSGResult
& aResult
) {
2329 MOZ_LOG(gIMELog
, LogLevel::Info
,
2330 ("IMMHandler::OnKeyDownEvent, hWnd=%08x, wParam=%08x, lParam=%08x",
2331 aWindow
->GetWindowHandle(), wParam
, lParam
));
2332 aResult
.mConsumed
= false;
2344 // If IME didn't process the key message (the virtual key code wasn't
2345 // converted to VK_PROCESSKEY), and the virtual key code event causes
2346 // moving caret or editing text with keeping composing state, we should
2347 // cancel the composition here because we cannot support moving
2348 // composition string with DOM events (IE also cancels the composition
2349 // in same cases). Then, this event will be dispatched.
2350 if (IsComposingOnOurEditor()) {
2351 // NOTE: We don't need to cancel the composition on another window.
2352 CancelComposition(aWindow
, false);
2360 Maybe
<ContentSelection
> IMMHandler::QueryContentSelection(nsWindow
* aWindow
) {
2361 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
2363 LayoutDeviceIntPoint
point(0, 0);
2364 aWindow
->InitEvent(querySelectedTextEvent
, &point
);
2365 DispatchEvent(aWindow
, querySelectedTextEvent
);
2366 if (NS_WARN_IF(querySelectedTextEvent
.Failed())) {
2367 MOZ_LOG(gIMELog
, LogLevel::Error
,
2368 (" IMMHandler::Selection::Init, FAILED, due to eQuerySelectedText "
2372 // If the window is destroyed during querying selected text, we shouldn't
2374 if (aWindow
->Destroyed()) {
2376 gIMELog
, LogLevel::Error
,
2377 (" IMMHandler::Selection::Init, FAILED, due to the widget destroyed"));
2381 ContentSelection
contentSelection(querySelectedTextEvent
);
2383 MOZ_LOG(gIMELog
, LogLevel::Info
,
2384 ("IMMHandler::Selection::Init, querySelectedTextEvent={ mReply=%s }",
2385 ToString(querySelectedTextEvent
.mReply
).c_str()));
2387 if (contentSelection
.HasRange() &&
2388 !contentSelection
.OffsetAndDataRef().IsValid()) {
2389 MOZ_LOG(gIMELog
, LogLevel::Error
,
2390 (" IMMHandler::Selection::Init, FAILED, due to invalid range"));
2393 return Some(contentSelection
);
2396 } // namespace widget
2397 } // namespace mozilla