/* -*- 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 "nsWindowBase.h" #include "WinUtils.h" #include "WritingModes.h" #include "mozilla/Attributes.h" #include "mozilla/RefPtr.h" #include "mozilla/StaticPtr.h" #include "mozilla/TextEventDispatcher.h" #include "mozilla/TextRange.h" #include "mozilla/WindowsVersion.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; namespace mozilla { namespace widget { struct MSGResult; /* * Text Services Framework text store */ class TSFTextStore final : public ITextStoreACP , public ITfContextOwnerCompositionSink , public ITfMouseTrackerACP { 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(nsWindowBase* 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(nsWindowBase* aWidget, const InputContext& aContext, const InputContextAction& aAction); static nsresult OnFocusChange(bool aGotFocus, nsWindowBase* 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 nsIMEUpdatePreference GetIMEUpdatePreference(); // 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 ITfMessagePump* GetMessagePump() { return sMessagePump; } 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.IsComposing()); } static bool IsComposingOn(nsWindowBase* aWidget) { return (IsComposing() && sEnabledTextStore->mWidget == aWidget); } static nsWindowBase* 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(); #ifdef DEBUG // Returns true when keyboard layout has IME (TIP). static bool CurrentKeyboardLayoutHasIME(); #endif // #ifdef DEBUG protected: TSFTextStore(); ~TSFTextStore(); static bool CreateAndSetFocus(nsWindowBase* aFocusedWidget, const InputContext& aContext); static void EnsureToDestroyAndReleaseEnabledTextStoreIf( RefPtr<TSFTextStore>& aTextStore); static void MarkContextAsKeyboardDisabled(ITfContext* aContext); static void MarkContextAsEmpty(ITfContext* aContext); bool Init(nsWindowBase* 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); HRESULT RestartComposition(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* aComposition, 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. 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& aHTMLInputInputmode); // 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(); // Holds the pointer to our current win32 widget RefPtr<nsWindowBase> 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() { ++mHandlingKeyMessage; } void OnEndHandlingKeyMessage() { 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(); } } class Composition final { public: // nullptr if no composition is active, otherwise the current composition RefPtr<ITfCompositionView> mView; // Current copy of the active composition string. Only mString is // changed during a InsertTextAtSelection call if we have a composition. // mString acts as a buffer until OnUpdateComposition is called // and mString is flushed to editor through eCompositionChange. // This way all changes are updated in batches to avoid // inconsistencies/artifacts. nsString mString; // The start of the current active composition, in ACP offsets LONG mStart; bool IsComposing() const { return (mView != nullptr); } LONG EndOffset() const { return mStart + static_cast<LONG>(mString.Length()); } // Start() and End() updates the members for emulating the latest state. // Unless flush the pending actions, this data never matches the actual // content. void Start(ITfCompositionView* aCompositionView, LONG aCompositionStartOffset, const nsAString& aCompositionString); void End(); }; // 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. Composition mComposition; /** * IsComposingInContent() returns true if there is a composition in the * focused editor and it's caused by native IME (either TIP of TSF or IME of * IMM). I.e., returns true between eCompositionStart and * eCompositionCommit(AsIs). */ bool IsComposingInContent() const; class Selection { public: Selection() : mDirty(true) {} bool IsDirty() const { return mDirty; }; void MarkDirty() { mDirty = true; } TS_SELECTION_ACP& ACP() { MOZ_ASSERT(!mDirty); return mACP; } void SetSelection(const TS_SELECTION_ACP& aSelection) { mDirty = false; mACP = 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(uint32_t aStart, uint32_t aLength, bool aReversed, WritingMode aWritingMode) { bool changed = mDirty || mACP.acpStart != static_cast<LONG>(aStart) || mACP.acpEnd != static_cast<LONG>(aStart + aLength); mDirty = false; mACP.acpStart = static_cast<LONG>(aStart); mACP.acpEnd = static_cast<LONG>(aStart + aLength); mACP.style.ase = aReversed ? TS_AE_START : TS_AE_END; mACP.style.fInterimChar = FALSE; mWritingMode = aWritingMode; return changed; } bool IsCollapsed() const { MOZ_ASSERT(!mDirty); return (mACP.acpStart == mACP.acpEnd); } void CollapseAt(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. mDirty = false; mACP.acpStart = mACP.acpEnd = static_cast<LONG>(aOffset); mACP.style.ase = TS_AE_END; mACP.style.fInterimChar = FALSE; } LONG MinOffset() const { MOZ_ASSERT(!mDirty); LONG min = std::min(mACP.acpStart, mACP.acpEnd); MOZ_ASSERT(min >= 0); return min; } LONG MaxOffset() const { MOZ_ASSERT(!mDirty); LONG max = std::max(mACP.acpStart, mACP.acpEnd); MOZ_ASSERT(max >= 0); return max; } LONG StartOffset() const { MOZ_ASSERT(!mDirty); MOZ_ASSERT(mACP.acpStart >= 0); return mACP.acpStart; } LONG EndOffset() const { MOZ_ASSERT(!mDirty); MOZ_ASSERT(mACP.acpEnd >= 0); return mACP.acpEnd; } LONG Length() const { MOZ_ASSERT(!mDirty); MOZ_ASSERT(mACP.acpEnd >= mACP.acpStart); return std::abs(mACP.acpEnd - mACP.acpStart); } bool IsReversed() const { MOZ_ASSERT(!mDirty); return (mACP.style.ase == TS_AE_START); } TsActiveSelEnd ActiveSelEnd() const { MOZ_ASSERT(!mDirty); return mACP.style.ase; } bool IsInterimChar() const { MOZ_ASSERT(!mDirty); return (mACP.style.fInterimChar != FALSE); } WritingMode GetWritingMode() const { MOZ_ASSERT(!mDirty); return mWritingMode; } bool EqualsExceptDirection(const TS_SELECTION_ACP& aACP) const { 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(!mDirty); MOZ_ASSERT(aChangedSelection.IsValid()); return aChangedSelection.Length() == static_cast<uint32_t>(Length()) && aChangedSelection.mOffset == static_cast<uint32_t>(StartOffset()); } private: TS_SELECTION_ACP mACP; WritingMode mWritingMode; bool mDirty; }; // Don't access mSelection directly except at calling MarkDirty(). // Use SelectionForTSFRef() instead. This is modified immediately when // TSF requests to set selection and not updated by selection change in // content until mContentForTSF is cleared. 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 ContentForTSFRef(). */ Selection& SelectionForTSFRef(); class MOZ_STACK_CLASS AutoSetTemporarySelection final { public: AutoSetTemporarySelection(Selection& aSelection) : mSelection(aSelection) { mDirty = mSelection.IsDirty(); if (mDirty) { mSelection.CollapseAt(0); } } ~AutoSetTemporarySelection() { if (mDirty) { mSelection.MarkDirty(); } } private: Selection& mSelection; bool mDirty; }; struct PendingAction final { enum ActionType : uint8_t { COMPOSITION_START, COMPOSITION_UPDATE, COMPOSITION_END, SET_SELECTION }; ActionType mType; // For compositionstart and selectionset LONG mSelectionStart; LONG mSelectionLength; // For compositionstart, compositionupdate and compositionend nsString mData; // For compositionupdate RefPtr<TextRangeArray> mRanges; // For selectionset bool mSelectionReversed; // For compositionupdate bool mIncomplete; // For compositionstart 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::COMPOSITION_UPDATE) { return &lastAction; } } PendingAction* newAction = mPendingActions.AppendElement(); newAction->mType = PendingAction::COMPOSITION_UPDATE; newAction->mRanges = new TextRangeArray(); newAction->mIncomplete = true; return newAction; } /** * WasTextInsertedWithoutCompositionAt() checks if text was inserted without * composition immediately before (e.g., see InsertTextAtSelectionInternal()). * * @param aStart The inserted offset you expected. * @param aLength The inserted text length you expected. * @return true if the last pending actions are * COMPOSITION_START and COMPOSITION_END and * aStart and aLength match their information. */ bool WasTextInsertedWithoutCompositionAt(LONG aStart, LONG aLength) const { if (mPendingActions.Length() < 2) { return false; } const PendingAction& pendingLastAction = mPendingActions.LastElement(); if (pendingLastAction.mType != PendingAction::COMPOSITION_END || pendingLastAction.mData.Length() != aLength) { return false; } const PendingAction& pendingPreLastAction = mPendingActions[mPendingActions.Length() - 2]; return pendingPreLastAction.mType == PendingAction::COMPOSITION_START && pendingPreLastAction.mSelectionStart == aStart; } bool IsPendingCompositionUpdateIncomplete() const { if (mPendingActions.IsEmpty()) { return false; } const PendingAction& lastAction = mPendingActions.LastElement(); return lastAction.mType == PendingAction::COMPOSITION_UPDATE && lastAction.mIncomplete; } void CompleteLastActionIfStillIncomplete() { if (!IsPendingCompositionUpdateIncomplete()) { return; } RecordCompositionUpdateAction(); } // 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: 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::Composition& aComposition, TSFTextStore::Selection& aSelection) : mComposition(aComposition), mSelection(aSelection) { Clear(); } void Clear() { mText.Truncate(); mLastCompositionString.Truncate(); mInitialized = false; } bool IsInitialized() const { return mInitialized; } void Init(const nsAString& aText) { mText = aText; if (mComposition.IsComposing()) { mLastCompositionString = mComposition.mString; } else { mLastCompositionString.Truncate(); } mMinTextModifiedOffset = NOT_MODIFIED; mInitialized = true; } void OnLayoutChanged() { mMinTextModifiedOffset = NOT_MODIFIED; } 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(), there is a pending * compositionstart and a pending compositionend. In this case, we * need to cancel the pending compositionend and continue the composition. * * @param aCompositionView The composition view. * @param aPendingCompositionStart The pending compositionstart which * started the committed composition. * @param aCanceledCompositionEnd The pending compositionend which is * canceled for restarting the composition. */ void RestoreCommittedComposition( ITfCompositionView* aCompositionView, const PendingAction& aPendingCompositionStart, const PendingAction& aCanceledCompositionEnd); void EndComposition(const PendingAction& aCompEnd); const nsString& Text() const { MOZ_ASSERT(mInitialized); return mText; } const nsString& LastCompositionString() const { MOZ_ASSERT(mInitialized); return mLastCompositionString; } uint32_t MinTextModifiedOffset() const { MOZ_ASSERT(mInitialized); return mMinTextModifiedOffset; } // Returns true if layout of the character at the aOffset has not been // calculated. bool IsLayoutChangedAt(uint32_t aOffset) const { return IsLayoutChanged() && (mMinTextModifiedOffset <= aOffset); } // Returns true if layout of the content has been changed, i.e., the new // layout has not been calculated. bool IsLayoutChanged() const { return mInitialized && (mMinTextModifiedOffset != NOT_MODIFIED); } // Returns minimum offset of modified text range. uint32_t MinOffsetOfLayoutChanged() const { return mInitialized ? mMinTextModifiedOffset : NOT_MODIFIED; } TSFTextStore::Composition& Composition() { return mComposition; } TSFTextStore::Selection& Selection() { return mSelection; } private: nsString mText; // mLastCompositionString stores the composition string when the document // is locked. This is necessary to compute mMinTextModifiedOffset. nsString mLastCompositionString; TSFTextStore::Composition& mComposition; TSFTextStore::Selection& mSelection; // The minimum offset of modified part of the text. enum : uint32_t { NOT_MODIFIED = UINT32_MAX }; uint32_t mMinTextModifiedOffset; bool mInitialized; }; // 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 ContentForTSFRef() 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. Content mContentForTSF; Content& ContentForTSFRef(); // 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; } bool InRange(uint32_t aOffset) const { if (NS_WARN_IF(mStart < 0) || NS_WARN_IF(mLength <= 0)) { return false; } return aOffset >= static_cast<uint32_t>(mStart) && aOffset < static_cast<uint32_t>(mStart + mLength); } DWORD Cookie() const { return mCookie; } bool OnMouseButtonEvent(ULONG aEdge, ULONG aQuadrant, DWORD aButtonStatus); LONG RangeStart() const { return mStart; } private: RefPtr<ITfMouseSink> mSink; LONG mStart; LONG mLength; 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; // Support retrieving attributes. // TODO: We should support RightToLeft, perhaps. enum { // Used for result of GetRequestedAttrIndex() eNotSupported = -1, // Supported attributes eInputScope = 0, eTextVerticalWriting, eTextOrientation, // Count of the supported attributes NUM_OF_SUPPORTED_ATTRS }; bool mRequestedAttrs[NUM_OF_SUPPORTED_ATTRS]; int32_t GetRequestedAttrIndex(const TS_ATTRID& aAttrID); TS_ATTRID GetAttrID(int32_t aIndex); bool mRequestedAttrValues; // If edit actions are being recorded without document lock, this is true. // Otherwise, false. bool mIsRecordingActionsWithoutLock; // 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; // 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; // During the documet is locked, we shouldn't destroy the instance. // If this is true, the instance will be destroyed after unlocked. bool mPendingDestroy; // If this is false, MaybeFlushPendingNotifications() will clear the // mContentForTSF. bool mDeferClearingContentForTSF; // While there is native caret, this is true. Otherwise, false. bool mNativeCaretIsCreated; // While the instance is dispatching events, the event may not be handled // synchronously in e10s mode. So, in such case, in strictly speaking, // we shouldn't query layout information. However, TS_E_NOLAYOUT bugs of // ITextStoreAPC::GetTextExt() blocks us to behave ideally. // For preventing it to be called, we should put off notifying TSF of // anything until layout information becomes available. bool mDeferNotifyingTSF; // 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; bool mDeferCancellingComposition; // 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; // While the instance is being destroyed, this is set to true for avoiding // recursive Destroy() calls. bool mBeingDestroyed; // TSF thread manager object for the current application static StaticRefPtr<ITfThreadMgr> sThreadMgr; // sMessagePump is QI'ed from sThreadMgr static StaticRefPtr<ITfMessagePump> sMessagePump; // sKeystrokeMgr is QI'ed from sThreadMgr static StaticRefPtr<ITfKeystrokeMgr> sKeystrokeMgr; // TSF display attribute manager static StaticRefPtr<ITfDisplayAttributeMgr> sDisplayAttrMgr; // TSF category manager static StaticRefPtr<ITfCategoryMgr> sCategoryMgr; // 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; // TSF client ID for the current application static DWORD sClientId; // Enables/Disables hack for specific TIP. static bool sCreateNativeCaretForLegacyATOK; static bool sDoNotReturnNoLayoutErrorToATOKOfCompositionString; static bool sDoNotReturnNoLayoutErrorToMSSimplifiedTIP; static bool sDoNotReturnNoLayoutErrorToMSTraditionalTIP; static bool sDoNotReturnNoLayoutErrorToFreeChangJie; static bool sDoNotReturnNoLayoutErrorToEasyChangjei; static bool sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar; static bool sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret; static bool sHackQueryInsertForMSSimplifiedTIP; static bool sHackQueryInsertForMSTraditionalTIP; }; } // namespace widget } // namespace mozilla #endif // #ifndef TSFTextStore_h_