Bug 1758713 [wpt PR 33128] - Clarify the status of the CSS build system, a=testonly
[gecko.git] / widget / windows / IMMHandler.cpp
blobb1d2c9400a54a516762b657238f4fb559ee8af79
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"
10 #include "nsWindow.h"
11 #include "nsWindowDefs.h"
12 #include "WinIMEHandler.h"
13 #include "WinUtils.h"
14 #include "KeyboardLayout.h"
15 #include <algorithm>
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
25 #endif
27 //-------------------------------------------------------------------------
29 // from
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
46 // big file.
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 {
59 public:
60 explicit GetIMEGeneralPropertyName(DWORD aFlags) {
61 if (!aFlags) {
62 AppendLiteral("no flags");
63 return;
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 {
93 public:
94 explicit GetIMEUIPropertyName(DWORD aFlags) {
95 if (!aFlags) {
96 AppendLiteral("no flags");
97 return;
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 {
115 public:
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() {}
145 namespace mozilla {
146 namespace widget {
148 static IMMHandler* gIMMHandler = nullptr;
150 /******************************************************************************
151 * IMEContext
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) {
161 Clear();
162 mWnd = aWnd;
163 mIMC = ::ImmGetContext(mWnd);
166 void IMEContext::Init(nsWindow* aWindowBase) {
167 Init(aWindowBase->GetWindowHandle());
170 void IMEContext::Clear() {
171 if (mWnd && mIMC) {
172 ::ImmReleaseContext(mWnd, mIMC);
174 mWnd = nullptr;
175 mIMC = nullptr;
178 /******************************************************************************
179 * IMMHandler
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
211 // static
212 bool IMMHandler::IsActiveIMEInBlockList() {
213 if (sIMEName.IsEmpty()) {
214 return false;
216 #ifdef _WIN64
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
226 // crash reports.
227 if (IsWin8OrLater() &&
228 (IsATOK2006Active() || IsATOK2007Active() || IsATOK2008Active() ||
229 IsATOK2009Active() || IsATOK2010Active())) {
230 return true;
232 #endif // #ifdef _WIN64
233 return false;
236 // static
237 void IMMHandler::EnsureHandlerInstance() {
238 if (!gIMMHandler) {
239 gIMMHandler = new IMMHandler();
243 // static
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));
253 // static
254 void IMMHandler::Terminate() {
255 if (!gIMMHandler) return;
256 delete gIMMHandler;
257 gIMMHandler = nullptr;
260 // static
261 bool IMMHandler::IsComposingOnOurEditor() {
262 return gIMMHandler && gIMMHandler->mIsComposing;
265 // static
266 bool IMMHandler::IsComposingWindow(nsWindow* aWindow) {
267 return gIMMHandler && gIMMHandler->mComposingWindow == aWindow;
270 // static
271 bool IMMHandler::IsTopLevelWindowOfComposition(nsWindow* aWindow) {
272 if (!gIMMHandler || !gIMMHandler->mComposingWindow) {
273 return false;
275 HWND wnd = gIMMHandler->mComposingWindow->GetWindowHandle();
276 return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle();
279 // static
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
283 // ourselves.
284 return !(sIMEProperty & IME_PROP_SPECIAL_UI) &&
285 (sIMEProperty & IME_PROP_AT_CARET);
288 // static
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) {
293 return false;
295 // Google Japanese Input doesn't support vertical writing mode. We should
296 // return false if it's active IME.
297 if (IsGoogleJapaneseInputActive()) {
298 return false;
300 return !!(sIMEUIProperty & (UI_CAP_2700 | UI_CAP_ROT90 | UI_CAP_ROTANY));
303 // static
304 void IMMHandler::InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout) {
305 UINT IMENameLength = ::ImmGetDescriptionW(aKeyboardLayout, nullptr, 0);
306 if (IMENameLength) {
307 // Add room for the terminating null character
308 sIMEName.SetLength(++IMENameLength);
309 IMENameLength =
310 ::ImmGetDescriptionW(aKeyboardLayout, sIMEName.get(), IMENameLength);
311 // Adjust the length to ignore the terminating null character
312 sIMEName.SetLength(IMENameLength);
313 } else {
314 sIMEName.Truncate();
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
326 // pref.
327 if (sCodePage == 932 && sIMEName.IsEmpty()) {
328 Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as",
329 sIMEName);
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.
335 if (aWindow) {
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()));
347 // static
348 UINT IMMHandler::GetKeyboardCodePage() { return sCodePage; }
350 // static
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() {
374 if (mIsComposing) {
375 MOZ_LOG(
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);
385 return NS_OK;
388 nsresult IMMHandler::EnsureAttributeArray(int32_t aCount) {
389 NS_ENSURE_ARG_MIN(aCount, 0);
390 mAttributeArray.SetCapacity(aCount + 64);
391 return NS_OK;
394 // static
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)"
404 : ""));
405 if (!aForce && !IsComposingWindow(aWindow)) {
406 return;
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);
420 if (associated) {
421 context.Disassociate();
425 // static
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)"
435 : ""));
436 if (!aForce && !IsComposingWindow(aWindow)) {
437 return;
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);
450 if (associated) {
451 context.Disassociate();
455 // static
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())));
464 if (!aFocus) {
465 IMEHandler::MaybeDestroyNativeCaret();
466 if (IsComposingWindow(aWindow) && aWindow->Destroyed()) {
467 CancelComposition(aWindow);
470 if (gIMMHandler) {
471 gIMMHandler->mContentSelection.reset();
473 sHasFocus = aFocus;
476 // static
477 void IMMHandler::OnUpdateComposition(nsWindow* aWindow) {
478 if (!gIMMHandler) {
479 return;
482 IMEContext context(aWindow);
483 gIMMHandler->SetIMERelatedWindowsPos(aWindow, context);
486 // static
487 void IMMHandler::OnSelectionChange(nsWindow* aWindow,
488 const IMENotification& aIMENotification,
489 bool aIsIMMActive) {
490 if (!aIMENotification.mSelectionChangeData.mCausedByComposition &&
491 aIsIMMActive) {
492 MaybeAdjustCompositionFont(
493 aWindow, aIMENotification.mSelectionChangeData.GetWritingMode());
495 // MaybeAdjustCompositionFont() may create gIMMHandler. So, check it
496 // after a call of MaybeAdjustCompositionFont().
497 if (gIMMHandler) {
498 gIMMHandler->mContentSelection =
499 Some(ContentSelection(aIMENotification.mSelectionChangeData));
503 // static
504 void IMMHandler::MaybeAdjustCompositionFont(nsWindow* aWindow,
505 const WritingMode& aWritingMode,
506 bool aForceUpdate) {
507 switch (sCodePage) {
508 case 932: // Japanese Shift-JIS
509 case 936: // Simlified Chinese GBK
510 case 949: // Korean
511 case 950: // Traditional Chinese Big5
512 EnsureHandlerInstance();
513 break;
514 default:
515 // If there is no instance of nsIMM32Hander, we shouldn't waste footprint.
516 if (!gIMMHandler) {
517 return;
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,
525 aForceUpdate);
528 // static
529 bool IMMHandler::ProcessInputLangChangeMessage(nsWindow* aWindow, WPARAM wParam,
530 LPARAM lParam,
531 MSGResult& aResult) {
532 aResult.mResult = 0;
533 aResult.mConsumed = false;
534 // We don't need to create the instance of the handler here.
535 if (gIMMHandler) {
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.
541 Terminate();
542 // Don't return as "processed", the messages should be processed on nsWindow
543 // too.
544 return false;
547 // static
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.
555 aResult.mResult = 0;
556 switch (msg) {
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);
568 case WM_IME_CHAR:
569 return OnIMEChar(aWindow, wParam, lParam, aResult);
570 case WM_IME_NOTIFY:
571 return OnIMENotify(aWindow, wParam, lParam, aResult);
572 case WM_IME_REQUEST:
573 EnsureHandlerInstance();
574 return gIMMHandler->OnIMERequest(aWindow, wParam, lParam, aResult);
575 case WM_IME_SELECT:
576 return OnIMESelect(aWindow, wParam, lParam, aResult);
577 case WM_IME_SETCONTEXT:
578 return OnIMESetContext(aWindow, wParam, lParam, aResult);
579 case WM_KEYDOWN:
580 return OnKeyDownEvent(aWindow, wParam, lParam, aResult);
581 case WM_CHAR:
582 if (!gIMMHandler) {
583 return false;
585 return gIMMHandler->OnChar(aWindow, wParam, lParam, aResult);
586 default:
587 return false;
591 /****************************************************************************
592 * message handlers
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");
604 if (mIsComposing) {
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();
616 if (mIsComposing) {
617 NS_WARNING("Composition has been already started");
618 return true;
621 IMEContext context(aWindow);
622 HandleStartComposition(aWindow, context);
623 return true;
626 bool IMMHandler::OnIMEComposition(nsWindow* aWindow, WPARAM wParam,
627 LPARAM lParam, MSGResult& aResult) {
628 MOZ_LOG(
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, "
632 "GCS_CURSORPOS=%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);
640 return true;
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();
649 if (!mIsComposing) {
650 return true;
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.
656 MSG compositionMsg;
657 if (WinUtils::PeekMessage(&compositionMsg, aWindow->GetWindowHandle(),
658 WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION,
659 PM_NOREMOVE) &&
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..."));
665 return true;
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());
681 return true;
684 // static
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;
698 return true;
701 // static
702 bool IMMHandler::OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult) {
703 MOZ_LOG(gIMELog, LogLevel::Info,
704 ("IMMHandler::OnIMECompositionFull, hWnd=%08x",
705 aWindow->GetWindowHandle()));
707 // not implement yet
708 aResult.mConsumed = false;
709 return true;
712 // static
713 bool IMMHandler::OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
714 MSGResult& aResult) {
715 switch (wParam) {
716 case IMN_CHANGECANDIDATE:
717 MOZ_LOG(gIMELog, LogLevel::Info,
718 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_CHANGECANDIDATE, "
719 "lParam=%08x",
720 aWindow->GetWindowHandle(), lParam));
721 break;
722 case IMN_CLOSECANDIDATE:
723 MOZ_LOG(gIMELog, LogLevel::Info,
724 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_CLOSECANDIDATE, "
725 "lParam=%08x",
726 aWindow->GetWindowHandle(), lParam));
727 break;
728 case IMN_CLOSESTATUSWINDOW:
729 MOZ_LOG(gIMELog, LogLevel::Info,
730 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_CLOSESTATUSWINDOW",
731 aWindow->GetWindowHandle()));
732 break;
733 case IMN_GUIDELINE:
734 MOZ_LOG(gIMELog, LogLevel::Info,
735 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_GUIDELINE",
736 aWindow->GetWindowHandle()));
737 break;
738 case IMN_OPENCANDIDATE:
739 MOZ_LOG(
740 gIMELog, LogLevel::Info,
741 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_OPENCANDIDATE, lParam=%08x",
742 aWindow->GetWindowHandle(), lParam));
743 break;
744 case IMN_OPENSTATUSWINDOW:
745 MOZ_LOG(gIMELog, LogLevel::Info,
746 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_OPENSTATUSWINDOW",
747 aWindow->GetWindowHandle()));
748 break;
749 case IMN_SETCANDIDATEPOS:
750 MOZ_LOG(gIMELog, LogLevel::Info,
751 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETCANDIDATEPOS, "
752 "lParam=%08x",
753 aWindow->GetWindowHandle(), lParam));
754 break;
755 case IMN_SETCOMPOSITIONFONT:
756 MOZ_LOG(gIMELog, LogLevel::Info,
757 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONFONT",
758 aWindow->GetWindowHandle()));
759 break;
760 case IMN_SETCOMPOSITIONWINDOW:
761 MOZ_LOG(gIMELog, LogLevel::Info,
762 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONWINDOW",
763 aWindow->GetWindowHandle()));
764 break;
765 case IMN_SETCONVERSIONMODE:
766 MOZ_LOG(gIMELog, LogLevel::Info,
767 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETCONVERSIONMODE",
768 aWindow->GetWindowHandle()));
769 break;
770 case IMN_SETOPENSTATUS:
771 MOZ_LOG(gIMELog, LogLevel::Info,
772 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETOPENSTATUS",
773 aWindow->GetWindowHandle()));
774 break;
775 case IMN_SETSENTENCEMODE:
776 MOZ_LOG(gIMELog, LogLevel::Info,
777 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETSENTENCEMODE",
778 aWindow->GetWindowHandle()));
779 break;
780 case IMN_SETSTATUSWINDOWPOS:
781 MOZ_LOG(gIMELog, LogLevel::Info,
782 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_SETSTATUSWINDOWPOS",
783 aWindow->GetWindowHandle()));
784 break;
785 case IMN_PRIVATE:
786 MOZ_LOG(gIMELog, LogLevel::Info,
787 ("IMMHandler::OnIMENotify, hWnd=%08x, IMN_PRIVATE",
788 aWindow->GetWindowHandle()));
789 break;
792 // not implement yet
793 aResult.mConsumed = false;
794 return true;
797 bool IMMHandler::OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
798 MSGResult& aResult) {
799 switch (wParam) {
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);
805 return true;
806 case IMR_QUERYCHARPOSITION:
807 MOZ_LOG(gIMELog, LogLevel::Info,
808 ("IMMHandler::OnIMERequest, hWnd=%08x, IMR_QUERYCHARPOSITION",
809 aWindow->GetWindowHandle()));
810 aResult.mConsumed =
811 HandleQueryCharPosition(aWindow, lParam, &aResult.mResult);
812 return true;
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);
818 return true;
819 default:
820 MOZ_LOG(gIMELog, LogLevel::Info,
821 ("IMMHandler::OnIMERequest, hWnd=%08x, wParam=%08x",
822 aWindow->GetWindowHandle(), wParam));
823 aResult.mConsumed = false;
824 return true;
828 // static
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));
835 // not implement yet
836 aResult.mConsumed = false;
837 return true;
840 // static
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"));
859 return true;
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 "
873 "removed"));
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
884 // another window.
885 if (cancelComposition) {
886 CancelComposition(aWindow, true);
889 aResult.mConsumed = true;
890 return 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;
902 WPARAM recWParam;
903 LPARAM recLParam;
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 /****************************************************************************
923 * others
924 ****************************************************************************/
926 TextEventDispatcher* IMMHandler::GetTextEventDispatcherFor(nsWindow* aWindow) {
927 return aWindow == mComposingWindow && mDispatcher
928 ? mDispatcher.get()
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"));
943 return;
945 if (!contentSelection->HasRange()) {
946 MOZ_LOG(gIMELog, LogLevel::Error,
947 (" IMMHandler::HandleStartComposition, FAILED, due to "
948 "there is no selection"));
949 return;
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"));
963 return;
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"));
972 return;
975 mIsComposing = true;
976 mComposingWindow = aWindow;
977 mDispatcher = dispatcher;
979 MOZ_LOG(gIMELog, LogLevel::Info,
980 ("IMMHandler::HandleStartComposition, START composition, "
981 "mCompositionStart=%ld",
982 mCompositionStart));
985 bool IMMHandler::HandleComposition(nsWindow* aWindow,
986 const IMEContext& aContext, LPARAM lParam) {
987 // for bug #60050
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.
999 if (!mIsComposing) {
1000 MSG msg1, msg2;
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)) {
1054 MOZ_LOG(
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
1081 // information.
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(),
1144 compANSIStr)) {
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);
1150 mClauseArray[i] =
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
1183 // error code.
1184 mAttributeArray.SetLength(std::max<long>(0, attrArrayLength));
1186 MOZ_LOG(
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) {
1196 mCursorPosition =
1197 ::ImmGetCompositionStringW(aContext.get(), GCS_CURSORPOS, nullptr, 0);
1198 if (mCursorPosition < 0) {
1199 mCursorPosition = NO_IME_CARET; // The result is error
1201 } else {
1202 mCursorPosition = NO_IME_CARET;
1205 NS_ASSERTION(mCursorPosition <= (long)mCompositionString.Length(),
1206 "illegal pos");
1208 MOZ_LOG(gIMELog, LogLevel::Info,
1209 (" IMMHandler::HandleComposition, GCS_CURSORPOS, mCursorPosition=%d",
1210 mCursorPosition));
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 "
1227 "(\"%s\"))",
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"));
1239 return;
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"));
1248 return;
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,
1257 LRESULT* oResult) {
1258 *oResult = 0;
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"));
1267 return false;
1270 const uint32_t len = contentSelection->HasRange()
1271 ? contentSelection->OffsetAndDataRef().Length()
1272 : 0u;
1273 uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
1275 if (!pReconv) {
1276 // Return need size to reconvert.
1277 if (len == 0) {
1278 MOZ_LOG(gIMELog, LogLevel::Error,
1279 ("IMMHandler::HandleReconvert, There are not selected text"));
1280 return false;
1282 *oResult = needSize;
1283 MOZ_LOG(gIMELog, LogLevel::Info,
1284 ("IMMHandler::HandleReconvert, succeeded, result=%ld", *oResult));
1285 return true;
1288 if (pReconv->dwSize < needSize) {
1289 MOZ_LOG(gIMELog, LogLevel::Info,
1290 ("IMMHandler::HandleReconvert, FAILED, pReconv->dwSize=%ld, "
1291 "needSize=%ld",
1292 pReconv->dwSize, needSize));
1293 return false;
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;
1307 if (len) {
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));
1317 return true;
1320 bool IMMHandler::HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam,
1321 LRESULT* oResult) {
1322 uint32_t len = mIsComposing ? mCompositionString.Length() : 0;
1323 *oResult = false;
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"));
1329 return false;
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)));
1336 return false;
1338 if (::GetFocus() != aWindow->GetWindowHandle()) {
1339 MOZ_LOG(gIMELog, LogLevel::Error,
1340 ("IMMHandler::HandleReconvert, FAILED, ::GetFocus()=%08x, "
1341 "OurWindowHandle=%08x",
1342 ::GetFocus(), aWindow->GetWindowHandle()));
1343 return false;
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));
1350 return false;
1353 LayoutDeviceIntRect r;
1354 bool ret =
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);
1381 } else {
1382 LayoutDeviceIntRect editorRectInWindow = queryEditorRectEvent.mReply->mRect;
1383 nsWindow* window = !!queryEditorRectEvent.mReply->mFocusedWidget
1384 ? static_cast<nsWindow*>(
1385 queryEditorRectEvent.mReply->mFocusedWidget)
1386 : aWindow;
1387 LayoutDeviceIntRect editorRectInScreen;
1388 ResolveIMECaretPos(window, editorRectInWindow, nullptr, editorRectInScreen);
1389 ::SetRect(&pCharPosition->rcDocument, editorRectInScreen.X(),
1390 editorRectInScreen.Y(), editorRectInScreen.XMost(),
1391 editorRectInScreen.YMost());
1394 *oResult = TRUE;
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));
1403 return true;
1406 bool IMMHandler::HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam,
1407 LRESULT* oResult) {
1408 *oResult = 0;
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"));
1424 return false;
1426 if (contentSelection->HasRange()) {
1427 targetOffset = static_cast<int32_t>(
1428 contentSelection->OffsetAndDataRef().StartOffset());
1429 targetLength =
1430 static_cast<int32_t>(contentSelection->OffsetAndDataRef().Length());
1431 } else {
1432 // If there is no selection range, let's return all text in the editor.
1433 targetOffset = 0;
1434 targetLength = INT32_MAX;
1436 } else {
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
1443 // INT32_MAX.
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"));
1448 return false;
1451 // Get all contents of the focused editor.
1452 WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
1453 aWindow);
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"));
1461 return false;
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"));
1469 return false;
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);
1485 if (!pReconv) {
1486 *oResult = needSize;
1487 MOZ_LOG(
1488 gIMELog, LogLevel::Info,
1489 ("IMMHandler::HandleDocumentFeed, succeeded, result=%ld", *oResult));
1490 return true;
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));
1498 return false;
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"));
1514 return false;
1516 pReconv->dwTargetStrLen = length;
1517 pReconv->dwTargetStrOffset = (offset - paragraphStart) * sizeof(WCHAR);
1518 } else {
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));
1536 return true;
1539 bool IMMHandler::CommitCompositionOnPreviousWindow(nsWindow* aWindow) {
1540 if (!mComposingWindow || mComposingWindow == aWindow) {
1541 return false;
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.
1549 if (mIsComposing) {
1550 IMEContext context(mComposingWindow);
1551 NS_ASSERTION(context.IsValid(), "IME context must be valid");
1553 HandleEndComposition(mComposingWindow);
1554 return true;
1557 return false;
1560 static TextRangeType PlatformToNSAttr(uint8_t aAttr) {
1561 switch (aAttr) {
1562 case ATTR_INPUT_ERROR:
1563 // case ATTR_FIXEDCONVERTED:
1564 case ATTR_INPUT:
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;
1572 default:
1573 NS_ASSERTION(false, "unknown attribute");
1574 return TextRangeType::eCaret;
1578 // static
1579 void IMMHandler::DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent) {
1580 MOZ_LOG(
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()) {
1587 return;
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);
1604 return;
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"));
1614 return;
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"));
1639 return;
1641 } else {
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"));
1648 return;
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"));
1667 return;
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() "
1676 "failure"));
1677 return;
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"));
1692 } else {
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))) {
1721 MOZ_LOG(
1722 gIMELog, LogLevel::Error,
1723 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
1724 "TextEventDispatcher::SetCaretInPendingComposition() failure"));
1725 return;
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"));
1737 return;
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));
1760 MOZ_LOG(
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);
1771 bool found = 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;
1777 found = true;
1778 break;
1782 if (!aLength) {
1783 return true;
1786 if (!found) {
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();
1790 return true;
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;
1799 break;
1802 return true;
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"));
1814 return false;
1816 ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(),
1817 (LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr);
1818 return true;
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"));
1832 return false;
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"));
1841 return false;
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
1847 // aOffset.
1848 const uint32_t targetLength =
1849 mIsComposing ? mCompositionString.Length()
1850 : contentSelection->OffsetAndDataRef().Length();
1851 if (NS_WARN_IF(aOffset > targetLength)) {
1852 MOZ_LOG(
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)));
1857 return false;
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()) {
1865 if (mIsComposing) {
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.
1875 caretOffset = 0;
1877 } else {
1878 // If there is no composition, the selection offset is the caret offset.
1879 caretOffset = 0;
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;
1894 if (aWritingMode) {
1895 *aWritingMode = queryTextRectEvent.mReply->WritingModeRef();
1897 MOZ_LOG(
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()));
1904 return true;
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()) {
1923 MOZ_LOG(
1924 gIMELog, LogLevel::Info,
1925 ("IMMHandler::GetCaretRect, FAILED, due to eQueryCaretRect failure"));
1926 return false;
1928 aCaretRect = queryCaretRectEvent.mReply->mRect;
1929 if (aWritingMode) {
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()));
1938 return true;
1941 bool IMMHandler::SetIMERelatedWindowsPos(nsWindow* aWindow,
1942 const IMEContext& aContext) {
1943 // Get first character rect of current a normal selected text or a composing
1944 // string.
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
1958 // touch it.
1959 if (!IMEHandler::IsA11yHandlingNativeCaret()) {
1960 LayoutDeviceIntRect caretRect(firstSelectedCharRect),
1961 caretRectRelativeToWindow;
1962 if (GetCaretRect(aWindow, caretRectRelativeToWindow)) {
1963 ResolveIMECaretPos(toplevelWindow, caretRectRelativeToWindow, aWindow,
1964 caretRect);
1965 } else {
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
1980 // rect instead.
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"));
1986 return false;
1988 ret =
1989 GetCharacterRectOfSelectedTextAt(aWindow, offset - mCompositionStart,
1990 firstTargetCharRect, &writingMode);
1991 NS_ENSURE_TRUE(ret, false);
1992 if (length) {
1993 ret = GetCharacterRectOfSelectedTextAt(
1994 aWindow, offset + length - 1 - mCompositionStart,
1995 lastTargetCharRect);
1996 NS_ENSURE_TRUE(ret, false);
1997 } else {
1998 lastTargetCharRect = firstTargetCharRect;
2000 } else {
2001 // If there are no composition string, we should use a first character
2002 // rect.
2003 ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, firstTargetCharRect,
2004 &writingMode);
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
2016 // far as possible.
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
2022 // mode.
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();
2039 } else {
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();
2048 } else {
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 }, "
2061 "writingMode=%s",
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);
2066 } else {
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);
2083 return true;
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"));
2106 return;
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"));
2118 return;
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
2143 // is horizontal.
2144 bool setCompositionFontForcibly =
2145 aForceUpdate ||
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.
2154 return;
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;
2169 if (NS_WARN_IF(
2170 !::ImmGetCompositionFont(aContext.get(), &defaultLogFont))) {
2171 MOZ_LOG(
2172 gIMELog, LogLevel::Error,
2173 (" IMMHandler::AdjustCompositionFont, ::ImmGetCompositionFont() "
2174 "failed"));
2175 sCompositionFont.AssignLiteral("System");
2176 } else {
2177 // The font face is typically, "System".
2178 sCompositionFont.Assign(defaultLogFont.lfFaceName);
2182 MOZ_LOG(gIMELog, LogLevel::Info,
2183 (" IMMHandler::AdjustCompositionFont, sCompositionFont=\"%s\" is "
2184 "initialized",
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;
2206 LOGFONTW logFont;
2207 memset(&logFont, 0, sizeof(logFont));
2208 if (!::ImmGetCompositionFont(aContext.get(), &logFont)) {
2209 MOZ_LOG(gIMELog, LogLevel::Error,
2210 (" IMMHandler::AdjustCompositionFont, ::ImmGetCompositionFont() "
2211 "failed"));
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
2224 : sCompositionFont,
2225 logFont);
2226 } else {
2227 SetHorizontalFontToLogFont(IsJapanist2003Active()
2228 ? sCompositionFontForJapanist2003
2229 : sCompositionFont,
2230 logFont);
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);
2239 // static
2240 nsresult IMMHandler::OnMouseButtonEvent(
2241 nsWindow* aWindow, const IMENotification& aIMENotification) {
2242 // We don't need to create the instance of the handler here.
2243 if (!gIMMHandler) {
2244 return NS_OK;
2247 if (!sWM_MSIME_MOUSE || !IsComposingOnOurEditor() ||
2248 !ShouldDrawCompositionStringOurselves()) {
2249 return NS_OK;
2252 // We need to handle only mousedown event.
2253 if (aIMENotification.mMouseButtonEventData.mEventMessage != eMouseDown) {
2254 return NS_OK;
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) {
2264 return NS_OK;
2267 BYTE button;
2268 switch (aIMENotification.mMouseButtonEventData.mButton) {
2269 case MouseButton::ePrimary:
2270 button = IMEMOUSE_LDOWN;
2271 break;
2272 case MouseButton::eMiddle:
2273 button = IMEMOUSE_MDOWN;
2274 break;
2275 case MouseButton::eSecondary:
2276 button = IMEMOUSE_RDOWN;
2277 break;
2278 default:
2279 return NS_OK;
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;
2304 int offset =
2305 aIMENotification.mMouseButtonEventData.mOffset - compositionStart;
2306 if (positioning < 2) {
2307 offset++;
2310 MOZ_LOG(gIMELog, LogLevel::Info,
2311 ("IMMHandler::OnMouseButtonEvent, x,y=%ld,%ld, offset=%ld, "
2312 "positioning=%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;
2323 return NS_OK;
2326 // static
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;
2333 switch (wParam) {
2334 case VK_TAB:
2335 case VK_PRIOR:
2336 case VK_NEXT:
2337 case VK_END:
2338 case VK_HOME:
2339 case VK_LEFT:
2340 case VK_UP:
2341 case VK_RIGHT:
2342 case VK_DOWN:
2343 case VK_RETURN:
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);
2354 return false;
2355 default:
2356 return false;
2360 Maybe<ContentSelection> IMMHandler::QueryContentSelection(nsWindow* aWindow) {
2361 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
2362 aWindow);
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 "
2369 "failure"));
2370 return Nothing();
2372 // If the window is destroyed during querying selected text, we shouldn't
2373 // do anymore.
2374 if (aWindow->Destroyed()) {
2375 MOZ_LOG(
2376 gIMELog, LogLevel::Error,
2377 (" IMMHandler::Selection::Init, FAILED, due to the widget destroyed"));
2378 return Nothing();
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"));
2391 return Nothing();
2393 return Some(contentSelection);
2396 } // namespace widget
2397 } // namespace mozilla