summaryrefslogtreecommitdiffstats
path: root/widget/windows/TSFTextStore.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--widget/windows/TSFTextStore.cpp7463
1 files changed, 7463 insertions, 0 deletions
diff --git a/widget/windows/TSFTextStore.cpp b/widget/windows/TSFTextStore.cpp
new file mode 100644
index 0000000000..d42c75fff8
--- /dev/null
+++ b/widget/windows/TSFTextStore.cpp
@@ -0,0 +1,7463 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define INPUTSCOPE_INIT_GUID
+#define TEXTATTRS_INIT_GUID
+#include "TSFTextStore.h"
+
+#include <algorithm>
+#include <comutil.h> // for _bstr_t
+#include <oleauto.h> // for SysAllocString
+#include <olectl.h>
+#include "nscore.h"
+
+#include "IMMHandler.h"
+#include "KeyboardLayout.h"
+#include "WinIMEHandler.h"
+#include "WinUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_intl.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/ToString.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsWindow.h"
+#include "nsPrintfCString.h"
+
+// For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync`
+// rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
+// big file.
+// Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
+mozilla::LazyLogModule gIMELog("IMEHandler");
+
+// TODO: GUID_PROP_URL has not been declared in the SDK yet. We should drop the
+// `s` prefix after it's released by a new SDK and define it with #if.
+static const GUID sGUID_PROP_URL = {
+ 0xd5138268,
+ 0xa1bf,
+ 0x4308,
+ {0xbc, 0xbf, 0x2e, 0x73, 0x93, 0x98, 0xe2, 0x34}};
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * TSF related code should log its behavior even on release build especially
+ * in the interface methods.
+ *
+ * In interface methods, use LogLevel::Info.
+ * In internal methods, use LogLevel::Debug for logging normal behavior.
+ * For logging error, use LogLevel::Error.
+ *
+ * When an instance method is called, start with following text:
+ * "0x%p TSFFoo::Bar(", the 0x%p should be the "this" of the nsFoo.
+ * after that, start with:
+ * "0x%p TSFFoo::Bar("
+ * In an internal method, start with following text:
+ * "0x%p TSFFoo::Bar("
+ * When a static method is called, start with following text:
+ * "TSFFoo::Bar("
+ */
+
+enum class TextInputProcessorID {
+ // Internal use only. This won't be returned by TSFStaticSink::ActiveTIP().
+ eNotComputed,
+
+ // Not a TIP. E.g., simple keyboard layout or IMM-IME.
+ eNone,
+
+ // Used for other TIPs, i.e., any TIPs which we don't support specifically.
+ eUnknown,
+
+ // TIP for Japanese.
+ eMicrosoftIMEForJapanese,
+ eMicrosoftOfficeIME2010ForJapanese,
+ eGoogleJapaneseInput,
+ eATOK2011,
+ eATOK2012,
+ eATOK2013,
+ eATOK2014,
+ eATOK2015,
+ eATOK2016,
+ eATOKUnknown,
+ eJapanist10,
+
+ // TIP for Traditional Chinese.
+ eMicrosoftBopomofo,
+ eMicrosoftChangJie,
+ eMicrosoftPhonetic,
+ eMicrosoftQuick,
+ eMicrosoftNewChangJie,
+ eMicrosoftNewPhonetic,
+ eMicrosoftNewQuick,
+ eFreeChangJie,
+
+ // TIP for Simplified Chinese.
+ eMicrosoftPinyin,
+ eMicrosoftPinyinNewExperienceInputStyle,
+ eMicrosoftWubi,
+
+ // TIP for Korean.
+ eMicrosoftIMEForKorean,
+ eMicrosoftOldHangul,
+
+ // Keyman Desktop, which can install various language keyboards.
+ eKeymanDesktop,
+};
+
+static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
+
+static void HandleSeparator(nsCString& aDesc) {
+ if (!aDesc.IsEmpty()) {
+ aDesc.AppendLiteral(" | ");
+ }
+}
+
+static const nsCString GetFindFlagName(DWORD aFindFlag) {
+ nsCString description;
+ if (!aFindFlag) {
+ description.AppendLiteral("no flags (0)");
+ return description;
+ }
+ if (aFindFlag & TS_ATTR_FIND_BACKWARDS) {
+ description.AppendLiteral("TS_ATTR_FIND_BACKWARDS");
+ }
+ if (aFindFlag & TS_ATTR_FIND_WANT_OFFSET) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_WANT_OFFSET");
+ }
+ if (aFindFlag & TS_ATTR_FIND_UPDATESTART) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_UPDATESTART");
+ }
+ if (aFindFlag & TS_ATTR_FIND_WANT_VALUE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_WANT_VALUE");
+ }
+ if (aFindFlag & TS_ATTR_FIND_WANT_END) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_WANT_END");
+ }
+ if (aFindFlag & TS_ATTR_FIND_HIDDEN) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_HIDDEN");
+ }
+ if (description.IsEmpty()) {
+ description.AppendLiteral("Unknown (");
+ description.AppendInt(static_cast<uint32_t>(aFindFlag));
+ description.Append(')');
+ }
+ return description;
+}
+
+class GetACPFromPointFlagName : public nsAutoCString {
+ public:
+ explicit GetACPFromPointFlagName(DWORD aFlags) {
+ if (!aFlags) {
+ AppendLiteral("no flags (0)");
+ return;
+ }
+ if (aFlags & GXFPF_ROUND_NEAREST) {
+ AppendLiteral("GXFPF_ROUND_NEAREST");
+ aFlags &= ~GXFPF_ROUND_NEAREST;
+ }
+ if (aFlags & GXFPF_NEAREST) {
+ HandleSeparator(*this);
+ AppendLiteral("GXFPF_NEAREST");
+ aFlags &= ~GXFPF_NEAREST;
+ }
+ if (aFlags) {
+ HandleSeparator(*this);
+ AppendLiteral("Unknown(");
+ AppendInt(static_cast<uint32_t>(aFlags));
+ Append(')');
+ }
+ }
+ virtual ~GetACPFromPointFlagName() {}
+};
+
+static const char* GetFocusChangeName(
+ InputContextAction::FocusChange aFocusChange) {
+ switch (aFocusChange) {
+ case InputContextAction::FOCUS_NOT_CHANGED:
+ return "FOCUS_NOT_CHANGED";
+ case InputContextAction::GOT_FOCUS:
+ return "GOT_FOCUS";
+ case InputContextAction::LOST_FOCUS:
+ return "LOST_FOCUS";
+ case InputContextAction::MENU_GOT_PSEUDO_FOCUS:
+ return "MENU_GOT_PSEUDO_FOCUS";
+ case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
+ return "MENU_LOST_PSEUDO_FOCUS";
+ case InputContextAction::WIDGET_CREATED:
+ return "WIDGET_CREATED";
+ default:
+ return "Unknown";
+ }
+}
+
+static nsCString GetCLSIDNameStr(REFCLSID aCLSID) {
+ LPOLESTR str = nullptr;
+ HRESULT hr = ::StringFromCLSID(aCLSID, &str);
+ if (FAILED(hr) || !str || !str[0]) {
+ return ""_ns;
+ }
+
+ nsCString result;
+ result = NS_ConvertUTF16toUTF8(str);
+ ::CoTaskMemFree(str);
+ return result;
+}
+
+static nsCString GetGUIDNameStr(REFGUID aGUID) {
+ OLECHAR str[40];
+ int len = ::StringFromGUID2(aGUID, str, ArrayLength(str));
+ if (!len || !str[0]) {
+ return ""_ns;
+ }
+
+ return NS_ConvertUTF16toUTF8(str);
+}
+
+static nsCString GetGUIDNameStrWithTable(REFGUID aGUID) {
+#define RETURN_GUID_NAME(aNamedGUID) \
+ if (IsEqualGUID(aGUID, aNamedGUID)) { \
+ return nsLiteralCString(#aNamedGUID); \
+ }
+
+ RETURN_GUID_NAME(GUID_PROP_INPUTSCOPE)
+ RETURN_GUID_NAME(sGUID_PROP_URL)
+ RETURN_GUID_NAME(TSATTRID_OTHERS)
+ RETURN_GUID_NAME(TSATTRID_Font)
+ RETURN_GUID_NAME(TSATTRID_Font_FaceName)
+ RETURN_GUID_NAME(TSATTRID_Font_SizePts)
+ RETURN_GUID_NAME(TSATTRID_Font_Style)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Bold)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Italic)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_SmallCaps)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Capitalize)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Uppercase)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Lowercase)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_LasVegasLights)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_BlinkingBackground)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_SparkleText)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingBlackAnts)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingRedAnts)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_Shimmer)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeDown)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeRight)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Emboss)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Engrave)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Hidden)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Kerning)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Outlined)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Position)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Protected)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Shadow)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Spacing)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Weight)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Height)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Underline)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Single)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Double)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Single)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Double)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Overline)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Single)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Double)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Blink)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Subscript)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Superscript)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Color)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_BackgroundColor)
+ RETURN_GUID_NAME(TSATTRID_Text)
+ RETURN_GUID_NAME(TSATTRID_Text_VerticalWriting)
+ RETURN_GUID_NAME(TSATTRID_Text_RightToLeft)
+ RETURN_GUID_NAME(TSATTRID_Text_Orientation)
+ RETURN_GUID_NAME(TSATTRID_Text_Language)
+ RETURN_GUID_NAME(TSATTRID_Text_ReadOnly)
+ RETURN_GUID_NAME(TSATTRID_Text_EmbeddedObject)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment_Left)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment_Right)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment_Center)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment_Justify)
+ RETURN_GUID_NAME(TSATTRID_Text_Link)
+ RETURN_GUID_NAME(TSATTRID_Text_Hyphenation)
+ RETURN_GUID_NAME(TSATTRID_Text_Para)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_FirstLineIndent)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LeftIndent)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_RightIndent)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceAfter)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceBefore)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Single)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_OnePtFive)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Double)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_AtLeast)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Exactly)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Multiple)
+ RETURN_GUID_NAME(TSATTRID_List)
+ RETURN_GUID_NAME(TSATTRID_List_LevelIndel)
+ RETURN_GUID_NAME(TSATTRID_List_Type)
+ RETURN_GUID_NAME(TSATTRID_List_Type_Bullet)
+ RETURN_GUID_NAME(TSATTRID_List_Type_Arabic)
+ RETURN_GUID_NAME(TSATTRID_List_Type_LowerLetter)
+ RETURN_GUID_NAME(TSATTRID_List_Type_UpperLetter)
+ RETURN_GUID_NAME(TSATTRID_List_Type_LowerRoman)
+ RETURN_GUID_NAME(TSATTRID_List_Type_UpperRoman)
+ RETURN_GUID_NAME(TSATTRID_App)
+ RETURN_GUID_NAME(TSATTRID_App_IncorrectSpelling)
+ RETURN_GUID_NAME(TSATTRID_App_IncorrectGrammar)
+
+#undef RETURN_GUID_NAME
+
+ return GetGUIDNameStr(aGUID);
+}
+
+static nsCString GetRIIDNameStr(REFIID aRIID) {
+ LPOLESTR str = nullptr;
+ HRESULT hr = ::StringFromIID(aRIID, &str);
+ if (FAILED(hr) || !str || !str[0]) {
+ return ""_ns;
+ }
+
+ nsAutoString key(L"Interface\\");
+ key += str;
+
+ nsCString result;
+ wchar_t buf[256];
+ if (WinUtils::GetRegistryKey(HKEY_CLASSES_ROOT, key.get(), nullptr, buf,
+ sizeof(buf))) {
+ result = NS_ConvertUTF16toUTF8(buf);
+ } else {
+ result = NS_ConvertUTF16toUTF8(str);
+ }
+
+ ::CoTaskMemFree(str);
+ return result;
+}
+
+static const char* GetCommonReturnValueName(HRESULT aResult) {
+ switch (aResult) {
+ case S_OK:
+ return "S_OK";
+ case E_ABORT:
+ return "E_ABORT";
+ case E_ACCESSDENIED:
+ return "E_ACCESSDENIED";
+ case E_FAIL:
+ return "E_FAIL";
+ case E_HANDLE:
+ return "E_HANDLE";
+ case E_INVALIDARG:
+ return "E_INVALIDARG";
+ case E_NOINTERFACE:
+ return "E_NOINTERFACE";
+ case E_NOTIMPL:
+ return "E_NOTIMPL";
+ case E_OUTOFMEMORY:
+ return "E_OUTOFMEMORY";
+ case E_POINTER:
+ return "E_POINTER";
+ case E_UNEXPECTED:
+ return "E_UNEXPECTED";
+ default:
+ return SUCCEEDED(aResult) ? "Succeeded" : "Failed";
+ }
+}
+
+static const char* GetTextStoreReturnValueName(HRESULT aResult) {
+ switch (aResult) {
+ case TS_E_FORMAT:
+ return "TS_E_FORMAT";
+ case TS_E_INVALIDPOINT:
+ return "TS_E_INVALIDPOINT";
+ case TS_E_INVALIDPOS:
+ return "TS_E_INVALIDPOS";
+ case TS_E_NOINTERFACE:
+ return "TS_E_NOINTERFACE";
+ case TS_E_NOLAYOUT:
+ return "TS_E_NOLAYOUT";
+ case TS_E_NOLOCK:
+ return "TS_E_NOLOCK";
+ case TS_E_NOOBJECT:
+ return "TS_E_NOOBJECT";
+ case TS_E_NOSELECTION:
+ return "TS_E_NOSELECTION";
+ case TS_E_NOSERVICE:
+ return "TS_E_NOSERVICE";
+ case TS_E_READONLY:
+ return "TS_E_READONLY";
+ case TS_E_SYNCHRONOUS:
+ return "TS_E_SYNCHRONOUS";
+ case TS_S_ASYNC:
+ return "TS_S_ASYNC";
+ default:
+ return GetCommonReturnValueName(aResult);
+ }
+}
+
+static const nsCString GetSinkMaskNameStr(DWORD aSinkMask) {
+ nsCString description;
+ if (aSinkMask & TS_AS_TEXT_CHANGE) {
+ description.AppendLiteral("TS_AS_TEXT_CHANGE");
+ }
+ if (aSinkMask & TS_AS_SEL_CHANGE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_AS_SEL_CHANGE");
+ }
+ if (aSinkMask & TS_AS_LAYOUT_CHANGE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_AS_LAYOUT_CHANGE");
+ }
+ if (aSinkMask & TS_AS_ATTR_CHANGE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_AS_ATTR_CHANGE");
+ }
+ if (aSinkMask & TS_AS_STATUS_CHANGE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_AS_STATUS_CHANGE");
+ }
+ if (description.IsEmpty()) {
+ description.AppendLiteral("not-specified");
+ }
+ return description;
+}
+
+static const nsCString GetLockFlagNameStr(DWORD aLockFlags) {
+ nsCString description;
+ if ((aLockFlags & TS_LF_READWRITE) == TS_LF_READWRITE) {
+ description.AppendLiteral("TS_LF_READWRITE");
+ } else if (aLockFlags & TS_LF_READ) {
+ description.AppendLiteral("TS_LF_READ");
+ }
+ if (aLockFlags & TS_LF_SYNC) {
+ if (!description.IsEmpty()) {
+ description.AppendLiteral(" | ");
+ }
+ description.AppendLiteral("TS_LF_SYNC");
+ }
+ if (description.IsEmpty()) {
+ description.AppendLiteral("not-specified");
+ }
+ return description;
+}
+
+static const char* GetTextRunTypeName(TsRunType aRunType) {
+ switch (aRunType) {
+ case TS_RT_PLAIN:
+ return "TS_RT_PLAIN";
+ case TS_RT_HIDDEN:
+ return "TS_RT_HIDDEN";
+ case TS_RT_OPAQUE:
+ return "TS_RT_OPAQUE";
+ default:
+ return "Unknown";
+ }
+}
+
+static nsCString GetColorName(const TF_DA_COLOR& aColor) {
+ switch (aColor.type) {
+ case TF_CT_NONE:
+ return "TF_CT_NONE"_ns;
+ case TF_CT_SYSCOLOR:
+ return nsPrintfCString("TF_CT_SYSCOLOR, nIndex:0x%08X",
+ static_cast<int32_t>(aColor.nIndex));
+ case TF_CT_COLORREF:
+ return nsPrintfCString("TF_CT_COLORREF, cr:0x%08X",
+ static_cast<int32_t>(aColor.cr));
+ break;
+ default:
+ return nsPrintfCString("Unknown(%08X)",
+ static_cast<int32_t>(aColor.type));
+ }
+}
+
+static nsCString GetLineStyleName(TF_DA_LINESTYLE aLineStyle) {
+ switch (aLineStyle) {
+ case TF_LS_NONE:
+ return "TF_LS_NONE"_ns;
+ case TF_LS_SOLID:
+ return "TF_LS_SOLID"_ns;
+ case TF_LS_DOT:
+ return "TF_LS_DOT"_ns;
+ case TF_LS_DASH:
+ return "TF_LS_DASH"_ns;
+ case TF_LS_SQUIGGLE:
+ return "TF_LS_SQUIGGLE"_ns;
+ default: {
+ return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aLineStyle));
+ }
+ }
+}
+
+static nsCString GetClauseAttrName(TF_DA_ATTR_INFO aAttr) {
+ switch (aAttr) {
+ case TF_ATTR_INPUT:
+ return "TF_ATTR_INPUT"_ns;
+ case TF_ATTR_TARGET_CONVERTED:
+ return "TF_ATTR_TARGET_CONVERTED"_ns;
+ case TF_ATTR_CONVERTED:
+ return "TF_ATTR_CONVERTED"_ns;
+ case TF_ATTR_TARGET_NOTCONVERTED:
+ return "TF_ATTR_TARGET_NOTCONVERTED"_ns;
+ case TF_ATTR_INPUT_ERROR:
+ return "TF_ATTR_INPUT_ERROR"_ns;
+ case TF_ATTR_FIXEDCONVERTED:
+ return "TF_ATTR_FIXEDCONVERTED"_ns;
+ case TF_ATTR_OTHER:
+ return "TF_ATTR_OTHER"_ns;
+ default: {
+ return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aAttr));
+ }
+ }
+}
+
+static nsCString GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE& aDispAttr) {
+ nsCString str;
+ str = "crText:{ ";
+ str += GetColorName(aDispAttr.crText);
+ str += " }, crBk:{ ";
+ str += GetColorName(aDispAttr.crBk);
+ str += " }, lsStyle: ";
+ str += GetLineStyleName(aDispAttr.lsStyle);
+ str += ", fBoldLine: ";
+ str += GetBoolName(aDispAttr.fBoldLine);
+ str += ", crLine:{ ";
+ str += GetColorName(aDispAttr.crLine);
+ str += " }, bAttr: ";
+ str += GetClauseAttrName(aDispAttr.bAttr);
+ return str;
+}
+
+static const char* GetMouseButtonName(int16_t aButton) {
+ switch (aButton) {
+ case MouseButton::ePrimary:
+ return "LeftButton";
+ case MouseButton::eMiddle:
+ return "MiddleButton";
+ case MouseButton::eSecondary:
+ return "RightButton";
+ default:
+ return "UnknownButton";
+ }
+}
+
+#define ADD_SEPARATOR_IF_NECESSARY(aStr) \
+ if (!aStr.IsEmpty()) { \
+ aStr.AppendLiteral(", "); \
+ }
+
+static nsCString GetMouseButtonsName(int16_t aButtons) {
+ if (!aButtons) {
+ return "no buttons"_ns;
+ }
+ nsCString names;
+ if (aButtons & MouseButtonsFlag::ePrimaryFlag) {
+ names = "LeftButton";
+ }
+ if (aButtons & MouseButtonsFlag::eSecondaryFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "RightButton";
+ }
+ if (aButtons & MouseButtonsFlag::eMiddleFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "MiddleButton";
+ }
+ if (aButtons & MouseButtonsFlag::e4thFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "4thButton";
+ }
+ if (aButtons & MouseButtonsFlag::e5thFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "5thButton";
+ }
+ return names;
+}
+
+static nsCString GetModifiersName(Modifiers aModifiers) {
+ if (aModifiers == MODIFIER_NONE) {
+ return "no modifiers"_ns;
+ }
+ nsCString names;
+ if (aModifiers & MODIFIER_ALT) {
+ names = NS_DOM_KEYNAME_ALT;
+ }
+ if (aModifiers & MODIFIER_ALTGRAPH) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_ALTGRAPH;
+ }
+ if (aModifiers & MODIFIER_CAPSLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_CAPSLOCK;
+ }
+ if (aModifiers & MODIFIER_CONTROL) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_CONTROL;
+ }
+ if (aModifiers & MODIFIER_FN) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_FN;
+ }
+ if (aModifiers & MODIFIER_FNLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_FNLOCK;
+ }
+ if (aModifiers & MODIFIER_META) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_META;
+ }
+ if (aModifiers & MODIFIER_NUMLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_NUMLOCK;
+ }
+ if (aModifiers & MODIFIER_SCROLLLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_SCROLLLOCK;
+ }
+ if (aModifiers & MODIFIER_SHIFT) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_SHIFT;
+ }
+ if (aModifiers & MODIFIER_SYMBOL) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_SYMBOL;
+ }
+ if (aModifiers & MODIFIER_SYMBOLLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_SYMBOLLOCK;
+ }
+ if (aModifiers & MODIFIER_OS) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_OS;
+ }
+ return names;
+}
+
+class GetWritingModeName : public nsAutoCString {
+ public:
+ explicit GetWritingModeName(const WritingMode& aWritingMode) {
+ if (!aWritingMode.IsVertical()) {
+ AssignLiteral("Horizontal");
+ return;
+ }
+ if (aWritingMode.IsVerticalLR()) {
+ AssignLiteral("Vertical (LR)");
+ return;
+ }
+ AssignLiteral("Vertical (RL)");
+ }
+ virtual ~GetWritingModeName() {}
+};
+
+class GetEscapedUTF8String final : public NS_ConvertUTF16toUTF8 {
+ public:
+ explicit GetEscapedUTF8String(const nsAString& aString)
+ : NS_ConvertUTF16toUTF8(aString) {
+ Escape();
+ }
+ explicit GetEscapedUTF8String(const char16ptr_t aString)
+ : NS_ConvertUTF16toUTF8(aString) {
+ Escape();
+ }
+ GetEscapedUTF8String(const char16ptr_t aString, uint32_t aLength)
+ : NS_ConvertUTF16toUTF8(aString, aLength) {
+ Escape();
+ }
+
+ private:
+ void Escape() {
+ ReplaceSubstring("\r", "\\r");
+ ReplaceSubstring("\n", "\\n");
+ ReplaceSubstring("\t", "\\t");
+ }
+};
+
+class GetInputScopeString : public nsAutoCString {
+ public:
+ explicit GetInputScopeString(const nsTArray<InputScope>& aList) {
+ for (InputScope inputScope : aList) {
+ if (!IsEmpty()) {
+ AppendLiteral(", ");
+ }
+ switch (inputScope) {
+ case IS_DEFAULT:
+ AppendLiteral("IS_DEFAULT");
+ break;
+ case IS_URL:
+ AppendLiteral("IS_URL");
+ break;
+ case IS_FILE_FULLFILEPATH:
+ AppendLiteral("IS_FILE_FULLFILEPATH");
+ break;
+ case IS_FILE_FILENAME:
+ AppendLiteral("IS_FILE_FILENAME");
+ break;
+ case IS_EMAIL_USERNAME:
+ AppendLiteral("IS_EMAIL_USERNAME");
+ break;
+ case IS_EMAIL_SMTPEMAILADDRESS:
+ AppendLiteral("IS_EMAIL_SMTPEMAILADDRESS");
+ break;
+ case IS_LOGINNAME:
+ AppendLiteral("IS_LOGINNAME");
+ break;
+ case IS_PERSONALNAME_FULLNAME:
+ AppendLiteral("IS_PERSONALNAME_FULLNAME");
+ break;
+ case IS_PERSONALNAME_PREFIX:
+ AppendLiteral("IS_PERSONALNAME_PREFIX");
+ break;
+ case IS_PERSONALNAME_GIVENNAME:
+ AppendLiteral("IS_PERSONALNAME_GIVENNAME");
+ break;
+ case IS_PERSONALNAME_MIDDLENAME:
+ AppendLiteral("IS_PERSONALNAME_MIDDLENAME");
+ break;
+ case IS_PERSONALNAME_SURNAME:
+ AppendLiteral("IS_PERSONALNAME_SURNAME");
+ break;
+ case IS_PERSONALNAME_SUFFIX:
+ AppendLiteral("IS_PERSONALNAME_SUFFIX");
+ break;
+ case IS_ADDRESS_FULLPOSTALADDRESS:
+ AppendLiteral("IS_ADDRESS_FULLPOSTALADDRESS");
+ break;
+ case IS_ADDRESS_POSTALCODE:
+ AppendLiteral("IS_ADDRESS_POSTALCODE");
+ break;
+ case IS_ADDRESS_STREET:
+ AppendLiteral("IS_ADDRESS_STREET");
+ break;
+ case IS_ADDRESS_STATEORPROVINCE:
+ AppendLiteral("IS_ADDRESS_STATEORPROVINCE");
+ break;
+ case IS_ADDRESS_CITY:
+ AppendLiteral("IS_ADDRESS_CITY");
+ break;
+ case IS_ADDRESS_COUNTRYNAME:
+ AppendLiteral("IS_ADDRESS_COUNTRYNAME");
+ break;
+ case IS_ADDRESS_COUNTRYSHORTNAME:
+ AppendLiteral("IS_ADDRESS_COUNTRYSHORTNAME");
+ break;
+ case IS_CURRENCY_AMOUNTANDSYMBOL:
+ AppendLiteral("IS_CURRENCY_AMOUNTANDSYMBOL");
+ break;
+ case IS_CURRENCY_AMOUNT:
+ AppendLiteral("IS_CURRENCY_AMOUNT");
+ break;
+ case IS_DATE_FULLDATE:
+ AppendLiteral("IS_DATE_FULLDATE");
+ break;
+ case IS_DATE_MONTH:
+ AppendLiteral("IS_DATE_MONTH");
+ break;
+ case IS_DATE_DAY:
+ AppendLiteral("IS_DATE_DAY");
+ break;
+ case IS_DATE_YEAR:
+ AppendLiteral("IS_DATE_YEAR");
+ break;
+ case IS_DATE_MONTHNAME:
+ AppendLiteral("IS_DATE_MONTHNAME");
+ break;
+ case IS_DATE_DAYNAME:
+ AppendLiteral("IS_DATE_DAYNAME");
+ break;
+ case IS_DIGITS:
+ AppendLiteral("IS_DIGITS");
+ break;
+ case IS_NUMBER:
+ AppendLiteral("IS_NUMBER");
+ break;
+ case IS_ONECHAR:
+ AppendLiteral("IS_ONECHAR");
+ break;
+ case IS_PASSWORD:
+ AppendLiteral("IS_PASSWORD");
+ break;
+ case IS_TELEPHONE_FULLTELEPHONENUMBER:
+ AppendLiteral("IS_TELEPHONE_FULLTELEPHONENUMBER");
+ break;
+ case IS_TELEPHONE_COUNTRYCODE:
+ AppendLiteral("IS_TELEPHONE_COUNTRYCODE");
+ break;
+ case IS_TELEPHONE_AREACODE:
+ AppendLiteral("IS_TELEPHONE_AREACODE");
+ break;
+ case IS_TELEPHONE_LOCALNUMBER:
+ AppendLiteral("IS_TELEPHONE_LOCALNUMBER");
+ break;
+ case IS_TIME_FULLTIME:
+ AppendLiteral("IS_TIME_FULLTIME");
+ break;
+ case IS_TIME_HOUR:
+ AppendLiteral("IS_TIME_HOUR");
+ break;
+ case IS_TIME_MINORSEC:
+ AppendLiteral("IS_TIME_MINORSEC");
+ break;
+ case IS_NUMBER_FULLWIDTH:
+ AppendLiteral("IS_NUMBER_FULLWIDTH");
+ break;
+ case IS_ALPHANUMERIC_HALFWIDTH:
+ AppendLiteral("IS_ALPHANUMERIC_HALFWIDTH");
+ break;
+ case IS_ALPHANUMERIC_FULLWIDTH:
+ AppendLiteral("IS_ALPHANUMERIC_FULLWIDTH");
+ break;
+ case IS_CURRENCY_CHINESE:
+ AppendLiteral("IS_CURRENCY_CHINESE");
+ break;
+ case IS_BOPOMOFO:
+ AppendLiteral("IS_BOPOMOFO");
+ break;
+ case IS_HIRAGANA:
+ AppendLiteral("IS_HIRAGANA");
+ break;
+ case IS_KATAKANA_HALFWIDTH:
+ AppendLiteral("IS_KATAKANA_HALFWIDTH");
+ break;
+ case IS_KATAKANA_FULLWIDTH:
+ AppendLiteral("IS_KATAKANA_FULLWIDTH");
+ break;
+ case IS_HANJA:
+ AppendLiteral("IS_HANJA");
+ break;
+ case IS_PHRASELIST:
+ AppendLiteral("IS_PHRASELIST");
+ break;
+ case IS_REGULAREXPRESSION:
+ AppendLiteral("IS_REGULAREXPRESSION");
+ break;
+ case IS_SRGS:
+ AppendLiteral("IS_SRGS");
+ break;
+ case IS_XML:
+ AppendLiteral("IS_XML");
+ break;
+ case IS_PRIVATE:
+ AppendLiteral("IS_PRIVATE");
+ break;
+ default:
+ AppendPrintf("Unknown Value(%d)", inputScope);
+ break;
+ }
+ }
+ }
+};
+
+/******************************************************************/
+/* InputScopeImpl */
+/******************************************************************/
+
+class InputScopeImpl final : public ITfInputScope {
+ ~InputScopeImpl() {}
+
+ public:
+ explicit InputScopeImpl(const nsTArray<InputScope>& aList)
+ : mInputScopes(aList.Clone()) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p InputScopeImpl(%s)", this, GetInputScopeString(aList).get()));
+ }
+
+ NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(InputScopeImpl)
+
+ STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+ if ((IID_IUnknown == riid) || (IID_ITfInputScope == riid)) {
+ *ppv = static_cast<ITfInputScope*>(this);
+ }
+ if (*ppv) {
+ AddRef();
+ return S_OK;
+ }
+ return E_NOINTERFACE;
+ }
+
+ STDMETHODIMP GetInputScopes(InputScope** pprgInputScopes, UINT* pcCount) {
+ uint32_t count = (mInputScopes.IsEmpty() ? 1 : mInputScopes.Length());
+
+ InputScope* pScope =
+ (InputScope*)CoTaskMemAlloc(sizeof(InputScope) * count);
+ NS_ENSURE_TRUE(pScope, E_OUTOFMEMORY);
+
+ if (mInputScopes.IsEmpty()) {
+ *pScope = IS_DEFAULT;
+ *pcCount = 1;
+ *pprgInputScopes = pScope;
+ return S_OK;
+ }
+
+ *pcCount = 0;
+
+ for (uint32_t idx = 0; idx < count; idx++) {
+ *(pScope + idx) = mInputScopes[idx];
+ (*pcCount)++;
+ }
+
+ *pprgInputScopes = pScope;
+ return S_OK;
+ }
+
+ STDMETHODIMP GetPhrase(BSTR** ppbstrPhrases, UINT* pcCount) {
+ return E_NOTIMPL;
+ }
+ STDMETHODIMP GetRegularExpression(BSTR* pbstrRegExp) { return E_NOTIMPL; }
+ STDMETHODIMP GetSRGS(BSTR* pbstrSRGS) { return E_NOTIMPL; }
+ STDMETHODIMP GetXML(BSTR* pbstrXML) { return E_NOTIMPL; }
+
+ private:
+ nsTArray<InputScope> mInputScopes;
+};
+
+/******************************************************************/
+/* TSFStaticSink */
+/******************************************************************/
+
+class TSFStaticSink final : public ITfInputProcessorProfileActivationSink {
+ public:
+ static TSFStaticSink* GetInstance() {
+ if (!sInstance) {
+ RefPtr<ITfThreadMgr> threadMgr = TSFTextStore::GetThreadMgr();
+ if (NS_WARN_IF(!threadMgr)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
+ "instance due to no ThreadMgr instance"));
+ return nullptr;
+ }
+ RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
+ TSFTextStore::GetInputProcessorProfiles();
+ if (NS_WARN_IF(!inputProcessorProfiles)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
+ "instance due to no InputProcessorProfiles instance"));
+ return nullptr;
+ }
+ RefPtr<TSFStaticSink> staticSink = new TSFStaticSink();
+ if (NS_WARN_IF(!staticSink->Init(threadMgr, inputProcessorProfiles))) {
+ staticSink->Destroy();
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
+ "instance"));
+ return nullptr;
+ }
+ sInstance = staticSink.forget();
+ }
+ return sInstance;
+ }
+
+ static void Shutdown() {
+ if (sInstance) {
+ sInstance->Destroy();
+ sInstance = nullptr;
+ }
+ }
+
+ bool Init(ITfThreadMgr* aThreadMgr,
+ ITfInputProcessorProfiles* aInputProcessorProfiles);
+ STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+ if (IID_IUnknown == riid ||
+ IID_ITfInputProcessorProfileActivationSink == riid) {
+ *ppv = static_cast<ITfInputProcessorProfileActivationSink*>(this);
+ }
+ if (*ppv) {
+ AddRef();
+ return S_OK;
+ }
+ return E_NOINTERFACE;
+ }
+
+ NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFStaticSink)
+
+ const nsString& GetActiveTIPKeyboardDescription() const {
+ return mActiveTIPKeyboardDescription;
+ }
+
+ static bool IsIMM_IMEActive() {
+ // Use IMM API until TSFStaticSink starts to work.
+ if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
+ return IsIMM_IME(::GetKeyboardLayout(0));
+ }
+ return sInstance->mIsIMM_IME;
+ }
+
+ static bool IsIMM_IME(HKL aHKL) {
+ return (::ImmGetIMEFileNameW(aHKL, nullptr, 0) > 0);
+ }
+
+ static bool IsTraditionalChinese() {
+ EnsureInstance();
+ return sInstance && sInstance->IsTraditionalChineseInternal();
+ }
+ static bool IsSimplifiedChinese() {
+ EnsureInstance();
+ return sInstance && sInstance->IsSimplifiedChineseInternal();
+ }
+ static bool IsJapanese() {
+ EnsureInstance();
+ return sInstance && sInstance->IsJapaneseInternal();
+ }
+ static bool IsKorean() {
+ EnsureInstance();
+ return sInstance && sInstance->IsKoreanInternal();
+ }
+
+ /**
+ * ActiveTIP() returns an ID for currently active TIP.
+ * Please note that this method is expensive due to needs a lot of GUID
+ * comparations if active language ID is one of CJKT. If you need to
+ * check TIPs for a specific language, you should check current language
+ * first.
+ */
+ static TextInputProcessorID ActiveTIP() {
+ EnsureInstance();
+ if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
+ return TextInputProcessorID::eUnknown;
+ }
+ sInstance->ComputeActiveTextInputProcessor();
+ if (NS_WARN_IF(sInstance->mActiveTIP ==
+ TextInputProcessorID::eNotComputed)) {
+ return TextInputProcessorID::eUnknown;
+ }
+ return sInstance->mActiveTIP;
+ }
+
+ static bool IsMSChangJieOrMSQuickActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if the language
+ // for current TIP is Traditional Chinese.
+ if (!IsTraditionalChinese()) {
+ return false;
+ }
+ switch (ActiveTIP()) {
+ case TextInputProcessorID::eMicrosoftChangJie:
+ case TextInputProcessorID::eMicrosoftQuick:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static bool IsMSPinyinOrMSWubiActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if the language
+ // for current TIP is Simplified Chinese.
+ if (!IsSimplifiedChinese()) {
+ return false;
+ }
+ switch (ActiveTIP()) {
+ case TextInputProcessorID::eMicrosoftPinyin:
+ case TextInputProcessorID::eMicrosoftWubi:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static bool IsMSJapaneseIMEActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if the language
+ // for current TIP is Japanese.
+ if (!IsJapanese()) {
+ return false;
+ }
+ return ActiveTIP() == TextInputProcessorID::eMicrosoftIMEForJapanese;
+ }
+
+ static bool IsGoogleJapaneseInputActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if the language
+ // for current TIP is Japanese.
+ if (!IsJapanese()) {
+ return false;
+ }
+ return ActiveTIP() == TextInputProcessorID::eGoogleJapaneseInput;
+ }
+
+ static bool IsATOKActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if active TIP is
+ // ATOK first since it's cheaper.
+ return IsJapanese() && sInstance->IsATOKActiveInternal();
+ }
+
+ // Note that ATOK 2011 - 2016 refers native caret position for deciding its
+ // popup window position.
+ static bool IsATOKReferringNativeCaretActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if active TIP is
+ // ATOK first since it's cheaper.
+ if (!IsJapanese() || !sInstance->IsATOKActiveInternal()) {
+ return false;
+ }
+ switch (ActiveTIP()) {
+ case TextInputProcessorID::eATOK2011:
+ case TextInputProcessorID::eATOK2012:
+ case TextInputProcessorID::eATOK2013:
+ case TextInputProcessorID::eATOK2014:
+ case TextInputProcessorID::eATOK2015:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private:
+ static void EnsureInstance() {
+ if (!sInstance) {
+ RefPtr<TSFStaticSink> staticSink = GetInstance();
+ Unused << staticSink;
+ }
+ }
+
+ bool IsTraditionalChineseInternal() const { return mLangID == 0x0404; }
+ bool IsSimplifiedChineseInternal() const { return mLangID == 0x0804; }
+ bool IsJapaneseInternal() const { return mLangID == 0x0411; }
+ bool IsKoreanInternal() const { return mLangID == 0x0412; }
+
+ bool IsATOKActiveInternal() {
+ EnsureInitActiveTIPKeyboard();
+ // FYI: Name of packaged ATOK includes the release year like "ATOK 2015".
+ // Name of ATOK Passport (subscription) equals "ATOK".
+ return StringBeginsWith(mActiveTIPKeyboardDescription, u"ATOK "_ns) ||
+ mActiveTIPKeyboardDescription.EqualsLiteral("ATOK");
+ }
+
+ void ComputeActiveTextInputProcessor() {
+ if (mActiveTIP != TextInputProcessorID::eNotComputed) {
+ return;
+ }
+
+ if (mActiveTIPGUID == GUID_NULL) {
+ mActiveTIP = TextInputProcessorID::eNone;
+ return;
+ }
+
+ // Comparing GUID is slow. So, we should use language information to
+ // reduce the comparing cost for TIP which is not we do not support
+ // specifically since they are always compared with all supported TIPs.
+ switch (mLangID) {
+ case 0x0404:
+ mActiveTIP = ComputeActiveTIPAsTraditionalChinese();
+ break;
+ case 0x0411:
+ mActiveTIP = ComputeActiveTIPAsJapanese();
+ break;
+ case 0x0412:
+ mActiveTIP = ComputeActiveTIPAsKorean();
+ break;
+ case 0x0804:
+ mActiveTIP = ComputeActiveTIPAsSimplifiedChinese();
+ break;
+ default:
+ mActiveTIP = TextInputProcessorID::eUnknown;
+ break;
+ }
+ // Special case for Keyman Desktop, it is available for any languages.
+ // Therefore, we need to check it only if we don't know the active TIP.
+ if (mActiveTIP != TextInputProcessorID::eUnknown) {
+ return;
+ }
+
+ // Note that keyboard layouts for Keyman assign its GUID on install
+ // randomly, but CLSID is constant in any environments.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1670834#c7
+ // https://github.com/keymanapp/keyman/blob/318c73a9e1d571d942837ff9964590626e5bd5aa/windows/src/engine/kmtip/globals.cpp#L37
+ // {FE0420F1-38D1-4B4C-96BF-E7E20A74CFB7}
+ static constexpr CLSID kKeymanDesktop_CLSID = {
+ 0xFE0420F1,
+ 0x38D1,
+ 0x4B4C,
+ {0x96, 0xBF, 0xE7, 0xE2, 0x0A, 0x74, 0xCF, 0xB7}};
+ if (mActiveTIPCLSID == kKeymanDesktop_CLSID) {
+ mActiveTIP = TextInputProcessorID::eKeymanDesktop;
+ }
+ }
+
+ TextInputProcessorID ComputeActiveTIPAsJapanese() {
+ // {A76C93D9-5523-4E90-AAFA-4DB112F9AC76} (Win7, Win8.1, Win10)
+ static constexpr GUID kMicrosoftIMEForJapaneseGUID = {
+ 0xA76C93D9,
+ 0x5523,
+ 0x4E90,
+ {0xAA, 0xFA, 0x4D, 0xB1, 0x12, 0xF9, 0xAC, 0x76}};
+ if (mActiveTIPGUID == kMicrosoftIMEForJapaneseGUID) {
+ return TextInputProcessorID::eMicrosoftIMEForJapanese;
+ }
+ // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64}
+ static constexpr GUID kMicrosoftOfficeIME2010ForJapaneseGUID = {
+ 0x54EDCC94,
+ 0x1524,
+ 0x4BB1,
+ {0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64}};
+ if (mActiveTIPGUID == kMicrosoftOfficeIME2010ForJapaneseGUID) {
+ return TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese;
+ }
+ // {773EB24E-CA1D-4B1B-B420-FA985BB0B80D}
+ static constexpr GUID kGoogleJapaneseInputGUID = {
+ 0x773EB24E,
+ 0xCA1D,
+ 0x4B1B,
+ {0xB4, 0x20, 0xFA, 0x98, 0x5B, 0xB0, 0xB8, 0x0D}};
+ if (mActiveTIPGUID == kGoogleJapaneseInputGUID) {
+ return TextInputProcessorID::eGoogleJapaneseInput;
+ }
+ // {F9C24A5C-8A53-499D-9572-93B2FF582115}
+ static const GUID kATOK2011GUID = {
+ 0xF9C24A5C,
+ 0x8A53,
+ 0x499D,
+ {0x95, 0x72, 0x93, 0xB2, 0xFF, 0x58, 0x21, 0x15}};
+ if (mActiveTIPGUID == kATOK2011GUID) {
+ return TextInputProcessorID::eATOK2011;
+ }
+ // {1DE01562-F445-401B-B6C3-E5B18DB79461}
+ static constexpr GUID kATOK2012GUID = {
+ 0x1DE01562,
+ 0xF445,
+ 0x401B,
+ {0xB6, 0xC3, 0xE5, 0xB1, 0x8D, 0xB7, 0x94, 0x61}};
+ if (mActiveTIPGUID == kATOK2012GUID) {
+ return TextInputProcessorID::eATOK2012;
+ }
+ // {3C4DB511-189A-4168-B6EA-BFD0B4C85615}
+ static constexpr GUID kATOK2013GUID = {
+ 0x3C4DB511,
+ 0x189A,
+ 0x4168,
+ {0xB6, 0xEA, 0xBF, 0xD0, 0xB4, 0xC8, 0x56, 0x15}};
+ if (mActiveTIPGUID == kATOK2013GUID) {
+ return TextInputProcessorID::eATOK2013;
+ }
+ // {4EF33B79-6AA9-4271-B4BF-9321C279381B}
+ static constexpr GUID kATOK2014GUID = {
+ 0x4EF33B79,
+ 0x6AA9,
+ 0x4271,
+ {0xB4, 0xBF, 0x93, 0x21, 0xC2, 0x79, 0x38, 0x1B}};
+ if (mActiveTIPGUID == kATOK2014GUID) {
+ return TextInputProcessorID::eATOK2014;
+ }
+ // {EAB4DC00-CE2E-483D-A86A-E6B99DA9599A}
+ static constexpr GUID kATOK2015GUID = {
+ 0xEAB4DC00,
+ 0xCE2E,
+ 0x483D,
+ {0xA8, 0x6A, 0xE6, 0xB9, 0x9D, 0xA9, 0x59, 0x9A}};
+ if (mActiveTIPGUID == kATOK2015GUID) {
+ return TextInputProcessorID::eATOK2015;
+ }
+ // {0B557B4C-5740-4110-A60A-1493FA10BF2B}
+ static constexpr GUID kATOK2016GUID = {
+ 0x0B557B4C,
+ 0x5740,
+ 0x4110,
+ {0xA6, 0x0A, 0x14, 0x93, 0xFA, 0x10, 0xBF, 0x2B}};
+ if (mActiveTIPGUID == kATOK2016GUID) {
+ return TextInputProcessorID::eATOK2016;
+ }
+
+ // * ATOK 2017
+ // - {6DBFD8F5-701D-11E6-920F-782BCBA6348F}
+ // * ATOK Passport (confirmed with version 31.1.2)
+ // - {A38F2FD9-7199-45E1-841C-BE0313D8052F}
+
+ if (IsATOKActiveInternal()) {
+ return TextInputProcessorID::eATOKUnknown;
+ }
+
+ // {E6D66705-1EDA-4373-8D01-1D0CB2D054C7}
+ static constexpr GUID kJapanist10GUID = {
+ 0xE6D66705,
+ 0x1EDA,
+ 0x4373,
+ {0x8D, 0x01, 0x1D, 0x0C, 0xB2, 0xD0, 0x54, 0xC7}};
+ if (mActiveTIPGUID == kJapanist10GUID) {
+ return TextInputProcessorID::eJapanist10;
+ }
+
+ return TextInputProcessorID::eUnknown;
+ }
+
+ TextInputProcessorID ComputeActiveTIPAsTraditionalChinese() {
+ // {B2F9C502-1742-11D4-9790-0080C882687E} (Win8.1, Win10)
+ static constexpr GUID kMicrosoftBopomofoGUID = {
+ 0xB2F9C502,
+ 0x1742,
+ 0x11D4,
+ {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftBopomofoGUID) {
+ return TextInputProcessorID::eMicrosoftBopomofo;
+ }
+ // {4BDF9F03-C7D3-11D4-B2AB-0080C882687E} (Win7, Win8.1, Win10)
+ static const GUID kMicrosoftChangJieGUID = {
+ 0x4BDF9F03,
+ 0xC7D3,
+ 0x11D4,
+ {0xB2, 0xAB, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftChangJieGUID) {
+ return TextInputProcessorID::eMicrosoftChangJie;
+ }
+ // {761309DE-317A-11D4-9B5D-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftPhoneticGUID = {
+ 0x761309DE,
+ 0x317A,
+ 0x11D4,
+ {0x9B, 0x5D, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftPhoneticGUID) {
+ return TextInputProcessorID::eMicrosoftPhonetic;
+ }
+ // {6024B45F-5C54-11D4-B921-0080C882687E} (Win7, Win8.1, Win10)
+ static constexpr GUID kMicrosoftQuickGUID = {
+ 0x6024B45F,
+ 0x5C54,
+ 0x11D4,
+ {0xB9, 0x21, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftQuickGUID) {
+ return TextInputProcessorID::eMicrosoftQuick;
+ }
+ // {F3BA907A-6C7E-11D4-97FA-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftNewChangJieGUID = {
+ 0xF3BA907A,
+ 0x6C7E,
+ 0x11D4,
+ {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftNewChangJieGUID) {
+ return TextInputProcessorID::eMicrosoftNewChangJie;
+ }
+ // {B2F9C502-1742-11D4-9790-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftNewPhoneticGUID = {
+ 0xB2F9C502,
+ 0x1742,
+ 0x11D4,
+ {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftNewPhoneticGUID) {
+ return TextInputProcessorID::eMicrosoftNewPhonetic;
+ }
+ // {0B883BA0-C1C7-11D4-87F9-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftNewQuickGUID = {
+ 0x0B883BA0,
+ 0xC1C7,
+ 0x11D4,
+ {0x87, 0xF9, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftNewQuickGUID) {
+ return TextInputProcessorID::eMicrosoftNewQuick;
+ }
+
+ // NOTE: There are some other Traditional Chinese TIPs installed in Windows:
+ // * Chinese Traditional Array (version 6.0)
+ // - {D38EFF65-AA46-4FD5-91A7-67845FB02F5B} (Win7, Win8.1)
+ // * Chinese Traditional DaYi (version 6.0)
+ // - {037B2C25-480C-4D7F-B027-D6CA6B69788A} (Win7, Win8.1)
+
+ // {B58630B5-0ED3-4335-BBC9-E77BBCB43CAD}
+ static const GUID kFreeChangJieGUID = {
+ 0xB58630B5,
+ 0x0ED3,
+ 0x4335,
+ {0xBB, 0xC9, 0xE7, 0x7B, 0xBC, 0xB4, 0x3C, 0xAD}};
+ if (mActiveTIPGUID == kFreeChangJieGUID) {
+ return TextInputProcessorID::eFreeChangJie;
+ }
+
+ return TextInputProcessorID::eUnknown;
+ }
+
+ TextInputProcessorID ComputeActiveTIPAsSimplifiedChinese() {
+ // FYI: This matches with neither "Microsoft Pinyin ABC Input Style" nor
+ // "Microsoft Pinyin New Experience Input Style" on Win7.
+ // {FA550B04-5AD7-411F-A5AC-CA038EC515D7} (Win8.1, Win10)
+ static constexpr GUID kMicrosoftPinyinGUID = {
+ 0xFA550B04,
+ 0x5AD7,
+ 0x411F,
+ {0xA5, 0xAC, 0xCA, 0x03, 0x8E, 0xC5, 0x15, 0xD7}};
+ if (mActiveTIPGUID == kMicrosoftPinyinGUID) {
+ return TextInputProcessorID::eMicrosoftPinyin;
+ }
+
+ // {F3BA9077-6C7E-11D4-97FA-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftPinyinNewExperienceInputStyleGUID = {
+ 0xF3BA9077,
+ 0x6C7E,
+ 0x11D4,
+ {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftPinyinNewExperienceInputStyleGUID) {
+ return TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle;
+ }
+ // {82590C13-F4DD-44F4-BA1D-8667246FDF8E} (Win8.1, Win10)
+ static constexpr GUID kMicrosoftWubiGUID = {
+ 0x82590C13,
+ 0xF4DD,
+ 0x44F4,
+ {0xBA, 0x1D, 0x86, 0x67, 0x24, 0x6F, 0xDF, 0x8E}};
+ if (mActiveTIPGUID == kMicrosoftWubiGUID) {
+ return TextInputProcessorID::eMicrosoftWubi;
+ }
+ // NOTE: There are some other Simplified Chinese TIPs installed in Windows:
+ // * Chinese Simplified QuanPin (version 6.0)
+ // - {54FC610E-6ABD-4685-9DDD-A130BDF1B170} (Win8.1)
+ // * Chinese Simplified ZhengMa (version 6.0)
+ // - {733B4D81-3BC3-4132-B91A-E9CDD5E2BFC9} (Win8.1)
+ // * Chinese Simplified ShuangPin (version 6.0)
+ // - {EF63706D-31C4-490E-9DBB-BD150ADC454B} (Win8.1)
+ // * Microsoft Pinyin ABC Input Style
+ // - {FCA121D2-8C6D-41FB-B2DE-A2AD110D4820} (Win7)
+ return TextInputProcessorID::eUnknown;
+ }
+
+ TextInputProcessorID ComputeActiveTIPAsKorean() {
+ // {B5FE1F02-D5F2-4445-9C03-C568F23C99A1} (Win7, Win8.1, Win10)
+ static constexpr GUID kMicrosoftIMEForKoreanGUID = {
+ 0xB5FE1F02,
+ 0xD5F2,
+ 0x4445,
+ {0x9C, 0x03, 0xC5, 0x68, 0xF2, 0x3C, 0x99, 0xA1}};
+ if (mActiveTIPGUID == kMicrosoftIMEForKoreanGUID) {
+ return TextInputProcessorID::eMicrosoftIMEForKorean;
+ }
+ // {B60AF051-257A-46BC-B9D3-84DAD819BAFB} (Win8.1, Win10)
+ static constexpr GUID kMicrosoftOldHangulGUID = {
+ 0xB60AF051,
+ 0x257A,
+ 0x46BC,
+ {0xB9, 0xD3, 0x84, 0xDA, 0xD8, 0x19, 0xBA, 0xFB}};
+ if (mActiveTIPGUID == kMicrosoftOldHangulGUID) {
+ return TextInputProcessorID::eMicrosoftOldHangul;
+ }
+
+ // NOTE: There is the other Korean TIP installed in Windows:
+ // * Microsoft IME 2010
+ // - {48878C45-93F9-4aaf-A6A1-272CD863C4F5} (Win7)
+
+ return TextInputProcessorID::eUnknown;
+ }
+
+ public: // ITfInputProcessorProfileActivationSink
+ STDMETHODIMP OnActivated(DWORD, LANGID, REFCLSID, REFGUID, REFGUID, HKL,
+ DWORD);
+
+ private:
+ TSFStaticSink();
+ virtual ~TSFStaticSink() {}
+
+ bool EnsureInitActiveTIPKeyboard();
+
+ void Destroy();
+
+ void GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile, nsAString& aDescription);
+ bool IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile);
+
+ TextInputProcessorID mActiveTIP;
+
+ // Cookie of installing ITfInputProcessorProfileActivationSink
+ DWORD mIPProfileCookie;
+
+ LANGID mLangID;
+
+ // True if current IME is implemented with IMM.
+ bool mIsIMM_IME;
+ // True if OnActivated() is already called
+ bool mOnActivatedCalled;
+
+ RefPtr<ITfThreadMgr> mThreadMgr;
+ RefPtr<ITfInputProcessorProfiles> mInputProcessorProfiles;
+
+ // Active TIP keyboard's description. If active language profile isn't TIP,
+ // i.e., IMM-IME or just a keyboard layout, this is empty.
+ nsString mActiveTIPKeyboardDescription;
+
+ // Active TIP's GUID and CLSID
+ GUID mActiveTIPGUID;
+ CLSID mActiveTIPCLSID;
+
+ static StaticRefPtr<TSFStaticSink> sInstance;
+};
+
+StaticRefPtr<TSFStaticSink> TSFStaticSink::sInstance;
+
+TSFStaticSink::TSFStaticSink()
+ : mActiveTIP(TextInputProcessorID::eNotComputed),
+ mIPProfileCookie(TF_INVALID_COOKIE),
+ mLangID(0),
+ mIsIMM_IME(false),
+ mOnActivatedCalled(false),
+ mActiveTIPGUID(GUID_NULL) {}
+
+bool TSFStaticSink::Init(ITfThreadMgr* aThreadMgr,
+ ITfInputProcessorProfiles* aInputProcessorProfiles) {
+ MOZ_ASSERT(!mThreadMgr && !mInputProcessorProfiles,
+ "TSFStaticSink::Init() must be called only once");
+
+ mThreadMgr = aThreadMgr;
+ mInputProcessorProfiles = aInputProcessorProfiles;
+
+ RefPtr<ITfSource> source;
+ HRESULT hr =
+ mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::Init() FAILED to get ITfSource "
+ "instance (0x%08lX)",
+ this, hr));
+ return false;
+ }
+
+ // NOTE: On Vista or later, Windows let us know activate IME changed only
+ // with ITfInputProcessorProfileActivationSink.
+ hr = source->AdviseSink(
+ IID_ITfInputProcessorProfileActivationSink,
+ static_cast<ITfInputProcessorProfileActivationSink*>(this),
+ &mIPProfileCookie);
+ if (FAILED(hr) || mIPProfileCookie == TF_INVALID_COOKIE) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::Init() FAILED to install "
+ "ITfInputProcessorProfileActivationSink (0x%08lX)",
+ this, hr));
+ return false;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFStaticSink::Init(), "
+ "mIPProfileCookie=0x%08lX",
+ this, mIPProfileCookie));
+ return true;
+}
+
+void TSFStaticSink::Destroy() {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFStaticSink::Shutdown() "
+ "mIPProfileCookie=0x%08lX",
+ this, mIPProfileCookie));
+
+ if (mIPProfileCookie != TF_INVALID_COOKIE) {
+ RefPtr<ITfSource> source;
+ HRESULT hr =
+ mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::Shutdown() FAILED to get "
+ "ITfSource instance (0x%08lX)",
+ this, hr));
+ } else {
+ hr = source->UnadviseSink(mIPProfileCookie);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Shutdown() FAILED to uninstall "
+ "ITfInputProcessorProfileActivationSink (0x%08lX)",
+ this, hr));
+ }
+ }
+ }
+
+ mThreadMgr = nullptr;
+ mInputProcessorProfiles = nullptr;
+}
+
+STDMETHODIMP
+TSFStaticSink::OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID rclsid,
+ REFGUID catid, REFGUID guidProfile, HKL hkl,
+ DWORD dwFlags) {
+ if ((dwFlags & TF_IPSINK_FLAG_ACTIVE) &&
+ (dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT ||
+ catid == GUID_TFCAT_TIP_KEYBOARD)) {
+ mOnActivatedCalled = true;
+ mActiveTIP = TextInputProcessorID::eNotComputed;
+ mActiveTIPGUID = guidProfile;
+ mActiveTIPCLSID = rclsid;
+ mLangID = langid & 0xFFFF;
+ mIsIMM_IME = IsIMM_IME(hkl);
+ GetTIPDescription(rclsid, langid, guidProfile,
+ mActiveTIPKeyboardDescription);
+ if (mActiveTIPGUID != GUID_NULL) {
+ // key should be "LocaleID|Description". Although GUID of the
+ // profile is unique key since description may be localized for system
+ // language, unfortunately, it's too long to record as key with its
+ // description. Therefore, we should record only the description with
+ // LocaleID because Microsoft IME may not include language information.
+ // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
+ nsAutoString key;
+ key.AppendPrintf("0x%04X|", mLangID);
+ nsAutoString description(mActiveTIPKeyboardDescription);
+ static const uint32_t kMaxDescriptionLength = 72 - key.Length();
+ if (description.Length() > kMaxDescriptionLength) {
+ if (NS_IS_LOW_SURROGATE(description[kMaxDescriptionLength - 1]) &&
+ NS_IS_HIGH_SURROGATE(description[kMaxDescriptionLength - 2])) {
+ description.Truncate(kMaxDescriptionLength - 2);
+ } else {
+ description.Truncate(kMaxDescriptionLength - 1);
+ }
+ // U+2026 is "..."
+ description.Append(char16_t(0x2026));
+ }
+ key.Append(description);
+ Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_WINDOWS, key,
+ true);
+ }
+ // Notify IMEHandler of changing active keyboard layout.
+ IMEHandler::OnKeyboardLayoutChanged();
+ }
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFStaticSink::OnActivated(dwProfileType=%s (0x%08lX), "
+ "langid=0x%08X, rclsid=%s, catid=%s, guidProfile=%s, hkl=0x%p, "
+ "dwFlags=0x%08lX (TF_IPSINK_FLAG_ACTIVE: %s)), mIsIMM_IME=%s, "
+ "mActiveTIPDescription=\"%s\"",
+ this,
+ dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR
+ ? "TF_PROFILETYPE_INPUTPROCESSOR"
+ : dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT
+ ? "TF_PROFILETYPE_KEYBOARDLAYOUT"
+ : "Unknown",
+ dwProfileType, langid, GetCLSIDNameStr(rclsid).get(),
+ GetGUIDNameStr(catid).get(), GetGUIDNameStr(guidProfile).get(), hkl,
+ dwFlags, GetBoolName(dwFlags & TF_IPSINK_FLAG_ACTIVE),
+ GetBoolName(mIsIMM_IME),
+ NS_ConvertUTF16toUTF8(mActiveTIPKeyboardDescription).get()));
+ return S_OK;
+}
+
+bool TSFStaticSink::EnsureInitActiveTIPKeyboard() {
+ if (mOnActivatedCalled) {
+ return true;
+ }
+
+ RefPtr<ITfInputProcessorProfileMgr> profileMgr;
+ HRESULT hr = mInputProcessorProfiles->QueryInterface(
+ IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
+ if (FAILED(hr) || !profileMgr) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
+ "to get input processor profile manager, hr=0x%08lX",
+ this, hr));
+ return false;
+ }
+
+ TF_INPUTPROCESSORPROFILE profile;
+ hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
+ if (hr == S_FALSE) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
+ "to get active keyboard layout profile due to no active profile, "
+ "hr=0x%08lX",
+ this, hr));
+ // XXX Should we call OnActivated() with arguments like non-TIP in this
+ // case?
+ return false;
+ }
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
+ "to get active TIP keyboard, hr=0x%08lX",
+ this, hr));
+ return false;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), "
+ "calling OnActivated() manually...",
+ this));
+ OnActivated(profile.dwProfileType, profile.langid, profile.clsid,
+ profile.catid, profile.guidProfile, ::GetKeyboardLayout(0),
+ TF_IPSINK_FLAG_ACTIVE);
+ return true;
+}
+
+void TSFStaticSink::GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile,
+ nsAString& aDescription) {
+ aDescription.Truncate();
+
+ if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
+ return;
+ }
+
+ BSTR description = nullptr;
+ HRESULT hr = mInputProcessorProfiles->GetLanguageProfileDescription(
+ aTextService, aLangID, aProfile, &description);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::InitActiveTIPDescription() FAILED "
+ "due to GetLanguageProfileDescription() failure, hr=0x%08lX",
+ this, hr));
+ return;
+ }
+
+ if (description && description[0]) {
+ aDescription.Assign(description);
+ }
+ ::SysFreeString(description);
+}
+
+bool TSFStaticSink::IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile) {
+ if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
+ return false;
+ }
+
+ RefPtr<IEnumTfLanguageProfiles> enumLangProfiles;
+ HRESULT hr = mInputProcessorProfiles->EnumLanguageProfiles(
+ aLangID, getter_AddRefs(enumLangProfiles));
+ if (FAILED(hr) || !enumLangProfiles) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::IsTIPCategoryKeyboard(), FAILED "
+ "to get language profiles enumerator, hr=0x%08lX",
+ this, hr));
+ return false;
+ }
+
+ TF_LANGUAGEPROFILE profile;
+ ULONG fetch = 0;
+ while (SUCCEEDED(enumLangProfiles->Next(1, &profile, &fetch)) && fetch) {
+ // XXX We're not sure a profile is registered with two or more categories.
+ if (profile.clsid == aTextService && profile.guidProfile == aProfile &&
+ profile.catid == GUID_TFCAT_TIP_KEYBOARD) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/******************************************************************/
+/* TSFTextStore */
+/******************************************************************/
+
+StaticRefPtr<ITfThreadMgr> TSFTextStore::sThreadMgr;
+StaticRefPtr<ITfMessagePump> TSFTextStore::sMessagePump;
+StaticRefPtr<ITfKeystrokeMgr> TSFTextStore::sKeystrokeMgr;
+StaticRefPtr<ITfDisplayAttributeMgr> TSFTextStore::sDisplayAttrMgr;
+StaticRefPtr<ITfCategoryMgr> TSFTextStore::sCategoryMgr;
+StaticRefPtr<ITfCompartment> TSFTextStore::sCompartmentForOpenClose;
+StaticRefPtr<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr;
+StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext;
+StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles;
+StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore;
+const MSG* TSFTextStore::sHandlingKeyMsg = nullptr;
+DWORD TSFTextStore::sClientId = 0;
+bool TSFTextStore::sIsKeyboardEventDispatched = false;
+
+#define TEXTSTORE_DEFAULT_VIEW (1)
+
+TSFTextStore::TSFTextStore()
+ : mEditCookie(0),
+ mSinkMask(0),
+ mLock(0),
+ mLockQueued(0),
+ mHandlingKeyMessage(0) {
+ // We hope that 5 or more actions don't occur at once.
+ mPendingActions.SetCapacity(5);
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::TSFTextStore() SUCCEEDED", this));
+}
+
+TSFTextStore::~TSFTextStore() {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore instance is destroyed", this));
+}
+
+bool TSFTextStore::Init(nsWindow* aWidget, const InputContext& aContext) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::Init(aWidget=0x%p)", this, aWidget));
+
+ if (NS_WARN_IF(!aWidget) || NS_WARN_IF(aWidget->Destroyed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED due to being initialized with "
+ "destroyed widget",
+ this));
+ return false;
+ }
+
+ if (mDocumentMgr) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED due to already initialized",
+ this));
+ return false;
+ }
+
+ mWidget = aWidget;
+ if (NS_WARN_IF(!mWidget)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED "
+ "due to aWidget is nullptr ",
+ this));
+ return false;
+ }
+ mDispatcher = mWidget->GetTextEventDispatcher();
+ if (NS_WARN_IF(!mDispatcher)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED "
+ "due to aWidget->GetTextEventDispatcher() failure",
+ this));
+ return false;
+ }
+
+ mInPrivateBrowsing = aContext.mInPrivateBrowsing;
+ SetInputScope(aContext.mHTMLInputType, aContext.mHTMLInputMode);
+
+ if (aContext.mURI) {
+ // We don't need the document URL if it fails, let's ignore the error.
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) {
+ CopyUTF8toUTF16(spec, mDocumentURL);
+ }
+ }
+
+ // Create document manager
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ RefPtr<ITfDocumentMgr> documentMgr;
+ HRESULT hr = threadMgr->CreateDocumentMgr(getter_AddRefs(documentMgr));
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr "
+ "(0x%08lX)",
+ this, hr));
+ return false;
+ }
+ if (NS_WARN_IF(mDestroyed)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr due to "
+ "TextStore being destroyed during calling "
+ "ITfThreadMgr::CreateDocumentMgr()",
+ this));
+ return false;
+ }
+ // Create context and add it to document manager
+ RefPtr<ITfContext> context;
+ hr = documentMgr->CreateContext(sClientId, 0,
+ static_cast<ITextStoreACP*>(this),
+ getter_AddRefs(context), &mEditCookie);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create the context "
+ "(0x%08lX)",
+ this, hr));
+ return false;
+ }
+ if (NS_WARN_IF(mDestroyed)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
+ "TextStore being destroyed during calling "
+ "ITfDocumentMgr::CreateContext()",
+ this));
+ return false;
+ }
+
+ hr = documentMgr->Push(context);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to push the context (0x%08lX)",
+ this, hr));
+ return false;
+ }
+ if (NS_WARN_IF(mDestroyed)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
+ "TextStore being destroyed during calling ITfDocumentMgr::Push()",
+ this));
+ documentMgr->Pop(TF_POPF_ALL);
+ return false;
+ }
+
+ mDocumentMgr = documentMgr;
+ mContext = context;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::Init() succeeded: "
+ "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08lX",
+ this, mDocumentMgr.get(), mContext.get(), mEditCookie));
+
+ return true;
+}
+
+void TSFTextStore::Destroy() {
+ if (mBeingDestroyed) {
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::Destroy(), mLock=%s, "
+ "mComposition=%s, mHandlingKeyMessage=%u",
+ this, GetLockFlagNameStr(mLock).get(),
+ ToString(mComposition).c_str(), mHandlingKeyMessage));
+
+ mDestroyed = true;
+
+ // Destroy native caret first because it's not directly related to TSF and
+ // there may be another textstore which gets focus. So, we should avoid
+ // to destroy caret after the new one recreates caret.
+ IMEHandler::MaybeDestroyNativeCaret();
+
+ if (mLock) {
+ mPendingDestroy = true;
+ return;
+ }
+
+ AutoRestore<bool> savedBeingDestroyed(mBeingDestroyed);
+ mBeingDestroyed = true;
+
+ // If there is composition, TSF keeps the composition even after the text
+ // store destroyed. So, we should clear the composition here.
+ if (mComposition.isSome()) {
+ CommitCompositionInternal(false);
+ }
+
+ if (mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::Destroy(), calling "
+ "ITextStoreACPSink::OnLayoutChange(TS_LC_DESTROY)...",
+ this));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ sink->OnLayoutChange(TS_LC_DESTROY, TEXTSTORE_DEFAULT_VIEW);
+ }
+
+ // If this is called during handling a keydown or keyup message, we should
+ // put off to release TSF objects until it completely finishes since
+ // MS-IME for Japanese refers some objects without grabbing them.
+ if (!mHandlingKeyMessage) {
+ ReleaseTSFObjects();
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::Destroy() succeeded", this));
+}
+
+void TSFTextStore::ReleaseTSFObjects() {
+ MOZ_ASSERT(!mHandlingKeyMessage);
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::ReleaseTSFObjects()", this));
+
+ mDocumentURL.Truncate();
+ mContext = nullptr;
+ if (mDocumentMgr) {
+ RefPtr<ITfDocumentMgr> documentMgr = mDocumentMgr.forget();
+ documentMgr->Pop(TF_POPF_ALL);
+ }
+ mSink = nullptr;
+ mWidget = nullptr;
+ mDispatcher = nullptr;
+
+ if (!mMouseTrackers.IsEmpty()) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::ReleaseTSFObjects(), "
+ "removing a mouse tracker...",
+ this));
+ mMouseTrackers.Clear();
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::ReleaseTSFObjects() completed", this));
+}
+
+STDMETHODIMP
+TSFTextStore::QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+ if ((IID_IUnknown == riid) || (IID_ITextStoreACP == riid)) {
+ *ppv = static_cast<ITextStoreACP*>(this);
+ } else if (IID_ITfContextOwnerCompositionSink == riid) {
+ *ppv = static_cast<ITfContextOwnerCompositionSink*>(this);
+ } else if (IID_ITfMouseTrackerACP == riid) {
+ *ppv = static_cast<ITfMouseTrackerACP*>(this);
+ }
+ if (*ppv) {
+ AddRef();
+ return S_OK;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::QueryInterface() FAILED, riid=%s", this,
+ GetRIIDNameStr(riid).get()));
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP
+TSFTextStore::AdviseSink(REFIID riid, IUnknown* punk, DWORD dwMask) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), "
+ "mSink=0x%p, mSinkMask=%s",
+ this, GetRIIDNameStr(riid).get(), punk, GetSinkMaskNameStr(dwMask).get(),
+ mSink.get(), GetSinkMaskNameStr(mSinkMask).get()));
+
+ if (!punk) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseSink() FAILED due to the null punk",
+ this));
+ return E_UNEXPECTED;
+ }
+
+ if (IID_ITextStoreACPSink != riid) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseSink() FAILED due to "
+ "unsupported interface",
+ this));
+ return E_INVALIDARG; // means unsupported interface.
+ }
+
+ if (!mSink) {
+ // Install sink
+ punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink));
+ if (!mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseSink() FAILED due to "
+ "punk not having the interface",
+ this));
+ return E_UNEXPECTED;
+ }
+ } else {
+ // If sink is already installed we check to see if they are the same
+ // Get IUnknown from both sides for comparison
+ RefPtr<IUnknown> comparison1, comparison2;
+ punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
+ mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
+ if (comparison1 != comparison2) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseSink() FAILED due to "
+ "the sink being different from the stored sink",
+ this));
+ return CONNECT_E_ADVISELIMIT;
+ }
+ }
+ // Update mask either for a new sink or an existing sink
+ mSinkMask = dwMask;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::UnadviseSink(IUnknown* punk) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::UnadviseSink(punk=0x%p), mSink=0x%p", this, punk,
+ mSink.get()));
+
+ if (!punk) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseSink() FAILED due to the null punk",
+ this));
+ return E_INVALIDARG;
+ }
+ if (!mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
+ "any sink not stored",
+ this));
+ return CONNECT_E_NOCONNECTION;
+ }
+ // Get IUnknown from both sides for comparison
+ RefPtr<IUnknown> comparison1, comparison2;
+ punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
+ mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
+ // Unadvise only if sinks are the same
+ if (comparison1 != comparison2) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
+ "the sink being different from the stored sink",
+ this));
+ return CONNECT_E_NOCONNECTION;
+ }
+ mSink = nullptr;
+ mSinkMask = 0;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::RequestLock(DWORD dwLockFlags, HRESULT* phrSession) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestLock(dwLockFlags=%s, phrSession=0x%p), "
+ "mLock=%s, mDestroyed=%s",
+ this, GetLockFlagNameStr(dwLockFlags).get(), phrSession,
+ GetLockFlagNameStr(mLock).get(), GetBoolName(mDestroyed)));
+
+ if (!mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RequestLock() FAILED due to "
+ "any sink not stored",
+ this));
+ return E_FAIL;
+ }
+ if (mDestroyed &&
+ (mContentForTSF.isNothing() || mSelectionForTSF.isNothing())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RequestLock() FAILED due to "
+ "being destroyed and no information of the contents",
+ this));
+ return E_FAIL;
+ }
+ if (!phrSession) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RequestLock() FAILED due to "
+ "null phrSession",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!mLock) {
+ // put on lock
+ mLock = dwLockFlags & (~TS_LF_SYNC);
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p Locking (%s) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
+ ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
+ this, GetLockFlagNameStr(mLock).get()));
+ // Don't release this instance during this lock because this is called by
+ // TSF but they don't grab us during this call.
+ RefPtr<TSFTextStore> kungFuDeathGrip(this);
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ *phrSession = sink->OnLockGranted(mLock);
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
+ "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
+ this, GetLockFlagNameStr(mLock).get()));
+ DidLockGranted();
+ while (mLockQueued) {
+ mLock = mLockQueued;
+ mLockQueued = 0;
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p Locking for the request in the queue (%s) >>>>>>>>>>>>>>"
+ ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
+ ">>>>>",
+ this, GetLockFlagNameStr(mLock).get()));
+ sink->OnLockGranted(mLock);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
+ "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
+ "<<<<<",
+ this, GetLockFlagNameStr(mLock).get()));
+ DidLockGranted();
+ }
+
+ // The document is now completely unlocked.
+ mLock = 0;
+
+ MaybeFlushPendingNotifications();
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestLock() succeeded: *phrSession=%s",
+ this, GetTextStoreReturnValueName(*phrSession)));
+ return S_OK;
+ }
+
+ // only time when reentrant lock is allowed is when caller holds a
+ // read-only lock and is requesting an async write lock
+ if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags) &&
+ !(dwLockFlags & TS_LF_SYNC)) {
+ *phrSession = TS_S_ASYNC;
+ mLockQueued = dwLockFlags & (~TS_LF_SYNC);
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestLock() stores the request in the "
+ "queue, *phrSession=TS_S_ASYNC",
+ this));
+ return S_OK;
+ }
+
+ // no more locks allowed
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestLock() didn't allow to lock, "
+ "*phrSession=TS_E_SYNCHRONOUS",
+ this));
+ *phrSession = TS_E_SYNCHRONOUS;
+ return E_FAIL;
+}
+
+void TSFTextStore::DidLockGranted() {
+ if (IsReadWriteLocked()) {
+ // FreeCJ (TIP for Traditional Chinese) calls SetSelection() to set caret
+ // to the start of composition string and insert a full width space for
+ // a placeholder with a call of SetText(). After that, it calls
+ // OnUpdateComposition() without new range. Therefore, let's record the
+ // composition update information here.
+ CompleteLastActionIfStillIncomplete();
+
+ FlushPendingActions();
+ }
+
+ // If the widget has gone, we don't need to notify anything.
+ if (mDestroyed || !mWidget || mWidget->Destroyed()) {
+ mPendingSelectionChangeData.reset();
+ mHasReturnedNoLayoutError = false;
+ }
+}
+
+void TSFTextStore::DispatchEvent(WidgetGUIEvent& aEvent) {
+ if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) {
+ return;
+ }
+ // If the event isn't a query content event, the event may be handled
+ // asynchronously. So, we should put off to answer from GetTextExt() etc.
+ if (!aEvent.AsQueryContentEvent()) {
+ mDeferNotifyingTSFUntilNextUpdate = true;
+ }
+ mWidget->DispatchWindowEvent(aEvent);
+}
+
+void TSFTextStore::FlushPendingActions() {
+ if (!mWidget || mWidget->Destroyed()) {
+ // Note that don't clear mContentForTSF because TIP may try to commit
+ // composition with a document lock. In such case, TSFTextStore needs to
+ // behave as expected by TIP.
+ mPendingActions.Clear();
+ mPendingSelectionChangeData.reset();
+ mHasReturnedNoLayoutError = false;
+ return;
+ }
+
+ // Some TIP may request lock but does nothing during the lock. In such case,
+ // this should do nothing. For example, when MS-IME for Japanese is active
+ // and we're inactivating, this case occurs and causes different behavior
+ // from the other TIPs.
+ if (mPendingActions.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<nsWindow> widget(mWidget);
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED due to BeginNativeInputTransaction() failure",
+ this));
+ return;
+ }
+ for (uint32_t i = 0; i < mPendingActions.Length(); i++) {
+ PendingAction& action = mPendingActions[i];
+ switch (action.mType) {
+ case PendingAction::Type::eKeyboardEvent:
+ if (mDestroyed) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "IGNORED pending KeyboardEvent(%s) due to already destroyed",
+ this,
+ action.mKeyMsg.message == WM_KEYDOWN ? "eKeyDown" : "eKeyUp"));
+ }
+ MOZ_DIAGNOSTIC_ASSERT(action.mKeyMsg.message == WM_KEYDOWN ||
+ action.mKeyMsg.message == WM_KEYUP);
+ DispatchKeyboardEventAsProcessedByIME(action.mKeyMsg);
+ if (!widget || widget->Destroyed()) {
+ break;
+ }
+ break;
+ case PendingAction::Type::eCompositionStart: {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing Type::eCompositionStart={ mSelectionStart=%ld, "
+ "mSelectionLength=%ld }, mDestroyed=%s",
+ this, action.mSelectionStart, action.mSelectionLength,
+ GetBoolName(mDestroyed)));
+
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "IGNORED pending compositionstart due to already destroyed",
+ this));
+ break;
+ }
+
+ if (action.mAdjustSelection) {
+ // Select composition range so the new composition replaces the range
+ WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
+ widget->InitEvent(selectionSet);
+ selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
+ selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
+ selectionSet.mReversed = false;
+ selectionSet.mExpandToClusterBoundary =
+ TSFStaticSink::ActiveTIP() !=
+ TextInputProcessorID::eKeymanDesktop &&
+ StaticPrefs::
+ intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
+ DispatchEvent(selectionSet);
+ if (!selectionSet.mSucceeded) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED due to eSetSelection failure",
+ this));
+ break;
+ }
+ }
+
+ // eCompositionStart always causes
+ // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. Therefore, we should
+ // wait to clear mContentForTSF until it's notified.
+ mDeferClearingContentForTSF = true;
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "dispatching compositionstart event...",
+ this));
+ WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = mDispatcher->StartComposition(status, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to dispatch compositionstart event, "
+ "IsHandlingCompositionInContent()=%s",
+ this, GetBoolName(IsHandlingCompositionInContent())));
+ // XXX Is this right? If there is a composition in content,
+ // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
+ mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
+ }
+ if (!widget || widget->Destroyed()) {
+ break;
+ }
+ break;
+ }
+ case PendingAction::Type::eCompositionUpdate: {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing Type::eCompositionUpdate={ mData=\"%s\", "
+ "mRanges=0x%p, mRanges->Length()=%zu }",
+ this, GetEscapedUTF8String(action.mData).get(),
+ action.mRanges.get(),
+ action.mRanges ? action.mRanges->Length() : 0));
+
+ // eCompositionChange causes a DOM text event, the IME will be notified
+ // of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. In this case, we
+ // should not clear mContentForTSF until we notify the IME of the
+ // composition update.
+ mDeferClearingContentForTSF = true;
+
+ rv = mDispatcher->SetPendingComposition(action.mData, action.mRanges);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to setting pending composition... "
+ "IsHandlingCompositionInContent()=%s",
+ this, GetBoolName(IsHandlingCompositionInContent())));
+ // XXX Is this right? If there is a composition in content,
+ // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
+ mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
+ } else {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "dispatching compositionchange event...",
+ this));
+ WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = mDispatcher->FlushPendingComposition(status, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to dispatch compositionchange event, "
+ "IsHandlingCompositionInContent()=%s",
+ this, GetBoolName(IsHandlingCompositionInContent())));
+ // XXX Is this right? If there is a composition in content,
+ // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
+ mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
+ }
+ // Be aware, the mWidget might already have been destroyed.
+ }
+ break;
+ }
+ case PendingAction::Type::eCompositionEnd: {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing Type::eCompositionEnd={ mData=\"%s\" }",
+ this, GetEscapedUTF8String(action.mData).get()));
+
+ // Dispatching eCompositionCommit causes a DOM text event, then,
+ // the IME will be notified of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
+ // when focused content actually handles the event. For example,
+ // when focused content is in a remote process, it's sent when
+ // all dispatched composition events have been handled in the remote
+ // process. So, until then, we don't have newer content information.
+ // Therefore, we need to put off to clear mContentForTSF.
+ mDeferClearingContentForTSF = true;
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions(), "
+ "dispatching compositioncommit event...",
+ this));
+ WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = mDispatcher->CommitComposition(status, &action.mData, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to dispatch compositioncommit event, "
+ "IsHandlingCompositionInContent()=%s",
+ this, GetBoolName(IsHandlingCompositionInContent())));
+ // XXX Is this right? If there is a composition in content,
+ // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
+ mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
+ }
+ break;
+ }
+ case PendingAction::Type::eSetSelection: {
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing Type::eSetSelection={ mSelectionStart=%ld, "
+ "mSelectionLength=%ld, mSelectionReversed=%s }, "
+ "mDestroyed=%s",
+ this, action.mSelectionStart, action.mSelectionLength,
+ GetBoolName(action.mSelectionReversed), GetBoolName(mDestroyed)));
+
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "IGNORED pending selectionset due to already destroyed",
+ this));
+ break;
+ }
+
+ WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
+ selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
+ selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
+ selectionSet.mReversed = action.mSelectionReversed;
+ selectionSet.mExpandToClusterBoundary =
+ TSFStaticSink::ActiveTIP() !=
+ TextInputProcessorID::eKeymanDesktop &&
+ StaticPrefs::
+ intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
+ DispatchEvent(selectionSet);
+ if (!selectionSet.mSucceeded) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED due to eSetSelection failure",
+ this));
+ break;
+ }
+ break;
+ }
+ default:
+ MOZ_CRASH("unexpected action type");
+ }
+
+ if (widget && !widget->Destroyed()) {
+ continue;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::FlushPendingActions(), "
+ "qutting since the mWidget has gone",
+ this));
+ break;
+ }
+ mPendingActions.Clear();
+}
+
+void TSFTextStore::MaybeFlushPendingNotifications() {
+ if (mDeferNotifyingTSF) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "putting off flushing pending notifications due to initializing "
+ "something...",
+ this));
+ return;
+ }
+
+ if (IsReadLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "putting off flushing pending notifications due to being the "
+ "document locked...",
+ this));
+ return;
+ }
+
+ if (mDeferCommittingComposition) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::CommitCompositionInternal(false)...",
+ this));
+ mDeferCommittingComposition = mDeferCancellingComposition = false;
+ CommitCompositionInternal(false);
+ } else if (mDeferCancellingComposition) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::CommitCompositionInternal(true)...",
+ this));
+ mDeferCommittingComposition = mDeferCancellingComposition = false;
+ CommitCompositionInternal(true);
+ }
+
+ if (mDeferNotifyingTSFUntilNextUpdate) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "putting off flushing pending notifications due to being "
+ "dispatching events...",
+ this));
+ return;
+ }
+
+ if (mPendingDestroy) {
+ Destroy();
+ return;
+ }
+
+ if (mDestroyed) {
+ // If it's already been destroyed completely, this shouldn't notify TSF of
+ // anything anymore.
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "does nothing because this has already destroyed completely...",
+ this));
+ return;
+ }
+
+ if (!mDeferClearingContentForTSF && mContentForTSF.isSome()) {
+ mContentForTSF.reset();
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "mContentForTSF is set to `Nothing`",
+ this));
+ }
+
+ // When there is no cached content, we can sync actual contents and TSF/TIP
+ // expecting contents.
+ RefPtr<TSFTextStore> kungFuDeathGrip = this;
+ Unused << kungFuDeathGrip;
+ if (mContentForTSF.isNothing()) {
+ if (mPendingTextChangeData.IsValid()) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::NotifyTSFOfTextChange()...",
+ this));
+ NotifyTSFOfTextChange();
+ }
+ if (mPendingSelectionChangeData.isSome()) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::NotifyTSFOfSelectionChange()...",
+ this));
+ NotifyTSFOfSelectionChange();
+ }
+ }
+
+ if (mHasReturnedNoLayoutError) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::NotifyTSFOfLayoutChange()...",
+ this));
+ NotifyTSFOfLayoutChange();
+ }
+}
+
+void TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME() {
+ // If we've already been destroyed, we cannot do anything.
+ if (mDestroyed) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+ "does nothing because it's already been destroyed",
+ this));
+ return;
+ }
+
+ // If we're not handling key message or we've already dispatched a keyboard
+ // event for the handling key message, we should do nothing anymore.
+ if (!sHandlingKeyMsg || sIsKeyboardEventDispatched) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+ "does nothing because not necessary to dispatch keyboard event",
+ this));
+ return;
+ }
+
+ sIsKeyboardEventDispatched = true;
+ // If the document is locked, just adding the task to dispatching an event
+ // to the queue.
+ if (IsReadLocked()) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+ "adding to dispatch a keyboard event into the queue...",
+ this));
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::Type::eKeyboardEvent;
+ memcpy(&action->mKeyMsg, sHandlingKeyMsg, sizeof(MSG));
+ return;
+ }
+
+ // Otherwise, dispatch a keyboard event.
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+ "trying to dispatch a keyboard event...",
+ this));
+ DispatchKeyboardEventAsProcessedByIME(*sHandlingKeyMsg);
+}
+
+void TSFTextStore::DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg) {
+ MOZ_ASSERT(mWidget);
+ MOZ_ASSERT(!mWidget->Destroyed());
+ MOZ_ASSERT(!mDestroyed);
+
+ ModifierKeyState modKeyState;
+ MSG msg(aMsg);
+ msg.wParam = VK_PROCESSKEY;
+ NativeKey nativeKey(mWidget, msg, modKeyState);
+ switch (aMsg.message) {
+ case WM_KEYDOWN:
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
+ "dispatching an eKeyDown event...",
+ this));
+ nativeKey.HandleKeyDownMessage();
+ break;
+ case WM_KEYUP:
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
+ "dispatching an eKeyUp event...",
+ this));
+ nativeKey.HandleKeyUpMessage();
+ break;
+ default:
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
+ "ERROR, it doesn't handle the message",
+ this));
+ break;
+ }
+}
+
+STDMETHODIMP
+TSFTextStore::GetStatus(TS_STATUS* pdcs) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetStatus(pdcs=0x%p)", this, pdcs));
+
+ if (!pdcs) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetStatus() FAILED due to null pdcs", this));
+ return E_INVALIDARG;
+ }
+ // We manage on-screen keyboard by own.
+ pdcs->dwDynamicFlags = TS_SD_INPUTPANEMANUALDISPLAYENABLE;
+ // we use a "flat" text model for TSF support so no hidden text
+ pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::QueryInsert(LONG acpTestStart, LONG acpTestEnd, ULONG cch,
+ LONG* pacpResultStart, LONG* pacpResultEnd) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::QueryInsert(acpTestStart=%ld, "
+ "acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)",
+ this, acpTestStart, acpTestEnd, cch, pacpResultStart, pacpResultEnd));
+
+ if (!pacpResultStart || !pacpResultEnd) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::QueryInsert() FAILED due to "
+ "the null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (acpTestStart < 0 || acpTestStart > acpTestEnd) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::QueryInsert() FAILED due to "
+ "wrong argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ // XXX need to adjust to cluster boundary
+ // Assume we are given good offsets for now
+ if (IsWin8OrLater() && mComposition.isNothing() &&
+ ((StaticPrefs::
+ intl_tsf_hack_ms_traditional_chinese_query_insert_result() &&
+ TSFStaticSink::IsMSChangJieOrMSQuickActive()) ||
+ (StaticPrefs::
+ intl_tsf_hack_ms_simplified_chinese_query_insert_result() &&
+ TSFStaticSink::IsMSPinyinOrMSWubiActive()))) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::QueryInsert() WARNING using different "
+ "result for the TIP",
+ this));
+ // Chinese TIPs of Microsoft assume that QueryInsert() returns selected
+ // range which should be removed.
+ *pacpResultStart = acpTestStart;
+ *pacpResultEnd = acpTestEnd;
+ } else {
+ *pacpResultStart = acpTestStart;
+ *pacpResultEnd = acpTestStart + cch;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::QueryInsert() succeeded: "
+ "*pacpResultStart=%ld, *pacpResultEnd=%ld)",
+ this, *pacpResultStart, *pacpResultEnd));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetSelection(ULONG ulIndex, ULONG ulCount,
+ TS_SELECTION_ACP* pSelection, ULONG* pcFetched) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetSelection(ulIndex=%lu, ulCount=%lu, "
+ "pSelection=0x%p, pcFetched=0x%p)",
+ this, ulIndex, ulCount, pSelection, pcFetched));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to not locked", this));
+ return TS_E_NOLOCK;
+ }
+ if (!ulCount || !pSelection || !pcFetched) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ *pcFetched = 0;
+
+ if (ulIndex != static_cast<ULONG>(TS_DEFAULT_SELECTION) && ulIndex != 0) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to "
+ "unsupported selection",
+ this));
+ return TS_E_NOSELECTION;
+ }
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ if (DoNotReturnErrorFromGetSelection()) {
+ *pSelection = Selection::EmptyACP();
+ *pcFetched = 1;
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetSelection() returns fake selection range "
+ "for avoiding a crash in TSF, *pSelection=%s",
+ this, mozilla::ToString(*pSelection).c_str()));
+ return S_OK;
+ }
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ if (!selectionForTSF->HasRange()) {
+ *pSelection = Selection::EmptyACP();
+ *pcFetched = 0;
+ return TS_E_NOSELECTION;
+ }
+ *pSelection = selectionForTSF->ACPRef();
+ *pcFetched = 1;
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetSelection() succeeded, *pSelection=%s",
+ this, mozilla::ToString(*pSelection).c_str()));
+ return S_OK;
+}
+
+// static
+bool TSFTextStore::DoNotReturnErrorFromGetSelection() {
+ // There is a crash bug of TSF if we return error from GetSelection().
+ // That was introduced in Anniversary Update (build 14393, see bug 1312302)
+ // TODO: We should avoid to run this hack on fixed builds. When we get
+ // exact build number, we should get back here.
+ static bool sTSFMayCrashIfGetSelectionReturnsError =
+ IsWin10AnniversaryUpdateOrLater();
+ return sTSFMayCrashIfGetSelectionReturnsError;
+}
+
+Maybe<TSFTextStore::Content>& TSFTextStore::ContentForTSF() {
+ // This should be called when the document is locked or the content hasn't
+ // been abandoned yet.
+ if (NS_WARN_IF(!IsReadLocked() && mContentForTSF.isNothing())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
+ "called wrong timing, IsReadLocked()=%s, mContentForTSF=Nothing",
+ this, GetBoolName(IsReadLocked())));
+ return mContentForTSF;
+ }
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
+ "SelectionForTSF() failure",
+ this));
+ mContentForTSF.reset();
+ return mContentForTSF;
+ }
+
+ if (mContentForTSF.isNothing()) {
+ AutoNotifyingTSFBatch deferNotifyingTSF(*this);
+
+ nsString text; // Don't use auto string for avoiding to copy long string.
+ if (NS_WARN_IF(!GetCurrentText(text))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
+ "GetCurrentText() failure",
+ this));
+ return mContentForTSF;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mContentForTSF.isNothing(),
+ "How was it initialized recursively?");
+ mContentForTSF.reset(); // For avoiding crash in release channel
+ mContentForTSF.emplace(*this, text);
+ // Basically, the cached content which is expected by TSF/TIP should be
+ // cleared after active composition is committed or the document lock is
+ // unlocked. However, in e10s mode, content will be modified
+ // asynchronously. In such case, mDeferClearingContentForTSF may be
+ // true until whole dispatched events are handled by the focused editor.
+ mDeferClearingContentForTSF = false;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::ContentForTSF(): mContentForTSF=%s", this,
+ mozilla::ToString(mContentForTSF).c_str()));
+
+ return mContentForTSF;
+}
+
+bool TSFTextStore::CanAccessActualContentDirectly() const {
+ if (mContentForTSF.isNothing() || mSelectionForTSF.isNothing()) {
+ return true;
+ }
+
+ // If the cached content has been changed by something except composition,
+ // the content cache may be different from actual content.
+ if (mPendingTextChangeData.IsValid() &&
+ !mPendingTextChangeData.mCausedOnlyByComposition) {
+ return false;
+ }
+
+ // If the cached selection isn't changed, cached content and actual content
+ // should be same.
+ if (mPendingSelectionChangeData.isNothing()) {
+ return true;
+ }
+
+ return mSelectionForTSF->EqualsExceptDirection(*mPendingSelectionChangeData);
+}
+
+bool TSFTextStore::GetCurrentText(nsAString& aTextContent) {
+ if (mContentForTSF.isSome()) {
+ aTextContent = mContentForTSF->TextRef();
+ return true;
+ }
+
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(mWidget && !mWidget->Destroyed());
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetCurrentText(): "
+ "retrieving text from the content...",
+ this));
+
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ mWidget);
+ queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
+ mWidget->InitEvent(queryTextContentEvent);
+ DispatchEvent(queryTextContentEvent);
+ if (NS_WARN_IF(queryTextContentEvent.Failed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetCurrentText(), FAILED, due to "
+ "eQueryTextContent failure",
+ this));
+ aTextContent.Truncate();
+ return false;
+ }
+
+ aTextContent = queryTextContentEvent.mReply->DataRef();
+ return true;
+}
+
+Maybe<TSFTextStore::Selection>& TSFTextStore::SelectionForTSF() {
+ if (mSelectionForTSF.isNothing()) {
+ MOZ_ASSERT(!mDestroyed);
+ // If the window has never been available, we should crash since working
+ // with broken values may make TIP confused.
+ if (!mWidget || mWidget->Destroyed()) {
+ MOZ_ASSERT_UNREACHABLE("There should be non-destroyed widget");
+ }
+
+ AutoNotifyingTSFBatch deferNotifyingTSF(*this);
+
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ mWidget);
+ mWidget->InitEvent(querySelectedTextEvent);
+ DispatchEvent(querySelectedTextEvent);
+ if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
+ return mSelectionForTSF;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(mSelectionForTSF.isNothing(),
+ "How was it initialized recursively?");
+ mSelectionForTSF = Some(Selection(querySelectedTextEvent));
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::SelectionForTSF() succeeded, "
+ "mSelectionForTSF=%s",
+ this, ToString(mSelectionForTSF).c_str()));
+
+ return mSelectionForTSF;
+}
+
+static HRESULT GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength) {
+ RefPtr<ITfRangeACP> rangeACP;
+ aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP));
+ NS_ENSURE_TRUE(rangeACP, E_FAIL);
+ return rangeACP->GetExtent(aStart, aLength);
+}
+
+static TextRangeType GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE& aDisplayAttr) {
+ switch (aDisplayAttr.bAttr) {
+ case TF_ATTR_TARGET_CONVERTED:
+ return TextRangeType::eSelectedClause;
+ case TF_ATTR_CONVERTED:
+ return TextRangeType::eConvertedClause;
+ case TF_ATTR_TARGET_NOTCONVERTED:
+ return TextRangeType::eSelectedRawClause;
+ default:
+ return TextRangeType::eRawClause;
+ }
+}
+
+HRESULT
+TSFTextStore::GetDisplayAttribute(ITfProperty* aAttrProperty, ITfRange* aRange,
+ TF_DISPLAYATTRIBUTE* aResult) {
+ NS_ENSURE_TRUE(aAttrProperty, E_FAIL);
+ NS_ENSURE_TRUE(aRange, E_FAIL);
+ NS_ENSURE_TRUE(aResult, E_FAIL);
+
+ HRESULT hr;
+
+ if (MOZ_LOG_TEST(gIMELog, LogLevel::Debug)) {
+ LONG start = 0, length = 0;
+ hr = GetRangeExtent(aRange, &start, &length);
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetDisplayAttribute(): "
+ "GetDisplayAttribute range=%ld-%ld (hr=%s)",
+ this, start - mComposition->StartOffset(),
+ start - mComposition->StartOffset() + length,
+ GetCommonReturnValueName(hr)));
+ }
+
+ VARIANT propValue;
+ ::VariantInit(&propValue);
+ hr = aAttrProperty->GetValue(TfEditCookie(mEditCookie), aRange, &propValue);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfProperty::GetValue() failed",
+ this));
+ return hr;
+ }
+ if (VT_I4 != propValue.vt) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfProperty::GetValue() returns non-VT_I4 value",
+ this));
+ ::VariantClear(&propValue);
+ return E_FAIL;
+ }
+
+ RefPtr<ITfCategoryMgr> categoryMgr = GetCategoryMgr();
+ if (NS_WARN_IF(!categoryMgr)) {
+ return E_FAIL;
+ }
+ GUID guid;
+ hr = categoryMgr->GetGUID(DWORD(propValue.lVal), &guid);
+ ::VariantClear(&propValue);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfCategoryMgr::GetGUID() failed",
+ this));
+ return hr;
+ }
+
+ RefPtr<ITfDisplayAttributeMgr> displayAttrMgr = GetDisplayAttributeMgr();
+ if (NS_WARN_IF(!displayAttrMgr)) {
+ return E_FAIL;
+ }
+ RefPtr<ITfDisplayAttributeInfo> info;
+ hr = displayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info),
+ nullptr);
+ if (FAILED(hr) || !info) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed",
+ this));
+ return hr;
+ }
+
+ hr = info->GetAttributeInfo(aResult);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfDisplayAttributeInfo::GetAttributeInfo() failed",
+ this));
+ return hr;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetDisplayAttribute() succeeded: "
+ "Result={ %s }",
+ this, GetDisplayAttrStr(*aResult).get()));
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::RestartCompositionIfNecessary(ITfRange* aRangeNew) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary("
+ "aRangeNew=0x%p), mComposition=%s",
+ this, aRangeNew, ToString(mComposition).c_str()));
+
+ if (mComposition.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
+ "due to no composition view",
+ this));
+ return E_FAIL;
+ }
+
+ HRESULT hr;
+ RefPtr<ITfCompositionView> pComposition(mComposition->GetView());
+ RefPtr<ITfRange> composingRange(aRangeNew);
+ if (!composingRange) {
+ hr = pComposition->GetRange(getter_AddRefs(composingRange));
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
+ "FAILED due to pComposition->GetRange() failure",
+ this));
+ return hr;
+ }
+ }
+
+ // Get starting offset of the composition
+ LONG compStart = 0, compLength = 0;
+ hr = GetRangeExtent(composingRange, &compStart, &compLength);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
+ "due to GetRangeExtent() failure",
+ this));
+ return hr;
+ }
+
+ if (mComposition->StartOffset() == compStart &&
+ mComposition->Length() == compLength) {
+ return S_OK;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary(), "
+ "restaring composition because of compostion range is changed "
+ "(range=%ld-%ld, mComposition=%s)",
+ this, compStart, compStart + compLength,
+ ToString(mComposition).c_str()));
+
+ // If the queried composition length is different from the length
+ // of our composition string, OnUpdateComposition is being called
+ // because a part of the original composition was committed.
+ hr = RestartComposition(*mComposition, pComposition, composingRange);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
+ "FAILED due to RestartComposition() failure",
+ this));
+ return hr;
+ }
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() succeeded", this));
+ return S_OK;
+}
+
+HRESULT TSFTextStore::RestartComposition(Composition& aCurrentComposition,
+ ITfCompositionView* aCompositionView,
+ ITfRange* aNewRange) {
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+
+ LONG newStart, newLength;
+ HRESULT hr = GetRangeExtent(aNewRange, &newStart, &newLength);
+ LONG newEnd = newStart + newLength;
+
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartComposition() FAILED "
+ "due to SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartComposition() FAILED "
+ "due to GetRangeExtent() failure",
+ this));
+ return hr;
+ }
+
+ // If the new range has no overlap with the crrent range, we just commit
+ // the composition and restart new composition with the new range but
+ // current selection range should be preserved.
+ if (newStart >= aCurrentComposition.EndOffset() ||
+ newEnd <= aCurrentComposition.StartOffset()) {
+ RecordCompositionEndAction();
+ RecordCompositionStartAction(aCompositionView, newStart, newLength, true);
+ return S_OK;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartComposition(aCompositionView=0x%p, "
+ "aNewRange=0x%p { newStart=%ld, newLength=%ld }), "
+ "aCurrentComposition=%s, "
+ "selectionForTSF=%s",
+ this, aCompositionView, aNewRange, newStart, newLength,
+ ToString(aCurrentComposition).c_str(),
+ ToString(selectionForTSF).c_str()));
+
+ // If the new range has an overlap with the current one, we should not commit
+ // the whole current range to avoid creating an odd undo transaction.
+ // I.e., the overlapped range which is being composed should not appear in
+ // undo transaction.
+
+ // Backup current composition data and selection data.
+ Composition oldComposition = aCurrentComposition;
+ Selection oldSelection = *selectionForTSF;
+
+ // Commit only the part of composition.
+ LONG keepComposingStartOffset =
+ std::max(oldComposition.StartOffset(), newStart);
+ LONG keepComposingEndOffset = std::min(oldComposition.EndOffset(), newEnd);
+ MOZ_ASSERT(
+ keepComposingStartOffset <= keepComposingEndOffset,
+ "Why keepComposingEndOffset is smaller than keepComposingStartOffset?");
+ LONG keepComposingLength = keepComposingEndOffset - keepComposingStartOffset;
+ // Remove the overlapped part from the commit string.
+ nsAutoString commitString(oldComposition.DataRef());
+ commitString.Cut(keepComposingStartOffset - oldComposition.StartOffset(),
+ keepComposingLength);
+ // Update the composition string.
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartComposition() FAILED "
+ "due to ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ contentForTSF->ReplaceTextWith(oldComposition.StartOffset(),
+ oldComposition.Length(), commitString);
+ MOZ_ASSERT(mComposition.isSome());
+ // Record a compositionupdate action for commit the part of composing string.
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ if (mComposition.isSome()) {
+ action->mData = mComposition->DataRef();
+ }
+ action->mRanges->Clear();
+ // Note that we shouldn't append ranges when composition string
+ // is empty because it may cause TextComposition confused.
+ if (!action->mData.IsEmpty()) {
+ TextRange caretRange;
+ caretRange.mStartOffset = caretRange.mEndOffset = static_cast<uint32_t>(
+ oldComposition.StartOffset() + commitString.Length());
+ caretRange.mRangeType = TextRangeType::eCaret;
+ action->mRanges->AppendElement(caretRange);
+ }
+ action->mIncomplete = false;
+
+ // Record compositionend action.
+ RecordCompositionEndAction();
+
+ // Record compositionstart action only with the new start since this method
+ // hasn't restored composing string yet.
+ RecordCompositionStartAction(aCompositionView, newStart, 0, false);
+
+ // Restore the latest text content and selection.
+ contentForTSF->ReplaceSelectedTextWith(nsDependentSubstring(
+ oldComposition.DataRef(),
+ keepComposingStartOffset - oldComposition.StartOffset(),
+ keepComposingLength));
+ selectionForTSF = Some(oldSelection);
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartComposition() succeeded, "
+ "mComposition=%s, selectionForTSF=%s",
+ this, ToString(mComposition).c_str(),
+ ToString(selectionForTSF).c_str()));
+
+ return S_OK;
+}
+
+static bool GetColor(const TF_DA_COLOR& aTSFColor, nscolor& aResult) {
+ switch (aTSFColor.type) {
+ case TF_CT_SYSCOLOR: {
+ DWORD sysColor = ::GetSysColor(aTSFColor.nIndex);
+ aResult =
+ NS_RGB(GetRValue(sysColor), GetGValue(sysColor), GetBValue(sysColor));
+ return true;
+ }
+ case TF_CT_COLORREF:
+ aResult = NS_RGB(GetRValue(aTSFColor.cr), GetGValue(aTSFColor.cr),
+ GetBValue(aTSFColor.cr));
+ return true;
+ case TF_CT_NONE:
+ default:
+ return false;
+ }
+}
+
+static bool GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle,
+ TextRangeStyle::LineStyle& aTextRangeLineStyle) {
+ switch (aTSFLineStyle) {
+ case TF_LS_NONE:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::None;
+ return true;
+ case TF_LS_SOLID:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::Solid;
+ return true;
+ case TF_LS_DOT:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::Dotted;
+ return true;
+ case TF_LS_DASH:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::Dashed;
+ return true;
+ case TF_LS_SQUIGGLE:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::Wavy;
+ return true;
+ default:
+ return false;
+ }
+}
+
+HRESULT
+TSFTextStore::RecordCompositionUpdateAction() {
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction(), mComposition=%s",
+ this, ToString(mComposition).c_str()));
+
+ if (mComposition.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to no composition view",
+ this));
+ return E_FAIL;
+ }
+
+ // Getting display attributes is *really* complicated!
+ // We first get the context and the property objects to query for
+ // attributes, but since a big range can have a variety of values for
+ // the attribute, we have to find out all the ranges that have distinct
+ // attribute values. Then we query for what the value represents through
+ // the display attribute manager and translate that to TextRange to be
+ // sent in eCompositionChange
+
+ RefPtr<ITfProperty> attrPropetry;
+ HRESULT hr =
+ mContext->GetProperty(GUID_PROP_ATTRIBUTE, getter_AddRefs(attrPropetry));
+ if (FAILED(hr) || !attrPropetry) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to mContext->GetProperty() failure",
+ this));
+ return FAILED(hr) ? hr : E_FAIL;
+ }
+
+ RefPtr<ITfRange> composingRange;
+ hr = mComposition->GetView()->GetRange(getter_AddRefs(composingRange));
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "FAILED due to mComposition->GetView()->GetRange() failure",
+ this));
+ return hr;
+ }
+
+ RefPtr<IEnumTfRanges> enumRanges;
+ hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie),
+ getter_AddRefs(enumRanges), composingRange);
+ if (FAILED(hr) || !enumRanges) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to attrPropetry->EnumRanges() failure",
+ this));
+ return FAILED(hr) ? hr : E_FAIL;
+ }
+
+ // First, put the log of content and selection here.
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ action->mData = mComposition->DataRef();
+ // The ranges might already have been initialized, however, if this is
+ // called again, that means we need to overwrite the ranges with current
+ // information.
+ action->mRanges->Clear();
+
+ // Note that we shouldn't append ranges when composition string
+ // is empty because it may cause TextComposition confused.
+ if (!action->mData.IsEmpty()) {
+ TextRange newRange;
+ // No matter if we have display attribute info or not,
+ // we always pass in at least one range to eCompositionChange
+ newRange.mStartOffset = 0;
+ newRange.mEndOffset = action->mData.Length();
+ newRange.mRangeType = TextRangeType::eRawClause;
+ action->mRanges->AppendElement(newRange);
+
+ RefPtr<ITfRange> range;
+ while (enumRanges->Next(1, getter_AddRefs(range), nullptr) == S_OK) {
+ if (NS_WARN_IF(!range)) {
+ break;
+ }
+
+ LONG rangeStart = 0, rangeLength = 0;
+ if (FAILED(GetRangeExtent(range, &rangeStart, &rangeLength))) {
+ continue;
+ }
+ // The range may include out of composition string. We should ignore
+ // outside of the composition string.
+ LONG start = std::min(std::max(rangeStart, mComposition->StartOffset()),
+ mComposition->EndOffset());
+ LONG end = std::max(
+ std::min(rangeStart + rangeLength, mComposition->EndOffset()),
+ mComposition->StartOffset());
+ LONG length = end - start;
+ if (length < 0) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "ignores invalid range (%ld-%ld)",
+ this, rangeStart - mComposition->StartOffset(),
+ rangeStart - mComposition->StartOffset() + rangeLength));
+ continue;
+ }
+ if (!length) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "ignores a range due to outside of the composition or empty "
+ "(%ld-%ld)",
+ this, rangeStart - mComposition->StartOffset(),
+ rangeStart - mComposition->StartOffset() + rangeLength));
+ continue;
+ }
+
+ TextRange newRange;
+ newRange.mStartOffset =
+ static_cast<uint32_t>(start - mComposition->StartOffset());
+ // The end of the last range in the array is
+ // always kept at the end of composition
+ newRange.mEndOffset = mComposition->Length();
+
+ TF_DISPLAYATTRIBUTE attr;
+ hr = GetDisplayAttribute(attrPropetry, range, &attr);
+ if (FAILED(hr)) {
+ newRange.mRangeType = TextRangeType::eRawClause;
+ } else {
+ newRange.mRangeType = GetGeckoSelectionValue(attr);
+ if (GetColor(attr.crText, newRange.mRangeStyle.mForegroundColor)) {
+ newRange.mRangeStyle.mDefinedStyles |=
+ TextRangeStyle::DEFINED_FOREGROUND_COLOR;
+ }
+ if (GetColor(attr.crBk, newRange.mRangeStyle.mBackgroundColor)) {
+ newRange.mRangeStyle.mDefinedStyles |=
+ TextRangeStyle::DEFINED_BACKGROUND_COLOR;
+ }
+ if (GetColor(attr.crLine, newRange.mRangeStyle.mUnderlineColor)) {
+ newRange.mRangeStyle.mDefinedStyles |=
+ TextRangeStyle::DEFINED_UNDERLINE_COLOR;
+ }
+ if (GetLineStyle(attr.lsStyle, newRange.mRangeStyle.mLineStyle)) {
+ newRange.mRangeStyle.mDefinedStyles |=
+ TextRangeStyle::DEFINED_LINESTYLE;
+ newRange.mRangeStyle.mIsBoldLine = attr.fBoldLine != 0;
+ }
+ }
+
+ TextRange& lastRange = action->mRanges->LastElement();
+ if (lastRange.mStartOffset == newRange.mStartOffset) {
+ // Replace range if last range is the same as this one
+ // So that ranges don't overlap and confuse the editor
+ lastRange = newRange;
+ } else {
+ lastRange.mEndOffset = newRange.mStartOffset;
+ action->mRanges->AppendElement(newRange);
+ }
+ }
+
+ // We need to hack for Korean Input System which is Korean standard TIP.
+ // It sets no change style to IME selection (the selection is always only
+ // one). So, the composition string looks like normal (or committed)
+ // string. At this time, current selection range is same as the
+ // composition string range. Other applications set a wide caret which
+ // covers the composition string, however, Gecko doesn't support the wide
+ // caret drawing now (Gecko doesn't support XOR drawing), unfortunately.
+ // For now, we should change the range style to undefined.
+ if (!selectionForTSF->Collapsed() && action->mRanges->Length() == 1) {
+ TextRange& range = action->mRanges->ElementAt(0);
+ LONG start = selectionForTSF->MinOffset();
+ LONG end = selectionForTSF->MaxOffset();
+ if (static_cast<LONG>(range.mStartOffset) ==
+ start - mComposition->StartOffset() &&
+ static_cast<LONG>(range.mEndOffset) ==
+ end - mComposition->StartOffset() &&
+ range.mRangeStyle.IsNoChangeStyle()) {
+ range.mRangeStyle.Clear();
+ // The looks of selected type is better than others.
+ range.mRangeType = TextRangeType::eSelectedRawClause;
+ }
+ }
+
+ // The caret position has to be collapsed.
+ uint32_t caretPosition = static_cast<uint32_t>(
+ selectionForTSF->HasRange()
+ ? selectionForTSF->MaxOffset() - mComposition->StartOffset()
+ : mComposition->StartOffset());
+
+ // If caret is in the target clause and it doesn't have specific style,
+ // the target clause will be painted as normal selection range. Since
+ // caret shouldn't be in selection range on Windows, we shouldn't append
+ // caret range in such case.
+ const TextRange* targetClause = action->mRanges->GetTargetClause();
+ if (!targetClause || targetClause->mRangeStyle.IsDefined() ||
+ caretPosition < targetClause->mStartOffset ||
+ caretPosition > targetClause->mEndOffset) {
+ TextRange caretRange;
+ caretRange.mStartOffset = caretRange.mEndOffset = caretPosition;
+ caretRange.mRangeType = TextRangeType::eCaret;
+ action->mRanges->AppendElement(caretRange);
+ }
+ }
+
+ action->mIncomplete = false;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "succeeded",
+ this));
+
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection,
+ bool aDispatchCompositionChangeEvent) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::SetSelectionInternal(pSelection=%s, "
+ "aDispatchCompositionChangeEvent=%s), mComposition=%s",
+ this, pSelection ? mozilla::ToString(*pSelection).c_str() : "nullptr",
+ GetBoolName(aDispatchCompositionChangeEvent),
+ ToString(mComposition).c_str()));
+
+ MOZ_ASSERT(IsReadWriteLocked());
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return E_FAIL;
+ }
+
+ // If actually the range is not changing, we should do nothing.
+ // Perhaps, we can ignore the difference change because it must not be
+ // important for following edit.
+ if (selectionForTSF->EqualsExceptDirection(*pSelection)) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::SetSelectionInternal() Succeeded but "
+ "did nothing because the selection range isn't changing",
+ this));
+ selectionForTSF->SetSelection(*pSelection);
+ return S_OK;
+ }
+
+ if (mComposition.isSome()) {
+ if (aDispatchCompositionChangeEvent) {
+ HRESULT hr = RestartCompositionIfNecessary();
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "RestartCompositionIfNecessary() failure",
+ this));
+ return hr;
+ }
+ }
+ if (pSelection->acpStart < mComposition->StartOffset() ||
+ pSelection->acpEnd > mComposition->EndOffset()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "the selection being out of the composition string",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+ // Emulate selection during compositions
+ selectionForTSF->SetSelection(*pSelection);
+ if (aDispatchCompositionChangeEvent) {
+ HRESULT hr = RecordCompositionUpdateAction();
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "RecordCompositionUpdateAction() failure",
+ this));
+ return hr;
+ }
+ }
+ return S_OK;
+ }
+
+ TS_SELECTION_ACP selectionInContent(*pSelection);
+
+ // If mContentForTSF caches old contents which is now different from
+ // actual contents, we need some complicated hack here...
+ // Note that this hack assumes that this is used for reconversion.
+ if (mContentForTSF.isSome() && mPendingTextChangeData.IsValid() &&
+ !mPendingTextChangeData.mCausedOnlyByComposition) {
+ uint32_t startOffset = static_cast<uint32_t>(selectionInContent.acpStart);
+ uint32_t endOffset = static_cast<uint32_t>(selectionInContent.acpEnd);
+ if (mPendingTextChangeData.mStartOffset >= endOffset) {
+ // Setting selection before any changed ranges is fine.
+ } else if (mPendingTextChangeData.mRemovedEndOffset <= startOffset) {
+ // Setting selection after removed range is fine with following
+ // adjustment.
+ selectionInContent.acpStart += mPendingTextChangeData.Difference();
+ selectionInContent.acpEnd += mPendingTextChangeData.Difference();
+ } else if (startOffset == endOffset) {
+ // Moving caret position may be fine in most cases even if the insertion
+ // point has already gone but in this case, composition will be inserted
+ // to unexpected position, though.
+ // It seems that moving caret into middle of the new text is odd.
+ // Perhaps, end of it is expected by users in most cases.
+ selectionInContent.acpStart = mPendingTextChangeData.mAddedEndOffset;
+ selectionInContent.acpEnd = selectionInContent.acpStart;
+ } else {
+ // Otherwise, i.e., setting range has already gone, we cannot set
+ // selection properly.
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "there is unknown content change",
+ this));
+ return E_FAIL;
+ }
+ }
+
+ CompleteLastActionIfStillIncomplete();
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::Type::eSetSelection;
+ action->mSelectionStart = selectionInContent.acpStart;
+ action->mSelectionLength =
+ selectionInContent.acpEnd - selectionInContent.acpStart;
+ action->mSelectionReversed = (selectionInContent.style.ase == TS_AE_START);
+
+ // Use TSF specified selection for updating mSelectionForTSF.
+ selectionForTSF->SetSelection(*pSelection);
+
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::SetSelection(ULONG ulCount, const TS_SELECTION_ACP* pSelection) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetSelection(ulCount=%lu, pSelection=%s }), "
+ "mComposition=%s",
+ this, ulCount,
+ pSelection ? mozilla::ToString(pSelection).c_str() : "nullptr",
+ ToString(mComposition).c_str()));
+
+ if (!IsReadWriteLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "not locked (read-write)",
+ this));
+ return TS_E_NOLOCK;
+ }
+ if (ulCount != 1) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "trying setting multiple selection",
+ this));
+ return E_INVALIDARG;
+ }
+ if (!pSelection) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = SetSelectionInternal(pSelection, true);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "SetSelectionInternal() failure",
+ this));
+ } else {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetSelection() succeeded", this));
+ }
+ return hr;
+}
+
+STDMETHODIMP
+TSFTextStore::GetText(LONG acpStart, LONG acpEnd, WCHAR* pchPlain,
+ ULONG cchPlainReq, ULONG* pcchPlainOut,
+ TS_RUNINFO* prgRunInfo, ULONG ulRunInfoReq,
+ ULONG* pulRunInfoOut, LONG* pacpNext) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetText(acpStart=%ld, acpEnd=%ld, pchPlain=0x%p, "
+ "cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, ulRunInfoReq=%lu, "
+ "pulRunInfoOut=0x%p, pacpNext=0x%p), mComposition=%s",
+ this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, prgRunInfo,
+ ulRunInfoReq, pulRunInfoOut, pacpNext, ToString(mComposition).c_str()));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pcchPlainOut || (!pchPlain && !prgRunInfo) ||
+ !cchPlainReq != !pchPlain || !ulRunInfoReq != !prgRunInfo) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "invalid argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (acpStart < 0 || acpEnd < -1 || (acpEnd != -1 && acpStart > acpEnd)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "invalid position",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+
+ // Making sure to null-terminate string just to be on the safe side
+ *pcchPlainOut = 0;
+ if (pchPlain && cchPlainReq) *pchPlain = 0;
+ if (pulRunInfoOut) *pulRunInfoOut = 0;
+ if (pacpNext) *pacpNext = acpStart;
+ if (prgRunInfo && ulRunInfoReq) {
+ prgRunInfo->uCount = 0;
+ prgRunInfo->type = TS_RT_PLAIN;
+ }
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ if (contentForTSF->TextRef().Length() < static_cast<uint32_t>(acpStart)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "acpStart is larger offset than the actual text length",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+ if (acpEnd != -1 &&
+ contentForTSF->TextRef().Length() < static_cast<uint32_t>(acpEnd)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "acpEnd is larger offset than the actual text length",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+ uint32_t length = (acpEnd == -1) ? contentForTSF->TextRef().Length() -
+ static_cast<uint32_t>(acpStart)
+ : static_cast<uint32_t>(acpEnd - acpStart);
+ if (cchPlainReq && cchPlainReq - 1 < length) {
+ length = cchPlainReq - 1;
+ }
+ if (length) {
+ if (pchPlain && cchPlainReq) {
+ const char16_t* startChar =
+ contentForTSF->TextRef().BeginReading() + acpStart;
+ memcpy(pchPlain, startChar, length * sizeof(*pchPlain));
+ pchPlain[length] = 0;
+ *pcchPlainOut = length;
+ }
+ if (prgRunInfo && ulRunInfoReq) {
+ prgRunInfo->uCount = length;
+ prgRunInfo->type = TS_RT_PLAIN;
+ if (pulRunInfoOut) *pulRunInfoOut = 1;
+ }
+ if (pacpNext) *pacpNext = acpStart + length;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetText() succeeded: pcchPlainOut=0x%p, "
+ "*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, "
+ "*pacpNext=%ld)",
+ this, pcchPlainOut, prgRunInfo ? prgRunInfo->uCount : 0,
+ prgRunInfo ? GetTextRunTypeName(prgRunInfo->type) : "N/A",
+ pulRunInfoOut ? *pulRunInfoOut : 0, pacpNext ? *pacpNext : 0));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd,
+ const WCHAR* pchText, ULONG cch, TS_TEXTCHANGE* pChange) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetText(dwFlags=%s, acpStart=%ld, acpEnd=%ld, "
+ "pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), mComposition=%s",
+ this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" : "not-specified",
+ acpStart, acpEnd, pchText,
+ pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "", cch,
+ pChange, ToString(mComposition).c_str()));
+
+ // Per SDK documentation, and since we don't have better
+ // ways to do this, this method acts as a helper to
+ // call SetSelection followed by InsertTextAtSelection
+ if (!IsReadWriteLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetText() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ TS_SELECTION_ACP selection;
+ selection.acpStart = acpStart;
+ selection.acpEnd = acpEnd;
+ selection.style.ase = TS_AE_END;
+ selection.style.fInterimChar = 0;
+ // Set selection to desired range
+ HRESULT hr = SetSelectionInternal(&selection);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetText() FAILED due to "
+ "SetSelectionInternal() failure",
+ this));
+ return hr;
+ }
+ // Replace just selected text
+ if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
+ pChange)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetText() FAILED due to "
+ "InsertTextAtSelectionInternal() failure",
+ this));
+ return E_FAIL;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetText() succeeded: pChange={ "
+ "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
+ this, pChange ? pChange->acpStart : 0,
+ pChange ? pChange->acpOldEnd : 0, pChange ? pChange->acpNewEnd : 0));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetFormattedText(LONG acpStart, LONG acpEnd,
+ IDataObject** ppDataObject) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetFormattedText() called "
+ "but not supported (E_NOTIMPL)",
+ this));
+
+ // no support for formatted text
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP
+TSFTextStore::GetEmbedded(LONG acpPos, REFGUID rguidService, REFIID riid,
+ IUnknown** ppunk) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetEmbedded() called "
+ "but not supported (E_NOTIMPL)",
+ this));
+
+ // embedded objects are not supported
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP
+TSFTextStore::QueryInsertEmbedded(const GUID* pguidService,
+ const FORMATETC* pFormatEtc,
+ BOOL* pfInsertable) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::QueryInsertEmbedded() called "
+ "but not supported, *pfInsertable=FALSE (S_OK)",
+ this));
+
+ // embedded objects are not supported
+ *pfInsertable = FALSE;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::InsertEmbedded(DWORD dwFlags, LONG acpStart, LONG acpEnd,
+ IDataObject* pDataObject, TS_TEXTCHANGE* pChange) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertEmbedded() called "
+ "but not supported (E_NOTIMPL)",
+ this));
+
+ // embedded objects are not supported
+ return E_NOTIMPL;
+}
+
+// static
+bool TSFTextStore::ShouldSetInputScopeOfURLBarToDefault() {
+ // FYI: Google Japanese Input may be an IMM-IME. If it's installed on
+ // Win7, it's always IMM-IME. Otherwise, basically, it's a TIP.
+ // However, if it's installed on Win7 and has not been updated yet
+ // after the OS is upgraded to Win8 or later, it's still an IMM-IME.
+ // Therefore, we also need to check with IMMHandler here.
+ if (!StaticPrefs::intl_ime_hack_set_input_scope_of_url_bar_to_default()) {
+ return false;
+ }
+
+ if (IMMHandler::IsGoogleJapaneseInputActive()) {
+ return true;
+ }
+
+ switch (TSFStaticSink::ActiveTIP()) {
+ case TextInputProcessorID::eMicrosoftIMEForJapanese:
+ case TextInputProcessorID::eGoogleJapaneseInput:
+ case TextInputProcessorID::eMicrosoftBopomofo:
+ case TextInputProcessorID::eMicrosoftChangJie:
+ case TextInputProcessorID::eMicrosoftPhonetic:
+ case TextInputProcessorID::eMicrosoftQuick:
+ case TextInputProcessorID::eMicrosoftNewChangJie:
+ case TextInputProcessorID::eMicrosoftNewPhonetic:
+ case TextInputProcessorID::eMicrosoftNewQuick:
+ case TextInputProcessorID::eMicrosoftPinyin:
+ case TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle:
+ case TextInputProcessorID::eMicrosoftOldHangul:
+ case TextInputProcessorID::eMicrosoftWubi:
+ return true;
+ case TextInputProcessorID::eMicrosoftIMEForKorean:
+ return IsWin8OrLater();
+ default:
+ return false;
+ }
+}
+
+void TSFTextStore::SetInputScope(const nsString& aHTMLInputType,
+ const nsString& aHTMLInputMode) {
+ mInputScopes.Clear();
+
+ // IME may refer only first input scope, but we will append inputmode's
+ // input scopes too like Chrome since IME may refer it.
+ IMEHandler::AppendInputScopeFromType(aHTMLInputType, mInputScopes);
+ IMEHandler::AppendInputScopeFromInputMode(aHTMLInputMode, mInputScopes);
+
+ if (mInPrivateBrowsing) {
+ mInputScopes.AppendElement(IS_PRIVATE);
+ }
+}
+
+int32_t TSFTextStore::GetRequestedAttrIndex(const TS_ATTRID& aAttrID) {
+ if (IsEqualGUID(aAttrID, GUID_PROP_INPUTSCOPE)) {
+ return eInputScope;
+ }
+ if (IsEqualGUID(aAttrID, sGUID_PROP_URL)) {
+ return eDocumentURL;
+ }
+ if (IsEqualGUID(aAttrID, TSATTRID_Text_VerticalWriting)) {
+ return eTextVerticalWriting;
+ }
+ if (IsEqualGUID(aAttrID, TSATTRID_Text_Orientation)) {
+ return eTextOrientation;
+ }
+ return eNotSupported;
+}
+
+TS_ATTRID
+TSFTextStore::GetAttrID(int32_t aIndex) {
+ switch (aIndex) {
+ case eInputScope:
+ return GUID_PROP_INPUTSCOPE;
+ case eDocumentURL:
+ return sGUID_PROP_URL;
+ case eTextVerticalWriting:
+ return TSATTRID_Text_VerticalWriting;
+ case eTextOrientation:
+ return TSATTRID_Text_Orientation;
+ default:
+ MOZ_CRASH("Invalid index? Or not implemented yet?");
+ return GUID_NULL;
+ }
+}
+
+HRESULT
+TSFTextStore::HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
+ const TS_ATTRID* aFilterAttrs) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::HandleRequestAttrs(aFlags=%s, "
+ "aFilterCount=%lu)",
+ this, GetFindFlagName(aFlags).get(), aFilterCount));
+
+ // This is a little weird! RequestSupportedAttrs gives us advanced notice
+ // of a support query via RetrieveRequestedAttrs for a specific attribute.
+ // RetrieveRequestedAttrs needs to return valid data for all attributes we
+ // support, but the text service will only want the input scope object
+ // returned in RetrieveRequestedAttrs if the dwFlags passed in here contains
+ // TS_ATTR_FIND_WANT_VALUE.
+ for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
+ mRequestedAttrs[i] = false;
+ }
+ mRequestedAttrValues = !!(aFlags & TS_ATTR_FIND_WANT_VALUE);
+
+ for (uint32_t i = 0; i < aFilterCount; i++) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::HandleRequestAttrs(), "
+ "requested attr=%s",
+ this, GetGUIDNameStrWithTable(aFilterAttrs[i]).get()));
+ int32_t index = GetRequestedAttrIndex(aFilterAttrs[i]);
+ if (index != eNotSupported) {
+ mRequestedAttrs[index] = true;
+ }
+ }
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::RequestSupportedAttrs(DWORD dwFlags, ULONG cFilterAttrs,
+ const TS_ATTRID* paFilterAttrs) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestSupportedAttrs(dwFlags=%s, "
+ "cFilterAttrs=%lu)",
+ this, GetFindFlagName(dwFlags).get(), cFilterAttrs));
+
+ return HandleRequestAttrs(dwFlags, cFilterAttrs, paFilterAttrs);
+}
+
+STDMETHODIMP
+TSFTextStore::RequestAttrsAtPosition(LONG acpPos, ULONG cFilterAttrs,
+ const TS_ATTRID* paFilterAttrs,
+ DWORD dwFlags) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestAttrsAtPosition(acpPos=%ld, "
+ "cFilterAttrs=%lu, dwFlags=%s)",
+ this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
+
+ return HandleRequestAttrs(dwFlags | TS_ATTR_FIND_WANT_VALUE, cFilterAttrs,
+ paFilterAttrs);
+}
+
+STDMETHODIMP
+TSFTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos,
+ ULONG cFilterAttrs,
+ const TS_ATTRID* paFilterAttr,
+ DWORD dwFlags) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestAttrsTransitioningAtPosition("
+ "acpPos=%ld, cFilterAttrs=%lu, dwFlags=%s) called but not supported "
+ "(S_OK)",
+ this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
+
+ // no per character attributes defined
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::FindNextAttrTransition(LONG acpStart, LONG acpHalt,
+ ULONG cFilterAttrs,
+ const TS_ATTRID* paFilterAttrs,
+ DWORD dwFlags, LONG* pacpNext,
+ BOOL* pfFound, LONG* plFoundOffset) {
+ if (!pacpNext || !pfFound || !plFoundOffset) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" 0x%p TSFTextStore::FindNextAttrTransition() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::FindNextAttrTransition() called "
+ "but not supported (S_OK)",
+ this));
+
+ // no per character attributes defined
+ *pacpNext = *plFoundOffset = acpHalt;
+ *pfFound = FALSE;
+ return S_OK;
+}
+
+// To test the document URL result, define this to out put it to the stdout
+// #define DEBUG_PRINT_DOCUMENT_URL
+
+STDMETHODIMP
+TSFTextStore::RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL* paAttrVals,
+ ULONG* pcFetched) {
+ if (!pcFetched || !paAttrVals) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ ULONG expectedCount = 0;
+ for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
+ if (mRequestedAttrs[i]) {
+ expectedCount++;
+ }
+ }
+ if (ulCount < expectedCount) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
+ "not enough count ulCount=%lu, expectedCount=%lu",
+ this, ulCount, expectedCount));
+ return E_INVALIDARG;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
+ "ulCount=%lu, mRequestedAttrValues=%s",
+ this, ulCount, GetBoolName(mRequestedAttrValues)));
+
+ auto GetExposingURL = [&]() -> BSTR {
+ const bool allowed =
+ StaticPrefs::intl_tsf_expose_url_allowed() &&
+ (!mInPrivateBrowsing ||
+ StaticPrefs::intl_tsf_expose_url_in_private_browsing_allowed());
+ if (!allowed || mDocumentURL.IsEmpty()) {
+ BSTR emptyString = ::SysAllocString(L"");
+ MOZ_ASSERT(
+ emptyString,
+ "We need to return valid BSTR pointer to notify TSF of supporting it "
+ "with a pointer to empty string");
+ return emptyString;
+ }
+ return ::SysAllocString(mDocumentURL.get());
+ };
+
+#ifdef DEBUG_PRINT_DOCUMENT_URL
+ {
+ BSTR exposingURL = GetExposingURL();
+ printf("TSFTextStore::RetrieveRequestedAttrs: DocumentURL=\"%s\"\n",
+ NS_ConvertUTF16toUTF8(static_cast<char16ptr_t>(_bstr_t(exposingURL)))
+ .get());
+ ::SysFreeString(exposingURL);
+ }
+#endif // #ifdef DEBUG_PRINT_DOCUMENT_URL
+
+ int32_t count = 0;
+ for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
+ if (!mRequestedAttrs[i]) {
+ continue;
+ }
+ mRequestedAttrs[i] = false;
+
+ TS_ATTRID attrID = GetAttrID(i);
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() for %s", this,
+ GetGUIDNameStrWithTable(attrID).get()));
+
+ paAttrVals[count].idAttr = attrID;
+ paAttrVals[count].dwOverlapId = 0;
+
+ if (!mRequestedAttrValues) {
+ paAttrVals[count].varValue.vt = VT_EMPTY;
+ } else {
+ switch (i) {
+ case eInputScope: {
+ paAttrVals[count].varValue.vt = VT_UNKNOWN;
+ RefPtr<IUnknown> inputScope = new InputScopeImpl(mInputScopes);
+ paAttrVals[count].varValue.punkVal = inputScope.forget().take();
+ break;
+ }
+ case eDocumentURL: {
+ paAttrVals[count].varValue.vt = VT_BSTR;
+ paAttrVals[count].varValue.bstrVal = GetExposingURL();
+ break;
+ }
+ case eTextVerticalWriting: {
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ paAttrVals[count].varValue.vt = VT_BOOL;
+ paAttrVals[count].varValue.boolVal =
+ selectionForTSF.isSome() &&
+ selectionForTSF->WritingModeRef().IsVertical()
+ ? VARIANT_TRUE
+ : VARIANT_FALSE;
+ break;
+ }
+ case eTextOrientation: {
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ paAttrVals[count].varValue.vt = VT_I4;
+ paAttrVals[count].varValue.lVal =
+ selectionForTSF.isSome() &&
+ selectionForTSF->WritingModeRef().IsVertical()
+ ? 2700
+ : 0;
+ break;
+ }
+ default:
+ MOZ_CRASH("Invalid index? Or not implemented yet?");
+ break;
+ }
+ }
+ count++;
+ }
+
+ mRequestedAttrValues = false;
+
+ if (count) {
+ *pcFetched = count;
+ return S_OK;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
+ "for unknown TS_ATTRVAL, *pcFetched=0 (S_OK)",
+ this));
+
+ paAttrVals->dwOverlapId = 0;
+ paAttrVals->varValue.vt = VT_EMPTY;
+ *pcFetched = 0;
+ return S_OK;
+}
+
+#undef DEBUG_PRINT_DOCUMENT_URL
+
+STDMETHODIMP
+TSFTextStore::GetEndACP(LONG* pacp) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetEndACP(pacp=0x%p)", this, pacp));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetEndACP() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pacp) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetEndACP() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetEndACP() FAILED due to "
+ "ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ *pacp = static_cast<LONG>(contentForTSF->TextRef().Length());
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetActiveView(TsViewCookie* pvcView) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetActiveView(pvcView=0x%p)", this, pvcView));
+
+ if (!pvcView) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetActiveView() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ *pvcView = TEXTSTORE_DEFAULT_VIEW;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetActiveView() succeeded: *pvcView=%ld", this,
+ *pvcView));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetACPFromPoint(TsViewCookie vcView, const POINT* pt,
+ DWORD dwFlags, LONG* pacp) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetACPFromPoint(pvcView=%ld, pt=%p (x=%ld, "
+ "y=%ld), dwFlags=%s, pacp=%p, mDeferNotifyingTSFUntilNextUpdate=%s, "
+ "mWaitingQueryLayout=%s",
+ this, vcView, pt, pt ? pt->x : 0, pt ? pt->y : 0,
+ GetACPFromPointFlagName(dwFlags).get(), pacp,
+ GetBoolName(mDeferNotifyingTSFUntilNextUpdate),
+ GetBoolName(mWaitingQueryLayout)));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "called with invalid view",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!pt) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "null pt",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!pacp) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "null pacp",
+ this));
+ return E_INVALIDARG;
+ }
+
+ mWaitingQueryLayout = false;
+
+ if (mDestroyed ||
+ (mContentForTSF.isSome() && mContentForTSF->IsLayoutChanged())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() returned "
+ "TS_E_NOLAYOUT",
+ this));
+ mHasReturnedNoLayoutError = true;
+ return TS_E_NOLAYOUT;
+ }
+
+ LayoutDeviceIntPoint ourPt(pt->x, pt->y);
+ // Convert to widget relative coordinates from screen's.
+ ourPt -= mWidget->WidgetToScreenOffset();
+
+ // NOTE: Don't check if the point is in the widget since the point can be
+ // outside of the widget if focused editor is in a XUL <panel>.
+
+ WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint,
+ mWidget);
+ mWidget->InitEvent(queryCharAtPointEvent, &ourPt);
+
+ // FYI: WidgetQueryContentEvent may cause flushing pending layout and it
+ // may cause focus change or something.
+ RefPtr<TSFTextStore> kungFuDeathGrip(this);
+ DispatchEvent(queryCharAtPointEvent);
+ if (!mWidget || mWidget->Destroyed()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "mWidget was destroyed during eQueryCharacterAtPoint",
+ this));
+ return E_FAIL;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetACPFromPoint(), queryCharAtPointEvent={ "
+ "mReply=%s }",
+ this, ToString(queryCharAtPointEvent.mReply).c_str()));
+
+ if (NS_WARN_IF(queryCharAtPointEvent.Failed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "eQueryCharacterAtPoint failure",
+ this));
+ return E_FAIL;
+ }
+
+ // If dwFlags isn't set and the point isn't in any character's bounding box,
+ // we should return TS_E_INVALIDPOINT.
+ if (!(dwFlags & GXFPF_NEAREST) && queryCharAtPointEvent.DidNotFindChar()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to the "
+ "point contained by no bounding box",
+ this));
+ return TS_E_INVALIDPOINT;
+ }
+
+ // Although, we're not sure if mTentativeCaretOffset becomes NOT_FOUND,
+ // let's assume that there is no content in such case.
+ NS_WARNING_ASSERTION(queryCharAtPointEvent.DidNotFindTentativeCaretOffset(),
+ "Tentative caret offset was not found");
+
+ uint32_t offset;
+
+ // If dwFlags includes GXFPF_ROUND_NEAREST, we should return tentative
+ // caret offset (MSDN calls it "range position").
+ if (dwFlags & GXFPF_ROUND_NEAREST) {
+ offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0);
+ } else if (queryCharAtPointEvent.FoundChar()) {
+ // Otherwise, we should return character offset whose bounding box contains
+ // the point.
+ offset = queryCharAtPointEvent.mReply->StartOffset();
+ } else {
+ // If the point isn't in any character's bounding box but we need to return
+ // the nearest character from the point, we should *guess* the character
+ // offset since there is no inexpensive API to check it strictly.
+ // XXX If we retrieve 2 bounding boxes, one is before the offset and
+ // the other is after the offset, we could resolve the offset.
+ // However, dispatching 2 eQueryTextRect may be expensive.
+
+ // So, use tentative offset for now.
+ offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0);
+
+ // However, if it's after the last character, we need to decrement the
+ // offset.
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ if (contentForTSF->TextRef().Length() <= offset) {
+ // If the tentative caret is after the last character, let's return
+ // the last character's offset.
+ offset = contentForTSF->TextRef().Length() - 1;
+ }
+ }
+
+ if (NS_WARN_IF(offset > LONG_MAX)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to out of "
+ "range of the result",
+ this));
+ return TS_E_INVALIDPOINT;
+ }
+
+ *pacp = static_cast<LONG>(offset);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetACPFromPoint() succeeded: *pacp=%ld", this,
+ *pacp));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd,
+ RECT* prc, BOOL* pfClipped) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetTextExt(vcView=%ld, "
+ "acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p), "
+ "IsHandlingCompositionInParent()=%s, "
+ "IsHandlingCompositionInContent()=%s, mContentForTSF=%s, "
+ "mSelectionForTSF=%s, mComposition=%s, "
+ "mDeferNotifyingTSFUntilNextUpdate=%s, mWaitingQueryLayout=%s, "
+ "IMEHandler::IsA11yHandlingNativeCaret()=%s",
+ this, vcView, acpStart, acpEnd, prc, pfClipped,
+ GetBoolName(IsHandlingCompositionInParent()),
+ GetBoolName(IsHandlingCompositionInContent()),
+ mozilla::ToString(mContentForTSF).c_str(),
+ ToString(mSelectionForTSF).c_str(), ToString(mComposition).c_str(),
+ GetBoolName(mDeferNotifyingTSFUntilNextUpdate),
+ GetBoolName(mWaitingQueryLayout),
+ GetBoolName(IMEHandler::IsA11yHandlingNativeCaret())));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "called with invalid view",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!prc || !pfClipped) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ // According to MSDN, ITextStoreACP::GetTextExt() should return
+ // TS_E_INVALIDARG when acpStart and acpEnd are same (i.e., collapsed range).
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms538435(v=vs.85).aspx
+ // > TS_E_INVALIDARG: The specified start and end character positions are
+ // > equal.
+ // However, some TIPs (including Microsoft's Chinese TIPs!) call this with
+ // collapsed range and if we return TS_E_INVALIDARG, they stops showing their
+ // owning window or shows it but odd position. So, we should just return
+ // error only when acpStart and/or acpEnd are really odd.
+
+ if (acpStart < 0 || acpEnd < acpStart) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "invalid position",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+
+ mWaitingQueryLayout = false;
+
+ if (IsHandlingCompositionInContent() && mContentForTSF.isSome() &&
+ mContentForTSF->HasOrHadComposition() &&
+ mContentForTSF->IsLayoutChanged() &&
+ mContentForTSF->MinModifiedOffset().value() >
+ static_cast<uint32_t>(LONG_MAX)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text "
+ "is too big for TSF (cannot treat modified offset as LONG), "
+ "mContentForTSF=%s",
+ this, ToString(mContentForTSF).c_str()));
+ return E_FAIL;
+ }
+
+ // At Windows 10 build 17643 (an insider preview for RS5), Microsoft fixed
+ // the bug of TS_E_NOLAYOUT (even when we returned TS_E_NOLAYOUT, TSF
+ // returned E_FAIL to TIP). However, until we drop to support older Windows
+ // and all TIPs are aware of TS_E_NOLAYOUT result, we need to keep returning
+ // S_OK and available rectangle only for them.
+ if (!MaybeHackNoErrorLayoutBugs(acpStart, acpEnd) &&
+ mContentForTSF.isSome() && mContentForTSF->IsLayoutChangedAt(acpEnd)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
+ "(acpEnd=%ld)",
+ this, acpEnd));
+ mHasReturnedNoLayoutError = true;
+ return TS_E_NOLAYOUT;
+ }
+
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
+ "(acpEnd=%ld) because this has already been destroyed",
+ this, acpEnd));
+ mHasReturnedNoLayoutError = true;
+ return TS_E_NOLAYOUT;
+ }
+
+ // use eQueryTextRect to get rect in system, screen coordinates
+ WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, mWidget);
+ mWidget->InitEvent(queryTextRectEvent);
+
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = acpStart;
+ if (mComposition.isSome()) {
+ // If there is a composition, TSF must want character rects related to
+ // the composition. Therefore, we should use insertion point relative
+ // query because the composition might be at different position from
+ // the position where TSFTextStore believes it at.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mComposition->StartOffset();
+ } else if (IsHandlingCompositionInParent() && mContentForTSF.isSome() &&
+ mContentForTSF->HasOrHadComposition()) {
+ // If there was a composition and its commit event hasn't been dispatched
+ // yet, ContentCacheInParent is still open for relative offset query from
+ // the latest composition.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mContentForTSF->LatestCompositionRange()->StartOffset();
+ } else if (!CanAccessActualContentDirectly() &&
+ mSelectionForTSF->HasRange()) {
+ // If TSF/TIP cannot access actual content directly, there may be pending
+ // text and/or selection changes which have not been notified TSF yet.
+ // Therefore, we should use relative to insertion point query since
+ // TSF/TIP computes the offset from the cached selection.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mSelectionForTSF->StartOffset();
+ }
+ // ContentEventHandler and ContentCache return actual caret rect when
+ // the queried range is collapsed and selection is collapsed at the
+ // queried range. Then, its height (in horizontal layout, width in vertical
+ // layout) may be different from actual font height of the line. In such
+ // case, users see "dancing" of candidate or suggest window of TIP.
+ // For preventing it, we should query text rect with at least 1 length.
+ uint32_t length = std::max(static_cast<int32_t>(acpEnd - acpStart), 1);
+ queryTextRectEvent.InitForQueryTextRect(startOffset, length, options);
+
+ DispatchEvent(queryTextRectEvent);
+ if (NS_WARN_IF(queryTextRectEvent.Failed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "eQueryTextRect failure",
+ this));
+ return TS_E_INVALIDPOS; // but unexpected failure, maybe.
+ }
+
+ // IMEs don't like empty rects, fix here
+ if (queryTextRectEvent.mReply->mRect.Width() <= 0) {
+ queryTextRectEvent.mReply->mRect.SetWidth(1);
+ }
+ if (queryTextRectEvent.mReply->mRect.Height() <= 0) {
+ queryTextRectEvent.mReply->mRect.SetHeight(1);
+ }
+
+ // convert to unclipped screen rect
+ nsWindow* refWindow =
+ static_cast<nsWindow*>(!!queryTextRectEvent.mReply->mFocusedWidget
+ ? queryTextRectEvent.mReply->mFocusedWidget
+ : static_cast<nsIWidget*>(mWidget.get()));
+ // Result rect is in top level widget coordinates
+ refWindow = refWindow->GetTopLevelWindow(false);
+ if (!refWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "no top level window",
+ this));
+ return E_FAIL;
+ }
+
+ queryTextRectEvent.mReply->mRect.MoveBy(refWindow->WidgetToScreenOffset());
+
+ // get bounding screen rect to test for clipping
+ if (!GetScreenExtInternal(*prc)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "GetScreenExtInternal() failure",
+ this));
+ return E_FAIL;
+ }
+
+ // clip text rect to bounding rect
+ RECT textRect;
+ ::SetRect(&textRect, queryTextRectEvent.mReply->mRect.X(),
+ queryTextRectEvent.mReply->mRect.Y(),
+ queryTextRectEvent.mReply->mRect.XMost(),
+ queryTextRectEvent.mReply->mRect.YMost());
+ if (!::IntersectRect(prc, prc, &textRect))
+ // Text is not visible
+ ::SetRectEmpty(prc);
+
+ // not equal if text rect was clipped
+ *pfClipped = !::EqualRect(prc, &textRect);
+
+ // ATOK 2011 - 2016 refers native caret position and size on windows whose
+ // class name is one of Mozilla's windows for deciding candidate window
+ // position. Additionally, ATOK 2015 and earlier behaves really odd when
+ // we don't create native caret. Therefore, we need to create native caret
+ // only when ATOK 2011 - 2015 is active (i.e., not necessary for ATOK 2016).
+ // However, if a11y module is handling native caret, we shouldn't touch it.
+ // Note that ATOK must require the latest information of the caret. So,
+ // even if we'll create native caret later, we need to creat it here with
+ // current information.
+ if (!IMEHandler::IsA11yHandlingNativeCaret() &&
+ StaticPrefs::intl_tsf_hack_atok_create_native_caret() &&
+ TSFStaticSink::IsATOKReferringNativeCaretActive() &&
+ mComposition.isSome() &&
+ mComposition->IsOffsetInRangeOrEndOffset(acpStart) &&
+ mComposition->IsOffsetInRangeOrEndOffset(acpEnd)) {
+ CreateNativeCaret();
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetTextExt() succeeded: "
+ "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }, *pfClipped=%s",
+ this, prc->left, prc->top, prc->right, prc->bottom,
+ GetBoolName(*pfClipped)));
+
+ return S_OK;
+}
+
+bool TSFTextStore::MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd) {
+ // When ITextStoreACP::GetTextExt() returns TS_E_NOLAYOUT, TSF returns E_FAIL
+ // to its caller (typically, active TIP). Then, most TIPs abort current job
+ // or treat such application as non-GUI apps. E.g., some of them give up
+ // showing candidate window, some others show candidate window at top-left of
+ // the screen. For avoiding this issue, when there is composition (until
+ // composition is actually committed in remote content), we should not
+ // return TS_E_NOLAYOUT error for TIPs whose some features are broken by
+ // this issue.
+ // Note that ideally, this issue should be avoided by each TIP since this
+ // won't be fixed at least on non-latest Windows. Actually, Google Japanese
+ // Input (based on Mozc) does it. When GetTextExt() returns E_FAIL, TIPs
+ // should try to check result of GetRangeFromPoint() because TSF returns
+ // TS_E_NOLAYOUT correctly in this case. See:
+ // https://github.com/google/mozc/blob/6b878e31fb6ac4347dc9dfd8ccc1080fe718479f/src/win32/tip/tip_range_util.cc#L237-L257
+
+ if (!IsHandlingCompositionInContent() || mContentForTSF.isNothing() ||
+ !mContentForTSF->HasOrHadComposition() ||
+ !mContentForTSF->IsLayoutChangedAt(aACPEnd)) {
+ return false;
+ }
+
+ MOZ_ASSERT(mComposition.isNothing() ||
+ mComposition->StartOffset() ==
+ mContentForTSF->LatestCompositionRange()->StartOffset());
+ MOZ_ASSERT(mComposition.isNothing() ||
+ mComposition->EndOffset() ==
+ mContentForTSF->LatestCompositionRange()->EndOffset());
+
+ // If TSF does not have the bug, we need to hack only with a few TIPs.
+ static const bool sAlllowToStopHackingIfFine =
+ IsWindows10BuildOrLater(17643) &&
+ StaticPrefs::
+ intl_tsf_hack_allow_to_stop_hacking_on_build_17643_or_later();
+
+ // We need to compute active TIP now. This may take a couple of milliseconds,
+ // however, it'll be cached, so, must be faster than check active TIP every
+ // GetTextExt() calls.
+ const Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ switch (TSFStaticSink::ActiveTIP()) {
+ // MS IME for Japanese doesn't support asynchronous handling at deciding
+ // its suggest list window position. The feature was implemented
+ // starting from Windows 8. And also we may meet same trouble in e10s
+ // mode on Win7. So, we should never return TS_E_NOLAYOUT to MS IME for
+ // Japanese.
+ case TextInputProcessorID::eMicrosoftIMEForJapanese:
+ // Basically, MS-IME tries to retrieve whole composition string rect
+ // at deciding suggest window immediately after unlocking the document.
+ // However, in e10s mode, the content hasn't updated yet in most cases.
+ // Therefore, if the first character at the retrieving range rect is
+ // available, we should use it as the result.
+ // Note that according to bug 1609675, MS-IME for Japanese itself does
+ // not handle TS_E_NOLAYOUT correctly at least on Build 18363.657 (1909).
+ if (StaticPrefs::
+ intl_tsf_hack_ms_japanese_ime_do_not_return_no_layout_error_at_first_char() &&
+ aACPStart < aACPEnd) {
+ aACPEnd = aACPStart;
+ break;
+ }
+ if (sAlllowToStopHackingIfFine) {
+ return false;
+ }
+ // Although, the condition is not clear, MS-IME sometimes retrieves the
+ // caret rect immediately after modifying the composition string but
+ // before unlocking the document. In such case, we should return the
+ // nearest character rect.
+ // (Let's return true if there is no selection which must be not expected
+ // by MS-IME nor TSF.)
+ if (StaticPrefs::
+ intl_tsf_hack_ms_japanese_ime_do_not_return_no_layout_error_at_caret() &&
+ aACPStart == aACPEnd && selectionForTSF.isSome() &&
+ (!selectionForTSF->HasRange() ||
+ (selectionForTSF->Collapsed() &&
+ selectionForTSF->EndOffset() == aACPEnd))) {
+ int32_t minOffsetOfLayoutChanged =
+ static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
+ aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
+ } else {
+ return false;
+ }
+ break;
+ // The bug of Microsoft Office IME 2010 for Japanese is similar to
+ // MS-IME for Win 8.1 and Win 10. Newer version of MS Office IME is not
+ // released yet. So, we can hack it without prefs because there must be
+ // no developers who want to disable this hack for tests.
+ // XXX We have not tested with Microsoft Office IME 2010 since it's
+ // installable only with Win7 and Win8 (i.e., cannot install Win8.1
+ // and Win10), and requires upgrade to Win10.
+ case TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese:
+ // Basically, MS-IME tries to retrieve whole composition string rect
+ // at deciding suggest window immediately after unlocking the document.
+ // However, in e10s mode, the content hasn't updated yet in most cases.
+ // Therefore, if the first character at the retrieving range rect is
+ // available, we should use it as the result.
+ if (aACPStart < aACPEnd) {
+ aACPEnd = aACPStart;
+ }
+ // Although, the condition is not clear, MS-IME sometimes retrieves the
+ // caret rect immediately after modifying the composition string but
+ // before unlocking the document. In such case, we should return the
+ // nearest character rect.
+ // (Let's return true if there is no selection which must be not expected
+ // by MS-IME nor TSF.)
+ else if (aACPStart == aACPEnd && selectionForTSF.isSome() &&
+ (!selectionForTSF->HasRange() ||
+ (selectionForTSF->Collapsed() &&
+ selectionForTSF->EndOffset() == aACPEnd))) {
+ int32_t minOffsetOfLayoutChanged =
+ static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
+ aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
+ } else {
+ return false;
+ }
+ break;
+ // ATOK fails to handle TS_E_NOLAYOUT only when it decides the position of
+ // suggest window. In such case, ATOK tries to query rect of whole or a
+ // part of composition string.
+ // FYI: ATOK changes their implementation around candidate window and
+ // suggest widget at ATOK 2016. Therefore, there are some differences
+ // ATOK 2015 (or older) and ATOK 2016 (or newer).
+ // FYI: ATOK 2017 stops referring our window class name. I.e., ATOK 2016
+ // and older may behave differently only on Gecko but this must be
+ // finished from ATOK 2017.
+ // FYI: For testing with legacy ATOK, we should hack it even if current ATOK
+ // refers native caret rect on windows whose window class is one of
+ // Mozilla window classes and we stop creating native caret for ATOK
+ // because creating native caret causes ATOK refers caret position
+ // when GetTextExt() returns TS_E_NOLAYOUT.
+ case TextInputProcessorID::eATOK2011:
+ case TextInputProcessorID::eATOK2012:
+ case TextInputProcessorID::eATOK2013:
+ case TextInputProcessorID::eATOK2014:
+ case TextInputProcessorID::eATOK2015:
+ // ATOK 2016 and later may temporarily show candidate window at odd
+ // position when you convert a word quickly (e.g., keep pressing
+ // space bar). So, on ATOK 2016 or later, we need to keep hacking the
+ // result of GetTextExt().
+ if (sAlllowToStopHackingIfFine) {
+ return false;
+ }
+ // If we'll create native caret where we paint our caret. Then, ATOK
+ // will refer native caret. So, we don't need to hack anything in
+ // this case.
+ if (StaticPrefs::intl_tsf_hack_atok_create_native_caret()) {
+ MOZ_ASSERT(TSFStaticSink::IsATOKReferringNativeCaretActive());
+ return false;
+ }
+ [[fallthrough]];
+ case TextInputProcessorID::eATOK2016:
+ case TextInputProcessorID::eATOKUnknown:
+ if (!StaticPrefs::
+ intl_tsf_hack_atok_do_not_return_no_layout_error_of_composition_string()) {
+ return false;
+ }
+ // If the range is in the composition string, we should return rectangle
+ // in it as far as possible.
+ if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
+ aACPStart) ||
+ !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
+ aACPEnd)) {
+ return false;
+ }
+ break;
+ // Japanist 10 fails to handle TS_E_NOLAYOUT when it decides the position
+ // of candidate window. In such case, Japanist shows candidate window at
+ // top-left of the screen. So, we should return the nearest caret rect
+ // where we know. This is Japanist's bug. So, even after build 17643,
+ // we need this hack.
+ case TextInputProcessorID::eJapanist10:
+ if (!StaticPrefs::
+ intl_tsf_hack_japanist10_do_not_return_no_layout_error_of_composition_string()) {
+ return false;
+ }
+ if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
+ aACPStart) ||
+ !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
+ aACPEnd)) {
+ return false;
+ }
+ break;
+ // Free ChangJie 2010 doesn't handle ITfContextView::GetTextExt() properly.
+ // This must be caused by the bug of TSF since Free ChangJie works fine on
+ // build 17643 and later.
+ case TextInputProcessorID::eFreeChangJie:
+ if (sAlllowToStopHackingIfFine) {
+ return false;
+ }
+ if (!StaticPrefs::
+ intl_tsf_hack_free_chang_jie_do_not_return_no_layout_error()) {
+ return false;
+ }
+ aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
+ aACPStart = std::min(aACPStart, aACPEnd);
+ break;
+ // Some Traditional Chinese TIPs of Microsoft don't show candidate window
+ // in e10s mode on Win8 or later.
+ case TextInputProcessorID::eMicrosoftQuick:
+ if (sAlllowToStopHackingIfFine) {
+ return false; // MS Quick works fine with Win10 build 17643.
+ }
+ [[fallthrough]];
+ case TextInputProcessorID::eMicrosoftChangJie:
+ if (!IsWin8OrLater() ||
+ !StaticPrefs::
+ intl_tsf_hack_ms_traditional_chinese_do_not_return_no_layout_error()) {
+ return false;
+ }
+ aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
+ aACPStart = std::min(aACPStart, aACPEnd);
+ break;
+ // Some Simplified Chinese TIPs of Microsoft don't show candidate window
+ // in e10s mode on Win8 or later.
+ // FYI: Only Simplified Chinese TIPs of Microsoft still require this hack
+ // because they sometimes do not show candidate window when we return
+ // TS_E_NOLAYOUT for first query. Note that even when they show
+ // candidate window properly, we return TS_E_NOLAYOUT and following
+ // log looks same as when they don't show candidate window. Perhaps,
+ // there is stateful cause or race in them.
+ case TextInputProcessorID::eMicrosoftPinyin:
+ case TextInputProcessorID::eMicrosoftWubi:
+ if (!IsWin8OrLater() ||
+ !StaticPrefs::
+ intl_tsf_hack_ms_simplified_chinese_do_not_return_no_layout_error()) {
+ return false;
+ }
+ aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
+ aACPStart = std::min(aACPStart, aACPEnd);
+ break;
+ default:
+ return false;
+ }
+
+ // If we hack the queried range for active TIP, that means we should not
+ // return TS_E_NOLAYOUT even if hacked offset is still modified. So, as
+ // far as possible, we should adjust the offset.
+ MOZ_ASSERT(mContentForTSF->IsLayoutChanged());
+ bool collapsed = aACPStart == aACPEnd;
+ // Note that even if all characters in the editor or the composition
+ // string was modified, 0 or start offset of the composition string is
+ // useful because it may return caret rect or old character's rect which
+ // the user still see. That must be useful information for TIP.
+ int32_t firstModifiedOffset =
+ static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
+ LONG lastUnmodifiedOffset = std::max(firstModifiedOffset - 1, 0);
+ if (mContentForTSF->IsLayoutChangedAt(aACPStart)) {
+ if (aACPStart >= mContentForTSF->LatestCompositionRange()->StartOffset()) {
+ // If mContentForTSF has last composition string and current
+ // composition string, we can assume that ContentCacheInParent has
+ // cached rects of composition string at least length of current
+ // composition string. Otherwise, we can assume that rect for
+ // first character of composition string is stored since it was
+ // selection start or caret position.
+ LONG maxCachedOffset =
+ mContentForTSF->LatestCompositionRange()->EndOffset();
+ if (mContentForTSF->LastComposition().isSome()) {
+ maxCachedOffset = std::min(
+ maxCachedOffset, mContentForTSF->LastComposition()->EndOffset());
+ }
+ aACPStart = std::min(aACPStart, maxCachedOffset);
+ }
+ // Otherwise, we don't know which character rects are cached. So, we
+ // need to use first unmodified character's rect in this case. Even
+ // if there is no character, the query event will return caret rect
+ // instead.
+ else {
+ aACPStart = lastUnmodifiedOffset;
+ }
+ MOZ_ASSERT(aACPStart <= aACPEnd);
+ }
+
+ // If TIP requests caret rect with collapsed range, we should keep
+ // collapsing the range.
+ if (collapsed) {
+ aACPEnd = aACPStart;
+ }
+ // Let's set aACPEnd to larger offset of last unmodified offset or
+ // aACPStart which may be the first character offset of the composition
+ // string. However, some TIPs may want to know the right edge of the
+ // range. Therefore, if aACPEnd is in composition string and active TIP
+ // doesn't retrieve caret rect (i.e., the range isn't collapsed), we
+ // should keep using the original aACPEnd. Otherwise, we should set
+ // aACPEnd to larger value of aACPStart and lastUnmodifiedOffset.
+ else if (mContentForTSF->IsLayoutChangedAt(aACPEnd) &&
+ !mContentForTSF->LatestCompositionRange()
+ ->IsOffsetInRangeOrEndOffset(aACPEnd)) {
+ aACPEnd = std::max(aACPStart, lastUnmodifiedOffset);
+ }
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::HackNoErrorLayoutBugs() hacked the queried range "
+ "for not returning TS_E_NOLAYOUT, new values are: "
+ "aACPStart=%ld, aACPEnd=%ld",
+ this, aACPStart, aACPEnd));
+
+ return true;
+}
+
+STDMETHODIMP
+TSFTextStore::GetScreenExt(TsViewCookie vcView, RECT* prc) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetScreenExt(vcView=%ld, prc=0x%p)", this,
+ vcView, prc));
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "called with invalid view",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!prc) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() returns empty rect "
+ "due to already destroyed",
+ this));
+ prc->left = prc->top = prc->right = prc->bottom = 0;
+ return S_OK;
+ }
+
+ if (!GetScreenExtInternal(*prc)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "GetScreenExtInternal() failure",
+ this));
+ return E_FAIL;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetScreenExt() succeeded: "
+ "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
+ this, prc->left, prc->top, prc->right, prc->bottom));
+ return S_OK;
+}
+
+bool TSFTextStore::GetScreenExtInternal(RECT& aScreenExt) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetScreenExtInternal()", this));
+
+ MOZ_ASSERT(!mDestroyed);
+
+ // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
+ WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, mWidget);
+ mWidget->InitEvent(queryEditorRectEvent);
+ DispatchEvent(queryEditorRectEvent);
+ if (queryEditorRectEvent.Failed()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
+ "eQueryEditorRect failure",
+ this));
+ return false;
+ }
+
+ nsWindow* refWindow =
+ static_cast<nsWindow*>(!!queryEditorRectEvent.mReply->mFocusedWidget
+ ? queryEditorRectEvent.mReply->mFocusedWidget
+ : static_cast<nsIWidget*>(mWidget.get()));
+ // Result rect is in top level widget coordinates
+ refWindow = refWindow->GetTopLevelWindow(false);
+ if (!refWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
+ "no top level window",
+ this));
+ return false;
+ }
+
+ LayoutDeviceIntRect boundRect = refWindow->GetClientBounds();
+ boundRect.MoveTo(0, 0);
+
+ // Clip frame rect to window rect
+ boundRect.IntersectRect(queryEditorRectEvent.mReply->mRect, boundRect);
+ if (!boundRect.IsEmpty()) {
+ boundRect.MoveBy(refWindow->WidgetToScreenOffset());
+ ::SetRect(&aScreenExt, boundRect.X(), boundRect.Y(), boundRect.XMost(),
+ boundRect.YMost());
+ } else {
+ ::SetRectEmpty(&aScreenExt);
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetScreenExtInternal() succeeded: "
+ "aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
+ this, aScreenExt.left, aScreenExt.top, aScreenExt.right,
+ aScreenExt.bottom));
+ return true;
+}
+
+STDMETHODIMP
+TSFTextStore::GetWnd(TsViewCookie vcView, HWND* phwnd) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetWnd(vcView=%ld, phwnd=0x%p), "
+ "mWidget=0x%p",
+ this, vcView, phwnd, mWidget.get()));
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetWnd() FAILED due to "
+ "called with invalid view",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!phwnd) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ *phwnd = mWidget ? mWidget->GetWindowHandle() : nullptr;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetWnd() succeeded: *phwnd=0x%p", this,
+ static_cast<void*>(*phwnd)));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::InsertTextAtSelection(DWORD dwFlags, const WCHAR* pchText,
+ ULONG cch, LONG* pacpStart, LONG* pacpEnd,
+ TS_TEXTCHANGE* pChange) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertTextAtSelection(dwFlags=%s, "
+ "pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, "
+ "pChange=0x%p), mComposition=%s",
+ this,
+ dwFlags == 0 ? "0"
+ : dwFlags == TF_IAS_NOQUERY ? "TF_IAS_NOQUERY"
+ : dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY"
+ : "Unknown",
+ pchText, pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "",
+ cch, pacpStart, pacpEnd, pChange, ToString(mComposition).c_str()));
+
+ if (cch && !pchText) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null pchText",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (TS_IAS_QUERYONLY == dwFlags) {
+ if (!IsReadLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pacpStart || !pacpEnd) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ // Get selection first
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ // Simulate text insertion
+ if (selectionForTSF->HasRange()) {
+ *pacpStart = selectionForTSF->StartOffset();
+ *pacpEnd = selectionForTSF->EndOffset();
+ if (pChange) {
+ *pChange = TS_TEXTCHANGE{.acpStart = selectionForTSF->StartOffset(),
+ .acpOldEnd = selectionForTSF->EndOffset(),
+ .acpNewEnd = selectionForTSF->StartOffset() +
+ static_cast<LONG>(cch)};
+ }
+ } else {
+ // There is no error code to return "no selection" state from this method.
+ // This means that TSF/TIP should check `GetSelection` result first and
+ // stop using this. However, this could be called by TIP/TSF if they do
+ // not do so. Therefore, we should use start of editor instead, but
+ // notify the caller of nothing will be inserted with pChange->acpNewEnd.
+ *pacpStart = *pacpEnd = 0;
+ if (pChange) {
+ *pChange = TS_TEXTCHANGE{.acpStart = 0, .acpOldEnd = 0, .acpNewEnd = 0};
+ }
+ }
+ } else {
+ if (!IsReadWriteLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "not locked (read-write)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pChange) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null pChange",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
+ pChange)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "InsertTextAtSelectionInternal() failure",
+ this));
+ return E_FAIL;
+ }
+
+ if (TS_IAS_NOQUERY != dwFlags) {
+ *pacpStart = pChange->acpStart;
+ *pacpEnd = pChange->acpNewEnd;
+ }
+ }
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertTextAtSelection() succeeded: "
+ "*pacpStart=%ld, *pacpEnd=%ld, "
+ "*pChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld })",
+ this, pacpStart ? *pacpStart : 0, pacpEnd ? *pacpEnd : 0,
+ pChange ? pChange->acpStart : 0, pChange ? pChange->acpOldEnd : 0,
+ pChange ? pChange->acpNewEnd : 0));
+ return S_OK;
+}
+
+bool TSFTextStore::InsertTextAtSelectionInternal(const nsAString& aInsertStr,
+ TS_TEXTCHANGE* aTextChange) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal("
+ "aInsertStr=\"%s\", aTextChange=0x%p), mComposition=%s",
+ this, GetEscapedUTF8String(aInsertStr).get(), aTextChange,
+ ToString(mComposition).c_str()));
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() failed "
+ "due to ContentForTSF() failure()",
+ this));
+ return false;
+ }
+
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return false;
+ }
+
+ TS_SELECTION_ACP oldSelection = contentForTSF->Selection()->ACPRef();
+ if (mComposition.isNothing()) {
+ // Use a temporary composition to contain the text
+ PendingAction* compositionStart = mPendingActions.AppendElements(2);
+ PendingAction* compositionEnd = compositionStart + 1;
+
+ compositionStart->mType = PendingAction::Type::eCompositionStart;
+ compositionStart->mSelectionStart = oldSelection.acpStart;
+ compositionStart->mSelectionLength =
+ oldSelection.acpEnd - oldSelection.acpStart;
+ compositionStart->mAdjustSelection = false;
+
+ compositionEnd->mType = PendingAction::Type::eCompositionEnd;
+ compositionEnd->mData = aInsertStr;
+ compositionEnd->mSelectionStart = compositionStart->mSelectionStart;
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
+ "appending pending compositionstart and compositionend... "
+ "PendingCompositionStart={ mSelectionStart=%ld, "
+ "mSelectionLength=%ld }, PendingCompositionEnd={ mData=\"%s\" "
+ "(Length()=%zu), mSelectionStart=%ld }",
+ this, compositionStart->mSelectionStart,
+ compositionStart->mSelectionLength,
+ GetEscapedUTF8String(compositionEnd->mData).get(),
+ compositionEnd->mData.Length(), compositionEnd->mSelectionStart));
+ }
+
+ contentForTSF->ReplaceSelectedTextWith(aInsertStr);
+
+ if (aTextChange) {
+ aTextChange->acpStart = oldSelection.acpStart;
+ aTextChange->acpOldEnd = oldSelection.acpEnd;
+ aTextChange->acpNewEnd = contentForTSF->Selection()->EndOffset();
+ }
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
+ "succeeded: mWidget=0x%p, mWidget->Destroyed()=%s, aTextChange={ "
+ "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
+ this, mWidget.get(), GetBoolName(mWidget ? mWidget->Destroyed() : true),
+ aTextChange ? aTextChange->acpStart : 0,
+ aTextChange ? aTextChange->acpOldEnd : 0,
+ aTextChange ? aTextChange->acpNewEnd : 0));
+ return true;
+}
+
+STDMETHODIMP
+TSFTextStore::InsertEmbeddedAtSelection(DWORD dwFlags, IDataObject* pDataObject,
+ LONG* pacpStart, LONG* pacpEnd,
+ TS_TEXTCHANGE* pChange) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertEmbeddedAtSelection() called "
+ "but not supported (E_NOTIMPL)",
+ this));
+
+ // embedded objects are not supported
+ return E_NOTIMPL;
+}
+
+HRESULT TSFTextStore::RecordCompositionStartAction(
+ ITfCompositionView* aCompositionView, ITfRange* aRange,
+ bool aPreserveSelection) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionStartAction("
+ "aCompositionView=0x%p, aRange=0x%p, aPreserveSelection=%s), "
+ "mComposition=%s",
+ this, aCompositionView, aRange, GetBoolName(aPreserveSelection),
+ ToString(mComposition).c_str()));
+
+ LONG start = 0, length = 0;
+ HRESULT hr = GetRangeExtent(aRange, &start, &length);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to GetRangeExtent() failure",
+ this));
+ return hr;
+ }
+
+ return RecordCompositionStartAction(aCompositionView, start, length,
+ aPreserveSelection);
+}
+
+HRESULT TSFTextStore::RecordCompositionStartAction(
+ ITfCompositionView* aCompositionView, LONG aStart, LONG aLength,
+ bool aPreserveSelection) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionStartAction("
+ "aCompositionView=0x%p, aStart=%ld, aLength=%ld, "
+ "aPreserveSelection=%s), "
+ "mComposition=%s",
+ this, aCompositionView, aStart, aLength,
+ GetBoolName(aPreserveSelection), ToString(mComposition).c_str()));
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return false;
+ }
+
+ CompleteLastActionIfStillIncomplete();
+
+ // TIP may have inserted text at selection before calling
+ // OnStartComposition(). In this case, we've already created a pending
+ // compositionend. If new composition replaces all commit string of the
+ // pending compositionend, we should cancel the pending compositionend and
+ // keep the previous composition normally.
+ // On Windows 7, MS-IME for Korean, MS-IME 2010 for Korean and MS Old Hangul
+ // may start composition with calling InsertTextAtSelection() and
+ // OnStartComposition() with this order (bug 1208043).
+ // On Windows 10, MS Pinyin, MS Wubi, MS ChangJie and MS Quick commits
+ // last character and replace it with empty string with new composition
+ // when user removes last character of composition string with Backspace
+ // key (bug 1462257).
+ if (!aPreserveSelection &&
+ IsLastPendingActionCompositionEndAt(aStart, aLength)) {
+ const PendingAction& pendingCompositionEnd = mPendingActions.LastElement();
+ contentForTSF->RestoreCommittedComposition(aCompositionView,
+ pendingCompositionEnd);
+ mPendingActions.RemoveLastElement();
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() "
+ "succeeded: restoring the committed string as composing string, "
+ "mComposition=%s, mSelectionForTSF=%s",
+ this, ToString(mComposition).c_str(),
+ ToString(mSelectionForTSF).c_str()));
+ return S_OK;
+ }
+
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::Type::eCompositionStart;
+ action->mSelectionStart = aStart;
+ action->mSelectionLength = aLength;
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to SelectionForTSF() failure",
+ this));
+ action->mAdjustSelection = true;
+ } else if (!selectionForTSF->HasRange()) {
+ // If there is no selection, let's collapse seletion to the insertion point.
+ action->mAdjustSelection = true;
+ } else if (selectionForTSF->MinOffset() != aStart ||
+ selectionForTSF->MaxOffset() != aStart + aLength) {
+ // If new composition range is different from current selection range,
+ // we need to set selection before dispatching compositionstart event.
+ action->mAdjustSelection = true;
+ } else {
+ // We shouldn't dispatch selection set event before dispatching
+ // compositionstart event because it may cause put caret different
+ // position in HTML editor since generated flat text content and offset in
+ // it are lossy data of HTML contents.
+ action->mAdjustSelection = false;
+ }
+
+ contentForTSF->StartComposition(aCompositionView, *action,
+ aPreserveSelection);
+ MOZ_ASSERT(mComposition.isSome());
+ action->mData = mComposition->DataRef();
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() succeeded: "
+ "mComposition=%s, mSelectionForTSF=%s }",
+ this, ToString(mComposition).c_str(),
+ ToString(mSelectionForTSF).c_str()));
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::RecordCompositionEndAction() {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionEndAction(), "
+ "mComposition=%s",
+ this, ToString(mComposition).c_str()));
+
+ MOZ_ASSERT(mComposition.isSome());
+
+ if (mComposition.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
+ "no composition",
+ this));
+ return false;
+ }
+
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return false;
+ }
+
+ // If we're handling incomplete composition update or already handled
+ // composition update, we can forget them since composition end will send
+ // the latest composition string and it overwrites the composition string
+ // even if we dispatch eCompositionChange event before that. So, let's
+ // forget all composition updates now.
+ RemoveLastCompositionUpdateActions();
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::Type::eCompositionEnd;
+ action->mData = mComposition->DataRef();
+ action->mSelectionStart = mComposition->StartOffset();
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due "
+ "to ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ contentForTSF->EndComposition(*action);
+
+ // If this composition was restart but the composition doesn't modify
+ // anything, we should remove the pending composition for preventing to
+ // dispatch redundant composition events.
+ for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) {
+ PendingAction& pendingAction = mPendingActions[i - 1];
+ if (pendingAction.mType == PendingAction::Type::eCompositionStart) {
+ if (pendingAction.mData != action->mData) {
+ break;
+ }
+ // When only setting selection is necessary, we should append it.
+ if (pendingAction.mAdjustSelection) {
+ LONG selectionStart = pendingAction.mSelectionStart;
+ LONG selectionLength = pendingAction.mSelectionLength;
+
+ PendingAction* setSelection = mPendingActions.AppendElement();
+ setSelection->mType = PendingAction::Type::eSetSelection;
+ setSelection->mSelectionStart = selectionStart;
+ setSelection->mSelectionLength = selectionLength;
+ setSelection->mSelectionReversed = false;
+ }
+ // Remove the redundant pending composition.
+ mPendingActions.RemoveElementsAt(i - 1, j);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionEndAction(), "
+ "succeeded, but the composition was canceled due to redundant",
+ this));
+ return S_OK;
+ }
+ }
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionEndAction(), succeeded", this));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnStartComposition(pComposition=0x%p, "
+ "pfOk=0x%p), mComposition=%s",
+ this, pComposition, pfOk, ToString(mComposition).c_str()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ *pfOk = FALSE;
+
+ // Only one composition at a time
+ if (mComposition.isSome()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
+ "there is another composition already (but returns S_OK)",
+ this));
+ return S_OK;
+ }
+
+ RefPtr<ITfRange> range;
+ HRESULT hr = pComposition->GetRange(getter_AddRefs(range));
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
+ "pComposition->GetRange() failure",
+ this));
+ return hr;
+ }
+ hr = RecordCompositionStartAction(pComposition, range, false);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
+ "RecordCompositionStartAction() failure",
+ this));
+ return hr;
+ }
+
+ *pfOk = TRUE;
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnStartComposition() succeeded", this));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::OnUpdateComposition(ITfCompositionView* pComposition,
+ ITfRange* pRangeNew) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnUpdateComposition(pComposition=0x%p, "
+ "pRangeNew=0x%p), mComposition=%s",
+ this, pComposition, pRangeNew, ToString(mComposition).c_str()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ if (!mDocumentMgr || !mContext) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "not ready for the composition",
+ this));
+ return E_UNEXPECTED;
+ }
+ if (mComposition.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "no active composition",
+ this));
+ return E_UNEXPECTED;
+ }
+ if (mComposition->GetView() != pComposition) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "different composition view specified",
+ this));
+ return E_UNEXPECTED;
+ }
+
+ // pRangeNew is null when the update is not complete
+ if (!pRangeNew) {
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return E_FAIL;
+ }
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ action->mIncomplete = true;
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnUpdateComposition() succeeded but "
+ "not complete",
+ this));
+ return S_OK;
+ }
+
+ HRESULT hr = RestartCompositionIfNecessary(pRangeNew);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "RestartCompositionIfNecessary() failure",
+ this));
+ return hr;
+ }
+
+ hr = RecordCompositionUpdateAction();
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "RecordCompositionUpdateAction() failure",
+ this));
+ return hr;
+ }
+
+ if (MOZ_LOG_TEST(gIMELog, LogLevel::Info)) {
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return S_OK; // Don't return error only when we're logging.
+ }
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnUpdateComposition() succeeded: "
+ "mComposition=%s, SelectionForTSF()=%s",
+ this, ToString(mComposition).c_str(),
+ ToString(selectionForTSF).c_str()));
+ }
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::OnEndComposition(ITfCompositionView* pComposition) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnEndComposition(pComposition=0x%p), "
+ "mComposition=%s",
+ this, pComposition, ToString(mComposition).c_str()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ if (mComposition.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
+ "no active composition",
+ this));
+ return E_UNEXPECTED;
+ }
+
+ if (mComposition->GetView() != pComposition) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
+ "different composition view specified",
+ this));
+ return E_UNEXPECTED;
+ }
+
+ HRESULT hr = RecordCompositionEndAction();
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
+ "RecordCompositionEndAction() failure",
+ this));
+ return hr;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnEndComposition(), succeeded", this));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::AdviseMouseSink(ITfRangeACP* range, ITfMouseSink* pSink,
+ DWORD* pdwCookie) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::AdviseMouseSink(range=0x%p, pSink=0x%p, "
+ "pdwCookie=0x%p)",
+ this, range, pSink, pdwCookie));
+
+ if (!pdwCookie) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
+ "pdwCookie is null",
+ this));
+ return E_INVALIDARG;
+ }
+ // Initialize the result with invalid cookie for safety.
+ *pdwCookie = MouseTracker::kInvalidCookie;
+
+ if (!range) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
+ "range is null",
+ this));
+ return E_INVALIDARG;
+ }
+ if (!pSink) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
+ "pSink is null",
+ this));
+ return E_INVALIDARG;
+ }
+
+ // Looking for an unusing tracker.
+ MouseTracker* tracker = nullptr;
+ for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
+ if (mMouseTrackers[i].IsUsing()) {
+ continue;
+ }
+ tracker = &mMouseTrackers[i];
+ }
+ // If there is no unusing tracker, create new one.
+ // XXX Should we make limitation of the number of installs?
+ if (!tracker) {
+ tracker = mMouseTrackers.AppendElement();
+ HRESULT hr = tracker->Init(this);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to "
+ "failure of MouseTracker::Init()",
+ this));
+ return hr;
+ }
+ }
+ HRESULT hr = tracker->AdviseSink(this, range, pSink);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to failure "
+ "of MouseTracker::Init()",
+ this));
+ return hr;
+ }
+ *pdwCookie = tracker->Cookie();
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::AdviseMouseSink(), succeeded, "
+ "*pdwCookie=%ld",
+ this, *pdwCookie));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::UnadviseMouseSink(DWORD dwCookie) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::UnadviseMouseSink(dwCookie=%ld)", this, dwCookie));
+ if (dwCookie == MouseTracker::kInvalidCookie) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
+ "the cookie is invalid value",
+ this));
+ return E_INVALIDARG;
+ }
+ // The cookie value must be an index of mMouseTrackers.
+ // We can use this shortcut for now.
+ if (static_cast<size_t>(dwCookie) >= mMouseTrackers.Length()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
+ "the cookie is too large value",
+ this));
+ return E_INVALIDARG;
+ }
+ MouseTracker& tracker = mMouseTrackers[dwCookie];
+ if (!tracker.IsUsing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
+ "the found tracker uninstalled already",
+ this));
+ return E_INVALIDARG;
+ }
+ tracker.UnadviseSink();
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::UnadviseMouseSink(), succeeded", this));
+ return S_OK;
+}
+
+// static
+nsresult TSFTextStore::OnFocusChange(bool aGotFocus, nsWindow* aFocusedWidget,
+ const InputContext& aContext) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ (" TSFTextStore::OnFocusChange(aGotFocus=%s, "
+ "aFocusedWidget=0x%p, aContext=%s), "
+ "sThreadMgr=0x%p, sEnabledTextStore=0x%p",
+ GetBoolName(aGotFocus), aFocusedWidget,
+ mozilla::ToString(aContext).c_str(), sThreadMgr.get(),
+ sEnabledTextStore.get()));
+
+ if (NS_WARN_IF(!IsInTSFMode())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
+ bool hasFocus = ThinksHavingFocus();
+ RefPtr<TSFTextStore> oldTextStore = sEnabledTextStore.forget();
+
+ // If currently oldTextStore still has focus, notifies TSF of losing focus.
+ if (hasFocus) {
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ DebugOnly<HRESULT> hr = threadMgr->AssociateFocus(
+ oldTextStore->mWidget->GetWindowHandle(), nullptr,
+ getter_AddRefs(prevFocusedDocumentMgr));
+ NS_ASSERTION(SUCCEEDED(hr), "Disassociating focus failed");
+ NS_ASSERTION(prevFocusedDocumentMgr == oldTextStore->mDocumentMgr,
+ "different documentMgr has been associated with the window");
+ }
+
+ // Even if there was a focused TextStore, we won't use it with new focused
+ // editor. So, release it now.
+ if (oldTextStore) {
+ oldTextStore->Destroy();
+ }
+
+ if (NS_WARN_IF(!sThreadMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::OnFocusChange() FAILED, due to "
+ "sThreadMgr being destroyed during calling "
+ "ITfThreadMgr::AssociateFocus()"));
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_WARN_IF(sEnabledTextStore)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ (" TSFTextStore::OnFocusChange() FAILED, due to "
+ "nested event handling has created another focused TextStore during "
+ "calling ITfThreadMgr::AssociateFocus()"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this is a notification of blur, move focus to the dummy document
+ // manager.
+ if (!aGotFocus || !aContext.mIMEState.IsEditable()) {
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ RefPtr<ITfDocumentMgr> disabledDocumentMgr = sDisabledDocumentMgr;
+ HRESULT hr = threadMgr->SetFocus(disabledDocumentMgr);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::OnFocusChange() FAILED due to "
+ "ITfThreadMgr::SetFocus() failure"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+ }
+
+ // If an editor is getting focus, create new TextStore and set focus.
+ if (NS_WARN_IF(!CreateAndSetFocus(aFocusedWidget, aContext))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::OnFocusChange() FAILED due to "
+ "ITfThreadMgr::CreateAndSetFocus() failure"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+// static
+void TSFTextStore::EnsureToDestroyAndReleaseEnabledTextStoreIf(
+ RefPtr<TSFTextStore>& aTextStore) {
+ aTextStore->Destroy();
+ if (sEnabledTextStore == aTextStore) {
+ sEnabledTextStore = nullptr;
+ }
+ aTextStore = nullptr;
+}
+
+// static
+bool TSFTextStore::CreateAndSetFocus(nsWindow* aFocusedWidget,
+ const InputContext& aContext) {
+ // TSF might do something which causes that we need to access static methods
+ // of TSFTextStore. At that time, sEnabledTextStore may be necessary.
+ // So, we should set sEnabledTextStore directly.
+ RefPtr<TSFTextStore> textStore = new TSFTextStore();
+ sEnabledTextStore = textStore;
+ if (NS_WARN_IF(!textStore->Init(aFocusedWidget, aContext))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "TSFTextStore::Init() failure"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ RefPtr<ITfDocumentMgr> newDocMgr = textStore->mDocumentMgr;
+ if (NS_WARN_IF(!newDocMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "invalid TSFTextStore::mDocumentMgr"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (aContext.mIMEState.mEnabled == IMEEnabled::Password) {
+ MarkContextAsKeyboardDisabled(textStore->mContext);
+ RefPtr<ITfContext> topContext;
+ newDocMgr->GetTop(getter_AddRefs(topContext));
+ if (topContext && topContext != textStore->mContext) {
+ MarkContextAsKeyboardDisabled(topContext);
+ }
+ }
+
+ HRESULT hr;
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ hr = threadMgr->SetFocus(newDocMgr);
+
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "ITfTheadMgr::SetFocus() failure"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(!sThreadMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "sThreadMgr being destroyed during calling "
+ "ITfTheadMgr::SetFocus()"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(sEnabledTextStore != textStore)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "creating TextStore has lost focus during calling "
+ "ITfThreadMgr::SetFocus()"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+
+ // Use AssociateFocus() for ensuring that any native focus event
+ // never steal focus from our documentMgr.
+ RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
+ hr = threadMgr->AssociateFocus(aFocusedWidget->GetWindowHandle(), newDocMgr,
+ getter_AddRefs(prevFocusedDocumentMgr));
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "ITfTheadMgr::AssociateFocus() failure"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(!sThreadMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "sThreadMgr being destroyed during calling "
+ "ITfTheadMgr::AssociateFocus()"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(sEnabledTextStore != textStore)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "creating TextStore has lost focus during calling "
+ "ITfTheadMgr::AssociateFocus()"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+
+ if (textStore->mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" TSFTextStore::CreateAndSetFocus(), calling "
+ "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE) for 0x%p...",
+ textStore.get()));
+ RefPtr<ITextStoreACPSink> sink = textStore->mSink;
+ sink->OnLayoutChange(TS_LC_CREATE, TEXTSTORE_DEFAULT_VIEW);
+ if (NS_WARN_IF(sEnabledTextStore != textStore)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "creating TextStore has lost focus during calling "
+ "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE)"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ }
+ return true;
+}
+
+// static
+IMENotificationRequests TSFTextStore::GetIMENotificationRequests() {
+ if (!sEnabledTextStore || NS_WARN_IF(!sEnabledTextStore->mDocumentMgr)) {
+ // If there is no active text store, we don't need any notifications
+ // since there is no sink which needs notifications.
+ return IMENotificationRequests();
+ }
+
+ // Otherwise, requests all notifications since even if some of them may not
+ // be required by the sink of active TIP, active TIP may be changed and
+ // other TIPs may need all notifications.
+ // Note that Windows temporarily steal focus from active window if the main
+ // process which created the window becomes busy. In this case, we shouldn't
+ // commit composition since user may want to continue to compose the
+ // composition after becoming not busy. Therefore, we need notifications
+ // even during deactive.
+ // Be aware, we don't need to check actual focused text store. For example,
+ // MS-IME for Japanese handles focus messages by themselves and sets focused
+ // text store to nullptr when the process is being inactivated. However,
+ // we still need to reuse sEnabledTextStore if the process is activated and
+ // focused element isn't changed. Therefore, if sEnabledTextStore isn't
+ // nullptr, we need to keep notifying the sink even when it is not focused
+ // text store for the thread manager.
+ return IMENotificationRequests(
+ IMENotificationRequests::NOTIFY_TEXT_CHANGE |
+ IMENotificationRequests::NOTIFY_POSITION_CHANGE |
+ IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR |
+ IMENotificationRequests::NOTIFY_DURING_DEACTIVE);
+}
+
+nsresult TSFTextStore::OnTextChangeInternal(
+ const IMENotification& aIMENotification) {
+ const TextChangeDataBase& textChangeData = aIMENotification.mTextChangeData;
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnTextChangeInternal(aIMENotification={ "
+ "mMessage=0x%08X, mTextChangeData=%s }), "
+ "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, "
+ "mComposition=%s",
+ this, aIMENotification.mMessage,
+ mozilla::ToString(textChangeData).c_str(), GetBoolName(mDestroyed),
+ mSink.get(), GetSinkMaskNameStr(mSinkMask).get(),
+ ToString(mComposition).c_str()));
+
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // changes.
+ return NS_OK;
+ }
+
+ mDeferNotifyingTSFUntilNextUpdate = false;
+
+ // Different from selection change, we don't modify anything with text
+ // change data. Therefore, if neither TSF not TIP wants text change
+ // notifications, we don't need to store the changes.
+ if (!mSink || !(mSinkMask & TS_AS_TEXT_CHANGE)) {
+ return NS_OK;
+ }
+
+ // Merge any text change data even if it's caused by composition.
+ mPendingTextChangeData.MergeWith(textChangeData);
+
+ MaybeFlushPendingNotifications();
+
+ return NS_OK;
+}
+
+void TSFTextStore::NotifyTSFOfTextChange() {
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(!IsReadLocked());
+ MOZ_ASSERT(mComposition.isNothing());
+ MOZ_ASSERT(mPendingTextChangeData.IsValid());
+
+ // If the text changes are caused only by composition, we don't need to
+ // notify TSF of the text changes.
+ if (mPendingTextChangeData.mCausedOnlyByComposition) {
+ mPendingTextChangeData.Clear();
+ return;
+ }
+
+ // First, forget cached selection.
+ mSelectionForTSF.reset();
+
+ // For making it safer, we should check if there is a valid sink to receive
+ // text change notification.
+ if (NS_WARN_IF(!mSink) || NS_WARN_IF(!(mSinkMask & TS_AS_TEXT_CHANGE))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
+ "mSink is not ready to call ITextStoreACPSink::OnTextChange()...",
+ this));
+ mPendingTextChangeData.Clear();
+ return;
+ }
+
+ if (NS_WARN_IF(!mPendingTextChangeData.IsInInt32Range())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
+ "offset is too big for calling "
+ "ITextStoreACPSink::OnTextChange()...",
+ this));
+ mPendingTextChangeData.Clear();
+ return;
+ }
+
+ TS_TEXTCHANGE textChange;
+ textChange.acpStart = static_cast<LONG>(mPendingTextChangeData.mStartOffset);
+ textChange.acpOldEnd =
+ static_cast<LONG>(mPendingTextChangeData.mRemovedEndOffset);
+ textChange.acpNewEnd =
+ static_cast<LONG>(mPendingTextChangeData.mAddedEndOffset);
+ mPendingTextChangeData.Clear();
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfTextChange(), calling "
+ "ITextStoreACPSink::OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
+ "acpNewEnd=%ld })...",
+ this, textChange.acpStart, textChange.acpOldEnd, textChange.acpNewEnd));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ sink->OnTextChange(0, &textChange);
+}
+
+nsresult TSFTextStore::OnSelectionChangeInternal(
+ const IMENotification& aIMENotification) {
+ const SelectionChangeDataBase& selectionChangeData =
+ aIMENotification.mSelectionChangeData;
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnSelectionChangeInternal("
+ "aIMENotification={ mSelectionChangeData=%s }), mDestroyed=%s, "
+ "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, "
+ "mComposition=%s",
+ this, mozilla::ToString(selectionChangeData).c_str(),
+ GetBoolName(mDestroyed), mSink.get(),
+ GetSinkMaskNameStr(mSinkMask).get(),
+ GetBoolName(mIsRecordingActionsWithoutLock),
+ ToString(mComposition).c_str()));
+
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // changes.
+ return NS_OK;
+ }
+
+ mDeferNotifyingTSFUntilNextUpdate = false;
+
+ // Assign the new selection change data to the pending selection change data
+ // because only the latest selection data is necessary.
+ // Note that this is necessary to update mSelectionForTSF. Therefore, even if
+ // neither TSF nor TIP wants selection change notifications, we need to
+ // store the selection information.
+ mPendingSelectionChangeData = Some(selectionChangeData);
+
+ // Flush remaining pending notifications here if it's possible.
+ MaybeFlushPendingNotifications();
+
+ // If we're available, we should create native caret instead of IMEHandler
+ // because we may have some cache to do it.
+ // Note that if we have composition, we'll notified composition-updated
+ // later so that we don't need to create native caret in such case.
+ if (!IsHandlingCompositionInContent() &&
+ IMEHandler::NeedsToCreateNativeCaret()) {
+ CreateNativeCaret();
+ }
+
+ return NS_OK;
+}
+
+void TSFTextStore::NotifyTSFOfSelectionChange() {
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(!IsReadLocked());
+ MOZ_ASSERT(mComposition.isNothing());
+ MOZ_ASSERT(mPendingSelectionChangeData.isSome());
+
+ // If selection range isn't actually changed, we don't need to notify TSF
+ // of this selection change.
+ if (mSelectionForTSF.isNothing()) {
+ mSelectionForTSF.emplace(*mPendingSelectionChangeData);
+ } else if (!mSelectionForTSF->SetSelection(*mPendingSelectionChangeData)) {
+ mPendingSelectionChangeData.reset();
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), "
+ "selection isn't actually changed.",
+ this));
+ return;
+ }
+
+ mPendingSelectionChangeData.reset();
+
+ if (!mSink || !(mSinkMask & TS_AS_SEL_CHANGE)) {
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), calling "
+ "ITextStoreACPSink::OnSelectionChange()...",
+ this));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ sink->OnSelectionChange();
+}
+
+nsresult TSFTextStore::OnLayoutChangeInternal() {
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // changes.
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(mSink, NS_ERROR_FAILURE);
+
+ mDeferNotifyingTSFUntilNextUpdate = false;
+
+ nsresult rv = NS_OK;
+
+ // We need to notify TSF of layout change even if the document is locked.
+ // So, don't use MaybeFlushPendingNotifications() for flushing pending
+ // layout change.
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
+ "NotifyTSFOfLayoutChange()...",
+ this));
+ if (NS_WARN_IF(!NotifyTSFOfLayoutChange())) {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
+ "MaybeFlushPendingNotifications()...",
+ this));
+ MaybeFlushPendingNotifications();
+
+ return rv;
+}
+
+bool TSFTextStore::NotifyTSFOfLayoutChange() {
+ MOZ_ASSERT(!mDestroyed);
+
+ // If we're waiting a query of layout information from TIP, it means that
+ // we've returned TS_E_NOLAYOUT error.
+ bool returnedNoLayoutError = mHasReturnedNoLayoutError || mWaitingQueryLayout;
+
+ // If we returned TS_E_NOLAYOUT, TIP should query the computed layout again.
+ mWaitingQueryLayout = returnedNoLayoutError;
+
+ // For avoiding to call this method again at unlocking the document during
+ // calls of OnLayoutChange(), reset mHasReturnedNoLayoutError.
+ mHasReturnedNoLayoutError = false;
+
+ // Now, layout has been computed. We should notify mContentForTSF for
+ // making GetTextExt() and GetACPFromPoint() not return TS_E_NOLAYOUT.
+ if (mContentForTSF.isSome()) {
+ mContentForTSF->OnLayoutChanged();
+ }
+
+ if (IMEHandler::NeedsToCreateNativeCaret()) {
+ // If we're available, we should create native caret instead of IMEHandler
+ // because we may have some cache to do it.
+ CreateNativeCaret();
+ } else {
+ // Now, the caret position is different from ours. Destroy the native caret
+ // if we've create it only for GetTextExt().
+ IMEHandler::MaybeDestroyNativeCaret();
+ }
+
+ // This method should return true if either way succeeds.
+ bool ret = true;
+
+ if (mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "calling ITextStoreACPSink::OnLayoutChange()...",
+ this));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ HRESULT hr = sink->OnLayoutChange(TS_LC_CHANGE, TEXTSTORE_DEFAULT_VIEW);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "called ITextStoreACPSink::OnLayoutChange()",
+ this));
+ ret = SUCCEEDED(hr);
+ }
+
+ // The layout change caused by composition string change should cause
+ // calling ITfContextOwnerServices::OnLayoutChange() too.
+ if (returnedNoLayoutError && mContext) {
+ RefPtr<ITfContextOwnerServices> service;
+ mContext->QueryInterface(IID_ITfContextOwnerServices,
+ getter_AddRefs(service));
+ if (service) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "calling ITfContextOwnerServices::OnLayoutChange()...",
+ this));
+ HRESULT hr = service->OnLayoutChange();
+ ret = ret && SUCCEEDED(hr);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "called ITfContextOwnerServices::OnLayoutChange()",
+ this));
+ }
+ }
+
+ if (!mWidget || mWidget->Destroyed()) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "the widget is destroyed during calling OnLayoutChange()",
+ this));
+ return ret;
+ }
+
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "the TSFTextStore instance is destroyed during calling "
+ "OnLayoutChange()",
+ this));
+ return ret;
+ }
+
+ // If we returned TS_E_NOLAYOUT again, we need another call of
+ // OnLayoutChange() later. So, let's wait a query from TIP.
+ if (mHasReturnedNoLayoutError) {
+ mWaitingQueryLayout = true;
+ }
+
+ if (!mWaitingQueryLayout) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "succeeded notifying TIP of our layout change",
+ this));
+ return ret;
+ }
+
+ // If we believe that TIP needs to retry to retrieve our layout information
+ // later, we should call it with ::PostMessage() hack.
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "posing MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE for calling "
+ "OnLayoutChange() again...",
+ this));
+ ::PostMessage(mWidget->GetWindowHandle(), MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE,
+ reinterpret_cast<WPARAM>(this), 0);
+
+ return true;
+}
+
+void TSFTextStore::NotifyTSFOfLayoutChangeAgain() {
+ // Don't notify TSF of layout change after destroyed.
+ if (mDestroyed) {
+ mWaitingQueryLayout = false;
+ return;
+ }
+
+ // Before preforming this method, TIP has accessed our layout information by
+ // itself. In such case, we don't need to call OnLayoutChange() anymore.
+ if (!mWaitingQueryLayout) {
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
+ "calling NotifyTSFOfLayoutChange()...",
+ this));
+ NotifyTSFOfLayoutChange();
+
+ // If TIP didn't retrieved our layout information during a call of
+ // NotifyTSFOfLayoutChange(), it means that the TIP already gave up to
+ // retry to retrieve layout information or doesn't necessary it anymore.
+ // But don't forget that the call may have caused returning TS_E_NOLAYOUT
+ // error again. In such case we still need to call OnLayoutChange() later.
+ if (!mHasReturnedNoLayoutError && mWaitingQueryLayout) {
+ mWaitingQueryLayout = false;
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
+ "called NotifyTSFOfLayoutChange() but TIP didn't retry to "
+ "retrieve the layout information",
+ this));
+ } else {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
+ "called NotifyTSFOfLayoutChange()",
+ this));
+ }
+}
+
+nsresult TSFTextStore::OnUpdateCompositionInternal() {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnUpdateCompositionInternal(), "
+ "mDestroyed=%s, mDeferNotifyingTSFUntilNextUpdate=%s",
+ this, GetBoolName(mDestroyed),
+ GetBoolName(mDeferNotifyingTSFUntilNextUpdate)));
+
+ // There are nothing to do after destroyed.
+ if (mDestroyed) {
+ return NS_OK;
+ }
+
+ // Update cached data now because all pending events have been handled now.
+ if (mContentForTSF.isSome()) {
+ mContentForTSF->OnCompositionEventsHandled();
+ }
+
+ // If composition is completely finished both in TSF/TIP and the focused
+ // editor which may be in a remote process, we can clear the cache and don't
+ // have it until starting next composition.
+ if (mComposition.isNothing() && !IsHandlingCompositionInContent()) {
+ mDeferClearingContentForTSF = false;
+ }
+ mDeferNotifyingTSFUntilNextUpdate = false;
+ MaybeFlushPendingNotifications();
+
+ // If we're available, we should create native caret instead of IMEHandler
+ // because we may have some cache to do it.
+ if (IMEHandler::NeedsToCreateNativeCaret()) {
+ CreateNativeCaret();
+ }
+
+ return NS_OK;
+}
+
+nsresult TSFTextStore::OnMouseButtonEventInternal(
+ const IMENotification& aIMENotification) {
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // events.
+ return NS_OK;
+ }
+
+ if (mMouseTrackers.IsEmpty()) {
+ return NS_OK;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnMouseButtonEventInternal("
+ "aIMENotification={ mEventMessage=%s, mOffset=%u, mCursorPos=%s, "
+ "mCharRect=%s, mButton=%s, mButtons=%s, mModifiers=%s })",
+ this, ToChar(aIMENotification.mMouseButtonEventData.mEventMessage),
+ aIMENotification.mMouseButtonEventData.mOffset,
+ ToString(aIMENotification.mMouseButtonEventData.mCursorPos).c_str(),
+ ToString(aIMENotification.mMouseButtonEventData.mCharRect).c_str(),
+ GetMouseButtonName(aIMENotification.mMouseButtonEventData.mButton),
+ GetMouseButtonsName(aIMENotification.mMouseButtonEventData.mButtons)
+ .get(),
+ GetModifiersName(aIMENotification.mMouseButtonEventData.mModifiers)
+ .get()));
+
+ uint32_t offset = aIMENotification.mMouseButtonEventData.mOffset;
+ if (offset > static_cast<uint32_t>(LONG_MAX)) {
+ return NS_OK;
+ }
+ LayoutDeviceIntRect charRect =
+ aIMENotification.mMouseButtonEventData.mCharRect;
+ LayoutDeviceIntPoint cursorPos =
+ aIMENotification.mMouseButtonEventData.mCursorPos;
+ ULONG quadrant = 1;
+ if (charRect.Width() > 0) {
+ int32_t cursorXInChar = cursorPos.x - charRect.X();
+ quadrant = cursorXInChar * 4 / charRect.Width();
+ quadrant = (quadrant + 2) % 4;
+ }
+ ULONG edge = quadrant < 2 ? offset + 1 : offset;
+ DWORD buttonStatus = 0;
+ bool isMouseUp =
+ aIMENotification.mMouseButtonEventData.mEventMessage == eMouseUp;
+ if (!isMouseUp) {
+ switch (aIMENotification.mMouseButtonEventData.mButton) {
+ case MouseButton::ePrimary:
+ buttonStatus = MK_LBUTTON;
+ break;
+ case MouseButton::eMiddle:
+ buttonStatus = MK_MBUTTON;
+ break;
+ case MouseButton::eSecondary:
+ buttonStatus = MK_RBUTTON;
+ break;
+ }
+ }
+ if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_CONTROL) {
+ buttonStatus |= MK_CONTROL;
+ }
+ if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_SHIFT) {
+ buttonStatus |= MK_SHIFT;
+ }
+ for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
+ MouseTracker& tracker = mMouseTrackers[i];
+ if (!tracker.IsUsing() || tracker.Range().isNothing() ||
+ !tracker.Range()->IsOffsetInRange(offset)) {
+ continue;
+ }
+ if (tracker.OnMouseButtonEvent(edge - tracker.Range()->StartOffset(),
+ quadrant, buttonStatus)) {
+ return NS_SUCCESS_EVENT_CONSUMED;
+ }
+ }
+ return NS_OK;
+}
+
+void TSFTextStore::CreateNativeCaret() {
+ MOZ_ASSERT(!IMEHandler::IsA11yHandlingNativeCaret());
+
+ IMEHandler::MaybeDestroyNativeCaret();
+
+ // Don't create native caret after destroyed.
+ if (mDestroyed) {
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CreateNativeCaret(), mComposition=%s", this,
+ ToString(mComposition).c_str()));
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (MOZ_UNLIKELY(selectionForTSF.isNothing())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return;
+ }
+ if (!selectionForTSF->HasRange() && mComposition.isNothing()) {
+ // If there is no selection range nor composition, then, we don't have a
+ // good position to show windows of TIP...
+ // XXX It seems that storing last caret rect and using it in this case might
+ // be better?
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::CreateNativeCaret() couludn't create native "
+ "caret due to no selection range",
+ this));
+ return;
+ }
+
+ WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, mWidget);
+ mWidget->InitEvent(queryCaretRectEvent);
+
+ WidgetQueryContentEvent::Options options;
+ // XXX If this is called without composition and the selection isn't
+ // collapsed, is it OK?
+ int64_t caretOffset = selectionForTSF->HasRange()
+ ? selectionForTSF->MaxOffset()
+ : mComposition->StartOffset();
+ if (mComposition.isSome()) {
+ // If there is a composition, use the relative query for deciding caret
+ // position because composition might be different place from that
+ // TSFTextStore assumes.
+ options.mRelativeToInsertionPoint = true;
+ caretOffset -= mComposition->StartOffset();
+ } else if (!CanAccessActualContentDirectly()) {
+ // If TSF/TIP cannot access actual content directly, there may be pending
+ // text and/or selection changes which have not been notified TSF yet.
+ // Therefore, we should use the relative query from start of selection where
+ // TSFTextStore assumes since TSF/TIP computes the offset from our cached
+ // selection.
+ options.mRelativeToInsertionPoint = true;
+ caretOffset -= selectionForTSF->StartOffset();
+ }
+ queryCaretRectEvent.InitForQueryCaretRect(caretOffset, options);
+
+ DispatchEvent(queryCaretRectEvent);
+ if (NS_WARN_IF(queryCaretRectEvent.Failed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "eQueryCaretRect failure (offset=%lld)",
+ this, caretOffset));
+ return;
+ }
+
+ if (!IMEHandler::CreateNativeCaret(static_cast<nsWindow*>(mWidget.get()),
+ queryCaretRectEvent.mReply->mRect)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "IMEHandler::CreateNativeCaret() failure",
+ this));
+ return;
+ }
+}
+
+void TSFTextStore::CommitCompositionInternal(bool aDiscard) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(aDiscard=%s), "
+ "mSink=0x%p, mContext=0x%p, mComposition=%s",
+ this, GetBoolName(aDiscard), mSink.get(), mContext.get(),
+ ToString(mComposition).c_str()));
+
+ // If the document is locked, TSF will fail to commit composition since
+ // TSF needs another document lock. So, let's put off the request.
+ // Note that TextComposition will commit composition in the focused editor
+ // with the latest composition string for web apps and waits asynchronous
+ // committing messages. Therefore, we can and need to perform this
+ // asynchronously.
+ if (IsReadLocked()) {
+ if (mDeferCommittingComposition || mDeferCancellingComposition) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(), "
+ "does nothing because already called and waiting unlock...",
+ this));
+ return;
+ }
+ if (aDiscard) {
+ mDeferCancellingComposition = true;
+ } else {
+ mDeferCommittingComposition = true;
+ }
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(), "
+ "putting off to request to %s composition after unlocking the "
+ "document",
+ this, aDiscard ? "cancel" : "commit"));
+ return;
+ }
+
+ if (mComposition.isSome() && aDiscard) {
+ LONG endOffset = mComposition->EndOffset();
+ mComposition->SetData(EmptyString());
+ // Note that don't notify TSF of text change after this is destroyed.
+ if (mSink && !mDestroyed) {
+ TS_TEXTCHANGE textChange;
+ textChange.acpStart = mComposition->StartOffset();
+ textChange.acpOldEnd = endOffset;
+ textChange.acpNewEnd = mComposition->StartOffset();
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::CommitCompositionInternal(), calling"
+ "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
+ "acpNewEnd=%ld })...",
+ this, textChange.acpStart, textChange.acpOldEnd,
+ textChange.acpNewEnd));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ sink->OnTextChange(0, &textChange);
+ }
+ }
+ // Terminate two contexts, the base context (mContext) and the top
+ // if the top context is not the same as the base context
+ RefPtr<ITfContext> context = mContext;
+ do {
+ if (context) {
+ RefPtr<ITfContextOwnerCompositionServices> services;
+ context->QueryInterface(IID_ITfContextOwnerCompositionServices,
+ getter_AddRefs(services));
+ if (services) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(), "
+ "requesting TerminateComposition() for the context 0x%p...",
+ this, context.get()));
+ services->TerminateComposition(nullptr);
+ }
+ }
+ if (context != mContext) break;
+ if (mDocumentMgr) mDocumentMgr->GetTop(getter_AddRefs(context));
+ } while (context != mContext);
+}
+
+static bool GetCompartment(IUnknown* pUnk, const GUID& aID,
+ ITfCompartment** aCompartment) {
+ if (!pUnk) return false;
+
+ RefPtr<ITfCompartmentMgr> compMgr;
+ pUnk->QueryInterface(IID_ITfCompartmentMgr, getter_AddRefs(compMgr));
+ if (!compMgr) return false;
+
+ return SUCCEEDED(compMgr->GetCompartment(aID, aCompartment)) &&
+ (*aCompartment) != nullptr;
+}
+
+// static
+void TSFTextStore::SetIMEOpenState(bool aState) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("TSFTextStore::SetIMEOpenState(aState=%s)", GetBoolName(aState)));
+
+ if (!sThreadMgr) {
+ return;
+ }
+
+ RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
+ if (NS_WARN_IF(!comp)) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ (" TSFTextStore::SetIMEOpenState() FAILED due to"
+ "no compartment available"));
+ return;
+ }
+
+ VARIANT variant;
+ variant.vt = VT_I4;
+ variant.lVal = aState;
+ HRESULT hr = comp->SetValue(sClientId, &variant);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::SetIMEOpenState() FAILED due to "
+ "ITfCompartment::SetValue() failure, hr=0x%08lX",
+ hr));
+ return;
+ }
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ (" TSFTextStore::SetIMEOpenState(), setting "
+ "0x%04lX to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...",
+ variant.lVal));
+}
+
+// static
+bool TSFTextStore::GetIMEOpenState() {
+ if (!sThreadMgr) {
+ return false;
+ }
+
+ RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
+ if (NS_WARN_IF(!comp)) {
+ return false;
+ }
+
+ VARIANT variant;
+ ::VariantInit(&variant);
+ HRESULT hr = comp->GetValue(&variant);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetIMEOpenState() FAILED due to "
+ "ITfCompartment::GetValue() failure, hr=0x%08lX",
+ hr));
+ return false;
+ }
+ // Until IME is open in this process, the result may be empty.
+ if (variant.vt == VT_EMPTY) {
+ return false;
+ }
+ if (NS_WARN_IF(variant.vt != VT_I4)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetIMEOpenState() FAILED due to "
+ "invalid result of ITfCompartment::GetValue()"));
+ ::VariantClear(&variant);
+ return false;
+ }
+
+ return variant.lVal != 0;
+}
+
+// static
+void TSFTextStore::SetInputContext(nsWindow* aWidget,
+ const InputContext& aContext,
+ const InputContextAction& aAction) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("TSFTextStore::SetInputContext(aWidget=%p, "
+ "aContext=%s, aAction.mFocusChange=%s), "
+ "sEnabledTextStore(0x%p)={ mWidget=0x%p }, ThinksHavingFocus()=%s",
+ aWidget, mozilla::ToString(aContext).c_str(),
+ GetFocusChangeName(aAction.mFocusChange), sEnabledTextStore.get(),
+ sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr,
+ GetBoolName(ThinksHavingFocus())));
+
+ switch (aAction.mFocusChange) {
+ case InputContextAction::WIDGET_CREATED:
+ // If this is called when the widget is created, there is nothing to do.
+ return;
+ case InputContextAction::FOCUS_NOT_CHANGED:
+ case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
+ if (NS_WARN_IF(!IsInTSFMode())) {
+ return;
+ }
+ // In these cases, `NOTIFY_IME_OF_FOCUS` won't be sent. Therefore,
+ // we need to reset text store for new state right now.
+ break;
+ default:
+ NS_WARNING_ASSERTION(IsInTSFMode(),
+ "Why is this called when TSF is disabled?");
+ if (sEnabledTextStore) {
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ textStore->mInPrivateBrowsing = aContext.mInPrivateBrowsing;
+ textStore->SetInputScope(aContext.mHTMLInputType,
+ aContext.mHTMLInputMode);
+ if (aContext.mURI) {
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) {
+ CopyUTF8toUTF16(spec, textStore->mDocumentURL);
+ } else {
+ textStore->mDocumentURL.Truncate();
+ }
+ } else {
+ textStore->mDocumentURL.Truncate();
+ }
+ }
+ return;
+ }
+
+ // If focus isn't actually changed but the enabled state is changed,
+ // emulate the focus move.
+ if (!ThinksHavingFocus() && aContext.mIMEState.IsEditable()) {
+ if (!IMEHandler::GetFocusedWindow()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::SetInputContent() gets called to enable IME, "
+ "but IMEHandler has not received focus notification"));
+ } else {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ (" TSFTextStore::SetInputContent() emulates focus for IME "
+ "state change"));
+ OnFocusChange(true, aWidget, aContext);
+ }
+ } else if (ThinksHavingFocus() && !aContext.mIMEState.IsEditable()) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ (" TSFTextStore::SetInputContent() emulates blur for IME "
+ "state change"));
+ OnFocusChange(false, aWidget, aContext);
+ }
+}
+
+// static
+void TSFTextStore::MarkContextAsKeyboardDisabled(ITfContext* aContext) {
+ VARIANT variant_int4_value1;
+ variant_int4_value1.vt = VT_I4;
+ variant_int4_value1.lVal = 1;
+
+ RefPtr<ITfCompartment> comp;
+ if (!GetCompartment(aContext, GUID_COMPARTMENT_KEYBOARD_DISABLED,
+ getter_AddRefs(comp))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::MarkContextAsKeyboardDisabled() failed"
+ "aContext=0x%p...",
+ aContext));
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("TSFTextStore::MarkContextAsKeyboardDisabled(), setting "
+ "to disable context 0x%p...",
+ aContext));
+ comp->SetValue(sClientId, &variant_int4_value1);
+}
+
+// static
+void TSFTextStore::MarkContextAsEmpty(ITfContext* aContext) {
+ VARIANT variant_int4_value1;
+ variant_int4_value1.vt = VT_I4;
+ variant_int4_value1.lVal = 1;
+
+ RefPtr<ITfCompartment> comp;
+ if (!GetCompartment(aContext, GUID_COMPARTMENT_EMPTYCONTEXT,
+ getter_AddRefs(comp))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::MarkContextAsEmpty() failed"
+ "aContext=0x%p...",
+ aContext));
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("TSFTextStore::MarkContextAsEmpty(), setting "
+ "to mark empty context 0x%p...",
+ aContext));
+ comp->SetValue(sClientId, &variant_int4_value1);
+}
+
+// static
+void TSFTextStore::Initialize() {
+ MOZ_LOG(gIMELog, LogLevel::Info, ("TSFTextStore::Initialize() is called..."));
+
+ if (sThreadMgr) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED due to already initialized"));
+ return;
+ }
+
+ const bool enableTsf = StaticPrefs::intl_tsf_enabled_AtStartup();
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" TSFTextStore::Initialize(), TSF is %s",
+ enableTsf ? "enabled" : "disabled"));
+ if (!enableTsf) {
+ return;
+ }
+
+ RefPtr<ITfThreadMgr> threadMgr;
+ HRESULT hr =
+ ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITfThreadMgr, getter_AddRefs(threadMgr));
+ if (FAILED(hr) || !threadMgr) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to "
+ "create the thread manager, hr=0x%08lX",
+ hr));
+ return;
+ }
+
+ hr = threadMgr->Activate(&sClientId);
+ if (FAILED(hr)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to activate, hr=0x%08lX", hr));
+ return;
+ }
+
+ RefPtr<ITfDocumentMgr> disabledDocumentMgr;
+ hr = threadMgr->CreateDocumentMgr(getter_AddRefs(disabledDocumentMgr));
+ if (FAILED(hr) || !disabledDocumentMgr) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create "
+ "a document manager for disabled mode, hr=0x%08lX",
+ hr));
+ return;
+ }
+
+ RefPtr<ITfContext> disabledContext;
+ DWORD editCookie = 0;
+ hr = disabledDocumentMgr->CreateContext(
+ sClientId, 0, nullptr, getter_AddRefs(disabledContext), &editCookie);
+ if (FAILED(hr) || !disabledContext) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create "
+ "a context for disabled mode, hr=0x%08lX",
+ hr));
+ return;
+ }
+
+ MarkContextAsKeyboardDisabled(disabledContext);
+ MarkContextAsEmpty(disabledContext);
+
+ sThreadMgr = threadMgr;
+ sDisabledDocumentMgr = disabledDocumentMgr;
+ sDisabledContext = disabledContext;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" TSFTextStore::Initialize(), sThreadMgr=0x%p, "
+ "sClientId=0x%08lX, sDisabledDocumentMgr=0x%p, sDisabledContext=%p",
+ sThreadMgr.get(), sClientId, sDisabledDocumentMgr.get(),
+ sDisabledContext.get()));
+}
+
+// static
+already_AddRefed<ITfThreadMgr> TSFTextStore::GetThreadMgr() {
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ return threadMgr.forget();
+}
+
+// static
+already_AddRefed<ITfMessagePump> TSFTextStore::GetMessagePump() {
+ static bool sInitialized = false;
+ if (!sThreadMgr) {
+ return nullptr;
+ }
+ if (sMessagePump) {
+ RefPtr<ITfMessagePump> messagePump = sMessagePump;
+ return messagePump.forget();
+ }
+ // If it tried to retrieve ITfMessagePump from sThreadMgr but it failed,
+ // we shouldn't retry it at every message due to performance reason.
+ // Although this shouldn't occur actually.
+ if (sInitialized) {
+ return nullptr;
+ }
+ sInitialized = true;
+
+ RefPtr<ITfMessagePump> messagePump;
+ HRESULT hr = sThreadMgr->QueryInterface(IID_ITfMessagePump,
+ getter_AddRefs(messagePump));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!messagePump)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetMessagePump() FAILED to "
+ "QI message pump from the thread manager, hr=0x%08lX",
+ hr));
+ return nullptr;
+ }
+ sMessagePump = messagePump;
+ return messagePump.forget();
+}
+
+// static
+already_AddRefed<ITfDisplayAttributeMgr>
+TSFTextStore::GetDisplayAttributeMgr() {
+ RefPtr<ITfDisplayAttributeMgr> displayAttributeMgr;
+ if (sDisplayAttrMgr) {
+ displayAttributeMgr = sDisplayAttrMgr;
+ return displayAttributeMgr.forget();
+ }
+
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_TF_DisplayAttributeMgr, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITfDisplayAttributeMgr, getter_AddRefs(displayAttributeMgr));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!displayAttributeMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetDisplayAttributeMgr() FAILED to create "
+ "a display attribute manager instance, hr=0x%08lX",
+ hr));
+ return nullptr;
+ }
+ sDisplayAttrMgr = displayAttributeMgr;
+ return displayAttributeMgr.forget();
+}
+
+// static
+already_AddRefed<ITfCategoryMgr> TSFTextStore::GetCategoryMgr() {
+ RefPtr<ITfCategoryMgr> categoryMgr;
+ if (sCategoryMgr) {
+ categoryMgr = sCategoryMgr;
+ return categoryMgr.forget();
+ }
+ HRESULT hr =
+ ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITfCategoryMgr, getter_AddRefs(categoryMgr));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!categoryMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetCategoryMgr() FAILED to create "
+ "a category manager instance, hr=0x%08lX",
+ hr));
+ return nullptr;
+ }
+ sCategoryMgr = categoryMgr;
+ return categoryMgr.forget();
+}
+
+// static
+already_AddRefed<ITfCompartment> TSFTextStore::GetCompartmentForOpenClose() {
+ if (sCompartmentForOpenClose) {
+ RefPtr<ITfCompartment> compartment = sCompartmentForOpenClose;
+ return compartment.forget();
+ }
+
+ if (!sThreadMgr) {
+ return nullptr;
+ }
+
+ RefPtr<ITfCompartmentMgr> compartmentMgr;
+ HRESULT hr = sThreadMgr->QueryInterface(IID_ITfCompartmentMgr,
+ getter_AddRefs(compartmentMgr));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartmentMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
+ "sThreadMgr not having ITfCompartmentMgr, hr=0x%08lX",
+ hr));
+ return nullptr;
+ }
+
+ RefPtr<ITfCompartment> compartment;
+ hr = compartmentMgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
+ getter_AddRefs(compartment));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartment)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
+ "ITfCompartmentMgr::GetCompartment() failuere, hr=0x%08lX",
+ hr));
+ return nullptr;
+ }
+
+ sCompartmentForOpenClose = compartment;
+ return compartment.forget();
+}
+
+// static
+already_AddRefed<ITfInputProcessorProfiles>
+TSFTextStore::GetInputProcessorProfiles() {
+ RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles;
+ if (sInputProcessorProfiles) {
+ inputProcessorProfiles = sInputProcessorProfiles;
+ return inputProcessorProfiles.forget();
+ }
+ // XXX MSDN documents that ITfInputProcessorProfiles is available only on
+ // desktop apps. However, there is no known way to obtain
+ // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles
+ // instance.
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_TF_InputProcessorProfiles, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITfInputProcessorProfiles, getter_AddRefs(inputProcessorProfiles));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!inputProcessorProfiles)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetInputProcessorProfiles() FAILED to create input "
+ "processor profiles, hr=0x%08lX",
+ hr));
+ return nullptr;
+ }
+ sInputProcessorProfiles = inputProcessorProfiles;
+ return inputProcessorProfiles.forget();
+}
+
+// static
+void TSFTextStore::Terminate() {
+ MOZ_LOG(gIMELog, LogLevel::Info, ("TSFTextStore::Terminate()"));
+
+ TSFStaticSink::Shutdown();
+
+ sDisplayAttrMgr = nullptr;
+ sCategoryMgr = nullptr;
+ sEnabledTextStore = nullptr;
+ sDisabledDocumentMgr = nullptr;
+ sDisabledContext = nullptr;
+ sCompartmentForOpenClose = nullptr;
+ sInputProcessorProfiles = nullptr;
+ sClientId = 0;
+ if (sThreadMgr) {
+ sThreadMgr->Deactivate();
+ sThreadMgr = nullptr;
+ sMessagePump = nullptr;
+ sKeystrokeMgr = nullptr;
+ }
+}
+
+// static
+bool TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg) {
+ if (!sThreadMgr) {
+ return false; // not in TSF mode
+ }
+ static bool sInitialized = false;
+ if (!sKeystrokeMgr) {
+ // If it tried to retrieve ITfKeystrokeMgr from sThreadMgr but it failed,
+ // we shouldn't retry it at every keydown nor keyup due to performance
+ // reason. Although this shouldn't occur actually.
+ if (sInitialized) {
+ return false;
+ }
+ sInitialized = true;
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr;
+ HRESULT hr = sThreadMgr->QueryInterface(IID_ITfKeystrokeMgr,
+ getter_AddRefs(keystrokeMgr));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!keystrokeMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::ProcessRawKeyMessage() FAILED to "
+ "QI keystroke manager from the thread manager, hr=0x%08lX",
+ hr));
+ return false;
+ }
+ sKeystrokeMgr = keystrokeMgr.forget();
+ }
+
+ if (aMsg.message == WM_KEYDOWN) {
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ if (textStore) {
+ textStore->OnStartToHandleKeyMessage();
+ if (NS_WARN_IF(textStore != sEnabledTextStore)) {
+ // Let's handle the key message with new focused TSFTextStore.
+ textStore = sEnabledTextStore;
+ }
+ }
+ AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
+ AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
+ sHandlingKeyMsg = &aMsg;
+ sIsKeyboardEventDispatched = false;
+ BOOL eaten;
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
+ HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten);
+ if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
+ return false;
+ }
+ hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
+ if (textStore) {
+ textStore->OnEndHandlingKeyMessage(!!eaten);
+ }
+ return SUCCEEDED(hr) &&
+ (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
+ }
+ if (aMsg.message == WM_KEYUP) {
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ if (textStore) {
+ textStore->OnStartToHandleKeyMessage();
+ if (NS_WARN_IF(textStore != sEnabledTextStore)) {
+ // Let's handle the key message with new focused TSFTextStore.
+ textStore = sEnabledTextStore;
+ }
+ }
+ AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
+ AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
+ sHandlingKeyMsg = &aMsg;
+ sIsKeyboardEventDispatched = false;
+ BOOL eaten;
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
+ HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten);
+ if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
+ return false;
+ }
+ hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten);
+ if (textStore) {
+ textStore->OnEndHandlingKeyMessage(!!eaten);
+ }
+ return SUCCEEDED(hr) &&
+ (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
+ }
+ return false;
+}
+
+// static
+void TSFTextStore::ProcessMessage(nsWindow* aWindow, UINT aMessage,
+ WPARAM& aWParam, LPARAM& aLParam,
+ MSGResult& aResult) {
+ switch (aMessage) {
+ case WM_IME_SETCONTEXT:
+ // If a windowless plugin had focus and IME was handled on it, composition
+ // window was set the position. After that, even in TSF mode, WinXP keeps
+ // to use composition window at the position if the active IME is not
+ // aware TSF. For avoiding this issue, we need to hide the composition
+ // window here.
+ if (aWParam) {
+ aLParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
+ }
+ break;
+ case WM_ENTERIDLE:
+ // When an modal dialog such as a file picker is open, composition
+ // should be committed because IME might be used on it.
+ if (!IsComposingOn(aWindow)) {
+ break;
+ }
+ CommitComposition(false);
+ break;
+ case MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE: {
+ TSFTextStore* maybeTextStore = reinterpret_cast<TSFTextStore*>(aWParam);
+ if (maybeTextStore == sEnabledTextStore) {
+ RefPtr<TSFTextStore> textStore(maybeTextStore);
+ textStore->NotifyTSFOfLayoutChangeAgain();
+ }
+ break;
+ }
+ }
+}
+
+// static
+bool TSFTextStore::IsIMM_IMEActive() {
+ return TSFStaticSink::IsIMM_IMEActive();
+}
+
+// static
+bool TSFTextStore::IsMSJapaneseIMEActive() {
+ return TSFStaticSink::IsMSJapaneseIMEActive();
+}
+
+// static
+bool TSFTextStore::IsGoogleJapaneseInputActive() {
+ return TSFStaticSink::IsGoogleJapaneseInputActive();
+}
+
+// static
+bool TSFTextStore::IsATOKActive() { return TSFStaticSink::IsATOKActive(); }
+
+/******************************************************************************
+ * TSFTextStore::Content
+ *****************************************************************************/
+
+const nsDependentSubstring TSFTextStore::Content::GetSelectedText() const {
+ if (NS_WARN_IF(mSelection.isNothing())) {
+ return nsDependentSubstring();
+ }
+ return GetSubstring(static_cast<uint32_t>(mSelection->StartOffset()),
+ static_cast<uint32_t>(mSelection->Length()));
+}
+
+const nsDependentSubstring TSFTextStore::Content::GetSubstring(
+ uint32_t aStart, uint32_t aLength) const {
+ return nsDependentSubstring(mText, aStart, aLength);
+}
+
+void TSFTextStore::Content::ReplaceSelectedTextWith(const nsAString& aString) {
+ if (NS_WARN_IF(mSelection.isNothing())) {
+ return;
+ }
+ ReplaceTextWith(mSelection->StartOffset(), mSelection->Length(), aString);
+}
+
+inline uint32_t FirstDifferentCharOffset(const nsAString& aStr1,
+ const nsAString& aStr2) {
+ MOZ_ASSERT(aStr1 != aStr2);
+ uint32_t i = 0;
+ uint32_t minLength = std::min(aStr1.Length(), aStr2.Length());
+ for (; i < minLength && aStr1[i] == aStr2[i]; i++) {
+ /* nothing to do */
+ }
+ return i;
+}
+
+void TSFTextStore::Content::ReplaceTextWith(LONG aStart, LONG aLength,
+ const nsAString& aReplaceString) {
+ MOZ_ASSERT(aStart >= 0);
+ MOZ_ASSERT(aLength >= 0);
+ const nsDependentSubstring replacedString = GetSubstring(
+ static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength));
+ if (aReplaceString != replacedString) {
+ uint32_t firstDifferentOffset = mMinModifiedOffset.valueOr(UINT32_MAX);
+ if (mComposition.isSome()) {
+ // Emulate text insertion during compositions, because during a
+ // composition, editor expects the whole composition string to
+ // be sent in eCompositionChange, not just the inserted part.
+ // The actual eCompositionChange will be sent in SetSelection
+ // or OnUpdateComposition.
+ MOZ_ASSERT(aStart >= mComposition->StartOffset());
+ MOZ_ASSERT(aStart + aLength <= mComposition->EndOffset());
+ mComposition->ReplaceData(
+ static_cast<uint32_t>(aStart - mComposition->StartOffset()),
+ static_cast<uint32_t>(aLength), aReplaceString);
+ // TIP may set composition string twice or more times during a document
+ // lock. Therefore, we should compute the first difference offset with
+ // mLastComposition.
+ if (mLastComposition.isNothing()) {
+ firstDifferentOffset = mComposition->StartOffset();
+ } else if (mComposition->DataRef() != mLastComposition->DataRef()) {
+ firstDifferentOffset =
+ mComposition->StartOffset() +
+ FirstDifferentCharOffset(mComposition->DataRef(),
+ mLastComposition->DataRef());
+ // The previous change to the composition string is canceled.
+ if (mMinModifiedOffset.isSome() &&
+ mMinModifiedOffset.value() >=
+ static_cast<uint32_t>(mComposition->StartOffset()) &&
+ mMinModifiedOffset.value() < firstDifferentOffset) {
+ mMinModifiedOffset = Some(firstDifferentOffset);
+ }
+ } else if (mMinModifiedOffset.isSome() &&
+ mMinModifiedOffset.value() < static_cast<uint32_t>(LONG_MAX) &&
+ mComposition->IsOffsetInRange(
+ static_cast<long>(mMinModifiedOffset.value()))) {
+ // The previous change to the composition string is canceled.
+ firstDifferentOffset = mComposition->EndOffset();
+ mMinModifiedOffset = Some(firstDifferentOffset);
+ }
+ mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::Content::ReplaceTextWith(aStart=%ld, "
+ "aLength=%ld, aReplaceString=\"%s\"), mComposition=%s, "
+ "mLastComposition=%s, mMinModifiedOffset=%s, "
+ "firstDifferentOffset=%u",
+ this, aStart, aLength, GetEscapedUTF8String(aReplaceString).get(),
+ ToString(mComposition).c_str(), ToString(mLastComposition).c_str(),
+ ToString(mMinModifiedOffset).c_str(), firstDifferentOffset));
+ } else {
+ firstDifferentOffset =
+ static_cast<uint32_t>(aStart) +
+ FirstDifferentCharOffset(aReplaceString, replacedString);
+ }
+ mMinModifiedOffset =
+ mMinModifiedOffset.isNothing()
+ ? Some(firstDifferentOffset)
+ : Some(std::min(mMinModifiedOffset.value(), firstDifferentOffset));
+ mText.Replace(static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength),
+ aReplaceString);
+ }
+ // Selection should be collapsed at the end of the inserted string.
+ mSelection = Some(TSFTextStore::Selection(static_cast<uint32_t>(aStart) +
+ aReplaceString.Length()));
+}
+
+void TSFTextStore::Content::StartComposition(
+ ITfCompositionView* aCompositionView, const PendingAction& aCompStart,
+ bool aPreserveSelection) {
+ MOZ_ASSERT(aCompositionView);
+ MOZ_ASSERT(mComposition.isNothing());
+ MOZ_ASSERT(aCompStart.mType == PendingAction::Type::eCompositionStart);
+
+ mComposition.reset(); // Avoid new crash in the beta and nightly channels.
+ mComposition.emplace(
+ aCompositionView, aCompStart.mSelectionStart,
+ GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart),
+ static_cast<uint32_t>(aCompStart.mSelectionLength)));
+ mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
+ if (!aPreserveSelection) {
+ // XXX Do we need to set a new writing-mode here when setting a new
+ // selection? Currently, we just preserve the existing value.
+ WritingMode writingMode =
+ mSelection.isNothing() ? WritingMode() : mSelection->WritingModeRef();
+ mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset(),
+ mComposition->Length(), false,
+ writingMode));
+ }
+}
+
+void TSFTextStore::Content::RestoreCommittedComposition(
+ ITfCompositionView* aCompositionView,
+ const PendingAction& aCanceledCompositionEnd) {
+ MOZ_ASSERT(aCompositionView);
+ MOZ_ASSERT(mComposition.isNothing());
+ MOZ_ASSERT(aCanceledCompositionEnd.mType ==
+ PendingAction::Type::eCompositionEnd);
+ MOZ_ASSERT(
+ GetSubstring(
+ static_cast<uint32_t>(aCanceledCompositionEnd.mSelectionStart),
+ static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
+ aCanceledCompositionEnd.mData);
+
+ // Restore the committed string as composing string.
+ mComposition.reset(); // Avoid new crash in the beta and nightly channels.
+ mComposition.emplace(aCompositionView,
+ aCanceledCompositionEnd.mSelectionStart,
+ aCanceledCompositionEnd.mData);
+ mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
+}
+
+void TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd) {
+ MOZ_ASSERT(mComposition.isSome());
+ MOZ_ASSERT(aCompEnd.mType == PendingAction::Type::eCompositionEnd);
+
+ if (mComposition.isNothing()) {
+ return; // Avoid new crash in the beta and nightly channels.
+ }
+
+ mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset() +
+ aCompEnd.mData.Length()));
+ mComposition.reset();
+}
+
+/******************************************************************************
+ * TSFTextStore::MouseTracker
+ *****************************************************************************/
+
+TSFTextStore::MouseTracker::MouseTracker() : mCookie(kInvalidCookie) {}
+
+HRESULT
+TSFTextStore::MouseTracker::Init(TSFTextStore* aTextStore) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::Init(aTextStore=0x%p), "
+ "aTextStore->mMouseTrackers.Length()=%zu",
+ this, aTextStore, aTextStore->mMouseTrackers.Length()));
+
+ if (&aTextStore->mMouseTrackers.LastElement() != this) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
+ "this is not the last element of mMouseTrackers",
+ this));
+ return E_FAIL;
+ }
+ if (aTextStore->mMouseTrackers.Length() > kInvalidCookie) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
+ "no new cookie available",
+ this));
+ return E_FAIL;
+ }
+ MOZ_ASSERT(!aTextStore->mMouseTrackers.IsEmpty(),
+ "This instance must be in TSFTextStore::mMouseTrackers");
+ mCookie = static_cast<DWORD>(aTextStore->mMouseTrackers.Length() - 1);
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::MouseTracker::AdviseSink(TSFTextStore* aTextStore,
+ ITfRangeACP* aTextRange,
+ ITfMouseSink* aMouseSink) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::AdviseSink(aTextStore=0x%p, "
+ "aTextRange=0x%p, aMouseSink=0x%p), mCookie=%ld, mSink=0x%p",
+ this, aTextStore, aTextRange, aMouseSink, mCookie, mSink.get()));
+ MOZ_ASSERT(mCookie != kInvalidCookie, "This hasn't been initalized?");
+
+ if (mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to already being used",
+ this));
+ return E_FAIL;
+ }
+
+ MOZ_ASSERT(mRange.isNothing());
+
+ LONG start = 0, length = 0;
+ HRESULT hr = aTextRange->GetExtent(&start, &length);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to failure of ITfRangeACP::GetExtent()",
+ this));
+ return hr;
+ }
+
+ if (start < 0 || length <= 0 || start + length > LONG_MAX) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to odd result of ITfRangeACP::GetExtent(), "
+ "start=%ld, length=%ld",
+ this, start, length));
+ return E_INVALIDARG;
+ }
+
+ nsAutoString textContent;
+ if (NS_WARN_IF(!aTextStore->GetCurrentText(textContent))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to failure of TSFTextStore::GetCurrentText()",
+ this));
+ return E_FAIL;
+ }
+
+ if (textContent.Length() <= static_cast<uint32_t>(start) ||
+ textContent.Length() < static_cast<uint32_t>(start + length)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to out of range, start=%ld, length=%ld, "
+ "textContent.Length()=%zu",
+ this, start, length, textContent.Length()));
+ return E_INVALIDARG;
+ }
+
+ mRange.emplace(start, start + length);
+
+ mSink = aMouseSink;
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink(), "
+ "succeeded, mRange=%s, textContent.Length()=%zu",
+ this, ToString(mRange).c_str(), textContent.Length()));
+ return S_OK;
+}
+
+void TSFTextStore::MouseTracker::UnadviseSink() {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::UnadviseSink(), "
+ "mCookie=%ld, mSink=0x%p, mRange=%s",
+ this, mCookie, mSink.get(), ToString(mRange).c_str()));
+ mSink = nullptr;
+ mRange.reset();
+}
+
+bool TSFTextStore::MouseTracker::OnMouseButtonEvent(ULONG aEdge,
+ ULONG aQuadrant,
+ DWORD aButtonStatus) {
+ MOZ_ASSERT(IsUsing(), "The caller must check before calling OnMouseEvent()");
+
+ BOOL eaten = FALSE;
+ RefPtr<ITfMouseSink> sink = mSink;
+ HRESULT hr = sink->OnMouseEvent(aEdge, aQuadrant, aButtonStatus, &eaten);
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::OnMouseEvent(aEdge=%ld, "
+ "aQuadrant=%ld, aButtonStatus=0x%08lX), hr=0x%08lX, eaten=%s",
+ this, aEdge, aQuadrant, aButtonStatus, hr, GetBoolName(!!eaten)));
+
+ return SUCCEEDED(hr) && eaten;
+}
+
+#ifdef DEBUG
+// static
+bool TSFTextStore::CurrentKeyboardLayoutHasIME() {
+ RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
+ TSFTextStore::GetInputProcessorProfiles();
+ if (!inputProcessorProfiles) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED due to "
+ "there is no input processor profiles instance"));
+ return false;
+ }
+ RefPtr<ITfInputProcessorProfileMgr> profileMgr;
+ HRESULT hr = inputProcessorProfiles->QueryInterface(
+ IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
+ if (FAILED(hr) || !profileMgr) {
+ // On Windows Vista or later, ImmIsIME() API always returns true.
+ // If we failed to obtain the profile manager, we cannot know if current
+ // keyboard layout has IME.
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to query "
+ "ITfInputProcessorProfileMgr"));
+ return false;
+ }
+
+ TF_INPUTPROCESSORPROFILE profile;
+ hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
+ if (hr == S_FALSE) {
+ return false; // not found or not active
+ }
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to retreive "
+ "active profile"));
+ return false;
+ }
+ return (profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR);
+}
+#endif // #ifdef DEBUG
+
+} // namespace widget
+} // namespace mozilla