/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #ifdef MOZ_LOGGING #define FORCE_PR_LOG /* Allow logging in the release build */ #endif // MOZ_LOGGING #include "prlog.h" #include "nscore.h" #include "nsTextStore.h" #include "nsWindow.h" #include "nsPrintfCString.h" #include "WinUtils.h" #include "mozilla/Preferences.h" using namespace mozilla; using namespace mozilla::widget; /******************************************************************/ /* nsTextStore */ /******************************************************************/ ITfThreadMgr* nsTextStore::sTsfThreadMgr = NULL; ITfDisplayAttributeMgr* nsTextStore::sDisplayAttrMgr = NULL; ITfCategoryMgr* nsTextStore::sCategoryMgr = NULL; DWORD nsTextStore::sTsfClientId = 0; nsTextStore* nsTextStore::sTsfTextStore = NULL; UINT nsTextStore::sFlushTIPInputMessage = 0; #ifdef PR_LOGGING /** * TSF related code should log its behavior even on release build especially * in the interface methods. * * In interface methods, use PR_LOG_ALWAYS. * In internal methods, use PR_LOG_DEBUG for logging normal behavior. * For logging error, use PR_LOG_ERROR. * * When an instance method is called, start with following text: * "TSF: 0x%p nsFoo::Bar(", the 0x%p should be the "this" of the nsFoo. * after that, start with: * "TSF: 0x%p nsFoo::Bar(" * In an internal method, start with following text: * "TSF: 0x%p nsFoo::Bar(" * When a static method is called, start with following text: * "TSF: nsFoo::Bar(" */ PRLogModuleInfo* sTextStoreLog = nullptr; static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; } static const char* GetIMEEnabledName(IMEState::Enabled aIMEEnabled) { switch (aIMEEnabled) { case IMEState::DISABLED: return "DISABLED"; case IMEState::ENABLED: return "ENABLED"; case IMEState::PASSWORD: return "PASSWORD"; case IMEState::PLUGIN: return "PLUGIN"; default: return "Invalid"; } } static nsCString GetRIIDNameStr(REFIID aRIID) { LPOLESTR str = nullptr; HRESULT hr = ::StringFromIID(aRIID, &str); if (FAILED(hr) || !str || !str[0]) { return EmptyCString(); } nsAutoString key(L"Interface\\"); key += str; nsAutoCString result; PRUnichar buf[256]; if (WinUtils::GetRegistryKey(HKEY_CLASSES_ROOT, key.get(), nullptr, buf, sizeof(buf))) { result = NS_ConvertUTF16toUTF8(buf); } else { result = NS_ConvertUTF16toUTF8(str); } ::CoTaskMemFree(str); return result; } static const char* GetCommonReturnValueName(HRESULT aResult) { switch (aResult) { case S_OK: return "S_OK"; case E_ABORT: return "E_ABORT"; case E_ACCESSDENIED: return "E_ACCESSDENIED"; case E_FAIL: return "E_FAIL"; case E_HANDLE: return "E_HANDLE"; case E_INVALIDARG: return "E_INVALIDARG"; case E_NOINTERFACE: return "E_NOINTERFACE"; case E_NOTIMPL: return "E_NOTIMPL"; case E_OUTOFMEMORY: return "E_OUTOFMEMORY"; case E_POINTER: return "E_POINTER"; case E_UNEXPECTED: return "E_UNEXPECTED"; default: return SUCCEEDED(aResult) ? "Succeeded" : "Failed"; } } static const char* GetTextStoreReturnValueName(HRESULT aResult) { switch (aResult) { case TS_E_FORMAT: return "TS_E_FORMAT"; case TS_E_INVALIDPOINT: return "TS_E_INVALIDPOINT"; case TS_E_INVALIDPOS: return "TS_E_INVALIDPOS"; case TS_E_NOINTERFACE: return "TS_E_NOINTERFACE"; case TS_E_NOLAYOUT: return "TS_E_NOLAYOUT"; case TS_E_NOLOCK: return "TS_E_NOLOCK"; case TS_E_NOOBJECT: return "TS_E_NOOBJECT"; case TS_E_NOSELECTION: return "TS_E_NOSELECTION"; case TS_E_NOSERVICE: return "TS_E_NOSERVICE"; case TS_E_READONLY: return "TS_E_READONLY"; case TS_E_SYNCHRONOUS: return "TS_E_SYNCHRONOUS"; case TS_S_ASYNC: return "TS_S_ASYNC"; default: return GetCommonReturnValueName(aResult); } } static const nsCString GetSinkMaskNameStr(DWORD aSinkMask) { nsAutoCString description; if (aSinkMask & TS_AS_TEXT_CHANGE) { description.AppendLiteral("TS_AS_TEXT_CHANGE"); } if (aSinkMask & TS_AS_SEL_CHANGE) { if (!description.IsEmpty()) { description.AppendLiteral(" | "); } description.AppendLiteral("TS_AS_SEL_CHANGE"); } if (aSinkMask & TS_AS_LAYOUT_CHANGE) { if (!description.IsEmpty()) { description.AppendLiteral(" | "); } description.AppendLiteral("TS_AS_LAYOUT_CHANGE"); } if (aSinkMask & TS_AS_ATTR_CHANGE) { if (!description.IsEmpty()) { description.AppendLiteral(" | "); } description.AppendLiteral("TS_AS_ATTR_CHANGE"); } if (aSinkMask & TS_AS_STATUS_CHANGE) { if (!description.IsEmpty()) { description.AppendLiteral(" | "); } description.AppendLiteral("TS_AS_STATUS_CHANGE"); } if (description.IsEmpty()) { description.AppendLiteral("not-specified"); } return description; } static const char* GetActiveSelEndName(TsActiveSelEnd aSelEnd) { return aSelEnd == TS_AE_NONE ? "TS_AE_NONE" : aSelEnd == TS_AE_START ? "TS_AE_START" : aSelEnd == TS_AE_END ? "TS_AE_END" : "Unknown"; } static const nsCString GetLockFlagNameStr(DWORD aLockFlags) { nsAutoCString description; if ((aLockFlags & TS_LF_READWRITE) == TS_LF_READWRITE) { description.AppendLiteral("TS_LF_READWRITE"); } else if (aLockFlags & TS_LF_READ) { description.AppendLiteral("TS_LF_READ"); } if (aLockFlags & TS_LF_SYNC) { if (!description.IsEmpty()) { description.AppendLiteral(" | "); } description.AppendLiteral("TS_LF_SYNC"); } if (description.IsEmpty()) { description.AppendLiteral("not-specified"); } return description; } static const char* GetTextRunTypeName(TsRunType aRunType) { switch (aRunType) { case TS_RT_PLAIN: return "TS_RT_PLAIN"; case TS_RT_HIDDEN: return "TS_RT_HIDDEN"; case TS_RT_OPAQUE: return "TS_RT_OPAQUE"; default: return "Unknown"; } } static nsCString GetColorName(const TF_DA_COLOR &aColor) { switch (aColor.type) { case TF_CT_NONE: return NS_LITERAL_CSTRING("TF_CT_NONE"); case TF_CT_SYSCOLOR: return nsPrintfCString("TF_CT_SYSCOLOR, nIndex:0x%08X", static_cast(aColor.nIndex)); case TF_CT_COLORREF: return nsPrintfCString("TF_CT_COLORREF, cr:0x%08X", static_cast(aColor.cr)); break; default: return nsPrintfCString("Unknown(%08X)", static_cast(aColor.type)); } } static nsCString GetLineStyleName(TF_DA_LINESTYLE aLineStyle) { switch (aLineStyle) { case TF_LS_NONE: return NS_LITERAL_CSTRING("TF_LS_NONE"); case TF_LS_SOLID: return NS_LITERAL_CSTRING("TF_LS_SOLID"); case TF_LS_DOT: return NS_LITERAL_CSTRING("TF_LS_DOT"); case TF_LS_DASH: return NS_LITERAL_CSTRING("TF_LS_DASH"); case TF_LS_SQUIGGLE: return NS_LITERAL_CSTRING("TF_LS_SQUIGGLE"); default: { return nsPrintfCString("Unknown(%08X)", static_cast(aLineStyle)); } } } static nsCString GetClauseAttrName(TF_DA_ATTR_INFO aAttr) { switch (aAttr) { case TF_ATTR_INPUT: return NS_LITERAL_CSTRING("TF_ATTR_INPUT"); case TF_ATTR_TARGET_CONVERTED: return NS_LITERAL_CSTRING("TF_ATTR_TARGET_CONVERTED"); case TF_ATTR_CONVERTED: return NS_LITERAL_CSTRING("TF_ATTR_CONVERTED"); case TF_ATTR_TARGET_NOTCONVERTED: return NS_LITERAL_CSTRING("TF_ATTR_TARGET_NOTCONVERTED"); case TF_ATTR_INPUT_ERROR: return NS_LITERAL_CSTRING("TF_ATTR_INPUT_ERROR"); case TF_ATTR_FIXEDCONVERTED: return NS_LITERAL_CSTRING("TF_ATTR_FIXEDCONVERTED"); case TF_ATTR_OTHER: return NS_LITERAL_CSTRING("TF_ATTR_OTHER"); default: { return nsPrintfCString("Unknown(%08X)", static_cast(aAttr)); } } } static nsCString GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE &aDispAttr) { nsAutoCString str; str = "crText:{ "; str += GetColorName(aDispAttr.crText); str += " }, crBk:{ "; str += GetColorName(aDispAttr.crBk); str += " }, lsStyle: "; str += GetLineStyleName(aDispAttr.lsStyle); str += ", fBoldLine: "; str += GetBoolName(aDispAttr.fBoldLine); str += ", crLine:{ "; str += GetColorName(aDispAttr.crLine); str += " }, bAttr: "; str += GetClauseAttrName(aDispAttr.bAttr); return str; } #endif // #ifdef PR_LOGGING nsTextStore::nsTextStore() { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::nsTestStore(): instance is created", this)); mRefCnt = 1; mEditCookie = 0; mSinkMask = 0; mWindow = nullptr; mLock = 0; mLockQueued = 0; mTextChange.acpStart = INT32_MAX; mTextChange.acpOldEnd = mTextChange.acpNewEnd = 0; mLastDispatchedTextEvent = nullptr; } nsTextStore::~nsTextStore() { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore instance is destroyed, " "mWindow=0x%p, mDocumentMgr=0x%p, mContext=0x%p", this, mWindow, mDocumentMgr.get(), mContext.get())); if (mCompositionTimer) { mCompositionTimer->Cancel(); mCompositionTimer = nullptr; } SaveTextEvent(nullptr); } bool nsTextStore::Create(nsWindow* aWindow, IMEState::Enabled aIMEEnabled) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::Create(aWindow=0x%p, aIMEEnabled=%s)", this, aWindow, GetIMEEnabledName(aIMEEnabled))); if (mDocumentMgr) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::Create() FAILED due to already initialized", this)); return false; } // Create document manager HRESULT hr = sTsfThreadMgr->CreateDocumentMgr( getter_AddRefs(mDocumentMgr)); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::Create() FAILED to create DocumentMgr " "(0x%08X)", this, hr)); return false; } mWindow = aWindow; // Create context and add it to document manager hr = mDocumentMgr->CreateContext(sTsfClientId, 0, static_cast(this), getter_AddRefs(mContext), &mEditCookie); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::Create() FAILED to create the context " "(0x%08X)", this, hr)); mDocumentMgr = NULL; return false; } SetInputContextInternal(aIMEEnabled); hr = mDocumentMgr->Push(mContext); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::Create() FAILED to push the context (0x%08X)", this, hr)); // XXX Why don't we use NS_IF_RELEASE() here?? mContext = NULL; mDocumentMgr = NULL; return false; } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::Create() succeeded: " "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08X", this, mDocumentMgr.get(), mContext.get(), mEditCookie)); return true; } bool nsTextStore::Destroy(void) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::Destroy()", this)); if (mWindow) { // When blurred, Tablet Input Panel posts "blur" messages // and try to insert text when the message is retrieved later. // But by that time the text store is already destroyed, // so try to get the message early MSG msg; if (::PeekMessageW(&msg, mWindow->GetWindowHandle(), sFlushTIPInputMessage, sFlushTIPInputMessage, PM_REMOVE)) { ::DispatchMessageW(&msg); } } mContext = NULL; if (mDocumentMgr) { mDocumentMgr->Pop(TF_POPF_ALL); mDocumentMgr = NULL; } mSink = NULL; mWindow = NULL; PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::Destroy() succeeded", this)); return true; } STDMETHODIMP nsTextStore::QueryInterface(REFIID riid, void** ppv) { *ppv=NULL; if ( (IID_IUnknown == riid) || (IID_ITextStoreACP == riid) ) { *ppv = static_cast(this); } else if (IID_ITfContextOwnerCompositionSink == riid) { *ppv = static_cast(this); } if (*ppv) { AddRef(); return S_OK; } PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::QueryInterface() FAILED, riid=%s", this, GetRIIDNameStr(riid).get())); return E_NOINTERFACE; } STDMETHODIMP_(ULONG) nsTextStore::AddRef() { return ++mRefCnt; } STDMETHODIMP_(ULONG) nsTextStore::Release() { --mRefCnt; if (0 != mRefCnt) return mRefCnt; delete this; return 0; } STDMETHODIMP nsTextStore::AdviseSink(REFIID riid, IUnknown *punk, DWORD dwMask) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), " "mSink=0x%p, mSinkMask=%s", this, GetRIIDNameStr(riid).get(), punk, GetSinkMaskNameStr(dwMask).get(), mSink.get(), GetSinkMaskNameStr(mSinkMask).get())); if (!punk) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::AdviseSink() FAILED due to the null punk", this)); return E_UNEXPECTED; } if (IID_ITextStoreACPSink != riid) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::AdviseSink() FAILED due to " "unsupported interface", this)); return E_INVALIDARG; // means unsupported interface. } if (!mSink) { // Install sink punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink)); if (!mSink) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::AdviseSink() FAILED due to " "punk not having the interface", this)); return E_UNEXPECTED; } } else { // If sink is already installed we check to see if they are the same // Get IUnknown from both sides for comparison nsRefPtr comparison1, comparison2; punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1)); mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2)); if (comparison1 != comparison2) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::AdviseSink() FAILED due to " "the sink being different from the stored sink", this)); return CONNECT_E_ADVISELIMIT; } } // Update mask either for a new sink or an existing sink mSinkMask = dwMask; return S_OK; } STDMETHODIMP nsTextStore::UnadviseSink(IUnknown *punk) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::UnadviseSink(punk=0x%p), mSink=0x%p", this, punk, mSink.get())); if (!punk) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::UnadviseSink() FAILED due to the null punk", this)); return E_INVALIDARG; } if (!mSink) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::UnadviseSink() FAILED due to " "any sink not stored", this)); return CONNECT_E_NOCONNECTION; } // Get IUnknown from both sides for comparison nsRefPtr comparison1, comparison2; punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1)); mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2)); // Unadvise only if sinks are the same if (comparison1 != comparison2) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::UnadviseSink() FAILED due to " "the sink being different from the stored sink", this)); return CONNECT_E_NOCONNECTION; } mSink = NULL; mSinkMask = 0; return S_OK; } STDMETHODIMP nsTextStore::RequestLock(DWORD dwLockFlags, HRESULT *phrSession) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::RequestLock(dwLockFlags=%s, phrSession=0x%p), " "mLock=%s", this, GetLockFlagNameStr(dwLockFlags).get(), phrSession, GetLockFlagNameStr(mLock).get())); if (!mSink) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::RequestLock() FAILED due to " "any sink not stored", this)); return E_FAIL; } if (!phrSession) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::RequestLock() FAILED due to " "null phrSession", this)); return E_INVALIDARG; } if (!mLock) { // put on lock mLock = dwLockFlags & (~TS_LF_SYNC); PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::RequestLock() notifying OnLockGranted()...", this)); *phrSession = mSink->OnLockGranted(mLock); while (mLockQueued) { mLock = mLockQueued; mLockQueued = 0; PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::RequestLock() notifying OnLockGranted() " "with mLockQueued (%s)...", this, GetLockFlagNameStr(mLock).get())); mSink->OnLockGranted(mLock); } mLock = 0; PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::RequestLock() succeeded: *phrSession=%s", this, GetTextStoreReturnValueName(*phrSession))); return S_OK; } // only time when reentrant lock is allowed is when caller holds a // read-only lock and is requesting an async write lock if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags) && !(dwLockFlags & TS_LF_SYNC)) { *phrSession = TS_S_ASYNC; mLockQueued = dwLockFlags & (~TS_LF_SYNC); PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::RequestLock() stores the request in the " "queue, *phrSession=TS_S_ASYNC", this)); return S_OK; } // no more locks allowed PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::RequestLock() didn't allow to lock, " "*phrSession=TS_E_SYNCHRONOUS", this)); *phrSession = TS_E_SYNCHRONOUS; return E_FAIL; } STDMETHODIMP nsTextStore::GetStatus(TS_STATUS *pdcs) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetStatus(pdcs=0x%p)", this, pdcs)); if (!pdcs) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetStatus() FAILED due to null pdcs", this)); return E_INVALIDARG; } pdcs->dwDynamicFlags = 0; // we use a "flat" text model for TSF support so no hidden text pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT; return S_OK; } STDMETHODIMP nsTextStore::QueryInsert(LONG acpTestStart, LONG acpTestEnd, ULONG cch, LONG *pacpResultStart, LONG *pacpResultEnd) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::QueryInsert(acpTestStart=%ld, " "acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)", this, acpTestStart, acpTestEnd, cch, acpTestStart, acpTestEnd)); if (!pacpResultStart || !pacpResultEnd) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::QueryInsert() FAILED due to " "the null argument", this)); return E_INVALIDARG; } if (acpTestStart < 0 || acpTestStart > acpTestEnd) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::QueryInsert() FAILED due to " "wrong argument", this)); return E_INVALIDARG; } // XXX need to adjust to cluster boundary // Assume we are given good offsets for now *pacpResultStart = acpTestStart; *pacpResultEnd = acpTestStart + cch; PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::QueryInsert() succeeded: " "*pacpResultStart=%ld, *pacpResultEnd=%ld)", this, *pacpResultStart, *pacpResultEnd)); return S_OK; } STDMETHODIMP nsTextStore::GetSelection(ULONG ulIndex, ULONG ulCount, TS_SELECTION_ACP *pSelection, ULONG *pcFetched) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetSelection(ulIndex=%lu, ulCount=%lu, " "pSelection=0x%p, pcFetched=0x%p)", this, ulIndex, ulCount, pSelection, pcFetched)); if (!IsReadLocked()) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetSelection() FAILED due to not locked", this)); return TS_E_NOLOCK; } if (!ulCount || !pSelection || !pcFetched) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetSelection() FAILED due to " "null argument", this)); return E_INVALIDARG; } *pcFetched = 0; if (ulIndex != static_cast(TS_DEFAULT_SELECTION) && ulIndex != 0) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetSelection() FAILED due to " "unsupported selection", this)); return TS_E_NOSELECTION; } if (!GetSelectionInternal(*pSelection)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetSelection() FAILED to get selection", this)); return E_FAIL; } *pcFetched = 1; PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetSelection() succeeded", this)); return S_OK; } bool nsTextStore::GetSelectionInternal(TS_SELECTION_ACP &aSelectionACP) { if (mCompositionView) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::GetSelectionInternal(), " "there is no composition view", this)); // Emulate selection during compositions aSelectionACP = mCompositionSelection; } else { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::GetSelectionInternal(), " "try to get normal selection...", this)); // Construct and initialize an event to get selection info nsQueryContentEvent event(true, NS_QUERY_SELECTED_TEXT, mWindow); mWindow->InitEvent(event); mWindow->DispatchWindowEvent(&event); if (!event.mSucceeded) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetSelectionInternal() FAILED to " "query selected text", this)); return false; } // Usually the selection anchor (beginning) position corresponds to the // TSF start and the selection focus (ending) position corresponds to // the TSF end, but if selection is reversed the focus now corresponds // to the TSF start and the anchor now corresponds to the TSF end aSelectionACP.acpStart = event.mReply.mOffset; aSelectionACP.acpEnd = aSelectionACP.acpStart + event.mReply.mString.Length(); aSelectionACP.style.ase = event.mReply.mString.Length() && event.mReply.mReversed ? TS_AE_START : TS_AE_END; // No support for interim character aSelectionACP.style.fInterimChar = FALSE; } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetSelectionInternal() succeeded: " "acpStart=%lu, acpEnd=%lu, style.ase=%s, style.fInterimChar=%s", this, aSelectionACP.acpStart, aSelectionACP.acpEnd, GetActiveSelEndName(aSelectionACP.style.ase), GetBoolName(aSelectionACP.style.fInterimChar))); return true; } static HRESULT GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength) { nsRefPtr rangeACP; aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP)); NS_ENSURE_TRUE(rangeACP, E_FAIL); return rangeACP->GetExtent(aStart, aLength); } static uint32_t GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE &aDisplayAttr) { uint32_t result; switch (aDisplayAttr.bAttr) { case TF_ATTR_TARGET_CONVERTED: result = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT; break; case TF_ATTR_CONVERTED: result = NS_TEXTRANGE_CONVERTEDTEXT; break; case TF_ATTR_TARGET_NOTCONVERTED: result = NS_TEXTRANGE_SELECTEDRAWTEXT; break; default: result = NS_TEXTRANGE_RAWINPUT; break; } return result; } HRESULT nsTextStore::GetDisplayAttribute(ITfProperty* aAttrProperty, ITfRange* aRange, TF_DISPLAYATTRIBUTE* aResult) { NS_ENSURE_TRUE(aAttrProperty, E_FAIL); NS_ENSURE_TRUE(aRange, E_FAIL); NS_ENSURE_TRUE(aResult, E_FAIL); HRESULT hr; #ifdef PR_LOGGING if (PR_LOG_TEST(sTextStoreLog, PR_LOG_DEBUG)) { LONG start = 0, length = 0; hr = GetRangeExtent(aRange, &start, &length); PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::GetDisplayAttribute(): " "GetDisplayAttribute range=%ld-%ld (hr=%s)", this, start - mCompositionStart, start - mCompositionStart + length, GetCommonReturnValueName(hr))); } #endif VARIANT propValue; ::VariantInit(&propValue); hr = aAttrProperty->GetValue(TfEditCookie(mEditCookie), aRange, &propValue); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to " "ITfProperty::GetValue() failed", this)); return hr; } if (VT_I4 != propValue.vt) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to " "ITfProperty::GetValue() returns non-VT_I4 value", this)); ::VariantClear(&propValue); return E_FAIL; } NS_ENSURE_TRUE(sCategoryMgr, E_FAIL); GUID guid; hr = sCategoryMgr->GetGUID(DWORD(propValue.lVal), &guid); ::VariantClear(&propValue); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to " "ITfCategoryMgr::GetGUID() failed", this)); return hr; } NS_ENSURE_TRUE(sDisplayAttrMgr, E_FAIL); nsRefPtr info; hr = sDisplayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info), NULL); if (FAILED(hr) || !info) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to " "ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed", this)); return hr; } hr = info->GetAttributeInfo(aResult); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to " "ITfDisplayAttributeInfo::GetAttributeInfo() failed", this)); return hr; } PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::GetDisplayAttribute() succeeded: " "Result={ %s }", this, GetDisplayAttrStr(*aResult).get())); return S_OK; } HRESULT nsTextStore::SaveTextEvent(const nsTextEvent* aEvent) { if (mLastDispatchedTextEvent) { if (mLastDispatchedTextEvent->rangeArray) delete [] mLastDispatchedTextEvent->rangeArray; delete mLastDispatchedTextEvent; mLastDispatchedTextEvent = nullptr; } if (!aEvent) return S_OK; mLastDispatchedTextEvent = new nsTextEvent(true, NS_TEXT_TEXT, nullptr); if (!mLastDispatchedTextEvent) return E_OUTOFMEMORY; mLastDispatchedTextEvent->rangeCount = aEvent->rangeCount; mLastDispatchedTextEvent->theText = aEvent->theText; mLastDispatchedTextEvent->rangeArray = nullptr; if (aEvent->rangeCount == 0) return S_OK; NS_ENSURE_TRUE(aEvent->rangeArray, E_FAIL); mLastDispatchedTextEvent->rangeArray = new nsTextRange[aEvent->rangeCount]; if (!mLastDispatchedTextEvent->rangeArray) { delete mLastDispatchedTextEvent; mLastDispatchedTextEvent = nullptr; return E_OUTOFMEMORY; } memcpy(mLastDispatchedTextEvent->rangeArray, aEvent->rangeArray, sizeof(nsTextRange) * aEvent->rangeCount); return S_OK; } static bool IsSameTextEvent(const nsTextEvent* aEvent1, const nsTextEvent* aEvent2) { NS_PRECONDITION(aEvent1 || aEvent2, "both events are null"); NS_PRECONDITION(aEvent2 && (aEvent1 != aEvent2), "both events are same instance"); return (aEvent1 && aEvent2 && aEvent1->rangeCount == aEvent2->rangeCount && aEvent1->theText == aEvent2->theText && (aEvent1->rangeCount == 0 || ((aEvent1->rangeArray && aEvent2->rangeArray) && !memcmp(aEvent1->rangeArray, aEvent2->rangeArray, sizeof(nsTextRange) * aEvent1->rangeCount)))); } HRESULT nsTextStore::UpdateCompositionExtent(ITfRange* aRangeNew) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::UpdateCompositionExtent(aRangeNew=0x%p), " "mCompositionView=0x%p", this, aRangeNew, mCompositionView.get())); if (!mCompositionView) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::UpdateCompositionExtent() FAILED due to " "no composition view", this)); return E_FAIL; } HRESULT hr; nsRefPtr pComposition(mCompositionView); nsRefPtr composingRange(aRangeNew); if (!composingRange) { hr = pComposition->GetRange(getter_AddRefs(composingRange)); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::UpdateCompositionExtent() FAILED due to " "pComposition->GetRange() failure", this)); return hr; } } // Get starting offset of the composition LONG compStart = 0, compLength = 0; hr = GetRangeExtent(composingRange, &compStart, &compLength); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::UpdateCompositionExtent() FAILED due to " "GetRangeExtent() failure", this)); return hr; } PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::UpdateCompositionExtent(), range=%ld-%ld, " "mCompositionStart=%ld, mCompositionString.Length()=%lu", this, compStart, compStart + compLength, mCompositionStart, mCompositionString.Length())); if (mCompositionStart != compStart || mCompositionString.Length() != (ULONG)compLength) { // If the queried composition length is different from the length // of our composition string, OnUpdateComposition is being called // because a part of the original composition was committed. // Reflect that by committing existing composition and starting // a new one. OnEndComposition followed by OnStartComposition // will accomplish this automagically. OnEndComposition(pComposition); OnStartCompositionInternal(pComposition, composingRange, true); } else { mCompositionLength = compLength; } PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::UpdateCompositionExtent() succeeded", this)); return S_OK; } static bool GetColor(const TF_DA_COLOR &aTSFColor, nscolor &aResult) { switch (aTSFColor.type) { case TF_CT_SYSCOLOR: { DWORD sysColor = ::GetSysColor(aTSFColor.nIndex); aResult = NS_RGB(GetRValue(sysColor), GetGValue(sysColor), GetBValue(sysColor)); return true; } case TF_CT_COLORREF: aResult = NS_RGB(GetRValue(aTSFColor.cr), GetGValue(aTSFColor.cr), GetBValue(aTSFColor.cr)); return true; case TF_CT_NONE: default: return false; } } static bool GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle, uint8_t &aTextRangeLineStyle) { switch (aTSFLineStyle) { case TF_LS_NONE: aTextRangeLineStyle = nsTextRangeStyle::LINESTYLE_NONE; return true; case TF_LS_SOLID: aTextRangeLineStyle = nsTextRangeStyle::LINESTYLE_SOLID; return true; case TF_LS_DOT: aTextRangeLineStyle = nsTextRangeStyle::LINESTYLE_DOTTED; return true; case TF_LS_DASH: aTextRangeLineStyle = nsTextRangeStyle::LINESTYLE_DASHED; return true; case TF_LS_SQUIGGLE: aTextRangeLineStyle = nsTextRangeStyle::LINESTYLE_WAVY; return true; default: return false; } } HRESULT nsTextStore::SendTextEventForCompositionString() { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::SendTextEventForCompositionString(), " "mCompositionView=0x%p, mCompositionString=\"%s\"", this, mCompositionView.get(), NS_ConvertUTF16toUTF8(mCompositionString).get())); if (!mCompositionView) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() FAILED " "due to no composition view", this)); return E_FAIL; } // Getting display attributes is *really* complicated! // We first get the context and the property objects to query for // attributes, but since a big range can have a variety of values for // the attribute, we have to find out all the ranges that have distinct // attribute values. Then we query for what the value represents through // the display attribute manager and translate that to nsTextRange to be // sent in NS_TEXT_TEXT nsRefPtr attrPropetry; HRESULT hr = mContext->GetProperty(GUID_PROP_ATTRIBUTE, getter_AddRefs(attrPropetry)); if (FAILED(hr) || !attrPropetry) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() FAILED " "due to mContext->GetProperty() failure", this)); return FAILED(hr) ? hr : E_FAIL; } // Use NS_TEXT_TEXT to set composition string nsTextEvent event(true, NS_TEXT_TEXT, mWindow); mWindow->InitEvent(event); nsRefPtr composingRange; hr = mCompositionView->GetRange(getter_AddRefs(composingRange)); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() FAILED " "due to mCompositionView->GetRange() failure", this)); return hr; } nsRefPtr enumRanges; hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie), getter_AddRefs(enumRanges), composingRange); if (FAILED(hr) || !enumRanges) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() FAILED " "due to attrPropetry->EnumRanges() failure", this)); return FAILED(hr) ? hr : E_FAIL; } nsAutoTArray textRanges; nsTextRange newRange; // No matter if we have display attribute info or not, // we always pass in at least one range to NS_TEXT_TEXT newRange.mStartOffset = 0; newRange.mEndOffset = mCompositionString.Length(); newRange.mRangeType = NS_TEXTRANGE_RAWINPUT; textRanges.AppendElement(newRange); nsRefPtr range; while (S_OK == enumRanges->Next(1, getter_AddRefs(range), NULL) && range) { LONG start = 0, length = 0; if (FAILED(GetRangeExtent(range, &start, &length))) continue; nsTextRange newRange; newRange.mStartOffset = uint32_t(start - mCompositionStart); // The end of the last range in the array is // always kept at the end of composition newRange.mEndOffset = mCompositionString.Length(); TF_DISPLAYATTRIBUTE attr; hr = GetDisplayAttribute(attrPropetry, range, &attr); if (FAILED(hr)) { newRange.mRangeType = NS_TEXTRANGE_RAWINPUT; } else { newRange.mRangeType = GetGeckoSelectionValue(attr); if (GetColor(attr.crText, newRange.mRangeStyle.mForegroundColor)) { newRange.mRangeStyle.mDefinedStyles |= nsTextRangeStyle::DEFINED_FOREGROUND_COLOR; } if (GetColor(attr.crBk, newRange.mRangeStyle.mBackgroundColor)) { newRange.mRangeStyle.mDefinedStyles |= nsTextRangeStyle::DEFINED_BACKGROUND_COLOR; } if (GetColor(attr.crLine, newRange.mRangeStyle.mUnderlineColor)) { newRange.mRangeStyle.mDefinedStyles |= nsTextRangeStyle::DEFINED_UNDERLINE_COLOR; } if (GetLineStyle(attr.lsStyle, newRange.mRangeStyle.mLineStyle)) { newRange.mRangeStyle.mDefinedStyles |= nsTextRangeStyle::DEFINED_LINESTYLE; newRange.mRangeStyle.mIsBoldLine = attr.fBoldLine != 0; } } nsTextRange& lastRange = textRanges[textRanges.Length() - 1]; if (lastRange.mStartOffset == newRange.mStartOffset) { // Replace range if last range is the same as this one // So that ranges don't overlap and confuse the editor lastRange = newRange; } else { lastRange.mEndOffset = newRange.mStartOffset; textRanges.AppendElement(newRange); } } // We need to hack for Korean Input System which is Korean standard TIP. // It sets no change style to IME selection (the selection is always only // one). So, the composition string looks like normal (or committed) string. // At this time, mCompositionSelection range is same as the composition // string range. Other applications set a wide caret which covers the // composition string, however, Gecko doesn't support the wide caret drawing // now (Gecko doesn't support XOR drawing), unfortunately. For now, we should // change the range style to undefined. if (mCompositionSelection.acpStart != mCompositionSelection.acpEnd && textRanges.Length() == 1) { nsTextRange& range = textRanges[0]; LONG start = NS_MIN(mCompositionSelection.acpStart, mCompositionSelection.acpEnd); LONG end = NS_MAX(mCompositionSelection.acpStart, mCompositionSelection.acpEnd); if ((LONG)range.mStartOffset == start - mCompositionStart && (LONG)range.mEndOffset == end - mCompositionStart && range.mRangeStyle.IsNoChangeStyle()) { range.mRangeStyle.Clear(); // The looks of selected type is better than others. range.mRangeType = NS_TEXTRANGE_SELECTEDRAWTEXT; } } // The caret position has to be collapsed. LONG caretPosition = NS_MAX(mCompositionSelection.acpStart, mCompositionSelection.acpEnd); caretPosition -= mCompositionStart; nsTextRange caretRange; caretRange.mStartOffset = caretRange.mEndOffset = uint32_t(caretPosition); caretRange.mRangeType = NS_TEXTRANGE_CARETPOSITION; textRanges.AppendElement(caretRange); event.theText = mCompositionString; event.rangeArray = textRanges.Elements(); event.rangeCount = textRanges.Length(); // If we are already send same text event, we should not resend it. Because // it can be a cause of flickering. if (IsSameTextEvent(mLastDispatchedTextEvent, &event)) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() " "succeeded but any DOM events are not dispatched", this)); return S_OK; } if (mCompositionString != mLastDispatchedCompositionString) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() " "dispatching compositionupdate event...", this)); nsCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE, mWindow); mWindow->InitEvent(compositionUpdate); compositionUpdate.data = mCompositionString; mLastDispatchedCompositionString = mCompositionString; mWindow->DispatchWindowEvent(&compositionUpdate); } if (mWindow && !mWindow->Destroyed()) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() " "dispatching text event...", this)); mWindow->DispatchWindowEvent(&event); } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() " "succeeded", this)); return SaveTextEvent(&event); } HRESULT nsTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection, bool aDispatchTextEvent) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::SetSelectionInternal(pSelection=%ld-%ld, " "aDispatchTextEvent=%s), %s", this, pSelection->acpStart, pSelection->acpEnd, GetBoolName(aDispatchTextEvent), mCompositionView ? "there is composition view" : "there is no composition view")); if (mCompositionView) { if (aDispatchTextEvent) { HRESULT hr = UpdateCompositionExtent(nullptr); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SetSelectionInternal() FAILED due to " "UpdateCompositionExtent() failure", this)); return hr; } } if (pSelection->acpStart < mCompositionStart || pSelection->acpEnd > mCompositionStart + static_cast(mCompositionString.Length())) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SetSelectionInternal() FAILED due to " "the selection being out of the composition string", this)); return TS_E_INVALIDPOS; } // Emulate selection during compositions mCompositionSelection = *pSelection; if (aDispatchTextEvent) { HRESULT hr = SendTextEventForCompositionString(); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SetSelectionInternal() FAILED due to " "SendTextEventForCompositionString() failure", this)); return hr; } } return S_OK; } else { nsSelectionEvent event(true, NS_SELECTION_SET, mWindow); event.mOffset = pSelection->acpStart; event.mLength = uint32_t(pSelection->acpEnd - pSelection->acpStart); event.mReversed = pSelection->style.ase == TS_AE_START; mWindow->InitEvent(event); mWindow->DispatchWindowEvent(&event); if (!event.mSucceeded) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SetSelectionInternal() FAILED due to " "NS_SELECTION_SET failure", this)); return E_FAIL; } } return S_OK; } STDMETHODIMP nsTextStore::SetSelection(ULONG ulCount, const TS_SELECTION_ACP *pSelection) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::SetSelection(ulCount=%lu)", this, ulCount)); if (!IsReadWriteLocked()) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SetSelection() FAILED due to " "not locked (read-write)", this)); return TS_E_NOLOCK; } if (ulCount != 1) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SetSelection() FAILED due to " "trying setting multiple selection", this)); return E_INVALIDARG; } if (!pSelection) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SetSelection() FAILED due to " "null argument", this)); return E_INVALIDARG; } HRESULT hr = SetSelectionInternal(pSelection, true); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SetSelection() FAILED due to " "SetSelectionInternal() failure", this)); } else { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::SetSelection() succeeded", this)); } return hr; } STDMETHODIMP nsTextStore::GetText(LONG acpStart, LONG acpEnd, WCHAR *pchPlain, ULONG cchPlainReq, ULONG *pcchPlainOut, TS_RUNINFO *prgRunInfo, ULONG ulRunInfoReq, ULONG *pulRunInfoOut, LONG *pacpNext) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetText(acpStart=%ld, acpEnd=%ld, pchPlain=0x%p, " "cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, ulRunInfoReq=%lu, " "pulRunInfoOut=0x%p, pacpNext=0x%p), %s, mCompositionStart=%ld, " "mCompositionLength=%ld, mCompositionString.Length()=%lu", this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, prgRunInfo, ulRunInfoReq, pulRunInfoOut, pacpNext, mCompositionView ? "there is composition view" : "there is no composition view", mCompositionStart, mCompositionLength, mCompositionString.Length())); if (!IsReadLocked()) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetText() FAILED due to " "not locked (read)", this)); return TS_E_NOLOCK; } if (!pcchPlainOut || (!pchPlain && !prgRunInfo) || !cchPlainReq != !pchPlain || !ulRunInfoReq != !prgRunInfo) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetText() FAILED due to " "invalid argument", this)); return E_INVALIDARG; } if (acpStart < 0 || acpEnd < -1 || (acpEnd != -1 && acpStart > acpEnd)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetText() FAILED due to " "invalid position", this)); return TS_E_INVALIDPOS; } // Making sure to NULL-terminate string just to be on the safe side *pcchPlainOut = 0; if (pchPlain && cchPlainReq) *pchPlain = 0; if (pulRunInfoOut) *pulRunInfoOut = 0; if (pacpNext) *pacpNext = acpStart; if (prgRunInfo && ulRunInfoReq) { prgRunInfo->uCount = 0; prgRunInfo->type = TS_RT_PLAIN; } uint32_t length = -1 == acpEnd ? UINT32_MAX : uint32_t(acpEnd - acpStart); if (cchPlainReq && cchPlainReq - 1 < length) { length = cchPlainReq - 1; } if (length) { LONG compNewStart = 0, compOldEnd = 0, compNewEnd = 0; if (mCompositionView) { // Sometimes GetText gets called between InsertTextAtSelection and // OnUpdateComposition. In this case the returned text would // be out of sync because we haven't sent NS_TEXT_TEXT in // OnUpdateComposition yet. Manually resync here. compOldEnd = NS_MIN(LONG(length) + acpStart, mCompositionLength + mCompositionStart); compNewEnd = NS_MIN(LONG(length) + acpStart, LONG(mCompositionString.Length()) + mCompositionStart); compNewStart = NS_MAX(acpStart, mCompositionStart); // Check if the range is affected if (compOldEnd > compNewStart || compNewEnd > compNewStart) { NS_ASSERTION(compOldEnd >= mCompositionStart && compNewEnd >= mCompositionStart, "Range end is less than start\n"); length = uint32_t(LONG(length) + compOldEnd - compNewEnd); } } // Send NS_QUERY_TEXT_CONTENT to get text content nsQueryContentEvent event(true, NS_QUERY_TEXT_CONTENT, mWindow); mWindow->InitEvent(event); event.InitForQueryTextContent(uint32_t(acpStart), length); mWindow->DispatchWindowEvent(&event); if (!event.mSucceeded) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetText() FAILED due to " "NS_QUERY_TEXT_CONTENT failure: length=%lu", this, length)); return E_FAIL; } if (compOldEnd > compNewStart || compNewEnd > compNewStart) { // Resync composition string const PRUnichar* compStrStart = mCompositionString.BeginReading() + NS_MAX(compNewStart - mCompositionStart, 0); event.mReply.mString.Replace(compNewStart - acpStart, compOldEnd - mCompositionStart, compStrStart, compNewEnd - mCompositionStart); length = uint32_t(LONG(length) - compOldEnd + compNewEnd); } if (-1 != acpEnd && event.mReply.mString.Length() != length) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetText() FAILED due to " "unexpected length=%lu", this, length)); return TS_E_INVALIDPOS; } length = NS_MIN(length, event.mReply.mString.Length()); if (pchPlain && cchPlainReq) { memcpy(pchPlain, event.mReply.mString.BeginReading(), length * sizeof(*pchPlain)); pchPlain[length] = 0; *pcchPlainOut = length; } if (prgRunInfo && ulRunInfoReq) { prgRunInfo->uCount = length; prgRunInfo->type = TS_RT_PLAIN; if (pulRunInfoOut) *pulRunInfoOut = 1; } if (pacpNext) *pacpNext = acpStart + length; } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetText() succeeded: pcchPlainOut=0x%p, " "*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, " "*pacpNext=%ld)", this, pcchPlainOut, prgRunInfo ? prgRunInfo->uCount : 0, prgRunInfo ? GetTextRunTypeName(prgRunInfo->type) : "N/A", pulRunInfoOut ? pulRunInfoOut : 0, pacpNext ? pacpNext : 0)); return S_OK; } STDMETHODIMP nsTextStore::SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd, const WCHAR *pchText, ULONG cch, TS_TEXTCHANGE *pChange) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::SetText(dwFlags=%s, acpStart=%ld, acpEnd=%ld, " "pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), %s", this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" : "not-specified", acpStart, acpEnd, pchText, pchText && cch ? NS_ConvertUTF16toUTF8(pchText, cch).get() : "", cch, pChange, mCompositionView ? "there is composition view" : "there is no composition view")); // Per SDK documentation, and since we don't have better // ways to do this, this method acts as a helper to // call SetSelection followed by InsertTextAtSelection if (!IsReadWriteLocked()) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SetText() FAILED due to " "not locked (read)", this)); return TS_E_NOLOCK; } TS_SELECTION_ACP selection; selection.acpStart = acpStart; selection.acpEnd = acpEnd; selection.style.ase = TS_AE_END; selection.style.fInterimChar = 0; // Set selection to desired range HRESULT hr = SetSelectionInternal(&selection); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SetText() FAILED due to " "SetSelectionInternal() failure", this)); return hr; } // Replace just selected text if (!InsertTextAtSelectionInternal(nsDependentString(pchText, cch), pChange)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::SetText() FAILED due to " "InsertTextAtSelectionInternal() failure", this)); return E_FAIL; } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::SetText() succeeded: pChange={ " "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }", this, pChange ? pChange->acpStart : 0, pChange ? pChange->acpOldEnd : 0, pChange ? pChange->acpNewEnd : 0)); return S_OK; } STDMETHODIMP nsTextStore::GetFormattedText(LONG acpStart, LONG acpEnd, IDataObject **ppDataObject) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetFormattedText() called " "but not supported (E_NOTIMPL)", this)); // no support for formatted text return E_NOTIMPL; } STDMETHODIMP nsTextStore::GetEmbedded(LONG acpPos, REFGUID rguidService, REFIID riid, IUnknown **ppunk) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetEmbedded() called " "but not supported (E_NOTIMPL)", this)); // embedded objects are not supported return E_NOTIMPL; } STDMETHODIMP nsTextStore::QueryInsertEmbedded(const GUID *pguidService, const FORMATETC *pFormatEtc, BOOL *pfInsertable) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::QueryInsertEmbedded() called " "but not supported, *pfInsertable=FALSE (S_OK)", this)); // embedded objects are not supported *pfInsertable = FALSE; return S_OK; } STDMETHODIMP nsTextStore::InsertEmbedded(DWORD dwFlags, LONG acpStart, LONG acpEnd, IDataObject *pDataObject, TS_TEXTCHANGE *pChange) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::InsertEmbedded() called " "but not supported (E_NOTIMPL)", this)); // embedded objects are not supported return E_NOTIMPL; } STDMETHODIMP nsTextStore::RequestSupportedAttrs(DWORD dwFlags, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::RequestSupportedAttrs() called " "but not supported (S_OK)", this)); // no attributes defined return S_OK; } STDMETHODIMP nsTextStore::RequestAttrsAtPosition(LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs, DWORD dwFlags) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::RequestAttrsAtPosition() called " "but not supported (S_OK)", this)); // no per character attributes defined return S_OK; } STDMETHODIMP nsTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttr, DWORD dwFlags) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::RequestAttrsTransitioningAtPosition() called " "but not supported (S_OK)", this)); // no per character attributes defined return S_OK; } STDMETHODIMP nsTextStore::FindNextAttrTransition(LONG acpStart, LONG acpHalt, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs, DWORD dwFlags, LONG *pacpNext, BOOL *pfFound, LONG *plFoundOffset) { if (!pacpNext || !pfFound || !plFoundOffset) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::FindNextAttrTransition() FAILED due to " "null argument", this)); return E_INVALIDARG; } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::FindNextAttrTransition() called " "but not supported (S_OK)", this)); // no per character attributes defined *pacpNext = *plFoundOffset = acpHalt; *pfFound = FALSE; return S_OK; } STDMETHODIMP nsTextStore::RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL *paAttrVals, ULONG *pcFetched) { if (!pcFetched || !ulCount || !paAttrVals) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::RetrieveRequestedAttrs() FAILED due to " "null argument", this)); return E_INVALIDARG; } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::RetrieveRequestedAttrs() called " "but not supported, *pcFetched=0 (S_OK)", this)); // no attributes defined *pcFetched = 0; return S_OK; } STDMETHODIMP nsTextStore::GetEndACP(LONG *pacp) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetEndACP(pacp=0x%p)", this, pacp)); if (!IsReadLocked()) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetEndACP() FAILED due to " "not locked (read)", this)); return TS_E_NOLOCK; } if (!pacp) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetEndACP() FAILED due to " "null argument", this)); return E_INVALIDARG; } // Flattened text is retrieved and its length returned nsQueryContentEvent event(true, NS_QUERY_TEXT_CONTENT, mWindow); mWindow->InitEvent(event); // Return entire text event.InitForQueryTextContent(0, INT32_MAX); mWindow->DispatchWindowEvent(&event); if (!event.mSucceeded) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetEndACP() FAILED due to " "NS_QUERY_TEXT_CONTENT failure", this)); return E_FAIL; } *pacp = LONG(event.mReply.mString.Length()); return S_OK; } #define TEXTSTORE_DEFAULT_VIEW (1) STDMETHODIMP nsTextStore::GetActiveView(TsViewCookie *pvcView) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetActiveView(pvcView=0x%p)", this, pvcView)); if (!pvcView) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetActiveView() FAILED due to " "null argument", this)); return E_INVALIDARG; } *pvcView = TEXTSTORE_DEFAULT_VIEW; PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetActiveView() succeeded: *pvcView=%ld", this, *pvcView)); return S_OK; } STDMETHODIMP nsTextStore::GetACPFromPoint(TsViewCookie vcView, const POINT *pt, DWORD dwFlags, LONG *pacp) { if (!IsReadLocked()) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetACPFromPoint() FAILED due to " "not locked (read)", this)); return TS_E_NOLOCK; } if (vcView != TEXTSTORE_DEFAULT_VIEW) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetACPFromPoint() FAILED due to " "called with invalid view", this)); return E_INVALIDARG; } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetACPFromPoint(vcView=%ld, " "pt(0x%p)={ x=%ld, y=%ld }, dwFlags=%s, pacp=0x%p) called " "but not supported (E_NOTIMPL)", this)); // not supported for now return E_NOTIMPL; } STDMETHODIMP nsTextStore::GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd, RECT *prc, BOOL *pfClipped) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetTextExt(vcView=%ld, " "acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p)", this, vcView, acpStart, acpEnd, prc, pfClipped)); if (!IsReadLocked()) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " "not locked (read)", this)); return TS_E_NOLOCK; } if (vcView != TEXTSTORE_DEFAULT_VIEW) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " "called with invalid view", this)); return E_INVALIDARG; } if (!prc || !pfClipped) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " "null argument", this)); return E_INVALIDARG; } if (acpStart < 0 || acpEnd < acpStart) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " "invalid position", this)); return TS_E_INVALIDPOS; } // use NS_QUERY_TEXT_RECT to get rect in system, screen coordinates nsQueryContentEvent event(true, NS_QUERY_TEXT_RECT, mWindow); mWindow->InitEvent(event); event.InitForQueryTextRect(acpStart, acpEnd - acpStart); mWindow->DispatchWindowEvent(&event); if (!event.mSucceeded) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " "NS_QUERY_TEXT_RECT failure", this)); return TS_E_INVALIDPOS; // but unexpected failure, maybe. } // IMEs don't like empty rects, fix here if (event.mReply.mRect.width <= 0) event.mReply.mRect.width = 1; if (event.mReply.mRect.height <= 0) event.mReply.mRect.height = 1; // convert to unclipped screen rect nsWindow* refWindow = static_cast( event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWindow); // Result rect is in top level widget coordinates refWindow = refWindow->GetTopLevelWindow(false); if (!refWindow) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " "no top level window", this)); return E_FAIL; } event.mReply.mRect.MoveBy(refWindow->WidgetToScreenOffset()); // get bounding screen rect to test for clipping if (!GetScreenExtInternal(*prc)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to " "GetScreenExtInternal() failure", this)); return E_FAIL; } // clip text rect to bounding rect RECT textRect; ::SetRect(&textRect, event.mReply.mRect.x, event.mReply.mRect.y, event.mReply.mRect.XMost(), event.mReply.mRect.YMost()); if (!::IntersectRect(prc, prc, &textRect)) // Text is not visible ::SetRectEmpty(prc); // not equal if text rect was clipped *pfClipped = !::EqualRect(prc, &textRect); PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetTextExt() succeeded: " "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }, *pfClipped=%s", this, prc->left, prc->top, prc->right, prc->bottom, GetBoolName(*pfClipped))); return S_OK; } STDMETHODIMP nsTextStore::GetScreenExt(TsViewCookie vcView, RECT *prc) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetScreenExt(vcView=%ld, prc=0x%p)", this, vcView, prc)); if (vcView != TEXTSTORE_DEFAULT_VIEW) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetScreenExt() FAILED due to " "called with invalid view", this)); return E_INVALIDARG; } if (!prc) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetScreenExt() FAILED due to " "null argument", this)); return E_INVALIDARG; } if (!GetScreenExtInternal(*prc)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetScreenExt() FAILED due to " "GetScreenExtInternal() failure", this)); return E_FAIL; } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetScreenExt() succeeded: " "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }", this, prc->left, prc->top, prc->right, prc->bottom)); return S_OK; } bool nsTextStore::GetScreenExtInternal(RECT &aScreenExt) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::GetScreenExtInternal()", this)); // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates nsQueryContentEvent event(true, NS_QUERY_EDITOR_RECT, mWindow); mWindow->InitEvent(event); mWindow->DispatchWindowEvent(&event); if (!event.mSucceeded) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetScreenExtInternal() FAILED due to " "NS_QUERY_EDITOR_RECT failure", this)); return false; } nsWindow* refWindow = static_cast( event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWindow); // Result rect is in top level widget coordinates refWindow = refWindow->GetTopLevelWindow(false); if (!refWindow) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetScreenExtInternal() FAILED due to " "no top level window", this)); return false; } nsIntRect boundRect; if (NS_FAILED(refWindow->GetClientBounds(boundRect))) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetScreenExtInternal() FAILED due to " "failed to get the client bounds", this)); return false; } boundRect.MoveTo(0, 0); // Clip frame rect to window rect boundRect.IntersectRect(event.mReply.mRect, boundRect); if (!boundRect.IsEmpty()) { boundRect.MoveBy(refWindow->WidgetToScreenOffset()); ::SetRect(&aScreenExt, boundRect.x, boundRect.y, boundRect.XMost(), boundRect.YMost()); } else { ::SetRectEmpty(&aScreenExt); } PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::GetScreenExtInternal() succeeded: " "aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }", this, aScreenExt.left, aScreenExt.top, aScreenExt.right, aScreenExt.bottom)); return true; } STDMETHODIMP nsTextStore::GetWnd(TsViewCookie vcView, HWND *phwnd) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetWnd(vcView=%ld, phwnd=0x%p), mWindow=0x%p", this, vcView, phwnd, mWindow)); if (vcView != TEXTSTORE_DEFAULT_VIEW) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetWnd() FAILED due to " "called with invalid view", this)); return E_INVALIDARG; } if (!phwnd) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::GetScreenExt() FAILED due to " "null argument", this)); return E_INVALIDARG; } *phwnd = mWindow->GetWindowHandle(); PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::GetWnd() succeeded: *phwnd=0x%p", this, static_cast(*phwnd))); return S_OK; } STDMETHODIMP nsTextStore::InsertTextAtSelection(DWORD dwFlags, const WCHAR *pchText, ULONG cch, LONG *pacpStart, LONG *pacpEnd, TS_TEXTCHANGE *pChange) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::InsertTextAtSelection(dwFlags=%s, " "pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, " "pChange=0x%p), %s", this, dwFlags == 0 ? "0" : dwFlags == TF_IAS_NOQUERY ? "TF_IAS_NOQUERY" : dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY" : "Unknown", pchText, pchText && cch ? NS_ConvertUTF16toUTF8(pchText, cch).get() : "", cch, pacpStart, pacpEnd, pChange, mCompositionView ? "there is composition view" : "there is no composition view")); if (cch && !pchText) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " "null pchText", this)); return E_INVALIDARG; } if (TS_IAS_QUERYONLY == dwFlags) { if (!IsReadLocked()) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " "not locked (read)", this)); return TS_E_NOLOCK; } if (!pacpStart || !pacpEnd) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " "null argument", this)); return E_INVALIDARG; } // Get selection first TS_SELECTION_ACP sel; if (!GetSelectionInternal(sel)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " "GetSelectionInternal() failure", this)); return E_FAIL; } // Simulate text insertion *pacpStart = sel.acpStart; *pacpEnd = sel.acpEnd; if (pChange) { pChange->acpStart = sel.acpStart; pChange->acpOldEnd = sel.acpEnd; pChange->acpNewEnd = sel.acpStart + cch; } } else { if (!IsReadWriteLocked()) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " "not locked (read-write)", this)); return TS_E_NOLOCK; } if (!pChange) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " "null pChange", this)); return E_INVALIDARG; } if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " "null argument", this)); return E_INVALIDARG; } if (!InsertTextAtSelectionInternal(nsDependentString(pchText, cch), pChange)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to " "InsertTextAtSelectionInternal() failure", this)); return E_FAIL; } if (TS_IAS_NOQUERY != dwFlags) { *pacpStart = pChange->acpStart; *pacpEnd = pChange->acpNewEnd; } } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::InsertTextAtSelection() succeeded: " "*pacpStart=%ld, *pacpEnd=%ld, " "*pChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld })", this, pacpStart ? *pacpStart : 0, pacpEnd ? *pacpEnd : 0, pChange ? pChange->acpStart: 0, pChange ? pChange->acpOldEnd : 0, pChange ? pChange->acpNewEnd : 0)); return S_OK; } bool nsTextStore::InsertTextAtSelectionInternal(const nsAString &aInsertStr, TS_TEXTCHANGE* aTextChange) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal(" "aInsertStr=\"%s\", aTextChange=0x%p), %s", this, NS_ConvertUTF16toUTF8(aInsertStr).get(), aTextChange, mCompositionView ? "there is composition view" : "there is no composition view")); TS_SELECTION_ACP oldSelection; oldSelection.acpStart = 0; oldSelection.acpEnd = 0; if (mCompositionView) { oldSelection = mCompositionSelection; // Emulate text insertion during compositions, because during a // composition, editor expects the whole composition string to // be sent in NS_TEXT_TEXT, not just the inserted part. // The actual NS_TEXT_TEXT will be sent in SetSelection or // OnUpdateComposition. mCompositionString.Replace( static_cast(oldSelection.acpStart) - mCompositionStart, static_cast(oldSelection.acpEnd - oldSelection.acpStart), aInsertStr); mCompositionSelection.acpStart += aInsertStr.Length(); mCompositionSelection.acpEnd = mCompositionSelection.acpStart; mCompositionSelection.style.ase = TS_AE_END; PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() replaced " "a part of (%lu-%lu) the composition string, waiting " "SetSelection() or OnUpdateComposition()...", this, oldSelection.acpStart - mCompositionStart, oldSelection.acpEnd - mCompositionStart)); } else { // Use actual selection if it's not composing. if (aTextChange && !GetSelectionInternal(oldSelection)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() FAILED " "due to GetSelectionInternal() failure", this)); return false; } // Use a temporary composition to contain the text PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() " "dispatching a compositionstart event...", this)); nsCompositionEvent compStartEvent(true, NS_COMPOSITION_START, mWindow); mWindow->InitEvent(compStartEvent); mWindow->DispatchWindowEvent(&compStartEvent); if (!mWindow || mWindow->Destroyed()) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() FAILED " "due to the widget destroyed by compositionstart event", this)); return false; } if (!aInsertStr.IsEmpty()) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() " "dispatching a compositionupdate event...", this)); nsCompositionEvent compUpdateEvent(true, NS_COMPOSITION_UPDATE, mWindow); compUpdateEvent.data = aInsertStr; mWindow->DispatchWindowEvent(&compUpdateEvent); if (!mWindow || mWindow->Destroyed()) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() " "FAILED due to the widget destroyed by compositionupdate event", this)); return false; } } PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() " "dispatching a text event...", this)); nsTextEvent textEvent(true, NS_TEXT_TEXT, mWindow); mWindow->InitEvent(textEvent); textEvent.theText = aInsertStr; textEvent.theText.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), NS_LITERAL_STRING("\n")); mWindow->DispatchWindowEvent(&textEvent); if (!mWindow || mWindow->Destroyed()) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() FAILED " "due to the widget destroyed by text event", this)); return false; } PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() " "dispatching a compositionend event...", this)); nsCompositionEvent compEndEvent(true, NS_COMPOSITION_END, mWindow); compEndEvent.data = aInsertStr; mWindow->DispatchWindowEvent(&compEndEvent); if (!mWindow || mWindow->Destroyed()) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() FAILED " "due to the widget destroyed by compositionend event", this)); return false; } } if (aTextChange) { aTextChange->acpStart = oldSelection.acpStart; aTextChange->acpOldEnd = oldSelection.acpEnd; // Get new selection TS_SELECTION_ACP newSelection; if (!GetSelectionInternal(newSelection)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() FAILED " "due to GetSelectionInternal() failure after inserted the text", this)); return false; } aTextChange->acpNewEnd = newSelection.acpEnd; } PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() succeeded: " "mWindow=0x%p, mWindow->Destroyed()=%s, aTextChange={ acpStart=%ld, " "acpOldEnd=%ld, acpNewEnd=%ld }", this, mWindow, GetBoolName(mWindow ? mWindow->Destroyed() : true), aTextChange ? aTextChange->acpStart : 0, aTextChange ? aTextChange->acpOldEnd : 0, aTextChange ? aTextChange->acpNewEnd : 0)); return true; } STDMETHODIMP nsTextStore::InsertEmbeddedAtSelection(DWORD dwFlags, IDataObject *pDataObject, LONG *pacpStart, LONG *pacpEnd, TS_TEXTCHANGE *pChange) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::InsertEmbeddedAtSelection() called " "but not supported (E_NOTIMPL)", this)); // embedded objects are not supported return E_NOTIMPL; } HRESULT nsTextStore::OnStartCompositionInternal(ITfCompositionView* pComposition, ITfRange* aRange, bool aPreserveSelection) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::OnStartCompositionInternal(" "pComposition=0x%p, aRange=0x%p, aPreserveSelection=%s), " "mCompositionView=0x%p", this, pComposition, aRange, GetBoolName(aPreserveSelection), mCompositionView.get())); mCompositionView = pComposition; HRESULT hr = GetRangeExtent(aRange, &mCompositionStart, &mCompositionLength); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::OnStartCompositionInternal() FAILED due " "to GetRangeExtent() failure", this)); return hr; } PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::OnStartCompositionInternal(), " "mCompositionStart=%ld, mCompositionLength=%ld", this, mCompositionStart, mCompositionLength)); PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::OnStartCompositionInternal(), " "dispatching selectionset event...")); // Select composition range so the new composition replaces the range nsSelectionEvent selEvent(true, NS_SELECTION_SET, mWindow); mWindow->InitEvent(selEvent); selEvent.mOffset = uint32_t(mCompositionStart); selEvent.mLength = uint32_t(mCompositionLength); selEvent.mReversed = false; mWindow->DispatchWindowEvent(&selEvent); if (!selEvent.mSucceeded) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::OnStartCompositionInternal() FAILED due " "to NS_SELECTION_SET failure", this)); return E_FAIL; } // Set up composition nsQueryContentEvent queryEvent(true, NS_QUERY_SELECTED_TEXT, mWindow); mWindow->InitEvent(queryEvent); mWindow->DispatchWindowEvent(&queryEvent); if (!queryEvent.mSucceeded) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::OnStartCompositionInternal() FAILED due " "to NS_QUERY_SELECTED_TEXT failure", this)); return E_FAIL; } PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::OnStartCompositionInternal(), " "dispatching compositionstart event...")); mCompositionString = queryEvent.mReply.mString; if (!aPreserveSelection) { mCompositionSelection.acpStart = mCompositionStart; mCompositionSelection.acpEnd = mCompositionStart + mCompositionLength; mCompositionSelection.style.ase = TS_AE_END; mCompositionSelection.style.fInterimChar = FALSE; } nsCompositionEvent event(true, NS_COMPOSITION_START, mWindow); mWindow->InitEvent(event); mWindow->DispatchWindowEvent(&event); PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::OnStartCompositionInternal() succeeded: " "mCompositionStart=%ld, mCompositionLength=%ld, " "mCompositionSelection={ acpStart=%ld, acpEnd=%ld, style.ase=%s, " "style.iInterimChar=%s }", this, mCompositionStart, mCompositionLength, mCompositionSelection.acpStart, mCompositionSelection.acpEnd, GetActiveSelEndName(mCompositionSelection.style.ase), GetBoolName(mCompositionSelection.style.fInterimChar))); return S_OK; } static uint32_t GetLayoutChangeIntervalTime() { static int32_t sTime = -1; if (sTime > 0) return uint32_t(sTime); sTime = NS_MAX(10, Preferences::GetInt("intl.tsf.on_layout_change_interval", 100)); return uint32_t(sTime); } STDMETHODIMP nsTextStore::OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnStartComposition(pComposition=0x%p, " "pfOk=0x%p), mCompositionView=0x%p", this, pComposition, pfOk, mCompositionView.get())); *pfOk = FALSE; // Only one composition at a time if (mCompositionView) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::OnStartComposition() FAILED due to " "there is another composition already (but returns S_OK)", this)); return S_OK; } nsRefPtr range; HRESULT hr = pComposition->GetRange(getter_AddRefs(range)); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::OnStartComposition() FAILED due to " "pComposition->GetRange() failure", this)); return hr; } hr = OnStartCompositionInternal(pComposition, range, false); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::OnStartComposition() FAILED due to " "OnStartCompositionInternal() failure", this)); return hr; } NS_ASSERTION(!mCompositionTimer, "The timer is alive!"); mCompositionTimer = do_CreateInstance(NS_TIMER_CONTRACTID); if (mCompositionTimer) { mCompositionTimer->InitWithFuncCallback(CompositionTimerCallbackFunc, this, GetLayoutChangeIntervalTime(), nsITimer::TYPE_REPEATING_SLACK); } *pfOk = TRUE; PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnStartComposition() succeeded", this)); return S_OK; } STDMETHODIMP nsTextStore::OnUpdateComposition(ITfCompositionView* pComposition, ITfRange* pRangeNew) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnUpdateComposition(pComposition=0x%p, " "pRangeNew=0x%p), mCompositionView=0x%p", this, pComposition, pRangeNew, mCompositionView.get())); if (!mDocumentMgr || !mContext) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to " "not ready for the composition", this)); return E_UNEXPECTED; } if (!mCompositionView) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to " "no active composition", this)); return E_UNEXPECTED; } if (mCompositionView != pComposition) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to " "different composition view specified", this)); return E_UNEXPECTED; } // pRangeNew is null when the update is not complete if (!pRangeNew) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnUpdateComposition() succeeded but " "not complete", this)); return S_OK; } HRESULT hr = UpdateCompositionExtent(pRangeNew); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to " "UpdateCompositionExtent() failure", this)); return hr; } hr = SendTextEventForCompositionString(); if (FAILED(hr)) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to " "SendTextEventForCompositionString() failure", this)); return hr; } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnUpdateComposition() succeeded: " "mCompositionStart=%ld, mCompositionLength=%ld, " "mCompositionSelection={ acpStart=%ld, acpEnd=%ld, style.ase=%s, " "style.iInterimChar=%s }, mCompositionString=\"%s\"", this, mCompositionStart, mCompositionLength, mCompositionSelection.acpStart, mCompositionSelection.acpEnd, GetActiveSelEndName(mCompositionSelection.style.ase), GetBoolName(mCompositionSelection.style.fInterimChar), NS_ConvertUTF16toUTF8(mCompositionString).get())); return S_OK; } STDMETHODIMP nsTextStore::OnEndComposition(ITfCompositionView* pComposition) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnEndComposition(pComposition=0x%p), " "mCompositionView=0x%p, mCompositionString=\"%s\"", this, pComposition, mCompositionView.get(), NS_ConvertUTF16toUTF8(mCompositionString).get())); if (!mCompositionView) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::OnEndComposition() FAILED due to " "no active composition", this)); return E_UNEXPECTED; } if (mCompositionView != pComposition) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: 0x%p nsTextStore::OnEndComposition() FAILED due to " "different composition view specified", this)); return E_UNEXPECTED; } // Clear the saved text event SaveTextEvent(nullptr); if (mCompositionTimer) { mCompositionTimer->Cancel(); mCompositionTimer = nullptr; } if (mCompositionString != mLastDispatchedCompositionString) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnEndComposition(), " "dispatching compositionupdate event...", this)); nsCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE, mWindow); mWindow->InitEvent(compositionUpdate); compositionUpdate.data = mCompositionString; mLastDispatchedCompositionString = mCompositionString; mWindow->DispatchWindowEvent(&compositionUpdate); if (!mWindow || mWindow->Destroyed()) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnEndComposition(), " "succeeded, but the widget has gone", this)); return S_OK; } } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnEndComposition(), " "dispatching text event...", this)); // Use NS_TEXT_TEXT to commit composition string nsTextEvent textEvent(true, NS_TEXT_TEXT, mWindow); mWindow->InitEvent(textEvent); textEvent.theText = mCompositionString; textEvent.theText.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), NS_LITERAL_STRING("\n")); mWindow->DispatchWindowEvent(&textEvent); if (!mWindow || mWindow->Destroyed()) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnEndComposition(), " "succeeded, but the widget has gone", this)); return S_OK; } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnEndComposition(), " "dispatching compositionend event...", this)); nsCompositionEvent event(true, NS_COMPOSITION_END, mWindow); event.data = mLastDispatchedCompositionString; mWindow->InitEvent(event); mWindow->DispatchWindowEvent(&event); if (!mWindow || mWindow->Destroyed()) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnEndComposition(), " "succeeded, but the widget has gone", this)); return S_OK; } mCompositionView = NULL; mCompositionString.Truncate(0); mLastDispatchedCompositionString.Truncate(); // Maintain selection SetSelectionInternal(&mCompositionSelection); PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnEndComposition(), succeeded", this)); return S_OK; } // static nsresult nsTextStore::OnFocusChange(bool aFocus, nsWindow* aWindow, IMEState::Enabled aIMEEnabled) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: nsTextStore::OnFocusChange(aFocus=%s, aWindow=0x%p, " "aIMEEnabled=%s), sTsfThreadMgr=0x%p, sTsfTextStore=0x%p", GetBoolName(aFocus), aWindow, GetIMEEnabledName(aIMEEnabled), sTsfThreadMgr, sTsfTextStore)); // no change notifications if TSF is disabled if (!sTsfThreadMgr || !sTsfTextStore) return NS_ERROR_NOT_AVAILABLE; if (aFocus) { bool bRet = sTsfTextStore->Create(aWindow, aIMEEnabled); NS_ENSURE_TRUE(bRet, NS_ERROR_FAILURE); NS_ENSURE_TRUE(sTsfTextStore->mDocumentMgr, NS_ERROR_FAILURE); HRESULT hr = sTsfThreadMgr->SetFocus(sTsfTextStore->mDocumentMgr); NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); } else { sTsfTextStore->Destroy(); } return NS_OK; } // static nsIMEUpdatePreference nsTextStore::GetIMEUpdatePreference() { bool hasFocus = false; if (sTsfThreadMgr && sTsfTextStore && sTsfTextStore->mDocumentMgr) { nsRefPtr docMgr; sTsfThreadMgr->GetFocus(getter_AddRefs(docMgr)); hasFocus = (docMgr == sTsfTextStore->mDocumentMgr); } return nsIMEUpdatePreference(hasFocus, false); } nsresult nsTextStore::OnTextChangeInternal(uint32_t aStart, uint32_t aOldEnd, uint32_t aNewEnd) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::OnTextChangeInternal(aStart=%lu, " "aOldEnd=%lu, aNewEnd=%lu), mLock=%s, mSink=0x%p, mSinkMask=%s, " "mTextChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }", this, aStart, aOldEnd, aNewEnd, GetLockFlagNameStr(mLock).get(), mSink.get(), GetSinkMaskNameStr(mSinkMask).get(), mTextChange.acpStart, mTextChange.acpOldEnd, mTextChange.acpNewEnd)); if (!mLock && mSink && 0 != (mSinkMask & TS_AS_TEXT_CHANGE)) { mTextChange.acpStart = NS_MIN(mTextChange.acpStart, LONG(aStart)); mTextChange.acpOldEnd = NS_MAX(mTextChange.acpOldEnd, LONG(aOldEnd)); mTextChange.acpNewEnd = NS_MAX(mTextChange.acpNewEnd, LONG(aNewEnd)); ::PostMessageW(mWindow->GetWindowHandle(), WM_USER_TSF_TEXTCHANGE, 0, 0); } return NS_OK; } void nsTextStore::OnTextChangeMsgInternal(void) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::OnTextChangeMsgInternal(), mLock=%s, " "mSink=0x%p, mSinkMask=%s, mTextChange={ acpStart=%ld, " "acpOldEnd=%ld, acpNewEnd=%ld }", this, GetLockFlagNameStr(mLock).get(), mSink.get(), GetSinkMaskNameStr(mSinkMask).get(), mTextChange.acpStart, mTextChange.acpOldEnd, mTextChange.acpNewEnd)); if (!mLock && mSink && 0 != (mSinkMask & TS_AS_TEXT_CHANGE) && INT32_MAX > mTextChange.acpStart) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnTextChangeMsgInternal(), calling" "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, " "acpNewEnd=%ld })...", this, mTextChange.acpStart, mTextChange.acpOldEnd, mTextChange.acpNewEnd)); mSink->OnTextChange(0, &mTextChange); mTextChange.acpStart = INT32_MAX; mTextChange.acpOldEnd = mTextChange.acpNewEnd = 0; } } nsresult nsTextStore::OnSelectionChangeInternal(void) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::OnSelectionChangeInternal(), mLock=%s, " "mSink=0x%p, mSinkMask=%s", this, GetLockFlagNameStr(mLock).get(), mSink.get(), GetSinkMaskNameStr(mSinkMask).get())); if (!mLock && mSink && 0 != (mSinkMask & TS_AS_SEL_CHANGE)) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnSelectionChangeInternal(), calling " "mSink->OnSelectionChange()...", this)); mSink->OnSelectionChange(); } return NS_OK; } nsresult nsTextStore::OnCompositionTimer() { NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE); NS_ENSURE_TRUE(mSink, NS_ERROR_FAILURE); // XXXmnakano We always call OnLayoutChange for now, but this might use CPU // power when the focused editor has very long text. Ideally, we should call // this only when the composition string screen position is changed by window // moving, resizing. And also reflowing and scrolling the contents. PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::OnCompositionTimer(), calling " "mSink->OnLayoutChange()...", this)); HRESULT hr = mSink->OnLayoutChange(TS_LC_CHANGE, TEXTSTORE_DEFAULT_VIEW); NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); return NS_OK; } void nsTextStore::CommitCompositionInternal(bool aDiscard) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::CommitCompositionInternal(aDiscard=%s), " "mLock=%s, mSink=0x%p, mContext=0x%p, mCompositionView=0x%p, " "mCompositionString=\"%s\"", this, GetBoolName(aDiscard), GetLockFlagNameStr(mLock).get(), mSink.get(), mContext.get(), mCompositionView.get(), NS_ConvertUTF16toUTF8(mCompositionString).get())); if (mCompositionView && aDiscard) { mCompositionString.Truncate(0); if (mSink && !mLock) { TS_TEXTCHANGE textChange; textChange.acpStart = mCompositionStart; textChange.acpOldEnd = mCompositionStart + mCompositionLength; textChange.acpNewEnd = mCompositionStart; PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: 0x%p nsTextStore::CommitCompositionInternal(), calling" "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, " "acpNewEnd=%ld })...", this, textChange.acpStart, textChange.acpOldEnd, textChange.acpNewEnd)); mSink->OnTextChange(0, &textChange); } } // Terminate two contexts, the base context (mContext) and the top // if the top context is not the same as the base context nsRefPtr context = mContext; do { if (context) { nsRefPtr services; context->QueryInterface(IID_ITfContextOwnerCompositionServices, getter_AddRefs(services)); if (services) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::CommitCompositionInternal(), " "requesting TerminateComposition() for the context 0x%p...", this, context.get())); services->TerminateComposition(NULL); } } if (context != mContext) break; if (mDocumentMgr) mDocumentMgr->GetTop(getter_AddRefs(context)); } while (context != mContext); } static bool GetCompartment(IUnknown* pUnk, const GUID& aID, ITfCompartment** aCompartment) { if (!pUnk) return false; nsRefPtr compMgr; pUnk->QueryInterface(IID_ITfCompartmentMgr, getter_AddRefs(compMgr)); if (!compMgr) return false; return SUCCEEDED(compMgr->GetCompartment(aID, aCompartment)) && (*aCompartment) != NULL; } // static void nsTextStore::SetIMEOpenState(bool aState) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: nsTextStore::SetIMEOpenState(aState=%s)", GetBoolName(aState))); nsRefPtr comp; if (!GetCompartment(sTsfThreadMgr, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE, getter_AddRefs(comp))) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: nsTextStore::SetIMEOpenState() FAILED due to" "no compartment available")); return; } VARIANT variant; variant.vt = VT_I4; variant.lVal = aState; PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: nsTextStore::SetIMEOpenState(), setting " "0x%04X to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...", variant.lVal)); comp->SetValue(sTsfClientId, &variant); } // static bool nsTextStore::GetIMEOpenState(void) { nsRefPtr comp; if (!GetCompartment(sTsfThreadMgr, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE, getter_AddRefs(comp))) return false; VARIANT variant; ::VariantInit(&variant); if (SUCCEEDED(comp->GetValue(&variant)) && variant.vt == VT_I4) return variant.lVal != 0; ::VariantClear(&variant); // clear up in case variant.vt != VT_I4 return false; } void nsTextStore::SetInputContextInternal(IMEState::Enabled aState) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::SetInputContextInternal(aState=%s), " "mContext=0x%p", this, GetIMEEnabledName(aState), mContext.get())); VARIANT variant; variant.vt = VT_I4; variant.lVal = (aState != IMEState::ENABLED); // Set two contexts, the base context (mContext) and the top // if the top context is not the same as the base context nsRefPtr context = mContext; nsRefPtr comp; do { if (GetCompartment(context, GUID_COMPARTMENT_KEYBOARD_DISABLED, getter_AddRefs(comp))) { PR_LOG(sTextStoreLog, PR_LOG_DEBUG, ("TSF: 0x%p nsTextStore::SetInputContextInternal(), setting " "0x%04X to GUID_COMPARTMENT_KEYBOARD_DISABLED of context 0x%p...", this, variant.lVal, context.get())); comp->SetValue(sTsfClientId, &variant); } if (context != mContext) break; if (mDocumentMgr) mDocumentMgr->GetTop(getter_AddRefs(context)); } while (context != mContext); } // static void nsTextStore::Initialize(void) { #ifdef PR_LOGGING if (!sTextStoreLog) { sTextStoreLog = PR_NewLogModule("nsTextStoreWidgets"); } #endif bool enableTsf = Preferences::GetBool("intl.enable_tsf_support", false); PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: nsTextStore::Initialize(), TSF is %s", enableTsf ? "enabled" : "disabled")); if (!enableTsf) { return; } if (!sTsfThreadMgr) { if (SUCCEEDED(CoCreateInstance(CLSID_TF_ThreadMgr, NULL, CLSCTX_INPROC_SERVER, IID_ITfThreadMgr, reinterpret_cast(&sTsfThreadMgr)))) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: nsTextStore::Initialize() succeeded to " "create the thread manager, activating...")); if (FAILED(sTsfThreadMgr->Activate(&sTsfClientId))) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: nsTextStore::Initialize() FAILED to activate, " "releasing the thread manager...")); NS_RELEASE(sTsfThreadMgr); } } #ifdef PR_LOGGING else { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: nsTextStore::Initialize() FAILED to " "create the thread manager")); } #endif // #ifdef PR_LOGGING } if (sTsfThreadMgr && !sTsfTextStore) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: nsTextStore::Initialize() is creating " "an nsTextStore instance...")); sTsfTextStore = new nsTextStore(); } if (sTsfThreadMgr && !sDisplayAttrMgr) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: nsTextStore::Initialize() is creating " "a display attribute manager instance...")); HRESULT hr = ::CoCreateInstance(CLSID_TF_DisplayAttributeMgr, NULL, CLSCTX_INPROC_SERVER, IID_ITfDisplayAttributeMgr, reinterpret_cast(&sDisplayAttrMgr)); if (FAILED(hr) || !sDisplayAttrMgr) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: nsTextStore::Initialize() FAILED to create " "a display attribute manager instance")); } } if (sTsfThreadMgr && sDisplayAttrMgr && !sCategoryMgr) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: nsTextStore::Initialize() is creating " "a category manager instance...")); HRESULT hr = ::CoCreateInstance(CLSID_TF_CategoryMgr, NULL, CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr, reinterpret_cast(&sCategoryMgr)); if (FAILED(hr) || !sCategoryMgr) { PR_LOG(sTextStoreLog, PR_LOG_ERROR, ("TSF: nsTextStore::Initialize() FAILED to create " "a category manager instance")); // release the display manager because it cannot work without the // category manager NS_RELEASE(sDisplayAttrMgr); } } if (sTsfThreadMgr && !sFlushTIPInputMessage) { sFlushTIPInputMessage = ::RegisterWindowMessageW( NS_LITERAL_STRING("Flush TIP Input Message").get()); } PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: nsTextStore::Initialize(), sTsfThreadMgr=0x%p, " "sTsfClientId=0x%08X, sTsfTextStore=0x%p, sDisplayAttrMgr=0x%p, " "sCategoryMgr=0x%p", sTsfThreadMgr, sTsfClientId, sTsfTextStore, sDisplayAttrMgr, sCategoryMgr)); } // static void nsTextStore::Terminate(void) { PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: nsTextStore::Terminate()")); NS_IF_RELEASE(sDisplayAttrMgr); NS_IF_RELEASE(sCategoryMgr); NS_IF_RELEASE(sTsfTextStore); sTsfClientId = 0; if (sTsfThreadMgr) { sTsfThreadMgr->Deactivate(); NS_RELEASE(sTsfThreadMgr); } }