Bumping manifests a=b2g-bump
[gecko.git] / widget / windows / nsIMM32Handler.cpp
blob33a747b51d806292cc5600594cf8b3caf34f9f0b
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 #ifdef MOZ_LOGGING
8 #define FORCE_PR_LOG /* Allow logging in the release build */
9 #endif // MOZ_LOGGING
10 #include "prlog.h"
12 #include "nsIMM32Handler.h"
13 #include "nsWindow.h"
14 #include "nsWindowDefs.h"
15 #include "WinUtils.h"
16 #include "KeyboardLayout.h"
17 #include <algorithm>
19 #include "mozilla/MiscEvents.h"
20 #include "mozilla/TextEvents.h"
22 using namespace mozilla;
23 using namespace mozilla::widget;
25 static nsIMM32Handler* gIMM32Handler = nullptr;
27 #ifdef PR_LOGGING
28 PRLogModuleInfo* gIMM32Log = nullptr;
29 #endif
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;
52 /* static */ void
53 nsIMM32Handler::EnsureHandlerInstance()
55 if (!gIMM32Handler) {
56 gIMM32Handler = new nsIMM32Handler();
60 /* static */ void
61 nsIMM32Handler::Initialize()
63 #ifdef PR_LOGGING
64 if (!gIMM32Log)
65 gIMM32Log = PR_NewLogModule("nsIMM32HandlerWidgets");
66 #endif
68 if (!sWM_MSIME_MOUSE) {
69 sWM_MSIME_MOUSE = ::RegisterWindowMessage(RWM_MOUSE);
71 InitKeyboardLayout(::GetKeyboardLayout(0));
74 /* static */ void
75 nsIMM32Handler::Terminate()
77 if (!gIMM32Handler)
78 return;
79 delete gIMM32Handler;
80 gIMM32Handler = nullptr;
83 /* static */ bool
84 nsIMM32Handler::IsComposingOnOurEditor()
86 return gIMM32Handler && gIMM32Handler->mIsComposing;
89 /* static */ bool
90 nsIMM32Handler::IsComposingOnPlugin()
92 return gIMM32Handler && gIMM32Handler->mIsComposingOnPlugin;
95 /* static */ bool
96 nsIMM32Handler::IsComposingWindow(nsWindow* aWindow)
98 return gIMM32Handler && gIMM32Handler->mComposingWindow == aWindow;
101 /* static */ bool
102 nsIMM32Handler::IsTopLevelWindowOfComposition(nsWindow* aWindow)
104 if (!gIMM32Handler || !gIMM32Handler->mComposingWindow) {
105 return false;
107 HWND wnd = gIMM32Handler->mComposingWindow->GetWindowHandle();
108 return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle();
111 /* static */ bool
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
116 // ourselves.
117 return !(sIMEProperty & IME_PROP_SPECIAL_UI) &&
118 (sIMEProperty & IME_PROP_AT_CARET);
121 /* static */ void
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, "
131 "sIMEProperty=%08x",
132 aKeyboardLayout, sCodePage, sIMEProperty));
135 /* static */ UINT
136 nsIMM32Handler::GetKeyboardCodePage()
138 return sCodePage;
141 /* static */
142 nsIMEUpdatePreference
143 nsIMM32Handler::GetIMEUpdatePreference()
145 return nsIMEUpdatePreference(nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE);
148 // used for checking the lParam of WM_IME_COMPOSITION
149 #define IS_COMPOSING_LPARAM(lParam) \
150 ((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS))
151 #define IS_COMMITTING_LPARAM(lParam) ((lParam) & GCS_RESULTSTR)
152 // Some IMEs (e.g., the standard IME for Korean) don't have caret position,
153 // then, we should not set caret position to text event.
154 #define NO_IME_CARET -1
156 nsIMM32Handler::nsIMM32Handler() :
157 mComposingWindow(nullptr), mCursorPosition(NO_IME_CARET), mCompositionStart(0),
158 mIsComposing(false), mIsComposingOnPlugin(false),
159 mNativeCaretIsCreated(false)
161 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, ("IMM32: nsIMM32Handler is created\n"));
164 nsIMM32Handler::~nsIMM32Handler()
166 if (mIsComposing) {
167 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
168 ("IMM32: ~nsIMM32Handler, ERROR, the instance is still composing\n"));
170 PR_LOG(gIMM32Log, PR_LOG_ALWAYS, ("IMM32: nsIMM32Handler is destroyed\n"));
173 nsresult
174 nsIMM32Handler::EnsureClauseArray(int32_t aCount)
176 NS_ENSURE_ARG_MIN(aCount, 0);
177 mClauseArray.SetCapacity(aCount + 32);
178 return NS_OK;
181 nsresult
182 nsIMM32Handler::EnsureAttributeArray(int32_t aCount)
184 NS_ENSURE_ARG_MIN(aCount, 0);
185 mAttributeArray.SetCapacity(aCount + 64);
186 return NS_OK;
189 /* static */ void
190 nsIMM32Handler::CommitComposition(nsWindow* aWindow, bool aForce)
192 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
193 ("IMM32: CommitComposition, aForce=%s, aWindow=%p, hWnd=%08x, mComposingWindow=%p%s\n",
194 aForce ? "TRUE" : "FALSE",
195 aWindow, aWindow->GetWindowHandle(),
196 gIMM32Handler ? gIMM32Handler->mComposingWindow : nullptr,
197 gIMM32Handler && gIMM32Handler->mComposingWindow ?
198 IsComposingOnOurEditor() ? " (composing on editor)" :
199 " (composing on plug-in)" : ""));
200 if (!aForce && !IsComposingWindow(aWindow)) {
201 return;
204 nsIMEContext IMEContext(aWindow->GetWindowHandle());
205 bool associated = IMEContext.AssociateDefaultContext();
206 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
207 ("IMM32: CommitComposition, associated=%s\n",
208 associated ? "YES" : "NO"));
210 if (IMEContext.IsValid()) {
211 ::ImmNotifyIME(IMEContext.get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
212 ::ImmNotifyIME(IMEContext.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0);
215 if (associated) {
216 IMEContext.Disassociate();
220 /* static */ void
221 nsIMM32Handler::CancelComposition(nsWindow* aWindow, bool aForce)
223 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
224 ("IMM32: CancelComposition, aForce=%s, aWindow=%p, hWnd=%08x, mComposingWindow=%p%s\n",
225 aForce ? "TRUE" : "FALSE",
226 aWindow, aWindow->GetWindowHandle(),
227 gIMM32Handler ? gIMM32Handler->mComposingWindow : nullptr,
228 gIMM32Handler && gIMM32Handler->mComposingWindow ?
229 IsComposingOnOurEditor() ? " (composing on editor)" :
230 " (composing on plug-in)" : ""));
231 if (!aForce && !IsComposingWindow(aWindow)) {
232 return;
235 nsIMEContext IMEContext(aWindow->GetWindowHandle());
236 bool associated = IMEContext.AssociateDefaultContext();
237 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
238 ("IMM32: CancelComposition, associated=%s\n",
239 associated ? "YES" : "NO"));
241 if (IMEContext.IsValid()) {
242 ::ImmNotifyIME(IMEContext.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0);
245 if (associated) {
246 IMEContext.Disassociate();
250 // static
251 void
252 nsIMM32Handler::OnUpdateComposition(nsWindow* aWindow)
254 if (!gIMM32Handler) {
255 return;
258 if (aWindow->PluginHasFocus()) {
259 return;
262 nsIMEContext IMEContext(aWindow->GetWindowHandle());
263 gIMM32Handler->SetIMERelatedWindowsPos(aWindow, IMEContext);
267 /* static */ bool
268 nsIMM32Handler::ProcessInputLangChangeMessage(nsWindow* aWindow,
269 WPARAM wParam,
270 LPARAM lParam,
271 MSGResult& aResult)
273 aResult.mResult = 0;
274 aResult.mConsumed = false;
275 // We don't need to create the instance of the handler here.
276 if (gIMM32Handler) {
277 gIMM32Handler->OnInputLangChange(aWindow, wParam, lParam, aResult);
279 InitKeyboardLayout(reinterpret_cast<HKL>(lParam));
280 // We can release the instance here, because the instance may be never
281 // used. E.g., the new keyboard layout may not use IME, or it may use TSF.
282 Terminate();
283 // Don't return as "processed", the messages should be processed on nsWindow
284 // too.
285 return false;
288 /* static */ bool
289 nsIMM32Handler::ProcessMessage(nsWindow* aWindow, UINT msg,
290 WPARAM &wParam, LPARAM &lParam,
291 MSGResult& aResult)
293 // XXX We store the composing window in mComposingWindow. If IME messages are
294 // sent to different window, we should commit the old transaction. And also
295 // if the new window handle is not focused, probably, we should not start
296 // the composition, however, such case should not be, it's just bad scenario.
298 // When a plug-in has focus or compsition, we should dispatch the IME events
299 // to the plug-in.
300 if (aWindow->PluginHasFocus() || IsComposingOnPlugin()) {
301 return ProcessMessageForPlugin(aWindow, msg, wParam, lParam, aResult);
304 aResult.mResult = 0;
305 switch (msg) {
306 case WM_LBUTTONDOWN:
307 case WM_MBUTTONDOWN:
308 case WM_RBUTTONDOWN: {
309 // We don't need to create the instance of the handler here.
310 if (!gIMM32Handler) {
311 return false;
313 return gIMM32Handler->OnMouseEvent(aWindow, lParam,
314 msg == WM_LBUTTONDOWN ? IMEMOUSE_LDOWN :
315 msg == WM_MBUTTONDOWN ? IMEMOUSE_MDOWN :
316 IMEMOUSE_RDOWN, aResult);
318 case WM_INPUTLANGCHANGE:
319 return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult);
320 case WM_IME_STARTCOMPOSITION:
321 EnsureHandlerInstance();
322 return gIMM32Handler->OnIMEStartComposition(aWindow, aResult);
323 case WM_IME_COMPOSITION:
324 EnsureHandlerInstance();
325 return gIMM32Handler->OnIMEComposition(aWindow, wParam, lParam, aResult);
326 case WM_IME_ENDCOMPOSITION:
327 EnsureHandlerInstance();
328 return gIMM32Handler->OnIMEEndComposition(aWindow, aResult);
329 case WM_IME_CHAR:
330 return OnIMEChar(aWindow, wParam, lParam, aResult);
331 case WM_IME_NOTIFY:
332 return OnIMENotify(aWindow, wParam, lParam, aResult);
333 case WM_IME_REQUEST:
334 EnsureHandlerInstance();
335 return gIMM32Handler->OnIMERequest(aWindow, wParam, lParam, aResult);
336 case WM_IME_SELECT:
337 return OnIMESelect(aWindow, wParam, lParam, aResult);
338 case WM_IME_SETCONTEXT:
339 return OnIMESetContext(aWindow, wParam, lParam, aResult);
340 case WM_KEYDOWN:
341 return OnKeyDownEvent(aWindow, wParam, lParam, aResult);
342 case WM_CHAR:
343 if (!gIMM32Handler) {
344 return false;
346 return gIMM32Handler->OnChar(aWindow, wParam, lParam, aResult);
347 default:
348 return false;
352 /* static */ bool
353 nsIMM32Handler::ProcessMessageForPlugin(nsWindow* aWindow, UINT msg,
354 WPARAM &wParam, LPARAM &lParam,
355 MSGResult& aResult)
357 aResult.mResult = 0;
358 aResult.mConsumed = false;
359 switch (msg) {
360 case WM_INPUTLANGCHANGEREQUEST:
361 case WM_INPUTLANGCHANGE:
362 aWindow->DispatchPluginEvent(msg, wParam, lParam, false);
363 return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult);
364 case WM_IME_COMPOSITION:
365 EnsureHandlerInstance();
366 return gIMM32Handler->OnIMECompositionOnPlugin(aWindow, wParam, lParam,
367 aResult);
368 case WM_IME_STARTCOMPOSITION:
369 EnsureHandlerInstance();
370 return gIMM32Handler->OnIMEStartCompositionOnPlugin(aWindow, wParam,
371 lParam, aResult);
372 case WM_IME_ENDCOMPOSITION:
373 EnsureHandlerInstance();
374 return gIMM32Handler->OnIMEEndCompositionOnPlugin(aWindow, wParam, lParam,
375 aResult);
376 case WM_IME_CHAR:
377 EnsureHandlerInstance();
378 return gIMM32Handler->OnIMECharOnPlugin(aWindow, wParam, lParam, aResult);
379 case WM_IME_SETCONTEXT:
380 return OnIMESetContextOnPlugin(aWindow, wParam, lParam, aResult);
381 case WM_CHAR:
382 if (!gIMM32Handler) {
383 return false;
385 return gIMM32Handler->OnCharOnPlugin(aWindow, wParam, lParam, aResult);
386 case WM_IME_COMPOSITIONFULL:
387 case WM_IME_CONTROL:
388 case WM_IME_KEYDOWN:
389 case WM_IME_KEYUP:
390 case WM_IME_REQUEST:
391 case WM_IME_SELECT:
392 aResult.mConsumed =
393 aWindow->DispatchPluginEvent(msg, wParam, lParam, false);
394 return true;
396 return false;
399 /****************************************************************************
400 * message handlers
401 ****************************************************************************/
403 void
404 nsIMM32Handler::OnInputLangChange(nsWindow* aWindow,
405 WPARAM wParam,
406 LPARAM lParam,
407 MSGResult& aResult)
409 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
410 ("IMM32: OnInputLangChange, hWnd=%08x, wParam=%08x, lParam=%08x\n",
411 aWindow->GetWindowHandle(), wParam, lParam));
413 aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
414 NS_ASSERTION(!mIsComposing, "ResetInputState failed");
416 if (mIsComposing) {
417 HandleEndComposition(aWindow);
420 aResult.mConsumed = false;
423 bool
424 nsIMM32Handler::OnIMEStartComposition(nsWindow* aWindow,
425 MSGResult& aResult)
427 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
428 ("IMM32: OnIMEStartComposition, hWnd=%08x, mIsComposing=%s\n",
429 aWindow->GetWindowHandle(), mIsComposing ? "TRUE" : "FALSE"));
430 aResult.mConsumed = ShouldDrawCompositionStringOurselves();
431 if (mIsComposing) {
432 NS_WARNING("Composition has been already started");
433 return true;
436 nsIMEContext IMEContext(aWindow->GetWindowHandle());
437 HandleStartComposition(aWindow, IMEContext);
438 return true;
441 bool
442 nsIMM32Handler::OnIMEComposition(nsWindow* aWindow,
443 WPARAM wParam,
444 LPARAM lParam,
445 MSGResult& aResult)
447 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
448 ("IMM32: OnIMEComposition, hWnd=%08x, lParam=%08x, mIsComposing=%s\n",
449 aWindow->GetWindowHandle(), lParam, mIsComposing ? "TRUE" : "FALSE"));
450 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
451 ("IMM32: OnIMEComposition, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s\n",
452 lParam & GCS_RESULTSTR ? "YES" : "no",
453 lParam & GCS_COMPSTR ? "YES" : "no",
454 lParam & GCS_COMPATTR ? "YES" : "no",
455 lParam & GCS_COMPCLAUSE ? "YES" : "no",
456 lParam & GCS_CURSORPOS ? "YES" : "no"));
458 NS_PRECONDITION(!aWindow->PluginHasFocus(),
459 "OnIMEComposition should not be called when a plug-in has focus");
461 nsIMEContext IMEContext(aWindow->GetWindowHandle());
462 aResult.mConsumed = HandleComposition(aWindow, IMEContext, lParam);
463 return true;
466 bool
467 nsIMM32Handler::OnIMEEndComposition(nsWindow* aWindow,
468 MSGResult& aResult)
470 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
471 ("IMM32: OnIMEEndComposition, hWnd=%08x, mIsComposing=%s\n",
472 aWindow->GetWindowHandle(), mIsComposing ? "TRUE" : "FALSE"));
474 aResult.mConsumed = ShouldDrawCompositionStringOurselves();
475 if (!mIsComposing) {
476 return true;
479 // Korean IME posts WM_IME_ENDCOMPOSITION first when we hit space during
480 // composition. Then, we should ignore the message and commit the composition
481 // string at following WM_IME_COMPOSITION.
482 MSG compositionMsg;
483 if (WinUtils::PeekMessage(&compositionMsg, aWindow->GetWindowHandle(),
484 WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION,
485 PM_NOREMOVE) &&
486 compositionMsg.message == WM_IME_COMPOSITION &&
487 IS_COMMITTING_LPARAM(compositionMsg.lParam)) {
488 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
489 ("IMM32: OnIMEEndComposition, WM_IME_ENDCOMPOSITION is followed by "
490 "WM_IME_COMPOSITION, ignoring the message..."));
491 return true;
494 // Otherwise, e.g., ChangJie doesn't post WM_IME_COMPOSITION before
495 // WM_IME_ENDCOMPOSITION when composition string becomes empty.
496 // Then, we should dispatch a compositionupdate event, a text event and
497 // a compositionend event.
498 // XXX Shouldn't we dispatch the text event with actual or latest composition
499 // string?
500 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
501 ("IMM32: OnIMEEndComposition, mCompositionString=\"%s\"%s",
502 NS_ConvertUTF16toUTF8(mCompositionString).get(),
503 mCompositionString.IsEmpty() ? "" : ", but canceling it..."));
505 mCompositionString.Truncate();
507 nsIMEContext IMEContext(aWindow->GetWindowHandle());
508 DispatchTextEvent(aWindow, IMEContext, false);
510 HandleEndComposition(aWindow);
512 return true;
515 /* static */ bool
516 nsIMM32Handler::OnIMEChar(nsWindow* aWindow,
517 WPARAM wParam,
518 LPARAM lParam,
519 MSGResult& aResult)
521 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
522 ("IMM32: OnIMEChar, hWnd=%08x, char=%08x\n",
523 aWindow->GetWindowHandle(), wParam));
525 // We don't need to fire any text events from here. This method will be
526 // called when the composition string of the current IME is not drawn by us
527 // and some characters are committed. In that case, the committed string was
528 // processed in nsWindow::OnIMEComposition already.
530 // We need to consume the message so that Windows don't send two WM_CHAR msgs
531 aResult.mConsumed = true;
532 return true;
535 /* static */ bool
536 nsIMM32Handler::OnIMECompositionFull(nsWindow* aWindow,
537 MSGResult& aResult)
539 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
540 ("IMM32: OnIMECompositionFull, hWnd=%08x\n",
541 aWindow->GetWindowHandle()));
543 // not implement yet
544 aResult.mConsumed = false;
545 return true;
548 /* static */ bool
549 nsIMM32Handler::OnIMENotify(nsWindow* aWindow,
550 WPARAM wParam,
551 LPARAM lParam,
552 MSGResult& aResult)
554 #ifdef PR_LOGGING
555 switch (wParam) {
556 case IMN_CHANGECANDIDATE:
557 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
558 ("IMM32: OnIMENotify, hWnd=%08x, IMN_CHANGECANDIDATE, lParam=%08x\n",
559 aWindow->GetWindowHandle(), lParam));
560 break;
561 case IMN_CLOSECANDIDATE:
562 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
563 ("IMM32: OnIMENotify, hWnd=%08x, IMN_CLOSECANDIDATE, lParam=%08x\n",
564 aWindow->GetWindowHandle(), lParam));
565 break;
566 case IMN_CLOSESTATUSWINDOW:
567 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
568 ("IMM32: OnIMENotify, hWnd=%08x, IMN_CLOSESTATUSWINDOW\n",
569 aWindow->GetWindowHandle()));
570 break;
571 case IMN_GUIDELINE:
572 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
573 ("IMM32: OnIMENotify, hWnd=%08x, IMN_GUIDELINE\n",
574 aWindow->GetWindowHandle()));
575 break;
576 case IMN_OPENCANDIDATE:
577 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
578 ("IMM32: OnIMENotify, hWnd=%08x, IMN_OPENCANDIDATE, lParam=%08x\n",
579 aWindow->GetWindowHandle(), lParam));
580 break;
581 case IMN_OPENSTATUSWINDOW:
582 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
583 ("IMM32: OnIMENotify, hWnd=%08x, IMN_OPENSTATUSWINDOW\n",
584 aWindow->GetWindowHandle()));
585 break;
586 case IMN_SETCANDIDATEPOS:
587 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
588 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCANDIDATEPOS, lParam=%08x\n",
589 aWindow->GetWindowHandle(), lParam));
590 break;
591 case IMN_SETCOMPOSITIONFONT:
592 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
593 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONFONT\n",
594 aWindow->GetWindowHandle()));
595 break;
596 case IMN_SETCOMPOSITIONWINDOW:
597 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
598 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONWINDOW\n",
599 aWindow->GetWindowHandle()));
600 break;
601 case IMN_SETCONVERSIONMODE:
602 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
603 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCONVERSIONMODE\n",
604 aWindow->GetWindowHandle()));
605 break;
606 case IMN_SETOPENSTATUS:
607 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
608 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETOPENSTATUS\n",
609 aWindow->GetWindowHandle()));
610 break;
611 case IMN_SETSENTENCEMODE:
612 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
613 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETSENTENCEMODE\n",
614 aWindow->GetWindowHandle()));
615 break;
616 case IMN_SETSTATUSWINDOWPOS:
617 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
618 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETSTATUSWINDOWPOS\n",
619 aWindow->GetWindowHandle()));
620 break;
621 case IMN_PRIVATE:
622 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
623 ("IMM32: OnIMENotify, hWnd=%08x, IMN_PRIVATE\n",
624 aWindow->GetWindowHandle()));
625 break;
627 #endif // PR_LOGGING
629 // not implement yet
630 aResult.mConsumed = false;
631 return true;
634 bool
635 nsIMM32Handler::OnIMERequest(nsWindow* aWindow,
636 WPARAM wParam,
637 LPARAM lParam,
638 MSGResult& aResult)
640 switch (wParam) {
641 case IMR_RECONVERTSTRING:
642 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
643 ("IMM32: OnIMERequest, hWnd=%08x, IMR_RECONVERTSTRING\n",
644 aWindow->GetWindowHandle()));
645 aResult.mConsumed = HandleReconvert(aWindow, lParam, &aResult.mResult);
646 return true;
647 case IMR_QUERYCHARPOSITION:
648 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
649 ("IMM32: OnIMERequest, hWnd=%08x, IMR_QUERYCHARPOSITION\n",
650 aWindow->GetWindowHandle()));
651 aResult.mConsumed =
652 HandleQueryCharPosition(aWindow, lParam, &aResult.mResult);
653 return true;
654 case IMR_DOCUMENTFEED:
655 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
656 ("IMM32: OnIMERequest, hWnd=%08x, IMR_DOCUMENTFEED\n",
657 aWindow->GetWindowHandle()));
658 aResult.mConsumed = HandleDocumentFeed(aWindow, lParam, &aResult.mResult);
659 return true;
660 default:
661 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
662 ("IMM32: OnIMERequest, hWnd=%08x, wParam=%08x\n",
663 aWindow->GetWindowHandle(), wParam));
664 aResult.mConsumed = false;
665 return true;
669 /* static */ bool
670 nsIMM32Handler::OnIMESelect(nsWindow* aWindow,
671 WPARAM wParam,
672 LPARAM lParam,
673 MSGResult& aResult)
675 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
676 ("IMM32: OnIMESelect, hWnd=%08x, wParam=%08x, lParam=%08x\n",
677 aWindow->GetWindowHandle(), wParam, lParam));
679 // not implement yet
680 aResult.mConsumed = false;
681 return true;
684 /* static */ bool
685 nsIMM32Handler::OnIMESetContext(nsWindow* aWindow,
686 WPARAM wParam,
687 LPARAM lParam,
688 MSGResult& aResult)
690 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
691 ("IMM32: OnIMESetContext, hWnd=%08x, %s, lParam=%08x\n",
692 aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam));
694 aResult.mConsumed = false;
696 // NOTE: If the aWindow is top level window of the composing window because
697 // when a window on deactive window gets focus, WM_IME_SETCONTEXT (wParam is
698 // TRUE) is sent to the top level window first. After that,
699 // WM_IME_SETCONTEXT (wParam is FALSE) is sent to the top level window.
700 // Finally, WM_IME_SETCONTEXT (wParam is TRUE) is sent to the focused window.
701 // The top level window never becomes composing window, so, we can ignore
702 // the WM_IME_SETCONTEXT on the top level window.
703 if (IsTopLevelWindowOfComposition(aWindow)) {
704 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
705 ("IMM32: OnIMESetContext, hWnd=%08x is top level window\n"));
706 return true;
709 // When IME context is activating on another window,
710 // we should commit the old composition on the old window.
711 bool cancelComposition = false;
712 if (wParam && gIMM32Handler) {
713 cancelComposition =
714 gIMM32Handler->CommitCompositionOnPreviousWindow(aWindow);
717 if (wParam && (lParam & ISC_SHOWUICOMPOSITIONWINDOW) &&
718 ShouldDrawCompositionStringOurselves()) {
719 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
720 ("IMM32: OnIMESetContext, ISC_SHOWUICOMPOSITIONWINDOW is removed\n"));
721 lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
724 // We should sent WM_IME_SETCONTEXT to the DefWndProc here because the
725 // ancestor windows shouldn't receive this message. If they receive the
726 // message, we cannot know whether which window is the target of the message.
727 aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(),
728 WM_IME_SETCONTEXT, wParam, lParam);
730 // Cancel composition on the new window if we committed our composition on
731 // another window.
732 if (cancelComposition) {
733 CancelComposition(aWindow, true);
736 aResult.mConsumed = true;
737 return true;
740 bool
741 nsIMM32Handler::OnChar(nsWindow* aWindow,
742 WPARAM wParam,
743 LPARAM lParam,
744 MSGResult& aResult)
746 // The return value must be same as aResult.mConsumed because only when we
747 // consume the message, the caller shouldn't do anything anymore but
748 // otherwise, the caller should handle the message.
749 aResult.mConsumed = false;
750 if (IsIMECharRecordsEmpty()) {
751 return aResult.mConsumed;
753 WPARAM recWParam;
754 LPARAM recLParam;
755 DequeueIMECharRecords(recWParam, recLParam);
756 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
757 ("IMM32: OnChar, aWindow=%p, wParam=%08x, lParam=%08x,\n",
758 aWindow->GetWindowHandle(), wParam, lParam));
759 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
760 (" recorded: wParam=%08x, lParam=%08x\n",
761 recWParam, recLParam));
762 // If an unexpected char message comes, we should reset the records,
763 // of course, this shouldn't happen.
764 if (recWParam != wParam || recLParam != lParam) {
765 ResetIMECharRecords();
766 return aResult.mConsumed;
768 // Eat the char message which is caused by WM_IME_CHAR because we should
769 // have processed the IME messages, so, this message could be come from
770 // a windowless plug-in.
771 aResult.mConsumed = true;
772 return aResult.mConsumed;
775 /****************************************************************************
776 * message handlers for plug-in
777 ****************************************************************************/
779 bool
780 nsIMM32Handler::OnIMEStartCompositionOnPlugin(nsWindow* aWindow,
781 WPARAM wParam,
782 LPARAM lParam,
783 MSGResult& aResult)
785 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
786 ("IMM32: OnIMEStartCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s\n",
787 aWindow->GetWindowHandle(), mIsComposingOnPlugin ? "TRUE" : "FALSE"));
788 mIsComposingOnPlugin = true;
789 mComposingWindow = aWindow;
790 nsIMEContext IMEContext(aWindow->GetWindowHandle());
791 SetIMERelatedWindowsPosOnPlugin(aWindow, IMEContext);
792 aResult.mConsumed =
793 aWindow->DispatchPluginEvent(WM_IME_STARTCOMPOSITION, wParam, lParam,
794 false);
795 return true;
798 bool
799 nsIMM32Handler::OnIMECompositionOnPlugin(nsWindow* aWindow,
800 WPARAM wParam,
801 LPARAM lParam,
802 MSGResult& aResult)
804 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
805 ("IMM32: OnIMECompositionOnPlugin, hWnd=%08x, lParam=%08x, mIsComposingOnPlugin=%s\n",
806 aWindow->GetWindowHandle(), lParam,
807 mIsComposingOnPlugin ? "TRUE" : "FALSE"));
808 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
809 ("IMM32: OnIMECompositionOnPlugin, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s\n",
810 lParam & GCS_RESULTSTR ? "YES" : "no",
811 lParam & GCS_COMPSTR ? "YES" : "no",
812 lParam & GCS_COMPATTR ? "YES" : "no",
813 lParam & GCS_COMPCLAUSE ? "YES" : "no",
814 lParam & GCS_CURSORPOS ? "YES" : "no"));
815 // We should end composition if there is a committed string.
816 if (IS_COMMITTING_LPARAM(lParam)) {
817 mIsComposingOnPlugin = false;
818 mComposingWindow = nullptr;
820 // Continue composition if there is still a string being composed.
821 if (IS_COMPOSING_LPARAM(lParam)) {
822 mIsComposingOnPlugin = true;
823 mComposingWindow = aWindow;
824 nsIMEContext IMEContext(aWindow->GetWindowHandle());
825 SetIMERelatedWindowsPosOnPlugin(aWindow, IMEContext);
827 aResult.mConsumed =
828 aWindow->DispatchPluginEvent(WM_IME_COMPOSITION, wParam, lParam, true);
829 return true;
832 bool
833 nsIMM32Handler::OnIMEEndCompositionOnPlugin(nsWindow* aWindow,
834 WPARAM wParam,
835 LPARAM lParam,
836 MSGResult& aResult)
838 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
839 ("IMM32: OnIMEEndCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s\n",
840 aWindow->GetWindowHandle(), mIsComposingOnPlugin ? "TRUE" : "FALSE"));
842 mIsComposingOnPlugin = false;
843 mComposingWindow = nullptr;
845 if (mNativeCaretIsCreated) {
846 ::DestroyCaret();
847 mNativeCaretIsCreated = false;
850 aResult.mConsumed =
851 aWindow->DispatchPluginEvent(WM_IME_ENDCOMPOSITION, wParam, lParam,
852 false);
853 return true;
856 bool
857 nsIMM32Handler::OnIMECharOnPlugin(nsWindow* aWindow,
858 WPARAM wParam,
859 LPARAM lParam,
860 MSGResult& aResult)
862 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
863 ("IMM32: OnIMECharOnPlugin, hWnd=%08x, char=%08x, scancode=%08x\n",
864 aWindow->GetWindowHandle(), wParam, lParam));
866 aResult.mConsumed =
867 aWindow->DispatchPluginEvent(WM_IME_CHAR, wParam, lParam, true);
869 if (!aResult.mConsumed) {
870 // Record the WM_CHAR messages which are going to be coming.
871 EnsureHandlerInstance();
872 EnqueueIMECharRecords(wParam, lParam);
874 return true;
877 /* static */ bool
878 nsIMM32Handler::OnIMESetContextOnPlugin(nsWindow* aWindow,
879 WPARAM wParam,
880 LPARAM lParam,
881 MSGResult& aResult)
883 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
884 ("IMM32: OnIMESetContextOnPlugin, hWnd=%08x, %s, lParam=%08x\n",
885 aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam));
887 // If the IME context becomes active on a plug-in, we should commit
888 // our composition. And also we should cancel the composition on new
889 // window. Note that if IsTopLevelWindowOfComposition(aWindow) returns
890 // true, we should ignore the message here, see the comment in
891 // OnIMESetContext() for the detail.
892 if (wParam && gIMM32Handler && !IsTopLevelWindowOfComposition(aWindow)) {
893 if (gIMM32Handler->CommitCompositionOnPreviousWindow(aWindow)) {
894 CancelComposition(aWindow);
898 // Dispatch message to the plug-in.
899 // XXX When a windowless plug-in gets focus, we should send
900 // WM_IME_SETCONTEXT
901 aWindow->DispatchPluginEvent(WM_IME_SETCONTEXT, wParam, lParam, false);
903 // We should send WM_IME_SETCONTEXT to the DefWndProc here. It shouldn't
904 // be received on ancestor windows, see OnIMESetContext() for the detail.
905 aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(),
906 WM_IME_SETCONTEXT, wParam, lParam);
908 // Don't synchronously dispatch the pending events when we receive
909 // WM_IME_SETCONTEXT because we get it during plugin destruction.
910 // (bug 491848)
911 aResult.mConsumed = true;
912 return true;
915 bool
916 nsIMM32Handler::OnCharOnPlugin(nsWindow* aWindow,
917 WPARAM wParam,
918 LPARAM lParam,
919 MSGResult& aResult)
921 // We should never consume char message on windowless plugin.
922 aResult.mConsumed = false;
923 if (IsIMECharRecordsEmpty()) {
924 return false;
927 WPARAM recWParam;
928 LPARAM recLParam;
929 DequeueIMECharRecords(recWParam, recLParam);
930 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
931 ("IMM32: OnCharOnPlugin, aWindow=%p, wParam=%08x, lParam=%08x,\n",
932 aWindow->GetWindowHandle(), wParam, lParam));
933 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
934 (" recorded: wParam=%08x, lParam=%08x\n",
935 recWParam, recLParam));
936 // If an unexpected char message comes, we should reset the records,
937 // of course, this shouldn't happen.
938 if (recWParam != wParam || recLParam != lParam) {
939 ResetIMECharRecords();
941 // WM_CHAR on plug-in is always handled by nsWindow.
942 return false;
945 /****************************************************************************
946 * others
947 ****************************************************************************/
949 void
950 nsIMM32Handler::HandleStartComposition(nsWindow* aWindow,
951 const nsIMEContext &aIMEContext)
953 NS_PRECONDITION(!mIsComposing,
954 "HandleStartComposition is called but mIsComposing is TRUE");
955 NS_PRECONDITION(!aWindow->PluginHasFocus(),
956 "HandleStartComposition should not be called when a plug-in has focus");
958 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow);
959 nsIntPoint point(0, 0);
960 aWindow->InitEvent(selection, &point);
961 aWindow->DispatchWindowEvent(&selection);
962 if (!selection.mSucceeded) {
963 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
964 ("IMM32: HandleStartComposition, FAILED (NS_QUERY_SELECTED_TEXT)\n"));
965 return;
968 mCompositionStart = selection.mReply.mOffset;
969 mLastDispatchedCompositionString.Truncate();
971 WidgetCompositionEvent event(true, NS_COMPOSITION_START, aWindow);
972 aWindow->InitEvent(event, &point);
973 aWindow->DispatchWindowEvent(&event);
975 mIsComposing = true;
976 mComposingWindow = aWindow;
978 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
979 ("IMM32: HandleStartComposition, START composition, mCompositionStart=%ld\n",
980 mCompositionStart));
983 bool
984 nsIMM32Handler::HandleComposition(nsWindow* aWindow,
985 const nsIMEContext &aIMEContext,
986 LPARAM lParam)
988 NS_PRECONDITION(!aWindow->PluginHasFocus(),
989 "HandleComposition should not be called when a plug-in has focus");
991 // for bug #60050
992 // MS-IME 95/97/98/2000 may send WM_IME_COMPOSITION with non-conversion
993 // mode before it send WM_IME_STARTCOMPOSITION.
994 // However, ATOK sends a WM_IME_COMPOSITION before WM_IME_STARTCOMPOSITION,
995 // and if we access ATOK via some APIs, ATOK will sometimes fail to
996 // initialize its state. If WM_IME_STARTCOMPOSITION is already in the
997 // message queue, we should ignore the strange WM_IME_COMPOSITION message and
998 // skip to the next. So, we should look for next composition message
999 // (WM_IME_STARTCOMPOSITION or WM_IME_ENDCOMPOSITION or WM_IME_COMPOSITION),
1000 // and if it's WM_IME_STARTCOMPOSITION, and one more next composition message
1001 // is WM_IME_COMPOSITION, current IME is ATOK, probably. Otherwise, we
1002 // should start composition forcibly.
1003 if (!mIsComposing) {
1004 MSG msg1, msg2;
1005 HWND wnd = aWindow->GetWindowHandle();
1006 if (WinUtils::PeekMessage(&msg1, wnd, WM_IME_STARTCOMPOSITION,
1007 WM_IME_COMPOSITION, PM_NOREMOVE) &&
1008 msg1.message == WM_IME_STARTCOMPOSITION &&
1009 WinUtils::PeekMessage(&msg2, wnd, WM_IME_ENDCOMPOSITION,
1010 WM_IME_COMPOSITION, PM_NOREMOVE) &&
1011 msg2.message == WM_IME_COMPOSITION) {
1012 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1013 ("IMM32: HandleComposition, Ignores due to find a WM_IME_STARTCOMPOSITION\n"));
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, aIMEContext);
1028 GetCompositionString(aIMEContext, GCS_RESULTSTR);
1030 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1031 ("IMM32: HandleComposition, GCS_RESULTSTR\n"));
1033 DispatchTextEvent(aWindow, aIMEContext, false);
1034 HandleEndComposition(aWindow);
1036 if (!IS_COMPOSING_LPARAM(lParam)) {
1037 return ShouldDrawCompositionStringOurselves();
1043 // This provides us with a composition string
1045 if (!mIsComposing) {
1046 HandleStartComposition(aWindow, aIMEContext);
1049 //--------------------------------------------------------
1050 // 1. Get GCS_COMPSTR
1051 //--------------------------------------------------------
1052 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1053 ("IMM32: HandleComposition, GCS_COMPSTR\n"));
1055 GetCompositionString(aIMEContext, GCS_COMPSTR);
1057 if (!IS_COMPOSING_LPARAM(lParam)) {
1058 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1059 ("IMM32: HandleComposition, lParam doesn't indicate composing, "
1060 "mCompositionString=\"%s\", mLastDispatchedCompositionString=\"%s\"",
1061 NS_ConvertUTF16toUTF8(mCompositionString).get(),
1062 NS_ConvertUTF16toUTF8(mLastDispatchedCompositionString).get()));
1064 // If composition string isn't changed, we can trust the lParam.
1065 // So, we need to do nothing.
1066 if (mLastDispatchedCompositionString == 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 text event with
1073 // empty string.
1074 if (mCompositionString.IsEmpty()) {
1075 DispatchTextEvent(aWindow, aIMEContext, false);
1076 return ShouldDrawCompositionStringOurselves();
1079 // Otherwise, we cannot trust the lParam value. We might need to
1080 // dispatch text event with the latest composition string information.
1083 // See https://bugzilla.mozilla.org/show_bug.cgi?id=296339
1084 if (mCompositionString.IsEmpty() && !startCompositionMessageHasBeenSent) {
1085 // In this case, maybe, the sender is MSPinYin. That sends *only*
1086 // WM_IME_COMPOSITION with GCS_COMP* and GCS_RESULT* when
1087 // user inputted the Chinese full stop. So, that doesn't send
1088 // WM_IME_STARTCOMPOSITION and WM_IME_ENDCOMPOSITION.
1089 // If WM_IME_STARTCOMPOSITION was not sent and the composition
1090 // string is null (it indicates the composition transaction ended),
1091 // WM_IME_ENDCOMPOSITION may not be sent. If so, we cannot run
1092 // HandleEndComposition() in other place.
1093 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1094 ("IMM32: HandleComposition, Aborting GCS_COMPSTR\n"));
1095 HandleEndComposition(aWindow);
1096 return IS_COMMITTING_LPARAM(lParam);
1099 //--------------------------------------------------------
1100 // 2. Get GCS_COMPCLAUSE
1101 //--------------------------------------------------------
1102 long clauseArrayLength =
1103 ::ImmGetCompositionStringW(aIMEContext.get(), GCS_COMPCLAUSE, nullptr, 0);
1104 clauseArrayLength /= sizeof(uint32_t);
1106 if (clauseArrayLength > 0) {
1107 nsresult rv = EnsureClauseArray(clauseArrayLength);
1108 NS_ENSURE_SUCCESS(rv, false);
1110 // Intelligent ABC IME (Simplified Chinese IME, the code page is 936)
1111 // will crash in ImmGetCompositionStringW for GCS_COMPCLAUSE (bug 424663).
1112 // See comment 35 of the bug for the detail. Therefore, we should use A
1113 // API for it, however, we should not kill Unicode support on all IMEs.
1114 bool useA_API = !(sIMEProperty & IME_PROP_UNICODE);
1116 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1117 ("IMM32: HandleComposition, GCS_COMPCLAUSE, useA_API=%s\n",
1118 useA_API ? "TRUE" : "FALSE"));
1120 long clauseArrayLength2 =
1121 useA_API ?
1122 ::ImmGetCompositionStringA(aIMEContext.get(), GCS_COMPCLAUSE,
1123 mClauseArray.Elements(),
1124 mClauseArray.Capacity() * sizeof(uint32_t)) :
1125 ::ImmGetCompositionStringW(aIMEContext.get(), GCS_COMPCLAUSE,
1126 mClauseArray.Elements(),
1127 mClauseArray.Capacity() * sizeof(uint32_t));
1128 clauseArrayLength2 /= sizeof(uint32_t);
1130 if (clauseArrayLength != clauseArrayLength2) {
1131 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1132 ("IMM32: HandleComposition, GCS_COMPCLAUSE, clauseArrayLength=%ld but clauseArrayLength2=%ld\n",
1133 clauseArrayLength, clauseArrayLength2));
1134 if (clauseArrayLength > clauseArrayLength2)
1135 clauseArrayLength = clauseArrayLength2;
1138 if (useA_API) {
1139 // Convert each values of sIMECompClauseArray. The values mean offset of
1140 // the clauses in ANSI string. But we need the values in Unicode string.
1141 nsAutoCString compANSIStr;
1142 if (ConvertToANSIString(mCompositionString, GetKeyboardCodePage(),
1143 compANSIStr)) {
1144 uint32_t maxlen = compANSIStr.Length();
1145 mClauseArray[0] = 0; // first value must be 0
1146 for (int32_t i = 1; i < clauseArrayLength; i++) {
1147 uint32_t len = std::min(mClauseArray[i], maxlen);
1148 mClauseArray[i] = ::MultiByteToWideChar(GetKeyboardCodePage(),
1149 MB_PRECOMPOSED,
1150 (LPCSTR)compANSIStr.get(),
1151 len, nullptr, 0);
1156 // compClauseArrayLength may be negative. I.e., ImmGetCompositionStringW
1157 // may return an error code.
1158 mClauseArray.SetLength(std::max<long>(0, clauseArrayLength));
1160 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1161 ("IMM32: HandleComposition, GCS_COMPCLAUSE, mClauseLength=%ld\n",
1162 mClauseArray.Length()));
1164 //--------------------------------------------------------
1165 // 3. Get GCS_COMPATTR
1166 //--------------------------------------------------------
1167 // This provides us with the attribute string necessary
1168 // for doing hiliting
1169 long attrArrayLength =
1170 ::ImmGetCompositionStringW(aIMEContext.get(), GCS_COMPATTR, nullptr, 0);
1171 attrArrayLength /= sizeof(uint8_t);
1173 if (attrArrayLength > 0) {
1174 nsresult rv = EnsureAttributeArray(attrArrayLength);
1175 NS_ENSURE_SUCCESS(rv, false);
1176 attrArrayLength =
1177 ::ImmGetCompositionStringW(aIMEContext.get(), GCS_COMPATTR,
1178 mAttributeArray.Elements(),
1179 mAttributeArray.Capacity() * sizeof(uint8_t));
1182 // attrStrLen may be negative. I.e., ImmGetCompositionStringW may return an
1183 // error code.
1184 mAttributeArray.SetLength(std::max<long>(0, attrArrayLength));
1186 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1187 ("IMM32: HandleComposition, GCS_COMPATTR, mAttributeLength=%ld\n",
1188 mAttributeArray.Length()));
1190 //--------------------------------------------------------
1191 // 4. Get GCS_CURSOPOS
1192 //--------------------------------------------------------
1193 // Some IMEs (e.g., the standard IME for Korean) don't have caret position.
1194 if (lParam & GCS_CURSORPOS) {
1195 mCursorPosition =
1196 ::ImmGetCompositionStringW(aIMEContext.get(), GCS_CURSORPOS, nullptr, 0);
1197 if (mCursorPosition < 0) {
1198 mCursorPosition = NO_IME_CARET; // The result is error
1200 } else {
1201 mCursorPosition = NO_IME_CARET;
1204 NS_ASSERTION(mCursorPosition <= (long)mCompositionString.Length(),
1205 "illegal pos");
1207 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1208 ("IMM32: HandleComposition, GCS_CURSORPOS, mCursorPosition=%d\n",
1209 mCursorPosition));
1211 //--------------------------------------------------------
1212 // 5. Send the text event
1213 //--------------------------------------------------------
1214 DispatchTextEvent(aWindow, aIMEContext);
1216 return ShouldDrawCompositionStringOurselves();
1219 void
1220 nsIMM32Handler::HandleEndComposition(nsWindow* aWindow)
1222 NS_PRECONDITION(mIsComposing,
1223 "HandleEndComposition is called but mIsComposing is FALSE");
1224 NS_PRECONDITION(!aWindow->PluginHasFocus(),
1225 "HandleComposition should not be called when a plug-in has focus");
1227 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1228 ("IMM32: HandleEndComposition\n"));
1230 WidgetCompositionEvent event(true, NS_COMPOSITION_END, aWindow);
1231 nsIntPoint point(0, 0);
1233 if (mNativeCaretIsCreated) {
1234 ::DestroyCaret();
1235 mNativeCaretIsCreated = false;
1238 aWindow->InitEvent(event, &point);
1239 // The last dispatched composition string must be the committed string.
1240 event.data = mLastDispatchedCompositionString;
1241 aWindow->DispatchWindowEvent(&event);
1242 mIsComposing = false;
1243 mComposingWindow = nullptr;
1244 mLastDispatchedCompositionString.Truncate();
1247 static void
1248 DumpReconvertString(RECONVERTSTRING* aReconv)
1250 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1251 (" dwSize=%ld, dwVersion=%ld, dwStrLen=%ld, dwStrOffset=%ld\n",
1252 aReconv->dwSize, aReconv->dwVersion,
1253 aReconv->dwStrLen, aReconv->dwStrOffset));
1254 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1255 (" dwCompStrLen=%ld, dwCompStrOffset=%ld, dwTargetStrLen=%ld, dwTargetStrOffset=%ld\n",
1256 aReconv->dwCompStrLen, aReconv->dwCompStrOffset,
1257 aReconv->dwTargetStrLen, aReconv->dwTargetStrOffset));
1258 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1259 (" result str=\"%s\"\n",
1260 NS_ConvertUTF16toUTF8(
1261 nsAutoString((char16_t*)((char*)(aReconv) + aReconv->dwStrOffset),
1262 aReconv->dwStrLen)).get()));
1265 bool
1266 nsIMM32Handler::HandleReconvert(nsWindow* aWindow,
1267 LPARAM lParam,
1268 LRESULT *oResult)
1270 *oResult = 0;
1271 RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
1273 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow);
1274 nsIntPoint point(0, 0);
1275 aWindow->InitEvent(selection, &point);
1276 aWindow->DispatchWindowEvent(&selection);
1277 if (!selection.mSucceeded) {
1278 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1279 ("IMM32: HandleReconvert, FAILED (NS_QUERY_SELECTED_TEXT)\n"));
1280 return false;
1283 uint32_t len = selection.mReply.mString.Length();
1284 uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
1286 if (!pReconv) {
1287 // Return need size to reconvert.
1288 if (len == 0) {
1289 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1290 ("IMM32: HandleReconvert, There are not selected text\n"));
1291 return false;
1293 *oResult = needSize;
1294 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1295 ("IMM32: HandleReconvert, SUCCEEDED result=%ld\n",
1296 *oResult));
1297 return true;
1300 if (pReconv->dwSize < needSize) {
1301 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1302 ("IMM32: HandleReconvert, FAILED pReconv->dwSize=%ld, needSize=%ld\n",
1303 pReconv->dwSize, needSize));
1304 return false;
1307 *oResult = needSize;
1309 // Fill reconvert struct
1310 pReconv->dwVersion = 0;
1311 pReconv->dwStrLen = len;
1312 pReconv->dwStrOffset = sizeof(RECONVERTSTRING);
1313 pReconv->dwCompStrLen = len;
1314 pReconv->dwCompStrOffset = 0;
1315 pReconv->dwTargetStrLen = len;
1316 pReconv->dwTargetStrOffset = 0;
1318 ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)),
1319 selection.mReply.mString.get(), len * sizeof(WCHAR));
1321 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1322 ("IMM32: HandleReconvert, SUCCEEDED result=%ld\n",
1323 *oResult));
1324 DumpReconvertString(pReconv);
1326 return true;
1329 bool
1330 nsIMM32Handler::HandleQueryCharPosition(nsWindow* aWindow,
1331 LPARAM lParam,
1332 LRESULT *oResult)
1334 uint32_t len = mIsComposing ? mCompositionString.Length() : 0;
1335 *oResult = false;
1336 IMECHARPOSITION* pCharPosition = reinterpret_cast<IMECHARPOSITION*>(lParam);
1337 if (!pCharPosition) {
1338 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1339 ("IMM32: HandleQueryCharPosition, FAILED (pCharPosition is null)\n"));
1340 return false;
1342 if (pCharPosition->dwSize < sizeof(IMECHARPOSITION)) {
1343 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1344 ("IMM32: HandleReconvert, FAILED, pCharPosition->dwSize=%ld, sizeof(IMECHARPOSITION)=%ld\n",
1345 pCharPosition->dwSize, sizeof(IMECHARPOSITION)));
1346 return false;
1348 if (::GetFocus() != aWindow->GetWindowHandle()) {
1349 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1350 ("IMM32: HandleReconvert, FAILED, ::GetFocus()=%08x, OurWindowHandle=%08x\n",
1351 ::GetFocus(), aWindow->GetWindowHandle()));
1352 return false;
1354 if (pCharPosition->dwCharPos > len) {
1355 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1356 ("IMM32: HandleQueryCharPosition, FAILED, pCharPosition->dwCharPos=%ld, len=%ld\n",
1357 pCharPosition->dwCharPos, len));
1358 return false;
1361 nsIntRect r;
1362 bool ret =
1363 GetCharacterRectOfSelectedTextAt(aWindow, pCharPosition->dwCharPos, r);
1364 NS_ENSURE_TRUE(ret, false);
1366 nsIntRect screenRect;
1367 // We always need top level window that is owner window of the popup window
1368 // even if the content of the popup window has focus.
1369 ResolveIMECaretPos(aWindow->GetTopLevelWindow(false),
1370 r, nullptr, screenRect);
1371 pCharPosition->pt.x = screenRect.x;
1372 pCharPosition->pt.y = screenRect.y;
1374 pCharPosition->cLineHeight = r.height;
1376 // XXX we should use NS_QUERY_EDITOR_RECT event here.
1377 ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument);
1379 *oResult = TRUE;
1381 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1382 ("IMM32: HandleQueryCharPosition, SUCCEEDED\n"));
1383 return true;
1386 bool
1387 nsIMM32Handler::HandleDocumentFeed(nsWindow* aWindow,
1388 LPARAM lParam,
1389 LRESULT *oResult)
1391 *oResult = 0;
1392 RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
1394 nsIntPoint point(0, 0);
1396 bool hasCompositionString =
1397 mIsComposing && ShouldDrawCompositionStringOurselves();
1399 int32_t targetOffset, targetLength;
1400 if (!hasCompositionString) {
1401 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow);
1402 aWindow->InitEvent(selection, &point);
1403 aWindow->DispatchWindowEvent(&selection);
1404 if (!selection.mSucceeded) {
1405 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1406 ("IMM32: HandleDocumentFeed, FAILED (NS_QUERY_SELECTED_TEXT)\n"));
1407 return false;
1409 targetOffset = int32_t(selection.mReply.mOffset);
1410 targetLength = int32_t(selection.mReply.mString.Length());
1411 } else {
1412 targetOffset = int32_t(mCompositionStart);
1413 targetLength = int32_t(mCompositionString.Length());
1416 // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
1417 // we cannot support this message when the current offset is larger than
1418 // INT32_MAX.
1419 if (targetOffset < 0 || targetLength < 0 ||
1420 targetOffset + targetLength < 0) {
1421 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1422 ("IMM32: HandleDocumentFeed, FAILED (The selection is out of range)\n"));
1423 return false;
1426 // Get all contents of the focused editor.
1427 WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, aWindow);
1428 textContent.InitForQueryTextContent(0, UINT32_MAX);
1429 aWindow->InitEvent(textContent, &point);
1430 aWindow->DispatchWindowEvent(&textContent);
1431 if (!textContent.mSucceeded) {
1432 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1433 ("IMM32: HandleDocumentFeed, FAILED (NS_QUERY_TEXT_CONTENT)\n"));
1434 return false;
1437 nsAutoString str(textContent.mReply.mString);
1438 if (targetOffset > int32_t(str.Length())) {
1439 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1440 ("IMM32: HandleDocumentFeed, FAILED (The caret offset is invalid)\n"));
1441 return false;
1444 // Get the focused paragraph, we decide that it starts from the previous CRLF
1445 // (or start of the editor) to the next one (or the end of the editor).
1446 int32_t paragraphStart = str.RFind("\n", false, targetOffset, -1) + 1;
1447 int32_t paragraphEnd =
1448 str.Find("\r", false, targetOffset + targetLength, -1);
1449 if (paragraphEnd < 0) {
1450 paragraphEnd = str.Length();
1452 nsDependentSubstring paragraph(str, paragraphStart,
1453 paragraphEnd - paragraphStart);
1455 uint32_t len = paragraph.Length();
1456 uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
1458 if (!pReconv) {
1459 *oResult = needSize;
1460 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1461 ("IMM32: HandleDocumentFeed, SUCCEEDED result=%ld\n",
1462 *oResult));
1463 return true;
1466 if (pReconv->dwSize < needSize) {
1467 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1468 ("IMM32: HandleDocumentFeed, FAILED pReconv->dwSize=%ld, needSize=%ld\n",
1469 pReconv->dwSize, needSize));
1470 return false;
1473 // Fill reconvert struct
1474 pReconv->dwVersion = 0;
1475 pReconv->dwStrLen = len;
1476 pReconv->dwStrOffset = sizeof(RECONVERTSTRING);
1477 if (hasCompositionString) {
1478 pReconv->dwCompStrLen = targetLength;
1479 pReconv->dwCompStrOffset =
1480 (targetOffset - paragraphStart) * sizeof(WCHAR);
1481 // Set composition target clause information
1482 uint32_t offset, length;
1483 if (!GetTargetClauseRange(&offset, &length)) {
1484 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1485 ("IMM32: HandleDocumentFeed, FAILED, by GetTargetClauseRange\n"));
1486 return false;
1488 pReconv->dwTargetStrLen = length;
1489 pReconv->dwTargetStrOffset = (offset - paragraphStart) * sizeof(WCHAR);
1490 } else {
1491 pReconv->dwTargetStrLen = targetLength;
1492 pReconv->dwTargetStrOffset =
1493 (targetOffset - paragraphStart) * sizeof(WCHAR);
1494 // There is no composition string, so, the length is zero but we should
1495 // set the cursor offset to the composition str offset.
1496 pReconv->dwCompStrLen = 0;
1497 pReconv->dwCompStrOffset = pReconv->dwTargetStrOffset;
1500 *oResult = needSize;
1501 ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)),
1502 paragraph.BeginReading(), len * sizeof(WCHAR));
1504 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1505 ("IMM32: HandleDocumentFeed, SUCCEEDED result=%ld\n",
1506 *oResult));
1507 DumpReconvertString(pReconv);
1509 return true;
1512 bool
1513 nsIMM32Handler::CommitCompositionOnPreviousWindow(nsWindow* aWindow)
1515 if (!mComposingWindow || mComposingWindow == aWindow) {
1516 return false;
1519 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1520 ("IMM32: CommitCompositionOnPreviousWindow, mIsComposing=%s, mIsComposingOnPlugin=%s\n",
1521 mIsComposing ? "TRUE" : "FALSE", mIsComposingOnPlugin ? "TRUE" : "FALSE"));
1523 // If we have composition, we should dispatch composition events internally.
1524 if (mIsComposing) {
1525 nsIMEContext IMEContext(mComposingWindow->GetWindowHandle());
1526 NS_ASSERTION(IMEContext.IsValid(), "IME context must be valid");
1528 DispatchTextEvent(mComposingWindow, IMEContext, false);
1529 HandleEndComposition(mComposingWindow);
1530 return true;
1533 // XXX When plug-in has composition, we should commit composition on the
1534 // plug-in. However, we need some more work for that.
1535 return mIsComposingOnPlugin;
1538 static uint32_t
1539 PlatformToNSAttr(uint8_t aAttr)
1541 switch (aAttr)
1543 case ATTR_INPUT_ERROR:
1544 // case ATTR_FIXEDCONVERTED:
1545 case ATTR_INPUT:
1546 return NS_TEXTRANGE_RAWINPUT;
1547 case ATTR_CONVERTED:
1548 return NS_TEXTRANGE_CONVERTEDTEXT;
1549 case ATTR_TARGET_NOTCONVERTED:
1550 return NS_TEXTRANGE_SELECTEDRAWTEXT;
1551 case ATTR_TARGET_CONVERTED:
1552 return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
1553 default:
1554 NS_ASSERTION(false, "unknown attribute");
1555 return NS_TEXTRANGE_CARETPOSITION;
1559 #ifdef PR_LOGGING
1560 static const char*
1561 GetRangeTypeName(uint32_t aRangeType)
1563 switch (aRangeType) {
1564 case NS_TEXTRANGE_RAWINPUT:
1565 return "NS_TEXTRANGE_RAWINPUT";
1566 case NS_TEXTRANGE_CONVERTEDTEXT:
1567 return "NS_TEXTRANGE_CONVERTEDTEXT";
1568 case NS_TEXTRANGE_SELECTEDRAWTEXT:
1569 return "NS_TEXTRANGE_SELECTEDRAWTEXT";
1570 case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT:
1571 return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT";
1572 case NS_TEXTRANGE_CARETPOSITION:
1573 return "NS_TEXTRANGE_CARETPOSITION";
1574 default:
1575 return "UNKNOWN SELECTION TYPE!!";
1578 #endif
1580 void
1581 nsIMM32Handler::DispatchTextEvent(nsWindow* aWindow,
1582 const nsIMEContext &aIMEContext,
1583 bool aCheckAttr)
1585 NS_ASSERTION(mIsComposing, "conflict state");
1586 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1587 ("IMM32: DispatchTextEvent, aCheckAttr=%s\n",
1588 aCheckAttr ? "TRUE": "FALSE"));
1590 // If we don't need to draw composition string ourselves and this is not
1591 // commit event (i.e., under composing), we don't need to fire text event
1592 // during composing.
1593 if (aCheckAttr && !ShouldDrawCompositionStringOurselves()) {
1594 // But we need to adjust composition window pos and native caret pos, here.
1595 SetIMERelatedWindowsPos(aWindow, aIMEContext);
1596 return;
1599 nsRefPtr<nsWindow> kungFuDeathGrip(aWindow);
1601 nsIntPoint point(0, 0);
1603 if (mCompositionString != mLastDispatchedCompositionString) {
1604 WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE,
1605 aWindow);
1606 aWindow->InitEvent(compositionUpdate, &point);
1607 compositionUpdate.data = mCompositionString;
1608 mLastDispatchedCompositionString = mCompositionString;
1610 aWindow->DispatchWindowEvent(&compositionUpdate);
1612 if (!mIsComposing || aWindow->Destroyed()) {
1613 return;
1615 SetIMERelatedWindowsPos(aWindow, aIMEContext);
1618 WidgetTextEvent event(true, NS_TEXT_TEXT, aWindow);
1620 aWindow->InitEvent(event, &point);
1622 if (aCheckAttr) {
1623 event.mRanges = CreateTextRangeArray();
1626 event.theText = mCompositionString.get();
1628 aWindow->DispatchWindowEvent(&event);
1630 // Calling SetIMERelatedWindowsPos will be failure on e10s at this point.
1631 // text event will notify NOTIFY_IME_OF_COMPOSITION_UPDATE, then
1632 // it will call SetIMERelatedWindowsPos.
1635 already_AddRefed<TextRangeArray>
1636 nsIMM32Handler::CreateTextRangeArray()
1638 // Sogou (Simplified Chinese IME) returns contradictory values: The cursor
1639 // position is actual cursor position. However, other values (composition
1640 // string and attributes) are empty. So, if you want to remove following
1641 // assertion, be careful.
1642 NS_ASSERTION(ShouldDrawCompositionStringOurselves(),
1643 "CreateTextRangeArray is called when we don't need to fire text event");
1645 nsRefPtr<TextRangeArray> textRangeArray = new TextRangeArray();
1647 TextRange range;
1648 if (mClauseArray.Length() == 0) {
1649 // Some IMEs don't return clause array information, then, we assume that
1650 // all characters in the composition string are in one clause.
1651 range.mStartOffset = 0;
1652 range.mEndOffset = mCompositionString.Length();
1653 range.mRangeType = NS_TEXTRANGE_RAWINPUT;
1654 textRangeArray->AppendElement(range);
1656 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1657 ("IMM32: CreateTextRangeArray, mClauseLength=0\n"));
1658 } else {
1659 // iterate over the attributes
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 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1665 ("IMM32: CreateTextRangeArray, mClauseArray[%ld]=%lu. "
1666 "This is larger than mCompositionString.Length()=%lu\n",
1667 i + 1, current, mCompositionString.Length()));
1668 current = int32_t(mCompositionString.Length());
1671 range.mRangeType = PlatformToNSAttr(mAttributeArray[lastOffset]);
1672 range.mStartOffset = lastOffset;
1673 range.mEndOffset = current;
1674 textRangeArray->AppendElement(range);
1676 lastOffset = current;
1678 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1679 ("IMM32: CreateTextRangeArray, index=%ld, rangeType=%s, range=[%lu-%lu]\n",
1680 i, GetRangeTypeName(range.mRangeType), range.mStartOffset,
1681 range.mEndOffset));
1685 if (mCursorPosition == NO_IME_CARET) {
1686 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1687 ("IMM32: CreateTextRangeArray, no caret\n"));
1688 return textRangeArray.forget();
1691 int32_t cursor = mCursorPosition;
1692 if (uint32_t(cursor) > mCompositionString.Length()) {
1693 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1694 ("IMM32: CreateTextRangeArray, mCursorPosition=%ld. "
1695 "This is larger than mCompositionString.Length()=%lu\n",
1696 mCursorPosition, mCompositionString.Length()));
1697 cursor = mCompositionString.Length();
1700 range.mStartOffset = range.mEndOffset = cursor;
1701 range.mRangeType = NS_TEXTRANGE_CARETPOSITION;
1702 textRangeArray->AppendElement(range);
1704 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1705 ("IMM32: CreateTextRangeArray, caret position=%ld\n",
1706 range.mStartOffset));
1708 return textRangeArray.forget();
1711 void
1712 nsIMM32Handler::GetCompositionString(const nsIMEContext &aIMEContext,
1713 DWORD aIndex)
1715 // Retrieve the size of the required output buffer.
1716 long lRtn = ::ImmGetCompositionStringW(aIMEContext.get(), aIndex, nullptr, 0);
1717 if (lRtn < 0 ||
1718 !mCompositionString.SetLength((lRtn / sizeof(WCHAR)) + 1, mozilla::fallible_t())) {
1719 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1720 ("IMM32: GetCompositionString, FAILED by OOM\n"));
1721 return; // Error or out of memory.
1724 // Actually retrieve the composition string information.
1725 lRtn = ::ImmGetCompositionStringW(aIMEContext.get(), aIndex,
1726 (LPVOID)mCompositionString.BeginWriting(),
1727 lRtn + sizeof(WCHAR));
1728 mCompositionString.SetLength(lRtn / sizeof(WCHAR));
1730 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1731 ("IMM32: GetCompositionString, SUCCEEDED mCompositionString=\"%s\"\n",
1732 NS_ConvertUTF16toUTF8(mCompositionString).get()));
1735 bool
1736 nsIMM32Handler::GetTargetClauseRange(uint32_t *aOffset, uint32_t *aLength)
1738 NS_ENSURE_TRUE(aOffset, false);
1739 NS_ENSURE_TRUE(mIsComposing, false);
1740 NS_ENSURE_TRUE(ShouldDrawCompositionStringOurselves(), false);
1742 bool found = false;
1743 *aOffset = mCompositionStart;
1744 for (uint32_t i = 0; i < mAttributeArray.Length(); i++) {
1745 if (mAttributeArray[i] == ATTR_TARGET_NOTCONVERTED ||
1746 mAttributeArray[i] == ATTR_TARGET_CONVERTED) {
1747 *aOffset = mCompositionStart + i;
1748 found = true;
1749 break;
1753 if (!aLength) {
1754 return true;
1757 if (!found) {
1758 // The all composition string is targetted when there is no ATTR_TARGET_*
1759 // clause. E.g., there is only ATTR_INPUT
1760 *aLength = mCompositionString.Length();
1761 return true;
1764 uint32_t offsetInComposition = *aOffset - mCompositionStart;
1765 *aLength = mCompositionString.Length() - offsetInComposition;
1766 for (uint32_t i = offsetInComposition; i < mAttributeArray.Length(); i++) {
1767 if (mAttributeArray[i] != ATTR_TARGET_NOTCONVERTED &&
1768 mAttributeArray[i] != ATTR_TARGET_CONVERTED) {
1769 *aLength = i - offsetInComposition;
1770 break;
1773 return true;
1776 bool
1777 nsIMM32Handler::ConvertToANSIString(const nsAFlatString& aStr, UINT aCodePage,
1778 nsACString& aANSIStr)
1780 int len = ::WideCharToMultiByte(aCodePage, 0,
1781 (LPCWSTR)aStr.get(), aStr.Length(),
1782 nullptr, 0, nullptr, nullptr);
1783 NS_ENSURE_TRUE(len >= 0, false);
1785 if (!aANSIStr.SetLength(len, mozilla::fallible_t())) {
1786 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1787 ("IMM32: ConvertToANSIString, FAILED by OOM\n"));
1788 return false;
1790 ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(),
1791 (LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr);
1792 return true;
1795 bool
1796 nsIMM32Handler::GetCharacterRectOfSelectedTextAt(nsWindow* aWindow,
1797 uint32_t aOffset,
1798 nsIntRect &aCharRect)
1800 nsIntPoint point(0, 0);
1802 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow);
1803 aWindow->InitEvent(selection, &point);
1804 aWindow->DispatchWindowEvent(&selection);
1805 if (!selection.mSucceeded) {
1806 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1807 ("IMM32: GetCharacterRectOfSelectedTextAt, aOffset=%lu, FAILED (NS_QUERY_SELECTED_TEXT)\n",
1808 aOffset));
1809 return false;
1812 uint32_t offset = selection.mReply.mOffset + aOffset;
1813 bool useCaretRect = selection.mReply.mString.IsEmpty();
1814 if (useCaretRect && ShouldDrawCompositionStringOurselves() &&
1815 mIsComposing && !mCompositionString.IsEmpty()) {
1816 // There is not a normal selection, but we have composition string.
1817 // XXX mnakano - Should we implement NS_QUERY_IME_SELECTED_TEXT?
1818 useCaretRect = false;
1819 if (mCursorPosition != NO_IME_CARET) {
1820 uint32_t cursorPosition =
1821 std::min<uint32_t>(mCursorPosition, mCompositionString.Length());
1822 NS_ASSERTION(offset >= cursorPosition, "offset is less than cursorPosition!");
1823 offset -= cursorPosition;
1827 nsIntRect r;
1828 if (!useCaretRect) {
1829 WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT, aWindow);
1830 charRect.InitForQueryTextRect(offset, 1);
1831 aWindow->InitEvent(charRect, &point);
1832 aWindow->DispatchWindowEvent(&charRect);
1833 if (charRect.mSucceeded) {
1834 aCharRect = charRect.mReply.mRect;
1835 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1836 ("IMM32: GetCharacterRectOfSelectedTextAt, aOffset=%lu, SUCCEEDED\n",
1837 aOffset));
1838 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1839 ("IMM32: GetCharacterRectOfSelectedTextAt, aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }\n",
1840 aCharRect.x, aCharRect.y, aCharRect.width, aCharRect.height));
1841 return true;
1845 return GetCaretRect(aWindow, aCharRect);
1848 bool
1849 nsIMM32Handler::GetCaretRect(nsWindow* aWindow, nsIntRect &aCaretRect)
1851 nsIntPoint point(0, 0);
1853 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow);
1854 aWindow->InitEvent(selection, &point);
1855 aWindow->DispatchWindowEvent(&selection);
1856 if (!selection.mSucceeded) {
1857 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1858 ("IMM32: GetCaretRect, FAILED (NS_QUERY_SELECTED_TEXT)\n"));
1859 return false;
1862 uint32_t offset = selection.mReply.mOffset;
1864 WidgetQueryContentEvent caretRect(true, NS_QUERY_CARET_RECT, aWindow);
1865 caretRect.InitForQueryCaretRect(offset);
1866 aWindow->InitEvent(caretRect, &point);
1867 aWindow->DispatchWindowEvent(&caretRect);
1868 if (!caretRect.mSucceeded) {
1869 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1870 ("IMM32: GetCaretRect, FAILED (NS_QUERY_CARET_RECT)\n"));
1871 return false;
1873 aCaretRect = caretRect.mReply.mRect;
1874 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1875 ("IMM32: GetCaretRect, SUCCEEDED, aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }\n",
1876 aCaretRect.x, aCaretRect.y, aCaretRect.width, aCaretRect.height));
1877 return true;
1880 bool
1881 nsIMM32Handler::SetIMERelatedWindowsPos(nsWindow* aWindow,
1882 const nsIMEContext &aIMEContext)
1884 nsIntRect r;
1885 // Get first character rect of current a normal selected text or a composing
1886 // string.
1887 bool ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r);
1888 NS_ENSURE_TRUE(ret, false);
1889 nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
1890 nsIntRect firstSelectedCharRect;
1891 ResolveIMECaretPos(toplevelWindow, r, aWindow, firstSelectedCharRect);
1893 // Set native caret size/position to our caret. Some IMEs honor it. E.g.,
1894 // "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified
1895 // Chinese) on XP.
1896 nsIntRect caretRect(firstSelectedCharRect);
1897 if (GetCaretRect(aWindow, r)) {
1898 ResolveIMECaretPos(toplevelWindow, r, aWindow, caretRect);
1899 } else {
1900 NS_WARNING("failed to get caret rect");
1901 caretRect.width = 1;
1903 if (!mNativeCaretIsCreated) {
1904 mNativeCaretIsCreated = ::CreateCaret(aWindow->GetWindowHandle(), nullptr,
1905 caretRect.width, caretRect.height);
1906 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1907 ("IMM32: SetIMERelatedWindowsPos, mNativeCaretIsCreated=%s, width=%ld height=%ld\n",
1908 mNativeCaretIsCreated ? "TRUE" : "FALSE",
1909 caretRect.width, caretRect.height));
1911 ::SetCaretPos(caretRect.x, caretRect.y);
1913 if (ShouldDrawCompositionStringOurselves()) {
1914 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1915 ("IMM32: SetIMERelatedWindowsPos, Set candidate window\n"));
1917 // Get a rect of first character in current target in composition string.
1918 if (mIsComposing && !mCompositionString.IsEmpty()) {
1919 // If there are no targetted selection, we should use it's first character
1920 // rect instead.
1921 uint32_t offset;
1922 if (!GetTargetClauseRange(&offset)) {
1923 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1924 ("IMM32: SetIMERelatedWindowsPos, FAILED, by GetTargetClauseRange\n"));
1925 return false;
1927 ret = GetCharacterRectOfSelectedTextAt(aWindow,
1928 offset - mCompositionStart, r);
1929 NS_ENSURE_TRUE(ret, false);
1930 } else {
1931 // If there are no composition string, we should use a first character
1932 // rect.
1933 ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r);
1934 NS_ENSURE_TRUE(ret, false);
1936 nsIntRect firstTargetCharRect;
1937 ResolveIMECaretPos(toplevelWindow, r, aWindow, firstTargetCharRect);
1939 // Move the candidate window to first character position of the target.
1940 CANDIDATEFORM candForm;
1941 candForm.dwIndex = 0;
1942 candForm.dwStyle = CFS_EXCLUDE;
1943 candForm.ptCurrentPos.x = firstTargetCharRect.x;
1944 candForm.ptCurrentPos.y = firstTargetCharRect.y;
1945 candForm.rcArea.right = candForm.rcArea.left = candForm.ptCurrentPos.x;
1946 candForm.rcArea.top = candForm.ptCurrentPos.y;
1947 candForm.rcArea.bottom = candForm.ptCurrentPos.y +
1948 firstTargetCharRect.height;
1949 ::ImmSetCandidateWindow(aIMEContext.get(), &candForm);
1950 } else {
1951 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1952 ("IMM32: SetIMERelatedWindowsPos, Set composition window\n"));
1954 // Move the composition window to caret position (if selected some
1955 // characters, we should use first character rect of them).
1956 // And in this mode, IME adjusts the candidate window position
1957 // automatically. So, we don't need to set it.
1958 COMPOSITIONFORM compForm;
1959 compForm.dwStyle = CFS_POINT;
1960 compForm.ptCurrentPos.x = firstSelectedCharRect.x;
1961 compForm.ptCurrentPos.y = firstSelectedCharRect.y;
1962 ::ImmSetCompositionWindow(aIMEContext.get(), &compForm);
1965 return true;
1968 void
1969 nsIMM32Handler::SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow,
1970 const nsIMEContext& aIMEContext)
1972 WidgetQueryContentEvent editorRectEvent(true, NS_QUERY_EDITOR_RECT, aWindow);
1973 aWindow->InitEvent(editorRectEvent);
1974 aWindow->DispatchWindowEvent(&editorRectEvent);
1975 if (!editorRectEvent.mSucceeded) {
1976 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1977 ("IMM32: SetIMERelatedWindowsPosOnPlugin, "
1978 "FAILED (NS_QUERY_EDITOR_RECT)"));
1979 return;
1982 // Clip the plugin rect by the client rect of the window because composition
1983 // window needs to be specified the position in the client area.
1984 nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
1985 nsIntRect pluginRectInScreen =
1986 editorRectEvent.mReply.mRect + toplevelWindow->WidgetToScreenOffset();
1987 nsIntRect winRectInScreen;
1988 aWindow->GetClientBounds(winRectInScreen);
1989 // composition window cannot be positioned on the edge of client area.
1990 winRectInScreen.width--;
1991 winRectInScreen.height--;
1992 nsIntRect clippedPluginRect;
1993 clippedPluginRect.x =
1994 std::min(std::max(pluginRectInScreen.x, winRectInScreen.x),
1995 winRectInScreen.XMost());
1996 clippedPluginRect.y =
1997 std::min(std::max(pluginRectInScreen.y, winRectInScreen.y),
1998 winRectInScreen.YMost());
1999 int32_t xMost = std::min(pluginRectInScreen.XMost(), winRectInScreen.XMost());
2000 int32_t yMost = std::min(pluginRectInScreen.YMost(), winRectInScreen.YMost());
2001 clippedPluginRect.width = std::max(0, xMost - clippedPluginRect.x);
2002 clippedPluginRect.height = std::max(0, yMost - clippedPluginRect.y);
2003 clippedPluginRect -= aWindow->WidgetToScreenOffset();
2005 // Cover the plugin with native caret. This prevents IME's window and plugin
2006 // overlap.
2007 if (mNativeCaretIsCreated) {
2008 ::DestroyCaret();
2010 mNativeCaretIsCreated =
2011 ::CreateCaret(aWindow->GetWindowHandle(), nullptr,
2012 clippedPluginRect.width, clippedPluginRect.height);
2013 ::SetCaretPos(clippedPluginRect.x, clippedPluginRect.y);
2015 // Set the composition window to bottom-left of the clipped plugin.
2016 // As far as we know, there is no IME for RTL language. Therefore, this code
2017 // must not need to take care of RTL environment.
2018 COMPOSITIONFORM compForm;
2019 compForm.dwStyle = CFS_POINT;
2020 compForm.ptCurrentPos.x = clippedPluginRect.BottomLeft().x;
2021 compForm.ptCurrentPos.y = clippedPluginRect.BottomLeft().y;
2022 if (!::ImmSetCompositionWindow(aIMEContext.get(), &compForm)) {
2023 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
2024 ("IMM32: SetIMERelatedWindowsPosOnPlugin, "
2025 "FAILED to set composition window"));
2026 return;
2030 void
2031 nsIMM32Handler::ResolveIMECaretPos(nsIWidget* aReferenceWidget,
2032 nsIntRect& aCursorRect,
2033 nsIWidget* aNewOriginWidget,
2034 nsIntRect& aOutRect)
2036 aOutRect = aCursorRect;
2038 if (aReferenceWidget == aNewOriginWidget)
2039 return;
2041 if (aReferenceWidget)
2042 aOutRect.MoveBy(aReferenceWidget->WidgetToScreenOffset());
2044 if (aNewOriginWidget)
2045 aOutRect.MoveBy(-aNewOriginWidget->WidgetToScreenOffset());
2048 bool
2049 nsIMM32Handler::OnMouseEvent(nsWindow* aWindow, LPARAM lParam, int aAction,
2050 MSGResult& aResult)
2052 aResult.mConsumed = false; // always call next wndprc
2054 if (!sWM_MSIME_MOUSE || !mIsComposing ||
2055 !ShouldDrawCompositionStringOurselves()) {
2056 return false;
2059 nsIntPoint cursor(LOWORD(lParam), HIWORD(lParam));
2060 WidgetQueryContentEvent charAtPt(true, NS_QUERY_CHARACTER_AT_POINT, aWindow);
2061 aWindow->InitEvent(charAtPt, &cursor);
2062 aWindow->DispatchWindowEvent(&charAtPt);
2063 if (!charAtPt.mSucceeded ||
2064 charAtPt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND ||
2065 charAtPt.mReply.mOffset < mCompositionStart ||
2066 charAtPt.mReply.mOffset >
2067 mCompositionStart + mCompositionString.Length()) {
2068 return false;
2071 // calcurate positioning and offset
2072 // char : JCH1|JCH2|JCH3
2073 // offset: 0011 1122 2233
2074 // positioning: 2301 2301 2301
2075 nsIntRect cursorInTopLevel, cursorRect(cursor, nsIntSize(0, 0));
2076 ResolveIMECaretPos(aWindow, cursorRect,
2077 aWindow->GetTopLevelWindow(false), cursorInTopLevel);
2078 int32_t cursorXInChar = cursorInTopLevel.x - charAtPt.mReply.mRect.x;
2079 // The event might hit to zero-width character, see bug 694913.
2080 // The reason might be:
2081 // * There are some zero-width characters are actually.
2082 // * font-size is specified zero.
2083 // But nobody reproduced this bug actually...
2084 // We should assume that user clicked on right most of the zero-width
2085 // character in such case.
2086 int positioning = 1;
2087 if (charAtPt.mReply.mRect.width > 0) {
2088 positioning = cursorXInChar * 4 / charAtPt.mReply.mRect.width;
2089 positioning = (positioning + 2) % 4;
2092 int offset = charAtPt.mReply.mOffset - mCompositionStart;
2093 if (positioning < 2) {
2094 offset++;
2097 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
2098 ("IMM32: OnMouseEvent, x,y=%ld,%ld, offset=%ld, positioning=%ld\n",
2099 cursor.x, cursor.y, offset, positioning));
2101 // send MS_MSIME_MOUSE message to default IME window.
2102 HWND imeWnd = ::ImmGetDefaultIMEWnd(aWindow->GetWindowHandle());
2103 nsIMEContext IMEContext(aWindow->GetWindowHandle());
2104 return ::SendMessageW(imeWnd, sWM_MSIME_MOUSE,
2105 MAKELONG(MAKEWORD(aAction, positioning), offset),
2106 (LPARAM) IMEContext.get()) == 1;
2109 /* static */ bool
2110 nsIMM32Handler::OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
2111 MSGResult& aResult)
2113 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
2114 ("IMM32: OnKeyDownEvent, hWnd=%08x, wParam=%08x, lParam=%08x\n",
2115 aWindow->GetWindowHandle(), wParam, lParam));
2116 aResult.mConsumed = false;
2117 switch (wParam) {
2118 case VK_TAB:
2119 case VK_PRIOR:
2120 case VK_NEXT:
2121 case VK_END:
2122 case VK_HOME:
2123 case VK_LEFT:
2124 case VK_UP:
2125 case VK_RIGHT:
2126 case VK_DOWN:
2127 // If IME didn't process the key message (the virtual key code wasn't
2128 // converted to VK_PROCESSKEY), and the virtual key code event causes
2129 // to move caret, we should cancel the composition here. Then, this
2130 // event will be dispatched.
2131 // XXX I think that we should dispatch all key events during composition,
2132 // and nsEditor should cancel/commit the composition if it *thinks*
2133 // it's needed.
2134 if (IsComposingOnOurEditor()) {
2135 // NOTE: We don't need to cancel the composition on another window.
2136 CancelComposition(aWindow, false);
2138 return false;
2139 default:
2140 return false;