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"
22 #ifndef IME_PROP_ACCEPT_WIDE_VKEY
23 # define IME_PROP_ACCEPT_WIDE_VKEY 0x20
26 //-------------------------------------------------------------------------
29 // http://download.microsoft.com/download/6/0/9/60908e9e-d2c1-47db-98f6-216af76a235f/msime.h
30 // The document for this has been removed from MSDN...
32 //-------------------------------------------------------------------------
34 #define RWM_MOUSE TEXT("MSIMEMouseOperation")
36 #define IMEMOUSE_NONE 0x00 // no mouse button was pushed
37 #define IMEMOUSE_LDOWN 0x01
38 #define IMEMOUSE_RDOWN 0x02
39 #define IMEMOUSE_MDOWN 0x04
40 #define IMEMOUSE_WUP 0x10 // wheel up
41 #define IMEMOUSE_WDOWN 0x20 // wheel down
43 // For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync`
44 // rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
46 // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
47 extern mozilla::LazyLogModule gIMELog
;
49 static const char* GetBoolName(bool aBool
) { return aBool
? "true" : "false"; }
51 static void HandleSeparator(nsACString
& aDesc
) {
52 if (!aDesc
.IsEmpty()) {
53 aDesc
.AppendLiteral(" | ");
57 class GetIMEGeneralPropertyName
: public nsAutoCString
{
59 explicit GetIMEGeneralPropertyName(DWORD aFlags
) {
61 AppendLiteral("no flags");
64 if (aFlags
& IME_PROP_AT_CARET
) {
65 AppendLiteral("IME_PROP_AT_CARET");
67 if (aFlags
& IME_PROP_SPECIAL_UI
) {
68 HandleSeparator(*this);
69 AppendLiteral("IME_PROP_SPECIAL_UI");
71 if (aFlags
& IME_PROP_CANDLIST_START_FROM_1
) {
72 HandleSeparator(*this);
73 AppendLiteral("IME_PROP_CANDLIST_START_FROM_1");
75 if (aFlags
& IME_PROP_UNICODE
) {
76 HandleSeparator(*this);
77 AppendLiteral("IME_PROP_UNICODE");
79 if (aFlags
& IME_PROP_COMPLETE_ON_UNSELECT
) {
80 HandleSeparator(*this);
81 AppendLiteral("IME_PROP_COMPLETE_ON_UNSELECT");
83 if (aFlags
& IME_PROP_ACCEPT_WIDE_VKEY
) {
84 HandleSeparator(*this);
85 AppendLiteral("IME_PROP_ACCEPT_WIDE_VKEY");
88 virtual ~GetIMEGeneralPropertyName() {}
91 class GetIMEUIPropertyName
: public nsAutoCString
{
93 explicit GetIMEUIPropertyName(DWORD aFlags
) {
95 AppendLiteral("no flags");
98 if (aFlags
& UI_CAP_2700
) {
99 AppendLiteral("UI_CAP_2700");
101 if (aFlags
& UI_CAP_ROT90
) {
102 HandleSeparator(*this);
103 AppendLiteral("UI_CAP_ROT90");
105 if (aFlags
& UI_CAP_ROTANY
) {
106 HandleSeparator(*this);
107 AppendLiteral("UI_CAP_ROTANY");
110 virtual ~GetIMEUIPropertyName() {}
113 class GetReconvertStringLog
: public nsAutoCString
{
115 explicit GetReconvertStringLog(RECONVERTSTRING
* aReconv
) {
116 AssignLiteral("{ dwSize=");
117 AppendInt(static_cast<uint32_t>(aReconv
->dwSize
));
118 AppendLiteral(", dwVersion=");
119 AppendInt(static_cast<uint32_t>(aReconv
->dwVersion
));
120 AppendLiteral(", dwStrLen=");
121 AppendInt(static_cast<uint32_t>(aReconv
->dwStrLen
));
122 AppendLiteral(", dwStrOffset=");
123 AppendInt(static_cast<uint32_t>(aReconv
->dwStrOffset
));
124 AppendLiteral(", dwCompStrLen=");
125 AppendInt(static_cast<uint32_t>(aReconv
->dwCompStrLen
));
126 AppendLiteral(", dwCompStrOffset=");
127 AppendInt(static_cast<uint32_t>(aReconv
->dwCompStrOffset
));
128 AppendLiteral(", dwTargetStrLen=");
129 AppendInt(static_cast<uint32_t>(aReconv
->dwTargetStrLen
));
130 AppendLiteral(", dwTargetStrOffset=");
131 AppendInt(static_cast<uint32_t>(aReconv
->dwTargetStrOffset
));
132 AppendLiteral(", result str=\"");
133 if (aReconv
->dwStrLen
) {
134 char16_t
* strStart
= reinterpret_cast<char16_t
*>(
135 reinterpret_cast<char*>(aReconv
) + aReconv
->dwStrOffset
);
136 nsDependentString
str(strStart
, aReconv
->dwStrLen
);
137 Append(NS_ConvertUTF16toUTF8(str
));
139 AppendLiteral("\" }");
141 virtual ~GetReconvertStringLog() {}
147 static IMMHandler
* gIMMHandler
= nullptr;
149 /******************************************************************************
151 ******************************************************************************/
153 IMEContext::IMEContext(HWND aWnd
) : mWnd(aWnd
), mIMC(::ImmGetContext(aWnd
)) {}
155 IMEContext::IMEContext(nsWindow
* aWindowBase
)
156 : mWnd(aWindowBase
->GetWindowHandle()),
157 mIMC(::ImmGetContext(aWindowBase
->GetWindowHandle())) {}
159 void IMEContext::Init(HWND aWnd
) {
162 mIMC
= ::ImmGetContext(mWnd
);
165 void IMEContext::Init(nsWindow
* aWindowBase
) {
166 Init(aWindowBase
->GetWindowHandle());
169 void IMEContext::Clear() {
171 ::ImmReleaseContext(mWnd
, mIMC
);
177 /******************************************************************************
179 ******************************************************************************/
181 static UINT sWM_MSIME_MOUSE
= 0; // mouse message for MSIME 98/2000
183 WritingMode
IMMHandler::sWritingModeOfCompositionFont
;
184 nsString
IMMHandler::sIMEName
;
185 UINT
IMMHandler::sCodePage
= 0;
186 DWORD
IMMHandler::sIMEProperty
= 0;
187 DWORD
IMMHandler::sIMEUIProperty
= 0;
188 bool IMMHandler::sAssumeVerticalWritingModeNotSupported
= false;
189 bool IMMHandler::sHasFocus
= false;
191 #define IMPL_IS_IME_ACTIVE(aReadableName, aActualName) \
192 bool IMMHandler::Is##aReadableName##Active() { \
193 return sIMEName.Equals(aActualName); \
196 IMPL_IS_IME_ACTIVE(ATOK2006
, u
"ATOK 2006")
197 IMPL_IS_IME_ACTIVE(ATOK2007
, u
"ATOK 2007")
198 IMPL_IS_IME_ACTIVE(ATOK2008
, u
"ATOK 2008")
199 IMPL_IS_IME_ACTIVE(ATOK2009
, u
"ATOK 2009")
200 IMPL_IS_IME_ACTIVE(ATOK2010
, u
"ATOK 2010")
201 // NOTE: Even on Windows for en-US, the name of Google Japanese Input is
202 // written in Japanese.
203 IMPL_IS_IME_ACTIVE(GoogleJapaneseInput
,
204 u
"Google \x65E5\x672C\x8A9E\x5165\x529B "
205 u
"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB")
206 IMPL_IS_IME_ACTIVE(Japanist2003
, u
"Japanist 2003")
208 #undef IMPL_IS_IME_ACTIVE
211 bool IMMHandler::IsActiveIMEInBlockList() {
212 if (sIMEName
.IsEmpty()) {
216 // ATOK started to be TIP of TSF since 2011. Older than it, i.e., ATOK 2010
217 // and earlier have a lot of problems even for daily use. Perhaps, the
218 // reason is Win 8 has a lot of changes around IMM-IME support and TSF,
219 // and ATOK 2010 is released earlier than Win 8.
220 // ATOK 2006 crashes while converting a word with candidate window.
221 // ATOK 2007 doesn't paint and resize suggest window and candidate window
222 // correctly (showing white window or too big window).
223 // ATOK 2008 and ATOK 2009 crash when user just opens their open state.
224 // ATOK 2010 isn't installable newly on Win 7 or later, but we have a lot of
226 if ((IsATOK2006Active() || IsATOK2007Active() || IsATOK2008Active() ||
227 IsATOK2009Active() || IsATOK2010Active())) {
230 #endif // #ifdef _WIN64
235 void IMMHandler::EnsureHandlerInstance() {
237 gIMMHandler
= new IMMHandler();
242 void IMMHandler::Initialize() {
243 if (!sWM_MSIME_MOUSE
) {
244 sWM_MSIME_MOUSE
= ::RegisterWindowMessage(RWM_MOUSE
);
246 sAssumeVerticalWritingModeNotSupported
= Preferences::GetBool(
247 "intl.imm.vertical_writing.always_assume_not_supported", false);
248 InitKeyboardLayout(nullptr, ::GetKeyboardLayout(0));
252 void IMMHandler::Terminate() {
253 if (!gIMMHandler
) return;
255 gIMMHandler
= nullptr;
259 bool IMMHandler::IsComposingOnOurEditor() {
260 return gIMMHandler
&& gIMMHandler
->mIsComposing
;
264 bool IMMHandler::IsComposingWindow(nsWindow
* aWindow
) {
265 return gIMMHandler
&& gIMMHandler
->mComposingWindow
== aWindow
;
269 bool IMMHandler::IsTopLevelWindowOfComposition(nsWindow
* aWindow
) {
270 if (!gIMMHandler
|| !gIMMHandler
->mComposingWindow
) {
273 HWND wnd
= gIMMHandler
->mComposingWindow
->GetWindowHandle();
274 return WinUtils::GetTopLevelHWND(wnd
, true) == aWindow
->GetWindowHandle();
278 bool IMMHandler::ShouldDrawCompositionStringOurselves() {
279 // If current IME has special UI or its composition window should not
280 // positioned to caret position, we should now draw composition string
282 return !(sIMEProperty
& IME_PROP_SPECIAL_UI
) &&
283 (sIMEProperty
& IME_PROP_AT_CARET
);
287 bool IMMHandler::IsVerticalWritingSupported() {
288 // Even if IME claims that they support vertical writing mode but it may not
289 // support vertical writing mode for its candidate window.
290 if (sAssumeVerticalWritingModeNotSupported
) {
293 // Google Japanese Input doesn't support vertical writing mode. We should
294 // return false if it's active IME.
295 if (IsGoogleJapaneseInputActive()) {
298 return !!(sIMEUIProperty
& (UI_CAP_2700
| UI_CAP_ROT90
| UI_CAP_ROTANY
));
302 void IMMHandler::InitKeyboardLayout(nsWindow
* aWindow
, HKL aKeyboardLayout
) {
303 UINT IMENameLength
= ::ImmGetDescriptionW(aKeyboardLayout
, nullptr, 0);
305 // Add room for the terminating null character
306 sIMEName
.SetLength(++IMENameLength
);
308 ::ImmGetDescriptionW(aKeyboardLayout
, sIMEName
.get(), IMENameLength
);
309 // Adjust the length to ignore the terminating null character
310 sIMEName
.SetLength(IMENameLength
);
315 WORD langID
= LOWORD(aKeyboardLayout
);
316 ::GetLocaleInfoW(MAKELCID(langID
, SORT_DEFAULT
),
317 LOCALE_IDEFAULTANSICODEPAGE
| LOCALE_RETURN_NUMBER
,
318 (PWSTR
)&sCodePage
, sizeof(sCodePage
) / sizeof(WCHAR
));
319 sIMEProperty
= ::ImmGetProperty(aKeyboardLayout
, IGP_PROPERTY
);
320 sIMEUIProperty
= ::ImmGetProperty(aKeyboardLayout
, IGP_UI
);
322 // If active IME is a TIP of TSF, we cannot retrieve the name with IMM32 API.
323 // For hacking some bugs of some TIP, we should set an IME name from the
325 if (sCodePage
== 932 && sIMEName
.IsEmpty()) {
326 Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as",
330 // Whether the IME supports vertical writing mode might be changed or
331 // some IMEs may need specific font for their UI. Therefore, we should
332 // update composition font forcibly here.
334 MaybeAdjustCompositionFont(aWindow
, sWritingModeOfCompositionFont
, true);
337 MOZ_LOG(gIMELog
, LogLevel::Info
,
338 ("IMMHandler::InitKeyboardLayout, aKeyboardLayout=%p (\"%s\"), "
339 "sCodePage=%u, sIMEProperty=%s, sIMEUIProperty=%s",
340 aKeyboardLayout
, NS_ConvertUTF16toUTF8(sIMEName
).get(), sCodePage
,
341 GetIMEGeneralPropertyName(sIMEProperty
).get(),
342 GetIMEUIPropertyName(sIMEUIProperty
).get()));
346 UINT
IMMHandler::GetKeyboardCodePage() { return sCodePage
; }
349 IMENotificationRequests
IMMHandler::GetIMENotificationRequests() {
350 return IMENotificationRequests(
351 IMENotificationRequests::NOTIFY_POSITION_CHANGE
|
352 IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR
);
355 // used for checking the lParam of WM_IME_COMPOSITION
356 #define IS_COMPOSING_LPARAM(lParam) \
357 ((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS))
358 #define IS_COMMITTING_LPARAM(lParam) ((lParam)&GCS_RESULTSTR)
359 // Some IMEs (e.g., the standard IME for Korean) don't have caret position,
360 // then, we should not set caret position to compositionchange event.
361 #define NO_IME_CARET -1
363 IMMHandler::IMMHandler()
364 : mComposingWindow(nullptr),
365 mCursorPosition(NO_IME_CARET
),
366 mCompositionStart(0),
367 mIsComposing(false) {
368 MOZ_LOG(gIMELog
, LogLevel::Debug
, ("IMMHandler::IMMHandler is created"));
371 IMMHandler::~IMMHandler() {
374 gIMELog
, LogLevel::Error
,
375 (" IMMHandler::~IMMHandler, ERROR, the instance is still composing"));
377 MOZ_LOG(gIMELog
, LogLevel::Debug
, ("IMMHandler::IMMHandler is destroyed"));
380 nsresult
IMMHandler::EnsureClauseArray(int32_t aCount
) {
381 NS_ENSURE_ARG_MIN(aCount
, 0);
382 mClauseArray
.SetCapacity(aCount
+ 32);
386 nsresult
IMMHandler::EnsureAttributeArray(int32_t aCount
) {
387 NS_ENSURE_ARG_MIN(aCount
, 0);
388 mAttributeArray
.SetCapacity(aCount
+ 64);
393 void IMMHandler::CommitComposition(nsWindow
* aWindow
, bool aForce
) {
394 MOZ_LOG(gIMELog
, LogLevel::Info
,
395 ("IMMHandler::CommitComposition, aForce=%s, aWindow=%p, hWnd=%p, "
396 "mComposingWindow=%p%s",
397 GetBoolName(aForce
), aWindow
, aWindow
->GetWindowHandle(),
398 gIMMHandler
? gIMMHandler
->mComposingWindow
: nullptr,
399 gIMMHandler
&& gIMMHandler
->mComposingWindow
400 ? IsComposingOnOurEditor() ? " (composing on editor)"
401 : " (composing on plug-in)"
403 if (!aForce
&& !IsComposingWindow(aWindow
)) {
407 IMEContext
context(aWindow
);
408 bool associated
= context
.AssociateDefaultContext();
409 MOZ_LOG(gIMELog
, LogLevel::Info
,
410 (" IMMHandler::CommitComposition, associated=%s",
411 GetBoolName(associated
)));
413 if (context
.IsValid()) {
414 ::ImmNotifyIME(context
.get(), NI_COMPOSITIONSTR
, CPS_COMPLETE
, 0);
415 ::ImmNotifyIME(context
.get(), NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
419 context
.Disassociate();
424 void IMMHandler::CancelComposition(nsWindow
* aWindow
, bool aForce
) {
425 MOZ_LOG(gIMELog
, LogLevel::Info
,
426 ("IMMHandler::CancelComposition, aForce=%s, aWindow=%p, hWnd=%p, "
427 "mComposingWindow=%p%s",
428 GetBoolName(aForce
), aWindow
, aWindow
->GetWindowHandle(),
429 gIMMHandler
? gIMMHandler
->mComposingWindow
: nullptr,
430 gIMMHandler
&& gIMMHandler
->mComposingWindow
431 ? IsComposingOnOurEditor() ? " (composing on editor)"
432 : " (composing on plug-in)"
434 if (!aForce
&& !IsComposingWindow(aWindow
)) {
438 IMEContext
context(aWindow
);
439 bool associated
= context
.AssociateDefaultContext();
440 MOZ_LOG(gIMELog
, LogLevel::Info
,
441 (" IMMHandler::CancelComposition, associated=%s",
442 GetBoolName(associated
)));
444 if (context
.IsValid()) {
445 ::ImmNotifyIME(context
.get(), NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
449 context
.Disassociate();
454 void IMMHandler::OnFocusChange(bool aFocus
, nsWindow
* aWindow
) {
455 MOZ_LOG(gIMELog
, LogLevel::Info
,
456 ("IMMHandler::OnFocusChange(aFocus=%s, aWindow=%p), sHasFocus=%s, "
457 "IsComposingWindow(aWindow)=%s, aWindow->Destroyed()=%s",
458 GetBoolName(aFocus
), aWindow
, GetBoolName(sHasFocus
),
459 GetBoolName(IsComposingWindow(aWindow
)),
460 GetBoolName(aWindow
->Destroyed())));
463 IMEHandler::MaybeDestroyNativeCaret();
464 if (IsComposingWindow(aWindow
) && aWindow
->Destroyed()) {
465 CancelComposition(aWindow
);
469 gIMMHandler
->mContentSelection
.reset();
475 void IMMHandler::OnUpdateComposition(nsWindow
* aWindow
) {
480 IMEContext
context(aWindow
);
481 gIMMHandler
->SetIMERelatedWindowsPos(aWindow
, context
);
485 void IMMHandler::OnSelectionChange(nsWindow
* aWindow
,
486 const IMENotification
& aIMENotification
,
488 if (!aIMENotification
.mSelectionChangeData
.mCausedByComposition
&&
490 MaybeAdjustCompositionFont(
491 aWindow
, aIMENotification
.mSelectionChangeData
.GetWritingMode());
493 // MaybeAdjustCompositionFont() may create gIMMHandler. So, check it
494 // after a call of MaybeAdjustCompositionFont().
496 gIMMHandler
->mContentSelection
=
497 Some(ContentSelection(aIMENotification
.mSelectionChangeData
));
502 void IMMHandler::MaybeAdjustCompositionFont(nsWindow
* aWindow
,
503 const WritingMode
& aWritingMode
,
506 case 932: // Japanese Shift-JIS
507 case 936: // Simlified Chinese GBK
509 case 950: // Traditional Chinese Big5
510 EnsureHandlerInstance();
513 // If there is no instance of nsIMM32Hander, we shouldn't waste footprint.
519 // Like Navi-Bar of ATOK, some IMEs may require proper composition font even
520 // before sending WM_IME_STARTCOMPOSITION.
521 IMEContext
context(aWindow
);
522 gIMMHandler
->AdjustCompositionFont(aWindow
, context
, aWritingMode
,
527 bool IMMHandler::ProcessInputLangChangeMessage(nsWindow
* aWindow
, WPARAM wParam
,
529 MSGResult
& aResult
) {
531 aResult
.mConsumed
= false;
532 // We don't need to create the instance of the handler here.
534 gIMMHandler
->OnInputLangChange(aWindow
, wParam
, lParam
, aResult
);
536 InitKeyboardLayout(aWindow
, reinterpret_cast<HKL
>(lParam
));
537 // We can release the instance here, because the instance may be never
538 // used. E.g., the new keyboard layout may not use IME, or it may use TSF.
540 // Don't return as "processed", the messages should be processed on nsWindow
546 bool IMMHandler::ProcessMessage(nsWindow
* aWindow
, UINT msg
, WPARAM
& wParam
,
547 LPARAM
& lParam
, MSGResult
& aResult
) {
548 // XXX We store the composing window in mComposingWindow. If IME messages are
549 // sent to different window, we should commit the old transaction. And also
550 // if the new window handle is not focused, probably, we should not start
551 // the composition, however, such case should not be, it's just bad scenario.
555 case WM_INPUTLANGCHANGE
:
556 return ProcessInputLangChangeMessage(aWindow
, wParam
, lParam
, aResult
);
557 case WM_IME_STARTCOMPOSITION
:
558 EnsureHandlerInstance();
559 return gIMMHandler
->OnIMEStartComposition(aWindow
, aResult
);
560 case WM_IME_COMPOSITION
:
561 EnsureHandlerInstance();
562 return gIMMHandler
->OnIMEComposition(aWindow
, wParam
, lParam
, aResult
);
563 case WM_IME_ENDCOMPOSITION
:
564 EnsureHandlerInstance();
565 return gIMMHandler
->OnIMEEndComposition(aWindow
, aResult
);
567 return OnIMEChar(aWindow
, wParam
, lParam
, aResult
);
569 return OnIMENotify(aWindow
, wParam
, lParam
, aResult
);
571 EnsureHandlerInstance();
572 return gIMMHandler
->OnIMERequest(aWindow
, wParam
, lParam
, aResult
);
574 return OnIMESelect(aWindow
, wParam
, lParam
, aResult
);
575 case WM_IME_SETCONTEXT
:
576 return OnIMESetContext(aWindow
, wParam
, lParam
, aResult
);
578 return OnKeyDownEvent(aWindow
, wParam
, lParam
, aResult
);
583 return gIMMHandler
->OnChar(aWindow
, wParam
, lParam
, aResult
);
589 /****************************************************************************
591 ****************************************************************************/
593 void IMMHandler::OnInputLangChange(nsWindow
* aWindow
, WPARAM wParam
,
594 LPARAM lParam
, MSGResult
& aResult
) {
595 MOZ_LOG(gIMELog
, LogLevel::Info
,
596 ("IMMHandler::OnInputLangChange, hWnd=%p, wParam=%08zx, "
597 "lParam=%08" PRIxLPTR
,
598 aWindow
->GetWindowHandle(), wParam
, lParam
));
600 aWindow
->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION
);
601 NS_ASSERTION(!mIsComposing
, "ResetInputState failed");
604 HandleEndComposition(aWindow
);
607 aResult
.mConsumed
= false;
610 bool IMMHandler::OnIMEStartComposition(nsWindow
* aWindow
, MSGResult
& aResult
) {
611 MOZ_LOG(gIMELog
, LogLevel::Info
,
612 ("IMMHandler::OnIMEStartComposition, hWnd=%p, mIsComposing=%s",
613 aWindow
->GetWindowHandle(), GetBoolName(mIsComposing
)));
614 aResult
.mConsumed
= ShouldDrawCompositionStringOurselves();
616 NS_WARNING("Composition has been already started");
620 IMEContext
context(aWindow
);
621 HandleStartComposition(aWindow
, context
);
625 bool IMMHandler::OnIMEComposition(nsWindow
* aWindow
, WPARAM wParam
,
626 LPARAM lParam
, MSGResult
& aResult
) {
628 gIMELog
, LogLevel::Info
,
629 ("IMMHandler::OnIMEComposition, hWnd=%p, lParam=%08" PRIxLPTR
630 ", 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=%p, 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=%p, char=%08zx",
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=%p",
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=%p, IMN_CHANGECANDIDATE, "
719 "lParam=%08" PRIxLPTR
,
720 aWindow
->GetWindowHandle(), lParam
));
722 case IMN_CLOSECANDIDATE
:
723 MOZ_LOG(gIMELog
, LogLevel::Info
,
724 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_CLOSECANDIDATE, "
725 "lParam=%08" PRIxLPTR
,
726 aWindow
->GetWindowHandle(), lParam
));
728 case IMN_CLOSESTATUSWINDOW
:
729 MOZ_LOG(gIMELog
, LogLevel::Info
,
730 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_CLOSESTATUSWINDOW",
731 aWindow
->GetWindowHandle()));
734 MOZ_LOG(gIMELog
, LogLevel::Info
,
735 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_GUIDELINE",
736 aWindow
->GetWindowHandle()));
738 case IMN_OPENCANDIDATE
:
739 MOZ_LOG(gIMELog
, LogLevel::Info
,
740 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_OPENCANDIDATE, "
741 "lParam=%08" PRIxLPTR
,
742 aWindow
->GetWindowHandle(), lParam
));
744 case IMN_OPENSTATUSWINDOW
:
745 MOZ_LOG(gIMELog
, LogLevel::Info
,
746 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_OPENSTATUSWINDOW",
747 aWindow
->GetWindowHandle()));
749 case IMN_SETCANDIDATEPOS
:
750 MOZ_LOG(gIMELog
, LogLevel::Info
,
751 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETCANDIDATEPOS, "
752 "lParam=%08" PRIxLPTR
,
753 aWindow
->GetWindowHandle(), lParam
));
755 case IMN_SETCOMPOSITIONFONT
:
756 MOZ_LOG(gIMELog
, LogLevel::Info
,
757 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETCOMPOSITIONFONT",
758 aWindow
->GetWindowHandle()));
760 case IMN_SETCOMPOSITIONWINDOW
:
761 MOZ_LOG(gIMELog
, LogLevel::Info
,
762 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETCOMPOSITIONWINDOW",
763 aWindow
->GetWindowHandle()));
765 case IMN_SETCONVERSIONMODE
:
766 MOZ_LOG(gIMELog
, LogLevel::Info
,
767 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETCONVERSIONMODE",
768 aWindow
->GetWindowHandle()));
770 case IMN_SETOPENSTATUS
:
771 MOZ_LOG(gIMELog
, LogLevel::Info
,
772 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETOPENSTATUS",
773 aWindow
->GetWindowHandle()));
775 case IMN_SETSENTENCEMODE
:
776 MOZ_LOG(gIMELog
, LogLevel::Info
,
777 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETSENTENCEMODE",
778 aWindow
->GetWindowHandle()));
780 case IMN_SETSTATUSWINDOWPOS
:
781 MOZ_LOG(gIMELog
, LogLevel::Info
,
782 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETSTATUSWINDOWPOS",
783 aWindow
->GetWindowHandle()));
786 MOZ_LOG(gIMELog
, LogLevel::Info
,
787 ("IMMHandler::OnIMENotify, hWnd=%p, 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=%p, 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=%p, 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=%p, IMR_DOCUMENTFEED",
816 aWindow
->GetWindowHandle()));
817 aResult
.mConsumed
= HandleDocumentFeed(aWindow
, lParam
, &aResult
.mResult
);
820 MOZ_LOG(gIMELog
, LogLevel::Info
,
821 ("IMMHandler::OnIMERequest, hWnd=%p, wParam=%08zx",
822 aWindow
->GetWindowHandle(), wParam
));
823 aResult
.mConsumed
= false;
829 bool IMMHandler::OnIMESelect(nsWindow
* aWindow
, WPARAM wParam
, LPARAM lParam
,
830 MSGResult
& aResult
) {
832 gIMELog
, LogLevel::Info
,
833 ("IMMHandler::OnIMESelect, hWnd=%p, wParam=%08zx, lParam=%08" PRIxLPTR
,
834 aWindow
->GetWindowHandle(), wParam
, lParam
));
837 aResult
.mConsumed
= false;
842 bool IMMHandler::OnIMESetContext(nsWindow
* aWindow
, WPARAM wParam
,
843 LPARAM lParam
, MSGResult
& aResult
) {
844 MOZ_LOG(gIMELog
, LogLevel::Info
,
845 ("IMMHandler::OnIMESetContext, hWnd=%p, %s, lParam=%08" PRIxLPTR
,
846 aWindow
->GetWindowHandle(), wParam
? "Active" : "Deactive", lParam
));
848 aResult
.mConsumed
= false;
850 // NOTE: If the aWindow is top level window of the composing window because
851 // when a window on deactive window gets focus, WM_IME_SETCONTEXT (wParam is
852 // TRUE) is sent to the top level window first. After that,
853 // WM_IME_SETCONTEXT (wParam is FALSE) is sent to the top level window.
854 // Finally, WM_IME_SETCONTEXT (wParam is TRUE) is sent to the focused window.
855 // The top level window never becomes composing window, so, we can ignore
856 // the WM_IME_SETCONTEXT on the top level window.
857 if (IsTopLevelWindowOfComposition(aWindow
)) {
858 MOZ_LOG(gIMELog
, LogLevel::Info
,
859 (" IMMHandler::OnIMESetContext, hWnd=%p is top level window",
860 aWindow
->GetWindowHandle()));
864 // When IME context is activating on another window,
865 // we should commit the old composition on the old window.
866 bool cancelComposition
= false;
867 if (wParam
&& gIMMHandler
) {
868 cancelComposition
= gIMMHandler
->CommitCompositionOnPreviousWindow(aWindow
);
871 if (wParam
&& (lParam
& ISC_SHOWUICOMPOSITIONWINDOW
) &&
872 ShouldDrawCompositionStringOurselves()) {
873 MOZ_LOG(gIMELog
, LogLevel::Info
,
874 (" IMMHandler::OnIMESetContext, ISC_SHOWUICOMPOSITIONWINDOW is "
876 lParam
&= ~ISC_SHOWUICOMPOSITIONWINDOW
;
879 // We should sent WM_IME_SETCONTEXT to the DefWndProc here because the
880 // ancestor windows shouldn't receive this message. If they receive the
881 // message, we cannot know whether which window is the target of the message.
882 aResult
.mResult
= ::DefWindowProc(aWindow
->GetWindowHandle(),
883 WM_IME_SETCONTEXT
, wParam
, lParam
);
885 // Cancel composition on the new window if we committed our composition on
887 if (cancelComposition
) {
888 CancelComposition(aWindow
, true);
891 aResult
.mConsumed
= true;
895 bool IMMHandler::OnChar(nsWindow
* aWindow
, WPARAM wParam
, LPARAM lParam
,
896 MSGResult
& aResult
) {
897 // The return value must be same as aResult.mConsumed because only when we
898 // consume the message, the caller shouldn't do anything anymore but
899 // otherwise, the caller should handle the message.
900 aResult
.mConsumed
= false;
901 if (IsIMECharRecordsEmpty()) {
902 return aResult
.mConsumed
;
906 DequeueIMECharRecords(recWParam
, recLParam
);
908 gIMELog
, LogLevel::Info
,
909 ("IMMHandler::OnChar, aWindow=%p, wParam=%08zx, lParam=%08" PRIxLPTR
", "
910 "recorded: wParam=%08zx, lParam=%08" PRIxLPTR
,
911 aWindow
->GetWindowHandle(), wParam
, lParam
, recWParam
, recLParam
));
912 // If an unexpected char message comes, we should reset the records,
913 // of course, this shouldn't happen.
914 if (recWParam
!= wParam
|| recLParam
!= lParam
) {
915 ResetIMECharRecords();
916 return aResult
.mConsumed
;
918 // Eat the char message which is caused by WM_IME_CHAR because we should
919 // have processed the IME messages, so, this message could be come from
920 // a windowless plug-in.
921 aResult
.mConsumed
= true;
922 return aResult
.mConsumed
;
925 /****************************************************************************
927 ****************************************************************************/
929 TextEventDispatcher
* IMMHandler::GetTextEventDispatcherFor(nsWindow
* aWindow
) {
930 return aWindow
== mComposingWindow
&& mDispatcher
932 : aWindow
->GetTextEventDispatcher();
935 void IMMHandler::HandleStartComposition(nsWindow
* aWindow
,
936 const IMEContext
& aContext
) {
937 MOZ_ASSERT(!mIsComposing
,
938 "HandleStartComposition is called but mIsComposing is TRUE");
940 const Maybe
<ContentSelection
>& contentSelection
=
941 GetContentSelectionWithQueryIfNothing(aWindow
);
942 if (contentSelection
.isNothing()) {
943 MOZ_LOG(gIMELog
, LogLevel::Error
,
944 (" IMMHandler::HandleStartComposition, FAILED, due to "
945 "Selection::GetContentSelectionWithQueryIfNothing() failure"));
948 if (!contentSelection
->HasRange()) {
949 MOZ_LOG(gIMELog
, LogLevel::Error
,
950 (" IMMHandler::HandleStartComposition, FAILED, due to "
951 "there is no selection"));
955 AdjustCompositionFont(aWindow
, aContext
, contentSelection
->WritingModeRef());
957 mCompositionStart
= contentSelection
->OffsetAndDataRef().StartOffset();
958 mCursorPosition
= NO_IME_CARET
;
960 RefPtr
<TextEventDispatcher
> dispatcher
= GetTextEventDispatcherFor(aWindow
);
961 nsresult rv
= dispatcher
->BeginNativeInputTransaction();
962 if (NS_WARN_IF(NS_FAILED(rv
))) {
963 MOZ_LOG(gIMELog
, LogLevel::Error
,
964 (" IMMHandler::HandleStartComposition, FAILED due to "
965 "TextEventDispatcher::BeginNativeInputTransaction() failure"));
968 WidgetEventTime eventTime
= aWindow
->CurrentMessageWidgetEventTime();
969 nsEventStatus status
;
970 rv
= dispatcher
->StartComposition(status
, &eventTime
);
971 if (NS_WARN_IF(NS_FAILED(rv
))) {
972 MOZ_LOG(gIMELog
, LogLevel::Error
,
973 (" IMMHandler::HandleStartComposition, FAILED, due to "
974 "TextEventDispatcher::StartComposition() failure"));
979 mComposingWindow
= aWindow
;
980 mDispatcher
= dispatcher
;
982 MOZ_LOG(gIMELog
, LogLevel::Info
,
983 ("IMMHandler::HandleStartComposition, START composition, "
984 "mCompositionStart=%u",
988 bool IMMHandler::HandleComposition(nsWindow
* aWindow
,
989 const IMEContext
& aContext
, LPARAM lParam
) {
991 // MS-IME 95/97/98/2000 may send WM_IME_COMPOSITION with non-conversion
992 // mode before it send WM_IME_STARTCOMPOSITION.
993 // However, ATOK sends a WM_IME_COMPOSITION before WM_IME_STARTCOMPOSITION,
994 // and if we access ATOK via some APIs, ATOK will sometimes fail to
995 // initialize its state. If WM_IME_STARTCOMPOSITION is already in the
996 // message queue, we should ignore the strange WM_IME_COMPOSITION message and
997 // skip to the next. So, we should look for next composition message
998 // (WM_IME_STARTCOMPOSITION or WM_IME_ENDCOMPOSITION or WM_IME_COMPOSITION),
999 // and if it's WM_IME_STARTCOMPOSITION, and one more next composition message
1000 // is WM_IME_COMPOSITION, current IME is ATOK, probably. Otherwise, we
1001 // should start composition forcibly.
1002 if (!mIsComposing
) {
1004 HWND wnd
= aWindow
->GetWindowHandle();
1005 if (WinUtils::PeekMessage(&msg1
, wnd
, WM_IME_STARTCOMPOSITION
,
1006 WM_IME_COMPOSITION
, PM_NOREMOVE
) &&
1007 msg1
.message
== WM_IME_STARTCOMPOSITION
&&
1008 WinUtils::PeekMessage(&msg2
, wnd
, WM_IME_ENDCOMPOSITION
,
1009 WM_IME_COMPOSITION
, PM_NOREMOVE
) &&
1010 msg2
.message
== WM_IME_COMPOSITION
) {
1011 MOZ_LOG(gIMELog
, LogLevel::Info
,
1012 ("IMMHandler::HandleComposition, Ignores due to find a "
1013 "WM_IME_STARTCOMPOSITION"));
1014 return ShouldDrawCompositionStringOurselves();
1018 bool startCompositionMessageHasBeenSent
= mIsComposing
;
1021 // This catches a fixed result
1023 if (IS_COMMITTING_LPARAM(lParam
)) {
1024 if (!mIsComposing
) {
1025 HandleStartComposition(aWindow
, aContext
);
1028 GetCompositionString(aContext
, GCS_RESULTSTR
, mCompositionString
);
1030 MOZ_LOG(gIMELog
, LogLevel::Info
,
1031 ("IMMHandler::HandleComposition, GCS_RESULTSTR"));
1033 HandleEndComposition(aWindow
, &mCompositionString
);
1035 if (!IS_COMPOSING_LPARAM(lParam
)) {
1036 return ShouldDrawCompositionStringOurselves();
1041 // This provides us with a composition string
1043 if (!mIsComposing
) {
1044 HandleStartComposition(aWindow
, aContext
);
1047 //--------------------------------------------------------
1048 // 1. Get GCS_COMPSTR
1049 //--------------------------------------------------------
1050 MOZ_LOG(gIMELog
, LogLevel::Info
,
1051 ("IMMHandler::HandleComposition, GCS_COMPSTR"));
1053 nsAutoString
previousCompositionString(mCompositionString
);
1054 GetCompositionString(aContext
, GCS_COMPSTR
, mCompositionString
);
1056 if (!IS_COMPOSING_LPARAM(lParam
)) {
1058 gIMELog
, LogLevel::Info
,
1059 (" IMMHandler::HandleComposition, lParam doesn't indicate composing, "
1060 "mCompositionString=\"%s\", previousCompositionString=\"%s\"",
1061 NS_ConvertUTF16toUTF8(mCompositionString
).get(),
1062 NS_ConvertUTF16toUTF8(previousCompositionString
).get()));
1064 // If composition string isn't changed, we can trust the lParam.
1065 // So, we need to do nothing.
1066 if (previousCompositionString
== mCompositionString
) {
1067 return ShouldDrawCompositionStringOurselves();
1070 // IME may send WM_IME_COMPOSITION without composing lParam values
1071 // when composition string becomes empty (e.g., using Backspace key).
1072 // If composition string is empty, we should dispatch a compositionchange
1073 // event with empty string and clear the clause information.
1074 if (mCompositionString
.IsEmpty()) {
1075 mClauseArray
.Clear();
1076 mAttributeArray
.Clear();
1077 mCursorPosition
= 0;
1078 DispatchCompositionChangeEvent(aWindow
, aContext
);
1079 return ShouldDrawCompositionStringOurselves();
1082 // Otherwise, we cannot trust the lParam value. We might need to
1083 // dispatch compositionchange event with the latest composition string
1087 // See https://bugzilla.mozilla.org/show_bug.cgi?id=296339
1088 if (mCompositionString
.IsEmpty() && !startCompositionMessageHasBeenSent
) {
1089 // In this case, maybe, the sender is MSPinYin. That sends *only*
1090 // WM_IME_COMPOSITION with GCS_COMP* and GCS_RESULT* when
1091 // user inputted the Chinese full stop. So, that doesn't send
1092 // WM_IME_STARTCOMPOSITION and WM_IME_ENDCOMPOSITION.
1093 // If WM_IME_STARTCOMPOSITION was not sent and the composition
1094 // string is null (it indicates the composition transaction ended),
1095 // WM_IME_ENDCOMPOSITION may not be sent. If so, we cannot run
1096 // HandleEndComposition() in other place.
1097 MOZ_LOG(gIMELog
, LogLevel::Info
,
1098 (" IMMHandler::HandleComposition, Aborting GCS_COMPSTR"));
1099 HandleEndComposition(aWindow
);
1100 return IS_COMMITTING_LPARAM(lParam
);
1103 //--------------------------------------------------------
1104 // 2. Get GCS_COMPCLAUSE
1105 //--------------------------------------------------------
1106 long clauseArrayLength
=
1107 ::ImmGetCompositionStringW(aContext
.get(), GCS_COMPCLAUSE
, nullptr, 0);
1108 clauseArrayLength
/= sizeof(uint32_t);
1110 if (clauseArrayLength
> 0) {
1111 nsresult rv
= EnsureClauseArray(clauseArrayLength
);
1112 NS_ENSURE_SUCCESS(rv
, false);
1114 // Intelligent ABC IME (Simplified Chinese IME, the code page is 936)
1115 // will crash in ImmGetCompositionStringW for GCS_COMPCLAUSE (bug 424663).
1116 // See comment 35 of the bug for the detail. Therefore, we should use A
1117 // API for it, however, we should not kill Unicode support on all IMEs.
1118 bool useA_API
= !(sIMEProperty
& IME_PROP_UNICODE
);
1120 MOZ_LOG(gIMELog
, LogLevel::Info
,
1121 (" IMMHandler::HandleComposition, GCS_COMPCLAUSE, useA_API=%s",
1122 useA_API
? "TRUE" : "FALSE"));
1124 long clauseArrayLength2
=
1125 useA_API
? ::ImmGetCompositionStringA(
1126 aContext
.get(), GCS_COMPCLAUSE
, mClauseArray
.Elements(),
1127 mClauseArray
.Capacity() * sizeof(uint32_t))
1128 : ::ImmGetCompositionStringW(
1129 aContext
.get(), GCS_COMPCLAUSE
, mClauseArray
.Elements(),
1130 mClauseArray
.Capacity() * sizeof(uint32_t));
1131 clauseArrayLength2
/= sizeof(uint32_t);
1133 if (clauseArrayLength
!= clauseArrayLength2
) {
1134 MOZ_LOG(gIMELog
, LogLevel::Info
,
1135 (" IMMHandler::HandleComposition, GCS_COMPCLAUSE, "
1136 "clauseArrayLength=%ld but clauseArrayLength2=%ld",
1137 clauseArrayLength
, clauseArrayLength2
));
1138 if (clauseArrayLength
> clauseArrayLength2
)
1139 clauseArrayLength
= clauseArrayLength2
;
1142 if (useA_API
&& clauseArrayLength
> 0) {
1143 // Convert each values of sIMECompClauseArray. The values mean offset of
1144 // the clauses in ANSI string. But we need the values in Unicode string.
1145 nsAutoCString compANSIStr
;
1146 if (ConvertToANSIString(mCompositionString
, GetKeyboardCodePage(),
1148 uint32_t maxlen
= compANSIStr
.Length();
1149 mClauseArray
.SetLength(clauseArrayLength
);
1150 mClauseArray
[0] = 0; // first value must be 0
1151 for (int32_t i
= 1; i
< clauseArrayLength
; i
++) {
1152 uint32_t len
= std::min(mClauseArray
[i
], maxlen
);
1154 ::MultiByteToWideChar(GetKeyboardCodePage(), MB_PRECOMPOSED
,
1155 (LPCSTR
)compANSIStr
.get(), len
, nullptr, 0);
1160 // compClauseArrayLength may be negative. I.e., ImmGetCompositionStringW
1161 // may return an error code.
1162 mClauseArray
.SetLength(std::max
<long>(0, clauseArrayLength
));
1164 MOZ_LOG(gIMELog
, LogLevel::Info
,
1165 (" IMMHandler::HandleComposition, GCS_COMPCLAUSE, mClauseLength=%zu",
1166 mClauseArray
.Length()));
1168 //--------------------------------------------------------
1169 // 3. Get GCS_COMPATTR
1170 //--------------------------------------------------------
1171 // This provides us with the attribute string necessary
1172 // for doing hiliting
1173 long attrArrayLength
=
1174 ::ImmGetCompositionStringW(aContext
.get(), GCS_COMPATTR
, nullptr, 0);
1175 attrArrayLength
/= sizeof(uint8_t);
1177 if (attrArrayLength
> 0) {
1178 nsresult rv
= EnsureAttributeArray(attrArrayLength
);
1179 NS_ENSURE_SUCCESS(rv
, false);
1180 attrArrayLength
= ::ImmGetCompositionStringW(
1181 aContext
.get(), GCS_COMPATTR
, mAttributeArray
.Elements(),
1182 mAttributeArray
.Capacity() * sizeof(uint8_t));
1185 // attrStrLen may be negative. I.e., ImmGetCompositionStringW may return an
1187 mAttributeArray
.SetLength(std::max
<long>(0, attrArrayLength
));
1190 gIMELog
, LogLevel::Info
,
1191 (" IMMHandler::HandleComposition, GCS_COMPATTR, mAttributeLength=%zu",
1192 mAttributeArray
.Length()));
1194 //--------------------------------------------------------
1195 // 4. Get GCS_CURSOPOS
1196 //--------------------------------------------------------
1197 // Some IMEs (e.g., the standard IME for Korean) don't have caret position.
1198 if (lParam
& GCS_CURSORPOS
) {
1200 ::ImmGetCompositionStringW(aContext
.get(), GCS_CURSORPOS
, nullptr, 0);
1201 if (mCursorPosition
< 0) {
1202 mCursorPosition
= NO_IME_CARET
; // The result is error
1205 mCursorPosition
= NO_IME_CARET
;
1208 NS_ASSERTION(mCursorPosition
<= (long)mCompositionString
.Length(),
1211 MOZ_LOG(gIMELog
, LogLevel::Info
,
1212 (" IMMHandler::HandleComposition, GCS_CURSORPOS, mCursorPosition=%d",
1215 //--------------------------------------------------------
1216 // 5. Send the compositionchange event
1217 //--------------------------------------------------------
1218 DispatchCompositionChangeEvent(aWindow
, aContext
);
1220 return ShouldDrawCompositionStringOurselves();
1223 void IMMHandler::HandleEndComposition(nsWindow
* aWindow
,
1224 const nsAString
* aCommitString
) {
1225 MOZ_ASSERT(mIsComposing
,
1226 "HandleEndComposition is called but mIsComposing is FALSE");
1228 MOZ_LOG(gIMELog
, LogLevel::Info
,
1229 ("IMMHandler::HandleEndComposition(aWindow=0x%p, aCommitString=0x%p "
1231 aWindow
, aCommitString
,
1232 aCommitString
? NS_ConvertUTF16toUTF8(*aCommitString
).get() : ""));
1234 IMEHandler::MaybeDestroyNativeCaret();
1236 RefPtr
<TextEventDispatcher
> dispatcher
= GetTextEventDispatcherFor(aWindow
);
1237 nsresult rv
= dispatcher
->BeginNativeInputTransaction();
1238 if (NS_WARN_IF(NS_FAILED(rv
))) {
1239 MOZ_LOG(gIMELog
, LogLevel::Error
,
1240 (" IMMHandler::HandleEndComposition, FAILED due to "
1241 "TextEventDispatcher::BeginNativeInputTransaction() failure"));
1244 WidgetEventTime eventTime
= aWindow
->CurrentMessageWidgetEventTime();
1245 nsEventStatus status
;
1246 rv
= dispatcher
->CommitComposition(status
, aCommitString
, &eventTime
);
1247 if (NS_WARN_IF(NS_FAILED(rv
))) {
1248 MOZ_LOG(gIMELog
, LogLevel::Error
,
1249 (" IMMHandler::HandleStartComposition, FAILED, due to "
1250 "TextEventDispatcher::CommitComposition() failure"));
1253 mIsComposing
= false;
1254 // XXX aWindow and mComposingWindow are always same??
1255 mComposingWindow
= nullptr;
1256 mDispatcher
= nullptr;
1259 bool IMMHandler::HandleReconvert(nsWindow
* aWindow
, LPARAM lParam
,
1262 RECONVERTSTRING
* pReconv
= reinterpret_cast<RECONVERTSTRING
*>(lParam
);
1264 const Maybe
<ContentSelection
>& contentSelection
=
1265 GetContentSelectionWithQueryIfNothing(aWindow
);
1266 if (contentSelection
.isNothing()) {
1267 MOZ_LOG(gIMELog
, LogLevel::Error
,
1268 ("IMMHandler::HandleReconvert, FAILED, due to "
1269 "Selection::GetContentSelectionWithQueryIfNothing() failure"));
1273 const uint32_t len
= contentSelection
->HasRange()
1274 ? contentSelection
->OffsetAndDataRef().Length()
1276 uint32_t needSize
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
1279 // Return need size to reconvert.
1281 MOZ_LOG(gIMELog
, LogLevel::Error
,
1282 ("IMMHandler::HandleReconvert, There are not selected text"));
1285 *oResult
= needSize
;
1286 MOZ_LOG(gIMELog
, LogLevel::Info
,
1287 ("IMMHandler::HandleReconvert, succeeded, result=%" PRIdLPTR
,
1292 if (pReconv
->dwSize
< needSize
) {
1293 MOZ_LOG(gIMELog
, LogLevel::Info
,
1294 ("IMMHandler::HandleReconvert, FAILED, pReconv->dwSize=%ld, "
1296 pReconv
->dwSize
, needSize
));
1300 *oResult
= needSize
;
1302 // Fill reconvert struct
1303 pReconv
->dwVersion
= 0;
1304 pReconv
->dwStrLen
= len
;
1305 pReconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
1306 pReconv
->dwCompStrLen
= len
;
1307 pReconv
->dwCompStrOffset
= 0;
1308 pReconv
->dwTargetStrLen
= len
;
1309 pReconv
->dwTargetStrOffset
= 0;
1312 ::CopyMemory(reinterpret_cast<LPVOID
>(lParam
+ sizeof(RECONVERTSTRING
)),
1313 contentSelection
->OffsetAndDataRef().DataRef().get(),
1314 len
* sizeof(WCHAR
));
1318 gIMELog
, LogLevel::Info
,
1319 ("IMMHandler::HandleReconvert, SUCCEEDED, pReconv=%s, result=%" PRIdLPTR
,
1320 GetReconvertStringLog(pReconv
).get(), *oResult
));
1325 bool IMMHandler::HandleQueryCharPosition(nsWindow
* aWindow
, LPARAM lParam
,
1327 uint32_t len
= mIsComposing
? mCompositionString
.Length() : 0;
1329 IMECHARPOSITION
* pCharPosition
= reinterpret_cast<IMECHARPOSITION
*>(lParam
);
1330 if (!pCharPosition
) {
1331 MOZ_LOG(gIMELog
, LogLevel::Error
,
1332 ("IMMHandler::HandleQueryCharPosition, FAILED, due to "
1333 "pCharPosition is null"));
1336 if (pCharPosition
->dwSize
< sizeof(IMECHARPOSITION
)) {
1337 MOZ_LOG(gIMELog
, LogLevel::Error
,
1338 ("IMMHandler::HandleReconvert, FAILED, pCharPosition->dwSize=%lu, "
1339 "sizeof(IMECHARPOSITION)=%zu",
1340 pCharPosition
->dwSize
, sizeof(IMECHARPOSITION
)));
1343 if (::GetFocus() != aWindow
->GetWindowHandle()) {
1344 MOZ_LOG(gIMELog
, LogLevel::Error
,
1345 ("IMMHandler::HandleReconvert, FAILED, ::GetFocus()=%p, "
1346 "OurWindowHandle=%p",
1347 ::GetFocus(), aWindow
->GetWindowHandle()));
1350 if (pCharPosition
->dwCharPos
> len
) {
1351 MOZ_LOG(gIMELog
, LogLevel::Error
,
1352 ("IMMHandler::HandleQueryCharPosition, FAILED, "
1353 "pCharPosition->dwCharPos=%ld, len=%u",
1354 pCharPosition
->dwCharPos
, len
));
1358 LayoutDeviceIntRect r
;
1360 GetCharacterRectOfSelectedTextAt(aWindow
, pCharPosition
->dwCharPos
, r
);
1361 NS_ENSURE_TRUE(ret
, false);
1363 LayoutDeviceIntRect screenRect
;
1364 // We always need top level window that is owner window of the popup window
1365 // even if the content of the popup window has focus.
1366 ResolveIMECaretPos(aWindow
->GetTopLevelWindow(false), r
, nullptr, screenRect
);
1368 // XXX This might need to check writing mode. However, MSDN doesn't explain
1369 // how to set the values in vertical writing mode. Additionally, IME
1370 // doesn't work well with top-left of the character (this is explicitly
1371 // documented) and its horizontal width. So, it might be better to set
1372 // top-right corner of the character and horizontal width, but we're not
1373 // sure if it doesn't cause any problems with a lot of IMEs...
1374 pCharPosition
->pt
.x
= screenRect
.X();
1375 pCharPosition
->pt
.y
= screenRect
.Y();
1377 pCharPosition
->cLineHeight
= r
.Height();
1379 WidgetQueryContentEvent
queryEditorRectEvent(true, eQueryEditorRect
, aWindow
);
1380 aWindow
->InitEvent(queryEditorRectEvent
);
1381 DispatchEvent(aWindow
, queryEditorRectEvent
);
1382 if (NS_WARN_IF(queryEditorRectEvent
.Failed())) {
1383 MOZ_LOG(gIMELog
, LogLevel::Error
,
1384 (" IMMHandler::HandleQueryCharPosition, eQueryEditorRect failed"));
1385 ::GetWindowRect(aWindow
->GetWindowHandle(), &pCharPosition
->rcDocument
);
1387 LayoutDeviceIntRect editorRectInWindow
= queryEditorRectEvent
.mReply
->mRect
;
1388 nsWindow
* window
= !!queryEditorRectEvent
.mReply
->mFocusedWidget
1389 ? static_cast<nsWindow
*>(
1390 queryEditorRectEvent
.mReply
->mFocusedWidget
)
1392 LayoutDeviceIntRect editorRectInScreen
;
1393 ResolveIMECaretPos(window
, editorRectInWindow
, nullptr, editorRectInScreen
);
1394 ::SetRect(&pCharPosition
->rcDocument
, editorRectInScreen
.X(),
1395 editorRectInScreen
.Y(), editorRectInScreen
.XMost(),
1396 editorRectInScreen
.YMost());
1402 gIMELog
, LogLevel::Info
,
1403 ("IMMHandler::HandleQueryCharPosition, SUCCEEDED, pCharPosition={ "
1404 "pt={ x=%ld, y=%ld }, cLineHeight=%d, rcDocument={ left=%ld, top=%ld, "
1405 "right=%ld, bottom=%ld } }",
1406 pCharPosition
->pt
.x
, pCharPosition
->pt
.y
, pCharPosition
->cLineHeight
,
1407 pCharPosition
->rcDocument
.left
, pCharPosition
->rcDocument
.top
,
1408 pCharPosition
->rcDocument
.right
, pCharPosition
->rcDocument
.bottom
));
1412 bool IMMHandler::HandleDocumentFeed(nsWindow
* aWindow
, LPARAM lParam
,
1415 RECONVERTSTRING
* pReconv
= reinterpret_cast<RECONVERTSTRING
*>(lParam
);
1417 LayoutDeviceIntPoint
point(0, 0);
1419 bool hasCompositionString
=
1420 mIsComposing
&& ShouldDrawCompositionStringOurselves();
1422 int32_t targetOffset
, targetLength
;
1423 if (!hasCompositionString
) {
1424 const Maybe
<ContentSelection
>& contentSelection
=
1425 GetContentSelectionWithQueryIfNothing(aWindow
);
1426 if (contentSelection
.isNothing()) {
1427 MOZ_LOG(gIMELog
, LogLevel::Error
,
1428 ("IMMHandler::HandleDocumentFeed, FAILED, due to "
1429 "Selection::GetContentSelectionWithQueryIfNothing() failure"));
1432 if (contentSelection
->HasRange()) {
1433 targetOffset
= static_cast<int32_t>(
1434 contentSelection
->OffsetAndDataRef().StartOffset());
1436 static_cast<int32_t>(contentSelection
->OffsetAndDataRef().Length());
1438 // If there is no selection range, let's return all text in the editor.
1440 targetLength
= INT32_MAX
;
1443 targetOffset
= int32_t(mCompositionStart
);
1444 targetLength
= int32_t(mCompositionString
.Length());
1447 // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
1448 // we cannot support this message when the current offset is larger than
1450 if (targetOffset
< 0 || targetLength
< 0 || targetOffset
+ targetLength
< 0) {
1451 MOZ_LOG(gIMELog
, LogLevel::Error
,
1452 ("IMMHandler::HandleDocumentFeed, FAILED, "
1453 "due to the selection is out of range"));
1457 // Get all contents of the focused editor.
1458 WidgetQueryContentEvent
queryTextContentEvent(true, eQueryTextContent
,
1460 queryTextContentEvent
.InitForQueryTextContent(0, UINT32_MAX
);
1461 aWindow
->InitEvent(queryTextContentEvent
, &point
);
1462 DispatchEvent(aWindow
, queryTextContentEvent
);
1463 if (NS_WARN_IF(queryTextContentEvent
.Failed())) {
1464 MOZ_LOG(gIMELog
, LogLevel::Error
,
1465 ("IMMHandler::HandleDocumentFeed, FAILED, "
1466 "due to eQueryTextContent failure"));
1470 nsAutoString
str(queryTextContentEvent
.mReply
->DataRef());
1471 if (targetOffset
> static_cast<int32_t>(str
.Length())) {
1472 MOZ_LOG(gIMELog
, LogLevel::Error
,
1473 (" IMMHandler::HandleDocumentFeed, FAILED, "
1474 "due to the caret offset is invalid"));
1478 // Get the focused paragraph, we decide that it starts from the previous CRLF
1479 // (or start of the editor) to the next one (or the end of the editor).
1480 int32_t paragraphStart
= 0;
1481 if (targetOffset
> 0) {
1482 paragraphStart
= Substring(str
, 0, targetOffset
).RFind(u
"\n") + 1;
1484 int32_t paragraphEnd
= str
.Find(u
"\r", targetOffset
+ targetLength
);
1485 if (paragraphEnd
< 0) {
1486 paragraphEnd
= str
.Length();
1488 nsDependentSubstring
paragraph(str
, paragraphStart
,
1489 paragraphEnd
- paragraphStart
);
1491 uint32_t len
= paragraph
.Length();
1492 uint32_t needSize
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
1495 *oResult
= needSize
;
1496 MOZ_LOG(gIMELog
, LogLevel::Info
,
1497 ("IMMHandler::HandleDocumentFeed, succeeded, result=%" PRIdLPTR
,
1502 if (pReconv
->dwSize
< needSize
) {
1503 MOZ_LOG(gIMELog
, LogLevel::Error
,
1504 ("IMMHandler::HandleDocumentFeed, FAILED, "
1505 "pReconv->dwSize=%ld, needSize=%u",
1506 pReconv
->dwSize
, needSize
));
1510 // Fill reconvert struct
1511 pReconv
->dwVersion
= 0;
1512 pReconv
->dwStrLen
= len
;
1513 pReconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
1514 if (hasCompositionString
) {
1515 pReconv
->dwCompStrLen
= targetLength
;
1516 pReconv
->dwCompStrOffset
= (targetOffset
- paragraphStart
) * sizeof(WCHAR
);
1517 // Set composition target clause information
1518 uint32_t offset
, length
;
1519 if (!GetTargetClauseRange(&offset
, &length
)) {
1520 MOZ_LOG(gIMELog
, LogLevel::Error
,
1521 ("IMMHandler::HandleDocumentFeed, FAILED, "
1522 "due to IMMHandler::GetTargetClauseRange() failure"));
1525 pReconv
->dwTargetStrLen
= length
;
1526 pReconv
->dwTargetStrOffset
= (offset
- paragraphStart
) * sizeof(WCHAR
);
1528 pReconv
->dwTargetStrLen
= targetLength
;
1529 pReconv
->dwTargetStrOffset
=
1530 (targetOffset
- paragraphStart
) * sizeof(WCHAR
);
1531 // There is no composition string, so, the length is zero but we should
1532 // set the cursor offset to the composition str offset.
1533 pReconv
->dwCompStrLen
= 0;
1534 pReconv
->dwCompStrOffset
= pReconv
->dwTargetStrOffset
;
1537 *oResult
= needSize
;
1538 ::CopyMemory(reinterpret_cast<LPVOID
>(lParam
+ sizeof(RECONVERTSTRING
)),
1539 paragraph
.BeginReading(), len
* sizeof(WCHAR
));
1541 MOZ_LOG(gIMELog
, LogLevel::Info
,
1542 ("IMMHandler::HandleDocumentFeed, SUCCEEDED, pReconv=%s, "
1543 "result=%" PRIdLPTR
,
1544 GetReconvertStringLog(pReconv
).get(), *oResult
));
1549 bool IMMHandler::CommitCompositionOnPreviousWindow(nsWindow
* aWindow
) {
1550 if (!mComposingWindow
|| mComposingWindow
== aWindow
) {
1554 MOZ_LOG(gIMELog
, LogLevel::Info
,
1555 ("IMMHandler::CommitCompositionOnPreviousWindow, mIsComposing=%s",
1556 GetBoolName(mIsComposing
)));
1558 // If we have composition, we should dispatch composition events internally.
1560 IMEContext
context(mComposingWindow
);
1561 NS_ASSERTION(context
.IsValid(), "IME context must be valid");
1563 HandleEndComposition(mComposingWindow
);
1570 static TextRangeType
PlatformToNSAttr(uint8_t aAttr
) {
1572 case ATTR_INPUT_ERROR
:
1573 // case ATTR_FIXEDCONVERTED:
1575 return TextRangeType::eRawClause
;
1576 case ATTR_CONVERTED
:
1577 return TextRangeType::eConvertedClause
;
1578 case ATTR_TARGET_NOTCONVERTED
:
1579 return TextRangeType::eSelectedRawClause
;
1580 case ATTR_TARGET_CONVERTED
:
1581 return TextRangeType::eSelectedClause
;
1583 NS_ASSERTION(false, "unknown attribute");
1584 return TextRangeType::eCaret
;
1589 void IMMHandler::DispatchEvent(nsWindow
* aWindow
, WidgetGUIEvent
& aEvent
) {
1591 gIMELog
, LogLevel::Info
,
1592 ("IMMHandler::DispatchEvent(aWindow=0x%p, aEvent={ mMessage=%s }, "
1593 "aWindow->Destroyed()=%s",
1594 aWindow
, ToChar(aEvent
.mMessage
), GetBoolName(aWindow
->Destroyed())));
1596 if (aWindow
->Destroyed()) {
1600 aWindow
->DispatchWindowEvent(aEvent
);
1603 void IMMHandler::DispatchCompositionChangeEvent(nsWindow
* aWindow
,
1604 const IMEContext
& aContext
) {
1605 NS_ASSERTION(mIsComposing
, "conflict state");
1606 MOZ_LOG(gIMELog
, LogLevel::Info
,
1607 ("IMMHandler::DispatchCompositionChangeEvent"));
1609 // If we don't need to draw composition string ourselves, we don't need to
1610 // fire compositionchange event during composing.
1611 if (!ShouldDrawCompositionStringOurselves()) {
1612 // But we need to adjust composition window pos and native caret pos, here.
1613 SetIMERelatedWindowsPos(aWindow
, aContext
);
1617 RefPtr
<nsWindow
> kungFuDeathGrip(aWindow
);
1618 RefPtr
<TextEventDispatcher
> dispatcher
= GetTextEventDispatcherFor(aWindow
);
1619 nsresult rv
= dispatcher
->BeginNativeInputTransaction();
1620 if (NS_WARN_IF(NS_FAILED(rv
))) {
1621 MOZ_LOG(gIMELog
, LogLevel::Error
,
1622 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to "
1623 "TextEventDispatcher::BeginNativeInputTransaction() failure"));
1627 // NOTE: Calling SetIMERelatedWindowsPos() from this method will be failure
1628 // in e10s mode. compositionchange event will notify this of
1629 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED, then
1630 // SetIMERelatedWindowsPos() will be called.
1632 // XXX Sogou (Simplified Chinese IME) returns contradictory values:
1633 // The cursor position is actual cursor position. However, other values
1634 // (composition string and attributes) are empty.
1636 if (mCompositionString
.IsEmpty()) {
1637 // Don't append clause information if composition string is empty.
1638 } else if (mClauseArray
.IsEmpty()) {
1639 // Some IMEs don't return clause array information, then, we assume that
1640 // all characters in the composition string are in one clause.
1641 MOZ_LOG(gIMELog
, LogLevel::Info
,
1642 (" IMMHandler::DispatchCompositionChangeEvent, "
1643 "mClauseArray.Length()=0"));
1644 rv
= dispatcher
->SetPendingComposition(mCompositionString
, nullptr);
1645 if (NS_WARN_IF(NS_FAILED(rv
))) {
1646 MOZ_LOG(gIMELog
, LogLevel::Error
,
1647 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
1648 "TextEventDispatcher::SetPendingComposition() failure"));
1652 // iterate over the attributes
1653 rv
= dispatcher
->SetPendingCompositionString(mCompositionString
);
1654 if (NS_WARN_IF(NS_FAILED(rv
))) {
1655 MOZ_LOG(gIMELog
, LogLevel::Error
,
1656 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
1657 "TextEventDispatcher::SetPendingCompositionString() failure"));
1660 uint32_t lastOffset
= 0;
1661 for (uint32_t i
= 0; i
< mClauseArray
.Length() - 1; i
++) {
1662 uint32_t current
= mClauseArray
[i
+ 1];
1663 if (current
> mCompositionString
.Length()) {
1664 MOZ_LOG(gIMELog
, LogLevel::Info
,
1665 (" IMMHandler::DispatchCompositionChangeEvent, "
1666 "mClauseArray[%u]=%u. "
1667 "This is larger than mCompositionString.Length()=%zu",
1668 i
+ 1, current
, mCompositionString
.Length()));
1669 current
= int32_t(mCompositionString
.Length());
1672 uint32_t length
= current
- lastOffset
;
1673 if (NS_WARN_IF(lastOffset
>= mAttributeArray
.Length())) {
1674 MOZ_LOG(gIMELog
, LogLevel::Error
,
1675 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to "
1676 "invalid data of mClauseArray or mAttributeArray"));
1679 TextRangeType textRangeType
=
1680 PlatformToNSAttr(mAttributeArray
[lastOffset
]);
1681 rv
= dispatcher
->AppendClauseToPendingComposition(length
, textRangeType
);
1682 if (NS_WARN_IF(NS_FAILED(rv
))) {
1683 MOZ_LOG(gIMELog
, LogLevel::Error
,
1684 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
1685 "TextEventDispatcher::AppendClauseToPendingComposition() "
1690 lastOffset
= current
;
1692 MOZ_LOG(gIMELog
, LogLevel::Info
,
1693 (" IMMHandler::DispatchCompositionChangeEvent, index=%u, "
1694 "rangeType=%s, range length=%u",
1695 i
, ToChar(textRangeType
), length
));
1699 if (mCursorPosition
== NO_IME_CARET
) {
1700 MOZ_LOG(gIMELog
, LogLevel::Info
,
1701 (" IMMHandler::DispatchCompositionChangeEvent, no caret"));
1703 uint32_t cursor
= static_cast<uint32_t>(mCursorPosition
);
1704 if (cursor
> mCompositionString
.Length()) {
1705 MOZ_LOG(gIMELog
, LogLevel::Info
,
1706 (" IMMHandler::CreateTextRangeArray, mCursorPosition=%d. "
1707 "This is larger than mCompositionString.Length()=%zu",
1708 mCursorPosition
, mCompositionString
.Length()));
1709 cursor
= mCompositionString
.Length();
1712 // If caret is in the target clause, the target clause will be painted as
1713 // normal selection range. Since caret shouldn't be in selection range on
1714 // Windows, we shouldn't append caret range in such case.
1715 const TextRangeArray
* clauses
= dispatcher
->GetPendingCompositionClauses();
1716 const TextRange
* targetClause
=
1717 clauses
? clauses
->GetTargetClause() : nullptr;
1718 if (targetClause
&& cursor
>= targetClause
->mStartOffset
&&
1719 cursor
<= targetClause
->mEndOffset
) {
1720 // Forget the caret position specified by IME since Gecko's caret position
1721 // will be at the end of composition string.
1722 mCursorPosition
= NO_IME_CARET
;
1723 MOZ_LOG(gIMELog
, LogLevel::Info
,
1724 (" IMMHandler::CreateTextRangeArray, no caret due to it's in "
1725 "the target clause, now, mCursorPosition is NO_IME_CARET"));
1728 if (mCursorPosition
!= NO_IME_CARET
) {
1729 rv
= dispatcher
->SetCaretInPendingComposition(cursor
, 0);
1730 if (NS_WARN_IF(NS_FAILED(rv
))) {
1732 gIMELog
, LogLevel::Error
,
1733 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
1734 "TextEventDispatcher::SetCaretInPendingComposition() failure"));
1740 WidgetEventTime eventTime
= aWindow
->CurrentMessageWidgetEventTime();
1741 nsEventStatus status
;
1742 rv
= dispatcher
->FlushPendingComposition(status
, &eventTime
);
1743 if (NS_WARN_IF(NS_FAILED(rv
))) {
1744 MOZ_LOG(gIMELog
, LogLevel::Error
,
1745 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
1746 "TextEventDispatcher::FlushPendingComposition() failure"));
1751 void IMMHandler::GetCompositionString(const IMEContext
& aContext
, DWORD aIndex
,
1752 nsAString
& aCompositionString
) const {
1753 aCompositionString
.Truncate();
1755 // Retrieve the size of the required output buffer.
1756 long lRtn
= ::ImmGetCompositionStringW(aContext
.get(), aIndex
, nullptr, 0);
1757 if (lRtn
< 0 || !aCompositionString
.SetLength((lRtn
/ sizeof(WCHAR
)) + 1,
1758 mozilla::fallible
)) {
1759 MOZ_LOG(gIMELog
, LogLevel::Error
,
1760 ("IMMHandler::GetCompositionString, FAILED, due to OOM"));
1761 return; // Error or out of memory.
1764 // Actually retrieve the composition string information.
1765 lRtn
= ::ImmGetCompositionStringW(aContext
.get(), aIndex
,
1766 (LPVOID
)aCompositionString
.BeginWriting(),
1767 lRtn
+ sizeof(WCHAR
));
1768 aCompositionString
.SetLength(lRtn
/ sizeof(WCHAR
));
1771 gIMELog
, LogLevel::Info
,
1772 ("IMMHandler::GetCompositionString, succeeded, aCompositionString=\"%s\"",
1773 NS_ConvertUTF16toUTF8(aCompositionString
).get()));
1776 bool IMMHandler::GetTargetClauseRange(uint32_t* aOffset
, uint32_t* aLength
) {
1777 NS_ENSURE_TRUE(aOffset
, false);
1778 NS_ENSURE_TRUE(mIsComposing
, false);
1779 NS_ENSURE_TRUE(ShouldDrawCompositionStringOurselves(), false);
1782 *aOffset
= mCompositionStart
;
1783 for (uint32_t i
= 0; i
< mAttributeArray
.Length(); i
++) {
1784 if (mAttributeArray
[i
] == ATTR_TARGET_NOTCONVERTED
||
1785 mAttributeArray
[i
] == ATTR_TARGET_CONVERTED
) {
1786 *aOffset
= mCompositionStart
+ i
;
1797 // The all composition string is targetted when there is no ATTR_TARGET_*
1798 // clause. E.g., there is only ATTR_INPUT
1799 *aLength
= mCompositionString
.Length();
1803 uint32_t offsetInComposition
= *aOffset
- mCompositionStart
;
1804 *aLength
= mCompositionString
.Length() - offsetInComposition
;
1805 for (uint32_t i
= offsetInComposition
; i
< mAttributeArray
.Length(); i
++) {
1806 if (mAttributeArray
[i
] != ATTR_TARGET_NOTCONVERTED
&&
1807 mAttributeArray
[i
] != ATTR_TARGET_CONVERTED
) {
1808 *aLength
= i
- offsetInComposition
;
1815 bool IMMHandler::ConvertToANSIString(const nsString
& aStr
, UINT aCodePage
,
1816 nsACString
& aANSIStr
) {
1817 int len
= ::WideCharToMultiByte(aCodePage
, 0, (LPCWSTR
)aStr
.get(),
1818 aStr
.Length(), nullptr, 0, nullptr, nullptr);
1819 NS_ENSURE_TRUE(len
>= 0, false);
1821 if (!aANSIStr
.SetLength(len
, mozilla::fallible
)) {
1822 MOZ_LOG(gIMELog
, LogLevel::Error
,
1823 ("IMMHandler::ConvertToANSIString, FAILED, due to OOM"));
1826 ::WideCharToMultiByte(aCodePage
, 0, (LPCWSTR
)aStr
.get(), aStr
.Length(),
1827 (LPSTR
)aANSIStr
.BeginWriting(), len
, nullptr, nullptr);
1831 bool IMMHandler::GetCharacterRectOfSelectedTextAt(
1832 nsWindow
* aWindow
, uint32_t aOffset
, LayoutDeviceIntRect
& aCharRect
,
1833 WritingMode
* aWritingMode
) {
1834 LayoutDeviceIntPoint
point(0, 0);
1836 const Maybe
<ContentSelection
>& contentSelection
=
1837 GetContentSelectionWithQueryIfNothing(aWindow
);
1838 if (contentSelection
.isNothing()) {
1839 MOZ_LOG(gIMELog
, LogLevel::Error
,
1840 ("IMMHandler::GetCharacterRectOfSelectedTextAt, FAILED, due to "
1841 "Selection::GetContentSelectionWithQueryIfNothing() failure"));
1845 // If there is neither a selection range nor composition string, cannot return
1846 // character rect, of course.
1847 if (!contentSelection
->HasRange() && !mIsComposing
) {
1848 MOZ_LOG(gIMELog
, LogLevel::Warning
,
1849 ("IMMHandler::GetCharacterRectOfSelectedTextAt, FAILED, due to "
1850 "there is neither a selection range nor composition string"));
1854 // If the offset is larger than the end of composition string or selected
1855 // string, we should return false since such case must be a bug of the caller
1856 // or the active IME. If it's an IME's bug, we need to set targetLength to
1858 const uint32_t targetLength
=
1859 mIsComposing
? mCompositionString
.Length()
1860 : contentSelection
->OffsetAndDataRef().Length();
1861 if (NS_WARN_IF(aOffset
> targetLength
)) {
1863 gIMELog
, LogLevel::Error
,
1864 ("IMMHandler::GetCharacterRectOfSelectedTextAt, FAILED, due to "
1865 "aOffset is too large (aOffset=%u, targetLength=%u, mIsComposing=%s)",
1866 aOffset
, targetLength
, GetBoolName(mIsComposing
)));
1870 // If there is caret, we might be able to use caret rect.
1871 uint32_t caretOffset
= UINT32_MAX
;
1872 // There is a caret only when the normal selection is collapsed.
1873 if (contentSelection
.isNothing() ||
1874 contentSelection
->OffsetAndDataRef().IsDataEmpty()) {
1876 // If it's composing, mCursorPosition is the offset to caret in
1877 // the composition string.
1878 if (mCursorPosition
!= NO_IME_CARET
) {
1879 MOZ_ASSERT(mCursorPosition
>= 0);
1880 caretOffset
= mCursorPosition
;
1881 } else if (!ShouldDrawCompositionStringOurselves() ||
1882 mCompositionString
.IsEmpty()) {
1883 // Otherwise, if there is no composition string, we should assume that
1884 // there is a caret at the start of composition string.
1888 // If there is no composition, the selection offset is the caret offset.
1893 // If there is a caret and retrieving offset is same as the caret offset,
1894 // we should use the caret rect.
1895 if (aOffset
!= caretOffset
) {
1896 WidgetQueryContentEvent
queryTextRectEvent(true, eQueryTextRect
, aWindow
);
1897 WidgetQueryContentEvent::Options options
;
1898 options
.mRelativeToInsertionPoint
= true;
1899 queryTextRectEvent
.InitForQueryTextRect(aOffset
, 1, options
);
1900 aWindow
->InitEvent(queryTextRectEvent
, &point
);
1901 DispatchEvent(aWindow
, queryTextRectEvent
);
1902 if (queryTextRectEvent
.Succeeded()) {
1903 aCharRect
= queryTextRectEvent
.mReply
->mRect
;
1905 *aWritingMode
= queryTextRectEvent
.mReply
->WritingModeRef();
1908 gIMELog
, LogLevel::Debug
,
1909 ("IMMHandler::GetCharacterRectOfSelectedTextAt, Succeeded, "
1910 "aOffset=%u, aCharRect={ x: %d, y: %d, width: %d, height: %d }, "
1911 "queryTextRectEvent={ mReply=%s }",
1912 aOffset
, aCharRect
.X(), aCharRect
.Y(), aCharRect
.Width(),
1913 aCharRect
.Height(), ToString(queryTextRectEvent
.mReply
).c_str()));
1918 return GetCaretRect(aWindow
, aCharRect
, aWritingMode
);
1921 bool IMMHandler::GetCaretRect(nsWindow
* aWindow
,
1922 LayoutDeviceIntRect
& aCaretRect
,
1923 WritingMode
* aWritingMode
) {
1924 LayoutDeviceIntPoint
point(0, 0);
1926 WidgetQueryContentEvent
queryCaretRectEvent(true, eQueryCaretRect
, aWindow
);
1927 WidgetQueryContentEvent::Options options
;
1928 options
.mRelativeToInsertionPoint
= true;
1929 queryCaretRectEvent
.InitForQueryCaretRect(0, options
);
1930 aWindow
->InitEvent(queryCaretRectEvent
, &point
);
1931 DispatchEvent(aWindow
, queryCaretRectEvent
);
1932 if (queryCaretRectEvent
.Failed()) {
1934 gIMELog
, LogLevel::Info
,
1935 ("IMMHandler::GetCaretRect, FAILED, due to eQueryCaretRect failure"));
1938 aCaretRect
= queryCaretRectEvent
.mReply
->mRect
;
1940 *aWritingMode
= queryCaretRectEvent
.mReply
->WritingModeRef();
1942 MOZ_LOG(gIMELog
, LogLevel::Info
,
1943 ("IMMHandler::GetCaretRect, SUCCEEDED, "
1944 "aCaretRect={ x: %d, y: %d, width: %d, height: %d }, "
1945 "queryCaretRectEvent={ mReply=%s }",
1946 aCaretRect
.X(), aCaretRect
.Y(), aCaretRect
.Width(),
1947 aCaretRect
.Height(), ToString(queryCaretRectEvent
.mReply
).c_str()));
1951 bool IMMHandler::SetIMERelatedWindowsPos(nsWindow
* aWindow
,
1952 const IMEContext
& aContext
) {
1953 // Get first character rect of current a normal selected text or a composing
1955 WritingMode writingMode
;
1956 LayoutDeviceIntRect firstSelectedCharRectRelativeToWindow
;
1957 bool ret
= GetCharacterRectOfSelectedTextAt(
1958 aWindow
, 0, firstSelectedCharRectRelativeToWindow
, &writingMode
);
1959 NS_ENSURE_TRUE(ret
, false);
1960 nsWindow
* toplevelWindow
= aWindow
->GetTopLevelWindow(false);
1961 LayoutDeviceIntRect firstSelectedCharRect
;
1962 ResolveIMECaretPos(toplevelWindow
, firstSelectedCharRectRelativeToWindow
,
1963 aWindow
, firstSelectedCharRect
);
1965 // Set native caret size/position to our caret. Some IMEs honor it. E.g.,
1966 // "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified
1967 // Chinese) on XP. But if a11y module is handling native caret, we shouldn't
1969 if (!IMEHandler::IsA11yHandlingNativeCaret()) {
1970 LayoutDeviceIntRect
caretRect(firstSelectedCharRect
),
1971 caretRectRelativeToWindow
;
1972 if (GetCaretRect(aWindow
, caretRectRelativeToWindow
)) {
1973 ResolveIMECaretPos(toplevelWindow
, caretRectRelativeToWindow
, aWindow
,
1976 NS_WARNING("failed to get caret rect");
1977 caretRect
.SetWidth(1);
1979 IMEHandler::CreateNativeCaret(aWindow
, caretRect
);
1982 if (ShouldDrawCompositionStringOurselves()) {
1983 MOZ_LOG(gIMELog
, LogLevel::Info
,
1984 ("IMMHandler::SetIMERelatedWindowsPos, Set candidate window"));
1986 // Get a rect of first character in current target in composition string.
1987 LayoutDeviceIntRect firstTargetCharRect
, lastTargetCharRect
;
1988 if (mIsComposing
&& !mCompositionString
.IsEmpty()) {
1989 // If there are no targetted selection, we should use it's first character
1991 uint32_t offset
, length
;
1992 if (!GetTargetClauseRange(&offset
, &length
)) {
1993 MOZ_LOG(gIMELog
, LogLevel::Error
,
1994 (" IMMHandler::SetIMERelatedWindowsPos, FAILED, due to "
1995 "GetTargetClauseRange() failure"));
1999 GetCharacterRectOfSelectedTextAt(aWindow
, offset
- mCompositionStart
,
2000 firstTargetCharRect
, &writingMode
);
2001 NS_ENSURE_TRUE(ret
, false);
2003 ret
= GetCharacterRectOfSelectedTextAt(
2004 aWindow
, offset
+ length
- 1 - mCompositionStart
,
2005 lastTargetCharRect
);
2006 NS_ENSURE_TRUE(ret
, false);
2008 lastTargetCharRect
= firstTargetCharRect
;
2011 // If there are no composition string, we should use a first character
2013 ret
= GetCharacterRectOfSelectedTextAt(aWindow
, 0, firstTargetCharRect
,
2015 NS_ENSURE_TRUE(ret
, false);
2016 lastTargetCharRect
= firstTargetCharRect
;
2018 ResolveIMECaretPos(toplevelWindow
, firstTargetCharRect
, aWindow
,
2019 firstTargetCharRect
);
2020 ResolveIMECaretPos(toplevelWindow
, lastTargetCharRect
, aWindow
,
2021 lastTargetCharRect
);
2022 LayoutDeviceIntRect targetClauseRect
;
2023 targetClauseRect
.UnionRect(firstTargetCharRect
, lastTargetCharRect
);
2025 // Move the candidate window to proper position from the target clause as
2027 CANDIDATEFORM candForm
;
2028 candForm
.dwIndex
= 0;
2029 if (!writingMode
.IsVertical() || IsVerticalWritingSupported()) {
2030 candForm
.dwStyle
= CFS_EXCLUDE
;
2031 // Candidate window shouldn't overlap the target clause in any writing
2033 candForm
.rcArea
.left
= targetClauseRect
.X();
2034 candForm
.rcArea
.right
= targetClauseRect
.XMost();
2035 candForm
.rcArea
.top
= targetClauseRect
.Y();
2036 candForm
.rcArea
.bottom
= targetClauseRect
.YMost();
2037 if (!writingMode
.IsVertical()) {
2038 // In horizontal layout, current point of interest should be top-left
2039 // of the first character.
2040 candForm
.ptCurrentPos
.x
= firstTargetCharRect
.X();
2041 candForm
.ptCurrentPos
.y
= firstTargetCharRect
.Y();
2042 } else if (writingMode
.IsVerticalRL()) {
2043 // In vertical layout (RL), candidate window should be positioned right
2044 // side of target clause. However, we don't set vertical writing font
2045 // to the IME. Therefore, the candidate window may be positioned
2046 // bottom-left of target clause rect with these information.
2047 candForm
.ptCurrentPos
.x
= targetClauseRect
.X();
2048 candForm
.ptCurrentPos
.y
= targetClauseRect
.Y();
2050 MOZ_ASSERT(writingMode
.IsVerticalLR(), "Did we miss some causes?");
2051 // In vertical layout (LR), candidate window should be poisitioned left
2052 // side of target clause. Although, we don't set vertical writing font
2053 // to the IME, the candidate window may be positioned bottom-right of
2054 // the target clause rect with these information.
2055 candForm
.ptCurrentPos
.x
= targetClauseRect
.XMost();
2056 candForm
.ptCurrentPos
.y
= targetClauseRect
.Y();
2059 // If vertical writing is not supported by IME, let's set candidate
2060 // window position to the bottom-left of the target clause because
2061 // the position must be the safest position to prevent the candidate
2062 // window to overlap with the target clause.
2063 candForm
.dwStyle
= CFS_CANDIDATEPOS
;
2064 candForm
.ptCurrentPos
.x
= targetClauseRect
.X();
2065 candForm
.ptCurrentPos
.y
= targetClauseRect
.YMost();
2067 MOZ_LOG(gIMELog
, LogLevel::Info
,
2068 (" IMMHandler::SetIMERelatedWindowsPos, Calling "
2069 "ImmSetCandidateWindow()... ptCurrentPos={ x=%ld, y=%ld }, "
2070 "rcArea={ left=%ld, top=%ld, right=%ld, bottom=%ld }, "
2072 candForm
.ptCurrentPos
.x
, candForm
.ptCurrentPos
.y
,
2073 candForm
.rcArea
.left
, candForm
.rcArea
.top
, candForm
.rcArea
.right
,
2074 candForm
.rcArea
.bottom
, ToString(writingMode
).c_str()));
2075 ::ImmSetCandidateWindow(aContext
.get(), &candForm
);
2077 MOZ_LOG(gIMELog
, LogLevel::Info
,
2078 ("IMMHandler::SetIMERelatedWindowsPos, Set composition window"));
2080 // Move the composition window to caret position (if selected some
2081 // characters, we should use first character rect of them).
2082 // And in this mode, IME adjusts the candidate window position
2083 // automatically. So, we don't need to set it.
2084 COMPOSITIONFORM compForm
;
2085 compForm
.dwStyle
= CFS_POINT
;
2086 compForm
.ptCurrentPos
.x
= !writingMode
.IsVerticalLR()
2087 ? firstSelectedCharRect
.X()
2088 : firstSelectedCharRect
.XMost();
2089 compForm
.ptCurrentPos
.y
= firstSelectedCharRect
.Y();
2090 ::ImmSetCompositionWindow(aContext
.get(), &compForm
);
2096 void IMMHandler::ResolveIMECaretPos(nsIWidget
* aReferenceWidget
,
2097 LayoutDeviceIntRect
& aCursorRect
,
2098 nsIWidget
* aNewOriginWidget
,
2099 LayoutDeviceIntRect
& aOutRect
) {
2100 aOutRect
= aCursorRect
;
2102 if (aReferenceWidget
== aNewOriginWidget
) return;
2104 if (aReferenceWidget
)
2105 aOutRect
.MoveBy(aReferenceWidget
->WidgetToScreenOffset());
2107 if (aNewOriginWidget
)
2108 aOutRect
.MoveBy(-aNewOriginWidget
->WidgetToScreenOffset());
2111 static void SetHorizontalFontToLogFont(const nsAString
& aFontFace
,
2112 LOGFONTW
& aLogFont
) {
2113 aLogFont
.lfEscapement
= aLogFont
.lfOrientation
= 0;
2114 if (NS_WARN_IF(aFontFace
.Length() > LF_FACESIZE
- 1)) {
2115 memcpy(aLogFont
.lfFaceName
, L
"System", sizeof(L
"System"));
2118 memcpy(aLogFont
.lfFaceName
, aFontFace
.BeginReading(),
2119 aFontFace
.Length() * sizeof(wchar_t));
2120 aLogFont
.lfFaceName
[aFontFace
.Length()] = 0;
2123 static void SetVerticalFontToLogFont(const nsAString
& aFontFace
,
2124 LOGFONTW
& aLogFont
) {
2125 aLogFont
.lfEscapement
= aLogFont
.lfOrientation
= 2700;
2126 if (NS_WARN_IF(aFontFace
.Length() > LF_FACESIZE
- 2)) {
2127 memcpy(aLogFont
.lfFaceName
, L
"@System", sizeof(L
"@System"));
2130 aLogFont
.lfFaceName
[0] = '@';
2131 memcpy(&aLogFont
.lfFaceName
[1], aFontFace
.BeginReading(),
2132 aFontFace
.Length() * sizeof(wchar_t));
2133 aLogFont
.lfFaceName
[aFontFace
.Length() + 1] = 0;
2136 void IMMHandler::AdjustCompositionFont(nsWindow
* aWindow
,
2137 const IMEContext
& aContext
,
2138 const WritingMode
& aWritingMode
,
2139 bool aForceUpdate
) {
2140 // An instance of IMMHandler is destroyed when active IME is changed.
2141 // Therefore, we need to store the information which are set to the IM
2142 // context to static variables since IM context is never recreated.
2143 static bool sCompositionFontsInitialized
= false;
2144 static nsString sCompositionFont
;
2145 static bool sCompositionFontPrefDone
= false;
2146 if (!sCompositionFontPrefDone
) {
2147 sCompositionFontPrefDone
= true;
2148 Preferences::GetString("intl.imm.composition_font", sCompositionFont
);
2151 // If composition font is customized by pref, we need to modify the
2152 // composition font of the IME context at first time even if the writing mode
2154 bool setCompositionFontForcibly
=
2156 (!sCompositionFontsInitialized
&& !sCompositionFont
.IsEmpty());
2158 static WritingMode sCurrentWritingMode
;
2159 static nsString sCurrentIMEName
;
2160 if (!setCompositionFontForcibly
&&
2161 sWritingModeOfCompositionFont
== aWritingMode
&&
2162 sCurrentIMEName
== sIMEName
) {
2163 // Nothing to do if writing mode isn't being changed.
2167 // Decide composition fonts for both horizontal writing mode and vertical
2168 // writing mode. If the font isn't specified by the pref, use default
2169 // font which is already set to the IM context. And also in vertical writing
2170 // mode, insert '@' to the start of the font.
2171 if (!sCompositionFontsInitialized
) {
2172 sCompositionFontsInitialized
= true;
2173 // sCompositionFontH must not start with '@' and its length is less than
2174 // LF_FACESIZE since it needs to end with null terminating character.
2175 if (sCompositionFont
.IsEmpty() ||
2176 sCompositionFont
.Length() > LF_FACESIZE
- 1 ||
2177 sCompositionFont
[0] == '@') {
2178 LOGFONTW defaultLogFont
;
2180 !::ImmGetCompositionFont(aContext
.get(), &defaultLogFont
))) {
2182 gIMELog
, LogLevel::Error
,
2183 (" IMMHandler::AdjustCompositionFont, ::ImmGetCompositionFont() "
2185 sCompositionFont
.AssignLiteral("System");
2187 // The font face is typically, "System".
2188 sCompositionFont
.Assign(defaultLogFont
.lfFaceName
);
2192 MOZ_LOG(gIMELog
, LogLevel::Info
,
2193 (" IMMHandler::AdjustCompositionFont, sCompositionFont=\"%s\" is "
2195 NS_ConvertUTF16toUTF8(sCompositionFont
).get()));
2198 static nsString sCompositionFontForJapanist2003
;
2199 if (IsJapanist2003Active() && sCompositionFontForJapanist2003
.IsEmpty()) {
2200 const char* kCompositionFontForJapanist2003
=
2201 "intl.imm.composition_font.japanist_2003";
2202 Preferences::GetString(kCompositionFontForJapanist2003
,
2203 sCompositionFontForJapanist2003
);
2204 // If the font name is not specified properly, let's use
2205 // "MS PGothic" instead.
2206 if (sCompositionFontForJapanist2003
.IsEmpty() ||
2207 sCompositionFontForJapanist2003
.Length() > LF_FACESIZE
- 2 ||
2208 sCompositionFontForJapanist2003
[0] == '@') {
2209 sCompositionFontForJapanist2003
.AssignLiteral("MS PGothic");
2213 sWritingModeOfCompositionFont
= aWritingMode
;
2214 sCurrentIMEName
= sIMEName
;
2217 memset(&logFont
, 0, sizeof(logFont
));
2218 if (!::ImmGetCompositionFont(aContext
.get(), &logFont
)) {
2219 MOZ_LOG(gIMELog
, LogLevel::Error
,
2220 (" IMMHandler::AdjustCompositionFont, ::ImmGetCompositionFont() "
2222 logFont
.lfFaceName
[0] = 0;
2224 // Need to reset some information which should be recomputed with new font.
2225 logFont
.lfWidth
= 0;
2226 logFont
.lfWeight
= FW_DONTCARE
;
2227 logFont
.lfOutPrecision
= OUT_DEFAULT_PRECIS
;
2228 logFont
.lfClipPrecision
= CLIP_DEFAULT_PRECIS
;
2229 logFont
.lfPitchAndFamily
= DEFAULT_PITCH
;
2231 if (aWritingMode
.IsVertical() && IsVerticalWritingSupported()) {
2232 SetVerticalFontToLogFont(IsJapanist2003Active()
2233 ? sCompositionFontForJapanist2003
2237 SetHorizontalFontToLogFont(IsJapanist2003Active()
2238 ? sCompositionFontForJapanist2003
2242 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2243 (" IMMHandler::AdjustCompositionFont, calling "
2244 "::ImmSetCompositionFont(\"%s\")",
2245 NS_ConvertUTF16toUTF8(nsDependentString(logFont
.lfFaceName
)).get()));
2246 ::ImmSetCompositionFontW(aContext
.get(), &logFont
);
2250 nsresult
IMMHandler::OnMouseButtonEvent(
2251 nsWindow
* aWindow
, const IMENotification
& aIMENotification
) {
2252 // We don't need to create the instance of the handler here.
2257 if (!sWM_MSIME_MOUSE
|| !IsComposingOnOurEditor() ||
2258 !ShouldDrawCompositionStringOurselves()) {
2262 // We need to handle only mousedown event.
2263 if (aIMENotification
.mMouseButtonEventData
.mEventMessage
!= eMouseDown
) {
2267 // If the character under the cursor is not in the composition string,
2268 // we don't need to notify IME of it.
2269 uint32_t compositionStart
= gIMMHandler
->mCompositionStart
;
2270 uint32_t compositionEnd
=
2271 compositionStart
+ gIMMHandler
->mCompositionString
.Length();
2272 if (aIMENotification
.mMouseButtonEventData
.mOffset
< compositionStart
||
2273 aIMENotification
.mMouseButtonEventData
.mOffset
>= compositionEnd
) {
2278 switch (aIMENotification
.mMouseButtonEventData
.mButton
) {
2279 case MouseButton::ePrimary
:
2280 button
= IMEMOUSE_LDOWN
;
2282 case MouseButton::eMiddle
:
2283 button
= IMEMOUSE_MDOWN
;
2285 case MouseButton::eSecondary
:
2286 button
= IMEMOUSE_RDOWN
;
2292 // calcurate positioning and offset
2293 // char : JCH1|JCH2|JCH3
2294 // offset: 0011 1122 2233
2295 // positioning: 2301 2301 2301
2296 LayoutDeviceIntPoint cursorPos
=
2297 aIMENotification
.mMouseButtonEventData
.mCursorPos
;
2298 LayoutDeviceIntRect charRect
=
2299 aIMENotification
.mMouseButtonEventData
.mCharRect
;
2300 int32_t cursorXInChar
= cursorPos
.x
- charRect
.X();
2301 // The event might hit to zero-width character, see bug 694913.
2302 // The reason might be:
2303 // * There are some zero-width characters are actually.
2304 // * font-size is specified zero.
2305 // But nobody reproduced this bug actually...
2306 // We should assume that user clicked on right most of the zero-width
2307 // character in such case.
2308 int positioning
= 1;
2309 if (charRect
.Width() > 0) {
2310 positioning
= cursorXInChar
* 4 / charRect
.Width();
2311 positioning
= (positioning
+ 2) % 4;
2315 aIMENotification
.mMouseButtonEventData
.mOffset
- compositionStart
;
2316 if (positioning
< 2) {
2320 MOZ_LOG(gIMELog
, LogLevel::Info
,
2321 ("IMMHandler::OnMouseButtonEvent, x,y=%d,%d, offset=%d, "
2323 cursorPos
.x
.value
, cursorPos
.y
.value
, offset
, positioning
));
2325 // send MS_MSIME_MOUSE message to default IME window.
2326 HWND imeWnd
= ::ImmGetDefaultIMEWnd(aWindow
->GetWindowHandle());
2327 IMEContext
context(aWindow
);
2328 if (::SendMessageW(imeWnd
, sWM_MSIME_MOUSE
,
2329 MAKELONG(MAKEWORD(button
, positioning
), offset
),
2330 (LPARAM
)context
.get()) == 1) {
2331 return NS_SUCCESS_EVENT_CONSUMED
;
2337 bool IMMHandler::OnKeyDownEvent(nsWindow
* aWindow
, WPARAM wParam
, LPARAM lParam
,
2338 MSGResult
& aResult
) {
2340 gIMELog
, LogLevel::Info
,
2341 ("IMMHandler::OnKeyDownEvent, hWnd=%p, wParam=%08zx, lParam=%08" PRIxLPTR
,
2342 aWindow
->GetWindowHandle(), wParam
, lParam
));
2343 aResult
.mConsumed
= false;
2355 // If IME didn't process the key message (the virtual key code wasn't
2356 // converted to VK_PROCESSKEY), and the virtual key code event causes
2357 // moving caret or editing text with keeping composing state, we should
2358 // cancel the composition here because we cannot support moving
2359 // composition string with DOM events (IE also cancels the composition
2360 // in same cases). Then, this event will be dispatched.
2361 if (IsComposingOnOurEditor()) {
2362 // NOTE: We don't need to cancel the composition on another window.
2363 CancelComposition(aWindow
, false);
2371 Maybe
<ContentSelection
> IMMHandler::QueryContentSelection(nsWindow
* aWindow
) {
2372 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
2374 LayoutDeviceIntPoint
point(0, 0);
2375 aWindow
->InitEvent(querySelectedTextEvent
, &point
);
2376 DispatchEvent(aWindow
, querySelectedTextEvent
);
2377 if (NS_WARN_IF(querySelectedTextEvent
.Failed())) {
2378 MOZ_LOG(gIMELog
, LogLevel::Error
,
2379 (" IMMHandler::Selection::Init, FAILED, due to eQuerySelectedText "
2383 // If the window is destroyed during querying selected text, we shouldn't
2385 if (aWindow
->Destroyed()) {
2387 gIMELog
, LogLevel::Error
,
2388 (" IMMHandler::Selection::Init, FAILED, due to the widget destroyed"));
2392 ContentSelection
contentSelection(querySelectedTextEvent
);
2394 MOZ_LOG(gIMELog
, LogLevel::Info
,
2395 ("IMMHandler::Selection::Init, querySelectedTextEvent={ mReply=%s }",
2396 ToString(querySelectedTextEvent
.mReply
).c_str()));
2398 if (contentSelection
.HasRange() &&
2399 !contentSelection
.OffsetAndDataRef().IsValid()) {
2400 MOZ_LOG(gIMELog
, LogLevel::Error
,
2401 (" IMMHandler::Selection::Init, FAILED, due to invalid range"));
2404 return Some(contentSelection
);
2407 } // namespace widget
2408 } // namespace mozilla