mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-11 16:32:59 +00:00
e7b7bb65d1
I don't know understand how to recursively run initializing `mContentForTSF` with dispatching a query content event because it may flush pending layout, but the notifications will be sent in next tick by `IMEContentObserver`. However, anyway, it rarely occurs in some environments. Therefore, this patch makes `TSFTextStore` defer notifying TSF until finishing initializing the content/selection cache to avoid to emplace `Maybe` twice. Additionally, with adding a call of `.reset()` and `MOZ_DIAGNOSTIC_ASSERT`, this patch prevents the crash in the release channel. Depends on D159660 Differential Revision: https://phabricator.services.mozilla.com/D159661
1158 lines
44 KiB
C++
1158 lines
44 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 TSFTextStore_h_
|
|
#define TSFTextStore_h_
|
|
|
|
#include "nsCOMPtr.h"
|
|
#include "nsIWidget.h"
|
|
#include "nsString.h"
|
|
#include "nsWindow.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/WindowsVersion.h"
|
|
#include "mozilla/widget/IMEData.h"
|
|
|
|
#include <msctf.h>
|
|
#include <textstor.h>
|
|
|
|
// GUID_PROP_INPUTSCOPE is declared in inputscope.h using INIT_GUID.
|
|
// With initguid.h, we get its instance instead of extern declaration.
|
|
#ifdef INPUTSCOPE_INIT_GUID
|
|
# include <initguid.h>
|
|
#endif
|
|
#ifdef TEXTATTRS_INIT_GUID
|
|
# include <tsattrs.h>
|
|
#endif
|
|
#include <inputscope.h>
|
|
|
|
// TSF InputScope, for earlier SDK 8
|
|
#define IS_SEARCH static_cast<InputScope>(50)
|
|
|
|
struct ITfThreadMgr;
|
|
struct ITfDocumentMgr;
|
|
struct ITfDisplayAttributeMgr;
|
|
struct ITfCategoryMgr;
|
|
class nsWindow;
|
|
|
|
inline std::ostream& operator<<(std::ostream& aStream,
|
|
const TS_SELECTIONSTYLE& aSelectionStyle) {
|
|
const char* ase = "Unknown";
|
|
switch (aSelectionStyle.ase) {
|
|
case TS_AE_START:
|
|
ase = "TS_AE_START";
|
|
break;
|
|
case TS_AE_END:
|
|
ase = "TS_AE_END";
|
|
break;
|
|
case TS_AE_NONE:
|
|
ase = "TS_AE_NONE";
|
|
break;
|
|
}
|
|
aStream << "{ ase=" << ase << ", fInterimChar="
|
|
<< (aSelectionStyle.fInterimChar ? "TRUE" : "FALSE") << " }";
|
|
return aStream;
|
|
}
|
|
|
|
inline std::ostream& operator<<(std::ostream& aStream,
|
|
const TS_SELECTION_ACP& aACP) {
|
|
aStream << "{ acpStart=" << aACP.acpStart << ", acpEnd=" << aACP.acpEnd
|
|
<< ", style=" << mozilla::ToString(aACP.style).c_str() << " }";
|
|
return aStream;
|
|
}
|
|
|
|
namespace mozilla {
|
|
namespace widget {
|
|
|
|
class TSFStaticSink;
|
|
struct MSGResult;
|
|
|
|
/*
|
|
* Text Services Framework text store
|
|
*/
|
|
|
|
class TSFTextStore final : public ITextStoreACP,
|
|
public ITfContextOwnerCompositionSink,
|
|
public ITfMouseTrackerACP {
|
|
friend class TSFStaticSink;
|
|
|
|
private:
|
|
typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase;
|
|
typedef IMENotification::SelectionChangeData SelectionChangeData;
|
|
typedef IMENotification::TextChangeDataBase TextChangeDataBase;
|
|
typedef IMENotification::TextChangeData TextChangeData;
|
|
|
|
public: /*IUnknown*/
|
|
STDMETHODIMP QueryInterface(REFIID, void**);
|
|
|
|
NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFTextStore)
|
|
|
|
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 RequestSupportedAttrs(DWORD, ULONG, const TS_ATTRID*);
|
|
STDMETHODIMP RequestAttrsAtPosition(LONG, ULONG, const TS_ATTRID*, DWORD);
|
|
STDMETHODIMP RequestAttrsTransitioningAtPosition(LONG, ULONG,
|
|
const TS_ATTRID*, DWORD);
|
|
STDMETHODIMP FindNextAttrTransition(LONG, LONG, ULONG, const TS_ATTRID*,
|
|
DWORD, LONG*, BOOL*, LONG*);
|
|
STDMETHODIMP RetrieveRequestedAttrs(ULONG, TS_ATTRVAL*, ULONG*);
|
|
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: /*ITfContextOwnerCompositionSink*/
|
|
STDMETHODIMP OnStartComposition(ITfCompositionView*, BOOL*);
|
|
STDMETHODIMP OnUpdateComposition(ITfCompositionView*, ITfRange*);
|
|
STDMETHODIMP OnEndComposition(ITfCompositionView*);
|
|
|
|
public: /*ITfMouseTrackerACP*/
|
|
STDMETHODIMP AdviseMouseSink(ITfRangeACP*, ITfMouseSink*, DWORD*);
|
|
STDMETHODIMP UnadviseMouseSink(DWORD);
|
|
|
|
public:
|
|
static void Initialize(void);
|
|
static void Terminate(void);
|
|
|
|
static bool ProcessRawKeyMessage(const MSG& aMsg);
|
|
static void ProcessMessage(nsWindow* aWindow, UINT aMessage, WPARAM& aWParam,
|
|
LPARAM& aLParam, MSGResult& aResult);
|
|
|
|
static void SetIMEOpenState(bool);
|
|
static bool GetIMEOpenState(void);
|
|
|
|
static void CommitComposition(bool aDiscard) {
|
|
NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
|
|
if (!sEnabledTextStore) {
|
|
return;
|
|
}
|
|
RefPtr<TSFTextStore> textStore(sEnabledTextStore);
|
|
textStore->CommitCompositionInternal(aDiscard);
|
|
}
|
|
|
|
static void SetInputContext(nsWindow* aWidget, const InputContext& aContext,
|
|
const InputContextAction& aAction);
|
|
|
|
static nsresult OnFocusChange(bool aGotFocus, nsWindow* aFocusedWidget,
|
|
const InputContext& aContext);
|
|
static nsresult OnTextChange(const IMENotification& aIMENotification) {
|
|
NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
|
|
if (!sEnabledTextStore) {
|
|
return NS_OK;
|
|
}
|
|
RefPtr<TSFTextStore> textStore(sEnabledTextStore);
|
|
return textStore->OnTextChangeInternal(aIMENotification);
|
|
}
|
|
|
|
static nsresult OnSelectionChange(const IMENotification& aIMENotification) {
|
|
NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
|
|
if (!sEnabledTextStore) {
|
|
return NS_OK;
|
|
}
|
|
RefPtr<TSFTextStore> textStore(sEnabledTextStore);
|
|
return textStore->OnSelectionChangeInternal(aIMENotification);
|
|
}
|
|
|
|
static nsresult OnLayoutChange() {
|
|
NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
|
|
if (!sEnabledTextStore) {
|
|
return NS_OK;
|
|
}
|
|
RefPtr<TSFTextStore> textStore(sEnabledTextStore);
|
|
return textStore->OnLayoutChangeInternal();
|
|
}
|
|
|
|
static nsresult OnUpdateComposition() {
|
|
NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
|
|
if (!sEnabledTextStore) {
|
|
return NS_OK;
|
|
}
|
|
RefPtr<TSFTextStore> textStore(sEnabledTextStore);
|
|
return textStore->OnUpdateCompositionInternal();
|
|
}
|
|
|
|
static nsresult OnMouseButtonEvent(const IMENotification& aIMENotification) {
|
|
NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
|
|
if (!sEnabledTextStore) {
|
|
return NS_OK;
|
|
}
|
|
RefPtr<TSFTextStore> textStore(sEnabledTextStore);
|
|
return textStore->OnMouseButtonEventInternal(aIMENotification);
|
|
}
|
|
|
|
static IMENotificationRequests GetIMENotificationRequests();
|
|
|
|
// Returns the address of the pointer so that the TSF automatic test can
|
|
// replace the system object with a custom implementation for testing.
|
|
// XXX TSF doesn't work now. Should we remove it?
|
|
static void* GetNativeData(uint32_t aDataType) {
|
|
switch (aDataType) {
|
|
case NS_NATIVE_TSF_THREAD_MGR:
|
|
Initialize(); // Apply any previous changes
|
|
return static_cast<void*>(&sThreadMgr);
|
|
case NS_NATIVE_TSF_CATEGORY_MGR:
|
|
return static_cast<void*>(&sCategoryMgr);
|
|
case NS_NATIVE_TSF_DISPLAY_ATTR_MGR:
|
|
return static_cast<void*>(&sDisplayAttrMgr);
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
static void* GetThreadManager() { return static_cast<void*>(sThreadMgr); }
|
|
|
|
static bool ThinksHavingFocus() {
|
|
return (sEnabledTextStore && sEnabledTextStore->mContext);
|
|
}
|
|
|
|
static bool IsInTSFMode() { return sThreadMgr != nullptr; }
|
|
|
|
static bool IsComposing() {
|
|
return (sEnabledTextStore && sEnabledTextStore->mComposition.isSome());
|
|
}
|
|
|
|
static bool IsComposingOn(nsWindow* aWidget) {
|
|
return (IsComposing() && sEnabledTextStore->mWidget == aWidget);
|
|
}
|
|
|
|
static nsWindow* GetEnabledWindowBase() {
|
|
return sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr;
|
|
}
|
|
|
|
/**
|
|
* Returns true if active keyboard layout is a legacy IMM-IME.
|
|
*/
|
|
static bool IsIMM_IMEActive();
|
|
|
|
/**
|
|
* Returns true if active TIP is MS-IME for Japanese.
|
|
*/
|
|
static bool IsMSJapaneseIMEActive();
|
|
|
|
/**
|
|
* Returns true if active TIP is Google Japanese Input.
|
|
* Note that if Google Japanese Input is installed as an IMM-IME,
|
|
* this return false even if Google Japanese Input is active.
|
|
* So, you may need to check IMMHandler::IsGoogleJapaneseInputActive() too.
|
|
*/
|
|
static bool IsGoogleJapaneseInputActive();
|
|
|
|
/**
|
|
* Returns true if active TIP is ATOK.
|
|
*/
|
|
static bool IsATOKActive();
|
|
|
|
/**
|
|
* Returns true if active TIP or IME is a black listed one and we should
|
|
* set input scope of URL bar to IS_DEFAULT rather than IS_URL.
|
|
*/
|
|
static bool ShouldSetInputScopeOfURLBarToDefault();
|
|
|
|
/**
|
|
* Returns true if TSF may crash if GetSelection() returns E_FAIL.
|
|
*/
|
|
static bool DoNotReturnErrorFromGetSelection();
|
|
|
|
#ifdef DEBUG
|
|
// Returns true when keyboard layout has IME (TIP).
|
|
static bool CurrentKeyboardLayoutHasIME();
|
|
#endif // #ifdef DEBUG
|
|
|
|
protected:
|
|
TSFTextStore();
|
|
~TSFTextStore();
|
|
|
|
static bool CreateAndSetFocus(nsWindow* aFocusedWidget,
|
|
const InputContext& aContext);
|
|
static void EnsureToDestroyAndReleaseEnabledTextStoreIf(
|
|
RefPtr<TSFTextStore>& aTextStore);
|
|
static void MarkContextAsKeyboardDisabled(ITfContext* aContext);
|
|
static void MarkContextAsEmpty(ITfContext* aContext);
|
|
|
|
bool Init(nsWindow* aWidget, const InputContext& aContext);
|
|
void Destroy();
|
|
void ReleaseTSFObjects();
|
|
|
|
bool IsReadLock(DWORD aLock) const {
|
|
return (TS_LF_READ == (aLock & TS_LF_READ));
|
|
}
|
|
bool IsReadWriteLock(DWORD aLock) const {
|
|
return (TS_LF_READWRITE == (aLock & TS_LF_READWRITE));
|
|
}
|
|
bool IsReadLocked() const { return IsReadLock(mLock); }
|
|
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.
|
|
void DidLockGranted();
|
|
|
|
bool GetScreenExtInternal(RECT& aScreenExt);
|
|
// If aDispatchCompositionChangeEvent is true, this method will dispatch
|
|
// compositionchange event if this is called during IME composing.
|
|
// aDispatchCompositionChangeEvent should be true only when this is called
|
|
// from SetSelection. Because otherwise, the compositionchange event should
|
|
// not be sent from here.
|
|
HRESULT SetSelectionInternal(const TS_SELECTION_ACP*,
|
|
bool aDispatchCompositionChangeEvent = false);
|
|
bool InsertTextAtSelectionInternal(const nsAString& aInsertStr,
|
|
TS_TEXTCHANGE* aTextChange);
|
|
void CommitCompositionInternal(bool);
|
|
HRESULT GetDisplayAttribute(ITfProperty* aProperty, ITfRange* aRange,
|
|
TF_DISPLAYATTRIBUTE* aResult);
|
|
HRESULT RestartCompositionIfNecessary(ITfRange* pRangeNew = nullptr);
|
|
class Composition;
|
|
HRESULT RestartComposition(Composition& aCurrentComposition,
|
|
ITfCompositionView* aCompositionView,
|
|
ITfRange* aNewRange);
|
|
|
|
// Following methods record composing action(s) to mPendingActions.
|
|
// They will be flushed FlushPendingActions().
|
|
HRESULT RecordCompositionStartAction(ITfCompositionView* aCompositionView,
|
|
ITfRange* aRange,
|
|
bool aPreserveSelection);
|
|
HRESULT RecordCompositionStartAction(ITfCompositionView* aCompositionView,
|
|
LONG aStart, LONG aLength,
|
|
bool aPreserveSelection);
|
|
HRESULT RecordCompositionUpdateAction();
|
|
HRESULT RecordCompositionEndAction();
|
|
|
|
// 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 OnLayoutInformationAvaliable();
|
|
|
|
// FlushPendingActions() performs pending actions recorded in mPendingActions
|
|
// and clear it.
|
|
void FlushPendingActions();
|
|
// MaybeFlushPendingNotifications() performs pending notifications to TSF.
|
|
void MaybeFlushPendingNotifications();
|
|
|
|
nsresult OnTextChangeInternal(const IMENotification& aIMENotification);
|
|
nsresult OnSelectionChangeInternal(const IMENotification& aIMENotification);
|
|
nsresult OnMouseButtonEventInternal(const IMENotification& aIMENotification);
|
|
nsresult OnLayoutChangeInternal();
|
|
nsresult OnUpdateCompositionInternal();
|
|
|
|
// mPendingSelectionChangeData stores selection change data until notifying
|
|
// TSF of selection change. If two or more selection changes occur, this
|
|
// stores the latest selection change data because only it is necessary.
|
|
Maybe<SelectionChangeData> mPendingSelectionChangeData;
|
|
|
|
// mPendingTextChangeData stores one or more text change data until notifying
|
|
// TSF of text change. If two or more text changes occur, this merges
|
|
// every text change data.
|
|
TextChangeData mPendingTextChangeData;
|
|
|
|
void NotifyTSFOfTextChange();
|
|
void NotifyTSFOfSelectionChange();
|
|
bool NotifyTSFOfLayoutChange();
|
|
void NotifyTSFOfLayoutChangeAgain();
|
|
|
|
HRESULT HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
|
|
const TS_ATTRID* aFilterAttrs);
|
|
void SetInputScope(const nsString& aHTMLInputType,
|
|
const nsString& aHTMLInputMode);
|
|
|
|
// Creates native caret over our caret. This method only works on desktop
|
|
// application. Otherwise, this does nothing.
|
|
void CreateNativeCaret();
|
|
// Destroys native caret if there is.
|
|
void MaybeDestroyNativeCaret();
|
|
|
|
/**
|
|
* MaybeHackNoErrorLayoutBugs() is a helper method of GetTextExt(). In
|
|
* strictly speaking, TSF is aware of asynchronous layout computation like us.
|
|
* However, Windows 10 version 1803 and older (including Windows 8.1 and
|
|
* older) Windows has a bug which is that the caller of GetTextExt() of TSF
|
|
* does not return TS_E_NOLAYOUT to TIP as is. Additionally, even after
|
|
* fixing this bug, some TIPs are not work well when we return TS_E_NOLAYOUT.
|
|
* For avoiding this issue, this method checks current Windows version and
|
|
* active TIP, and if in case we cannot return TS_E_NOLAYOUT, this modifies
|
|
* aACPStart and aACPEnd to making sure that they are in range of unmodified
|
|
* characters.
|
|
*
|
|
* @param aACPStart Initial value should be acpStart of GetTextExt().
|
|
* If this method returns true, this may be modified
|
|
* to be in range of unmodified characters.
|
|
* @param aACPEnd Initial value should be acpEnd of GetTextExt().
|
|
* If this method returns true, this may be modified
|
|
* to be in range of unmodified characters.
|
|
* And also this may become same as aACPStart.
|
|
* @return true if the caller shouldn't return TS_E_NOLAYOUT.
|
|
* In this case, this method modifies aACPStart and/or
|
|
* aASCPEnd to compute rectangle of unmodified characters.
|
|
* false if the caller can return TS_E_NOLAYOUT or
|
|
* we cannot have proper unmodified characters.
|
|
*/
|
|
bool MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd);
|
|
|
|
// 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;
|
|
// 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 if not locked, otherwise TS_LF_* indicating the current lock
|
|
DWORD mLock;
|
|
// 0 if no lock is queued, otherwise TS_LF_* indicating the queue lock
|
|
DWORD mLockQueued;
|
|
|
|
uint32_t mHandlingKeyMessage;
|
|
void OnStartToHandleKeyMessage() {
|
|
// If we're starting to handle another key message during handling a
|
|
// key message, let's assume that the handling key message is handled by
|
|
// TIP and it sends another key message for hacking something.
|
|
// Let's try to dispatch a keyboard event now.
|
|
// FYI: All callers of this method grab this instance with local variable.
|
|
// So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(),
|
|
// we're safe to access any members.
|
|
if (!mDestroyed && sHandlingKeyMsg && !sIsKeyboardEventDispatched) {
|
|
MaybeDispatchKeyboardEventAsProcessedByIME();
|
|
}
|
|
++mHandlingKeyMessage;
|
|
}
|
|
void OnEndHandlingKeyMessage(bool aIsProcessedByTSF) {
|
|
// If sHandlingKeyMsg has been handled by TSF or TIP and we're still
|
|
// alive, but we haven't dispatch keyboard event for it, let's fire it now.
|
|
// FYI: All callers of this method grab this instance with local variable.
|
|
// So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(),
|
|
// we're safe to access any members.
|
|
if (!mDestroyed && sHandlingKeyMsg && aIsProcessedByTSF &&
|
|
!sIsKeyboardEventDispatched) {
|
|
MaybeDispatchKeyboardEventAsProcessedByIME();
|
|
}
|
|
MOZ_ASSERT(mHandlingKeyMessage);
|
|
if (--mHandlingKeyMessage) {
|
|
return;
|
|
}
|
|
// If TSFTextStore instance is destroyed during handling key message(s),
|
|
// release all TSF objects when all nested key messages have been handled.
|
|
if (mDestroyed) {
|
|
ReleaseTSFObjects();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* MaybeDispatchKeyboardEventAsProcessedByIME() tries to dispatch eKeyDown
|
|
* event or eKeyUp event for sHandlingKeyMsg and marking the dispatching
|
|
* event as "processed by IME". Note that if the document is locked, this
|
|
* just adds a pending action into the queue and sets
|
|
* sIsKeyboardEventDispatched to true.
|
|
*/
|
|
void MaybeDispatchKeyboardEventAsProcessedByIME();
|
|
|
|
/**
|
|
* DispatchKeyboardEventAsProcessedByIME() dispatches an eKeyDown or
|
|
* eKeyUp event with NativeKey class and aMsg.
|
|
*/
|
|
void DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg);
|
|
|
|
// Composition class stores a copy of the active composition string. Only
|
|
// the data is updated during an InsertTextAtSelection call if we have a
|
|
// composition. The data acts as a buffer until OnUpdateComposition is
|
|
// called and the data is flushed to editor through eCompositionChange.
|
|
// This allows all changes to be updated in batches to avoid inconsistencies
|
|
// and artifacts.
|
|
class Composition final : public OffsetAndData<LONG> {
|
|
public:
|
|
explicit Composition(ITfCompositionView* aCompositionView,
|
|
LONG aCompositionStartOffset,
|
|
const nsAString& aCompositionString)
|
|
: OffsetAndData<LONG>(aCompositionStartOffset, aCompositionString),
|
|
mView(aCompositionView) {}
|
|
|
|
ITfCompositionView* GetView() const { return mView; }
|
|
|
|
friend std::ostream& operator<<(std::ostream& aStream,
|
|
const Composition& aComposition) {
|
|
aStream << "{ mView=0x" << aComposition.mView.get()
|
|
<< ", OffsetAndData<LONG>="
|
|
<< static_cast<const OffsetAndData<LONG>&>(aComposition) << " }";
|
|
return aStream;
|
|
}
|
|
|
|
private:
|
|
RefPtr<ITfCompositionView> const mView;
|
|
};
|
|
// While the document is locked, we cannot dispatch any events which cause
|
|
// DOM events since the DOM events' handlers may modify the locked document.
|
|
// However, even while the document is locked, TSF may queries us.
|
|
// For that, TSFTextStore modifies mComposition even while the document is
|
|
// locked. With mComposition, query methods can returns the text content
|
|
// information.
|
|
Maybe<Composition> mComposition;
|
|
|
|
/**
|
|
* 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().
|
|
*/
|
|
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.
|
|
*/
|
|
bool IsHandlingCompositionInContent() const {
|
|
return mDispatcher && mDispatcher->IsHandlingComposition();
|
|
}
|
|
|
|
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(); }
|
|
|
|
explicit Selection(const TS_SELECTION_ACP& aSelection) {
|
|
SetSelection(aSelection);
|
|
}
|
|
|
|
explicit Selection(uint32_t aOffsetToCollapse) {
|
|
Collapse(aOffsetToCollapse);
|
|
}
|
|
|
|
explicit Selection(const SelectionChangeDataBase& aSelectionChangeData) {
|
|
SetSelection(aSelectionChangeData);
|
|
}
|
|
|
|
explicit Selection(const WidgetQueryContentEvent& aQuerySelectionEvent) {
|
|
SetSelection(aQuerySelectionEvent);
|
|
}
|
|
|
|
Selection(uint32_t aStart, uint32_t aLength, bool aReversed,
|
|
const WritingMode& aWritingMode) {
|
|
SetSelection(aStart, aLength, aReversed, aWritingMode);
|
|
}
|
|
|
|
void SetSelection(const TS_SELECTION_ACP& aSelection) {
|
|
mACP = Some(aSelection);
|
|
// Selection end must be active in our editor.
|
|
if (mACP->style.ase != TS_AE_START) {
|
|
mACP->style.ase = TS_AE_END;
|
|
}
|
|
// We're not support interim char selection for now.
|
|
// XXX Probably, this is necessary for supporting South Asian languages.
|
|
mACP->style.fInterimChar = FALSE;
|
|
}
|
|
|
|
bool SetSelection(const SelectionChangeDataBase& aSelectionChangeData) {
|
|
MOZ_ASSERT(aSelectionChangeData.IsInitialized());
|
|
if (!aSelectionChangeData.HasRange()) {
|
|
if (mACP.isNothing()) {
|
|
return false;
|
|
}
|
|
mACP.reset();
|
|
// Let's keep the WritingMode because users don't want to change the UI
|
|
// of TIP temporarily since no selection case is created only by web
|
|
// apps, but they or TIP would restore selection at last point later.
|
|
return true;
|
|
}
|
|
return SetSelection(aSelectionChangeData.mOffset,
|
|
aSelectionChangeData.Length(),
|
|
aSelectionChangeData.mReversed,
|
|
aSelectionChangeData.GetWritingMode());
|
|
}
|
|
|
|
bool SetSelection(const WidgetQueryContentEvent& aQuerySelectionEvent) {
|
|
MOZ_ASSERT(aQuerySelectionEvent.mMessage == eQuerySelectedText);
|
|
MOZ_ASSERT(aQuerySelectionEvent.Succeeded());
|
|
if (aQuerySelectionEvent.DidNotFindSelection()) {
|
|
if (mACP.isNothing()) {
|
|
return false;
|
|
}
|
|
mACP.reset();
|
|
// Let's keep the WritingMode because users don't want to change the UI
|
|
// of TIP temporarily since no selection case is created only by web
|
|
// apps, but they or TIP would restore selection at last point later.
|
|
return true;
|
|
}
|
|
return SetSelection(aQuerySelectionEvent.mReply->StartOffset(),
|
|
aQuerySelectionEvent.mReply->DataLength(),
|
|
aQuerySelectionEvent.mReply->mReversed,
|
|
aQuerySelectionEvent.mReply->WritingModeRef());
|
|
}
|
|
|
|
bool SetSelection(uint32_t aStart, uint32_t aLength, bool aReversed,
|
|
const WritingMode& aWritingMode) {
|
|
const bool changed = mACP.isNothing() ||
|
|
mACP->acpStart != static_cast<LONG>(aStart) ||
|
|
mACP->acpEnd != static_cast<LONG>(aStart + aLength);
|
|
mACP = Some(
|
|
TS_SELECTION_ACP{.acpStart = static_cast<LONG>(aStart),
|
|
.acpEnd = static_cast<LONG>(aStart + aLength),
|
|
.style = {.ase = aReversed ? TS_AE_START : TS_AE_END,
|
|
.fInterimChar = FALSE}});
|
|
mWritingMode = aWritingMode;
|
|
|
|
return changed;
|
|
}
|
|
|
|
bool Collapsed() const {
|
|
return mACP.isNothing() || mACP->acpStart == mACP->acpEnd;
|
|
}
|
|
|
|
void Collapse(uint32_t aOffset) {
|
|
// XXX This does not update the selection's mWritingMode.
|
|
// If it is ever used to "collapse" to an entirely new location,
|
|
// we may need to fix that.
|
|
mACP = Some(
|
|
TS_SELECTION_ACP{.acpStart = static_cast<LONG>(aOffset),
|
|
.acpEnd = static_cast<LONG>(aOffset),
|
|
.style = {.ase = TS_AE_END, .fInterimChar = FALSE}});
|
|
}
|
|
|
|
LONG MinOffset() const {
|
|
MOZ_ASSERT(mACP.isSome());
|
|
LONG min = std::min(mACP->acpStart, mACP->acpEnd);
|
|
MOZ_ASSERT(min >= 0);
|
|
return min;
|
|
}
|
|
|
|
LONG MaxOffset() const {
|
|
MOZ_ASSERT(mACP.isSome());
|
|
LONG max = std::max(mACP->acpStart, mACP->acpEnd);
|
|
MOZ_ASSERT(max >= 0);
|
|
return max;
|
|
}
|
|
|
|
LONG StartOffset() const {
|
|
MOZ_ASSERT(mACP.isSome());
|
|
MOZ_ASSERT(mACP->acpStart >= 0);
|
|
return mACP->acpStart;
|
|
}
|
|
|
|
LONG EndOffset() const {
|
|
MOZ_ASSERT(mACP.isSome());
|
|
MOZ_ASSERT(mACP->acpEnd >= 0);
|
|
return mACP->acpEnd;
|
|
}
|
|
|
|
LONG Length() const {
|
|
MOZ_ASSERT(mACP->acpEnd >= mACP->acpStart);
|
|
return mACP.isSome() ? std::abs(mACP->acpEnd - mACP->acpStart) : 0;
|
|
}
|
|
|
|
bool IsReversed() const {
|
|
return mACP.isSome() && mACP->style.ase == TS_AE_START;
|
|
}
|
|
|
|
TsActiveSelEnd ActiveSelEnd() const {
|
|
return mACP.isSome() ? mACP->style.ase : TS_AE_NONE;
|
|
}
|
|
|
|
bool IsInterimChar() const {
|
|
return mACP.isSome() && mACP->style.fInterimChar != FALSE;
|
|
}
|
|
|
|
const WritingMode& WritingModeRef() const { return mWritingMode; }
|
|
|
|
bool EqualsExceptDirection(const TS_SELECTION_ACP& aACP) const {
|
|
if (mACP.isNothing()) {
|
|
return false;
|
|
}
|
|
if (mACP->style.ase == aACP.style.ase) {
|
|
return mACP->acpStart == aACP.acpStart && mACP->acpEnd == aACP.acpEnd;
|
|
}
|
|
return mACP->acpStart == aACP.acpEnd && mACP->acpEnd == aACP.acpStart;
|
|
}
|
|
|
|
bool EqualsExceptDirection(
|
|
const SelectionChangeDataBase& aChangedSelection) const {
|
|
MOZ_ASSERT(aChangedSelection.IsInitialized());
|
|
if (mACP.isNothing()) {
|
|
return aChangedSelection.HasRange();
|
|
}
|
|
return aChangedSelection.Length() == static_cast<uint32_t>(Length()) &&
|
|
aChangedSelection.mOffset == static_cast<uint32_t>(StartOffset());
|
|
}
|
|
|
|
friend std::ostream& operator<<(std::ostream& aStream,
|
|
const Selection& aSelection) {
|
|
aStream << "{ mACP=" << ToString(aSelection.mACP).c_str()
|
|
<< ", mWritingMode=" << ToString(aSelection.mWritingMode).c_str()
|
|
<< ", Collapsed()="
|
|
<< (aSelection.Collapsed() ? "true" : "false")
|
|
<< ", Length=" << aSelection.Length() << " }";
|
|
return aStream;
|
|
}
|
|
|
|
private:
|
|
Maybe<TS_SELECTION_ACP> mACP; // If Nothing, there is no selection
|
|
WritingMode mWritingMode;
|
|
};
|
|
// Don't access mSelection directly. Instead, Use SelectionForTSFRef().
|
|
// This is modified immediately when TSF requests to set selection and not
|
|
// updated by selection change in content until mContentForTSF is cleared.
|
|
Maybe<Selection> mSelectionForTSF;
|
|
|
|
/**
|
|
* Get the selection expected by TSF. If mSelectionForTSF is already valid,
|
|
* this just return the reference to it. Otherwise, this initializes it
|
|
* with eQuerySelectedText. Please check if the result is valid before
|
|
* actually using it.
|
|
* Note that this is also called by ContentForTSF().
|
|
*/
|
|
Maybe<Selection>& SelectionForTSF();
|
|
|
|
struct PendingAction final {
|
|
enum class Type : uint8_t {
|
|
eCompositionStart,
|
|
eCompositionUpdate,
|
|
eCompositionEnd,
|
|
eSetSelection,
|
|
eKeyboardEvent,
|
|
};
|
|
Type mType;
|
|
// For eCompositionStart, eCompositionEnd and eSetSelection
|
|
LONG mSelectionStart;
|
|
// For eCompositionStart and eSetSelection
|
|
LONG mSelectionLength;
|
|
// For eCompositionStart, eCompositionUpdate and eCompositionEnd
|
|
nsString mData;
|
|
// For eCompositionUpdate
|
|
RefPtr<TextRangeArray> mRanges;
|
|
// For eKeyboardEvent
|
|
MSG mKeyMsg;
|
|
// For eSetSelection
|
|
bool mSelectionReversed;
|
|
// For eCompositionUpdate
|
|
bool mIncomplete;
|
|
// For eCompositionStart
|
|
bool mAdjustSelection;
|
|
};
|
|
// Items of mPendingActions are appended when TSF tells us to need to dispatch
|
|
// DOM composition events. However, we cannot dispatch while the document is
|
|
// locked because it can cause modifying the locked document. So, the pending
|
|
// actions should be performed when document lock is unlocked.
|
|
nsTArray<PendingAction> mPendingActions;
|
|
|
|
PendingAction* LastOrNewPendingCompositionUpdate() {
|
|
if (!mPendingActions.IsEmpty()) {
|
|
PendingAction& lastAction = mPendingActions.LastElement();
|
|
if (lastAction.mType == PendingAction::Type::eCompositionUpdate) {
|
|
return &lastAction;
|
|
}
|
|
}
|
|
PendingAction* newAction = mPendingActions.AppendElement();
|
|
newAction->mType = PendingAction::Type::eCompositionUpdate;
|
|
newAction->mRanges = new TextRangeArray();
|
|
newAction->mIncomplete = true;
|
|
return newAction;
|
|
}
|
|
|
|
/**
|
|
* IsLastPendingActionCompositionEndAt() checks whether the previous pending
|
|
* action is committing composition whose range starts from aStart and its
|
|
* length is aLength. In other words, this checks whether new composition
|
|
* which will replace same range as previous pending commit can be merged
|
|
* with the previous composition.
|
|
*
|
|
* @param aStart The inserted offset you expected.
|
|
* @param aLength The inserted text length you expected.
|
|
* @return true if the last pending action is
|
|
* eCompositionEnd and it inserted the text
|
|
* between aStart and aStart + aLength.
|
|
*/
|
|
bool IsLastPendingActionCompositionEndAt(LONG aStart, LONG aLength) const {
|
|
if (mPendingActions.IsEmpty()) {
|
|
return false;
|
|
}
|
|
const PendingAction& pendingLastAction = mPendingActions.LastElement();
|
|
return pendingLastAction.mType == PendingAction::Type::eCompositionEnd &&
|
|
pendingLastAction.mSelectionStart == aStart &&
|
|
pendingLastAction.mData.Length() == static_cast<ULONG>(aLength);
|
|
}
|
|
|
|
bool IsPendingCompositionUpdateIncomplete() const {
|
|
if (mPendingActions.IsEmpty()) {
|
|
return false;
|
|
}
|
|
const PendingAction& lastAction = mPendingActions.LastElement();
|
|
return lastAction.mType == PendingAction::Type::eCompositionUpdate &&
|
|
lastAction.mIncomplete;
|
|
}
|
|
|
|
void CompleteLastActionIfStillIncomplete() {
|
|
if (!IsPendingCompositionUpdateIncomplete()) {
|
|
return;
|
|
}
|
|
RecordCompositionUpdateAction();
|
|
}
|
|
|
|
void RemoveLastCompositionUpdateActions() {
|
|
while (!mPendingActions.IsEmpty()) {
|
|
const PendingAction& lastAction = mPendingActions.LastElement();
|
|
if (lastAction.mType != PendingAction::Type::eCompositionUpdate) {
|
|
break;
|
|
}
|
|
mPendingActions.RemoveLastElement();
|
|
}
|
|
}
|
|
|
|
// When On*Composition() is called without document lock, we need to flush
|
|
// the recorded actions at quitting the method.
|
|
// AutoPendingActionAndContentFlusher class is usedful for it.
|
|
class MOZ_STACK_CLASS AutoPendingActionAndContentFlusher final {
|
|
public:
|
|
explicit AutoPendingActionAndContentFlusher(TSFTextStore* aTextStore)
|
|
: mTextStore(aTextStore) {
|
|
MOZ_ASSERT(!mTextStore->mIsRecordingActionsWithoutLock);
|
|
if (!mTextStore->IsReadWriteLocked()) {
|
|
mTextStore->mIsRecordingActionsWithoutLock = true;
|
|
}
|
|
}
|
|
|
|
~AutoPendingActionAndContentFlusher() {
|
|
if (!mTextStore->mIsRecordingActionsWithoutLock) {
|
|
return;
|
|
}
|
|
mTextStore->FlushPendingActions();
|
|
mTextStore->mIsRecordingActionsWithoutLock = false;
|
|
}
|
|
|
|
private:
|
|
AutoPendingActionAndContentFlusher() {}
|
|
|
|
RefPtr<TSFTextStore> mTextStore;
|
|
};
|
|
|
|
class Content final {
|
|
public:
|
|
Content(TSFTextStore& aTSFTextStore, const nsAString& aText)
|
|
: mText(aText),
|
|
mLastComposition(aTSFTextStore.mComposition),
|
|
mComposition(aTSFTextStore.mComposition),
|
|
mSelection(aTSFTextStore.mSelectionForTSF) {}
|
|
|
|
void OnLayoutChanged() { mMinModifiedOffset.reset(); }
|
|
|
|
// OnCompositionEventsHandled() is called when all pending composition
|
|
// events are handled in the focused content which may be in a remote
|
|
// process.
|
|
void OnCompositionEventsHandled() { mLastComposition = mComposition; }
|
|
|
|
const nsDependentSubstring GetSelectedText() const;
|
|
const nsDependentSubstring GetSubstring(uint32_t aStart,
|
|
uint32_t aLength) const;
|
|
void ReplaceSelectedTextWith(const nsAString& aString);
|
|
void ReplaceTextWith(LONG aStart, LONG aLength, const nsAString& aString);
|
|
|
|
void StartComposition(ITfCompositionView* aCompositionView,
|
|
const PendingAction& aCompStart,
|
|
bool aPreserveSelection);
|
|
/**
|
|
* RestoreCommittedComposition() restores the committed string as
|
|
* composing string. If InsertTextAtSelection() or something is called
|
|
* before a call of OnStartComposition() or previous composition is
|
|
* committed and new composition is restarted to clean up the commited
|
|
* string, there is a pending compositionend. In this case, we need to
|
|
* cancel the pending compositionend and continue the composition.
|
|
*
|
|
* @param aCompositionView The composition view.
|
|
* @param aCanceledCompositionEnd The pending compositionend which is
|
|
* canceled for restarting the composition.
|
|
*/
|
|
void RestoreCommittedComposition(
|
|
ITfCompositionView* aCompositionView,
|
|
const PendingAction& aCanceledCompositionEnd);
|
|
void EndComposition(const PendingAction& aCompEnd);
|
|
|
|
const nsString& TextRef() const { return mText; }
|
|
const Maybe<OffsetAndData<LONG>>& LastComposition() const {
|
|
return mLastComposition;
|
|
}
|
|
const Maybe<uint32_t>& MinModifiedOffset() const {
|
|
return mMinModifiedOffset;
|
|
}
|
|
const Maybe<StartAndEndOffsets<LONG>>& LatestCompositionRange() const {
|
|
return mLatestCompositionRange;
|
|
}
|
|
|
|
// Returns true if layout of the character at the aOffset has not been
|
|
// calculated.
|
|
bool IsLayoutChangedAt(uint32_t aOffset) const {
|
|
return IsLayoutChanged() && (mMinModifiedOffset.value() <= aOffset);
|
|
}
|
|
// Returns true if layout of the content has been changed, i.e., the new
|
|
// layout has not been calculated.
|
|
bool IsLayoutChanged() const { return mMinModifiedOffset.isSome(); }
|
|
bool HasOrHadComposition() const {
|
|
return mLatestCompositionRange.isSome();
|
|
}
|
|
|
|
Maybe<TSFTextStore::Composition>& Composition() { return mComposition; }
|
|
Maybe<TSFTextStore::Selection>& Selection() { return mSelection; }
|
|
|
|
friend std::ostream& operator<<(std::ostream& aStream,
|
|
const Content& aContent) {
|
|
aStream << "{ mText="
|
|
<< PrintStringDetail(aContent.mText,
|
|
PrintStringDetail::kMaxLengthForEditor)
|
|
.get()
|
|
<< ", mLastComposition=" << aContent.mLastComposition
|
|
<< ", mLatestCompositionRange="
|
|
<< aContent.mLatestCompositionRange
|
|
<< ", mMinModifiedOffset=" << aContent.mMinModifiedOffset << " }";
|
|
return aStream;
|
|
}
|
|
|
|
private:
|
|
nsString mText;
|
|
|
|
// mLastComposition may store the composition string and its start offset
|
|
// when the document is locked. This is necessary to compute
|
|
// mMinTextModifiedOffset.
|
|
Maybe<OffsetAndData<LONG>> mLastComposition;
|
|
|
|
Maybe<TSFTextStore::Composition>& mComposition;
|
|
Maybe<TSFTextStore::Selection>& mSelection;
|
|
|
|
// The latest composition's start and end offset.
|
|
Maybe<StartAndEndOffsets<LONG>> mLatestCompositionRange;
|
|
|
|
// The minimum offset of modified part of the text.
|
|
Maybe<uint32_t> mMinModifiedOffset;
|
|
};
|
|
// mContentForTSF is cache of content. The information is expected by TSF
|
|
// and TIP. Therefore, this is useful for answering the query from TSF or
|
|
// TIP.
|
|
// This is initialized by ContentForTSF() automatically (therefore, don't
|
|
// access this member directly except at calling Clear(), IsInitialized(),
|
|
// IsLayoutChangeAfter() or IsLayoutChanged()).
|
|
// This is cleared when:
|
|
// - When there is no composition, the document is unlocked.
|
|
// - When there is a composition, all dispatched events are handled by
|
|
// the focused editor which may be in a remote process.
|
|
// So, if two compositions are created very quickly, this cache may not be
|
|
// cleared between eCompositionCommit(AsIs) and eCompositionStart.
|
|
Maybe<Content> mContentForTSF;
|
|
|
|
Maybe<Content>& ContentForTSF();
|
|
|
|
class MOZ_STACK_CLASS AutoNotifyingTSFBatch final {
|
|
public:
|
|
explicit AutoNotifyingTSFBatch(TSFTextStore& aTextStore)
|
|
: mTextStore(aTextStore), mOldValue(aTextStore.mDeferNotifyingTSF) {
|
|
mTextStore.mDeferNotifyingTSF = true;
|
|
}
|
|
~AutoNotifyingTSFBatch() {
|
|
mTextStore.mDeferNotifyingTSF = mOldValue;
|
|
mTextStore.MaybeFlushPendingNotifications();
|
|
}
|
|
|
|
private:
|
|
TSFTextStore& mTextStore;
|
|
bool mOldValue;
|
|
};
|
|
|
|
// CanAccessActualContentDirectly() returns true when TSF/TIP can access
|
|
// actual content directly. In other words, mContentForTSF and/or
|
|
// mSelectionForTSF doesn't cache content or they matches with actual
|
|
// contents due to no pending text/selection change notifications.
|
|
bool CanAccessActualContentDirectly() const;
|
|
|
|
// While mContentForTSF is valid, this returns the text stored by it.
|
|
// Otherwise, return the current text content retrieved by eQueryTextContent.
|
|
bool GetCurrentText(nsAString& aTextContent);
|
|
|
|
class MouseTracker final {
|
|
public:
|
|
static const DWORD kInvalidCookie = static_cast<DWORD>(-1);
|
|
|
|
MouseTracker();
|
|
|
|
HRESULT Init(TSFTextStore* aTextStore);
|
|
HRESULT AdviseSink(TSFTextStore* aTextStore, ITfRangeACP* aTextRange,
|
|
ITfMouseSink* aMouseSink);
|
|
void UnadviseSink();
|
|
|
|
bool IsUsing() const { return mSink != nullptr; }
|
|
DWORD Cookie() const { return mCookie; }
|
|
bool OnMouseButtonEvent(ULONG aEdge, ULONG aQuadrant, DWORD aButtonStatus);
|
|
const Maybe<StartAndEndOffsets<LONG>> Range() const { return mRange; }
|
|
|
|
private:
|
|
RefPtr<ITfMouseSink> mSink;
|
|
Maybe<StartAndEndOffsets<LONG>> mRange;
|
|
DWORD mCookie;
|
|
};
|
|
// mMouseTrackers is an array to store each information of installed
|
|
// ITfMouseSink instance.
|
|
nsTArray<MouseTracker> mMouseTrackers;
|
|
|
|
// The input scopes for this context, defaults to IS_DEFAULT.
|
|
nsTArray<InputScope> mInputScopes;
|
|
|
|
// The URL cache of the focused document.
|
|
nsString mDocumentURL;
|
|
|
|
// Support retrieving attributes.
|
|
// TODO: We should support RightToLeft, perhaps.
|
|
enum {
|
|
// Used for result of GetRequestedAttrIndex()
|
|
eNotSupported = -1,
|
|
|
|
// Supported attributes
|
|
eInputScope = 0,
|
|
eDocumentURL,
|
|
eTextVerticalWriting,
|
|
eTextOrientation,
|
|
|
|
// Count of the supported attributes
|
|
NUM_OF_SUPPORTED_ATTRS
|
|
};
|
|
bool mRequestedAttrs[NUM_OF_SUPPORTED_ATTRS] = {false};
|
|
|
|
int32_t GetRequestedAttrIndex(const TS_ATTRID& aAttrID);
|
|
TS_ATTRID GetAttrID(int32_t aIndex);
|
|
|
|
bool mRequestedAttrValues = false;
|
|
|
|
// If edit actions are being recorded without document lock, this is true.
|
|
// Otherwise, false.
|
|
bool mIsRecordingActionsWithoutLock = false;
|
|
// If GetTextExt() or GetACPFromPoint() is called and the layout hasn't been
|
|
// calculated yet, these methods return TS_E_NOLAYOUT. At that time,
|
|
// mHasReturnedNoLayoutError is set to true.
|
|
bool mHasReturnedNoLayoutError = 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;
|
|
// If this is false, MaybeFlushPendingNotifications() will clear the
|
|
// mContentForTSF.
|
|
bool mDeferClearingContentForTSF = 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;
|
|
// While the document is locked, committing composition always fails since
|
|
// TSF needs another document lock for modifying the composition, selection
|
|
// and etc. So, committing composition should be performed after the
|
|
// document is unlocked.
|
|
bool mDeferCommittingComposition = false;
|
|
bool mDeferCancellingComposition = 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;
|
|
|
|
// TSF thread manager object for the current application
|
|
static StaticRefPtr<ITfThreadMgr> sThreadMgr;
|
|
static already_AddRefed<ITfThreadMgr> GetThreadMgr();
|
|
// sMessagePump is QI'ed from sThreadMgr
|
|
static StaticRefPtr<ITfMessagePump> sMessagePump;
|
|
|
|
public:
|
|
// Expose GetMessagePump() for WinUtils.
|
|
static already_AddRefed<ITfMessagePump> GetMessagePump();
|
|
|
|
private:
|
|
// sKeystrokeMgr is QI'ed from sThreadMgr
|
|
static StaticRefPtr<ITfKeystrokeMgr> sKeystrokeMgr;
|
|
// TSF display attribute manager
|
|
static StaticRefPtr<ITfDisplayAttributeMgr> sDisplayAttrMgr;
|
|
static already_AddRefed<ITfDisplayAttributeMgr> GetDisplayAttributeMgr();
|
|
// TSF category manager
|
|
static StaticRefPtr<ITfCategoryMgr> sCategoryMgr;
|
|
static already_AddRefed<ITfCategoryMgr> GetCategoryMgr();
|
|
// Compartment for (Get|Set)IMEOpenState()
|
|
static StaticRefPtr<ITfCompartment> sCompartmentForOpenClose;
|
|
static already_AddRefed<ITfCompartment> GetCompartmentForOpenClose();
|
|
|
|
// 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.
|
|
static StaticRefPtr<TSFTextStore> sEnabledTextStore;
|
|
|
|
// For IME (keyboard) disabled state:
|
|
static StaticRefPtr<ITfDocumentMgr> sDisabledDocumentMgr;
|
|
static StaticRefPtr<ITfContext> sDisabledContext;
|
|
|
|
static StaticRefPtr<ITfInputProcessorProfiles> sInputProcessorProfiles;
|
|
static already_AddRefed<ITfInputProcessorProfiles>
|
|
GetInputProcessorProfiles();
|
|
|
|
// Handling key message.
|
|
static const MSG* sHandlingKeyMsg;
|
|
|
|
// TSF client ID for the current application
|
|
static DWORD sClientId;
|
|
|
|
// true if an eKeyDown or eKeyUp event for sHandlingKeyMsg has already
|
|
// been dispatched.
|
|
static bool sIsKeyboardEventDispatched;
|
|
};
|
|
|
|
} // namespace widget
|
|
} // namespace mozilla
|
|
|
|
#endif // #ifndef TSFTextStore_h_
|