Bug 1953047 - part 4: Implement TSFEmptyTextStore for the disabled document manager and context r=m_kato

Even while IME is disabled (i.e., when non-editable element or nobody has
focus), we'll expose active tab's URL and whether it's in the private browsing
mode via the input scope.  Therefore, we need to handle requests for retrieving
attributes.  So, we need a TextStore instance for that.  However, they may
require special behavior due to its context is marked as empty and not editable.
Unfortunately, `TSFTextStore` is enough complicated and big even after some
features are moved to `TSFUtils` and `TSFTextStoreBase`.  Therefore, this patch
creates `TSFEmptyTextStore` to isolate the implementation from `TSFTextStore`.

Differential Revision: https://phabricator.services.mozilla.com/D242682
This commit is contained in:
Masayuki Nakano
2025-03-31 22:03:19 +00:00
parent be5c66ce93
commit b401605f20
11 changed files with 847 additions and 217 deletions

View File

@@ -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 <algorithm>
#include <comutil.h> // for _bstr_t
#include <oleauto.h> // for SysAllocString
#include <olectl.h>
// For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync`
// rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
// big file.
// Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
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<ITfThreadMgr> threadMgr = TSFUtils::GetThreadMgr();
RefPtr<ITfDocumentMgr> 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<ITfContext> 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<bool> 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<ITfDocumentMgr> 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<RefPtr<TSFEmptyTextStore>, nsresult>
TSFEmptyTextStore::CreateAndSetFocus(nsWindow* aFocusedWindow,
const InputContext& aContext) {
const RefPtr<TSFEmptyTextStore> 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<ITfDocumentMgr> 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<ITfThreadMgr> 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<ITfDocumentMgr> 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

View File

@@ -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 <msctf.h>
#include <textstor.h>
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<RefPtr<TSFEmptyTextStore>, 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

View File

@@ -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<ITfThreadMgr> threadMgr = TSFUtils::GetThreadMgr();
const RefPtr<ITfThreadMgr> threadMgr = TSFUtils::GetThreadMgr();
RefPtr<ITfDocumentMgr> 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<ITfDocumentMgr> documentMgr = mDocumentMgr.forget();
if (const RefPtr<ITfDocumentMgr> 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<Selection>& 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<RefPtr<TSFTextStore>, 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<RefPtr<TSFTextStore>, 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<RefPtr<TSFTextStore>, 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<RefPtr<TSFTextStore>, nsresult> TSFTextStore::CreateAndSetFocus(
textStore.get()));
const RefPtr<ITextStoreACPSink> 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<TSFTextStore> 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()) {

View File

@@ -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<TSFTextStore> 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<TSFTextStore> 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<TSFTextStore> 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<TSFTextStore> 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<TSFTextStore> 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<TSFTextStore> 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(); }

View File

@@ -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<TSFTextStoreBase> 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

View File

@@ -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 <msctf.h>
@@ -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;

View File

@@ -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<ITfKeystrokeMgr> TSFUtils::sKeystrokeMgr;
StaticRefPtr<ITfDisplayAttributeMgr> TSFUtils::sDisplayAttrMgr;
StaticRefPtr<ITfCategoryMgr> TSFUtils::sCategoryMgr;
StaticRefPtr<ITfCompartment> TSFUtils::sCompartmentForOpenClose;
StaticRefPtr<ITfDocumentMgr> TSFUtils::sDisabledDocumentMgr;
StaticRefPtr<ITfContext> TSFUtils::sDisabledContext;
StaticRefPtr<ITfInputProcessorProfiles> TSFUtils::sInputProcessorProfiles;
StaticRefPtr<TSFTextStore> TSFUtils::sActiveTextStore;
StaticRefPtr<TSFTextStoreBase> TSFUtils::sCurrentTextStore;
DWORD TSFUtils::sClientId = 0;
template void TSFUtils::ClearStoringTextStoresIf(
const RefPtr<TSFTextStoreBase>& aTextStore);
template void TSFUtils::ClearStoringTextStoresIf(
const RefPtr<TSFTextStore>& aTextStore);
template void TSFUtils::ClearStoringTextStoresIf(
const RefPtr<TSFEmptyTextStore>& aTextStore);
void TSFUtils::Initialize() {
MOZ_LOG(gIMELog, LogLevel::Info, ("TSFUtils::Initialize() is called..."));
@@ -574,48 +577,10 @@ void TSFUtils::Initialize() {
return;
}
RefPtr<ITfDocumentMgr> 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<ITfContext> 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<ITfDocumentMgr> disabledDocumentMgr =
sDisabledDocumentMgr.forget()) {
MOZ_ASSERT(!sDisabledDocumentMgr);
disabledDocumentMgr->Pop(TF_POPF_ALL);
sDisabledContext = nullptr;
if (RefPtr<TSFTextStoreBase> textStore = sCurrentTextStore.forget()) {
textStore->Destroy();
MOZ_ASSERT(!sCurrentTextStore);
}
sCompartmentForOpenClose = nullptr;
sInputProcessorProfiles = nullptr;
@@ -647,13 +610,24 @@ void TSFUtils::Shutdown() {
template <typename TSFTextStoreClass>
void TSFUtils::ClearStoringTextStoresIf(
const RefPtr<TSFTextStoreClass>& aTextStore) {
if (sCurrentTextStore.get() ==
static_cast<TSFTextStoreBase*>(aTextStore.get())) {
MOZ_ASSERT(sActiveTextStore == sCurrentTextStore);
sActiveTextStore = nullptr;
sCurrentTextStore = nullptr;
return;
}
if (static_cast<TSFTextStoreBase*>(sActiveTextStore.get()) ==
static_cast<TSFTextStoreBase*>(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<bool>(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<TSFTextStore> oldTextStore = sActiveTextStore.forget();
const RefPtr<TSFTextStoreBase> oldTextStore = sCurrentTextStore.forget();
sActiveTextStore = nullptr;
// If currently oldTextStore still has focus, notifies TSF of losing focus.
if (hasFocus) {
if (oldTextStore && oldTextStore->MaybeHasFocus()) {
const RefPtr<ITfThreadMgr> threadMgr(sThreadMgr);
// 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<ITfDocumentMgr> focusedDocumentMgr;
threadMgr->GetFocus(getter_AddRefs(focusedDocumentMgr));
if (focusedDocumentMgr) {
RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
DebugOnly<HRESULT> 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");
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 this is a notification of blur, move focus to the dummy document
// manager.
if (aGotFocus == GotFocus::No || !aContext.mIMEState.IsEditable()) {
const RefPtr<ITfThreadMgr> threadMgr(sThreadMgr);
const RefPtr<ITfDocumentMgr> 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"));
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 empty text store.
if (aGotFocus == GotFocus::No || !aContext.mIMEState.IsEditable()) {
if (aFocusedWindow->Destroyed()) {
return NS_OK;
}
Result<RefPtr<TSFEmptyTextStore>, 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;
}

View File

@@ -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 <typename TSFTextStoreClass>
static void ClearStoringTextStoresIf(
const RefPtr<TSFTextStoreClass>& 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<ITfInputProcessorProfiles> sInputProcessorProfiles;
// For IME (keyboard) disabled state:
static StaticRefPtr<ITfDocumentMgr> sDisabledDocumentMgr;
static StaticRefPtr<ITfContext> 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<TSFTextStore> sActiveTextStore;
// Current text store which may be an empty one for disabled state.
static StaticRefPtr<TSFTextStoreBase> sCurrentTextStore;
// TSF client ID for the current application
static DWORD sClientId;
};

View File

@@ -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();
}
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));

View File

@@ -106,6 +106,7 @@ UNIFIED_SOURCES += [
"TaskbarPreviewButton.cpp",
"TaskbarTabPreview.cpp",
"TaskbarWindowPreview.cpp",
"TSFEmptyTextStore.cpp",
"TSFInputScope.cpp",
"TSFStaticSink.cpp",
"TSFTextStore.cpp",

View File

@@ -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;