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"
11 #include <comutil.h> // for _bstr_t
12 #include <oleauto.h> // for SysAllocString
16 #include "IMMHandler.h"
17 #include "KeyboardLayout.h"
18 #include "WinIMEHandler.h"
20 #include "mozilla/AutoRestore.h"
21 #include "mozilla/Logging.h"
22 #include "mozilla/StaticPrefs_intl.h"
23 #include "mozilla/Telemetry.h"
24 #include "mozilla/TextEventDispatcher.h"
25 #include "mozilla/TextEvents.h"
26 #include "mozilla/ToString.h"
27 #include "mozilla/WindowsVersion.h"
28 #include "mozilla/widget/WinRegistry.h"
30 #include "nsPrintfCString.h"
32 // For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync`
33 // rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
35 // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
36 mozilla::LazyLogModule
gIMELog("IMEHandler");
38 // TODO: GUID_PROP_URL has not been declared in the SDK yet. We should drop the
39 // `s` prefix after it's released by a new SDK and define it with #if.
40 static const GUID sGUID_PROP_URL
= {
44 {0xbc, 0xbf, 0x2e, 0x73, 0x93, 0x98, 0xe2, 0x34}};
50 * TSF related code should log its behavior even on release build especially
51 * in the interface methods.
53 * In interface methods, use LogLevel::Info.
54 * In internal methods, use LogLevel::Debug for logging normal behavior.
55 * For logging error, use LogLevel::Error.
57 * When an instance method is called, start with following text:
58 * "0x%p TSFFoo::Bar(", the 0x%p should be the "this" of the nsFoo.
59 * after that, start with:
61 * In an internal method, start with following text:
63 * When a static method is called, start with following text:
67 enum class TextInputProcessorID
{
68 // Internal use only. This won't be returned by TSFStaticSink::ActiveTIP().
71 // Not a TIP. E.g., simple keyboard layout or IMM-IME.
74 // Used for other TIPs, i.e., any TIPs which we don't support specifically.
78 eMicrosoftIMEForJapanese
,
79 eMicrosoftOfficeIME2010ForJapanese
,
90 // TIP for Traditional Chinese.
95 eMicrosoftNewChangJie
,
96 eMicrosoftNewPhonetic
,
100 // TIP for Simplified Chinese.
102 eMicrosoftPinyinNewExperienceInputStyle
,
106 eMicrosoftIMEForKorean
,
109 // Keyman Desktop, which can install various language keyboards.
113 static const char* GetBoolName(bool aBool
) { return aBool
? "true" : "false"; }
115 static void HandleSeparator(nsCString
& aDesc
) {
116 if (!aDesc
.IsEmpty()) {
117 aDesc
.AppendLiteral(" | ");
121 static const nsCString
GetFindFlagName(DWORD aFindFlag
) {
122 nsCString description
;
124 description
.AppendLiteral("no flags (0)");
127 if (aFindFlag
& TS_ATTR_FIND_BACKWARDS
) {
128 description
.AppendLiteral("TS_ATTR_FIND_BACKWARDS");
130 if (aFindFlag
& TS_ATTR_FIND_WANT_OFFSET
) {
131 HandleSeparator(description
);
132 description
.AppendLiteral("TS_ATTR_FIND_WANT_OFFSET");
134 if (aFindFlag
& TS_ATTR_FIND_UPDATESTART
) {
135 HandleSeparator(description
);
136 description
.AppendLiteral("TS_ATTR_FIND_UPDATESTART");
138 if (aFindFlag
& TS_ATTR_FIND_WANT_VALUE
) {
139 HandleSeparator(description
);
140 description
.AppendLiteral("TS_ATTR_FIND_WANT_VALUE");
142 if (aFindFlag
& TS_ATTR_FIND_WANT_END
) {
143 HandleSeparator(description
);
144 description
.AppendLiteral("TS_ATTR_FIND_WANT_END");
146 if (aFindFlag
& TS_ATTR_FIND_HIDDEN
) {
147 HandleSeparator(description
);
148 description
.AppendLiteral("TS_ATTR_FIND_HIDDEN");
150 if (description
.IsEmpty()) {
151 description
.AppendLiteral("Unknown (");
152 description
.AppendInt(static_cast<uint32_t>(aFindFlag
));
153 description
.Append(')');
158 class GetACPFromPointFlagName
: public nsAutoCString
{
160 explicit GetACPFromPointFlagName(DWORD aFlags
) {
162 AppendLiteral("no flags (0)");
165 if (aFlags
& GXFPF_ROUND_NEAREST
) {
166 AppendLiteral("GXFPF_ROUND_NEAREST");
167 aFlags
&= ~GXFPF_ROUND_NEAREST
;
169 if (aFlags
& GXFPF_NEAREST
) {
170 HandleSeparator(*this);
171 AppendLiteral("GXFPF_NEAREST");
172 aFlags
&= ~GXFPF_NEAREST
;
175 HandleSeparator(*this);
176 AppendLiteral("Unknown(");
177 AppendInt(static_cast<uint32_t>(aFlags
));
181 virtual ~GetACPFromPointFlagName() {}
184 static const char* GetFocusChangeName(
185 InputContextAction::FocusChange aFocusChange
) {
186 switch (aFocusChange
) {
187 case InputContextAction::FOCUS_NOT_CHANGED
:
188 return "FOCUS_NOT_CHANGED";
189 case InputContextAction::GOT_FOCUS
:
191 case InputContextAction::LOST_FOCUS
:
193 case InputContextAction::MENU_GOT_PSEUDO_FOCUS
:
194 return "MENU_GOT_PSEUDO_FOCUS";
195 case InputContextAction::MENU_LOST_PSEUDO_FOCUS
:
196 return "MENU_LOST_PSEUDO_FOCUS";
197 case InputContextAction::WIDGET_CREATED
:
198 return "WIDGET_CREATED";
204 static nsCString
GetCLSIDNameStr(REFCLSID aCLSID
) {
205 LPOLESTR str
= nullptr;
206 HRESULT hr
= ::StringFromCLSID(aCLSID
, &str
);
207 if (FAILED(hr
) || !str
|| !str
[0]) {
212 result
= NS_ConvertUTF16toUTF8(str
);
213 ::CoTaskMemFree(str
);
217 static nsCString
GetGUIDNameStr(REFGUID aGUID
) {
219 int len
= ::StringFromGUID2(aGUID
, str
, ArrayLength(str
));
220 if (!len
|| !str
[0]) {
224 return NS_ConvertUTF16toUTF8(str
);
227 static nsCString
GetGUIDNameStrWithTable(REFGUID aGUID
) {
228 #define RETURN_GUID_NAME(aNamedGUID) \
229 if (IsEqualGUID(aGUID, aNamedGUID)) { \
230 return nsLiteralCString(#aNamedGUID); \
233 RETURN_GUID_NAME(GUID_PROP_INPUTSCOPE
)
234 RETURN_GUID_NAME(sGUID_PROP_URL
)
235 RETURN_GUID_NAME(TSATTRID_OTHERS
)
236 RETURN_GUID_NAME(TSATTRID_Font
)
237 RETURN_GUID_NAME(TSATTRID_Font_FaceName
)
238 RETURN_GUID_NAME(TSATTRID_Font_SizePts
)
239 RETURN_GUID_NAME(TSATTRID_Font_Style
)
240 RETURN_GUID_NAME(TSATTRID_Font_Style_Bold
)
241 RETURN_GUID_NAME(TSATTRID_Font_Style_Italic
)
242 RETURN_GUID_NAME(TSATTRID_Font_Style_SmallCaps
)
243 RETURN_GUID_NAME(TSATTRID_Font_Style_Capitalize
)
244 RETURN_GUID_NAME(TSATTRID_Font_Style_Uppercase
)
245 RETURN_GUID_NAME(TSATTRID_Font_Style_Lowercase
)
246 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation
)
247 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_LasVegasLights
)
248 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_BlinkingBackground
)
249 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_SparkleText
)
250 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingBlackAnts
)
251 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingRedAnts
)
252 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_Shimmer
)
253 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeDown
)
254 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeRight
)
255 RETURN_GUID_NAME(TSATTRID_Font_Style_Emboss
)
256 RETURN_GUID_NAME(TSATTRID_Font_Style_Engrave
)
257 RETURN_GUID_NAME(TSATTRID_Font_Style_Hidden
)
258 RETURN_GUID_NAME(TSATTRID_Font_Style_Kerning
)
259 RETURN_GUID_NAME(TSATTRID_Font_Style_Outlined
)
260 RETURN_GUID_NAME(TSATTRID_Font_Style_Position
)
261 RETURN_GUID_NAME(TSATTRID_Font_Style_Protected
)
262 RETURN_GUID_NAME(TSATTRID_Font_Style_Shadow
)
263 RETURN_GUID_NAME(TSATTRID_Font_Style_Spacing
)
264 RETURN_GUID_NAME(TSATTRID_Font_Style_Weight
)
265 RETURN_GUID_NAME(TSATTRID_Font_Style_Height
)
266 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline
)
267 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Single
)
268 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Double
)
269 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough
)
270 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Single
)
271 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Double
)
272 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline
)
273 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Single
)
274 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Double
)
275 RETURN_GUID_NAME(TSATTRID_Font_Style_Blink
)
276 RETURN_GUID_NAME(TSATTRID_Font_Style_Subscript
)
277 RETURN_GUID_NAME(TSATTRID_Font_Style_Superscript
)
278 RETURN_GUID_NAME(TSATTRID_Font_Style_Color
)
279 RETURN_GUID_NAME(TSATTRID_Font_Style_BackgroundColor
)
280 RETURN_GUID_NAME(TSATTRID_Text
)
281 RETURN_GUID_NAME(TSATTRID_Text_VerticalWriting
)
282 RETURN_GUID_NAME(TSATTRID_Text_RightToLeft
)
283 RETURN_GUID_NAME(TSATTRID_Text_Orientation
)
284 RETURN_GUID_NAME(TSATTRID_Text_Language
)
285 RETURN_GUID_NAME(TSATTRID_Text_ReadOnly
)
286 RETURN_GUID_NAME(TSATTRID_Text_EmbeddedObject
)
287 RETURN_GUID_NAME(TSATTRID_Text_Alignment
)
288 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Left
)
289 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Right
)
290 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Center
)
291 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Justify
)
292 RETURN_GUID_NAME(TSATTRID_Text_Link
)
293 RETURN_GUID_NAME(TSATTRID_Text_Hyphenation
)
294 RETURN_GUID_NAME(TSATTRID_Text_Para
)
295 RETURN_GUID_NAME(TSATTRID_Text_Para_FirstLineIndent
)
296 RETURN_GUID_NAME(TSATTRID_Text_Para_LeftIndent
)
297 RETURN_GUID_NAME(TSATTRID_Text_Para_RightIndent
)
298 RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceAfter
)
299 RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceBefore
)
300 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing
)
301 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Single
)
302 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_OnePtFive
)
303 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Double
)
304 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_AtLeast
)
305 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Exactly
)
306 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Multiple
)
307 RETURN_GUID_NAME(TSATTRID_List
)
308 RETURN_GUID_NAME(TSATTRID_List_LevelIndel
)
309 RETURN_GUID_NAME(TSATTRID_List_Type
)
310 RETURN_GUID_NAME(TSATTRID_List_Type_Bullet
)
311 RETURN_GUID_NAME(TSATTRID_List_Type_Arabic
)
312 RETURN_GUID_NAME(TSATTRID_List_Type_LowerLetter
)
313 RETURN_GUID_NAME(TSATTRID_List_Type_UpperLetter
)
314 RETURN_GUID_NAME(TSATTRID_List_Type_LowerRoman
)
315 RETURN_GUID_NAME(TSATTRID_List_Type_UpperRoman
)
316 RETURN_GUID_NAME(TSATTRID_App
)
317 RETURN_GUID_NAME(TSATTRID_App_IncorrectSpelling
)
318 RETURN_GUID_NAME(TSATTRID_App_IncorrectGrammar
)
320 #undef RETURN_GUID_NAME
322 return GetGUIDNameStr(aGUID
);
325 static nsCString
GetRIIDNameStr(REFIID aRIID
) {
326 LPOLESTR str
= nullptr;
327 HRESULT hr
= ::StringFromIID(aRIID
, &str
);
328 if (FAILED(hr
) || !str
|| !str
[0]) {
332 nsAutoString
key(L
"Interface\\");
337 if (WinRegistry::GetString(HKEY_CLASSES_ROOT
, key
, u
""_ns
, buf
,
338 WinRegistry::kLegacyWinUtilsStringFlags
)) {
339 result
= NS_ConvertUTF16toUTF8(buf
);
341 result
= NS_ConvertUTF16toUTF8(str
);
344 ::CoTaskMemFree(str
);
348 static const char* GetCommonReturnValueName(HRESULT aResult
) {
355 return "E_ACCESSDENIED";
361 return "E_INVALIDARG";
363 return "E_NOINTERFACE";
367 return "E_OUTOFMEMORY";
371 return "E_UNEXPECTED";
373 return SUCCEEDED(aResult
) ? "Succeeded" : "Failed";
377 static const char* GetTextStoreReturnValueName(HRESULT aResult
) {
380 return "TS_E_FORMAT";
381 case TS_E_INVALIDPOINT
:
382 return "TS_E_INVALIDPOINT";
383 case TS_E_INVALIDPOS
:
384 return "TS_E_INVALIDPOS";
385 case TS_E_NOINTERFACE
:
386 return "TS_E_NOINTERFACE";
388 return "TS_E_NOLAYOUT";
390 return "TS_E_NOLOCK";
392 return "TS_E_NOOBJECT";
393 case TS_E_NOSELECTION
:
394 return "TS_E_NOSELECTION";
396 return "TS_E_NOSERVICE";
398 return "TS_E_READONLY";
399 case TS_E_SYNCHRONOUS
:
400 return "TS_E_SYNCHRONOUS";
404 return GetCommonReturnValueName(aResult
);
408 static const nsCString
GetSinkMaskNameStr(DWORD aSinkMask
) {
409 nsCString description
;
410 if (aSinkMask
& TS_AS_TEXT_CHANGE
) {
411 description
.AppendLiteral("TS_AS_TEXT_CHANGE");
413 if (aSinkMask
& TS_AS_SEL_CHANGE
) {
414 HandleSeparator(description
);
415 description
.AppendLiteral("TS_AS_SEL_CHANGE");
417 if (aSinkMask
& TS_AS_LAYOUT_CHANGE
) {
418 HandleSeparator(description
);
419 description
.AppendLiteral("TS_AS_LAYOUT_CHANGE");
421 if (aSinkMask
& TS_AS_ATTR_CHANGE
) {
422 HandleSeparator(description
);
423 description
.AppendLiteral("TS_AS_ATTR_CHANGE");
425 if (aSinkMask
& TS_AS_STATUS_CHANGE
) {
426 HandleSeparator(description
);
427 description
.AppendLiteral("TS_AS_STATUS_CHANGE");
429 if (description
.IsEmpty()) {
430 description
.AppendLiteral("not-specified");
435 static const nsCString
GetLockFlagNameStr(DWORD aLockFlags
) {
436 nsCString description
;
437 if ((aLockFlags
& TS_LF_READWRITE
) == TS_LF_READWRITE
) {
438 description
.AppendLiteral("TS_LF_READWRITE");
439 } else if (aLockFlags
& TS_LF_READ
) {
440 description
.AppendLiteral("TS_LF_READ");
442 if (aLockFlags
& TS_LF_SYNC
) {
443 if (!description
.IsEmpty()) {
444 description
.AppendLiteral(" | ");
446 description
.AppendLiteral("TS_LF_SYNC");
448 if (description
.IsEmpty()) {
449 description
.AppendLiteral("not-specified");
454 static const char* GetTextRunTypeName(TsRunType aRunType
) {
457 return "TS_RT_PLAIN";
459 return "TS_RT_HIDDEN";
461 return "TS_RT_OPAQUE";
467 static nsCString
GetColorName(const TF_DA_COLOR
& aColor
) {
468 switch (aColor
.type
) {
470 return "TF_CT_NONE"_ns
;
472 return nsPrintfCString("TF_CT_SYSCOLOR, nIndex:0x%08X",
473 static_cast<int32_t>(aColor
.nIndex
));
475 return nsPrintfCString("TF_CT_COLORREF, cr:0x%08X",
476 static_cast<int32_t>(aColor
.cr
));
479 return nsPrintfCString("Unknown(%08X)",
480 static_cast<int32_t>(aColor
.type
));
484 static nsCString
GetLineStyleName(TF_DA_LINESTYLE aLineStyle
) {
485 switch (aLineStyle
) {
487 return "TF_LS_NONE"_ns
;
489 return "TF_LS_SOLID"_ns
;
491 return "TF_LS_DOT"_ns
;
493 return "TF_LS_DASH"_ns
;
495 return "TF_LS_SQUIGGLE"_ns
;
497 return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aLineStyle
));
502 static nsCString
GetClauseAttrName(TF_DA_ATTR_INFO aAttr
) {
505 return "TF_ATTR_INPUT"_ns
;
506 case TF_ATTR_TARGET_CONVERTED
:
507 return "TF_ATTR_TARGET_CONVERTED"_ns
;
508 case TF_ATTR_CONVERTED
:
509 return "TF_ATTR_CONVERTED"_ns
;
510 case TF_ATTR_TARGET_NOTCONVERTED
:
511 return "TF_ATTR_TARGET_NOTCONVERTED"_ns
;
512 case TF_ATTR_INPUT_ERROR
:
513 return "TF_ATTR_INPUT_ERROR"_ns
;
514 case TF_ATTR_FIXEDCONVERTED
:
515 return "TF_ATTR_FIXEDCONVERTED"_ns
;
517 return "TF_ATTR_OTHER"_ns
;
519 return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aAttr
));
524 static nsCString
GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE
& aDispAttr
) {
527 str
+= GetColorName(aDispAttr
.crText
);
528 str
+= " }, crBk:{ ";
529 str
+= GetColorName(aDispAttr
.crBk
);
530 str
+= " }, lsStyle: ";
531 str
+= GetLineStyleName(aDispAttr
.lsStyle
);
532 str
+= ", fBoldLine: ";
533 str
+= GetBoolName(aDispAttr
.fBoldLine
);
534 str
+= ", crLine:{ ";
535 str
+= GetColorName(aDispAttr
.crLine
);
536 str
+= " }, bAttr: ";
537 str
+= GetClauseAttrName(aDispAttr
.bAttr
);
541 static const char* GetMouseButtonName(int16_t aButton
) {
543 case MouseButton::ePrimary
:
545 case MouseButton::eMiddle
:
546 return "MiddleButton";
547 case MouseButton::eSecondary
:
548 return "RightButton";
550 return "UnknownButton";
554 #define ADD_SEPARATOR_IF_NECESSARY(aStr) \
555 if (!aStr.IsEmpty()) { \
556 aStr.AppendLiteral(", "); \
559 static nsCString
GetMouseButtonsName(int16_t aButtons
) {
561 return "no buttons"_ns
;
564 if (aButtons
& MouseButtonsFlag::ePrimaryFlag
) {
565 names
= "LeftButton";
567 if (aButtons
& MouseButtonsFlag::eSecondaryFlag
) {
568 ADD_SEPARATOR_IF_NECESSARY(names
);
569 names
+= "RightButton";
571 if (aButtons
& MouseButtonsFlag::eMiddleFlag
) {
572 ADD_SEPARATOR_IF_NECESSARY(names
);
573 names
+= "MiddleButton";
575 if (aButtons
& MouseButtonsFlag::e4thFlag
) {
576 ADD_SEPARATOR_IF_NECESSARY(names
);
577 names
+= "4thButton";
579 if (aButtons
& MouseButtonsFlag::e5thFlag
) {
580 ADD_SEPARATOR_IF_NECESSARY(names
);
581 names
+= "5thButton";
586 static nsCString
GetModifiersName(Modifiers aModifiers
) {
587 if (aModifiers
== MODIFIER_NONE
) {
588 return "no modifiers"_ns
;
591 if (aModifiers
& MODIFIER_ALT
) {
592 names
= NS_DOM_KEYNAME_ALT
;
594 if (aModifiers
& MODIFIER_ALTGRAPH
) {
595 ADD_SEPARATOR_IF_NECESSARY(names
);
596 names
+= NS_DOM_KEYNAME_ALTGRAPH
;
598 if (aModifiers
& MODIFIER_CAPSLOCK
) {
599 ADD_SEPARATOR_IF_NECESSARY(names
);
600 names
+= NS_DOM_KEYNAME_CAPSLOCK
;
602 if (aModifiers
& MODIFIER_CONTROL
) {
603 ADD_SEPARATOR_IF_NECESSARY(names
);
604 names
+= NS_DOM_KEYNAME_CONTROL
;
606 if (aModifiers
& MODIFIER_FN
) {
607 ADD_SEPARATOR_IF_NECESSARY(names
);
608 names
+= NS_DOM_KEYNAME_FN
;
610 if (aModifiers
& MODIFIER_FNLOCK
) {
611 ADD_SEPARATOR_IF_NECESSARY(names
);
612 names
+= NS_DOM_KEYNAME_FNLOCK
;
614 if (aModifiers
& MODIFIER_META
) {
615 ADD_SEPARATOR_IF_NECESSARY(names
);
616 names
+= NS_DOM_KEYNAME_META
;
618 if (aModifiers
& MODIFIER_NUMLOCK
) {
619 ADD_SEPARATOR_IF_NECESSARY(names
);
620 names
+= NS_DOM_KEYNAME_NUMLOCK
;
622 if (aModifiers
& MODIFIER_SCROLLLOCK
) {
623 ADD_SEPARATOR_IF_NECESSARY(names
);
624 names
+= NS_DOM_KEYNAME_SCROLLLOCK
;
626 if (aModifiers
& MODIFIER_SHIFT
) {
627 ADD_SEPARATOR_IF_NECESSARY(names
);
628 names
+= NS_DOM_KEYNAME_SHIFT
;
630 if (aModifiers
& MODIFIER_SYMBOL
) {
631 ADD_SEPARATOR_IF_NECESSARY(names
);
632 names
+= NS_DOM_KEYNAME_SYMBOL
;
634 if (aModifiers
& MODIFIER_SYMBOLLOCK
) {
635 ADD_SEPARATOR_IF_NECESSARY(names
);
636 names
+= NS_DOM_KEYNAME_SYMBOLLOCK
;
641 class GetWritingModeName
: public nsAutoCString
{
643 explicit GetWritingModeName(const WritingMode
& aWritingMode
) {
644 if (!aWritingMode
.IsVertical()) {
645 AssignLiteral("Horizontal");
648 if (aWritingMode
.IsVerticalLR()) {
649 AssignLiteral("Vertical (LR)");
652 AssignLiteral("Vertical (RL)");
654 virtual ~GetWritingModeName() {}
657 class GetEscapedUTF8String final
: public NS_ConvertUTF16toUTF8
{
659 explicit GetEscapedUTF8String(const nsAString
& aString
)
660 : NS_ConvertUTF16toUTF8(aString
) {
663 explicit GetEscapedUTF8String(const char16ptr_t aString
)
664 : NS_ConvertUTF16toUTF8(aString
) {
667 GetEscapedUTF8String(const char16ptr_t aString
, uint32_t aLength
)
668 : NS_ConvertUTF16toUTF8(aString
, aLength
) {
674 ReplaceSubstring("\r", "\\r");
675 ReplaceSubstring("\n", "\\n");
676 ReplaceSubstring("\t", "\\t");
680 class GetInputScopeString
: public nsAutoCString
{
682 explicit GetInputScopeString(const nsTArray
<InputScope
>& aList
) {
683 for (InputScope inputScope
: aList
) {
687 switch (inputScope
) {
689 AppendLiteral("IS_DEFAULT");
692 AppendLiteral("IS_URL");
694 case IS_FILE_FULLFILEPATH
:
695 AppendLiteral("IS_FILE_FULLFILEPATH");
697 case IS_FILE_FILENAME
:
698 AppendLiteral("IS_FILE_FILENAME");
700 case IS_EMAIL_USERNAME
:
701 AppendLiteral("IS_EMAIL_USERNAME");
703 case IS_EMAIL_SMTPEMAILADDRESS
:
704 AppendLiteral("IS_EMAIL_SMTPEMAILADDRESS");
707 AppendLiteral("IS_LOGINNAME");
709 case IS_PERSONALNAME_FULLNAME
:
710 AppendLiteral("IS_PERSONALNAME_FULLNAME");
712 case IS_PERSONALNAME_PREFIX
:
713 AppendLiteral("IS_PERSONALNAME_PREFIX");
715 case IS_PERSONALNAME_GIVENNAME
:
716 AppendLiteral("IS_PERSONALNAME_GIVENNAME");
718 case IS_PERSONALNAME_MIDDLENAME
:
719 AppendLiteral("IS_PERSONALNAME_MIDDLENAME");
721 case IS_PERSONALNAME_SURNAME
:
722 AppendLiteral("IS_PERSONALNAME_SURNAME");
724 case IS_PERSONALNAME_SUFFIX
:
725 AppendLiteral("IS_PERSONALNAME_SUFFIX");
727 case IS_ADDRESS_FULLPOSTALADDRESS
:
728 AppendLiteral("IS_ADDRESS_FULLPOSTALADDRESS");
730 case IS_ADDRESS_POSTALCODE
:
731 AppendLiteral("IS_ADDRESS_POSTALCODE");
733 case IS_ADDRESS_STREET
:
734 AppendLiteral("IS_ADDRESS_STREET");
736 case IS_ADDRESS_STATEORPROVINCE
:
737 AppendLiteral("IS_ADDRESS_STATEORPROVINCE");
739 case IS_ADDRESS_CITY
:
740 AppendLiteral("IS_ADDRESS_CITY");
742 case IS_ADDRESS_COUNTRYNAME
:
743 AppendLiteral("IS_ADDRESS_COUNTRYNAME");
745 case IS_ADDRESS_COUNTRYSHORTNAME
:
746 AppendLiteral("IS_ADDRESS_COUNTRYSHORTNAME");
748 case IS_CURRENCY_AMOUNTANDSYMBOL
:
749 AppendLiteral("IS_CURRENCY_AMOUNTANDSYMBOL");
751 case IS_CURRENCY_AMOUNT
:
752 AppendLiteral("IS_CURRENCY_AMOUNT");
754 case IS_DATE_FULLDATE
:
755 AppendLiteral("IS_DATE_FULLDATE");
758 AppendLiteral("IS_DATE_MONTH");
761 AppendLiteral("IS_DATE_DAY");
764 AppendLiteral("IS_DATE_YEAR");
766 case IS_DATE_MONTHNAME
:
767 AppendLiteral("IS_DATE_MONTHNAME");
769 case IS_DATE_DAYNAME
:
770 AppendLiteral("IS_DATE_DAYNAME");
773 AppendLiteral("IS_DIGITS");
776 AppendLiteral("IS_NUMBER");
779 AppendLiteral("IS_ONECHAR");
782 AppendLiteral("IS_PASSWORD");
784 case IS_TELEPHONE_FULLTELEPHONENUMBER
:
785 AppendLiteral("IS_TELEPHONE_FULLTELEPHONENUMBER");
787 case IS_TELEPHONE_COUNTRYCODE
:
788 AppendLiteral("IS_TELEPHONE_COUNTRYCODE");
790 case IS_TELEPHONE_AREACODE
:
791 AppendLiteral("IS_TELEPHONE_AREACODE");
793 case IS_TELEPHONE_LOCALNUMBER
:
794 AppendLiteral("IS_TELEPHONE_LOCALNUMBER");
796 case IS_TIME_FULLTIME
:
797 AppendLiteral("IS_TIME_FULLTIME");
800 AppendLiteral("IS_TIME_HOUR");
802 case IS_TIME_MINORSEC
:
803 AppendLiteral("IS_TIME_MINORSEC");
805 case IS_NUMBER_FULLWIDTH
:
806 AppendLiteral("IS_NUMBER_FULLWIDTH");
808 case IS_ALPHANUMERIC_HALFWIDTH
:
809 AppendLiteral("IS_ALPHANUMERIC_HALFWIDTH");
811 case IS_ALPHANUMERIC_FULLWIDTH
:
812 AppendLiteral("IS_ALPHANUMERIC_FULLWIDTH");
814 case IS_CURRENCY_CHINESE
:
815 AppendLiteral("IS_CURRENCY_CHINESE");
818 AppendLiteral("IS_BOPOMOFO");
821 AppendLiteral("IS_HIRAGANA");
823 case IS_KATAKANA_HALFWIDTH
:
824 AppendLiteral("IS_KATAKANA_HALFWIDTH");
826 case IS_KATAKANA_FULLWIDTH
:
827 AppendLiteral("IS_KATAKANA_FULLWIDTH");
830 AppendLiteral("IS_HANJA");
833 AppendLiteral("IS_PHRASELIST");
835 case IS_REGULAREXPRESSION
:
836 AppendLiteral("IS_REGULAREXPRESSION");
839 AppendLiteral("IS_SRGS");
842 AppendLiteral("IS_XML");
845 AppendLiteral("IS_PRIVATE");
848 AppendPrintf("Unknown Value(%d)", inputScope
);
855 /******************************************************************/
857 /******************************************************************/
859 class InputScopeImpl final
: public ITfInputScope
{
863 explicit InputScopeImpl(const nsTArray
<InputScope
>& aList
)
864 : mInputScopes(aList
.Clone()) {
866 gIMELog
, LogLevel::Info
,
867 ("0x%p InputScopeImpl(%s)", this, GetInputScopeString(aList
).get()));
870 NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(InputScopeImpl
)
872 STDMETHODIMP
QueryInterface(REFIID riid
, void** ppv
) {
874 if ((IID_IUnknown
== riid
) || (IID_ITfInputScope
== riid
)) {
875 *ppv
= static_cast<ITfInputScope
*>(this);
881 return E_NOINTERFACE
;
884 STDMETHODIMP
GetInputScopes(InputScope
** pprgInputScopes
, UINT
* pcCount
) {
885 uint32_t count
= (mInputScopes
.IsEmpty() ? 1 : mInputScopes
.Length());
888 (InputScope
*)CoTaskMemAlloc(sizeof(InputScope
) * count
);
889 NS_ENSURE_TRUE(pScope
, E_OUTOFMEMORY
);
891 if (mInputScopes
.IsEmpty()) {
892 *pScope
= IS_DEFAULT
;
894 *pprgInputScopes
= pScope
;
900 for (uint32_t idx
= 0; idx
< count
; idx
++) {
901 *(pScope
+ idx
) = mInputScopes
[idx
];
905 *pprgInputScopes
= pScope
;
909 STDMETHODIMP
GetPhrase(BSTR
** ppbstrPhrases
, UINT
* pcCount
) {
912 STDMETHODIMP
GetRegularExpression(BSTR
* pbstrRegExp
) { return E_NOTIMPL
; }
913 STDMETHODIMP
GetSRGS(BSTR
* pbstrSRGS
) { return E_NOTIMPL
; }
914 STDMETHODIMP
GetXML(BSTR
* pbstrXML
) { return E_NOTIMPL
; }
917 nsTArray
<InputScope
> mInputScopes
;
920 /******************************************************************/
922 /******************************************************************/
924 class TSFStaticSink final
: public ITfInputProcessorProfileActivationSink
{
926 static TSFStaticSink
* GetInstance() {
928 RefPtr
<ITfThreadMgr
> threadMgr
= TSFTextStore::GetThreadMgr();
929 if (NS_WARN_IF(!threadMgr
)) {
931 gIMELog
, LogLevel::Error
,
932 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
933 "instance due to no ThreadMgr instance"));
936 RefPtr
<ITfInputProcessorProfiles
> inputProcessorProfiles
=
937 TSFTextStore::GetInputProcessorProfiles();
938 if (NS_WARN_IF(!inputProcessorProfiles
)) {
940 gIMELog
, LogLevel::Error
,
941 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
942 "instance due to no InputProcessorProfiles instance"));
945 RefPtr
<TSFStaticSink
> staticSink
= new TSFStaticSink();
946 if (NS_WARN_IF(!staticSink
->Init(threadMgr
, inputProcessorProfiles
))) {
947 staticSink
->Destroy();
949 gIMELog
, LogLevel::Error
,
950 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
954 sInstance
= staticSink
.forget();
959 static void Shutdown() {
961 sInstance
->Destroy();
966 bool Init(ITfThreadMgr
* aThreadMgr
,
967 ITfInputProcessorProfiles
* aInputProcessorProfiles
);
968 STDMETHODIMP
QueryInterface(REFIID riid
, void** ppv
) {
970 if (IID_IUnknown
== riid
||
971 IID_ITfInputProcessorProfileActivationSink
== riid
) {
972 *ppv
= static_cast<ITfInputProcessorProfileActivationSink
*>(this);
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() {
1001 return sInstance
&& sInstance
->IsTraditionalChineseInternal();
1003 static bool IsSimplifiedChinese() {
1005 return sInstance
&& sInstance
->IsSimplifiedChineseInternal();
1007 static bool IsJapanese() {
1009 return sInstance
&& sInstance
->IsJapaneseInternal();
1011 static bool IsKorean() {
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
1023 static TextInputProcessorID
ActiveTIP() {
1025 if (!sInstance
|| !sInstance
->EnsureInitActiveTIPKeyboard()) {
1026 return TextInputProcessorID::eUnknown
;
1028 sInstance
->ComputeActiveTextInputProcessor();
1029 if (NS_WARN_IF(sInstance
->mActiveTIP
==
1030 TextInputProcessorID::eNotComputed
)) {
1031 return TextInputProcessorID::eUnknown
;
1033 return sInstance
->mActiveTIP
;
1036 static bool GetActiveTIPNameForTelemetry(nsAString
& aName
) {
1037 if (!sInstance
|| !sInstance
->EnsureInitActiveTIPKeyboard()) {
1040 if (sInstance
->mActiveTIPGUID
== GUID_NULL
) {
1042 aName
.AppendPrintf("0x%04X", sInstance
->mLangID
);
1045 // key should be "LocaleID|Description". Although GUID of the
1046 // profile is unique key since description may be localized for system
1047 // language, unfortunately, it's too long to record as key with its
1048 // description. Therefore, we should record only the description with
1049 // LocaleID because Microsoft IME may not include language information.
1050 // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
1052 aName
.AppendPrintf("0x%04X|", sInstance
->mLangID
);
1053 nsAutoString description
;
1054 description
.Assign(sInstance
->mActiveTIPKeyboardDescription
);
1055 static const uint32_t kMaxDescriptionLength
= 72 - aName
.Length();
1056 if (description
.Length() > kMaxDescriptionLength
) {
1057 if (NS_IS_LOW_SURROGATE(description
[kMaxDescriptionLength
- 1]) &&
1058 NS_IS_HIGH_SURROGATE(description
[kMaxDescriptionLength
- 2])) {
1059 description
.Truncate(kMaxDescriptionLength
- 2);
1061 description
.Truncate(kMaxDescriptionLength
- 1);
1064 description
.Append(char16_t(0x2026));
1066 aName
.Append(description
);
1070 static bool IsMSChangJieOrMSQuickActive() {
1071 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1072 // For avoiding unnecessary computation, we should check if the language
1073 // for current TIP is Traditional Chinese.
1074 if (!IsTraditionalChinese()) {
1077 switch (ActiveTIP()) {
1078 case TextInputProcessorID::eMicrosoftChangJie
:
1079 case TextInputProcessorID::eMicrosoftQuick
:
1086 static bool IsMSPinyinOrMSWubiActive() {
1087 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1088 // For avoiding unnecessary computation, we should check if the language
1089 // for current TIP is Simplified Chinese.
1090 if (!IsSimplifiedChinese()) {
1093 switch (ActiveTIP()) {
1094 case TextInputProcessorID::eMicrosoftPinyin
:
1095 case TextInputProcessorID::eMicrosoftWubi
:
1102 static bool IsMSJapaneseIMEActive() {
1103 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1104 // For avoiding unnecessary computation, we should check if the language
1105 // for current TIP is Japanese.
1106 if (!IsJapanese()) {
1109 return ActiveTIP() == TextInputProcessorID::eMicrosoftIMEForJapanese
;
1112 static bool IsGoogleJapaneseInputActive() {
1113 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1114 // For avoiding unnecessary computation, we should check if the language
1115 // for current TIP is Japanese.
1116 if (!IsJapanese()) {
1119 return ActiveTIP() == TextInputProcessorID::eGoogleJapaneseInput
;
1122 static bool IsATOKActive() {
1123 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1124 // For avoiding unnecessary computation, we should check if active TIP is
1125 // ATOK first since it's cheaper.
1126 return IsJapanese() && sInstance
->IsATOKActiveInternal();
1129 // Note that ATOK 2011 - 2016 refers native caret position for deciding its
1130 // popup window position.
1131 static bool IsATOKReferringNativeCaretActive() {
1132 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1133 // For avoiding unnecessary computation, we should check if active TIP is
1134 // ATOK first since it's cheaper.
1135 if (!IsJapanese() || !sInstance
->IsATOKActiveInternal()) {
1138 switch (ActiveTIP()) {
1139 case TextInputProcessorID::eATOK2011
:
1140 case TextInputProcessorID::eATOK2012
:
1141 case TextInputProcessorID::eATOK2013
:
1142 case TextInputProcessorID::eATOK2014
:
1143 case TextInputProcessorID::eATOK2015
:
1151 static void EnsureInstance() {
1153 RefPtr
<TSFStaticSink
> staticSink
= GetInstance();
1154 Unused
<< staticSink
;
1158 bool IsTraditionalChineseInternal() const { return mLangID
== 0x0404; }
1159 bool IsSimplifiedChineseInternal() const { return mLangID
== 0x0804; }
1160 bool IsJapaneseInternal() const { return mLangID
== 0x0411; }
1161 bool IsKoreanInternal() const { return mLangID
== 0x0412; }
1163 bool IsATOKActiveInternal() {
1164 EnsureInitActiveTIPKeyboard();
1165 // FYI: Name of packaged ATOK includes the release year like "ATOK 2015".
1166 // Name of ATOK Passport (subscription) equals "ATOK".
1167 return StringBeginsWith(mActiveTIPKeyboardDescription
, u
"ATOK "_ns
) ||
1168 mActiveTIPKeyboardDescription
.EqualsLiteral("ATOK");
1171 void ComputeActiveTextInputProcessor() {
1172 if (mActiveTIP
!= TextInputProcessorID::eNotComputed
) {
1176 if (mActiveTIPGUID
== GUID_NULL
) {
1177 mActiveTIP
= TextInputProcessorID::eNone
;
1181 // Comparing GUID is slow. So, we should use language information to
1182 // reduce the comparing cost for TIP which is not we do not support
1183 // specifically since they are always compared with all supported TIPs.
1186 mActiveTIP
= ComputeActiveTIPAsTraditionalChinese();
1189 mActiveTIP
= ComputeActiveTIPAsJapanese();
1192 mActiveTIP
= ComputeActiveTIPAsKorean();
1195 mActiveTIP
= ComputeActiveTIPAsSimplifiedChinese();
1198 mActiveTIP
= TextInputProcessorID::eUnknown
;
1201 // Special case for Keyman Desktop, it is available for any languages.
1202 // Therefore, we need to check it only if we don't know the active TIP.
1203 if (mActiveTIP
!= TextInputProcessorID::eUnknown
) {
1207 // Note that keyboard layouts for Keyman assign its GUID on install
1208 // randomly, but CLSID is constant in any environments.
1209 // https://bugzilla.mozilla.org/show_bug.cgi?id=1670834#c7
1210 // https://github.com/keymanapp/keyman/blob/318c73a9e1d571d942837ff9964590626e5bd5aa/windows/src/engine/kmtip/globals.cpp#L37
1211 // {FE0420F1-38D1-4B4C-96BF-E7E20A74CFB7}
1212 static constexpr CLSID kKeymanDesktop_CLSID
= {
1216 {0x96, 0xBF, 0xE7, 0xE2, 0x0A, 0x74, 0xCF, 0xB7}};
1217 if (mActiveTIPCLSID
== kKeymanDesktop_CLSID
) {
1218 mActiveTIP
= TextInputProcessorID::eKeymanDesktop
;
1222 TextInputProcessorID
ComputeActiveTIPAsJapanese() {
1223 // {A76C93D9-5523-4E90-AAFA-4DB112F9AC76} (Win7, Win8.1, Win10)
1224 static constexpr GUID kMicrosoftIMEForJapaneseGUID
= {
1228 {0xAA, 0xFA, 0x4D, 0xB1, 0x12, 0xF9, 0xAC, 0x76}};
1229 if (mActiveTIPGUID
== kMicrosoftIMEForJapaneseGUID
) {
1230 return TextInputProcessorID::eMicrosoftIMEForJapanese
;
1232 // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64}
1233 static constexpr GUID kMicrosoftOfficeIME2010ForJapaneseGUID
= {
1237 {0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64}};
1238 if (mActiveTIPGUID
== kMicrosoftOfficeIME2010ForJapaneseGUID
) {
1239 return TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese
;
1241 // {773EB24E-CA1D-4B1B-B420-FA985BB0B80D}
1242 static constexpr GUID kGoogleJapaneseInputGUID
= {
1246 {0xB4, 0x20, 0xFA, 0x98, 0x5B, 0xB0, 0xB8, 0x0D}};
1247 if (mActiveTIPGUID
== kGoogleJapaneseInputGUID
) {
1248 return TextInputProcessorID::eGoogleJapaneseInput
;
1250 // {F9C24A5C-8A53-499D-9572-93B2FF582115}
1251 static const GUID kATOK2011GUID
= {
1255 {0x95, 0x72, 0x93, 0xB2, 0xFF, 0x58, 0x21, 0x15}};
1256 if (mActiveTIPGUID
== kATOK2011GUID
) {
1257 return TextInputProcessorID::eATOK2011
;
1259 // {1DE01562-F445-401B-B6C3-E5B18DB79461}
1260 static constexpr GUID kATOK2012GUID
= {
1264 {0xB6, 0xC3, 0xE5, 0xB1, 0x8D, 0xB7, 0x94, 0x61}};
1265 if (mActiveTIPGUID
== kATOK2012GUID
) {
1266 return TextInputProcessorID::eATOK2012
;
1268 // {3C4DB511-189A-4168-B6EA-BFD0B4C85615}
1269 static constexpr GUID kATOK2013GUID
= {
1273 {0xB6, 0xEA, 0xBF, 0xD0, 0xB4, 0xC8, 0x56, 0x15}};
1274 if (mActiveTIPGUID
== kATOK2013GUID
) {
1275 return TextInputProcessorID::eATOK2013
;
1277 // {4EF33B79-6AA9-4271-B4BF-9321C279381B}
1278 static constexpr GUID kATOK2014GUID
= {
1282 {0xB4, 0xBF, 0x93, 0x21, 0xC2, 0x79, 0x38, 0x1B}};
1283 if (mActiveTIPGUID
== kATOK2014GUID
) {
1284 return TextInputProcessorID::eATOK2014
;
1286 // {EAB4DC00-CE2E-483D-A86A-E6B99DA9599A}
1287 static constexpr GUID kATOK2015GUID
= {
1291 {0xA8, 0x6A, 0xE6, 0xB9, 0x9D, 0xA9, 0x59, 0x9A}};
1292 if (mActiveTIPGUID
== kATOK2015GUID
) {
1293 return TextInputProcessorID::eATOK2015
;
1295 // {0B557B4C-5740-4110-A60A-1493FA10BF2B}
1296 static constexpr GUID kATOK2016GUID
= {
1300 {0xA6, 0x0A, 0x14, 0x93, 0xFA, 0x10, 0xBF, 0x2B}};
1301 if (mActiveTIPGUID
== kATOK2016GUID
) {
1302 return TextInputProcessorID::eATOK2016
;
1306 // - {6DBFD8F5-701D-11E6-920F-782BCBA6348F}
1307 // * ATOK Passport (confirmed with version 31.1.2)
1308 // - {A38F2FD9-7199-45E1-841C-BE0313D8052F}
1310 if (IsATOKActiveInternal()) {
1311 return TextInputProcessorID::eATOKUnknown
;
1314 // {E6D66705-1EDA-4373-8D01-1D0CB2D054C7}
1315 static constexpr GUID kJapanist10GUID
= {
1319 {0x8D, 0x01, 0x1D, 0x0C, 0xB2, 0xD0, 0x54, 0xC7}};
1320 if (mActiveTIPGUID
== kJapanist10GUID
) {
1321 return TextInputProcessorID::eJapanist10
;
1324 return TextInputProcessorID::eUnknown
;
1327 TextInputProcessorID
ComputeActiveTIPAsTraditionalChinese() {
1328 // {B2F9C502-1742-11D4-9790-0080C882687E} (Win8.1, Win10)
1329 static constexpr GUID kMicrosoftBopomofoGUID
= {
1333 {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1334 if (mActiveTIPGUID
== kMicrosoftBopomofoGUID
) {
1335 return TextInputProcessorID::eMicrosoftBopomofo
;
1337 // {4BDF9F03-C7D3-11D4-B2AB-0080C882687E} (Win7, Win8.1, Win10)
1338 static const GUID kMicrosoftChangJieGUID
= {
1342 {0xB2, 0xAB, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1343 if (mActiveTIPGUID
== kMicrosoftChangJieGUID
) {
1344 return TextInputProcessorID::eMicrosoftChangJie
;
1346 // {761309DE-317A-11D4-9B5D-0080C882687E} (Win7)
1347 static constexpr GUID kMicrosoftPhoneticGUID
= {
1351 {0x9B, 0x5D, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1352 if (mActiveTIPGUID
== kMicrosoftPhoneticGUID
) {
1353 return TextInputProcessorID::eMicrosoftPhonetic
;
1355 // {6024B45F-5C54-11D4-B921-0080C882687E} (Win7, Win8.1, Win10)
1356 static constexpr GUID kMicrosoftQuickGUID
= {
1360 {0xB9, 0x21, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1361 if (mActiveTIPGUID
== kMicrosoftQuickGUID
) {
1362 return TextInputProcessorID::eMicrosoftQuick
;
1364 // {F3BA907A-6C7E-11D4-97FA-0080C882687E} (Win7)
1365 static constexpr GUID kMicrosoftNewChangJieGUID
= {
1369 {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1370 if (mActiveTIPGUID
== kMicrosoftNewChangJieGUID
) {
1371 return TextInputProcessorID::eMicrosoftNewChangJie
;
1373 // {B2F9C502-1742-11D4-9790-0080C882687E} (Win7)
1374 static constexpr GUID kMicrosoftNewPhoneticGUID
= {
1378 {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1379 if (mActiveTIPGUID
== kMicrosoftNewPhoneticGUID
) {
1380 return TextInputProcessorID::eMicrosoftNewPhonetic
;
1382 // {0B883BA0-C1C7-11D4-87F9-0080C882687E} (Win7)
1383 static constexpr GUID kMicrosoftNewQuickGUID
= {
1387 {0x87, 0xF9, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1388 if (mActiveTIPGUID
== kMicrosoftNewQuickGUID
) {
1389 return TextInputProcessorID::eMicrosoftNewQuick
;
1392 // NOTE: There are some other Traditional Chinese TIPs installed in Windows:
1393 // * Chinese Traditional Array (version 6.0)
1394 // - {D38EFF65-AA46-4FD5-91A7-67845FB02F5B} (Win7, Win8.1)
1395 // * Chinese Traditional DaYi (version 6.0)
1396 // - {037B2C25-480C-4D7F-B027-D6CA6B69788A} (Win7, Win8.1)
1398 // {B58630B5-0ED3-4335-BBC9-E77BBCB43CAD}
1399 static const GUID kFreeChangJieGUID
= {
1403 {0xBB, 0xC9, 0xE7, 0x7B, 0xBC, 0xB4, 0x3C, 0xAD}};
1404 if (mActiveTIPGUID
== kFreeChangJieGUID
) {
1405 return TextInputProcessorID::eFreeChangJie
;
1408 return TextInputProcessorID::eUnknown
;
1411 TextInputProcessorID
ComputeActiveTIPAsSimplifiedChinese() {
1412 // FYI: This matches with neither "Microsoft Pinyin ABC Input Style" nor
1413 // "Microsoft Pinyin New Experience Input Style" on Win7.
1414 // {FA550B04-5AD7-411F-A5AC-CA038EC515D7} (Win8.1, Win10)
1415 static constexpr GUID kMicrosoftPinyinGUID
= {
1419 {0xA5, 0xAC, 0xCA, 0x03, 0x8E, 0xC5, 0x15, 0xD7}};
1420 if (mActiveTIPGUID
== kMicrosoftPinyinGUID
) {
1421 return TextInputProcessorID::eMicrosoftPinyin
;
1424 // {F3BA9077-6C7E-11D4-97FA-0080C882687E} (Win7)
1425 static constexpr GUID kMicrosoftPinyinNewExperienceInputStyleGUID
= {
1429 {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1430 if (mActiveTIPGUID
== kMicrosoftPinyinNewExperienceInputStyleGUID
) {
1431 return TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle
;
1433 // {82590C13-F4DD-44F4-BA1D-8667246FDF8E} (Win8.1, Win10)
1434 static constexpr GUID kMicrosoftWubiGUID
= {
1438 {0xBA, 0x1D, 0x86, 0x67, 0x24, 0x6F, 0xDF, 0x8E}};
1439 if (mActiveTIPGUID
== kMicrosoftWubiGUID
) {
1440 return TextInputProcessorID::eMicrosoftWubi
;
1442 // NOTE: There are some other Simplified Chinese TIPs installed in Windows:
1443 // * Chinese Simplified QuanPin (version 6.0)
1444 // - {54FC610E-6ABD-4685-9DDD-A130BDF1B170} (Win8.1)
1445 // * Chinese Simplified ZhengMa (version 6.0)
1446 // - {733B4D81-3BC3-4132-B91A-E9CDD5E2BFC9} (Win8.1)
1447 // * Chinese Simplified ShuangPin (version 6.0)
1448 // - {EF63706D-31C4-490E-9DBB-BD150ADC454B} (Win8.1)
1449 // * Microsoft Pinyin ABC Input Style
1450 // - {FCA121D2-8C6D-41FB-B2DE-A2AD110D4820} (Win7)
1451 return TextInputProcessorID::eUnknown
;
1454 TextInputProcessorID
ComputeActiveTIPAsKorean() {
1455 // {B5FE1F02-D5F2-4445-9C03-C568F23C99A1} (Win7, Win8.1, Win10)
1456 static constexpr GUID kMicrosoftIMEForKoreanGUID
= {
1460 {0x9C, 0x03, 0xC5, 0x68, 0xF2, 0x3C, 0x99, 0xA1}};
1461 if (mActiveTIPGUID
== kMicrosoftIMEForKoreanGUID
) {
1462 return TextInputProcessorID::eMicrosoftIMEForKorean
;
1464 // {B60AF051-257A-46BC-B9D3-84DAD819BAFB} (Win8.1, Win10)
1465 static constexpr GUID kMicrosoftOldHangulGUID
= {
1469 {0xB9, 0xD3, 0x84, 0xDA, 0xD8, 0x19, 0xBA, 0xFB}};
1470 if (mActiveTIPGUID
== kMicrosoftOldHangulGUID
) {
1471 return TextInputProcessorID::eMicrosoftOldHangul
;
1474 // NOTE: There is the other Korean TIP installed in Windows:
1475 // * Microsoft IME 2010
1476 // - {48878C45-93F9-4aaf-A6A1-272CD863C4F5} (Win7)
1478 return TextInputProcessorID::eUnknown
;
1481 public: // ITfInputProcessorProfileActivationSink
1482 STDMETHODIMP
OnActivated(DWORD
, LANGID
, REFCLSID
, REFGUID
, REFGUID
, HKL
,
1487 virtual ~TSFStaticSink() {}
1489 bool EnsureInitActiveTIPKeyboard();
1493 void GetTIPDescription(REFCLSID aTextService
, LANGID aLangID
,
1494 REFGUID aProfile
, nsAString
& aDescription
);
1495 bool IsTIPCategoryKeyboard(REFCLSID aTextService
, LANGID aLangID
,
1498 TextInputProcessorID mActiveTIP
;
1500 // Cookie of installing ITfInputProcessorProfileActivationSink
1501 DWORD mIPProfileCookie
;
1505 // True if current IME is implemented with IMM.
1507 // True if OnActivated() is already called
1508 bool mOnActivatedCalled
;
1510 RefPtr
<ITfThreadMgr
> mThreadMgr
;
1511 RefPtr
<ITfInputProcessorProfiles
> mInputProcessorProfiles
;
1513 // Active TIP keyboard's description. If active language profile isn't TIP,
1514 // i.e., IMM-IME or just a keyboard layout, this is empty.
1515 nsString mActiveTIPKeyboardDescription
;
1517 // Active TIP's GUID and CLSID
1518 GUID mActiveTIPGUID
;
1519 CLSID mActiveTIPCLSID
;
1521 static StaticRefPtr
<TSFStaticSink
> sInstance
;
1524 StaticRefPtr
<TSFStaticSink
> TSFStaticSink::sInstance
;
1526 TSFStaticSink::TSFStaticSink()
1527 : mActiveTIP(TextInputProcessorID::eNotComputed
),
1528 mIPProfileCookie(TF_INVALID_COOKIE
),
1531 mOnActivatedCalled(false),
1532 mActiveTIPGUID(GUID_NULL
) {}
1534 bool TSFStaticSink::Init(ITfThreadMgr
* aThreadMgr
,
1535 ITfInputProcessorProfiles
* aInputProcessorProfiles
) {
1536 MOZ_ASSERT(!mThreadMgr
&& !mInputProcessorProfiles
,
1537 "TSFStaticSink::Init() must be called only once");
1539 mThreadMgr
= aThreadMgr
;
1540 mInputProcessorProfiles
= aInputProcessorProfiles
;
1542 RefPtr
<ITfSource
> source
;
1544 mThreadMgr
->QueryInterface(IID_ITfSource
, getter_AddRefs(source
));
1546 MOZ_LOG(gIMELog
, LogLevel::Error
,
1547 ("0x%p TSFStaticSink::Init() FAILED to get ITfSource "
1548 "instance (0x%08lX)",
1553 // NOTE: On Vista or later, Windows let us know activate IME changed only
1554 // with ITfInputProcessorProfileActivationSink.
1555 hr
= source
->AdviseSink(
1556 IID_ITfInputProcessorProfileActivationSink
,
1557 static_cast<ITfInputProcessorProfileActivationSink
*>(this),
1559 if (FAILED(hr
) || mIPProfileCookie
== TF_INVALID_COOKIE
) {
1560 MOZ_LOG(gIMELog
, LogLevel::Error
,
1561 ("0x%p TSFStaticSink::Init() FAILED to install "
1562 "ITfInputProcessorProfileActivationSink (0x%08lX)",
1567 MOZ_LOG(gIMELog
, LogLevel::Info
,
1568 ("0x%p TSFStaticSink::Init(), "
1569 "mIPProfileCookie=0x%08lX",
1570 this, mIPProfileCookie
));
1574 void TSFStaticSink::Destroy() {
1575 MOZ_LOG(gIMELog
, LogLevel::Info
,
1576 ("0x%p TSFStaticSink::Shutdown() "
1577 "mIPProfileCookie=0x%08lX",
1578 this, mIPProfileCookie
));
1580 if (mIPProfileCookie
!= TF_INVALID_COOKIE
) {
1581 RefPtr
<ITfSource
> source
;
1583 mThreadMgr
->QueryInterface(IID_ITfSource
, getter_AddRefs(source
));
1585 MOZ_LOG(gIMELog
, LogLevel::Error
,
1586 ("0x%p TSFStaticSink::Shutdown() FAILED to get "
1587 "ITfSource instance (0x%08lX)",
1590 hr
= source
->UnadviseSink(mIPProfileCookie
);
1592 MOZ_LOG(gIMELog
, LogLevel::Error
,
1593 ("0x%p TSFTextStore::Shutdown() FAILED to uninstall "
1594 "ITfInputProcessorProfileActivationSink (0x%08lX)",
1600 mThreadMgr
= nullptr;
1601 mInputProcessorProfiles
= nullptr;
1605 TSFStaticSink::OnActivated(DWORD dwProfileType
, LANGID langid
, REFCLSID rclsid
,
1606 REFGUID catid
, REFGUID guidProfile
, HKL hkl
,
1608 if ((dwFlags
& TF_IPSINK_FLAG_ACTIVE
) &&
1609 (dwProfileType
== TF_PROFILETYPE_KEYBOARDLAYOUT
||
1610 catid
== GUID_TFCAT_TIP_KEYBOARD
)) {
1611 mOnActivatedCalled
= true;
1612 mActiveTIP
= TextInputProcessorID::eNotComputed
;
1613 mActiveTIPGUID
= guidProfile
;
1614 mActiveTIPCLSID
= rclsid
;
1615 mLangID
= langid
& 0xFFFF;
1616 mIsIMM_IME
= IsIMM_IME(hkl
);
1617 GetTIPDescription(rclsid
, langid
, guidProfile
,
1618 mActiveTIPKeyboardDescription
);
1619 if (mActiveTIPGUID
!= GUID_NULL
) {
1620 // key should be "LocaleID|Description". Although GUID of the
1621 // profile is unique key since description may be localized for system
1622 // language, unfortunately, it's too long to record as key with its
1623 // description. Therefore, we should record only the description with
1624 // LocaleID because Microsoft IME may not include language information.
1625 // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
1627 TSFStaticSink::GetActiveTIPNameForTelemetry(key
);
1628 Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_WINDOWS
, key
,
1631 // Notify IMEHandler of changing active keyboard layout.
1632 IMEHandler::OnKeyboardLayoutChanged();
1634 MOZ_LOG(gIMELog
, LogLevel::Info
,
1635 ("0x%p TSFStaticSink::OnActivated(dwProfileType=%s (0x%08lX), "
1636 "langid=0x%08X, rclsid=%s, catid=%s, guidProfile=%s, hkl=0x%p, "
1637 "dwFlags=0x%08lX (TF_IPSINK_FLAG_ACTIVE: %s)), mIsIMM_IME=%s, "
1638 "mActiveTIPDescription=\"%s\"",
1640 dwProfileType
== TF_PROFILETYPE_INPUTPROCESSOR
1641 ? "TF_PROFILETYPE_INPUTPROCESSOR"
1642 : dwProfileType
== TF_PROFILETYPE_KEYBOARDLAYOUT
1643 ? "TF_PROFILETYPE_KEYBOARDLAYOUT"
1645 dwProfileType
, langid
, GetCLSIDNameStr(rclsid
).get(),
1646 GetGUIDNameStr(catid
).get(), GetGUIDNameStr(guidProfile
).get(), hkl
,
1647 dwFlags
, GetBoolName(dwFlags
& TF_IPSINK_FLAG_ACTIVE
),
1648 GetBoolName(mIsIMM_IME
),
1649 NS_ConvertUTF16toUTF8(mActiveTIPKeyboardDescription
).get()));
1653 bool TSFStaticSink::EnsureInitActiveTIPKeyboard() {
1654 if (mOnActivatedCalled
) {
1658 RefPtr
<ITfInputProcessorProfileMgr
> profileMgr
;
1659 HRESULT hr
= mInputProcessorProfiles
->QueryInterface(
1660 IID_ITfInputProcessorProfileMgr
, getter_AddRefs(profileMgr
));
1661 if (FAILED(hr
) || !profileMgr
) {
1662 MOZ_LOG(gIMELog
, LogLevel::Error
,
1663 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1664 "to get input processor profile manager, hr=0x%08lX",
1669 TF_INPUTPROCESSORPROFILE profile
;
1670 hr
= profileMgr
->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD
, &profile
);
1671 if (hr
== S_FALSE
) {
1672 MOZ_LOG(gIMELog
, LogLevel::Info
,
1673 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1674 "to get active keyboard layout profile due to no active profile, "
1677 // XXX Should we call OnActivated() with arguments like non-TIP in this
1682 MOZ_LOG(gIMELog
, LogLevel::Error
,
1683 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1684 "to get active TIP keyboard, hr=0x%08lX",
1689 MOZ_LOG(gIMELog
, LogLevel::Info
,
1690 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), "
1691 "calling OnActivated() manually...",
1693 OnActivated(profile
.dwProfileType
, profile
.langid
, profile
.clsid
,
1694 profile
.catid
, profile
.guidProfile
, ::GetKeyboardLayout(0),
1695 TF_IPSINK_FLAG_ACTIVE
);
1699 void TSFStaticSink::GetTIPDescription(REFCLSID aTextService
, LANGID aLangID
,
1701 nsAString
& aDescription
) {
1702 aDescription
.Truncate();
1704 if (aTextService
== CLSID_NULL
|| aProfile
== GUID_NULL
) {
1708 BSTR description
= nullptr;
1709 HRESULT hr
= mInputProcessorProfiles
->GetLanguageProfileDescription(
1710 aTextService
, aLangID
, aProfile
, &description
);
1712 MOZ_LOG(gIMELog
, LogLevel::Error
,
1713 ("0x%p TSFStaticSink::InitActiveTIPDescription() FAILED "
1714 "due to GetLanguageProfileDescription() failure, hr=0x%08lX",
1719 if (description
&& description
[0]) {
1720 aDescription
.Assign(description
);
1722 ::SysFreeString(description
);
1725 bool TSFStaticSink::IsTIPCategoryKeyboard(REFCLSID aTextService
, LANGID aLangID
,
1727 if (aTextService
== CLSID_NULL
|| aProfile
== GUID_NULL
) {
1731 RefPtr
<IEnumTfLanguageProfiles
> enumLangProfiles
;
1732 HRESULT hr
= mInputProcessorProfiles
->EnumLanguageProfiles(
1733 aLangID
, getter_AddRefs(enumLangProfiles
));
1734 if (FAILED(hr
) || !enumLangProfiles
) {
1735 MOZ_LOG(gIMELog
, LogLevel::Error
,
1736 ("0x%p TSFStaticSink::IsTIPCategoryKeyboard(), FAILED "
1737 "to get language profiles enumerator, hr=0x%08lX",
1742 TF_LANGUAGEPROFILE profile
;
1744 while (SUCCEEDED(enumLangProfiles
->Next(1, &profile
, &fetch
)) && fetch
) {
1745 // XXX We're not sure a profile is registered with two or more categories.
1746 if (profile
.clsid
== aTextService
&& profile
.guidProfile
== aProfile
&&
1747 profile
.catid
== GUID_TFCAT_TIP_KEYBOARD
) {
1754 /******************************************************************/
1756 /******************************************************************/
1758 StaticRefPtr
<ITfThreadMgr
> TSFTextStore::sThreadMgr
;
1759 StaticRefPtr
<ITfMessagePump
> TSFTextStore::sMessagePump
;
1760 StaticRefPtr
<ITfKeystrokeMgr
> TSFTextStore::sKeystrokeMgr
;
1761 StaticRefPtr
<ITfDisplayAttributeMgr
> TSFTextStore::sDisplayAttrMgr
;
1762 StaticRefPtr
<ITfCategoryMgr
> TSFTextStore::sCategoryMgr
;
1763 StaticRefPtr
<ITfCompartment
> TSFTextStore::sCompartmentForOpenClose
;
1764 StaticRefPtr
<ITfDocumentMgr
> TSFTextStore::sDisabledDocumentMgr
;
1765 StaticRefPtr
<ITfContext
> TSFTextStore::sDisabledContext
;
1766 StaticRefPtr
<ITfInputProcessorProfiles
> TSFTextStore::sInputProcessorProfiles
;
1767 StaticRefPtr
<TSFTextStore
> TSFTextStore::sEnabledTextStore
;
1768 const MSG
* TSFTextStore::sHandlingKeyMsg
= nullptr;
1769 DWORD
TSFTextStore::sClientId
= 0;
1770 bool TSFTextStore::sIsKeyboardEventDispatched
= false;
1772 #define TEXTSTORE_DEFAULT_VIEW (1)
1774 TSFTextStore::TSFTextStore()
1779 mHandlingKeyMessage(0) {
1780 // We hope that 5 or more actions don't occur at once.
1781 mPendingActions
.SetCapacity(5);
1783 MOZ_LOG(gIMELog
, LogLevel::Info
,
1784 ("0x%p TSFTextStore::TSFTextStore() SUCCEEDED", this));
1787 TSFTextStore::~TSFTextStore() {
1788 MOZ_LOG(gIMELog
, LogLevel::Info
,
1789 ("0x%p TSFTextStore instance is destroyed", this));
1792 bool TSFTextStore::Init(nsWindow
* aWidget
, const InputContext
& aContext
) {
1793 MOZ_LOG(gIMELog
, LogLevel::Info
,
1794 ("0x%p TSFTextStore::Init(aWidget=0x%p)", this, aWidget
));
1796 if (NS_WARN_IF(!aWidget
) || NS_WARN_IF(aWidget
->Destroyed())) {
1797 MOZ_LOG(gIMELog
, LogLevel::Error
,
1798 ("0x%p TSFTextStore::Init() FAILED due to being initialized with "
1805 MOZ_LOG(gIMELog
, LogLevel::Error
,
1806 ("0x%p TSFTextStore::Init() FAILED due to already initialized",
1812 if (NS_WARN_IF(!mWidget
)) {
1813 MOZ_LOG(gIMELog
, LogLevel::Error
,
1814 ("0x%p TSFTextStore::Init() FAILED "
1815 "due to aWidget is nullptr ",
1819 mDispatcher
= mWidget
->GetTextEventDispatcher();
1820 if (NS_WARN_IF(!mDispatcher
)) {
1821 MOZ_LOG(gIMELog
, LogLevel::Error
,
1822 ("0x%p TSFTextStore::Init() FAILED "
1823 "due to aWidget->GetTextEventDispatcher() failure",
1828 mInPrivateBrowsing
= aContext
.mInPrivateBrowsing
;
1829 SetInputScope(aContext
.mHTMLInputType
, aContext
.mHTMLInputMode
);
1831 if (aContext
.mURI
) {
1832 // We don't need the document URL if it fails, let's ignore the error.
1834 if (NS_SUCCEEDED(aContext
.mURI
->GetSpec(spec
))) {
1835 CopyUTF8toUTF16(spec
, mDocumentURL
);
1839 // Create document manager
1840 RefPtr
<ITfThreadMgr
> threadMgr
= sThreadMgr
;
1841 RefPtr
<ITfDocumentMgr
> documentMgr
;
1842 HRESULT hr
= threadMgr
->CreateDocumentMgr(getter_AddRefs(documentMgr
));
1843 if (NS_WARN_IF(FAILED(hr
))) {
1844 MOZ_LOG(gIMELog
, LogLevel::Error
,
1845 ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr "
1850 if (NS_WARN_IF(mDestroyed
)) {
1852 gIMELog
, LogLevel::Error
,
1853 ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr due to "
1854 "TextStore being destroyed during calling "
1855 "ITfThreadMgr::CreateDocumentMgr()",
1859 // Create context and add it to document manager
1860 RefPtr
<ITfContext
> context
;
1861 hr
= documentMgr
->CreateContext(sClientId
, 0,
1862 static_cast<ITextStoreACP
*>(this),
1863 getter_AddRefs(context
), &mEditCookie
);
1864 if (NS_WARN_IF(FAILED(hr
))) {
1865 MOZ_LOG(gIMELog
, LogLevel::Error
,
1866 ("0x%p TSFTextStore::Init() FAILED to create the context "
1871 if (NS_WARN_IF(mDestroyed
)) {
1872 MOZ_LOG(gIMELog
, LogLevel::Error
,
1873 ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
1874 "TextStore being destroyed during calling "
1875 "ITfDocumentMgr::CreateContext()",
1880 hr
= documentMgr
->Push(context
);
1881 if (NS_WARN_IF(FAILED(hr
))) {
1882 MOZ_LOG(gIMELog
, LogLevel::Error
,
1883 ("0x%p TSFTextStore::Init() FAILED to push the context (0x%08lX)",
1887 if (NS_WARN_IF(mDestroyed
)) {
1888 MOZ_LOG(gIMELog
, LogLevel::Error
,
1889 ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
1890 "TextStore being destroyed during calling ITfDocumentMgr::Push()",
1892 documentMgr
->Pop(TF_POPF_ALL
);
1896 mDocumentMgr
= documentMgr
;
1899 MOZ_LOG(gIMELog
, LogLevel::Info
,
1900 ("0x%p TSFTextStore::Init() succeeded: "
1901 "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08lX",
1902 this, mDocumentMgr
.get(), mContext
.get(), mEditCookie
));
1907 void TSFTextStore::Destroy() {
1908 if (mBeingDestroyed
) {
1912 MOZ_LOG(gIMELog
, LogLevel::Info
,
1913 ("0x%p TSFTextStore::Destroy(), mLock=%s, "
1914 "mComposition=%s, mHandlingKeyMessage=%u",
1915 this, GetLockFlagNameStr(mLock
).get(),
1916 ToString(mComposition
).c_str(), mHandlingKeyMessage
));
1920 // Destroy native caret first because it's not directly related to TSF and
1921 // there may be another textstore which gets focus. So, we should avoid
1922 // to destroy caret after the new one recreates caret.
1923 IMEHandler::MaybeDestroyNativeCaret();
1926 mPendingDestroy
= true;
1930 AutoRestore
<bool> savedBeingDestroyed(mBeingDestroyed
);
1931 mBeingDestroyed
= true;
1933 // If there is composition, TSF keeps the composition even after the text
1934 // store destroyed. So, we should clear the composition here.
1935 if (mComposition
.isSome()) {
1936 CommitCompositionInternal(false);
1940 MOZ_LOG(gIMELog
, LogLevel::Debug
,
1941 ("0x%p TSFTextStore::Destroy(), calling "
1942 "ITextStoreACPSink::OnLayoutChange(TS_LC_DESTROY)...",
1944 RefPtr
<ITextStoreACPSink
> sink
= mSink
;
1945 sink
->OnLayoutChange(TS_LC_DESTROY
, TEXTSTORE_DEFAULT_VIEW
);
1948 // If this is called during handling a keydown or keyup message, we should
1949 // put off to release TSF objects until it completely finishes since
1950 // MS-IME for Japanese refers some objects without grabbing them.
1951 if (!mHandlingKeyMessage
) {
1952 ReleaseTSFObjects();
1955 MOZ_LOG(gIMELog
, LogLevel::Info
,
1956 ("0x%p TSFTextStore::Destroy() succeeded", this));
1959 void TSFTextStore::ReleaseTSFObjects() {
1960 MOZ_ASSERT(!mHandlingKeyMessage
);
1962 MOZ_LOG(gIMELog
, LogLevel::Info
,
1963 ("0x%p TSFTextStore::ReleaseTSFObjects()", this));
1965 mDocumentURL
.Truncate();
1968 RefPtr
<ITfDocumentMgr
> documentMgr
= mDocumentMgr
.forget();
1969 documentMgr
->Pop(TF_POPF_ALL
);
1973 mDispatcher
= nullptr;
1975 if (!mMouseTrackers
.IsEmpty()) {
1976 MOZ_LOG(gIMELog
, LogLevel::Debug
,
1977 ("0x%p TSFTextStore::ReleaseTSFObjects(), "
1978 "removing a mouse tracker...",
1980 mMouseTrackers
.Clear();
1983 MOZ_LOG(gIMELog
, LogLevel::Debug
,
1984 ("0x%p TSFTextStore::ReleaseTSFObjects() completed", this));
1988 TSFTextStore::QueryInterface(REFIID riid
, void** ppv
) {
1990 if ((IID_IUnknown
== riid
) || (IID_ITextStoreACP
== riid
)) {
1991 *ppv
= static_cast<ITextStoreACP
*>(this);
1992 } else if (IID_ITfContextOwnerCompositionSink
== riid
) {
1993 *ppv
= static_cast<ITfContextOwnerCompositionSink
*>(this);
1994 } else if (IID_ITfMouseTrackerACP
== riid
) {
1995 *ppv
= static_cast<ITfMouseTrackerACP
*>(this);
2002 MOZ_LOG(gIMELog
, LogLevel::Error
,
2003 ("0x%p TSFTextStore::QueryInterface() FAILED, riid=%s", this,
2004 GetRIIDNameStr(riid
).get()));
2005 return E_NOINTERFACE
;
2009 TSFTextStore::AdviseSink(REFIID riid
, IUnknown
* punk
, DWORD dwMask
) {
2011 gIMELog
, LogLevel::Info
,
2012 ("0x%p TSFTextStore::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), "
2013 "mSink=0x%p, mSinkMask=%s",
2014 this, GetRIIDNameStr(riid
).get(), punk
, GetSinkMaskNameStr(dwMask
).get(),
2015 mSink
.get(), GetSinkMaskNameStr(mSinkMask
).get()));
2018 MOZ_LOG(gIMELog
, LogLevel::Error
,
2019 ("0x%p TSFTextStore::AdviseSink() FAILED due to the null punk",
2021 return E_UNEXPECTED
;
2024 if (IID_ITextStoreACPSink
!= riid
) {
2025 MOZ_LOG(gIMELog
, LogLevel::Error
,
2026 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2027 "unsupported interface",
2029 return E_INVALIDARG
; // means unsupported interface.
2034 punk
->QueryInterface(IID_ITextStoreACPSink
, getter_AddRefs(mSink
));
2036 MOZ_LOG(gIMELog
, LogLevel::Error
,
2037 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2038 "punk not having the interface",
2040 return E_UNEXPECTED
;
2043 // If sink is already installed we check to see if they are the same
2044 // Get IUnknown from both sides for comparison
2045 RefPtr
<IUnknown
> comparison1
, comparison2
;
2046 punk
->QueryInterface(IID_IUnknown
, getter_AddRefs(comparison1
));
2047 mSink
->QueryInterface(IID_IUnknown
, getter_AddRefs(comparison2
));
2048 if (comparison1
!= comparison2
) {
2049 MOZ_LOG(gIMELog
, LogLevel::Error
,
2050 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2051 "the sink being different from the stored sink",
2053 return CONNECT_E_ADVISELIMIT
;
2056 // Update mask either for a new sink or an existing sink
2062 TSFTextStore::UnadviseSink(IUnknown
* punk
) {
2063 MOZ_LOG(gIMELog
, LogLevel::Info
,
2064 ("0x%p TSFTextStore::UnadviseSink(punk=0x%p), mSink=0x%p", this, punk
,
2068 MOZ_LOG(gIMELog
, LogLevel::Error
,
2069 ("0x%p TSFTextStore::UnadviseSink() FAILED due to the null punk",
2071 return E_INVALIDARG
;
2074 MOZ_LOG(gIMELog
, LogLevel::Error
,
2075 ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
2076 "any sink not stored",
2078 return CONNECT_E_NOCONNECTION
;
2080 // Get IUnknown from both sides for comparison
2081 RefPtr
<IUnknown
> comparison1
, comparison2
;
2082 punk
->QueryInterface(IID_IUnknown
, getter_AddRefs(comparison1
));
2083 mSink
->QueryInterface(IID_IUnknown
, getter_AddRefs(comparison2
));
2084 // Unadvise only if sinks are the same
2085 if (comparison1
!= comparison2
) {
2086 MOZ_LOG(gIMELog
, LogLevel::Error
,
2087 ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
2088 "the sink being different from the stored sink",
2090 return CONNECT_E_NOCONNECTION
;
2098 TSFTextStore::RequestLock(DWORD dwLockFlags
, HRESULT
* phrSession
) {
2099 MOZ_LOG(gIMELog
, LogLevel::Info
,
2100 ("0x%p TSFTextStore::RequestLock(dwLockFlags=%s, phrSession=0x%p), "
2101 "mLock=%s, mDestroyed=%s",
2102 this, GetLockFlagNameStr(dwLockFlags
).get(), phrSession
,
2103 GetLockFlagNameStr(mLock
).get(), GetBoolName(mDestroyed
)));
2106 MOZ_LOG(gIMELog
, LogLevel::Error
,
2107 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2108 "any sink not stored",
2113 (mContentForTSF
.isNothing() || mSelectionForTSF
.isNothing())) {
2114 MOZ_LOG(gIMELog
, LogLevel::Error
,
2115 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2116 "being destroyed and no information of the contents",
2121 MOZ_LOG(gIMELog
, LogLevel::Error
,
2122 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2125 return E_INVALIDARG
;
2130 mLock
= dwLockFlags
& (~TS_LF_SYNC
);
2132 gIMELog
, LogLevel::Info
,
2133 ("0x%p Locking (%s) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
2134 ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
2135 this, GetLockFlagNameStr(mLock
).get()));
2136 // Don't release this instance during this lock because this is called by
2137 // TSF but they don't grab us during this call.
2138 RefPtr
<TSFTextStore
> kungFuDeathGrip(this);
2139 RefPtr
<ITextStoreACPSink
> sink
= mSink
;
2140 *phrSession
= sink
->OnLockGranted(mLock
);
2142 gIMELog
, LogLevel::Info
,
2143 ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2144 "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
2145 this, GetLockFlagNameStr(mLock
).get()));
2147 while (mLockQueued
) {
2148 mLock
= mLockQueued
;
2150 MOZ_LOG(gIMELog
, LogLevel::Info
,
2151 ("0x%p Locking for the request in the queue (%s) >>>>>>>>>>>>>>"
2152 ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
2154 this, GetLockFlagNameStr(mLock
).get()));
2155 sink
->OnLockGranted(mLock
);
2156 MOZ_LOG(gIMELog
, LogLevel::Info
,
2157 ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2158 "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2160 this, GetLockFlagNameStr(mLock
).get()));
2164 // The document is now completely unlocked.
2167 MaybeFlushPendingNotifications();
2169 MOZ_LOG(gIMELog
, LogLevel::Info
,
2170 ("0x%p TSFTextStore::RequestLock() succeeded: *phrSession=%s",
2171 this, GetTextStoreReturnValueName(*phrSession
)));
2175 // only time when reentrant lock is allowed is when caller holds a
2176 // read-only lock and is requesting an async write lock
2177 if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags
) &&
2178 !(dwLockFlags
& TS_LF_SYNC
)) {
2179 *phrSession
= TS_S_ASYNC
;
2180 mLockQueued
= dwLockFlags
& (~TS_LF_SYNC
);
2182 MOZ_LOG(gIMELog
, LogLevel::Info
,
2183 ("0x%p TSFTextStore::RequestLock() stores the request in the "
2184 "queue, *phrSession=TS_S_ASYNC",
2189 // no more locks allowed
2190 MOZ_LOG(gIMELog
, LogLevel::Info
,
2191 ("0x%p TSFTextStore::RequestLock() didn't allow to lock, "
2192 "*phrSession=TS_E_SYNCHRONOUS",
2194 *phrSession
= TS_E_SYNCHRONOUS
;
2198 void TSFTextStore::DidLockGranted() {
2199 if (IsReadWriteLocked()) {
2200 // FreeCJ (TIP for Traditional Chinese) calls SetSelection() to set caret
2201 // to the start of composition string and insert a full width space for
2202 // a placeholder with a call of SetText(). After that, it calls
2203 // OnUpdateComposition() without new range. Therefore, let's record the
2204 // composition update information here.
2205 CompleteLastActionIfStillIncomplete();
2207 FlushPendingActions();
2210 // If the widget has gone, we don't need to notify anything.
2211 if (mDestroyed
|| !mWidget
|| mWidget
->Destroyed()) {
2212 mPendingSelectionChangeData
.reset();
2213 mHasReturnedNoLayoutError
= false;
2217 void TSFTextStore::DispatchEvent(WidgetGUIEvent
& aEvent
) {
2218 if (NS_WARN_IF(!mWidget
) || NS_WARN_IF(mWidget
->Destroyed())) {
2221 // If the event isn't a query content event, the event may be handled
2222 // asynchronously. So, we should put off to answer from GetTextExt() etc.
2223 if (!aEvent
.AsQueryContentEvent()) {
2224 mDeferNotifyingTSFUntilNextUpdate
= true;
2226 mWidget
->DispatchWindowEvent(aEvent
);
2229 void TSFTextStore::FlushPendingActions() {
2230 if (!mWidget
|| mWidget
->Destroyed()) {
2231 // Note that don't clear mContentForTSF because TIP may try to commit
2232 // composition with a document lock. In such case, TSFTextStore needs to
2233 // behave as expected by TIP.
2234 mPendingActions
.Clear();
2235 mPendingSelectionChangeData
.reset();
2236 mHasReturnedNoLayoutError
= false;
2240 // Some TIP may request lock but does nothing during the lock. In such case,
2241 // this should do nothing. For example, when MS-IME for Japanese is active
2242 // and we're inactivating, this case occurs and causes different behavior
2243 // from the other TIPs.
2244 if (mPendingActions
.IsEmpty()) {
2248 RefPtr
<nsWindow
> widget(mWidget
);
2249 nsresult rv
= mDispatcher
->BeginNativeInputTransaction();
2250 if (NS_WARN_IF(NS_FAILED(rv
))) {
2251 MOZ_LOG(gIMELog
, LogLevel::Error
,
2252 ("0x%p TSFTextStore::FlushPendingActions() "
2253 "FAILED due to BeginNativeInputTransaction() failure",
2257 for (uint32_t i
= 0; i
< mPendingActions
.Length(); i
++) {
2258 PendingAction
& action
= mPendingActions
[i
];
2259 switch (action
.mType
) {
2260 case PendingAction::Type::eKeyboardEvent
:
2263 gIMELog
, LogLevel::Warning
,
2264 ("0x%p TSFTextStore::FlushPendingActions() "
2265 "IGNORED pending KeyboardEvent(%s) due to already destroyed",
2267 action
.mKeyMsg
.message
== WM_KEYDOWN
? "eKeyDown" : "eKeyUp"));
2269 MOZ_DIAGNOSTIC_ASSERT(action
.mKeyMsg
.message
== WM_KEYDOWN
||
2270 action
.mKeyMsg
.message
== WM_KEYUP
);
2271 DispatchKeyboardEventAsProcessedByIME(action
.mKeyMsg
);
2272 if (!widget
|| widget
->Destroyed()) {
2276 case PendingAction::Type::eCompositionStart
: {
2277 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2278 ("0x%p TSFTextStore::FlushPendingActions() "
2279 "flushing Type::eCompositionStart={ mSelectionStart=%ld, "
2280 "mSelectionLength=%ld }, mDestroyed=%s",
2281 this, action
.mSelectionStart
, action
.mSelectionLength
,
2282 GetBoolName(mDestroyed
)));
2285 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2286 ("0x%p TSFTextStore::FlushPendingActions() "
2287 "IGNORED pending compositionstart due to already destroyed",
2292 if (action
.mAdjustSelection
) {
2293 // Select composition range so the new composition replaces the range
2294 WidgetSelectionEvent
selectionSet(true, eSetSelection
, widget
);
2295 widget
->InitEvent(selectionSet
);
2296 selectionSet
.mOffset
= static_cast<uint32_t>(action
.mSelectionStart
);
2297 selectionSet
.mLength
= static_cast<uint32_t>(action
.mSelectionLength
);
2298 selectionSet
.mReversed
= false;
2299 selectionSet
.mExpandToClusterBoundary
=
2300 TSFStaticSink::ActiveTIP() !=
2301 TextInputProcessorID::eKeymanDesktop
&&
2303 intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
2304 DispatchEvent(selectionSet
);
2305 if (!selectionSet
.mSucceeded
) {
2306 MOZ_LOG(gIMELog
, LogLevel::Error
,
2307 ("0x%p TSFTextStore::FlushPendingActions() "
2308 "FAILED due to eSetSelection failure",
2314 // eCompositionStart always causes
2315 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. Therefore, we should
2316 // wait to clear mContentForTSF until it's notified.
2317 mDeferClearingContentForTSF
= true;
2319 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2320 ("0x%p TSFTextStore::FlushPendingActions() "
2321 "dispatching compositionstart event...",
2323 WidgetEventTime eventTime
= widget
->CurrentMessageWidgetEventTime();
2324 nsEventStatus status
;
2325 rv
= mDispatcher
->StartComposition(status
, &eventTime
);
2326 if (NS_WARN_IF(NS_FAILED(rv
))) {
2327 MOZ_LOG(gIMELog
, LogLevel::Error
,
2328 ("0x%p TSFTextStore::FlushPendingActions() "
2329 "FAILED to dispatch compositionstart event, "
2330 "IsHandlingCompositionInContent()=%s",
2331 this, GetBoolName(IsHandlingCompositionInContent())));
2332 // XXX Is this right? If there is a composition in content,
2333 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2334 mDeferClearingContentForTSF
= !IsHandlingCompositionInContent();
2336 if (!widget
|| widget
->Destroyed()) {
2341 case PendingAction::Type::eCompositionUpdate
: {
2342 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2343 ("0x%p TSFTextStore::FlushPendingActions() "
2344 "flushing Type::eCompositionUpdate={ mData=\"%s\", "
2345 "mRanges=0x%p, mRanges->Length()=%zu }",
2346 this, GetEscapedUTF8String(action
.mData
).get(),
2347 action
.mRanges
.get(),
2348 action
.mRanges
? action
.mRanges
->Length() : 0));
2350 // eCompositionChange causes a DOM text event, the IME will be notified
2351 // of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. In this case, we
2352 // should not clear mContentForTSF until we notify the IME of the
2353 // composition update.
2354 mDeferClearingContentForTSF
= true;
2356 rv
= mDispatcher
->SetPendingComposition(action
.mData
, action
.mRanges
);
2357 if (NS_WARN_IF(NS_FAILED(rv
))) {
2358 MOZ_LOG(gIMELog
, LogLevel::Error
,
2359 ("0x%p TSFTextStore::FlushPendingActions() "
2360 "FAILED to setting pending composition... "
2361 "IsHandlingCompositionInContent()=%s",
2362 this, GetBoolName(IsHandlingCompositionInContent())));
2363 // XXX Is this right? If there is a composition in content,
2364 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2365 mDeferClearingContentForTSF
= !IsHandlingCompositionInContent();
2367 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2368 ("0x%p TSFTextStore::FlushPendingActions() "
2369 "dispatching compositionchange event...",
2371 WidgetEventTime eventTime
= widget
->CurrentMessageWidgetEventTime();
2372 nsEventStatus status
;
2373 rv
= mDispatcher
->FlushPendingComposition(status
, &eventTime
);
2374 if (NS_WARN_IF(NS_FAILED(rv
))) {
2375 MOZ_LOG(gIMELog
, LogLevel::Error
,
2376 ("0x%p TSFTextStore::FlushPendingActions() "
2377 "FAILED to dispatch compositionchange event, "
2378 "IsHandlingCompositionInContent()=%s",
2379 this, GetBoolName(IsHandlingCompositionInContent())));
2380 // XXX Is this right? If there is a composition in content,
2381 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2382 mDeferClearingContentForTSF
= !IsHandlingCompositionInContent();
2384 // Be aware, the mWidget might already have been destroyed.
2388 case PendingAction::Type::eCompositionEnd
: {
2389 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2390 ("0x%p TSFTextStore::FlushPendingActions() "
2391 "flushing Type::eCompositionEnd={ mData=\"%s\" }",
2392 this, GetEscapedUTF8String(action
.mData
).get()));
2394 // Dispatching eCompositionCommit causes a DOM text event, then,
2395 // the IME will be notified of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
2396 // when focused content actually handles the event. For example,
2397 // when focused content is in a remote process, it's sent when
2398 // all dispatched composition events have been handled in the remote
2399 // process. So, until then, we don't have newer content information.
2400 // Therefore, we need to put off to clear mContentForTSF.
2401 mDeferClearingContentForTSF
= true;
2403 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2404 ("0x%p TSFTextStore::FlushPendingActions(), "
2405 "dispatching compositioncommit event...",
2407 WidgetEventTime eventTime
= widget
->CurrentMessageWidgetEventTime();
2408 nsEventStatus status
;
2409 rv
= mDispatcher
->CommitComposition(status
, &action
.mData
, &eventTime
);
2410 if (NS_WARN_IF(NS_FAILED(rv
))) {
2411 MOZ_LOG(gIMELog
, LogLevel::Error
,
2412 ("0x%p TSFTextStore::FlushPendingActions() "
2413 "FAILED to dispatch compositioncommit event, "
2414 "IsHandlingCompositionInContent()=%s",
2415 this, GetBoolName(IsHandlingCompositionInContent())));
2416 // XXX Is this right? If there is a composition in content,
2417 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2418 mDeferClearingContentForTSF
= !IsHandlingCompositionInContent();
2422 case PendingAction::Type::eSetSelection
: {
2424 gIMELog
, LogLevel::Debug
,
2425 ("0x%p TSFTextStore::FlushPendingActions() "
2426 "flushing Type::eSetSelection={ mSelectionStart=%ld, "
2427 "mSelectionLength=%ld, mSelectionReversed=%s }, "
2429 this, action
.mSelectionStart
, action
.mSelectionLength
,
2430 GetBoolName(action
.mSelectionReversed
), GetBoolName(mDestroyed
)));
2433 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2434 ("0x%p TSFTextStore::FlushPendingActions() "
2435 "IGNORED pending selectionset due to already destroyed",
2440 WidgetSelectionEvent
selectionSet(true, eSetSelection
, widget
);
2441 selectionSet
.mOffset
= static_cast<uint32_t>(action
.mSelectionStart
);
2442 selectionSet
.mLength
= static_cast<uint32_t>(action
.mSelectionLength
);
2443 selectionSet
.mReversed
= action
.mSelectionReversed
;
2444 selectionSet
.mExpandToClusterBoundary
=
2445 TSFStaticSink::ActiveTIP() !=
2446 TextInputProcessorID::eKeymanDesktop
&&
2448 intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
2449 DispatchEvent(selectionSet
);
2450 if (!selectionSet
.mSucceeded
) {
2451 MOZ_LOG(gIMELog
, LogLevel::Error
,
2452 ("0x%p TSFTextStore::FlushPendingActions() "
2453 "FAILED due to eSetSelection failure",
2460 MOZ_CRASH("unexpected action type");
2463 if (widget
&& !widget
->Destroyed()) {
2467 MOZ_LOG(gIMELog
, LogLevel::Info
,
2468 ("0x%p TSFTextStore::FlushPendingActions(), "
2469 "qutting since the mWidget has gone",
2473 mPendingActions
.Clear();
2476 void TSFTextStore::MaybeFlushPendingNotifications() {
2477 if (mDeferNotifyingTSF
) {
2478 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2479 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2480 "putting off flushing pending notifications due to initializing "
2486 if (IsReadLocked()) {
2487 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2488 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2489 "putting off flushing pending notifications due to being the "
2490 "document locked...",
2495 if (mDeferCommittingComposition
) {
2496 MOZ_LOG(gIMELog
, LogLevel::Info
,
2497 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2498 "calling TSFTextStore::CommitCompositionInternal(false)...",
2500 mDeferCommittingComposition
= mDeferCancellingComposition
= false;
2501 CommitCompositionInternal(false);
2502 } else if (mDeferCancellingComposition
) {
2503 MOZ_LOG(gIMELog
, LogLevel::Info
,
2504 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2505 "calling TSFTextStore::CommitCompositionInternal(true)...",
2507 mDeferCommittingComposition
= mDeferCancellingComposition
= false;
2508 CommitCompositionInternal(true);
2511 if (mDeferNotifyingTSFUntilNextUpdate
) {
2512 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2513 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2514 "putting off flushing pending notifications due to being "
2515 "dispatching events...",
2520 if (mPendingDestroy
) {
2526 // If it's already been destroyed completely, this shouldn't notify TSF of
2527 // anything anymore.
2528 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2529 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2530 "does nothing because this has already destroyed completely...",
2535 if (!mDeferClearingContentForTSF
&& mContentForTSF
.isSome()) {
2536 mContentForTSF
.reset();
2537 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2538 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2539 "mContentForTSF is set to `Nothing`",
2543 // When there is no cached content, we can sync actual contents and TSF/TIP
2544 // expecting contents.
2545 RefPtr
<TSFTextStore
> kungFuDeathGrip
= this;
2546 Unused
<< kungFuDeathGrip
;
2547 if (mContentForTSF
.isNothing()) {
2548 if (mPendingTextChangeData
.IsValid()) {
2549 MOZ_LOG(gIMELog
, LogLevel::Info
,
2550 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2551 "calling TSFTextStore::NotifyTSFOfTextChange()...",
2553 NotifyTSFOfTextChange();
2555 if (mPendingSelectionChangeData
.isSome()) {
2556 MOZ_LOG(gIMELog
, LogLevel::Info
,
2557 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2558 "calling TSFTextStore::NotifyTSFOfSelectionChange()...",
2560 NotifyTSFOfSelectionChange();
2564 if (mHasReturnedNoLayoutError
) {
2565 MOZ_LOG(gIMELog
, LogLevel::Info
,
2566 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2567 "calling TSFTextStore::NotifyTSFOfLayoutChange()...",
2569 NotifyTSFOfLayoutChange();
2573 void TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME() {
2574 // If we've already been destroyed, we cannot do anything.
2577 gIMELog
, LogLevel::Debug
,
2578 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2579 "does nothing because it's already been destroyed",
2584 // If we're not handling key message or we've already dispatched a keyboard
2585 // event for the handling key message, we should do nothing anymore.
2586 if (!sHandlingKeyMsg
|| sIsKeyboardEventDispatched
) {
2588 gIMELog
, LogLevel::Debug
,
2589 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2590 "does nothing because not necessary to dispatch keyboard event",
2595 sIsKeyboardEventDispatched
= true;
2596 // If the document is locked, just adding the task to dispatching an event
2598 if (IsReadLocked()) {
2600 gIMELog
, LogLevel::Debug
,
2601 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2602 "adding to dispatch a keyboard event into the queue...",
2604 PendingAction
* action
= mPendingActions
.AppendElement();
2605 action
->mType
= PendingAction::Type::eKeyboardEvent
;
2606 memcpy(&action
->mKeyMsg
, sHandlingKeyMsg
, sizeof(MSG
));
2610 // Otherwise, dispatch a keyboard event.
2611 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2612 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2613 "trying to dispatch a keyboard event...",
2615 DispatchKeyboardEventAsProcessedByIME(*sHandlingKeyMsg
);
2618 void TSFTextStore::DispatchKeyboardEventAsProcessedByIME(const MSG
& aMsg
) {
2619 MOZ_ASSERT(mWidget
);
2620 MOZ_ASSERT(!mWidget
->Destroyed());
2621 MOZ_ASSERT(!mDestroyed
);
2623 ModifierKeyState modKeyState
;
2625 msg
.wParam
= VK_PROCESSKEY
;
2626 NativeKey
nativeKey(mWidget
, msg
, modKeyState
);
2627 switch (aMsg
.message
) {
2629 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2630 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2631 "dispatching an eKeyDown event...",
2633 nativeKey
.HandleKeyDownMessage();
2636 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2637 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2638 "dispatching an eKeyUp event...",
2640 nativeKey
.HandleKeyUpMessage();
2643 MOZ_LOG(gIMELog
, LogLevel::Error
,
2644 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2645 "ERROR, it doesn't handle the message",
2652 TSFTextStore::GetStatus(TS_STATUS
* pdcs
) {
2653 MOZ_LOG(gIMELog
, LogLevel::Info
,
2654 ("0x%p TSFTextStore::GetStatus(pdcs=0x%p)", this, pdcs
));
2657 MOZ_LOG(gIMELog
, LogLevel::Error
,
2658 ("0x%p TSFTextStore::GetStatus() FAILED due to null pdcs", this));
2659 return E_INVALIDARG
;
2661 // We manage on-screen keyboard by own.
2662 pdcs
->dwDynamicFlags
= TS_SD_INPUTPANEMANUALDISPLAYENABLE
;
2663 // we use a "flat" text model for TSF support so no hidden text
2664 pdcs
->dwStaticFlags
= TS_SS_NOHIDDENTEXT
;
2669 TSFTextStore::QueryInsert(LONG acpTestStart
, LONG acpTestEnd
, ULONG cch
,
2670 LONG
* pacpResultStart
, LONG
* pacpResultEnd
) {
2672 gIMELog
, LogLevel::Info
,
2673 ("0x%p TSFTextStore::QueryInsert(acpTestStart=%ld, "
2674 "acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)",
2675 this, acpTestStart
, acpTestEnd
, cch
, pacpResultStart
, pacpResultEnd
));
2677 if (!pacpResultStart
|| !pacpResultEnd
) {
2678 MOZ_LOG(gIMELog
, LogLevel::Error
,
2679 ("0x%p TSFTextStore::QueryInsert() FAILED due to "
2680 "the null argument",
2682 return E_INVALIDARG
;
2685 if (acpTestStart
< 0 || acpTestStart
> acpTestEnd
) {
2686 MOZ_LOG(gIMELog
, LogLevel::Error
,
2687 ("0x%p TSFTextStore::QueryInsert() FAILED due to "
2690 return E_INVALIDARG
;
2693 // XXX need to adjust to cluster boundary
2694 // Assume we are given good offsets for now
2695 if (mComposition
.isNothing() &&
2697 intl_tsf_hack_ms_traditional_chinese_query_insert_result() &&
2698 TSFStaticSink::IsMSChangJieOrMSQuickActive()) ||
2700 intl_tsf_hack_ms_simplified_chinese_query_insert_result() &&
2701 TSFStaticSink::IsMSPinyinOrMSWubiActive()))) {
2702 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2703 ("0x%p TSFTextStore::QueryInsert() WARNING using different "
2704 "result for the TIP",
2706 // Chinese TIPs of Microsoft assume that QueryInsert() returns selected
2707 // range which should be removed.
2708 *pacpResultStart
= acpTestStart
;
2709 *pacpResultEnd
= acpTestEnd
;
2711 *pacpResultStart
= acpTestStart
;
2712 *pacpResultEnd
= acpTestStart
+ cch
;
2715 MOZ_LOG(gIMELog
, LogLevel::Info
,
2716 ("0x%p TSFTextStore::QueryInsert() succeeded: "
2717 "*pacpResultStart=%ld, *pacpResultEnd=%ld)",
2718 this, *pacpResultStart
, *pacpResultEnd
));
2723 TSFTextStore::GetSelection(ULONG ulIndex
, ULONG ulCount
,
2724 TS_SELECTION_ACP
* pSelection
, ULONG
* pcFetched
) {
2725 MOZ_LOG(gIMELog
, LogLevel::Info
,
2726 ("0x%p TSFTextStore::GetSelection(ulIndex=%lu, ulCount=%lu, "
2727 "pSelection=0x%p, pcFetched=0x%p)",
2728 this, ulIndex
, ulCount
, pSelection
, pcFetched
));
2730 if (!IsReadLocked()) {
2732 gIMELog
, LogLevel::Error
,
2733 ("0x%p TSFTextStore::GetSelection() FAILED due to not locked", this));
2736 if (!ulCount
|| !pSelection
|| !pcFetched
) {
2737 MOZ_LOG(gIMELog
, LogLevel::Error
,
2738 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2741 return E_INVALIDARG
;
2746 if (ulIndex
!= static_cast<ULONG
>(TS_DEFAULT_SELECTION
) && ulIndex
!= 0) {
2747 MOZ_LOG(gIMELog
, LogLevel::Error
,
2748 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2749 "unsupported selection",
2751 return TS_E_NOSELECTION
;
2754 Maybe
<Selection
>& selectionForTSF
= SelectionForTSF();
2755 if (selectionForTSF
.isNothing()) {
2756 if (DoNotReturnErrorFromGetSelection()) {
2757 *pSelection
= Selection::EmptyACP();
2760 gIMELog
, LogLevel::Info
,
2761 ("0x%p TSFTextStore::GetSelection() returns fake selection range "
2762 "for avoiding a crash in TSF, *pSelection=%s",
2763 this, mozilla::ToString(*pSelection
).c_str()));
2766 MOZ_LOG(gIMELog
, LogLevel::Error
,
2767 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2768 "SelectionForTSF() failure",
2772 if (!selectionForTSF
->HasRange()) {
2773 *pSelection
= Selection::EmptyACP();
2775 return TS_E_NOSELECTION
;
2777 *pSelection
= selectionForTSF
->ACPRef();
2779 MOZ_LOG(gIMELog
, LogLevel::Info
,
2780 ("0x%p TSFTextStore::GetSelection() succeeded, *pSelection=%s",
2781 this, mozilla::ToString(*pSelection
).c_str()));
2786 bool TSFTextStore::DoNotReturnErrorFromGetSelection() {
2787 // There is a crash bug of TSF if we return error from GetSelection().
2788 // That was introduced in Anniversary Update (build 14393, see bug 1312302)
2789 // TODO: We should avoid to run this hack on fixed builds. When we get
2790 // exact build number, we should get back here.
2791 static bool sTSFMayCrashIfGetSelectionReturnsError
=
2792 IsWin10AnniversaryUpdateOrLater();
2793 return sTSFMayCrashIfGetSelectionReturnsError
;
2796 Maybe
<TSFTextStore::Content
>& TSFTextStore::ContentForTSF() {
2797 // This should be called when the document is locked or the content hasn't
2798 // been abandoned yet.
2799 if (NS_WARN_IF(!IsReadLocked() && mContentForTSF
.isNothing())) {
2800 MOZ_LOG(gIMELog
, LogLevel::Error
,
2801 ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
2802 "called wrong timing, IsReadLocked()=%s, mContentForTSF=Nothing",
2803 this, GetBoolName(IsReadLocked())));
2804 return mContentForTSF
;
2807 Maybe
<Selection
>& selectionForTSF
= SelectionForTSF();
2808 if (selectionForTSF
.isNothing()) {
2809 MOZ_LOG(gIMELog
, LogLevel::Error
,
2810 ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
2811 "SelectionForTSF() failure",
2813 mContentForTSF
.reset();
2814 return mContentForTSF
;
2817 if (mContentForTSF
.isNothing()) {
2818 MOZ_DIAGNOSTIC_ASSERT(
2819 !mIsInitializingContentForTSF
,
2820 "TSFTextStore::ContentForTSF() shouldn't be called recursively");
2822 AutoNotifyingTSFBatch
deferNotifyingTSF(*this);
2823 AutoRestore
<bool> saveInitializingContetTSF(mIsInitializingContentForTSF
);
2824 mIsInitializingContentForTSF
= true;
2826 nsString text
; // Don't use auto string for avoiding to copy long string.
2827 if (NS_WARN_IF(!GetCurrentText(text
))) {
2828 MOZ_LOG(gIMELog
, LogLevel::Error
,
2829 ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
2830 "GetCurrentText() failure",
2832 return mContentForTSF
;
2835 MOZ_DIAGNOSTIC_ASSERT(mContentForTSF
.isNothing(),
2836 "How was it initialized recursively?");
2837 mContentForTSF
.reset(); // For avoiding crash in release channel
2838 mContentForTSF
.emplace(*this, text
);
2839 // Basically, the cached content which is expected by TSF/TIP should be
2840 // cleared after active composition is committed or the document lock is
2841 // unlocked. However, in e10s mode, content will be modified
2842 // asynchronously. In such case, mDeferClearingContentForTSF may be
2843 // true until whole dispatched events are handled by the focused editor.
2844 mDeferClearingContentForTSF
= false;
2847 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2848 ("0x%p TSFTextStore::ContentForTSF(): mContentForTSF=%s", this,
2849 mozilla::ToString(mContentForTSF
).c_str()));
2851 return mContentForTSF
;
2854 bool TSFTextStore::CanAccessActualContentDirectly() const {
2855 if (mContentForTSF
.isNothing() || mSelectionForTSF
.isNothing()) {
2859 // If the cached content has been changed by something except composition,
2860 // the content cache may be different from actual content.
2861 if (mPendingTextChangeData
.IsValid() &&
2862 !mPendingTextChangeData
.mCausedOnlyByComposition
) {
2866 // If the cached selection isn't changed, cached content and actual content
2868 if (mPendingSelectionChangeData
.isNothing()) {
2872 return mSelectionForTSF
->EqualsExceptDirection(*mPendingSelectionChangeData
);
2875 bool TSFTextStore::GetCurrentText(nsAString
& aTextContent
) {
2876 if (mContentForTSF
.isSome()) {
2877 aTextContent
= mContentForTSF
->TextRef();
2881 MOZ_ASSERT(!mDestroyed
);
2882 MOZ_ASSERT(mWidget
&& !mWidget
->Destroyed());
2884 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2885 ("0x%p TSFTextStore::GetCurrentText(): "
2886 "retrieving text from the content...",
2889 WidgetQueryContentEvent
queryTextContentEvent(true, eQueryTextContent
,
2891 queryTextContentEvent
.InitForQueryTextContent(0, UINT32_MAX
);
2892 mWidget
->InitEvent(queryTextContentEvent
);
2893 DispatchEvent(queryTextContentEvent
);
2894 if (NS_WARN_IF(queryTextContentEvent
.Failed())) {
2895 MOZ_LOG(gIMELog
, LogLevel::Error
,
2896 ("0x%p TSFTextStore::GetCurrentText(), FAILED, due to "
2897 "eQueryTextContent failure",
2899 aTextContent
.Truncate();
2903 aTextContent
= queryTextContentEvent
.mReply
->DataRef();
2907 Maybe
<TSFTextStore::Selection
>& TSFTextStore::SelectionForTSF() {
2908 if (mSelectionForTSF
.isNothing()) {
2909 MOZ_ASSERT(!mDestroyed
);
2910 // If the window has never been available, we should crash since working
2911 // with broken values may make TIP confused.
2912 if (!mWidget
|| mWidget
->Destroyed()) {
2913 MOZ_ASSERT_UNREACHABLE("There should be non-destroyed widget");
2916 MOZ_DIAGNOSTIC_ASSERT(
2917 !mIsInitializingSelectionForTSF
,
2918 "TSFTextStore::SelectionForTSF() shouldn't be called recursively");
2920 AutoNotifyingTSFBatch
deferNotifyingTSF(*this);
2921 AutoRestore
<bool> saveInitializingSelectionForTSF(
2922 mIsInitializingSelectionForTSF
);
2923 mIsInitializingSelectionForTSF
= true;
2925 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
2927 mWidget
->InitEvent(querySelectedTextEvent
);
2928 DispatchEvent(querySelectedTextEvent
);
2929 if (NS_WARN_IF(querySelectedTextEvent
.Failed())) {
2930 return mSelectionForTSF
;
2932 MOZ_DIAGNOSTIC_ASSERT(mSelectionForTSF
.isNothing(),
2933 "How was it initialized recursively?");
2934 mSelectionForTSF
= Some(Selection(querySelectedTextEvent
));
2937 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2938 ("0x%p TSFTextStore::SelectionForTSF() succeeded, "
2939 "mSelectionForTSF=%s",
2940 this, ToString(mSelectionForTSF
).c_str()));
2942 return mSelectionForTSF
;
2945 static HRESULT
GetRangeExtent(ITfRange
* aRange
, LONG
* aStart
, LONG
* aLength
) {
2946 RefPtr
<ITfRangeACP
> rangeACP
;
2947 aRange
->QueryInterface(IID_ITfRangeACP
, getter_AddRefs(rangeACP
));
2948 NS_ENSURE_TRUE(rangeACP
, E_FAIL
);
2949 return rangeACP
->GetExtent(aStart
, aLength
);
2952 static TextRangeType
GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE
& aDisplayAttr
) {
2953 switch (aDisplayAttr
.bAttr
) {
2954 case TF_ATTR_TARGET_CONVERTED
:
2955 return TextRangeType::eSelectedClause
;
2956 case TF_ATTR_CONVERTED
:
2957 return TextRangeType::eConvertedClause
;
2958 case TF_ATTR_TARGET_NOTCONVERTED
:
2959 return TextRangeType::eSelectedRawClause
;
2961 return TextRangeType::eRawClause
;
2966 TSFTextStore::GetDisplayAttribute(ITfProperty
* aAttrProperty
, ITfRange
* aRange
,
2967 TF_DISPLAYATTRIBUTE
* aResult
) {
2968 NS_ENSURE_TRUE(aAttrProperty
, E_FAIL
);
2969 NS_ENSURE_TRUE(aRange
, E_FAIL
);
2970 NS_ENSURE_TRUE(aResult
, E_FAIL
);
2974 if (MOZ_LOG_TEST(gIMELog
, LogLevel::Debug
)) {
2975 LONG start
= 0, length
= 0;
2976 hr
= GetRangeExtent(aRange
, &start
, &length
);
2977 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2978 ("0x%p TSFTextStore::GetDisplayAttribute(): "
2979 "GetDisplayAttribute range=%ld-%ld (hr=%s)",
2980 this, start
- mComposition
->StartOffset(),
2981 start
- mComposition
->StartOffset() + length
,
2982 GetCommonReturnValueName(hr
)));
2986 ::VariantInit(&propValue
);
2987 hr
= aAttrProperty
->GetValue(TfEditCookie(mEditCookie
), aRange
, &propValue
);
2989 MOZ_LOG(gIMELog
, LogLevel::Error
,
2990 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
2991 "ITfProperty::GetValue() failed",
2995 if (VT_I4
!= propValue
.vt
) {
2996 MOZ_LOG(gIMELog
, LogLevel::Error
,
2997 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
2998 "ITfProperty::GetValue() returns non-VT_I4 value",
3000 ::VariantClear(&propValue
);
3004 RefPtr
<ITfCategoryMgr
> categoryMgr
= GetCategoryMgr();
3005 if (NS_WARN_IF(!categoryMgr
)) {
3009 hr
= categoryMgr
->GetGUID(DWORD(propValue
.lVal
), &guid
);
3010 ::VariantClear(&propValue
);
3012 MOZ_LOG(gIMELog
, LogLevel::Error
,
3013 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3014 "ITfCategoryMgr::GetGUID() failed",
3019 RefPtr
<ITfDisplayAttributeMgr
> displayAttrMgr
= GetDisplayAttributeMgr();
3020 if (NS_WARN_IF(!displayAttrMgr
)) {
3023 RefPtr
<ITfDisplayAttributeInfo
> info
;
3024 hr
= displayAttrMgr
->GetDisplayAttributeInfo(guid
, getter_AddRefs(info
),
3026 if (FAILED(hr
) || !info
) {
3027 MOZ_LOG(gIMELog
, LogLevel::Error
,
3028 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3029 "ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed",
3034 hr
= info
->GetAttributeInfo(aResult
);
3036 MOZ_LOG(gIMELog
, LogLevel::Error
,
3037 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3038 "ITfDisplayAttributeInfo::GetAttributeInfo() failed",
3043 MOZ_LOG(gIMELog
, LogLevel::Debug
,
3044 ("0x%p TSFTextStore::GetDisplayAttribute() succeeded: "
3046 this, GetDisplayAttrStr(*aResult
).get()));
3051 TSFTextStore::RestartCompositionIfNecessary(ITfRange
* aRangeNew
) {
3052 MOZ_LOG(gIMELog
, LogLevel::Debug
,
3053 ("0x%p TSFTextStore::RestartCompositionIfNecessary("
3054 "aRangeNew=0x%p), mComposition=%s",
3055 this, aRangeNew
, ToString(mComposition
).c_str()));
3057 if (mComposition
.isNothing()) {
3058 MOZ_LOG(gIMELog
, LogLevel::Error
,
3059 ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
3060 "due to no composition view",
3066 RefPtr
<ITfCompositionView
> pComposition(mComposition
->GetView());
3067 RefPtr
<ITfRange
> composingRange(aRangeNew
);
3068 if (!composingRange
) {
3069 hr
= pComposition
->GetRange(getter_AddRefs(composingRange
));
3071 MOZ_LOG(gIMELog
, LogLevel::Error
,
3072 ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
3073 "FAILED due to pComposition->GetRange() failure",
3079 // Get starting offset of the composition
3080 LONG compStart
= 0, compLength
= 0;
3081 hr
= GetRangeExtent(composingRange
, &compStart
, &compLength
);
3083 MOZ_LOG(gIMELog
, LogLevel::Error
,
3084 ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
3085 "due to GetRangeExtent() failure",
3090 if (mComposition
->StartOffset() == compStart
&&
3091 mComposition
->Length() == compLength
) {
3095 MOZ_LOG(gIMELog
, LogLevel::Debug
,
3096 ("0x%p TSFTextStore::RestartCompositionIfNecessary(), "
3097 "restaring composition because of compostion range is changed "
3098 "(range=%ld-%ld, mComposition=%s)",
3099 this, compStart
, compStart
+ compLength
,
3100 ToString(mComposition
).c_str()));
3102 // If the queried composition length is different from the length
3103 // of our composition string, OnUpdateComposition is being called
3104 // because a part of the original composition was committed.
3105 hr
= RestartComposition(*mComposition
, pComposition
, composingRange
);
3107 MOZ_LOG(gIMELog
, LogLevel::Error
,
3108 ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
3109 "FAILED due to RestartComposition() failure",
3115 gIMELog
, LogLevel::Debug
,
3116 ("0x%p TSFTextStore::RestartCompositionIfNecessary() succeeded", this));
3120 HRESULT
TSFTextStore::RestartComposition(Composition
& aCurrentComposition
,
3121 ITfCompositionView
* aCompositionView
,
3122 ITfRange
* aNewRange
) {
3123 Maybe
<Selection
>& selectionForTSF
= SelectionForTSF();
3125 LONG newStart
, newLength
;
3126 HRESULT hr
= GetRangeExtent(aNewRange
, &newStart
, &newLength
);
3127 LONG newEnd
= newStart
+ newLength
;
3129 if (selectionForTSF
.isNothing()) {
3130 MOZ_LOG(gIMELog
, LogLevel::Error
,
3131 ("0x%p TSFTextStore::RestartComposition() FAILED "
3132 "due to SelectionForTSF() failure",
3138 MOZ_LOG(gIMELog
, LogLevel::Error
,
3139 ("0x%p TSFTextStore::RestartComposition() FAILED "
3140 "due to GetRangeExtent() failure",
3145 // If the new range has no overlap with the crrent range, we just commit
3146 // the composition and restart new composition with the new range but
3147 // current selection range should be preserved.
3148 if (newStart
>= aCurrentComposition
.EndOffset() ||
3149 newEnd
<= aCurrentComposition
.StartOffset()) {
3150 RecordCompositionEndAction();
3151 RecordCompositionStartAction(aCompositionView
, newStart
, newLength
, true);
3155 MOZ_LOG(gIMELog
, LogLevel::Debug
,
3156 ("0x%p TSFTextStore::RestartComposition(aCompositionView=0x%p, "
3157 "aNewRange=0x%p { newStart=%ld, newLength=%ld }), "
3158 "aCurrentComposition=%s, "
3159 "selectionForTSF=%s",
3160 this, aCompositionView
, aNewRange
, newStart
, newLength
,
3161 ToString(aCurrentComposition
).c_str(),
3162 ToString(selectionForTSF
).c_str()));
3164 // If the new range has an overlap with the current one, we should not commit
3165 // the whole current range to avoid creating an odd undo transaction.
3166 // I.e., the overlapped range which is being composed should not appear in
3167 // undo transaction.
3169 // Backup current composition data and selection data.
3170 Composition oldComposition
= aCurrentComposition
;
3171 Selection oldSelection
= *selectionForTSF
;
3173 // Commit only the part of composition.
3174 LONG keepComposingStartOffset
=
3175 std::max(oldComposition
.StartOffset(), newStart
);
3176 LONG keepComposingEndOffset
= std::min(oldComposition
.EndOffset(), newEnd
);
3178 keepComposingStartOffset
<= keepComposingEndOffset
,
3179 "Why keepComposingEndOffset is smaller than keepComposingStartOffset?");
3180 LONG keepComposingLength
= keepComposingEndOffset
- keepComposingStartOffset
;
3181 // Remove the overlapped part from the commit string.
3182 nsAutoString
commitString(oldComposition
.DataRef());
3183 commitString
.Cut(keepComposingStartOffset
- oldComposition
.StartOffset(),
3184 keepComposingLength
);
3185 // Update the composition string.
3186 Maybe
<Content
>& contentForTSF
= ContentForTSF();
3187 if (contentForTSF
.isNothing()) {
3188 MOZ_LOG(gIMELog
, LogLevel::Error
,
3189 ("0x%p TSFTextStore::RestartComposition() FAILED "
3190 "due to ContentForTSF() failure",
3194 contentForTSF
->ReplaceTextWith(oldComposition
.StartOffset(),
3195 oldComposition
.Length(), commitString
);
3196 MOZ_ASSERT(mComposition
.isSome());
3197 // Record a compositionupdate action for commit the part of composing string.
3198 PendingAction
* action
= LastOrNewPendingCompositionUpdate();
3199 if (mComposition
.isSome()) {
3200 action
->mData
= mComposition
->DataRef();
3202 action
->mRanges
->Clear();
3203 // Note that we shouldn't append ranges when composition string
3204 // is empty because it may cause TextComposition confused.
3205 if (!action
->mData
.IsEmpty()) {
3206 TextRange caretRange
;
3207 caretRange
.mStartOffset
= caretRange
.mEndOffset
= static_cast<uint32_t>(
3208 oldComposition
.StartOffset() + commitString
.Length());
3209 caretRange
.mRangeType
= TextRangeType::eCaret
;
3210 action
->mRanges
->AppendElement(caretRange
);
3212 action
->mIncomplete
= false;
3214 // Record compositionend action.
3215 RecordCompositionEndAction();
3217 // Record compositionstart action only with the new start since this method
3218 // hasn't restored composing string yet.
3219 RecordCompositionStartAction(aCompositionView
, newStart
, 0, false);
3221 // Restore the latest text content and selection.
3222 contentForTSF
->ReplaceSelectedTextWith(nsDependentSubstring(
3223 oldComposition
.DataRef(),
3224 keepComposingStartOffset
- oldComposition
.StartOffset(),
3225 keepComposingLength
));
3226 selectionForTSF
= Some(oldSelection
);
3228 MOZ_LOG(gIMELog
, LogLevel::Debug
,
3229 ("0x%p TSFTextStore::RestartComposition() succeeded, "
3230 "mComposition=%s, selectionForTSF=%s",
3231 this, ToString(mComposition
).c_str(),
3232 ToString(selectionForTSF
).c_str()));
3237 static bool GetColor(const TF_DA_COLOR
& aTSFColor
, nscolor
& aResult
) {
3238 switch (aTSFColor
.type
) {
3239 case TF_CT_SYSCOLOR
: {
3240 DWORD sysColor
= ::GetSysColor(aTSFColor
.nIndex
);
3242 NS_RGB(GetRValue(sysColor
), GetGValue(sysColor
), GetBValue(sysColor
));
3245 case TF_CT_COLORREF
:
3246 aResult
= NS_RGB(GetRValue(aTSFColor
.cr
), GetGValue(aTSFColor
.cr
),
3247 GetBValue(aTSFColor
.cr
));
3255 static bool GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle
,
3256 TextRangeStyle::LineStyle
& aTextRangeLineStyle
) {
3257 switch (aTSFLineStyle
) {
3259 aTextRangeLineStyle
= TextRangeStyle::LineStyle::None
;
3262 aTextRangeLineStyle
= TextRangeStyle::LineStyle::Solid
;
3265 aTextRangeLineStyle
= TextRangeStyle::LineStyle::Dotted
;
3268 aTextRangeLineStyle
= TextRangeStyle::LineStyle::Dashed
;
3270 case TF_LS_SQUIGGLE
:
3271 aTextRangeLineStyle
= TextRangeStyle::LineStyle::Wavy
;
3279 TSFTextStore::RecordCompositionUpdateAction() {
3281 gIMELog
, LogLevel::Debug
,
3282 ("0x%p TSFTextStore::RecordCompositionUpdateAction(), mComposition=%s",
3283 this, ToString(mComposition
).c_str()));
3285 if (mComposition
.isNothing()) {
3286 MOZ_LOG(gIMELog
, LogLevel::Error
,
3287 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3288 "due to no composition view",
3293 // Getting display attributes is *really* complicated!
3294 // We first get the context and the property objects to query for
3295 // attributes, but since a big range can have a variety of values for
3296 // the attribute, we have to find out all the ranges that have distinct
3297 // attribute values. Then we query for what the value represents through
3298 // the display attribute manager and translate that to TextRange to be
3299 // sent in eCompositionChange
3301 RefPtr
<ITfProperty
> attrPropetry
;
3303 mContext
->GetProperty(GUID_PROP_ATTRIBUTE
, getter_AddRefs(attrPropetry
));
3304 if (FAILED(hr
) || !attrPropetry
) {
3305 MOZ_LOG(gIMELog
, LogLevel::Error
,
3306 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3307 "due to mContext->GetProperty() failure",
3309 return FAILED(hr
) ? hr
: E_FAIL
;
3312 RefPtr
<ITfRange
> composingRange
;
3313 hr
= mComposition
->GetView()->GetRange(getter_AddRefs(composingRange
));
3315 MOZ_LOG(gIMELog
, LogLevel::Error
,
3316 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3317 "FAILED due to mComposition->GetView()->GetRange() failure",
3322 RefPtr
<IEnumTfRanges
> enumRanges
;
3323 hr
= attrPropetry
->EnumRanges(TfEditCookie(mEditCookie
),
3324 getter_AddRefs(enumRanges
), composingRange
);
3325 if (FAILED(hr
) || !enumRanges
) {
3326 MOZ_LOG(gIMELog
, LogLevel::Error
,
3327 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3328 "due to attrPropetry->EnumRanges() failure",
3330 return FAILED(hr
) ? hr
: E_FAIL
;
3333 // First, put the log of content and selection here.
3334 Maybe
<Selection
>& selectionForTSF
= SelectionForTSF();
3335 if (selectionForTSF
.isNothing()) {
3336 MOZ_LOG(gIMELog
, LogLevel::Error
,
3337 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3338 "due to SelectionForTSF() failure",
3343 PendingAction
* action
= LastOrNewPendingCompositionUpdate();
3344 action
->mData
= mComposition
->DataRef();
3345 // The ranges might already have been initialized, however, if this is
3346 // called again, that means we need to overwrite the ranges with current
3348 action
->mRanges
->Clear();
3350 // Note that we shouldn't append ranges when composition string
3351 // is empty because it may cause TextComposition confused.
3352 if (!action
->mData
.IsEmpty()) {
3354 // No matter if we have display attribute info or not,
3355 // we always pass in at least one range to eCompositionChange
3356 newRange
.mStartOffset
= 0;
3357 newRange
.mEndOffset
= action
->mData
.Length();
3358 newRange
.mRangeType
= TextRangeType::eRawClause
;
3359 action
->mRanges
->AppendElement(newRange
);
3361 RefPtr
<ITfRange
> range
;
3362 while (enumRanges
->Next(1, getter_AddRefs(range
), nullptr) == S_OK
) {
3363 if (NS_WARN_IF(!range
)) {
3367 LONG rangeStart
= 0, rangeLength
= 0;
3368 if (FAILED(GetRangeExtent(range
, &rangeStart
, &rangeLength
))) {
3371 // The range may include out of composition string. We should ignore
3372 // outside of the composition string.
3373 LONG start
= std::min(std::max(rangeStart
, mComposition
->StartOffset()),
3374 mComposition
->EndOffset());
3375 LONG end
= std::max(
3376 std::min(rangeStart
+ rangeLength
, mComposition
->EndOffset()),
3377 mComposition
->StartOffset());
3378 LONG length
= end
- start
;
3380 MOZ_LOG(gIMELog
, LogLevel::Error
,
3381 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3382 "ignores invalid range (%ld-%ld)",
3383 this, rangeStart
- mComposition
->StartOffset(),
3384 rangeStart
- mComposition
->StartOffset() + rangeLength
));
3388 MOZ_LOG(gIMELog
, LogLevel::Debug
,
3389 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3390 "ignores a range due to outside of the composition or empty "
3392 this, rangeStart
- mComposition
->StartOffset(),
3393 rangeStart
- mComposition
->StartOffset() + rangeLength
));
3398 newRange
.mStartOffset
=
3399 static_cast<uint32_t>(start
- mComposition
->StartOffset());
3400 // The end of the last range in the array is
3401 // always kept at the end of composition
3402 newRange
.mEndOffset
= mComposition
->Length();
3404 TF_DISPLAYATTRIBUTE attr
;
3405 hr
= GetDisplayAttribute(attrPropetry
, range
, &attr
);
3407 newRange
.mRangeType
= TextRangeType::eRawClause
;
3409 newRange
.mRangeType
= GetGeckoSelectionValue(attr
);
3410 if (GetColor(attr
.crText
, newRange
.mRangeStyle
.mForegroundColor
)) {
3411 newRange
.mRangeStyle
.mDefinedStyles
|=
3412 TextRangeStyle::DEFINED_FOREGROUND_COLOR
;
3414 if (GetColor(attr
.crBk
, newRange
.mRangeStyle
.mBackgroundColor
)) {
3415 newRange
.mRangeStyle
.mDefinedStyles
|=
3416 TextRangeStyle::DEFINED_BACKGROUND_COLOR
;
3418 if (GetColor(attr
.crLine
, newRange
.mRangeStyle
.mUnderlineColor
)) {
3419 newRange
.mRangeStyle
.mDefinedStyles
|=
3420 TextRangeStyle::DEFINED_UNDERLINE_COLOR
;
3422 if (GetLineStyle(attr
.lsStyle
, newRange
.mRangeStyle
.mLineStyle
)) {
3423 newRange
.mRangeStyle
.mDefinedStyles
|=
3424 TextRangeStyle::DEFINED_LINESTYLE
;
3425 newRange
.mRangeStyle
.mIsBoldLine
= attr
.fBoldLine
!= 0;
3429 TextRange
& lastRange
= action
->mRanges
->LastElement();
3430 if (lastRange
.mStartOffset
== newRange
.mStartOffset
) {
3431 // Replace range if last range is the same as this one
3432 // So that ranges don't overlap and confuse the editor
3433 lastRange
= newRange
;
3435 lastRange
.mEndOffset
= newRange
.mStartOffset
;
3436 action
->mRanges
->AppendElement(newRange
);
3440 // We need to hack for Korean Input System which is Korean standard TIP.
3441 // It sets no change style to IME selection (the selection is always only
3442 // one). So, the composition string looks like normal (or committed)
3443 // string. At this time, current selection range is same as the
3444 // composition string range. Other applications set a wide caret which
3445 // covers the composition string, however, Gecko doesn't support the wide
3446 // caret drawing now (Gecko doesn't support XOR drawing), unfortunately.
3447 // For now, we should change the range style to undefined.
3448 if (!selectionForTSF
->Collapsed() && action
->mRanges
->Length() == 1) {
3449 TextRange
& range
= action
->mRanges
->ElementAt(0);
3450 LONG start
= selectionForTSF
->MinOffset();
3451 LONG end
= selectionForTSF
->MaxOffset();
3452 if (static_cast<LONG
>(range
.mStartOffset
) ==
3453 start
- mComposition
->StartOffset() &&
3454 static_cast<LONG
>(range
.mEndOffset
) ==
3455 end
- mComposition
->StartOffset() &&
3456 range
.mRangeStyle
.IsNoChangeStyle()) {
3457 range
.mRangeStyle
.Clear();
3458 // The looks of selected type is better than others.
3459 range
.mRangeType
= TextRangeType::eSelectedRawClause
;
3463 // The caret position has to be collapsed.
3464 uint32_t caretPosition
= static_cast<uint32_t>(
3465 selectionForTSF
->HasRange()
3466 ? selectionForTSF
->MaxOffset() - mComposition
->StartOffset()
3467 : mComposition
->StartOffset());
3469 // If caret is in the target clause and it doesn't have specific style,
3470 // the target clause will be painted as normal selection range. Since
3471 // caret shouldn't be in selection range on Windows, we shouldn't append
3472 // caret range in such case.
3473 const TextRange
* targetClause
= action
->mRanges
->GetTargetClause();
3474 if (!targetClause
|| targetClause
->mRangeStyle
.IsDefined() ||
3475 caretPosition
< targetClause
->mStartOffset
||
3476 caretPosition
> targetClause
->mEndOffset
) {
3477 TextRange caretRange
;
3478 caretRange
.mStartOffset
= caretRange
.mEndOffset
= caretPosition
;
3479 caretRange
.mRangeType
= TextRangeType::eCaret
;
3480 action
->mRanges
->AppendElement(caretRange
);
3484 action
->mIncomplete
= false;
3486 MOZ_LOG(gIMELog
, LogLevel::Info
,
3487 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3495 TSFTextStore::SetSelectionInternal(const TS_SELECTION_ACP
* pSelection
,
3496 bool aDispatchCompositionChangeEvent
) {
3498 gIMELog
, LogLevel::Debug
,
3499 ("0x%p TSFTextStore::SetSelectionInternal(pSelection=%s, "
3500 "aDispatchCompositionChangeEvent=%s), mComposition=%s",
3501 this, pSelection
? mozilla::ToString(*pSelection
).c_str() : "nullptr",
3502 GetBoolName(aDispatchCompositionChangeEvent
),
3503 ToString(mComposition
).c_str()));
3505 MOZ_ASSERT(IsReadWriteLocked());
3507 Maybe
<Selection
>& selectionForTSF
= SelectionForTSF();
3508 if (selectionForTSF
.isNothing()) {
3509 MOZ_LOG(gIMELog
, LogLevel::Error
,
3510 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3511 "SelectionForTSF() failure",
3516 MaybeDispatchKeyboardEventAsProcessedByIME();
3518 MOZ_LOG(gIMELog
, LogLevel::Error
,
3519 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3520 "destroyed during dispatching a keyboard event",
3525 // If actually the range is not changing, we should do nothing.
3526 // Perhaps, we can ignore the difference change because it must not be
3527 // important for following edit.
3528 if (selectionForTSF
->EqualsExceptDirection(*pSelection
)) {
3529 MOZ_LOG(gIMELog
, LogLevel::Warning
,
3530 ("0x%p TSFTextStore::SetSelectionInternal() Succeeded but "
3531 "did nothing because the selection range isn't changing",
3533 selectionForTSF
->SetSelection(*pSelection
);
3537 if (mComposition
.isSome()) {
3538 if (aDispatchCompositionChangeEvent
) {
3539 HRESULT hr
= RestartCompositionIfNecessary();
3541 MOZ_LOG(gIMELog
, LogLevel::Error
,
3542 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3543 "RestartCompositionIfNecessary() failure",
3548 if (pSelection
->acpStart
< mComposition
->StartOffset() ||
3549 pSelection
->acpEnd
> mComposition
->EndOffset()) {
3550 MOZ_LOG(gIMELog
, LogLevel::Error
,
3551 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3552 "the selection being out of the composition string",
3554 return TS_E_INVALIDPOS
;
3556 // Emulate selection during compositions
3557 selectionForTSF
->SetSelection(*pSelection
);
3558 if (aDispatchCompositionChangeEvent
) {
3559 HRESULT hr
= RecordCompositionUpdateAction();
3561 MOZ_LOG(gIMELog
, LogLevel::Error
,
3562 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3563 "RecordCompositionUpdateAction() failure",
3571 TS_SELECTION_ACP
selectionInContent(*pSelection
);
3573 // If mContentForTSF caches old contents which is now different from
3574 // actual contents, we need some complicated hack here...
3575 // Note that this hack assumes that this is used for reconversion.
3576 if (mContentForTSF
.isSome() && mPendingTextChangeData
.IsValid() &&
3577 !mPendingTextChangeData
.mCausedOnlyByComposition
) {
3578 uint32_t startOffset
= static_cast<uint32_t>(selectionInContent
.acpStart
);
3579 uint32_t endOffset
= static_cast<uint32_t>(selectionInContent
.acpEnd
);
3580 if (mPendingTextChangeData
.mStartOffset
>= endOffset
) {
3581 // Setting selection before any changed ranges is fine.
3582 } else if (mPendingTextChangeData
.mRemovedEndOffset
<= startOffset
) {
3583 // Setting selection after removed range is fine with following
3585 selectionInContent
.acpStart
+= mPendingTextChangeData
.Difference();
3586 selectionInContent
.acpEnd
+= mPendingTextChangeData
.Difference();
3587 } else if (startOffset
== endOffset
) {
3588 // Moving caret position may be fine in most cases even if the insertion
3589 // point has already gone but in this case, composition will be inserted
3590 // to unexpected position, though.
3591 // It seems that moving caret into middle of the new text is odd.
3592 // Perhaps, end of it is expected by users in most cases.
3593 selectionInContent
.acpStart
= mPendingTextChangeData
.mAddedEndOffset
;
3594 selectionInContent
.acpEnd
= selectionInContent
.acpStart
;
3596 // Otherwise, i.e., setting range has already gone, we cannot set
3597 // selection properly.
3598 MOZ_LOG(gIMELog
, LogLevel::Error
,
3599 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3600 "there is unknown content change",
3606 CompleteLastActionIfStillIncomplete();
3607 PendingAction
* action
= mPendingActions
.AppendElement();
3608 action
->mType
= PendingAction::Type::eSetSelection
;
3609 action
->mSelectionStart
= selectionInContent
.acpStart
;
3610 action
->mSelectionLength
=
3611 selectionInContent
.acpEnd
- selectionInContent
.acpStart
;
3612 action
->mSelectionReversed
= (selectionInContent
.style
.ase
== TS_AE_START
);
3614 // Use TSF specified selection for updating mSelectionForTSF.
3615 selectionForTSF
->SetSelection(*pSelection
);
3621 TSFTextStore::SetSelection(ULONG ulCount
, const TS_SELECTION_ACP
* pSelection
) {
3622 MOZ_LOG(gIMELog
, LogLevel::Info
,
3623 ("0x%p TSFTextStore::SetSelection(ulCount=%lu, pSelection=%s }), "
3626 pSelection
? mozilla::ToString(pSelection
).c_str() : "nullptr",
3627 ToString(mComposition
).c_str()));
3629 if (!IsReadWriteLocked()) {
3630 MOZ_LOG(gIMELog
, LogLevel::Error
,
3631 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3632 "not locked (read-write)",
3637 MOZ_LOG(gIMELog
, LogLevel::Error
,
3638 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3639 "trying setting multiple selection",
3641 return E_INVALIDARG
;
3644 MOZ_LOG(gIMELog
, LogLevel::Error
,
3645 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3648 return E_INVALIDARG
;
3651 HRESULT hr
= SetSelectionInternal(pSelection
, true);
3653 MOZ_LOG(gIMELog
, LogLevel::Error
,
3654 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3655 "SetSelectionInternal() failure",
3658 MOZ_LOG(gIMELog
, LogLevel::Info
,
3659 ("0x%p TSFTextStore::SetSelection() succeeded", this));
3665 TSFTextStore::GetText(LONG acpStart
, LONG acpEnd
, WCHAR
* pchPlain
,
3666 ULONG cchPlainReq
, ULONG
* pcchPlainOut
,
3667 TS_RUNINFO
* prgRunInfo
, ULONG ulRunInfoReq
,
3668 ULONG
* pulRunInfoOut
, LONG
* pacpNext
) {
3670 gIMELog
, LogLevel::Info
,
3671 ("0x%p TSFTextStore::GetText(acpStart=%ld, acpEnd=%ld, pchPlain=0x%p, "
3672 "cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, ulRunInfoReq=%lu, "
3673 "pulRunInfoOut=0x%p, pacpNext=0x%p), mComposition=%s",
3674 this, acpStart
, acpEnd
, pchPlain
, cchPlainReq
, pcchPlainOut
, prgRunInfo
,
3675 ulRunInfoReq
, pulRunInfoOut
, pacpNext
, ToString(mComposition
).c_str()));
3677 if (!IsReadLocked()) {
3678 MOZ_LOG(gIMELog
, LogLevel::Error
,
3679 ("0x%p TSFTextStore::GetText() FAILED due to "
3680 "not locked (read)",
3685 if (!pcchPlainOut
|| (!pchPlain
&& !prgRunInfo
) ||
3686 !cchPlainReq
!= !pchPlain
|| !ulRunInfoReq
!= !prgRunInfo
) {
3687 MOZ_LOG(gIMELog
, LogLevel::Error
,
3688 ("0x%p TSFTextStore::GetText() FAILED due to "
3691 return E_INVALIDARG
;
3694 if (acpStart
< 0 || acpEnd
< -1 || (acpEnd
!= -1 && acpStart
> acpEnd
)) {
3695 MOZ_LOG(gIMELog
, LogLevel::Error
,
3696 ("0x%p TSFTextStore::GetText() FAILED due to "
3699 return TS_E_INVALIDPOS
;
3702 // Making sure to null-terminate string just to be on the safe side
3704 if (pchPlain
&& cchPlainReq
) *pchPlain
= 0;
3705 if (pulRunInfoOut
) *pulRunInfoOut
= 0;
3706 if (pacpNext
) *pacpNext
= acpStart
;
3707 if (prgRunInfo
&& ulRunInfoReq
) {
3708 prgRunInfo
->uCount
= 0;
3709 prgRunInfo
->type
= TS_RT_PLAIN
;
3712 Maybe
<Content
>& contentForTSF
= ContentForTSF();
3713 if (contentForTSF
.isNothing()) {
3714 MOZ_LOG(gIMELog
, LogLevel::Error
,
3715 ("0x%p TSFTextStore::GetText() FAILED due to "
3716 "ContentForTSF() failure",
3720 if (contentForTSF
->TextRef().Length() < static_cast<uint32_t>(acpStart
)) {
3721 MOZ_LOG(gIMELog
, LogLevel::Error
,
3722 ("0x%p TSFTextStore::GetText() FAILED due to "
3723 "acpStart is larger offset than the actual text length",
3725 return TS_E_INVALIDPOS
;
3728 contentForTSF
->TextRef().Length() < static_cast<uint32_t>(acpEnd
)) {
3729 MOZ_LOG(gIMELog
, LogLevel::Error
,
3730 ("0x%p TSFTextStore::GetText() FAILED due to "
3731 "acpEnd is larger offset than the actual text length",
3733 return TS_E_INVALIDPOS
;
3735 uint32_t length
= (acpEnd
== -1) ? contentForTSF
->TextRef().Length() -
3736 static_cast<uint32_t>(acpStart
)
3737 : static_cast<uint32_t>(acpEnd
- acpStart
);
3738 if (cchPlainReq
&& cchPlainReq
- 1 < length
) {
3739 length
= cchPlainReq
- 1;
3742 if (pchPlain
&& cchPlainReq
) {
3743 const char16_t
* startChar
=
3744 contentForTSF
->TextRef().BeginReading() + acpStart
;
3745 memcpy(pchPlain
, startChar
, length
* sizeof(*pchPlain
));
3746 pchPlain
[length
] = 0;
3747 *pcchPlainOut
= length
;
3749 if (prgRunInfo
&& ulRunInfoReq
) {
3750 prgRunInfo
->uCount
= length
;
3751 prgRunInfo
->type
= TS_RT_PLAIN
;
3752 if (pulRunInfoOut
) *pulRunInfoOut
= 1;
3754 if (pacpNext
) *pacpNext
= acpStart
+ length
;
3757 MOZ_LOG(gIMELog
, LogLevel::Info
,
3758 ("0x%p TSFTextStore::GetText() succeeded: pcchPlainOut=0x%p, "
3759 "*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, "
3761 this, pcchPlainOut
, prgRunInfo
? prgRunInfo
->uCount
: 0,
3762 prgRunInfo
? GetTextRunTypeName(prgRunInfo
->type
) : "N/A",
3763 pulRunInfoOut
? *pulRunInfoOut
: 0, pacpNext
? *pacpNext
: 0));
3768 TSFTextStore::SetText(DWORD dwFlags
, LONG acpStart
, LONG acpEnd
,
3769 const WCHAR
* pchText
, ULONG cch
, TS_TEXTCHANGE
* pChange
) {
3771 gIMELog
, LogLevel::Info
,
3772 ("0x%p TSFTextStore::SetText(dwFlags=%s, acpStart=%ld, acpEnd=%ld, "
3773 "pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), mComposition=%s",
3774 this, dwFlags
== TS_ST_CORRECTION
? "TS_ST_CORRECTION" : "not-specified",
3775 acpStart
, acpEnd
, pchText
,
3776 pchText
&& cch
? GetEscapedUTF8String(pchText
, cch
).get() : "", cch
,
3777 pChange
, ToString(mComposition
).c_str()));
3779 // Per SDK documentation, and since we don't have better
3780 // ways to do this, this method acts as a helper to
3781 // call SetSelection followed by InsertTextAtSelection
3782 if (!IsReadWriteLocked()) {
3783 MOZ_LOG(gIMELog
, LogLevel::Error
,
3784 ("0x%p TSFTextStore::SetText() FAILED due to "
3785 "not locked (read)",
3790 TS_SELECTION_ACP selection
;
3791 selection
.acpStart
= acpStart
;
3792 selection
.acpEnd
= acpEnd
;
3793 selection
.style
.ase
= TS_AE_END
;
3794 selection
.style
.fInterimChar
= 0;
3795 // Set selection to desired range
3796 HRESULT hr
= SetSelectionInternal(&selection
);
3798 MOZ_LOG(gIMELog
, LogLevel::Error
,
3799 ("0x%p TSFTextStore::SetText() FAILED due to "
3800 "SetSelectionInternal() failure",
3804 // Replace just selected text
3805 if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText
, cch
),
3807 MOZ_LOG(gIMELog
, LogLevel::Error
,
3808 ("0x%p TSFTextStore::SetText() FAILED due to "
3809 "InsertTextAtSelectionInternal() failure",
3814 MOZ_LOG(gIMELog
, LogLevel::Info
,
3815 ("0x%p TSFTextStore::SetText() succeeded: pChange={ "
3816 "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
3817 this, pChange
? pChange
->acpStart
: 0,
3818 pChange
? pChange
->acpOldEnd
: 0, pChange
? pChange
->acpNewEnd
: 0));
3823 TSFTextStore::GetFormattedText(LONG acpStart
, LONG acpEnd
,
3824 IDataObject
** ppDataObject
) {
3825 MOZ_LOG(gIMELog
, LogLevel::Info
,
3826 ("0x%p TSFTextStore::GetFormattedText() called "
3827 "but not supported (E_NOTIMPL)",
3830 // no support for formatted text
3835 TSFTextStore::GetEmbedded(LONG acpPos
, REFGUID rguidService
, REFIID riid
,
3837 MOZ_LOG(gIMELog
, LogLevel::Info
,
3838 ("0x%p TSFTextStore::GetEmbedded() called "
3839 "but not supported (E_NOTIMPL)",
3842 // embedded objects are not supported
3847 TSFTextStore::QueryInsertEmbedded(const GUID
* pguidService
,
3848 const FORMATETC
* pFormatEtc
,
3849 BOOL
* pfInsertable
) {
3850 MOZ_LOG(gIMELog
, LogLevel::Info
,
3851 ("0x%p TSFTextStore::QueryInsertEmbedded() called "
3852 "but not supported, *pfInsertable=FALSE (S_OK)",
3855 // embedded objects are not supported
3856 *pfInsertable
= FALSE
;
3861 TSFTextStore::InsertEmbedded(DWORD dwFlags
, LONG acpStart
, LONG acpEnd
,
3862 IDataObject
* pDataObject
, TS_TEXTCHANGE
* pChange
) {
3863 MOZ_LOG(gIMELog
, LogLevel::Info
,
3864 ("0x%p TSFTextStore::InsertEmbedded() called "
3865 "but not supported (E_NOTIMPL)",
3868 // embedded objects are not supported
3873 bool TSFTextStore::ShouldSetInputScopeOfURLBarToDefault() {
3874 // FYI: Google Japanese Input may be an IMM-IME. If it's installed on
3875 // Win7, it's always IMM-IME. Otherwise, basically, it's a TIP.
3876 // However, if it's installed on Win7 and has not been updated yet
3877 // after the OS is upgraded to Win8 or later, it's still an IMM-IME.
3878 // Therefore, we also need to check with IMMHandler here.
3879 if (!StaticPrefs::intl_ime_hack_set_input_scope_of_url_bar_to_default()) {
3883 if (IMMHandler::IsGoogleJapaneseInputActive()) {
3887 switch (TSFStaticSink::ActiveTIP()) {
3888 case TextInputProcessorID::eMicrosoftIMEForJapanese
:
3889 case TextInputProcessorID::eGoogleJapaneseInput
:
3890 case TextInputProcessorID::eMicrosoftBopomofo
:
3891 case TextInputProcessorID::eMicrosoftChangJie
:
3892 case TextInputProcessorID::eMicrosoftPhonetic
:
3893 case TextInputProcessorID::eMicrosoftQuick
:
3894 case TextInputProcessorID::eMicrosoftNewChangJie
:
3895 case TextInputProcessorID::eMicrosoftNewPhonetic
:
3896 case TextInputProcessorID::eMicrosoftNewQuick
:
3897 case TextInputProcessorID::eMicrosoftPinyin
:
3898 case TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle
:
3899 case TextInputProcessorID::eMicrosoftOldHangul
:
3900 case TextInputProcessorID::eMicrosoftWubi
:
3901 case TextInputProcessorID::eMicrosoftIMEForKorean
:
3908 void TSFTextStore::SetInputScope(const nsString
& aHTMLInputType
,
3909 const nsString
& aHTMLInputMode
) {
3910 mInputScopes
.Clear();
3912 // IME may refer only first input scope, but we will append inputmode's
3913 // input scopes too like Chrome since IME may refer it.
3914 IMEHandler::AppendInputScopeFromType(aHTMLInputType
, mInputScopes
);
3915 IMEHandler::AppendInputScopeFromInputMode(aHTMLInputMode
, mInputScopes
);
3917 if (mInPrivateBrowsing
) {
3918 mInputScopes
.AppendElement(IS_PRIVATE
);
3922 int32_t TSFTextStore::GetRequestedAttrIndex(const TS_ATTRID
& aAttrID
) {
3923 if (IsEqualGUID(aAttrID
, GUID_PROP_INPUTSCOPE
)) {
3926 if (IsEqualGUID(aAttrID
, sGUID_PROP_URL
)) {
3927 return eDocumentURL
;
3929 if (IsEqualGUID(aAttrID
, TSATTRID_Text_VerticalWriting
)) {
3930 return eTextVerticalWriting
;
3932 if (IsEqualGUID(aAttrID
, TSATTRID_Text_Orientation
)) {
3933 return eTextOrientation
;
3935 return eNotSupported
;
3939 TSFTextStore::GetAttrID(int32_t aIndex
) {
3942 return GUID_PROP_INPUTSCOPE
;
3944 return sGUID_PROP_URL
;
3945 case eTextVerticalWriting
:
3946 return TSATTRID_Text_VerticalWriting
;
3947 case eTextOrientation
:
3948 return TSATTRID_Text_Orientation
;
3950 MOZ_CRASH("Invalid index? Or not implemented yet?");
3956 TSFTextStore::HandleRequestAttrs(DWORD aFlags
, ULONG aFilterCount
,
3957 const TS_ATTRID
* aFilterAttrs
) {
3958 MOZ_LOG(gIMELog
, LogLevel::Info
,
3959 ("0x%p TSFTextStore::HandleRequestAttrs(aFlags=%s, "
3960 "aFilterCount=%lu)",
3961 this, GetFindFlagName(aFlags
).get(), aFilterCount
));
3963 // This is a little weird! RequestSupportedAttrs gives us advanced notice
3964 // of a support query via RetrieveRequestedAttrs for a specific attribute.
3965 // RetrieveRequestedAttrs needs to return valid data for all attributes we
3966 // support, but the text service will only want the input scope object
3967 // returned in RetrieveRequestedAttrs if the dwFlags passed in here contains
3968 // TS_ATTR_FIND_WANT_VALUE.
3969 for (int32_t i
= 0; i
< NUM_OF_SUPPORTED_ATTRS
; i
++) {
3970 mRequestedAttrs
[i
] = false;
3972 mRequestedAttrValues
= !!(aFlags
& TS_ATTR_FIND_WANT_VALUE
);
3974 for (uint32_t i
= 0; i
< aFilterCount
; i
++) {
3975 MOZ_LOG(gIMELog
, LogLevel::Info
,
3976 ("0x%p TSFTextStore::HandleRequestAttrs(), "
3977 "requested attr=%s",
3978 this, GetGUIDNameStrWithTable(aFilterAttrs
[i
]).get()));
3979 int32_t index
= GetRequestedAttrIndex(aFilterAttrs
[i
]);
3980 if (index
!= eNotSupported
) {
3981 mRequestedAttrs
[index
] = true;
3988 TSFTextStore::RequestSupportedAttrs(DWORD dwFlags
, ULONG cFilterAttrs
,
3989 const TS_ATTRID
* paFilterAttrs
) {
3990 MOZ_LOG(gIMELog
, LogLevel::Info
,
3991 ("0x%p TSFTextStore::RequestSupportedAttrs(dwFlags=%s, "
3992 "cFilterAttrs=%lu)",
3993 this, GetFindFlagName(dwFlags
).get(), cFilterAttrs
));
3995 return HandleRequestAttrs(dwFlags
, cFilterAttrs
, paFilterAttrs
);
3999 TSFTextStore::RequestAttrsAtPosition(LONG acpPos
, ULONG cFilterAttrs
,
4000 const TS_ATTRID
* paFilterAttrs
,
4002 MOZ_LOG(gIMELog
, LogLevel::Info
,
4003 ("0x%p TSFTextStore::RequestAttrsAtPosition(acpPos=%ld, "
4004 "cFilterAttrs=%lu, dwFlags=%s)",
4005 this, acpPos
, cFilterAttrs
, GetFindFlagName(dwFlags
).get()));
4007 return HandleRequestAttrs(dwFlags
| TS_ATTR_FIND_WANT_VALUE
, cFilterAttrs
,
4012 TSFTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos
,
4014 const TS_ATTRID
* paFilterAttr
,
4016 MOZ_LOG(gIMELog
, LogLevel::Info
,
4017 ("0x%p TSFTextStore::RequestAttrsTransitioningAtPosition("
4018 "acpPos=%ld, cFilterAttrs=%lu, dwFlags=%s) called but not supported "
4020 this, acpPos
, cFilterAttrs
, GetFindFlagName(dwFlags
).get()));
4022 // no per character attributes defined
4027 TSFTextStore::FindNextAttrTransition(LONG acpStart
, LONG acpHalt
,
4029 const TS_ATTRID
* paFilterAttrs
,
4030 DWORD dwFlags
, LONG
* pacpNext
,
4031 BOOL
* pfFound
, LONG
* plFoundOffset
) {
4032 if (!pacpNext
|| !pfFound
|| !plFoundOffset
) {
4033 MOZ_LOG(gIMELog
, LogLevel::Error
,
4034 (" 0x%p TSFTextStore::FindNextAttrTransition() FAILED due to "
4037 return E_INVALIDARG
;
4040 MOZ_LOG(gIMELog
, LogLevel::Info
,
4041 ("0x%p TSFTextStore::FindNextAttrTransition() called "
4042 "but not supported (S_OK)",
4045 // no per character attributes defined
4046 *pacpNext
= *plFoundOffset
= acpHalt
;
4051 // To test the document URL result, define this to out put it to the stdout
4052 // #define DEBUG_PRINT_DOCUMENT_URL
4055 TSFTextStore::RetrieveRequestedAttrs(ULONG ulCount
, TS_ATTRVAL
* paAttrVals
,
4057 if (!pcFetched
|| !paAttrVals
) {
4058 MOZ_LOG(gIMELog
, LogLevel::Error
,
4059 ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
4062 return E_INVALIDARG
;
4065 ULONG expectedCount
= 0;
4066 for (int32_t i
= 0; i
< NUM_OF_SUPPORTED_ATTRS
; i
++) {
4067 if (mRequestedAttrs
[i
]) {
4071 if (ulCount
< expectedCount
) {
4072 MOZ_LOG(gIMELog
, LogLevel::Error
,
4073 ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
4074 "not enough count ulCount=%lu, expectedCount=%lu",
4075 this, ulCount
, expectedCount
));
4076 return E_INVALIDARG
;
4079 MOZ_LOG(gIMELog
, LogLevel::Info
,
4080 ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
4081 "ulCount=%lu, mRequestedAttrValues=%s",
4082 this, ulCount
, GetBoolName(mRequestedAttrValues
)));
4084 auto GetExposingURL
= [&]() -> BSTR
{
4085 const bool allowed
=
4086 StaticPrefs::intl_tsf_expose_url_allowed() &&
4087 (!mInPrivateBrowsing
||
4088 StaticPrefs::intl_tsf_expose_url_in_private_browsing_allowed());
4089 if (!allowed
|| mDocumentURL
.IsEmpty()) {
4090 BSTR emptyString
= ::SysAllocString(L
"");
4093 "We need to return valid BSTR pointer to notify TSF of supporting it "
4094 "with a pointer to empty string");
4097 return ::SysAllocString(mDocumentURL
.get());
4100 #ifdef DEBUG_PRINT_DOCUMENT_URL
4102 BSTR exposingURL
= GetExposingURL();
4103 printf("TSFTextStore::RetrieveRequestedAttrs: DocumentURL=\"%s\"\n",
4104 NS_ConvertUTF16toUTF8(static_cast<char16ptr_t
>(_bstr_t(exposingURL
)))
4106 ::SysFreeString(exposingURL
);
4108 #endif // #ifdef DEBUG_PRINT_DOCUMENT_URL
4111 for (int32_t i
= 0; i
< NUM_OF_SUPPORTED_ATTRS
; i
++) {
4112 if (!mRequestedAttrs
[i
]) {
4115 mRequestedAttrs
[i
] = false;
4117 TS_ATTRID attrID
= GetAttrID(i
);
4119 MOZ_LOG(gIMELog
, LogLevel::Info
,
4120 ("0x%p TSFTextStore::RetrieveRequestedAttrs() for %s", this,
4121 GetGUIDNameStrWithTable(attrID
).get()));
4123 paAttrVals
[count
].idAttr
= attrID
;
4124 paAttrVals
[count
].dwOverlapId
= 0;
4126 if (!mRequestedAttrValues
) {
4127 paAttrVals
[count
].varValue
.vt
= VT_EMPTY
;
4131 paAttrVals
[count
].varValue
.vt
= VT_UNKNOWN
;
4132 RefPtr
<IUnknown
> inputScope
= new InputScopeImpl(mInputScopes
);
4133 paAttrVals
[count
].varValue
.punkVal
= inputScope
.forget().take();
4136 case eDocumentURL
: {
4137 paAttrVals
[count
].varValue
.vt
= VT_BSTR
;
4138 paAttrVals
[count
].varValue
.bstrVal
= GetExposingURL();
4141 case eTextVerticalWriting
: {
4142 Maybe
<Selection
>& selectionForTSF
= SelectionForTSF();
4143 paAttrVals
[count
].varValue
.vt
= VT_BOOL
;
4144 paAttrVals
[count
].varValue
.boolVal
=
4145 selectionForTSF
.isSome() &&
4146 selectionForTSF
->WritingModeRef().IsVertical()
4151 case eTextOrientation
: {
4152 Maybe
<Selection
>& selectionForTSF
= SelectionForTSF();
4153 paAttrVals
[count
].varValue
.vt
= VT_I4
;
4154 paAttrVals
[count
].varValue
.lVal
=
4155 selectionForTSF
.isSome() &&
4156 selectionForTSF
->WritingModeRef().IsVertical()
4162 MOZ_CRASH("Invalid index? Or not implemented yet?");
4169 mRequestedAttrValues
= false;
4176 MOZ_LOG(gIMELog
, LogLevel::Info
,
4177 ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
4178 "for unknown TS_ATTRVAL, *pcFetched=0 (S_OK)",
4181 paAttrVals
->dwOverlapId
= 0;
4182 paAttrVals
->varValue
.vt
= VT_EMPTY
;
4187 #undef DEBUG_PRINT_DOCUMENT_URL
4190 TSFTextStore::GetEndACP(LONG
* pacp
) {
4191 MOZ_LOG(gIMELog
, LogLevel::Info
,
4192 ("0x%p TSFTextStore::GetEndACP(pacp=0x%p)", this, pacp
));
4194 if (!IsReadLocked()) {
4195 MOZ_LOG(gIMELog
, LogLevel::Error
,
4196 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4197 "not locked (read)",
4203 MOZ_LOG(gIMELog
, LogLevel::Error
,
4204 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4207 return E_INVALIDARG
;
4210 Maybe
<Content
>& contentForTSF
= ContentForTSF();
4211 if (contentForTSF
.isNothing()) {
4212 MOZ_LOG(gIMELog
, LogLevel::Error
,
4213 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4214 "ContentForTSF() failure",
4218 *pacp
= static_cast<LONG
>(contentForTSF
->TextRef().Length());
4223 TSFTextStore::GetActiveView(TsViewCookie
* pvcView
) {
4224 MOZ_LOG(gIMELog
, LogLevel::Info
,
4225 ("0x%p TSFTextStore::GetActiveView(pvcView=0x%p)", this, pvcView
));
4228 MOZ_LOG(gIMELog
, LogLevel::Error
,
4229 ("0x%p TSFTextStore::GetActiveView() FAILED due to "
4232 return E_INVALIDARG
;
4235 *pvcView
= TEXTSTORE_DEFAULT_VIEW
;
4237 MOZ_LOG(gIMELog
, LogLevel::Info
,
4238 ("0x%p TSFTextStore::GetActiveView() succeeded: *pvcView=%ld", this,
4244 TSFTextStore::GetACPFromPoint(TsViewCookie vcView
, const POINT
* pt
,
4245 DWORD dwFlags
, LONG
* pacp
) {
4246 MOZ_LOG(gIMELog
, LogLevel::Info
,
4247 ("0x%p TSFTextStore::GetACPFromPoint(pvcView=%ld, pt=%p (x=%ld, "
4248 "y=%ld), dwFlags=%s, pacp=%p, mDeferNotifyingTSFUntilNextUpdate=%s, "
4249 "mWaitingQueryLayout=%s",
4250 this, vcView
, pt
, pt
? pt
->x
: 0, pt
? pt
->y
: 0,
4251 GetACPFromPointFlagName(dwFlags
).get(), pacp
,
4252 GetBoolName(mDeferNotifyingTSFUntilNextUpdate
),
4253 GetBoolName(mWaitingQueryLayout
)));
4255 if (!IsReadLocked()) {
4256 MOZ_LOG(gIMELog
, LogLevel::Error
,
4257 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4258 "not locked (read)",
4263 if (vcView
!= TEXTSTORE_DEFAULT_VIEW
) {
4264 MOZ_LOG(gIMELog
, LogLevel::Error
,
4265 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4266 "called with invalid view",
4268 return E_INVALIDARG
;
4272 MOZ_LOG(gIMELog
, LogLevel::Error
,
4273 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4276 return E_INVALIDARG
;
4280 MOZ_LOG(gIMELog
, LogLevel::Error
,
4281 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4284 return E_INVALIDARG
;
4287 mWaitingQueryLayout
= false;
4290 (mContentForTSF
.isSome() && mContentForTSF
->IsLayoutChanged())) {
4291 MOZ_LOG(gIMELog
, LogLevel::Error
,
4292 ("0x%p TSFTextStore::GetACPFromPoint() returned "
4295 mHasReturnedNoLayoutError
= true;
4296 return TS_E_NOLAYOUT
;
4299 LayoutDeviceIntPoint
ourPt(pt
->x
, pt
->y
);
4300 // Convert to widget relative coordinates from screen's.
4301 ourPt
-= mWidget
->WidgetToScreenOffset();
4303 // NOTE: Don't check if the point is in the widget since the point can be
4304 // outside of the widget if focused editor is in a XUL <panel>.
4306 WidgetQueryContentEvent
queryCharAtPointEvent(true, eQueryCharacterAtPoint
,
4308 mWidget
->InitEvent(queryCharAtPointEvent
, &ourPt
);
4310 // FYI: WidgetQueryContentEvent may cause flushing pending layout and it
4311 // may cause focus change or something.
4312 RefPtr
<TSFTextStore
> kungFuDeathGrip(this);
4313 DispatchEvent(queryCharAtPointEvent
);
4314 if (!mWidget
|| mWidget
->Destroyed()) {
4315 MOZ_LOG(gIMELog
, LogLevel::Error
,
4316 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4317 "mWidget was destroyed during eQueryCharacterAtPoint",
4322 MOZ_LOG(gIMELog
, LogLevel::Debug
,
4323 ("0x%p TSFTextStore::GetACPFromPoint(), queryCharAtPointEvent={ "
4325 this, ToString(queryCharAtPointEvent
.mReply
).c_str()));
4327 if (NS_WARN_IF(queryCharAtPointEvent
.Failed())) {
4328 MOZ_LOG(gIMELog
, LogLevel::Error
,
4329 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4330 "eQueryCharacterAtPoint failure",
4335 // If dwFlags isn't set and the point isn't in any character's bounding box,
4336 // we should return TS_E_INVALIDPOINT.
4337 if (!(dwFlags
& GXFPF_NEAREST
) && queryCharAtPointEvent
.DidNotFindChar()) {
4338 MOZ_LOG(gIMELog
, LogLevel::Error
,
4339 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to the "
4340 "point contained by no bounding box",
4342 return TS_E_INVALIDPOINT
;
4345 // Although, we're not sure if mTentativeCaretOffset becomes NOT_FOUND,
4346 // let's assume that there is no content in such case.
4347 NS_WARNING_ASSERTION(queryCharAtPointEvent
.DidNotFindTentativeCaretOffset(),
4348 "Tentative caret offset was not found");
4352 // If dwFlags includes GXFPF_ROUND_NEAREST, we should return tentative
4353 // caret offset (MSDN calls it "range position").
4354 if (dwFlags
& GXFPF_ROUND_NEAREST
) {
4355 offset
= queryCharAtPointEvent
.mReply
->mTentativeCaretOffset
.valueOr(0);
4356 } else if (queryCharAtPointEvent
.FoundChar()) {
4357 // Otherwise, we should return character offset whose bounding box contains
4359 offset
= queryCharAtPointEvent
.mReply
->StartOffset();
4361 // If the point isn't in any character's bounding box but we need to return
4362 // the nearest character from the point, we should *guess* the character
4363 // offset since there is no inexpensive API to check it strictly.
4364 // XXX If we retrieve 2 bounding boxes, one is before the offset and
4365 // the other is after the offset, we could resolve the offset.
4366 // However, dispatching 2 eQueryTextRect may be expensive.
4368 // So, use tentative offset for now.
4369 offset
= queryCharAtPointEvent
.mReply
->mTentativeCaretOffset
.valueOr(0);
4371 // However, if it's after the last character, we need to decrement the
4373 Maybe
<Content
>& contentForTSF
= ContentForTSF();
4374 if (contentForTSF
.isNothing()) {
4375 MOZ_LOG(gIMELog
, LogLevel::Error
,
4376 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4377 "ContentForTSF() failure",
4381 if (contentForTSF
->TextRef().Length() <= offset
) {
4382 // If the tentative caret is after the last character, let's return
4383 // the last character's offset.
4384 offset
= contentForTSF
->TextRef().Length() - 1;
4388 if (NS_WARN_IF(offset
> LONG_MAX
)) {
4389 MOZ_LOG(gIMELog
, LogLevel::Error
,
4390 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to out of "
4391 "range of the result",
4393 return TS_E_INVALIDPOINT
;
4396 *pacp
= static_cast<LONG
>(offset
);
4397 MOZ_LOG(gIMELog
, LogLevel::Info
,
4398 ("0x%p TSFTextStore::GetACPFromPoint() succeeded: *pacp=%ld", this,
4404 TSFTextStore::GetTextExt(TsViewCookie vcView
, LONG acpStart
, LONG acpEnd
,
4405 RECT
* prc
, BOOL
* pfClipped
) {
4406 MOZ_LOG(gIMELog
, LogLevel::Info
,
4407 ("0x%p TSFTextStore::GetTextExt(vcView=%ld, "
4408 "acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p), "
4409 "IsHandlingCompositionInParent()=%s, "
4410 "IsHandlingCompositionInContent()=%s, mContentForTSF=%s, "
4411 "mSelectionForTSF=%s, mComposition=%s, "
4412 "mDeferNotifyingTSFUntilNextUpdate=%s, mWaitingQueryLayout=%s, "
4413 "IMEHandler::IsA11yHandlingNativeCaret()=%s",
4414 this, vcView
, acpStart
, acpEnd
, prc
, pfClipped
,
4415 GetBoolName(IsHandlingCompositionInParent()),
4416 GetBoolName(IsHandlingCompositionInContent()),
4417 mozilla::ToString(mContentForTSF
).c_str(),
4418 ToString(mSelectionForTSF
).c_str(), ToString(mComposition
).c_str(),
4419 GetBoolName(mDeferNotifyingTSFUntilNextUpdate
),
4420 GetBoolName(mWaitingQueryLayout
),
4421 GetBoolName(IMEHandler::IsA11yHandlingNativeCaret())));
4423 if (!IsReadLocked()) {
4424 MOZ_LOG(gIMELog
, LogLevel::Error
,
4425 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4426 "not locked (read)",
4431 if (vcView
!= TEXTSTORE_DEFAULT_VIEW
) {
4432 MOZ_LOG(gIMELog
, LogLevel::Error
,
4433 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4434 "called with invalid view",
4436 return E_INVALIDARG
;
4439 if (!prc
|| !pfClipped
) {
4440 MOZ_LOG(gIMELog
, LogLevel::Error
,
4441 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4444 return E_INVALIDARG
;
4447 // According to MSDN, ITextStoreACP::GetTextExt() should return
4448 // TS_E_INVALIDARG when acpStart and acpEnd are same (i.e., collapsed range).
4449 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms538435(v=vs.85).aspx
4450 // > TS_E_INVALIDARG: The specified start and end character positions are
4452 // However, some TIPs (including Microsoft's Chinese TIPs!) call this with
4453 // collapsed range and if we return TS_E_INVALIDARG, they stops showing their
4454 // owning window or shows it but odd position. So, we should just return
4455 // error only when acpStart and/or acpEnd are really odd.
4457 if (acpStart
< 0 || acpEnd
< acpStart
) {
4458 MOZ_LOG(gIMELog
, LogLevel::Error
,
4459 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4462 return TS_E_INVALIDPOS
;
4465 mWaitingQueryLayout
= false;
4467 if (IsHandlingCompositionInContent() && mContentForTSF
.isSome() &&
4468 mContentForTSF
->HasOrHadComposition() &&
4469 mContentForTSF
->IsLayoutChanged() &&
4470 mContentForTSF
->MinModifiedOffset().value() >
4471 static_cast<uint32_t>(LONG_MAX
)) {
4472 MOZ_LOG(gIMELog
, LogLevel::Error
,
4473 ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text "
4474 "is too big for TSF (cannot treat modified offset as LONG), "
4475 "mContentForTSF=%s",
4476 this, ToString(mContentForTSF
).c_str()));
4480 // At Windows 10 build 17643 (an insider preview for RS5), Microsoft fixed
4481 // the bug of TS_E_NOLAYOUT (even when we returned TS_E_NOLAYOUT, TSF
4482 // returned E_FAIL to TIP). However, until we drop to support older Windows
4483 // and all TIPs are aware of TS_E_NOLAYOUT result, we need to keep returning
4484 // S_OK and available rectangle only for them.
4485 if (!MaybeHackNoErrorLayoutBugs(acpStart
, acpEnd
) &&
4486 mContentForTSF
.isSome() && mContentForTSF
->IsLayoutChangedAt(acpEnd
)) {
4487 MOZ_LOG(gIMELog
, LogLevel::Error
,
4488 ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
4491 mHasReturnedNoLayoutError
= true;
4492 return TS_E_NOLAYOUT
;
4496 MOZ_LOG(gIMELog
, LogLevel::Error
,
4497 ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
4498 "(acpEnd=%ld) because this has already been destroyed",
4500 mHasReturnedNoLayoutError
= true;
4501 return TS_E_NOLAYOUT
;
4504 // use eQueryTextRect to get rect in system, screen coordinates
4505 WidgetQueryContentEvent
queryTextRectEvent(true, eQueryTextRect
, mWidget
);
4506 mWidget
->InitEvent(queryTextRectEvent
);
4508 WidgetQueryContentEvent::Options options
;
4509 int64_t startOffset
= acpStart
;
4510 if (mComposition
.isSome()) {
4511 // If there is a composition, TSF must want character rects related to
4512 // the composition. Therefore, we should use insertion point relative
4513 // query because the composition might be at different position from
4514 // the position where TSFTextStore believes it at.
4515 options
.mRelativeToInsertionPoint
= true;
4516 startOffset
-= mComposition
->StartOffset();
4517 } else if (IsHandlingCompositionInParent() && mContentForTSF
.isSome() &&
4518 mContentForTSF
->HasOrHadComposition()) {
4519 // If there was a composition and its commit event hasn't been dispatched
4520 // yet, ContentCacheInParent is still open for relative offset query from
4521 // the latest composition.
4522 options
.mRelativeToInsertionPoint
= true;
4523 startOffset
-= mContentForTSF
->LatestCompositionRange()->StartOffset();
4524 } else if (!CanAccessActualContentDirectly() &&
4525 mSelectionForTSF
->HasRange()) {
4526 // If TSF/TIP cannot access actual content directly, there may be pending
4527 // text and/or selection changes which have not been notified TSF yet.
4528 // Therefore, we should use relative to insertion point query since
4529 // TSF/TIP computes the offset from the cached selection.
4530 options
.mRelativeToInsertionPoint
= true;
4531 startOffset
-= mSelectionForTSF
->StartOffset();
4533 // ContentEventHandler and ContentCache return actual caret rect when
4534 // the queried range is collapsed and selection is collapsed at the
4535 // queried range. Then, its height (in horizontal layout, width in vertical
4536 // layout) may be different from actual font height of the line. In such
4537 // case, users see "dancing" of candidate or suggest window of TIP.
4538 // For preventing it, we should query text rect with at least 1 length.
4539 uint32_t length
= std::max(static_cast<int32_t>(acpEnd
- acpStart
), 1);
4540 queryTextRectEvent
.InitForQueryTextRect(startOffset
, length
, options
);
4542 DispatchEvent(queryTextRectEvent
);
4543 if (NS_WARN_IF(queryTextRectEvent
.Failed())) {
4544 MOZ_LOG(gIMELog
, LogLevel::Error
,
4545 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4546 "eQueryTextRect failure",
4548 return TS_E_INVALIDPOS
; // but unexpected failure, maybe.
4551 // IMEs don't like empty rects, fix here
4552 if (queryTextRectEvent
.mReply
->mRect
.Width() <= 0) {
4553 queryTextRectEvent
.mReply
->mRect
.SetWidth(1);
4555 if (queryTextRectEvent
.mReply
->mRect
.Height() <= 0) {
4556 queryTextRectEvent
.mReply
->mRect
.SetHeight(1);
4559 // convert to unclipped screen rect
4560 nsWindow
* refWindow
=
4561 static_cast<nsWindow
*>(!!queryTextRectEvent
.mReply
->mFocusedWidget
4562 ? queryTextRectEvent
.mReply
->mFocusedWidget
4563 : static_cast<nsIWidget
*>(mWidget
.get()));
4564 // Result rect is in top level widget coordinates
4565 refWindow
= refWindow
->GetTopLevelWindow(false);
4567 MOZ_LOG(gIMELog
, LogLevel::Error
,
4568 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4569 "no top level window",
4574 queryTextRectEvent
.mReply
->mRect
.MoveBy(refWindow
->WidgetToScreenOffset());
4576 // get bounding screen rect to test for clipping
4577 if (!GetScreenExtInternal(*prc
)) {
4578 MOZ_LOG(gIMELog
, LogLevel::Error
,
4579 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4580 "GetScreenExtInternal() failure",
4585 // clip text rect to bounding rect
4587 ::SetRect(&textRect
, queryTextRectEvent
.mReply
->mRect
.X(),
4588 queryTextRectEvent
.mReply
->mRect
.Y(),
4589 queryTextRectEvent
.mReply
->mRect
.XMost(),
4590 queryTextRectEvent
.mReply
->mRect
.YMost());
4591 if (!::IntersectRect(prc
, prc
, &textRect
))
4592 // Text is not visible
4593 ::SetRectEmpty(prc
);
4595 // not equal if text rect was clipped
4596 *pfClipped
= !::EqualRect(prc
, &textRect
);
4598 // ATOK 2011 - 2016 refers native caret position and size on windows whose
4599 // class name is one of Mozilla's windows for deciding candidate window
4600 // position. Additionally, ATOK 2015 and earlier behaves really odd when
4601 // we don't create native caret. Therefore, we need to create native caret
4602 // only when ATOK 2011 - 2015 is active (i.e., not necessary for ATOK 2016).
4603 // However, if a11y module is handling native caret, we shouldn't touch it.
4604 // Note that ATOK must require the latest information of the caret. So,
4605 // even if we'll create native caret later, we need to creat it here with
4606 // current information.
4607 if (!IMEHandler::IsA11yHandlingNativeCaret() &&
4608 StaticPrefs::intl_tsf_hack_atok_create_native_caret() &&
4609 TSFStaticSink::IsATOKReferringNativeCaretActive() &&
4610 mComposition
.isSome() &&
4611 mComposition
->IsOffsetInRangeOrEndOffset(acpStart
) &&
4612 mComposition
->IsOffsetInRangeOrEndOffset(acpEnd
)) {
4613 CreateNativeCaret();
4616 MOZ_LOG(gIMELog
, LogLevel::Info
,
4617 ("0x%p TSFTextStore::GetTextExt() succeeded: "
4618 "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }, *pfClipped=%s",
4619 this, prc
->left
, prc
->top
, prc
->right
, prc
->bottom
,
4620 GetBoolName(*pfClipped
)));
4625 bool TSFTextStore::MaybeHackNoErrorLayoutBugs(LONG
& aACPStart
, LONG
& aACPEnd
) {
4626 // When ITextStoreACP::GetTextExt() returns TS_E_NOLAYOUT, TSF returns E_FAIL
4627 // to its caller (typically, active TIP). Then, most TIPs abort current job
4628 // or treat such application as non-GUI apps. E.g., some of them give up
4629 // showing candidate window, some others show candidate window at top-left of
4630 // the screen. For avoiding this issue, when there is composition (until
4631 // composition is actually committed in remote content), we should not
4632 // return TS_E_NOLAYOUT error for TIPs whose some features are broken by
4634 // Note that ideally, this issue should be avoided by each TIP since this
4635 // won't be fixed at least on non-latest Windows. Actually, Google Japanese
4636 // Input (based on Mozc) does it. When GetTextExt() returns E_FAIL, TIPs
4637 // should try to check result of GetRangeFromPoint() because TSF returns
4638 // TS_E_NOLAYOUT correctly in this case. See:
4639 // https://github.com/google/mozc/blob/6b878e31fb6ac4347dc9dfd8ccc1080fe718479f/src/win32/tip/tip_range_util.cc#L237-L257
4641 if (!IsHandlingCompositionInContent() || mContentForTSF
.isNothing() ||
4642 !mContentForTSF
->HasOrHadComposition() ||
4643 !mContentForTSF
->IsLayoutChangedAt(aACPEnd
)) {
4647 MOZ_ASSERT(mComposition
.isNothing() ||
4648 mComposition
->StartOffset() ==
4649 mContentForTSF
->LatestCompositionRange()->StartOffset());
4650 MOZ_ASSERT(mComposition
.isNothing() ||
4651 mComposition
->EndOffset() ==
4652 mContentForTSF
->LatestCompositionRange()->EndOffset());
4654 // If TSF does not have the bug, we need to hack only with a few TIPs.
4655 static const bool sAlllowToStopHackingIfFine
=
4656 IsWindows10BuildOrLater(17643) &&
4658 intl_tsf_hack_allow_to_stop_hacking_on_build_17643_or_later();
4660 // We need to compute active TIP now. This may take a couple of milliseconds,
4661 // however, it'll be cached, so, must be faster than check active TIP every
4662 // GetTextExt() calls.
4663 const Maybe
<Selection
>& selectionForTSF
= SelectionForTSF();
4664 switch (TSFStaticSink::ActiveTIP()) {
4665 // MS IME for Japanese doesn't support asynchronous handling at deciding
4666 // its suggest list window position. The feature was implemented
4667 // starting from Windows 8. And also we may meet same trouble in e10s
4668 // mode on Win7. So, we should never return TS_E_NOLAYOUT to MS IME for
4670 case TextInputProcessorID::eMicrosoftIMEForJapanese
:
4671 // Basically, MS-IME tries to retrieve whole composition string rect
4672 // at deciding suggest window immediately after unlocking the document.
4673 // However, in e10s mode, the content hasn't updated yet in most cases.
4674 // Therefore, if the first character at the retrieving range rect is
4675 // available, we should use it as the result.
4676 // Note that according to bug 1609675, MS-IME for Japanese itself does
4677 // not handle TS_E_NOLAYOUT correctly at least on Build 18363.657 (1909).
4679 intl_tsf_hack_ms_japanese_ime_do_not_return_no_layout_error_at_first_char() &&
4680 aACPStart
< aACPEnd
) {
4681 aACPEnd
= aACPStart
;
4684 if (sAlllowToStopHackingIfFine
) {
4687 // Although, the condition is not clear, MS-IME sometimes retrieves the
4688 // caret rect immediately after modifying the composition string but
4689 // before unlocking the document. In such case, we should return the
4690 // nearest character rect.
4691 // (Let's return true if there is no selection which must be not expected
4692 // by MS-IME nor TSF.)
4694 intl_tsf_hack_ms_japanese_ime_do_not_return_no_layout_error_at_caret() &&
4695 aACPStart
== aACPEnd
&& selectionForTSF
.isSome() &&
4696 (!selectionForTSF
->HasRange() ||
4697 (selectionForTSF
->Collapsed() &&
4698 selectionForTSF
->EndOffset() == aACPEnd
))) {
4699 int32_t minOffsetOfLayoutChanged
=
4700 static_cast<int32_t>(mContentForTSF
->MinModifiedOffset().value());
4701 aACPEnd
= aACPStart
= std::max(minOffsetOfLayoutChanged
- 1, 0);
4706 // The bug of Microsoft Office IME 2010 for Japanese is similar to
4707 // MS-IME for Win 8.1 and Win 10. Newer version of MS Office IME is not
4708 // released yet. So, we can hack it without prefs because there must be
4709 // no developers who want to disable this hack for tests.
4710 // XXX We have not tested with Microsoft Office IME 2010 since it's
4711 // installable only with Win7 and Win8 (i.e., cannot install Win8.1
4712 // and Win10), and requires upgrade to Win10.
4713 case TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese
:
4714 // Basically, MS-IME tries to retrieve whole composition string rect
4715 // at deciding suggest window immediately after unlocking the document.
4716 // However, in e10s mode, the content hasn't updated yet in most cases.
4717 // Therefore, if the first character at the retrieving range rect is
4718 // available, we should use it as the result.
4719 if (aACPStart
< aACPEnd
) {
4720 aACPEnd
= aACPStart
;
4722 // Although, the condition is not clear, MS-IME sometimes retrieves the
4723 // caret rect immediately after modifying the composition string but
4724 // before unlocking the document. In such case, we should return the
4725 // nearest character rect.
4726 // (Let's return true if there is no selection which must be not expected
4727 // by MS-IME nor TSF.)
4728 else if (aACPStart
== aACPEnd
&& selectionForTSF
.isSome() &&
4729 (!selectionForTSF
->HasRange() ||
4730 (selectionForTSF
->Collapsed() &&
4731 selectionForTSF
->EndOffset() == aACPEnd
))) {
4732 int32_t minOffsetOfLayoutChanged
=
4733 static_cast<int32_t>(mContentForTSF
->MinModifiedOffset().value());
4734 aACPEnd
= aACPStart
= std::max(minOffsetOfLayoutChanged
- 1, 0);
4739 // ATOK fails to handle TS_E_NOLAYOUT only when it decides the position of
4740 // suggest window. In such case, ATOK tries to query rect of whole or a
4741 // part of composition string.
4742 // FYI: ATOK changes their implementation around candidate window and
4743 // suggest widget at ATOK 2016. Therefore, there are some differences
4744 // ATOK 2015 (or older) and ATOK 2016 (or newer).
4745 // FYI: ATOK 2017 stops referring our window class name. I.e., ATOK 2016
4746 // and older may behave differently only on Gecko but this must be
4747 // finished from ATOK 2017.
4748 // FYI: For testing with legacy ATOK, we should hack it even if current ATOK
4749 // refers native caret rect on windows whose window class is one of
4750 // Mozilla window classes and we stop creating native caret for ATOK
4751 // because creating native caret causes ATOK refers caret position
4752 // when GetTextExt() returns TS_E_NOLAYOUT.
4753 case TextInputProcessorID::eATOK2011
:
4754 case TextInputProcessorID::eATOK2012
:
4755 case TextInputProcessorID::eATOK2013
:
4756 case TextInputProcessorID::eATOK2014
:
4757 case TextInputProcessorID::eATOK2015
:
4758 // ATOK 2016 and later may temporarily show candidate window at odd
4759 // position when you convert a word quickly (e.g., keep pressing
4760 // space bar). So, on ATOK 2016 or later, we need to keep hacking the
4761 // result of GetTextExt().
4762 if (sAlllowToStopHackingIfFine
) {
4765 // If we'll create native caret where we paint our caret. Then, ATOK
4766 // will refer native caret. So, we don't need to hack anything in
4768 if (StaticPrefs::intl_tsf_hack_atok_create_native_caret()) {
4769 MOZ_ASSERT(TSFStaticSink::IsATOKReferringNativeCaretActive());
4773 case TextInputProcessorID::eATOK2016
:
4774 case TextInputProcessorID::eATOKUnknown
:
4776 intl_tsf_hack_atok_do_not_return_no_layout_error_of_composition_string()) {
4779 // If the range is in the composition string, we should return rectangle
4780 // in it as far as possible.
4781 if (!mContentForTSF
->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4783 !mContentForTSF
->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4788 // Japanist 10 fails to handle TS_E_NOLAYOUT when it decides the position
4789 // of candidate window. In such case, Japanist shows candidate window at
4790 // top-left of the screen. So, we should return the nearest caret rect
4791 // where we know. This is Japanist's bug. So, even after build 17643,
4792 // we need this hack.
4793 case TextInputProcessorID::eJapanist10
:
4795 intl_tsf_hack_japanist10_do_not_return_no_layout_error_of_composition_string()) {
4798 if (!mContentForTSF
->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4800 !mContentForTSF
->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
4805 // Free ChangJie 2010 doesn't handle ITfContextView::GetTextExt() properly.
4806 // This must be caused by the bug of TSF since Free ChangJie works fine on
4807 // build 17643 and later.
4808 case TextInputProcessorID::eFreeChangJie
:
4809 if (sAlllowToStopHackingIfFine
) {
4813 intl_tsf_hack_free_chang_jie_do_not_return_no_layout_error()) {
4816 aACPEnd
= mContentForTSF
->LatestCompositionRange()->StartOffset();
4817 aACPStart
= std::min(aACPStart
, aACPEnd
);
4819 // Some Traditional Chinese TIPs of Microsoft don't show candidate window
4820 // in e10s mode on Win8 or later.
4821 case TextInputProcessorID::eMicrosoftQuick
:
4822 if (sAlllowToStopHackingIfFine
) {
4823 return false; // MS Quick works fine with Win10 build 17643.
4826 case TextInputProcessorID::eMicrosoftChangJie
:
4828 intl_tsf_hack_ms_traditional_chinese_do_not_return_no_layout_error()) {
4831 aACPEnd
= mContentForTSF
->LatestCompositionRange()->StartOffset();
4832 aACPStart
= std::min(aACPStart
, aACPEnd
);
4834 // Some Simplified Chinese TIPs of Microsoft don't show candidate window
4835 // in e10s mode on Win8 or later.
4836 // FYI: Only Simplified Chinese TIPs of Microsoft still require this hack
4837 // because they sometimes do not show candidate window when we return
4838 // TS_E_NOLAYOUT for first query. Note that even when they show
4839 // candidate window properly, we return TS_E_NOLAYOUT and following
4840 // log looks same as when they don't show candidate window. Perhaps,
4841 // there is stateful cause or race in them.
4842 case TextInputProcessorID::eMicrosoftPinyin
:
4843 case TextInputProcessorID::eMicrosoftWubi
:
4845 intl_tsf_hack_ms_simplified_chinese_do_not_return_no_layout_error()) {
4848 aACPEnd
= mContentForTSF
->LatestCompositionRange()->StartOffset();
4849 aACPStart
= std::min(aACPStart
, aACPEnd
);
4855 // If we hack the queried range for active TIP, that means we should not
4856 // return TS_E_NOLAYOUT even if hacked offset is still modified. So, as
4857 // far as possible, we should adjust the offset.
4858 MOZ_ASSERT(mContentForTSF
->IsLayoutChanged());
4859 bool collapsed
= aACPStart
== aACPEnd
;
4860 // Note that even if all characters in the editor or the composition
4861 // string was modified, 0 or start offset of the composition string is
4862 // useful because it may return caret rect or old character's rect which
4863 // the user still see. That must be useful information for TIP.
4864 int32_t firstModifiedOffset
=
4865 static_cast<int32_t>(mContentForTSF
->MinModifiedOffset().value());
4866 LONG lastUnmodifiedOffset
= std::max(firstModifiedOffset
- 1, 0);
4867 if (mContentForTSF
->IsLayoutChangedAt(aACPStart
)) {
4868 if (aACPStart
>= mContentForTSF
->LatestCompositionRange()->StartOffset()) {
4869 // If mContentForTSF has last composition string and current
4870 // composition string, we can assume that ContentCacheInParent has
4871 // cached rects of composition string at least length of current
4872 // composition string. Otherwise, we can assume that rect for
4873 // first character of composition string is stored since it was
4874 // selection start or caret position.
4875 LONG maxCachedOffset
=
4876 mContentForTSF
->LatestCompositionRange()->EndOffset();
4877 if (mContentForTSF
->LastComposition().isSome()) {
4878 maxCachedOffset
= std::min(
4879 maxCachedOffset
, mContentForTSF
->LastComposition()->EndOffset());
4881 aACPStart
= std::min(aACPStart
, maxCachedOffset
);
4883 // Otherwise, we don't know which character rects are cached. So, we
4884 // need to use first unmodified character's rect in this case. Even
4885 // if there is no character, the query event will return caret rect
4888 aACPStart
= lastUnmodifiedOffset
;
4890 MOZ_ASSERT(aACPStart
<= aACPEnd
);
4893 // If TIP requests caret rect with collapsed range, we should keep
4894 // collapsing the range.
4896 aACPEnd
= aACPStart
;
4898 // Let's set aACPEnd to larger offset of last unmodified offset or
4899 // aACPStart which may be the first character offset of the composition
4900 // string. However, some TIPs may want to know the right edge of the
4901 // range. Therefore, if aACPEnd is in composition string and active TIP
4902 // doesn't retrieve caret rect (i.e., the range isn't collapsed), we
4903 // should keep using the original aACPEnd. Otherwise, we should set
4904 // aACPEnd to larger value of aACPStart and lastUnmodifiedOffset.
4905 else if (mContentForTSF
->IsLayoutChangedAt(aACPEnd
) &&
4906 !mContentForTSF
->LatestCompositionRange()
4907 ->IsOffsetInRangeOrEndOffset(aACPEnd
)) {
4908 aACPEnd
= std::max(aACPStart
, lastUnmodifiedOffset
);
4912 gIMELog
, LogLevel::Debug
,
4913 ("0x%p TSFTextStore::HackNoErrorLayoutBugs() hacked the queried range "
4914 "for not returning TS_E_NOLAYOUT, new values are: "
4915 "aACPStart=%ld, aACPEnd=%ld",
4916 this, aACPStart
, aACPEnd
));
4922 TSFTextStore::GetScreenExt(TsViewCookie vcView
, RECT
* prc
) {
4923 MOZ_LOG(gIMELog
, LogLevel::Info
,
4924 ("0x%p TSFTextStore::GetScreenExt(vcView=%ld, prc=0x%p)", this,
4927 if (vcView
!= TEXTSTORE_DEFAULT_VIEW
) {
4928 MOZ_LOG(gIMELog
, LogLevel::Error
,
4929 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4930 "called with invalid view",
4932 return E_INVALIDARG
;
4936 MOZ_LOG(gIMELog
, LogLevel::Error
,
4937 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4940 return E_INVALIDARG
;
4944 MOZ_LOG(gIMELog
, LogLevel::Error
,
4945 ("0x%p TSFTextStore::GetScreenExt() returns empty rect "
4946 "due to already destroyed",
4948 prc
->left
= prc
->top
= prc
->right
= prc
->bottom
= 0;
4952 if (!GetScreenExtInternal(*prc
)) {
4953 MOZ_LOG(gIMELog
, LogLevel::Error
,
4954 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4955 "GetScreenExtInternal() failure",
4960 MOZ_LOG(gIMELog
, LogLevel::Info
,
4961 ("0x%p TSFTextStore::GetScreenExt() succeeded: "
4962 "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
4963 this, prc
->left
, prc
->top
, prc
->right
, prc
->bottom
));
4967 bool TSFTextStore::GetScreenExtInternal(RECT
& aScreenExt
) {
4968 MOZ_LOG(gIMELog
, LogLevel::Debug
,
4969 ("0x%p TSFTextStore::GetScreenExtInternal()", this));
4971 MOZ_ASSERT(!mDestroyed
);
4973 // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
4974 WidgetQueryContentEvent
queryEditorRectEvent(true, eQueryEditorRect
, mWidget
);
4975 mWidget
->InitEvent(queryEditorRectEvent
);
4976 DispatchEvent(queryEditorRectEvent
);
4977 if (queryEditorRectEvent
.Failed()) {
4978 MOZ_LOG(gIMELog
, LogLevel::Error
,
4979 ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
4980 "eQueryEditorRect failure",
4985 nsWindow
* refWindow
=
4986 static_cast<nsWindow
*>(!!queryEditorRectEvent
.mReply
->mFocusedWidget
4987 ? queryEditorRectEvent
.mReply
->mFocusedWidget
4988 : static_cast<nsIWidget
*>(mWidget
.get()));
4989 // Result rect is in top level widget coordinates
4990 refWindow
= refWindow
->GetTopLevelWindow(false);
4992 MOZ_LOG(gIMELog
, LogLevel::Error
,
4993 ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
4994 "no top level window",
4999 LayoutDeviceIntRect boundRect
= refWindow
->GetClientBounds();
5000 boundRect
.MoveTo(0, 0);
5002 // Clip frame rect to window rect
5003 boundRect
.IntersectRect(queryEditorRectEvent
.mReply
->mRect
, boundRect
);
5004 if (!boundRect
.IsEmpty()) {
5005 boundRect
.MoveBy(refWindow
->WidgetToScreenOffset());
5006 ::SetRect(&aScreenExt
, boundRect
.X(), boundRect
.Y(), boundRect
.XMost(),
5009 ::SetRectEmpty(&aScreenExt
);
5012 MOZ_LOG(gIMELog
, LogLevel::Debug
,
5013 ("0x%p TSFTextStore::GetScreenExtInternal() succeeded: "
5014 "aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
5015 this, aScreenExt
.left
, aScreenExt
.top
, aScreenExt
.right
,
5016 aScreenExt
.bottom
));
5021 TSFTextStore::GetWnd(TsViewCookie vcView
, HWND
* phwnd
) {
5022 MOZ_LOG(gIMELog
, LogLevel::Info
,
5023 ("0x%p TSFTextStore::GetWnd(vcView=%ld, phwnd=0x%p), "
5025 this, vcView
, phwnd
, mWidget
.get()));
5027 if (vcView
!= TEXTSTORE_DEFAULT_VIEW
) {
5028 MOZ_LOG(gIMELog
, LogLevel::Error
,
5029 ("0x%p TSFTextStore::GetWnd() FAILED due to "
5030 "called with invalid view",
5032 return E_INVALIDARG
;
5036 MOZ_LOG(gIMELog
, LogLevel::Error
,
5037 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
5040 return E_INVALIDARG
;
5043 *phwnd
= mWidget
? mWidget
->GetWindowHandle() : nullptr;
5045 MOZ_LOG(gIMELog
, LogLevel::Info
,
5046 ("0x%p TSFTextStore::GetWnd() succeeded: *phwnd=0x%p", this,
5047 static_cast<void*>(*phwnd
)));
5052 TSFTextStore::InsertTextAtSelection(DWORD dwFlags
, const WCHAR
* pchText
,
5053 ULONG cch
, LONG
* pacpStart
, LONG
* pacpEnd
,
5054 TS_TEXTCHANGE
* pChange
) {
5056 gIMELog
, LogLevel::Info
,
5057 ("0x%p TSFTextStore::InsertTextAtSelection(dwFlags=%s, "
5058 "pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, "
5059 "pChange=0x%p), mComposition=%s",
5062 : dwFlags
== TF_IAS_NOQUERY
? "TF_IAS_NOQUERY"
5063 : dwFlags
== TF_IAS_QUERYONLY
? "TF_IAS_QUERYONLY"
5065 pchText
, pchText
&& cch
? GetEscapedUTF8String(pchText
, cch
).get() : "",
5066 cch
, pacpStart
, pacpEnd
, pChange
, ToString(mComposition
).c_str()));
5068 if (cch
&& !pchText
) {
5069 MOZ_LOG(gIMELog
, LogLevel::Error
,
5070 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5073 return E_INVALIDARG
;
5076 if (TS_IAS_QUERYONLY
== dwFlags
) {
5077 if (!IsReadLocked()) {
5078 MOZ_LOG(gIMELog
, LogLevel::Error
,
5079 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5080 "not locked (read)",
5085 if (!pacpStart
|| !pacpEnd
) {
5086 MOZ_LOG(gIMELog
, LogLevel::Error
,
5087 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5090 return E_INVALIDARG
;
5093 // Get selection first
5094 Maybe
<Selection
>& selectionForTSF
= SelectionForTSF();
5095 if (selectionForTSF
.isNothing()) {
5096 MOZ_LOG(gIMELog
, LogLevel::Error
,
5097 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5098 "SelectionForTSF() failure",
5103 // Simulate text insertion
5104 if (selectionForTSF
->HasRange()) {
5105 *pacpStart
= selectionForTSF
->StartOffset();
5106 *pacpEnd
= selectionForTSF
->EndOffset();
5108 *pChange
= TS_TEXTCHANGE
{.acpStart
= selectionForTSF
->StartOffset(),
5109 .acpOldEnd
= selectionForTSF
->EndOffset(),
5110 .acpNewEnd
= selectionForTSF
->StartOffset() +
5111 static_cast<LONG
>(cch
)};
5114 // There is no error code to return "no selection" state from this method.
5115 // This means that TSF/TIP should check `GetSelection` result first and
5116 // stop using this. However, this could be called by TIP/TSF if they do
5117 // not do so. Therefore, we should use start of editor instead, but
5118 // notify the caller of nothing will be inserted with pChange->acpNewEnd.
5119 *pacpStart
= *pacpEnd
= 0;
5121 *pChange
= TS_TEXTCHANGE
{.acpStart
= 0, .acpOldEnd
= 0, .acpNewEnd
= 0};
5125 if (!IsReadWriteLocked()) {
5126 MOZ_LOG(gIMELog
, LogLevel::Error
,
5127 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5128 "not locked (read-write)",
5134 MOZ_LOG(gIMELog
, LogLevel::Error
,
5135 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5138 return E_INVALIDARG
;
5141 if (TS_IAS_NOQUERY
!= dwFlags
&& (!pacpStart
|| !pacpEnd
)) {
5142 MOZ_LOG(gIMELog
, LogLevel::Error
,
5143 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5146 return E_INVALIDARG
;
5149 if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText
, cch
),
5151 MOZ_LOG(gIMELog
, LogLevel::Error
,
5152 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5153 "InsertTextAtSelectionInternal() failure",
5158 if (TS_IAS_NOQUERY
!= dwFlags
) {
5159 *pacpStart
= pChange
->acpStart
;
5160 *pacpEnd
= pChange
->acpNewEnd
;
5163 MOZ_LOG(gIMELog
, LogLevel::Info
,
5164 ("0x%p TSFTextStore::InsertTextAtSelection() succeeded: "
5165 "*pacpStart=%ld, *pacpEnd=%ld, "
5166 "*pChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld })",
5167 this, pacpStart
? *pacpStart
: 0, pacpEnd
? *pacpEnd
: 0,
5168 pChange
? pChange
->acpStart
: 0, pChange
? pChange
->acpOldEnd
: 0,
5169 pChange
? pChange
->acpNewEnd
: 0));
5173 bool TSFTextStore::InsertTextAtSelectionInternal(const nsAString
& aInsertStr
,
5174 TS_TEXTCHANGE
* aTextChange
) {
5175 MOZ_LOG(gIMELog
, LogLevel::Debug
,
5176 ("0x%p TSFTextStore::InsertTextAtSelectionInternal("
5177 "aInsertStr=\"%s\", aTextChange=0x%p), mComposition=%s",
5178 this, GetEscapedUTF8String(aInsertStr
).get(), aTextChange
,
5179 ToString(mComposition
).c_str()));
5181 Maybe
<Content
>& contentForTSF
= ContentForTSF();
5182 if (contentForTSF
.isNothing()) {
5183 MOZ_LOG(gIMELog
, LogLevel::Error
,
5184 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() failed "
5185 "due to ContentForTSF() failure()",
5190 MaybeDispatchKeyboardEventAsProcessedByIME();
5193 gIMELog
, LogLevel::Error
,
5194 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() FAILED due to "
5195 "destroyed during dispatching a keyboard event",
5200 const auto numberOfCRLFs
= [&]() -> uint32_t {
5201 const auto* str
= aInsertStr
.BeginReading();
5203 for (uint32_t i
= 0; i
+ 1 < aInsertStr
.Length(); i
++) {
5204 if (str
[i
] == '\r' && str
[i
+ 1] == '\n') {
5211 if (numberOfCRLFs
) {
5213 if (TSFStaticSink::GetActiveTIPNameForTelemetry(key
)) {
5214 Telemetry::ScalarSet(
5215 Telemetry::ScalarID::WIDGET_IME_NAME_ON_WINDOWS_INSERTED_CRLF
, key
,
5220 TS_SELECTION_ACP oldSelection
= contentForTSF
->Selection()->ACPRef();
5221 if (mComposition
.isNothing()) {
5222 // Use a temporary composition to contain the text
5223 PendingAction
* compositionStart
= mPendingActions
.AppendElements(2);
5224 PendingAction
* compositionEnd
= compositionStart
+ 1;
5226 compositionStart
->mType
= PendingAction::Type::eCompositionStart
;
5227 compositionStart
->mSelectionStart
= oldSelection
.acpStart
;
5228 compositionStart
->mSelectionLength
=
5229 oldSelection
.acpEnd
- oldSelection
.acpStart
;
5230 compositionStart
->mAdjustSelection
= false;
5232 compositionEnd
->mType
= PendingAction::Type::eCompositionEnd
;
5233 compositionEnd
->mData
= aInsertStr
;
5234 compositionEnd
->mSelectionStart
= compositionStart
->mSelectionStart
;
5236 MOZ_LOG(gIMELog
, LogLevel::Debug
,
5237 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
5238 "appending pending compositionstart and compositionend... "
5239 "PendingCompositionStart={ mSelectionStart=%ld, "
5240 "mSelectionLength=%ld }, PendingCompositionEnd={ mData=\"%s\" "
5241 "(Length()=%zu), mSelectionStart=%ld }",
5242 this, compositionStart
->mSelectionStart
,
5243 compositionStart
->mSelectionLength
,
5244 GetEscapedUTF8String(compositionEnd
->mData
).get(),
5245 compositionEnd
->mData
.Length(), compositionEnd
->mSelectionStart
));
5248 contentForTSF
->ReplaceSelectedTextWith(aInsertStr
);
5251 aTextChange
->acpStart
= oldSelection
.acpStart
;
5252 aTextChange
->acpOldEnd
= oldSelection
.acpEnd
;
5253 aTextChange
->acpNewEnd
= contentForTSF
->Selection()->EndOffset();
5257 gIMELog
, LogLevel::Debug
,
5258 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
5259 "succeeded: mWidget=0x%p, mWidget->Destroyed()=%s, aTextChange={ "
5260 "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
5261 this, mWidget
.get(), GetBoolName(mWidget
? mWidget
->Destroyed() : true),
5262 aTextChange
? aTextChange
->acpStart
: 0,
5263 aTextChange
? aTextChange
->acpOldEnd
: 0,
5264 aTextChange
? aTextChange
->acpNewEnd
: 0));
5269 TSFTextStore::InsertEmbeddedAtSelection(DWORD dwFlags
, IDataObject
* pDataObject
,
5270 LONG
* pacpStart
, LONG
* pacpEnd
,
5271 TS_TEXTCHANGE
* pChange
) {
5272 MOZ_LOG(gIMELog
, LogLevel::Info
,
5273 ("0x%p TSFTextStore::InsertEmbeddedAtSelection() called "
5274 "but not supported (E_NOTIMPL)",
5277 // embedded objects are not supported
5281 HRESULT
TSFTextStore::RecordCompositionStartAction(
5282 ITfCompositionView
* aCompositionView
, ITfRange
* aRange
,
5283 bool aPreserveSelection
) {
5284 MOZ_LOG(gIMELog
, LogLevel::Debug
,
5285 ("0x%p TSFTextStore::RecordCompositionStartAction("
5286 "aCompositionView=0x%p, aRange=0x%p, aPreserveSelection=%s), "
5288 this, aCompositionView
, aRange
, GetBoolName(aPreserveSelection
),
5289 ToString(mComposition
).c_str()));
5291 LONG start
= 0, length
= 0;
5292 HRESULT hr
= GetRangeExtent(aRange
, &start
, &length
);
5294 MOZ_LOG(gIMELog
, LogLevel::Error
,
5295 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5296 "due to GetRangeExtent() failure",
5301 return RecordCompositionStartAction(aCompositionView
, start
, length
,
5302 aPreserveSelection
);
5305 HRESULT
TSFTextStore::RecordCompositionStartAction(
5306 ITfCompositionView
* aCompositionView
, LONG aStart
, LONG aLength
,
5307 bool aPreserveSelection
) {
5308 MOZ_LOG(gIMELog
, LogLevel::Debug
,
5309 ("0x%p TSFTextStore::RecordCompositionStartAction("
5310 "aCompositionView=0x%p, aStart=%ld, aLength=%ld, "
5311 "aPreserveSelection=%s), "
5313 this, aCompositionView
, aStart
, aLength
,
5314 GetBoolName(aPreserveSelection
), ToString(mComposition
).c_str()));
5316 Maybe
<Content
>& contentForTSF
= ContentForTSF();
5317 if (contentForTSF
.isNothing()) {
5318 MOZ_LOG(gIMELog
, LogLevel::Error
,
5319 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5320 "due to ContentForTSF() failure",
5325 MaybeDispatchKeyboardEventAsProcessedByIME();
5328 gIMELog
, LogLevel::Error
,
5329 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED due to "
5330 "destroyed during dispatching a keyboard event",
5335 CompleteLastActionIfStillIncomplete();
5337 // TIP may have inserted text at selection before calling
5338 // OnStartComposition(). In this case, we've already created a pending
5339 // compositionend. If new composition replaces all commit string of the
5340 // pending compositionend, we should cancel the pending compositionend and
5341 // keep the previous composition normally.
5342 // On Windows 7, MS-IME for Korean, MS-IME 2010 for Korean and MS Old Hangul
5343 // may start composition with calling InsertTextAtSelection() and
5344 // OnStartComposition() with this order (bug 1208043).
5345 // On Windows 10, MS Pinyin, MS Wubi, MS ChangJie and MS Quick commits
5346 // last character and replace it with empty string with new composition
5347 // when user removes last character of composition string with Backspace
5348 // key (bug 1462257).
5349 if (!aPreserveSelection
&&
5350 IsLastPendingActionCompositionEndAt(aStart
, aLength
)) {
5351 const PendingAction
& pendingCompositionEnd
= mPendingActions
.LastElement();
5352 contentForTSF
->RestoreCommittedComposition(aCompositionView
,
5353 pendingCompositionEnd
);
5354 mPendingActions
.RemoveLastElement();
5355 MOZ_LOG(gIMELog
, LogLevel::Info
,
5356 ("0x%p TSFTextStore::RecordCompositionStartAction() "
5357 "succeeded: restoring the committed string as composing string, "
5358 "mComposition=%s, mSelectionForTSF=%s",
5359 this, ToString(mComposition
).c_str(),
5360 ToString(mSelectionForTSF
).c_str()));
5364 PendingAction
* action
= mPendingActions
.AppendElement();
5365 action
->mType
= PendingAction::Type::eCompositionStart
;
5366 action
->mSelectionStart
= aStart
;
5367 action
->mSelectionLength
= aLength
;
5369 Maybe
<Selection
>& selectionForTSF
= SelectionForTSF();
5370 if (selectionForTSF
.isNothing()) {
5371 MOZ_LOG(gIMELog
, LogLevel::Error
,
5372 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5373 "due to SelectionForTSF() failure",
5375 action
->mAdjustSelection
= true;
5376 } else if (!selectionForTSF
->HasRange()) {
5377 // If there is no selection, let's collapse seletion to the insertion point.
5378 action
->mAdjustSelection
= true;
5379 } else if (selectionForTSF
->MinOffset() != aStart
||
5380 selectionForTSF
->MaxOffset() != aStart
+ aLength
) {
5381 // If new composition range is different from current selection range,
5382 // we need to set selection before dispatching compositionstart event.
5383 action
->mAdjustSelection
= true;
5385 // We shouldn't dispatch selection set event before dispatching
5386 // compositionstart event because it may cause put caret different
5387 // position in HTML editor since generated flat text content and offset in
5388 // it are lossy data of HTML contents.
5389 action
->mAdjustSelection
= false;
5392 contentForTSF
->StartComposition(aCompositionView
, *action
,
5393 aPreserveSelection
);
5394 MOZ_ASSERT(mComposition
.isSome());
5395 action
->mData
= mComposition
->DataRef();
5397 MOZ_LOG(gIMELog
, LogLevel::Info
,
5398 ("0x%p TSFTextStore::RecordCompositionStartAction() succeeded: "
5399 "mComposition=%s, mSelectionForTSF=%s }",
5400 this, ToString(mComposition
).c_str(),
5401 ToString(mSelectionForTSF
).c_str()));
5406 TSFTextStore::RecordCompositionEndAction() {
5407 MOZ_LOG(gIMELog
, LogLevel::Debug
,
5408 ("0x%p TSFTextStore::RecordCompositionEndAction(), "
5410 this, ToString(mComposition
).c_str()));
5412 MOZ_ASSERT(mComposition
.isSome());
5414 if (mComposition
.isNothing()) {
5415 MOZ_LOG(gIMELog
, LogLevel::Error
,
5416 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
5422 MaybeDispatchKeyboardEventAsProcessedByIME();
5424 MOZ_LOG(gIMELog
, LogLevel::Error
,
5425 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
5426 "destroyed during dispatching a keyboard event",
5431 // If we're handling incomplete composition update or already handled
5432 // composition update, we can forget them since composition end will send
5433 // the latest composition string and it overwrites the composition string
5434 // even if we dispatch eCompositionChange event before that. So, let's
5435 // forget all composition updates now.
5436 RemoveLastCompositionUpdateActions();
5437 PendingAction
* action
= mPendingActions
.AppendElement();
5438 action
->mType
= PendingAction::Type::eCompositionEnd
;
5439 action
->mData
= mComposition
->DataRef();
5440 action
->mSelectionStart
= mComposition
->StartOffset();
5442 Maybe
<Content
>& contentForTSF
= ContentForTSF();
5443 if (contentForTSF
.isNothing()) {
5444 MOZ_LOG(gIMELog
, LogLevel::Error
,
5445 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due "
5446 "to ContentForTSF() failure",
5450 contentForTSF
->EndComposition(*action
);
5452 // If this composition was restart but the composition doesn't modify
5453 // anything, we should remove the pending composition for preventing to
5454 // dispatch redundant composition events.
5455 for (size_t i
= mPendingActions
.Length(), j
= 1; i
> 0; --i
, ++j
) {
5456 PendingAction
& pendingAction
= mPendingActions
[i
- 1];
5457 if (pendingAction
.mType
== PendingAction::Type::eCompositionStart
) {
5458 if (pendingAction
.mData
!= action
->mData
) {
5461 // When only setting selection is necessary, we should append it.
5462 if (pendingAction
.mAdjustSelection
) {
5463 LONG selectionStart
= pendingAction
.mSelectionStart
;
5464 LONG selectionLength
= pendingAction
.mSelectionLength
;
5466 PendingAction
* setSelection
= mPendingActions
.AppendElement();
5467 setSelection
->mType
= PendingAction::Type::eSetSelection
;
5468 setSelection
->mSelectionStart
= selectionStart
;
5469 setSelection
->mSelectionLength
= selectionLength
;
5470 setSelection
->mSelectionReversed
= false;
5472 // Remove the redundant pending composition.
5473 mPendingActions
.RemoveElementsAt(i
- 1, j
);
5474 MOZ_LOG(gIMELog
, LogLevel::Info
,
5475 ("0x%p TSFTextStore::RecordCompositionEndAction(), "
5476 "succeeded, but the composition was canceled due to redundant",
5483 gIMELog
, LogLevel::Info
,
5484 ("0x%p TSFTextStore::RecordCompositionEndAction(), succeeded", this));
5489 TSFTextStore::OnStartComposition(ITfCompositionView
* pComposition
, BOOL
* pfOk
) {
5490 MOZ_LOG(gIMELog
, LogLevel::Info
,
5491 ("0x%p TSFTextStore::OnStartComposition(pComposition=0x%p, "
5492 "pfOk=0x%p), mComposition=%s",
5493 this, pComposition
, pfOk
, ToString(mComposition
).c_str()));
5495 AutoPendingActionAndContentFlusher
flusher(this);
5499 // Only one composition at a time
5500 if (mComposition
.isSome()) {
5501 MOZ_LOG(gIMELog
, LogLevel::Error
,
5502 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5503 "there is another composition already (but returns S_OK)",
5508 RefPtr
<ITfRange
> range
;
5509 HRESULT hr
= pComposition
->GetRange(getter_AddRefs(range
));
5511 MOZ_LOG(gIMELog
, LogLevel::Error
,
5512 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5513 "pComposition->GetRange() failure",
5517 hr
= RecordCompositionStartAction(pComposition
, range
, false);
5519 MOZ_LOG(gIMELog
, LogLevel::Error
,
5520 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5521 "RecordCompositionStartAction() failure",
5527 MOZ_LOG(gIMELog
, LogLevel::Info
,
5528 ("0x%p TSFTextStore::OnStartComposition() succeeded", this));
5533 TSFTextStore::OnUpdateComposition(ITfCompositionView
* pComposition
,
5534 ITfRange
* pRangeNew
) {
5535 MOZ_LOG(gIMELog
, LogLevel::Info
,
5536 ("0x%p TSFTextStore::OnUpdateComposition(pComposition=0x%p, "
5537 "pRangeNew=0x%p), mComposition=%s",
5538 this, pComposition
, pRangeNew
, ToString(mComposition
).c_str()));
5540 AutoPendingActionAndContentFlusher
flusher(this);
5542 if (!mDocumentMgr
|| !mContext
) {
5543 MOZ_LOG(gIMELog
, LogLevel::Error
,
5544 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5545 "not ready for the composition",
5547 return E_UNEXPECTED
;
5549 if (mComposition
.isNothing()) {
5550 MOZ_LOG(gIMELog
, LogLevel::Error
,
5551 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5552 "no active composition",
5554 return E_UNEXPECTED
;
5556 if (mComposition
->GetView() != pComposition
) {
5557 MOZ_LOG(gIMELog
, LogLevel::Error
,
5558 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5559 "different composition view specified",
5561 return E_UNEXPECTED
;
5564 // pRangeNew is null when the update is not complete
5566 MaybeDispatchKeyboardEventAsProcessedByIME();
5568 MOZ_LOG(gIMELog
, LogLevel::Error
,
5569 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5570 "destroyed during dispatching a keyboard event",
5574 PendingAction
* action
= LastOrNewPendingCompositionUpdate();
5575 action
->mIncomplete
= true;
5576 MOZ_LOG(gIMELog
, LogLevel::Info
,
5577 ("0x%p TSFTextStore::OnUpdateComposition() succeeded but "
5583 HRESULT hr
= RestartCompositionIfNecessary(pRangeNew
);
5585 MOZ_LOG(gIMELog
, LogLevel::Error
,
5586 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5587 "RestartCompositionIfNecessary() failure",
5592 hr
= RecordCompositionUpdateAction();
5594 MOZ_LOG(gIMELog
, LogLevel::Error
,
5595 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5596 "RecordCompositionUpdateAction() failure",
5601 if (MOZ_LOG_TEST(gIMELog
, LogLevel::Info
)) {
5602 Maybe
<Selection
>& selectionForTSF
= SelectionForTSF();
5603 if (selectionForTSF
.isNothing()) {
5604 MOZ_LOG(gIMELog
, LogLevel::Error
,
5605 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5606 "SelectionForTSF() failure",
5608 return S_OK
; // Don't return error only when we're logging.
5610 MOZ_LOG(gIMELog
, LogLevel::Info
,
5611 ("0x%p TSFTextStore::OnUpdateComposition() succeeded: "
5612 "mComposition=%s, SelectionForTSF()=%s",
5613 this, ToString(mComposition
).c_str(),
5614 ToString(selectionForTSF
).c_str()));
5620 TSFTextStore::OnEndComposition(ITfCompositionView
* pComposition
) {
5621 MOZ_LOG(gIMELog
, LogLevel::Info
,
5622 ("0x%p TSFTextStore::OnEndComposition(pComposition=0x%p), "
5624 this, pComposition
, ToString(mComposition
).c_str()));
5626 AutoPendingActionAndContentFlusher
flusher(this);
5628 if (mComposition
.isNothing()) {
5629 MOZ_LOG(gIMELog
, LogLevel::Error
,
5630 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5631 "no active composition",
5633 return E_UNEXPECTED
;
5636 if (mComposition
->GetView() != pComposition
) {
5637 MOZ_LOG(gIMELog
, LogLevel::Error
,
5638 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5639 "different composition view specified",
5641 return E_UNEXPECTED
;
5644 HRESULT hr
= RecordCompositionEndAction();
5646 MOZ_LOG(gIMELog
, LogLevel::Error
,
5647 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5648 "RecordCompositionEndAction() failure",
5653 MOZ_LOG(gIMELog
, LogLevel::Info
,
5654 ("0x%p TSFTextStore::OnEndComposition(), succeeded", this));
5659 TSFTextStore::AdviseMouseSink(ITfRangeACP
* range
, ITfMouseSink
* pSink
,
5661 MOZ_LOG(gIMELog
, LogLevel::Info
,
5662 ("0x%p TSFTextStore::AdviseMouseSink(range=0x%p, pSink=0x%p, "
5664 this, range
, pSink
, pdwCookie
));
5667 MOZ_LOG(gIMELog
, LogLevel::Error
,
5668 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5669 "pdwCookie is null",
5671 return E_INVALIDARG
;
5673 // Initialize the result with invalid cookie for safety.
5674 *pdwCookie
= MouseTracker::kInvalidCookie
;
5677 MOZ_LOG(gIMELog
, LogLevel::Error
,
5678 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5681 return E_INVALIDARG
;
5684 MOZ_LOG(gIMELog
, LogLevel::Error
,
5685 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5688 return E_INVALIDARG
;
5691 // Looking for an unusing tracker.
5692 MouseTracker
* tracker
= nullptr;
5693 for (size_t i
= 0; i
< mMouseTrackers
.Length(); i
++) {
5694 if (mMouseTrackers
[i
].IsUsing()) {
5697 tracker
= &mMouseTrackers
[i
];
5699 // If there is no unusing tracker, create new one.
5700 // XXX Should we make limitation of the number of installs?
5702 tracker
= mMouseTrackers
.AppendElement();
5703 HRESULT hr
= tracker
->Init(this);
5705 MOZ_LOG(gIMELog
, LogLevel::Error
,
5706 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to "
5707 "failure of MouseTracker::Init()",
5712 HRESULT hr
= tracker
->AdviseSink(this, range
, pSink
);
5714 MOZ_LOG(gIMELog
, LogLevel::Error
,
5715 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to failure "
5716 "of MouseTracker::Init()",
5720 *pdwCookie
= tracker
->Cookie();
5721 MOZ_LOG(gIMELog
, LogLevel::Info
,
5722 ("0x%p TSFTextStore::AdviseMouseSink(), succeeded, "
5729 TSFTextStore::UnadviseMouseSink(DWORD dwCookie
) {
5731 gIMELog
, LogLevel::Info
,
5732 ("0x%p TSFTextStore::UnadviseMouseSink(dwCookie=%ld)", this, dwCookie
));
5733 if (dwCookie
== MouseTracker::kInvalidCookie
) {
5734 MOZ_LOG(gIMELog
, LogLevel::Error
,
5735 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5736 "the cookie is invalid value",
5738 return E_INVALIDARG
;
5740 // The cookie value must be an index of mMouseTrackers.
5741 // We can use this shortcut for now.
5742 if (static_cast<size_t>(dwCookie
) >= mMouseTrackers
.Length()) {
5743 MOZ_LOG(gIMELog
, LogLevel::Error
,
5744 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5745 "the cookie is too large value",
5747 return E_INVALIDARG
;
5749 MouseTracker
& tracker
= mMouseTrackers
[dwCookie
];
5750 if (!tracker
.IsUsing()) {
5751 MOZ_LOG(gIMELog
, LogLevel::Error
,
5752 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5753 "the found tracker uninstalled already",
5755 return E_INVALIDARG
;
5757 tracker
.UnadviseSink();
5758 MOZ_LOG(gIMELog
, LogLevel::Info
,
5759 ("0x%p TSFTextStore::UnadviseMouseSink(), succeeded", this));
5764 nsresult
TSFTextStore::OnFocusChange(bool aGotFocus
, nsWindow
* aFocusedWidget
,
5765 const InputContext
& aContext
) {
5766 MOZ_LOG(gIMELog
, LogLevel::Debug
,
5767 (" TSFTextStore::OnFocusChange(aGotFocus=%s, "
5768 "aFocusedWidget=0x%p, aContext=%s), "
5769 "sThreadMgr=0x%p, sEnabledTextStore=0x%p",
5770 GetBoolName(aGotFocus
), aFocusedWidget
,
5771 mozilla::ToString(aContext
).c_str(), sThreadMgr
.get(),
5772 sEnabledTextStore
.get()));
5774 if (NS_WARN_IF(!IsInTSFMode())) {
5775 return NS_ERROR_NOT_AVAILABLE
;
5778 RefPtr
<ITfDocumentMgr
> prevFocusedDocumentMgr
;
5779 bool hasFocus
= ThinksHavingFocus();
5780 RefPtr
<TSFTextStore
> oldTextStore
= sEnabledTextStore
.forget();
5782 // If currently oldTextStore still has focus, notifies TSF of losing focus.
5784 RefPtr
<ITfThreadMgr
> threadMgr
= sThreadMgr
;
5785 DebugOnly
<HRESULT
> hr
= threadMgr
->AssociateFocus(
5786 oldTextStore
->mWidget
->GetWindowHandle(), nullptr,
5787 getter_AddRefs(prevFocusedDocumentMgr
));
5788 NS_ASSERTION(SUCCEEDED(hr
), "Disassociating focus failed");
5789 NS_ASSERTION(prevFocusedDocumentMgr
== oldTextStore
->mDocumentMgr
,
5790 "different documentMgr has been associated with the window");
5793 // Even if there was a focused TextStore, we won't use it with new focused
5794 // editor. So, release it now.
5796 oldTextStore
->Destroy();
5799 if (NS_WARN_IF(!sThreadMgr
)) {
5800 MOZ_LOG(gIMELog
, LogLevel::Error
,
5801 (" TSFTextStore::OnFocusChange() FAILED, due to "
5802 "sThreadMgr being destroyed during calling "
5803 "ITfThreadMgr::AssociateFocus()"));
5804 return NS_ERROR_FAILURE
;
5806 if (NS_WARN_IF(sEnabledTextStore
)) {
5808 gIMELog
, LogLevel::Error
,
5809 (" TSFTextStore::OnFocusChange() FAILED, due to "
5810 "nested event handling has created another focused TextStore during "
5811 "calling ITfThreadMgr::AssociateFocus()"));
5812 return NS_ERROR_FAILURE
;
5815 // If this is a notification of blur, move focus to the dummy document
5817 if (!aGotFocus
|| !aContext
.mIMEState
.IsEditable()) {
5818 RefPtr
<ITfThreadMgr
> threadMgr
= sThreadMgr
;
5819 RefPtr
<ITfDocumentMgr
> disabledDocumentMgr
= sDisabledDocumentMgr
;
5820 HRESULT hr
= threadMgr
->SetFocus(disabledDocumentMgr
);
5821 if (NS_WARN_IF(FAILED(hr
))) {
5822 MOZ_LOG(gIMELog
, LogLevel::Error
,
5823 (" TSFTextStore::OnFocusChange() FAILED due to "
5824 "ITfThreadMgr::SetFocus() failure"));
5825 return NS_ERROR_FAILURE
;
5830 // If an editor is getting focus, create new TextStore and set focus.
5831 if (NS_WARN_IF(!CreateAndSetFocus(aFocusedWidget
, aContext
))) {
5832 MOZ_LOG(gIMELog
, LogLevel::Error
,
5833 (" TSFTextStore::OnFocusChange() FAILED due to "
5834 "ITfThreadMgr::CreateAndSetFocus() failure"));
5835 return NS_ERROR_FAILURE
;
5841 void TSFTextStore::EnsureToDestroyAndReleaseEnabledTextStoreIf(
5842 RefPtr
<TSFTextStore
>& aTextStore
) {
5843 aTextStore
->Destroy();
5844 if (sEnabledTextStore
== aTextStore
) {
5845 sEnabledTextStore
= nullptr;
5847 aTextStore
= nullptr;
5851 bool TSFTextStore::CreateAndSetFocus(nsWindow
* aFocusedWidget
,
5852 const InputContext
& aContext
) {
5853 // TSF might do something which causes that we need to access static methods
5854 // of TSFTextStore. At that time, sEnabledTextStore may be necessary.
5855 // So, we should set sEnabledTextStore directly.
5856 RefPtr
<TSFTextStore
> textStore
= new TSFTextStore();
5857 sEnabledTextStore
= textStore
;
5858 if (NS_WARN_IF(!textStore
->Init(aFocusedWidget
, aContext
))) {
5859 MOZ_LOG(gIMELog
, LogLevel::Error
,
5860 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5861 "TSFTextStore::Init() failure"));
5862 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore
);
5865 RefPtr
<ITfDocumentMgr
> newDocMgr
= textStore
->mDocumentMgr
;
5866 if (NS_WARN_IF(!newDocMgr
)) {
5867 MOZ_LOG(gIMELog
, LogLevel::Error
,
5868 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5869 "invalid TSFTextStore::mDocumentMgr"));
5870 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore
);
5873 if (aContext
.mIMEState
.mEnabled
== IMEEnabled::Password
) {
5874 MarkContextAsKeyboardDisabled(textStore
->mContext
);
5875 RefPtr
<ITfContext
> topContext
;
5876 newDocMgr
->GetTop(getter_AddRefs(topContext
));
5877 if (topContext
&& topContext
!= textStore
->mContext
) {
5878 MarkContextAsKeyboardDisabled(topContext
);
5883 RefPtr
<ITfThreadMgr
> threadMgr
= sThreadMgr
;
5884 hr
= threadMgr
->SetFocus(newDocMgr
);
5886 if (NS_WARN_IF(FAILED(hr
))) {
5887 MOZ_LOG(gIMELog
, LogLevel::Error
,
5888 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5889 "ITfTheadMgr::SetFocus() failure"));
5890 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore
);
5893 if (NS_WARN_IF(!sThreadMgr
)) {
5894 MOZ_LOG(gIMELog
, LogLevel::Error
,
5895 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5896 "sThreadMgr being destroyed during calling "
5897 "ITfTheadMgr::SetFocus()"));
5898 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore
);
5901 if (NS_WARN_IF(sEnabledTextStore
!= textStore
)) {
5902 MOZ_LOG(gIMELog
, LogLevel::Error
,
5903 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5904 "creating TextStore has lost focus during calling "
5905 "ITfThreadMgr::SetFocus()"));
5906 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore
);
5910 // Use AssociateFocus() for ensuring that any native focus event
5911 // never steal focus from our documentMgr.
5912 RefPtr
<ITfDocumentMgr
> prevFocusedDocumentMgr
;
5913 hr
= threadMgr
->AssociateFocus(aFocusedWidget
->GetWindowHandle(), newDocMgr
,
5914 getter_AddRefs(prevFocusedDocumentMgr
));
5915 if (NS_WARN_IF(FAILED(hr
))) {
5916 MOZ_LOG(gIMELog
, LogLevel::Error
,
5917 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5918 "ITfTheadMgr::AssociateFocus() failure"));
5919 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore
);
5922 if (NS_WARN_IF(!sThreadMgr
)) {
5923 MOZ_LOG(gIMELog
, LogLevel::Error
,
5924 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5925 "sThreadMgr being destroyed during calling "
5926 "ITfTheadMgr::AssociateFocus()"));
5927 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore
);
5930 if (NS_WARN_IF(sEnabledTextStore
!= textStore
)) {
5931 MOZ_LOG(gIMELog
, LogLevel::Error
,
5932 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5933 "creating TextStore has lost focus during calling "
5934 "ITfTheadMgr::AssociateFocus()"));
5935 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore
);
5939 if (textStore
->mSink
) {
5940 MOZ_LOG(gIMELog
, LogLevel::Info
,
5941 (" TSFTextStore::CreateAndSetFocus(), calling "
5942 "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE) for 0x%p...",
5944 RefPtr
<ITextStoreACPSink
> sink
= textStore
->mSink
;
5945 sink
->OnLayoutChange(TS_LC_CREATE
, TEXTSTORE_DEFAULT_VIEW
);
5946 if (NS_WARN_IF(sEnabledTextStore
!= textStore
)) {
5947 MOZ_LOG(gIMELog
, LogLevel::Error
,
5948 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5949 "creating TextStore has lost focus during calling "
5950 "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE)"));
5951 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore
);
5959 IMENotificationRequests
TSFTextStore::GetIMENotificationRequests() {
5960 if (!sEnabledTextStore
|| NS_WARN_IF(!sEnabledTextStore
->mDocumentMgr
)) {
5961 // If there is no active text store, we don't need any notifications
5962 // since there is no sink which needs notifications.
5963 return IMENotificationRequests();
5966 // Otherwise, requests all notifications since even if some of them may not
5967 // be required by the sink of active TIP, active TIP may be changed and
5968 // other TIPs may need all notifications.
5969 // Note that Windows temporarily steal focus from active window if the main
5970 // process which created the window becomes busy. In this case, we shouldn't
5971 // commit composition since user may want to continue to compose the
5972 // composition after becoming not busy. Therefore, we need notifications
5973 // even during deactive.
5974 // Be aware, we don't need to check actual focused text store. For example,
5975 // MS-IME for Japanese handles focus messages by themselves and sets focused
5976 // text store to nullptr when the process is being inactivated. However,
5977 // we still need to reuse sEnabledTextStore if the process is activated and
5978 // focused element isn't changed. Therefore, if sEnabledTextStore isn't
5979 // nullptr, we need to keep notifying the sink even when it is not focused
5980 // text store for the thread manager.
5981 return IMENotificationRequests(
5982 IMENotificationRequests::NOTIFY_TEXT_CHANGE
|
5983 IMENotificationRequests::NOTIFY_POSITION_CHANGE
|
5984 IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR
|
5985 IMENotificationRequests::NOTIFY_DURING_DEACTIVE
);
5988 nsresult
TSFTextStore::OnTextChangeInternal(
5989 const IMENotification
& aIMENotification
) {
5990 const TextChangeDataBase
& textChangeData
= aIMENotification
.mTextChangeData
;
5992 MOZ_LOG(gIMELog
, LogLevel::Debug
,
5993 ("0x%p TSFTextStore::OnTextChangeInternal(aIMENotification={ "
5994 "mMessage=0x%08X, mTextChangeData=%s }), "
5995 "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, "
5997 this, aIMENotification
.mMessage
,
5998 mozilla::ToString(textChangeData
).c_str(), GetBoolName(mDestroyed
),
5999 mSink
.get(), GetSinkMaskNameStr(mSinkMask
).get(),
6000 ToString(mComposition
).c_str()));
6003 // If this instance is already destroyed, we shouldn't notify TSF of any
6008 mDeferNotifyingTSFUntilNextUpdate
= false;
6010 // Different from selection change, we don't modify anything with text
6011 // change data. Therefore, if neither TSF not TIP wants text change
6012 // notifications, we don't need to store the changes.
6013 if (!mSink
|| !(mSinkMask
& TS_AS_TEXT_CHANGE
)) {
6017 // Merge any text change data even if it's caused by composition.
6018 mPendingTextChangeData
.MergeWith(textChangeData
);
6020 MaybeFlushPendingNotifications();
6025 void TSFTextStore::NotifyTSFOfTextChange() {
6026 MOZ_ASSERT(!mDestroyed
);
6027 MOZ_ASSERT(!IsReadLocked());
6028 MOZ_ASSERT(mComposition
.isNothing());
6029 MOZ_ASSERT(mPendingTextChangeData
.IsValid());
6031 // If the text changes are caused only by composition, we don't need to
6032 // notify TSF of the text changes.
6033 if (mPendingTextChangeData
.mCausedOnlyByComposition
) {
6034 mPendingTextChangeData
.Clear();
6038 // First, forget cached selection.
6039 mSelectionForTSF
.reset();
6041 // For making it safer, we should check if there is a valid sink to receive
6042 // text change notification.
6043 if (NS_WARN_IF(!mSink
) || NS_WARN_IF(!(mSinkMask
& TS_AS_TEXT_CHANGE
))) {
6044 MOZ_LOG(gIMELog
, LogLevel::Error
,
6045 ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
6046 "mSink is not ready to call ITextStoreACPSink::OnTextChange()...",
6048 mPendingTextChangeData
.Clear();
6052 if (NS_WARN_IF(!mPendingTextChangeData
.IsInInt32Range())) {
6053 MOZ_LOG(gIMELog
, LogLevel::Error
,
6054 ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
6055 "offset is too big for calling "
6056 "ITextStoreACPSink::OnTextChange()...",
6058 mPendingTextChangeData
.Clear();
6062 TS_TEXTCHANGE textChange
;
6063 textChange
.acpStart
= static_cast<LONG
>(mPendingTextChangeData
.mStartOffset
);
6064 textChange
.acpOldEnd
=
6065 static_cast<LONG
>(mPendingTextChangeData
.mRemovedEndOffset
);
6066 textChange
.acpNewEnd
=
6067 static_cast<LONG
>(mPendingTextChangeData
.mAddedEndOffset
);
6068 mPendingTextChangeData
.Clear();
6071 gIMELog
, LogLevel::Info
,
6072 ("0x%p TSFTextStore::NotifyTSFOfTextChange(), calling "
6073 "ITextStoreACPSink::OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
6074 "acpNewEnd=%ld })...",
6075 this, textChange
.acpStart
, textChange
.acpOldEnd
, textChange
.acpNewEnd
));
6076 RefPtr
<ITextStoreACPSink
> sink
= mSink
;
6077 sink
->OnTextChange(0, &textChange
);
6080 nsresult
TSFTextStore::OnSelectionChangeInternal(
6081 const IMENotification
& aIMENotification
) {
6082 const SelectionChangeDataBase
& selectionChangeData
=
6083 aIMENotification
.mSelectionChangeData
;
6084 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6085 ("0x%p TSFTextStore::OnSelectionChangeInternal("
6086 "aIMENotification={ mSelectionChangeData=%s }), mDestroyed=%s, "
6087 "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, "
6089 this, mozilla::ToString(selectionChangeData
).c_str(),
6090 GetBoolName(mDestroyed
), mSink
.get(),
6091 GetSinkMaskNameStr(mSinkMask
).get(),
6092 GetBoolName(mIsRecordingActionsWithoutLock
),
6093 ToString(mComposition
).c_str()));
6096 // If this instance is already destroyed, we shouldn't notify TSF of any
6101 mDeferNotifyingTSFUntilNextUpdate
= false;
6103 // Assign the new selection change data to the pending selection change data
6104 // because only the latest selection data is necessary.
6105 // Note that this is necessary to update mSelectionForTSF. Therefore, even if
6106 // neither TSF nor TIP wants selection change notifications, we need to
6107 // store the selection information.
6108 mPendingSelectionChangeData
= Some(selectionChangeData
);
6110 // Flush remaining pending notifications here if it's possible.
6111 MaybeFlushPendingNotifications();
6113 // If we're available, we should create native caret instead of IMEHandler
6114 // because we may have some cache to do it.
6115 // Note that if we have composition, we'll notified composition-updated
6116 // later so that we don't need to create native caret in such case.
6117 if (!IsHandlingCompositionInContent() &&
6118 IMEHandler::NeedsToCreateNativeCaret()) {
6119 CreateNativeCaret();
6125 void TSFTextStore::NotifyTSFOfSelectionChange() {
6126 MOZ_ASSERT(!mDestroyed
);
6127 MOZ_ASSERT(!IsReadLocked());
6128 MOZ_ASSERT(mComposition
.isNothing());
6129 MOZ_ASSERT(mPendingSelectionChangeData
.isSome());
6131 // If selection range isn't actually changed, we don't need to notify TSF
6132 // of this selection change.
6133 if (mSelectionForTSF
.isNothing()) {
6134 MOZ_DIAGNOSTIC_ASSERT(!mIsInitializingSelectionForTSF
,
6135 "While mSelectionForTSF is being initialized, this "
6136 "should not be called");
6137 mSelectionForTSF
.emplace(*mPendingSelectionChangeData
);
6138 } else if (!mSelectionForTSF
->SetSelection(*mPendingSelectionChangeData
)) {
6139 mPendingSelectionChangeData
.reset();
6140 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6141 ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), "
6142 "selection isn't actually changed.",
6147 mPendingSelectionChangeData
.reset();
6149 if (!mSink
|| !(mSinkMask
& TS_AS_SEL_CHANGE
)) {
6153 MOZ_LOG(gIMELog
, LogLevel::Info
,
6154 ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), calling "
6155 "ITextStoreACPSink::OnSelectionChange()...",
6157 RefPtr
<ITextStoreACPSink
> sink
= mSink
;
6158 sink
->OnSelectionChange();
6161 nsresult
TSFTextStore::OnLayoutChangeInternal() {
6163 // If this instance is already destroyed, we shouldn't notify TSF of any
6168 NS_ENSURE_TRUE(mContext
, NS_ERROR_FAILURE
);
6169 NS_ENSURE_TRUE(mSink
, NS_ERROR_FAILURE
);
6171 mDeferNotifyingTSFUntilNextUpdate
= false;
6173 nsresult rv
= NS_OK
;
6175 // We need to notify TSF of layout change even if the document is locked.
6176 // So, don't use MaybeFlushPendingNotifications() for flushing pending
6178 MOZ_LOG(gIMELog
, LogLevel::Info
,
6179 ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
6180 "NotifyTSFOfLayoutChange()...",
6182 if (NS_WARN_IF(!NotifyTSFOfLayoutChange())) {
6183 rv
= NS_ERROR_FAILURE
;
6186 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6187 ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
6188 "MaybeFlushPendingNotifications()...",
6190 MaybeFlushPendingNotifications();
6195 bool TSFTextStore::NotifyTSFOfLayoutChange() {
6196 MOZ_ASSERT(!mDestroyed
);
6198 // If we're waiting a query of layout information from TIP, it means that
6199 // we've returned TS_E_NOLAYOUT error.
6200 bool returnedNoLayoutError
= mHasReturnedNoLayoutError
|| mWaitingQueryLayout
;
6202 // If we returned TS_E_NOLAYOUT, TIP should query the computed layout again.
6203 mWaitingQueryLayout
= returnedNoLayoutError
;
6205 // For avoiding to call this method again at unlocking the document during
6206 // calls of OnLayoutChange(), reset mHasReturnedNoLayoutError.
6207 mHasReturnedNoLayoutError
= false;
6209 // Now, layout has been computed. We should notify mContentForTSF for
6210 // making GetTextExt() and GetACPFromPoint() not return TS_E_NOLAYOUT.
6211 if (mContentForTSF
.isSome()) {
6212 mContentForTSF
->OnLayoutChanged();
6215 if (IMEHandler::NeedsToCreateNativeCaret()) {
6216 // If we're available, we should create native caret instead of IMEHandler
6217 // because we may have some cache to do it.
6218 CreateNativeCaret();
6220 // Now, the caret position is different from ours. Destroy the native caret
6221 // if we've create it only for GetTextExt().
6222 IMEHandler::MaybeDestroyNativeCaret();
6225 // This method should return true if either way succeeds.
6229 MOZ_LOG(gIMELog
, LogLevel::Info
,
6230 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6231 "calling ITextStoreACPSink::OnLayoutChange()...",
6233 RefPtr
<ITextStoreACPSink
> sink
= mSink
;
6234 HRESULT hr
= sink
->OnLayoutChange(TS_LC_CHANGE
, TEXTSTORE_DEFAULT_VIEW
);
6235 MOZ_LOG(gIMELog
, LogLevel::Info
,
6236 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6237 "called ITextStoreACPSink::OnLayoutChange()",
6239 ret
= SUCCEEDED(hr
);
6242 // The layout change caused by composition string change should cause
6243 // calling ITfContextOwnerServices::OnLayoutChange() too.
6244 if (returnedNoLayoutError
&& mContext
) {
6245 RefPtr
<ITfContextOwnerServices
> service
;
6246 mContext
->QueryInterface(IID_ITfContextOwnerServices
,
6247 getter_AddRefs(service
));
6249 MOZ_LOG(gIMELog
, LogLevel::Info
,
6250 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6251 "calling ITfContextOwnerServices::OnLayoutChange()...",
6253 HRESULT hr
= service
->OnLayoutChange();
6254 ret
= ret
&& SUCCEEDED(hr
);
6255 MOZ_LOG(gIMELog
, LogLevel::Info
,
6256 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6257 "called ITfContextOwnerServices::OnLayoutChange()",
6262 if (!mWidget
|| mWidget
->Destroyed()) {
6263 MOZ_LOG(gIMELog
, LogLevel::Info
,
6264 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6265 "the widget is destroyed during calling OnLayoutChange()",
6271 MOZ_LOG(gIMELog
, LogLevel::Info
,
6272 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6273 "the TSFTextStore instance is destroyed during calling "
6279 // If we returned TS_E_NOLAYOUT again, we need another call of
6280 // OnLayoutChange() later. So, let's wait a query from TIP.
6281 if (mHasReturnedNoLayoutError
) {
6282 mWaitingQueryLayout
= true;
6285 if (!mWaitingQueryLayout
) {
6286 MOZ_LOG(gIMELog
, LogLevel::Info
,
6287 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6288 "succeeded notifying TIP of our layout change",
6293 // If we believe that TIP needs to retry to retrieve our layout information
6294 // later, we should call it with ::PostMessage() hack.
6295 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6296 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6297 "posing MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE for calling "
6298 "OnLayoutChange() again...",
6300 ::PostMessage(mWidget
->GetWindowHandle(), MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE
,
6301 reinterpret_cast<WPARAM
>(this), 0);
6306 void TSFTextStore::NotifyTSFOfLayoutChangeAgain() {
6307 // Don't notify TSF of layout change after destroyed.
6309 mWaitingQueryLayout
= false;
6313 // Before preforming this method, TIP has accessed our layout information by
6314 // itself. In such case, we don't need to call OnLayoutChange() anymore.
6315 if (!mWaitingQueryLayout
) {
6319 MOZ_LOG(gIMELog
, LogLevel::Info
,
6320 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6321 "calling NotifyTSFOfLayoutChange()...",
6323 NotifyTSFOfLayoutChange();
6325 // If TIP didn't retrieved our layout information during a call of
6326 // NotifyTSFOfLayoutChange(), it means that the TIP already gave up to
6327 // retry to retrieve layout information or doesn't necessary it anymore.
6328 // But don't forget that the call may have caused returning TS_E_NOLAYOUT
6329 // error again. In such case we still need to call OnLayoutChange() later.
6330 if (!mHasReturnedNoLayoutError
&& mWaitingQueryLayout
) {
6331 mWaitingQueryLayout
= false;
6332 MOZ_LOG(gIMELog
, LogLevel::Warning
,
6333 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6334 "called NotifyTSFOfLayoutChange() but TIP didn't retry to "
6335 "retrieve the layout information",
6338 MOZ_LOG(gIMELog
, LogLevel::Info
,
6339 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6340 "called NotifyTSFOfLayoutChange()",
6345 nsresult
TSFTextStore::OnUpdateCompositionInternal() {
6346 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6347 ("0x%p TSFTextStore::OnUpdateCompositionInternal(), "
6348 "mDestroyed=%s, mDeferNotifyingTSFUntilNextUpdate=%s",
6349 this, GetBoolName(mDestroyed
),
6350 GetBoolName(mDeferNotifyingTSFUntilNextUpdate
)));
6352 // There are nothing to do after destroyed.
6357 // Update cached data now because all pending events have been handled now.
6358 if (mContentForTSF
.isSome()) {
6359 mContentForTSF
->OnCompositionEventsHandled();
6362 // If composition is completely finished both in TSF/TIP and the focused
6363 // editor which may be in a remote process, we can clear the cache and don't
6364 // have it until starting next composition.
6365 if (mComposition
.isNothing() && !IsHandlingCompositionInContent()) {
6366 mDeferClearingContentForTSF
= false;
6368 mDeferNotifyingTSFUntilNextUpdate
= false;
6369 MaybeFlushPendingNotifications();
6371 // If we're available, we should create native caret instead of IMEHandler
6372 // because we may have some cache to do it.
6373 if (IMEHandler::NeedsToCreateNativeCaret()) {
6374 CreateNativeCaret();
6380 nsresult
TSFTextStore::OnMouseButtonEventInternal(
6381 const IMENotification
& aIMENotification
) {
6383 // If this instance is already destroyed, we shouldn't notify TSF of any
6388 if (mMouseTrackers
.IsEmpty()) {
6392 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6393 ("0x%p TSFTextStore::OnMouseButtonEventInternal("
6394 "aIMENotification={ mEventMessage=%s, mOffset=%u, mCursorPos=%s, "
6395 "mCharRect=%s, mButton=%s, mButtons=%s, mModifiers=%s })",
6396 this, ToChar(aIMENotification
.mMouseButtonEventData
.mEventMessage
),
6397 aIMENotification
.mMouseButtonEventData
.mOffset
,
6398 ToString(aIMENotification
.mMouseButtonEventData
.mCursorPos
).c_str(),
6399 ToString(aIMENotification
.mMouseButtonEventData
.mCharRect
).c_str(),
6400 GetMouseButtonName(aIMENotification
.mMouseButtonEventData
.mButton
),
6401 GetMouseButtonsName(aIMENotification
.mMouseButtonEventData
.mButtons
)
6403 GetModifiersName(aIMENotification
.mMouseButtonEventData
.mModifiers
)
6406 uint32_t offset
= aIMENotification
.mMouseButtonEventData
.mOffset
;
6407 if (offset
> static_cast<uint32_t>(LONG_MAX
)) {
6410 LayoutDeviceIntRect charRect
=
6411 aIMENotification
.mMouseButtonEventData
.mCharRect
;
6412 LayoutDeviceIntPoint cursorPos
=
6413 aIMENotification
.mMouseButtonEventData
.mCursorPos
;
6415 if (charRect
.Width() > 0) {
6416 int32_t cursorXInChar
= cursorPos
.x
- charRect
.X();
6417 quadrant
= cursorXInChar
* 4 / charRect
.Width();
6418 quadrant
= (quadrant
+ 2) % 4;
6420 ULONG edge
= quadrant
< 2 ? offset
+ 1 : offset
;
6421 DWORD buttonStatus
= 0;
6423 aIMENotification
.mMouseButtonEventData
.mEventMessage
== eMouseUp
;
6425 switch (aIMENotification
.mMouseButtonEventData
.mButton
) {
6426 case MouseButton::ePrimary
:
6427 buttonStatus
= MK_LBUTTON
;
6429 case MouseButton::eMiddle
:
6430 buttonStatus
= MK_MBUTTON
;
6432 case MouseButton::eSecondary
:
6433 buttonStatus
= MK_RBUTTON
;
6437 if (aIMENotification
.mMouseButtonEventData
.mModifiers
& MODIFIER_CONTROL
) {
6438 buttonStatus
|= MK_CONTROL
;
6440 if (aIMENotification
.mMouseButtonEventData
.mModifiers
& MODIFIER_SHIFT
) {
6441 buttonStatus
|= MK_SHIFT
;
6443 for (size_t i
= 0; i
< mMouseTrackers
.Length(); i
++) {
6444 MouseTracker
& tracker
= mMouseTrackers
[i
];
6445 if (!tracker
.IsUsing() || tracker
.Range().isNothing() ||
6446 !tracker
.Range()->IsOffsetInRange(offset
)) {
6449 if (tracker
.OnMouseButtonEvent(edge
- tracker
.Range()->StartOffset(),
6450 quadrant
, buttonStatus
)) {
6451 return NS_SUCCESS_EVENT_CONSUMED
;
6457 void TSFTextStore::CreateNativeCaret() {
6458 MOZ_ASSERT(!IMEHandler::IsA11yHandlingNativeCaret());
6460 IMEHandler::MaybeDestroyNativeCaret();
6462 // Don't create native caret after destroyed.
6467 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6468 ("0x%p TSFTextStore::CreateNativeCaret(), mComposition=%s", this,
6469 ToString(mComposition
).c_str()));
6471 Maybe
<Selection
>& selectionForTSF
= SelectionForTSF();
6472 if (MOZ_UNLIKELY(selectionForTSF
.isNothing())) {
6473 MOZ_LOG(gIMELog
, LogLevel::Error
,
6474 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6475 "SelectionForTSF() failure",
6479 if (!selectionForTSF
->HasRange() && mComposition
.isNothing()) {
6480 // If there is no selection range nor composition, then, we don't have a
6481 // good position to show windows of TIP...
6482 // XXX It seems that storing last caret rect and using it in this case might
6484 MOZ_LOG(gIMELog
, LogLevel::Warning
,
6485 ("0x%p TSFTextStore::CreateNativeCaret() couludn't create native "
6486 "caret due to no selection range",
6491 WidgetQueryContentEvent
queryCaretRectEvent(true, eQueryCaretRect
, mWidget
);
6492 mWidget
->InitEvent(queryCaretRectEvent
);
6494 WidgetQueryContentEvent::Options options
;
6495 // XXX If this is called without composition and the selection isn't
6496 // collapsed, is it OK?
6497 int64_t caretOffset
= selectionForTSF
->HasRange()
6498 ? selectionForTSF
->MaxOffset()
6499 : mComposition
->StartOffset();
6500 if (mComposition
.isSome()) {
6501 // If there is a composition, use the relative query for deciding caret
6502 // position because composition might be different place from that
6503 // TSFTextStore assumes.
6504 options
.mRelativeToInsertionPoint
= true;
6505 caretOffset
-= mComposition
->StartOffset();
6506 } else if (!CanAccessActualContentDirectly()) {
6507 // If TSF/TIP cannot access actual content directly, there may be pending
6508 // text and/or selection changes which have not been notified TSF yet.
6509 // Therefore, we should use the relative query from start of selection where
6510 // TSFTextStore assumes since TSF/TIP computes the offset from our cached
6512 options
.mRelativeToInsertionPoint
= true;
6513 caretOffset
-= selectionForTSF
->StartOffset();
6515 queryCaretRectEvent
.InitForQueryCaretRect(caretOffset
, options
);
6517 DispatchEvent(queryCaretRectEvent
);
6518 if (NS_WARN_IF(queryCaretRectEvent
.Failed())) {
6519 MOZ_LOG(gIMELog
, LogLevel::Error
,
6520 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6521 "eQueryCaretRect failure (offset=%lld)",
6522 this, caretOffset
));
6526 if (!IMEHandler::CreateNativeCaret(static_cast<nsWindow
*>(mWidget
.get()),
6527 queryCaretRectEvent
.mReply
->mRect
)) {
6528 MOZ_LOG(gIMELog
, LogLevel::Error
,
6529 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6530 "IMEHandler::CreateNativeCaret() failure",
6536 void TSFTextStore::CommitCompositionInternal(bool aDiscard
) {
6537 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6538 ("0x%p TSFTextStore::CommitCompositionInternal(aDiscard=%s), "
6539 "mSink=0x%p, mContext=0x%p, mComposition=%s",
6540 this, GetBoolName(aDiscard
), mSink
.get(), mContext
.get(),
6541 ToString(mComposition
).c_str()));
6543 // If the document is locked, TSF will fail to commit composition since
6544 // TSF needs another document lock. So, let's put off the request.
6545 // Note that TextComposition will commit composition in the focused editor
6546 // with the latest composition string for web apps and waits asynchronous
6547 // committing messages. Therefore, we can and need to perform this
6549 if (IsReadLocked()) {
6550 if (mDeferCommittingComposition
|| mDeferCancellingComposition
) {
6551 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6552 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6553 "does nothing because already called and waiting unlock...",
6558 mDeferCancellingComposition
= true;
6560 mDeferCommittingComposition
= true;
6562 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6563 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6564 "putting off to request to %s composition after unlocking the "
6566 this, aDiscard
? "cancel" : "commit"));
6570 if (mComposition
.isSome() && aDiscard
) {
6571 LONG endOffset
= mComposition
->EndOffset();
6572 mComposition
->SetData(EmptyString());
6573 // Note that don't notify TSF of text change after this is destroyed.
6574 if (mSink
&& !mDestroyed
) {
6575 TS_TEXTCHANGE textChange
;
6576 textChange
.acpStart
= mComposition
->StartOffset();
6577 textChange
.acpOldEnd
= endOffset
;
6578 textChange
.acpNewEnd
= mComposition
->StartOffset();
6579 MOZ_LOG(gIMELog
, LogLevel::Info
,
6580 ("0x%p TSFTextStore::CommitCompositionInternal(), calling"
6581 "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
6582 "acpNewEnd=%ld })...",
6583 this, textChange
.acpStart
, textChange
.acpOldEnd
,
6584 textChange
.acpNewEnd
));
6585 RefPtr
<ITextStoreACPSink
> sink
= mSink
;
6586 sink
->OnTextChange(0, &textChange
);
6589 // Terminate two contexts, the base context (mContext) and the top
6590 // if the top context is not the same as the base context
6591 RefPtr
<ITfContext
> context
= mContext
;
6594 RefPtr
<ITfContextOwnerCompositionServices
> services
;
6595 context
->QueryInterface(IID_ITfContextOwnerCompositionServices
,
6596 getter_AddRefs(services
));
6598 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6599 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6600 "requesting TerminateComposition() for the context 0x%p...",
6601 this, context
.get()));
6602 services
->TerminateComposition(nullptr);
6605 if (context
!= mContext
) break;
6606 if (mDocumentMgr
) mDocumentMgr
->GetTop(getter_AddRefs(context
));
6607 } while (context
!= mContext
);
6610 static bool GetCompartment(IUnknown
* pUnk
, const GUID
& aID
,
6611 ITfCompartment
** aCompartment
) {
6612 if (!pUnk
) return false;
6614 RefPtr
<ITfCompartmentMgr
> compMgr
;
6615 pUnk
->QueryInterface(IID_ITfCompartmentMgr
, getter_AddRefs(compMgr
));
6616 if (!compMgr
) return false;
6618 return SUCCEEDED(compMgr
->GetCompartment(aID
, aCompartment
)) &&
6619 (*aCompartment
) != nullptr;
6623 void TSFTextStore::SetIMEOpenState(bool aState
) {
6624 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6625 ("TSFTextStore::SetIMEOpenState(aState=%s)", GetBoolName(aState
)));
6631 RefPtr
<ITfCompartment
> comp
= GetCompartmentForOpenClose();
6632 if (NS_WARN_IF(!comp
)) {
6633 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6634 (" TSFTextStore::SetIMEOpenState() FAILED due to"
6635 "no compartment available"));
6641 variant
.lVal
= aState
;
6642 HRESULT hr
= comp
->SetValue(sClientId
, &variant
);
6643 if (NS_WARN_IF(FAILED(hr
))) {
6644 MOZ_LOG(gIMELog
, LogLevel::Error
,
6645 (" TSFTextStore::SetIMEOpenState() FAILED due to "
6646 "ITfCompartment::SetValue() failure, hr=0x%08lX",
6650 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6651 (" TSFTextStore::SetIMEOpenState(), setting "
6652 "0x%04lX to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...",
6657 bool TSFTextStore::GetIMEOpenState() {
6662 RefPtr
<ITfCompartment
> comp
= GetCompartmentForOpenClose();
6663 if (NS_WARN_IF(!comp
)) {
6668 ::VariantInit(&variant
);
6669 HRESULT hr
= comp
->GetValue(&variant
);
6670 if (NS_WARN_IF(FAILED(hr
))) {
6671 MOZ_LOG(gIMELog
, LogLevel::Error
,
6672 ("TSFTextStore::GetIMEOpenState() FAILED due to "
6673 "ITfCompartment::GetValue() failure, hr=0x%08lX",
6677 // Until IME is open in this process, the result may be empty.
6678 if (variant
.vt
== VT_EMPTY
) {
6681 if (NS_WARN_IF(variant
.vt
!= VT_I4
)) {
6682 MOZ_LOG(gIMELog
, LogLevel::Error
,
6683 ("TSFTextStore::GetIMEOpenState() FAILED due to "
6684 "invalid result of ITfCompartment::GetValue()"));
6685 ::VariantClear(&variant
);
6689 return variant
.lVal
!= 0;
6693 void TSFTextStore::SetInputContext(nsWindow
* aWidget
,
6694 const InputContext
& aContext
,
6695 const InputContextAction
& aAction
) {
6696 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6697 ("TSFTextStore::SetInputContext(aWidget=%p, "
6698 "aContext=%s, aAction.mFocusChange=%s), "
6699 "sEnabledTextStore(0x%p)={ mWidget=0x%p }, ThinksHavingFocus()=%s",
6700 aWidget
, mozilla::ToString(aContext
).c_str(),
6701 GetFocusChangeName(aAction
.mFocusChange
), sEnabledTextStore
.get(),
6702 sEnabledTextStore
? sEnabledTextStore
->mWidget
.get() : nullptr,
6703 GetBoolName(ThinksHavingFocus())));
6705 switch (aAction
.mFocusChange
) {
6706 case InputContextAction::WIDGET_CREATED
:
6707 // If this is called when the widget is created, there is nothing to do.
6709 case InputContextAction::FOCUS_NOT_CHANGED
:
6710 case InputContextAction::MENU_LOST_PSEUDO_FOCUS
:
6711 if (NS_WARN_IF(!IsInTSFMode())) {
6714 // In these cases, `NOTIFY_IME_OF_FOCUS` won't be sent. Therefore,
6715 // we need to reset text store for new state right now.
6718 NS_WARNING_ASSERTION(IsInTSFMode(),
6719 "Why is this called when TSF is disabled?");
6720 if (sEnabledTextStore
) {
6721 RefPtr
<TSFTextStore
> textStore(sEnabledTextStore
);
6722 textStore
->mInPrivateBrowsing
= aContext
.mInPrivateBrowsing
;
6723 textStore
->SetInputScope(aContext
.mHTMLInputType
,
6724 aContext
.mHTMLInputMode
);
6725 if (aContext
.mURI
) {
6727 if (NS_SUCCEEDED(aContext
.mURI
->GetSpec(spec
))) {
6728 CopyUTF8toUTF16(spec
, textStore
->mDocumentURL
);
6730 textStore
->mDocumentURL
.Truncate();
6733 textStore
->mDocumentURL
.Truncate();
6739 // If focus isn't actually changed but the enabled state is changed,
6740 // emulate the focus move.
6741 if (!ThinksHavingFocus() && aContext
.mIMEState
.IsEditable()) {
6742 if (!IMEHandler::GetFocusedWindow()) {
6743 MOZ_LOG(gIMELog
, LogLevel::Error
,
6744 (" TSFTextStore::SetInputContent() gets called to enable IME, "
6745 "but IMEHandler has not received focus notification"));
6747 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6748 (" TSFTextStore::SetInputContent() emulates focus for IME "
6750 OnFocusChange(true, aWidget
, aContext
);
6752 } else if (ThinksHavingFocus() && !aContext
.mIMEState
.IsEditable()) {
6753 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6754 (" TSFTextStore::SetInputContent() emulates blur for IME "
6756 OnFocusChange(false, aWidget
, aContext
);
6761 void TSFTextStore::MarkContextAsKeyboardDisabled(ITfContext
* aContext
) {
6762 VARIANT variant_int4_value1
;
6763 variant_int4_value1
.vt
= VT_I4
;
6764 variant_int4_value1
.lVal
= 1;
6766 RefPtr
<ITfCompartment
> comp
;
6767 if (!GetCompartment(aContext
, GUID_COMPARTMENT_KEYBOARD_DISABLED
,
6768 getter_AddRefs(comp
))) {
6769 MOZ_LOG(gIMELog
, LogLevel::Error
,
6770 ("TSFTextStore::MarkContextAsKeyboardDisabled() failed"
6776 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6777 ("TSFTextStore::MarkContextAsKeyboardDisabled(), setting "
6778 "to disable context 0x%p...",
6780 comp
->SetValue(sClientId
, &variant_int4_value1
);
6784 void TSFTextStore::MarkContextAsEmpty(ITfContext
* aContext
) {
6785 VARIANT variant_int4_value1
;
6786 variant_int4_value1
.vt
= VT_I4
;
6787 variant_int4_value1
.lVal
= 1;
6789 RefPtr
<ITfCompartment
> comp
;
6790 if (!GetCompartment(aContext
, GUID_COMPARTMENT_EMPTYCONTEXT
,
6791 getter_AddRefs(comp
))) {
6792 MOZ_LOG(gIMELog
, LogLevel::Error
,
6793 ("TSFTextStore::MarkContextAsEmpty() failed"
6799 MOZ_LOG(gIMELog
, LogLevel::Debug
,
6800 ("TSFTextStore::MarkContextAsEmpty(), setting "
6801 "to mark empty context 0x%p...",
6803 comp
->SetValue(sClientId
, &variant_int4_value1
);
6807 void TSFTextStore::Initialize() {
6808 MOZ_LOG(gIMELog
, LogLevel::Info
, ("TSFTextStore::Initialize() is called..."));
6811 MOZ_LOG(gIMELog
, LogLevel::Error
,
6812 (" TSFTextStore::Initialize() FAILED due to already initialized"));
6816 const bool enableTsf
= StaticPrefs::intl_tsf_enabled_AtStartup();
6817 MOZ_LOG(gIMELog
, LogLevel::Info
,
6818 (" TSFTextStore::Initialize(), TSF is %s",
6819 enableTsf
? "enabled" : "disabled"));
6824 RefPtr
<ITfThreadMgr
> threadMgr
;
6826 ::CoCreateInstance(CLSID_TF_ThreadMgr
, nullptr, CLSCTX_INPROC_SERVER
,
6827 IID_ITfThreadMgr
, getter_AddRefs(threadMgr
));
6828 if (FAILED(hr
) || !threadMgr
) {
6829 MOZ_LOG(gIMELog
, LogLevel::Error
,
6830 (" TSFTextStore::Initialize() FAILED to "
6831 "create the thread manager, hr=0x%08lX",
6836 hr
= threadMgr
->Activate(&sClientId
);
6839 gIMELog
, LogLevel::Error
,
6840 (" TSFTextStore::Initialize() FAILED to activate, hr=0x%08lX", hr
));
6844 RefPtr
<ITfDocumentMgr
> disabledDocumentMgr
;
6845 hr
= threadMgr
->CreateDocumentMgr(getter_AddRefs(disabledDocumentMgr
));
6846 if (FAILED(hr
) || !disabledDocumentMgr
) {
6847 MOZ_LOG(gIMELog
, LogLevel::Error
,
6848 (" TSFTextStore::Initialize() FAILED to create "
6849 "a document manager for disabled mode, hr=0x%08lX",
6854 RefPtr
<ITfContext
> disabledContext
;
6855 DWORD editCookie
= 0;
6856 hr
= disabledDocumentMgr
->CreateContext(
6857 sClientId
, 0, nullptr, getter_AddRefs(disabledContext
), &editCookie
);
6858 if (FAILED(hr
) || !disabledContext
) {
6859 MOZ_LOG(gIMELog
, LogLevel::Error
,
6860 (" TSFTextStore::Initialize() FAILED to create "
6861 "a context for disabled mode, hr=0x%08lX",
6866 MarkContextAsKeyboardDisabled(disabledContext
);
6867 MarkContextAsEmpty(disabledContext
);
6869 sThreadMgr
= threadMgr
;
6870 sDisabledDocumentMgr
= disabledDocumentMgr
;
6871 sDisabledContext
= disabledContext
;
6873 MOZ_LOG(gIMELog
, LogLevel::Info
,
6874 (" TSFTextStore::Initialize(), sThreadMgr=0x%p, "
6875 "sClientId=0x%08lX, sDisabledDocumentMgr=0x%p, sDisabledContext=%p",
6876 sThreadMgr
.get(), sClientId
, sDisabledDocumentMgr
.get(),
6877 sDisabledContext
.get()));
6881 already_AddRefed
<ITfThreadMgr
> TSFTextStore::GetThreadMgr() {
6882 RefPtr
<ITfThreadMgr
> threadMgr
= sThreadMgr
;
6883 return threadMgr
.forget();
6887 already_AddRefed
<ITfMessagePump
> TSFTextStore::GetMessagePump() {
6888 static bool sInitialized
= false;
6893 RefPtr
<ITfMessagePump
> messagePump
= sMessagePump
;
6894 return messagePump
.forget();
6896 // If it tried to retrieve ITfMessagePump from sThreadMgr but it failed,
6897 // we shouldn't retry it at every message due to performance reason.
6898 // Although this shouldn't occur actually.
6902 sInitialized
= true;
6904 RefPtr
<ITfMessagePump
> messagePump
;
6905 HRESULT hr
= sThreadMgr
->QueryInterface(IID_ITfMessagePump
,
6906 getter_AddRefs(messagePump
));
6907 if (NS_WARN_IF(FAILED(hr
)) || NS_WARN_IF(!messagePump
)) {
6908 MOZ_LOG(gIMELog
, LogLevel::Error
,
6909 ("TSFTextStore::GetMessagePump() FAILED to "
6910 "QI message pump from the thread manager, hr=0x%08lX",
6914 sMessagePump
= messagePump
;
6915 return messagePump
.forget();
6919 already_AddRefed
<ITfDisplayAttributeMgr
>
6920 TSFTextStore::GetDisplayAttributeMgr() {
6921 RefPtr
<ITfDisplayAttributeMgr
> displayAttributeMgr
;
6922 if (sDisplayAttrMgr
) {
6923 displayAttributeMgr
= sDisplayAttrMgr
;
6924 return displayAttributeMgr
.forget();
6927 HRESULT hr
= ::CoCreateInstance(
6928 CLSID_TF_DisplayAttributeMgr
, nullptr, CLSCTX_INPROC_SERVER
,
6929 IID_ITfDisplayAttributeMgr
, getter_AddRefs(displayAttributeMgr
));
6930 if (NS_WARN_IF(FAILED(hr
)) || NS_WARN_IF(!displayAttributeMgr
)) {
6931 MOZ_LOG(gIMELog
, LogLevel::Error
,
6932 ("TSFTextStore::GetDisplayAttributeMgr() FAILED to create "
6933 "a display attribute manager instance, hr=0x%08lX",
6937 sDisplayAttrMgr
= displayAttributeMgr
;
6938 return displayAttributeMgr
.forget();
6942 already_AddRefed
<ITfCategoryMgr
> TSFTextStore::GetCategoryMgr() {
6943 RefPtr
<ITfCategoryMgr
> categoryMgr
;
6945 categoryMgr
= sCategoryMgr
;
6946 return categoryMgr
.forget();
6949 ::CoCreateInstance(CLSID_TF_CategoryMgr
, nullptr, CLSCTX_INPROC_SERVER
,
6950 IID_ITfCategoryMgr
, getter_AddRefs(categoryMgr
));
6951 if (NS_WARN_IF(FAILED(hr
)) || NS_WARN_IF(!categoryMgr
)) {
6952 MOZ_LOG(gIMELog
, LogLevel::Error
,
6953 ("TSFTextStore::GetCategoryMgr() FAILED to create "
6954 "a category manager instance, hr=0x%08lX",
6958 sCategoryMgr
= categoryMgr
;
6959 return categoryMgr
.forget();
6963 already_AddRefed
<ITfCompartment
> TSFTextStore::GetCompartmentForOpenClose() {
6964 if (sCompartmentForOpenClose
) {
6965 RefPtr
<ITfCompartment
> compartment
= sCompartmentForOpenClose
;
6966 return compartment
.forget();
6973 RefPtr
<ITfCompartmentMgr
> compartmentMgr
;
6974 HRESULT hr
= sThreadMgr
->QueryInterface(IID_ITfCompartmentMgr
,
6975 getter_AddRefs(compartmentMgr
));
6976 if (NS_WARN_IF(FAILED(hr
)) || NS_WARN_IF(!compartmentMgr
)) {
6977 MOZ_LOG(gIMELog
, LogLevel::Error
,
6978 ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
6979 "sThreadMgr not having ITfCompartmentMgr, hr=0x%08lX",
6984 RefPtr
<ITfCompartment
> compartment
;
6985 hr
= compartmentMgr
->GetCompartment(GUID_COMPARTMENT_KEYBOARD_OPENCLOSE
,
6986 getter_AddRefs(compartment
));
6987 if (NS_WARN_IF(FAILED(hr
)) || NS_WARN_IF(!compartment
)) {
6988 MOZ_LOG(gIMELog
, LogLevel::Error
,
6989 ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
6990 "ITfCompartmentMgr::GetCompartment() failuere, hr=0x%08lX",
6995 sCompartmentForOpenClose
= compartment
;
6996 return compartment
.forget();
7000 already_AddRefed
<ITfInputProcessorProfiles
>
7001 TSFTextStore::GetInputProcessorProfiles() {
7002 RefPtr
<ITfInputProcessorProfiles
> inputProcessorProfiles
;
7003 if (sInputProcessorProfiles
) {
7004 inputProcessorProfiles
= sInputProcessorProfiles
;
7005 return inputProcessorProfiles
.forget();
7007 // XXX MSDN documents that ITfInputProcessorProfiles is available only on
7008 // desktop apps. However, there is no known way to obtain
7009 // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles
7011 HRESULT hr
= ::CoCreateInstance(
7012 CLSID_TF_InputProcessorProfiles
, nullptr, CLSCTX_INPROC_SERVER
,
7013 IID_ITfInputProcessorProfiles
, getter_AddRefs(inputProcessorProfiles
));
7014 if (NS_WARN_IF(FAILED(hr
)) || NS_WARN_IF(!inputProcessorProfiles
)) {
7015 MOZ_LOG(gIMELog
, LogLevel::Error
,
7016 ("TSFTextStore::GetInputProcessorProfiles() FAILED to create input "
7017 "processor profiles, hr=0x%08lX",
7021 sInputProcessorProfiles
= inputProcessorProfiles
;
7022 return inputProcessorProfiles
.forget();
7026 void TSFTextStore::Terminate() {
7027 MOZ_LOG(gIMELog
, LogLevel::Info
, ("TSFTextStore::Terminate()"));
7029 TSFStaticSink::Shutdown();
7031 sDisplayAttrMgr
= nullptr;
7032 sCategoryMgr
= nullptr;
7033 sEnabledTextStore
= nullptr;
7034 sDisabledDocumentMgr
= nullptr;
7035 sDisabledContext
= nullptr;
7036 sCompartmentForOpenClose
= nullptr;
7037 sInputProcessorProfiles
= nullptr;
7040 sThreadMgr
->Deactivate();
7041 sThreadMgr
= nullptr;
7042 sMessagePump
= nullptr;
7043 sKeystrokeMgr
= nullptr;
7048 bool TSFTextStore::ProcessRawKeyMessage(const MSG
& aMsg
) {
7050 return false; // not in TSF mode
7052 static bool sInitialized
= false;
7053 if (!sKeystrokeMgr
) {
7054 // If it tried to retrieve ITfKeystrokeMgr from sThreadMgr but it failed,
7055 // we shouldn't retry it at every keydown nor keyup due to performance
7056 // reason. Although this shouldn't occur actually.
7060 sInitialized
= true;
7061 RefPtr
<ITfKeystrokeMgr
> keystrokeMgr
;
7062 HRESULT hr
= sThreadMgr
->QueryInterface(IID_ITfKeystrokeMgr
,
7063 getter_AddRefs(keystrokeMgr
));
7064 if (NS_WARN_IF(FAILED(hr
)) || NS_WARN_IF(!keystrokeMgr
)) {
7065 MOZ_LOG(gIMELog
, LogLevel::Error
,
7066 ("TSFTextStore::ProcessRawKeyMessage() FAILED to "
7067 "QI keystroke manager from the thread manager, hr=0x%08lX",
7071 sKeystrokeMgr
= keystrokeMgr
.forget();
7074 if (aMsg
.message
== WM_KEYDOWN
) {
7075 RefPtr
<TSFTextStore
> textStore(sEnabledTextStore
);
7077 textStore
->OnStartToHandleKeyMessage();
7078 if (NS_WARN_IF(textStore
!= sEnabledTextStore
)) {
7079 // Let's handle the key message with new focused TSFTextStore.
7080 textStore
= sEnabledTextStore
;
7083 AutoRestore
<const MSG
*> savePreviousKeyMsg(sHandlingKeyMsg
);
7084 AutoRestore
<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched
);
7085 sHandlingKeyMsg
= &aMsg
;
7086 sIsKeyboardEventDispatched
= false;
7088 RefPtr
<ITfKeystrokeMgr
> keystrokeMgr
= sKeystrokeMgr
;
7089 HRESULT hr
= keystrokeMgr
->TestKeyDown(aMsg
.wParam
, aMsg
.lParam
, &eaten
);
7090 if (FAILED(hr
) || !sKeystrokeMgr
|| !eaten
) {
7093 hr
= keystrokeMgr
->KeyDown(aMsg
.wParam
, aMsg
.lParam
, &eaten
);
7095 textStore
->OnEndHandlingKeyMessage(!!eaten
);
7097 return SUCCEEDED(hr
) &&
7098 (eaten
|| !sKeystrokeMgr
|| sIsKeyboardEventDispatched
);
7100 if (aMsg
.message
== WM_KEYUP
) {
7101 RefPtr
<TSFTextStore
> textStore(sEnabledTextStore
);
7103 textStore
->OnStartToHandleKeyMessage();
7104 if (NS_WARN_IF(textStore
!= sEnabledTextStore
)) {
7105 // Let's handle the key message with new focused TSFTextStore.
7106 textStore
= sEnabledTextStore
;
7109 AutoRestore
<const MSG
*> savePreviousKeyMsg(sHandlingKeyMsg
);
7110 AutoRestore
<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched
);
7111 sHandlingKeyMsg
= &aMsg
;
7112 sIsKeyboardEventDispatched
= false;
7114 RefPtr
<ITfKeystrokeMgr
> keystrokeMgr
= sKeystrokeMgr
;
7115 HRESULT hr
= keystrokeMgr
->TestKeyUp(aMsg
.wParam
, aMsg
.lParam
, &eaten
);
7116 if (FAILED(hr
) || !sKeystrokeMgr
|| !eaten
) {
7119 hr
= keystrokeMgr
->KeyUp(aMsg
.wParam
, aMsg
.lParam
, &eaten
);
7121 textStore
->OnEndHandlingKeyMessage(!!eaten
);
7123 return SUCCEEDED(hr
) &&
7124 (eaten
|| !sKeystrokeMgr
|| sIsKeyboardEventDispatched
);
7130 void TSFTextStore::ProcessMessage(nsWindow
* aWindow
, UINT aMessage
,
7131 WPARAM
& aWParam
, LPARAM
& aLParam
,
7132 MSGResult
& aResult
) {
7134 case WM_IME_SETCONTEXT
:
7135 // If a windowless plugin had focus and IME was handled on it, composition
7136 // window was set the position. After that, even in TSF mode, WinXP keeps
7137 // to use composition window at the position if the active IME is not
7138 // aware TSF. For avoiding this issue, we need to hide the composition
7141 aLParam
&= ~ISC_SHOWUICOMPOSITIONWINDOW
;
7145 // When an modal dialog such as a file picker is open, composition
7146 // should be committed because IME might be used on it.
7147 if (!IsComposingOn(aWindow
)) {
7150 CommitComposition(false);
7152 case MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE
: {
7153 TSFTextStore
* maybeTextStore
= reinterpret_cast<TSFTextStore
*>(aWParam
);
7154 if (maybeTextStore
== sEnabledTextStore
) {
7155 RefPtr
<TSFTextStore
> textStore(maybeTextStore
);
7156 textStore
->NotifyTSFOfLayoutChangeAgain();
7164 bool TSFTextStore::IsIMM_IMEActive() {
7165 return TSFStaticSink::IsIMM_IMEActive();
7169 bool TSFTextStore::IsMSJapaneseIMEActive() {
7170 return TSFStaticSink::IsMSJapaneseIMEActive();
7174 bool TSFTextStore::IsGoogleJapaneseInputActive() {
7175 return TSFStaticSink::IsGoogleJapaneseInputActive();
7179 bool TSFTextStore::IsATOKActive() { return TSFStaticSink::IsATOKActive(); }
7181 /******************************************************************************
7182 * TSFTextStore::Content
7183 *****************************************************************************/
7185 const nsDependentSubstring
TSFTextStore::Content::GetSelectedText() const {
7186 if (NS_WARN_IF(mSelection
.isNothing())) {
7187 return nsDependentSubstring();
7189 return GetSubstring(static_cast<uint32_t>(mSelection
->StartOffset()),
7190 static_cast<uint32_t>(mSelection
->Length()));
7193 const nsDependentSubstring
TSFTextStore::Content::GetSubstring(
7194 uint32_t aStart
, uint32_t aLength
) const {
7195 return nsDependentSubstring(mText
, aStart
, aLength
);
7198 void TSFTextStore::Content::ReplaceSelectedTextWith(const nsAString
& aString
) {
7199 if (NS_WARN_IF(mSelection
.isNothing())) {
7202 ReplaceTextWith(mSelection
->StartOffset(), mSelection
->Length(), aString
);
7205 inline uint32_t FirstDifferentCharOffset(const nsAString
& aStr1
,
7206 const nsAString
& aStr2
) {
7207 MOZ_ASSERT(aStr1
!= aStr2
);
7209 uint32_t minLength
= std::min(aStr1
.Length(), aStr2
.Length());
7210 for (; i
< minLength
&& aStr1
[i
] == aStr2
[i
]; i
++) {
7216 void TSFTextStore::Content::ReplaceTextWith(LONG aStart
, LONG aLength
,
7217 const nsAString
& aReplaceString
) {
7218 MOZ_ASSERT(aStart
>= 0);
7219 MOZ_ASSERT(aLength
>= 0);
7220 const nsDependentSubstring replacedString
= GetSubstring(
7221 static_cast<uint32_t>(aStart
), static_cast<uint32_t>(aLength
));
7222 if (aReplaceString
!= replacedString
) {
7223 uint32_t firstDifferentOffset
= mMinModifiedOffset
.valueOr(UINT32_MAX
);
7224 if (mComposition
.isSome()) {
7225 // Emulate text insertion during compositions, because during a
7226 // composition, editor expects the whole composition string to
7227 // be sent in eCompositionChange, not just the inserted part.
7228 // The actual eCompositionChange will be sent in SetSelection
7229 // or OnUpdateComposition.
7230 MOZ_ASSERT(aStart
>= mComposition
->StartOffset());
7231 MOZ_ASSERT(aStart
+ aLength
<= mComposition
->EndOffset());
7232 mComposition
->ReplaceData(
7233 static_cast<uint32_t>(aStart
- mComposition
->StartOffset()),
7234 static_cast<uint32_t>(aLength
), aReplaceString
);
7235 // TIP may set composition string twice or more times during a document
7236 // lock. Therefore, we should compute the first difference offset with
7237 // mLastComposition.
7238 if (mLastComposition
.isNothing()) {
7239 firstDifferentOffset
= mComposition
->StartOffset();
7240 } else if (mComposition
->DataRef() != mLastComposition
->DataRef()) {
7241 firstDifferentOffset
=
7242 mComposition
->StartOffset() +
7243 FirstDifferentCharOffset(mComposition
->DataRef(),
7244 mLastComposition
->DataRef());
7245 // The previous change to the composition string is canceled.
7246 if (mMinModifiedOffset
.isSome() &&
7247 mMinModifiedOffset
.value() >=
7248 static_cast<uint32_t>(mComposition
->StartOffset()) &&
7249 mMinModifiedOffset
.value() < firstDifferentOffset
) {
7250 mMinModifiedOffset
= Some(firstDifferentOffset
);
7252 } else if (mMinModifiedOffset
.isSome() &&
7253 mMinModifiedOffset
.value() < static_cast<uint32_t>(LONG_MAX
) &&
7254 mComposition
->IsOffsetInRange(
7255 static_cast<long>(mMinModifiedOffset
.value()))) {
7256 // The previous change to the composition string is canceled.
7257 firstDifferentOffset
= mComposition
->EndOffset();
7258 mMinModifiedOffset
= Some(firstDifferentOffset
);
7260 mLatestCompositionRange
= Some(mComposition
->CreateStartAndEndOffsets());
7262 gIMELog
, LogLevel::Debug
,
7263 ("0x%p TSFTextStore::Content::ReplaceTextWith(aStart=%ld, "
7264 "aLength=%ld, aReplaceString=\"%s\"), mComposition=%s, "
7265 "mLastComposition=%s, mMinModifiedOffset=%s, "
7266 "firstDifferentOffset=%u",
7267 this, aStart
, aLength
, GetEscapedUTF8String(aReplaceString
).get(),
7268 ToString(mComposition
).c_str(), ToString(mLastComposition
).c_str(),
7269 ToString(mMinModifiedOffset
).c_str(), firstDifferentOffset
));
7271 firstDifferentOffset
=
7272 static_cast<uint32_t>(aStart
) +
7273 FirstDifferentCharOffset(aReplaceString
, replacedString
);
7275 mMinModifiedOffset
=
7276 mMinModifiedOffset
.isNothing()
7277 ? Some(firstDifferentOffset
)
7278 : Some(std::min(mMinModifiedOffset
.value(), firstDifferentOffset
));
7279 mText
.Replace(static_cast<uint32_t>(aStart
), static_cast<uint32_t>(aLength
),
7282 // Selection should be collapsed at the end of the inserted string.
7283 mSelection
= Some(TSFTextStore::Selection(static_cast<uint32_t>(aStart
) +
7284 aReplaceString
.Length()));
7287 void TSFTextStore::Content::StartComposition(
7288 ITfCompositionView
* aCompositionView
, const PendingAction
& aCompStart
,
7289 bool aPreserveSelection
) {
7290 MOZ_ASSERT(aCompositionView
);
7291 MOZ_ASSERT(mComposition
.isNothing());
7292 MOZ_ASSERT(aCompStart
.mType
== PendingAction::Type::eCompositionStart
);
7294 mComposition
.reset(); // Avoid new crash in the beta and nightly channels.
7295 mComposition
.emplace(
7296 aCompositionView
, aCompStart
.mSelectionStart
,
7297 GetSubstring(static_cast<uint32_t>(aCompStart
.mSelectionStart
),
7298 static_cast<uint32_t>(aCompStart
.mSelectionLength
)));
7299 mLatestCompositionRange
= Some(mComposition
->CreateStartAndEndOffsets());
7300 if (!aPreserveSelection
) {
7301 // XXX Do we need to set a new writing-mode here when setting a new
7302 // selection? Currently, we just preserve the existing value.
7303 WritingMode writingMode
=
7304 mSelection
.isNothing() ? WritingMode() : mSelection
->WritingModeRef();
7305 mSelection
= Some(TSFTextStore::Selection(mComposition
->StartOffset(),
7306 mComposition
->Length(), false,
7311 void TSFTextStore::Content::RestoreCommittedComposition(
7312 ITfCompositionView
* aCompositionView
,
7313 const PendingAction
& aCanceledCompositionEnd
) {
7314 MOZ_ASSERT(aCompositionView
);
7315 MOZ_ASSERT(mComposition
.isNothing());
7316 MOZ_ASSERT(aCanceledCompositionEnd
.mType
==
7317 PendingAction::Type::eCompositionEnd
);
7320 static_cast<uint32_t>(aCanceledCompositionEnd
.mSelectionStart
),
7321 static_cast<uint32_t>(aCanceledCompositionEnd
.mData
.Length())) ==
7322 aCanceledCompositionEnd
.mData
);
7324 // Restore the committed string as composing string.
7325 mComposition
.reset(); // Avoid new crash in the beta and nightly channels.
7326 mComposition
.emplace(aCompositionView
,
7327 aCanceledCompositionEnd
.mSelectionStart
,
7328 aCanceledCompositionEnd
.mData
);
7329 mLatestCompositionRange
= Some(mComposition
->CreateStartAndEndOffsets());
7332 void TSFTextStore::Content::EndComposition(const PendingAction
& aCompEnd
) {
7333 MOZ_ASSERT(mComposition
.isSome());
7334 MOZ_ASSERT(aCompEnd
.mType
== PendingAction::Type::eCompositionEnd
);
7336 if (mComposition
.isNothing()) {
7337 return; // Avoid new crash in the beta and nightly channels.
7340 mSelection
= Some(TSFTextStore::Selection(mComposition
->StartOffset() +
7341 aCompEnd
.mData
.Length()));
7342 mComposition
.reset();
7345 /******************************************************************************
7346 * TSFTextStore::MouseTracker
7347 *****************************************************************************/
7349 TSFTextStore::MouseTracker::MouseTracker() : mCookie(kInvalidCookie
) {}
7352 TSFTextStore::MouseTracker::Init(TSFTextStore
* aTextStore
) {
7353 MOZ_LOG(gIMELog
, LogLevel::Debug
,
7354 ("0x%p TSFTextStore::MouseTracker::Init(aTextStore=0x%p), "
7355 "aTextStore->mMouseTrackers.Length()=%zu",
7356 this, aTextStore
, aTextStore
->mMouseTrackers
.Length()));
7358 if (&aTextStore
->mMouseTrackers
.LastElement() != this) {
7359 MOZ_LOG(gIMELog
, LogLevel::Error
,
7360 ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
7361 "this is not the last element of mMouseTrackers",
7365 if (aTextStore
->mMouseTrackers
.Length() > kInvalidCookie
) {
7366 MOZ_LOG(gIMELog
, LogLevel::Error
,
7367 ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
7368 "no new cookie available",
7372 MOZ_ASSERT(!aTextStore
->mMouseTrackers
.IsEmpty(),
7373 "This instance must be in TSFTextStore::mMouseTrackers");
7374 mCookie
= static_cast<DWORD
>(aTextStore
->mMouseTrackers
.Length() - 1);
7379 TSFTextStore::MouseTracker::AdviseSink(TSFTextStore
* aTextStore
,
7380 ITfRangeACP
* aTextRange
,
7381 ITfMouseSink
* aMouseSink
) {
7382 MOZ_LOG(gIMELog
, LogLevel::Debug
,
7383 ("0x%p TSFTextStore::MouseTracker::AdviseSink(aTextStore=0x%p, "
7384 "aTextRange=0x%p, aMouseSink=0x%p), mCookie=%ld, mSink=0x%p",
7385 this, aTextStore
, aTextRange
, aMouseSink
, mCookie
, mSink
.get()));
7386 MOZ_ASSERT(mCookie
!= kInvalidCookie
, "This hasn't been initalized?");
7389 MOZ_LOG(gIMELog
, LogLevel::Error
,
7390 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7391 "due to already being used",
7396 MOZ_ASSERT(mRange
.isNothing());
7398 LONG start
= 0, length
= 0;
7399 HRESULT hr
= aTextRange
->GetExtent(&start
, &length
);
7401 MOZ_LOG(gIMELog
, LogLevel::Error
,
7402 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7403 "due to failure of ITfRangeACP::GetExtent()",
7408 if (start
< 0 || length
<= 0 || start
+ length
> LONG_MAX
) {
7409 MOZ_LOG(gIMELog
, LogLevel::Error
,
7410 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7411 "due to odd result of ITfRangeACP::GetExtent(), "
7412 "start=%ld, length=%ld",
7413 this, start
, length
));
7414 return E_INVALIDARG
;
7417 nsAutoString textContent
;
7418 if (NS_WARN_IF(!aTextStore
->GetCurrentText(textContent
))) {
7419 MOZ_LOG(gIMELog
, LogLevel::Error
,
7420 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7421 "due to failure of TSFTextStore::GetCurrentText()",
7426 if (textContent
.Length() <= static_cast<uint32_t>(start
) ||
7427 textContent
.Length() < static_cast<uint32_t>(start
+ length
)) {
7428 MOZ_LOG(gIMELog
, LogLevel::Error
,
7429 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7430 "due to out of range, start=%ld, length=%ld, "
7431 "textContent.Length()=%zu",
7432 this, start
, length
, textContent
.Length()));
7433 return E_INVALIDARG
;
7436 mRange
.emplace(start
, start
+ length
);
7440 MOZ_LOG(gIMELog
, LogLevel::Debug
,
7441 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink(), "
7442 "succeeded, mRange=%s, textContent.Length()=%zu",
7443 this, ToString(mRange
).c_str(), textContent
.Length()));
7447 void TSFTextStore::MouseTracker::UnadviseSink() {
7448 MOZ_LOG(gIMELog
, LogLevel::Debug
,
7449 ("0x%p TSFTextStore::MouseTracker::UnadviseSink(), "
7450 "mCookie=%ld, mSink=0x%p, mRange=%s",
7451 this, mCookie
, mSink
.get(), ToString(mRange
).c_str()));
7456 bool TSFTextStore::MouseTracker::OnMouseButtonEvent(ULONG aEdge
,
7458 DWORD aButtonStatus
) {
7459 MOZ_ASSERT(IsUsing(), "The caller must check before calling OnMouseEvent()");
7462 RefPtr
<ITfMouseSink
> sink
= mSink
;
7463 HRESULT hr
= sink
->OnMouseEvent(aEdge
, aQuadrant
, aButtonStatus
, &eaten
);
7465 MOZ_LOG(gIMELog
, LogLevel::Debug
,
7466 ("0x%p TSFTextStore::MouseTracker::OnMouseEvent(aEdge=%ld, "
7467 "aQuadrant=%ld, aButtonStatus=0x%08lX), hr=0x%08lX, eaten=%s",
7468 this, aEdge
, aQuadrant
, aButtonStatus
, hr
, GetBoolName(!!eaten
)));
7470 return SUCCEEDED(hr
) && eaten
;
7475 bool TSFTextStore::CurrentKeyboardLayoutHasIME() {
7476 RefPtr
<ITfInputProcessorProfiles
> inputProcessorProfiles
=
7477 TSFTextStore::GetInputProcessorProfiles();
7478 if (!inputProcessorProfiles
) {
7479 MOZ_LOG(gIMELog
, LogLevel::Error
,
7480 ("TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED due to "
7481 "there is no input processor profiles instance"));
7484 RefPtr
<ITfInputProcessorProfileMgr
> profileMgr
;
7485 HRESULT hr
= inputProcessorProfiles
->QueryInterface(
7486 IID_ITfInputProcessorProfileMgr
, getter_AddRefs(profileMgr
));
7487 if (FAILED(hr
) || !profileMgr
) {
7488 // On Windows Vista or later, ImmIsIME() API always returns true.
7489 // If we failed to obtain the profile manager, we cannot know if current
7490 // keyboard layout has IME.
7491 MOZ_LOG(gIMELog
, LogLevel::Error
,
7492 (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to query "
7493 "ITfInputProcessorProfileMgr"));
7497 TF_INPUTPROCESSORPROFILE profile
;
7498 hr
= profileMgr
->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD
, &profile
);
7499 if (hr
== S_FALSE
) {
7500 return false; // not found or not active
7503 MOZ_LOG(gIMELog
, LogLevel::Error
,
7504 (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to retreive "
7508 return (profile
.dwProfileType
== TF_PROFILETYPE_INPUTPROCESSOR
);
7510 #endif // #ifdef DEBUG
7512 } // namespace widget
7513 } // namespace mozilla