Bug 1614864 [wpt PR 21746] - [webnfc] Add WPT tests for smart poster, a=testonly
[gecko.git] / widget / windows / TSFTextStore.cpp
blob3304124e00296b01301f99eb3393c7fbb45c3a52
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #define INPUTSCOPE_INIT_GUID
7 #define TEXTATTRS_INIT_GUID
8 #include "TSFTextStore.h"
10 #include <olectl.h>
11 #include <algorithm>
12 #include "nscore.h"
14 #include "IMMHandler.h"
15 #include "KeyboardLayout.h"
16 #include "WinIMEHandler.h"
17 #include "WinUtils.h"
18 #include "mozilla/AutoRestore.h"
19 #include "mozilla/Logging.h"
20 #include "mozilla/Preferences.h"
21 #include "mozilla/Telemetry.h"
22 #include "mozilla/TextEventDispatcher.h"
23 #include "mozilla/TextEvents.h"
24 #include "mozilla/WindowsVersion.h"
25 #include "nsWindow.h"
26 #include "nsPrintfCString.h"
28 namespace mozilla {
29 namespace widget {
31 static const char* kPrefNameEnableTSF = "intl.tsf.enable";
33 /**
34 * TSF related code should log its behavior even on release build especially
35 * in the interface methods.
37 * In interface methods, use LogLevel::Info.
38 * In internal methods, use LogLevel::Debug for logging normal behavior.
39 * For logging error, use LogLevel::Error.
41 * When an instance method is called, start with following text:
42 * "0x%p TSFFoo::Bar(", the 0x%p should be the "this" of the nsFoo.
43 * after that, start with:
44 * "0x%p TSFFoo::Bar("
45 * In an internal method, start with following text:
46 * "0x%p TSFFoo::Bar("
47 * When a static method is called, start with following text:
48 * "TSFFoo::Bar("
51 LazyLogModule sTextStoreLog("nsTextStoreWidgets");
53 enum class TextInputProcessorID {
54 // Internal use only. This won't be returned by TSFStaticSink::ActiveTIP().
55 eNotComputed,
57 // Not a TIP. E.g., simple keyboard layout or IMM-IME.
58 eNone,
60 // Used for other TIPs, i.e., any TIPs which we don't support specifically.
61 eUnknown,
63 // TIP for Japanese.
64 eMicrosoftIMEForJapanese,
65 eMicrosoftOfficeIME2010ForJapanese,
66 eGoogleJapaneseInput,
67 eATOK2011,
68 eATOK2012,
69 eATOK2013,
70 eATOK2014,
71 eATOK2015,
72 eATOK2016,
73 eATOKUnknown,
74 eJapanist10,
76 // TIP for Traditional Chinese.
77 eMicrosoftBopomofo,
78 eMicrosoftChangJie,
79 eMicrosoftPhonetic,
80 eMicrosoftQuick,
81 eMicrosoftNewChangJie,
82 eMicrosoftNewPhonetic,
83 eMicrosoftNewQuick,
84 eFreeChangJie,
86 // TIP for Simplified Chinese.
87 eMicrosoftPinyin,
88 eMicrosoftPinyinNewExperienceInputStyle,
89 eMicrosoftWubi,
91 // TIP for Korean.
92 eMicrosoftIMEForKorean,
93 eMicrosoftOldHangul,
96 static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
98 static void HandleSeparator(nsCString& aDesc) {
99 if (!aDesc.IsEmpty()) {
100 aDesc.AppendLiteral(" | ");
104 static const nsCString GetFindFlagName(DWORD aFindFlag) {
105 nsCString description;
106 if (!aFindFlag) {
107 description.AppendLiteral("no flags (0)");
108 return description;
110 if (aFindFlag & TS_ATTR_FIND_BACKWARDS) {
111 description.AppendLiteral("TS_ATTR_FIND_BACKWARDS");
113 if (aFindFlag & TS_ATTR_FIND_WANT_OFFSET) {
114 HandleSeparator(description);
115 description.AppendLiteral("TS_ATTR_FIND_WANT_OFFSET");
117 if (aFindFlag & TS_ATTR_FIND_UPDATESTART) {
118 HandleSeparator(description);
119 description.AppendLiteral("TS_ATTR_FIND_UPDATESTART");
121 if (aFindFlag & TS_ATTR_FIND_WANT_VALUE) {
122 HandleSeparator(description);
123 description.AppendLiteral("TS_ATTR_FIND_WANT_VALUE");
125 if (aFindFlag & TS_ATTR_FIND_WANT_END) {
126 HandleSeparator(description);
127 description.AppendLiteral("TS_ATTR_FIND_WANT_END");
129 if (aFindFlag & TS_ATTR_FIND_HIDDEN) {
130 HandleSeparator(description);
131 description.AppendLiteral("TS_ATTR_FIND_HIDDEN");
133 if (description.IsEmpty()) {
134 description.AppendLiteral("Unknown (");
135 description.AppendInt(static_cast<uint32_t>(aFindFlag));
136 description.Append(')');
138 return description;
141 class GetACPFromPointFlagName : public nsAutoCString {
142 public:
143 explicit GetACPFromPointFlagName(DWORD aFlags) {
144 if (!aFlags) {
145 AppendLiteral("no flags (0)");
146 return;
148 if (aFlags & GXFPF_ROUND_NEAREST) {
149 AppendLiteral("GXFPF_ROUND_NEAREST");
150 aFlags &= ~GXFPF_ROUND_NEAREST;
152 if (aFlags & GXFPF_NEAREST) {
153 HandleSeparator(*this);
154 AppendLiteral("GXFPF_NEAREST");
155 aFlags &= ~GXFPF_NEAREST;
157 if (aFlags) {
158 HandleSeparator(*this);
159 AppendLiteral("Unknown(");
160 AppendInt(static_cast<uint32_t>(aFlags));
161 Append(')');
164 virtual ~GetACPFromPointFlagName() {}
167 static const char* GetFocusChangeName(
168 InputContextAction::FocusChange aFocusChange) {
169 switch (aFocusChange) {
170 case InputContextAction::FOCUS_NOT_CHANGED:
171 return "FOCUS_NOT_CHANGED";
172 case InputContextAction::GOT_FOCUS:
173 return "GOT_FOCUS";
174 case InputContextAction::LOST_FOCUS:
175 return "LOST_FOCUS";
176 case InputContextAction::MENU_GOT_PSEUDO_FOCUS:
177 return "MENU_GOT_PSEUDO_FOCUS";
178 case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
179 return "MENU_LOST_PSEUDO_FOCUS";
180 case InputContextAction::WIDGET_CREATED:
181 return "WIDGET_CREATED";
182 default:
183 return "Unknown";
187 static nsCString GetCLSIDNameStr(REFCLSID aCLSID) {
188 LPOLESTR str = nullptr;
189 HRESULT hr = ::StringFromCLSID(aCLSID, &str);
190 if (FAILED(hr) || !str || !str[0]) {
191 return EmptyCString();
194 nsCString result;
195 result = NS_ConvertUTF16toUTF8(str);
196 ::CoTaskMemFree(str);
197 return result;
200 static nsCString GetGUIDNameStr(REFGUID aGUID) {
201 OLECHAR str[40];
202 int len = ::StringFromGUID2(aGUID, str, ArrayLength(str));
203 if (!len || !str[0]) {
204 return EmptyCString();
207 return NS_ConvertUTF16toUTF8(str);
210 static nsCString GetGUIDNameStrWithTable(REFGUID aGUID) {
211 #define RETURN_GUID_NAME(aNamedGUID) \
212 if (IsEqualGUID(aGUID, aNamedGUID)) { \
213 return NS_LITERAL_CSTRING(#aNamedGUID); \
216 RETURN_GUID_NAME(GUID_PROP_INPUTSCOPE)
217 RETURN_GUID_NAME(TSATTRID_OTHERS)
218 RETURN_GUID_NAME(TSATTRID_Font)
219 RETURN_GUID_NAME(TSATTRID_Font_FaceName)
220 RETURN_GUID_NAME(TSATTRID_Font_SizePts)
221 RETURN_GUID_NAME(TSATTRID_Font_Style)
222 RETURN_GUID_NAME(TSATTRID_Font_Style_Bold)
223 RETURN_GUID_NAME(TSATTRID_Font_Style_Italic)
224 RETURN_GUID_NAME(TSATTRID_Font_Style_SmallCaps)
225 RETURN_GUID_NAME(TSATTRID_Font_Style_Capitalize)
226 RETURN_GUID_NAME(TSATTRID_Font_Style_Uppercase)
227 RETURN_GUID_NAME(TSATTRID_Font_Style_Lowercase)
228 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation)
229 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_LasVegasLights)
230 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_BlinkingBackground)
231 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_SparkleText)
232 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingBlackAnts)
233 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingRedAnts)
234 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_Shimmer)
235 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeDown)
236 RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeRight)
237 RETURN_GUID_NAME(TSATTRID_Font_Style_Emboss)
238 RETURN_GUID_NAME(TSATTRID_Font_Style_Engrave)
239 RETURN_GUID_NAME(TSATTRID_Font_Style_Hidden)
240 RETURN_GUID_NAME(TSATTRID_Font_Style_Kerning)
241 RETURN_GUID_NAME(TSATTRID_Font_Style_Outlined)
242 RETURN_GUID_NAME(TSATTRID_Font_Style_Position)
243 RETURN_GUID_NAME(TSATTRID_Font_Style_Protected)
244 RETURN_GUID_NAME(TSATTRID_Font_Style_Shadow)
245 RETURN_GUID_NAME(TSATTRID_Font_Style_Spacing)
246 RETURN_GUID_NAME(TSATTRID_Font_Style_Weight)
247 RETURN_GUID_NAME(TSATTRID_Font_Style_Height)
248 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline)
249 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Single)
250 RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Double)
251 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough)
252 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Single)
253 RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Double)
254 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline)
255 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Single)
256 RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Double)
257 RETURN_GUID_NAME(TSATTRID_Font_Style_Blink)
258 RETURN_GUID_NAME(TSATTRID_Font_Style_Subscript)
259 RETURN_GUID_NAME(TSATTRID_Font_Style_Superscript)
260 RETURN_GUID_NAME(TSATTRID_Font_Style_Color)
261 RETURN_GUID_NAME(TSATTRID_Font_Style_BackgroundColor)
262 RETURN_GUID_NAME(TSATTRID_Text)
263 RETURN_GUID_NAME(TSATTRID_Text_VerticalWriting)
264 RETURN_GUID_NAME(TSATTRID_Text_RightToLeft)
265 RETURN_GUID_NAME(TSATTRID_Text_Orientation)
266 RETURN_GUID_NAME(TSATTRID_Text_Language)
267 RETURN_GUID_NAME(TSATTRID_Text_ReadOnly)
268 RETURN_GUID_NAME(TSATTRID_Text_EmbeddedObject)
269 RETURN_GUID_NAME(TSATTRID_Text_Alignment)
270 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Left)
271 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Right)
272 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Center)
273 RETURN_GUID_NAME(TSATTRID_Text_Alignment_Justify)
274 RETURN_GUID_NAME(TSATTRID_Text_Link)
275 RETURN_GUID_NAME(TSATTRID_Text_Hyphenation)
276 RETURN_GUID_NAME(TSATTRID_Text_Para)
277 RETURN_GUID_NAME(TSATTRID_Text_Para_FirstLineIndent)
278 RETURN_GUID_NAME(TSATTRID_Text_Para_LeftIndent)
279 RETURN_GUID_NAME(TSATTRID_Text_Para_RightIndent)
280 RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceAfter)
281 RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceBefore)
282 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing)
283 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Single)
284 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_OnePtFive)
285 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Double)
286 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_AtLeast)
287 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Exactly)
288 RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Multiple)
289 RETURN_GUID_NAME(TSATTRID_List)
290 RETURN_GUID_NAME(TSATTRID_List_LevelIndel)
291 RETURN_GUID_NAME(TSATTRID_List_Type)
292 RETURN_GUID_NAME(TSATTRID_List_Type_Bullet)
293 RETURN_GUID_NAME(TSATTRID_List_Type_Arabic)
294 RETURN_GUID_NAME(TSATTRID_List_Type_LowerLetter)
295 RETURN_GUID_NAME(TSATTRID_List_Type_UpperLetter)
296 RETURN_GUID_NAME(TSATTRID_List_Type_LowerRoman)
297 RETURN_GUID_NAME(TSATTRID_List_Type_UpperRoman)
298 RETURN_GUID_NAME(TSATTRID_App)
299 RETURN_GUID_NAME(TSATTRID_App_IncorrectSpelling)
300 RETURN_GUID_NAME(TSATTRID_App_IncorrectGrammar)
302 #undef RETURN_GUID_NAME
304 return GetGUIDNameStr(aGUID);
307 static nsCString GetRIIDNameStr(REFIID aRIID) {
308 LPOLESTR str = nullptr;
309 HRESULT hr = ::StringFromIID(aRIID, &str);
310 if (FAILED(hr) || !str || !str[0]) {
311 return EmptyCString();
314 nsAutoString key(L"Interface\\");
315 key += str;
317 nsCString result;
318 wchar_t buf[256];
319 if (WinUtils::GetRegistryKey(HKEY_CLASSES_ROOT, key.get(), nullptr, buf,
320 sizeof(buf))) {
321 result = NS_ConvertUTF16toUTF8(buf);
322 } else {
323 result = NS_ConvertUTF16toUTF8(str);
326 ::CoTaskMemFree(str);
327 return result;
330 static const char* GetCommonReturnValueName(HRESULT aResult) {
331 switch (aResult) {
332 case S_OK:
333 return "S_OK";
334 case E_ABORT:
335 return "E_ABORT";
336 case E_ACCESSDENIED:
337 return "E_ACCESSDENIED";
338 case E_FAIL:
339 return "E_FAIL";
340 case E_HANDLE:
341 return "E_HANDLE";
342 case E_INVALIDARG:
343 return "E_INVALIDARG";
344 case E_NOINTERFACE:
345 return "E_NOINTERFACE";
346 case E_NOTIMPL:
347 return "E_NOTIMPL";
348 case E_OUTOFMEMORY:
349 return "E_OUTOFMEMORY";
350 case E_POINTER:
351 return "E_POINTER";
352 case E_UNEXPECTED:
353 return "E_UNEXPECTED";
354 default:
355 return SUCCEEDED(aResult) ? "Succeeded" : "Failed";
359 static const char* GetTextStoreReturnValueName(HRESULT aResult) {
360 switch (aResult) {
361 case TS_E_FORMAT:
362 return "TS_E_FORMAT";
363 case TS_E_INVALIDPOINT:
364 return "TS_E_INVALIDPOINT";
365 case TS_E_INVALIDPOS:
366 return "TS_E_INVALIDPOS";
367 case TS_E_NOINTERFACE:
368 return "TS_E_NOINTERFACE";
369 case TS_E_NOLAYOUT:
370 return "TS_E_NOLAYOUT";
371 case TS_E_NOLOCK:
372 return "TS_E_NOLOCK";
373 case TS_E_NOOBJECT:
374 return "TS_E_NOOBJECT";
375 case TS_E_NOSELECTION:
376 return "TS_E_NOSELECTION";
377 case TS_E_NOSERVICE:
378 return "TS_E_NOSERVICE";
379 case TS_E_READONLY:
380 return "TS_E_READONLY";
381 case TS_E_SYNCHRONOUS:
382 return "TS_E_SYNCHRONOUS";
383 case TS_S_ASYNC:
384 return "TS_S_ASYNC";
385 default:
386 return GetCommonReturnValueName(aResult);
390 static const nsCString GetSinkMaskNameStr(DWORD aSinkMask) {
391 nsCString description;
392 if (aSinkMask & TS_AS_TEXT_CHANGE) {
393 description.AppendLiteral("TS_AS_TEXT_CHANGE");
395 if (aSinkMask & TS_AS_SEL_CHANGE) {
396 HandleSeparator(description);
397 description.AppendLiteral("TS_AS_SEL_CHANGE");
399 if (aSinkMask & TS_AS_LAYOUT_CHANGE) {
400 HandleSeparator(description);
401 description.AppendLiteral("TS_AS_LAYOUT_CHANGE");
403 if (aSinkMask & TS_AS_ATTR_CHANGE) {
404 HandleSeparator(description);
405 description.AppendLiteral("TS_AS_ATTR_CHANGE");
407 if (aSinkMask & TS_AS_STATUS_CHANGE) {
408 HandleSeparator(description);
409 description.AppendLiteral("TS_AS_STATUS_CHANGE");
411 if (description.IsEmpty()) {
412 description.AppendLiteral("not-specified");
414 return description;
417 static const char* GetActiveSelEndName(TsActiveSelEnd aSelEnd) {
418 return aSelEnd == TS_AE_NONE
419 ? "TS_AE_NONE"
420 : aSelEnd == TS_AE_START
421 ? "TS_AE_START"
422 : aSelEnd == TS_AE_END ? "TS_AE_END" : "Unknown";
425 static const nsCString GetLockFlagNameStr(DWORD aLockFlags) {
426 nsCString description;
427 if ((aLockFlags & TS_LF_READWRITE) == TS_LF_READWRITE) {
428 description.AppendLiteral("TS_LF_READWRITE");
429 } else if (aLockFlags & TS_LF_READ) {
430 description.AppendLiteral("TS_LF_READ");
432 if (aLockFlags & TS_LF_SYNC) {
433 if (!description.IsEmpty()) {
434 description.AppendLiteral(" | ");
436 description.AppendLiteral("TS_LF_SYNC");
438 if (description.IsEmpty()) {
439 description.AppendLiteral("not-specified");
441 return description;
444 static const char* GetTextRunTypeName(TsRunType aRunType) {
445 switch (aRunType) {
446 case TS_RT_PLAIN:
447 return "TS_RT_PLAIN";
448 case TS_RT_HIDDEN:
449 return "TS_RT_HIDDEN";
450 case TS_RT_OPAQUE:
451 return "TS_RT_OPAQUE";
452 default:
453 return "Unknown";
457 static nsCString GetColorName(const TF_DA_COLOR& aColor) {
458 switch (aColor.type) {
459 case TF_CT_NONE:
460 return NS_LITERAL_CSTRING("TF_CT_NONE");
461 case TF_CT_SYSCOLOR:
462 return nsPrintfCString("TF_CT_SYSCOLOR, nIndex:0x%08X",
463 static_cast<int32_t>(aColor.nIndex));
464 case TF_CT_COLORREF:
465 return nsPrintfCString("TF_CT_COLORREF, cr:0x%08X",
466 static_cast<int32_t>(aColor.cr));
467 break;
468 default:
469 return nsPrintfCString("Unknown(%08X)",
470 static_cast<int32_t>(aColor.type));
474 static nsCString GetLineStyleName(TF_DA_LINESTYLE aLineStyle) {
475 switch (aLineStyle) {
476 case TF_LS_NONE:
477 return NS_LITERAL_CSTRING("TF_LS_NONE");
478 case TF_LS_SOLID:
479 return NS_LITERAL_CSTRING("TF_LS_SOLID");
480 case TF_LS_DOT:
481 return NS_LITERAL_CSTRING("TF_LS_DOT");
482 case TF_LS_DASH:
483 return NS_LITERAL_CSTRING("TF_LS_DASH");
484 case TF_LS_SQUIGGLE:
485 return NS_LITERAL_CSTRING("TF_LS_SQUIGGLE");
486 default: {
487 return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aLineStyle));
492 static nsCString GetClauseAttrName(TF_DA_ATTR_INFO aAttr) {
493 switch (aAttr) {
494 case TF_ATTR_INPUT:
495 return NS_LITERAL_CSTRING("TF_ATTR_INPUT");
496 case TF_ATTR_TARGET_CONVERTED:
497 return NS_LITERAL_CSTRING("TF_ATTR_TARGET_CONVERTED");
498 case TF_ATTR_CONVERTED:
499 return NS_LITERAL_CSTRING("TF_ATTR_CONVERTED");
500 case TF_ATTR_TARGET_NOTCONVERTED:
501 return NS_LITERAL_CSTRING("TF_ATTR_TARGET_NOTCONVERTED");
502 case TF_ATTR_INPUT_ERROR:
503 return NS_LITERAL_CSTRING("TF_ATTR_INPUT_ERROR");
504 case TF_ATTR_FIXEDCONVERTED:
505 return NS_LITERAL_CSTRING("TF_ATTR_FIXEDCONVERTED");
506 case TF_ATTR_OTHER:
507 return NS_LITERAL_CSTRING("TF_ATTR_OTHER");
508 default: {
509 return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aAttr));
514 static nsCString GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE& aDispAttr) {
515 nsCString str;
516 str = "crText:{ ";
517 str += GetColorName(aDispAttr.crText);
518 str += " }, crBk:{ ";
519 str += GetColorName(aDispAttr.crBk);
520 str += " }, lsStyle: ";
521 str += GetLineStyleName(aDispAttr.lsStyle);
522 str += ", fBoldLine: ";
523 str += GetBoolName(aDispAttr.fBoldLine);
524 str += ", crLine:{ ";
525 str += GetColorName(aDispAttr.crLine);
526 str += " }, bAttr: ";
527 str += GetClauseAttrName(aDispAttr.bAttr);
528 return str;
531 static const char* GetMouseButtonName(int16_t aButton) {
532 switch (aButton) {
533 case MouseButton::eLeft:
534 return "LeftButton";
535 case MouseButton::eMiddle:
536 return "MiddleButton";
537 case MouseButton::eRight:
538 return "RightButton";
539 default:
540 return "UnknownButton";
544 #define ADD_SEPARATOR_IF_NECESSARY(aStr) \
545 if (!aStr.IsEmpty()) { \
546 aStr.AppendLiteral(", "); \
549 static nsCString GetMouseButtonsName(int16_t aButtons) {
550 if (!aButtons) {
551 return NS_LITERAL_CSTRING("no buttons");
553 nsCString names;
554 if (aButtons & MouseButtonsFlag::eLeftFlag) {
555 names = "LeftButton";
557 if (aButtons & MouseButtonsFlag::eRightFlag) {
558 ADD_SEPARATOR_IF_NECESSARY(names);
559 names += "RightButton";
561 if (aButtons & MouseButtonsFlag::eMiddleFlag) {
562 ADD_SEPARATOR_IF_NECESSARY(names);
563 names += "MiddleButton";
565 if (aButtons & MouseButtonsFlag::e4thFlag) {
566 ADD_SEPARATOR_IF_NECESSARY(names);
567 names += "4thButton";
569 if (aButtons & MouseButtonsFlag::e5thFlag) {
570 ADD_SEPARATOR_IF_NECESSARY(names);
571 names += "5thButton";
573 return names;
576 static nsCString GetModifiersName(Modifiers aModifiers) {
577 if (aModifiers == MODIFIER_NONE) {
578 return NS_LITERAL_CSTRING("no modifiers");
580 nsCString names;
581 if (aModifiers & MODIFIER_ALT) {
582 names = NS_DOM_KEYNAME_ALT;
584 if (aModifiers & MODIFIER_ALTGRAPH) {
585 ADD_SEPARATOR_IF_NECESSARY(names);
586 names += NS_DOM_KEYNAME_ALTGRAPH;
588 if (aModifiers & MODIFIER_CAPSLOCK) {
589 ADD_SEPARATOR_IF_NECESSARY(names);
590 names += NS_DOM_KEYNAME_CAPSLOCK;
592 if (aModifiers & MODIFIER_CONTROL) {
593 ADD_SEPARATOR_IF_NECESSARY(names);
594 names += NS_DOM_KEYNAME_CONTROL;
596 if (aModifiers & MODIFIER_FN) {
597 ADD_SEPARATOR_IF_NECESSARY(names);
598 names += NS_DOM_KEYNAME_FN;
600 if (aModifiers & MODIFIER_FNLOCK) {
601 ADD_SEPARATOR_IF_NECESSARY(names);
602 names += NS_DOM_KEYNAME_FNLOCK;
604 if (aModifiers & MODIFIER_META) {
605 ADD_SEPARATOR_IF_NECESSARY(names);
606 names += NS_DOM_KEYNAME_META;
608 if (aModifiers & MODIFIER_NUMLOCK) {
609 ADD_SEPARATOR_IF_NECESSARY(names);
610 names += NS_DOM_KEYNAME_NUMLOCK;
612 if (aModifiers & MODIFIER_SCROLLLOCK) {
613 ADD_SEPARATOR_IF_NECESSARY(names);
614 names += NS_DOM_KEYNAME_SCROLLLOCK;
616 if (aModifiers & MODIFIER_SHIFT) {
617 ADD_SEPARATOR_IF_NECESSARY(names);
618 names += NS_DOM_KEYNAME_SHIFT;
620 if (aModifiers & MODIFIER_SYMBOL) {
621 ADD_SEPARATOR_IF_NECESSARY(names);
622 names += NS_DOM_KEYNAME_SYMBOL;
624 if (aModifiers & MODIFIER_SYMBOLLOCK) {
625 ADD_SEPARATOR_IF_NECESSARY(names);
626 names += NS_DOM_KEYNAME_SYMBOLLOCK;
628 if (aModifiers & MODIFIER_OS) {
629 ADD_SEPARATOR_IF_NECESSARY(names);
630 names += NS_DOM_KEYNAME_OS;
632 return names;
635 class GetWritingModeName : public nsAutoCString {
636 public:
637 explicit GetWritingModeName(const WritingMode& aWritingMode) {
638 if (!aWritingMode.IsVertical()) {
639 AssignLiteral("Horizontal");
640 return;
642 if (aWritingMode.IsVerticalLR()) {
643 AssignLiteral("Vertical (LR)");
644 return;
646 AssignLiteral("Vertical (RL)");
648 virtual ~GetWritingModeName() {}
651 class GetEscapedUTF8String final : public NS_ConvertUTF16toUTF8 {
652 public:
653 explicit GetEscapedUTF8String(const nsAString& aString)
654 : NS_ConvertUTF16toUTF8(aString) {
655 Escape();
657 explicit GetEscapedUTF8String(const char16ptr_t aString)
658 : NS_ConvertUTF16toUTF8(aString) {
659 Escape();
661 GetEscapedUTF8String(const char16ptr_t aString, uint32_t aLength)
662 : NS_ConvertUTF16toUTF8(aString, aLength) {
663 Escape();
666 private:
667 void Escape() {
668 ReplaceSubstring("\r", "\\r");
669 ReplaceSubstring("\n", "\\n");
670 ReplaceSubstring("\t", "\\t");
674 class GetIMEStateString : public nsAutoCString {
675 public:
676 explicit GetIMEStateString(const IMEState& aIMEState) {
677 AppendLiteral("{ mEnabled=");
678 switch (aIMEState.mEnabled) {
679 case IMEState::DISABLED:
680 AppendLiteral("DISABLED");
681 break;
682 case IMEState::ENABLED:
683 AppendLiteral("ENABLED");
684 break;
685 case IMEState::PASSWORD:
686 AppendLiteral("PASSWORD");
687 break;
688 case IMEState::PLUGIN:
689 AppendLiteral("PLUGIN");
690 break;
691 case IMEState::UNKNOWN:
692 AppendLiteral("UNKNOWN");
693 break;
694 default:
695 AppendPrintf("Unknown value (%d)", aIMEState.mEnabled);
696 break;
698 AppendLiteral(", mOpen=");
699 switch (aIMEState.mOpen) {
700 case IMEState::OPEN_STATE_NOT_SUPPORTED:
701 AppendLiteral("OPEN_STATE_NOT_SUPPORTED or DONT_CHANGE_OPEN_STATE");
702 break;
703 case IMEState::OPEN:
704 AppendLiteral("OPEN");
705 break;
706 case IMEState::CLOSED:
707 AppendLiteral("CLOSED");
708 break;
709 default:
710 AppendPrintf("Unknown value (%d)", aIMEState.mOpen);
711 break;
713 AppendLiteral(" }");
717 class GetInputContextString : public nsAutoCString {
718 public:
719 explicit GetInputContextString(const InputContext& aInputContext) {
720 AppendPrintf("{ mIMEState=%s, ",
721 GetIMEStateString(aInputContext.mIMEState).get());
722 AppendLiteral("mOrigin=");
723 switch (aInputContext.mOrigin) {
724 case InputContext::ORIGIN_MAIN:
725 AppendLiteral("ORIGIN_MAIN");
726 break;
727 case InputContext::ORIGIN_CONTENT:
728 AppendLiteral("ORIGIN_CONTENT");
729 break;
730 default:
731 AppendPrintf("Unknown value (%d)", aInputContext.mOrigin);
732 break;
734 AppendPrintf(
735 ", mHTMLInputType=\"%s\", mHTMLInputInputmode=\"%s\", "
736 "mActionHint=\"%s\", mMayBeIMEUnaware=%s }",
737 NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputType).get(),
738 NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputInputmode).get(),
739 NS_ConvertUTF16toUTF8(aInputContext.mActionHint).get(),
740 GetBoolName(aInputContext.mMayBeIMEUnaware));
744 class GetInputScopeString : public nsAutoCString {
745 public:
746 explicit GetInputScopeString(const nsTArray<InputScope>& aList) {
747 for (InputScope inputScope : aList) {
748 if (!IsEmpty()) {
749 AppendLiteral(", ");
751 switch (inputScope) {
752 case IS_DEFAULT:
753 AppendLiteral("IS_DEFAULT");
754 break;
755 case IS_URL:
756 AppendLiteral("IS_URL");
757 break;
758 case IS_FILE_FULLFILEPATH:
759 AppendLiteral("IS_FILE_FULLFILEPATH");
760 break;
761 case IS_FILE_FILENAME:
762 AppendLiteral("IS_FILE_FILENAME");
763 break;
764 case IS_EMAIL_USERNAME:
765 AppendLiteral("IS_EMAIL_USERNAME");
766 break;
767 case IS_EMAIL_SMTPEMAILADDRESS:
768 AppendLiteral("IS_EMAIL_SMTPEMAILADDRESS");
769 break;
770 case IS_LOGINNAME:
771 AppendLiteral("IS_LOGINNAME");
772 break;
773 case IS_PERSONALNAME_FULLNAME:
774 AppendLiteral("IS_PERSONALNAME_FULLNAME");
775 break;
776 case IS_PERSONALNAME_PREFIX:
777 AppendLiteral("IS_PERSONALNAME_PREFIX");
778 break;
779 case IS_PERSONALNAME_GIVENNAME:
780 AppendLiteral("IS_PERSONALNAME_GIVENNAME");
781 break;
782 case IS_PERSONALNAME_MIDDLENAME:
783 AppendLiteral("IS_PERSONALNAME_MIDDLENAME");
784 break;
785 case IS_PERSONALNAME_SURNAME:
786 AppendLiteral("IS_PERSONALNAME_SURNAME");
787 break;
788 case IS_PERSONALNAME_SUFFIX:
789 AppendLiteral("IS_PERSONALNAME_SUFFIX");
790 break;
791 case IS_ADDRESS_FULLPOSTALADDRESS:
792 AppendLiteral("IS_ADDRESS_FULLPOSTALADDRESS");
793 break;
794 case IS_ADDRESS_POSTALCODE:
795 AppendLiteral("IS_ADDRESS_POSTALCODE");
796 break;
797 case IS_ADDRESS_STREET:
798 AppendLiteral("IS_ADDRESS_STREET");
799 break;
800 case IS_ADDRESS_STATEORPROVINCE:
801 AppendLiteral("IS_ADDRESS_STATEORPROVINCE");
802 break;
803 case IS_ADDRESS_CITY:
804 AppendLiteral("IS_ADDRESS_CITY");
805 break;
806 case IS_ADDRESS_COUNTRYNAME:
807 AppendLiteral("IS_ADDRESS_COUNTRYNAME");
808 break;
809 case IS_ADDRESS_COUNTRYSHORTNAME:
810 AppendLiteral("IS_ADDRESS_COUNTRYSHORTNAME");
811 break;
812 case IS_CURRENCY_AMOUNTANDSYMBOL:
813 AppendLiteral("IS_CURRENCY_AMOUNTANDSYMBOL");
814 break;
815 case IS_CURRENCY_AMOUNT:
816 AppendLiteral("IS_CURRENCY_AMOUNT");
817 break;
818 case IS_DATE_FULLDATE:
819 AppendLiteral("IS_DATE_FULLDATE");
820 break;
821 case IS_DATE_MONTH:
822 AppendLiteral("IS_DATE_MONTH");
823 break;
824 case IS_DATE_DAY:
825 AppendLiteral("IS_DATE_DAY");
826 break;
827 case IS_DATE_YEAR:
828 AppendLiteral("IS_DATE_YEAR");
829 break;
830 case IS_DATE_MONTHNAME:
831 AppendLiteral("IS_DATE_MONTHNAME");
832 break;
833 case IS_DATE_DAYNAME:
834 AppendLiteral("IS_DATE_DAYNAME");
835 break;
836 case IS_DIGITS:
837 AppendLiteral("IS_DIGITS");
838 break;
839 case IS_NUMBER:
840 AppendLiteral("IS_NUMBER");
841 break;
842 case IS_ONECHAR:
843 AppendLiteral("IS_ONECHAR");
844 break;
845 case IS_PASSWORD:
846 AppendLiteral("IS_PASSWORD");
847 break;
848 case IS_TELEPHONE_FULLTELEPHONENUMBER:
849 AppendLiteral("IS_TELEPHONE_FULLTELEPHONENUMBER");
850 break;
851 case IS_TELEPHONE_COUNTRYCODE:
852 AppendLiteral("IS_TELEPHONE_COUNTRYCODE");
853 break;
854 case IS_TELEPHONE_AREACODE:
855 AppendLiteral("IS_TELEPHONE_AREACODE");
856 break;
857 case IS_TELEPHONE_LOCALNUMBER:
858 AppendLiteral("IS_TELEPHONE_LOCALNUMBER");
859 break;
860 case IS_TIME_FULLTIME:
861 AppendLiteral("IS_TIME_FULLTIME");
862 break;
863 case IS_TIME_HOUR:
864 AppendLiteral("IS_TIME_HOUR");
865 break;
866 case IS_TIME_MINORSEC:
867 AppendLiteral("IS_TIME_MINORSEC");
868 break;
869 case IS_NUMBER_FULLWIDTH:
870 AppendLiteral("IS_NUMBER_FULLWIDTH");
871 break;
872 case IS_ALPHANUMERIC_HALFWIDTH:
873 AppendLiteral("IS_ALPHANUMERIC_HALFWIDTH");
874 break;
875 case IS_ALPHANUMERIC_FULLWIDTH:
876 AppendLiteral("IS_ALPHANUMERIC_FULLWIDTH");
877 break;
878 case IS_CURRENCY_CHINESE:
879 AppendLiteral("IS_CURRENCY_CHINESE");
880 break;
881 case IS_BOPOMOFO:
882 AppendLiteral("IS_BOPOMOFO");
883 break;
884 case IS_HIRAGANA:
885 AppendLiteral("IS_HIRAGANA");
886 break;
887 case IS_KATAKANA_HALFWIDTH:
888 AppendLiteral("IS_KATAKANA_HALFWIDTH");
889 break;
890 case IS_KATAKANA_FULLWIDTH:
891 AppendLiteral("IS_KATAKANA_FULLWIDTH");
892 break;
893 case IS_HANJA:
894 AppendLiteral("IS_HANJA");
895 break;
896 case IS_PHRASELIST:
897 AppendLiteral("IS_PHRASELIST");
898 break;
899 case IS_REGULAREXPRESSION:
900 AppendLiteral("IS_REGULAREXPRESSION");
901 break;
902 case IS_SRGS:
903 AppendLiteral("IS_SRGS");
904 break;
905 case IS_XML:
906 AppendLiteral("IS_XML");
907 break;
908 case IS_PRIVATE:
909 AppendLiteral("IS_PRIVATE");
910 break;
911 default:
912 AppendPrintf("Unknown Value(%d)", inputScope);
913 break;
919 /******************************************************************/
920 /* InputScopeImpl */
921 /******************************************************************/
923 class InputScopeImpl final : public ITfInputScope {
924 ~InputScopeImpl() {}
926 public:
927 explicit InputScopeImpl(const nsTArray<InputScope>& aList)
928 : mInputScopes(aList) {
929 MOZ_LOG(
930 sTextStoreLog, LogLevel::Info,
931 ("0x%p InputScopeImpl(%s)", this, GetInputScopeString(aList).get()));
934 NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(InputScopeImpl)
936 STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
937 *ppv = nullptr;
938 if ((IID_IUnknown == riid) || (IID_ITfInputScope == riid)) {
939 *ppv = static_cast<ITfInputScope*>(this);
941 if (*ppv) {
942 AddRef();
943 return S_OK;
945 return E_NOINTERFACE;
948 STDMETHODIMP GetInputScopes(InputScope** pprgInputScopes, UINT* pcCount) {
949 uint32_t count = (mInputScopes.IsEmpty() ? 1 : mInputScopes.Length());
951 InputScope* pScope =
952 (InputScope*)CoTaskMemAlloc(sizeof(InputScope) * count);
953 NS_ENSURE_TRUE(pScope, E_OUTOFMEMORY);
955 if (mInputScopes.IsEmpty()) {
956 *pScope = IS_DEFAULT;
957 *pcCount = 1;
958 *pprgInputScopes = pScope;
959 return S_OK;
962 *pcCount = 0;
964 for (uint32_t idx = 0; idx < count; idx++) {
965 *(pScope + idx) = mInputScopes[idx];
966 (*pcCount)++;
969 *pprgInputScopes = pScope;
970 return S_OK;
973 STDMETHODIMP GetPhrase(BSTR** ppbstrPhrases, UINT* pcCount) {
974 return E_NOTIMPL;
976 STDMETHODIMP GetRegularExpression(BSTR* pbstrRegExp) { return E_NOTIMPL; }
977 STDMETHODIMP GetSRGS(BSTR* pbstrSRGS) { return E_NOTIMPL; }
978 STDMETHODIMP GetXML(BSTR* pbstrXML) { return E_NOTIMPL; }
980 private:
981 nsTArray<InputScope> mInputScopes;
984 /******************************************************************/
985 /* TSFStaticSink */
986 /******************************************************************/
988 class TSFStaticSink final : public ITfInputProcessorProfileActivationSink {
989 public:
990 static TSFStaticSink* GetInstance() {
991 if (!sInstance) {
992 RefPtr<ITfThreadMgr> threadMgr = TSFTextStore::GetThreadMgr();
993 if (NS_WARN_IF(!threadMgr)) {
994 MOZ_LOG(
995 sTextStoreLog, LogLevel::Error,
996 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
997 "instance due to no ThreadMgr instance"));
998 return nullptr;
1000 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
1001 TSFTextStore::GetInputProcessorProfiles();
1002 if (NS_WARN_IF(!inputProcessorProfiles)) {
1003 MOZ_LOG(
1004 sTextStoreLog, LogLevel::Error,
1005 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
1006 "instance due to no InputProcessorProfiles instance"));
1007 return nullptr;
1009 RefPtr<TSFStaticSink> staticSink = new TSFStaticSink();
1010 if (NS_WARN_IF(!staticSink->Init(threadMgr, inputProcessorProfiles))) {
1011 staticSink->Destroy();
1012 MOZ_LOG(
1013 sTextStoreLog, LogLevel::Error,
1014 ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
1015 "instance"));
1016 return nullptr;
1018 sInstance = staticSink.forget();
1020 return sInstance;
1023 static void Shutdown() {
1024 if (sInstance) {
1025 sInstance->Destroy();
1026 sInstance = nullptr;
1030 bool Init(ITfThreadMgr* aThreadMgr,
1031 ITfInputProcessorProfiles* aInputProcessorProfiles);
1032 STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
1033 *ppv = nullptr;
1034 if (IID_IUnknown == riid ||
1035 IID_ITfInputProcessorProfileActivationSink == riid) {
1036 *ppv = static_cast<ITfInputProcessorProfileActivationSink*>(this);
1038 if (*ppv) {
1039 AddRef();
1040 return S_OK;
1042 return E_NOINTERFACE;
1045 NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFStaticSink)
1047 const nsString& GetActiveTIPKeyboardDescription() const {
1048 return mActiveTIPKeyboardDescription;
1051 static bool IsIMM_IMEActive() {
1052 // Use IMM API until TSFStaticSink starts to work.
1053 if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
1054 return IsIMM_IME(::GetKeyboardLayout(0));
1056 return sInstance->mIsIMM_IME;
1059 static bool IsIMM_IME(HKL aHKL) {
1060 return (::ImmGetIMEFileNameW(aHKL, nullptr, 0) > 0);
1063 static bool IsTraditionalChinese() {
1064 EnsureInstance();
1065 return sInstance && sInstance->IsTraditionalChineseInternal();
1067 static bool IsSimplifiedChinese() {
1068 EnsureInstance();
1069 return sInstance && sInstance->IsSimplifiedChineseInternal();
1071 static bool IsJapanese() {
1072 EnsureInstance();
1073 return sInstance && sInstance->IsJapaneseInternal();
1075 static bool IsKorean() {
1076 EnsureInstance();
1077 return sInstance && sInstance->IsKoreanInternal();
1081 * ActiveTIP() returns an ID for currently active TIP.
1082 * Please note that this method is expensive due to needs a lot of GUID
1083 * comparations if active language ID is one of CJKT. If you need to
1084 * check TIPs for a specific language, you should check current language
1085 * first.
1087 static TextInputProcessorID ActiveTIP() {
1088 EnsureInstance();
1089 if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
1090 return TextInputProcessorID::eUnknown;
1092 sInstance->ComputeActiveTextInputProcessor();
1093 if (NS_WARN_IF(sInstance->mActiveTIP ==
1094 TextInputProcessorID::eNotComputed)) {
1095 return TextInputProcessorID::eUnknown;
1097 return sInstance->mActiveTIP;
1100 static bool IsMSChangJieOrMSQuickActive() {
1101 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1102 // For avoiding unnecessary computation, we should check if the language
1103 // for current TIP is Traditional Chinese.
1104 if (!IsTraditionalChinese()) {
1105 return false;
1107 switch (ActiveTIP()) {
1108 case TextInputProcessorID::eMicrosoftChangJie:
1109 case TextInputProcessorID::eMicrosoftQuick:
1110 return true;
1111 default:
1112 return false;
1116 static bool IsMSPinyinOrMSWubiActive() {
1117 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1118 // For avoiding unnecessary computation, we should check if the language
1119 // for current TIP is Simplified Chinese.
1120 if (!IsSimplifiedChinese()) {
1121 return false;
1123 switch (ActiveTIP()) {
1124 case TextInputProcessorID::eMicrosoftPinyin:
1125 case TextInputProcessorID::eMicrosoftWubi:
1126 return true;
1127 default:
1128 return false;
1132 static bool IsMSJapaneseIMEActive() {
1133 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1134 // For avoiding unnecessary computation, we should check if the language
1135 // for current TIP is Japanese.
1136 if (!IsJapanese()) {
1137 return false;
1139 return ActiveTIP() == TextInputProcessorID::eMicrosoftIMEForJapanese;
1142 static bool IsGoogleJapaneseInputActive() {
1143 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1144 // For avoiding unnecessary computation, we should check if the language
1145 // for current TIP is Japanese.
1146 if (!IsJapanese()) {
1147 return false;
1149 return ActiveTIP() == TextInputProcessorID::eGoogleJapaneseInput;
1152 static bool IsATOKActive() {
1153 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1154 // For avoiding unnecessary computation, we should check if active TIP is
1155 // ATOK first since it's cheaper.
1156 return IsJapanese() && sInstance->IsATOKActiveInternal();
1159 // Note that ATOK 2011 - 2016 refers native caret position for deciding its
1160 // popup window position.
1161 static bool IsATOKReferringNativeCaretActive() {
1162 // ActiveTIP() is expensive if it hasn't computed active TIP yet.
1163 // For avoiding unnecessary computation, we should check if active TIP is
1164 // ATOK first since it's cheaper.
1165 if (!IsJapanese() || !sInstance->IsATOKActiveInternal()) {
1166 return false;
1168 switch (ActiveTIP()) {
1169 case TextInputProcessorID::eATOK2011:
1170 case TextInputProcessorID::eATOK2012:
1171 case TextInputProcessorID::eATOK2013:
1172 case TextInputProcessorID::eATOK2014:
1173 case TextInputProcessorID::eATOK2015:
1174 return true;
1175 default:
1176 return false;
1180 private:
1181 static void EnsureInstance() {
1182 if (!sInstance) {
1183 RefPtr<TSFStaticSink> staticSink = GetInstance();
1184 Unused << staticSink;
1188 bool IsTraditionalChineseInternal() const { return mLangID == 0x0404; }
1189 bool IsSimplifiedChineseInternal() const { return mLangID == 0x0804; }
1190 bool IsJapaneseInternal() const { return mLangID == 0x0411; }
1191 bool IsKoreanInternal() const { return mLangID == 0x0412; }
1193 bool IsATOKActiveInternal() {
1194 EnsureInitActiveTIPKeyboard();
1195 // FYI: Name of packaged ATOK includes the release year like "ATOK 2015".
1196 // Name of ATOK Passport (subscription) equals "ATOK".
1197 return StringBeginsWith(mActiveTIPKeyboardDescription,
1198 NS_LITERAL_STRING("ATOK ")) ||
1199 mActiveTIPKeyboardDescription.EqualsLiteral("ATOK");
1202 void ComputeActiveTextInputProcessor() {
1203 if (mActiveTIP != TextInputProcessorID::eNotComputed) {
1204 return;
1207 if (mActiveTIPGUID == GUID_NULL) {
1208 mActiveTIP = TextInputProcessorID::eNone;
1209 return;
1212 // Comparing GUID is slow. So, we should use language information to
1213 // reduce the comparing cost for TIP which is not we do not support
1214 // specifically since they are always compared with all supported TIPs.
1215 switch (mLangID) {
1216 case 0x0404:
1217 mActiveTIP = ComputeActiveTIPAsTraditionalChinese();
1218 break;
1219 case 0x0411:
1220 mActiveTIP = ComputeActiveTIPAsJapanese();
1221 break;
1222 case 0x0412:
1223 mActiveTIP = ComputeActiveTIPAsKorean();
1224 break;
1225 case 0x0804:
1226 mActiveTIP = ComputeActiveTIPAsSimplifiedChinese();
1227 break;
1228 default:
1229 mActiveTIP = TextInputProcessorID::eUnknown;
1233 TextInputProcessorID ComputeActiveTIPAsJapanese() {
1234 // {A76C93D9-5523-4E90-AAFA-4DB112F9AC76} (Win7, Win8.1, Win10)
1235 static const GUID kMicrosoftIMEForJapaneseGUID = {
1236 0xA76C93D9,
1237 0x5523,
1238 0x4E90,
1239 {0xAA, 0xFA, 0x4D, 0xB1, 0x12, 0xF9, 0xAC, 0x76}};
1240 if (mActiveTIPGUID == kMicrosoftIMEForJapaneseGUID) {
1241 return TextInputProcessorID::eMicrosoftIMEForJapanese;
1243 // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64}
1244 static const GUID kMicrosoftOfficeIME2010ForJapaneseGUID = {
1245 0x54EDCC94,
1246 0x1524,
1247 0x4BB1,
1248 {0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64}};
1249 if (mActiveTIPGUID == kMicrosoftOfficeIME2010ForJapaneseGUID) {
1250 return TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese;
1252 // {773EB24E-CA1D-4B1B-B420-FA985BB0B80D}
1253 static const GUID kGoogleJapaneseInputGUID = {
1254 0x773EB24E,
1255 0xCA1D,
1256 0x4B1B,
1257 {0xB4, 0x20, 0xFA, 0x98, 0x5B, 0xB0, 0xB8, 0x0D}};
1258 if (mActiveTIPGUID == kGoogleJapaneseInputGUID) {
1259 return TextInputProcessorID::eGoogleJapaneseInput;
1261 // {F9C24A5C-8A53-499D-9572-93B2FF582115}
1262 static const GUID kATOK2011GUID = {
1263 0xF9C24A5C,
1264 0x8A53,
1265 0x499D,
1266 {0x95, 0x72, 0x93, 0xB2, 0xFF, 0x58, 0x21, 0x15}};
1267 if (mActiveTIPGUID == kATOK2011GUID) {
1268 return TextInputProcessorID::eATOK2011;
1270 // {1DE01562-F445-401B-B6C3-E5B18DB79461}
1271 static const GUID kATOK2012GUID = {
1272 0x1DE01562,
1273 0xF445,
1274 0x401B,
1275 {0xB6, 0xC3, 0xE5, 0xB1, 0x8D, 0xB7, 0x94, 0x61}};
1276 if (mActiveTIPGUID == kATOK2012GUID) {
1277 return TextInputProcessorID::eATOK2012;
1279 // {3C4DB511-189A-4168-B6EA-BFD0B4C85615}
1280 static const GUID kATOK2013GUID = {
1281 0x3C4DB511,
1282 0x189A,
1283 0x4168,
1284 {0xB6, 0xEA, 0xBF, 0xD0, 0xB4, 0xC8, 0x56, 0x15}};
1285 if (mActiveTIPGUID == kATOK2013GUID) {
1286 return TextInputProcessorID::eATOK2013;
1288 // {4EF33B79-6AA9-4271-B4BF-9321C279381B}
1289 static const GUID kATOK2014GUID = {
1290 0x4EF33B79,
1291 0x6AA9,
1292 0x4271,
1293 {0xB4, 0xBF, 0x93, 0x21, 0xC2, 0x79, 0x38, 0x1B}};
1294 if (mActiveTIPGUID == kATOK2014GUID) {
1295 return TextInputProcessorID::eATOK2014;
1297 // {EAB4DC00-CE2E-483D-A86A-E6B99DA9599A}
1298 static const GUID kATOK2015GUID = {
1299 0xEAB4DC00,
1300 0xCE2E,
1301 0x483D,
1302 {0xA8, 0x6A, 0xE6, 0xB9, 0x9D, 0xA9, 0x59, 0x9A}};
1303 if (mActiveTIPGUID == kATOK2015GUID) {
1304 return TextInputProcessorID::eATOK2015;
1306 // {0B557B4C-5740-4110-A60A-1493FA10BF2B}
1307 static const GUID kATOK2016GUID = {
1308 0x0B557B4C,
1309 0x5740,
1310 0x4110,
1311 {0xA6, 0x0A, 0x14, 0x93, 0xFA, 0x10, 0xBF, 0x2B}};
1312 if (mActiveTIPGUID == kATOK2016GUID) {
1313 return TextInputProcessorID::eATOK2016;
1316 // * ATOK 2017
1317 // - {6DBFD8F5-701D-11E6-920F-782BCBA6348F}
1318 // * ATOK Passport (confirmed with version 31.1.2)
1319 // - {A38F2FD9-7199-45E1-841C-BE0313D8052F}
1321 if (IsATOKActiveInternal()) {
1322 return TextInputProcessorID::eATOKUnknown;
1325 // {E6D66705-1EDA-4373-8D01-1D0CB2D054C7}
1326 static const GUID kJapanist10GUID = {
1327 0xE6D66705,
1328 0x1EDA,
1329 0x4373,
1330 {0x8D, 0x01, 0x1D, 0x0C, 0xB2, 0xD0, 0x54, 0xC7}};
1331 if (mActiveTIPGUID == kJapanist10GUID) {
1332 return TextInputProcessorID::eJapanist10;
1335 return TextInputProcessorID::eUnknown;
1338 TextInputProcessorID ComputeActiveTIPAsTraditionalChinese() {
1339 // {B2F9C502-1742-11D4-9790-0080C882687E} (Win8.1, Win10)
1340 static const GUID kMicrosoftBopomofoGUID = {
1341 0xB2F9C502,
1342 0x1742,
1343 0x11D4,
1344 {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1345 if (mActiveTIPGUID == kMicrosoftBopomofoGUID) {
1346 return TextInputProcessorID::eMicrosoftBopomofo;
1348 // {4BDF9F03-C7D3-11D4-B2AB-0080C882687E} (Win7, Win8.1, Win10)
1349 static const GUID kMicrosoftChangJieGUID = {
1350 0x4BDF9F03,
1351 0xC7D3,
1352 0x11D4,
1353 {0xB2, 0xAB, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1354 if (mActiveTIPGUID == kMicrosoftChangJieGUID) {
1355 return TextInputProcessorID::eMicrosoftChangJie;
1357 // {761309DE-317A-11D4-9B5D-0080C882687E} (Win7)
1358 static const GUID kMicrosoftPhoneticGUID = {
1359 0x761309DE,
1360 0x317A,
1361 0x11D4,
1362 {0x9B, 0x5D, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1363 if (mActiveTIPGUID == kMicrosoftPhoneticGUID) {
1364 return TextInputProcessorID::eMicrosoftPhonetic;
1366 // {6024B45F-5C54-11D4-B921-0080C882687E} (Win7, Win8.1, Win10)
1367 static const GUID kMicrosoftQuickGUID = {
1368 0x6024B45F,
1369 0x5C54,
1370 0x11D4,
1371 {0xB9, 0x21, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1372 if (mActiveTIPGUID == kMicrosoftQuickGUID) {
1373 return TextInputProcessorID::eMicrosoftQuick;
1375 // {F3BA907A-6C7E-11D4-97FA-0080C882687E} (Win7)
1376 static const GUID kMicrosoftNewChangJieGUID = {
1377 0xF3BA907A,
1378 0x6C7E,
1379 0x11D4,
1380 {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1381 if (mActiveTIPGUID == kMicrosoftNewChangJieGUID) {
1382 return TextInputProcessorID::eMicrosoftNewChangJie;
1384 // {B2F9C502-1742-11D4-9790-0080C882687E} (Win7)
1385 static const GUID kMicrosoftNewPhoneticGUID = {
1386 0xB2F9C502,
1387 0x1742,
1388 0x11D4,
1389 {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1390 if (mActiveTIPGUID == kMicrosoftNewPhoneticGUID) {
1391 return TextInputProcessorID::eMicrosoftNewPhonetic;
1393 // {0B883BA0-C1C7-11D4-87F9-0080C882687E} (Win7)
1394 static const GUID kMicrosoftNewQuickGUID = {
1395 0x0B883BA0,
1396 0xC1C7,
1397 0x11D4,
1398 {0x87, 0xF9, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1399 if (mActiveTIPGUID == kMicrosoftNewQuickGUID) {
1400 return TextInputProcessorID::eMicrosoftNewQuick;
1403 // NOTE: There are some other Traditional Chinese TIPs installed in Windows:
1404 // * Chinese Traditional Array (version 6.0)
1405 // - {D38EFF65-AA46-4FD5-91A7-67845FB02F5B} (Win7, Win8.1)
1406 // * Chinese Traditional DaYi (version 6.0)
1407 // - {037B2C25-480C-4D7F-B027-D6CA6B69788A} (Win7, Win8.1)
1409 // {B58630B5-0ED3-4335-BBC9-E77BBCB43CAD}
1410 static const GUID kFreeChangJieGUID = {
1411 0xB58630B5,
1412 0x0ED3,
1413 0x4335,
1414 {0xBB, 0xC9, 0xE7, 0x7B, 0xBC, 0xB4, 0x3C, 0xAD}};
1415 if (mActiveTIPGUID == kFreeChangJieGUID) {
1416 return TextInputProcessorID::eFreeChangJie;
1419 return TextInputProcessorID::eUnknown;
1422 TextInputProcessorID ComputeActiveTIPAsSimplifiedChinese() {
1423 // FYI: This matches with neither "Microsoft Pinyin ABC Input Style" nor
1424 // "Microsoft Pinyin New Experience Input Style" on Win7.
1425 // {FA550B04-5AD7-411F-A5AC-CA038EC515D7} (Win8.1, Win10)
1426 static const GUID kMicrosoftPinyinGUID = {
1427 0xFA550B04,
1428 0x5AD7,
1429 0x411F,
1430 {0xA5, 0xAC, 0xCA, 0x03, 0x8E, 0xC5, 0x15, 0xD7}};
1431 if (mActiveTIPGUID == kMicrosoftPinyinGUID) {
1432 return TextInputProcessorID::eMicrosoftPinyin;
1435 // {F3BA9077-6C7E-11D4-97FA-0080C882687E} (Win7)
1436 static const GUID kMicrosoftPinyinNewExperienceInputStyleGUID = {
1437 0xF3BA9077,
1438 0x6C7E,
1439 0x11D4,
1440 {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
1441 if (mActiveTIPGUID == kMicrosoftPinyinNewExperienceInputStyleGUID) {
1442 return TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle;
1444 // {82590C13-F4DD-44F4-BA1D-8667246FDF8E} (Win8.1, Win10)
1445 static const GUID kMicrosoftWubiGUID = {
1446 0x82590C13,
1447 0xF4DD,
1448 0x44F4,
1449 {0xBA, 0x1D, 0x86, 0x67, 0x24, 0x6F, 0xDF, 0x8E}};
1450 if (mActiveTIPGUID == kMicrosoftWubiGUID) {
1451 return TextInputProcessorID::eMicrosoftWubi;
1453 // NOTE: There are some other Simplified Chinese TIPs installed in Windows:
1454 // * Chinese Simplified QuanPin (version 6.0)
1455 // - {54FC610E-6ABD-4685-9DDD-A130BDF1B170} (Win8.1)
1456 // * Chinese Simplified ZhengMa (version 6.0)
1457 // - {733B4D81-3BC3-4132-B91A-E9CDD5E2BFC9} (Win8.1)
1458 // * Chinese Simplified ShuangPin (version 6.0)
1459 // - {EF63706D-31C4-490E-9DBB-BD150ADC454B} (Win8.1)
1460 // * Microsoft Pinyin ABC Input Style
1461 // - {FCA121D2-8C6D-41FB-B2DE-A2AD110D4820} (Win7)
1462 return TextInputProcessorID::eUnknown;
1465 TextInputProcessorID ComputeActiveTIPAsKorean() {
1466 // {B5FE1F02-D5F2-4445-9C03-C568F23C99A1} (Win7, Win8.1, Win10)
1467 static const GUID kMicrosoftIMEForKoreanGUID = {
1468 0xB5FE1F02,
1469 0xD5F2,
1470 0x4445,
1471 {0x9C, 0x03, 0xC5, 0x68, 0xF2, 0x3C, 0x99, 0xA1}};
1472 if (mActiveTIPGUID == kMicrosoftIMEForKoreanGUID) {
1473 return TextInputProcessorID::eMicrosoftIMEForKorean;
1475 // {B60AF051-257A-46BC-B9D3-84DAD819BAFB} (Win8.1, Win10)
1476 static const GUID kMicrosoftOldHangulGUID = {
1477 0xB60AF051,
1478 0x257A,
1479 0x46BC,
1480 {0xB9, 0xD3, 0x84, 0xDA, 0xD8, 0x19, 0xBA, 0xFB}};
1481 if (mActiveTIPGUID == kMicrosoftOldHangulGUID) {
1482 return TextInputProcessorID::eMicrosoftOldHangul;
1485 // NOTE: There is the other Korean TIP installed in Windows:
1486 // * Microsoft IME 2010
1487 // - {48878C45-93F9-4aaf-A6A1-272CD863C4F5} (Win7)
1489 return TextInputProcessorID::eUnknown;
1492 public: // ITfInputProcessorProfileActivationSink
1493 STDMETHODIMP OnActivated(DWORD, LANGID, REFCLSID, REFGUID, REFGUID, HKL,
1494 DWORD);
1496 private:
1497 TSFStaticSink();
1498 virtual ~TSFStaticSink() {}
1500 bool EnsureInitActiveTIPKeyboard();
1502 void Destroy();
1504 void GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
1505 REFGUID aProfile, nsAString& aDescription);
1506 bool IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
1507 REFGUID aProfile);
1509 TextInputProcessorID mActiveTIP;
1511 // Cookie of installing ITfInputProcessorProfileActivationSink
1512 DWORD mIPProfileCookie;
1514 LANGID mLangID;
1516 // True if current IME is implemented with IMM.
1517 bool mIsIMM_IME;
1518 // True if OnActivated() is already called
1519 bool mOnActivatedCalled;
1521 RefPtr<ITfThreadMgr> mThreadMgr;
1522 RefPtr<ITfInputProcessorProfiles> mInputProcessorProfiles;
1524 // Active TIP keyboard's description. If active language profile isn't TIP,
1525 // i.e., IMM-IME or just a keyboard layout, this is empty.
1526 nsString mActiveTIPKeyboardDescription;
1528 // Active TIP's GUID
1529 GUID mActiveTIPGUID;
1531 static StaticRefPtr<TSFStaticSink> sInstance;
1534 StaticRefPtr<TSFStaticSink> TSFStaticSink::sInstance;
1536 TSFStaticSink::TSFStaticSink()
1537 : mActiveTIP(TextInputProcessorID::eNotComputed),
1538 mIPProfileCookie(TF_INVALID_COOKIE),
1539 mLangID(0),
1540 mIsIMM_IME(false),
1541 mOnActivatedCalled(false),
1542 mActiveTIPGUID(GUID_NULL) {}
1544 bool TSFStaticSink::Init(ITfThreadMgr* aThreadMgr,
1545 ITfInputProcessorProfiles* aInputProcessorProfiles) {
1546 MOZ_ASSERT(!mThreadMgr && !mInputProcessorProfiles,
1547 "TSFStaticSink::Init() must be called only once");
1549 mThreadMgr = aThreadMgr;
1550 mInputProcessorProfiles = aInputProcessorProfiles;
1552 RefPtr<ITfSource> source;
1553 HRESULT hr =
1554 mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
1555 if (FAILED(hr)) {
1556 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1557 ("0x%p TSFStaticSink::Init() FAILED to get ITfSource "
1558 "instance (0x%08X)",
1559 this, hr));
1560 return false;
1563 // NOTE: On Vista or later, Windows let us know activate IME changed only
1564 // with ITfInputProcessorProfileActivationSink.
1565 hr = source->AdviseSink(
1566 IID_ITfInputProcessorProfileActivationSink,
1567 static_cast<ITfInputProcessorProfileActivationSink*>(this),
1568 &mIPProfileCookie);
1569 if (FAILED(hr) || mIPProfileCookie == TF_INVALID_COOKIE) {
1570 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1571 ("0x%p TSFStaticSink::Init() FAILED to install "
1572 "ITfInputProcessorProfileActivationSink (0x%08X)",
1573 this, hr));
1574 return false;
1577 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1578 ("0x%p TSFStaticSink::Init(), "
1579 "mIPProfileCookie=0x%08X",
1580 this, mIPProfileCookie));
1581 return true;
1584 void TSFStaticSink::Destroy() {
1585 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1586 ("0x%p TSFStaticSink::Shutdown() "
1587 "mIPProfileCookie=0x%08X",
1588 this, mIPProfileCookie));
1590 if (mIPProfileCookie != TF_INVALID_COOKIE) {
1591 RefPtr<ITfSource> source;
1592 HRESULT hr =
1593 mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
1594 if (FAILED(hr)) {
1595 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1596 ("0x%p TSFStaticSink::Shutdown() FAILED to get "
1597 "ITfSource instance (0x%08X)",
1598 this, hr));
1599 } else {
1600 hr = source->UnadviseSink(mIPProfileCookie);
1601 if (FAILED(hr)) {
1602 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1603 ("0x%p TSFTextStore::Shutdown() FAILED to uninstall "
1604 "ITfInputProcessorProfileActivationSink (0x%08X)",
1605 this, hr));
1610 mThreadMgr = nullptr;
1611 mInputProcessorProfiles = nullptr;
1614 STDMETHODIMP
1615 TSFStaticSink::OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID rclsid,
1616 REFGUID catid, REFGUID guidProfile, HKL hkl,
1617 DWORD dwFlags) {
1618 if ((dwFlags & TF_IPSINK_FLAG_ACTIVE) &&
1619 (dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT ||
1620 catid == GUID_TFCAT_TIP_KEYBOARD)) {
1621 mOnActivatedCalled = true;
1622 mActiveTIP = TextInputProcessorID::eNotComputed;
1623 mActiveTIPGUID = guidProfile;
1624 mLangID = langid & 0xFFFF;
1625 mIsIMM_IME = IsIMM_IME(hkl);
1626 GetTIPDescription(rclsid, langid, guidProfile,
1627 mActiveTIPKeyboardDescription);
1628 if (mActiveTIPGUID != GUID_NULL) {
1629 // key should be "LocaleID|Description". Although GUID of the
1630 // profile is unique key since description may be localized for system
1631 // language, unfortunately, it's too long to record as key with its
1632 // description. Therefore, we should record only the description with
1633 // LocaleID because Microsoft IME may not include language information.
1634 // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
1635 nsAutoString key;
1636 key.AppendPrintf("0x%04X|", mLangID);
1637 nsAutoString description(mActiveTIPKeyboardDescription);
1638 static const uint32_t kMaxDescriptionLength = 72 - key.Length();
1639 if (description.Length() > kMaxDescriptionLength) {
1640 if (NS_IS_LOW_SURROGATE(description[kMaxDescriptionLength - 1]) &&
1641 NS_IS_HIGH_SURROGATE(description[kMaxDescriptionLength - 2])) {
1642 description.Truncate(kMaxDescriptionLength - 2);
1643 } else {
1644 description.Truncate(kMaxDescriptionLength - 1);
1646 // U+2026 is "..."
1647 description.Append(char16_t(0x2026));
1649 key.Append(description);
1650 Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_WINDOWS, key,
1651 true);
1653 // Notify IMEHandler of changing active keyboard layout.
1654 IMEHandler::OnKeyboardLayoutChanged();
1656 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1657 ("0x%p TSFStaticSink::OnActivated(dwProfileType=%s (0x%08X), "
1658 "langid=0x%08X, rclsid=%s, catid=%s, guidProfile=%s, hkl=0x%08X, "
1659 "dwFlags=0x%08X (TF_IPSINK_FLAG_ACTIVE: %s)), mIsIMM_IME=%s, "
1660 "mActiveTIPDescription=\"%s\"",
1661 this,
1662 dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR
1663 ? "TF_PROFILETYPE_INPUTPROCESSOR"
1664 : dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT
1665 ? "TF_PROFILETYPE_KEYBOARDLAYOUT"
1666 : "Unknown",
1667 dwProfileType, langid, GetCLSIDNameStr(rclsid).get(),
1668 GetGUIDNameStr(catid).get(), GetGUIDNameStr(guidProfile).get(), hkl,
1669 dwFlags, GetBoolName(dwFlags & TF_IPSINK_FLAG_ACTIVE),
1670 GetBoolName(mIsIMM_IME),
1671 NS_ConvertUTF16toUTF8(mActiveTIPKeyboardDescription).get()));
1672 return S_OK;
1675 bool TSFStaticSink::EnsureInitActiveTIPKeyboard() {
1676 if (mOnActivatedCalled) {
1677 return true;
1680 RefPtr<ITfInputProcessorProfileMgr> profileMgr;
1681 HRESULT hr = mInputProcessorProfiles->QueryInterface(
1682 IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
1683 if (FAILED(hr) || !profileMgr) {
1684 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1685 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1686 "to get input processor profile manager, hr=0x%08X",
1687 this, hr));
1688 return false;
1691 TF_INPUTPROCESSORPROFILE profile;
1692 hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
1693 if (hr == S_FALSE) {
1694 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1695 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1696 "to get active keyboard layout profile due to no active profile, "
1697 "hr=0x%08X",
1698 this, hr));
1699 // XXX Should we call OnActivated() with arguments like non-TIP in this
1700 // case?
1701 return false;
1703 if (FAILED(hr)) {
1704 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1705 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
1706 "to get active TIP keyboard, hr=0x%08X",
1707 this, hr));
1708 return false;
1711 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1712 ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), "
1713 "calling OnActivated() manually...",
1714 this));
1715 OnActivated(profile.dwProfileType, profile.langid, profile.clsid,
1716 profile.catid, profile.guidProfile, ::GetKeyboardLayout(0),
1717 TF_IPSINK_FLAG_ACTIVE);
1718 return true;
1721 void TSFStaticSink::GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
1722 REFGUID aProfile,
1723 nsAString& aDescription) {
1724 aDescription.Truncate();
1726 if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
1727 return;
1730 BSTR description = nullptr;
1731 HRESULT hr = mInputProcessorProfiles->GetLanguageProfileDescription(
1732 aTextService, aLangID, aProfile, &description);
1733 if (FAILED(hr)) {
1734 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1735 ("0x%p TSFStaticSink::InitActiveTIPDescription() FAILED "
1736 "due to GetLanguageProfileDescription() failure, hr=0x%08X",
1737 this, hr));
1738 return;
1741 if (description && description[0]) {
1742 aDescription.Assign(description);
1744 ::SysFreeString(description);
1747 bool TSFStaticSink::IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
1748 REFGUID aProfile) {
1749 if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
1750 return false;
1753 RefPtr<IEnumTfLanguageProfiles> enumLangProfiles;
1754 HRESULT hr = mInputProcessorProfiles->EnumLanguageProfiles(
1755 aLangID, getter_AddRefs(enumLangProfiles));
1756 if (FAILED(hr) || !enumLangProfiles) {
1757 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1758 ("0x%p TSFStaticSink::IsTIPCategoryKeyboard(), FAILED "
1759 "to get language profiles enumerator, hr=0x%08X",
1760 this, hr));
1761 return false;
1764 TF_LANGUAGEPROFILE profile;
1765 ULONG fetch = 0;
1766 while (SUCCEEDED(enumLangProfiles->Next(1, &profile, &fetch)) && fetch) {
1767 // XXX We're not sure a profile is registered with two or more categories.
1768 if (profile.clsid == aTextService && profile.guidProfile == aProfile &&
1769 profile.catid == GUID_TFCAT_TIP_KEYBOARD) {
1770 return true;
1773 return false;
1776 /******************************************************************/
1777 /* TSFPreference */
1778 /******************************************************************/
1780 class TSFPrefs final {
1781 public:
1782 #define DECL_AND_IMPL_BOOL_PREF(aPref, aName, aDefaultValue) \
1783 static bool aName() { \
1784 static bool s##aName##Value = Preferences::GetBool(aPref, aDefaultValue); \
1785 return s##aName##Value; \
1788 DECL_AND_IMPL_BOOL_PREF("intl.ime.hack.set_input_scope_of_url_bar_to_default",
1789 ShouldSetInputScopeOfURLBarToDefault, true)
1790 DECL_AND_IMPL_BOOL_PREF(
1791 "intl.tsf.hack.allow_to_stop_hacking_on_build_17643_or_later",
1792 AllowToStopHackingOnBuild17643OrLater, false)
1793 DECL_AND_IMPL_BOOL_PREF("intl.tsf.hack.atok.create_native_caret",
1794 NeedToCreateNativeCaretForLegacyATOK, true)
1795 DECL_AND_IMPL_BOOL_PREF(
1796 "intl.tsf.hack.atok.do_not_return_no_layout_error_of_composition_string",
1797 DoNotReturnNoLayoutErrorToATOKOfCompositionString, true)
1798 DECL_AND_IMPL_BOOL_PREF(
1799 "intl.tsf.hack.japanist10."
1800 "do_not_return_no_layout_error_of_composition_string",
1801 DoNotReturnNoLayoutErrorToJapanist10OfCompositionString, true)
1802 DECL_AND_IMPL_BOOL_PREF(
1803 "intl.tsf.hack.ms_simplified_chinese.do_not_return_no_layout_error",
1804 DoNotReturnNoLayoutErrorToMSSimplifiedTIP, true)
1805 DECL_AND_IMPL_BOOL_PREF(
1806 "intl.tsf.hack.ms_traditional_chinese.do_not_return_no_layout_error",
1807 DoNotReturnNoLayoutErrorToMSTraditionalTIP, true)
1808 DECL_AND_IMPL_BOOL_PREF(
1809 "intl.tsf.hack.free_chang_jie.do_not_return_no_layout_error",
1810 DoNotReturnNoLayoutErrorToFreeChangJie, true)
1811 DECL_AND_IMPL_BOOL_PREF(
1812 "intl.tsf.hack.ms_japanese_ime.do_not_return_no_layout_error_at_first_"
1813 "char",
1814 DoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar, true)
1815 DECL_AND_IMPL_BOOL_PREF(
1816 "intl.tsf.hack.ms_japanese_ime.do_not_return_no_layout_error_at_caret",
1817 DoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret, true)
1818 DECL_AND_IMPL_BOOL_PREF(
1819 "intl.tsf.hack.ms_simplified_chinese.query_insert_result",
1820 NeedToHackQueryInsertForMSSimplifiedTIP, true)
1821 DECL_AND_IMPL_BOOL_PREF(
1822 "intl.tsf.hack.ms_traditional_chinese.query_insert_result",
1823 NeedToHackQueryInsertForMSTraditionalTIP, true)
1825 #undef DECL_AND_IMPL_BOOL_PREF
1828 /******************************************************************/
1829 /* TSFTextStore */
1830 /******************************************************************/
1832 StaticRefPtr<ITfThreadMgr> TSFTextStore::sThreadMgr;
1833 StaticRefPtr<ITfMessagePump> TSFTextStore::sMessagePump;
1834 StaticRefPtr<ITfKeystrokeMgr> TSFTextStore::sKeystrokeMgr;
1835 StaticRefPtr<ITfDisplayAttributeMgr> TSFTextStore::sDisplayAttrMgr;
1836 StaticRefPtr<ITfCategoryMgr> TSFTextStore::sCategoryMgr;
1837 StaticRefPtr<ITfCompartment> TSFTextStore::sCompartmentForOpenClose;
1838 StaticRefPtr<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr;
1839 StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext;
1840 StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles;
1841 StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore;
1842 const MSG* TSFTextStore::sHandlingKeyMsg = nullptr;
1843 DWORD TSFTextStore::sClientId = 0;
1844 bool TSFTextStore::sIsKeyboardEventDispatched = false;
1846 #define TEXTSTORE_DEFAULT_VIEW (1)
1848 TSFTextStore::TSFTextStore()
1849 : mEditCookie(0),
1850 mSinkMask(0),
1851 mLock(0),
1852 mLockQueued(0),
1853 mHandlingKeyMessage(0),
1854 mContentForTSF(mComposition, mSelectionForTSF),
1855 mRequestedAttrValues(false),
1856 mIsRecordingActionsWithoutLock(false),
1857 mHasReturnedNoLayoutError(false),
1858 mWaitingQueryLayout(false),
1859 mPendingDestroy(false),
1860 mDeferClearingContentForTSF(false),
1861 mDeferNotifyingTSF(false),
1862 mDeferCommittingComposition(false),
1863 mDeferCancellingComposition(false),
1864 mDestroyed(false),
1865 mBeingDestroyed(false) {
1866 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
1867 mRequestedAttrs[i] = false;
1870 // We hope that 5 or more actions don't occur at once.
1871 mPendingActions.SetCapacity(5);
1873 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1874 ("0x%p TSFTextStore::TSFTextStore() SUCCEEDED", this));
1877 TSFTextStore::~TSFTextStore() {
1878 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1879 ("0x%p TSFTextStore instance is destroyed", this));
1882 bool TSFTextStore::Init(nsWindowBase* aWidget, const InputContext& aContext) {
1883 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1884 ("0x%p TSFTextStore::Init(aWidget=0x%p)", this, aWidget));
1886 if (NS_WARN_IF(!aWidget) || NS_WARN_IF(aWidget->Destroyed())) {
1887 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1888 ("0x%p TSFTextStore::Init() FAILED due to being initialized with "
1889 "destroyed widget",
1890 this));
1891 return false;
1894 if (mDocumentMgr) {
1895 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1896 ("0x%p TSFTextStore::Init() FAILED due to already initialized",
1897 this));
1898 return false;
1901 mWidget = aWidget;
1902 if (NS_WARN_IF(!mWidget)) {
1903 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1904 ("0x%p TSFTextStore::Init() FAILED "
1905 "due to aWidget is nullptr ",
1906 this));
1907 return false;
1909 mDispatcher = mWidget->GetTextEventDispatcher();
1910 if (NS_WARN_IF(!mDispatcher)) {
1911 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1912 ("0x%p TSFTextStore::Init() FAILED "
1913 "due to aWidget->GetTextEventDispatcher() failure",
1914 this));
1915 return false;
1918 SetInputScope(aContext.mHTMLInputType, aContext.mHTMLInputInputmode,
1919 aContext.mInPrivateBrowsing);
1921 // Create document manager
1922 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
1923 RefPtr<ITfDocumentMgr> documentMgr;
1924 HRESULT hr = threadMgr->CreateDocumentMgr(getter_AddRefs(documentMgr));
1925 if (NS_WARN_IF(FAILED(hr))) {
1926 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1927 ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr "
1928 "(0x%08X)",
1929 this, hr));
1930 return false;
1932 if (NS_WARN_IF(mDestroyed)) {
1933 MOZ_LOG(
1934 sTextStoreLog, LogLevel::Error,
1935 ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr due to "
1936 "TextStore being destroyed during calling "
1937 "ITfThreadMgr::CreateDocumentMgr()",
1938 this));
1939 return false;
1941 // Create context and add it to document manager
1942 RefPtr<ITfContext> context;
1943 hr = documentMgr->CreateContext(sClientId, 0,
1944 static_cast<ITextStoreACP*>(this),
1945 getter_AddRefs(context), &mEditCookie);
1946 if (NS_WARN_IF(FAILED(hr))) {
1947 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1948 ("0x%p TSFTextStore::Init() FAILED to create the context "
1949 "(0x%08X)",
1950 this, hr));
1951 return false;
1953 if (NS_WARN_IF(mDestroyed)) {
1954 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1955 ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
1956 "TextStore being destroyed during calling "
1957 "ITfDocumentMgr::CreateContext()",
1958 this));
1959 return false;
1962 hr = documentMgr->Push(context);
1963 if (NS_WARN_IF(FAILED(hr))) {
1964 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1965 ("0x%p TSFTextStore::Init() FAILED to push the context (0x%08X)",
1966 this, hr));
1967 return false;
1969 if (NS_WARN_IF(mDestroyed)) {
1970 MOZ_LOG(sTextStoreLog, LogLevel::Error,
1971 ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
1972 "TextStore being destroyed during calling ITfDocumentMgr::Push()",
1973 this));
1974 documentMgr->Pop(TF_POPF_ALL);
1975 return false;
1978 mDocumentMgr = documentMgr;
1979 mContext = context;
1981 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1982 ("0x%p TSFTextStore::Init() succeeded: "
1983 "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08X",
1984 this, mDocumentMgr.get(), mContext.get(), mEditCookie));
1986 return true;
1989 void TSFTextStore::Destroy() {
1990 if (mBeingDestroyed) {
1991 return;
1994 MOZ_LOG(sTextStoreLog, LogLevel::Info,
1995 ("0x%p TSFTextStore::Destroy(), mLock=%s, "
1996 "mComposition.IsComposing()=%s, mHandlingKeyMessage=%u",
1997 this, GetLockFlagNameStr(mLock).get(),
1998 GetBoolName(mComposition.IsComposing()), mHandlingKeyMessage));
2000 mDestroyed = true;
2002 // Destroy native caret first because it's not directly related to TSF and
2003 // there may be another textstore which gets focus. So, we should avoid
2004 // to destroy caret after the new one recreates caret.
2005 IMEHandler::MaybeDestroyNativeCaret();
2007 if (mLock) {
2008 mPendingDestroy = true;
2009 return;
2012 AutoRestore<bool> savedBeingDestroyed(mBeingDestroyed);
2013 mBeingDestroyed = true;
2015 // If there is composition, TSF keeps the composition even after the text
2016 // store destroyed. So, we should clear the composition here.
2017 if (mComposition.IsComposing()) {
2018 CommitCompositionInternal(false);
2021 if (mSink) {
2022 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2023 ("0x%p TSFTextStore::Destroy(), calling "
2024 "ITextStoreACPSink::OnLayoutChange(TS_LC_DESTROY)...",
2025 this));
2026 RefPtr<ITextStoreACPSink> sink = mSink;
2027 sink->OnLayoutChange(TS_LC_DESTROY, TEXTSTORE_DEFAULT_VIEW);
2030 // If this is called during handling a keydown or keyup message, we should
2031 // put off to release TSF objects until it completely finishes since
2032 // MS-IME for Japanese refers some objects without grabbing them.
2033 if (!mHandlingKeyMessage) {
2034 ReleaseTSFObjects();
2037 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2038 ("0x%p TSFTextStore::Destroy() succeeded", this));
2041 void TSFTextStore::ReleaseTSFObjects() {
2042 MOZ_ASSERT(!mHandlingKeyMessage);
2044 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2045 ("0x%p TSFTextStore::ReleaseTSFObjects()", this));
2047 mContext = nullptr;
2048 if (mDocumentMgr) {
2049 RefPtr<ITfDocumentMgr> documentMgr = mDocumentMgr.forget();
2050 documentMgr->Pop(TF_POPF_ALL);
2052 mSink = nullptr;
2053 mWidget = nullptr;
2054 mDispatcher = nullptr;
2056 if (!mMouseTrackers.IsEmpty()) {
2057 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2058 ("0x%p TSFTextStore::ReleaseTSFObjects(), "
2059 "removing a mouse tracker...",
2060 this));
2061 mMouseTrackers.Clear();
2064 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2065 ("0x%p TSFTextStore::ReleaseTSFObjects() completed", this));
2068 STDMETHODIMP
2069 TSFTextStore::QueryInterface(REFIID riid, void** ppv) {
2070 *ppv = nullptr;
2071 if ((IID_IUnknown == riid) || (IID_ITextStoreACP == riid)) {
2072 *ppv = static_cast<ITextStoreACP*>(this);
2073 } else if (IID_ITfContextOwnerCompositionSink == riid) {
2074 *ppv = static_cast<ITfContextOwnerCompositionSink*>(this);
2075 } else if (IID_ITfMouseTrackerACP == riid) {
2076 *ppv = static_cast<ITfMouseTrackerACP*>(this);
2078 if (*ppv) {
2079 AddRef();
2080 return S_OK;
2083 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2084 ("0x%p TSFTextStore::QueryInterface() FAILED, riid=%s", this,
2085 GetRIIDNameStr(riid).get()));
2086 return E_NOINTERFACE;
2089 STDMETHODIMP
2090 TSFTextStore::AdviseSink(REFIID riid, IUnknown* punk, DWORD dwMask) {
2091 MOZ_LOG(
2092 sTextStoreLog, LogLevel::Info,
2093 ("0x%p TSFTextStore::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), "
2094 "mSink=0x%p, mSinkMask=%s",
2095 this, GetRIIDNameStr(riid).get(), punk, GetSinkMaskNameStr(dwMask).get(),
2096 mSink.get(), GetSinkMaskNameStr(mSinkMask).get()));
2098 if (!punk) {
2099 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2100 ("0x%p TSFTextStore::AdviseSink() FAILED due to the null punk",
2101 this));
2102 return E_UNEXPECTED;
2105 if (IID_ITextStoreACPSink != riid) {
2106 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2107 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2108 "unsupported interface",
2109 this));
2110 return E_INVALIDARG; // means unsupported interface.
2113 if (!mSink) {
2114 // Install sink
2115 punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink));
2116 if (!mSink) {
2117 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2118 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2119 "punk not having the interface",
2120 this));
2121 return E_UNEXPECTED;
2123 } else {
2124 // If sink is already installed we check to see if they are the same
2125 // Get IUnknown from both sides for comparison
2126 RefPtr<IUnknown> comparison1, comparison2;
2127 punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
2128 mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
2129 if (comparison1 != comparison2) {
2130 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2131 ("0x%p TSFTextStore::AdviseSink() FAILED due to "
2132 "the sink being different from the stored sink",
2133 this));
2134 return CONNECT_E_ADVISELIMIT;
2137 // Update mask either for a new sink or an existing sink
2138 mSinkMask = dwMask;
2139 return S_OK;
2142 STDMETHODIMP
2143 TSFTextStore::UnadviseSink(IUnknown* punk) {
2144 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2145 ("0x%p TSFTextStore::UnadviseSink(punk=0x%p), mSink=0x%p", this, punk,
2146 mSink.get()));
2148 if (!punk) {
2149 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2150 ("0x%p TSFTextStore::UnadviseSink() FAILED due to the null punk",
2151 this));
2152 return E_INVALIDARG;
2154 if (!mSink) {
2155 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2156 ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
2157 "any sink not stored",
2158 this));
2159 return CONNECT_E_NOCONNECTION;
2161 // Get IUnknown from both sides for comparison
2162 RefPtr<IUnknown> comparison1, comparison2;
2163 punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
2164 mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
2165 // Unadvise only if sinks are the same
2166 if (comparison1 != comparison2) {
2167 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2168 ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
2169 "the sink being different from the stored sink",
2170 this));
2171 return CONNECT_E_NOCONNECTION;
2173 mSink = nullptr;
2174 mSinkMask = 0;
2175 return S_OK;
2178 STDMETHODIMP
2179 TSFTextStore::RequestLock(DWORD dwLockFlags, HRESULT* phrSession) {
2180 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2181 ("0x%p TSFTextStore::RequestLock(dwLockFlags=%s, phrSession=0x%p), "
2182 "mLock=%s, mDestroyed=%s",
2183 this, GetLockFlagNameStr(dwLockFlags).get(), phrSession,
2184 GetLockFlagNameStr(mLock).get(), GetBoolName(mDestroyed)));
2186 if (!mSink) {
2187 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2188 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2189 "any sink not stored",
2190 this));
2191 return E_FAIL;
2193 if (mDestroyed &&
2194 (!mContentForTSF.IsInitialized() || mSelectionForTSF.IsDirty())) {
2195 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2196 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2197 "being destroyed and no information of the contents",
2198 this));
2199 return E_FAIL;
2201 if (!phrSession) {
2202 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2203 ("0x%p TSFTextStore::RequestLock() FAILED due to "
2204 "null phrSession",
2205 this));
2206 return E_INVALIDARG;
2209 if (!mLock) {
2210 // put on lock
2211 mLock = dwLockFlags & (~TS_LF_SYNC);
2212 MOZ_LOG(
2213 sTextStoreLog, LogLevel::Info,
2214 ("0x%p Locking (%s) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
2215 ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
2216 this, GetLockFlagNameStr(mLock).get()));
2217 // Don't release this instance during this lock because this is called by
2218 // TSF but they don't grab us during this call.
2219 RefPtr<TSFTextStore> kungFuDeathGrip(this);
2220 RefPtr<ITextStoreACPSink> sink = mSink;
2221 *phrSession = sink->OnLockGranted(mLock);
2222 MOZ_LOG(
2223 sTextStoreLog, LogLevel::Info,
2224 ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2225 "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
2226 this, GetLockFlagNameStr(mLock).get()));
2227 DidLockGranted();
2228 while (mLockQueued) {
2229 mLock = mLockQueued;
2230 mLockQueued = 0;
2231 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2232 ("0x%p Locking for the request in the queue (%s) >>>>>>>>>>>>>>"
2233 ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
2234 ">>>>>",
2235 this, GetLockFlagNameStr(mLock).get()));
2236 sink->OnLockGranted(mLock);
2237 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2238 ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2239 "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
2240 "<<<<<",
2241 this, GetLockFlagNameStr(mLock).get()));
2242 DidLockGranted();
2245 // The document is now completely unlocked.
2246 mLock = 0;
2248 MaybeFlushPendingNotifications();
2250 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2251 ("0x%p TSFTextStore::RequestLock() succeeded: *phrSession=%s",
2252 this, GetTextStoreReturnValueName(*phrSession)));
2253 return S_OK;
2256 // only time when reentrant lock is allowed is when caller holds a
2257 // read-only lock and is requesting an async write lock
2258 if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags) &&
2259 !(dwLockFlags & TS_LF_SYNC)) {
2260 *phrSession = TS_S_ASYNC;
2261 mLockQueued = dwLockFlags & (~TS_LF_SYNC);
2263 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2264 ("0x%p TSFTextStore::RequestLock() stores the request in the "
2265 "queue, *phrSession=TS_S_ASYNC",
2266 this));
2267 return S_OK;
2270 // no more locks allowed
2271 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2272 ("0x%p TSFTextStore::RequestLock() didn't allow to lock, "
2273 "*phrSession=TS_E_SYNCHRONOUS",
2274 this));
2275 *phrSession = TS_E_SYNCHRONOUS;
2276 return E_FAIL;
2279 void TSFTextStore::DidLockGranted() {
2280 if (IsReadWriteLocked()) {
2281 // FreeCJ (TIP for Traditional Chinese) calls SetSelection() to set caret
2282 // to the start of composition string and insert a full width space for
2283 // a placeholder with a call of SetText(). After that, it calls
2284 // OnUpdateComposition() without new range. Therefore, let's record the
2285 // composition update information here.
2286 CompleteLastActionIfStillIncomplete();
2288 FlushPendingActions();
2291 // If the widget has gone, we don't need to notify anything.
2292 if (mDestroyed || !mWidget || mWidget->Destroyed()) {
2293 mPendingSelectionChangeData.Clear();
2294 mHasReturnedNoLayoutError = false;
2298 void TSFTextStore::DispatchEvent(WidgetGUIEvent& aEvent) {
2299 if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) {
2300 return;
2302 // If the event isn't a query content event, the event may be handled
2303 // asynchronously. So, we should put off to answer from GetTextExt() etc.
2304 if (!aEvent.AsQueryContentEvent()) {
2305 mDeferNotifyingTSF = true;
2307 mWidget->DispatchWindowEvent(&aEvent);
2310 void TSFTextStore::FlushPendingActions() {
2311 if (!mWidget || mWidget->Destroyed()) {
2312 // Note that don't clear mContentForTSF because TIP may try to commit
2313 // composition with a document lock. In such case, TSFTextStore needs to
2314 // behave as expected by TIP.
2315 mPendingActions.Clear();
2316 mPendingSelectionChangeData.Clear();
2317 mHasReturnedNoLayoutError = false;
2318 return;
2321 // Some TIP may request lock but does nothing during the lock. In such case,
2322 // this should do nothing. For example, when MS-IME for Japanese is active
2323 // and we're inactivating, this case occurs and causes different behavior
2324 // from the other TIPs.
2325 if (mPendingActions.IsEmpty()) {
2326 return;
2329 RefPtr<nsWindowBase> widget(mWidget);
2330 nsresult rv = mDispatcher->BeginNativeInputTransaction();
2331 if (NS_WARN_IF(NS_FAILED(rv))) {
2332 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2333 ("0x%p TSFTextStore::FlushPendingActions() "
2334 "FAILED due to BeginNativeInputTransaction() failure",
2335 this));
2336 return;
2338 for (uint32_t i = 0; i < mPendingActions.Length(); i++) {
2339 PendingAction& action = mPendingActions[i];
2340 switch (action.mType) {
2341 case PendingAction::Type::eKeyboardEvent:
2342 if (mDestroyed) {
2343 MOZ_LOG(
2344 sTextStoreLog, LogLevel::Warning,
2345 ("0x%p TSFTextStore::FlushPendingActions() "
2346 "IGNORED pending KeyboardEvent(%s) due to already destroyed",
2347 action.mKeyMsg->message == WM_KEYDOWN ? "eKeyDown" : "eKeyUp",
2348 this));
2350 MOZ_DIAGNOSTIC_ASSERT(action.mKeyMsg);
2351 DispatchKeyboardEventAsProcessedByIME(*action.mKeyMsg);
2352 if (!widget || widget->Destroyed()) {
2353 break;
2355 break;
2356 case PendingAction::Type::eCompositionStart: {
2357 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2358 ("0x%p TSFTextStore::FlushPendingActions() "
2359 "flushing Type::eCompositionStart={ mSelectionStart=%d, "
2360 "mSelectionLength=%d }, mDestroyed=%s",
2361 this, action.mSelectionStart, action.mSelectionLength,
2362 GetBoolName(mDestroyed)));
2364 if (mDestroyed) {
2365 MOZ_LOG(sTextStoreLog, LogLevel::Warning,
2366 ("0x%p TSFTextStore::FlushPendingActions() "
2367 "IGNORED pending compositionstart due to already destroyed",
2368 this));
2369 break;
2372 if (action.mAdjustSelection) {
2373 // Select composition range so the new composition replaces the range
2374 WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
2375 widget->InitEvent(selectionSet);
2376 selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
2377 selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
2378 selectionSet.mReversed = false;
2379 DispatchEvent(selectionSet);
2380 if (!selectionSet.mSucceeded) {
2381 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2382 ("0x%p TSFTextStore::FlushPendingActions() "
2383 "FAILED due to eSetSelection failure",
2384 this));
2385 break;
2389 // eCompositionStart always causes
2390 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. Therefore, we should
2391 // wait to clear mContentForTSF until it's notified.
2392 mDeferClearingContentForTSF = true;
2394 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2395 ("0x%p TSFTextStore::FlushPendingActions() "
2396 "dispatching compositionstart event...",
2397 this));
2398 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2399 nsEventStatus status;
2400 rv = mDispatcher->StartComposition(status, &eventTime);
2401 if (NS_WARN_IF(NS_FAILED(rv))) {
2402 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2403 ("0x%p TSFTextStore::FlushPendingActions() "
2404 "FAILED to dispatch compositionstart event, "
2405 "IsHandlingComposition()=%s",
2406 this, GetBoolName(IsHandlingComposition())));
2407 // XXX Is this right? If there is a composition in content,
2408 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2409 mDeferClearingContentForTSF = !IsHandlingComposition();
2411 if (!widget || widget->Destroyed()) {
2412 break;
2414 break;
2416 case PendingAction::Type::eCompositionUpdate: {
2417 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2418 ("0x%p TSFTextStore::FlushPendingActions() "
2419 "flushing Type::eCompositionUpdate={ mData=\"%s\", "
2420 "mRanges=0x%p, mRanges->Length()=%d }",
2421 this, GetEscapedUTF8String(action.mData).get(),
2422 action.mRanges.get(),
2423 action.mRanges ? action.mRanges->Length() : 0));
2425 // eCompositionChange causes a DOM text event, the IME will be notified
2426 // of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. In this case, we
2427 // should not clear mContentForTSF until we notify the IME of the
2428 // composition update.
2429 mDeferClearingContentForTSF = true;
2431 rv = mDispatcher->SetPendingComposition(action.mData, action.mRanges);
2432 if (NS_WARN_IF(NS_FAILED(rv))) {
2433 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2434 ("0x%p TSFTextStore::FlushPendingActions() "
2435 "FAILED to setting pending composition... "
2436 "IsHandlingComposition()=%s",
2437 this, GetBoolName(IsHandlingComposition())));
2438 // XXX Is this right? If there is a composition in content,
2439 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2440 mDeferClearingContentForTSF = !IsHandlingComposition();
2441 } else {
2442 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2443 ("0x%p TSFTextStore::FlushPendingActions() "
2444 "dispatching compositionchange event...",
2445 this));
2446 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2447 nsEventStatus status;
2448 rv = mDispatcher->FlushPendingComposition(status, &eventTime);
2449 if (NS_WARN_IF(NS_FAILED(rv))) {
2450 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2451 ("0x%p TSFTextStore::FlushPendingActions() "
2452 "FAILED to dispatch compositionchange event, "
2453 "IsHandlingComposition()=%s",
2454 this, GetBoolName(IsHandlingComposition())));
2455 // XXX Is this right? If there is a composition in content,
2456 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2457 mDeferClearingContentForTSF = !IsHandlingComposition();
2459 // Be aware, the mWidget might already have been destroyed.
2461 break;
2463 case PendingAction::Type::eCompositionEnd: {
2464 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2465 ("0x%p TSFTextStore::FlushPendingActions() "
2466 "flushing Type::eCompositionEnd={ mData=\"%s\" }",
2467 this, GetEscapedUTF8String(action.mData).get()));
2469 // Dispatching eCompositionCommit causes a DOM text event, then,
2470 // the IME will be notified of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
2471 // when focused content actually handles the event. For example,
2472 // when focused content is in a remote process, it's sent when
2473 // all dispatched composition events have been handled in the remote
2474 // process. So, until then, we don't have newer content information.
2475 // Therefore, we need to put off to clear mContentForTSF.
2476 mDeferClearingContentForTSF = true;
2478 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2479 ("0x%p TSFTextStore::FlushPendingActions(), "
2480 "dispatching compositioncommit event...",
2481 this));
2482 WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
2483 nsEventStatus status;
2484 rv = mDispatcher->CommitComposition(status, &action.mData, &eventTime);
2485 if (NS_WARN_IF(NS_FAILED(rv))) {
2486 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2487 ("0x%p TSFTextStore::FlushPendingActions() "
2488 "FAILED to dispatch compositioncommit event, "
2489 "IsHandlingComposition()=%s",
2490 this, GetBoolName(IsHandlingComposition())));
2491 // XXX Is this right? If there is a composition in content,
2492 // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
2493 mDeferClearingContentForTSF = !IsHandlingComposition();
2495 break;
2497 case PendingAction::Type::eSetSelection: {
2498 MOZ_LOG(
2499 sTextStoreLog, LogLevel::Debug,
2500 ("0x%p TSFTextStore::FlushPendingActions() "
2501 "flushing Type::eSetSelection={ mSelectionStart=%d, "
2502 "mSelectionLength=%d, mSelectionReversed=%s }, "
2503 "mDestroyed=%s",
2504 this, action.mSelectionStart, action.mSelectionLength,
2505 GetBoolName(action.mSelectionReversed), GetBoolName(mDestroyed)));
2507 if (mDestroyed) {
2508 MOZ_LOG(sTextStoreLog, LogLevel::Warning,
2509 ("0x%p TSFTextStore::FlushPendingActions() "
2510 "IGNORED pending selectionset due to already destroyed",
2511 this));
2512 break;
2515 WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
2516 selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
2517 selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
2518 selectionSet.mReversed = action.mSelectionReversed;
2519 DispatchEvent(selectionSet);
2520 if (!selectionSet.mSucceeded) {
2521 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2522 ("0x%p TSFTextStore::FlushPendingActions() "
2523 "FAILED due to eSetSelection failure",
2524 this));
2525 break;
2527 break;
2529 default:
2530 MOZ_CRASH("unexpected action type");
2533 if (widget && !widget->Destroyed()) {
2534 continue;
2537 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2538 ("0x%p TSFTextStore::FlushPendingActions(), "
2539 "qutting since the mWidget has gone",
2540 this));
2541 break;
2543 mPendingActions.Clear();
2546 void TSFTextStore::MaybeFlushPendingNotifications() {
2547 if (IsReadLocked()) {
2548 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2549 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2550 "putting off flushing pending notifications due to being the "
2551 "document locked...",
2552 this));
2553 return;
2556 if (mDeferCommittingComposition) {
2557 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2558 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2559 "calling TSFTextStore::CommitCompositionInternal(false)...",
2560 this));
2561 mDeferCommittingComposition = mDeferCancellingComposition = false;
2562 CommitCompositionInternal(false);
2563 } else if (mDeferCancellingComposition) {
2564 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2565 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2566 "calling TSFTextStore::CommitCompositionInternal(true)...",
2567 this));
2568 mDeferCommittingComposition = mDeferCancellingComposition = false;
2569 CommitCompositionInternal(true);
2572 if (mDeferNotifyingTSF) {
2573 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2574 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2575 "putting off flushing pending notifications due to being "
2576 "dispatching events...",
2577 this));
2578 return;
2581 if (mPendingDestroy) {
2582 Destroy();
2583 return;
2586 if (mDestroyed) {
2587 // If it's already been destroyed completely, this shouldn't notify TSF of
2588 // anything anymore.
2589 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2590 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2591 "does nothing because this has already destroyed completely...",
2592 this));
2593 return;
2596 if (!mDeferClearingContentForTSF && mContentForTSF.IsInitialized()) {
2597 mContentForTSF.Clear();
2598 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2599 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2600 "mContentForTSF is cleared",
2601 this));
2604 // When there is no cached content, we can sync actual contents and TSF/TIP
2605 // expecting contents.
2606 RefPtr<TSFTextStore> kungFuDeathGrip = this;
2607 Unused << kungFuDeathGrip;
2608 if (!mContentForTSF.IsInitialized()) {
2609 if (mPendingTextChangeData.IsValid()) {
2610 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2611 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2612 "calling TSFTextStore::NotifyTSFOfTextChange()...",
2613 this));
2614 NotifyTSFOfTextChange();
2616 if (mPendingSelectionChangeData.IsValid()) {
2617 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2618 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2619 "calling TSFTextStore::NotifyTSFOfSelectionChange()...",
2620 this));
2621 NotifyTSFOfSelectionChange();
2625 if (mHasReturnedNoLayoutError) {
2626 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2627 ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
2628 "calling TSFTextStore::NotifyTSFOfLayoutChange()...",
2629 this));
2630 NotifyTSFOfLayoutChange();
2634 void TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME() {
2635 // If we've already been destroyed, we cannot do anything.
2636 if (mDestroyed) {
2637 MOZ_LOG(
2638 sTextStoreLog, LogLevel::Debug,
2639 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2640 "does nothing because it's already been destroyed",
2641 this));
2642 return;
2645 // If we're not handling key message or we've already dispatched a keyboard
2646 // event for the handling key message, we should do nothing anymore.
2647 if (!sHandlingKeyMsg || sIsKeyboardEventDispatched) {
2648 MOZ_LOG(
2649 sTextStoreLog, LogLevel::Debug,
2650 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2651 "does nothing because not necessary to dispatch keyboard event",
2652 this));
2653 return;
2656 sIsKeyboardEventDispatched = true;
2657 // If the document is locked, just adding the task to dispatching an event
2658 // to the queue.
2659 if (IsReadLocked()) {
2660 MOZ_LOG(
2661 sTextStoreLog, LogLevel::Debug,
2662 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2663 "adding to dispatch a keyboard event into the queue...",
2664 this));
2665 PendingAction* action = mPendingActions.AppendElement();
2666 action->mType = PendingAction::Type::eKeyboardEvent;
2667 action->mKeyMsg = sHandlingKeyMsg;
2668 return;
2671 // Otherwise, dispatch a keyboard event.
2672 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2673 ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
2674 "trying to dispatch a keyboard event...",
2675 this));
2676 DispatchKeyboardEventAsProcessedByIME(*sHandlingKeyMsg);
2679 void TSFTextStore::DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg) {
2680 MOZ_ASSERT(mWidget);
2681 MOZ_ASSERT(!mWidget->Destroyed());
2682 MOZ_ASSERT(!mDestroyed);
2684 ModifierKeyState modKeyState;
2685 MSG msg(aMsg);
2686 msg.wParam = VK_PROCESSKEY;
2687 NativeKey nativeKey(mWidget, msg, modKeyState);
2688 switch (aMsg.message) {
2689 case WM_KEYDOWN:
2690 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2691 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2692 "dispatching an eKeyDown event...",
2693 this));
2694 nativeKey.HandleKeyDownMessage();
2695 break;
2696 case WM_KEYUP:
2697 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2698 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2699 "dispatching an eKeyUp event...",
2700 this));
2701 nativeKey.HandleKeyUpMessage();
2702 break;
2703 default:
2704 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2705 ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
2706 "ERROR, it doesn't handle the message",
2707 this));
2708 break;
2712 STDMETHODIMP
2713 TSFTextStore::GetStatus(TS_STATUS* pdcs) {
2714 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2715 ("0x%p TSFTextStore::GetStatus(pdcs=0x%p)", this, pdcs));
2717 if (!pdcs) {
2718 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2719 ("0x%p TSFTextStore::GetStatus() FAILED due to null pdcs", this));
2720 return E_INVALIDARG;
2722 pdcs->dwDynamicFlags = 0;
2723 // we use a "flat" text model for TSF support so no hidden text
2724 pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT;
2725 return S_OK;
2728 STDMETHODIMP
2729 TSFTextStore::QueryInsert(LONG acpTestStart, LONG acpTestEnd, ULONG cch,
2730 LONG* pacpResultStart, LONG* pacpResultEnd) {
2731 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2732 ("0x%p TSFTextStore::QueryInsert(acpTestStart=%ld, "
2733 "acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)",
2734 this, acpTestStart, acpTestEnd, cch, acpTestStart, acpTestEnd));
2736 if (!pacpResultStart || !pacpResultEnd) {
2737 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2738 ("0x%p TSFTextStore::QueryInsert() FAILED due to "
2739 "the null argument",
2740 this));
2741 return E_INVALIDARG;
2744 if (acpTestStart < 0 || acpTestStart > acpTestEnd) {
2745 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2746 ("0x%p TSFTextStore::QueryInsert() FAILED due to "
2747 "wrong argument",
2748 this));
2749 return E_INVALIDARG;
2752 // XXX need to adjust to cluster boundary
2753 // Assume we are given good offsets for now
2754 if (IsWin8OrLater() && !mComposition.IsComposing() &&
2755 ((TSFPrefs::NeedToHackQueryInsertForMSTraditionalTIP() &&
2756 TSFStaticSink::IsMSChangJieOrMSQuickActive()) ||
2757 (TSFPrefs::NeedToHackQueryInsertForMSSimplifiedTIP() &&
2758 TSFStaticSink::IsMSPinyinOrMSWubiActive()))) {
2759 MOZ_LOG(sTextStoreLog, LogLevel::Warning,
2760 ("0x%p TSFTextStore::QueryInsert() WARNING using different "
2761 "result for the TIP",
2762 this));
2763 // Chinese TIPs of Microsoft assume that QueryInsert() returns selected
2764 // range which should be removed.
2765 *pacpResultStart = acpTestStart;
2766 *pacpResultEnd = acpTestEnd;
2767 } else {
2768 *pacpResultStart = acpTestStart;
2769 *pacpResultEnd = acpTestStart + cch;
2772 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2773 ("0x%p TSFTextStore::QueryInsert() succeeded: "
2774 "*pacpResultStart=%ld, *pacpResultEnd=%ld)",
2775 this, *pacpResultStart, *pacpResultEnd));
2776 return S_OK;
2779 STDMETHODIMP
2780 TSFTextStore::GetSelection(ULONG ulIndex, ULONG ulCount,
2781 TS_SELECTION_ACP* pSelection, ULONG* pcFetched) {
2782 MOZ_LOG(sTextStoreLog, LogLevel::Info,
2783 ("0x%p TSFTextStore::GetSelection(ulIndex=%lu, ulCount=%lu, "
2784 "pSelection=0x%p, pcFetched=0x%p)",
2785 this, ulIndex, ulCount, pSelection, pcFetched));
2787 if (!IsReadLocked()) {
2788 MOZ_LOG(
2789 sTextStoreLog, LogLevel::Error,
2790 ("0x%p TSFTextStore::GetSelection() FAILED due to not locked", this));
2791 return TS_E_NOLOCK;
2793 if (!ulCount || !pSelection || !pcFetched) {
2794 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2795 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2796 "null argument",
2797 this));
2798 return E_INVALIDARG;
2801 *pcFetched = 0;
2803 if (ulIndex != static_cast<ULONG>(TS_DEFAULT_SELECTION) && ulIndex != 0) {
2804 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2805 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2806 "unsupported selection",
2807 this));
2808 return TS_E_NOSELECTION;
2811 Selection& selectionForTSF = SelectionForTSFRef();
2812 if (selectionForTSF.IsDirty()) {
2813 if (DoNotReturnErrorFromGetSelection()) {
2814 AutoSetTemporarySelection temprarySetter(selectionForTSF);
2815 *pSelection = selectionForTSF.ACP();
2816 *pcFetched = 1;
2817 MOZ_LOG(
2818 sTextStoreLog, LogLevel::Info,
2819 ("0x%p TSFTextStore::GetSelection() returns fake selection range "
2820 "for avoiding a crash in TSF, "
2821 "acpStart=%d, acpEnd=%d (length=%d), reverted=%s",
2822 this, selectionForTSF.StartOffset(), selectionForTSF.EndOffset(),
2823 selectionForTSF.Length(),
2824 GetBoolName(selectionForTSF.IsReversed())));
2825 return S_OK;
2827 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2828 ("0x%p TSFTextStore::GetSelection() FAILED due to "
2829 "SelectionForTSFRef() failure",
2830 this));
2831 return E_FAIL;
2833 *pSelection = selectionForTSF.ACP();
2834 *pcFetched = 1;
2835 MOZ_LOG(
2836 sTextStoreLog, LogLevel::Info,
2837 ("0x%p TSFTextStore::GetSelection() succeeded, "
2838 "acpStart=%d, acpEnd=%d (length=%d), reverted=%s",
2839 this, selectionForTSF.StartOffset(), selectionForTSF.EndOffset(),
2840 selectionForTSF.Length(), GetBoolName(selectionForTSF.IsReversed())));
2841 return S_OK;
2844 // static
2845 bool TSFTextStore::DoNotReturnErrorFromGetSelection() {
2846 // There is a crash bug of TSF if we return error from GetSelection().
2847 // That was introduced in Anniversary Update (build 14393, see bug 1312302)
2848 // TODO: We should avoid to run this hack on fixed builds. When we get
2849 // exact build number, we should get back here.
2850 static bool sTSFMayCrashIfGetSelectionReturnsError =
2851 IsWindows10BuildOrLater(14393);
2852 return sTSFMayCrashIfGetSelectionReturnsError;
2855 TSFTextStore::Content& TSFTextStore::ContentForTSFRef() {
2856 // This should be called when the document is locked or the content hasn't
2857 // been abandoned yet.
2858 if (NS_WARN_IF(!IsReadLocked() && !mContentForTSF.IsInitialized())) {
2859 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2860 ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to "
2861 "called wrong timing, IsReadLocked()=%s, "
2862 "mContentForTSF.IsInitialized()=%s",
2863 this, GetBoolName(IsReadLocked()),
2864 GetBoolName(mContentForTSF.IsInitialized())));
2865 mContentForTSF.Clear();
2866 return mContentForTSF;
2869 Selection& selectionForTSF = SelectionForTSFRef();
2870 if (selectionForTSF.IsDirty()) {
2871 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2872 ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to "
2873 "SelectionForTSFRef() failure",
2874 this));
2875 mContentForTSF.Clear();
2876 return mContentForTSF;
2879 if (!mContentForTSF.IsInitialized()) {
2880 nsAutoString text;
2881 if (NS_WARN_IF(!GetCurrentText(text))) {
2882 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2883 ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to "
2884 "GetCurrentText() failure",
2885 this));
2886 mContentForTSF.Clear();
2887 return mContentForTSF;
2890 mContentForTSF.Init(text);
2891 // Basically, the cached content which is expected by TSF/TIP should be
2892 // cleared after active composition is committed or the document lock is
2893 // unlocked. However, in e10s mode, content will be modified
2894 // asynchronously. In such case, mDeferClearingContentForTSF may be
2895 // true until whole dispatched events are handled by the focused editor.
2896 mDeferClearingContentForTSF = false;
2899 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2900 ("0x%p TSFTextStore::ContentForTSFRef(): "
2901 "mContentForTSF={ mText=\"%s\" (Length()=%u), "
2902 "mLastCompositionString=\"%s\" (Length()=%u), "
2903 "mMinTextModifiedOffset=%u }",
2904 this,
2905 mContentForTSF.Text().Length() <= 40
2906 ? GetEscapedUTF8String(mContentForTSF.Text()).get()
2907 : "<omitted>",
2908 mContentForTSF.Text().Length(),
2909 GetEscapedUTF8String(mContentForTSF.LastCompositionString()).get(),
2910 mContentForTSF.LastCompositionString().Length(),
2911 mContentForTSF.MinTextModifiedOffset()));
2913 return mContentForTSF;
2916 bool TSFTextStore::CanAccessActualContentDirectly() const {
2917 if (!mContentForTSF.IsInitialized() || mSelectionForTSF.IsDirty()) {
2918 return true;
2921 // If the cached content has been changed by something except composition,
2922 // the content cache may be different from actual content.
2923 if (mPendingTextChangeData.IsValid() &&
2924 !mPendingTextChangeData.mCausedOnlyByComposition) {
2925 return false;
2928 // If the cached selection isn't changed, cached content and actual content
2929 // should be same.
2930 if (!mPendingSelectionChangeData.IsValid()) {
2931 return true;
2934 return mSelectionForTSF.EqualsExceptDirection(mPendingSelectionChangeData);
2937 bool TSFTextStore::GetCurrentText(nsAString& aTextContent) {
2938 if (mContentForTSF.IsInitialized()) {
2939 aTextContent = mContentForTSF.Text();
2940 return true;
2943 MOZ_ASSERT(!mDestroyed);
2944 MOZ_ASSERT(mWidget && !mWidget->Destroyed());
2946 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
2947 ("0x%p TSFTextStore::GetCurrentText(): "
2948 "retrieving text from the content...",
2949 this));
2951 WidgetQueryContentEvent queryText(true, eQueryTextContent, mWidget);
2952 queryText.InitForQueryTextContent(0, UINT32_MAX);
2953 mWidget->InitEvent(queryText);
2954 DispatchEvent(queryText);
2955 if (NS_WARN_IF(!queryText.mSucceeded)) {
2956 MOZ_LOG(sTextStoreLog, LogLevel::Error,
2957 ("0x%p TSFTextStore::GetCurrentText(), FAILED, due to "
2958 "eQueryTextContent failure",
2959 this));
2960 aTextContent.Truncate();
2961 return false;
2964 aTextContent = queryText.mReply.mString;
2965 return true;
2968 TSFTextStore::Selection& TSFTextStore::SelectionForTSFRef() {
2969 if (mSelectionForTSF.IsDirty()) {
2970 MOZ_ASSERT(!mDestroyed);
2971 // If the window has never been available, we should crash since working
2972 // with broken values may make TIP confused.
2973 if (!mWidget || mWidget->Destroyed()) {
2974 MOZ_CRASH();
2977 WidgetQueryContentEvent querySelection(true, eQuerySelectedText, mWidget);
2978 mWidget->InitEvent(querySelection);
2979 DispatchEvent(querySelection);
2980 if (NS_WARN_IF(!querySelection.mSucceeded)) {
2981 return mSelectionForTSF;
2984 mSelectionForTSF.SetSelection(
2985 querySelection.mReply.mOffset, querySelection.mReply.mString.Length(),
2986 querySelection.mReply.mReversed, querySelection.GetWritingMode());
2989 MOZ_LOG(
2990 sTextStoreLog, LogLevel::Debug,
2991 ("0x%p TSFTextStore::SelectionForTSFRef(): "
2992 "acpStart=%d, acpEnd=%d (length=%d), reverted=%s",
2993 this, mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
2994 mSelectionForTSF.Length(), GetBoolName(mSelectionForTSF.IsReversed())));
2996 return mSelectionForTSF;
2999 static HRESULT GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength) {
3000 RefPtr<ITfRangeACP> rangeACP;
3001 aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP));
3002 NS_ENSURE_TRUE(rangeACP, E_FAIL);
3003 return rangeACP->GetExtent(aStart, aLength);
3006 static TextRangeType GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE& aDisplayAttr) {
3007 switch (aDisplayAttr.bAttr) {
3008 case TF_ATTR_TARGET_CONVERTED:
3009 return TextRangeType::eSelectedClause;
3010 case TF_ATTR_CONVERTED:
3011 return TextRangeType::eConvertedClause;
3012 case TF_ATTR_TARGET_NOTCONVERTED:
3013 return TextRangeType::eSelectedRawClause;
3014 default:
3015 return TextRangeType::eRawClause;
3019 HRESULT
3020 TSFTextStore::GetDisplayAttribute(ITfProperty* aAttrProperty, ITfRange* aRange,
3021 TF_DISPLAYATTRIBUTE* aResult) {
3022 NS_ENSURE_TRUE(aAttrProperty, E_FAIL);
3023 NS_ENSURE_TRUE(aRange, E_FAIL);
3024 NS_ENSURE_TRUE(aResult, E_FAIL);
3026 HRESULT hr;
3028 if (MOZ_LOG_TEST(sTextStoreLog, LogLevel::Debug)) {
3029 LONG start = 0, length = 0;
3030 hr = GetRangeExtent(aRange, &start, &length);
3031 MOZ_LOG(
3032 sTextStoreLog, LogLevel::Debug,
3033 ("0x%p TSFTextStore::GetDisplayAttribute(): "
3034 "GetDisplayAttribute range=%ld-%ld (hr=%s)",
3035 this, start - mComposition.mStart,
3036 start - mComposition.mStart + length, GetCommonReturnValueName(hr)));
3039 VARIANT propValue;
3040 ::VariantInit(&propValue);
3041 hr = aAttrProperty->GetValue(TfEditCookie(mEditCookie), aRange, &propValue);
3042 if (FAILED(hr)) {
3043 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3044 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3045 "ITfProperty::GetValue() failed",
3046 this));
3047 return hr;
3049 if (VT_I4 != propValue.vt) {
3050 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3051 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3052 "ITfProperty::GetValue() returns non-VT_I4 value",
3053 this));
3054 ::VariantClear(&propValue);
3055 return E_FAIL;
3058 RefPtr<ITfCategoryMgr> categoryMgr = GetCategoryMgr();
3059 if (NS_WARN_IF(!categoryMgr)) {
3060 return E_FAIL;
3062 GUID guid;
3063 hr = categoryMgr->GetGUID(DWORD(propValue.lVal), &guid);
3064 ::VariantClear(&propValue);
3065 if (FAILED(hr)) {
3066 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3067 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3068 "ITfCategoryMgr::GetGUID() failed",
3069 this));
3070 return hr;
3073 RefPtr<ITfDisplayAttributeMgr> displayAttrMgr = GetDisplayAttributeMgr();
3074 if (NS_WARN_IF(!displayAttrMgr)) {
3075 return E_FAIL;
3077 RefPtr<ITfDisplayAttributeInfo> info;
3078 hr = displayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info),
3079 nullptr);
3080 if (FAILED(hr) || !info) {
3081 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3082 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3083 "ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed",
3084 this));
3085 return hr;
3088 hr = info->GetAttributeInfo(aResult);
3089 if (FAILED(hr)) {
3090 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3091 ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
3092 "ITfDisplayAttributeInfo::GetAttributeInfo() failed",
3093 this));
3094 return hr;
3097 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3098 ("0x%p TSFTextStore::GetDisplayAttribute() succeeded: "
3099 "Result={ %s }",
3100 this, GetDisplayAttrStr(*aResult).get()));
3101 return S_OK;
3104 HRESULT
3105 TSFTextStore::RestartCompositionIfNecessary(ITfRange* aRangeNew) {
3106 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3107 ("0x%p TSFTextStore::RestartCompositionIfNecessary("
3108 "aRangeNew=0x%p), mComposition.mView=0x%p",
3109 this, aRangeNew, mComposition.mView.get()));
3111 if (!mComposition.IsComposing()) {
3112 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3113 ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
3114 "due to no composition view",
3115 this));
3116 return E_FAIL;
3119 HRESULT hr;
3120 RefPtr<ITfCompositionView> pComposition(mComposition.mView);
3121 RefPtr<ITfRange> composingRange(aRangeNew);
3122 if (!composingRange) {
3123 hr = pComposition->GetRange(getter_AddRefs(composingRange));
3124 if (FAILED(hr)) {
3125 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3126 ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
3127 "FAILED due to pComposition->GetRange() failure",
3128 this));
3129 return hr;
3133 // Get starting offset of the composition
3134 LONG compStart = 0, compLength = 0;
3135 hr = GetRangeExtent(composingRange, &compStart, &compLength);
3136 if (FAILED(hr)) {
3137 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3138 ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
3139 "due to GetRangeExtent() failure",
3140 this));
3141 return hr;
3144 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3145 ("0x%p TSFTextStore::RestartCompositionIfNecessary(), "
3146 "range=%ld-%ld, mComposition={ mStart=%ld, mString.Length()=%lu }",
3147 this, compStart, compStart + compLength, mComposition.mStart,
3148 mComposition.mString.Length()));
3150 if (mComposition.mStart != compStart ||
3151 mComposition.mString.Length() != (ULONG)compLength) {
3152 // If the queried composition length is different from the length
3153 // of our composition string, OnUpdateComposition is being called
3154 // because a part of the original composition was committed.
3155 hr = RestartComposition(pComposition, composingRange);
3156 if (FAILED(hr)) {
3157 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3158 ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
3159 "FAILED due to RestartComposition() failure",
3160 this));
3161 return hr;
3165 MOZ_LOG(
3166 sTextStoreLog, LogLevel::Debug,
3167 ("0x%p TSFTextStore::RestartCompositionIfNecessary() succeeded", this));
3168 return S_OK;
3171 HRESULT
3172 TSFTextStore::RestartComposition(ITfCompositionView* aCompositionView,
3173 ITfRange* aNewRange) {
3174 Selection& selectionForTSF = SelectionForTSFRef();
3176 LONG newStart, newLength;
3177 HRESULT hr = GetRangeExtent(aNewRange, &newStart, &newLength);
3178 LONG newEnd = newStart + newLength;
3180 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3181 ("0x%p TSFTextStore::RestartComposition(aCompositionView=0x%p, "
3182 "aNewRange=0x%p { newStart=%d, newLength=%d }), "
3183 "mComposition={ mStart=%d, mCompositionString.Length()=%d }, "
3184 "selectionForTSF={ IsDirty()=%s, StartOffset()=%d, Length()=%d }",
3185 this, aCompositionView, aNewRange, newStart, newLength,
3186 mComposition.mStart, mComposition.mString.Length(),
3187 GetBoolName(selectionForTSF.IsDirty()),
3188 selectionForTSF.StartOffset(), selectionForTSF.Length()));
3190 if (selectionForTSF.IsDirty()) {
3191 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3192 ("0x%p TSFTextStore::RestartComposition() FAILED "
3193 "due to SelectionForTSFRef() failure",
3194 this));
3195 return E_FAIL;
3198 if (FAILED(hr)) {
3199 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3200 ("0x%p TSFTextStore::RestartComposition() FAILED "
3201 "due to GetRangeExtent() failure",
3202 this));
3203 return hr;
3206 // If the new range has no overlap with the crrent range, we just commit
3207 // the composition and restart new composition with the new range but
3208 // current selection range should be preserved.
3209 if (newStart >= mComposition.EndOffset() || newEnd <= mComposition.mStart) {
3210 RecordCompositionEndAction();
3211 RecordCompositionStartAction(aCompositionView, newStart, newLength, true);
3212 return S_OK;
3215 // If the new range has an overlap with the current one, we should not commit
3216 // the whole current range to avoid creating an odd undo transaction.
3217 // I.e., the overlapped range which is being composed should not appear in
3218 // undo transaction.
3220 // Backup current composition data and selection data.
3221 Composition oldComposition = mComposition;
3222 Selection oldSelection = selectionForTSF;
3224 // Commit only the part of composition.
3225 LONG keepComposingStartOffset = std::max(mComposition.mStart, newStart);
3226 LONG keepComposingEndOffset = std::min(mComposition.EndOffset(), newEnd);
3227 MOZ_ASSERT(
3228 keepComposingStartOffset <= keepComposingEndOffset,
3229 "Why keepComposingEndOffset is smaller than keepComposingStartOffset?");
3230 LONG keepComposingLength = keepComposingEndOffset - keepComposingStartOffset;
3231 // Remove the overlapped part from the commit string.
3232 nsAutoString commitString(mComposition.mString);
3233 commitString.Cut(keepComposingStartOffset - mComposition.mStart,
3234 keepComposingLength);
3235 // Update the composition string.
3236 Content& contentForTSF = ContentForTSFRef();
3237 if (!contentForTSF.IsInitialized()) {
3238 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3239 ("0x%p TSFTextStore::RestartComposition() FAILED "
3240 "due to ContentForTSFRef() failure",
3241 this));
3242 return E_FAIL;
3244 contentForTSF.ReplaceTextWith(mComposition.mStart,
3245 mComposition.mString.Length(), commitString);
3246 // Record a compositionupdate action for commit the part of composing string.
3247 PendingAction* action = LastOrNewPendingCompositionUpdate();
3248 action->mData = mComposition.mString;
3249 action->mRanges->Clear();
3250 // Note that we shouldn't append ranges when composition string
3251 // is empty because it may cause TextComposition confused.
3252 if (!action->mData.IsEmpty()) {
3253 TextRange caretRange;
3254 caretRange.mStartOffset = caretRange.mEndOffset =
3255 uint32_t(oldComposition.mStart + commitString.Length());
3256 caretRange.mRangeType = TextRangeType::eCaret;
3257 action->mRanges->AppendElement(caretRange);
3259 action->mIncomplete = false;
3261 // Record compositionend action.
3262 RecordCompositionEndAction();
3264 // Record compositionstart action only with the new start since this method
3265 // hasn't restored composing string yet.
3266 RecordCompositionStartAction(aCompositionView, newStart, 0, false);
3268 // Restore the latest text content and selection.
3269 contentForTSF.ReplaceSelectedTextWith(nsDependentSubstring(
3270 oldComposition.mString, keepComposingStartOffset - oldComposition.mStart,
3271 keepComposingLength));
3272 selectionForTSF = oldSelection;
3274 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3275 ("0x%p TSFTextStore::RestartComposition() succeeded, "
3276 "mComposition={ mStart=%d, mCompositionString.Length()=%d }, "
3277 "selectionForTSF={ IsDirty()=%s, StartOffset()=%d, Length()=%d }",
3278 this, mComposition.mStart, mComposition.mString.Length(),
3279 GetBoolName(selectionForTSF.IsDirty()),
3280 selectionForTSF.StartOffset(), selectionForTSF.Length()));
3282 return S_OK;
3285 static bool GetColor(const TF_DA_COLOR& aTSFColor, nscolor& aResult) {
3286 switch (aTSFColor.type) {
3287 case TF_CT_SYSCOLOR: {
3288 DWORD sysColor = ::GetSysColor(aTSFColor.nIndex);
3289 aResult =
3290 NS_RGB(GetRValue(sysColor), GetGValue(sysColor), GetBValue(sysColor));
3291 return true;
3293 case TF_CT_COLORREF:
3294 aResult = NS_RGB(GetRValue(aTSFColor.cr), GetGValue(aTSFColor.cr),
3295 GetBValue(aTSFColor.cr));
3296 return true;
3297 case TF_CT_NONE:
3298 default:
3299 return false;
3303 static bool GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle,
3304 TextRangeStyle::LineStyle& aTextRangeLineStyle) {
3305 switch (aTSFLineStyle) {
3306 case TF_LS_NONE:
3307 aTextRangeLineStyle = TextRangeStyle::LineStyle::None;
3308 return true;
3309 case TF_LS_SOLID:
3310 aTextRangeLineStyle = TextRangeStyle::LineStyle::Solid;
3311 return true;
3312 case TF_LS_DOT:
3313 aTextRangeLineStyle = TextRangeStyle::LineStyle::Dotted;
3314 return true;
3315 case TF_LS_DASH:
3316 aTextRangeLineStyle = TextRangeStyle::LineStyle::Dashed;
3317 return true;
3318 case TF_LS_SQUIGGLE:
3319 aTextRangeLineStyle = TextRangeStyle::LineStyle::Wavy;
3320 return true;
3321 default:
3322 return false;
3326 HRESULT
3327 TSFTextStore::RecordCompositionUpdateAction() {
3328 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3329 ("0x%p TSFTextStore::RecordCompositionUpdateAction(), "
3330 "mComposition={ mView=0x%p, mStart=%d, mString=\"%s\" "
3331 "(Length()=%d) }",
3332 this, mComposition.mView.get(), mComposition.mStart,
3333 GetEscapedUTF8String(mComposition.mString).get(),
3334 mComposition.mString.Length()));
3336 if (!mComposition.IsComposing()) {
3337 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3338 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3339 "due to no composition view",
3340 this));
3341 return E_FAIL;
3344 // Getting display attributes is *really* complicated!
3345 // We first get the context and the property objects to query for
3346 // attributes, but since a big range can have a variety of values for
3347 // the attribute, we have to find out all the ranges that have distinct
3348 // attribute values. Then we query for what the value represents through
3349 // the display attribute manager and translate that to TextRange to be
3350 // sent in eCompositionChange
3352 RefPtr<ITfProperty> attrPropetry;
3353 HRESULT hr =
3354 mContext->GetProperty(GUID_PROP_ATTRIBUTE, getter_AddRefs(attrPropetry));
3355 if (FAILED(hr) || !attrPropetry) {
3356 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3357 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3358 "due to mContext->GetProperty() failure",
3359 this));
3360 return FAILED(hr) ? hr : E_FAIL;
3363 RefPtr<ITfRange> composingRange;
3364 hr = mComposition.mView->GetRange(getter_AddRefs(composingRange));
3365 if (FAILED(hr)) {
3366 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3367 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3368 "FAILED due to mComposition.mView->GetRange() failure",
3369 this));
3370 return hr;
3373 RefPtr<IEnumTfRanges> enumRanges;
3374 hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie),
3375 getter_AddRefs(enumRanges), composingRange);
3376 if (FAILED(hr) || !enumRanges) {
3377 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3378 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3379 "due to attrPropetry->EnumRanges() failure",
3380 this));
3381 return FAILED(hr) ? hr : E_FAIL;
3384 // First, put the log of content and selection here.
3385 Selection& selectionForTSF = SelectionForTSFRef();
3386 if (selectionForTSF.IsDirty()) {
3387 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3388 ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
3389 "due to SelectionForTSFRef() failure",
3390 this));
3391 return E_FAIL;
3394 PendingAction* action = LastOrNewPendingCompositionUpdate();
3395 action->mData = mComposition.mString;
3396 // The ranges might already have been initialized, however, if this is
3397 // called again, that means we need to overwrite the ranges with current
3398 // information.
3399 action->mRanges->Clear();
3401 // Note that we shouldn't append ranges when composition string
3402 // is empty because it may cause TextComposition confused.
3403 if (!action->mData.IsEmpty()) {
3404 TextRange newRange;
3405 // No matter if we have display attribute info or not,
3406 // we always pass in at least one range to eCompositionChange
3407 newRange.mStartOffset = 0;
3408 newRange.mEndOffset = action->mData.Length();
3409 newRange.mRangeType = TextRangeType::eRawClause;
3410 action->mRanges->AppendElement(newRange);
3412 RefPtr<ITfRange> range;
3413 while (enumRanges->Next(1, getter_AddRefs(range), nullptr) == S_OK) {
3414 if (NS_WARN_IF(!range)) {
3415 break;
3418 LONG rangeStart = 0, rangeLength = 0;
3419 if (FAILED(GetRangeExtent(range, &rangeStart, &rangeLength))) {
3420 continue;
3422 // The range may include out of composition string. We should ignore
3423 // outside of the composition string.
3424 LONG start = std::min(std::max(rangeStart, mComposition.mStart),
3425 mComposition.EndOffset());
3426 LONG end =
3427 std::max(std::min(rangeStart + rangeLength, mComposition.EndOffset()),
3428 mComposition.mStart);
3429 LONG length = end - start;
3430 if (length < 0) {
3431 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3432 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3433 "ignores invalid range (%d-%d)",
3434 this, rangeStart - mComposition.mStart,
3435 rangeStart - mComposition.mStart + rangeLength));
3436 continue;
3438 if (!length) {
3439 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3440 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3441 "ignores a range due to outside of the composition or empty "
3442 "(%d-%d)",
3443 this, rangeStart - mComposition.mStart,
3444 rangeStart - mComposition.mStart + rangeLength));
3445 continue;
3448 TextRange newRange;
3449 newRange.mStartOffset = uint32_t(start - mComposition.mStart);
3450 // The end of the last range in the array is
3451 // always kept at the end of composition
3452 newRange.mEndOffset = mComposition.mString.Length();
3454 TF_DISPLAYATTRIBUTE attr;
3455 hr = GetDisplayAttribute(attrPropetry, range, &attr);
3456 if (FAILED(hr)) {
3457 newRange.mRangeType = TextRangeType::eRawClause;
3458 } else {
3459 newRange.mRangeType = GetGeckoSelectionValue(attr);
3460 if (GetColor(attr.crText, newRange.mRangeStyle.mForegroundColor)) {
3461 newRange.mRangeStyle.mDefinedStyles |=
3462 TextRangeStyle::DEFINED_FOREGROUND_COLOR;
3464 if (GetColor(attr.crBk, newRange.mRangeStyle.mBackgroundColor)) {
3465 newRange.mRangeStyle.mDefinedStyles |=
3466 TextRangeStyle::DEFINED_BACKGROUND_COLOR;
3468 if (GetColor(attr.crLine, newRange.mRangeStyle.mUnderlineColor)) {
3469 newRange.mRangeStyle.mDefinedStyles |=
3470 TextRangeStyle::DEFINED_UNDERLINE_COLOR;
3472 if (GetLineStyle(attr.lsStyle, newRange.mRangeStyle.mLineStyle)) {
3473 newRange.mRangeStyle.mDefinedStyles |=
3474 TextRangeStyle::DEFINED_LINESTYLE;
3475 newRange.mRangeStyle.mIsBoldLine = attr.fBoldLine != 0;
3479 TextRange& lastRange = action->mRanges->LastElement();
3480 if (lastRange.mStartOffset == newRange.mStartOffset) {
3481 // Replace range if last range is the same as this one
3482 // So that ranges don't overlap and confuse the editor
3483 lastRange = newRange;
3484 } else {
3485 lastRange.mEndOffset = newRange.mStartOffset;
3486 action->mRanges->AppendElement(newRange);
3490 // We need to hack for Korean Input System which is Korean standard TIP.
3491 // It sets no change style to IME selection (the selection is always only
3492 // one). So, the composition string looks like normal (or committed)
3493 // string. At this time, current selection range is same as the
3494 // composition string range. Other applications set a wide caret which
3495 // covers the composition string, however, Gecko doesn't support the wide
3496 // caret drawing now (Gecko doesn't support XOR drawing), unfortunately.
3497 // For now, we should change the range style to undefined.
3498 if (!selectionForTSF.IsCollapsed() && action->mRanges->Length() == 1) {
3499 TextRange& range = action->mRanges->ElementAt(0);
3500 LONG start = selectionForTSF.MinOffset();
3501 LONG end = selectionForTSF.MaxOffset();
3502 if ((LONG)range.mStartOffset == start - mComposition.mStart &&
3503 (LONG)range.mEndOffset == end - mComposition.mStart &&
3504 range.mRangeStyle.IsNoChangeStyle()) {
3505 range.mRangeStyle.Clear();
3506 // The looks of selected type is better than others.
3507 range.mRangeType = TextRangeType::eSelectedRawClause;
3511 // The caret position has to be collapsed.
3512 uint32_t caretPosition = static_cast<uint32_t>(selectionForTSF.MaxOffset() -
3513 mComposition.mStart);
3515 // If caret is in the target clause and it doesn't have specific style,
3516 // the target clause will be painted as normal selection range. Since
3517 // caret shouldn't be in selection range on Windows, we shouldn't append
3518 // caret range in such case.
3519 const TextRange* targetClause = action->mRanges->GetTargetClause();
3520 if (!targetClause || targetClause->mRangeStyle.IsDefined() ||
3521 caretPosition < targetClause->mStartOffset ||
3522 caretPosition > targetClause->mEndOffset) {
3523 TextRange caretRange;
3524 caretRange.mStartOffset = caretRange.mEndOffset = caretPosition;
3525 caretRange.mRangeType = TextRangeType::eCaret;
3526 action->mRanges->AppendElement(caretRange);
3530 action->mIncomplete = false;
3532 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3533 ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
3534 "succeeded",
3535 this));
3537 return S_OK;
3540 HRESULT
3541 TSFTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection,
3542 bool aDispatchCompositionChangeEvent) {
3543 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
3544 ("0x%p TSFTextStore::SetSelectionInternal(pSelection={ "
3545 "acpStart=%ld, acpEnd=%ld, style={ ase=%s, fInterimChar=%s} }, "
3546 "aDispatchCompositionChangeEvent=%s), mComposition.IsComposing()=%s",
3547 this, pSelection->acpStart, pSelection->acpEnd,
3548 GetActiveSelEndName(pSelection->style.ase),
3549 GetBoolName(pSelection->style.fInterimChar),
3550 GetBoolName(aDispatchCompositionChangeEvent),
3551 GetBoolName(mComposition.IsComposing())));
3553 MOZ_ASSERT(IsReadWriteLocked());
3555 Selection& selectionForTSF = SelectionForTSFRef();
3556 if (selectionForTSF.IsDirty()) {
3557 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3558 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3559 "SelectionForTSFRef() failure",
3560 this));
3561 return E_FAIL;
3564 MaybeDispatchKeyboardEventAsProcessedByIME();
3565 if (mDestroyed) {
3566 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3567 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3568 "destroyed during dispatching a keyboard event",
3569 this));
3570 return E_FAIL;
3573 // If actually the range is not changing, we should do nothing.
3574 // Perhaps, we can ignore the difference change because it must not be
3575 // important for following edit.
3576 if (selectionForTSF.EqualsExceptDirection(*pSelection)) {
3577 MOZ_LOG(sTextStoreLog, LogLevel::Warning,
3578 ("0x%p TSFTextStore::SetSelectionInternal() Succeeded but "
3579 "did nothing because the selection range isn't changing",
3580 this));
3581 selectionForTSF.SetSelection(*pSelection);
3582 return S_OK;
3585 if (mComposition.IsComposing()) {
3586 if (aDispatchCompositionChangeEvent) {
3587 HRESULT hr = RestartCompositionIfNecessary();
3588 if (FAILED(hr)) {
3589 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3590 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3591 "RestartCompositionIfNecessary() failure",
3592 this));
3593 return hr;
3596 if (pSelection->acpStart < mComposition.mStart ||
3597 pSelection->acpEnd > mComposition.EndOffset()) {
3598 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3599 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3600 "the selection being out of the composition string",
3601 this));
3602 return TS_E_INVALIDPOS;
3604 // Emulate selection during compositions
3605 selectionForTSF.SetSelection(*pSelection);
3606 if (aDispatchCompositionChangeEvent) {
3607 HRESULT hr = RecordCompositionUpdateAction();
3608 if (FAILED(hr)) {
3609 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3610 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3611 "RecordCompositionUpdateAction() failure",
3612 this));
3613 return hr;
3616 return S_OK;
3619 TS_SELECTION_ACP selectionInContent(*pSelection);
3621 // If mContentForTSF caches old contents which is now different from
3622 // actual contents, we need some complicated hack here...
3623 // Note that this hack assumes that this is used for reconversion.
3624 if (mContentForTSF.IsInitialized() && mPendingTextChangeData.IsValid() &&
3625 !mPendingTextChangeData.mCausedOnlyByComposition) {
3626 uint32_t startOffset = static_cast<uint32_t>(selectionInContent.acpStart);
3627 uint32_t endOffset = static_cast<uint32_t>(selectionInContent.acpEnd);
3628 if (mPendingTextChangeData.mStartOffset >= endOffset) {
3629 // Setting selection before any changed ranges is fine.
3630 } else if (mPendingTextChangeData.mRemovedEndOffset <= startOffset) {
3631 // Setting selection after removed range is fine with following
3632 // adjustment.
3633 selectionInContent.acpStart += mPendingTextChangeData.Difference();
3634 selectionInContent.acpEnd += mPendingTextChangeData.Difference();
3635 } else if (startOffset == endOffset) {
3636 // Moving caret position may be fine in most cases even if the insertion
3637 // point has already gone but in this case, composition will be inserted
3638 // to unexpected position, though.
3639 // It seems that moving caret into middle of the new text is odd.
3640 // Perhaps, end of it is expected by users in most cases.
3641 selectionInContent.acpStart = mPendingTextChangeData.mAddedEndOffset;
3642 selectionInContent.acpEnd = selectionInContent.acpStart;
3643 } else {
3644 // Otherwise, i.e., setting range has already gone, we cannot set
3645 // selection properly.
3646 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3647 ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
3648 "there is unknown content change",
3649 this));
3650 return E_FAIL;
3654 CompleteLastActionIfStillIncomplete();
3655 PendingAction* action = mPendingActions.AppendElement();
3656 action->mType = PendingAction::Type::eSetSelection;
3657 action->mSelectionStart = selectionInContent.acpStart;
3658 action->mSelectionLength =
3659 selectionInContent.acpEnd - selectionInContent.acpStart;
3660 action->mSelectionReversed = (selectionInContent.style.ase == TS_AE_START);
3662 // Use TSF specified selection for updating mSelectionForTSF.
3663 selectionForTSF.SetSelection(*pSelection);
3665 return S_OK;
3668 STDMETHODIMP
3669 TSFTextStore::SetSelection(ULONG ulCount, const TS_SELECTION_ACP* pSelection) {
3670 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3671 ("0x%p TSFTextStore::SetSelection(ulCount=%lu, pSelection=%p { "
3672 "acpStart=%ld, acpEnd=%ld, style={ ase=%s, fInterimChar=%s } }), "
3673 "mComposition.IsComposing()=%s",
3674 this, ulCount, pSelection, pSelection ? pSelection->acpStart : 0,
3675 pSelection ? pSelection->acpEnd : 0,
3676 pSelection ? GetActiveSelEndName(pSelection->style.ase) : "",
3677 pSelection ? GetBoolName(pSelection->style.fInterimChar) : "",
3678 GetBoolName(mComposition.IsComposing())));
3680 if (!IsReadWriteLocked()) {
3681 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3682 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3683 "not locked (read-write)",
3684 this));
3685 return TS_E_NOLOCK;
3687 if (ulCount != 1) {
3688 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3689 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3690 "trying setting multiple selection",
3691 this));
3692 return E_INVALIDARG;
3694 if (!pSelection) {
3695 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3696 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3697 "null argument",
3698 this));
3699 return E_INVALIDARG;
3702 HRESULT hr = SetSelectionInternal(pSelection, true);
3703 if (FAILED(hr)) {
3704 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3705 ("0x%p TSFTextStore::SetSelection() FAILED due to "
3706 "SetSelectionInternal() failure",
3707 this));
3708 } else {
3709 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3710 ("0x%p TSFTextStore::SetSelection() succeeded", this));
3712 return hr;
3715 STDMETHODIMP
3716 TSFTextStore::GetText(LONG acpStart, LONG acpEnd, WCHAR* pchPlain,
3717 ULONG cchPlainReq, ULONG* pcchPlainOut,
3718 TS_RUNINFO* prgRunInfo, ULONG ulRunInfoReq,
3719 ULONG* pulRunInfoOut, LONG* pacpNext) {
3720 MOZ_LOG(
3721 sTextStoreLog, LogLevel::Info,
3722 ("0x%p TSFTextStore::GetText(acpStart=%ld, acpEnd=%ld, pchPlain=0x%p, "
3723 "cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, ulRunInfoReq=%lu, "
3724 "pulRunInfoOut=0x%p, pacpNext=0x%p), mComposition={ mStart=%ld, "
3725 "mString.Length()=%lu, IsComposing()=%s }",
3726 this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, prgRunInfo,
3727 ulRunInfoReq, pulRunInfoOut, pacpNext, mComposition.mStart,
3728 mComposition.mString.Length(), GetBoolName(mComposition.IsComposing())));
3730 if (!IsReadLocked()) {
3731 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3732 ("0x%p TSFTextStore::GetText() FAILED due to "
3733 "not locked (read)",
3734 this));
3735 return TS_E_NOLOCK;
3738 if (!pcchPlainOut || (!pchPlain && !prgRunInfo) ||
3739 !cchPlainReq != !pchPlain || !ulRunInfoReq != !prgRunInfo) {
3740 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3741 ("0x%p TSFTextStore::GetText() FAILED due to "
3742 "invalid argument",
3743 this));
3744 return E_INVALIDARG;
3747 if (acpStart < 0 || acpEnd < -1 || (acpEnd != -1 && acpStart > acpEnd)) {
3748 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3749 ("0x%p TSFTextStore::GetText() FAILED due to "
3750 "invalid position",
3751 this));
3752 return TS_E_INVALIDPOS;
3755 // Making sure to null-terminate string just to be on the safe side
3756 *pcchPlainOut = 0;
3757 if (pchPlain && cchPlainReq) *pchPlain = 0;
3758 if (pulRunInfoOut) *pulRunInfoOut = 0;
3759 if (pacpNext) *pacpNext = acpStart;
3760 if (prgRunInfo && ulRunInfoReq) {
3761 prgRunInfo->uCount = 0;
3762 prgRunInfo->type = TS_RT_PLAIN;
3765 Content& contentForTSF = ContentForTSFRef();
3766 if (!contentForTSF.IsInitialized()) {
3767 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3768 ("0x%p TSFTextStore::GetText() FAILED due to "
3769 "ContentForTSFRef() failure",
3770 this));
3771 return E_FAIL;
3773 if (contentForTSF.Text().Length() < static_cast<uint32_t>(acpStart)) {
3774 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3775 ("0x%p TSFTextStore::GetText() FAILED due to "
3776 "acpStart is larger offset than the actual text length",
3777 this));
3778 return TS_E_INVALIDPOS;
3780 if (acpEnd != -1 &&
3781 contentForTSF.Text().Length() < static_cast<uint32_t>(acpEnd)) {
3782 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3783 ("0x%p TSFTextStore::GetText() FAILED due to "
3784 "acpEnd is larger offset than the actual text length",
3785 this));
3786 return TS_E_INVALIDPOS;
3788 uint32_t length = (acpEnd == -1) ? contentForTSF.Text().Length() -
3789 static_cast<uint32_t>(acpStart)
3790 : static_cast<uint32_t>(acpEnd - acpStart);
3791 if (cchPlainReq && cchPlainReq - 1 < length) {
3792 length = cchPlainReq - 1;
3794 if (length) {
3795 if (pchPlain && cchPlainReq) {
3796 const char16_t* startChar =
3797 contentForTSF.Text().BeginReading() + acpStart;
3798 memcpy(pchPlain, startChar, length * sizeof(*pchPlain));
3799 pchPlain[length] = 0;
3800 *pcchPlainOut = length;
3802 if (prgRunInfo && ulRunInfoReq) {
3803 prgRunInfo->uCount = length;
3804 prgRunInfo->type = TS_RT_PLAIN;
3805 if (pulRunInfoOut) *pulRunInfoOut = 1;
3807 if (pacpNext) *pacpNext = acpStart + length;
3810 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3811 ("0x%p TSFTextStore::GetText() succeeded: pcchPlainOut=0x%p, "
3812 "*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, "
3813 "*pacpNext=%ld)",
3814 this, pcchPlainOut, prgRunInfo ? prgRunInfo->uCount : 0,
3815 prgRunInfo ? GetTextRunTypeName(prgRunInfo->type) : "N/A",
3816 pulRunInfoOut ? *pulRunInfoOut : 0, pacpNext ? *pacpNext : 0));
3817 return S_OK;
3820 STDMETHODIMP
3821 TSFTextStore::SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd,
3822 const WCHAR* pchText, ULONG cch, TS_TEXTCHANGE* pChange) {
3823 MOZ_LOG(
3824 sTextStoreLog, LogLevel::Info,
3825 ("0x%p TSFTextStore::SetText(dwFlags=%s, acpStart=%ld, "
3826 "acpEnd=%ld, pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), "
3827 "mComposition.IsComposing()=%s",
3828 this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" : "not-specified",
3829 acpStart, acpEnd, pchText,
3830 pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "", cch,
3831 pChange, GetBoolName(mComposition.IsComposing())));
3833 // Per SDK documentation, and since we don't have better
3834 // ways to do this, this method acts as a helper to
3835 // call SetSelection followed by InsertTextAtSelection
3836 if (!IsReadWriteLocked()) {
3837 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3838 ("0x%p TSFTextStore::SetText() FAILED due to "
3839 "not locked (read)",
3840 this));
3841 return TS_E_NOLOCK;
3844 TS_SELECTION_ACP selection;
3845 selection.acpStart = acpStart;
3846 selection.acpEnd = acpEnd;
3847 selection.style.ase = TS_AE_END;
3848 selection.style.fInterimChar = 0;
3849 // Set selection to desired range
3850 HRESULT hr = SetSelectionInternal(&selection);
3851 if (FAILED(hr)) {
3852 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3853 ("0x%p TSFTextStore::SetText() FAILED due to "
3854 "SetSelectionInternal() failure",
3855 this));
3856 return hr;
3858 // Replace just selected text
3859 if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
3860 pChange)) {
3861 MOZ_LOG(sTextStoreLog, LogLevel::Error,
3862 ("0x%p TSFTextStore::SetText() FAILED due to "
3863 "InsertTextAtSelectionInternal() failure",
3864 this));
3865 return E_FAIL;
3868 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3869 ("0x%p TSFTextStore::SetText() succeeded: pChange={ "
3870 "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
3871 this, pChange ? pChange->acpStart : 0,
3872 pChange ? pChange->acpOldEnd : 0, pChange ? pChange->acpNewEnd : 0));
3873 return S_OK;
3876 STDMETHODIMP
3877 TSFTextStore::GetFormattedText(LONG acpStart, LONG acpEnd,
3878 IDataObject** ppDataObject) {
3879 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3880 ("0x%p TSFTextStore::GetFormattedText() called "
3881 "but not supported (E_NOTIMPL)",
3882 this));
3884 // no support for formatted text
3885 return E_NOTIMPL;
3888 STDMETHODIMP
3889 TSFTextStore::GetEmbedded(LONG acpPos, REFGUID rguidService, REFIID riid,
3890 IUnknown** ppunk) {
3891 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3892 ("0x%p TSFTextStore::GetEmbedded() called "
3893 "but not supported (E_NOTIMPL)",
3894 this));
3896 // embedded objects are not supported
3897 return E_NOTIMPL;
3900 STDMETHODIMP
3901 TSFTextStore::QueryInsertEmbedded(const GUID* pguidService,
3902 const FORMATETC* pFormatEtc,
3903 BOOL* pfInsertable) {
3904 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3905 ("0x%p TSFTextStore::QueryInsertEmbedded() called "
3906 "but not supported, *pfInsertable=FALSE (S_OK)",
3907 this));
3909 // embedded objects are not supported
3910 *pfInsertable = FALSE;
3911 return S_OK;
3914 STDMETHODIMP
3915 TSFTextStore::InsertEmbedded(DWORD dwFlags, LONG acpStart, LONG acpEnd,
3916 IDataObject* pDataObject, TS_TEXTCHANGE* pChange) {
3917 MOZ_LOG(sTextStoreLog, LogLevel::Info,
3918 ("0x%p TSFTextStore::InsertEmbedded() called "
3919 "but not supported (E_NOTIMPL)",
3920 this));
3922 // embedded objects are not supported
3923 return E_NOTIMPL;
3926 // static
3927 bool TSFTextStore::ShouldSetInputScopeOfURLBarToDefault() {
3928 // FYI: Google Japanese Input may be an IMM-IME. If it's installed on
3929 // Win7, it's always IMM-IME. Otherwise, basically, it's a TIP.
3930 // However, if it's installed on Win7 and has not been updated yet
3931 // after the OS is upgraded to Win8 or later, it's still an IMM-IME.
3932 // Therefore, we also need to check with IMMHandler here.
3933 if (!TSFPrefs::ShouldSetInputScopeOfURLBarToDefault()) {
3934 return false;
3937 if (IMMHandler::IsGoogleJapaneseInputActive()) {
3938 return true;
3941 switch (TSFStaticSink::ActiveTIP()) {
3942 case TextInputProcessorID::eMicrosoftIMEForJapanese:
3943 case TextInputProcessorID::eGoogleJapaneseInput:
3944 case TextInputProcessorID::eMicrosoftBopomofo:
3945 case TextInputProcessorID::eMicrosoftChangJie:
3946 case TextInputProcessorID::eMicrosoftPhonetic:
3947 case TextInputProcessorID::eMicrosoftQuick:
3948 case TextInputProcessorID::eMicrosoftNewChangJie:
3949 case TextInputProcessorID::eMicrosoftNewPhonetic:
3950 case TextInputProcessorID::eMicrosoftNewQuick:
3951 case TextInputProcessorID::eMicrosoftPinyin:
3952 case TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle:
3953 case TextInputProcessorID::eMicrosoftOldHangul:
3954 case TextInputProcessorID::eMicrosoftWubi:
3955 return true;
3956 case TextInputProcessorID::eMicrosoftIMEForKorean:
3957 return IsWin8OrLater();
3958 default:
3959 return false;
3963 void TSFTextStore::SetInputScope(const nsString& aHTMLInputType,
3964 const nsString& aHTMLInputInputMode,
3965 bool aInPrivateBrowsing) {
3966 mInputScopes.Clear();
3968 if (aInPrivateBrowsing) {
3969 mInputScopes.AppendElement(IS_PRIVATE);
3972 if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) {
3973 if (aHTMLInputInputMode.EqualsLiteral("url")) {
3974 mInputScopes.AppendElement(IS_URL);
3975 } else if (aHTMLInputInputMode.EqualsLiteral("mozAwesomebar")) {
3976 // Even if Awesomebar has focus, user may not input URL directly.
3977 // However, on-screen keyboard for URL should be shown because it has
3978 // some useful additional keys like ".com" and they are not hindrances
3979 // even when inputting non-URL text, e.g., words to search something in
3980 // the web. On the other hand, a lot of Microsoft's IMEs and Google
3981 // Japanese Input make their open state "closed" automatically if we
3982 // notify them of URL as the input scope. However, this is very annoying
3983 // for the users when they try to input some words to search the web or
3984 // bookmark/history items. Therefore, if they are active, we need to
3985 // notify them of the default input scope for avoiding this issue.
3986 if (TSFTextStore::ShouldSetInputScopeOfURLBarToDefault()) {
3987 return;
3989 // Don't append IS_SEARCH here for showing on-screen keyboard for URL.
3990 mInputScopes.AppendElement(IS_URL);
3991 } else if (aHTMLInputInputMode.EqualsLiteral("email")) {
3992 mInputScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
3993 } else if (aHTMLInputType.EqualsLiteral("tel")) {
3994 mInputScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
3995 mInputScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
3996 } else if (aHTMLInputType.EqualsLiteral("numeric")) {
3997 mInputScopes.AppendElement(IS_NUMBER);
3999 return;
4002 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html
4003 if (aHTMLInputType.EqualsLiteral("url")) {
4004 mInputScopes.AppendElement(IS_URL);
4005 } else if (aHTMLInputType.EqualsLiteral("search")) {
4006 mInputScopes.AppendElement(IS_SEARCH);
4007 } else if (aHTMLInputType.EqualsLiteral("email")) {
4008 mInputScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
4009 } else if (aHTMLInputType.EqualsLiteral("password")) {
4010 mInputScopes.AppendElement(IS_PASSWORD);
4011 } else if (aHTMLInputType.EqualsLiteral("datetime") ||
4012 aHTMLInputType.EqualsLiteral("datetime-local")) {
4013 mInputScopes.AppendElement(IS_DATE_FULLDATE);
4014 mInputScopes.AppendElement(IS_TIME_FULLTIME);
4015 } else if (aHTMLInputType.EqualsLiteral("date") ||
4016 aHTMLInputType.EqualsLiteral("month") ||
4017 aHTMLInputType.EqualsLiteral("week")) {
4018 mInputScopes.AppendElement(IS_DATE_FULLDATE);
4019 } else if (aHTMLInputType.EqualsLiteral("time")) {
4020 mInputScopes.AppendElement(IS_TIME_FULLTIME);
4021 } else if (aHTMLInputType.EqualsLiteral("tel")) {
4022 mInputScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
4023 mInputScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
4024 } else if (aHTMLInputType.EqualsLiteral("number")) {
4025 mInputScopes.AppendElement(IS_NUMBER);
4029 int32_t TSFTextStore::GetRequestedAttrIndex(const TS_ATTRID& aAttrID) {
4030 if (IsEqualGUID(aAttrID, GUID_PROP_INPUTSCOPE)) {
4031 return eInputScope;
4033 if (IsEqualGUID(aAttrID, TSATTRID_Text_VerticalWriting)) {
4034 return eTextVerticalWriting;
4036 if (IsEqualGUID(aAttrID, TSATTRID_Text_Orientation)) {
4037 return eTextOrientation;
4039 return eNotSupported;
4042 TS_ATTRID
4043 TSFTextStore::GetAttrID(int32_t aIndex) {
4044 switch (aIndex) {
4045 case eInputScope:
4046 return GUID_PROP_INPUTSCOPE;
4047 case eTextVerticalWriting:
4048 return TSATTRID_Text_VerticalWriting;
4049 case eTextOrientation:
4050 return TSATTRID_Text_Orientation;
4051 default:
4052 MOZ_CRASH("Invalid index? Or not implemented yet?");
4053 return GUID_NULL;
4057 HRESULT
4058 TSFTextStore::HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
4059 const TS_ATTRID* aFilterAttrs) {
4060 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4061 ("0x%p TSFTextStore::HandleRequestAttrs(aFlags=%s, "
4062 "aFilterCount=%u)",
4063 this, GetFindFlagName(aFlags).get(), aFilterCount));
4065 // This is a little weird! RequestSupportedAttrs gives us advanced notice
4066 // of a support query via RetrieveRequestedAttrs for a specific attribute.
4067 // RetrieveRequestedAttrs needs to return valid data for all attributes we
4068 // support, but the text service will only want the input scope object
4069 // returned in RetrieveRequestedAttrs if the dwFlags passed in here contains
4070 // TS_ATTR_FIND_WANT_VALUE.
4071 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
4072 mRequestedAttrs[i] = false;
4074 mRequestedAttrValues = !!(aFlags & TS_ATTR_FIND_WANT_VALUE);
4076 for (uint32_t i = 0; i < aFilterCount; i++) {
4077 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4078 ("0x%p TSFTextStore::HandleRequestAttrs(), "
4079 "requested attr=%s",
4080 this, GetGUIDNameStrWithTable(aFilterAttrs[i]).get()));
4081 int32_t index = GetRequestedAttrIndex(aFilterAttrs[i]);
4082 if (index != eNotSupported) {
4083 mRequestedAttrs[index] = true;
4086 return S_OK;
4089 STDMETHODIMP
4090 TSFTextStore::RequestSupportedAttrs(DWORD dwFlags, ULONG cFilterAttrs,
4091 const TS_ATTRID* paFilterAttrs) {
4092 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4093 ("0x%p TSFTextStore::RequestSupportedAttrs(dwFlags=%s, "
4094 "cFilterAttrs=%lu)",
4095 this, GetFindFlagName(dwFlags).get(), cFilterAttrs));
4097 return HandleRequestAttrs(dwFlags, cFilterAttrs, paFilterAttrs);
4100 STDMETHODIMP
4101 TSFTextStore::RequestAttrsAtPosition(LONG acpPos, ULONG cFilterAttrs,
4102 const TS_ATTRID* paFilterAttrs,
4103 DWORD dwFlags) {
4104 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4105 ("0x%p TSFTextStore::RequestAttrsAtPosition(acpPos=%ld, "
4106 "cFilterAttrs=%lu, dwFlags=%s)",
4107 this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
4109 return HandleRequestAttrs(dwFlags | TS_ATTR_FIND_WANT_VALUE, cFilterAttrs,
4110 paFilterAttrs);
4113 STDMETHODIMP
4114 TSFTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos,
4115 ULONG cFilterAttrs,
4116 const TS_ATTRID* paFilterAttr,
4117 DWORD dwFlags) {
4118 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4119 ("0x%p TSFTextStore::RequestAttrsTransitioningAtPosition("
4120 "acpPos=%ld, cFilterAttrs=%lu, dwFlags=%s) called but not supported "
4121 "(S_OK)",
4122 this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
4124 // no per character attributes defined
4125 return S_OK;
4128 STDMETHODIMP
4129 TSFTextStore::FindNextAttrTransition(LONG acpStart, LONG acpHalt,
4130 ULONG cFilterAttrs,
4131 const TS_ATTRID* paFilterAttrs,
4132 DWORD dwFlags, LONG* pacpNext,
4133 BOOL* pfFound, LONG* plFoundOffset) {
4134 if (!pacpNext || !pfFound || !plFoundOffset) {
4135 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4136 (" 0x%p TSFTextStore::FindNextAttrTransition() FAILED due to "
4137 "null argument",
4138 this));
4139 return E_INVALIDARG;
4142 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4143 ("0x%p TSFTextStore::FindNextAttrTransition() called "
4144 "but not supported (S_OK)",
4145 this));
4147 // no per character attributes defined
4148 *pacpNext = *plFoundOffset = acpHalt;
4149 *pfFound = FALSE;
4150 return S_OK;
4153 STDMETHODIMP
4154 TSFTextStore::RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL* paAttrVals,
4155 ULONG* pcFetched) {
4156 if (!pcFetched || !paAttrVals) {
4157 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4158 ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
4159 "null argument",
4160 this));
4161 return E_INVALIDARG;
4164 ULONG expectedCount = 0;
4165 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
4166 if (mRequestedAttrs[i]) {
4167 expectedCount++;
4170 if (ulCount < expectedCount) {
4171 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4172 ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
4173 "not enough count ulCount=%u, expectedCount=%u",
4174 this, ulCount, expectedCount));
4175 return E_INVALIDARG;
4178 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4179 ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
4180 "ulCount=%d, mRequestedAttrValues=%s",
4181 this, ulCount, GetBoolName(mRequestedAttrValues)));
4183 int32_t count = 0;
4184 for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
4185 if (!mRequestedAttrs[i]) {
4186 continue;
4188 mRequestedAttrs[i] = false;
4190 TS_ATTRID attrID = GetAttrID(i);
4192 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4193 ("0x%p TSFTextStore::RetrieveRequestedAttrs() for %s", this,
4194 GetGUIDNameStrWithTable(attrID).get()));
4196 paAttrVals[count].idAttr = attrID;
4197 paAttrVals[count].dwOverlapId = 0;
4199 if (!mRequestedAttrValues) {
4200 paAttrVals[count].varValue.vt = VT_EMPTY;
4201 } else {
4202 switch (i) {
4203 case eInputScope: {
4204 paAttrVals[count].varValue.vt = VT_UNKNOWN;
4205 RefPtr<IUnknown> inputScope = new InputScopeImpl(mInputScopes);
4206 paAttrVals[count].varValue.punkVal = inputScope.forget().take();
4207 break;
4209 case eTextVerticalWriting: {
4210 Selection& selectionForTSF = SelectionForTSFRef();
4211 paAttrVals[count].varValue.vt = VT_BOOL;
4212 paAttrVals[count].varValue.boolVal =
4213 !selectionForTSF.IsDirty() &&
4214 selectionForTSF.GetWritingMode().IsVertical()
4215 ? VARIANT_TRUE
4216 : VARIANT_FALSE;
4217 break;
4219 case eTextOrientation: {
4220 Selection& selectionForTSF = SelectionForTSFRef();
4221 paAttrVals[count].varValue.vt = VT_I4;
4222 paAttrVals[count].varValue.lVal =
4223 !selectionForTSF.IsDirty() &&
4224 selectionForTSF.GetWritingMode().IsVertical()
4225 ? 2700
4226 : 0;
4227 break;
4229 default:
4230 MOZ_CRASH("Invalid index? Or not implemented yet?");
4231 break;
4234 count++;
4237 mRequestedAttrValues = false;
4239 if (count) {
4240 *pcFetched = count;
4241 return S_OK;
4244 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4245 ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
4246 "for unknown TS_ATTRVAL, *pcFetched=0 (S_OK)",
4247 this));
4249 paAttrVals->dwOverlapId = 0;
4250 paAttrVals->varValue.vt = VT_EMPTY;
4251 *pcFetched = 0;
4252 return S_OK;
4255 STDMETHODIMP
4256 TSFTextStore::GetEndACP(LONG* pacp) {
4257 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4258 ("0x%p TSFTextStore::GetEndACP(pacp=0x%p)", this, pacp));
4260 if (!IsReadLocked()) {
4261 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4262 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4263 "not locked (read)",
4264 this));
4265 return TS_E_NOLOCK;
4268 if (!pacp) {
4269 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4270 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4271 "null argument",
4272 this));
4273 return E_INVALIDARG;
4276 Content& contentForTSF = ContentForTSFRef();
4277 if (!contentForTSF.IsInitialized()) {
4278 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4279 ("0x%p TSFTextStore::GetEndACP() FAILED due to "
4280 "ContentForTSFRef() failure",
4281 this));
4282 return E_FAIL;
4284 *pacp = static_cast<LONG>(contentForTSF.Text().Length());
4285 return S_OK;
4288 STDMETHODIMP
4289 TSFTextStore::GetActiveView(TsViewCookie* pvcView) {
4290 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4291 ("0x%p TSFTextStore::GetActiveView(pvcView=0x%p)", this, pvcView));
4293 if (!pvcView) {
4294 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4295 ("0x%p TSFTextStore::GetActiveView() FAILED due to "
4296 "null argument",
4297 this));
4298 return E_INVALIDARG;
4301 *pvcView = TEXTSTORE_DEFAULT_VIEW;
4303 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4304 ("0x%p TSFTextStore::GetActiveView() succeeded: *pvcView=%ld", this,
4305 *pvcView));
4306 return S_OK;
4309 STDMETHODIMP
4310 TSFTextStore::GetACPFromPoint(TsViewCookie vcView, const POINT* pt,
4311 DWORD dwFlags, LONG* pacp) {
4312 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4313 ("0x%p TSFTextStore::GetACPFromPoint(pvcView=%d, pt=%p (x=%d, "
4314 "y=%d), dwFlags=%s, pacp=%p, mDeferNotifyingTSF=%s, "
4315 "mWaitingQueryLayout=%s",
4316 this, vcView, pt, pt ? pt->x : 0, pt ? pt->y : 0,
4317 GetACPFromPointFlagName(dwFlags).get(), pacp,
4318 GetBoolName(mDeferNotifyingTSF), GetBoolName(mWaitingQueryLayout)));
4320 if (!IsReadLocked()) {
4321 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4322 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4323 "not locked (read)",
4324 this));
4325 return TS_E_NOLOCK;
4328 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4329 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4330 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4331 "called with invalid view",
4332 this));
4333 return E_INVALIDARG;
4336 if (!pt) {
4337 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4338 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4339 "null pt",
4340 this));
4341 return E_INVALIDARG;
4344 if (!pacp) {
4345 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4346 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4347 "null pacp",
4348 this));
4349 return E_INVALIDARG;
4352 mWaitingQueryLayout = false;
4354 if (mDestroyed || mContentForTSF.IsLayoutChanged()) {
4355 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4356 ("0x%p TSFTextStore::GetACPFromPoint() returned "
4357 "TS_E_NOLAYOUT",
4358 this));
4359 mHasReturnedNoLayoutError = true;
4360 return TS_E_NOLAYOUT;
4363 LayoutDeviceIntPoint ourPt(pt->x, pt->y);
4364 // Convert to widget relative coordinates from screen's.
4365 ourPt -= mWidget->WidgetToScreenOffset();
4367 // NOTE: Don't check if the point is in the widget since the point can be
4368 // outside of the widget if focused editor is in a XUL <panel>.
4370 WidgetQueryContentEvent charAtPt(true, eQueryCharacterAtPoint, mWidget);
4371 mWidget->InitEvent(charAtPt, &ourPt);
4373 // FYI: WidgetQueryContentEvent may cause flushing pending layout and it
4374 // may cause focus change or something.
4375 RefPtr<TSFTextStore> kungFuDeathGrip(this);
4376 DispatchEvent(charAtPt);
4377 if (!mWidget || mWidget->Destroyed()) {
4378 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4379 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4380 "mWidget was destroyed during eQueryCharacterAtPoint",
4381 this));
4382 return E_FAIL;
4385 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
4386 ("0x%p TSFTextStore::GetACPFromPoint(), charAtPt={ "
4387 "mSucceeded=%s, mReply={ mOffset=%u, mTentativeCaretOffset=%u }}",
4388 this, GetBoolName(charAtPt.mSucceeded), charAtPt.mReply.mOffset,
4389 charAtPt.mReply.mTentativeCaretOffset));
4391 if (NS_WARN_IF(!charAtPt.mSucceeded)) {
4392 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4393 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4394 "eQueryCharacterAtPoint failure",
4395 this));
4396 return E_FAIL;
4399 // If dwFlags isn't set and the point isn't in any character's bounding box,
4400 // we should return TS_E_INVALIDPOINT.
4401 if (!(dwFlags & GXFPF_NEAREST) &&
4402 charAtPt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND) {
4403 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4404 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to the "
4405 "point contained by no bounding box",
4406 this));
4407 return TS_E_INVALIDPOINT;
4410 // Although, we're not sure if mTentativeCaretOffset becomes NOT_FOUND,
4411 // let's assume that there is no content in such case.
4412 if (NS_WARN_IF(charAtPt.mReply.mTentativeCaretOffset ==
4413 WidgetQueryContentEvent::NOT_FOUND)) {
4414 charAtPt.mReply.mTentativeCaretOffset = 0;
4417 uint32_t offset;
4419 // If dwFlags includes GXFPF_ROUND_NEAREST, we should return tentative
4420 // caret offset (MSDN calls it "range position").
4421 if (dwFlags & GXFPF_ROUND_NEAREST) {
4422 offset = charAtPt.mReply.mTentativeCaretOffset;
4423 } else if (charAtPt.mReply.mOffset != WidgetQueryContentEvent::NOT_FOUND) {
4424 // Otherwise, we should return character offset whose bounding box contains
4425 // the point.
4426 offset = charAtPt.mReply.mOffset;
4427 } else {
4428 // If the point isn't in any character's bounding box but we need to return
4429 // the nearest character from the point, we should *guess* the character
4430 // offset since there is no inexpensive API to check it strictly.
4431 // XXX If we retrieve 2 bounding boxes, one is before the offset and
4432 // the other is after the offset, we could resolve the offset.
4433 // However, dispatching 2 eQueryTextRect may be expensive.
4435 // So, use tentative offset for now.
4436 offset = charAtPt.mReply.mTentativeCaretOffset;
4438 // However, if it's after the last character, we need to decrement the
4439 // offset.
4440 Content& contentForTSF = ContentForTSFRef();
4441 if (!contentForTSF.IsInitialized()) {
4442 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4443 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
4444 "ContentForTSFRef() failure",
4445 this));
4446 return E_FAIL;
4448 if (contentForTSF.Text().Length() <= offset) {
4449 // If the tentative caret is after the last character, let's return
4450 // the last character's offset.
4451 offset = contentForTSF.Text().Length() - 1;
4455 if (NS_WARN_IF(offset > LONG_MAX)) {
4456 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4457 ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to out of "
4458 "range of the result",
4459 this));
4460 return TS_E_INVALIDPOINT;
4463 *pacp = static_cast<LONG>(offset);
4464 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4465 ("0x%p TSFTextStore::GetACPFromPoint() succeeded: *pacp=%d", this,
4466 *pacp));
4467 return S_OK;
4470 STDMETHODIMP
4471 TSFTextStore::GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd,
4472 RECT* prc, BOOL* pfClipped) {
4473 MOZ_LOG(
4474 sTextStoreLog, LogLevel::Info,
4475 ("0x%p TSFTextStore::GetTextExt(vcView=%ld, "
4476 "acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p), "
4477 "IsHandlingComposition()=%s, "
4478 "mContentForTSF={ MinOffsetOfLayoutChanged()=%u, "
4479 "LatestCompositionStartOffset()=%d, LatestCompositionEndOffset()=%d }, "
4480 "mComposition= { IsComposing()=%s, mStart=%d, EndOffset()=%d }, "
4481 "mDeferNotifyingTSF=%s, mWaitingQueryLayout=%s, "
4482 "IMEHandler::IsA11yHandlingNativeCaret()=%s",
4483 this, vcView, acpStart, acpEnd, prc, pfClipped,
4484 GetBoolName(IsHandlingComposition()),
4485 mContentForTSF.MinOffsetOfLayoutChanged(),
4486 mContentForTSF.HasOrHadComposition()
4487 ? mContentForTSF.LatestCompositionStartOffset()
4488 : -1,
4489 mContentForTSF.HasOrHadComposition()
4490 ? mContentForTSF.LatestCompositionEndOffset()
4491 : -1,
4492 GetBoolName(mComposition.IsComposing()), mComposition.mStart,
4493 mComposition.EndOffset(), GetBoolName(mDeferNotifyingTSF),
4494 GetBoolName(mWaitingQueryLayout),
4495 GetBoolName(IMEHandler::IsA11yHandlingNativeCaret())));
4497 if (!IsReadLocked()) {
4498 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4499 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4500 "not locked (read)",
4501 this));
4502 return TS_E_NOLOCK;
4505 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4506 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4507 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4508 "called with invalid view",
4509 this));
4510 return E_INVALIDARG;
4513 if (!prc || !pfClipped) {
4514 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4515 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4516 "null argument",
4517 this));
4518 return E_INVALIDARG;
4521 // According to MSDN, ITextStoreACP::GetTextExt() should return
4522 // TS_E_INVALIDARG when acpStart and acpEnd are same (i.e., collapsed range).
4523 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms538435(v=vs.85).aspx
4524 // > TS_E_INVALIDARG: The specified start and end character positions are
4525 // > equal.
4526 // However, some TIPs (including Microsoft's Chinese TIPs!) call this with
4527 // collapsed range and if we return TS_E_INVALIDARG, they stops showing their
4528 // owning window or shows it but odd position. So, we should just return
4529 // error only when acpStart and/or acpEnd are really odd.
4531 if (acpStart < 0 || acpEnd < acpStart) {
4532 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4533 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4534 "invalid position",
4535 this));
4536 return TS_E_INVALIDPOS;
4539 mWaitingQueryLayout = false;
4541 if (IsHandlingComposition() && mContentForTSF.HasOrHadComposition() &&
4542 mContentForTSF.IsLayoutChanged() &&
4543 mContentForTSF.MinOffsetOfLayoutChanged() > LONG_MAX) {
4544 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4545 ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text "
4546 "is too big for TSF (cannot treat modified offset as LONG), "
4547 "mContentForTSF.MinOffsetOfLayoutChanged()=%u",
4548 this, mContentForTSF.MinOffsetOfLayoutChanged()));
4549 return E_FAIL;
4552 // At Windows 10 build 17643 (an insider preview for RS5), Microsoft fixed
4553 // the bug of TS_E_NOLAYOUT (even when we returned TS_E_NOLAYOUT, TSF
4554 // returned E_FAIL to TIP). However, until we drop to support older Windows
4555 // and all TIPs are aware of TS_E_NOLAYOUT result, we need to keep returning
4556 // S_OK and available rectangle only for them.
4557 if (!MaybeHackNoErrorLayoutBugs(acpStart, acpEnd) &&
4558 mContentForTSF.IsLayoutChangedAt(acpEnd)) {
4559 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4560 ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
4561 "(acpEnd=%d)",
4562 this, acpEnd));
4563 mHasReturnedNoLayoutError = true;
4564 return TS_E_NOLAYOUT;
4567 if (mDestroyed) {
4568 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4569 ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
4570 "(acpEnd=%d) because this has already been destroyed",
4571 this, acpEnd));
4572 mHasReturnedNoLayoutError = true;
4573 return TS_E_NOLAYOUT;
4576 // use eQueryTextRect to get rect in system, screen coordinates
4577 WidgetQueryContentEvent event(true, eQueryTextRect, mWidget);
4578 mWidget->InitEvent(event);
4580 WidgetQueryContentEvent::Options options;
4581 int64_t startOffset = acpStart;
4582 if (mComposition.IsComposing()) {
4583 // If there is a composition, TSF must want character rects related to
4584 // the composition. Therefore, we should use insertion point relative
4585 // query because the composition might be at different position from
4586 // the position where TSFTextStore believes it at.
4587 options.mRelativeToInsertionPoint = true;
4588 startOffset -= mComposition.mStart;
4589 } else if (IsHandlingComposition() && mContentForTSF.HasOrHadComposition()) {
4590 // If there was a composition and it hasn't been committed in the content
4591 // yet, ContentCacheInParent is still open for relative offset query from
4592 // the latest composition.
4593 options.mRelativeToInsertionPoint = true;
4594 startOffset -= mContentForTSF.LatestCompositionStartOffset();
4595 } else if (!CanAccessActualContentDirectly()) {
4596 // If TSF/TIP cannot access actual content directly, there may be pending
4597 // text and/or selection changes which have not been notified TSF yet.
4598 // Therefore, we should use relative to insertion point query since
4599 // TSF/TIP computes the offset from the cached selection.
4600 options.mRelativeToInsertionPoint = true;
4601 startOffset -= mSelectionForTSF.StartOffset();
4603 // ContentEventHandler and ContentCache return actual caret rect when
4604 // the queried range is collapsed and selection is collapsed at the
4605 // queried range. Then, its height (in horizontal layout, width in vertical
4606 // layout) may be different from actual font height of the line. In such
4607 // case, users see "dancing" of candidate or suggest window of TIP.
4608 // For preventing it, we should query text rect with at least 1 length.
4609 uint32_t length = std::max(static_cast<int32_t>(acpEnd - acpStart), 1);
4610 event.InitForQueryTextRect(startOffset, length, options);
4612 DispatchEvent(event);
4613 if (NS_WARN_IF(!event.mSucceeded)) {
4614 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4615 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4616 "eQueryTextRect failure",
4617 this));
4618 return TS_E_INVALIDPOS; // but unexpected failure, maybe.
4621 // IMEs don't like empty rects, fix here
4622 if (event.mReply.mRect.Width() <= 0) event.mReply.mRect.SetWidth(1);
4623 if (event.mReply.mRect.Height() <= 0) event.mReply.mRect.SetHeight(1);
4625 // convert to unclipped screen rect
4626 nsWindow* refWindow = static_cast<nsWindow*>(
4627 event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWidget);
4628 // Result rect is in top level widget coordinates
4629 refWindow = refWindow->GetTopLevelWindow(false);
4630 if (!refWindow) {
4631 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4632 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4633 "no top level window",
4634 this));
4635 return E_FAIL;
4638 event.mReply.mRect.MoveBy(refWindow->WidgetToScreenOffset());
4640 // get bounding screen rect to test for clipping
4641 if (!GetScreenExtInternal(*prc)) {
4642 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4643 ("0x%p TSFTextStore::GetTextExt() FAILED due to "
4644 "GetScreenExtInternal() failure",
4645 this));
4646 return E_FAIL;
4649 // clip text rect to bounding rect
4650 RECT textRect;
4651 ::SetRect(&textRect, event.mReply.mRect.X(), event.mReply.mRect.Y(),
4652 event.mReply.mRect.XMost(), event.mReply.mRect.YMost());
4653 if (!::IntersectRect(prc, prc, &textRect))
4654 // Text is not visible
4655 ::SetRectEmpty(prc);
4657 // not equal if text rect was clipped
4658 *pfClipped = !::EqualRect(prc, &textRect);
4660 // ATOK 2011 - 2016 refers native caret position and size on windows whose
4661 // class name is one of Mozilla's windows for deciding candidate window
4662 // position. Additionally, ATOK 2015 and earlier behaves really odd when
4663 // we don't create native caret. Therefore, we need to create native caret
4664 // only when ATOK 2011 - 2015 is active (i.e., not necessary for ATOK 2016).
4665 // However, if a11y module is handling native caret, we shouldn't touch it.
4666 // Note that ATOK must require the latest information of the caret. So,
4667 // even if we'll create native caret later, we need to creat it here with
4668 // current information.
4669 if (!IMEHandler::IsA11yHandlingNativeCaret() &&
4670 TSFPrefs::NeedToCreateNativeCaretForLegacyATOK() &&
4671 TSFStaticSink::IsATOKReferringNativeCaretActive() &&
4672 mComposition.IsComposing() && mComposition.mStart <= acpStart &&
4673 mComposition.EndOffset() >= acpStart && mComposition.mStart <= acpEnd &&
4674 mComposition.EndOffset() >= acpEnd) {
4675 CreateNativeCaret();
4678 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4679 ("0x%p TSFTextStore::GetTextExt() succeeded: "
4680 "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }, *pfClipped=%s",
4681 this, prc->left, prc->top, prc->right, prc->bottom,
4682 GetBoolName(*pfClipped)));
4684 return S_OK;
4687 bool TSFTextStore::MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd) {
4688 // When ITextStoreACP::GetTextExt() returns TS_E_NOLAYOUT, TSF returns E_FAIL
4689 // to its caller (typically, active TIP). Then, most TIPs abort current job
4690 // or treat such application as non-GUI apps. E.g., some of them give up
4691 // showing candidate window, some others show candidate window at top-left of
4692 // the screen. For avoiding this issue, when there is composition (until
4693 // composition is actually committed in remote content), we should not
4694 // return TS_E_NOLAYOUT error for TIPs whose some features are broken by
4695 // this issue.
4696 // Note that ideally, this issue should be avoided by each TIP since this
4697 // won't be fixed at least on non-latest Windows. Actually, Google Japanese
4698 // Input (based on Mozc) does it. When GetTextExt() returns E_FAIL, TIPs
4699 // should try to check result of GetRangeFromPoint() because TSF returns
4700 // TS_E_NOLAYOUT correctly in this case. See:
4701 // https://github.com/google/mozc/blob/6b878e31fb6ac4347dc9dfd8ccc1080fe718479f/src/win32/tip/tip_range_util.cc#L237-L257
4703 if (!IsHandlingComposition() || !mContentForTSF.HasOrHadComposition() ||
4704 !mContentForTSF.IsLayoutChangedAt(aACPEnd)) {
4705 return false;
4708 MOZ_ASSERT(!mComposition.IsComposing() ||
4709 mComposition.mStart ==
4710 mContentForTSF.LatestCompositionStartOffset());
4711 MOZ_ASSERT(!mComposition.IsComposing() ||
4712 mComposition.EndOffset() ==
4713 mContentForTSF.LatestCompositionEndOffset());
4715 // If TSF does not have the bug, we need to hack only with a few TIPs.
4716 static const bool sAlllowToStopHackingIfFine =
4717 IsWindows10BuildOrLater(17643) &&
4718 TSFPrefs::AllowToStopHackingOnBuild17643OrLater();
4720 // We need to compute active TIP now. This may take a couple of milliseconds,
4721 // however, it'll be cached, so, must be faster than check active TIP every
4722 // GetTextExt() calls.
4723 const Selection& selectionForTSF = SelectionForTSFRef();
4724 switch (TSFStaticSink::ActiveTIP()) {
4725 // MS IME for Japanese doesn't support asynchronous handling at deciding
4726 // its suggest list window position. The feature was implemented
4727 // starting from Windows 8. And also we may meet same trouble in e10s
4728 // mode on Win7. So, we should never return TS_E_NOLAYOUT to MS IME for
4729 // Japanese.
4730 case TextInputProcessorID::eMicrosoftIMEForJapanese:
4731 if (sAlllowToStopHackingIfFine) {
4732 return false;
4734 // Basically, MS-IME tries to retrieve whole composition string rect
4735 // at deciding suggest window immediately after unlocking the document.
4736 // However, in e10s mode, the content hasn't updated yet in most cases.
4737 // Therefore, if the first character at the retrieving range rect is
4738 // available, we should use it as the result.
4739 if (TSFPrefs::DoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar() &&
4740 aACPStart < aACPEnd) {
4741 aACPEnd = aACPStart;
4743 // Although, the condition is not clear, MS-IME sometimes retrieves the
4744 // caret rect immediately after modifying the composition string but
4745 // before unlocking the document. In such case, we should return the
4746 // nearest character rect.
4747 else if (TSFPrefs::DoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret() &&
4748 aACPStart == aACPEnd && selectionForTSF.IsCollapsed() &&
4749 selectionForTSF.EndOffset() == aACPEnd) {
4750 int32_t minOffsetOfLayoutChanged =
4751 static_cast<int32_t>(mContentForTSF.MinOffsetOfLayoutChanged());
4752 aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
4753 } else {
4754 return false;
4756 break;
4757 // The bug of Microsoft Office IME 2010 for Japanese is similar to
4758 // MS-IME for Win 8.1 and Win 10. Newer version of MS Office IME is not
4759 // released yet. So, we can hack it without prefs because there must be
4760 // no developers who want to disable this hack for tests.
4761 // XXX We have not tested with Microsoft Office IME 2010 since it's
4762 // installable only with Win7 and Win8 (i.e., cannot install Win8.1
4763 // and Win10), and requires upgrade to Win10.
4764 case TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese:
4765 // Basically, MS-IME tries to retrieve whole composition string rect
4766 // at deciding suggest window immediately after unlocking the document.
4767 // However, in e10s mode, the content hasn't updated yet in most cases.
4768 // Therefore, if the first character at the retrieving range rect is
4769 // available, we should use it as the result.
4770 if (aACPStart < aACPEnd) {
4771 aACPEnd = aACPStart;
4773 // Although, the condition is not clear, MS-IME sometimes retrieves the
4774 // caret rect immediately after modifying the composition string but
4775 // before unlocking the document. In such case, we should return the
4776 // nearest character rect.
4777 else if (aACPStart == aACPEnd && selectionForTSF.IsCollapsed() &&
4778 selectionForTSF.EndOffset() == aACPEnd) {
4779 int32_t minOffsetOfLayoutChanged =
4780 static_cast<int32_t>(mContentForTSF.MinOffsetOfLayoutChanged());
4781 aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
4782 } else {
4783 return false;
4785 break;
4786 // ATOK fails to handle TS_E_NOLAYOUT only when it decides the position of
4787 // suggest window. In such case, ATOK tries to query rect of whole or a
4788 // part of composition string.
4789 // FYI: ATOK changes their implementation around candidate window and
4790 // suggest widget at ATOK 2016. Therefore, there are some differences
4791 // ATOK 2015 (or older) and ATOK 2016 (or newer).
4792 // FYI: ATOK 2017 stops referring our window class name. I.e., ATOK 2016
4793 // and older may behave differently only on Gecko but this must be
4794 // finished from ATOK 2017.
4795 // FYI: For testing with legacy ATOK, we should hack it even if current ATOK
4796 // refers native caret rect on windows whose window class is one of
4797 // Mozilla window classes and we stop creating native caret for ATOK
4798 // because creating native caret causes ATOK refers caret position
4799 // when GetTextExt() returns TS_E_NOLAYOUT.
4800 case TextInputProcessorID::eATOK2011:
4801 case TextInputProcessorID::eATOK2012:
4802 case TextInputProcessorID::eATOK2013:
4803 case TextInputProcessorID::eATOK2014:
4804 case TextInputProcessorID::eATOK2015:
4805 // ATOK 2016 and later may temporarily show candidate window at odd
4806 // position when you convert a word quickly (e.g., keep pressing
4807 // space bar). So, on ATOK 2016 or later, we need to keep hacking the
4808 // result of GetTextExt().
4809 if (sAlllowToStopHackingIfFine) {
4810 return false;
4812 // If we'll create native caret where we paint our caret. Then, ATOK
4813 // will refer native caret. So, we don't need to hack anything in
4814 // this case.
4815 if (TSFPrefs::NeedToCreateNativeCaretForLegacyATOK()) {
4816 MOZ_ASSERT(TSFStaticSink::IsATOKReferringNativeCaretActive());
4817 return false;
4819 [[fallthrough]];
4820 case TextInputProcessorID::eATOK2016:
4821 case TextInputProcessorID::eATOKUnknown:
4822 if (!TSFPrefs::DoNotReturnNoLayoutErrorToATOKOfCompositionString()) {
4823 return false;
4825 // If the range is in the composition string, we should return rectangle
4826 // in it as far as possible.
4827 if (aACPStart < mContentForTSF.LatestCompositionStartOffset() ||
4828 aACPStart > mContentForTSF.LatestCompositionEndOffset() ||
4829 aACPEnd < mContentForTSF.LatestCompositionStartOffset() ||
4830 aACPEnd > mContentForTSF.LatestCompositionEndOffset()) {
4831 return false;
4833 break;
4834 // Japanist 10 fails to handle TS_E_NOLAYOUT when it decides the position
4835 // of candidate window. In such case, Japanist shows candidate window at
4836 // top-left of the screen. So, we should return the nearest caret rect
4837 // where we know. This is Japanist's bug. So, even after build 17643,
4838 // we need this hack.
4839 case TextInputProcessorID::eJapanist10:
4840 if (!TSFPrefs::
4841 DoNotReturnNoLayoutErrorToJapanist10OfCompositionString()) {
4842 return false;
4844 if (aACPStart < mContentForTSF.LatestCompositionStartOffset() ||
4845 aACPStart > mContentForTSF.LatestCompositionEndOffset() ||
4846 aACPEnd < mContentForTSF.LatestCompositionStartOffset() ||
4847 aACPEnd > mContentForTSF.LatestCompositionEndOffset()) {
4848 return false;
4850 break;
4851 // Free ChangJie 2010 doesn't handle ITfContextView::GetTextExt() properly.
4852 // This must be caused by the bug of TSF since Free ChangJie works fine on
4853 // build 17643 and later.
4854 case TextInputProcessorID::eFreeChangJie:
4855 if (sAlllowToStopHackingIfFine) {
4856 return false;
4858 if (!TSFPrefs::DoNotReturnNoLayoutErrorToFreeChangJie()) {
4859 return false;
4861 aACPEnd = mContentForTSF.LatestCompositionStartOffset();
4862 aACPStart = std::min(aACPStart, aACPEnd);
4863 break;
4864 // Some Traditional Chinese TIPs of Microsoft don't show candidate window
4865 // in e10s mode on Win8 or later.
4866 case TextInputProcessorID::eMicrosoftChangJie:
4867 case TextInputProcessorID::eMicrosoftQuick:
4868 if (sAlllowToStopHackingIfFine) {
4869 return false;
4871 if (!IsWin8OrLater() ||
4872 !TSFPrefs::DoNotReturnNoLayoutErrorToMSTraditionalTIP()) {
4873 return false;
4875 aACPEnd = mContentForTSF.LatestCompositionStartOffset();
4876 aACPStart = std::min(aACPStart, aACPEnd);
4877 break;
4878 // Some Simplified Chinese TIPs of Microsoft don't show candidate window
4879 // in e10s mode on Win8 or later.
4880 // FYI: Only Simplified Chinese TIPs of Microsoft still require this hack
4881 // because they sometimes do not show candidate window when we return
4882 // TS_E_NOLAYOUT for first query. Note that even when they show
4883 // candidate window properly, we return TS_E_NOLAYOUT and following
4884 // log looks same as when they don't show candidate window. Perhaps,
4885 // there is stateful cause or race in them.
4886 case TextInputProcessorID::eMicrosoftPinyin:
4887 case TextInputProcessorID::eMicrosoftWubi:
4888 if (!IsWin8OrLater() ||
4889 !TSFPrefs::DoNotReturnNoLayoutErrorToMSSimplifiedTIP()) {
4890 return false;
4892 aACPEnd = mContentForTSF.LatestCompositionStartOffset();
4893 aACPStart = std::min(aACPStart, aACPEnd);
4894 break;
4895 default:
4896 return false;
4899 // If we hack the queried range for active TIP, that means we should not
4900 // return TS_E_NOLAYOUT even if hacked offset is still modified. So, as
4901 // far as possible, we should adjust the offset.
4902 MOZ_ASSERT(mContentForTSF.IsLayoutChanged());
4903 bool collapsed = aACPStart == aACPEnd;
4904 // Note that even if all characters in the editor or the composition
4905 // string was modified, 0 or start offset of the composition string is
4906 // useful because it may return caret rect or old character's rect which
4907 // the user still see. That must be useful information for TIP.
4908 int32_t firstModifiedOffset =
4909 static_cast<int32_t>(mContentForTSF.MinOffsetOfLayoutChanged());
4910 LONG lastUnmodifiedOffset = std::max(firstModifiedOffset - 1, 0);
4911 if (mContentForTSF.IsLayoutChangedAt(aACPStart)) {
4912 if (aACPStart >= mContentForTSF.LatestCompositionStartOffset()) {
4913 // If mContentForTSF has last composition string and current
4914 // composition string, we can assume that ContentCacheInParent has
4915 // cached rects of composition string at least length of current
4916 // composition string. Otherwise, we can assume that rect for
4917 // first character of composition string is stored since it was
4918 // selection start or caret position.
4919 LONG maxCachedOffset = mContentForTSF.LatestCompositionEndOffset();
4920 if (mContentForTSF.WasLastComposition()) {
4921 maxCachedOffset = std::min(
4922 maxCachedOffset, mContentForTSF.LastCompositionStringEndOffset());
4924 aACPStart = std::min(aACPStart, maxCachedOffset);
4926 // Otherwise, we don't know which character rects are cached. So, we
4927 // need to use first unmodified character's rect in this case. Even
4928 // if there is no character, the query event will return caret rect
4929 // instead.
4930 else {
4931 aACPStart = lastUnmodifiedOffset;
4933 MOZ_ASSERT(aACPStart <= aACPEnd);
4936 // If TIP requests caret rect with collapsed range, we should keep
4937 // collapsing the range.
4938 if (collapsed) {
4939 aACPEnd = aACPStart;
4941 // Let's set aACPEnd to larger offset of last unmodified offset or
4942 // aACPStart which may be the first character offset of the composition
4943 // string. However, some TIPs may want to know the right edge of the
4944 // range. Therefore, if aACPEnd is in composition string and active TIP
4945 // doesn't retrieve caret rect (i.e., the range isn't collapsed), we
4946 // should keep using the original aACPEnd. Otherwise, we should set
4947 // aACPEnd to larger value of aACPStart and lastUnmodifiedOffset.
4948 else if (mContentForTSF.IsLayoutChangedAt(aACPEnd) &&
4949 (aACPEnd < mContentForTSF.LatestCompositionStartOffset() ||
4950 aACPEnd > mContentForTSF.LatestCompositionEndOffset())) {
4951 aACPEnd = std::max(aACPStart, lastUnmodifiedOffset);
4954 MOZ_LOG(
4955 sTextStoreLog, LogLevel::Debug,
4956 ("0x%p TSFTextStore::HackNoErrorLayoutBugs() hacked the queried range "
4957 "for not returning TS_E_NOLAYOUT, new values are: "
4958 "aACPStart=%d, aACPEnd=%d",
4959 this, aACPStart, aACPEnd));
4961 return true;
4964 STDMETHODIMP
4965 TSFTextStore::GetScreenExt(TsViewCookie vcView, RECT* prc) {
4966 MOZ_LOG(sTextStoreLog, LogLevel::Info,
4967 ("0x%p TSFTextStore::GetScreenExt(vcView=%ld, prc=0x%p)", this,
4968 vcView, prc));
4970 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
4971 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4972 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4973 "called with invalid view",
4974 this));
4975 return E_INVALIDARG;
4978 if (!prc) {
4979 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4980 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4981 "null argument",
4982 this));
4983 return E_INVALIDARG;
4986 if (mDestroyed) {
4987 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4988 ("0x%p TSFTextStore::GetScreenExt() returns empty rect "
4989 "due to already destroyed",
4990 this));
4991 prc->left = prc->top = prc->right = prc->bottom = 0;
4992 return S_OK;
4995 if (!GetScreenExtInternal(*prc)) {
4996 MOZ_LOG(sTextStoreLog, LogLevel::Error,
4997 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
4998 "GetScreenExtInternal() failure",
4999 this));
5000 return E_FAIL;
5003 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5004 ("0x%p TSFTextStore::GetScreenExt() succeeded: "
5005 "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
5006 this, prc->left, prc->top, prc->right, prc->bottom));
5007 return S_OK;
5010 bool TSFTextStore::GetScreenExtInternal(RECT& aScreenExt) {
5011 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5012 ("0x%p TSFTextStore::GetScreenExtInternal()", this));
5014 MOZ_ASSERT(!mDestroyed);
5016 // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
5017 WidgetQueryContentEvent event(true, eQueryEditorRect, mWidget);
5018 mWidget->InitEvent(event);
5019 DispatchEvent(event);
5020 if (!event.mSucceeded) {
5021 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5022 ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
5023 "eQueryEditorRect failure",
5024 this));
5025 return false;
5028 nsWindow* refWindow = static_cast<nsWindow*>(
5029 event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWidget);
5030 // Result rect is in top level widget coordinates
5031 refWindow = refWindow->GetTopLevelWindow(false);
5032 if (!refWindow) {
5033 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5034 ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
5035 "no top level window",
5036 this));
5037 return false;
5040 LayoutDeviceIntRect boundRect = refWindow->GetClientBounds();
5041 boundRect.MoveTo(0, 0);
5043 // Clip frame rect to window rect
5044 boundRect.IntersectRect(event.mReply.mRect, boundRect);
5045 if (!boundRect.IsEmpty()) {
5046 boundRect.MoveBy(refWindow->WidgetToScreenOffset());
5047 ::SetRect(&aScreenExt, boundRect.X(), boundRect.Y(), boundRect.XMost(),
5048 boundRect.YMost());
5049 } else {
5050 ::SetRectEmpty(&aScreenExt);
5053 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5054 ("0x%p TSFTextStore::GetScreenExtInternal() succeeded: "
5055 "aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
5056 this, aScreenExt.left, aScreenExt.top, aScreenExt.right,
5057 aScreenExt.bottom));
5058 return true;
5061 STDMETHODIMP
5062 TSFTextStore::GetWnd(TsViewCookie vcView, HWND* phwnd) {
5063 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5064 ("0x%p TSFTextStore::GetWnd(vcView=%ld, phwnd=0x%p), "
5065 "mWidget=0x%p",
5066 this, vcView, phwnd, mWidget.get()));
5068 if (vcView != TEXTSTORE_DEFAULT_VIEW) {
5069 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5070 ("0x%p TSFTextStore::GetWnd() FAILED due to "
5071 "called with invalid view",
5072 this));
5073 return E_INVALIDARG;
5076 if (!phwnd) {
5077 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5078 ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
5079 "null argument",
5080 this));
5081 return E_INVALIDARG;
5084 *phwnd = mWidget ? mWidget->GetWindowHandle() : nullptr;
5086 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5087 ("0x%p TSFTextStore::GetWnd() succeeded: *phwnd=0x%p", this,
5088 static_cast<void*>(*phwnd)));
5089 return S_OK;
5092 STDMETHODIMP
5093 TSFTextStore::InsertTextAtSelection(DWORD dwFlags, const WCHAR* pchText,
5094 ULONG cch, LONG* pacpStart, LONG* pacpEnd,
5095 TS_TEXTCHANGE* pChange) {
5096 MOZ_LOG(
5097 sTextStoreLog, LogLevel::Info,
5098 ("0x%p TSFTextStore::InsertTextAtSelection(dwFlags=%s, "
5099 "pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, "
5100 "pChange=0x%p), IsComposing()=%s",
5101 this,
5102 dwFlags == 0
5103 ? "0"
5104 : dwFlags == TF_IAS_NOQUERY
5105 ? "TF_IAS_NOQUERY"
5106 : dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY" : "Unknown",
5107 pchText, pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "",
5108 cch, pacpStart, pacpEnd, pChange,
5109 GetBoolName(mComposition.IsComposing())));
5111 if (cch && !pchText) {
5112 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5113 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5114 "null pchText",
5115 this));
5116 return E_INVALIDARG;
5119 if (TS_IAS_QUERYONLY == dwFlags) {
5120 if (!IsReadLocked()) {
5121 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5122 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5123 "not locked (read)",
5124 this));
5125 return TS_E_NOLOCK;
5128 if (!pacpStart || !pacpEnd) {
5129 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5130 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5131 "null argument",
5132 this));
5133 return E_INVALIDARG;
5136 // Get selection first
5137 Selection& selectionForTSF = SelectionForTSFRef();
5138 if (selectionForTSF.IsDirty()) {
5139 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5140 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5141 "SelectionForTSFRef() failure",
5142 this));
5143 return E_FAIL;
5146 // Simulate text insertion
5147 *pacpStart = selectionForTSF.StartOffset();
5148 *pacpEnd = selectionForTSF.EndOffset();
5149 if (pChange) {
5150 pChange->acpStart = selectionForTSF.StartOffset();
5151 pChange->acpOldEnd = selectionForTSF.EndOffset();
5152 pChange->acpNewEnd =
5153 selectionForTSF.StartOffset() + static_cast<LONG>(cch);
5155 } else {
5156 if (!IsReadWriteLocked()) {
5157 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5158 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5159 "not locked (read-write)",
5160 this));
5161 return TS_E_NOLOCK;
5164 if (!pChange) {
5165 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5166 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5167 "null pChange",
5168 this));
5169 return E_INVALIDARG;
5172 if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) {
5173 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5174 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5175 "null argument",
5176 this));
5177 return E_INVALIDARG;
5180 if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
5181 pChange)) {
5182 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5183 ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
5184 "InsertTextAtSelectionInternal() failure",
5185 this));
5186 return E_FAIL;
5189 if (TS_IAS_NOQUERY != dwFlags) {
5190 *pacpStart = pChange->acpStart;
5191 *pacpEnd = pChange->acpNewEnd;
5194 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5195 ("0x%p TSFTextStore::InsertTextAtSelection() succeeded: "
5196 "*pacpStart=%ld, *pacpEnd=%ld, "
5197 "*pChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld })",
5198 this, pacpStart ? *pacpStart : 0, pacpEnd ? *pacpEnd : 0,
5199 pChange ? pChange->acpStart : 0, pChange ? pChange->acpOldEnd : 0,
5200 pChange ? pChange->acpNewEnd : 0));
5201 return S_OK;
5204 bool TSFTextStore::InsertTextAtSelectionInternal(const nsAString& aInsertStr,
5205 TS_TEXTCHANGE* aTextChange) {
5206 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5207 ("0x%p TSFTextStore::InsertTextAtSelectionInternal("
5208 "aInsertStr=\"%s\", aTextChange=0x%p), IsComposing=%s",
5209 this, GetEscapedUTF8String(aInsertStr).get(), aTextChange,
5210 GetBoolName(mComposition.IsComposing())));
5212 Content& contentForTSF = ContentForTSFRef();
5213 if (!contentForTSF.IsInitialized()) {
5214 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5215 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() failed "
5216 "due to ContentForTSFRef() failure()",
5217 this));
5218 return false;
5221 MaybeDispatchKeyboardEventAsProcessedByIME();
5222 if (mDestroyed) {
5223 MOZ_LOG(
5224 sTextStoreLog, LogLevel::Error,
5225 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() FAILED due to "
5226 "destroyed during dispatching a keyboard event",
5227 this));
5228 return false;
5231 TS_SELECTION_ACP oldSelection = contentForTSF.Selection().ACP();
5232 if (!mComposition.IsComposing()) {
5233 // Use a temporary composition to contain the text
5234 PendingAction* compositionStart = mPendingActions.AppendElements(2);
5235 PendingAction* compositionEnd = compositionStart + 1;
5237 compositionStart->mType = PendingAction::Type::eCompositionStart;
5238 compositionStart->mSelectionStart = oldSelection.acpStart;
5239 compositionStart->mSelectionLength =
5240 oldSelection.acpEnd - oldSelection.acpStart;
5241 compositionStart->mAdjustSelection = false;
5243 compositionEnd->mType = PendingAction::Type::eCompositionEnd;
5244 compositionEnd->mData = aInsertStr;
5245 compositionEnd->mSelectionStart = compositionStart->mSelectionStart;
5247 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5248 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
5249 "appending pending compositionstart and compositionend... "
5250 "PendingCompositionStart={ mSelectionStart=%d, "
5251 "mSelectionLength=%d }, PendingCompositionEnd={ mData=\"%s\" "
5252 "(Length()=%u), mSelectionStart=%d }",
5253 this, compositionStart->mSelectionStart,
5254 compositionStart->mSelectionLength,
5255 GetEscapedUTF8String(compositionEnd->mData).get(),
5256 compositionEnd->mData.Length(), compositionEnd->mSelectionStart));
5259 contentForTSF.ReplaceSelectedTextWith(aInsertStr);
5261 if (aTextChange) {
5262 aTextChange->acpStart = oldSelection.acpStart;
5263 aTextChange->acpOldEnd = oldSelection.acpEnd;
5264 aTextChange->acpNewEnd = contentForTSF.Selection().EndOffset();
5267 MOZ_LOG(
5268 sTextStoreLog, LogLevel::Debug,
5269 ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
5270 "succeeded: mWidget=0x%p, mWidget->Destroyed()=%s, aTextChange={ "
5271 "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
5272 this, mWidget.get(), GetBoolName(mWidget ? mWidget->Destroyed() : true),
5273 aTextChange ? aTextChange->acpStart : 0,
5274 aTextChange ? aTextChange->acpOldEnd : 0,
5275 aTextChange ? aTextChange->acpNewEnd : 0));
5276 return true;
5279 STDMETHODIMP
5280 TSFTextStore::InsertEmbeddedAtSelection(DWORD dwFlags, IDataObject* pDataObject,
5281 LONG* pacpStart, LONG* pacpEnd,
5282 TS_TEXTCHANGE* pChange) {
5283 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5284 ("0x%p TSFTextStore::InsertEmbeddedAtSelection() called "
5285 "but not supported (E_NOTIMPL)",
5286 this));
5288 // embedded objects are not supported
5289 return E_NOTIMPL;
5292 HRESULT
5293 TSFTextStore::RecordCompositionStartAction(ITfCompositionView* aComposition,
5294 ITfRange* aRange,
5295 bool aPreserveSelection) {
5296 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5297 ("0x%p TSFTextStore::RecordCompositionStartAction("
5298 "aComposition=0x%p, aRange=0x%p, aPreserveSelection=%s), "
5299 "mComposition.mView=0x%p",
5300 this, aComposition, aRange, GetBoolName(aPreserveSelection),
5301 mComposition.mView.get()));
5303 LONG start = 0, length = 0;
5304 HRESULT hr = GetRangeExtent(aRange, &start, &length);
5305 if (FAILED(hr)) {
5306 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5307 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5308 "due to GetRangeExtent() failure",
5309 this));
5310 return hr;
5313 return RecordCompositionStartAction(aComposition, start, length,
5314 aPreserveSelection);
5317 HRESULT
5318 TSFTextStore::RecordCompositionStartAction(ITfCompositionView* aComposition,
5319 LONG aStart, LONG aLength,
5320 bool aPreserveSelection) {
5321 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5322 ("0x%p TSFTextStore::RecordCompositionStartAction("
5323 "aComposition=0x%p, aStart=%d, aLength=%d, aPreserveSelection=%s), "
5324 "mComposition.mView=0x%p",
5325 this, aComposition, aStart, aLength, GetBoolName(aPreserveSelection),
5326 mComposition.mView.get()));
5328 Content& contentForTSF = ContentForTSFRef();
5329 if (!contentForTSF.IsInitialized()) {
5330 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5331 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5332 "due to ContentForTSFRef() failure",
5333 this));
5334 return E_FAIL;
5337 MaybeDispatchKeyboardEventAsProcessedByIME();
5338 if (mDestroyed) {
5339 MOZ_LOG(
5340 sTextStoreLog, LogLevel::Error,
5341 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED due to "
5342 "destroyed during dispatching a keyboard event",
5343 this));
5344 return false;
5347 CompleteLastActionIfStillIncomplete();
5349 // TIP may have inserted text at selection before calling
5350 // OnStartComposition(). In this case, we've already created a pending
5351 // compositionend. If new composition replaces all commit string of the
5352 // pending compositionend, we should cancel the pending compositionend and
5353 // keep the previous composition normally.
5354 // On Windows 7, MS-IME for Korean, MS-IME 2010 for Korean and MS Old Hangul
5355 // may start composition with calling InsertTextAtSelection() and
5356 // OnStartComposition() with this order (bug 1208043).
5357 // On Windows 10, MS Pinyin, MS Wubi, MS ChangJie and MS Quick commits
5358 // last character and replace it with empty string with new composition
5359 // when user removes last character of composition string with Backspace
5360 // key (bug 1462257).
5361 if (!aPreserveSelection &&
5362 IsLastPendingActionCompositionEndAt(aStart, aLength)) {
5363 const PendingAction& pendingCompositionEnd = mPendingActions.LastElement();
5364 contentForTSF.RestoreCommittedComposition(aComposition,
5365 pendingCompositionEnd);
5366 mPendingActions.RemoveLastElement();
5367 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5368 ("0x%p TSFTextStore::RecordCompositionStartAction() "
5369 "succeeded: restoring the committed string as composing string, "
5370 "mComposition={ mStart=%ld, mString.Length()=%ld, "
5371 "mSelectionForTSF={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
5372 "style.fInterimChar=%s } }",
5373 this, mComposition.mStart, mComposition.mString.Length(),
5374 mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
5375 GetActiveSelEndName(mSelectionForTSF.ActiveSelEnd()),
5376 GetBoolName(mSelectionForTSF.IsInterimChar())));
5377 return S_OK;
5380 PendingAction* action = mPendingActions.AppendElement();
5381 action->mType = PendingAction::Type::eCompositionStart;
5382 action->mSelectionStart = aStart;
5383 action->mSelectionLength = aLength;
5385 Selection& selectionForTSF = SelectionForTSFRef();
5386 if (selectionForTSF.IsDirty()) {
5387 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5388 ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
5389 "due to SelectionForTSFRef() failure",
5390 this));
5391 action->mAdjustSelection = true;
5392 } else if (selectionForTSF.MinOffset() != aStart ||
5393 selectionForTSF.MaxOffset() != aStart + aLength) {
5394 // If new composition range is different from current selection range,
5395 // we need to set selection before dispatching compositionstart event.
5396 action->mAdjustSelection = true;
5397 } else {
5398 // We shouldn't dispatch selection set event before dispatching
5399 // compositionstart event because it may cause put caret different
5400 // position in HTML editor since generated flat text content and offset in
5401 // it are lossy data of HTML contents.
5402 action->mAdjustSelection = false;
5405 contentForTSF.StartComposition(aComposition, *action, aPreserveSelection);
5406 action->mData = mComposition.mString;
5408 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5409 ("0x%p TSFTextStore::RecordCompositionStartAction() succeeded: "
5410 "mComposition={ mStart=%ld, mString.Length()=%ld, "
5411 "mSelectionForTSF={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
5412 "style.fInterimChar=%s } }",
5413 this, mComposition.mStart, mComposition.mString.Length(),
5414 mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
5415 GetActiveSelEndName(mSelectionForTSF.ActiveSelEnd()),
5416 GetBoolName(mSelectionForTSF.IsInterimChar())));
5417 return S_OK;
5420 HRESULT
5421 TSFTextStore::RecordCompositionEndAction() {
5422 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5423 ("0x%p TSFTextStore::RecordCompositionEndAction(), "
5424 "mComposition={ mView=0x%p, mString=\"%s\" }",
5425 this, mComposition.mView.get(),
5426 GetEscapedUTF8String(mComposition.mString).get()));
5428 MOZ_ASSERT(mComposition.IsComposing());
5430 MaybeDispatchKeyboardEventAsProcessedByIME();
5431 if (mDestroyed) {
5432 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5433 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
5434 "destroyed during dispatching a keyboard event",
5435 this));
5436 return false;
5439 // If we're handling incomplete composition update or already handled
5440 // composition update, we can forget them since composition end will send
5441 // the latest composition string and it overwrites the composition string
5442 // even if we dispatch eCompositionChange event before that. So, let's
5443 // forget all composition updates now.
5444 RemoveLastCompositionUpdateActions();
5445 PendingAction* action = mPendingActions.AppendElement();
5446 action->mType = PendingAction::Type::eCompositionEnd;
5447 action->mData = mComposition.mString;
5448 action->mSelectionStart = mComposition.mStart;
5450 Content& contentForTSF = ContentForTSFRef();
5451 if (!contentForTSF.IsInitialized()) {
5452 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5453 ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due "
5454 "to ContentForTSFRef() failure",
5455 this));
5456 return E_FAIL;
5458 contentForTSF.EndComposition(*action);
5460 // If this composition was restart but the composition doesn't modify
5461 // anything, we should remove the pending composition for preventing to
5462 // dispatch redundant composition events.
5463 for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) {
5464 PendingAction& pendingAction = mPendingActions[i - 1];
5465 if (pendingAction.mType == PendingAction::Type::eCompositionStart) {
5466 if (pendingAction.mData != action->mData) {
5467 break;
5469 // When only setting selection is necessary, we should append it.
5470 if (pendingAction.mAdjustSelection) {
5471 LONG selectionStart = pendingAction.mSelectionStart;
5472 LONG selectionLength = pendingAction.mSelectionLength;
5474 PendingAction* setSelection = mPendingActions.AppendElement();
5475 setSelection->mType = PendingAction::Type::eSetSelection;
5476 setSelection->mSelectionStart = selectionStart;
5477 setSelection->mSelectionLength = selectionLength;
5478 setSelection->mSelectionReversed = false;
5480 // Remove the redundant pending composition.
5481 mPendingActions.RemoveElementsAt(i - 1, j);
5482 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5483 ("0x%p TSFTextStore::RecordCompositionEndAction(), "
5484 "succeeded, but the composition was canceled due to redundant",
5485 this));
5486 return S_OK;
5490 MOZ_LOG(
5491 sTextStoreLog, LogLevel::Info,
5492 ("0x%p TSFTextStore::RecordCompositionEndAction(), succeeded", this));
5493 return S_OK;
5496 STDMETHODIMP
5497 TSFTextStore::OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) {
5498 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5499 ("0x%p TSFTextStore::OnStartComposition(pComposition=0x%p, "
5500 "pfOk=0x%p), mComposition.mView=0x%p",
5501 this, pComposition, pfOk, mComposition.mView.get()));
5503 AutoPendingActionAndContentFlusher flusher(this);
5505 *pfOk = FALSE;
5507 // Only one composition at a time
5508 if (mComposition.IsComposing()) {
5509 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5510 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5511 "there is another composition already (but returns S_OK)",
5512 this));
5513 return S_OK;
5516 RefPtr<ITfRange> range;
5517 HRESULT hr = pComposition->GetRange(getter_AddRefs(range));
5518 if (FAILED(hr)) {
5519 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5520 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5521 "pComposition->GetRange() failure",
5522 this));
5523 return hr;
5525 hr = RecordCompositionStartAction(pComposition, range, false);
5526 if (FAILED(hr)) {
5527 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5528 ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
5529 "RecordCompositionStartAction() failure",
5530 this));
5531 return hr;
5534 *pfOk = TRUE;
5535 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5536 ("0x%p TSFTextStore::OnStartComposition() succeeded", this));
5537 return S_OK;
5540 STDMETHODIMP
5541 TSFTextStore::OnUpdateComposition(ITfCompositionView* pComposition,
5542 ITfRange* pRangeNew) {
5543 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5544 ("0x%p TSFTextStore::OnUpdateComposition(pComposition=0x%p, "
5545 "pRangeNew=0x%p), mComposition.mView=0x%p",
5546 this, pComposition, pRangeNew, mComposition.mView.get()));
5548 AutoPendingActionAndContentFlusher flusher(this);
5550 if (!mDocumentMgr || !mContext) {
5551 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5552 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5553 "not ready for the composition",
5554 this));
5555 return E_UNEXPECTED;
5557 if (!mComposition.IsComposing()) {
5558 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5559 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5560 "no active composition",
5561 this));
5562 return E_UNEXPECTED;
5564 if (mComposition.mView != pComposition) {
5565 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5566 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5567 "different composition view specified",
5568 this));
5569 return E_UNEXPECTED;
5572 // pRangeNew is null when the update is not complete
5573 if (!pRangeNew) {
5574 MaybeDispatchKeyboardEventAsProcessedByIME();
5575 if (mDestroyed) {
5576 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5577 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5578 "destroyed during dispatching a keyboard event",
5579 this));
5580 return E_FAIL;
5582 PendingAction* action = LastOrNewPendingCompositionUpdate();
5583 action->mIncomplete = true;
5584 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5585 ("0x%p TSFTextStore::OnUpdateComposition() succeeded but "
5586 "not complete",
5587 this));
5588 return S_OK;
5591 HRESULT hr = RestartCompositionIfNecessary(pRangeNew);
5592 if (FAILED(hr)) {
5593 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5594 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5595 "RestartCompositionIfNecessary() failure",
5596 this));
5597 return hr;
5600 hr = RecordCompositionUpdateAction();
5601 if (FAILED(hr)) {
5602 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5603 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5604 "RecordCompositionUpdateAction() failure",
5605 this));
5606 return hr;
5609 if (MOZ_LOG_TEST(sTextStoreLog, LogLevel::Info)) {
5610 Selection& selectionForTSF = SelectionForTSFRef();
5611 if (selectionForTSF.IsDirty()) {
5612 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5613 ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
5614 "SelectionForTSFRef() failure",
5615 this));
5616 return E_FAIL;
5618 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5619 ("0x%p TSFTextStore::OnUpdateComposition() succeeded: "
5620 "mComposition={ mStart=%ld, mString=\"%s\" }, "
5621 "SelectionForTSFRef()={ acpStart=%ld, acpEnd=%ld, style.ase=%s }",
5622 this, mComposition.mStart,
5623 GetEscapedUTF8String(mComposition.mString).get(),
5624 selectionForTSF.StartOffset(), selectionForTSF.EndOffset(),
5625 GetActiveSelEndName(selectionForTSF.ActiveSelEnd())));
5627 return S_OK;
5630 STDMETHODIMP
5631 TSFTextStore::OnEndComposition(ITfCompositionView* pComposition) {
5632 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5633 ("0x%p TSFTextStore::OnEndComposition(pComposition=0x%p), "
5634 "mComposition={ mView=0x%p, mString=\"%s\" }",
5635 this, pComposition, mComposition.mView.get(),
5636 GetEscapedUTF8String(mComposition.mString).get()));
5638 AutoPendingActionAndContentFlusher flusher(this);
5640 if (!mComposition.IsComposing()) {
5641 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5642 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5643 "no active composition",
5644 this));
5645 return E_UNEXPECTED;
5648 if (mComposition.mView != pComposition) {
5649 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5650 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5651 "different composition view specified",
5652 this));
5653 return E_UNEXPECTED;
5656 HRESULT hr = RecordCompositionEndAction();
5657 if (FAILED(hr)) {
5658 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5659 ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
5660 "RecordCompositionEndAction() failure",
5661 this));
5662 return hr;
5665 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5666 ("0x%p TSFTextStore::OnEndComposition(), succeeded", this));
5667 return S_OK;
5670 STDMETHODIMP
5671 TSFTextStore::AdviseMouseSink(ITfRangeACP* range, ITfMouseSink* pSink,
5672 DWORD* pdwCookie) {
5673 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5674 ("0x%p TSFTextStore::AdviseMouseSink(range=0x%p, pSink=0x%p, "
5675 "pdwCookie=0x%p)",
5676 this, range, pSink, pdwCookie));
5678 if (!pdwCookie) {
5679 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5680 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5681 "pdwCookie is null",
5682 this));
5683 return E_INVALIDARG;
5685 // Initialize the result with invalid cookie for safety.
5686 *pdwCookie = MouseTracker::kInvalidCookie;
5688 if (!range) {
5689 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5690 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5691 "range is null",
5692 this));
5693 return E_INVALIDARG;
5695 if (!pSink) {
5696 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5697 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
5698 "pSink is null",
5699 this));
5700 return E_INVALIDARG;
5703 // Looking for an unusing tracker.
5704 MouseTracker* tracker = nullptr;
5705 for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
5706 if (mMouseTrackers[i].IsUsing()) {
5707 continue;
5709 tracker = &mMouseTrackers[i];
5711 // If there is no unusing tracker, create new one.
5712 // XXX Should we make limitation of the number of installs?
5713 if (!tracker) {
5714 tracker = mMouseTrackers.AppendElement();
5715 HRESULT hr = tracker->Init(this);
5716 if (FAILED(hr)) {
5717 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5718 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to "
5719 "failure of MouseTracker::Init()",
5720 this));
5721 return hr;
5724 HRESULT hr = tracker->AdviseSink(this, range, pSink);
5725 if (FAILED(hr)) {
5726 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5727 ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to failure "
5728 "of MouseTracker::Init()",
5729 this));
5730 return hr;
5732 *pdwCookie = tracker->Cookie();
5733 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5734 ("0x%p TSFTextStore::AdviseMouseSink(), succeeded, "
5735 "*pdwCookie=%d",
5736 this, *pdwCookie));
5737 return S_OK;
5740 STDMETHODIMP
5741 TSFTextStore::UnadviseMouseSink(DWORD dwCookie) {
5742 MOZ_LOG(
5743 sTextStoreLog, LogLevel::Info,
5744 ("0x%p TSFTextStore::UnadviseMouseSink(dwCookie=%d)", this, dwCookie));
5745 if (dwCookie == MouseTracker::kInvalidCookie) {
5746 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5747 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5748 "the cookie is invalid value",
5749 this));
5750 return E_INVALIDARG;
5752 // The cookie value must be an index of mMouseTrackers.
5753 // We can use this shortcut for now.
5754 if (static_cast<size_t>(dwCookie) >= mMouseTrackers.Length()) {
5755 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5756 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5757 "the cookie is too large value",
5758 this));
5759 return E_INVALIDARG;
5761 MouseTracker& tracker = mMouseTrackers[dwCookie];
5762 if (!tracker.IsUsing()) {
5763 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5764 ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
5765 "the found tracker uninstalled already",
5766 this));
5767 return E_INVALIDARG;
5769 tracker.UnadviseSink();
5770 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5771 ("0x%p TSFTextStore::UnadviseMouseSink(), succeeded", this));
5772 return S_OK;
5775 // static
5776 nsresult TSFTextStore::OnFocusChange(bool aGotFocus,
5777 nsWindowBase* aFocusedWidget,
5778 const InputContext& aContext) {
5779 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
5780 (" TSFTextStore::OnFocusChange(aGotFocus=%s, "
5781 "aFocusedWidget=0x%p, aContext=%s), "
5782 "sThreadMgr=0x%p, sEnabledTextStore=0x%p",
5783 GetBoolName(aGotFocus), aFocusedWidget,
5784 GetInputContextString(aContext).get(), sThreadMgr.get(),
5785 sEnabledTextStore.get()));
5787 if (NS_WARN_IF(!IsInTSFMode())) {
5788 return NS_ERROR_NOT_AVAILABLE;
5791 RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
5792 bool hasFocus = ThinksHavingFocus();
5793 RefPtr<TSFTextStore> oldTextStore = sEnabledTextStore.forget();
5795 // If currently oldTextStore still has focus, notifies TSF of losing focus.
5796 if (hasFocus) {
5797 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5798 DebugOnly<HRESULT> hr = threadMgr->AssociateFocus(
5799 oldTextStore->mWidget->GetWindowHandle(), nullptr,
5800 getter_AddRefs(prevFocusedDocumentMgr));
5801 NS_ASSERTION(SUCCEEDED(hr), "Disassociating focus failed");
5802 NS_ASSERTION(prevFocusedDocumentMgr == oldTextStore->mDocumentMgr,
5803 "different documentMgr has been associated with the window");
5806 // Even if there was a focused TextStore, we won't use it with new focused
5807 // editor. So, release it now.
5808 if (oldTextStore) {
5809 oldTextStore->Destroy();
5812 if (NS_WARN_IF(!sThreadMgr)) {
5813 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5814 (" TSFTextStore::OnFocusChange() FAILED, due to "
5815 "sThreadMgr being destroyed during calling "
5816 "ITfThreadMgr::AssociateFocus()"));
5817 return NS_ERROR_FAILURE;
5819 if (NS_WARN_IF(sEnabledTextStore)) {
5820 MOZ_LOG(
5821 sTextStoreLog, LogLevel::Error,
5822 (" TSFTextStore::OnFocusChange() FAILED, due to "
5823 "nested event handling has created another focused TextStore during "
5824 "calling ITfThreadMgr::AssociateFocus()"));
5825 return NS_ERROR_FAILURE;
5828 // If this is a notification of blur, move focus to the dummy document
5829 // manager.
5830 if (!aGotFocus || !aContext.mIMEState.IsEditable()) {
5831 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5832 RefPtr<ITfDocumentMgr> disabledDocumentMgr = sDisabledDocumentMgr;
5833 HRESULT hr = threadMgr->SetFocus(disabledDocumentMgr);
5834 if (NS_WARN_IF(FAILED(hr))) {
5835 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5836 (" TSFTextStore::OnFocusChange() FAILED due to "
5837 "ITfThreadMgr::SetFocus() failure"));
5838 return NS_ERROR_FAILURE;
5840 return NS_OK;
5843 // If an editor is getting focus, create new TextStore and set focus.
5844 if (NS_WARN_IF(!CreateAndSetFocus(aFocusedWidget, aContext))) {
5845 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5846 (" TSFTextStore::OnFocusChange() FAILED due to "
5847 "ITfThreadMgr::CreateAndSetFocus() failure"));
5848 return NS_ERROR_FAILURE;
5850 return NS_OK;
5853 // static
5854 void TSFTextStore::EnsureToDestroyAndReleaseEnabledTextStoreIf(
5855 RefPtr<TSFTextStore>& aTextStore) {
5856 aTextStore->Destroy();
5857 if (sEnabledTextStore == aTextStore) {
5858 sEnabledTextStore = nullptr;
5860 aTextStore = nullptr;
5863 // static
5864 bool TSFTextStore::CreateAndSetFocus(nsWindowBase* aFocusedWidget,
5865 const InputContext& aContext) {
5866 // TSF might do something which causes that we need to access static methods
5867 // of TSFTextStore. At that time, sEnabledTextStore may be necessary.
5868 // So, we should set sEnabledTextStore directly.
5869 RefPtr<TSFTextStore> textStore = new TSFTextStore();
5870 sEnabledTextStore = textStore;
5871 if (NS_WARN_IF(!textStore->Init(aFocusedWidget, aContext))) {
5872 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5873 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5874 "TSFTextStore::Init() failure"));
5875 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5876 return false;
5878 RefPtr<ITfDocumentMgr> newDocMgr = textStore->mDocumentMgr;
5879 if (NS_WARN_IF(!newDocMgr)) {
5880 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5881 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5882 "invalid TSFTextStore::mDocumentMgr"));
5883 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5884 return false;
5886 if (aContext.mIMEState.mEnabled == IMEState::PASSWORD) {
5887 MarkContextAsKeyboardDisabled(textStore->mContext);
5888 RefPtr<ITfContext> topContext;
5889 newDocMgr->GetTop(getter_AddRefs(topContext));
5890 if (topContext && topContext != textStore->mContext) {
5891 MarkContextAsKeyboardDisabled(topContext);
5895 HRESULT hr;
5896 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
5897 hr = threadMgr->SetFocus(newDocMgr);
5899 if (NS_WARN_IF(FAILED(hr))) {
5900 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5901 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5902 "ITfTheadMgr::SetFocus() failure"));
5903 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5904 return false;
5906 if (NS_WARN_IF(!sThreadMgr)) {
5907 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5908 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5909 "sThreadMgr being destroyed during calling "
5910 "ITfTheadMgr::SetFocus()"));
5911 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5912 return false;
5914 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5915 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5916 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5917 "creating TextStore has lost focus during calling "
5918 "ITfThreadMgr::SetFocus()"));
5919 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5920 return false;
5923 // Use AssociateFocus() for ensuring that any native focus event
5924 // never steal focus from our documentMgr.
5925 RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
5926 hr = threadMgr->AssociateFocus(aFocusedWidget->GetWindowHandle(), newDocMgr,
5927 getter_AddRefs(prevFocusedDocumentMgr));
5928 if (NS_WARN_IF(FAILED(hr))) {
5929 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5930 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5931 "ITfTheadMgr::AssociateFocus() failure"));
5932 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5933 return false;
5935 if (NS_WARN_IF(!sThreadMgr)) {
5936 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5937 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5938 "sThreadMgr being destroyed during calling "
5939 "ITfTheadMgr::AssociateFocus()"));
5940 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5941 return false;
5943 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5944 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5945 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5946 "creating TextStore has lost focus during calling "
5947 "ITfTheadMgr::AssociateFocus()"));
5948 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5949 return false;
5952 if (textStore->mSink) {
5953 MOZ_LOG(sTextStoreLog, LogLevel::Info,
5954 (" TSFTextStore::CreateAndSetFocus(), calling "
5955 "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE) for 0x%p...",
5956 textStore.get()));
5957 RefPtr<ITextStoreACPSink> sink = textStore->mSink;
5958 sink->OnLayoutChange(TS_LC_CREATE, TEXTSTORE_DEFAULT_VIEW);
5959 if (NS_WARN_IF(sEnabledTextStore != textStore)) {
5960 MOZ_LOG(sTextStoreLog, LogLevel::Error,
5961 (" TSFTextStore::CreateAndSetFocus() FAILED due to "
5962 "creating TextStore has lost focus during calling "
5963 "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE)"));
5964 EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
5965 return false;
5968 return true;
5971 // static
5972 IMENotificationRequests TSFTextStore::GetIMENotificationRequests() {
5973 if (!sEnabledTextStore || NS_WARN_IF(!sEnabledTextStore->mDocumentMgr)) {
5974 // If there is no active text store, we don't need any notifications
5975 // since there is no sink which needs notifications.
5976 return IMENotificationRequests();
5979 // Otherwise, requests all notifications since even if some of them may not
5980 // be required by the sink of active TIP, active TIP may be changed and
5981 // other TIPs may need all notifications.
5982 // Note that Windows temporarily steal focus from active window if the main
5983 // process which created the window becomes busy. In this case, we shouldn't
5984 // commit composition since user may want to continue to compose the
5985 // composition after becoming not busy. Therefore, we need notifications
5986 // even during deactive.
5987 // Be aware, we don't need to check actual focused text store. For example,
5988 // MS-IME for Japanese handles focus messages by themselves and sets focused
5989 // text store to nullptr when the process is being inactivated. However,
5990 // we still need to reuse sEnabledTextStore if the process is activated and
5991 // focused element isn't changed. Therefore, if sEnabledTextStore isn't
5992 // nullptr, we need to keep notifying the sink even when it is not focused
5993 // text store for the thread manager.
5994 return IMENotificationRequests(
5995 IMENotificationRequests::NOTIFY_TEXT_CHANGE |
5996 IMENotificationRequests::NOTIFY_POSITION_CHANGE |
5997 IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR |
5998 IMENotificationRequests::NOTIFY_DURING_DEACTIVE);
6001 nsresult TSFTextStore::OnTextChangeInternal(
6002 const IMENotification& aIMENotification) {
6003 const TextChangeDataBase& textChangeData = aIMENotification.mTextChangeData;
6005 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6006 ("0x%p TSFTextStore::OnTextChangeInternal(aIMENotification={ "
6007 "mMessage=0x%08X, mTextChangeData={ mStartOffset=%lu, "
6008 "mRemovedEndOffset=%lu, mAddedEndOffset=%lu, "
6009 "mCausedOnlyByComposition=%s, "
6010 "mIncludingChangesDuringComposition=%s, "
6011 "mIncludingChangesWithoutComposition=%s }), "
6012 "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, "
6013 "mComposition.IsComposing()=%s",
6014 this, aIMENotification.mMessage, textChangeData.mStartOffset,
6015 textChangeData.mRemovedEndOffset, textChangeData.mAddedEndOffset,
6016 GetBoolName(textChangeData.mCausedOnlyByComposition),
6017 GetBoolName(textChangeData.mIncludingChangesDuringComposition),
6018 GetBoolName(textChangeData.mIncludingChangesWithoutComposition),
6019 GetBoolName(mDestroyed), mSink.get(),
6020 GetSinkMaskNameStr(mSinkMask).get(),
6021 GetBoolName(mComposition.IsComposing())));
6023 if (mDestroyed) {
6024 // If this instance is already destroyed, we shouldn't notify TSF of any
6025 // changes.
6026 return NS_OK;
6029 mDeferNotifyingTSF = false;
6031 // Different from selection change, we don't modify anything with text
6032 // change data. Therefore, if neither TSF not TIP wants text change
6033 // notifications, we don't need to store the changes.
6034 if (!mSink || !(mSinkMask & TS_AS_TEXT_CHANGE)) {
6035 return NS_OK;
6038 // Merge any text change data even if it's caused by composition.
6039 mPendingTextChangeData.MergeWith(textChangeData);
6041 MaybeFlushPendingNotifications();
6043 return NS_OK;
6046 void TSFTextStore::NotifyTSFOfTextChange() {
6047 MOZ_ASSERT(!mDestroyed);
6048 MOZ_ASSERT(!IsReadLocked());
6049 MOZ_ASSERT(!mComposition.IsComposing());
6050 MOZ_ASSERT(mPendingTextChangeData.IsValid());
6052 // If the text changes are caused only by composition, we don't need to
6053 // notify TSF of the text changes.
6054 if (mPendingTextChangeData.mCausedOnlyByComposition) {
6055 mPendingTextChangeData.Clear();
6056 return;
6059 // First, forget cached selection.
6060 mSelectionForTSF.MarkDirty();
6062 // For making it safer, we should check if there is a valid sink to receive
6063 // text change notification.
6064 if (NS_WARN_IF(!mSink) || NS_WARN_IF(!(mSinkMask & TS_AS_TEXT_CHANGE))) {
6065 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6066 ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
6067 "mSink is not ready to call ITextStoreACPSink::OnTextChange()...",
6068 this));
6069 mPendingTextChangeData.Clear();
6070 return;
6073 if (NS_WARN_IF(!mPendingTextChangeData.IsInInt32Range())) {
6074 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6075 ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
6076 "offset is too big for calling "
6077 "ITextStoreACPSink::OnTextChange()...",
6078 this));
6079 mPendingTextChangeData.Clear();
6080 return;
6083 TS_TEXTCHANGE textChange;
6084 textChange.acpStart = static_cast<LONG>(mPendingTextChangeData.mStartOffset);
6085 textChange.acpOldEnd =
6086 static_cast<LONG>(mPendingTextChangeData.mRemovedEndOffset);
6087 textChange.acpNewEnd =
6088 static_cast<LONG>(mPendingTextChangeData.mAddedEndOffset);
6089 mPendingTextChangeData.Clear();
6091 MOZ_LOG(
6092 sTextStoreLog, LogLevel::Info,
6093 ("0x%p TSFTextStore::NotifyTSFOfTextChange(), calling "
6094 "ITextStoreACPSink::OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
6095 "acpNewEnd=%ld })...",
6096 this, textChange.acpStart, textChange.acpOldEnd, textChange.acpNewEnd));
6097 RefPtr<ITextStoreACPSink> sink = mSink;
6098 sink->OnTextChange(0, &textChange);
6101 nsresult TSFTextStore::OnSelectionChangeInternal(
6102 const IMENotification& aIMENotification) {
6103 const SelectionChangeDataBase& selectionChangeData =
6104 aIMENotification.mSelectionChangeData;
6105 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6106 ("0x%p TSFTextStore::OnSelectionChangeInternal("
6107 "aIMENotification={ mSelectionChangeData={ mOffset=%lu, "
6108 "Length()=%lu, mReversed=%s, mWritingMode=%s, "
6109 "mCausedByComposition=%s, mCausedBySelectionEvent=%s, "
6110 "mOccurredDuringComposition=%s } }), mDestroyed=%s, "
6111 "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, "
6112 "mComposition.IsComposing()=%s",
6113 this, selectionChangeData.mOffset, selectionChangeData.Length(),
6114 GetBoolName(selectionChangeData.mReversed),
6115 GetWritingModeName(selectionChangeData.GetWritingMode()).get(),
6116 GetBoolName(selectionChangeData.mCausedByComposition),
6117 GetBoolName(selectionChangeData.mCausedBySelectionEvent),
6118 GetBoolName(selectionChangeData.mOccurredDuringComposition),
6119 GetBoolName(mDestroyed), mSink.get(),
6120 GetSinkMaskNameStr(mSinkMask).get(),
6121 GetBoolName(mIsRecordingActionsWithoutLock),
6122 GetBoolName(mComposition.IsComposing())));
6124 if (mDestroyed) {
6125 // If this instance is already destroyed, we shouldn't notify TSF of any
6126 // changes.
6127 return NS_OK;
6130 mDeferNotifyingTSF = false;
6132 // Assign the new selection change data to the pending selection change data
6133 // because only the latest selection data is necessary.
6134 // Note that this is necessary to update mSelectionForTSF. Therefore, even if
6135 // neither TSF nor TIP wants selection change notifications, we need to
6136 // store the selection information.
6137 mPendingSelectionChangeData.Assign(selectionChangeData);
6139 // Flush remaining pending notifications here if it's possible.
6140 MaybeFlushPendingNotifications();
6142 // If we're available, we should create native caret instead of IMEHandler
6143 // because we may have some cache to do it.
6144 // Note that if we have composition, we'll notified composition-updated
6145 // later so that we don't need to create native caret in such case.
6146 if (!IsHandlingComposition() && IMEHandler::NeedsToCreateNativeCaret()) {
6147 CreateNativeCaret();
6150 return NS_OK;
6153 void TSFTextStore::NotifyTSFOfSelectionChange() {
6154 MOZ_ASSERT(!mDestroyed);
6155 MOZ_ASSERT(!IsReadLocked());
6156 MOZ_ASSERT(!mComposition.IsComposing());
6157 MOZ_ASSERT(mPendingSelectionChangeData.IsValid());
6159 // If selection range isn't actually changed, we don't need to notify TSF
6160 // of this selection change.
6161 if (!mSelectionForTSF.SetSelection(
6162 mPendingSelectionChangeData.mOffset,
6163 mPendingSelectionChangeData.Length(),
6164 mPendingSelectionChangeData.mReversed,
6165 mPendingSelectionChangeData.GetWritingMode())) {
6166 mPendingSelectionChangeData.Clear();
6167 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6168 ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), "
6169 "selection isn't actually changed.",
6170 this));
6171 return;
6174 mPendingSelectionChangeData.Clear();
6176 if (!mSink || !(mSinkMask & TS_AS_SEL_CHANGE)) {
6177 return;
6180 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6181 ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), calling "
6182 "ITextStoreACPSink::OnSelectionChange()...",
6183 this));
6184 RefPtr<ITextStoreACPSink> sink = mSink;
6185 sink->OnSelectionChange();
6188 nsresult TSFTextStore::OnLayoutChangeInternal() {
6189 if (mDestroyed) {
6190 // If this instance is already destroyed, we shouldn't notify TSF of any
6191 // changes.
6192 return NS_OK;
6195 NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE);
6196 NS_ENSURE_TRUE(mSink, NS_ERROR_FAILURE);
6198 mDeferNotifyingTSF = false;
6200 nsresult rv = NS_OK;
6202 // We need to notify TSF of layout change even if the document is locked.
6203 // So, don't use MaybeFlushPendingNotifications() for flushing pending
6204 // layout change.
6205 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6206 ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
6207 "NotifyTSFOfLayoutChange()...",
6208 this));
6209 if (NS_WARN_IF(!NotifyTSFOfLayoutChange())) {
6210 rv = NS_ERROR_FAILURE;
6213 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6214 ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
6215 "MaybeFlushPendingNotifications()...",
6216 this));
6217 MaybeFlushPendingNotifications();
6219 return rv;
6222 bool TSFTextStore::NotifyTSFOfLayoutChange() {
6223 MOZ_ASSERT(!mDestroyed);
6225 // If we're waiting a query of layout information from TIP, it means that
6226 // we've returned TS_E_NOLAYOUT error.
6227 bool returnedNoLayoutError = mHasReturnedNoLayoutError || mWaitingQueryLayout;
6229 // If we returned TS_E_NOLAYOUT, TIP should query the computed layout again.
6230 mWaitingQueryLayout = returnedNoLayoutError;
6232 // For avoiding to call this method again at unlocking the document during
6233 // calls of OnLayoutChange(), reset mHasReturnedNoLayoutError.
6234 mHasReturnedNoLayoutError = false;
6236 // Now, layout has been computed. We should notify mContentForTSF for
6237 // making GetTextExt() and GetACPFromPoint() not return TS_E_NOLAYOUT.
6238 if (mContentForTSF.IsInitialized()) {
6239 mContentForTSF.OnLayoutChanged();
6242 if (IMEHandler::NeedsToCreateNativeCaret()) {
6243 // If we're available, we should create native caret instead of IMEHandler
6244 // because we may have some cache to do it.
6245 CreateNativeCaret();
6246 } else {
6247 // Now, the caret position is different from ours. Destroy the native caret
6248 // if we've create it only for GetTextExt().
6249 IMEHandler::MaybeDestroyNativeCaret();
6252 // This method should return true if either way succeeds.
6253 bool ret = true;
6255 if (mSink) {
6256 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6257 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6258 "calling ITextStoreACPSink::OnLayoutChange()...",
6259 this));
6260 RefPtr<ITextStoreACPSink> sink = mSink;
6261 HRESULT hr = sink->OnLayoutChange(TS_LC_CHANGE, TEXTSTORE_DEFAULT_VIEW);
6262 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6263 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6264 "called ITextStoreACPSink::OnLayoutChange()",
6265 this));
6266 ret = SUCCEEDED(hr);
6269 // The layout change caused by composition string change should cause
6270 // calling ITfContextOwnerServices::OnLayoutChange() too.
6271 if (returnedNoLayoutError && mContext) {
6272 RefPtr<ITfContextOwnerServices> service;
6273 mContext->QueryInterface(IID_ITfContextOwnerServices,
6274 getter_AddRefs(service));
6275 if (service) {
6276 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6277 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6278 "calling ITfContextOwnerServices::OnLayoutChange()...",
6279 this));
6280 HRESULT hr = service->OnLayoutChange();
6281 ret = ret && SUCCEEDED(hr);
6282 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6283 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6284 "called ITfContextOwnerServices::OnLayoutChange()",
6285 this));
6289 if (!mWidget || mWidget->Destroyed()) {
6290 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6291 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6292 "the widget is destroyed during calling OnLayoutChange()",
6293 this));
6294 return ret;
6297 if (mDestroyed) {
6298 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6299 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6300 "the TSFTextStore instance is destroyed during calling "
6301 "OnLayoutChange()",
6302 this));
6303 return ret;
6306 // If we returned TS_E_NOLAYOUT again, we need another call of
6307 // OnLayoutChange() later. So, let's wait a query from TIP.
6308 if (mHasReturnedNoLayoutError) {
6309 mWaitingQueryLayout = true;
6312 if (!mWaitingQueryLayout) {
6313 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6314 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6315 "succeeded notifying TIP of our layout change",
6316 this));
6317 return ret;
6320 // If we believe that TIP needs to retry to retrieve our layout information
6321 // later, we should call it with ::PostMessage() hack.
6322 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6323 ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
6324 "posing MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE for calling "
6325 "OnLayoutChange() again...",
6326 this));
6327 ::PostMessage(mWidget->GetWindowHandle(), MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE,
6328 reinterpret_cast<WPARAM>(this), 0);
6330 return true;
6333 void TSFTextStore::NotifyTSFOfLayoutChangeAgain() {
6334 // Don't notify TSF of layout change after destroyed.
6335 if (mDestroyed) {
6336 mWaitingQueryLayout = false;
6337 return;
6340 // Before preforming this method, TIP has accessed our layout information by
6341 // itself. In such case, we don't need to call OnLayoutChange() anymore.
6342 if (!mWaitingQueryLayout) {
6343 return;
6346 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6347 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6348 "calling NotifyTSFOfLayoutChange()...",
6349 this));
6350 NotifyTSFOfLayoutChange();
6352 // If TIP didn't retrieved our layout information during a call of
6353 // NotifyTSFOfLayoutChange(), it means that the TIP already gave up to
6354 // retry to retrieve layout information or doesn't necessary it anymore.
6355 // But don't forget that the call may have caused returning TS_E_NOLAYOUT
6356 // error again. In such case we still need to call OnLayoutChange() later.
6357 if (!mHasReturnedNoLayoutError && mWaitingQueryLayout) {
6358 mWaitingQueryLayout = false;
6359 MOZ_LOG(sTextStoreLog, LogLevel::Warning,
6360 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6361 "called NotifyTSFOfLayoutChange() but TIP didn't retry to "
6362 "retrieve the layout information",
6363 this));
6364 } else {
6365 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6366 ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
6367 "called NotifyTSFOfLayoutChange()",
6368 this));
6372 nsresult TSFTextStore::OnUpdateCompositionInternal() {
6373 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6374 ("0x%p TSFTextStore::OnUpdateCompositionInternal(), "
6375 "mDestroyed=%s, mDeferNotifyingTSF=%s",
6376 this, GetBoolName(mDestroyed), GetBoolName(mDeferNotifyingTSF)));
6378 // There are nothing to do after destroyed.
6379 if (mDestroyed) {
6380 return NS_OK;
6383 // Update cached data now because all pending events have been handled now.
6384 mContentForTSF.OnCompositionEventsHandled();
6386 // If composition is completely finished both in TSF/TIP and the focused
6387 // editor which may be in a remote process, we can clear the cache and don't
6388 // have it until starting next composition.
6389 if (!mComposition.IsComposing() && !IsHandlingComposition()) {
6390 mDeferClearingContentForTSF = false;
6392 mDeferNotifyingTSF = false;
6393 MaybeFlushPendingNotifications();
6395 // If we're available, we should create native caret instead of IMEHandler
6396 // because we may have some cache to do it.
6397 if (IMEHandler::NeedsToCreateNativeCaret()) {
6398 CreateNativeCaret();
6401 return NS_OK;
6404 nsresult TSFTextStore::OnMouseButtonEventInternal(
6405 const IMENotification& aIMENotification) {
6406 if (mDestroyed) {
6407 // If this instance is already destroyed, we shouldn't notify TSF of any
6408 // events.
6409 return NS_OK;
6412 if (mMouseTrackers.IsEmpty()) {
6413 return NS_OK;
6416 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6417 ("0x%p TSFTextStore::OnMouseButtonEventInternal("
6418 "aIMENotification={ mEventMessage=%s, mOffset=%u, mCursorPos={ "
6419 "mX=%d, mY=%d }, mCharRect={ mX=%d, mY=%d, mWidth=%d, mHeight=%d }, "
6420 "mButton=%s, mButtons=%s, mModifiers=%s })",
6421 this, ToChar(aIMENotification.mMouseButtonEventData.mEventMessage),
6422 aIMENotification.mMouseButtonEventData.mOffset,
6423 aIMENotification.mMouseButtonEventData.mCursorPos.mX,
6424 aIMENotification.mMouseButtonEventData.mCursorPos.mY,
6425 aIMENotification.mMouseButtonEventData.mCharRect.mX,
6426 aIMENotification.mMouseButtonEventData.mCharRect.mY,
6427 aIMENotification.mMouseButtonEventData.mCharRect.mWidth,
6428 aIMENotification.mMouseButtonEventData.mCharRect.mHeight,
6429 GetMouseButtonName(aIMENotification.mMouseButtonEventData.mButton),
6430 GetMouseButtonsName(aIMENotification.mMouseButtonEventData.mButtons)
6431 .get(),
6432 GetModifiersName(aIMENotification.mMouseButtonEventData.mModifiers)
6433 .get()));
6435 uint32_t offset = aIMENotification.mMouseButtonEventData.mOffset;
6436 nsIntRect charRect =
6437 aIMENotification.mMouseButtonEventData.mCharRect.AsIntRect();
6438 nsIntPoint cursorPos =
6439 aIMENotification.mMouseButtonEventData.mCursorPos.AsIntPoint();
6440 ULONG quadrant = 1;
6441 if (charRect.Width() > 0) {
6442 int32_t cursorXInChar = cursorPos.x - charRect.X();
6443 quadrant = cursorXInChar * 4 / charRect.Width();
6444 quadrant = (quadrant + 2) % 4;
6446 ULONG edge = quadrant < 2 ? offset + 1 : offset;
6447 DWORD buttonStatus = 0;
6448 bool isMouseUp =
6449 aIMENotification.mMouseButtonEventData.mEventMessage == eMouseUp;
6450 if (!isMouseUp) {
6451 switch (aIMENotification.mMouseButtonEventData.mButton) {
6452 case MouseButton::eLeft:
6453 buttonStatus = MK_LBUTTON;
6454 break;
6455 case MouseButton::eMiddle:
6456 buttonStatus = MK_MBUTTON;
6457 break;
6458 case MouseButton::eRight:
6459 buttonStatus = MK_RBUTTON;
6460 break;
6463 if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_CONTROL) {
6464 buttonStatus |= MK_CONTROL;
6466 if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_SHIFT) {
6467 buttonStatus |= MK_SHIFT;
6469 for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
6470 MouseTracker& tracker = mMouseTrackers[i];
6471 if (!tracker.IsUsing() || !tracker.InRange(offset)) {
6472 continue;
6474 if (tracker.OnMouseButtonEvent(edge - tracker.RangeStart(), quadrant,
6475 buttonStatus)) {
6476 return NS_SUCCESS_EVENT_CONSUMED;
6479 return NS_OK;
6482 void TSFTextStore::CreateNativeCaret() {
6483 MOZ_ASSERT(!IMEHandler::IsA11yHandlingNativeCaret());
6485 IMEHandler::MaybeDestroyNativeCaret();
6487 // Don't create native caret after destroyed.
6488 if (mDestroyed) {
6489 return;
6492 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6493 ("0x%p TSFTextStore::CreateNativeCaret(), "
6494 "mComposition.IsComposing()=%s",
6495 this, GetBoolName(mComposition.IsComposing())));
6497 Selection& selectionForTSF = SelectionForTSFRef();
6498 if (selectionForTSF.IsDirty()) {
6499 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6500 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6501 "SelectionForTSFRef() failure",
6502 this));
6503 return;
6506 WidgetQueryContentEvent queryCaretRect(true, eQueryCaretRect, mWidget);
6507 mWidget->InitEvent(queryCaretRect);
6509 WidgetQueryContentEvent::Options options;
6510 // XXX If this is called without composition and the selection isn't
6511 // collapsed, is it OK?
6512 int64_t caretOffset = selectionForTSF.MaxOffset();
6513 if (mComposition.IsComposing()) {
6514 // If there is a composition, use insertion point relative query for
6515 // deciding caret position because composition might be at different
6516 // position where TSFTextStore believes it at.
6517 options.mRelativeToInsertionPoint = true;
6518 caretOffset -= mComposition.mStart;
6519 } else if (!CanAccessActualContentDirectly()) {
6520 // If TSF/TIP cannot access actual content directly, there may be pending
6521 // text and/or selection changes which have not been notified TSF yet.
6522 // Therefore, we should use relative to insertion point query since
6523 // TSF/TIP computes the offset from the cached selection.
6524 options.mRelativeToInsertionPoint = true;
6525 caretOffset -= mSelectionForTSF.StartOffset();
6527 queryCaretRect.InitForQueryCaretRect(caretOffset, options);
6529 DispatchEvent(queryCaretRect);
6530 if (NS_WARN_IF(!queryCaretRect.mSucceeded)) {
6531 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6532 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6533 "eQueryCaretRect failure (offset=%d)",
6534 this, caretOffset));
6535 return;
6538 if (!IMEHandler::CreateNativeCaret(static_cast<nsWindow*>(mWidget.get()),
6539 queryCaretRect.mReply.mRect)) {
6540 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6541 ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
6542 "IMEHandler::CreateNativeCaret() failure",
6543 this));
6544 return;
6548 void TSFTextStore::CommitCompositionInternal(bool aDiscard) {
6549 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6550 ("0x%p TSFTextStore::CommitCompositionInternal(aDiscard=%s), "
6551 "mSink=0x%p, mContext=0x%p, mComposition.mView=0x%p, "
6552 "mComposition.mString=\"%s\"",
6553 this, GetBoolName(aDiscard), mSink.get(), mContext.get(),
6554 mComposition.mView.get(),
6555 GetEscapedUTF8String(mComposition.mString).get()));
6557 // If the document is locked, TSF will fail to commit composition since
6558 // TSF needs another document lock. So, let's put off the request.
6559 // Note that TextComposition will commit composition in the focused editor
6560 // with the latest composition string for web apps and waits asynchronous
6561 // committing messages. Therefore, we can and need to perform this
6562 // asynchronously.
6563 if (IsReadLocked()) {
6564 if (mDeferCommittingComposition || mDeferCancellingComposition) {
6565 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6566 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6567 "does nothing because already called and waiting unlock...",
6568 this));
6569 return;
6571 if (aDiscard) {
6572 mDeferCancellingComposition = true;
6573 } else {
6574 mDeferCommittingComposition = true;
6576 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6577 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6578 "putting off to request to %s composition after unlocking the "
6579 "document",
6580 this, aDiscard ? "cancel" : "commit"));
6581 return;
6584 if (mComposition.IsComposing() && aDiscard) {
6585 LONG endOffset = mComposition.EndOffset();
6586 mComposition.mString.Truncate(0);
6587 // Note that don't notify TSF of text change after this is destroyed.
6588 if (mSink && !mDestroyed) {
6589 TS_TEXTCHANGE textChange;
6590 textChange.acpStart = mComposition.mStart;
6591 textChange.acpOldEnd = endOffset;
6592 textChange.acpNewEnd = mComposition.mStart;
6593 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6594 ("0x%p TSFTextStore::CommitCompositionInternal(), calling"
6595 "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
6596 "acpNewEnd=%ld })...",
6597 this, textChange.acpStart, textChange.acpOldEnd,
6598 textChange.acpNewEnd));
6599 RefPtr<ITextStoreACPSink> sink = mSink;
6600 sink->OnTextChange(0, &textChange);
6603 // Terminate two contexts, the base context (mContext) and the top
6604 // if the top context is not the same as the base context
6605 RefPtr<ITfContext> context = mContext;
6606 do {
6607 if (context) {
6608 RefPtr<ITfContextOwnerCompositionServices> services;
6609 context->QueryInterface(IID_ITfContextOwnerCompositionServices,
6610 getter_AddRefs(services));
6611 if (services) {
6612 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6613 ("0x%p TSFTextStore::CommitCompositionInternal(), "
6614 "requesting TerminateComposition() for the context 0x%p...",
6615 this, context.get()));
6616 services->TerminateComposition(nullptr);
6619 if (context != mContext) break;
6620 if (mDocumentMgr) mDocumentMgr->GetTop(getter_AddRefs(context));
6621 } while (context != mContext);
6624 static bool GetCompartment(IUnknown* pUnk, const GUID& aID,
6625 ITfCompartment** aCompartment) {
6626 if (!pUnk) return false;
6628 RefPtr<ITfCompartmentMgr> compMgr;
6629 pUnk->QueryInterface(IID_ITfCompartmentMgr, getter_AddRefs(compMgr));
6630 if (!compMgr) return false;
6632 return SUCCEEDED(compMgr->GetCompartment(aID, aCompartment)) &&
6633 (*aCompartment) != nullptr;
6636 // static
6637 void TSFTextStore::SetIMEOpenState(bool aState) {
6638 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6639 ("TSFTextStore::SetIMEOpenState(aState=%s)", GetBoolName(aState)));
6641 if (!sThreadMgr) {
6642 return;
6645 RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
6646 if (NS_WARN_IF(!comp)) {
6647 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6648 (" TSFTextStore::SetIMEOpenState() FAILED due to"
6649 "no compartment available"));
6650 return;
6653 VARIANT variant;
6654 variant.vt = VT_I4;
6655 variant.lVal = aState;
6656 HRESULT hr = comp->SetValue(sClientId, &variant);
6657 if (NS_WARN_IF(FAILED(hr))) {
6658 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6659 (" TSFTextStore::SetIMEOpenState() FAILED due to "
6660 "ITfCompartment::SetValue() failure, hr=0x%08X",
6661 hr));
6662 return;
6664 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6665 (" TSFTextStore::SetIMEOpenState(), setting "
6666 "0x%04X to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...",
6667 variant.lVal));
6670 // static
6671 bool TSFTextStore::GetIMEOpenState() {
6672 if (!sThreadMgr) {
6673 return false;
6676 RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
6677 if (NS_WARN_IF(!comp)) {
6678 return false;
6681 VARIANT variant;
6682 ::VariantInit(&variant);
6683 HRESULT hr = comp->GetValue(&variant);
6684 if (NS_WARN_IF(FAILED(hr))) {
6685 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6686 ("TSFTextStore::GetIMEOpenState() FAILED due to "
6687 "ITfCompartment::GetValue() failure, hr=0x%08X",
6688 hr));
6689 return false;
6691 // Until IME is open in this process, the result may be empty.
6692 if (variant.vt == VT_EMPTY) {
6693 return false;
6695 if (NS_WARN_IF(variant.vt != VT_I4)) {
6696 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6697 ("TSFTextStore::GetIMEOpenState() FAILED due to "
6698 "invalid result of ITfCompartment::GetValue()"));
6699 ::VariantClear(&variant);
6700 return false;
6703 return variant.lVal != 0;
6706 // static
6707 void TSFTextStore::SetInputContext(nsWindowBase* aWidget,
6708 const InputContext& aContext,
6709 const InputContextAction& aAction) {
6710 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6711 ("TSFTextStore::SetInputContext(aWidget=%p, "
6712 "aContext=%s, aAction.mFocusChange=%s), "
6713 "sEnabledTextStore(0x%p)={ mWidget=0x%p }, ThinksHavingFocus()=%s",
6714 aWidget, GetInputContextString(aContext).get(),
6715 GetFocusChangeName(aAction.mFocusChange), sEnabledTextStore.get(),
6716 sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr,
6717 GetBoolName(ThinksHavingFocus())));
6719 // When this is called when the widget is created, there is nothing to do.
6720 if (aAction.mFocusChange == InputContextAction::WIDGET_CREATED) {
6721 return;
6724 NS_ENSURE_TRUE_VOID(IsInTSFMode());
6726 if (aAction.mFocusChange != InputContextAction::FOCUS_NOT_CHANGED) {
6727 if (sEnabledTextStore) {
6728 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
6729 textStore->SetInputScope(aContext.mHTMLInputType,
6730 aContext.mHTMLInputInputmode,
6731 aContext.mInPrivateBrowsing);
6733 return;
6736 // If focus isn't actually changed but the enabled state is changed,
6737 // emulate the focus move.
6738 if (!ThinksHavingFocus() && aContext.mIMEState.IsEditable()) {
6739 OnFocusChange(true, aWidget, aContext);
6740 } else if (ThinksHavingFocus() && !aContext.mIMEState.IsEditable()) {
6741 OnFocusChange(false, aWidget, aContext);
6745 // static
6746 void TSFTextStore::MarkContextAsKeyboardDisabled(ITfContext* aContext) {
6747 VARIANT variant_int4_value1;
6748 variant_int4_value1.vt = VT_I4;
6749 variant_int4_value1.lVal = 1;
6751 RefPtr<ITfCompartment> comp;
6752 if (!GetCompartment(aContext, GUID_COMPARTMENT_KEYBOARD_DISABLED,
6753 getter_AddRefs(comp))) {
6754 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6755 ("TSFTextStore::MarkContextAsKeyboardDisabled() failed"
6756 "aContext=0x%p...",
6757 aContext));
6758 return;
6761 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6762 ("TSFTextStore::MarkContextAsKeyboardDisabled(), setting "
6763 "to disable context 0x%p...",
6764 aContext));
6765 comp->SetValue(sClientId, &variant_int4_value1);
6768 // static
6769 void TSFTextStore::MarkContextAsEmpty(ITfContext* aContext) {
6770 VARIANT variant_int4_value1;
6771 variant_int4_value1.vt = VT_I4;
6772 variant_int4_value1.lVal = 1;
6774 RefPtr<ITfCompartment> comp;
6775 if (!GetCompartment(aContext, GUID_COMPARTMENT_EMPTYCONTEXT,
6776 getter_AddRefs(comp))) {
6777 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6778 ("TSFTextStore::MarkContextAsEmpty() failed"
6779 "aContext=0x%p...",
6780 aContext));
6781 return;
6784 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
6785 ("TSFTextStore::MarkContextAsEmpty(), setting "
6786 "to mark empty context 0x%p...",
6787 aContext));
6788 comp->SetValue(sClientId, &variant_int4_value1);
6791 // static
6792 void TSFTextStore::Initialize() {
6793 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6794 ("TSFTextStore::Initialize() is called..."));
6796 if (sThreadMgr) {
6797 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6798 (" TSFTextStore::Initialize() FAILED due to already initialized"));
6799 return;
6802 bool enableTsf = Preferences::GetBool(kPrefNameEnableTSF, false);
6803 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6804 (" TSFTextStore::Initialize(), TSF is %s",
6805 enableTsf ? "enabled" : "disabled"));
6806 if (!enableTsf) {
6807 return;
6810 RefPtr<ITfThreadMgr> threadMgr;
6811 HRESULT hr =
6812 ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_INPROC_SERVER,
6813 IID_ITfThreadMgr, getter_AddRefs(threadMgr));
6814 if (FAILED(hr) || !threadMgr) {
6815 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6816 (" TSFTextStore::Initialize() FAILED to "
6817 "create the thread manager, hr=0x%08X",
6818 hr));
6819 return;
6822 hr = threadMgr->Activate(&sClientId);
6823 if (FAILED(hr)) {
6824 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6825 (" TSFTextStore::Initialize() FAILED to activate, hr=0x%08X", hr));
6826 return;
6829 RefPtr<ITfDocumentMgr> disabledDocumentMgr;
6830 hr = threadMgr->CreateDocumentMgr(getter_AddRefs(disabledDocumentMgr));
6831 if (FAILED(hr) || !disabledDocumentMgr) {
6832 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6833 (" TSFTextStore::Initialize() FAILED to create "
6834 "a document manager for disabled mode, hr=0x%08X",
6835 hr));
6836 return;
6839 RefPtr<ITfContext> disabledContext;
6840 DWORD editCookie = 0;
6841 hr = disabledDocumentMgr->CreateContext(
6842 sClientId, 0, nullptr, getter_AddRefs(disabledContext), &editCookie);
6843 if (FAILED(hr) || !disabledContext) {
6844 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6845 (" TSFTextStore::Initialize() FAILED to create "
6846 "a context for disabled mode, hr=0x%08X",
6847 hr));
6848 return;
6851 MarkContextAsKeyboardDisabled(disabledContext);
6852 MarkContextAsEmpty(disabledContext);
6854 sThreadMgr = threadMgr;
6855 sDisabledDocumentMgr = disabledDocumentMgr;
6856 sDisabledContext = disabledContext;
6858 MOZ_LOG(sTextStoreLog, LogLevel::Info,
6859 (" TSFTextStore::Initialize(), sThreadMgr=0x%p, "
6860 "sClientId=0x%08X, sDisabledDocumentMgr=0x%p, sDisabledContext=%p",
6861 sThreadMgr.get(), sClientId, sDisabledDocumentMgr.get(),
6862 sDisabledContext.get()));
6865 // static
6866 already_AddRefed<ITfThreadMgr> TSFTextStore::GetThreadMgr() {
6867 RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
6868 return threadMgr.forget();
6871 // static
6872 already_AddRefed<ITfMessagePump> TSFTextStore::GetMessagePump() {
6873 static bool sInitialized = false;
6874 if (!sThreadMgr) {
6875 return nullptr;
6877 if (sMessagePump) {
6878 RefPtr<ITfMessagePump> messagePump = sMessagePump;
6879 return messagePump.forget();
6881 // If it tried to retrieve ITfMessagePump from sThreadMgr but it failed,
6882 // we shouldn't retry it at every message due to performance reason.
6883 // Although this shouldn't occur actually.
6884 if (sInitialized) {
6885 return nullptr;
6887 sInitialized = true;
6889 RefPtr<ITfMessagePump> messagePump;
6890 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfMessagePump,
6891 getter_AddRefs(messagePump));
6892 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!messagePump)) {
6893 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6894 ("TSFTextStore::GetMessagePump() FAILED to "
6895 "QI message pump from the thread manager, hr=0x%08X",
6896 hr));
6897 return nullptr;
6899 sMessagePump = messagePump;
6900 return messagePump.forget();
6903 // static
6904 already_AddRefed<ITfDisplayAttributeMgr>
6905 TSFTextStore::GetDisplayAttributeMgr() {
6906 RefPtr<ITfDisplayAttributeMgr> displayAttributeMgr;
6907 if (sDisplayAttrMgr) {
6908 displayAttributeMgr = sDisplayAttrMgr;
6909 return displayAttributeMgr.forget();
6912 HRESULT hr = ::CoCreateInstance(
6913 CLSID_TF_DisplayAttributeMgr, nullptr, CLSCTX_INPROC_SERVER,
6914 IID_ITfDisplayAttributeMgr, getter_AddRefs(displayAttributeMgr));
6915 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!displayAttributeMgr)) {
6916 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6917 ("TSFTextStore::GetDisplayAttributeMgr() FAILED to create "
6918 "a display attribute manager instance, hr=0x%08X",
6919 hr));
6920 return nullptr;
6922 sDisplayAttrMgr = displayAttributeMgr;
6923 return displayAttributeMgr.forget();
6926 // static
6927 already_AddRefed<ITfCategoryMgr> TSFTextStore::GetCategoryMgr() {
6928 RefPtr<ITfCategoryMgr> categoryMgr;
6929 if (sCategoryMgr) {
6930 categoryMgr = sCategoryMgr;
6931 return categoryMgr.forget();
6933 HRESULT hr =
6934 ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, CLSCTX_INPROC_SERVER,
6935 IID_ITfCategoryMgr, getter_AddRefs(categoryMgr));
6936 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!categoryMgr)) {
6937 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6938 ("TSFTextStore::GetCategoryMgr() FAILED to create "
6939 "a category manager instance, hr=0x%08X",
6940 hr));
6941 return nullptr;
6943 sCategoryMgr = categoryMgr;
6944 return categoryMgr.forget();
6947 // static
6948 already_AddRefed<ITfCompartment> TSFTextStore::GetCompartmentForOpenClose() {
6949 if (sCompartmentForOpenClose) {
6950 RefPtr<ITfCompartment> compartment = sCompartmentForOpenClose;
6951 return compartment.forget();
6954 if (!sThreadMgr) {
6955 return nullptr;
6958 RefPtr<ITfCompartmentMgr> compartmentMgr;
6959 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfCompartmentMgr,
6960 getter_AddRefs(compartmentMgr));
6961 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartmentMgr)) {
6962 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6963 ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
6964 "sThreadMgr not having ITfCompartmentMgr, hr=0x%08X",
6965 hr));
6966 return nullptr;
6969 RefPtr<ITfCompartment> compartment;
6970 hr = compartmentMgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
6971 getter_AddRefs(compartment));
6972 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartment)) {
6973 MOZ_LOG(sTextStoreLog, LogLevel::Error,
6974 ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
6975 "ITfCompartmentMgr::GetCompartment() failuere, hr=0x%08X",
6976 hr));
6977 return nullptr;
6980 sCompartmentForOpenClose = compartment;
6981 return compartment.forget();
6984 // static
6985 already_AddRefed<ITfInputProcessorProfiles>
6986 TSFTextStore::GetInputProcessorProfiles() {
6987 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles;
6988 if (sInputProcessorProfiles) {
6989 inputProcessorProfiles = sInputProcessorProfiles;
6990 return inputProcessorProfiles.forget();
6992 // XXX MSDN documents that ITfInputProcessorProfiles is available only on
6993 // desktop apps. However, there is no known way to obtain
6994 // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles
6995 // instance.
6996 HRESULT hr = ::CoCreateInstance(
6997 CLSID_TF_InputProcessorProfiles, nullptr, CLSCTX_INPROC_SERVER,
6998 IID_ITfInputProcessorProfiles, getter_AddRefs(inputProcessorProfiles));
6999 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!inputProcessorProfiles)) {
7000 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7001 ("TSFTextStore::GetInputProcessorProfiles() FAILED to create input "
7002 "processor profiles, hr=0x%08X",
7003 hr));
7004 return nullptr;
7006 sInputProcessorProfiles = inputProcessorProfiles;
7007 return inputProcessorProfiles.forget();
7010 // static
7011 void TSFTextStore::Terminate() {
7012 MOZ_LOG(sTextStoreLog, LogLevel::Info, ("TSFTextStore::Terminate()"));
7014 TSFStaticSink::Shutdown();
7016 sDisplayAttrMgr = nullptr;
7017 sCategoryMgr = nullptr;
7018 sEnabledTextStore = nullptr;
7019 sDisabledDocumentMgr = nullptr;
7020 sDisabledContext = nullptr;
7021 sCompartmentForOpenClose = nullptr;
7022 sInputProcessorProfiles = nullptr;
7023 sClientId = 0;
7024 if (sThreadMgr) {
7025 sThreadMgr->Deactivate();
7026 sThreadMgr = nullptr;
7027 sMessagePump = nullptr;
7028 sKeystrokeMgr = nullptr;
7032 // static
7033 bool TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg) {
7034 if (!sThreadMgr) {
7035 return false; // not in TSF mode
7037 static bool sInitialized = false;
7038 if (!sKeystrokeMgr) {
7039 // If it tried to retrieve ITfKeystrokeMgr from sThreadMgr but it failed,
7040 // we shouldn't retry it at every keydown nor keyup due to performance
7041 // reason. Although this shouldn't occur actually.
7042 if (sInitialized) {
7043 return false;
7045 sInitialized = true;
7046 RefPtr<ITfKeystrokeMgr> keystrokeMgr;
7047 HRESULT hr = sThreadMgr->QueryInterface(IID_ITfKeystrokeMgr,
7048 getter_AddRefs(keystrokeMgr));
7049 if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!keystrokeMgr)) {
7050 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7051 ("TSFTextStore::ProcessRawKeyMessage() FAILED to "
7052 "QI keystroke manager from the thread manager, hr=0x%08X",
7053 hr));
7054 return false;
7056 sKeystrokeMgr = keystrokeMgr.forget();
7059 if (aMsg.message == WM_KEYDOWN) {
7060 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
7061 if (textStore) {
7062 textStore->OnStartToHandleKeyMessage();
7063 if (NS_WARN_IF(textStore != sEnabledTextStore)) {
7064 // Let's handle the key message with new focused TSFTextStore.
7065 textStore = sEnabledTextStore;
7068 AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
7069 AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
7070 sHandlingKeyMsg = &aMsg;
7071 sIsKeyboardEventDispatched = false;
7072 BOOL eaten;
7073 RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
7074 HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten);
7075 if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
7076 return false;
7078 hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
7079 if (textStore) {
7080 textStore->OnEndHandlingKeyMessage(!!eaten);
7082 return SUCCEEDED(hr) &&
7083 (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
7085 if (aMsg.message == WM_KEYUP) {
7086 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
7087 if (textStore) {
7088 textStore->OnStartToHandleKeyMessage();
7089 if (NS_WARN_IF(textStore != sEnabledTextStore)) {
7090 // Let's handle the key message with new focused TSFTextStore.
7091 textStore = sEnabledTextStore;
7094 AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
7095 AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
7096 sHandlingKeyMsg = &aMsg;
7097 sIsKeyboardEventDispatched = false;
7098 BOOL eaten;
7099 RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
7100 HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten);
7101 if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
7102 return false;
7104 hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten);
7105 if (textStore) {
7106 textStore->OnEndHandlingKeyMessage(!!eaten);
7108 return SUCCEEDED(hr) &&
7109 (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
7111 return false;
7114 // static
7115 void TSFTextStore::ProcessMessage(nsWindowBase* aWindow, UINT aMessage,
7116 WPARAM& aWParam, LPARAM& aLParam,
7117 MSGResult& aResult) {
7118 switch (aMessage) {
7119 case WM_IME_SETCONTEXT:
7120 // If a windowless plugin had focus and IME was handled on it, composition
7121 // window was set the position. After that, even in TSF mode, WinXP keeps
7122 // to use composition window at the position if the active IME is not
7123 // aware TSF. For avoiding this issue, we need to hide the composition
7124 // window here.
7125 if (aWParam) {
7126 aLParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
7128 break;
7129 case WM_ENTERIDLE:
7130 // When an modal dialog such as a file picker is open, composition
7131 // should be committed because IME might be used on it.
7132 if (!IsComposingOn(aWindow)) {
7133 break;
7135 CommitComposition(false);
7136 break;
7137 case MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE: {
7138 TSFTextStore* maybeTextStore = reinterpret_cast<TSFTextStore*>(aWParam);
7139 if (maybeTextStore == sEnabledTextStore) {
7140 RefPtr<TSFTextStore> textStore(maybeTextStore);
7141 textStore->NotifyTSFOfLayoutChangeAgain();
7143 break;
7148 // static
7149 bool TSFTextStore::IsIMM_IMEActive() {
7150 return TSFStaticSink::IsIMM_IMEActive();
7153 // static
7154 bool TSFTextStore::IsMSJapaneseIMEActive() {
7155 return TSFStaticSink::IsMSJapaneseIMEActive();
7158 // static
7159 bool TSFTextStore::IsGoogleJapaneseInputActive() {
7160 return TSFStaticSink::IsGoogleJapaneseInputActive();
7163 /******************************************************************/
7164 /* TSFTextStore::Composition */
7165 /******************************************************************/
7167 void TSFTextStore::Composition::Start(ITfCompositionView* aCompositionView,
7168 LONG aCompositionStartOffset,
7169 const nsAString& aCompositionString) {
7170 mView = aCompositionView;
7171 mString = aCompositionString;
7172 mStart = aCompositionStartOffset;
7175 void TSFTextStore::Composition::End() {
7176 mView = nullptr;
7177 mString.Truncate();
7180 /******************************************************************************
7181 * TSFTextStore::Content
7182 *****************************************************************************/
7184 const nsDependentSubstring TSFTextStore::Content::GetSelectedText() const {
7185 MOZ_ASSERT(mInitialized);
7186 return GetSubstring(static_cast<uint32_t>(mSelection.StartOffset()),
7187 static_cast<uint32_t>(mSelection.Length()));
7190 const nsDependentSubstring TSFTextStore::Content::GetSubstring(
7191 uint32_t aStart, uint32_t aLength) const {
7192 MOZ_ASSERT(mInitialized);
7193 return nsDependentSubstring(mText, aStart, aLength);
7196 void TSFTextStore::Content::ReplaceSelectedTextWith(const nsAString& aString) {
7197 MOZ_ASSERT(mInitialized);
7198 ReplaceTextWith(mSelection.StartOffset(), mSelection.Length(), aString);
7201 inline uint32_t FirstDifferentCharOffset(const nsAString& aStr1,
7202 const nsAString& aStr2) {
7203 MOZ_ASSERT(aStr1 != aStr2);
7204 uint32_t i = 0;
7205 uint32_t minLength = std::min(aStr1.Length(), aStr2.Length());
7206 for (; i < minLength && aStr1[i] == aStr2[i]; i++) {
7207 /* nothing to do */
7209 return i;
7212 void TSFTextStore::Content::ReplaceTextWith(LONG aStart, LONG aLength,
7213 const nsAString& aReplaceString) {
7214 MOZ_ASSERT(mInitialized);
7215 const nsDependentSubstring replacedString = GetSubstring(
7216 static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength));
7217 if (aReplaceString != replacedString) {
7218 uint32_t firstDifferentOffset = mMinTextModifiedOffset;
7219 if (mComposition.IsComposing()) {
7220 // Emulate text insertion during compositions, because during a
7221 // composition, editor expects the whole composition string to
7222 // be sent in eCompositionChange, not just the inserted part.
7223 // The actual eCompositionChange will be sent in SetSelection
7224 // or OnUpdateComposition.
7225 MOZ_ASSERT(aStart >= mComposition.mStart);
7226 MOZ_ASSERT(aStart + aLength <= mComposition.EndOffset());
7227 mComposition.mString.Replace(
7228 static_cast<uint32_t>(aStart - mComposition.mStart),
7229 static_cast<uint32_t>(aLength), aReplaceString);
7230 // TIP may set composition string twice or more times during a document
7231 // lock. Therefore, we should compute the first difference offset with
7232 // mLastCompositionString.
7233 if (mComposition.mString != mLastCompositionString) {
7234 firstDifferentOffset = mComposition.mStart +
7235 FirstDifferentCharOffset(mComposition.mString,
7236 mLastCompositionString);
7237 // The previous change to the composition string is canceled.
7238 if (mMinTextModifiedOffset >=
7239 static_cast<uint32_t>(mComposition.mStart) &&
7240 mMinTextModifiedOffset < firstDifferentOffset) {
7241 mMinTextModifiedOffset = firstDifferentOffset;
7243 } else if (mMinTextModifiedOffset >=
7244 static_cast<uint32_t>(mComposition.mStart) &&
7245 mMinTextModifiedOffset <
7246 static_cast<uint32_t>(mComposition.EndOffset())) {
7247 // The previous change to the composition string is canceled.
7248 mMinTextModifiedOffset = firstDifferentOffset =
7249 mComposition.EndOffset();
7251 mLatestCompositionEndOffset = mComposition.EndOffset();
7252 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
7253 ("0x%p TSFTextStore::Content::ReplaceTextWith(aStart=%d, "
7254 "aLength=%d, aReplaceString=\"%s\"), mComposition={ mStart=%d, "
7255 "mString=\"%s\" }, mLastCompositionString=\"%s\", "
7256 "mMinTextModifiedOffset=%u, firstDifferentOffset=%u",
7257 this, aStart, aLength,
7258 GetEscapedUTF8String(aReplaceString).get(), mComposition.mStart,
7259 GetEscapedUTF8String(mComposition.mString).get(),
7260 GetEscapedUTF8String(mLastCompositionString).get(),
7261 mMinTextModifiedOffset, firstDifferentOffset));
7262 } else {
7263 firstDifferentOffset =
7264 static_cast<uint32_t>(aStart) +
7265 FirstDifferentCharOffset(aReplaceString, replacedString);
7267 mMinTextModifiedOffset =
7268 std::min(mMinTextModifiedOffset, firstDifferentOffset);
7269 mText.Replace(static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength),
7270 aReplaceString);
7272 // Selection should be collapsed at the end of the inserted string.
7273 mSelection.CollapseAt(static_cast<uint32_t>(aStart) +
7274 aReplaceString.Length());
7277 void TSFTextStore::Content::StartComposition(
7278 ITfCompositionView* aCompositionView, const PendingAction& aCompStart,
7279 bool aPreserveSelection) {
7280 MOZ_ASSERT(mInitialized);
7281 MOZ_ASSERT(aCompositionView);
7282 MOZ_ASSERT(!mComposition.mView);
7283 MOZ_ASSERT(aCompStart.mType == PendingAction::Type::eCompositionStart);
7285 mComposition.Start(
7286 aCompositionView, aCompStart.mSelectionStart,
7287 GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart),
7288 static_cast<uint32_t>(aCompStart.mSelectionLength)));
7289 mLatestCompositionStartOffset = mComposition.mStart;
7290 mLatestCompositionEndOffset = mComposition.EndOffset();
7291 if (!aPreserveSelection) {
7292 // XXX Do we need to set a new writing-mode here when setting a new
7293 // selection? Currently, we just preserve the existing value.
7294 WritingMode writingMode =
7295 mSelection.IsDirty() ? WritingMode() : mSelection.GetWritingMode();
7296 mSelection.SetSelection(mComposition.mStart, mComposition.mString.Length(),
7297 false, writingMode);
7301 void TSFTextStore::Content::RestoreCommittedComposition(
7302 ITfCompositionView* aCompositionView,
7303 const PendingAction& aCanceledCompositionEnd) {
7304 MOZ_ASSERT(mInitialized);
7305 MOZ_ASSERT(aCompositionView);
7306 MOZ_ASSERT(!mComposition.mView);
7307 MOZ_ASSERT(aCanceledCompositionEnd.mType ==
7308 PendingAction::Type::eCompositionEnd);
7309 MOZ_ASSERT(
7310 GetSubstring(
7311 static_cast<uint32_t>(aCanceledCompositionEnd.mSelectionStart),
7312 static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
7313 aCanceledCompositionEnd.mData);
7315 // Restore the committed string as composing string.
7316 mComposition.Start(aCompositionView, aCanceledCompositionEnd.mSelectionStart,
7317 aCanceledCompositionEnd.mData);
7318 mLatestCompositionStartOffset = mComposition.mStart;
7319 mLatestCompositionEndOffset = mComposition.EndOffset();
7322 void TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd) {
7323 MOZ_ASSERT(mInitialized);
7324 MOZ_ASSERT(mComposition.mView);
7325 MOZ_ASSERT(aCompEnd.mType == PendingAction::Type::eCompositionEnd);
7327 mSelection.CollapseAt(mComposition.mStart + aCompEnd.mData.Length());
7328 mComposition.End();
7331 /******************************************************************************
7332 * TSFTextStore::MouseTracker
7333 *****************************************************************************/
7335 TSFTextStore::MouseTracker::MouseTracker()
7336 : mStart(-1), mLength(-1), mCookie(kInvalidCookie) {}
7338 HRESULT
7339 TSFTextStore::MouseTracker::Init(TSFTextStore* aTextStore) {
7340 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
7341 ("0x%p TSFTextStore::MouseTracker::Init(aTextStore=0x%p), "
7342 "aTextStore->mMouseTrackers.Length()=%d",
7343 this, aTextStore->mMouseTrackers.Length()));
7345 if (&aTextStore->mMouseTrackers.LastElement() != this) {
7346 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7347 ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
7348 "this is not the last element of mMouseTrackers",
7349 this));
7350 return E_FAIL;
7352 if (aTextStore->mMouseTrackers.Length() > kInvalidCookie) {
7353 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7354 ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
7355 "no new cookie available",
7356 this));
7357 return E_FAIL;
7359 MOZ_ASSERT(!aTextStore->mMouseTrackers.IsEmpty(),
7360 "This instance must be in TSFTextStore::mMouseTrackers");
7361 mCookie = static_cast<DWORD>(aTextStore->mMouseTrackers.Length() - 1);
7362 return S_OK;
7365 HRESULT
7366 TSFTextStore::MouseTracker::AdviseSink(TSFTextStore* aTextStore,
7367 ITfRangeACP* aTextRange,
7368 ITfMouseSink* aMouseSink) {
7369 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
7370 ("0x%p TSFTextStore::MouseTracker::AdviseSink(aTextStore=0x%p, "
7371 "aTextRange=0x%p, aMouseSink=0x%p), mCookie=%d, mSink=0x%p",
7372 this, aTextStore, aTextRange, aMouseSink, mCookie, mSink.get()));
7373 MOZ_ASSERT(mCookie != kInvalidCookie, "This hasn't been initalized?");
7375 if (mSink) {
7376 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7377 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7378 "due to already being used",
7379 this));
7380 return E_FAIL;
7383 HRESULT hr = aTextRange->GetExtent(&mStart, &mLength);
7384 if (FAILED(hr)) {
7385 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7386 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7387 "due to failure of ITfRangeACP::GetExtent()",
7388 this));
7389 return hr;
7392 if (mStart < 0 || mLength <= 0) {
7393 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7394 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7395 "due to odd result of ITfRangeACP::GetExtent(), "
7396 "mStart=%d, mLength=%d",
7397 this, mStart, mLength));
7398 return E_INVALIDARG;
7401 nsAutoString textContent;
7402 if (NS_WARN_IF(!aTextStore->GetCurrentText(textContent))) {
7403 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7404 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7405 "due to failure of TSFTextStore::GetCurrentText()",
7406 this));
7407 return E_FAIL;
7410 if (textContent.Length() <= static_cast<uint32_t>(mStart) ||
7411 textContent.Length() < static_cast<uint32_t>(mStart + mLength)) {
7412 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7413 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
7414 "due to out of range, mStart=%d, mLength=%d, "
7415 "textContent.Length()=%d",
7416 this, mStart, mLength, textContent.Length()));
7417 return E_INVALIDARG;
7420 mSink = aMouseSink;
7422 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
7423 ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink(), "
7424 "succeeded, mStart=%d, mLength=%d, textContent.Length()=%d",
7425 this, mStart, mLength, textContent.Length()));
7426 return S_OK;
7429 void TSFTextStore::MouseTracker::UnadviseSink() {
7430 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
7431 ("0x%p TSFTextStore::MouseTracker::UnadviseSink(), "
7432 "mCookie=%d, mSink=0x%p, mStart=%d, mLength=%d",
7433 this, mCookie, mSink.get(), mStart, mLength));
7434 mSink = nullptr;
7435 mStart = mLength = -1;
7438 bool TSFTextStore::MouseTracker::OnMouseButtonEvent(ULONG aEdge,
7439 ULONG aQuadrant,
7440 DWORD aButtonStatus) {
7441 MOZ_ASSERT(IsUsing(), "The caller must check before calling OnMouseEvent()");
7443 BOOL eaten = FALSE;
7444 RefPtr<ITfMouseSink> sink = mSink;
7445 HRESULT hr = sink->OnMouseEvent(aEdge, aQuadrant, aButtonStatus, &eaten);
7447 MOZ_LOG(sTextStoreLog, LogLevel::Debug,
7448 ("0x%p TSFTextStore::MouseTracker::OnMouseEvent(aEdge=%d, "
7449 "aQuadrant=%d, aButtonStatus=0x%08X), hr=0x%08X, eaten=%s",
7450 this, aEdge, aQuadrant, aButtonStatus, hr, GetBoolName(!!eaten)));
7452 return SUCCEEDED(hr) && eaten;
7455 #ifdef DEBUG
7456 // static
7457 bool TSFTextStore::CurrentKeyboardLayoutHasIME() {
7458 RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
7459 TSFTextStore::GetInputProcessorProfiles();
7460 if (!inputProcessorProfiles) {
7461 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7462 ("TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED due to "
7463 "there is no input processor profiles instance"));
7464 return false;
7466 RefPtr<ITfInputProcessorProfileMgr> profileMgr;
7467 HRESULT hr = inputProcessorProfiles->QueryInterface(
7468 IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
7469 if (FAILED(hr) || !profileMgr) {
7470 // On Windows Vista or later, ImmIsIME() API always returns true.
7471 // If we failed to obtain the profile manager, we cannot know if current
7472 // keyboard layout has IME.
7473 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7474 (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to query "
7475 "ITfInputProcessorProfileMgr"));
7476 return false;
7479 TF_INPUTPROCESSORPROFILE profile;
7480 hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
7481 if (hr == S_FALSE) {
7482 return false; // not found or not active
7484 if (FAILED(hr)) {
7485 MOZ_LOG(sTextStoreLog, LogLevel::Error,
7486 (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to retreive "
7487 "active profile"));
7488 return false;
7490 return (profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR);
7492 #endif // #ifdef DEBUG
7494 } // namespace widget
7495 } // namespace mozilla