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/. */
8 #define FORCE_PR_LOG /* Allow logging in the release build */
12 #include "nsIMM32Handler.h"
14 #include "nsWindowDefs.h"
16 #include "KeyboardLayout.h"
19 #include "mozilla/MiscEvents.h"
20 #include "mozilla/TextEvents.h"
22 using namespace mozilla
;
23 using namespace mozilla::widget
;
25 static nsIMM32Handler
* gIMM32Handler
= nullptr;
28 PRLogModuleInfo
* gIMM32Log
= nullptr;
31 static UINT sWM_MSIME_MOUSE
= 0; // mouse message for MSIME 98/2000
33 //-------------------------------------------------------------------------
35 // from http://download.microsoft.com/download/6/0/9/60908e9e-d2c1-47db-98f6-216af76a235f/msime.h
36 // The document for this has been removed from MSDN...
38 //-------------------------------------------------------------------------
40 #define RWM_MOUSE TEXT("MSIMEMouseOperation")
42 #define IMEMOUSE_NONE 0x00 // no mouse button was pushed
43 #define IMEMOUSE_LDOWN 0x01
44 #define IMEMOUSE_RDOWN 0x02
45 #define IMEMOUSE_MDOWN 0x04
46 #define IMEMOUSE_WUP 0x10 // wheel up
47 #define IMEMOUSE_WDOWN 0x20 // wheel down
49 UINT
nsIMM32Handler::sCodePage
= 0;
50 DWORD
nsIMM32Handler::sIMEProperty
= 0;
53 nsIMM32Handler::EnsureHandlerInstance()
56 gIMM32Handler
= new nsIMM32Handler();
61 nsIMM32Handler::Initialize()
65 gIMM32Log
= PR_NewLogModule("nsIMM32HandlerWidgets");
68 if (!sWM_MSIME_MOUSE
) {
69 sWM_MSIME_MOUSE
= ::RegisterWindowMessage(RWM_MOUSE
);
71 InitKeyboardLayout(::GetKeyboardLayout(0));
75 nsIMM32Handler::Terminate()
80 gIMM32Handler
= nullptr;
84 nsIMM32Handler::IsComposingOnOurEditor()
86 return gIMM32Handler
&& gIMM32Handler
->mIsComposing
;
90 nsIMM32Handler::IsComposingOnPlugin()
92 return gIMM32Handler
&& gIMM32Handler
->mIsComposingOnPlugin
;
96 nsIMM32Handler::IsComposingWindow(nsWindow
* aWindow
)
98 return gIMM32Handler
&& gIMM32Handler
->mComposingWindow
== aWindow
;
102 nsIMM32Handler::IsTopLevelWindowOfComposition(nsWindow
* aWindow
)
104 if (!gIMM32Handler
|| !gIMM32Handler
->mComposingWindow
) {
107 HWND wnd
= gIMM32Handler
->mComposingWindow
->GetWindowHandle();
108 return WinUtils::GetTopLevelHWND(wnd
, true) == aWindow
->GetWindowHandle();
112 nsIMM32Handler::ShouldDrawCompositionStringOurselves()
114 // If current IME has special UI or its composition window should not
115 // positioned to caret position, we should now draw composition string
117 return !(sIMEProperty
& IME_PROP_SPECIAL_UI
) &&
118 (sIMEProperty
& IME_PROP_AT_CARET
);
122 nsIMM32Handler::InitKeyboardLayout(HKL aKeyboardLayout
)
124 WORD langID
= LOWORD(aKeyboardLayout
);
125 ::GetLocaleInfoW(MAKELCID(langID
, SORT_DEFAULT
),
126 LOCALE_IDEFAULTANSICODEPAGE
| LOCALE_RETURN_NUMBER
,
127 (PWSTR
)&sCodePage
, sizeof(sCodePage
) / sizeof(WCHAR
));
128 sIMEProperty
= ::ImmGetProperty(aKeyboardLayout
, IGP_PROPERTY
);
129 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
130 ("IMM32: InitKeyboardLayout, aKeyboardLayout=%08x, sCodePage=%lu, "
132 aKeyboardLayout
, sCodePage
, sIMEProperty
));
136 nsIMM32Handler::GetKeyboardCodePage()
141 // used for checking the lParam of WM_IME_COMPOSITION
142 #define IS_COMPOSING_LPARAM(lParam) \
143 ((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS))
144 #define IS_COMMITTING_LPARAM(lParam) ((lParam) & GCS_RESULTSTR)
145 // Some IMEs (e.g., the standard IME for Korean) don't have caret position,
146 // then, we should not set caret position to text event.
147 #define NO_IME_CARET -1
149 nsIMM32Handler::nsIMM32Handler() :
150 mComposingWindow(nullptr), mCursorPosition(NO_IME_CARET
), mCompositionStart(0),
151 mIsComposing(false), mIsComposingOnPlugin(false),
152 mNativeCaretIsCreated(false)
154 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
, ("IMM32: nsIMM32Handler is created\n"));
157 nsIMM32Handler::~nsIMM32Handler()
160 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
161 ("IMM32: ~nsIMM32Handler, ERROR, the instance is still composing\n"));
163 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
, ("IMM32: nsIMM32Handler is destroyed\n"));
167 nsIMM32Handler::EnsureClauseArray(int32_t aCount
)
169 NS_ENSURE_ARG_MIN(aCount
, 0);
170 mClauseArray
.SetCapacity(aCount
+ 32);
175 nsIMM32Handler::EnsureAttributeArray(int32_t aCount
)
177 NS_ENSURE_ARG_MIN(aCount
, 0);
178 mAttributeArray
.SetCapacity(aCount
+ 64);
183 nsIMM32Handler::CommitComposition(nsWindow
* aWindow
, bool aForce
)
185 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
186 ("IMM32: CommitComposition, aForce=%s, aWindow=%p, hWnd=%08x, mComposingWindow=%p%s\n",
187 aForce
? "TRUE" : "FALSE",
188 aWindow
, aWindow
->GetWindowHandle(),
189 gIMM32Handler
? gIMM32Handler
->mComposingWindow
: nullptr,
190 gIMM32Handler
&& gIMM32Handler
->mComposingWindow
?
191 IsComposingOnOurEditor() ? " (composing on editor)" :
192 " (composing on plug-in)" : ""));
193 if (!aForce
&& !IsComposingWindow(aWindow
)) {
197 nsIMEContext
IMEContext(aWindow
->GetWindowHandle());
198 bool associated
= IMEContext
.AssociateDefaultContext();
199 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
200 ("IMM32: CommitComposition, associated=%s\n",
201 associated
? "YES" : "NO"));
203 if (IMEContext
.IsValid()) {
204 ::ImmNotifyIME(IMEContext
.get(), NI_COMPOSITIONSTR
, CPS_COMPLETE
, 0);
205 ::ImmNotifyIME(IMEContext
.get(), NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
209 IMEContext
.Disassociate();
214 nsIMM32Handler::CancelComposition(nsWindow
* aWindow
, bool aForce
)
216 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
217 ("IMM32: CancelComposition, aForce=%s, aWindow=%p, hWnd=%08x, mComposingWindow=%p%s\n",
218 aForce
? "TRUE" : "FALSE",
219 aWindow
, aWindow
->GetWindowHandle(),
220 gIMM32Handler
? gIMM32Handler
->mComposingWindow
: nullptr,
221 gIMM32Handler
&& gIMM32Handler
->mComposingWindow
?
222 IsComposingOnOurEditor() ? " (composing on editor)" :
223 " (composing on plug-in)" : ""));
224 if (!aForce
&& !IsComposingWindow(aWindow
)) {
228 nsIMEContext
IMEContext(aWindow
->GetWindowHandle());
229 bool associated
= IMEContext
.AssociateDefaultContext();
230 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
231 ("IMM32: CancelComposition, associated=%s\n",
232 associated
? "YES" : "NO"));
234 if (IMEContext
.IsValid()) {
235 ::ImmNotifyIME(IMEContext
.get(), NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
239 IMEContext
.Disassociate();
244 nsIMM32Handler::ProcessInputLangChangeMessage(nsWindow
* aWindow
,
250 aResult
.mConsumed
= false;
251 // We don't need to create the instance of the handler here.
253 gIMM32Handler
->OnInputLangChange(aWindow
, wParam
, lParam
, aResult
);
255 InitKeyboardLayout(reinterpret_cast<HKL
>(lParam
));
256 // We can release the instance here, because the instance may be never
257 // used. E.g., the new keyboard layout may not use IME, or it may use TSF.
259 // Don't return as "processed", the messages should be processed on nsWindow
265 nsIMM32Handler::ProcessMessage(nsWindow
* aWindow
, UINT msg
,
266 WPARAM
&wParam
, LPARAM
&lParam
,
269 // XXX We store the composing window in mComposingWindow. If IME messages are
270 // sent to different window, we should commit the old transaction. And also
271 // if the new window handle is not focused, probably, we should not start
272 // the composition, however, such case should not be, it's just bad scenario.
274 // When a plug-in has focus or compsition, we should dispatch the IME events
276 if (aWindow
->PluginHasFocus() || IsComposingOnPlugin()) {
277 return ProcessMessageForPlugin(aWindow
, msg
, wParam
, lParam
, aResult
);
284 case WM_RBUTTONDOWN
: {
285 // We don't need to create the instance of the handler here.
286 if (!gIMM32Handler
) {
289 return gIMM32Handler
->OnMouseEvent(aWindow
, lParam
,
290 msg
== WM_LBUTTONDOWN
? IMEMOUSE_LDOWN
:
291 msg
== WM_MBUTTONDOWN
? IMEMOUSE_MDOWN
:
292 IMEMOUSE_RDOWN
, aResult
);
294 case WM_INPUTLANGCHANGE
:
295 return ProcessInputLangChangeMessage(aWindow
, wParam
, lParam
, aResult
);
296 case WM_IME_STARTCOMPOSITION
:
297 EnsureHandlerInstance();
298 return gIMM32Handler
->OnIMEStartComposition(aWindow
, aResult
);
299 case WM_IME_COMPOSITION
:
300 EnsureHandlerInstance();
301 return gIMM32Handler
->OnIMEComposition(aWindow
, wParam
, lParam
, aResult
);
302 case WM_IME_ENDCOMPOSITION
:
303 EnsureHandlerInstance();
304 return gIMM32Handler
->OnIMEEndComposition(aWindow
, aResult
);
306 return OnIMEChar(aWindow
, wParam
, lParam
, aResult
);
308 return OnIMENotify(aWindow
, wParam
, lParam
, aResult
);
310 EnsureHandlerInstance();
311 return gIMM32Handler
->OnIMERequest(aWindow
, wParam
, lParam
, aResult
);
313 return OnIMESelect(aWindow
, wParam
, lParam
, aResult
);
314 case WM_IME_SETCONTEXT
:
315 return OnIMESetContext(aWindow
, wParam
, lParam
, aResult
);
317 return OnKeyDownEvent(aWindow
, wParam
, lParam
, aResult
);
319 if (!gIMM32Handler
) {
322 return gIMM32Handler
->OnChar(aWindow
, wParam
, lParam
, aResult
);
329 nsIMM32Handler::ProcessMessageForPlugin(nsWindow
* aWindow
, UINT msg
,
330 WPARAM
&wParam
, LPARAM
&lParam
,
334 aResult
.mConsumed
= false;
336 case WM_INPUTLANGCHANGEREQUEST
:
337 case WM_INPUTLANGCHANGE
:
338 aWindow
->DispatchPluginEvent(msg
, wParam
, lParam
, false);
339 return ProcessInputLangChangeMessage(aWindow
, wParam
, lParam
, aResult
);
340 case WM_IME_COMPOSITION
:
341 EnsureHandlerInstance();
342 return gIMM32Handler
->OnIMECompositionOnPlugin(aWindow
, wParam
, lParam
,
344 case WM_IME_STARTCOMPOSITION
:
345 EnsureHandlerInstance();
346 return gIMM32Handler
->OnIMEStartCompositionOnPlugin(aWindow
, wParam
,
348 case WM_IME_ENDCOMPOSITION
:
349 EnsureHandlerInstance();
350 return gIMM32Handler
->OnIMEEndCompositionOnPlugin(aWindow
, wParam
, lParam
,
353 EnsureHandlerInstance();
354 return gIMM32Handler
->OnIMECharOnPlugin(aWindow
, wParam
, lParam
, aResult
);
355 case WM_IME_SETCONTEXT
:
356 return OnIMESetContextOnPlugin(aWindow
, wParam
, lParam
, aResult
);
358 if (!gIMM32Handler
) {
361 return gIMM32Handler
->OnCharOnPlugin(aWindow
, wParam
, lParam
, aResult
);
362 case WM_IME_COMPOSITIONFULL
:
369 aWindow
->DispatchPluginEvent(msg
, wParam
, lParam
, false);
375 /****************************************************************************
377 ****************************************************************************/
380 nsIMM32Handler::OnInputLangChange(nsWindow
* aWindow
,
385 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
386 ("IMM32: OnInputLangChange, hWnd=%08x, wParam=%08x, lParam=%08x\n",
387 aWindow
->GetWindowHandle(), wParam
, lParam
));
389 aWindow
->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION
);
390 NS_ASSERTION(!mIsComposing
, "ResetInputState failed");
393 HandleEndComposition(aWindow
);
396 aResult
.mConsumed
= false;
400 nsIMM32Handler::OnIMEStartComposition(nsWindow
* aWindow
,
403 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
404 ("IMM32: OnIMEStartComposition, hWnd=%08x, mIsComposing=%s\n",
405 aWindow
->GetWindowHandle(), mIsComposing
? "TRUE" : "FALSE"));
406 aResult
.mConsumed
= ShouldDrawCompositionStringOurselves();
408 NS_WARNING("Composition has been already started");
412 nsIMEContext
IMEContext(aWindow
->GetWindowHandle());
413 HandleStartComposition(aWindow
, IMEContext
);
418 nsIMM32Handler::OnIMEComposition(nsWindow
* aWindow
,
423 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
424 ("IMM32: OnIMEComposition, hWnd=%08x, lParam=%08x, mIsComposing=%s\n",
425 aWindow
->GetWindowHandle(), lParam
, mIsComposing
? "TRUE" : "FALSE"));
426 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
427 ("IMM32: OnIMEComposition, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s\n",
428 lParam
& GCS_RESULTSTR
? "YES" : "no",
429 lParam
& GCS_COMPSTR
? "YES" : "no",
430 lParam
& GCS_COMPATTR
? "YES" : "no",
431 lParam
& GCS_COMPCLAUSE
? "YES" : "no",
432 lParam
& GCS_CURSORPOS
? "YES" : "no"));
434 NS_PRECONDITION(!aWindow
->PluginHasFocus(),
435 "OnIMEComposition should not be called when a plug-in has focus");
437 nsIMEContext
IMEContext(aWindow
->GetWindowHandle());
438 aResult
.mConsumed
= HandleComposition(aWindow
, IMEContext
, lParam
);
443 nsIMM32Handler::OnIMEEndComposition(nsWindow
* aWindow
,
446 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
447 ("IMM32: OnIMEEndComposition, hWnd=%08x, mIsComposing=%s\n",
448 aWindow
->GetWindowHandle(), mIsComposing
? "TRUE" : "FALSE"));
450 aResult
.mConsumed
= ShouldDrawCompositionStringOurselves();
455 // Korean IME posts WM_IME_ENDCOMPOSITION first when we hit space during
456 // composition. Then, we should ignore the message and commit the composition
457 // string at following WM_IME_COMPOSITION.
459 if (WinUtils::PeekMessage(&compositionMsg
, aWindow
->GetWindowHandle(),
460 WM_IME_STARTCOMPOSITION
, WM_IME_COMPOSITION
,
462 compositionMsg
.message
== WM_IME_COMPOSITION
&&
463 IS_COMMITTING_LPARAM(compositionMsg
.lParam
)) {
464 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
465 ("IMM32: OnIMEEndComposition, WM_IME_ENDCOMPOSITION is followed by "
466 "WM_IME_COMPOSITION, ignoring the message..."));
470 // Otherwise, e.g., ChangJie doesn't post WM_IME_COMPOSITION before
471 // WM_IME_ENDCOMPOSITION when composition string becomes empty.
472 // Then, we should dispatch a compositionupdate event, a text event and
473 // a compositionend event.
474 // XXX Shouldn't we dispatch the text event with actual or latest composition
476 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
477 ("IMM32: OnIMEEndComposition, mCompositionString=\"%s\"%s",
478 NS_ConvertUTF16toUTF8(mCompositionString
).get(),
479 mCompositionString
.IsEmpty() ? "" : ", but canceling it..."));
481 mCompositionString
.Truncate();
483 nsIMEContext
IMEContext(aWindow
->GetWindowHandle());
484 DispatchTextEvent(aWindow
, IMEContext
, false);
486 HandleEndComposition(aWindow
);
492 nsIMM32Handler::OnIMEChar(nsWindow
* aWindow
,
497 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
498 ("IMM32: OnIMEChar, hWnd=%08x, char=%08x\n",
499 aWindow
->GetWindowHandle(), wParam
));
501 // We don't need to fire any text events from here. This method will be
502 // called when the composition string of the current IME is not drawn by us
503 // and some characters are committed. In that case, the committed string was
504 // processed in nsWindow::OnIMEComposition already.
506 // We need to consume the message so that Windows don't send two WM_CHAR msgs
507 aResult
.mConsumed
= true;
512 nsIMM32Handler::OnIMECompositionFull(nsWindow
* aWindow
,
515 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
516 ("IMM32: OnIMECompositionFull, hWnd=%08x\n",
517 aWindow
->GetWindowHandle()));
520 aResult
.mConsumed
= false;
525 nsIMM32Handler::OnIMENotify(nsWindow
* aWindow
,
532 case IMN_CHANGECANDIDATE
:
533 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
534 ("IMM32: OnIMENotify, hWnd=%08x, IMN_CHANGECANDIDATE, lParam=%08x\n",
535 aWindow
->GetWindowHandle(), lParam
));
537 case IMN_CLOSECANDIDATE
:
538 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
539 ("IMM32: OnIMENotify, hWnd=%08x, IMN_CLOSECANDIDATE, lParam=%08x\n",
540 aWindow
->GetWindowHandle(), lParam
));
542 case IMN_CLOSESTATUSWINDOW
:
543 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
544 ("IMM32: OnIMENotify, hWnd=%08x, IMN_CLOSESTATUSWINDOW\n",
545 aWindow
->GetWindowHandle()));
548 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
549 ("IMM32: OnIMENotify, hWnd=%08x, IMN_GUIDELINE\n",
550 aWindow
->GetWindowHandle()));
552 case IMN_OPENCANDIDATE
:
553 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
554 ("IMM32: OnIMENotify, hWnd=%08x, IMN_OPENCANDIDATE, lParam=%08x\n",
555 aWindow
->GetWindowHandle(), lParam
));
557 case IMN_OPENSTATUSWINDOW
:
558 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
559 ("IMM32: OnIMENotify, hWnd=%08x, IMN_OPENSTATUSWINDOW\n",
560 aWindow
->GetWindowHandle()));
562 case IMN_SETCANDIDATEPOS
:
563 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
564 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCANDIDATEPOS, lParam=%08x\n",
565 aWindow
->GetWindowHandle(), lParam
));
567 case IMN_SETCOMPOSITIONFONT
:
568 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
569 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONFONT\n",
570 aWindow
->GetWindowHandle()));
572 case IMN_SETCOMPOSITIONWINDOW
:
573 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
574 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONWINDOW\n",
575 aWindow
->GetWindowHandle()));
577 case IMN_SETCONVERSIONMODE
:
578 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
579 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCONVERSIONMODE\n",
580 aWindow
->GetWindowHandle()));
582 case IMN_SETOPENSTATUS
:
583 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
584 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETOPENSTATUS\n",
585 aWindow
->GetWindowHandle()));
587 case IMN_SETSENTENCEMODE
:
588 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
589 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETSENTENCEMODE\n",
590 aWindow
->GetWindowHandle()));
592 case IMN_SETSTATUSWINDOWPOS
:
593 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
594 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETSTATUSWINDOWPOS\n",
595 aWindow
->GetWindowHandle()));
598 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
599 ("IMM32: OnIMENotify, hWnd=%08x, IMN_PRIVATE\n",
600 aWindow
->GetWindowHandle()));
606 aResult
.mConsumed
= false;
611 nsIMM32Handler::OnIMERequest(nsWindow
* aWindow
,
617 case IMR_RECONVERTSTRING
:
618 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
619 ("IMM32: OnIMERequest, hWnd=%08x, IMR_RECONVERTSTRING\n",
620 aWindow
->GetWindowHandle()));
621 aResult
.mConsumed
= HandleReconvert(aWindow
, lParam
, &aResult
.mResult
);
623 case IMR_QUERYCHARPOSITION
:
624 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
625 ("IMM32: OnIMERequest, hWnd=%08x, IMR_QUERYCHARPOSITION\n",
626 aWindow
->GetWindowHandle()));
628 HandleQueryCharPosition(aWindow
, lParam
, &aResult
.mResult
);
630 case IMR_DOCUMENTFEED
:
631 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
632 ("IMM32: OnIMERequest, hWnd=%08x, IMR_DOCUMENTFEED\n",
633 aWindow
->GetWindowHandle()));
634 aResult
.mConsumed
= HandleDocumentFeed(aWindow
, lParam
, &aResult
.mResult
);
637 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
638 ("IMM32: OnIMERequest, hWnd=%08x, wParam=%08x\n",
639 aWindow
->GetWindowHandle(), wParam
));
640 aResult
.mConsumed
= false;
646 nsIMM32Handler::OnIMESelect(nsWindow
* aWindow
,
651 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
652 ("IMM32: OnIMESelect, hWnd=%08x, wParam=%08x, lParam=%08x\n",
653 aWindow
->GetWindowHandle(), wParam
, lParam
));
656 aResult
.mConsumed
= false;
661 nsIMM32Handler::OnIMESetContext(nsWindow
* aWindow
,
666 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
667 ("IMM32: OnIMESetContext, hWnd=%08x, %s, lParam=%08x\n",
668 aWindow
->GetWindowHandle(), wParam
? "Active" : "Deactive", lParam
));
670 aResult
.mConsumed
= false;
672 // NOTE: If the aWindow is top level window of the composing window because
673 // when a window on deactive window gets focus, WM_IME_SETCONTEXT (wParam is
674 // TRUE) is sent to the top level window first. After that,
675 // WM_IME_SETCONTEXT (wParam is FALSE) is sent to the top level window.
676 // Finally, WM_IME_SETCONTEXT (wParam is TRUE) is sent to the focused window.
677 // The top level window never becomes composing window, so, we can ignore
678 // the WM_IME_SETCONTEXT on the top level window.
679 if (IsTopLevelWindowOfComposition(aWindow
)) {
680 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
681 ("IMM32: OnIMESetContext, hWnd=%08x is top level window\n"));
685 // When IME context is activating on another window,
686 // we should commit the old composition on the old window.
687 bool cancelComposition
= false;
688 if (wParam
&& gIMM32Handler
) {
690 gIMM32Handler
->CommitCompositionOnPreviousWindow(aWindow
);
693 if (wParam
&& (lParam
& ISC_SHOWUICOMPOSITIONWINDOW
) &&
694 ShouldDrawCompositionStringOurselves()) {
695 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
696 ("IMM32: OnIMESetContext, ISC_SHOWUICOMPOSITIONWINDOW is removed\n"));
697 lParam
&= ~ISC_SHOWUICOMPOSITIONWINDOW
;
700 // We should sent WM_IME_SETCONTEXT to the DefWndProc here because the
701 // ancestor windows shouldn't receive this message. If they receive the
702 // message, we cannot know whether which window is the target of the message.
703 aResult
.mResult
= ::DefWindowProc(aWindow
->GetWindowHandle(),
704 WM_IME_SETCONTEXT
, wParam
, lParam
);
706 // Cancel composition on the new window if we committed our composition on
708 if (cancelComposition
) {
709 CancelComposition(aWindow
, true);
712 aResult
.mConsumed
= true;
717 nsIMM32Handler::OnChar(nsWindow
* aWindow
,
722 // The return value must be same as aResult.mConsumed because only when we
723 // consume the message, the caller shouldn't do anything anymore but
724 // otherwise, the caller should handle the message.
725 aResult
.mConsumed
= false;
726 if (IsIMECharRecordsEmpty()) {
727 return aResult
.mConsumed
;
731 DequeueIMECharRecords(recWParam
, recLParam
);
732 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
733 ("IMM32: OnChar, aWindow=%p, wParam=%08x, lParam=%08x,\n",
734 aWindow
->GetWindowHandle(), wParam
, lParam
));
735 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
736 (" recorded: wParam=%08x, lParam=%08x\n",
737 recWParam
, recLParam
));
738 // If an unexpected char message comes, we should reset the records,
739 // of course, this shouldn't happen.
740 if (recWParam
!= wParam
|| recLParam
!= lParam
) {
741 ResetIMECharRecords();
742 return aResult
.mConsumed
;
744 // Eat the char message which is caused by WM_IME_CHAR because we should
745 // have processed the IME messages, so, this message could be come from
746 // a windowless plug-in.
747 aResult
.mConsumed
= true;
748 return aResult
.mConsumed
;
751 /****************************************************************************
752 * message handlers for plug-in
753 ****************************************************************************/
756 nsIMM32Handler::OnIMEStartCompositionOnPlugin(nsWindow
* aWindow
,
761 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
762 ("IMM32: OnIMEStartCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s\n",
763 aWindow
->GetWindowHandle(), mIsComposingOnPlugin
? "TRUE" : "FALSE"));
764 mIsComposingOnPlugin
= true;
765 mComposingWindow
= aWindow
;
767 aWindow
->DispatchPluginEvent(WM_IME_STARTCOMPOSITION
, wParam
, lParam
,
773 nsIMM32Handler::OnIMECompositionOnPlugin(nsWindow
* aWindow
,
778 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
779 ("IMM32: OnIMECompositionOnPlugin, hWnd=%08x, lParam=%08x, mIsComposingOnPlugin=%s\n",
780 aWindow
->GetWindowHandle(), lParam
,
781 mIsComposingOnPlugin
? "TRUE" : "FALSE"));
782 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
783 ("IMM32: OnIMECompositionOnPlugin, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s\n",
784 lParam
& GCS_RESULTSTR
? "YES" : "no",
785 lParam
& GCS_COMPSTR
? "YES" : "no",
786 lParam
& GCS_COMPATTR
? "YES" : "no",
787 lParam
& GCS_COMPCLAUSE
? "YES" : "no",
788 lParam
& GCS_CURSORPOS
? "YES" : "no"));
789 // We should end composition if there is a committed string.
790 if (IS_COMMITTING_LPARAM(lParam
)) {
791 mIsComposingOnPlugin
= false;
792 mComposingWindow
= nullptr;
794 // Continue composition if there is still a string being composed.
795 if (IS_COMPOSING_LPARAM(lParam
)) {
796 mIsComposingOnPlugin
= true;
797 mComposingWindow
= aWindow
;
800 aWindow
->DispatchPluginEvent(WM_IME_COMPOSITION
, wParam
, lParam
, true);
805 nsIMM32Handler::OnIMEEndCompositionOnPlugin(nsWindow
* aWindow
,
810 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
811 ("IMM32: OnIMEEndCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s\n",
812 aWindow
->GetWindowHandle(), mIsComposingOnPlugin
? "TRUE" : "FALSE"));
814 mIsComposingOnPlugin
= false;
815 mComposingWindow
= nullptr;
817 aWindow
->DispatchPluginEvent(WM_IME_ENDCOMPOSITION
, wParam
, lParam
,
823 nsIMM32Handler::OnIMECharOnPlugin(nsWindow
* aWindow
,
828 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
829 ("IMM32: OnIMECharOnPlugin, hWnd=%08x, char=%08x, scancode=%08x\n",
830 aWindow
->GetWindowHandle(), wParam
, lParam
));
833 aWindow
->DispatchPluginEvent(WM_IME_CHAR
, wParam
, lParam
, true);
835 if (!aResult
.mConsumed
) {
836 // Record the WM_CHAR messages which are going to be coming.
837 EnsureHandlerInstance();
838 EnqueueIMECharRecords(wParam
, lParam
);
844 nsIMM32Handler::OnIMESetContextOnPlugin(nsWindow
* aWindow
,
849 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
850 ("IMM32: OnIMESetContextOnPlugin, hWnd=%08x, %s, lParam=%08x\n",
851 aWindow
->GetWindowHandle(), wParam
? "Active" : "Deactive", lParam
));
853 // If the IME context becomes active on a plug-in, we should commit
854 // our composition. And also we should cancel the composition on new
855 // window. Note that if IsTopLevelWindowOfComposition(aWindow) returns
856 // true, we should ignore the message here, see the comment in
857 // OnIMESetContext() for the detail.
858 if (wParam
&& gIMM32Handler
&& !IsTopLevelWindowOfComposition(aWindow
)) {
859 if (gIMM32Handler
->CommitCompositionOnPreviousWindow(aWindow
)) {
860 CancelComposition(aWindow
);
864 // Dispatch message to the plug-in.
865 // XXX When a windowless plug-in gets focus, we should send
867 aWindow
->DispatchPluginEvent(WM_IME_SETCONTEXT
, wParam
, lParam
, false);
869 // We should send WM_IME_SETCONTEXT to the DefWndProc here. It shouldn't
870 // be received on ancestor windows, see OnIMESetContext() for the detail.
871 aResult
.mResult
= ::DefWindowProc(aWindow
->GetWindowHandle(),
872 WM_IME_SETCONTEXT
, wParam
, lParam
);
874 // Don't synchronously dispatch the pending events when we receive
875 // WM_IME_SETCONTEXT because we get it during plugin destruction.
877 aResult
.mConsumed
= true;
882 nsIMM32Handler::OnCharOnPlugin(nsWindow
* aWindow
,
887 // We should never consume char message on windowless plugin.
888 aResult
.mConsumed
= false;
889 if (IsIMECharRecordsEmpty()) {
895 DequeueIMECharRecords(recWParam
, recLParam
);
896 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
897 ("IMM32: OnCharOnPlugin, aWindow=%p, wParam=%08x, lParam=%08x,\n",
898 aWindow
->GetWindowHandle(), wParam
, lParam
));
899 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
900 (" recorded: wParam=%08x, lParam=%08x\n",
901 recWParam
, recLParam
));
902 // If an unexpected char message comes, we should reset the records,
903 // of course, this shouldn't happen.
904 if (recWParam
!= wParam
|| recLParam
!= lParam
) {
905 ResetIMECharRecords();
907 // WM_CHAR on plug-in is always handled by nsWindow.
911 /****************************************************************************
913 ****************************************************************************/
916 nsIMM32Handler::HandleStartComposition(nsWindow
* aWindow
,
917 const nsIMEContext
&aIMEContext
)
919 NS_PRECONDITION(!mIsComposing
,
920 "HandleStartComposition is called but mIsComposing is TRUE");
921 NS_PRECONDITION(!aWindow
->PluginHasFocus(),
922 "HandleStartComposition should not be called when a plug-in has focus");
924 WidgetQueryContentEvent
selection(true, NS_QUERY_SELECTED_TEXT
, aWindow
);
925 nsIntPoint
point(0, 0);
926 aWindow
->InitEvent(selection
, &point
);
927 aWindow
->DispatchWindowEvent(&selection
);
928 if (!selection
.mSucceeded
) {
929 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
930 ("IMM32: HandleStartComposition, FAILED (NS_QUERY_SELECTED_TEXT)\n"));
934 mCompositionStart
= selection
.mReply
.mOffset
;
935 mLastDispatchedCompositionString
.Truncate();
937 WidgetCompositionEvent
event(true, NS_COMPOSITION_START
, aWindow
);
938 aWindow
->InitEvent(event
, &point
);
939 aWindow
->DispatchWindowEvent(&event
);
941 SetIMERelatedWindowsPos(aWindow
, aIMEContext
);
944 mComposingWindow
= aWindow
;
946 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
947 ("IMM32: HandleStartComposition, START composition, mCompositionStart=%ld\n",
952 nsIMM32Handler::HandleComposition(nsWindow
* aWindow
,
953 const nsIMEContext
&aIMEContext
,
956 NS_PRECONDITION(!aWindow
->PluginHasFocus(),
957 "HandleComposition should not be called when a plug-in has focus");
960 // MS-IME 95/97/98/2000 may send WM_IME_COMPOSITION with non-conversion
961 // mode before it send WM_IME_STARTCOMPOSITION.
962 // However, ATOK sends a WM_IME_COMPOSITION before WM_IME_STARTCOMPOSITION,
963 // and if we access ATOK via some APIs, ATOK will sometimes fail to
964 // initialize its state. If WM_IME_STARTCOMPOSITION is already in the
965 // message queue, we should ignore the strange WM_IME_COMPOSITION message and
966 // skip to the next. So, we should look for next composition message
967 // (WM_IME_STARTCOMPOSITION or WM_IME_ENDCOMPOSITION or WM_IME_COMPOSITION),
968 // and if it's WM_IME_STARTCOMPOSITION, and one more next composition message
969 // is WM_IME_COMPOSITION, current IME is ATOK, probably. Otherwise, we
970 // should start composition forcibly.
973 HWND wnd
= aWindow
->GetWindowHandle();
974 if (WinUtils::PeekMessage(&msg1
, wnd
, WM_IME_STARTCOMPOSITION
,
975 WM_IME_COMPOSITION
, PM_NOREMOVE
) &&
976 msg1
.message
== WM_IME_STARTCOMPOSITION
&&
977 WinUtils::PeekMessage(&msg2
, wnd
, WM_IME_ENDCOMPOSITION
,
978 WM_IME_COMPOSITION
, PM_NOREMOVE
) &&
979 msg2
.message
== WM_IME_COMPOSITION
) {
980 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
981 ("IMM32: HandleComposition, Ignores due to find a WM_IME_STARTCOMPOSITION\n"));
982 return ShouldDrawCompositionStringOurselves();
986 bool startCompositionMessageHasBeenSent
= mIsComposing
;
989 // This catches a fixed result
991 if (IS_COMMITTING_LPARAM(lParam
)) {
993 HandleStartComposition(aWindow
, aIMEContext
);
996 GetCompositionString(aIMEContext
, GCS_RESULTSTR
);
998 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
999 ("IMM32: HandleComposition, GCS_RESULTSTR\n"));
1001 DispatchTextEvent(aWindow
, aIMEContext
, false);
1002 HandleEndComposition(aWindow
);
1004 if (!IS_COMPOSING_LPARAM(lParam
)) {
1005 return ShouldDrawCompositionStringOurselves();
1011 // This provides us with a composition string
1013 if (!mIsComposing
) {
1014 HandleStartComposition(aWindow
, aIMEContext
);
1017 //--------------------------------------------------------
1018 // 1. Get GCS_COMPSTR
1019 //--------------------------------------------------------
1020 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1021 ("IMM32: HandleComposition, GCS_COMPSTR\n"));
1023 GetCompositionString(aIMEContext
, GCS_COMPSTR
);
1025 if (!IS_COMPOSING_LPARAM(lParam
)) {
1026 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1027 ("IMM32: HandleComposition, lParam doesn't indicate composing, "
1028 "mCompositionString=\"%s\", mLastDispatchedCompositionString=\"%s\"",
1029 NS_ConvertUTF16toUTF8(mCompositionString
).get(),
1030 NS_ConvertUTF16toUTF8(mLastDispatchedCompositionString
).get()));
1032 // If composition string isn't changed, we can trust the lParam.
1033 // So, we need to do nothing.
1034 if (mLastDispatchedCompositionString
== mCompositionString
) {
1035 return ShouldDrawCompositionStringOurselves();
1038 // IME may send WM_IME_COMPOSITION without composing lParam values
1039 // when composition string becomes empty (e.g., using Backspace key).
1040 // If composition string is empty, we should dispatch a text event with
1042 if (mCompositionString
.IsEmpty()) {
1043 DispatchTextEvent(aWindow
, aIMEContext
, false);
1044 return ShouldDrawCompositionStringOurselves();
1047 // Otherwise, we cannot trust the lParam value. We might need to
1048 // dispatch text event with the latest composition string information.
1051 // See https://bugzilla.mozilla.org/show_bug.cgi?id=296339
1052 if (mCompositionString
.IsEmpty() && !startCompositionMessageHasBeenSent
) {
1053 // In this case, maybe, the sender is MSPinYin. That sends *only*
1054 // WM_IME_COMPOSITION with GCS_COMP* and GCS_RESULT* when
1055 // user inputted the Chinese full stop. So, that doesn't send
1056 // WM_IME_STARTCOMPOSITION and WM_IME_ENDCOMPOSITION.
1057 // If WM_IME_STARTCOMPOSITION was not sent and the composition
1058 // string is null (it indicates the composition transaction ended),
1059 // WM_IME_ENDCOMPOSITION may not be sent. If so, we cannot run
1060 // HandleEndComposition() in other place.
1061 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1062 ("IMM32: HandleComposition, Aborting GCS_COMPSTR\n"));
1063 HandleEndComposition(aWindow
);
1064 return IS_COMMITTING_LPARAM(lParam
);
1067 //--------------------------------------------------------
1068 // 2. Get GCS_COMPCLAUSE
1069 //--------------------------------------------------------
1070 long clauseArrayLength
=
1071 ::ImmGetCompositionStringW(aIMEContext
.get(), GCS_COMPCLAUSE
, nullptr, 0);
1072 clauseArrayLength
/= sizeof(uint32_t);
1074 if (clauseArrayLength
> 0) {
1075 nsresult rv
= EnsureClauseArray(clauseArrayLength
);
1076 NS_ENSURE_SUCCESS(rv
, false);
1078 // Intelligent ABC IME (Simplified Chinese IME, the code page is 936)
1079 // will crash in ImmGetCompositionStringW for GCS_COMPCLAUSE (bug 424663).
1080 // See comment 35 of the bug for the detail. Therefore, we should use A
1081 // API for it, however, we should not kill Unicode support on all IMEs.
1082 bool useA_API
= !(sIMEProperty
& IME_PROP_UNICODE
);
1084 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1085 ("IMM32: HandleComposition, GCS_COMPCLAUSE, useA_API=%s\n",
1086 useA_API
? "TRUE" : "FALSE"));
1088 long clauseArrayLength2
=
1090 ::ImmGetCompositionStringA(aIMEContext
.get(), GCS_COMPCLAUSE
,
1091 mClauseArray
.Elements(),
1092 mClauseArray
.Capacity() * sizeof(uint32_t)) :
1093 ::ImmGetCompositionStringW(aIMEContext
.get(), GCS_COMPCLAUSE
,
1094 mClauseArray
.Elements(),
1095 mClauseArray
.Capacity() * sizeof(uint32_t));
1096 clauseArrayLength2
/= sizeof(uint32_t);
1098 if (clauseArrayLength
!= clauseArrayLength2
) {
1099 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1100 ("IMM32: HandleComposition, GCS_COMPCLAUSE, clauseArrayLength=%ld but clauseArrayLength2=%ld\n",
1101 clauseArrayLength
, clauseArrayLength2
));
1102 if (clauseArrayLength
> clauseArrayLength2
)
1103 clauseArrayLength
= clauseArrayLength2
;
1107 // Convert each values of sIMECompClauseArray. The values mean offset of
1108 // the clauses in ANSI string. But we need the values in Unicode string.
1109 nsAutoCString compANSIStr
;
1110 if (ConvertToANSIString(mCompositionString
, GetKeyboardCodePage(),
1112 uint32_t maxlen
= compANSIStr
.Length();
1113 mClauseArray
[0] = 0; // first value must be 0
1114 for (int32_t i
= 1; i
< clauseArrayLength
; i
++) {
1115 uint32_t len
= std::min(mClauseArray
[i
], maxlen
);
1116 mClauseArray
[i
] = ::MultiByteToWideChar(GetKeyboardCodePage(),
1118 (LPCSTR
)compANSIStr
.get(),
1124 // compClauseArrayLength may be negative. I.e., ImmGetCompositionStringW
1125 // may return an error code.
1126 mClauseArray
.SetLength(std::max
<long>(0, clauseArrayLength
));
1128 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1129 ("IMM32: HandleComposition, GCS_COMPCLAUSE, mClauseLength=%ld\n",
1130 mClauseArray
.Length()));
1132 //--------------------------------------------------------
1133 // 3. Get GCS_COMPATTR
1134 //--------------------------------------------------------
1135 // This provides us with the attribute string necessary
1136 // for doing hiliting
1137 long attrArrayLength
=
1138 ::ImmGetCompositionStringW(aIMEContext
.get(), GCS_COMPATTR
, nullptr, 0);
1139 attrArrayLength
/= sizeof(uint8_t);
1141 if (attrArrayLength
> 0) {
1142 nsresult rv
= EnsureAttributeArray(attrArrayLength
);
1143 NS_ENSURE_SUCCESS(rv
, false);
1145 ::ImmGetCompositionStringW(aIMEContext
.get(), GCS_COMPATTR
,
1146 mAttributeArray
.Elements(),
1147 mAttributeArray
.Capacity() * sizeof(uint8_t));
1150 // attrStrLen may be negative. I.e., ImmGetCompositionStringW may return an
1152 mAttributeArray
.SetLength(std::max
<long>(0, attrArrayLength
));
1154 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1155 ("IMM32: HandleComposition, GCS_COMPATTR, mAttributeLength=%ld\n",
1156 mAttributeArray
.Length()));
1158 //--------------------------------------------------------
1159 // 4. Get GCS_CURSOPOS
1160 //--------------------------------------------------------
1161 // Some IMEs (e.g., the standard IME for Korean) don't have caret position.
1162 if (lParam
& GCS_CURSORPOS
) {
1164 ::ImmGetCompositionStringW(aIMEContext
.get(), GCS_CURSORPOS
, nullptr, 0);
1165 if (mCursorPosition
< 0) {
1166 mCursorPosition
= NO_IME_CARET
; // The result is error
1169 mCursorPosition
= NO_IME_CARET
;
1172 NS_ASSERTION(mCursorPosition
<= (long)mCompositionString
.Length(),
1175 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1176 ("IMM32: HandleComposition, GCS_CURSORPOS, mCursorPosition=%d\n",
1179 //--------------------------------------------------------
1180 // 5. Send the text event
1181 //--------------------------------------------------------
1182 DispatchTextEvent(aWindow
, aIMEContext
);
1184 return ShouldDrawCompositionStringOurselves();
1188 nsIMM32Handler::HandleEndComposition(nsWindow
* aWindow
)
1190 NS_PRECONDITION(mIsComposing
,
1191 "HandleEndComposition is called but mIsComposing is FALSE");
1192 NS_PRECONDITION(!aWindow
->PluginHasFocus(),
1193 "HandleComposition should not be called when a plug-in has focus");
1195 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1196 ("IMM32: HandleEndComposition\n"));
1198 WidgetCompositionEvent
event(true, NS_COMPOSITION_END
, aWindow
);
1199 nsIntPoint
point(0, 0);
1201 if (mNativeCaretIsCreated
) {
1203 mNativeCaretIsCreated
= false;
1206 aWindow
->InitEvent(event
, &point
);
1207 // The last dispatched composition string must be the committed string.
1208 event
.data
= mLastDispatchedCompositionString
;
1209 aWindow
->DispatchWindowEvent(&event
);
1210 mIsComposing
= false;
1211 mComposingWindow
= nullptr;
1212 mLastDispatchedCompositionString
.Truncate();
1216 DumpReconvertString(RECONVERTSTRING
* aReconv
)
1218 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1219 (" dwSize=%ld, dwVersion=%ld, dwStrLen=%ld, dwStrOffset=%ld\n",
1220 aReconv
->dwSize
, aReconv
->dwVersion
,
1221 aReconv
->dwStrLen
, aReconv
->dwStrOffset
));
1222 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1223 (" dwCompStrLen=%ld, dwCompStrOffset=%ld, dwTargetStrLen=%ld, dwTargetStrOffset=%ld\n",
1224 aReconv
->dwCompStrLen
, aReconv
->dwCompStrOffset
,
1225 aReconv
->dwTargetStrLen
, aReconv
->dwTargetStrOffset
));
1226 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1227 (" result str=\"%s\"\n",
1228 NS_ConvertUTF16toUTF8(
1229 nsAutoString((PRUnichar
*)((char*)(aReconv
) + aReconv
->dwStrOffset
),
1230 aReconv
->dwStrLen
)).get()));
1234 nsIMM32Handler::HandleReconvert(nsWindow
* aWindow
,
1239 RECONVERTSTRING
* pReconv
= reinterpret_cast<RECONVERTSTRING
*>(lParam
);
1241 WidgetQueryContentEvent
selection(true, NS_QUERY_SELECTED_TEXT
, aWindow
);
1242 nsIntPoint
point(0, 0);
1243 aWindow
->InitEvent(selection
, &point
);
1244 aWindow
->DispatchWindowEvent(&selection
);
1245 if (!selection
.mSucceeded
) {
1246 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1247 ("IMM32: HandleReconvert, FAILED (NS_QUERY_SELECTED_TEXT)\n"));
1251 uint32_t len
= selection
.mReply
.mString
.Length();
1252 uint32_t needSize
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
1255 // Return need size to reconvert.
1257 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1258 ("IMM32: HandleReconvert, There are not selected text\n"));
1261 *oResult
= needSize
;
1262 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1263 ("IMM32: HandleReconvert, SUCCEEDED result=%ld\n",
1268 if (pReconv
->dwSize
< needSize
) {
1269 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1270 ("IMM32: HandleReconvert, FAILED pReconv->dwSize=%ld, needSize=%ld\n",
1271 pReconv
->dwSize
, needSize
));
1275 *oResult
= needSize
;
1277 // Fill reconvert struct
1278 pReconv
->dwVersion
= 0;
1279 pReconv
->dwStrLen
= len
;
1280 pReconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
1281 pReconv
->dwCompStrLen
= len
;
1282 pReconv
->dwCompStrOffset
= 0;
1283 pReconv
->dwTargetStrLen
= len
;
1284 pReconv
->dwTargetStrOffset
= 0;
1286 ::CopyMemory(reinterpret_cast<LPVOID
>(lParam
+ sizeof(RECONVERTSTRING
)),
1287 selection
.mReply
.mString
.get(), len
* sizeof(WCHAR
));
1289 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1290 ("IMM32: HandleReconvert, SUCCEEDED result=%ld\n",
1292 DumpReconvertString(pReconv
);
1298 nsIMM32Handler::HandleQueryCharPosition(nsWindow
* aWindow
,
1302 uint32_t len
= mIsComposing
? mCompositionString
.Length() : 0;
1304 IMECHARPOSITION
* pCharPosition
= reinterpret_cast<IMECHARPOSITION
*>(lParam
);
1305 if (!pCharPosition
) {
1306 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1307 ("IMM32: HandleQueryCharPosition, FAILED (pCharPosition is null)\n"));
1310 if (pCharPosition
->dwSize
< sizeof(IMECHARPOSITION
)) {
1311 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1312 ("IMM32: HandleReconvert, FAILED, pCharPosition->dwSize=%ld, sizeof(IMECHARPOSITION)=%ld\n",
1313 pCharPosition
->dwSize
, sizeof(IMECHARPOSITION
)));
1316 if (::GetFocus() != aWindow
->GetWindowHandle()) {
1317 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1318 ("IMM32: HandleReconvert, FAILED, ::GetFocus()=%08x, OurWindowHandle=%08x\n",
1319 ::GetFocus(), aWindow
->GetWindowHandle()));
1322 if (pCharPosition
->dwCharPos
> len
) {
1323 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1324 ("IMM32: HandleQueryCharPosition, FAILED, pCharPosition->dwCharPos=%ld, len=%ld\n",
1325 pCharPosition
->dwCharPos
, len
));
1331 GetCharacterRectOfSelectedTextAt(aWindow
, pCharPosition
->dwCharPos
, r
);
1332 NS_ENSURE_TRUE(ret
, false);
1334 nsIntRect screenRect
;
1335 // We always need top level window that is owner window of the popup window
1336 // even if the content of the popup window has focus.
1337 ResolveIMECaretPos(aWindow
->GetTopLevelWindow(false),
1338 r
, nullptr, screenRect
);
1339 pCharPosition
->pt
.x
= screenRect
.x
;
1340 pCharPosition
->pt
.y
= screenRect
.y
;
1342 pCharPosition
->cLineHeight
= r
.height
;
1344 // XXX we should use NS_QUERY_EDITOR_RECT event here.
1345 ::GetWindowRect(aWindow
->GetWindowHandle(), &pCharPosition
->rcDocument
);
1349 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1350 ("IMM32: HandleQueryCharPosition, SUCCEEDED\n"));
1355 nsIMM32Handler::HandleDocumentFeed(nsWindow
* aWindow
,
1360 RECONVERTSTRING
* pReconv
= reinterpret_cast<RECONVERTSTRING
*>(lParam
);
1362 nsIntPoint
point(0, 0);
1364 bool hasCompositionString
=
1365 mIsComposing
&& ShouldDrawCompositionStringOurselves();
1367 int32_t targetOffset
, targetLength
;
1368 if (!hasCompositionString
) {
1369 WidgetQueryContentEvent
selection(true, NS_QUERY_SELECTED_TEXT
, aWindow
);
1370 aWindow
->InitEvent(selection
, &point
);
1371 aWindow
->DispatchWindowEvent(&selection
);
1372 if (!selection
.mSucceeded
) {
1373 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1374 ("IMM32: HandleDocumentFeed, FAILED (NS_QUERY_SELECTED_TEXT)\n"));
1377 targetOffset
= int32_t(selection
.mReply
.mOffset
);
1378 targetLength
= int32_t(selection
.mReply
.mString
.Length());
1380 targetOffset
= int32_t(mCompositionStart
);
1381 targetLength
= int32_t(mCompositionString
.Length());
1384 // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
1385 // we cannot support this message when the current offset is larger than
1387 if (targetOffset
< 0 || targetLength
< 0 ||
1388 targetOffset
+ targetLength
< 0) {
1389 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1390 ("IMM32: HandleDocumentFeed, FAILED (The selection is out of range)\n"));
1394 // Get all contents of the focused editor.
1395 WidgetQueryContentEvent
textContent(true, NS_QUERY_TEXT_CONTENT
, aWindow
);
1396 textContent
.InitForQueryTextContent(0, UINT32_MAX
);
1397 aWindow
->InitEvent(textContent
, &point
);
1398 aWindow
->DispatchWindowEvent(&textContent
);
1399 if (!textContent
.mSucceeded
) {
1400 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1401 ("IMM32: HandleDocumentFeed, FAILED (NS_QUERY_TEXT_CONTENT)\n"));
1405 nsAutoString
str(textContent
.mReply
.mString
);
1406 if (targetOffset
> int32_t(str
.Length())) {
1407 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1408 ("IMM32: HandleDocumentFeed, FAILED (The caret offset is invalid)\n"));
1412 // Get the focused paragraph, we decide that it starts from the previous CRLF
1413 // (or start of the editor) to the next one (or the end of the editor).
1414 int32_t paragraphStart
= str
.RFind("\n", false, targetOffset
, -1) + 1;
1415 int32_t paragraphEnd
=
1416 str
.Find("\r", false, targetOffset
+ targetLength
, -1);
1417 if (paragraphEnd
< 0) {
1418 paragraphEnd
= str
.Length();
1420 nsDependentSubstring
paragraph(str
, paragraphStart
,
1421 paragraphEnd
- paragraphStart
);
1423 uint32_t len
= paragraph
.Length();
1424 uint32_t needSize
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
1427 *oResult
= needSize
;
1428 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1429 ("IMM32: HandleDocumentFeed, SUCCEEDED result=%ld\n",
1434 if (pReconv
->dwSize
< needSize
) {
1435 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1436 ("IMM32: HandleDocumentFeed, FAILED pReconv->dwSize=%ld, needSize=%ld\n",
1437 pReconv
->dwSize
, needSize
));
1441 // Fill reconvert struct
1442 pReconv
->dwVersion
= 0;
1443 pReconv
->dwStrLen
= len
;
1444 pReconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
1445 if (hasCompositionString
) {
1446 pReconv
->dwCompStrLen
= targetLength
;
1447 pReconv
->dwCompStrOffset
=
1448 (targetOffset
- paragraphStart
) * sizeof(WCHAR
);
1449 // Set composition target clause information
1450 uint32_t offset
, length
;
1451 if (!GetTargetClauseRange(&offset
, &length
)) {
1452 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1453 ("IMM32: HandleDocumentFeed, FAILED, by GetTargetClauseRange\n"));
1456 pReconv
->dwTargetStrLen
= length
;
1457 pReconv
->dwTargetStrOffset
= (offset
- paragraphStart
) * sizeof(WCHAR
);
1459 pReconv
->dwTargetStrLen
= targetLength
;
1460 pReconv
->dwTargetStrOffset
=
1461 (targetOffset
- paragraphStart
) * sizeof(WCHAR
);
1462 // There is no composition string, so, the length is zero but we should
1463 // set the cursor offset to the composition str offset.
1464 pReconv
->dwCompStrLen
= 0;
1465 pReconv
->dwCompStrOffset
= pReconv
->dwTargetStrOffset
;
1468 *oResult
= needSize
;
1469 ::CopyMemory(reinterpret_cast<LPVOID
>(lParam
+ sizeof(RECONVERTSTRING
)),
1470 paragraph
.BeginReading(), len
* sizeof(WCHAR
));
1472 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1473 ("IMM32: HandleDocumentFeed, SUCCEEDED result=%ld\n",
1475 DumpReconvertString(pReconv
);
1481 nsIMM32Handler::CommitCompositionOnPreviousWindow(nsWindow
* aWindow
)
1483 if (!mComposingWindow
|| mComposingWindow
== aWindow
) {
1487 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1488 ("IMM32: CommitCompositionOnPreviousWindow, mIsComposing=%s, mIsComposingOnPlugin=%s\n",
1489 mIsComposing
? "TRUE" : "FALSE", mIsComposingOnPlugin
? "TRUE" : "FALSE"));
1491 // If we have composition, we should dispatch composition events internally.
1493 nsIMEContext
IMEContext(mComposingWindow
->GetWindowHandle());
1494 NS_ASSERTION(IMEContext
.IsValid(), "IME context must be valid");
1496 DispatchTextEvent(mComposingWindow
, IMEContext
, false);
1497 HandleEndComposition(mComposingWindow
);
1501 // XXX When plug-in has composition, we should commit composition on the
1502 // plug-in. However, we need some more work for that.
1503 return mIsComposingOnPlugin
;
1507 PlatformToNSAttr(uint8_t aAttr
)
1511 case ATTR_INPUT_ERROR
:
1512 // case ATTR_FIXEDCONVERTED:
1514 return NS_TEXTRANGE_RAWINPUT
;
1515 case ATTR_CONVERTED
:
1516 return NS_TEXTRANGE_CONVERTEDTEXT
;
1517 case ATTR_TARGET_NOTCONVERTED
:
1518 return NS_TEXTRANGE_SELECTEDRAWTEXT
;
1519 case ATTR_TARGET_CONVERTED
:
1520 return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT
;
1522 NS_ASSERTION(false, "unknown attribute");
1523 return NS_TEXTRANGE_CARETPOSITION
;
1529 GetRangeTypeName(uint32_t aRangeType
)
1531 switch (aRangeType
) {
1532 case NS_TEXTRANGE_RAWINPUT
:
1533 return "NS_TEXTRANGE_RAWINPUT";
1534 case NS_TEXTRANGE_CONVERTEDTEXT
:
1535 return "NS_TEXTRANGE_CONVERTEDTEXT";
1536 case NS_TEXTRANGE_SELECTEDRAWTEXT
:
1537 return "NS_TEXTRANGE_SELECTEDRAWTEXT";
1538 case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT
:
1539 return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT";
1540 case NS_TEXTRANGE_CARETPOSITION
:
1541 return "NS_TEXTRANGE_CARETPOSITION";
1543 return "UNKNOWN SELECTION TYPE!!";
1549 nsIMM32Handler::DispatchTextEvent(nsWindow
* aWindow
,
1550 const nsIMEContext
&aIMEContext
,
1553 NS_ASSERTION(mIsComposing
, "conflict state");
1554 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1555 ("IMM32: DispatchTextEvent, aCheckAttr=%s\n",
1556 aCheckAttr
? "TRUE": "FALSE"));
1558 // If we don't need to draw composition string ourselves and this is not
1559 // commit event (i.e., under composing), we don't need to fire text event
1560 // during composing.
1561 if (aCheckAttr
&& !ShouldDrawCompositionStringOurselves()) {
1562 // But we need to adjust composition window pos and native caret pos, here.
1563 SetIMERelatedWindowsPos(aWindow
, aIMEContext
);
1567 nsRefPtr
<nsWindow
> kungFuDeathGrip(aWindow
);
1569 nsIntPoint
point(0, 0);
1571 if (mCompositionString
!= mLastDispatchedCompositionString
) {
1572 WidgetCompositionEvent
compositionUpdate(true, NS_COMPOSITION_UPDATE
,
1574 aWindow
->InitEvent(compositionUpdate
, &point
);
1575 compositionUpdate
.data
= mCompositionString
;
1576 mLastDispatchedCompositionString
= mCompositionString
;
1578 aWindow
->DispatchWindowEvent(&compositionUpdate
);
1580 if (!mIsComposing
|| aWindow
->Destroyed()) {
1583 SetIMERelatedWindowsPos(aWindow
, aIMEContext
);
1586 WidgetTextEvent
event(true, NS_TEXT_TEXT
, aWindow
);
1588 aWindow
->InitEvent(event
, &point
);
1590 nsAutoTArray
<TextRange
, 4> textRanges
;
1593 SetTextRangeList(textRanges
);
1596 event
.rangeCount
= textRanges
.Length();
1597 event
.rangeArray
= textRanges
.Elements();
1599 event
.theText
= mCompositionString
.get();
1601 aWindow
->DispatchWindowEvent(&event
);
1603 SetIMERelatedWindowsPos(aWindow
, aIMEContext
);
1607 nsIMM32Handler::SetTextRangeList(nsTArray
<TextRange
> &aTextRangeList
)
1609 // Sogou (Simplified Chinese IME) returns contradictory values: The cursor
1610 // position is actual cursor position. However, other values (composition
1611 // string and attributes) are empty. So, if you want to remove following
1612 // assertion, be careful.
1613 NS_ASSERTION(ShouldDrawCompositionStringOurselves(),
1614 "SetTextRangeList is called when we don't need to fire text event");
1617 if (mClauseArray
.Length() == 0) {
1618 // Some IMEs don't return clause array information, then, we assume that
1619 // all characters in the composition string are in one clause.
1620 range
.mStartOffset
= 0;
1621 range
.mEndOffset
= mCompositionString
.Length();
1622 range
.mRangeType
= NS_TEXTRANGE_RAWINPUT
;
1623 aTextRangeList
.AppendElement(range
);
1625 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1626 ("IMM32: SetTextRangeList, mClauseLength=0\n"));
1628 // iterate over the attributes
1629 uint32_t lastOffset
= 0;
1630 for (uint32_t i
= 0; i
< mClauseArray
.Length() - 1; i
++) {
1631 uint32_t current
= mClauseArray
[i
+ 1];
1632 if (current
> mCompositionString
.Length()) {
1633 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1634 ("IMM32: SetTextRangeList, mClauseArray[%ld]=%lu. This is larger than mCompositionString.Length()=%lu\n",
1635 i
+ 1, current
, mCompositionString
.Length()));
1636 current
= int32_t(mCompositionString
.Length());
1639 range
.mRangeType
= PlatformToNSAttr(mAttributeArray
[lastOffset
]);
1640 range
.mStartOffset
= lastOffset
;
1641 range
.mEndOffset
= current
;
1642 aTextRangeList
.AppendElement(range
);
1644 lastOffset
= current
;
1646 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1647 ("IMM32: SetTextRangeList, index=%ld, rangeType=%s, range=[%lu-%lu]\n",
1648 i
, GetRangeTypeName(range
.mRangeType
), range
.mStartOffset
,
1653 if (mCursorPosition
== NO_IME_CARET
) {
1654 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1655 ("IMM32: GetTextRangeList, no caret\n"));
1659 int32_t cursor
= mCursorPosition
;
1660 if (uint32_t(cursor
) > mCompositionString
.Length()) {
1661 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1662 ("IMM32: SetTextRangeList, mCursorPosition=%ld. This is larger than mCompositionString.Length()=%lu\n",
1663 mCursorPosition
, mCompositionString
.Length()));
1664 cursor
= mCompositionString
.Length();
1667 range
.mStartOffset
= range
.mEndOffset
= cursor
;
1668 range
.mRangeType
= NS_TEXTRANGE_CARETPOSITION
;
1669 aTextRangeList
.AppendElement(range
);
1671 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1672 ("IMM32: SetTextRangeList, caret position=%ld\n",
1673 range
.mStartOffset
));
1677 nsIMM32Handler::GetCompositionString(const nsIMEContext
&aIMEContext
,
1680 // Retrieve the size of the required output buffer.
1681 long lRtn
= ::ImmGetCompositionStringW(aIMEContext
.get(), aIndex
, nullptr, 0);
1683 !mCompositionString
.SetLength((lRtn
/ sizeof(WCHAR
)) + 1, mozilla::fallible_t())) {
1684 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1685 ("IMM32: GetCompositionString, FAILED by OOM\n"));
1686 return; // Error or out of memory.
1689 // Actually retrieve the composition string information.
1690 lRtn
= ::ImmGetCompositionStringW(aIMEContext
.get(), aIndex
,
1691 (LPVOID
)mCompositionString
.BeginWriting(),
1692 lRtn
+ sizeof(WCHAR
));
1693 mCompositionString
.SetLength(lRtn
/ sizeof(WCHAR
));
1695 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1696 ("IMM32: GetCompositionString, SUCCEEDED mCompositionString=\"%s\"\n",
1697 NS_ConvertUTF16toUTF8(mCompositionString
).get()));
1701 nsIMM32Handler::GetTargetClauseRange(uint32_t *aOffset
, uint32_t *aLength
)
1703 NS_ENSURE_TRUE(aOffset
, false);
1704 NS_ENSURE_TRUE(mIsComposing
, false);
1705 NS_ENSURE_TRUE(ShouldDrawCompositionStringOurselves(), false);
1708 *aOffset
= mCompositionStart
;
1709 for (uint32_t i
= 0; i
< mAttributeArray
.Length(); i
++) {
1710 if (mAttributeArray
[i
] == ATTR_TARGET_NOTCONVERTED
||
1711 mAttributeArray
[i
] == ATTR_TARGET_CONVERTED
) {
1712 *aOffset
= mCompositionStart
+ i
;
1723 // The all composition string is targetted when there is no ATTR_TARGET_*
1724 // clause. E.g., there is only ATTR_INPUT
1725 *aLength
= mCompositionString
.Length();
1729 uint32_t offsetInComposition
= *aOffset
- mCompositionStart
;
1730 *aLength
= mCompositionString
.Length() - offsetInComposition
;
1731 for (uint32_t i
= offsetInComposition
; i
< mAttributeArray
.Length(); i
++) {
1732 if (mAttributeArray
[i
] != ATTR_TARGET_NOTCONVERTED
&&
1733 mAttributeArray
[i
] != ATTR_TARGET_CONVERTED
) {
1734 *aLength
= i
- offsetInComposition
;
1742 nsIMM32Handler::ConvertToANSIString(const nsAFlatString
& aStr
, UINT aCodePage
,
1743 nsACString
& aANSIStr
)
1745 int len
= ::WideCharToMultiByte(aCodePage
, 0,
1746 (LPCWSTR
)aStr
.get(), aStr
.Length(),
1747 nullptr, 0, nullptr, nullptr);
1748 NS_ENSURE_TRUE(len
>= 0, false);
1750 if (!aANSIStr
.SetLength(len
, mozilla::fallible_t())) {
1751 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1752 ("IMM32: ConvertToANSIString, FAILED by OOM\n"));
1755 ::WideCharToMultiByte(aCodePage
, 0, (LPCWSTR
)aStr
.get(), aStr
.Length(),
1756 (LPSTR
)aANSIStr
.BeginWriting(), len
, nullptr, nullptr);
1761 nsIMM32Handler::GetCharacterRectOfSelectedTextAt(nsWindow
* aWindow
,
1763 nsIntRect
&aCharRect
)
1765 nsIntPoint
point(0, 0);
1767 WidgetQueryContentEvent
selection(true, NS_QUERY_SELECTED_TEXT
, aWindow
);
1768 aWindow
->InitEvent(selection
, &point
);
1769 aWindow
->DispatchWindowEvent(&selection
);
1770 if (!selection
.mSucceeded
) {
1771 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1772 ("IMM32: GetCharacterRectOfSelectedTextAt, aOffset=%lu, FAILED (NS_QUERY_SELECTED_TEXT)\n",
1777 uint32_t offset
= selection
.mReply
.mOffset
+ aOffset
;
1778 bool useCaretRect
= selection
.mReply
.mString
.IsEmpty();
1779 if (useCaretRect
&& ShouldDrawCompositionStringOurselves() &&
1780 mIsComposing
&& !mCompositionString
.IsEmpty()) {
1781 // There is not a normal selection, but we have composition string.
1782 // XXX mnakano - Should we implement NS_QUERY_IME_SELECTED_TEXT?
1783 useCaretRect
= false;
1784 if (mCursorPosition
!= NO_IME_CARET
) {
1785 uint32_t cursorPosition
=
1786 std::min
<uint32_t>(mCursorPosition
, mCompositionString
.Length());
1787 NS_ASSERTION(offset
>= cursorPosition
, "offset is less than cursorPosition!");
1788 offset
-= cursorPosition
;
1793 if (!useCaretRect
) {
1794 WidgetQueryContentEvent
charRect(true, NS_QUERY_TEXT_RECT
, aWindow
);
1795 charRect
.InitForQueryTextRect(offset
, 1);
1796 aWindow
->InitEvent(charRect
, &point
);
1797 aWindow
->DispatchWindowEvent(&charRect
);
1798 if (charRect
.mSucceeded
) {
1799 aCharRect
= charRect
.mReply
.mRect
;
1800 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1801 ("IMM32: GetCharacterRectOfSelectedTextAt, aOffset=%lu, SUCCEEDED\n",
1803 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1804 ("IMM32: GetCharacterRectOfSelectedTextAt, aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }\n",
1805 aCharRect
.x
, aCharRect
.y
, aCharRect
.width
, aCharRect
.height
));
1810 return GetCaretRect(aWindow
, aCharRect
);
1814 nsIMM32Handler::GetCaretRect(nsWindow
* aWindow
, nsIntRect
&aCaretRect
)
1816 nsIntPoint
point(0, 0);
1818 WidgetQueryContentEvent
selection(true, NS_QUERY_SELECTED_TEXT
, aWindow
);
1819 aWindow
->InitEvent(selection
, &point
);
1820 aWindow
->DispatchWindowEvent(&selection
);
1821 if (!selection
.mSucceeded
) {
1822 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1823 ("IMM32: GetCaretRect, FAILED (NS_QUERY_SELECTED_TEXT)\n"));
1827 uint32_t offset
= selection
.mReply
.mOffset
;
1829 WidgetQueryContentEvent
caretRect(true, NS_QUERY_CARET_RECT
, aWindow
);
1830 caretRect
.InitForQueryCaretRect(offset
);
1831 aWindow
->InitEvent(caretRect
, &point
);
1832 aWindow
->DispatchWindowEvent(&caretRect
);
1833 if (!caretRect
.mSucceeded
) {
1834 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1835 ("IMM32: GetCaretRect, FAILED (NS_QUERY_CARET_RECT)\n"));
1838 aCaretRect
= caretRect
.mReply
.mRect
;
1839 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1840 ("IMM32: GetCaretRect, SUCCEEDED, aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }\n",
1841 aCaretRect
.x
, aCaretRect
.y
, aCaretRect
.width
, aCaretRect
.height
));
1846 nsIMM32Handler::SetIMERelatedWindowsPos(nsWindow
* aWindow
,
1847 const nsIMEContext
&aIMEContext
)
1850 // Get first character rect of current a normal selected text or a composing
1852 bool ret
= GetCharacterRectOfSelectedTextAt(aWindow
, 0, r
);
1853 NS_ENSURE_TRUE(ret
, false);
1854 nsWindow
* toplevelWindow
= aWindow
->GetTopLevelWindow(false);
1855 nsIntRect firstSelectedCharRect
;
1856 ResolveIMECaretPos(toplevelWindow
, r
, aWindow
, firstSelectedCharRect
);
1858 // Set native caret size/position to our caret. Some IMEs honor it. E.g.,
1859 // "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified
1861 nsIntRect
caretRect(firstSelectedCharRect
);
1862 if (GetCaretRect(aWindow
, r
)) {
1863 ResolveIMECaretPos(toplevelWindow
, r
, aWindow
, caretRect
);
1865 NS_WARNING("failed to get caret rect");
1866 caretRect
.width
= 1;
1868 if (!mNativeCaretIsCreated
) {
1869 mNativeCaretIsCreated
= ::CreateCaret(aWindow
->GetWindowHandle(), nullptr,
1870 caretRect
.width
, caretRect
.height
);
1871 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1872 ("IMM32: SetIMERelatedWindowsPos, mNativeCaretIsCreated=%s, width=%ld height=%ld\n",
1873 mNativeCaretIsCreated
? "TRUE" : "FALSE",
1874 caretRect
.width
, caretRect
.height
));
1876 ::SetCaretPos(caretRect
.x
, caretRect
.y
);
1878 if (ShouldDrawCompositionStringOurselves()) {
1879 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1880 ("IMM32: SetIMERelatedWindowsPos, Set candidate window\n"));
1882 // Get a rect of first character in current target in composition string.
1883 if (mIsComposing
&& !mCompositionString
.IsEmpty()) {
1884 // If there are no targetted selection, we should use it's first character
1887 if (!GetTargetClauseRange(&offset
)) {
1888 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1889 ("IMM32: SetIMERelatedWindowsPos, FAILED, by GetTargetClauseRange\n"));
1892 ret
= GetCharacterRectOfSelectedTextAt(aWindow
,
1893 offset
- mCompositionStart
, r
);
1894 NS_ENSURE_TRUE(ret
, false);
1896 // If there are no composition string, we should use a first character
1898 ret
= GetCharacterRectOfSelectedTextAt(aWindow
, 0, r
);
1899 NS_ENSURE_TRUE(ret
, false);
1901 nsIntRect firstTargetCharRect
;
1902 ResolveIMECaretPos(toplevelWindow
, r
, aWindow
, firstTargetCharRect
);
1904 // Move the candidate window to first character position of the target.
1905 CANDIDATEFORM candForm
;
1906 candForm
.dwIndex
= 0;
1907 candForm
.dwStyle
= CFS_EXCLUDE
;
1908 candForm
.ptCurrentPos
.x
= firstTargetCharRect
.x
;
1909 candForm
.ptCurrentPos
.y
= firstTargetCharRect
.y
;
1910 candForm
.rcArea
.right
= candForm
.rcArea
.left
= candForm
.ptCurrentPos
.x
;
1911 candForm
.rcArea
.top
= candForm
.ptCurrentPos
.y
;
1912 candForm
.rcArea
.bottom
= candForm
.ptCurrentPos
.y
+
1913 firstTargetCharRect
.height
;
1914 ::ImmSetCandidateWindow(aIMEContext
.get(), &candForm
);
1916 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
1917 ("IMM32: SetIMERelatedWindowsPos, Set composition window\n"));
1919 // Move the composition window to caret position (if selected some
1920 // characters, we should use first character rect of them).
1921 // And in this mode, IME adjusts the candidate window position
1922 // automatically. So, we don't need to set it.
1923 COMPOSITIONFORM compForm
;
1924 compForm
.dwStyle
= CFS_POINT
;
1925 compForm
.ptCurrentPos
.x
= firstSelectedCharRect
.x
;
1926 compForm
.ptCurrentPos
.y
= firstSelectedCharRect
.y
;
1927 ::ImmSetCompositionWindow(aIMEContext
.get(), &compForm
);
1934 nsIMM32Handler::ResolveIMECaretPos(nsIWidget
* aReferenceWidget
,
1935 nsIntRect
& aCursorRect
,
1936 nsIWidget
* aNewOriginWidget
,
1937 nsIntRect
& aOutRect
)
1939 aOutRect
= aCursorRect
;
1941 if (aReferenceWidget
== aNewOriginWidget
)
1944 if (aReferenceWidget
)
1945 aOutRect
.MoveBy(aReferenceWidget
->WidgetToScreenOffset());
1947 if (aNewOriginWidget
)
1948 aOutRect
.MoveBy(-aNewOriginWidget
->WidgetToScreenOffset());
1952 nsIMM32Handler::OnMouseEvent(nsWindow
* aWindow
, LPARAM lParam
, int aAction
,
1955 aResult
.mConsumed
= false; // always call next wndprc
1957 if (!sWM_MSIME_MOUSE
|| !mIsComposing
||
1958 !ShouldDrawCompositionStringOurselves()) {
1962 nsIntPoint
cursor(LOWORD(lParam
), HIWORD(lParam
));
1963 WidgetQueryContentEvent
charAtPt(true, NS_QUERY_CHARACTER_AT_POINT
, aWindow
);
1964 aWindow
->InitEvent(charAtPt
, &cursor
);
1965 aWindow
->DispatchWindowEvent(&charAtPt
);
1966 if (!charAtPt
.mSucceeded
||
1967 charAtPt
.mReply
.mOffset
== WidgetQueryContentEvent::NOT_FOUND
||
1968 charAtPt
.mReply
.mOffset
< mCompositionStart
||
1969 charAtPt
.mReply
.mOffset
>
1970 mCompositionStart
+ mCompositionString
.Length()) {
1974 // calcurate positioning and offset
1975 // char : JCH1|JCH2|JCH3
1976 // offset: 0011 1122 2233
1977 // positioning: 2301 2301 2301
1978 nsIntRect cursorInTopLevel
, cursorRect(cursor
, nsIntSize(0, 0));
1979 ResolveIMECaretPos(aWindow
, cursorRect
,
1980 aWindow
->GetTopLevelWindow(false), cursorInTopLevel
);
1981 int32_t cursorXInChar
= cursorInTopLevel
.x
- charAtPt
.mReply
.mRect
.x
;
1982 // The event might hit to zero-width character, see bug 694913.
1983 // The reason might be:
1984 // * There are some zero-width characters are actually.
1985 // * font-size is specified zero.
1986 // But nobody reproduced this bug actually...
1987 // We should assume that user clicked on right most of the zero-width
1988 // character in such case.
1989 int positioning
= 1;
1990 if (charAtPt
.mReply
.mRect
.width
> 0) {
1991 positioning
= cursorXInChar
* 4 / charAtPt
.mReply
.mRect
.width
;
1992 positioning
= (positioning
+ 2) % 4;
1995 int offset
= charAtPt
.mReply
.mOffset
- mCompositionStart
;
1996 if (positioning
< 2) {
2000 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
2001 ("IMM32: OnMouseEvent, x,y=%ld,%ld, offset=%ld, positioning=%ld\n",
2002 cursor
.x
, cursor
.y
, offset
, positioning
));
2004 // send MS_MSIME_MOUSE message to default IME window.
2005 HWND imeWnd
= ::ImmGetDefaultIMEWnd(aWindow
->GetWindowHandle());
2006 nsIMEContext
IMEContext(aWindow
->GetWindowHandle());
2007 return ::SendMessageW(imeWnd
, sWM_MSIME_MOUSE
,
2008 MAKELONG(MAKEWORD(aAction
, positioning
), offset
),
2009 (LPARAM
) IMEContext
.get()) == 1;
2013 nsIMM32Handler::OnKeyDownEvent(nsWindow
* aWindow
, WPARAM wParam
, LPARAM lParam
,
2016 PR_LOG(gIMM32Log
, PR_LOG_ALWAYS
,
2017 ("IMM32: OnKeyDownEvent, hWnd=%08x, wParam=%08x, lParam=%08x\n",
2018 aWindow
->GetWindowHandle(), wParam
, lParam
));
2019 aResult
.mConsumed
= false;
2030 // If IME didn't process the key message (the virtual key code wasn't
2031 // converted to VK_PROCESSKEY), and the virtual key code event causes
2032 // to move caret, we should cancel the composition here. Then, this
2033 // event will be dispatched.
2034 // XXX I think that we should dispatch all key events during composition,
2035 // and nsEditor should cancel/commit the composition if it *thinks*
2037 if (IsComposingOnOurEditor()) {
2038 // NOTE: We don't need to cancel the composition on another window.
2039 CancelComposition(aWindow
, false);