Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / widget / windows / IMMHandler.cpp
blob2f64f93922533e18fda67df7216eb00c8eb265df
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"
22 #ifndef IME_PROP_ACCEPT_WIDE_VKEY
23 # define IME_PROP_ACCEPT_WIDE_VKEY 0x20
24 #endif
26 //-------------------------------------------------------------------------
28 // from
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
45 // big file.
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 {
58 public:
59 explicit GetIMEGeneralPropertyName(DWORD aFlags) {
60 if (!aFlags) {
61 AppendLiteral("no flags");
62 return;
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 {
92 public:
93 explicit GetIMEUIPropertyName(DWORD aFlags) {
94 if (!aFlags) {
95 AppendLiteral("no flags");
96 return;
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 {
114 public:
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() {}
144 namespace mozilla {
145 namespace widget {
147 static IMMHandler* gIMMHandler = nullptr;
149 /******************************************************************************
150 * IMEContext
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) {
160 Clear();
161 mWnd = aWnd;
162 mIMC = ::ImmGetContext(mWnd);
165 void IMEContext::Init(nsWindow* aWindowBase) {
166 Init(aWindowBase->GetWindowHandle());
169 void IMEContext::Clear() {
170 if (mWnd && mIMC) {
171 ::ImmReleaseContext(mWnd, mIMC);
173 mWnd = nullptr;
174 mIMC = nullptr;
177 /******************************************************************************
178 * IMMHandler
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
210 // static
211 bool IMMHandler::IsActiveIMEInBlockList() {
212 if (sIMEName.IsEmpty()) {
213 return false;
215 #ifdef _WIN64
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
225 // crash reports.
226 if ((IsATOK2006Active() || IsATOK2007Active() || IsATOK2008Active() ||
227 IsATOK2009Active() || IsATOK2010Active())) {
228 return true;
230 #endif // #ifdef _WIN64
231 return false;
234 // static
235 void IMMHandler::EnsureHandlerInstance() {
236 if (!gIMMHandler) {
237 gIMMHandler = new IMMHandler();
241 // static
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));
251 // static
252 void IMMHandler::Terminate() {
253 if (!gIMMHandler) return;
254 delete gIMMHandler;
255 gIMMHandler = nullptr;
258 // static
259 bool IMMHandler::IsComposingOnOurEditor() {
260 return gIMMHandler && gIMMHandler->mIsComposing;
263 // static
264 bool IMMHandler::IsComposingWindow(nsWindow* aWindow) {
265 return gIMMHandler && gIMMHandler->mComposingWindow == aWindow;
268 // static
269 bool IMMHandler::IsTopLevelWindowOfComposition(nsWindow* aWindow) {
270 if (!gIMMHandler || !gIMMHandler->mComposingWindow) {
271 return false;
273 HWND wnd = gIMMHandler->mComposingWindow->GetWindowHandle();
274 return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle();
277 // static
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
281 // ourselves.
282 return !(sIMEProperty & IME_PROP_SPECIAL_UI) &&
283 (sIMEProperty & IME_PROP_AT_CARET);
286 // static
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) {
291 return false;
293 // Google Japanese Input doesn't support vertical writing mode. We should
294 // return false if it's active IME.
295 if (IsGoogleJapaneseInputActive()) {
296 return false;
298 return !!(sIMEUIProperty & (UI_CAP_2700 | UI_CAP_ROT90 | UI_CAP_ROTANY));
301 // static
302 void IMMHandler::InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout) {
303 UINT IMENameLength = ::ImmGetDescriptionW(aKeyboardLayout, nullptr, 0);
304 if (IMENameLength) {
305 // Add room for the terminating null character
306 sIMEName.SetLength(++IMENameLength);
307 IMENameLength =
308 ::ImmGetDescriptionW(aKeyboardLayout, sIMEName.get(), IMENameLength);
309 // Adjust the length to ignore the terminating null character
310 sIMEName.SetLength(IMENameLength);
311 } else {
312 sIMEName.Truncate();
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
324 // pref.
325 if (sCodePage == 932 && sIMEName.IsEmpty()) {
326 Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as",
327 sIMEName);
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.
333 if (aWindow) {
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()));
345 // static
346 UINT IMMHandler::GetKeyboardCodePage() { return sCodePage; }
348 // static
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() {
372 if (mIsComposing) {
373 MOZ_LOG(
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);
383 return NS_OK;
386 nsresult IMMHandler::EnsureAttributeArray(int32_t aCount) {
387 NS_ENSURE_ARG_MIN(aCount, 0);
388 mAttributeArray.SetCapacity(aCount + 64);
389 return NS_OK;
392 // static
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)"
402 : ""));
403 if (!aForce && !IsComposingWindow(aWindow)) {
404 return;
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);
418 if (associated) {
419 context.Disassociate();
423 // static
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)"
433 : ""));
434 if (!aForce && !IsComposingWindow(aWindow)) {
435 return;
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);
448 if (associated) {
449 context.Disassociate();
453 // static
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())));
462 if (!aFocus) {
463 IMEHandler::MaybeDestroyNativeCaret();
464 if (IsComposingWindow(aWindow) && aWindow->Destroyed()) {
465 CancelComposition(aWindow);
468 if (gIMMHandler) {
469 gIMMHandler->mContentSelection.reset();
471 sHasFocus = aFocus;
474 // static
475 void IMMHandler::OnUpdateComposition(nsWindow* aWindow) {
476 if (!gIMMHandler) {
477 return;
480 IMEContext context(aWindow);
481 gIMMHandler->SetIMERelatedWindowsPos(aWindow, context);
484 // static
485 void IMMHandler::OnSelectionChange(nsWindow* aWindow,
486 const IMENotification& aIMENotification,
487 bool aIsIMMActive) {
488 if (!aIMENotification.mSelectionChangeData.mCausedByComposition &&
489 aIsIMMActive) {
490 MaybeAdjustCompositionFont(
491 aWindow, aIMENotification.mSelectionChangeData.GetWritingMode());
493 // MaybeAdjustCompositionFont() may create gIMMHandler. So, check it
494 // after a call of MaybeAdjustCompositionFont().
495 if (gIMMHandler) {
496 gIMMHandler->mContentSelection =
497 Some(ContentSelection(aIMENotification.mSelectionChangeData));
501 // static
502 void IMMHandler::MaybeAdjustCompositionFont(nsWindow* aWindow,
503 const WritingMode& aWritingMode,
504 bool aForceUpdate) {
505 switch (sCodePage) {
506 case 932: // Japanese Shift-JIS
507 case 936: // Simlified Chinese GBK
508 case 949: // Korean
509 case 950: // Traditional Chinese Big5
510 EnsureHandlerInstance();
511 break;
512 default:
513 // If there is no instance of nsIMM32Hander, we shouldn't waste footprint.
514 if (!gIMMHandler) {
515 return;
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,
523 aForceUpdate);
526 // static
527 bool IMMHandler::ProcessInputLangChangeMessage(nsWindow* aWindow, WPARAM wParam,
528 LPARAM lParam,
529 MSGResult& aResult) {
530 aResult.mResult = 0;
531 aResult.mConsumed = false;
532 // We don't need to create the instance of the handler here.
533 if (gIMMHandler) {
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.
539 Terminate();
540 // Don't return as "processed", the messages should be processed on nsWindow
541 // too.
542 return false;
545 // static
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.
553 aResult.mResult = 0;
554 switch (msg) {
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);
566 case WM_IME_CHAR:
567 return OnIMEChar(aWindow, wParam, lParam, aResult);
568 case WM_IME_NOTIFY:
569 return OnIMENotify(aWindow, wParam, lParam, aResult);
570 case WM_IME_REQUEST:
571 EnsureHandlerInstance();
572 return gIMMHandler->OnIMERequest(aWindow, wParam, lParam, aResult);
573 case WM_IME_SELECT:
574 return OnIMESelect(aWindow, wParam, lParam, aResult);
575 case WM_IME_SETCONTEXT:
576 return OnIMESetContext(aWindow, wParam, lParam, aResult);
577 case WM_KEYDOWN:
578 return OnKeyDownEvent(aWindow, wParam, lParam, aResult);
579 case WM_CHAR:
580 if (!gIMMHandler) {
581 return false;
583 return gIMMHandler->OnChar(aWindow, wParam, lParam, aResult);
584 default:
585 return false;
589 /****************************************************************************
590 * message handlers
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");
603 if (mIsComposing) {
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();
615 if (mIsComposing) {
616 NS_WARNING("Composition has been already started");
617 return true;
620 IMEContext context(aWindow);
621 HandleStartComposition(aWindow, context);
622 return true;
625 bool IMMHandler::OnIMEComposition(nsWindow* aWindow, WPARAM wParam,
626 LPARAM lParam, MSGResult& aResult) {
627 MOZ_LOG(
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, "
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=%p, 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=%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;
698 return true;
701 // static
702 bool IMMHandler::OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult) {
703 MOZ_LOG(gIMELog, LogLevel::Info,
704 ("IMMHandler::OnIMECompositionFull, hWnd=%p",
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=%p, IMN_CHANGECANDIDATE, "
719 "lParam=%08" PRIxLPTR,
720 aWindow->GetWindowHandle(), lParam));
721 break;
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));
727 break;
728 case IMN_CLOSESTATUSWINDOW:
729 MOZ_LOG(gIMELog, LogLevel::Info,
730 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_CLOSESTATUSWINDOW",
731 aWindow->GetWindowHandle()));
732 break;
733 case IMN_GUIDELINE:
734 MOZ_LOG(gIMELog, LogLevel::Info,
735 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_GUIDELINE",
736 aWindow->GetWindowHandle()));
737 break;
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));
743 break;
744 case IMN_OPENSTATUSWINDOW:
745 MOZ_LOG(gIMELog, LogLevel::Info,
746 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_OPENSTATUSWINDOW",
747 aWindow->GetWindowHandle()));
748 break;
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));
754 break;
755 case IMN_SETCOMPOSITIONFONT:
756 MOZ_LOG(gIMELog, LogLevel::Info,
757 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETCOMPOSITIONFONT",
758 aWindow->GetWindowHandle()));
759 break;
760 case IMN_SETCOMPOSITIONWINDOW:
761 MOZ_LOG(gIMELog, LogLevel::Info,
762 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETCOMPOSITIONWINDOW",
763 aWindow->GetWindowHandle()));
764 break;
765 case IMN_SETCONVERSIONMODE:
766 MOZ_LOG(gIMELog, LogLevel::Info,
767 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETCONVERSIONMODE",
768 aWindow->GetWindowHandle()));
769 break;
770 case IMN_SETOPENSTATUS:
771 MOZ_LOG(gIMELog, LogLevel::Info,
772 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETOPENSTATUS",
773 aWindow->GetWindowHandle()));
774 break;
775 case IMN_SETSENTENCEMODE:
776 MOZ_LOG(gIMELog, LogLevel::Info,
777 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETSENTENCEMODE",
778 aWindow->GetWindowHandle()));
779 break;
780 case IMN_SETSTATUSWINDOWPOS:
781 MOZ_LOG(gIMELog, LogLevel::Info,
782 ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETSTATUSWINDOWPOS",
783 aWindow->GetWindowHandle()));
784 break;
785 case IMN_PRIVATE:
786 MOZ_LOG(gIMELog, LogLevel::Info,
787 ("IMMHandler::OnIMENotify, hWnd=%p, 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=%p, 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=%p, 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=%p, 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=%p, wParam=%08zx",
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(
832 gIMELog, LogLevel::Info,
833 ("IMMHandler::OnIMESelect, hWnd=%p, wParam=%08zx, lParam=%08" PRIxLPTR,
834 aWindow->GetWindowHandle(), wParam, lParam));
836 // not implement yet
837 aResult.mConsumed = false;
838 return true;
841 // static
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()));
861 return true;
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 "
875 "removed"));
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
886 // another window.
887 if (cancelComposition) {
888 CancelComposition(aWindow, true);
891 aResult.mConsumed = true;
892 return 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;
904 WPARAM recWParam;
905 LPARAM recLParam;
906 DequeueIMECharRecords(recWParam, recLParam);
907 MOZ_LOG(
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 /****************************************************************************
926 * others
927 ****************************************************************************/
929 TextEventDispatcher* IMMHandler::GetTextEventDispatcherFor(nsWindow* aWindow) {
930 return aWindow == mComposingWindow && mDispatcher
931 ? mDispatcher.get()
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"));
946 return;
948 if (!contentSelection->HasRange()) {
949 MOZ_LOG(gIMELog, LogLevel::Error,
950 (" IMMHandler::HandleStartComposition, FAILED, due to "
951 "there is no selection"));
952 return;
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"));
966 return;
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"));
975 return;
978 mIsComposing = true;
979 mComposingWindow = aWindow;
980 mDispatcher = dispatcher;
982 MOZ_LOG(gIMELog, LogLevel::Info,
983 ("IMMHandler::HandleStartComposition, START composition, "
984 "mCompositionStart=%u",
985 mCompositionStart));
988 bool IMMHandler::HandleComposition(nsWindow* aWindow,
989 const IMEContext& aContext, LPARAM lParam) {
990 // for bug #60050
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) {
1003 MSG msg1, msg2;
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)) {
1057 MOZ_LOG(
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
1084 // information.
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(),
1147 compANSIStr)) {
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);
1153 mClauseArray[i] =
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
1186 // error code.
1187 mAttributeArray.SetLength(std::max<long>(0, attrArrayLength));
1189 MOZ_LOG(
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) {
1199 mCursorPosition =
1200 ::ImmGetCompositionStringW(aContext.get(), GCS_CURSORPOS, nullptr, 0);
1201 if (mCursorPosition < 0) {
1202 mCursorPosition = NO_IME_CARET; // The result is error
1204 } else {
1205 mCursorPosition = NO_IME_CARET;
1208 NS_ASSERTION(mCursorPosition <= (long)mCompositionString.Length(),
1209 "illegal pos");
1211 MOZ_LOG(gIMELog, LogLevel::Info,
1212 (" IMMHandler::HandleComposition, GCS_CURSORPOS, mCursorPosition=%d",
1213 mCursorPosition));
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 "
1230 "(\"%s\"))",
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"));
1242 return;
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"));
1251 return;
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,
1260 LRESULT* oResult) {
1261 *oResult = 0;
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"));
1270 return false;
1273 const uint32_t len = contentSelection->HasRange()
1274 ? contentSelection->OffsetAndDataRef().Length()
1275 : 0u;
1276 uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
1278 if (!pReconv) {
1279 // Return need size to reconvert.
1280 if (len == 0) {
1281 MOZ_LOG(gIMELog, LogLevel::Error,
1282 ("IMMHandler::HandleReconvert, There are not selected text"));
1283 return false;
1285 *oResult = needSize;
1286 MOZ_LOG(gIMELog, LogLevel::Info,
1287 ("IMMHandler::HandleReconvert, succeeded, result=%" PRIdLPTR,
1288 *oResult));
1289 return true;
1292 if (pReconv->dwSize < needSize) {
1293 MOZ_LOG(gIMELog, LogLevel::Info,
1294 ("IMMHandler::HandleReconvert, FAILED, pReconv->dwSize=%ld, "
1295 "needSize=%u",
1296 pReconv->dwSize, needSize));
1297 return false;
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;
1311 if (len) {
1312 ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)),
1313 contentSelection->OffsetAndDataRef().DataRef().get(),
1314 len * sizeof(WCHAR));
1317 MOZ_LOG(
1318 gIMELog, LogLevel::Info,
1319 ("IMMHandler::HandleReconvert, SUCCEEDED, pReconv=%s, result=%" PRIdLPTR,
1320 GetReconvertStringLog(pReconv).get(), *oResult));
1322 return true;
1325 bool IMMHandler::HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam,
1326 LRESULT* oResult) {
1327 uint32_t len = mIsComposing ? mCompositionString.Length() : 0;
1328 *oResult = false;
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"));
1334 return false;
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)));
1341 return false;
1343 if (::GetFocus() != aWindow->GetWindowHandle()) {
1344 MOZ_LOG(gIMELog, LogLevel::Error,
1345 ("IMMHandler::HandleReconvert, FAILED, ::GetFocus()=%p, "
1346 "OurWindowHandle=%p",
1347 ::GetFocus(), aWindow->GetWindowHandle()));
1348 return false;
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));
1355 return false;
1358 LayoutDeviceIntRect r;
1359 bool ret =
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);
1386 } else {
1387 LayoutDeviceIntRect editorRectInWindow = queryEditorRectEvent.mReply->mRect;
1388 nsWindow* window = !!queryEditorRectEvent.mReply->mFocusedWidget
1389 ? static_cast<nsWindow*>(
1390 queryEditorRectEvent.mReply->mFocusedWidget)
1391 : aWindow;
1392 LayoutDeviceIntRect editorRectInScreen;
1393 ResolveIMECaretPos(window, editorRectInWindow, nullptr, editorRectInScreen);
1394 ::SetRect(&pCharPosition->rcDocument, editorRectInScreen.X(),
1395 editorRectInScreen.Y(), editorRectInScreen.XMost(),
1396 editorRectInScreen.YMost());
1399 *oResult = TRUE;
1401 MOZ_LOG(
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));
1409 return true;
1412 bool IMMHandler::HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam,
1413 LRESULT* oResult) {
1414 *oResult = 0;
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"));
1430 return false;
1432 if (contentSelection->HasRange()) {
1433 targetOffset = static_cast<int32_t>(
1434 contentSelection->OffsetAndDataRef().StartOffset());
1435 targetLength =
1436 static_cast<int32_t>(contentSelection->OffsetAndDataRef().Length());
1437 } else {
1438 // If there is no selection range, let's return all text in the editor.
1439 targetOffset = 0;
1440 targetLength = INT32_MAX;
1442 } else {
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
1449 // INT32_MAX.
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"));
1454 return false;
1457 // Get all contents of the focused editor.
1458 WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
1459 aWindow);
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"));
1467 return false;
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"));
1475 return false;
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);
1494 if (!pReconv) {
1495 *oResult = needSize;
1496 MOZ_LOG(gIMELog, LogLevel::Info,
1497 ("IMMHandler::HandleDocumentFeed, succeeded, result=%" PRIdLPTR,
1498 *oResult));
1499 return true;
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));
1507 return false;
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"));
1523 return false;
1525 pReconv->dwTargetStrLen = length;
1526 pReconv->dwTargetStrOffset = (offset - paragraphStart) * sizeof(WCHAR);
1527 } else {
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));
1546 return true;
1549 bool IMMHandler::CommitCompositionOnPreviousWindow(nsWindow* aWindow) {
1550 if (!mComposingWindow || mComposingWindow == aWindow) {
1551 return false;
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.
1559 if (mIsComposing) {
1560 IMEContext context(mComposingWindow);
1561 NS_ASSERTION(context.IsValid(), "IME context must be valid");
1563 HandleEndComposition(mComposingWindow);
1564 return true;
1567 return false;
1570 static TextRangeType PlatformToNSAttr(uint8_t aAttr) {
1571 switch (aAttr) {
1572 case ATTR_INPUT_ERROR:
1573 // case ATTR_FIXEDCONVERTED:
1574 case ATTR_INPUT:
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;
1582 default:
1583 NS_ASSERTION(false, "unknown attribute");
1584 return TextRangeType::eCaret;
1588 // static
1589 void IMMHandler::DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent) {
1590 MOZ_LOG(
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()) {
1597 return;
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);
1614 return;
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"));
1624 return;
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"));
1649 return;
1651 } else {
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"));
1658 return;
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"));
1677 return;
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() "
1686 "failure"));
1687 return;
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"));
1702 } else {
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))) {
1731 MOZ_LOG(
1732 gIMELog, LogLevel::Error,
1733 (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
1734 "TextEventDispatcher::SetCaretInPendingComposition() failure"));
1735 return;
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"));
1747 return;
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));
1770 MOZ_LOG(
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);
1781 bool found = 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;
1787 found = true;
1788 break;
1792 if (!aLength) {
1793 return true;
1796 if (!found) {
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();
1800 return true;
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;
1809 break;
1812 return true;
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"));
1824 return false;
1826 ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(),
1827 (LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr);
1828 return true;
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"));
1842 return false;
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"));
1851 return false;
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
1857 // aOffset.
1858 const uint32_t targetLength =
1859 mIsComposing ? mCompositionString.Length()
1860 : contentSelection->OffsetAndDataRef().Length();
1861 if (NS_WARN_IF(aOffset > targetLength)) {
1862 MOZ_LOG(
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)));
1867 return false;
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()) {
1875 if (mIsComposing) {
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.
1885 caretOffset = 0;
1887 } else {
1888 // If there is no composition, the selection offset is the caret offset.
1889 caretOffset = 0;
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;
1904 if (aWritingMode) {
1905 *aWritingMode = queryTextRectEvent.mReply->WritingModeRef();
1907 MOZ_LOG(
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()));
1914 return true;
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()) {
1933 MOZ_LOG(
1934 gIMELog, LogLevel::Info,
1935 ("IMMHandler::GetCaretRect, FAILED, due to eQueryCaretRect failure"));
1936 return false;
1938 aCaretRect = queryCaretRectEvent.mReply->mRect;
1939 if (aWritingMode) {
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()));
1948 return true;
1951 bool IMMHandler::SetIMERelatedWindowsPos(nsWindow* aWindow,
1952 const IMEContext& aContext) {
1953 // Get first character rect of current a normal selected text or a composing
1954 // string.
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
1968 // touch it.
1969 if (!IMEHandler::IsA11yHandlingNativeCaret()) {
1970 LayoutDeviceIntRect caretRect(firstSelectedCharRect),
1971 caretRectRelativeToWindow;
1972 if (GetCaretRect(aWindow, caretRectRelativeToWindow)) {
1973 ResolveIMECaretPos(toplevelWindow, caretRectRelativeToWindow, aWindow,
1974 caretRect);
1975 } else {
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
1990 // rect instead.
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"));
1996 return false;
1998 ret =
1999 GetCharacterRectOfSelectedTextAt(aWindow, offset - mCompositionStart,
2000 firstTargetCharRect, &writingMode);
2001 NS_ENSURE_TRUE(ret, false);
2002 if (length) {
2003 ret = GetCharacterRectOfSelectedTextAt(
2004 aWindow, offset + length - 1 - mCompositionStart,
2005 lastTargetCharRect);
2006 NS_ENSURE_TRUE(ret, false);
2007 } else {
2008 lastTargetCharRect = firstTargetCharRect;
2010 } else {
2011 // If there are no composition string, we should use a first character
2012 // rect.
2013 ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, firstTargetCharRect,
2014 &writingMode);
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
2026 // far as possible.
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
2032 // mode.
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();
2049 } else {
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();
2058 } else {
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 }, "
2071 "writingMode=%s",
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);
2076 } else {
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);
2093 return true;
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"));
2116 return;
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"));
2128 return;
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
2153 // is horizontal.
2154 bool setCompositionFontForcibly =
2155 aForceUpdate ||
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.
2164 return;
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;
2179 if (NS_WARN_IF(
2180 !::ImmGetCompositionFont(aContext.get(), &defaultLogFont))) {
2181 MOZ_LOG(
2182 gIMELog, LogLevel::Error,
2183 (" IMMHandler::AdjustCompositionFont, ::ImmGetCompositionFont() "
2184 "failed"));
2185 sCompositionFont.AssignLiteral("System");
2186 } else {
2187 // The font face is typically, "System".
2188 sCompositionFont.Assign(defaultLogFont.lfFaceName);
2192 MOZ_LOG(gIMELog, LogLevel::Info,
2193 (" IMMHandler::AdjustCompositionFont, sCompositionFont=\"%s\" is "
2194 "initialized",
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;
2216 LOGFONTW logFont;
2217 memset(&logFont, 0, sizeof(logFont));
2218 if (!::ImmGetCompositionFont(aContext.get(), &logFont)) {
2219 MOZ_LOG(gIMELog, LogLevel::Error,
2220 (" IMMHandler::AdjustCompositionFont, ::ImmGetCompositionFont() "
2221 "failed"));
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
2234 : sCompositionFont,
2235 logFont);
2236 } else {
2237 SetHorizontalFontToLogFont(IsJapanist2003Active()
2238 ? sCompositionFontForJapanist2003
2239 : sCompositionFont,
2240 logFont);
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);
2249 // static
2250 nsresult IMMHandler::OnMouseButtonEvent(
2251 nsWindow* aWindow, const IMENotification& aIMENotification) {
2252 // We don't need to create the instance of the handler here.
2253 if (!gIMMHandler) {
2254 return NS_OK;
2257 if (!sWM_MSIME_MOUSE || !IsComposingOnOurEditor() ||
2258 !ShouldDrawCompositionStringOurselves()) {
2259 return NS_OK;
2262 // We need to handle only mousedown event.
2263 if (aIMENotification.mMouseButtonEventData.mEventMessage != eMouseDown) {
2264 return NS_OK;
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) {
2274 return NS_OK;
2277 BYTE button;
2278 switch (aIMENotification.mMouseButtonEventData.mButton) {
2279 case MouseButton::ePrimary:
2280 button = IMEMOUSE_LDOWN;
2281 break;
2282 case MouseButton::eMiddle:
2283 button = IMEMOUSE_MDOWN;
2284 break;
2285 case MouseButton::eSecondary:
2286 button = IMEMOUSE_RDOWN;
2287 break;
2288 default:
2289 return NS_OK;
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;
2314 int offset =
2315 aIMENotification.mMouseButtonEventData.mOffset - compositionStart;
2316 if (positioning < 2) {
2317 offset++;
2320 MOZ_LOG(gIMELog, LogLevel::Info,
2321 ("IMMHandler::OnMouseButtonEvent, x,y=%d,%d, offset=%d, "
2322 "positioning=%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;
2333 return NS_OK;
2336 // static
2337 bool IMMHandler::OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
2338 MSGResult& aResult) {
2339 MOZ_LOG(
2340 gIMELog, LogLevel::Info,
2341 ("IMMHandler::OnKeyDownEvent, hWnd=%p, wParam=%08zx, lParam=%08" PRIxLPTR,
2342 aWindow->GetWindowHandle(), wParam, lParam));
2343 aResult.mConsumed = false;
2344 switch (wParam) {
2345 case VK_TAB:
2346 case VK_PRIOR:
2347 case VK_NEXT:
2348 case VK_END:
2349 case VK_HOME:
2350 case VK_LEFT:
2351 case VK_UP:
2352 case VK_RIGHT:
2353 case VK_DOWN:
2354 case VK_RETURN:
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);
2365 return false;
2366 default:
2367 return false;
2371 Maybe<ContentSelection> IMMHandler::QueryContentSelection(nsWindow* aWindow) {
2372 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
2373 aWindow);
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 "
2380 "failure"));
2381 return Nothing();
2383 // If the window is destroyed during querying selected text, we shouldn't
2384 // do anymore.
2385 if (aWindow->Destroyed()) {
2386 MOZ_LOG(
2387 gIMELog, LogLevel::Error,
2388 (" IMMHandler::Selection::Init, FAILED, due to the widget destroyed"));
2389 return Nothing();
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"));
2402 return Nothing();
2404 return Some(contentSelection);
2407 } // namespace widget
2408 } // namespace mozilla