michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG /* Allow logging in the release build */ michael@0: #endif // MOZ_LOGGING michael@0: #include "prlog.h" michael@0: michael@0: #include "nscore.h" michael@0: #include "nsWindow.h" michael@0: #ifdef MOZ_METRO michael@0: #include "winrt/MetroWidget.h" michael@0: #endif michael@0: #include "nsPrintfCString.h" michael@0: #include "WinUtils.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/TextEvents.h" michael@0: #include "mozilla/WindowsVersion.h" michael@0: michael@0: #define INPUTSCOPE_INIT_GUID michael@0: #include "nsTextStore.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::widget; michael@0: michael@0: static const char* kPrefNameTSFEnabled = "intl.tsf.enable"; michael@0: michael@0: static const char* kLegacyPrefNameTSFEnabled = "intl.enable_tsf_support"; michael@0: michael@0: #ifdef PR_LOGGING michael@0: /** michael@0: * TSF related code should log its behavior even on release build especially michael@0: * in the interface methods. michael@0: * michael@0: * In interface methods, use PR_LOG_ALWAYS. michael@0: * In internal methods, use PR_LOG_DEBUG for logging normal behavior. michael@0: * For logging error, use PR_LOG_ERROR. michael@0: * michael@0: * When an instance method is called, start with following text: michael@0: * "TSF: 0x%p nsFoo::Bar(", the 0x%p should be the "this" of the nsFoo. michael@0: * after that, start with: michael@0: * "TSF: 0x%p nsFoo::Bar(" michael@0: * In an internal method, start with following text: michael@0: * "TSF: 0x%p nsFoo::Bar(" michael@0: * When a static method is called, start with following text: michael@0: * "TSF: nsFoo::Bar(" michael@0: */ michael@0: michael@0: PRLogModuleInfo* sTextStoreLog = nullptr; michael@0: #endif // #ifdef PR_LOGGING michael@0: michael@0: /******************************************************************/ michael@0: /* InputScopeImpl */ michael@0: /******************************************************************/ michael@0: michael@0: class InputScopeImpl MOZ_FINAL : public ITfInputScope michael@0: { michael@0: public: michael@0: InputScopeImpl(const nsTArray& aList) : michael@0: mRefCnt(1), michael@0: mInputScopes(aList) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p InputScopeImpl()", this)); michael@0: } michael@0: michael@0: STDMETHODIMP_(ULONG) AddRef(void) { return ++mRefCnt; } michael@0: michael@0: STDMETHODIMP_(ULONG) Release(void) michael@0: { michael@0: --mRefCnt; michael@0: if (mRefCnt) { michael@0: return mRefCnt; michael@0: } michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p InputScopeImpl::Release() final", this)); michael@0: delete this; michael@0: return 0; michael@0: } michael@0: michael@0: STDMETHODIMP QueryInterface(REFIID riid, void** ppv) michael@0: { michael@0: *ppv=nullptr; michael@0: if ( (IID_IUnknown == riid) || (IID_ITfInputScope == riid) ) { michael@0: *ppv = static_cast(this); michael@0: } michael@0: if (*ppv) { michael@0: AddRef(); michael@0: return S_OK; michael@0: } michael@0: return E_NOINTERFACE; michael@0: } michael@0: michael@0: STDMETHODIMP GetInputScopes(InputScope** pprgInputScopes, UINT* pcCount) michael@0: { michael@0: uint32_t count = (mInputScopes.IsEmpty() ? 1 : mInputScopes.Length()); michael@0: michael@0: InputScope* pScope = (InputScope*) CoTaskMemAlloc(sizeof(InputScope) * count); michael@0: NS_ENSURE_TRUE(pScope, E_OUTOFMEMORY); michael@0: michael@0: if (mInputScopes.IsEmpty()) { michael@0: *pScope = IS_DEFAULT; michael@0: *pcCount = 1; michael@0: *pprgInputScopes = pScope; michael@0: return S_OK; michael@0: } michael@0: michael@0: *pcCount = 0; michael@0: michael@0: for (uint32_t idx = 0; idx < count; idx++) { michael@0: *(pScope + idx) = mInputScopes[idx]; michael@0: (*pcCount)++; michael@0: } michael@0: michael@0: *pprgInputScopes = pScope; michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP GetPhrase(BSTR **ppbstrPhrases, UINT *pcCount) { return E_NOTIMPL; } michael@0: STDMETHODIMP GetRegularExpression(BSTR *pbstrRegExp) { return E_NOTIMPL; } michael@0: STDMETHODIMP GetSRGS(BSTR *pbstrSRGS) { return E_NOTIMPL; } michael@0: STDMETHODIMP GetXML(BSTR *pbstrXML) { return E_NOTIMPL; } michael@0: michael@0: private: michael@0: DWORD mRefCnt; michael@0: nsTArray mInputScopes; michael@0: }; michael@0: michael@0: /******************************************************************/ michael@0: /* nsTextStore */ michael@0: /******************************************************************/ michael@0: michael@0: ITfThreadMgr* nsTextStore::sTsfThreadMgr = nullptr; michael@0: ITfMessagePump* nsTextStore::sMessagePump = nullptr; michael@0: ITfKeystrokeMgr* nsTextStore::sKeystrokeMgr = nullptr; michael@0: ITfDisplayAttributeMgr* nsTextStore::sDisplayAttrMgr = nullptr; michael@0: ITfCategoryMgr* nsTextStore::sCategoryMgr = nullptr; michael@0: ITfDocumentMgr* nsTextStore::sTsfDisabledDocumentMgr = nullptr; michael@0: ITfContext* nsTextStore::sTsfDisabledContext = nullptr; michael@0: ITfInputProcessorProfiles* nsTextStore::sInputProcessorProfiles = nullptr; michael@0: DWORD nsTextStore::sTsfClientId = 0; michael@0: nsTextStore* nsTextStore::sTsfTextStore = nullptr; michael@0: michael@0: bool nsTextStore::sCreateNativeCaretForATOK = false; michael@0: michael@0: UINT nsTextStore::sFlushTIPInputMessage = 0; michael@0: michael@0: #define TEXTSTORE_DEFAULT_VIEW (1) michael@0: michael@0: #ifdef PR_LOGGING michael@0: michael@0: static const char* michael@0: GetBoolName(bool aBool) michael@0: { michael@0: return aBool ? "true" : "false"; michael@0: } michael@0: michael@0: static void michael@0: HandleSeparator(nsCString& aDesc) michael@0: { michael@0: if (!aDesc.IsEmpty()) { michael@0: aDesc.AppendLiteral(" | "); michael@0: } michael@0: } michael@0: michael@0: static const nsCString michael@0: GetFindFlagName(DWORD aFindFlag) michael@0: { michael@0: nsAutoCString description; michael@0: if (!aFindFlag) { michael@0: description.AppendLiteral("no flags (0)"); michael@0: return description; michael@0: } michael@0: if (aFindFlag & TS_ATTR_FIND_BACKWARDS) { michael@0: description.AppendLiteral("TS_ATTR_FIND_BACKWARDS"); michael@0: } michael@0: if (aFindFlag & TS_ATTR_FIND_WANT_OFFSET) { michael@0: HandleSeparator(description); michael@0: description.AppendLiteral("TS_ATTR_FIND_WANT_OFFSET"); michael@0: } michael@0: if (aFindFlag & TS_ATTR_FIND_UPDATESTART) { michael@0: HandleSeparator(description); michael@0: description.AppendLiteral("TS_ATTR_FIND_UPDATESTART"); michael@0: } michael@0: if (aFindFlag & TS_ATTR_FIND_WANT_VALUE) { michael@0: HandleSeparator(description); michael@0: description.AppendLiteral("TS_ATTR_FIND_WANT_VALUE"); michael@0: } michael@0: if (aFindFlag & TS_ATTR_FIND_WANT_END) { michael@0: HandleSeparator(description); michael@0: description.AppendLiteral("TS_ATTR_FIND_WANT_END"); michael@0: } michael@0: if (aFindFlag & TS_ATTR_FIND_HIDDEN) { michael@0: HandleSeparator(description); michael@0: description.AppendLiteral("TS_ATTR_FIND_HIDDEN"); michael@0: } michael@0: if (description.IsEmpty()) { michael@0: description.AppendLiteral("Unknown ("); michael@0: description.AppendInt(static_cast(aFindFlag)); michael@0: description.AppendLiteral(")"); michael@0: } michael@0: return description; michael@0: } michael@0: michael@0: static const char* michael@0: GetIMEEnabledName(IMEState::Enabled aIMEEnabled) michael@0: { michael@0: switch (aIMEEnabled) { michael@0: case IMEState::DISABLED: michael@0: return "DISABLED"; michael@0: case IMEState::ENABLED: michael@0: return "ENABLED"; michael@0: case IMEState::PASSWORD: michael@0: return "PASSWORD"; michael@0: case IMEState::PLUGIN: michael@0: return "PLUGIN"; michael@0: default: michael@0: return "Invalid"; michael@0: } michael@0: } michael@0: michael@0: static const char* michael@0: GetFocusChangeName(InputContextAction::FocusChange aFocusChange) michael@0: { michael@0: switch (aFocusChange) { michael@0: case InputContextAction::FOCUS_NOT_CHANGED: michael@0: return "FOCUS_NOT_CHANGED"; michael@0: case InputContextAction::GOT_FOCUS: michael@0: return "GOT_FOCUS"; michael@0: case InputContextAction::LOST_FOCUS: michael@0: return "LOST_FOCUS"; michael@0: case InputContextAction::MENU_GOT_PSEUDO_FOCUS: michael@0: return "MENU_GOT_PSEUDO_FOCUS"; michael@0: case InputContextAction::MENU_LOST_PSEUDO_FOCUS: michael@0: return "MENU_LOST_PSEUDO_FOCUS"; michael@0: default: michael@0: return "Unknown"; michael@0: } michael@0: } michael@0: michael@0: static nsCString michael@0: GetCLSIDNameStr(REFCLSID aCLSID) michael@0: { michael@0: LPOLESTR str = nullptr; michael@0: HRESULT hr = ::StringFromCLSID(aCLSID, &str); michael@0: if (FAILED(hr) || !str || !str[0]) { michael@0: return EmptyCString(); michael@0: } michael@0: michael@0: nsAutoCString result; michael@0: result = NS_ConvertUTF16toUTF8(str); michael@0: ::CoTaskMemFree(str); michael@0: return result; michael@0: } michael@0: michael@0: static nsCString michael@0: GetGUIDNameStr(REFGUID aGUID) michael@0: { michael@0: OLECHAR str[40]; michael@0: int len = ::StringFromGUID2(aGUID, str, ArrayLength(str)); michael@0: if (!len || !str[0]) { michael@0: return EmptyCString(); michael@0: } michael@0: michael@0: return NS_ConvertUTF16toUTF8(str); michael@0: } michael@0: michael@0: static nsCString michael@0: GetRIIDNameStr(REFIID aRIID) michael@0: { michael@0: LPOLESTR str = nullptr; michael@0: HRESULT hr = ::StringFromIID(aRIID, &str); michael@0: if (FAILED(hr) || !str || !str[0]) { michael@0: return EmptyCString(); michael@0: } michael@0: michael@0: nsAutoString key(L"Interface\\"); michael@0: key += str; michael@0: michael@0: nsAutoCString result; michael@0: wchar_t buf[256]; michael@0: if (WinUtils::GetRegistryKey(HKEY_CLASSES_ROOT, key.get(), nullptr, michael@0: buf, sizeof(buf))) { michael@0: result = NS_ConvertUTF16toUTF8(buf); michael@0: } else { michael@0: result = NS_ConvertUTF16toUTF8(str); michael@0: } michael@0: michael@0: ::CoTaskMemFree(str); michael@0: return result; michael@0: } michael@0: michael@0: static const char* michael@0: GetCommonReturnValueName(HRESULT aResult) michael@0: { michael@0: switch (aResult) { michael@0: case S_OK: michael@0: return "S_OK"; michael@0: case E_ABORT: michael@0: return "E_ABORT"; michael@0: case E_ACCESSDENIED: michael@0: return "E_ACCESSDENIED"; michael@0: case E_FAIL: michael@0: return "E_FAIL"; michael@0: case E_HANDLE: michael@0: return "E_HANDLE"; michael@0: case E_INVALIDARG: michael@0: return "E_INVALIDARG"; michael@0: case E_NOINTERFACE: michael@0: return "E_NOINTERFACE"; michael@0: case E_NOTIMPL: michael@0: return "E_NOTIMPL"; michael@0: case E_OUTOFMEMORY: michael@0: return "E_OUTOFMEMORY"; michael@0: case E_POINTER: michael@0: return "E_POINTER"; michael@0: case E_UNEXPECTED: michael@0: return "E_UNEXPECTED"; michael@0: default: michael@0: return SUCCEEDED(aResult) ? "Succeeded" : "Failed"; michael@0: } michael@0: } michael@0: michael@0: static const char* michael@0: GetTextStoreReturnValueName(HRESULT aResult) michael@0: { michael@0: switch (aResult) { michael@0: case TS_E_FORMAT: michael@0: return "TS_E_FORMAT"; michael@0: case TS_E_INVALIDPOINT: michael@0: return "TS_E_INVALIDPOINT"; michael@0: case TS_E_INVALIDPOS: michael@0: return "TS_E_INVALIDPOS"; michael@0: case TS_E_NOINTERFACE: michael@0: return "TS_E_NOINTERFACE"; michael@0: case TS_E_NOLAYOUT: michael@0: return "TS_E_NOLAYOUT"; michael@0: case TS_E_NOLOCK: michael@0: return "TS_E_NOLOCK"; michael@0: case TS_E_NOOBJECT: michael@0: return "TS_E_NOOBJECT"; michael@0: case TS_E_NOSELECTION: michael@0: return "TS_E_NOSELECTION"; michael@0: case TS_E_NOSERVICE: michael@0: return "TS_E_NOSERVICE"; michael@0: case TS_E_READONLY: michael@0: return "TS_E_READONLY"; michael@0: case TS_E_SYNCHRONOUS: michael@0: return "TS_E_SYNCHRONOUS"; michael@0: case TS_S_ASYNC: michael@0: return "TS_S_ASYNC"; michael@0: default: michael@0: return GetCommonReturnValueName(aResult); michael@0: } michael@0: } michael@0: michael@0: static const nsCString michael@0: GetSinkMaskNameStr(DWORD aSinkMask) michael@0: { michael@0: nsAutoCString description; michael@0: if (aSinkMask & TS_AS_TEXT_CHANGE) { michael@0: description.AppendLiteral("TS_AS_TEXT_CHANGE"); michael@0: } michael@0: if (aSinkMask & TS_AS_SEL_CHANGE) { michael@0: HandleSeparator(description); michael@0: description.AppendLiteral("TS_AS_SEL_CHANGE"); michael@0: } michael@0: if (aSinkMask & TS_AS_LAYOUT_CHANGE) { michael@0: HandleSeparator(description); michael@0: description.AppendLiteral("TS_AS_LAYOUT_CHANGE"); michael@0: } michael@0: if (aSinkMask & TS_AS_ATTR_CHANGE) { michael@0: HandleSeparator(description); michael@0: description.AppendLiteral("TS_AS_ATTR_CHANGE"); michael@0: } michael@0: if (aSinkMask & TS_AS_STATUS_CHANGE) { michael@0: HandleSeparator(description); michael@0: description.AppendLiteral("TS_AS_STATUS_CHANGE"); michael@0: } michael@0: if (description.IsEmpty()) { michael@0: description.AppendLiteral("not-specified"); michael@0: } michael@0: return description; michael@0: } michael@0: michael@0: static const char* michael@0: GetActiveSelEndName(TsActiveSelEnd aSelEnd) michael@0: { michael@0: return aSelEnd == TS_AE_NONE ? "TS_AE_NONE" : michael@0: aSelEnd == TS_AE_START ? "TS_AE_START" : michael@0: aSelEnd == TS_AE_END ? "TS_AE_END" : "Unknown"; michael@0: } michael@0: michael@0: static const nsCString michael@0: GetLockFlagNameStr(DWORD aLockFlags) michael@0: { michael@0: nsAutoCString description; michael@0: if ((aLockFlags & TS_LF_READWRITE) == TS_LF_READWRITE) { michael@0: description.AppendLiteral("TS_LF_READWRITE"); michael@0: } else if (aLockFlags & TS_LF_READ) { michael@0: description.AppendLiteral("TS_LF_READ"); michael@0: } michael@0: if (aLockFlags & TS_LF_SYNC) { michael@0: if (!description.IsEmpty()) { michael@0: description.AppendLiteral(" | "); michael@0: } michael@0: description.AppendLiteral("TS_LF_SYNC"); michael@0: } michael@0: if (description.IsEmpty()) { michael@0: description.AppendLiteral("not-specified"); michael@0: } michael@0: return description; michael@0: } michael@0: michael@0: static const char* michael@0: GetTextRunTypeName(TsRunType aRunType) michael@0: { michael@0: switch (aRunType) { michael@0: case TS_RT_PLAIN: michael@0: return "TS_RT_PLAIN"; michael@0: case TS_RT_HIDDEN: michael@0: return "TS_RT_HIDDEN"; michael@0: case TS_RT_OPAQUE: michael@0: return "TS_RT_OPAQUE"; michael@0: default: michael@0: return "Unknown"; michael@0: } michael@0: } michael@0: michael@0: static nsCString michael@0: GetColorName(const TF_DA_COLOR &aColor) michael@0: { michael@0: switch (aColor.type) { michael@0: case TF_CT_NONE: michael@0: return NS_LITERAL_CSTRING("TF_CT_NONE"); michael@0: case TF_CT_SYSCOLOR: michael@0: return nsPrintfCString("TF_CT_SYSCOLOR, nIndex:0x%08X", michael@0: static_cast(aColor.nIndex)); michael@0: case TF_CT_COLORREF: michael@0: return nsPrintfCString("TF_CT_COLORREF, cr:0x%08X", michael@0: static_cast(aColor.cr)); michael@0: break; michael@0: default: michael@0: return nsPrintfCString("Unknown(%08X)", michael@0: static_cast(aColor.type)); michael@0: } michael@0: } michael@0: michael@0: static nsCString michael@0: GetLineStyleName(TF_DA_LINESTYLE aLineStyle) michael@0: { michael@0: switch (aLineStyle) { michael@0: case TF_LS_NONE: michael@0: return NS_LITERAL_CSTRING("TF_LS_NONE"); michael@0: case TF_LS_SOLID: michael@0: return NS_LITERAL_CSTRING("TF_LS_SOLID"); michael@0: case TF_LS_DOT: michael@0: return NS_LITERAL_CSTRING("TF_LS_DOT"); michael@0: case TF_LS_DASH: michael@0: return NS_LITERAL_CSTRING("TF_LS_DASH"); michael@0: case TF_LS_SQUIGGLE: michael@0: return NS_LITERAL_CSTRING("TF_LS_SQUIGGLE"); michael@0: default: { michael@0: return nsPrintfCString("Unknown(%08X)", static_cast(aLineStyle)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static nsCString michael@0: GetClauseAttrName(TF_DA_ATTR_INFO aAttr) michael@0: { michael@0: switch (aAttr) { michael@0: case TF_ATTR_INPUT: michael@0: return NS_LITERAL_CSTRING("TF_ATTR_INPUT"); michael@0: case TF_ATTR_TARGET_CONVERTED: michael@0: return NS_LITERAL_CSTRING("TF_ATTR_TARGET_CONVERTED"); michael@0: case TF_ATTR_CONVERTED: michael@0: return NS_LITERAL_CSTRING("TF_ATTR_CONVERTED"); michael@0: case TF_ATTR_TARGET_NOTCONVERTED: michael@0: return NS_LITERAL_CSTRING("TF_ATTR_TARGET_NOTCONVERTED"); michael@0: case TF_ATTR_INPUT_ERROR: michael@0: return NS_LITERAL_CSTRING("TF_ATTR_INPUT_ERROR"); michael@0: case TF_ATTR_FIXEDCONVERTED: michael@0: return NS_LITERAL_CSTRING("TF_ATTR_FIXEDCONVERTED"); michael@0: case TF_ATTR_OTHER: michael@0: return NS_LITERAL_CSTRING("TF_ATTR_OTHER"); michael@0: default: { michael@0: return nsPrintfCString("Unknown(%08X)", static_cast(aAttr)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static nsCString michael@0: GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE &aDispAttr) michael@0: { michael@0: nsAutoCString str; michael@0: str = "crText:{ "; michael@0: str += GetColorName(aDispAttr.crText); michael@0: str += " }, crBk:{ "; michael@0: str += GetColorName(aDispAttr.crBk); michael@0: str += " }, lsStyle: "; michael@0: str += GetLineStyleName(aDispAttr.lsStyle); michael@0: str += ", fBoldLine: "; michael@0: str += GetBoolName(aDispAttr.fBoldLine); michael@0: str += ", crLine:{ "; michael@0: str += GetColorName(aDispAttr.crLine); michael@0: str += " }, bAttr: "; michael@0: str += GetClauseAttrName(aDispAttr.bAttr); michael@0: return str; michael@0: } michael@0: michael@0: #endif // #ifdef PR_LOGGING michael@0: michael@0: nsTextStore::nsTextStore() michael@0: : mContent(mComposition, mSelection) michael@0: { michael@0: mRefCnt = 1; michael@0: mEditCookie = 0; michael@0: mIPProfileCookie = TF_INVALID_COOKIE; michael@0: mLangProfileCookie = TF_INVALID_COOKIE; michael@0: mSinkMask = 0; michael@0: mLock = 0; michael@0: mLockQueued = 0; michael@0: mInputScopeDetected = false; michael@0: mInputScopeRequested = false; michael@0: mIsRecordingActionsWithoutLock = false; michael@0: mPendingOnSelectionChange = false; michael@0: mPendingOnLayoutChange = false; michael@0: mNativeCaretIsCreated = false; michael@0: mIsIMM_IME = false; michael@0: mOnActivatedCalled = false; michael@0: // We hope that 5 or more actions don't occur at once. michael@0: mPendingActions.SetCapacity(5); michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::nsTestStore() SUCCEEDED", this)); michael@0: } michael@0: michael@0: bool michael@0: nsTextStore::Init(ITfThreadMgr* aThreadMgr) michael@0: { michael@0: nsRefPtr source; michael@0: HRESULT hr = michael@0: aThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source)); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::Init() FAILED to get ITfSource instance " michael@0: "(0x%08X)", this, hr)); michael@0: return false; michael@0: } michael@0: michael@0: // On Vista or later, Windows let us know activate IME changed only with michael@0: // ITfInputProcessorProfileActivationSink. However, it's not available on XP. michael@0: // On XP, ITfActiveLanguageProfileNotifySink is available for it. michael@0: // NOTE: Each OnActivated() should be called when TSF becomes available. michael@0: if (IsVistaOrLater()) { michael@0: hr = source->AdviseSink(IID_ITfInputProcessorProfileActivationSink, michael@0: static_cast(this), michael@0: &mIPProfileCookie); michael@0: if (FAILED(hr) || mIPProfileCookie == TF_INVALID_COOKIE) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::Init() FAILED to install " michael@0: "ITfInputProcessorProfileActivationSink (0x%08X)", this, hr)); michael@0: return false; michael@0: } michael@0: } else { michael@0: hr = source->AdviseSink(IID_ITfActiveLanguageProfileNotifySink, michael@0: static_cast(this), michael@0: &mLangProfileCookie); michael@0: if (FAILED(hr) || mLangProfileCookie == TF_INVALID_COOKIE) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::Init() FAILED to install " michael@0: "ITfActiveLanguageProfileNotifySink (0x%08X)", this, hr)); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::Init(), " michael@0: "mIPProfileCookie=0x%08X, mLangProfileCookie=0x%08X", michael@0: this, mIPProfileCookie, mLangProfileCookie)); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsTextStore::~nsTextStore() michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore instance is destroyed, " michael@0: "mWidget=0x%p, mDocumentMgr=0x%p, mContext=0x%p, mIPProfileCookie=0x%08X, " michael@0: "mLangProfileCookie=0x%08X", michael@0: this, mWidget.get(), mDocumentMgr.get(), mContext.get(), michael@0: mIPProfileCookie, mLangProfileCookie)); michael@0: michael@0: if (mIPProfileCookie != TF_INVALID_COOKIE) { michael@0: nsRefPtr source; michael@0: HRESULT hr = michael@0: sTsfThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source)); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p ~nsTextStore FAILED to get ITfSource instance " michael@0: "(0x%08X)", this, hr)); michael@0: } else { michael@0: hr = source->UnadviseSink(mIPProfileCookie); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p ~nsTextStore FAILED to uninstall " michael@0: "ITfInputProcessorProfileActivationSink (0x%08X)", michael@0: this, hr)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mLangProfileCookie != TF_INVALID_COOKIE) { michael@0: nsRefPtr source; michael@0: HRESULT hr = michael@0: sTsfThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source)); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p ~nsTextStore FAILED to get ITfSource instance " michael@0: "(0x%08X)", this, hr)); michael@0: } else { michael@0: hr = source->UnadviseSink(mLangProfileCookie); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p ~nsTextStore FAILED to uninstall " michael@0: "ITfActiveLanguageProfileNotifySink (0x%08X)", michael@0: this, hr)); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsTextStore::Create(nsWindowBase* aWidget) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::Create(aWidget=0x%p)", michael@0: this, aWidget)); michael@0: michael@0: EnsureInitActiveTIPKeyboard(); michael@0: michael@0: if (mDocumentMgr) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::Create() FAILED due to already initialized", michael@0: this)); michael@0: return false; michael@0: } michael@0: michael@0: // Create document manager michael@0: HRESULT hr = sTsfThreadMgr->CreateDocumentMgr( michael@0: getter_AddRefs(mDocumentMgr)); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::Create() FAILED to create DocumentMgr " michael@0: "(0x%08X)", this, hr)); michael@0: return false; michael@0: } michael@0: mWidget = aWidget; michael@0: michael@0: // Create context and add it to document manager michael@0: hr = mDocumentMgr->CreateContext(sTsfClientId, 0, michael@0: static_cast(this), michael@0: getter_AddRefs(mContext), &mEditCookie); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::Create() FAILED to create the context " michael@0: "(0x%08X)", this, hr)); michael@0: mDocumentMgr = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: hr = mDocumentMgr->Push(mContext); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::Create() FAILED to push the context (0x%08X)", michael@0: this, hr)); michael@0: // XXX Why don't we use NS_IF_RELEASE() here?? michael@0: mContext = nullptr; michael@0: mDocumentMgr = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::Create() succeeded: " michael@0: "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08X", michael@0: this, mDocumentMgr.get(), mContext.get(), mEditCookie)); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsTextStore::Destroy(void) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::Destroy(), mComposition.IsComposing()=%s", michael@0: this, GetBoolName(mComposition.IsComposing()))); michael@0: michael@0: // If there is composition, TSF keeps the composition even after the text michael@0: // store destroyed. So, we should clear the composition here. michael@0: if (mComposition.IsComposing()) { michael@0: NS_WARNING("Composition is still alive at destroying the text store"); michael@0: CommitCompositionInternal(false); michael@0: } michael@0: michael@0: mContent.Clear(); michael@0: mSelection.MarkDirty(); michael@0: michael@0: if (mWidget) { michael@0: // When blurred, Tablet Input Panel posts "blur" messages michael@0: // and try to insert text when the message is retrieved later. michael@0: // But by that time the text store is already destroyed, michael@0: // so try to get the message early michael@0: MSG msg; michael@0: if (WinUtils::PeekMessage(&msg, mWidget->GetWindowHandle(), michael@0: sFlushTIPInputMessage, sFlushTIPInputMessage, michael@0: PM_REMOVE)) { michael@0: ::DispatchMessageW(&msg); michael@0: } michael@0: } michael@0: mContext = nullptr; michael@0: if (mDocumentMgr) { michael@0: mDocumentMgr->Pop(TF_POPF_ALL); michael@0: mDocumentMgr = nullptr; michael@0: } michael@0: mSink = nullptr; michael@0: mWidget = nullptr; michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::Destroy() succeeded", this)); michael@0: return true; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::QueryInterface(REFIID riid, michael@0: void** ppv) michael@0: { michael@0: *ppv=nullptr; michael@0: if ( (IID_IUnknown == riid) || (IID_ITextStoreACP == riid) ) { michael@0: *ppv = static_cast(this); michael@0: } else if (IID_ITfContextOwnerCompositionSink == riid) { michael@0: *ppv = static_cast(this); michael@0: } else if (IID_ITfActiveLanguageProfileNotifySink == riid) { michael@0: *ppv = static_cast(this); michael@0: } else if (IID_ITfInputProcessorProfileActivationSink == riid) { michael@0: *ppv = static_cast(this); michael@0: } michael@0: if (*ppv) { michael@0: AddRef(); michael@0: return S_OK; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::QueryInterface() FAILED, riid=%s", michael@0: this, GetRIIDNameStr(riid).get())); michael@0: return E_NOINTERFACE; michael@0: } michael@0: michael@0: STDMETHODIMP_(ULONG) nsTextStore::AddRef() michael@0: { michael@0: return ++mRefCnt; michael@0: } michael@0: michael@0: STDMETHODIMP_(ULONG) nsTextStore::Release() michael@0: { michael@0: --mRefCnt; michael@0: if (0 != mRefCnt) michael@0: return mRefCnt; michael@0: delete this; michael@0: return 0; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::AdviseSink(REFIID riid, michael@0: IUnknown *punk, michael@0: DWORD dwMask) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), " michael@0: "mSink=0x%p, mSinkMask=%s", michael@0: this, GetRIIDNameStr(riid).get(), punk, GetSinkMaskNameStr(dwMask).get(), michael@0: mSink.get(), GetSinkMaskNameStr(mSinkMask).get())); michael@0: michael@0: if (!punk) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::AdviseSink() FAILED due to the null punk", michael@0: this)); michael@0: return E_UNEXPECTED; michael@0: } michael@0: michael@0: if (IID_ITextStoreACPSink != riid) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::AdviseSink() FAILED due to " michael@0: "unsupported interface", this)); michael@0: return E_INVALIDARG; // means unsupported interface. michael@0: } michael@0: michael@0: if (!mSink) { michael@0: // Install sink michael@0: punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink)); michael@0: if (!mSink) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::AdviseSink() FAILED due to " michael@0: "punk not having the interface", this)); michael@0: return E_UNEXPECTED; michael@0: } michael@0: } else { michael@0: // If sink is already installed we check to see if they are the same michael@0: // Get IUnknown from both sides for comparison michael@0: nsRefPtr comparison1, comparison2; michael@0: punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1)); michael@0: mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2)); michael@0: if (comparison1 != comparison2) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::AdviseSink() FAILED due to " michael@0: "the sink being different from the stored sink", this)); michael@0: return CONNECT_E_ADVISELIMIT; michael@0: } michael@0: } michael@0: // Update mask either for a new sink or an existing sink michael@0: mSinkMask = dwMask; michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::UnadviseSink(IUnknown *punk) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::UnadviseSink(punk=0x%p), mSink=0x%p", michael@0: this, punk, mSink.get())); michael@0: michael@0: if (!punk) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::UnadviseSink() FAILED due to the null punk", michael@0: this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: if (!mSink) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::UnadviseSink() FAILED due to " michael@0: "any sink not stored", this)); michael@0: return CONNECT_E_NOCONNECTION; michael@0: } michael@0: // Get IUnknown from both sides for comparison michael@0: nsRefPtr comparison1, comparison2; michael@0: punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1)); michael@0: mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2)); michael@0: // Unadvise only if sinks are the same michael@0: if (comparison1 != comparison2) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::UnadviseSink() FAILED due to " michael@0: "the sink being different from the stored sink", this)); michael@0: return CONNECT_E_NOCONNECTION; michael@0: } michael@0: mSink = nullptr; michael@0: mSinkMask = 0; michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::RequestLock(DWORD dwLockFlags, michael@0: HRESULT *phrSession) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RequestLock(dwLockFlags=%s, phrSession=0x%p), " michael@0: "mLock=%s", this, GetLockFlagNameStr(dwLockFlags).get(), phrSession, michael@0: GetLockFlagNameStr(mLock).get())); michael@0: michael@0: if (!mSink) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RequestLock() FAILED due to " michael@0: "any sink not stored", this)); michael@0: return E_FAIL; michael@0: } michael@0: if (!phrSession) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RequestLock() FAILED due to " michael@0: "null phrSession", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: if (!mLock) { michael@0: // put on lock michael@0: mLock = dwLockFlags & (~TS_LF_SYNC); michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p Locking (%s) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" michael@0: ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", michael@0: this, GetLockFlagNameStr(mLock).get())); michael@0: *phrSession = mSink->OnLockGranted(mLock); michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" michael@0: "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", michael@0: this, GetLockFlagNameStr(mLock).get())); michael@0: DidLockGranted(); michael@0: while (mLockQueued) { michael@0: mLock = mLockQueued; michael@0: mLockQueued = 0; michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p Locking for the request in the queue (%s) >>>>>>>>>>>>>>" michael@0: ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", michael@0: this, GetLockFlagNameStr(mLock).get())); michael@0: mSink->OnLockGranted(mLock); michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" michael@0: "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", michael@0: this, GetLockFlagNameStr(mLock).get())); michael@0: DidLockGranted(); michael@0: } michael@0: michael@0: // The document is now completely unlocked. michael@0: mLock = 0; michael@0: michael@0: if (mPendingOnLayoutChange) { michael@0: mPendingOnLayoutChange = false; michael@0: if (mSink) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RequestLock(), " michael@0: "calling ITextStoreACPSink::OnLayoutChange()...", this)); michael@0: mSink->OnLayoutChange(TS_LC_CHANGE, TEXTSTORE_DEFAULT_VIEW); michael@0: } michael@0: // The layout change caused by composition string change should cause michael@0: // calling ITfContextOwnerServices::OnLayoutChange() too. michael@0: if (mContext) { michael@0: nsRefPtr service; michael@0: mContext->QueryInterface(IID_ITfContextOwnerServices, michael@0: getter_AddRefs(service)); michael@0: if (service) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RequestLock(), " michael@0: "calling ITfContextOwnerServices::OnLayoutChange()...", michael@0: this)); michael@0: service->OnLayoutChange(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mPendingOnSelectionChange) { michael@0: mPendingOnSelectionChange = false; michael@0: if (mSink) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RequestLock(), " michael@0: "calling ITextStoreACPSink::OnSelectionChange()...", this)); michael@0: mSink->OnSelectionChange(); michael@0: } michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RequestLock() succeeded: *phrSession=%s", michael@0: this, GetTextStoreReturnValueName(*phrSession))); michael@0: return S_OK; michael@0: } michael@0: michael@0: // only time when reentrant lock is allowed is when caller holds a michael@0: // read-only lock and is requesting an async write lock michael@0: if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags) && michael@0: !(dwLockFlags & TS_LF_SYNC)) { michael@0: *phrSession = TS_S_ASYNC; michael@0: mLockQueued = dwLockFlags & (~TS_LF_SYNC); michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RequestLock() stores the request in the " michael@0: "queue, *phrSession=TS_S_ASYNC", this)); michael@0: return S_OK; michael@0: } michael@0: michael@0: // no more locks allowed michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RequestLock() didn't allow to lock, " michael@0: "*phrSession=TS_E_SYNCHRONOUS", this)); michael@0: *phrSession = TS_E_SYNCHRONOUS; michael@0: return E_FAIL; michael@0: } michael@0: michael@0: void michael@0: nsTextStore::DidLockGranted() michael@0: { michael@0: if (mNativeCaretIsCreated) { michael@0: ::DestroyCaret(); michael@0: mNativeCaretIsCreated = false; michael@0: } michael@0: if (IsReadWriteLocked()) { michael@0: FlushPendingActions(); michael@0: } michael@0: michael@0: // If the widget has gone, we don't need to notify anything. michael@0: if (!mWidget || mWidget->Destroyed()) { michael@0: mPendingOnSelectionChange = false; michael@0: mPendingOnLayoutChange = false; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTextStore::FlushPendingActions() michael@0: { michael@0: if (!mWidget || mWidget->Destroyed()) { michael@0: mPendingActions.Clear(); michael@0: mContent.Clear(); michael@0: mPendingOnSelectionChange = false; michael@0: mPendingOnLayoutChange = false; michael@0: return; michael@0: } michael@0: michael@0: mContent.Clear(); michael@0: michael@0: nsRefPtr kungFuDeathGrip(mWidget); michael@0: for (uint32_t i = 0; i < mPendingActions.Length(); i++) { michael@0: PendingAction& action = mPendingActions[i]; michael@0: switch (action.mType) { michael@0: case PendingAction::COMPOSITION_START: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::FlushPendingActions() " michael@0: "flushing COMPOSITION_START={ mSelectionStart=%d, " michael@0: "mSelectionLength=%d }", michael@0: this, action.mSelectionStart, action.mSelectionLength)); michael@0: michael@0: MOZ_ASSERT(mComposition.mLastData.IsEmpty()); michael@0: michael@0: // Select composition range so the new composition replaces the range michael@0: WidgetSelectionEvent selectionSet(true, NS_SELECTION_SET, mWidget); michael@0: mWidget->InitEvent(selectionSet); michael@0: selectionSet.mOffset = static_cast(action.mSelectionStart); michael@0: selectionSet.mLength = static_cast(action.mSelectionLength); michael@0: selectionSet.mReversed = false; michael@0: mWidget->DispatchWindowEvent(&selectionSet); michael@0: if (!selectionSet.mSucceeded) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::FlushPendingActions() " michael@0: "FAILED due to NS_SELECTION_SET failure", this)); michael@0: break; michael@0: } michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::FlushPendingActions() " michael@0: "dispatching compositionstart event...", this)); michael@0: WidgetCompositionEvent compositionStart(true, NS_COMPOSITION_START, michael@0: mWidget); michael@0: mWidget->InitEvent(compositionStart); michael@0: mWidget->DispatchWindowEvent(&compositionStart); michael@0: if (!mWidget || mWidget->Destroyed()) { michael@0: break; michael@0: } michael@0: break; michael@0: } michael@0: case PendingAction::COMPOSITION_UPDATE: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::FlushPendingActions() " michael@0: "flushing COMPOSITION_UPDATE={ mData=\"%s\", " michael@0: "mRanges=0x%p, mRanges->Length()=%d }", michael@0: this, NS_ConvertUTF16toUTF8(action.mData).get(), action.mRanges.get(), michael@0: action.mRanges ? action.mRanges->Length() : 0)); michael@0: michael@0: if (!action.mRanges) { michael@0: NS_WARNING("How does this case occur?"); michael@0: action.mRanges = new TextRangeArray(); michael@0: } michael@0: michael@0: // Adjust offsets in the ranges for XP linefeed character (only \n). michael@0: // XXX Following code is the safest approach. However, it wastes michael@0: // a little performance. For ensuring the clauses do not michael@0: // overlap each other, we should redesign TextRange later. michael@0: for (uint32_t i = 0; i < action.mRanges->Length(); ++i) { michael@0: TextRange& range = action.mRanges->ElementAt(i); michael@0: TextRange nativeRange = range; michael@0: if (nativeRange.mStartOffset > 0) { michael@0: nsAutoString preText( michael@0: Substring(action.mData, 0, nativeRange.mStartOffset)); michael@0: preText.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), michael@0: NS_LITERAL_STRING("\n")); michael@0: range.mStartOffset = preText.Length(); michael@0: } michael@0: if (nativeRange.Length() == 0) { michael@0: range.mEndOffset = range.mStartOffset; michael@0: } else { michael@0: nsAutoString clause( michael@0: Substring(action.mData, michael@0: nativeRange.mStartOffset, nativeRange.Length())); michael@0: clause.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), michael@0: NS_LITERAL_STRING("\n")); michael@0: range.mEndOffset = range.mStartOffset + clause.Length(); michael@0: } michael@0: } michael@0: michael@0: action.mData.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), michael@0: NS_LITERAL_STRING("\n")); michael@0: michael@0: if (action.mData != mComposition.mLastData) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::FlushPendingActions(), " michael@0: "dispatching compositionupdate event...", this)); michael@0: WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE, michael@0: mWidget); michael@0: mWidget->InitEvent(compositionUpdate); michael@0: compositionUpdate.data = action.mData; michael@0: mComposition.mLastData = compositionUpdate.data; michael@0: mWidget->DispatchWindowEvent(&compositionUpdate); michael@0: if (!mWidget || mWidget->Destroyed()) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSERT(action.mData == mComposition.mLastData); michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::FlushPendingActions(), " michael@0: "dispatching text event...", this)); michael@0: WidgetTextEvent textEvent(true, NS_TEXT_TEXT, mWidget); michael@0: mWidget->InitEvent(textEvent); michael@0: textEvent.theText = mComposition.mLastData; michael@0: if (action.mRanges->IsEmpty()) { michael@0: TextRange wholeRange; michael@0: wholeRange.mStartOffset = 0; michael@0: wholeRange.mEndOffset = textEvent.theText.Length(); michael@0: wholeRange.mRangeType = NS_TEXTRANGE_RAWINPUT; michael@0: action.mRanges->AppendElement(wholeRange); michael@0: } michael@0: textEvent.mRanges = action.mRanges; michael@0: mWidget->DispatchWindowEvent(&textEvent); michael@0: // Be aware, the mWidget might already have been destroyed. michael@0: break; michael@0: } michael@0: case PendingAction::COMPOSITION_END: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::FlushPendingActions() " michael@0: "flushing COMPOSITION_END={ mData=\"%s\" }", michael@0: this, NS_ConvertUTF16toUTF8(action.mData).get())); michael@0: michael@0: action.mData.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), michael@0: NS_LITERAL_STRING("\n")); michael@0: if (action.mData != mComposition.mLastData) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::FlushPendingActions(), " michael@0: "dispatching compositionupdate event...", this)); michael@0: WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE, michael@0: mWidget); michael@0: mWidget->InitEvent(compositionUpdate); michael@0: compositionUpdate.data = action.mData; michael@0: mComposition.mLastData = compositionUpdate.data; michael@0: mWidget->DispatchWindowEvent(&compositionUpdate); michael@0: if (!mWidget || mWidget->Destroyed()) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSERT(action.mData == mComposition.mLastData); michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::FlushPendingActions(), " michael@0: "dispatching text event...", this)); michael@0: WidgetTextEvent textEvent(true, NS_TEXT_TEXT, mWidget); michael@0: mWidget->InitEvent(textEvent); michael@0: textEvent.theText = mComposition.mLastData; michael@0: mWidget->DispatchWindowEvent(&textEvent); michael@0: if (!mWidget || mWidget->Destroyed()) { michael@0: break; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::FlushPendingActions(), " michael@0: "dispatching compositionend event...", this)); michael@0: WidgetCompositionEvent compositionEnd(true, NS_COMPOSITION_END, michael@0: mWidget); michael@0: compositionEnd.data = mComposition.mLastData; michael@0: mWidget->InitEvent(compositionEnd); michael@0: mWidget->DispatchWindowEvent(&compositionEnd); michael@0: if (!mWidget || mWidget->Destroyed()) { michael@0: break; michael@0: } michael@0: mComposition.mLastData.Truncate(); michael@0: break; michael@0: } michael@0: case PendingAction::SELECTION_SET: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::FlushPendingActions() " michael@0: "flushing SELECTION_SET={ mSelectionStart=%d, " michael@0: "mSelectionLength=%d, mSelectionReversed=%s }", michael@0: this, action.mSelectionStart, action.mSelectionLength, michael@0: GetBoolName(action.mSelectionReversed))); michael@0: michael@0: WidgetSelectionEvent selectionSet(true, NS_SELECTION_SET, mWidget); michael@0: selectionSet.mOffset = michael@0: static_cast(action.mSelectionStart); michael@0: selectionSet.mLength = michael@0: static_cast(action.mSelectionLength); michael@0: selectionSet.mReversed = action.mSelectionReversed; michael@0: break; michael@0: } michael@0: default: michael@0: MOZ_CRASH("unexpected action type"); michael@0: } michael@0: michael@0: if (mWidget && !mWidget->Destroyed()) { michael@0: continue; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::FlushPendingActions(), " michael@0: "qutting since the mWidget has gone", this)); michael@0: break; michael@0: } michael@0: mPendingActions.Clear(); michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::GetStatus(TS_STATUS *pdcs) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetStatus(pdcs=0x%p)", this, pdcs)); michael@0: michael@0: if (!pdcs) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetStatus() FAILED due to null pdcs", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: pdcs->dwDynamicFlags = 0; michael@0: // we use a "flat" text model for TSF support so no hidden text michael@0: pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT; michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::QueryInsert(LONG acpTestStart, michael@0: LONG acpTestEnd, michael@0: ULONG cch, michael@0: LONG *pacpResultStart, michael@0: LONG *pacpResultEnd) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::QueryInsert(acpTestStart=%ld, " michael@0: "acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)", michael@0: this, acpTestStart, acpTestEnd, cch, acpTestStart, acpTestEnd)); michael@0: michael@0: if (!pacpResultStart || !pacpResultEnd) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::QueryInsert() FAILED due to " michael@0: "the null argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: if (acpTestStart < 0 || acpTestStart > acpTestEnd) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::QueryInsert() FAILED due to " michael@0: "wrong argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: // XXX need to adjust to cluster boundary michael@0: // Assume we are given good offsets for now michael@0: *pacpResultStart = acpTestStart; michael@0: *pacpResultEnd = acpTestStart + cch; michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::QueryInsert() succeeded: " michael@0: "*pacpResultStart=%ld, *pacpResultEnd=%ld)", michael@0: this, *pacpResultStart, *pacpResultEnd)); michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::GetSelection(ULONG ulIndex, michael@0: ULONG ulCount, michael@0: TS_SELECTION_ACP *pSelection, michael@0: ULONG *pcFetched) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetSelection(ulIndex=%lu, ulCount=%lu, " michael@0: "pSelection=0x%p, pcFetched=0x%p)", michael@0: this, ulIndex, ulCount, pSelection, pcFetched)); michael@0: michael@0: if (!IsReadLocked()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetSelection() FAILED due to not locked", michael@0: this)); michael@0: return TS_E_NOLOCK; michael@0: } michael@0: if (!ulCount || !pSelection || !pcFetched) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetSelection() FAILED due to " michael@0: "null argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: *pcFetched = 0; michael@0: michael@0: if (ulIndex != static_cast(TS_DEFAULT_SELECTION) && michael@0: ulIndex != 0) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetSelection() FAILED due to " michael@0: "unsupported selection", this)); michael@0: return TS_E_NOSELECTION; michael@0: } michael@0: michael@0: Selection& currentSel = CurrentSelection(); michael@0: if (currentSel.IsDirty()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetSelection() FAILED due to " michael@0: "CurrentSelection() failure", this)); michael@0: return E_FAIL; michael@0: } michael@0: *pSelection = currentSel.ACP(); michael@0: *pcFetched = 1; michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetSelection() succeeded", this)); michael@0: return S_OK; michael@0: } michael@0: michael@0: nsTextStore::Content& michael@0: nsTextStore::CurrentContent() michael@0: { michael@0: Selection& currentSel = CurrentSelection(); michael@0: if (currentSel.IsDirty()) { michael@0: mContent.Clear(); michael@0: return mContent; michael@0: } michael@0: michael@0: if (!mContent.IsInitialized()) { michael@0: MOZ_ASSERT(mWidget && !mWidget->Destroyed()); michael@0: michael@0: WidgetQueryContentEvent queryText(true, NS_QUERY_TEXT_CONTENT, mWidget); michael@0: queryText.InitForQueryTextContent(0, UINT32_MAX); michael@0: mWidget->InitEvent(queryText); michael@0: mWidget->DispatchWindowEvent(&queryText); michael@0: NS_ENSURE_TRUE(queryText.mSucceeded, mContent); michael@0: michael@0: mContent.Init(queryText.mReply.mString); michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::CurrentContent(): " michael@0: "mContent={ mText.Length()=%d }", michael@0: this, mContent.Text().Length())); michael@0: michael@0: return mContent; michael@0: } michael@0: michael@0: nsTextStore::Selection& michael@0: nsTextStore::CurrentSelection() michael@0: { michael@0: if (mSelection.IsDirty()) { michael@0: // If the window has never been available, we should crash since working michael@0: // with broken values may make TIP confused. michael@0: if (!mWidget || mWidget->Destroyed()) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: WidgetQueryContentEvent querySelection(true, NS_QUERY_SELECTED_TEXT, michael@0: mWidget); michael@0: mWidget->InitEvent(querySelection); michael@0: mWidget->DispatchWindowEvent(&querySelection); michael@0: NS_ENSURE_TRUE(querySelection.mSucceeded, mSelection); michael@0: michael@0: mSelection.SetSelection(querySelection.mReply.mOffset, michael@0: querySelection.mReply.mString.Length(), michael@0: querySelection.mReply.mReversed); michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::CurrentSelection(): " michael@0: "acpStart=%d, acpEnd=%d (length=%d), reverted=%s", michael@0: this, mSelection.StartOffset(), mSelection.EndOffset(), michael@0: mSelection.Length(), michael@0: GetBoolName(mSelection.IsReversed()))); michael@0: michael@0: return mSelection; michael@0: } michael@0: michael@0: static HRESULT michael@0: GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength) michael@0: { michael@0: nsRefPtr rangeACP; michael@0: aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP)); michael@0: NS_ENSURE_TRUE(rangeACP, E_FAIL); michael@0: return rangeACP->GetExtent(aStart, aLength); michael@0: } michael@0: michael@0: static uint32_t michael@0: GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE &aDisplayAttr) michael@0: { michael@0: uint32_t result; michael@0: switch (aDisplayAttr.bAttr) { michael@0: case TF_ATTR_TARGET_CONVERTED: michael@0: result = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT; michael@0: break; michael@0: case TF_ATTR_CONVERTED: michael@0: result = NS_TEXTRANGE_CONVERTEDTEXT; michael@0: break; michael@0: case TF_ATTR_TARGET_NOTCONVERTED: michael@0: result = NS_TEXTRANGE_SELECTEDRAWTEXT; michael@0: break; michael@0: default: michael@0: result = NS_TEXTRANGE_RAWINPUT; michael@0: break; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: HRESULT michael@0: nsTextStore::GetDisplayAttribute(ITfProperty* aAttrProperty, michael@0: ITfRange* aRange, michael@0: TF_DISPLAYATTRIBUTE* aResult) michael@0: { michael@0: NS_ENSURE_TRUE(aAttrProperty, E_FAIL); michael@0: NS_ENSURE_TRUE(aRange, E_FAIL); michael@0: NS_ENSURE_TRUE(aResult, E_FAIL); michael@0: michael@0: HRESULT hr; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(sTextStoreLog, PR_LOG_DEBUG)) { michael@0: LONG start = 0, length = 0; michael@0: hr = GetRangeExtent(aRange, &start, &length); michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::GetDisplayAttribute(): " michael@0: "GetDisplayAttribute range=%ld-%ld (hr=%s)", michael@0: this, start - mComposition.mStart, michael@0: start - mComposition.mStart + length, michael@0: GetCommonReturnValueName(hr))); michael@0: } michael@0: #endif michael@0: michael@0: VARIANT propValue; michael@0: ::VariantInit(&propValue); michael@0: hr = aAttrProperty->GetValue(TfEditCookie(mEditCookie), aRange, &propValue); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to " michael@0: "ITfProperty::GetValue() failed", this)); michael@0: return hr; michael@0: } michael@0: if (VT_I4 != propValue.vt) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to " michael@0: "ITfProperty::GetValue() returns non-VT_I4 value", this)); michael@0: ::VariantClear(&propValue); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(sCategoryMgr, E_FAIL); michael@0: GUID guid; michael@0: hr = sCategoryMgr->GetGUID(DWORD(propValue.lVal), &guid); michael@0: ::VariantClear(&propValue); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to " michael@0: "ITfCategoryMgr::GetGUID() failed", this)); michael@0: return hr; michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(sDisplayAttrMgr, E_FAIL); michael@0: nsRefPtr info; michael@0: hr = sDisplayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info), michael@0: nullptr); michael@0: if (FAILED(hr) || !info) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to " michael@0: "ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed", this)); michael@0: return hr; michael@0: } michael@0: michael@0: hr = info->GetAttributeInfo(aResult); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to " michael@0: "ITfDisplayAttributeInfo::GetAttributeInfo() failed", this)); michael@0: return hr; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::GetDisplayAttribute() succeeded: " michael@0: "Result={ %s }", this, GetDisplayAttrStr(*aResult).get())); michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT michael@0: nsTextStore::RestartCompositionIfNecessary(ITfRange* aRangeNew) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::RestartCompositionIfNecessary(" michael@0: "aRangeNew=0x%p), mComposition.mView=0x%p", michael@0: this, aRangeNew, mComposition.mView.get())); michael@0: michael@0: if (!mComposition.IsComposing()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RestartCompositionIfNecessary() FAILED " michael@0: "due to no composition view", this)); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: HRESULT hr; michael@0: nsRefPtr pComposition(mComposition.mView); michael@0: nsRefPtr composingRange(aRangeNew); michael@0: if (!composingRange) { michael@0: hr = pComposition->GetRange(getter_AddRefs(composingRange)); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RestartCompositionIfNecessary() FAILED " michael@0: "due to pComposition->GetRange() failure", this)); michael@0: return hr; michael@0: } michael@0: } michael@0: michael@0: // Get starting offset of the composition michael@0: LONG compStart = 0, compLength = 0; michael@0: hr = GetRangeExtent(composingRange, &compStart, &compLength); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RestartCompositionIfNecessary() FAILED " michael@0: "due to GetRangeExtent() failure", this)); michael@0: return hr; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::RestartCompositionIfNecessary(), " michael@0: "range=%ld-%ld, mComposition={ mStart=%ld, mString.Length()=%lu }", michael@0: this, compStart, compStart + compLength, mComposition.mStart, michael@0: mComposition.mString.Length())); michael@0: michael@0: if (mComposition.mStart != compStart || michael@0: mComposition.mString.Length() != (ULONG)compLength) { michael@0: // If the queried composition length is different from the length michael@0: // of our composition string, OnUpdateComposition is being called michael@0: // because a part of the original composition was committed. michael@0: // Reflect that by committing existing composition and starting michael@0: // a new one. RecordCompositionEndAction() followed by michael@0: // RecordCompositionStartAction() will accomplish this automagically. michael@0: RecordCompositionEndAction(); michael@0: RecordCompositionStartAction(pComposition, composingRange, true); michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::RestartCompositionIfNecessary() succeeded", michael@0: this)); michael@0: return S_OK; michael@0: } michael@0: michael@0: static bool michael@0: GetColor(const TF_DA_COLOR &aTSFColor, nscolor &aResult) michael@0: { michael@0: switch (aTSFColor.type) { michael@0: case TF_CT_SYSCOLOR: { michael@0: DWORD sysColor = ::GetSysColor(aTSFColor.nIndex); michael@0: aResult = NS_RGB(GetRValue(sysColor), GetGValue(sysColor), michael@0: GetBValue(sysColor)); michael@0: return true; michael@0: } michael@0: case TF_CT_COLORREF: michael@0: aResult = NS_RGB(GetRValue(aTSFColor.cr), GetGValue(aTSFColor.cr), michael@0: GetBValue(aTSFColor.cr)); michael@0: return true; michael@0: case TF_CT_NONE: michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle, uint8_t &aTextRangeLineStyle) michael@0: { michael@0: switch (aTSFLineStyle) { michael@0: case TF_LS_NONE: michael@0: aTextRangeLineStyle = TextRangeStyle::LINESTYLE_NONE; michael@0: return true; michael@0: case TF_LS_SOLID: michael@0: aTextRangeLineStyle = TextRangeStyle::LINESTYLE_SOLID; michael@0: return true; michael@0: case TF_LS_DOT: michael@0: aTextRangeLineStyle = TextRangeStyle::LINESTYLE_DOTTED; michael@0: return true; michael@0: case TF_LS_DASH: michael@0: aTextRangeLineStyle = TextRangeStyle::LINESTYLE_DASHED; michael@0: return true; michael@0: case TF_LS_SQUIGGLE: michael@0: aTextRangeLineStyle = TextRangeStyle::LINESTYLE_WAVY; michael@0: return true; michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: HRESULT michael@0: nsTextStore::RecordCompositionUpdateAction() michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionUpdateAction(), " michael@0: "mComposition={ mView=0x%p, mString=\"%s\" }", michael@0: this, mComposition.mView.get(), michael@0: NS_ConvertUTF16toUTF8(mComposition.mString).get())); michael@0: michael@0: if (!mComposition.IsComposing()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionUpdateAction() FAILED " michael@0: "due to no composition view", this)); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: // Getting display attributes is *really* complicated! michael@0: // We first get the context and the property objects to query for michael@0: // attributes, but since a big range can have a variety of values for michael@0: // the attribute, we have to find out all the ranges that have distinct michael@0: // attribute values. Then we query for what the value represents through michael@0: // the display attribute manager and translate that to TextRange to be michael@0: // sent in NS_TEXT_TEXT michael@0: michael@0: nsRefPtr attrPropetry; michael@0: HRESULT hr = mContext->GetProperty(GUID_PROP_ATTRIBUTE, michael@0: getter_AddRefs(attrPropetry)); michael@0: if (FAILED(hr) || !attrPropetry) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionUpdateAction() FAILED " michael@0: "due to mContext->GetProperty() failure", this)); michael@0: return FAILED(hr) ? hr : E_FAIL; michael@0: } michael@0: michael@0: nsRefPtr composingRange; michael@0: hr = mComposition.mView->GetRange(getter_AddRefs(composingRange)); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionUpdateAction() " michael@0: "FAILED due to mComposition.mView->GetRange() failure", this)); michael@0: return hr; michael@0: } michael@0: michael@0: nsRefPtr enumRanges; michael@0: hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie), michael@0: getter_AddRefs(enumRanges), composingRange); michael@0: if (FAILED(hr) || !enumRanges) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionUpdateAction() FAILED " michael@0: "due to attrPropetry->EnumRanges() failure", this)); michael@0: return FAILED(hr) ? hr : E_FAIL; michael@0: } michael@0: michael@0: // First, put the log of content and selection here. michael@0: Selection& currentSel = CurrentSelection(); michael@0: if (currentSel.IsDirty()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionUpdateAction() FAILED " michael@0: "due to CurrentSelection() failure", this)); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: PendingAction* action = GetPendingCompositionUpdate(); michael@0: action->mData = mComposition.mString; michael@0: // The ranges might already have been initialized, however, if this is michael@0: // called again, that means we need to overwrite the ranges with current michael@0: // information. michael@0: action->mRanges->Clear(); michael@0: michael@0: TextRange newRange; michael@0: // No matter if we have display attribute info or not, michael@0: // we always pass in at least one range to NS_TEXT_TEXT michael@0: newRange.mStartOffset = 0; michael@0: newRange.mEndOffset = action->mData.Length(); michael@0: newRange.mRangeType = NS_TEXTRANGE_RAWINPUT; michael@0: action->mRanges->AppendElement(newRange); michael@0: michael@0: nsRefPtr range; michael@0: while (S_OK == enumRanges->Next(1, getter_AddRefs(range), nullptr) && range) { michael@0: michael@0: LONG start = 0, length = 0; michael@0: if (FAILED(GetRangeExtent(range, &start, &length))) michael@0: continue; michael@0: michael@0: TextRange newRange; michael@0: newRange.mStartOffset = uint32_t(start - mComposition.mStart); michael@0: // The end of the last range in the array is michael@0: // always kept at the end of composition michael@0: newRange.mEndOffset = mComposition.mString.Length(); michael@0: michael@0: TF_DISPLAYATTRIBUTE attr; michael@0: hr = GetDisplayAttribute(attrPropetry, range, &attr); michael@0: if (FAILED(hr)) { michael@0: newRange.mRangeType = NS_TEXTRANGE_RAWINPUT; michael@0: } else { michael@0: newRange.mRangeType = GetGeckoSelectionValue(attr); michael@0: if (GetColor(attr.crText, newRange.mRangeStyle.mForegroundColor)) { michael@0: newRange.mRangeStyle.mDefinedStyles |= michael@0: TextRangeStyle::DEFINED_FOREGROUND_COLOR; michael@0: } michael@0: if (GetColor(attr.crBk, newRange.mRangeStyle.mBackgroundColor)) { michael@0: newRange.mRangeStyle.mDefinedStyles |= michael@0: TextRangeStyle::DEFINED_BACKGROUND_COLOR; michael@0: } michael@0: if (GetColor(attr.crLine, newRange.mRangeStyle.mUnderlineColor)) { michael@0: newRange.mRangeStyle.mDefinedStyles |= michael@0: TextRangeStyle::DEFINED_UNDERLINE_COLOR; michael@0: } michael@0: if (GetLineStyle(attr.lsStyle, newRange.mRangeStyle.mLineStyle)) { michael@0: newRange.mRangeStyle.mDefinedStyles |= michael@0: TextRangeStyle::DEFINED_LINESTYLE; michael@0: newRange.mRangeStyle.mIsBoldLine = attr.fBoldLine != 0; michael@0: } michael@0: } michael@0: michael@0: TextRange& lastRange = action->mRanges->LastElement(); michael@0: if (lastRange.mStartOffset == newRange.mStartOffset) { michael@0: // Replace range if last range is the same as this one michael@0: // So that ranges don't overlap and confuse the editor michael@0: lastRange = newRange; michael@0: } else { michael@0: lastRange.mEndOffset = newRange.mStartOffset; michael@0: action->mRanges->AppendElement(newRange); michael@0: } michael@0: } michael@0: michael@0: // We need to hack for Korean Input System which is Korean standard TIP. michael@0: // It sets no change style to IME selection (the selection is always only michael@0: // one). So, the composition string looks like normal (or committed) string. michael@0: // At this time, current selection range is same as the composition string michael@0: // range. Other applications set a wide caret which covers the composition michael@0: // string, however, Gecko doesn't support the wide caret drawing now (Gecko michael@0: // doesn't support XOR drawing), unfortunately. For now, we should change michael@0: // the range style to undefined. michael@0: if (!currentSel.IsCollapsed() && action->mRanges->Length() == 1) { michael@0: TextRange& range = action->mRanges->ElementAt(0); michael@0: LONG start = currentSel.MinOffset(); michael@0: LONG end = currentSel.MaxOffset(); michael@0: if ((LONG)range.mStartOffset == start - mComposition.mStart && michael@0: (LONG)range.mEndOffset == end - mComposition.mStart && michael@0: range.mRangeStyle.IsNoChangeStyle()) { michael@0: range.mRangeStyle.Clear(); michael@0: // The looks of selected type is better than others. michael@0: range.mRangeType = NS_TEXTRANGE_SELECTEDRAWTEXT; michael@0: } michael@0: } michael@0: michael@0: // The caret position has to be collapsed. michael@0: LONG caretPosition = currentSel.MaxOffset(); michael@0: caretPosition -= mComposition.mStart; michael@0: TextRange caretRange; michael@0: caretRange.mStartOffset = caretRange.mEndOffset = uint32_t(caretPosition); michael@0: caretRange.mRangeType = NS_TEXTRANGE_CARETPOSITION; michael@0: action->mRanges->AppendElement(caretRange); michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionUpdateAction() " michael@0: "succeeded", this)); michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT michael@0: nsTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection, michael@0: bool aDispatchTextEvent) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::SetSelectionInternal(pSelection={ " michael@0: "acpStart=%ld, acpEnd=%ld, style={ ase=%s, fInterimChar=%s} }, " michael@0: "aDispatchTextEvent=%s), mComposition.IsComposing()=%s", michael@0: this, pSelection->acpStart, pSelection->acpEnd, michael@0: GetActiveSelEndName(pSelection->style.ase), michael@0: GetBoolName(pSelection->style.fInterimChar), michael@0: GetBoolName(aDispatchTextEvent), michael@0: GetBoolName(mComposition.IsComposing()))); michael@0: michael@0: MOZ_ASSERT(IsReadWriteLocked()); michael@0: michael@0: Selection& currentSel = CurrentSelection(); michael@0: if (currentSel.IsDirty()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::SetSelectionInternal() FAILED due to " michael@0: "CurrentSelection() failure", this)); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: if (mComposition.IsComposing()) { michael@0: if (aDispatchTextEvent) { michael@0: HRESULT hr = RestartCompositionIfNecessary(); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::SetSelectionInternal() FAILED due to " michael@0: "RestartCompositionIfNecessary() failure", this)); michael@0: return hr; michael@0: } michael@0: } michael@0: if (pSelection->acpStart < mComposition.mStart || michael@0: pSelection->acpEnd > mComposition.EndOffset()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::SetSelectionInternal() FAILED due to " michael@0: "the selection being out of the composition string", this)); michael@0: return TS_E_INVALIDPOS; michael@0: } michael@0: // Emulate selection during compositions michael@0: currentSel.SetSelection(*pSelection); michael@0: if (aDispatchTextEvent) { michael@0: HRESULT hr = RecordCompositionUpdateAction(); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::SetSelectionInternal() FAILED due to " michael@0: "RecordCompositionUpdateAction() failure", this)); michael@0: return hr; michael@0: } michael@0: } michael@0: return S_OK; michael@0: } michael@0: michael@0: PendingAction* action = mPendingActions.AppendElement(); michael@0: action->mType = PendingAction::SELECTION_SET; michael@0: action->mSelectionStart = pSelection->acpStart; michael@0: action->mSelectionLength = pSelection->acpEnd - pSelection->acpStart; michael@0: action->mSelectionReversed = (pSelection->style.ase == TS_AE_START); michael@0: michael@0: currentSel.SetSelection(*pSelection); michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::SetSelection(ULONG ulCount, michael@0: const TS_SELECTION_ACP *pSelection) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::SetSelection(ulCount=%lu, pSelection=%p { " michael@0: "acpStart=%ld, acpEnd=%ld, style={ ase=%s, fInterimChar=%s } }), " michael@0: "mComposition.IsComposing()=%s", michael@0: this, ulCount, pSelection, michael@0: pSelection ? pSelection->acpStart : 0, michael@0: pSelection ? pSelection->acpEnd : 0, michael@0: pSelection ? GetActiveSelEndName(pSelection->style.ase) : "", michael@0: pSelection ? GetBoolName(pSelection->style.fInterimChar) : "", michael@0: GetBoolName(mComposition.IsComposing()))); michael@0: michael@0: if (!IsReadWriteLocked()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::SetSelection() FAILED due to " michael@0: "not locked (read-write)", this)); michael@0: return TS_E_NOLOCK; michael@0: } michael@0: if (ulCount != 1) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::SetSelection() FAILED due to " michael@0: "trying setting multiple selection", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: if (!pSelection) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::SetSelection() FAILED due to " michael@0: "null argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: HRESULT hr = SetSelectionInternal(pSelection, true); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::SetSelection() FAILED due to " michael@0: "SetSelectionInternal() failure", this)); michael@0: } else { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::SetSelection() succeeded", this)); michael@0: } michael@0: return hr; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::GetText(LONG acpStart, michael@0: LONG acpEnd, michael@0: WCHAR *pchPlain, michael@0: ULONG cchPlainReq, michael@0: ULONG *pcchPlainOut, michael@0: TS_RUNINFO *prgRunInfo, michael@0: ULONG ulRunInfoReq, michael@0: ULONG *pulRunInfoOut, michael@0: LONG *pacpNext) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetText(acpStart=%ld, acpEnd=%ld, pchPlain=0x%p, " michael@0: "cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, ulRunInfoReq=%lu, " michael@0: "pulRunInfoOut=0x%p, pacpNext=0x%p), mComposition={ mStart=%ld, " michael@0: "mString.Length()=%lu, IsComposing()=%s }", michael@0: this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, michael@0: prgRunInfo, ulRunInfoReq, pulRunInfoOut, pacpNext, michael@0: mComposition.mStart, mComposition.mString.Length(), michael@0: GetBoolName(mComposition.IsComposing()))); michael@0: michael@0: if (!IsReadLocked()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetText() FAILED due to " michael@0: "not locked (read)", this)); michael@0: return TS_E_NOLOCK; michael@0: } michael@0: michael@0: if (!pcchPlainOut || (!pchPlain && !prgRunInfo) || michael@0: !cchPlainReq != !pchPlain || !ulRunInfoReq != !prgRunInfo) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetText() FAILED due to " michael@0: "invalid argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: if (acpStart < 0 || acpEnd < -1 || (acpEnd != -1 && acpStart > acpEnd)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetText() FAILED due to " michael@0: "invalid position", this)); michael@0: return TS_E_INVALIDPOS; michael@0: } michael@0: michael@0: // Making sure to null-terminate string just to be on the safe side michael@0: *pcchPlainOut = 0; michael@0: if (pchPlain && cchPlainReq) *pchPlain = 0; michael@0: if (pulRunInfoOut) *pulRunInfoOut = 0; michael@0: if (pacpNext) *pacpNext = acpStart; michael@0: if (prgRunInfo && ulRunInfoReq) { michael@0: prgRunInfo->uCount = 0; michael@0: prgRunInfo->type = TS_RT_PLAIN; michael@0: } michael@0: michael@0: Content& currentContent = CurrentContent(); michael@0: if (!currentContent.IsInitialized()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetText() FAILED due to " michael@0: "CurrentContent() failure", this)); michael@0: return E_FAIL; michael@0: } michael@0: if (currentContent.Text().Length() < static_cast(acpStart)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetText() FAILED due to " michael@0: "acpStart is larger offset than the actual text length", this)); michael@0: return TS_E_INVALIDPOS; michael@0: } michael@0: if (acpEnd != -1 && michael@0: currentContent.Text().Length() < static_cast(acpEnd)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetText() FAILED due to " michael@0: "acpEnd is larger offset than the actual text length", this)); michael@0: return TS_E_INVALIDPOS; michael@0: } michael@0: uint32_t length = (acpEnd == -1) ? michael@0: currentContent.Text().Length() - static_cast(acpStart) : michael@0: static_cast(acpEnd - acpStart); michael@0: if (cchPlainReq && cchPlainReq - 1 < length) { michael@0: length = cchPlainReq - 1; michael@0: } michael@0: if (length) { michael@0: if (pchPlain && cchPlainReq) { michael@0: const char16_t* startChar = michael@0: currentContent.Text().BeginReading() + acpStart; michael@0: memcpy(pchPlain, startChar, length * sizeof(*pchPlain)); michael@0: pchPlain[length] = 0; michael@0: *pcchPlainOut = length; michael@0: } michael@0: if (prgRunInfo && ulRunInfoReq) { michael@0: prgRunInfo->uCount = length; michael@0: prgRunInfo->type = TS_RT_PLAIN; michael@0: if (pulRunInfoOut) *pulRunInfoOut = 1; michael@0: } michael@0: if (pacpNext) *pacpNext = acpStart + length; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetText() succeeded: pcchPlainOut=0x%p, " michael@0: "*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, " michael@0: "*pacpNext=%ld)", michael@0: this, pcchPlainOut, prgRunInfo ? prgRunInfo->uCount : 0, michael@0: prgRunInfo ? GetTextRunTypeName(prgRunInfo->type) : "N/A", michael@0: pulRunInfoOut ? pulRunInfoOut : 0, pacpNext ? pacpNext : 0)); michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::SetText(DWORD dwFlags, michael@0: LONG acpStart, michael@0: LONG acpEnd, michael@0: const WCHAR *pchText, michael@0: ULONG cch, michael@0: TS_TEXTCHANGE *pChange) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::SetText(dwFlags=%s, acpStart=%ld, " michael@0: "acpEnd=%ld, pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), " michael@0: "mComposition.IsComposing()=%s", michael@0: this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" : michael@0: "not-specified", michael@0: acpStart, acpEnd, pchText, michael@0: pchText && cch ? michael@0: NS_ConvertUTF16toUTF8(pchText, cch).get() : "", michael@0: cch, pChange, GetBoolName(mComposition.IsComposing()))); michael@0: michael@0: // Per SDK documentation, and since we don't have better michael@0: // ways to do this, this method acts as a helper to michael@0: // call SetSelection followed by InsertTextAtSelection michael@0: if (!IsReadWriteLocked()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::SetText() FAILED due to " michael@0: "not locked (read)", this)); michael@0: return TS_E_NOLOCK; michael@0: } michael@0: michael@0: TS_SELECTION_ACP selection; michael@0: selection.acpStart = acpStart; michael@0: selection.acpEnd = acpEnd; michael@0: selection.style.ase = TS_AE_END; michael@0: selection.style.fInterimChar = 0; michael@0: // Set selection to desired range michael@0: HRESULT hr = SetSelectionInternal(&selection); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::SetText() FAILED due to " michael@0: "SetSelectionInternal() failure", this)); michael@0: return hr; michael@0: } michael@0: // Replace just selected text michael@0: if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch), michael@0: pChange)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::SetText() FAILED due to " michael@0: "InsertTextAtSelectionInternal() failure", this)); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::SetText() succeeded: pChange={ " michael@0: "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }", michael@0: this, pChange ? pChange->acpStart : 0, michael@0: pChange ? pChange->acpOldEnd : 0, pChange ? pChange->acpNewEnd : 0)); michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::GetFormattedText(LONG acpStart, michael@0: LONG acpEnd, michael@0: IDataObject **ppDataObject) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetFormattedText() called " michael@0: "but not supported (E_NOTIMPL)", this)); michael@0: michael@0: // no support for formatted text michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::GetEmbedded(LONG acpPos, michael@0: REFGUID rguidService, michael@0: REFIID riid, michael@0: IUnknown **ppunk) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetEmbedded() called " michael@0: "but not supported (E_NOTIMPL)", this)); michael@0: michael@0: // embedded objects are not supported michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::QueryInsertEmbedded(const GUID *pguidService, michael@0: const FORMATETC *pFormatEtc, michael@0: BOOL *pfInsertable) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::QueryInsertEmbedded() called " michael@0: "but not supported, *pfInsertable=FALSE (S_OK)", this)); michael@0: michael@0: // embedded objects are not supported michael@0: *pfInsertable = FALSE; michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::InsertEmbedded(DWORD dwFlags, michael@0: LONG acpStart, michael@0: LONG acpEnd, michael@0: IDataObject *pDataObject, michael@0: TS_TEXTCHANGE *pChange) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::InsertEmbedded() called " michael@0: "but not supported (E_NOTIMPL)", this)); michael@0: michael@0: // embedded objects are not supported michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: void michael@0: nsTextStore::SetInputScope(const nsString& aHTMLInputType) michael@0: { michael@0: mInputScopes.Clear(); michael@0: if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) { michael@0: return; michael@0: } michael@0: michael@0: // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html michael@0: if (aHTMLInputType.EqualsLiteral("url")) { michael@0: mInputScopes.AppendElement(IS_URL); michael@0: } else if (aHTMLInputType.EqualsLiteral("search")) { michael@0: mInputScopes.AppendElement(IS_SEARCH); michael@0: } else if (aHTMLInputType.EqualsLiteral("email")) { michael@0: mInputScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS); michael@0: } else if (aHTMLInputType.EqualsLiteral("password")) { michael@0: mInputScopes.AppendElement(IS_PASSWORD); michael@0: } else if (aHTMLInputType.EqualsLiteral("datetime") || michael@0: aHTMLInputType.EqualsLiteral("datetime-local")) { michael@0: mInputScopes.AppendElement(IS_DATE_FULLDATE); michael@0: mInputScopes.AppendElement(IS_TIME_FULLTIME); michael@0: } else if (aHTMLInputType.EqualsLiteral("date") || michael@0: aHTMLInputType.EqualsLiteral("month") || michael@0: aHTMLInputType.EqualsLiteral("week")) { michael@0: mInputScopes.AppendElement(IS_DATE_FULLDATE); michael@0: } else if (aHTMLInputType.EqualsLiteral("time")) { michael@0: mInputScopes.AppendElement(IS_TIME_FULLTIME); michael@0: } else if (aHTMLInputType.EqualsLiteral("tel")) { michael@0: mInputScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER); michael@0: mInputScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER); michael@0: } else if (aHTMLInputType.EqualsLiteral("number")) { michael@0: mInputScopes.AppendElement(IS_NUMBER); michael@0: } michael@0: } michael@0: michael@0: HRESULT michael@0: nsTextStore::ProcessScopeRequest(DWORD dwFlags, michael@0: ULONG cFilterAttrs, michael@0: const TS_ATTRID *paFilterAttrs) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::ProcessScopeRequest(dwFlags=%s, " michael@0: "cFilterAttrs=%ld", michael@0: this, GetFindFlagName(dwFlags).get(), cFilterAttrs)); michael@0: michael@0: // This is a little weird! RequestSupportedAttrs gives us advanced notice michael@0: // of a support query via RetrieveRequestedAttrs for a specific attribute. michael@0: // RetrieveRequestedAttrs needs to return valid data for all attributes we michael@0: // support, but the text service will only want the input scope object michael@0: // returned in RetrieveRequestedAttrs if the dwFlags passed in here contains michael@0: // TS_ATTR_FIND_WANT_VALUE. michael@0: mInputScopeDetected = mInputScopeRequested = false; michael@0: michael@0: // Currently we only support GUID_PROP_INPUTSCOPE michael@0: for (uint32_t idx = 0; idx < cFilterAttrs; ++idx) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::ProcessScopeRequest() " michael@0: "requested attr=%s", michael@0: this, GetCLSIDNameStr(paFilterAttrs[idx]).get())); michael@0: if (IsEqualGUID(paFilterAttrs[idx], GUID_PROP_INPUTSCOPE)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::ProcessScopeRequest() " michael@0: "GUID_PROP_INPUTSCOPE queried", this)); michael@0: mInputScopeDetected = true; michael@0: if (dwFlags & TS_ATTR_FIND_WANT_VALUE) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::ProcessScopeRequest() " michael@0: "TS_ATTR_FIND_WANT_VALUE specified", this)); michael@0: mInputScopeRequested = true; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::RequestSupportedAttrs(DWORD dwFlags, michael@0: ULONG cFilterAttrs, michael@0: const TS_ATTRID *paFilterAttrs) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RequestSupportedAttrs(dwFlags=%s, " michael@0: "cFilterAttrs=%lu)", michael@0: this, GetFindFlagName(dwFlags).get(), cFilterAttrs)); michael@0: michael@0: return ProcessScopeRequest(dwFlags, cFilterAttrs, paFilterAttrs); michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::RequestAttrsAtPosition(LONG acpPos, michael@0: ULONG cFilterAttrs, michael@0: const TS_ATTRID *paFilterAttrs, michael@0: DWORD dwFlags) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RequestAttrsAtPosition(acpPos=%ld, " michael@0: "cFilterAttrs=%lu, dwFlags=%s)", michael@0: this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get())); michael@0: michael@0: return ProcessScopeRequest(dwFlags | TS_ATTR_FIND_WANT_VALUE, michael@0: cFilterAttrs, paFilterAttrs); michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos, michael@0: ULONG cFilterAttrs, michael@0: const TS_ATTRID *paFilterAttr, michael@0: DWORD dwFlags) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RequestAttrsTransitioningAtPosition(" michael@0: "acpPos=%ld, cFilterAttrs=%lu, dwFlags=%s) called but not supported " michael@0: "(S_OK)", michael@0: this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get())); michael@0: michael@0: // no per character attributes defined michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::FindNextAttrTransition(LONG acpStart, michael@0: LONG acpHalt, michael@0: ULONG cFilterAttrs, michael@0: const TS_ATTRID *paFilterAttrs, michael@0: DWORD dwFlags, michael@0: LONG *pacpNext, michael@0: BOOL *pfFound, michael@0: LONG *plFoundOffset) michael@0: { michael@0: if (!pacpNext || !pfFound || !plFoundOffset) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::FindNextAttrTransition() FAILED due to " michael@0: "null argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::FindNextAttrTransition() called " michael@0: "but not supported (S_OK)", this)); michael@0: michael@0: // no per character attributes defined michael@0: *pacpNext = *plFoundOffset = acpHalt; michael@0: *pfFound = FALSE; michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::RetrieveRequestedAttrs(ULONG ulCount, michael@0: TS_ATTRVAL *paAttrVals, michael@0: ULONG *pcFetched) michael@0: { michael@0: if (!pcFetched || !ulCount || !paAttrVals) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RetrieveRequestedAttrs() FAILED due to " michael@0: "null argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RetrieveRequestedAttrs() called " michael@0: "ulCount=%d", this, ulCount)); michael@0: michael@0: if (mInputScopeDetected || mInputScopeRequested) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RetrieveRequestedAttrs() for " michael@0: "GUID_PROP_INPUTSCOPE: " michael@0: "mInputScopeDetected=%s mInputScopeRequested=%s", michael@0: this, GetBoolName(mInputScopeDetected), michael@0: GetBoolName(mInputScopeRequested))); michael@0: michael@0: paAttrVals->idAttr = GUID_PROP_INPUTSCOPE; michael@0: paAttrVals->dwOverlapId = 0; michael@0: paAttrVals->varValue.vt = VT_EMPTY; michael@0: *pcFetched = 1; michael@0: michael@0: if (mInputScopeRequested) { michael@0: paAttrVals->varValue.vt = VT_UNKNOWN; michael@0: paAttrVals->varValue.punkVal = (IUnknown*) new InputScopeImpl(mInputScopes); michael@0: } michael@0: michael@0: mInputScopeDetected = mInputScopeRequested = false; michael@0: return S_OK; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RetrieveRequestedAttrs() called " michael@0: "for unknown TS_ATTRVAL, *pcFetched=0 (S_OK)", this)); michael@0: michael@0: paAttrVals->dwOverlapId = 0; michael@0: paAttrVals->varValue.vt = VT_EMPTY; michael@0: *pcFetched = 0; michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::GetEndACP(LONG *pacp) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetEndACP(pacp=0x%p)", this, pacp)); michael@0: michael@0: if (!IsReadLocked()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetEndACP() FAILED due to " michael@0: "not locked (read)", this)); michael@0: return TS_E_NOLOCK; michael@0: } michael@0: michael@0: if (!pacp) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetEndACP() FAILED due to " michael@0: "null argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: Content& currentContent = CurrentContent(); michael@0: if (!currentContent.IsInitialized()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetEndACP() FAILED due to " michael@0: "CurrentContent() failure", this)); michael@0: return E_FAIL; michael@0: } michael@0: *pacp = static_cast(currentContent.Text().Length()); michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::GetActiveView(TsViewCookie *pvcView) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetActiveView(pvcView=0x%p)", this, pvcView)); michael@0: michael@0: if (!pvcView) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetActiveView() FAILED due to " michael@0: "null argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: *pvcView = TEXTSTORE_DEFAULT_VIEW; michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetActiveView() succeeded: *pvcView=%ld", michael@0: this, *pvcView)); michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::GetACPFromPoint(TsViewCookie vcView, michael@0: const POINT *pt, michael@0: DWORD dwFlags, michael@0: LONG *pacp) michael@0: { michael@0: if (!IsReadLocked()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetACPFromPoint() FAILED due to " michael@0: "not locked (read)", this)); michael@0: return TS_E_NOLOCK; michael@0: } michael@0: michael@0: if (vcView != TEXTSTORE_DEFAULT_VIEW) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetACPFromPoint() FAILED due to " michael@0: "called with invalid view", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: if (mContent.IsLayoutChanged()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetACPFromPoint() FAILED due to " michael@0: "layout not recomputed", this)); michael@0: mPendingOnLayoutChange = true; michael@0: return TS_E_NOLAYOUT; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetACPFromPoint(vcView=%ld, " michael@0: "pt(0x%p)={ x=%ld, y=%ld }, dwFlags=%s, pacp=0x%p) called " michael@0: "but not supported (E_NOTIMPL)", this)); michael@0: michael@0: // not supported for now michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::GetTextExt(TsViewCookie vcView, michael@0: LONG acpStart, michael@0: LONG acpEnd, michael@0: RECT *prc, michael@0: BOOL *pfClipped) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetTextExt(vcView=%ld, " michael@0: "acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p)", michael@0: this, vcView, acpStart, acpEnd, prc, pfClipped)); michael@0: michael@0: if (!IsReadLocked()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " michael@0: "not locked (read)", this)); michael@0: return TS_E_NOLOCK; michael@0: } michael@0: michael@0: if (vcView != TEXTSTORE_DEFAULT_VIEW) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " michael@0: "called with invalid view", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: if (!prc || !pfClipped) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " michael@0: "null argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: if (acpStart < 0 || acpEnd < acpStart) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " michael@0: "invalid position", this)); michael@0: return TS_E_INVALIDPOS; michael@0: } michael@0: michael@0: if (mContent.IsLayoutChangedAfter(acpEnd)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " michael@0: "layout not recomputed at %d", this, acpEnd)); michael@0: mPendingOnLayoutChange = true; michael@0: return TS_E_NOLAYOUT; michael@0: } michael@0: michael@0: // use NS_QUERY_TEXT_RECT to get rect in system, screen coordinates michael@0: WidgetQueryContentEvent event(true, NS_QUERY_TEXT_RECT, mWidget); michael@0: mWidget->InitEvent(event); michael@0: event.InitForQueryTextRect(acpStart, acpEnd - acpStart); michael@0: mWidget->DispatchWindowEvent(&event); michael@0: if (!event.mSucceeded) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " michael@0: "NS_QUERY_TEXT_RECT failure", this)); michael@0: return TS_E_INVALIDPOS; // but unexpected failure, maybe. michael@0: } michael@0: // IMEs don't like empty rects, fix here michael@0: if (event.mReply.mRect.width <= 0) michael@0: event.mReply.mRect.width = 1; michael@0: if (event.mReply.mRect.height <= 0) michael@0: event.mReply.mRect.height = 1; michael@0: michael@0: if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Desktop) { michael@0: // convert to unclipped screen rect michael@0: nsWindow* refWindow = static_cast( michael@0: event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWidget); michael@0: // Result rect is in top level widget coordinates michael@0: refWindow = refWindow->GetTopLevelWindow(false); michael@0: if (!refWindow) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " michael@0: "no top level window", this)); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: event.mReply.mRect.MoveBy(refWindow->WidgetToScreenOffset()); michael@0: } michael@0: michael@0: // get bounding screen rect to test for clipping michael@0: if (!GetScreenExtInternal(*prc)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " michael@0: "GetScreenExtInternal() failure", this)); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: // clip text rect to bounding rect michael@0: RECT textRect; michael@0: ::SetRect(&textRect, event.mReply.mRect.x, event.mReply.mRect.y, michael@0: event.mReply.mRect.XMost(), event.mReply.mRect.YMost()); michael@0: if (!::IntersectRect(prc, prc, &textRect)) michael@0: // Text is not visible michael@0: ::SetRectEmpty(prc); michael@0: michael@0: // not equal if text rect was clipped michael@0: *pfClipped = !::EqualRect(prc, &textRect); michael@0: michael@0: // ATOK refers native caret position and size on Desktop applications for michael@0: // deciding candidate window. Therefore, we need to create native caret michael@0: // for hacking the bug. michael@0: if (sCreateNativeCaretForATOK && michael@0: StringBeginsWith( michael@0: mActiveTIPKeyboardDescription, NS_LITERAL_STRING("ATOK ")) && michael@0: mComposition.IsComposing() && michael@0: mComposition.mStart <= acpStart && mComposition.EndOffset() >= acpStart && michael@0: mComposition.mStart <= acpEnd && mComposition.EndOffset() >= acpEnd) { michael@0: if (mNativeCaretIsCreated) { michael@0: ::DestroyCaret(); michael@0: mNativeCaretIsCreated = false; michael@0: } michael@0: CreateNativeCaret(); michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetTextExt() succeeded: " michael@0: "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }, *pfClipped=%s", michael@0: this, prc->left, prc->top, prc->right, prc->bottom, michael@0: GetBoolName(*pfClipped))); michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::GetScreenExt(TsViewCookie vcView, michael@0: RECT *prc) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetScreenExt(vcView=%ld, prc=0x%p)", michael@0: this, vcView, prc)); michael@0: michael@0: if (vcView != TEXTSTORE_DEFAULT_VIEW) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetScreenExt() FAILED due to " michael@0: "called with invalid view", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: if (!prc) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetScreenExt() FAILED due to " michael@0: "null argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: if (!GetScreenExtInternal(*prc)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetScreenExt() FAILED due to " michael@0: "GetScreenExtInternal() failure", this)); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetScreenExt() succeeded: " michael@0: "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }", michael@0: this, prc->left, prc->top, prc->right, prc->bottom)); michael@0: return S_OK; michael@0: } michael@0: michael@0: bool michael@0: nsTextStore::GetScreenExtInternal(RECT &aScreenExt) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::GetScreenExtInternal()", this)); michael@0: michael@0: // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates michael@0: WidgetQueryContentEvent event(true, NS_QUERY_EDITOR_RECT, mWidget); michael@0: mWidget->InitEvent(event); michael@0: mWidget->DispatchWindowEvent(&event); michael@0: if (!event.mSucceeded) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetScreenExtInternal() FAILED due to " michael@0: "NS_QUERY_EDITOR_RECT failure", this)); michael@0: return false; michael@0: } michael@0: michael@0: if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Metro) { michael@0: nsIntRect boundRect; michael@0: if (NS_FAILED(mWidget->GetClientBounds(boundRect))) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetScreenExtInternal() FAILED due to " michael@0: "failed to get the client bounds", this)); michael@0: return false; michael@0: } michael@0: ::SetRect(&aScreenExt, boundRect.x, boundRect.y, michael@0: boundRect.XMost(), boundRect.YMost()); michael@0: } else { michael@0: NS_ASSERTION(XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Desktop, michael@0: "environment isn't WindowsEnvironmentType_Desktop!"); michael@0: nsWindow* refWindow = static_cast( michael@0: event.mReply.mFocusedWidget ? michael@0: event.mReply.mFocusedWidget : mWidget); michael@0: // Result rect is in top level widget coordinates michael@0: refWindow = refWindow->GetTopLevelWindow(false); michael@0: if (!refWindow) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetScreenExtInternal() FAILED due to " michael@0: "no top level window", this)); michael@0: return false; michael@0: } michael@0: michael@0: nsIntRect boundRect; michael@0: if (NS_FAILED(refWindow->GetClientBounds(boundRect))) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetScreenExtInternal() FAILED due to " michael@0: "failed to get the client bounds", this)); michael@0: return false; michael@0: } michael@0: michael@0: boundRect.MoveTo(0, 0); michael@0: michael@0: // Clip frame rect to window rect michael@0: boundRect.IntersectRect(event.mReply.mRect, boundRect); michael@0: if (!boundRect.IsEmpty()) { michael@0: boundRect.MoveBy(refWindow->WidgetToScreenOffset()); michael@0: ::SetRect(&aScreenExt, boundRect.x, boundRect.y, michael@0: boundRect.XMost(), boundRect.YMost()); michael@0: } else { michael@0: ::SetRectEmpty(&aScreenExt); michael@0: } michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::GetScreenExtInternal() succeeded: " michael@0: "aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }", michael@0: this, aScreenExt.left, aScreenExt.top, michael@0: aScreenExt.right, aScreenExt.bottom)); michael@0: return true; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::GetWnd(TsViewCookie vcView, michael@0: HWND *phwnd) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetWnd(vcView=%ld, phwnd=0x%p), " michael@0: "mWidget=0x%p", michael@0: this, vcView, phwnd, mWidget.get())); michael@0: michael@0: if (vcView != TEXTSTORE_DEFAULT_VIEW) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetWnd() FAILED due to " michael@0: "called with invalid view", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: if (!phwnd) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::GetScreenExt() FAILED due to " michael@0: "null argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: *phwnd = mWidget->GetWindowHandle(); michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::GetWnd() succeeded: *phwnd=0x%p", michael@0: this, static_cast(*phwnd))); michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::InsertTextAtSelection(DWORD dwFlags, michael@0: const WCHAR *pchText, michael@0: ULONG cch, michael@0: LONG *pacpStart, michael@0: LONG *pacpEnd, michael@0: TS_TEXTCHANGE *pChange) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::InsertTextAtSelection(dwFlags=%s, " michael@0: "pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, " michael@0: "pChange=0x%p), IsComposing()=%s", michael@0: this, dwFlags == 0 ? "0" : michael@0: dwFlags == TF_IAS_NOQUERY ? "TF_IAS_NOQUERY" : michael@0: dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY" : "Unknown", michael@0: pchText, michael@0: pchText && cch ? NS_ConvertUTF16toUTF8(pchText, cch).get() : "", michael@0: cch, pacpStart, pacpEnd, pChange, michael@0: GetBoolName(mComposition.IsComposing()))); michael@0: michael@0: if (cch && !pchText) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " michael@0: "null pchText", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: if (TS_IAS_QUERYONLY == dwFlags) { michael@0: if (!IsReadLocked()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " michael@0: "not locked (read)", this)); michael@0: return TS_E_NOLOCK; michael@0: } michael@0: michael@0: if (!pacpStart || !pacpEnd) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " michael@0: "null argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: // Get selection first michael@0: Selection& currentSel = CurrentSelection(); michael@0: if (currentSel.IsDirty()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " michael@0: "CurrentSelection() failure", this)); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: // Simulate text insertion michael@0: *pacpStart = currentSel.StartOffset(); michael@0: *pacpEnd = currentSel.EndOffset(); michael@0: if (pChange) { michael@0: pChange->acpStart = currentSel.StartOffset(); michael@0: pChange->acpOldEnd = currentSel.EndOffset(); michael@0: pChange->acpNewEnd = currentSel.StartOffset() + static_cast(cch); michael@0: } michael@0: } else { michael@0: if (!IsReadWriteLocked()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " michael@0: "not locked (read-write)", this)); michael@0: return TS_E_NOLOCK; michael@0: } michael@0: michael@0: if (!pChange) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " michael@0: "null pChange", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " michael@0: "null argument", this)); michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch), michael@0: pChange)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " michael@0: "InsertTextAtSelectionInternal() failure", this)); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: if (TS_IAS_NOQUERY != dwFlags) { michael@0: *pacpStart = pChange->acpStart; michael@0: *pacpEnd = pChange->acpNewEnd; michael@0: } michael@0: } michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::InsertTextAtSelection() succeeded: " michael@0: "*pacpStart=%ld, *pacpEnd=%ld, " michael@0: "*pChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld })", michael@0: this, pacpStart ? *pacpStart : 0, pacpEnd ? *pacpEnd : 0, michael@0: pChange ? pChange->acpStart: 0, pChange ? pChange->acpOldEnd : 0, michael@0: pChange ? pChange->acpNewEnd : 0)); michael@0: return S_OK; michael@0: } michael@0: michael@0: bool michael@0: nsTextStore::InsertTextAtSelectionInternal(const nsAString &aInsertStr, michael@0: TS_TEXTCHANGE* aTextChange) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal(" michael@0: "aInsertStr=\"%s\", aTextChange=0x%p), IsComposing=%s", michael@0: this, NS_ConvertUTF16toUTF8(aInsertStr).get(), aTextChange, michael@0: GetBoolName(mComposition.IsComposing()))); michael@0: michael@0: Content& currentContent = CurrentContent(); michael@0: if (!currentContent.IsInitialized()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() failed " michael@0: "due to CurrentContent() failure()", this)); michael@0: return false; michael@0: } michael@0: michael@0: TS_SELECTION_ACP oldSelection = currentContent.Selection().ACP(); michael@0: if (!mComposition.IsComposing()) { michael@0: // Use a temporary composition to contain the text michael@0: PendingAction* compositionStart = mPendingActions.AppendElement(); michael@0: compositionStart->mType = PendingAction::COMPOSITION_START; michael@0: compositionStart->mSelectionStart = oldSelection.acpStart; michael@0: compositionStart->mSelectionLength = michael@0: oldSelection.acpEnd - oldSelection.acpStart; michael@0: michael@0: PendingAction* compositionEnd = mPendingActions.AppendElement(); michael@0: compositionEnd->mType = PendingAction::COMPOSITION_END; michael@0: compositionEnd->mData = aInsertStr; michael@0: } michael@0: michael@0: currentContent.ReplaceSelectedTextWith(aInsertStr); michael@0: michael@0: if (aTextChange) { michael@0: aTextChange->acpStart = oldSelection.acpStart; michael@0: aTextChange->acpOldEnd = oldSelection.acpEnd; michael@0: aTextChange->acpNewEnd = currentContent.Selection().EndOffset(); michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() succeeded: " michael@0: "mWidget=0x%p, mWidget->Destroyed()=%s, aTextChange={ acpStart=%ld, " michael@0: "acpOldEnd=%ld, acpNewEnd=%ld }", michael@0: this, mWidget.get(), michael@0: GetBoolName(mWidget ? mWidget->Destroyed() : true), michael@0: aTextChange ? aTextChange->acpStart : 0, michael@0: aTextChange ? aTextChange->acpOldEnd : 0, michael@0: aTextChange ? aTextChange->acpNewEnd : 0)); michael@0: return true; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::InsertEmbeddedAtSelection(DWORD dwFlags, michael@0: IDataObject *pDataObject, michael@0: LONG *pacpStart, michael@0: LONG *pacpEnd, michael@0: TS_TEXTCHANGE *pChange) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::InsertEmbeddedAtSelection() called " michael@0: "but not supported (E_NOTIMPL)", this)); michael@0: michael@0: // embedded objects are not supported michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: HRESULT michael@0: nsTextStore::RecordCompositionStartAction(ITfCompositionView* pComposition, michael@0: ITfRange* aRange, michael@0: bool aPreserveSelection) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionStartAction(" michael@0: "pComposition=0x%p, aRange=0x%p, aPreserveSelection=%s), " michael@0: "mComposition.mView=0x%p", michael@0: this, pComposition, aRange, GetBoolName(aPreserveSelection), michael@0: mComposition.mView.get())); michael@0: michael@0: LONG start = 0, length = 0; michael@0: HRESULT hr = GetRangeExtent(aRange, &start, &length); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionStartAction() FAILED " michael@0: "due to GetRangeExtent() failure", this)); michael@0: return hr; michael@0: } michael@0: michael@0: Content& currentContent = CurrentContent(); michael@0: if (!currentContent.IsInitialized()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionStartAction() FAILED " michael@0: "due to CurrentContent() failure", this)); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: PendingAction* action = mPendingActions.AppendElement(); michael@0: action->mType = PendingAction::COMPOSITION_START; michael@0: action->mSelectionStart = start; michael@0: action->mSelectionLength = length; michael@0: michael@0: currentContent.StartComposition(pComposition, *action, aPreserveSelection); michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionStartAction() succeeded: " michael@0: "mComposition={ mStart=%ld, mString.Length()=%ld, " michael@0: "mSelection={ acpStart=%ld, acpEnd=%ld, style.ase=%s, " michael@0: "style.fInterimChar=%s } }", michael@0: this, mComposition.mStart, mComposition.mString.Length(), michael@0: mSelection.StartOffset(), mSelection.EndOffset(), michael@0: GetActiveSelEndName(mSelection.ActiveSelEnd()), michael@0: GetBoolName(mSelection.IsInterimChar()))); michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT michael@0: nsTextStore::RecordCompositionEndAction() michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionEndAction(), " michael@0: "mComposition={ mView=0x%p, mString=\"%s\" }", michael@0: this, mComposition.mView.get(), michael@0: NS_ConvertUTF16toUTF8(mComposition.mString).get())); michael@0: michael@0: MOZ_ASSERT(mComposition.IsComposing()); michael@0: michael@0: PendingAction* action = mPendingActions.AppendElement(); michael@0: action->mType = PendingAction::COMPOSITION_END; michael@0: action->mData = mComposition.mString; michael@0: michael@0: Content& currentContent = CurrentContent(); michael@0: if (!currentContent.IsInitialized()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionEndAction() FAILED due " michael@0: "to CurrentContent() failure", this)); michael@0: return E_FAIL; michael@0: } michael@0: currentContent.EndComposition(*action); michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::RecordCompositionEndAction(), succeeded", michael@0: this)); michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::OnStartComposition(ITfCompositionView* pComposition, michael@0: BOOL* pfOk) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnStartComposition(pComposition=0x%p, " michael@0: "pfOk=0x%p), mComposition.mView=0x%p", michael@0: this, pComposition, pfOk, mComposition.mView.get())); michael@0: michael@0: AutoPendingActionAndContentFlusher flusher(this); michael@0: michael@0: *pfOk = FALSE; michael@0: michael@0: // Only one composition at a time michael@0: if (mComposition.IsComposing()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::OnStartComposition() FAILED due to " michael@0: "there is another composition already (but returns S_OK)", this)); michael@0: return S_OK; michael@0: } michael@0: michael@0: nsRefPtr range; michael@0: HRESULT hr = pComposition->GetRange(getter_AddRefs(range)); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::OnStartComposition() FAILED due to " michael@0: "pComposition->GetRange() failure", this)); michael@0: return hr; michael@0: } michael@0: hr = RecordCompositionStartAction(pComposition, range, false); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::OnStartComposition() FAILED due to " michael@0: "RecordCompositionStartAction() failure", this)); michael@0: return hr; michael@0: } michael@0: michael@0: *pfOk = TRUE; michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnStartComposition() succeeded", this)); michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::OnUpdateComposition(ITfCompositionView* pComposition, michael@0: ITfRange* pRangeNew) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnUpdateComposition(pComposition=0x%p, " michael@0: "pRangeNew=0x%p), mComposition.mView=0x%p", michael@0: this, pComposition, pRangeNew, mComposition.mView.get())); michael@0: michael@0: AutoPendingActionAndContentFlusher flusher(this); michael@0: michael@0: if (!mDocumentMgr || !mContext) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to " michael@0: "not ready for the composition", this)); michael@0: return E_UNEXPECTED; michael@0: } michael@0: if (!mComposition.IsComposing()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to " michael@0: "no active composition", this)); michael@0: return E_UNEXPECTED; michael@0: } michael@0: if (mComposition.mView != pComposition) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to " michael@0: "different composition view specified", this)); michael@0: return E_UNEXPECTED; michael@0: } michael@0: michael@0: // pRangeNew is null when the update is not complete michael@0: if (!pRangeNew) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnUpdateComposition() succeeded but " michael@0: "not complete", this)); michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT hr = RestartCompositionIfNecessary(pRangeNew); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to " michael@0: "RestartCompositionIfNecessary() failure", this)); michael@0: return hr; michael@0: } michael@0: michael@0: hr = RecordCompositionUpdateAction(); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to " michael@0: "RecordCompositionUpdateAction() failure", this)); michael@0: return hr; michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(sTextStoreLog, PR_LOG_ALWAYS)) { michael@0: Selection& currentSel = CurrentSelection(); michael@0: if (currentSel.IsDirty()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to " michael@0: "CurrentSelection() failure", this)); michael@0: return E_FAIL; michael@0: } michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnUpdateComposition() succeeded: " michael@0: "mComposition={ mStart=%ld, mString=\"%s\" }, " michael@0: "CurrentSelection()={ acpStart=%ld, acpEnd=%ld, style.ase=%s }", michael@0: this, mComposition.mStart, michael@0: NS_ConvertUTF16toUTF8(mComposition.mString).get(), michael@0: currentSel.StartOffset(), currentSel.EndOffset(), michael@0: GetActiveSelEndName(currentSel.ActiveSelEnd()))); michael@0: } michael@0: #endif // #ifdef PR_LOGGING michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::OnEndComposition(ITfCompositionView* pComposition) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnEndComposition(pComposition=0x%p), " michael@0: "mComposition={ mView=0x%p, mString=\"%s\" }", michael@0: this, pComposition, mComposition.mView.get(), michael@0: NS_ConvertUTF16toUTF8(mComposition.mString).get())); michael@0: michael@0: AutoPendingActionAndContentFlusher flusher(this); michael@0: michael@0: if (!mComposition.IsComposing()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::OnEndComposition() FAILED due to " michael@0: "no active composition", this)); michael@0: return E_UNEXPECTED; michael@0: } michael@0: michael@0: if (mComposition.mView != pComposition) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::OnEndComposition() FAILED due to " michael@0: "different composition view specified", this)); michael@0: return E_UNEXPECTED; michael@0: } michael@0: michael@0: HRESULT hr = RecordCompositionEndAction(); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::OnEndComposition() FAILED due to " michael@0: "RecordCompositionEndAction() failure", this)); michael@0: return hr; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnEndComposition(), succeeded", this)); michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::OnActivated(REFCLSID clsid, REFGUID guidProfile, michael@0: BOOL fActivated) michael@0: { michael@0: // NOTE: This is installed only on XP or Server 2003. michael@0: if (fActivated) { michael@0: // TODO: We should check if the profile's category is keyboard or not. michael@0: mOnActivatedCalled = true; michael@0: mIsIMM_IME = IsIMM_IME(::GetKeyboardLayout(0)); michael@0: michael@0: LANGID langID; michael@0: HRESULT hr = sInputProcessorProfiles->GetCurrentLanguage(&langID); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::OnActivated() FAILED due to " michael@0: "GetCurrentLanguage() failure, hr=0x%08X", hr)); michael@0: } else if (IsTIPCategoryKeyboard(clsid, langID, guidProfile)) { michael@0: GetTIPDescription(clsid, langID, guidProfile, michael@0: mActiveTIPKeyboardDescription); michael@0: } else if (clsid == CLSID_NULL || guidProfile == GUID_NULL) { michael@0: // Perhaps, this case is that keyboard layout without TIP is activated. michael@0: mActiveTIPKeyboardDescription.Truncate(); michael@0: } michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnActivated(rclsid=%s, guidProfile=%s, " michael@0: "fActivated=%s), mIsIMM_IME=%s, mActiveTIPDescription=\"%s\"", michael@0: this, GetCLSIDNameStr(clsid).get(), michael@0: GetGUIDNameStr(guidProfile).get(), GetBoolName(fActivated), michael@0: GetBoolName(mIsIMM_IME), michael@0: NS_ConvertUTF16toUTF8(mActiveTIPKeyboardDescription).get())); michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsTextStore::OnActivated(DWORD dwProfileType, michael@0: LANGID langid, michael@0: REFCLSID rclsid, michael@0: REFGUID catid, michael@0: REFGUID guidProfile, michael@0: HKL hkl, michael@0: DWORD dwFlags) michael@0: { michael@0: // NOTE: This is installed only on Vista or later. However, this may be michael@0: // called by EnsureInitActiveLanguageProfile() even on XP or Server michael@0: // 2003. michael@0: if ((dwFlags & TF_IPSINK_FLAG_ACTIVE) && michael@0: (dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT || michael@0: catid == GUID_TFCAT_TIP_KEYBOARD)) { michael@0: mOnActivatedCalled = true; michael@0: mIsIMM_IME = IsIMM_IME(hkl); michael@0: GetTIPDescription(rclsid, langid, guidProfile, michael@0: mActiveTIPKeyboardDescription); michael@0: } michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnActivated(dwProfileType=%s (0x%08X), " michael@0: "langid=0x%08X, rclsid=%s, catid=%s, guidProfile=%s, hkl=0x%08X, " michael@0: "dwFlags=0x%08X (TF_IPSINK_FLAG_ACTIVE: %s)), mIsIMM_IME=%s, " michael@0: "mActiveTIPDescription=\"%s\"", michael@0: this, dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR ? michael@0: "TF_PROFILETYPE_INPUTPROCESSOR" : michael@0: dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT ? michael@0: "TF_PROFILETYPE_KEYBOARDLAYOUT" : "Unknown", dwProfileType, michael@0: langid, GetCLSIDNameStr(rclsid).get(), GetGUIDNameStr(catid).get(), michael@0: GetGUIDNameStr(guidProfile).get(), hkl, dwFlags, michael@0: GetBoolName(dwFlags & TF_IPSINK_FLAG_ACTIVE), michael@0: GetBoolName(mIsIMM_IME), michael@0: NS_ConvertUTF16toUTF8(mActiveTIPKeyboardDescription).get())); michael@0: return S_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: nsTextStore::OnFocusChange(bool aGotFocus, michael@0: nsWindowBase* aFocusedWidget, michael@0: IMEState::Enabled aIMEEnabled) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: nsTextStore::OnFocusChange(aGotFocus=%s, " michael@0: "aFocusedWidget=0x%p, aIMEEnabled=%s), sTsfThreadMgr=0x%p, " michael@0: "sTsfTextStore=0x%p", michael@0: GetBoolName(aGotFocus), aFocusedWidget, michael@0: GetIMEEnabledName(aIMEEnabled), sTsfThreadMgr, sTsfTextStore)); michael@0: michael@0: // no change notifications if TSF is disabled michael@0: NS_ENSURE_TRUE(sTsfThreadMgr && sTsfTextStore, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: nsRefPtr prevFocusedDocumentMgr; michael@0: if (aGotFocus && (aIMEEnabled == IMEState::ENABLED || michael@0: aIMEEnabled == IMEState::PASSWORD)) { michael@0: bool bRet = sTsfTextStore->Create(aFocusedWidget); michael@0: NS_ENSURE_TRUE(bRet, NS_ERROR_FAILURE); michael@0: NS_ENSURE_TRUE(sTsfTextStore->mDocumentMgr, NS_ERROR_FAILURE); michael@0: if (aIMEEnabled == IMEState::PASSWORD) { michael@0: MarkContextAsKeyboardDisabled(sTsfTextStore->mContext); michael@0: nsRefPtr topContext; michael@0: sTsfTextStore->mDocumentMgr->GetTop(getter_AddRefs(topContext)); michael@0: if (topContext && topContext != sTsfTextStore->mContext) { michael@0: MarkContextAsKeyboardDisabled(topContext); michael@0: } michael@0: } michael@0: HRESULT hr = sTsfThreadMgr->SetFocus(sTsfTextStore->mDocumentMgr); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: // Use AssociateFocus() for ensuring that any native focus event michael@0: // never steal focus from our documentMgr. michael@0: hr = sTsfThreadMgr->AssociateFocus(aFocusedWidget->GetWindowHandle(), michael@0: sTsfTextStore->mDocumentMgr, michael@0: getter_AddRefs(prevFocusedDocumentMgr)); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: } else { michael@0: if (ThinksHavingFocus()) { michael@0: DebugOnly hr = sTsfThreadMgr->AssociateFocus( michael@0: sTsfTextStore->mWidget->GetWindowHandle(), michael@0: nullptr, getter_AddRefs(prevFocusedDocumentMgr)); michael@0: NS_ASSERTION(SUCCEEDED(hr), "Disassociating focus failed"); michael@0: NS_ASSERTION(prevFocusedDocumentMgr == sTsfTextStore->mDocumentMgr, michael@0: "different documentMgr has been associated with the window"); michael@0: sTsfTextStore->Destroy(); michael@0: } michael@0: HRESULT hr = sTsfThreadMgr->SetFocus(sTsfDisabledDocumentMgr); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsIMEUpdatePreference michael@0: nsTextStore::GetIMEUpdatePreference() michael@0: { michael@0: if (sTsfThreadMgr && sTsfTextStore && sTsfTextStore->mDocumentMgr) { michael@0: nsRefPtr docMgr; michael@0: sTsfThreadMgr->GetFocus(getter_AddRefs(docMgr)); michael@0: if (docMgr == sTsfTextStore->mDocumentMgr) { michael@0: nsIMEUpdatePreference updatePreference( michael@0: nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE | michael@0: nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE | michael@0: nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE | michael@0: nsIMEUpdatePreference::NOTIFY_DURING_DEACTIVE); michael@0: // nsTextStore shouldn't notify TSF of selection change and text change michael@0: // which are caused by composition. michael@0: updatePreference.DontNotifyChangesCausedByComposition(); michael@0: return updatePreference; michael@0: } michael@0: } michael@0: return nsIMEUpdatePreference(); michael@0: } michael@0: michael@0: nsresult michael@0: nsTextStore::OnTextChangeInternal(const IMENotification& aIMENotification) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::OnTextChangeInternal(aIMENotification={ " michael@0: "mMessage=0x%08X, mTextChangeData={ mStartOffset=%lu, " michael@0: "mOldEndOffset=%lu, mNewEndOffset=%lu}), mSink=0x%p, mSinkMask=%s, " michael@0: "mComposition.IsComposing()=%s", michael@0: this, aIMENotification.mMessage, michael@0: aIMENotification.mTextChangeData.mStartOffset, michael@0: aIMENotification.mTextChangeData.mOldEndOffset, michael@0: aIMENotification.mTextChangeData.mNewEndOffset, mSink.get(), michael@0: GetSinkMaskNameStr(mSinkMask).get(), michael@0: GetBoolName(mComposition.IsComposing()))); michael@0: michael@0: if (IsReadLocked()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mSelection.MarkDirty(); michael@0: michael@0: if (!mSink || !(mSinkMask & TS_AS_TEXT_CHANGE)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!aIMENotification.mTextChangeData.IsInInt32Range()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::OnTextChangeInternal() FAILED due to " michael@0: "offset is too big for calling mSink->OnTextChange()...", michael@0: this)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Some TIPs are confused by text change notification during composition. michael@0: // Especially, some of them stop working for composition in our process. michael@0: // For preventing it, let's commit the composition. michael@0: if (mComposition.IsComposing()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnTextChangeInternal(), " michael@0: "committing the composition for avoiding making TIP confused...", michael@0: this)); michael@0: CommitCompositionInternal(false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: TS_TEXTCHANGE textChange; michael@0: textChange.acpStart = michael@0: static_cast(aIMENotification.mTextChangeData.mStartOffset); michael@0: textChange.acpOldEnd = michael@0: static_cast(aIMENotification.mTextChangeData.mOldEndOffset); michael@0: textChange.acpNewEnd = michael@0: static_cast(aIMENotification.mTextChangeData.mNewEndOffset); michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnTextChangeInternal(), calling " michael@0: "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, " michael@0: "acpNewEnd=%ld })...", this, textChange.acpStart, michael@0: textChange.acpOldEnd, textChange.acpNewEnd)); michael@0: mSink->OnTextChange(0, &textChange); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextStore::OnSelectionChangeInternal(void) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::OnSelectionChangeInternal(), " michael@0: "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, " michael@0: "mComposition.IsComposing()=%s", michael@0: this, mSink.get(), GetSinkMaskNameStr(mSinkMask).get(), michael@0: GetBoolName(mIsRecordingActionsWithoutLock), michael@0: GetBoolName(mComposition.IsComposing()))); michael@0: michael@0: if (IsReadLocked()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mSelection.MarkDirty(); michael@0: michael@0: if (!mSink || !(mSinkMask & TS_AS_SEL_CHANGE)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Some TIPs are confused by selection change notification during composition. michael@0: // Especially, some of them stop working for composition in our process. michael@0: // For preventing it, let's commit the composition. michael@0: if (mComposition.IsComposing()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnSelectionChangeInternal(), " michael@0: "committing the composition for avoiding making TIP confused...", michael@0: this)); michael@0: CommitCompositionInternal(false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!mIsRecordingActionsWithoutLock) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnSelectionChangeInternal(), calling " michael@0: "mSink->OnSelectionChange()...", this)); michael@0: mSink->OnSelectionChange(); michael@0: } else { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnSelectionChangeInternal(), pending " michael@0: "a call of mSink->OnSelectionChange()...", this)); michael@0: mPendingOnSelectionChange = true; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextStore::OnLayoutChangeInternal() michael@0: { michael@0: NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE); michael@0: NS_ENSURE_TRUE(mSink, NS_ERROR_FAILURE); michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::OnLayoutChangeInternal(), calling " michael@0: "mSink->OnLayoutChange()...", this)); michael@0: HRESULT hr = mSink->OnLayoutChange(TS_LC_CHANGE, TEXTSTORE_DEFAULT_VIEW); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsTextStore::CreateNativeCaret() michael@0: { michael@0: // This method must work only on desktop application. michael@0: if (XRE_GetWindowsEnvironment() != WindowsEnvironmentType_Desktop) { michael@0: return; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::CreateNativeCaret(), " michael@0: "mComposition.IsComposing()=%s", michael@0: this, GetBoolName(mComposition.IsComposing()))); michael@0: michael@0: Selection& currentSel = CurrentSelection(); michael@0: if (currentSel.IsDirty()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::CreateNativeCaret() FAILED due to " michael@0: "CurrentSelection() failure", this)); michael@0: return; michael@0: } michael@0: michael@0: // XXX If this is called without composition and the selection isn't michael@0: // collapsed, is it OK? michael@0: uint32_t caretOffset = currentSel.MaxOffset(); michael@0: michael@0: WidgetQueryContentEvent queryCaretRect(true, NS_QUERY_CARET_RECT, mWidget); michael@0: queryCaretRect.InitForQueryCaretRect(caretOffset); michael@0: mWidget->InitEvent(queryCaretRect); michael@0: mWidget->DispatchWindowEvent(&queryCaretRect); michael@0: if (!queryCaretRect.mSucceeded) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::CreateNativeCaret() FAILED due to " michael@0: "NS_QUERY_CARET_RECT failure (offset=%d)", this, caretOffset)); michael@0: return; michael@0: } michael@0: michael@0: nsIntRect& caretRect = queryCaretRect.mReply.mRect; michael@0: mNativeCaretIsCreated = ::CreateCaret(mWidget->GetWindowHandle(), nullptr, michael@0: caretRect.width, caretRect.height); michael@0: if (!mNativeCaretIsCreated) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::CreateNativeCaret() FAILED due to " michael@0: "CreateCaret() failure", this)); michael@0: return; michael@0: } michael@0: michael@0: nsWindow* window = static_cast(mWidget.get()); michael@0: nsWindow* toplevelWindow = window->GetTopLevelWindow(false); michael@0: if (!toplevelWindow) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::CreateNativeCaret() FAILED due to " michael@0: "no top level window", this)); michael@0: return; michael@0: } michael@0: michael@0: if (toplevelWindow != window) { michael@0: caretRect.MoveBy(toplevelWindow->WidgetToScreenOffset()); michael@0: caretRect.MoveBy(-window->WidgetToScreenOffset()); michael@0: } michael@0: michael@0: ::SetCaretPos(caretRect.x, caretRect.y); michael@0: } michael@0: michael@0: bool michael@0: nsTextStore::EnsureInitActiveTIPKeyboard() michael@0: { michael@0: if (mOnActivatedCalled) { michael@0: return true; michael@0: } michael@0: michael@0: if (IsVistaOrLater()) { michael@0: nsRefPtr profileMgr; michael@0: HRESULT hr = michael@0: sInputProcessorProfiles->QueryInterface(IID_ITfInputProcessorProfileMgr, michael@0: getter_AddRefs(profileMgr)); michael@0: if (FAILED(hr) || !profileMgr) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::EnsureInitActiveLanguageProfile(), FAILED " michael@0: "to get input processor profile manager, hr=0x%08X", this, hr)); michael@0: return false; michael@0: } michael@0: michael@0: TF_INPUTPROCESSORPROFILE profile; michael@0: hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile); michael@0: if (hr == S_FALSE) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::EnsureInitActiveLanguageProfile(), FAILED " michael@0: "to get active keyboard layout profile due to no active profile, " michael@0: "hr=0x%08X", this, hr)); michael@0: // XXX Should we call OnActivated() with arguments like non-TIP in this michael@0: // case? michael@0: return false; michael@0: } michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::EnsureInitActiveLanguageProfile(), FAILED " michael@0: "to get active TIP keyboard, hr=0x%08X", this, hr)); michael@0: return false; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::EnsureInitActiveLanguageProfile(), " michael@0: "calling OnActivated() manually...", this)); michael@0: OnActivated(profile.dwProfileType, profile.langid, profile.clsid, michael@0: profile.catid, profile.guidProfile, ::GetKeyboardLayout(0), michael@0: TF_IPSINK_FLAG_ACTIVE); michael@0: return true; michael@0: } michael@0: michael@0: LANGID langID; michael@0: HRESULT hr = sInputProcessorProfiles->GetCurrentLanguage(&langID); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::EnsureInitActiveLanguageProfile(), FAILED " michael@0: "to get current language ID, hr=0x%08X", this, hr)); michael@0: return false; michael@0: } michael@0: michael@0: nsRefPtr enumLangProfiles; michael@0: hr = sInputProcessorProfiles->EnumLanguageProfiles(langID, michael@0: getter_AddRefs(enumLangProfiles)); michael@0: if (FAILED(hr) || !enumLangProfiles) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: 0x%p nsTextStore::EnsureInitActiveLanguageProfile(), FAILED " michael@0: "to get language profiles enumerator, hr=0x%08X", this, hr)); michael@0: return false; michael@0: } michael@0: michael@0: TF_LANGUAGEPROFILE profile; michael@0: ULONG fetch = 0; michael@0: while (SUCCEEDED(enumLangProfiles->Next(1, &profile, &fetch)) && fetch) { michael@0: if (!profile.fActive || profile.catid != GUID_TFCAT_TIP_KEYBOARD) { michael@0: continue; michael@0: } michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::EnsureInitActiveLanguageProfile(), " michael@0: "calling OnActivated() manually...", this)); michael@0: bool isTIP = profile.guidProfile != GUID_NULL; michael@0: OnActivated(isTIP ? TF_PROFILETYPE_INPUTPROCESSOR : michael@0: TF_PROFILETYPE_KEYBOARDLAYOUT, michael@0: profile.langid, profile.clsid, profile.catid, michael@0: profile.guidProfile, ::GetKeyboardLayout(0), michael@0: TF_IPSINK_FLAG_ACTIVE); michael@0: return true; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::EnsureInitActiveLanguageProfile(), " michael@0: "calling OnActivated() without active TIP manually...", this)); michael@0: OnActivated(TF_PROFILETYPE_KEYBOARDLAYOUT, michael@0: langID, CLSID_NULL, GUID_TFCAT_TIP_KEYBOARD, michael@0: GUID_NULL, ::GetKeyboardLayout(0), michael@0: TF_IPSINK_FLAG_ACTIVE); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsTextStore::CommitCompositionInternal(bool aDiscard) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::CommitCompositionInternal(aDiscard=%s), " michael@0: "mSink=0x%p, mContext=0x%p, mComposition.mView=0x%p, " michael@0: "mComposition.mString=\"%s\"", michael@0: this, GetBoolName(aDiscard), mSink.get(), mContext.get(), michael@0: mComposition.mView.get(), michael@0: NS_ConvertUTF16toUTF8(mComposition.mString).get())); michael@0: michael@0: if (mComposition.IsComposing() && aDiscard) { michael@0: LONG endOffset = mComposition.EndOffset(); michael@0: mComposition.mString.Truncate(0); michael@0: if (mSink && !mLock) { michael@0: TS_TEXTCHANGE textChange; michael@0: textChange.acpStart = mComposition.mStart; michael@0: textChange.acpOldEnd = endOffset; michael@0: textChange.acpNewEnd = mComposition.mStart; michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: 0x%p nsTextStore::CommitCompositionInternal(), calling" michael@0: "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, " michael@0: "acpNewEnd=%ld })...", this, textChange.acpStart, michael@0: textChange.acpOldEnd, textChange.acpNewEnd)); michael@0: mSink->OnTextChange(0, &textChange); michael@0: } michael@0: } michael@0: // Terminate two contexts, the base context (mContext) and the top michael@0: // if the top context is not the same as the base context michael@0: nsRefPtr context = mContext; michael@0: do { michael@0: if (context) { michael@0: nsRefPtr services; michael@0: context->QueryInterface(IID_ITfContextOwnerCompositionServices, michael@0: getter_AddRefs(services)); michael@0: if (services) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: 0x%p nsTextStore::CommitCompositionInternal(), " michael@0: "requesting TerminateComposition() for the context 0x%p...", michael@0: this, context.get())); michael@0: services->TerminateComposition(nullptr); michael@0: } michael@0: } michael@0: if (context != mContext) michael@0: break; michael@0: if (mDocumentMgr) michael@0: mDocumentMgr->GetTop(getter_AddRefs(context)); michael@0: } while (context != mContext); michael@0: } michael@0: michael@0: static michael@0: bool michael@0: GetCompartment(IUnknown* pUnk, michael@0: const GUID& aID, michael@0: ITfCompartment** aCompartment) michael@0: { michael@0: if (!pUnk) return false; michael@0: michael@0: nsRefPtr compMgr; michael@0: pUnk->QueryInterface(IID_ITfCompartmentMgr, getter_AddRefs(compMgr)); michael@0: if (!compMgr) return false; michael@0: michael@0: return SUCCEEDED(compMgr->GetCompartment(aID, aCompartment)) && michael@0: (*aCompartment) != nullptr; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsTextStore::SetIMEOpenState(bool aState) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: nsTextStore::SetIMEOpenState(aState=%s)", GetBoolName(aState))); michael@0: michael@0: nsRefPtr comp; michael@0: if (!GetCompartment(sTsfThreadMgr, michael@0: GUID_COMPARTMENT_KEYBOARD_OPENCLOSE, michael@0: getter_AddRefs(comp))) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: nsTextStore::SetIMEOpenState() FAILED due to" michael@0: "no compartment available")); michael@0: return; michael@0: } michael@0: michael@0: VARIANT variant; michael@0: variant.vt = VT_I4; michael@0: variant.lVal = aState; michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: nsTextStore::SetIMEOpenState(), setting " michael@0: "0x%04X to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...", michael@0: variant.lVal)); michael@0: comp->SetValue(sTsfClientId, &variant); michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: nsTextStore::GetIMEOpenState(void) michael@0: { michael@0: nsRefPtr comp; michael@0: if (!GetCompartment(sTsfThreadMgr, michael@0: GUID_COMPARTMENT_KEYBOARD_OPENCLOSE, michael@0: getter_AddRefs(comp))) michael@0: return false; michael@0: michael@0: VARIANT variant; michael@0: ::VariantInit(&variant); michael@0: if (SUCCEEDED(comp->GetValue(&variant)) && variant.vt == VT_I4) michael@0: return variant.lVal != 0; michael@0: michael@0: ::VariantClear(&variant); // clear up in case variant.vt != VT_I4 michael@0: return false; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsTextStore::SetInputContext(nsWindowBase* aWidget, michael@0: const InputContext& aContext, michael@0: const InputContextAction& aAction) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: nsTextStore::SetInputContext(aWidget=%p, " michael@0: "aContext.mIMEState.mEnabled=%s, aAction.mFocusChange=%s), " michael@0: "ThinksHavingFocus()=%s", michael@0: aWidget, GetIMEEnabledName(aContext.mIMEState.mEnabled), michael@0: GetFocusChangeName(aAction.mFocusChange), michael@0: GetBoolName(ThinksHavingFocus()))); michael@0: michael@0: NS_ENSURE_TRUE_VOID(sTsfTextStore); michael@0: sTsfTextStore->SetInputScope(aContext.mHTMLInputType); michael@0: michael@0: if (aAction.mFocusChange != InputContextAction::FOCUS_NOT_CHANGED) { michael@0: return; michael@0: } michael@0: michael@0: // If focus isn't actually changed but the enabled state is changed, michael@0: // emulate the focus move. michael@0: if (!ThinksHavingFocus() && michael@0: aContext.mIMEState.mEnabled == IMEState::ENABLED) { michael@0: OnFocusChange(true, aWidget, aContext.mIMEState.mEnabled); michael@0: } else if (ThinksHavingFocus() && michael@0: aContext.mIMEState.mEnabled != IMEState::ENABLED) { michael@0: OnFocusChange(false, aWidget, aContext.mIMEState.mEnabled); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsTextStore::MarkContextAsKeyboardDisabled(ITfContext* aContext) michael@0: { michael@0: VARIANT variant_int4_value1; michael@0: variant_int4_value1.vt = VT_I4; michael@0: variant_int4_value1.lVal = 1; michael@0: michael@0: nsRefPtr comp; michael@0: if (!GetCompartment(aContext, michael@0: GUID_COMPARTMENT_KEYBOARD_DISABLED, michael@0: getter_AddRefs(comp))) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::MarkContextAsKeyboardDisabled() failed" michael@0: "aContext=0x%p...", aContext)); michael@0: return; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: nsTextStore::MarkContextAsKeyboardDisabled(), setting " michael@0: "to disable context 0x%p...", michael@0: aContext)); michael@0: comp->SetValue(sTsfClientId, &variant_int4_value1); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsTextStore::MarkContextAsEmpty(ITfContext* aContext) michael@0: { michael@0: VARIANT variant_int4_value1; michael@0: variant_int4_value1.vt = VT_I4; michael@0: variant_int4_value1.lVal = 1; michael@0: michael@0: nsRefPtr comp; michael@0: if (!GetCompartment(aContext, michael@0: GUID_COMPARTMENT_EMPTYCONTEXT, michael@0: getter_AddRefs(comp))) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::MarkContextAsEmpty() failed" michael@0: "aContext=0x%p...", aContext)); michael@0: return; michael@0: } michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_DEBUG, michael@0: ("TSF: nsTextStore::MarkContextAsEmpty(), setting " michael@0: "to mark empty context 0x%p...", aContext)); michael@0: comp->SetValue(sTsfClientId, &variant_int4_value1); michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: nsTextStore::IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID, michael@0: REFGUID aProfile) michael@0: { michael@0: if (aTextService == CLSID_NULL || aProfile == GUID_NULL) { michael@0: return false; michael@0: } michael@0: michael@0: nsRefPtr enumLangProfiles; michael@0: HRESULT hr = michael@0: sInputProcessorProfiles->EnumLanguageProfiles(aLangID, michael@0: getter_AddRefs(enumLangProfiles)); michael@0: if (FAILED(hr) || !enumLangProfiles) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::IsTIPCategoryKeyboard(), FAILED " michael@0: "to get language profiles enumerator, hr=0x%08X", hr)); michael@0: return false; michael@0: } michael@0: michael@0: TF_LANGUAGEPROFILE profile; michael@0: ULONG fetch = 0; michael@0: while (SUCCEEDED(enumLangProfiles->Next(1, &profile, &fetch)) && fetch) { michael@0: // XXX We're not sure a profile is registered with two or more categories. michael@0: if (profile.clsid == aTextService && michael@0: profile.guidProfile == aProfile && michael@0: profile.catid == GUID_TFCAT_TIP_KEYBOARD) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsTextStore::GetTIPDescription(REFCLSID aTextService, LANGID aLangID, michael@0: REFGUID aProfile, nsAString& aDescription) michael@0: { michael@0: aDescription.Truncate(); michael@0: michael@0: if (aTextService == CLSID_NULL || aProfile == GUID_NULL) { michael@0: return; michael@0: } michael@0: michael@0: BSTR description = nullptr; michael@0: HRESULT hr = michael@0: sInputProcessorProfiles->GetLanguageProfileDescription(aTextService, michael@0: aLangID, michael@0: aProfile, michael@0: &description); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::InitActiveTIPDescription() FAILED due to " michael@0: "GetLanguageProfileDescription() failure, hr=0x%08X", hr)); michael@0: return; michael@0: } michael@0: michael@0: if (description && description[0]) { michael@0: aDescription.Assign(description); michael@0: } michael@0: ::SysFreeString(description); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsTextStore::Initialize() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (!sTextStoreLog) { michael@0: sTextStoreLog = PR_NewLogModule("nsTextStoreWidgets"); michael@0: } michael@0: #endif michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: nsTextStore::Initialize() is called...")); michael@0: michael@0: if (sTsfThreadMgr) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::Initialize() FAILED due to already initialized")); michael@0: return; michael@0: } michael@0: michael@0: bool enableTsf = Preferences::GetBool(kPrefNameTSFEnabled, false); michael@0: // Migrate legacy TSF pref to new pref. This should be removed in next michael@0: // release cycle or later. michael@0: if (!enableTsf && Preferences::GetBool(kLegacyPrefNameTSFEnabled, false)) { michael@0: enableTsf = true; michael@0: Preferences::SetBool(kPrefNameTSFEnabled, true); michael@0: Preferences::ClearUser(kLegacyPrefNameTSFEnabled); michael@0: } michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: nsTextStore::Initialize(), TSF is %s", michael@0: enableTsf ? "enabled" : "disabled")); michael@0: if (!enableTsf) { michael@0: return; michael@0: } michael@0: michael@0: // XXX MSDN documents that ITfInputProcessorProfiles is available only on michael@0: // desktop apps. However, there is no known way to obtain michael@0: // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles michael@0: // instance. michael@0: nsRefPtr inputProcessorProfiles; michael@0: HRESULT hr = michael@0: ::CoCreateInstance(CLSID_TF_InputProcessorProfiles, nullptr, michael@0: CLSCTX_INPROC_SERVER, michael@0: IID_ITfInputProcessorProfiles, michael@0: getter_AddRefs(inputProcessorProfiles)); michael@0: if (FAILED(hr) || !inputProcessorProfiles) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::Initialize() FAILED to create input processor " michael@0: "profiles, hr=0x%08X", hr)); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr threadMgr; michael@0: hr = ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, michael@0: CLSCTX_INPROC_SERVER, IID_ITfThreadMgr, michael@0: getter_AddRefs(threadMgr)); michael@0: if (FAILED(hr) || !threadMgr) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::Initialize() FAILED to " michael@0: "create the thread manager, hr=0x%08X", hr)); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr messagePump; michael@0: hr = threadMgr->QueryInterface(IID_ITfMessagePump, michael@0: getter_AddRefs(messagePump)); michael@0: if (FAILED(hr) || !messagePump) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::Initialize() FAILED to " michael@0: "QI message pump from the thread manager, hr=0x%08X", hr)); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr keystrokeMgr; michael@0: hr = threadMgr->QueryInterface(IID_ITfKeystrokeMgr, michael@0: getter_AddRefs(keystrokeMgr)); michael@0: if (FAILED(hr) || !keystrokeMgr) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::Initialize() FAILED to " michael@0: "QI keystroke manager from the thread manager, hr=0x%08X", hr)); michael@0: return; michael@0: } michael@0: michael@0: hr = threadMgr->Activate(&sTsfClientId); michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::Initialize() FAILED to activate, hr=0x%08X", hr)); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr displayAttributeMgr; michael@0: hr = ::CoCreateInstance(CLSID_TF_DisplayAttributeMgr, nullptr, michael@0: CLSCTX_INPROC_SERVER, IID_ITfDisplayAttributeMgr, michael@0: getter_AddRefs(displayAttributeMgr)); michael@0: if (FAILED(hr) || !displayAttributeMgr) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::Initialize() FAILED to create " michael@0: "a display attribute manager instance, hr=0x%08X", hr)); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr categoryMgr; michael@0: hr = ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, michael@0: CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr, michael@0: getter_AddRefs(categoryMgr)); michael@0: if (FAILED(hr) || !categoryMgr) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::Initialize() FAILED to create " michael@0: "a category manager instance, hr=0x%08X", hr)); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr disabledDocumentMgr; michael@0: hr = threadMgr->CreateDocumentMgr(getter_AddRefs(disabledDocumentMgr)); michael@0: if (FAILED(hr) || !disabledDocumentMgr) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::Initialize() FAILED to create " michael@0: "a document manager for disabled mode, hr=0x%08X", hr)); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr disabledContext; michael@0: DWORD editCookie = 0; michael@0: hr = disabledDocumentMgr->CreateContext(sTsfClientId, 0, nullptr, michael@0: getter_AddRefs(disabledContext), michael@0: &editCookie); michael@0: if (FAILED(hr) || !disabledContext) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::Initialize() FAILED to create " michael@0: "a context for disabled mode, hr=0x%08X", hr)); michael@0: return; michael@0: } michael@0: michael@0: MarkContextAsKeyboardDisabled(disabledContext); michael@0: MarkContextAsEmpty(disabledContext); michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: nsTextStore::Initialize() is creating " michael@0: "an nsTextStore instance...")); michael@0: nsRefPtr textStore = new nsTextStore(); michael@0: if (!textStore->Init(threadMgr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::Initialize() FAILED to initialize nsTextStore " michael@0: "instance")); michael@0: return; michael@0: } michael@0: michael@0: inputProcessorProfiles.swap(sInputProcessorProfiles); michael@0: threadMgr.swap(sTsfThreadMgr); michael@0: messagePump.swap(sMessagePump); michael@0: keystrokeMgr.swap(sKeystrokeMgr); michael@0: displayAttributeMgr.swap(sDisplayAttrMgr); michael@0: categoryMgr.swap(sCategoryMgr); michael@0: disabledDocumentMgr.swap(sTsfDisabledDocumentMgr); michael@0: disabledContext.swap(sTsfDisabledContext); michael@0: textStore.swap(sTsfTextStore); michael@0: michael@0: sCreateNativeCaretForATOK = michael@0: Preferences::GetBool("intl.tsf.hack.atok.create_native_caret", true); michael@0: michael@0: MOZ_ASSERT(!sFlushTIPInputMessage); michael@0: sFlushTIPInputMessage = ::RegisterWindowMessageW(L"Flush TIP Input Message"); michael@0: michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, michael@0: ("TSF: nsTextStore::Initialize(), sTsfThreadMgr=0x%p, " michael@0: "sTsfClientId=0x%08X, sTsfTextStore=0x%p, sDisplayAttrMgr=0x%p, " michael@0: "sCategoryMgr=0x%p, sTsfDisabledDocumentMgr=0x%p, sTsfDisabledContext=%p, " michael@0: "sCreateNativeCaretForATOK=%s", michael@0: sTsfThreadMgr, sTsfClientId, sTsfTextStore, sDisplayAttrMgr, sCategoryMgr, michael@0: sTsfDisabledDocumentMgr, sTsfDisabledContext, michael@0: GetBoolName(sCreateNativeCaretForATOK))); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsTextStore::Terminate(void) michael@0: { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: nsTextStore::Terminate()")); michael@0: michael@0: NS_IF_RELEASE(sDisplayAttrMgr); michael@0: NS_IF_RELEASE(sCategoryMgr); michael@0: NS_IF_RELEASE(sTsfTextStore); michael@0: NS_IF_RELEASE(sTsfDisabledDocumentMgr); michael@0: NS_IF_RELEASE(sTsfDisabledContext); michael@0: NS_IF_RELEASE(sInputProcessorProfiles); michael@0: sTsfClientId = 0; michael@0: if (sTsfThreadMgr) { michael@0: sTsfThreadMgr->Deactivate(); michael@0: NS_RELEASE(sTsfThreadMgr); michael@0: NS_RELEASE(sMessagePump); michael@0: NS_RELEASE(sKeystrokeMgr); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: nsTextStore::ProcessRawKeyMessage(const MSG& aMsg) michael@0: { michael@0: if (!sKeystrokeMgr) { michael@0: return false; // not in TSF mode michael@0: } michael@0: michael@0: if (aMsg.message == WM_KEYDOWN) { michael@0: BOOL eaten; michael@0: HRESULT hr = sKeystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten); michael@0: if (FAILED(hr) || !eaten) { michael@0: return false; michael@0: } michael@0: hr = sKeystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten); michael@0: return SUCCEEDED(hr) && eaten; michael@0: } michael@0: if (aMsg.message == WM_KEYUP) { michael@0: BOOL eaten; michael@0: HRESULT hr = sKeystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten); michael@0: if (FAILED(hr) || !eaten) { michael@0: return false; michael@0: } michael@0: hr = sKeystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten); michael@0: return SUCCEEDED(hr) && eaten; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsTextStore::ProcessMessage(nsWindowBase* aWindow, UINT aMessage, michael@0: WPARAM& aWParam, LPARAM& aLParam, michael@0: MSGResult& aResult) michael@0: { michael@0: switch (aMessage) { michael@0: case WM_IME_SETCONTEXT: michael@0: // If a windowless plugin had focus and IME was handled on it, composition michael@0: // window was set the position. After that, even in TSF mode, WinXP keeps michael@0: // to use composition window at the position if the active IME is not michael@0: // aware TSF. For avoiding this issue, we need to hide the composition michael@0: // window here. michael@0: if (aWParam) { michael@0: aLParam &= ~ISC_SHOWUICOMPOSITIONWINDOW; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /******************************************************************/ michael@0: /* nsTextStore::Composition */ michael@0: /******************************************************************/ michael@0: michael@0: void michael@0: nsTextStore::Composition::Start(ITfCompositionView* aCompositionView, michael@0: LONG aCompositionStartOffset, michael@0: const nsAString& aCompositionString) michael@0: { michael@0: mView = aCompositionView; michael@0: mString = aCompositionString; michael@0: mStart = aCompositionStartOffset; michael@0: } michael@0: michael@0: void michael@0: nsTextStore::Composition::End() michael@0: { michael@0: mView = nullptr; michael@0: mString.Truncate(); michael@0: } michael@0: michael@0: /****************************************************************************** michael@0: * nsTextStore::Content michael@0: *****************************************************************************/ michael@0: michael@0: const nsDependentSubstring michael@0: nsTextStore::Content::GetSelectedText() const michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: return GetSubstring(static_cast(mSelection.StartOffset()), michael@0: static_cast(mSelection.Length())); michael@0: } michael@0: michael@0: const nsDependentSubstring michael@0: nsTextStore::Content::GetSubstring(uint32_t aStart, uint32_t aLength) const michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: return nsDependentSubstring(mText, aStart, aLength); michael@0: } michael@0: michael@0: void michael@0: nsTextStore::Content::ReplaceSelectedTextWith(const nsAString& aString) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: ReplaceTextWith(mSelection.StartOffset(), mSelection.Length(), aString); michael@0: } michael@0: michael@0: inline uint32_t michael@0: FirstDifferentCharOffset(const nsAString& aStr1, const nsAString& aStr2) michael@0: { michael@0: MOZ_ASSERT(aStr1 != aStr2); michael@0: uint32_t i = 0; michael@0: uint32_t minLength = std::min(aStr1.Length(), aStr2.Length()); michael@0: for (; i < minLength && aStr1[i] == aStr2[i]; i++) { michael@0: /* nothing to do */ michael@0: } michael@0: return i; michael@0: } michael@0: michael@0: void michael@0: nsTextStore::Content::ReplaceTextWith(LONG aStart, LONG aLength, michael@0: const nsAString& aReplaceString) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: const nsDependentSubstring replacedString = michael@0: GetSubstring(static_cast(aStart), michael@0: static_cast(aLength)); michael@0: if (aReplaceString != replacedString) { michael@0: uint32_t firstDifferentOffset = michael@0: static_cast(aStart) + FirstDifferentCharOffset(aReplaceString, michael@0: replacedString); michael@0: mMinTextModifiedOffset = michael@0: std::min(mMinTextModifiedOffset, firstDifferentOffset); michael@0: if (mComposition.IsComposing()) { michael@0: // Emulate text insertion during compositions, because during a michael@0: // composition, editor expects the whole composition string to michael@0: // be sent in NS_TEXT_TEXT, not just the inserted part. michael@0: // The actual NS_TEXT_TEXT will be sent in SetSelection or michael@0: // OnUpdateComposition. michael@0: MOZ_ASSERT(aStart >= mComposition.mStart); michael@0: MOZ_ASSERT(aStart + aLength <= mComposition.EndOffset()); michael@0: mComposition.mString.Replace( michael@0: static_cast(aStart - mComposition.mStart), michael@0: static_cast(aLength), aReplaceString); michael@0: } michael@0: mText.Replace(static_cast(aStart), michael@0: static_cast(aLength), aReplaceString); michael@0: } michael@0: // Selection should be collapsed at the end of the inserted string. michael@0: mSelection.CollapseAt( michael@0: static_cast(aStart) + aReplaceString.Length()); michael@0: } michael@0: michael@0: void michael@0: nsTextStore::Content::StartComposition(ITfCompositionView* aCompositionView, michael@0: const PendingAction& aCompStart, michael@0: bool aPreserveSelection) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: MOZ_ASSERT(aCompositionView); michael@0: MOZ_ASSERT(!mComposition.mView); michael@0: MOZ_ASSERT(aCompStart.mType == PendingAction::COMPOSITION_START); michael@0: michael@0: mComposition.Start(aCompositionView, aCompStart.mSelectionStart, michael@0: GetSubstring(static_cast(aCompStart.mSelectionStart), michael@0: static_cast(aCompStart.mSelectionLength))); michael@0: if (!aPreserveSelection) { michael@0: mSelection.SetSelection(mComposition.mStart, mComposition.mString.Length(), michael@0: false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTextStore::Content::EndComposition(const PendingAction& aCompEnd) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: MOZ_ASSERT(mComposition.mView); michael@0: MOZ_ASSERT(aCompEnd.mType == PendingAction::COMPOSITION_END); michael@0: michael@0: mSelection.CollapseAt(mComposition.mStart + aCompEnd.mData.Length()); michael@0: mComposition.End(); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: // static michael@0: bool michael@0: nsTextStore::CurrentKeyboardLayoutHasIME() michael@0: { michael@0: if (!sInputProcessorProfiles) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::CurrentKeyboardLayoutHasIME() FAILED due to there is " michael@0: "no input processor profiles instance")); michael@0: return false; michael@0: } michael@0: nsRefPtr profileMgr; michael@0: HRESULT hr = michael@0: sInputProcessorProfiles->QueryInterface(IID_ITfInputProcessorProfileMgr, michael@0: getter_AddRefs(profileMgr)); michael@0: if (FAILED(hr) || !profileMgr) { michael@0: // On Windows Vista or later, ImmIsIME() API always returns true. michael@0: // If we failed to obtain the profile manager, we cannot know if current michael@0: // keyboard layout has IME. michael@0: if (IsVistaOrLater()) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::CurrentKeyboardLayoutHasIME() FAILED to query " michael@0: "ITfInputProcessorProfileMgr")); michael@0: return false; michael@0: } michael@0: // If the profiles instance doesn't have ITfInputProcessorProfileMgr michael@0: // interface, that means probably we're running on WinXP or WinServer2003 michael@0: // (except WinServer2003 R2). Then, we should use ImmIsIME(). michael@0: return ::ImmIsIME(::GetKeyboardLayout(0)); michael@0: } michael@0: michael@0: TF_INPUTPROCESSORPROFILE profile; michael@0: hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile); michael@0: if (hr == S_FALSE) { michael@0: return false; // not found or not active michael@0: } michael@0: if (FAILED(hr)) { michael@0: PR_LOG(sTextStoreLog, PR_LOG_ERROR, michael@0: ("TSF: nsTextStore::CurrentKeyboardLayoutHasIME() FAILED to retreive " michael@0: "active profile")); michael@0: return false; michael@0: } michael@0: return (profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR); michael@0: } michael@0: #endif // #ifdef DEBUG