Backed out changeset 8517afe50156 (bug 540456) for reftest failures.
[gecko.git] / widget / windows / nsIMM32Handler.cpp
blobb331b4ef80f11e67f41d5ff372a1e8abc35eae89
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 // 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()
159 if (mIsComposing) {
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"));
166 nsresult
167 nsIMM32Handler::EnsureClauseArray(int32_t aCount)
169 NS_ENSURE_ARG_MIN(aCount, 0);
170 mClauseArray.SetCapacity(aCount + 32);
171 return NS_OK;
174 nsresult
175 nsIMM32Handler::EnsureAttributeArray(int32_t aCount)
177 NS_ENSURE_ARG_MIN(aCount, 0);
178 mAttributeArray.SetCapacity(aCount + 64);
179 return NS_OK;
182 /* static */ void
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)) {
194 return;
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);
208 if (associated) {
209 IMEContext.Disassociate();
213 /* static */ void
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)) {
225 return;
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);
238 if (associated) {
239 IMEContext.Disassociate();
243 /* static */ bool
244 nsIMM32Handler::ProcessInputLangChangeMessage(nsWindow* aWindow,
245 WPARAM wParam,
246 LPARAM lParam,
247 MSGResult& aResult)
249 aResult.mResult = 0;
250 aResult.mConsumed = false;
251 // We don't need to create the instance of the handler here.
252 if (gIMM32Handler) {
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.
258 Terminate();
259 // Don't return as "processed", the messages should be processed on nsWindow
260 // too.
261 return false;
264 /* static */ bool
265 nsIMM32Handler::ProcessMessage(nsWindow* aWindow, UINT msg,
266 WPARAM &wParam, LPARAM &lParam,
267 MSGResult& aResult)
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
275 // to the plug-in.
276 if (aWindow->PluginHasFocus() || IsComposingOnPlugin()) {
277 return ProcessMessageForPlugin(aWindow, msg, wParam, lParam, aResult);
280 aResult.mResult = 0;
281 switch (msg) {
282 case WM_LBUTTONDOWN:
283 case WM_MBUTTONDOWN:
284 case WM_RBUTTONDOWN: {
285 // We don't need to create the instance of the handler here.
286 if (!gIMM32Handler) {
287 return false;
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);
305 case WM_IME_CHAR:
306 return OnIMEChar(aWindow, wParam, lParam, aResult);
307 case WM_IME_NOTIFY:
308 return OnIMENotify(aWindow, wParam, lParam, aResult);
309 case WM_IME_REQUEST:
310 EnsureHandlerInstance();
311 return gIMM32Handler->OnIMERequest(aWindow, wParam, lParam, aResult);
312 case WM_IME_SELECT:
313 return OnIMESelect(aWindow, wParam, lParam, aResult);
314 case WM_IME_SETCONTEXT:
315 return OnIMESetContext(aWindow, wParam, lParam, aResult);
316 case WM_KEYDOWN:
317 return OnKeyDownEvent(aWindow, wParam, lParam, aResult);
318 case WM_CHAR:
319 if (!gIMM32Handler) {
320 return false;
322 return gIMM32Handler->OnChar(aWindow, wParam, lParam, aResult);
323 default:
324 return false;
328 /* static */ bool
329 nsIMM32Handler::ProcessMessageForPlugin(nsWindow* aWindow, UINT msg,
330 WPARAM &wParam, LPARAM &lParam,
331 MSGResult& aResult)
333 aResult.mResult = 0;
334 aResult.mConsumed = false;
335 switch (msg) {
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,
343 aResult);
344 case WM_IME_STARTCOMPOSITION:
345 EnsureHandlerInstance();
346 return gIMM32Handler->OnIMEStartCompositionOnPlugin(aWindow, wParam,
347 lParam, aResult);
348 case WM_IME_ENDCOMPOSITION:
349 EnsureHandlerInstance();
350 return gIMM32Handler->OnIMEEndCompositionOnPlugin(aWindow, wParam, lParam,
351 aResult);
352 case WM_IME_CHAR:
353 EnsureHandlerInstance();
354 return gIMM32Handler->OnIMECharOnPlugin(aWindow, wParam, lParam, aResult);
355 case WM_IME_SETCONTEXT:
356 return OnIMESetContextOnPlugin(aWindow, wParam, lParam, aResult);
357 case WM_CHAR:
358 if (!gIMM32Handler) {
359 return false;
361 return gIMM32Handler->OnCharOnPlugin(aWindow, wParam, lParam, aResult);
362 case WM_IME_COMPOSITIONFULL:
363 case WM_IME_CONTROL:
364 case WM_IME_KEYDOWN:
365 case WM_IME_KEYUP:
366 case WM_IME_REQUEST:
367 case WM_IME_SELECT:
368 aResult.mConsumed =
369 aWindow->DispatchPluginEvent(msg, wParam, lParam, false);
370 return true;
372 return false;
375 /****************************************************************************
376 * message handlers
377 ****************************************************************************/
379 void
380 nsIMM32Handler::OnInputLangChange(nsWindow* aWindow,
381 WPARAM wParam,
382 LPARAM lParam,
383 MSGResult& aResult)
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");
392 if (mIsComposing) {
393 HandleEndComposition(aWindow);
396 aResult.mConsumed = false;
399 bool
400 nsIMM32Handler::OnIMEStartComposition(nsWindow* aWindow,
401 MSGResult& aResult)
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();
407 if (mIsComposing) {
408 NS_WARNING("Composition has been already started");
409 return true;
412 nsIMEContext IMEContext(aWindow->GetWindowHandle());
413 HandleStartComposition(aWindow, IMEContext);
414 return true;
417 bool
418 nsIMM32Handler::OnIMEComposition(nsWindow* aWindow,
419 WPARAM wParam,
420 LPARAM lParam,
421 MSGResult& aResult)
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);
439 return true;
442 bool
443 nsIMM32Handler::OnIMEEndComposition(nsWindow* aWindow,
444 MSGResult& aResult)
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();
451 if (!mIsComposing) {
452 return true;
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.
458 MSG compositionMsg;
459 if (WinUtils::PeekMessage(&compositionMsg, aWindow->GetWindowHandle(),
460 WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION,
461 PM_NOREMOVE) &&
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..."));
467 return true;
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
475 // string?
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);
488 return true;
491 /* static */ bool
492 nsIMM32Handler::OnIMEChar(nsWindow* aWindow,
493 WPARAM wParam,
494 LPARAM lParam,
495 MSGResult& aResult)
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;
508 return true;
511 /* static */ bool
512 nsIMM32Handler::OnIMECompositionFull(nsWindow* aWindow,
513 MSGResult& aResult)
515 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
516 ("IMM32: OnIMECompositionFull, hWnd=%08x\n",
517 aWindow->GetWindowHandle()));
519 // not implement yet
520 aResult.mConsumed = false;
521 return true;
524 /* static */ bool
525 nsIMM32Handler::OnIMENotify(nsWindow* aWindow,
526 WPARAM wParam,
527 LPARAM lParam,
528 MSGResult& aResult)
530 #ifdef PR_LOGGING
531 switch (wParam) {
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));
536 break;
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));
541 break;
542 case IMN_CLOSESTATUSWINDOW:
543 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
544 ("IMM32: OnIMENotify, hWnd=%08x, IMN_CLOSESTATUSWINDOW\n",
545 aWindow->GetWindowHandle()));
546 break;
547 case IMN_GUIDELINE:
548 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
549 ("IMM32: OnIMENotify, hWnd=%08x, IMN_GUIDELINE\n",
550 aWindow->GetWindowHandle()));
551 break;
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));
556 break;
557 case IMN_OPENSTATUSWINDOW:
558 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
559 ("IMM32: OnIMENotify, hWnd=%08x, IMN_OPENSTATUSWINDOW\n",
560 aWindow->GetWindowHandle()));
561 break;
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));
566 break;
567 case IMN_SETCOMPOSITIONFONT:
568 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
569 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONFONT\n",
570 aWindow->GetWindowHandle()));
571 break;
572 case IMN_SETCOMPOSITIONWINDOW:
573 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
574 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONWINDOW\n",
575 aWindow->GetWindowHandle()));
576 break;
577 case IMN_SETCONVERSIONMODE:
578 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
579 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCONVERSIONMODE\n",
580 aWindow->GetWindowHandle()));
581 break;
582 case IMN_SETOPENSTATUS:
583 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
584 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETOPENSTATUS\n",
585 aWindow->GetWindowHandle()));
586 break;
587 case IMN_SETSENTENCEMODE:
588 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
589 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETSENTENCEMODE\n",
590 aWindow->GetWindowHandle()));
591 break;
592 case IMN_SETSTATUSWINDOWPOS:
593 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
594 ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETSTATUSWINDOWPOS\n",
595 aWindow->GetWindowHandle()));
596 break;
597 case IMN_PRIVATE:
598 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
599 ("IMM32: OnIMENotify, hWnd=%08x, IMN_PRIVATE\n",
600 aWindow->GetWindowHandle()));
601 break;
603 #endif // PR_LOGGING
605 // not implement yet
606 aResult.mConsumed = false;
607 return true;
610 bool
611 nsIMM32Handler::OnIMERequest(nsWindow* aWindow,
612 WPARAM wParam,
613 LPARAM lParam,
614 MSGResult& aResult)
616 switch (wParam) {
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);
622 return true;
623 case IMR_QUERYCHARPOSITION:
624 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
625 ("IMM32: OnIMERequest, hWnd=%08x, IMR_QUERYCHARPOSITION\n",
626 aWindow->GetWindowHandle()));
627 aResult.mConsumed =
628 HandleQueryCharPosition(aWindow, lParam, &aResult.mResult);
629 return true;
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);
635 return true;
636 default:
637 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
638 ("IMM32: OnIMERequest, hWnd=%08x, wParam=%08x\n",
639 aWindow->GetWindowHandle(), wParam));
640 aResult.mConsumed = false;
641 return true;
645 /* static */ bool
646 nsIMM32Handler::OnIMESelect(nsWindow* aWindow,
647 WPARAM wParam,
648 LPARAM lParam,
649 MSGResult& aResult)
651 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
652 ("IMM32: OnIMESelect, hWnd=%08x, wParam=%08x, lParam=%08x\n",
653 aWindow->GetWindowHandle(), wParam, lParam));
655 // not implement yet
656 aResult.mConsumed = false;
657 return true;
660 /* static */ bool
661 nsIMM32Handler::OnIMESetContext(nsWindow* aWindow,
662 WPARAM wParam,
663 LPARAM lParam,
664 MSGResult& aResult)
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"));
682 return true;
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) {
689 cancelComposition =
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
707 // another window.
708 if (cancelComposition) {
709 CancelComposition(aWindow, true);
712 aResult.mConsumed = true;
713 return true;
716 bool
717 nsIMM32Handler::OnChar(nsWindow* aWindow,
718 WPARAM wParam,
719 LPARAM lParam,
720 MSGResult& aResult)
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;
729 WPARAM recWParam;
730 LPARAM recLParam;
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 ****************************************************************************/
755 bool
756 nsIMM32Handler::OnIMEStartCompositionOnPlugin(nsWindow* aWindow,
757 WPARAM wParam,
758 LPARAM lParam,
759 MSGResult& aResult)
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;
766 aResult.mConsumed =
767 aWindow->DispatchPluginEvent(WM_IME_STARTCOMPOSITION, wParam, lParam,
768 false);
769 return true;
772 bool
773 nsIMM32Handler::OnIMECompositionOnPlugin(nsWindow* aWindow,
774 WPARAM wParam,
775 LPARAM lParam,
776 MSGResult& aResult)
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;
799 aResult.mConsumed =
800 aWindow->DispatchPluginEvent(WM_IME_COMPOSITION, wParam, lParam, true);
801 return true;
804 bool
805 nsIMM32Handler::OnIMEEndCompositionOnPlugin(nsWindow* aWindow,
806 WPARAM wParam,
807 LPARAM lParam,
808 MSGResult& aResult)
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;
816 aResult.mConsumed =
817 aWindow->DispatchPluginEvent(WM_IME_ENDCOMPOSITION, wParam, lParam,
818 false);
819 return true;
822 bool
823 nsIMM32Handler::OnIMECharOnPlugin(nsWindow* aWindow,
824 WPARAM wParam,
825 LPARAM lParam,
826 MSGResult& aResult)
828 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
829 ("IMM32: OnIMECharOnPlugin, hWnd=%08x, char=%08x, scancode=%08x\n",
830 aWindow->GetWindowHandle(), wParam, lParam));
832 aResult.mConsumed =
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);
840 return true;
843 /* static */ bool
844 nsIMM32Handler::OnIMESetContextOnPlugin(nsWindow* aWindow,
845 WPARAM wParam,
846 LPARAM lParam,
847 MSGResult& aResult)
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
866 // WM_IME_SETCONTEXT
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.
876 // (bug 491848)
877 aResult.mConsumed = true;
878 return true;
881 bool
882 nsIMM32Handler::OnCharOnPlugin(nsWindow* aWindow,
883 WPARAM wParam,
884 LPARAM lParam,
885 MSGResult& aResult)
887 // We should never consume char message on windowless plugin.
888 aResult.mConsumed = false;
889 if (IsIMECharRecordsEmpty()) {
890 return false;
893 WPARAM recWParam;
894 LPARAM recLParam;
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.
908 return false;
911 /****************************************************************************
912 * others
913 ****************************************************************************/
915 void
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"));
931 return;
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);
943 mIsComposing = true;
944 mComposingWindow = aWindow;
946 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
947 ("IMM32: HandleStartComposition, START composition, mCompositionStart=%ld\n",
948 mCompositionStart));
951 bool
952 nsIMM32Handler::HandleComposition(nsWindow* aWindow,
953 const nsIMEContext &aIMEContext,
954 LPARAM lParam)
956 NS_PRECONDITION(!aWindow->PluginHasFocus(),
957 "HandleComposition should not be called when a plug-in has focus");
959 // for bug #60050
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.
971 if (!mIsComposing) {
972 MSG msg1, msg2;
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)) {
992 if (!mIsComposing) {
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
1041 // empty string.
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 =
1089 useA_API ?
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;
1106 if (useA_API) {
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(),
1111 compANSIStr)) {
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(),
1117 MB_PRECOMPOSED,
1118 (LPCSTR)compANSIStr.get(),
1119 len, nullptr, 0);
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);
1144 attrArrayLength =
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
1151 // error code.
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) {
1163 mCursorPosition =
1164 ::ImmGetCompositionStringW(aIMEContext.get(), GCS_CURSORPOS, nullptr, 0);
1165 if (mCursorPosition < 0) {
1166 mCursorPosition = NO_IME_CARET; // The result is error
1168 } else {
1169 mCursorPosition = NO_IME_CARET;
1172 NS_ASSERTION(mCursorPosition <= (long)mCompositionString.Length(),
1173 "illegal pos");
1175 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1176 ("IMM32: HandleComposition, GCS_CURSORPOS, mCursorPosition=%d\n",
1177 mCursorPosition));
1179 //--------------------------------------------------------
1180 // 5. Send the text event
1181 //--------------------------------------------------------
1182 DispatchTextEvent(aWindow, aIMEContext);
1184 return ShouldDrawCompositionStringOurselves();
1187 void
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) {
1202 ::DestroyCaret();
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();
1215 static void
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()));
1233 bool
1234 nsIMM32Handler::HandleReconvert(nsWindow* aWindow,
1235 LPARAM lParam,
1236 LRESULT *oResult)
1238 *oResult = 0;
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"));
1248 return false;
1251 uint32_t len = selection.mReply.mString.Length();
1252 uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
1254 if (!pReconv) {
1255 // Return need size to reconvert.
1256 if (len == 0) {
1257 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1258 ("IMM32: HandleReconvert, There are not selected text\n"));
1259 return false;
1261 *oResult = needSize;
1262 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1263 ("IMM32: HandleReconvert, SUCCEEDED result=%ld\n",
1264 *oResult));
1265 return true;
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));
1272 return false;
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",
1291 *oResult));
1292 DumpReconvertString(pReconv);
1294 return true;
1297 bool
1298 nsIMM32Handler::HandleQueryCharPosition(nsWindow* aWindow,
1299 LPARAM lParam,
1300 LRESULT *oResult)
1302 uint32_t len = mIsComposing ? mCompositionString.Length() : 0;
1303 *oResult = false;
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"));
1308 return false;
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)));
1314 return false;
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()));
1320 return false;
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));
1326 return false;
1329 nsIntRect r;
1330 bool ret =
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);
1347 *oResult = TRUE;
1349 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1350 ("IMM32: HandleQueryCharPosition, SUCCEEDED\n"));
1351 return true;
1354 bool
1355 nsIMM32Handler::HandleDocumentFeed(nsWindow* aWindow,
1356 LPARAM lParam,
1357 LRESULT *oResult)
1359 *oResult = 0;
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"));
1375 return false;
1377 targetOffset = int32_t(selection.mReply.mOffset);
1378 targetLength = int32_t(selection.mReply.mString.Length());
1379 } else {
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
1386 // INT32_MAX.
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"));
1391 return false;
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"));
1402 return false;
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"));
1409 return false;
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);
1426 if (!pReconv) {
1427 *oResult = needSize;
1428 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1429 ("IMM32: HandleDocumentFeed, SUCCEEDED result=%ld\n",
1430 *oResult));
1431 return true;
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));
1438 return false;
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"));
1454 return false;
1456 pReconv->dwTargetStrLen = length;
1457 pReconv->dwTargetStrOffset = (offset - paragraphStart) * sizeof(WCHAR);
1458 } else {
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",
1474 *oResult));
1475 DumpReconvertString(pReconv);
1477 return true;
1480 bool
1481 nsIMM32Handler::CommitCompositionOnPreviousWindow(nsWindow* aWindow)
1483 if (!mComposingWindow || mComposingWindow == aWindow) {
1484 return false;
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.
1492 if (mIsComposing) {
1493 nsIMEContext IMEContext(mComposingWindow->GetWindowHandle());
1494 NS_ASSERTION(IMEContext.IsValid(), "IME context must be valid");
1496 DispatchTextEvent(mComposingWindow, IMEContext, false);
1497 HandleEndComposition(mComposingWindow);
1498 return true;
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;
1506 static uint32_t
1507 PlatformToNSAttr(uint8_t aAttr)
1509 switch (aAttr)
1511 case ATTR_INPUT_ERROR:
1512 // case ATTR_FIXEDCONVERTED:
1513 case ATTR_INPUT:
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;
1521 default:
1522 NS_ASSERTION(false, "unknown attribute");
1523 return NS_TEXTRANGE_CARETPOSITION;
1527 #ifdef PR_LOGGING
1528 static const char*
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";
1542 default:
1543 return "UNKNOWN SELECTION TYPE!!";
1546 #endif
1548 void
1549 nsIMM32Handler::DispatchTextEvent(nsWindow* aWindow,
1550 const nsIMEContext &aIMEContext,
1551 bool aCheckAttr)
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);
1564 return;
1567 nsRefPtr<nsWindow> kungFuDeathGrip(aWindow);
1569 nsIntPoint point(0, 0);
1571 if (mCompositionString != mLastDispatchedCompositionString) {
1572 WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE,
1573 aWindow);
1574 aWindow->InitEvent(compositionUpdate, &point);
1575 compositionUpdate.data = mCompositionString;
1576 mLastDispatchedCompositionString = mCompositionString;
1578 aWindow->DispatchWindowEvent(&compositionUpdate);
1580 if (!mIsComposing || aWindow->Destroyed()) {
1581 return;
1583 SetIMERelatedWindowsPos(aWindow, aIMEContext);
1586 WidgetTextEvent event(true, NS_TEXT_TEXT, aWindow);
1588 aWindow->InitEvent(event, &point);
1590 nsAutoTArray<TextRange, 4> textRanges;
1592 if (aCheckAttr) {
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);
1606 void
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");
1616 TextRange range;
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"));
1627 } else {
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,
1649 range.mEndOffset));
1653 if (mCursorPosition == NO_IME_CARET) {
1654 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1655 ("IMM32: GetTextRangeList, no caret\n"));
1656 return;
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));
1676 void
1677 nsIMM32Handler::GetCompositionString(const nsIMEContext &aIMEContext,
1678 DWORD aIndex)
1680 // Retrieve the size of the required output buffer.
1681 long lRtn = ::ImmGetCompositionStringW(aIMEContext.get(), aIndex, nullptr, 0);
1682 if (lRtn < 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()));
1700 bool
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);
1707 bool found = 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;
1713 found = true;
1714 break;
1718 if (!aLength) {
1719 return true;
1722 if (!found) {
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();
1726 return true;
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;
1735 break;
1738 return true;
1741 bool
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"));
1753 return false;
1755 ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(),
1756 (LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr);
1757 return true;
1760 bool
1761 nsIMM32Handler::GetCharacterRectOfSelectedTextAt(nsWindow* aWindow,
1762 uint32_t aOffset,
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",
1773 aOffset));
1774 return false;
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;
1792 nsIntRect r;
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",
1802 aOffset));
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));
1806 return true;
1810 return GetCaretRect(aWindow, aCharRect);
1813 bool
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"));
1824 return false;
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"));
1836 return false;
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));
1842 return true;
1845 bool
1846 nsIMM32Handler::SetIMERelatedWindowsPos(nsWindow* aWindow,
1847 const nsIMEContext &aIMEContext)
1849 nsIntRect r;
1850 // Get first character rect of current a normal selected text or a composing
1851 // string.
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
1860 // Chinese) on XP.
1861 nsIntRect caretRect(firstSelectedCharRect);
1862 if (GetCaretRect(aWindow, r)) {
1863 ResolveIMECaretPos(toplevelWindow, r, aWindow, caretRect);
1864 } else {
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
1885 // rect instead.
1886 uint32_t offset;
1887 if (!GetTargetClauseRange(&offset)) {
1888 PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
1889 ("IMM32: SetIMERelatedWindowsPos, FAILED, by GetTargetClauseRange\n"));
1890 return false;
1892 ret = GetCharacterRectOfSelectedTextAt(aWindow,
1893 offset - mCompositionStart, r);
1894 NS_ENSURE_TRUE(ret, false);
1895 } else {
1896 // If there are no composition string, we should use a first character
1897 // rect.
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);
1915 } else {
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);
1930 return true;
1933 void
1934 nsIMM32Handler::ResolveIMECaretPos(nsIWidget* aReferenceWidget,
1935 nsIntRect& aCursorRect,
1936 nsIWidget* aNewOriginWidget,
1937 nsIntRect& aOutRect)
1939 aOutRect = aCursorRect;
1941 if (aReferenceWidget == aNewOriginWidget)
1942 return;
1944 if (aReferenceWidget)
1945 aOutRect.MoveBy(aReferenceWidget->WidgetToScreenOffset());
1947 if (aNewOriginWidget)
1948 aOutRect.MoveBy(-aNewOriginWidget->WidgetToScreenOffset());
1951 bool
1952 nsIMM32Handler::OnMouseEvent(nsWindow* aWindow, LPARAM lParam, int aAction,
1953 MSGResult& aResult)
1955 aResult.mConsumed = false; // always call next wndprc
1957 if (!sWM_MSIME_MOUSE || !mIsComposing ||
1958 !ShouldDrawCompositionStringOurselves()) {
1959 return false;
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()) {
1971 return false;
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) {
1997 offset++;
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;
2012 /* static */ bool
2013 nsIMM32Handler::OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
2014 MSGResult& aResult)
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;
2020 switch (wParam) {
2021 case VK_TAB:
2022 case VK_PRIOR:
2023 case VK_NEXT:
2024 case VK_END:
2025 case VK_HOME:
2026 case VK_LEFT:
2027 case VK_UP:
2028 case VK_RIGHT:
2029 case VK_DOWN:
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*
2036 // it's needed.
2037 if (IsComposingOnOurEditor()) {
2038 // NOTE: We don't need to cancel the composition on another window.
2039 CancelComposition(aWindow, false);
2041 return false;
2042 default:
2043 return false;