Bug 1671598 [wpt PR 26128] - [AspectRatio] Fix divide by zero with a small float...
[gecko.git] / widget / windows / TSFTextStore.cpp
blob1a4c4e85df69c3e088b5a5fdd44cb9814843385e
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 <olectl.h>
11 #include <algorithm>
12 #include "nscore.h"
14 #include "IMMHandler.h"
15 #include "KeyboardLayout.h"
16 #include "WinIMEHandler.h"
17 #include "WinUtils.h"
18 #include "mozilla/AutoRestore.h"
19 #include "mozilla/Logging.h"
20 #include "mozilla/Preferences.h"
21 #include "mozilla/Telemetry.h"
22 #include "mozilla/TextEventDispatcher.h"
23 #include "mozilla/TextEvents.h"
24 #include "mozilla/ToString.h"
25 #include "mozilla/WindowsVersion.h"
26 #include "nsWindow.h"
27 #include "nsPrintfCString.h"
29 // Workaround for mingw32
30 #ifndef TS_SD_INPUTPANEMANUALDISPLAYENABLE
31 # define TS_SD_INPUTPANEMANUALDISPLAYENABLE 0x40
32 #endif
34 namespace mozilla {
35 namespace widget {
37 static const char* kPrefNameEnableTSF = "intl.tsf.enable";
39 /**
40 * TSF related code should log its behavior even on release build especially
41 * in the interface methods.
43 * In interface methods, use LogLevel::Info.
44 * In internal methods, use LogLevel::Debug for logging normal behavior.
45 * For logging error, use LogLevel::Error.
47 * When an instance method is called, start with following text:
48 * "0x%p TSFFoo::Bar(", the 0x%p should be the "this" of the nsFoo.
49 * after that, start with:
50 * "0x%p TSFFoo::Bar("
51 * In an internal method, start with following text:
52 * "0x%p TSFFoo::Bar("
53 * When a static method is called, start with following text:
54 * "TSFFoo::Bar("
57 LazyLogModule sTextStoreLog("nsTextStoreWidgets");
59 enum class TextInputProcessorID {
60 // Internal use only. This won't be returned by TSFStaticSink::ActiveTIP().
61 eNotComputed,
63 // Not a TIP. E.g., simple keyboard layout or IMM-IME.
64 eNone,
66 // Used for other TIPs, i.e., any TIPs which we don't support specifically.
67 eUnknown,
69 // TIP for Japanese.
70 eMicrosoftIMEForJapanese,
71 eMicrosoftOfficeIME2010ForJapanese,
72 eGoogleJapaneseInput,
73 eATOK2011,
74 eATOK2012,
75 eATOK2013,
76 eATOK2014,
77 eATOK2015,
78 eATOK2016,
79 eATOKUnknown,
80 eJapanist10,
82 // TIP for Traditional Chinese.
83 eMicrosoftBopomofo,
84 eMicrosoftChangJie,
85 eMicrosoftPhonetic,
86 eMicrosoftQuick,
87 eMicrosoftNewChangJie,
88 eMicrosoftNewPhonetic,
89 eMicrosoftNewQuick,
90 eFreeChangJie,
92 // TIP for Simplified Chinese.
93 eMicrosoftPinyin,
94 eMicrosoftPinyinNewExperienceInputStyle,
95 eMicrosoftWubi,
97 // TIP for Korean.
98 eMicrosoftIMEForKorean,
99 eMicrosoftOldHangul,
102 static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
104 static void HandleSeparator(nsCString& aDesc) {
105 if (!aDesc.IsEmpty()) {
106 aDesc.AppendLiteral(" | ");
110 static const nsCString GetFindFlagName(DWORD aFindFlag) {
111 nsCString description;
112 if (!aFindFlag) {
113 description.AppendLiteral("no flags (0)");
114 return description;
116 if (aFindFlag & TS_ATTR_FIND_BACKWARDS) {
117 description.AppendLiteral("TS_ATTR_FIND_BACKWARDS");
119 if (aFindFlag & TS_ATTR_FIND_WANT_OFFSET) {
120 HandleSeparator(description);
121 description.AppendLiteral("TS_ATTR_FIND_WANT_OFFSET");
123 if (aFindFlag & TS_ATTR_FIND_UPDATESTART) {
124 HandleSeparator(description);
125 description.AppendLiteral("TS_ATTR_FIND_UPDATESTART");
127 if (aFindFlag & TS_ATTR_FIND_WANT_VALUE) {
128 HandleSeparator(description);
129 description.AppendLiteral("TS_ATTR_FIND_WANT_VALUE");
131 if (aFindFlag & TS_ATTR_FIND_WANT_END) {
132 HandleSeparator(description);
133 description.AppendLiteral("TS_ATTR_FIND_WANT_END");
135 if (aFindFlag & TS_ATTR_FIND_HIDDEN) {
136 HandleSeparator(description);
137 description.AppendLiteral("TS_ATTR_FIND_HIDDEN");
139 if (description.IsEmpty()) {
140 description.AppendLiteral("Unknown (");
141 description.AppendInt(static_cast<uint32_t>(aFindFlag));
142 description.Append(')');
144 return description;
147 class GetACPFromPointFlagName : public nsAutoCString {
148 public:
149 explicit GetACPFromPointFlagName(DWORD aFlags) {
150 if (!aFlags) {
151 AppendLiteral("no flags (0)");
152 return;
154 if (aFlags & GXFPF_ROUND_NEAREST) {
155 AppendLiteral("GXFPF_ROUND_NEAREST");
156 aFlags &= ~GXFPF_ROUND_NEAREST;
158 if (aFlags & GXFPF_NEAREST) {
159 HandleSeparator(*this);
160 AppendLiteral("GXFPF_NEAREST");
161 aFlags &= ~GXFPF_NEAREST;
163 if (aFlags) {
164 HandleSeparator(*this);
165 AppendLiteral("Unknown(");
166 AppendInt(static_cast<uint32_t>(aFlags));
167 Append(')');
170 virtual ~GetACPFromPointFlagName() {}
173 static const char* GetFocusChangeName(
174 InputContextAction::FocusChange aFocusChange) {
175 switch (aFocusChange) {
176 case InputContextAction::FOCUS_NOT_CHANGED:
177 return "FOCUS_NOT_CHANGED";
178 case InputContextAction::GOT_FOCUS:
179 return "GOT_FOCUS";
180 case InputContextAction::LOST_FOCUS:
181 return "LOST_FOCUS";
182 case InputContextAction::MENU_GOT_PSEUDO_FOCUS:
183 return "MENU_GOT_PSEUDO_FOCUS";
184 case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
185 return "MENU_LOST_PSEUDO_FOCUS";
186 case InputContextAction::WIDGET_CREATED:
187 return "WIDGET_CREATED";
188 default:
189 return "Unknown";
193 static nsCString GetCLSIDNameStr(REFCLSID aCLSID) {
194 LPOLESTR str = nullptr;
195 HRESULT hr = ::StringFromCLSID(aCLSID, &str);
196 if (FAILED(hr) || !str || !str[0]) {
197 return ""_ns;
200 nsCString result;
201 result = NS_ConvertUTF16toUTF8(str);
202 ::CoTaskMemFree(str);
203 return result;
206 static nsCString GetGUIDNameStr(REFGUID aGUID) {
207 OLECHAR str[40];
208 int len = ::StringFromGUID2(aGUID, str, ArrayLength(str));
209 if (!len || !str[0]) {
210 return ""_ns;
213 return NS_ConvertUTF16toUTF8(str);
216 static nsCString GetGUIDNameStrWithTable(REFGUID aGUID) {
217 #define RETURN_GUID_NAME(aNamedGUID) \
218 if (IsEqualGUID(aGUID, aNamedGUID)) { \
219 return nsLiteralCString(#aNamedGUID); \
222 RETURN_GUID_NAME(GUID_PROP_INPUTSCOPE)
223 RETURN_GUID_NAME(TSATTRID_OTHERS)
224 RETURN_GUID_NAME(TSATTRID_Font)
225 RETURN_GUID_NAME(TSATTRID_Font_FaceName)
226 RETURN_GUID_NAME(TSATTRID_Font_SizePts)
227 RETURN_GUID_NAME(TSATTRID_Font_Style)
228 RETURN_GUID_NAME(TSATTRID_Font_Style_Bold)
229 RETURN_GUID_NAME(TSATTRID_Font_Style_Italic)
230 RETURN_GUID_NAME(TSATTRID_Font_Style_SmallCaps)
231 RETURN_GUID_NAME(TSATTRID_Font_Style_Capitalize)
232 RETURN_GUID_NAME(TSATTRID_Font_Style_Uppercase)
233 RETURN_GUID_NAME(TSATTRID_Font_Style_Lowercase)
234 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation)
235 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_LasVegasLights)
236 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_BlinkingBackground)
237 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_SparkleText)
238 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingBlackAnts)
239 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingRedAnts)
240 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_Shimmer)
241 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeDown)
242 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeRight)
243 RETURN_GUID_NAME(TSATTRID_Font_Style_Emboss)
244 RETURN_GUID_NAME(TSATTRID_Font_Style_Engrave)
245 RETURN_GUID_NAME(TSATTRID_Font_Style_Hidden)
246 RETURN_GUID_NAME(TSATTRID_Font_Style_Kerning)
247 RETURN_GUID_NAME(TSATTRID_Font_Style_Outlined)
248 RETURN_GUID_NAME(TSATTRID_Font_Style_Position)
249 RETURN_GUID_NAME(TSATTRID_Font_Style_Protected)
250 RETURN_GUID_NAME(TSATTRID_Font_Style_Shadow)
251 RETURN_GUID_NAME(TSATTRID_Font_Style_Spacing)
252 RETURN_GUID_NAME(TSATTRID_Font_Style_Weight)
253 RETURN_GUID_NAME(TSATTRID_Font_Style_Height)
254 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline)
255 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Single)
256 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Double)
257 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough)
258 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Single)
259 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Double)
260 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline)
261 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Single)
262 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Double)
263 RETURN_GUID_NAME(TSATTRID_Font_Style_Blink)
264 RETURN_GUID_NAME(TSATTRID_Font_Style_Subscript)
265 RETURN_GUID_NAME(TSATTRID_Font_Style_Superscript)
266 RETURN_GUID_NAME(TSATTRID_Font_Style_Color)
267 RETURN_GUID_NAME(TSATTRID_Font_Style_BackgroundColor)
268 RETURN_GUID_NAME(TSATTRID_Text)
269 RETURN_GUID_NAME(TSATTRID_Text_VerticalWriting)
270 RETURN_GUID_NAME(TSATTRID_Text_RightToLeft)
271 RETURN_GUID_NAME(TSATTRID_Text_Orientation)
272 RETURN_GUID_NAME(TSATTRID_Text_Language)
273 RETURN_GUID_NAME(TSATTRID_Text_ReadOnly)
274 RETURN_GUID_NAME(TSATTRID_Text_EmbeddedObject)
275 RETURN_GUID_NAME(TSATTRID_Text_Alignment)
276 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Left)
277 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Right)
278 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Center)
279 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Justify)
280 RETURN_GUID_NAME(TSATTRID_Text_Link)
281 RETURN_GUID_NAME(TSATTRID_Text_Hyphenation)
282 RETURN_GUID_NAME(TSATTRID_Text_Para)
283 RETURN_GUID_NAME(TSATTRID_Text_Para_FirstLineIndent)
284 RETURN_GUID_NAME(TSATTRID_Text_Para_LeftIndent)
285 RETURN_GUID_NAME(TSATTRID_Text_Para_RightIndent)
286 RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceAfter)
287 RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceBefore)
288 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing)
289 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Single)
290 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_OnePtFive)
291 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Double)
292 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_AtLeast)
293 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Exactly)
294 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Multiple)
295 RETURN_GUID_NAME(TSATTRID_List)
296 RETURN_GUID_NAME(TSATTRID_List_LevelIndel)
297 RETURN_GUID_NAME(TSATTRID_List_Type)
298 RETURN_GUID_NAME(TSATTRID_List_Type_Bullet)
299 RETURN_GUID_NAME(TSATTRID_List_Type_Arabic)
300 RETURN_GUID_NAME(TSATTRID_List_Type_LowerLetter)
301 RETURN_GUID_NAME(TSATTRID_List_Type_UpperLetter)
302 RETURN_GUID_NAME(TSATTRID_List_Type_LowerRoman)
303 RETURN_GUID_NAME(TSATTRID_List_Type_UpperRoman)
304 RETURN_GUID_NAME(TSATTRID_App)
305 RETURN_GUID_NAME(TSATTRID_App_IncorrectSpelling)
306 RETURN_GUID_NAME(TSATTRID_App_IncorrectGrammar)
308 #undef RETURN_GUID_NAME
310 return GetGUIDNameStr(aGUID);
313 static nsCString GetRIIDNameStr(REFIID aRIID) {
314 LPOLESTR str = nullptr;
315 HRESULT hr = ::StringFromIID(aRIID, &str);
316 if (FAILED(hr) || !str || !str[0]) {
317 return ""_ns;
320 nsAutoString key(L"Interface\\");
321 key += str;
323 nsCString result;
324 wchar_t buf[256];
325 if (WinUtils::GetRegistryKey(HKEY_CLASSES_ROOT, key.get(), nullptr, buf,
326 sizeof(buf))) {
327 result = NS_ConvertUTF16toUTF8(buf);
328 } else {
329 result = NS_ConvertUTF16toUTF8(str);
332 ::CoTaskMemFree(str);
333 return result;
336 static const char* GetCommonReturnValueName(HRESULT aResult) {
337 switch (aResult) {
338 case S_OK:
339 return "S_OK";
340 case E_ABORT:
341 return "E_ABORT";
342 case E_ACCESSDENIED:
343 return "E_ACCESSDENIED";
344 case E_FAIL:
345 return "E_FAIL";
346 case E_HANDLE:
347 return "E_HANDLE";
348 case E_INVALIDARG:
349 return "E_INVALIDARG";
350 case E_NOINTERFACE:
351 return "E_NOINTERFACE";
352 case E_NOTIMPL:
353 return "E_NOTIMPL";
354 case E_OUTOFMEMORY:
355 return "E_OUTOFMEMORY";
356 case E_POINTER:
357 return "E_POINTER";
358 case E_UNEXPECTED:
359 return "E_UNEXPECTED";
360 default:
361 return SUCCEEDED(aResult) ? "Succeeded" : "Failed";
365 static const char* GetTextStoreReturnValueName(HRESULT aResult) {
366 switch (aResult) {
367 case TS_E_FORMAT:
368 return "TS_E_FORMAT";
369 case TS_E_INVALIDPOINT:
370 return "TS_E_INVALIDPOINT";
371 case TS_E_INVALIDPOS:
372 return "TS_E_INVALIDPOS";
373 case TS_E_NOINTERFACE:
374 return "TS_E_NOINTERFACE";
375 case TS_E_NOLAYOUT:
376 return "TS_E_NOLAYOUT";
377 case TS_E_NOLOCK:
378 return "TS_E_NOLOCK";
379 case TS_E_NOOBJECT:
380 return "TS_E_NOOBJECT";
381 case TS_E_NOSELECTION:
382 return "TS_E_NOSELECTION";
383 case TS_E_NOSERVICE:
384 return "TS_E_NOSERVICE";
385 case TS_E_READONLY:
386 return "TS_E_READONLY";
387 case TS_E_SYNCHRONOUS:
388 return "TS_E_SYNCHRONOUS";
389 case TS_S_ASYNC:
390 return "TS_S_ASYNC";
391 default:
392 return GetCommonReturnValueName(aResult);
396 static const nsCString GetSinkMaskNameStr(DWORD aSinkMask) {
397 nsCString description;
398 if (aSinkMask & TS_AS_TEXT_CHANGE) {
399 description.AppendLiteral("TS_AS_TEXT_CHANGE");
401 if (aSinkMask & TS_AS_SEL_CHANGE) {
402 HandleSeparator(description);
403 description.AppendLiteral("TS_AS_SEL_CHANGE");
405 if (aSinkMask & TS_AS_LAYOUT_CHANGE) {
406 HandleSeparator(description);
407 description.AppendLiteral("TS_AS_LAYOUT_CHANGE");
409 if (aSinkMask & TS_AS_ATTR_CHANGE) {
410 HandleSeparator(description);
411 description.AppendLiteral("TS_AS_ATTR_CHANGE");
413 if (aSinkMask & TS_AS_STATUS_CHANGE) {
414 HandleSeparator(description);
415 description.AppendLiteral("TS_AS_STATUS_CHANGE");
417 if (description.IsEmpty()) {
418 description.AppendLiteral("not-specified");
420 return description;
423 static const char* GetActiveSelEndName(TsActiveSelEnd aSelEnd) {
424 return aSelEnd == TS_AE_NONE
425 ? "TS_AE_NONE"
426 : aSelEnd == TS_AE_START
427 ? "TS_AE_START"
428 : aSelEnd == TS_AE_END ? "TS_AE_END" : "Unknown";
431 static const nsCString GetLockFlagNameStr(DWORD aLockFlags) {
432 nsCString description;
433 if ((aLockFlags & TS_LF_READWRITE) == TS_LF_READWRITE) {
434 description.AppendLiteral("TS_LF_READWRITE");
435 } else if (aLockFlags & TS_LF_READ) {
436 description.AppendLiteral("TS_LF_READ");
438 if (aLockFlags & TS_LF_SYNC) {
439 if (!description.IsEmpty()) {
440 description.AppendLiteral(" | ");
442 description.AppendLiteral("TS_LF_SYNC");
444 if (description.IsEmpty()) {
445 description.AppendLiteral("not-specified");
447 return description;
450 static const char* GetTextRunTypeName(TsRunType aRunType) {
451 switch (aRunType) {
452 case TS_RT_PLAIN:
453 return "TS_RT_PLAIN";
454 case TS_RT_HIDDEN:
455 return "TS_RT_HIDDEN";
456 case TS_RT_OPAQUE:
457 return "TS_RT_OPAQUE";
458 default:
459 return "Unknown";
463 static nsCString GetColorName(const TF_DA_COLOR& aColor) {
464 switch (aColor.type) {
465 case TF_CT_NONE:
466 return "TF_CT_NONE"_ns;
467 case TF_CT_SYSCOLOR:
468 return nsPrintfCString("TF_CT_SYSCOLOR, nIndex:0x%08X",
469 static_cast<int32_t>(aColor.nIndex));
470 case TF_CT_COLORREF:
471 return nsPrintfCString("TF_CT_COLORREF, cr:0x%08X",
472 static_cast<int32_t>(aColor.cr));
473 break;
474 default:
475 return nsPrintfCString("Unknown(%08X)",
476 static_cast<int32_t>(aColor.type));
480 static nsCString GetLineStyleName(TF_DA_LINESTYLE aLineStyle) {
481 switch (aLineStyle) {
482 case TF_LS_NONE:
483 return "TF_LS_NONE"_ns;
484 case TF_LS_SOLID:
485 return "TF_LS_SOLID"_ns;
486 case TF_LS_DOT:
487 return "TF_LS_DOT"_ns;
488 case TF_LS_DASH:
489 return "TF_LS_DASH"_ns;
490 case TF_LS_SQUIGGLE:
491 return "TF_LS_SQUIGGLE"_ns;
492 default: {
493 return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aLineStyle));
498 static nsCString GetClauseAttrName(TF_DA_ATTR_INFO aAttr) {
499 switch (aAttr) {
500 case TF_ATTR_INPUT:
501 return "TF_ATTR_INPUT"_ns;
502 case TF_ATTR_TARGET_CONVERTED:
503 return "TF_ATTR_TARGET_CONVERTED"_ns;
504 case TF_ATTR_CONVERTED:
505 return "TF_ATTR_CONVERTED"_ns;
506 case TF_ATTR_TARGET_NOTCONVERTED:
507 return "TF_ATTR_TARGET_NOTCONVERTED"_ns;
508 case TF_ATTR_INPUT_ERROR:
509 return "TF_ATTR_INPUT_ERROR"_ns;
510 case TF_ATTR_FIXEDCONVERTED:
511 return "TF_ATTR_FIXEDCONVERTED"_ns;
512 case TF_ATTR_OTHER:
513 return "TF_ATTR_OTHER"_ns;
514 default: {
515 return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aAttr));
520 static nsCString GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE& aDispAttr) {
521 nsCString str;
522 str = "crText:{ ";
523 str += GetColorName(aDispAttr.crText);
524 str += " }, crBk:{ ";
525 str += GetColorName(aDispAttr.crBk);
526 str += " }, lsStyle: ";
527 str += GetLineStyleName(aDispAttr.lsStyle);
528 str += ", fBoldLine: ";
529 str += GetBoolName(aDispAttr.fBoldLine);
530 str += ", crLine:{ ";
531 str += GetColorName(aDispAttr.crLine);
532 str += " }, bAttr: ";
533 str += GetClauseAttrName(aDispAttr.bAttr);
534 return str;
537 static const char* GetMouseButtonName(int16_t aButton) {
538 switch (aButton) {
539 case MouseButton::ePrimary:
540 return "LeftButton";
541 case MouseButton::eMiddle:
542 return "MiddleButton";
543 case MouseButton::eSecondary:
544 return "RightButton";
545 default:
546 return "UnknownButton";
550 #define ADD_SEPARATOR_IF_NECESSARY(aStr) \
551 if (!aStr.IsEmpty()) { \
552 aStr.AppendLiteral(", "); \
555 static nsCString GetMouseButtonsName(int16_t aButtons) {
556 if (!aButtons) {
557 return "no buttons"_ns;
559 nsCString names;
560 if (aButtons & MouseButtonsFlag::ePrimaryFlag) {
561 names = "LeftButton";
563 if (aButtons & MouseButtonsFlag::eSecondaryFlag) {
564 ADD_SEPARATOR_IF_NECESSARY(names);
565 names += "RightButton";
567 if (aButtons & MouseButtonsFlag::eMiddleFlag) {
568 ADD_SEPARATOR_IF_NECESSARY(names);
569 names += "MiddleButton";
571 if (aButtons & MouseButtonsFlag::e4thFlag) {
572 ADD_SEPARATOR_IF_NECESSARY(names);
573 names += "4thButton";
575 if (aButtons & MouseButtonsFlag::e5thFlag) {
576 ADD_SEPARATOR_IF_NECESSARY(names);
577 names += "5thButton";
579 return names;
582 static nsCString GetModifiersName(Modifiers aModifiers) {
583 if (aModifiers == MODIFIER_NONE) {
584 return "no modifiers"_ns;
586 nsCString names;
587 if (aModifiers & MODIFIER_ALT) {
588 names = NS_DOM_KEYNAME_ALT;
590 if (aModifiers & MODIFIER_ALTGRAPH) {
591 ADD_SEPARATOR_IF_NECESSARY(names);
592 names += NS_DOM_KEYNAME_ALTGRAPH;
594 if (aModifiers & MODIFIER_CAPSLOCK) {
595 ADD_SEPARATOR_IF_NECESSARY(names);
596 names += NS_DOM_KEYNAME_CAPSLOCK;
598 if (aModifiers & MODIFIER_CONTROL) {
599 ADD_SEPARATOR_IF_NECESSARY(names);
600 names += NS_DOM_KEYNAME_CONTROL;
602 if (aModifiers & MODIFIER_FN) {
603 ADD_SEPARATOR_IF_NECESSARY(names);
604 names += NS_DOM_KEYNAME_FN;
606 if (aModifiers & MODIFIER_FNLOCK) {
607 ADD_SEPARATOR_IF_NECESSARY(names);
608 names += NS_DOM_KEYNAME_FNLOCK;
610 if (aModifiers & MODIFIER_META) {
611 ADD_SEPARATOR_IF_NECESSARY(names);
612 names += NS_DOM_KEYNAME_META;
614 if (aModifiers & MODIFIER_NUMLOCK) {
615 ADD_SEPARATOR_IF_NECESSARY(names);
616 names += NS_DOM_KEYNAME_NUMLOCK;
618 if (aModifiers & MODIFIER_SCROLLLOCK) {
619 ADD_SEPARATOR_IF_NECESSARY(names);
620 names += NS_DOM_KEYNAME_SCROLLLOCK;
622 if (aModifiers & MODIFIER_SHIFT) {
623 ADD_SEPARATOR_IF_NECESSARY(names);
624 names += NS_DOM_KEYNAME_SHIFT;
626 if (aModifiers & MODIFIER_SYMBOL) {
627 ADD_SEPARATOR_IF_NECESSARY(names);
628 names += NS_DOM_KEYNAME_SYMBOL;
630 if (aModifiers & MODIFIER_SYMBOLLOCK) {
631 ADD_SEPARATOR_IF_NECESSARY(names);
632 names += NS_DOM_KEYNAME_SYMBOLLOCK;
634 if (aModifiers & MODIFIER_OS) {
635 ADD_SEPARATOR_IF_NECESSARY(names);
636 names += NS_DOM_KEYNAME_OS;
638 return names;
641 class GetWritingModeName : public nsAutoCString {
642 public:
643 explicit GetWritingModeName(const WritingMode& aWritingMode) {
644 if (!aWritingMode.IsVertical()) {
645 AssignLiteral("Horizontal");
646 return;
648 if (aWritingMode.IsVerticalLR()) {
649 AssignLiteral("Vertical (LR)");
650 return;
652 AssignLiteral("Vertical (RL)");
654 virtual ~GetWritingModeName() {}
657 class GetEscapedUTF8String final : public NS_ConvertUTF16toUTF8 {
658 public:
659 explicit GetEscapedUTF8String(const nsAString& aString)
660 : NS_ConvertUTF16toUTF8(aString) {
661 Escape();
663 explicit GetEscapedUTF8String(const char16ptr_t aString)
664 : NS_ConvertUTF16toUTF8(aString) {
665 Escape();
667 GetEscapedUTF8String(const char16ptr_t aString, uint32_t aLength)
668 : NS_ConvertUTF16toUTF8(aString, aLength) {
669 Escape();
672 private:
673 void Escape() {
674 ReplaceSubstring("\r", "\\r");
675 ReplaceSubstring("\n", "\\n");
676 ReplaceSubstring("\t", "\\t");
680 class GetInputScopeString : public nsAutoCString {
681 public:
682 explicit GetInputScopeString(const nsTArray<InputScope>& aList) {
683 for (InputScope inputScope : aList) {
684 if (!IsEmpty()) {
685 AppendLiteral(", ");
687 switch (inputScope) {
688 case IS_DEFAULT:
689 AppendLiteral("IS_DEFAULT");
690 break;
691 case IS_URL:
692 AppendLiteral("IS_URL");
693 break;
694 case IS_FILE_FULLFILEPATH:
695 AppendLiteral("IS_FILE_FULLFILEPATH");
696 break;
697 case IS_FILE_FILENAME:
698 AppendLiteral("IS_FILE_FILENAME");
699 break;
700 case IS_EMAIL_USERNAME:
701 AppendLiteral("IS_EMAIL_USERNAME");
702 break;
703 case IS_EMAIL_SMTPEMAILADDRESS:
704 AppendLiteral("IS_EMAIL_SMTPEMAILADDRESS");
705 break;
706 case IS_LOGINNAME:
707 AppendLiteral("IS_LOGINNAME");
708 break;
709 case IS_PERSONALNAME_FULLNAME:
710 AppendLiteral("IS_PERSONALNAME_FULLNAME");
711 break;
712 case IS_PERSONALNAME_PREFIX:
713 AppendLiteral("IS_PERSONALNAME_PREFIX");
714 break;
715 case IS_PERSONALNAME_GIVENNAME:
716 AppendLiteral("IS_PERSONALNAME_GIVENNAME");
717 break;
718 case IS_PERSONALNAME_MIDDLENAME:
719 AppendLiteral("IS_PERSONALNAME_MIDDLENAME");
720 break;
721 case IS_PERSONALNAME_SURNAME:
722 AppendLiteral("IS_PERSONALNAME_SURNAME");
723 break;
724 case IS_PERSONALNAME_SUFFIX:
725 AppendLiteral("IS_PERSONALNAME_SUFFIX");
726 break;
727 case IS_ADDRESS_FULLPOSTALADDRESS:
728 AppendLiteral("IS_ADDRESS_FULLPOSTALADDRESS");
729 break;
730 case IS_ADDRESS_POSTALCODE:
731 AppendLiteral("IS_ADDRESS_POSTALCODE");
732 break;
733 case IS_ADDRESS_STREET:
734 AppendLiteral("IS_ADDRESS_STREET");
735 break;
736 case IS_ADDRESS_STATEORPROVINCE:
737 AppendLiteral("IS_ADDRESS_STATEORPROVINCE");
738 break;
739 case IS_ADDRESS_CITY:
740 AppendLiteral("IS_ADDRESS_CITY");
741 break;
742 case IS_ADDRESS_COUNTRYNAME:
743 AppendLiteral("IS_ADDRESS_COUNTRYNAME");
744 break;
745 case IS_ADDRESS_COUNTRYSHORTNAME:
746 AppendLiteral("IS_ADDRESS_COUNTRYSHORTNAME");
747 break;
748 case IS_CURRENCY_AMOUNTANDSYMBOL:
749 AppendLiteral("IS_CURRENCY_AMOUNTANDSYMBOL");
750 break;
751 case IS_CURRENCY_AMOUNT:
752 AppendLiteral("IS_CURRENCY_AMOUNT");
753 break;
754 case IS_DATE_FULLDATE:
755 AppendLiteral("IS_DATE_FULLDATE");
756 break;
757 case IS_DATE_MONTH:
758 AppendLiteral("IS_DATE_MONTH");
759 break;
760 case IS_DATE_DAY:
761 AppendLiteral("IS_DATE_DAY");
762 break;
763 case IS_DATE_YEAR:
764 AppendLiteral("IS_DATE_YEAR");
765 break;
766 case IS_DATE_MONTHNAME:
767 AppendLiteral("IS_DATE_MONTHNAME");
768 break;
769 case IS_DATE_DAYNAME:
770 AppendLiteral("IS_DATE_DAYNAME");
771 break;
772 case IS_DIGITS:
773 AppendLiteral("IS_DIGITS");
774 break;
775 case IS_NUMBER:
776 AppendLiteral("IS_NUMBER");
777 break;
778 case IS_ONECHAR:
779 AppendLiteral("IS_ONECHAR");
780 break;
781 case IS_PASSWORD:
782 AppendLiteral("IS_PASSWORD");
783 break;
784 case IS_TELEPHONE_FULLTELEPHONENUMBER:
785 AppendLiteral("IS_TELEPHONE_FULLTELEPHONENUMBER");
786 break;
787 case IS_TELEPHONE_COUNTRYCODE:
788 AppendLiteral("IS_TELEPHONE_COUNTRYCODE");
789 break;
790 case IS_TELEPHONE_AREACODE:
791 AppendLiteral("IS_TELEPHONE_AREACODE");
792 break;
793 case IS_TELEPHONE_LOCALNUMBER:
794 AppendLiteral("IS_TELEPHONE_LOCALNUMBER");
795 break;
796 case IS_TIME_FULLTIME:
797 AppendLiteral("IS_TIME_FULLTIME");
798 break;
799 case IS_TIME_HOUR:
800 AppendLiteral("IS_TIME_HOUR");
801 break;
802 case IS_TIME_MINORSEC:
803 AppendLiteral("IS_TIME_MINORSEC");
804 break;
805 case IS_NUMBER_FULLWIDTH:
806 AppendLiteral("IS_NUMBER_FULLWIDTH");
807 break;
808 case IS_ALPHANUMERIC_HALFWIDTH:
809 AppendLiteral("IS_ALPHANUMERIC_HALFWIDTH");
810 break;
811 case IS_ALPHANUMERIC_FULLWIDTH:
812 AppendLiteral("IS_ALPHANUMERIC_FULLWIDTH");
813 break;
814 case IS_CURRENCY_CHINESE:
815 AppendLiteral("IS_CURRENCY_CHINESE");
816 break;
817 case IS_BOPOMOFO:
818 AppendLiteral("IS_BOPOMOFO");
819 break;
820 case IS_HIRAGANA:
821 AppendLiteral("IS_HIRAGANA");
822 break;
823 case IS_KATAKANA_HALFWIDTH:
824 AppendLiteral("IS_KATAKANA_HALFWIDTH");
825 break;
826 case IS_KATAKANA_FULLWIDTH:
827 AppendLiteral("IS_KATAKANA_FULLWIDTH");
828 break;
829 case IS_HANJA:
830 AppendLiteral("IS_HANJA");
831 break;
832 case IS_PHRASELIST:
833 AppendLiteral("IS_PHRASELIST");
834 break;
835 case IS_REGULAREXPRESSION:
836 AppendLiteral("IS_REGULAREXPRESSION");
837 break;
838 case IS_SRGS:
839 AppendLiteral("IS_SRGS");
840 break;
841 case IS_XML:
842 AppendLiteral("IS_XML");
843 break;
844 case IS_PRIVATE:
845 AppendLiteral("IS_PRIVATE");
846 break;
847 default:
848 AppendPrintf("Unknown Value(%d)", inputScope);
849 break;
855 /******************************************************************/
856 /* InputScopeImpl */
857 /******************************************************************/
859 class InputScopeImpl final : public ITfInputScope {
860 ~InputScopeImpl() {}
862 public:
863 explicit InputScopeImpl(const nsTArray<InputScope>& aList)
864 : mInputScopes(aList.Clone()) {
865 MOZ_LOG(
866 sTextStoreLog, LogLevel::Info,
867 ("0x%p InputScopeImpl(%s)", this, GetInputScopeString(aList).get()));
870 NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(InputScopeImpl)
872 STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
873 *ppv = nullptr;
874 if ((IID_IUnknown == riid) || (IID_ITfInputScope == riid)) {
875 *ppv = static_cast<ITfInputScope*>(this);
877 if (*ppv) {
878 AddRef();
879 return S_OK;
881 return E_NOINTERFACE;
884 STDMETHODIMP GetInputScopes(InputScope** pprgInputScopes, UINT* pcCount) {
885 uint32_t count = (mInputScopes.IsEmpty() ? 1 : mInputScopes.Length());
887 InputScope* pScope =
888 (InputScope*)CoTaskMemAlloc(sizeof(InputScope) * count);
889 NS_ENSURE_TRUE(pScope, E_OUTOFMEMORY);
891 if (mInputScopes.IsEmpty()) {
892 *pScope = IS_DEFAULT;
893 *pcCount = 1;
894 *pprgInputScopes = pScope;
895 return S_OK;
898 *pcCount = 0;
900 for (uint32_t idx = 0; idx < count; idx++) {
901 *(pScope + idx) = mInputScopes[idx];
902 (*pcCount)++;
905 *pprgInputScopes = pScope;
906 return S_OK;
909 STDMETHODIMP GetPhrase(BSTR** ppbstrPhrases, UINT* pcCount) {
910 return E_NOTIMPL;
912 STDMETHODIMP GetRegularExpression(BSTR* pbstrRegExp) { return E_NOTIMPL; }
913 STDMETHODIMP GetSRGS(BSTR* pbstrSRGS) { return E_NOTIMPL; }
914 STDMETHODIMP GetXML(BSTR* pbstrXML) { return E_NOTIMPL; }
916 private:
917 nsTArray<InputScope> mInputScopes;
920 /******************************************************************/
921 /* TSFStaticSink */
922 /******************************************************************/
924 class TSFStaticSink final : public ITfInputProcessorProfileActivationSink {
925 public:
926 static TSFStaticSink* GetInstance() {
927 if (!sInstance) {
928 RefPtr<ITfThreadMgr> threadMgr = TSFTextStore::GetThreadMgr();
929 if (NS_WARN_IF(!threadMgr)) {
930 MOZ_LOG(
931 sTextStoreLog, LogLevel::Error,
932 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
933 "instance due to no ThreadMgr instance"));
934 return nullptr;
936 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
937 TSFTextStore::GetInputProcessorProfiles();
938 if (NS_WARN_IF(!inputProcessorProfiles)) {
939 MOZ_LOG(
940 sTextStoreLog, LogLevel::Error,
941 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
942 "instance due to no InputProcessorProfiles instance"));
943 return nullptr;
945 RefPtr<TSFStaticSink> staticSink = new TSFStaticSink();
946 if (NS_WARN_IF(!staticSink->Init(threadMgr, inputProcessorProfiles))) {
947 staticSink->Destroy();
948 MOZ_LOG(
949 sTextStoreLog, LogLevel::Error,
950 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
951 "instance"));
952 return nullptr;
954 sInstance = staticSink.forget();
956 return sInstance;
959 static void Shutdown() {
960 if (sInstance) {
961 sInstance->Destroy();
962 sInstance = nullptr;
966 bool Init(ITfThreadMgr* aThreadMgr,
967 ITfInputProcessorProfiles* aInputProcessorProfiles);
968 STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
969 *ppv = nullptr;
970 if (IID_IUnknown == riid ||
971 IID_ITfInputProcessorProfileActivationSink == riid) {
972 *ppv = static_cast<ITfInputProcessorProfileActivationSink*>(this);
974 if (*ppv) {
975 AddRef();
976 return S_OK;
978 return E_NOINTERFACE;
981 NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFStaticSink)
983 const nsString& GetActiveTIPKeyboardDescription() const {
984 return mActiveTIPKeyboardDescription;
987 static bool IsIMM_IMEActive() {
988 // Use IMM API until TSFStaticSink starts to work.
989 if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
990 return IsIMM_IME(::GetKeyboardLayout(0));
992 return sInstance->mIsIMM_IME;
995 static bool IsIMM_IME(HKL aHKL) {
996 return (::ImmGetIMEFileNameW(aHKL, nullptr, 0) > 0);
999 static bool IsTraditionalChinese() {
1000 EnsureInstance();
1001 return sInstance && sInstance->IsTraditionalChineseInternal();
1003 static bool IsSimplifiedChinese() {
1004 EnsureInstance();
1005 return sInstance && sInstance->IsSimplifiedChineseInternal();
1007 static bool IsJapanese() {
1008 EnsureInstance();
1009 return sInstance && sInstance->IsJapaneseInternal();
1011 static bool IsKorean() {
1012 EnsureInstance();
1013 return sInstance && sInstance->IsKoreanInternal();
1017 * ActiveTIP() returns an ID for currently active TIP.
1018 * Please note that this method is expensive due to needs a lot of GUID
1019 * comparations if active language ID is one of CJKT. If you need to
1020 * check TIPs for a specific language, you should check current language
1021 * first.
1023 static TextInputProcessorID ActiveTIP() {
1024 EnsureInstance();
1025 if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
1026 return TextInputProcessorID::eUnknown;
1028 sInstance->ComputeActiveTextInputProcessor();
1029 if (NS_WARN_IF(sInstance->mActiveTIP ==
1030 TextInputProcessorID::eNotComputed)) {
1031 return TextInputProcessorID::eUnknown;
1033 return sInstance->mActiveTIP;
1036 static bool IsMSChangJieOrMSQuickActive() {
1037 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1038 // For avoiding unnecessary computation, we should check if the language
1039 // for current TIP is Traditional Chinese.
1040 if (!IsTraditionalChinese()) {
1041 return false;
1043 switch (ActiveTIP()) {
1044 case TextInputProcessorID::eMicrosoftChangJie:
1045 case TextInputProcessorID::eMicrosoftQuick:
1046 return true;
1047 default:
1048 return false;
1052 static bool IsMSPinyinOrMSWubiActive() {
1053 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1054 // For avoiding unnecessary computation, we should check if the language
1055 // for current TIP is Simplified Chinese.
1056 if (!IsSimplifiedChinese()) {
1057 return false;
1059 switch (ActiveTIP()) {
1060 case TextInputProcessorID::eMicrosoftPinyin:
1061 case TextInputProcessorID::eMicrosoftWubi:
1062 return true;
1063 default:
1064 return false;
1068 static bool IsMSJapaneseIMEActive() {
1069 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1070 // For avoiding unnecessary computation, we should check if the language
1071 // for current TIP is Japanese.
1072 if (!IsJapanese()) {
1073 return false;
1075 return ActiveTIP() == TextInputProcessorID::eMicrosoftIMEForJapanese;
1078 static bool IsGoogleJapaneseInputActive() {
1079 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1080 // For avoiding unnecessary computation, we should check if the language
1081 // for current TIP is Japanese.
1082 if (!IsJapanese()) {
1083 return false;
1085 return ActiveTIP() == TextInputProcessorID::eGoogleJapaneseInput;
1088 static bool IsATOKActive() {
1089 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1090 // For avoiding unnecessary computation, we should check if active TIP is
1091 // ATOK first since it's cheaper.
1092 return IsJapanese() && sInstance->IsATOKActiveInternal();
1095 // Note that ATOK 2011 - 2016 refers native caret position for deciding its
1096 // popup window position.
1097 static bool IsATOKReferringNativeCaretActive() {
1098 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1099 // For avoiding unnecessary computation, we should check if active TIP is
1100 // ATOK first since it's cheaper.
1101 if (!IsJapanese() || !sInstance->IsATOKActiveInternal()) {
1102 return false;
1104 switch (ActiveTIP()) {
1105 case TextInputProcessorID::eATOK2011:
1106 case TextInputProcessorID::eATOK2012:
1107 case TextInputProcessorID::eATOK2013:
1108 case TextInputProcessorID::eATOK2014:
1109 case TextInputProcessorID::eATOK2015:
1110 return true;
1111 default:
1112 return false;
1116 private:
1117 static void EnsureInstance() {
1118 if (!sInstance) {
1119 RefPtr<TSFStaticSink> staticSink = GetInstance();
1120 Unused << staticSink;
1124 bool IsTraditionalChineseInternal() const { return mLangID == 0x0404; }
1125 bool IsSimplifiedChineseInternal() const { return mLangID == 0x0804; }
1126 bool IsJapaneseInternal() const { return mLangID == 0x0411; }
1127 bool IsKoreanInternal() const { return mLangID == 0x0412; }
1129 bool IsATOKActiveInternal() {
1130 EnsureInitActiveTIPKeyboard();
1131 // FYI: Name of packaged ATOK includes the release year like "ATOK 2015".
1132 // Name of ATOK Passport (subscription) equals "ATOK".
1133 return StringBeginsWith(mActiveTIPKeyboardDescription, u"ATOK "_ns) ||
1134 mActiveTIPKeyboardDescription.EqualsLiteral("ATOK");
1137 void ComputeActiveTextInputProcessor() {
1138 if (mActiveTIP != TextInputProcessorID::eNotComputed) {
1139 return;
1142 if (mActiveTIPGUID == GUID_NULL) {
1143 mActiveTIP = TextInputProcessorID::eNone;
1144 return;
1147 // Comparing GUID is slow. So, we should use language information to
1148 // reduce the comparing cost for TIP which is not we do not support
1149 // specifically since they are always compared with all supported TIPs.
1150 switch (mLangID) {
1151 case 0x0404:
1152 mActiveTIP = ComputeActiveTIPAsTraditionalChinese();
1153 break;
1154 case 0x0411:
1155 mActiveTIP = ComputeActiveTIPAsJapanese();
1156 break;
1157 case 0x0412:
1158 mActiveTIP = ComputeActiveTIPAsKorean();
1159 break;
1160 case 0x0804:
1161 mActiveTIP = ComputeActiveTIPAsSimplifiedChinese();
1162 break;
1163 default:
1164 mActiveTIP = TextInputProcessorID::eUnknown;
1168 TextInputProcessorID ComputeActiveTIPAsJapanese() {
1169 // {A76C93D9-5523-4E90-AAFA-4DB112F9AC76} (Win7, Win8.1, Win10)
1170 static const GUID kMicrosoftIMEForJapaneseGUID = {
1171 0xA76C93D9,
1172 0x5523,
1173 0x4E90,
1174 {0xAA, 0xFA, 0x4D, 0xB1, 0x12, 0xF9, 0xAC, 0x76}};
1175 if (mActiveTIPGUID == kMicrosoftIMEForJapaneseGUID) {
1176 return TextInputProcessorID::eMicrosoftIMEForJapanese;
1178 // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64}
1179 static const GUID kMicrosoftOfficeIME2010ForJapaneseGUID = {
1180 0x54EDCC94,
1181 0x1524,
1182 0x4BB1,
1183 {0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64}};
1184 if (mActiveTIPGUID == kMicrosoftOfficeIME2010ForJapaneseGUID) {
1185 return TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese;
1187 // {773EB24E-CA1D-4B1B-B420-FA985BB0B80D}
1188 static const GUID kGoogleJapaneseInputGUID = {
1189 0x773EB24E,
1190 0xCA1D,
1191 0x4B1B,
1192 {0xB4, 0x20, 0xFA, 0x98, 0x5B, 0xB0, 0xB8, 0x0D}};
1193 if (mActiveTIPGUID == kGoogleJapaneseInputGUID) {
1194 return TextInputProcessorID::eGoogleJapaneseInput;
1196 // {F9C24A5C-8A53-499D-9572-93B2FF582115}
1197 static const GUID kATOK2011GUID = {
1198 0xF9C24A5C,
1199 0x8A53,
1200 0x499D,
1201 {0x95, 0x72, 0x93, 0xB2, 0xFF, 0x58, 0x21, 0x15}};
1202 if (mActiveTIPGUID == kATOK2011GUID) {
1203 return TextInputProcessorID::eATOK2011;
1205 // {1DE01562-F445-401B-B6C3-E5B18DB79461}
1206 static const GUID kATOK2012GUID = {
1207 0x1DE01562,
1208 0xF445,
1209 0x401B,
1210 {0xB6, 0xC3, 0xE5, 0xB1, 0x8D, 0xB7, 0x94, 0x61}};
1211 if (mActiveTIPGUID == kATOK2012GUID) {
1212 return TextInputProcessorID::eATOK2012;
1214 // {3C4DB511-189A-4168-B6EA-BFD0B4C85615}
1215 static const GUID kATOK2013GUID = {
1216 0x3C4DB511,
1217 0x189A,
1218 0x4168,
1219 {0xB6, 0xEA, 0xBF, 0xD0, 0xB4, 0xC8, 0x56, 0x15}};
1220 if (mActiveTIPGUID == kATOK2013GUID) {
1221 return TextInputProcessorID::eATOK2013;
1223 // {4EF33B79-6AA9-4271-B4BF-9321C279381B}
1224 static const GUID kATOK2014GUID = {
1225 0x4EF33B79,
1226 0x6AA9,
1227 0x4271,
1228 {0xB4, 0xBF, 0x93, 0x21, 0xC2, 0x79, 0x38, 0x1B}};
1229 if (mActiveTIPGUID == kATOK2014GUID) {
1230 return TextInputProcessorID::eATOK2014;
1232 // {EAB4DC00-CE2E-483D-A86A-E6B99DA9599A}
1233 static const GUID kATOK2015GUID = {
1234 0xEAB4DC00,
1235 0xCE2E,
1236 0x483D,
1237 {0xA8, 0x6A, 0xE6, 0xB9, 0x9D, 0xA9, 0x59, 0x9A}};
1238 if (mActiveTIPGUID == kATOK2015GUID) {
1239 return TextInputProcessorID::eATOK2015;
1241 // {0B557B4C-5740-4110-A60A-1493FA10BF2B}
1242 static const GUID kATOK2016GUID = {
1243 0x0B557B4C,
1244 0x5740,
1245 0x4110,
1246 {0xA6, 0x0A, 0x14, 0x93, 0xFA, 0x10, 0xBF, 0x2B}};
1247 if (mActiveTIPGUID == kATOK2016GUID) {
1248 return TextInputProcessorID::eATOK2016;
1251 // * ATOK 2017
1252 // - {6DBFD8F5-701D-11E6-920F-782BCBA6348F}
1253 // * ATOK Passport (confirmed with version 31.1.2)
1254 // - {A38F2FD9-7199-45E1-841C-BE0313D8052F}
1256 if (IsATOKActiveInternal()) {
1257 return TextInputProcessorID::eATOKUnknown;
1260 // {E6D66705-1EDA-4373-8D01-1D0CB2D054C7}
1261 static const GUID kJapanist10GUID = {
1262 0xE6D66705,
1263 0x1EDA,
1264 0x4373,
1265 {0x8D, 0x01, 0x1D, 0x0C, 0xB2, 0xD0, 0x54, 0xC7}};
1266 if (mActiveTIPGUID == kJapanist10GUID) {
1267 return TextInputProcessorID::eJapanist10;
1270 return TextInputProcessorID::eUnknown;
1273 TextInputProcessorID ComputeActiveTIPAsTraditionalChinese() {
1274 // {B2F9C502-1742-11D4-9790-0080C882687E} (Win8.1, Win10)
1275 static const GUID kMicrosoftBopomofoGUID = {
1276 0xB2F9C502,
1277 0x1742,
1278 0x11D4,
1279 {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1280 if (mActiveTIPGUID == kMicrosoftBopomofoGUID) {
1281 return TextInputProcessorID::eMicrosoftBopomofo;
1283 // {4BDF9F03-C7D3-11D4-B2AB-0080C882687E} (Win7, Win8.1, Win10)
1284 static const GUID kMicrosoftChangJieGUID = {
1285 0x4BDF9F03,
1286 0xC7D3,
1287 0x11D4,
1288 {0xB2, 0xAB, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1289 if (mActiveTIPGUID == kMicrosoftChangJieGUID) {
1290 return TextInputProcessorID::eMicrosoftChangJie;
1292 // {761309DE-317A-11D4-9B5D-0080C882687E} (Win7)
1293 static const GUID kMicrosoftPhoneticGUID = {
1294 0x761309DE,
1295 0x317A,
1296 0x11D4,
1297 {0x9B, 0x5D, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1298 if (mActiveTIPGUID == kMicrosoftPhoneticGUID) {
1299 return TextInputProcessorID::eMicrosoftPhonetic;
1301 // {6024B45F-5C54-11D4-B921-0080C882687E} (Win7, Win8.1, Win10)
1302 static const GUID kMicrosoftQuickGUID = {
1303 0x6024B45F,
1304 0x5C54,
1305 0x11D4,
1306 {0xB9, 0x21, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1307 if (mActiveTIPGUID == kMicrosoftQuickGUID) {
1308 return TextInputProcessorID::eMicrosoftQuick;
1310 // {F3BA907A-6C7E-11D4-97FA-0080C882687E} (Win7)
1311 static const GUID kMicrosoftNewChangJieGUID = {
1312 0xF3BA907A,
1313 0x6C7E,
1314 0x11D4,
1315 {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1316 if (mActiveTIPGUID == kMicrosoftNewChangJieGUID) {
1317 return TextInputProcessorID::eMicrosoftNewChangJie;
1319 // {B2F9C502-1742-11D4-9790-0080C882687E} (Win7)
1320 static const GUID kMicrosoftNewPhoneticGUID = {
1321 0xB2F9C502,
1322 0x1742,
1323 0x11D4,
1324 {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1325 if (mActiveTIPGUID == kMicrosoftNewPhoneticGUID) {
1326 return TextInputProcessorID::eMicrosoftNewPhonetic;
1328 // {0B883BA0-C1C7-11D4-87F9-0080C882687E} (Win7)
1329 static const GUID kMicrosoftNewQuickGUID = {
1330 0x0B883BA0,
1331 0xC1C7,
1332 0x11D4,
1333 {0x87, 0xF9, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1334 if (mActiveTIPGUID == kMicrosoftNewQuickGUID) {
1335 return TextInputProcessorID::eMicrosoftNewQuick;
1338 // NOTE: There are some other Traditional Chinese TIPs installed in Windows:
1339 // * Chinese Traditional Array (version 6.0)
1340 // - {D38EFF65-AA46-4FD5-91A7-67845FB02F5B} (Win7, Win8.1)
1341 // * Chinese Traditional DaYi (version 6.0)
1342 // - {037B2C25-480C-4D7F-B027-D6CA6B69788A} (Win7, Win8.1)
1344 // {B58630B5-0ED3-4335-BBC9-E77BBCB43CAD}
1345 static const GUID kFreeChangJieGUID = {
1346 0xB58630B5,
1347 0x0ED3,
1348 0x4335,
1349 {0xBB, 0xC9, 0xE7, 0x7B, 0xBC, 0xB4, 0x3C, 0xAD}};
1350 if (mActiveTIPGUID == kFreeChangJieGUID) {
1351 return TextInputProcessorID::eFreeChangJie;
1354 return TextInputProcessorID::eUnknown;
1357 TextInputProcessorID ComputeActiveTIPAsSimplifiedChinese() {
1358 // FYI: This matches with neither "Microsoft Pinyin ABC Input Style" nor
1359 // "Microsoft Pinyin New Experience Input Style" on Win7.
1360 // {FA550B04-5AD7-411F-A5AC-CA038EC515D7} (Win8.1, Win10)
1361 static const GUID kMicrosoftPinyinGUID = {
1362 0xFA550B04,
1363 0x5AD7,
1364 0x411F,
1365 {0xA5, 0xAC, 0xCA, 0x03, 0x8E, 0xC5, 0x15, 0xD7}};
1366 if (mActiveTIPGUID == kMicrosoftPinyinGUID) {
1367 return TextInputProcessorID::eMicrosoftPinyin;
1370 // {F3BA9077-6C7E-11D4-97FA-0080C882687E} (Win7)
1371 static const GUID kMicrosoftPinyinNewExperienceInputStyleGUID = {
1372 0xF3BA9077,
1373 0x6C7E,
1374 0x11D4,
1375 {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1376 if (mActiveTIPGUID == kMicrosoftPinyinNewExperienceInputStyleGUID) {
1377 return TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle;
1379 // {82590C13-F4DD-44F4-BA1D-8667246FDF8E} (Win8.1, Win10)
1380 static const GUID kMicrosoftWubiGUID = {
1381 0x82590C13,
1382 0xF4DD,
1383 0x44F4,
1384 {0xBA, 0x1D, 0x86, 0x67, 0x24, 0x6F, 0xDF, 0x8E}};
1385 if (mActiveTIPGUID == kMicrosoftWubiGUID) {
1386 return TextInputProcessorID::eMicrosoftWubi;
1388 // NOTE: There are some other Simplified Chinese TIPs installed in Windows:
1389 // * Chinese Simplified QuanPin (version 6.0)
1390 // - {54FC610E-6ABD-4685-9DDD-A130BDF1B170} (Win8.1)
1391 // * Chinese Simplified ZhengMa (version 6.0)
1392 // - {733B4D81-3BC3-4132-B91A-E9CDD5E2BFC9} (Win8.1)
1393 // * Chinese Simplified ShuangPin (version 6.0)
1394 // - {EF63706D-31C4-490E-9DBB-BD150ADC454B} (Win8.1)
1395 // * Microsoft Pinyin ABC Input Style
1396 // - {FCA121D2-8C6D-41FB-B2DE-A2AD110D4820} (Win7)
1397 return TextInputProcessorID::eUnknown;
1400 TextInputProcessorID ComputeActiveTIPAsKorean() {
1401 // {B5FE1F02-D5F2-4445-9C03-C568F23C99A1} (Win7, Win8.1, Win10)
1402 static const GUID kMicrosoftIMEForKoreanGUID = {
1403 0xB5FE1F02,
1404 0xD5F2,
1405 0x4445,
1406 {0x9C, 0x03, 0xC5, 0x68, 0xF2, 0x3C, 0x99, 0xA1}};
1407 if (mActiveTIPGUID == kMicrosoftIMEForKoreanGUID) {
1408 return TextInputProcessorID::eMicrosoftIMEForKorean;
1410 // {B60AF051-257A-46BC-B9D3-84DAD819BAFB} (Win8.1, Win10)
1411 static const GUID kMicrosoftOldHangulGUID = {
1412 0xB60AF051,
1413 0x257A,
1414 0x46BC,
1415 {0xB9, 0xD3, 0x84, 0xDA, 0xD8, 0x19, 0xBA, 0xFB}};
1416 if (mActiveTIPGUID == kMicrosoftOldHangulGUID) {
1417 return TextInputProcessorID::eMicrosoftOldHangul;
1420 // NOTE: There is the other Korean TIP installed in Windows:
1421 // * Microsoft IME 2010
1422 // - {48878C45-93F9-4aaf-A6A1-272CD863C4F5} (Win7)
1424 return TextInputProcessorID::eUnknown;
1427 public: // ITfInputProcessorProfileActivationSink
1428 STDMETHODIMP OnActivated(DWORD, LANGID, REFCLSID, REFGUID, REFGUID, HKL,
1429 DWORD);
1431 private:
1432 TSFStaticSink();
1433 virtual ~TSFStaticSink() {}
1435 bool EnsureInitActiveTIPKeyboard();
1437 void Destroy();
1439 void GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
1440 REFGUID aProfile, nsAString& aDescription);
1441 bool IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
1442 REFGUID aProfile);
1444 TextInputProcessorID mActiveTIP;
1446 // Cookie of installing ITfInputProcessorProfileActivationSink
1447 DWORD mIPProfileCookie;
1449 LANGID mLangID;
1451 // True if current IME is implemented with IMM.
1452 bool mIsIMM_IME;
1453 // True if OnActivated() is already called
1454 bool mOnActivatedCalled;
1456 RefPtr<ITfThreadMgr> mThreadMgr;
1457 RefPtr<ITfInputProcessorProfiles> mInputProcessorProfiles;
1459 // Active TIP keyboard's description. If active language profile isn't TIP,
1460 // i.e., IMM-IME or just a keyboard layout, this is empty.
1461 nsString mActiveTIPKeyboardDescription;
1463 // Active TIP's GUID
1464 GUID mActiveTIPGUID;
1466 static StaticRefPtr<TSFStaticSink> sInstance;
1469 StaticRefPtr<TSFStaticSink> TSFStaticSink::sInstance;
1471 TSFStaticSink::TSFStaticSink()
1472 : mActiveTIP(TextInputProcessorID::eNotComputed),
1473 mIPProfileCookie(TF_INVALID_COOKIE),
1474 mLangID(0),
1475 mIsIMM_IME(false),
1476 mOnActivatedCalled(false),
1477 mActiveTIPGUID(GUID_NULL) {}
1479 bool TSFStaticSink::Init(ITfThreadMgr* aThreadMgr,
1480 ITfInputProcessorProfiles* aInputProcessorProfiles) {
1481 MOZ_ASSERT(!mThreadMgr && !mInputProcessorProfiles,
1482 "TSFStaticSink::Init() must be called only once");
1484 mThreadMgr = aThreadMgr;
1485 mInputProcessorProfiles = aInputProcessorProfiles;
1487 RefPtr<ITfSource> source;
1488 HRESULT hr =
1489 mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
1490 if (FAILED(hr)) {
1491 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1492 ("0x%p TSFStaticSink::Init() FAILED to get ITfSource "
1493 "instance (0x%08X)",
1494 this, hr));
1495 return false;
1498 // NOTE: On Vista or later, Windows let us know activate IME changed only
1499 // with ITfInputProcessorProfileActivationSink.
1500 hr = source->AdviseSink(
1501 IID_ITfInputProcessorProfileActivationSink,
1502 static_cast<ITfInputProcessorProfileActivationSink*>(this),
1503 &mIPProfileCookie);
1504 if (FAILED(hr) || mIPProfileCookie == TF_INVALID_COOKIE) {
1505 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1506 ("0x%p TSFStaticSink::Init() FAILED to install "
1507 "ITfInputProcessorProfileActivationSink (0x%08X)",
1508 this, hr));
1509 return false;
1512 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1513 ("0x%p TSFStaticSink::Init(), "
1514 "mIPProfileCookie=0x%08X",
1515 this, mIPProfileCookie));
1516 return true;
1519 void TSFStaticSink::Destroy() {
1520 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1521 ("0x%p TSFStaticSink::Shutdown() "
1522 "mIPProfileCookie=0x%08X",
1523 this, mIPProfileCookie));
1525 if (mIPProfileCookie != TF_INVALID_COOKIE) {
1526 RefPtr<ITfSource> source;
1527 HRESULT hr =
1528 mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
1529 if (FAILED(hr)) {
1530 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1531 ("0x%p TSFStaticSink::Shutdown() FAILED to get "
1532 "ITfSource instance (0x%08X)",
1533 this, hr));
1534 } else {
1535 hr = source->UnadviseSink(mIPProfileCookie);
1536 if (FAILED(hr)) {
1537 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1538 ("0x%p TSFTextStore::Shutdown() FAILED to uninstall "
1539 "ITfInputProcessorProfileActivationSink (0x%08X)",
1540 this, hr));
1545 mThreadMgr = nullptr;
1546 mInputProcessorProfiles = nullptr;
1549 STDMETHODIMP
1550 TSFStaticSink::OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID rclsid,
1551 REFGUID catid, REFGUID guidProfile, HKL hkl,
1552 DWORD dwFlags) {
1553 if ((dwFlags & TF_IPSINK_FLAG_ACTIVE) &&
1554 (dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT ||
1555 catid == GUID_TFCAT_TIP_KEYBOARD)) {
1556 mOnActivatedCalled = true;
1557 mActiveTIP = TextInputProcessorID::eNotComputed;
1558 mActiveTIPGUID = guidProfile;
1559 mLangID = langid & 0xFFFF;
1560 mIsIMM_IME = IsIMM_IME(hkl);
1561 GetTIPDescription(rclsid, langid, guidProfile,
1562 mActiveTIPKeyboardDescription);
1563 if (mActiveTIPGUID != GUID_NULL) {
1564 // key should be "LocaleID|Description". Although GUID of the
1565 // profile is unique key since description may be localized for system
1566 // language, unfortunately, it's too long to record as key with its
1567 // description. Therefore, we should record only the description with
1568 // LocaleID because Microsoft IME may not include language information.
1569 // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
1570 nsAutoString key;
1571 key.AppendPrintf("0x%04X|", mLangID);
1572 nsAutoString description(mActiveTIPKeyboardDescription);
1573 static const uint32_t kMaxDescriptionLength = 72 - key.Length();
1574 if (description.Length() > kMaxDescriptionLength) {
1575 if (NS_IS_LOW_SURROGATE(description[kMaxDescriptionLength - 1]) &&
1576 NS_IS_HIGH_SURROGATE(description[kMaxDescriptionLength - 2])) {
1577 description.Truncate(kMaxDescriptionLength - 2);
1578 } else {
1579 description.Truncate(kMaxDescriptionLength - 1);
1581 // U+2026 is "..."
1582 description.Append(char16_t(0x2026));
1584 key.Append(description);
1585 Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_WINDOWS, key,
1586 true);
1588 // Notify IMEHandler of changing active keyboard layout.
1589 IMEHandler::OnKeyboardLayoutChanged();
1591 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1592 ("0x%p TSFStaticSink::OnActivated(dwProfileType=%s (0x%08X), "
1593 "langid=0x%08X, rclsid=%s, catid=%s, guidProfile=%s, hkl=0x%08X, "
1594 "dwFlags=0x%08X (TF_IPSINK_FLAG_ACTIVE: %s)), mIsIMM_IME=%s, "
1595 "mActiveTIPDescription=\"%s\"",
1596 this,
1597 dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR
1598 ? "TF_PROFILETYPE_INPUTPROCESSOR"
1599 : dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT
1600 ? "TF_PROFILETYPE_KEYBOARDLAYOUT"
1601 : "Unknown",
1602 dwProfileType, langid, GetCLSIDNameStr(rclsid).get(),
1603 GetGUIDNameStr(catid).get(), GetGUIDNameStr(guidProfile).get(), hkl,
1604 dwFlags, GetBoolName(dwFlags & TF_IPSINK_FLAG_ACTIVE),
1605 GetBoolName(mIsIMM_IME),
1606 NS_ConvertUTF16toUTF8(mActiveTIPKeyboardDescription).get()));
1607 return S_OK;
1610 bool TSFStaticSink::EnsureInitActiveTIPKeyboard() {
1611 if (mOnActivatedCalled) {
1612 return true;
1615 RefPtr<ITfInputProcessorProfileMgr> profileMgr;
1616 HRESULT hr = mInputProcessorProfiles->QueryInterface(
1617 IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
1618 if (FAILED(hr) || !profileMgr) {
1619 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1620 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1621 "to get input processor profile manager, hr=0x%08X",
1622 this, hr));
1623 return false;
1626 TF_INPUTPROCESSORPROFILE profile;
1627 hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
1628 if (hr == S_FALSE) {
1629 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1630 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1631 "to get active keyboard layout profile due to no active profile, "
1632 "hr=0x%08X",
1633 this, hr));
1634 // XXX Should we call OnActivated() with arguments like non-TIP in this
1635 // case?
1636 return false;
1638 if (FAILED(hr)) {
1639 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1640 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1641 "to get active TIP keyboard, hr=0x%08X",
1642 this, hr));
1643 return false;
1646 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1647 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), "
1648 "calling OnActivated() manually...",
1649 this));
1650 OnActivated(profile.dwProfileType, profile.langid, profile.clsid,
1651 profile.catid, profile.guidProfile, ::GetKeyboardLayout(0),
1652 TF_IPSINK_FLAG_ACTIVE);
1653 return true;
1656 void TSFStaticSink::GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
1657 REFGUID aProfile,
1658 nsAString& aDescription) {
1659 aDescription.Truncate();
1661 if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
1662 return;
1665 BSTR description = nullptr;
1666 HRESULT hr = mInputProcessorProfiles->GetLanguageProfileDescription(
1667 aTextService, aLangID, aProfile, &description);
1668 if (FAILED(hr)) {
1669 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1670 ("0x%p TSFStaticSink::InitActiveTIPDescription() FAILED "
1671 "due to GetLanguageProfileDescription() failure, hr=0x%08X",
1672 this, hr));
1673 return;
1676 if (description && description[0]) {
1677 aDescription.Assign(description);
1679 ::SysFreeString(description);
1682 bool TSFStaticSink::IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
1683 REFGUID aProfile) {
1684 if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
1685 return false;
1688 RefPtr<IEnumTfLanguageProfiles> enumLangProfiles;
1689 HRESULT hr = mInputProcessorProfiles->EnumLanguageProfiles(
1690 aLangID, getter_AddRefs(enumLangProfiles));
1691 if (FAILED(hr) || !enumLangProfiles) {
1692 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1693 ("0x%p TSFStaticSink::IsTIPCategoryKeyboard(), FAILED "
1694 "to get language profiles enumerator, hr=0x%08X",
1695 this, hr));
1696 return false;
1699 TF_LANGUAGEPROFILE profile;
1700 ULONG fetch = 0;
1701 while (SUCCEEDED(enumLangProfiles->Next(1, &profile, &fetch)) && fetch) {
1702 // XXX We're not sure a profile is registered with two or more categories.
1703 if (profile.clsid == aTextService && profile.guidProfile == aProfile &&
1704 profile.catid == GUID_TFCAT_TIP_KEYBOARD) {
1705 return true;
1708 return false;
1711 /******************************************************************/
1712 /* TSFPreference */
1713 /******************************************************************/
1715 class TSFPrefs final {
1716 public:
1717 #define DECL_AND_IMPL_BOOL_PREF(aPref, aName, aDefaultValue) \
1718 static bool aName() { \
1719 static bool s##aName##Value = Preferences::GetBool(aPref, aDefaultValue); \
1720 return s##aName##Value; \
1723 DECL_AND_IMPL_BOOL_PREF("intl.ime.hack.set_input_scope_of_url_bar_to_default",
1724 ShouldSetInputScopeOfURLBarToDefault, true)
1725 DECL_AND_IMPL_BOOL_PREF(
1726 "intl.tsf.hack.allow_to_stop_hacking_on_build_17643_or_later",
1727 AllowToStopHackingOnBuild17643OrLater, false)
1728 DECL_AND_IMPL_BOOL_PREF("intl.tsf.hack.atok.create_native_caret",
1729 NeedToCreateNativeCaretForLegacyATOK, true)
1730 DECL_AND_IMPL_BOOL_PREF(
1731 "intl.tsf.hack.atok.do_not_return_no_layout_error_of_composition_string",
1732 DoNotReturnNoLayoutErrorToATOKOfCompositionString, true)
1733 DECL_AND_IMPL_BOOL_PREF(
1734 "intl.tsf.hack.japanist10."
1735 "do_not_return_no_layout_error_of_composition_string",
1736 DoNotReturnNoLayoutErrorToJapanist10OfCompositionString, true)
1737 DECL_AND_IMPL_BOOL_PREF(
1738 "intl.tsf.hack.ms_simplified_chinese.do_not_return_no_layout_error",
1739 DoNotReturnNoLayoutErrorToMSSimplifiedTIP, true)
1740 DECL_AND_IMPL_BOOL_PREF(
1741 "intl.tsf.hack.ms_traditional_chinese.do_not_return_no_layout_error",
1742 DoNotReturnNoLayoutErrorToMSTraditionalTIP, true)
1743 DECL_AND_IMPL_BOOL_PREF(
1744 "intl.tsf.hack.free_chang_jie.do_not_return_no_layout_error",
1745 DoNotReturnNoLayoutErrorToFreeChangJie, true)
1746 DECL_AND_IMPL_BOOL_PREF(
1747 "intl.tsf.hack.ms_japanese_ime.do_not_return_no_layout_error_at_first_"
1748 "char",
1749 DoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar, true)
1750 DECL_AND_IMPL_BOOL_PREF(
1751 "intl.tsf.hack.ms_japanese_ime.do_not_return_no_layout_error_at_caret",
1752 DoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret, true)
1753 DECL_AND_IMPL_BOOL_PREF(
1754 "intl.tsf.hack.ms_simplified_chinese.query_insert_result",
1755 NeedToHackQueryInsertForMSSimplifiedTIP, true)
1756 DECL_AND_IMPL_BOOL_PREF(
1757 "intl.tsf.hack.ms_traditional_chinese.query_insert_result",
1758 NeedToHackQueryInsertForMSTraditionalTIP, true)
1760 #undef DECL_AND_IMPL_BOOL_PREF
1763 /******************************************************************/
1764 /* TSFTextStore */
1765 /******************************************************************/
1767 StaticRefPtr<ITfThreadMgr> TSFTextStore::sThreadMgr;
1768 StaticRefPtr<ITfMessagePump> TSFTextStore::sMessagePump;
1769 StaticRefPtr<ITfKeystrokeMgr> TSFTextStore::sKeystrokeMgr;
1770 StaticRefPtr<ITfDisplayAttributeMgr> TSFTextStore::sDisplayAttrMgr;
1771 StaticRefPtr<ITfCategoryMgr> TSFTextStore::sCategoryMgr;
1772 StaticRefPtr<ITfCompartment> TSFTextStore::sCompartmentForOpenClose;
1773 StaticRefPtr<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr;
1774 StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext;
1775 StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles;
1776 StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore;
1777 const MSG* TSFTextStore::sHandlingKeyMsg = nullptr;
1778 DWORD TSFTextStore::sClientId = 0;
1779 bool TSFTextStore::sIsKeyboardEventDispatched = false;
1781 #define TEXTSTORE_DEFAULT_VIEW (1)
1783 TSFTextStore::TSFTextStore()
1784 : mEditCookie(0),
1785 mSinkMask(0),
1786 mLock(0),
1787 mLockQueued(0),
1788 mHandlingKeyMessage(0),
1789 mContentForTSF(mComposition, mSelectionForTSF),
1790 mRequestedAttrValues(false),
1791 mIsRecordingActionsWithoutLock(false),
1792 mHasReturnedNoLayoutError(false),
1793 mWaitingQueryLayout(false),
1794 mPendingDestroy(false),
1795 mDeferClearingContentForTSF(false),
1796 mDeferNotifyingTSF(false),
1797 mDeferCommittingComposition(false),
1798 mDeferCancellingComposition(false),
1799 mDestroyed(false),
1800 mBeingDestroyed(false) {
1801 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
1802 mRequestedAttrs[i] = false;
1805 // We hope that 5 or more actions don't occur at once.
1806 mPendingActions.SetCapacity(5);
1808 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1809 ("0x%p TSFTextStore::TSFTextStore() SUCCEEDED", this));
1812 TSFTextStore::~TSFTextStore() {
1813 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1814 ("0x%p TSFTextStore instance is destroyed", this));
1817 bool TSFTextStore::Init(nsWindowBase* aWidget, const InputContext& aContext) {
1818 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1819 ("0x%p TSFTextStore::Init(aWidget=0x%p)", this, aWidget));
1821 if (NS_WARN_IF(!aWidget) || NS_WARN_IF(aWidget->Destroyed())) {
1822 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1823 ("0x%p TSFTextStore::Init() FAILED due to being initialized with "
1824 "destroyed widget",
1825 this));
1826 return false;
1829 if (mDocumentMgr) {
1830 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1831 ("0x%p TSFTextStore::Init() FAILED due to already initialized",
1832 this));
1833 return false;
1836 mWidget = aWidget;
1837 if (NS_WARN_IF(!mWidget)) {
1838 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1839 ("0x%p TSFTextStore::Init() FAILED "
1840 "due to aWidget is nullptr ",
1841 this));
1842 return false;
1844 mDispatcher = mWidget->GetTextEventDispatcher();
1845 if (NS_WARN_IF(!mDispatcher)) {
1846 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1847 ("0x%p TSFTextStore::Init() FAILED "
1848 "due to aWidget->GetTextEventDispatcher() failure",
1849 this));
1850 return false;
1853 SetInputScope(aContext.mHTMLInputType, aContext.mHTMLInputInputmode,
1854 aContext.mInPrivateBrowsing);
1856 // Create document manager
1857 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
1858 RefPtr<ITfDocumentMgr> documentMgr;
1859 HRESULT hr = threadMgr->CreateDocumentMgr(getter_AddRefs(documentMgr));
1860 if (NS_WARN_IF(FAILED(hr))) {
1861 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1862 ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr "
1863 "(0x%08X)",
1864 this, hr));
1865 return false;
1867 if (NS_WARN_IF(mDestroyed)) {
1868 MOZ_LOG(
1869 sTextStoreLog, LogLevel::Error,
1870 ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr due to "
1871 "TextStore being destroyed during calling "
1872 "ITfThreadMgr::CreateDocumentMgr()",
1873 this));
1874 return false;
1876 // Create context and add it to document manager
1877 RefPtr<ITfContext> context;
1878 hr = documentMgr->CreateContext(sClientId, 0,
1879 static_cast<ITextStoreACP*>(this),
1880 getter_AddRefs(context), &mEditCookie);
1881 if (NS_WARN_IF(FAILED(hr))) {
1882 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1883 ("0x%p TSFTextStore::Init() FAILED to create the context "
1884 "(0x%08X)",
1885 this, hr));
1886 return false;
1888 if (NS_WARN_IF(mDestroyed)) {
1889 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1890 ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
1891 "TextStore being destroyed during calling "
1892 "ITfDocumentMgr::CreateContext()",
1893 this));
1894 return false;
1897 hr = documentMgr->Push(context);
1898 if (NS_WARN_IF(FAILED(hr))) {
1899 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1900 ("0x%p TSFTextStore::Init() FAILED to push the context (0x%08X)",
1901 this, hr));
1902 return false;
1904 if (NS_WARN_IF(mDestroyed)) {
1905 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1906 ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
1907 "TextStore being destroyed during calling ITfDocumentMgr::Push()",
1908 this));
1909 documentMgr->Pop(TF_POPF_ALL);
1910 return false;
1913 mDocumentMgr = documentMgr;
1914 mContext = context;
1916 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1917 ("0x%p TSFTextStore::Init() succeeded: "
1918 "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08X",
1919 this, mDocumentMgr.get(), mContext.get(), mEditCookie));
1921 return true;
1924 void TSFTextStore::Destroy() {
1925 if (mBeingDestroyed) {
1926 return;
1929 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1930 ("0x%p TSFTextStore::Destroy(), mLock=%s, "
1931 "mComposition.IsComposing()=%s, mHandlingKeyMessage=%u",
1932 this, GetLockFlagNameStr(mLock).get(),
1933 GetBoolName(mComposition.IsComposing()), mHandlingKeyMessage));
1935 mDestroyed = true;
1937 // Destroy native caret first because it's not directly related to TSF and
1938 // there may be another textstore which gets focus. So, we should avoid
1939 // to destroy caret after the new one recreates caret.
1940 IMEHandler::MaybeDestroyNativeCaret();
1942 if (mLock) {
1943 mPendingDestroy = true;
1944 return;
1947 AutoRestore<bool> savedBeingDestroyed(mBeingDestroyed);
1948 mBeingDestroyed = true;
1950 // If there is composition, TSF keeps the composition even after the text
1951 // store destroyed. So, we should clear the composition here.
1952 if (mComposition.IsComposing()) {
1953 CommitCompositionInternal(false);
1956 if (mSink) {
1957 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
1958 ("0x%p TSFTextStore::Destroy(), calling "
1959 "ITextStoreACPSink::OnLayoutChange(TS_LC_DESTROY)...",
1960 this));
1961 RefPtr<ITextStoreACPSink> sink = mSink;
1962 sink->OnLayoutChange(TS_LC_DESTROY, TEXTSTORE_DEFAULT_VIEW);
1965 // If this is called during handling a keydown or keyup message, we should
1966 // put off to release TSF objects until it completely finishes since
1967 // MS-IME for Japanese refers some objects without grabbing them.
1968 if (!mHandlingKeyMessage) {
1969 ReleaseTSFObjects();
1972 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1973 ("0x%p TSFTextStore::Destroy() succeeded", this));
1976 void TSFTextStore::ReleaseTSFObjects() {
1977 MOZ_ASSERT(!mHandlingKeyMessage);
1979 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1980 ("0x%p TSFTextStore::ReleaseTSFObjects()", this));
1982 mContext = nullptr;
1983 if (mDocumentMgr) {
1984 RefPtr<ITfDocumentMgr> documentMgr = mDocumentMgr.forget();
1985 documentMgr->Pop(TF_POPF_ALL);
1987 mSink = nullptr;
1988 mWidget = nullptr;
1989 mDispatcher = nullptr;
1991 if (!mMouseTrackers.IsEmpty()) {
1992 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
1993 ("0x%p TSFTextStore::ReleaseTSFObjects(), "
1994 "removing a mouse tracker...",
1995 this));
1996 mMouseTrackers.Clear();
1999 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2000 ("0x%p TSFTextStore::ReleaseTSFObjects() completed", this));
2003 STDMETHODIMP
2004 TSFTextStore::QueryInterface(REFIID riid, void** ppv) {
2005 *ppv = nullptr;
2006 if ((IID_IUnknown == riid) || (IID_ITextStoreACP == riid)) {
2007 *ppv = static_cast<ITextStoreACP*>(this);
2008 } else if (IID_ITfContextOwnerCompositionSink == riid) {
2009 *ppv = static_cast<ITfContextOwnerCompositionSink*>(this);
2010 } else if (IID_ITfMouseTrackerACP == riid) {
2011 *ppv = static_cast<ITfMouseTrackerACP*>(this);
2013 if (*ppv) {
2014 AddRef();
2015 return S_OK;
2018 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2019 ("0x%p TSFTextStore::QueryInterface() FAILED, riid=%s", this,
2020 GetRIIDNameStr(riid).get()));
2021 return E_NOINTERFACE;
2024 STDMETHODIMP
2025 TSFTextStore::AdviseSink(REFIID riid, IUnknown* punk, DWORD dwMask) {
2026 MOZ_LOG(
2027 sTextStoreLog, LogLevel::Info,
2028 ("0x%p TSFTextStore::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), "
2029 "mSink=0x%p, mSinkMask=%s",
2030 this, GetRIIDNameStr(riid).get(), punk, GetSinkMaskNameStr(dwMask).get(),
2031 mSink.get(), GetSinkMaskNameStr(mSinkMask).get()));
2033 if (!punk) {
2034 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2035 ("0x%p TSFTextStore::AdviseSink() FAILED due to the null punk",
2036 this));
2037 return E_UNEXPECTED;
2040 if (IID_ITextStoreACPSink != riid) {
2041 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2042 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2043 "unsupported interface",
2044 this));
2045 return E_INVALIDARG; // means unsupported interface.
2048 if (!mSink) {
2049 // Install sink
2050 punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink));
2051 if (!mSink) {
2052 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2053 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2054 "punk not having the interface",
2055 this));
2056 return E_UNEXPECTED;
2058 } else {
2059 // If sink is already installed we check to see if they are the same
2060 // Get IUnknown from both sides for comparison
2061 RefPtr<IUnknown> comparison1, comparison2;
2062 punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
2063 mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
2064 if (comparison1 != comparison2) {
2065 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2066 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2067 "the sink being different from the stored sink",
2068 this));
2069 return CONNECT_E_ADVISELIMIT;
2072 // Update mask either for a new sink or an existing sink
2073 mSinkMask = dwMask;
2074 return S_OK;
2077 STDMETHODIMP
2078 TSFTextStore::UnadviseSink(IUnknown* punk) {
2079 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2080 ("0x%p TSFTextStore::UnadviseSink(punk=0x%p), mSink=0x%p", this, punk,
2081 mSink.get()));
2083 if (!punk) {
2084 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2085 ("0x%p TSFTextStore::UnadviseSink() FAILED due to the null punk",
2086 this));
2087 return E_INVALIDARG;
2089 if (!mSink) {
2090 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2091 ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
2092 "any sink not stored",
2093 this));
2094 return CONNECT_E_NOCONNECTION;
2096 // Get IUnknown from both sides for comparison
2097 RefPtr<IUnknown> comparison1, comparison2;
2098 punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
2099 mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
2100 // Unadvise only if sinks are the same
2101 if (comparison1 != comparison2) {
2102 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2103 ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
2104 "the sink being different from the stored sink",
2105 this));
2106 return CONNECT_E_NOCONNECTION;
2108 mSink = nullptr;
2109 mSinkMask = 0;
2110 return S_OK;
2113 STDMETHODIMP
2114 TSFTextStore::RequestLock(DWORD dwLockFlags, HRESULT* phrSession) {
2115 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2116 ("0x%p TSFTextStore::RequestLock(dwLockFlags=%s, phrSession=0x%p), "
2117 "mLock=%s, mDestroyed=%s",
2118 this, GetLockFlagNameStr(dwLockFlags).get(), phrSession,
2119 GetLockFlagNameStr(mLock).get(), GetBoolName(mDestroyed)));
2121 if (!mSink) {
2122 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2123 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2124 "any sink not stored",
2125 this));
2126 return E_FAIL;
2128 if (mDestroyed &&
2129 (!mContentForTSF.IsInitialized() || mSelectionForTSF.IsDirty())) {
2130 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2131 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2132 "being destroyed and no information of the contents",
2133 this));
2134 return E_FAIL;
2136 if (!phrSession) {
2137 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2138 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2139 "null phrSession",
2140 this));
2141 return E_INVALIDARG;
2144 if (!mLock) {
2145 // put on lock
2146 mLock = dwLockFlags & (~TS_LF_SYNC);
2147 MOZ_LOG(
2148 sTextStoreLog, LogLevel::Info,
2149 ("0x%p Locking (%s) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
2150 ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
2151 this, GetLockFlagNameStr(mLock).get()));
2152 // Don't release this instance during this lock because this is called by
2153 // TSF but they don't grab us during this call.
2154 RefPtr<TSFTextStore> kungFuDeathGrip(this);
2155 RefPtr<ITextStoreACPSink> sink = mSink;
2156 *phrSession = sink->OnLockGranted(mLock);
2157 MOZ_LOG(
2158 sTextStoreLog, LogLevel::Info,
2159 ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2160 "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
2161 this, GetLockFlagNameStr(mLock).get()));
2162 DidLockGranted();
2163 while (mLockQueued) {
2164 mLock = mLockQueued;
2165 mLockQueued = 0;
2166 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2167 ("0x%p Locking for the request in the queue (%s) >>>>>>>>>>>>>>"
2168 ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
2169 ">>>>>",
2170 this, GetLockFlagNameStr(mLock).get()));
2171 sink->OnLockGranted(mLock);
2172 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2173 ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2174 "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2175 "<<<<<",
2176 this, GetLockFlagNameStr(mLock).get()));
2177 DidLockGranted();
2180 // The document is now completely unlocked.
2181 mLock = 0;
2183 MaybeFlushPendingNotifications();
2185 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2186 ("0x%p TSFTextStore::RequestLock() succeeded: *phrSession=%s",
2187 this, GetTextStoreReturnValueName(*phrSession)));
2188 return S_OK;
2191 // only time when reentrant lock is allowed is when caller holds a
2192 // read-only lock and is requesting an async write lock
2193 if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags) &&
2194 !(dwLockFlags & TS_LF_SYNC)) {
2195 *phrSession = TS_S_ASYNC;
2196 mLockQueued = dwLockFlags & (~TS_LF_SYNC);
2198 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2199 ("0x%p TSFTextStore::RequestLock() stores the request in the "
2200 "queue, *phrSession=TS_S_ASYNC",
2201 this));
2202 return S_OK;
2205 // no more locks allowed
2206 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2207 ("0x%p TSFTextStore::RequestLock() didn't allow to lock, "
2208 "*phrSession=TS_E_SYNCHRONOUS",
2209 this));
2210 *phrSession = TS_E_SYNCHRONOUS;
2211 return E_FAIL;
2214 void TSFTextStore::DidLockGranted() {
2215 if (IsReadWriteLocked()) {
2216 // FreeCJ (TIP for Traditional Chinese) calls SetSelection() to set caret
2217 // to the start of composition string and insert a full width space for
2218 // a placeholder with a call of SetText(). After that, it calls
2219 // OnUpdateComposition() without new range. Therefore, let's record the
2220 // composition update information here.
2221 CompleteLastActionIfStillIncomplete();
2223 FlushPendingActions();
2226 // If the widget has gone, we don't need to notify anything.
2227 if (mDestroyed || !mWidget || mWidget->Destroyed()) {
2228 mPendingSelectionChangeData.Clear();
2229 mHasReturnedNoLayoutError = false;
2233 void TSFTextStore::DispatchEvent(WidgetGUIEvent& aEvent) {
2234 if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) {
2235 return;
2237 // If the event isn't a query content event, the event may be handled
2238 // asynchronously. So, we should put off to answer from GetTextExt() etc.
2239 if (!aEvent.AsQueryContentEvent()) {
2240 mDeferNotifyingTSF = true;
2242 mWidget->DispatchWindowEvent(&aEvent);
2245 void TSFTextStore::FlushPendingActions() {
2246 if (!mWidget || mWidget->Destroyed()) {
2247 // Note that don't clear mContentForTSF because TIP may try to commit
2248 // composition with a document lock. In such case, TSFTextStore needs to
2249 // behave as expected by TIP.
2250 mPendingActions.Clear();
2251 mPendingSelectionChangeData.Clear();
2252 mHasReturnedNoLayoutError = false;
2253 return;
2256 // Some TIP may request lock but does nothing during the lock. In such case,
2257 // this should do nothing. For example, when MS-IME for Japanese is active
2258 // and we're inactivating, this case occurs and causes different behavior
2259 // from the other TIPs.
2260 if (mPendingActions.IsEmpty()) {
2261 return;
2264 RefPtr<nsWindowBase> widget(mWidget);
2265 nsresult rv = mDispatcher->BeginNativeInputTransaction();
2266 if (NS_WARN_IF(NS_FAILED(rv))) {
2267 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2268 ("0x%p TSFTextStore::FlushPendingActions() "
2269 "FAILED due to BeginNativeInputTransaction() failure",
2270 this));
2271 return;
2273 for (uint32_t i = 0; i < mPendingActions.Length(); i++) {
2274 PendingAction& action = mPendingActions[i];
2275 switch (action.mType) {
2276 case PendingAction::Type::eKeyboardEvent:
2277 if (mDestroyed) {
2278 MOZ_LOG(sTextStoreLog, LogLevel::Warning,
2279 ("0x%p TSFTextStore::FlushPendingActions() "
2280 "IGNORED pending KeyboardEvent(%s) due to already destroyed",
2281 action.mKeyMsg.message == WM_KEYDOWN ? "eKeyDown" : "eKeyUp",
2282 this));
2284 MOZ_DIAGNOSTIC_ASSERT(action.mKeyMsg.message == WM_KEYDOWN ||
2285 action.mKeyMsg.message == WM_KEYUP);
2286 DispatchKeyboardEventAsProcessedByIME(action.mKeyMsg);
2287 if (!widget || widget->Destroyed()) {
2288 break;
2290 break;
2291 case PendingAction::Type::eCompositionStart: {
2292 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2293 ("0x%p TSFTextStore::FlushPendingActions() "
2294 "flushing Type::eCompositionStart={ mSelectionStart=%d, "
2295 "mSelectionLength=%d }, mDestroyed=%s",
2296 this, action.mSelectionStart, action.mSelectionLength,
2297 GetBoolName(mDestroyed)));
2299 if (mDestroyed) {
2300 MOZ_LOG(sTextStoreLog, LogLevel::Warning,
2301 ("0x%p TSFTextStore::FlushPendingActions() "
2302 "IGNORED pending compositionstart due to already destroyed",
2303 this));
2304 break;
2307 if (action.mAdjustSelection) {
2308 // Select composition range so the new composition replaces the range
2309 WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
2310 widget->InitEvent(selectionSet);
2311 selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
2312 selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
2313 selectionSet.mReversed = false;
2314 DispatchEvent(selectionSet);
2315 if (!selectionSet.mSucceeded) {
2316 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2317 ("0x%p TSFTextStore::FlushPendingActions() "
2318 "FAILED due to eSetSelection failure",
2319 this));
2320 break;
2324 // eCompositionStart always causes
2325 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. Therefore, we should
2326 // wait to clear mContentForTSF until it's notified.
2327 mDeferClearingContentForTSF = true;
2329 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2330 ("0x%p TSFTextStore::FlushPendingActions() "
2331 "dispatching compositionstart event...",
2332 this));
2333 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2334 nsEventStatus status;
2335 rv = mDispatcher->StartComposition(status, &eventTime);
2336 if (NS_WARN_IF(NS_FAILED(rv))) {
2337 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2338 ("0x%p TSFTextStore::FlushPendingActions() "
2339 "FAILED to dispatch compositionstart event, "
2340 "IsHandlingComposition()=%s",
2341 this, GetBoolName(IsHandlingComposition())));
2342 // XXX Is this right? If there is a composition in content,
2343 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2344 mDeferClearingContentForTSF = !IsHandlingComposition();
2346 if (!widget || widget->Destroyed()) {
2347 break;
2349 break;
2351 case PendingAction::Type::eCompositionUpdate: {
2352 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2353 ("0x%p TSFTextStore::FlushPendingActions() "
2354 "flushing Type::eCompositionUpdate={ mData=\"%s\", "
2355 "mRanges=0x%p, mRanges->Length()=%d }",
2356 this, GetEscapedUTF8String(action.mData).get(),
2357 action.mRanges.get(),
2358 action.mRanges ? action.mRanges->Length() : 0));
2360 // eCompositionChange causes a DOM text event, the IME will be notified
2361 // of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. In this case, we
2362 // should not clear mContentForTSF until we notify the IME of the
2363 // composition update.
2364 mDeferClearingContentForTSF = true;
2366 rv = mDispatcher->SetPendingComposition(action.mData, action.mRanges);
2367 if (NS_WARN_IF(NS_FAILED(rv))) {
2368 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2369 ("0x%p TSFTextStore::FlushPendingActions() "
2370 "FAILED to setting pending composition... "
2371 "IsHandlingComposition()=%s",
2372 this, GetBoolName(IsHandlingComposition())));
2373 // XXX Is this right? If there is a composition in content,
2374 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2375 mDeferClearingContentForTSF = !IsHandlingComposition();
2376 } else {
2377 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2378 ("0x%p TSFTextStore::FlushPendingActions() "
2379 "dispatching compositionchange event...",
2380 this));
2381 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2382 nsEventStatus status;
2383 rv = mDispatcher->FlushPendingComposition(status, &eventTime);
2384 if (NS_WARN_IF(NS_FAILED(rv))) {
2385 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2386 ("0x%p TSFTextStore::FlushPendingActions() "
2387 "FAILED to dispatch compositionchange event, "
2388 "IsHandlingComposition()=%s",
2389 this, GetBoolName(IsHandlingComposition())));
2390 // XXX Is this right? If there is a composition in content,
2391 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2392 mDeferClearingContentForTSF = !IsHandlingComposition();
2394 // Be aware, the mWidget might already have been destroyed.
2396 break;
2398 case PendingAction::Type::eCompositionEnd: {
2399 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2400 ("0x%p TSFTextStore::FlushPendingActions() "
2401 "flushing Type::eCompositionEnd={ mData=\"%s\" }",
2402 this, GetEscapedUTF8String(action.mData).get()));
2404 // Dispatching eCompositionCommit causes a DOM text event, then,
2405 // the IME will be notified of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
2406 // when focused content actually handles the event. For example,
2407 // when focused content is in a remote process, it's sent when
2408 // all dispatched composition events have been handled in the remote
2409 // process. So, until then, we don't have newer content information.
2410 // Therefore, we need to put off to clear mContentForTSF.
2411 mDeferClearingContentForTSF = true;
2413 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2414 ("0x%p TSFTextStore::FlushPendingActions(), "
2415 "dispatching compositioncommit event...",
2416 this));
2417 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2418 nsEventStatus status;
2419 rv = mDispatcher->CommitComposition(status, &action.mData, &eventTime);
2420 if (NS_WARN_IF(NS_FAILED(rv))) {
2421 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2422 ("0x%p TSFTextStore::FlushPendingActions() "
2423 "FAILED to dispatch compositioncommit event, "
2424 "IsHandlingComposition()=%s",
2425 this, GetBoolName(IsHandlingComposition())));
2426 // XXX Is this right? If there is a composition in content,
2427 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2428 mDeferClearingContentForTSF = !IsHandlingComposition();
2430 break;
2432 case PendingAction::Type::eSetSelection: {
2433 MOZ_LOG(
2434 sTextStoreLog, LogLevel::Debug,
2435 ("0x%p TSFTextStore::FlushPendingActions() "
2436 "flushing Type::eSetSelection={ mSelectionStart=%d, "
2437 "mSelectionLength=%d, mSelectionReversed=%s }, "
2438 "mDestroyed=%s",
2439 this, action.mSelectionStart, action.mSelectionLength,
2440 GetBoolName(action.mSelectionReversed), GetBoolName(mDestroyed)));
2442 if (mDestroyed) {
2443 MOZ_LOG(sTextStoreLog, LogLevel::Warning,
2444 ("0x%p TSFTextStore::FlushPendingActions() "
2445 "IGNORED pending selectionset due to already destroyed",
2446 this));
2447 break;
2450 WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
2451 selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
2452 selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
2453 selectionSet.mReversed = action.mSelectionReversed;
2454 DispatchEvent(selectionSet);
2455 if (!selectionSet.mSucceeded) {
2456 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2457 ("0x%p TSFTextStore::FlushPendingActions() "
2458 "FAILED due to eSetSelection failure",
2459 this));
2460 break;
2462 break;
2464 default:
2465 MOZ_CRASH("unexpected action type");
2468 if (widget && !widget->Destroyed()) {
2469 continue;
2472 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2473 ("0x%p TSFTextStore::FlushPendingActions(), "
2474 "qutting since the mWidget has gone",
2475 this));
2476 break;
2478 mPendingActions.Clear();
2481 void TSFTextStore::MaybeFlushPendingNotifications() {
2482 if (IsReadLocked()) {
2483 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2484 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2485 "putting off flushing pending notifications due to being the "
2486 "document locked...",
2487 this));
2488 return;
2491 if (mDeferCommittingComposition) {
2492 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2493 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2494 "calling TSFTextStore::CommitCompositionInternal(false)...",
2495 this));
2496 mDeferCommittingComposition = mDeferCancellingComposition = false;
2497 CommitCompositionInternal(false);
2498 } else if (mDeferCancellingComposition) {
2499 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2500 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2501 "calling TSFTextStore::CommitCompositionInternal(true)...",
2502 this));
2503 mDeferCommittingComposition = mDeferCancellingComposition = false;
2504 CommitCompositionInternal(true);
2507 if (mDeferNotifyingTSF) {
2508 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2509 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2510 "putting off flushing pending notifications due to being "
2511 "dispatching events...",
2512 this));
2513 return;
2516 if (mPendingDestroy) {
2517 Destroy();
2518 return;
2521 if (mDestroyed) {
2522 // If it's already been destroyed completely, this shouldn't notify TSF of
2523 // anything anymore.
2524 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2525 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2526 "does nothing because this has already destroyed completely...",
2527 this));
2528 return;
2531 if (!mDeferClearingContentForTSF && mContentForTSF.IsInitialized()) {
2532 mContentForTSF.Clear();
2533 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2534 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2535 "mContentForTSF is cleared",
2536 this));
2539 // When there is no cached content, we can sync actual contents and TSF/TIP
2540 // expecting contents.
2541 RefPtr<TSFTextStore> kungFuDeathGrip = this;
2542 Unused << kungFuDeathGrip;
2543 if (!mContentForTSF.IsInitialized()) {
2544 if (mPendingTextChangeData.IsValid()) {
2545 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2546 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2547 "calling TSFTextStore::NotifyTSFOfTextChange()...",
2548 this));
2549 NotifyTSFOfTextChange();
2551 if (mPendingSelectionChangeData.IsValid()) {
2552 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2553 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2554 "calling TSFTextStore::NotifyTSFOfSelectionChange()...",
2555 this));
2556 NotifyTSFOfSelectionChange();
2560 if (mHasReturnedNoLayoutError) {
2561 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2562 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2563 "calling TSFTextStore::NotifyTSFOfLayoutChange()...",
2564 this));
2565 NotifyTSFOfLayoutChange();
2569 void TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME() {
2570 // If we've already been destroyed, we cannot do anything.
2571 if (mDestroyed) {
2572 MOZ_LOG(
2573 sTextStoreLog, LogLevel::Debug,
2574 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2575 "does nothing because it's already been destroyed",
2576 this));
2577 return;
2580 // If we're not handling key message or we've already dispatched a keyboard
2581 // event for the handling key message, we should do nothing anymore.
2582 if (!sHandlingKeyMsg || sIsKeyboardEventDispatched) {
2583 MOZ_LOG(
2584 sTextStoreLog, LogLevel::Debug,
2585 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2586 "does nothing because not necessary to dispatch keyboard event",
2587 this));
2588 return;
2591 sIsKeyboardEventDispatched = true;
2592 // If the document is locked, just adding the task to dispatching an event
2593 // to the queue.
2594 if (IsReadLocked()) {
2595 MOZ_LOG(
2596 sTextStoreLog, LogLevel::Debug,
2597 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2598 "adding to dispatch a keyboard event into the queue...",
2599 this));
2600 PendingAction* action = mPendingActions.AppendElement();
2601 action->mType = PendingAction::Type::eKeyboardEvent;
2602 memcpy(&action->mKeyMsg, sHandlingKeyMsg, sizeof(MSG));
2603 return;
2606 // Otherwise, dispatch a keyboard event.
2607 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2608 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2609 "trying to dispatch a keyboard event...",
2610 this));
2611 DispatchKeyboardEventAsProcessedByIME(*sHandlingKeyMsg);
2614 void TSFTextStore::DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg) {
2615 MOZ_ASSERT(mWidget);
2616 MOZ_ASSERT(!mWidget->Destroyed());
2617 MOZ_ASSERT(!mDestroyed);
2619 ModifierKeyState modKeyState;
2620 MSG msg(aMsg);
2621 msg.wParam = VK_PROCESSKEY;
2622 NativeKey nativeKey(mWidget, msg, modKeyState);
2623 switch (aMsg.message) {
2624 case WM_KEYDOWN:
2625 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2626 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2627 "dispatching an eKeyDown event...",
2628 this));
2629 nativeKey.HandleKeyDownMessage();
2630 break;
2631 case WM_KEYUP:
2632 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2633 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2634 "dispatching an eKeyUp event...",
2635 this));
2636 nativeKey.HandleKeyUpMessage();
2637 break;
2638 default:
2639 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2640 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2641 "ERROR, it doesn't handle the message",
2642 this));
2643 break;
2647 STDMETHODIMP
2648 TSFTextStore::GetStatus(TS_STATUS* pdcs) {
2649 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2650 ("0x%p TSFTextStore::GetStatus(pdcs=0x%p)", this, pdcs));
2652 if (!pdcs) {
2653 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2654 ("0x%p TSFTextStore::GetStatus() FAILED due to null pdcs", this));
2655 return E_INVALIDARG;
2657 // We manage on-screen keyboard by own.
2658 pdcs->dwDynamicFlags = TS_SD_INPUTPANEMANUALDISPLAYENABLE;
2659 // we use a "flat" text model for TSF support so no hidden text
2660 pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT;
2661 return S_OK;
2664 STDMETHODIMP
2665 TSFTextStore::QueryInsert(LONG acpTestStart, LONG acpTestEnd, ULONG cch,
2666 LONG* pacpResultStart, LONG* pacpResultEnd) {
2667 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2668 ("0x%p TSFTextStore::QueryInsert(acpTestStart=%ld, "
2669 "acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)",
2670 this, acpTestStart, acpTestEnd, cch, acpTestStart, acpTestEnd));
2672 if (!pacpResultStart || !pacpResultEnd) {
2673 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2674 ("0x%p TSFTextStore::QueryInsert() FAILED due to "
2675 "the null argument",
2676 this));
2677 return E_INVALIDARG;
2680 if (acpTestStart < 0 || acpTestStart > acpTestEnd) {
2681 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2682 ("0x%p TSFTextStore::QueryInsert() FAILED due to "
2683 "wrong argument",
2684 this));
2685 return E_INVALIDARG;
2688 // XXX need to adjust to cluster boundary
2689 // Assume we are given good offsets for now
2690 if (IsWin8OrLater() && !mComposition.IsComposing() &&
2691 ((TSFPrefs::NeedToHackQueryInsertForMSTraditionalTIP() &&
2692 TSFStaticSink::IsMSChangJieOrMSQuickActive()) ||
2693 (TSFPrefs::NeedToHackQueryInsertForMSSimplifiedTIP() &&
2694 TSFStaticSink::IsMSPinyinOrMSWubiActive()))) {
2695 MOZ_LOG(sTextStoreLog, LogLevel::Warning,
2696 ("0x%p TSFTextStore::QueryInsert() WARNING using different "
2697 "result for the TIP",
2698 this));
2699 // Chinese TIPs of Microsoft assume that QueryInsert() returns selected
2700 // range which should be removed.
2701 *pacpResultStart = acpTestStart;
2702 *pacpResultEnd = acpTestEnd;
2703 } else {
2704 *pacpResultStart = acpTestStart;
2705 *pacpResultEnd = acpTestStart + cch;
2708 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2709 ("0x%p TSFTextStore::QueryInsert() succeeded: "
2710 "*pacpResultStart=%ld, *pacpResultEnd=%ld)",
2711 this, *pacpResultStart, *pacpResultEnd));
2712 return S_OK;
2715 STDMETHODIMP
2716 TSFTextStore::GetSelection(ULONG ulIndex, ULONG ulCount,
2717 TS_SELECTION_ACP* pSelection, ULONG* pcFetched) {
2718 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2719 ("0x%p TSFTextStore::GetSelection(ulIndex=%lu, ulCount=%lu, "
2720 "pSelection=0x%p, pcFetched=0x%p)",
2721 this, ulIndex, ulCount, pSelection, pcFetched));
2723 if (!IsReadLocked()) {
2724 MOZ_LOG(
2725 sTextStoreLog, LogLevel::Error,
2726 ("0x%p TSFTextStore::GetSelection() FAILED due to not locked", this));
2727 return TS_E_NOLOCK;
2729 if (!ulCount || !pSelection || !pcFetched) {
2730 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2731 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2732 "null argument",
2733 this));
2734 return E_INVALIDARG;
2737 *pcFetched = 0;
2739 if (ulIndex != static_cast<ULONG>(TS_DEFAULT_SELECTION) && ulIndex != 0) {
2740 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2741 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2742 "unsupported selection",
2743 this));
2744 return TS_E_NOSELECTION;
2747 Selection& selectionForTSF = SelectionForTSFRef();
2748 if (selectionForTSF.IsDirty()) {
2749 if (DoNotReturnErrorFromGetSelection()) {
2750 AutoSetTemporarySelection temprarySetter(selectionForTSF);
2751 *pSelection = selectionForTSF.ACP();
2752 *pcFetched = 1;
2753 MOZ_LOG(
2754 sTextStoreLog, LogLevel::Info,
2755 ("0x%p TSFTextStore::GetSelection() returns fake selection range "
2756 "for avoiding a crash in TSF, "
2757 "acpStart=%d, acpEnd=%d (length=%d), reverted=%s",
2758 this, selectionForTSF.StartOffset(), selectionForTSF.EndOffset(),
2759 selectionForTSF.Length(),
2760 GetBoolName(selectionForTSF.IsReversed())));
2761 return S_OK;
2763 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2764 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2765 "SelectionForTSFRef() failure",
2766 this));
2767 return E_FAIL;
2769 *pSelection = selectionForTSF.ACP();
2770 *pcFetched = 1;
2771 MOZ_LOG(
2772 sTextStoreLog, LogLevel::Info,
2773 ("0x%p TSFTextStore::GetSelection() succeeded, "
2774 "acpStart=%d, acpEnd=%d (length=%d), reverted=%s",
2775 this, selectionForTSF.StartOffset(), selectionForTSF.EndOffset(),
2776 selectionForTSF.Length(), GetBoolName(selectionForTSF.IsReversed())));
2777 return S_OK;
2780 // static
2781 bool TSFTextStore::DoNotReturnErrorFromGetSelection() {
2782 // There is a crash bug of TSF if we return error from GetSelection().
2783 // That was introduced in Anniversary Update (build 14393, see bug 1312302)
2784 // TODO: We should avoid to run this hack on fixed builds. When we get
2785 // exact build number, we should get back here.
2786 static bool sTSFMayCrashIfGetSelectionReturnsError =
2787 IsWindows10BuildOrLater(14393);
2788 return sTSFMayCrashIfGetSelectionReturnsError;
2791 TSFTextStore::Content& TSFTextStore::ContentForTSFRef() {
2792 // This should be called when the document is locked or the content hasn't
2793 // been abandoned yet.
2794 if (NS_WARN_IF(!IsReadLocked() && !mContentForTSF.IsInitialized())) {
2795 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2796 ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to "
2797 "called wrong timing, IsReadLocked()=%s, "
2798 "mContentForTSF.IsInitialized()=%s",
2799 this, GetBoolName(IsReadLocked()),
2800 GetBoolName(mContentForTSF.IsInitialized())));
2801 mContentForTSF.Clear();
2802 return mContentForTSF;
2805 Selection& selectionForTSF = SelectionForTSFRef();
2806 if (selectionForTSF.IsDirty()) {
2807 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2808 ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to "
2809 "SelectionForTSFRef() failure",
2810 this));
2811 mContentForTSF.Clear();
2812 return mContentForTSF;
2815 if (!mContentForTSF.IsInitialized()) {
2816 nsAutoString text;
2817 if (NS_WARN_IF(!GetCurrentText(text))) {
2818 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2819 ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to "
2820 "GetCurrentText() failure",
2821 this));
2822 mContentForTSF.Clear();
2823 return mContentForTSF;
2826 mContentForTSF.Init(text);
2827 // Basically, the cached content which is expected by TSF/TIP should be
2828 // cleared after active composition is committed or the document lock is
2829 // unlocked. However, in e10s mode, content will be modified
2830 // asynchronously. In such case, mDeferClearingContentForTSF may be
2831 // true until whole dispatched events are handled by the focused editor.
2832 mDeferClearingContentForTSF = false;
2835 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2836 ("0x%p TSFTextStore::ContentForTSFRef(): "
2837 "mContentForTSF={ mText=\"%s\" (Length()=%u), "
2838 "mLastCompositionString=\"%s\" (Length()=%u), "
2839 "mMinTextModifiedOffset=%u }",
2840 this,
2841 mContentForTSF.Text().Length() <= 40
2842 ? GetEscapedUTF8String(mContentForTSF.Text()).get()
2843 : "<omitted>",
2844 mContentForTSF.Text().Length(),
2845 GetEscapedUTF8String(mContentForTSF.LastCompositionString()).get(),
2846 mContentForTSF.LastCompositionString().Length(),
2847 mContentForTSF.MinTextModifiedOffset()));
2849 return mContentForTSF;
2852 bool TSFTextStore::CanAccessActualContentDirectly() const {
2853 if (!mContentForTSF.IsInitialized() || mSelectionForTSF.IsDirty()) {
2854 return true;
2857 // If the cached content has been changed by something except composition,
2858 // the content cache may be different from actual content.
2859 if (mPendingTextChangeData.IsValid() &&
2860 !mPendingTextChangeData.mCausedOnlyByComposition) {
2861 return false;
2864 // If the cached selection isn't changed, cached content and actual content
2865 // should be same.
2866 if (!mPendingSelectionChangeData.IsValid()) {
2867 return true;
2870 return mSelectionForTSF.EqualsExceptDirection(mPendingSelectionChangeData);
2873 bool TSFTextStore::GetCurrentText(nsAString& aTextContent) {
2874 if (mContentForTSF.IsInitialized()) {
2875 aTextContent = mContentForTSF.Text();
2876 return true;
2879 MOZ_ASSERT(!mDestroyed);
2880 MOZ_ASSERT(mWidget && !mWidget->Destroyed());
2882 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2883 ("0x%p TSFTextStore::GetCurrentText(): "
2884 "retrieving text from the content...",
2885 this));
2887 WidgetQueryContentEvent queryText(true, eQueryTextContent, mWidget);
2888 queryText.InitForQueryTextContent(0, UINT32_MAX);
2889 mWidget->InitEvent(queryText);
2890 DispatchEvent(queryText);
2891 if (NS_WARN_IF(!queryText.mSucceeded)) {
2892 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2893 ("0x%p TSFTextStore::GetCurrentText(), FAILED, due to "
2894 "eQueryTextContent failure",
2895 this));
2896 aTextContent.Truncate();
2897 return false;
2900 aTextContent = queryText.mReply.mString;
2901 return true;
2904 TSFTextStore::Selection& TSFTextStore::SelectionForTSFRef() {
2905 if (mSelectionForTSF.IsDirty()) {
2906 MOZ_ASSERT(!mDestroyed);
2907 // If the window has never been available, we should crash since working
2908 // with broken values may make TIP confused.
2909 if (!mWidget || mWidget->Destroyed()) {
2910 MOZ_CRASH();
2913 WidgetQueryContentEvent querySelection(true, eQuerySelectedText, mWidget);
2914 mWidget->InitEvent(querySelection);
2915 DispatchEvent(querySelection);
2916 if (NS_WARN_IF(!querySelection.mSucceeded)) {
2917 return mSelectionForTSF;
2920 mSelectionForTSF.SetSelection(
2921 querySelection.mReply.mOffset, querySelection.mReply.mString.Length(),
2922 querySelection.mReply.mReversed, querySelection.GetWritingMode());
2925 MOZ_LOG(
2926 sTextStoreLog, LogLevel::Debug,
2927 ("0x%p TSFTextStore::SelectionForTSFRef(): "
2928 "acpStart=%d, acpEnd=%d (length=%d), reverted=%s",
2929 this, mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
2930 mSelectionForTSF.Length(), GetBoolName(mSelectionForTSF.IsReversed())));
2932 return mSelectionForTSF;
2935 static HRESULT GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength) {
2936 RefPtr<ITfRangeACP> rangeACP;
2937 aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP));
2938 NS_ENSURE_TRUE(rangeACP, E_FAIL);
2939 return rangeACP->GetExtent(aStart, aLength);
2942 static TextRangeType GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE& aDisplayAttr) {
2943 switch (aDisplayAttr.bAttr) {
2944 case TF_ATTR_TARGET_CONVERTED:
2945 return TextRangeType::eSelectedClause;
2946 case TF_ATTR_CONVERTED:
2947 return TextRangeType::eConvertedClause;
2948 case TF_ATTR_TARGET_NOTCONVERTED:
2949 return TextRangeType::eSelectedRawClause;
2950 default:
2951 return TextRangeType::eRawClause;
2955 HRESULT
2956 TSFTextStore::GetDisplayAttribute(ITfProperty* aAttrProperty, ITfRange* aRange,
2957 TF_DISPLAYATTRIBUTE* aResult) {
2958 NS_ENSURE_TRUE(aAttrProperty, E_FAIL);
2959 NS_ENSURE_TRUE(aRange, E_FAIL);
2960 NS_ENSURE_TRUE(aResult, E_FAIL);
2962 HRESULT hr;
2964 if (MOZ_LOG_TEST(sTextStoreLog, LogLevel::Debug)) {
2965 LONG start = 0, length = 0;
2966 hr = GetRangeExtent(aRange, &start, &length);
2967 MOZ_LOG(
2968 sTextStoreLog, LogLevel::Debug,
2969 ("0x%p TSFTextStore::GetDisplayAttribute(): "
2970 "GetDisplayAttribute range=%ld-%ld (hr=%s)",
2971 this, start - mComposition.mStart,
2972 start - mComposition.mStart + length, GetCommonReturnValueName(hr)));
2975 VARIANT propValue;
2976 ::VariantInit(&propValue);
2977 hr = aAttrProperty->GetValue(TfEditCookie(mEditCookie), aRange, &propValue);
2978 if (FAILED(hr)) {
2979 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2980 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
2981 "ITfProperty::GetValue() failed",
2982 this));
2983 return hr;
2985 if (VT_I4 != propValue.vt) {
2986 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2987 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
2988 "ITfProperty::GetValue() returns non-VT_I4 value",
2989 this));
2990 ::VariantClear(&propValue);
2991 return E_FAIL;
2994 RefPtr<ITfCategoryMgr> categoryMgr = GetCategoryMgr();
2995 if (NS_WARN_IF(!categoryMgr)) {
2996 return E_FAIL;
2998 GUID guid;
2999 hr = categoryMgr->GetGUID(DWORD(propValue.lVal), &guid);
3000 ::VariantClear(&propValue);
3001 if (FAILED(hr)) {
3002 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3003 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3004 "ITfCategoryMgr::GetGUID() failed",
3005 this));
3006 return hr;
3009 RefPtr<ITfDisplayAttributeMgr> displayAttrMgr = GetDisplayAttributeMgr();
3010 if (NS_WARN_IF(!displayAttrMgr)) {
3011 return E_FAIL;
3013 RefPtr<ITfDisplayAttributeInfo> info;
3014 hr = displayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info),
3015 nullptr);
3016 if (FAILED(hr) || !info) {
3017 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3018 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3019 "ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed",
3020 this));
3021 return hr;
3024 hr = info->GetAttributeInfo(aResult);
3025 if (FAILED(hr)) {
3026 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3027 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3028 "ITfDisplayAttributeInfo::GetAttributeInfo() failed",
3029 this));
3030 return hr;
3033 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3034 ("0x%p TSFTextStore::GetDisplayAttribute() succeeded: "
3035 "Result={ %s }",
3036 this, GetDisplayAttrStr(*aResult).get()));
3037 return S_OK;
3040 HRESULT
3041 TSFTextStore::RestartCompositionIfNecessary(ITfRange* aRangeNew) {
3042 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3043 ("0x%p TSFTextStore::RestartCompositionIfNecessary("
3044 "aRangeNew=0x%p), mComposition.mView=0x%p",
3045 this, aRangeNew, mComposition.mView.get()));
3047 if (!mComposition.IsComposing()) {
3048 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3049 ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
3050 "due to no composition view",
3051 this));
3052 return E_FAIL;
3055 HRESULT hr;
3056 RefPtr<ITfCompositionView> pComposition(mComposition.mView);
3057 RefPtr<ITfRange> composingRange(aRangeNew);
3058 if (!composingRange) {
3059 hr = pComposition->GetRange(getter_AddRefs(composingRange));
3060 if (FAILED(hr)) {
3061 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3062 ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
3063 "FAILED due to pComposition->GetRange() failure",
3064 this));
3065 return hr;
3069 // Get starting offset of the composition
3070 LONG compStart = 0, compLength = 0;
3071 hr = GetRangeExtent(composingRange, &compStart, &compLength);
3072 if (FAILED(hr)) {
3073 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3074 ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
3075 "due to GetRangeExtent() failure",
3076 this));
3077 return hr;
3080 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3081 ("0x%p TSFTextStore::RestartCompositionIfNecessary(), "
3082 "range=%ld-%ld, mComposition={ mStart=%ld, mString.Length()=%lu }",
3083 this, compStart, compStart + compLength, mComposition.mStart,
3084 mComposition.mString.Length()));
3086 if (mComposition.mStart != compStart ||
3087 mComposition.mString.Length() != (ULONG)compLength) {
3088 // If the queried composition length is different from the length
3089 // of our composition string, OnUpdateComposition is being called
3090 // because a part of the original composition was committed.
3091 hr = RestartComposition(pComposition, composingRange);
3092 if (FAILED(hr)) {
3093 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3094 ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
3095 "FAILED due to RestartComposition() failure",
3096 this));
3097 return hr;
3101 MOZ_LOG(
3102 sTextStoreLog, LogLevel::Debug,
3103 ("0x%p TSFTextStore::RestartCompositionIfNecessary() succeeded", this));
3104 return S_OK;
3107 HRESULT
3108 TSFTextStore::RestartComposition(ITfCompositionView* aCompositionView,
3109 ITfRange* aNewRange) {
3110 Selection& selectionForTSF = SelectionForTSFRef();
3112 LONG newStart, newLength;
3113 HRESULT hr = GetRangeExtent(aNewRange, &newStart, &newLength);
3114 LONG newEnd = newStart + newLength;
3116 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3117 ("0x%p TSFTextStore::RestartComposition(aCompositionView=0x%p, "
3118 "aNewRange=0x%p { newStart=%d, newLength=%d }), "
3119 "mComposition={ mStart=%d, mCompositionString.Length()=%d }, "
3120 "selectionForTSF={ IsDirty()=%s, StartOffset()=%d, Length()=%d }",
3121 this, aCompositionView, aNewRange, newStart, newLength,
3122 mComposition.mStart, mComposition.mString.Length(),
3123 GetBoolName(selectionForTSF.IsDirty()),
3124 selectionForTSF.StartOffset(), selectionForTSF.Length()));
3126 if (selectionForTSF.IsDirty()) {
3127 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3128 ("0x%p TSFTextStore::RestartComposition() FAILED "
3129 "due to SelectionForTSFRef() failure",
3130 this));
3131 return E_FAIL;
3134 if (FAILED(hr)) {
3135 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3136 ("0x%p TSFTextStore::RestartComposition() FAILED "
3137 "due to GetRangeExtent() failure",
3138 this));
3139 return hr;
3142 // If the new range has no overlap with the crrent range, we just commit
3143 // the composition and restart new composition with the new range but
3144 // current selection range should be preserved.
3145 if (newStart >= mComposition.EndOffset() || newEnd <= mComposition.mStart) {
3146 RecordCompositionEndAction();
3147 RecordCompositionStartAction(aCompositionView, newStart, newLength, true);
3148 return S_OK;
3151 // If the new range has an overlap with the current one, we should not commit
3152 // the whole current range to avoid creating an odd undo transaction.
3153 // I.e., the overlapped range which is being composed should not appear in
3154 // undo transaction.
3156 // Backup current composition data and selection data.
3157 Composition oldComposition = mComposition;
3158 Selection oldSelection = selectionForTSF;
3160 // Commit only the part of composition.
3161 LONG keepComposingStartOffset = std::max(mComposition.mStart, newStart);
3162 LONG keepComposingEndOffset = std::min(mComposition.EndOffset(), newEnd);
3163 MOZ_ASSERT(
3164 keepComposingStartOffset <= keepComposingEndOffset,
3165 "Why keepComposingEndOffset is smaller than keepComposingStartOffset?");
3166 LONG keepComposingLength = keepComposingEndOffset - keepComposingStartOffset;
3167 // Remove the overlapped part from the commit string.
3168 nsAutoString commitString(mComposition.mString);
3169 commitString.Cut(keepComposingStartOffset - mComposition.mStart,
3170 keepComposingLength);
3171 // Update the composition string.
3172 Content& contentForTSF = ContentForTSFRef();
3173 if (!contentForTSF.IsInitialized()) {
3174 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3175 ("0x%p TSFTextStore::RestartComposition() FAILED "
3176 "due to ContentForTSFRef() failure",
3177 this));
3178 return E_FAIL;
3180 contentForTSF.ReplaceTextWith(mComposition.mStart,
3181 mComposition.mString.Length(), commitString);
3182 // Record a compositionupdate action for commit the part of composing string.
3183 PendingAction* action = LastOrNewPendingCompositionUpdate();
3184 action->mData = mComposition.mString;
3185 action->mRanges->Clear();
3186 // Note that we shouldn't append ranges when composition string
3187 // is empty because it may cause TextComposition confused.
3188 if (!action->mData.IsEmpty()) {
3189 TextRange caretRange;
3190 caretRange.mStartOffset = caretRange.mEndOffset =
3191 uint32_t(oldComposition.mStart + commitString.Length());
3192 caretRange.mRangeType = TextRangeType::eCaret;
3193 action->mRanges->AppendElement(caretRange);
3195 action->mIncomplete = false;
3197 // Record compositionend action.
3198 RecordCompositionEndAction();
3200 // Record compositionstart action only with the new start since this method
3201 // hasn't restored composing string yet.
3202 RecordCompositionStartAction(aCompositionView, newStart, 0, false);
3204 // Restore the latest text content and selection.
3205 contentForTSF.ReplaceSelectedTextWith(nsDependentSubstring(
3206 oldComposition.mString, keepComposingStartOffset - oldComposition.mStart,
3207 keepComposingLength));
3208 selectionForTSF = oldSelection;
3210 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3211 ("0x%p TSFTextStore::RestartComposition() succeeded, "
3212 "mComposition={ mStart=%d, mCompositionString.Length()=%d }, "
3213 "selectionForTSF={ IsDirty()=%s, StartOffset()=%d, Length()=%d }",
3214 this, mComposition.mStart, mComposition.mString.Length(),
3215 GetBoolName(selectionForTSF.IsDirty()),
3216 selectionForTSF.StartOffset(), selectionForTSF.Length()));
3218 return S_OK;
3221 static bool GetColor(const TF_DA_COLOR& aTSFColor, nscolor& aResult) {
3222 switch (aTSFColor.type) {
3223 case TF_CT_SYSCOLOR: {
3224 DWORD sysColor = ::GetSysColor(aTSFColor.nIndex);
3225 aResult =
3226 NS_RGB(GetRValue(sysColor), GetGValue(sysColor), GetBValue(sysColor));
3227 return true;
3229 case TF_CT_COLORREF:
3230 aResult = NS_RGB(GetRValue(aTSFColor.cr), GetGValue(aTSFColor.cr),
3231 GetBValue(aTSFColor.cr));
3232 return true;
3233 case TF_CT_NONE:
3234 default:
3235 return false;
3239 static bool GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle,
3240 TextRangeStyle::LineStyle& aTextRangeLineStyle) {
3241 switch (aTSFLineStyle) {
3242 case TF_LS_NONE:
3243 aTextRangeLineStyle = TextRangeStyle::LineStyle::None;
3244 return true;
3245 case TF_LS_SOLID:
3246 aTextRangeLineStyle = TextRangeStyle::LineStyle::Solid;
3247 return true;
3248 case TF_LS_DOT:
3249 aTextRangeLineStyle = TextRangeStyle::LineStyle::Dotted;
3250 return true;
3251 case TF_LS_DASH:
3252 aTextRangeLineStyle = TextRangeStyle::LineStyle::Dashed;
3253 return true;
3254 case TF_LS_SQUIGGLE:
3255 aTextRangeLineStyle = TextRangeStyle::LineStyle::Wavy;
3256 return true;
3257 default:
3258 return false;
3262 HRESULT
3263 TSFTextStore::RecordCompositionUpdateAction() {
3264 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3265 ("0x%p TSFTextStore::RecordCompositionUpdateAction(), "
3266 "mComposition={ mView=0x%p, mStart=%d, mString=\"%s\" "
3267 "(Length()=%d) }",
3268 this, mComposition.mView.get(), mComposition.mStart,
3269 GetEscapedUTF8String(mComposition.mString).get(),
3270 mComposition.mString.Length()));
3272 if (!mComposition.IsComposing()) {
3273 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3274 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3275 "due to no composition view",
3276 this));
3277 return E_FAIL;
3280 // Getting display attributes is *really* complicated!
3281 // We first get the context and the property objects to query for
3282 // attributes, but since a big range can have a variety of values for
3283 // the attribute, we have to find out all the ranges that have distinct
3284 // attribute values. Then we query for what the value represents through
3285 // the display attribute manager and translate that to TextRange to be
3286 // sent in eCompositionChange
3288 RefPtr<ITfProperty> attrPropetry;
3289 HRESULT hr =
3290 mContext->GetProperty(GUID_PROP_ATTRIBUTE, getter_AddRefs(attrPropetry));
3291 if (FAILED(hr) || !attrPropetry) {
3292 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3293 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3294 "due to mContext->GetProperty() failure",
3295 this));
3296 return FAILED(hr) ? hr : E_FAIL;
3299 RefPtr<ITfRange> composingRange;
3300 hr = mComposition.mView->GetRange(getter_AddRefs(composingRange));
3301 if (FAILED(hr)) {
3302 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3303 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3304 "FAILED due to mComposition.mView->GetRange() failure",
3305 this));
3306 return hr;
3309 RefPtr<IEnumTfRanges> enumRanges;
3310 hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie),
3311 getter_AddRefs(enumRanges), composingRange);
3312 if (FAILED(hr) || !enumRanges) {
3313 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3314 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3315 "due to attrPropetry->EnumRanges() failure",
3316 this));
3317 return FAILED(hr) ? hr : E_FAIL;
3320 // First, put the log of content and selection here.
3321 Selection& selectionForTSF = SelectionForTSFRef();
3322 if (selectionForTSF.IsDirty()) {
3323 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3324 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3325 "due to SelectionForTSFRef() failure",
3326 this));
3327 return E_FAIL;
3330 PendingAction* action = LastOrNewPendingCompositionUpdate();
3331 action->mData = mComposition.mString;
3332 // The ranges might already have been initialized, however, if this is
3333 // called again, that means we need to overwrite the ranges with current
3334 // information.
3335 action->mRanges->Clear();
3337 // Note that we shouldn't append ranges when composition string
3338 // is empty because it may cause TextComposition confused.
3339 if (!action->mData.IsEmpty()) {
3340 TextRange newRange;
3341 // No matter if we have display attribute info or not,
3342 // we always pass in at least one range to eCompositionChange
3343 newRange.mStartOffset = 0;
3344 newRange.mEndOffset = action->mData.Length();
3345 newRange.mRangeType = TextRangeType::eRawClause;
3346 action->mRanges->AppendElement(newRange);
3348 RefPtr<ITfRange> range;
3349 while (enumRanges->Next(1, getter_AddRefs(range), nullptr) == S_OK) {
3350 if (NS_WARN_IF(!range)) {
3351 break;
3354 LONG rangeStart = 0, rangeLength = 0;
3355 if (FAILED(GetRangeExtent(range, &rangeStart, &rangeLength))) {
3356 continue;
3358 // The range may include out of composition string. We should ignore
3359 // outside of the composition string.
3360 LONG start = std::min(std::max(rangeStart, mComposition.mStart),
3361 mComposition.EndOffset());
3362 LONG end =
3363 std::max(std::min(rangeStart + rangeLength, mComposition.EndOffset()),
3364 mComposition.mStart);
3365 LONG length = end - start;
3366 if (length < 0) {
3367 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3368 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3369 "ignores invalid range (%d-%d)",
3370 this, rangeStart - mComposition.mStart,
3371 rangeStart - mComposition.mStart + rangeLength));
3372 continue;
3374 if (!length) {
3375 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3376 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3377 "ignores a range due to outside of the composition or empty "
3378 "(%d-%d)",
3379 this, rangeStart - mComposition.mStart,
3380 rangeStart - mComposition.mStart + rangeLength));
3381 continue;
3384 TextRange newRange;
3385 newRange.mStartOffset = uint32_t(start - mComposition.mStart);
3386 // The end of the last range in the array is
3387 // always kept at the end of composition
3388 newRange.mEndOffset = mComposition.mString.Length();
3390 TF_DISPLAYATTRIBUTE attr;
3391 hr = GetDisplayAttribute(attrPropetry, range, &attr);
3392 if (FAILED(hr)) {
3393 newRange.mRangeType = TextRangeType::eRawClause;
3394 } else {
3395 newRange.mRangeType = GetGeckoSelectionValue(attr);
3396 if (GetColor(attr.crText, newRange.mRangeStyle.mForegroundColor)) {
3397 newRange.mRangeStyle.mDefinedStyles |=
3398 TextRangeStyle::DEFINED_FOREGROUND_COLOR;
3400 if (GetColor(attr.crBk, newRange.mRangeStyle.mBackgroundColor)) {
3401 newRange.mRangeStyle.mDefinedStyles |=
3402 TextRangeStyle::DEFINED_BACKGROUND_COLOR;
3404 if (GetColor(attr.crLine, newRange.mRangeStyle.mUnderlineColor)) {
3405 newRange.mRangeStyle.mDefinedStyles |=
3406 TextRangeStyle::DEFINED_UNDERLINE_COLOR;
3408 if (GetLineStyle(attr.lsStyle, newRange.mRangeStyle.mLineStyle)) {
3409 newRange.mRangeStyle.mDefinedStyles |=
3410 TextRangeStyle::DEFINED_LINESTYLE;
3411 newRange.mRangeStyle.mIsBoldLine = attr.fBoldLine != 0;
3415 TextRange& lastRange = action->mRanges->LastElement();
3416 if (lastRange.mStartOffset == newRange.mStartOffset) {
3417 // Replace range if last range is the same as this one
3418 // So that ranges don't overlap and confuse the editor
3419 lastRange = newRange;
3420 } else {
3421 lastRange.mEndOffset = newRange.mStartOffset;
3422 action->mRanges->AppendElement(newRange);
3426 // We need to hack for Korean Input System which is Korean standard TIP.
3427 // It sets no change style to IME selection (the selection is always only
3428 // one). So, the composition string looks like normal (or committed)
3429 // string. At this time, current selection range is same as the
3430 // composition string range. Other applications set a wide caret which
3431 // covers the composition string, however, Gecko doesn't support the wide
3432 // caret drawing now (Gecko doesn't support XOR drawing), unfortunately.
3433 // For now, we should change the range style to undefined.
3434 if (!selectionForTSF.IsCollapsed() && action->mRanges->Length() == 1) {
3435 TextRange& range = action->mRanges->ElementAt(0);
3436 LONG start = selectionForTSF.MinOffset();
3437 LONG end = selectionForTSF.MaxOffset();
3438 if ((LONG)range.mStartOffset == start - mComposition.mStart &&
3439 (LONG)range.mEndOffset == end - mComposition.mStart &&
3440 range.mRangeStyle.IsNoChangeStyle()) {
3441 range.mRangeStyle.Clear();
3442 // The looks of selected type is better than others.
3443 range.mRangeType = TextRangeType::eSelectedRawClause;
3447 // The caret position has to be collapsed.
3448 uint32_t caretPosition = static_cast<uint32_t>(selectionForTSF.MaxOffset() -
3449 mComposition.mStart);
3451 // If caret is in the target clause and it doesn't have specific style,
3452 // the target clause will be painted as normal selection range. Since
3453 // caret shouldn't be in selection range on Windows, we shouldn't append
3454 // caret range in such case.
3455 const TextRange* targetClause = action->mRanges->GetTargetClause();
3456 if (!targetClause || targetClause->mRangeStyle.IsDefined() ||
3457 caretPosition < targetClause->mStartOffset ||
3458 caretPosition > targetClause->mEndOffset) {
3459 TextRange caretRange;
3460 caretRange.mStartOffset = caretRange.mEndOffset = caretPosition;
3461 caretRange.mRangeType = TextRangeType::eCaret;
3462 action->mRanges->AppendElement(caretRange);
3466 action->mIncomplete = false;
3468 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3469 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3470 "succeeded",
3471 this));
3473 return S_OK;
3476 HRESULT
3477 TSFTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection,
3478 bool aDispatchCompositionChangeEvent) {
3479 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3480 ("0x%p TSFTextStore::SetSelectionInternal(pSelection={ "
3481 "acpStart=%ld, acpEnd=%ld, style={ ase=%s, fInterimChar=%s} }, "
3482 "aDispatchCompositionChangeEvent=%s), mComposition.IsComposing()=%s",
3483 this, pSelection->acpStart, pSelection->acpEnd,
3484 GetActiveSelEndName(pSelection->style.ase),
3485 GetBoolName(pSelection->style.fInterimChar),
3486 GetBoolName(aDispatchCompositionChangeEvent),
3487 GetBoolName(mComposition.IsComposing())));
3489 MOZ_ASSERT(IsReadWriteLocked());
3491 Selection& selectionForTSF = SelectionForTSFRef();
3492 if (selectionForTSF.IsDirty()) {
3493 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3494 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3495 "SelectionForTSFRef() failure",
3496 this));
3497 return E_FAIL;
3500 MaybeDispatchKeyboardEventAsProcessedByIME();
3501 if (mDestroyed) {
3502 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3503 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3504 "destroyed during dispatching a keyboard event",
3505 this));
3506 return E_FAIL;
3509 // If actually the range is not changing, we should do nothing.
3510 // Perhaps, we can ignore the difference change because it must not be
3511 // important for following edit.
3512 if (selectionForTSF.EqualsExceptDirection(*pSelection)) {
3513 MOZ_LOG(sTextStoreLog, LogLevel::Warning,
3514 ("0x%p TSFTextStore::SetSelectionInternal() Succeeded but "
3515 "did nothing because the selection range isn't changing",
3516 this));
3517 selectionForTSF.SetSelection(*pSelection);
3518 return S_OK;
3521 if (mComposition.IsComposing()) {
3522 if (aDispatchCompositionChangeEvent) {
3523 HRESULT hr = RestartCompositionIfNecessary();
3524 if (FAILED(hr)) {
3525 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3526 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3527 "RestartCompositionIfNecessary() failure",
3528 this));
3529 return hr;
3532 if (pSelection->acpStart < mComposition.mStart ||
3533 pSelection->acpEnd > mComposition.EndOffset()) {
3534 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3535 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3536 "the selection being out of the composition string",
3537 this));
3538 return TS_E_INVALIDPOS;
3540 // Emulate selection during compositions
3541 selectionForTSF.SetSelection(*pSelection);
3542 if (aDispatchCompositionChangeEvent) {
3543 HRESULT hr = RecordCompositionUpdateAction();
3544 if (FAILED(hr)) {
3545 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3546 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3547 "RecordCompositionUpdateAction() failure",
3548 this));
3549 return hr;
3552 return S_OK;
3555 TS_SELECTION_ACP selectionInContent(*pSelection);
3557 // If mContentForTSF caches old contents which is now different from
3558 // actual contents, we need some complicated hack here...
3559 // Note that this hack assumes that this is used for reconversion.
3560 if (mContentForTSF.IsInitialized() && mPendingTextChangeData.IsValid() &&
3561 !mPendingTextChangeData.mCausedOnlyByComposition) {
3562 uint32_t startOffset = static_cast<uint32_t>(selectionInContent.acpStart);
3563 uint32_t endOffset = static_cast<uint32_t>(selectionInContent.acpEnd);
3564 if (mPendingTextChangeData.mStartOffset >= endOffset) {
3565 // Setting selection before any changed ranges is fine.
3566 } else if (mPendingTextChangeData.mRemovedEndOffset <= startOffset) {
3567 // Setting selection after removed range is fine with following
3568 // adjustment.
3569 selectionInContent.acpStart += mPendingTextChangeData.Difference();
3570 selectionInContent.acpEnd += mPendingTextChangeData.Difference();
3571 } else if (startOffset == endOffset) {
3572 // Moving caret position may be fine in most cases even if the insertion
3573 // point has already gone but in this case, composition will be inserted
3574 // to unexpected position, though.
3575 // It seems that moving caret into middle of the new text is odd.
3576 // Perhaps, end of it is expected by users in most cases.
3577 selectionInContent.acpStart = mPendingTextChangeData.mAddedEndOffset;
3578 selectionInContent.acpEnd = selectionInContent.acpStart;
3579 } else {
3580 // Otherwise, i.e., setting range has already gone, we cannot set
3581 // selection properly.
3582 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3583 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3584 "there is unknown content change",
3585 this));
3586 return E_FAIL;
3590 CompleteLastActionIfStillIncomplete();
3591 PendingAction* action = mPendingActions.AppendElement();
3592 action->mType = PendingAction::Type::eSetSelection;
3593 action->mSelectionStart = selectionInContent.acpStart;
3594 action->mSelectionLength =
3595 selectionInContent.acpEnd - selectionInContent.acpStart;
3596 action->mSelectionReversed = (selectionInContent.style.ase == TS_AE_START);
3598 // Use TSF specified selection for updating mSelectionForTSF.
3599 selectionForTSF.SetSelection(*pSelection);
3601 return S_OK;
3604 STDMETHODIMP
3605 TSFTextStore::SetSelection(ULONG ulCount, const TS_SELECTION_ACP* pSelection) {
3606 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3607 ("0x%p TSFTextStore::SetSelection(ulCount=%lu, pSelection=%p { "
3608 "acpStart=%ld, acpEnd=%ld, style={ ase=%s, fInterimChar=%s } }), "
3609 "mComposition.IsComposing()=%s",
3610 this, ulCount, pSelection, pSelection ? pSelection->acpStart : 0,
3611 pSelection ? pSelection->acpEnd : 0,
3612 pSelection ? GetActiveSelEndName(pSelection->style.ase) : "",
3613 pSelection ? GetBoolName(pSelection->style.fInterimChar) : "",
3614 GetBoolName(mComposition.IsComposing())));
3616 if (!IsReadWriteLocked()) {
3617 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3618 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3619 "not locked (read-write)",
3620 this));
3621 return TS_E_NOLOCK;
3623 if (ulCount != 1) {
3624 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3625 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3626 "trying setting multiple selection",
3627 this));
3628 return E_INVALIDARG;
3630 if (!pSelection) {
3631 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3632 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3633 "null argument",
3634 this));
3635 return E_INVALIDARG;
3638 HRESULT hr = SetSelectionInternal(pSelection, true);
3639 if (FAILED(hr)) {
3640 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3641 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3642 "SetSelectionInternal() failure",
3643 this));
3644 } else {
3645 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3646 ("0x%p TSFTextStore::SetSelection() succeeded", this));
3648 return hr;
3651 STDMETHODIMP
3652 TSFTextStore::GetText(LONG acpStart, LONG acpEnd, WCHAR* pchPlain,
3653 ULONG cchPlainReq, ULONG* pcchPlainOut,
3654 TS_RUNINFO* prgRunInfo, ULONG ulRunInfoReq,
3655 ULONG* pulRunInfoOut, LONG* pacpNext) {
3656 MOZ_LOG(
3657 sTextStoreLog, LogLevel::Info,
3658 ("0x%p TSFTextStore::GetText(acpStart=%ld, acpEnd=%ld, pchPlain=0x%p, "
3659 "cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, ulRunInfoReq=%lu, "
3660 "pulRunInfoOut=0x%p, pacpNext=0x%p), mComposition={ mStart=%ld, "
3661 "mString.Length()=%lu, IsComposing()=%s }",
3662 this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, prgRunInfo,
3663 ulRunInfoReq, pulRunInfoOut, pacpNext, mComposition.mStart,
3664 mComposition.mString.Length(), GetBoolName(mComposition.IsComposing())));
3666 if (!IsReadLocked()) {
3667 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3668 ("0x%p TSFTextStore::GetText() FAILED due to "
3669 "not locked (read)",
3670 this));
3671 return TS_E_NOLOCK;
3674 if (!pcchPlainOut || (!pchPlain && !prgRunInfo) ||
3675 !cchPlainReq != !pchPlain || !ulRunInfoReq != !prgRunInfo) {
3676 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3677 ("0x%p TSFTextStore::GetText() FAILED due to "
3678 "invalid argument",
3679 this));
3680 return E_INVALIDARG;
3683 if (acpStart < 0 || acpEnd < -1 || (acpEnd != -1 && acpStart > acpEnd)) {
3684 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3685 ("0x%p TSFTextStore::GetText() FAILED due to "
3686 "invalid position",
3687 this));
3688 return TS_E_INVALIDPOS;
3691 // Making sure to null-terminate string just to be on the safe side
3692 *pcchPlainOut = 0;
3693 if (pchPlain && cchPlainReq) *pchPlain = 0;
3694 if (pulRunInfoOut) *pulRunInfoOut = 0;
3695 if (pacpNext) *pacpNext = acpStart;
3696 if (prgRunInfo && ulRunInfoReq) {
3697 prgRunInfo->uCount = 0;
3698 prgRunInfo->type = TS_RT_PLAIN;
3701 Content& contentForTSF = ContentForTSFRef();
3702 if (!contentForTSF.IsInitialized()) {
3703 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3704 ("0x%p TSFTextStore::GetText() FAILED due to "
3705 "ContentForTSFRef() failure",
3706 this));
3707 return E_FAIL;
3709 if (contentForTSF.Text().Length() < static_cast<uint32_t>(acpStart)) {
3710 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3711 ("0x%p TSFTextStore::GetText() FAILED due to "
3712 "acpStart is larger offset than the actual text length",
3713 this));
3714 return TS_E_INVALIDPOS;
3716 if (acpEnd != -1 &&
3717 contentForTSF.Text().Length() < static_cast<uint32_t>(acpEnd)) {
3718 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3719 ("0x%p TSFTextStore::GetText() FAILED due to "
3720 "acpEnd is larger offset than the actual text length",
3721 this));
3722 return TS_E_INVALIDPOS;
3724 uint32_t length = (acpEnd == -1) ? contentForTSF.Text().Length() -
3725 static_cast<uint32_t>(acpStart)
3726 : static_cast<uint32_t>(acpEnd - acpStart);
3727 if (cchPlainReq && cchPlainReq - 1 < length) {
3728 length = cchPlainReq - 1;
3730 if (length) {
3731 if (pchPlain && cchPlainReq) {
3732 const char16_t* startChar =
3733 contentForTSF.Text().BeginReading() + acpStart;
3734 memcpy(pchPlain, startChar, length * sizeof(*pchPlain));
3735 pchPlain[length] = 0;
3736 *pcchPlainOut = length;
3738 if (prgRunInfo && ulRunInfoReq) {
3739 prgRunInfo->uCount = length;
3740 prgRunInfo->type = TS_RT_PLAIN;
3741 if (pulRunInfoOut) *pulRunInfoOut = 1;
3743 if (pacpNext) *pacpNext = acpStart + length;
3746 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3747 ("0x%p TSFTextStore::GetText() succeeded: pcchPlainOut=0x%p, "
3748 "*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, "
3749 "*pacpNext=%ld)",
3750 this, pcchPlainOut, prgRunInfo ? prgRunInfo->uCount : 0,
3751 prgRunInfo ? GetTextRunTypeName(prgRunInfo->type) : "N/A",
3752 pulRunInfoOut ? *pulRunInfoOut : 0, pacpNext ? *pacpNext : 0));
3753 return S_OK;
3756 STDMETHODIMP
3757 TSFTextStore::SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd,
3758 const WCHAR* pchText, ULONG cch, TS_TEXTCHANGE* pChange) {
3759 MOZ_LOG(
3760 sTextStoreLog, LogLevel::Info,
3761 ("0x%p TSFTextStore::SetText(dwFlags=%s, acpStart=%ld, "
3762 "acpEnd=%ld, pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), "
3763 "mComposition.IsComposing()=%s",
3764 this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" : "not-specified",
3765 acpStart, acpEnd, pchText,
3766 pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "", cch,
3767 pChange, GetBoolName(mComposition.IsComposing())));
3769 // Per SDK documentation, and since we don't have better
3770 // ways to do this, this method acts as a helper to
3771 // call SetSelection followed by InsertTextAtSelection
3772 if (!IsReadWriteLocked()) {
3773 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3774 ("0x%p TSFTextStore::SetText() FAILED due to "
3775 "not locked (read)",
3776 this));
3777 return TS_E_NOLOCK;
3780 TS_SELECTION_ACP selection;
3781 selection.acpStart = acpStart;
3782 selection.acpEnd = acpEnd;
3783 selection.style.ase = TS_AE_END;
3784 selection.style.fInterimChar = 0;
3785 // Set selection to desired range
3786 HRESULT hr = SetSelectionInternal(&selection);
3787 if (FAILED(hr)) {
3788 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3789 ("0x%p TSFTextStore::SetText() FAILED due to "
3790 "SetSelectionInternal() failure",
3791 this));
3792 return hr;
3794 // Replace just selected text
3795 if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
3796 pChange)) {
3797 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3798 ("0x%p TSFTextStore::SetText() FAILED due to "
3799 "InsertTextAtSelectionInternal() failure",
3800 this));
3801 return E_FAIL;
3804 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3805 ("0x%p TSFTextStore::SetText() succeeded: pChange={ "
3806 "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
3807 this, pChange ? pChange->acpStart : 0,
3808 pChange ? pChange->acpOldEnd : 0, pChange ? pChange->acpNewEnd : 0));
3809 return S_OK;
3812 STDMETHODIMP
3813 TSFTextStore::GetFormattedText(LONG acpStart, LONG acpEnd,
3814 IDataObject** ppDataObject) {
3815 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3816 ("0x%p TSFTextStore::GetFormattedText() called "
3817 "but not supported (E_NOTIMPL)",
3818 this));
3820 // no support for formatted text
3821 return E_NOTIMPL;
3824 STDMETHODIMP
3825 TSFTextStore::GetEmbedded(LONG acpPos, REFGUID rguidService, REFIID riid,
3826 IUnknown** ppunk) {
3827 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3828 ("0x%p TSFTextStore::GetEmbedded() called "
3829 "but not supported (E_NOTIMPL)",
3830 this));
3832 // embedded objects are not supported
3833 return E_NOTIMPL;
3836 STDMETHODIMP
3837 TSFTextStore::QueryInsertEmbedded(const GUID* pguidService,
3838 const FORMATETC* pFormatEtc,
3839 BOOL* pfInsertable) {
3840 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3841 ("0x%p TSFTextStore::QueryInsertEmbedded() called "
3842 "but not supported, *pfInsertable=FALSE (S_OK)",
3843 this));
3845 // embedded objects are not supported
3846 *pfInsertable = FALSE;
3847 return S_OK;
3850 STDMETHODIMP
3851 TSFTextStore::InsertEmbedded(DWORD dwFlags, LONG acpStart, LONG acpEnd,
3852 IDataObject* pDataObject, TS_TEXTCHANGE* pChange) {
3853 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3854 ("0x%p TSFTextStore::InsertEmbedded() called "
3855 "but not supported (E_NOTIMPL)",
3856 this));
3858 // embedded objects are not supported
3859 return E_NOTIMPL;
3862 // static
3863 bool TSFTextStore::ShouldSetInputScopeOfURLBarToDefault() {
3864 // FYI: Google Japanese Input may be an IMM-IME. If it's installed on
3865 // Win7, it's always IMM-IME. Otherwise, basically, it's a TIP.
3866 // However, if it's installed on Win7 and has not been updated yet
3867 // after the OS is upgraded to Win8 or later, it's still an IMM-IME.
3868 // Therefore, we also need to check with IMMHandler here.
3869 if (!TSFPrefs::ShouldSetInputScopeOfURLBarToDefault()) {
3870 return false;
3873 if (IMMHandler::IsGoogleJapaneseInputActive()) {
3874 return true;
3877 switch (TSFStaticSink::ActiveTIP()) {
3878 case TextInputProcessorID::eMicrosoftIMEForJapanese:
3879 case TextInputProcessorID::eGoogleJapaneseInput:
3880 case TextInputProcessorID::eMicrosoftBopomofo:
3881 case TextInputProcessorID::eMicrosoftChangJie:
3882 case TextInputProcessorID::eMicrosoftPhonetic:
3883 case TextInputProcessorID::eMicrosoftQuick:
3884 case TextInputProcessorID::eMicrosoftNewChangJie:
3885 case TextInputProcessorID::eMicrosoftNewPhonetic:
3886 case TextInputProcessorID::eMicrosoftNewQuick:
3887 case TextInputProcessorID::eMicrosoftPinyin:
3888 case TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle:
3889 case TextInputProcessorID::eMicrosoftOldHangul:
3890 case TextInputProcessorID::eMicrosoftWubi:
3891 return true;
3892 case TextInputProcessorID::eMicrosoftIMEForKorean:
3893 return IsWin8OrLater();
3894 default:
3895 return false;
3899 void TSFTextStore::SetInputScope(const nsString& aHTMLInputType,
3900 const nsString& aHTMLInputInputMode,
3901 bool aInPrivateBrowsing) {
3902 mInputScopes.Clear();
3904 // IME may refer only first input scope, but we will append inputmode's
3905 // input scopes too like Chrome since IME may refer it.
3906 IMEHandler::AppendInputScopeFromType(aHTMLInputType, mInputScopes);
3907 IMEHandler::AppendInputScopeFromInputmode(aHTMLInputInputMode, mInputScopes);
3909 if (aInPrivateBrowsing) {
3910 mInputScopes.AppendElement(IS_PRIVATE);
3914 int32_t TSFTextStore::GetRequestedAttrIndex(const TS_ATTRID& aAttrID) {
3915 if (IsEqualGUID(aAttrID, GUID_PROP_INPUTSCOPE)) {
3916 return eInputScope;
3918 if (IsEqualGUID(aAttrID, TSATTRID_Text_VerticalWriting)) {
3919 return eTextVerticalWriting;
3921 if (IsEqualGUID(aAttrID, TSATTRID_Text_Orientation)) {
3922 return eTextOrientation;
3924 return eNotSupported;
3927 TS_ATTRID
3928 TSFTextStore::GetAttrID(int32_t aIndex) {
3929 switch (aIndex) {
3930 case eInputScope:
3931 return GUID_PROP_INPUTSCOPE;
3932 case eTextVerticalWriting:
3933 return TSATTRID_Text_VerticalWriting;
3934 case eTextOrientation:
3935 return TSATTRID_Text_Orientation;
3936 default:
3937 MOZ_CRASH("Invalid index? Or not implemented yet?");
3938 return GUID_NULL;
3942 HRESULT
3943 TSFTextStore::HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
3944 const TS_ATTRID* aFilterAttrs) {
3945 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3946 ("0x%p TSFTextStore::HandleRequestAttrs(aFlags=%s, "
3947 "aFilterCount=%u)",
3948 this, GetFindFlagName(aFlags).get(), aFilterCount));
3950 // This is a little weird! RequestSupportedAttrs gives us advanced notice
3951 // of a support query via RetrieveRequestedAttrs for a specific attribute.
3952 // RetrieveRequestedAttrs needs to return valid data for all attributes we
3953 // support, but the text service will only want the input scope object
3954 // returned in RetrieveRequestedAttrs if the dwFlags passed in here contains
3955 // TS_ATTR_FIND_WANT_VALUE.
3956 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
3957 mRequestedAttrs[i] = false;
3959 mRequestedAttrValues = !!(aFlags & TS_ATTR_FIND_WANT_VALUE);
3961 for (uint32_t i = 0; i < aFilterCount; i++) {
3962 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3963 ("0x%p TSFTextStore::HandleRequestAttrs(), "
3964 "requested attr=%s",
3965 this, GetGUIDNameStrWithTable(aFilterAttrs[i]).get()));
3966 int32_t index = GetRequestedAttrIndex(aFilterAttrs[i]);
3967 if (index != eNotSupported) {
3968 mRequestedAttrs[index] = true;
3971 return S_OK;
3974 STDMETHODIMP
3975 TSFTextStore::RequestSupportedAttrs(DWORD dwFlags, ULONG cFilterAttrs,
3976 const TS_ATTRID* paFilterAttrs) {
3977 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3978 ("0x%p TSFTextStore::RequestSupportedAttrs(dwFlags=%s, "
3979 "cFilterAttrs=%lu)",
3980 this, GetFindFlagName(dwFlags).get(), cFilterAttrs));
3982 return HandleRequestAttrs(dwFlags, cFilterAttrs, paFilterAttrs);
3985 STDMETHODIMP
3986 TSFTextStore::RequestAttrsAtPosition(LONG acpPos, ULONG cFilterAttrs,
3987 const TS_ATTRID* paFilterAttrs,
3988 DWORD dwFlags) {
3989 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3990 ("0x%p TSFTextStore::RequestAttrsAtPosition(acpPos=%ld, "
3991 "cFilterAttrs=%lu, dwFlags=%s)",
3992 this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
3994 return HandleRequestAttrs(dwFlags | TS_ATTR_FIND_WANT_VALUE, cFilterAttrs,
3995 paFilterAttrs);
3998 STDMETHODIMP
3999 TSFTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos,
4000 ULONG cFilterAttrs,
4001 const TS_ATTRID* paFilterAttr,
4002 DWORD dwFlags) {
4003 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4004 ("0x%p TSFTextStore::RequestAttrsTransitioningAtPosition("
4005 "acpPos=%ld, cFilterAttrs=%lu, dwFlags=%s) called but not supported "
4006 "(S_OK)",
4007 this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
4009 // no per character attributes defined
4010 return S_OK;
4013 STDMETHODIMP
4014 TSFTextStore::FindNextAttrTransition(LONG acpStart, LONG acpHalt,
4015 ULONG cFilterAttrs,
4016 const TS_ATTRID* paFilterAttrs,
4017 DWORD dwFlags, LONG* pacpNext,
4018 BOOL* pfFound, LONG* plFoundOffset) {
4019 if (!pacpNext || !pfFound || !plFoundOffset) {
4020 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4021 (" 0x%p TSFTextStore::FindNextAttrTransition() FAILED due to "
4022 "null argument",
4023 this));
4024 return E_INVALIDARG;
4027 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4028 ("0x%p TSFTextStore::FindNextAttrTransition() called "
4029 "but not supported (S_OK)",
4030 this));
4032 // no per character attributes defined
4033 *pacpNext = *plFoundOffset = acpHalt;
4034 *pfFound = FALSE;
4035 return S_OK;
4038 STDMETHODIMP
4039 TSFTextStore::RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL* paAttrVals,
4040 ULONG* pcFetched) {
4041 if (!pcFetched || !paAttrVals) {
4042 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4043 ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
4044 "null argument",
4045 this));
4046 return E_INVALIDARG;
4049 ULONG expectedCount = 0;
4050 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
4051 if (mRequestedAttrs[i]) {
4052 expectedCount++;
4055 if (ulCount < expectedCount) {
4056 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4057 ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
4058 "not enough count ulCount=%u, expectedCount=%u",
4059 this, ulCount, expectedCount));
4060 return E_INVALIDARG;
4063 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4064 ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
4065 "ulCount=%d, mRequestedAttrValues=%s",
4066 this, ulCount, GetBoolName(mRequestedAttrValues)));
4068 int32_t count = 0;
4069 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
4070 if (!mRequestedAttrs[i]) {
4071 continue;
4073 mRequestedAttrs[i] = false;
4075 TS_ATTRID attrID = GetAttrID(i);
4077 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4078 ("0x%p TSFTextStore::RetrieveRequestedAttrs() for %s", this,
4079 GetGUIDNameStrWithTable(attrID).get()));
4081 paAttrVals[count].idAttr = attrID;
4082 paAttrVals[count].dwOverlapId = 0;
4084 if (!mRequestedAttrValues) {
4085 paAttrVals[count].varValue.vt = VT_EMPTY;
4086 } else {
4087 switch (i) {
4088 case eInputScope: {
4089 paAttrVals[count].varValue.vt = VT_UNKNOWN;
4090 RefPtr<IUnknown> inputScope = new InputScopeImpl(mInputScopes);
4091 paAttrVals[count].varValue.punkVal = inputScope.forget().take();
4092 break;
4094 case eTextVerticalWriting: {
4095 Selection& selectionForTSF = SelectionForTSFRef();
4096 paAttrVals[count].varValue.vt = VT_BOOL;
4097 paAttrVals[count].varValue.boolVal =
4098 !selectionForTSF.IsDirty() &&
4099 selectionForTSF.GetWritingMode().IsVertical()
4100 ? VARIANT_TRUE
4101 : VARIANT_FALSE;
4102 break;
4104 case eTextOrientation: {
4105 Selection& selectionForTSF = SelectionForTSFRef();
4106 paAttrVals[count].varValue.vt = VT_I4;
4107 paAttrVals[count].varValue.lVal =
4108 !selectionForTSF.IsDirty() &&
4109 selectionForTSF.GetWritingMode().IsVertical()
4110 ? 2700
4111 : 0;
4112 break;
4114 default:
4115 MOZ_CRASH("Invalid index? Or not implemented yet?");
4116 break;
4119 count++;
4122 mRequestedAttrValues = false;
4124 if (count) {
4125 *pcFetched = count;
4126 return S_OK;
4129 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4130 ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
4131 "for unknown TS_ATTRVAL, *pcFetched=0 (S_OK)",
4132 this));
4134 paAttrVals->dwOverlapId = 0;
4135 paAttrVals->varValue.vt = VT_EMPTY;
4136 *pcFetched = 0;
4137 return S_OK;
4140 STDMETHODIMP
4141 TSFTextStore::GetEndACP(LONG* pacp) {
4142 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4143 ("0x%p TSFTextStore::GetEndACP(pacp=0x%p)", this, pacp));
4145 if (!IsReadLocked()) {
4146 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4147 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4148 "not locked (read)",
4149 this));
4150 return TS_E_NOLOCK;
4153 if (!pacp) {
4154 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4155 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4156 "null argument",
4157 this));
4158 return E_INVALIDARG;
4161 Content& contentForTSF = ContentForTSFRef();
4162 if (!contentForTSF.IsInitialized()) {
4163 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4164 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4165 "ContentForTSFRef() failure",
4166 this));
4167 return E_FAIL;
4169 *pacp = static_cast<LONG>(contentForTSF.Text().Length());
4170 return S_OK;
4173 STDMETHODIMP
4174 TSFTextStore::GetActiveView(TsViewCookie* pvcView) {
4175 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4176 ("0x%p TSFTextStore::GetActiveView(pvcView=0x%p)", this, pvcView));
4178 if (!pvcView) {
4179 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4180 ("0x%p TSFTextStore::GetActiveView() FAILED due to "
4181 "null argument",
4182 this));
4183 return E_INVALIDARG;
4186 *pvcView = TEXTSTORE_DEFAULT_VIEW;
4188 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4189 ("0x%p TSFTextStore::GetActiveView() succeeded: *pvcView=%ld", this,
4190 *pvcView));
4191 return S_OK;
4194 STDMETHODIMP
4195 TSFTextStore::GetACPFromPoint(TsViewCookie vcView, const POINT* pt,
4196 DWORD dwFlags, LONG* pacp) {
4197 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4198 ("0x%p TSFTextStore::GetACPFromPoint(pvcView=%d, pt=%p (x=%d, "
4199 "y=%d), dwFlags=%s, pacp=%p, mDeferNotifyingTSF=%s, "
4200 "mWaitingQueryLayout=%s",
4201 this, vcView, pt, pt ? pt->x : 0, pt ? pt->y : 0,
4202 GetACPFromPointFlagName(dwFlags).get(), pacp,
4203 GetBoolName(mDeferNotifyingTSF), GetBoolName(mWaitingQueryLayout)));
4205 if (!IsReadLocked()) {
4206 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4207 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4208 "not locked (read)",
4209 this));
4210 return TS_E_NOLOCK;
4213 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4214 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4215 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4216 "called with invalid view",
4217 this));
4218 return E_INVALIDARG;
4221 if (!pt) {
4222 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4223 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4224 "null pt",
4225 this));
4226 return E_INVALIDARG;
4229 if (!pacp) {
4230 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4231 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4232 "null pacp",
4233 this));
4234 return E_INVALIDARG;
4237 mWaitingQueryLayout = false;
4239 if (mDestroyed || mContentForTSF.IsLayoutChanged()) {
4240 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4241 ("0x%p TSFTextStore::GetACPFromPoint() returned "
4242 "TS_E_NOLAYOUT",
4243 this));
4244 mHasReturnedNoLayoutError = true;
4245 return TS_E_NOLAYOUT;
4248 LayoutDeviceIntPoint ourPt(pt->x, pt->y);
4249 // Convert to widget relative coordinates from screen's.
4250 ourPt -= mWidget->WidgetToScreenOffset();
4252 // NOTE: Don't check if the point is in the widget since the point can be
4253 // outside of the widget if focused editor is in a XUL <panel>.
4255 WidgetQueryContentEvent charAtPt(true, eQueryCharacterAtPoint, mWidget);
4256 mWidget->InitEvent(charAtPt, &ourPt);
4258 // FYI: WidgetQueryContentEvent may cause flushing pending layout and it
4259 // may cause focus change or something.
4260 RefPtr<TSFTextStore> kungFuDeathGrip(this);
4261 DispatchEvent(charAtPt);
4262 if (!mWidget || mWidget->Destroyed()) {
4263 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4264 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4265 "mWidget was destroyed during eQueryCharacterAtPoint",
4266 this));
4267 return E_FAIL;
4270 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
4271 ("0x%p TSFTextStore::GetACPFromPoint(), charAtPt={ "
4272 "mSucceeded=%s, mReply={ mOffset=%u, mTentativeCaretOffset=%u }}",
4273 this, GetBoolName(charAtPt.mSucceeded), charAtPt.mReply.mOffset,
4274 charAtPt.mReply.mTentativeCaretOffset));
4276 if (NS_WARN_IF(!charAtPt.mSucceeded)) {
4277 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4278 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4279 "eQueryCharacterAtPoint failure",
4280 this));
4281 return E_FAIL;
4284 // If dwFlags isn't set and the point isn't in any character's bounding box,
4285 // we should return TS_E_INVALIDPOINT.
4286 if (!(dwFlags & GXFPF_NEAREST) &&
4287 charAtPt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND) {
4288 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4289 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to the "
4290 "point contained by no bounding box",
4291 this));
4292 return TS_E_INVALIDPOINT;
4295 // Although, we're not sure if mTentativeCaretOffset becomes NOT_FOUND,
4296 // let's assume that there is no content in such case.
4297 if (NS_WARN_IF(charAtPt.mReply.mTentativeCaretOffset ==
4298 WidgetQueryContentEvent::NOT_FOUND)) {
4299 charAtPt.mReply.mTentativeCaretOffset = 0;
4302 uint32_t offset;
4304 // If dwFlags includes GXFPF_ROUND_NEAREST, we should return tentative
4305 // caret offset (MSDN calls it "range position").
4306 if (dwFlags & GXFPF_ROUND_NEAREST) {
4307 offset = charAtPt.mReply.mTentativeCaretOffset;
4308 } else if (charAtPt.mReply.mOffset != WidgetQueryContentEvent::NOT_FOUND) {
4309 // Otherwise, we should return character offset whose bounding box contains
4310 // the point.
4311 offset = charAtPt.mReply.mOffset;
4312 } else {
4313 // If the point isn't in any character's bounding box but we need to return
4314 // the nearest character from the point, we should *guess* the character
4315 // offset since there is no inexpensive API to check it strictly.
4316 // XXX If we retrieve 2 bounding boxes, one is before the offset and
4317 // the other is after the offset, we could resolve the offset.
4318 // However, dispatching 2 eQueryTextRect may be expensive.
4320 // So, use tentative offset for now.
4321 offset = charAtPt.mReply.mTentativeCaretOffset;
4323 // However, if it's after the last character, we need to decrement the
4324 // offset.
4325 Content& contentForTSF = ContentForTSFRef();
4326 if (!contentForTSF.IsInitialized()) {
4327 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4328 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4329 "ContentForTSFRef() failure",
4330 this));
4331 return E_FAIL;
4333 if (contentForTSF.Text().Length() <= offset) {
4334 // If the tentative caret is after the last character, let's return
4335 // the last character's offset.
4336 offset = contentForTSF.Text().Length() - 1;
4340 if (NS_WARN_IF(offset > LONG_MAX)) {
4341 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4342 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to out of "
4343 "range of the result",
4344 this));
4345 return TS_E_INVALIDPOINT;
4348 *pacp = static_cast<LONG>(offset);
4349 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4350 ("0x%p TSFTextStore::GetACPFromPoint() succeeded: *pacp=%d", this,
4351 *pacp));
4352 return S_OK;
4355 STDMETHODIMP
4356 TSFTextStore::GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd,
4357 RECT* prc, BOOL* pfClipped) {
4358 MOZ_LOG(
4359 sTextStoreLog, LogLevel::Info,
4360 ("0x%p TSFTextStore::GetTextExt(vcView=%ld, "
4361 "acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p), "
4362 "IsHandlingComposition()=%s, "
4363 "mContentForTSF={ MinOffsetOfLayoutChanged()=%u, "
4364 "LatestCompositionStartOffset()=%d, LatestCompositionEndOffset()=%d }, "
4365 "mComposition= { IsComposing()=%s, mStart=%d, EndOffset()=%d }, "
4366 "mDeferNotifyingTSF=%s, mWaitingQueryLayout=%s, "
4367 "IMEHandler::IsA11yHandlingNativeCaret()=%s",
4368 this, vcView, acpStart, acpEnd, prc, pfClipped,
4369 GetBoolName(IsHandlingComposition()),
4370 mContentForTSF.MinOffsetOfLayoutChanged(),
4371 mContentForTSF.HasOrHadComposition()
4372 ? mContentForTSF.LatestCompositionStartOffset()
4373 : -1,
4374 mContentForTSF.HasOrHadComposition()
4375 ? mContentForTSF.LatestCompositionEndOffset()
4376 : -1,
4377 GetBoolName(mComposition.IsComposing()), mComposition.mStart,
4378 mComposition.EndOffset(), GetBoolName(mDeferNotifyingTSF),
4379 GetBoolName(mWaitingQueryLayout),
4380 GetBoolName(IMEHandler::IsA11yHandlingNativeCaret())));
4382 if (!IsReadLocked()) {
4383 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4384 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4385 "not locked (read)",
4386 this));
4387 return TS_E_NOLOCK;
4390 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4391 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4392 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4393 "called with invalid view",
4394 this));
4395 return E_INVALIDARG;
4398 if (!prc || !pfClipped) {
4399 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4400 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4401 "null argument",
4402 this));
4403 return E_INVALIDARG;
4406 // According to MSDN, ITextStoreACP::GetTextExt() should return
4407 // TS_E_INVALIDARG when acpStart and acpEnd are same (i.e., collapsed range).
4408 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms538435(v=vs.85).aspx
4409 // > TS_E_INVALIDARG: The specified start and end character positions are
4410 // > equal.
4411 // However, some TIPs (including Microsoft's Chinese TIPs!) call this with
4412 // collapsed range and if we return TS_E_INVALIDARG, they stops showing their
4413 // owning window or shows it but odd position. So, we should just return
4414 // error only when acpStart and/or acpEnd are really odd.
4416 if (acpStart < 0 || acpEnd < acpStart) {
4417 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4418 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4419 "invalid position",
4420 this));
4421 return TS_E_INVALIDPOS;
4424 mWaitingQueryLayout = false;
4426 if (IsHandlingComposition() && mContentForTSF.HasOrHadComposition() &&
4427 mContentForTSF.IsLayoutChanged() &&
4428 mContentForTSF.MinOffsetOfLayoutChanged() > LONG_MAX) {
4429 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4430 ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text "
4431 "is too big for TSF (cannot treat modified offset as LONG), "
4432 "mContentForTSF.MinOffsetOfLayoutChanged()=%u",
4433 this, mContentForTSF.MinOffsetOfLayoutChanged()));
4434 return E_FAIL;
4437 // At Windows 10 build 17643 (an insider preview for RS5), Microsoft fixed
4438 // the bug of TS_E_NOLAYOUT (even when we returned TS_E_NOLAYOUT, TSF
4439 // returned E_FAIL to TIP). However, until we drop to support older Windows
4440 // and all TIPs are aware of TS_E_NOLAYOUT result, we need to keep returning
4441 // S_OK and available rectangle only for them.
4442 if (!MaybeHackNoErrorLayoutBugs(acpStart, acpEnd) &&
4443 mContentForTSF.IsLayoutChangedAt(acpEnd)) {
4444 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4445 ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
4446 "(acpEnd=%d)",
4447 this, acpEnd));
4448 mHasReturnedNoLayoutError = true;
4449 return TS_E_NOLAYOUT;
4452 if (mDestroyed) {
4453 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4454 ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
4455 "(acpEnd=%d) because this has already been destroyed",
4456 this, acpEnd));
4457 mHasReturnedNoLayoutError = true;
4458 return TS_E_NOLAYOUT;
4461 // use eQueryTextRect to get rect in system, screen coordinates
4462 WidgetQueryContentEvent event(true, eQueryTextRect, mWidget);
4463 mWidget->InitEvent(event);
4465 WidgetQueryContentEvent::Options options;
4466 int64_t startOffset = acpStart;
4467 if (mComposition.IsComposing()) {
4468 // If there is a composition, TSF must want character rects related to
4469 // the composition. Therefore, we should use insertion point relative
4470 // query because the composition might be at different position from
4471 // the position where TSFTextStore believes it at.
4472 options.mRelativeToInsertionPoint = true;
4473 startOffset -= mComposition.mStart;
4474 } else if (IsHandlingComposition() && mContentForTSF.HasOrHadComposition()) {
4475 // If there was a composition and it hasn't been committed in the content
4476 // yet, ContentCacheInParent is still open for relative offset query from
4477 // the latest composition.
4478 options.mRelativeToInsertionPoint = true;
4479 startOffset -= mContentForTSF.LatestCompositionStartOffset();
4480 } else if (!CanAccessActualContentDirectly()) {
4481 // If TSF/TIP cannot access actual content directly, there may be pending
4482 // text and/or selection changes which have not been notified TSF yet.
4483 // Therefore, we should use relative to insertion point query since
4484 // TSF/TIP computes the offset from the cached selection.
4485 options.mRelativeToInsertionPoint = true;
4486 startOffset -= mSelectionForTSF.StartOffset();
4488 // ContentEventHandler and ContentCache return actual caret rect when
4489 // the queried range is collapsed and selection is collapsed at the
4490 // queried range. Then, its height (in horizontal layout, width in vertical
4491 // layout) may be different from actual font height of the line. In such
4492 // case, users see "dancing" of candidate or suggest window of TIP.
4493 // For preventing it, we should query text rect with at least 1 length.
4494 uint32_t length = std::max(static_cast<int32_t>(acpEnd - acpStart), 1);
4495 event.InitForQueryTextRect(startOffset, length, options);
4497 DispatchEvent(event);
4498 if (NS_WARN_IF(!event.mSucceeded)) {
4499 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4500 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4501 "eQueryTextRect failure",
4502 this));
4503 return TS_E_INVALIDPOS; // but unexpected failure, maybe.
4506 // IMEs don't like empty rects, fix here
4507 if (event.mReply.mRect.Width() <= 0) event.mReply.mRect.SetWidth(1);
4508 if (event.mReply.mRect.Height() <= 0) event.mReply.mRect.SetHeight(1);
4510 // convert to unclipped screen rect
4511 nsWindow* refWindow = static_cast<nsWindow*>(
4512 event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWidget);
4513 // Result rect is in top level widget coordinates
4514 refWindow = refWindow->GetTopLevelWindow(false);
4515 if (!refWindow) {
4516 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4517 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4518 "no top level window",
4519 this));
4520 return E_FAIL;
4523 event.mReply.mRect.MoveBy(refWindow->WidgetToScreenOffset());
4525 // get bounding screen rect to test for clipping
4526 if (!GetScreenExtInternal(*prc)) {
4527 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4528 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4529 "GetScreenExtInternal() failure",
4530 this));
4531 return E_FAIL;
4534 // clip text rect to bounding rect
4535 RECT textRect;
4536 ::SetRect(&textRect, event.mReply.mRect.X(), event.mReply.mRect.Y(),
4537 event.mReply.mRect.XMost(), event.mReply.mRect.YMost());
4538 if (!::IntersectRect(prc, prc, &textRect))
4539 // Text is not visible
4540 ::SetRectEmpty(prc);
4542 // not equal if text rect was clipped
4543 *pfClipped = !::EqualRect(prc, &textRect);
4545 // ATOK 2011 - 2016 refers native caret position and size on windows whose
4546 // class name is one of Mozilla's windows for deciding candidate window
4547 // position. Additionally, ATOK 2015 and earlier behaves really odd when
4548 // we don't create native caret. Therefore, we need to create native caret
4549 // only when ATOK 2011 - 2015 is active (i.e., not necessary for ATOK 2016).
4550 // However, if a11y module is handling native caret, we shouldn't touch it.
4551 // Note that ATOK must require the latest information of the caret. So,
4552 // even if we'll create native caret later, we need to creat it here with
4553 // current information.
4554 if (!IMEHandler::IsA11yHandlingNativeCaret() &&
4555 TSFPrefs::NeedToCreateNativeCaretForLegacyATOK() &&
4556 TSFStaticSink::IsATOKReferringNativeCaretActive() &&
4557 mComposition.IsComposing() && mComposition.mStart <= acpStart &&
4558 mComposition.EndOffset() >= acpStart && mComposition.mStart <= acpEnd &&
4559 mComposition.EndOffset() >= acpEnd) {
4560 CreateNativeCaret();
4563 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4564 ("0x%p TSFTextStore::GetTextExt() succeeded: "
4565 "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }, *pfClipped=%s",
4566 this, prc->left, prc->top, prc->right, prc->bottom,
4567 GetBoolName(*pfClipped)));
4569 return S_OK;
4572 bool TSFTextStore::MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd) {
4573 // When ITextStoreACP::GetTextExt() returns TS_E_NOLAYOUT, TSF returns E_FAIL
4574 // to its caller (typically, active TIP). Then, most TIPs abort current job
4575 // or treat such application as non-GUI apps. E.g., some of them give up
4576 // showing candidate window, some others show candidate window at top-left of
4577 // the screen. For avoiding this issue, when there is composition (until
4578 // composition is actually committed in remote content), we should not
4579 // return TS_E_NOLAYOUT error for TIPs whose some features are broken by
4580 // this issue.
4581 // Note that ideally, this issue should be avoided by each TIP since this
4582 // won't be fixed at least on non-latest Windows. Actually, Google Japanese
4583 // Input (based on Mozc) does it. When GetTextExt() returns E_FAIL, TIPs
4584 // should try to check result of GetRangeFromPoint() because TSF returns
4585 // TS_E_NOLAYOUT correctly in this case. See:
4586 // https://github.com/google/mozc/blob/6b878e31fb6ac4347dc9dfd8ccc1080fe718479f/src/win32/tip/tip_range_util.cc#L237-L257
4588 if (!IsHandlingComposition() || !mContentForTSF.HasOrHadComposition() ||
4589 !mContentForTSF.IsLayoutChangedAt(aACPEnd)) {
4590 return false;
4593 MOZ_ASSERT(!mComposition.IsComposing() ||
4594 mComposition.mStart ==
4595 mContentForTSF.LatestCompositionStartOffset());
4596 MOZ_ASSERT(!mComposition.IsComposing() ||
4597 mComposition.EndOffset() ==
4598 mContentForTSF.LatestCompositionEndOffset());
4600 // If TSF does not have the bug, we need to hack only with a few TIPs.
4601 static const bool sAlllowToStopHackingIfFine =
4602 IsWindows10BuildOrLater(17643) &&
4603 TSFPrefs::AllowToStopHackingOnBuild17643OrLater();
4605 // We need to compute active TIP now. This may take a couple of milliseconds,
4606 // however, it'll be cached, so, must be faster than check active TIP every
4607 // GetTextExt() calls.
4608 const Selection& selectionForTSF = SelectionForTSFRef();
4609 switch (TSFStaticSink::ActiveTIP()) {
4610 // MS IME for Japanese doesn't support asynchronous handling at deciding
4611 // its suggest list window position. The feature was implemented
4612 // starting from Windows 8. And also we may meet same trouble in e10s
4613 // mode on Win7. So, we should never return TS_E_NOLAYOUT to MS IME for
4614 // Japanese.
4615 case TextInputProcessorID::eMicrosoftIMEForJapanese:
4616 // Basically, MS-IME tries to retrieve whole composition string rect
4617 // at deciding suggest window immediately after unlocking the document.
4618 // However, in e10s mode, the content hasn't updated yet in most cases.
4619 // Therefore, if the first character at the retrieving range rect is
4620 // available, we should use it as the result.
4621 // Note that according to bug 1609675, MS-IME for Japanese itself does
4622 // not handle TS_E_NOLAYOUT correctly at least on Build 18363.657 (1909).
4623 if (TSFPrefs::DoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar() &&
4624 aACPStart < aACPEnd) {
4625 aACPEnd = aACPStart;
4626 break;
4628 if (sAlllowToStopHackingIfFine) {
4629 return false;
4631 // Although, the condition is not clear, MS-IME sometimes retrieves the
4632 // caret rect immediately after modifying the composition string but
4633 // before unlocking the document. In such case, we should return the
4634 // nearest character rect.
4635 if (TSFPrefs::DoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret() &&
4636 aACPStart == aACPEnd && selectionForTSF.IsCollapsed() &&
4637 selectionForTSF.EndOffset() == aACPEnd) {
4638 int32_t minOffsetOfLayoutChanged =
4639 static_cast<int32_t>(mContentForTSF.MinOffsetOfLayoutChanged());
4640 aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
4641 } else {
4642 return false;
4644 break;
4645 // The bug of Microsoft Office IME 2010 for Japanese is similar to
4646 // MS-IME for Win 8.1 and Win 10. Newer version of MS Office IME is not
4647 // released yet. So, we can hack it without prefs because there must be
4648 // no developers who want to disable this hack for tests.
4649 // XXX We have not tested with Microsoft Office IME 2010 since it's
4650 // installable only with Win7 and Win8 (i.e., cannot install Win8.1
4651 // and Win10), and requires upgrade to Win10.
4652 case TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese:
4653 // Basically, MS-IME tries to retrieve whole composition string rect
4654 // at deciding suggest window immediately after unlocking the document.
4655 // However, in e10s mode, the content hasn't updated yet in most cases.
4656 // Therefore, if the first character at the retrieving range rect is
4657 // available, we should use it as the result.
4658 if (aACPStart < aACPEnd) {
4659 aACPEnd = aACPStart;
4661 // Although, the condition is not clear, MS-IME sometimes retrieves the
4662 // caret rect immediately after modifying the composition string but
4663 // before unlocking the document. In such case, we should return the
4664 // nearest character rect.
4665 else if (aACPStart == aACPEnd && selectionForTSF.IsCollapsed() &&
4666 selectionForTSF.EndOffset() == aACPEnd) {
4667 int32_t minOffsetOfLayoutChanged =
4668 static_cast<int32_t>(mContentForTSF.MinOffsetOfLayoutChanged());
4669 aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
4670 } else {
4671 return false;
4673 break;
4674 // ATOK fails to handle TS_E_NOLAYOUT only when it decides the position of
4675 // suggest window. In such case, ATOK tries to query rect of whole or a
4676 // part of composition string.
4677 // FYI: ATOK changes their implementation around candidate window and
4678 // suggest widget at ATOK 2016. Therefore, there are some differences
4679 // ATOK 2015 (or older) and ATOK 2016 (or newer).
4680 // FYI: ATOK 2017 stops referring our window class name. I.e., ATOK 2016
4681 // and older may behave differently only on Gecko but this must be
4682 // finished from ATOK 2017.
4683 // FYI: For testing with legacy ATOK, we should hack it even if current ATOK
4684 // refers native caret rect on windows whose window class is one of
4685 // Mozilla window classes and we stop creating native caret for ATOK
4686 // because creating native caret causes ATOK refers caret position
4687 // when GetTextExt() returns TS_E_NOLAYOUT.
4688 case TextInputProcessorID::eATOK2011:
4689 case TextInputProcessorID::eATOK2012:
4690 case TextInputProcessorID::eATOK2013:
4691 case TextInputProcessorID::eATOK2014:
4692 case TextInputProcessorID::eATOK2015:
4693 // ATOK 2016 and later may temporarily show candidate window at odd
4694 // position when you convert a word quickly (e.g., keep pressing
4695 // space bar). So, on ATOK 2016 or later, we need to keep hacking the
4696 // result of GetTextExt().
4697 if (sAlllowToStopHackingIfFine) {
4698 return false;
4700 // If we'll create native caret where we paint our caret. Then, ATOK
4701 // will refer native caret. So, we don't need to hack anything in
4702 // this case.
4703 if (TSFPrefs::NeedToCreateNativeCaretForLegacyATOK()) {
4704 MOZ_ASSERT(TSFStaticSink::IsATOKReferringNativeCaretActive());
4705 return false;
4707 [[fallthrough]];
4708 case TextInputProcessorID::eATOK2016:
4709 case TextInputProcessorID::eATOKUnknown:
4710 if (!TSFPrefs::DoNotReturnNoLayoutErrorToATOKOfCompositionString()) {
4711 return false;
4713 // If the range is in the composition string, we should return rectangle
4714 // in it as far as possible.
4715 if (aACPStart < mContentForTSF.LatestCompositionStartOffset() ||
4716 aACPStart > mContentForTSF.LatestCompositionEndOffset() ||
4717 aACPEnd < mContentForTSF.LatestCompositionStartOffset() ||
4718 aACPEnd > mContentForTSF.LatestCompositionEndOffset()) {
4719 return false;
4721 break;
4722 // Japanist 10 fails to handle TS_E_NOLAYOUT when it decides the position
4723 // of candidate window. In such case, Japanist shows candidate window at
4724 // top-left of the screen. So, we should return the nearest caret rect
4725 // where we know. This is Japanist's bug. So, even after build 17643,
4726 // we need this hack.
4727 case TextInputProcessorID::eJapanist10:
4728 if (!TSFPrefs::
4729 DoNotReturnNoLayoutErrorToJapanist10OfCompositionString()) {
4730 return false;
4732 if (aACPStart < mContentForTSF.LatestCompositionStartOffset() ||
4733 aACPStart > mContentForTSF.LatestCompositionEndOffset() ||
4734 aACPEnd < mContentForTSF.LatestCompositionStartOffset() ||
4735 aACPEnd > mContentForTSF.LatestCompositionEndOffset()) {
4736 return false;
4738 break;
4739 // Free ChangJie 2010 doesn't handle ITfContextView::GetTextExt() properly.
4740 // This must be caused by the bug of TSF since Free ChangJie works fine on
4741 // build 17643 and later.
4742 case TextInputProcessorID::eFreeChangJie:
4743 if (sAlllowToStopHackingIfFine) {
4744 return false;
4746 if (!TSFPrefs::DoNotReturnNoLayoutErrorToFreeChangJie()) {
4747 return false;
4749 aACPEnd = mContentForTSF.LatestCompositionStartOffset();
4750 aACPStart = std::min(aACPStart, aACPEnd);
4751 break;
4752 // Some Traditional Chinese TIPs of Microsoft don't show candidate window
4753 // in e10s mode on Win8 or later.
4754 case TextInputProcessorID::eMicrosoftQuick:
4755 if (sAlllowToStopHackingIfFine) {
4756 return false; // MS Quick works fine with Win10 build 17643.
4758 [[fallthrough]];
4759 case TextInputProcessorID::eMicrosoftChangJie:
4760 if (!IsWin8OrLater() ||
4761 !TSFPrefs::DoNotReturnNoLayoutErrorToMSTraditionalTIP()) {
4762 return false;
4764 aACPEnd = mContentForTSF.LatestCompositionStartOffset();
4765 aACPStart = std::min(aACPStart, aACPEnd);
4766 break;
4767 // Some Simplified Chinese TIPs of Microsoft don't show candidate window
4768 // in e10s mode on Win8 or later.
4769 // FYI: Only Simplified Chinese TIPs of Microsoft still require this hack
4770 // because they sometimes do not show candidate window when we return
4771 // TS_E_NOLAYOUT for first query. Note that even when they show
4772 // candidate window properly, we return TS_E_NOLAYOUT and following
4773 // log looks same as when they don't show candidate window. Perhaps,
4774 // there is stateful cause or race in them.
4775 case TextInputProcessorID::eMicrosoftPinyin:
4776 case TextInputProcessorID::eMicrosoftWubi:
4777 if (!IsWin8OrLater() ||
4778 !TSFPrefs::DoNotReturnNoLayoutErrorToMSSimplifiedTIP()) {
4779 return false;
4781 aACPEnd = mContentForTSF.LatestCompositionStartOffset();
4782 aACPStart = std::min(aACPStart, aACPEnd);
4783 break;
4784 default:
4785 return false;
4788 // If we hack the queried range for active TIP, that means we should not
4789 // return TS_E_NOLAYOUT even if hacked offset is still modified. So, as
4790 // far as possible, we should adjust the offset.
4791 MOZ_ASSERT(mContentForTSF.IsLayoutChanged());
4792 bool collapsed = aACPStart == aACPEnd;
4793 // Note that even if all characters in the editor or the composition
4794 // string was modified, 0 or start offset of the composition string is
4795 // useful because it may return caret rect or old character's rect which
4796 // the user still see. That must be useful information for TIP.
4797 int32_t firstModifiedOffset =
4798 static_cast<int32_t>(mContentForTSF.MinOffsetOfLayoutChanged());
4799 LONG lastUnmodifiedOffset = std::max(firstModifiedOffset - 1, 0);
4800 if (mContentForTSF.IsLayoutChangedAt(aACPStart)) {
4801 if (aACPStart >= mContentForTSF.LatestCompositionStartOffset()) {
4802 // If mContentForTSF has last composition string and current
4803 // composition string, we can assume that ContentCacheInParent has
4804 // cached rects of composition string at least length of current
4805 // composition string. Otherwise, we can assume that rect for
4806 // first character of composition string is stored since it was
4807 // selection start or caret position.
4808 LONG maxCachedOffset = mContentForTSF.LatestCompositionEndOffset();
4809 if (mContentForTSF.WasLastComposition()) {
4810 maxCachedOffset = std::min(
4811 maxCachedOffset, mContentForTSF.LastCompositionStringEndOffset());
4813 aACPStart = std::min(aACPStart, maxCachedOffset);
4815 // Otherwise, we don't know which character rects are cached. So, we
4816 // need to use first unmodified character's rect in this case. Even
4817 // if there is no character, the query event will return caret rect
4818 // instead.
4819 else {
4820 aACPStart = lastUnmodifiedOffset;
4822 MOZ_ASSERT(aACPStart <= aACPEnd);
4825 // If TIP requests caret rect with collapsed range, we should keep
4826 // collapsing the range.
4827 if (collapsed) {
4828 aACPEnd = aACPStart;
4830 // Let's set aACPEnd to larger offset of last unmodified offset or
4831 // aACPStart which may be the first character offset of the composition
4832 // string. However, some TIPs may want to know the right edge of the
4833 // range. Therefore, if aACPEnd is in composition string and active TIP
4834 // doesn't retrieve caret rect (i.e., the range isn't collapsed), we
4835 // should keep using the original aACPEnd. Otherwise, we should set
4836 // aACPEnd to larger value of aACPStart and lastUnmodifiedOffset.
4837 else if (mContentForTSF.IsLayoutChangedAt(aACPEnd) &&
4838 (aACPEnd < mContentForTSF.LatestCompositionStartOffset() ||
4839 aACPEnd > mContentForTSF.LatestCompositionEndOffset())) {
4840 aACPEnd = std::max(aACPStart, lastUnmodifiedOffset);
4843 MOZ_LOG(
4844 sTextStoreLog, LogLevel::Debug,
4845 ("0x%p TSFTextStore::HackNoErrorLayoutBugs() hacked the queried range "
4846 "for not returning TS_E_NOLAYOUT, new values are: "
4847 "aACPStart=%d, aACPEnd=%d",
4848 this, aACPStart, aACPEnd));
4850 return true;
4853 STDMETHODIMP
4854 TSFTextStore::GetScreenExt(TsViewCookie vcView, RECT* prc) {
4855 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4856 ("0x%p TSFTextStore::GetScreenExt(vcView=%ld, prc=0x%p)", this,
4857 vcView, prc));
4859 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4860 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4861 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4862 "called with invalid view",
4863 this));
4864 return E_INVALIDARG;
4867 if (!prc) {
4868 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4869 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4870 "null argument",
4871 this));
4872 return E_INVALIDARG;
4875 if (mDestroyed) {
4876 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4877 ("0x%p TSFTextStore::GetScreenExt() returns empty rect "
4878 "due to already destroyed",
4879 this));
4880 prc->left = prc->top = prc->right = prc->bottom = 0;
4881 return S_OK;
4884 if (!GetScreenExtInternal(*prc)) {
4885 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4886 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4887 "GetScreenExtInternal() failure",
4888 this));
4889 return E_FAIL;
4892 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4893 ("0x%p TSFTextStore::GetScreenExt() succeeded: "
4894 "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
4895 this, prc->left, prc->top, prc->right, prc->bottom));
4896 return S_OK;
4899 bool TSFTextStore::GetScreenExtInternal(RECT& aScreenExt) {
4900 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
4901 ("0x%p TSFTextStore::GetScreenExtInternal()", this));
4903 MOZ_ASSERT(!mDestroyed);
4905 // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
4906 WidgetQueryContentEvent event(true, eQueryEditorRect, mWidget);
4907 mWidget->InitEvent(event);
4908 DispatchEvent(event);
4909 if (!event.mSucceeded) {
4910 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4911 ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
4912 "eQueryEditorRect failure",
4913 this));
4914 return false;
4917 nsWindow* refWindow = static_cast<nsWindow*>(
4918 event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWidget);
4919 // Result rect is in top level widget coordinates
4920 refWindow = refWindow->GetTopLevelWindow(false);
4921 if (!refWindow) {
4922 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4923 ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
4924 "no top level window",
4925 this));
4926 return false;
4929 LayoutDeviceIntRect boundRect = refWindow->GetClientBounds();
4930 boundRect.MoveTo(0, 0);
4932 // Clip frame rect to window rect
4933 boundRect.IntersectRect(event.mReply.mRect, boundRect);
4934 if (!boundRect.IsEmpty()) {
4935 boundRect.MoveBy(refWindow->WidgetToScreenOffset());
4936 ::SetRect(&aScreenExt, boundRect.X(), boundRect.Y(), boundRect.XMost(),
4937 boundRect.YMost());
4938 } else {
4939 ::SetRectEmpty(&aScreenExt);
4942 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
4943 ("0x%p TSFTextStore::GetScreenExtInternal() succeeded: "
4944 "aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
4945 this, aScreenExt.left, aScreenExt.top, aScreenExt.right,
4946 aScreenExt.bottom));
4947 return true;
4950 STDMETHODIMP
4951 TSFTextStore::GetWnd(TsViewCookie vcView, HWND* phwnd) {
4952 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4953 ("0x%p TSFTextStore::GetWnd(vcView=%ld, phwnd=0x%p), "
4954 "mWidget=0x%p",
4955 this, vcView, phwnd, mWidget.get()));
4957 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4958 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4959 ("0x%p TSFTextStore::GetWnd() FAILED due to "
4960 "called with invalid view",
4961 this));
4962 return E_INVALIDARG;
4965 if (!phwnd) {
4966 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4967 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4968 "null argument",
4969 this));
4970 return E_INVALIDARG;
4973 *phwnd = mWidget ? mWidget->GetWindowHandle() : nullptr;
4975 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4976 ("0x%p TSFTextStore::GetWnd() succeeded: *phwnd=0x%p", this,
4977 static_cast<void*>(*phwnd)));
4978 return S_OK;
4981 STDMETHODIMP
4982 TSFTextStore::InsertTextAtSelection(DWORD dwFlags, const WCHAR* pchText,
4983 ULONG cch, LONG* pacpStart, LONG* pacpEnd,
4984 TS_TEXTCHANGE* pChange) {
4985 MOZ_LOG(
4986 sTextStoreLog, LogLevel::Info,
4987 ("0x%p TSFTextStore::InsertTextAtSelection(dwFlags=%s, "
4988 "pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, "
4989 "pChange=0x%p), IsComposing()=%s",
4990 this,
4991 dwFlags == 0
4992 ? "0"
4993 : dwFlags == TF_IAS_NOQUERY
4994 ? "TF_IAS_NOQUERY"
4995 : dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY" : "Unknown",
4996 pchText, pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "",
4997 cch, pacpStart, pacpEnd, pChange,
4998 GetBoolName(mComposition.IsComposing())));
5000 if (cch && !pchText) {
5001 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5002 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5003 "null pchText",
5004 this));
5005 return E_INVALIDARG;
5008 if (TS_IAS_QUERYONLY == dwFlags) {
5009 if (!IsReadLocked()) {
5010 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5011 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5012 "not locked (read)",
5013 this));
5014 return TS_E_NOLOCK;
5017 if (!pacpStart || !pacpEnd) {
5018 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5019 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5020 "null argument",
5021 this));
5022 return E_INVALIDARG;
5025 // Get selection first
5026 Selection& selectionForTSF = SelectionForTSFRef();
5027 if (selectionForTSF.IsDirty()) {
5028 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5029 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5030 "SelectionForTSFRef() failure",
5031 this));
5032 return E_FAIL;
5035 // Simulate text insertion
5036 *pacpStart = selectionForTSF.StartOffset();
5037 *pacpEnd = selectionForTSF.EndOffset();
5038 if (pChange) {
5039 pChange->acpStart = selectionForTSF.StartOffset();
5040 pChange->acpOldEnd = selectionForTSF.EndOffset();
5041 pChange->acpNewEnd =
5042 selectionForTSF.StartOffset() + static_cast<LONG>(cch);
5044 } else {
5045 if (!IsReadWriteLocked()) {
5046 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5047 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5048 "not locked (read-write)",
5049 this));
5050 return TS_E_NOLOCK;
5053 if (!pChange) {
5054 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5055 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5056 "null pChange",
5057 this));
5058 return E_INVALIDARG;
5061 if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) {
5062 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5063 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5064 "null argument",
5065 this));
5066 return E_INVALIDARG;
5069 if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
5070 pChange)) {
5071 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5072 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5073 "InsertTextAtSelectionInternal() failure",
5074 this));
5075 return E_FAIL;
5078 if (TS_IAS_NOQUERY != dwFlags) {
5079 *pacpStart = pChange->acpStart;
5080 *pacpEnd = pChange->acpNewEnd;
5083 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5084 ("0x%p TSFTextStore::InsertTextAtSelection() succeeded: "
5085 "*pacpStart=%ld, *pacpEnd=%ld, "
5086 "*pChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld })",
5087 this, pacpStart ? *pacpStart : 0, pacpEnd ? *pacpEnd : 0,
5088 pChange ? pChange->acpStart : 0, pChange ? pChange->acpOldEnd : 0,
5089 pChange ? pChange->acpNewEnd : 0));
5090 return S_OK;
5093 bool TSFTextStore::InsertTextAtSelectionInternal(const nsAString& aInsertStr,
5094 TS_TEXTCHANGE* aTextChange) {
5095 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5096 ("0x%p TSFTextStore::InsertTextAtSelectionInternal("
5097 "aInsertStr=\"%s\", aTextChange=0x%p), IsComposing=%s",
5098 this, GetEscapedUTF8String(aInsertStr).get(), aTextChange,
5099 GetBoolName(mComposition.IsComposing())));
5101 Content& contentForTSF = ContentForTSFRef();
5102 if (!contentForTSF.IsInitialized()) {
5103 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5104 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() failed "
5105 "due to ContentForTSFRef() failure()",
5106 this));
5107 return false;
5110 MaybeDispatchKeyboardEventAsProcessedByIME();
5111 if (mDestroyed) {
5112 MOZ_LOG(
5113 sTextStoreLog, LogLevel::Error,
5114 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() FAILED due to "
5115 "destroyed during dispatching a keyboard event",
5116 this));
5117 return false;
5120 TS_SELECTION_ACP oldSelection = contentForTSF.Selection().ACP();
5121 if (!mComposition.IsComposing()) {
5122 // Use a temporary composition to contain the text
5123 PendingAction* compositionStart = mPendingActions.AppendElements(2);
5124 PendingAction* compositionEnd = compositionStart + 1;
5126 compositionStart->mType = PendingAction::Type::eCompositionStart;
5127 compositionStart->mSelectionStart = oldSelection.acpStart;
5128 compositionStart->mSelectionLength =
5129 oldSelection.acpEnd - oldSelection.acpStart;
5130 compositionStart->mAdjustSelection = false;
5132 compositionEnd->mType = PendingAction::Type::eCompositionEnd;
5133 compositionEnd->mData = aInsertStr;
5134 compositionEnd->mSelectionStart = compositionStart->mSelectionStart;
5136 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5137 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
5138 "appending pending compositionstart and compositionend... "
5139 "PendingCompositionStart={ mSelectionStart=%d, "
5140 "mSelectionLength=%d }, PendingCompositionEnd={ mData=\"%s\" "
5141 "(Length()=%u), mSelectionStart=%d }",
5142 this, compositionStart->mSelectionStart,
5143 compositionStart->mSelectionLength,
5144 GetEscapedUTF8String(compositionEnd->mData).get(),
5145 compositionEnd->mData.Length(), compositionEnd->mSelectionStart));
5148 contentForTSF.ReplaceSelectedTextWith(aInsertStr);
5150 if (aTextChange) {
5151 aTextChange->acpStart = oldSelection.acpStart;
5152 aTextChange->acpOldEnd = oldSelection.acpEnd;
5153 aTextChange->acpNewEnd = contentForTSF.Selection().EndOffset();
5156 MOZ_LOG(
5157 sTextStoreLog, LogLevel::Debug,
5158 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
5159 "succeeded: mWidget=0x%p, mWidget->Destroyed()=%s, aTextChange={ "
5160 "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
5161 this, mWidget.get(), GetBoolName(mWidget ? mWidget->Destroyed() : true),
5162 aTextChange ? aTextChange->acpStart : 0,
5163 aTextChange ? aTextChange->acpOldEnd : 0,
5164 aTextChange ? aTextChange->acpNewEnd : 0));
5165 return true;
5168 STDMETHODIMP
5169 TSFTextStore::InsertEmbeddedAtSelection(DWORD dwFlags, IDataObject* pDataObject,
5170 LONG* pacpStart, LONG* pacpEnd,
5171 TS_TEXTCHANGE* pChange) {
5172 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5173 ("0x%p TSFTextStore::InsertEmbeddedAtSelection() called "
5174 "but not supported (E_NOTIMPL)",
5175 this));
5177 // embedded objects are not supported
5178 return E_NOTIMPL;
5181 HRESULT
5182 TSFTextStore::RecordCompositionStartAction(ITfCompositionView* aComposition,
5183 ITfRange* aRange,
5184 bool aPreserveSelection) {
5185 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5186 ("0x%p TSFTextStore::RecordCompositionStartAction("
5187 "aComposition=0x%p, aRange=0x%p, aPreserveSelection=%s), "
5188 "mComposition.mView=0x%p",
5189 this, aComposition, aRange, GetBoolName(aPreserveSelection),
5190 mComposition.mView.get()));
5192 LONG start = 0, length = 0;
5193 HRESULT hr = GetRangeExtent(aRange, &start, &length);
5194 if (FAILED(hr)) {
5195 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5196 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5197 "due to GetRangeExtent() failure",
5198 this));
5199 return hr;
5202 return RecordCompositionStartAction(aComposition, start, length,
5203 aPreserveSelection);
5206 HRESULT
5207 TSFTextStore::RecordCompositionStartAction(ITfCompositionView* aComposition,
5208 LONG aStart, LONG aLength,
5209 bool aPreserveSelection) {
5210 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5211 ("0x%p TSFTextStore::RecordCompositionStartAction("
5212 "aComposition=0x%p, aStart=%d, aLength=%d, aPreserveSelection=%s), "
5213 "mComposition.mView=0x%p",
5214 this, aComposition, aStart, aLength, GetBoolName(aPreserveSelection),
5215 mComposition.mView.get()));
5217 Content& contentForTSF = ContentForTSFRef();
5218 if (!contentForTSF.IsInitialized()) {
5219 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5220 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5221 "due to ContentForTSFRef() failure",
5222 this));
5223 return E_FAIL;
5226 MaybeDispatchKeyboardEventAsProcessedByIME();
5227 if (mDestroyed) {
5228 MOZ_LOG(
5229 sTextStoreLog, LogLevel::Error,
5230 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED due to "
5231 "destroyed during dispatching a keyboard event",
5232 this));
5233 return false;
5236 CompleteLastActionIfStillIncomplete();
5238 // TIP may have inserted text at selection before calling
5239 // OnStartComposition(). In this case, we've already created a pending
5240 // compositionend. If new composition replaces all commit string of the
5241 // pending compositionend, we should cancel the pending compositionend and
5242 // keep the previous composition normally.
5243 // On Windows 7, MS-IME for Korean, MS-IME 2010 for Korean and MS Old Hangul
5244 // may start composition with calling InsertTextAtSelection() and
5245 // OnStartComposition() with this order (bug 1208043).
5246 // On Windows 10, MS Pinyin, MS Wubi, MS ChangJie and MS Quick commits
5247 // last character and replace it with empty string with new composition
5248 // when user removes last character of composition string with Backspace
5249 // key (bug 1462257).
5250 if (!aPreserveSelection &&
5251 IsLastPendingActionCompositionEndAt(aStart, aLength)) {
5252 const PendingAction& pendingCompositionEnd = mPendingActions.LastElement();
5253 contentForTSF.RestoreCommittedComposition(aComposition,
5254 pendingCompositionEnd);
5255 mPendingActions.RemoveLastElement();
5256 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5257 ("0x%p TSFTextStore::RecordCompositionStartAction() "
5258 "succeeded: restoring the committed string as composing string, "
5259 "mComposition={ mStart=%ld, mString.Length()=%ld, "
5260 "mSelectionForTSF={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
5261 "style.fInterimChar=%s } }",
5262 this, mComposition.mStart, mComposition.mString.Length(),
5263 mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
5264 GetActiveSelEndName(mSelectionForTSF.ActiveSelEnd()),
5265 GetBoolName(mSelectionForTSF.IsInterimChar())));
5266 return S_OK;
5269 PendingAction* action = mPendingActions.AppendElement();
5270 action->mType = PendingAction::Type::eCompositionStart;
5271 action->mSelectionStart = aStart;
5272 action->mSelectionLength = aLength;
5274 Selection& selectionForTSF = SelectionForTSFRef();
5275 if (selectionForTSF.IsDirty()) {
5276 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5277 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5278 "due to SelectionForTSFRef() failure",
5279 this));
5280 action->mAdjustSelection = true;
5281 } else if (selectionForTSF.MinOffset() != aStart ||
5282 selectionForTSF.MaxOffset() != aStart + aLength) {
5283 // If new composition range is different from current selection range,
5284 // we need to set selection before dispatching compositionstart event.
5285 action->mAdjustSelection = true;
5286 } else {
5287 // We shouldn't dispatch selection set event before dispatching
5288 // compositionstart event because it may cause put caret different
5289 // position in HTML editor since generated flat text content and offset in
5290 // it are lossy data of HTML contents.
5291 action->mAdjustSelection = false;
5294 contentForTSF.StartComposition(aComposition, *action, aPreserveSelection);
5295 action->mData = mComposition.mString;
5297 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5298 ("0x%p TSFTextStore::RecordCompositionStartAction() succeeded: "
5299 "mComposition={ mStart=%ld, mString.Length()=%ld, "
5300 "mSelectionForTSF={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
5301 "style.fInterimChar=%s } }",
5302 this, mComposition.mStart, mComposition.mString.Length(),
5303 mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
5304 GetActiveSelEndName(mSelectionForTSF.ActiveSelEnd()),
5305 GetBoolName(mSelectionForTSF.IsInterimChar())));
5306 return S_OK;
5309 HRESULT
5310 TSFTextStore::RecordCompositionEndAction() {
5311 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5312 ("0x%p TSFTextStore::RecordCompositionEndAction(), "
5313 "mComposition={ mView=0x%p, mString=\"%s\" }",
5314 this, mComposition.mView.get(),
5315 GetEscapedUTF8String(mComposition.mString).get()));
5317 MOZ_ASSERT(mComposition.IsComposing());
5319 MaybeDispatchKeyboardEventAsProcessedByIME();
5320 if (mDestroyed) {
5321 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5322 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
5323 "destroyed during dispatching a keyboard event",
5324 this));
5325 return false;
5328 // If we're handling incomplete composition update or already handled
5329 // composition update, we can forget them since composition end will send
5330 // the latest composition string and it overwrites the composition string
5331 // even if we dispatch eCompositionChange event before that. So, let's
5332 // forget all composition updates now.
5333 RemoveLastCompositionUpdateActions();
5334 PendingAction* action = mPendingActions.AppendElement();
5335 action->mType = PendingAction::Type::eCompositionEnd;
5336 action->mData = mComposition.mString;
5337 action->mSelectionStart = mComposition.mStart;
5339 Content& contentForTSF = ContentForTSFRef();
5340 if (!contentForTSF.IsInitialized()) {
5341 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5342 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due "
5343 "to ContentForTSFRef() failure",
5344 this));
5345 return E_FAIL;
5347 contentForTSF.EndComposition(*action);
5349 // If this composition was restart but the composition doesn't modify
5350 // anything, we should remove the pending composition for preventing to
5351 // dispatch redundant composition events.
5352 for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) {
5353 PendingAction& pendingAction = mPendingActions[i - 1];
5354 if (pendingAction.mType == PendingAction::Type::eCompositionStart) {
5355 if (pendingAction.mData != action->mData) {
5356 break;
5358 // When only setting selection is necessary, we should append it.
5359 if (pendingAction.mAdjustSelection) {
5360 LONG selectionStart = pendingAction.mSelectionStart;
5361 LONG selectionLength = pendingAction.mSelectionLength;
5363 PendingAction* setSelection = mPendingActions.AppendElement();
5364 setSelection->mType = PendingAction::Type::eSetSelection;
5365 setSelection->mSelectionStart = selectionStart;
5366 setSelection->mSelectionLength = selectionLength;
5367 setSelection->mSelectionReversed = false;
5369 // Remove the redundant pending composition.
5370 mPendingActions.RemoveElementsAt(i - 1, j);
5371 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5372 ("0x%p TSFTextStore::RecordCompositionEndAction(), "
5373 "succeeded, but the composition was canceled due to redundant",
5374 this));
5375 return S_OK;
5379 MOZ_LOG(
5380 sTextStoreLog, LogLevel::Info,
5381 ("0x%p TSFTextStore::RecordCompositionEndAction(), succeeded", this));
5382 return S_OK;
5385 STDMETHODIMP
5386 TSFTextStore::OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) {
5387 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5388 ("0x%p TSFTextStore::OnStartComposition(pComposition=0x%p, "
5389 "pfOk=0x%p), mComposition.mView=0x%p",
5390 this, pComposition, pfOk, mComposition.mView.get()));
5392 AutoPendingActionAndContentFlusher flusher(this);
5394 *pfOk = FALSE;
5396 // Only one composition at a time
5397 if (mComposition.IsComposing()) {
5398 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5399 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5400 "there is another composition already (but returns S_OK)",
5401 this));
5402 return S_OK;
5405 RefPtr<ITfRange> range;
5406 HRESULT hr = pComposition->GetRange(getter_AddRefs(range));
5407 if (FAILED(hr)) {
5408 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5409 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5410 "pComposition->GetRange() failure",
5411 this));
5412 return hr;
5414 hr = RecordCompositionStartAction(pComposition, range, false);
5415 if (FAILED(hr)) {
5416 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5417 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5418 "RecordCompositionStartAction() failure",
5419 this));
5420 return hr;
5423 *pfOk = TRUE;
5424 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5425 ("0x%p TSFTextStore::OnStartComposition() succeeded", this));
5426 return S_OK;
5429 STDMETHODIMP
5430 TSFTextStore::OnUpdateComposition(ITfCompositionView* pComposition,
5431 ITfRange* pRangeNew) {
5432 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5433 ("0x%p TSFTextStore::OnUpdateComposition(pComposition=0x%p, "
5434 "pRangeNew=0x%p), mComposition.mView=0x%p",
5435 this, pComposition, pRangeNew, mComposition.mView.get()));
5437 AutoPendingActionAndContentFlusher flusher(this);
5439 if (!mDocumentMgr || !mContext) {
5440 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5441 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5442 "not ready for the composition",
5443 this));
5444 return E_UNEXPECTED;
5446 if (!mComposition.IsComposing()) {
5447 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5448 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5449 "no active composition",
5450 this));
5451 return E_UNEXPECTED;
5453 if (mComposition.mView != pComposition) {
5454 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5455 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5456 "different composition view specified",
5457 this));
5458 return E_UNEXPECTED;
5461 // pRangeNew is null when the update is not complete
5462 if (!pRangeNew) {
5463 MaybeDispatchKeyboardEventAsProcessedByIME();
5464 if (mDestroyed) {
5465 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5466 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5467 "destroyed during dispatching a keyboard event",
5468 this));
5469 return E_FAIL;
5471 PendingAction* action = LastOrNewPendingCompositionUpdate();
5472 action->mIncomplete = true;
5473 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5474 ("0x%p TSFTextStore::OnUpdateComposition() succeeded but "
5475 "not complete",
5476 this));
5477 return S_OK;
5480 HRESULT hr = RestartCompositionIfNecessary(pRangeNew);
5481 if (FAILED(hr)) {
5482 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5483 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5484 "RestartCompositionIfNecessary() failure",
5485 this));
5486 return hr;
5489 hr = RecordCompositionUpdateAction();
5490 if (FAILED(hr)) {
5491 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5492 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5493 "RecordCompositionUpdateAction() failure",
5494 this));
5495 return hr;
5498 if (MOZ_LOG_TEST(sTextStoreLog, LogLevel::Info)) {
5499 Selection& selectionForTSF = SelectionForTSFRef();
5500 if (selectionForTSF.IsDirty()) {
5501 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5502 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5503 "SelectionForTSFRef() failure",
5504 this));
5505 return E_FAIL;
5507 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5508 ("0x%p TSFTextStore::OnUpdateComposition() succeeded: "
5509 "mComposition={ mStart=%ld, mString=\"%s\" }, "
5510 "SelectionForTSFRef()={ acpStart=%ld, acpEnd=%ld, style.ase=%s }",
5511 this, mComposition.mStart,
5512 GetEscapedUTF8String(mComposition.mString).get(),
5513 selectionForTSF.StartOffset(), selectionForTSF.EndOffset(),
5514 GetActiveSelEndName(selectionForTSF.ActiveSelEnd())));
5516 return S_OK;
5519 STDMETHODIMP
5520 TSFTextStore::OnEndComposition(ITfCompositionView* pComposition) {
5521 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5522 ("0x%p TSFTextStore::OnEndComposition(pComposition=0x%p), "
5523 "mComposition={ mView=0x%p, mString=\"%s\" }",
5524 this, pComposition, mComposition.mView.get(),
5525 GetEscapedUTF8String(mComposition.mString).get()));
5527 AutoPendingActionAndContentFlusher flusher(this);
5529 if (!mComposition.IsComposing()) {
5530 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5531 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5532 "no active composition",
5533 this));
5534 return E_UNEXPECTED;
5537 if (mComposition.mView != pComposition) {
5538 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5539 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5540 "different composition view specified",
5541 this));
5542 return E_UNEXPECTED;
5545 HRESULT hr = RecordCompositionEndAction();
5546 if (FAILED(hr)) {
5547 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5548 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5549 "RecordCompositionEndAction() failure",
5550 this));
5551 return hr;
5554 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5555 ("0x%p TSFTextStore::OnEndComposition(), succeeded", this));
5556 return S_OK;
5559 STDMETHODIMP
5560 TSFTextStore::AdviseMouseSink(ITfRangeACP* range, ITfMouseSink* pSink,
5561 DWORD* pdwCookie) {
5562 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5563 ("0x%p TSFTextStore::AdviseMouseSink(range=0x%p, pSink=0x%p, "
5564 "pdwCookie=0x%p)",
5565 this, range, pSink, pdwCookie));
5567 if (!pdwCookie) {
5568 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5569 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5570 "pdwCookie is null",
5571 this));
5572 return E_INVALIDARG;
5574 // Initialize the result with invalid cookie for safety.
5575 *pdwCookie = MouseTracker::kInvalidCookie;
5577 if (!range) {
5578 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5579 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5580 "range is null",
5581 this));
5582 return E_INVALIDARG;
5584 if (!pSink) {
5585 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5586 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5587 "pSink is null",
5588 this));
5589 return E_INVALIDARG;
5592 // Looking for an unusing tracker.
5593 MouseTracker* tracker = nullptr;
5594 for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
5595 if (mMouseTrackers[i].IsUsing()) {
5596 continue;
5598 tracker = &mMouseTrackers[i];
5600 // If there is no unusing tracker, create new one.
5601 // XXX Should we make limitation of the number of installs?
5602 if (!tracker) {
5603 tracker = mMouseTrackers.AppendElement();
5604 HRESULT hr = tracker->Init(this);
5605 if (FAILED(hr)) {
5606 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5607 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to "
5608 "failure of MouseTracker::Init()",
5609 this));
5610 return hr;
5613 HRESULT hr = tracker->AdviseSink(this, range, pSink);
5614 if (FAILED(hr)) {
5615 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5616 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to failure "
5617 "of MouseTracker::Init()",
5618 this));
5619 return hr;
5621 *pdwCookie = tracker->Cookie();
5622 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5623 ("0x%p TSFTextStore::AdviseMouseSink(), succeeded, "
5624 "*pdwCookie=%d",
5625 this, *pdwCookie));
5626 return S_OK;
5629 STDMETHODIMP
5630 TSFTextStore::UnadviseMouseSink(DWORD dwCookie) {
5631 MOZ_LOG(
5632 sTextStoreLog, LogLevel::Info,
5633 ("0x%p TSFTextStore::UnadviseMouseSink(dwCookie=%d)", this, dwCookie));
5634 if (dwCookie == MouseTracker::kInvalidCookie) {
5635 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5636 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5637 "the cookie is invalid value",
5638 this));
5639 return E_INVALIDARG;
5641 // The cookie value must be an index of mMouseTrackers.
5642 // We can use this shortcut for now.
5643 if (static_cast<size_t>(dwCookie) >= mMouseTrackers.Length()) {
5644 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5645 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5646 "the cookie is too large value",
5647 this));
5648 return E_INVALIDARG;
5650 MouseTracker& tracker = mMouseTrackers[dwCookie];
5651 if (!tracker.IsUsing()) {
5652 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5653 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5654 "the found tracker uninstalled already",
5655 this));
5656 return E_INVALIDARG;
5658 tracker.UnadviseSink();
5659 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5660 ("0x%p TSFTextStore::UnadviseMouseSink(), succeeded", this));
5661 return S_OK;
5664 // static
5665 nsresult TSFTextStore::OnFocusChange(bool aGotFocus,
5666 nsWindowBase* aFocusedWidget,
5667 const InputContext& aContext) {
5668 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5669 (" TSFTextStore::OnFocusChange(aGotFocus=%s, "
5670 "aFocusedWidget=0x%p, aContext=%s), "
5671 "sThreadMgr=0x%p, sEnabledTextStore=0x%p",
5672 GetBoolName(aGotFocus), aFocusedWidget,
5673 mozilla::ToString(aContext).c_str(), sThreadMgr.get(),
5674 sEnabledTextStore.get()));
5676 if (NS_WARN_IF(!IsInTSFMode())) {
5677 return NS_ERROR_NOT_AVAILABLE;
5680 RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
5681 bool hasFocus = ThinksHavingFocus();
5682 RefPtr<TSFTextStore> oldTextStore = sEnabledTextStore.forget();
5684 // If currently oldTextStore still has focus, notifies TSF of losing focus.
5685 if (hasFocus) {
5686 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5687 DebugOnly<HRESULT> hr = threadMgr->AssociateFocus(
5688 oldTextStore->mWidget->GetWindowHandle(), nullptr,
5689 getter_AddRefs(prevFocusedDocumentMgr));
5690 NS_ASSERTION(SUCCEEDED(hr), "Disassociating focus failed");
5691 NS_ASSERTION(prevFocusedDocumentMgr == oldTextStore->mDocumentMgr,
5692 "different documentMgr has been associated with the window");
5695 // Even if there was a focused TextStore, we won't use it with new focused
5696 // editor. So, release it now.
5697 if (oldTextStore) {
5698 oldTextStore->Destroy();
5701 if (NS_WARN_IF(!sThreadMgr)) {
5702 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5703 (" TSFTextStore::OnFocusChange() FAILED, due to "
5704 "sThreadMgr being destroyed during calling "
5705 "ITfThreadMgr::AssociateFocus()"));
5706 return NS_ERROR_FAILURE;
5708 if (NS_WARN_IF(sEnabledTextStore)) {
5709 MOZ_LOG(
5710 sTextStoreLog, LogLevel::Error,
5711 (" TSFTextStore::OnFocusChange() FAILED, due to "
5712 "nested event handling has created another focused TextStore during "
5713 "calling ITfThreadMgr::AssociateFocus()"));
5714 return NS_ERROR_FAILURE;
5717 // If this is a notification of blur, move focus to the dummy document
5718 // manager.
5719 if (!aGotFocus || !aContext.mIMEState.IsEditable()) {
5720 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5721 RefPtr<ITfDocumentMgr> disabledDocumentMgr = sDisabledDocumentMgr;
5722 HRESULT hr = threadMgr->SetFocus(disabledDocumentMgr);
5723 if (NS_WARN_IF(FAILED(hr))) {
5724 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5725 (" TSFTextStore::OnFocusChange() FAILED due to "
5726 "ITfThreadMgr::SetFocus() failure"));
5727 return NS_ERROR_FAILURE;
5729 return NS_OK;
5732 // If an editor is getting focus, create new TextStore and set focus.
5733 if (NS_WARN_IF(!CreateAndSetFocus(aFocusedWidget, aContext))) {
5734 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5735 (" TSFTextStore::OnFocusChange() FAILED due to "
5736 "ITfThreadMgr::CreateAndSetFocus() failure"));
5737 return NS_ERROR_FAILURE;
5739 return NS_OK;
5742 // static
5743 void TSFTextStore::EnsureToDestroyAndReleaseEnabledTextStoreIf(
5744 RefPtr<TSFTextStore>& aTextStore) {
5745 aTextStore->Destroy();
5746 if (sEnabledTextStore == aTextStore) {
5747 sEnabledTextStore = nullptr;
5749 aTextStore = nullptr;
5752 // static
5753 bool TSFTextStore::CreateAndSetFocus(nsWindowBase* aFocusedWidget,
5754 const InputContext& aContext) {
5755 // TSF might do something which causes that we need to access static methods
5756 // of TSFTextStore. At that time, sEnabledTextStore may be necessary.
5757 // So, we should set sEnabledTextStore directly.
5758 RefPtr<TSFTextStore> textStore = new TSFTextStore();
5759 sEnabledTextStore = textStore;
5760 if (NS_WARN_IF(!textStore->Init(aFocusedWidget, aContext))) {
5761 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5762 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5763 "TSFTextStore::Init() failure"));
5764 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5765 return false;
5767 RefPtr<ITfDocumentMgr> newDocMgr = textStore->mDocumentMgr;
5768 if (NS_WARN_IF(!newDocMgr)) {
5769 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5770 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5771 "invalid TSFTextStore::mDocumentMgr"));
5772 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5773 return false;
5775 if (aContext.mIMEState.mEnabled == IMEState::PASSWORD) {
5776 MarkContextAsKeyboardDisabled(textStore->mContext);
5777 RefPtr<ITfContext> topContext;
5778 newDocMgr->GetTop(getter_AddRefs(topContext));
5779 if (topContext && topContext != textStore->mContext) {
5780 MarkContextAsKeyboardDisabled(topContext);
5784 HRESULT hr;
5785 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5786 hr = threadMgr->SetFocus(newDocMgr);
5788 if (NS_WARN_IF(FAILED(hr))) {
5789 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5790 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5791 "ITfTheadMgr::SetFocus() failure"));
5792 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5793 return false;
5795 if (NS_WARN_IF(!sThreadMgr)) {
5796 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5797 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5798 "sThreadMgr being destroyed during calling "
5799 "ITfTheadMgr::SetFocus()"));
5800 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5801 return false;
5803 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5804 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5805 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5806 "creating TextStore has lost focus during calling "
5807 "ITfThreadMgr::SetFocus()"));
5808 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5809 return false;
5812 // Use AssociateFocus() for ensuring that any native focus event
5813 // never steal focus from our documentMgr.
5814 RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
5815 hr = threadMgr->AssociateFocus(aFocusedWidget->GetWindowHandle(), newDocMgr,
5816 getter_AddRefs(prevFocusedDocumentMgr));
5817 if (NS_WARN_IF(FAILED(hr))) {
5818 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5819 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5820 "ITfTheadMgr::AssociateFocus() failure"));
5821 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5822 return false;
5824 if (NS_WARN_IF(!sThreadMgr)) {
5825 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5826 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5827 "sThreadMgr being destroyed during calling "
5828 "ITfTheadMgr::AssociateFocus()"));
5829 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5830 return false;
5832 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5833 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5834 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5835 "creating TextStore has lost focus during calling "
5836 "ITfTheadMgr::AssociateFocus()"));
5837 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5838 return false;
5841 if (textStore->mSink) {
5842 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5843 (" TSFTextStore::CreateAndSetFocus(), calling "
5844 "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE) for 0x%p...",
5845 textStore.get()));
5846 RefPtr<ITextStoreACPSink> sink = textStore->mSink;
5847 sink->OnLayoutChange(TS_LC_CREATE, TEXTSTORE_DEFAULT_VIEW);
5848 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5849 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5850 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5851 "creating TextStore has lost focus during calling "
5852 "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE)"));
5853 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5854 return false;
5857 return true;
5860 // static
5861 IMENotificationRequests TSFTextStore::GetIMENotificationRequests() {
5862 if (!sEnabledTextStore || NS_WARN_IF(!sEnabledTextStore->mDocumentMgr)) {
5863 // If there is no active text store, we don't need any notifications
5864 // since there is no sink which needs notifications.
5865 return IMENotificationRequests();
5868 // Otherwise, requests all notifications since even if some of them may not
5869 // be required by the sink of active TIP, active TIP may be changed and
5870 // other TIPs may need all notifications.
5871 // Note that Windows temporarily steal focus from active window if the main
5872 // process which created the window becomes busy. In this case, we shouldn't
5873 // commit composition since user may want to continue to compose the
5874 // composition after becoming not busy. Therefore, we need notifications
5875 // even during deactive.
5876 // Be aware, we don't need to check actual focused text store. For example,
5877 // MS-IME for Japanese handles focus messages by themselves and sets focused
5878 // text store to nullptr when the process is being inactivated. However,
5879 // we still need to reuse sEnabledTextStore if the process is activated and
5880 // focused element isn't changed. Therefore, if sEnabledTextStore isn't
5881 // nullptr, we need to keep notifying the sink even when it is not focused
5882 // text store for the thread manager.
5883 return IMENotificationRequests(
5884 IMENotificationRequests::NOTIFY_TEXT_CHANGE |
5885 IMENotificationRequests::NOTIFY_POSITION_CHANGE |
5886 IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR |
5887 IMENotificationRequests::NOTIFY_DURING_DEACTIVE);
5890 nsresult TSFTextStore::OnTextChangeInternal(
5891 const IMENotification& aIMENotification) {
5892 const TextChangeDataBase& textChangeData = aIMENotification.mTextChangeData;
5894 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5895 ("0x%p TSFTextStore::OnTextChangeInternal(aIMENotification={ "
5896 "mMessage=0x%08X, mTextChangeData=%s }), "
5897 "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, "
5898 "mComposition.IsComposing()=%s",
5899 this, aIMENotification.mMessage,
5900 mozilla::ToString(textChangeData).c_str(), GetBoolName(mDestroyed),
5901 mSink.get(), GetSinkMaskNameStr(mSinkMask).get(),
5902 GetBoolName(mComposition.IsComposing())));
5904 if (mDestroyed) {
5905 // If this instance is already destroyed, we shouldn't notify TSF of any
5906 // changes.
5907 return NS_OK;
5910 mDeferNotifyingTSF = false;
5912 // Different from selection change, we don't modify anything with text
5913 // change data. Therefore, if neither TSF not TIP wants text change
5914 // notifications, we don't need to store the changes.
5915 if (!mSink || !(mSinkMask & TS_AS_TEXT_CHANGE)) {
5916 return NS_OK;
5919 // Merge any text change data even if it's caused by composition.
5920 mPendingTextChangeData.MergeWith(textChangeData);
5922 MaybeFlushPendingNotifications();
5924 return NS_OK;
5927 void TSFTextStore::NotifyTSFOfTextChange() {
5928 MOZ_ASSERT(!mDestroyed);
5929 MOZ_ASSERT(!IsReadLocked());
5930 MOZ_ASSERT(!mComposition.IsComposing());
5931 MOZ_ASSERT(mPendingTextChangeData.IsValid());
5933 // If the text changes are caused only by composition, we don't need to
5934 // notify TSF of the text changes.
5935 if (mPendingTextChangeData.mCausedOnlyByComposition) {
5936 mPendingTextChangeData.Clear();
5937 return;
5940 // First, forget cached selection.
5941 mSelectionForTSF.MarkDirty();
5943 // For making it safer, we should check if there is a valid sink to receive
5944 // text change notification.
5945 if (NS_WARN_IF(!mSink) || NS_WARN_IF(!(mSinkMask & TS_AS_TEXT_CHANGE))) {
5946 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5947 ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
5948 "mSink is not ready to call ITextStoreACPSink::OnTextChange()...",
5949 this));
5950 mPendingTextChangeData.Clear();
5951 return;
5954 if (NS_WARN_IF(!mPendingTextChangeData.IsInInt32Range())) {
5955 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5956 ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
5957 "offset is too big for calling "
5958 "ITextStoreACPSink::OnTextChange()...",
5959 this));
5960 mPendingTextChangeData.Clear();
5961 return;
5964 TS_TEXTCHANGE textChange;
5965 textChange.acpStart = static_cast<LONG>(mPendingTextChangeData.mStartOffset);
5966 textChange.acpOldEnd =
5967 static_cast<LONG>(mPendingTextChangeData.mRemovedEndOffset);
5968 textChange.acpNewEnd =
5969 static_cast<LONG>(mPendingTextChangeData.mAddedEndOffset);
5970 mPendingTextChangeData.Clear();
5972 MOZ_LOG(
5973 sTextStoreLog, LogLevel::Info,
5974 ("0x%p TSFTextStore::NotifyTSFOfTextChange(), calling "
5975 "ITextStoreACPSink::OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
5976 "acpNewEnd=%ld })...",
5977 this, textChange.acpStart, textChange.acpOldEnd, textChange.acpNewEnd));
5978 RefPtr<ITextStoreACPSink> sink = mSink;
5979 sink->OnTextChange(0, &textChange);
5982 nsresult TSFTextStore::OnSelectionChangeInternal(
5983 const IMENotification& aIMENotification) {
5984 const SelectionChangeDataBase& selectionChangeData =
5985 aIMENotification.mSelectionChangeData;
5986 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5987 ("0x%p TSFTextStore::OnSelectionChangeInternal("
5988 "aIMENotification={ mSelectionChangeData=%s }), mDestroyed=%s, "
5989 "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, "
5990 "mComposition.IsComposing()=%s",
5991 this, mozilla::ToString(selectionChangeData).c_str(),
5992 GetBoolName(mDestroyed), mSink.get(),
5993 GetSinkMaskNameStr(mSinkMask).get(),
5994 GetBoolName(mIsRecordingActionsWithoutLock),
5995 GetBoolName(mComposition.IsComposing())));
5997 if (mDestroyed) {
5998 // If this instance is already destroyed, we shouldn't notify TSF of any
5999 // changes.
6000 return NS_OK;
6003 mDeferNotifyingTSF = false;
6005 // Assign the new selection change data to the pending selection change data
6006 // because only the latest selection data is necessary.
6007 // Note that this is necessary to update mSelectionForTSF. Therefore, even if
6008 // neither TSF nor TIP wants selection change notifications, we need to
6009 // store the selection information.
6010 mPendingSelectionChangeData.Assign(selectionChangeData);
6012 // Flush remaining pending notifications here if it's possible.
6013 MaybeFlushPendingNotifications();
6015 // If we're available, we should create native caret instead of IMEHandler
6016 // because we may have some cache to do it.
6017 // Note that if we have composition, we'll notified composition-updated
6018 // later so that we don't need to create native caret in such case.
6019 if (!IsHandlingComposition() && IMEHandler::NeedsToCreateNativeCaret()) {
6020 CreateNativeCaret();
6023 return NS_OK;
6026 void TSFTextStore::NotifyTSFOfSelectionChange() {
6027 MOZ_ASSERT(!mDestroyed);
6028 MOZ_ASSERT(!IsReadLocked());
6029 MOZ_ASSERT(!mComposition.IsComposing());
6030 MOZ_ASSERT(mPendingSelectionChangeData.IsValid());
6032 // If selection range isn't actually changed, we don't need to notify TSF
6033 // of this selection change.
6034 if (!mSelectionForTSF.SetSelection(
6035 mPendingSelectionChangeData.mOffset,
6036 mPendingSelectionChangeData.Length(),
6037 mPendingSelectionChangeData.mReversed,
6038 mPendingSelectionChangeData.GetWritingMode())) {
6039 mPendingSelectionChangeData.Clear();
6040 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6041 ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), "
6042 "selection isn't actually changed.",
6043 this));
6044 return;
6047 mPendingSelectionChangeData.Clear();
6049 if (!mSink || !(mSinkMask & TS_AS_SEL_CHANGE)) {
6050 return;
6053 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6054 ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), calling "
6055 "ITextStoreACPSink::OnSelectionChange()...",
6056 this));
6057 RefPtr<ITextStoreACPSink> sink = mSink;
6058 sink->OnSelectionChange();
6061 nsresult TSFTextStore::OnLayoutChangeInternal() {
6062 if (mDestroyed) {
6063 // If this instance is already destroyed, we shouldn't notify TSF of any
6064 // changes.
6065 return NS_OK;
6068 NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE);
6069 NS_ENSURE_TRUE(mSink, NS_ERROR_FAILURE);
6071 mDeferNotifyingTSF = false;
6073 nsresult rv = NS_OK;
6075 // We need to notify TSF of layout change even if the document is locked.
6076 // So, don't use MaybeFlushPendingNotifications() for flushing pending
6077 // layout change.
6078 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6079 ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
6080 "NotifyTSFOfLayoutChange()...",
6081 this));
6082 if (NS_WARN_IF(!NotifyTSFOfLayoutChange())) {
6083 rv = NS_ERROR_FAILURE;
6086 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6087 ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
6088 "MaybeFlushPendingNotifications()...",
6089 this));
6090 MaybeFlushPendingNotifications();
6092 return rv;
6095 bool TSFTextStore::NotifyTSFOfLayoutChange() {
6096 MOZ_ASSERT(!mDestroyed);
6098 // If we're waiting a query of layout information from TIP, it means that
6099 // we've returned TS_E_NOLAYOUT error.
6100 bool returnedNoLayoutError = mHasReturnedNoLayoutError || mWaitingQueryLayout;
6102 // If we returned TS_E_NOLAYOUT, TIP should query the computed layout again.
6103 mWaitingQueryLayout = returnedNoLayoutError;
6105 // For avoiding to call this method again at unlocking the document during
6106 // calls of OnLayoutChange(), reset mHasReturnedNoLayoutError.
6107 mHasReturnedNoLayoutError = false;
6109 // Now, layout has been computed. We should notify mContentForTSF for
6110 // making GetTextExt() and GetACPFromPoint() not return TS_E_NOLAYOUT.
6111 if (mContentForTSF.IsInitialized()) {
6112 mContentForTSF.OnLayoutChanged();
6115 if (IMEHandler::NeedsToCreateNativeCaret()) {
6116 // If we're available, we should create native caret instead of IMEHandler
6117 // because we may have some cache to do it.
6118 CreateNativeCaret();
6119 } else {
6120 // Now, the caret position is different from ours. Destroy the native caret
6121 // if we've create it only for GetTextExt().
6122 IMEHandler::MaybeDestroyNativeCaret();
6125 // This method should return true if either way succeeds.
6126 bool ret = true;
6128 if (mSink) {
6129 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6130 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6131 "calling ITextStoreACPSink::OnLayoutChange()...",
6132 this));
6133 RefPtr<ITextStoreACPSink> sink = mSink;
6134 HRESULT hr = sink->OnLayoutChange(TS_LC_CHANGE, TEXTSTORE_DEFAULT_VIEW);
6135 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6136 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6137 "called ITextStoreACPSink::OnLayoutChange()",
6138 this));
6139 ret = SUCCEEDED(hr);
6142 // The layout change caused by composition string change should cause
6143 // calling ITfContextOwnerServices::OnLayoutChange() too.
6144 if (returnedNoLayoutError && mContext) {
6145 RefPtr<ITfContextOwnerServices> service;
6146 mContext->QueryInterface(IID_ITfContextOwnerServices,
6147 getter_AddRefs(service));
6148 if (service) {
6149 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6150 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6151 "calling ITfContextOwnerServices::OnLayoutChange()...",
6152 this));
6153 HRESULT hr = service->OnLayoutChange();
6154 ret = ret && SUCCEEDED(hr);
6155 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6156 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6157 "called ITfContextOwnerServices::OnLayoutChange()",
6158 this));
6162 if (!mWidget || mWidget->Destroyed()) {
6163 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6164 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6165 "the widget is destroyed during calling OnLayoutChange()",
6166 this));
6167 return ret;
6170 if (mDestroyed) {
6171 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6172 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6173 "the TSFTextStore instance is destroyed during calling "
6174 "OnLayoutChange()",
6175 this));
6176 return ret;
6179 // If we returned TS_E_NOLAYOUT again, we need another call of
6180 // OnLayoutChange() later. So, let's wait a query from TIP.
6181 if (mHasReturnedNoLayoutError) {
6182 mWaitingQueryLayout = true;
6185 if (!mWaitingQueryLayout) {
6186 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6187 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6188 "succeeded notifying TIP of our layout change",
6189 this));
6190 return ret;
6193 // If we believe that TIP needs to retry to retrieve our layout information
6194 // later, we should call it with ::PostMessage() hack.
6195 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6196 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6197 "posing MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE for calling "
6198 "OnLayoutChange() again...",
6199 this));
6200 ::PostMessage(mWidget->GetWindowHandle(), MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE,
6201 reinterpret_cast<WPARAM>(this), 0);
6203 return true;
6206 void TSFTextStore::NotifyTSFOfLayoutChangeAgain() {
6207 // Don't notify TSF of layout change after destroyed.
6208 if (mDestroyed) {
6209 mWaitingQueryLayout = false;
6210 return;
6213 // Before preforming this method, TIP has accessed our layout information by
6214 // itself. In such case, we don't need to call OnLayoutChange() anymore.
6215 if (!mWaitingQueryLayout) {
6216 return;
6219 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6220 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6221 "calling NotifyTSFOfLayoutChange()...",
6222 this));
6223 NotifyTSFOfLayoutChange();
6225 // If TIP didn't retrieved our layout information during a call of
6226 // NotifyTSFOfLayoutChange(), it means that the TIP already gave up to
6227 // retry to retrieve layout information or doesn't necessary it anymore.
6228 // But don't forget that the call may have caused returning TS_E_NOLAYOUT
6229 // error again. In such case we still need to call OnLayoutChange() later.
6230 if (!mHasReturnedNoLayoutError && mWaitingQueryLayout) {
6231 mWaitingQueryLayout = false;
6232 MOZ_LOG(sTextStoreLog, LogLevel::Warning,
6233 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6234 "called NotifyTSFOfLayoutChange() but TIP didn't retry to "
6235 "retrieve the layout information",
6236 this));
6237 } else {
6238 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6239 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6240 "called NotifyTSFOfLayoutChange()",
6241 this));
6245 nsresult TSFTextStore::OnUpdateCompositionInternal() {
6246 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6247 ("0x%p TSFTextStore::OnUpdateCompositionInternal(), "
6248 "mDestroyed=%s, mDeferNotifyingTSF=%s",
6249 this, GetBoolName(mDestroyed), GetBoolName(mDeferNotifyingTSF)));
6251 // There are nothing to do after destroyed.
6252 if (mDestroyed) {
6253 return NS_OK;
6256 // Update cached data now because all pending events have been handled now.
6257 mContentForTSF.OnCompositionEventsHandled();
6259 // If composition is completely finished both in TSF/TIP and the focused
6260 // editor which may be in a remote process, we can clear the cache and don't
6261 // have it until starting next composition.
6262 if (!mComposition.IsComposing() && !IsHandlingComposition()) {
6263 mDeferClearingContentForTSF = false;
6265 mDeferNotifyingTSF = false;
6266 MaybeFlushPendingNotifications();
6268 // If we're available, we should create native caret instead of IMEHandler
6269 // because we may have some cache to do it.
6270 if (IMEHandler::NeedsToCreateNativeCaret()) {
6271 CreateNativeCaret();
6274 return NS_OK;
6277 nsresult TSFTextStore::OnMouseButtonEventInternal(
6278 const IMENotification& aIMENotification) {
6279 if (mDestroyed) {
6280 // If this instance is already destroyed, we shouldn't notify TSF of any
6281 // events.
6282 return NS_OK;
6285 if (mMouseTrackers.IsEmpty()) {
6286 return NS_OK;
6289 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6290 ("0x%p TSFTextStore::OnMouseButtonEventInternal("
6291 "aIMENotification={ mEventMessage=%s, mOffset=%u, mCursorPos={ "
6292 "mX=%d, mY=%d }, mCharRect={ mX=%d, mY=%d, mWidth=%d, mHeight=%d }, "
6293 "mButton=%s, mButtons=%s, mModifiers=%s })",
6294 this, ToChar(aIMENotification.mMouseButtonEventData.mEventMessage),
6295 aIMENotification.mMouseButtonEventData.mOffset,
6296 aIMENotification.mMouseButtonEventData.mCursorPos.mX,
6297 aIMENotification.mMouseButtonEventData.mCursorPos.mY,
6298 aIMENotification.mMouseButtonEventData.mCharRect.mX,
6299 aIMENotification.mMouseButtonEventData.mCharRect.mY,
6300 aIMENotification.mMouseButtonEventData.mCharRect.mWidth,
6301 aIMENotification.mMouseButtonEventData.mCharRect.mHeight,
6302 GetMouseButtonName(aIMENotification.mMouseButtonEventData.mButton),
6303 GetMouseButtonsName(aIMENotification.mMouseButtonEventData.mButtons)
6304 .get(),
6305 GetModifiersName(aIMENotification.mMouseButtonEventData.mModifiers)
6306 .get()));
6308 uint32_t offset = aIMENotification.mMouseButtonEventData.mOffset;
6309 nsIntRect charRect =
6310 aIMENotification.mMouseButtonEventData.mCharRect.AsIntRect();
6311 nsIntPoint cursorPos =
6312 aIMENotification.mMouseButtonEventData.mCursorPos.AsIntPoint();
6313 ULONG quadrant = 1;
6314 if (charRect.Width() > 0) {
6315 int32_t cursorXInChar = cursorPos.x - charRect.X();
6316 quadrant = cursorXInChar * 4 / charRect.Width();
6317 quadrant = (quadrant + 2) % 4;
6319 ULONG edge = quadrant < 2 ? offset + 1 : offset;
6320 DWORD buttonStatus = 0;
6321 bool isMouseUp =
6322 aIMENotification.mMouseButtonEventData.mEventMessage == eMouseUp;
6323 if (!isMouseUp) {
6324 switch (aIMENotification.mMouseButtonEventData.mButton) {
6325 case MouseButton::ePrimary:
6326 buttonStatus = MK_LBUTTON;
6327 break;
6328 case MouseButton::eMiddle:
6329 buttonStatus = MK_MBUTTON;
6330 break;
6331 case MouseButton::eSecondary:
6332 buttonStatus = MK_RBUTTON;
6333 break;
6336 if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_CONTROL) {
6337 buttonStatus |= MK_CONTROL;
6339 if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_SHIFT) {
6340 buttonStatus |= MK_SHIFT;
6342 for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
6343 MouseTracker& tracker = mMouseTrackers[i];
6344 if (!tracker.IsUsing() || !tracker.InRange(offset)) {
6345 continue;
6347 if (tracker.OnMouseButtonEvent(edge - tracker.RangeStart(), quadrant,
6348 buttonStatus)) {
6349 return NS_SUCCESS_EVENT_CONSUMED;
6352 return NS_OK;
6355 void TSFTextStore::CreateNativeCaret() {
6356 MOZ_ASSERT(!IMEHandler::IsA11yHandlingNativeCaret());
6358 IMEHandler::MaybeDestroyNativeCaret();
6360 // Don't create native caret after destroyed.
6361 if (mDestroyed) {
6362 return;
6365 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6366 ("0x%p TSFTextStore::CreateNativeCaret(), "
6367 "mComposition.IsComposing()=%s",
6368 this, GetBoolName(mComposition.IsComposing())));
6370 Selection& selectionForTSF = SelectionForTSFRef();
6371 if (selectionForTSF.IsDirty()) {
6372 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6373 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6374 "SelectionForTSFRef() failure",
6375 this));
6376 return;
6379 WidgetQueryContentEvent queryCaretRect(true, eQueryCaretRect, mWidget);
6380 mWidget->InitEvent(queryCaretRect);
6382 WidgetQueryContentEvent::Options options;
6383 // XXX If this is called without composition and the selection isn't
6384 // collapsed, is it OK?
6385 int64_t caretOffset = selectionForTSF.MaxOffset();
6386 if (mComposition.IsComposing()) {
6387 // If there is a composition, use insertion point relative query for
6388 // deciding caret position because composition might be at different
6389 // position where TSFTextStore believes it at.
6390 options.mRelativeToInsertionPoint = true;
6391 caretOffset -= mComposition.mStart;
6392 } else if (!CanAccessActualContentDirectly()) {
6393 // If TSF/TIP cannot access actual content directly, there may be pending
6394 // text and/or selection changes which have not been notified TSF yet.
6395 // Therefore, we should use relative to insertion point query since
6396 // TSF/TIP computes the offset from the cached selection.
6397 options.mRelativeToInsertionPoint = true;
6398 caretOffset -= mSelectionForTSF.StartOffset();
6400 queryCaretRect.InitForQueryCaretRect(caretOffset, options);
6402 DispatchEvent(queryCaretRect);
6403 if (NS_WARN_IF(!queryCaretRect.mSucceeded)) {
6404 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6405 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6406 "eQueryCaretRect failure (offset=%d)",
6407 this, caretOffset));
6408 return;
6411 if (!IMEHandler::CreateNativeCaret(static_cast<nsWindow*>(mWidget.get()),
6412 queryCaretRect.mReply.mRect)) {
6413 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6414 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6415 "IMEHandler::CreateNativeCaret() failure",
6416 this));
6417 return;
6421 void TSFTextStore::CommitCompositionInternal(bool aDiscard) {
6422 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6423 ("0x%p TSFTextStore::CommitCompositionInternal(aDiscard=%s), "
6424 "mSink=0x%p, mContext=0x%p, mComposition.mView=0x%p, "
6425 "mComposition.mString=\"%s\"",
6426 this, GetBoolName(aDiscard), mSink.get(), mContext.get(),
6427 mComposition.mView.get(),
6428 GetEscapedUTF8String(mComposition.mString).get()));
6430 // If the document is locked, TSF will fail to commit composition since
6431 // TSF needs another document lock. So, let's put off the request.
6432 // Note that TextComposition will commit composition in the focused editor
6433 // with the latest composition string for web apps and waits asynchronous
6434 // committing messages. Therefore, we can and need to perform this
6435 // asynchronously.
6436 if (IsReadLocked()) {
6437 if (mDeferCommittingComposition || mDeferCancellingComposition) {
6438 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6439 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6440 "does nothing because already called and waiting unlock...",
6441 this));
6442 return;
6444 if (aDiscard) {
6445 mDeferCancellingComposition = true;
6446 } else {
6447 mDeferCommittingComposition = true;
6449 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6450 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6451 "putting off to request to %s composition after unlocking the "
6452 "document",
6453 this, aDiscard ? "cancel" : "commit"));
6454 return;
6457 if (mComposition.IsComposing() && aDiscard) {
6458 LONG endOffset = mComposition.EndOffset();
6459 mComposition.mString.Truncate(0);
6460 // Note that don't notify TSF of text change after this is destroyed.
6461 if (mSink && !mDestroyed) {
6462 TS_TEXTCHANGE textChange;
6463 textChange.acpStart = mComposition.mStart;
6464 textChange.acpOldEnd = endOffset;
6465 textChange.acpNewEnd = mComposition.mStart;
6466 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6467 ("0x%p TSFTextStore::CommitCompositionInternal(), calling"
6468 "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
6469 "acpNewEnd=%ld })...",
6470 this, textChange.acpStart, textChange.acpOldEnd,
6471 textChange.acpNewEnd));
6472 RefPtr<ITextStoreACPSink> sink = mSink;
6473 sink->OnTextChange(0, &textChange);
6476 // Terminate two contexts, the base context (mContext) and the top
6477 // if the top context is not the same as the base context
6478 RefPtr<ITfContext> context = mContext;
6479 do {
6480 if (context) {
6481 RefPtr<ITfContextOwnerCompositionServices> services;
6482 context->QueryInterface(IID_ITfContextOwnerCompositionServices,
6483 getter_AddRefs(services));
6484 if (services) {
6485 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6486 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6487 "requesting TerminateComposition() for the context 0x%p...",
6488 this, context.get()));
6489 services->TerminateComposition(nullptr);
6492 if (context != mContext) break;
6493 if (mDocumentMgr) mDocumentMgr->GetTop(getter_AddRefs(context));
6494 } while (context != mContext);
6497 static bool GetCompartment(IUnknown* pUnk, const GUID& aID,
6498 ITfCompartment** aCompartment) {
6499 if (!pUnk) return false;
6501 RefPtr<ITfCompartmentMgr> compMgr;
6502 pUnk->QueryInterface(IID_ITfCompartmentMgr, getter_AddRefs(compMgr));
6503 if (!compMgr) return false;
6505 return SUCCEEDED(compMgr->GetCompartment(aID, aCompartment)) &&
6506 (*aCompartment) != nullptr;
6509 // static
6510 void TSFTextStore::SetIMEOpenState(bool aState) {
6511 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6512 ("TSFTextStore::SetIMEOpenState(aState=%s)", GetBoolName(aState)));
6514 if (!sThreadMgr) {
6515 return;
6518 RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
6519 if (NS_WARN_IF(!comp)) {
6520 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6521 (" TSFTextStore::SetIMEOpenState() FAILED due to"
6522 "no compartment available"));
6523 return;
6526 VARIANT variant;
6527 variant.vt = VT_I4;
6528 variant.lVal = aState;
6529 HRESULT hr = comp->SetValue(sClientId, &variant);
6530 if (NS_WARN_IF(FAILED(hr))) {
6531 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6532 (" TSFTextStore::SetIMEOpenState() FAILED due to "
6533 "ITfCompartment::SetValue() failure, hr=0x%08X",
6534 hr));
6535 return;
6537 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6538 (" TSFTextStore::SetIMEOpenState(), setting "
6539 "0x%04X to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...",
6540 variant.lVal));
6543 // static
6544 bool TSFTextStore::GetIMEOpenState() {
6545 if (!sThreadMgr) {
6546 return false;
6549 RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
6550 if (NS_WARN_IF(!comp)) {
6551 return false;
6554 VARIANT variant;
6555 ::VariantInit(&variant);
6556 HRESULT hr = comp->GetValue(&variant);
6557 if (NS_WARN_IF(FAILED(hr))) {
6558 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6559 ("TSFTextStore::GetIMEOpenState() FAILED due to "
6560 "ITfCompartment::GetValue() failure, hr=0x%08X",
6561 hr));
6562 return false;
6564 // Until IME is open in this process, the result may be empty.
6565 if (variant.vt == VT_EMPTY) {
6566 return false;
6568 if (NS_WARN_IF(variant.vt != VT_I4)) {
6569 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6570 ("TSFTextStore::GetIMEOpenState() FAILED due to "
6571 "invalid result of ITfCompartment::GetValue()"));
6572 ::VariantClear(&variant);
6573 return false;
6576 return variant.lVal != 0;
6579 // static
6580 void TSFTextStore::SetInputContext(nsWindowBase* aWidget,
6581 const InputContext& aContext,
6582 const InputContextAction& aAction) {
6583 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6584 ("TSFTextStore::SetInputContext(aWidget=%p, "
6585 "aContext=%s, aAction.mFocusChange=%s), "
6586 "sEnabledTextStore(0x%p)={ mWidget=0x%p }, ThinksHavingFocus()=%s",
6587 aWidget, mozilla::ToString(aContext).c_str(),
6588 GetFocusChangeName(aAction.mFocusChange), sEnabledTextStore.get(),
6589 sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr,
6590 GetBoolName(ThinksHavingFocus())));
6592 switch (aAction.mFocusChange) {
6593 case InputContextAction::WIDGET_CREATED:
6594 // If this is called when the widget is created, there is nothing to do.
6595 return;
6596 case InputContextAction::FOCUS_NOT_CHANGED:
6597 case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
6598 if (NS_WARN_IF(!IsInTSFMode())) {
6599 return;
6601 // In these cases, `NOTIFY_IME_OF_FOCUS` won't be sent. Therefore,
6602 // we need to reset text store for new state right now.
6603 break;
6604 default:
6605 NS_WARNING_ASSERTION(IsInTSFMode(),
6606 "Why is this called when TSF is disabled?");
6607 if (sEnabledTextStore) {
6608 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
6609 textStore->SetInputScope(aContext.mHTMLInputType,
6610 aContext.mHTMLInputInputmode,
6611 aContext.mInPrivateBrowsing);
6613 return;
6616 // If focus isn't actually changed but the enabled state is changed,
6617 // emulate the focus move.
6618 if (!ThinksHavingFocus() && aContext.mIMEState.IsEditable()) {
6619 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6620 (" TSFTextStore::SetInputContent() emulates focus for IME "
6621 "state change"));
6622 OnFocusChange(true, aWidget, aContext);
6623 } else if (ThinksHavingFocus() && !aContext.mIMEState.IsEditable()) {
6624 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6625 (" TSFTextStore::SetInputContent() emulates blur for IME "
6626 "state change"));
6627 OnFocusChange(false, aWidget, aContext);
6631 // static
6632 void TSFTextStore::MarkContextAsKeyboardDisabled(ITfContext* aContext) {
6633 VARIANT variant_int4_value1;
6634 variant_int4_value1.vt = VT_I4;
6635 variant_int4_value1.lVal = 1;
6637 RefPtr<ITfCompartment> comp;
6638 if (!GetCompartment(aContext, GUID_COMPARTMENT_KEYBOARD_DISABLED,
6639 getter_AddRefs(comp))) {
6640 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6641 ("TSFTextStore::MarkContextAsKeyboardDisabled() failed"
6642 "aContext=0x%p...",
6643 aContext));
6644 return;
6647 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6648 ("TSFTextStore::MarkContextAsKeyboardDisabled(), setting "
6649 "to disable context 0x%p...",
6650 aContext));
6651 comp->SetValue(sClientId, &variant_int4_value1);
6654 // static
6655 void TSFTextStore::MarkContextAsEmpty(ITfContext* aContext) {
6656 VARIANT variant_int4_value1;
6657 variant_int4_value1.vt = VT_I4;
6658 variant_int4_value1.lVal = 1;
6660 RefPtr<ITfCompartment> comp;
6661 if (!GetCompartment(aContext, GUID_COMPARTMENT_EMPTYCONTEXT,
6662 getter_AddRefs(comp))) {
6663 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6664 ("TSFTextStore::MarkContextAsEmpty() failed"
6665 "aContext=0x%p...",
6666 aContext));
6667 return;
6670 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6671 ("TSFTextStore::MarkContextAsEmpty(), setting "
6672 "to mark empty context 0x%p...",
6673 aContext));
6674 comp->SetValue(sClientId, &variant_int4_value1);
6677 // static
6678 void TSFTextStore::Initialize() {
6679 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6680 ("TSFTextStore::Initialize() is called..."));
6682 if (sThreadMgr) {
6683 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6684 (" TSFTextStore::Initialize() FAILED due to already initialized"));
6685 return;
6688 bool enableTsf = Preferences::GetBool(kPrefNameEnableTSF, false);
6689 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6690 (" TSFTextStore::Initialize(), TSF is %s",
6691 enableTsf ? "enabled" : "disabled"));
6692 if (!enableTsf) {
6693 return;
6696 RefPtr<ITfThreadMgr> threadMgr;
6697 HRESULT hr =
6698 ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_INPROC_SERVER,
6699 IID_ITfThreadMgr, getter_AddRefs(threadMgr));
6700 if (FAILED(hr) || !threadMgr) {
6701 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6702 (" TSFTextStore::Initialize() FAILED to "
6703 "create the thread manager, hr=0x%08X",
6704 hr));
6705 return;
6708 hr = threadMgr->Activate(&sClientId);
6709 if (FAILED(hr)) {
6710 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6711 (" TSFTextStore::Initialize() FAILED to activate, hr=0x%08X", hr));
6712 return;
6715 RefPtr<ITfDocumentMgr> disabledDocumentMgr;
6716 hr = threadMgr->CreateDocumentMgr(getter_AddRefs(disabledDocumentMgr));
6717 if (FAILED(hr) || !disabledDocumentMgr) {
6718 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6719 (" TSFTextStore::Initialize() FAILED to create "
6720 "a document manager for disabled mode, hr=0x%08X",
6721 hr));
6722 return;
6725 RefPtr<ITfContext> disabledContext;
6726 DWORD editCookie = 0;
6727 hr = disabledDocumentMgr->CreateContext(
6728 sClientId, 0, nullptr, getter_AddRefs(disabledContext), &editCookie);
6729 if (FAILED(hr) || !disabledContext) {
6730 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6731 (" TSFTextStore::Initialize() FAILED to create "
6732 "a context for disabled mode, hr=0x%08X",
6733 hr));
6734 return;
6737 MarkContextAsKeyboardDisabled(disabledContext);
6738 MarkContextAsEmpty(disabledContext);
6740 sThreadMgr = threadMgr;
6741 sDisabledDocumentMgr = disabledDocumentMgr;
6742 sDisabledContext = disabledContext;
6744 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6745 (" TSFTextStore::Initialize(), sThreadMgr=0x%p, "
6746 "sClientId=0x%08X, sDisabledDocumentMgr=0x%p, sDisabledContext=%p",
6747 sThreadMgr.get(), sClientId, sDisabledDocumentMgr.get(),
6748 sDisabledContext.get()));
6751 // static
6752 already_AddRefed<ITfThreadMgr> TSFTextStore::GetThreadMgr() {
6753 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
6754 return threadMgr.forget();
6757 // static
6758 already_AddRefed<ITfMessagePump> TSFTextStore::GetMessagePump() {
6759 static bool sInitialized = false;
6760 if (!sThreadMgr) {
6761 return nullptr;
6763 if (sMessagePump) {
6764 RefPtr<ITfMessagePump> messagePump = sMessagePump;
6765 return messagePump.forget();
6767 // If it tried to retrieve ITfMessagePump from sThreadMgr but it failed,
6768 // we shouldn't retry it at every message due to performance reason.
6769 // Although this shouldn't occur actually.
6770 if (sInitialized) {
6771 return nullptr;
6773 sInitialized = true;
6775 RefPtr<ITfMessagePump> messagePump;
6776 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfMessagePump,
6777 getter_AddRefs(messagePump));
6778 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!messagePump)) {
6779 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6780 ("TSFTextStore::GetMessagePump() FAILED to "
6781 "QI message pump from the thread manager, hr=0x%08X",
6782 hr));
6783 return nullptr;
6785 sMessagePump = messagePump;
6786 return messagePump.forget();
6789 // static
6790 already_AddRefed<ITfDisplayAttributeMgr>
6791 TSFTextStore::GetDisplayAttributeMgr() {
6792 RefPtr<ITfDisplayAttributeMgr> displayAttributeMgr;
6793 if (sDisplayAttrMgr) {
6794 displayAttributeMgr = sDisplayAttrMgr;
6795 return displayAttributeMgr.forget();
6798 HRESULT hr = ::CoCreateInstance(
6799 CLSID_TF_DisplayAttributeMgr, nullptr, CLSCTX_INPROC_SERVER,
6800 IID_ITfDisplayAttributeMgr, getter_AddRefs(displayAttributeMgr));
6801 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!displayAttributeMgr)) {
6802 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6803 ("TSFTextStore::GetDisplayAttributeMgr() FAILED to create "
6804 "a display attribute manager instance, hr=0x%08X",
6805 hr));
6806 return nullptr;
6808 sDisplayAttrMgr = displayAttributeMgr;
6809 return displayAttributeMgr.forget();
6812 // static
6813 already_AddRefed<ITfCategoryMgr> TSFTextStore::GetCategoryMgr() {
6814 RefPtr<ITfCategoryMgr> categoryMgr;
6815 if (sCategoryMgr) {
6816 categoryMgr = sCategoryMgr;
6817 return categoryMgr.forget();
6819 HRESULT hr =
6820 ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, CLSCTX_INPROC_SERVER,
6821 IID_ITfCategoryMgr, getter_AddRefs(categoryMgr));
6822 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!categoryMgr)) {
6823 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6824 ("TSFTextStore::GetCategoryMgr() FAILED to create "
6825 "a category manager instance, hr=0x%08X",
6826 hr));
6827 return nullptr;
6829 sCategoryMgr = categoryMgr;
6830 return categoryMgr.forget();
6833 // static
6834 already_AddRefed<ITfCompartment> TSFTextStore::GetCompartmentForOpenClose() {
6835 if (sCompartmentForOpenClose) {
6836 RefPtr<ITfCompartment> compartment = sCompartmentForOpenClose;
6837 return compartment.forget();
6840 if (!sThreadMgr) {
6841 return nullptr;
6844 RefPtr<ITfCompartmentMgr> compartmentMgr;
6845 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfCompartmentMgr,
6846 getter_AddRefs(compartmentMgr));
6847 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartmentMgr)) {
6848 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6849 ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
6850 "sThreadMgr not having ITfCompartmentMgr, hr=0x%08X",
6851 hr));
6852 return nullptr;
6855 RefPtr<ITfCompartment> compartment;
6856 hr = compartmentMgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
6857 getter_AddRefs(compartment));
6858 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartment)) {
6859 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6860 ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
6861 "ITfCompartmentMgr::GetCompartment() failuere, hr=0x%08X",
6862 hr));
6863 return nullptr;
6866 sCompartmentForOpenClose = compartment;
6867 return compartment.forget();
6870 // static
6871 already_AddRefed<ITfInputProcessorProfiles>
6872 TSFTextStore::GetInputProcessorProfiles() {
6873 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles;
6874 if (sInputProcessorProfiles) {
6875 inputProcessorProfiles = sInputProcessorProfiles;
6876 return inputProcessorProfiles.forget();
6878 // XXX MSDN documents that ITfInputProcessorProfiles is available only on
6879 // desktop apps. However, there is no known way to obtain
6880 // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles
6881 // instance.
6882 HRESULT hr = ::CoCreateInstance(
6883 CLSID_TF_InputProcessorProfiles, nullptr, CLSCTX_INPROC_SERVER,
6884 IID_ITfInputProcessorProfiles, getter_AddRefs(inputProcessorProfiles));
6885 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!inputProcessorProfiles)) {
6886 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6887 ("TSFTextStore::GetInputProcessorProfiles() FAILED to create input "
6888 "processor profiles, hr=0x%08X",
6889 hr));
6890 return nullptr;
6892 sInputProcessorProfiles = inputProcessorProfiles;
6893 return inputProcessorProfiles.forget();
6896 // static
6897 void TSFTextStore::Terminate() {
6898 MOZ_LOG(sTextStoreLog, LogLevel::Info, ("TSFTextStore::Terminate()"));
6900 TSFStaticSink::Shutdown();
6902 sDisplayAttrMgr = nullptr;
6903 sCategoryMgr = nullptr;
6904 sEnabledTextStore = nullptr;
6905 sDisabledDocumentMgr = nullptr;
6906 sDisabledContext = nullptr;
6907 sCompartmentForOpenClose = nullptr;
6908 sInputProcessorProfiles = nullptr;
6909 sClientId = 0;
6910 if (sThreadMgr) {
6911 sThreadMgr->Deactivate();
6912 sThreadMgr = nullptr;
6913 sMessagePump = nullptr;
6914 sKeystrokeMgr = nullptr;
6918 // static
6919 bool TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg) {
6920 if (!sThreadMgr) {
6921 return false; // not in TSF mode
6923 static bool sInitialized = false;
6924 if (!sKeystrokeMgr) {
6925 // If it tried to retrieve ITfKeystrokeMgr from sThreadMgr but it failed,
6926 // we shouldn't retry it at every keydown nor keyup due to performance
6927 // reason. Although this shouldn't occur actually.
6928 if (sInitialized) {
6929 return false;
6931 sInitialized = true;
6932 RefPtr<ITfKeystrokeMgr> keystrokeMgr;
6933 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfKeystrokeMgr,
6934 getter_AddRefs(keystrokeMgr));
6935 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!keystrokeMgr)) {
6936 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6937 ("TSFTextStore::ProcessRawKeyMessage() FAILED to "
6938 "QI keystroke manager from the thread manager, hr=0x%08X",
6939 hr));
6940 return false;
6942 sKeystrokeMgr = keystrokeMgr.forget();
6945 if (aMsg.message == WM_KEYDOWN) {
6946 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
6947 if (textStore) {
6948 textStore->OnStartToHandleKeyMessage();
6949 if (NS_WARN_IF(textStore != sEnabledTextStore)) {
6950 // Let's handle the key message with new focused TSFTextStore.
6951 textStore = sEnabledTextStore;
6954 AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
6955 AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
6956 sHandlingKeyMsg = &aMsg;
6957 sIsKeyboardEventDispatched = false;
6958 BOOL eaten;
6959 RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
6960 HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten);
6961 if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
6962 return false;
6964 hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
6965 if (textStore) {
6966 textStore->OnEndHandlingKeyMessage(!!eaten);
6968 return SUCCEEDED(hr) &&
6969 (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
6971 if (aMsg.message == WM_KEYUP) {
6972 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
6973 if (textStore) {
6974 textStore->OnStartToHandleKeyMessage();
6975 if (NS_WARN_IF(textStore != sEnabledTextStore)) {
6976 // Let's handle the key message with new focused TSFTextStore.
6977 textStore = sEnabledTextStore;
6980 AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
6981 AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
6982 sHandlingKeyMsg = &aMsg;
6983 sIsKeyboardEventDispatched = false;
6984 BOOL eaten;
6985 RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
6986 HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten);
6987 if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
6988 return false;
6990 hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten);
6991 if (textStore) {
6992 textStore->OnEndHandlingKeyMessage(!!eaten);
6994 return SUCCEEDED(hr) &&
6995 (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
6997 return false;
7000 // static
7001 void TSFTextStore::ProcessMessage(nsWindowBase* aWindow, UINT aMessage,
7002 WPARAM& aWParam, LPARAM& aLParam,
7003 MSGResult& aResult) {
7004 switch (aMessage) {
7005 case WM_IME_SETCONTEXT:
7006 // If a windowless plugin had focus and IME was handled on it, composition
7007 // window was set the position. After that, even in TSF mode, WinXP keeps
7008 // to use composition window at the position if the active IME is not
7009 // aware TSF. For avoiding this issue, we need to hide the composition
7010 // window here.
7011 if (aWParam) {
7012 aLParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
7014 break;
7015 case WM_ENTERIDLE:
7016 // When an modal dialog such as a file picker is open, composition
7017 // should be committed because IME might be used on it.
7018 if (!IsComposingOn(aWindow)) {
7019 break;
7021 CommitComposition(false);
7022 break;
7023 case MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE: {
7024 TSFTextStore* maybeTextStore = reinterpret_cast<TSFTextStore*>(aWParam);
7025 if (maybeTextStore == sEnabledTextStore) {
7026 RefPtr<TSFTextStore> textStore(maybeTextStore);
7027 textStore->NotifyTSFOfLayoutChangeAgain();
7029 break;
7034 // static
7035 bool TSFTextStore::IsIMM_IMEActive() {
7036 return TSFStaticSink::IsIMM_IMEActive();
7039 // static
7040 bool TSFTextStore::IsMSJapaneseIMEActive() {
7041 return TSFStaticSink::IsMSJapaneseIMEActive();
7044 // static
7045 bool TSFTextStore::IsGoogleJapaneseInputActive() {
7046 return TSFStaticSink::IsGoogleJapaneseInputActive();
7049 /******************************************************************/
7050 /* TSFTextStore::Composition */
7051 /******************************************************************/
7053 void TSFTextStore::Composition::Start(ITfCompositionView* aCompositionView,
7054 LONG aCompositionStartOffset,
7055 const nsAString& aCompositionString) {
7056 mView = aCompositionView;
7057 mString = aCompositionString;
7058 mStart = aCompositionStartOffset;
7061 void TSFTextStore::Composition::End() {
7062 mView = nullptr;
7063 mString.Truncate();
7066 /******************************************************************************
7067 * TSFTextStore::Content
7068 *****************************************************************************/
7070 const nsDependentSubstring TSFTextStore::Content::GetSelectedText() const {
7071 MOZ_ASSERT(mInitialized);
7072 return GetSubstring(static_cast<uint32_t>(mSelection.StartOffset()),
7073 static_cast<uint32_t>(mSelection.Length()));
7076 const nsDependentSubstring TSFTextStore::Content::GetSubstring(
7077 uint32_t aStart, uint32_t aLength) const {
7078 MOZ_ASSERT(mInitialized);
7079 return nsDependentSubstring(mText, aStart, aLength);
7082 void TSFTextStore::Content::ReplaceSelectedTextWith(const nsAString& aString) {
7083 MOZ_ASSERT(mInitialized);
7084 ReplaceTextWith(mSelection.StartOffset(), mSelection.Length(), aString);
7087 inline uint32_t FirstDifferentCharOffset(const nsAString& aStr1,
7088 const nsAString& aStr2) {
7089 MOZ_ASSERT(aStr1 != aStr2);
7090 uint32_t i = 0;
7091 uint32_t minLength = std::min(aStr1.Length(), aStr2.Length());
7092 for (; i < minLength && aStr1[i] == aStr2[i]; i++) {
7093 /* nothing to do */
7095 return i;
7098 void TSFTextStore::Content::ReplaceTextWith(LONG aStart, LONG aLength,
7099 const nsAString& aReplaceString) {
7100 MOZ_ASSERT(mInitialized);
7101 const nsDependentSubstring replacedString = GetSubstring(
7102 static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength));
7103 if (aReplaceString != replacedString) {
7104 uint32_t firstDifferentOffset = mMinTextModifiedOffset;
7105 if (mComposition.IsComposing()) {
7106 // Emulate text insertion during compositions, because during a
7107 // composition, editor expects the whole composition string to
7108 // be sent in eCompositionChange, not just the inserted part.
7109 // The actual eCompositionChange will be sent in SetSelection
7110 // or OnUpdateComposition.
7111 MOZ_ASSERT(aStart >= mComposition.mStart);
7112 MOZ_ASSERT(aStart + aLength <= mComposition.EndOffset());
7113 mComposition.mString.Replace(
7114 static_cast<uint32_t>(aStart - mComposition.mStart),
7115 static_cast<uint32_t>(aLength), aReplaceString);
7116 // TIP may set composition string twice or more times during a document
7117 // lock. Therefore, we should compute the first difference offset with
7118 // mLastCompositionString.
7119 if (mComposition.mString != mLastCompositionString) {
7120 firstDifferentOffset = mComposition.mStart +
7121 FirstDifferentCharOffset(mComposition.mString,
7122 mLastCompositionString);
7123 // The previous change to the composition string is canceled.
7124 if (mMinTextModifiedOffset >=
7125 static_cast<uint32_t>(mComposition.mStart) &&
7126 mMinTextModifiedOffset < firstDifferentOffset) {
7127 mMinTextModifiedOffset = firstDifferentOffset;
7129 } else if (mMinTextModifiedOffset >=
7130 static_cast<uint32_t>(mComposition.mStart) &&
7131 mMinTextModifiedOffset <
7132 static_cast<uint32_t>(mComposition.EndOffset())) {
7133 // The previous change to the composition string is canceled.
7134 mMinTextModifiedOffset = firstDifferentOffset =
7135 mComposition.EndOffset();
7137 mLatestCompositionEndOffset = mComposition.EndOffset();
7138 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
7139 ("0x%p TSFTextStore::Content::ReplaceTextWith(aStart=%d, "
7140 "aLength=%d, aReplaceString=\"%s\"), mComposition={ mStart=%d, "
7141 "mString=\"%s\" }, mLastCompositionString=\"%s\", "
7142 "mMinTextModifiedOffset=%u, firstDifferentOffset=%u",
7143 this, aStart, aLength,
7144 GetEscapedUTF8String(aReplaceString).get(), mComposition.mStart,
7145 GetEscapedUTF8String(mComposition.mString).get(),
7146 GetEscapedUTF8String(mLastCompositionString).get(),
7147 mMinTextModifiedOffset, firstDifferentOffset));
7148 } else {
7149 firstDifferentOffset =
7150 static_cast<uint32_t>(aStart) +
7151 FirstDifferentCharOffset(aReplaceString, replacedString);
7153 mMinTextModifiedOffset =
7154 std::min(mMinTextModifiedOffset, firstDifferentOffset);
7155 mText.Replace(static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength),
7156 aReplaceString);
7158 // Selection should be collapsed at the end of the inserted string.
7159 mSelection.CollapseAt(static_cast<uint32_t>(aStart) +
7160 aReplaceString.Length());
7163 void TSFTextStore::Content::StartComposition(
7164 ITfCompositionView* aCompositionView, const PendingAction& aCompStart,
7165 bool aPreserveSelection) {
7166 MOZ_ASSERT(mInitialized);
7167 MOZ_ASSERT(aCompositionView);
7168 MOZ_ASSERT(!mComposition.mView);
7169 MOZ_ASSERT(aCompStart.mType == PendingAction::Type::eCompositionStart);
7171 mComposition.Start(
7172 aCompositionView, aCompStart.mSelectionStart,
7173 GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart),
7174 static_cast<uint32_t>(aCompStart.mSelectionLength)));
7175 mLatestCompositionStartOffset = mComposition.mStart;
7176 mLatestCompositionEndOffset = mComposition.EndOffset();
7177 if (!aPreserveSelection) {
7178 // XXX Do we need to set a new writing-mode here when setting a new
7179 // selection? Currently, we just preserve the existing value.
7180 WritingMode writingMode =
7181 mSelection.IsDirty() ? WritingMode() : mSelection.GetWritingMode();
7182 mSelection.SetSelection(mComposition.mStart, mComposition.mString.Length(),
7183 false, writingMode);
7187 void TSFTextStore::Content::RestoreCommittedComposition(
7188 ITfCompositionView* aCompositionView,
7189 const PendingAction& aCanceledCompositionEnd) {
7190 MOZ_ASSERT(mInitialized);
7191 MOZ_ASSERT(aCompositionView);
7192 MOZ_ASSERT(!mComposition.mView);
7193 MOZ_ASSERT(aCanceledCompositionEnd.mType ==
7194 PendingAction::Type::eCompositionEnd);
7195 MOZ_ASSERT(
7196 GetSubstring(
7197 static_cast<uint32_t>(aCanceledCompositionEnd.mSelectionStart),
7198 static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
7199 aCanceledCompositionEnd.mData);
7201 // Restore the committed string as composing string.
7202 mComposition.Start(aCompositionView, aCanceledCompositionEnd.mSelectionStart,
7203 aCanceledCompositionEnd.mData);
7204 mLatestCompositionStartOffset = mComposition.mStart;
7205 mLatestCompositionEndOffset = mComposition.EndOffset();
7208 void TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd) {
7209 MOZ_ASSERT(mInitialized);
7210 MOZ_ASSERT(mComposition.mView);
7211 MOZ_ASSERT(aCompEnd.mType == PendingAction::Type::eCompositionEnd);
7213 mSelection.CollapseAt(mComposition.mStart + aCompEnd.mData.Length());
7214 mComposition.End();
7217 /******************************************************************************
7218 * TSFTextStore::MouseTracker
7219 *****************************************************************************/
7221 TSFTextStore::MouseTracker::MouseTracker()
7222 : mStart(-1), mLength(-1), mCookie(kInvalidCookie) {}
7224 HRESULT
7225 TSFTextStore::MouseTracker::Init(TSFTextStore* aTextStore) {
7226 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
7227 ("0x%p TSFTextStore::MouseTracker::Init(aTextStore=0x%p), "
7228 "aTextStore->mMouseTrackers.Length()=%d",
7229 this, aTextStore->mMouseTrackers.Length()));
7231 if (&aTextStore->mMouseTrackers.LastElement() != this) {
7232 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7233 ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
7234 "this is not the last element of mMouseTrackers",
7235 this));
7236 return E_FAIL;
7238 if (aTextStore->mMouseTrackers.Length() > kInvalidCookie) {
7239 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7240 ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
7241 "no new cookie available",
7242 this));
7243 return E_FAIL;
7245 MOZ_ASSERT(!aTextStore->mMouseTrackers.IsEmpty(),
7246 "This instance must be in TSFTextStore::mMouseTrackers");
7247 mCookie = static_cast<DWORD>(aTextStore->mMouseTrackers.Length() - 1);
7248 return S_OK;
7251 HRESULT
7252 TSFTextStore::MouseTracker::AdviseSink(TSFTextStore* aTextStore,
7253 ITfRangeACP* aTextRange,
7254 ITfMouseSink* aMouseSink) {
7255 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
7256 ("0x%p TSFTextStore::MouseTracker::AdviseSink(aTextStore=0x%p, "
7257 "aTextRange=0x%p, aMouseSink=0x%p), mCookie=%d, mSink=0x%p",
7258 this, aTextStore, aTextRange, aMouseSink, mCookie, mSink.get()));
7259 MOZ_ASSERT(mCookie != kInvalidCookie, "This hasn't been initalized?");
7261 if (mSink) {
7262 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7263 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7264 "due to already being used",
7265 this));
7266 return E_FAIL;
7269 HRESULT hr = aTextRange->GetExtent(&mStart, &mLength);
7270 if (FAILED(hr)) {
7271 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7272 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7273 "due to failure of ITfRangeACP::GetExtent()",
7274 this));
7275 return hr;
7278 if (mStart < 0 || mLength <= 0) {
7279 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7280 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7281 "due to odd result of ITfRangeACP::GetExtent(), "
7282 "mStart=%d, mLength=%d",
7283 this, mStart, mLength));
7284 return E_INVALIDARG;
7287 nsAutoString textContent;
7288 if (NS_WARN_IF(!aTextStore->GetCurrentText(textContent))) {
7289 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7290 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7291 "due to failure of TSFTextStore::GetCurrentText()",
7292 this));
7293 return E_FAIL;
7296 if (textContent.Length() <= static_cast<uint32_t>(mStart) ||
7297 textContent.Length() < static_cast<uint32_t>(mStart + mLength)) {
7298 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7299 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7300 "due to out of range, mStart=%d, mLength=%d, "
7301 "textContent.Length()=%d",
7302 this, mStart, mLength, textContent.Length()));
7303 return E_INVALIDARG;
7306 mSink = aMouseSink;
7308 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
7309 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink(), "
7310 "succeeded, mStart=%d, mLength=%d, textContent.Length()=%d",
7311 this, mStart, mLength, textContent.Length()));
7312 return S_OK;
7315 void TSFTextStore::MouseTracker::UnadviseSink() {
7316 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
7317 ("0x%p TSFTextStore::MouseTracker::UnadviseSink(), "
7318 "mCookie=%d, mSink=0x%p, mStart=%d, mLength=%d",
7319 this, mCookie, mSink.get(), mStart, mLength));
7320 mSink = nullptr;
7321 mStart = mLength = -1;
7324 bool TSFTextStore::MouseTracker::OnMouseButtonEvent(ULONG aEdge,
7325 ULONG aQuadrant,
7326 DWORD aButtonStatus) {
7327 MOZ_ASSERT(IsUsing(), "The caller must check before calling OnMouseEvent()");
7329 BOOL eaten = FALSE;
7330 RefPtr<ITfMouseSink> sink = mSink;
7331 HRESULT hr = sink->OnMouseEvent(aEdge, aQuadrant, aButtonStatus, &eaten);
7333 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
7334 ("0x%p TSFTextStore::MouseTracker::OnMouseEvent(aEdge=%d, "
7335 "aQuadrant=%d, aButtonStatus=0x%08X), hr=0x%08X, eaten=%s",
7336 this, aEdge, aQuadrant, aButtonStatus, hr, GetBoolName(!!eaten)));
7338 return SUCCEEDED(hr) && eaten;
7341 #ifdef DEBUG
7342 // static
7343 bool TSFTextStore::CurrentKeyboardLayoutHasIME() {
7344 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
7345 TSFTextStore::GetInputProcessorProfiles();
7346 if (!inputProcessorProfiles) {
7347 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7348 ("TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED due to "
7349 "there is no input processor profiles instance"));
7350 return false;
7352 RefPtr<ITfInputProcessorProfileMgr> profileMgr;
7353 HRESULT hr = inputProcessorProfiles->QueryInterface(
7354 IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
7355 if (FAILED(hr) || !profileMgr) {
7356 // On Windows Vista or later, ImmIsIME() API always returns true.
7357 // If we failed to obtain the profile manager, we cannot know if current
7358 // keyboard layout has IME.
7359 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7360 (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to query "
7361 "ITfInputProcessorProfileMgr"));
7362 return false;
7365 TF_INPUTPROCESSORPROFILE profile;
7366 hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
7367 if (hr == S_FALSE) {
7368 return false; // not found or not active
7370 if (FAILED(hr)) {
7371 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7372 (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to retreive "
7373 "active profile"));
7374 return false;
7376 return (profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR);
7378 #endif // #ifdef DEBUG
7380 } // namespace widget
7381 } // namespace mozilla