Although I'm not sure why this breaks the legacy MS-IME for Japanese. When `InputContext` is set, we may need to emulate focus to recreate `TSFTextStoreBase` instance. This is not required if the editable state is not changed, but the check was removed by D242682. This patch restores the check. However, instead, making it sure to update the document URL and private browsing mode for the privacy. Additionally, I found another difference. D242682 made `TSFEmptyTextStore` starts associating its document manager with the active window. However, this is not required and that may cause making TSF calling `SetFocus()` of the document manager even when editable `TSFTextStore` already got focus. Therefore, this removes the unnecessary new thing too. Therefore, `TSFUtils::OnFocusChange` skips to disassociate document manager from a window handle. Differential Revision: https://phabricator.services.mozilla.com/D245348
260 lines
10 KiB
C++
260 lines
10 KiB
C++
/* -*- 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 TSFTextStoreBase_h
|
|
#define TSFTextStoreBase_h
|
|
|
|
#include "nsIWidget.h"
|
|
#include "nsWindow.h"
|
|
#include "TSFUtils.h" // for inputscope.h with the hack for MinGW
|
|
#include "WinUtils.h"
|
|
#include "WritingModes.h"
|
|
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/TextEventDispatcher.h"
|
|
#include "mozilla/WritingModes.h"
|
|
#include "mozilla/widget/IMEData.h"
|
|
|
|
#include <msctf.h>
|
|
#include <textstor.h>
|
|
|
|
struct ITfDocumentMgr;
|
|
class nsWindow;
|
|
|
|
namespace mozilla::widget {
|
|
|
|
class TSFTextStoreBase : public ITextStoreACP {
|
|
protected:
|
|
using SelectionChangeDataBase = IMENotification::SelectionChangeDataBase;
|
|
using SelectionChangeData = IMENotification::SelectionChangeData;
|
|
using TextChangeDataBase = IMENotification::TextChangeDataBase;
|
|
using TextChangeData = IMENotification::TextChangeData;
|
|
|
|
public: /*IUnknown*/
|
|
STDMETHODIMP QueryInterface(REFIID, void**);
|
|
|
|
NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFTextStoreBase)
|
|
|
|
public: /*ITextStoreACP*/
|
|
STDMETHODIMP AdviseSink(REFIID, IUnknown*, DWORD);
|
|
STDMETHODIMP UnadviseSink(IUnknown*);
|
|
STDMETHODIMP RequestLock(DWORD, HRESULT*);
|
|
STDMETHODIMP GetStatus(TS_STATUS*);
|
|
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 GetFormattedText(LONG, LONG, IDataObject**);
|
|
STDMETHODIMP GetEmbedded(LONG, REFGUID, REFIID, IUnknown**);
|
|
STDMETHODIMP QueryInsertEmbedded(const GUID*, const FORMATETC*, BOOL*);
|
|
STDMETHODIMP InsertEmbedded(DWORD, LONG, LONG, IDataObject*, TS_TEXTCHANGE*);
|
|
STDMETHODIMP RequestAttrsTransitioningAtPosition(LONG, ULONG,
|
|
const TS_ATTRID*, DWORD);
|
|
STDMETHODIMP FindNextAttrTransition(LONG, LONG, ULONG, const TS_ATTRID*,
|
|
DWORD, LONG*, BOOL*, LONG*);
|
|
STDMETHODIMP GetEndACP(LONG*);
|
|
STDMETHODIMP GetActiveView(TsViewCookie*);
|
|
STDMETHODIMP GetACPFromPoint(TsViewCookie, const POINT*, DWORD, LONG*);
|
|
STDMETHODIMP GetTextExt(TsViewCookie, LONG, LONG, RECT*, BOOL*);
|
|
STDMETHODIMP GetScreenExt(TsViewCookie, RECT*);
|
|
STDMETHODIMP GetWnd(TsViewCookie, HWND*);
|
|
STDMETHODIMP InsertTextAtSelection(DWORD, const WCHAR*, ULONG, LONG*, LONG*,
|
|
TS_TEXTCHANGE*);
|
|
STDMETHODIMP InsertEmbeddedAtSelection(DWORD, IDataObject*, LONG*, LONG*,
|
|
TS_TEXTCHANGE*);
|
|
|
|
public:
|
|
[[nodiscard]] bool MaybeHasFocus() const { return mContext; }
|
|
|
|
/**
|
|
* Return true if this is for editable content, i.e., supporting retrieving
|
|
* and modifying the content. I.e., when this is a TSFTextStore instance,
|
|
* returns true. Otherwise, this is a TSFEmptyTextStoreInstance, returns
|
|
* false.
|
|
*/
|
|
[[nodiscard]] bool IsEditable() const {
|
|
return static_cast<bool>(mIsEditable);
|
|
}
|
|
|
|
[[nodiscard]] ITfDocumentMgr* GetDocumentMgr() const { return mDocumentMgr; }
|
|
[[nodiscard]] ITfContext* GetContext() const { return mContext; }
|
|
[[nodiscard]] nsWindow* GetWindow() const { return mWidget; }
|
|
|
|
[[nodiscard]] virtual IMENotificationRequests GetIMENotificationRequests()
|
|
const = 0;
|
|
|
|
virtual void Destroy() = 0;
|
|
|
|
static void SetInputContext(nsWindow* aWindow, const InputContext& aContext,
|
|
const InputContextAction& aAction);
|
|
|
|
protected:
|
|
enum class Editable : bool { No, Yes };
|
|
friend inline std::ostream& operator<<(std::ostream& aStream,
|
|
const Editable& aEditable) {
|
|
return aStream << (static_cast<bool>(aEditable) ? "Editable::Yes"
|
|
: "Editable::No");
|
|
};
|
|
explicit TSFTextStoreBase(Editable aIsEditable) : mIsEditable(aIsEditable) {}
|
|
virtual ~TSFTextStoreBase() = default;
|
|
|
|
[[nodiscard]] bool InitBase(nsWindow* aWidget, const InputContext& aContext);
|
|
|
|
[[nodiscard]] static bool IsReadLock(DWORD aLock) {
|
|
return (TS_LF_READ == (aLock & TS_LF_READ));
|
|
}
|
|
[[nodiscard]] static bool IsReadWriteLock(DWORD aLock) {
|
|
return (TS_LF_READWRITE == (aLock & TS_LF_READWRITE));
|
|
}
|
|
[[nodiscard]] bool IsReadLocked() const { return IsReadLock(mLock); }
|
|
[[nodiscard]] bool IsReadWriteLocked() const {
|
|
return IsReadWriteLock(mLock);
|
|
}
|
|
|
|
// This is called immediately after a call of OnLockGranted() of mSink.
|
|
// Note that mLock isn't cleared yet when this is called.
|
|
virtual void DidLockGranted() {}
|
|
|
|
using AttrIndices = EnumSet<TSFUtils::AttrIndex>;
|
|
constexpr static auto NothingChanged = AttrIndices{};
|
|
constexpr static auto OnlyURLChanged =
|
|
AttrIndices{TSFUtils::AttrIndex::DocumentURL};
|
|
constexpr static auto OnlyInputScopeChanged =
|
|
AttrIndices{TSFUtils::AttrIndex::InputScope};
|
|
constexpr static auto URLAndInputScopeChanged = AttrIndices{
|
|
TSFUtils::AttrIndex::DocumentURL, TSFUtils::AttrIndex::InputScope};
|
|
/**
|
|
* Called when either the URL or the input scope is changed.
|
|
*/
|
|
void NotifyTSFOfInputContextChange(AttrIndices aAttrIndices);
|
|
|
|
[[nodiscard]] bool GetScreenExtInternal(RECT& aScreenExt);
|
|
|
|
[[nodiscard]] virtual Maybe<WritingMode> GetWritingMode() {
|
|
return Nothing();
|
|
}
|
|
|
|
// DispatchEvent() dispatches the event and if it may not be handled
|
|
// synchronously, this makes the instance not notify TSF of pending
|
|
// notifications until next notification from content.
|
|
void DispatchEvent(WidgetGUIEvent& aEvent);
|
|
|
|
void SetInputScope(const nsString& aHTMLInputType,
|
|
const nsString& aHTMLInputMode);
|
|
|
|
/**
|
|
* Return URL which can be exposed to TSF. Use ::SysFreeString() to delete
|
|
* the result if you use it by yourself.
|
|
*/
|
|
BSTR GetExposingURL() const;
|
|
|
|
/**
|
|
* Debug utility method to print the result of GetExposingURL().
|
|
*/
|
|
void PrintExposingURL(const char* aPrefix) const;
|
|
|
|
HRESULT HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
|
|
const TS_ATTRID* aFilterAttrs,
|
|
int32_t aNumOfSupportedAttrs);
|
|
HRESULT RetrieveRequestedAttrsInternal(ULONG ulCount, TS_ATTRVAL* paAttrVals,
|
|
ULONG* pcFetched,
|
|
int32_t aNumOfSupportedAttrs);
|
|
|
|
/**
|
|
* IsHandlingCompositionInParent() returns true if eCompositionStart is
|
|
* dispatched, but eCompositionCommit(AsIs) is not dispatched. This means
|
|
* that if composition is handled in a content process, this status indicates
|
|
* whether ContentCacheInParent has composition or not. On the other hand,
|
|
* if it's handled in the chrome process, this is exactly same as
|
|
* IsHandlingCompositionInContent().
|
|
*/
|
|
[[nodiscard]] bool IsHandlingCompositionInParent() const {
|
|
return mDispatcher && mDispatcher->IsComposing();
|
|
}
|
|
|
|
/**
|
|
* IsHandlingCompositionInContent() returns true if there is a composition in
|
|
* the focused editor which may be in a content process.
|
|
*/
|
|
[[nodiscard]] bool IsHandlingCompositionInContent() const {
|
|
return mDispatcher && mDispatcher->IsHandlingComposition();
|
|
}
|
|
|
|
/**
|
|
* Return error if editable state does not match with this instance.
|
|
* Otherwise, updates mDocumentURL and mInPrivateBrowsing and notify TSF of
|
|
* the changes.
|
|
*/
|
|
nsresult UpdateDocumentURLAndBrowsingMode(nsWindow* aWindow,
|
|
const InputContext& aContext);
|
|
|
|
// Holds the pointer to our current win32 widget
|
|
RefPtr<nsWindow> mWidget;
|
|
// mDispatcher is a helper class to dispatch composition events.
|
|
RefPtr<TextEventDispatcher> mDispatcher;
|
|
// Document manager for the currently focused editor
|
|
RefPtr<ITfDocumentMgr> mDocumentMgr;
|
|
// Edit cookie associated with the current editing context
|
|
DWORD mEditCookie = 0;
|
|
// Editing context at the bottom of mDocumentMgr's context stack
|
|
RefPtr<ITfContext> mContext;
|
|
// Currently installed notification sink
|
|
RefPtr<ITextStoreACPSink> mSink;
|
|
// TS_AS_* mask of what events to notify
|
|
DWORD mSinkMask = 0;
|
|
// 0 if not locked, otherwise TS_LF_* indicating the current lock
|
|
DWORD mLock = 0;
|
|
// 0 if no lock is queued, otherwise TS_LF_* indicating the queue lock
|
|
DWORD mLockQueued = 0;
|
|
|
|
// The input scopes for this context, defaults to IS_DEFAULT.
|
|
nsTArray<InputScope> mInputScopes;
|
|
|
|
// The URL cache of the focused document.
|
|
nsString mDocumentURL;
|
|
|
|
bool mRequestedAttrs[TSFUtils::NUM_OF_SUPPORTED_ATTRS] = {false};
|
|
|
|
bool mRequestedAttrValues = false;
|
|
|
|
// Before calling ITextStoreACPSink::OnLayoutChange() and
|
|
// ITfContextOwnerServices::OnLayoutChange(), mWaitingQueryLayout is set to
|
|
// true. This is set to false when GetTextExt() or GetACPFromPoint() is
|
|
// called.
|
|
bool mWaitingQueryLayout = false;
|
|
// During the document is locked, we shouldn't destroy the instance.
|
|
// If this is true, the instance will be destroyed after unlocked.
|
|
bool mPendingDestroy = false;
|
|
// While the instance is initializing content/selection cache, another
|
|
// initialization shouldn't run recursively. Therefore, while the
|
|
// initialization is running, this is set to true. Use AutoNotifyingTSFBatch
|
|
// to set this.
|
|
bool mDeferNotifyingTSF = false;
|
|
// While the instance is dispatching events, the event may not be handled
|
|
// synchronously when remote content has focus. In the case, we cannot
|
|
// return the latest layout/content information to TSF/TIP until we get next
|
|
// update notification from ContentCacheInParent. For preventing TSF/TIP
|
|
// retrieves the latest content/layout information while it becomes available,
|
|
// we should put off notifying TSF of any updates.
|
|
bool mDeferNotifyingTSFUntilNextUpdate = false;
|
|
// Immediately after a call of Destroy(), mDestroyed becomes true. If this
|
|
// is true, the instance shouldn't grant any requests from the TIP anymore.
|
|
bool mDestroyed = false;
|
|
// While the instance is being destroyed, this is set to true for avoiding
|
|
// recursive Destroy() calls.
|
|
bool mBeingDestroyed = false;
|
|
// Whether we're in the private browsing mode.
|
|
bool mInPrivateBrowsing = true;
|
|
// Whether this is for editable content or not.
|
|
Editable mIsEditable;
|
|
};
|
|
|
|
} // namespace mozilla::widget
|
|
|
|
#endif // #ifndef TSFTextStoreBase_h
|