Bug 1861709 replace AudioCallbackDriver::ThreadRunning() assertions that mean to...
[gecko.git] / widget / windows / TSFTextStore.cpp
blob48921c667b45fd66f87b858ba2546f9f1567dd5d
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #define INPUTSCOPE_INIT_GUID
7 #define TEXTATTRS_INIT_GUID
8 #include "TSFTextStore.h"
10 #include <algorithm>
11 #include <comutil.h> // for _bstr_t
12 #include <oleauto.h> // for SysAllocString
13 #include <olectl.h>
14 #include "nscore.h"
16 #include "IMMHandler.h"
17 #include "KeyboardLayout.h"
18 #include "WinIMEHandler.h"
19 #include "WinUtils.h"
20 #include "mozilla/AutoRestore.h"
21 #include "mozilla/Logging.h"
22 #include "mozilla/Preferences.h"
23 #include "mozilla/StaticPrefs_intl.h"
24 #include "mozilla/Telemetry.h"
25 #include "mozilla/TextEventDispatcher.h"
26 #include "mozilla/TextEvents.h"
27 #include "mozilla/ToString.h"
28 #include "mozilla/WindowsVersion.h"
29 #include "nsWindow.h"
30 #include "nsPrintfCString.h"
32 // For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync`
33 // rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
34 // big file.
35 // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
36 mozilla::LazyLogModule gIMELog("IMEHandler");
38 // TODO: GUID_PROP_URL has not been declared in the SDK yet. We should drop the
39 // `s` prefix after it's released by a new SDK and define it with #if.
40 static const GUID sGUID_PROP_URL = {
41 0xd5138268,
42 0xa1bf,
43 0x4308,
44 {0xbc, 0xbf, 0x2e, 0x73, 0x93, 0x98, 0xe2, 0x34}};
46 namespace mozilla {
47 namespace widget {
49 /**
50 * TSF related code should log its behavior even on release build especially
51 * in the interface methods.
53 * In interface methods, use LogLevel::Info.
54 * In internal methods, use LogLevel::Debug for logging normal behavior.
55 * For logging error, use LogLevel::Error.
57 * When an instance method is called, start with following text:
58 * "0x%p TSFFoo::Bar(", the 0x%p should be the "this" of the nsFoo.
59 * after that, start with:
60 * "0x%p TSFFoo::Bar("
61 * In an internal method, start with following text:
62 * "0x%p TSFFoo::Bar("
63 * When a static method is called, start with following text:
64 * "TSFFoo::Bar("
67 enum class TextInputProcessorID {
68 // Internal use only. This won't be returned by TSFStaticSink::ActiveTIP().
69 eNotComputed,
71 // Not a TIP. E.g., simple keyboard layout or IMM-IME.
72 eNone,
74 // Used for other TIPs, i.e., any TIPs which we don't support specifically.
75 eUnknown,
77 // TIP for Japanese.
78 eMicrosoftIMEForJapanese,
79 eMicrosoftOfficeIME2010ForJapanese,
80 eGoogleJapaneseInput,
81 eATOK2011,
82 eATOK2012,
83 eATOK2013,
84 eATOK2014,
85 eATOK2015,
86 eATOK2016,
87 eATOKUnknown,
88 eJapanist10,
90 // TIP for Traditional Chinese.
91 eMicrosoftBopomofo,
92 eMicrosoftChangJie,
93 eMicrosoftPhonetic,
94 eMicrosoftQuick,
95 eMicrosoftNewChangJie,
96 eMicrosoftNewPhonetic,
97 eMicrosoftNewQuick,
98 eFreeChangJie,
100 // TIP for Simplified Chinese.
101 eMicrosoftPinyin,
102 eMicrosoftPinyinNewExperienceInputStyle,
103 eMicrosoftWubi,
105 // TIP for Korean.
106 eMicrosoftIMEForKorean,
107 eMicrosoftOldHangul,
109 // Keyman Desktop, which can install various language keyboards.
110 eKeymanDesktop,
113 static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
115 static void HandleSeparator(nsCString& aDesc) {
116 if (!aDesc.IsEmpty()) {
117 aDesc.AppendLiteral(" | ");
121 static const nsCString GetFindFlagName(DWORD aFindFlag) {
122 nsCString description;
123 if (!aFindFlag) {
124 description.AppendLiteral("no flags (0)");
125 return description;
127 if (aFindFlag & TS_ATTR_FIND_BACKWARDS) {
128 description.AppendLiteral("TS_ATTR_FIND_BACKWARDS");
130 if (aFindFlag & TS_ATTR_FIND_WANT_OFFSET) {
131 HandleSeparator(description);
132 description.AppendLiteral("TS_ATTR_FIND_WANT_OFFSET");
134 if (aFindFlag & TS_ATTR_FIND_UPDATESTART) {
135 HandleSeparator(description);
136 description.AppendLiteral("TS_ATTR_FIND_UPDATESTART");
138 if (aFindFlag & TS_ATTR_FIND_WANT_VALUE) {
139 HandleSeparator(description);
140 description.AppendLiteral("TS_ATTR_FIND_WANT_VALUE");
142 if (aFindFlag & TS_ATTR_FIND_WANT_END) {
143 HandleSeparator(description);
144 description.AppendLiteral("TS_ATTR_FIND_WANT_END");
146 if (aFindFlag & TS_ATTR_FIND_HIDDEN) {
147 HandleSeparator(description);
148 description.AppendLiteral("TS_ATTR_FIND_HIDDEN");
150 if (description.IsEmpty()) {
151 description.AppendLiteral("Unknown (");
152 description.AppendInt(static_cast<uint32_t>(aFindFlag));
153 description.Append(')');
155 return description;
158 class GetACPFromPointFlagName : public nsAutoCString {
159 public:
160 explicit GetACPFromPointFlagName(DWORD aFlags) {
161 if (!aFlags) {
162 AppendLiteral("no flags (0)");
163 return;
165 if (aFlags & GXFPF_ROUND_NEAREST) {
166 AppendLiteral("GXFPF_ROUND_NEAREST");
167 aFlags &= ~GXFPF_ROUND_NEAREST;
169 if (aFlags & GXFPF_NEAREST) {
170 HandleSeparator(*this);
171 AppendLiteral("GXFPF_NEAREST");
172 aFlags &= ~GXFPF_NEAREST;
174 if (aFlags) {
175 HandleSeparator(*this);
176 AppendLiteral("Unknown(");
177 AppendInt(static_cast<uint32_t>(aFlags));
178 Append(')');
181 virtual ~GetACPFromPointFlagName() {}
184 static const char* GetFocusChangeName(
185 InputContextAction::FocusChange aFocusChange) {
186 switch (aFocusChange) {
187 case InputContextAction::FOCUS_NOT_CHANGED:
188 return "FOCUS_NOT_CHANGED";
189 case InputContextAction::GOT_FOCUS:
190 return "GOT_FOCUS";
191 case InputContextAction::LOST_FOCUS:
192 return "LOST_FOCUS";
193 case InputContextAction::MENU_GOT_PSEUDO_FOCUS:
194 return "MENU_GOT_PSEUDO_FOCUS";
195 case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
196 return "MENU_LOST_PSEUDO_FOCUS";
197 case InputContextAction::WIDGET_CREATED:
198 return "WIDGET_CREATED";
199 default:
200 return "Unknown";
204 static nsCString GetCLSIDNameStr(REFCLSID aCLSID) {
205 LPOLESTR str = nullptr;
206 HRESULT hr = ::StringFromCLSID(aCLSID, &str);
207 if (FAILED(hr) || !str || !str[0]) {
208 return ""_ns;
211 nsCString result;
212 result = NS_ConvertUTF16toUTF8(str);
213 ::CoTaskMemFree(str);
214 return result;
217 static nsCString GetGUIDNameStr(REFGUID aGUID) {
218 OLECHAR str[40];
219 int len = ::StringFromGUID2(aGUID, str, ArrayLength(str));
220 if (!len || !str[0]) {
221 return ""_ns;
224 return NS_ConvertUTF16toUTF8(str);
227 static nsCString GetGUIDNameStrWithTable(REFGUID aGUID) {
228 #define RETURN_GUID_NAME(aNamedGUID) \
229 if (IsEqualGUID(aGUID, aNamedGUID)) { \
230 return nsLiteralCString(#aNamedGUID); \
233 RETURN_GUID_NAME(GUID_PROP_INPUTSCOPE)
234 RETURN_GUID_NAME(sGUID_PROP_URL)
235 RETURN_GUID_NAME(TSATTRID_OTHERS)
236 RETURN_GUID_NAME(TSATTRID_Font)
237 RETURN_GUID_NAME(TSATTRID_Font_FaceName)
238 RETURN_GUID_NAME(TSATTRID_Font_SizePts)
239 RETURN_GUID_NAME(TSATTRID_Font_Style)
240 RETURN_GUID_NAME(TSATTRID_Font_Style_Bold)
241 RETURN_GUID_NAME(TSATTRID_Font_Style_Italic)
242 RETURN_GUID_NAME(TSATTRID_Font_Style_SmallCaps)
243 RETURN_GUID_NAME(TSATTRID_Font_Style_Capitalize)
244 RETURN_GUID_NAME(TSATTRID_Font_Style_Uppercase)
245 RETURN_GUID_NAME(TSATTRID_Font_Style_Lowercase)
246 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation)
247 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_LasVegasLights)
248 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_BlinkingBackground)
249 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_SparkleText)
250 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingBlackAnts)
251 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingRedAnts)
252 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_Shimmer)
253 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeDown)
254 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeRight)
255 RETURN_GUID_NAME(TSATTRID_Font_Style_Emboss)
256 RETURN_GUID_NAME(TSATTRID_Font_Style_Engrave)
257 RETURN_GUID_NAME(TSATTRID_Font_Style_Hidden)
258 RETURN_GUID_NAME(TSATTRID_Font_Style_Kerning)
259 RETURN_GUID_NAME(TSATTRID_Font_Style_Outlined)
260 RETURN_GUID_NAME(TSATTRID_Font_Style_Position)
261 RETURN_GUID_NAME(TSATTRID_Font_Style_Protected)
262 RETURN_GUID_NAME(TSATTRID_Font_Style_Shadow)
263 RETURN_GUID_NAME(TSATTRID_Font_Style_Spacing)
264 RETURN_GUID_NAME(TSATTRID_Font_Style_Weight)
265 RETURN_GUID_NAME(TSATTRID_Font_Style_Height)
266 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline)
267 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Single)
268 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Double)
269 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough)
270 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Single)
271 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Double)
272 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline)
273 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Single)
274 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Double)
275 RETURN_GUID_NAME(TSATTRID_Font_Style_Blink)
276 RETURN_GUID_NAME(TSATTRID_Font_Style_Subscript)
277 RETURN_GUID_NAME(TSATTRID_Font_Style_Superscript)
278 RETURN_GUID_NAME(TSATTRID_Font_Style_Color)
279 RETURN_GUID_NAME(TSATTRID_Font_Style_BackgroundColor)
280 RETURN_GUID_NAME(TSATTRID_Text)
281 RETURN_GUID_NAME(TSATTRID_Text_VerticalWriting)
282 RETURN_GUID_NAME(TSATTRID_Text_RightToLeft)
283 RETURN_GUID_NAME(TSATTRID_Text_Orientation)
284 RETURN_GUID_NAME(TSATTRID_Text_Language)
285 RETURN_GUID_NAME(TSATTRID_Text_ReadOnly)
286 RETURN_GUID_NAME(TSATTRID_Text_EmbeddedObject)
287 RETURN_GUID_NAME(TSATTRID_Text_Alignment)
288 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Left)
289 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Right)
290 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Center)
291 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Justify)
292 RETURN_GUID_NAME(TSATTRID_Text_Link)
293 RETURN_GUID_NAME(TSATTRID_Text_Hyphenation)
294 RETURN_GUID_NAME(TSATTRID_Text_Para)
295 RETURN_GUID_NAME(TSATTRID_Text_Para_FirstLineIndent)
296 RETURN_GUID_NAME(TSATTRID_Text_Para_LeftIndent)
297 RETURN_GUID_NAME(TSATTRID_Text_Para_RightIndent)
298 RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceAfter)
299 RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceBefore)
300 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing)
301 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Single)
302 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_OnePtFive)
303 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Double)
304 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_AtLeast)
305 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Exactly)
306 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Multiple)
307 RETURN_GUID_NAME(TSATTRID_List)
308 RETURN_GUID_NAME(TSATTRID_List_LevelIndel)
309 RETURN_GUID_NAME(TSATTRID_List_Type)
310 RETURN_GUID_NAME(TSATTRID_List_Type_Bullet)
311 RETURN_GUID_NAME(TSATTRID_List_Type_Arabic)
312 RETURN_GUID_NAME(TSATTRID_List_Type_LowerLetter)
313 RETURN_GUID_NAME(TSATTRID_List_Type_UpperLetter)
314 RETURN_GUID_NAME(TSATTRID_List_Type_LowerRoman)
315 RETURN_GUID_NAME(TSATTRID_List_Type_UpperRoman)
316 RETURN_GUID_NAME(TSATTRID_App)
317 RETURN_GUID_NAME(TSATTRID_App_IncorrectSpelling)
318 RETURN_GUID_NAME(TSATTRID_App_IncorrectGrammar)
320 #undef RETURN_GUID_NAME
322 return GetGUIDNameStr(aGUID);
325 static nsCString GetRIIDNameStr(REFIID aRIID) {
326 LPOLESTR str = nullptr;
327 HRESULT hr = ::StringFromIID(aRIID, &str);
328 if (FAILED(hr) || !str || !str[0]) {
329 return ""_ns;
332 nsAutoString key(L"Interface\\");
333 key += str;
335 nsCString result;
336 wchar_t buf[256];
337 if (WinUtils::GetRegistryKey(HKEY_CLASSES_ROOT, key.get(), nullptr, buf,
338 sizeof(buf))) {
339 result = NS_ConvertUTF16toUTF8(buf);
340 } else {
341 result = NS_ConvertUTF16toUTF8(str);
344 ::CoTaskMemFree(str);
345 return result;
348 static const char* GetCommonReturnValueName(HRESULT aResult) {
349 switch (aResult) {
350 case S_OK:
351 return "S_OK";
352 case E_ABORT:
353 return "E_ABORT";
354 case E_ACCESSDENIED:
355 return "E_ACCESSDENIED";
356 case E_FAIL:
357 return "E_FAIL";
358 case E_HANDLE:
359 return "E_HANDLE";
360 case E_INVALIDARG:
361 return "E_INVALIDARG";
362 case E_NOINTERFACE:
363 return "E_NOINTERFACE";
364 case E_NOTIMPL:
365 return "E_NOTIMPL";
366 case E_OUTOFMEMORY:
367 return "E_OUTOFMEMORY";
368 case E_POINTER:
369 return "E_POINTER";
370 case E_UNEXPECTED:
371 return "E_UNEXPECTED";
372 default:
373 return SUCCEEDED(aResult) ? "Succeeded" : "Failed";
377 static const char* GetTextStoreReturnValueName(HRESULT aResult) {
378 switch (aResult) {
379 case TS_E_FORMAT:
380 return "TS_E_FORMAT";
381 case TS_E_INVALIDPOINT:
382 return "TS_E_INVALIDPOINT";
383 case TS_E_INVALIDPOS:
384 return "TS_E_INVALIDPOS";
385 case TS_E_NOINTERFACE:
386 return "TS_E_NOINTERFACE";
387 case TS_E_NOLAYOUT:
388 return "TS_E_NOLAYOUT";
389 case TS_E_NOLOCK:
390 return "TS_E_NOLOCK";
391 case TS_E_NOOBJECT:
392 return "TS_E_NOOBJECT";
393 case TS_E_NOSELECTION:
394 return "TS_E_NOSELECTION";
395 case TS_E_NOSERVICE:
396 return "TS_E_NOSERVICE";
397 case TS_E_READONLY:
398 return "TS_E_READONLY";
399 case TS_E_SYNCHRONOUS:
400 return "TS_E_SYNCHRONOUS";
401 case TS_S_ASYNC:
402 return "TS_S_ASYNC";
403 default:
404 return GetCommonReturnValueName(aResult);
408 static const nsCString GetSinkMaskNameStr(DWORD aSinkMask) {
409 nsCString description;
410 if (aSinkMask & TS_AS_TEXT_CHANGE) {
411 description.AppendLiteral("TS_AS_TEXT_CHANGE");
413 if (aSinkMask & TS_AS_SEL_CHANGE) {
414 HandleSeparator(description);
415 description.AppendLiteral("TS_AS_SEL_CHANGE");
417 if (aSinkMask & TS_AS_LAYOUT_CHANGE) {
418 HandleSeparator(description);
419 description.AppendLiteral("TS_AS_LAYOUT_CHANGE");
421 if (aSinkMask & TS_AS_ATTR_CHANGE) {
422 HandleSeparator(description);
423 description.AppendLiteral("TS_AS_ATTR_CHANGE");
425 if (aSinkMask & TS_AS_STATUS_CHANGE) {
426 HandleSeparator(description);
427 description.AppendLiteral("TS_AS_STATUS_CHANGE");
429 if (description.IsEmpty()) {
430 description.AppendLiteral("not-specified");
432 return description;
435 static const nsCString GetLockFlagNameStr(DWORD aLockFlags) {
436 nsCString description;
437 if ((aLockFlags & TS_LF_READWRITE) == TS_LF_READWRITE) {
438 description.AppendLiteral("TS_LF_READWRITE");
439 } else if (aLockFlags & TS_LF_READ) {
440 description.AppendLiteral("TS_LF_READ");
442 if (aLockFlags & TS_LF_SYNC) {
443 if (!description.IsEmpty()) {
444 description.AppendLiteral(" | ");
446 description.AppendLiteral("TS_LF_SYNC");
448 if (description.IsEmpty()) {
449 description.AppendLiteral("not-specified");
451 return description;
454 static const char* GetTextRunTypeName(TsRunType aRunType) {
455 switch (aRunType) {
456 case TS_RT_PLAIN:
457 return "TS_RT_PLAIN";
458 case TS_RT_HIDDEN:
459 return "TS_RT_HIDDEN";
460 case TS_RT_OPAQUE:
461 return "TS_RT_OPAQUE";
462 default:
463 return "Unknown";
467 static nsCString GetColorName(const TF_DA_COLOR& aColor) {
468 switch (aColor.type) {
469 case TF_CT_NONE:
470 return "TF_CT_NONE"_ns;
471 case TF_CT_SYSCOLOR:
472 return nsPrintfCString("TF_CT_SYSCOLOR, nIndex:0x%08X",
473 static_cast<int32_t>(aColor.nIndex));
474 case TF_CT_COLORREF:
475 return nsPrintfCString("TF_CT_COLORREF, cr:0x%08X",
476 static_cast<int32_t>(aColor.cr));
477 break;
478 default:
479 return nsPrintfCString("Unknown(%08X)",
480 static_cast<int32_t>(aColor.type));
484 static nsCString GetLineStyleName(TF_DA_LINESTYLE aLineStyle) {
485 switch (aLineStyle) {
486 case TF_LS_NONE:
487 return "TF_LS_NONE"_ns;
488 case TF_LS_SOLID:
489 return "TF_LS_SOLID"_ns;
490 case TF_LS_DOT:
491 return "TF_LS_DOT"_ns;
492 case TF_LS_DASH:
493 return "TF_LS_DASH"_ns;
494 case TF_LS_SQUIGGLE:
495 return "TF_LS_SQUIGGLE"_ns;
496 default: {
497 return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aLineStyle));
502 static nsCString GetClauseAttrName(TF_DA_ATTR_INFO aAttr) {
503 switch (aAttr) {
504 case TF_ATTR_INPUT:
505 return "TF_ATTR_INPUT"_ns;
506 case TF_ATTR_TARGET_CONVERTED:
507 return "TF_ATTR_TARGET_CONVERTED"_ns;
508 case TF_ATTR_CONVERTED:
509 return "TF_ATTR_CONVERTED"_ns;
510 case TF_ATTR_TARGET_NOTCONVERTED:
511 return "TF_ATTR_TARGET_NOTCONVERTED"_ns;
512 case TF_ATTR_INPUT_ERROR:
513 return "TF_ATTR_INPUT_ERROR"_ns;
514 case TF_ATTR_FIXEDCONVERTED:
515 return "TF_ATTR_FIXEDCONVERTED"_ns;
516 case TF_ATTR_OTHER:
517 return "TF_ATTR_OTHER"_ns;
518 default: {
519 return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aAttr));
524 static nsCString GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE& aDispAttr) {
525 nsCString str;
526 str = "crText:{ ";
527 str += GetColorName(aDispAttr.crText);
528 str += " }, crBk:{ ";
529 str += GetColorName(aDispAttr.crBk);
530 str += " }, lsStyle: ";
531 str += GetLineStyleName(aDispAttr.lsStyle);
532 str += ", fBoldLine: ";
533 str += GetBoolName(aDispAttr.fBoldLine);
534 str += ", crLine:{ ";
535 str += GetColorName(aDispAttr.crLine);
536 str += " }, bAttr: ";
537 str += GetClauseAttrName(aDispAttr.bAttr);
538 return str;
541 static const char* GetMouseButtonName(int16_t aButton) {
542 switch (aButton) {
543 case MouseButton::ePrimary:
544 return "LeftButton";
545 case MouseButton::eMiddle:
546 return "MiddleButton";
547 case MouseButton::eSecondary:
548 return "RightButton";
549 default:
550 return "UnknownButton";
554 #define ADD_SEPARATOR_IF_NECESSARY(aStr) \
555 if (!aStr.IsEmpty()) { \
556 aStr.AppendLiteral(", "); \
559 static nsCString GetMouseButtonsName(int16_t aButtons) {
560 if (!aButtons) {
561 return "no buttons"_ns;
563 nsCString names;
564 if (aButtons & MouseButtonsFlag::ePrimaryFlag) {
565 names = "LeftButton";
567 if (aButtons & MouseButtonsFlag::eSecondaryFlag) {
568 ADD_SEPARATOR_IF_NECESSARY(names);
569 names += "RightButton";
571 if (aButtons & MouseButtonsFlag::eMiddleFlag) {
572 ADD_SEPARATOR_IF_NECESSARY(names);
573 names += "MiddleButton";
575 if (aButtons & MouseButtonsFlag::e4thFlag) {
576 ADD_SEPARATOR_IF_NECESSARY(names);
577 names += "4thButton";
579 if (aButtons & MouseButtonsFlag::e5thFlag) {
580 ADD_SEPARATOR_IF_NECESSARY(names);
581 names += "5thButton";
583 return names;
586 static nsCString GetModifiersName(Modifiers aModifiers) {
587 if (aModifiers == MODIFIER_NONE) {
588 return "no modifiers"_ns;
590 nsCString names;
591 if (aModifiers & MODIFIER_ALT) {
592 names = NS_DOM_KEYNAME_ALT;
594 if (aModifiers & MODIFIER_ALTGRAPH) {
595 ADD_SEPARATOR_IF_NECESSARY(names);
596 names += NS_DOM_KEYNAME_ALTGRAPH;
598 if (aModifiers & MODIFIER_CAPSLOCK) {
599 ADD_SEPARATOR_IF_NECESSARY(names);
600 names += NS_DOM_KEYNAME_CAPSLOCK;
602 if (aModifiers & MODIFIER_CONTROL) {
603 ADD_SEPARATOR_IF_NECESSARY(names);
604 names += NS_DOM_KEYNAME_CONTROL;
606 if (aModifiers & MODIFIER_FN) {
607 ADD_SEPARATOR_IF_NECESSARY(names);
608 names += NS_DOM_KEYNAME_FN;
610 if (aModifiers & MODIFIER_FNLOCK) {
611 ADD_SEPARATOR_IF_NECESSARY(names);
612 names += NS_DOM_KEYNAME_FNLOCK;
614 if (aModifiers & MODIFIER_META) {
615 ADD_SEPARATOR_IF_NECESSARY(names);
616 names += NS_DOM_KEYNAME_META;
618 if (aModifiers & MODIFIER_NUMLOCK) {
619 ADD_SEPARATOR_IF_NECESSARY(names);
620 names += NS_DOM_KEYNAME_NUMLOCK;
622 if (aModifiers & MODIFIER_SCROLLLOCK) {
623 ADD_SEPARATOR_IF_NECESSARY(names);
624 names += NS_DOM_KEYNAME_SCROLLLOCK;
626 if (aModifiers & MODIFIER_SHIFT) {
627 ADD_SEPARATOR_IF_NECESSARY(names);
628 names += NS_DOM_KEYNAME_SHIFT;
630 if (aModifiers & MODIFIER_SYMBOL) {
631 ADD_SEPARATOR_IF_NECESSARY(names);
632 names += NS_DOM_KEYNAME_SYMBOL;
634 if (aModifiers & MODIFIER_SYMBOLLOCK) {
635 ADD_SEPARATOR_IF_NECESSARY(names);
636 names += NS_DOM_KEYNAME_SYMBOLLOCK;
638 return names;
641 class GetWritingModeName : public nsAutoCString {
642 public:
643 explicit GetWritingModeName(const WritingMode& aWritingMode) {
644 if (!aWritingMode.IsVertical()) {
645 AssignLiteral("Horizontal");
646 return;
648 if (aWritingMode.IsVerticalLR()) {
649 AssignLiteral("Vertical (LR)");
650 return;
652 AssignLiteral("Vertical (RL)");
654 virtual ~GetWritingModeName() {}
657 class GetEscapedUTF8String final : public NS_ConvertUTF16toUTF8 {
658 public:
659 explicit GetEscapedUTF8String(const nsAString& aString)
660 : NS_ConvertUTF16toUTF8(aString) {
661 Escape();
663 explicit GetEscapedUTF8String(const char16ptr_t aString)
664 : NS_ConvertUTF16toUTF8(aString) {
665 Escape();
667 GetEscapedUTF8String(const char16ptr_t aString, uint32_t aLength)
668 : NS_ConvertUTF16toUTF8(aString, aLength) {
669 Escape();
672 private:
673 void Escape() {
674 ReplaceSubstring("\r", "\\r");
675 ReplaceSubstring("\n", "\\n");
676 ReplaceSubstring("\t", "\\t");
680 class GetInputScopeString : public nsAutoCString {
681 public:
682 explicit GetInputScopeString(const nsTArray<InputScope>& aList) {
683 for (InputScope inputScope : aList) {
684 if (!IsEmpty()) {
685 AppendLiteral(", ");
687 switch (inputScope) {
688 case IS_DEFAULT:
689 AppendLiteral("IS_DEFAULT");
690 break;
691 case IS_URL:
692 AppendLiteral("IS_URL");
693 break;
694 case IS_FILE_FULLFILEPATH:
695 AppendLiteral("IS_FILE_FULLFILEPATH");
696 break;
697 case IS_FILE_FILENAME:
698 AppendLiteral("IS_FILE_FILENAME");
699 break;
700 case IS_EMAIL_USERNAME:
701 AppendLiteral("IS_EMAIL_USERNAME");
702 break;
703 case IS_EMAIL_SMTPEMAILADDRESS:
704 AppendLiteral("IS_EMAIL_SMTPEMAILADDRESS");
705 break;
706 case IS_LOGINNAME:
707 AppendLiteral("IS_LOGINNAME");
708 break;
709 case IS_PERSONALNAME_FULLNAME:
710 AppendLiteral("IS_PERSONALNAME_FULLNAME");
711 break;
712 case IS_PERSONALNAME_PREFIX:
713 AppendLiteral("IS_PERSONALNAME_PREFIX");
714 break;
715 case IS_PERSONALNAME_GIVENNAME:
716 AppendLiteral("IS_PERSONALNAME_GIVENNAME");
717 break;
718 case IS_PERSONALNAME_MIDDLENAME:
719 AppendLiteral("IS_PERSONALNAME_MIDDLENAME");
720 break;
721 case IS_PERSONALNAME_SURNAME:
722 AppendLiteral("IS_PERSONALNAME_SURNAME");
723 break;
724 case IS_PERSONALNAME_SUFFIX:
725 AppendLiteral("IS_PERSONALNAME_SUFFIX");
726 break;
727 case IS_ADDRESS_FULLPOSTALADDRESS:
728 AppendLiteral("IS_ADDRESS_FULLPOSTALADDRESS");
729 break;
730 case IS_ADDRESS_POSTALCODE:
731 AppendLiteral("IS_ADDRESS_POSTALCODE");
732 break;
733 case IS_ADDRESS_STREET:
734 AppendLiteral("IS_ADDRESS_STREET");
735 break;
736 case IS_ADDRESS_STATEORPROVINCE:
737 AppendLiteral("IS_ADDRESS_STATEORPROVINCE");
738 break;
739 case IS_ADDRESS_CITY:
740 AppendLiteral("IS_ADDRESS_CITY");
741 break;
742 case IS_ADDRESS_COUNTRYNAME:
743 AppendLiteral("IS_ADDRESS_COUNTRYNAME");
744 break;
745 case IS_ADDRESS_COUNTRYSHORTNAME:
746 AppendLiteral("IS_ADDRESS_COUNTRYSHORTNAME");
747 break;
748 case IS_CURRENCY_AMOUNTANDSYMBOL:
749 AppendLiteral("IS_CURRENCY_AMOUNTANDSYMBOL");
750 break;
751 case IS_CURRENCY_AMOUNT:
752 AppendLiteral("IS_CURRENCY_AMOUNT");
753 break;
754 case IS_DATE_FULLDATE:
755 AppendLiteral("IS_DATE_FULLDATE");
756 break;
757 case IS_DATE_MONTH:
758 AppendLiteral("IS_DATE_MONTH");
759 break;
760 case IS_DATE_DAY:
761 AppendLiteral("IS_DATE_DAY");
762 break;
763 case IS_DATE_YEAR:
764 AppendLiteral("IS_DATE_YEAR");
765 break;
766 case IS_DATE_MONTHNAME:
767 AppendLiteral("IS_DATE_MONTHNAME");
768 break;
769 case IS_DATE_DAYNAME:
770 AppendLiteral("IS_DATE_DAYNAME");
771 break;
772 case IS_DIGITS:
773 AppendLiteral("IS_DIGITS");
774 break;
775 case IS_NUMBER:
776 AppendLiteral("IS_NUMBER");
777 break;
778 case IS_ONECHAR:
779 AppendLiteral("IS_ONECHAR");
780 break;
781 case IS_PASSWORD:
782 AppendLiteral("IS_PASSWORD");
783 break;
784 case IS_TELEPHONE_FULLTELEPHONENUMBER:
785 AppendLiteral("IS_TELEPHONE_FULLTELEPHONENUMBER");
786 break;
787 case IS_TELEPHONE_COUNTRYCODE:
788 AppendLiteral("IS_TELEPHONE_COUNTRYCODE");
789 break;
790 case IS_TELEPHONE_AREACODE:
791 AppendLiteral("IS_TELEPHONE_AREACODE");
792 break;
793 case IS_TELEPHONE_LOCALNUMBER:
794 AppendLiteral("IS_TELEPHONE_LOCALNUMBER");
795 break;
796 case IS_TIME_FULLTIME:
797 AppendLiteral("IS_TIME_FULLTIME");
798 break;
799 case IS_TIME_HOUR:
800 AppendLiteral("IS_TIME_HOUR");
801 break;
802 case IS_TIME_MINORSEC:
803 AppendLiteral("IS_TIME_MINORSEC");
804 break;
805 case IS_NUMBER_FULLWIDTH:
806 AppendLiteral("IS_NUMBER_FULLWIDTH");
807 break;
808 case IS_ALPHANUMERIC_HALFWIDTH:
809 AppendLiteral("IS_ALPHANUMERIC_HALFWIDTH");
810 break;
811 case IS_ALPHANUMERIC_FULLWIDTH:
812 AppendLiteral("IS_ALPHANUMERIC_FULLWIDTH");
813 break;
814 case IS_CURRENCY_CHINESE:
815 AppendLiteral("IS_CURRENCY_CHINESE");
816 break;
817 case IS_BOPOMOFO:
818 AppendLiteral("IS_BOPOMOFO");
819 break;
820 case IS_HIRAGANA:
821 AppendLiteral("IS_HIRAGANA");
822 break;
823 case IS_KATAKANA_HALFWIDTH:
824 AppendLiteral("IS_KATAKANA_HALFWIDTH");
825 break;
826 case IS_KATAKANA_FULLWIDTH:
827 AppendLiteral("IS_KATAKANA_FULLWIDTH");
828 break;
829 case IS_HANJA:
830 AppendLiteral("IS_HANJA");
831 break;
832 case IS_PHRASELIST:
833 AppendLiteral("IS_PHRASELIST");
834 break;
835 case IS_REGULAREXPRESSION:
836 AppendLiteral("IS_REGULAREXPRESSION");
837 break;
838 case IS_SRGS:
839 AppendLiteral("IS_SRGS");
840 break;
841 case IS_XML:
842 AppendLiteral("IS_XML");
843 break;
844 case IS_PRIVATE:
845 AppendLiteral("IS_PRIVATE");
846 break;
847 default:
848 AppendPrintf("Unknown Value(%d)", inputScope);
849 break;
855 /******************************************************************/
856 /* InputScopeImpl */
857 /******************************************************************/
859 class InputScopeImpl final : public ITfInputScope {
860 ~InputScopeImpl() {}
862 public:
863 explicit InputScopeImpl(const nsTArray<InputScope>& aList)
864 : mInputScopes(aList.Clone()) {
865 MOZ_LOG(
866 gIMELog, LogLevel::Info,
867 ("0x%p InputScopeImpl(%s)", this, GetInputScopeString(aList).get()));
870 NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(InputScopeImpl)
872 STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
873 *ppv = nullptr;
874 if ((IID_IUnknown == riid) || (IID_ITfInputScope == riid)) {
875 *ppv = static_cast<ITfInputScope*>(this);
877 if (*ppv) {
878 AddRef();
879 return S_OK;
881 return E_NOINTERFACE;
884 STDMETHODIMP GetInputScopes(InputScope** pprgInputScopes, UINT* pcCount) {
885 uint32_t count = (mInputScopes.IsEmpty() ? 1 : mInputScopes.Length());
887 InputScope* pScope =
888 (InputScope*)CoTaskMemAlloc(sizeof(InputScope) * count);
889 NS_ENSURE_TRUE(pScope, E_OUTOFMEMORY);
891 if (mInputScopes.IsEmpty()) {
892 *pScope = IS_DEFAULT;
893 *pcCount = 1;
894 *pprgInputScopes = pScope;
895 return S_OK;
898 *pcCount = 0;
900 for (uint32_t idx = 0; idx < count; idx++) {
901 *(pScope + idx) = mInputScopes[idx];
902 (*pcCount)++;
905 *pprgInputScopes = pScope;
906 return S_OK;
909 STDMETHODIMP GetPhrase(BSTR** ppbstrPhrases, UINT* pcCount) {
910 return E_NOTIMPL;
912 STDMETHODIMP GetRegularExpression(BSTR* pbstrRegExp) { return E_NOTIMPL; }
913 STDMETHODIMP GetSRGS(BSTR* pbstrSRGS) { return E_NOTIMPL; }
914 STDMETHODIMP GetXML(BSTR* pbstrXML) { return E_NOTIMPL; }
916 private:
917 nsTArray<InputScope> mInputScopes;
920 /******************************************************************/
921 /* TSFStaticSink */
922 /******************************************************************/
924 class TSFStaticSink final : public ITfInputProcessorProfileActivationSink {
925 public:
926 static TSFStaticSink* GetInstance() {
927 if (!sInstance) {
928 RefPtr<ITfThreadMgr> threadMgr = TSFTextStore::GetThreadMgr();
929 if (NS_WARN_IF(!threadMgr)) {
930 MOZ_LOG(
931 gIMELog, LogLevel::Error,
932 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
933 "instance due to no ThreadMgr instance"));
934 return nullptr;
936 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
937 TSFTextStore::GetInputProcessorProfiles();
938 if (NS_WARN_IF(!inputProcessorProfiles)) {
939 MOZ_LOG(
940 gIMELog, LogLevel::Error,
941 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
942 "instance due to no InputProcessorProfiles instance"));
943 return nullptr;
945 RefPtr<TSFStaticSink> staticSink = new TSFStaticSink();
946 if (NS_WARN_IF(!staticSink->Init(threadMgr, inputProcessorProfiles))) {
947 staticSink->Destroy();
948 MOZ_LOG(
949 gIMELog, LogLevel::Error,
950 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
951 "instance"));
952 return nullptr;
954 sInstance = staticSink.forget();
956 return sInstance;
959 static void Shutdown() {
960 if (sInstance) {
961 sInstance->Destroy();
962 sInstance = nullptr;
966 bool Init(ITfThreadMgr* aThreadMgr,
967 ITfInputProcessorProfiles* aInputProcessorProfiles);
968 STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
969 *ppv = nullptr;
970 if (IID_IUnknown == riid ||
971 IID_ITfInputProcessorProfileActivationSink == riid) {
972 *ppv = static_cast<ITfInputProcessorProfileActivationSink*>(this);
974 if (*ppv) {
975 AddRef();
976 return S_OK;
978 return E_NOINTERFACE;
981 NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFStaticSink)
983 const nsString& GetActiveTIPKeyboardDescription() const {
984 return mActiveTIPKeyboardDescription;
987 static bool IsIMM_IMEActive() {
988 // Use IMM API until TSFStaticSink starts to work.
989 if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
990 return IsIMM_IME(::GetKeyboardLayout(0));
992 return sInstance->mIsIMM_IME;
995 static bool IsIMM_IME(HKL aHKL) {
996 return (::ImmGetIMEFileNameW(aHKL, nullptr, 0) > 0);
999 static bool IsTraditionalChinese() {
1000 EnsureInstance();
1001 return sInstance && sInstance->IsTraditionalChineseInternal();
1003 static bool IsSimplifiedChinese() {
1004 EnsureInstance();
1005 return sInstance && sInstance->IsSimplifiedChineseInternal();
1007 static bool IsJapanese() {
1008 EnsureInstance();
1009 return sInstance && sInstance->IsJapaneseInternal();
1011 static bool IsKorean() {
1012 EnsureInstance();
1013 return sInstance && sInstance->IsKoreanInternal();
1017 * ActiveTIP() returns an ID for currently active TIP.
1018 * Please note that this method is expensive due to needs a lot of GUID
1019 * comparations if active language ID is one of CJKT. If you need to
1020 * check TIPs for a specific language, you should check current language
1021 * first.
1023 static TextInputProcessorID ActiveTIP() {
1024 EnsureInstance();
1025 if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
1026 return TextInputProcessorID::eUnknown;
1028 sInstance->ComputeActiveTextInputProcessor();
1029 if (NS_WARN_IF(sInstance->mActiveTIP ==
1030 TextInputProcessorID::eNotComputed)) {
1031 return TextInputProcessorID::eUnknown;
1033 return sInstance->mActiveTIP;
1036 static bool IsMSChangJieOrMSQuickActive() {
1037 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1038 // For avoiding unnecessary computation, we should check if the language
1039 // for current TIP is Traditional Chinese.
1040 if (!IsTraditionalChinese()) {
1041 return false;
1043 switch (ActiveTIP()) {
1044 case TextInputProcessorID::eMicrosoftChangJie:
1045 case TextInputProcessorID::eMicrosoftQuick:
1046 return true;
1047 default:
1048 return false;
1052 static bool IsMSPinyinOrMSWubiActive() {
1053 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1054 // For avoiding unnecessary computation, we should check if the language
1055 // for current TIP is Simplified Chinese.
1056 if (!IsSimplifiedChinese()) {
1057 return false;
1059 switch (ActiveTIP()) {
1060 case TextInputProcessorID::eMicrosoftPinyin:
1061 case TextInputProcessorID::eMicrosoftWubi:
1062 return true;
1063 default:
1064 return false;
1068 static bool IsMSJapaneseIMEActive() {
1069 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1070 // For avoiding unnecessary computation, we should check if the language
1071 // for current TIP is Japanese.
1072 if (!IsJapanese()) {
1073 return false;
1075 return ActiveTIP() == TextInputProcessorID::eMicrosoftIMEForJapanese;
1078 static bool IsGoogleJapaneseInputActive() {
1079 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1080 // For avoiding unnecessary computation, we should check if the language
1081 // for current TIP is Japanese.
1082 if (!IsJapanese()) {
1083 return false;
1085 return ActiveTIP() == TextInputProcessorID::eGoogleJapaneseInput;
1088 static bool IsATOKActive() {
1089 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1090 // For avoiding unnecessary computation, we should check if active TIP is
1091 // ATOK first since it's cheaper.
1092 return IsJapanese() && sInstance->IsATOKActiveInternal();
1095 // Note that ATOK 2011 - 2016 refers native caret position for deciding its
1096 // popup window position.
1097 static bool IsATOKReferringNativeCaretActive() {
1098 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1099 // For avoiding unnecessary computation, we should check if active TIP is
1100 // ATOK first since it's cheaper.
1101 if (!IsJapanese() || !sInstance->IsATOKActiveInternal()) {
1102 return false;
1104 switch (ActiveTIP()) {
1105 case TextInputProcessorID::eATOK2011:
1106 case TextInputProcessorID::eATOK2012:
1107 case TextInputProcessorID::eATOK2013:
1108 case TextInputProcessorID::eATOK2014:
1109 case TextInputProcessorID::eATOK2015:
1110 return true;
1111 default:
1112 return false;
1116 private:
1117 static void EnsureInstance() {
1118 if (!sInstance) {
1119 RefPtr<TSFStaticSink> staticSink = GetInstance();
1120 Unused << staticSink;
1124 bool IsTraditionalChineseInternal() const { return mLangID == 0x0404; }
1125 bool IsSimplifiedChineseInternal() const { return mLangID == 0x0804; }
1126 bool IsJapaneseInternal() const { return mLangID == 0x0411; }
1127 bool IsKoreanInternal() const { return mLangID == 0x0412; }
1129 bool IsATOKActiveInternal() {
1130 EnsureInitActiveTIPKeyboard();
1131 // FYI: Name of packaged ATOK includes the release year like "ATOK 2015".
1132 // Name of ATOK Passport (subscription) equals "ATOK".
1133 return StringBeginsWith(mActiveTIPKeyboardDescription, u"ATOK "_ns) ||
1134 mActiveTIPKeyboardDescription.EqualsLiteral("ATOK");
1137 void ComputeActiveTextInputProcessor() {
1138 if (mActiveTIP != TextInputProcessorID::eNotComputed) {
1139 return;
1142 if (mActiveTIPGUID == GUID_NULL) {
1143 mActiveTIP = TextInputProcessorID::eNone;
1144 return;
1147 // Comparing GUID is slow. So, we should use language information to
1148 // reduce the comparing cost for TIP which is not we do not support
1149 // specifically since they are always compared with all supported TIPs.
1150 switch (mLangID) {
1151 case 0x0404:
1152 mActiveTIP = ComputeActiveTIPAsTraditionalChinese();
1153 break;
1154 case 0x0411:
1155 mActiveTIP = ComputeActiveTIPAsJapanese();
1156 break;
1157 case 0x0412:
1158 mActiveTIP = ComputeActiveTIPAsKorean();
1159 break;
1160 case 0x0804:
1161 mActiveTIP = ComputeActiveTIPAsSimplifiedChinese();
1162 break;
1163 default:
1164 mActiveTIP = TextInputProcessorID::eUnknown;
1165 break;
1167 // Special case for Keyman Desktop, it is available for any languages.
1168 // Therefore, we need to check it only if we don't know the active TIP.
1169 if (mActiveTIP != TextInputProcessorID::eUnknown) {
1170 return;
1173 // Note that keyboard layouts for Keyman assign its GUID on install
1174 // randomly, but CLSID is constant in any environments.
1175 // https://bugzilla.mozilla.org/show_bug.cgi?id=1670834#c7
1176 // https://github.com/keymanapp/keyman/blob/318c73a9e1d571d942837ff9964590626e5bd5aa/windows/src/engine/kmtip/globals.cpp#L37
1177 // {FE0420F1-38D1-4B4C-96BF-E7E20A74CFB7}
1178 static constexpr CLSID kKeymanDesktop_CLSID = {
1179 0xFE0420F1,
1180 0x38D1,
1181 0x4B4C,
1182 {0x96, 0xBF, 0xE7, 0xE2, 0x0A, 0x74, 0xCF, 0xB7}};
1183 if (mActiveTIPCLSID == kKeymanDesktop_CLSID) {
1184 mActiveTIP = TextInputProcessorID::eKeymanDesktop;
1188 TextInputProcessorID ComputeActiveTIPAsJapanese() {
1189 // {A76C93D9-5523-4E90-AAFA-4DB112F9AC76} (Win7, Win8.1, Win10)
1190 static constexpr GUID kMicrosoftIMEForJapaneseGUID = {
1191 0xA76C93D9,
1192 0x5523,
1193 0x4E90,
1194 {0xAA, 0xFA, 0x4D, 0xB1, 0x12, 0xF9, 0xAC, 0x76}};
1195 if (mActiveTIPGUID == kMicrosoftIMEForJapaneseGUID) {
1196 return TextInputProcessorID::eMicrosoftIMEForJapanese;
1198 // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64}
1199 static constexpr GUID kMicrosoftOfficeIME2010ForJapaneseGUID = {
1200 0x54EDCC94,
1201 0x1524,
1202 0x4BB1,
1203 {0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64}};
1204 if (mActiveTIPGUID == kMicrosoftOfficeIME2010ForJapaneseGUID) {
1205 return TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese;
1207 // {773EB24E-CA1D-4B1B-B420-FA985BB0B80D}
1208 static constexpr GUID kGoogleJapaneseInputGUID = {
1209 0x773EB24E,
1210 0xCA1D,
1211 0x4B1B,
1212 {0xB4, 0x20, 0xFA, 0x98, 0x5B, 0xB0, 0xB8, 0x0D}};
1213 if (mActiveTIPGUID == kGoogleJapaneseInputGUID) {
1214 return TextInputProcessorID::eGoogleJapaneseInput;
1216 // {F9C24A5C-8A53-499D-9572-93B2FF582115}
1217 static const GUID kATOK2011GUID = {
1218 0xF9C24A5C,
1219 0x8A53,
1220 0x499D,
1221 {0x95, 0x72, 0x93, 0xB2, 0xFF, 0x58, 0x21, 0x15}};
1222 if (mActiveTIPGUID == kATOK2011GUID) {
1223 return TextInputProcessorID::eATOK2011;
1225 // {1DE01562-F445-401B-B6C3-E5B18DB79461}
1226 static constexpr GUID kATOK2012GUID = {
1227 0x1DE01562,
1228 0xF445,
1229 0x401B,
1230 {0xB6, 0xC3, 0xE5, 0xB1, 0x8D, 0xB7, 0x94, 0x61}};
1231 if (mActiveTIPGUID == kATOK2012GUID) {
1232 return TextInputProcessorID::eATOK2012;
1234 // {3C4DB511-189A-4168-B6EA-BFD0B4C85615}
1235 static constexpr GUID kATOK2013GUID = {
1236 0x3C4DB511,
1237 0x189A,
1238 0x4168,
1239 {0xB6, 0xEA, 0xBF, 0xD0, 0xB4, 0xC8, 0x56, 0x15}};
1240 if (mActiveTIPGUID == kATOK2013GUID) {
1241 return TextInputProcessorID::eATOK2013;
1243 // {4EF33B79-6AA9-4271-B4BF-9321C279381B}
1244 static constexpr GUID kATOK2014GUID = {
1245 0x4EF33B79,
1246 0x6AA9,
1247 0x4271,
1248 {0xB4, 0xBF, 0x93, 0x21, 0xC2, 0x79, 0x38, 0x1B}};
1249 if (mActiveTIPGUID == kATOK2014GUID) {
1250 return TextInputProcessorID::eATOK2014;
1252 // {EAB4DC00-CE2E-483D-A86A-E6B99DA9599A}
1253 static constexpr GUID kATOK2015GUID = {
1254 0xEAB4DC00,
1255 0xCE2E,
1256 0x483D,
1257 {0xA8, 0x6A, 0xE6, 0xB9, 0x9D, 0xA9, 0x59, 0x9A}};
1258 if (mActiveTIPGUID == kATOK2015GUID) {
1259 return TextInputProcessorID::eATOK2015;
1261 // {0B557B4C-5740-4110-A60A-1493FA10BF2B}
1262 static constexpr GUID kATOK2016GUID = {
1263 0x0B557B4C,
1264 0x5740,
1265 0x4110,
1266 {0xA6, 0x0A, 0x14, 0x93, 0xFA, 0x10, 0xBF, 0x2B}};
1267 if (mActiveTIPGUID == kATOK2016GUID) {
1268 return TextInputProcessorID::eATOK2016;
1271 // * ATOK 2017
1272 // - {6DBFD8F5-701D-11E6-920F-782BCBA6348F}
1273 // * ATOK Passport (confirmed with version 31.1.2)
1274 // - {A38F2FD9-7199-45E1-841C-BE0313D8052F}
1276 if (IsATOKActiveInternal()) {
1277 return TextInputProcessorID::eATOKUnknown;
1280 // {E6D66705-1EDA-4373-8D01-1D0CB2D054C7}
1281 static constexpr GUID kJapanist10GUID = {
1282 0xE6D66705,
1283 0x1EDA,
1284 0x4373,
1285 {0x8D, 0x01, 0x1D, 0x0C, 0xB2, 0xD0, 0x54, 0xC7}};
1286 if (mActiveTIPGUID == kJapanist10GUID) {
1287 return TextInputProcessorID::eJapanist10;
1290 return TextInputProcessorID::eUnknown;
1293 TextInputProcessorID ComputeActiveTIPAsTraditionalChinese() {
1294 // {B2F9C502-1742-11D4-9790-0080C882687E} (Win8.1, Win10)
1295 static constexpr GUID kMicrosoftBopomofoGUID = {
1296 0xB2F9C502,
1297 0x1742,
1298 0x11D4,
1299 {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1300 if (mActiveTIPGUID == kMicrosoftBopomofoGUID) {
1301 return TextInputProcessorID::eMicrosoftBopomofo;
1303 // {4BDF9F03-C7D3-11D4-B2AB-0080C882687E} (Win7, Win8.1, Win10)
1304 static const GUID kMicrosoftChangJieGUID = {
1305 0x4BDF9F03,
1306 0xC7D3,
1307 0x11D4,
1308 {0xB2, 0xAB, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1309 if (mActiveTIPGUID == kMicrosoftChangJieGUID) {
1310 return TextInputProcessorID::eMicrosoftChangJie;
1312 // {761309DE-317A-11D4-9B5D-0080C882687E} (Win7)
1313 static constexpr GUID kMicrosoftPhoneticGUID = {
1314 0x761309DE,
1315 0x317A,
1316 0x11D4,
1317 {0x9B, 0x5D, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1318 if (mActiveTIPGUID == kMicrosoftPhoneticGUID) {
1319 return TextInputProcessorID::eMicrosoftPhonetic;
1321 // {6024B45F-5C54-11D4-B921-0080C882687E} (Win7, Win8.1, Win10)
1322 static constexpr GUID kMicrosoftQuickGUID = {
1323 0x6024B45F,
1324 0x5C54,
1325 0x11D4,
1326 {0xB9, 0x21, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1327 if (mActiveTIPGUID == kMicrosoftQuickGUID) {
1328 return TextInputProcessorID::eMicrosoftQuick;
1330 // {F3BA907A-6C7E-11D4-97FA-0080C882687E} (Win7)
1331 static constexpr GUID kMicrosoftNewChangJieGUID = {
1332 0xF3BA907A,
1333 0x6C7E,
1334 0x11D4,
1335 {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1336 if (mActiveTIPGUID == kMicrosoftNewChangJieGUID) {
1337 return TextInputProcessorID::eMicrosoftNewChangJie;
1339 // {B2F9C502-1742-11D4-9790-0080C882687E} (Win7)
1340 static constexpr GUID kMicrosoftNewPhoneticGUID = {
1341 0xB2F9C502,
1342 0x1742,
1343 0x11D4,
1344 {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1345 if (mActiveTIPGUID == kMicrosoftNewPhoneticGUID) {
1346 return TextInputProcessorID::eMicrosoftNewPhonetic;
1348 // {0B883BA0-C1C7-11D4-87F9-0080C882687E} (Win7)
1349 static constexpr GUID kMicrosoftNewQuickGUID = {
1350 0x0B883BA0,
1351 0xC1C7,
1352 0x11D4,
1353 {0x87, 0xF9, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1354 if (mActiveTIPGUID == kMicrosoftNewQuickGUID) {
1355 return TextInputProcessorID::eMicrosoftNewQuick;
1358 // NOTE: There are some other Traditional Chinese TIPs installed in Windows:
1359 // * Chinese Traditional Array (version 6.0)
1360 // - {D38EFF65-AA46-4FD5-91A7-67845FB02F5B} (Win7, Win8.1)
1361 // * Chinese Traditional DaYi (version 6.0)
1362 // - {037B2C25-480C-4D7F-B027-D6CA6B69788A} (Win7, Win8.1)
1364 // {B58630B5-0ED3-4335-BBC9-E77BBCB43CAD}
1365 static const GUID kFreeChangJieGUID = {
1366 0xB58630B5,
1367 0x0ED3,
1368 0x4335,
1369 {0xBB, 0xC9, 0xE7, 0x7B, 0xBC, 0xB4, 0x3C, 0xAD}};
1370 if (mActiveTIPGUID == kFreeChangJieGUID) {
1371 return TextInputProcessorID::eFreeChangJie;
1374 return TextInputProcessorID::eUnknown;
1377 TextInputProcessorID ComputeActiveTIPAsSimplifiedChinese() {
1378 // FYI: This matches with neither "Microsoft Pinyin ABC Input Style" nor
1379 // "Microsoft Pinyin New Experience Input Style" on Win7.
1380 // {FA550B04-5AD7-411F-A5AC-CA038EC515D7} (Win8.1, Win10)
1381 static constexpr GUID kMicrosoftPinyinGUID = {
1382 0xFA550B04,
1383 0x5AD7,
1384 0x411F,
1385 {0xA5, 0xAC, 0xCA, 0x03, 0x8E, 0xC5, 0x15, 0xD7}};
1386 if (mActiveTIPGUID == kMicrosoftPinyinGUID) {
1387 return TextInputProcessorID::eMicrosoftPinyin;
1390 // {F3BA9077-6C7E-11D4-97FA-0080C882687E} (Win7)
1391 static constexpr GUID kMicrosoftPinyinNewExperienceInputStyleGUID = {
1392 0xF3BA9077,
1393 0x6C7E,
1394 0x11D4,
1395 {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1396 if (mActiveTIPGUID == kMicrosoftPinyinNewExperienceInputStyleGUID) {
1397 return TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle;
1399 // {82590C13-F4DD-44F4-BA1D-8667246FDF8E} (Win8.1, Win10)
1400 static constexpr GUID kMicrosoftWubiGUID = {
1401 0x82590C13,
1402 0xF4DD,
1403 0x44F4,
1404 {0xBA, 0x1D, 0x86, 0x67, 0x24, 0x6F, 0xDF, 0x8E}};
1405 if (mActiveTIPGUID == kMicrosoftWubiGUID) {
1406 return TextInputProcessorID::eMicrosoftWubi;
1408 // NOTE: There are some other Simplified Chinese TIPs installed in Windows:
1409 // * Chinese Simplified QuanPin (version 6.0)
1410 // - {54FC610E-6ABD-4685-9DDD-A130BDF1B170} (Win8.1)
1411 // * Chinese Simplified ZhengMa (version 6.0)
1412 // - {733B4D81-3BC3-4132-B91A-E9CDD5E2BFC9} (Win8.1)
1413 // * Chinese Simplified ShuangPin (version 6.0)
1414 // - {EF63706D-31C4-490E-9DBB-BD150ADC454B} (Win8.1)
1415 // * Microsoft Pinyin ABC Input Style
1416 // - {FCA121D2-8C6D-41FB-B2DE-A2AD110D4820} (Win7)
1417 return TextInputProcessorID::eUnknown;
1420 TextInputProcessorID ComputeActiveTIPAsKorean() {
1421 // {B5FE1F02-D5F2-4445-9C03-C568F23C99A1} (Win7, Win8.1, Win10)
1422 static constexpr GUID kMicrosoftIMEForKoreanGUID = {
1423 0xB5FE1F02,
1424 0xD5F2,
1425 0x4445,
1426 {0x9C, 0x03, 0xC5, 0x68, 0xF2, 0x3C, 0x99, 0xA1}};
1427 if (mActiveTIPGUID == kMicrosoftIMEForKoreanGUID) {
1428 return TextInputProcessorID::eMicrosoftIMEForKorean;
1430 // {B60AF051-257A-46BC-B9D3-84DAD819BAFB} (Win8.1, Win10)
1431 static constexpr GUID kMicrosoftOldHangulGUID = {
1432 0xB60AF051,
1433 0x257A,
1434 0x46BC,
1435 {0xB9, 0xD3, 0x84, 0xDA, 0xD8, 0x19, 0xBA, 0xFB}};
1436 if (mActiveTIPGUID == kMicrosoftOldHangulGUID) {
1437 return TextInputProcessorID::eMicrosoftOldHangul;
1440 // NOTE: There is the other Korean TIP installed in Windows:
1441 // * Microsoft IME 2010
1442 // - {48878C45-93F9-4aaf-A6A1-272CD863C4F5} (Win7)
1444 return TextInputProcessorID::eUnknown;
1447 public: // ITfInputProcessorProfileActivationSink
1448 STDMETHODIMP OnActivated(DWORD, LANGID, REFCLSID, REFGUID, REFGUID, HKL,
1449 DWORD);
1451 private:
1452 TSFStaticSink();
1453 virtual ~TSFStaticSink() {}
1455 bool EnsureInitActiveTIPKeyboard();
1457 void Destroy();
1459 void GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
1460 REFGUID aProfile, nsAString& aDescription);
1461 bool IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
1462 REFGUID aProfile);
1464 TextInputProcessorID mActiveTIP;
1466 // Cookie of installing ITfInputProcessorProfileActivationSink
1467 DWORD mIPProfileCookie;
1469 LANGID mLangID;
1471 // True if current IME is implemented with IMM.
1472 bool mIsIMM_IME;
1473 // True if OnActivated() is already called
1474 bool mOnActivatedCalled;
1476 RefPtr<ITfThreadMgr> mThreadMgr;
1477 RefPtr<ITfInputProcessorProfiles> mInputProcessorProfiles;
1479 // Active TIP keyboard's description. If active language profile isn't TIP,
1480 // i.e., IMM-IME or just a keyboard layout, this is empty.
1481 nsString mActiveTIPKeyboardDescription;
1483 // Active TIP's GUID and CLSID
1484 GUID mActiveTIPGUID;
1485 CLSID mActiveTIPCLSID;
1487 static StaticRefPtr<TSFStaticSink> sInstance;
1490 StaticRefPtr<TSFStaticSink> TSFStaticSink::sInstance;
1492 TSFStaticSink::TSFStaticSink()
1493 : mActiveTIP(TextInputProcessorID::eNotComputed),
1494 mIPProfileCookie(TF_INVALID_COOKIE),
1495 mLangID(0),
1496 mIsIMM_IME(false),
1497 mOnActivatedCalled(false),
1498 mActiveTIPGUID(GUID_NULL) {}
1500 bool TSFStaticSink::Init(ITfThreadMgr* aThreadMgr,
1501 ITfInputProcessorProfiles* aInputProcessorProfiles) {
1502 MOZ_ASSERT(!mThreadMgr && !mInputProcessorProfiles,
1503 "TSFStaticSink::Init() must be called only once");
1505 mThreadMgr = aThreadMgr;
1506 mInputProcessorProfiles = aInputProcessorProfiles;
1508 RefPtr<ITfSource> source;
1509 HRESULT hr =
1510 mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
1511 if (FAILED(hr)) {
1512 MOZ_LOG(gIMELog, LogLevel::Error,
1513 ("0x%p TSFStaticSink::Init() FAILED to get ITfSource "
1514 "instance (0x%08lX)",
1515 this, hr));
1516 return false;
1519 // NOTE: On Vista or later, Windows let us know activate IME changed only
1520 // with ITfInputProcessorProfileActivationSink.
1521 hr = source->AdviseSink(
1522 IID_ITfInputProcessorProfileActivationSink,
1523 static_cast<ITfInputProcessorProfileActivationSink*>(this),
1524 &mIPProfileCookie);
1525 if (FAILED(hr) || mIPProfileCookie == TF_INVALID_COOKIE) {
1526 MOZ_LOG(gIMELog, LogLevel::Error,
1527 ("0x%p TSFStaticSink::Init() FAILED to install "
1528 "ITfInputProcessorProfileActivationSink (0x%08lX)",
1529 this, hr));
1530 return false;
1533 MOZ_LOG(gIMELog, LogLevel::Info,
1534 ("0x%p TSFStaticSink::Init(), "
1535 "mIPProfileCookie=0x%08lX",
1536 this, mIPProfileCookie));
1537 return true;
1540 void TSFStaticSink::Destroy() {
1541 MOZ_LOG(gIMELog, LogLevel::Info,
1542 ("0x%p TSFStaticSink::Shutdown() "
1543 "mIPProfileCookie=0x%08lX",
1544 this, mIPProfileCookie));
1546 if (mIPProfileCookie != TF_INVALID_COOKIE) {
1547 RefPtr<ITfSource> source;
1548 HRESULT hr =
1549 mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
1550 if (FAILED(hr)) {
1551 MOZ_LOG(gIMELog, LogLevel::Error,
1552 ("0x%p TSFStaticSink::Shutdown() FAILED to get "
1553 "ITfSource instance (0x%08lX)",
1554 this, hr));
1555 } else {
1556 hr = source->UnadviseSink(mIPProfileCookie);
1557 if (FAILED(hr)) {
1558 MOZ_LOG(gIMELog, LogLevel::Error,
1559 ("0x%p TSFTextStore::Shutdown() FAILED to uninstall "
1560 "ITfInputProcessorProfileActivationSink (0x%08lX)",
1561 this, hr));
1566 mThreadMgr = nullptr;
1567 mInputProcessorProfiles = nullptr;
1570 STDMETHODIMP
1571 TSFStaticSink::OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID rclsid,
1572 REFGUID catid, REFGUID guidProfile, HKL hkl,
1573 DWORD dwFlags) {
1574 if ((dwFlags & TF_IPSINK_FLAG_ACTIVE) &&
1575 (dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT ||
1576 catid == GUID_TFCAT_TIP_KEYBOARD)) {
1577 mOnActivatedCalled = true;
1578 mActiveTIP = TextInputProcessorID::eNotComputed;
1579 mActiveTIPGUID = guidProfile;
1580 mActiveTIPCLSID = rclsid;
1581 mLangID = langid & 0xFFFF;
1582 mIsIMM_IME = IsIMM_IME(hkl);
1583 GetTIPDescription(rclsid, langid, guidProfile,
1584 mActiveTIPKeyboardDescription);
1585 if (mActiveTIPGUID != GUID_NULL) {
1586 // key should be "LocaleID|Description". Although GUID of the
1587 // profile is unique key since description may be localized for system
1588 // language, unfortunately, it's too long to record as key with its
1589 // description. Therefore, we should record only the description with
1590 // LocaleID because Microsoft IME may not include language information.
1591 // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
1592 nsAutoString key;
1593 key.AppendPrintf("0x%04X|", mLangID);
1594 nsAutoString description(mActiveTIPKeyboardDescription);
1595 static const uint32_t kMaxDescriptionLength = 72 - key.Length();
1596 if (description.Length() > kMaxDescriptionLength) {
1597 if (NS_IS_LOW_SURROGATE(description[kMaxDescriptionLength - 1]) &&
1598 NS_IS_HIGH_SURROGATE(description[kMaxDescriptionLength - 2])) {
1599 description.Truncate(kMaxDescriptionLength - 2);
1600 } else {
1601 description.Truncate(kMaxDescriptionLength - 1);
1603 // U+2026 is "..."
1604 description.Append(char16_t(0x2026));
1606 key.Append(description);
1607 Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_WINDOWS, key,
1608 true);
1610 // Notify IMEHandler of changing active keyboard layout.
1611 IMEHandler::OnKeyboardLayoutChanged();
1613 MOZ_LOG(gIMELog, LogLevel::Info,
1614 ("0x%p TSFStaticSink::OnActivated(dwProfileType=%s (0x%08lX), "
1615 "langid=0x%08X, rclsid=%s, catid=%s, guidProfile=%s, hkl=0x%p, "
1616 "dwFlags=0x%08lX (TF_IPSINK_FLAG_ACTIVE: %s)), mIsIMM_IME=%s, "
1617 "mActiveTIPDescription=\"%s\"",
1618 this,
1619 dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR
1620 ? "TF_PROFILETYPE_INPUTPROCESSOR"
1621 : dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT
1622 ? "TF_PROFILETYPE_KEYBOARDLAYOUT"
1623 : "Unknown",
1624 dwProfileType, langid, GetCLSIDNameStr(rclsid).get(),
1625 GetGUIDNameStr(catid).get(), GetGUIDNameStr(guidProfile).get(), hkl,
1626 dwFlags, GetBoolName(dwFlags & TF_IPSINK_FLAG_ACTIVE),
1627 GetBoolName(mIsIMM_IME),
1628 NS_ConvertUTF16toUTF8(mActiveTIPKeyboardDescription).get()));
1629 return S_OK;
1632 bool TSFStaticSink::EnsureInitActiveTIPKeyboard() {
1633 if (mOnActivatedCalled) {
1634 return true;
1637 RefPtr<ITfInputProcessorProfileMgr> profileMgr;
1638 HRESULT hr = mInputProcessorProfiles->QueryInterface(
1639 IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
1640 if (FAILED(hr) || !profileMgr) {
1641 MOZ_LOG(gIMELog, LogLevel::Error,
1642 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1643 "to get input processor profile manager, hr=0x%08lX",
1644 this, hr));
1645 return false;
1648 TF_INPUTPROCESSORPROFILE profile;
1649 hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
1650 if (hr == S_FALSE) {
1651 MOZ_LOG(gIMELog, LogLevel::Info,
1652 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1653 "to get active keyboard layout profile due to no active profile, "
1654 "hr=0x%08lX",
1655 this, hr));
1656 // XXX Should we call OnActivated() with arguments like non-TIP in this
1657 // case?
1658 return false;
1660 if (FAILED(hr)) {
1661 MOZ_LOG(gIMELog, LogLevel::Error,
1662 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1663 "to get active TIP keyboard, hr=0x%08lX",
1664 this, hr));
1665 return false;
1668 MOZ_LOG(gIMELog, LogLevel::Info,
1669 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), "
1670 "calling OnActivated() manually...",
1671 this));
1672 OnActivated(profile.dwProfileType, profile.langid, profile.clsid,
1673 profile.catid, profile.guidProfile, ::GetKeyboardLayout(0),
1674 TF_IPSINK_FLAG_ACTIVE);
1675 return true;
1678 void TSFStaticSink::GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
1679 REFGUID aProfile,
1680 nsAString& aDescription) {
1681 aDescription.Truncate();
1683 if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
1684 return;
1687 BSTR description = nullptr;
1688 HRESULT hr = mInputProcessorProfiles->GetLanguageProfileDescription(
1689 aTextService, aLangID, aProfile, &description);
1690 if (FAILED(hr)) {
1691 MOZ_LOG(gIMELog, LogLevel::Error,
1692 ("0x%p TSFStaticSink::InitActiveTIPDescription() FAILED "
1693 "due to GetLanguageProfileDescription() failure, hr=0x%08lX",
1694 this, hr));
1695 return;
1698 if (description && description[0]) {
1699 aDescription.Assign(description);
1701 ::SysFreeString(description);
1704 bool TSFStaticSink::IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
1705 REFGUID aProfile) {
1706 if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
1707 return false;
1710 RefPtr<IEnumTfLanguageProfiles> enumLangProfiles;
1711 HRESULT hr = mInputProcessorProfiles->EnumLanguageProfiles(
1712 aLangID, getter_AddRefs(enumLangProfiles));
1713 if (FAILED(hr) || !enumLangProfiles) {
1714 MOZ_LOG(gIMELog, LogLevel::Error,
1715 ("0x%p TSFStaticSink::IsTIPCategoryKeyboard(), FAILED "
1716 "to get language profiles enumerator, hr=0x%08lX",
1717 this, hr));
1718 return false;
1721 TF_LANGUAGEPROFILE profile;
1722 ULONG fetch = 0;
1723 while (SUCCEEDED(enumLangProfiles->Next(1, &profile, &fetch)) && fetch) {
1724 // XXX We're not sure a profile is registered with two or more categories.
1725 if (profile.clsid == aTextService && profile.guidProfile == aProfile &&
1726 profile.catid == GUID_TFCAT_TIP_KEYBOARD) {
1727 return true;
1730 return false;
1733 /******************************************************************/
1734 /* TSFTextStore */
1735 /******************************************************************/
1737 StaticRefPtr<ITfThreadMgr> TSFTextStore::sThreadMgr;
1738 StaticRefPtr<ITfMessagePump> TSFTextStore::sMessagePump;
1739 StaticRefPtr<ITfKeystrokeMgr> TSFTextStore::sKeystrokeMgr;
1740 StaticRefPtr<ITfDisplayAttributeMgr> TSFTextStore::sDisplayAttrMgr;
1741 StaticRefPtr<ITfCategoryMgr> TSFTextStore::sCategoryMgr;
1742 StaticRefPtr<ITfCompartment> TSFTextStore::sCompartmentForOpenClose;
1743 StaticRefPtr<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr;
1744 StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext;
1745 StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles;
1746 StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore;
1747 const MSG* TSFTextStore::sHandlingKeyMsg = nullptr;
1748 DWORD TSFTextStore::sClientId = 0;
1749 bool TSFTextStore::sIsKeyboardEventDispatched = false;
1751 #define TEXTSTORE_DEFAULT_VIEW (1)
1753 TSFTextStore::TSFTextStore()
1754 : mEditCookie(0),
1755 mSinkMask(0),
1756 mLock(0),
1757 mLockQueued(0),
1758 mHandlingKeyMessage(0) {
1759 // We hope that 5 or more actions don't occur at once.
1760 mPendingActions.SetCapacity(5);
1762 MOZ_LOG(gIMELog, LogLevel::Info,
1763 ("0x%p TSFTextStore::TSFTextStore() SUCCEEDED", this));
1766 TSFTextStore::~TSFTextStore() {
1767 MOZ_LOG(gIMELog, LogLevel::Info,
1768 ("0x%p TSFTextStore instance is destroyed", this));
1771 bool TSFTextStore::Init(nsWindow* aWidget, const InputContext& aContext) {
1772 MOZ_LOG(gIMELog, LogLevel::Info,
1773 ("0x%p TSFTextStore::Init(aWidget=0x%p)", this, aWidget));
1775 if (NS_WARN_IF(!aWidget) || NS_WARN_IF(aWidget->Destroyed())) {
1776 MOZ_LOG(gIMELog, LogLevel::Error,
1777 ("0x%p TSFTextStore::Init() FAILED due to being initialized with "
1778 "destroyed widget",
1779 this));
1780 return false;
1783 if (mDocumentMgr) {
1784 MOZ_LOG(gIMELog, LogLevel::Error,
1785 ("0x%p TSFTextStore::Init() FAILED due to already initialized",
1786 this));
1787 return false;
1790 mWidget = aWidget;
1791 if (NS_WARN_IF(!mWidget)) {
1792 MOZ_LOG(gIMELog, LogLevel::Error,
1793 ("0x%p TSFTextStore::Init() FAILED "
1794 "due to aWidget is nullptr ",
1795 this));
1796 return false;
1798 mDispatcher = mWidget->GetTextEventDispatcher();
1799 if (NS_WARN_IF(!mDispatcher)) {
1800 MOZ_LOG(gIMELog, LogLevel::Error,
1801 ("0x%p TSFTextStore::Init() FAILED "
1802 "due to aWidget->GetTextEventDispatcher() failure",
1803 this));
1804 return false;
1807 mInPrivateBrowsing = aContext.mInPrivateBrowsing;
1808 SetInputScope(aContext.mHTMLInputType, aContext.mHTMLInputMode);
1810 if (aContext.mURI) {
1811 // We don't need the document URL if it fails, let's ignore the error.
1812 nsAutoCString spec;
1813 if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) {
1814 CopyUTF8toUTF16(spec, mDocumentURL);
1818 // Create document manager
1819 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
1820 RefPtr<ITfDocumentMgr> documentMgr;
1821 HRESULT hr = threadMgr->CreateDocumentMgr(getter_AddRefs(documentMgr));
1822 if (NS_WARN_IF(FAILED(hr))) {
1823 MOZ_LOG(gIMELog, LogLevel::Error,
1824 ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr "
1825 "(0x%08lX)",
1826 this, hr));
1827 return false;
1829 if (NS_WARN_IF(mDestroyed)) {
1830 MOZ_LOG(
1831 gIMELog, LogLevel::Error,
1832 ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr due to "
1833 "TextStore being destroyed during calling "
1834 "ITfThreadMgr::CreateDocumentMgr()",
1835 this));
1836 return false;
1838 // Create context and add it to document manager
1839 RefPtr<ITfContext> context;
1840 hr = documentMgr->CreateContext(sClientId, 0,
1841 static_cast<ITextStoreACP*>(this),
1842 getter_AddRefs(context), &mEditCookie);
1843 if (NS_WARN_IF(FAILED(hr))) {
1844 MOZ_LOG(gIMELog, LogLevel::Error,
1845 ("0x%p TSFTextStore::Init() FAILED to create the context "
1846 "(0x%08lX)",
1847 this, hr));
1848 return false;
1850 if (NS_WARN_IF(mDestroyed)) {
1851 MOZ_LOG(gIMELog, LogLevel::Error,
1852 ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
1853 "TextStore being destroyed during calling "
1854 "ITfDocumentMgr::CreateContext()",
1855 this));
1856 return false;
1859 hr = documentMgr->Push(context);
1860 if (NS_WARN_IF(FAILED(hr))) {
1861 MOZ_LOG(gIMELog, LogLevel::Error,
1862 ("0x%p TSFTextStore::Init() FAILED to push the context (0x%08lX)",
1863 this, hr));
1864 return false;
1866 if (NS_WARN_IF(mDestroyed)) {
1867 MOZ_LOG(gIMELog, LogLevel::Error,
1868 ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
1869 "TextStore being destroyed during calling ITfDocumentMgr::Push()",
1870 this));
1871 documentMgr->Pop(TF_POPF_ALL);
1872 return false;
1875 mDocumentMgr = documentMgr;
1876 mContext = context;
1878 MOZ_LOG(gIMELog, LogLevel::Info,
1879 ("0x%p TSFTextStore::Init() succeeded: "
1880 "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08lX",
1881 this, mDocumentMgr.get(), mContext.get(), mEditCookie));
1883 return true;
1886 void TSFTextStore::Destroy() {
1887 if (mBeingDestroyed) {
1888 return;
1891 MOZ_LOG(gIMELog, LogLevel::Info,
1892 ("0x%p TSFTextStore::Destroy(), mLock=%s, "
1893 "mComposition=%s, mHandlingKeyMessage=%u",
1894 this, GetLockFlagNameStr(mLock).get(),
1895 ToString(mComposition).c_str(), mHandlingKeyMessage));
1897 mDestroyed = true;
1899 // Destroy native caret first because it's not directly related to TSF and
1900 // there may be another textstore which gets focus. So, we should avoid
1901 // to destroy caret after the new one recreates caret.
1902 IMEHandler::MaybeDestroyNativeCaret();
1904 if (mLock) {
1905 mPendingDestroy = true;
1906 return;
1909 AutoRestore<bool> savedBeingDestroyed(mBeingDestroyed);
1910 mBeingDestroyed = true;
1912 // If there is composition, TSF keeps the composition even after the text
1913 // store destroyed. So, we should clear the composition here.
1914 if (mComposition.isSome()) {
1915 CommitCompositionInternal(false);
1918 if (mSink) {
1919 MOZ_LOG(gIMELog, LogLevel::Debug,
1920 ("0x%p TSFTextStore::Destroy(), calling "
1921 "ITextStoreACPSink::OnLayoutChange(TS_LC_DESTROY)...",
1922 this));
1923 RefPtr<ITextStoreACPSink> sink = mSink;
1924 sink->OnLayoutChange(TS_LC_DESTROY, TEXTSTORE_DEFAULT_VIEW);
1927 // If this is called during handling a keydown or keyup message, we should
1928 // put off to release TSF objects until it completely finishes since
1929 // MS-IME for Japanese refers some objects without grabbing them.
1930 if (!mHandlingKeyMessage) {
1931 ReleaseTSFObjects();
1934 MOZ_LOG(gIMELog, LogLevel::Info,
1935 ("0x%p TSFTextStore::Destroy() succeeded", this));
1938 void TSFTextStore::ReleaseTSFObjects() {
1939 MOZ_ASSERT(!mHandlingKeyMessage);
1941 MOZ_LOG(gIMELog, LogLevel::Info,
1942 ("0x%p TSFTextStore::ReleaseTSFObjects()", this));
1944 mDocumentURL.Truncate();
1945 mContext = nullptr;
1946 if (mDocumentMgr) {
1947 RefPtr<ITfDocumentMgr> documentMgr = mDocumentMgr.forget();
1948 documentMgr->Pop(TF_POPF_ALL);
1950 mSink = nullptr;
1951 mWidget = nullptr;
1952 mDispatcher = nullptr;
1954 if (!mMouseTrackers.IsEmpty()) {
1955 MOZ_LOG(gIMELog, LogLevel::Debug,
1956 ("0x%p TSFTextStore::ReleaseTSFObjects(), "
1957 "removing a mouse tracker...",
1958 this));
1959 mMouseTrackers.Clear();
1962 MOZ_LOG(gIMELog, LogLevel::Debug,
1963 ("0x%p TSFTextStore::ReleaseTSFObjects() completed", this));
1966 STDMETHODIMP
1967 TSFTextStore::QueryInterface(REFIID riid, void** ppv) {
1968 *ppv = nullptr;
1969 if ((IID_IUnknown == riid) || (IID_ITextStoreACP == riid)) {
1970 *ppv = static_cast<ITextStoreACP*>(this);
1971 } else if (IID_ITfContextOwnerCompositionSink == riid) {
1972 *ppv = static_cast<ITfContextOwnerCompositionSink*>(this);
1973 } else if (IID_ITfMouseTrackerACP == riid) {
1974 *ppv = static_cast<ITfMouseTrackerACP*>(this);
1976 if (*ppv) {
1977 AddRef();
1978 return S_OK;
1981 MOZ_LOG(gIMELog, LogLevel::Error,
1982 ("0x%p TSFTextStore::QueryInterface() FAILED, riid=%s", this,
1983 GetRIIDNameStr(riid).get()));
1984 return E_NOINTERFACE;
1987 STDMETHODIMP
1988 TSFTextStore::AdviseSink(REFIID riid, IUnknown* punk, DWORD dwMask) {
1989 MOZ_LOG(
1990 gIMELog, LogLevel::Info,
1991 ("0x%p TSFTextStore::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), "
1992 "mSink=0x%p, mSinkMask=%s",
1993 this, GetRIIDNameStr(riid).get(), punk, GetSinkMaskNameStr(dwMask).get(),
1994 mSink.get(), GetSinkMaskNameStr(mSinkMask).get()));
1996 if (!punk) {
1997 MOZ_LOG(gIMELog, LogLevel::Error,
1998 ("0x%p TSFTextStore::AdviseSink() FAILED due to the null punk",
1999 this));
2000 return E_UNEXPECTED;
2003 if (IID_ITextStoreACPSink != riid) {
2004 MOZ_LOG(gIMELog, LogLevel::Error,
2005 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2006 "unsupported interface",
2007 this));
2008 return E_INVALIDARG; // means unsupported interface.
2011 if (!mSink) {
2012 // Install sink
2013 punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink));
2014 if (!mSink) {
2015 MOZ_LOG(gIMELog, LogLevel::Error,
2016 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2017 "punk not having the interface",
2018 this));
2019 return E_UNEXPECTED;
2021 } else {
2022 // If sink is already installed we check to see if they are the same
2023 // Get IUnknown from both sides for comparison
2024 RefPtr<IUnknown> comparison1, comparison2;
2025 punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
2026 mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
2027 if (comparison1 != comparison2) {
2028 MOZ_LOG(gIMELog, LogLevel::Error,
2029 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2030 "the sink being different from the stored sink",
2031 this));
2032 return CONNECT_E_ADVISELIMIT;
2035 // Update mask either for a new sink or an existing sink
2036 mSinkMask = dwMask;
2037 return S_OK;
2040 STDMETHODIMP
2041 TSFTextStore::UnadviseSink(IUnknown* punk) {
2042 MOZ_LOG(gIMELog, LogLevel::Info,
2043 ("0x%p TSFTextStore::UnadviseSink(punk=0x%p), mSink=0x%p", this, punk,
2044 mSink.get()));
2046 if (!punk) {
2047 MOZ_LOG(gIMELog, LogLevel::Error,
2048 ("0x%p TSFTextStore::UnadviseSink() FAILED due to the null punk",
2049 this));
2050 return E_INVALIDARG;
2052 if (!mSink) {
2053 MOZ_LOG(gIMELog, LogLevel::Error,
2054 ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
2055 "any sink not stored",
2056 this));
2057 return CONNECT_E_NOCONNECTION;
2059 // Get IUnknown from both sides for comparison
2060 RefPtr<IUnknown> comparison1, comparison2;
2061 punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
2062 mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
2063 // Unadvise only if sinks are the same
2064 if (comparison1 != comparison2) {
2065 MOZ_LOG(gIMELog, LogLevel::Error,
2066 ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
2067 "the sink being different from the stored sink",
2068 this));
2069 return CONNECT_E_NOCONNECTION;
2071 mSink = nullptr;
2072 mSinkMask = 0;
2073 return S_OK;
2076 STDMETHODIMP
2077 TSFTextStore::RequestLock(DWORD dwLockFlags, HRESULT* phrSession) {
2078 MOZ_LOG(gIMELog, LogLevel::Info,
2079 ("0x%p TSFTextStore::RequestLock(dwLockFlags=%s, phrSession=0x%p), "
2080 "mLock=%s, mDestroyed=%s",
2081 this, GetLockFlagNameStr(dwLockFlags).get(), phrSession,
2082 GetLockFlagNameStr(mLock).get(), GetBoolName(mDestroyed)));
2084 if (!mSink) {
2085 MOZ_LOG(gIMELog, LogLevel::Error,
2086 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2087 "any sink not stored",
2088 this));
2089 return E_FAIL;
2091 if (mDestroyed &&
2092 (mContentForTSF.isNothing() || mSelectionForTSF.isNothing())) {
2093 MOZ_LOG(gIMELog, LogLevel::Error,
2094 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2095 "being destroyed and no information of the contents",
2096 this));
2097 return E_FAIL;
2099 if (!phrSession) {
2100 MOZ_LOG(gIMELog, LogLevel::Error,
2101 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2102 "null phrSession",
2103 this));
2104 return E_INVALIDARG;
2107 if (!mLock) {
2108 // put on lock
2109 mLock = dwLockFlags & (~TS_LF_SYNC);
2110 MOZ_LOG(
2111 gIMELog, LogLevel::Info,
2112 ("0x%p Locking (%s) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
2113 ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
2114 this, GetLockFlagNameStr(mLock).get()));
2115 // Don't release this instance during this lock because this is called by
2116 // TSF but they don't grab us during this call.
2117 RefPtr<TSFTextStore> kungFuDeathGrip(this);
2118 RefPtr<ITextStoreACPSink> sink = mSink;
2119 *phrSession = sink->OnLockGranted(mLock);
2120 MOZ_LOG(
2121 gIMELog, LogLevel::Info,
2122 ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2123 "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
2124 this, GetLockFlagNameStr(mLock).get()));
2125 DidLockGranted();
2126 while (mLockQueued) {
2127 mLock = mLockQueued;
2128 mLockQueued = 0;
2129 MOZ_LOG(gIMELog, LogLevel::Info,
2130 ("0x%p Locking for the request in the queue (%s) >>>>>>>>>>>>>>"
2131 ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
2132 ">>>>>",
2133 this, GetLockFlagNameStr(mLock).get()));
2134 sink->OnLockGranted(mLock);
2135 MOZ_LOG(gIMELog, LogLevel::Info,
2136 ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2137 "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2138 "<<<<<",
2139 this, GetLockFlagNameStr(mLock).get()));
2140 DidLockGranted();
2143 // The document is now completely unlocked.
2144 mLock = 0;
2146 MaybeFlushPendingNotifications();
2148 MOZ_LOG(gIMELog, LogLevel::Info,
2149 ("0x%p TSFTextStore::RequestLock() succeeded: *phrSession=%s",
2150 this, GetTextStoreReturnValueName(*phrSession)));
2151 return S_OK;
2154 // only time when reentrant lock is allowed is when caller holds a
2155 // read-only lock and is requesting an async write lock
2156 if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags) &&
2157 !(dwLockFlags & TS_LF_SYNC)) {
2158 *phrSession = TS_S_ASYNC;
2159 mLockQueued = dwLockFlags & (~TS_LF_SYNC);
2161 MOZ_LOG(gIMELog, LogLevel::Info,
2162 ("0x%p TSFTextStore::RequestLock() stores the request in the "
2163 "queue, *phrSession=TS_S_ASYNC",
2164 this));
2165 return S_OK;
2168 // no more locks allowed
2169 MOZ_LOG(gIMELog, LogLevel::Info,
2170 ("0x%p TSFTextStore::RequestLock() didn't allow to lock, "
2171 "*phrSession=TS_E_SYNCHRONOUS",
2172 this));
2173 *phrSession = TS_E_SYNCHRONOUS;
2174 return E_FAIL;
2177 void TSFTextStore::DidLockGranted() {
2178 if (IsReadWriteLocked()) {
2179 // FreeCJ (TIP for Traditional Chinese) calls SetSelection() to set caret
2180 // to the start of composition string and insert a full width space for
2181 // a placeholder with a call of SetText(). After that, it calls
2182 // OnUpdateComposition() without new range. Therefore, let's record the
2183 // composition update information here.
2184 CompleteLastActionIfStillIncomplete();
2186 FlushPendingActions();
2189 // If the widget has gone, we don't need to notify anything.
2190 if (mDestroyed || !mWidget || mWidget->Destroyed()) {
2191 mPendingSelectionChangeData.reset();
2192 mHasReturnedNoLayoutError = false;
2196 void TSFTextStore::DispatchEvent(WidgetGUIEvent& aEvent) {
2197 if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) {
2198 return;
2200 // If the event isn't a query content event, the event may be handled
2201 // asynchronously. So, we should put off to answer from GetTextExt() etc.
2202 if (!aEvent.AsQueryContentEvent()) {
2203 mDeferNotifyingTSFUntilNextUpdate = true;
2205 mWidget->DispatchWindowEvent(aEvent);
2208 void TSFTextStore::FlushPendingActions() {
2209 if (!mWidget || mWidget->Destroyed()) {
2210 // Note that don't clear mContentForTSF because TIP may try to commit
2211 // composition with a document lock. In such case, TSFTextStore needs to
2212 // behave as expected by TIP.
2213 mPendingActions.Clear();
2214 mPendingSelectionChangeData.reset();
2215 mHasReturnedNoLayoutError = false;
2216 return;
2219 // Some TIP may request lock but does nothing during the lock. In such case,
2220 // this should do nothing. For example, when MS-IME for Japanese is active
2221 // and we're inactivating, this case occurs and causes different behavior
2222 // from the other TIPs.
2223 if (mPendingActions.IsEmpty()) {
2224 return;
2227 RefPtr<nsWindow> widget(mWidget);
2228 nsresult rv = mDispatcher->BeginNativeInputTransaction();
2229 if (NS_WARN_IF(NS_FAILED(rv))) {
2230 MOZ_LOG(gIMELog, LogLevel::Error,
2231 ("0x%p TSFTextStore::FlushPendingActions() "
2232 "FAILED due to BeginNativeInputTransaction() failure",
2233 this));
2234 return;
2236 for (uint32_t i = 0; i < mPendingActions.Length(); i++) {
2237 PendingAction& action = mPendingActions[i];
2238 switch (action.mType) {
2239 case PendingAction::Type::eKeyboardEvent:
2240 if (mDestroyed) {
2241 MOZ_LOG(
2242 gIMELog, LogLevel::Warning,
2243 ("0x%p TSFTextStore::FlushPendingActions() "
2244 "IGNORED pending KeyboardEvent(%s) due to already destroyed",
2245 this,
2246 action.mKeyMsg.message == WM_KEYDOWN ? "eKeyDown" : "eKeyUp"));
2248 MOZ_DIAGNOSTIC_ASSERT(action.mKeyMsg.message == WM_KEYDOWN ||
2249 action.mKeyMsg.message == WM_KEYUP);
2250 DispatchKeyboardEventAsProcessedByIME(action.mKeyMsg);
2251 if (!widget || widget->Destroyed()) {
2252 break;
2254 break;
2255 case PendingAction::Type::eCompositionStart: {
2256 MOZ_LOG(gIMELog, LogLevel::Debug,
2257 ("0x%p TSFTextStore::FlushPendingActions() "
2258 "flushing Type::eCompositionStart={ mSelectionStart=%ld, "
2259 "mSelectionLength=%ld }, mDestroyed=%s",
2260 this, action.mSelectionStart, action.mSelectionLength,
2261 GetBoolName(mDestroyed)));
2263 if (mDestroyed) {
2264 MOZ_LOG(gIMELog, LogLevel::Warning,
2265 ("0x%p TSFTextStore::FlushPendingActions() "
2266 "IGNORED pending compositionstart due to already destroyed",
2267 this));
2268 break;
2271 if (action.mAdjustSelection) {
2272 // Select composition range so the new composition replaces the range
2273 WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
2274 widget->InitEvent(selectionSet);
2275 selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
2276 selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
2277 selectionSet.mReversed = false;
2278 selectionSet.mExpandToClusterBoundary =
2279 TSFStaticSink::ActiveTIP() !=
2280 TextInputProcessorID::eKeymanDesktop &&
2281 StaticPrefs::
2282 intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
2283 DispatchEvent(selectionSet);
2284 if (!selectionSet.mSucceeded) {
2285 MOZ_LOG(gIMELog, LogLevel::Error,
2286 ("0x%p TSFTextStore::FlushPendingActions() "
2287 "FAILED due to eSetSelection failure",
2288 this));
2289 break;
2293 // eCompositionStart always causes
2294 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. Therefore, we should
2295 // wait to clear mContentForTSF until it's notified.
2296 mDeferClearingContentForTSF = true;
2298 MOZ_LOG(gIMELog, LogLevel::Debug,
2299 ("0x%p TSFTextStore::FlushPendingActions() "
2300 "dispatching compositionstart event...",
2301 this));
2302 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2303 nsEventStatus status;
2304 rv = mDispatcher->StartComposition(status, &eventTime);
2305 if (NS_WARN_IF(NS_FAILED(rv))) {
2306 MOZ_LOG(gIMELog, LogLevel::Error,
2307 ("0x%p TSFTextStore::FlushPendingActions() "
2308 "FAILED to dispatch compositionstart event, "
2309 "IsHandlingCompositionInContent()=%s",
2310 this, GetBoolName(IsHandlingCompositionInContent())));
2311 // XXX Is this right? If there is a composition in content,
2312 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2313 mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
2315 if (!widget || widget->Destroyed()) {
2316 break;
2318 break;
2320 case PendingAction::Type::eCompositionUpdate: {
2321 MOZ_LOG(gIMELog, LogLevel::Debug,
2322 ("0x%p TSFTextStore::FlushPendingActions() "
2323 "flushing Type::eCompositionUpdate={ mData=\"%s\", "
2324 "mRanges=0x%p, mRanges->Length()=%zu }",
2325 this, GetEscapedUTF8String(action.mData).get(),
2326 action.mRanges.get(),
2327 action.mRanges ? action.mRanges->Length() : 0));
2329 // eCompositionChange causes a DOM text event, the IME will be notified
2330 // of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. In this case, we
2331 // should not clear mContentForTSF until we notify the IME of the
2332 // composition update.
2333 mDeferClearingContentForTSF = true;
2335 rv = mDispatcher->SetPendingComposition(action.mData, action.mRanges);
2336 if (NS_WARN_IF(NS_FAILED(rv))) {
2337 MOZ_LOG(gIMELog, LogLevel::Error,
2338 ("0x%p TSFTextStore::FlushPendingActions() "
2339 "FAILED to setting pending composition... "
2340 "IsHandlingCompositionInContent()=%s",
2341 this, GetBoolName(IsHandlingCompositionInContent())));
2342 // XXX Is this right? If there is a composition in content,
2343 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2344 mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
2345 } else {
2346 MOZ_LOG(gIMELog, LogLevel::Debug,
2347 ("0x%p TSFTextStore::FlushPendingActions() "
2348 "dispatching compositionchange event...",
2349 this));
2350 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2351 nsEventStatus status;
2352 rv = mDispatcher->FlushPendingComposition(status, &eventTime);
2353 if (NS_WARN_IF(NS_FAILED(rv))) {
2354 MOZ_LOG(gIMELog, LogLevel::Error,
2355 ("0x%p TSFTextStore::FlushPendingActions() "
2356 "FAILED to dispatch compositionchange event, "
2357 "IsHandlingCompositionInContent()=%s",
2358 this, GetBoolName(IsHandlingCompositionInContent())));
2359 // XXX Is this right? If there is a composition in content,
2360 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2361 mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
2363 // Be aware, the mWidget might already have been destroyed.
2365 break;
2367 case PendingAction::Type::eCompositionEnd: {
2368 MOZ_LOG(gIMELog, LogLevel::Debug,
2369 ("0x%p TSFTextStore::FlushPendingActions() "
2370 "flushing Type::eCompositionEnd={ mData=\"%s\" }",
2371 this, GetEscapedUTF8String(action.mData).get()));
2373 // Dispatching eCompositionCommit causes a DOM text event, then,
2374 // the IME will be notified of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
2375 // when focused content actually handles the event. For example,
2376 // when focused content is in a remote process, it's sent when
2377 // all dispatched composition events have been handled in the remote
2378 // process. So, until then, we don't have newer content information.
2379 // Therefore, we need to put off to clear mContentForTSF.
2380 mDeferClearingContentForTSF = true;
2382 MOZ_LOG(gIMELog, LogLevel::Debug,
2383 ("0x%p TSFTextStore::FlushPendingActions(), "
2384 "dispatching compositioncommit event...",
2385 this));
2386 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2387 nsEventStatus status;
2388 rv = mDispatcher->CommitComposition(status, &action.mData, &eventTime);
2389 if (NS_WARN_IF(NS_FAILED(rv))) {
2390 MOZ_LOG(gIMELog, LogLevel::Error,
2391 ("0x%p TSFTextStore::FlushPendingActions() "
2392 "FAILED to dispatch compositioncommit event, "
2393 "IsHandlingCompositionInContent()=%s",
2394 this, GetBoolName(IsHandlingCompositionInContent())));
2395 // XXX Is this right? If there is a composition in content,
2396 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2397 mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
2399 break;
2401 case PendingAction::Type::eSetSelection: {
2402 MOZ_LOG(
2403 gIMELog, LogLevel::Debug,
2404 ("0x%p TSFTextStore::FlushPendingActions() "
2405 "flushing Type::eSetSelection={ mSelectionStart=%ld, "
2406 "mSelectionLength=%ld, mSelectionReversed=%s }, "
2407 "mDestroyed=%s",
2408 this, action.mSelectionStart, action.mSelectionLength,
2409 GetBoolName(action.mSelectionReversed), GetBoolName(mDestroyed)));
2411 if (mDestroyed) {
2412 MOZ_LOG(gIMELog, LogLevel::Warning,
2413 ("0x%p TSFTextStore::FlushPendingActions() "
2414 "IGNORED pending selectionset due to already destroyed",
2415 this));
2416 break;
2419 WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
2420 selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
2421 selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
2422 selectionSet.mReversed = action.mSelectionReversed;
2423 selectionSet.mExpandToClusterBoundary =
2424 TSFStaticSink::ActiveTIP() !=
2425 TextInputProcessorID::eKeymanDesktop &&
2426 StaticPrefs::
2427 intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
2428 DispatchEvent(selectionSet);
2429 if (!selectionSet.mSucceeded) {
2430 MOZ_LOG(gIMELog, LogLevel::Error,
2431 ("0x%p TSFTextStore::FlushPendingActions() "
2432 "FAILED due to eSetSelection failure",
2433 this));
2434 break;
2436 break;
2438 default:
2439 MOZ_CRASH("unexpected action type");
2442 if (widget && !widget->Destroyed()) {
2443 continue;
2446 MOZ_LOG(gIMELog, LogLevel::Info,
2447 ("0x%p TSFTextStore::FlushPendingActions(), "
2448 "qutting since the mWidget has gone",
2449 this));
2450 break;
2452 mPendingActions.Clear();
2455 void TSFTextStore::MaybeFlushPendingNotifications() {
2456 if (mDeferNotifyingTSF) {
2457 MOZ_LOG(gIMELog, LogLevel::Debug,
2458 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2459 "putting off flushing pending notifications due to initializing "
2460 "something...",
2461 this));
2462 return;
2465 if (IsReadLocked()) {
2466 MOZ_LOG(gIMELog, LogLevel::Debug,
2467 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2468 "putting off flushing pending notifications due to being the "
2469 "document locked...",
2470 this));
2471 return;
2474 if (mDeferCommittingComposition) {
2475 MOZ_LOG(gIMELog, LogLevel::Info,
2476 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2477 "calling TSFTextStore::CommitCompositionInternal(false)...",
2478 this));
2479 mDeferCommittingComposition = mDeferCancellingComposition = false;
2480 CommitCompositionInternal(false);
2481 } else if (mDeferCancellingComposition) {
2482 MOZ_LOG(gIMELog, LogLevel::Info,
2483 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2484 "calling TSFTextStore::CommitCompositionInternal(true)...",
2485 this));
2486 mDeferCommittingComposition = mDeferCancellingComposition = false;
2487 CommitCompositionInternal(true);
2490 if (mDeferNotifyingTSFUntilNextUpdate) {
2491 MOZ_LOG(gIMELog, LogLevel::Debug,
2492 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2493 "putting off flushing pending notifications due to being "
2494 "dispatching events...",
2495 this));
2496 return;
2499 if (mPendingDestroy) {
2500 Destroy();
2501 return;
2504 if (mDestroyed) {
2505 // If it's already been destroyed completely, this shouldn't notify TSF of
2506 // anything anymore.
2507 MOZ_LOG(gIMELog, LogLevel::Debug,
2508 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2509 "does nothing because this has already destroyed completely...",
2510 this));
2511 return;
2514 if (!mDeferClearingContentForTSF && mContentForTSF.isSome()) {
2515 mContentForTSF.reset();
2516 MOZ_LOG(gIMELog, LogLevel::Debug,
2517 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2518 "mContentForTSF is set to `Nothing`",
2519 this));
2522 // When there is no cached content, we can sync actual contents and TSF/TIP
2523 // expecting contents.
2524 RefPtr<TSFTextStore> kungFuDeathGrip = this;
2525 Unused << kungFuDeathGrip;
2526 if (mContentForTSF.isNothing()) {
2527 if (mPendingTextChangeData.IsValid()) {
2528 MOZ_LOG(gIMELog, LogLevel::Info,
2529 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2530 "calling TSFTextStore::NotifyTSFOfTextChange()...",
2531 this));
2532 NotifyTSFOfTextChange();
2534 if (mPendingSelectionChangeData.isSome()) {
2535 MOZ_LOG(gIMELog, LogLevel::Info,
2536 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2537 "calling TSFTextStore::NotifyTSFOfSelectionChange()...",
2538 this));
2539 NotifyTSFOfSelectionChange();
2543 if (mHasReturnedNoLayoutError) {
2544 MOZ_LOG(gIMELog, LogLevel::Info,
2545 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2546 "calling TSFTextStore::NotifyTSFOfLayoutChange()...",
2547 this));
2548 NotifyTSFOfLayoutChange();
2552 void TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME() {
2553 // If we've already been destroyed, we cannot do anything.
2554 if (mDestroyed) {
2555 MOZ_LOG(
2556 gIMELog, LogLevel::Debug,
2557 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2558 "does nothing because it's already been destroyed",
2559 this));
2560 return;
2563 // If we're not handling key message or we've already dispatched a keyboard
2564 // event for the handling key message, we should do nothing anymore.
2565 if (!sHandlingKeyMsg || sIsKeyboardEventDispatched) {
2566 MOZ_LOG(
2567 gIMELog, LogLevel::Debug,
2568 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2569 "does nothing because not necessary to dispatch keyboard event",
2570 this));
2571 return;
2574 sIsKeyboardEventDispatched = true;
2575 // If the document is locked, just adding the task to dispatching an event
2576 // to the queue.
2577 if (IsReadLocked()) {
2578 MOZ_LOG(
2579 gIMELog, LogLevel::Debug,
2580 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2581 "adding to dispatch a keyboard event into the queue...",
2582 this));
2583 PendingAction* action = mPendingActions.AppendElement();
2584 action->mType = PendingAction::Type::eKeyboardEvent;
2585 memcpy(&action->mKeyMsg, sHandlingKeyMsg, sizeof(MSG));
2586 return;
2589 // Otherwise, dispatch a keyboard event.
2590 MOZ_LOG(gIMELog, LogLevel::Debug,
2591 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2592 "trying to dispatch a keyboard event...",
2593 this));
2594 DispatchKeyboardEventAsProcessedByIME(*sHandlingKeyMsg);
2597 void TSFTextStore::DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg) {
2598 MOZ_ASSERT(mWidget);
2599 MOZ_ASSERT(!mWidget->Destroyed());
2600 MOZ_ASSERT(!mDestroyed);
2602 ModifierKeyState modKeyState;
2603 MSG msg(aMsg);
2604 msg.wParam = VK_PROCESSKEY;
2605 NativeKey nativeKey(mWidget, msg, modKeyState);
2606 switch (aMsg.message) {
2607 case WM_KEYDOWN:
2608 MOZ_LOG(gIMELog, LogLevel::Debug,
2609 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2610 "dispatching an eKeyDown event...",
2611 this));
2612 nativeKey.HandleKeyDownMessage();
2613 break;
2614 case WM_KEYUP:
2615 MOZ_LOG(gIMELog, LogLevel::Debug,
2616 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2617 "dispatching an eKeyUp event...",
2618 this));
2619 nativeKey.HandleKeyUpMessage();
2620 break;
2621 default:
2622 MOZ_LOG(gIMELog, LogLevel::Error,
2623 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2624 "ERROR, it doesn't handle the message",
2625 this));
2626 break;
2630 STDMETHODIMP
2631 TSFTextStore::GetStatus(TS_STATUS* pdcs) {
2632 MOZ_LOG(gIMELog, LogLevel::Info,
2633 ("0x%p TSFTextStore::GetStatus(pdcs=0x%p)", this, pdcs));
2635 if (!pdcs) {
2636 MOZ_LOG(gIMELog, LogLevel::Error,
2637 ("0x%p TSFTextStore::GetStatus() FAILED due to null pdcs", this));
2638 return E_INVALIDARG;
2640 // We manage on-screen keyboard by own.
2641 pdcs->dwDynamicFlags = TS_SD_INPUTPANEMANUALDISPLAYENABLE;
2642 // we use a "flat" text model for TSF support so no hidden text
2643 pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT;
2644 return S_OK;
2647 STDMETHODIMP
2648 TSFTextStore::QueryInsert(LONG acpTestStart, LONG acpTestEnd, ULONG cch,
2649 LONG* pacpResultStart, LONG* pacpResultEnd) {
2650 MOZ_LOG(
2651 gIMELog, LogLevel::Info,
2652 ("0x%p TSFTextStore::QueryInsert(acpTestStart=%ld, "
2653 "acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)",
2654 this, acpTestStart, acpTestEnd, cch, pacpResultStart, pacpResultEnd));
2656 if (!pacpResultStart || !pacpResultEnd) {
2657 MOZ_LOG(gIMELog, LogLevel::Error,
2658 ("0x%p TSFTextStore::QueryInsert() FAILED due to "
2659 "the null argument",
2660 this));
2661 return E_INVALIDARG;
2664 if (acpTestStart < 0 || acpTestStart > acpTestEnd) {
2665 MOZ_LOG(gIMELog, LogLevel::Error,
2666 ("0x%p TSFTextStore::QueryInsert() FAILED due to "
2667 "wrong argument",
2668 this));
2669 return E_INVALIDARG;
2672 // XXX need to adjust to cluster boundary
2673 // Assume we are given good offsets for now
2674 if (mComposition.isNothing() &&
2675 ((StaticPrefs::
2676 intl_tsf_hack_ms_traditional_chinese_query_insert_result() &&
2677 TSFStaticSink::IsMSChangJieOrMSQuickActive()) ||
2678 (StaticPrefs::
2679 intl_tsf_hack_ms_simplified_chinese_query_insert_result() &&
2680 TSFStaticSink::IsMSPinyinOrMSWubiActive()))) {
2681 MOZ_LOG(gIMELog, LogLevel::Warning,
2682 ("0x%p TSFTextStore::QueryInsert() WARNING using different "
2683 "result for the TIP",
2684 this));
2685 // Chinese TIPs of Microsoft assume that QueryInsert() returns selected
2686 // range which should be removed.
2687 *pacpResultStart = acpTestStart;
2688 *pacpResultEnd = acpTestEnd;
2689 } else {
2690 *pacpResultStart = acpTestStart;
2691 *pacpResultEnd = acpTestStart + cch;
2694 MOZ_LOG(gIMELog, LogLevel::Info,
2695 ("0x%p TSFTextStore::QueryInsert() succeeded: "
2696 "*pacpResultStart=%ld, *pacpResultEnd=%ld)",
2697 this, *pacpResultStart, *pacpResultEnd));
2698 return S_OK;
2701 STDMETHODIMP
2702 TSFTextStore::GetSelection(ULONG ulIndex, ULONG ulCount,
2703 TS_SELECTION_ACP* pSelection, ULONG* pcFetched) {
2704 MOZ_LOG(gIMELog, LogLevel::Info,
2705 ("0x%p TSFTextStore::GetSelection(ulIndex=%lu, ulCount=%lu, "
2706 "pSelection=0x%p, pcFetched=0x%p)",
2707 this, ulIndex, ulCount, pSelection, pcFetched));
2709 if (!IsReadLocked()) {
2710 MOZ_LOG(
2711 gIMELog, LogLevel::Error,
2712 ("0x%p TSFTextStore::GetSelection() FAILED due to not locked", this));
2713 return TS_E_NOLOCK;
2715 if (!ulCount || !pSelection || !pcFetched) {
2716 MOZ_LOG(gIMELog, LogLevel::Error,
2717 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2718 "null argument",
2719 this));
2720 return E_INVALIDARG;
2723 *pcFetched = 0;
2725 if (ulIndex != static_cast<ULONG>(TS_DEFAULT_SELECTION) && ulIndex != 0) {
2726 MOZ_LOG(gIMELog, LogLevel::Error,
2727 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2728 "unsupported selection",
2729 this));
2730 return TS_E_NOSELECTION;
2733 Maybe<Selection>& selectionForTSF = SelectionForTSF();
2734 if (selectionForTSF.isNothing()) {
2735 if (DoNotReturnErrorFromGetSelection()) {
2736 *pSelection = Selection::EmptyACP();
2737 *pcFetched = 1;
2738 MOZ_LOG(
2739 gIMELog, LogLevel::Info,
2740 ("0x%p TSFTextStore::GetSelection() returns fake selection range "
2741 "for avoiding a crash in TSF, *pSelection=%s",
2742 this, mozilla::ToString(*pSelection).c_str()));
2743 return S_OK;
2745 MOZ_LOG(gIMELog, LogLevel::Error,
2746 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2747 "SelectionForTSF() failure",
2748 this));
2749 return E_FAIL;
2751 if (!selectionForTSF->HasRange()) {
2752 *pSelection = Selection::EmptyACP();
2753 *pcFetched = 0;
2754 return TS_E_NOSELECTION;
2756 *pSelection = selectionForTSF->ACPRef();
2757 *pcFetched = 1;
2758 MOZ_LOG(gIMELog, LogLevel::Info,
2759 ("0x%p TSFTextStore::GetSelection() succeeded, *pSelection=%s",
2760 this, mozilla::ToString(*pSelection).c_str()));
2761 return S_OK;
2764 // static
2765 bool TSFTextStore::DoNotReturnErrorFromGetSelection() {
2766 // There is a crash bug of TSF if we return error from GetSelection().
2767 // That was introduced in Anniversary Update (build 14393, see bug 1312302)
2768 // TODO: We should avoid to run this hack on fixed builds. When we get
2769 // exact build number, we should get back here.
2770 static bool sTSFMayCrashIfGetSelectionReturnsError =
2771 IsWin10AnniversaryUpdateOrLater();
2772 return sTSFMayCrashIfGetSelectionReturnsError;
2775 Maybe<TSFTextStore::Content>& TSFTextStore::ContentForTSF() {
2776 // This should be called when the document is locked or the content hasn't
2777 // been abandoned yet.
2778 if (NS_WARN_IF(!IsReadLocked() && mContentForTSF.isNothing())) {
2779 MOZ_LOG(gIMELog, LogLevel::Error,
2780 ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
2781 "called wrong timing, IsReadLocked()=%s, mContentForTSF=Nothing",
2782 this, GetBoolName(IsReadLocked())));
2783 return mContentForTSF;
2786 Maybe<Selection>& selectionForTSF = SelectionForTSF();
2787 if (selectionForTSF.isNothing()) {
2788 MOZ_LOG(gIMELog, LogLevel::Error,
2789 ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
2790 "SelectionForTSF() failure",
2791 this));
2792 mContentForTSF.reset();
2793 return mContentForTSF;
2796 if (mContentForTSF.isNothing()) {
2797 MOZ_DIAGNOSTIC_ASSERT(
2798 !mIsInitializingContentForTSF,
2799 "TSFTextStore::ContentForTSF() shouldn't be called recursively");
2801 AutoNotifyingTSFBatch deferNotifyingTSF(*this);
2802 AutoRestore<bool> saveInitializingContetTSF(mIsInitializingContentForTSF);
2803 mIsInitializingContentForTSF = true;
2805 nsString text; // Don't use auto string for avoiding to copy long string.
2806 if (NS_WARN_IF(!GetCurrentText(text))) {
2807 MOZ_LOG(gIMELog, LogLevel::Error,
2808 ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
2809 "GetCurrentText() failure",
2810 this));
2811 return mContentForTSF;
2814 MOZ_DIAGNOSTIC_ASSERT(mContentForTSF.isNothing(),
2815 "How was it initialized recursively?");
2816 mContentForTSF.reset(); // For avoiding crash in release channel
2817 mContentForTSF.emplace(*this, text);
2818 // Basically, the cached content which is expected by TSF/TIP should be
2819 // cleared after active composition is committed or the document lock is
2820 // unlocked. However, in e10s mode, content will be modified
2821 // asynchronously. In such case, mDeferClearingContentForTSF may be
2822 // true until whole dispatched events are handled by the focused editor.
2823 mDeferClearingContentForTSF = false;
2826 MOZ_LOG(gIMELog, LogLevel::Debug,
2827 ("0x%p TSFTextStore::ContentForTSF(): mContentForTSF=%s", this,
2828 mozilla::ToString(mContentForTSF).c_str()));
2830 return mContentForTSF;
2833 bool TSFTextStore::CanAccessActualContentDirectly() const {
2834 if (mContentForTSF.isNothing() || mSelectionForTSF.isNothing()) {
2835 return true;
2838 // If the cached content has been changed by something except composition,
2839 // the content cache may be different from actual content.
2840 if (mPendingTextChangeData.IsValid() &&
2841 !mPendingTextChangeData.mCausedOnlyByComposition) {
2842 return false;
2845 // If the cached selection isn't changed, cached content and actual content
2846 // should be same.
2847 if (mPendingSelectionChangeData.isNothing()) {
2848 return true;
2851 return mSelectionForTSF->EqualsExceptDirection(*mPendingSelectionChangeData);
2854 bool TSFTextStore::GetCurrentText(nsAString& aTextContent) {
2855 if (mContentForTSF.isSome()) {
2856 aTextContent = mContentForTSF->TextRef();
2857 return true;
2860 MOZ_ASSERT(!mDestroyed);
2861 MOZ_ASSERT(mWidget && !mWidget->Destroyed());
2863 MOZ_LOG(gIMELog, LogLevel::Debug,
2864 ("0x%p TSFTextStore::GetCurrentText(): "
2865 "retrieving text from the content...",
2866 this));
2868 WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
2869 mWidget);
2870 queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
2871 mWidget->InitEvent(queryTextContentEvent);
2872 DispatchEvent(queryTextContentEvent);
2873 if (NS_WARN_IF(queryTextContentEvent.Failed())) {
2874 MOZ_LOG(gIMELog, LogLevel::Error,
2875 ("0x%p TSFTextStore::GetCurrentText(), FAILED, due to "
2876 "eQueryTextContent failure",
2877 this));
2878 aTextContent.Truncate();
2879 return false;
2882 aTextContent = queryTextContentEvent.mReply->DataRef();
2883 return true;
2886 Maybe<TSFTextStore::Selection>& TSFTextStore::SelectionForTSF() {
2887 if (mSelectionForTSF.isNothing()) {
2888 MOZ_ASSERT(!mDestroyed);
2889 // If the window has never been available, we should crash since working
2890 // with broken values may make TIP confused.
2891 if (!mWidget || mWidget->Destroyed()) {
2892 MOZ_ASSERT_UNREACHABLE("There should be non-destroyed widget");
2895 MOZ_DIAGNOSTIC_ASSERT(
2896 !mIsInitializingSelectionForTSF,
2897 "TSFTextStore::SelectionForTSF() shouldn't be called recursively");
2899 AutoNotifyingTSFBatch deferNotifyingTSF(*this);
2900 AutoRestore<bool> saveInitializingSelectionForTSF(
2901 mIsInitializingSelectionForTSF);
2902 mIsInitializingSelectionForTSF = true;
2904 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
2905 mWidget);
2906 mWidget->InitEvent(querySelectedTextEvent);
2907 DispatchEvent(querySelectedTextEvent);
2908 if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
2909 return mSelectionForTSF;
2911 MOZ_DIAGNOSTIC_ASSERT(mSelectionForTSF.isNothing(),
2912 "How was it initialized recursively?");
2913 mSelectionForTSF = Some(Selection(querySelectedTextEvent));
2916 MOZ_LOG(gIMELog, LogLevel::Debug,
2917 ("0x%p TSFTextStore::SelectionForTSF() succeeded, "
2918 "mSelectionForTSF=%s",
2919 this, ToString(mSelectionForTSF).c_str()));
2921 return mSelectionForTSF;
2924 static HRESULT GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength) {
2925 RefPtr<ITfRangeACP> rangeACP;
2926 aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP));
2927 NS_ENSURE_TRUE(rangeACP, E_FAIL);
2928 return rangeACP->GetExtent(aStart, aLength);
2931 static TextRangeType GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE& aDisplayAttr) {
2932 switch (aDisplayAttr.bAttr) {
2933 case TF_ATTR_TARGET_CONVERTED:
2934 return TextRangeType::eSelectedClause;
2935 case TF_ATTR_CONVERTED:
2936 return TextRangeType::eConvertedClause;
2937 case TF_ATTR_TARGET_NOTCONVERTED:
2938 return TextRangeType::eSelectedRawClause;
2939 default:
2940 return TextRangeType::eRawClause;
2944 HRESULT
2945 TSFTextStore::GetDisplayAttribute(ITfProperty* aAttrProperty, ITfRange* aRange,
2946 TF_DISPLAYATTRIBUTE* aResult) {
2947 NS_ENSURE_TRUE(aAttrProperty, E_FAIL);
2948 NS_ENSURE_TRUE(aRange, E_FAIL);
2949 NS_ENSURE_TRUE(aResult, E_FAIL);
2951 HRESULT hr;
2953 if (MOZ_LOG_TEST(gIMELog, LogLevel::Debug)) {
2954 LONG start = 0, length = 0;
2955 hr = GetRangeExtent(aRange, &start, &length);
2956 MOZ_LOG(gIMELog, LogLevel::Debug,
2957 ("0x%p TSFTextStore::GetDisplayAttribute(): "
2958 "GetDisplayAttribute range=%ld-%ld (hr=%s)",
2959 this, start - mComposition->StartOffset(),
2960 start - mComposition->StartOffset() + length,
2961 GetCommonReturnValueName(hr)));
2964 VARIANT propValue;
2965 ::VariantInit(&propValue);
2966 hr = aAttrProperty->GetValue(TfEditCookie(mEditCookie), aRange, &propValue);
2967 if (FAILED(hr)) {
2968 MOZ_LOG(gIMELog, LogLevel::Error,
2969 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
2970 "ITfProperty::GetValue() failed",
2971 this));
2972 return hr;
2974 if (VT_I4 != propValue.vt) {
2975 MOZ_LOG(gIMELog, LogLevel::Error,
2976 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
2977 "ITfProperty::GetValue() returns non-VT_I4 value",
2978 this));
2979 ::VariantClear(&propValue);
2980 return E_FAIL;
2983 RefPtr<ITfCategoryMgr> categoryMgr = GetCategoryMgr();
2984 if (NS_WARN_IF(!categoryMgr)) {
2985 return E_FAIL;
2987 GUID guid;
2988 hr = categoryMgr->GetGUID(DWORD(propValue.lVal), &guid);
2989 ::VariantClear(&propValue);
2990 if (FAILED(hr)) {
2991 MOZ_LOG(gIMELog, LogLevel::Error,
2992 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
2993 "ITfCategoryMgr::GetGUID() failed",
2994 this));
2995 return hr;
2998 RefPtr<ITfDisplayAttributeMgr> displayAttrMgr = GetDisplayAttributeMgr();
2999 if (NS_WARN_IF(!displayAttrMgr)) {
3000 return E_FAIL;
3002 RefPtr<ITfDisplayAttributeInfo> info;
3003 hr = displayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info),
3004 nullptr);
3005 if (FAILED(hr) || !info) {
3006 MOZ_LOG(gIMELog, LogLevel::Error,
3007 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3008 "ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed",
3009 this));
3010 return hr;
3013 hr = info->GetAttributeInfo(aResult);
3014 if (FAILED(hr)) {
3015 MOZ_LOG(gIMELog, LogLevel::Error,
3016 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3017 "ITfDisplayAttributeInfo::GetAttributeInfo() failed",
3018 this));
3019 return hr;
3022 MOZ_LOG(gIMELog, LogLevel::Debug,
3023 ("0x%p TSFTextStore::GetDisplayAttribute() succeeded: "
3024 "Result={ %s }",
3025 this, GetDisplayAttrStr(*aResult).get()));
3026 return S_OK;
3029 HRESULT
3030 TSFTextStore::RestartCompositionIfNecessary(ITfRange* aRangeNew) {
3031 MOZ_LOG(gIMELog, LogLevel::Debug,
3032 ("0x%p TSFTextStore::RestartCompositionIfNecessary("
3033 "aRangeNew=0x%p), mComposition=%s",
3034 this, aRangeNew, ToString(mComposition).c_str()));
3036 if (mComposition.isNothing()) {
3037 MOZ_LOG(gIMELog, LogLevel::Error,
3038 ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
3039 "due to no composition view",
3040 this));
3041 return E_FAIL;
3044 HRESULT hr;
3045 RefPtr<ITfCompositionView> pComposition(mComposition->GetView());
3046 RefPtr<ITfRange> composingRange(aRangeNew);
3047 if (!composingRange) {
3048 hr = pComposition->GetRange(getter_AddRefs(composingRange));
3049 if (FAILED(hr)) {
3050 MOZ_LOG(gIMELog, LogLevel::Error,
3051 ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
3052 "FAILED due to pComposition->GetRange() failure",
3053 this));
3054 return hr;
3058 // Get starting offset of the composition
3059 LONG compStart = 0, compLength = 0;
3060 hr = GetRangeExtent(composingRange, &compStart, &compLength);
3061 if (FAILED(hr)) {
3062 MOZ_LOG(gIMELog, LogLevel::Error,
3063 ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
3064 "due to GetRangeExtent() failure",
3065 this));
3066 return hr;
3069 if (mComposition->StartOffset() == compStart &&
3070 mComposition->Length() == compLength) {
3071 return S_OK;
3074 MOZ_LOG(gIMELog, LogLevel::Debug,
3075 ("0x%p TSFTextStore::RestartCompositionIfNecessary(), "
3076 "restaring composition because of compostion range is changed "
3077 "(range=%ld-%ld, mComposition=%s)",
3078 this, compStart, compStart + compLength,
3079 ToString(mComposition).c_str()));
3081 // If the queried composition length is different from the length
3082 // of our composition string, OnUpdateComposition is being called
3083 // because a part of the original composition was committed.
3084 hr = RestartComposition(*mComposition, pComposition, composingRange);
3085 if (FAILED(hr)) {
3086 MOZ_LOG(gIMELog, LogLevel::Error,
3087 ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
3088 "FAILED due to RestartComposition() failure",
3089 this));
3090 return hr;
3093 MOZ_LOG(
3094 gIMELog, LogLevel::Debug,
3095 ("0x%p TSFTextStore::RestartCompositionIfNecessary() succeeded", this));
3096 return S_OK;
3099 HRESULT TSFTextStore::RestartComposition(Composition& aCurrentComposition,
3100 ITfCompositionView* aCompositionView,
3101 ITfRange* aNewRange) {
3102 Maybe<Selection>& selectionForTSF = SelectionForTSF();
3104 LONG newStart, newLength;
3105 HRESULT hr = GetRangeExtent(aNewRange, &newStart, &newLength);
3106 LONG newEnd = newStart + newLength;
3108 if (selectionForTSF.isNothing()) {
3109 MOZ_LOG(gIMELog, LogLevel::Error,
3110 ("0x%p TSFTextStore::RestartComposition() FAILED "
3111 "due to SelectionForTSF() failure",
3112 this));
3113 return E_FAIL;
3116 if (FAILED(hr)) {
3117 MOZ_LOG(gIMELog, LogLevel::Error,
3118 ("0x%p TSFTextStore::RestartComposition() FAILED "
3119 "due to GetRangeExtent() failure",
3120 this));
3121 return hr;
3124 // If the new range has no overlap with the crrent range, we just commit
3125 // the composition and restart new composition with the new range but
3126 // current selection range should be preserved.
3127 if (newStart >= aCurrentComposition.EndOffset() ||
3128 newEnd <= aCurrentComposition.StartOffset()) {
3129 RecordCompositionEndAction();
3130 RecordCompositionStartAction(aCompositionView, newStart, newLength, true);
3131 return S_OK;
3134 MOZ_LOG(gIMELog, LogLevel::Debug,
3135 ("0x%p TSFTextStore::RestartComposition(aCompositionView=0x%p, "
3136 "aNewRange=0x%p { newStart=%ld, newLength=%ld }), "
3137 "aCurrentComposition=%s, "
3138 "selectionForTSF=%s",
3139 this, aCompositionView, aNewRange, newStart, newLength,
3140 ToString(aCurrentComposition).c_str(),
3141 ToString(selectionForTSF).c_str()));
3143 // If the new range has an overlap with the current one, we should not commit
3144 // the whole current range to avoid creating an odd undo transaction.
3145 // I.e., the overlapped range which is being composed should not appear in
3146 // undo transaction.
3148 // Backup current composition data and selection data.
3149 Composition oldComposition = aCurrentComposition;
3150 Selection oldSelection = *selectionForTSF;
3152 // Commit only the part of composition.
3153 LONG keepComposingStartOffset =
3154 std::max(oldComposition.StartOffset(), newStart);
3155 LONG keepComposingEndOffset = std::min(oldComposition.EndOffset(), newEnd);
3156 MOZ_ASSERT(
3157 keepComposingStartOffset <= keepComposingEndOffset,
3158 "Why keepComposingEndOffset is smaller than keepComposingStartOffset?");
3159 LONG keepComposingLength = keepComposingEndOffset - keepComposingStartOffset;
3160 // Remove the overlapped part from the commit string.
3161 nsAutoString commitString(oldComposition.DataRef());
3162 commitString.Cut(keepComposingStartOffset - oldComposition.StartOffset(),
3163 keepComposingLength);
3164 // Update the composition string.
3165 Maybe<Content>& contentForTSF = ContentForTSF();
3166 if (contentForTSF.isNothing()) {
3167 MOZ_LOG(gIMELog, LogLevel::Error,
3168 ("0x%p TSFTextStore::RestartComposition() FAILED "
3169 "due to ContentForTSF() failure",
3170 this));
3171 return E_FAIL;
3173 contentForTSF->ReplaceTextWith(oldComposition.StartOffset(),
3174 oldComposition.Length(), commitString);
3175 MOZ_ASSERT(mComposition.isSome());
3176 // Record a compositionupdate action for commit the part of composing string.
3177 PendingAction* action = LastOrNewPendingCompositionUpdate();
3178 if (mComposition.isSome()) {
3179 action->mData = mComposition->DataRef();
3181 action->mRanges->Clear();
3182 // Note that we shouldn't append ranges when composition string
3183 // is empty because it may cause TextComposition confused.
3184 if (!action->mData.IsEmpty()) {
3185 TextRange caretRange;
3186 caretRange.mStartOffset = caretRange.mEndOffset = static_cast<uint32_t>(
3187 oldComposition.StartOffset() + commitString.Length());
3188 caretRange.mRangeType = TextRangeType::eCaret;
3189 action->mRanges->AppendElement(caretRange);
3191 action->mIncomplete = false;
3193 // Record compositionend action.
3194 RecordCompositionEndAction();
3196 // Record compositionstart action only with the new start since this method
3197 // hasn't restored composing string yet.
3198 RecordCompositionStartAction(aCompositionView, newStart, 0, false);
3200 // Restore the latest text content and selection.
3201 contentForTSF->ReplaceSelectedTextWith(nsDependentSubstring(
3202 oldComposition.DataRef(),
3203 keepComposingStartOffset - oldComposition.StartOffset(),
3204 keepComposingLength));
3205 selectionForTSF = Some(oldSelection);
3207 MOZ_LOG(gIMELog, LogLevel::Debug,
3208 ("0x%p TSFTextStore::RestartComposition() succeeded, "
3209 "mComposition=%s, selectionForTSF=%s",
3210 this, ToString(mComposition).c_str(),
3211 ToString(selectionForTSF).c_str()));
3213 return S_OK;
3216 static bool GetColor(const TF_DA_COLOR& aTSFColor, nscolor& aResult) {
3217 switch (aTSFColor.type) {
3218 case TF_CT_SYSCOLOR: {
3219 DWORD sysColor = ::GetSysColor(aTSFColor.nIndex);
3220 aResult =
3221 NS_RGB(GetRValue(sysColor), GetGValue(sysColor), GetBValue(sysColor));
3222 return true;
3224 case TF_CT_COLORREF:
3225 aResult = NS_RGB(GetRValue(aTSFColor.cr), GetGValue(aTSFColor.cr),
3226 GetBValue(aTSFColor.cr));
3227 return true;
3228 case TF_CT_NONE:
3229 default:
3230 return false;
3234 static bool GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle,
3235 TextRangeStyle::LineStyle& aTextRangeLineStyle) {
3236 switch (aTSFLineStyle) {
3237 case TF_LS_NONE:
3238 aTextRangeLineStyle = TextRangeStyle::LineStyle::None;
3239 return true;
3240 case TF_LS_SOLID:
3241 aTextRangeLineStyle = TextRangeStyle::LineStyle::Solid;
3242 return true;
3243 case TF_LS_DOT:
3244 aTextRangeLineStyle = TextRangeStyle::LineStyle::Dotted;
3245 return true;
3246 case TF_LS_DASH:
3247 aTextRangeLineStyle = TextRangeStyle::LineStyle::Dashed;
3248 return true;
3249 case TF_LS_SQUIGGLE:
3250 aTextRangeLineStyle = TextRangeStyle::LineStyle::Wavy;
3251 return true;
3252 default:
3253 return false;
3257 HRESULT
3258 TSFTextStore::RecordCompositionUpdateAction() {
3259 MOZ_LOG(
3260 gIMELog, LogLevel::Debug,
3261 ("0x%p TSFTextStore::RecordCompositionUpdateAction(), mComposition=%s",
3262 this, ToString(mComposition).c_str()));
3264 if (mComposition.isNothing()) {
3265 MOZ_LOG(gIMELog, LogLevel::Error,
3266 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3267 "due to no composition view",
3268 this));
3269 return E_FAIL;
3272 // Getting display attributes is *really* complicated!
3273 // We first get the context and the property objects to query for
3274 // attributes, but since a big range can have a variety of values for
3275 // the attribute, we have to find out all the ranges that have distinct
3276 // attribute values. Then we query for what the value represents through
3277 // the display attribute manager and translate that to TextRange to be
3278 // sent in eCompositionChange
3280 RefPtr<ITfProperty> attrPropetry;
3281 HRESULT hr =
3282 mContext->GetProperty(GUID_PROP_ATTRIBUTE, getter_AddRefs(attrPropetry));
3283 if (FAILED(hr) || !attrPropetry) {
3284 MOZ_LOG(gIMELog, LogLevel::Error,
3285 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3286 "due to mContext->GetProperty() failure",
3287 this));
3288 return FAILED(hr) ? hr : E_FAIL;
3291 RefPtr<ITfRange> composingRange;
3292 hr = mComposition->GetView()->GetRange(getter_AddRefs(composingRange));
3293 if (FAILED(hr)) {
3294 MOZ_LOG(gIMELog, LogLevel::Error,
3295 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3296 "FAILED due to mComposition->GetView()->GetRange() failure",
3297 this));
3298 return hr;
3301 RefPtr<IEnumTfRanges> enumRanges;
3302 hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie),
3303 getter_AddRefs(enumRanges), composingRange);
3304 if (FAILED(hr) || !enumRanges) {
3305 MOZ_LOG(gIMELog, LogLevel::Error,
3306 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3307 "due to attrPropetry->EnumRanges() failure",
3308 this));
3309 return FAILED(hr) ? hr : E_FAIL;
3312 // First, put the log of content and selection here.
3313 Maybe<Selection>& selectionForTSF = SelectionForTSF();
3314 if (selectionForTSF.isNothing()) {
3315 MOZ_LOG(gIMELog, LogLevel::Error,
3316 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3317 "due to SelectionForTSF() failure",
3318 this));
3319 return E_FAIL;
3322 PendingAction* action = LastOrNewPendingCompositionUpdate();
3323 action->mData = mComposition->DataRef();
3324 // The ranges might already have been initialized, however, if this is
3325 // called again, that means we need to overwrite the ranges with current
3326 // information.
3327 action->mRanges->Clear();
3329 // Note that we shouldn't append ranges when composition string
3330 // is empty because it may cause TextComposition confused.
3331 if (!action->mData.IsEmpty()) {
3332 TextRange newRange;
3333 // No matter if we have display attribute info or not,
3334 // we always pass in at least one range to eCompositionChange
3335 newRange.mStartOffset = 0;
3336 newRange.mEndOffset = action->mData.Length();
3337 newRange.mRangeType = TextRangeType::eRawClause;
3338 action->mRanges->AppendElement(newRange);
3340 RefPtr<ITfRange> range;
3341 while (enumRanges->Next(1, getter_AddRefs(range), nullptr) == S_OK) {
3342 if (NS_WARN_IF(!range)) {
3343 break;
3346 LONG rangeStart = 0, rangeLength = 0;
3347 if (FAILED(GetRangeExtent(range, &rangeStart, &rangeLength))) {
3348 continue;
3350 // The range may include out of composition string. We should ignore
3351 // outside of the composition string.
3352 LONG start = std::min(std::max(rangeStart, mComposition->StartOffset()),
3353 mComposition->EndOffset());
3354 LONG end = std::max(
3355 std::min(rangeStart + rangeLength, mComposition->EndOffset()),
3356 mComposition->StartOffset());
3357 LONG length = end - start;
3358 if (length < 0) {
3359 MOZ_LOG(gIMELog, LogLevel::Error,
3360 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3361 "ignores invalid range (%ld-%ld)",
3362 this, rangeStart - mComposition->StartOffset(),
3363 rangeStart - mComposition->StartOffset() + rangeLength));
3364 continue;
3366 if (!length) {
3367 MOZ_LOG(gIMELog, LogLevel::Debug,
3368 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3369 "ignores a range due to outside of the composition or empty "
3370 "(%ld-%ld)",
3371 this, rangeStart - mComposition->StartOffset(),
3372 rangeStart - mComposition->StartOffset() + rangeLength));
3373 continue;
3376 TextRange newRange;
3377 newRange.mStartOffset =
3378 static_cast<uint32_t>(start - mComposition->StartOffset());
3379 // The end of the last range in the array is
3380 // always kept at the end of composition
3381 newRange.mEndOffset = mComposition->Length();
3383 TF_DISPLAYATTRIBUTE attr;
3384 hr = GetDisplayAttribute(attrPropetry, range, &attr);
3385 if (FAILED(hr)) {
3386 newRange.mRangeType = TextRangeType::eRawClause;
3387 } else {
3388 newRange.mRangeType = GetGeckoSelectionValue(attr);
3389 if (GetColor(attr.crText, newRange.mRangeStyle.mForegroundColor)) {
3390 newRange.mRangeStyle.mDefinedStyles |=
3391 TextRangeStyle::DEFINED_FOREGROUND_COLOR;
3393 if (GetColor(attr.crBk, newRange.mRangeStyle.mBackgroundColor)) {
3394 newRange.mRangeStyle.mDefinedStyles |=
3395 TextRangeStyle::DEFINED_BACKGROUND_COLOR;
3397 if (GetColor(attr.crLine, newRange.mRangeStyle.mUnderlineColor)) {
3398 newRange.mRangeStyle.mDefinedStyles |=
3399 TextRangeStyle::DEFINED_UNDERLINE_COLOR;
3401 if (GetLineStyle(attr.lsStyle, newRange.mRangeStyle.mLineStyle)) {
3402 newRange.mRangeStyle.mDefinedStyles |=
3403 TextRangeStyle::DEFINED_LINESTYLE;
3404 newRange.mRangeStyle.mIsBoldLine = attr.fBoldLine != 0;
3408 TextRange& lastRange = action->mRanges->LastElement();
3409 if (lastRange.mStartOffset == newRange.mStartOffset) {
3410 // Replace range if last range is the same as this one
3411 // So that ranges don't overlap and confuse the editor
3412 lastRange = newRange;
3413 } else {
3414 lastRange.mEndOffset = newRange.mStartOffset;
3415 action->mRanges->AppendElement(newRange);
3419 // We need to hack for Korean Input System which is Korean standard TIP.
3420 // It sets no change style to IME selection (the selection is always only
3421 // one). So, the composition string looks like normal (or committed)
3422 // string. At this time, current selection range is same as the
3423 // composition string range. Other applications set a wide caret which
3424 // covers the composition string, however, Gecko doesn't support the wide
3425 // caret drawing now (Gecko doesn't support XOR drawing), unfortunately.
3426 // For now, we should change the range style to undefined.
3427 if (!selectionForTSF->Collapsed() && action->mRanges->Length() == 1) {
3428 TextRange& range = action->mRanges->ElementAt(0);
3429 LONG start = selectionForTSF->MinOffset();
3430 LONG end = selectionForTSF->MaxOffset();
3431 if (static_cast<LONG>(range.mStartOffset) ==
3432 start - mComposition->StartOffset() &&
3433 static_cast<LONG>(range.mEndOffset) ==
3434 end - mComposition->StartOffset() &&
3435 range.mRangeStyle.IsNoChangeStyle()) {
3436 range.mRangeStyle.Clear();
3437 // The looks of selected type is better than others.
3438 range.mRangeType = TextRangeType::eSelectedRawClause;
3442 // The caret position has to be collapsed.
3443 uint32_t caretPosition = static_cast<uint32_t>(
3444 selectionForTSF->HasRange()
3445 ? selectionForTSF->MaxOffset() - mComposition->StartOffset()
3446 : mComposition->StartOffset());
3448 // If caret is in the target clause and it doesn't have specific style,
3449 // the target clause will be painted as normal selection range. Since
3450 // caret shouldn't be in selection range on Windows, we shouldn't append
3451 // caret range in such case.
3452 const TextRange* targetClause = action->mRanges->GetTargetClause();
3453 if (!targetClause || targetClause->mRangeStyle.IsDefined() ||
3454 caretPosition < targetClause->mStartOffset ||
3455 caretPosition > targetClause->mEndOffset) {
3456 TextRange caretRange;
3457 caretRange.mStartOffset = caretRange.mEndOffset = caretPosition;
3458 caretRange.mRangeType = TextRangeType::eCaret;
3459 action->mRanges->AppendElement(caretRange);
3463 action->mIncomplete = false;
3465 MOZ_LOG(gIMELog, LogLevel::Info,
3466 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3467 "succeeded",
3468 this));
3470 return S_OK;
3473 HRESULT
3474 TSFTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection,
3475 bool aDispatchCompositionChangeEvent) {
3476 MOZ_LOG(
3477 gIMELog, LogLevel::Debug,
3478 ("0x%p TSFTextStore::SetSelectionInternal(pSelection=%s, "
3479 "aDispatchCompositionChangeEvent=%s), mComposition=%s",
3480 this, pSelection ? mozilla::ToString(*pSelection).c_str() : "nullptr",
3481 GetBoolName(aDispatchCompositionChangeEvent),
3482 ToString(mComposition).c_str()));
3484 MOZ_ASSERT(IsReadWriteLocked());
3486 Maybe<Selection>& selectionForTSF = SelectionForTSF();
3487 if (selectionForTSF.isNothing()) {
3488 MOZ_LOG(gIMELog, LogLevel::Error,
3489 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3490 "SelectionForTSF() failure",
3491 this));
3492 return E_FAIL;
3495 MaybeDispatchKeyboardEventAsProcessedByIME();
3496 if (mDestroyed) {
3497 MOZ_LOG(gIMELog, LogLevel::Error,
3498 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3499 "destroyed during dispatching a keyboard event",
3500 this));
3501 return E_FAIL;
3504 // If actually the range is not changing, we should do nothing.
3505 // Perhaps, we can ignore the difference change because it must not be
3506 // important for following edit.
3507 if (selectionForTSF->EqualsExceptDirection(*pSelection)) {
3508 MOZ_LOG(gIMELog, LogLevel::Warning,
3509 ("0x%p TSFTextStore::SetSelectionInternal() Succeeded but "
3510 "did nothing because the selection range isn't changing",
3511 this));
3512 selectionForTSF->SetSelection(*pSelection);
3513 return S_OK;
3516 if (mComposition.isSome()) {
3517 if (aDispatchCompositionChangeEvent) {
3518 HRESULT hr = RestartCompositionIfNecessary();
3519 if (FAILED(hr)) {
3520 MOZ_LOG(gIMELog, LogLevel::Error,
3521 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3522 "RestartCompositionIfNecessary() failure",
3523 this));
3524 return hr;
3527 if (pSelection->acpStart < mComposition->StartOffset() ||
3528 pSelection->acpEnd > mComposition->EndOffset()) {
3529 MOZ_LOG(gIMELog, LogLevel::Error,
3530 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3531 "the selection being out of the composition string",
3532 this));
3533 return TS_E_INVALIDPOS;
3535 // Emulate selection during compositions
3536 selectionForTSF->SetSelection(*pSelection);
3537 if (aDispatchCompositionChangeEvent) {
3538 HRESULT hr = RecordCompositionUpdateAction();
3539 if (FAILED(hr)) {
3540 MOZ_LOG(gIMELog, LogLevel::Error,
3541 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3542 "RecordCompositionUpdateAction() failure",
3543 this));
3544 return hr;
3547 return S_OK;
3550 TS_SELECTION_ACP selectionInContent(*pSelection);
3552 // If mContentForTSF caches old contents which is now different from
3553 // actual contents, we need some complicated hack here...
3554 // Note that this hack assumes that this is used for reconversion.
3555 if (mContentForTSF.isSome() && mPendingTextChangeData.IsValid() &&
3556 !mPendingTextChangeData.mCausedOnlyByComposition) {
3557 uint32_t startOffset = static_cast<uint32_t>(selectionInContent.acpStart);
3558 uint32_t endOffset = static_cast<uint32_t>(selectionInContent.acpEnd);
3559 if (mPendingTextChangeData.mStartOffset >= endOffset) {
3560 // Setting selection before any changed ranges is fine.
3561 } else if (mPendingTextChangeData.mRemovedEndOffset <= startOffset) {
3562 // Setting selection after removed range is fine with following
3563 // adjustment.
3564 selectionInContent.acpStart += mPendingTextChangeData.Difference();
3565 selectionInContent.acpEnd += mPendingTextChangeData.Difference();
3566 } else if (startOffset == endOffset) {
3567 // Moving caret position may be fine in most cases even if the insertion
3568 // point has already gone but in this case, composition will be inserted
3569 // to unexpected position, though.
3570 // It seems that moving caret into middle of the new text is odd.
3571 // Perhaps, end of it is expected by users in most cases.
3572 selectionInContent.acpStart = mPendingTextChangeData.mAddedEndOffset;
3573 selectionInContent.acpEnd = selectionInContent.acpStart;
3574 } else {
3575 // Otherwise, i.e., setting range has already gone, we cannot set
3576 // selection properly.
3577 MOZ_LOG(gIMELog, LogLevel::Error,
3578 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3579 "there is unknown content change",
3580 this));
3581 return E_FAIL;
3585 CompleteLastActionIfStillIncomplete();
3586 PendingAction* action = mPendingActions.AppendElement();
3587 action->mType = PendingAction::Type::eSetSelection;
3588 action->mSelectionStart = selectionInContent.acpStart;
3589 action->mSelectionLength =
3590 selectionInContent.acpEnd - selectionInContent.acpStart;
3591 action->mSelectionReversed = (selectionInContent.style.ase == TS_AE_START);
3593 // Use TSF specified selection for updating mSelectionForTSF.
3594 selectionForTSF->SetSelection(*pSelection);
3596 return S_OK;
3599 STDMETHODIMP
3600 TSFTextStore::SetSelection(ULONG ulCount, const TS_SELECTION_ACP* pSelection) {
3601 MOZ_LOG(gIMELog, LogLevel::Info,
3602 ("0x%p TSFTextStore::SetSelection(ulCount=%lu, pSelection=%s }), "
3603 "mComposition=%s",
3604 this, ulCount,
3605 pSelection ? mozilla::ToString(pSelection).c_str() : "nullptr",
3606 ToString(mComposition).c_str()));
3608 if (!IsReadWriteLocked()) {
3609 MOZ_LOG(gIMELog, LogLevel::Error,
3610 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3611 "not locked (read-write)",
3612 this));
3613 return TS_E_NOLOCK;
3615 if (ulCount != 1) {
3616 MOZ_LOG(gIMELog, LogLevel::Error,
3617 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3618 "trying setting multiple selection",
3619 this));
3620 return E_INVALIDARG;
3622 if (!pSelection) {
3623 MOZ_LOG(gIMELog, LogLevel::Error,
3624 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3625 "null argument",
3626 this));
3627 return E_INVALIDARG;
3630 HRESULT hr = SetSelectionInternal(pSelection, true);
3631 if (FAILED(hr)) {
3632 MOZ_LOG(gIMELog, LogLevel::Error,
3633 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3634 "SetSelectionInternal() failure",
3635 this));
3636 } else {
3637 MOZ_LOG(gIMELog, LogLevel::Info,
3638 ("0x%p TSFTextStore::SetSelection() succeeded", this));
3640 return hr;
3643 STDMETHODIMP
3644 TSFTextStore::GetText(LONG acpStart, LONG acpEnd, WCHAR* pchPlain,
3645 ULONG cchPlainReq, ULONG* pcchPlainOut,
3646 TS_RUNINFO* prgRunInfo, ULONG ulRunInfoReq,
3647 ULONG* pulRunInfoOut, LONG* pacpNext) {
3648 MOZ_LOG(
3649 gIMELog, LogLevel::Info,
3650 ("0x%p TSFTextStore::GetText(acpStart=%ld, acpEnd=%ld, pchPlain=0x%p, "
3651 "cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, ulRunInfoReq=%lu, "
3652 "pulRunInfoOut=0x%p, pacpNext=0x%p), mComposition=%s",
3653 this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, prgRunInfo,
3654 ulRunInfoReq, pulRunInfoOut, pacpNext, ToString(mComposition).c_str()));
3656 if (!IsReadLocked()) {
3657 MOZ_LOG(gIMELog, LogLevel::Error,
3658 ("0x%p TSFTextStore::GetText() FAILED due to "
3659 "not locked (read)",
3660 this));
3661 return TS_E_NOLOCK;
3664 if (!pcchPlainOut || (!pchPlain && !prgRunInfo) ||
3665 !cchPlainReq != !pchPlain || !ulRunInfoReq != !prgRunInfo) {
3666 MOZ_LOG(gIMELog, LogLevel::Error,
3667 ("0x%p TSFTextStore::GetText() FAILED due to "
3668 "invalid argument",
3669 this));
3670 return E_INVALIDARG;
3673 if (acpStart < 0 || acpEnd < -1 || (acpEnd != -1 && acpStart > acpEnd)) {
3674 MOZ_LOG(gIMELog, LogLevel::Error,
3675 ("0x%p TSFTextStore::GetText() FAILED due to "
3676 "invalid position",
3677 this));
3678 return TS_E_INVALIDPOS;
3681 // Making sure to null-terminate string just to be on the safe side
3682 *pcchPlainOut = 0;
3683 if (pchPlain && cchPlainReq) *pchPlain = 0;
3684 if (pulRunInfoOut) *pulRunInfoOut = 0;
3685 if (pacpNext) *pacpNext = acpStart;
3686 if (prgRunInfo && ulRunInfoReq) {
3687 prgRunInfo->uCount = 0;
3688 prgRunInfo->type = TS_RT_PLAIN;
3691 Maybe<Content>& contentForTSF = ContentForTSF();
3692 if (contentForTSF.isNothing()) {
3693 MOZ_LOG(gIMELog, LogLevel::Error,
3694 ("0x%p TSFTextStore::GetText() FAILED due to "
3695 "ContentForTSF() failure",
3696 this));
3697 return E_FAIL;
3699 if (contentForTSF->TextRef().Length() < static_cast<uint32_t>(acpStart)) {
3700 MOZ_LOG(gIMELog, LogLevel::Error,
3701 ("0x%p TSFTextStore::GetText() FAILED due to "
3702 "acpStart is larger offset than the actual text length",
3703 this));
3704 return TS_E_INVALIDPOS;
3706 if (acpEnd != -1 &&
3707 contentForTSF->TextRef().Length() < static_cast<uint32_t>(acpEnd)) {
3708 MOZ_LOG(gIMELog, LogLevel::Error,
3709 ("0x%p TSFTextStore::GetText() FAILED due to "
3710 "acpEnd is larger offset than the actual text length",
3711 this));
3712 return TS_E_INVALIDPOS;
3714 uint32_t length = (acpEnd == -1) ? contentForTSF->TextRef().Length() -
3715 static_cast<uint32_t>(acpStart)
3716 : static_cast<uint32_t>(acpEnd - acpStart);
3717 if (cchPlainReq && cchPlainReq - 1 < length) {
3718 length = cchPlainReq - 1;
3720 if (length) {
3721 if (pchPlain && cchPlainReq) {
3722 const char16_t* startChar =
3723 contentForTSF->TextRef().BeginReading() + acpStart;
3724 memcpy(pchPlain, startChar, length * sizeof(*pchPlain));
3725 pchPlain[length] = 0;
3726 *pcchPlainOut = length;
3728 if (prgRunInfo && ulRunInfoReq) {
3729 prgRunInfo->uCount = length;
3730 prgRunInfo->type = TS_RT_PLAIN;
3731 if (pulRunInfoOut) *pulRunInfoOut = 1;
3733 if (pacpNext) *pacpNext = acpStart + length;
3736 MOZ_LOG(gIMELog, LogLevel::Info,
3737 ("0x%p TSFTextStore::GetText() succeeded: pcchPlainOut=0x%p, "
3738 "*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, "
3739 "*pacpNext=%ld)",
3740 this, pcchPlainOut, prgRunInfo ? prgRunInfo->uCount : 0,
3741 prgRunInfo ? GetTextRunTypeName(prgRunInfo->type) : "N/A",
3742 pulRunInfoOut ? *pulRunInfoOut : 0, pacpNext ? *pacpNext : 0));
3743 return S_OK;
3746 STDMETHODIMP
3747 TSFTextStore::SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd,
3748 const WCHAR* pchText, ULONG cch, TS_TEXTCHANGE* pChange) {
3749 MOZ_LOG(
3750 gIMELog, LogLevel::Info,
3751 ("0x%p TSFTextStore::SetText(dwFlags=%s, acpStart=%ld, acpEnd=%ld, "
3752 "pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), mComposition=%s",
3753 this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" : "not-specified",
3754 acpStart, acpEnd, pchText,
3755 pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "", cch,
3756 pChange, ToString(mComposition).c_str()));
3758 // Per SDK documentation, and since we don't have better
3759 // ways to do this, this method acts as a helper to
3760 // call SetSelection followed by InsertTextAtSelection
3761 if (!IsReadWriteLocked()) {
3762 MOZ_LOG(gIMELog, LogLevel::Error,
3763 ("0x%p TSFTextStore::SetText() FAILED due to "
3764 "not locked (read)",
3765 this));
3766 return TS_E_NOLOCK;
3769 TS_SELECTION_ACP selection;
3770 selection.acpStart = acpStart;
3771 selection.acpEnd = acpEnd;
3772 selection.style.ase = TS_AE_END;
3773 selection.style.fInterimChar = 0;
3774 // Set selection to desired range
3775 HRESULT hr = SetSelectionInternal(&selection);
3776 if (FAILED(hr)) {
3777 MOZ_LOG(gIMELog, LogLevel::Error,
3778 ("0x%p TSFTextStore::SetText() FAILED due to "
3779 "SetSelectionInternal() failure",
3780 this));
3781 return hr;
3783 // Replace just selected text
3784 if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
3785 pChange)) {
3786 MOZ_LOG(gIMELog, LogLevel::Error,
3787 ("0x%p TSFTextStore::SetText() FAILED due to "
3788 "InsertTextAtSelectionInternal() failure",
3789 this));
3790 return E_FAIL;
3793 MOZ_LOG(gIMELog, LogLevel::Info,
3794 ("0x%p TSFTextStore::SetText() succeeded: pChange={ "
3795 "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
3796 this, pChange ? pChange->acpStart : 0,
3797 pChange ? pChange->acpOldEnd : 0, pChange ? pChange->acpNewEnd : 0));
3798 return S_OK;
3801 STDMETHODIMP
3802 TSFTextStore::GetFormattedText(LONG acpStart, LONG acpEnd,
3803 IDataObject** ppDataObject) {
3804 MOZ_LOG(gIMELog, LogLevel::Info,
3805 ("0x%p TSFTextStore::GetFormattedText() called "
3806 "but not supported (E_NOTIMPL)",
3807 this));
3809 // no support for formatted text
3810 return E_NOTIMPL;
3813 STDMETHODIMP
3814 TSFTextStore::GetEmbedded(LONG acpPos, REFGUID rguidService, REFIID riid,
3815 IUnknown** ppunk) {
3816 MOZ_LOG(gIMELog, LogLevel::Info,
3817 ("0x%p TSFTextStore::GetEmbedded() called "
3818 "but not supported (E_NOTIMPL)",
3819 this));
3821 // embedded objects are not supported
3822 return E_NOTIMPL;
3825 STDMETHODIMP
3826 TSFTextStore::QueryInsertEmbedded(const GUID* pguidService,
3827 const FORMATETC* pFormatEtc,
3828 BOOL* pfInsertable) {
3829 MOZ_LOG(gIMELog, LogLevel::Info,
3830 ("0x%p TSFTextStore::QueryInsertEmbedded() called "
3831 "but not supported, *pfInsertable=FALSE (S_OK)",
3832 this));
3834 // embedded objects are not supported
3835 *pfInsertable = FALSE;
3836 return S_OK;
3839 STDMETHODIMP
3840 TSFTextStore::InsertEmbedded(DWORD dwFlags, LONG acpStart, LONG acpEnd,
3841 IDataObject* pDataObject, TS_TEXTCHANGE* pChange) {
3842 MOZ_LOG(gIMELog, LogLevel::Info,
3843 ("0x%p TSFTextStore::InsertEmbedded() called "
3844 "but not supported (E_NOTIMPL)",
3845 this));
3847 // embedded objects are not supported
3848 return E_NOTIMPL;
3851 // static
3852 bool TSFTextStore::ShouldSetInputScopeOfURLBarToDefault() {
3853 // FYI: Google Japanese Input may be an IMM-IME. If it's installed on
3854 // Win7, it's always IMM-IME. Otherwise, basically, it's a TIP.
3855 // However, if it's installed on Win7 and has not been updated yet
3856 // after the OS is upgraded to Win8 or later, it's still an IMM-IME.
3857 // Therefore, we also need to check with IMMHandler here.
3858 if (!StaticPrefs::intl_ime_hack_set_input_scope_of_url_bar_to_default()) {
3859 return false;
3862 if (IMMHandler::IsGoogleJapaneseInputActive()) {
3863 return true;
3866 switch (TSFStaticSink::ActiveTIP()) {
3867 case TextInputProcessorID::eMicrosoftIMEForJapanese:
3868 case TextInputProcessorID::eGoogleJapaneseInput:
3869 case TextInputProcessorID::eMicrosoftBopomofo:
3870 case TextInputProcessorID::eMicrosoftChangJie:
3871 case TextInputProcessorID::eMicrosoftPhonetic:
3872 case TextInputProcessorID::eMicrosoftQuick:
3873 case TextInputProcessorID::eMicrosoftNewChangJie:
3874 case TextInputProcessorID::eMicrosoftNewPhonetic:
3875 case TextInputProcessorID::eMicrosoftNewQuick:
3876 case TextInputProcessorID::eMicrosoftPinyin:
3877 case TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle:
3878 case TextInputProcessorID::eMicrosoftOldHangul:
3879 case TextInputProcessorID::eMicrosoftWubi:
3880 case TextInputProcessorID::eMicrosoftIMEForKorean:
3881 return true;
3882 default:
3883 return false;
3887 void TSFTextStore::SetInputScope(const nsString& aHTMLInputType,
3888 const nsString& aHTMLInputMode) {
3889 mInputScopes.Clear();
3891 // IME may refer only first input scope, but we will append inputmode's
3892 // input scopes too like Chrome since IME may refer it.
3893 IMEHandler::AppendInputScopeFromType(aHTMLInputType, mInputScopes);
3894 IMEHandler::AppendInputScopeFromInputMode(aHTMLInputMode, mInputScopes);
3896 if (mInPrivateBrowsing) {
3897 mInputScopes.AppendElement(IS_PRIVATE);
3901 int32_t TSFTextStore::GetRequestedAttrIndex(const TS_ATTRID& aAttrID) {
3902 if (IsEqualGUID(aAttrID, GUID_PROP_INPUTSCOPE)) {
3903 return eInputScope;
3905 if (IsEqualGUID(aAttrID, sGUID_PROP_URL)) {
3906 return eDocumentURL;
3908 if (IsEqualGUID(aAttrID, TSATTRID_Text_VerticalWriting)) {
3909 return eTextVerticalWriting;
3911 if (IsEqualGUID(aAttrID, TSATTRID_Text_Orientation)) {
3912 return eTextOrientation;
3914 return eNotSupported;
3917 TS_ATTRID
3918 TSFTextStore::GetAttrID(int32_t aIndex) {
3919 switch (aIndex) {
3920 case eInputScope:
3921 return GUID_PROP_INPUTSCOPE;
3922 case eDocumentURL:
3923 return sGUID_PROP_URL;
3924 case eTextVerticalWriting:
3925 return TSATTRID_Text_VerticalWriting;
3926 case eTextOrientation:
3927 return TSATTRID_Text_Orientation;
3928 default:
3929 MOZ_CRASH("Invalid index? Or not implemented yet?");
3930 return GUID_NULL;
3934 HRESULT
3935 TSFTextStore::HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
3936 const TS_ATTRID* aFilterAttrs) {
3937 MOZ_LOG(gIMELog, LogLevel::Info,
3938 ("0x%p TSFTextStore::HandleRequestAttrs(aFlags=%s, "
3939 "aFilterCount=%lu)",
3940 this, GetFindFlagName(aFlags).get(), aFilterCount));
3942 // This is a little weird! RequestSupportedAttrs gives us advanced notice
3943 // of a support query via RetrieveRequestedAttrs for a specific attribute.
3944 // RetrieveRequestedAttrs needs to return valid data for all attributes we
3945 // support, but the text service will only want the input scope object
3946 // returned in RetrieveRequestedAttrs if the dwFlags passed in here contains
3947 // TS_ATTR_FIND_WANT_VALUE.
3948 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
3949 mRequestedAttrs[i] = false;
3951 mRequestedAttrValues = !!(aFlags & TS_ATTR_FIND_WANT_VALUE);
3953 for (uint32_t i = 0; i < aFilterCount; i++) {
3954 MOZ_LOG(gIMELog, LogLevel::Info,
3955 ("0x%p TSFTextStore::HandleRequestAttrs(), "
3956 "requested attr=%s",
3957 this, GetGUIDNameStrWithTable(aFilterAttrs[i]).get()));
3958 int32_t index = GetRequestedAttrIndex(aFilterAttrs[i]);
3959 if (index != eNotSupported) {
3960 mRequestedAttrs[index] = true;
3963 return S_OK;
3966 STDMETHODIMP
3967 TSFTextStore::RequestSupportedAttrs(DWORD dwFlags, ULONG cFilterAttrs,
3968 const TS_ATTRID* paFilterAttrs) {
3969 MOZ_LOG(gIMELog, LogLevel::Info,
3970 ("0x%p TSFTextStore::RequestSupportedAttrs(dwFlags=%s, "
3971 "cFilterAttrs=%lu)",
3972 this, GetFindFlagName(dwFlags).get(), cFilterAttrs));
3974 return HandleRequestAttrs(dwFlags, cFilterAttrs, paFilterAttrs);
3977 STDMETHODIMP
3978 TSFTextStore::RequestAttrsAtPosition(LONG acpPos, ULONG cFilterAttrs,
3979 const TS_ATTRID* paFilterAttrs,
3980 DWORD dwFlags) {
3981 MOZ_LOG(gIMELog, LogLevel::Info,
3982 ("0x%p TSFTextStore::RequestAttrsAtPosition(acpPos=%ld, "
3983 "cFilterAttrs=%lu, dwFlags=%s)",
3984 this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
3986 return HandleRequestAttrs(dwFlags | TS_ATTR_FIND_WANT_VALUE, cFilterAttrs,
3987 paFilterAttrs);
3990 STDMETHODIMP
3991 TSFTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos,
3992 ULONG cFilterAttrs,
3993 const TS_ATTRID* paFilterAttr,
3994 DWORD dwFlags) {
3995 MOZ_LOG(gIMELog, LogLevel::Info,
3996 ("0x%p TSFTextStore::RequestAttrsTransitioningAtPosition("
3997 "acpPos=%ld, cFilterAttrs=%lu, dwFlags=%s) called but not supported "
3998 "(S_OK)",
3999 this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
4001 // no per character attributes defined
4002 return S_OK;
4005 STDMETHODIMP
4006 TSFTextStore::FindNextAttrTransition(LONG acpStart, LONG acpHalt,
4007 ULONG cFilterAttrs,
4008 const TS_ATTRID* paFilterAttrs,
4009 DWORD dwFlags, LONG* pacpNext,
4010 BOOL* pfFound, LONG* plFoundOffset) {
4011 if (!pacpNext || !pfFound || !plFoundOffset) {
4012 MOZ_LOG(gIMELog, LogLevel::Error,
4013 (" 0x%p TSFTextStore::FindNextAttrTransition() FAILED due to "
4014 "null argument",
4015 this));
4016 return E_INVALIDARG;
4019 MOZ_LOG(gIMELog, LogLevel::Info,
4020 ("0x%p TSFTextStore::FindNextAttrTransition() called "
4021 "but not supported (S_OK)",
4022 this));
4024 // no per character attributes defined
4025 *pacpNext = *plFoundOffset = acpHalt;
4026 *pfFound = FALSE;
4027 return S_OK;
4030 // To test the document URL result, define this to out put it to the stdout
4031 // #define DEBUG_PRINT_DOCUMENT_URL
4033 STDMETHODIMP
4034 TSFTextStore::RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL* paAttrVals,
4035 ULONG* pcFetched) {
4036 if (!pcFetched || !paAttrVals) {
4037 MOZ_LOG(gIMELog, LogLevel::Error,
4038 ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
4039 "null argument",
4040 this));
4041 return E_INVALIDARG;
4044 ULONG expectedCount = 0;
4045 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
4046 if (mRequestedAttrs[i]) {
4047 expectedCount++;
4050 if (ulCount < expectedCount) {
4051 MOZ_LOG(gIMELog, LogLevel::Error,
4052 ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
4053 "not enough count ulCount=%lu, expectedCount=%lu",
4054 this, ulCount, expectedCount));
4055 return E_INVALIDARG;
4058 MOZ_LOG(gIMELog, LogLevel::Info,
4059 ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
4060 "ulCount=%lu, mRequestedAttrValues=%s",
4061 this, ulCount, GetBoolName(mRequestedAttrValues)));
4063 auto GetExposingURL = [&]() -> BSTR {
4064 const bool allowed =
4065 StaticPrefs::intl_tsf_expose_url_allowed() &&
4066 (!mInPrivateBrowsing ||
4067 StaticPrefs::intl_tsf_expose_url_in_private_browsing_allowed());
4068 if (!allowed || mDocumentURL.IsEmpty()) {
4069 BSTR emptyString = ::SysAllocString(L"");
4070 MOZ_ASSERT(
4071 emptyString,
4072 "We need to return valid BSTR pointer to notify TSF of supporting it "
4073 "with a pointer to empty string");
4074 return emptyString;
4076 return ::SysAllocString(mDocumentURL.get());
4079 #ifdef DEBUG_PRINT_DOCUMENT_URL
4081 BSTR exposingURL = GetExposingURL();
4082 printf("TSFTextStore::RetrieveRequestedAttrs: DocumentURL=\"%s\"\n",
4083 NS_ConvertUTF16toUTF8(static_cast<char16ptr_t>(_bstr_t(exposingURL)))
4084 .get());
4085 ::SysFreeString(exposingURL);
4087 #endif // #ifdef DEBUG_PRINT_DOCUMENT_URL
4089 int32_t count = 0;
4090 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
4091 if (!mRequestedAttrs[i]) {
4092 continue;
4094 mRequestedAttrs[i] = false;
4096 TS_ATTRID attrID = GetAttrID(i);
4098 MOZ_LOG(gIMELog, LogLevel::Info,
4099 ("0x%p TSFTextStore::RetrieveRequestedAttrs() for %s", this,
4100 GetGUIDNameStrWithTable(attrID).get()));
4102 paAttrVals[count].idAttr = attrID;
4103 paAttrVals[count].dwOverlapId = 0;
4105 if (!mRequestedAttrValues) {
4106 paAttrVals[count].varValue.vt = VT_EMPTY;
4107 } else {
4108 switch (i) {
4109 case eInputScope: {
4110 paAttrVals[count].varValue.vt = VT_UNKNOWN;
4111 RefPtr<IUnknown> inputScope = new InputScopeImpl(mInputScopes);
4112 paAttrVals[count].varValue.punkVal = inputScope.forget().take();
4113 break;
4115 case eDocumentURL: {
4116 paAttrVals[count].varValue.vt = VT_BSTR;
4117 paAttrVals[count].varValue.bstrVal = GetExposingURL();
4118 break;
4120 case eTextVerticalWriting: {
4121 Maybe<Selection>& selectionForTSF = SelectionForTSF();
4122 paAttrVals[count].varValue.vt = VT_BOOL;
4123 paAttrVals[count].varValue.boolVal =
4124 selectionForTSF.isSome() &&
4125 selectionForTSF->WritingModeRef().IsVertical()
4126 ? VARIANT_TRUE
4127 : VARIANT_FALSE;
4128 break;
4130 case eTextOrientation: {
4131 Maybe<Selection>& selectionForTSF = SelectionForTSF();
4132 paAttrVals[count].varValue.vt = VT_I4;
4133 paAttrVals[count].varValue.lVal =
4134 selectionForTSF.isSome() &&
4135 selectionForTSF->WritingModeRef().IsVertical()
4136 ? 2700
4137 : 0;
4138 break;
4140 default:
4141 MOZ_CRASH("Invalid index? Or not implemented yet?");
4142 break;
4145 count++;
4148 mRequestedAttrValues = false;
4150 if (count) {
4151 *pcFetched = count;
4152 return S_OK;
4155 MOZ_LOG(gIMELog, LogLevel::Info,
4156 ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
4157 "for unknown TS_ATTRVAL, *pcFetched=0 (S_OK)",
4158 this));
4160 paAttrVals->dwOverlapId = 0;
4161 paAttrVals->varValue.vt = VT_EMPTY;
4162 *pcFetched = 0;
4163 return S_OK;
4166 #undef DEBUG_PRINT_DOCUMENT_URL
4168 STDMETHODIMP
4169 TSFTextStore::GetEndACP(LONG* pacp) {
4170 MOZ_LOG(gIMELog, LogLevel::Info,
4171 ("0x%p TSFTextStore::GetEndACP(pacp=0x%p)", this, pacp));
4173 if (!IsReadLocked()) {
4174 MOZ_LOG(gIMELog, LogLevel::Error,
4175 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4176 "not locked (read)",
4177 this));
4178 return TS_E_NOLOCK;
4181 if (!pacp) {
4182 MOZ_LOG(gIMELog, LogLevel::Error,
4183 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4184 "null argument",
4185 this));
4186 return E_INVALIDARG;
4189 Maybe<Content>& contentForTSF = ContentForTSF();
4190 if (contentForTSF.isNothing()) {
4191 MOZ_LOG(gIMELog, LogLevel::Error,
4192 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4193 "ContentForTSF() failure",
4194 this));
4195 return E_FAIL;
4197 *pacp = static_cast<LONG>(contentForTSF->TextRef().Length());
4198 return S_OK;
4201 STDMETHODIMP
4202 TSFTextStore::GetActiveView(TsViewCookie* pvcView) {
4203 MOZ_LOG(gIMELog, LogLevel::Info,
4204 ("0x%p TSFTextStore::GetActiveView(pvcView=0x%p)", this, pvcView));
4206 if (!pvcView) {
4207 MOZ_LOG(gIMELog, LogLevel::Error,
4208 ("0x%p TSFTextStore::GetActiveView() FAILED due to "
4209 "null argument",
4210 this));
4211 return E_INVALIDARG;
4214 *pvcView = TEXTSTORE_DEFAULT_VIEW;
4216 MOZ_LOG(gIMELog, LogLevel::Info,
4217 ("0x%p TSFTextStore::GetActiveView() succeeded: *pvcView=%ld", this,
4218 *pvcView));
4219 return S_OK;
4222 STDMETHODIMP
4223 TSFTextStore::GetACPFromPoint(TsViewCookie vcView, const POINT* pt,
4224 DWORD dwFlags, LONG* pacp) {
4225 MOZ_LOG(gIMELog, LogLevel::Info,
4226 ("0x%p TSFTextStore::GetACPFromPoint(pvcView=%ld, pt=%p (x=%ld, "
4227 "y=%ld), dwFlags=%s, pacp=%p, mDeferNotifyingTSFUntilNextUpdate=%s, "
4228 "mWaitingQueryLayout=%s",
4229 this, vcView, pt, pt ? pt->x : 0, pt ? pt->y : 0,
4230 GetACPFromPointFlagName(dwFlags).get(), pacp,
4231 GetBoolName(mDeferNotifyingTSFUntilNextUpdate),
4232 GetBoolName(mWaitingQueryLayout)));
4234 if (!IsReadLocked()) {
4235 MOZ_LOG(gIMELog, LogLevel::Error,
4236 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4237 "not locked (read)",
4238 this));
4239 return TS_E_NOLOCK;
4242 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4243 MOZ_LOG(gIMELog, LogLevel::Error,
4244 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4245 "called with invalid view",
4246 this));
4247 return E_INVALIDARG;
4250 if (!pt) {
4251 MOZ_LOG(gIMELog, LogLevel::Error,
4252 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4253 "null pt",
4254 this));
4255 return E_INVALIDARG;
4258 if (!pacp) {
4259 MOZ_LOG(gIMELog, LogLevel::Error,
4260 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4261 "null pacp",
4262 this));
4263 return E_INVALIDARG;
4266 mWaitingQueryLayout = false;
4268 if (mDestroyed ||
4269 (mContentForTSF.isSome() && mContentForTSF->IsLayoutChanged())) {
4270 MOZ_LOG(gIMELog, LogLevel::Error,
4271 ("0x%p TSFTextStore::GetACPFromPoint() returned "
4272 "TS_E_NOLAYOUT",
4273 this));
4274 mHasReturnedNoLayoutError = true;
4275 return TS_E_NOLAYOUT;
4278 LayoutDeviceIntPoint ourPt(pt->x, pt->y);
4279 // Convert to widget relative coordinates from screen's.
4280 ourPt -= mWidget->WidgetToScreenOffset();
4282 // NOTE: Don't check if the point is in the widget since the point can be
4283 // outside of the widget if focused editor is in a XUL <panel>.
4285 WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint,
4286 mWidget);
4287 mWidget->InitEvent(queryCharAtPointEvent, &ourPt);
4289 // FYI: WidgetQueryContentEvent may cause flushing pending layout and it
4290 // may cause focus change or something.
4291 RefPtr<TSFTextStore> kungFuDeathGrip(this);
4292 DispatchEvent(queryCharAtPointEvent);
4293 if (!mWidget || mWidget->Destroyed()) {
4294 MOZ_LOG(gIMELog, LogLevel::Error,
4295 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4296 "mWidget was destroyed during eQueryCharacterAtPoint",
4297 this));
4298 return E_FAIL;
4301 MOZ_LOG(gIMELog, LogLevel::Debug,
4302 ("0x%p TSFTextStore::GetACPFromPoint(), queryCharAtPointEvent={ "
4303 "mReply=%s }",
4304 this, ToString(queryCharAtPointEvent.mReply).c_str()));
4306 if (NS_WARN_IF(queryCharAtPointEvent.Failed())) {
4307 MOZ_LOG(gIMELog, LogLevel::Error,
4308 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4309 "eQueryCharacterAtPoint failure",
4310 this));
4311 return E_FAIL;
4314 // If dwFlags isn't set and the point isn't in any character's bounding box,
4315 // we should return TS_E_INVALIDPOINT.
4316 if (!(dwFlags & GXFPF_NEAREST) && queryCharAtPointEvent.DidNotFindChar()) {
4317 MOZ_LOG(gIMELog, LogLevel::Error,
4318 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to the "
4319 "point contained by no bounding box",
4320 this));
4321 return TS_E_INVALIDPOINT;
4324 // Although, we're not sure if mTentativeCaretOffset becomes NOT_FOUND,
4325 // let's assume that there is no content in such case.
4326 NS_WARNING_ASSERTION(queryCharAtPointEvent.DidNotFindTentativeCaretOffset(),
4327 "Tentative caret offset was not found");
4329 uint32_t offset;
4331 // If dwFlags includes GXFPF_ROUND_NEAREST, we should return tentative
4332 // caret offset (MSDN calls it "range position").
4333 if (dwFlags & GXFPF_ROUND_NEAREST) {
4334 offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0);
4335 } else if (queryCharAtPointEvent.FoundChar()) {
4336 // Otherwise, we should return character offset whose bounding box contains
4337 // the point.
4338 offset = queryCharAtPointEvent.mReply->StartOffset();
4339 } else {
4340 // If the point isn't in any character's bounding box but we need to return
4341 // the nearest character from the point, we should *guess* the character
4342 // offset since there is no inexpensive API to check it strictly.
4343 // XXX If we retrieve 2 bounding boxes, one is before the offset and
4344 // the other is after the offset, we could resolve the offset.
4345 // However, dispatching 2 eQueryTextRect may be expensive.
4347 // So, use tentative offset for now.
4348 offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0);
4350 // However, if it's after the last character, we need to decrement the
4351 // offset.
4352 Maybe<Content>& contentForTSF = ContentForTSF();
4353 if (contentForTSF.isNothing()) {
4354 MOZ_LOG(gIMELog, LogLevel::Error,
4355 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4356 "ContentForTSF() failure",
4357 this));
4358 return E_FAIL;
4360 if (contentForTSF->TextRef().Length() <= offset) {
4361 // If the tentative caret is after the last character, let's return
4362 // the last character's offset.
4363 offset = contentForTSF->TextRef().Length() - 1;
4367 if (NS_WARN_IF(offset > LONG_MAX)) {
4368 MOZ_LOG(gIMELog, LogLevel::Error,
4369 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to out of "
4370 "range of the result",
4371 this));
4372 return TS_E_INVALIDPOINT;
4375 *pacp = static_cast<LONG>(offset);
4376 MOZ_LOG(gIMELog, LogLevel::Info,
4377 ("0x%p TSFTextStore::GetACPFromPoint() succeeded: *pacp=%ld", this,
4378 *pacp));
4379 return S_OK;
4382 STDMETHODIMP
4383 TSFTextStore::GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd,
4384 RECT* prc, BOOL* pfClipped) {
4385 MOZ_LOG(gIMELog, LogLevel::Info,
4386 ("0x%p TSFTextStore::GetTextExt(vcView=%ld, "
4387 "acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p), "
4388 "IsHandlingCompositionInParent()=%s, "
4389 "IsHandlingCompositionInContent()=%s, mContentForTSF=%s, "
4390 "mSelectionForTSF=%s, mComposition=%s, "
4391 "mDeferNotifyingTSFUntilNextUpdate=%s, mWaitingQueryLayout=%s, "
4392 "IMEHandler::IsA11yHandlingNativeCaret()=%s",
4393 this, vcView, acpStart, acpEnd, prc, pfClipped,
4394 GetBoolName(IsHandlingCompositionInParent()),
4395 GetBoolName(IsHandlingCompositionInContent()),
4396 mozilla::ToString(mContentForTSF).c_str(),
4397 ToString(mSelectionForTSF).c_str(), ToString(mComposition).c_str(),
4398 GetBoolName(mDeferNotifyingTSFUntilNextUpdate),
4399 GetBoolName(mWaitingQueryLayout),
4400 GetBoolName(IMEHandler::IsA11yHandlingNativeCaret())));
4402 if (!IsReadLocked()) {
4403 MOZ_LOG(gIMELog, LogLevel::Error,
4404 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4405 "not locked (read)",
4406 this));
4407 return TS_E_NOLOCK;
4410 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4411 MOZ_LOG(gIMELog, LogLevel::Error,
4412 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4413 "called with invalid view",
4414 this));
4415 return E_INVALIDARG;
4418 if (!prc || !pfClipped) {
4419 MOZ_LOG(gIMELog, LogLevel::Error,
4420 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4421 "null argument",
4422 this));
4423 return E_INVALIDARG;
4426 // According to MSDN, ITextStoreACP::GetTextExt() should return
4427 // TS_E_INVALIDARG when acpStart and acpEnd are same (i.e., collapsed range).
4428 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms538435(v=vs.85).aspx
4429 // > TS_E_INVALIDARG: The specified start and end character positions are
4430 // > equal.
4431 // However, some TIPs (including Microsoft's Chinese TIPs!) call this with
4432 // collapsed range and if we return TS_E_INVALIDARG, they stops showing their
4433 // owning window or shows it but odd position. So, we should just return
4434 // error only when acpStart and/or acpEnd are really odd.
4436 if (acpStart < 0 || acpEnd < acpStart) {
4437 MOZ_LOG(gIMELog, LogLevel::Error,
4438 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4439 "invalid position",
4440 this));
4441 return TS_E_INVALIDPOS;
4444 mWaitingQueryLayout = false;
4446 if (IsHandlingCompositionInContent() && mContentForTSF.isSome() &&
4447 mContentForTSF->HasOrHadComposition() &&
4448 mContentForTSF->IsLayoutChanged() &&
4449 mContentForTSF->MinModifiedOffset().value() >
4450 static_cast<uint32_t>(LONG_MAX)) {
4451 MOZ_LOG(gIMELog, LogLevel::Error,
4452 ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text "
4453 "is too big for TSF (cannot treat modified offset as LONG), "
4454 "mContentForTSF=%s",
4455 this, ToString(mContentForTSF).c_str()));
4456 return E_FAIL;
4459 // At Windows 10 build 17643 (an insider preview for RS5), Microsoft fixed
4460 // the bug of TS_E_NOLAYOUT (even when we returned TS_E_NOLAYOUT, TSF
4461 // returned E_FAIL to TIP). However, until we drop to support older Windows
4462 // and all TIPs are aware of TS_E_NOLAYOUT result, we need to keep returning
4463 // S_OK and available rectangle only for them.
4464 if (!MaybeHackNoErrorLayoutBugs(acpStart, acpEnd) &&
4465 mContentForTSF.isSome() && mContentForTSF->IsLayoutChangedAt(acpEnd)) {
4466 MOZ_LOG(gIMELog, LogLevel::Error,
4467 ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
4468 "(acpEnd=%ld)",
4469 this, acpEnd));
4470 mHasReturnedNoLayoutError = true;
4471 return TS_E_NOLAYOUT;
4474 if (mDestroyed) {
4475 MOZ_LOG(gIMELog, LogLevel::Error,
4476 ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
4477 "(acpEnd=%ld) because this has already been destroyed",
4478 this, acpEnd));
4479 mHasReturnedNoLayoutError = true;
4480 return TS_E_NOLAYOUT;
4483 // use eQueryTextRect to get rect in system, screen coordinates
4484 WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, mWidget);
4485 mWidget->InitEvent(queryTextRectEvent);
4487 WidgetQueryContentEvent::Options options;
4488 int64_t startOffset = acpStart;
4489 if (mComposition.isSome()) {
4490 // If there is a composition, TSF must want character rects related to
4491 // the composition. Therefore, we should use insertion point relative
4492 // query because the composition might be at different position from
4493 // the position where TSFTextStore believes it at.
4494 options.mRelativeToInsertionPoint = true;
4495 startOffset -= mComposition->StartOffset();
4496 } else if (IsHandlingCompositionInParent() && mContentForTSF.isSome() &&
4497 mContentForTSF->HasOrHadComposition()) {
4498 // If there was a composition and its commit event hasn't been dispatched
4499 // yet, ContentCacheInParent is still open for relative offset query from
4500 // the latest composition.
4501 options.mRelativeToInsertionPoint = true;
4502 startOffset -= mContentForTSF->LatestCompositionRange()->StartOffset();
4503 } else if (!CanAccessActualContentDirectly() &&
4504 mSelectionForTSF->HasRange()) {
4505 // If TSF/TIP cannot access actual content directly, there may be pending
4506 // text and/or selection changes which have not been notified TSF yet.
4507 // Therefore, we should use relative to insertion point query since
4508 // TSF/TIP computes the offset from the cached selection.
4509 options.mRelativeToInsertionPoint = true;
4510 startOffset -= mSelectionForTSF->StartOffset();
4512 // ContentEventHandler and ContentCache return actual caret rect when
4513 // the queried range is collapsed and selection is collapsed at the
4514 // queried range. Then, its height (in horizontal layout, width in vertical
4515 // layout) may be different from actual font height of the line. In such
4516 // case, users see "dancing" of candidate or suggest window of TIP.
4517 // For preventing it, we should query text rect with at least 1 length.
4518 uint32_t length = std::max(static_cast<int32_t>(acpEnd - acpStart), 1);
4519 queryTextRectEvent.InitForQueryTextRect(startOffset, length, options);
4521 DispatchEvent(queryTextRectEvent);
4522 if (NS_WARN_IF(queryTextRectEvent.Failed())) {
4523 MOZ_LOG(gIMELog, LogLevel::Error,
4524 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4525 "eQueryTextRect failure",
4526 this));
4527 return TS_E_INVALIDPOS; // but unexpected failure, maybe.
4530 // IMEs don't like empty rects, fix here
4531 if (queryTextRectEvent.mReply->mRect.Width() <= 0) {
4532 queryTextRectEvent.mReply->mRect.SetWidth(1);
4534 if (queryTextRectEvent.mReply->mRect.Height() <= 0) {
4535 queryTextRectEvent.mReply->mRect.SetHeight(1);
4538 // convert to unclipped screen rect
4539 nsWindow* refWindow =
4540 static_cast<nsWindow*>(!!queryTextRectEvent.mReply->mFocusedWidget
4541 ? queryTextRectEvent.mReply->mFocusedWidget
4542 : static_cast<nsIWidget*>(mWidget.get()));
4543 // Result rect is in top level widget coordinates
4544 refWindow = refWindow->GetTopLevelWindow(false);
4545 if (!refWindow) {
4546 MOZ_LOG(gIMELog, LogLevel::Error,
4547 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4548 "no top level window",
4549 this));
4550 return E_FAIL;
4553 queryTextRectEvent.mReply->mRect.MoveBy(refWindow->WidgetToScreenOffset());
4555 // get bounding screen rect to test for clipping
4556 if (!GetScreenExtInternal(*prc)) {
4557 MOZ_LOG(gIMELog, LogLevel::Error,
4558 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4559 "GetScreenExtInternal() failure",
4560 this));
4561 return E_FAIL;
4564 // clip text rect to bounding rect
4565 RECT textRect;
4566 ::SetRect(&textRect, queryTextRectEvent.mReply->mRect.X(),
4567 queryTextRectEvent.mReply->mRect.Y(),
4568 queryTextRectEvent.mReply->mRect.XMost(),
4569 queryTextRectEvent.mReply->mRect.YMost());
4570 if (!::IntersectRect(prc, prc, &textRect))
4571 // Text is not visible
4572 ::SetRectEmpty(prc);
4574 // not equal if text rect was clipped
4575 *pfClipped = !::EqualRect(prc, &textRect);
4577 // ATOK 2011 - 2016 refers native caret position and size on windows whose
4578 // class name is one of Mozilla's windows for deciding candidate window
4579 // position. Additionally, ATOK 2015 and earlier behaves really odd when
4580 // we don't create native caret. Therefore, we need to create native caret
4581 // only when ATOK 2011 - 2015 is active (i.e., not necessary for ATOK 2016).
4582 // However, if a11y module is handling native caret, we shouldn't touch it.
4583 // Note that ATOK must require the latest information of the caret. So,
4584 // even if we'll create native caret later, we need to creat it here with
4585 // current information.
4586 if (!IMEHandler::IsA11yHandlingNativeCaret() &&
4587 StaticPrefs::intl_tsf_hack_atok_create_native_caret() &&
4588 TSFStaticSink::IsATOKReferringNativeCaretActive() &&
4589 mComposition.isSome() &&
4590 mComposition->IsOffsetInRangeOrEndOffset(acpStart) &&
4591 mComposition->IsOffsetInRangeOrEndOffset(acpEnd)) {
4592 CreateNativeCaret();
4595 MOZ_LOG(gIMELog, LogLevel::Info,
4596 ("0x%p TSFTextStore::GetTextExt() succeeded: "
4597 "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }, *pfClipped=%s",
4598 this, prc->left, prc->top, prc->right, prc->bottom,
4599 GetBoolName(*pfClipped)));
4601 return S_OK;
4604 bool TSFTextStore::MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd) {
4605 // When ITextStoreACP::GetTextExt() returns TS_E_NOLAYOUT, TSF returns E_FAIL
4606 // to its caller (typically, active TIP). Then, most TIPs abort current job
4607 // or treat such application as non-GUI apps. E.g., some of them give up
4608 // showing candidate window, some others show candidate window at top-left of
4609 // the screen. For avoiding this issue, when there is composition (until
4610 // composition is actually committed in remote content), we should not
4611 // return TS_E_NOLAYOUT error for TIPs whose some features are broken by
4612 // this issue.
4613 // Note that ideally, this issue should be avoided by each TIP since this
4614 // won't be fixed at least on non-latest Windows. Actually, Google Japanese
4615 // Input (based on Mozc) does it. When GetTextExt() returns E_FAIL, TIPs
4616 // should try to check result of GetRangeFromPoint() because TSF returns
4617 // TS_E_NOLAYOUT correctly in this case. See:
4618 // https://github.com/google/mozc/blob/6b878e31fb6ac4347dc9dfd8ccc1080fe718479f/src/win32/tip/tip_range_util.cc#L237-L257
4620 if (!IsHandlingCompositionInContent() || mContentForTSF.isNothing() ||
4621 !mContentForTSF->HasOrHadComposition() ||
4622 !mContentForTSF->IsLayoutChangedAt(aACPEnd)) {
4623 return false;
4626 MOZ_ASSERT(mComposition.isNothing() ||
4627 mComposition->StartOffset() ==
4628 mContentForTSF->LatestCompositionRange()->StartOffset());
4629 MOZ_ASSERT(mComposition.isNothing() ||
4630 mComposition->EndOffset() ==
4631 mContentForTSF->LatestCompositionRange()->EndOffset());
4633 // If TSF does not have the bug, we need to hack only with a few TIPs.
4634 static const bool sAlllowToStopHackingIfFine =
4635 IsWindows10BuildOrLater(17643) &&
4636 StaticPrefs::
4637 intl_tsf_hack_allow_to_stop_hacking_on_build_17643_or_later();
4639 // We need to compute active TIP now. This may take a couple of milliseconds,
4640 // however, it'll be cached, so, must be faster than check active TIP every
4641 // GetTextExt() calls.
4642 const Maybe<Selection>& selectionForTSF = SelectionForTSF();
4643 switch (TSFStaticSink::ActiveTIP()) {
4644 // MS IME for Japanese doesn't support asynchronous handling at deciding
4645 // its suggest list window position. The feature was implemented
4646 // starting from Windows 8. And also we may meet same trouble in e10s
4647 // mode on Win7. So, we should never return TS_E_NOLAYOUT to MS IME for
4648 // Japanese.
4649 case TextInputProcessorID::eMicrosoftIMEForJapanese:
4650 // Basically, MS-IME tries to retrieve whole composition string rect
4651 // at deciding suggest window immediately after unlocking the document.
4652 // However, in e10s mode, the content hasn't updated yet in most cases.
4653 // Therefore, if the first character at the retrieving range rect is
4654 // available, we should use it as the result.
4655 // Note that according to bug 1609675, MS-IME for Japanese itself does
4656 // not handle TS_E_NOLAYOUT correctly at least on Build 18363.657 (1909).
4657 if (StaticPrefs::
4658 intl_tsf_hack_ms_japanese_ime_do_not_return_no_layout_error_at_first_char() &&
4659 aACPStart < aACPEnd) {
4660 aACPEnd = aACPStart;
4661 break;
4663 if (sAlllowToStopHackingIfFine) {
4664 return false;
4666 // Although, the condition is not clear, MS-IME sometimes retrieves the
4667 // caret rect immediately after modifying the composition string but
4668 // before unlocking the document. In such case, we should return the
4669 // nearest character rect.
4670 // (Let's return true if there is no selection which must be not expected
4671 // by MS-IME nor TSF.)
4672 if (StaticPrefs::
4673 intl_tsf_hack_ms_japanese_ime_do_not_return_no_layout_error_at_caret() &&
4674 aACPStart == aACPEnd && selectionForTSF.isSome() &&
4675 (!selectionForTSF->HasRange() ||
4676 (selectionForTSF->Collapsed() &&
4677 selectionForTSF->EndOffset() == aACPEnd))) {
4678 int32_t minOffsetOfLayoutChanged =
4679 static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
4680 aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
4681 } else {
4682 return false;
4684 break;
4685 // The bug of Microsoft Office IME 2010 for Japanese is similar to
4686 // MS-IME for Win 8.1 and Win 10. Newer version of MS Office IME is not
4687 // released yet. So, we can hack it without prefs because there must be
4688 // no developers who want to disable this hack for tests.
4689 // XXX We have not tested with Microsoft Office IME 2010 since it's
4690 // installable only with Win7 and Win8 (i.e., cannot install Win8.1
4691 // and Win10), and requires upgrade to Win10.
4692 case TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese:
4693 // Basically, MS-IME tries to retrieve whole composition string rect
4694 // at deciding suggest window immediately after unlocking the document.
4695 // However, in e10s mode, the content hasn't updated yet in most cases.
4696 // Therefore, if the first character at the retrieving range rect is
4697 // available, we should use it as the result.
4698 if (aACPStart < aACPEnd) {
4699 aACPEnd = aACPStart;
4701 // Although, the condition is not clear, MS-IME sometimes retrieves the
4702 // caret rect immediately after modifying the composition string but
4703 // before unlocking the document. In such case, we should return the
4704 // nearest character rect.
4705 // (Let's return true if there is no selection which must be not expected
4706 // by MS-IME nor TSF.)
4707 else if (aACPStart == aACPEnd && selectionForTSF.isSome() &&
4708 (!selectionForTSF->HasRange() ||
4709 (selectionForTSF->Collapsed() &&
4710 selectionForTSF->EndOffset() == aACPEnd))) {
4711 int32_t minOffsetOfLayoutChanged =
4712 static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
4713 aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
4714 } else {
4715 return false;
4717 break;
4718 // ATOK fails to handle TS_E_NOLAYOUT only when it decides the position of
4719 // suggest window. In such case, ATOK tries to query rect of whole or a
4720 // part of composition string.
4721 // FYI: ATOK changes their implementation around candidate window and
4722 // suggest widget at ATOK 2016. Therefore, there are some differences
4723 // ATOK 2015 (or older) and ATOK 2016 (or newer).
4724 // FYI: ATOK 2017 stops referring our window class name. I.e., ATOK 2016
4725 // and older may behave differently only on Gecko but this must be
4726 // finished from ATOK 2017.
4727 // FYI: For testing with legacy ATOK, we should hack it even if current ATOK
4728 // refers native caret rect on windows whose window class is one of
4729 // Mozilla window classes and we stop creating native caret for ATOK
4730 // because creating native caret causes ATOK refers caret position
4731 // when GetTextExt() returns TS_E_NOLAYOUT.
4732 case TextInputProcessorID::eATOK2011:
4733 case TextInputProcessorID::eATOK2012:
4734 case TextInputProcessorID::eATOK2013:
4735 case TextInputProcessorID::eATOK2014:
4736 case TextInputProcessorID::eATOK2015:
4737 // ATOK 2016 and later may temporarily show candidate window at odd
4738 // position when you convert a word quickly (e.g., keep pressing
4739 // space bar). So, on ATOK 2016 or later, we need to keep hacking the
4740 // result of GetTextExt().
4741 if (sAlllowToStopHackingIfFine) {
4742 return false;
4744 // If we'll create native caret where we paint our caret. Then, ATOK
4745 // will refer native caret. So, we don't need to hack anything in
4746 // this case.
4747 if (StaticPrefs::intl_tsf_hack_atok_create_native_caret()) {
4748 MOZ_ASSERT(TSFStaticSink::IsATOKReferringNativeCaretActive());
4749 return false;
4751 [[fallthrough]];
4752 case TextInputProcessorID::eATOK2016:
4753 case TextInputProcessorID::eATOKUnknown:
4754 if (!StaticPrefs::
4755 intl_tsf_hack_atok_do_not_return_no_layout_error_of_composition_string()) {
4756 return false;
4758 // If the range is in the composition string, we should return rectangle
4759 // in it as far as possible.
4760 if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4761 aACPStart) ||
4762 !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4763 aACPEnd)) {
4764 return false;
4766 break;
4767 // Japanist 10 fails to handle TS_E_NOLAYOUT when it decides the position
4768 // of candidate window. In such case, Japanist shows candidate window at
4769 // top-left of the screen. So, we should return the nearest caret rect
4770 // where we know. This is Japanist's bug. So, even after build 17643,
4771 // we need this hack.
4772 case TextInputProcessorID::eJapanist10:
4773 if (!StaticPrefs::
4774 intl_tsf_hack_japanist10_do_not_return_no_layout_error_of_composition_string()) {
4775 return false;
4777 if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4778 aACPStart) ||
4779 !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4780 aACPEnd)) {
4781 return false;
4783 break;
4784 // Free ChangJie 2010 doesn't handle ITfContextView::GetTextExt() properly.
4785 // This must be caused by the bug of TSF since Free ChangJie works fine on
4786 // build 17643 and later.
4787 case TextInputProcessorID::eFreeChangJie:
4788 if (sAlllowToStopHackingIfFine) {
4789 return false;
4791 if (!StaticPrefs::
4792 intl_tsf_hack_free_chang_jie_do_not_return_no_layout_error()) {
4793 return false;
4795 aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
4796 aACPStart = std::min(aACPStart, aACPEnd);
4797 break;
4798 // Some Traditional Chinese TIPs of Microsoft don't show candidate window
4799 // in e10s mode on Win8 or later.
4800 case TextInputProcessorID::eMicrosoftQuick:
4801 if (sAlllowToStopHackingIfFine) {
4802 return false; // MS Quick works fine with Win10 build 17643.
4804 [[fallthrough]];
4805 case TextInputProcessorID::eMicrosoftChangJie:
4806 if (!StaticPrefs::
4807 intl_tsf_hack_ms_traditional_chinese_do_not_return_no_layout_error()) {
4808 return false;
4810 aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
4811 aACPStart = std::min(aACPStart, aACPEnd);
4812 break;
4813 // Some Simplified Chinese TIPs of Microsoft don't show candidate window
4814 // in e10s mode on Win8 or later.
4815 // FYI: Only Simplified Chinese TIPs of Microsoft still require this hack
4816 // because they sometimes do not show candidate window when we return
4817 // TS_E_NOLAYOUT for first query. Note that even when they show
4818 // candidate window properly, we return TS_E_NOLAYOUT and following
4819 // log looks same as when they don't show candidate window. Perhaps,
4820 // there is stateful cause or race in them.
4821 case TextInputProcessorID::eMicrosoftPinyin:
4822 case TextInputProcessorID::eMicrosoftWubi:
4823 if (!StaticPrefs::
4824 intl_tsf_hack_ms_simplified_chinese_do_not_return_no_layout_error()) {
4825 return false;
4827 aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
4828 aACPStart = std::min(aACPStart, aACPEnd);
4829 break;
4830 default:
4831 return false;
4834 // If we hack the queried range for active TIP, that means we should not
4835 // return TS_E_NOLAYOUT even if hacked offset is still modified. So, as
4836 // far as possible, we should adjust the offset.
4837 MOZ_ASSERT(mContentForTSF->IsLayoutChanged());
4838 bool collapsed = aACPStart == aACPEnd;
4839 // Note that even if all characters in the editor or the composition
4840 // string was modified, 0 or start offset of the composition string is
4841 // useful because it may return caret rect or old character's rect which
4842 // the user still see. That must be useful information for TIP.
4843 int32_t firstModifiedOffset =
4844 static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
4845 LONG lastUnmodifiedOffset = std::max(firstModifiedOffset - 1, 0);
4846 if (mContentForTSF->IsLayoutChangedAt(aACPStart)) {
4847 if (aACPStart >= mContentForTSF->LatestCompositionRange()->StartOffset()) {
4848 // If mContentForTSF has last composition string and current
4849 // composition string, we can assume that ContentCacheInParent has
4850 // cached rects of composition string at least length of current
4851 // composition string. Otherwise, we can assume that rect for
4852 // first character of composition string is stored since it was
4853 // selection start or caret position.
4854 LONG maxCachedOffset =
4855 mContentForTSF->LatestCompositionRange()->EndOffset();
4856 if (mContentForTSF->LastComposition().isSome()) {
4857 maxCachedOffset = std::min(
4858 maxCachedOffset, mContentForTSF->LastComposition()->EndOffset());
4860 aACPStart = std::min(aACPStart, maxCachedOffset);
4862 // Otherwise, we don't know which character rects are cached. So, we
4863 // need to use first unmodified character's rect in this case. Even
4864 // if there is no character, the query event will return caret rect
4865 // instead.
4866 else {
4867 aACPStart = lastUnmodifiedOffset;
4869 MOZ_ASSERT(aACPStart <= aACPEnd);
4872 // If TIP requests caret rect with collapsed range, we should keep
4873 // collapsing the range.
4874 if (collapsed) {
4875 aACPEnd = aACPStart;
4877 // Let's set aACPEnd to larger offset of last unmodified offset or
4878 // aACPStart which may be the first character offset of the composition
4879 // string. However, some TIPs may want to know the right edge of the
4880 // range. Therefore, if aACPEnd is in composition string and active TIP
4881 // doesn't retrieve caret rect (i.e., the range isn't collapsed), we
4882 // should keep using the original aACPEnd. Otherwise, we should set
4883 // aACPEnd to larger value of aACPStart and lastUnmodifiedOffset.
4884 else if (mContentForTSF->IsLayoutChangedAt(aACPEnd) &&
4885 !mContentForTSF->LatestCompositionRange()
4886 ->IsOffsetInRangeOrEndOffset(aACPEnd)) {
4887 aACPEnd = std::max(aACPStart, lastUnmodifiedOffset);
4890 MOZ_LOG(
4891 gIMELog, LogLevel::Debug,
4892 ("0x%p TSFTextStore::HackNoErrorLayoutBugs() hacked the queried range "
4893 "for not returning TS_E_NOLAYOUT, new values are: "
4894 "aACPStart=%ld, aACPEnd=%ld",
4895 this, aACPStart, aACPEnd));
4897 return true;
4900 STDMETHODIMP
4901 TSFTextStore::GetScreenExt(TsViewCookie vcView, RECT* prc) {
4902 MOZ_LOG(gIMELog, LogLevel::Info,
4903 ("0x%p TSFTextStore::GetScreenExt(vcView=%ld, prc=0x%p)", this,
4904 vcView, prc));
4906 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4907 MOZ_LOG(gIMELog, LogLevel::Error,
4908 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4909 "called with invalid view",
4910 this));
4911 return E_INVALIDARG;
4914 if (!prc) {
4915 MOZ_LOG(gIMELog, LogLevel::Error,
4916 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4917 "null argument",
4918 this));
4919 return E_INVALIDARG;
4922 if (mDestroyed) {
4923 MOZ_LOG(gIMELog, LogLevel::Error,
4924 ("0x%p TSFTextStore::GetScreenExt() returns empty rect "
4925 "due to already destroyed",
4926 this));
4927 prc->left = prc->top = prc->right = prc->bottom = 0;
4928 return S_OK;
4931 if (!GetScreenExtInternal(*prc)) {
4932 MOZ_LOG(gIMELog, LogLevel::Error,
4933 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4934 "GetScreenExtInternal() failure",
4935 this));
4936 return E_FAIL;
4939 MOZ_LOG(gIMELog, LogLevel::Info,
4940 ("0x%p TSFTextStore::GetScreenExt() succeeded: "
4941 "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
4942 this, prc->left, prc->top, prc->right, prc->bottom));
4943 return S_OK;
4946 bool TSFTextStore::GetScreenExtInternal(RECT& aScreenExt) {
4947 MOZ_LOG(gIMELog, LogLevel::Debug,
4948 ("0x%p TSFTextStore::GetScreenExtInternal()", this));
4950 MOZ_ASSERT(!mDestroyed);
4952 // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
4953 WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, mWidget);
4954 mWidget->InitEvent(queryEditorRectEvent);
4955 DispatchEvent(queryEditorRectEvent);
4956 if (queryEditorRectEvent.Failed()) {
4957 MOZ_LOG(gIMELog, LogLevel::Error,
4958 ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
4959 "eQueryEditorRect failure",
4960 this));
4961 return false;
4964 nsWindow* refWindow =
4965 static_cast<nsWindow*>(!!queryEditorRectEvent.mReply->mFocusedWidget
4966 ? queryEditorRectEvent.mReply->mFocusedWidget
4967 : static_cast<nsIWidget*>(mWidget.get()));
4968 // Result rect is in top level widget coordinates
4969 refWindow = refWindow->GetTopLevelWindow(false);
4970 if (!refWindow) {
4971 MOZ_LOG(gIMELog, LogLevel::Error,
4972 ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
4973 "no top level window",
4974 this));
4975 return false;
4978 LayoutDeviceIntRect boundRect = refWindow->GetClientBounds();
4979 boundRect.MoveTo(0, 0);
4981 // Clip frame rect to window rect
4982 boundRect.IntersectRect(queryEditorRectEvent.mReply->mRect, boundRect);
4983 if (!boundRect.IsEmpty()) {
4984 boundRect.MoveBy(refWindow->WidgetToScreenOffset());
4985 ::SetRect(&aScreenExt, boundRect.X(), boundRect.Y(), boundRect.XMost(),
4986 boundRect.YMost());
4987 } else {
4988 ::SetRectEmpty(&aScreenExt);
4991 MOZ_LOG(gIMELog, LogLevel::Debug,
4992 ("0x%p TSFTextStore::GetScreenExtInternal() succeeded: "
4993 "aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
4994 this, aScreenExt.left, aScreenExt.top, aScreenExt.right,
4995 aScreenExt.bottom));
4996 return true;
4999 STDMETHODIMP
5000 TSFTextStore::GetWnd(TsViewCookie vcView, HWND* phwnd) {
5001 MOZ_LOG(gIMELog, LogLevel::Info,
5002 ("0x%p TSFTextStore::GetWnd(vcView=%ld, phwnd=0x%p), "
5003 "mWidget=0x%p",
5004 this, vcView, phwnd, mWidget.get()));
5006 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
5007 MOZ_LOG(gIMELog, LogLevel::Error,
5008 ("0x%p TSFTextStore::GetWnd() FAILED due to "
5009 "called with invalid view",
5010 this));
5011 return E_INVALIDARG;
5014 if (!phwnd) {
5015 MOZ_LOG(gIMELog, LogLevel::Error,
5016 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
5017 "null argument",
5018 this));
5019 return E_INVALIDARG;
5022 *phwnd = mWidget ? mWidget->GetWindowHandle() : nullptr;
5024 MOZ_LOG(gIMELog, LogLevel::Info,
5025 ("0x%p TSFTextStore::GetWnd() succeeded: *phwnd=0x%p", this,
5026 static_cast<void*>(*phwnd)));
5027 return S_OK;
5030 STDMETHODIMP
5031 TSFTextStore::InsertTextAtSelection(DWORD dwFlags, const WCHAR* pchText,
5032 ULONG cch, LONG* pacpStart, LONG* pacpEnd,
5033 TS_TEXTCHANGE* pChange) {
5034 MOZ_LOG(
5035 gIMELog, LogLevel::Info,
5036 ("0x%p TSFTextStore::InsertTextAtSelection(dwFlags=%s, "
5037 "pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, "
5038 "pChange=0x%p), mComposition=%s",
5039 this,
5040 dwFlags == 0 ? "0"
5041 : dwFlags == TF_IAS_NOQUERY ? "TF_IAS_NOQUERY"
5042 : dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY"
5043 : "Unknown",
5044 pchText, pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "",
5045 cch, pacpStart, pacpEnd, pChange, ToString(mComposition).c_str()));
5047 if (cch && !pchText) {
5048 MOZ_LOG(gIMELog, LogLevel::Error,
5049 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5050 "null pchText",
5051 this));
5052 return E_INVALIDARG;
5055 if (TS_IAS_QUERYONLY == dwFlags) {
5056 if (!IsReadLocked()) {
5057 MOZ_LOG(gIMELog, LogLevel::Error,
5058 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5059 "not locked (read)",
5060 this));
5061 return TS_E_NOLOCK;
5064 if (!pacpStart || !pacpEnd) {
5065 MOZ_LOG(gIMELog, LogLevel::Error,
5066 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5067 "null argument",
5068 this));
5069 return E_INVALIDARG;
5072 // Get selection first
5073 Maybe<Selection>& selectionForTSF = SelectionForTSF();
5074 if (selectionForTSF.isNothing()) {
5075 MOZ_LOG(gIMELog, LogLevel::Error,
5076 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5077 "SelectionForTSF() failure",
5078 this));
5079 return E_FAIL;
5082 // Simulate text insertion
5083 if (selectionForTSF->HasRange()) {
5084 *pacpStart = selectionForTSF->StartOffset();
5085 *pacpEnd = selectionForTSF->EndOffset();
5086 if (pChange) {
5087 *pChange = TS_TEXTCHANGE{.acpStart = selectionForTSF->StartOffset(),
5088 .acpOldEnd = selectionForTSF->EndOffset(),
5089 .acpNewEnd = selectionForTSF->StartOffset() +
5090 static_cast<LONG>(cch)};
5092 } else {
5093 // There is no error code to return "no selection" state from this method.
5094 // This means that TSF/TIP should check `GetSelection` result first and
5095 // stop using this. However, this could be called by TIP/TSF if they do
5096 // not do so. Therefore, we should use start of editor instead, but
5097 // notify the caller of nothing will be inserted with pChange->acpNewEnd.
5098 *pacpStart = *pacpEnd = 0;
5099 if (pChange) {
5100 *pChange = TS_TEXTCHANGE{.acpStart = 0, .acpOldEnd = 0, .acpNewEnd = 0};
5103 } else {
5104 if (!IsReadWriteLocked()) {
5105 MOZ_LOG(gIMELog, LogLevel::Error,
5106 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5107 "not locked (read-write)",
5108 this));
5109 return TS_E_NOLOCK;
5112 if (!pChange) {
5113 MOZ_LOG(gIMELog, LogLevel::Error,
5114 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5115 "null pChange",
5116 this));
5117 return E_INVALIDARG;
5120 if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) {
5121 MOZ_LOG(gIMELog, LogLevel::Error,
5122 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5123 "null argument",
5124 this));
5125 return E_INVALIDARG;
5128 if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
5129 pChange)) {
5130 MOZ_LOG(gIMELog, LogLevel::Error,
5131 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5132 "InsertTextAtSelectionInternal() failure",
5133 this));
5134 return E_FAIL;
5137 if (TS_IAS_NOQUERY != dwFlags) {
5138 *pacpStart = pChange->acpStart;
5139 *pacpEnd = pChange->acpNewEnd;
5142 MOZ_LOG(gIMELog, LogLevel::Info,
5143 ("0x%p TSFTextStore::InsertTextAtSelection() succeeded: "
5144 "*pacpStart=%ld, *pacpEnd=%ld, "
5145 "*pChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld })",
5146 this, pacpStart ? *pacpStart : 0, pacpEnd ? *pacpEnd : 0,
5147 pChange ? pChange->acpStart : 0, pChange ? pChange->acpOldEnd : 0,
5148 pChange ? pChange->acpNewEnd : 0));
5149 return S_OK;
5152 bool TSFTextStore::InsertTextAtSelectionInternal(const nsAString& aInsertStr,
5153 TS_TEXTCHANGE* aTextChange) {
5154 MOZ_LOG(gIMELog, LogLevel::Debug,
5155 ("0x%p TSFTextStore::InsertTextAtSelectionInternal("
5156 "aInsertStr=\"%s\", aTextChange=0x%p), mComposition=%s",
5157 this, GetEscapedUTF8String(aInsertStr).get(), aTextChange,
5158 ToString(mComposition).c_str()));
5160 Maybe<Content>& contentForTSF = ContentForTSF();
5161 if (contentForTSF.isNothing()) {
5162 MOZ_LOG(gIMELog, LogLevel::Error,
5163 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() failed "
5164 "due to ContentForTSF() failure()",
5165 this));
5166 return false;
5169 MaybeDispatchKeyboardEventAsProcessedByIME();
5170 if (mDestroyed) {
5171 MOZ_LOG(
5172 gIMELog, LogLevel::Error,
5173 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() FAILED due to "
5174 "destroyed during dispatching a keyboard event",
5175 this));
5176 return false;
5179 TS_SELECTION_ACP oldSelection = contentForTSF->Selection()->ACPRef();
5180 if (mComposition.isNothing()) {
5181 // Use a temporary composition to contain the text
5182 PendingAction* compositionStart = mPendingActions.AppendElements(2);
5183 PendingAction* compositionEnd = compositionStart + 1;
5185 compositionStart->mType = PendingAction::Type::eCompositionStart;
5186 compositionStart->mSelectionStart = oldSelection.acpStart;
5187 compositionStart->mSelectionLength =
5188 oldSelection.acpEnd - oldSelection.acpStart;
5189 compositionStart->mAdjustSelection = false;
5191 compositionEnd->mType = PendingAction::Type::eCompositionEnd;
5192 compositionEnd->mData = aInsertStr;
5193 compositionEnd->mSelectionStart = compositionStart->mSelectionStart;
5195 MOZ_LOG(gIMELog, LogLevel::Debug,
5196 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
5197 "appending pending compositionstart and compositionend... "
5198 "PendingCompositionStart={ mSelectionStart=%ld, "
5199 "mSelectionLength=%ld }, PendingCompositionEnd={ mData=\"%s\" "
5200 "(Length()=%zu), mSelectionStart=%ld }",
5201 this, compositionStart->mSelectionStart,
5202 compositionStart->mSelectionLength,
5203 GetEscapedUTF8String(compositionEnd->mData).get(),
5204 compositionEnd->mData.Length(), compositionEnd->mSelectionStart));
5207 contentForTSF->ReplaceSelectedTextWith(aInsertStr);
5209 if (aTextChange) {
5210 aTextChange->acpStart = oldSelection.acpStart;
5211 aTextChange->acpOldEnd = oldSelection.acpEnd;
5212 aTextChange->acpNewEnd = contentForTSF->Selection()->EndOffset();
5215 MOZ_LOG(
5216 gIMELog, LogLevel::Debug,
5217 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
5218 "succeeded: mWidget=0x%p, mWidget->Destroyed()=%s, aTextChange={ "
5219 "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
5220 this, mWidget.get(), GetBoolName(mWidget ? mWidget->Destroyed() : true),
5221 aTextChange ? aTextChange->acpStart : 0,
5222 aTextChange ? aTextChange->acpOldEnd : 0,
5223 aTextChange ? aTextChange->acpNewEnd : 0));
5224 return true;
5227 STDMETHODIMP
5228 TSFTextStore::InsertEmbeddedAtSelection(DWORD dwFlags, IDataObject* pDataObject,
5229 LONG* pacpStart, LONG* pacpEnd,
5230 TS_TEXTCHANGE* pChange) {
5231 MOZ_LOG(gIMELog, LogLevel::Info,
5232 ("0x%p TSFTextStore::InsertEmbeddedAtSelection() called "
5233 "but not supported (E_NOTIMPL)",
5234 this));
5236 // embedded objects are not supported
5237 return E_NOTIMPL;
5240 HRESULT TSFTextStore::RecordCompositionStartAction(
5241 ITfCompositionView* aCompositionView, ITfRange* aRange,
5242 bool aPreserveSelection) {
5243 MOZ_LOG(gIMELog, LogLevel::Debug,
5244 ("0x%p TSFTextStore::RecordCompositionStartAction("
5245 "aCompositionView=0x%p, aRange=0x%p, aPreserveSelection=%s), "
5246 "mComposition=%s",
5247 this, aCompositionView, aRange, GetBoolName(aPreserveSelection),
5248 ToString(mComposition).c_str()));
5250 LONG start = 0, length = 0;
5251 HRESULT hr = GetRangeExtent(aRange, &start, &length);
5252 if (FAILED(hr)) {
5253 MOZ_LOG(gIMELog, LogLevel::Error,
5254 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5255 "due to GetRangeExtent() failure",
5256 this));
5257 return hr;
5260 return RecordCompositionStartAction(aCompositionView, start, length,
5261 aPreserveSelection);
5264 HRESULT TSFTextStore::RecordCompositionStartAction(
5265 ITfCompositionView* aCompositionView, LONG aStart, LONG aLength,
5266 bool aPreserveSelection) {
5267 MOZ_LOG(gIMELog, LogLevel::Debug,
5268 ("0x%p TSFTextStore::RecordCompositionStartAction("
5269 "aCompositionView=0x%p, aStart=%ld, aLength=%ld, "
5270 "aPreserveSelection=%s), "
5271 "mComposition=%s",
5272 this, aCompositionView, aStart, aLength,
5273 GetBoolName(aPreserveSelection), ToString(mComposition).c_str()));
5275 Maybe<Content>& contentForTSF = ContentForTSF();
5276 if (contentForTSF.isNothing()) {
5277 MOZ_LOG(gIMELog, LogLevel::Error,
5278 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5279 "due to ContentForTSF() failure",
5280 this));
5281 return E_FAIL;
5284 MaybeDispatchKeyboardEventAsProcessedByIME();
5285 if (mDestroyed) {
5286 MOZ_LOG(
5287 gIMELog, LogLevel::Error,
5288 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED due to "
5289 "destroyed during dispatching a keyboard event",
5290 this));
5291 return false;
5294 CompleteLastActionIfStillIncomplete();
5296 // TIP may have inserted text at selection before calling
5297 // OnStartComposition(). In this case, we've already created a pending
5298 // compositionend. If new composition replaces all commit string of the
5299 // pending compositionend, we should cancel the pending compositionend and
5300 // keep the previous composition normally.
5301 // On Windows 7, MS-IME for Korean, MS-IME 2010 for Korean and MS Old Hangul
5302 // may start composition with calling InsertTextAtSelection() and
5303 // OnStartComposition() with this order (bug 1208043).
5304 // On Windows 10, MS Pinyin, MS Wubi, MS ChangJie and MS Quick commits
5305 // last character and replace it with empty string with new composition
5306 // when user removes last character of composition string with Backspace
5307 // key (bug 1462257).
5308 if (!aPreserveSelection &&
5309 IsLastPendingActionCompositionEndAt(aStart, aLength)) {
5310 const PendingAction& pendingCompositionEnd = mPendingActions.LastElement();
5311 contentForTSF->RestoreCommittedComposition(aCompositionView,
5312 pendingCompositionEnd);
5313 mPendingActions.RemoveLastElement();
5314 MOZ_LOG(gIMELog, LogLevel::Info,
5315 ("0x%p TSFTextStore::RecordCompositionStartAction() "
5316 "succeeded: restoring the committed string as composing string, "
5317 "mComposition=%s, mSelectionForTSF=%s",
5318 this, ToString(mComposition).c_str(),
5319 ToString(mSelectionForTSF).c_str()));
5320 return S_OK;
5323 PendingAction* action = mPendingActions.AppendElement();
5324 action->mType = PendingAction::Type::eCompositionStart;
5325 action->mSelectionStart = aStart;
5326 action->mSelectionLength = aLength;
5328 Maybe<Selection>& selectionForTSF = SelectionForTSF();
5329 if (selectionForTSF.isNothing()) {
5330 MOZ_LOG(gIMELog, LogLevel::Error,
5331 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5332 "due to SelectionForTSF() failure",
5333 this));
5334 action->mAdjustSelection = true;
5335 } else if (!selectionForTSF->HasRange()) {
5336 // If there is no selection, let's collapse seletion to the insertion point.
5337 action->mAdjustSelection = true;
5338 } else if (selectionForTSF->MinOffset() != aStart ||
5339 selectionForTSF->MaxOffset() != aStart + aLength) {
5340 // If new composition range is different from current selection range,
5341 // we need to set selection before dispatching compositionstart event.
5342 action->mAdjustSelection = true;
5343 } else {
5344 // We shouldn't dispatch selection set event before dispatching
5345 // compositionstart event because it may cause put caret different
5346 // position in HTML editor since generated flat text content and offset in
5347 // it are lossy data of HTML contents.
5348 action->mAdjustSelection = false;
5351 contentForTSF->StartComposition(aCompositionView, *action,
5352 aPreserveSelection);
5353 MOZ_ASSERT(mComposition.isSome());
5354 action->mData = mComposition->DataRef();
5356 MOZ_LOG(gIMELog, LogLevel::Info,
5357 ("0x%p TSFTextStore::RecordCompositionStartAction() succeeded: "
5358 "mComposition=%s, mSelectionForTSF=%s }",
5359 this, ToString(mComposition).c_str(),
5360 ToString(mSelectionForTSF).c_str()));
5361 return S_OK;
5364 HRESULT
5365 TSFTextStore::RecordCompositionEndAction() {
5366 MOZ_LOG(gIMELog, LogLevel::Debug,
5367 ("0x%p TSFTextStore::RecordCompositionEndAction(), "
5368 "mComposition=%s",
5369 this, ToString(mComposition).c_str()));
5371 MOZ_ASSERT(mComposition.isSome());
5373 if (mComposition.isNothing()) {
5374 MOZ_LOG(gIMELog, LogLevel::Error,
5375 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
5376 "no composition",
5377 this));
5378 return false;
5381 MaybeDispatchKeyboardEventAsProcessedByIME();
5382 if (mDestroyed) {
5383 MOZ_LOG(gIMELog, LogLevel::Error,
5384 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
5385 "destroyed during dispatching a keyboard event",
5386 this));
5387 return false;
5390 // If we're handling incomplete composition update or already handled
5391 // composition update, we can forget them since composition end will send
5392 // the latest composition string and it overwrites the composition string
5393 // even if we dispatch eCompositionChange event before that. So, let's
5394 // forget all composition updates now.
5395 RemoveLastCompositionUpdateActions();
5396 PendingAction* action = mPendingActions.AppendElement();
5397 action->mType = PendingAction::Type::eCompositionEnd;
5398 action->mData = mComposition->DataRef();
5399 action->mSelectionStart = mComposition->StartOffset();
5401 Maybe<Content>& contentForTSF = ContentForTSF();
5402 if (contentForTSF.isNothing()) {
5403 MOZ_LOG(gIMELog, LogLevel::Error,
5404 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due "
5405 "to ContentForTSF() failure",
5406 this));
5407 return E_FAIL;
5409 contentForTSF->EndComposition(*action);
5411 // If this composition was restart but the composition doesn't modify
5412 // anything, we should remove the pending composition for preventing to
5413 // dispatch redundant composition events.
5414 for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) {
5415 PendingAction& pendingAction = mPendingActions[i - 1];
5416 if (pendingAction.mType == PendingAction::Type::eCompositionStart) {
5417 if (pendingAction.mData != action->mData) {
5418 break;
5420 // When only setting selection is necessary, we should append it.
5421 if (pendingAction.mAdjustSelection) {
5422 LONG selectionStart = pendingAction.mSelectionStart;
5423 LONG selectionLength = pendingAction.mSelectionLength;
5425 PendingAction* setSelection = mPendingActions.AppendElement();
5426 setSelection->mType = PendingAction::Type::eSetSelection;
5427 setSelection->mSelectionStart = selectionStart;
5428 setSelection->mSelectionLength = selectionLength;
5429 setSelection->mSelectionReversed = false;
5431 // Remove the redundant pending composition.
5432 mPendingActions.RemoveElementsAt(i - 1, j);
5433 MOZ_LOG(gIMELog, LogLevel::Info,
5434 ("0x%p TSFTextStore::RecordCompositionEndAction(), "
5435 "succeeded, but the composition was canceled due to redundant",
5436 this));
5437 return S_OK;
5441 MOZ_LOG(
5442 gIMELog, LogLevel::Info,
5443 ("0x%p TSFTextStore::RecordCompositionEndAction(), succeeded", this));
5444 return S_OK;
5447 STDMETHODIMP
5448 TSFTextStore::OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) {
5449 MOZ_LOG(gIMELog, LogLevel::Info,
5450 ("0x%p TSFTextStore::OnStartComposition(pComposition=0x%p, "
5451 "pfOk=0x%p), mComposition=%s",
5452 this, pComposition, pfOk, ToString(mComposition).c_str()));
5454 AutoPendingActionAndContentFlusher flusher(this);
5456 *pfOk = FALSE;
5458 // Only one composition at a time
5459 if (mComposition.isSome()) {
5460 MOZ_LOG(gIMELog, LogLevel::Error,
5461 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5462 "there is another composition already (but returns S_OK)",
5463 this));
5464 return S_OK;
5467 RefPtr<ITfRange> range;
5468 HRESULT hr = pComposition->GetRange(getter_AddRefs(range));
5469 if (FAILED(hr)) {
5470 MOZ_LOG(gIMELog, LogLevel::Error,
5471 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5472 "pComposition->GetRange() failure",
5473 this));
5474 return hr;
5476 hr = RecordCompositionStartAction(pComposition, range, false);
5477 if (FAILED(hr)) {
5478 MOZ_LOG(gIMELog, LogLevel::Error,
5479 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5480 "RecordCompositionStartAction() failure",
5481 this));
5482 return hr;
5485 *pfOk = TRUE;
5486 MOZ_LOG(gIMELog, LogLevel::Info,
5487 ("0x%p TSFTextStore::OnStartComposition() succeeded", this));
5488 return S_OK;
5491 STDMETHODIMP
5492 TSFTextStore::OnUpdateComposition(ITfCompositionView* pComposition,
5493 ITfRange* pRangeNew) {
5494 MOZ_LOG(gIMELog, LogLevel::Info,
5495 ("0x%p TSFTextStore::OnUpdateComposition(pComposition=0x%p, "
5496 "pRangeNew=0x%p), mComposition=%s",
5497 this, pComposition, pRangeNew, ToString(mComposition).c_str()));
5499 AutoPendingActionAndContentFlusher flusher(this);
5501 if (!mDocumentMgr || !mContext) {
5502 MOZ_LOG(gIMELog, LogLevel::Error,
5503 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5504 "not ready for the composition",
5505 this));
5506 return E_UNEXPECTED;
5508 if (mComposition.isNothing()) {
5509 MOZ_LOG(gIMELog, LogLevel::Error,
5510 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5511 "no active composition",
5512 this));
5513 return E_UNEXPECTED;
5515 if (mComposition->GetView() != pComposition) {
5516 MOZ_LOG(gIMELog, LogLevel::Error,
5517 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5518 "different composition view specified",
5519 this));
5520 return E_UNEXPECTED;
5523 // pRangeNew is null when the update is not complete
5524 if (!pRangeNew) {
5525 MaybeDispatchKeyboardEventAsProcessedByIME();
5526 if (mDestroyed) {
5527 MOZ_LOG(gIMELog, LogLevel::Error,
5528 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5529 "destroyed during dispatching a keyboard event",
5530 this));
5531 return E_FAIL;
5533 PendingAction* action = LastOrNewPendingCompositionUpdate();
5534 action->mIncomplete = true;
5535 MOZ_LOG(gIMELog, LogLevel::Info,
5536 ("0x%p TSFTextStore::OnUpdateComposition() succeeded but "
5537 "not complete",
5538 this));
5539 return S_OK;
5542 HRESULT hr = RestartCompositionIfNecessary(pRangeNew);
5543 if (FAILED(hr)) {
5544 MOZ_LOG(gIMELog, LogLevel::Error,
5545 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5546 "RestartCompositionIfNecessary() failure",
5547 this));
5548 return hr;
5551 hr = RecordCompositionUpdateAction();
5552 if (FAILED(hr)) {
5553 MOZ_LOG(gIMELog, LogLevel::Error,
5554 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5555 "RecordCompositionUpdateAction() failure",
5556 this));
5557 return hr;
5560 if (MOZ_LOG_TEST(gIMELog, LogLevel::Info)) {
5561 Maybe<Selection>& selectionForTSF = SelectionForTSF();
5562 if (selectionForTSF.isNothing()) {
5563 MOZ_LOG(gIMELog, LogLevel::Error,
5564 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5565 "SelectionForTSF() failure",
5566 this));
5567 return S_OK; // Don't return error only when we're logging.
5569 MOZ_LOG(gIMELog, LogLevel::Info,
5570 ("0x%p TSFTextStore::OnUpdateComposition() succeeded: "
5571 "mComposition=%s, SelectionForTSF()=%s",
5572 this, ToString(mComposition).c_str(),
5573 ToString(selectionForTSF).c_str()));
5575 return S_OK;
5578 STDMETHODIMP
5579 TSFTextStore::OnEndComposition(ITfCompositionView* pComposition) {
5580 MOZ_LOG(gIMELog, LogLevel::Info,
5581 ("0x%p TSFTextStore::OnEndComposition(pComposition=0x%p), "
5582 "mComposition=%s",
5583 this, pComposition, ToString(mComposition).c_str()));
5585 AutoPendingActionAndContentFlusher flusher(this);
5587 if (mComposition.isNothing()) {
5588 MOZ_LOG(gIMELog, LogLevel::Error,
5589 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5590 "no active composition",
5591 this));
5592 return E_UNEXPECTED;
5595 if (mComposition->GetView() != pComposition) {
5596 MOZ_LOG(gIMELog, LogLevel::Error,
5597 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5598 "different composition view specified",
5599 this));
5600 return E_UNEXPECTED;
5603 HRESULT hr = RecordCompositionEndAction();
5604 if (FAILED(hr)) {
5605 MOZ_LOG(gIMELog, LogLevel::Error,
5606 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5607 "RecordCompositionEndAction() failure",
5608 this));
5609 return hr;
5612 MOZ_LOG(gIMELog, LogLevel::Info,
5613 ("0x%p TSFTextStore::OnEndComposition(), succeeded", this));
5614 return S_OK;
5617 STDMETHODIMP
5618 TSFTextStore::AdviseMouseSink(ITfRangeACP* range, ITfMouseSink* pSink,
5619 DWORD* pdwCookie) {
5620 MOZ_LOG(gIMELog, LogLevel::Info,
5621 ("0x%p TSFTextStore::AdviseMouseSink(range=0x%p, pSink=0x%p, "
5622 "pdwCookie=0x%p)",
5623 this, range, pSink, pdwCookie));
5625 if (!pdwCookie) {
5626 MOZ_LOG(gIMELog, LogLevel::Error,
5627 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5628 "pdwCookie is null",
5629 this));
5630 return E_INVALIDARG;
5632 // Initialize the result with invalid cookie for safety.
5633 *pdwCookie = MouseTracker::kInvalidCookie;
5635 if (!range) {
5636 MOZ_LOG(gIMELog, LogLevel::Error,
5637 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5638 "range is null",
5639 this));
5640 return E_INVALIDARG;
5642 if (!pSink) {
5643 MOZ_LOG(gIMELog, LogLevel::Error,
5644 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5645 "pSink is null",
5646 this));
5647 return E_INVALIDARG;
5650 // Looking for an unusing tracker.
5651 MouseTracker* tracker = nullptr;
5652 for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
5653 if (mMouseTrackers[i].IsUsing()) {
5654 continue;
5656 tracker = &mMouseTrackers[i];
5658 // If there is no unusing tracker, create new one.
5659 // XXX Should we make limitation of the number of installs?
5660 if (!tracker) {
5661 tracker = mMouseTrackers.AppendElement();
5662 HRESULT hr = tracker->Init(this);
5663 if (FAILED(hr)) {
5664 MOZ_LOG(gIMELog, LogLevel::Error,
5665 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to "
5666 "failure of MouseTracker::Init()",
5667 this));
5668 return hr;
5671 HRESULT hr = tracker->AdviseSink(this, range, pSink);
5672 if (FAILED(hr)) {
5673 MOZ_LOG(gIMELog, LogLevel::Error,
5674 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to failure "
5675 "of MouseTracker::Init()",
5676 this));
5677 return hr;
5679 *pdwCookie = tracker->Cookie();
5680 MOZ_LOG(gIMELog, LogLevel::Info,
5681 ("0x%p TSFTextStore::AdviseMouseSink(), succeeded, "
5682 "*pdwCookie=%ld",
5683 this, *pdwCookie));
5684 return S_OK;
5687 STDMETHODIMP
5688 TSFTextStore::UnadviseMouseSink(DWORD dwCookie) {
5689 MOZ_LOG(
5690 gIMELog, LogLevel::Info,
5691 ("0x%p TSFTextStore::UnadviseMouseSink(dwCookie=%ld)", this, dwCookie));
5692 if (dwCookie == MouseTracker::kInvalidCookie) {
5693 MOZ_LOG(gIMELog, LogLevel::Error,
5694 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5695 "the cookie is invalid value",
5696 this));
5697 return E_INVALIDARG;
5699 // The cookie value must be an index of mMouseTrackers.
5700 // We can use this shortcut for now.
5701 if (static_cast<size_t>(dwCookie) >= mMouseTrackers.Length()) {
5702 MOZ_LOG(gIMELog, LogLevel::Error,
5703 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5704 "the cookie is too large value",
5705 this));
5706 return E_INVALIDARG;
5708 MouseTracker& tracker = mMouseTrackers[dwCookie];
5709 if (!tracker.IsUsing()) {
5710 MOZ_LOG(gIMELog, LogLevel::Error,
5711 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5712 "the found tracker uninstalled already",
5713 this));
5714 return E_INVALIDARG;
5716 tracker.UnadviseSink();
5717 MOZ_LOG(gIMELog, LogLevel::Info,
5718 ("0x%p TSFTextStore::UnadviseMouseSink(), succeeded", this));
5719 return S_OK;
5722 // static
5723 nsresult TSFTextStore::OnFocusChange(bool aGotFocus, nsWindow* aFocusedWidget,
5724 const InputContext& aContext) {
5725 MOZ_LOG(gIMELog, LogLevel::Debug,
5726 (" TSFTextStore::OnFocusChange(aGotFocus=%s, "
5727 "aFocusedWidget=0x%p, aContext=%s), "
5728 "sThreadMgr=0x%p, sEnabledTextStore=0x%p",
5729 GetBoolName(aGotFocus), aFocusedWidget,
5730 mozilla::ToString(aContext).c_str(), sThreadMgr.get(),
5731 sEnabledTextStore.get()));
5733 if (NS_WARN_IF(!IsInTSFMode())) {
5734 return NS_ERROR_NOT_AVAILABLE;
5737 RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
5738 bool hasFocus = ThinksHavingFocus();
5739 RefPtr<TSFTextStore> oldTextStore = sEnabledTextStore.forget();
5741 // If currently oldTextStore still has focus, notifies TSF of losing focus.
5742 if (hasFocus) {
5743 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5744 DebugOnly<HRESULT> hr = threadMgr->AssociateFocus(
5745 oldTextStore->mWidget->GetWindowHandle(), nullptr,
5746 getter_AddRefs(prevFocusedDocumentMgr));
5747 NS_ASSERTION(SUCCEEDED(hr), "Disassociating focus failed");
5748 NS_ASSERTION(prevFocusedDocumentMgr == oldTextStore->mDocumentMgr,
5749 "different documentMgr has been associated with the window");
5752 // Even if there was a focused TextStore, we won't use it with new focused
5753 // editor. So, release it now.
5754 if (oldTextStore) {
5755 oldTextStore->Destroy();
5758 if (NS_WARN_IF(!sThreadMgr)) {
5759 MOZ_LOG(gIMELog, LogLevel::Error,
5760 (" TSFTextStore::OnFocusChange() FAILED, due to "
5761 "sThreadMgr being destroyed during calling "
5762 "ITfThreadMgr::AssociateFocus()"));
5763 return NS_ERROR_FAILURE;
5765 if (NS_WARN_IF(sEnabledTextStore)) {
5766 MOZ_LOG(
5767 gIMELog, LogLevel::Error,
5768 (" TSFTextStore::OnFocusChange() FAILED, due to "
5769 "nested event handling has created another focused TextStore during "
5770 "calling ITfThreadMgr::AssociateFocus()"));
5771 return NS_ERROR_FAILURE;
5774 // If this is a notification of blur, move focus to the dummy document
5775 // manager.
5776 if (!aGotFocus || !aContext.mIMEState.IsEditable()) {
5777 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5778 RefPtr<ITfDocumentMgr> disabledDocumentMgr = sDisabledDocumentMgr;
5779 HRESULT hr = threadMgr->SetFocus(disabledDocumentMgr);
5780 if (NS_WARN_IF(FAILED(hr))) {
5781 MOZ_LOG(gIMELog, LogLevel::Error,
5782 (" TSFTextStore::OnFocusChange() FAILED due to "
5783 "ITfThreadMgr::SetFocus() failure"));
5784 return NS_ERROR_FAILURE;
5786 return NS_OK;
5789 // If an editor is getting focus, create new TextStore and set focus.
5790 if (NS_WARN_IF(!CreateAndSetFocus(aFocusedWidget, aContext))) {
5791 MOZ_LOG(gIMELog, LogLevel::Error,
5792 (" TSFTextStore::OnFocusChange() FAILED due to "
5793 "ITfThreadMgr::CreateAndSetFocus() failure"));
5794 return NS_ERROR_FAILURE;
5796 return NS_OK;
5799 // static
5800 void TSFTextStore::EnsureToDestroyAndReleaseEnabledTextStoreIf(
5801 RefPtr<TSFTextStore>& aTextStore) {
5802 aTextStore->Destroy();
5803 if (sEnabledTextStore == aTextStore) {
5804 sEnabledTextStore = nullptr;
5806 aTextStore = nullptr;
5809 // static
5810 bool TSFTextStore::CreateAndSetFocus(nsWindow* aFocusedWidget,
5811 const InputContext& aContext) {
5812 // TSF might do something which causes that we need to access static methods
5813 // of TSFTextStore. At that time, sEnabledTextStore may be necessary.
5814 // So, we should set sEnabledTextStore directly.
5815 RefPtr<TSFTextStore> textStore = new TSFTextStore();
5816 sEnabledTextStore = textStore;
5817 if (NS_WARN_IF(!textStore->Init(aFocusedWidget, aContext))) {
5818 MOZ_LOG(gIMELog, LogLevel::Error,
5819 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5820 "TSFTextStore::Init() failure"));
5821 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5822 return false;
5824 RefPtr<ITfDocumentMgr> newDocMgr = textStore->mDocumentMgr;
5825 if (NS_WARN_IF(!newDocMgr)) {
5826 MOZ_LOG(gIMELog, LogLevel::Error,
5827 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5828 "invalid TSFTextStore::mDocumentMgr"));
5829 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5830 return false;
5832 if (aContext.mIMEState.mEnabled == IMEEnabled::Password) {
5833 MarkContextAsKeyboardDisabled(textStore->mContext);
5834 RefPtr<ITfContext> topContext;
5835 newDocMgr->GetTop(getter_AddRefs(topContext));
5836 if (topContext && topContext != textStore->mContext) {
5837 MarkContextAsKeyboardDisabled(topContext);
5841 HRESULT hr;
5842 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5843 hr = threadMgr->SetFocus(newDocMgr);
5845 if (NS_WARN_IF(FAILED(hr))) {
5846 MOZ_LOG(gIMELog, LogLevel::Error,
5847 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5848 "ITfTheadMgr::SetFocus() failure"));
5849 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5850 return false;
5852 if (NS_WARN_IF(!sThreadMgr)) {
5853 MOZ_LOG(gIMELog, LogLevel::Error,
5854 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5855 "sThreadMgr being destroyed during calling "
5856 "ITfTheadMgr::SetFocus()"));
5857 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5858 return false;
5860 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5861 MOZ_LOG(gIMELog, LogLevel::Error,
5862 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5863 "creating TextStore has lost focus during calling "
5864 "ITfThreadMgr::SetFocus()"));
5865 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5866 return false;
5869 // Use AssociateFocus() for ensuring that any native focus event
5870 // never steal focus from our documentMgr.
5871 RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
5872 hr = threadMgr->AssociateFocus(aFocusedWidget->GetWindowHandle(), newDocMgr,
5873 getter_AddRefs(prevFocusedDocumentMgr));
5874 if (NS_WARN_IF(FAILED(hr))) {
5875 MOZ_LOG(gIMELog, LogLevel::Error,
5876 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5877 "ITfTheadMgr::AssociateFocus() failure"));
5878 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5879 return false;
5881 if (NS_WARN_IF(!sThreadMgr)) {
5882 MOZ_LOG(gIMELog, LogLevel::Error,
5883 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5884 "sThreadMgr being destroyed during calling "
5885 "ITfTheadMgr::AssociateFocus()"));
5886 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5887 return false;
5889 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5890 MOZ_LOG(gIMELog, LogLevel::Error,
5891 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5892 "creating TextStore has lost focus during calling "
5893 "ITfTheadMgr::AssociateFocus()"));
5894 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5895 return false;
5898 if (textStore->mSink) {
5899 MOZ_LOG(gIMELog, LogLevel::Info,
5900 (" TSFTextStore::CreateAndSetFocus(), calling "
5901 "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE) for 0x%p...",
5902 textStore.get()));
5903 RefPtr<ITextStoreACPSink> sink = textStore->mSink;
5904 sink->OnLayoutChange(TS_LC_CREATE, TEXTSTORE_DEFAULT_VIEW);
5905 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5906 MOZ_LOG(gIMELog, LogLevel::Error,
5907 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5908 "creating TextStore has lost focus during calling "
5909 "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE)"));
5910 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5911 return false;
5914 return true;
5917 // static
5918 IMENotificationRequests TSFTextStore::GetIMENotificationRequests() {
5919 if (!sEnabledTextStore || NS_WARN_IF(!sEnabledTextStore->mDocumentMgr)) {
5920 // If there is no active text store, we don't need any notifications
5921 // since there is no sink which needs notifications.
5922 return IMENotificationRequests();
5925 // Otherwise, requests all notifications since even if some of them may not
5926 // be required by the sink of active TIP, active TIP may be changed and
5927 // other TIPs may need all notifications.
5928 // Note that Windows temporarily steal focus from active window if the main
5929 // process which created the window becomes busy. In this case, we shouldn't
5930 // commit composition since user may want to continue to compose the
5931 // composition after becoming not busy. Therefore, we need notifications
5932 // even during deactive.
5933 // Be aware, we don't need to check actual focused text store. For example,
5934 // MS-IME for Japanese handles focus messages by themselves and sets focused
5935 // text store to nullptr when the process is being inactivated. However,
5936 // we still need to reuse sEnabledTextStore if the process is activated and
5937 // focused element isn't changed. Therefore, if sEnabledTextStore isn't
5938 // nullptr, we need to keep notifying the sink even when it is not focused
5939 // text store for the thread manager.
5940 return IMENotificationRequests(
5941 IMENotificationRequests::NOTIFY_TEXT_CHANGE |
5942 IMENotificationRequests::NOTIFY_POSITION_CHANGE |
5943 IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR |
5944 IMENotificationRequests::NOTIFY_DURING_DEACTIVE);
5947 nsresult TSFTextStore::OnTextChangeInternal(
5948 const IMENotification& aIMENotification) {
5949 const TextChangeDataBase& textChangeData = aIMENotification.mTextChangeData;
5951 MOZ_LOG(gIMELog, LogLevel::Debug,
5952 ("0x%p TSFTextStore::OnTextChangeInternal(aIMENotification={ "
5953 "mMessage=0x%08X, mTextChangeData=%s }), "
5954 "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, "
5955 "mComposition=%s",
5956 this, aIMENotification.mMessage,
5957 mozilla::ToString(textChangeData).c_str(), GetBoolName(mDestroyed),
5958 mSink.get(), GetSinkMaskNameStr(mSinkMask).get(),
5959 ToString(mComposition).c_str()));
5961 if (mDestroyed) {
5962 // If this instance is already destroyed, we shouldn't notify TSF of any
5963 // changes.
5964 return NS_OK;
5967 mDeferNotifyingTSFUntilNextUpdate = false;
5969 // Different from selection change, we don't modify anything with text
5970 // change data. Therefore, if neither TSF not TIP wants text change
5971 // notifications, we don't need to store the changes.
5972 if (!mSink || !(mSinkMask & TS_AS_TEXT_CHANGE)) {
5973 return NS_OK;
5976 // Merge any text change data even if it's caused by composition.
5977 mPendingTextChangeData.MergeWith(textChangeData);
5979 MaybeFlushPendingNotifications();
5981 return NS_OK;
5984 void TSFTextStore::NotifyTSFOfTextChange() {
5985 MOZ_ASSERT(!mDestroyed);
5986 MOZ_ASSERT(!IsReadLocked());
5987 MOZ_ASSERT(mComposition.isNothing());
5988 MOZ_ASSERT(mPendingTextChangeData.IsValid());
5990 // If the text changes are caused only by composition, we don't need to
5991 // notify TSF of the text changes.
5992 if (mPendingTextChangeData.mCausedOnlyByComposition) {
5993 mPendingTextChangeData.Clear();
5994 return;
5997 // First, forget cached selection.
5998 mSelectionForTSF.reset();
6000 // For making it safer, we should check if there is a valid sink to receive
6001 // text change notification.
6002 if (NS_WARN_IF(!mSink) || NS_WARN_IF(!(mSinkMask & TS_AS_TEXT_CHANGE))) {
6003 MOZ_LOG(gIMELog, LogLevel::Error,
6004 ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
6005 "mSink is not ready to call ITextStoreACPSink::OnTextChange()...",
6006 this));
6007 mPendingTextChangeData.Clear();
6008 return;
6011 if (NS_WARN_IF(!mPendingTextChangeData.IsInInt32Range())) {
6012 MOZ_LOG(gIMELog, LogLevel::Error,
6013 ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
6014 "offset is too big for calling "
6015 "ITextStoreACPSink::OnTextChange()...",
6016 this));
6017 mPendingTextChangeData.Clear();
6018 return;
6021 TS_TEXTCHANGE textChange;
6022 textChange.acpStart = static_cast<LONG>(mPendingTextChangeData.mStartOffset);
6023 textChange.acpOldEnd =
6024 static_cast<LONG>(mPendingTextChangeData.mRemovedEndOffset);
6025 textChange.acpNewEnd =
6026 static_cast<LONG>(mPendingTextChangeData.mAddedEndOffset);
6027 mPendingTextChangeData.Clear();
6029 MOZ_LOG(
6030 gIMELog, LogLevel::Info,
6031 ("0x%p TSFTextStore::NotifyTSFOfTextChange(), calling "
6032 "ITextStoreACPSink::OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
6033 "acpNewEnd=%ld })...",
6034 this, textChange.acpStart, textChange.acpOldEnd, textChange.acpNewEnd));
6035 RefPtr<ITextStoreACPSink> sink = mSink;
6036 sink->OnTextChange(0, &textChange);
6039 nsresult TSFTextStore::OnSelectionChangeInternal(
6040 const IMENotification& aIMENotification) {
6041 const SelectionChangeDataBase& selectionChangeData =
6042 aIMENotification.mSelectionChangeData;
6043 MOZ_LOG(gIMELog, LogLevel::Debug,
6044 ("0x%p TSFTextStore::OnSelectionChangeInternal("
6045 "aIMENotification={ mSelectionChangeData=%s }), mDestroyed=%s, "
6046 "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, "
6047 "mComposition=%s",
6048 this, mozilla::ToString(selectionChangeData).c_str(),
6049 GetBoolName(mDestroyed), mSink.get(),
6050 GetSinkMaskNameStr(mSinkMask).get(),
6051 GetBoolName(mIsRecordingActionsWithoutLock),
6052 ToString(mComposition).c_str()));
6054 if (mDestroyed) {
6055 // If this instance is already destroyed, we shouldn't notify TSF of any
6056 // changes.
6057 return NS_OK;
6060 mDeferNotifyingTSFUntilNextUpdate = false;
6062 // Assign the new selection change data to the pending selection change data
6063 // because only the latest selection data is necessary.
6064 // Note that this is necessary to update mSelectionForTSF. Therefore, even if
6065 // neither TSF nor TIP wants selection change notifications, we need to
6066 // store the selection information.
6067 mPendingSelectionChangeData = Some(selectionChangeData);
6069 // Flush remaining pending notifications here if it's possible.
6070 MaybeFlushPendingNotifications();
6072 // If we're available, we should create native caret instead of IMEHandler
6073 // because we may have some cache to do it.
6074 // Note that if we have composition, we'll notified composition-updated
6075 // later so that we don't need to create native caret in such case.
6076 if (!IsHandlingCompositionInContent() &&
6077 IMEHandler::NeedsToCreateNativeCaret()) {
6078 CreateNativeCaret();
6081 return NS_OK;
6084 void TSFTextStore::NotifyTSFOfSelectionChange() {
6085 MOZ_ASSERT(!mDestroyed);
6086 MOZ_ASSERT(!IsReadLocked());
6087 MOZ_ASSERT(mComposition.isNothing());
6088 MOZ_ASSERT(mPendingSelectionChangeData.isSome());
6090 // If selection range isn't actually changed, we don't need to notify TSF
6091 // of this selection change.
6092 if (mSelectionForTSF.isNothing()) {
6093 MOZ_DIAGNOSTIC_ASSERT(!mIsInitializingSelectionForTSF,
6094 "While mSelectionForTSF is being initialized, this "
6095 "should not be called");
6096 mSelectionForTSF.emplace(*mPendingSelectionChangeData);
6097 } else if (!mSelectionForTSF->SetSelection(*mPendingSelectionChangeData)) {
6098 mPendingSelectionChangeData.reset();
6099 MOZ_LOG(gIMELog, LogLevel::Debug,
6100 ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), "
6101 "selection isn't actually changed.",
6102 this));
6103 return;
6106 mPendingSelectionChangeData.reset();
6108 if (!mSink || !(mSinkMask & TS_AS_SEL_CHANGE)) {
6109 return;
6112 MOZ_LOG(gIMELog, LogLevel::Info,
6113 ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), calling "
6114 "ITextStoreACPSink::OnSelectionChange()...",
6115 this));
6116 RefPtr<ITextStoreACPSink> sink = mSink;
6117 sink->OnSelectionChange();
6120 nsresult TSFTextStore::OnLayoutChangeInternal() {
6121 if (mDestroyed) {
6122 // If this instance is already destroyed, we shouldn't notify TSF of any
6123 // changes.
6124 return NS_OK;
6127 NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE);
6128 NS_ENSURE_TRUE(mSink, NS_ERROR_FAILURE);
6130 mDeferNotifyingTSFUntilNextUpdate = false;
6132 nsresult rv = NS_OK;
6134 // We need to notify TSF of layout change even if the document is locked.
6135 // So, don't use MaybeFlushPendingNotifications() for flushing pending
6136 // layout change.
6137 MOZ_LOG(gIMELog, LogLevel::Info,
6138 ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
6139 "NotifyTSFOfLayoutChange()...",
6140 this));
6141 if (NS_WARN_IF(!NotifyTSFOfLayoutChange())) {
6142 rv = NS_ERROR_FAILURE;
6145 MOZ_LOG(gIMELog, LogLevel::Debug,
6146 ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
6147 "MaybeFlushPendingNotifications()...",
6148 this));
6149 MaybeFlushPendingNotifications();
6151 return rv;
6154 bool TSFTextStore::NotifyTSFOfLayoutChange() {
6155 MOZ_ASSERT(!mDestroyed);
6157 // If we're waiting a query of layout information from TIP, it means that
6158 // we've returned TS_E_NOLAYOUT error.
6159 bool returnedNoLayoutError = mHasReturnedNoLayoutError || mWaitingQueryLayout;
6161 // If we returned TS_E_NOLAYOUT, TIP should query the computed layout again.
6162 mWaitingQueryLayout = returnedNoLayoutError;
6164 // For avoiding to call this method again at unlocking the document during
6165 // calls of OnLayoutChange(), reset mHasReturnedNoLayoutError.
6166 mHasReturnedNoLayoutError = false;
6168 // Now, layout has been computed. We should notify mContentForTSF for
6169 // making GetTextExt() and GetACPFromPoint() not return TS_E_NOLAYOUT.
6170 if (mContentForTSF.isSome()) {
6171 mContentForTSF->OnLayoutChanged();
6174 if (IMEHandler::NeedsToCreateNativeCaret()) {
6175 // If we're available, we should create native caret instead of IMEHandler
6176 // because we may have some cache to do it.
6177 CreateNativeCaret();
6178 } else {
6179 // Now, the caret position is different from ours. Destroy the native caret
6180 // if we've create it only for GetTextExt().
6181 IMEHandler::MaybeDestroyNativeCaret();
6184 // This method should return true if either way succeeds.
6185 bool ret = true;
6187 if (mSink) {
6188 MOZ_LOG(gIMELog, LogLevel::Info,
6189 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6190 "calling ITextStoreACPSink::OnLayoutChange()...",
6191 this));
6192 RefPtr<ITextStoreACPSink> sink = mSink;
6193 HRESULT hr = sink->OnLayoutChange(TS_LC_CHANGE, TEXTSTORE_DEFAULT_VIEW);
6194 MOZ_LOG(gIMELog, LogLevel::Info,
6195 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6196 "called ITextStoreACPSink::OnLayoutChange()",
6197 this));
6198 ret = SUCCEEDED(hr);
6201 // The layout change caused by composition string change should cause
6202 // calling ITfContextOwnerServices::OnLayoutChange() too.
6203 if (returnedNoLayoutError && mContext) {
6204 RefPtr<ITfContextOwnerServices> service;
6205 mContext->QueryInterface(IID_ITfContextOwnerServices,
6206 getter_AddRefs(service));
6207 if (service) {
6208 MOZ_LOG(gIMELog, LogLevel::Info,
6209 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6210 "calling ITfContextOwnerServices::OnLayoutChange()...",
6211 this));
6212 HRESULT hr = service->OnLayoutChange();
6213 ret = ret && SUCCEEDED(hr);
6214 MOZ_LOG(gIMELog, LogLevel::Info,
6215 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6216 "called ITfContextOwnerServices::OnLayoutChange()",
6217 this));
6221 if (!mWidget || mWidget->Destroyed()) {
6222 MOZ_LOG(gIMELog, LogLevel::Info,
6223 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6224 "the widget is destroyed during calling OnLayoutChange()",
6225 this));
6226 return ret;
6229 if (mDestroyed) {
6230 MOZ_LOG(gIMELog, LogLevel::Info,
6231 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6232 "the TSFTextStore instance is destroyed during calling "
6233 "OnLayoutChange()",
6234 this));
6235 return ret;
6238 // If we returned TS_E_NOLAYOUT again, we need another call of
6239 // OnLayoutChange() later. So, let's wait a query from TIP.
6240 if (mHasReturnedNoLayoutError) {
6241 mWaitingQueryLayout = true;
6244 if (!mWaitingQueryLayout) {
6245 MOZ_LOG(gIMELog, LogLevel::Info,
6246 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6247 "succeeded notifying TIP of our layout change",
6248 this));
6249 return ret;
6252 // If we believe that TIP needs to retry to retrieve our layout information
6253 // later, we should call it with ::PostMessage() hack.
6254 MOZ_LOG(gIMELog, LogLevel::Debug,
6255 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6256 "posing MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE for calling "
6257 "OnLayoutChange() again...",
6258 this));
6259 ::PostMessage(mWidget->GetWindowHandle(), MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE,
6260 reinterpret_cast<WPARAM>(this), 0);
6262 return true;
6265 void TSFTextStore::NotifyTSFOfLayoutChangeAgain() {
6266 // Don't notify TSF of layout change after destroyed.
6267 if (mDestroyed) {
6268 mWaitingQueryLayout = false;
6269 return;
6272 // Before preforming this method, TIP has accessed our layout information by
6273 // itself. In such case, we don't need to call OnLayoutChange() anymore.
6274 if (!mWaitingQueryLayout) {
6275 return;
6278 MOZ_LOG(gIMELog, LogLevel::Info,
6279 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6280 "calling NotifyTSFOfLayoutChange()...",
6281 this));
6282 NotifyTSFOfLayoutChange();
6284 // If TIP didn't retrieved our layout information during a call of
6285 // NotifyTSFOfLayoutChange(), it means that the TIP already gave up to
6286 // retry to retrieve layout information or doesn't necessary it anymore.
6287 // But don't forget that the call may have caused returning TS_E_NOLAYOUT
6288 // error again. In such case we still need to call OnLayoutChange() later.
6289 if (!mHasReturnedNoLayoutError && mWaitingQueryLayout) {
6290 mWaitingQueryLayout = false;
6291 MOZ_LOG(gIMELog, LogLevel::Warning,
6292 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6293 "called NotifyTSFOfLayoutChange() but TIP didn't retry to "
6294 "retrieve the layout information",
6295 this));
6296 } else {
6297 MOZ_LOG(gIMELog, LogLevel::Info,
6298 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6299 "called NotifyTSFOfLayoutChange()",
6300 this));
6304 nsresult TSFTextStore::OnUpdateCompositionInternal() {
6305 MOZ_LOG(gIMELog, LogLevel::Debug,
6306 ("0x%p TSFTextStore::OnUpdateCompositionInternal(), "
6307 "mDestroyed=%s, mDeferNotifyingTSFUntilNextUpdate=%s",
6308 this, GetBoolName(mDestroyed),
6309 GetBoolName(mDeferNotifyingTSFUntilNextUpdate)));
6311 // There are nothing to do after destroyed.
6312 if (mDestroyed) {
6313 return NS_OK;
6316 // Update cached data now because all pending events have been handled now.
6317 if (mContentForTSF.isSome()) {
6318 mContentForTSF->OnCompositionEventsHandled();
6321 // If composition is completely finished both in TSF/TIP and the focused
6322 // editor which may be in a remote process, we can clear the cache and don't
6323 // have it until starting next composition.
6324 if (mComposition.isNothing() && !IsHandlingCompositionInContent()) {
6325 mDeferClearingContentForTSF = false;
6327 mDeferNotifyingTSFUntilNextUpdate = false;
6328 MaybeFlushPendingNotifications();
6330 // If we're available, we should create native caret instead of IMEHandler
6331 // because we may have some cache to do it.
6332 if (IMEHandler::NeedsToCreateNativeCaret()) {
6333 CreateNativeCaret();
6336 return NS_OK;
6339 nsresult TSFTextStore::OnMouseButtonEventInternal(
6340 const IMENotification& aIMENotification) {
6341 if (mDestroyed) {
6342 // If this instance is already destroyed, we shouldn't notify TSF of any
6343 // events.
6344 return NS_OK;
6347 if (mMouseTrackers.IsEmpty()) {
6348 return NS_OK;
6351 MOZ_LOG(gIMELog, LogLevel::Debug,
6352 ("0x%p TSFTextStore::OnMouseButtonEventInternal("
6353 "aIMENotification={ mEventMessage=%s, mOffset=%u, mCursorPos=%s, "
6354 "mCharRect=%s, mButton=%s, mButtons=%s, mModifiers=%s })",
6355 this, ToChar(aIMENotification.mMouseButtonEventData.mEventMessage),
6356 aIMENotification.mMouseButtonEventData.mOffset,
6357 ToString(aIMENotification.mMouseButtonEventData.mCursorPos).c_str(),
6358 ToString(aIMENotification.mMouseButtonEventData.mCharRect).c_str(),
6359 GetMouseButtonName(aIMENotification.mMouseButtonEventData.mButton),
6360 GetMouseButtonsName(aIMENotification.mMouseButtonEventData.mButtons)
6361 .get(),
6362 GetModifiersName(aIMENotification.mMouseButtonEventData.mModifiers)
6363 .get()));
6365 uint32_t offset = aIMENotification.mMouseButtonEventData.mOffset;
6366 if (offset > static_cast<uint32_t>(LONG_MAX)) {
6367 return NS_OK;
6369 LayoutDeviceIntRect charRect =
6370 aIMENotification.mMouseButtonEventData.mCharRect;
6371 LayoutDeviceIntPoint cursorPos =
6372 aIMENotification.mMouseButtonEventData.mCursorPos;
6373 ULONG quadrant = 1;
6374 if (charRect.Width() > 0) {
6375 int32_t cursorXInChar = cursorPos.x - charRect.X();
6376 quadrant = cursorXInChar * 4 / charRect.Width();
6377 quadrant = (quadrant + 2) % 4;
6379 ULONG edge = quadrant < 2 ? offset + 1 : offset;
6380 DWORD buttonStatus = 0;
6381 bool isMouseUp =
6382 aIMENotification.mMouseButtonEventData.mEventMessage == eMouseUp;
6383 if (!isMouseUp) {
6384 switch (aIMENotification.mMouseButtonEventData.mButton) {
6385 case MouseButton::ePrimary:
6386 buttonStatus = MK_LBUTTON;
6387 break;
6388 case MouseButton::eMiddle:
6389 buttonStatus = MK_MBUTTON;
6390 break;
6391 case MouseButton::eSecondary:
6392 buttonStatus = MK_RBUTTON;
6393 break;
6396 if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_CONTROL) {
6397 buttonStatus |= MK_CONTROL;
6399 if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_SHIFT) {
6400 buttonStatus |= MK_SHIFT;
6402 for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
6403 MouseTracker& tracker = mMouseTrackers[i];
6404 if (!tracker.IsUsing() || tracker.Range().isNothing() ||
6405 !tracker.Range()->IsOffsetInRange(offset)) {
6406 continue;
6408 if (tracker.OnMouseButtonEvent(edge - tracker.Range()->StartOffset(),
6409 quadrant, buttonStatus)) {
6410 return NS_SUCCESS_EVENT_CONSUMED;
6413 return NS_OK;
6416 void TSFTextStore::CreateNativeCaret() {
6417 MOZ_ASSERT(!IMEHandler::IsA11yHandlingNativeCaret());
6419 IMEHandler::MaybeDestroyNativeCaret();
6421 // Don't create native caret after destroyed.
6422 if (mDestroyed) {
6423 return;
6426 MOZ_LOG(gIMELog, LogLevel::Debug,
6427 ("0x%p TSFTextStore::CreateNativeCaret(), mComposition=%s", this,
6428 ToString(mComposition).c_str()));
6430 Maybe<Selection>& selectionForTSF = SelectionForTSF();
6431 if (MOZ_UNLIKELY(selectionForTSF.isNothing())) {
6432 MOZ_LOG(gIMELog, LogLevel::Error,
6433 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6434 "SelectionForTSF() failure",
6435 this));
6436 return;
6438 if (!selectionForTSF->HasRange() && mComposition.isNothing()) {
6439 // If there is no selection range nor composition, then, we don't have a
6440 // good position to show windows of TIP...
6441 // XXX It seems that storing last caret rect and using it in this case might
6442 // be better?
6443 MOZ_LOG(gIMELog, LogLevel::Warning,
6444 ("0x%p TSFTextStore::CreateNativeCaret() couludn't create native "
6445 "caret due to no selection range",
6446 this));
6447 return;
6450 WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, mWidget);
6451 mWidget->InitEvent(queryCaretRectEvent);
6453 WidgetQueryContentEvent::Options options;
6454 // XXX If this is called without composition and the selection isn't
6455 // collapsed, is it OK?
6456 int64_t caretOffset = selectionForTSF->HasRange()
6457 ? selectionForTSF->MaxOffset()
6458 : mComposition->StartOffset();
6459 if (mComposition.isSome()) {
6460 // If there is a composition, use the relative query for deciding caret
6461 // position because composition might be different place from that
6462 // TSFTextStore assumes.
6463 options.mRelativeToInsertionPoint = true;
6464 caretOffset -= mComposition->StartOffset();
6465 } else if (!CanAccessActualContentDirectly()) {
6466 // If TSF/TIP cannot access actual content directly, there may be pending
6467 // text and/or selection changes which have not been notified TSF yet.
6468 // Therefore, we should use the relative query from start of selection where
6469 // TSFTextStore assumes since TSF/TIP computes the offset from our cached
6470 // selection.
6471 options.mRelativeToInsertionPoint = true;
6472 caretOffset -= selectionForTSF->StartOffset();
6474 queryCaretRectEvent.InitForQueryCaretRect(caretOffset, options);
6476 DispatchEvent(queryCaretRectEvent);
6477 if (NS_WARN_IF(queryCaretRectEvent.Failed())) {
6478 MOZ_LOG(gIMELog, LogLevel::Error,
6479 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6480 "eQueryCaretRect failure (offset=%lld)",
6481 this, caretOffset));
6482 return;
6485 if (!IMEHandler::CreateNativeCaret(static_cast<nsWindow*>(mWidget.get()),
6486 queryCaretRectEvent.mReply->mRect)) {
6487 MOZ_LOG(gIMELog, LogLevel::Error,
6488 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6489 "IMEHandler::CreateNativeCaret() failure",
6490 this));
6491 return;
6495 void TSFTextStore::CommitCompositionInternal(bool aDiscard) {
6496 MOZ_LOG(gIMELog, LogLevel::Debug,
6497 ("0x%p TSFTextStore::CommitCompositionInternal(aDiscard=%s), "
6498 "mSink=0x%p, mContext=0x%p, mComposition=%s",
6499 this, GetBoolName(aDiscard), mSink.get(), mContext.get(),
6500 ToString(mComposition).c_str()));
6502 // If the document is locked, TSF will fail to commit composition since
6503 // TSF needs another document lock. So, let's put off the request.
6504 // Note that TextComposition will commit composition in the focused editor
6505 // with the latest composition string for web apps and waits asynchronous
6506 // committing messages. Therefore, we can and need to perform this
6507 // asynchronously.
6508 if (IsReadLocked()) {
6509 if (mDeferCommittingComposition || mDeferCancellingComposition) {
6510 MOZ_LOG(gIMELog, LogLevel::Debug,
6511 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6512 "does nothing because already called and waiting unlock...",
6513 this));
6514 return;
6516 if (aDiscard) {
6517 mDeferCancellingComposition = true;
6518 } else {
6519 mDeferCommittingComposition = true;
6521 MOZ_LOG(gIMELog, LogLevel::Debug,
6522 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6523 "putting off to request to %s composition after unlocking the "
6524 "document",
6525 this, aDiscard ? "cancel" : "commit"));
6526 return;
6529 if (mComposition.isSome() && aDiscard) {
6530 LONG endOffset = mComposition->EndOffset();
6531 mComposition->SetData(EmptyString());
6532 // Note that don't notify TSF of text change after this is destroyed.
6533 if (mSink && !mDestroyed) {
6534 TS_TEXTCHANGE textChange;
6535 textChange.acpStart = mComposition->StartOffset();
6536 textChange.acpOldEnd = endOffset;
6537 textChange.acpNewEnd = mComposition->StartOffset();
6538 MOZ_LOG(gIMELog, LogLevel::Info,
6539 ("0x%p TSFTextStore::CommitCompositionInternal(), calling"
6540 "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
6541 "acpNewEnd=%ld })...",
6542 this, textChange.acpStart, textChange.acpOldEnd,
6543 textChange.acpNewEnd));
6544 RefPtr<ITextStoreACPSink> sink = mSink;
6545 sink->OnTextChange(0, &textChange);
6548 // Terminate two contexts, the base context (mContext) and the top
6549 // if the top context is not the same as the base context
6550 RefPtr<ITfContext> context = mContext;
6551 do {
6552 if (context) {
6553 RefPtr<ITfContextOwnerCompositionServices> services;
6554 context->QueryInterface(IID_ITfContextOwnerCompositionServices,
6555 getter_AddRefs(services));
6556 if (services) {
6557 MOZ_LOG(gIMELog, LogLevel::Debug,
6558 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6559 "requesting TerminateComposition() for the context 0x%p...",
6560 this, context.get()));
6561 services->TerminateComposition(nullptr);
6564 if (context != mContext) break;
6565 if (mDocumentMgr) mDocumentMgr->GetTop(getter_AddRefs(context));
6566 } while (context != mContext);
6569 static bool GetCompartment(IUnknown* pUnk, const GUID& aID,
6570 ITfCompartment** aCompartment) {
6571 if (!pUnk) return false;
6573 RefPtr<ITfCompartmentMgr> compMgr;
6574 pUnk->QueryInterface(IID_ITfCompartmentMgr, getter_AddRefs(compMgr));
6575 if (!compMgr) return false;
6577 return SUCCEEDED(compMgr->GetCompartment(aID, aCompartment)) &&
6578 (*aCompartment) != nullptr;
6581 // static
6582 void TSFTextStore::SetIMEOpenState(bool aState) {
6583 MOZ_LOG(gIMELog, LogLevel::Debug,
6584 ("TSFTextStore::SetIMEOpenState(aState=%s)", GetBoolName(aState)));
6586 if (!sThreadMgr) {
6587 return;
6590 RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
6591 if (NS_WARN_IF(!comp)) {
6592 MOZ_LOG(gIMELog, LogLevel::Debug,
6593 (" TSFTextStore::SetIMEOpenState() FAILED due to"
6594 "no compartment available"));
6595 return;
6598 VARIANT variant;
6599 variant.vt = VT_I4;
6600 variant.lVal = aState;
6601 HRESULT hr = comp->SetValue(sClientId, &variant);
6602 if (NS_WARN_IF(FAILED(hr))) {
6603 MOZ_LOG(gIMELog, LogLevel::Error,
6604 (" TSFTextStore::SetIMEOpenState() FAILED due to "
6605 "ITfCompartment::SetValue() failure, hr=0x%08lX",
6606 hr));
6607 return;
6609 MOZ_LOG(gIMELog, LogLevel::Debug,
6610 (" TSFTextStore::SetIMEOpenState(), setting "
6611 "0x%04lX to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...",
6612 variant.lVal));
6615 // static
6616 bool TSFTextStore::GetIMEOpenState() {
6617 if (!sThreadMgr) {
6618 return false;
6621 RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
6622 if (NS_WARN_IF(!comp)) {
6623 return false;
6626 VARIANT variant;
6627 ::VariantInit(&variant);
6628 HRESULT hr = comp->GetValue(&variant);
6629 if (NS_WARN_IF(FAILED(hr))) {
6630 MOZ_LOG(gIMELog, LogLevel::Error,
6631 ("TSFTextStore::GetIMEOpenState() FAILED due to "
6632 "ITfCompartment::GetValue() failure, hr=0x%08lX",
6633 hr));
6634 return false;
6636 // Until IME is open in this process, the result may be empty.
6637 if (variant.vt == VT_EMPTY) {
6638 return false;
6640 if (NS_WARN_IF(variant.vt != VT_I4)) {
6641 MOZ_LOG(gIMELog, LogLevel::Error,
6642 ("TSFTextStore::GetIMEOpenState() FAILED due to "
6643 "invalid result of ITfCompartment::GetValue()"));
6644 ::VariantClear(&variant);
6645 return false;
6648 return variant.lVal != 0;
6651 // static
6652 void TSFTextStore::SetInputContext(nsWindow* aWidget,
6653 const InputContext& aContext,
6654 const InputContextAction& aAction) {
6655 MOZ_LOG(gIMELog, LogLevel::Debug,
6656 ("TSFTextStore::SetInputContext(aWidget=%p, "
6657 "aContext=%s, aAction.mFocusChange=%s), "
6658 "sEnabledTextStore(0x%p)={ mWidget=0x%p }, ThinksHavingFocus()=%s",
6659 aWidget, mozilla::ToString(aContext).c_str(),
6660 GetFocusChangeName(aAction.mFocusChange), sEnabledTextStore.get(),
6661 sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr,
6662 GetBoolName(ThinksHavingFocus())));
6664 switch (aAction.mFocusChange) {
6665 case InputContextAction::WIDGET_CREATED:
6666 // If this is called when the widget is created, there is nothing to do.
6667 return;
6668 case InputContextAction::FOCUS_NOT_CHANGED:
6669 case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
6670 if (NS_WARN_IF(!IsInTSFMode())) {
6671 return;
6673 // In these cases, `NOTIFY_IME_OF_FOCUS` won't be sent. Therefore,
6674 // we need to reset text store for new state right now.
6675 break;
6676 default:
6677 NS_WARNING_ASSERTION(IsInTSFMode(),
6678 "Why is this called when TSF is disabled?");
6679 if (sEnabledTextStore) {
6680 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
6681 textStore->mInPrivateBrowsing = aContext.mInPrivateBrowsing;
6682 textStore->SetInputScope(aContext.mHTMLInputType,
6683 aContext.mHTMLInputMode);
6684 if (aContext.mURI) {
6685 nsAutoCString spec;
6686 if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) {
6687 CopyUTF8toUTF16(spec, textStore->mDocumentURL);
6688 } else {
6689 textStore->mDocumentURL.Truncate();
6691 } else {
6692 textStore->mDocumentURL.Truncate();
6695 return;
6698 // If focus isn't actually changed but the enabled state is changed,
6699 // emulate the focus move.
6700 if (!ThinksHavingFocus() && aContext.mIMEState.IsEditable()) {
6701 if (!IMEHandler::GetFocusedWindow()) {
6702 MOZ_LOG(gIMELog, LogLevel::Error,
6703 (" TSFTextStore::SetInputContent() gets called to enable IME, "
6704 "but IMEHandler has not received focus notification"));
6705 } else {
6706 MOZ_LOG(gIMELog, LogLevel::Debug,
6707 (" TSFTextStore::SetInputContent() emulates focus for IME "
6708 "state change"));
6709 OnFocusChange(true, aWidget, aContext);
6711 } else if (ThinksHavingFocus() && !aContext.mIMEState.IsEditable()) {
6712 MOZ_LOG(gIMELog, LogLevel::Debug,
6713 (" TSFTextStore::SetInputContent() emulates blur for IME "
6714 "state change"));
6715 OnFocusChange(false, aWidget, aContext);
6719 // static
6720 void TSFTextStore::MarkContextAsKeyboardDisabled(ITfContext* aContext) {
6721 VARIANT variant_int4_value1;
6722 variant_int4_value1.vt = VT_I4;
6723 variant_int4_value1.lVal = 1;
6725 RefPtr<ITfCompartment> comp;
6726 if (!GetCompartment(aContext, GUID_COMPARTMENT_KEYBOARD_DISABLED,
6727 getter_AddRefs(comp))) {
6728 MOZ_LOG(gIMELog, LogLevel::Error,
6729 ("TSFTextStore::MarkContextAsKeyboardDisabled() failed"
6730 "aContext=0x%p...",
6731 aContext));
6732 return;
6735 MOZ_LOG(gIMELog, LogLevel::Debug,
6736 ("TSFTextStore::MarkContextAsKeyboardDisabled(), setting "
6737 "to disable context 0x%p...",
6738 aContext));
6739 comp->SetValue(sClientId, &variant_int4_value1);
6742 // static
6743 void TSFTextStore::MarkContextAsEmpty(ITfContext* aContext) {
6744 VARIANT variant_int4_value1;
6745 variant_int4_value1.vt = VT_I4;
6746 variant_int4_value1.lVal = 1;
6748 RefPtr<ITfCompartment> comp;
6749 if (!GetCompartment(aContext, GUID_COMPARTMENT_EMPTYCONTEXT,
6750 getter_AddRefs(comp))) {
6751 MOZ_LOG(gIMELog, LogLevel::Error,
6752 ("TSFTextStore::MarkContextAsEmpty() failed"
6753 "aContext=0x%p...",
6754 aContext));
6755 return;
6758 MOZ_LOG(gIMELog, LogLevel::Debug,
6759 ("TSFTextStore::MarkContextAsEmpty(), setting "
6760 "to mark empty context 0x%p...",
6761 aContext));
6762 comp->SetValue(sClientId, &variant_int4_value1);
6765 // static
6766 void TSFTextStore::Initialize() {
6767 MOZ_LOG(gIMELog, LogLevel::Info, ("TSFTextStore::Initialize() is called..."));
6769 if (sThreadMgr) {
6770 MOZ_LOG(gIMELog, LogLevel::Error,
6771 (" TSFTextStore::Initialize() FAILED due to already initialized"));
6772 return;
6775 const bool enableTsf = StaticPrefs::intl_tsf_enabled_AtStartup();
6776 MOZ_LOG(gIMELog, LogLevel::Info,
6777 (" TSFTextStore::Initialize(), TSF is %s",
6778 enableTsf ? "enabled" : "disabled"));
6779 if (!enableTsf) {
6780 return;
6783 RefPtr<ITfThreadMgr> threadMgr;
6784 HRESULT hr =
6785 ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_INPROC_SERVER,
6786 IID_ITfThreadMgr, getter_AddRefs(threadMgr));
6787 if (FAILED(hr) || !threadMgr) {
6788 MOZ_LOG(gIMELog, LogLevel::Error,
6789 (" TSFTextStore::Initialize() FAILED to "
6790 "create the thread manager, hr=0x%08lX",
6791 hr));
6792 return;
6795 hr = threadMgr->Activate(&sClientId);
6796 if (FAILED(hr)) {
6797 MOZ_LOG(
6798 gIMELog, LogLevel::Error,
6799 (" TSFTextStore::Initialize() FAILED to activate, hr=0x%08lX", hr));
6800 return;
6803 RefPtr<ITfDocumentMgr> disabledDocumentMgr;
6804 hr = threadMgr->CreateDocumentMgr(getter_AddRefs(disabledDocumentMgr));
6805 if (FAILED(hr) || !disabledDocumentMgr) {
6806 MOZ_LOG(gIMELog, LogLevel::Error,
6807 (" TSFTextStore::Initialize() FAILED to create "
6808 "a document manager for disabled mode, hr=0x%08lX",
6809 hr));
6810 return;
6813 RefPtr<ITfContext> disabledContext;
6814 DWORD editCookie = 0;
6815 hr = disabledDocumentMgr->CreateContext(
6816 sClientId, 0, nullptr, getter_AddRefs(disabledContext), &editCookie);
6817 if (FAILED(hr) || !disabledContext) {
6818 MOZ_LOG(gIMELog, LogLevel::Error,
6819 (" TSFTextStore::Initialize() FAILED to create "
6820 "a context for disabled mode, hr=0x%08lX",
6821 hr));
6822 return;
6825 MarkContextAsKeyboardDisabled(disabledContext);
6826 MarkContextAsEmpty(disabledContext);
6828 sThreadMgr = threadMgr;
6829 sDisabledDocumentMgr = disabledDocumentMgr;
6830 sDisabledContext = disabledContext;
6832 MOZ_LOG(gIMELog, LogLevel::Info,
6833 (" TSFTextStore::Initialize(), sThreadMgr=0x%p, "
6834 "sClientId=0x%08lX, sDisabledDocumentMgr=0x%p, sDisabledContext=%p",
6835 sThreadMgr.get(), sClientId, sDisabledDocumentMgr.get(),
6836 sDisabledContext.get()));
6839 // static
6840 already_AddRefed<ITfThreadMgr> TSFTextStore::GetThreadMgr() {
6841 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
6842 return threadMgr.forget();
6845 // static
6846 already_AddRefed<ITfMessagePump> TSFTextStore::GetMessagePump() {
6847 static bool sInitialized = false;
6848 if (!sThreadMgr) {
6849 return nullptr;
6851 if (sMessagePump) {
6852 RefPtr<ITfMessagePump> messagePump = sMessagePump;
6853 return messagePump.forget();
6855 // If it tried to retrieve ITfMessagePump from sThreadMgr but it failed,
6856 // we shouldn't retry it at every message due to performance reason.
6857 // Although this shouldn't occur actually.
6858 if (sInitialized) {
6859 return nullptr;
6861 sInitialized = true;
6863 RefPtr<ITfMessagePump> messagePump;
6864 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfMessagePump,
6865 getter_AddRefs(messagePump));
6866 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!messagePump)) {
6867 MOZ_LOG(gIMELog, LogLevel::Error,
6868 ("TSFTextStore::GetMessagePump() FAILED to "
6869 "QI message pump from the thread manager, hr=0x%08lX",
6870 hr));
6871 return nullptr;
6873 sMessagePump = messagePump;
6874 return messagePump.forget();
6877 // static
6878 already_AddRefed<ITfDisplayAttributeMgr>
6879 TSFTextStore::GetDisplayAttributeMgr() {
6880 RefPtr<ITfDisplayAttributeMgr> displayAttributeMgr;
6881 if (sDisplayAttrMgr) {
6882 displayAttributeMgr = sDisplayAttrMgr;
6883 return displayAttributeMgr.forget();
6886 HRESULT hr = ::CoCreateInstance(
6887 CLSID_TF_DisplayAttributeMgr, nullptr, CLSCTX_INPROC_SERVER,
6888 IID_ITfDisplayAttributeMgr, getter_AddRefs(displayAttributeMgr));
6889 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!displayAttributeMgr)) {
6890 MOZ_LOG(gIMELog, LogLevel::Error,
6891 ("TSFTextStore::GetDisplayAttributeMgr() FAILED to create "
6892 "a display attribute manager instance, hr=0x%08lX",
6893 hr));
6894 return nullptr;
6896 sDisplayAttrMgr = displayAttributeMgr;
6897 return displayAttributeMgr.forget();
6900 // static
6901 already_AddRefed<ITfCategoryMgr> TSFTextStore::GetCategoryMgr() {
6902 RefPtr<ITfCategoryMgr> categoryMgr;
6903 if (sCategoryMgr) {
6904 categoryMgr = sCategoryMgr;
6905 return categoryMgr.forget();
6907 HRESULT hr =
6908 ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, CLSCTX_INPROC_SERVER,
6909 IID_ITfCategoryMgr, getter_AddRefs(categoryMgr));
6910 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!categoryMgr)) {
6911 MOZ_LOG(gIMELog, LogLevel::Error,
6912 ("TSFTextStore::GetCategoryMgr() FAILED to create "
6913 "a category manager instance, hr=0x%08lX",
6914 hr));
6915 return nullptr;
6917 sCategoryMgr = categoryMgr;
6918 return categoryMgr.forget();
6921 // static
6922 already_AddRefed<ITfCompartment> TSFTextStore::GetCompartmentForOpenClose() {
6923 if (sCompartmentForOpenClose) {
6924 RefPtr<ITfCompartment> compartment = sCompartmentForOpenClose;
6925 return compartment.forget();
6928 if (!sThreadMgr) {
6929 return nullptr;
6932 RefPtr<ITfCompartmentMgr> compartmentMgr;
6933 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfCompartmentMgr,
6934 getter_AddRefs(compartmentMgr));
6935 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartmentMgr)) {
6936 MOZ_LOG(gIMELog, LogLevel::Error,
6937 ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
6938 "sThreadMgr not having ITfCompartmentMgr, hr=0x%08lX",
6939 hr));
6940 return nullptr;
6943 RefPtr<ITfCompartment> compartment;
6944 hr = compartmentMgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
6945 getter_AddRefs(compartment));
6946 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartment)) {
6947 MOZ_LOG(gIMELog, LogLevel::Error,
6948 ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
6949 "ITfCompartmentMgr::GetCompartment() failuere, hr=0x%08lX",
6950 hr));
6951 return nullptr;
6954 sCompartmentForOpenClose = compartment;
6955 return compartment.forget();
6958 // static
6959 already_AddRefed<ITfInputProcessorProfiles>
6960 TSFTextStore::GetInputProcessorProfiles() {
6961 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles;
6962 if (sInputProcessorProfiles) {
6963 inputProcessorProfiles = sInputProcessorProfiles;
6964 return inputProcessorProfiles.forget();
6966 // XXX MSDN documents that ITfInputProcessorProfiles is available only on
6967 // desktop apps. However, there is no known way to obtain
6968 // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles
6969 // instance.
6970 HRESULT hr = ::CoCreateInstance(
6971 CLSID_TF_InputProcessorProfiles, nullptr, CLSCTX_INPROC_SERVER,
6972 IID_ITfInputProcessorProfiles, getter_AddRefs(inputProcessorProfiles));
6973 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!inputProcessorProfiles)) {
6974 MOZ_LOG(gIMELog, LogLevel::Error,
6975 ("TSFTextStore::GetInputProcessorProfiles() FAILED to create input "
6976 "processor profiles, hr=0x%08lX",
6977 hr));
6978 return nullptr;
6980 sInputProcessorProfiles = inputProcessorProfiles;
6981 return inputProcessorProfiles.forget();
6984 // static
6985 void TSFTextStore::Terminate() {
6986 MOZ_LOG(gIMELog, LogLevel::Info, ("TSFTextStore::Terminate()"));
6988 TSFStaticSink::Shutdown();
6990 sDisplayAttrMgr = nullptr;
6991 sCategoryMgr = nullptr;
6992 sEnabledTextStore = nullptr;
6993 sDisabledDocumentMgr = nullptr;
6994 sDisabledContext = nullptr;
6995 sCompartmentForOpenClose = nullptr;
6996 sInputProcessorProfiles = nullptr;
6997 sClientId = 0;
6998 if (sThreadMgr) {
6999 sThreadMgr->Deactivate();
7000 sThreadMgr = nullptr;
7001 sMessagePump = nullptr;
7002 sKeystrokeMgr = nullptr;
7006 // static
7007 bool TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg) {
7008 if (!sThreadMgr) {
7009 return false; // not in TSF mode
7011 static bool sInitialized = false;
7012 if (!sKeystrokeMgr) {
7013 // If it tried to retrieve ITfKeystrokeMgr from sThreadMgr but it failed,
7014 // we shouldn't retry it at every keydown nor keyup due to performance
7015 // reason. Although this shouldn't occur actually.
7016 if (sInitialized) {
7017 return false;
7019 sInitialized = true;
7020 RefPtr<ITfKeystrokeMgr> keystrokeMgr;
7021 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfKeystrokeMgr,
7022 getter_AddRefs(keystrokeMgr));
7023 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!keystrokeMgr)) {
7024 MOZ_LOG(gIMELog, LogLevel::Error,
7025 ("TSFTextStore::ProcessRawKeyMessage() FAILED to "
7026 "QI keystroke manager from the thread manager, hr=0x%08lX",
7027 hr));
7028 return false;
7030 sKeystrokeMgr = keystrokeMgr.forget();
7033 if (aMsg.message == WM_KEYDOWN) {
7034 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
7035 if (textStore) {
7036 textStore->OnStartToHandleKeyMessage();
7037 if (NS_WARN_IF(textStore != sEnabledTextStore)) {
7038 // Let's handle the key message with new focused TSFTextStore.
7039 textStore = sEnabledTextStore;
7042 AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
7043 AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
7044 sHandlingKeyMsg = &aMsg;
7045 sIsKeyboardEventDispatched = false;
7046 BOOL eaten;
7047 RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
7048 HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten);
7049 if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
7050 return false;
7052 hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
7053 if (textStore) {
7054 textStore->OnEndHandlingKeyMessage(!!eaten);
7056 return SUCCEEDED(hr) &&
7057 (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
7059 if (aMsg.message == WM_KEYUP) {
7060 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
7061 if (textStore) {
7062 textStore->OnStartToHandleKeyMessage();
7063 if (NS_WARN_IF(textStore != sEnabledTextStore)) {
7064 // Let's handle the key message with new focused TSFTextStore.
7065 textStore = sEnabledTextStore;
7068 AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
7069 AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
7070 sHandlingKeyMsg = &aMsg;
7071 sIsKeyboardEventDispatched = false;
7072 BOOL eaten;
7073 RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
7074 HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten);
7075 if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
7076 return false;
7078 hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten);
7079 if (textStore) {
7080 textStore->OnEndHandlingKeyMessage(!!eaten);
7082 return SUCCEEDED(hr) &&
7083 (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
7085 return false;
7088 // static
7089 void TSFTextStore::ProcessMessage(nsWindow* aWindow, UINT aMessage,
7090 WPARAM& aWParam, LPARAM& aLParam,
7091 MSGResult& aResult) {
7092 switch (aMessage) {
7093 case WM_IME_SETCONTEXT:
7094 // If a windowless plugin had focus and IME was handled on it, composition
7095 // window was set the position. After that, even in TSF mode, WinXP keeps
7096 // to use composition window at the position if the active IME is not
7097 // aware TSF. For avoiding this issue, we need to hide the composition
7098 // window here.
7099 if (aWParam) {
7100 aLParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
7102 break;
7103 case WM_ENTERIDLE:
7104 // When an modal dialog such as a file picker is open, composition
7105 // should be committed because IME might be used on it.
7106 if (!IsComposingOn(aWindow)) {
7107 break;
7109 CommitComposition(false);
7110 break;
7111 case MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE: {
7112 TSFTextStore* maybeTextStore = reinterpret_cast<TSFTextStore*>(aWParam);
7113 if (maybeTextStore == sEnabledTextStore) {
7114 RefPtr<TSFTextStore> textStore(maybeTextStore);
7115 textStore->NotifyTSFOfLayoutChangeAgain();
7117 break;
7122 // static
7123 bool TSFTextStore::IsIMM_IMEActive() {
7124 return TSFStaticSink::IsIMM_IMEActive();
7127 // static
7128 bool TSFTextStore::IsMSJapaneseIMEActive() {
7129 return TSFStaticSink::IsMSJapaneseIMEActive();
7132 // static
7133 bool TSFTextStore::IsGoogleJapaneseInputActive() {
7134 return TSFStaticSink::IsGoogleJapaneseInputActive();
7137 // static
7138 bool TSFTextStore::IsATOKActive() { return TSFStaticSink::IsATOKActive(); }
7140 /******************************************************************************
7141 * TSFTextStore::Content
7142 *****************************************************************************/
7144 const nsDependentSubstring TSFTextStore::Content::GetSelectedText() const {
7145 if (NS_WARN_IF(mSelection.isNothing())) {
7146 return nsDependentSubstring();
7148 return GetSubstring(static_cast<uint32_t>(mSelection->StartOffset()),
7149 static_cast<uint32_t>(mSelection->Length()));
7152 const nsDependentSubstring TSFTextStore::Content::GetSubstring(
7153 uint32_t aStart, uint32_t aLength) const {
7154 return nsDependentSubstring(mText, aStart, aLength);
7157 void TSFTextStore::Content::ReplaceSelectedTextWith(const nsAString& aString) {
7158 if (NS_WARN_IF(mSelection.isNothing())) {
7159 return;
7161 ReplaceTextWith(mSelection->StartOffset(), mSelection->Length(), aString);
7164 inline uint32_t FirstDifferentCharOffset(const nsAString& aStr1,
7165 const nsAString& aStr2) {
7166 MOZ_ASSERT(aStr1 != aStr2);
7167 uint32_t i = 0;
7168 uint32_t minLength = std::min(aStr1.Length(), aStr2.Length());
7169 for (; i < minLength && aStr1[i] == aStr2[i]; i++) {
7170 /* nothing to do */
7172 return i;
7175 void TSFTextStore::Content::ReplaceTextWith(LONG aStart, LONG aLength,
7176 const nsAString& aReplaceString) {
7177 MOZ_ASSERT(aStart >= 0);
7178 MOZ_ASSERT(aLength >= 0);
7179 const nsDependentSubstring replacedString = GetSubstring(
7180 static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength));
7181 if (aReplaceString != replacedString) {
7182 uint32_t firstDifferentOffset = mMinModifiedOffset.valueOr(UINT32_MAX);
7183 if (mComposition.isSome()) {
7184 // Emulate text insertion during compositions, because during a
7185 // composition, editor expects the whole composition string to
7186 // be sent in eCompositionChange, not just the inserted part.
7187 // The actual eCompositionChange will be sent in SetSelection
7188 // or OnUpdateComposition.
7189 MOZ_ASSERT(aStart >= mComposition->StartOffset());
7190 MOZ_ASSERT(aStart + aLength <= mComposition->EndOffset());
7191 mComposition->ReplaceData(
7192 static_cast<uint32_t>(aStart - mComposition->StartOffset()),
7193 static_cast<uint32_t>(aLength), aReplaceString);
7194 // TIP may set composition string twice or more times during a document
7195 // lock. Therefore, we should compute the first difference offset with
7196 // mLastComposition.
7197 if (mLastComposition.isNothing()) {
7198 firstDifferentOffset = mComposition->StartOffset();
7199 } else if (mComposition->DataRef() != mLastComposition->DataRef()) {
7200 firstDifferentOffset =
7201 mComposition->StartOffset() +
7202 FirstDifferentCharOffset(mComposition->DataRef(),
7203 mLastComposition->DataRef());
7204 // The previous change to the composition string is canceled.
7205 if (mMinModifiedOffset.isSome() &&
7206 mMinModifiedOffset.value() >=
7207 static_cast<uint32_t>(mComposition->StartOffset()) &&
7208 mMinModifiedOffset.value() < firstDifferentOffset) {
7209 mMinModifiedOffset = Some(firstDifferentOffset);
7211 } else if (mMinModifiedOffset.isSome() &&
7212 mMinModifiedOffset.value() < static_cast<uint32_t>(LONG_MAX) &&
7213 mComposition->IsOffsetInRange(
7214 static_cast<long>(mMinModifiedOffset.value()))) {
7215 // The previous change to the composition string is canceled.
7216 firstDifferentOffset = mComposition->EndOffset();
7217 mMinModifiedOffset = Some(firstDifferentOffset);
7219 mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
7220 MOZ_LOG(
7221 gIMELog, LogLevel::Debug,
7222 ("0x%p TSFTextStore::Content::ReplaceTextWith(aStart=%ld, "
7223 "aLength=%ld, aReplaceString=\"%s\"), mComposition=%s, "
7224 "mLastComposition=%s, mMinModifiedOffset=%s, "
7225 "firstDifferentOffset=%u",
7226 this, aStart, aLength, GetEscapedUTF8String(aReplaceString).get(),
7227 ToString(mComposition).c_str(), ToString(mLastComposition).c_str(),
7228 ToString(mMinModifiedOffset).c_str(), firstDifferentOffset));
7229 } else {
7230 firstDifferentOffset =
7231 static_cast<uint32_t>(aStart) +
7232 FirstDifferentCharOffset(aReplaceString, replacedString);
7234 mMinModifiedOffset =
7235 mMinModifiedOffset.isNothing()
7236 ? Some(firstDifferentOffset)
7237 : Some(std::min(mMinModifiedOffset.value(), firstDifferentOffset));
7238 mText.Replace(static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength),
7239 aReplaceString);
7241 // Selection should be collapsed at the end of the inserted string.
7242 mSelection = Some(TSFTextStore::Selection(static_cast<uint32_t>(aStart) +
7243 aReplaceString.Length()));
7246 void TSFTextStore::Content::StartComposition(
7247 ITfCompositionView* aCompositionView, const PendingAction& aCompStart,
7248 bool aPreserveSelection) {
7249 MOZ_ASSERT(aCompositionView);
7250 MOZ_ASSERT(mComposition.isNothing());
7251 MOZ_ASSERT(aCompStart.mType == PendingAction::Type::eCompositionStart);
7253 mComposition.reset(); // Avoid new crash in the beta and nightly channels.
7254 mComposition.emplace(
7255 aCompositionView, aCompStart.mSelectionStart,
7256 GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart),
7257 static_cast<uint32_t>(aCompStart.mSelectionLength)));
7258 mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
7259 if (!aPreserveSelection) {
7260 // XXX Do we need to set a new writing-mode here when setting a new
7261 // selection? Currently, we just preserve the existing value.
7262 WritingMode writingMode =
7263 mSelection.isNothing() ? WritingMode() : mSelection->WritingModeRef();
7264 mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset(),
7265 mComposition->Length(), false,
7266 writingMode));
7270 void TSFTextStore::Content::RestoreCommittedComposition(
7271 ITfCompositionView* aCompositionView,
7272 const PendingAction& aCanceledCompositionEnd) {
7273 MOZ_ASSERT(aCompositionView);
7274 MOZ_ASSERT(mComposition.isNothing());
7275 MOZ_ASSERT(aCanceledCompositionEnd.mType ==
7276 PendingAction::Type::eCompositionEnd);
7277 MOZ_ASSERT(
7278 GetSubstring(
7279 static_cast<uint32_t>(aCanceledCompositionEnd.mSelectionStart),
7280 static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
7281 aCanceledCompositionEnd.mData);
7283 // Restore the committed string as composing string.
7284 mComposition.reset(); // Avoid new crash in the beta and nightly channels.
7285 mComposition.emplace(aCompositionView,
7286 aCanceledCompositionEnd.mSelectionStart,
7287 aCanceledCompositionEnd.mData);
7288 mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
7291 void TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd) {
7292 MOZ_ASSERT(mComposition.isSome());
7293 MOZ_ASSERT(aCompEnd.mType == PendingAction::Type::eCompositionEnd);
7295 if (mComposition.isNothing()) {
7296 return; // Avoid new crash in the beta and nightly channels.
7299 mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset() +
7300 aCompEnd.mData.Length()));
7301 mComposition.reset();
7304 /******************************************************************************
7305 * TSFTextStore::MouseTracker
7306 *****************************************************************************/
7308 TSFTextStore::MouseTracker::MouseTracker() : mCookie(kInvalidCookie) {}
7310 HRESULT
7311 TSFTextStore::MouseTracker::Init(TSFTextStore* aTextStore) {
7312 MOZ_LOG(gIMELog, LogLevel::Debug,
7313 ("0x%p TSFTextStore::MouseTracker::Init(aTextStore=0x%p), "
7314 "aTextStore->mMouseTrackers.Length()=%zu",
7315 this, aTextStore, aTextStore->mMouseTrackers.Length()));
7317 if (&aTextStore->mMouseTrackers.LastElement() != this) {
7318 MOZ_LOG(gIMELog, LogLevel::Error,
7319 ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
7320 "this is not the last element of mMouseTrackers",
7321 this));
7322 return E_FAIL;
7324 if (aTextStore->mMouseTrackers.Length() > kInvalidCookie) {
7325 MOZ_LOG(gIMELog, LogLevel::Error,
7326 ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
7327 "no new cookie available",
7328 this));
7329 return E_FAIL;
7331 MOZ_ASSERT(!aTextStore->mMouseTrackers.IsEmpty(),
7332 "This instance must be in TSFTextStore::mMouseTrackers");
7333 mCookie = static_cast<DWORD>(aTextStore->mMouseTrackers.Length() - 1);
7334 return S_OK;
7337 HRESULT
7338 TSFTextStore::MouseTracker::AdviseSink(TSFTextStore* aTextStore,
7339 ITfRangeACP* aTextRange,
7340 ITfMouseSink* aMouseSink) {
7341 MOZ_LOG(gIMELog, LogLevel::Debug,
7342 ("0x%p TSFTextStore::MouseTracker::AdviseSink(aTextStore=0x%p, "
7343 "aTextRange=0x%p, aMouseSink=0x%p), mCookie=%ld, mSink=0x%p",
7344 this, aTextStore, aTextRange, aMouseSink, mCookie, mSink.get()));
7345 MOZ_ASSERT(mCookie != kInvalidCookie, "This hasn't been initalized?");
7347 if (mSink) {
7348 MOZ_LOG(gIMELog, LogLevel::Error,
7349 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7350 "due to already being used",
7351 this));
7352 return E_FAIL;
7355 MOZ_ASSERT(mRange.isNothing());
7357 LONG start = 0, length = 0;
7358 HRESULT hr = aTextRange->GetExtent(&start, &length);
7359 if (FAILED(hr)) {
7360 MOZ_LOG(gIMELog, LogLevel::Error,
7361 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7362 "due to failure of ITfRangeACP::GetExtent()",
7363 this));
7364 return hr;
7367 if (start < 0 || length <= 0 || start + length > LONG_MAX) {
7368 MOZ_LOG(gIMELog, LogLevel::Error,
7369 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7370 "due to odd result of ITfRangeACP::GetExtent(), "
7371 "start=%ld, length=%ld",
7372 this, start, length));
7373 return E_INVALIDARG;
7376 nsAutoString textContent;
7377 if (NS_WARN_IF(!aTextStore->GetCurrentText(textContent))) {
7378 MOZ_LOG(gIMELog, LogLevel::Error,
7379 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7380 "due to failure of TSFTextStore::GetCurrentText()",
7381 this));
7382 return E_FAIL;
7385 if (textContent.Length() <= static_cast<uint32_t>(start) ||
7386 textContent.Length() < static_cast<uint32_t>(start + length)) {
7387 MOZ_LOG(gIMELog, LogLevel::Error,
7388 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7389 "due to out of range, start=%ld, length=%ld, "
7390 "textContent.Length()=%zu",
7391 this, start, length, textContent.Length()));
7392 return E_INVALIDARG;
7395 mRange.emplace(start, start + length);
7397 mSink = aMouseSink;
7399 MOZ_LOG(gIMELog, LogLevel::Debug,
7400 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink(), "
7401 "succeeded, mRange=%s, textContent.Length()=%zu",
7402 this, ToString(mRange).c_str(), textContent.Length()));
7403 return S_OK;
7406 void TSFTextStore::MouseTracker::UnadviseSink() {
7407 MOZ_LOG(gIMELog, LogLevel::Debug,
7408 ("0x%p TSFTextStore::MouseTracker::UnadviseSink(), "
7409 "mCookie=%ld, mSink=0x%p, mRange=%s",
7410 this, mCookie, mSink.get(), ToString(mRange).c_str()));
7411 mSink = nullptr;
7412 mRange.reset();
7415 bool TSFTextStore::MouseTracker::OnMouseButtonEvent(ULONG aEdge,
7416 ULONG aQuadrant,
7417 DWORD aButtonStatus) {
7418 MOZ_ASSERT(IsUsing(), "The caller must check before calling OnMouseEvent()");
7420 BOOL eaten = FALSE;
7421 RefPtr<ITfMouseSink> sink = mSink;
7422 HRESULT hr = sink->OnMouseEvent(aEdge, aQuadrant, aButtonStatus, &eaten);
7424 MOZ_LOG(gIMELog, LogLevel::Debug,
7425 ("0x%p TSFTextStore::MouseTracker::OnMouseEvent(aEdge=%ld, "
7426 "aQuadrant=%ld, aButtonStatus=0x%08lX), hr=0x%08lX, eaten=%s",
7427 this, aEdge, aQuadrant, aButtonStatus, hr, GetBoolName(!!eaten)));
7429 return SUCCEEDED(hr) && eaten;
7432 #ifdef DEBUG
7433 // static
7434 bool TSFTextStore::CurrentKeyboardLayoutHasIME() {
7435 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
7436 TSFTextStore::GetInputProcessorProfiles();
7437 if (!inputProcessorProfiles) {
7438 MOZ_LOG(gIMELog, LogLevel::Error,
7439 ("TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED due to "
7440 "there is no input processor profiles instance"));
7441 return false;
7443 RefPtr<ITfInputProcessorProfileMgr> profileMgr;
7444 HRESULT hr = inputProcessorProfiles->QueryInterface(
7445 IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
7446 if (FAILED(hr) || !profileMgr) {
7447 // On Windows Vista or later, ImmIsIME() API always returns true.
7448 // If we failed to obtain the profile manager, we cannot know if current
7449 // keyboard layout has IME.
7450 MOZ_LOG(gIMELog, LogLevel::Error,
7451 (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to query "
7452 "ITfInputProcessorProfileMgr"));
7453 return false;
7456 TF_INPUTPROCESSORPROFILE profile;
7457 hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
7458 if (hr == S_FALSE) {
7459 return false; // not found or not active
7461 if (FAILED(hr)) {
7462 MOZ_LOG(gIMELog, LogLevel::Error,
7463 (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to retreive "
7464 "active profile"));
7465 return false;
7467 return (profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR);
7469 #endif // #ifdef DEBUG
7471 } // namespace widget
7472 } // namespace mozilla