Bug 1794292 - [ANGLE] cherry-pick init-gl-point-size. r=gfx-reviewers,bradwerth
[gecko.git] / widget / windows / TSFTextStore.cpp
blob671b7c9d087f430d6c9e29a28cbed7f52b1672c3
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #define INPUTSCOPE_INIT_GUID
7 #define TEXTATTRS_INIT_GUID
8 #include "TSFTextStore.h"
10 #include <algorithm>
11 #include <comutil.h> // for _bstr_t
12 #include <oleauto.h> // for SysAllocString
13 #include <olectl.h>
14 #include "nscore.h"
16 #include "IMMHandler.h"
17 #include "KeyboardLayout.h"
18 #include "WinIMEHandler.h"
19 #include "WinUtils.h"
20 #include "mozilla/AutoRestore.h"
21 #include "mozilla/Logging.h"
22 #include "mozilla/Preferences.h"
23 #include "mozilla/StaticPrefs_intl.h"
24 #include "mozilla/Telemetry.h"
25 #include "mozilla/TextEventDispatcher.h"
26 #include "mozilla/TextEvents.h"
27 #include "mozilla/ToString.h"
28 #include "mozilla/WindowsVersion.h"
29 #include "nsWindow.h"
30 #include "nsPrintfCString.h"
32 // For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync`
33 // rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
34 // big file.
35 // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
36 mozilla::LazyLogModule gIMELog("IMEHandler");
38 // TODO: GUID_PROP_URL has not been declared in the SDK yet. We should drop the
39 // `s` prefix after it's released by a new SDK and define it with #if.
40 static const GUID sGUID_PROP_URL = {
41 0xd5138268,
42 0xa1bf,
43 0x4308,
44 {0xbc, 0xbf, 0x2e, 0x73, 0x93, 0x98, 0xe2, 0x34}};
46 namespace mozilla {
47 namespace widget {
49 /**
50 * TSF related code should log its behavior even on release build especially
51 * in the interface methods.
53 * In interface methods, use LogLevel::Info.
54 * In internal methods, use LogLevel::Debug for logging normal behavior.
55 * For logging error, use LogLevel::Error.
57 * When an instance method is called, start with following text:
58 * "0x%p TSFFoo::Bar(", the 0x%p should be the "this" of the nsFoo.
59 * after that, start with:
60 * "0x%p TSFFoo::Bar("
61 * In an internal method, start with following text:
62 * "0x%p TSFFoo::Bar("
63 * When a static method is called, start with following text:
64 * "TSFFoo::Bar("
67 enum class TextInputProcessorID {
68 // Internal use only. This won't be returned by TSFStaticSink::ActiveTIP().
69 eNotComputed,
71 // Not a TIP. E.g., simple keyboard layout or IMM-IME.
72 eNone,
74 // Used for other TIPs, i.e., any TIPs which we don't support specifically.
75 eUnknown,
77 // TIP for Japanese.
78 eMicrosoftIMEForJapanese,
79 eMicrosoftOfficeIME2010ForJapanese,
80 eGoogleJapaneseInput,
81 eATOK2011,
82 eATOK2012,
83 eATOK2013,
84 eATOK2014,
85 eATOK2015,
86 eATOK2016,
87 eATOKUnknown,
88 eJapanist10,
90 // TIP for Traditional Chinese.
91 eMicrosoftBopomofo,
92 eMicrosoftChangJie,
93 eMicrosoftPhonetic,
94 eMicrosoftQuick,
95 eMicrosoftNewChangJie,
96 eMicrosoftNewPhonetic,
97 eMicrosoftNewQuick,
98 eFreeChangJie,
100 // TIP for Simplified Chinese.
101 eMicrosoftPinyin,
102 eMicrosoftPinyinNewExperienceInputStyle,
103 eMicrosoftWubi,
105 // TIP for Korean.
106 eMicrosoftIMEForKorean,
107 eMicrosoftOldHangul,
109 // Keyman Desktop, which can install various language keyboards.
110 eKeymanDesktop,
113 static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
115 static void HandleSeparator(nsCString& aDesc) {
116 if (!aDesc.IsEmpty()) {
117 aDesc.AppendLiteral(" | ");
121 static const nsCString GetFindFlagName(DWORD aFindFlag) {
122 nsCString description;
123 if (!aFindFlag) {
124 description.AppendLiteral("no flags (0)");
125 return description;
127 if (aFindFlag & TS_ATTR_FIND_BACKWARDS) {
128 description.AppendLiteral("TS_ATTR_FIND_BACKWARDS");
130 if (aFindFlag & TS_ATTR_FIND_WANT_OFFSET) {
131 HandleSeparator(description);
132 description.AppendLiteral("TS_ATTR_FIND_WANT_OFFSET");
134 if (aFindFlag & TS_ATTR_FIND_UPDATESTART) {
135 HandleSeparator(description);
136 description.AppendLiteral("TS_ATTR_FIND_UPDATESTART");
138 if (aFindFlag & TS_ATTR_FIND_WANT_VALUE) {
139 HandleSeparator(description);
140 description.AppendLiteral("TS_ATTR_FIND_WANT_VALUE");
142 if (aFindFlag & TS_ATTR_FIND_WANT_END) {
143 HandleSeparator(description);
144 description.AppendLiteral("TS_ATTR_FIND_WANT_END");
146 if (aFindFlag & TS_ATTR_FIND_HIDDEN) {
147 HandleSeparator(description);
148 description.AppendLiteral("TS_ATTR_FIND_HIDDEN");
150 if (description.IsEmpty()) {
151 description.AppendLiteral("Unknown (");
152 description.AppendInt(static_cast<uint32_t>(aFindFlag));
153 description.Append(')');
155 return description;
158 class GetACPFromPointFlagName : public nsAutoCString {
159 public:
160 explicit GetACPFromPointFlagName(DWORD aFlags) {
161 if (!aFlags) {
162 AppendLiteral("no flags (0)");
163 return;
165 if (aFlags & GXFPF_ROUND_NEAREST) {
166 AppendLiteral("GXFPF_ROUND_NEAREST");
167 aFlags &= ~GXFPF_ROUND_NEAREST;
169 if (aFlags & GXFPF_NEAREST) {
170 HandleSeparator(*this);
171 AppendLiteral("GXFPF_NEAREST");
172 aFlags &= ~GXFPF_NEAREST;
174 if (aFlags) {
175 HandleSeparator(*this);
176 AppendLiteral("Unknown(");
177 AppendInt(static_cast<uint32_t>(aFlags));
178 Append(')');
181 virtual ~GetACPFromPointFlagName() {}
184 static const char* GetFocusChangeName(
185 InputContextAction::FocusChange aFocusChange) {
186 switch (aFocusChange) {
187 case InputContextAction::FOCUS_NOT_CHANGED:
188 return "FOCUS_NOT_CHANGED";
189 case InputContextAction::GOT_FOCUS:
190 return "GOT_FOCUS";
191 case InputContextAction::LOST_FOCUS:
192 return "LOST_FOCUS";
193 case InputContextAction::MENU_GOT_PSEUDO_FOCUS:
194 return "MENU_GOT_PSEUDO_FOCUS";
195 case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
196 return "MENU_LOST_PSEUDO_FOCUS";
197 case InputContextAction::WIDGET_CREATED:
198 return "WIDGET_CREATED";
199 default:
200 return "Unknown";
204 static nsCString GetCLSIDNameStr(REFCLSID aCLSID) {
205 LPOLESTR str = nullptr;
206 HRESULT hr = ::StringFromCLSID(aCLSID, &str);
207 if (FAILED(hr) || !str || !str[0]) {
208 return ""_ns;
211 nsCString result;
212 result = NS_ConvertUTF16toUTF8(str);
213 ::CoTaskMemFree(str);
214 return result;
217 static nsCString GetGUIDNameStr(REFGUID aGUID) {
218 OLECHAR str[40];
219 int len = ::StringFromGUID2(aGUID, str, ArrayLength(str));
220 if (!len || !str[0]) {
221 return ""_ns;
224 return NS_ConvertUTF16toUTF8(str);
227 static nsCString GetGUIDNameStrWithTable(REFGUID aGUID) {
228 #define RETURN_GUID_NAME(aNamedGUID) \
229 if (IsEqualGUID(aGUID, aNamedGUID)) { \
230 return nsLiteralCString(#aNamedGUID); \
233 RETURN_GUID_NAME(GUID_PROP_INPUTSCOPE)
234 RETURN_GUID_NAME(sGUID_PROP_URL)
235 RETURN_GUID_NAME(TSATTRID_OTHERS)
236 RETURN_GUID_NAME(TSATTRID_Font)
237 RETURN_GUID_NAME(TSATTRID_Font_FaceName)
238 RETURN_GUID_NAME(TSATTRID_Font_SizePts)
239 RETURN_GUID_NAME(TSATTRID_Font_Style)
240 RETURN_GUID_NAME(TSATTRID_Font_Style_Bold)
241 RETURN_GUID_NAME(TSATTRID_Font_Style_Italic)
242 RETURN_GUID_NAME(TSATTRID_Font_Style_SmallCaps)
243 RETURN_GUID_NAME(TSATTRID_Font_Style_Capitalize)
244 RETURN_GUID_NAME(TSATTRID_Font_Style_Uppercase)
245 RETURN_GUID_NAME(TSATTRID_Font_Style_Lowercase)
246 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation)
247 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_LasVegasLights)
248 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_BlinkingBackground)
249 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_SparkleText)
250 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingBlackAnts)
251 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingRedAnts)
252 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_Shimmer)
253 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeDown)
254 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeRight)
255 RETURN_GUID_NAME(TSATTRID_Font_Style_Emboss)
256 RETURN_GUID_NAME(TSATTRID_Font_Style_Engrave)
257 RETURN_GUID_NAME(TSATTRID_Font_Style_Hidden)
258 RETURN_GUID_NAME(TSATTRID_Font_Style_Kerning)
259 RETURN_GUID_NAME(TSATTRID_Font_Style_Outlined)
260 RETURN_GUID_NAME(TSATTRID_Font_Style_Position)
261 RETURN_GUID_NAME(TSATTRID_Font_Style_Protected)
262 RETURN_GUID_NAME(TSATTRID_Font_Style_Shadow)
263 RETURN_GUID_NAME(TSATTRID_Font_Style_Spacing)
264 RETURN_GUID_NAME(TSATTRID_Font_Style_Weight)
265 RETURN_GUID_NAME(TSATTRID_Font_Style_Height)
266 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline)
267 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Single)
268 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Double)
269 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough)
270 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Single)
271 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Double)
272 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline)
273 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Single)
274 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Double)
275 RETURN_GUID_NAME(TSATTRID_Font_Style_Blink)
276 RETURN_GUID_NAME(TSATTRID_Font_Style_Subscript)
277 RETURN_GUID_NAME(TSATTRID_Font_Style_Superscript)
278 RETURN_GUID_NAME(TSATTRID_Font_Style_Color)
279 RETURN_GUID_NAME(TSATTRID_Font_Style_BackgroundColor)
280 RETURN_GUID_NAME(TSATTRID_Text)
281 RETURN_GUID_NAME(TSATTRID_Text_VerticalWriting)
282 RETURN_GUID_NAME(TSATTRID_Text_RightToLeft)
283 RETURN_GUID_NAME(TSATTRID_Text_Orientation)
284 RETURN_GUID_NAME(TSATTRID_Text_Language)
285 RETURN_GUID_NAME(TSATTRID_Text_ReadOnly)
286 RETURN_GUID_NAME(TSATTRID_Text_EmbeddedObject)
287 RETURN_GUID_NAME(TSATTRID_Text_Alignment)
288 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Left)
289 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Right)
290 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Center)
291 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Justify)
292 RETURN_GUID_NAME(TSATTRID_Text_Link)
293 RETURN_GUID_NAME(TSATTRID_Text_Hyphenation)
294 RETURN_GUID_NAME(TSATTRID_Text_Para)
295 RETURN_GUID_NAME(TSATTRID_Text_Para_FirstLineIndent)
296 RETURN_GUID_NAME(TSATTRID_Text_Para_LeftIndent)
297 RETURN_GUID_NAME(TSATTRID_Text_Para_RightIndent)
298 RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceAfter)
299 RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceBefore)
300 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing)
301 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Single)
302 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_OnePtFive)
303 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Double)
304 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_AtLeast)
305 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Exactly)
306 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Multiple)
307 RETURN_GUID_NAME(TSATTRID_List)
308 RETURN_GUID_NAME(TSATTRID_List_LevelIndel)
309 RETURN_GUID_NAME(TSATTRID_List_Type)
310 RETURN_GUID_NAME(TSATTRID_List_Type_Bullet)
311 RETURN_GUID_NAME(TSATTRID_List_Type_Arabic)
312 RETURN_GUID_NAME(TSATTRID_List_Type_LowerLetter)
313 RETURN_GUID_NAME(TSATTRID_List_Type_UpperLetter)
314 RETURN_GUID_NAME(TSATTRID_List_Type_LowerRoman)
315 RETURN_GUID_NAME(TSATTRID_List_Type_UpperRoman)
316 RETURN_GUID_NAME(TSATTRID_App)
317 RETURN_GUID_NAME(TSATTRID_App_IncorrectSpelling)
318 RETURN_GUID_NAME(TSATTRID_App_IncorrectGrammar)
320 #undef RETURN_GUID_NAME
322 return GetGUIDNameStr(aGUID);
325 static nsCString GetRIIDNameStr(REFIID aRIID) {
326 LPOLESTR str = nullptr;
327 HRESULT hr = ::StringFromIID(aRIID, &str);
328 if (FAILED(hr) || !str || !str[0]) {
329 return ""_ns;
332 nsAutoString key(L"Interface\\");
333 key += str;
335 nsCString result;
336 wchar_t buf[256];
337 if (WinUtils::GetRegistryKey(HKEY_CLASSES_ROOT, key.get(), nullptr, buf,
338 sizeof(buf))) {
339 result = NS_ConvertUTF16toUTF8(buf);
340 } else {
341 result = NS_ConvertUTF16toUTF8(str);
344 ::CoTaskMemFree(str);
345 return result;
348 static const char* GetCommonReturnValueName(HRESULT aResult) {
349 switch (aResult) {
350 case S_OK:
351 return "S_OK";
352 case E_ABORT:
353 return "E_ABORT";
354 case E_ACCESSDENIED:
355 return "E_ACCESSDENIED";
356 case E_FAIL:
357 return "E_FAIL";
358 case E_HANDLE:
359 return "E_HANDLE";
360 case E_INVALIDARG:
361 return "E_INVALIDARG";
362 case E_NOINTERFACE:
363 return "E_NOINTERFACE";
364 case E_NOTIMPL:
365 return "E_NOTIMPL";
366 case E_OUTOFMEMORY:
367 return "E_OUTOFMEMORY";
368 case E_POINTER:
369 return "E_POINTER";
370 case E_UNEXPECTED:
371 return "E_UNEXPECTED";
372 default:
373 return SUCCEEDED(aResult) ? "Succeeded" : "Failed";
377 static const char* GetTextStoreReturnValueName(HRESULT aResult) {
378 switch (aResult) {
379 case TS_E_FORMAT:
380 return "TS_E_FORMAT";
381 case TS_E_INVALIDPOINT:
382 return "TS_E_INVALIDPOINT";
383 case TS_E_INVALIDPOS:
384 return "TS_E_INVALIDPOS";
385 case TS_E_NOINTERFACE:
386 return "TS_E_NOINTERFACE";
387 case TS_E_NOLAYOUT:
388 return "TS_E_NOLAYOUT";
389 case TS_E_NOLOCK:
390 return "TS_E_NOLOCK";
391 case TS_E_NOOBJECT:
392 return "TS_E_NOOBJECT";
393 case TS_E_NOSELECTION:
394 return "TS_E_NOSELECTION";
395 case TS_E_NOSERVICE:
396 return "TS_E_NOSERVICE";
397 case TS_E_READONLY:
398 return "TS_E_READONLY";
399 case TS_E_SYNCHRONOUS:
400 return "TS_E_SYNCHRONOUS";
401 case TS_S_ASYNC:
402 return "TS_S_ASYNC";
403 default:
404 return GetCommonReturnValueName(aResult);
408 static const nsCString GetSinkMaskNameStr(DWORD aSinkMask) {
409 nsCString description;
410 if (aSinkMask & TS_AS_TEXT_CHANGE) {
411 description.AppendLiteral("TS_AS_TEXT_CHANGE");
413 if (aSinkMask & TS_AS_SEL_CHANGE) {
414 HandleSeparator(description);
415 description.AppendLiteral("TS_AS_SEL_CHANGE");
417 if (aSinkMask & TS_AS_LAYOUT_CHANGE) {
418 HandleSeparator(description);
419 description.AppendLiteral("TS_AS_LAYOUT_CHANGE");
421 if (aSinkMask & TS_AS_ATTR_CHANGE) {
422 HandleSeparator(description);
423 description.AppendLiteral("TS_AS_ATTR_CHANGE");
425 if (aSinkMask & TS_AS_STATUS_CHANGE) {
426 HandleSeparator(description);
427 description.AppendLiteral("TS_AS_STATUS_CHANGE");
429 if (description.IsEmpty()) {
430 description.AppendLiteral("not-specified");
432 return description;
435 static const nsCString GetLockFlagNameStr(DWORD aLockFlags) {
436 nsCString description;
437 if ((aLockFlags & TS_LF_READWRITE) == TS_LF_READWRITE) {
438 description.AppendLiteral("TS_LF_READWRITE");
439 } else if (aLockFlags & TS_LF_READ) {
440 description.AppendLiteral("TS_LF_READ");
442 if (aLockFlags & TS_LF_SYNC) {
443 if (!description.IsEmpty()) {
444 description.AppendLiteral(" | ");
446 description.AppendLiteral("TS_LF_SYNC");
448 if (description.IsEmpty()) {
449 description.AppendLiteral("not-specified");
451 return description;
454 static const char* GetTextRunTypeName(TsRunType aRunType) {
455 switch (aRunType) {
456 case TS_RT_PLAIN:
457 return "TS_RT_PLAIN";
458 case TS_RT_HIDDEN:
459 return "TS_RT_HIDDEN";
460 case TS_RT_OPAQUE:
461 return "TS_RT_OPAQUE";
462 default:
463 return "Unknown";
467 static nsCString GetColorName(const TF_DA_COLOR& aColor) {
468 switch (aColor.type) {
469 case TF_CT_NONE:
470 return "TF_CT_NONE"_ns;
471 case TF_CT_SYSCOLOR:
472 return nsPrintfCString("TF_CT_SYSCOLOR, nIndex:0x%08X",
473 static_cast<int32_t>(aColor.nIndex));
474 case TF_CT_COLORREF:
475 return nsPrintfCString("TF_CT_COLORREF, cr:0x%08X",
476 static_cast<int32_t>(aColor.cr));
477 break;
478 default:
479 return nsPrintfCString("Unknown(%08X)",
480 static_cast<int32_t>(aColor.type));
484 static nsCString GetLineStyleName(TF_DA_LINESTYLE aLineStyle) {
485 switch (aLineStyle) {
486 case TF_LS_NONE:
487 return "TF_LS_NONE"_ns;
488 case TF_LS_SOLID:
489 return "TF_LS_SOLID"_ns;
490 case TF_LS_DOT:
491 return "TF_LS_DOT"_ns;
492 case TF_LS_DASH:
493 return "TF_LS_DASH"_ns;
494 case TF_LS_SQUIGGLE:
495 return "TF_LS_SQUIGGLE"_ns;
496 default: {
497 return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aLineStyle));
502 static nsCString GetClauseAttrName(TF_DA_ATTR_INFO aAttr) {
503 switch (aAttr) {
504 case TF_ATTR_INPUT:
505 return "TF_ATTR_INPUT"_ns;
506 case TF_ATTR_TARGET_CONVERTED:
507 return "TF_ATTR_TARGET_CONVERTED"_ns;
508 case TF_ATTR_CONVERTED:
509 return "TF_ATTR_CONVERTED"_ns;
510 case TF_ATTR_TARGET_NOTCONVERTED:
511 return "TF_ATTR_TARGET_NOTCONVERTED"_ns;
512 case TF_ATTR_INPUT_ERROR:
513 return "TF_ATTR_INPUT_ERROR"_ns;
514 case TF_ATTR_FIXEDCONVERTED:
515 return "TF_ATTR_FIXEDCONVERTED"_ns;
516 case TF_ATTR_OTHER:
517 return "TF_ATTR_OTHER"_ns;
518 default: {
519 return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aAttr));
524 static nsCString GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE& aDispAttr) {
525 nsCString str;
526 str = "crText:{ ";
527 str += GetColorName(aDispAttr.crText);
528 str += " }, crBk:{ ";
529 str += GetColorName(aDispAttr.crBk);
530 str += " }, lsStyle: ";
531 str += GetLineStyleName(aDispAttr.lsStyle);
532 str += ", fBoldLine: ";
533 str += GetBoolName(aDispAttr.fBoldLine);
534 str += ", crLine:{ ";
535 str += GetColorName(aDispAttr.crLine);
536 str += " }, bAttr: ";
537 str += GetClauseAttrName(aDispAttr.bAttr);
538 return str;
541 static const char* GetMouseButtonName(int16_t aButton) {
542 switch (aButton) {
543 case MouseButton::ePrimary:
544 return "LeftButton";
545 case MouseButton::eMiddle:
546 return "MiddleButton";
547 case MouseButton::eSecondary:
548 return "RightButton";
549 default:
550 return "UnknownButton";
554 #define ADD_SEPARATOR_IF_NECESSARY(aStr) \
555 if (!aStr.IsEmpty()) { \
556 aStr.AppendLiteral(", "); \
559 static nsCString GetMouseButtonsName(int16_t aButtons) {
560 if (!aButtons) {
561 return "no buttons"_ns;
563 nsCString names;
564 if (aButtons & MouseButtonsFlag::ePrimaryFlag) {
565 names = "LeftButton";
567 if (aButtons & MouseButtonsFlag::eSecondaryFlag) {
568 ADD_SEPARATOR_IF_NECESSARY(names);
569 names += "RightButton";
571 if (aButtons & MouseButtonsFlag::eMiddleFlag) {
572 ADD_SEPARATOR_IF_NECESSARY(names);
573 names += "MiddleButton";
575 if (aButtons & MouseButtonsFlag::e4thFlag) {
576 ADD_SEPARATOR_IF_NECESSARY(names);
577 names += "4thButton";
579 if (aButtons & MouseButtonsFlag::e5thFlag) {
580 ADD_SEPARATOR_IF_NECESSARY(names);
581 names += "5thButton";
583 return names;
586 static nsCString GetModifiersName(Modifiers aModifiers) {
587 if (aModifiers == MODIFIER_NONE) {
588 return "no modifiers"_ns;
590 nsCString names;
591 if (aModifiers & MODIFIER_ALT) {
592 names = NS_DOM_KEYNAME_ALT;
594 if (aModifiers & MODIFIER_ALTGRAPH) {
595 ADD_SEPARATOR_IF_NECESSARY(names);
596 names += NS_DOM_KEYNAME_ALTGRAPH;
598 if (aModifiers & MODIFIER_CAPSLOCK) {
599 ADD_SEPARATOR_IF_NECESSARY(names);
600 names += NS_DOM_KEYNAME_CAPSLOCK;
602 if (aModifiers & MODIFIER_CONTROL) {
603 ADD_SEPARATOR_IF_NECESSARY(names);
604 names += NS_DOM_KEYNAME_CONTROL;
606 if (aModifiers & MODIFIER_FN) {
607 ADD_SEPARATOR_IF_NECESSARY(names);
608 names += NS_DOM_KEYNAME_FN;
610 if (aModifiers & MODIFIER_FNLOCK) {
611 ADD_SEPARATOR_IF_NECESSARY(names);
612 names += NS_DOM_KEYNAME_FNLOCK;
614 if (aModifiers & MODIFIER_META) {
615 ADD_SEPARATOR_IF_NECESSARY(names);
616 names += NS_DOM_KEYNAME_META;
618 if (aModifiers & MODIFIER_NUMLOCK) {
619 ADD_SEPARATOR_IF_NECESSARY(names);
620 names += NS_DOM_KEYNAME_NUMLOCK;
622 if (aModifiers & MODIFIER_SCROLLLOCK) {
623 ADD_SEPARATOR_IF_NECESSARY(names);
624 names += NS_DOM_KEYNAME_SCROLLLOCK;
626 if (aModifiers & MODIFIER_SHIFT) {
627 ADD_SEPARATOR_IF_NECESSARY(names);
628 names += NS_DOM_KEYNAME_SHIFT;
630 if (aModifiers & MODIFIER_SYMBOL) {
631 ADD_SEPARATOR_IF_NECESSARY(names);
632 names += NS_DOM_KEYNAME_SYMBOL;
634 if (aModifiers & MODIFIER_SYMBOLLOCK) {
635 ADD_SEPARATOR_IF_NECESSARY(names);
636 names += NS_DOM_KEYNAME_SYMBOLLOCK;
638 if (aModifiers & MODIFIER_OS) {
639 ADD_SEPARATOR_IF_NECESSARY(names);
640 names += NS_DOM_KEYNAME_OS;
642 return names;
645 class GetWritingModeName : public nsAutoCString {
646 public:
647 explicit GetWritingModeName(const WritingMode& aWritingMode) {
648 if (!aWritingMode.IsVertical()) {
649 AssignLiteral("Horizontal");
650 return;
652 if (aWritingMode.IsVerticalLR()) {
653 AssignLiteral("Vertical (LR)");
654 return;
656 AssignLiteral("Vertical (RL)");
658 virtual ~GetWritingModeName() {}
661 class GetEscapedUTF8String final : public NS_ConvertUTF16toUTF8 {
662 public:
663 explicit GetEscapedUTF8String(const nsAString& aString)
664 : NS_ConvertUTF16toUTF8(aString) {
665 Escape();
667 explicit GetEscapedUTF8String(const char16ptr_t aString)
668 : NS_ConvertUTF16toUTF8(aString) {
669 Escape();
671 GetEscapedUTF8String(const char16ptr_t aString, uint32_t aLength)
672 : NS_ConvertUTF16toUTF8(aString, aLength) {
673 Escape();
676 private:
677 void Escape() {
678 ReplaceSubstring("\r", "\\r");
679 ReplaceSubstring("\n", "\\n");
680 ReplaceSubstring("\t", "\\t");
684 class GetInputScopeString : public nsAutoCString {
685 public:
686 explicit GetInputScopeString(const nsTArray<InputScope>& aList) {
687 for (InputScope inputScope : aList) {
688 if (!IsEmpty()) {
689 AppendLiteral(", ");
691 switch (inputScope) {
692 case IS_DEFAULT:
693 AppendLiteral("IS_DEFAULT");
694 break;
695 case IS_URL:
696 AppendLiteral("IS_URL");
697 break;
698 case IS_FILE_FULLFILEPATH:
699 AppendLiteral("IS_FILE_FULLFILEPATH");
700 break;
701 case IS_FILE_FILENAME:
702 AppendLiteral("IS_FILE_FILENAME");
703 break;
704 case IS_EMAIL_USERNAME:
705 AppendLiteral("IS_EMAIL_USERNAME");
706 break;
707 case IS_EMAIL_SMTPEMAILADDRESS:
708 AppendLiteral("IS_EMAIL_SMTPEMAILADDRESS");
709 break;
710 case IS_LOGINNAME:
711 AppendLiteral("IS_LOGINNAME");
712 break;
713 case IS_PERSONALNAME_FULLNAME:
714 AppendLiteral("IS_PERSONALNAME_FULLNAME");
715 break;
716 case IS_PERSONALNAME_PREFIX:
717 AppendLiteral("IS_PERSONALNAME_PREFIX");
718 break;
719 case IS_PERSONALNAME_GIVENNAME:
720 AppendLiteral("IS_PERSONALNAME_GIVENNAME");
721 break;
722 case IS_PERSONALNAME_MIDDLENAME:
723 AppendLiteral("IS_PERSONALNAME_MIDDLENAME");
724 break;
725 case IS_PERSONALNAME_SURNAME:
726 AppendLiteral("IS_PERSONALNAME_SURNAME");
727 break;
728 case IS_PERSONALNAME_SUFFIX:
729 AppendLiteral("IS_PERSONALNAME_SUFFIX");
730 break;
731 case IS_ADDRESS_FULLPOSTALADDRESS:
732 AppendLiteral("IS_ADDRESS_FULLPOSTALADDRESS");
733 break;
734 case IS_ADDRESS_POSTALCODE:
735 AppendLiteral("IS_ADDRESS_POSTALCODE");
736 break;
737 case IS_ADDRESS_STREET:
738 AppendLiteral("IS_ADDRESS_STREET");
739 break;
740 case IS_ADDRESS_STATEORPROVINCE:
741 AppendLiteral("IS_ADDRESS_STATEORPROVINCE");
742 break;
743 case IS_ADDRESS_CITY:
744 AppendLiteral("IS_ADDRESS_CITY");
745 break;
746 case IS_ADDRESS_COUNTRYNAME:
747 AppendLiteral("IS_ADDRESS_COUNTRYNAME");
748 break;
749 case IS_ADDRESS_COUNTRYSHORTNAME:
750 AppendLiteral("IS_ADDRESS_COUNTRYSHORTNAME");
751 break;
752 case IS_CURRENCY_AMOUNTANDSYMBOL:
753 AppendLiteral("IS_CURRENCY_AMOUNTANDSYMBOL");
754 break;
755 case IS_CURRENCY_AMOUNT:
756 AppendLiteral("IS_CURRENCY_AMOUNT");
757 break;
758 case IS_DATE_FULLDATE:
759 AppendLiteral("IS_DATE_FULLDATE");
760 break;
761 case IS_DATE_MONTH:
762 AppendLiteral("IS_DATE_MONTH");
763 break;
764 case IS_DATE_DAY:
765 AppendLiteral("IS_DATE_DAY");
766 break;
767 case IS_DATE_YEAR:
768 AppendLiteral("IS_DATE_YEAR");
769 break;
770 case IS_DATE_MONTHNAME:
771 AppendLiteral("IS_DATE_MONTHNAME");
772 break;
773 case IS_DATE_DAYNAME:
774 AppendLiteral("IS_DATE_DAYNAME");
775 break;
776 case IS_DIGITS:
777 AppendLiteral("IS_DIGITS");
778 break;
779 case IS_NUMBER:
780 AppendLiteral("IS_NUMBER");
781 break;
782 case IS_ONECHAR:
783 AppendLiteral("IS_ONECHAR");
784 break;
785 case IS_PASSWORD:
786 AppendLiteral("IS_PASSWORD");
787 break;
788 case IS_TELEPHONE_FULLTELEPHONENUMBER:
789 AppendLiteral("IS_TELEPHONE_FULLTELEPHONENUMBER");
790 break;
791 case IS_TELEPHONE_COUNTRYCODE:
792 AppendLiteral("IS_TELEPHONE_COUNTRYCODE");
793 break;
794 case IS_TELEPHONE_AREACODE:
795 AppendLiteral("IS_TELEPHONE_AREACODE");
796 break;
797 case IS_TELEPHONE_LOCALNUMBER:
798 AppendLiteral("IS_TELEPHONE_LOCALNUMBER");
799 break;
800 case IS_TIME_FULLTIME:
801 AppendLiteral("IS_TIME_FULLTIME");
802 break;
803 case IS_TIME_HOUR:
804 AppendLiteral("IS_TIME_HOUR");
805 break;
806 case IS_TIME_MINORSEC:
807 AppendLiteral("IS_TIME_MINORSEC");
808 break;
809 case IS_NUMBER_FULLWIDTH:
810 AppendLiteral("IS_NUMBER_FULLWIDTH");
811 break;
812 case IS_ALPHANUMERIC_HALFWIDTH:
813 AppendLiteral("IS_ALPHANUMERIC_HALFWIDTH");
814 break;
815 case IS_ALPHANUMERIC_FULLWIDTH:
816 AppendLiteral("IS_ALPHANUMERIC_FULLWIDTH");
817 break;
818 case IS_CURRENCY_CHINESE:
819 AppendLiteral("IS_CURRENCY_CHINESE");
820 break;
821 case IS_BOPOMOFO:
822 AppendLiteral("IS_BOPOMOFO");
823 break;
824 case IS_HIRAGANA:
825 AppendLiteral("IS_HIRAGANA");
826 break;
827 case IS_KATAKANA_HALFWIDTH:
828 AppendLiteral("IS_KATAKANA_HALFWIDTH");
829 break;
830 case IS_KATAKANA_FULLWIDTH:
831 AppendLiteral("IS_KATAKANA_FULLWIDTH");
832 break;
833 case IS_HANJA:
834 AppendLiteral("IS_HANJA");
835 break;
836 case IS_PHRASELIST:
837 AppendLiteral("IS_PHRASELIST");
838 break;
839 case IS_REGULAREXPRESSION:
840 AppendLiteral("IS_REGULAREXPRESSION");
841 break;
842 case IS_SRGS:
843 AppendLiteral("IS_SRGS");
844 break;
845 case IS_XML:
846 AppendLiteral("IS_XML");
847 break;
848 case IS_PRIVATE:
849 AppendLiteral("IS_PRIVATE");
850 break;
851 default:
852 AppendPrintf("Unknown Value(%d)", inputScope);
853 break;
859 /******************************************************************/
860 /* InputScopeImpl */
861 /******************************************************************/
863 class InputScopeImpl final : public ITfInputScope {
864 ~InputScopeImpl() {}
866 public:
867 explicit InputScopeImpl(const nsTArray<InputScope>& aList)
868 : mInputScopes(aList.Clone()) {
869 MOZ_LOG(
870 gIMELog, LogLevel::Info,
871 ("0x%p InputScopeImpl(%s)", this, GetInputScopeString(aList).get()));
874 NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(InputScopeImpl)
876 STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
877 *ppv = nullptr;
878 if ((IID_IUnknown == riid) || (IID_ITfInputScope == riid)) {
879 *ppv = static_cast<ITfInputScope*>(this);
881 if (*ppv) {
882 AddRef();
883 return S_OK;
885 return E_NOINTERFACE;
888 STDMETHODIMP GetInputScopes(InputScope** pprgInputScopes, UINT* pcCount) {
889 uint32_t count = (mInputScopes.IsEmpty() ? 1 : mInputScopes.Length());
891 InputScope* pScope =
892 (InputScope*)CoTaskMemAlloc(sizeof(InputScope) * count);
893 NS_ENSURE_TRUE(pScope, E_OUTOFMEMORY);
895 if (mInputScopes.IsEmpty()) {
896 *pScope = IS_DEFAULT;
897 *pcCount = 1;
898 *pprgInputScopes = pScope;
899 return S_OK;
902 *pcCount = 0;
904 for (uint32_t idx = 0; idx < count; idx++) {
905 *(pScope + idx) = mInputScopes[idx];
906 (*pcCount)++;
909 *pprgInputScopes = pScope;
910 return S_OK;
913 STDMETHODIMP GetPhrase(BSTR** ppbstrPhrases, UINT* pcCount) {
914 return E_NOTIMPL;
916 STDMETHODIMP GetRegularExpression(BSTR* pbstrRegExp) { return E_NOTIMPL; }
917 STDMETHODIMP GetSRGS(BSTR* pbstrSRGS) { return E_NOTIMPL; }
918 STDMETHODIMP GetXML(BSTR* pbstrXML) { return E_NOTIMPL; }
920 private:
921 nsTArray<InputScope> mInputScopes;
924 /******************************************************************/
925 /* TSFStaticSink */
926 /******************************************************************/
928 class TSFStaticSink final : public ITfInputProcessorProfileActivationSink {
929 public:
930 static TSFStaticSink* GetInstance() {
931 if (!sInstance) {
932 RefPtr<ITfThreadMgr> threadMgr = TSFTextStore::GetThreadMgr();
933 if (NS_WARN_IF(!threadMgr)) {
934 MOZ_LOG(
935 gIMELog, LogLevel::Error,
936 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
937 "instance due to no ThreadMgr instance"));
938 return nullptr;
940 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
941 TSFTextStore::GetInputProcessorProfiles();
942 if (NS_WARN_IF(!inputProcessorProfiles)) {
943 MOZ_LOG(
944 gIMELog, LogLevel::Error,
945 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
946 "instance due to no InputProcessorProfiles instance"));
947 return nullptr;
949 RefPtr<TSFStaticSink> staticSink = new TSFStaticSink();
950 if (NS_WARN_IF(!staticSink->Init(threadMgr, inputProcessorProfiles))) {
951 staticSink->Destroy();
952 MOZ_LOG(
953 gIMELog, LogLevel::Error,
954 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
955 "instance"));
956 return nullptr;
958 sInstance = staticSink.forget();
960 return sInstance;
963 static void Shutdown() {
964 if (sInstance) {
965 sInstance->Destroy();
966 sInstance = nullptr;
970 bool Init(ITfThreadMgr* aThreadMgr,
971 ITfInputProcessorProfiles* aInputProcessorProfiles);
972 STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
973 *ppv = nullptr;
974 if (IID_IUnknown == riid ||
975 IID_ITfInputProcessorProfileActivationSink == riid) {
976 *ppv = static_cast<ITfInputProcessorProfileActivationSink*>(this);
978 if (*ppv) {
979 AddRef();
980 return S_OK;
982 return E_NOINTERFACE;
985 NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFStaticSink)
987 const nsString& GetActiveTIPKeyboardDescription() const {
988 return mActiveTIPKeyboardDescription;
991 static bool IsIMM_IMEActive() {
992 // Use IMM API until TSFStaticSink starts to work.
993 if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
994 return IsIMM_IME(::GetKeyboardLayout(0));
996 return sInstance->mIsIMM_IME;
999 static bool IsIMM_IME(HKL aHKL) {
1000 return (::ImmGetIMEFileNameW(aHKL, nullptr, 0) > 0);
1003 static bool IsTraditionalChinese() {
1004 EnsureInstance();
1005 return sInstance && sInstance->IsTraditionalChineseInternal();
1007 static bool IsSimplifiedChinese() {
1008 EnsureInstance();
1009 return sInstance && sInstance->IsSimplifiedChineseInternal();
1011 static bool IsJapanese() {
1012 EnsureInstance();
1013 return sInstance && sInstance->IsJapaneseInternal();
1015 static bool IsKorean() {
1016 EnsureInstance();
1017 return sInstance && sInstance->IsKoreanInternal();
1021 * ActiveTIP() returns an ID for currently active TIP.
1022 * Please note that this method is expensive due to needs a lot of GUID
1023 * comparations if active language ID is one of CJKT. If you need to
1024 * check TIPs for a specific language, you should check current language
1025 * first.
1027 static TextInputProcessorID ActiveTIP() {
1028 EnsureInstance();
1029 if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
1030 return TextInputProcessorID::eUnknown;
1032 sInstance->ComputeActiveTextInputProcessor();
1033 if (NS_WARN_IF(sInstance->mActiveTIP ==
1034 TextInputProcessorID::eNotComputed)) {
1035 return TextInputProcessorID::eUnknown;
1037 return sInstance->mActiveTIP;
1040 static bool IsMSChangJieOrMSQuickActive() {
1041 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1042 // For avoiding unnecessary computation, we should check if the language
1043 // for current TIP is Traditional Chinese.
1044 if (!IsTraditionalChinese()) {
1045 return false;
1047 switch (ActiveTIP()) {
1048 case TextInputProcessorID::eMicrosoftChangJie:
1049 case TextInputProcessorID::eMicrosoftQuick:
1050 return true;
1051 default:
1052 return false;
1056 static bool IsMSPinyinOrMSWubiActive() {
1057 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1058 // For avoiding unnecessary computation, we should check if the language
1059 // for current TIP is Simplified Chinese.
1060 if (!IsSimplifiedChinese()) {
1061 return false;
1063 switch (ActiveTIP()) {
1064 case TextInputProcessorID::eMicrosoftPinyin:
1065 case TextInputProcessorID::eMicrosoftWubi:
1066 return true;
1067 default:
1068 return false;
1072 static bool IsMSJapaneseIMEActive() {
1073 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1074 // For avoiding unnecessary computation, we should check if the language
1075 // for current TIP is Japanese.
1076 if (!IsJapanese()) {
1077 return false;
1079 return ActiveTIP() == TextInputProcessorID::eMicrosoftIMEForJapanese;
1082 static bool IsGoogleJapaneseInputActive() {
1083 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1084 // For avoiding unnecessary computation, we should check if the language
1085 // for current TIP is Japanese.
1086 if (!IsJapanese()) {
1087 return false;
1089 return ActiveTIP() == TextInputProcessorID::eGoogleJapaneseInput;
1092 static bool IsATOKActive() {
1093 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1094 // For avoiding unnecessary computation, we should check if active TIP is
1095 // ATOK first since it's cheaper.
1096 return IsJapanese() && sInstance->IsATOKActiveInternal();
1099 // Note that ATOK 2011 - 2016 refers native caret position for deciding its
1100 // popup window position.
1101 static bool IsATOKReferringNativeCaretActive() {
1102 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1103 // For avoiding unnecessary computation, we should check if active TIP is
1104 // ATOK first since it's cheaper.
1105 if (!IsJapanese() || !sInstance->IsATOKActiveInternal()) {
1106 return false;
1108 switch (ActiveTIP()) {
1109 case TextInputProcessorID::eATOK2011:
1110 case TextInputProcessorID::eATOK2012:
1111 case TextInputProcessorID::eATOK2013:
1112 case TextInputProcessorID::eATOK2014:
1113 case TextInputProcessorID::eATOK2015:
1114 return true;
1115 default:
1116 return false;
1120 private:
1121 static void EnsureInstance() {
1122 if (!sInstance) {
1123 RefPtr<TSFStaticSink> staticSink = GetInstance();
1124 Unused << staticSink;
1128 bool IsTraditionalChineseInternal() const { return mLangID == 0x0404; }
1129 bool IsSimplifiedChineseInternal() const { return mLangID == 0x0804; }
1130 bool IsJapaneseInternal() const { return mLangID == 0x0411; }
1131 bool IsKoreanInternal() const { return mLangID == 0x0412; }
1133 bool IsATOKActiveInternal() {
1134 EnsureInitActiveTIPKeyboard();
1135 // FYI: Name of packaged ATOK includes the release year like "ATOK 2015".
1136 // Name of ATOK Passport (subscription) equals "ATOK".
1137 return StringBeginsWith(mActiveTIPKeyboardDescription, u"ATOK "_ns) ||
1138 mActiveTIPKeyboardDescription.EqualsLiteral("ATOK");
1141 void ComputeActiveTextInputProcessor() {
1142 if (mActiveTIP != TextInputProcessorID::eNotComputed) {
1143 return;
1146 if (mActiveTIPGUID == GUID_NULL) {
1147 mActiveTIP = TextInputProcessorID::eNone;
1148 return;
1151 // Comparing GUID is slow. So, we should use language information to
1152 // reduce the comparing cost for TIP which is not we do not support
1153 // specifically since they are always compared with all supported TIPs.
1154 switch (mLangID) {
1155 case 0x0404:
1156 mActiveTIP = ComputeActiveTIPAsTraditionalChinese();
1157 break;
1158 case 0x0411:
1159 mActiveTIP = ComputeActiveTIPAsJapanese();
1160 break;
1161 case 0x0412:
1162 mActiveTIP = ComputeActiveTIPAsKorean();
1163 break;
1164 case 0x0804:
1165 mActiveTIP = ComputeActiveTIPAsSimplifiedChinese();
1166 break;
1167 default:
1168 mActiveTIP = TextInputProcessorID::eUnknown;
1169 break;
1171 // Special case for Keyman Desktop, it is available for any languages.
1172 // Therefore, we need to check it only if we don't know the active TIP.
1173 if (mActiveTIP != TextInputProcessorID::eUnknown) {
1174 return;
1177 // Note that keyboard layouts for Keyman assign its GUID on install
1178 // randomly, but CLSID is constant in any environments.
1179 // https://bugzilla.mozilla.org/show_bug.cgi?id=1670834#c7
1180 // https://github.com/keymanapp/keyman/blob/318c73a9e1d571d942837ff9964590626e5bd5aa/windows/src/engine/kmtip/globals.cpp#L37
1181 // {FE0420F1-38D1-4B4C-96BF-E7E20A74CFB7}
1182 static constexpr CLSID kKeymanDesktop_CLSID = {
1183 0xFE0420F1,
1184 0x38D1,
1185 0x4B4C,
1186 {0x96, 0xBF, 0xE7, 0xE2, 0x0A, 0x74, 0xCF, 0xB7}};
1187 if (mActiveTIPCLSID == kKeymanDesktop_CLSID) {
1188 mActiveTIP = TextInputProcessorID::eKeymanDesktop;
1192 TextInputProcessorID ComputeActiveTIPAsJapanese() {
1193 // {A76C93D9-5523-4E90-AAFA-4DB112F9AC76} (Win7, Win8.1, Win10)
1194 static constexpr GUID kMicrosoftIMEForJapaneseGUID = {
1195 0xA76C93D9,
1196 0x5523,
1197 0x4E90,
1198 {0xAA, 0xFA, 0x4D, 0xB1, 0x12, 0xF9, 0xAC, 0x76}};
1199 if (mActiveTIPGUID == kMicrosoftIMEForJapaneseGUID) {
1200 return TextInputProcessorID::eMicrosoftIMEForJapanese;
1202 // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64}
1203 static constexpr GUID kMicrosoftOfficeIME2010ForJapaneseGUID = {
1204 0x54EDCC94,
1205 0x1524,
1206 0x4BB1,
1207 {0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64}};
1208 if (mActiveTIPGUID == kMicrosoftOfficeIME2010ForJapaneseGUID) {
1209 return TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese;
1211 // {773EB24E-CA1D-4B1B-B420-FA985BB0B80D}
1212 static constexpr GUID kGoogleJapaneseInputGUID = {
1213 0x773EB24E,
1214 0xCA1D,
1215 0x4B1B,
1216 {0xB4, 0x20, 0xFA, 0x98, 0x5B, 0xB0, 0xB8, 0x0D}};
1217 if (mActiveTIPGUID == kGoogleJapaneseInputGUID) {
1218 return TextInputProcessorID::eGoogleJapaneseInput;
1220 // {F9C24A5C-8A53-499D-9572-93B2FF582115}
1221 static const GUID kATOK2011GUID = {
1222 0xF9C24A5C,
1223 0x8A53,
1224 0x499D,
1225 {0x95, 0x72, 0x93, 0xB2, 0xFF, 0x58, 0x21, 0x15}};
1226 if (mActiveTIPGUID == kATOK2011GUID) {
1227 return TextInputProcessorID::eATOK2011;
1229 // {1DE01562-F445-401B-B6C3-E5B18DB79461}
1230 static constexpr GUID kATOK2012GUID = {
1231 0x1DE01562,
1232 0xF445,
1233 0x401B,
1234 {0xB6, 0xC3, 0xE5, 0xB1, 0x8D, 0xB7, 0x94, 0x61}};
1235 if (mActiveTIPGUID == kATOK2012GUID) {
1236 return TextInputProcessorID::eATOK2012;
1238 // {3C4DB511-189A-4168-B6EA-BFD0B4C85615}
1239 static constexpr GUID kATOK2013GUID = {
1240 0x3C4DB511,
1241 0x189A,
1242 0x4168,
1243 {0xB6, 0xEA, 0xBF, 0xD0, 0xB4, 0xC8, 0x56, 0x15}};
1244 if (mActiveTIPGUID == kATOK2013GUID) {
1245 return TextInputProcessorID::eATOK2013;
1247 // {4EF33B79-6AA9-4271-B4BF-9321C279381B}
1248 static constexpr GUID kATOK2014GUID = {
1249 0x4EF33B79,
1250 0x6AA9,
1251 0x4271,
1252 {0xB4, 0xBF, 0x93, 0x21, 0xC2, 0x79, 0x38, 0x1B}};
1253 if (mActiveTIPGUID == kATOK2014GUID) {
1254 return TextInputProcessorID::eATOK2014;
1256 // {EAB4DC00-CE2E-483D-A86A-E6B99DA9599A}
1257 static constexpr GUID kATOK2015GUID = {
1258 0xEAB4DC00,
1259 0xCE2E,
1260 0x483D,
1261 {0xA8, 0x6A, 0xE6, 0xB9, 0x9D, 0xA9, 0x59, 0x9A}};
1262 if (mActiveTIPGUID == kATOK2015GUID) {
1263 return TextInputProcessorID::eATOK2015;
1265 // {0B557B4C-5740-4110-A60A-1493FA10BF2B}
1266 static constexpr GUID kATOK2016GUID = {
1267 0x0B557B4C,
1268 0x5740,
1269 0x4110,
1270 {0xA6, 0x0A, 0x14, 0x93, 0xFA, 0x10, 0xBF, 0x2B}};
1271 if (mActiveTIPGUID == kATOK2016GUID) {
1272 return TextInputProcessorID::eATOK2016;
1275 // * ATOK 2017
1276 // - {6DBFD8F5-701D-11E6-920F-782BCBA6348F}
1277 // * ATOK Passport (confirmed with version 31.1.2)
1278 // - {A38F2FD9-7199-45E1-841C-BE0313D8052F}
1280 if (IsATOKActiveInternal()) {
1281 return TextInputProcessorID::eATOKUnknown;
1284 // {E6D66705-1EDA-4373-8D01-1D0CB2D054C7}
1285 static constexpr GUID kJapanist10GUID = {
1286 0xE6D66705,
1287 0x1EDA,
1288 0x4373,
1289 {0x8D, 0x01, 0x1D, 0x0C, 0xB2, 0xD0, 0x54, 0xC7}};
1290 if (mActiveTIPGUID == kJapanist10GUID) {
1291 return TextInputProcessorID::eJapanist10;
1294 return TextInputProcessorID::eUnknown;
1297 TextInputProcessorID ComputeActiveTIPAsTraditionalChinese() {
1298 // {B2F9C502-1742-11D4-9790-0080C882687E} (Win8.1, Win10)
1299 static constexpr GUID kMicrosoftBopomofoGUID = {
1300 0xB2F9C502,
1301 0x1742,
1302 0x11D4,
1303 {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1304 if (mActiveTIPGUID == kMicrosoftBopomofoGUID) {
1305 return TextInputProcessorID::eMicrosoftBopomofo;
1307 // {4BDF9F03-C7D3-11D4-B2AB-0080C882687E} (Win7, Win8.1, Win10)
1308 static const GUID kMicrosoftChangJieGUID = {
1309 0x4BDF9F03,
1310 0xC7D3,
1311 0x11D4,
1312 {0xB2, 0xAB, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1313 if (mActiveTIPGUID == kMicrosoftChangJieGUID) {
1314 return TextInputProcessorID::eMicrosoftChangJie;
1316 // {761309DE-317A-11D4-9B5D-0080C882687E} (Win7)
1317 static constexpr GUID kMicrosoftPhoneticGUID = {
1318 0x761309DE,
1319 0x317A,
1320 0x11D4,
1321 {0x9B, 0x5D, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1322 if (mActiveTIPGUID == kMicrosoftPhoneticGUID) {
1323 return TextInputProcessorID::eMicrosoftPhonetic;
1325 // {6024B45F-5C54-11D4-B921-0080C882687E} (Win7, Win8.1, Win10)
1326 static constexpr GUID kMicrosoftQuickGUID = {
1327 0x6024B45F,
1328 0x5C54,
1329 0x11D4,
1330 {0xB9, 0x21, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1331 if (mActiveTIPGUID == kMicrosoftQuickGUID) {
1332 return TextInputProcessorID::eMicrosoftQuick;
1334 // {F3BA907A-6C7E-11D4-97FA-0080C882687E} (Win7)
1335 static constexpr GUID kMicrosoftNewChangJieGUID = {
1336 0xF3BA907A,
1337 0x6C7E,
1338 0x11D4,
1339 {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1340 if (mActiveTIPGUID == kMicrosoftNewChangJieGUID) {
1341 return TextInputProcessorID::eMicrosoftNewChangJie;
1343 // {B2F9C502-1742-11D4-9790-0080C882687E} (Win7)
1344 static constexpr GUID kMicrosoftNewPhoneticGUID = {
1345 0xB2F9C502,
1346 0x1742,
1347 0x11D4,
1348 {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1349 if (mActiveTIPGUID == kMicrosoftNewPhoneticGUID) {
1350 return TextInputProcessorID::eMicrosoftNewPhonetic;
1352 // {0B883BA0-C1C7-11D4-87F9-0080C882687E} (Win7)
1353 static constexpr GUID kMicrosoftNewQuickGUID = {
1354 0x0B883BA0,
1355 0xC1C7,
1356 0x11D4,
1357 {0x87, 0xF9, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1358 if (mActiveTIPGUID == kMicrosoftNewQuickGUID) {
1359 return TextInputProcessorID::eMicrosoftNewQuick;
1362 // NOTE: There are some other Traditional Chinese TIPs installed in Windows:
1363 // * Chinese Traditional Array (version 6.0)
1364 // - {D38EFF65-AA46-4FD5-91A7-67845FB02F5B} (Win7, Win8.1)
1365 // * Chinese Traditional DaYi (version 6.0)
1366 // - {037B2C25-480C-4D7F-B027-D6CA6B69788A} (Win7, Win8.1)
1368 // {B58630B5-0ED3-4335-BBC9-E77BBCB43CAD}
1369 static const GUID kFreeChangJieGUID = {
1370 0xB58630B5,
1371 0x0ED3,
1372 0x4335,
1373 {0xBB, 0xC9, 0xE7, 0x7B, 0xBC, 0xB4, 0x3C, 0xAD}};
1374 if (mActiveTIPGUID == kFreeChangJieGUID) {
1375 return TextInputProcessorID::eFreeChangJie;
1378 return TextInputProcessorID::eUnknown;
1381 TextInputProcessorID ComputeActiveTIPAsSimplifiedChinese() {
1382 // FYI: This matches with neither "Microsoft Pinyin ABC Input Style" nor
1383 // "Microsoft Pinyin New Experience Input Style" on Win7.
1384 // {FA550B04-5AD7-411F-A5AC-CA038EC515D7} (Win8.1, Win10)
1385 static constexpr GUID kMicrosoftPinyinGUID = {
1386 0xFA550B04,
1387 0x5AD7,
1388 0x411F,
1389 {0xA5, 0xAC, 0xCA, 0x03, 0x8E, 0xC5, 0x15, 0xD7}};
1390 if (mActiveTIPGUID == kMicrosoftPinyinGUID) {
1391 return TextInputProcessorID::eMicrosoftPinyin;
1394 // {F3BA9077-6C7E-11D4-97FA-0080C882687E} (Win7)
1395 static constexpr GUID kMicrosoftPinyinNewExperienceInputStyleGUID = {
1396 0xF3BA9077,
1397 0x6C7E,
1398 0x11D4,
1399 {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1400 if (mActiveTIPGUID == kMicrosoftPinyinNewExperienceInputStyleGUID) {
1401 return TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle;
1403 // {82590C13-F4DD-44F4-BA1D-8667246FDF8E} (Win8.1, Win10)
1404 static constexpr GUID kMicrosoftWubiGUID = {
1405 0x82590C13,
1406 0xF4DD,
1407 0x44F4,
1408 {0xBA, 0x1D, 0x86, 0x67, 0x24, 0x6F, 0xDF, 0x8E}};
1409 if (mActiveTIPGUID == kMicrosoftWubiGUID) {
1410 return TextInputProcessorID::eMicrosoftWubi;
1412 // NOTE: There are some other Simplified Chinese TIPs installed in Windows:
1413 // * Chinese Simplified QuanPin (version 6.0)
1414 // - {54FC610E-6ABD-4685-9DDD-A130BDF1B170} (Win8.1)
1415 // * Chinese Simplified ZhengMa (version 6.0)
1416 // - {733B4D81-3BC3-4132-B91A-E9CDD5E2BFC9} (Win8.1)
1417 // * Chinese Simplified ShuangPin (version 6.0)
1418 // - {EF63706D-31C4-490E-9DBB-BD150ADC454B} (Win8.1)
1419 // * Microsoft Pinyin ABC Input Style
1420 // - {FCA121D2-8C6D-41FB-B2DE-A2AD110D4820} (Win7)
1421 return TextInputProcessorID::eUnknown;
1424 TextInputProcessorID ComputeActiveTIPAsKorean() {
1425 // {B5FE1F02-D5F2-4445-9C03-C568F23C99A1} (Win7, Win8.1, Win10)
1426 static constexpr GUID kMicrosoftIMEForKoreanGUID = {
1427 0xB5FE1F02,
1428 0xD5F2,
1429 0x4445,
1430 {0x9C, 0x03, 0xC5, 0x68, 0xF2, 0x3C, 0x99, 0xA1}};
1431 if (mActiveTIPGUID == kMicrosoftIMEForKoreanGUID) {
1432 return TextInputProcessorID::eMicrosoftIMEForKorean;
1434 // {B60AF051-257A-46BC-B9D3-84DAD819BAFB} (Win8.1, Win10)
1435 static constexpr GUID kMicrosoftOldHangulGUID = {
1436 0xB60AF051,
1437 0x257A,
1438 0x46BC,
1439 {0xB9, 0xD3, 0x84, 0xDA, 0xD8, 0x19, 0xBA, 0xFB}};
1440 if (mActiveTIPGUID == kMicrosoftOldHangulGUID) {
1441 return TextInputProcessorID::eMicrosoftOldHangul;
1444 // NOTE: There is the other Korean TIP installed in Windows:
1445 // * Microsoft IME 2010
1446 // - {48878C45-93F9-4aaf-A6A1-272CD863C4F5} (Win7)
1448 return TextInputProcessorID::eUnknown;
1451 public: // ITfInputProcessorProfileActivationSink
1452 STDMETHODIMP OnActivated(DWORD, LANGID, REFCLSID, REFGUID, REFGUID, HKL,
1453 DWORD);
1455 private:
1456 TSFStaticSink();
1457 virtual ~TSFStaticSink() {}
1459 bool EnsureInitActiveTIPKeyboard();
1461 void Destroy();
1463 void GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
1464 REFGUID aProfile, nsAString& aDescription);
1465 bool IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
1466 REFGUID aProfile);
1468 TextInputProcessorID mActiveTIP;
1470 // Cookie of installing ITfInputProcessorProfileActivationSink
1471 DWORD mIPProfileCookie;
1473 LANGID mLangID;
1475 // True if current IME is implemented with IMM.
1476 bool mIsIMM_IME;
1477 // True if OnActivated() is already called
1478 bool mOnActivatedCalled;
1480 RefPtr<ITfThreadMgr> mThreadMgr;
1481 RefPtr<ITfInputProcessorProfiles> mInputProcessorProfiles;
1483 // Active TIP keyboard's description. If active language profile isn't TIP,
1484 // i.e., IMM-IME or just a keyboard layout, this is empty.
1485 nsString mActiveTIPKeyboardDescription;
1487 // Active TIP's GUID and CLSID
1488 GUID mActiveTIPGUID;
1489 CLSID mActiveTIPCLSID;
1491 static StaticRefPtr<TSFStaticSink> sInstance;
1494 StaticRefPtr<TSFStaticSink> TSFStaticSink::sInstance;
1496 TSFStaticSink::TSFStaticSink()
1497 : mActiveTIP(TextInputProcessorID::eNotComputed),
1498 mIPProfileCookie(TF_INVALID_COOKIE),
1499 mLangID(0),
1500 mIsIMM_IME(false),
1501 mOnActivatedCalled(false),
1502 mActiveTIPGUID(GUID_NULL) {}
1504 bool TSFStaticSink::Init(ITfThreadMgr* aThreadMgr,
1505 ITfInputProcessorProfiles* aInputProcessorProfiles) {
1506 MOZ_ASSERT(!mThreadMgr && !mInputProcessorProfiles,
1507 "TSFStaticSink::Init() must be called only once");
1509 mThreadMgr = aThreadMgr;
1510 mInputProcessorProfiles = aInputProcessorProfiles;
1512 RefPtr<ITfSource> source;
1513 HRESULT hr =
1514 mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
1515 if (FAILED(hr)) {
1516 MOZ_LOG(gIMELog, LogLevel::Error,
1517 ("0x%p TSFStaticSink::Init() FAILED to get ITfSource "
1518 "instance (0x%08lX)",
1519 this, hr));
1520 return false;
1523 // NOTE: On Vista or later, Windows let us know activate IME changed only
1524 // with ITfInputProcessorProfileActivationSink.
1525 hr = source->AdviseSink(
1526 IID_ITfInputProcessorProfileActivationSink,
1527 static_cast<ITfInputProcessorProfileActivationSink*>(this),
1528 &mIPProfileCookie);
1529 if (FAILED(hr) || mIPProfileCookie == TF_INVALID_COOKIE) {
1530 MOZ_LOG(gIMELog, LogLevel::Error,
1531 ("0x%p TSFStaticSink::Init() FAILED to install "
1532 "ITfInputProcessorProfileActivationSink (0x%08lX)",
1533 this, hr));
1534 return false;
1537 MOZ_LOG(gIMELog, LogLevel::Info,
1538 ("0x%p TSFStaticSink::Init(), "
1539 "mIPProfileCookie=0x%08lX",
1540 this, mIPProfileCookie));
1541 return true;
1544 void TSFStaticSink::Destroy() {
1545 MOZ_LOG(gIMELog, LogLevel::Info,
1546 ("0x%p TSFStaticSink::Shutdown() "
1547 "mIPProfileCookie=0x%08lX",
1548 this, mIPProfileCookie));
1550 if (mIPProfileCookie != TF_INVALID_COOKIE) {
1551 RefPtr<ITfSource> source;
1552 HRESULT hr =
1553 mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
1554 if (FAILED(hr)) {
1555 MOZ_LOG(gIMELog, LogLevel::Error,
1556 ("0x%p TSFStaticSink::Shutdown() FAILED to get "
1557 "ITfSource instance (0x%08lX)",
1558 this, hr));
1559 } else {
1560 hr = source->UnadviseSink(mIPProfileCookie);
1561 if (FAILED(hr)) {
1562 MOZ_LOG(gIMELog, LogLevel::Error,
1563 ("0x%p TSFTextStore::Shutdown() FAILED to uninstall "
1564 "ITfInputProcessorProfileActivationSink (0x%08lX)",
1565 this, hr));
1570 mThreadMgr = nullptr;
1571 mInputProcessorProfiles = nullptr;
1574 STDMETHODIMP
1575 TSFStaticSink::OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID rclsid,
1576 REFGUID catid, REFGUID guidProfile, HKL hkl,
1577 DWORD dwFlags) {
1578 if ((dwFlags & TF_IPSINK_FLAG_ACTIVE) &&
1579 (dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT ||
1580 catid == GUID_TFCAT_TIP_KEYBOARD)) {
1581 mOnActivatedCalled = true;
1582 mActiveTIP = TextInputProcessorID::eNotComputed;
1583 mActiveTIPGUID = guidProfile;
1584 mActiveTIPCLSID = rclsid;
1585 mLangID = langid & 0xFFFF;
1586 mIsIMM_IME = IsIMM_IME(hkl);
1587 GetTIPDescription(rclsid, langid, guidProfile,
1588 mActiveTIPKeyboardDescription);
1589 if (mActiveTIPGUID != GUID_NULL) {
1590 // key should be "LocaleID|Description". Although GUID of the
1591 // profile is unique key since description may be localized for system
1592 // language, unfortunately, it's too long to record as key with its
1593 // description. Therefore, we should record only the description with
1594 // LocaleID because Microsoft IME may not include language information.
1595 // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
1596 nsAutoString key;
1597 key.AppendPrintf("0x%04X|", mLangID);
1598 nsAutoString description(mActiveTIPKeyboardDescription);
1599 static const uint32_t kMaxDescriptionLength = 72 - key.Length();
1600 if (description.Length() > kMaxDescriptionLength) {
1601 if (NS_IS_LOW_SURROGATE(description[kMaxDescriptionLength - 1]) &&
1602 NS_IS_HIGH_SURROGATE(description[kMaxDescriptionLength - 2])) {
1603 description.Truncate(kMaxDescriptionLength - 2);
1604 } else {
1605 description.Truncate(kMaxDescriptionLength - 1);
1607 // U+2026 is "..."
1608 description.Append(char16_t(0x2026));
1610 key.Append(description);
1611 Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_WINDOWS, key,
1612 true);
1614 // Notify IMEHandler of changing active keyboard layout.
1615 IMEHandler::OnKeyboardLayoutChanged();
1617 MOZ_LOG(gIMELog, LogLevel::Info,
1618 ("0x%p TSFStaticSink::OnActivated(dwProfileType=%s (0x%08lX), "
1619 "langid=0x%08X, rclsid=%s, catid=%s, guidProfile=%s, hkl=0x%p, "
1620 "dwFlags=0x%08lX (TF_IPSINK_FLAG_ACTIVE: %s)), mIsIMM_IME=%s, "
1621 "mActiveTIPDescription=\"%s\"",
1622 this,
1623 dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR
1624 ? "TF_PROFILETYPE_INPUTPROCESSOR"
1625 : dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT
1626 ? "TF_PROFILETYPE_KEYBOARDLAYOUT"
1627 : "Unknown",
1628 dwProfileType, langid, GetCLSIDNameStr(rclsid).get(),
1629 GetGUIDNameStr(catid).get(), GetGUIDNameStr(guidProfile).get(), hkl,
1630 dwFlags, GetBoolName(dwFlags & TF_IPSINK_FLAG_ACTIVE),
1631 GetBoolName(mIsIMM_IME),
1632 NS_ConvertUTF16toUTF8(mActiveTIPKeyboardDescription).get()));
1633 return S_OK;
1636 bool TSFStaticSink::EnsureInitActiveTIPKeyboard() {
1637 if (mOnActivatedCalled) {
1638 return true;
1641 RefPtr<ITfInputProcessorProfileMgr> profileMgr;
1642 HRESULT hr = mInputProcessorProfiles->QueryInterface(
1643 IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
1644 if (FAILED(hr) || !profileMgr) {
1645 MOZ_LOG(gIMELog, LogLevel::Error,
1646 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1647 "to get input processor profile manager, hr=0x%08lX",
1648 this, hr));
1649 return false;
1652 TF_INPUTPROCESSORPROFILE profile;
1653 hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
1654 if (hr == S_FALSE) {
1655 MOZ_LOG(gIMELog, LogLevel::Info,
1656 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1657 "to get active keyboard layout profile due to no active profile, "
1658 "hr=0x%08lX",
1659 this, hr));
1660 // XXX Should we call OnActivated() with arguments like non-TIP in this
1661 // case?
1662 return false;
1664 if (FAILED(hr)) {
1665 MOZ_LOG(gIMELog, LogLevel::Error,
1666 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1667 "to get active TIP keyboard, hr=0x%08lX",
1668 this, hr));
1669 return false;
1672 MOZ_LOG(gIMELog, LogLevel::Info,
1673 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), "
1674 "calling OnActivated() manually...",
1675 this));
1676 OnActivated(profile.dwProfileType, profile.langid, profile.clsid,
1677 profile.catid, profile.guidProfile, ::GetKeyboardLayout(0),
1678 TF_IPSINK_FLAG_ACTIVE);
1679 return true;
1682 void TSFStaticSink::GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
1683 REFGUID aProfile,
1684 nsAString& aDescription) {
1685 aDescription.Truncate();
1687 if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
1688 return;
1691 BSTR description = nullptr;
1692 HRESULT hr = mInputProcessorProfiles->GetLanguageProfileDescription(
1693 aTextService, aLangID, aProfile, &description);
1694 if (FAILED(hr)) {
1695 MOZ_LOG(gIMELog, LogLevel::Error,
1696 ("0x%p TSFStaticSink::InitActiveTIPDescription() FAILED "
1697 "due to GetLanguageProfileDescription() failure, hr=0x%08lX",
1698 this, hr));
1699 return;
1702 if (description && description[0]) {
1703 aDescription.Assign(description);
1705 ::SysFreeString(description);
1708 bool TSFStaticSink::IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
1709 REFGUID aProfile) {
1710 if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
1711 return false;
1714 RefPtr<IEnumTfLanguageProfiles> enumLangProfiles;
1715 HRESULT hr = mInputProcessorProfiles->EnumLanguageProfiles(
1716 aLangID, getter_AddRefs(enumLangProfiles));
1717 if (FAILED(hr) || !enumLangProfiles) {
1718 MOZ_LOG(gIMELog, LogLevel::Error,
1719 ("0x%p TSFStaticSink::IsTIPCategoryKeyboard(), FAILED "
1720 "to get language profiles enumerator, hr=0x%08lX",
1721 this, hr));
1722 return false;
1725 TF_LANGUAGEPROFILE profile;
1726 ULONG fetch = 0;
1727 while (SUCCEEDED(enumLangProfiles->Next(1, &profile, &fetch)) && fetch) {
1728 // XXX We're not sure a profile is registered with two or more categories.
1729 if (profile.clsid == aTextService && profile.guidProfile == aProfile &&
1730 profile.catid == GUID_TFCAT_TIP_KEYBOARD) {
1731 return true;
1734 return false;
1737 /******************************************************************/
1738 /* TSFTextStore */
1739 /******************************************************************/
1741 StaticRefPtr<ITfThreadMgr> TSFTextStore::sThreadMgr;
1742 StaticRefPtr<ITfMessagePump> TSFTextStore::sMessagePump;
1743 StaticRefPtr<ITfKeystrokeMgr> TSFTextStore::sKeystrokeMgr;
1744 StaticRefPtr<ITfDisplayAttributeMgr> TSFTextStore::sDisplayAttrMgr;
1745 StaticRefPtr<ITfCategoryMgr> TSFTextStore::sCategoryMgr;
1746 StaticRefPtr<ITfCompartment> TSFTextStore::sCompartmentForOpenClose;
1747 StaticRefPtr<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr;
1748 StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext;
1749 StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles;
1750 StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore;
1751 const MSG* TSFTextStore::sHandlingKeyMsg = nullptr;
1752 DWORD TSFTextStore::sClientId = 0;
1753 bool TSFTextStore::sIsKeyboardEventDispatched = false;
1755 #define TEXTSTORE_DEFAULT_VIEW (1)
1757 TSFTextStore::TSFTextStore()
1758 : mEditCookie(0),
1759 mSinkMask(0),
1760 mLock(0),
1761 mLockQueued(0),
1762 mHandlingKeyMessage(0) {
1763 // We hope that 5 or more actions don't occur at once.
1764 mPendingActions.SetCapacity(5);
1766 MOZ_LOG(gIMELog, LogLevel::Info,
1767 ("0x%p TSFTextStore::TSFTextStore() SUCCEEDED", this));
1770 TSFTextStore::~TSFTextStore() {
1771 MOZ_LOG(gIMELog, LogLevel::Info,
1772 ("0x%p TSFTextStore instance is destroyed", this));
1775 bool TSFTextStore::Init(nsWindow* aWidget, const InputContext& aContext) {
1776 MOZ_LOG(gIMELog, LogLevel::Info,
1777 ("0x%p TSFTextStore::Init(aWidget=0x%p)", this, aWidget));
1779 if (NS_WARN_IF(!aWidget) || NS_WARN_IF(aWidget->Destroyed())) {
1780 MOZ_LOG(gIMELog, LogLevel::Error,
1781 ("0x%p TSFTextStore::Init() FAILED due to being initialized with "
1782 "destroyed widget",
1783 this));
1784 return false;
1787 if (mDocumentMgr) {
1788 MOZ_LOG(gIMELog, LogLevel::Error,
1789 ("0x%p TSFTextStore::Init() FAILED due to already initialized",
1790 this));
1791 return false;
1794 mWidget = aWidget;
1795 if (NS_WARN_IF(!mWidget)) {
1796 MOZ_LOG(gIMELog, LogLevel::Error,
1797 ("0x%p TSFTextStore::Init() FAILED "
1798 "due to aWidget is nullptr ",
1799 this));
1800 return false;
1802 mDispatcher = mWidget->GetTextEventDispatcher();
1803 if (NS_WARN_IF(!mDispatcher)) {
1804 MOZ_LOG(gIMELog, LogLevel::Error,
1805 ("0x%p TSFTextStore::Init() FAILED "
1806 "due to aWidget->GetTextEventDispatcher() failure",
1807 this));
1808 return false;
1811 mInPrivateBrowsing = aContext.mInPrivateBrowsing;
1812 SetInputScope(aContext.mHTMLInputType, aContext.mHTMLInputMode);
1814 if (aContext.mURI) {
1815 // We don't need the document URL if it fails, let's ignore the error.
1816 nsAutoCString spec;
1817 if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) {
1818 CopyUTF8toUTF16(spec, mDocumentURL);
1822 // Create document manager
1823 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
1824 RefPtr<ITfDocumentMgr> documentMgr;
1825 HRESULT hr = threadMgr->CreateDocumentMgr(getter_AddRefs(documentMgr));
1826 if (NS_WARN_IF(FAILED(hr))) {
1827 MOZ_LOG(gIMELog, LogLevel::Error,
1828 ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr "
1829 "(0x%08lX)",
1830 this, hr));
1831 return false;
1833 if (NS_WARN_IF(mDestroyed)) {
1834 MOZ_LOG(
1835 gIMELog, LogLevel::Error,
1836 ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr due to "
1837 "TextStore being destroyed during calling "
1838 "ITfThreadMgr::CreateDocumentMgr()",
1839 this));
1840 return false;
1842 // Create context and add it to document manager
1843 RefPtr<ITfContext> context;
1844 hr = documentMgr->CreateContext(sClientId, 0,
1845 static_cast<ITextStoreACP*>(this),
1846 getter_AddRefs(context), &mEditCookie);
1847 if (NS_WARN_IF(FAILED(hr))) {
1848 MOZ_LOG(gIMELog, LogLevel::Error,
1849 ("0x%p TSFTextStore::Init() FAILED to create the context "
1850 "(0x%08lX)",
1851 this, hr));
1852 return false;
1854 if (NS_WARN_IF(mDestroyed)) {
1855 MOZ_LOG(gIMELog, LogLevel::Error,
1856 ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
1857 "TextStore being destroyed during calling "
1858 "ITfDocumentMgr::CreateContext()",
1859 this));
1860 return false;
1863 hr = documentMgr->Push(context);
1864 if (NS_WARN_IF(FAILED(hr))) {
1865 MOZ_LOG(gIMELog, LogLevel::Error,
1866 ("0x%p TSFTextStore::Init() FAILED to push the context (0x%08lX)",
1867 this, hr));
1868 return false;
1870 if (NS_WARN_IF(mDestroyed)) {
1871 MOZ_LOG(gIMELog, LogLevel::Error,
1872 ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
1873 "TextStore being destroyed during calling ITfDocumentMgr::Push()",
1874 this));
1875 documentMgr->Pop(TF_POPF_ALL);
1876 return false;
1879 mDocumentMgr = documentMgr;
1880 mContext = context;
1882 MOZ_LOG(gIMELog, LogLevel::Info,
1883 ("0x%p TSFTextStore::Init() succeeded: "
1884 "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08lX",
1885 this, mDocumentMgr.get(), mContext.get(), mEditCookie));
1887 return true;
1890 void TSFTextStore::Destroy() {
1891 if (mBeingDestroyed) {
1892 return;
1895 MOZ_LOG(gIMELog, LogLevel::Info,
1896 ("0x%p TSFTextStore::Destroy(), mLock=%s, "
1897 "mComposition=%s, mHandlingKeyMessage=%u",
1898 this, GetLockFlagNameStr(mLock).get(),
1899 ToString(mComposition).c_str(), mHandlingKeyMessage));
1901 mDestroyed = true;
1903 // Destroy native caret first because it's not directly related to TSF and
1904 // there may be another textstore which gets focus. So, we should avoid
1905 // to destroy caret after the new one recreates caret.
1906 IMEHandler::MaybeDestroyNativeCaret();
1908 if (mLock) {
1909 mPendingDestroy = true;
1910 return;
1913 AutoRestore<bool> savedBeingDestroyed(mBeingDestroyed);
1914 mBeingDestroyed = true;
1916 // If there is composition, TSF keeps the composition even after the text
1917 // store destroyed. So, we should clear the composition here.
1918 if (mComposition.isSome()) {
1919 CommitCompositionInternal(false);
1922 if (mSink) {
1923 MOZ_LOG(gIMELog, LogLevel::Debug,
1924 ("0x%p TSFTextStore::Destroy(), calling "
1925 "ITextStoreACPSink::OnLayoutChange(TS_LC_DESTROY)...",
1926 this));
1927 RefPtr<ITextStoreACPSink> sink = mSink;
1928 sink->OnLayoutChange(TS_LC_DESTROY, TEXTSTORE_DEFAULT_VIEW);
1931 // If this is called during handling a keydown or keyup message, we should
1932 // put off to release TSF objects until it completely finishes since
1933 // MS-IME for Japanese refers some objects without grabbing them.
1934 if (!mHandlingKeyMessage) {
1935 ReleaseTSFObjects();
1938 MOZ_LOG(gIMELog, LogLevel::Info,
1939 ("0x%p TSFTextStore::Destroy() succeeded", this));
1942 void TSFTextStore::ReleaseTSFObjects() {
1943 MOZ_ASSERT(!mHandlingKeyMessage);
1945 MOZ_LOG(gIMELog, LogLevel::Info,
1946 ("0x%p TSFTextStore::ReleaseTSFObjects()", this));
1948 mDocumentURL.Truncate();
1949 mContext = nullptr;
1950 if (mDocumentMgr) {
1951 RefPtr<ITfDocumentMgr> documentMgr = mDocumentMgr.forget();
1952 documentMgr->Pop(TF_POPF_ALL);
1954 mSink = nullptr;
1955 mWidget = nullptr;
1956 mDispatcher = nullptr;
1958 if (!mMouseTrackers.IsEmpty()) {
1959 MOZ_LOG(gIMELog, LogLevel::Debug,
1960 ("0x%p TSFTextStore::ReleaseTSFObjects(), "
1961 "removing a mouse tracker...",
1962 this));
1963 mMouseTrackers.Clear();
1966 MOZ_LOG(gIMELog, LogLevel::Debug,
1967 ("0x%p TSFTextStore::ReleaseTSFObjects() completed", this));
1970 STDMETHODIMP
1971 TSFTextStore::QueryInterface(REFIID riid, void** ppv) {
1972 *ppv = nullptr;
1973 if ((IID_IUnknown == riid) || (IID_ITextStoreACP == riid)) {
1974 *ppv = static_cast<ITextStoreACP*>(this);
1975 } else if (IID_ITfContextOwnerCompositionSink == riid) {
1976 *ppv = static_cast<ITfContextOwnerCompositionSink*>(this);
1977 } else if (IID_ITfMouseTrackerACP == riid) {
1978 *ppv = static_cast<ITfMouseTrackerACP*>(this);
1980 if (*ppv) {
1981 AddRef();
1982 return S_OK;
1985 MOZ_LOG(gIMELog, LogLevel::Error,
1986 ("0x%p TSFTextStore::QueryInterface() FAILED, riid=%s", this,
1987 GetRIIDNameStr(riid).get()));
1988 return E_NOINTERFACE;
1991 STDMETHODIMP
1992 TSFTextStore::AdviseSink(REFIID riid, IUnknown* punk, DWORD dwMask) {
1993 MOZ_LOG(
1994 gIMELog, LogLevel::Info,
1995 ("0x%p TSFTextStore::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), "
1996 "mSink=0x%p, mSinkMask=%s",
1997 this, GetRIIDNameStr(riid).get(), punk, GetSinkMaskNameStr(dwMask).get(),
1998 mSink.get(), GetSinkMaskNameStr(mSinkMask).get()));
2000 if (!punk) {
2001 MOZ_LOG(gIMELog, LogLevel::Error,
2002 ("0x%p TSFTextStore::AdviseSink() FAILED due to the null punk",
2003 this));
2004 return E_UNEXPECTED;
2007 if (IID_ITextStoreACPSink != riid) {
2008 MOZ_LOG(gIMELog, LogLevel::Error,
2009 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2010 "unsupported interface",
2011 this));
2012 return E_INVALIDARG; // means unsupported interface.
2015 if (!mSink) {
2016 // Install sink
2017 punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink));
2018 if (!mSink) {
2019 MOZ_LOG(gIMELog, LogLevel::Error,
2020 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2021 "punk not having the interface",
2022 this));
2023 return E_UNEXPECTED;
2025 } else {
2026 // If sink is already installed we check to see if they are the same
2027 // Get IUnknown from both sides for comparison
2028 RefPtr<IUnknown> comparison1, comparison2;
2029 punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
2030 mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
2031 if (comparison1 != comparison2) {
2032 MOZ_LOG(gIMELog, LogLevel::Error,
2033 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2034 "the sink being different from the stored sink",
2035 this));
2036 return CONNECT_E_ADVISELIMIT;
2039 // Update mask either for a new sink or an existing sink
2040 mSinkMask = dwMask;
2041 return S_OK;
2044 STDMETHODIMP
2045 TSFTextStore::UnadviseSink(IUnknown* punk) {
2046 MOZ_LOG(gIMELog, LogLevel::Info,
2047 ("0x%p TSFTextStore::UnadviseSink(punk=0x%p), mSink=0x%p", this, punk,
2048 mSink.get()));
2050 if (!punk) {
2051 MOZ_LOG(gIMELog, LogLevel::Error,
2052 ("0x%p TSFTextStore::UnadviseSink() FAILED due to the null punk",
2053 this));
2054 return E_INVALIDARG;
2056 if (!mSink) {
2057 MOZ_LOG(gIMELog, LogLevel::Error,
2058 ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
2059 "any sink not stored",
2060 this));
2061 return CONNECT_E_NOCONNECTION;
2063 // Get IUnknown from both sides for comparison
2064 RefPtr<IUnknown> comparison1, comparison2;
2065 punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
2066 mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
2067 // Unadvise only if sinks are the same
2068 if (comparison1 != comparison2) {
2069 MOZ_LOG(gIMELog, LogLevel::Error,
2070 ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
2071 "the sink being different from the stored sink",
2072 this));
2073 return CONNECT_E_NOCONNECTION;
2075 mSink = nullptr;
2076 mSinkMask = 0;
2077 return S_OK;
2080 STDMETHODIMP
2081 TSFTextStore::RequestLock(DWORD dwLockFlags, HRESULT* phrSession) {
2082 MOZ_LOG(gIMELog, LogLevel::Info,
2083 ("0x%p TSFTextStore::RequestLock(dwLockFlags=%s, phrSession=0x%p), "
2084 "mLock=%s, mDestroyed=%s",
2085 this, GetLockFlagNameStr(dwLockFlags).get(), phrSession,
2086 GetLockFlagNameStr(mLock).get(), GetBoolName(mDestroyed)));
2088 if (!mSink) {
2089 MOZ_LOG(gIMELog, LogLevel::Error,
2090 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2091 "any sink not stored",
2092 this));
2093 return E_FAIL;
2095 if (mDestroyed &&
2096 (mContentForTSF.isNothing() || mSelectionForTSF.isNothing())) {
2097 MOZ_LOG(gIMELog, LogLevel::Error,
2098 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2099 "being destroyed and no information of the contents",
2100 this));
2101 return E_FAIL;
2103 if (!phrSession) {
2104 MOZ_LOG(gIMELog, LogLevel::Error,
2105 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2106 "null phrSession",
2107 this));
2108 return E_INVALIDARG;
2111 if (!mLock) {
2112 // put on lock
2113 mLock = dwLockFlags & (~TS_LF_SYNC);
2114 MOZ_LOG(
2115 gIMELog, LogLevel::Info,
2116 ("0x%p Locking (%s) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
2117 ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
2118 this, GetLockFlagNameStr(mLock).get()));
2119 // Don't release this instance during this lock because this is called by
2120 // TSF but they don't grab us during this call.
2121 RefPtr<TSFTextStore> kungFuDeathGrip(this);
2122 RefPtr<ITextStoreACPSink> sink = mSink;
2123 *phrSession = sink->OnLockGranted(mLock);
2124 MOZ_LOG(
2125 gIMELog, LogLevel::Info,
2126 ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2127 "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
2128 this, GetLockFlagNameStr(mLock).get()));
2129 DidLockGranted();
2130 while (mLockQueued) {
2131 mLock = mLockQueued;
2132 mLockQueued = 0;
2133 MOZ_LOG(gIMELog, LogLevel::Info,
2134 ("0x%p Locking for the request in the queue (%s) >>>>>>>>>>>>>>"
2135 ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
2136 ">>>>>",
2137 this, GetLockFlagNameStr(mLock).get()));
2138 sink->OnLockGranted(mLock);
2139 MOZ_LOG(gIMELog, LogLevel::Info,
2140 ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2141 "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2142 "<<<<<",
2143 this, GetLockFlagNameStr(mLock).get()));
2144 DidLockGranted();
2147 // The document is now completely unlocked.
2148 mLock = 0;
2150 MaybeFlushPendingNotifications();
2152 MOZ_LOG(gIMELog, LogLevel::Info,
2153 ("0x%p TSFTextStore::RequestLock() succeeded: *phrSession=%s",
2154 this, GetTextStoreReturnValueName(*phrSession)));
2155 return S_OK;
2158 // only time when reentrant lock is allowed is when caller holds a
2159 // read-only lock and is requesting an async write lock
2160 if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags) &&
2161 !(dwLockFlags & TS_LF_SYNC)) {
2162 *phrSession = TS_S_ASYNC;
2163 mLockQueued = dwLockFlags & (~TS_LF_SYNC);
2165 MOZ_LOG(gIMELog, LogLevel::Info,
2166 ("0x%p TSFTextStore::RequestLock() stores the request in the "
2167 "queue, *phrSession=TS_S_ASYNC",
2168 this));
2169 return S_OK;
2172 // no more locks allowed
2173 MOZ_LOG(gIMELog, LogLevel::Info,
2174 ("0x%p TSFTextStore::RequestLock() didn't allow to lock, "
2175 "*phrSession=TS_E_SYNCHRONOUS",
2176 this));
2177 *phrSession = TS_E_SYNCHRONOUS;
2178 return E_FAIL;
2181 void TSFTextStore::DidLockGranted() {
2182 if (IsReadWriteLocked()) {
2183 // FreeCJ (TIP for Traditional Chinese) calls SetSelection() to set caret
2184 // to the start of composition string and insert a full width space for
2185 // a placeholder with a call of SetText(). After that, it calls
2186 // OnUpdateComposition() without new range. Therefore, let's record the
2187 // composition update information here.
2188 CompleteLastActionIfStillIncomplete();
2190 FlushPendingActions();
2193 // If the widget has gone, we don't need to notify anything.
2194 if (mDestroyed || !mWidget || mWidget->Destroyed()) {
2195 mPendingSelectionChangeData.reset();
2196 mHasReturnedNoLayoutError = false;
2200 void TSFTextStore::DispatchEvent(WidgetGUIEvent& aEvent) {
2201 if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) {
2202 return;
2204 // If the event isn't a query content event, the event may be handled
2205 // asynchronously. So, we should put off to answer from GetTextExt() etc.
2206 if (!aEvent.AsQueryContentEvent()) {
2207 mDeferNotifyingTSFUntilNextUpdate = true;
2209 mWidget->DispatchWindowEvent(aEvent);
2212 void TSFTextStore::FlushPendingActions() {
2213 if (!mWidget || mWidget->Destroyed()) {
2214 // Note that don't clear mContentForTSF because TIP may try to commit
2215 // composition with a document lock. In such case, TSFTextStore needs to
2216 // behave as expected by TIP.
2217 mPendingActions.Clear();
2218 mPendingSelectionChangeData.reset();
2219 mHasReturnedNoLayoutError = false;
2220 return;
2223 // Some TIP may request lock but does nothing during the lock. In such case,
2224 // this should do nothing. For example, when MS-IME for Japanese is active
2225 // and we're inactivating, this case occurs and causes different behavior
2226 // from the other TIPs.
2227 if (mPendingActions.IsEmpty()) {
2228 return;
2231 RefPtr<nsWindow> widget(mWidget);
2232 nsresult rv = mDispatcher->BeginNativeInputTransaction();
2233 if (NS_WARN_IF(NS_FAILED(rv))) {
2234 MOZ_LOG(gIMELog, LogLevel::Error,
2235 ("0x%p TSFTextStore::FlushPendingActions() "
2236 "FAILED due to BeginNativeInputTransaction() failure",
2237 this));
2238 return;
2240 for (uint32_t i = 0; i < mPendingActions.Length(); i++) {
2241 PendingAction& action = mPendingActions[i];
2242 switch (action.mType) {
2243 case PendingAction::Type::eKeyboardEvent:
2244 if (mDestroyed) {
2245 MOZ_LOG(
2246 gIMELog, LogLevel::Warning,
2247 ("0x%p TSFTextStore::FlushPendingActions() "
2248 "IGNORED pending KeyboardEvent(%s) due to already destroyed",
2249 this,
2250 action.mKeyMsg.message == WM_KEYDOWN ? "eKeyDown" : "eKeyUp"));
2252 MOZ_DIAGNOSTIC_ASSERT(action.mKeyMsg.message == WM_KEYDOWN ||
2253 action.mKeyMsg.message == WM_KEYUP);
2254 DispatchKeyboardEventAsProcessedByIME(action.mKeyMsg);
2255 if (!widget || widget->Destroyed()) {
2256 break;
2258 break;
2259 case PendingAction::Type::eCompositionStart: {
2260 MOZ_LOG(gIMELog, LogLevel::Debug,
2261 ("0x%p TSFTextStore::FlushPendingActions() "
2262 "flushing Type::eCompositionStart={ mSelectionStart=%ld, "
2263 "mSelectionLength=%ld }, mDestroyed=%s",
2264 this, action.mSelectionStart, action.mSelectionLength,
2265 GetBoolName(mDestroyed)));
2267 if (mDestroyed) {
2268 MOZ_LOG(gIMELog, LogLevel::Warning,
2269 ("0x%p TSFTextStore::FlushPendingActions() "
2270 "IGNORED pending compositionstart due to already destroyed",
2271 this));
2272 break;
2275 if (action.mAdjustSelection) {
2276 // Select composition range so the new composition replaces the range
2277 WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
2278 widget->InitEvent(selectionSet);
2279 selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
2280 selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
2281 selectionSet.mReversed = false;
2282 selectionSet.mExpandToClusterBoundary =
2283 TSFStaticSink::ActiveTIP() !=
2284 TextInputProcessorID::eKeymanDesktop &&
2285 StaticPrefs::
2286 intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
2287 DispatchEvent(selectionSet);
2288 if (!selectionSet.mSucceeded) {
2289 MOZ_LOG(gIMELog, LogLevel::Error,
2290 ("0x%p TSFTextStore::FlushPendingActions() "
2291 "FAILED due to eSetSelection failure",
2292 this));
2293 break;
2297 // eCompositionStart always causes
2298 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. Therefore, we should
2299 // wait to clear mContentForTSF until it's notified.
2300 mDeferClearingContentForTSF = true;
2302 MOZ_LOG(gIMELog, LogLevel::Debug,
2303 ("0x%p TSFTextStore::FlushPendingActions() "
2304 "dispatching compositionstart event...",
2305 this));
2306 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2307 nsEventStatus status;
2308 rv = mDispatcher->StartComposition(status, &eventTime);
2309 if (NS_WARN_IF(NS_FAILED(rv))) {
2310 MOZ_LOG(gIMELog, LogLevel::Error,
2311 ("0x%p TSFTextStore::FlushPendingActions() "
2312 "FAILED to dispatch compositionstart event, "
2313 "IsHandlingCompositionInContent()=%s",
2314 this, GetBoolName(IsHandlingCompositionInContent())));
2315 // XXX Is this right? If there is a composition in content,
2316 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2317 mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
2319 if (!widget || widget->Destroyed()) {
2320 break;
2322 break;
2324 case PendingAction::Type::eCompositionUpdate: {
2325 MOZ_LOG(gIMELog, LogLevel::Debug,
2326 ("0x%p TSFTextStore::FlushPendingActions() "
2327 "flushing Type::eCompositionUpdate={ mData=\"%s\", "
2328 "mRanges=0x%p, mRanges->Length()=%zu }",
2329 this, GetEscapedUTF8String(action.mData).get(),
2330 action.mRanges.get(),
2331 action.mRanges ? action.mRanges->Length() : 0));
2333 // eCompositionChange causes a DOM text event, the IME will be notified
2334 // of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. In this case, we
2335 // should not clear mContentForTSF until we notify the IME of the
2336 // composition update.
2337 mDeferClearingContentForTSF = true;
2339 rv = mDispatcher->SetPendingComposition(action.mData, action.mRanges);
2340 if (NS_WARN_IF(NS_FAILED(rv))) {
2341 MOZ_LOG(gIMELog, LogLevel::Error,
2342 ("0x%p TSFTextStore::FlushPendingActions() "
2343 "FAILED to setting pending composition... "
2344 "IsHandlingCompositionInContent()=%s",
2345 this, GetBoolName(IsHandlingCompositionInContent())));
2346 // XXX Is this right? If there is a composition in content,
2347 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2348 mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
2349 } else {
2350 MOZ_LOG(gIMELog, LogLevel::Debug,
2351 ("0x%p TSFTextStore::FlushPendingActions() "
2352 "dispatching compositionchange event...",
2353 this));
2354 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2355 nsEventStatus status;
2356 rv = mDispatcher->FlushPendingComposition(status, &eventTime);
2357 if (NS_WARN_IF(NS_FAILED(rv))) {
2358 MOZ_LOG(gIMELog, LogLevel::Error,
2359 ("0x%p TSFTextStore::FlushPendingActions() "
2360 "FAILED to dispatch compositionchange event, "
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();
2367 // Be aware, the mWidget might already have been destroyed.
2369 break;
2371 case PendingAction::Type::eCompositionEnd: {
2372 MOZ_LOG(gIMELog, LogLevel::Debug,
2373 ("0x%p TSFTextStore::FlushPendingActions() "
2374 "flushing Type::eCompositionEnd={ mData=\"%s\" }",
2375 this, GetEscapedUTF8String(action.mData).get()));
2377 // Dispatching eCompositionCommit causes a DOM text event, then,
2378 // the IME will be notified of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
2379 // when focused content actually handles the event. For example,
2380 // when focused content is in a remote process, it's sent when
2381 // all dispatched composition events have been handled in the remote
2382 // process. So, until then, we don't have newer content information.
2383 // Therefore, we need to put off to clear mContentForTSF.
2384 mDeferClearingContentForTSF = true;
2386 MOZ_LOG(gIMELog, LogLevel::Debug,
2387 ("0x%p TSFTextStore::FlushPendingActions(), "
2388 "dispatching compositioncommit event...",
2389 this));
2390 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2391 nsEventStatus status;
2392 rv = mDispatcher->CommitComposition(status, &action.mData, &eventTime);
2393 if (NS_WARN_IF(NS_FAILED(rv))) {
2394 MOZ_LOG(gIMELog, LogLevel::Error,
2395 ("0x%p TSFTextStore::FlushPendingActions() "
2396 "FAILED to dispatch compositioncommit event, "
2397 "IsHandlingCompositionInContent()=%s",
2398 this, GetBoolName(IsHandlingCompositionInContent())));
2399 // XXX Is this right? If there is a composition in content,
2400 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2401 mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
2403 break;
2405 case PendingAction::Type::eSetSelection: {
2406 MOZ_LOG(
2407 gIMELog, LogLevel::Debug,
2408 ("0x%p TSFTextStore::FlushPendingActions() "
2409 "flushing Type::eSetSelection={ mSelectionStart=%ld, "
2410 "mSelectionLength=%ld, mSelectionReversed=%s }, "
2411 "mDestroyed=%s",
2412 this, action.mSelectionStart, action.mSelectionLength,
2413 GetBoolName(action.mSelectionReversed), GetBoolName(mDestroyed)));
2415 if (mDestroyed) {
2416 MOZ_LOG(gIMELog, LogLevel::Warning,
2417 ("0x%p TSFTextStore::FlushPendingActions() "
2418 "IGNORED pending selectionset due to already destroyed",
2419 this));
2420 break;
2423 WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
2424 selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
2425 selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
2426 selectionSet.mReversed = action.mSelectionReversed;
2427 selectionSet.mExpandToClusterBoundary =
2428 TSFStaticSink::ActiveTIP() !=
2429 TextInputProcessorID::eKeymanDesktop &&
2430 StaticPrefs::
2431 intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
2432 DispatchEvent(selectionSet);
2433 if (!selectionSet.mSucceeded) {
2434 MOZ_LOG(gIMELog, LogLevel::Error,
2435 ("0x%p TSFTextStore::FlushPendingActions() "
2436 "FAILED due to eSetSelection failure",
2437 this));
2438 break;
2440 break;
2442 default:
2443 MOZ_CRASH("unexpected action type");
2446 if (widget && !widget->Destroyed()) {
2447 continue;
2450 MOZ_LOG(gIMELog, LogLevel::Info,
2451 ("0x%p TSFTextStore::FlushPendingActions(), "
2452 "qutting since the mWidget has gone",
2453 this));
2454 break;
2456 mPendingActions.Clear();
2459 void TSFTextStore::MaybeFlushPendingNotifications() {
2460 if (mDeferNotifyingTSF) {
2461 MOZ_LOG(gIMELog, LogLevel::Debug,
2462 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2463 "putting off flushing pending notifications due to initializing "
2464 "something...",
2465 this));
2466 return;
2469 if (IsReadLocked()) {
2470 MOZ_LOG(gIMELog, LogLevel::Debug,
2471 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2472 "putting off flushing pending notifications due to being the "
2473 "document locked...",
2474 this));
2475 return;
2478 if (mDeferCommittingComposition) {
2479 MOZ_LOG(gIMELog, LogLevel::Info,
2480 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2481 "calling TSFTextStore::CommitCompositionInternal(false)...",
2482 this));
2483 mDeferCommittingComposition = mDeferCancellingComposition = false;
2484 CommitCompositionInternal(false);
2485 } else if (mDeferCancellingComposition) {
2486 MOZ_LOG(gIMELog, LogLevel::Info,
2487 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2488 "calling TSFTextStore::CommitCompositionInternal(true)...",
2489 this));
2490 mDeferCommittingComposition = mDeferCancellingComposition = false;
2491 CommitCompositionInternal(true);
2494 if (mDeferNotifyingTSFUntilNextUpdate) {
2495 MOZ_LOG(gIMELog, LogLevel::Debug,
2496 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2497 "putting off flushing pending notifications due to being "
2498 "dispatching events...",
2499 this));
2500 return;
2503 if (mPendingDestroy) {
2504 Destroy();
2505 return;
2508 if (mDestroyed) {
2509 // If it's already been destroyed completely, this shouldn't notify TSF of
2510 // anything anymore.
2511 MOZ_LOG(gIMELog, LogLevel::Debug,
2512 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2513 "does nothing because this has already destroyed completely...",
2514 this));
2515 return;
2518 if (!mDeferClearingContentForTSF && mContentForTSF.isSome()) {
2519 mContentForTSF.reset();
2520 MOZ_LOG(gIMELog, LogLevel::Debug,
2521 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2522 "mContentForTSF is set to `Nothing`",
2523 this));
2526 // When there is no cached content, we can sync actual contents and TSF/TIP
2527 // expecting contents.
2528 RefPtr<TSFTextStore> kungFuDeathGrip = this;
2529 Unused << kungFuDeathGrip;
2530 if (mContentForTSF.isNothing()) {
2531 if (mPendingTextChangeData.IsValid()) {
2532 MOZ_LOG(gIMELog, LogLevel::Info,
2533 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2534 "calling TSFTextStore::NotifyTSFOfTextChange()...",
2535 this));
2536 NotifyTSFOfTextChange();
2538 if (mPendingSelectionChangeData.isSome()) {
2539 MOZ_LOG(gIMELog, LogLevel::Info,
2540 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2541 "calling TSFTextStore::NotifyTSFOfSelectionChange()...",
2542 this));
2543 NotifyTSFOfSelectionChange();
2547 if (mHasReturnedNoLayoutError) {
2548 MOZ_LOG(gIMELog, LogLevel::Info,
2549 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2550 "calling TSFTextStore::NotifyTSFOfLayoutChange()...",
2551 this));
2552 NotifyTSFOfLayoutChange();
2556 void TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME() {
2557 // If we've already been destroyed, we cannot do anything.
2558 if (mDestroyed) {
2559 MOZ_LOG(
2560 gIMELog, LogLevel::Debug,
2561 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2562 "does nothing because it's already been destroyed",
2563 this));
2564 return;
2567 // If we're not handling key message or we've already dispatched a keyboard
2568 // event for the handling key message, we should do nothing anymore.
2569 if (!sHandlingKeyMsg || sIsKeyboardEventDispatched) {
2570 MOZ_LOG(
2571 gIMELog, LogLevel::Debug,
2572 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2573 "does nothing because not necessary to dispatch keyboard event",
2574 this));
2575 return;
2578 sIsKeyboardEventDispatched = true;
2579 // If the document is locked, just adding the task to dispatching an event
2580 // to the queue.
2581 if (IsReadLocked()) {
2582 MOZ_LOG(
2583 gIMELog, LogLevel::Debug,
2584 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2585 "adding to dispatch a keyboard event into the queue...",
2586 this));
2587 PendingAction* action = mPendingActions.AppendElement();
2588 action->mType = PendingAction::Type::eKeyboardEvent;
2589 memcpy(&action->mKeyMsg, sHandlingKeyMsg, sizeof(MSG));
2590 return;
2593 // Otherwise, dispatch a keyboard event.
2594 MOZ_LOG(gIMELog, LogLevel::Debug,
2595 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2596 "trying to dispatch a keyboard event...",
2597 this));
2598 DispatchKeyboardEventAsProcessedByIME(*sHandlingKeyMsg);
2601 void TSFTextStore::DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg) {
2602 MOZ_ASSERT(mWidget);
2603 MOZ_ASSERT(!mWidget->Destroyed());
2604 MOZ_ASSERT(!mDestroyed);
2606 ModifierKeyState modKeyState;
2607 MSG msg(aMsg);
2608 msg.wParam = VK_PROCESSKEY;
2609 NativeKey nativeKey(mWidget, msg, modKeyState);
2610 switch (aMsg.message) {
2611 case WM_KEYDOWN:
2612 MOZ_LOG(gIMELog, LogLevel::Debug,
2613 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2614 "dispatching an eKeyDown event...",
2615 this));
2616 nativeKey.HandleKeyDownMessage();
2617 break;
2618 case WM_KEYUP:
2619 MOZ_LOG(gIMELog, LogLevel::Debug,
2620 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2621 "dispatching an eKeyUp event...",
2622 this));
2623 nativeKey.HandleKeyUpMessage();
2624 break;
2625 default:
2626 MOZ_LOG(gIMELog, LogLevel::Error,
2627 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2628 "ERROR, it doesn't handle the message",
2629 this));
2630 break;
2634 STDMETHODIMP
2635 TSFTextStore::GetStatus(TS_STATUS* pdcs) {
2636 MOZ_LOG(gIMELog, LogLevel::Info,
2637 ("0x%p TSFTextStore::GetStatus(pdcs=0x%p)", this, pdcs));
2639 if (!pdcs) {
2640 MOZ_LOG(gIMELog, LogLevel::Error,
2641 ("0x%p TSFTextStore::GetStatus() FAILED due to null pdcs", this));
2642 return E_INVALIDARG;
2644 // We manage on-screen keyboard by own.
2645 pdcs->dwDynamicFlags = TS_SD_INPUTPANEMANUALDISPLAYENABLE;
2646 // we use a "flat" text model for TSF support so no hidden text
2647 pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT;
2648 return S_OK;
2651 STDMETHODIMP
2652 TSFTextStore::QueryInsert(LONG acpTestStart, LONG acpTestEnd, ULONG cch,
2653 LONG* pacpResultStart, LONG* pacpResultEnd) {
2654 MOZ_LOG(
2655 gIMELog, LogLevel::Info,
2656 ("0x%p TSFTextStore::QueryInsert(acpTestStart=%ld, "
2657 "acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)",
2658 this, acpTestStart, acpTestEnd, cch, pacpResultStart, pacpResultEnd));
2660 if (!pacpResultStart || !pacpResultEnd) {
2661 MOZ_LOG(gIMELog, LogLevel::Error,
2662 ("0x%p TSFTextStore::QueryInsert() FAILED due to "
2663 "the null argument",
2664 this));
2665 return E_INVALIDARG;
2668 if (acpTestStart < 0 || acpTestStart > acpTestEnd) {
2669 MOZ_LOG(gIMELog, LogLevel::Error,
2670 ("0x%p TSFTextStore::QueryInsert() FAILED due to "
2671 "wrong argument",
2672 this));
2673 return E_INVALIDARG;
2676 // XXX need to adjust to cluster boundary
2677 // Assume we are given good offsets for now
2678 if (IsWin8OrLater() && mComposition.isNothing() &&
2679 ((StaticPrefs::
2680 intl_tsf_hack_ms_traditional_chinese_query_insert_result() &&
2681 TSFStaticSink::IsMSChangJieOrMSQuickActive()) ||
2682 (StaticPrefs::
2683 intl_tsf_hack_ms_simplified_chinese_query_insert_result() &&
2684 TSFStaticSink::IsMSPinyinOrMSWubiActive()))) {
2685 MOZ_LOG(gIMELog, LogLevel::Warning,
2686 ("0x%p TSFTextStore::QueryInsert() WARNING using different "
2687 "result for the TIP",
2688 this));
2689 // Chinese TIPs of Microsoft assume that QueryInsert() returns selected
2690 // range which should be removed.
2691 *pacpResultStart = acpTestStart;
2692 *pacpResultEnd = acpTestEnd;
2693 } else {
2694 *pacpResultStart = acpTestStart;
2695 *pacpResultEnd = acpTestStart + cch;
2698 MOZ_LOG(gIMELog, LogLevel::Info,
2699 ("0x%p TSFTextStore::QueryInsert() succeeded: "
2700 "*pacpResultStart=%ld, *pacpResultEnd=%ld)",
2701 this, *pacpResultStart, *pacpResultEnd));
2702 return S_OK;
2705 STDMETHODIMP
2706 TSFTextStore::GetSelection(ULONG ulIndex, ULONG ulCount,
2707 TS_SELECTION_ACP* pSelection, ULONG* pcFetched) {
2708 MOZ_LOG(gIMELog, LogLevel::Info,
2709 ("0x%p TSFTextStore::GetSelection(ulIndex=%lu, ulCount=%lu, "
2710 "pSelection=0x%p, pcFetched=0x%p)",
2711 this, ulIndex, ulCount, pSelection, pcFetched));
2713 if (!IsReadLocked()) {
2714 MOZ_LOG(
2715 gIMELog, LogLevel::Error,
2716 ("0x%p TSFTextStore::GetSelection() FAILED due to not locked", this));
2717 return TS_E_NOLOCK;
2719 if (!ulCount || !pSelection || !pcFetched) {
2720 MOZ_LOG(gIMELog, LogLevel::Error,
2721 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2722 "null argument",
2723 this));
2724 return E_INVALIDARG;
2727 *pcFetched = 0;
2729 if (ulIndex != static_cast<ULONG>(TS_DEFAULT_SELECTION) && ulIndex != 0) {
2730 MOZ_LOG(gIMELog, LogLevel::Error,
2731 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2732 "unsupported selection",
2733 this));
2734 return TS_E_NOSELECTION;
2737 Maybe<Selection>& selectionForTSF = SelectionForTSF();
2738 if (selectionForTSF.isNothing()) {
2739 if (DoNotReturnErrorFromGetSelection()) {
2740 *pSelection = Selection::EmptyACP();
2741 *pcFetched = 1;
2742 MOZ_LOG(
2743 gIMELog, LogLevel::Info,
2744 ("0x%p TSFTextStore::GetSelection() returns fake selection range "
2745 "for avoiding a crash in TSF, *pSelection=%s",
2746 this, mozilla::ToString(*pSelection).c_str()));
2747 return S_OK;
2749 MOZ_LOG(gIMELog, LogLevel::Error,
2750 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2751 "SelectionForTSF() failure",
2752 this));
2753 return E_FAIL;
2755 if (!selectionForTSF->HasRange()) {
2756 *pSelection = Selection::EmptyACP();
2757 *pcFetched = 0;
2758 return TS_E_NOSELECTION;
2760 *pSelection = selectionForTSF->ACPRef();
2761 *pcFetched = 1;
2762 MOZ_LOG(gIMELog, LogLevel::Info,
2763 ("0x%p TSFTextStore::GetSelection() succeeded, *pSelection=%s",
2764 this, mozilla::ToString(*pSelection).c_str()));
2765 return S_OK;
2768 // static
2769 bool TSFTextStore::DoNotReturnErrorFromGetSelection() {
2770 // There is a crash bug of TSF if we return error from GetSelection().
2771 // That was introduced in Anniversary Update (build 14393, see bug 1312302)
2772 // TODO: We should avoid to run this hack on fixed builds. When we get
2773 // exact build number, we should get back here.
2774 static bool sTSFMayCrashIfGetSelectionReturnsError =
2775 IsWin10AnniversaryUpdateOrLater();
2776 return sTSFMayCrashIfGetSelectionReturnsError;
2779 Maybe<TSFTextStore::Content>& TSFTextStore::ContentForTSF() {
2780 // This should be called when the document is locked or the content hasn't
2781 // been abandoned yet.
2782 if (NS_WARN_IF(!IsReadLocked() && mContentForTSF.isNothing())) {
2783 MOZ_LOG(gIMELog, LogLevel::Error,
2784 ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
2785 "called wrong timing, IsReadLocked()=%s, mContentForTSF=Nothing",
2786 this, GetBoolName(IsReadLocked())));
2787 return mContentForTSF;
2790 Maybe<Selection>& selectionForTSF = SelectionForTSF();
2791 if (selectionForTSF.isNothing()) {
2792 MOZ_LOG(gIMELog, LogLevel::Error,
2793 ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
2794 "SelectionForTSF() failure",
2795 this));
2796 mContentForTSF.reset();
2797 return mContentForTSF;
2800 if (mContentForTSF.isNothing()) {
2801 AutoNotifyingTSFBatch deferNotifyingTSF(*this);
2803 nsString text; // Don't use auto string for avoiding to copy long string.
2804 if (NS_WARN_IF(!GetCurrentText(text))) {
2805 MOZ_LOG(gIMELog, LogLevel::Error,
2806 ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
2807 "GetCurrentText() failure",
2808 this));
2809 return mContentForTSF;
2812 MOZ_DIAGNOSTIC_ASSERT(mContentForTSF.isNothing(),
2813 "How was it initialized recursively?");
2814 mContentForTSF.reset(); // For avoiding crash in release channel
2815 mContentForTSF.emplace(*this, text);
2816 // Basically, the cached content which is expected by TSF/TIP should be
2817 // cleared after active composition is committed or the document lock is
2818 // unlocked. However, in e10s mode, content will be modified
2819 // asynchronously. In such case, mDeferClearingContentForTSF may be
2820 // true until whole dispatched events are handled by the focused editor.
2821 mDeferClearingContentForTSF = false;
2824 MOZ_LOG(gIMELog, LogLevel::Debug,
2825 ("0x%p TSFTextStore::ContentForTSF(): mContentForTSF=%s", this,
2826 mozilla::ToString(mContentForTSF).c_str()));
2828 return mContentForTSF;
2831 bool TSFTextStore::CanAccessActualContentDirectly() const {
2832 if (mContentForTSF.isNothing() || mSelectionForTSF.isNothing()) {
2833 return true;
2836 // If the cached content has been changed by something except composition,
2837 // the content cache may be different from actual content.
2838 if (mPendingTextChangeData.IsValid() &&
2839 !mPendingTextChangeData.mCausedOnlyByComposition) {
2840 return false;
2843 // If the cached selection isn't changed, cached content and actual content
2844 // should be same.
2845 if (mPendingSelectionChangeData.isNothing()) {
2846 return true;
2849 return mSelectionForTSF->EqualsExceptDirection(*mPendingSelectionChangeData);
2852 bool TSFTextStore::GetCurrentText(nsAString& aTextContent) {
2853 if (mContentForTSF.isSome()) {
2854 aTextContent = mContentForTSF->TextRef();
2855 return true;
2858 MOZ_ASSERT(!mDestroyed);
2859 MOZ_ASSERT(mWidget && !mWidget->Destroyed());
2861 MOZ_LOG(gIMELog, LogLevel::Debug,
2862 ("0x%p TSFTextStore::GetCurrentText(): "
2863 "retrieving text from the content...",
2864 this));
2866 WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
2867 mWidget);
2868 queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
2869 mWidget->InitEvent(queryTextContentEvent);
2870 DispatchEvent(queryTextContentEvent);
2871 if (NS_WARN_IF(queryTextContentEvent.Failed())) {
2872 MOZ_LOG(gIMELog, LogLevel::Error,
2873 ("0x%p TSFTextStore::GetCurrentText(), FAILED, due to "
2874 "eQueryTextContent failure",
2875 this));
2876 aTextContent.Truncate();
2877 return false;
2880 aTextContent = queryTextContentEvent.mReply->DataRef();
2881 return true;
2884 Maybe<TSFTextStore::Selection>& TSFTextStore::SelectionForTSF() {
2885 if (mSelectionForTSF.isNothing()) {
2886 MOZ_ASSERT(!mDestroyed);
2887 // If the window has never been available, we should crash since working
2888 // with broken values may make TIP confused.
2889 if (!mWidget || mWidget->Destroyed()) {
2890 MOZ_ASSERT_UNREACHABLE("There should be non-destroyed widget");
2893 AutoNotifyingTSFBatch deferNotifyingTSF(*this);
2895 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
2896 mWidget);
2897 mWidget->InitEvent(querySelectedTextEvent);
2898 DispatchEvent(querySelectedTextEvent);
2899 if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
2900 return mSelectionForTSF;
2902 MOZ_DIAGNOSTIC_ASSERT(mSelectionForTSF.isNothing(),
2903 "How was it initialized recursively?");
2904 mSelectionForTSF = Some(Selection(querySelectedTextEvent));
2907 MOZ_LOG(gIMELog, LogLevel::Debug,
2908 ("0x%p TSFTextStore::SelectionForTSF() succeeded, "
2909 "mSelectionForTSF=%s",
2910 this, ToString(mSelectionForTSF).c_str()));
2912 return mSelectionForTSF;
2915 static HRESULT GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength) {
2916 RefPtr<ITfRangeACP> rangeACP;
2917 aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP));
2918 NS_ENSURE_TRUE(rangeACP, E_FAIL);
2919 return rangeACP->GetExtent(aStart, aLength);
2922 static TextRangeType GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE& aDisplayAttr) {
2923 switch (aDisplayAttr.bAttr) {
2924 case TF_ATTR_TARGET_CONVERTED:
2925 return TextRangeType::eSelectedClause;
2926 case TF_ATTR_CONVERTED:
2927 return TextRangeType::eConvertedClause;
2928 case TF_ATTR_TARGET_NOTCONVERTED:
2929 return TextRangeType::eSelectedRawClause;
2930 default:
2931 return TextRangeType::eRawClause;
2935 HRESULT
2936 TSFTextStore::GetDisplayAttribute(ITfProperty* aAttrProperty, ITfRange* aRange,
2937 TF_DISPLAYATTRIBUTE* aResult) {
2938 NS_ENSURE_TRUE(aAttrProperty, E_FAIL);
2939 NS_ENSURE_TRUE(aRange, E_FAIL);
2940 NS_ENSURE_TRUE(aResult, E_FAIL);
2942 HRESULT hr;
2944 if (MOZ_LOG_TEST(gIMELog, LogLevel::Debug)) {
2945 LONG start = 0, length = 0;
2946 hr = GetRangeExtent(aRange, &start, &length);
2947 MOZ_LOG(gIMELog, LogLevel::Debug,
2948 ("0x%p TSFTextStore::GetDisplayAttribute(): "
2949 "GetDisplayAttribute range=%ld-%ld (hr=%s)",
2950 this, start - mComposition->StartOffset(),
2951 start - mComposition->StartOffset() + length,
2952 GetCommonReturnValueName(hr)));
2955 VARIANT propValue;
2956 ::VariantInit(&propValue);
2957 hr = aAttrProperty->GetValue(TfEditCookie(mEditCookie), aRange, &propValue);
2958 if (FAILED(hr)) {
2959 MOZ_LOG(gIMELog, LogLevel::Error,
2960 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
2961 "ITfProperty::GetValue() failed",
2962 this));
2963 return hr;
2965 if (VT_I4 != propValue.vt) {
2966 MOZ_LOG(gIMELog, LogLevel::Error,
2967 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
2968 "ITfProperty::GetValue() returns non-VT_I4 value",
2969 this));
2970 ::VariantClear(&propValue);
2971 return E_FAIL;
2974 RefPtr<ITfCategoryMgr> categoryMgr = GetCategoryMgr();
2975 if (NS_WARN_IF(!categoryMgr)) {
2976 return E_FAIL;
2978 GUID guid;
2979 hr = categoryMgr->GetGUID(DWORD(propValue.lVal), &guid);
2980 ::VariantClear(&propValue);
2981 if (FAILED(hr)) {
2982 MOZ_LOG(gIMELog, LogLevel::Error,
2983 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
2984 "ITfCategoryMgr::GetGUID() failed",
2985 this));
2986 return hr;
2989 RefPtr<ITfDisplayAttributeMgr> displayAttrMgr = GetDisplayAttributeMgr();
2990 if (NS_WARN_IF(!displayAttrMgr)) {
2991 return E_FAIL;
2993 RefPtr<ITfDisplayAttributeInfo> info;
2994 hr = displayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info),
2995 nullptr);
2996 if (FAILED(hr) || !info) {
2997 MOZ_LOG(gIMELog, LogLevel::Error,
2998 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
2999 "ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed",
3000 this));
3001 return hr;
3004 hr = info->GetAttributeInfo(aResult);
3005 if (FAILED(hr)) {
3006 MOZ_LOG(gIMELog, LogLevel::Error,
3007 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3008 "ITfDisplayAttributeInfo::GetAttributeInfo() failed",
3009 this));
3010 return hr;
3013 MOZ_LOG(gIMELog, LogLevel::Debug,
3014 ("0x%p TSFTextStore::GetDisplayAttribute() succeeded: "
3015 "Result={ %s }",
3016 this, GetDisplayAttrStr(*aResult).get()));
3017 return S_OK;
3020 HRESULT
3021 TSFTextStore::RestartCompositionIfNecessary(ITfRange* aRangeNew) {
3022 MOZ_LOG(gIMELog, LogLevel::Debug,
3023 ("0x%p TSFTextStore::RestartCompositionIfNecessary("
3024 "aRangeNew=0x%p), mComposition=%s",
3025 this, aRangeNew, ToString(mComposition).c_str()));
3027 if (mComposition.isNothing()) {
3028 MOZ_LOG(gIMELog, LogLevel::Error,
3029 ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
3030 "due to no composition view",
3031 this));
3032 return E_FAIL;
3035 HRESULT hr;
3036 RefPtr<ITfCompositionView> pComposition(mComposition->GetView());
3037 RefPtr<ITfRange> composingRange(aRangeNew);
3038 if (!composingRange) {
3039 hr = pComposition->GetRange(getter_AddRefs(composingRange));
3040 if (FAILED(hr)) {
3041 MOZ_LOG(gIMELog, LogLevel::Error,
3042 ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
3043 "FAILED due to pComposition->GetRange() failure",
3044 this));
3045 return hr;
3049 // Get starting offset of the composition
3050 LONG compStart = 0, compLength = 0;
3051 hr = GetRangeExtent(composingRange, &compStart, &compLength);
3052 if (FAILED(hr)) {
3053 MOZ_LOG(gIMELog, LogLevel::Error,
3054 ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
3055 "due to GetRangeExtent() failure",
3056 this));
3057 return hr;
3060 if (mComposition->StartOffset() == compStart &&
3061 mComposition->Length() == compLength) {
3062 return S_OK;
3065 MOZ_LOG(gIMELog, LogLevel::Debug,
3066 ("0x%p TSFTextStore::RestartCompositionIfNecessary(), "
3067 "restaring composition because of compostion range is changed "
3068 "(range=%ld-%ld, mComposition=%s)",
3069 this, compStart, compStart + compLength,
3070 ToString(mComposition).c_str()));
3072 // If the queried composition length is different from the length
3073 // of our composition string, OnUpdateComposition is being called
3074 // because a part of the original composition was committed.
3075 hr = RestartComposition(*mComposition, pComposition, composingRange);
3076 if (FAILED(hr)) {
3077 MOZ_LOG(gIMELog, LogLevel::Error,
3078 ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
3079 "FAILED due to RestartComposition() failure",
3080 this));
3081 return hr;
3084 MOZ_LOG(
3085 gIMELog, LogLevel::Debug,
3086 ("0x%p TSFTextStore::RestartCompositionIfNecessary() succeeded", this));
3087 return S_OK;
3090 HRESULT TSFTextStore::RestartComposition(Composition& aCurrentComposition,
3091 ITfCompositionView* aCompositionView,
3092 ITfRange* aNewRange) {
3093 Maybe<Selection>& selectionForTSF = SelectionForTSF();
3095 LONG newStart, newLength;
3096 HRESULT hr = GetRangeExtent(aNewRange, &newStart, &newLength);
3097 LONG newEnd = newStart + newLength;
3099 if (selectionForTSF.isNothing()) {
3100 MOZ_LOG(gIMELog, LogLevel::Error,
3101 ("0x%p TSFTextStore::RestartComposition() FAILED "
3102 "due to SelectionForTSF() failure",
3103 this));
3104 return E_FAIL;
3107 if (FAILED(hr)) {
3108 MOZ_LOG(gIMELog, LogLevel::Error,
3109 ("0x%p TSFTextStore::RestartComposition() FAILED "
3110 "due to GetRangeExtent() failure",
3111 this));
3112 return hr;
3115 // If the new range has no overlap with the crrent range, we just commit
3116 // the composition and restart new composition with the new range but
3117 // current selection range should be preserved.
3118 if (newStart >= aCurrentComposition.EndOffset() ||
3119 newEnd <= aCurrentComposition.StartOffset()) {
3120 RecordCompositionEndAction();
3121 RecordCompositionStartAction(aCompositionView, newStart, newLength, true);
3122 return S_OK;
3125 MOZ_LOG(gIMELog, LogLevel::Debug,
3126 ("0x%p TSFTextStore::RestartComposition(aCompositionView=0x%p, "
3127 "aNewRange=0x%p { newStart=%ld, newLength=%ld }), "
3128 "aCurrentComposition=%s, "
3129 "selectionForTSF=%s",
3130 this, aCompositionView, aNewRange, newStart, newLength,
3131 ToString(aCurrentComposition).c_str(),
3132 ToString(selectionForTSF).c_str()));
3134 // If the new range has an overlap with the current one, we should not commit
3135 // the whole current range to avoid creating an odd undo transaction.
3136 // I.e., the overlapped range which is being composed should not appear in
3137 // undo transaction.
3139 // Backup current composition data and selection data.
3140 Composition oldComposition = aCurrentComposition;
3141 Selection oldSelection = *selectionForTSF;
3143 // Commit only the part of composition.
3144 LONG keepComposingStartOffset =
3145 std::max(oldComposition.StartOffset(), newStart);
3146 LONG keepComposingEndOffset = std::min(oldComposition.EndOffset(), newEnd);
3147 MOZ_ASSERT(
3148 keepComposingStartOffset <= keepComposingEndOffset,
3149 "Why keepComposingEndOffset is smaller than keepComposingStartOffset?");
3150 LONG keepComposingLength = keepComposingEndOffset - keepComposingStartOffset;
3151 // Remove the overlapped part from the commit string.
3152 nsAutoString commitString(oldComposition.DataRef());
3153 commitString.Cut(keepComposingStartOffset - oldComposition.StartOffset(),
3154 keepComposingLength);
3155 // Update the composition string.
3156 Maybe<Content>& contentForTSF = ContentForTSF();
3157 if (contentForTSF.isNothing()) {
3158 MOZ_LOG(gIMELog, LogLevel::Error,
3159 ("0x%p TSFTextStore::RestartComposition() FAILED "
3160 "due to ContentForTSF() failure",
3161 this));
3162 return E_FAIL;
3164 contentForTSF->ReplaceTextWith(oldComposition.StartOffset(),
3165 oldComposition.Length(), commitString);
3166 MOZ_ASSERT(mComposition.isSome());
3167 // Record a compositionupdate action for commit the part of composing string.
3168 PendingAction* action = LastOrNewPendingCompositionUpdate();
3169 if (mComposition.isSome()) {
3170 action->mData = mComposition->DataRef();
3172 action->mRanges->Clear();
3173 // Note that we shouldn't append ranges when composition string
3174 // is empty because it may cause TextComposition confused.
3175 if (!action->mData.IsEmpty()) {
3176 TextRange caretRange;
3177 caretRange.mStartOffset = caretRange.mEndOffset = static_cast<uint32_t>(
3178 oldComposition.StartOffset() + commitString.Length());
3179 caretRange.mRangeType = TextRangeType::eCaret;
3180 action->mRanges->AppendElement(caretRange);
3182 action->mIncomplete = false;
3184 // Record compositionend action.
3185 RecordCompositionEndAction();
3187 // Record compositionstart action only with the new start since this method
3188 // hasn't restored composing string yet.
3189 RecordCompositionStartAction(aCompositionView, newStart, 0, false);
3191 // Restore the latest text content and selection.
3192 contentForTSF->ReplaceSelectedTextWith(nsDependentSubstring(
3193 oldComposition.DataRef(),
3194 keepComposingStartOffset - oldComposition.StartOffset(),
3195 keepComposingLength));
3196 selectionForTSF = Some(oldSelection);
3198 MOZ_LOG(gIMELog, LogLevel::Debug,
3199 ("0x%p TSFTextStore::RestartComposition() succeeded, "
3200 "mComposition=%s, selectionForTSF=%s",
3201 this, ToString(mComposition).c_str(),
3202 ToString(selectionForTSF).c_str()));
3204 return S_OK;
3207 static bool GetColor(const TF_DA_COLOR& aTSFColor, nscolor& aResult) {
3208 switch (aTSFColor.type) {
3209 case TF_CT_SYSCOLOR: {
3210 DWORD sysColor = ::GetSysColor(aTSFColor.nIndex);
3211 aResult =
3212 NS_RGB(GetRValue(sysColor), GetGValue(sysColor), GetBValue(sysColor));
3213 return true;
3215 case TF_CT_COLORREF:
3216 aResult = NS_RGB(GetRValue(aTSFColor.cr), GetGValue(aTSFColor.cr),
3217 GetBValue(aTSFColor.cr));
3218 return true;
3219 case TF_CT_NONE:
3220 default:
3221 return false;
3225 static bool GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle,
3226 TextRangeStyle::LineStyle& aTextRangeLineStyle) {
3227 switch (aTSFLineStyle) {
3228 case TF_LS_NONE:
3229 aTextRangeLineStyle = TextRangeStyle::LineStyle::None;
3230 return true;
3231 case TF_LS_SOLID:
3232 aTextRangeLineStyle = TextRangeStyle::LineStyle::Solid;
3233 return true;
3234 case TF_LS_DOT:
3235 aTextRangeLineStyle = TextRangeStyle::LineStyle::Dotted;
3236 return true;
3237 case TF_LS_DASH:
3238 aTextRangeLineStyle = TextRangeStyle::LineStyle::Dashed;
3239 return true;
3240 case TF_LS_SQUIGGLE:
3241 aTextRangeLineStyle = TextRangeStyle::LineStyle::Wavy;
3242 return true;
3243 default:
3244 return false;
3248 HRESULT
3249 TSFTextStore::RecordCompositionUpdateAction() {
3250 MOZ_LOG(
3251 gIMELog, LogLevel::Debug,
3252 ("0x%p TSFTextStore::RecordCompositionUpdateAction(), mComposition=%s",
3253 this, ToString(mComposition).c_str()));
3255 if (mComposition.isNothing()) {
3256 MOZ_LOG(gIMELog, LogLevel::Error,
3257 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3258 "due to no composition view",
3259 this));
3260 return E_FAIL;
3263 // Getting display attributes is *really* complicated!
3264 // We first get the context and the property objects to query for
3265 // attributes, but since a big range can have a variety of values for
3266 // the attribute, we have to find out all the ranges that have distinct
3267 // attribute values. Then we query for what the value represents through
3268 // the display attribute manager and translate that to TextRange to be
3269 // sent in eCompositionChange
3271 RefPtr<ITfProperty> attrPropetry;
3272 HRESULT hr =
3273 mContext->GetProperty(GUID_PROP_ATTRIBUTE, getter_AddRefs(attrPropetry));
3274 if (FAILED(hr) || !attrPropetry) {
3275 MOZ_LOG(gIMELog, LogLevel::Error,
3276 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3277 "due to mContext->GetProperty() failure",
3278 this));
3279 return FAILED(hr) ? hr : E_FAIL;
3282 RefPtr<ITfRange> composingRange;
3283 hr = mComposition->GetView()->GetRange(getter_AddRefs(composingRange));
3284 if (FAILED(hr)) {
3285 MOZ_LOG(gIMELog, LogLevel::Error,
3286 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3287 "FAILED due to mComposition->GetView()->GetRange() failure",
3288 this));
3289 return hr;
3292 RefPtr<IEnumTfRanges> enumRanges;
3293 hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie),
3294 getter_AddRefs(enumRanges), composingRange);
3295 if (FAILED(hr) || !enumRanges) {
3296 MOZ_LOG(gIMELog, LogLevel::Error,
3297 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3298 "due to attrPropetry->EnumRanges() failure",
3299 this));
3300 return FAILED(hr) ? hr : E_FAIL;
3303 // First, put the log of content and selection here.
3304 Maybe<Selection>& selectionForTSF = SelectionForTSF();
3305 if (selectionForTSF.isNothing()) {
3306 MOZ_LOG(gIMELog, LogLevel::Error,
3307 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3308 "due to SelectionForTSF() failure",
3309 this));
3310 return E_FAIL;
3313 PendingAction* action = LastOrNewPendingCompositionUpdate();
3314 action->mData = mComposition->DataRef();
3315 // The ranges might already have been initialized, however, if this is
3316 // called again, that means we need to overwrite the ranges with current
3317 // information.
3318 action->mRanges->Clear();
3320 // Note that we shouldn't append ranges when composition string
3321 // is empty because it may cause TextComposition confused.
3322 if (!action->mData.IsEmpty()) {
3323 TextRange newRange;
3324 // No matter if we have display attribute info or not,
3325 // we always pass in at least one range to eCompositionChange
3326 newRange.mStartOffset = 0;
3327 newRange.mEndOffset = action->mData.Length();
3328 newRange.mRangeType = TextRangeType::eRawClause;
3329 action->mRanges->AppendElement(newRange);
3331 RefPtr<ITfRange> range;
3332 while (enumRanges->Next(1, getter_AddRefs(range), nullptr) == S_OK) {
3333 if (NS_WARN_IF(!range)) {
3334 break;
3337 LONG rangeStart = 0, rangeLength = 0;
3338 if (FAILED(GetRangeExtent(range, &rangeStart, &rangeLength))) {
3339 continue;
3341 // The range may include out of composition string. We should ignore
3342 // outside of the composition string.
3343 LONG start = std::min(std::max(rangeStart, mComposition->StartOffset()),
3344 mComposition->EndOffset());
3345 LONG end = std::max(
3346 std::min(rangeStart + rangeLength, mComposition->EndOffset()),
3347 mComposition->StartOffset());
3348 LONG length = end - start;
3349 if (length < 0) {
3350 MOZ_LOG(gIMELog, LogLevel::Error,
3351 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3352 "ignores invalid range (%ld-%ld)",
3353 this, rangeStart - mComposition->StartOffset(),
3354 rangeStart - mComposition->StartOffset() + rangeLength));
3355 continue;
3357 if (!length) {
3358 MOZ_LOG(gIMELog, LogLevel::Debug,
3359 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3360 "ignores a range due to outside of the composition or empty "
3361 "(%ld-%ld)",
3362 this, rangeStart - mComposition->StartOffset(),
3363 rangeStart - mComposition->StartOffset() + rangeLength));
3364 continue;
3367 TextRange newRange;
3368 newRange.mStartOffset =
3369 static_cast<uint32_t>(start - mComposition->StartOffset());
3370 // The end of the last range in the array is
3371 // always kept at the end of composition
3372 newRange.mEndOffset = mComposition->Length();
3374 TF_DISPLAYATTRIBUTE attr;
3375 hr = GetDisplayAttribute(attrPropetry, range, &attr);
3376 if (FAILED(hr)) {
3377 newRange.mRangeType = TextRangeType::eRawClause;
3378 } else {
3379 newRange.mRangeType = GetGeckoSelectionValue(attr);
3380 if (GetColor(attr.crText, newRange.mRangeStyle.mForegroundColor)) {
3381 newRange.mRangeStyle.mDefinedStyles |=
3382 TextRangeStyle::DEFINED_FOREGROUND_COLOR;
3384 if (GetColor(attr.crBk, newRange.mRangeStyle.mBackgroundColor)) {
3385 newRange.mRangeStyle.mDefinedStyles |=
3386 TextRangeStyle::DEFINED_BACKGROUND_COLOR;
3388 if (GetColor(attr.crLine, newRange.mRangeStyle.mUnderlineColor)) {
3389 newRange.mRangeStyle.mDefinedStyles |=
3390 TextRangeStyle::DEFINED_UNDERLINE_COLOR;
3392 if (GetLineStyle(attr.lsStyle, newRange.mRangeStyle.mLineStyle)) {
3393 newRange.mRangeStyle.mDefinedStyles |=
3394 TextRangeStyle::DEFINED_LINESTYLE;
3395 newRange.mRangeStyle.mIsBoldLine = attr.fBoldLine != 0;
3399 TextRange& lastRange = action->mRanges->LastElement();
3400 if (lastRange.mStartOffset == newRange.mStartOffset) {
3401 // Replace range if last range is the same as this one
3402 // So that ranges don't overlap and confuse the editor
3403 lastRange = newRange;
3404 } else {
3405 lastRange.mEndOffset = newRange.mStartOffset;
3406 action->mRanges->AppendElement(newRange);
3410 // We need to hack for Korean Input System which is Korean standard TIP.
3411 // It sets no change style to IME selection (the selection is always only
3412 // one). So, the composition string looks like normal (or committed)
3413 // string. At this time, current selection range is same as the
3414 // composition string range. Other applications set a wide caret which
3415 // covers the composition string, however, Gecko doesn't support the wide
3416 // caret drawing now (Gecko doesn't support XOR drawing), unfortunately.
3417 // For now, we should change the range style to undefined.
3418 if (!selectionForTSF->Collapsed() && action->mRanges->Length() == 1) {
3419 TextRange& range = action->mRanges->ElementAt(0);
3420 LONG start = selectionForTSF->MinOffset();
3421 LONG end = selectionForTSF->MaxOffset();
3422 if (static_cast<LONG>(range.mStartOffset) ==
3423 start - mComposition->StartOffset() &&
3424 static_cast<LONG>(range.mEndOffset) ==
3425 end - mComposition->StartOffset() &&
3426 range.mRangeStyle.IsNoChangeStyle()) {
3427 range.mRangeStyle.Clear();
3428 // The looks of selected type is better than others.
3429 range.mRangeType = TextRangeType::eSelectedRawClause;
3433 // The caret position has to be collapsed.
3434 uint32_t caretPosition = static_cast<uint32_t>(
3435 selectionForTSF->HasRange()
3436 ? selectionForTSF->MaxOffset() - mComposition->StartOffset()
3437 : mComposition->StartOffset());
3439 // If caret is in the target clause and it doesn't have specific style,
3440 // the target clause will be painted as normal selection range. Since
3441 // caret shouldn't be in selection range on Windows, we shouldn't append
3442 // caret range in such case.
3443 const TextRange* targetClause = action->mRanges->GetTargetClause();
3444 if (!targetClause || targetClause->mRangeStyle.IsDefined() ||
3445 caretPosition < targetClause->mStartOffset ||
3446 caretPosition > targetClause->mEndOffset) {
3447 TextRange caretRange;
3448 caretRange.mStartOffset = caretRange.mEndOffset = caretPosition;
3449 caretRange.mRangeType = TextRangeType::eCaret;
3450 action->mRanges->AppendElement(caretRange);
3454 action->mIncomplete = false;
3456 MOZ_LOG(gIMELog, LogLevel::Info,
3457 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3458 "succeeded",
3459 this));
3461 return S_OK;
3464 HRESULT
3465 TSFTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection,
3466 bool aDispatchCompositionChangeEvent) {
3467 MOZ_LOG(
3468 gIMELog, LogLevel::Debug,
3469 ("0x%p TSFTextStore::SetSelectionInternal(pSelection=%s, "
3470 "aDispatchCompositionChangeEvent=%s), mComposition=%s",
3471 this, pSelection ? mozilla::ToString(*pSelection).c_str() : "nullptr",
3472 GetBoolName(aDispatchCompositionChangeEvent),
3473 ToString(mComposition).c_str()));
3475 MOZ_ASSERT(IsReadWriteLocked());
3477 Maybe<Selection>& selectionForTSF = SelectionForTSF();
3478 if (selectionForTSF.isNothing()) {
3479 MOZ_LOG(gIMELog, LogLevel::Error,
3480 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3481 "SelectionForTSF() failure",
3482 this));
3483 return E_FAIL;
3486 MaybeDispatchKeyboardEventAsProcessedByIME();
3487 if (mDestroyed) {
3488 MOZ_LOG(gIMELog, LogLevel::Error,
3489 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3490 "destroyed during dispatching a keyboard event",
3491 this));
3492 return E_FAIL;
3495 // If actually the range is not changing, we should do nothing.
3496 // Perhaps, we can ignore the difference change because it must not be
3497 // important for following edit.
3498 if (selectionForTSF->EqualsExceptDirection(*pSelection)) {
3499 MOZ_LOG(gIMELog, LogLevel::Warning,
3500 ("0x%p TSFTextStore::SetSelectionInternal() Succeeded but "
3501 "did nothing because the selection range isn't changing",
3502 this));
3503 selectionForTSF->SetSelection(*pSelection);
3504 return S_OK;
3507 if (mComposition.isSome()) {
3508 if (aDispatchCompositionChangeEvent) {
3509 HRESULT hr = RestartCompositionIfNecessary();
3510 if (FAILED(hr)) {
3511 MOZ_LOG(gIMELog, LogLevel::Error,
3512 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3513 "RestartCompositionIfNecessary() failure",
3514 this));
3515 return hr;
3518 if (pSelection->acpStart < mComposition->StartOffset() ||
3519 pSelection->acpEnd > mComposition->EndOffset()) {
3520 MOZ_LOG(gIMELog, LogLevel::Error,
3521 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3522 "the selection being out of the composition string",
3523 this));
3524 return TS_E_INVALIDPOS;
3526 // Emulate selection during compositions
3527 selectionForTSF->SetSelection(*pSelection);
3528 if (aDispatchCompositionChangeEvent) {
3529 HRESULT hr = RecordCompositionUpdateAction();
3530 if (FAILED(hr)) {
3531 MOZ_LOG(gIMELog, LogLevel::Error,
3532 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3533 "RecordCompositionUpdateAction() failure",
3534 this));
3535 return hr;
3538 return S_OK;
3541 TS_SELECTION_ACP selectionInContent(*pSelection);
3543 // If mContentForTSF caches old contents which is now different from
3544 // actual contents, we need some complicated hack here...
3545 // Note that this hack assumes that this is used for reconversion.
3546 if (mContentForTSF.isSome() && mPendingTextChangeData.IsValid() &&
3547 !mPendingTextChangeData.mCausedOnlyByComposition) {
3548 uint32_t startOffset = static_cast<uint32_t>(selectionInContent.acpStart);
3549 uint32_t endOffset = static_cast<uint32_t>(selectionInContent.acpEnd);
3550 if (mPendingTextChangeData.mStartOffset >= endOffset) {
3551 // Setting selection before any changed ranges is fine.
3552 } else if (mPendingTextChangeData.mRemovedEndOffset <= startOffset) {
3553 // Setting selection after removed range is fine with following
3554 // adjustment.
3555 selectionInContent.acpStart += mPendingTextChangeData.Difference();
3556 selectionInContent.acpEnd += mPendingTextChangeData.Difference();
3557 } else if (startOffset == endOffset) {
3558 // Moving caret position may be fine in most cases even if the insertion
3559 // point has already gone but in this case, composition will be inserted
3560 // to unexpected position, though.
3561 // It seems that moving caret into middle of the new text is odd.
3562 // Perhaps, end of it is expected by users in most cases.
3563 selectionInContent.acpStart = mPendingTextChangeData.mAddedEndOffset;
3564 selectionInContent.acpEnd = selectionInContent.acpStart;
3565 } else {
3566 // Otherwise, i.e., setting range has already gone, we cannot set
3567 // selection properly.
3568 MOZ_LOG(gIMELog, LogLevel::Error,
3569 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3570 "there is unknown content change",
3571 this));
3572 return E_FAIL;
3576 CompleteLastActionIfStillIncomplete();
3577 PendingAction* action = mPendingActions.AppendElement();
3578 action->mType = PendingAction::Type::eSetSelection;
3579 action->mSelectionStart = selectionInContent.acpStart;
3580 action->mSelectionLength =
3581 selectionInContent.acpEnd - selectionInContent.acpStart;
3582 action->mSelectionReversed = (selectionInContent.style.ase == TS_AE_START);
3584 // Use TSF specified selection for updating mSelectionForTSF.
3585 selectionForTSF->SetSelection(*pSelection);
3587 return S_OK;
3590 STDMETHODIMP
3591 TSFTextStore::SetSelection(ULONG ulCount, const TS_SELECTION_ACP* pSelection) {
3592 MOZ_LOG(gIMELog, LogLevel::Info,
3593 ("0x%p TSFTextStore::SetSelection(ulCount=%lu, pSelection=%s }), "
3594 "mComposition=%s",
3595 this, ulCount,
3596 pSelection ? mozilla::ToString(pSelection).c_str() : "nullptr",
3597 ToString(mComposition).c_str()));
3599 if (!IsReadWriteLocked()) {
3600 MOZ_LOG(gIMELog, LogLevel::Error,
3601 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3602 "not locked (read-write)",
3603 this));
3604 return TS_E_NOLOCK;
3606 if (ulCount != 1) {
3607 MOZ_LOG(gIMELog, LogLevel::Error,
3608 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3609 "trying setting multiple selection",
3610 this));
3611 return E_INVALIDARG;
3613 if (!pSelection) {
3614 MOZ_LOG(gIMELog, LogLevel::Error,
3615 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3616 "null argument",
3617 this));
3618 return E_INVALIDARG;
3621 HRESULT hr = SetSelectionInternal(pSelection, true);
3622 if (FAILED(hr)) {
3623 MOZ_LOG(gIMELog, LogLevel::Error,
3624 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3625 "SetSelectionInternal() failure",
3626 this));
3627 } else {
3628 MOZ_LOG(gIMELog, LogLevel::Info,
3629 ("0x%p TSFTextStore::SetSelection() succeeded", this));
3631 return hr;
3634 STDMETHODIMP
3635 TSFTextStore::GetText(LONG acpStart, LONG acpEnd, WCHAR* pchPlain,
3636 ULONG cchPlainReq, ULONG* pcchPlainOut,
3637 TS_RUNINFO* prgRunInfo, ULONG ulRunInfoReq,
3638 ULONG* pulRunInfoOut, LONG* pacpNext) {
3639 MOZ_LOG(
3640 gIMELog, LogLevel::Info,
3641 ("0x%p TSFTextStore::GetText(acpStart=%ld, acpEnd=%ld, pchPlain=0x%p, "
3642 "cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, ulRunInfoReq=%lu, "
3643 "pulRunInfoOut=0x%p, pacpNext=0x%p), mComposition=%s",
3644 this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, prgRunInfo,
3645 ulRunInfoReq, pulRunInfoOut, pacpNext, ToString(mComposition).c_str()));
3647 if (!IsReadLocked()) {
3648 MOZ_LOG(gIMELog, LogLevel::Error,
3649 ("0x%p TSFTextStore::GetText() FAILED due to "
3650 "not locked (read)",
3651 this));
3652 return TS_E_NOLOCK;
3655 if (!pcchPlainOut || (!pchPlain && !prgRunInfo) ||
3656 !cchPlainReq != !pchPlain || !ulRunInfoReq != !prgRunInfo) {
3657 MOZ_LOG(gIMELog, LogLevel::Error,
3658 ("0x%p TSFTextStore::GetText() FAILED due to "
3659 "invalid argument",
3660 this));
3661 return E_INVALIDARG;
3664 if (acpStart < 0 || acpEnd < -1 || (acpEnd != -1 && acpStart > acpEnd)) {
3665 MOZ_LOG(gIMELog, LogLevel::Error,
3666 ("0x%p TSFTextStore::GetText() FAILED due to "
3667 "invalid position",
3668 this));
3669 return TS_E_INVALIDPOS;
3672 // Making sure to null-terminate string just to be on the safe side
3673 *pcchPlainOut = 0;
3674 if (pchPlain && cchPlainReq) *pchPlain = 0;
3675 if (pulRunInfoOut) *pulRunInfoOut = 0;
3676 if (pacpNext) *pacpNext = acpStart;
3677 if (prgRunInfo && ulRunInfoReq) {
3678 prgRunInfo->uCount = 0;
3679 prgRunInfo->type = TS_RT_PLAIN;
3682 Maybe<Content>& contentForTSF = ContentForTSF();
3683 if (contentForTSF.isNothing()) {
3684 MOZ_LOG(gIMELog, LogLevel::Error,
3685 ("0x%p TSFTextStore::GetText() FAILED due to "
3686 "ContentForTSF() failure",
3687 this));
3688 return E_FAIL;
3690 if (contentForTSF->TextRef().Length() < static_cast<uint32_t>(acpStart)) {
3691 MOZ_LOG(gIMELog, LogLevel::Error,
3692 ("0x%p TSFTextStore::GetText() FAILED due to "
3693 "acpStart is larger offset than the actual text length",
3694 this));
3695 return TS_E_INVALIDPOS;
3697 if (acpEnd != -1 &&
3698 contentForTSF->TextRef().Length() < static_cast<uint32_t>(acpEnd)) {
3699 MOZ_LOG(gIMELog, LogLevel::Error,
3700 ("0x%p TSFTextStore::GetText() FAILED due to "
3701 "acpEnd is larger offset than the actual text length",
3702 this));
3703 return TS_E_INVALIDPOS;
3705 uint32_t length = (acpEnd == -1) ? contentForTSF->TextRef().Length() -
3706 static_cast<uint32_t>(acpStart)
3707 : static_cast<uint32_t>(acpEnd - acpStart);
3708 if (cchPlainReq && cchPlainReq - 1 < length) {
3709 length = cchPlainReq - 1;
3711 if (length) {
3712 if (pchPlain && cchPlainReq) {
3713 const char16_t* startChar =
3714 contentForTSF->TextRef().BeginReading() + acpStart;
3715 memcpy(pchPlain, startChar, length * sizeof(*pchPlain));
3716 pchPlain[length] = 0;
3717 *pcchPlainOut = length;
3719 if (prgRunInfo && ulRunInfoReq) {
3720 prgRunInfo->uCount = length;
3721 prgRunInfo->type = TS_RT_PLAIN;
3722 if (pulRunInfoOut) *pulRunInfoOut = 1;
3724 if (pacpNext) *pacpNext = acpStart + length;
3727 MOZ_LOG(gIMELog, LogLevel::Info,
3728 ("0x%p TSFTextStore::GetText() succeeded: pcchPlainOut=0x%p, "
3729 "*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, "
3730 "*pacpNext=%ld)",
3731 this, pcchPlainOut, prgRunInfo ? prgRunInfo->uCount : 0,
3732 prgRunInfo ? GetTextRunTypeName(prgRunInfo->type) : "N/A",
3733 pulRunInfoOut ? *pulRunInfoOut : 0, pacpNext ? *pacpNext : 0));
3734 return S_OK;
3737 STDMETHODIMP
3738 TSFTextStore::SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd,
3739 const WCHAR* pchText, ULONG cch, TS_TEXTCHANGE* pChange) {
3740 MOZ_LOG(
3741 gIMELog, LogLevel::Info,
3742 ("0x%p TSFTextStore::SetText(dwFlags=%s, acpStart=%ld, acpEnd=%ld, "
3743 "pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), mComposition=%s",
3744 this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" : "not-specified",
3745 acpStart, acpEnd, pchText,
3746 pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "", cch,
3747 pChange, ToString(mComposition).c_str()));
3749 // Per SDK documentation, and since we don't have better
3750 // ways to do this, this method acts as a helper to
3751 // call SetSelection followed by InsertTextAtSelection
3752 if (!IsReadWriteLocked()) {
3753 MOZ_LOG(gIMELog, LogLevel::Error,
3754 ("0x%p TSFTextStore::SetText() FAILED due to "
3755 "not locked (read)",
3756 this));
3757 return TS_E_NOLOCK;
3760 TS_SELECTION_ACP selection;
3761 selection.acpStart = acpStart;
3762 selection.acpEnd = acpEnd;
3763 selection.style.ase = TS_AE_END;
3764 selection.style.fInterimChar = 0;
3765 // Set selection to desired range
3766 HRESULT hr = SetSelectionInternal(&selection);
3767 if (FAILED(hr)) {
3768 MOZ_LOG(gIMELog, LogLevel::Error,
3769 ("0x%p TSFTextStore::SetText() FAILED due to "
3770 "SetSelectionInternal() failure",
3771 this));
3772 return hr;
3774 // Replace just selected text
3775 if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
3776 pChange)) {
3777 MOZ_LOG(gIMELog, LogLevel::Error,
3778 ("0x%p TSFTextStore::SetText() FAILED due to "
3779 "InsertTextAtSelectionInternal() failure",
3780 this));
3781 return E_FAIL;
3784 MOZ_LOG(gIMELog, LogLevel::Info,
3785 ("0x%p TSFTextStore::SetText() succeeded: pChange={ "
3786 "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
3787 this, pChange ? pChange->acpStart : 0,
3788 pChange ? pChange->acpOldEnd : 0, pChange ? pChange->acpNewEnd : 0));
3789 return S_OK;
3792 STDMETHODIMP
3793 TSFTextStore::GetFormattedText(LONG acpStart, LONG acpEnd,
3794 IDataObject** ppDataObject) {
3795 MOZ_LOG(gIMELog, LogLevel::Info,
3796 ("0x%p TSFTextStore::GetFormattedText() called "
3797 "but not supported (E_NOTIMPL)",
3798 this));
3800 // no support for formatted text
3801 return E_NOTIMPL;
3804 STDMETHODIMP
3805 TSFTextStore::GetEmbedded(LONG acpPos, REFGUID rguidService, REFIID riid,
3806 IUnknown** ppunk) {
3807 MOZ_LOG(gIMELog, LogLevel::Info,
3808 ("0x%p TSFTextStore::GetEmbedded() called "
3809 "but not supported (E_NOTIMPL)",
3810 this));
3812 // embedded objects are not supported
3813 return E_NOTIMPL;
3816 STDMETHODIMP
3817 TSFTextStore::QueryInsertEmbedded(const GUID* pguidService,
3818 const FORMATETC* pFormatEtc,
3819 BOOL* pfInsertable) {
3820 MOZ_LOG(gIMELog, LogLevel::Info,
3821 ("0x%p TSFTextStore::QueryInsertEmbedded() called "
3822 "but not supported, *pfInsertable=FALSE (S_OK)",
3823 this));
3825 // embedded objects are not supported
3826 *pfInsertable = FALSE;
3827 return S_OK;
3830 STDMETHODIMP
3831 TSFTextStore::InsertEmbedded(DWORD dwFlags, LONG acpStart, LONG acpEnd,
3832 IDataObject* pDataObject, TS_TEXTCHANGE* pChange) {
3833 MOZ_LOG(gIMELog, LogLevel::Info,
3834 ("0x%p TSFTextStore::InsertEmbedded() called "
3835 "but not supported (E_NOTIMPL)",
3836 this));
3838 // embedded objects are not supported
3839 return E_NOTIMPL;
3842 // static
3843 bool TSFTextStore::ShouldSetInputScopeOfURLBarToDefault() {
3844 // FYI: Google Japanese Input may be an IMM-IME. If it's installed on
3845 // Win7, it's always IMM-IME. Otherwise, basically, it's a TIP.
3846 // However, if it's installed on Win7 and has not been updated yet
3847 // after the OS is upgraded to Win8 or later, it's still an IMM-IME.
3848 // Therefore, we also need to check with IMMHandler here.
3849 if (!StaticPrefs::intl_ime_hack_set_input_scope_of_url_bar_to_default()) {
3850 return false;
3853 if (IMMHandler::IsGoogleJapaneseInputActive()) {
3854 return true;
3857 switch (TSFStaticSink::ActiveTIP()) {
3858 case TextInputProcessorID::eMicrosoftIMEForJapanese:
3859 case TextInputProcessorID::eGoogleJapaneseInput:
3860 case TextInputProcessorID::eMicrosoftBopomofo:
3861 case TextInputProcessorID::eMicrosoftChangJie:
3862 case TextInputProcessorID::eMicrosoftPhonetic:
3863 case TextInputProcessorID::eMicrosoftQuick:
3864 case TextInputProcessorID::eMicrosoftNewChangJie:
3865 case TextInputProcessorID::eMicrosoftNewPhonetic:
3866 case TextInputProcessorID::eMicrosoftNewQuick:
3867 case TextInputProcessorID::eMicrosoftPinyin:
3868 case TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle:
3869 case TextInputProcessorID::eMicrosoftOldHangul:
3870 case TextInputProcessorID::eMicrosoftWubi:
3871 return true;
3872 case TextInputProcessorID::eMicrosoftIMEForKorean:
3873 return IsWin8OrLater();
3874 default:
3875 return false;
3879 void TSFTextStore::SetInputScope(const nsString& aHTMLInputType,
3880 const nsString& aHTMLInputMode) {
3881 mInputScopes.Clear();
3883 // IME may refer only first input scope, but we will append inputmode's
3884 // input scopes too like Chrome since IME may refer it.
3885 IMEHandler::AppendInputScopeFromType(aHTMLInputType, mInputScopes);
3886 IMEHandler::AppendInputScopeFromInputMode(aHTMLInputMode, mInputScopes);
3888 if (mInPrivateBrowsing) {
3889 mInputScopes.AppendElement(IS_PRIVATE);
3893 int32_t TSFTextStore::GetRequestedAttrIndex(const TS_ATTRID& aAttrID) {
3894 if (IsEqualGUID(aAttrID, GUID_PROP_INPUTSCOPE)) {
3895 return eInputScope;
3897 if (IsEqualGUID(aAttrID, sGUID_PROP_URL)) {
3898 return eDocumentURL;
3900 if (IsEqualGUID(aAttrID, TSATTRID_Text_VerticalWriting)) {
3901 return eTextVerticalWriting;
3903 if (IsEqualGUID(aAttrID, TSATTRID_Text_Orientation)) {
3904 return eTextOrientation;
3906 return eNotSupported;
3909 TS_ATTRID
3910 TSFTextStore::GetAttrID(int32_t aIndex) {
3911 switch (aIndex) {
3912 case eInputScope:
3913 return GUID_PROP_INPUTSCOPE;
3914 case eDocumentURL:
3915 return sGUID_PROP_URL;
3916 case eTextVerticalWriting:
3917 return TSATTRID_Text_VerticalWriting;
3918 case eTextOrientation:
3919 return TSATTRID_Text_Orientation;
3920 default:
3921 MOZ_CRASH("Invalid index? Or not implemented yet?");
3922 return GUID_NULL;
3926 HRESULT
3927 TSFTextStore::HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
3928 const TS_ATTRID* aFilterAttrs) {
3929 MOZ_LOG(gIMELog, LogLevel::Info,
3930 ("0x%p TSFTextStore::HandleRequestAttrs(aFlags=%s, "
3931 "aFilterCount=%lu)",
3932 this, GetFindFlagName(aFlags).get(), aFilterCount));
3934 // This is a little weird! RequestSupportedAttrs gives us advanced notice
3935 // of a support query via RetrieveRequestedAttrs for a specific attribute.
3936 // RetrieveRequestedAttrs needs to return valid data for all attributes we
3937 // support, but the text service will only want the input scope object
3938 // returned in RetrieveRequestedAttrs if the dwFlags passed in here contains
3939 // TS_ATTR_FIND_WANT_VALUE.
3940 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
3941 mRequestedAttrs[i] = false;
3943 mRequestedAttrValues = !!(aFlags & TS_ATTR_FIND_WANT_VALUE);
3945 for (uint32_t i = 0; i < aFilterCount; i++) {
3946 MOZ_LOG(gIMELog, LogLevel::Info,
3947 ("0x%p TSFTextStore::HandleRequestAttrs(), "
3948 "requested attr=%s",
3949 this, GetGUIDNameStrWithTable(aFilterAttrs[i]).get()));
3950 int32_t index = GetRequestedAttrIndex(aFilterAttrs[i]);
3951 if (index != eNotSupported) {
3952 mRequestedAttrs[index] = true;
3955 return S_OK;
3958 STDMETHODIMP
3959 TSFTextStore::RequestSupportedAttrs(DWORD dwFlags, ULONG cFilterAttrs,
3960 const TS_ATTRID* paFilterAttrs) {
3961 MOZ_LOG(gIMELog, LogLevel::Info,
3962 ("0x%p TSFTextStore::RequestSupportedAttrs(dwFlags=%s, "
3963 "cFilterAttrs=%lu)",
3964 this, GetFindFlagName(dwFlags).get(), cFilterAttrs));
3966 return HandleRequestAttrs(dwFlags, cFilterAttrs, paFilterAttrs);
3969 STDMETHODIMP
3970 TSFTextStore::RequestAttrsAtPosition(LONG acpPos, ULONG cFilterAttrs,
3971 const TS_ATTRID* paFilterAttrs,
3972 DWORD dwFlags) {
3973 MOZ_LOG(gIMELog, LogLevel::Info,
3974 ("0x%p TSFTextStore::RequestAttrsAtPosition(acpPos=%ld, "
3975 "cFilterAttrs=%lu, dwFlags=%s)",
3976 this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
3978 return HandleRequestAttrs(dwFlags | TS_ATTR_FIND_WANT_VALUE, cFilterAttrs,
3979 paFilterAttrs);
3982 STDMETHODIMP
3983 TSFTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos,
3984 ULONG cFilterAttrs,
3985 const TS_ATTRID* paFilterAttr,
3986 DWORD dwFlags) {
3987 MOZ_LOG(gIMELog, LogLevel::Info,
3988 ("0x%p TSFTextStore::RequestAttrsTransitioningAtPosition("
3989 "acpPos=%ld, cFilterAttrs=%lu, dwFlags=%s) called but not supported "
3990 "(S_OK)",
3991 this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
3993 // no per character attributes defined
3994 return S_OK;
3997 STDMETHODIMP
3998 TSFTextStore::FindNextAttrTransition(LONG acpStart, LONG acpHalt,
3999 ULONG cFilterAttrs,
4000 const TS_ATTRID* paFilterAttrs,
4001 DWORD dwFlags, LONG* pacpNext,
4002 BOOL* pfFound, LONG* plFoundOffset) {
4003 if (!pacpNext || !pfFound || !plFoundOffset) {
4004 MOZ_LOG(gIMELog, LogLevel::Error,
4005 (" 0x%p TSFTextStore::FindNextAttrTransition() FAILED due to "
4006 "null argument",
4007 this));
4008 return E_INVALIDARG;
4011 MOZ_LOG(gIMELog, LogLevel::Info,
4012 ("0x%p TSFTextStore::FindNextAttrTransition() called "
4013 "but not supported (S_OK)",
4014 this));
4016 // no per character attributes defined
4017 *pacpNext = *plFoundOffset = acpHalt;
4018 *pfFound = FALSE;
4019 return S_OK;
4022 // To test the document URL result, define this to out put it to the stdout
4023 // #define DEBUG_PRINT_DOCUMENT_URL
4025 STDMETHODIMP
4026 TSFTextStore::RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL* paAttrVals,
4027 ULONG* pcFetched) {
4028 if (!pcFetched || !paAttrVals) {
4029 MOZ_LOG(gIMELog, LogLevel::Error,
4030 ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
4031 "null argument",
4032 this));
4033 return E_INVALIDARG;
4036 ULONG expectedCount = 0;
4037 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
4038 if (mRequestedAttrs[i]) {
4039 expectedCount++;
4042 if (ulCount < expectedCount) {
4043 MOZ_LOG(gIMELog, LogLevel::Error,
4044 ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
4045 "not enough count ulCount=%lu, expectedCount=%lu",
4046 this, ulCount, expectedCount));
4047 return E_INVALIDARG;
4050 MOZ_LOG(gIMELog, LogLevel::Info,
4051 ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
4052 "ulCount=%lu, mRequestedAttrValues=%s",
4053 this, ulCount, GetBoolName(mRequestedAttrValues)));
4055 auto GetExposingURL = [&]() -> BSTR {
4056 const bool allowed =
4057 StaticPrefs::intl_tsf_expose_url_allowed() &&
4058 (!mInPrivateBrowsing ||
4059 StaticPrefs::intl_tsf_expose_url_in_private_browsing_allowed());
4060 if (!allowed || mDocumentURL.IsEmpty()) {
4061 BSTR emptyString = ::SysAllocString(L"");
4062 MOZ_ASSERT(
4063 emptyString,
4064 "We need to return valid BSTR pointer to notify TSF of supporting it "
4065 "with a pointer to empty string");
4066 return emptyString;
4068 return ::SysAllocString(mDocumentURL.get());
4071 #ifdef DEBUG_PRINT_DOCUMENT_URL
4073 BSTR exposingURL = GetExposingURL();
4074 printf("TSFTextStore::RetrieveRequestedAttrs: DocumentURL=\"%s\"\n",
4075 NS_ConvertUTF16toUTF8(static_cast<char16ptr_t>(_bstr_t(exposingURL)))
4076 .get());
4077 ::SysFreeString(exposingURL);
4079 #endif // #ifdef DEBUG_PRINT_DOCUMENT_URL
4081 int32_t count = 0;
4082 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
4083 if (!mRequestedAttrs[i]) {
4084 continue;
4086 mRequestedAttrs[i] = false;
4088 TS_ATTRID attrID = GetAttrID(i);
4090 MOZ_LOG(gIMELog, LogLevel::Info,
4091 ("0x%p TSFTextStore::RetrieveRequestedAttrs() for %s", this,
4092 GetGUIDNameStrWithTable(attrID).get()));
4094 paAttrVals[count].idAttr = attrID;
4095 paAttrVals[count].dwOverlapId = 0;
4097 if (!mRequestedAttrValues) {
4098 paAttrVals[count].varValue.vt = VT_EMPTY;
4099 } else {
4100 switch (i) {
4101 case eInputScope: {
4102 paAttrVals[count].varValue.vt = VT_UNKNOWN;
4103 RefPtr<IUnknown> inputScope = new InputScopeImpl(mInputScopes);
4104 paAttrVals[count].varValue.punkVal = inputScope.forget().take();
4105 break;
4107 case eDocumentURL: {
4108 paAttrVals[count].varValue.vt = VT_BSTR;
4109 paAttrVals[count].varValue.bstrVal = GetExposingURL();
4110 break;
4112 case eTextVerticalWriting: {
4113 Maybe<Selection>& selectionForTSF = SelectionForTSF();
4114 paAttrVals[count].varValue.vt = VT_BOOL;
4115 paAttrVals[count].varValue.boolVal =
4116 selectionForTSF.isSome() &&
4117 selectionForTSF->WritingModeRef().IsVertical()
4118 ? VARIANT_TRUE
4119 : VARIANT_FALSE;
4120 break;
4122 case eTextOrientation: {
4123 Maybe<Selection>& selectionForTSF = SelectionForTSF();
4124 paAttrVals[count].varValue.vt = VT_I4;
4125 paAttrVals[count].varValue.lVal =
4126 selectionForTSF.isSome() &&
4127 selectionForTSF->WritingModeRef().IsVertical()
4128 ? 2700
4129 : 0;
4130 break;
4132 default:
4133 MOZ_CRASH("Invalid index? Or not implemented yet?");
4134 break;
4137 count++;
4140 mRequestedAttrValues = false;
4142 if (count) {
4143 *pcFetched = count;
4144 return S_OK;
4147 MOZ_LOG(gIMELog, LogLevel::Info,
4148 ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
4149 "for unknown TS_ATTRVAL, *pcFetched=0 (S_OK)",
4150 this));
4152 paAttrVals->dwOverlapId = 0;
4153 paAttrVals->varValue.vt = VT_EMPTY;
4154 *pcFetched = 0;
4155 return S_OK;
4158 #undef DEBUG_PRINT_DOCUMENT_URL
4160 STDMETHODIMP
4161 TSFTextStore::GetEndACP(LONG* pacp) {
4162 MOZ_LOG(gIMELog, LogLevel::Info,
4163 ("0x%p TSFTextStore::GetEndACP(pacp=0x%p)", this, pacp));
4165 if (!IsReadLocked()) {
4166 MOZ_LOG(gIMELog, LogLevel::Error,
4167 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4168 "not locked (read)",
4169 this));
4170 return TS_E_NOLOCK;
4173 if (!pacp) {
4174 MOZ_LOG(gIMELog, LogLevel::Error,
4175 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4176 "null argument",
4177 this));
4178 return E_INVALIDARG;
4181 Maybe<Content>& contentForTSF = ContentForTSF();
4182 if (contentForTSF.isNothing()) {
4183 MOZ_LOG(gIMELog, LogLevel::Error,
4184 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4185 "ContentForTSF() failure",
4186 this));
4187 return E_FAIL;
4189 *pacp = static_cast<LONG>(contentForTSF->TextRef().Length());
4190 return S_OK;
4193 STDMETHODIMP
4194 TSFTextStore::GetActiveView(TsViewCookie* pvcView) {
4195 MOZ_LOG(gIMELog, LogLevel::Info,
4196 ("0x%p TSFTextStore::GetActiveView(pvcView=0x%p)", this, pvcView));
4198 if (!pvcView) {
4199 MOZ_LOG(gIMELog, LogLevel::Error,
4200 ("0x%p TSFTextStore::GetActiveView() FAILED due to "
4201 "null argument",
4202 this));
4203 return E_INVALIDARG;
4206 *pvcView = TEXTSTORE_DEFAULT_VIEW;
4208 MOZ_LOG(gIMELog, LogLevel::Info,
4209 ("0x%p TSFTextStore::GetActiveView() succeeded: *pvcView=%ld", this,
4210 *pvcView));
4211 return S_OK;
4214 STDMETHODIMP
4215 TSFTextStore::GetACPFromPoint(TsViewCookie vcView, const POINT* pt,
4216 DWORD dwFlags, LONG* pacp) {
4217 MOZ_LOG(gIMELog, LogLevel::Info,
4218 ("0x%p TSFTextStore::GetACPFromPoint(pvcView=%ld, pt=%p (x=%ld, "
4219 "y=%ld), dwFlags=%s, pacp=%p, mDeferNotifyingTSFUntilNextUpdate=%s, "
4220 "mWaitingQueryLayout=%s",
4221 this, vcView, pt, pt ? pt->x : 0, pt ? pt->y : 0,
4222 GetACPFromPointFlagName(dwFlags).get(), pacp,
4223 GetBoolName(mDeferNotifyingTSFUntilNextUpdate),
4224 GetBoolName(mWaitingQueryLayout)));
4226 if (!IsReadLocked()) {
4227 MOZ_LOG(gIMELog, LogLevel::Error,
4228 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4229 "not locked (read)",
4230 this));
4231 return TS_E_NOLOCK;
4234 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4235 MOZ_LOG(gIMELog, LogLevel::Error,
4236 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4237 "called with invalid view",
4238 this));
4239 return E_INVALIDARG;
4242 if (!pt) {
4243 MOZ_LOG(gIMELog, LogLevel::Error,
4244 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4245 "null pt",
4246 this));
4247 return E_INVALIDARG;
4250 if (!pacp) {
4251 MOZ_LOG(gIMELog, LogLevel::Error,
4252 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4253 "null pacp",
4254 this));
4255 return E_INVALIDARG;
4258 mWaitingQueryLayout = false;
4260 if (mDestroyed ||
4261 (mContentForTSF.isSome() && mContentForTSF->IsLayoutChanged())) {
4262 MOZ_LOG(gIMELog, LogLevel::Error,
4263 ("0x%p TSFTextStore::GetACPFromPoint() returned "
4264 "TS_E_NOLAYOUT",
4265 this));
4266 mHasReturnedNoLayoutError = true;
4267 return TS_E_NOLAYOUT;
4270 LayoutDeviceIntPoint ourPt(pt->x, pt->y);
4271 // Convert to widget relative coordinates from screen's.
4272 ourPt -= mWidget->WidgetToScreenOffset();
4274 // NOTE: Don't check if the point is in the widget since the point can be
4275 // outside of the widget if focused editor is in a XUL <panel>.
4277 WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint,
4278 mWidget);
4279 mWidget->InitEvent(queryCharAtPointEvent, &ourPt);
4281 // FYI: WidgetQueryContentEvent may cause flushing pending layout and it
4282 // may cause focus change or something.
4283 RefPtr<TSFTextStore> kungFuDeathGrip(this);
4284 DispatchEvent(queryCharAtPointEvent);
4285 if (!mWidget || mWidget->Destroyed()) {
4286 MOZ_LOG(gIMELog, LogLevel::Error,
4287 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4288 "mWidget was destroyed during eQueryCharacterAtPoint",
4289 this));
4290 return E_FAIL;
4293 MOZ_LOG(gIMELog, LogLevel::Debug,
4294 ("0x%p TSFTextStore::GetACPFromPoint(), queryCharAtPointEvent={ "
4295 "mReply=%s }",
4296 this, ToString(queryCharAtPointEvent.mReply).c_str()));
4298 if (NS_WARN_IF(queryCharAtPointEvent.Failed())) {
4299 MOZ_LOG(gIMELog, LogLevel::Error,
4300 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4301 "eQueryCharacterAtPoint failure",
4302 this));
4303 return E_FAIL;
4306 // If dwFlags isn't set and the point isn't in any character's bounding box,
4307 // we should return TS_E_INVALIDPOINT.
4308 if (!(dwFlags & GXFPF_NEAREST) && queryCharAtPointEvent.DidNotFindChar()) {
4309 MOZ_LOG(gIMELog, LogLevel::Error,
4310 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to the "
4311 "point contained by no bounding box",
4312 this));
4313 return TS_E_INVALIDPOINT;
4316 // Although, we're not sure if mTentativeCaretOffset becomes NOT_FOUND,
4317 // let's assume that there is no content in such case.
4318 NS_WARNING_ASSERTION(queryCharAtPointEvent.DidNotFindTentativeCaretOffset(),
4319 "Tentative caret offset was not found");
4321 uint32_t offset;
4323 // If dwFlags includes GXFPF_ROUND_NEAREST, we should return tentative
4324 // caret offset (MSDN calls it "range position").
4325 if (dwFlags & GXFPF_ROUND_NEAREST) {
4326 offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0);
4327 } else if (queryCharAtPointEvent.FoundChar()) {
4328 // Otherwise, we should return character offset whose bounding box contains
4329 // the point.
4330 offset = queryCharAtPointEvent.mReply->StartOffset();
4331 } else {
4332 // If the point isn't in any character's bounding box but we need to return
4333 // the nearest character from the point, we should *guess* the character
4334 // offset since there is no inexpensive API to check it strictly.
4335 // XXX If we retrieve 2 bounding boxes, one is before the offset and
4336 // the other is after the offset, we could resolve the offset.
4337 // However, dispatching 2 eQueryTextRect may be expensive.
4339 // So, use tentative offset for now.
4340 offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0);
4342 // However, if it's after the last character, we need to decrement the
4343 // offset.
4344 Maybe<Content>& contentForTSF = ContentForTSF();
4345 if (contentForTSF.isNothing()) {
4346 MOZ_LOG(gIMELog, LogLevel::Error,
4347 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4348 "ContentForTSF() failure",
4349 this));
4350 return E_FAIL;
4352 if (contentForTSF->TextRef().Length() <= offset) {
4353 // If the tentative caret is after the last character, let's return
4354 // the last character's offset.
4355 offset = contentForTSF->TextRef().Length() - 1;
4359 if (NS_WARN_IF(offset > LONG_MAX)) {
4360 MOZ_LOG(gIMELog, LogLevel::Error,
4361 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to out of "
4362 "range of the result",
4363 this));
4364 return TS_E_INVALIDPOINT;
4367 *pacp = static_cast<LONG>(offset);
4368 MOZ_LOG(gIMELog, LogLevel::Info,
4369 ("0x%p TSFTextStore::GetACPFromPoint() succeeded: *pacp=%ld", this,
4370 *pacp));
4371 return S_OK;
4374 STDMETHODIMP
4375 TSFTextStore::GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd,
4376 RECT* prc, BOOL* pfClipped) {
4377 MOZ_LOG(gIMELog, LogLevel::Info,
4378 ("0x%p TSFTextStore::GetTextExt(vcView=%ld, "
4379 "acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p), "
4380 "IsHandlingCompositionInParent()=%s, "
4381 "IsHandlingCompositionInContent()=%s, mContentForTSF=%s, "
4382 "mSelectionForTSF=%s, mComposition=%s, "
4383 "mDeferNotifyingTSFUntilNextUpdate=%s, mWaitingQueryLayout=%s, "
4384 "IMEHandler::IsA11yHandlingNativeCaret()=%s",
4385 this, vcView, acpStart, acpEnd, prc, pfClipped,
4386 GetBoolName(IsHandlingCompositionInParent()),
4387 GetBoolName(IsHandlingCompositionInContent()),
4388 mozilla::ToString(mContentForTSF).c_str(),
4389 ToString(mSelectionForTSF).c_str(), ToString(mComposition).c_str(),
4390 GetBoolName(mDeferNotifyingTSFUntilNextUpdate),
4391 GetBoolName(mWaitingQueryLayout),
4392 GetBoolName(IMEHandler::IsA11yHandlingNativeCaret())));
4394 if (!IsReadLocked()) {
4395 MOZ_LOG(gIMELog, LogLevel::Error,
4396 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4397 "not locked (read)",
4398 this));
4399 return TS_E_NOLOCK;
4402 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4403 MOZ_LOG(gIMELog, LogLevel::Error,
4404 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4405 "called with invalid view",
4406 this));
4407 return E_INVALIDARG;
4410 if (!prc || !pfClipped) {
4411 MOZ_LOG(gIMELog, LogLevel::Error,
4412 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4413 "null argument",
4414 this));
4415 return E_INVALIDARG;
4418 // According to MSDN, ITextStoreACP::GetTextExt() should return
4419 // TS_E_INVALIDARG when acpStart and acpEnd are same (i.e., collapsed range).
4420 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms538435(v=vs.85).aspx
4421 // > TS_E_INVALIDARG: The specified start and end character positions are
4422 // > equal.
4423 // However, some TIPs (including Microsoft's Chinese TIPs!) call this with
4424 // collapsed range and if we return TS_E_INVALIDARG, they stops showing their
4425 // owning window or shows it but odd position. So, we should just return
4426 // error only when acpStart and/or acpEnd are really odd.
4428 if (acpStart < 0 || acpEnd < acpStart) {
4429 MOZ_LOG(gIMELog, LogLevel::Error,
4430 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4431 "invalid position",
4432 this));
4433 return TS_E_INVALIDPOS;
4436 mWaitingQueryLayout = false;
4438 if (IsHandlingCompositionInContent() && mContentForTSF.isSome() &&
4439 mContentForTSF->HasOrHadComposition() &&
4440 mContentForTSF->IsLayoutChanged() &&
4441 mContentForTSF->MinModifiedOffset().value() >
4442 static_cast<uint32_t>(LONG_MAX)) {
4443 MOZ_LOG(gIMELog, LogLevel::Error,
4444 ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text "
4445 "is too big for TSF (cannot treat modified offset as LONG), "
4446 "mContentForTSF=%s",
4447 this, ToString(mContentForTSF).c_str()));
4448 return E_FAIL;
4451 // At Windows 10 build 17643 (an insider preview for RS5), Microsoft fixed
4452 // the bug of TS_E_NOLAYOUT (even when we returned TS_E_NOLAYOUT, TSF
4453 // returned E_FAIL to TIP). However, until we drop to support older Windows
4454 // and all TIPs are aware of TS_E_NOLAYOUT result, we need to keep returning
4455 // S_OK and available rectangle only for them.
4456 if (!MaybeHackNoErrorLayoutBugs(acpStart, acpEnd) &&
4457 mContentForTSF.isSome() && mContentForTSF->IsLayoutChangedAt(acpEnd)) {
4458 MOZ_LOG(gIMELog, LogLevel::Error,
4459 ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
4460 "(acpEnd=%ld)",
4461 this, acpEnd));
4462 mHasReturnedNoLayoutError = true;
4463 return TS_E_NOLAYOUT;
4466 if (mDestroyed) {
4467 MOZ_LOG(gIMELog, LogLevel::Error,
4468 ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
4469 "(acpEnd=%ld) because this has already been destroyed",
4470 this, acpEnd));
4471 mHasReturnedNoLayoutError = true;
4472 return TS_E_NOLAYOUT;
4475 // use eQueryTextRect to get rect in system, screen coordinates
4476 WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, mWidget);
4477 mWidget->InitEvent(queryTextRectEvent);
4479 WidgetQueryContentEvent::Options options;
4480 int64_t startOffset = acpStart;
4481 if (mComposition.isSome()) {
4482 // If there is a composition, TSF must want character rects related to
4483 // the composition. Therefore, we should use insertion point relative
4484 // query because the composition might be at different position from
4485 // the position where TSFTextStore believes it at.
4486 options.mRelativeToInsertionPoint = true;
4487 startOffset -= mComposition->StartOffset();
4488 } else if (IsHandlingCompositionInParent() && mContentForTSF.isSome() &&
4489 mContentForTSF->HasOrHadComposition()) {
4490 // If there was a composition and its commit event hasn't been dispatched
4491 // yet, ContentCacheInParent is still open for relative offset query from
4492 // the latest composition.
4493 options.mRelativeToInsertionPoint = true;
4494 startOffset -= mContentForTSF->LatestCompositionRange()->StartOffset();
4495 } else if (!CanAccessActualContentDirectly() &&
4496 mSelectionForTSF->HasRange()) {
4497 // If TSF/TIP cannot access actual content directly, there may be pending
4498 // text and/or selection changes which have not been notified TSF yet.
4499 // Therefore, we should use relative to insertion point query since
4500 // TSF/TIP computes the offset from the cached selection.
4501 options.mRelativeToInsertionPoint = true;
4502 startOffset -= mSelectionForTSF->StartOffset();
4504 // ContentEventHandler and ContentCache return actual caret rect when
4505 // the queried range is collapsed and selection is collapsed at the
4506 // queried range. Then, its height (in horizontal layout, width in vertical
4507 // layout) may be different from actual font height of the line. In such
4508 // case, users see "dancing" of candidate or suggest window of TIP.
4509 // For preventing it, we should query text rect with at least 1 length.
4510 uint32_t length = std::max(static_cast<int32_t>(acpEnd - acpStart), 1);
4511 queryTextRectEvent.InitForQueryTextRect(startOffset, length, options);
4513 DispatchEvent(queryTextRectEvent);
4514 if (NS_WARN_IF(queryTextRectEvent.Failed())) {
4515 MOZ_LOG(gIMELog, LogLevel::Error,
4516 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4517 "eQueryTextRect failure",
4518 this));
4519 return TS_E_INVALIDPOS; // but unexpected failure, maybe.
4522 // IMEs don't like empty rects, fix here
4523 if (queryTextRectEvent.mReply->mRect.Width() <= 0) {
4524 queryTextRectEvent.mReply->mRect.SetWidth(1);
4526 if (queryTextRectEvent.mReply->mRect.Height() <= 0) {
4527 queryTextRectEvent.mReply->mRect.SetHeight(1);
4530 // convert to unclipped screen rect
4531 nsWindow* refWindow =
4532 static_cast<nsWindow*>(!!queryTextRectEvent.mReply->mFocusedWidget
4533 ? queryTextRectEvent.mReply->mFocusedWidget
4534 : static_cast<nsIWidget*>(mWidget.get()));
4535 // Result rect is in top level widget coordinates
4536 refWindow = refWindow->GetTopLevelWindow(false);
4537 if (!refWindow) {
4538 MOZ_LOG(gIMELog, LogLevel::Error,
4539 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4540 "no top level window",
4541 this));
4542 return E_FAIL;
4545 queryTextRectEvent.mReply->mRect.MoveBy(refWindow->WidgetToScreenOffset());
4547 // get bounding screen rect to test for clipping
4548 if (!GetScreenExtInternal(*prc)) {
4549 MOZ_LOG(gIMELog, LogLevel::Error,
4550 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4551 "GetScreenExtInternal() failure",
4552 this));
4553 return E_FAIL;
4556 // clip text rect to bounding rect
4557 RECT textRect;
4558 ::SetRect(&textRect, queryTextRectEvent.mReply->mRect.X(),
4559 queryTextRectEvent.mReply->mRect.Y(),
4560 queryTextRectEvent.mReply->mRect.XMost(),
4561 queryTextRectEvent.mReply->mRect.YMost());
4562 if (!::IntersectRect(prc, prc, &textRect))
4563 // Text is not visible
4564 ::SetRectEmpty(prc);
4566 // not equal if text rect was clipped
4567 *pfClipped = !::EqualRect(prc, &textRect);
4569 // ATOK 2011 - 2016 refers native caret position and size on windows whose
4570 // class name is one of Mozilla's windows for deciding candidate window
4571 // position. Additionally, ATOK 2015 and earlier behaves really odd when
4572 // we don't create native caret. Therefore, we need to create native caret
4573 // only when ATOK 2011 - 2015 is active (i.e., not necessary for ATOK 2016).
4574 // However, if a11y module is handling native caret, we shouldn't touch it.
4575 // Note that ATOK must require the latest information of the caret. So,
4576 // even if we'll create native caret later, we need to creat it here with
4577 // current information.
4578 if (!IMEHandler::IsA11yHandlingNativeCaret() &&
4579 StaticPrefs::intl_tsf_hack_atok_create_native_caret() &&
4580 TSFStaticSink::IsATOKReferringNativeCaretActive() &&
4581 mComposition.isSome() &&
4582 mComposition->IsOffsetInRangeOrEndOffset(acpStart) &&
4583 mComposition->IsOffsetInRangeOrEndOffset(acpEnd)) {
4584 CreateNativeCaret();
4587 MOZ_LOG(gIMELog, LogLevel::Info,
4588 ("0x%p TSFTextStore::GetTextExt() succeeded: "
4589 "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }, *pfClipped=%s",
4590 this, prc->left, prc->top, prc->right, prc->bottom,
4591 GetBoolName(*pfClipped)));
4593 return S_OK;
4596 bool TSFTextStore::MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd) {
4597 // When ITextStoreACP::GetTextExt() returns TS_E_NOLAYOUT, TSF returns E_FAIL
4598 // to its caller (typically, active TIP). Then, most TIPs abort current job
4599 // or treat such application as non-GUI apps. E.g., some of them give up
4600 // showing candidate window, some others show candidate window at top-left of
4601 // the screen. For avoiding this issue, when there is composition (until
4602 // composition is actually committed in remote content), we should not
4603 // return TS_E_NOLAYOUT error for TIPs whose some features are broken by
4604 // this issue.
4605 // Note that ideally, this issue should be avoided by each TIP since this
4606 // won't be fixed at least on non-latest Windows. Actually, Google Japanese
4607 // Input (based on Mozc) does it. When GetTextExt() returns E_FAIL, TIPs
4608 // should try to check result of GetRangeFromPoint() because TSF returns
4609 // TS_E_NOLAYOUT correctly in this case. See:
4610 // https://github.com/google/mozc/blob/6b878e31fb6ac4347dc9dfd8ccc1080fe718479f/src/win32/tip/tip_range_util.cc#L237-L257
4612 if (!IsHandlingCompositionInContent() || mContentForTSF.isNothing() ||
4613 !mContentForTSF->HasOrHadComposition() ||
4614 !mContentForTSF->IsLayoutChangedAt(aACPEnd)) {
4615 return false;
4618 MOZ_ASSERT(mComposition.isNothing() ||
4619 mComposition->StartOffset() ==
4620 mContentForTSF->LatestCompositionRange()->StartOffset());
4621 MOZ_ASSERT(mComposition.isNothing() ||
4622 mComposition->EndOffset() ==
4623 mContentForTSF->LatestCompositionRange()->EndOffset());
4625 // If TSF does not have the bug, we need to hack only with a few TIPs.
4626 static const bool sAlllowToStopHackingIfFine =
4627 IsWindows10BuildOrLater(17643) &&
4628 StaticPrefs::
4629 intl_tsf_hack_allow_to_stop_hacking_on_build_17643_or_later();
4631 // We need to compute active TIP now. This may take a couple of milliseconds,
4632 // however, it'll be cached, so, must be faster than check active TIP every
4633 // GetTextExt() calls.
4634 const Maybe<Selection>& selectionForTSF = SelectionForTSF();
4635 switch (TSFStaticSink::ActiveTIP()) {
4636 // MS IME for Japanese doesn't support asynchronous handling at deciding
4637 // its suggest list window position. The feature was implemented
4638 // starting from Windows 8. And also we may meet same trouble in e10s
4639 // mode on Win7. So, we should never return TS_E_NOLAYOUT to MS IME for
4640 // Japanese.
4641 case TextInputProcessorID::eMicrosoftIMEForJapanese:
4642 // Basically, MS-IME tries to retrieve whole composition string rect
4643 // at deciding suggest window immediately after unlocking the document.
4644 // However, in e10s mode, the content hasn't updated yet in most cases.
4645 // Therefore, if the first character at the retrieving range rect is
4646 // available, we should use it as the result.
4647 // Note that according to bug 1609675, MS-IME for Japanese itself does
4648 // not handle TS_E_NOLAYOUT correctly at least on Build 18363.657 (1909).
4649 if (StaticPrefs::
4650 intl_tsf_hack_ms_japanese_ime_do_not_return_no_layout_error_at_first_char() &&
4651 aACPStart < aACPEnd) {
4652 aACPEnd = aACPStart;
4653 break;
4655 if (sAlllowToStopHackingIfFine) {
4656 return false;
4658 // Although, the condition is not clear, MS-IME sometimes retrieves the
4659 // caret rect immediately after modifying the composition string but
4660 // before unlocking the document. In such case, we should return the
4661 // nearest character rect.
4662 // (Let's return true if there is no selection which must be not expected
4663 // by MS-IME nor TSF.)
4664 if (StaticPrefs::
4665 intl_tsf_hack_ms_japanese_ime_do_not_return_no_layout_error_at_caret() &&
4666 aACPStart == aACPEnd && selectionForTSF.isSome() &&
4667 (!selectionForTSF->HasRange() ||
4668 (selectionForTSF->Collapsed() &&
4669 selectionForTSF->EndOffset() == aACPEnd))) {
4670 int32_t minOffsetOfLayoutChanged =
4671 static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
4672 aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
4673 } else {
4674 return false;
4676 break;
4677 // The bug of Microsoft Office IME 2010 for Japanese is similar to
4678 // MS-IME for Win 8.1 and Win 10. Newer version of MS Office IME is not
4679 // released yet. So, we can hack it without prefs because there must be
4680 // no developers who want to disable this hack for tests.
4681 // XXX We have not tested with Microsoft Office IME 2010 since it's
4682 // installable only with Win7 and Win8 (i.e., cannot install Win8.1
4683 // and Win10), and requires upgrade to Win10.
4684 case TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese:
4685 // Basically, MS-IME tries to retrieve whole composition string rect
4686 // at deciding suggest window immediately after unlocking the document.
4687 // However, in e10s mode, the content hasn't updated yet in most cases.
4688 // Therefore, if the first character at the retrieving range rect is
4689 // available, we should use it as the result.
4690 if (aACPStart < aACPEnd) {
4691 aACPEnd = aACPStart;
4693 // Although, the condition is not clear, MS-IME sometimes retrieves the
4694 // caret rect immediately after modifying the composition string but
4695 // before unlocking the document. In such case, we should return the
4696 // nearest character rect.
4697 // (Let's return true if there is no selection which must be not expected
4698 // by MS-IME nor TSF.)
4699 else if (aACPStart == aACPEnd && selectionForTSF.isSome() &&
4700 (!selectionForTSF->HasRange() ||
4701 (selectionForTSF->Collapsed() &&
4702 selectionForTSF->EndOffset() == aACPEnd))) {
4703 int32_t minOffsetOfLayoutChanged =
4704 static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
4705 aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
4706 } else {
4707 return false;
4709 break;
4710 // ATOK fails to handle TS_E_NOLAYOUT only when it decides the position of
4711 // suggest window. In such case, ATOK tries to query rect of whole or a
4712 // part of composition string.
4713 // FYI: ATOK changes their implementation around candidate window and
4714 // suggest widget at ATOK 2016. Therefore, there are some differences
4715 // ATOK 2015 (or older) and ATOK 2016 (or newer).
4716 // FYI: ATOK 2017 stops referring our window class name. I.e., ATOK 2016
4717 // and older may behave differently only on Gecko but this must be
4718 // finished from ATOK 2017.
4719 // FYI: For testing with legacy ATOK, we should hack it even if current ATOK
4720 // refers native caret rect on windows whose window class is one of
4721 // Mozilla window classes and we stop creating native caret for ATOK
4722 // because creating native caret causes ATOK refers caret position
4723 // when GetTextExt() returns TS_E_NOLAYOUT.
4724 case TextInputProcessorID::eATOK2011:
4725 case TextInputProcessorID::eATOK2012:
4726 case TextInputProcessorID::eATOK2013:
4727 case TextInputProcessorID::eATOK2014:
4728 case TextInputProcessorID::eATOK2015:
4729 // ATOK 2016 and later may temporarily show candidate window at odd
4730 // position when you convert a word quickly (e.g., keep pressing
4731 // space bar). So, on ATOK 2016 or later, we need to keep hacking the
4732 // result of GetTextExt().
4733 if (sAlllowToStopHackingIfFine) {
4734 return false;
4736 // If we'll create native caret where we paint our caret. Then, ATOK
4737 // will refer native caret. So, we don't need to hack anything in
4738 // this case.
4739 if (StaticPrefs::intl_tsf_hack_atok_create_native_caret()) {
4740 MOZ_ASSERT(TSFStaticSink::IsATOKReferringNativeCaretActive());
4741 return false;
4743 [[fallthrough]];
4744 case TextInputProcessorID::eATOK2016:
4745 case TextInputProcessorID::eATOKUnknown:
4746 if (!StaticPrefs::
4747 intl_tsf_hack_atok_do_not_return_no_layout_error_of_composition_string()) {
4748 return false;
4750 // If the range is in the composition string, we should return rectangle
4751 // in it as far as possible.
4752 if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4753 aACPStart) ||
4754 !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4755 aACPEnd)) {
4756 return false;
4758 break;
4759 // Japanist 10 fails to handle TS_E_NOLAYOUT when it decides the position
4760 // of candidate window. In such case, Japanist shows candidate window at
4761 // top-left of the screen. So, we should return the nearest caret rect
4762 // where we know. This is Japanist's bug. So, even after build 17643,
4763 // we need this hack.
4764 case TextInputProcessorID::eJapanist10:
4765 if (!StaticPrefs::
4766 intl_tsf_hack_japanist10_do_not_return_no_layout_error_of_composition_string()) {
4767 return false;
4769 if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4770 aACPStart) ||
4771 !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4772 aACPEnd)) {
4773 return false;
4775 break;
4776 // Free ChangJie 2010 doesn't handle ITfContextView::GetTextExt() properly.
4777 // This must be caused by the bug of TSF since Free ChangJie works fine on
4778 // build 17643 and later.
4779 case TextInputProcessorID::eFreeChangJie:
4780 if (sAlllowToStopHackingIfFine) {
4781 return false;
4783 if (!StaticPrefs::
4784 intl_tsf_hack_free_chang_jie_do_not_return_no_layout_error()) {
4785 return false;
4787 aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
4788 aACPStart = std::min(aACPStart, aACPEnd);
4789 break;
4790 // Some Traditional Chinese TIPs of Microsoft don't show candidate window
4791 // in e10s mode on Win8 or later.
4792 case TextInputProcessorID::eMicrosoftQuick:
4793 if (sAlllowToStopHackingIfFine) {
4794 return false; // MS Quick works fine with Win10 build 17643.
4796 [[fallthrough]];
4797 case TextInputProcessorID::eMicrosoftChangJie:
4798 if (!IsWin8OrLater() ||
4799 !StaticPrefs::
4800 intl_tsf_hack_ms_traditional_chinese_do_not_return_no_layout_error()) {
4801 return false;
4803 aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
4804 aACPStart = std::min(aACPStart, aACPEnd);
4805 break;
4806 // Some Simplified Chinese TIPs of Microsoft don't show candidate window
4807 // in e10s mode on Win8 or later.
4808 // FYI: Only Simplified Chinese TIPs of Microsoft still require this hack
4809 // because they sometimes do not show candidate window when we return
4810 // TS_E_NOLAYOUT for first query. Note that even when they show
4811 // candidate window properly, we return TS_E_NOLAYOUT and following
4812 // log looks same as when they don't show candidate window. Perhaps,
4813 // there is stateful cause or race in them.
4814 case TextInputProcessorID::eMicrosoftPinyin:
4815 case TextInputProcessorID::eMicrosoftWubi:
4816 if (!IsWin8OrLater() ||
4817 !StaticPrefs::
4818 intl_tsf_hack_ms_simplified_chinese_do_not_return_no_layout_error()) {
4819 return false;
4821 aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
4822 aACPStart = std::min(aACPStart, aACPEnd);
4823 break;
4824 default:
4825 return false;
4828 // If we hack the queried range for active TIP, that means we should not
4829 // return TS_E_NOLAYOUT even if hacked offset is still modified. So, as
4830 // far as possible, we should adjust the offset.
4831 MOZ_ASSERT(mContentForTSF->IsLayoutChanged());
4832 bool collapsed = aACPStart == aACPEnd;
4833 // Note that even if all characters in the editor or the composition
4834 // string was modified, 0 or start offset of the composition string is
4835 // useful because it may return caret rect or old character's rect which
4836 // the user still see. That must be useful information for TIP.
4837 int32_t firstModifiedOffset =
4838 static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
4839 LONG lastUnmodifiedOffset = std::max(firstModifiedOffset - 1, 0);
4840 if (mContentForTSF->IsLayoutChangedAt(aACPStart)) {
4841 if (aACPStart >= mContentForTSF->LatestCompositionRange()->StartOffset()) {
4842 // If mContentForTSF has last composition string and current
4843 // composition string, we can assume that ContentCacheInParent has
4844 // cached rects of composition string at least length of current
4845 // composition string. Otherwise, we can assume that rect for
4846 // first character of composition string is stored since it was
4847 // selection start or caret position.
4848 LONG maxCachedOffset =
4849 mContentForTSF->LatestCompositionRange()->EndOffset();
4850 if (mContentForTSF->LastComposition().isSome()) {
4851 maxCachedOffset = std::min(
4852 maxCachedOffset, mContentForTSF->LastComposition()->EndOffset());
4854 aACPStart = std::min(aACPStart, maxCachedOffset);
4856 // Otherwise, we don't know which character rects are cached. So, we
4857 // need to use first unmodified character's rect in this case. Even
4858 // if there is no character, the query event will return caret rect
4859 // instead.
4860 else {
4861 aACPStart = lastUnmodifiedOffset;
4863 MOZ_ASSERT(aACPStart <= aACPEnd);
4866 // If TIP requests caret rect with collapsed range, we should keep
4867 // collapsing the range.
4868 if (collapsed) {
4869 aACPEnd = aACPStart;
4871 // Let's set aACPEnd to larger offset of last unmodified offset or
4872 // aACPStart which may be the first character offset of the composition
4873 // string. However, some TIPs may want to know the right edge of the
4874 // range. Therefore, if aACPEnd is in composition string and active TIP
4875 // doesn't retrieve caret rect (i.e., the range isn't collapsed), we
4876 // should keep using the original aACPEnd. Otherwise, we should set
4877 // aACPEnd to larger value of aACPStart and lastUnmodifiedOffset.
4878 else if (mContentForTSF->IsLayoutChangedAt(aACPEnd) &&
4879 !mContentForTSF->LatestCompositionRange()
4880 ->IsOffsetInRangeOrEndOffset(aACPEnd)) {
4881 aACPEnd = std::max(aACPStart, lastUnmodifiedOffset);
4884 MOZ_LOG(
4885 gIMELog, LogLevel::Debug,
4886 ("0x%p TSFTextStore::HackNoErrorLayoutBugs() hacked the queried range "
4887 "for not returning TS_E_NOLAYOUT, new values are: "
4888 "aACPStart=%ld, aACPEnd=%ld",
4889 this, aACPStart, aACPEnd));
4891 return true;
4894 STDMETHODIMP
4895 TSFTextStore::GetScreenExt(TsViewCookie vcView, RECT* prc) {
4896 MOZ_LOG(gIMELog, LogLevel::Info,
4897 ("0x%p TSFTextStore::GetScreenExt(vcView=%ld, prc=0x%p)", this,
4898 vcView, prc));
4900 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4901 MOZ_LOG(gIMELog, LogLevel::Error,
4902 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4903 "called with invalid view",
4904 this));
4905 return E_INVALIDARG;
4908 if (!prc) {
4909 MOZ_LOG(gIMELog, LogLevel::Error,
4910 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4911 "null argument",
4912 this));
4913 return E_INVALIDARG;
4916 if (mDestroyed) {
4917 MOZ_LOG(gIMELog, LogLevel::Error,
4918 ("0x%p TSFTextStore::GetScreenExt() returns empty rect "
4919 "due to already destroyed",
4920 this));
4921 prc->left = prc->top = prc->right = prc->bottom = 0;
4922 return S_OK;
4925 if (!GetScreenExtInternal(*prc)) {
4926 MOZ_LOG(gIMELog, LogLevel::Error,
4927 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4928 "GetScreenExtInternal() failure",
4929 this));
4930 return E_FAIL;
4933 MOZ_LOG(gIMELog, LogLevel::Info,
4934 ("0x%p TSFTextStore::GetScreenExt() succeeded: "
4935 "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
4936 this, prc->left, prc->top, prc->right, prc->bottom));
4937 return S_OK;
4940 bool TSFTextStore::GetScreenExtInternal(RECT& aScreenExt) {
4941 MOZ_LOG(gIMELog, LogLevel::Debug,
4942 ("0x%p TSFTextStore::GetScreenExtInternal()", this));
4944 MOZ_ASSERT(!mDestroyed);
4946 // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
4947 WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, mWidget);
4948 mWidget->InitEvent(queryEditorRectEvent);
4949 DispatchEvent(queryEditorRectEvent);
4950 if (queryEditorRectEvent.Failed()) {
4951 MOZ_LOG(gIMELog, LogLevel::Error,
4952 ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
4953 "eQueryEditorRect failure",
4954 this));
4955 return false;
4958 nsWindow* refWindow =
4959 static_cast<nsWindow*>(!!queryEditorRectEvent.mReply->mFocusedWidget
4960 ? queryEditorRectEvent.mReply->mFocusedWidget
4961 : static_cast<nsIWidget*>(mWidget.get()));
4962 // Result rect is in top level widget coordinates
4963 refWindow = refWindow->GetTopLevelWindow(false);
4964 if (!refWindow) {
4965 MOZ_LOG(gIMELog, LogLevel::Error,
4966 ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
4967 "no top level window",
4968 this));
4969 return false;
4972 LayoutDeviceIntRect boundRect = refWindow->GetClientBounds();
4973 boundRect.MoveTo(0, 0);
4975 // Clip frame rect to window rect
4976 boundRect.IntersectRect(queryEditorRectEvent.mReply->mRect, boundRect);
4977 if (!boundRect.IsEmpty()) {
4978 boundRect.MoveBy(refWindow->WidgetToScreenOffset());
4979 ::SetRect(&aScreenExt, boundRect.X(), boundRect.Y(), boundRect.XMost(),
4980 boundRect.YMost());
4981 } else {
4982 ::SetRectEmpty(&aScreenExt);
4985 MOZ_LOG(gIMELog, LogLevel::Debug,
4986 ("0x%p TSFTextStore::GetScreenExtInternal() succeeded: "
4987 "aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
4988 this, aScreenExt.left, aScreenExt.top, aScreenExt.right,
4989 aScreenExt.bottom));
4990 return true;
4993 STDMETHODIMP
4994 TSFTextStore::GetWnd(TsViewCookie vcView, HWND* phwnd) {
4995 MOZ_LOG(gIMELog, LogLevel::Info,
4996 ("0x%p TSFTextStore::GetWnd(vcView=%ld, phwnd=0x%p), "
4997 "mWidget=0x%p",
4998 this, vcView, phwnd, mWidget.get()));
5000 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
5001 MOZ_LOG(gIMELog, LogLevel::Error,
5002 ("0x%p TSFTextStore::GetWnd() FAILED due to "
5003 "called with invalid view",
5004 this));
5005 return E_INVALIDARG;
5008 if (!phwnd) {
5009 MOZ_LOG(gIMELog, LogLevel::Error,
5010 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
5011 "null argument",
5012 this));
5013 return E_INVALIDARG;
5016 *phwnd = mWidget ? mWidget->GetWindowHandle() : nullptr;
5018 MOZ_LOG(gIMELog, LogLevel::Info,
5019 ("0x%p TSFTextStore::GetWnd() succeeded: *phwnd=0x%p", this,
5020 static_cast<void*>(*phwnd)));
5021 return S_OK;
5024 STDMETHODIMP
5025 TSFTextStore::InsertTextAtSelection(DWORD dwFlags, const WCHAR* pchText,
5026 ULONG cch, LONG* pacpStart, LONG* pacpEnd,
5027 TS_TEXTCHANGE* pChange) {
5028 MOZ_LOG(
5029 gIMELog, LogLevel::Info,
5030 ("0x%p TSFTextStore::InsertTextAtSelection(dwFlags=%s, "
5031 "pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, "
5032 "pChange=0x%p), mComposition=%s",
5033 this,
5034 dwFlags == 0 ? "0"
5035 : dwFlags == TF_IAS_NOQUERY ? "TF_IAS_NOQUERY"
5036 : dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY"
5037 : "Unknown",
5038 pchText, pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "",
5039 cch, pacpStart, pacpEnd, pChange, ToString(mComposition).c_str()));
5041 if (cch && !pchText) {
5042 MOZ_LOG(gIMELog, LogLevel::Error,
5043 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5044 "null pchText",
5045 this));
5046 return E_INVALIDARG;
5049 if (TS_IAS_QUERYONLY == dwFlags) {
5050 if (!IsReadLocked()) {
5051 MOZ_LOG(gIMELog, LogLevel::Error,
5052 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5053 "not locked (read)",
5054 this));
5055 return TS_E_NOLOCK;
5058 if (!pacpStart || !pacpEnd) {
5059 MOZ_LOG(gIMELog, LogLevel::Error,
5060 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5061 "null argument",
5062 this));
5063 return E_INVALIDARG;
5066 // Get selection first
5067 Maybe<Selection>& selectionForTSF = SelectionForTSF();
5068 if (selectionForTSF.isNothing()) {
5069 MOZ_LOG(gIMELog, LogLevel::Error,
5070 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5071 "SelectionForTSF() failure",
5072 this));
5073 return E_FAIL;
5076 // Simulate text insertion
5077 if (selectionForTSF->HasRange()) {
5078 *pacpStart = selectionForTSF->StartOffset();
5079 *pacpEnd = selectionForTSF->EndOffset();
5080 if (pChange) {
5081 *pChange = TS_TEXTCHANGE{.acpStart = selectionForTSF->StartOffset(),
5082 .acpOldEnd = selectionForTSF->EndOffset(),
5083 .acpNewEnd = selectionForTSF->StartOffset() +
5084 static_cast<LONG>(cch)};
5086 } else {
5087 // There is no error code to return "no selection" state from this method.
5088 // This means that TSF/TIP should check `GetSelection` result first and
5089 // stop using this. However, this could be called by TIP/TSF if they do
5090 // not do so. Therefore, we should use start of editor instead, but
5091 // notify the caller of nothing will be inserted with pChange->acpNewEnd.
5092 *pacpStart = *pacpEnd = 0;
5093 if (pChange) {
5094 *pChange = TS_TEXTCHANGE{.acpStart = 0, .acpOldEnd = 0, .acpNewEnd = 0};
5097 } else {
5098 if (!IsReadWriteLocked()) {
5099 MOZ_LOG(gIMELog, LogLevel::Error,
5100 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5101 "not locked (read-write)",
5102 this));
5103 return TS_E_NOLOCK;
5106 if (!pChange) {
5107 MOZ_LOG(gIMELog, LogLevel::Error,
5108 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5109 "null pChange",
5110 this));
5111 return E_INVALIDARG;
5114 if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) {
5115 MOZ_LOG(gIMELog, LogLevel::Error,
5116 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5117 "null argument",
5118 this));
5119 return E_INVALIDARG;
5122 if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
5123 pChange)) {
5124 MOZ_LOG(gIMELog, LogLevel::Error,
5125 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5126 "InsertTextAtSelectionInternal() failure",
5127 this));
5128 return E_FAIL;
5131 if (TS_IAS_NOQUERY != dwFlags) {
5132 *pacpStart = pChange->acpStart;
5133 *pacpEnd = pChange->acpNewEnd;
5136 MOZ_LOG(gIMELog, LogLevel::Info,
5137 ("0x%p TSFTextStore::InsertTextAtSelection() succeeded: "
5138 "*pacpStart=%ld, *pacpEnd=%ld, "
5139 "*pChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld })",
5140 this, pacpStart ? *pacpStart : 0, pacpEnd ? *pacpEnd : 0,
5141 pChange ? pChange->acpStart : 0, pChange ? pChange->acpOldEnd : 0,
5142 pChange ? pChange->acpNewEnd : 0));
5143 return S_OK;
5146 bool TSFTextStore::InsertTextAtSelectionInternal(const nsAString& aInsertStr,
5147 TS_TEXTCHANGE* aTextChange) {
5148 MOZ_LOG(gIMELog, LogLevel::Debug,
5149 ("0x%p TSFTextStore::InsertTextAtSelectionInternal("
5150 "aInsertStr=\"%s\", aTextChange=0x%p), mComposition=%s",
5151 this, GetEscapedUTF8String(aInsertStr).get(), aTextChange,
5152 ToString(mComposition).c_str()));
5154 Maybe<Content>& contentForTSF = ContentForTSF();
5155 if (contentForTSF.isNothing()) {
5156 MOZ_LOG(gIMELog, LogLevel::Error,
5157 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() failed "
5158 "due to ContentForTSF() failure()",
5159 this));
5160 return false;
5163 MaybeDispatchKeyboardEventAsProcessedByIME();
5164 if (mDestroyed) {
5165 MOZ_LOG(
5166 gIMELog, LogLevel::Error,
5167 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() FAILED due to "
5168 "destroyed during dispatching a keyboard event",
5169 this));
5170 return false;
5173 TS_SELECTION_ACP oldSelection = contentForTSF->Selection()->ACPRef();
5174 if (mComposition.isNothing()) {
5175 // Use a temporary composition to contain the text
5176 PendingAction* compositionStart = mPendingActions.AppendElements(2);
5177 PendingAction* compositionEnd = compositionStart + 1;
5179 compositionStart->mType = PendingAction::Type::eCompositionStart;
5180 compositionStart->mSelectionStart = oldSelection.acpStart;
5181 compositionStart->mSelectionLength =
5182 oldSelection.acpEnd - oldSelection.acpStart;
5183 compositionStart->mAdjustSelection = false;
5185 compositionEnd->mType = PendingAction::Type::eCompositionEnd;
5186 compositionEnd->mData = aInsertStr;
5187 compositionEnd->mSelectionStart = compositionStart->mSelectionStart;
5189 MOZ_LOG(gIMELog, LogLevel::Debug,
5190 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
5191 "appending pending compositionstart and compositionend... "
5192 "PendingCompositionStart={ mSelectionStart=%ld, "
5193 "mSelectionLength=%ld }, PendingCompositionEnd={ mData=\"%s\" "
5194 "(Length()=%zu), mSelectionStart=%ld }",
5195 this, compositionStart->mSelectionStart,
5196 compositionStart->mSelectionLength,
5197 GetEscapedUTF8String(compositionEnd->mData).get(),
5198 compositionEnd->mData.Length(), compositionEnd->mSelectionStart));
5201 contentForTSF->ReplaceSelectedTextWith(aInsertStr);
5203 if (aTextChange) {
5204 aTextChange->acpStart = oldSelection.acpStart;
5205 aTextChange->acpOldEnd = oldSelection.acpEnd;
5206 aTextChange->acpNewEnd = contentForTSF->Selection()->EndOffset();
5209 MOZ_LOG(
5210 gIMELog, LogLevel::Debug,
5211 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
5212 "succeeded: mWidget=0x%p, mWidget->Destroyed()=%s, aTextChange={ "
5213 "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
5214 this, mWidget.get(), GetBoolName(mWidget ? mWidget->Destroyed() : true),
5215 aTextChange ? aTextChange->acpStart : 0,
5216 aTextChange ? aTextChange->acpOldEnd : 0,
5217 aTextChange ? aTextChange->acpNewEnd : 0));
5218 return true;
5221 STDMETHODIMP
5222 TSFTextStore::InsertEmbeddedAtSelection(DWORD dwFlags, IDataObject* pDataObject,
5223 LONG* pacpStart, LONG* pacpEnd,
5224 TS_TEXTCHANGE* pChange) {
5225 MOZ_LOG(gIMELog, LogLevel::Info,
5226 ("0x%p TSFTextStore::InsertEmbeddedAtSelection() called "
5227 "but not supported (E_NOTIMPL)",
5228 this));
5230 // embedded objects are not supported
5231 return E_NOTIMPL;
5234 HRESULT TSFTextStore::RecordCompositionStartAction(
5235 ITfCompositionView* aCompositionView, ITfRange* aRange,
5236 bool aPreserveSelection) {
5237 MOZ_LOG(gIMELog, LogLevel::Debug,
5238 ("0x%p TSFTextStore::RecordCompositionStartAction("
5239 "aCompositionView=0x%p, aRange=0x%p, aPreserveSelection=%s), "
5240 "mComposition=%s",
5241 this, aCompositionView, aRange, GetBoolName(aPreserveSelection),
5242 ToString(mComposition).c_str()));
5244 LONG start = 0, length = 0;
5245 HRESULT hr = GetRangeExtent(aRange, &start, &length);
5246 if (FAILED(hr)) {
5247 MOZ_LOG(gIMELog, LogLevel::Error,
5248 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5249 "due to GetRangeExtent() failure",
5250 this));
5251 return hr;
5254 return RecordCompositionStartAction(aCompositionView, start, length,
5255 aPreserveSelection);
5258 HRESULT TSFTextStore::RecordCompositionStartAction(
5259 ITfCompositionView* aCompositionView, LONG aStart, LONG aLength,
5260 bool aPreserveSelection) {
5261 MOZ_LOG(gIMELog, LogLevel::Debug,
5262 ("0x%p TSFTextStore::RecordCompositionStartAction("
5263 "aCompositionView=0x%p, aStart=%ld, aLength=%ld, "
5264 "aPreserveSelection=%s), "
5265 "mComposition=%s",
5266 this, aCompositionView, aStart, aLength,
5267 GetBoolName(aPreserveSelection), ToString(mComposition).c_str()));
5269 Maybe<Content>& contentForTSF = ContentForTSF();
5270 if (contentForTSF.isNothing()) {
5271 MOZ_LOG(gIMELog, LogLevel::Error,
5272 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5273 "due to ContentForTSF() failure",
5274 this));
5275 return E_FAIL;
5278 MaybeDispatchKeyboardEventAsProcessedByIME();
5279 if (mDestroyed) {
5280 MOZ_LOG(
5281 gIMELog, LogLevel::Error,
5282 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED due to "
5283 "destroyed during dispatching a keyboard event",
5284 this));
5285 return false;
5288 CompleteLastActionIfStillIncomplete();
5290 // TIP may have inserted text at selection before calling
5291 // OnStartComposition(). In this case, we've already created a pending
5292 // compositionend. If new composition replaces all commit string of the
5293 // pending compositionend, we should cancel the pending compositionend and
5294 // keep the previous composition normally.
5295 // On Windows 7, MS-IME for Korean, MS-IME 2010 for Korean and MS Old Hangul
5296 // may start composition with calling InsertTextAtSelection() and
5297 // OnStartComposition() with this order (bug 1208043).
5298 // On Windows 10, MS Pinyin, MS Wubi, MS ChangJie and MS Quick commits
5299 // last character and replace it with empty string with new composition
5300 // when user removes last character of composition string with Backspace
5301 // key (bug 1462257).
5302 if (!aPreserveSelection &&
5303 IsLastPendingActionCompositionEndAt(aStart, aLength)) {
5304 const PendingAction& pendingCompositionEnd = mPendingActions.LastElement();
5305 contentForTSF->RestoreCommittedComposition(aCompositionView,
5306 pendingCompositionEnd);
5307 mPendingActions.RemoveLastElement();
5308 MOZ_LOG(gIMELog, LogLevel::Info,
5309 ("0x%p TSFTextStore::RecordCompositionStartAction() "
5310 "succeeded: restoring the committed string as composing string, "
5311 "mComposition=%s, mSelectionForTSF=%s",
5312 this, ToString(mComposition).c_str(),
5313 ToString(mSelectionForTSF).c_str()));
5314 return S_OK;
5317 PendingAction* action = mPendingActions.AppendElement();
5318 action->mType = PendingAction::Type::eCompositionStart;
5319 action->mSelectionStart = aStart;
5320 action->mSelectionLength = aLength;
5322 Maybe<Selection>& selectionForTSF = SelectionForTSF();
5323 if (selectionForTSF.isNothing()) {
5324 MOZ_LOG(gIMELog, LogLevel::Error,
5325 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5326 "due to SelectionForTSF() failure",
5327 this));
5328 action->mAdjustSelection = true;
5329 } else if (!selectionForTSF->HasRange()) {
5330 // If there is no selection, let's collapse seletion to the insertion point.
5331 action->mAdjustSelection = true;
5332 } else if (selectionForTSF->MinOffset() != aStart ||
5333 selectionForTSF->MaxOffset() != aStart + aLength) {
5334 // If new composition range is different from current selection range,
5335 // we need to set selection before dispatching compositionstart event.
5336 action->mAdjustSelection = true;
5337 } else {
5338 // We shouldn't dispatch selection set event before dispatching
5339 // compositionstart event because it may cause put caret different
5340 // position in HTML editor since generated flat text content and offset in
5341 // it are lossy data of HTML contents.
5342 action->mAdjustSelection = false;
5345 contentForTSF->StartComposition(aCompositionView, *action,
5346 aPreserveSelection);
5347 MOZ_ASSERT(mComposition.isSome());
5348 action->mData = mComposition->DataRef();
5350 MOZ_LOG(gIMELog, LogLevel::Info,
5351 ("0x%p TSFTextStore::RecordCompositionStartAction() succeeded: "
5352 "mComposition=%s, mSelectionForTSF=%s }",
5353 this, ToString(mComposition).c_str(),
5354 ToString(mSelectionForTSF).c_str()));
5355 return S_OK;
5358 HRESULT
5359 TSFTextStore::RecordCompositionEndAction() {
5360 MOZ_LOG(gIMELog, LogLevel::Debug,
5361 ("0x%p TSFTextStore::RecordCompositionEndAction(), "
5362 "mComposition=%s",
5363 this, ToString(mComposition).c_str()));
5365 MOZ_ASSERT(mComposition.isSome());
5367 if (mComposition.isNothing()) {
5368 MOZ_LOG(gIMELog, LogLevel::Error,
5369 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
5370 "no composition",
5371 this));
5372 return false;
5375 MaybeDispatchKeyboardEventAsProcessedByIME();
5376 if (mDestroyed) {
5377 MOZ_LOG(gIMELog, LogLevel::Error,
5378 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
5379 "destroyed during dispatching a keyboard event",
5380 this));
5381 return false;
5384 // If we're handling incomplete composition update or already handled
5385 // composition update, we can forget them since composition end will send
5386 // the latest composition string and it overwrites the composition string
5387 // even if we dispatch eCompositionChange event before that. So, let's
5388 // forget all composition updates now.
5389 RemoveLastCompositionUpdateActions();
5390 PendingAction* action = mPendingActions.AppendElement();
5391 action->mType = PendingAction::Type::eCompositionEnd;
5392 action->mData = mComposition->DataRef();
5393 action->mSelectionStart = mComposition->StartOffset();
5395 Maybe<Content>& contentForTSF = ContentForTSF();
5396 if (contentForTSF.isNothing()) {
5397 MOZ_LOG(gIMELog, LogLevel::Error,
5398 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due "
5399 "to ContentForTSF() failure",
5400 this));
5401 return E_FAIL;
5403 contentForTSF->EndComposition(*action);
5405 // If this composition was restart but the composition doesn't modify
5406 // anything, we should remove the pending composition for preventing to
5407 // dispatch redundant composition events.
5408 for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) {
5409 PendingAction& pendingAction = mPendingActions[i - 1];
5410 if (pendingAction.mType == PendingAction::Type::eCompositionStart) {
5411 if (pendingAction.mData != action->mData) {
5412 break;
5414 // When only setting selection is necessary, we should append it.
5415 if (pendingAction.mAdjustSelection) {
5416 LONG selectionStart = pendingAction.mSelectionStart;
5417 LONG selectionLength = pendingAction.mSelectionLength;
5419 PendingAction* setSelection = mPendingActions.AppendElement();
5420 setSelection->mType = PendingAction::Type::eSetSelection;
5421 setSelection->mSelectionStart = selectionStart;
5422 setSelection->mSelectionLength = selectionLength;
5423 setSelection->mSelectionReversed = false;
5425 // Remove the redundant pending composition.
5426 mPendingActions.RemoveElementsAt(i - 1, j);
5427 MOZ_LOG(gIMELog, LogLevel::Info,
5428 ("0x%p TSFTextStore::RecordCompositionEndAction(), "
5429 "succeeded, but the composition was canceled due to redundant",
5430 this));
5431 return S_OK;
5435 MOZ_LOG(
5436 gIMELog, LogLevel::Info,
5437 ("0x%p TSFTextStore::RecordCompositionEndAction(), succeeded", this));
5438 return S_OK;
5441 STDMETHODIMP
5442 TSFTextStore::OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) {
5443 MOZ_LOG(gIMELog, LogLevel::Info,
5444 ("0x%p TSFTextStore::OnStartComposition(pComposition=0x%p, "
5445 "pfOk=0x%p), mComposition=%s",
5446 this, pComposition, pfOk, ToString(mComposition).c_str()));
5448 AutoPendingActionAndContentFlusher flusher(this);
5450 *pfOk = FALSE;
5452 // Only one composition at a time
5453 if (mComposition.isSome()) {
5454 MOZ_LOG(gIMELog, LogLevel::Error,
5455 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5456 "there is another composition already (but returns S_OK)",
5457 this));
5458 return S_OK;
5461 RefPtr<ITfRange> range;
5462 HRESULT hr = pComposition->GetRange(getter_AddRefs(range));
5463 if (FAILED(hr)) {
5464 MOZ_LOG(gIMELog, LogLevel::Error,
5465 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5466 "pComposition->GetRange() failure",
5467 this));
5468 return hr;
5470 hr = RecordCompositionStartAction(pComposition, range, false);
5471 if (FAILED(hr)) {
5472 MOZ_LOG(gIMELog, LogLevel::Error,
5473 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5474 "RecordCompositionStartAction() failure",
5475 this));
5476 return hr;
5479 *pfOk = TRUE;
5480 MOZ_LOG(gIMELog, LogLevel::Info,
5481 ("0x%p TSFTextStore::OnStartComposition() succeeded", this));
5482 return S_OK;
5485 STDMETHODIMP
5486 TSFTextStore::OnUpdateComposition(ITfCompositionView* pComposition,
5487 ITfRange* pRangeNew) {
5488 MOZ_LOG(gIMELog, LogLevel::Info,
5489 ("0x%p TSFTextStore::OnUpdateComposition(pComposition=0x%p, "
5490 "pRangeNew=0x%p), mComposition=%s",
5491 this, pComposition, pRangeNew, ToString(mComposition).c_str()));
5493 AutoPendingActionAndContentFlusher flusher(this);
5495 if (!mDocumentMgr || !mContext) {
5496 MOZ_LOG(gIMELog, LogLevel::Error,
5497 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5498 "not ready for the composition",
5499 this));
5500 return E_UNEXPECTED;
5502 if (mComposition.isNothing()) {
5503 MOZ_LOG(gIMELog, LogLevel::Error,
5504 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5505 "no active composition",
5506 this));
5507 return E_UNEXPECTED;
5509 if (mComposition->GetView() != pComposition) {
5510 MOZ_LOG(gIMELog, LogLevel::Error,
5511 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5512 "different composition view specified",
5513 this));
5514 return E_UNEXPECTED;
5517 // pRangeNew is null when the update is not complete
5518 if (!pRangeNew) {
5519 MaybeDispatchKeyboardEventAsProcessedByIME();
5520 if (mDestroyed) {
5521 MOZ_LOG(gIMELog, LogLevel::Error,
5522 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5523 "destroyed during dispatching a keyboard event",
5524 this));
5525 return E_FAIL;
5527 PendingAction* action = LastOrNewPendingCompositionUpdate();
5528 action->mIncomplete = true;
5529 MOZ_LOG(gIMELog, LogLevel::Info,
5530 ("0x%p TSFTextStore::OnUpdateComposition() succeeded but "
5531 "not complete",
5532 this));
5533 return S_OK;
5536 HRESULT hr = RestartCompositionIfNecessary(pRangeNew);
5537 if (FAILED(hr)) {
5538 MOZ_LOG(gIMELog, LogLevel::Error,
5539 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5540 "RestartCompositionIfNecessary() failure",
5541 this));
5542 return hr;
5545 hr = RecordCompositionUpdateAction();
5546 if (FAILED(hr)) {
5547 MOZ_LOG(gIMELog, LogLevel::Error,
5548 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5549 "RecordCompositionUpdateAction() failure",
5550 this));
5551 return hr;
5554 if (MOZ_LOG_TEST(gIMELog, LogLevel::Info)) {
5555 Maybe<Selection>& selectionForTSF = SelectionForTSF();
5556 if (selectionForTSF.isNothing()) {
5557 MOZ_LOG(gIMELog, LogLevel::Error,
5558 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5559 "SelectionForTSF() failure",
5560 this));
5561 return S_OK; // Don't return error only when we're logging.
5563 MOZ_LOG(gIMELog, LogLevel::Info,
5564 ("0x%p TSFTextStore::OnUpdateComposition() succeeded: "
5565 "mComposition=%s, SelectionForTSF()=%s",
5566 this, ToString(mComposition).c_str(),
5567 ToString(selectionForTSF).c_str()));
5569 return S_OK;
5572 STDMETHODIMP
5573 TSFTextStore::OnEndComposition(ITfCompositionView* pComposition) {
5574 MOZ_LOG(gIMELog, LogLevel::Info,
5575 ("0x%p TSFTextStore::OnEndComposition(pComposition=0x%p), "
5576 "mComposition=%s",
5577 this, pComposition, ToString(mComposition).c_str()));
5579 AutoPendingActionAndContentFlusher flusher(this);
5581 if (mComposition.isNothing()) {
5582 MOZ_LOG(gIMELog, LogLevel::Error,
5583 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5584 "no active composition",
5585 this));
5586 return E_UNEXPECTED;
5589 if (mComposition->GetView() != pComposition) {
5590 MOZ_LOG(gIMELog, LogLevel::Error,
5591 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5592 "different composition view specified",
5593 this));
5594 return E_UNEXPECTED;
5597 HRESULT hr = RecordCompositionEndAction();
5598 if (FAILED(hr)) {
5599 MOZ_LOG(gIMELog, LogLevel::Error,
5600 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5601 "RecordCompositionEndAction() failure",
5602 this));
5603 return hr;
5606 MOZ_LOG(gIMELog, LogLevel::Info,
5607 ("0x%p TSFTextStore::OnEndComposition(), succeeded", this));
5608 return S_OK;
5611 STDMETHODIMP
5612 TSFTextStore::AdviseMouseSink(ITfRangeACP* range, ITfMouseSink* pSink,
5613 DWORD* pdwCookie) {
5614 MOZ_LOG(gIMELog, LogLevel::Info,
5615 ("0x%p TSFTextStore::AdviseMouseSink(range=0x%p, pSink=0x%p, "
5616 "pdwCookie=0x%p)",
5617 this, range, pSink, pdwCookie));
5619 if (!pdwCookie) {
5620 MOZ_LOG(gIMELog, LogLevel::Error,
5621 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5622 "pdwCookie is null",
5623 this));
5624 return E_INVALIDARG;
5626 // Initialize the result with invalid cookie for safety.
5627 *pdwCookie = MouseTracker::kInvalidCookie;
5629 if (!range) {
5630 MOZ_LOG(gIMELog, LogLevel::Error,
5631 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5632 "range is null",
5633 this));
5634 return E_INVALIDARG;
5636 if (!pSink) {
5637 MOZ_LOG(gIMELog, LogLevel::Error,
5638 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5639 "pSink is null",
5640 this));
5641 return E_INVALIDARG;
5644 // Looking for an unusing tracker.
5645 MouseTracker* tracker = nullptr;
5646 for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
5647 if (mMouseTrackers[i].IsUsing()) {
5648 continue;
5650 tracker = &mMouseTrackers[i];
5652 // If there is no unusing tracker, create new one.
5653 // XXX Should we make limitation of the number of installs?
5654 if (!tracker) {
5655 tracker = mMouseTrackers.AppendElement();
5656 HRESULT hr = tracker->Init(this);
5657 if (FAILED(hr)) {
5658 MOZ_LOG(gIMELog, LogLevel::Error,
5659 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to "
5660 "failure of MouseTracker::Init()",
5661 this));
5662 return hr;
5665 HRESULT hr = tracker->AdviseSink(this, range, pSink);
5666 if (FAILED(hr)) {
5667 MOZ_LOG(gIMELog, LogLevel::Error,
5668 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to failure "
5669 "of MouseTracker::Init()",
5670 this));
5671 return hr;
5673 *pdwCookie = tracker->Cookie();
5674 MOZ_LOG(gIMELog, LogLevel::Info,
5675 ("0x%p TSFTextStore::AdviseMouseSink(), succeeded, "
5676 "*pdwCookie=%ld",
5677 this, *pdwCookie));
5678 return S_OK;
5681 STDMETHODIMP
5682 TSFTextStore::UnadviseMouseSink(DWORD dwCookie) {
5683 MOZ_LOG(
5684 gIMELog, LogLevel::Info,
5685 ("0x%p TSFTextStore::UnadviseMouseSink(dwCookie=%ld)", this, dwCookie));
5686 if (dwCookie == MouseTracker::kInvalidCookie) {
5687 MOZ_LOG(gIMELog, LogLevel::Error,
5688 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5689 "the cookie is invalid value",
5690 this));
5691 return E_INVALIDARG;
5693 // The cookie value must be an index of mMouseTrackers.
5694 // We can use this shortcut for now.
5695 if (static_cast<size_t>(dwCookie) >= mMouseTrackers.Length()) {
5696 MOZ_LOG(gIMELog, LogLevel::Error,
5697 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5698 "the cookie is too large value",
5699 this));
5700 return E_INVALIDARG;
5702 MouseTracker& tracker = mMouseTrackers[dwCookie];
5703 if (!tracker.IsUsing()) {
5704 MOZ_LOG(gIMELog, LogLevel::Error,
5705 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5706 "the found tracker uninstalled already",
5707 this));
5708 return E_INVALIDARG;
5710 tracker.UnadviseSink();
5711 MOZ_LOG(gIMELog, LogLevel::Info,
5712 ("0x%p TSFTextStore::UnadviseMouseSink(), succeeded", this));
5713 return S_OK;
5716 // static
5717 nsresult TSFTextStore::OnFocusChange(bool aGotFocus, nsWindow* aFocusedWidget,
5718 const InputContext& aContext) {
5719 MOZ_LOG(gIMELog, LogLevel::Debug,
5720 (" TSFTextStore::OnFocusChange(aGotFocus=%s, "
5721 "aFocusedWidget=0x%p, aContext=%s), "
5722 "sThreadMgr=0x%p, sEnabledTextStore=0x%p",
5723 GetBoolName(aGotFocus), aFocusedWidget,
5724 mozilla::ToString(aContext).c_str(), sThreadMgr.get(),
5725 sEnabledTextStore.get()));
5727 if (NS_WARN_IF(!IsInTSFMode())) {
5728 return NS_ERROR_NOT_AVAILABLE;
5731 RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
5732 bool hasFocus = ThinksHavingFocus();
5733 RefPtr<TSFTextStore> oldTextStore = sEnabledTextStore.forget();
5735 // If currently oldTextStore still has focus, notifies TSF of losing focus.
5736 if (hasFocus) {
5737 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5738 DebugOnly<HRESULT> hr = threadMgr->AssociateFocus(
5739 oldTextStore->mWidget->GetWindowHandle(), nullptr,
5740 getter_AddRefs(prevFocusedDocumentMgr));
5741 NS_ASSERTION(SUCCEEDED(hr), "Disassociating focus failed");
5742 NS_ASSERTION(prevFocusedDocumentMgr == oldTextStore->mDocumentMgr,
5743 "different documentMgr has been associated with the window");
5746 // Even if there was a focused TextStore, we won't use it with new focused
5747 // editor. So, release it now.
5748 if (oldTextStore) {
5749 oldTextStore->Destroy();
5752 if (NS_WARN_IF(!sThreadMgr)) {
5753 MOZ_LOG(gIMELog, LogLevel::Error,
5754 (" TSFTextStore::OnFocusChange() FAILED, due to "
5755 "sThreadMgr being destroyed during calling "
5756 "ITfThreadMgr::AssociateFocus()"));
5757 return NS_ERROR_FAILURE;
5759 if (NS_WARN_IF(sEnabledTextStore)) {
5760 MOZ_LOG(
5761 gIMELog, LogLevel::Error,
5762 (" TSFTextStore::OnFocusChange() FAILED, due to "
5763 "nested event handling has created another focused TextStore during "
5764 "calling ITfThreadMgr::AssociateFocus()"));
5765 return NS_ERROR_FAILURE;
5768 // If this is a notification of blur, move focus to the dummy document
5769 // manager.
5770 if (!aGotFocus || !aContext.mIMEState.IsEditable()) {
5771 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5772 RefPtr<ITfDocumentMgr> disabledDocumentMgr = sDisabledDocumentMgr;
5773 HRESULT hr = threadMgr->SetFocus(disabledDocumentMgr);
5774 if (NS_WARN_IF(FAILED(hr))) {
5775 MOZ_LOG(gIMELog, LogLevel::Error,
5776 (" TSFTextStore::OnFocusChange() FAILED due to "
5777 "ITfThreadMgr::SetFocus() failure"));
5778 return NS_ERROR_FAILURE;
5780 return NS_OK;
5783 // If an editor is getting focus, create new TextStore and set focus.
5784 if (NS_WARN_IF(!CreateAndSetFocus(aFocusedWidget, aContext))) {
5785 MOZ_LOG(gIMELog, LogLevel::Error,
5786 (" TSFTextStore::OnFocusChange() FAILED due to "
5787 "ITfThreadMgr::CreateAndSetFocus() failure"));
5788 return NS_ERROR_FAILURE;
5790 return NS_OK;
5793 // static
5794 void TSFTextStore::EnsureToDestroyAndReleaseEnabledTextStoreIf(
5795 RefPtr<TSFTextStore>& aTextStore) {
5796 aTextStore->Destroy();
5797 if (sEnabledTextStore == aTextStore) {
5798 sEnabledTextStore = nullptr;
5800 aTextStore = nullptr;
5803 // static
5804 bool TSFTextStore::CreateAndSetFocus(nsWindow* aFocusedWidget,
5805 const InputContext& aContext) {
5806 // TSF might do something which causes that we need to access static methods
5807 // of TSFTextStore. At that time, sEnabledTextStore may be necessary.
5808 // So, we should set sEnabledTextStore directly.
5809 RefPtr<TSFTextStore> textStore = new TSFTextStore();
5810 sEnabledTextStore = textStore;
5811 if (NS_WARN_IF(!textStore->Init(aFocusedWidget, aContext))) {
5812 MOZ_LOG(gIMELog, LogLevel::Error,
5813 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5814 "TSFTextStore::Init() failure"));
5815 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5816 return false;
5818 RefPtr<ITfDocumentMgr> newDocMgr = textStore->mDocumentMgr;
5819 if (NS_WARN_IF(!newDocMgr)) {
5820 MOZ_LOG(gIMELog, LogLevel::Error,
5821 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5822 "invalid TSFTextStore::mDocumentMgr"));
5823 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5824 return false;
5826 if (aContext.mIMEState.mEnabled == IMEEnabled::Password) {
5827 MarkContextAsKeyboardDisabled(textStore->mContext);
5828 RefPtr<ITfContext> topContext;
5829 newDocMgr->GetTop(getter_AddRefs(topContext));
5830 if (topContext && topContext != textStore->mContext) {
5831 MarkContextAsKeyboardDisabled(topContext);
5835 HRESULT hr;
5836 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5837 hr = threadMgr->SetFocus(newDocMgr);
5839 if (NS_WARN_IF(FAILED(hr))) {
5840 MOZ_LOG(gIMELog, LogLevel::Error,
5841 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5842 "ITfTheadMgr::SetFocus() failure"));
5843 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5844 return false;
5846 if (NS_WARN_IF(!sThreadMgr)) {
5847 MOZ_LOG(gIMELog, LogLevel::Error,
5848 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5849 "sThreadMgr being destroyed during calling "
5850 "ITfTheadMgr::SetFocus()"));
5851 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5852 return false;
5854 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5855 MOZ_LOG(gIMELog, LogLevel::Error,
5856 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5857 "creating TextStore has lost focus during calling "
5858 "ITfThreadMgr::SetFocus()"));
5859 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5860 return false;
5863 // Use AssociateFocus() for ensuring that any native focus event
5864 // never steal focus from our documentMgr.
5865 RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
5866 hr = threadMgr->AssociateFocus(aFocusedWidget->GetWindowHandle(), newDocMgr,
5867 getter_AddRefs(prevFocusedDocumentMgr));
5868 if (NS_WARN_IF(FAILED(hr))) {
5869 MOZ_LOG(gIMELog, LogLevel::Error,
5870 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5871 "ITfTheadMgr::AssociateFocus() failure"));
5872 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5873 return false;
5875 if (NS_WARN_IF(!sThreadMgr)) {
5876 MOZ_LOG(gIMELog, LogLevel::Error,
5877 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5878 "sThreadMgr being destroyed during calling "
5879 "ITfTheadMgr::AssociateFocus()"));
5880 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5881 return false;
5883 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5884 MOZ_LOG(gIMELog, LogLevel::Error,
5885 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5886 "creating TextStore has lost focus during calling "
5887 "ITfTheadMgr::AssociateFocus()"));
5888 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5889 return false;
5892 if (textStore->mSink) {
5893 MOZ_LOG(gIMELog, LogLevel::Info,
5894 (" TSFTextStore::CreateAndSetFocus(), calling "
5895 "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE) for 0x%p...",
5896 textStore.get()));
5897 RefPtr<ITextStoreACPSink> sink = textStore->mSink;
5898 sink->OnLayoutChange(TS_LC_CREATE, TEXTSTORE_DEFAULT_VIEW);
5899 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5900 MOZ_LOG(gIMELog, LogLevel::Error,
5901 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5902 "creating TextStore has lost focus during calling "
5903 "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE)"));
5904 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5905 return false;
5908 return true;
5911 // static
5912 IMENotificationRequests TSFTextStore::GetIMENotificationRequests() {
5913 if (!sEnabledTextStore || NS_WARN_IF(!sEnabledTextStore->mDocumentMgr)) {
5914 // If there is no active text store, we don't need any notifications
5915 // since there is no sink which needs notifications.
5916 return IMENotificationRequests();
5919 // Otherwise, requests all notifications since even if some of them may not
5920 // be required by the sink of active TIP, active TIP may be changed and
5921 // other TIPs may need all notifications.
5922 // Note that Windows temporarily steal focus from active window if the main
5923 // process which created the window becomes busy. In this case, we shouldn't
5924 // commit composition since user may want to continue to compose the
5925 // composition after becoming not busy. Therefore, we need notifications
5926 // even during deactive.
5927 // Be aware, we don't need to check actual focused text store. For example,
5928 // MS-IME for Japanese handles focus messages by themselves and sets focused
5929 // text store to nullptr when the process is being inactivated. However,
5930 // we still need to reuse sEnabledTextStore if the process is activated and
5931 // focused element isn't changed. Therefore, if sEnabledTextStore isn't
5932 // nullptr, we need to keep notifying the sink even when it is not focused
5933 // text store for the thread manager.
5934 return IMENotificationRequests(
5935 IMENotificationRequests::NOTIFY_TEXT_CHANGE |
5936 IMENotificationRequests::NOTIFY_POSITION_CHANGE |
5937 IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR |
5938 IMENotificationRequests::NOTIFY_DURING_DEACTIVE);
5941 nsresult TSFTextStore::OnTextChangeInternal(
5942 const IMENotification& aIMENotification) {
5943 const TextChangeDataBase& textChangeData = aIMENotification.mTextChangeData;
5945 MOZ_LOG(gIMELog, LogLevel::Debug,
5946 ("0x%p TSFTextStore::OnTextChangeInternal(aIMENotification={ "
5947 "mMessage=0x%08X, mTextChangeData=%s }), "
5948 "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, "
5949 "mComposition=%s",
5950 this, aIMENotification.mMessage,
5951 mozilla::ToString(textChangeData).c_str(), GetBoolName(mDestroyed),
5952 mSink.get(), GetSinkMaskNameStr(mSinkMask).get(),
5953 ToString(mComposition).c_str()));
5955 if (mDestroyed) {
5956 // If this instance is already destroyed, we shouldn't notify TSF of any
5957 // changes.
5958 return NS_OK;
5961 mDeferNotifyingTSFUntilNextUpdate = false;
5963 // Different from selection change, we don't modify anything with text
5964 // change data. Therefore, if neither TSF not TIP wants text change
5965 // notifications, we don't need to store the changes.
5966 if (!mSink || !(mSinkMask & TS_AS_TEXT_CHANGE)) {
5967 return NS_OK;
5970 // Merge any text change data even if it's caused by composition.
5971 mPendingTextChangeData.MergeWith(textChangeData);
5973 MaybeFlushPendingNotifications();
5975 return NS_OK;
5978 void TSFTextStore::NotifyTSFOfTextChange() {
5979 MOZ_ASSERT(!mDestroyed);
5980 MOZ_ASSERT(!IsReadLocked());
5981 MOZ_ASSERT(mComposition.isNothing());
5982 MOZ_ASSERT(mPendingTextChangeData.IsValid());
5984 // If the text changes are caused only by composition, we don't need to
5985 // notify TSF of the text changes.
5986 if (mPendingTextChangeData.mCausedOnlyByComposition) {
5987 mPendingTextChangeData.Clear();
5988 return;
5991 // First, forget cached selection.
5992 mSelectionForTSF.reset();
5994 // For making it safer, we should check if there is a valid sink to receive
5995 // text change notification.
5996 if (NS_WARN_IF(!mSink) || NS_WARN_IF(!(mSinkMask & TS_AS_TEXT_CHANGE))) {
5997 MOZ_LOG(gIMELog, LogLevel::Error,
5998 ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
5999 "mSink is not ready to call ITextStoreACPSink::OnTextChange()...",
6000 this));
6001 mPendingTextChangeData.Clear();
6002 return;
6005 if (NS_WARN_IF(!mPendingTextChangeData.IsInInt32Range())) {
6006 MOZ_LOG(gIMELog, LogLevel::Error,
6007 ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
6008 "offset is too big for calling "
6009 "ITextStoreACPSink::OnTextChange()...",
6010 this));
6011 mPendingTextChangeData.Clear();
6012 return;
6015 TS_TEXTCHANGE textChange;
6016 textChange.acpStart = static_cast<LONG>(mPendingTextChangeData.mStartOffset);
6017 textChange.acpOldEnd =
6018 static_cast<LONG>(mPendingTextChangeData.mRemovedEndOffset);
6019 textChange.acpNewEnd =
6020 static_cast<LONG>(mPendingTextChangeData.mAddedEndOffset);
6021 mPendingTextChangeData.Clear();
6023 MOZ_LOG(
6024 gIMELog, LogLevel::Info,
6025 ("0x%p TSFTextStore::NotifyTSFOfTextChange(), calling "
6026 "ITextStoreACPSink::OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
6027 "acpNewEnd=%ld })...",
6028 this, textChange.acpStart, textChange.acpOldEnd, textChange.acpNewEnd));
6029 RefPtr<ITextStoreACPSink> sink = mSink;
6030 sink->OnTextChange(0, &textChange);
6033 nsresult TSFTextStore::OnSelectionChangeInternal(
6034 const IMENotification& aIMENotification) {
6035 const SelectionChangeDataBase& selectionChangeData =
6036 aIMENotification.mSelectionChangeData;
6037 MOZ_LOG(gIMELog, LogLevel::Debug,
6038 ("0x%p TSFTextStore::OnSelectionChangeInternal("
6039 "aIMENotification={ mSelectionChangeData=%s }), mDestroyed=%s, "
6040 "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, "
6041 "mComposition=%s",
6042 this, mozilla::ToString(selectionChangeData).c_str(),
6043 GetBoolName(mDestroyed), mSink.get(),
6044 GetSinkMaskNameStr(mSinkMask).get(),
6045 GetBoolName(mIsRecordingActionsWithoutLock),
6046 ToString(mComposition).c_str()));
6048 if (mDestroyed) {
6049 // If this instance is already destroyed, we shouldn't notify TSF of any
6050 // changes.
6051 return NS_OK;
6054 mDeferNotifyingTSFUntilNextUpdate = false;
6056 // Assign the new selection change data to the pending selection change data
6057 // because only the latest selection data is necessary.
6058 // Note that this is necessary to update mSelectionForTSF. Therefore, even if
6059 // neither TSF nor TIP wants selection change notifications, we need to
6060 // store the selection information.
6061 mPendingSelectionChangeData = Some(selectionChangeData);
6063 // Flush remaining pending notifications here if it's possible.
6064 MaybeFlushPendingNotifications();
6066 // If we're available, we should create native caret instead of IMEHandler
6067 // because we may have some cache to do it.
6068 // Note that if we have composition, we'll notified composition-updated
6069 // later so that we don't need to create native caret in such case.
6070 if (!IsHandlingCompositionInContent() &&
6071 IMEHandler::NeedsToCreateNativeCaret()) {
6072 CreateNativeCaret();
6075 return NS_OK;
6078 void TSFTextStore::NotifyTSFOfSelectionChange() {
6079 MOZ_ASSERT(!mDestroyed);
6080 MOZ_ASSERT(!IsReadLocked());
6081 MOZ_ASSERT(mComposition.isNothing());
6082 MOZ_ASSERT(mPendingSelectionChangeData.isSome());
6084 // If selection range isn't actually changed, we don't need to notify TSF
6085 // of this selection change.
6086 if (mSelectionForTSF.isNothing()) {
6087 mSelectionForTSF.emplace(*mPendingSelectionChangeData);
6088 } else if (!mSelectionForTSF->SetSelection(*mPendingSelectionChangeData)) {
6089 mPendingSelectionChangeData.reset();
6090 MOZ_LOG(gIMELog, LogLevel::Debug,
6091 ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), "
6092 "selection isn't actually changed.",
6093 this));
6094 return;
6097 mPendingSelectionChangeData.reset();
6099 if (!mSink || !(mSinkMask & TS_AS_SEL_CHANGE)) {
6100 return;
6103 MOZ_LOG(gIMELog, LogLevel::Info,
6104 ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), calling "
6105 "ITextStoreACPSink::OnSelectionChange()...",
6106 this));
6107 RefPtr<ITextStoreACPSink> sink = mSink;
6108 sink->OnSelectionChange();
6111 nsresult TSFTextStore::OnLayoutChangeInternal() {
6112 if (mDestroyed) {
6113 // If this instance is already destroyed, we shouldn't notify TSF of any
6114 // changes.
6115 return NS_OK;
6118 NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE);
6119 NS_ENSURE_TRUE(mSink, NS_ERROR_FAILURE);
6121 mDeferNotifyingTSFUntilNextUpdate = false;
6123 nsresult rv = NS_OK;
6125 // We need to notify TSF of layout change even if the document is locked.
6126 // So, don't use MaybeFlushPendingNotifications() for flushing pending
6127 // layout change.
6128 MOZ_LOG(gIMELog, LogLevel::Info,
6129 ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
6130 "NotifyTSFOfLayoutChange()...",
6131 this));
6132 if (NS_WARN_IF(!NotifyTSFOfLayoutChange())) {
6133 rv = NS_ERROR_FAILURE;
6136 MOZ_LOG(gIMELog, LogLevel::Debug,
6137 ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
6138 "MaybeFlushPendingNotifications()...",
6139 this));
6140 MaybeFlushPendingNotifications();
6142 return rv;
6145 bool TSFTextStore::NotifyTSFOfLayoutChange() {
6146 MOZ_ASSERT(!mDestroyed);
6148 // If we're waiting a query of layout information from TIP, it means that
6149 // we've returned TS_E_NOLAYOUT error.
6150 bool returnedNoLayoutError = mHasReturnedNoLayoutError || mWaitingQueryLayout;
6152 // If we returned TS_E_NOLAYOUT, TIP should query the computed layout again.
6153 mWaitingQueryLayout = returnedNoLayoutError;
6155 // For avoiding to call this method again at unlocking the document during
6156 // calls of OnLayoutChange(), reset mHasReturnedNoLayoutError.
6157 mHasReturnedNoLayoutError = false;
6159 // Now, layout has been computed. We should notify mContentForTSF for
6160 // making GetTextExt() and GetACPFromPoint() not return TS_E_NOLAYOUT.
6161 if (mContentForTSF.isSome()) {
6162 mContentForTSF->OnLayoutChanged();
6165 if (IMEHandler::NeedsToCreateNativeCaret()) {
6166 // If we're available, we should create native caret instead of IMEHandler
6167 // because we may have some cache to do it.
6168 CreateNativeCaret();
6169 } else {
6170 // Now, the caret position is different from ours. Destroy the native caret
6171 // if we've create it only for GetTextExt().
6172 IMEHandler::MaybeDestroyNativeCaret();
6175 // This method should return true if either way succeeds.
6176 bool ret = true;
6178 if (mSink) {
6179 MOZ_LOG(gIMELog, LogLevel::Info,
6180 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6181 "calling ITextStoreACPSink::OnLayoutChange()...",
6182 this));
6183 RefPtr<ITextStoreACPSink> sink = mSink;
6184 HRESULT hr = sink->OnLayoutChange(TS_LC_CHANGE, TEXTSTORE_DEFAULT_VIEW);
6185 MOZ_LOG(gIMELog, LogLevel::Info,
6186 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6187 "called ITextStoreACPSink::OnLayoutChange()",
6188 this));
6189 ret = SUCCEEDED(hr);
6192 // The layout change caused by composition string change should cause
6193 // calling ITfContextOwnerServices::OnLayoutChange() too.
6194 if (returnedNoLayoutError && mContext) {
6195 RefPtr<ITfContextOwnerServices> service;
6196 mContext->QueryInterface(IID_ITfContextOwnerServices,
6197 getter_AddRefs(service));
6198 if (service) {
6199 MOZ_LOG(gIMELog, LogLevel::Info,
6200 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6201 "calling ITfContextOwnerServices::OnLayoutChange()...",
6202 this));
6203 HRESULT hr = service->OnLayoutChange();
6204 ret = ret && SUCCEEDED(hr);
6205 MOZ_LOG(gIMELog, LogLevel::Info,
6206 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6207 "called ITfContextOwnerServices::OnLayoutChange()",
6208 this));
6212 if (!mWidget || mWidget->Destroyed()) {
6213 MOZ_LOG(gIMELog, LogLevel::Info,
6214 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6215 "the widget is destroyed during calling OnLayoutChange()",
6216 this));
6217 return ret;
6220 if (mDestroyed) {
6221 MOZ_LOG(gIMELog, LogLevel::Info,
6222 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6223 "the TSFTextStore instance is destroyed during calling "
6224 "OnLayoutChange()",
6225 this));
6226 return ret;
6229 // If we returned TS_E_NOLAYOUT again, we need another call of
6230 // OnLayoutChange() later. So, let's wait a query from TIP.
6231 if (mHasReturnedNoLayoutError) {
6232 mWaitingQueryLayout = true;
6235 if (!mWaitingQueryLayout) {
6236 MOZ_LOG(gIMELog, LogLevel::Info,
6237 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6238 "succeeded notifying TIP of our layout change",
6239 this));
6240 return ret;
6243 // If we believe that TIP needs to retry to retrieve our layout information
6244 // later, we should call it with ::PostMessage() hack.
6245 MOZ_LOG(gIMELog, LogLevel::Debug,
6246 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6247 "posing MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE for calling "
6248 "OnLayoutChange() again...",
6249 this));
6250 ::PostMessage(mWidget->GetWindowHandle(), MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE,
6251 reinterpret_cast<WPARAM>(this), 0);
6253 return true;
6256 void TSFTextStore::NotifyTSFOfLayoutChangeAgain() {
6257 // Don't notify TSF of layout change after destroyed.
6258 if (mDestroyed) {
6259 mWaitingQueryLayout = false;
6260 return;
6263 // Before preforming this method, TIP has accessed our layout information by
6264 // itself. In such case, we don't need to call OnLayoutChange() anymore.
6265 if (!mWaitingQueryLayout) {
6266 return;
6269 MOZ_LOG(gIMELog, LogLevel::Info,
6270 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6271 "calling NotifyTSFOfLayoutChange()...",
6272 this));
6273 NotifyTSFOfLayoutChange();
6275 // If TIP didn't retrieved our layout information during a call of
6276 // NotifyTSFOfLayoutChange(), it means that the TIP already gave up to
6277 // retry to retrieve layout information or doesn't necessary it anymore.
6278 // But don't forget that the call may have caused returning TS_E_NOLAYOUT
6279 // error again. In such case we still need to call OnLayoutChange() later.
6280 if (!mHasReturnedNoLayoutError && mWaitingQueryLayout) {
6281 mWaitingQueryLayout = false;
6282 MOZ_LOG(gIMELog, LogLevel::Warning,
6283 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6284 "called NotifyTSFOfLayoutChange() but TIP didn't retry to "
6285 "retrieve the layout information",
6286 this));
6287 } else {
6288 MOZ_LOG(gIMELog, LogLevel::Info,
6289 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6290 "called NotifyTSFOfLayoutChange()",
6291 this));
6295 nsresult TSFTextStore::OnUpdateCompositionInternal() {
6296 MOZ_LOG(gIMELog, LogLevel::Debug,
6297 ("0x%p TSFTextStore::OnUpdateCompositionInternal(), "
6298 "mDestroyed=%s, mDeferNotifyingTSFUntilNextUpdate=%s",
6299 this, GetBoolName(mDestroyed),
6300 GetBoolName(mDeferNotifyingTSFUntilNextUpdate)));
6302 // There are nothing to do after destroyed.
6303 if (mDestroyed) {
6304 return NS_OK;
6307 // Update cached data now because all pending events have been handled now.
6308 if (mContentForTSF.isSome()) {
6309 mContentForTSF->OnCompositionEventsHandled();
6312 // If composition is completely finished both in TSF/TIP and the focused
6313 // editor which may be in a remote process, we can clear the cache and don't
6314 // have it until starting next composition.
6315 if (mComposition.isNothing() && !IsHandlingCompositionInContent()) {
6316 mDeferClearingContentForTSF = false;
6318 mDeferNotifyingTSFUntilNextUpdate = false;
6319 MaybeFlushPendingNotifications();
6321 // If we're available, we should create native caret instead of IMEHandler
6322 // because we may have some cache to do it.
6323 if (IMEHandler::NeedsToCreateNativeCaret()) {
6324 CreateNativeCaret();
6327 return NS_OK;
6330 nsresult TSFTextStore::OnMouseButtonEventInternal(
6331 const IMENotification& aIMENotification) {
6332 if (mDestroyed) {
6333 // If this instance is already destroyed, we shouldn't notify TSF of any
6334 // events.
6335 return NS_OK;
6338 if (mMouseTrackers.IsEmpty()) {
6339 return NS_OK;
6342 MOZ_LOG(gIMELog, LogLevel::Debug,
6343 ("0x%p TSFTextStore::OnMouseButtonEventInternal("
6344 "aIMENotification={ mEventMessage=%s, mOffset=%u, mCursorPos=%s, "
6345 "mCharRect=%s, mButton=%s, mButtons=%s, mModifiers=%s })",
6346 this, ToChar(aIMENotification.mMouseButtonEventData.mEventMessage),
6347 aIMENotification.mMouseButtonEventData.mOffset,
6348 ToString(aIMENotification.mMouseButtonEventData.mCursorPos).c_str(),
6349 ToString(aIMENotification.mMouseButtonEventData.mCharRect).c_str(),
6350 GetMouseButtonName(aIMENotification.mMouseButtonEventData.mButton),
6351 GetMouseButtonsName(aIMENotification.mMouseButtonEventData.mButtons)
6352 .get(),
6353 GetModifiersName(aIMENotification.mMouseButtonEventData.mModifiers)
6354 .get()));
6356 uint32_t offset = aIMENotification.mMouseButtonEventData.mOffset;
6357 if (offset > static_cast<uint32_t>(LONG_MAX)) {
6358 return NS_OK;
6360 LayoutDeviceIntRect charRect =
6361 aIMENotification.mMouseButtonEventData.mCharRect;
6362 LayoutDeviceIntPoint cursorPos =
6363 aIMENotification.mMouseButtonEventData.mCursorPos;
6364 ULONG quadrant = 1;
6365 if (charRect.Width() > 0) {
6366 int32_t cursorXInChar = cursorPos.x - charRect.X();
6367 quadrant = cursorXInChar * 4 / charRect.Width();
6368 quadrant = (quadrant + 2) % 4;
6370 ULONG edge = quadrant < 2 ? offset + 1 : offset;
6371 DWORD buttonStatus = 0;
6372 bool isMouseUp =
6373 aIMENotification.mMouseButtonEventData.mEventMessage == eMouseUp;
6374 if (!isMouseUp) {
6375 switch (aIMENotification.mMouseButtonEventData.mButton) {
6376 case MouseButton::ePrimary:
6377 buttonStatus = MK_LBUTTON;
6378 break;
6379 case MouseButton::eMiddle:
6380 buttonStatus = MK_MBUTTON;
6381 break;
6382 case MouseButton::eSecondary:
6383 buttonStatus = MK_RBUTTON;
6384 break;
6387 if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_CONTROL) {
6388 buttonStatus |= MK_CONTROL;
6390 if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_SHIFT) {
6391 buttonStatus |= MK_SHIFT;
6393 for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
6394 MouseTracker& tracker = mMouseTrackers[i];
6395 if (!tracker.IsUsing() || tracker.Range().isNothing() ||
6396 !tracker.Range()->IsOffsetInRange(offset)) {
6397 continue;
6399 if (tracker.OnMouseButtonEvent(edge - tracker.Range()->StartOffset(),
6400 quadrant, buttonStatus)) {
6401 return NS_SUCCESS_EVENT_CONSUMED;
6404 return NS_OK;
6407 void TSFTextStore::CreateNativeCaret() {
6408 MOZ_ASSERT(!IMEHandler::IsA11yHandlingNativeCaret());
6410 IMEHandler::MaybeDestroyNativeCaret();
6412 // Don't create native caret after destroyed.
6413 if (mDestroyed) {
6414 return;
6417 MOZ_LOG(gIMELog, LogLevel::Debug,
6418 ("0x%p TSFTextStore::CreateNativeCaret(), mComposition=%s", this,
6419 ToString(mComposition).c_str()));
6421 Maybe<Selection>& selectionForTSF = SelectionForTSF();
6422 if (MOZ_UNLIKELY(selectionForTSF.isNothing())) {
6423 MOZ_LOG(gIMELog, LogLevel::Error,
6424 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6425 "SelectionForTSF() failure",
6426 this));
6427 return;
6429 if (!selectionForTSF->HasRange() && mComposition.isNothing()) {
6430 // If there is no selection range nor composition, then, we don't have a
6431 // good position to show windows of TIP...
6432 // XXX It seems that storing last caret rect and using it in this case might
6433 // be better?
6434 MOZ_LOG(gIMELog, LogLevel::Warning,
6435 ("0x%p TSFTextStore::CreateNativeCaret() couludn't create native "
6436 "caret due to no selection range",
6437 this));
6438 return;
6441 WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, mWidget);
6442 mWidget->InitEvent(queryCaretRectEvent);
6444 WidgetQueryContentEvent::Options options;
6445 // XXX If this is called without composition and the selection isn't
6446 // collapsed, is it OK?
6447 int64_t caretOffset = selectionForTSF->HasRange()
6448 ? selectionForTSF->MaxOffset()
6449 : mComposition->StartOffset();
6450 if (mComposition.isSome()) {
6451 // If there is a composition, use the relative query for deciding caret
6452 // position because composition might be different place from that
6453 // TSFTextStore assumes.
6454 options.mRelativeToInsertionPoint = true;
6455 caretOffset -= mComposition->StartOffset();
6456 } else if (!CanAccessActualContentDirectly()) {
6457 // If TSF/TIP cannot access actual content directly, there may be pending
6458 // text and/or selection changes which have not been notified TSF yet.
6459 // Therefore, we should use the relative query from start of selection where
6460 // TSFTextStore assumes since TSF/TIP computes the offset from our cached
6461 // selection.
6462 options.mRelativeToInsertionPoint = true;
6463 caretOffset -= selectionForTSF->StartOffset();
6465 queryCaretRectEvent.InitForQueryCaretRect(caretOffset, options);
6467 DispatchEvent(queryCaretRectEvent);
6468 if (NS_WARN_IF(queryCaretRectEvent.Failed())) {
6469 MOZ_LOG(gIMELog, LogLevel::Error,
6470 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6471 "eQueryCaretRect failure (offset=%lld)",
6472 this, caretOffset));
6473 return;
6476 if (!IMEHandler::CreateNativeCaret(static_cast<nsWindow*>(mWidget.get()),
6477 queryCaretRectEvent.mReply->mRect)) {
6478 MOZ_LOG(gIMELog, LogLevel::Error,
6479 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6480 "IMEHandler::CreateNativeCaret() failure",
6481 this));
6482 return;
6486 void TSFTextStore::CommitCompositionInternal(bool aDiscard) {
6487 MOZ_LOG(gIMELog, LogLevel::Debug,
6488 ("0x%p TSFTextStore::CommitCompositionInternal(aDiscard=%s), "
6489 "mSink=0x%p, mContext=0x%p, mComposition=%s",
6490 this, GetBoolName(aDiscard), mSink.get(), mContext.get(),
6491 ToString(mComposition).c_str()));
6493 // If the document is locked, TSF will fail to commit composition since
6494 // TSF needs another document lock. So, let's put off the request.
6495 // Note that TextComposition will commit composition in the focused editor
6496 // with the latest composition string for web apps and waits asynchronous
6497 // committing messages. Therefore, we can and need to perform this
6498 // asynchronously.
6499 if (IsReadLocked()) {
6500 if (mDeferCommittingComposition || mDeferCancellingComposition) {
6501 MOZ_LOG(gIMELog, LogLevel::Debug,
6502 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6503 "does nothing because already called and waiting unlock...",
6504 this));
6505 return;
6507 if (aDiscard) {
6508 mDeferCancellingComposition = true;
6509 } else {
6510 mDeferCommittingComposition = true;
6512 MOZ_LOG(gIMELog, LogLevel::Debug,
6513 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6514 "putting off to request to %s composition after unlocking the "
6515 "document",
6516 this, aDiscard ? "cancel" : "commit"));
6517 return;
6520 if (mComposition.isSome() && aDiscard) {
6521 LONG endOffset = mComposition->EndOffset();
6522 mComposition->SetData(EmptyString());
6523 // Note that don't notify TSF of text change after this is destroyed.
6524 if (mSink && !mDestroyed) {
6525 TS_TEXTCHANGE textChange;
6526 textChange.acpStart = mComposition->StartOffset();
6527 textChange.acpOldEnd = endOffset;
6528 textChange.acpNewEnd = mComposition->StartOffset();
6529 MOZ_LOG(gIMELog, LogLevel::Info,
6530 ("0x%p TSFTextStore::CommitCompositionInternal(), calling"
6531 "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
6532 "acpNewEnd=%ld })...",
6533 this, textChange.acpStart, textChange.acpOldEnd,
6534 textChange.acpNewEnd));
6535 RefPtr<ITextStoreACPSink> sink = mSink;
6536 sink->OnTextChange(0, &textChange);
6539 // Terminate two contexts, the base context (mContext) and the top
6540 // if the top context is not the same as the base context
6541 RefPtr<ITfContext> context = mContext;
6542 do {
6543 if (context) {
6544 RefPtr<ITfContextOwnerCompositionServices> services;
6545 context->QueryInterface(IID_ITfContextOwnerCompositionServices,
6546 getter_AddRefs(services));
6547 if (services) {
6548 MOZ_LOG(gIMELog, LogLevel::Debug,
6549 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6550 "requesting TerminateComposition() for the context 0x%p...",
6551 this, context.get()));
6552 services->TerminateComposition(nullptr);
6555 if (context != mContext) break;
6556 if (mDocumentMgr) mDocumentMgr->GetTop(getter_AddRefs(context));
6557 } while (context != mContext);
6560 static bool GetCompartment(IUnknown* pUnk, const GUID& aID,
6561 ITfCompartment** aCompartment) {
6562 if (!pUnk) return false;
6564 RefPtr<ITfCompartmentMgr> compMgr;
6565 pUnk->QueryInterface(IID_ITfCompartmentMgr, getter_AddRefs(compMgr));
6566 if (!compMgr) return false;
6568 return SUCCEEDED(compMgr->GetCompartment(aID, aCompartment)) &&
6569 (*aCompartment) != nullptr;
6572 // static
6573 void TSFTextStore::SetIMEOpenState(bool aState) {
6574 MOZ_LOG(gIMELog, LogLevel::Debug,
6575 ("TSFTextStore::SetIMEOpenState(aState=%s)", GetBoolName(aState)));
6577 if (!sThreadMgr) {
6578 return;
6581 RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
6582 if (NS_WARN_IF(!comp)) {
6583 MOZ_LOG(gIMELog, LogLevel::Debug,
6584 (" TSFTextStore::SetIMEOpenState() FAILED due to"
6585 "no compartment available"));
6586 return;
6589 VARIANT variant;
6590 variant.vt = VT_I4;
6591 variant.lVal = aState;
6592 HRESULT hr = comp->SetValue(sClientId, &variant);
6593 if (NS_WARN_IF(FAILED(hr))) {
6594 MOZ_LOG(gIMELog, LogLevel::Error,
6595 (" TSFTextStore::SetIMEOpenState() FAILED due to "
6596 "ITfCompartment::SetValue() failure, hr=0x%08lX",
6597 hr));
6598 return;
6600 MOZ_LOG(gIMELog, LogLevel::Debug,
6601 (" TSFTextStore::SetIMEOpenState(), setting "
6602 "0x%04lX to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...",
6603 variant.lVal));
6606 // static
6607 bool TSFTextStore::GetIMEOpenState() {
6608 if (!sThreadMgr) {
6609 return false;
6612 RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
6613 if (NS_WARN_IF(!comp)) {
6614 return false;
6617 VARIANT variant;
6618 ::VariantInit(&variant);
6619 HRESULT hr = comp->GetValue(&variant);
6620 if (NS_WARN_IF(FAILED(hr))) {
6621 MOZ_LOG(gIMELog, LogLevel::Error,
6622 ("TSFTextStore::GetIMEOpenState() FAILED due to "
6623 "ITfCompartment::GetValue() failure, hr=0x%08lX",
6624 hr));
6625 return false;
6627 // Until IME is open in this process, the result may be empty.
6628 if (variant.vt == VT_EMPTY) {
6629 return false;
6631 if (NS_WARN_IF(variant.vt != VT_I4)) {
6632 MOZ_LOG(gIMELog, LogLevel::Error,
6633 ("TSFTextStore::GetIMEOpenState() FAILED due to "
6634 "invalid result of ITfCompartment::GetValue()"));
6635 ::VariantClear(&variant);
6636 return false;
6639 return variant.lVal != 0;
6642 // static
6643 void TSFTextStore::SetInputContext(nsWindow* aWidget,
6644 const InputContext& aContext,
6645 const InputContextAction& aAction) {
6646 MOZ_LOG(gIMELog, LogLevel::Debug,
6647 ("TSFTextStore::SetInputContext(aWidget=%p, "
6648 "aContext=%s, aAction.mFocusChange=%s), "
6649 "sEnabledTextStore(0x%p)={ mWidget=0x%p }, ThinksHavingFocus()=%s",
6650 aWidget, mozilla::ToString(aContext).c_str(),
6651 GetFocusChangeName(aAction.mFocusChange), sEnabledTextStore.get(),
6652 sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr,
6653 GetBoolName(ThinksHavingFocus())));
6655 switch (aAction.mFocusChange) {
6656 case InputContextAction::WIDGET_CREATED:
6657 // If this is called when the widget is created, there is nothing to do.
6658 return;
6659 case InputContextAction::FOCUS_NOT_CHANGED:
6660 case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
6661 if (NS_WARN_IF(!IsInTSFMode())) {
6662 return;
6664 // In these cases, `NOTIFY_IME_OF_FOCUS` won't be sent. Therefore,
6665 // we need to reset text store for new state right now.
6666 break;
6667 default:
6668 NS_WARNING_ASSERTION(IsInTSFMode(),
6669 "Why is this called when TSF is disabled?");
6670 if (sEnabledTextStore) {
6671 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
6672 textStore->mInPrivateBrowsing = aContext.mInPrivateBrowsing;
6673 textStore->SetInputScope(aContext.mHTMLInputType,
6674 aContext.mHTMLInputMode);
6675 if (aContext.mURI) {
6676 nsAutoCString spec;
6677 if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) {
6678 CopyUTF8toUTF16(spec, textStore->mDocumentURL);
6679 } else {
6680 textStore->mDocumentURL.Truncate();
6682 } else {
6683 textStore->mDocumentURL.Truncate();
6686 return;
6689 // If focus isn't actually changed but the enabled state is changed,
6690 // emulate the focus move.
6691 if (!ThinksHavingFocus() && aContext.mIMEState.IsEditable()) {
6692 MOZ_LOG(gIMELog, LogLevel::Debug,
6693 (" TSFTextStore::SetInputContent() emulates focus for IME "
6694 "state change"));
6695 OnFocusChange(true, aWidget, aContext);
6696 } else if (ThinksHavingFocus() && !aContext.mIMEState.IsEditable()) {
6697 MOZ_LOG(gIMELog, LogLevel::Debug,
6698 (" TSFTextStore::SetInputContent() emulates blur for IME "
6699 "state change"));
6700 OnFocusChange(false, aWidget, aContext);
6704 // static
6705 void TSFTextStore::MarkContextAsKeyboardDisabled(ITfContext* aContext) {
6706 VARIANT variant_int4_value1;
6707 variant_int4_value1.vt = VT_I4;
6708 variant_int4_value1.lVal = 1;
6710 RefPtr<ITfCompartment> comp;
6711 if (!GetCompartment(aContext, GUID_COMPARTMENT_KEYBOARD_DISABLED,
6712 getter_AddRefs(comp))) {
6713 MOZ_LOG(gIMELog, LogLevel::Error,
6714 ("TSFTextStore::MarkContextAsKeyboardDisabled() failed"
6715 "aContext=0x%p...",
6716 aContext));
6717 return;
6720 MOZ_LOG(gIMELog, LogLevel::Debug,
6721 ("TSFTextStore::MarkContextAsKeyboardDisabled(), setting "
6722 "to disable context 0x%p...",
6723 aContext));
6724 comp->SetValue(sClientId, &variant_int4_value1);
6727 // static
6728 void TSFTextStore::MarkContextAsEmpty(ITfContext* aContext) {
6729 VARIANT variant_int4_value1;
6730 variant_int4_value1.vt = VT_I4;
6731 variant_int4_value1.lVal = 1;
6733 RefPtr<ITfCompartment> comp;
6734 if (!GetCompartment(aContext, GUID_COMPARTMENT_EMPTYCONTEXT,
6735 getter_AddRefs(comp))) {
6736 MOZ_LOG(gIMELog, LogLevel::Error,
6737 ("TSFTextStore::MarkContextAsEmpty() failed"
6738 "aContext=0x%p...",
6739 aContext));
6740 return;
6743 MOZ_LOG(gIMELog, LogLevel::Debug,
6744 ("TSFTextStore::MarkContextAsEmpty(), setting "
6745 "to mark empty context 0x%p...",
6746 aContext));
6747 comp->SetValue(sClientId, &variant_int4_value1);
6750 // static
6751 void TSFTextStore::Initialize() {
6752 MOZ_LOG(gIMELog, LogLevel::Info, ("TSFTextStore::Initialize() is called..."));
6754 if (sThreadMgr) {
6755 MOZ_LOG(gIMELog, LogLevel::Error,
6756 (" TSFTextStore::Initialize() FAILED due to already initialized"));
6757 return;
6760 const bool enableTsf = StaticPrefs::intl_tsf_enabled_AtStartup();
6761 MOZ_LOG(gIMELog, LogLevel::Info,
6762 (" TSFTextStore::Initialize(), TSF is %s",
6763 enableTsf ? "enabled" : "disabled"));
6764 if (!enableTsf) {
6765 return;
6768 RefPtr<ITfThreadMgr> threadMgr;
6769 HRESULT hr =
6770 ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_INPROC_SERVER,
6771 IID_ITfThreadMgr, getter_AddRefs(threadMgr));
6772 if (FAILED(hr) || !threadMgr) {
6773 MOZ_LOG(gIMELog, LogLevel::Error,
6774 (" TSFTextStore::Initialize() FAILED to "
6775 "create the thread manager, hr=0x%08lX",
6776 hr));
6777 return;
6780 hr = threadMgr->Activate(&sClientId);
6781 if (FAILED(hr)) {
6782 MOZ_LOG(
6783 gIMELog, LogLevel::Error,
6784 (" TSFTextStore::Initialize() FAILED to activate, hr=0x%08lX", hr));
6785 return;
6788 RefPtr<ITfDocumentMgr> disabledDocumentMgr;
6789 hr = threadMgr->CreateDocumentMgr(getter_AddRefs(disabledDocumentMgr));
6790 if (FAILED(hr) || !disabledDocumentMgr) {
6791 MOZ_LOG(gIMELog, LogLevel::Error,
6792 (" TSFTextStore::Initialize() FAILED to create "
6793 "a document manager for disabled mode, hr=0x%08lX",
6794 hr));
6795 return;
6798 RefPtr<ITfContext> disabledContext;
6799 DWORD editCookie = 0;
6800 hr = disabledDocumentMgr->CreateContext(
6801 sClientId, 0, nullptr, getter_AddRefs(disabledContext), &editCookie);
6802 if (FAILED(hr) || !disabledContext) {
6803 MOZ_LOG(gIMELog, LogLevel::Error,
6804 (" TSFTextStore::Initialize() FAILED to create "
6805 "a context for disabled mode, hr=0x%08lX",
6806 hr));
6807 return;
6810 MarkContextAsKeyboardDisabled(disabledContext);
6811 MarkContextAsEmpty(disabledContext);
6813 sThreadMgr = threadMgr;
6814 sDisabledDocumentMgr = disabledDocumentMgr;
6815 sDisabledContext = disabledContext;
6817 MOZ_LOG(gIMELog, LogLevel::Info,
6818 (" TSFTextStore::Initialize(), sThreadMgr=0x%p, "
6819 "sClientId=0x%08lX, sDisabledDocumentMgr=0x%p, sDisabledContext=%p",
6820 sThreadMgr.get(), sClientId, sDisabledDocumentMgr.get(),
6821 sDisabledContext.get()));
6824 // static
6825 already_AddRefed<ITfThreadMgr> TSFTextStore::GetThreadMgr() {
6826 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
6827 return threadMgr.forget();
6830 // static
6831 already_AddRefed<ITfMessagePump> TSFTextStore::GetMessagePump() {
6832 static bool sInitialized = false;
6833 if (!sThreadMgr) {
6834 return nullptr;
6836 if (sMessagePump) {
6837 RefPtr<ITfMessagePump> messagePump = sMessagePump;
6838 return messagePump.forget();
6840 // If it tried to retrieve ITfMessagePump from sThreadMgr but it failed,
6841 // we shouldn't retry it at every message due to performance reason.
6842 // Although this shouldn't occur actually.
6843 if (sInitialized) {
6844 return nullptr;
6846 sInitialized = true;
6848 RefPtr<ITfMessagePump> messagePump;
6849 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfMessagePump,
6850 getter_AddRefs(messagePump));
6851 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!messagePump)) {
6852 MOZ_LOG(gIMELog, LogLevel::Error,
6853 ("TSFTextStore::GetMessagePump() FAILED to "
6854 "QI message pump from the thread manager, hr=0x%08lX",
6855 hr));
6856 return nullptr;
6858 sMessagePump = messagePump;
6859 return messagePump.forget();
6862 // static
6863 already_AddRefed<ITfDisplayAttributeMgr>
6864 TSFTextStore::GetDisplayAttributeMgr() {
6865 RefPtr<ITfDisplayAttributeMgr> displayAttributeMgr;
6866 if (sDisplayAttrMgr) {
6867 displayAttributeMgr = sDisplayAttrMgr;
6868 return displayAttributeMgr.forget();
6871 HRESULT hr = ::CoCreateInstance(
6872 CLSID_TF_DisplayAttributeMgr, nullptr, CLSCTX_INPROC_SERVER,
6873 IID_ITfDisplayAttributeMgr, getter_AddRefs(displayAttributeMgr));
6874 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!displayAttributeMgr)) {
6875 MOZ_LOG(gIMELog, LogLevel::Error,
6876 ("TSFTextStore::GetDisplayAttributeMgr() FAILED to create "
6877 "a display attribute manager instance, hr=0x%08lX",
6878 hr));
6879 return nullptr;
6881 sDisplayAttrMgr = displayAttributeMgr;
6882 return displayAttributeMgr.forget();
6885 // static
6886 already_AddRefed<ITfCategoryMgr> TSFTextStore::GetCategoryMgr() {
6887 RefPtr<ITfCategoryMgr> categoryMgr;
6888 if (sCategoryMgr) {
6889 categoryMgr = sCategoryMgr;
6890 return categoryMgr.forget();
6892 HRESULT hr =
6893 ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, CLSCTX_INPROC_SERVER,
6894 IID_ITfCategoryMgr, getter_AddRefs(categoryMgr));
6895 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!categoryMgr)) {
6896 MOZ_LOG(gIMELog, LogLevel::Error,
6897 ("TSFTextStore::GetCategoryMgr() FAILED to create "
6898 "a category manager instance, hr=0x%08lX",
6899 hr));
6900 return nullptr;
6902 sCategoryMgr = categoryMgr;
6903 return categoryMgr.forget();
6906 // static
6907 already_AddRefed<ITfCompartment> TSFTextStore::GetCompartmentForOpenClose() {
6908 if (sCompartmentForOpenClose) {
6909 RefPtr<ITfCompartment> compartment = sCompartmentForOpenClose;
6910 return compartment.forget();
6913 if (!sThreadMgr) {
6914 return nullptr;
6917 RefPtr<ITfCompartmentMgr> compartmentMgr;
6918 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfCompartmentMgr,
6919 getter_AddRefs(compartmentMgr));
6920 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartmentMgr)) {
6921 MOZ_LOG(gIMELog, LogLevel::Error,
6922 ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
6923 "sThreadMgr not having ITfCompartmentMgr, hr=0x%08lX",
6924 hr));
6925 return nullptr;
6928 RefPtr<ITfCompartment> compartment;
6929 hr = compartmentMgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
6930 getter_AddRefs(compartment));
6931 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartment)) {
6932 MOZ_LOG(gIMELog, LogLevel::Error,
6933 ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
6934 "ITfCompartmentMgr::GetCompartment() failuere, hr=0x%08lX",
6935 hr));
6936 return nullptr;
6939 sCompartmentForOpenClose = compartment;
6940 return compartment.forget();
6943 // static
6944 already_AddRefed<ITfInputProcessorProfiles>
6945 TSFTextStore::GetInputProcessorProfiles() {
6946 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles;
6947 if (sInputProcessorProfiles) {
6948 inputProcessorProfiles = sInputProcessorProfiles;
6949 return inputProcessorProfiles.forget();
6951 // XXX MSDN documents that ITfInputProcessorProfiles is available only on
6952 // desktop apps. However, there is no known way to obtain
6953 // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles
6954 // instance.
6955 HRESULT hr = ::CoCreateInstance(
6956 CLSID_TF_InputProcessorProfiles, nullptr, CLSCTX_INPROC_SERVER,
6957 IID_ITfInputProcessorProfiles, getter_AddRefs(inputProcessorProfiles));
6958 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!inputProcessorProfiles)) {
6959 MOZ_LOG(gIMELog, LogLevel::Error,
6960 ("TSFTextStore::GetInputProcessorProfiles() FAILED to create input "
6961 "processor profiles, hr=0x%08lX",
6962 hr));
6963 return nullptr;
6965 sInputProcessorProfiles = inputProcessorProfiles;
6966 return inputProcessorProfiles.forget();
6969 // static
6970 void TSFTextStore::Terminate() {
6971 MOZ_LOG(gIMELog, LogLevel::Info, ("TSFTextStore::Terminate()"));
6973 TSFStaticSink::Shutdown();
6975 sDisplayAttrMgr = nullptr;
6976 sCategoryMgr = nullptr;
6977 sEnabledTextStore = nullptr;
6978 sDisabledDocumentMgr = nullptr;
6979 sDisabledContext = nullptr;
6980 sCompartmentForOpenClose = nullptr;
6981 sInputProcessorProfiles = nullptr;
6982 sClientId = 0;
6983 if (sThreadMgr) {
6984 sThreadMgr->Deactivate();
6985 sThreadMgr = nullptr;
6986 sMessagePump = nullptr;
6987 sKeystrokeMgr = nullptr;
6991 // static
6992 bool TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg) {
6993 if (!sThreadMgr) {
6994 return false; // not in TSF mode
6996 static bool sInitialized = false;
6997 if (!sKeystrokeMgr) {
6998 // If it tried to retrieve ITfKeystrokeMgr from sThreadMgr but it failed,
6999 // we shouldn't retry it at every keydown nor keyup due to performance
7000 // reason. Although this shouldn't occur actually.
7001 if (sInitialized) {
7002 return false;
7004 sInitialized = true;
7005 RefPtr<ITfKeystrokeMgr> keystrokeMgr;
7006 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfKeystrokeMgr,
7007 getter_AddRefs(keystrokeMgr));
7008 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!keystrokeMgr)) {
7009 MOZ_LOG(gIMELog, LogLevel::Error,
7010 ("TSFTextStore::ProcessRawKeyMessage() FAILED to "
7011 "QI keystroke manager from the thread manager, hr=0x%08lX",
7012 hr));
7013 return false;
7015 sKeystrokeMgr = keystrokeMgr.forget();
7018 if (aMsg.message == WM_KEYDOWN) {
7019 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
7020 if (textStore) {
7021 textStore->OnStartToHandleKeyMessage();
7022 if (NS_WARN_IF(textStore != sEnabledTextStore)) {
7023 // Let's handle the key message with new focused TSFTextStore.
7024 textStore = sEnabledTextStore;
7027 AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
7028 AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
7029 sHandlingKeyMsg = &aMsg;
7030 sIsKeyboardEventDispatched = false;
7031 BOOL eaten;
7032 RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
7033 HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten);
7034 if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
7035 return false;
7037 hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
7038 if (textStore) {
7039 textStore->OnEndHandlingKeyMessage(!!eaten);
7041 return SUCCEEDED(hr) &&
7042 (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
7044 if (aMsg.message == WM_KEYUP) {
7045 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
7046 if (textStore) {
7047 textStore->OnStartToHandleKeyMessage();
7048 if (NS_WARN_IF(textStore != sEnabledTextStore)) {
7049 // Let's handle the key message with new focused TSFTextStore.
7050 textStore = sEnabledTextStore;
7053 AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
7054 AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
7055 sHandlingKeyMsg = &aMsg;
7056 sIsKeyboardEventDispatched = false;
7057 BOOL eaten;
7058 RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
7059 HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten);
7060 if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
7061 return false;
7063 hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten);
7064 if (textStore) {
7065 textStore->OnEndHandlingKeyMessage(!!eaten);
7067 return SUCCEEDED(hr) &&
7068 (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
7070 return false;
7073 // static
7074 void TSFTextStore::ProcessMessage(nsWindow* aWindow, UINT aMessage,
7075 WPARAM& aWParam, LPARAM& aLParam,
7076 MSGResult& aResult) {
7077 switch (aMessage) {
7078 case WM_IME_SETCONTEXT:
7079 // If a windowless plugin had focus and IME was handled on it, composition
7080 // window was set the position. After that, even in TSF mode, WinXP keeps
7081 // to use composition window at the position if the active IME is not
7082 // aware TSF. For avoiding this issue, we need to hide the composition
7083 // window here.
7084 if (aWParam) {
7085 aLParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
7087 break;
7088 case WM_ENTERIDLE:
7089 // When an modal dialog such as a file picker is open, composition
7090 // should be committed because IME might be used on it.
7091 if (!IsComposingOn(aWindow)) {
7092 break;
7094 CommitComposition(false);
7095 break;
7096 case MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE: {
7097 TSFTextStore* maybeTextStore = reinterpret_cast<TSFTextStore*>(aWParam);
7098 if (maybeTextStore == sEnabledTextStore) {
7099 RefPtr<TSFTextStore> textStore(maybeTextStore);
7100 textStore->NotifyTSFOfLayoutChangeAgain();
7102 break;
7107 // static
7108 bool TSFTextStore::IsIMM_IMEActive() {
7109 return TSFStaticSink::IsIMM_IMEActive();
7112 // static
7113 bool TSFTextStore::IsMSJapaneseIMEActive() {
7114 return TSFStaticSink::IsMSJapaneseIMEActive();
7117 // static
7118 bool TSFTextStore::IsGoogleJapaneseInputActive() {
7119 return TSFStaticSink::IsGoogleJapaneseInputActive();
7122 // static
7123 bool TSFTextStore::IsATOKActive() { return TSFStaticSink::IsATOKActive(); }
7125 /******************************************************************************
7126 * TSFTextStore::Content
7127 *****************************************************************************/
7129 const nsDependentSubstring TSFTextStore::Content::GetSelectedText() const {
7130 if (NS_WARN_IF(mSelection.isNothing())) {
7131 return nsDependentSubstring();
7133 return GetSubstring(static_cast<uint32_t>(mSelection->StartOffset()),
7134 static_cast<uint32_t>(mSelection->Length()));
7137 const nsDependentSubstring TSFTextStore::Content::GetSubstring(
7138 uint32_t aStart, uint32_t aLength) const {
7139 return nsDependentSubstring(mText, aStart, aLength);
7142 void TSFTextStore::Content::ReplaceSelectedTextWith(const nsAString& aString) {
7143 if (NS_WARN_IF(mSelection.isNothing())) {
7144 return;
7146 ReplaceTextWith(mSelection->StartOffset(), mSelection->Length(), aString);
7149 inline uint32_t FirstDifferentCharOffset(const nsAString& aStr1,
7150 const nsAString& aStr2) {
7151 MOZ_ASSERT(aStr1 != aStr2);
7152 uint32_t i = 0;
7153 uint32_t minLength = std::min(aStr1.Length(), aStr2.Length());
7154 for (; i < minLength && aStr1[i] == aStr2[i]; i++) {
7155 /* nothing to do */
7157 return i;
7160 void TSFTextStore::Content::ReplaceTextWith(LONG aStart, LONG aLength,
7161 const nsAString& aReplaceString) {
7162 MOZ_ASSERT(aStart >= 0);
7163 MOZ_ASSERT(aLength >= 0);
7164 const nsDependentSubstring replacedString = GetSubstring(
7165 static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength));
7166 if (aReplaceString != replacedString) {
7167 uint32_t firstDifferentOffset = mMinModifiedOffset.valueOr(UINT32_MAX);
7168 if (mComposition.isSome()) {
7169 // Emulate text insertion during compositions, because during a
7170 // composition, editor expects the whole composition string to
7171 // be sent in eCompositionChange, not just the inserted part.
7172 // The actual eCompositionChange will be sent in SetSelection
7173 // or OnUpdateComposition.
7174 MOZ_ASSERT(aStart >= mComposition->StartOffset());
7175 MOZ_ASSERT(aStart + aLength <= mComposition->EndOffset());
7176 mComposition->ReplaceData(
7177 static_cast<uint32_t>(aStart - mComposition->StartOffset()),
7178 static_cast<uint32_t>(aLength), aReplaceString);
7179 // TIP may set composition string twice or more times during a document
7180 // lock. Therefore, we should compute the first difference offset with
7181 // mLastComposition.
7182 if (mLastComposition.isNothing()) {
7183 firstDifferentOffset = mComposition->StartOffset();
7184 } else if (mComposition->DataRef() != mLastComposition->DataRef()) {
7185 firstDifferentOffset =
7186 mComposition->StartOffset() +
7187 FirstDifferentCharOffset(mComposition->DataRef(),
7188 mLastComposition->DataRef());
7189 // The previous change to the composition string is canceled.
7190 if (mMinModifiedOffset.isSome() &&
7191 mMinModifiedOffset.value() >=
7192 static_cast<uint32_t>(mComposition->StartOffset()) &&
7193 mMinModifiedOffset.value() < firstDifferentOffset) {
7194 mMinModifiedOffset = Some(firstDifferentOffset);
7196 } else if (mMinModifiedOffset.isSome() &&
7197 mMinModifiedOffset.value() < static_cast<uint32_t>(LONG_MAX) &&
7198 mComposition->IsOffsetInRange(
7199 static_cast<long>(mMinModifiedOffset.value()))) {
7200 // The previous change to the composition string is canceled.
7201 firstDifferentOffset = mComposition->EndOffset();
7202 mMinModifiedOffset = Some(firstDifferentOffset);
7204 mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
7205 MOZ_LOG(
7206 gIMELog, LogLevel::Debug,
7207 ("0x%p TSFTextStore::Content::ReplaceTextWith(aStart=%ld, "
7208 "aLength=%ld, aReplaceString=\"%s\"), mComposition=%s, "
7209 "mLastComposition=%s, mMinModifiedOffset=%s, "
7210 "firstDifferentOffset=%u",
7211 this, aStart, aLength, GetEscapedUTF8String(aReplaceString).get(),
7212 ToString(mComposition).c_str(), ToString(mLastComposition).c_str(),
7213 ToString(mMinModifiedOffset).c_str(), firstDifferentOffset));
7214 } else {
7215 firstDifferentOffset =
7216 static_cast<uint32_t>(aStart) +
7217 FirstDifferentCharOffset(aReplaceString, replacedString);
7219 mMinModifiedOffset =
7220 mMinModifiedOffset.isNothing()
7221 ? Some(firstDifferentOffset)
7222 : Some(std::min(mMinModifiedOffset.value(), firstDifferentOffset));
7223 mText.Replace(static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength),
7224 aReplaceString);
7226 // Selection should be collapsed at the end of the inserted string.
7227 mSelection = Some(TSFTextStore::Selection(static_cast<uint32_t>(aStart) +
7228 aReplaceString.Length()));
7231 void TSFTextStore::Content::StartComposition(
7232 ITfCompositionView* aCompositionView, const PendingAction& aCompStart,
7233 bool aPreserveSelection) {
7234 MOZ_ASSERT(aCompositionView);
7235 MOZ_ASSERT(mComposition.isNothing());
7236 MOZ_ASSERT(aCompStart.mType == PendingAction::Type::eCompositionStart);
7238 mComposition.reset(); // Avoid new crash in the beta and nightly channels.
7239 mComposition.emplace(
7240 aCompositionView, aCompStart.mSelectionStart,
7241 GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart),
7242 static_cast<uint32_t>(aCompStart.mSelectionLength)));
7243 mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
7244 if (!aPreserveSelection) {
7245 // XXX Do we need to set a new writing-mode here when setting a new
7246 // selection? Currently, we just preserve the existing value.
7247 WritingMode writingMode =
7248 mSelection.isNothing() ? WritingMode() : mSelection->WritingModeRef();
7249 mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset(),
7250 mComposition->Length(), false,
7251 writingMode));
7255 void TSFTextStore::Content::RestoreCommittedComposition(
7256 ITfCompositionView* aCompositionView,
7257 const PendingAction& aCanceledCompositionEnd) {
7258 MOZ_ASSERT(aCompositionView);
7259 MOZ_ASSERT(mComposition.isNothing());
7260 MOZ_ASSERT(aCanceledCompositionEnd.mType ==
7261 PendingAction::Type::eCompositionEnd);
7262 MOZ_ASSERT(
7263 GetSubstring(
7264 static_cast<uint32_t>(aCanceledCompositionEnd.mSelectionStart),
7265 static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
7266 aCanceledCompositionEnd.mData);
7268 // Restore the committed string as composing string.
7269 mComposition.reset(); // Avoid new crash in the beta and nightly channels.
7270 mComposition.emplace(aCompositionView,
7271 aCanceledCompositionEnd.mSelectionStart,
7272 aCanceledCompositionEnd.mData);
7273 mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
7276 void TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd) {
7277 MOZ_ASSERT(mComposition.isSome());
7278 MOZ_ASSERT(aCompEnd.mType == PendingAction::Type::eCompositionEnd);
7280 if (mComposition.isNothing()) {
7281 return; // Avoid new crash in the beta and nightly channels.
7284 mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset() +
7285 aCompEnd.mData.Length()));
7286 mComposition.reset();
7289 /******************************************************************************
7290 * TSFTextStore::MouseTracker
7291 *****************************************************************************/
7293 TSFTextStore::MouseTracker::MouseTracker() : mCookie(kInvalidCookie) {}
7295 HRESULT
7296 TSFTextStore::MouseTracker::Init(TSFTextStore* aTextStore) {
7297 MOZ_LOG(gIMELog, LogLevel::Debug,
7298 ("0x%p TSFTextStore::MouseTracker::Init(aTextStore=0x%p), "
7299 "aTextStore->mMouseTrackers.Length()=%zu",
7300 this, aTextStore, aTextStore->mMouseTrackers.Length()));
7302 if (&aTextStore->mMouseTrackers.LastElement() != this) {
7303 MOZ_LOG(gIMELog, LogLevel::Error,
7304 ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
7305 "this is not the last element of mMouseTrackers",
7306 this));
7307 return E_FAIL;
7309 if (aTextStore->mMouseTrackers.Length() > kInvalidCookie) {
7310 MOZ_LOG(gIMELog, LogLevel::Error,
7311 ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
7312 "no new cookie available",
7313 this));
7314 return E_FAIL;
7316 MOZ_ASSERT(!aTextStore->mMouseTrackers.IsEmpty(),
7317 "This instance must be in TSFTextStore::mMouseTrackers");
7318 mCookie = static_cast<DWORD>(aTextStore->mMouseTrackers.Length() - 1);
7319 return S_OK;
7322 HRESULT
7323 TSFTextStore::MouseTracker::AdviseSink(TSFTextStore* aTextStore,
7324 ITfRangeACP* aTextRange,
7325 ITfMouseSink* aMouseSink) {
7326 MOZ_LOG(gIMELog, LogLevel::Debug,
7327 ("0x%p TSFTextStore::MouseTracker::AdviseSink(aTextStore=0x%p, "
7328 "aTextRange=0x%p, aMouseSink=0x%p), mCookie=%ld, mSink=0x%p",
7329 this, aTextStore, aTextRange, aMouseSink, mCookie, mSink.get()));
7330 MOZ_ASSERT(mCookie != kInvalidCookie, "This hasn't been initalized?");
7332 if (mSink) {
7333 MOZ_LOG(gIMELog, LogLevel::Error,
7334 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7335 "due to already being used",
7336 this));
7337 return E_FAIL;
7340 MOZ_ASSERT(mRange.isNothing());
7342 LONG start = 0, length = 0;
7343 HRESULT hr = aTextRange->GetExtent(&start, &length);
7344 if (FAILED(hr)) {
7345 MOZ_LOG(gIMELog, LogLevel::Error,
7346 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7347 "due to failure of ITfRangeACP::GetExtent()",
7348 this));
7349 return hr;
7352 if (start < 0 || length <= 0 || start + length > LONG_MAX) {
7353 MOZ_LOG(gIMELog, LogLevel::Error,
7354 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7355 "due to odd result of ITfRangeACP::GetExtent(), "
7356 "start=%ld, length=%ld",
7357 this, start, length));
7358 return E_INVALIDARG;
7361 nsAutoString textContent;
7362 if (NS_WARN_IF(!aTextStore->GetCurrentText(textContent))) {
7363 MOZ_LOG(gIMELog, LogLevel::Error,
7364 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7365 "due to failure of TSFTextStore::GetCurrentText()",
7366 this));
7367 return E_FAIL;
7370 if (textContent.Length() <= static_cast<uint32_t>(start) ||
7371 textContent.Length() < static_cast<uint32_t>(start + length)) {
7372 MOZ_LOG(gIMELog, LogLevel::Error,
7373 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7374 "due to out of range, start=%ld, length=%ld, "
7375 "textContent.Length()=%zu",
7376 this, start, length, textContent.Length()));
7377 return E_INVALIDARG;
7380 mRange.emplace(start, start + length);
7382 mSink = aMouseSink;
7384 MOZ_LOG(gIMELog, LogLevel::Debug,
7385 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink(), "
7386 "succeeded, mRange=%s, textContent.Length()=%zu",
7387 this, ToString(mRange).c_str(), textContent.Length()));
7388 return S_OK;
7391 void TSFTextStore::MouseTracker::UnadviseSink() {
7392 MOZ_LOG(gIMELog, LogLevel::Debug,
7393 ("0x%p TSFTextStore::MouseTracker::UnadviseSink(), "
7394 "mCookie=%ld, mSink=0x%p, mRange=%s",
7395 this, mCookie, mSink.get(), ToString(mRange).c_str()));
7396 mSink = nullptr;
7397 mRange.reset();
7400 bool TSFTextStore::MouseTracker::OnMouseButtonEvent(ULONG aEdge,
7401 ULONG aQuadrant,
7402 DWORD aButtonStatus) {
7403 MOZ_ASSERT(IsUsing(), "The caller must check before calling OnMouseEvent()");
7405 BOOL eaten = FALSE;
7406 RefPtr<ITfMouseSink> sink = mSink;
7407 HRESULT hr = sink->OnMouseEvent(aEdge, aQuadrant, aButtonStatus, &eaten);
7409 MOZ_LOG(gIMELog, LogLevel::Debug,
7410 ("0x%p TSFTextStore::MouseTracker::OnMouseEvent(aEdge=%ld, "
7411 "aQuadrant=%ld, aButtonStatus=0x%08lX), hr=0x%08lX, eaten=%s",
7412 this, aEdge, aQuadrant, aButtonStatus, hr, GetBoolName(!!eaten)));
7414 return SUCCEEDED(hr) && eaten;
7417 #ifdef DEBUG
7418 // static
7419 bool TSFTextStore::CurrentKeyboardLayoutHasIME() {
7420 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
7421 TSFTextStore::GetInputProcessorProfiles();
7422 if (!inputProcessorProfiles) {
7423 MOZ_LOG(gIMELog, LogLevel::Error,
7424 ("TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED due to "
7425 "there is no input processor profiles instance"));
7426 return false;
7428 RefPtr<ITfInputProcessorProfileMgr> profileMgr;
7429 HRESULT hr = inputProcessorProfiles->QueryInterface(
7430 IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
7431 if (FAILED(hr) || !profileMgr) {
7432 // On Windows Vista or later, ImmIsIME() API always returns true.
7433 // If we failed to obtain the profile manager, we cannot know if current
7434 // keyboard layout has IME.
7435 MOZ_LOG(gIMELog, LogLevel::Error,
7436 (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to query "
7437 "ITfInputProcessorProfileMgr"));
7438 return false;
7441 TF_INPUTPROCESSORPROFILE profile;
7442 hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
7443 if (hr == S_FALSE) {
7444 return false; // not found or not active
7446 if (FAILED(hr)) {
7447 MOZ_LOG(gIMELog, LogLevel::Error,
7448 (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to retreive "
7449 "active profile"));
7450 return false;
7452 return (profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR);
7454 #endif // #ifdef DEBUG
7456 } // namespace widget
7457 } // namespace mozilla