diff --git a/widget/windows/TSFEmptyTextStore.cpp b/widget/windows/TSFEmptyTextStore.cpp new file mode 100644 index 000000000000..a523545ea02a --- /dev/null +++ b/widget/windows/TSFEmptyTextStore.cpp @@ -0,0 +1,507 @@ +/* -*- 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() { + 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; + } + // Create context and add it to document manager + // TODO: Set ourselves to the 3rd param. + RefPtr context; + hr = documentMgr->CreateContext(TSFUtils::ClientId(), 0, 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); + + 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, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "TSFTextStore::Init() failure")); + textStore->Destroy(); + TSFUtils::ClearStoringTextStoresIf(textStore); + return Err(NS_ERROR_FAILURE); + } + const RefPtr newDocMgr = textStore->mDocumentMgr; + if (NS_WARN_IF(!newDocMgr)) { + MOZ_LOG(gIMELog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "invalid TSFTextStore::mDocumentMgr")); + textStore->Destroy(); + TSFUtils::ClearStoringTextStoresIf(textStore); + return Err(NS_ERROR_FAILURE); + } + const RefPtr threadMgr = TSFUtils::GetThreadMgr(); + if (NS_WARN_IF(FAILED(threadMgr->SetFocus(newDocMgr)))) { + MOZ_LOG(gIMELog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "ITfTheadMgr::SetFocus() failure")); + textStore->Destroy(); + TSFUtils::ClearStoringTextStoresIf(textStore); + return Err(NS_ERROR_FAILURE); + } + if (NS_WARN_IF(!TSFUtils::GetThreadMgr())) { + MOZ_LOG(gIMELog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "sThreadMgr being destroyed during calling " + "ITfTheadMgr::SetFocus()")); + textStore->Destroy(); + TSFUtils::ClearStoringTextStoresIf(textStore); + return Err(NS_ERROR_FAILURE); + } + if (NS_WARN_IF(TSFUtils::GetCurrentTextStore())) { + MOZ_LOG(gIMELog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "creating TextStore has lost focus during calling " + "ITfThreadMgr::SetFocus()")); + textStore->Destroy(); + TSFUtils::ClearStoringTextStoresIf(textStore); + return Err(NS_ERROR_FAILURE); + } + // Use AssociateFocus() for ensuring that any native focus event + // never steal focus from our documentMgr. + { + RefPtr prevFocusedDocumentMgr; + if (NS_WARN_IF(FAILED(threadMgr->AssociateFocus( + aFocusedWindow->GetWindowHandle(), newDocMgr, + getter_AddRefs(prevFocusedDocumentMgr))))) { + MOZ_LOG(gIMELog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "ITfTheadMgr::AssociateFocus() failure")); + textStore->Destroy(); + TSFUtils::ClearStoringTextStoresIf(textStore); + return Err(NS_ERROR_FAILURE); + } + } + if (NS_WARN_IF(!TSFUtils::GetThreadMgr())) { + MOZ_LOG(gIMELog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "sThreadMgr being destroyed during calling " + "ITfTheadMgr::AssociateFocus()")); + textStore->Destroy(); + TSFUtils::ClearStoringTextStoresIf(textStore); + return Err(NS_ERROR_FAILURE); + } + if (NS_WARN_IF(TSFUtils::GetCurrentTextStore())) { + MOZ_LOG(gIMELog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "creating TextStore has lost focus during calling " + "ITfTheadMgr::AssociateFocus()")); + textStore->Destroy(); + TSFUtils::ClearStoringTextStoresIf(textStore); + return Err(NS_ERROR_FAILURE); + } + return textStore; +} + +// static +IMENotificationRequests TSFEmptyTextStore::GetIMENotificationRequests() { + return IMENotificationRequests(); +} + +} // namespace mozilla::widget diff --git a/widget/windows/TSFEmptyTextStore.h b/widget/windows/TSFEmptyTextStore.h new file mode 100644 index 000000000000..36dd280aee87 --- /dev/null +++ b/widget/windows/TSFEmptyTextStore.h @@ -0,0 +1,85 @@ +/* -*- 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/. */ + +#ifndef TSFEmptyTextStore_h +#define TSFEmptyTextStore_h + +#include "nsIWidget.h" +#include "nsWindow.h" + +#include "TSFTextStoreBase.h" +#include "TSFUtils.h" +#include "WinUtils.h" +#include "WritingModes.h" + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TextRange.h" +#include "mozilla/widget/IMEData.h" + +#include +#include + +struct ITfThreadMgr; +struct ITfDocumentMgr; +struct ITfDisplayAttributeMgr; +struct ITfCategoryMgr; +class nsWindow; + +namespace mozilla::widget { + +class TSFEmptyTextStore final : public TSFTextStoreBase { + friend class TSFStaticSink; + + public: /*IUnknown*/ + STDMETHODIMP QueryInterface(REFIID, void**); + + NS_INLINE_DECL_IUNKNOWN_ADDREF_RELEASE(TSFEmptyTextStore); + + public: /*ITextStoreACP*/ + STDMETHODIMP QueryInsert(LONG, LONG, ULONG, LONG*, LONG*); + STDMETHODIMP GetSelection(ULONG, ULONG, TS_SELECTION_ACP*, ULONG*); + STDMETHODIMP SetSelection(ULONG, const TS_SELECTION_ACP*); + STDMETHODIMP GetText(LONG, LONG, WCHAR*, ULONG, ULONG*, TS_RUNINFO*, ULONG, + ULONG*, LONG*); + STDMETHODIMP SetText(DWORD, LONG, LONG, const WCHAR*, ULONG, TS_TEXTCHANGE*); + STDMETHODIMP RequestSupportedAttrs(DWORD, ULONG, const TS_ATTRID*); + STDMETHODIMP RequestAttrsAtPosition(LONG, ULONG, const TS_ATTRID*, DWORD); + STDMETHODIMP RetrieveRequestedAttrs(ULONG, TS_ATTRVAL*, ULONG*); + STDMETHODIMP GetEndACP(LONG*); + STDMETHODIMP GetACPFromPoint(TsViewCookie, const POINT*, DWORD, LONG*); + STDMETHODIMP GetTextExt(TsViewCookie, LONG, LONG, RECT*, BOOL*); + STDMETHODIMP InsertTextAtSelection(DWORD, const WCHAR*, ULONG, LONG*, LONG*, + TS_TEXTCHANGE*); + + public: + [[nodiscard]] IMENotificationRequests GetIMENotificationRequests() final; + + void Destroy() final; + + [[nodiscard]] static Result, nsresult> + CreateAndSetFocus(nsWindow* aFocusedWindow, const InputContext& aContext); + + protected: + TSFEmptyTextStore(); + virtual ~TSFEmptyTextStore(); + + bool Init(nsWindow* aWidget, const InputContext& aContext); + void ReleaseTSFObjects(); + + // This is called immediately after a call of OnLockGranted() of mSink. + // Note that mLock isn't cleared yet when this is called. + void DidLockGranted() final { + mDeferNotifyingTSF = mDeferNotifyingTSFUntilNextUpdate = false; + } +}; + +} // namespace mozilla::widget + +#endif // #ifndef TSFEmptyTextStore_h diff --git a/widget/windows/TSFTextStore.cpp b/widget/windows/TSFTextStore.cpp index cb12e8adab48..4f33f3068ce1 100644 --- a/widget/windows/TSFTextStore.cpp +++ b/widget/windows/TSFTextStore.cpp @@ -7,7 +7,6 @@ #include "IMMHandler.h" #include "KeyboardLayout.h" -#include "TSFInputScope.h" #include "TSFStaticSink.h" #include "TSFUtils.h" #include "WinIMEHandler.h" @@ -84,7 +83,7 @@ bool TSFTextStore::Init(nsWindow* aWidget, const InputContext& aContext) { } // Create document manager - RefPtr threadMgr = TSFUtils::GetThreadMgr(); + const RefPtr threadMgr = TSFUtils::GetThreadMgr(); RefPtr documentMgr; HRESULT hr = threadMgr->CreateDocumentMgr(getter_AddRefs(documentMgr)); if (NS_WARN_IF(FAILED(hr))) { @@ -211,10 +210,10 @@ void TSFTextStore::ReleaseTSFObjects() { mDocumentURL.Truncate(); mContext = nullptr; - if (mDocumentMgr) { - RefPtr documentMgr = mDocumentMgr.forget(); + if (const RefPtr documentMgr = mDocumentMgr.forget()) { documentMgr->Pop(TF_POPF_ALL); } + MOZ_ASSERT(!mDocumentMgr); mSink = nullptr; mWidget = nullptr; mDispatcher = nullptr; @@ -766,7 +765,7 @@ STDMETHODIMP TSFTextStore::GetSelection(ULONG ulIndex, ULONG ulCount, Maybe& selectionForTSF = SelectionForTSF(); if (selectionForTSF.isNothing()) { if (TSFUtils::DoNotReturnErrorFromGetSelection()) { - *pSelection = Selection::EmptyACP(); + *pSelection = TSFUtils::EmptySelectionACP(); *pcFetched = 1; MOZ_LOG( gIMELog, LogLevel::Info, @@ -782,7 +781,7 @@ STDMETHODIMP TSFTextStore::GetSelection(ULONG ulIndex, ULONG ulCount, return E_FAIL; } if (!selectionForTSF->HasRange()) { - *pSelection = Selection::EmptyACP(); + *pSelection = TSFUtils::EmptySelectionACP(); *pcFetched = 0; return TS_E_NOSELECTION; } @@ -1780,7 +1779,6 @@ STDMETHODIMP TSFTextStore::RetrieveRequestedAttrs(ULONG ulCount, if (*pcFetched) { return S_OK; } - MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFTextStore::RetrieveRequestedAttrs() called " "for unknown TS_ATTRVAL, *pcFetched=0 (S_OK)", @@ -3090,7 +3088,7 @@ Result, nsresult> TSFTextStore::CreateAndSetFocus( TSFUtils::ClearStoringTextStoresIf(textStore); return Err(NS_ERROR_FAILURE); } - if (NS_WARN_IF(TSFUtils::GetActiveTextStore())) { + if (NS_WARN_IF(TSFUtils::GetCurrentTextStore())) { MOZ_LOG(gIMELog, LogLevel::Error, (" TSFTextStore::CreateAndSetFocus() FAILED due to " "creating TextStore has lost focus during calling " @@ -3123,7 +3121,7 @@ Result, nsresult> TSFTextStore::CreateAndSetFocus( TSFUtils::ClearStoringTextStoresIf(textStore); return Err(NS_ERROR_FAILURE); } - if (NS_WARN_IF(TSFUtils::GetActiveTextStore())) { + if (NS_WARN_IF(TSFUtils::GetCurrentTextStore())) { MOZ_LOG(gIMELog, LogLevel::Error, (" TSFTextStore::CreateAndSetFocus() FAILED due to " "creating TextStore has lost focus during calling " @@ -3132,6 +3130,7 @@ Result, nsresult> TSFTextStore::CreateAndSetFocus( TSFUtils::ClearStoringTextStoresIf(textStore); return Err(NS_ERROR_FAILURE); } + if (textStore->mSink) { MOZ_LOG(gIMELog, LogLevel::Info, (" TSFTextStore::CreateAndSetFocus(), calling " @@ -3139,7 +3138,7 @@ Result, nsresult> TSFTextStore::CreateAndSetFocus( textStore.get())); const RefPtr sink = textStore->mSink; sink->OnLayoutChange(TS_LC_CREATE, TSFUtils::sDefaultView); - if (NS_WARN_IF(TSFUtils::GetActiveTextStore())) { + if (NS_WARN_IF(TSFUtils::GetCurrentTextStore())) { MOZ_LOG(gIMELog, LogLevel::Error, (" TSFTextStore::CreateAndSetFocus() FAILED due to " "creating TextStore has lost focus during calling " @@ -3902,83 +3901,6 @@ bool TSFTextStore::GetIMEOpenState() { return variant.lVal != 0; } -// static -void TSFTextStore::SetInputContext(nsWindow* aWidget, - const InputContext& aContext, - const InputContextAction& aAction) { - MOZ_LOG( - gIMELog, LogLevel::Debug, - ("TSFTextStore::SetInputContext(aWidget=%p, " - "aContext=%s, aAction.mFocusChange=%s), " - "EnabledTextStore(0x%p)={ mWidget=0x%p }, " - "TSFUtils::GetActiveTextStore()->MaybeHasFocus()=%s", - aWidget, mozilla::ToString(aContext).c_str(), - mozilla::ToString(aAction.mFocusChange).c_str(), - TSFUtils::GetActiveTextStore(), - TSFUtils::GetActiveTextStore() - ? TSFUtils::GetActiveTextStore()->mWidget.get() - : nullptr, - TSFUtils::BoolToChar(TSFUtils::GetActiveTextStore() && - TSFUtils::GetActiveTextStore()->MaybeHasFocus()))); - switch (aAction.mFocusChange) { - case InputContextAction::WIDGET_CREATED: - // If this is called when the widget is created, there is nothing to do. - return; - case InputContextAction::FOCUS_NOT_CHANGED: - case InputContextAction::MENU_LOST_PSEUDO_FOCUS: - if (NS_WARN_IF(!IsInTSFMode())) { - return; - } - // In these cases, `NOTIFY_IME_OF_FOCUS` won't be sent. Therefore, - // we need to reset text store for new state right now. - break; - default: - NS_WARNING_ASSERTION(IsInTSFMode(), - "Why is this called when TSF is disabled?"); - if (TSFUtils::GetActiveTextStore()) { - const RefPtr textStore(TSFUtils::GetActiveTextStore()); - textStore->mInPrivateBrowsing = aContext.mInPrivateBrowsing; - textStore->SetInputScope(aContext.mHTMLInputType, - aContext.mHTMLInputMode); - if (aContext.mURI) { - nsAutoCString spec; - if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) { - CopyUTF8toUTF16(spec, textStore->mDocumentURL); - } else { - textStore->mDocumentURL.Truncate(); - } - } else { - textStore->mDocumentURL.Truncate(); - } - } - return; - } - - const bool activeTextStoreMaybeHasFocus = - TSFUtils::GetActiveTextStore() && - TSFUtils::GetActiveTextStore()->MaybeHasFocus(); - - // If focus isn't actually changed but the enabled state is changed, - // emulate the focus move. - if (!activeTextStoreMaybeHasFocus && aContext.mIMEState.IsEditable()) { - if (!IMEHandler::GetFocusedWindow()) { - MOZ_LOG(gIMELog, LogLevel::Error, - (" TSFTextStore::SetInputContent() gets called to enable IME, " - "but IMEHandler has not received focus notification")); - } else { - MOZ_LOG(gIMELog, LogLevel::Debug, - (" TSFTextStore::SetInputContent() emulates focus for IME " - "state change")); - TSFUtils::OnFocusChange(TSFUtils::GotFocus::Yes, aWidget, aContext); - } - } else if (activeTextStoreMaybeHasFocus && !aContext.mIMEState.IsEditable()) { - MOZ_LOG(gIMELog, LogLevel::Debug, - (" TSFTextStore::SetInputContent() emulates blur for IME " - "state change")); - TSFUtils::OnFocusChange(TSFUtils::GotFocus::No, aWidget, aContext); - } -} - // static bool TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg) { if (!TSFUtils::GetThreadMgr()) { diff --git a/widget/windows/TSFTextStore.h b/widget/windows/TSFTextStore.h index cda200cd6e2c..b6bf66b179d2 100644 --- a/widget/windows/TSFTextStore.h +++ b/widget/windows/TSFTextStore.h @@ -17,8 +17,6 @@ #include "mozilla/Attributes.h" #include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" -#include "mozilla/StaticPtr.h" -#include "mozilla/TextEventDispatcher.h" #include "mozilla/TextEvents.h" #include "mozilla/TextRange.h" #include "mozilla/widget/IMEData.h" @@ -86,17 +84,19 @@ class TSFTextStore final : public TSFTextStoreBase, static bool GetIMEOpenState(void); static void CommitComposition(bool aDiscard) { - NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called"); + NS_ASSERTION(TSFUtils::IsAvailable(), + "Not in TSF mode, shouldn't be called"); if (const RefPtr textStore = TSFUtils::GetActiveTextStore()) { textStore->CommitCompositionInternal(aDiscard); } } - static void SetInputContext(nsWindow* aWidget, const InputContext& aContext, - const InputContextAction& aAction); + // TODO: Move the following notification receiver methods to TSFUtils because + // TSFEmptyTextStore might want to receive the notifications in the future. static nsresult OnTextChange(const IMENotification& aIMENotification) { - NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called"); + NS_ASSERTION(TSFUtils::IsAvailable(), + "Not in TSF mode, shouldn't be called"); if (const RefPtr textStore = TSFUtils::GetActiveTextStore()) { return textStore->OnTextChangeInternal(aIMENotification); } @@ -104,7 +104,8 @@ class TSFTextStore final : public TSFTextStoreBase, } static nsresult OnSelectionChange(const IMENotification& aIMENotification) { - NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called"); + NS_ASSERTION(TSFUtils::IsAvailable(), + "Not in TSF mode, shouldn't be called"); if (const RefPtr textStore = TSFUtils::GetActiveTextStore()) { return textStore->OnSelectionChangeInternal(aIMENotification); } @@ -112,7 +113,8 @@ class TSFTextStore final : public TSFTextStoreBase, } static nsresult OnLayoutChange() { - NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called"); + NS_ASSERTION(TSFUtils::IsAvailable(), + "Not in TSF mode, shouldn't be called"); if (const RefPtr textStore = TSFUtils::GetActiveTextStore()) { return textStore->OnLayoutChangeInternal(); } @@ -120,7 +122,8 @@ class TSFTextStore final : public TSFTextStoreBase, } static nsresult OnUpdateComposition() { - NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called"); + NS_ASSERTION(TSFUtils::IsAvailable(), + "Not in TSF mode, shouldn't be called"); if (const RefPtr textStore = TSFUtils::GetActiveTextStore()) { return textStore->OnUpdateCompositionInternal(); } @@ -128,16 +131,15 @@ class TSFTextStore final : public TSFTextStoreBase, } static nsresult OnMouseButtonEvent(const IMENotification& aIMENotification) { - NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called"); + NS_ASSERTION(TSFUtils::IsAvailable(), + "Not in TSF mode, shouldn't be called"); if (const RefPtr textStore = TSFUtils::GetActiveTextStore()) { return textStore->OnMouseButtonEventInternal(aIMENotification); } return NS_OK; } - static IMENotificationRequests GetIMENotificationRequests(); - - static bool IsInTSFMode() { return TSFUtils::GetThreadMgr() != nullptr; } + [[nodiscard]] IMENotificationRequests GetIMENotificationRequests() final; static bool IsComposing() { return TSFUtils::GetActiveTextStore() && @@ -376,13 +378,6 @@ class TSFTextStore final : public TSFTextStoreBase, class Selection { public: - static TS_SELECTION_ACP EmptyACP() { - return TS_SELECTION_ACP{ - .acpStart = 0, - .acpEnd = 0, - .style = {.ase = TS_AE_NONE, .fInterimChar = FALSE}}; - } - bool HasRange() const { return mACP.isSome(); } const TS_SELECTION_ACP& ACPRef() const { return mACP.ref(); } diff --git a/widget/windows/TSFTextStoreBase.cpp b/widget/windows/TSFTextStoreBase.cpp index e875909bd416..dd3aa642fff3 100644 --- a/widget/windows/TSFTextStoreBase.cpp +++ b/widget/windows/TSFTextStoreBase.cpp @@ -7,6 +7,7 @@ #include "IMMHandler.h" #include "TSFInputScope.h" +#include "TSFTextStore.h" #include "TSFUtils.h" #include "WinIMEHandler.h" #include "WinMessages.h" @@ -1012,7 +1013,9 @@ STDMETHODIMP TSFTextStoreBase::InsertEmbeddedAtSelection( HRESULT TSFTextStoreBase::HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount, const TS_ATTRID* aFilterAttrs, int32_t aNumOfSupportedAttrs) { - MOZ_ASSERT(aNumOfSupportedAttrs == TSFUtils::NUM_OF_SUPPORTED_ATTRS); + MOZ_ASSERT(aNumOfSupportedAttrs == TSFUtils::NUM_OF_SUPPORTED_ATTRS || + aNumOfSupportedAttrs == + TSFUtils::NUM_OF_SUPPORTED_ATTRS_IN_EMPTY_TEXT_STORE); MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p TSFTextStoreBase::HandleRequestAttrs(aFlags=%s, " "aFilterCount=%lu, aNumOfSupportedAttrs=%d)", @@ -1050,7 +1053,9 @@ HRESULT TSFTextStoreBase::HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount, HRESULT TSFTextStoreBase::RetrieveRequestedAttrsInternal( ULONG ulCount, TS_ATTRVAL* paAttrVals, ULONG* pcFetched, int32_t aNumOfSupportedAttrs) { - MOZ_ASSERT(aNumOfSupportedAttrs == TSFUtils::NUM_OF_SUPPORTED_ATTRS); + MOZ_ASSERT(aNumOfSupportedAttrs == TSFUtils::NUM_OF_SUPPORTED_ATTRS || + aNumOfSupportedAttrs == + TSFUtils::NUM_OF_SUPPORTED_ATTRS_IN_EMPTY_TEXT_STORE); if (!pcFetched || !paAttrVals) { MOZ_LOG(gIMELog, LogLevel::Error, @@ -1155,4 +1160,69 @@ HRESULT TSFTextStoreBase::RetrieveRequestedAttrsInternal( #undef DEBUG_PRINT_DOCUMENT_URL +// static +void TSFTextStoreBase::SetInputContext(nsWindow* aWindow, + const InputContext& aContext, + const InputContextAction& aAction) { + MOZ_LOG(gIMELog, LogLevel::Debug, + ("TSFUtils::OnSetInputContext(aWidget=%p, " + "aContext=%s, aAction.mFocusChange=%s), " + "CurrentTextStore(0x%p)={ mWidget=0x%p, mContext=0x%p }", + aWindow, mozilla::ToString(aContext).c_str(), + mozilla::ToString(aAction.mFocusChange).c_str(), + TSFUtils::GetCurrentTextStore(), + TSFUtils::GetCurrentTextStore() + ? TSFUtils::GetCurrentTextStore()->GetWindow() + : nullptr, + TSFUtils::GetCurrentTextStore() + ? TSFUtils::GetCurrentTextStore()->GetContext() + : nullptr)); + + const bool isEditable = + aContext.mIMEState.IsEditable() && !aWindow->Destroyed(); + switch (aAction.mFocusChange) { + case InputContextAction::WIDGET_CREATED: + // If this is called when the widget is created, there is nothing to do. + return; + case InputContextAction::FOCUS_NOT_CHANGED: + case InputContextAction::MENU_LOST_PSEUDO_FOCUS: + // In these cases, `NOTIFY_IME_OF_FOCUS` won't be sent. Therefore, + // we need to reset text store for new state right now. + break; + default: { + // If IME enabled state is changed, we need to recreate proper + // TSFTextStoreBase instance. + if (isEditable != TSFUtils::CurrentTextStoreIsEditable()) { + break; + } + // If the editable state is not changed, we can just update the input + // scopes and the document URL. + const RefPtr textStore = + TSFUtils::GetCurrentTextStore(); + if (!textStore) { + break; + } + textStore->mInPrivateBrowsing = aContext.mInPrivateBrowsing; + textStore->SetInputScope(aContext.mHTMLInputType, + aContext.mHTMLInputMode); + if (aContext.mURI) { + nsAutoCString spec; + if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) { + CopyUTF8toUTF16(spec, textStore->mDocumentURL); + } else { + textStore->mDocumentURL.Truncate(); + } + } else { + textStore->mDocumentURL.Truncate(); + } + return; + } + } + + // Emulate a focus move to make a current TextStore to interact with TSF. + TSFUtils::OnFocusChange( + isEditable ? TSFUtils::GotFocus::Yes : TSFUtils::GotFocus::No, aWindow, + aContext); +} + } // namespace mozilla::widget diff --git a/widget/windows/TSFTextStoreBase.h b/widget/windows/TSFTextStoreBase.h index 2c60d76cdff8..40a056c4b471 100644 --- a/widget/windows/TSFTextStoreBase.h +++ b/widget/windows/TSFTextStoreBase.h @@ -15,6 +15,7 @@ #include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" #include "mozilla/TextEventDispatcher.h" +#include "mozilla/WritingModes.h" #include "mozilla/widget/IMEData.h" #include @@ -74,8 +75,14 @@ class TSFTextStoreBase : public ITextStoreACP { [[nodiscard]] ITfContext* GetContext() const { return mContext; } [[nodiscard]] nsWindow* GetWindow() const { return mWidget; } + [[nodiscard]] virtual IMENotificationRequests + GetIMENotificationRequests() = 0; + virtual void Destroy() = 0; + static void SetInputContext(nsWindow* aWindow, const InputContext& aContext, + const InputContextAction& aAction); + protected: TSFTextStoreBase() = default; virtual ~TSFTextStoreBase() = default; diff --git a/widget/windows/TSFUtils.cpp b/widget/windows/TSFUtils.cpp index 13a84f7bd6f3..b3d523710ad8 100644 --- a/widget/windows/TSFUtils.cpp +++ b/widget/windows/TSFUtils.cpp @@ -8,6 +8,7 @@ #include "TSFUtils.h" #include "IMMHandler.h" +#include "TSFEmptyTextStore.h" #include "TSFStaticSink.h" #include "TSFTextInputProcessorList.h" #include "TSFTextStore.h" @@ -17,6 +18,7 @@ #include "mozilla/StaticPrefs_intl.h" #include "mozilla/WindowsVersion.h" #include "mozilla/widget/WinRegistry.h" +#include "nsPrintfCString.h" // For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync` // rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too @@ -527,16 +529,17 @@ StaticRefPtr TSFUtils::sKeystrokeMgr; StaticRefPtr TSFUtils::sDisplayAttrMgr; StaticRefPtr TSFUtils::sCategoryMgr; StaticRefPtr TSFUtils::sCompartmentForOpenClose; -StaticRefPtr TSFUtils::sDisabledDocumentMgr; -StaticRefPtr TSFUtils::sDisabledContext; StaticRefPtr TSFUtils::sInputProcessorProfiles; StaticRefPtr TSFUtils::sActiveTextStore; +StaticRefPtr TSFUtils::sCurrentTextStore; DWORD TSFUtils::sClientId = 0; template void TSFUtils::ClearStoringTextStoresIf( const RefPtr& aTextStore); template void TSFUtils::ClearStoringTextStoresIf( const RefPtr& aTextStore); +template void TSFUtils::ClearStoringTextStoresIf( + const RefPtr& aTextStore); void TSFUtils::Initialize() { MOZ_LOG(gIMELog, LogLevel::Info, ("TSFUtils::Initialize() is called...")); @@ -574,48 +577,10 @@ void TSFUtils::Initialize() { return; } - RefPtr disabledDocumentMgr; - hr = threadMgr->CreateDocumentMgr(getter_AddRefs(disabledDocumentMgr)); - if (FAILED(hr) || !disabledDocumentMgr) { - MOZ_LOG(gIMELog, LogLevel::Error, - (" TSFUtils::Initialize() FAILED to create " - "a document manager for disabled mode, hr=0x%08lX", - hr)); - return; - } - - RefPtr disabledContext; - DWORD editCookie = 0; - hr = disabledDocumentMgr->CreateContext( - sClientId, 0, nullptr, getter_AddRefs(disabledContext), &editCookie); - if (FAILED(hr) || !disabledContext) { - MOZ_LOG(gIMELog, LogLevel::Error, - (" TSFUtils::Initialize() FAILED to create " - "a context for disabled mode, hr=0x%08lX", - hr)); - return; - } - - TSFUtils::MarkContextAsKeyboardDisabled(disabledContext); - TSFUtils::MarkContextAsEmpty(disabledContext); - hr = disabledDocumentMgr->Push(disabledContext); - if (FAILED(hr)) { - MOZ_LOG(gIMELog, LogLevel::Error, - (" TSFUtils::Initialize() FAILED to push disabled context, " - "hr=0x%08lX", - hr)); - // Don't return, we should ignore the failure and release them later. - } - - sThreadMgr = threadMgr; - sDisabledDocumentMgr = disabledDocumentMgr; - sDisabledContext = disabledContext; - + sThreadMgr = std::move(threadMgr); MOZ_LOG(gIMELog, LogLevel::Info, - (" TSFUtils::Initialize(), sThreadMgr=0x%p, " - "sClientId=0x%08lX, sDisabledDocumentMgr=0x%p, sDisabledContext=%p", - sThreadMgr.get(), sClientId, sDisabledDocumentMgr.get(), - sDisabledContext.get())); + (" TSFTextStore::Initialize(), sThreadMgr=0x%p, sClientId=0x%08lX", + sThreadMgr.get(), sClientId)); } // static @@ -627,11 +592,9 @@ void TSFUtils::Shutdown() { sDisplayAttrMgr = nullptr; sCategoryMgr = nullptr; sActiveTextStore = nullptr; - if (const RefPtr disabledDocumentMgr = - sDisabledDocumentMgr.forget()) { - MOZ_ASSERT(!sDisabledDocumentMgr); - disabledDocumentMgr->Pop(TF_POPF_ALL); - sDisabledContext = nullptr; + if (RefPtr textStore = sCurrentTextStore.forget()) { + textStore->Destroy(); + MOZ_ASSERT(!sCurrentTextStore); } sCompartmentForOpenClose = nullptr; sInputProcessorProfiles = nullptr; @@ -647,13 +610,24 @@ void TSFUtils::Shutdown() { template void TSFUtils::ClearStoringTextStoresIf( const RefPtr& aTextStore) { + if (sCurrentTextStore.get() == + static_cast(aTextStore.get())) { + MOZ_ASSERT(sActiveTextStore == sCurrentTextStore); + sActiveTextStore = nullptr; + sCurrentTextStore = nullptr; + return; + } if (static_cast(sActiveTextStore.get()) == static_cast(aTextStore.get())) { sActiveTextStore = nullptr; - return; } } +IMENotificationRequests TSFUtils::GetIMENotificationRequests() { + return sCurrentTextStore ? sCurrentTextStore->GetIMENotificationRequests() + : IMENotificationRequests(); +} + inline std::ostream& operator<<( std::ostream& aStream, const mozilla::widget::TSFUtils::GotFocus& aGotFocus) { @@ -661,33 +635,46 @@ inline std::ostream& operator<<( << (static_cast(aGotFocus) ? "Yes" : "No"); } -// static nsresult TSFUtils::OnFocusChange(GotFocus aGotFocus, nsWindow* aFocusedWindow, const InputContext& aContext) { MOZ_LOG(gIMELog, LogLevel::Debug, - (" TSFUtils::OnFocusChange(aGotFocus=%s, aFocusedWindow=0x%p, " - "aContext=%s), ThreadMgr=0x%p, EnabledTextStore=0x%p", + (" TSFUtils::OnFocusChange(aGotFocus=%s, " + "aFocusedWindow=0x%p, aContext=%s), " + "ThreadMgr=0x%p, EnabledTextStore=0x%p", mozilla::ToString(aGotFocus).c_str(), aFocusedWindow, mozilla::ToString(aContext).c_str(), TSFUtils::GetThreadMgr(), TSFUtils::GetActiveTextStore())); - if (NS_WARN_IF(!TSFUtils::IsAvailable())) { + if (!TSFUtils::IsAvailable()) { return NS_ERROR_NOT_AVAILABLE; } - const bool hasFocus = sActiveTextStore && sActiveTextStore->MaybeHasFocus(); - const RefPtr oldTextStore = sActiveTextStore.forget(); + const RefPtr oldTextStore = sCurrentTextStore.forget(); + sActiveTextStore = nullptr; // If currently oldTextStore still has focus, notifies TSF of losing focus. - if (hasFocus) { + if (oldTextStore && oldTextStore->MaybeHasFocus()) { const RefPtr threadMgr(sThreadMgr); - RefPtr prevFocusedDocumentMgr; - DebugOnly hr = threadMgr->AssociateFocus( - oldTextStore->GetWindow()->GetWindowHandle(), nullptr, - getter_AddRefs(prevFocusedDocumentMgr)); - NS_ASSERTION(SUCCEEDED(hr), "Disassociating focus failed"); - NS_ASSERTION(prevFocusedDocumentMgr == oldTextStore->GetDocumentMgr(), - "different documentMgr has been associated with the window"); + // If active window is switched, threadMgr has already handled the focus + // change, then, we'll fail AssociateFocus() and the following assertions + // will fail. To avoid the latter, we should check whether the focused + // documentMgr is still what oldTextStore set to. + RefPtr focusedDocumentMgr; + threadMgr->GetFocus(getter_AddRefs(focusedDocumentMgr)); + if (focusedDocumentMgr) { + RefPtr prevFocusedDocumentMgr; + DebugOnly hr = threadMgr->AssociateFocus( + oldTextStore->GetWindow()->GetWindowHandle(), nullptr, + getter_AddRefs(prevFocusedDocumentMgr)); + NS_WARNING_ASSERTION(SUCCEEDED(hr), "Disassociating focus failed"); + NS_ASSERTION(FAILED(hr) || + prevFocusedDocumentMgr == oldTextStore->GetDocumentMgr(), + nsPrintfCString("different documentMgr has been associated " + "with the window: expected: %p, but got: %p", + oldTextStore->GetDocumentMgr(), + prevFocusedDocumentMgr.get()) + .get()); + } } // Even if there was a focused TextStore, we won't use it with new focused @@ -698,30 +685,34 @@ nsresult TSFUtils::OnFocusChange(GotFocus aGotFocus, nsWindow* aFocusedWindow, if (NS_WARN_IF(!sThreadMgr)) { MOZ_LOG(gIMELog, LogLevel::Error, - (" TSFUtils::OnFocusChange() FAILED, due to ThreadMgr being " - "destroyed during calling ITfThreadMgr::AssociateFocus()")); - return NS_ERROR_FAILURE; - } - if (NS_WARN_IF(sActiveTextStore)) { - MOZ_LOG(gIMELog, LogLevel::Error, - (" TSFUtils::OnFocusChange() FAILED, due to nested event handling " - "has created another focused TextStore during calling " + (" TSFUtils::OnFocusChange() FAILED, due to " + "ThreadMgr being destroyed during calling " "ITfThreadMgr::AssociateFocus()")); return NS_ERROR_FAILURE; } + if (NS_WARN_IF(sCurrentTextStore)) { + MOZ_LOG( + gIMELog, LogLevel::Error, + (" TSFUtils::OnFocusChange() FAILED, due to " + "nested event handling has created another focused TextStore during " + "calling ITfThreadMgr::AssociateFocus()")); + return NS_ERROR_FAILURE; + } - // If this is a notification of blur, move focus to the dummy document - // manager. + // If this is a notification of blur, move focus to the empty text store. if (aGotFocus == GotFocus::No || !aContext.mIMEState.IsEditable()) { - const RefPtr threadMgr(sThreadMgr); - const RefPtr disabledDocumentMgr(sDisabledDocumentMgr); - HRESULT hr = threadMgr->SetFocus(disabledDocumentMgr); - if (NS_WARN_IF(FAILED(hr))) { - MOZ_LOG(gIMELog, LogLevel::Error, - (" TSFUtils::OnFocusChange() FAILED due to " - "ITfThreadMgr::SetFocus() failure")); - return NS_ERROR_FAILURE; + if (aFocusedWindow->Destroyed()) { + return NS_OK; } + Result, nsresult> ret = + TSFEmptyTextStore::CreateAndSetFocus(aFocusedWindow, aContext); + if (NS_WARN_IF(ret.isErr())) { + MOZ_LOG(gIMELog, LogLevel::Error, + (" TSFUtils::OnFocusChange() FAILED due to failing to " + "create and set focus to new TSFEmptyTextStore")); + return ret.unwrapErr(); + } + sCurrentTextStore = ret.unwrap().forget(); return NS_OK; } @@ -730,11 +721,12 @@ nsresult TSFUtils::OnFocusChange(GotFocus aGotFocus, nsWindow* aFocusedWindow, TSFTextStore::CreateAndSetFocus(aFocusedWindow, aContext); if (NS_WARN_IF(ret.isErr())) { MOZ_LOG(gIMELog, LogLevel::Error, - (" TSFUtils::OnFocusChange() FAILED due to failing to create and " - "set focus to new TSFTextStore failed")); + (" TSFUtils::OnFocusChange() FAILED due to failing to create " + "and set focus to new TSFTextStore failed")); return ret.unwrapErr(); } sActiveTextStore = ret.unwrap().forget(); + sCurrentTextStore = sActiveTextStore; return NS_OK; } diff --git a/widget/windows/TSFUtils.h b/widget/windows/TSFUtils.h index 19d1ee2f50d8..197152c41747 100644 --- a/widget/windows/TSFUtils.h +++ b/widget/windows/TSFUtils.h @@ -39,6 +39,10 @@ class nsWindow; namespace mozilla::widget { class TSFTextStore; +class TSFTextStoreBase; +struct IMENotificationRequests; +struct InputContext; +struct InputContextAction; class TSFUtils final { public: @@ -53,6 +57,8 @@ class TSFUtils final { */ [[nodiscard]] static bool IsAvailable() { return sThreadMgr; } + [[nodiscard]] static IMENotificationRequests GetIMENotificationRequests(); + enum class GotFocus : bool { No, Yes }; /** * Called when focus changed in the DOM level and aContext has the details of @@ -62,12 +68,45 @@ class TSFUtils final { const InputContext& aContext); /** - * Return active TextStore which is for handling inputs in editable content. + * Called when input context for aWindow is set. aWindow should have focus + * when this is called. + */ + static void OnSetInputContext(nsWindow* aWindow, const InputContext& aContext, + const InputContextAction& aAction); + + /** + * Active TextStore is a TSFTextStoreBase instance which is for editable + * content. + * Note that it may disable IME, e.g., when the editable content is a password + * field or `ime-mode: disabled`. */ [[nodiscard]] static TSFTextStore* GetActiveTextStore() { + MOZ_ASSERT_IF(sActiveTextStore, (void*)sActiveTextStore.get() == + (void*)sCurrentTextStore.get()); return sActiveTextStore.get(); } + /** + * Current TextStore ia TSFTextStoreBase instance for either the content is + * editable nor not editable. + */ + [[nodiscard]] static TSFTextStoreBase* GetCurrentTextStore() { + MOZ_ASSERT_IF(sActiveTextStore, (void*)sActiveTextStore.get() == + (void*)sCurrentTextStore.get()); + return sCurrentTextStore.get(); + } + + /** + * Check whether current TextStore is for editable one. I.e., when there is + * an active TextStore. This is just an alias to make the caller easier to + * read. + */ + [[nodiscard]] static bool CurrentTextStoreIsEditable() { + MOZ_ASSERT_IF(sActiveTextStore, (void*)sActiveTextStore.get() == + (void*)sCurrentTextStore.get()); + return sActiveTextStore; + } + template static void ClearStoringTextStoresIf( const RefPtr& aTextStore); @@ -90,10 +129,6 @@ class TSFUtils final { [[nodiscard]] static ITfCategoryMgr* GetCategoryMgr(); [[nodiscard]] static ITfCompartment* GetCompartmentForOpenClose(); - [[nodiscard]] static ITfDocumentMgr* GetDocumentMgrForDisabled() { - return sDisabledDocumentMgr; - } - [[nodiscard]] static DWORD ClientId() { return sClientId; } // TODO: GUID_PROP_URL has not been declared in the SDK yet. We should drop @@ -154,14 +189,19 @@ class TSFUtils final { // Used for result of GetRequestedAttrIndex() NotSupported = -1, - // Supported attributes + // Supported attributes even in TSFEmptyTextStore. InputScope = 0, DocumentURL, - TextVerticalWriting, + + // Count of the supported attrs in empty text store + NUM_OF_SUPPORTED_ATTRS_IN_EMPTY_TEXT_STORE, + + // Supported attributes in any TextStores. + TextVerticalWriting = NUM_OF_SUPPORTED_ATTRS_IN_EMPTY_TEXT_STORE, TextOrientation, // Count of the supported attributes - NUM_OF_SUPPORTED_ATTRS + NUM_OF_SUPPORTED_ATTRS, }; /** @@ -202,6 +242,13 @@ class TSFUtils final { static const char* CommonHRESULTToChar(HRESULT); static const char* HRESULTToChar(HRESULT); + static TS_SELECTION_ACP EmptySelectionACP() { + return TS_SELECTION_ACP{ + .acpStart = 0, + .acpEnd = 0, + .style = {.ase = TS_AE_NONE, .fInterimChar = FALSE}}; + } + private: static void EnsureMessagePump(); static void EnsureKeystrokeMgr(); @@ -222,16 +269,15 @@ class TSFUtils final { static StaticRefPtr sInputProcessorProfiles; - // For IME (keyboard) disabled state: - static StaticRefPtr sDisabledDocumentMgr; - static StaticRefPtr sDisabledContext; - - // Current text store which is managing a keyboard enabled editor (i.e., - // editable editor). Currently only ONE TSFTextStore instance is ever used, - // although Create is called when an editor is focused and Destroy called - // when the focused editor is blurred. + // Current active text store which is managing a keyboard enabled editor + // (i.e., editable editor). Currently only ONE TSFTextStore instance is ever + // used, although Create is called when an editor is focused and Destroy + // called when the focused editor is blurred. static StaticRefPtr sActiveTextStore; + // Current text store which may be an empty one for disabled state. + static StaticRefPtr sCurrentTextStore; + // TSF client ID for the current application static DWORD sClientId; }; diff --git a/widget/windows/WinIMEHandler.cpp b/widget/windows/WinIMEHandler.cpp index 46d490e34dc5..38a04c286832 100644 --- a/widget/windows/WinIMEHandler.cpp +++ b/widget/windows/WinIMEHandler.cpp @@ -82,14 +82,13 @@ void IMEHandler::Initialize() { IMMHandler::Initialize(); sForceDisableCurrentIMM_IME = IMMHandler::IsActiveIMEInBlockList(); + + mozilla::RunOnShutdown(IMEHandler::Terminate); } // static void IMEHandler::Terminate() { - if (TSFUtils::IsAvailable()) { - TSFUtils::Shutdown(); - } - + TSFUtils::Shutdown(); IMMHandler::Terminate(); WinTextEventDispatcherListener::Shutdown(); } @@ -355,7 +354,7 @@ nsresult IMEHandler::NotifyIME(nsWindow* aWindow, IMENotificationRequests IMEHandler::GetIMENotificationRequests() { if (IsTSFAvailable()) { if (!sIsIMMEnabled) { - return TSFTextStore::GetIMENotificationRequests(); + return TSFUtils::GetIMENotificationRequests(); } // Even if TSF is available, the active IME may be an IMM-IME. // Unfortunately, changing the result of GetIMENotificationRequests() while @@ -363,7 +362,7 @@ IMENotificationRequests IMEHandler::GetIMENotificationRequests() { // ContentCacheInParent. Therefore, we need to request whole notifications // which are necessary either IMMHandler or TSFTextStore. return IMMHandler::GetIMENotificationRequests() | - TSFTextStore::GetIMENotificationRequests(); + TSFUtils::GetIMENotificationRequests(); } return IMMHandler::GetIMENotificationRequests(); @@ -433,7 +432,7 @@ void IMEHandler::SetInputContext(nsWindow* aWindow, InputContext& aInputContext, // Note that even while a plugin has focus, we need to notify TSF of that. if (TSFUtils::IsAvailable()) { - TSFTextStore::SetInputContext(aWindow, aInputContext, aAction); + TSFTextStoreBase::SetInputContext(aWindow, aInputContext, aAction); if (IsTSFAvailable()) { if (sIsIMMEnabled) { // Associate IMC with aWindow only when it's necessary. @@ -496,7 +495,7 @@ void IMEHandler::InitInputContext(nsWindow* aWindow, aInputContext.mIMEState.mEnabled = IMEEnabled::Enabled; if (TSFUtils::IsAvailable()) { - TSFTextStore::SetInputContext( + TSFTextStoreBase::SetInputContext( aWindow, aInputContext, InputContextAction(InputContextAction::CAUSE_UNKNOWN, InputContextAction::WIDGET_CREATED)); diff --git a/widget/windows/moz.build b/widget/windows/moz.build index f628e7de66f1..1d3cd4e94697 100644 --- a/widget/windows/moz.build +++ b/widget/windows/moz.build @@ -106,6 +106,7 @@ UNIFIED_SOURCES += [ "TaskbarPreviewButton.cpp", "TaskbarTabPreview.cpp", "TaskbarWindowPreview.cpp", + "TSFEmptyTextStore.cpp", "TSFInputScope.cpp", "TSFStaticSink.cpp", "TSFTextStore.cpp", diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp index fcf81d405f6f..81d2037ec645 100644 --- a/widget/windows/nsWindow.cpp +++ b/widget/windows/nsWindow.cpp @@ -726,9 +726,15 @@ nsWindow::~nsWindow() { // Global shutdown if (sInstanceCount == 0) { - IMEHandler::Terminate(); sCurrentCursor = {}; if (sIsOleInitialized) { + // When we reach here, IMEHandler::Terminate() should've already been + // called because it causes releasing the last nsWindow instance. + // However, it **could** occur that we are shutting down without giving + // IME focus, but we need to release TSF objects before the following + // ::OleUninitialize() call. Fortunately, it's fine to call the method + // twice so that we can always call it here. + IMEHandler::Terminate(); ::OleFlushClipboard(); ::OleUninitialize(); sIsOleInitialized = false;