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