/* -*- 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 "TSFEmptyTextStore.h" #include "IMMHandler.h" #include "KeyboardLayout.h" #include "TSFInputScope.h" #include "TSFStaticSink.h" #include "TSFUtils.h" #include "WinIMEHandler.h" #include "WinMessages.h" #include "WinUtils.h" #include "mozilla/Assertions.h" #include "mozilla/AutoRestore.h" #include "mozilla/Logging.h" #include "mozilla/StaticPrefs_intl.h" #include "mozilla/glean/WidgetWindowsMetrics.h" #include "mozilla/TextEventDispatcher.h" #include "mozilla/TextEvents.h" #include "mozilla/ToString.h" #include "mozilla/WindowsVersion.h" #include "nsWindow.h" #include #include // for _bstr_t #include // for SysAllocString #include // For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync` // rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too // big file. // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior. extern mozilla::LazyLogModule gIMELog; // defined in TSFUtils.cpp namespace mozilla::widget { /** * TSF related code should log its behavior even on release build especially * in the interface methods. * * In interface methods, use LogLevel::Info. * In internal methods, use LogLevel::Debug for logging normal behavior. * For logging error, use LogLevel::Error. * * When an instance method is called, start with following text: * "0x%p TSFFoo::Bar(", the 0x%p should be the "this" of the nsFoo. * after that, start with: * "0x%p TSFFoo::Bar(" * In an internal method, start with following text: * "0x%p TSFFoo::Bar(" * When a static method is called, start with following text: * "TSFFoo::Bar(" */ /******************************************************************/ /* TSFEmptyTextStore */ /******************************************************************/ TSFEmptyTextStore::TSFEmptyTextStore() : TSFTextStoreBase(Editable::No) { MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore instance is created", this)); } TSFEmptyTextStore::~TSFEmptyTextStore() { MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore instance is destroyed", this)); } bool TSFEmptyTextStore::Init(nsWindow* aWidget, const InputContext& aContext) { MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::Init(aWidget=0x%p)", this, aWidget)); if (NS_WARN_IF(!InitBase(aWidget, aContext))) { return false; } // Create document manager const RefPtr threadMgr = TSFUtils::GetThreadMgr(); RefPtr documentMgr; HRESULT hr = threadMgr->CreateDocumentMgr(getter_AddRefs(documentMgr)); if (NS_WARN_IF(FAILED(hr))) { MOZ_LOG(gIMELog, LogLevel::Error, ("0x%p TSFEmptyTextStore::Init() FAILED to create ITfDocumentMgr " "(0x%08lX)", this, hr)); return false; } if (NS_WARN_IF(mDestroyed)) { MOZ_LOG(gIMELog, LogLevel::Error, ("0x%p TSFEmptyTextStore::Init() FAILED to create ITfDocumentMgr " "due to TextStore being destroyed during calling " "ITfThreadMgr::CreateDocumentMgr()", this)); return false; } const auto emptyContextIsSupported = [&]() -> bool { // The empty TSF text store support was introduced with Windows 11. // If it's supported, QI for GUID_COMPARTMENT_EMPTYCONTEXT will success. // Then, we use an empty text store when there is no editable text to expose // the content URL and InputScope properties. RefPtr emptyContextCompartment; HRESULT hr = threadMgr->QueryInterface( GUID_COMPARTMENT_EMPTYCONTEXT, getter_AddRefs(emptyContextCompartment)); return SUCCEEDED(hr); }(); // Create context and add it to document manager RefPtr context; hr = documentMgr->CreateContext( TSFUtils::ClientId(), 0, emptyContextIsSupported ? static_cast(this) : nullptr, getter_AddRefs(context), &mEditCookie); if (NS_WARN_IF(FAILED(hr))) { MOZ_LOG(gIMELog, LogLevel::Error, ("0x%p TSFEmptyTextStore::Init() FAILED to create the context " "(0x%08lX)", this, hr)); return false; } if (NS_WARN_IF(mDestroyed)) { MOZ_LOG( gIMELog, LogLevel::Error, ("0x%p TSFEmptyTextStore::Init() FAILED to create ITfContext due to " "TextStore being destroyed during calling " "ITfDocumentMgr::CreateContext()", this)); return false; } // Make the context for this disabled and empty. TSFUtils::MarkContextAsKeyboardDisabled(context); TSFUtils::MarkContextAsEmpty(context); if (emptyContextIsSupported) { HRESULT hr = documentMgr->Push(context); if (NS_WARN_IF(FAILED(hr))) { MOZ_LOG(gIMELog, LogLevel::Error, ("0x%p TSFEmptyTextStore::Init() FAILED to push the context " "(0x%08lX)", this, hr)); return false; } if (NS_WARN_IF(mDestroyed)) { MOZ_LOG( gIMELog, LogLevel::Error, ("0x%p TSFEmptyTextStore::Init() FAILED to create ITfContext due " "to TextStore being destroyed during calling ITfDocumentMgr::Push()", this)); documentMgr->Pop(TF_POPF_ALL); return false; } } mDocumentMgr = documentMgr; mContext = context; MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::Init() succeeded: " "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08lX", this, mDocumentMgr.get(), mContext.get(), mEditCookie)); return true; } void TSFEmptyTextStore::Destroy() { if (mBeingDestroyed) { return; } MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::Destroy(), mLock=%s", this, AutoLockFlagsCString(mLock).get())); mDestroyed = true; if (mLock) { mPendingDestroy = true; return; } AutoRestore savedBeingDestroyed(mBeingDestroyed); mBeingDestroyed = true; ReleaseTSFObjects(); MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::Destroy() succeeded", this)); } void TSFEmptyTextStore::ReleaseTSFObjects() { MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::ReleaseTSFObjects()", this)); mDocumentURL.Truncate(); mContext = nullptr; if (const RefPtr documentMgr = mDocumentMgr.forget()) { documentMgr->Pop(TF_POPF_ALL); } MOZ_ASSERT(!mDocumentMgr); mSink = nullptr; mWidget = nullptr; mDispatcher = nullptr; MOZ_LOG(gIMELog, LogLevel::Debug, ("0x%p TSFEmptyTextStore::ReleaseTSFObjects() completed", this)); } STDMETHODIMP TSFEmptyTextStore::QueryInterface(REFIID riid, void** ppv) { HRESULT hr = TSFTextStoreBase::QueryInterface(riid, ppv); if (*ppv) { return hr; } MOZ_ASSERT(riid != IID_IUnknown); MOZ_ASSERT(riid != IID_ITextStoreACP); MOZ_LOG(gIMELog, LogLevel::Error, ("0x%p TSFEmptyTextStore::QueryInterface() FAILED, riid=%s", this, AutoRiidCString(riid).get())); return E_NOINTERFACE; } STDMETHODIMP TSFEmptyTextStore::QueryInsert(LONG acpTestStart, LONG acpTestEnd, ULONG cch, LONG* pacpResultStart, LONG* pacpResultEnd) { HRESULT hr = TSFTextStoreBase::QueryInsert(acpTestStart, acpTestEnd, cch, pacpResultStart, pacpResultEnd); if (MOZ_UNLIKELY(hr != E_NOTIMPL)) { return hr; } if (acpTestStart || acpTestEnd) { MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::QueryInsert() FAILED due to non-zero " "offsets", this)); return E_INVALIDARG; } *pacpResultStart = *pacpResultEnd = 0; MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::QueryInsert() succeeded: " "*pacpResultStart=0, *pacpResultEnd=0)", this)); return S_OK; } STDMETHODIMP TSFEmptyTextStore::GetSelection(ULONG ulIndex, ULONG ulCount, TS_SELECTION_ACP* pSelection, ULONG* pcFetched) { HRESULT hr = TSFTextStoreBase::GetSelection(ulIndex, ulCount, pSelection, pcFetched); if (MOZ_UNLIKELY(hr != E_NOTIMPL)) { return hr; } // XXX Should we treat selection as collapsed at the start? *pSelection = TSFUtils::EmptySelectionACP(); *pcFetched = 0; return TS_E_NOSELECTION; } STDMETHODIMP TSFEmptyTextStore::SetSelection( ULONG ulCount, const TS_SELECTION_ACP* pSelection) { HRESULT hr = TSFTextStoreBase::SetSelection(ulCount, pSelection); if (MOZ_UNLIKELY(hr != E_NOTIMPL)) { return hr; } if (ulCount == 1 && !pSelection->acpStart && !pSelection->acpEnd) { return S_OK; } MOZ_LOG(gIMELog, LogLevel::Error, ("0x%p TSFEmptyTextStore::SetSelection() FAILED", this)); return TF_E_INVALIDPOS; } STDMETHODIMP TSFEmptyTextStore::GetText(LONG acpStart, LONG acpEnd, WCHAR* pchPlain, ULONG cchPlainReq, ULONG* pcchPlainOut, TS_RUNINFO* prgRunInfo, ULONG ulRunInfoReq, ULONG* pulRunInfoOut, LONG* pacpNext) { MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::GetText()", this)); HRESULT hr = TSFTextStoreBase::GetText(acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, prgRunInfo, ulRunInfoReq, pulRunInfoOut, pacpNext); if (MOZ_UNLIKELY(hr != E_NOTIMPL)) { return hr; } if (acpStart || acpEnd > 0) { MOZ_LOG(gIMELog, LogLevel::Error, ("0x%p TSFEmptyTextStore::GetText() FAILED due to invalid offset", this)); return TS_E_INVALIDPOS; } MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::GetText() succeeded: pcchPlainOut=0x%p, " "*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, " "*pacpNext=%ld)", this, pcchPlainOut, prgRunInfo ? prgRunInfo->uCount : 0, prgRunInfo ? mozilla::ToString(prgRunInfo->type).c_str() : "N/A", pulRunInfoOut ? *pulRunInfoOut : 0, pacpNext ? *pacpNext : 0)); return S_OK; } STDMETHODIMP TSFEmptyTextStore::SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd, const WCHAR* pchText, ULONG cch, TS_TEXTCHANGE* pChange) { MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::GetText()", this)); HRESULT hr = TSFTextStoreBase::SetText(dwFlags, acpStart, acpEnd, pchText, cch, pChange); if (MOZ_UNLIKELY(hr != E_NOTIMPL)) { return hr; } MOZ_LOG(gIMELog, LogLevel::Error, ("0x%p TSFEmptyTextStore::SetText() FAILED due to readonly", this)); return TS_E_READONLY; } STDMETHODIMP TSFEmptyTextStore::RequestSupportedAttrs( DWORD dwFlags, ULONG cFilterAttrs, const TS_ATTRID* paFilterAttrs) { MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::RequestSupportedAttrs(dwFlags=%s, " "cFilterAttrs=%lu)", this, AutoFindFlagsCString(dwFlags).get(), cFilterAttrs)); return HandleRequestAttrs( dwFlags, cFilterAttrs, paFilterAttrs, TSFUtils::NUM_OF_SUPPORTED_ATTRS_IN_EMPTY_TEXT_STORE); } STDMETHODIMP TSFEmptyTextStore::RequestAttrsAtPosition( LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID* paFilterAttrs, DWORD dwFlags) { MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::RequestAttrsAtPosition(acpPos=%ld, " "cFilterAttrs=%lu, dwFlags=%s)", this, acpPos, cFilterAttrs, AutoFindFlagsCString(dwFlags).get())); return HandleRequestAttrs( dwFlags | TS_ATTR_FIND_WANT_VALUE, cFilterAttrs, paFilterAttrs, TSFUtils::NUM_OF_SUPPORTED_ATTRS_IN_EMPTY_TEXT_STORE); } STDMETHODIMP TSFEmptyTextStore::RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL* paAttrVals, ULONG* pcFetched) { HRESULT hr = RetrieveRequestedAttrsInternal( ulCount, paAttrVals, pcFetched, TSFUtils::NUM_OF_SUPPORTED_ATTRS_IN_EMPTY_TEXT_STORE); if (FAILED(hr)) { return hr; } if (*pcFetched) { return S_OK; } MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::RetrieveRequestedAttrs() called " "for unknown TS_ATTRVAL, *pcFetched=0 (S_OK)", this)); return S_OK; } STDMETHODIMP TSFEmptyTextStore::GetEndACP(LONG* pacp) { HRESULT hr = TSFTextStoreBase::GetEndACP(pacp); if (MOZ_UNLIKELY(hr != E_NOTIMPL)) { return hr; } *pacp = 0; return S_OK; } STDMETHODIMP TSFEmptyTextStore::GetACPFromPoint(TsViewCookie vcView, const POINT* pt, DWORD dwFlags, LONG* pacp) { HRESULT hr = TSFTextStoreBase::GetACPFromPoint(vcView, pt, dwFlags, pacp); if (MOZ_UNLIKELY(hr != E_NOTIMPL)) { return hr; } *pacp = 0; MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFEmptyTextStore::GetACPFromPoint() succeeded: *pacp=%ld", this, *pacp)); return S_OK; } STDMETHODIMP TSFEmptyTextStore::GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd, RECT* prc, BOOL* pfClipped) { HRESULT hr = TSFTextStoreBase::GetTextExt(vcView, acpStart, acpEnd, prc, pfClipped); if (MOZ_UNLIKELY(hr != E_NOTIMPL)) { return hr; } if (acpStart || acpEnd) { MOZ_LOG(gIMELog, LogLevel::Error, ("0x%p TSFEmptyTextStore::GetTextExt(), FAILED due to invalid " "offset", this)); return TS_E_INVALIDPOS; } prc->left = prc->top = prc->right = prc->bottom = 0; return S_OK; } STDMETHODIMP TSFEmptyTextStore::InsertTextAtSelection( DWORD dwFlags, const WCHAR* pchText, ULONG cch, LONG* pacpStart, LONG* pacpEnd, TS_TEXTCHANGE* pChange) { HRESULT hr = TSFTextStoreBase::InsertTextAtSelection( dwFlags, pchText, cch, pacpStart, pacpEnd, pChange); if (MOZ_UNLIKELY(hr != E_NOTIMPL)) { return hr; } MOZ_LOG(gIMELog, LogLevel::Error, ("0x%p TSFEmptyTextStore::InsertTextAtSelection() FAILED due to " "readonly", this)); return TS_E_READONLY; } // static Result, nsresult> TSFEmptyTextStore::CreateAndSetFocus(nsWindow* aFocusedWindow, const InputContext& aContext) { const RefPtr textStore = new TSFEmptyTextStore(); if (NS_WARN_IF(!textStore->Init(aFocusedWindow, aContext))) { MOZ_LOG(gIMELog, LogLevel::Error, (" TSFEmptyTextStore::CreateAndSetFocus() FAILED due to " "TSFEmptyTextStore::Init() failure")); textStore->Destroy(); TSFUtils::ClearStoringTextStoresIf(textStore); return Err(NS_ERROR_FAILURE); } nsresult rv = textStore->SetFocus(aFocusedWindow, aContext); if (NS_FAILED(rv)) { textStore->Destroy(); TSFUtils::ClearStoringTextStoresIf(textStore); return Err(rv); } return textStore; } nsresult TSFEmptyTextStore::SetFocus(nsWindow* aFocusedWindow, const InputContext& aContext) { const RefPtr docMgr = mDocumentMgr; if (NS_WARN_IF(!docMgr)) { MOZ_LOG(gIMELog, LogLevel::Error, (" TSFEmptyTextStore::SetFocus() FAILED due to " "invalid TSFEmptyTextStore::mDocumentMgr")); return NS_ERROR_FAILURE; } const RefPtr threadMgr = TSFUtils::GetThreadMgr(); if (NS_WARN_IF(FAILED(threadMgr->SetFocus(docMgr)))) { MOZ_LOG(gIMELog, LogLevel::Error, (" TSFEmptyTextStore::SetFocus() FAILED due to " "ITfTheadMgr::SetFocus() failure")); return NS_ERROR_FAILURE; } if (NS_WARN_IF(!TSFUtils::GetThreadMgr())) { MOZ_LOG(gIMELog, LogLevel::Error, (" TSFEmptyTextStore::SetFocus() FAILED due to " "sThreadMgr being destroyed during calling " "ITfTheadMgr::SetFocus()")); return NS_ERROR_FAILURE; } if (NS_WARN_IF(TSFUtils::GetCurrentTextStore())) { MOZ_LOG(gIMELog, LogLevel::Error, (" TSFEmptyTextStore::SetFocus() FAILED due to " "creating TextStore has lost focus during calling " "ITfThreadMgr::SetFocus()")); return NS_ERROR_FAILURE; } return NS_OK; } nsresult TSFEmptyTextStore::SetFocusAndUpdateDocumentURLAndBrowsingMode( nsWindow* aFocusedWindow, const InputContext& aContext) { if (NS_WARN_IF(NS_FAILED( UpdateDocumentURLAndBrowsingMode(aFocusedWindow, aContext)))) { return NS_ERROR_FAILURE; } if (NS_WARN_IF(NS_FAILED(SetFocus(aFocusedWindow, aContext)))) { return NS_ERROR_FAILURE; } return NS_OK; } IMENotificationRequests TSFEmptyTextStore::GetIMENotificationRequests() const { return IMENotificationRequests(); } } // namespace mozilla::widget