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/. */
6 
7 #include "mozilla/Logging.h"
8 
9 #include "IMMHandler.h"
10 #include "nsWindow.h"
11 #include "nsWindowDefs.h"
12 #include "WinUtils.h"
13 #include "KeyboardLayout.h"
14 #include <algorithm>
15 
16 #include "mozilla/CheckedInt.h"
17 #include "mozilla/MiscEvents.h"
18 #include "mozilla/TextEvents.h"
19 #include "mozilla/WindowsVersion.h"
20 
21 #ifndef IME_PROP_ACCEPT_WIDE_VKEY
22 #define IME_PROP_ACCEPT_WIDE_VKEY 0x20
23 #endif
24 
25 //-------------------------------------------------------------------------
26 //
27 // from
28 // http://download.microsoft.com/download/6/0/9/60908e9e-d2c1-47db-98f6-216af76a235f/msime.h
29 // The document for this has been removed from MSDN...
30 //
31 //-------------------------------------------------------------------------
32 
33 #define RWM_MOUSE TEXT("MSIMEMouseOperation")
34 
35 #define IMEMOUSE_NONE 0x00  // no mouse button was pushed
36 #define IMEMOUSE_LDOWN 0x01
37 #define IMEMOUSE_RDOWN 0x02
38 #define IMEMOUSE_MDOWN 0x04
39 #define IMEMOUSE_WUP 0x10    // wheel up
40 #define IMEMOUSE_WDOWN 0x20  // wheel down
41 
GetBoolName(bool aBool)42 static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
43 
HandleSeparator(nsACString & aDesc)44 static void HandleSeparator(nsACString& aDesc) {
45   if (!aDesc.IsEmpty()) {
46     aDesc.AppendLiteral(" | ");
47   }
48 }
49 
50 class GetIMEGeneralPropertyName : public nsAutoCString {
51  public:
GetIMEGeneralPropertyName(DWORD aFlags)52   explicit GetIMEGeneralPropertyName(DWORD aFlags) {
53     if (!aFlags) {
54       AppendLiteral("no flags");
55       return;
56     }
57     if (aFlags & IME_PROP_AT_CARET) {
58       AppendLiteral("IME_PROP_AT_CARET");
59     }
60     if (aFlags & IME_PROP_SPECIAL_UI) {
61       HandleSeparator(*this);
62       AppendLiteral("IME_PROP_SPECIAL_UI");
63     }
64     if (aFlags & IME_PROP_CANDLIST_START_FROM_1) {
65       HandleSeparator(*this);
66       AppendLiteral("IME_PROP_CANDLIST_START_FROM_1");
67     }
68     if (aFlags & IME_PROP_UNICODE) {
69       HandleSeparator(*this);
70       AppendLiteral("IME_PROP_UNICODE");
71     }
72     if (aFlags & IME_PROP_COMPLETE_ON_UNSELECT) {
73       HandleSeparator(*this);
74       AppendLiteral("IME_PROP_COMPLETE_ON_UNSELECT");
75     }
76     if (aFlags & IME_PROP_ACCEPT_WIDE_VKEY) {
77       HandleSeparator(*this);
78       AppendLiteral("IME_PROP_ACCEPT_WIDE_VKEY");
79     }
80   }
~GetIMEGeneralPropertyName()81   virtual ~GetIMEGeneralPropertyName() {}
82 };
83 
84 class GetIMEUIPropertyName : public nsAutoCString {
85  public:
GetIMEUIPropertyName(DWORD aFlags)86   explicit GetIMEUIPropertyName(DWORD aFlags) {
87     if (!aFlags) {
88       AppendLiteral("no flags");
89       return;
90     }
91     if (aFlags & UI_CAP_2700) {
92       AppendLiteral("UI_CAP_2700");
93     }
94     if (aFlags & UI_CAP_ROT90) {
95       HandleSeparator(*this);
96       AppendLiteral("UI_CAP_ROT90");
97     }
98     if (aFlags & UI_CAP_ROTANY) {
99       HandleSeparator(*this);
100       AppendLiteral("UI_CAP_ROTANY");
101     }
102   }
~GetIMEUIPropertyName()103   virtual ~GetIMEUIPropertyName() {}
104 };
105 
106 class GetWritingModeName : public nsAutoCString {
107  public:
GetWritingModeName(const WritingMode & aWritingMode)108   explicit GetWritingModeName(const WritingMode& aWritingMode) {
109     if (!aWritingMode.IsVertical()) {
110       AssignLiteral("Horizontal");
111       return;
112     }
113     if (aWritingMode.IsVerticalLR()) {
114       AssignLiteral("Vertical (LR)");
115       return;
116     }
117     AssignLiteral("Vertical (RL)");
118   }
~GetWritingModeName()119   virtual ~GetWritingModeName() {}
120 };
121 
122 class GetReconvertStringLog : public nsAutoCString {
123  public:
GetReconvertStringLog(RECONVERTSTRING * aReconv)124   explicit GetReconvertStringLog(RECONVERTSTRING* aReconv) {
125     AssignLiteral("{ dwSize=");
126     AppendInt(static_cast<uint32_t>(aReconv->dwSize));
127     AppendLiteral(", dwVersion=");
128     AppendInt(static_cast<uint32_t>(aReconv->dwVersion));
129     AppendLiteral(", dwStrLen=");
130     AppendInt(static_cast<uint32_t>(aReconv->dwStrLen));
131     AppendLiteral(", dwStrOffset=");
132     AppendInt(static_cast<uint32_t>(aReconv->dwStrOffset));
133     AppendLiteral(", dwCompStrLen=");
134     AppendInt(static_cast<uint32_t>(aReconv->dwCompStrLen));
135     AppendLiteral(", dwCompStrOffset=");
136     AppendInt(static_cast<uint32_t>(aReconv->dwCompStrOffset));
137     AppendLiteral(", dwTargetStrLen=");
138     AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrLen));
139     AppendLiteral(", dwTargetStrOffset=");
140     AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrOffset));
141     AppendLiteral(", result str=\"");
142     if (aReconv->dwStrLen) {
143       char16_t* strStart = reinterpret_cast<char16_t*>(
144           reinterpret_cast<char*>(aReconv) + aReconv->dwStrOffset);
145       nsDependentString str(strStart, aReconv->dwStrLen);
146       Append(NS_ConvertUTF16toUTF8(str));
147     }
148     AppendLiteral("\" }");
149   }
~GetReconvertStringLog()150   virtual ~GetReconvertStringLog() {}
151 };
152 
153 namespace mozilla {
154 namespace widget {
155 
156 static IMMHandler* gIMMHandler = nullptr;
157 
158 LazyLogModule gIMMLog("nsIMM32HandlerWidgets");
159 
160 /******************************************************************************
161  * IMEContext
162  ******************************************************************************/
163 
IMEContext(HWND aWnd)164 IMEContext::IMEContext(HWND aWnd) : mWnd(aWnd), mIMC(::ImmGetContext(aWnd)) {}
165 
IMEContext(nsWindowBase * aWindowBase)166 IMEContext::IMEContext(nsWindowBase* aWindowBase)
167     : mWnd(aWindowBase->GetWindowHandle()),
168       mIMC(::ImmGetContext(aWindowBase->GetWindowHandle())) {}
169 
Init(HWND aWnd)170 void IMEContext::Init(HWND aWnd) {
171   Clear();
172   mWnd = aWnd;
173   mIMC = ::ImmGetContext(mWnd);
174 }
175 
Init(nsWindowBase * aWindowBase)176 void IMEContext::Init(nsWindowBase* aWindowBase) {
177   Init(aWindowBase->GetWindowHandle());
178 }
179 
Clear()180 void IMEContext::Clear() {
181   if (mWnd && mIMC) {
182     ::ImmReleaseContext(mWnd, mIMC);
183   }
184   mWnd = nullptr;
185   mIMC = nullptr;
186 }
187 
188 /******************************************************************************
189  * IMMHandler
190  ******************************************************************************/
191 
192 static UINT sWM_MSIME_MOUSE = 0;  // mouse message for MSIME 98/2000
193 
194 WritingMode IMMHandler::sWritingModeOfCompositionFont;
195 nsString IMMHandler::sIMEName;
196 UINT IMMHandler::sCodePage = 0;
197 DWORD IMMHandler::sIMEProperty = 0;
198 DWORD IMMHandler::sIMEUIProperty = 0;
199 bool IMMHandler::sAssumeVerticalWritingModeNotSupported = false;
200 bool IMMHandler::sHasFocus = false;
201 bool IMMHandler::sNativeCaretIsCreatedForPlugin = false;
202 
203 #define IMPL_IS_IME_ACTIVE(aReadableName, aActualName) \
204   bool IMMHandler::Is##aReadableName##Active() {       \
205     return sIMEName.Equals(aActualName);               \
206   }
207 
208 IMPL_IS_IME_ACTIVE(ATOK2006, u"ATOK 2006")
209 IMPL_IS_IME_ACTIVE(ATOK2007, u"ATOK 2007")
210 IMPL_IS_IME_ACTIVE(ATOK2008, u"ATOK 2008")
211 IMPL_IS_IME_ACTIVE(ATOK2009, u"ATOK 2009")
212 IMPL_IS_IME_ACTIVE(ATOK2010, u"ATOK 2010")
213 // NOTE: Even on Windows for en-US, the name of Google Japanese Input is
214 //       written in Japanese.
215 IMPL_IS_IME_ACTIVE(GoogleJapaneseInput,
216                    u"Google \x65E5\x672C\x8A9E\x5165\x529B "
217                    u"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB")
218 IMPL_IS_IME_ACTIVE(Japanist2003, u"Japanist 2003")
219 
220 #undef IMPL_IS_IME_ACTIVE
221 
222 // static
IsActiveIMEInBlockList()223 bool IMMHandler::IsActiveIMEInBlockList() {
224   if (sIMEName.IsEmpty()) {
225     return false;
226   }
227 #ifdef _WIN64
228   // ATOK started to be TIP of TSF since 2011.  Older than it, i.e., ATOK 2010
229   // and earlier have a lot of problems even for daily use.  Perhaps, the
230   // reason is Win 8 has a lot of changes around IMM-IME support and TSF,
231   // and ATOK 2010 is released earlier than Win 8.
232   // ATOK 2006 crashes while converting a word with candidate window.
233   // ATOK 2007 doesn't paint and resize suggest window and candidate window
234   // correctly (showing white window or too big window).
235   // ATOK 2008 and ATOK 2009 crash when user just opens their open state.
236   // ATOK 2010 isn't installable newly on Win 7 or later, but we have a lot of
237   // crash reports.
238   if (IsWin8OrLater() &&
239       (IsATOK2006Active() || IsATOK2007Active() || IsATOK2008Active() ||
240        IsATOK2009Active() || IsATOK2010Active())) {
241     return true;
242   }
243 #endif  // #ifdef _WIN64
244   return false;
245 }
246 
247 // static
EnsureHandlerInstance()248 void IMMHandler::EnsureHandlerInstance() {
249   if (!gIMMHandler) {
250     gIMMHandler = new IMMHandler();
251   }
252 }
253 
254 // static
Initialize()255 void IMMHandler::Initialize() {
256   if (!sWM_MSIME_MOUSE) {
257     sWM_MSIME_MOUSE = ::RegisterWindowMessage(RWM_MOUSE);
258   }
259   sAssumeVerticalWritingModeNotSupported = Preferences::GetBool(
260       "intl.imm.vertical_writing.always_assume_not_supported", false);
261   InitKeyboardLayout(nullptr, ::GetKeyboardLayout(0));
262 }
263 
264 // static
Terminate()265 void IMMHandler::Terminate() {
266   if (!gIMMHandler) return;
267   delete gIMMHandler;
268   gIMMHandler = nullptr;
269 }
270 
271 // static
IsComposingOnOurEditor()272 bool IMMHandler::IsComposingOnOurEditor() {
273   return gIMMHandler && gIMMHandler->mIsComposing;
274 }
275 
276 // static
IsComposingOnPlugin()277 bool IMMHandler::IsComposingOnPlugin() {
278   return gIMMHandler && gIMMHandler->mIsComposingOnPlugin;
279 }
280 
281 // static
IsComposingWindow(nsWindow * aWindow)282 bool IMMHandler::IsComposingWindow(nsWindow* aWindow) {
283   return gIMMHandler && gIMMHandler->mComposingWindow == aWindow;
284 }
285 
286 // static
IsTopLevelWindowOfComposition(nsWindow * aWindow)287 bool IMMHandler::IsTopLevelWindowOfComposition(nsWindow* aWindow) {
288   if (!gIMMHandler || !gIMMHandler->mComposingWindow) {
289     return false;
290   }
291   HWND wnd = gIMMHandler->mComposingWindow->GetWindowHandle();
292   return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle();
293 }
294 
295 // static
ShouldDrawCompositionStringOurselves()296 bool IMMHandler::ShouldDrawCompositionStringOurselves() {
297   // If current IME has special UI or its composition window should not
298   // positioned to caret position, we should now draw composition string
299   // ourselves.
300   return !(sIMEProperty & IME_PROP_SPECIAL_UI) &&
301          (sIMEProperty & IME_PROP_AT_CARET);
302 }
303 
304 // static
IsVerticalWritingSupported()305 bool IMMHandler::IsVerticalWritingSupported() {
306   // Even if IME claims that they support vertical writing mode but it may not
307   // support vertical writing mode for its candidate window.
308   if (sAssumeVerticalWritingModeNotSupported) {
309     return false;
310   }
311   // Google Japanese Input doesn't support vertical writing mode.  We should
312   // return false if it's active IME.
313   if (IsGoogleJapaneseInputActive()) {
314     return false;
315   }
316   return !!(sIMEUIProperty & (UI_CAP_2700 | UI_CAP_ROT90 | UI_CAP_ROTANY));
317 }
318 
319 // static
InitKeyboardLayout(nsWindow * aWindow,HKL aKeyboardLayout)320 void IMMHandler::InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout) {
321   UINT IMENameLength = ::ImmGetDescriptionW(aKeyboardLayout, nullptr, 0);
322   if (IMENameLength) {
323     // Add room for the terminating null character
324     sIMEName.SetLength(++IMENameLength);
325     IMENameLength =
326         ::ImmGetDescriptionW(aKeyboardLayout, sIMEName.get(), IMENameLength);
327     // Adjust the length to ignore the terminating null character
328     sIMEName.SetLength(IMENameLength);
329   } else {
330     sIMEName.Truncate();
331   }
332 
333   WORD langID = LOWORD(aKeyboardLayout);
334   ::GetLocaleInfoW(MAKELCID(langID, SORT_DEFAULT),
335                    LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
336                    (PWSTR)&sCodePage, sizeof(sCodePage) / sizeof(WCHAR));
337   sIMEProperty = ::ImmGetProperty(aKeyboardLayout, IGP_PROPERTY);
338   sIMEUIProperty = ::ImmGetProperty(aKeyboardLayout, IGP_UI);
339 
340   // If active IME is a TIP of TSF, we cannot retrieve the name with IMM32 API.
341   // For hacking some bugs of some TIP, we should set an IME name from the
342   // pref.
343   if (sCodePage == 932 && sIMEName.IsEmpty()) {
344     Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as",
345                            sIMEName);
346   }
347 
348   // Whether the IME supports vertical writing mode might be changed or
349   // some IMEs may need specific font for their UI.  Therefore, we should
350   // update composition font forcibly here.
351   if (aWindow) {
352     MaybeAdjustCompositionFont(aWindow, sWritingModeOfCompositionFont, true);
353   }
354 
355   MOZ_LOG(gIMMLog, LogLevel::Info,
356           ("InitKeyboardLayout, aKeyboardLayout=%08x (\"%s\"), sCodePage=%lu, "
357            "sIMEProperty=%s, sIMEUIProperty=%s",
358            aKeyboardLayout, NS_ConvertUTF16toUTF8(sIMEName).get(), sCodePage,
359            GetIMEGeneralPropertyName(sIMEProperty).get(),
360            GetIMEUIPropertyName(sIMEUIProperty).get()));
361 }
362 
363 // static
GetKeyboardCodePage()364 UINT IMMHandler::GetKeyboardCodePage() { return sCodePage; }
365 
366 // static
GetIMENotificationRequests()367 IMENotificationRequests IMMHandler::GetIMENotificationRequests() {
368   return IMENotificationRequests(
369       IMENotificationRequests::NOTIFY_POSITION_CHANGE |
370       IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR);
371 }
372 
373 // used for checking the lParam of WM_IME_COMPOSITION
374 #define IS_COMPOSING_LPARAM(lParam) \
375   ((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS))
376 #define IS_COMMITTING_LPARAM(lParam) ((lParam)&GCS_RESULTSTR)
377 // Some IMEs (e.g., the standard IME for Korean) don't have caret position,
378 // then, we should not set caret position to compositionchange event.
379 #define NO_IME_CARET -1
380 
IMMHandler()381 IMMHandler::IMMHandler()
382     : mComposingWindow(nullptr),
383       mCursorPosition(NO_IME_CARET),
384       mCompositionStart(0),
385       mIsComposing(false),
386       mIsComposingOnPlugin(false),
387       mNativeCaretIsCreated(false) {
388   MOZ_LOG(gIMMLog, LogLevel::Debug, ("IMMHandler is created"));
389 }
390 
~IMMHandler()391 IMMHandler::~IMMHandler() {
392   if (mIsComposing) {
393     MOZ_LOG(gIMMLog, LogLevel::Error,
394             ("~IMMHandler, ERROR, the instance is still composing"));
395   }
396   MOZ_LOG(gIMMLog, LogLevel::Debug, ("IMMHandler is destroyed"));
397 }
398 
EnsureClauseArray(int32_t aCount)399 nsresult IMMHandler::EnsureClauseArray(int32_t aCount) {
400   NS_ENSURE_ARG_MIN(aCount, 0);
401   mClauseArray.SetCapacity(aCount + 32);
402   return NS_OK;
403 }
404 
EnsureAttributeArray(int32_t aCount)405 nsresult IMMHandler::EnsureAttributeArray(int32_t aCount) {
406   NS_ENSURE_ARG_MIN(aCount, 0);
407   mAttributeArray.SetCapacity(aCount + 64);
408   return NS_OK;
409 }
410 
411 // static
CommitComposition(nsWindow * aWindow,bool aForce)412 void IMMHandler::CommitComposition(nsWindow* aWindow, bool aForce) {
413   MOZ_LOG(gIMMLog, LogLevel::Info,
414           ("CommitComposition, aForce=%s, aWindow=%p, hWnd=%08x, "
415            "mComposingWindow=%p%s",
416            GetBoolName(aForce), aWindow, aWindow->GetWindowHandle(),
417            gIMMHandler ? gIMMHandler->mComposingWindow : nullptr,
418            gIMMHandler && gIMMHandler->mComposingWindow
419                ? IsComposingOnOurEditor() ? " (composing on editor)"
420                                           : " (composing on plug-in)"
421                : ""));
422   if (!aForce && !IsComposingWindow(aWindow)) {
423     return;
424   }
425 
426   IMEContext context(aWindow);
427   bool associated = context.AssociateDefaultContext();
428   MOZ_LOG(gIMMLog, LogLevel::Info,
429           ("CommitComposition, associated=%s", GetBoolName(associated)));
430 
431   if (context.IsValid()) {
432     ::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
433     ::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0);
434   }
435 
436   if (associated) {
437     context.Disassociate();
438   }
439 }
440 
441 // static
CancelComposition(nsWindow * aWindow,bool aForce)442 void IMMHandler::CancelComposition(nsWindow* aWindow, bool aForce) {
443   MOZ_LOG(gIMMLog, LogLevel::Info,
444           ("CancelComposition, aForce=%s, aWindow=%p, hWnd=%08x, "
445            "mComposingWindow=%p%s",
446            GetBoolName(aForce), aWindow, aWindow->GetWindowHandle(),
447            gIMMHandler ? gIMMHandler->mComposingWindow : nullptr,
448            gIMMHandler && gIMMHandler->mComposingWindow
449                ? IsComposingOnOurEditor() ? " (composing on editor)"
450                                           : " (composing on plug-in)"
451                : ""));
452   if (!aForce && !IsComposingWindow(aWindow)) {
453     return;
454   }
455 
456   IMEContext context(aWindow);
457   bool associated = context.AssociateDefaultContext();
458   MOZ_LOG(gIMMLog, LogLevel::Info,
459           ("CancelComposition, associated=%s", GetBoolName(associated)));
460 
461   if (context.IsValid()) {
462     ::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0);
463   }
464 
465   if (associated) {
466     context.Disassociate();
467   }
468 }
469 
470 // static
OnFocusChange(bool aFocus,nsWindow * aWindow)471 void IMMHandler::OnFocusChange(bool aFocus, nsWindow* aWindow) {
472   MOZ_LOG(gIMMLog, LogLevel::Info,
473           ("OnFocusChange(aFocus=%s, aWindow=%p), sHasFocus=%s, "
474            "IsComposingWindow(aWindow)=%s, aWindow->Destroyed()=%s, "
475            "sNativeCaretIsCreatedForPlugin=%s",
476            GetBoolName(aFocus), aWindow, GetBoolName(sHasFocus),
477            GetBoolName(IsComposingWindow(aWindow)),
478            GetBoolName(aWindow->Destroyed()),
479            GetBoolName(sNativeCaretIsCreatedForPlugin)));
480 
481   if (!aFocus) {
482     if (sNativeCaretIsCreatedForPlugin) {
483       ::DestroyCaret();
484       sNativeCaretIsCreatedForPlugin = false;
485     }
486     if (IsComposingWindow(aWindow) && aWindow->Destroyed()) {
487       CancelComposition(aWindow);
488     }
489   }
490   if (gIMMHandler) {
491     gIMMHandler->mSelection.Clear();
492   }
493   sHasFocus = aFocus;
494 }
495 
496 // static
OnUpdateComposition(nsWindow * aWindow)497 void IMMHandler::OnUpdateComposition(nsWindow* aWindow) {
498   if (!gIMMHandler) {
499     return;
500   }
501 
502   if (aWindow->PluginHasFocus()) {
503     return;
504   }
505 
506   IMEContext context(aWindow);
507   gIMMHandler->SetIMERelatedWindowsPos(aWindow, context);
508 }
509 
510 // static
OnSelectionChange(nsWindow * aWindow,const IMENotification & aIMENotification,bool aIsIMMActive)511 void IMMHandler::OnSelectionChange(nsWindow* aWindow,
512                                    const IMENotification& aIMENotification,
513                                    bool aIsIMMActive) {
514   if (!aIMENotification.mSelectionChangeData.mCausedByComposition &&
515       aIsIMMActive) {
516     MaybeAdjustCompositionFont(
517         aWindow, aIMENotification.mSelectionChangeData.GetWritingMode());
518   }
519   // MaybeAdjustCompositionFont() may create gIMMHandler.  So, check it
520   // after a call of MaybeAdjustCompositionFont().
521   if (gIMMHandler) {
522     gIMMHandler->mSelection.Update(aIMENotification);
523   }
524 }
525 
526 // static
MaybeAdjustCompositionFont(nsWindow * aWindow,const WritingMode & aWritingMode,bool aForceUpdate)527 void IMMHandler::MaybeAdjustCompositionFont(nsWindow* aWindow,
528                                             const WritingMode& aWritingMode,
529                                             bool aForceUpdate) {
530   switch (sCodePage) {
531     case 932:  // Japanese Shift-JIS
532     case 936:  // Simlified Chinese GBK
533     case 949:  // Korean
534     case 950:  // Traditional Chinese Big5
535       EnsureHandlerInstance();
536       break;
537     default:
538       // If there is no instance of nsIMM32Hander, we shouldn't waste footprint.
539       if (!gIMMHandler) {
540         return;
541       }
542   }
543 
544   // Like Navi-Bar of ATOK, some IMEs may require proper composition font even
545   // before sending WM_IME_STARTCOMPOSITION.
546   IMEContext context(aWindow);
547   gIMMHandler->AdjustCompositionFont(aWindow, context, aWritingMode,
548                                      aForceUpdate);
549 }
550 
551 // static
ProcessInputLangChangeMessage(nsWindow * aWindow,WPARAM wParam,LPARAM lParam,MSGResult & aResult)552 bool IMMHandler::ProcessInputLangChangeMessage(nsWindow* aWindow, WPARAM wParam,
553                                                LPARAM lParam,
554                                                MSGResult& aResult) {
555   aResult.mResult = 0;
556   aResult.mConsumed = false;
557   // We don't need to create the instance of the handler here.
558   if (gIMMHandler) {
559     gIMMHandler->OnInputLangChange(aWindow, wParam, lParam, aResult);
560   }
561   InitKeyboardLayout(aWindow, reinterpret_cast<HKL>(lParam));
562   // We can release the instance here, because the instance may be never
563   // used. E.g., the new keyboard layout may not use IME, or it may use TSF.
564   Terminate();
565   // Don't return as "processed", the messages should be processed on nsWindow
566   // too.
567   return false;
568 }
569 
570 // static
ProcessMessage(nsWindow * aWindow,UINT msg,WPARAM & wParam,LPARAM & lParam,MSGResult & aResult)571 bool IMMHandler::ProcessMessage(nsWindow* aWindow, UINT msg, WPARAM& wParam,
572                                 LPARAM& lParam, MSGResult& aResult) {
573   // XXX We store the composing window in mComposingWindow.  If IME messages are
574   // sent to different window, we should commit the old transaction.  And also
575   // if the new window handle is not focused, probably, we should not start
576   // the composition, however, such case should not be, it's just bad scenario.
577 
578   // When a plug-in has focus, we should dispatch the IME events to
579   // the plug-in at first.
580   if (aWindow->PluginHasFocus()) {
581     bool ret = false;
582     if (ProcessMessageForPlugin(aWindow, msg, wParam, lParam, ret, aResult)) {
583       return ret;
584     }
585   }
586 
587   aResult.mResult = 0;
588   switch (msg) {
589     case WM_INPUTLANGCHANGE:
590       return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult);
591     case WM_IME_STARTCOMPOSITION:
592       EnsureHandlerInstance();
593       return gIMMHandler->OnIMEStartComposition(aWindow, aResult);
594     case WM_IME_COMPOSITION:
595       EnsureHandlerInstance();
596       return gIMMHandler->OnIMEComposition(aWindow, wParam, lParam, aResult);
597     case WM_IME_ENDCOMPOSITION:
598       EnsureHandlerInstance();
599       return gIMMHandler->OnIMEEndComposition(aWindow, aResult);
600     case WM_IME_CHAR:
601       return OnIMEChar(aWindow, wParam, lParam, aResult);
602     case WM_IME_NOTIFY:
603       return OnIMENotify(aWindow, wParam, lParam, aResult);
604     case WM_IME_REQUEST:
605       EnsureHandlerInstance();
606       return gIMMHandler->OnIMERequest(aWindow, wParam, lParam, aResult);
607     case WM_IME_SELECT:
608       return OnIMESelect(aWindow, wParam, lParam, aResult);
609     case WM_IME_SETCONTEXT:
610       return OnIMESetContext(aWindow, wParam, lParam, aResult);
611     case WM_KEYDOWN:
612       return OnKeyDownEvent(aWindow, wParam, lParam, aResult);
613     case WM_CHAR:
614       if (!gIMMHandler) {
615         return false;
616       }
617       return gIMMHandler->OnChar(aWindow, wParam, lParam, aResult);
618     default:
619       return false;
620   };
621 }
622 
623 // static
ProcessMessageForPlugin(nsWindow * aWindow,UINT msg,WPARAM & wParam,LPARAM & lParam,bool & aRet,MSGResult & aResult)624 bool IMMHandler::ProcessMessageForPlugin(nsWindow* aWindow, UINT msg,
625                                          WPARAM& wParam, LPARAM& lParam,
626                                          bool& aRet, MSGResult& aResult) {
627   aResult.mResult = 0;
628   aResult.mConsumed = false;
629   switch (msg) {
630     case WM_INPUTLANGCHANGEREQUEST:
631     case WM_INPUTLANGCHANGE:
632       aWindow->DispatchPluginEvent(msg, wParam, lParam, false);
633       aRet = ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult);
634       return true;
635     case WM_IME_CHAR:
636       EnsureHandlerInstance();
637       aRet = gIMMHandler->OnIMECharOnPlugin(aWindow, wParam, lParam, aResult);
638       return true;
639     case WM_IME_SETCONTEXT:
640       aRet = OnIMESetContextOnPlugin(aWindow, wParam, lParam, aResult);
641       return true;
642     case WM_CHAR:
643       if (!gIMMHandler) {
644         return true;
645       }
646       aRet = gIMMHandler->OnCharOnPlugin(aWindow, wParam, lParam, aResult);
647       return true;
648     case WM_IME_COMPOSITIONFULL:
649     case WM_IME_CONTROL:
650     case WM_IME_KEYDOWN:
651     case WM_IME_KEYUP:
652     case WM_IME_SELECT:
653       aResult.mConsumed =
654           aWindow->DispatchPluginEvent(msg, wParam, lParam, false);
655       aRet = true;
656       return true;
657     case WM_IME_REQUEST:
658       // Our plugin implementation is alwasy OOP.  So WM_IME_REQUEST doesn't
659       // allow that parameter is pointer and shouldn't handle into Gecko.
660       aRet = false;
661       return true;
662   }
663   return false;
664 }
665 
666 /****************************************************************************
667  * message handlers
668  ****************************************************************************/
669 
OnInputLangChange(nsWindow * aWindow,WPARAM wParam,LPARAM lParam,MSGResult & aResult)670 void IMMHandler::OnInputLangChange(nsWindow* aWindow, WPARAM wParam,
671                                    LPARAM lParam, MSGResult& aResult) {
672   MOZ_LOG(gIMMLog, LogLevel::Info,
673           ("OnInputLangChange, hWnd=%08x, wParam=%08x, lParam=%08x",
674            aWindow->GetWindowHandle(), wParam, lParam));
675 
676   aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
677   NS_ASSERTION(!mIsComposing, "ResetInputState failed");
678 
679   if (mIsComposing) {
680     HandleEndComposition(aWindow);
681   }
682 
683   aResult.mConsumed = false;
684 }
685 
OnIMEStartComposition(nsWindow * aWindow,MSGResult & aResult)686 bool IMMHandler::OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult) {
687   MOZ_LOG(gIMMLog, LogLevel::Info,
688           ("OnIMEStartComposition, hWnd=%08x, mIsComposing=%s",
689            aWindow->GetWindowHandle(), GetBoolName(mIsComposing)));
690   aResult.mConsumed = ShouldDrawCompositionStringOurselves();
691   if (mIsComposing) {
692     NS_WARNING("Composition has been already started");
693     return true;
694   }
695 
696   IMEContext context(aWindow);
697   HandleStartComposition(aWindow, context);
698   return true;
699 }
700 
OnIMEComposition(nsWindow * aWindow,WPARAM wParam,LPARAM lParam,MSGResult & aResult)701 bool IMMHandler::OnIMEComposition(nsWindow* aWindow, WPARAM wParam,
702                                   LPARAM lParam, MSGResult& aResult) {
703   MOZ_LOG(
704       gIMMLog, LogLevel::Info,
705       ("OnIMEComposition, hWnd=%08x, lParam=%08x, mIsComposing=%s, "
706        "GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, "
707        "GCS_CURSORPOS=%s,",
708        aWindow->GetWindowHandle(), lParam, GetBoolName(mIsComposing),
709        GetBoolName(lParam & GCS_RESULTSTR), GetBoolName(lParam & GCS_COMPSTR),
710        GetBoolName(lParam & GCS_COMPATTR), GetBoolName(lParam & GCS_COMPCLAUSE),
711        GetBoolName(lParam & GCS_CURSORPOS)));
712 
713   IMEContext context(aWindow);
714   aResult.mConsumed = HandleComposition(aWindow, context, lParam);
715   return true;
716 }
717 
OnIMEEndComposition(nsWindow * aWindow,MSGResult & aResult)718 bool IMMHandler::OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult) {
719   MOZ_LOG(gIMMLog, LogLevel::Info,
720           ("OnIMEEndComposition, hWnd=%08x, mIsComposing=%s",
721            aWindow->GetWindowHandle(), GetBoolName(mIsComposing)));
722 
723   aResult.mConsumed = ShouldDrawCompositionStringOurselves();
724   if (!mIsComposing) {
725     return true;
726   }
727 
728   // Korean IME posts WM_IME_ENDCOMPOSITION first when we hit space during
729   // composition. Then, we should ignore the message and commit the composition
730   // string at following WM_IME_COMPOSITION.
731   MSG compositionMsg;
732   if (WinUtils::PeekMessage(&compositionMsg, aWindow->GetWindowHandle(),
733                             WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION,
734                             PM_NOREMOVE) &&
735       compositionMsg.message == WM_IME_COMPOSITION &&
736       IS_COMMITTING_LPARAM(compositionMsg.lParam)) {
737     MOZ_LOG(gIMMLog, LogLevel::Info,
738             ("OnIMEEndComposition, WM_IME_ENDCOMPOSITION is followed by "
739              "WM_IME_COMPOSITION, ignoring the message..."));
740     return true;
741   }
742 
743   // Otherwise, e.g., ChangJie doesn't post WM_IME_COMPOSITION before
744   // WM_IME_ENDCOMPOSITION when composition string becomes empty.
745   // Then, we should dispatch a compositionupdate event, a compositionchange
746   // event and a compositionend event.
747   // XXX Shouldn't we dispatch the compositionchange event with actual or
748   //     latest composition string?
749   MOZ_LOG(gIMMLog, LogLevel::Info,
750           ("OnIMEEndComposition, mCompositionString=\"%s\"%s",
751            NS_ConvertUTF16toUTF8(mCompositionString).get(),
752            mCompositionString.IsEmpty() ? "" : ", but canceling it..."));
753 
754   HandleEndComposition(aWindow, &EmptyString());
755 
756   return true;
757 }
758 
759 // static
OnIMEChar(nsWindow * aWindow,WPARAM wParam,LPARAM lParam,MSGResult & aResult)760 bool IMMHandler::OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
761                            MSGResult& aResult) {
762   MOZ_LOG(
763       gIMMLog, LogLevel::Info,
764       ("OnIMEChar, hWnd=%08x, char=%08x", aWindow->GetWindowHandle(), wParam));
765 
766   // We don't need to fire any compositionchange events from here. This method
767   // will be called when the composition string of the current IME is not drawn
768   // by us and some characters are committed. In that case, the committed
769   // string was processed in nsWindow::OnIMEComposition already.
770 
771   // We need to consume the message so that Windows don't send two WM_CHAR msgs
772   aResult.mConsumed = true;
773   return true;
774 }
775 
776 // static
OnIMECompositionFull(nsWindow * aWindow,MSGResult & aResult)777 bool IMMHandler::OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult) {
778   MOZ_LOG(gIMMLog, LogLevel::Info,
779           ("OnIMECompositionFull, hWnd=%08x", aWindow->GetWindowHandle()));
780 
781   // not implement yet
782   aResult.mConsumed = false;
783   return true;
784 }
785 
786 // static
OnIMENotify(nsWindow * aWindow,WPARAM wParam,LPARAM lParam,MSGResult & aResult)787 bool IMMHandler::OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
788                              MSGResult& aResult) {
789   switch (wParam) {
790     case IMN_CHANGECANDIDATE:
791       MOZ_LOG(gIMMLog, LogLevel::Info,
792               ("OnIMENotify, hWnd=%08x, IMN_CHANGECANDIDATE, lParam=%08x",
793                aWindow->GetWindowHandle(), lParam));
794       break;
795     case IMN_CLOSECANDIDATE:
796       MOZ_LOG(gIMMLog, LogLevel::Info,
797               ("OnIMENotify, hWnd=%08x, IMN_CLOSECANDIDATE, lParam=%08x",
798                aWindow->GetWindowHandle(), lParam));
799       break;
800     case IMN_CLOSESTATUSWINDOW:
801       MOZ_LOG(gIMMLog, LogLevel::Info,
802               ("OnIMENotify, hWnd=%08x, IMN_CLOSESTATUSWINDOW",
803                aWindow->GetWindowHandle()));
804       break;
805     case IMN_GUIDELINE:
806       MOZ_LOG(gIMMLog, LogLevel::Info,
807               ("OnIMENotify, hWnd=%08x, IMN_GUIDELINE",
808                aWindow->GetWindowHandle()));
809       break;
810     case IMN_OPENCANDIDATE:
811       MOZ_LOG(gIMMLog, LogLevel::Info,
812               ("OnIMENotify, hWnd=%08x, IMN_OPENCANDIDATE, lParam=%08x",
813                aWindow->GetWindowHandle(), lParam));
814       break;
815     case IMN_OPENSTATUSWINDOW:
816       MOZ_LOG(gIMMLog, LogLevel::Info,
817               ("OnIMENotify, hWnd=%08x, IMN_OPENSTATUSWINDOW",
818                aWindow->GetWindowHandle()));
819       break;
820     case IMN_SETCANDIDATEPOS:
821       MOZ_LOG(gIMMLog, LogLevel::Info,
822               ("OnIMENotify, hWnd=%08x, IMN_SETCANDIDATEPOS, lParam=%08x",
823                aWindow->GetWindowHandle(), lParam));
824       break;
825     case IMN_SETCOMPOSITIONFONT:
826       MOZ_LOG(gIMMLog, LogLevel::Info,
827               ("OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONFONT",
828                aWindow->GetWindowHandle()));
829       break;
830     case IMN_SETCOMPOSITIONWINDOW:
831       MOZ_LOG(gIMMLog, LogLevel::Info,
832               ("OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONWINDOW",
833                aWindow->GetWindowHandle()));
834       break;
835     case IMN_SETCONVERSIONMODE:
836       MOZ_LOG(gIMMLog, LogLevel::Info,
837               ("OnIMENotify, hWnd=%08x, IMN_SETCONVERSIONMODE",
838                aWindow->GetWindowHandle()));
839       break;
840     case IMN_SETOPENSTATUS:
841       MOZ_LOG(gIMMLog, LogLevel::Info,
842               ("OnIMENotify, hWnd=%08x, IMN_SETOPENSTATUS",
843                aWindow->GetWindowHandle()));
844       break;
845     case IMN_SETSENTENCEMODE:
846       MOZ_LOG(gIMMLog, LogLevel::Info,
847               ("OnIMENotify, hWnd=%08x, IMN_SETSENTENCEMODE",
848                aWindow->GetWindowHandle()));
849       break;
850     case IMN_SETSTATUSWINDOWPOS:
851       MOZ_LOG(gIMMLog, LogLevel::Info,
852               ("OnIMENotify, hWnd=%08x, IMN_SETSTATUSWINDOWPOS",
853                aWindow->GetWindowHandle()));
854       break;
855     case IMN_PRIVATE:
856       MOZ_LOG(
857           gIMMLog, LogLevel::Info,
858           ("OnIMENotify, hWnd=%08x, IMN_PRIVATE", aWindow->GetWindowHandle()));
859       break;
860   }
861 
862   // not implement yet
863   aResult.mConsumed = false;
864   return true;
865 }
866 
OnIMERequest(nsWindow * aWindow,WPARAM wParam,LPARAM lParam,MSGResult & aResult)867 bool IMMHandler::OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
868                               MSGResult& aResult) {
869   switch (wParam) {
870     case IMR_RECONVERTSTRING:
871       MOZ_LOG(gIMMLog, LogLevel::Info,
872               ("OnIMERequest, hWnd=%08x, IMR_RECONVERTSTRING",
873                aWindow->GetWindowHandle()));
874       aResult.mConsumed = HandleReconvert(aWindow, lParam, &aResult.mResult);
875       return true;
876     case IMR_QUERYCHARPOSITION:
877       MOZ_LOG(gIMMLog, LogLevel::Info,
878               ("OnIMERequest, hWnd=%08x, IMR_QUERYCHARPOSITION",
879                aWindow->GetWindowHandle()));
880       aResult.mConsumed =
881           HandleQueryCharPosition(aWindow, lParam, &aResult.mResult);
882       return true;
883     case IMR_DOCUMENTFEED:
884       MOZ_LOG(gIMMLog, LogLevel::Info,
885               ("OnIMERequest, hWnd=%08x, IMR_DOCUMENTFEED",
886                aWindow->GetWindowHandle()));
887       aResult.mConsumed = HandleDocumentFeed(aWindow, lParam, &aResult.mResult);
888       return true;
889     default:
890       MOZ_LOG(gIMMLog, LogLevel::Info,
891               ("OnIMERequest, hWnd=%08x, wParam=%08x",
892                aWindow->GetWindowHandle(), wParam));
893       aResult.mConsumed = false;
894       return true;
895   }
896 }
897 
898 // static
OnIMESelect(nsWindow * aWindow,WPARAM wParam,LPARAM lParam,MSGResult & aResult)899 bool IMMHandler::OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
900                              MSGResult& aResult) {
901   MOZ_LOG(gIMMLog, LogLevel::Info,
902           ("OnIMESelect, hWnd=%08x, wParam=%08x, lParam=%08x",
903            aWindow->GetWindowHandle(), wParam, lParam));
904 
905   // not implement yet
906   aResult.mConsumed = false;
907   return true;
908 }
909 
910 // static
OnIMESetContext(nsWindow * aWindow,WPARAM wParam,LPARAM lParam,MSGResult & aResult)911 bool IMMHandler::OnIMESetContext(nsWindow* aWindow, WPARAM wParam,
912                                  LPARAM lParam, MSGResult& aResult) {
913   MOZ_LOG(gIMMLog, LogLevel::Info,
914           ("OnIMESetContext, hWnd=%08x, %s, lParam=%08x",
915            aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam));
916 
917   aResult.mConsumed = false;
918 
919   // NOTE: If the aWindow is top level window of the composing window because
920   // when a window on deactive window gets focus, WM_IME_SETCONTEXT (wParam is
921   // TRUE) is sent to the top level window first.  After that,
922   // WM_IME_SETCONTEXT (wParam is FALSE) is sent to the top level window.
923   // Finally, WM_IME_SETCONTEXT (wParam is TRUE) is sent to the focused window.
924   // The top level window never becomes composing window, so, we can ignore
925   // the WM_IME_SETCONTEXT on the top level window.
926   if (IsTopLevelWindowOfComposition(aWindow)) {
927     MOZ_LOG(gIMMLog, LogLevel::Info,
928             ("OnIMESetContext, hWnd=%08x is top level window"));
929     return true;
930   }
931 
932   // When IME context is activating on another window,
933   // we should commit the old composition on the old window.
934   bool cancelComposition = false;
935   if (wParam && gIMMHandler) {
936     cancelComposition = gIMMHandler->CommitCompositionOnPreviousWindow(aWindow);
937   }
938 
939   if (wParam && (lParam & ISC_SHOWUICOMPOSITIONWINDOW) &&
940       ShouldDrawCompositionStringOurselves()) {
941     MOZ_LOG(gIMMLog, LogLevel::Info,
942             ("OnIMESetContext, ISC_SHOWUICOMPOSITIONWINDOW is removed"));
943     lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
944   }
945 
946   // We should sent WM_IME_SETCONTEXT to the DefWndProc here because the
947   // ancestor windows shouldn't receive this message.  If they receive the
948   // message, we cannot know whether which window is the target of the message.
949   aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(),
950                                     WM_IME_SETCONTEXT, wParam, lParam);
951 
952   // Cancel composition on the new window if we committed our composition on
953   // another window.
954   if (cancelComposition) {
955     CancelComposition(aWindow, true);
956   }
957 
958   aResult.mConsumed = true;
959   return true;
960 }
961 
OnChar(nsWindow * aWindow,WPARAM wParam,LPARAM lParam,MSGResult & aResult)962 bool IMMHandler::OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
963                         MSGResult& aResult) {
964   // The return value must be same as aResult.mConsumed because only when we
965   // consume the message, the caller shouldn't do anything anymore but
966   // otherwise, the caller should handle the message.
967   aResult.mConsumed = false;
968   if (IsIMECharRecordsEmpty()) {
969     return aResult.mConsumed;
970   }
971   WPARAM recWParam;
972   LPARAM recLParam;
973   DequeueIMECharRecords(recWParam, recLParam);
974   MOZ_LOG(gIMMLog, LogLevel::Info,
975           ("OnChar, aWindow=%p, wParam=%08x, lParam=%08x, "
976            "recorded: wParam=%08x, lParam=%08x",
977            aWindow->GetWindowHandle(), wParam, lParam, recWParam, recLParam));
978   // If an unexpected char message comes, we should reset the records,
979   // of course, this shouldn't happen.
980   if (recWParam != wParam || recLParam != lParam) {
981     ResetIMECharRecords();
982     return aResult.mConsumed;
983   }
984   // Eat the char message which is caused by WM_IME_CHAR because we should
985   // have processed the IME messages, so, this message could be come from
986   // a windowless plug-in.
987   aResult.mConsumed = true;
988   return aResult.mConsumed;
989 }
990 
991 /****************************************************************************
992  * message handlers for plug-in
993  ****************************************************************************/
994 
OnIMEStartCompositionOnPlugin(nsWindow * aWindow,WPARAM wParam,LPARAM lParam)995 void IMMHandler::OnIMEStartCompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
996                                                LPARAM lParam) {
997   MOZ_LOG(gIMMLog, LogLevel::Info,
998           ("OnIMEStartCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s",
999            aWindow->GetWindowHandle(), GetBoolName(mIsComposingOnPlugin)));
1000   mIsComposingOnPlugin = true;
1001   mDispatcher = GetTextEventDispatcherFor(aWindow);
1002   mComposingWindow = aWindow;
1003   IMEContext context(aWindow);
1004   SetIMERelatedWindowsPosOnPlugin(aWindow, context);
1005   // On widnowless plugin, we should assume that the focused editor is always
1006   // in horizontal writing mode.
1007   AdjustCompositionFont(aWindow, context, WritingMode());
1008 }
1009 
OnIMECompositionOnPlugin(nsWindow * aWindow,WPARAM wParam,LPARAM lParam)1010 void IMMHandler::OnIMECompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
1011                                           LPARAM lParam) {
1012   MOZ_LOG(
1013       gIMMLog, LogLevel::Info,
1014       ("OnIMECompositionOnPlugin, hWnd=%08x, lParam=%08x, "
1015        "mIsComposingOnPlugin=%s, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, "
1016        "GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s",
1017        aWindow->GetWindowHandle(), lParam, GetBoolName(mIsComposingOnPlugin),
1018        GetBoolName(lParam & GCS_RESULTSTR), GetBoolName(lParam & GCS_COMPSTR),
1019        GetBoolName(lParam & GCS_COMPATTR), GetBoolName(lParam & GCS_COMPCLAUSE),
1020        GetBoolName(lParam & GCS_CURSORPOS)));
1021   // We should end composition if there is a committed string.
1022   if (IS_COMMITTING_LPARAM(lParam)) {
1023     mIsComposingOnPlugin = false;
1024     mComposingWindow = nullptr;
1025     mDispatcher = nullptr;
1026     return;
1027   }
1028   // Continue composition if there is still a string being composed.
1029   if (IS_COMPOSING_LPARAM(lParam)) {
1030     mIsComposingOnPlugin = true;
1031     mDispatcher = GetTextEventDispatcherFor(aWindow);
1032     mComposingWindow = aWindow;
1033     IMEContext context(aWindow);
1034     SetIMERelatedWindowsPosOnPlugin(aWindow, context);
1035   }
1036 }
1037 
OnIMEEndCompositionOnPlugin(nsWindow * aWindow,WPARAM wParam,LPARAM lParam)1038 void IMMHandler::OnIMEEndCompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
1039                                              LPARAM lParam) {
1040   MOZ_LOG(gIMMLog, LogLevel::Info,
1041           ("OnIMEEndCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s",
1042            aWindow->GetWindowHandle(), GetBoolName(mIsComposingOnPlugin)));
1043 
1044   mIsComposingOnPlugin = false;
1045   mComposingWindow = nullptr;
1046   mDispatcher = nullptr;
1047 
1048   if (mNativeCaretIsCreated) {
1049     ::DestroyCaret();
1050     mNativeCaretIsCreated = false;
1051   }
1052 }
1053 
OnIMECharOnPlugin(nsWindow * aWindow,WPARAM wParam,LPARAM lParam,MSGResult & aResult)1054 bool IMMHandler::OnIMECharOnPlugin(nsWindow* aWindow, WPARAM wParam,
1055                                    LPARAM lParam, MSGResult& aResult) {
1056   MOZ_LOG(gIMMLog, LogLevel::Info,
1057           ("OnIMECharOnPlugin, hWnd=%08x, char=%08x, scancode=%08x",
1058            aWindow->GetWindowHandle(), wParam, lParam));
1059 
1060   aResult.mConsumed =
1061       aWindow->DispatchPluginEvent(WM_IME_CHAR, wParam, lParam, true);
1062 
1063   if (!aResult.mConsumed) {
1064     // Record the WM_CHAR messages which are going to be coming.
1065     EnsureHandlerInstance();
1066     EnqueueIMECharRecords(wParam, lParam);
1067   }
1068   return true;
1069 }
1070 
1071 // static
OnIMESetContextOnPlugin(nsWindow * aWindow,WPARAM wParam,LPARAM lParam,MSGResult & aResult)1072 bool IMMHandler::OnIMESetContextOnPlugin(nsWindow* aWindow, WPARAM wParam,
1073                                          LPARAM lParam, MSGResult& aResult) {
1074   MOZ_LOG(gIMMLog, LogLevel::Info,
1075           ("OnIMESetContextOnPlugin, hWnd=%08x, %s, lParam=%08x",
1076            aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam));
1077 
1078   // If the IME context becomes active on a plug-in, we should commit
1079   // our composition.  And also we should cancel the composition on new
1080   // window.  Note that if IsTopLevelWindowOfComposition(aWindow) returns
1081   // true, we should ignore the message here, see the comment in
1082   // OnIMESetContext() for the detail.
1083   if (wParam && gIMMHandler && !IsTopLevelWindowOfComposition(aWindow)) {
1084     if (gIMMHandler->CommitCompositionOnPreviousWindow(aWindow)) {
1085       CancelComposition(aWindow);
1086     }
1087   }
1088 
1089   // Dispatch message to the plug-in.
1090   // XXX When a windowless plug-in gets focus, we should send
1091   //     WM_IME_SETCONTEXT
1092   aWindow->DispatchPluginEvent(WM_IME_SETCONTEXT, wParam, lParam, false);
1093 
1094   // We should send WM_IME_SETCONTEXT to the DefWndProc here.  It shouldn't
1095   // be received on ancestor windows, see OnIMESetContext() for the detail.
1096   aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(),
1097                                     WM_IME_SETCONTEXT, wParam, lParam);
1098 
1099   // Don't synchronously dispatch the pending events when we receive
1100   // WM_IME_SETCONTEXT because we get it during plugin destruction.
1101   // (bug 491848)
1102   aResult.mConsumed = true;
1103   return true;
1104 }
1105 
OnCharOnPlugin(nsWindow * aWindow,WPARAM wParam,LPARAM lParam,MSGResult & aResult)1106 bool IMMHandler::OnCharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
1107                                 MSGResult& aResult) {
1108   NS_WARNING("OnCharOnPlugin");
1109   if (mIsComposing) {
1110     aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
1111     return true;
1112   }
1113 
1114   // We should never consume char message on windowless plugin.
1115   aResult.mConsumed = false;
1116   if (IsIMECharRecordsEmpty()) {
1117     return false;
1118   }
1119 
1120   WPARAM recWParam;
1121   LPARAM recLParam;
1122   DequeueIMECharRecords(recWParam, recLParam);
1123   MOZ_LOG(gIMMLog, LogLevel::Info,
1124           ("OnCharOnPlugin, aWindow=%p, wParam=%08x, lParam=%08x, "
1125            "recorded: wParam=%08x, lParam=%08x",
1126            aWindow->GetWindowHandle(), wParam, lParam, recWParam, recLParam));
1127   // If an unexpected char message comes, we should reset the records,
1128   // of course, this shouldn't happen.
1129   if (recWParam != wParam || recLParam != lParam) {
1130     ResetIMECharRecords();
1131   }
1132   // WM_CHAR on plug-in is always handled by nsWindow.
1133   return false;
1134 }
1135 
1136 /****************************************************************************
1137  * others
1138  ****************************************************************************/
1139 
GetTextEventDispatcherFor(nsWindow * aWindow)1140 TextEventDispatcher* IMMHandler::GetTextEventDispatcherFor(nsWindow* aWindow) {
1141   return aWindow == mComposingWindow && mDispatcher
1142              ? mDispatcher.get()
1143              : aWindow->GetTextEventDispatcher();
1144 }
1145 
HandleStartComposition(nsWindow * aWindow,const IMEContext & aContext)1146 void IMMHandler::HandleStartComposition(nsWindow* aWindow,
1147                                         const IMEContext& aContext) {
1148   NS_PRECONDITION(!mIsComposing,
1149                   "HandleStartComposition is called but mIsComposing is TRUE");
1150 
1151   Selection& selection = GetSelection();
1152   if (!selection.EnsureValidSelection(aWindow)) {
1153     MOZ_LOG(gIMMLog, LogLevel::Error,
1154             ("HandleStartComposition, FAILED, due to "
1155              "Selection::EnsureValidSelection() failure"));
1156     return;
1157   }
1158 
1159   AdjustCompositionFont(aWindow, aContext, selection.mWritingMode);
1160 
1161   mCompositionStart = selection.mOffset;
1162   mCursorPosition = NO_IME_CARET;
1163 
1164   RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
1165   nsresult rv = dispatcher->BeginNativeInputTransaction();
1166   if (NS_WARN_IF(NS_FAILED(rv))) {
1167     MOZ_LOG(gIMMLog, LogLevel::Error,
1168             ("HandleStartComposition, FAILED due to "
1169              "TextEventDispatcher::BeginNativeInputTransaction() failure"));
1170     return;
1171   }
1172   WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
1173   nsEventStatus status;
1174   rv = dispatcher->StartComposition(status, &eventTime);
1175   if (NS_WARN_IF(NS_FAILED(rv))) {
1176     MOZ_LOG(gIMMLog, LogLevel::Error,
1177             ("HandleStartComposition, FAILED, due to "
1178              "TextEventDispatcher::StartComposition() failure"));
1179     return;
1180   }
1181 
1182   mIsComposing = true;
1183   mComposingWindow = aWindow;
1184   mDispatcher = dispatcher;
1185 
1186   MOZ_LOG(gIMMLog, LogLevel::Info,
1187           ("HandleStartComposition, START composition, mCompositionStart=%ld",
1188            mCompositionStart));
1189 }
1190 
HandleComposition(nsWindow * aWindow,const IMEContext & aContext,LPARAM lParam)1191 bool IMMHandler::HandleComposition(nsWindow* aWindow,
1192                                    const IMEContext& aContext, LPARAM lParam) {
1193   // for bug #60050
1194   // MS-IME 95/97/98/2000 may send WM_IME_COMPOSITION with non-conversion
1195   // mode before it send WM_IME_STARTCOMPOSITION.
1196   // However, ATOK sends a WM_IME_COMPOSITION before WM_IME_STARTCOMPOSITION,
1197   // and if we access ATOK via some APIs, ATOK will sometimes fail to
1198   // initialize its state.  If WM_IME_STARTCOMPOSITION is already in the
1199   // message queue, we should ignore the strange WM_IME_COMPOSITION message and
1200   // skip to the next.  So, we should look for next composition message
1201   // (WM_IME_STARTCOMPOSITION or WM_IME_ENDCOMPOSITION or WM_IME_COMPOSITION),
1202   // and if it's WM_IME_STARTCOMPOSITION, and one more next composition message
1203   // is WM_IME_COMPOSITION, current IME is ATOK, probably.  Otherwise, we
1204   // should start composition forcibly.
1205   if (!mIsComposing) {
1206     MSG msg1, msg2;
1207     HWND wnd = aWindow->GetWindowHandle();
1208     if (WinUtils::PeekMessage(&msg1, wnd, WM_IME_STARTCOMPOSITION,
1209                               WM_IME_COMPOSITION, PM_NOREMOVE) &&
1210         msg1.message == WM_IME_STARTCOMPOSITION &&
1211         WinUtils::PeekMessage(&msg2, wnd, WM_IME_ENDCOMPOSITION,
1212                               WM_IME_COMPOSITION, PM_NOREMOVE) &&
1213         msg2.message == WM_IME_COMPOSITION) {
1214       MOZ_LOG(gIMMLog, LogLevel::Info,
1215               ("HandleComposition, Ignores due to find a "
1216                "WM_IME_STARTCOMPOSITION"));
1217       return ShouldDrawCompositionStringOurselves();
1218     }
1219   }
1220 
1221   bool startCompositionMessageHasBeenSent = mIsComposing;
1222 
1223   //
1224   // This catches a fixed result
1225   //
1226   if (IS_COMMITTING_LPARAM(lParam)) {
1227     if (!mIsComposing) {
1228       HandleStartComposition(aWindow, aContext);
1229     }
1230 
1231     GetCompositionString(aContext, GCS_RESULTSTR, mCompositionString);
1232 
1233     MOZ_LOG(gIMMLog, LogLevel::Info, ("HandleComposition, GCS_RESULTSTR"));
1234 
1235     HandleEndComposition(aWindow, &mCompositionString);
1236 
1237     if (!IS_COMPOSING_LPARAM(lParam)) {
1238       return ShouldDrawCompositionStringOurselves();
1239     }
1240   }
1241 
1242   //
1243   // This provides us with a composition string
1244   //
1245   if (!mIsComposing) {
1246     HandleStartComposition(aWindow, aContext);
1247   }
1248 
1249   //--------------------------------------------------------
1250   // 1. Get GCS_COMPSTR
1251   //--------------------------------------------------------
1252   MOZ_LOG(gIMMLog, LogLevel::Info, ("HandleComposition, GCS_COMPSTR"));
1253 
1254   nsAutoString previousCompositionString(mCompositionString);
1255   GetCompositionString(aContext, GCS_COMPSTR, mCompositionString);
1256 
1257   if (!IS_COMPOSING_LPARAM(lParam)) {
1258     MOZ_LOG(gIMMLog, LogLevel::Info,
1259             ("HandleComposition, lParam doesn't indicate composing, "
1260              "mCompositionString=\"%s\", previousCompositionString=\"%s\"",
1261              NS_ConvertUTF16toUTF8(mCompositionString).get(),
1262              NS_ConvertUTF16toUTF8(previousCompositionString).get()));
1263 
1264     // If composition string isn't changed, we can trust the lParam.
1265     // So, we need to do nothing.
1266     if (previousCompositionString == mCompositionString) {
1267       return ShouldDrawCompositionStringOurselves();
1268     }
1269 
1270     // IME may send WM_IME_COMPOSITION without composing lParam values
1271     // when composition string becomes empty (e.g., using Backspace key).
1272     // If composition string is empty, we should dispatch a compositionchange
1273     // event with empty string and clear the clause information.
1274     if (mCompositionString.IsEmpty()) {
1275       mClauseArray.Clear();
1276       mAttributeArray.Clear();
1277       mCursorPosition = 0;
1278       DispatchCompositionChangeEvent(aWindow, aContext);
1279       return ShouldDrawCompositionStringOurselves();
1280     }
1281 
1282     // Otherwise, we cannot trust the lParam value.  We might need to
1283     // dispatch compositionchange event with the latest composition string
1284     // information.
1285   }
1286 
1287   // See https://bugzilla.mozilla.org/show_bug.cgi?id=296339
1288   if (mCompositionString.IsEmpty() && !startCompositionMessageHasBeenSent) {
1289     // In this case, maybe, the sender is MSPinYin. That sends *only*
1290     // WM_IME_COMPOSITION with GCS_COMP* and GCS_RESULT* when
1291     // user inputted the Chinese full stop. So, that doesn't send
1292     // WM_IME_STARTCOMPOSITION and WM_IME_ENDCOMPOSITION.
1293     // If WM_IME_STARTCOMPOSITION was not sent and the composition
1294     // string is null (it indicates the composition transaction ended),
1295     // WM_IME_ENDCOMPOSITION may not be sent. If so, we cannot run
1296     // HandleEndComposition() in other place.
1297     MOZ_LOG(gIMMLog, LogLevel::Info,
1298             ("HandleComposition, Aborting GCS_COMPSTR"));
1299     HandleEndComposition(aWindow);
1300     return IS_COMMITTING_LPARAM(lParam);
1301   }
1302 
1303   //--------------------------------------------------------
1304   // 2. Get GCS_COMPCLAUSE
1305   //--------------------------------------------------------
1306   long clauseArrayLength =
1307       ::ImmGetCompositionStringW(aContext.get(), GCS_COMPCLAUSE, nullptr, 0);
1308   clauseArrayLength /= sizeof(uint32_t);
1309 
1310   if (clauseArrayLength > 0) {
1311     nsresult rv = EnsureClauseArray(clauseArrayLength);
1312     NS_ENSURE_SUCCESS(rv, false);
1313 
1314     // Intelligent ABC IME (Simplified Chinese IME, the code page is 936)
1315     // will crash in ImmGetCompositionStringW for GCS_COMPCLAUSE (bug 424663).
1316     // See comment 35 of the bug for the detail. Therefore, we should use A
1317     // API for it, however, we should not kill Unicode support on all IMEs.
1318     bool useA_API = !(sIMEProperty & IME_PROP_UNICODE);
1319 
1320     MOZ_LOG(gIMMLog, LogLevel::Info,
1321             ("HandleComposition, GCS_COMPCLAUSE, useA_API=%s",
1322              useA_API ? "TRUE" : "FALSE"));
1323 
1324     long clauseArrayLength2 =
1325         useA_API ? ::ImmGetCompositionStringA(
1326                        aContext.get(), GCS_COMPCLAUSE, mClauseArray.Elements(),
1327                        mClauseArray.Capacity() * sizeof(uint32_t))
1328                  : ::ImmGetCompositionStringW(
1329                        aContext.get(), GCS_COMPCLAUSE, mClauseArray.Elements(),
1330                        mClauseArray.Capacity() * sizeof(uint32_t));
1331     clauseArrayLength2 /= sizeof(uint32_t);
1332 
1333     if (clauseArrayLength != clauseArrayLength2) {
1334       MOZ_LOG(gIMMLog, LogLevel::Info,
1335               ("HandleComposition, GCS_COMPCLAUSE, clauseArrayLength=%ld but "
1336                "clauseArrayLength2=%ld",
1337                clauseArrayLength, clauseArrayLength2));
1338       if (clauseArrayLength > clauseArrayLength2)
1339         clauseArrayLength = clauseArrayLength2;
1340     }
1341 
1342     if (useA_API && clauseArrayLength > 0) {
1343       // Convert each values of sIMECompClauseArray. The values mean offset of
1344       // the clauses in ANSI string. But we need the values in Unicode string.
1345       nsAutoCString compANSIStr;
1346       if (ConvertToANSIString(mCompositionString, GetKeyboardCodePage(),
1347                               compANSIStr)) {
1348         uint32_t maxlen = compANSIStr.Length();
1349         mClauseArray.SetLength(clauseArrayLength);
1350         mClauseArray[0] = 0;  // first value must be 0
1351         for (int32_t i = 1; i < clauseArrayLength; i++) {
1352           uint32_t len = std::min(mClauseArray[i], maxlen);
1353           mClauseArray[i] =
1354               ::MultiByteToWideChar(GetKeyboardCodePage(), MB_PRECOMPOSED,
1355                                     (LPCSTR)compANSIStr.get(), len, nullptr, 0);
1356         }
1357       }
1358     }
1359   }
1360   // compClauseArrayLength may be negative. I.e., ImmGetCompositionStringW
1361   // may return an error code.
1362   mClauseArray.SetLength(std::max<long>(0, clauseArrayLength));
1363 
1364   MOZ_LOG(gIMMLog, LogLevel::Info,
1365           ("HandleComposition, GCS_COMPCLAUSE, mClauseLength=%ld",
1366            mClauseArray.Length()));
1367 
1368   //--------------------------------------------------------
1369   // 3. Get GCS_COMPATTR
1370   //--------------------------------------------------------
1371   // This provides us with the attribute string necessary
1372   // for doing hiliting
1373   long attrArrayLength =
1374       ::ImmGetCompositionStringW(aContext.get(), GCS_COMPATTR, nullptr, 0);
1375   attrArrayLength /= sizeof(uint8_t);
1376 
1377   if (attrArrayLength > 0) {
1378     nsresult rv = EnsureAttributeArray(attrArrayLength);
1379     NS_ENSURE_SUCCESS(rv, false);
1380     attrArrayLength = ::ImmGetCompositionStringW(
1381         aContext.get(), GCS_COMPATTR, mAttributeArray.Elements(),
1382         mAttributeArray.Capacity() * sizeof(uint8_t));
1383   }
1384 
1385   // attrStrLen may be negative. I.e., ImmGetCompositionStringW may return an
1386   // error code.
1387   mAttributeArray.SetLength(std::max<long>(0, attrArrayLength));
1388 
1389   MOZ_LOG(gIMMLog, LogLevel::Info,
1390           ("HandleComposition, GCS_COMPATTR, mAttributeLength=%ld",
1391            mAttributeArray.Length()));
1392 
1393   //--------------------------------------------------------
1394   // 4. Get GCS_CURSOPOS
1395   //--------------------------------------------------------
1396   // Some IMEs (e.g., the standard IME for Korean) don't have caret position.
1397   if (lParam & GCS_CURSORPOS) {
1398     mCursorPosition =
1399         ::ImmGetCompositionStringW(aContext.get(), GCS_CURSORPOS, nullptr, 0);
1400     if (mCursorPosition < 0) {
1401       mCursorPosition = NO_IME_CARET;  // The result is error
1402     }
1403   } else {
1404     mCursorPosition = NO_IME_CARET;
1405   }
1406 
1407   NS_ASSERTION(mCursorPosition <= (long)mCompositionString.Length(),
1408                "illegal pos");
1409 
1410   MOZ_LOG(gIMMLog, LogLevel::Info,
1411           ("HandleComposition, GCS_CURSORPOS, mCursorPosition=%d",
1412            mCursorPosition));
1413 
1414   //--------------------------------------------------------
1415   // 5. Send the compositionchange event
1416   //--------------------------------------------------------
1417   DispatchCompositionChangeEvent(aWindow, aContext);
1418 
1419   return ShouldDrawCompositionStringOurselves();
1420 }
1421 
HandleEndComposition(nsWindow * aWindow,const nsAString * aCommitString)1422 void IMMHandler::HandleEndComposition(nsWindow* aWindow,
1423                                       const nsAString* aCommitString) {
1424   MOZ_ASSERT(mIsComposing,
1425              "HandleEndComposition is called but mIsComposing is FALSE");
1426 
1427   MOZ_LOG(gIMMLog, LogLevel::Info,
1428           ("HandleEndComposition(aWindow=0x%p, aCommitString=0x%p (\"%s\"))",
1429            aWindow, aCommitString,
1430            aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : ""));
1431 
1432   if (mNativeCaretIsCreated) {
1433     ::DestroyCaret();
1434     mNativeCaretIsCreated = false;
1435   }
1436 
1437   RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
1438   nsresult rv = dispatcher->BeginNativeInputTransaction();
1439   if (NS_WARN_IF(NS_FAILED(rv))) {
1440     MOZ_LOG(gIMMLog, LogLevel::Error,
1441             ("HandleEndComposition, FAILED due to "
1442              "TextEventDispatcher::BeginNativeInputTransaction() failure"));
1443     return;
1444   }
1445   WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
1446   nsEventStatus status;
1447   rv = dispatcher->CommitComposition(status, aCommitString, &eventTime);
1448   if (NS_WARN_IF(NS_FAILED(rv))) {
1449     MOZ_LOG(gIMMLog, LogLevel::Error,
1450             ("HandleStartComposition, FAILED, due to "
1451              "TextEventDispatcher::CommitComposition() failure"));
1452     return;
1453   }
1454   mIsComposing = false;
1455   // XXX aWindow and mComposingWindow are always same??
1456   mComposingWindow = nullptr;
1457   mDispatcher = nullptr;
1458 }
1459 
HandleReconvert(nsWindow * aWindow,LPARAM lParam,LRESULT * oResult)1460 bool IMMHandler::HandleReconvert(nsWindow* aWindow, LPARAM lParam,
1461                                  LRESULT* oResult) {
1462   *oResult = 0;
1463   RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
1464 
1465   Selection& selection = GetSelection();
1466   if (!selection.EnsureValidSelection(aWindow)) {
1467     MOZ_LOG(gIMMLog, LogLevel::Error,
1468             ("HandleReconvert, FAILED, due to "
1469              "Selection::EnsureValidSelection() failure"));
1470     return false;
1471   }
1472 
1473   uint32_t len = selection.Length();
1474   uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
1475 
1476   if (!pReconv) {
1477     // Return need size to reconvert.
1478     if (len == 0) {
1479       MOZ_LOG(gIMMLog, LogLevel::Error,
1480               ("HandleReconvert, There are not selected text"));
1481       return false;
1482     }
1483     *oResult = needSize;
1484     MOZ_LOG(gIMMLog, LogLevel::Info,
1485             ("HandleReconvert, succeeded, result=%ld", *oResult));
1486     return true;
1487   }
1488 
1489   if (pReconv->dwSize < needSize) {
1490     MOZ_LOG(gIMMLog, LogLevel::Info,
1491             ("HandleReconvert, FAILED, pReconv->dwSize=%ld, needSize=%ld",
1492              pReconv->dwSize, needSize));
1493     return false;
1494   }
1495 
1496   *oResult = needSize;
1497 
1498   // Fill reconvert struct
1499   pReconv->dwVersion = 0;
1500   pReconv->dwStrLen = len;
1501   pReconv->dwStrOffset = sizeof(RECONVERTSTRING);
1502   pReconv->dwCompStrLen = len;
1503   pReconv->dwCompStrOffset = 0;
1504   pReconv->dwTargetStrLen = len;
1505   pReconv->dwTargetStrOffset = 0;
1506 
1507   ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)),
1508                selection.mString.get(), len * sizeof(WCHAR));
1509 
1510   MOZ_LOG(gIMMLog, LogLevel::Info,
1511           ("HandleReconvert, SUCCEEDED, pReconv=%s, result=%ld",
1512            GetReconvertStringLog(pReconv).get(), *oResult));
1513 
1514   return true;
1515 }
1516 
HandleQueryCharPosition(nsWindow * aWindow,LPARAM lParam,LRESULT * oResult)1517 bool IMMHandler::HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam,
1518                                          LRESULT* oResult) {
1519   uint32_t len = mIsComposing ? mCompositionString.Length() : 0;
1520   *oResult = false;
1521   IMECHARPOSITION* pCharPosition = reinterpret_cast<IMECHARPOSITION*>(lParam);
1522   if (!pCharPosition) {
1523     MOZ_LOG(gIMMLog, LogLevel::Error,
1524             ("HandleQueryCharPosition, FAILED, due to pCharPosition is null"));
1525     return false;
1526   }
1527   if (pCharPosition->dwSize < sizeof(IMECHARPOSITION)) {
1528     MOZ_LOG(gIMMLog, LogLevel::Error,
1529             ("HandleReconvert, FAILED, pCharPosition->dwSize=%ld, "
1530              "sizeof(IMECHARPOSITION)=%ld",
1531              pCharPosition->dwSize, sizeof(IMECHARPOSITION)));
1532     return false;
1533   }
1534   if (::GetFocus() != aWindow->GetWindowHandle()) {
1535     MOZ_LOG(gIMMLog, LogLevel::Error,
1536             ("HandleReconvert, FAILED, ::GetFocus()=%08x, OurWindowHandle=%08x",
1537              ::GetFocus(), aWindow->GetWindowHandle()));
1538     return false;
1539   }
1540   if (pCharPosition->dwCharPos > len) {
1541     MOZ_LOG(gIMMLog, LogLevel::Error,
1542             ("HandleQueryCharPosition, FAILED, pCharPosition->dwCharPos=%ld, "
1543              "len=%ld",
1544              pCharPosition->dwCharPos, len));
1545     return false;
1546   }
1547 
1548   LayoutDeviceIntRect r;
1549   bool ret =
1550       GetCharacterRectOfSelectedTextAt(aWindow, pCharPosition->dwCharPos, r);
1551   NS_ENSURE_TRUE(ret, false);
1552 
1553   LayoutDeviceIntRect screenRect;
1554   // We always need top level window that is owner window of the popup window
1555   // even if the content of the popup window has focus.
1556   ResolveIMECaretPos(aWindow->GetTopLevelWindow(false), r, nullptr, screenRect);
1557 
1558   // XXX This might need to check writing mode.  However, MSDN doesn't explain
1559   //     how to set the values in vertical writing mode. Additionally, IME
1560   //     doesn't work well with top-left of the character (this is explicitly
1561   //     documented) and its horizontal width.  So, it might be better to set
1562   //     top-right corner of the character and horizontal width, but we're not
1563   //     sure if it doesn't cause any problems with a lot of IMEs...
1564   pCharPosition->pt.x = screenRect.X();
1565   pCharPosition->pt.y = screenRect.Y();
1566 
1567   pCharPosition->cLineHeight = r.Height();
1568 
1569   WidgetQueryContentEvent editorRect(true, eQueryEditorRect, aWindow);
1570   aWindow->InitEvent(editorRect);
1571   DispatchEvent(aWindow, editorRect);
1572   if (NS_WARN_IF(!editorRect.mSucceeded)) {
1573     MOZ_LOG(gIMMLog, LogLevel::Error,
1574             ("HandleQueryCharPosition, eQueryEditorRect failed"));
1575     ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument);
1576   } else {
1577     LayoutDeviceIntRect editorRectInWindow = editorRect.mReply.mRect;
1578     nsWindow* window =
1579         editorRect.mReply.mFocusedWidget
1580             ? static_cast<nsWindow*>(editorRect.mReply.mFocusedWidget)
1581             : aWindow;
1582     LayoutDeviceIntRect editorRectInScreen;
1583     ResolveIMECaretPos(window, editorRectInWindow, nullptr, editorRectInScreen);
1584     ::SetRect(&pCharPosition->rcDocument, editorRectInScreen.X(),
1585               editorRectInScreen.Y(), editorRectInScreen.XMost(),
1586               editorRectInScreen.YMost());
1587   }
1588 
1589   *oResult = TRUE;
1590 
1591   MOZ_LOG(gIMMLog, LogLevel::Info,
1592           ("HandleQueryCharPosition, SUCCEEDED, pCharPosition={ pt={ x=%d, "
1593            "y=%d }, cLineHeight=%d, rcDocument={ left=%d, top=%d, right=%d, "
1594            "bottom=%d } }",
1595            pCharPosition->pt.x, pCharPosition->pt.y, pCharPosition->cLineHeight,
1596            pCharPosition->rcDocument.left, pCharPosition->rcDocument.top,
1597            pCharPosition->rcDocument.right, pCharPosition->rcDocument.bottom));
1598   return true;
1599 }
1600 
HandleDocumentFeed(nsWindow * aWindow,LPARAM lParam,LRESULT * oResult)1601 bool IMMHandler::HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam,
1602                                     LRESULT* oResult) {
1603   *oResult = 0;
1604   RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
1605 
1606   LayoutDeviceIntPoint point(0, 0);
1607 
1608   bool hasCompositionString =
1609       mIsComposing && ShouldDrawCompositionStringOurselves();
1610 
1611   int32_t targetOffset, targetLength;
1612   if (!hasCompositionString) {
1613     Selection& selection = GetSelection();
1614     if (!selection.EnsureValidSelection(aWindow)) {
1615       MOZ_LOG(gIMMLog, LogLevel::Error,
1616               ("HandleDocumentFeed, FAILED, due to "
1617                "Selection::EnsureValidSelection() failure"));
1618       return false;
1619     }
1620     targetOffset = int32_t(selection.mOffset);
1621     targetLength = int32_t(selection.Length());
1622   } else {
1623     targetOffset = int32_t(mCompositionStart);
1624     targetLength = int32_t(mCompositionString.Length());
1625   }
1626 
1627   // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
1628   //     we cannot support this message when the current offset is larger than
1629   //     INT32_MAX.
1630   if (targetOffset < 0 || targetLength < 0 || targetOffset + targetLength < 0) {
1631     MOZ_LOG(gIMMLog, LogLevel::Error,
1632             ("HandleDocumentFeed, FAILED, due to the selection is out of "
1633              "range"));
1634     return false;
1635   }
1636 
1637   // Get all contents of the focused editor.
1638   WidgetQueryContentEvent textContent(true, eQueryTextContent, aWindow);
1639   textContent.InitForQueryTextContent(0, UINT32_MAX);
1640   aWindow->InitEvent(textContent, &point);
1641   DispatchEvent(aWindow, textContent);
1642   if (!textContent.mSucceeded) {
1643     MOZ_LOG(gIMMLog, LogLevel::Error,
1644             ("HandleDocumentFeed, FAILED, due to eQueryTextContent failure"));
1645     return false;
1646   }
1647 
1648   nsAutoString str(textContent.mReply.mString);
1649   if (targetOffset > int32_t(str.Length())) {
1650     MOZ_LOG(gIMMLog, LogLevel::Error,
1651             ("HandleDocumentFeed, FAILED, due to the caret offset is invalid"));
1652     return false;
1653   }
1654 
1655   // Get the focused paragraph, we decide that it starts from the previous CRLF
1656   // (or start of the editor) to the next one (or the end of the editor).
1657   int32_t paragraphStart = str.RFind("\n", false, targetOffset, -1) + 1;
1658   int32_t paragraphEnd = str.Find("\r", false, targetOffset + targetLength, -1);
1659   if (paragraphEnd < 0) {
1660     paragraphEnd = str.Length();
1661   }
1662   nsDependentSubstring paragraph(str, paragraphStart,
1663                                  paragraphEnd - paragraphStart);
1664 
1665   uint32_t len = paragraph.Length();
1666   uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
1667 
1668   if (!pReconv) {
1669     *oResult = needSize;
1670     MOZ_LOG(gIMMLog, LogLevel::Info,
1671             ("HandleDocumentFeed, succeeded, result=%ld", *oResult));
1672     return true;
1673   }
1674 
1675   if (pReconv->dwSize < needSize) {
1676     MOZ_LOG(gIMMLog, LogLevel::Error,
1677             ("HandleDocumentFeed, FAILED, pReconv->dwSize=%ld, needSize=%ld",
1678              pReconv->dwSize, needSize));
1679     return false;
1680   }
1681 
1682   // Fill reconvert struct
1683   pReconv->dwVersion = 0;
1684   pReconv->dwStrLen = len;
1685   pReconv->dwStrOffset = sizeof(RECONVERTSTRING);
1686   if (hasCompositionString) {
1687     pReconv->dwCompStrLen = targetLength;
1688     pReconv->dwCompStrOffset = (targetOffset - paragraphStart) * sizeof(WCHAR);
1689     // Set composition target clause information
1690     uint32_t offset, length;
1691     if (!GetTargetClauseRange(&offset, &length)) {
1692       MOZ_LOG(gIMMLog, LogLevel::Error,
1693               ("HandleDocumentFeed, FAILED, due to GetTargetClauseRange() "
1694                "failure"));
1695       return false;
1696     }
1697     pReconv->dwTargetStrLen = length;
1698     pReconv->dwTargetStrOffset = (offset - paragraphStart) * sizeof(WCHAR);
1699   } else {
1700     pReconv->dwTargetStrLen = targetLength;
1701     pReconv->dwTargetStrOffset =
1702         (targetOffset - paragraphStart) * sizeof(WCHAR);
1703     // There is no composition string, so, the length is zero but we should
1704     // set the cursor offset to the composition str offset.
1705     pReconv->dwCompStrLen = 0;
1706     pReconv->dwCompStrOffset = pReconv->dwTargetStrOffset;
1707   }
1708 
1709   *oResult = needSize;
1710   ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)),
1711                paragraph.BeginReading(), len * sizeof(WCHAR));
1712 
1713   MOZ_LOG(gIMMLog, LogLevel::Info,
1714           ("HandleDocumentFeed, SUCCEEDED, pReconv=%s, result=%ld",
1715            GetReconvertStringLog(pReconv).get(), *oResult));
1716 
1717   return true;
1718 }
1719 
CommitCompositionOnPreviousWindow(nsWindow * aWindow)1720 bool IMMHandler::CommitCompositionOnPreviousWindow(nsWindow* aWindow) {
1721   if (!mComposingWindow || mComposingWindow == aWindow) {
1722     return false;
1723   }
1724 
1725   MOZ_LOG(gIMMLog, LogLevel::Info,
1726           ("CommitCompositionOnPreviousWindow, mIsComposing=%s",
1727            GetBoolName(mIsComposing)));
1728 
1729   // If we have composition, we should dispatch composition events internally.
1730   if (mIsComposing) {
1731     IMEContext context(mComposingWindow);
1732     NS_ASSERTION(context.IsValid(), "IME context must be valid");
1733 
1734     HandleEndComposition(mComposingWindow);
1735     return true;
1736   }
1737 
1738   return false;
1739 }
1740 
PlatformToNSAttr(uint8_t aAttr)1741 static TextRangeType PlatformToNSAttr(uint8_t aAttr) {
1742   switch (aAttr) {
1743     case ATTR_INPUT_ERROR:
1744     // case ATTR_FIXEDCONVERTED:
1745     case ATTR_INPUT:
1746       return TextRangeType::eRawClause;
1747     case ATTR_CONVERTED:
1748       return TextRangeType::eConvertedClause;
1749     case ATTR_TARGET_NOTCONVERTED:
1750       return TextRangeType::eSelectedRawClause;
1751     case ATTR_TARGET_CONVERTED:
1752       return TextRangeType::eSelectedClause;
1753     default:
1754       NS_ASSERTION(false, "unknown attribute");
1755       return TextRangeType::eCaret;
1756   }
1757 }
1758 
1759 // static
DispatchEvent(nsWindow * aWindow,WidgetGUIEvent & aEvent)1760 void IMMHandler::DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent) {
1761   MOZ_LOG(
1762       gIMMLog, LogLevel::Info,
1763       ("DispatchEvent(aWindow=0x%p, aEvent={ mMessage=%s }, "
1764        "aWindow->Destroyed()=%s",
1765        aWindow, ToChar(aEvent.mMessage), GetBoolName(aWindow->Destroyed())));
1766 
1767   if (aWindow->Destroyed()) {
1768     return;
1769   }
1770 
1771   aWindow->DispatchWindowEvent(&aEvent);
1772 }
1773 
DispatchCompositionChangeEvent(nsWindow * aWindow,const IMEContext & aContext)1774 void IMMHandler::DispatchCompositionChangeEvent(nsWindow* aWindow,
1775                                                 const IMEContext& aContext) {
1776   NS_ASSERTION(mIsComposing, "conflict state");
1777   MOZ_LOG(gIMMLog, LogLevel::Info, ("DispatchCompositionChangeEvent"));
1778 
1779   // If we don't need to draw composition string ourselves, we don't need to
1780   // fire compositionchange event during composing.
1781   if (!ShouldDrawCompositionStringOurselves()) {
1782     // But we need to adjust composition window pos and native caret pos, here.
1783     SetIMERelatedWindowsPos(aWindow, aContext);
1784     return;
1785   }
1786 
1787   RefPtr<nsWindow> kungFuDeathGrip(aWindow);
1788   RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
1789   nsresult rv = dispatcher->BeginNativeInputTransaction();
1790   if (NS_WARN_IF(NS_FAILED(rv))) {
1791     MOZ_LOG(gIMMLog, LogLevel::Error,
1792             ("DispatchCompositionChangeEvent, FAILED due to "
1793              "TextEventDispatcher::BeginNativeInputTransaction() failure"));
1794     return;
1795   }
1796 
1797   // NOTE: Calling SetIMERelatedWindowsPos() from this method will be failure
1798   //       in e10s mode.  compositionchange event will notify this of
1799   //       NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED, then
1800   //       SetIMERelatedWindowsPos() will be called.
1801 
1802   // XXX Sogou (Simplified Chinese IME) returns contradictory values:
1803   //     The cursor position is actual cursor position. However, other values
1804   //     (composition string and attributes) are empty.
1805 
1806   if (mCompositionString.IsEmpty()) {
1807     // Don't append clause information if composition string is empty.
1808   } else if (mClauseArray.IsEmpty()) {
1809     // Some IMEs don't return clause array information, then, we assume that
1810     // all characters in the composition string are in one clause.
1811     MOZ_LOG(gIMMLog, LogLevel::Info,
1812             ("DispatchCompositionChangeEvent, mClauseArray.Length()=0"));
1813     rv = dispatcher->SetPendingComposition(mCompositionString, nullptr);
1814     if (NS_WARN_IF(NS_FAILED(rv))) {
1815       MOZ_LOG(gIMMLog, LogLevel::Error,
1816               ("DispatchCompositionChangeEvent, FAILED due to"
1817                "TextEventDispatcher::SetPendingComposition() failure"));
1818       return;
1819     }
1820   } else {
1821     // iterate over the attributes
1822     rv = dispatcher->SetPendingCompositionString(mCompositionString);
1823     if (NS_WARN_IF(NS_FAILED(rv))) {
1824       MOZ_LOG(gIMMLog, LogLevel::Error,
1825               ("DispatchCompositionChangeEvent, FAILED due to"
1826                "TextEventDispatcher::SetPendingCompositionString() failure"));
1827       return;
1828     }
1829     uint32_t lastOffset = 0;
1830     for (uint32_t i = 0; i < mClauseArray.Length() - 1; i++) {
1831       uint32_t current = mClauseArray[i + 1];
1832       if (current > mCompositionString.Length()) {
1833         MOZ_LOG(gIMMLog, LogLevel::Info,
1834                 ("DispatchCompositionChangeEvent, mClauseArray[%ld]=%lu. "
1835                  "This is larger than mCompositionString.Length()=%lu",
1836                  i + 1, current, mCompositionString.Length()));
1837         current = int32_t(mCompositionString.Length());
1838       }
1839 
1840       uint32_t length = current - lastOffset;
1841       if (NS_WARN_IF(lastOffset >= mAttributeArray.Length())) {
1842         MOZ_LOG(
1843             gIMMLog, LogLevel::Error,
1844             ("DispatchCompositionChangeEvent, FAILED due to invalid data of "
1845              "mClauseArray or mAttributeArray"));
1846         return;
1847       }
1848       TextRangeType textRangeType =
1849           PlatformToNSAttr(mAttributeArray[lastOffset]);
1850       rv = dispatcher->AppendClauseToPendingComposition(length, textRangeType);
1851       if (NS_WARN_IF(NS_FAILED(rv))) {
1852         MOZ_LOG(gIMMLog, LogLevel::Error,
1853                 ("DispatchCompositionChangeEvent, FAILED due to"
1854                  "TextEventDispatcher::AppendClauseToPendingComposition() "
1855                  "failure"));
1856         return;
1857       }
1858 
1859       lastOffset = current;
1860 
1861       MOZ_LOG(gIMMLog, LogLevel::Info,
1862               ("DispatchCompositionChangeEvent, index=%ld, rangeType=%s, "
1863                "range length=%lu",
1864                i, ToChar(textRangeType), length));
1865     }
1866   }
1867 
1868   if (mCursorPosition == NO_IME_CARET) {
1869     MOZ_LOG(gIMMLog, LogLevel::Info,
1870             ("DispatchCompositionChangeEvent, no caret"));
1871   } else {
1872     uint32_t cursor = static_cast<uint32_t>(mCursorPosition);
1873     if (cursor > mCompositionString.Length()) {
1874       MOZ_LOG(gIMMLog, LogLevel::Info,
1875               ("CreateTextRangeArray, mCursorPosition=%ld. "
1876                "This is larger than mCompositionString.Length()=%lu",
1877                mCursorPosition, mCompositionString.Length()));
1878       cursor = mCompositionString.Length();
1879     }
1880 
1881     // If caret is in the target clause, the target clause will be painted as
1882     // normal selection range.  Since caret shouldn't be in selection range on
1883     // Windows, we shouldn't append caret range in such case.
1884     const TextRangeArray* clauses = dispatcher->GetPendingCompositionClauses();
1885     const TextRange* targetClause =
1886         clauses ? clauses->GetTargetClause() : nullptr;
1887     if (targetClause && cursor >= targetClause->mStartOffset &&
1888         cursor <= targetClause->mEndOffset) {
1889       // Forget the caret position specified by IME since Gecko's caret position
1890       // will be at the end of composition string.
1891       mCursorPosition = NO_IME_CARET;
1892       MOZ_LOG(gIMMLog, LogLevel::Info,
1893               ("CreateTextRangeArray, no caret due to it's in the target "
1894                "clause, now, mCursorPosition is NO_IME_CARET"));
1895     }
1896 
1897     if (mCursorPosition != NO_IME_CARET) {
1898       rv = dispatcher->SetCaretInPendingComposition(cursor, 0);
1899       if (NS_WARN_IF(NS_FAILED(rv))) {
1900         MOZ_LOG(
1901             gIMMLog, LogLevel::Error,
1902             ("DispatchCompositionChangeEvent, FAILED due to"
1903              "TextEventDispatcher::SetCaretInPendingComposition() failure"));
1904         return;
1905       }
1906     }
1907   }
1908 
1909   WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
1910   nsEventStatus status;
1911   rv = dispatcher->FlushPendingComposition(status, &eventTime);
1912   if (NS_WARN_IF(NS_FAILED(rv))) {
1913     MOZ_LOG(gIMMLog, LogLevel::Error,
1914             ("DispatchCompositionChangeEvent, FAILED due to"
1915              "TextEventDispatcher::FlushPendingComposition() failure"));
1916     return;
1917   }
1918 }
1919 
GetCompositionString(const IMEContext & aContext,DWORD aIndex,nsAString & aCompositionString) const1920 void IMMHandler::GetCompositionString(const IMEContext& aContext, DWORD aIndex,
1921                                       nsAString& aCompositionString) const {
1922   aCompositionString.Truncate();
1923 
1924   // Retrieve the size of the required output buffer.
1925   long lRtn = ::ImmGetCompositionStringW(aContext.get(), aIndex, nullptr, 0);
1926   if (lRtn < 0 || !aCompositionString.SetLength((lRtn / sizeof(WCHAR)) + 1,
1927                                                 mozilla::fallible)) {
1928     MOZ_LOG(gIMMLog, LogLevel::Error,
1929             ("GetCompositionString, FAILED, due to OOM"));
1930     return;  // Error or out of memory.
1931   }
1932 
1933   // Actually retrieve the composition string information.
1934   lRtn = ::ImmGetCompositionStringW(aContext.get(), aIndex,
1935                                     (LPVOID)aCompositionString.BeginWriting(),
1936                                     lRtn + sizeof(WCHAR));
1937   aCompositionString.SetLength(lRtn / sizeof(WCHAR));
1938 
1939   MOZ_LOG(gIMMLog, LogLevel::Info,
1940           ("GetCompositionString, succeeded, aCompositionString=\"%s\"",
1941            NS_ConvertUTF16toUTF8(aCompositionString).get()));
1942 }
1943 
GetTargetClauseRange(uint32_t * aOffset,uint32_t * aLength)1944 bool IMMHandler::GetTargetClauseRange(uint32_t* aOffset, uint32_t* aLength) {
1945   NS_ENSURE_TRUE(aOffset, false);
1946   NS_ENSURE_TRUE(mIsComposing, false);
1947   NS_ENSURE_TRUE(ShouldDrawCompositionStringOurselves(), false);
1948 
1949   bool found = false;
1950   *aOffset = mCompositionStart;
1951   for (uint32_t i = 0; i < mAttributeArray.Length(); i++) {
1952     if (mAttributeArray[i] == ATTR_TARGET_NOTCONVERTED ||
1953         mAttributeArray[i] == ATTR_TARGET_CONVERTED) {
1954       *aOffset = mCompositionStart + i;
1955       found = true;
1956       break;
1957     }
1958   }
1959 
1960   if (!aLength) {
1961     return true;
1962   }
1963 
1964   if (!found) {
1965     // The all composition string is targetted when there is no ATTR_TARGET_*
1966     // clause. E.g., there is only ATTR_INPUT
1967     *aLength = mCompositionString.Length();
1968     return true;
1969   }
1970 
1971   uint32_t offsetInComposition = *aOffset - mCompositionStart;
1972   *aLength = mCompositionString.Length() - offsetInComposition;
1973   for (uint32_t i = offsetInComposition; i < mAttributeArray.Length(); i++) {
1974     if (mAttributeArray[i] != ATTR_TARGET_NOTCONVERTED &&
1975         mAttributeArray[i] != ATTR_TARGET_CONVERTED) {
1976       *aLength = i - offsetInComposition;
1977       break;
1978     }
1979   }
1980   return true;
1981 }
1982 
ConvertToANSIString(const nsString & aStr,UINT aCodePage,nsACString & aANSIStr)1983 bool IMMHandler::ConvertToANSIString(const nsString& aStr, UINT aCodePage,
1984                                      nsACString& aANSIStr) {
1985   int len = ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(),
1986                                   aStr.Length(), nullptr, 0, nullptr, nullptr);
1987   NS_ENSURE_TRUE(len >= 0, false);
1988 
1989   if (!aANSIStr.SetLength(len, mozilla::fallible)) {
1990     MOZ_LOG(gIMMLog, LogLevel::Error,
1991             ("ConvertToANSIString, FAILED, due to OOM"));
1992     return false;
1993   }
1994   ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(),
1995                         (LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr);
1996   return true;
1997 }
1998 
GetCharacterRectOfSelectedTextAt(nsWindow * aWindow,uint32_t aOffset,LayoutDeviceIntRect & aCharRect,WritingMode * aWritingMode)1999 bool IMMHandler::GetCharacterRectOfSelectedTextAt(
2000     nsWindow* aWindow, uint32_t aOffset, LayoutDeviceIntRect& aCharRect,
2001     WritingMode* aWritingMode) {
2002   LayoutDeviceIntPoint point(0, 0);
2003 
2004   Selection& selection = GetSelection();
2005   if (!selection.EnsureValidSelection(aWindow)) {
2006     MOZ_LOG(gIMMLog, LogLevel::Error,
2007             ("GetCharacterRectOfSelectedTextAt, FAILED, due to "
2008              "Selection::EnsureValidSelection() failure"));
2009     return false;
2010   }
2011 
2012   // If the offset is larger than the end of composition string or selected
2013   // string, we should return false since such case must be a bug of the caller
2014   // or the active IME.  If it's an IME's bug, we need to set targetLength to
2015   // aOffset.
2016   uint32_t targetLength =
2017       mIsComposing ? mCompositionString.Length() : selection.Length();
2018   if (NS_WARN_IF(aOffset > targetLength)) {
2019     MOZ_LOG(
2020         gIMMLog, LogLevel::Error,
2021         ("GetCharacterRectOfSelectedTextAt, FAILED, due to "
2022          "aOffset is too large (aOffset=%u, targetLength=%u, mIsComposing=%s)",
2023          aOffset, targetLength, GetBoolName(mIsComposing)));
2024     return false;
2025   }
2026 
2027   // If there is caret, we might be able to use caret rect.
2028   uint32_t caretOffset = UINT32_MAX;
2029   // There is a caret only when the normal selection is collapsed.
2030   if (selection.Collapsed()) {
2031     if (mIsComposing) {
2032       // If it's composing, mCursorPosition is the offset to caret in
2033       // the composition string.
2034       if (mCursorPosition != NO_IME_CARET) {
2035         MOZ_ASSERT(mCursorPosition >= 0);
2036         caretOffset = mCursorPosition;
2037       } else if (!ShouldDrawCompositionStringOurselves() ||
2038                  mCompositionString.IsEmpty()) {
2039         // Otherwise, if there is no composition string, we should assume that
2040         // there is a caret at the start of composition string.
2041         caretOffset = 0;
2042       }
2043     } else {
2044       // If there is no composition, the selection offset is the caret offset.
2045       caretOffset = 0;
2046     }
2047   }
2048 
2049   // If there is a caret and retrieving offset is same as the caret offset,
2050   // we should use the caret rect.
2051   if (aOffset != caretOffset) {
2052     WidgetQueryContentEvent charRect(true, eQueryTextRect, aWindow);
2053     WidgetQueryContentEvent::Options options;
2054     options.mRelativeToInsertionPoint = true;
2055     charRect.InitForQueryTextRect(aOffset, 1, options);
2056     aWindow->InitEvent(charRect, &point);
2057     DispatchEvent(aWindow, charRect);
2058     if (charRect.mSucceeded) {
2059       aCharRect = charRect.mReply.mRect;
2060       if (aWritingMode) {
2061         *aWritingMode = charRect.GetWritingMode();
2062       }
2063       MOZ_LOG(gIMMLog, LogLevel::Debug,
2064               ("GetCharacterRectOfSelectedTextAt, Succeeded, aOffset=%u, "
2065                "aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
2066                "charRect.GetWritingMode()=%s",
2067                aOffset, aCharRect.X(), aCharRect.Y(), aCharRect.Width(),
2068                aCharRect.Height(),
2069                GetWritingModeName(charRect.GetWritingMode()).get()));
2070       return true;
2071     }
2072   }
2073 
2074   return GetCaretRect(aWindow, aCharRect, aWritingMode);
2075 }
2076 
GetCaretRect(nsWindow * aWindow,LayoutDeviceIntRect & aCaretRect,WritingMode * aWritingMode)2077 bool IMMHandler::GetCaretRect(nsWindow* aWindow,
2078                               LayoutDeviceIntRect& aCaretRect,
2079                               WritingMode* aWritingMode) {
2080   LayoutDeviceIntPoint point(0, 0);
2081 
2082   WidgetQueryContentEvent caretRect(true, eQueryCaretRect, aWindow);
2083   WidgetQueryContentEvent::Options options;
2084   options.mRelativeToInsertionPoint = true;
2085   caretRect.InitForQueryCaretRect(0, options);
2086   aWindow->InitEvent(caretRect, &point);
2087   DispatchEvent(aWindow, caretRect);
2088   if (!caretRect.mSucceeded) {
2089     MOZ_LOG(gIMMLog, LogLevel::Info,
2090             ("GetCaretRect, FAILED, due to eQueryCaretRect failure"));
2091     return false;
2092   }
2093   aCaretRect = caretRect.mReply.mRect;
2094   if (aWritingMode) {
2095     *aWritingMode = caretRect.GetWritingMode();
2096   }
2097   MOZ_LOG(
2098       gIMMLog, LogLevel::Info,
2099       ("GetCaretRect, SUCCEEDED, "
2100        "aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
2101        "caretRect.GetWritingMode()=%s",
2102        aCaretRect.X(), aCaretRect.Y(), aCaretRect.Width(), aCaretRect.Height(),
2103        GetWritingModeName(caretRect.GetWritingMode()).get()));
2104   return true;
2105 }
2106 
SetIMERelatedWindowsPos(nsWindow * aWindow,const IMEContext & aContext)2107 bool IMMHandler::SetIMERelatedWindowsPos(nsWindow* aWindow,
2108                                          const IMEContext& aContext) {
2109   LayoutDeviceIntRect r;
2110   // Get first character rect of current a normal selected text or a composing
2111   // string.
2112   WritingMode writingMode;
2113   bool ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r, &writingMode);
2114   NS_ENSURE_TRUE(ret, false);
2115   nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
2116   LayoutDeviceIntRect firstSelectedCharRect;
2117   ResolveIMECaretPos(toplevelWindow, r, aWindow, firstSelectedCharRect);
2118 
2119   // Set native caret size/position to our caret. Some IMEs honor it. E.g.,
2120   // "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified
2121   // Chinese) on XP.
2122   LayoutDeviceIntRect caretRect(firstSelectedCharRect);
2123   if (GetCaretRect(aWindow, r)) {
2124     ResolveIMECaretPos(toplevelWindow, r, aWindow, caretRect);
2125   } else {
2126     NS_WARNING("failed to get caret rect");
2127     caretRect.SetWidth(1);
2128   }
2129   if (!mNativeCaretIsCreated) {
2130     mNativeCaretIsCreated =
2131         ::CreateCaret(aWindow->GetWindowHandle(), nullptr, caretRect.Width(),
2132                       caretRect.Height());
2133     MOZ_LOG(gIMMLog, LogLevel::Info,
2134             ("SetIMERelatedWindowsPos, mNativeCaretIsCreated=%s, "
2135              "width=%ld, height=%ld",
2136              GetBoolName(mNativeCaretIsCreated), caretRect.Width(),
2137              caretRect.Height()));
2138   }
2139   ::SetCaretPos(caretRect.X(), caretRect.Y());
2140 
2141   if (ShouldDrawCompositionStringOurselves()) {
2142     MOZ_LOG(gIMMLog, LogLevel::Info,
2143             ("SetIMERelatedWindowsPos, Set candidate window"));
2144 
2145     // Get a rect of first character in current target in composition string.
2146     LayoutDeviceIntRect firstTargetCharRect, lastTargetCharRect;
2147     if (mIsComposing && !mCompositionString.IsEmpty()) {
2148       // If there are no targetted selection, we should use it's first character
2149       // rect instead.
2150       uint32_t offset, length;
2151       if (!GetTargetClauseRange(&offset, &length)) {
2152         MOZ_LOG(gIMMLog, LogLevel::Error,
2153                 ("SetIMERelatedWindowsPos, FAILED, due to "
2154                  "GetTargetClauseRange() failure"));
2155         return false;
2156       }
2157       ret =
2158           GetCharacterRectOfSelectedTextAt(aWindow, offset - mCompositionStart,
2159                                            firstTargetCharRect, &writingMode);
2160       NS_ENSURE_TRUE(ret, false);
2161       if (length) {
2162         ret = GetCharacterRectOfSelectedTextAt(
2163             aWindow, offset + length - 1 - mCompositionStart,
2164             lastTargetCharRect);
2165         NS_ENSURE_TRUE(ret, false);
2166       } else {
2167         lastTargetCharRect = firstTargetCharRect;
2168       }
2169     } else {
2170       // If there are no composition string, we should use a first character
2171       // rect.
2172       ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, firstTargetCharRect,
2173                                              &writingMode);
2174       NS_ENSURE_TRUE(ret, false);
2175       lastTargetCharRect = firstTargetCharRect;
2176     }
2177     ResolveIMECaretPos(toplevelWindow, firstTargetCharRect, aWindow,
2178                        firstTargetCharRect);
2179     ResolveIMECaretPos(toplevelWindow, lastTargetCharRect, aWindow,
2180                        lastTargetCharRect);
2181     LayoutDeviceIntRect targetClauseRect;
2182     targetClauseRect.UnionRect(firstTargetCharRect, lastTargetCharRect);
2183 
2184     // Move the candidate window to proper position from the target clause as
2185     // far as possible.
2186     CANDIDATEFORM candForm;
2187     candForm.dwIndex = 0;
2188     if (!writingMode.IsVertical() || IsVerticalWritingSupported()) {
2189       candForm.dwStyle = CFS_EXCLUDE;
2190       // Candidate window shouldn't overlap the target clause in any writing
2191       // mode.
2192       candForm.rcArea.left = targetClauseRect.X();
2193       candForm.rcArea.right = targetClauseRect.XMost();
2194       candForm.rcArea.top = targetClauseRect.Y();
2195       candForm.rcArea.bottom = targetClauseRect.YMost();
2196       if (!writingMode.IsVertical()) {
2197         // In horizontal layout, current point of interest should be top-left
2198         // of the first character.
2199         candForm.ptCurrentPos.x = firstTargetCharRect.X();
2200         candForm.ptCurrentPos.y = firstTargetCharRect.Y();
2201       } else if (writingMode.IsVerticalRL()) {
2202         // In vertical layout (RL), candidate window should be positioned right
2203         // side of target clause.  However, we don't set vertical writing font
2204         // to the IME.  Therefore, the candidate window may be positioned
2205         // bottom-left of target clause rect with these information.
2206         candForm.ptCurrentPos.x = targetClauseRect.X();
2207         candForm.ptCurrentPos.y = targetClauseRect.Y();
2208       } else {
2209         MOZ_ASSERT(writingMode.IsVerticalLR(), "Did we miss some causes?");
2210         // In vertical layout (LR), candidate window should be poisitioned left
2211         // side of target clause.  Although, we don't set vertical writing font
2212         // to the IME, the candidate window may be positioned bottom-right of
2213         // the target clause rect with these information.
2214         candForm.ptCurrentPos.x = targetClauseRect.XMost();
2215         candForm.ptCurrentPos.y = targetClauseRect.Y();
2216       }
2217     } else {
2218       // If vertical writing is not supported by IME, let's set candidate
2219       // window position to the bottom-left of the target clause because
2220       // the position must be the safest position to prevent the candidate
2221       // window to overlap with the target clause.
2222       candForm.dwStyle = CFS_CANDIDATEPOS;
2223       candForm.ptCurrentPos.x = targetClauseRect.X();
2224       candForm.ptCurrentPos.y = targetClauseRect.YMost();
2225     }
2226     MOZ_LOG(gIMMLog, LogLevel::Info,
2227             ("SetIMERelatedWindowsPos, Calling ImmSetCandidateWindow()... "
2228              "ptCurrentPos={ x=%d, y=%d }, "
2229              "rcArea={ left=%d, top=%d, right=%d, bottom=%d }, "
2230              "writingMode=%s",
2231              candForm.ptCurrentPos.x, candForm.ptCurrentPos.y,
2232              candForm.rcArea.left, candForm.rcArea.top, candForm.rcArea.right,
2233              candForm.rcArea.bottom, GetWritingModeName(writingMode).get()));
2234     ::ImmSetCandidateWindow(aContext.get(), &candForm);
2235   } else {
2236     MOZ_LOG(gIMMLog, LogLevel::Info,
2237             ("SetIMERelatedWindowsPos, Set composition window"));
2238 
2239     // Move the composition window to caret position (if selected some
2240     // characters, we should use first character rect of them).
2241     // And in this mode, IME adjusts the candidate window position
2242     // automatically. So, we don't need to set it.
2243     COMPOSITIONFORM compForm;
2244     compForm.dwStyle = CFS_POINT;
2245     compForm.ptCurrentPos.x = !writingMode.IsVerticalLR()
2246                                   ? firstSelectedCharRect.X()
2247                                   : firstSelectedCharRect.XMost();
2248     compForm.ptCurrentPos.y = firstSelectedCharRect.Y();
2249     ::ImmSetCompositionWindow(aContext.get(), &compForm);
2250   }
2251 
2252   return true;
2253 }
2254 
SetIMERelatedWindowsPosOnPlugin(nsWindow * aWindow,const IMEContext & aContext)2255 void IMMHandler::SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow,
2256                                                  const IMEContext& aContext) {
2257   WidgetQueryContentEvent editorRectEvent(true, eQueryEditorRect, aWindow);
2258   aWindow->InitEvent(editorRectEvent);
2259   DispatchEvent(aWindow, editorRectEvent);
2260   if (!editorRectEvent.mSucceeded) {
2261     MOZ_LOG(gIMMLog, LogLevel::Info,
2262             ("SetIMERelatedWindowsPosOnPlugin, "
2263              "FAILED, due to eQueryEditorRect failure"));
2264     return;
2265   }
2266 
2267   // Clip the plugin rect by the client rect of the window because composition
2268   // window needs to be specified the position in the client area.
2269   nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
2270   LayoutDeviceIntRect pluginRectInScreen =
2271       editorRectEvent.mReply.mRect + toplevelWindow->WidgetToScreenOffset();
2272   LayoutDeviceIntRect winRectInScreen = aWindow->GetClientBounds();
2273   // composition window cannot be positioned on the edge of client area.
2274   winRectInScreen.SizeTo(winRectInScreen.Width() - 1,
2275                          winRectInScreen.Height() - 1);
2276   LayoutDeviceIntRect clippedPluginRect;
2277   clippedPluginRect.MoveTo(
2278       std::min(std::max(pluginRectInScreen.X(), winRectInScreen.X()),
2279                winRectInScreen.XMost()),
2280       std::min(std::max(pluginRectInScreen.Y(), winRectInScreen.Y()),
2281                winRectInScreen.YMost()));
2282   int32_t xMost = std::min(pluginRectInScreen.XMost(), winRectInScreen.XMost());
2283   int32_t yMost = std::min(pluginRectInScreen.YMost(), winRectInScreen.YMost());
2284   clippedPluginRect.SizeTo(std::max(0, xMost - clippedPluginRect.X()),
2285                            std::max(0, yMost - clippedPluginRect.Y()));
2286   clippedPluginRect -= aWindow->WidgetToScreenOffset();
2287 
2288   // Cover the plugin with native caret.  This prevents IME's window and plugin
2289   // overlap.
2290   if (mNativeCaretIsCreated) {
2291     ::DestroyCaret();
2292   }
2293   mNativeCaretIsCreated =
2294       ::CreateCaret(aWindow->GetWindowHandle(), nullptr,
2295                     clippedPluginRect.Width(), clippedPluginRect.Height());
2296   ::SetCaretPos(clippedPluginRect.X(), clippedPluginRect.Y());
2297 
2298   // Set the composition window to bottom-left of the clipped plugin.
2299   // As far as we know, there is no IME for RTL language.  Therefore, this code
2300   // must not need to take care of RTL environment.
2301   COMPOSITIONFORM compForm;
2302   compForm.dwStyle = CFS_POINT;
2303   compForm.ptCurrentPos.x = clippedPluginRect.BottomLeft().x;
2304   compForm.ptCurrentPos.y = clippedPluginRect.BottomLeft().y;
2305   if (!::ImmSetCompositionWindow(aContext.get(), &compForm)) {
2306     MOZ_LOG(gIMMLog, LogLevel::Info,
2307             ("SetIMERelatedWindowsPosOnPlugin, "
2308              "FAILED, due to ::ImmSetCompositionWindow() failure"));
2309     return;
2310   }
2311 }
2312 
ResolveIMECaretPos(nsIWidget * aReferenceWidget,LayoutDeviceIntRect & aCursorRect,nsIWidget * aNewOriginWidget,LayoutDeviceIntRect & aOutRect)2313 void IMMHandler::ResolveIMECaretPos(nsIWidget* aReferenceWidget,
2314                                     LayoutDeviceIntRect& aCursorRect,
2315                                     nsIWidget* aNewOriginWidget,
2316                                     LayoutDeviceIntRect& aOutRect) {
2317   aOutRect = aCursorRect;
2318 
2319   if (aReferenceWidget == aNewOriginWidget) return;
2320 
2321   if (aReferenceWidget)
2322     aOutRect.MoveBy(aReferenceWidget->WidgetToScreenOffset());
2323 
2324   if (aNewOriginWidget)
2325     aOutRect.MoveBy(-aNewOriginWidget->WidgetToScreenOffset());
2326 }
2327 
SetHorizontalFontToLogFont(const nsAString & aFontFace,LOGFONTW & aLogFont)2328 static void SetHorizontalFontToLogFont(const nsAString& aFontFace,
2329                                        LOGFONTW& aLogFont) {
2330   aLogFont.lfEscapement = aLogFont.lfOrientation = 0;
2331   if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 1)) {
2332     memcpy(aLogFont.lfFaceName, L"System", sizeof(L"System"));
2333     return;
2334   }
2335   memcpy(aLogFont.lfFaceName, aFontFace.BeginReading(),
2336          aFontFace.Length() * sizeof(wchar_t));
2337   aLogFont.lfFaceName[aFontFace.Length()] = 0;
2338 }
2339 
SetVerticalFontToLogFont(const nsAString & aFontFace,LOGFONTW & aLogFont)2340 static void SetVerticalFontToLogFont(const nsAString& aFontFace,
2341                                      LOGFONTW& aLogFont) {
2342   aLogFont.lfEscapement = aLogFont.lfOrientation = 2700;
2343   if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 2)) {
2344     memcpy(aLogFont.lfFaceName, L"@System", sizeof(L"@System"));
2345     return;
2346   }
2347   aLogFont.lfFaceName[0] = '@';
2348   memcpy(&aLogFont.lfFaceName[1], aFontFace.BeginReading(),
2349          aFontFace.Length() * sizeof(wchar_t));
2350   aLogFont.lfFaceName[aFontFace.Length() + 1] = 0;
2351 }
2352 
AdjustCompositionFont(nsWindow * aWindow,const IMEContext & aContext,const WritingMode & aWritingMode,bool aForceUpdate)2353 void IMMHandler::AdjustCompositionFont(nsWindow* aWindow,
2354                                        const IMEContext& aContext,
2355                                        const WritingMode& aWritingMode,
2356                                        bool aForceUpdate) {
2357   // An instance of IMMHandler is destroyed when active IME is changed.
2358   // Therefore, we need to store the information which are set to the IM
2359   // context to static variables since IM context is never recreated.
2360   static bool sCompositionFontsInitialized = false;
2361   static nsString sCompositionFont;
2362   static bool sCompositionFontPrefDone = false;
2363   if (!sCompositionFontPrefDone) {
2364     sCompositionFontPrefDone = true;
2365     Preferences::GetString("intl.imm.composition_font", sCompositionFont);
2366   }
2367 
2368   // If composition font is customized by pref, we need to modify the
2369   // composition font of the IME context at first time even if the writing mode
2370   // is horizontal.
2371   bool setCompositionFontForcibly =
2372       aForceUpdate ||
2373       (!sCompositionFontsInitialized && !sCompositionFont.IsEmpty());
2374 
2375   static WritingMode sCurrentWritingMode;
2376   static nsString sCurrentIMEName;
2377   if (!setCompositionFontForcibly &&
2378       sWritingModeOfCompositionFont == aWritingMode &&
2379       sCurrentIMEName == sIMEName) {
2380     // Nothing to do if writing mode isn't being changed.
2381     return;
2382   }
2383 
2384   // Decide composition fonts for both horizontal writing mode and vertical
2385   // writing mode.  If the font isn't specified by the pref, use default
2386   // font which is already set to the IM context.  And also in vertical writing
2387   // mode, insert '@' to the start of the font.
2388   if (!sCompositionFontsInitialized) {
2389     sCompositionFontsInitialized = true;
2390     // sCompositionFontH must not start with '@' and its length is less than
2391     // LF_FACESIZE since it needs to end with null terminating character.
2392     if (sCompositionFont.IsEmpty() ||
2393         sCompositionFont.Length() > LF_FACESIZE - 1 ||
2394         sCompositionFont[0] == '@') {
2395       LOGFONTW defaultLogFont;
2396       if (NS_WARN_IF(
2397               !::ImmGetCompositionFont(aContext.get(), &defaultLogFont))) {
2398         MOZ_LOG(gIMMLog, LogLevel::Error,
2399                 ("AdjustCompositionFont, ::ImmGetCompositionFont() failed"));
2400         sCompositionFont.AssignLiteral("System");
2401       } else {
2402         // The font face is typically, "System".
2403         sCompositionFont.Assign(defaultLogFont.lfFaceName);
2404       }
2405     }
2406 
2407     MOZ_LOG(gIMMLog, LogLevel::Info,
2408             ("AdjustCompositionFont, sCompositionFont=\"%s\" is initialized",
2409              NS_ConvertUTF16toUTF8(sCompositionFont).get()));
2410   }
2411 
2412   static nsString sCompositionFontForJapanist2003;
2413   if (IsJapanist2003Active() && sCompositionFontForJapanist2003.IsEmpty()) {
2414     const char* kCompositionFontForJapanist2003 =
2415         "intl.imm.composition_font.japanist_2003";
2416     Preferences::GetString(kCompositionFontForJapanist2003,
2417                            sCompositionFontForJapanist2003);
2418     // If the font name is not specified properly, let's use
2419     // "MS PGothic" instead.
2420     if (sCompositionFontForJapanist2003.IsEmpty() ||
2421         sCompositionFontForJapanist2003.Length() > LF_FACESIZE - 2 ||
2422         sCompositionFontForJapanist2003[0] == '@') {
2423       sCompositionFontForJapanist2003.AssignLiteral("MS PGothic");
2424     }
2425   }
2426 
2427   sWritingModeOfCompositionFont = aWritingMode;
2428   sCurrentIMEName = sIMEName;
2429 
2430   LOGFONTW logFont;
2431   memset(&logFont, 0, sizeof(logFont));
2432   if (!::ImmGetCompositionFont(aContext.get(), &logFont)) {
2433     MOZ_LOG(gIMMLog, LogLevel::Error,
2434             ("AdjustCompositionFont, ::ImmGetCompositionFont() failed"));
2435     logFont.lfFaceName[0] = 0;
2436   }
2437   // Need to reset some information which should be recomputed with new font.
2438   logFont.lfWidth = 0;
2439   logFont.lfWeight = FW_DONTCARE;
2440   logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
2441   logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
2442   logFont.lfPitchAndFamily = DEFAULT_PITCH;
2443 
2444   if (!aWindow->PluginHasFocus() && aWritingMode.IsVertical() &&
2445       IsVerticalWritingSupported()) {
2446     SetVerticalFontToLogFont(IsJapanist2003Active()
2447                                  ? sCompositionFontForJapanist2003
2448                                  : sCompositionFont,
2449                              logFont);
2450   } else {
2451     SetHorizontalFontToLogFont(IsJapanist2003Active()
2452                                    ? sCompositionFontForJapanist2003
2453                                    : sCompositionFont,
2454                                logFont);
2455   }
2456   MOZ_LOG(gIMMLog, LogLevel::Warning,
2457           ("AdjustCompositionFont, calling ::ImmSetCompositionFont(\"%s\")",
2458            NS_ConvertUTF16toUTF8(nsDependentString(logFont.lfFaceName)).get()));
2459   ::ImmSetCompositionFontW(aContext.get(), &logFont);
2460 }
2461 
2462 // static
OnMouseButtonEvent(nsWindow * aWindow,const IMENotification & aIMENotification)2463 nsresult IMMHandler::OnMouseButtonEvent(
2464     nsWindow* aWindow, const IMENotification& aIMENotification) {
2465   // We don't need to create the instance of the handler here.
2466   if (!gIMMHandler) {
2467     return NS_OK;
2468   }
2469 
2470   if (!sWM_MSIME_MOUSE || !IsComposingOnOurEditor() ||
2471       !ShouldDrawCompositionStringOurselves()) {
2472     return NS_OK;
2473   }
2474 
2475   // We need to handle only mousedown event.
2476   if (aIMENotification.mMouseButtonEventData.mEventMessage != eMouseDown) {
2477     return NS_OK;
2478   }
2479 
2480   // If the character under the cursor is not in the composition string,
2481   // we don't need to notify IME of it.
2482   uint32_t compositionStart = gIMMHandler->mCompositionStart;
2483   uint32_t compositionEnd =
2484       compositionStart + gIMMHandler->mCompositionString.Length();
2485   if (aIMENotification.mMouseButtonEventData.mOffset < compositionStart ||
2486       aIMENotification.mMouseButtonEventData.mOffset >= compositionEnd) {
2487     return NS_OK;
2488   }
2489 
2490   BYTE button;
2491   switch (aIMENotification.mMouseButtonEventData.mButton) {
2492     case WidgetMouseEventBase::eLeftButton:
2493       button = IMEMOUSE_LDOWN;
2494       break;
2495     case WidgetMouseEventBase::eMiddleButton:
2496       button = IMEMOUSE_MDOWN;
2497       break;
2498     case WidgetMouseEventBase::eRightButton:
2499       button = IMEMOUSE_RDOWN;
2500       break;
2501     default:
2502       return NS_OK;
2503   }
2504 
2505   // calcurate positioning and offset
2506   // char :            JCH1|JCH2|JCH3
2507   // offset:           0011 1122 2233
2508   // positioning:      2301 2301 2301
2509   nsIntPoint cursorPos =
2510       aIMENotification.mMouseButtonEventData.mCursorPos.AsIntPoint();
2511   nsIntRect charRect =
2512       aIMENotification.mMouseButtonEventData.mCharRect.AsIntRect();
2513   int32_t cursorXInChar = cursorPos.x - charRect.X();
2514   // The event might hit to zero-width character, see bug 694913.
2515   // The reason might be:
2516   // * There are some zero-width characters are actually.
2517   // * font-size is specified zero.
2518   // But nobody reproduced this bug actually...
2519   // We should assume that user clicked on right most of the zero-width
2520   // character in such case.
2521   int positioning = 1;
2522   if (charRect.Width() > 0) {
2523     positioning = cursorXInChar * 4 / charRect.Width();
2524     positioning = (positioning + 2) % 4;
2525   }
2526 
2527   int offset =
2528       aIMENotification.mMouseButtonEventData.mOffset - compositionStart;
2529   if (positioning < 2) {
2530     offset++;
2531   }
2532 
2533   MOZ_LOG(gIMMLog, LogLevel::Info,
2534           ("OnMouseButtonEvent, x,y=%ld,%ld, offset=%ld, positioning=%ld",
2535            cursorPos.x, cursorPos.y, offset, positioning));
2536 
2537   // send MS_MSIME_MOUSE message to default IME window.
2538   HWND imeWnd = ::ImmGetDefaultIMEWnd(aWindow->GetWindowHandle());
2539   IMEContext context(aWindow);
2540   if (::SendMessageW(imeWnd, sWM_MSIME_MOUSE,
2541                      MAKELONG(MAKEWORD(button, positioning), offset),
2542                      (LPARAM)context.get()) == 1) {
2543     return NS_SUCCESS_EVENT_CONSUMED;
2544   }
2545   return NS_OK;
2546 }
2547 
2548 // static
OnKeyDownEvent(nsWindow * aWindow,WPARAM wParam,LPARAM lParam,MSGResult & aResult)2549 bool IMMHandler::OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
2550                                 MSGResult& aResult) {
2551   MOZ_LOG(gIMMLog, LogLevel::Info,
2552           ("OnKeyDownEvent, hWnd=%08x, wParam=%08x, lParam=%08x",
2553            aWindow->GetWindowHandle(), wParam, lParam));
2554   aResult.mConsumed = false;
2555   switch (wParam) {
2556     case VK_TAB:
2557     case VK_PRIOR:
2558     case VK_NEXT:
2559     case VK_END:
2560     case VK_HOME:
2561     case VK_LEFT:
2562     case VK_UP:
2563     case VK_RIGHT:
2564     case VK_DOWN:
2565     case VK_RETURN:
2566       // If IME didn't process the key message (the virtual key code wasn't
2567       // converted to VK_PROCESSKEY), and the virtual key code event causes
2568       // moving caret or editing text with keeping composing state, we should
2569       // cancel the composition here because we cannot support moving
2570       // composition string with DOM events (IE also cancels the composition
2571       // in same cases).  Then, this event will be dispatched.
2572       if (IsComposingOnOurEditor()) {
2573         // NOTE: We don't need to cancel the composition on another window.
2574         CancelComposition(aWindow, false);
2575       }
2576       return false;
2577     default:
2578       return false;
2579   }
2580 }
2581 
2582 // static
SetCandidateWindow(nsWindow * aWindow,CANDIDATEFORM * aForm)2583 void IMMHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm) {
2584   // Hack for ATOK 2011 - 2016 (Japanese IME).  They refer native caret
2585   // position at deciding candidate window position.  Note that we cannot
2586   // check active IME since TIPs are wrapped and hidden by CUAS.
2587   if (aWindow->PluginHasFocus()) {
2588     // We cannot retrieve proper character height from plugin.  Therefore,
2589     // we should assume that the caret height is always 20px since if less than
2590     // this height, candidate window may overlap with composition string when
2591     // there is no enough space under composition string to show candidate
2592     // window.
2593     static const int32_t kCaretHeight = 20;
2594     if (sNativeCaretIsCreatedForPlugin) {
2595       ::DestroyCaret();
2596     }
2597     sNativeCaretIsCreatedForPlugin =
2598         ::CreateCaret(aWindow->GetWindowHandle(), nullptr, 0, kCaretHeight);
2599     if (sNativeCaretIsCreatedForPlugin) {
2600       LayoutDeviceIntPoint caretPosition(aForm->ptCurrentPos.x,
2601                                          aForm->ptCurrentPos.y - kCaretHeight);
2602       nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
2603       if (toplevelWindow && toplevelWindow != aWindow) {
2604         caretPosition += toplevelWindow->WidgetToScreenOffset();
2605         caretPosition -= aWindow->WidgetToScreenOffset();
2606       }
2607       ::SetCaretPos(caretPosition.x, caretPosition.y);
2608     }
2609   }
2610   IMEContext context(aWindow);
2611   ::ImmSetCandidateWindow(context.get(), aForm);
2612 }
2613 
2614 // staitc
DefaultProcOfPluginEvent(nsWindow * aWindow,const NPEvent * aEvent)2615 void IMMHandler::DefaultProcOfPluginEvent(nsWindow* aWindow,
2616                                           const NPEvent* aEvent) {
2617   switch (aEvent->event) {
2618     case WM_IME_STARTCOMPOSITION:
2619       EnsureHandlerInstance();
2620       gIMMHandler->OnIMEStartCompositionOnPlugin(aWindow, aEvent->wParam,
2621                                                  aEvent->lParam);
2622       break;
2623 
2624     case WM_IME_COMPOSITION:
2625       if (gIMMHandler) {
2626         gIMMHandler->OnIMECompositionOnPlugin(aWindow, aEvent->wParam,
2627                                               aEvent->lParam);
2628       }
2629       break;
2630 
2631     case WM_IME_ENDCOMPOSITION:
2632       if (gIMMHandler) {
2633         gIMMHandler->OnIMEEndCompositionOnPlugin(aWindow, aEvent->wParam,
2634                                                  aEvent->lParam);
2635       }
2636       break;
2637   }
2638 }
2639 
2640 /******************************************************************************
2641  * IMMHandler::Selection
2642  ******************************************************************************/
2643 
IsValid() const2644 bool IMMHandler::Selection::IsValid() const {
2645   if (!mIsValid || NS_WARN_IF(mOffset == UINT32_MAX)) {
2646     return false;
2647   }
2648   CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(mOffset) + Length();
2649   return endOffset.isValid();
2650 }
2651 
Update(const IMENotification & aIMENotification)2652 bool IMMHandler::Selection::Update(const IMENotification& aIMENotification) {
2653   mOffset = aIMENotification.mSelectionChangeData.mOffset;
2654   mString = aIMENotification.mSelectionChangeData.String();
2655   mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
2656   mIsValid = true;
2657 
2658   MOZ_LOG(gIMMLog, LogLevel::Info,
2659           ("Selection::Update, aIMENotification={ mSelectionChangeData={ "
2660            "mOffset=%u, mLength=%u, GetWritingMode()=%s } }",
2661            mOffset, mString.Length(), GetWritingModeName(mWritingMode).get()));
2662 
2663   if (!IsValid()) {
2664     MOZ_LOG(gIMMLog, LogLevel::Error,
2665             ("Selection::Update, FAILED, due to invalid range"));
2666     Clear();
2667     return false;
2668   }
2669   return true;
2670 }
2671 
Init(nsWindow * aWindow)2672 bool IMMHandler::Selection::Init(nsWindow* aWindow) {
2673   Clear();
2674 
2675   WidgetQueryContentEvent selection(true, eQuerySelectedText, aWindow);
2676   LayoutDeviceIntPoint point(0, 0);
2677   aWindow->InitEvent(selection, &point);
2678   DispatchEvent(aWindow, selection);
2679   if (NS_WARN_IF(!selection.mSucceeded)) {
2680     MOZ_LOG(gIMMLog, LogLevel::Error,
2681             ("Selection::Init, FAILED, due to eQuerySelectedText failure"));
2682     return false;
2683   }
2684   // If the window is destroyed during querying selected text, we shouldn't
2685   // do anymore.
2686   if (aWindow->Destroyed()) {
2687     MOZ_LOG(gIMMLog, LogLevel::Error,
2688             ("Selection::Init, FAILED, due to the widget destroyed"));
2689     return false;
2690   }
2691 
2692   mOffset = selection.mReply.mOffset;
2693   mString = selection.mReply.mString;
2694   mWritingMode = selection.GetWritingMode();
2695   mIsValid = true;
2696 
2697   MOZ_LOG(gIMMLog, LogLevel::Info,
2698           ("Selection::Init, selection={ mReply={ mOffset=%u, "
2699            "mString.Length()=%u, mWritingMode=%s } }",
2700            mOffset, mString.Length(), GetWritingModeName(mWritingMode).get()));
2701 
2702   if (!IsValid()) {
2703     MOZ_LOG(gIMMLog, LogLevel::Error,
2704             ("Selection::Init, FAILED, due to invalid range"));
2705     Clear();
2706     return false;
2707   }
2708   return true;
2709 }
2710 
EnsureValidSelection(nsWindow * aWindow)2711 bool IMMHandler::Selection::EnsureValidSelection(nsWindow* aWindow) {
2712   if (IsValid()) {
2713     return true;
2714   }
2715   return Init(aWindow);
2716 }
2717 
2718 }  // namespace widget
2719 }  // namespace mozilla
2720