зеркало из https://github.com/mozilla/gecko-dev.git
376 строки
12 KiB
C++
376 строки
12 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* 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 mozilla_IMEContentObserver_h_
|
|
#define mozilla_IMEContentObserver_h_
|
|
|
|
#include "mozilla/Attributes.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsCycleCollectionParticipant.h"
|
|
#include "nsIDocShell.h" // XXX Why does only this need to be included here?
|
|
#include "nsIEditor.h"
|
|
#include "nsIEditorObserver.h"
|
|
#include "nsIReflowObserver.h"
|
|
#include "nsISelectionListener.h"
|
|
#include "nsIScrollObserver.h"
|
|
#include "nsIWidget.h" // for nsIMEUpdatePreference
|
|
#include "nsStubMutationObserver.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsWeakReference.h"
|
|
|
|
class nsIContent;
|
|
class nsINode;
|
|
class nsISelection;
|
|
class nsPresContext;
|
|
|
|
namespace mozilla {
|
|
|
|
class EventStateManager;
|
|
|
|
// IMEContentObserver notifies widget of any text and selection changes
|
|
// in the currently focused editor
|
|
class IMEContentObserver final : public nsISelectionListener
|
|
, public nsStubMutationObserver
|
|
, public nsIReflowObserver
|
|
, public nsIScrollObserver
|
|
, public nsSupportsWeakReference
|
|
, public nsIEditorObserver
|
|
{
|
|
public:
|
|
typedef widget::IMENotification::SelectionChangeData SelectionChangeData;
|
|
typedef widget::IMENotification::TextChangeData TextChangeData;
|
|
typedef widget::IMENotification::TextChangeDataBase TextChangeDataBase;
|
|
|
|
IMEContentObserver();
|
|
|
|
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
|
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IMEContentObserver,
|
|
nsISelectionListener)
|
|
NS_DECL_NSIEDITOROBSERVER
|
|
NS_DECL_NSISELECTIONLISTENER
|
|
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
|
|
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
|
|
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
|
|
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
|
|
NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
|
|
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
|
|
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
|
|
NS_DECL_NSIREFLOWOBSERVER
|
|
|
|
// nsIScrollObserver
|
|
virtual void ScrollPositionChanged() override;
|
|
|
|
bool OnMouseButtonEvent(nsPresContext* aPresContext,
|
|
WidgetMouseEvent* aMouseEvent);
|
|
|
|
void Init(nsIWidget* aWidget, nsPresContext* aPresContext,
|
|
nsIContent* aContent, nsIEditor* aEditor);
|
|
void Destroy();
|
|
/**
|
|
* IMEContentObserver is stored by EventStateManager during observing.
|
|
* DisconnectFromEventStateManager() is called when EventStateManager stops
|
|
* storing the instance.
|
|
*/
|
|
void DisconnectFromEventStateManager();
|
|
/**
|
|
* MaybeReinitialize() tries to restart to observe the editor's root node.
|
|
* This is useful when the editor is reframed and all children are replaced
|
|
* with new node instances.
|
|
* @return Returns true if the instance is managing the content.
|
|
* Otherwise, false.
|
|
*/
|
|
bool MaybeReinitialize(nsIWidget* aWidget,
|
|
nsPresContext* aPresContext,
|
|
nsIContent* aContent,
|
|
nsIEditor* aEditor);
|
|
bool IsManaging(nsPresContext* aPresContext, nsIContent* aContent);
|
|
bool IsEditorHandlingEventForComposition() const;
|
|
bool KeepAliveDuringDeactive() const
|
|
{
|
|
return mUpdatePreference.WantDuringDeactive();
|
|
}
|
|
nsIWidget* GetWidget() const { return mWidget; }
|
|
nsIEditor* GetEditor() const { return mEditor; }
|
|
void SuppressNotifyingIME() { mSuppressNotifications++; }
|
|
void UnsuppressNotifyingIME()
|
|
{
|
|
if (!mSuppressNotifications || --mSuppressNotifications) {
|
|
return;
|
|
}
|
|
FlushMergeableNotifications();
|
|
}
|
|
nsPresContext* GetPresContext() const;
|
|
nsresult GetSelectionAndRoot(nsISelection** aSelection,
|
|
nsIContent** aRoot) const;
|
|
|
|
private:
|
|
~IMEContentObserver() {}
|
|
|
|
enum State {
|
|
eState_NotObserving,
|
|
eState_Initializing,
|
|
eState_StoppedObserving,
|
|
eState_Observing
|
|
};
|
|
State GetState() const;
|
|
bool IsObservingContent(nsPresContext* aPresContext,
|
|
nsIContent* aContent) const;
|
|
bool IsReflowLocked() const;
|
|
bool IsSafeToNotifyIME() const;
|
|
|
|
void PostFocusSetNotification();
|
|
void MaybeNotifyIMEOfFocusSet()
|
|
{
|
|
PostFocusSetNotification();
|
|
FlushMergeableNotifications();
|
|
}
|
|
void PostTextChangeNotification(const TextChangeDataBase& aTextChangeData);
|
|
void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData)
|
|
{
|
|
PostTextChangeNotification(aTextChangeData);
|
|
FlushMergeableNotifications();
|
|
}
|
|
void PostSelectionChangeNotification(bool aCausedByComposition,
|
|
bool aCausedBySelectionEvent);
|
|
void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition,
|
|
bool aCausedBySelectionEvent)
|
|
{
|
|
PostSelectionChangeNotification(aCausedByComposition,
|
|
aCausedBySelectionEvent);
|
|
FlushMergeableNotifications();
|
|
}
|
|
void PostPositionChangeNotification();
|
|
void MaybeNotifyIMEOfPositionChange()
|
|
{
|
|
PostPositionChangeNotification();
|
|
FlushMergeableNotifications();
|
|
}
|
|
|
|
void NotifyContentAdded(nsINode* aContainer, int32_t aStart, int32_t aEnd);
|
|
void ObserveEditableNode();
|
|
/**
|
|
* NotifyIMEOfBlur() notifies IME of blur.
|
|
*/
|
|
void NotifyIMEOfBlur();
|
|
/**
|
|
* UnregisterObservers() unregisters all listeners and observers.
|
|
*/
|
|
void UnregisterObservers();
|
|
void FlushMergeableNotifications();
|
|
void ClearPendingNotifications()
|
|
{
|
|
mIsFocusEventPending = false;
|
|
mIsSelectionChangeEventPending = false;
|
|
mIsPositionChangeEventPending = false;
|
|
mTextChangeData.Clear();
|
|
}
|
|
|
|
/**
|
|
* UpdateSelectionCache() updates mSelectionData with the latest selection.
|
|
* This should be called only when IsSafeToNotifyIME() returns true.
|
|
*
|
|
* Note that this does nothing if mUpdatePreference.WantSelectionChange()
|
|
* returns false.
|
|
*/
|
|
bool UpdateSelectionCache();
|
|
|
|
nsCOMPtr<nsIWidget> mWidget;
|
|
nsCOMPtr<nsISelection> mSelection;
|
|
nsCOMPtr<nsIContent> mRootContent;
|
|
nsCOMPtr<nsINode> mEditableNode;
|
|
nsCOMPtr<nsIDocShell> mDocShell;
|
|
nsCOMPtr<nsIEditor> mEditor;
|
|
|
|
/**
|
|
* FlatTextCache stores flat text length from start of the content to
|
|
* mNodeOffset of mContainerNode.
|
|
*/
|
|
struct FlatTextCache
|
|
{
|
|
// mContainerNode and mNodeOffset represent a point in DOM tree. E.g.,
|
|
// if mContainerNode is a div element, mNodeOffset is index of its child.
|
|
nsCOMPtr<nsINode> mContainerNode;
|
|
int32_t mNodeOffset;
|
|
// Length of flat text generated from contents between the start of content
|
|
// and a child node whose index is mNodeOffset of mContainerNode.
|
|
uint32_t mFlatTextLength;
|
|
|
|
FlatTextCache()
|
|
: mNodeOffset(0)
|
|
, mFlatTextLength(0)
|
|
{
|
|
}
|
|
|
|
void Clear()
|
|
{
|
|
mContainerNode = nullptr;
|
|
mNodeOffset = 0;
|
|
mFlatTextLength = 0;
|
|
}
|
|
|
|
void Cache(nsINode* aContainer, int32_t aNodeOffset,
|
|
uint32_t aFlatTextLength)
|
|
{
|
|
MOZ_ASSERT(aContainer, "aContainer must not be null");
|
|
MOZ_ASSERT(
|
|
aNodeOffset <= static_cast<int32_t>(aContainer->GetChildCount()),
|
|
"aNodeOffset must be same as or less than the count of children");
|
|
mContainerNode = aContainer;
|
|
mNodeOffset = aNodeOffset;
|
|
mFlatTextLength = aFlatTextLength;
|
|
}
|
|
|
|
bool Match(nsINode* aContainer, int32_t aNodeOffset) const
|
|
{
|
|
return aContainer == mContainerNode && aNodeOffset == mNodeOffset;
|
|
}
|
|
};
|
|
// mEndOfAddedTextCache caches text length from the start of content to
|
|
// the end of the last added content only while an edit action is being
|
|
// handled by the editor and no other mutation (e.g., removing node)
|
|
// occur.
|
|
FlatTextCache mEndOfAddedTextCache;
|
|
// mStartOfRemovingTextRangeCache caches text length from the start of content
|
|
// to the start of the last removed content only while an edit action is being
|
|
// handled by the editor and no other mutation (e.g., adding node) occur.
|
|
FlatTextCache mStartOfRemovingTextRangeCache;
|
|
|
|
TextChangeData mTextChangeData;
|
|
|
|
// mSelectionData is the last selection data which was notified. This is
|
|
// modified by UpdateSelectionCache(). Note that mCausedBy* are always
|
|
// false. Do NOT refer them.
|
|
SelectionChangeData mSelectionData;
|
|
|
|
EventStateManager* mESM;
|
|
|
|
nsIMEUpdatePreference mUpdatePreference;
|
|
uint32_t mPreAttrChangeLength;
|
|
uint32_t mSuppressNotifications;
|
|
int64_t mPreCharacterDataChangeLength;
|
|
|
|
bool mIsObserving;
|
|
bool mIMEHasFocus;
|
|
bool mIsFocusEventPending;
|
|
bool mIsSelectionChangeEventPending;
|
|
bool mSelectionChangeCausedOnlyByComposition;
|
|
bool mSelectionChangeCausedOnlyBySelectionEvent;
|
|
bool mIsPositionChangeEventPending;
|
|
bool mIsFlushingPendingNotifications;
|
|
|
|
|
|
/**
|
|
* Helper classes to notify IME.
|
|
*/
|
|
|
|
class AChangeEvent: public nsRunnable
|
|
{
|
|
protected:
|
|
enum ChangeEventType
|
|
{
|
|
eChangeEventType_Focus,
|
|
eChangeEventType_Selection,
|
|
eChangeEventType_Text,
|
|
eChangeEventType_Position,
|
|
eChangeEventType_FlushPendingEvents
|
|
};
|
|
|
|
AChangeEvent(ChangeEventType aChangeEventType,
|
|
IMEContentObserver* aIMEContentObserver)
|
|
: mIMEContentObserver(aIMEContentObserver)
|
|
, mChangeEventType(aChangeEventType)
|
|
{
|
|
MOZ_ASSERT(mIMEContentObserver);
|
|
}
|
|
|
|
nsRefPtr<IMEContentObserver> mIMEContentObserver;
|
|
ChangeEventType mChangeEventType;
|
|
|
|
/**
|
|
* CanNotifyIME() checks if mIMEContentObserver can and should notify IME.
|
|
*/
|
|
bool CanNotifyIME() const;
|
|
|
|
/**
|
|
* IsSafeToNotifyIME() checks if it's safe to noitify IME.
|
|
*/
|
|
bool IsSafeToNotifyIME() const;
|
|
};
|
|
|
|
class FocusSetEvent: public AChangeEvent
|
|
{
|
|
public:
|
|
explicit FocusSetEvent(IMEContentObserver* aIMEContentObserver)
|
|
: AChangeEvent(eChangeEventType_Focus, aIMEContentObserver)
|
|
{
|
|
}
|
|
NS_IMETHOD Run() override;
|
|
};
|
|
|
|
class SelectionChangeEvent : public AChangeEvent
|
|
{
|
|
public:
|
|
SelectionChangeEvent(IMEContentObserver* aIMEContentObserver,
|
|
bool aCausedByComposition,
|
|
bool aCausedBySelectionEvent)
|
|
: AChangeEvent(eChangeEventType_Selection, aIMEContentObserver)
|
|
, mCausedByComposition(aCausedByComposition)
|
|
, mCausedBySelectionEvent(aCausedBySelectionEvent)
|
|
{
|
|
aIMEContentObserver->mSelectionChangeCausedOnlyByComposition = false;
|
|
aIMEContentObserver->mSelectionChangeCausedOnlyBySelectionEvent = false;
|
|
}
|
|
NS_IMETHOD Run() override;
|
|
|
|
private:
|
|
bool mCausedByComposition;
|
|
bool mCausedBySelectionEvent;
|
|
};
|
|
|
|
class TextChangeEvent : public AChangeEvent
|
|
{
|
|
public:
|
|
TextChangeEvent(IMEContentObserver* aIMEContentObserver,
|
|
TextChangeDataBase& aTextChangeData)
|
|
: AChangeEvent(eChangeEventType_Text, aIMEContentObserver)
|
|
, mTextChangeData(aTextChangeData)
|
|
{
|
|
MOZ_ASSERT(mTextChangeData.IsValid());
|
|
// Reset aTextChangeData because this now consumes the data.
|
|
aTextChangeData.Clear();
|
|
}
|
|
NS_IMETHOD Run() override;
|
|
|
|
private:
|
|
TextChangeDataBase mTextChangeData;
|
|
};
|
|
|
|
class PositionChangeEvent final : public AChangeEvent
|
|
{
|
|
public:
|
|
explicit PositionChangeEvent(IMEContentObserver* aIMEContentObserver)
|
|
: AChangeEvent(eChangeEventType_Position, aIMEContentObserver)
|
|
{
|
|
}
|
|
NS_IMETHOD Run() override;
|
|
};
|
|
|
|
class AsyncMergeableNotificationsFlusher : public AChangeEvent
|
|
{
|
|
public:
|
|
explicit AsyncMergeableNotificationsFlusher(
|
|
IMEContentObserver* aIMEContentObserver)
|
|
: AChangeEvent(eChangeEventType_FlushPendingEvents, aIMEContentObserver)
|
|
{
|
|
}
|
|
NS_IMETHOD Run() override;
|
|
};
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // mozilla_IMEContentObserver_h_
|