Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / windows / TSFTextStore.cpp
blobb3e3a164aeda4bc71e3440f7a78ee12fe1314a27
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/StaticPrefs_intl.h"
23 #include "mozilla/Telemetry.h"
24 #include "mozilla/TextEventDispatcher.h"
25 #include "mozilla/TextEvents.h"
26 #include "mozilla/ToString.h"
27 #include "mozilla/WindowsVersion.h"
28 #include "mozilla/widget/WinRegistry.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 (WinRegistry::GetString(HKEY_CLASSES_ROOT, key, u""_ns, buf,
338 WinRegistry::kLegacyWinUtilsStringFlags)) {
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 GetActiveTIPNameForTelemetry(nsAString& aName) {
1037 if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
1038 return false;
1040 if (sInstance->mActiveTIPGUID == GUID_NULL) {
1041 aName.Truncate();
1042 aName.AppendPrintf("0x%04X", sInstance->mLangID);
1043 return true;
1045 // key should be "LocaleID|Description". Although GUID of the
1046 // profile is unique key since description may be localized for system
1047 // language, unfortunately, it's too long to record as key with its
1048 // description. Therefore, we should record only the description with
1049 // LocaleID because Microsoft IME may not include language information.
1050 // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
1051 aName.Truncate();
1052 aName.AppendPrintf("0x%04X|", sInstance->mLangID);
1053 nsAutoString description;
1054 description.Assign(sInstance->mActiveTIPKeyboardDescription);
1055 static const uint32_t kMaxDescriptionLength = 72 - aName.Length();
1056 if (description.Length() > kMaxDescriptionLength) {
1057 if (NS_IS_LOW_SURROGATE(description[kMaxDescriptionLength - 1]) &&
1058 NS_IS_HIGH_SURROGATE(description[kMaxDescriptionLength - 2])) {
1059 description.Truncate(kMaxDescriptionLength - 2);
1060 } else {
1061 description.Truncate(kMaxDescriptionLength - 1);
1063 // U+2026 is "..."
1064 description.Append(char16_t(0x2026));
1066 aName.Append(description);
1067 return true;
1070 static bool IsMSChangJieOrMSQuickActive() {
1071 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1072 // For avoiding unnecessary computation, we should check if the language
1073 // for current TIP is Traditional Chinese.
1074 if (!IsTraditionalChinese()) {
1075 return false;
1077 switch (ActiveTIP()) {
1078 case TextInputProcessorID::eMicrosoftChangJie:
1079 case TextInputProcessorID::eMicrosoftQuick:
1080 return true;
1081 default:
1082 return false;
1086 static bool IsMSPinyinOrMSWubiActive() {
1087 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1088 // For avoiding unnecessary computation, we should check if the language
1089 // for current TIP is Simplified Chinese.
1090 if (!IsSimplifiedChinese()) {
1091 return false;
1093 switch (ActiveTIP()) {
1094 case TextInputProcessorID::eMicrosoftPinyin:
1095 case TextInputProcessorID::eMicrosoftWubi:
1096 return true;
1097 default:
1098 return false;
1102 static bool IsMSJapaneseIMEActive() {
1103 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1104 // For avoiding unnecessary computation, we should check if the language
1105 // for current TIP is Japanese.
1106 if (!IsJapanese()) {
1107 return false;
1109 return ActiveTIP() == TextInputProcessorID::eMicrosoftIMEForJapanese;
1112 static bool IsGoogleJapaneseInputActive() {
1113 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1114 // For avoiding unnecessary computation, we should check if the language
1115 // for current TIP is Japanese.
1116 if (!IsJapanese()) {
1117 return false;
1119 return ActiveTIP() == TextInputProcessorID::eGoogleJapaneseInput;
1122 static bool IsATOKActive() {
1123 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1124 // For avoiding unnecessary computation, we should check if active TIP is
1125 // ATOK first since it's cheaper.
1126 return IsJapanese() && sInstance->IsATOKActiveInternal();
1129 // Note that ATOK 2011 - 2016 refers native caret position for deciding its
1130 // popup window position.
1131 static bool IsATOKReferringNativeCaretActive() {
1132 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1133 // For avoiding unnecessary computation, we should check if active TIP is
1134 // ATOK first since it's cheaper.
1135 if (!IsJapanese() || !sInstance->IsATOKActiveInternal()) {
1136 return false;
1138 switch (ActiveTIP()) {
1139 case TextInputProcessorID::eATOK2011:
1140 case TextInputProcessorID::eATOK2012:
1141 case TextInputProcessorID::eATOK2013:
1142 case TextInputProcessorID::eATOK2014:
1143 case TextInputProcessorID::eATOK2015:
1144 return true;
1145 default:
1146 return false;
1150 private:
1151 static void EnsureInstance() {
1152 if (!sInstance) {
1153 RefPtr<TSFStaticSink> staticSink = GetInstance();
1154 Unused << staticSink;
1158 bool IsTraditionalChineseInternal() const { return mLangID == 0x0404; }
1159 bool IsSimplifiedChineseInternal() const { return mLangID == 0x0804; }
1160 bool IsJapaneseInternal() const { return mLangID == 0x0411; }
1161 bool IsKoreanInternal() const { return mLangID == 0x0412; }
1163 bool IsATOKActiveInternal() {
1164 EnsureInitActiveTIPKeyboard();
1165 // FYI: Name of packaged ATOK includes the release year like "ATOK 2015".
1166 // Name of ATOK Passport (subscription) equals "ATOK".
1167 return StringBeginsWith(mActiveTIPKeyboardDescription, u"ATOK "_ns) ||
1168 mActiveTIPKeyboardDescription.EqualsLiteral("ATOK");
1171 void ComputeActiveTextInputProcessor() {
1172 if (mActiveTIP != TextInputProcessorID::eNotComputed) {
1173 return;
1176 if (mActiveTIPGUID == GUID_NULL) {
1177 mActiveTIP = TextInputProcessorID::eNone;
1178 return;
1181 // Comparing GUID is slow. So, we should use language information to
1182 // reduce the comparing cost for TIP which is not we do not support
1183 // specifically since they are always compared with all supported TIPs.
1184 switch (mLangID) {
1185 case 0x0404:
1186 mActiveTIP = ComputeActiveTIPAsTraditionalChinese();
1187 break;
1188 case 0x0411:
1189 mActiveTIP = ComputeActiveTIPAsJapanese();
1190 break;
1191 case 0x0412:
1192 mActiveTIP = ComputeActiveTIPAsKorean();
1193 break;
1194 case 0x0804:
1195 mActiveTIP = ComputeActiveTIPAsSimplifiedChinese();
1196 break;
1197 default:
1198 mActiveTIP = TextInputProcessorID::eUnknown;
1199 break;
1201 // Special case for Keyman Desktop, it is available for any languages.
1202 // Therefore, we need to check it only if we don't know the active TIP.
1203 if (mActiveTIP != TextInputProcessorID::eUnknown) {
1204 return;
1207 // Note that keyboard layouts for Keyman assign its GUID on install
1208 // randomly, but CLSID is constant in any environments.
1209 // https://bugzilla.mozilla.org/show_bug.cgi?id=1670834#c7
1210 // https://github.com/keymanapp/keyman/blob/318c73a9e1d571d942837ff9964590626e5bd5aa/windows/src/engine/kmtip/globals.cpp#L37
1211 // {FE0420F1-38D1-4B4C-96BF-E7E20A74CFB7}
1212 static constexpr CLSID kKeymanDesktop_CLSID = {
1213 0xFE0420F1,
1214 0x38D1,
1215 0x4B4C,
1216 {0x96, 0xBF, 0xE7, 0xE2, 0x0A, 0x74, 0xCF, 0xB7}};
1217 if (mActiveTIPCLSID == kKeymanDesktop_CLSID) {
1218 mActiveTIP = TextInputProcessorID::eKeymanDesktop;
1222 TextInputProcessorID ComputeActiveTIPAsJapanese() {
1223 // {A76C93D9-5523-4E90-AAFA-4DB112F9AC76} (Win7, Win8.1, Win10)
1224 static constexpr GUID kMicrosoftIMEForJapaneseGUID = {
1225 0xA76C93D9,
1226 0x5523,
1227 0x4E90,
1228 {0xAA, 0xFA, 0x4D, 0xB1, 0x12, 0xF9, 0xAC, 0x76}};
1229 if (mActiveTIPGUID == kMicrosoftIMEForJapaneseGUID) {
1230 return TextInputProcessorID::eMicrosoftIMEForJapanese;
1232 // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64}
1233 static constexpr GUID kMicrosoftOfficeIME2010ForJapaneseGUID = {
1234 0x54EDCC94,
1235 0x1524,
1236 0x4BB1,
1237 {0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64}};
1238 if (mActiveTIPGUID == kMicrosoftOfficeIME2010ForJapaneseGUID) {
1239 return TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese;
1241 // {773EB24E-CA1D-4B1B-B420-FA985BB0B80D}
1242 static constexpr GUID kGoogleJapaneseInputGUID = {
1243 0x773EB24E,
1244 0xCA1D,
1245 0x4B1B,
1246 {0xB4, 0x20, 0xFA, 0x98, 0x5B, 0xB0, 0xB8, 0x0D}};
1247 if (mActiveTIPGUID == kGoogleJapaneseInputGUID) {
1248 return TextInputProcessorID::eGoogleJapaneseInput;
1250 // {F9C24A5C-8A53-499D-9572-93B2FF582115}
1251 static const GUID kATOK2011GUID = {
1252 0xF9C24A5C,
1253 0x8A53,
1254 0x499D,
1255 {0x95, 0x72, 0x93, 0xB2, 0xFF, 0x58, 0x21, 0x15}};
1256 if (mActiveTIPGUID == kATOK2011GUID) {
1257 return TextInputProcessorID::eATOK2011;
1259 // {1DE01562-F445-401B-B6C3-E5B18DB79461}
1260 static constexpr GUID kATOK2012GUID = {
1261 0x1DE01562,
1262 0xF445,
1263 0x401B,
1264 {0xB6, 0xC3, 0xE5, 0xB1, 0x8D, 0xB7, 0x94, 0x61}};
1265 if (mActiveTIPGUID == kATOK2012GUID) {
1266 return TextInputProcessorID::eATOK2012;
1268 // {3C4DB511-189A-4168-B6EA-BFD0B4C85615}
1269 static constexpr GUID kATOK2013GUID = {
1270 0x3C4DB511,
1271 0x189A,
1272 0x4168,
1273 {0xB6, 0xEA, 0xBF, 0xD0, 0xB4, 0xC8, 0x56, 0x15}};
1274 if (mActiveTIPGUID == kATOK2013GUID) {
1275 return TextInputProcessorID::eATOK2013;
1277 // {4EF33B79-6AA9-4271-B4BF-9321C279381B}
1278 static constexpr GUID kATOK2014GUID = {
1279 0x4EF33B79,
1280 0x6AA9,
1281 0x4271,
1282 {0xB4, 0xBF, 0x93, 0x21, 0xC2, 0x79, 0x38, 0x1B}};
1283 if (mActiveTIPGUID == kATOK2014GUID) {
1284 return TextInputProcessorID::eATOK2014;
1286 // {EAB4DC00-CE2E-483D-A86A-E6B99DA9599A}
1287 static constexpr GUID kATOK2015GUID = {
1288 0xEAB4DC00,
1289 0xCE2E,
1290 0x483D,
1291 {0xA8, 0x6A, 0xE6, 0xB9, 0x9D, 0xA9, 0x59, 0x9A}};
1292 if (mActiveTIPGUID == kATOK2015GUID) {
1293 return TextInputProcessorID::eATOK2015;
1295 // {0B557B4C-5740-4110-A60A-1493FA10BF2B}
1296 static constexpr GUID kATOK2016GUID = {
1297 0x0B557B4C,
1298 0x5740,
1299 0x4110,
1300 {0xA6, 0x0A, 0x14, 0x93, 0xFA, 0x10, 0xBF, 0x2B}};
1301 if (mActiveTIPGUID == kATOK2016GUID) {
1302 return TextInputProcessorID::eATOK2016;
1305 // * ATOK 2017
1306 // - {6DBFD8F5-701D-11E6-920F-782BCBA6348F}
1307 // * ATOK Passport (confirmed with version 31.1.2)
1308 // - {A38F2FD9-7199-45E1-841C-BE0313D8052F}
1310 if (IsATOKActiveInternal()) {
1311 return TextInputProcessorID::eATOKUnknown;
1314 // {E6D66705-1EDA-4373-8D01-1D0CB2D054C7}
1315 static constexpr GUID kJapanist10GUID = {
1316 0xE6D66705,
1317 0x1EDA,
1318 0x4373,
1319 {0x8D, 0x01, 0x1D, 0x0C, 0xB2, 0xD0, 0x54, 0xC7}};
1320 if (mActiveTIPGUID == kJapanist10GUID) {
1321 return TextInputProcessorID::eJapanist10;
1324 return TextInputProcessorID::eUnknown;
1327 TextInputProcessorID ComputeActiveTIPAsTraditionalChinese() {
1328 // {B2F9C502-1742-11D4-9790-0080C882687E} (Win8.1, Win10)
1329 static constexpr GUID kMicrosoftBopomofoGUID = {
1330 0xB2F9C502,
1331 0x1742,
1332 0x11D4,
1333 {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1334 if (mActiveTIPGUID == kMicrosoftBopomofoGUID) {
1335 return TextInputProcessorID::eMicrosoftBopomofo;
1337 // {4BDF9F03-C7D3-11D4-B2AB-0080C882687E} (Win7, Win8.1, Win10)
1338 static const GUID kMicrosoftChangJieGUID = {
1339 0x4BDF9F03,
1340 0xC7D3,
1341 0x11D4,
1342 {0xB2, 0xAB, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1343 if (mActiveTIPGUID == kMicrosoftChangJieGUID) {
1344 return TextInputProcessorID::eMicrosoftChangJie;
1346 // {761309DE-317A-11D4-9B5D-0080C882687E} (Win7)
1347 static constexpr GUID kMicrosoftPhoneticGUID = {
1348 0x761309DE,
1349 0x317A,
1350 0x11D4,
1351 {0x9B, 0x5D, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1352 if (mActiveTIPGUID == kMicrosoftPhoneticGUID) {
1353 return TextInputProcessorID::eMicrosoftPhonetic;
1355 // {6024B45F-5C54-11D4-B921-0080C882687E} (Win7, Win8.1, Win10)
1356 static constexpr GUID kMicrosoftQuickGUID = {
1357 0x6024B45F,
1358 0x5C54,
1359 0x11D4,
1360 {0xB9, 0x21, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1361 if (mActiveTIPGUID == kMicrosoftQuickGUID) {
1362 return TextInputProcessorID::eMicrosoftQuick;
1364 // {F3BA907A-6C7E-11D4-97FA-0080C882687E} (Win7)
1365 static constexpr GUID kMicrosoftNewChangJieGUID = {
1366 0xF3BA907A,
1367 0x6C7E,
1368 0x11D4,
1369 {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1370 if (mActiveTIPGUID == kMicrosoftNewChangJieGUID) {
1371 return TextInputProcessorID::eMicrosoftNewChangJie;
1373 // {B2F9C502-1742-11D4-9790-0080C882687E} (Win7)
1374 static constexpr GUID kMicrosoftNewPhoneticGUID = {
1375 0xB2F9C502,
1376 0x1742,
1377 0x11D4,
1378 {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1379 if (mActiveTIPGUID == kMicrosoftNewPhoneticGUID) {
1380 return TextInputProcessorID::eMicrosoftNewPhonetic;
1382 // {0B883BA0-C1C7-11D4-87F9-0080C882687E} (Win7)
1383 static constexpr GUID kMicrosoftNewQuickGUID = {
1384 0x0B883BA0,
1385 0xC1C7,
1386 0x11D4,
1387 {0x87, 0xF9, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1388 if (mActiveTIPGUID == kMicrosoftNewQuickGUID) {
1389 return TextInputProcessorID::eMicrosoftNewQuick;
1392 // NOTE: There are some other Traditional Chinese TIPs installed in Windows:
1393 // * Chinese Traditional Array (version 6.0)
1394 // - {D38EFF65-AA46-4FD5-91A7-67845FB02F5B} (Win7, Win8.1)
1395 // * Chinese Traditional DaYi (version 6.0)
1396 // - {037B2C25-480C-4D7F-B027-D6CA6B69788A} (Win7, Win8.1)
1398 // {B58630B5-0ED3-4335-BBC9-E77BBCB43CAD}
1399 static const GUID kFreeChangJieGUID = {
1400 0xB58630B5,
1401 0x0ED3,
1402 0x4335,
1403 {0xBB, 0xC9, 0xE7, 0x7B, 0xBC, 0xB4, 0x3C, 0xAD}};
1404 if (mActiveTIPGUID == kFreeChangJieGUID) {
1405 return TextInputProcessorID::eFreeChangJie;
1408 return TextInputProcessorID::eUnknown;
1411 TextInputProcessorID ComputeActiveTIPAsSimplifiedChinese() {
1412 // FYI: This matches with neither "Microsoft Pinyin ABC Input Style" nor
1413 // "Microsoft Pinyin New Experience Input Style" on Win7.
1414 // {FA550B04-5AD7-411F-A5AC-CA038EC515D7} (Win8.1, Win10)
1415 static constexpr GUID kMicrosoftPinyinGUID = {
1416 0xFA550B04,
1417 0x5AD7,
1418 0x411F,
1419 {0xA5, 0xAC, 0xCA, 0x03, 0x8E, 0xC5, 0x15, 0xD7}};
1420 if (mActiveTIPGUID == kMicrosoftPinyinGUID) {
1421 return TextInputProcessorID::eMicrosoftPinyin;
1424 // {F3BA9077-6C7E-11D4-97FA-0080C882687E} (Win7)
1425 static constexpr GUID kMicrosoftPinyinNewExperienceInputStyleGUID = {
1426 0xF3BA9077,
1427 0x6C7E,
1428 0x11D4,
1429 {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1430 if (mActiveTIPGUID == kMicrosoftPinyinNewExperienceInputStyleGUID) {
1431 return TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle;
1433 // {82590C13-F4DD-44F4-BA1D-8667246FDF8E} (Win8.1, Win10)
1434 static constexpr GUID kMicrosoftWubiGUID = {
1435 0x82590C13,
1436 0xF4DD,
1437 0x44F4,
1438 {0xBA, 0x1D, 0x86, 0x67, 0x24, 0x6F, 0xDF, 0x8E}};
1439 if (mActiveTIPGUID == kMicrosoftWubiGUID) {
1440 return TextInputProcessorID::eMicrosoftWubi;
1442 // NOTE: There are some other Simplified Chinese TIPs installed in Windows:
1443 // * Chinese Simplified QuanPin (version 6.0)
1444 // - {54FC610E-6ABD-4685-9DDD-A130BDF1B170} (Win8.1)
1445 // * Chinese Simplified ZhengMa (version 6.0)
1446 // - {733B4D81-3BC3-4132-B91A-E9CDD5E2BFC9} (Win8.1)
1447 // * Chinese Simplified ShuangPin (version 6.0)
1448 // - {EF63706D-31C4-490E-9DBB-BD150ADC454B} (Win8.1)
1449 // * Microsoft Pinyin ABC Input Style
1450 // - {FCA121D2-8C6D-41FB-B2DE-A2AD110D4820} (Win7)
1451 return TextInputProcessorID::eUnknown;
1454 TextInputProcessorID ComputeActiveTIPAsKorean() {
1455 // {B5FE1F02-D5F2-4445-9C03-C568F23C99A1} (Win7, Win8.1, Win10)
1456 static constexpr GUID kMicrosoftIMEForKoreanGUID = {
1457 0xB5FE1F02,
1458 0xD5F2,
1459 0x4445,
1460 {0x9C, 0x03, 0xC5, 0x68, 0xF2, 0x3C, 0x99, 0xA1}};
1461 if (mActiveTIPGUID == kMicrosoftIMEForKoreanGUID) {
1462 return TextInputProcessorID::eMicrosoftIMEForKorean;
1464 // {B60AF051-257A-46BC-B9D3-84DAD819BAFB} (Win8.1, Win10)
1465 static constexpr GUID kMicrosoftOldHangulGUID = {
1466 0xB60AF051,
1467 0x257A,
1468 0x46BC,
1469 {0xB9, 0xD3, 0x84, 0xDA, 0xD8, 0x19, 0xBA, 0xFB}};
1470 if (mActiveTIPGUID == kMicrosoftOldHangulGUID) {
1471 return TextInputProcessorID::eMicrosoftOldHangul;
1474 // NOTE: There is the other Korean TIP installed in Windows:
1475 // * Microsoft IME 2010
1476 // - {48878C45-93F9-4aaf-A6A1-272CD863C4F5} (Win7)
1478 return TextInputProcessorID::eUnknown;
1481 public: // ITfInputProcessorProfileActivationSink
1482 STDMETHODIMP OnActivated(DWORD, LANGID, REFCLSID, REFGUID, REFGUID, HKL,
1483 DWORD);
1485 private:
1486 TSFStaticSink();
1487 virtual ~TSFStaticSink() {}
1489 bool EnsureInitActiveTIPKeyboard();
1491 void Destroy();
1493 void GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
1494 REFGUID aProfile, nsAString& aDescription);
1495 bool IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
1496 REFGUID aProfile);
1498 TextInputProcessorID mActiveTIP;
1500 // Cookie of installing ITfInputProcessorProfileActivationSink
1501 DWORD mIPProfileCookie;
1503 LANGID mLangID;
1505 // True if current IME is implemented with IMM.
1506 bool mIsIMM_IME;
1507 // True if OnActivated() is already called
1508 bool mOnActivatedCalled;
1510 RefPtr<ITfThreadMgr> mThreadMgr;
1511 RefPtr<ITfInputProcessorProfiles> mInputProcessorProfiles;
1513 // Active TIP keyboard's description. If active language profile isn't TIP,
1514 // i.e., IMM-IME or just a keyboard layout, this is empty.
1515 nsString mActiveTIPKeyboardDescription;
1517 // Active TIP's GUID and CLSID
1518 GUID mActiveTIPGUID;
1519 CLSID mActiveTIPCLSID;
1521 static StaticRefPtr<TSFStaticSink> sInstance;
1524 StaticRefPtr<TSFStaticSink> TSFStaticSink::sInstance;
1526 TSFStaticSink::TSFStaticSink()
1527 : mActiveTIP(TextInputProcessorID::eNotComputed),
1528 mIPProfileCookie(TF_INVALID_COOKIE),
1529 mLangID(0),
1530 mIsIMM_IME(false),
1531 mOnActivatedCalled(false),
1532 mActiveTIPGUID(GUID_NULL) {}
1534 bool TSFStaticSink::Init(ITfThreadMgr* aThreadMgr,
1535 ITfInputProcessorProfiles* aInputProcessorProfiles) {
1536 MOZ_ASSERT(!mThreadMgr && !mInputProcessorProfiles,
1537 "TSFStaticSink::Init() must be called only once");
1539 mThreadMgr = aThreadMgr;
1540 mInputProcessorProfiles = aInputProcessorProfiles;
1542 RefPtr<ITfSource> source;
1543 HRESULT hr =
1544 mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
1545 if (FAILED(hr)) {
1546 MOZ_LOG(gIMELog, LogLevel::Error,
1547 ("0x%p TSFStaticSink::Init() FAILED to get ITfSource "
1548 "instance (0x%08lX)",
1549 this, hr));
1550 return false;
1553 // NOTE: On Vista or later, Windows let us know activate IME changed only
1554 // with ITfInputProcessorProfileActivationSink.
1555 hr = source->AdviseSink(
1556 IID_ITfInputProcessorProfileActivationSink,
1557 static_cast<ITfInputProcessorProfileActivationSink*>(this),
1558 &mIPProfileCookie);
1559 if (FAILED(hr) || mIPProfileCookie == TF_INVALID_COOKIE) {
1560 MOZ_LOG(gIMELog, LogLevel::Error,
1561 ("0x%p TSFStaticSink::Init() FAILED to install "
1562 "ITfInputProcessorProfileActivationSink (0x%08lX)",
1563 this, hr));
1564 return false;
1567 MOZ_LOG(gIMELog, LogLevel::Info,
1568 ("0x%p TSFStaticSink::Init(), "
1569 "mIPProfileCookie=0x%08lX",
1570 this, mIPProfileCookie));
1571 return true;
1574 void TSFStaticSink::Destroy() {
1575 MOZ_LOG(gIMELog, LogLevel::Info,
1576 ("0x%p TSFStaticSink::Shutdown() "
1577 "mIPProfileCookie=0x%08lX",
1578 this, mIPProfileCookie));
1580 if (mIPProfileCookie != TF_INVALID_COOKIE) {
1581 RefPtr<ITfSource> source;
1582 HRESULT hr =
1583 mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
1584 if (FAILED(hr)) {
1585 MOZ_LOG(gIMELog, LogLevel::Error,
1586 ("0x%p TSFStaticSink::Shutdown() FAILED to get "
1587 "ITfSource instance (0x%08lX)",
1588 this, hr));
1589 } else {
1590 hr = source->UnadviseSink(mIPProfileCookie);
1591 if (FAILED(hr)) {
1592 MOZ_LOG(gIMELog, LogLevel::Error,
1593 ("0x%p TSFTextStore::Shutdown() FAILED to uninstall "
1594 "ITfInputProcessorProfileActivationSink (0x%08lX)",
1595 this, hr));
1600 mThreadMgr = nullptr;
1601 mInputProcessorProfiles = nullptr;
1604 STDMETHODIMP
1605 TSFStaticSink::OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID rclsid,
1606 REFGUID catid, REFGUID guidProfile, HKL hkl,
1607 DWORD dwFlags) {
1608 if ((dwFlags & TF_IPSINK_FLAG_ACTIVE) &&
1609 (dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT ||
1610 catid == GUID_TFCAT_TIP_KEYBOARD)) {
1611 mOnActivatedCalled = true;
1612 mActiveTIP = TextInputProcessorID::eNotComputed;
1613 mActiveTIPGUID = guidProfile;
1614 mActiveTIPCLSID = rclsid;
1615 mLangID = langid & 0xFFFF;
1616 mIsIMM_IME = IsIMM_IME(hkl);
1617 GetTIPDescription(rclsid, langid, guidProfile,
1618 mActiveTIPKeyboardDescription);
1619 if (mActiveTIPGUID != GUID_NULL) {
1620 // key should be "LocaleID|Description". Although GUID of the
1621 // profile is unique key since description may be localized for system
1622 // language, unfortunately, it's too long to record as key with its
1623 // description. Therefore, we should record only the description with
1624 // LocaleID because Microsoft IME may not include language information.
1625 // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
1626 nsAutoString key;
1627 TSFStaticSink::GetActiveTIPNameForTelemetry(key);
1628 Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_WINDOWS, key,
1629 true);
1631 // Notify IMEHandler of changing active keyboard layout.
1632 IMEHandler::OnKeyboardLayoutChanged();
1634 MOZ_LOG(gIMELog, LogLevel::Info,
1635 ("0x%p TSFStaticSink::OnActivated(dwProfileType=%s (0x%08lX), "
1636 "langid=0x%08X, rclsid=%s, catid=%s, guidProfile=%s, hkl=0x%p, "
1637 "dwFlags=0x%08lX (TF_IPSINK_FLAG_ACTIVE: %s)), mIsIMM_IME=%s, "
1638 "mActiveTIPDescription=\"%s\"",
1639 this,
1640 dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR
1641 ? "TF_PROFILETYPE_INPUTPROCESSOR"
1642 : dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT
1643 ? "TF_PROFILETYPE_KEYBOARDLAYOUT"
1644 : "Unknown",
1645 dwProfileType, langid, GetCLSIDNameStr(rclsid).get(),
1646 GetGUIDNameStr(catid).get(), GetGUIDNameStr(guidProfile).get(), hkl,
1647 dwFlags, GetBoolName(dwFlags & TF_IPSINK_FLAG_ACTIVE),
1648 GetBoolName(mIsIMM_IME),
1649 NS_ConvertUTF16toUTF8(mActiveTIPKeyboardDescription).get()));
1650 return S_OK;
1653 bool TSFStaticSink::EnsureInitActiveTIPKeyboard() {
1654 if (mOnActivatedCalled) {
1655 return true;
1658 RefPtr<ITfInputProcessorProfileMgr> profileMgr;
1659 HRESULT hr = mInputProcessorProfiles->QueryInterface(
1660 IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
1661 if (FAILED(hr) || !profileMgr) {
1662 MOZ_LOG(gIMELog, LogLevel::Error,
1663 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1664 "to get input processor profile manager, hr=0x%08lX",
1665 this, hr));
1666 return false;
1669 TF_INPUTPROCESSORPROFILE profile;
1670 hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
1671 if (hr == S_FALSE) {
1672 MOZ_LOG(gIMELog, LogLevel::Info,
1673 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1674 "to get active keyboard layout profile due to no active profile, "
1675 "hr=0x%08lX",
1676 this, hr));
1677 // XXX Should we call OnActivated() with arguments like non-TIP in this
1678 // case?
1679 return false;
1681 if (FAILED(hr)) {
1682 MOZ_LOG(gIMELog, LogLevel::Error,
1683 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1684 "to get active TIP keyboard, hr=0x%08lX",
1685 this, hr));
1686 return false;
1689 MOZ_LOG(gIMELog, LogLevel::Info,
1690 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), "
1691 "calling OnActivated() manually...",
1692 this));
1693 OnActivated(profile.dwProfileType, profile.langid, profile.clsid,
1694 profile.catid, profile.guidProfile, ::GetKeyboardLayout(0),
1695 TF_IPSINK_FLAG_ACTIVE);
1696 return true;
1699 void TSFStaticSink::GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
1700 REFGUID aProfile,
1701 nsAString& aDescription) {
1702 aDescription.Truncate();
1704 if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
1705 return;
1708 BSTR description = nullptr;
1709 HRESULT hr = mInputProcessorProfiles->GetLanguageProfileDescription(
1710 aTextService, aLangID, aProfile, &description);
1711 if (FAILED(hr)) {
1712 MOZ_LOG(gIMELog, LogLevel::Error,
1713 ("0x%p TSFStaticSink::InitActiveTIPDescription() FAILED "
1714 "due to GetLanguageProfileDescription() failure, hr=0x%08lX",
1715 this, hr));
1716 return;
1719 if (description && description[0]) {
1720 aDescription.Assign(description);
1722 ::SysFreeString(description);
1725 bool TSFStaticSink::IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
1726 REFGUID aProfile) {
1727 if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
1728 return false;
1731 RefPtr<IEnumTfLanguageProfiles> enumLangProfiles;
1732 HRESULT hr = mInputProcessorProfiles->EnumLanguageProfiles(
1733 aLangID, getter_AddRefs(enumLangProfiles));
1734 if (FAILED(hr) || !enumLangProfiles) {
1735 MOZ_LOG(gIMELog, LogLevel::Error,
1736 ("0x%p TSFStaticSink::IsTIPCategoryKeyboard(), FAILED "
1737 "to get language profiles enumerator, hr=0x%08lX",
1738 this, hr));
1739 return false;
1742 TF_LANGUAGEPROFILE profile;
1743 ULONG fetch = 0;
1744 while (SUCCEEDED(enumLangProfiles->Next(1, &profile, &fetch)) && fetch) {
1745 // XXX We're not sure a profile is registered with two or more categories.
1746 if (profile.clsid == aTextService && profile.guidProfile == aProfile &&
1747 profile.catid == GUID_TFCAT_TIP_KEYBOARD) {
1748 return true;
1751 return false;
1754 /******************************************************************/
1755 /* TSFTextStore */
1756 /******************************************************************/
1758 StaticRefPtr<ITfThreadMgr> TSFTextStore::sThreadMgr;
1759 StaticRefPtr<ITfMessagePump> TSFTextStore::sMessagePump;
1760 StaticRefPtr<ITfKeystrokeMgr> TSFTextStore::sKeystrokeMgr;
1761 StaticRefPtr<ITfDisplayAttributeMgr> TSFTextStore::sDisplayAttrMgr;
1762 StaticRefPtr<ITfCategoryMgr> TSFTextStore::sCategoryMgr;
1763 StaticRefPtr<ITfCompartment> TSFTextStore::sCompartmentForOpenClose;
1764 StaticRefPtr<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr;
1765 StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext;
1766 StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles;
1767 StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore;
1768 const MSG* TSFTextStore::sHandlingKeyMsg = nullptr;
1769 DWORD TSFTextStore::sClientId = 0;
1770 bool TSFTextStore::sIsKeyboardEventDispatched = false;
1772 #define TEXTSTORE_DEFAULT_VIEW (1)
1774 TSFTextStore::TSFTextStore()
1775 : mEditCookie(0),
1776 mSinkMask(0),
1777 mLock(0),
1778 mLockQueued(0),
1779 mHandlingKeyMessage(0) {
1780 // We hope that 5 or more actions don't occur at once.
1781 mPendingActions.SetCapacity(5);
1783 MOZ_LOG(gIMELog, LogLevel::Info,
1784 ("0x%p TSFTextStore::TSFTextStore() SUCCEEDED", this));
1787 TSFTextStore::~TSFTextStore() {
1788 MOZ_LOG(gIMELog, LogLevel::Info,
1789 ("0x%p TSFTextStore instance is destroyed", this));
1792 bool TSFTextStore::Init(nsWindow* aWidget, const InputContext& aContext) {
1793 MOZ_LOG(gIMELog, LogLevel::Info,
1794 ("0x%p TSFTextStore::Init(aWidget=0x%p)", this, aWidget));
1796 if (NS_WARN_IF(!aWidget) || NS_WARN_IF(aWidget->Destroyed())) {
1797 MOZ_LOG(gIMELog, LogLevel::Error,
1798 ("0x%p TSFTextStore::Init() FAILED due to being initialized with "
1799 "destroyed widget",
1800 this));
1801 return false;
1804 if (mDocumentMgr) {
1805 MOZ_LOG(gIMELog, LogLevel::Error,
1806 ("0x%p TSFTextStore::Init() FAILED due to already initialized",
1807 this));
1808 return false;
1811 mWidget = aWidget;
1812 if (NS_WARN_IF(!mWidget)) {
1813 MOZ_LOG(gIMELog, LogLevel::Error,
1814 ("0x%p TSFTextStore::Init() FAILED "
1815 "due to aWidget is nullptr ",
1816 this));
1817 return false;
1819 mDispatcher = mWidget->GetTextEventDispatcher();
1820 if (NS_WARN_IF(!mDispatcher)) {
1821 MOZ_LOG(gIMELog, LogLevel::Error,
1822 ("0x%p TSFTextStore::Init() FAILED "
1823 "due to aWidget->GetTextEventDispatcher() failure",
1824 this));
1825 return false;
1828 mInPrivateBrowsing = aContext.mInPrivateBrowsing;
1829 SetInputScope(aContext.mHTMLInputType, aContext.mHTMLInputMode);
1831 if (aContext.mURI) {
1832 // We don't need the document URL if it fails, let's ignore the error.
1833 nsAutoCString spec;
1834 if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) {
1835 CopyUTF8toUTF16(spec, mDocumentURL);
1839 // Create document manager
1840 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
1841 RefPtr<ITfDocumentMgr> documentMgr;
1842 HRESULT hr = threadMgr->CreateDocumentMgr(getter_AddRefs(documentMgr));
1843 if (NS_WARN_IF(FAILED(hr))) {
1844 MOZ_LOG(gIMELog, LogLevel::Error,
1845 ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr "
1846 "(0x%08lX)",
1847 this, hr));
1848 return false;
1850 if (NS_WARN_IF(mDestroyed)) {
1851 MOZ_LOG(
1852 gIMELog, LogLevel::Error,
1853 ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr due to "
1854 "TextStore being destroyed during calling "
1855 "ITfThreadMgr::CreateDocumentMgr()",
1856 this));
1857 return false;
1859 // Create context and add it to document manager
1860 RefPtr<ITfContext> context;
1861 hr = documentMgr->CreateContext(sClientId, 0,
1862 static_cast<ITextStoreACP*>(this),
1863 getter_AddRefs(context), &mEditCookie);
1864 if (NS_WARN_IF(FAILED(hr))) {
1865 MOZ_LOG(gIMELog, LogLevel::Error,
1866 ("0x%p TSFTextStore::Init() FAILED to create the context "
1867 "(0x%08lX)",
1868 this, hr));
1869 return false;
1871 if (NS_WARN_IF(mDestroyed)) {
1872 MOZ_LOG(gIMELog, LogLevel::Error,
1873 ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
1874 "TextStore being destroyed during calling "
1875 "ITfDocumentMgr::CreateContext()",
1876 this));
1877 return false;
1880 hr = documentMgr->Push(context);
1881 if (NS_WARN_IF(FAILED(hr))) {
1882 MOZ_LOG(gIMELog, LogLevel::Error,
1883 ("0x%p TSFTextStore::Init() FAILED to push the context (0x%08lX)",
1884 this, hr));
1885 return false;
1887 if (NS_WARN_IF(mDestroyed)) {
1888 MOZ_LOG(gIMELog, LogLevel::Error,
1889 ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
1890 "TextStore being destroyed during calling ITfDocumentMgr::Push()",
1891 this));
1892 documentMgr->Pop(TF_POPF_ALL);
1893 return false;
1896 mDocumentMgr = documentMgr;
1897 mContext = context;
1899 MOZ_LOG(gIMELog, LogLevel::Info,
1900 ("0x%p TSFTextStore::Init() succeeded: "
1901 "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08lX",
1902 this, mDocumentMgr.get(), mContext.get(), mEditCookie));
1904 return true;
1907 void TSFTextStore::Destroy() {
1908 if (mBeingDestroyed) {
1909 return;
1912 MOZ_LOG(gIMELog, LogLevel::Info,
1913 ("0x%p TSFTextStore::Destroy(), mLock=%s, "
1914 "mComposition=%s, mHandlingKeyMessage=%u",
1915 this, GetLockFlagNameStr(mLock).get(),
1916 ToString(mComposition).c_str(), mHandlingKeyMessage));
1918 mDestroyed = true;
1920 // Destroy native caret first because it's not directly related to TSF and
1921 // there may be another textstore which gets focus. So, we should avoid
1922 // to destroy caret after the new one recreates caret.
1923 IMEHandler::MaybeDestroyNativeCaret();
1925 if (mLock) {
1926 mPendingDestroy = true;
1927 return;
1930 AutoRestore<bool> savedBeingDestroyed(mBeingDestroyed);
1931 mBeingDestroyed = true;
1933 // If there is composition, TSF keeps the composition even after the text
1934 // store destroyed. So, we should clear the composition here.
1935 if (mComposition.isSome()) {
1936 CommitCompositionInternal(false);
1939 if (mSink) {
1940 MOZ_LOG(gIMELog, LogLevel::Debug,
1941 ("0x%p TSFTextStore::Destroy(), calling "
1942 "ITextStoreACPSink::OnLayoutChange(TS_LC_DESTROY)...",
1943 this));
1944 RefPtr<ITextStoreACPSink> sink = mSink;
1945 sink->OnLayoutChange(TS_LC_DESTROY, TEXTSTORE_DEFAULT_VIEW);
1948 // If this is called during handling a keydown or keyup message, we should
1949 // put off to release TSF objects until it completely finishes since
1950 // MS-IME for Japanese refers some objects without grabbing them.
1951 if (!mHandlingKeyMessage) {
1952 ReleaseTSFObjects();
1955 MOZ_LOG(gIMELog, LogLevel::Info,
1956 ("0x%p TSFTextStore::Destroy() succeeded", this));
1959 void TSFTextStore::ReleaseTSFObjects() {
1960 MOZ_ASSERT(!mHandlingKeyMessage);
1962 MOZ_LOG(gIMELog, LogLevel::Info,
1963 ("0x%p TSFTextStore::ReleaseTSFObjects()", this));
1965 mDocumentURL.Truncate();
1966 mContext = nullptr;
1967 if (mDocumentMgr) {
1968 RefPtr<ITfDocumentMgr> documentMgr = mDocumentMgr.forget();
1969 documentMgr->Pop(TF_POPF_ALL);
1971 mSink = nullptr;
1972 mWidget = nullptr;
1973 mDispatcher = nullptr;
1975 if (!mMouseTrackers.IsEmpty()) {
1976 MOZ_LOG(gIMELog, LogLevel::Debug,
1977 ("0x%p TSFTextStore::ReleaseTSFObjects(), "
1978 "removing a mouse tracker...",
1979 this));
1980 mMouseTrackers.Clear();
1983 MOZ_LOG(gIMELog, LogLevel::Debug,
1984 ("0x%p TSFTextStore::ReleaseTSFObjects() completed", this));
1987 STDMETHODIMP
1988 TSFTextStore::QueryInterface(REFIID riid, void** ppv) {
1989 *ppv = nullptr;
1990 if ((IID_IUnknown == riid) || (IID_ITextStoreACP == riid)) {
1991 *ppv = static_cast<ITextStoreACP*>(this);
1992 } else if (IID_ITfContextOwnerCompositionSink == riid) {
1993 *ppv = static_cast<ITfContextOwnerCompositionSink*>(this);
1994 } else if (IID_ITfMouseTrackerACP == riid) {
1995 *ppv = static_cast<ITfMouseTrackerACP*>(this);
1997 if (*ppv) {
1998 AddRef();
1999 return S_OK;
2002 MOZ_LOG(gIMELog, LogLevel::Error,
2003 ("0x%p TSFTextStore::QueryInterface() FAILED, riid=%s", this,
2004 GetRIIDNameStr(riid).get()));
2005 return E_NOINTERFACE;
2008 STDMETHODIMP
2009 TSFTextStore::AdviseSink(REFIID riid, IUnknown* punk, DWORD dwMask) {
2010 MOZ_LOG(
2011 gIMELog, LogLevel::Info,
2012 ("0x%p TSFTextStore::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), "
2013 "mSink=0x%p, mSinkMask=%s",
2014 this, GetRIIDNameStr(riid).get(), punk, GetSinkMaskNameStr(dwMask).get(),
2015 mSink.get(), GetSinkMaskNameStr(mSinkMask).get()));
2017 if (!punk) {
2018 MOZ_LOG(gIMELog, LogLevel::Error,
2019 ("0x%p TSFTextStore::AdviseSink() FAILED due to the null punk",
2020 this));
2021 return E_UNEXPECTED;
2024 if (IID_ITextStoreACPSink != riid) {
2025 MOZ_LOG(gIMELog, LogLevel::Error,
2026 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2027 "unsupported interface",
2028 this));
2029 return E_INVALIDARG; // means unsupported interface.
2032 if (!mSink) {
2033 // Install sink
2034 punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink));
2035 if (!mSink) {
2036 MOZ_LOG(gIMELog, LogLevel::Error,
2037 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2038 "punk not having the interface",
2039 this));
2040 return E_UNEXPECTED;
2042 } else {
2043 // If sink is already installed we check to see if they are the same
2044 // Get IUnknown from both sides for comparison
2045 RefPtr<IUnknown> comparison1, comparison2;
2046 punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
2047 mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
2048 if (comparison1 != comparison2) {
2049 MOZ_LOG(gIMELog, LogLevel::Error,
2050 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2051 "the sink being different from the stored sink",
2052 this));
2053 return CONNECT_E_ADVISELIMIT;
2056 // Update mask either for a new sink or an existing sink
2057 mSinkMask = dwMask;
2058 return S_OK;
2061 STDMETHODIMP
2062 TSFTextStore::UnadviseSink(IUnknown* punk) {
2063 MOZ_LOG(gIMELog, LogLevel::Info,
2064 ("0x%p TSFTextStore::UnadviseSink(punk=0x%p), mSink=0x%p", this, punk,
2065 mSink.get()));
2067 if (!punk) {
2068 MOZ_LOG(gIMELog, LogLevel::Error,
2069 ("0x%p TSFTextStore::UnadviseSink() FAILED due to the null punk",
2070 this));
2071 return E_INVALIDARG;
2073 if (!mSink) {
2074 MOZ_LOG(gIMELog, LogLevel::Error,
2075 ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
2076 "any sink not stored",
2077 this));
2078 return CONNECT_E_NOCONNECTION;
2080 // Get IUnknown from both sides for comparison
2081 RefPtr<IUnknown> comparison1, comparison2;
2082 punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
2083 mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
2084 // Unadvise only if sinks are the same
2085 if (comparison1 != comparison2) {
2086 MOZ_LOG(gIMELog, LogLevel::Error,
2087 ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
2088 "the sink being different from the stored sink",
2089 this));
2090 return CONNECT_E_NOCONNECTION;
2092 mSink = nullptr;
2093 mSinkMask = 0;
2094 return S_OK;
2097 STDMETHODIMP
2098 TSFTextStore::RequestLock(DWORD dwLockFlags, HRESULT* phrSession) {
2099 MOZ_LOG(gIMELog, LogLevel::Info,
2100 ("0x%p TSFTextStore::RequestLock(dwLockFlags=%s, phrSession=0x%p), "
2101 "mLock=%s, mDestroyed=%s",
2102 this, GetLockFlagNameStr(dwLockFlags).get(), phrSession,
2103 GetLockFlagNameStr(mLock).get(), GetBoolName(mDestroyed)));
2105 if (!mSink) {
2106 MOZ_LOG(gIMELog, LogLevel::Error,
2107 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2108 "any sink not stored",
2109 this));
2110 return E_FAIL;
2112 if (mDestroyed &&
2113 (mContentForTSF.isNothing() || mSelectionForTSF.isNothing())) {
2114 MOZ_LOG(gIMELog, LogLevel::Error,
2115 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2116 "being destroyed and no information of the contents",
2117 this));
2118 return E_FAIL;
2120 if (!phrSession) {
2121 MOZ_LOG(gIMELog, LogLevel::Error,
2122 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2123 "null phrSession",
2124 this));
2125 return E_INVALIDARG;
2128 if (!mLock) {
2129 // put on lock
2130 mLock = dwLockFlags & (~TS_LF_SYNC);
2131 MOZ_LOG(
2132 gIMELog, LogLevel::Info,
2133 ("0x%p Locking (%s) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
2134 ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
2135 this, GetLockFlagNameStr(mLock).get()));
2136 // Don't release this instance during this lock because this is called by
2137 // TSF but they don't grab us during this call.
2138 RefPtr<TSFTextStore> kungFuDeathGrip(this);
2139 RefPtr<ITextStoreACPSink> sink = mSink;
2140 *phrSession = sink->OnLockGranted(mLock);
2141 MOZ_LOG(
2142 gIMELog, LogLevel::Info,
2143 ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2144 "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
2145 this, GetLockFlagNameStr(mLock).get()));
2146 DidLockGranted();
2147 while (mLockQueued) {
2148 mLock = mLockQueued;
2149 mLockQueued = 0;
2150 MOZ_LOG(gIMELog, LogLevel::Info,
2151 ("0x%p Locking for the request in the queue (%s) >>>>>>>>>>>>>>"
2152 ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
2153 ">>>>>",
2154 this, GetLockFlagNameStr(mLock).get()));
2155 sink->OnLockGranted(mLock);
2156 MOZ_LOG(gIMELog, LogLevel::Info,
2157 ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2158 "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2159 "<<<<<",
2160 this, GetLockFlagNameStr(mLock).get()));
2161 DidLockGranted();
2164 // The document is now completely unlocked.
2165 mLock = 0;
2167 MaybeFlushPendingNotifications();
2169 MOZ_LOG(gIMELog, LogLevel::Info,
2170 ("0x%p TSFTextStore::RequestLock() succeeded: *phrSession=%s",
2171 this, GetTextStoreReturnValueName(*phrSession)));
2172 return S_OK;
2175 // only time when reentrant lock is allowed is when caller holds a
2176 // read-only lock and is requesting an async write lock
2177 if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags) &&
2178 !(dwLockFlags & TS_LF_SYNC)) {
2179 *phrSession = TS_S_ASYNC;
2180 mLockQueued = dwLockFlags & (~TS_LF_SYNC);
2182 MOZ_LOG(gIMELog, LogLevel::Info,
2183 ("0x%p TSFTextStore::RequestLock() stores the request in the "
2184 "queue, *phrSession=TS_S_ASYNC",
2185 this));
2186 return S_OK;
2189 // no more locks allowed
2190 MOZ_LOG(gIMELog, LogLevel::Info,
2191 ("0x%p TSFTextStore::RequestLock() didn't allow to lock, "
2192 "*phrSession=TS_E_SYNCHRONOUS",
2193 this));
2194 *phrSession = TS_E_SYNCHRONOUS;
2195 return E_FAIL;
2198 void TSFTextStore::DidLockGranted() {
2199 if (IsReadWriteLocked()) {
2200 // FreeCJ (TIP for Traditional Chinese) calls SetSelection() to set caret
2201 // to the start of composition string and insert a full width space for
2202 // a placeholder with a call of SetText(). After that, it calls
2203 // OnUpdateComposition() without new range. Therefore, let's record the
2204 // composition update information here.
2205 CompleteLastActionIfStillIncomplete();
2207 FlushPendingActions();
2210 // If the widget has gone, we don't need to notify anything.
2211 if (mDestroyed || !mWidget || mWidget->Destroyed()) {
2212 mPendingSelectionChangeData.reset();
2213 mHasReturnedNoLayoutError = false;
2217 void TSFTextStore::DispatchEvent(WidgetGUIEvent& aEvent) {
2218 if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) {
2219 return;
2221 // If the event isn't a query content event, the event may be handled
2222 // asynchronously. So, we should put off to answer from GetTextExt() etc.
2223 if (!aEvent.AsQueryContentEvent()) {
2224 mDeferNotifyingTSFUntilNextUpdate = true;
2226 mWidget->DispatchWindowEvent(aEvent);
2229 void TSFTextStore::FlushPendingActions() {
2230 if (!mWidget || mWidget->Destroyed()) {
2231 // Note that don't clear mContentForTSF because TIP may try to commit
2232 // composition with a document lock. In such case, TSFTextStore needs to
2233 // behave as expected by TIP.
2234 mPendingActions.Clear();
2235 mPendingSelectionChangeData.reset();
2236 mHasReturnedNoLayoutError = false;
2237 return;
2240 // Some TIP may request lock but does nothing during the lock. In such case,
2241 // this should do nothing. For example, when MS-IME for Japanese is active
2242 // and we're inactivating, this case occurs and causes different behavior
2243 // from the other TIPs.
2244 if (mPendingActions.IsEmpty()) {
2245 return;
2248 RefPtr<nsWindow> widget(mWidget);
2249 nsresult rv = mDispatcher->BeginNativeInputTransaction();
2250 if (NS_WARN_IF(NS_FAILED(rv))) {
2251 MOZ_LOG(gIMELog, LogLevel::Error,
2252 ("0x%p TSFTextStore::FlushPendingActions() "
2253 "FAILED due to BeginNativeInputTransaction() failure",
2254 this));
2255 return;
2257 for (uint32_t i = 0; i < mPendingActions.Length(); i++) {
2258 PendingAction& action = mPendingActions[i];
2259 switch (action.mType) {
2260 case PendingAction::Type::eKeyboardEvent:
2261 if (mDestroyed) {
2262 MOZ_LOG(
2263 gIMELog, LogLevel::Warning,
2264 ("0x%p TSFTextStore::FlushPendingActions() "
2265 "IGNORED pending KeyboardEvent(%s) due to already destroyed",
2266 this,
2267 action.mKeyMsg.message == WM_KEYDOWN ? "eKeyDown" : "eKeyUp"));
2269 MOZ_DIAGNOSTIC_ASSERT(action.mKeyMsg.message == WM_KEYDOWN ||
2270 action.mKeyMsg.message == WM_KEYUP);
2271 DispatchKeyboardEventAsProcessedByIME(action.mKeyMsg);
2272 if (!widget || widget->Destroyed()) {
2273 break;
2275 break;
2276 case PendingAction::Type::eCompositionStart: {
2277 MOZ_LOG(gIMELog, LogLevel::Debug,
2278 ("0x%p TSFTextStore::FlushPendingActions() "
2279 "flushing Type::eCompositionStart={ mSelectionStart=%ld, "
2280 "mSelectionLength=%ld }, mDestroyed=%s",
2281 this, action.mSelectionStart, action.mSelectionLength,
2282 GetBoolName(mDestroyed)));
2284 if (mDestroyed) {
2285 MOZ_LOG(gIMELog, LogLevel::Warning,
2286 ("0x%p TSFTextStore::FlushPendingActions() "
2287 "IGNORED pending compositionstart due to already destroyed",
2288 this));
2289 break;
2292 if (action.mAdjustSelection) {
2293 // Select composition range so the new composition replaces the range
2294 WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
2295 widget->InitEvent(selectionSet);
2296 selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
2297 selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
2298 selectionSet.mReversed = false;
2299 selectionSet.mExpandToClusterBoundary =
2300 TSFStaticSink::ActiveTIP() !=
2301 TextInputProcessorID::eKeymanDesktop &&
2302 StaticPrefs::
2303 intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
2304 DispatchEvent(selectionSet);
2305 if (!selectionSet.mSucceeded) {
2306 MOZ_LOG(gIMELog, LogLevel::Error,
2307 ("0x%p TSFTextStore::FlushPendingActions() "
2308 "FAILED due to eSetSelection failure",
2309 this));
2310 break;
2314 // eCompositionStart always causes
2315 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. Therefore, we should
2316 // wait to clear mContentForTSF until it's notified.
2317 mDeferClearingContentForTSF = true;
2319 MOZ_LOG(gIMELog, LogLevel::Debug,
2320 ("0x%p TSFTextStore::FlushPendingActions() "
2321 "dispatching compositionstart event...",
2322 this));
2323 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2324 nsEventStatus status;
2325 rv = mDispatcher->StartComposition(status, &eventTime);
2326 if (NS_WARN_IF(NS_FAILED(rv))) {
2327 MOZ_LOG(gIMELog, LogLevel::Error,
2328 ("0x%p TSFTextStore::FlushPendingActions() "
2329 "FAILED to dispatch compositionstart event, "
2330 "IsHandlingCompositionInContent()=%s",
2331 this, GetBoolName(IsHandlingCompositionInContent())));
2332 // XXX Is this right? If there is a composition in content,
2333 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2334 mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
2336 if (!widget || widget->Destroyed()) {
2337 break;
2339 break;
2341 case PendingAction::Type::eCompositionUpdate: {
2342 MOZ_LOG(gIMELog, LogLevel::Debug,
2343 ("0x%p TSFTextStore::FlushPendingActions() "
2344 "flushing Type::eCompositionUpdate={ mData=\"%s\", "
2345 "mRanges=0x%p, mRanges->Length()=%zu }",
2346 this, GetEscapedUTF8String(action.mData).get(),
2347 action.mRanges.get(),
2348 action.mRanges ? action.mRanges->Length() : 0));
2350 // eCompositionChange causes a DOM text event, the IME will be notified
2351 // of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. In this case, we
2352 // should not clear mContentForTSF until we notify the IME of the
2353 // composition update.
2354 mDeferClearingContentForTSF = true;
2356 rv = mDispatcher->SetPendingComposition(action.mData, action.mRanges);
2357 if (NS_WARN_IF(NS_FAILED(rv))) {
2358 MOZ_LOG(gIMELog, LogLevel::Error,
2359 ("0x%p TSFTextStore::FlushPendingActions() "
2360 "FAILED to setting pending composition... "
2361 "IsHandlingCompositionInContent()=%s",
2362 this, GetBoolName(IsHandlingCompositionInContent())));
2363 // XXX Is this right? If there is a composition in content,
2364 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2365 mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
2366 } else {
2367 MOZ_LOG(gIMELog, LogLevel::Debug,
2368 ("0x%p TSFTextStore::FlushPendingActions() "
2369 "dispatching compositionchange event...",
2370 this));
2371 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2372 nsEventStatus status;
2373 rv = mDispatcher->FlushPendingComposition(status, &eventTime);
2374 if (NS_WARN_IF(NS_FAILED(rv))) {
2375 MOZ_LOG(gIMELog, LogLevel::Error,
2376 ("0x%p TSFTextStore::FlushPendingActions() "
2377 "FAILED to dispatch compositionchange event, "
2378 "IsHandlingCompositionInContent()=%s",
2379 this, GetBoolName(IsHandlingCompositionInContent())));
2380 // XXX Is this right? If there is a composition in content,
2381 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2382 mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
2384 // Be aware, the mWidget might already have been destroyed.
2386 break;
2388 case PendingAction::Type::eCompositionEnd: {
2389 MOZ_LOG(gIMELog, LogLevel::Debug,
2390 ("0x%p TSFTextStore::FlushPendingActions() "
2391 "flushing Type::eCompositionEnd={ mData=\"%s\" }",
2392 this, GetEscapedUTF8String(action.mData).get()));
2394 // Dispatching eCompositionCommit causes a DOM text event, then,
2395 // the IME will be notified of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
2396 // when focused content actually handles the event. For example,
2397 // when focused content is in a remote process, it's sent when
2398 // all dispatched composition events have been handled in the remote
2399 // process. So, until then, we don't have newer content information.
2400 // Therefore, we need to put off to clear mContentForTSF.
2401 mDeferClearingContentForTSF = true;
2403 MOZ_LOG(gIMELog, LogLevel::Debug,
2404 ("0x%p TSFTextStore::FlushPendingActions(), "
2405 "dispatching compositioncommit event...",
2406 this));
2407 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2408 nsEventStatus status;
2409 rv = mDispatcher->CommitComposition(status, &action.mData, &eventTime);
2410 if (NS_WARN_IF(NS_FAILED(rv))) {
2411 MOZ_LOG(gIMELog, LogLevel::Error,
2412 ("0x%p TSFTextStore::FlushPendingActions() "
2413 "FAILED to dispatch compositioncommit event, "
2414 "IsHandlingCompositionInContent()=%s",
2415 this, GetBoolName(IsHandlingCompositionInContent())));
2416 // XXX Is this right? If there is a composition in content,
2417 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2418 mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
2420 break;
2422 case PendingAction::Type::eSetSelection: {
2423 MOZ_LOG(
2424 gIMELog, LogLevel::Debug,
2425 ("0x%p TSFTextStore::FlushPendingActions() "
2426 "flushing Type::eSetSelection={ mSelectionStart=%ld, "
2427 "mSelectionLength=%ld, mSelectionReversed=%s }, "
2428 "mDestroyed=%s",
2429 this, action.mSelectionStart, action.mSelectionLength,
2430 GetBoolName(action.mSelectionReversed), GetBoolName(mDestroyed)));
2432 if (mDestroyed) {
2433 MOZ_LOG(gIMELog, LogLevel::Warning,
2434 ("0x%p TSFTextStore::FlushPendingActions() "
2435 "IGNORED pending selectionset due to already destroyed",
2436 this));
2437 break;
2440 WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
2441 selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
2442 selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
2443 selectionSet.mReversed = action.mSelectionReversed;
2444 selectionSet.mExpandToClusterBoundary =
2445 TSFStaticSink::ActiveTIP() !=
2446 TextInputProcessorID::eKeymanDesktop &&
2447 StaticPrefs::
2448 intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
2449 DispatchEvent(selectionSet);
2450 if (!selectionSet.mSucceeded) {
2451 MOZ_LOG(gIMELog, LogLevel::Error,
2452 ("0x%p TSFTextStore::FlushPendingActions() "
2453 "FAILED due to eSetSelection failure",
2454 this));
2455 break;
2457 break;
2459 default:
2460 MOZ_CRASH("unexpected action type");
2463 if (widget && !widget->Destroyed()) {
2464 continue;
2467 MOZ_LOG(gIMELog, LogLevel::Info,
2468 ("0x%p TSFTextStore::FlushPendingActions(), "
2469 "qutting since the mWidget has gone",
2470 this));
2471 break;
2473 mPendingActions.Clear();
2476 void TSFTextStore::MaybeFlushPendingNotifications() {
2477 if (mDeferNotifyingTSF) {
2478 MOZ_LOG(gIMELog, LogLevel::Debug,
2479 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2480 "putting off flushing pending notifications due to initializing "
2481 "something...",
2482 this));
2483 return;
2486 if (IsReadLocked()) {
2487 MOZ_LOG(gIMELog, LogLevel::Debug,
2488 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2489 "putting off flushing pending notifications due to being the "
2490 "document locked...",
2491 this));
2492 return;
2495 if (mDeferCommittingComposition) {
2496 MOZ_LOG(gIMELog, LogLevel::Info,
2497 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2498 "calling TSFTextStore::CommitCompositionInternal(false)...",
2499 this));
2500 mDeferCommittingComposition = mDeferCancellingComposition = false;
2501 CommitCompositionInternal(false);
2502 } else if (mDeferCancellingComposition) {
2503 MOZ_LOG(gIMELog, LogLevel::Info,
2504 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2505 "calling TSFTextStore::CommitCompositionInternal(true)...",
2506 this));
2507 mDeferCommittingComposition = mDeferCancellingComposition = false;
2508 CommitCompositionInternal(true);
2511 if (mDeferNotifyingTSFUntilNextUpdate) {
2512 MOZ_LOG(gIMELog, LogLevel::Debug,
2513 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2514 "putting off flushing pending notifications due to being "
2515 "dispatching events...",
2516 this));
2517 return;
2520 if (mPendingDestroy) {
2521 Destroy();
2522 return;
2525 if (mDestroyed) {
2526 // If it's already been destroyed completely, this shouldn't notify TSF of
2527 // anything anymore.
2528 MOZ_LOG(gIMELog, LogLevel::Debug,
2529 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2530 "does nothing because this has already destroyed completely...",
2531 this));
2532 return;
2535 if (!mDeferClearingContentForTSF && mContentForTSF.isSome()) {
2536 mContentForTSF.reset();
2537 MOZ_LOG(gIMELog, LogLevel::Debug,
2538 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2539 "mContentForTSF is set to `Nothing`",
2540 this));
2543 // When there is no cached content, we can sync actual contents and TSF/TIP
2544 // expecting contents.
2545 RefPtr<TSFTextStore> kungFuDeathGrip = this;
2546 Unused << kungFuDeathGrip;
2547 if (mContentForTSF.isNothing()) {
2548 if (mPendingTextChangeData.IsValid()) {
2549 MOZ_LOG(gIMELog, LogLevel::Info,
2550 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2551 "calling TSFTextStore::NotifyTSFOfTextChange()...",
2552 this));
2553 NotifyTSFOfTextChange();
2555 if (mPendingSelectionChangeData.isSome()) {
2556 MOZ_LOG(gIMELog, LogLevel::Info,
2557 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2558 "calling TSFTextStore::NotifyTSFOfSelectionChange()...",
2559 this));
2560 NotifyTSFOfSelectionChange();
2564 if (mHasReturnedNoLayoutError) {
2565 MOZ_LOG(gIMELog, LogLevel::Info,
2566 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2567 "calling TSFTextStore::NotifyTSFOfLayoutChange()...",
2568 this));
2569 NotifyTSFOfLayoutChange();
2573 void TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME() {
2574 // If we've already been destroyed, we cannot do anything.
2575 if (mDestroyed) {
2576 MOZ_LOG(
2577 gIMELog, LogLevel::Debug,
2578 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2579 "does nothing because it's already been destroyed",
2580 this));
2581 return;
2584 // If we're not handling key message or we've already dispatched a keyboard
2585 // event for the handling key message, we should do nothing anymore.
2586 if (!sHandlingKeyMsg || sIsKeyboardEventDispatched) {
2587 MOZ_LOG(
2588 gIMELog, LogLevel::Debug,
2589 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2590 "does nothing because not necessary to dispatch keyboard event",
2591 this));
2592 return;
2595 sIsKeyboardEventDispatched = true;
2596 // If the document is locked, just adding the task to dispatching an event
2597 // to the queue.
2598 if (IsReadLocked()) {
2599 MOZ_LOG(
2600 gIMELog, LogLevel::Debug,
2601 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2602 "adding to dispatch a keyboard event into the queue...",
2603 this));
2604 PendingAction* action = mPendingActions.AppendElement();
2605 action->mType = PendingAction::Type::eKeyboardEvent;
2606 memcpy(&action->mKeyMsg, sHandlingKeyMsg, sizeof(MSG));
2607 return;
2610 // Otherwise, dispatch a keyboard event.
2611 MOZ_LOG(gIMELog, LogLevel::Debug,
2612 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2613 "trying to dispatch a keyboard event...",
2614 this));
2615 DispatchKeyboardEventAsProcessedByIME(*sHandlingKeyMsg);
2618 void TSFTextStore::DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg) {
2619 MOZ_ASSERT(mWidget);
2620 MOZ_ASSERT(!mWidget->Destroyed());
2621 MOZ_ASSERT(!mDestroyed);
2623 ModifierKeyState modKeyState;
2624 MSG msg(aMsg);
2625 msg.wParam = VK_PROCESSKEY;
2626 NativeKey nativeKey(mWidget, msg, modKeyState);
2627 switch (aMsg.message) {
2628 case WM_KEYDOWN:
2629 MOZ_LOG(gIMELog, LogLevel::Debug,
2630 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2631 "dispatching an eKeyDown event...",
2632 this));
2633 nativeKey.HandleKeyDownMessage();
2634 break;
2635 case WM_KEYUP:
2636 MOZ_LOG(gIMELog, LogLevel::Debug,
2637 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2638 "dispatching an eKeyUp event...",
2639 this));
2640 nativeKey.HandleKeyUpMessage();
2641 break;
2642 default:
2643 MOZ_LOG(gIMELog, LogLevel::Error,
2644 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2645 "ERROR, it doesn't handle the message",
2646 this));
2647 break;
2651 STDMETHODIMP
2652 TSFTextStore::GetStatus(TS_STATUS* pdcs) {
2653 MOZ_LOG(gIMELog, LogLevel::Info,
2654 ("0x%p TSFTextStore::GetStatus(pdcs=0x%p)", this, pdcs));
2656 if (!pdcs) {
2657 MOZ_LOG(gIMELog, LogLevel::Error,
2658 ("0x%p TSFTextStore::GetStatus() FAILED due to null pdcs", this));
2659 return E_INVALIDARG;
2661 // We manage on-screen keyboard by own.
2662 pdcs->dwDynamicFlags = TS_SD_INPUTPANEMANUALDISPLAYENABLE;
2663 // we use a "flat" text model for TSF support so no hidden text
2664 pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT;
2665 return S_OK;
2668 STDMETHODIMP
2669 TSFTextStore::QueryInsert(LONG acpTestStart, LONG acpTestEnd, ULONG cch,
2670 LONG* pacpResultStart, LONG* pacpResultEnd) {
2671 MOZ_LOG(
2672 gIMELog, LogLevel::Info,
2673 ("0x%p TSFTextStore::QueryInsert(acpTestStart=%ld, "
2674 "acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)",
2675 this, acpTestStart, acpTestEnd, cch, pacpResultStart, pacpResultEnd));
2677 if (!pacpResultStart || !pacpResultEnd) {
2678 MOZ_LOG(gIMELog, LogLevel::Error,
2679 ("0x%p TSFTextStore::QueryInsert() FAILED due to "
2680 "the null argument",
2681 this));
2682 return E_INVALIDARG;
2685 if (acpTestStart < 0 || acpTestStart > acpTestEnd) {
2686 MOZ_LOG(gIMELog, LogLevel::Error,
2687 ("0x%p TSFTextStore::QueryInsert() FAILED due to "
2688 "wrong argument",
2689 this));
2690 return E_INVALIDARG;
2693 // XXX need to adjust to cluster boundary
2694 // Assume we are given good offsets for now
2695 if (mComposition.isNothing() &&
2696 ((StaticPrefs::
2697 intl_tsf_hack_ms_traditional_chinese_query_insert_result() &&
2698 TSFStaticSink::IsMSChangJieOrMSQuickActive()) ||
2699 (StaticPrefs::
2700 intl_tsf_hack_ms_simplified_chinese_query_insert_result() &&
2701 TSFStaticSink::IsMSPinyinOrMSWubiActive()))) {
2702 MOZ_LOG(gIMELog, LogLevel::Warning,
2703 ("0x%p TSFTextStore::QueryInsert() WARNING using different "
2704 "result for the TIP",
2705 this));
2706 // Chinese TIPs of Microsoft assume that QueryInsert() returns selected
2707 // range which should be removed.
2708 *pacpResultStart = acpTestStart;
2709 *pacpResultEnd = acpTestEnd;
2710 } else {
2711 *pacpResultStart = acpTestStart;
2712 *pacpResultEnd = acpTestStart + cch;
2715 MOZ_LOG(gIMELog, LogLevel::Info,
2716 ("0x%p TSFTextStore::QueryInsert() succeeded: "
2717 "*pacpResultStart=%ld, *pacpResultEnd=%ld)",
2718 this, *pacpResultStart, *pacpResultEnd));
2719 return S_OK;
2722 STDMETHODIMP
2723 TSFTextStore::GetSelection(ULONG ulIndex, ULONG ulCount,
2724 TS_SELECTION_ACP* pSelection, ULONG* pcFetched) {
2725 MOZ_LOG(gIMELog, LogLevel::Info,
2726 ("0x%p TSFTextStore::GetSelection(ulIndex=%lu, ulCount=%lu, "
2727 "pSelection=0x%p, pcFetched=0x%p)",
2728 this, ulIndex, ulCount, pSelection, pcFetched));
2730 if (!IsReadLocked()) {
2731 MOZ_LOG(
2732 gIMELog, LogLevel::Error,
2733 ("0x%p TSFTextStore::GetSelection() FAILED due to not locked", this));
2734 return TS_E_NOLOCK;
2736 if (!ulCount || !pSelection || !pcFetched) {
2737 MOZ_LOG(gIMELog, LogLevel::Error,
2738 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2739 "null argument",
2740 this));
2741 return E_INVALIDARG;
2744 *pcFetched = 0;
2746 if (ulIndex != static_cast<ULONG>(TS_DEFAULT_SELECTION) && ulIndex != 0) {
2747 MOZ_LOG(gIMELog, LogLevel::Error,
2748 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2749 "unsupported selection",
2750 this));
2751 return TS_E_NOSELECTION;
2754 Maybe<Selection>& selectionForTSF = SelectionForTSF();
2755 if (selectionForTSF.isNothing()) {
2756 if (DoNotReturnErrorFromGetSelection()) {
2757 *pSelection = Selection::EmptyACP();
2758 *pcFetched = 1;
2759 MOZ_LOG(
2760 gIMELog, LogLevel::Info,
2761 ("0x%p TSFTextStore::GetSelection() returns fake selection range "
2762 "for avoiding a crash in TSF, *pSelection=%s",
2763 this, mozilla::ToString(*pSelection).c_str()));
2764 return S_OK;
2766 MOZ_LOG(gIMELog, LogLevel::Error,
2767 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2768 "SelectionForTSF() failure",
2769 this));
2770 return E_FAIL;
2772 if (!selectionForTSF->HasRange()) {
2773 *pSelection = Selection::EmptyACP();
2774 *pcFetched = 0;
2775 return TS_E_NOSELECTION;
2777 *pSelection = selectionForTSF->ACPRef();
2778 *pcFetched = 1;
2779 MOZ_LOG(gIMELog, LogLevel::Info,
2780 ("0x%p TSFTextStore::GetSelection() succeeded, *pSelection=%s",
2781 this, mozilla::ToString(*pSelection).c_str()));
2782 return S_OK;
2785 // static
2786 bool TSFTextStore::DoNotReturnErrorFromGetSelection() {
2787 // There is a crash bug of TSF if we return error from GetSelection().
2788 // That was introduced in Anniversary Update (build 14393, see bug 1312302)
2789 // TODO: We should avoid to run this hack on fixed builds. When we get
2790 // exact build number, we should get back here.
2791 static bool sTSFMayCrashIfGetSelectionReturnsError =
2792 IsWin10AnniversaryUpdateOrLater();
2793 return sTSFMayCrashIfGetSelectionReturnsError;
2796 Maybe<TSFTextStore::Content>& TSFTextStore::ContentForTSF() {
2797 // This should be called when the document is locked or the content hasn't
2798 // been abandoned yet.
2799 if (NS_WARN_IF(!IsReadLocked() && mContentForTSF.isNothing())) {
2800 MOZ_LOG(gIMELog, LogLevel::Error,
2801 ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
2802 "called wrong timing, IsReadLocked()=%s, mContentForTSF=Nothing",
2803 this, GetBoolName(IsReadLocked())));
2804 return mContentForTSF;
2807 Maybe<Selection>& selectionForTSF = SelectionForTSF();
2808 if (selectionForTSF.isNothing()) {
2809 MOZ_LOG(gIMELog, LogLevel::Error,
2810 ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
2811 "SelectionForTSF() failure",
2812 this));
2813 mContentForTSF.reset();
2814 return mContentForTSF;
2817 if (mContentForTSF.isNothing()) {
2818 MOZ_DIAGNOSTIC_ASSERT(
2819 !mIsInitializingContentForTSF,
2820 "TSFTextStore::ContentForTSF() shouldn't be called recursively");
2822 AutoNotifyingTSFBatch deferNotifyingTSF(*this);
2823 AutoRestore<bool> saveInitializingContetTSF(mIsInitializingContentForTSF);
2824 mIsInitializingContentForTSF = true;
2826 nsString text; // Don't use auto string for avoiding to copy long string.
2827 if (NS_WARN_IF(!GetCurrentText(text))) {
2828 MOZ_LOG(gIMELog, LogLevel::Error,
2829 ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
2830 "GetCurrentText() failure",
2831 this));
2832 return mContentForTSF;
2835 MOZ_DIAGNOSTIC_ASSERT(mContentForTSF.isNothing(),
2836 "How was it initialized recursively?");
2837 mContentForTSF.reset(); // For avoiding crash in release channel
2838 mContentForTSF.emplace(*this, text);
2839 // Basically, the cached content which is expected by TSF/TIP should be
2840 // cleared after active composition is committed or the document lock is
2841 // unlocked. However, in e10s mode, content will be modified
2842 // asynchronously. In such case, mDeferClearingContentForTSF may be
2843 // true until whole dispatched events are handled by the focused editor.
2844 mDeferClearingContentForTSF = false;
2847 MOZ_LOG(gIMELog, LogLevel::Debug,
2848 ("0x%p TSFTextStore::ContentForTSF(): mContentForTSF=%s", this,
2849 mozilla::ToString(mContentForTSF).c_str()));
2851 return mContentForTSF;
2854 bool TSFTextStore::CanAccessActualContentDirectly() const {
2855 if (mContentForTSF.isNothing() || mSelectionForTSF.isNothing()) {
2856 return true;
2859 // If the cached content has been changed by something except composition,
2860 // the content cache may be different from actual content.
2861 if (mPendingTextChangeData.IsValid() &&
2862 !mPendingTextChangeData.mCausedOnlyByComposition) {
2863 return false;
2866 // If the cached selection isn't changed, cached content and actual content
2867 // should be same.
2868 if (mPendingSelectionChangeData.isNothing()) {
2869 return true;
2872 return mSelectionForTSF->EqualsExceptDirection(*mPendingSelectionChangeData);
2875 bool TSFTextStore::GetCurrentText(nsAString& aTextContent) {
2876 if (mContentForTSF.isSome()) {
2877 aTextContent = mContentForTSF->TextRef();
2878 return true;
2881 MOZ_ASSERT(!mDestroyed);
2882 MOZ_ASSERT(mWidget && !mWidget->Destroyed());
2884 MOZ_LOG(gIMELog, LogLevel::Debug,
2885 ("0x%p TSFTextStore::GetCurrentText(): "
2886 "retrieving text from the content...",
2887 this));
2889 WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
2890 mWidget);
2891 queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
2892 mWidget->InitEvent(queryTextContentEvent);
2893 DispatchEvent(queryTextContentEvent);
2894 if (NS_WARN_IF(queryTextContentEvent.Failed())) {
2895 MOZ_LOG(gIMELog, LogLevel::Error,
2896 ("0x%p TSFTextStore::GetCurrentText(), FAILED, due to "
2897 "eQueryTextContent failure",
2898 this));
2899 aTextContent.Truncate();
2900 return false;
2903 aTextContent = queryTextContentEvent.mReply->DataRef();
2904 return true;
2907 Maybe<TSFTextStore::Selection>& TSFTextStore::SelectionForTSF() {
2908 if (mSelectionForTSF.isNothing()) {
2909 MOZ_ASSERT(!mDestroyed);
2910 // If the window has never been available, we should crash since working
2911 // with broken values may make TIP confused.
2912 if (!mWidget || mWidget->Destroyed()) {
2913 MOZ_ASSERT_UNREACHABLE("There should be non-destroyed widget");
2916 MOZ_DIAGNOSTIC_ASSERT(
2917 !mIsInitializingSelectionForTSF,
2918 "TSFTextStore::SelectionForTSF() shouldn't be called recursively");
2920 AutoNotifyingTSFBatch deferNotifyingTSF(*this);
2921 AutoRestore<bool> saveInitializingSelectionForTSF(
2922 mIsInitializingSelectionForTSF);
2923 mIsInitializingSelectionForTSF = true;
2925 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
2926 mWidget);
2927 mWidget->InitEvent(querySelectedTextEvent);
2928 DispatchEvent(querySelectedTextEvent);
2929 if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
2930 return mSelectionForTSF;
2932 MOZ_DIAGNOSTIC_ASSERT(mSelectionForTSF.isNothing(),
2933 "How was it initialized recursively?");
2934 mSelectionForTSF = Some(Selection(querySelectedTextEvent));
2937 MOZ_LOG(gIMELog, LogLevel::Debug,
2938 ("0x%p TSFTextStore::SelectionForTSF() succeeded, "
2939 "mSelectionForTSF=%s",
2940 this, ToString(mSelectionForTSF).c_str()));
2942 return mSelectionForTSF;
2945 static HRESULT GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength) {
2946 RefPtr<ITfRangeACP> rangeACP;
2947 aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP));
2948 NS_ENSURE_TRUE(rangeACP, E_FAIL);
2949 return rangeACP->GetExtent(aStart, aLength);
2952 static TextRangeType GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE& aDisplayAttr) {
2953 switch (aDisplayAttr.bAttr) {
2954 case TF_ATTR_TARGET_CONVERTED:
2955 return TextRangeType::eSelectedClause;
2956 case TF_ATTR_CONVERTED:
2957 return TextRangeType::eConvertedClause;
2958 case TF_ATTR_TARGET_NOTCONVERTED:
2959 return TextRangeType::eSelectedRawClause;
2960 default:
2961 return TextRangeType::eRawClause;
2965 HRESULT
2966 TSFTextStore::GetDisplayAttribute(ITfProperty* aAttrProperty, ITfRange* aRange,
2967 TF_DISPLAYATTRIBUTE* aResult) {
2968 NS_ENSURE_TRUE(aAttrProperty, E_FAIL);
2969 NS_ENSURE_TRUE(aRange, E_FAIL);
2970 NS_ENSURE_TRUE(aResult, E_FAIL);
2972 HRESULT hr;
2974 if (MOZ_LOG_TEST(gIMELog, LogLevel::Debug)) {
2975 LONG start = 0, length = 0;
2976 hr = GetRangeExtent(aRange, &start, &length);
2977 MOZ_LOG(gIMELog, LogLevel::Debug,
2978 ("0x%p TSFTextStore::GetDisplayAttribute(): "
2979 "GetDisplayAttribute range=%ld-%ld (hr=%s)",
2980 this, start - mComposition->StartOffset(),
2981 start - mComposition->StartOffset() + length,
2982 GetCommonReturnValueName(hr)));
2985 VARIANT propValue;
2986 ::VariantInit(&propValue);
2987 hr = aAttrProperty->GetValue(TfEditCookie(mEditCookie), aRange, &propValue);
2988 if (FAILED(hr)) {
2989 MOZ_LOG(gIMELog, LogLevel::Error,
2990 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
2991 "ITfProperty::GetValue() failed",
2992 this));
2993 return hr;
2995 if (VT_I4 != propValue.vt) {
2996 MOZ_LOG(gIMELog, LogLevel::Error,
2997 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
2998 "ITfProperty::GetValue() returns non-VT_I4 value",
2999 this));
3000 ::VariantClear(&propValue);
3001 return E_FAIL;
3004 RefPtr<ITfCategoryMgr> categoryMgr = GetCategoryMgr();
3005 if (NS_WARN_IF(!categoryMgr)) {
3006 return E_FAIL;
3008 GUID guid;
3009 hr = categoryMgr->GetGUID(DWORD(propValue.lVal), &guid);
3010 ::VariantClear(&propValue);
3011 if (FAILED(hr)) {
3012 MOZ_LOG(gIMELog, LogLevel::Error,
3013 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3014 "ITfCategoryMgr::GetGUID() failed",
3015 this));
3016 return hr;
3019 RefPtr<ITfDisplayAttributeMgr> displayAttrMgr = GetDisplayAttributeMgr();
3020 if (NS_WARN_IF(!displayAttrMgr)) {
3021 return E_FAIL;
3023 RefPtr<ITfDisplayAttributeInfo> info;
3024 hr = displayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info),
3025 nullptr);
3026 if (FAILED(hr) || !info) {
3027 MOZ_LOG(gIMELog, LogLevel::Error,
3028 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3029 "ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed",
3030 this));
3031 return hr;
3034 hr = info->GetAttributeInfo(aResult);
3035 if (FAILED(hr)) {
3036 MOZ_LOG(gIMELog, LogLevel::Error,
3037 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3038 "ITfDisplayAttributeInfo::GetAttributeInfo() failed",
3039 this));
3040 return hr;
3043 MOZ_LOG(gIMELog, LogLevel::Debug,
3044 ("0x%p TSFTextStore::GetDisplayAttribute() succeeded: "
3045 "Result={ %s }",
3046 this, GetDisplayAttrStr(*aResult).get()));
3047 return S_OK;
3050 HRESULT
3051 TSFTextStore::RestartCompositionIfNecessary(ITfRange* aRangeNew) {
3052 MOZ_LOG(gIMELog, LogLevel::Debug,
3053 ("0x%p TSFTextStore::RestartCompositionIfNecessary("
3054 "aRangeNew=0x%p), mComposition=%s",
3055 this, aRangeNew, ToString(mComposition).c_str()));
3057 if (mComposition.isNothing()) {
3058 MOZ_LOG(gIMELog, LogLevel::Error,
3059 ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
3060 "due to no composition view",
3061 this));
3062 return E_FAIL;
3065 HRESULT hr;
3066 RefPtr<ITfCompositionView> pComposition(mComposition->GetView());
3067 RefPtr<ITfRange> composingRange(aRangeNew);
3068 if (!composingRange) {
3069 hr = pComposition->GetRange(getter_AddRefs(composingRange));
3070 if (FAILED(hr)) {
3071 MOZ_LOG(gIMELog, LogLevel::Error,
3072 ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
3073 "FAILED due to pComposition->GetRange() failure",
3074 this));
3075 return hr;
3079 // Get starting offset of the composition
3080 LONG compStart = 0, compLength = 0;
3081 hr = GetRangeExtent(composingRange, &compStart, &compLength);
3082 if (FAILED(hr)) {
3083 MOZ_LOG(gIMELog, LogLevel::Error,
3084 ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
3085 "due to GetRangeExtent() failure",
3086 this));
3087 return hr;
3090 if (mComposition->StartOffset() == compStart &&
3091 mComposition->Length() == compLength) {
3092 return S_OK;
3095 MOZ_LOG(gIMELog, LogLevel::Debug,
3096 ("0x%p TSFTextStore::RestartCompositionIfNecessary(), "
3097 "restaring composition because of compostion range is changed "
3098 "(range=%ld-%ld, mComposition=%s)",
3099 this, compStart, compStart + compLength,
3100 ToString(mComposition).c_str()));
3102 // If the queried composition length is different from the length
3103 // of our composition string, OnUpdateComposition is being called
3104 // because a part of the original composition was committed.
3105 hr = RestartComposition(*mComposition, pComposition, composingRange);
3106 if (FAILED(hr)) {
3107 MOZ_LOG(gIMELog, LogLevel::Error,
3108 ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
3109 "FAILED due to RestartComposition() failure",
3110 this));
3111 return hr;
3114 MOZ_LOG(
3115 gIMELog, LogLevel::Debug,
3116 ("0x%p TSFTextStore::RestartCompositionIfNecessary() succeeded", this));
3117 return S_OK;
3120 HRESULT TSFTextStore::RestartComposition(Composition& aCurrentComposition,
3121 ITfCompositionView* aCompositionView,
3122 ITfRange* aNewRange) {
3123 Maybe<Selection>& selectionForTSF = SelectionForTSF();
3125 LONG newStart, newLength;
3126 HRESULT hr = GetRangeExtent(aNewRange, &newStart, &newLength);
3127 LONG newEnd = newStart + newLength;
3129 if (selectionForTSF.isNothing()) {
3130 MOZ_LOG(gIMELog, LogLevel::Error,
3131 ("0x%p TSFTextStore::RestartComposition() FAILED "
3132 "due to SelectionForTSF() failure",
3133 this));
3134 return E_FAIL;
3137 if (FAILED(hr)) {
3138 MOZ_LOG(gIMELog, LogLevel::Error,
3139 ("0x%p TSFTextStore::RestartComposition() FAILED "
3140 "due to GetRangeExtent() failure",
3141 this));
3142 return hr;
3145 // If the new range has no overlap with the crrent range, we just commit
3146 // the composition and restart new composition with the new range but
3147 // current selection range should be preserved.
3148 if (newStart >= aCurrentComposition.EndOffset() ||
3149 newEnd <= aCurrentComposition.StartOffset()) {
3150 RecordCompositionEndAction();
3151 RecordCompositionStartAction(aCompositionView, newStart, newLength, true);
3152 return S_OK;
3155 MOZ_LOG(gIMELog, LogLevel::Debug,
3156 ("0x%p TSFTextStore::RestartComposition(aCompositionView=0x%p, "
3157 "aNewRange=0x%p { newStart=%ld, newLength=%ld }), "
3158 "aCurrentComposition=%s, "
3159 "selectionForTSF=%s",
3160 this, aCompositionView, aNewRange, newStart, newLength,
3161 ToString(aCurrentComposition).c_str(),
3162 ToString(selectionForTSF).c_str()));
3164 // If the new range has an overlap with the current one, we should not commit
3165 // the whole current range to avoid creating an odd undo transaction.
3166 // I.e., the overlapped range which is being composed should not appear in
3167 // undo transaction.
3169 // Backup current composition data and selection data.
3170 Composition oldComposition = aCurrentComposition;
3171 Selection oldSelection = *selectionForTSF;
3173 // Commit only the part of composition.
3174 LONG keepComposingStartOffset =
3175 std::max(oldComposition.StartOffset(), newStart);
3176 LONG keepComposingEndOffset = std::min(oldComposition.EndOffset(), newEnd);
3177 MOZ_ASSERT(
3178 keepComposingStartOffset <= keepComposingEndOffset,
3179 "Why keepComposingEndOffset is smaller than keepComposingStartOffset?");
3180 LONG keepComposingLength = keepComposingEndOffset - keepComposingStartOffset;
3181 // Remove the overlapped part from the commit string.
3182 nsAutoString commitString(oldComposition.DataRef());
3183 commitString.Cut(keepComposingStartOffset - oldComposition.StartOffset(),
3184 keepComposingLength);
3185 // Update the composition string.
3186 Maybe<Content>& contentForTSF = ContentForTSF();
3187 if (contentForTSF.isNothing()) {
3188 MOZ_LOG(gIMELog, LogLevel::Error,
3189 ("0x%p TSFTextStore::RestartComposition() FAILED "
3190 "due to ContentForTSF() failure",
3191 this));
3192 return E_FAIL;
3194 contentForTSF->ReplaceTextWith(oldComposition.StartOffset(),
3195 oldComposition.Length(), commitString);
3196 MOZ_ASSERT(mComposition.isSome());
3197 // Record a compositionupdate action for commit the part of composing string.
3198 PendingAction* action = LastOrNewPendingCompositionUpdate();
3199 if (mComposition.isSome()) {
3200 action->mData = mComposition->DataRef();
3202 action->mRanges->Clear();
3203 // Note that we shouldn't append ranges when composition string
3204 // is empty because it may cause TextComposition confused.
3205 if (!action->mData.IsEmpty()) {
3206 TextRange caretRange;
3207 caretRange.mStartOffset = caretRange.mEndOffset = static_cast<uint32_t>(
3208 oldComposition.StartOffset() + commitString.Length());
3209 caretRange.mRangeType = TextRangeType::eCaret;
3210 action->mRanges->AppendElement(caretRange);
3212 action->mIncomplete = false;
3214 // Record compositionend action.
3215 RecordCompositionEndAction();
3217 // Record compositionstart action only with the new start since this method
3218 // hasn't restored composing string yet.
3219 RecordCompositionStartAction(aCompositionView, newStart, 0, false);
3221 // Restore the latest text content and selection.
3222 contentForTSF->ReplaceSelectedTextWith(nsDependentSubstring(
3223 oldComposition.DataRef(),
3224 keepComposingStartOffset - oldComposition.StartOffset(),
3225 keepComposingLength));
3226 selectionForTSF = Some(oldSelection);
3228 MOZ_LOG(gIMELog, LogLevel::Debug,
3229 ("0x%p TSFTextStore::RestartComposition() succeeded, "
3230 "mComposition=%s, selectionForTSF=%s",
3231 this, ToString(mComposition).c_str(),
3232 ToString(selectionForTSF).c_str()));
3234 return S_OK;
3237 static bool GetColor(const TF_DA_COLOR& aTSFColor, nscolor& aResult) {
3238 switch (aTSFColor.type) {
3239 case TF_CT_SYSCOLOR: {
3240 DWORD sysColor = ::GetSysColor(aTSFColor.nIndex);
3241 aResult =
3242 NS_RGB(GetRValue(sysColor), GetGValue(sysColor), GetBValue(sysColor));
3243 return true;
3245 case TF_CT_COLORREF:
3246 aResult = NS_RGB(GetRValue(aTSFColor.cr), GetGValue(aTSFColor.cr),
3247 GetBValue(aTSFColor.cr));
3248 return true;
3249 case TF_CT_NONE:
3250 default:
3251 return false;
3255 static bool GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle,
3256 TextRangeStyle::LineStyle& aTextRangeLineStyle) {
3257 switch (aTSFLineStyle) {
3258 case TF_LS_NONE:
3259 aTextRangeLineStyle = TextRangeStyle::LineStyle::None;
3260 return true;
3261 case TF_LS_SOLID:
3262 aTextRangeLineStyle = TextRangeStyle::LineStyle::Solid;
3263 return true;
3264 case TF_LS_DOT:
3265 aTextRangeLineStyle = TextRangeStyle::LineStyle::Dotted;
3266 return true;
3267 case TF_LS_DASH:
3268 aTextRangeLineStyle = TextRangeStyle::LineStyle::Dashed;
3269 return true;
3270 case TF_LS_SQUIGGLE:
3271 aTextRangeLineStyle = TextRangeStyle::LineStyle::Wavy;
3272 return true;
3273 default:
3274 return false;
3278 HRESULT
3279 TSFTextStore::RecordCompositionUpdateAction() {
3280 MOZ_LOG(
3281 gIMELog, LogLevel::Debug,
3282 ("0x%p TSFTextStore::RecordCompositionUpdateAction(), mComposition=%s",
3283 this, ToString(mComposition).c_str()));
3285 if (mComposition.isNothing()) {
3286 MOZ_LOG(gIMELog, LogLevel::Error,
3287 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3288 "due to no composition view",
3289 this));
3290 return E_FAIL;
3293 // Getting display attributes is *really* complicated!
3294 // We first get the context and the property objects to query for
3295 // attributes, but since a big range can have a variety of values for
3296 // the attribute, we have to find out all the ranges that have distinct
3297 // attribute values. Then we query for what the value represents through
3298 // the display attribute manager and translate that to TextRange to be
3299 // sent in eCompositionChange
3301 RefPtr<ITfProperty> attrPropetry;
3302 HRESULT hr =
3303 mContext->GetProperty(GUID_PROP_ATTRIBUTE, getter_AddRefs(attrPropetry));
3304 if (FAILED(hr) || !attrPropetry) {
3305 MOZ_LOG(gIMELog, LogLevel::Error,
3306 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3307 "due to mContext->GetProperty() failure",
3308 this));
3309 return FAILED(hr) ? hr : E_FAIL;
3312 RefPtr<ITfRange> composingRange;
3313 hr = mComposition->GetView()->GetRange(getter_AddRefs(composingRange));
3314 if (FAILED(hr)) {
3315 MOZ_LOG(gIMELog, LogLevel::Error,
3316 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3317 "FAILED due to mComposition->GetView()->GetRange() failure",
3318 this));
3319 return hr;
3322 RefPtr<IEnumTfRanges> enumRanges;
3323 hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie),
3324 getter_AddRefs(enumRanges), composingRange);
3325 if (FAILED(hr) || !enumRanges) {
3326 MOZ_LOG(gIMELog, LogLevel::Error,
3327 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3328 "due to attrPropetry->EnumRanges() failure",
3329 this));
3330 return FAILED(hr) ? hr : E_FAIL;
3333 // First, put the log of content and selection here.
3334 Maybe<Selection>& selectionForTSF = SelectionForTSF();
3335 if (selectionForTSF.isNothing()) {
3336 MOZ_LOG(gIMELog, LogLevel::Error,
3337 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3338 "due to SelectionForTSF() failure",
3339 this));
3340 return E_FAIL;
3343 PendingAction* action = LastOrNewPendingCompositionUpdate();
3344 action->mData = mComposition->DataRef();
3345 // The ranges might already have been initialized, however, if this is
3346 // called again, that means we need to overwrite the ranges with current
3347 // information.
3348 action->mRanges->Clear();
3350 // Note that we shouldn't append ranges when composition string
3351 // is empty because it may cause TextComposition confused.
3352 if (!action->mData.IsEmpty()) {
3353 TextRange newRange;
3354 // No matter if we have display attribute info or not,
3355 // we always pass in at least one range to eCompositionChange
3356 newRange.mStartOffset = 0;
3357 newRange.mEndOffset = action->mData.Length();
3358 newRange.mRangeType = TextRangeType::eRawClause;
3359 action->mRanges->AppendElement(newRange);
3361 RefPtr<ITfRange> range;
3362 while (enumRanges->Next(1, getter_AddRefs(range), nullptr) == S_OK) {
3363 if (NS_WARN_IF(!range)) {
3364 break;
3367 LONG rangeStart = 0, rangeLength = 0;
3368 if (FAILED(GetRangeExtent(range, &rangeStart, &rangeLength))) {
3369 continue;
3371 // The range may include out of composition string. We should ignore
3372 // outside of the composition string.
3373 LONG start = std::min(std::max(rangeStart, mComposition->StartOffset()),
3374 mComposition->EndOffset());
3375 LONG end = std::max(
3376 std::min(rangeStart + rangeLength, mComposition->EndOffset()),
3377 mComposition->StartOffset());
3378 LONG length = end - start;
3379 if (length < 0) {
3380 MOZ_LOG(gIMELog, LogLevel::Error,
3381 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3382 "ignores invalid range (%ld-%ld)",
3383 this, rangeStart - mComposition->StartOffset(),
3384 rangeStart - mComposition->StartOffset() + rangeLength));
3385 continue;
3387 if (!length) {
3388 MOZ_LOG(gIMELog, LogLevel::Debug,
3389 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3390 "ignores a range due to outside of the composition or empty "
3391 "(%ld-%ld)",
3392 this, rangeStart - mComposition->StartOffset(),
3393 rangeStart - mComposition->StartOffset() + rangeLength));
3394 continue;
3397 TextRange newRange;
3398 newRange.mStartOffset =
3399 static_cast<uint32_t>(start - mComposition->StartOffset());
3400 // The end of the last range in the array is
3401 // always kept at the end of composition
3402 newRange.mEndOffset = mComposition->Length();
3404 TF_DISPLAYATTRIBUTE attr;
3405 hr = GetDisplayAttribute(attrPropetry, range, &attr);
3406 if (FAILED(hr)) {
3407 newRange.mRangeType = TextRangeType::eRawClause;
3408 } else {
3409 newRange.mRangeType = GetGeckoSelectionValue(attr);
3410 if (GetColor(attr.crText, newRange.mRangeStyle.mForegroundColor)) {
3411 newRange.mRangeStyle.mDefinedStyles |=
3412 TextRangeStyle::DEFINED_FOREGROUND_COLOR;
3414 if (GetColor(attr.crBk, newRange.mRangeStyle.mBackgroundColor)) {
3415 newRange.mRangeStyle.mDefinedStyles |=
3416 TextRangeStyle::DEFINED_BACKGROUND_COLOR;
3418 if (GetColor(attr.crLine, newRange.mRangeStyle.mUnderlineColor)) {
3419 newRange.mRangeStyle.mDefinedStyles |=
3420 TextRangeStyle::DEFINED_UNDERLINE_COLOR;
3422 if (GetLineStyle(attr.lsStyle, newRange.mRangeStyle.mLineStyle)) {
3423 newRange.mRangeStyle.mDefinedStyles |=
3424 TextRangeStyle::DEFINED_LINESTYLE;
3425 newRange.mRangeStyle.mIsBoldLine = attr.fBoldLine != 0;
3429 TextRange& lastRange = action->mRanges->LastElement();
3430 if (lastRange.mStartOffset == newRange.mStartOffset) {
3431 // Replace range if last range is the same as this one
3432 // So that ranges don't overlap and confuse the editor
3433 lastRange = newRange;
3434 } else {
3435 lastRange.mEndOffset = newRange.mStartOffset;
3436 action->mRanges->AppendElement(newRange);
3440 // We need to hack for Korean Input System which is Korean standard TIP.
3441 // It sets no change style to IME selection (the selection is always only
3442 // one). So, the composition string looks like normal (or committed)
3443 // string. At this time, current selection range is same as the
3444 // composition string range. Other applications set a wide caret which
3445 // covers the composition string, however, Gecko doesn't support the wide
3446 // caret drawing now (Gecko doesn't support XOR drawing), unfortunately.
3447 // For now, we should change the range style to undefined.
3448 if (!selectionForTSF->Collapsed() && action->mRanges->Length() == 1) {
3449 TextRange& range = action->mRanges->ElementAt(0);
3450 LONG start = selectionForTSF->MinOffset();
3451 LONG end = selectionForTSF->MaxOffset();
3452 if (static_cast<LONG>(range.mStartOffset) ==
3453 start - mComposition->StartOffset() &&
3454 static_cast<LONG>(range.mEndOffset) ==
3455 end - mComposition->StartOffset() &&
3456 range.mRangeStyle.IsNoChangeStyle()) {
3457 range.mRangeStyle.Clear();
3458 // The looks of selected type is better than others.
3459 range.mRangeType = TextRangeType::eSelectedRawClause;
3463 // The caret position has to be collapsed.
3464 uint32_t caretPosition = static_cast<uint32_t>(
3465 selectionForTSF->HasRange()
3466 ? selectionForTSF->MaxOffset() - mComposition->StartOffset()
3467 : mComposition->StartOffset());
3469 // If caret is in the target clause and it doesn't have specific style,
3470 // the target clause will be painted as normal selection range. Since
3471 // caret shouldn't be in selection range on Windows, we shouldn't append
3472 // caret range in such case.
3473 const TextRange* targetClause = action->mRanges->GetTargetClause();
3474 if (!targetClause || targetClause->mRangeStyle.IsDefined() ||
3475 caretPosition < targetClause->mStartOffset ||
3476 caretPosition > targetClause->mEndOffset) {
3477 TextRange caretRange;
3478 caretRange.mStartOffset = caretRange.mEndOffset = caretPosition;
3479 caretRange.mRangeType = TextRangeType::eCaret;
3480 action->mRanges->AppendElement(caretRange);
3484 action->mIncomplete = false;
3486 MOZ_LOG(gIMELog, LogLevel::Info,
3487 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3488 "succeeded",
3489 this));
3491 return S_OK;
3494 HRESULT
3495 TSFTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection,
3496 bool aDispatchCompositionChangeEvent) {
3497 MOZ_LOG(
3498 gIMELog, LogLevel::Debug,
3499 ("0x%p TSFTextStore::SetSelectionInternal(pSelection=%s, "
3500 "aDispatchCompositionChangeEvent=%s), mComposition=%s",
3501 this, pSelection ? mozilla::ToString(*pSelection).c_str() : "nullptr",
3502 GetBoolName(aDispatchCompositionChangeEvent),
3503 ToString(mComposition).c_str()));
3505 MOZ_ASSERT(IsReadWriteLocked());
3507 Maybe<Selection>& selectionForTSF = SelectionForTSF();
3508 if (selectionForTSF.isNothing()) {
3509 MOZ_LOG(gIMELog, LogLevel::Error,
3510 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3511 "SelectionForTSF() failure",
3512 this));
3513 return E_FAIL;
3516 MaybeDispatchKeyboardEventAsProcessedByIME();
3517 if (mDestroyed) {
3518 MOZ_LOG(gIMELog, LogLevel::Error,
3519 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3520 "destroyed during dispatching a keyboard event",
3521 this));
3522 return E_FAIL;
3525 // If actually the range is not changing, we should do nothing.
3526 // Perhaps, we can ignore the difference change because it must not be
3527 // important for following edit.
3528 if (selectionForTSF->EqualsExceptDirection(*pSelection)) {
3529 MOZ_LOG(gIMELog, LogLevel::Warning,
3530 ("0x%p TSFTextStore::SetSelectionInternal() Succeeded but "
3531 "did nothing because the selection range isn't changing",
3532 this));
3533 selectionForTSF->SetSelection(*pSelection);
3534 return S_OK;
3537 if (mComposition.isSome()) {
3538 if (aDispatchCompositionChangeEvent) {
3539 HRESULT hr = RestartCompositionIfNecessary();
3540 if (FAILED(hr)) {
3541 MOZ_LOG(gIMELog, LogLevel::Error,
3542 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3543 "RestartCompositionIfNecessary() failure",
3544 this));
3545 return hr;
3548 if (pSelection->acpStart < mComposition->StartOffset() ||
3549 pSelection->acpEnd > mComposition->EndOffset()) {
3550 MOZ_LOG(gIMELog, LogLevel::Error,
3551 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3552 "the selection being out of the composition string",
3553 this));
3554 return TS_E_INVALIDPOS;
3556 // Emulate selection during compositions
3557 selectionForTSF->SetSelection(*pSelection);
3558 if (aDispatchCompositionChangeEvent) {
3559 HRESULT hr = RecordCompositionUpdateAction();
3560 if (FAILED(hr)) {
3561 MOZ_LOG(gIMELog, LogLevel::Error,
3562 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3563 "RecordCompositionUpdateAction() failure",
3564 this));
3565 return hr;
3568 return S_OK;
3571 TS_SELECTION_ACP selectionInContent(*pSelection);
3573 // If mContentForTSF caches old contents which is now different from
3574 // actual contents, we need some complicated hack here...
3575 // Note that this hack assumes that this is used for reconversion.
3576 if (mContentForTSF.isSome() && mPendingTextChangeData.IsValid() &&
3577 !mPendingTextChangeData.mCausedOnlyByComposition) {
3578 uint32_t startOffset = static_cast<uint32_t>(selectionInContent.acpStart);
3579 uint32_t endOffset = static_cast<uint32_t>(selectionInContent.acpEnd);
3580 if (mPendingTextChangeData.mStartOffset >= endOffset) {
3581 // Setting selection before any changed ranges is fine.
3582 } else if (mPendingTextChangeData.mRemovedEndOffset <= startOffset) {
3583 // Setting selection after removed range is fine with following
3584 // adjustment.
3585 selectionInContent.acpStart += mPendingTextChangeData.Difference();
3586 selectionInContent.acpEnd += mPendingTextChangeData.Difference();
3587 } else if (startOffset == endOffset) {
3588 // Moving caret position may be fine in most cases even if the insertion
3589 // point has already gone but in this case, composition will be inserted
3590 // to unexpected position, though.
3591 // It seems that moving caret into middle of the new text is odd.
3592 // Perhaps, end of it is expected by users in most cases.
3593 selectionInContent.acpStart = mPendingTextChangeData.mAddedEndOffset;
3594 selectionInContent.acpEnd = selectionInContent.acpStart;
3595 } else {
3596 // Otherwise, i.e., setting range has already gone, we cannot set
3597 // selection properly.
3598 MOZ_LOG(gIMELog, LogLevel::Error,
3599 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3600 "there is unknown content change",
3601 this));
3602 return E_FAIL;
3606 CompleteLastActionIfStillIncomplete();
3607 PendingAction* action = mPendingActions.AppendElement();
3608 action->mType = PendingAction::Type::eSetSelection;
3609 action->mSelectionStart = selectionInContent.acpStart;
3610 action->mSelectionLength =
3611 selectionInContent.acpEnd - selectionInContent.acpStart;
3612 action->mSelectionReversed = (selectionInContent.style.ase == TS_AE_START);
3614 // Use TSF specified selection for updating mSelectionForTSF.
3615 selectionForTSF->SetSelection(*pSelection);
3617 return S_OK;
3620 STDMETHODIMP
3621 TSFTextStore::SetSelection(ULONG ulCount, const TS_SELECTION_ACP* pSelection) {
3622 MOZ_LOG(gIMELog, LogLevel::Info,
3623 ("0x%p TSFTextStore::SetSelection(ulCount=%lu, pSelection=%s }), "
3624 "mComposition=%s",
3625 this, ulCount,
3626 pSelection ? mozilla::ToString(pSelection).c_str() : "nullptr",
3627 ToString(mComposition).c_str()));
3629 if (!IsReadWriteLocked()) {
3630 MOZ_LOG(gIMELog, LogLevel::Error,
3631 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3632 "not locked (read-write)",
3633 this));
3634 return TS_E_NOLOCK;
3636 if (ulCount != 1) {
3637 MOZ_LOG(gIMELog, LogLevel::Error,
3638 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3639 "trying setting multiple selection",
3640 this));
3641 return E_INVALIDARG;
3643 if (!pSelection) {
3644 MOZ_LOG(gIMELog, LogLevel::Error,
3645 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3646 "null argument",
3647 this));
3648 return E_INVALIDARG;
3651 HRESULT hr = SetSelectionInternal(pSelection, true);
3652 if (FAILED(hr)) {
3653 MOZ_LOG(gIMELog, LogLevel::Error,
3654 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3655 "SetSelectionInternal() failure",
3656 this));
3657 } else {
3658 MOZ_LOG(gIMELog, LogLevel::Info,
3659 ("0x%p TSFTextStore::SetSelection() succeeded", this));
3661 return hr;
3664 STDMETHODIMP
3665 TSFTextStore::GetText(LONG acpStart, LONG acpEnd, WCHAR* pchPlain,
3666 ULONG cchPlainReq, ULONG* pcchPlainOut,
3667 TS_RUNINFO* prgRunInfo, ULONG ulRunInfoReq,
3668 ULONG* pulRunInfoOut, LONG* pacpNext) {
3669 MOZ_LOG(
3670 gIMELog, LogLevel::Info,
3671 ("0x%p TSFTextStore::GetText(acpStart=%ld, acpEnd=%ld, pchPlain=0x%p, "
3672 "cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, ulRunInfoReq=%lu, "
3673 "pulRunInfoOut=0x%p, pacpNext=0x%p), mComposition=%s",
3674 this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, prgRunInfo,
3675 ulRunInfoReq, pulRunInfoOut, pacpNext, ToString(mComposition).c_str()));
3677 if (!IsReadLocked()) {
3678 MOZ_LOG(gIMELog, LogLevel::Error,
3679 ("0x%p TSFTextStore::GetText() FAILED due to "
3680 "not locked (read)",
3681 this));
3682 return TS_E_NOLOCK;
3685 if (!pcchPlainOut || (!pchPlain && !prgRunInfo) ||
3686 !cchPlainReq != !pchPlain || !ulRunInfoReq != !prgRunInfo) {
3687 MOZ_LOG(gIMELog, LogLevel::Error,
3688 ("0x%p TSFTextStore::GetText() FAILED due to "
3689 "invalid argument",
3690 this));
3691 return E_INVALIDARG;
3694 if (acpStart < 0 || acpEnd < -1 || (acpEnd != -1 && acpStart > acpEnd)) {
3695 MOZ_LOG(gIMELog, LogLevel::Error,
3696 ("0x%p TSFTextStore::GetText() FAILED due to "
3697 "invalid position",
3698 this));
3699 return TS_E_INVALIDPOS;
3702 // Making sure to null-terminate string just to be on the safe side
3703 *pcchPlainOut = 0;
3704 if (pchPlain && cchPlainReq) *pchPlain = 0;
3705 if (pulRunInfoOut) *pulRunInfoOut = 0;
3706 if (pacpNext) *pacpNext = acpStart;
3707 if (prgRunInfo && ulRunInfoReq) {
3708 prgRunInfo->uCount = 0;
3709 prgRunInfo->type = TS_RT_PLAIN;
3712 Maybe<Content>& contentForTSF = ContentForTSF();
3713 if (contentForTSF.isNothing()) {
3714 MOZ_LOG(gIMELog, LogLevel::Error,
3715 ("0x%p TSFTextStore::GetText() FAILED due to "
3716 "ContentForTSF() failure",
3717 this));
3718 return E_FAIL;
3720 if (contentForTSF->TextRef().Length() < static_cast<uint32_t>(acpStart)) {
3721 MOZ_LOG(gIMELog, LogLevel::Error,
3722 ("0x%p TSFTextStore::GetText() FAILED due to "
3723 "acpStart is larger offset than the actual text length",
3724 this));
3725 return TS_E_INVALIDPOS;
3727 if (acpEnd != -1 &&
3728 contentForTSF->TextRef().Length() < static_cast<uint32_t>(acpEnd)) {
3729 MOZ_LOG(gIMELog, LogLevel::Error,
3730 ("0x%p TSFTextStore::GetText() FAILED due to "
3731 "acpEnd is larger offset than the actual text length",
3732 this));
3733 return TS_E_INVALIDPOS;
3735 uint32_t length = (acpEnd == -1) ? contentForTSF->TextRef().Length() -
3736 static_cast<uint32_t>(acpStart)
3737 : static_cast<uint32_t>(acpEnd - acpStart);
3738 if (cchPlainReq && cchPlainReq - 1 < length) {
3739 length = cchPlainReq - 1;
3741 if (length) {
3742 if (pchPlain && cchPlainReq) {
3743 const char16_t* startChar =
3744 contentForTSF->TextRef().BeginReading() + acpStart;
3745 memcpy(pchPlain, startChar, length * sizeof(*pchPlain));
3746 pchPlain[length] = 0;
3747 *pcchPlainOut = length;
3749 if (prgRunInfo && ulRunInfoReq) {
3750 prgRunInfo->uCount = length;
3751 prgRunInfo->type = TS_RT_PLAIN;
3752 if (pulRunInfoOut) *pulRunInfoOut = 1;
3754 if (pacpNext) *pacpNext = acpStart + length;
3757 MOZ_LOG(gIMELog, LogLevel::Info,
3758 ("0x%p TSFTextStore::GetText() succeeded: pcchPlainOut=0x%p, "
3759 "*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, "
3760 "*pacpNext=%ld)",
3761 this, pcchPlainOut, prgRunInfo ? prgRunInfo->uCount : 0,
3762 prgRunInfo ? GetTextRunTypeName(prgRunInfo->type) : "N/A",
3763 pulRunInfoOut ? *pulRunInfoOut : 0, pacpNext ? *pacpNext : 0));
3764 return S_OK;
3767 STDMETHODIMP
3768 TSFTextStore::SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd,
3769 const WCHAR* pchText, ULONG cch, TS_TEXTCHANGE* pChange) {
3770 MOZ_LOG(
3771 gIMELog, LogLevel::Info,
3772 ("0x%p TSFTextStore::SetText(dwFlags=%s, acpStart=%ld, acpEnd=%ld, "
3773 "pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), mComposition=%s",
3774 this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" : "not-specified",
3775 acpStart, acpEnd, pchText,
3776 pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "", cch,
3777 pChange, ToString(mComposition).c_str()));
3779 // Per SDK documentation, and since we don't have better
3780 // ways to do this, this method acts as a helper to
3781 // call SetSelection followed by InsertTextAtSelection
3782 if (!IsReadWriteLocked()) {
3783 MOZ_LOG(gIMELog, LogLevel::Error,
3784 ("0x%p TSFTextStore::SetText() FAILED due to "
3785 "not locked (read)",
3786 this));
3787 return TS_E_NOLOCK;
3790 TS_SELECTION_ACP selection;
3791 selection.acpStart = acpStart;
3792 selection.acpEnd = acpEnd;
3793 selection.style.ase = TS_AE_END;
3794 selection.style.fInterimChar = 0;
3795 // Set selection to desired range
3796 HRESULT hr = SetSelectionInternal(&selection);
3797 if (FAILED(hr)) {
3798 MOZ_LOG(gIMELog, LogLevel::Error,
3799 ("0x%p TSFTextStore::SetText() FAILED due to "
3800 "SetSelectionInternal() failure",
3801 this));
3802 return hr;
3804 // Replace just selected text
3805 if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
3806 pChange)) {
3807 MOZ_LOG(gIMELog, LogLevel::Error,
3808 ("0x%p TSFTextStore::SetText() FAILED due to "
3809 "InsertTextAtSelectionInternal() failure",
3810 this));
3811 return E_FAIL;
3814 MOZ_LOG(gIMELog, LogLevel::Info,
3815 ("0x%p TSFTextStore::SetText() succeeded: pChange={ "
3816 "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
3817 this, pChange ? pChange->acpStart : 0,
3818 pChange ? pChange->acpOldEnd : 0, pChange ? pChange->acpNewEnd : 0));
3819 return S_OK;
3822 STDMETHODIMP
3823 TSFTextStore::GetFormattedText(LONG acpStart, LONG acpEnd,
3824 IDataObject** ppDataObject) {
3825 MOZ_LOG(gIMELog, LogLevel::Info,
3826 ("0x%p TSFTextStore::GetFormattedText() called "
3827 "but not supported (E_NOTIMPL)",
3828 this));
3830 // no support for formatted text
3831 return E_NOTIMPL;
3834 STDMETHODIMP
3835 TSFTextStore::GetEmbedded(LONG acpPos, REFGUID rguidService, REFIID riid,
3836 IUnknown** ppunk) {
3837 MOZ_LOG(gIMELog, LogLevel::Info,
3838 ("0x%p TSFTextStore::GetEmbedded() called "
3839 "but not supported (E_NOTIMPL)",
3840 this));
3842 // embedded objects are not supported
3843 return E_NOTIMPL;
3846 STDMETHODIMP
3847 TSFTextStore::QueryInsertEmbedded(const GUID* pguidService,
3848 const FORMATETC* pFormatEtc,
3849 BOOL* pfInsertable) {
3850 MOZ_LOG(gIMELog, LogLevel::Info,
3851 ("0x%p TSFTextStore::QueryInsertEmbedded() called "
3852 "but not supported, *pfInsertable=FALSE (S_OK)",
3853 this));
3855 // embedded objects are not supported
3856 *pfInsertable = FALSE;
3857 return S_OK;
3860 STDMETHODIMP
3861 TSFTextStore::InsertEmbedded(DWORD dwFlags, LONG acpStart, LONG acpEnd,
3862 IDataObject* pDataObject, TS_TEXTCHANGE* pChange) {
3863 MOZ_LOG(gIMELog, LogLevel::Info,
3864 ("0x%p TSFTextStore::InsertEmbedded() called "
3865 "but not supported (E_NOTIMPL)",
3866 this));
3868 // embedded objects are not supported
3869 return E_NOTIMPL;
3872 // static
3873 bool TSFTextStore::ShouldSetInputScopeOfURLBarToDefault() {
3874 // FYI: Google Japanese Input may be an IMM-IME. If it's installed on
3875 // Win7, it's always IMM-IME. Otherwise, basically, it's a TIP.
3876 // However, if it's installed on Win7 and has not been updated yet
3877 // after the OS is upgraded to Win8 or later, it's still an IMM-IME.
3878 // Therefore, we also need to check with IMMHandler here.
3879 if (!StaticPrefs::intl_ime_hack_set_input_scope_of_url_bar_to_default()) {
3880 return false;
3883 if (IMMHandler::IsGoogleJapaneseInputActive()) {
3884 return true;
3887 switch (TSFStaticSink::ActiveTIP()) {
3888 case TextInputProcessorID::eMicrosoftIMEForJapanese:
3889 case TextInputProcessorID::eGoogleJapaneseInput:
3890 case TextInputProcessorID::eMicrosoftBopomofo:
3891 case TextInputProcessorID::eMicrosoftChangJie:
3892 case TextInputProcessorID::eMicrosoftPhonetic:
3893 case TextInputProcessorID::eMicrosoftQuick:
3894 case TextInputProcessorID::eMicrosoftNewChangJie:
3895 case TextInputProcessorID::eMicrosoftNewPhonetic:
3896 case TextInputProcessorID::eMicrosoftNewQuick:
3897 case TextInputProcessorID::eMicrosoftPinyin:
3898 case TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle:
3899 case TextInputProcessorID::eMicrosoftOldHangul:
3900 case TextInputProcessorID::eMicrosoftWubi:
3901 case TextInputProcessorID::eMicrosoftIMEForKorean:
3902 return true;
3903 default:
3904 return false;
3908 void TSFTextStore::SetInputScope(const nsString& aHTMLInputType,
3909 const nsString& aHTMLInputMode) {
3910 mInputScopes.Clear();
3912 // IME may refer only first input scope, but we will append inputmode's
3913 // input scopes too like Chrome since IME may refer it.
3914 IMEHandler::AppendInputScopeFromType(aHTMLInputType, mInputScopes);
3915 IMEHandler::AppendInputScopeFromInputMode(aHTMLInputMode, mInputScopes);
3917 if (mInPrivateBrowsing) {
3918 mInputScopes.AppendElement(IS_PRIVATE);
3922 int32_t TSFTextStore::GetRequestedAttrIndex(const TS_ATTRID& aAttrID) {
3923 if (IsEqualGUID(aAttrID, GUID_PROP_INPUTSCOPE)) {
3924 return eInputScope;
3926 if (IsEqualGUID(aAttrID, sGUID_PROP_URL)) {
3927 return eDocumentURL;
3929 if (IsEqualGUID(aAttrID, TSATTRID_Text_VerticalWriting)) {
3930 return eTextVerticalWriting;
3932 if (IsEqualGUID(aAttrID, TSATTRID_Text_Orientation)) {
3933 return eTextOrientation;
3935 return eNotSupported;
3938 TS_ATTRID
3939 TSFTextStore::GetAttrID(int32_t aIndex) {
3940 switch (aIndex) {
3941 case eInputScope:
3942 return GUID_PROP_INPUTSCOPE;
3943 case eDocumentURL:
3944 return sGUID_PROP_URL;
3945 case eTextVerticalWriting:
3946 return TSATTRID_Text_VerticalWriting;
3947 case eTextOrientation:
3948 return TSATTRID_Text_Orientation;
3949 default:
3950 MOZ_CRASH("Invalid index? Or not implemented yet?");
3951 return GUID_NULL;
3955 HRESULT
3956 TSFTextStore::HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
3957 const TS_ATTRID* aFilterAttrs) {
3958 MOZ_LOG(gIMELog, LogLevel::Info,
3959 ("0x%p TSFTextStore::HandleRequestAttrs(aFlags=%s, "
3960 "aFilterCount=%lu)",
3961 this, GetFindFlagName(aFlags).get(), aFilterCount));
3963 // This is a little weird! RequestSupportedAttrs gives us advanced notice
3964 // of a support query via RetrieveRequestedAttrs for a specific attribute.
3965 // RetrieveRequestedAttrs needs to return valid data for all attributes we
3966 // support, but the text service will only want the input scope object
3967 // returned in RetrieveRequestedAttrs if the dwFlags passed in here contains
3968 // TS_ATTR_FIND_WANT_VALUE.
3969 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
3970 mRequestedAttrs[i] = false;
3972 mRequestedAttrValues = !!(aFlags & TS_ATTR_FIND_WANT_VALUE);
3974 for (uint32_t i = 0; i < aFilterCount; i++) {
3975 MOZ_LOG(gIMELog, LogLevel::Info,
3976 ("0x%p TSFTextStore::HandleRequestAttrs(), "
3977 "requested attr=%s",
3978 this, GetGUIDNameStrWithTable(aFilterAttrs[i]).get()));
3979 int32_t index = GetRequestedAttrIndex(aFilterAttrs[i]);
3980 if (index != eNotSupported) {
3981 mRequestedAttrs[index] = true;
3984 return S_OK;
3987 STDMETHODIMP
3988 TSFTextStore::RequestSupportedAttrs(DWORD dwFlags, ULONG cFilterAttrs,
3989 const TS_ATTRID* paFilterAttrs) {
3990 MOZ_LOG(gIMELog, LogLevel::Info,
3991 ("0x%p TSFTextStore::RequestSupportedAttrs(dwFlags=%s, "
3992 "cFilterAttrs=%lu)",
3993 this, GetFindFlagName(dwFlags).get(), cFilterAttrs));
3995 return HandleRequestAttrs(dwFlags, cFilterAttrs, paFilterAttrs);
3998 STDMETHODIMP
3999 TSFTextStore::RequestAttrsAtPosition(LONG acpPos, ULONG cFilterAttrs,
4000 const TS_ATTRID* paFilterAttrs,
4001 DWORD dwFlags) {
4002 MOZ_LOG(gIMELog, LogLevel::Info,
4003 ("0x%p TSFTextStore::RequestAttrsAtPosition(acpPos=%ld, "
4004 "cFilterAttrs=%lu, dwFlags=%s)",
4005 this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
4007 return HandleRequestAttrs(dwFlags | TS_ATTR_FIND_WANT_VALUE, cFilterAttrs,
4008 paFilterAttrs);
4011 STDMETHODIMP
4012 TSFTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos,
4013 ULONG cFilterAttrs,
4014 const TS_ATTRID* paFilterAttr,
4015 DWORD dwFlags) {
4016 MOZ_LOG(gIMELog, LogLevel::Info,
4017 ("0x%p TSFTextStore::RequestAttrsTransitioningAtPosition("
4018 "acpPos=%ld, cFilterAttrs=%lu, dwFlags=%s) called but not supported "
4019 "(S_OK)",
4020 this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
4022 // no per character attributes defined
4023 return S_OK;
4026 STDMETHODIMP
4027 TSFTextStore::FindNextAttrTransition(LONG acpStart, LONG acpHalt,
4028 ULONG cFilterAttrs,
4029 const TS_ATTRID* paFilterAttrs,
4030 DWORD dwFlags, LONG* pacpNext,
4031 BOOL* pfFound, LONG* plFoundOffset) {
4032 if (!pacpNext || !pfFound || !plFoundOffset) {
4033 MOZ_LOG(gIMELog, LogLevel::Error,
4034 (" 0x%p TSFTextStore::FindNextAttrTransition() FAILED due to "
4035 "null argument",
4036 this));
4037 return E_INVALIDARG;
4040 MOZ_LOG(gIMELog, LogLevel::Info,
4041 ("0x%p TSFTextStore::FindNextAttrTransition() called "
4042 "but not supported (S_OK)",
4043 this));
4045 // no per character attributes defined
4046 *pacpNext = *plFoundOffset = acpHalt;
4047 *pfFound = FALSE;
4048 return S_OK;
4051 // To test the document URL result, define this to out put it to the stdout
4052 // #define DEBUG_PRINT_DOCUMENT_URL
4054 STDMETHODIMP
4055 TSFTextStore::RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL* paAttrVals,
4056 ULONG* pcFetched) {
4057 if (!pcFetched || !paAttrVals) {
4058 MOZ_LOG(gIMELog, LogLevel::Error,
4059 ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
4060 "null argument",
4061 this));
4062 return E_INVALIDARG;
4065 ULONG expectedCount = 0;
4066 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
4067 if (mRequestedAttrs[i]) {
4068 expectedCount++;
4071 if (ulCount < expectedCount) {
4072 MOZ_LOG(gIMELog, LogLevel::Error,
4073 ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
4074 "not enough count ulCount=%lu, expectedCount=%lu",
4075 this, ulCount, expectedCount));
4076 return E_INVALIDARG;
4079 MOZ_LOG(gIMELog, LogLevel::Info,
4080 ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
4081 "ulCount=%lu, mRequestedAttrValues=%s",
4082 this, ulCount, GetBoolName(mRequestedAttrValues)));
4084 auto GetExposingURL = [&]() -> BSTR {
4085 const bool allowed =
4086 StaticPrefs::intl_tsf_expose_url_allowed() &&
4087 (!mInPrivateBrowsing ||
4088 StaticPrefs::intl_tsf_expose_url_in_private_browsing_allowed());
4089 if (!allowed || mDocumentURL.IsEmpty()) {
4090 BSTR emptyString = ::SysAllocString(L"");
4091 MOZ_ASSERT(
4092 emptyString,
4093 "We need to return valid BSTR pointer to notify TSF of supporting it "
4094 "with a pointer to empty string");
4095 return emptyString;
4097 return ::SysAllocString(mDocumentURL.get());
4100 #ifdef DEBUG_PRINT_DOCUMENT_URL
4102 BSTR exposingURL = GetExposingURL();
4103 printf("TSFTextStore::RetrieveRequestedAttrs: DocumentURL=\"%s\"\n",
4104 NS_ConvertUTF16toUTF8(static_cast<char16ptr_t>(_bstr_t(exposingURL)))
4105 .get());
4106 ::SysFreeString(exposingURL);
4108 #endif // #ifdef DEBUG_PRINT_DOCUMENT_URL
4110 int32_t count = 0;
4111 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
4112 if (!mRequestedAttrs[i]) {
4113 continue;
4115 mRequestedAttrs[i] = false;
4117 TS_ATTRID attrID = GetAttrID(i);
4119 MOZ_LOG(gIMELog, LogLevel::Info,
4120 ("0x%p TSFTextStore::RetrieveRequestedAttrs() for %s", this,
4121 GetGUIDNameStrWithTable(attrID).get()));
4123 paAttrVals[count].idAttr = attrID;
4124 paAttrVals[count].dwOverlapId = 0;
4126 if (!mRequestedAttrValues) {
4127 paAttrVals[count].varValue.vt = VT_EMPTY;
4128 } else {
4129 switch (i) {
4130 case eInputScope: {
4131 paAttrVals[count].varValue.vt = VT_UNKNOWN;
4132 RefPtr<IUnknown> inputScope = new InputScopeImpl(mInputScopes);
4133 paAttrVals[count].varValue.punkVal = inputScope.forget().take();
4134 break;
4136 case eDocumentURL: {
4137 paAttrVals[count].varValue.vt = VT_BSTR;
4138 paAttrVals[count].varValue.bstrVal = GetExposingURL();
4139 break;
4141 case eTextVerticalWriting: {
4142 Maybe<Selection>& selectionForTSF = SelectionForTSF();
4143 paAttrVals[count].varValue.vt = VT_BOOL;
4144 paAttrVals[count].varValue.boolVal =
4145 selectionForTSF.isSome() &&
4146 selectionForTSF->WritingModeRef().IsVertical()
4147 ? VARIANT_TRUE
4148 : VARIANT_FALSE;
4149 break;
4151 case eTextOrientation: {
4152 Maybe<Selection>& selectionForTSF = SelectionForTSF();
4153 paAttrVals[count].varValue.vt = VT_I4;
4154 paAttrVals[count].varValue.lVal =
4155 selectionForTSF.isSome() &&
4156 selectionForTSF->WritingModeRef().IsVertical()
4157 ? 2700
4158 : 0;
4159 break;
4161 default:
4162 MOZ_CRASH("Invalid index? Or not implemented yet?");
4163 break;
4166 count++;
4169 mRequestedAttrValues = false;
4171 if (count) {
4172 *pcFetched = count;
4173 return S_OK;
4176 MOZ_LOG(gIMELog, LogLevel::Info,
4177 ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
4178 "for unknown TS_ATTRVAL, *pcFetched=0 (S_OK)",
4179 this));
4181 paAttrVals->dwOverlapId = 0;
4182 paAttrVals->varValue.vt = VT_EMPTY;
4183 *pcFetched = 0;
4184 return S_OK;
4187 #undef DEBUG_PRINT_DOCUMENT_URL
4189 STDMETHODIMP
4190 TSFTextStore::GetEndACP(LONG* pacp) {
4191 MOZ_LOG(gIMELog, LogLevel::Info,
4192 ("0x%p TSFTextStore::GetEndACP(pacp=0x%p)", this, pacp));
4194 if (!IsReadLocked()) {
4195 MOZ_LOG(gIMELog, LogLevel::Error,
4196 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4197 "not locked (read)",
4198 this));
4199 return TS_E_NOLOCK;
4202 if (!pacp) {
4203 MOZ_LOG(gIMELog, LogLevel::Error,
4204 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4205 "null argument",
4206 this));
4207 return E_INVALIDARG;
4210 Maybe<Content>& contentForTSF = ContentForTSF();
4211 if (contentForTSF.isNothing()) {
4212 MOZ_LOG(gIMELog, LogLevel::Error,
4213 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4214 "ContentForTSF() failure",
4215 this));
4216 return E_FAIL;
4218 *pacp = static_cast<LONG>(contentForTSF->TextRef().Length());
4219 return S_OK;
4222 STDMETHODIMP
4223 TSFTextStore::GetActiveView(TsViewCookie* pvcView) {
4224 MOZ_LOG(gIMELog, LogLevel::Info,
4225 ("0x%p TSFTextStore::GetActiveView(pvcView=0x%p)", this, pvcView));
4227 if (!pvcView) {
4228 MOZ_LOG(gIMELog, LogLevel::Error,
4229 ("0x%p TSFTextStore::GetActiveView() FAILED due to "
4230 "null argument",
4231 this));
4232 return E_INVALIDARG;
4235 *pvcView = TEXTSTORE_DEFAULT_VIEW;
4237 MOZ_LOG(gIMELog, LogLevel::Info,
4238 ("0x%p TSFTextStore::GetActiveView() succeeded: *pvcView=%ld", this,
4239 *pvcView));
4240 return S_OK;
4243 STDMETHODIMP
4244 TSFTextStore::GetACPFromPoint(TsViewCookie vcView, const POINT* pt,
4245 DWORD dwFlags, LONG* pacp) {
4246 MOZ_LOG(gIMELog, LogLevel::Info,
4247 ("0x%p TSFTextStore::GetACPFromPoint(pvcView=%ld, pt=%p (x=%ld, "
4248 "y=%ld), dwFlags=%s, pacp=%p, mDeferNotifyingTSFUntilNextUpdate=%s, "
4249 "mWaitingQueryLayout=%s",
4250 this, vcView, pt, pt ? pt->x : 0, pt ? pt->y : 0,
4251 GetACPFromPointFlagName(dwFlags).get(), pacp,
4252 GetBoolName(mDeferNotifyingTSFUntilNextUpdate),
4253 GetBoolName(mWaitingQueryLayout)));
4255 if (!IsReadLocked()) {
4256 MOZ_LOG(gIMELog, LogLevel::Error,
4257 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4258 "not locked (read)",
4259 this));
4260 return TS_E_NOLOCK;
4263 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4264 MOZ_LOG(gIMELog, LogLevel::Error,
4265 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4266 "called with invalid view",
4267 this));
4268 return E_INVALIDARG;
4271 if (!pt) {
4272 MOZ_LOG(gIMELog, LogLevel::Error,
4273 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4274 "null pt",
4275 this));
4276 return E_INVALIDARG;
4279 if (!pacp) {
4280 MOZ_LOG(gIMELog, LogLevel::Error,
4281 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4282 "null pacp",
4283 this));
4284 return E_INVALIDARG;
4287 mWaitingQueryLayout = false;
4289 if (mDestroyed ||
4290 (mContentForTSF.isSome() && mContentForTSF->IsLayoutChanged())) {
4291 MOZ_LOG(gIMELog, LogLevel::Error,
4292 ("0x%p TSFTextStore::GetACPFromPoint() returned "
4293 "TS_E_NOLAYOUT",
4294 this));
4295 mHasReturnedNoLayoutError = true;
4296 return TS_E_NOLAYOUT;
4299 LayoutDeviceIntPoint ourPt(pt->x, pt->y);
4300 // Convert to widget relative coordinates from screen's.
4301 ourPt -= mWidget->WidgetToScreenOffset();
4303 // NOTE: Don't check if the point is in the widget since the point can be
4304 // outside of the widget if focused editor is in a XUL <panel>.
4306 WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint,
4307 mWidget);
4308 mWidget->InitEvent(queryCharAtPointEvent, &ourPt);
4310 // FYI: WidgetQueryContentEvent may cause flushing pending layout and it
4311 // may cause focus change or something.
4312 RefPtr<TSFTextStore> kungFuDeathGrip(this);
4313 DispatchEvent(queryCharAtPointEvent);
4314 if (!mWidget || mWidget->Destroyed()) {
4315 MOZ_LOG(gIMELog, LogLevel::Error,
4316 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4317 "mWidget was destroyed during eQueryCharacterAtPoint",
4318 this));
4319 return E_FAIL;
4322 MOZ_LOG(gIMELog, LogLevel::Debug,
4323 ("0x%p TSFTextStore::GetACPFromPoint(), queryCharAtPointEvent={ "
4324 "mReply=%s }",
4325 this, ToString(queryCharAtPointEvent.mReply).c_str()));
4327 if (NS_WARN_IF(queryCharAtPointEvent.Failed())) {
4328 MOZ_LOG(gIMELog, LogLevel::Error,
4329 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4330 "eQueryCharacterAtPoint failure",
4331 this));
4332 return E_FAIL;
4335 // If dwFlags isn't set and the point isn't in any character's bounding box,
4336 // we should return TS_E_INVALIDPOINT.
4337 if (!(dwFlags & GXFPF_NEAREST) && queryCharAtPointEvent.DidNotFindChar()) {
4338 MOZ_LOG(gIMELog, LogLevel::Error,
4339 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to the "
4340 "point contained by no bounding box",
4341 this));
4342 return TS_E_INVALIDPOINT;
4345 // Although, we're not sure if mTentativeCaretOffset becomes NOT_FOUND,
4346 // let's assume that there is no content in such case.
4347 NS_WARNING_ASSERTION(queryCharAtPointEvent.DidNotFindTentativeCaretOffset(),
4348 "Tentative caret offset was not found");
4350 uint32_t offset;
4352 // If dwFlags includes GXFPF_ROUND_NEAREST, we should return tentative
4353 // caret offset (MSDN calls it "range position").
4354 if (dwFlags & GXFPF_ROUND_NEAREST) {
4355 offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0);
4356 } else if (queryCharAtPointEvent.FoundChar()) {
4357 // Otherwise, we should return character offset whose bounding box contains
4358 // the point.
4359 offset = queryCharAtPointEvent.mReply->StartOffset();
4360 } else {
4361 // If the point isn't in any character's bounding box but we need to return
4362 // the nearest character from the point, we should *guess* the character
4363 // offset since there is no inexpensive API to check it strictly.
4364 // XXX If we retrieve 2 bounding boxes, one is before the offset and
4365 // the other is after the offset, we could resolve the offset.
4366 // However, dispatching 2 eQueryTextRect may be expensive.
4368 // So, use tentative offset for now.
4369 offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0);
4371 // However, if it's after the last character, we need to decrement the
4372 // offset.
4373 Maybe<Content>& contentForTSF = ContentForTSF();
4374 if (contentForTSF.isNothing()) {
4375 MOZ_LOG(gIMELog, LogLevel::Error,
4376 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4377 "ContentForTSF() failure",
4378 this));
4379 return E_FAIL;
4381 if (contentForTSF->TextRef().Length() <= offset) {
4382 // If the tentative caret is after the last character, let's return
4383 // the last character's offset.
4384 offset = contentForTSF->TextRef().Length() - 1;
4388 if (NS_WARN_IF(offset > LONG_MAX)) {
4389 MOZ_LOG(gIMELog, LogLevel::Error,
4390 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to out of "
4391 "range of the result",
4392 this));
4393 return TS_E_INVALIDPOINT;
4396 *pacp = static_cast<LONG>(offset);
4397 MOZ_LOG(gIMELog, LogLevel::Info,
4398 ("0x%p TSFTextStore::GetACPFromPoint() succeeded: *pacp=%ld", this,
4399 *pacp));
4400 return S_OK;
4403 STDMETHODIMP
4404 TSFTextStore::GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd,
4405 RECT* prc, BOOL* pfClipped) {
4406 MOZ_LOG(gIMELog, LogLevel::Info,
4407 ("0x%p TSFTextStore::GetTextExt(vcView=%ld, "
4408 "acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p), "
4409 "IsHandlingCompositionInParent()=%s, "
4410 "IsHandlingCompositionInContent()=%s, mContentForTSF=%s, "
4411 "mSelectionForTSF=%s, mComposition=%s, "
4412 "mDeferNotifyingTSFUntilNextUpdate=%s, mWaitingQueryLayout=%s, "
4413 "IMEHandler::IsA11yHandlingNativeCaret()=%s",
4414 this, vcView, acpStart, acpEnd, prc, pfClipped,
4415 GetBoolName(IsHandlingCompositionInParent()),
4416 GetBoolName(IsHandlingCompositionInContent()),
4417 mozilla::ToString(mContentForTSF).c_str(),
4418 ToString(mSelectionForTSF).c_str(), ToString(mComposition).c_str(),
4419 GetBoolName(mDeferNotifyingTSFUntilNextUpdate),
4420 GetBoolName(mWaitingQueryLayout),
4421 GetBoolName(IMEHandler::IsA11yHandlingNativeCaret())));
4423 if (!IsReadLocked()) {
4424 MOZ_LOG(gIMELog, LogLevel::Error,
4425 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4426 "not locked (read)",
4427 this));
4428 return TS_E_NOLOCK;
4431 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4432 MOZ_LOG(gIMELog, LogLevel::Error,
4433 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4434 "called with invalid view",
4435 this));
4436 return E_INVALIDARG;
4439 if (!prc || !pfClipped) {
4440 MOZ_LOG(gIMELog, LogLevel::Error,
4441 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4442 "null argument",
4443 this));
4444 return E_INVALIDARG;
4447 // According to MSDN, ITextStoreACP::GetTextExt() should return
4448 // TS_E_INVALIDARG when acpStart and acpEnd are same (i.e., collapsed range).
4449 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms538435(v=vs.85).aspx
4450 // > TS_E_INVALIDARG: The specified start and end character positions are
4451 // > equal.
4452 // However, some TIPs (including Microsoft's Chinese TIPs!) call this with
4453 // collapsed range and if we return TS_E_INVALIDARG, they stops showing their
4454 // owning window or shows it but odd position. So, we should just return
4455 // error only when acpStart and/or acpEnd are really odd.
4457 if (acpStart < 0 || acpEnd < acpStart) {
4458 MOZ_LOG(gIMELog, LogLevel::Error,
4459 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4460 "invalid position",
4461 this));
4462 return TS_E_INVALIDPOS;
4465 mWaitingQueryLayout = false;
4467 if (IsHandlingCompositionInContent() && mContentForTSF.isSome() &&
4468 mContentForTSF->HasOrHadComposition() &&
4469 mContentForTSF->IsLayoutChanged() &&
4470 mContentForTSF->MinModifiedOffset().value() >
4471 static_cast<uint32_t>(LONG_MAX)) {
4472 MOZ_LOG(gIMELog, LogLevel::Error,
4473 ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text "
4474 "is too big for TSF (cannot treat modified offset as LONG), "
4475 "mContentForTSF=%s",
4476 this, ToString(mContentForTSF).c_str()));
4477 return E_FAIL;
4480 // At Windows 10 build 17643 (an insider preview for RS5), Microsoft fixed
4481 // the bug of TS_E_NOLAYOUT (even when we returned TS_E_NOLAYOUT, TSF
4482 // returned E_FAIL to TIP). However, until we drop to support older Windows
4483 // and all TIPs are aware of TS_E_NOLAYOUT result, we need to keep returning
4484 // S_OK and available rectangle only for them.
4485 if (!MaybeHackNoErrorLayoutBugs(acpStart, acpEnd) &&
4486 mContentForTSF.isSome() && mContentForTSF->IsLayoutChangedAt(acpEnd)) {
4487 MOZ_LOG(gIMELog, LogLevel::Error,
4488 ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
4489 "(acpEnd=%ld)",
4490 this, acpEnd));
4491 mHasReturnedNoLayoutError = true;
4492 return TS_E_NOLAYOUT;
4495 if (mDestroyed) {
4496 MOZ_LOG(gIMELog, LogLevel::Error,
4497 ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
4498 "(acpEnd=%ld) because this has already been destroyed",
4499 this, acpEnd));
4500 mHasReturnedNoLayoutError = true;
4501 return TS_E_NOLAYOUT;
4504 // use eQueryTextRect to get rect in system, screen coordinates
4505 WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, mWidget);
4506 mWidget->InitEvent(queryTextRectEvent);
4508 WidgetQueryContentEvent::Options options;
4509 int64_t startOffset = acpStart;
4510 if (mComposition.isSome()) {
4511 // If there is a composition, TSF must want character rects related to
4512 // the composition. Therefore, we should use insertion point relative
4513 // query because the composition might be at different position from
4514 // the position where TSFTextStore believes it at.
4515 options.mRelativeToInsertionPoint = true;
4516 startOffset -= mComposition->StartOffset();
4517 } else if (IsHandlingCompositionInParent() && mContentForTSF.isSome() &&
4518 mContentForTSF->HasOrHadComposition()) {
4519 // If there was a composition and its commit event hasn't been dispatched
4520 // yet, ContentCacheInParent is still open for relative offset query from
4521 // the latest composition.
4522 options.mRelativeToInsertionPoint = true;
4523 startOffset -= mContentForTSF->LatestCompositionRange()->StartOffset();
4524 } else if (!CanAccessActualContentDirectly() &&
4525 mSelectionForTSF->HasRange()) {
4526 // If TSF/TIP cannot access actual content directly, there may be pending
4527 // text and/or selection changes which have not been notified TSF yet.
4528 // Therefore, we should use relative to insertion point query since
4529 // TSF/TIP computes the offset from the cached selection.
4530 options.mRelativeToInsertionPoint = true;
4531 startOffset -= mSelectionForTSF->StartOffset();
4533 // ContentEventHandler and ContentCache return actual caret rect when
4534 // the queried range is collapsed and selection is collapsed at the
4535 // queried range. Then, its height (in horizontal layout, width in vertical
4536 // layout) may be different from actual font height of the line. In such
4537 // case, users see "dancing" of candidate or suggest window of TIP.
4538 // For preventing it, we should query text rect with at least 1 length.
4539 uint32_t length = std::max(static_cast<int32_t>(acpEnd - acpStart), 1);
4540 queryTextRectEvent.InitForQueryTextRect(startOffset, length, options);
4542 DispatchEvent(queryTextRectEvent);
4543 if (NS_WARN_IF(queryTextRectEvent.Failed())) {
4544 MOZ_LOG(gIMELog, LogLevel::Error,
4545 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4546 "eQueryTextRect failure",
4547 this));
4548 return TS_E_INVALIDPOS; // but unexpected failure, maybe.
4551 // IMEs don't like empty rects, fix here
4552 if (queryTextRectEvent.mReply->mRect.Width() <= 0) {
4553 queryTextRectEvent.mReply->mRect.SetWidth(1);
4555 if (queryTextRectEvent.mReply->mRect.Height() <= 0) {
4556 queryTextRectEvent.mReply->mRect.SetHeight(1);
4559 // convert to unclipped screen rect
4560 nsWindow* refWindow =
4561 static_cast<nsWindow*>(!!queryTextRectEvent.mReply->mFocusedWidget
4562 ? queryTextRectEvent.mReply->mFocusedWidget
4563 : static_cast<nsIWidget*>(mWidget.get()));
4564 // Result rect is in top level widget coordinates
4565 refWindow = refWindow->GetTopLevelWindow(false);
4566 if (!refWindow) {
4567 MOZ_LOG(gIMELog, LogLevel::Error,
4568 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4569 "no top level window",
4570 this));
4571 return E_FAIL;
4574 queryTextRectEvent.mReply->mRect.MoveBy(refWindow->WidgetToScreenOffset());
4576 // get bounding screen rect to test for clipping
4577 if (!GetScreenExtInternal(*prc)) {
4578 MOZ_LOG(gIMELog, LogLevel::Error,
4579 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4580 "GetScreenExtInternal() failure",
4581 this));
4582 return E_FAIL;
4585 // clip text rect to bounding rect
4586 RECT textRect;
4587 ::SetRect(&textRect, queryTextRectEvent.mReply->mRect.X(),
4588 queryTextRectEvent.mReply->mRect.Y(),
4589 queryTextRectEvent.mReply->mRect.XMost(),
4590 queryTextRectEvent.mReply->mRect.YMost());
4591 if (!::IntersectRect(prc, prc, &textRect))
4592 // Text is not visible
4593 ::SetRectEmpty(prc);
4595 // not equal if text rect was clipped
4596 *pfClipped = !::EqualRect(prc, &textRect);
4598 // ATOK 2011 - 2016 refers native caret position and size on windows whose
4599 // class name is one of Mozilla's windows for deciding candidate window
4600 // position. Additionally, ATOK 2015 and earlier behaves really odd when
4601 // we don't create native caret. Therefore, we need to create native caret
4602 // only when ATOK 2011 - 2015 is active (i.e., not necessary for ATOK 2016).
4603 // However, if a11y module is handling native caret, we shouldn't touch it.
4604 // Note that ATOK must require the latest information of the caret. So,
4605 // even if we'll create native caret later, we need to creat it here with
4606 // current information.
4607 if (!IMEHandler::IsA11yHandlingNativeCaret() &&
4608 StaticPrefs::intl_tsf_hack_atok_create_native_caret() &&
4609 TSFStaticSink::IsATOKReferringNativeCaretActive() &&
4610 mComposition.isSome() &&
4611 mComposition->IsOffsetInRangeOrEndOffset(acpStart) &&
4612 mComposition->IsOffsetInRangeOrEndOffset(acpEnd)) {
4613 CreateNativeCaret();
4616 MOZ_LOG(gIMELog, LogLevel::Info,
4617 ("0x%p TSFTextStore::GetTextExt() succeeded: "
4618 "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }, *pfClipped=%s",
4619 this, prc->left, prc->top, prc->right, prc->bottom,
4620 GetBoolName(*pfClipped)));
4622 return S_OK;
4625 bool TSFTextStore::MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd) {
4626 // When ITextStoreACP::GetTextExt() returns TS_E_NOLAYOUT, TSF returns E_FAIL
4627 // to its caller (typically, active TIP). Then, most TIPs abort current job
4628 // or treat such application as non-GUI apps. E.g., some of them give up
4629 // showing candidate window, some others show candidate window at top-left of
4630 // the screen. For avoiding this issue, when there is composition (until
4631 // composition is actually committed in remote content), we should not
4632 // return TS_E_NOLAYOUT error for TIPs whose some features are broken by
4633 // this issue.
4634 // Note that ideally, this issue should be avoided by each TIP since this
4635 // won't be fixed at least on non-latest Windows. Actually, Google Japanese
4636 // Input (based on Mozc) does it. When GetTextExt() returns E_FAIL, TIPs
4637 // should try to check result of GetRangeFromPoint() because TSF returns
4638 // TS_E_NOLAYOUT correctly in this case. See:
4639 // https://github.com/google/mozc/blob/6b878e31fb6ac4347dc9dfd8ccc1080fe718479f/src/win32/tip/tip_range_util.cc#L237-L257
4641 if (!IsHandlingCompositionInContent() || mContentForTSF.isNothing() ||
4642 !mContentForTSF->HasOrHadComposition() ||
4643 !mContentForTSF->IsLayoutChangedAt(aACPEnd)) {
4644 return false;
4647 MOZ_ASSERT(mComposition.isNothing() ||
4648 mComposition->StartOffset() ==
4649 mContentForTSF->LatestCompositionRange()->StartOffset());
4650 MOZ_ASSERT(mComposition.isNothing() ||
4651 mComposition->EndOffset() ==
4652 mContentForTSF->LatestCompositionRange()->EndOffset());
4654 // If TSF does not have the bug, we need to hack only with a few TIPs.
4655 static const bool sAlllowToStopHackingIfFine =
4656 IsWindows10BuildOrLater(17643) &&
4657 StaticPrefs::
4658 intl_tsf_hack_allow_to_stop_hacking_on_build_17643_or_later();
4660 // We need to compute active TIP now. This may take a couple of milliseconds,
4661 // however, it'll be cached, so, must be faster than check active TIP every
4662 // GetTextExt() calls.
4663 const Maybe<Selection>& selectionForTSF = SelectionForTSF();
4664 switch (TSFStaticSink::ActiveTIP()) {
4665 // MS IME for Japanese doesn't support asynchronous handling at deciding
4666 // its suggest list window position. The feature was implemented
4667 // starting from Windows 8. And also we may meet same trouble in e10s
4668 // mode on Win7. So, we should never return TS_E_NOLAYOUT to MS IME for
4669 // Japanese.
4670 case TextInputProcessorID::eMicrosoftIMEForJapanese:
4671 // Basically, MS-IME tries to retrieve whole composition string rect
4672 // at deciding suggest window immediately after unlocking the document.
4673 // However, in e10s mode, the content hasn't updated yet in most cases.
4674 // Therefore, if the first character at the retrieving range rect is
4675 // available, we should use it as the result.
4676 // Note that according to bug 1609675, MS-IME for Japanese itself does
4677 // not handle TS_E_NOLAYOUT correctly at least on Build 18363.657 (1909).
4678 if (StaticPrefs::
4679 intl_tsf_hack_ms_japanese_ime_do_not_return_no_layout_error_at_first_char() &&
4680 aACPStart < aACPEnd) {
4681 aACPEnd = aACPStart;
4682 break;
4684 if (sAlllowToStopHackingIfFine) {
4685 return false;
4687 // Although, the condition is not clear, MS-IME sometimes retrieves the
4688 // caret rect immediately after modifying the composition string but
4689 // before unlocking the document. In such case, we should return the
4690 // nearest character rect.
4691 // (Let's return true if there is no selection which must be not expected
4692 // by MS-IME nor TSF.)
4693 if (StaticPrefs::
4694 intl_tsf_hack_ms_japanese_ime_do_not_return_no_layout_error_at_caret() &&
4695 aACPStart == aACPEnd && selectionForTSF.isSome() &&
4696 (!selectionForTSF->HasRange() ||
4697 (selectionForTSF->Collapsed() &&
4698 selectionForTSF->EndOffset() == aACPEnd))) {
4699 int32_t minOffsetOfLayoutChanged =
4700 static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
4701 aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
4702 } else {
4703 return false;
4705 break;
4706 // The bug of Microsoft Office IME 2010 for Japanese is similar to
4707 // MS-IME for Win 8.1 and Win 10. Newer version of MS Office IME is not
4708 // released yet. So, we can hack it without prefs because there must be
4709 // no developers who want to disable this hack for tests.
4710 // XXX We have not tested with Microsoft Office IME 2010 since it's
4711 // installable only with Win7 and Win8 (i.e., cannot install Win8.1
4712 // and Win10), and requires upgrade to Win10.
4713 case TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese:
4714 // Basically, MS-IME tries to retrieve whole composition string rect
4715 // at deciding suggest window immediately after unlocking the document.
4716 // However, in e10s mode, the content hasn't updated yet in most cases.
4717 // Therefore, if the first character at the retrieving range rect is
4718 // available, we should use it as the result.
4719 if (aACPStart < aACPEnd) {
4720 aACPEnd = aACPStart;
4722 // Although, the condition is not clear, MS-IME sometimes retrieves the
4723 // caret rect immediately after modifying the composition string but
4724 // before unlocking the document. In such case, we should return the
4725 // nearest character rect.
4726 // (Let's return true if there is no selection which must be not expected
4727 // by MS-IME nor TSF.)
4728 else if (aACPStart == aACPEnd && selectionForTSF.isSome() &&
4729 (!selectionForTSF->HasRange() ||
4730 (selectionForTSF->Collapsed() &&
4731 selectionForTSF->EndOffset() == aACPEnd))) {
4732 int32_t minOffsetOfLayoutChanged =
4733 static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
4734 aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
4735 } else {
4736 return false;
4738 break;
4739 // ATOK fails to handle TS_E_NOLAYOUT only when it decides the position of
4740 // suggest window. In such case, ATOK tries to query rect of whole or a
4741 // part of composition string.
4742 // FYI: ATOK changes their implementation around candidate window and
4743 // suggest widget at ATOK 2016. Therefore, there are some differences
4744 // ATOK 2015 (or older) and ATOK 2016 (or newer).
4745 // FYI: ATOK 2017 stops referring our window class name. I.e., ATOK 2016
4746 // and older may behave differently only on Gecko but this must be
4747 // finished from ATOK 2017.
4748 // FYI: For testing with legacy ATOK, we should hack it even if current ATOK
4749 // refers native caret rect on windows whose window class is one of
4750 // Mozilla window classes and we stop creating native caret for ATOK
4751 // because creating native caret causes ATOK refers caret position
4752 // when GetTextExt() returns TS_E_NOLAYOUT.
4753 case TextInputProcessorID::eATOK2011:
4754 case TextInputProcessorID::eATOK2012:
4755 case TextInputProcessorID::eATOK2013:
4756 case TextInputProcessorID::eATOK2014:
4757 case TextInputProcessorID::eATOK2015:
4758 // ATOK 2016 and later may temporarily show candidate window at odd
4759 // position when you convert a word quickly (e.g., keep pressing
4760 // space bar). So, on ATOK 2016 or later, we need to keep hacking the
4761 // result of GetTextExt().
4762 if (sAlllowToStopHackingIfFine) {
4763 return false;
4765 // If we'll create native caret where we paint our caret. Then, ATOK
4766 // will refer native caret. So, we don't need to hack anything in
4767 // this case.
4768 if (StaticPrefs::intl_tsf_hack_atok_create_native_caret()) {
4769 MOZ_ASSERT(TSFStaticSink::IsATOKReferringNativeCaretActive());
4770 return false;
4772 [[fallthrough]];
4773 case TextInputProcessorID::eATOK2016:
4774 case TextInputProcessorID::eATOKUnknown:
4775 if (!StaticPrefs::
4776 intl_tsf_hack_atok_do_not_return_no_layout_error_of_composition_string()) {
4777 return false;
4779 // If the range is in the composition string, we should return rectangle
4780 // in it as far as possible.
4781 if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4782 aACPStart) ||
4783 !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4784 aACPEnd)) {
4785 return false;
4787 break;
4788 // Japanist 10 fails to handle TS_E_NOLAYOUT when it decides the position
4789 // of candidate window. In such case, Japanist shows candidate window at
4790 // top-left of the screen. So, we should return the nearest caret rect
4791 // where we know. This is Japanist's bug. So, even after build 17643,
4792 // we need this hack.
4793 case TextInputProcessorID::eJapanist10:
4794 if (!StaticPrefs::
4795 intl_tsf_hack_japanist10_do_not_return_no_layout_error_of_composition_string()) {
4796 return false;
4798 if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4799 aACPStart) ||
4800 !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4801 aACPEnd)) {
4802 return false;
4804 break;
4805 // Free ChangJie 2010 doesn't handle ITfContextView::GetTextExt() properly.
4806 // This must be caused by the bug of TSF since Free ChangJie works fine on
4807 // build 17643 and later.
4808 case TextInputProcessorID::eFreeChangJie:
4809 if (sAlllowToStopHackingIfFine) {
4810 return false;
4812 if (!StaticPrefs::
4813 intl_tsf_hack_free_chang_jie_do_not_return_no_layout_error()) {
4814 return false;
4816 aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
4817 aACPStart = std::min(aACPStart, aACPEnd);
4818 break;
4819 // Some Traditional Chinese TIPs of Microsoft don't show candidate window
4820 // in e10s mode on Win8 or later.
4821 case TextInputProcessorID::eMicrosoftQuick:
4822 if (sAlllowToStopHackingIfFine) {
4823 return false; // MS Quick works fine with Win10 build 17643.
4825 [[fallthrough]];
4826 case TextInputProcessorID::eMicrosoftChangJie:
4827 if (!StaticPrefs::
4828 intl_tsf_hack_ms_traditional_chinese_do_not_return_no_layout_error()) {
4829 return false;
4831 aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
4832 aACPStart = std::min(aACPStart, aACPEnd);
4833 break;
4834 // Some Simplified Chinese TIPs of Microsoft don't show candidate window
4835 // in e10s mode on Win8 or later.
4836 // FYI: Only Simplified Chinese TIPs of Microsoft still require this hack
4837 // because they sometimes do not show candidate window when we return
4838 // TS_E_NOLAYOUT for first query. Note that even when they show
4839 // candidate window properly, we return TS_E_NOLAYOUT and following
4840 // log looks same as when they don't show candidate window. Perhaps,
4841 // there is stateful cause or race in them.
4842 case TextInputProcessorID::eMicrosoftPinyin:
4843 case TextInputProcessorID::eMicrosoftWubi:
4844 if (!StaticPrefs::
4845 intl_tsf_hack_ms_simplified_chinese_do_not_return_no_layout_error()) {
4846 return false;
4848 aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
4849 aACPStart = std::min(aACPStart, aACPEnd);
4850 break;
4851 default:
4852 return false;
4855 // If we hack the queried range for active TIP, that means we should not
4856 // return TS_E_NOLAYOUT even if hacked offset is still modified. So, as
4857 // far as possible, we should adjust the offset.
4858 MOZ_ASSERT(mContentForTSF->IsLayoutChanged());
4859 bool collapsed = aACPStart == aACPEnd;
4860 // Note that even if all characters in the editor or the composition
4861 // string was modified, 0 or start offset of the composition string is
4862 // useful because it may return caret rect or old character's rect which
4863 // the user still see. That must be useful information for TIP.
4864 int32_t firstModifiedOffset =
4865 static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
4866 LONG lastUnmodifiedOffset = std::max(firstModifiedOffset - 1, 0);
4867 if (mContentForTSF->IsLayoutChangedAt(aACPStart)) {
4868 if (aACPStart >= mContentForTSF->LatestCompositionRange()->StartOffset()) {
4869 // If mContentForTSF has last composition string and current
4870 // composition string, we can assume that ContentCacheInParent has
4871 // cached rects of composition string at least length of current
4872 // composition string. Otherwise, we can assume that rect for
4873 // first character of composition string is stored since it was
4874 // selection start or caret position.
4875 LONG maxCachedOffset =
4876 mContentForTSF->LatestCompositionRange()->EndOffset();
4877 if (mContentForTSF->LastComposition().isSome()) {
4878 maxCachedOffset = std::min(
4879 maxCachedOffset, mContentForTSF->LastComposition()->EndOffset());
4881 aACPStart = std::min(aACPStart, maxCachedOffset);
4883 // Otherwise, we don't know which character rects are cached. So, we
4884 // need to use first unmodified character's rect in this case. Even
4885 // if there is no character, the query event will return caret rect
4886 // instead.
4887 else {
4888 aACPStart = lastUnmodifiedOffset;
4890 MOZ_ASSERT(aACPStart <= aACPEnd);
4893 // If TIP requests caret rect with collapsed range, we should keep
4894 // collapsing the range.
4895 if (collapsed) {
4896 aACPEnd = aACPStart;
4898 // Let's set aACPEnd to larger offset of last unmodified offset or
4899 // aACPStart which may be the first character offset of the composition
4900 // string. However, some TIPs may want to know the right edge of the
4901 // range. Therefore, if aACPEnd is in composition string and active TIP
4902 // doesn't retrieve caret rect (i.e., the range isn't collapsed), we
4903 // should keep using the original aACPEnd. Otherwise, we should set
4904 // aACPEnd to larger value of aACPStart and lastUnmodifiedOffset.
4905 else if (mContentForTSF->IsLayoutChangedAt(aACPEnd) &&
4906 !mContentForTSF->LatestCompositionRange()
4907 ->IsOffsetInRangeOrEndOffset(aACPEnd)) {
4908 aACPEnd = std::max(aACPStart, lastUnmodifiedOffset);
4911 MOZ_LOG(
4912 gIMELog, LogLevel::Debug,
4913 ("0x%p TSFTextStore::HackNoErrorLayoutBugs() hacked the queried range "
4914 "for not returning TS_E_NOLAYOUT, new values are: "
4915 "aACPStart=%ld, aACPEnd=%ld",
4916 this, aACPStart, aACPEnd));
4918 return true;
4921 STDMETHODIMP
4922 TSFTextStore::GetScreenExt(TsViewCookie vcView, RECT* prc) {
4923 MOZ_LOG(gIMELog, LogLevel::Info,
4924 ("0x%p TSFTextStore::GetScreenExt(vcView=%ld, prc=0x%p)", this,
4925 vcView, prc));
4927 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4928 MOZ_LOG(gIMELog, LogLevel::Error,
4929 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4930 "called with invalid view",
4931 this));
4932 return E_INVALIDARG;
4935 if (!prc) {
4936 MOZ_LOG(gIMELog, LogLevel::Error,
4937 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4938 "null argument",
4939 this));
4940 return E_INVALIDARG;
4943 if (mDestroyed) {
4944 MOZ_LOG(gIMELog, LogLevel::Error,
4945 ("0x%p TSFTextStore::GetScreenExt() returns empty rect "
4946 "due to already destroyed",
4947 this));
4948 prc->left = prc->top = prc->right = prc->bottom = 0;
4949 return S_OK;
4952 if (!GetScreenExtInternal(*prc)) {
4953 MOZ_LOG(gIMELog, LogLevel::Error,
4954 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4955 "GetScreenExtInternal() failure",
4956 this));
4957 return E_FAIL;
4960 MOZ_LOG(gIMELog, LogLevel::Info,
4961 ("0x%p TSFTextStore::GetScreenExt() succeeded: "
4962 "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
4963 this, prc->left, prc->top, prc->right, prc->bottom));
4964 return S_OK;
4967 bool TSFTextStore::GetScreenExtInternal(RECT& aScreenExt) {
4968 MOZ_LOG(gIMELog, LogLevel::Debug,
4969 ("0x%p TSFTextStore::GetScreenExtInternal()", this));
4971 MOZ_ASSERT(!mDestroyed);
4973 // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
4974 WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, mWidget);
4975 mWidget->InitEvent(queryEditorRectEvent);
4976 DispatchEvent(queryEditorRectEvent);
4977 if (queryEditorRectEvent.Failed()) {
4978 MOZ_LOG(gIMELog, LogLevel::Error,
4979 ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
4980 "eQueryEditorRect failure",
4981 this));
4982 return false;
4985 nsWindow* refWindow =
4986 static_cast<nsWindow*>(!!queryEditorRectEvent.mReply->mFocusedWidget
4987 ? queryEditorRectEvent.mReply->mFocusedWidget
4988 : static_cast<nsIWidget*>(mWidget.get()));
4989 // Result rect is in top level widget coordinates
4990 refWindow = refWindow->GetTopLevelWindow(false);
4991 if (!refWindow) {
4992 MOZ_LOG(gIMELog, LogLevel::Error,
4993 ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
4994 "no top level window",
4995 this));
4996 return false;
4999 LayoutDeviceIntRect boundRect = refWindow->GetClientBounds();
5000 boundRect.MoveTo(0, 0);
5002 // Clip frame rect to window rect
5003 boundRect.IntersectRect(queryEditorRectEvent.mReply->mRect, boundRect);
5004 if (!boundRect.IsEmpty()) {
5005 boundRect.MoveBy(refWindow->WidgetToScreenOffset());
5006 ::SetRect(&aScreenExt, boundRect.X(), boundRect.Y(), boundRect.XMost(),
5007 boundRect.YMost());
5008 } else {
5009 ::SetRectEmpty(&aScreenExt);
5012 MOZ_LOG(gIMELog, LogLevel::Debug,
5013 ("0x%p TSFTextStore::GetScreenExtInternal() succeeded: "
5014 "aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
5015 this, aScreenExt.left, aScreenExt.top, aScreenExt.right,
5016 aScreenExt.bottom));
5017 return true;
5020 STDMETHODIMP
5021 TSFTextStore::GetWnd(TsViewCookie vcView, HWND* phwnd) {
5022 MOZ_LOG(gIMELog, LogLevel::Info,
5023 ("0x%p TSFTextStore::GetWnd(vcView=%ld, phwnd=0x%p), "
5024 "mWidget=0x%p",
5025 this, vcView, phwnd, mWidget.get()));
5027 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
5028 MOZ_LOG(gIMELog, LogLevel::Error,
5029 ("0x%p TSFTextStore::GetWnd() FAILED due to "
5030 "called with invalid view",
5031 this));
5032 return E_INVALIDARG;
5035 if (!phwnd) {
5036 MOZ_LOG(gIMELog, LogLevel::Error,
5037 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
5038 "null argument",
5039 this));
5040 return E_INVALIDARG;
5043 *phwnd = mWidget ? mWidget->GetWindowHandle() : nullptr;
5045 MOZ_LOG(gIMELog, LogLevel::Info,
5046 ("0x%p TSFTextStore::GetWnd() succeeded: *phwnd=0x%p", this,
5047 static_cast<void*>(*phwnd)));
5048 return S_OK;
5051 STDMETHODIMP
5052 TSFTextStore::InsertTextAtSelection(DWORD dwFlags, const WCHAR* pchText,
5053 ULONG cch, LONG* pacpStart, LONG* pacpEnd,
5054 TS_TEXTCHANGE* pChange) {
5055 MOZ_LOG(
5056 gIMELog, LogLevel::Info,
5057 ("0x%p TSFTextStore::InsertTextAtSelection(dwFlags=%s, "
5058 "pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, "
5059 "pChange=0x%p), mComposition=%s",
5060 this,
5061 dwFlags == 0 ? "0"
5062 : dwFlags == TF_IAS_NOQUERY ? "TF_IAS_NOQUERY"
5063 : dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY"
5064 : "Unknown",
5065 pchText, pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "",
5066 cch, pacpStart, pacpEnd, pChange, ToString(mComposition).c_str()));
5068 if (cch && !pchText) {
5069 MOZ_LOG(gIMELog, LogLevel::Error,
5070 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5071 "null pchText",
5072 this));
5073 return E_INVALIDARG;
5076 if (TS_IAS_QUERYONLY == dwFlags) {
5077 if (!IsReadLocked()) {
5078 MOZ_LOG(gIMELog, LogLevel::Error,
5079 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5080 "not locked (read)",
5081 this));
5082 return TS_E_NOLOCK;
5085 if (!pacpStart || !pacpEnd) {
5086 MOZ_LOG(gIMELog, LogLevel::Error,
5087 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5088 "null argument",
5089 this));
5090 return E_INVALIDARG;
5093 // Get selection first
5094 Maybe<Selection>& selectionForTSF = SelectionForTSF();
5095 if (selectionForTSF.isNothing()) {
5096 MOZ_LOG(gIMELog, LogLevel::Error,
5097 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5098 "SelectionForTSF() failure",
5099 this));
5100 return E_FAIL;
5103 // Simulate text insertion
5104 if (selectionForTSF->HasRange()) {
5105 *pacpStart = selectionForTSF->StartOffset();
5106 *pacpEnd = selectionForTSF->EndOffset();
5107 if (pChange) {
5108 *pChange = TS_TEXTCHANGE{.acpStart = selectionForTSF->StartOffset(),
5109 .acpOldEnd = selectionForTSF->EndOffset(),
5110 .acpNewEnd = selectionForTSF->StartOffset() +
5111 static_cast<LONG>(cch)};
5113 } else {
5114 // There is no error code to return "no selection" state from this method.
5115 // This means that TSF/TIP should check `GetSelection` result first and
5116 // stop using this. However, this could be called by TIP/TSF if they do
5117 // not do so. Therefore, we should use start of editor instead, but
5118 // notify the caller of nothing will be inserted with pChange->acpNewEnd.
5119 *pacpStart = *pacpEnd = 0;
5120 if (pChange) {
5121 *pChange = TS_TEXTCHANGE{.acpStart = 0, .acpOldEnd = 0, .acpNewEnd = 0};
5124 } else {
5125 if (!IsReadWriteLocked()) {
5126 MOZ_LOG(gIMELog, LogLevel::Error,
5127 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5128 "not locked (read-write)",
5129 this));
5130 return TS_E_NOLOCK;
5133 if (!pChange) {
5134 MOZ_LOG(gIMELog, LogLevel::Error,
5135 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5136 "null pChange",
5137 this));
5138 return E_INVALIDARG;
5141 if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) {
5142 MOZ_LOG(gIMELog, LogLevel::Error,
5143 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5144 "null argument",
5145 this));
5146 return E_INVALIDARG;
5149 if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
5150 pChange)) {
5151 MOZ_LOG(gIMELog, LogLevel::Error,
5152 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5153 "InsertTextAtSelectionInternal() failure",
5154 this));
5155 return E_FAIL;
5158 if (TS_IAS_NOQUERY != dwFlags) {
5159 *pacpStart = pChange->acpStart;
5160 *pacpEnd = pChange->acpNewEnd;
5163 MOZ_LOG(gIMELog, LogLevel::Info,
5164 ("0x%p TSFTextStore::InsertTextAtSelection() succeeded: "
5165 "*pacpStart=%ld, *pacpEnd=%ld, "
5166 "*pChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld })",
5167 this, pacpStart ? *pacpStart : 0, pacpEnd ? *pacpEnd : 0,
5168 pChange ? pChange->acpStart : 0, pChange ? pChange->acpOldEnd : 0,
5169 pChange ? pChange->acpNewEnd : 0));
5170 return S_OK;
5173 bool TSFTextStore::InsertTextAtSelectionInternal(const nsAString& aInsertStr,
5174 TS_TEXTCHANGE* aTextChange) {
5175 MOZ_LOG(gIMELog, LogLevel::Debug,
5176 ("0x%p TSFTextStore::InsertTextAtSelectionInternal("
5177 "aInsertStr=\"%s\", aTextChange=0x%p), mComposition=%s",
5178 this, GetEscapedUTF8String(aInsertStr).get(), aTextChange,
5179 ToString(mComposition).c_str()));
5181 Maybe<Content>& contentForTSF = ContentForTSF();
5182 if (contentForTSF.isNothing()) {
5183 MOZ_LOG(gIMELog, LogLevel::Error,
5184 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() failed "
5185 "due to ContentForTSF() failure()",
5186 this));
5187 return false;
5190 MaybeDispatchKeyboardEventAsProcessedByIME();
5191 if (mDestroyed) {
5192 MOZ_LOG(
5193 gIMELog, LogLevel::Error,
5194 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() FAILED due to "
5195 "destroyed during dispatching a keyboard event",
5196 this));
5197 return false;
5200 const auto numberOfCRLFs = [&]() -> uint32_t {
5201 const auto* str = aInsertStr.BeginReading();
5202 uint32_t num = 0;
5203 for (uint32_t i = 0; i + 1 < aInsertStr.Length(); i++) {
5204 if (str[i] == '\r' && str[i + 1] == '\n') {
5205 num++;
5206 i++;
5209 return num;
5210 }();
5211 if (numberOfCRLFs) {
5212 nsAutoString key;
5213 if (TSFStaticSink::GetActiveTIPNameForTelemetry(key)) {
5214 Telemetry::ScalarSet(
5215 Telemetry::ScalarID::WIDGET_IME_NAME_ON_WINDOWS_INSERTED_CRLF, key,
5216 true);
5220 TS_SELECTION_ACP oldSelection = contentForTSF->Selection()->ACPRef();
5221 if (mComposition.isNothing()) {
5222 // Use a temporary composition to contain the text
5223 PendingAction* compositionStart = mPendingActions.AppendElements(2);
5224 PendingAction* compositionEnd = compositionStart + 1;
5226 compositionStart->mType = PendingAction::Type::eCompositionStart;
5227 compositionStart->mSelectionStart = oldSelection.acpStart;
5228 compositionStart->mSelectionLength =
5229 oldSelection.acpEnd - oldSelection.acpStart;
5230 compositionStart->mAdjustSelection = false;
5232 compositionEnd->mType = PendingAction::Type::eCompositionEnd;
5233 compositionEnd->mData = aInsertStr;
5234 compositionEnd->mSelectionStart = compositionStart->mSelectionStart;
5236 MOZ_LOG(gIMELog, LogLevel::Debug,
5237 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
5238 "appending pending compositionstart and compositionend... "
5239 "PendingCompositionStart={ mSelectionStart=%ld, "
5240 "mSelectionLength=%ld }, PendingCompositionEnd={ mData=\"%s\" "
5241 "(Length()=%zu), mSelectionStart=%ld }",
5242 this, compositionStart->mSelectionStart,
5243 compositionStart->mSelectionLength,
5244 GetEscapedUTF8String(compositionEnd->mData).get(),
5245 compositionEnd->mData.Length(), compositionEnd->mSelectionStart));
5248 contentForTSF->ReplaceSelectedTextWith(aInsertStr);
5250 if (aTextChange) {
5251 aTextChange->acpStart = oldSelection.acpStart;
5252 aTextChange->acpOldEnd = oldSelection.acpEnd;
5253 aTextChange->acpNewEnd = contentForTSF->Selection()->EndOffset();
5256 MOZ_LOG(
5257 gIMELog, LogLevel::Debug,
5258 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
5259 "succeeded: mWidget=0x%p, mWidget->Destroyed()=%s, aTextChange={ "
5260 "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
5261 this, mWidget.get(), GetBoolName(mWidget ? mWidget->Destroyed() : true),
5262 aTextChange ? aTextChange->acpStart : 0,
5263 aTextChange ? aTextChange->acpOldEnd : 0,
5264 aTextChange ? aTextChange->acpNewEnd : 0));
5265 return true;
5268 STDMETHODIMP
5269 TSFTextStore::InsertEmbeddedAtSelection(DWORD dwFlags, IDataObject* pDataObject,
5270 LONG* pacpStart, LONG* pacpEnd,
5271 TS_TEXTCHANGE* pChange) {
5272 MOZ_LOG(gIMELog, LogLevel::Info,
5273 ("0x%p TSFTextStore::InsertEmbeddedAtSelection() called "
5274 "but not supported (E_NOTIMPL)",
5275 this));
5277 // embedded objects are not supported
5278 return E_NOTIMPL;
5281 HRESULT TSFTextStore::RecordCompositionStartAction(
5282 ITfCompositionView* aCompositionView, ITfRange* aRange,
5283 bool aPreserveSelection) {
5284 MOZ_LOG(gIMELog, LogLevel::Debug,
5285 ("0x%p TSFTextStore::RecordCompositionStartAction("
5286 "aCompositionView=0x%p, aRange=0x%p, aPreserveSelection=%s), "
5287 "mComposition=%s",
5288 this, aCompositionView, aRange, GetBoolName(aPreserveSelection),
5289 ToString(mComposition).c_str()));
5291 LONG start = 0, length = 0;
5292 HRESULT hr = GetRangeExtent(aRange, &start, &length);
5293 if (FAILED(hr)) {
5294 MOZ_LOG(gIMELog, LogLevel::Error,
5295 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5296 "due to GetRangeExtent() failure",
5297 this));
5298 return hr;
5301 return RecordCompositionStartAction(aCompositionView, start, length,
5302 aPreserveSelection);
5305 HRESULT TSFTextStore::RecordCompositionStartAction(
5306 ITfCompositionView* aCompositionView, LONG aStart, LONG aLength,
5307 bool aPreserveSelection) {
5308 MOZ_LOG(gIMELog, LogLevel::Debug,
5309 ("0x%p TSFTextStore::RecordCompositionStartAction("
5310 "aCompositionView=0x%p, aStart=%ld, aLength=%ld, "
5311 "aPreserveSelection=%s), "
5312 "mComposition=%s",
5313 this, aCompositionView, aStart, aLength,
5314 GetBoolName(aPreserveSelection), ToString(mComposition).c_str()));
5316 Maybe<Content>& contentForTSF = ContentForTSF();
5317 if (contentForTSF.isNothing()) {
5318 MOZ_LOG(gIMELog, LogLevel::Error,
5319 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5320 "due to ContentForTSF() failure",
5321 this));
5322 return E_FAIL;
5325 MaybeDispatchKeyboardEventAsProcessedByIME();
5326 if (mDestroyed) {
5327 MOZ_LOG(
5328 gIMELog, LogLevel::Error,
5329 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED due to "
5330 "destroyed during dispatching a keyboard event",
5331 this));
5332 return false;
5335 CompleteLastActionIfStillIncomplete();
5337 // TIP may have inserted text at selection before calling
5338 // OnStartComposition(). In this case, we've already created a pending
5339 // compositionend. If new composition replaces all commit string of the
5340 // pending compositionend, we should cancel the pending compositionend and
5341 // keep the previous composition normally.
5342 // On Windows 7, MS-IME for Korean, MS-IME 2010 for Korean and MS Old Hangul
5343 // may start composition with calling InsertTextAtSelection() and
5344 // OnStartComposition() with this order (bug 1208043).
5345 // On Windows 10, MS Pinyin, MS Wubi, MS ChangJie and MS Quick commits
5346 // last character and replace it with empty string with new composition
5347 // when user removes last character of composition string with Backspace
5348 // key (bug 1462257).
5349 if (!aPreserveSelection &&
5350 IsLastPendingActionCompositionEndAt(aStart, aLength)) {
5351 const PendingAction& pendingCompositionEnd = mPendingActions.LastElement();
5352 contentForTSF->RestoreCommittedComposition(aCompositionView,
5353 pendingCompositionEnd);
5354 mPendingActions.RemoveLastElement();
5355 MOZ_LOG(gIMELog, LogLevel::Info,
5356 ("0x%p TSFTextStore::RecordCompositionStartAction() "
5357 "succeeded: restoring the committed string as composing string, "
5358 "mComposition=%s, mSelectionForTSF=%s",
5359 this, ToString(mComposition).c_str(),
5360 ToString(mSelectionForTSF).c_str()));
5361 return S_OK;
5364 PendingAction* action = mPendingActions.AppendElement();
5365 action->mType = PendingAction::Type::eCompositionStart;
5366 action->mSelectionStart = aStart;
5367 action->mSelectionLength = aLength;
5369 Maybe<Selection>& selectionForTSF = SelectionForTSF();
5370 if (selectionForTSF.isNothing()) {
5371 MOZ_LOG(gIMELog, LogLevel::Error,
5372 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5373 "due to SelectionForTSF() failure",
5374 this));
5375 action->mAdjustSelection = true;
5376 } else if (!selectionForTSF->HasRange()) {
5377 // If there is no selection, let's collapse seletion to the insertion point.
5378 action->mAdjustSelection = true;
5379 } else if (selectionForTSF->MinOffset() != aStart ||
5380 selectionForTSF->MaxOffset() != aStart + aLength) {
5381 // If new composition range is different from current selection range,
5382 // we need to set selection before dispatching compositionstart event.
5383 action->mAdjustSelection = true;
5384 } else {
5385 // We shouldn't dispatch selection set event before dispatching
5386 // compositionstart event because it may cause put caret different
5387 // position in HTML editor since generated flat text content and offset in
5388 // it are lossy data of HTML contents.
5389 action->mAdjustSelection = false;
5392 contentForTSF->StartComposition(aCompositionView, *action,
5393 aPreserveSelection);
5394 MOZ_ASSERT(mComposition.isSome());
5395 action->mData = mComposition->DataRef();
5397 MOZ_LOG(gIMELog, LogLevel::Info,
5398 ("0x%p TSFTextStore::RecordCompositionStartAction() succeeded: "
5399 "mComposition=%s, mSelectionForTSF=%s }",
5400 this, ToString(mComposition).c_str(),
5401 ToString(mSelectionForTSF).c_str()));
5402 return S_OK;
5405 HRESULT
5406 TSFTextStore::RecordCompositionEndAction() {
5407 MOZ_LOG(gIMELog, LogLevel::Debug,
5408 ("0x%p TSFTextStore::RecordCompositionEndAction(), "
5409 "mComposition=%s",
5410 this, ToString(mComposition).c_str()));
5412 MOZ_ASSERT(mComposition.isSome());
5414 if (mComposition.isNothing()) {
5415 MOZ_LOG(gIMELog, LogLevel::Error,
5416 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
5417 "no composition",
5418 this));
5419 return false;
5422 MaybeDispatchKeyboardEventAsProcessedByIME();
5423 if (mDestroyed) {
5424 MOZ_LOG(gIMELog, LogLevel::Error,
5425 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
5426 "destroyed during dispatching a keyboard event",
5427 this));
5428 return false;
5431 // If we're handling incomplete composition update or already handled
5432 // composition update, we can forget them since composition end will send
5433 // the latest composition string and it overwrites the composition string
5434 // even if we dispatch eCompositionChange event before that. So, let's
5435 // forget all composition updates now.
5436 RemoveLastCompositionUpdateActions();
5437 PendingAction* action = mPendingActions.AppendElement();
5438 action->mType = PendingAction::Type::eCompositionEnd;
5439 action->mData = mComposition->DataRef();
5440 action->mSelectionStart = mComposition->StartOffset();
5442 Maybe<Content>& contentForTSF = ContentForTSF();
5443 if (contentForTSF.isNothing()) {
5444 MOZ_LOG(gIMELog, LogLevel::Error,
5445 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due "
5446 "to ContentForTSF() failure",
5447 this));
5448 return E_FAIL;
5450 contentForTSF->EndComposition(*action);
5452 // If this composition was restart but the composition doesn't modify
5453 // anything, we should remove the pending composition for preventing to
5454 // dispatch redundant composition events.
5455 for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) {
5456 PendingAction& pendingAction = mPendingActions[i - 1];
5457 if (pendingAction.mType == PendingAction::Type::eCompositionStart) {
5458 if (pendingAction.mData != action->mData) {
5459 break;
5461 // When only setting selection is necessary, we should append it.
5462 if (pendingAction.mAdjustSelection) {
5463 LONG selectionStart = pendingAction.mSelectionStart;
5464 LONG selectionLength = pendingAction.mSelectionLength;
5466 PendingAction* setSelection = mPendingActions.AppendElement();
5467 setSelection->mType = PendingAction::Type::eSetSelection;
5468 setSelection->mSelectionStart = selectionStart;
5469 setSelection->mSelectionLength = selectionLength;
5470 setSelection->mSelectionReversed = false;
5472 // Remove the redundant pending composition.
5473 mPendingActions.RemoveElementsAt(i - 1, j);
5474 MOZ_LOG(gIMELog, LogLevel::Info,
5475 ("0x%p TSFTextStore::RecordCompositionEndAction(), "
5476 "succeeded, but the composition was canceled due to redundant",
5477 this));
5478 return S_OK;
5482 MOZ_LOG(
5483 gIMELog, LogLevel::Info,
5484 ("0x%p TSFTextStore::RecordCompositionEndAction(), succeeded", this));
5485 return S_OK;
5488 STDMETHODIMP
5489 TSFTextStore::OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) {
5490 MOZ_LOG(gIMELog, LogLevel::Info,
5491 ("0x%p TSFTextStore::OnStartComposition(pComposition=0x%p, "
5492 "pfOk=0x%p), mComposition=%s",
5493 this, pComposition, pfOk, ToString(mComposition).c_str()));
5495 AutoPendingActionAndContentFlusher flusher(this);
5497 *pfOk = FALSE;
5499 // Only one composition at a time
5500 if (mComposition.isSome()) {
5501 MOZ_LOG(gIMELog, LogLevel::Error,
5502 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5503 "there is another composition already (but returns S_OK)",
5504 this));
5505 return S_OK;
5508 RefPtr<ITfRange> range;
5509 HRESULT hr = pComposition->GetRange(getter_AddRefs(range));
5510 if (FAILED(hr)) {
5511 MOZ_LOG(gIMELog, LogLevel::Error,
5512 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5513 "pComposition->GetRange() failure",
5514 this));
5515 return hr;
5517 hr = RecordCompositionStartAction(pComposition, range, false);
5518 if (FAILED(hr)) {
5519 MOZ_LOG(gIMELog, LogLevel::Error,
5520 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5521 "RecordCompositionStartAction() failure",
5522 this));
5523 return hr;
5526 *pfOk = TRUE;
5527 MOZ_LOG(gIMELog, LogLevel::Info,
5528 ("0x%p TSFTextStore::OnStartComposition() succeeded", this));
5529 return S_OK;
5532 STDMETHODIMP
5533 TSFTextStore::OnUpdateComposition(ITfCompositionView* pComposition,
5534 ITfRange* pRangeNew) {
5535 MOZ_LOG(gIMELog, LogLevel::Info,
5536 ("0x%p TSFTextStore::OnUpdateComposition(pComposition=0x%p, "
5537 "pRangeNew=0x%p), mComposition=%s",
5538 this, pComposition, pRangeNew, ToString(mComposition).c_str()));
5540 AutoPendingActionAndContentFlusher flusher(this);
5542 if (!mDocumentMgr || !mContext) {
5543 MOZ_LOG(gIMELog, LogLevel::Error,
5544 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5545 "not ready for the composition",
5546 this));
5547 return E_UNEXPECTED;
5549 if (mComposition.isNothing()) {
5550 MOZ_LOG(gIMELog, LogLevel::Error,
5551 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5552 "no active composition",
5553 this));
5554 return E_UNEXPECTED;
5556 if (mComposition->GetView() != pComposition) {
5557 MOZ_LOG(gIMELog, LogLevel::Error,
5558 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5559 "different composition view specified",
5560 this));
5561 return E_UNEXPECTED;
5564 // pRangeNew is null when the update is not complete
5565 if (!pRangeNew) {
5566 MaybeDispatchKeyboardEventAsProcessedByIME();
5567 if (mDestroyed) {
5568 MOZ_LOG(gIMELog, LogLevel::Error,
5569 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5570 "destroyed during dispatching a keyboard event",
5571 this));
5572 return E_FAIL;
5574 PendingAction* action = LastOrNewPendingCompositionUpdate();
5575 action->mIncomplete = true;
5576 MOZ_LOG(gIMELog, LogLevel::Info,
5577 ("0x%p TSFTextStore::OnUpdateComposition() succeeded but "
5578 "not complete",
5579 this));
5580 return S_OK;
5583 HRESULT hr = RestartCompositionIfNecessary(pRangeNew);
5584 if (FAILED(hr)) {
5585 MOZ_LOG(gIMELog, LogLevel::Error,
5586 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5587 "RestartCompositionIfNecessary() failure",
5588 this));
5589 return hr;
5592 hr = RecordCompositionUpdateAction();
5593 if (FAILED(hr)) {
5594 MOZ_LOG(gIMELog, LogLevel::Error,
5595 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5596 "RecordCompositionUpdateAction() failure",
5597 this));
5598 return hr;
5601 if (MOZ_LOG_TEST(gIMELog, LogLevel::Info)) {
5602 Maybe<Selection>& selectionForTSF = SelectionForTSF();
5603 if (selectionForTSF.isNothing()) {
5604 MOZ_LOG(gIMELog, LogLevel::Error,
5605 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5606 "SelectionForTSF() failure",
5607 this));
5608 return S_OK; // Don't return error only when we're logging.
5610 MOZ_LOG(gIMELog, LogLevel::Info,
5611 ("0x%p TSFTextStore::OnUpdateComposition() succeeded: "
5612 "mComposition=%s, SelectionForTSF()=%s",
5613 this, ToString(mComposition).c_str(),
5614 ToString(selectionForTSF).c_str()));
5616 return S_OK;
5619 STDMETHODIMP
5620 TSFTextStore::OnEndComposition(ITfCompositionView* pComposition) {
5621 MOZ_LOG(gIMELog, LogLevel::Info,
5622 ("0x%p TSFTextStore::OnEndComposition(pComposition=0x%p), "
5623 "mComposition=%s",
5624 this, pComposition, ToString(mComposition).c_str()));
5626 AutoPendingActionAndContentFlusher flusher(this);
5628 if (mComposition.isNothing()) {
5629 MOZ_LOG(gIMELog, LogLevel::Error,
5630 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5631 "no active composition",
5632 this));
5633 return E_UNEXPECTED;
5636 if (mComposition->GetView() != pComposition) {
5637 MOZ_LOG(gIMELog, LogLevel::Error,
5638 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5639 "different composition view specified",
5640 this));
5641 return E_UNEXPECTED;
5644 HRESULT hr = RecordCompositionEndAction();
5645 if (FAILED(hr)) {
5646 MOZ_LOG(gIMELog, LogLevel::Error,
5647 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5648 "RecordCompositionEndAction() failure",
5649 this));
5650 return hr;
5653 MOZ_LOG(gIMELog, LogLevel::Info,
5654 ("0x%p TSFTextStore::OnEndComposition(), succeeded", this));
5655 return S_OK;
5658 STDMETHODIMP
5659 TSFTextStore::AdviseMouseSink(ITfRangeACP* range, ITfMouseSink* pSink,
5660 DWORD* pdwCookie) {
5661 MOZ_LOG(gIMELog, LogLevel::Info,
5662 ("0x%p TSFTextStore::AdviseMouseSink(range=0x%p, pSink=0x%p, "
5663 "pdwCookie=0x%p)",
5664 this, range, pSink, pdwCookie));
5666 if (!pdwCookie) {
5667 MOZ_LOG(gIMELog, LogLevel::Error,
5668 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5669 "pdwCookie is null",
5670 this));
5671 return E_INVALIDARG;
5673 // Initialize the result with invalid cookie for safety.
5674 *pdwCookie = MouseTracker::kInvalidCookie;
5676 if (!range) {
5677 MOZ_LOG(gIMELog, LogLevel::Error,
5678 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5679 "range is null",
5680 this));
5681 return E_INVALIDARG;
5683 if (!pSink) {
5684 MOZ_LOG(gIMELog, LogLevel::Error,
5685 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5686 "pSink is null",
5687 this));
5688 return E_INVALIDARG;
5691 // Looking for an unusing tracker.
5692 MouseTracker* tracker = nullptr;
5693 for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
5694 if (mMouseTrackers[i].IsUsing()) {
5695 continue;
5697 tracker = &mMouseTrackers[i];
5699 // If there is no unusing tracker, create new one.
5700 // XXX Should we make limitation of the number of installs?
5701 if (!tracker) {
5702 tracker = mMouseTrackers.AppendElement();
5703 HRESULT hr = tracker->Init(this);
5704 if (FAILED(hr)) {
5705 MOZ_LOG(gIMELog, LogLevel::Error,
5706 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to "
5707 "failure of MouseTracker::Init()",
5708 this));
5709 return hr;
5712 HRESULT hr = tracker->AdviseSink(this, range, pSink);
5713 if (FAILED(hr)) {
5714 MOZ_LOG(gIMELog, LogLevel::Error,
5715 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to failure "
5716 "of MouseTracker::Init()",
5717 this));
5718 return hr;
5720 *pdwCookie = tracker->Cookie();
5721 MOZ_LOG(gIMELog, LogLevel::Info,
5722 ("0x%p TSFTextStore::AdviseMouseSink(), succeeded, "
5723 "*pdwCookie=%ld",
5724 this, *pdwCookie));
5725 return S_OK;
5728 STDMETHODIMP
5729 TSFTextStore::UnadviseMouseSink(DWORD dwCookie) {
5730 MOZ_LOG(
5731 gIMELog, LogLevel::Info,
5732 ("0x%p TSFTextStore::UnadviseMouseSink(dwCookie=%ld)", this, dwCookie));
5733 if (dwCookie == MouseTracker::kInvalidCookie) {
5734 MOZ_LOG(gIMELog, LogLevel::Error,
5735 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5736 "the cookie is invalid value",
5737 this));
5738 return E_INVALIDARG;
5740 // The cookie value must be an index of mMouseTrackers.
5741 // We can use this shortcut for now.
5742 if (static_cast<size_t>(dwCookie) >= mMouseTrackers.Length()) {
5743 MOZ_LOG(gIMELog, LogLevel::Error,
5744 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5745 "the cookie is too large value",
5746 this));
5747 return E_INVALIDARG;
5749 MouseTracker& tracker = mMouseTrackers[dwCookie];
5750 if (!tracker.IsUsing()) {
5751 MOZ_LOG(gIMELog, LogLevel::Error,
5752 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5753 "the found tracker uninstalled already",
5754 this));
5755 return E_INVALIDARG;
5757 tracker.UnadviseSink();
5758 MOZ_LOG(gIMELog, LogLevel::Info,
5759 ("0x%p TSFTextStore::UnadviseMouseSink(), succeeded", this));
5760 return S_OK;
5763 // static
5764 nsresult TSFTextStore::OnFocusChange(bool aGotFocus, nsWindow* aFocusedWidget,
5765 const InputContext& aContext) {
5766 MOZ_LOG(gIMELog, LogLevel::Debug,
5767 (" TSFTextStore::OnFocusChange(aGotFocus=%s, "
5768 "aFocusedWidget=0x%p, aContext=%s), "
5769 "sThreadMgr=0x%p, sEnabledTextStore=0x%p",
5770 GetBoolName(aGotFocus), aFocusedWidget,
5771 mozilla::ToString(aContext).c_str(), sThreadMgr.get(),
5772 sEnabledTextStore.get()));
5774 if (NS_WARN_IF(!IsInTSFMode())) {
5775 return NS_ERROR_NOT_AVAILABLE;
5778 RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
5779 bool hasFocus = ThinksHavingFocus();
5780 RefPtr<TSFTextStore> oldTextStore = sEnabledTextStore.forget();
5782 // If currently oldTextStore still has focus, notifies TSF of losing focus.
5783 if (hasFocus) {
5784 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5785 DebugOnly<HRESULT> hr = threadMgr->AssociateFocus(
5786 oldTextStore->mWidget->GetWindowHandle(), nullptr,
5787 getter_AddRefs(prevFocusedDocumentMgr));
5788 NS_ASSERTION(SUCCEEDED(hr), "Disassociating focus failed");
5789 NS_ASSERTION(prevFocusedDocumentMgr == oldTextStore->mDocumentMgr,
5790 "different documentMgr has been associated with the window");
5793 // Even if there was a focused TextStore, we won't use it with new focused
5794 // editor. So, release it now.
5795 if (oldTextStore) {
5796 oldTextStore->Destroy();
5799 if (NS_WARN_IF(!sThreadMgr)) {
5800 MOZ_LOG(gIMELog, LogLevel::Error,
5801 (" TSFTextStore::OnFocusChange() FAILED, due to "
5802 "sThreadMgr being destroyed during calling "
5803 "ITfThreadMgr::AssociateFocus()"));
5804 return NS_ERROR_FAILURE;
5806 if (NS_WARN_IF(sEnabledTextStore)) {
5807 MOZ_LOG(
5808 gIMELog, LogLevel::Error,
5809 (" TSFTextStore::OnFocusChange() FAILED, due to "
5810 "nested event handling has created another focused TextStore during "
5811 "calling ITfThreadMgr::AssociateFocus()"));
5812 return NS_ERROR_FAILURE;
5815 // If this is a notification of blur, move focus to the dummy document
5816 // manager.
5817 if (!aGotFocus || !aContext.mIMEState.IsEditable()) {
5818 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5819 RefPtr<ITfDocumentMgr> disabledDocumentMgr = sDisabledDocumentMgr;
5820 HRESULT hr = threadMgr->SetFocus(disabledDocumentMgr);
5821 if (NS_WARN_IF(FAILED(hr))) {
5822 MOZ_LOG(gIMELog, LogLevel::Error,
5823 (" TSFTextStore::OnFocusChange() FAILED due to "
5824 "ITfThreadMgr::SetFocus() failure"));
5825 return NS_ERROR_FAILURE;
5827 return NS_OK;
5830 // If an editor is getting focus, create new TextStore and set focus.
5831 if (NS_WARN_IF(!CreateAndSetFocus(aFocusedWidget, aContext))) {
5832 MOZ_LOG(gIMELog, LogLevel::Error,
5833 (" TSFTextStore::OnFocusChange() FAILED due to "
5834 "ITfThreadMgr::CreateAndSetFocus() failure"));
5835 return NS_ERROR_FAILURE;
5837 return NS_OK;
5840 // static
5841 void TSFTextStore::EnsureToDestroyAndReleaseEnabledTextStoreIf(
5842 RefPtr<TSFTextStore>& aTextStore) {
5843 aTextStore->Destroy();
5844 if (sEnabledTextStore == aTextStore) {
5845 sEnabledTextStore = nullptr;
5847 aTextStore = nullptr;
5850 // static
5851 bool TSFTextStore::CreateAndSetFocus(nsWindow* aFocusedWidget,
5852 const InputContext& aContext) {
5853 // TSF might do something which causes that we need to access static methods
5854 // of TSFTextStore. At that time, sEnabledTextStore may be necessary.
5855 // So, we should set sEnabledTextStore directly.
5856 RefPtr<TSFTextStore> textStore = new TSFTextStore();
5857 sEnabledTextStore = textStore;
5858 if (NS_WARN_IF(!textStore->Init(aFocusedWidget, aContext))) {
5859 MOZ_LOG(gIMELog, LogLevel::Error,
5860 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5861 "TSFTextStore::Init() failure"));
5862 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5863 return false;
5865 RefPtr<ITfDocumentMgr> newDocMgr = textStore->mDocumentMgr;
5866 if (NS_WARN_IF(!newDocMgr)) {
5867 MOZ_LOG(gIMELog, LogLevel::Error,
5868 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5869 "invalid TSFTextStore::mDocumentMgr"));
5870 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5871 return false;
5873 if (aContext.mIMEState.mEnabled == IMEEnabled::Password) {
5874 MarkContextAsKeyboardDisabled(textStore->mContext);
5875 RefPtr<ITfContext> topContext;
5876 newDocMgr->GetTop(getter_AddRefs(topContext));
5877 if (topContext && topContext != textStore->mContext) {
5878 MarkContextAsKeyboardDisabled(topContext);
5882 HRESULT hr;
5883 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5884 hr = threadMgr->SetFocus(newDocMgr);
5886 if (NS_WARN_IF(FAILED(hr))) {
5887 MOZ_LOG(gIMELog, LogLevel::Error,
5888 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5889 "ITfTheadMgr::SetFocus() failure"));
5890 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5891 return false;
5893 if (NS_WARN_IF(!sThreadMgr)) {
5894 MOZ_LOG(gIMELog, LogLevel::Error,
5895 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5896 "sThreadMgr being destroyed during calling "
5897 "ITfTheadMgr::SetFocus()"));
5898 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5899 return false;
5901 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5902 MOZ_LOG(gIMELog, LogLevel::Error,
5903 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5904 "creating TextStore has lost focus during calling "
5905 "ITfThreadMgr::SetFocus()"));
5906 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5907 return false;
5910 // Use AssociateFocus() for ensuring that any native focus event
5911 // never steal focus from our documentMgr.
5912 RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
5913 hr = threadMgr->AssociateFocus(aFocusedWidget->GetWindowHandle(), newDocMgr,
5914 getter_AddRefs(prevFocusedDocumentMgr));
5915 if (NS_WARN_IF(FAILED(hr))) {
5916 MOZ_LOG(gIMELog, LogLevel::Error,
5917 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5918 "ITfTheadMgr::AssociateFocus() failure"));
5919 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5920 return false;
5922 if (NS_WARN_IF(!sThreadMgr)) {
5923 MOZ_LOG(gIMELog, LogLevel::Error,
5924 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5925 "sThreadMgr being destroyed during calling "
5926 "ITfTheadMgr::AssociateFocus()"));
5927 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5928 return false;
5930 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5931 MOZ_LOG(gIMELog, LogLevel::Error,
5932 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5933 "creating TextStore has lost focus during calling "
5934 "ITfTheadMgr::AssociateFocus()"));
5935 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5936 return false;
5939 if (textStore->mSink) {
5940 MOZ_LOG(gIMELog, LogLevel::Info,
5941 (" TSFTextStore::CreateAndSetFocus(), calling "
5942 "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE) for 0x%p...",
5943 textStore.get()));
5944 RefPtr<ITextStoreACPSink> sink = textStore->mSink;
5945 sink->OnLayoutChange(TS_LC_CREATE, TEXTSTORE_DEFAULT_VIEW);
5946 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5947 MOZ_LOG(gIMELog, LogLevel::Error,
5948 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5949 "creating TextStore has lost focus during calling "
5950 "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE)"));
5951 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5952 return false;
5955 return true;
5958 // static
5959 IMENotificationRequests TSFTextStore::GetIMENotificationRequests() {
5960 if (!sEnabledTextStore || NS_WARN_IF(!sEnabledTextStore->mDocumentMgr)) {
5961 // If there is no active text store, we don't need any notifications
5962 // since there is no sink which needs notifications.
5963 return IMENotificationRequests();
5966 // Otherwise, requests all notifications since even if some of them may not
5967 // be required by the sink of active TIP, active TIP may be changed and
5968 // other TIPs may need all notifications.
5969 // Note that Windows temporarily steal focus from active window if the main
5970 // process which created the window becomes busy. In this case, we shouldn't
5971 // commit composition since user may want to continue to compose the
5972 // composition after becoming not busy. Therefore, we need notifications
5973 // even during deactive.
5974 // Be aware, we don't need to check actual focused text store. For example,
5975 // MS-IME for Japanese handles focus messages by themselves and sets focused
5976 // text store to nullptr when the process is being inactivated. However,
5977 // we still need to reuse sEnabledTextStore if the process is activated and
5978 // focused element isn't changed. Therefore, if sEnabledTextStore isn't
5979 // nullptr, we need to keep notifying the sink even when it is not focused
5980 // text store for the thread manager.
5981 return IMENotificationRequests(
5982 IMENotificationRequests::NOTIFY_TEXT_CHANGE |
5983 IMENotificationRequests::NOTIFY_POSITION_CHANGE |
5984 IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR |
5985 IMENotificationRequests::NOTIFY_DURING_DEACTIVE);
5988 nsresult TSFTextStore::OnTextChangeInternal(
5989 const IMENotification& aIMENotification) {
5990 const TextChangeDataBase& textChangeData = aIMENotification.mTextChangeData;
5992 MOZ_LOG(gIMELog, LogLevel::Debug,
5993 ("0x%p TSFTextStore::OnTextChangeInternal(aIMENotification={ "
5994 "mMessage=0x%08X, mTextChangeData=%s }), "
5995 "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, "
5996 "mComposition=%s",
5997 this, aIMENotification.mMessage,
5998 mozilla::ToString(textChangeData).c_str(), GetBoolName(mDestroyed),
5999 mSink.get(), GetSinkMaskNameStr(mSinkMask).get(),
6000 ToString(mComposition).c_str()));
6002 if (mDestroyed) {
6003 // If this instance is already destroyed, we shouldn't notify TSF of any
6004 // changes.
6005 return NS_OK;
6008 mDeferNotifyingTSFUntilNextUpdate = false;
6010 // Different from selection change, we don't modify anything with text
6011 // change data. Therefore, if neither TSF not TIP wants text change
6012 // notifications, we don't need to store the changes.
6013 if (!mSink || !(mSinkMask & TS_AS_TEXT_CHANGE)) {
6014 return NS_OK;
6017 // Merge any text change data even if it's caused by composition.
6018 mPendingTextChangeData.MergeWith(textChangeData);
6020 MaybeFlushPendingNotifications();
6022 return NS_OK;
6025 void TSFTextStore::NotifyTSFOfTextChange() {
6026 MOZ_ASSERT(!mDestroyed);
6027 MOZ_ASSERT(!IsReadLocked());
6028 MOZ_ASSERT(mComposition.isNothing());
6029 MOZ_ASSERT(mPendingTextChangeData.IsValid());
6031 // If the text changes are caused only by composition, we don't need to
6032 // notify TSF of the text changes.
6033 if (mPendingTextChangeData.mCausedOnlyByComposition) {
6034 mPendingTextChangeData.Clear();
6035 return;
6038 // First, forget cached selection.
6039 mSelectionForTSF.reset();
6041 // For making it safer, we should check if there is a valid sink to receive
6042 // text change notification.
6043 if (NS_WARN_IF(!mSink) || NS_WARN_IF(!(mSinkMask & TS_AS_TEXT_CHANGE))) {
6044 MOZ_LOG(gIMELog, LogLevel::Error,
6045 ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
6046 "mSink is not ready to call ITextStoreACPSink::OnTextChange()...",
6047 this));
6048 mPendingTextChangeData.Clear();
6049 return;
6052 if (NS_WARN_IF(!mPendingTextChangeData.IsInInt32Range())) {
6053 MOZ_LOG(gIMELog, LogLevel::Error,
6054 ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
6055 "offset is too big for calling "
6056 "ITextStoreACPSink::OnTextChange()...",
6057 this));
6058 mPendingTextChangeData.Clear();
6059 return;
6062 TS_TEXTCHANGE textChange;
6063 textChange.acpStart = static_cast<LONG>(mPendingTextChangeData.mStartOffset);
6064 textChange.acpOldEnd =
6065 static_cast<LONG>(mPendingTextChangeData.mRemovedEndOffset);
6066 textChange.acpNewEnd =
6067 static_cast<LONG>(mPendingTextChangeData.mAddedEndOffset);
6068 mPendingTextChangeData.Clear();
6070 MOZ_LOG(
6071 gIMELog, LogLevel::Info,
6072 ("0x%p TSFTextStore::NotifyTSFOfTextChange(), calling "
6073 "ITextStoreACPSink::OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
6074 "acpNewEnd=%ld })...",
6075 this, textChange.acpStart, textChange.acpOldEnd, textChange.acpNewEnd));
6076 RefPtr<ITextStoreACPSink> sink = mSink;
6077 sink->OnTextChange(0, &textChange);
6080 nsresult TSFTextStore::OnSelectionChangeInternal(
6081 const IMENotification& aIMENotification) {
6082 const SelectionChangeDataBase& selectionChangeData =
6083 aIMENotification.mSelectionChangeData;
6084 MOZ_LOG(gIMELog, LogLevel::Debug,
6085 ("0x%p TSFTextStore::OnSelectionChangeInternal("
6086 "aIMENotification={ mSelectionChangeData=%s }), mDestroyed=%s, "
6087 "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, "
6088 "mComposition=%s",
6089 this, mozilla::ToString(selectionChangeData).c_str(),
6090 GetBoolName(mDestroyed), mSink.get(),
6091 GetSinkMaskNameStr(mSinkMask).get(),
6092 GetBoolName(mIsRecordingActionsWithoutLock),
6093 ToString(mComposition).c_str()));
6095 if (mDestroyed) {
6096 // If this instance is already destroyed, we shouldn't notify TSF of any
6097 // changes.
6098 return NS_OK;
6101 mDeferNotifyingTSFUntilNextUpdate = false;
6103 // Assign the new selection change data to the pending selection change data
6104 // because only the latest selection data is necessary.
6105 // Note that this is necessary to update mSelectionForTSF. Therefore, even if
6106 // neither TSF nor TIP wants selection change notifications, we need to
6107 // store the selection information.
6108 mPendingSelectionChangeData = Some(selectionChangeData);
6110 // Flush remaining pending notifications here if it's possible.
6111 MaybeFlushPendingNotifications();
6113 // If we're available, we should create native caret instead of IMEHandler
6114 // because we may have some cache to do it.
6115 // Note that if we have composition, we'll notified composition-updated
6116 // later so that we don't need to create native caret in such case.
6117 if (!IsHandlingCompositionInContent() &&
6118 IMEHandler::NeedsToCreateNativeCaret()) {
6119 CreateNativeCaret();
6122 return NS_OK;
6125 void TSFTextStore::NotifyTSFOfSelectionChange() {
6126 MOZ_ASSERT(!mDestroyed);
6127 MOZ_ASSERT(!IsReadLocked());
6128 MOZ_ASSERT(mComposition.isNothing());
6129 MOZ_ASSERT(mPendingSelectionChangeData.isSome());
6131 // If selection range isn't actually changed, we don't need to notify TSF
6132 // of this selection change.
6133 if (mSelectionForTSF.isNothing()) {
6134 MOZ_DIAGNOSTIC_ASSERT(!mIsInitializingSelectionForTSF,
6135 "While mSelectionForTSF is being initialized, this "
6136 "should not be called");
6137 mSelectionForTSF.emplace(*mPendingSelectionChangeData);
6138 } else if (!mSelectionForTSF->SetSelection(*mPendingSelectionChangeData)) {
6139 mPendingSelectionChangeData.reset();
6140 MOZ_LOG(gIMELog, LogLevel::Debug,
6141 ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), "
6142 "selection isn't actually changed.",
6143 this));
6144 return;
6147 mPendingSelectionChangeData.reset();
6149 if (!mSink || !(mSinkMask & TS_AS_SEL_CHANGE)) {
6150 return;
6153 MOZ_LOG(gIMELog, LogLevel::Info,
6154 ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), calling "
6155 "ITextStoreACPSink::OnSelectionChange()...",
6156 this));
6157 RefPtr<ITextStoreACPSink> sink = mSink;
6158 sink->OnSelectionChange();
6161 nsresult TSFTextStore::OnLayoutChangeInternal() {
6162 if (mDestroyed) {
6163 // If this instance is already destroyed, we shouldn't notify TSF of any
6164 // changes.
6165 return NS_OK;
6168 NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE);
6169 NS_ENSURE_TRUE(mSink, NS_ERROR_FAILURE);
6171 mDeferNotifyingTSFUntilNextUpdate = false;
6173 nsresult rv = NS_OK;
6175 // We need to notify TSF of layout change even if the document is locked.
6176 // So, don't use MaybeFlushPendingNotifications() for flushing pending
6177 // layout change.
6178 MOZ_LOG(gIMELog, LogLevel::Info,
6179 ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
6180 "NotifyTSFOfLayoutChange()...",
6181 this));
6182 if (NS_WARN_IF(!NotifyTSFOfLayoutChange())) {
6183 rv = NS_ERROR_FAILURE;
6186 MOZ_LOG(gIMELog, LogLevel::Debug,
6187 ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
6188 "MaybeFlushPendingNotifications()...",
6189 this));
6190 MaybeFlushPendingNotifications();
6192 return rv;
6195 bool TSFTextStore::NotifyTSFOfLayoutChange() {
6196 MOZ_ASSERT(!mDestroyed);
6198 // If we're waiting a query of layout information from TIP, it means that
6199 // we've returned TS_E_NOLAYOUT error.
6200 bool returnedNoLayoutError = mHasReturnedNoLayoutError || mWaitingQueryLayout;
6202 // If we returned TS_E_NOLAYOUT, TIP should query the computed layout again.
6203 mWaitingQueryLayout = returnedNoLayoutError;
6205 // For avoiding to call this method again at unlocking the document during
6206 // calls of OnLayoutChange(), reset mHasReturnedNoLayoutError.
6207 mHasReturnedNoLayoutError = false;
6209 // Now, layout has been computed. We should notify mContentForTSF for
6210 // making GetTextExt() and GetACPFromPoint() not return TS_E_NOLAYOUT.
6211 if (mContentForTSF.isSome()) {
6212 mContentForTSF->OnLayoutChanged();
6215 if (IMEHandler::NeedsToCreateNativeCaret()) {
6216 // If we're available, we should create native caret instead of IMEHandler
6217 // because we may have some cache to do it.
6218 CreateNativeCaret();
6219 } else {
6220 // Now, the caret position is different from ours. Destroy the native caret
6221 // if we've create it only for GetTextExt().
6222 IMEHandler::MaybeDestroyNativeCaret();
6225 // This method should return true if either way succeeds.
6226 bool ret = true;
6228 if (mSink) {
6229 MOZ_LOG(gIMELog, LogLevel::Info,
6230 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6231 "calling ITextStoreACPSink::OnLayoutChange()...",
6232 this));
6233 RefPtr<ITextStoreACPSink> sink = mSink;
6234 HRESULT hr = sink->OnLayoutChange(TS_LC_CHANGE, TEXTSTORE_DEFAULT_VIEW);
6235 MOZ_LOG(gIMELog, LogLevel::Info,
6236 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6237 "called ITextStoreACPSink::OnLayoutChange()",
6238 this));
6239 ret = SUCCEEDED(hr);
6242 // The layout change caused by composition string change should cause
6243 // calling ITfContextOwnerServices::OnLayoutChange() too.
6244 if (returnedNoLayoutError && mContext) {
6245 RefPtr<ITfContextOwnerServices> service;
6246 mContext->QueryInterface(IID_ITfContextOwnerServices,
6247 getter_AddRefs(service));
6248 if (service) {
6249 MOZ_LOG(gIMELog, LogLevel::Info,
6250 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6251 "calling ITfContextOwnerServices::OnLayoutChange()...",
6252 this));
6253 HRESULT hr = service->OnLayoutChange();
6254 ret = ret && SUCCEEDED(hr);
6255 MOZ_LOG(gIMELog, LogLevel::Info,
6256 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6257 "called ITfContextOwnerServices::OnLayoutChange()",
6258 this));
6262 if (!mWidget || mWidget->Destroyed()) {
6263 MOZ_LOG(gIMELog, LogLevel::Info,
6264 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6265 "the widget is destroyed during calling OnLayoutChange()",
6266 this));
6267 return ret;
6270 if (mDestroyed) {
6271 MOZ_LOG(gIMELog, LogLevel::Info,
6272 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6273 "the TSFTextStore instance is destroyed during calling "
6274 "OnLayoutChange()",
6275 this));
6276 return ret;
6279 // If we returned TS_E_NOLAYOUT again, we need another call of
6280 // OnLayoutChange() later. So, let's wait a query from TIP.
6281 if (mHasReturnedNoLayoutError) {
6282 mWaitingQueryLayout = true;
6285 if (!mWaitingQueryLayout) {
6286 MOZ_LOG(gIMELog, LogLevel::Info,
6287 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6288 "succeeded notifying TIP of our layout change",
6289 this));
6290 return ret;
6293 // If we believe that TIP needs to retry to retrieve our layout information
6294 // later, we should call it with ::PostMessage() hack.
6295 MOZ_LOG(gIMELog, LogLevel::Debug,
6296 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6297 "posing MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE for calling "
6298 "OnLayoutChange() again...",
6299 this));
6300 ::PostMessage(mWidget->GetWindowHandle(), MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE,
6301 reinterpret_cast<WPARAM>(this), 0);
6303 return true;
6306 void TSFTextStore::NotifyTSFOfLayoutChangeAgain() {
6307 // Don't notify TSF of layout change after destroyed.
6308 if (mDestroyed) {
6309 mWaitingQueryLayout = false;
6310 return;
6313 // Before preforming this method, TIP has accessed our layout information by
6314 // itself. In such case, we don't need to call OnLayoutChange() anymore.
6315 if (!mWaitingQueryLayout) {
6316 return;
6319 MOZ_LOG(gIMELog, LogLevel::Info,
6320 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6321 "calling NotifyTSFOfLayoutChange()...",
6322 this));
6323 NotifyTSFOfLayoutChange();
6325 // If TIP didn't retrieved our layout information during a call of
6326 // NotifyTSFOfLayoutChange(), it means that the TIP already gave up to
6327 // retry to retrieve layout information or doesn't necessary it anymore.
6328 // But don't forget that the call may have caused returning TS_E_NOLAYOUT
6329 // error again. In such case we still need to call OnLayoutChange() later.
6330 if (!mHasReturnedNoLayoutError && mWaitingQueryLayout) {
6331 mWaitingQueryLayout = false;
6332 MOZ_LOG(gIMELog, LogLevel::Warning,
6333 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6334 "called NotifyTSFOfLayoutChange() but TIP didn't retry to "
6335 "retrieve the layout information",
6336 this));
6337 } else {
6338 MOZ_LOG(gIMELog, LogLevel::Info,
6339 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6340 "called NotifyTSFOfLayoutChange()",
6341 this));
6345 nsresult TSFTextStore::OnUpdateCompositionInternal() {
6346 MOZ_LOG(gIMELog, LogLevel::Debug,
6347 ("0x%p TSFTextStore::OnUpdateCompositionInternal(), "
6348 "mDestroyed=%s, mDeferNotifyingTSFUntilNextUpdate=%s",
6349 this, GetBoolName(mDestroyed),
6350 GetBoolName(mDeferNotifyingTSFUntilNextUpdate)));
6352 // There are nothing to do after destroyed.
6353 if (mDestroyed) {
6354 return NS_OK;
6357 // Update cached data now because all pending events have been handled now.
6358 if (mContentForTSF.isSome()) {
6359 mContentForTSF->OnCompositionEventsHandled();
6362 // If composition is completely finished both in TSF/TIP and the focused
6363 // editor which may be in a remote process, we can clear the cache and don't
6364 // have it until starting next composition.
6365 if (mComposition.isNothing() && !IsHandlingCompositionInContent()) {
6366 mDeferClearingContentForTSF = false;
6368 mDeferNotifyingTSFUntilNextUpdate = false;
6369 MaybeFlushPendingNotifications();
6371 // If we're available, we should create native caret instead of IMEHandler
6372 // because we may have some cache to do it.
6373 if (IMEHandler::NeedsToCreateNativeCaret()) {
6374 CreateNativeCaret();
6377 return NS_OK;
6380 nsresult TSFTextStore::OnMouseButtonEventInternal(
6381 const IMENotification& aIMENotification) {
6382 if (mDestroyed) {
6383 // If this instance is already destroyed, we shouldn't notify TSF of any
6384 // events.
6385 return NS_OK;
6388 if (mMouseTrackers.IsEmpty()) {
6389 return NS_OK;
6392 MOZ_LOG(gIMELog, LogLevel::Debug,
6393 ("0x%p TSFTextStore::OnMouseButtonEventInternal("
6394 "aIMENotification={ mEventMessage=%s, mOffset=%u, mCursorPos=%s, "
6395 "mCharRect=%s, mButton=%s, mButtons=%s, mModifiers=%s })",
6396 this, ToChar(aIMENotification.mMouseButtonEventData.mEventMessage),
6397 aIMENotification.mMouseButtonEventData.mOffset,
6398 ToString(aIMENotification.mMouseButtonEventData.mCursorPos).c_str(),
6399 ToString(aIMENotification.mMouseButtonEventData.mCharRect).c_str(),
6400 GetMouseButtonName(aIMENotification.mMouseButtonEventData.mButton),
6401 GetMouseButtonsName(aIMENotification.mMouseButtonEventData.mButtons)
6402 .get(),
6403 GetModifiersName(aIMENotification.mMouseButtonEventData.mModifiers)
6404 .get()));
6406 uint32_t offset = aIMENotification.mMouseButtonEventData.mOffset;
6407 if (offset > static_cast<uint32_t>(LONG_MAX)) {
6408 return NS_OK;
6410 LayoutDeviceIntRect charRect =
6411 aIMENotification.mMouseButtonEventData.mCharRect;
6412 LayoutDeviceIntPoint cursorPos =
6413 aIMENotification.mMouseButtonEventData.mCursorPos;
6414 ULONG quadrant = 1;
6415 if (charRect.Width() > 0) {
6416 int32_t cursorXInChar = cursorPos.x - charRect.X();
6417 quadrant = cursorXInChar * 4 / charRect.Width();
6418 quadrant = (quadrant + 2) % 4;
6420 ULONG edge = quadrant < 2 ? offset + 1 : offset;
6421 DWORD buttonStatus = 0;
6422 bool isMouseUp =
6423 aIMENotification.mMouseButtonEventData.mEventMessage == eMouseUp;
6424 if (!isMouseUp) {
6425 switch (aIMENotification.mMouseButtonEventData.mButton) {
6426 case MouseButton::ePrimary:
6427 buttonStatus = MK_LBUTTON;
6428 break;
6429 case MouseButton::eMiddle:
6430 buttonStatus = MK_MBUTTON;
6431 break;
6432 case MouseButton::eSecondary:
6433 buttonStatus = MK_RBUTTON;
6434 break;
6437 if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_CONTROL) {
6438 buttonStatus |= MK_CONTROL;
6440 if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_SHIFT) {
6441 buttonStatus |= MK_SHIFT;
6443 for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
6444 MouseTracker& tracker = mMouseTrackers[i];
6445 if (!tracker.IsUsing() || tracker.Range().isNothing() ||
6446 !tracker.Range()->IsOffsetInRange(offset)) {
6447 continue;
6449 if (tracker.OnMouseButtonEvent(edge - tracker.Range()->StartOffset(),
6450 quadrant, buttonStatus)) {
6451 return NS_SUCCESS_EVENT_CONSUMED;
6454 return NS_OK;
6457 void TSFTextStore::CreateNativeCaret() {
6458 MOZ_ASSERT(!IMEHandler::IsA11yHandlingNativeCaret());
6460 IMEHandler::MaybeDestroyNativeCaret();
6462 // Don't create native caret after destroyed.
6463 if (mDestroyed) {
6464 return;
6467 MOZ_LOG(gIMELog, LogLevel::Debug,
6468 ("0x%p TSFTextStore::CreateNativeCaret(), mComposition=%s", this,
6469 ToString(mComposition).c_str()));
6471 Maybe<Selection>& selectionForTSF = SelectionForTSF();
6472 if (MOZ_UNLIKELY(selectionForTSF.isNothing())) {
6473 MOZ_LOG(gIMELog, LogLevel::Error,
6474 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6475 "SelectionForTSF() failure",
6476 this));
6477 return;
6479 if (!selectionForTSF->HasRange() && mComposition.isNothing()) {
6480 // If there is no selection range nor composition, then, we don't have a
6481 // good position to show windows of TIP...
6482 // XXX It seems that storing last caret rect and using it in this case might
6483 // be better?
6484 MOZ_LOG(gIMELog, LogLevel::Warning,
6485 ("0x%p TSFTextStore::CreateNativeCaret() couludn't create native "
6486 "caret due to no selection range",
6487 this));
6488 return;
6491 WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, mWidget);
6492 mWidget->InitEvent(queryCaretRectEvent);
6494 WidgetQueryContentEvent::Options options;
6495 // XXX If this is called without composition and the selection isn't
6496 // collapsed, is it OK?
6497 int64_t caretOffset = selectionForTSF->HasRange()
6498 ? selectionForTSF->MaxOffset()
6499 : mComposition->StartOffset();
6500 if (mComposition.isSome()) {
6501 // If there is a composition, use the relative query for deciding caret
6502 // position because composition might be different place from that
6503 // TSFTextStore assumes.
6504 options.mRelativeToInsertionPoint = true;
6505 caretOffset -= mComposition->StartOffset();
6506 } else if (!CanAccessActualContentDirectly()) {
6507 // If TSF/TIP cannot access actual content directly, there may be pending
6508 // text and/or selection changes which have not been notified TSF yet.
6509 // Therefore, we should use the relative query from start of selection where
6510 // TSFTextStore assumes since TSF/TIP computes the offset from our cached
6511 // selection.
6512 options.mRelativeToInsertionPoint = true;
6513 caretOffset -= selectionForTSF->StartOffset();
6515 queryCaretRectEvent.InitForQueryCaretRect(caretOffset, options);
6517 DispatchEvent(queryCaretRectEvent);
6518 if (NS_WARN_IF(queryCaretRectEvent.Failed())) {
6519 MOZ_LOG(gIMELog, LogLevel::Error,
6520 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6521 "eQueryCaretRect failure (offset=%lld)",
6522 this, caretOffset));
6523 return;
6526 if (!IMEHandler::CreateNativeCaret(static_cast<nsWindow*>(mWidget.get()),
6527 queryCaretRectEvent.mReply->mRect)) {
6528 MOZ_LOG(gIMELog, LogLevel::Error,
6529 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6530 "IMEHandler::CreateNativeCaret() failure",
6531 this));
6532 return;
6536 void TSFTextStore::CommitCompositionInternal(bool aDiscard) {
6537 MOZ_LOG(gIMELog, LogLevel::Debug,
6538 ("0x%p TSFTextStore::CommitCompositionInternal(aDiscard=%s), "
6539 "mSink=0x%p, mContext=0x%p, mComposition=%s",
6540 this, GetBoolName(aDiscard), mSink.get(), mContext.get(),
6541 ToString(mComposition).c_str()));
6543 // If the document is locked, TSF will fail to commit composition since
6544 // TSF needs another document lock. So, let's put off the request.
6545 // Note that TextComposition will commit composition in the focused editor
6546 // with the latest composition string for web apps and waits asynchronous
6547 // committing messages. Therefore, we can and need to perform this
6548 // asynchronously.
6549 if (IsReadLocked()) {
6550 if (mDeferCommittingComposition || mDeferCancellingComposition) {
6551 MOZ_LOG(gIMELog, LogLevel::Debug,
6552 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6553 "does nothing because already called and waiting unlock...",
6554 this));
6555 return;
6557 if (aDiscard) {
6558 mDeferCancellingComposition = true;
6559 } else {
6560 mDeferCommittingComposition = true;
6562 MOZ_LOG(gIMELog, LogLevel::Debug,
6563 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6564 "putting off to request to %s composition after unlocking the "
6565 "document",
6566 this, aDiscard ? "cancel" : "commit"));
6567 return;
6570 if (mComposition.isSome() && aDiscard) {
6571 LONG endOffset = mComposition->EndOffset();
6572 mComposition->SetData(EmptyString());
6573 // Note that don't notify TSF of text change after this is destroyed.
6574 if (mSink && !mDestroyed) {
6575 TS_TEXTCHANGE textChange;
6576 textChange.acpStart = mComposition->StartOffset();
6577 textChange.acpOldEnd = endOffset;
6578 textChange.acpNewEnd = mComposition->StartOffset();
6579 MOZ_LOG(gIMELog, LogLevel::Info,
6580 ("0x%p TSFTextStore::CommitCompositionInternal(), calling"
6581 "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
6582 "acpNewEnd=%ld })...",
6583 this, textChange.acpStart, textChange.acpOldEnd,
6584 textChange.acpNewEnd));
6585 RefPtr<ITextStoreACPSink> sink = mSink;
6586 sink->OnTextChange(0, &textChange);
6589 // Terminate two contexts, the base context (mContext) and the top
6590 // if the top context is not the same as the base context
6591 RefPtr<ITfContext> context = mContext;
6592 do {
6593 if (context) {
6594 RefPtr<ITfContextOwnerCompositionServices> services;
6595 context->QueryInterface(IID_ITfContextOwnerCompositionServices,
6596 getter_AddRefs(services));
6597 if (services) {
6598 MOZ_LOG(gIMELog, LogLevel::Debug,
6599 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6600 "requesting TerminateComposition() for the context 0x%p...",
6601 this, context.get()));
6602 services->TerminateComposition(nullptr);
6605 if (context != mContext) break;
6606 if (mDocumentMgr) mDocumentMgr->GetTop(getter_AddRefs(context));
6607 } while (context != mContext);
6610 static bool GetCompartment(IUnknown* pUnk, const GUID& aID,
6611 ITfCompartment** aCompartment) {
6612 if (!pUnk) return false;
6614 RefPtr<ITfCompartmentMgr> compMgr;
6615 pUnk->QueryInterface(IID_ITfCompartmentMgr, getter_AddRefs(compMgr));
6616 if (!compMgr) return false;
6618 return SUCCEEDED(compMgr->GetCompartment(aID, aCompartment)) &&
6619 (*aCompartment) != nullptr;
6622 // static
6623 void TSFTextStore::SetIMEOpenState(bool aState) {
6624 MOZ_LOG(gIMELog, LogLevel::Debug,
6625 ("TSFTextStore::SetIMEOpenState(aState=%s)", GetBoolName(aState)));
6627 if (!sThreadMgr) {
6628 return;
6631 RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
6632 if (NS_WARN_IF(!comp)) {
6633 MOZ_LOG(gIMELog, LogLevel::Debug,
6634 (" TSFTextStore::SetIMEOpenState() FAILED due to"
6635 "no compartment available"));
6636 return;
6639 VARIANT variant;
6640 variant.vt = VT_I4;
6641 variant.lVal = aState;
6642 HRESULT hr = comp->SetValue(sClientId, &variant);
6643 if (NS_WARN_IF(FAILED(hr))) {
6644 MOZ_LOG(gIMELog, LogLevel::Error,
6645 (" TSFTextStore::SetIMEOpenState() FAILED due to "
6646 "ITfCompartment::SetValue() failure, hr=0x%08lX",
6647 hr));
6648 return;
6650 MOZ_LOG(gIMELog, LogLevel::Debug,
6651 (" TSFTextStore::SetIMEOpenState(), setting "
6652 "0x%04lX to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...",
6653 variant.lVal));
6656 // static
6657 bool TSFTextStore::GetIMEOpenState() {
6658 if (!sThreadMgr) {
6659 return false;
6662 RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
6663 if (NS_WARN_IF(!comp)) {
6664 return false;
6667 VARIANT variant;
6668 ::VariantInit(&variant);
6669 HRESULT hr = comp->GetValue(&variant);
6670 if (NS_WARN_IF(FAILED(hr))) {
6671 MOZ_LOG(gIMELog, LogLevel::Error,
6672 ("TSFTextStore::GetIMEOpenState() FAILED due to "
6673 "ITfCompartment::GetValue() failure, hr=0x%08lX",
6674 hr));
6675 return false;
6677 // Until IME is open in this process, the result may be empty.
6678 if (variant.vt == VT_EMPTY) {
6679 return false;
6681 if (NS_WARN_IF(variant.vt != VT_I4)) {
6682 MOZ_LOG(gIMELog, LogLevel::Error,
6683 ("TSFTextStore::GetIMEOpenState() FAILED due to "
6684 "invalid result of ITfCompartment::GetValue()"));
6685 ::VariantClear(&variant);
6686 return false;
6689 return variant.lVal != 0;
6692 // static
6693 void TSFTextStore::SetInputContext(nsWindow* aWidget,
6694 const InputContext& aContext,
6695 const InputContextAction& aAction) {
6696 MOZ_LOG(gIMELog, LogLevel::Debug,
6697 ("TSFTextStore::SetInputContext(aWidget=%p, "
6698 "aContext=%s, aAction.mFocusChange=%s), "
6699 "sEnabledTextStore(0x%p)={ mWidget=0x%p }, ThinksHavingFocus()=%s",
6700 aWidget, mozilla::ToString(aContext).c_str(),
6701 GetFocusChangeName(aAction.mFocusChange), sEnabledTextStore.get(),
6702 sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr,
6703 GetBoolName(ThinksHavingFocus())));
6705 switch (aAction.mFocusChange) {
6706 case InputContextAction::WIDGET_CREATED:
6707 // If this is called when the widget is created, there is nothing to do.
6708 return;
6709 case InputContextAction::FOCUS_NOT_CHANGED:
6710 case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
6711 if (NS_WARN_IF(!IsInTSFMode())) {
6712 return;
6714 // In these cases, `NOTIFY_IME_OF_FOCUS` won't be sent. Therefore,
6715 // we need to reset text store for new state right now.
6716 break;
6717 default:
6718 NS_WARNING_ASSERTION(IsInTSFMode(),
6719 "Why is this called when TSF is disabled?");
6720 if (sEnabledTextStore) {
6721 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
6722 textStore->mInPrivateBrowsing = aContext.mInPrivateBrowsing;
6723 textStore->SetInputScope(aContext.mHTMLInputType,
6724 aContext.mHTMLInputMode);
6725 if (aContext.mURI) {
6726 nsAutoCString spec;
6727 if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) {
6728 CopyUTF8toUTF16(spec, textStore->mDocumentURL);
6729 } else {
6730 textStore->mDocumentURL.Truncate();
6732 } else {
6733 textStore->mDocumentURL.Truncate();
6736 return;
6739 // If focus isn't actually changed but the enabled state is changed,
6740 // emulate the focus move.
6741 if (!ThinksHavingFocus() && aContext.mIMEState.IsEditable()) {
6742 if (!IMEHandler::GetFocusedWindow()) {
6743 MOZ_LOG(gIMELog, LogLevel::Error,
6744 (" TSFTextStore::SetInputContent() gets called to enable IME, "
6745 "but IMEHandler has not received focus notification"));
6746 } else {
6747 MOZ_LOG(gIMELog, LogLevel::Debug,
6748 (" TSFTextStore::SetInputContent() emulates focus for IME "
6749 "state change"));
6750 OnFocusChange(true, aWidget, aContext);
6752 } else if (ThinksHavingFocus() && !aContext.mIMEState.IsEditable()) {
6753 MOZ_LOG(gIMELog, LogLevel::Debug,
6754 (" TSFTextStore::SetInputContent() emulates blur for IME "
6755 "state change"));
6756 OnFocusChange(false, aWidget, aContext);
6760 // static
6761 void TSFTextStore::MarkContextAsKeyboardDisabled(ITfContext* aContext) {
6762 VARIANT variant_int4_value1;
6763 variant_int4_value1.vt = VT_I4;
6764 variant_int4_value1.lVal = 1;
6766 RefPtr<ITfCompartment> comp;
6767 if (!GetCompartment(aContext, GUID_COMPARTMENT_KEYBOARD_DISABLED,
6768 getter_AddRefs(comp))) {
6769 MOZ_LOG(gIMELog, LogLevel::Error,
6770 ("TSFTextStore::MarkContextAsKeyboardDisabled() failed"
6771 "aContext=0x%p...",
6772 aContext));
6773 return;
6776 MOZ_LOG(gIMELog, LogLevel::Debug,
6777 ("TSFTextStore::MarkContextAsKeyboardDisabled(), setting "
6778 "to disable context 0x%p...",
6779 aContext));
6780 comp->SetValue(sClientId, &variant_int4_value1);
6783 // static
6784 void TSFTextStore::MarkContextAsEmpty(ITfContext* aContext) {
6785 VARIANT variant_int4_value1;
6786 variant_int4_value1.vt = VT_I4;
6787 variant_int4_value1.lVal = 1;
6789 RefPtr<ITfCompartment> comp;
6790 if (!GetCompartment(aContext, GUID_COMPARTMENT_EMPTYCONTEXT,
6791 getter_AddRefs(comp))) {
6792 MOZ_LOG(gIMELog, LogLevel::Error,
6793 ("TSFTextStore::MarkContextAsEmpty() failed"
6794 "aContext=0x%p...",
6795 aContext));
6796 return;
6799 MOZ_LOG(gIMELog, LogLevel::Debug,
6800 ("TSFTextStore::MarkContextAsEmpty(), setting "
6801 "to mark empty context 0x%p...",
6802 aContext));
6803 comp->SetValue(sClientId, &variant_int4_value1);
6806 // static
6807 void TSFTextStore::Initialize() {
6808 MOZ_LOG(gIMELog, LogLevel::Info, ("TSFTextStore::Initialize() is called..."));
6810 if (sThreadMgr) {
6811 MOZ_LOG(gIMELog, LogLevel::Error,
6812 (" TSFTextStore::Initialize() FAILED due to already initialized"));
6813 return;
6816 const bool enableTsf = StaticPrefs::intl_tsf_enabled_AtStartup();
6817 MOZ_LOG(gIMELog, LogLevel::Info,
6818 (" TSFTextStore::Initialize(), TSF is %s",
6819 enableTsf ? "enabled" : "disabled"));
6820 if (!enableTsf) {
6821 return;
6824 RefPtr<ITfThreadMgr> threadMgr;
6825 HRESULT hr =
6826 ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_INPROC_SERVER,
6827 IID_ITfThreadMgr, getter_AddRefs(threadMgr));
6828 if (FAILED(hr) || !threadMgr) {
6829 MOZ_LOG(gIMELog, LogLevel::Error,
6830 (" TSFTextStore::Initialize() FAILED to "
6831 "create the thread manager, hr=0x%08lX",
6832 hr));
6833 return;
6836 hr = threadMgr->Activate(&sClientId);
6837 if (FAILED(hr)) {
6838 MOZ_LOG(
6839 gIMELog, LogLevel::Error,
6840 (" TSFTextStore::Initialize() FAILED to activate, hr=0x%08lX", hr));
6841 return;
6844 RefPtr<ITfDocumentMgr> disabledDocumentMgr;
6845 hr = threadMgr->CreateDocumentMgr(getter_AddRefs(disabledDocumentMgr));
6846 if (FAILED(hr) || !disabledDocumentMgr) {
6847 MOZ_LOG(gIMELog, LogLevel::Error,
6848 (" TSFTextStore::Initialize() FAILED to create "
6849 "a document manager for disabled mode, hr=0x%08lX",
6850 hr));
6851 return;
6854 RefPtr<ITfContext> disabledContext;
6855 DWORD editCookie = 0;
6856 hr = disabledDocumentMgr->CreateContext(
6857 sClientId, 0, nullptr, getter_AddRefs(disabledContext), &editCookie);
6858 if (FAILED(hr) || !disabledContext) {
6859 MOZ_LOG(gIMELog, LogLevel::Error,
6860 (" TSFTextStore::Initialize() FAILED to create "
6861 "a context for disabled mode, hr=0x%08lX",
6862 hr));
6863 return;
6866 MarkContextAsKeyboardDisabled(disabledContext);
6867 MarkContextAsEmpty(disabledContext);
6869 sThreadMgr = threadMgr;
6870 sDisabledDocumentMgr = disabledDocumentMgr;
6871 sDisabledContext = disabledContext;
6873 MOZ_LOG(gIMELog, LogLevel::Info,
6874 (" TSFTextStore::Initialize(), sThreadMgr=0x%p, "
6875 "sClientId=0x%08lX, sDisabledDocumentMgr=0x%p, sDisabledContext=%p",
6876 sThreadMgr.get(), sClientId, sDisabledDocumentMgr.get(),
6877 sDisabledContext.get()));
6880 // static
6881 already_AddRefed<ITfThreadMgr> TSFTextStore::GetThreadMgr() {
6882 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
6883 return threadMgr.forget();
6886 // static
6887 already_AddRefed<ITfMessagePump> TSFTextStore::GetMessagePump() {
6888 static bool sInitialized = false;
6889 if (!sThreadMgr) {
6890 return nullptr;
6892 if (sMessagePump) {
6893 RefPtr<ITfMessagePump> messagePump = sMessagePump;
6894 return messagePump.forget();
6896 // If it tried to retrieve ITfMessagePump from sThreadMgr but it failed,
6897 // we shouldn't retry it at every message due to performance reason.
6898 // Although this shouldn't occur actually.
6899 if (sInitialized) {
6900 return nullptr;
6902 sInitialized = true;
6904 RefPtr<ITfMessagePump> messagePump;
6905 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfMessagePump,
6906 getter_AddRefs(messagePump));
6907 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!messagePump)) {
6908 MOZ_LOG(gIMELog, LogLevel::Error,
6909 ("TSFTextStore::GetMessagePump() FAILED to "
6910 "QI message pump from the thread manager, hr=0x%08lX",
6911 hr));
6912 return nullptr;
6914 sMessagePump = messagePump;
6915 return messagePump.forget();
6918 // static
6919 already_AddRefed<ITfDisplayAttributeMgr>
6920 TSFTextStore::GetDisplayAttributeMgr() {
6921 RefPtr<ITfDisplayAttributeMgr> displayAttributeMgr;
6922 if (sDisplayAttrMgr) {
6923 displayAttributeMgr = sDisplayAttrMgr;
6924 return displayAttributeMgr.forget();
6927 HRESULT hr = ::CoCreateInstance(
6928 CLSID_TF_DisplayAttributeMgr, nullptr, CLSCTX_INPROC_SERVER,
6929 IID_ITfDisplayAttributeMgr, getter_AddRefs(displayAttributeMgr));
6930 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!displayAttributeMgr)) {
6931 MOZ_LOG(gIMELog, LogLevel::Error,
6932 ("TSFTextStore::GetDisplayAttributeMgr() FAILED to create "
6933 "a display attribute manager instance, hr=0x%08lX",
6934 hr));
6935 return nullptr;
6937 sDisplayAttrMgr = displayAttributeMgr;
6938 return displayAttributeMgr.forget();
6941 // static
6942 already_AddRefed<ITfCategoryMgr> TSFTextStore::GetCategoryMgr() {
6943 RefPtr<ITfCategoryMgr> categoryMgr;
6944 if (sCategoryMgr) {
6945 categoryMgr = sCategoryMgr;
6946 return categoryMgr.forget();
6948 HRESULT hr =
6949 ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, CLSCTX_INPROC_SERVER,
6950 IID_ITfCategoryMgr, getter_AddRefs(categoryMgr));
6951 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!categoryMgr)) {
6952 MOZ_LOG(gIMELog, LogLevel::Error,
6953 ("TSFTextStore::GetCategoryMgr() FAILED to create "
6954 "a category manager instance, hr=0x%08lX",
6955 hr));
6956 return nullptr;
6958 sCategoryMgr = categoryMgr;
6959 return categoryMgr.forget();
6962 // static
6963 already_AddRefed<ITfCompartment> TSFTextStore::GetCompartmentForOpenClose() {
6964 if (sCompartmentForOpenClose) {
6965 RefPtr<ITfCompartment> compartment = sCompartmentForOpenClose;
6966 return compartment.forget();
6969 if (!sThreadMgr) {
6970 return nullptr;
6973 RefPtr<ITfCompartmentMgr> compartmentMgr;
6974 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfCompartmentMgr,
6975 getter_AddRefs(compartmentMgr));
6976 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartmentMgr)) {
6977 MOZ_LOG(gIMELog, LogLevel::Error,
6978 ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
6979 "sThreadMgr not having ITfCompartmentMgr, hr=0x%08lX",
6980 hr));
6981 return nullptr;
6984 RefPtr<ITfCompartment> compartment;
6985 hr = compartmentMgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
6986 getter_AddRefs(compartment));
6987 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartment)) {
6988 MOZ_LOG(gIMELog, LogLevel::Error,
6989 ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
6990 "ITfCompartmentMgr::GetCompartment() failuere, hr=0x%08lX",
6991 hr));
6992 return nullptr;
6995 sCompartmentForOpenClose = compartment;
6996 return compartment.forget();
6999 // static
7000 already_AddRefed<ITfInputProcessorProfiles>
7001 TSFTextStore::GetInputProcessorProfiles() {
7002 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles;
7003 if (sInputProcessorProfiles) {
7004 inputProcessorProfiles = sInputProcessorProfiles;
7005 return inputProcessorProfiles.forget();
7007 // XXX MSDN documents that ITfInputProcessorProfiles is available only on
7008 // desktop apps. However, there is no known way to obtain
7009 // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles
7010 // instance.
7011 HRESULT hr = ::CoCreateInstance(
7012 CLSID_TF_InputProcessorProfiles, nullptr, CLSCTX_INPROC_SERVER,
7013 IID_ITfInputProcessorProfiles, getter_AddRefs(inputProcessorProfiles));
7014 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!inputProcessorProfiles)) {
7015 MOZ_LOG(gIMELog, LogLevel::Error,
7016 ("TSFTextStore::GetInputProcessorProfiles() FAILED to create input "
7017 "processor profiles, hr=0x%08lX",
7018 hr));
7019 return nullptr;
7021 sInputProcessorProfiles = inputProcessorProfiles;
7022 return inputProcessorProfiles.forget();
7025 // static
7026 void TSFTextStore::Terminate() {
7027 MOZ_LOG(gIMELog, LogLevel::Info, ("TSFTextStore::Terminate()"));
7029 TSFStaticSink::Shutdown();
7031 sDisplayAttrMgr = nullptr;
7032 sCategoryMgr = nullptr;
7033 sEnabledTextStore = nullptr;
7034 sDisabledDocumentMgr = nullptr;
7035 sDisabledContext = nullptr;
7036 sCompartmentForOpenClose = nullptr;
7037 sInputProcessorProfiles = nullptr;
7038 sClientId = 0;
7039 if (sThreadMgr) {
7040 sThreadMgr->Deactivate();
7041 sThreadMgr = nullptr;
7042 sMessagePump = nullptr;
7043 sKeystrokeMgr = nullptr;
7047 // static
7048 bool TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg) {
7049 if (!sThreadMgr) {
7050 return false; // not in TSF mode
7052 static bool sInitialized = false;
7053 if (!sKeystrokeMgr) {
7054 // If it tried to retrieve ITfKeystrokeMgr from sThreadMgr but it failed,
7055 // we shouldn't retry it at every keydown nor keyup due to performance
7056 // reason. Although this shouldn't occur actually.
7057 if (sInitialized) {
7058 return false;
7060 sInitialized = true;
7061 RefPtr<ITfKeystrokeMgr> keystrokeMgr;
7062 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfKeystrokeMgr,
7063 getter_AddRefs(keystrokeMgr));
7064 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!keystrokeMgr)) {
7065 MOZ_LOG(gIMELog, LogLevel::Error,
7066 ("TSFTextStore::ProcessRawKeyMessage() FAILED to "
7067 "QI keystroke manager from the thread manager, hr=0x%08lX",
7068 hr));
7069 return false;
7071 sKeystrokeMgr = keystrokeMgr.forget();
7074 if (aMsg.message == WM_KEYDOWN) {
7075 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
7076 if (textStore) {
7077 textStore->OnStartToHandleKeyMessage();
7078 if (NS_WARN_IF(textStore != sEnabledTextStore)) {
7079 // Let's handle the key message with new focused TSFTextStore.
7080 textStore = sEnabledTextStore;
7083 AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
7084 AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
7085 sHandlingKeyMsg = &aMsg;
7086 sIsKeyboardEventDispatched = false;
7087 BOOL eaten;
7088 RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
7089 HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten);
7090 if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
7091 return false;
7093 hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
7094 if (textStore) {
7095 textStore->OnEndHandlingKeyMessage(!!eaten);
7097 return SUCCEEDED(hr) &&
7098 (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
7100 if (aMsg.message == WM_KEYUP) {
7101 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
7102 if (textStore) {
7103 textStore->OnStartToHandleKeyMessage();
7104 if (NS_WARN_IF(textStore != sEnabledTextStore)) {
7105 // Let's handle the key message with new focused TSFTextStore.
7106 textStore = sEnabledTextStore;
7109 AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
7110 AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
7111 sHandlingKeyMsg = &aMsg;
7112 sIsKeyboardEventDispatched = false;
7113 BOOL eaten;
7114 RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
7115 HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten);
7116 if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
7117 return false;
7119 hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten);
7120 if (textStore) {
7121 textStore->OnEndHandlingKeyMessage(!!eaten);
7123 return SUCCEEDED(hr) &&
7124 (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
7126 return false;
7129 // static
7130 void TSFTextStore::ProcessMessage(nsWindow* aWindow, UINT aMessage,
7131 WPARAM& aWParam, LPARAM& aLParam,
7132 MSGResult& aResult) {
7133 switch (aMessage) {
7134 case WM_IME_SETCONTEXT:
7135 // If a windowless plugin had focus and IME was handled on it, composition
7136 // window was set the position. After that, even in TSF mode, WinXP keeps
7137 // to use composition window at the position if the active IME is not
7138 // aware TSF. For avoiding this issue, we need to hide the composition
7139 // window here.
7140 if (aWParam) {
7141 aLParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
7143 break;
7144 case WM_ENTERIDLE:
7145 // When an modal dialog such as a file picker is open, composition
7146 // should be committed because IME might be used on it.
7147 if (!IsComposingOn(aWindow)) {
7148 break;
7150 CommitComposition(false);
7151 break;
7152 case MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE: {
7153 TSFTextStore* maybeTextStore = reinterpret_cast<TSFTextStore*>(aWParam);
7154 if (maybeTextStore == sEnabledTextStore) {
7155 RefPtr<TSFTextStore> textStore(maybeTextStore);
7156 textStore->NotifyTSFOfLayoutChangeAgain();
7158 break;
7163 // static
7164 bool TSFTextStore::IsIMM_IMEActive() {
7165 return TSFStaticSink::IsIMM_IMEActive();
7168 // static
7169 bool TSFTextStore::IsMSJapaneseIMEActive() {
7170 return TSFStaticSink::IsMSJapaneseIMEActive();
7173 // static
7174 bool TSFTextStore::IsGoogleJapaneseInputActive() {
7175 return TSFStaticSink::IsGoogleJapaneseInputActive();
7178 // static
7179 bool TSFTextStore::IsATOKActive() { return TSFStaticSink::IsATOKActive(); }
7181 /******************************************************************************
7182 * TSFTextStore::Content
7183 *****************************************************************************/
7185 const nsDependentSubstring TSFTextStore::Content::GetSelectedText() const {
7186 if (NS_WARN_IF(mSelection.isNothing())) {
7187 return nsDependentSubstring();
7189 return GetSubstring(static_cast<uint32_t>(mSelection->StartOffset()),
7190 static_cast<uint32_t>(mSelection->Length()));
7193 const nsDependentSubstring TSFTextStore::Content::GetSubstring(
7194 uint32_t aStart, uint32_t aLength) const {
7195 return nsDependentSubstring(mText, aStart, aLength);
7198 void TSFTextStore::Content::ReplaceSelectedTextWith(const nsAString& aString) {
7199 if (NS_WARN_IF(mSelection.isNothing())) {
7200 return;
7202 ReplaceTextWith(mSelection->StartOffset(), mSelection->Length(), aString);
7205 inline uint32_t FirstDifferentCharOffset(const nsAString& aStr1,
7206 const nsAString& aStr2) {
7207 MOZ_ASSERT(aStr1 != aStr2);
7208 uint32_t i = 0;
7209 uint32_t minLength = std::min(aStr1.Length(), aStr2.Length());
7210 for (; i < minLength && aStr1[i] == aStr2[i]; i++) {
7211 /* nothing to do */
7213 return i;
7216 void TSFTextStore::Content::ReplaceTextWith(LONG aStart, LONG aLength,
7217 const nsAString& aReplaceString) {
7218 MOZ_ASSERT(aStart >= 0);
7219 MOZ_ASSERT(aLength >= 0);
7220 const nsDependentSubstring replacedString = GetSubstring(
7221 static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength));
7222 if (aReplaceString != replacedString) {
7223 uint32_t firstDifferentOffset = mMinModifiedOffset.valueOr(UINT32_MAX);
7224 if (mComposition.isSome()) {
7225 // Emulate text insertion during compositions, because during a
7226 // composition, editor expects the whole composition string to
7227 // be sent in eCompositionChange, not just the inserted part.
7228 // The actual eCompositionChange will be sent in SetSelection
7229 // or OnUpdateComposition.
7230 MOZ_ASSERT(aStart >= mComposition->StartOffset());
7231 MOZ_ASSERT(aStart + aLength <= mComposition->EndOffset());
7232 mComposition->ReplaceData(
7233 static_cast<uint32_t>(aStart - mComposition->StartOffset()),
7234 static_cast<uint32_t>(aLength), aReplaceString);
7235 // TIP may set composition string twice or more times during a document
7236 // lock. Therefore, we should compute the first difference offset with
7237 // mLastComposition.
7238 if (mLastComposition.isNothing()) {
7239 firstDifferentOffset = mComposition->StartOffset();
7240 } else if (mComposition->DataRef() != mLastComposition->DataRef()) {
7241 firstDifferentOffset =
7242 mComposition->StartOffset() +
7243 FirstDifferentCharOffset(mComposition->DataRef(),
7244 mLastComposition->DataRef());
7245 // The previous change to the composition string is canceled.
7246 if (mMinModifiedOffset.isSome() &&
7247 mMinModifiedOffset.value() >=
7248 static_cast<uint32_t>(mComposition->StartOffset()) &&
7249 mMinModifiedOffset.value() < firstDifferentOffset) {
7250 mMinModifiedOffset = Some(firstDifferentOffset);
7252 } else if (mMinModifiedOffset.isSome() &&
7253 mMinModifiedOffset.value() < static_cast<uint32_t>(LONG_MAX) &&
7254 mComposition->IsOffsetInRange(
7255 static_cast<long>(mMinModifiedOffset.value()))) {
7256 // The previous change to the composition string is canceled.
7257 firstDifferentOffset = mComposition->EndOffset();
7258 mMinModifiedOffset = Some(firstDifferentOffset);
7260 mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
7261 MOZ_LOG(
7262 gIMELog, LogLevel::Debug,
7263 ("0x%p TSFTextStore::Content::ReplaceTextWith(aStart=%ld, "
7264 "aLength=%ld, aReplaceString=\"%s\"), mComposition=%s, "
7265 "mLastComposition=%s, mMinModifiedOffset=%s, "
7266 "firstDifferentOffset=%u",
7267 this, aStart, aLength, GetEscapedUTF8String(aReplaceString).get(),
7268 ToString(mComposition).c_str(), ToString(mLastComposition).c_str(),
7269 ToString(mMinModifiedOffset).c_str(), firstDifferentOffset));
7270 } else {
7271 firstDifferentOffset =
7272 static_cast<uint32_t>(aStart) +
7273 FirstDifferentCharOffset(aReplaceString, replacedString);
7275 mMinModifiedOffset =
7276 mMinModifiedOffset.isNothing()
7277 ? Some(firstDifferentOffset)
7278 : Some(std::min(mMinModifiedOffset.value(), firstDifferentOffset));
7279 mText.Replace(static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength),
7280 aReplaceString);
7282 // Selection should be collapsed at the end of the inserted string.
7283 mSelection = Some(TSFTextStore::Selection(static_cast<uint32_t>(aStart) +
7284 aReplaceString.Length()));
7287 void TSFTextStore::Content::StartComposition(
7288 ITfCompositionView* aCompositionView, const PendingAction& aCompStart,
7289 bool aPreserveSelection) {
7290 MOZ_ASSERT(aCompositionView);
7291 MOZ_ASSERT(mComposition.isNothing());
7292 MOZ_ASSERT(aCompStart.mType == PendingAction::Type::eCompositionStart);
7294 mComposition.reset(); // Avoid new crash in the beta and nightly channels.
7295 mComposition.emplace(
7296 aCompositionView, aCompStart.mSelectionStart,
7297 GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart),
7298 static_cast<uint32_t>(aCompStart.mSelectionLength)));
7299 mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
7300 if (!aPreserveSelection) {
7301 // XXX Do we need to set a new writing-mode here when setting a new
7302 // selection? Currently, we just preserve the existing value.
7303 WritingMode writingMode =
7304 mSelection.isNothing() ? WritingMode() : mSelection->WritingModeRef();
7305 mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset(),
7306 mComposition->Length(), false,
7307 writingMode));
7311 void TSFTextStore::Content::RestoreCommittedComposition(
7312 ITfCompositionView* aCompositionView,
7313 const PendingAction& aCanceledCompositionEnd) {
7314 MOZ_ASSERT(aCompositionView);
7315 MOZ_ASSERT(mComposition.isNothing());
7316 MOZ_ASSERT(aCanceledCompositionEnd.mType ==
7317 PendingAction::Type::eCompositionEnd);
7318 MOZ_ASSERT(
7319 GetSubstring(
7320 static_cast<uint32_t>(aCanceledCompositionEnd.mSelectionStart),
7321 static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
7322 aCanceledCompositionEnd.mData);
7324 // Restore the committed string as composing string.
7325 mComposition.reset(); // Avoid new crash in the beta and nightly channels.
7326 mComposition.emplace(aCompositionView,
7327 aCanceledCompositionEnd.mSelectionStart,
7328 aCanceledCompositionEnd.mData);
7329 mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
7332 void TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd) {
7333 MOZ_ASSERT(mComposition.isSome());
7334 MOZ_ASSERT(aCompEnd.mType == PendingAction::Type::eCompositionEnd);
7336 if (mComposition.isNothing()) {
7337 return; // Avoid new crash in the beta and nightly channels.
7340 mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset() +
7341 aCompEnd.mData.Length()));
7342 mComposition.reset();
7345 /******************************************************************************
7346 * TSFTextStore::MouseTracker
7347 *****************************************************************************/
7349 TSFTextStore::MouseTracker::MouseTracker() : mCookie(kInvalidCookie) {}
7351 HRESULT
7352 TSFTextStore::MouseTracker::Init(TSFTextStore* aTextStore) {
7353 MOZ_LOG(gIMELog, LogLevel::Debug,
7354 ("0x%p TSFTextStore::MouseTracker::Init(aTextStore=0x%p), "
7355 "aTextStore->mMouseTrackers.Length()=%zu",
7356 this, aTextStore, aTextStore->mMouseTrackers.Length()));
7358 if (&aTextStore->mMouseTrackers.LastElement() != this) {
7359 MOZ_LOG(gIMELog, LogLevel::Error,
7360 ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
7361 "this is not the last element of mMouseTrackers",
7362 this));
7363 return E_FAIL;
7365 if (aTextStore->mMouseTrackers.Length() > kInvalidCookie) {
7366 MOZ_LOG(gIMELog, LogLevel::Error,
7367 ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
7368 "no new cookie available",
7369 this));
7370 return E_FAIL;
7372 MOZ_ASSERT(!aTextStore->mMouseTrackers.IsEmpty(),
7373 "This instance must be in TSFTextStore::mMouseTrackers");
7374 mCookie = static_cast<DWORD>(aTextStore->mMouseTrackers.Length() - 1);
7375 return S_OK;
7378 HRESULT
7379 TSFTextStore::MouseTracker::AdviseSink(TSFTextStore* aTextStore,
7380 ITfRangeACP* aTextRange,
7381 ITfMouseSink* aMouseSink) {
7382 MOZ_LOG(gIMELog, LogLevel::Debug,
7383 ("0x%p TSFTextStore::MouseTracker::AdviseSink(aTextStore=0x%p, "
7384 "aTextRange=0x%p, aMouseSink=0x%p), mCookie=%ld, mSink=0x%p",
7385 this, aTextStore, aTextRange, aMouseSink, mCookie, mSink.get()));
7386 MOZ_ASSERT(mCookie != kInvalidCookie, "This hasn't been initalized?");
7388 if (mSink) {
7389 MOZ_LOG(gIMELog, LogLevel::Error,
7390 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7391 "due to already being used",
7392 this));
7393 return E_FAIL;
7396 MOZ_ASSERT(mRange.isNothing());
7398 LONG start = 0, length = 0;
7399 HRESULT hr = aTextRange->GetExtent(&start, &length);
7400 if (FAILED(hr)) {
7401 MOZ_LOG(gIMELog, LogLevel::Error,
7402 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7403 "due to failure of ITfRangeACP::GetExtent()",
7404 this));
7405 return hr;
7408 if (start < 0 || length <= 0 || start + length > LONG_MAX) {
7409 MOZ_LOG(gIMELog, LogLevel::Error,
7410 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7411 "due to odd result of ITfRangeACP::GetExtent(), "
7412 "start=%ld, length=%ld",
7413 this, start, length));
7414 return E_INVALIDARG;
7417 nsAutoString textContent;
7418 if (NS_WARN_IF(!aTextStore->GetCurrentText(textContent))) {
7419 MOZ_LOG(gIMELog, LogLevel::Error,
7420 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7421 "due to failure of TSFTextStore::GetCurrentText()",
7422 this));
7423 return E_FAIL;
7426 if (textContent.Length() <= static_cast<uint32_t>(start) ||
7427 textContent.Length() < static_cast<uint32_t>(start + length)) {
7428 MOZ_LOG(gIMELog, LogLevel::Error,
7429 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7430 "due to out of range, start=%ld, length=%ld, "
7431 "textContent.Length()=%zu",
7432 this, start, length, textContent.Length()));
7433 return E_INVALIDARG;
7436 mRange.emplace(start, start + length);
7438 mSink = aMouseSink;
7440 MOZ_LOG(gIMELog, LogLevel::Debug,
7441 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink(), "
7442 "succeeded, mRange=%s, textContent.Length()=%zu",
7443 this, ToString(mRange).c_str(), textContent.Length()));
7444 return S_OK;
7447 void TSFTextStore::MouseTracker::UnadviseSink() {
7448 MOZ_LOG(gIMELog, LogLevel::Debug,
7449 ("0x%p TSFTextStore::MouseTracker::UnadviseSink(), "
7450 "mCookie=%ld, mSink=0x%p, mRange=%s",
7451 this, mCookie, mSink.get(), ToString(mRange).c_str()));
7452 mSink = nullptr;
7453 mRange.reset();
7456 bool TSFTextStore::MouseTracker::OnMouseButtonEvent(ULONG aEdge,
7457 ULONG aQuadrant,
7458 DWORD aButtonStatus) {
7459 MOZ_ASSERT(IsUsing(), "The caller must check before calling OnMouseEvent()");
7461 BOOL eaten = FALSE;
7462 RefPtr<ITfMouseSink> sink = mSink;
7463 HRESULT hr = sink->OnMouseEvent(aEdge, aQuadrant, aButtonStatus, &eaten);
7465 MOZ_LOG(gIMELog, LogLevel::Debug,
7466 ("0x%p TSFTextStore::MouseTracker::OnMouseEvent(aEdge=%ld, "
7467 "aQuadrant=%ld, aButtonStatus=0x%08lX), hr=0x%08lX, eaten=%s",
7468 this, aEdge, aQuadrant, aButtonStatus, hr, GetBoolName(!!eaten)));
7470 return SUCCEEDED(hr) && eaten;
7473 #ifdef DEBUG
7474 // static
7475 bool TSFTextStore::CurrentKeyboardLayoutHasIME() {
7476 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
7477 TSFTextStore::GetInputProcessorProfiles();
7478 if (!inputProcessorProfiles) {
7479 MOZ_LOG(gIMELog, LogLevel::Error,
7480 ("TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED due to "
7481 "there is no input processor profiles instance"));
7482 return false;
7484 RefPtr<ITfInputProcessorProfileMgr> profileMgr;
7485 HRESULT hr = inputProcessorProfiles->QueryInterface(
7486 IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
7487 if (FAILED(hr) || !profileMgr) {
7488 // On Windows Vista or later, ImmIsIME() API always returns true.
7489 // If we failed to obtain the profile manager, we cannot know if current
7490 // keyboard layout has IME.
7491 MOZ_LOG(gIMELog, LogLevel::Error,
7492 (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to query "
7493 "ITfInputProcessorProfileMgr"));
7494 return false;
7497 TF_INPUTPROCESSORPROFILE profile;
7498 hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
7499 if (hr == S_FALSE) {
7500 return false; // not found or not active
7502 if (FAILED(hr)) {
7503 MOZ_LOG(gIMELog, LogLevel::Error,
7504 (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to retreive "
7505 "active profile"));
7506 return false;
7508 return (profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR);
7510 #endif // #ifdef DEBUG
7512 } // namespace widget
7513 } // namespace mozilla