/* -*- 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 "mozilla/EditorBase.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Selection.h" #include "mozilla/dom/Text.h" #include "nsCOMPtr.h" #include "nsCycleCollectionParticipant.h" #include "nsIDocShell.h" // XXX Why does only this need to be included here? #include "nsIMutationObserver.h" #include "nsIReflowObserver.h" #include "nsIScrollObserver.h" #include "nsIWidget.h" #include "nsStubDocumentObserver.h" #include "nsStubMutationObserver.h" #include "nsThreadUtils.h" #include "nsWeakReference.h" class nsIContent; class nsINode; class nsPresContext; namespace mozilla { class EventStateManager; class TextComposition; namespace dom { class Selection; } // namespace dom // IMEContentObserver notifies widget of any text and selection changes // in the currently focused editor class IMEContentObserver final : public nsStubMutationObserver, public nsIReflowObserver, public nsIScrollObserver, public nsSupportsWeakReference { public: using SelectionChangeData = widget::IMENotification::SelectionChangeData; using TextChangeData = widget::IMENotification::TextChangeData; using TextChangeDataBase = widget::IMENotification::TextChangeDataBase; using IMENotificationRequests = widget::IMENotificationRequests; using IMEMessage = widget::IMEMessage; enum class ForRemoval : bool { No, Yes }; IMEContentObserver(); NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IMEContentObserver, nsIReflowObserver) NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED NS_DECL_NSIREFLOWOBSERVER // nsIScrollObserver virtual void ScrollPositionChanged() override; /** * OnSelectionChange() is called when selection is changed in the editor. */ void OnSelectionChange(dom::Selection& aSelection); MOZ_CAN_RUN_SCRIPT bool OnMouseButtonEvent(nsPresContext& aPresContext, WidgetMouseEvent& aMouseEvent); MOZ_CAN_RUN_SCRIPT nsresult HandleQueryContentEvent(WidgetQueryContentEvent* aEvent); /** * Handle eSetSelection event if and only if aEvent changes selection offset * or length. Doing nothing when selection range is same is important to * honer users' intention or web app's intention because ContentEventHandler * does not support to put range boundaries to arbitrary side of element * boundaries. E.g., `bold[] normal` vs. `bold[] normal`. * Note that this compares given range with selection cache which has been * notified IME via widget. Therefore, the caller needs to guarantee that * pending notifications should've been flushed. If you test this, you need * to wait 2 animation frames before sending eSetSelection event. */ MOZ_CAN_RUN_SCRIPT nsresult MaybeHandleSelectionEvent( nsPresContext* aPresContext, WidgetSelectionEvent* aEvent); /** * Init() initializes the instance, i.e., retrieving necessary objects and * starts to observe something. * Be aware, callers of this method need to guarantee that the instance * won't be released during calling this. * * @param aWidget The widget which can access native IME. * @param aPresContext The PresContext which has aContent. * @param aElement An editable element or nullptr if this will observe * design mode document. * @param aEditorBase The editor which is associated with aContent. */ MOZ_CAN_RUN_SCRIPT void Init(nsIWidget& aWidget, nsPresContext& aPresContext, dom::Element* aElement, EditorBase& aEditorBase); /** * Destroy() finalizes the instance, i.e., stops observing contents and * clearing the members. * Be aware, callers of this method need to guarantee that the instance * won't be released during calling this. */ void Destroy(); /** * Returns false if the instance refers some objects and observing them. * Otherwise, true. */ bool Destroyed() const; /** * 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. * Be aware, callers of this method need to guarantee that the instance * won't be released during calling this. * * @return Returns true if the instance is managing the content. * Otherwise, false. */ MOZ_CAN_RUN_SCRIPT bool MaybeReinitialize(nsIWidget& aWidget, nsPresContext& aPresContext, dom::Element* aElement, EditorBase& aEditorBase); /** * Return true if this is observing editable content and aElement has focus. * If aElement is a text control, check if this is observing its anonymous * subtree. Otherwise, check if this is observing the children of aElement in * the DOM tree. If aElement is nullptr, this returns true if entire the * document is editable, e.g., in the designMode. */ [[nodiscard]] bool IsObserving(const nsPresContext& aPresContext, const dom::Element* aElement) const; [[nodiscard]] bool IsBeingInitializedFor(const nsPresContext& aPresContext, const dom::Element* aElement, const EditorBase& aEditorBase) const; bool IsObserving(const TextComposition& aTextComposition) const; bool WasInitializedWith(const EditorBase& aEditorBase) const { return mEditorBase == &aEditorBase; } bool IsEditorHandlingEventForComposition() const; bool KeepAliveDuringDeactive() const { return mIMENotificationRequests && mIMENotificationRequests->WantDuringDeactive(); } [[nodiscard]] bool EditorIsTextEditor() const { return mEditorBase && mEditorBase->IsTextEditor(); } nsIWidget* GetWidget() const { return mWidget; } void SuppressNotifyingIME(); void UnsuppressNotifyingIME(); nsPresContext* GetPresContext() const; nsresult GetSelectionAndRoot(dom::Selection** aSelection, dom::Element** aRootElement) const; /** * TryToFlushPendingNotifications() should be called when pending events * should be flushed. This tries to run the queued IMENotificationSender. * Doesn't do anything in child processes where flushing happens * asynchronously unless aAllowAsync is false. */ void TryToFlushPendingNotifications(bool aAllowAsync); /** * MaybeNotifyCompositionEventHandled() posts composition event handled * notification into the pseudo queue. */ void MaybeNotifyCompositionEventHandled(); /** * Following methods are called when the editor: * - an edit action handled. * - before handling an edit action. * - canceled handling an edit action after calling BeforeEditAction(). */ void OnEditActionHandled(); void BeforeEditAction(); void CancelEditAction(); /** * Called when text control value is changed while this is not observing * mRootElement. This is typically there is no frame for the editor (i.e., * no proper anonymous
element for the editor yet) or the TextEditor * has not been created (i.e., IMEStateManager has not been reinitialized * this instance with new anonymous
element yet). */ void OnTextControlValueChangedWhileNotObservable(const nsAString& aNewValue); dom::Element* GetObservingElement() const { return mIsObserving ? mRootElement.get() : nullptr; } private: ~IMEContentObserver() = default; enum State { eState_NotObserving, eState_Initializing, eState_StoppedObserving, eState_Observing }; State GetState() const; MOZ_CAN_RUN_SCRIPT bool InitWithEditor(nsPresContext& aPresContext, dom::Element* aElement, EditorBase& aEditorBase); void OnIMEReceivedFocus(); void Clear(); [[nodiscard]] bool IsObservingContent(const nsPresContext& aPresContext, const dom::Element* aElement) const; [[nodiscard]] bool IsReflowLocked() const; [[nodiscard]] bool IsSafeToNotifyIME() const; [[nodiscard]] bool IsEditorComposing() const; // Following methods are called by DocumentObserver when // beginning to update the contents and ending updating the contents. void BeginDocumentUpdate(); void EndDocumentUpdate(); // Following methods manages added nodes during a document change. /** * IsInDocumentChange() returns true while the DOM tree is being modified * with mozAutoDocUpdate. E.g., it's being modified by setting innerHTML or * insertAdjacentHTML(). This returns false when user types something in * the focused editor editor. */ bool IsInDocumentChange() const { return mDocumentObserver && mDocumentObserver->IsUpdating(); } [[nodiscard]] bool EditorIsHandlingEditSubAction() const; void PostFocusSetNotification(); void MaybeNotifyIMEOfFocusSet(); void PostTextChangeNotification(); void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData); void CancelNotifyingIMEOfTextChange(); void PostSelectionChangeNotification(); void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition, bool aCausedBySelectionEvent, bool aOccurredDuringComposition); void PostPositionChangeNotification(); void MaybeNotifyIMEOfPositionChange(); void CancelNotifyingIMEOfPositionChange(); void PostCompositionEventHandledNotification(); void ContentAdded(nsINode* aContainer, nsIContent* aFirstContent, nsIContent* aLastContent); struct MOZ_STACK_CLASS OffsetAndLengthAdjustments { [[nodiscard]] uint32_t AdjustedOffset(uint32_t aOffset) const { MOZ_ASSERT_IF(mOffsetAdjustment < 0, aOffset >= mOffsetAdjustment); return aOffset + mOffsetAdjustment; } [[nodiscard]] uint32_t AdjustedLength(uint32_t aLength) const { MOZ_ASSERT_IF(mOffsetAdjustment < 0, aLength >= mLengthAdjustment); return aLength + mLengthAdjustment; } [[nodiscard]] uint32_t AdjustedEndOffset(uint32_t aEndOffset) const { MOZ_ASSERT_IF(mOffsetAdjustment + mLengthAdjustment < 0, aEndOffset >= mOffsetAdjustment + mLengthAdjustment); return aEndOffset + (mOffsetAdjustment + mLengthAdjustment); } int64_t mOffsetAdjustment = 0; int64_t mLengthAdjustment = 0; }; /** * Posts a text change caused by cached added content in mAddedContentCache. * * @param aOffsetOfFirstContent * Flattened text offset of mFirst. This can be * different value from the computed value in the * current tree. However, in the case, * aAdjustments should have the difference. If this * is Nothing, it's computed with the current DOM. * @param aLengthOfContentNNodes * Flattened text length starting from mFirst and * ending by end of mLast. This can be different * value from the computed value in the current * tree. However, in the case, aAdjustments should * have the difference. If this is Nothing, it's * computed with the current DOM. * @param aAdjustments When aOffsetOfFirstContent and/or * aLengthOfContentNodes are specified different * value(s) from the computed value(s) in the * current DOM, these members should have non-zero * values of the differences. */ void NotifyIMEOfCachedConsecutiveNewNodes( const char* aCallerName, const Maybe& aOffsetOfFirstContent = Nothing(), const Maybe& aLengthOfContentNNodes = Nothing(), const OffsetAndLengthAdjustments& aAdjustments = OffsetAndLengthAdjustments{0, 0}); void ObserveEditableNode(); /** * NotifyIMEOfBlur() notifies IME of blur. */ void NotifyIMEOfBlur(); /** * UnregisterObservers() unregisters all listeners and observers. */ void UnregisterObservers(); void FlushMergeableNotifications(); bool NeedsTextChangeNotification() const { return mIMENotificationRequests && mIMENotificationRequests->WantTextChange(); } bool NeedsPositionChangeNotification() const { return mIMENotificationRequests && mIMENotificationRequests->WantPositionChanged(); } void ClearPendingNotifications() { mNeedsToNotifyIMEOfFocusSet = false; mNeedsToNotifyIMEOfTextChange = false; mNeedsToNotifyIMEOfSelectionChange = false; mNeedsToNotifyIMEOfPositionChange = false; mNeedsToNotifyIMEOfCompositionEventHandled = false; mTextChangeData.Clear(); } bool NeedsToNotifyIMEOfSomething() const { return mNeedsToNotifyIMEOfFocusSet || mNeedsToNotifyIMEOfTextChange || mNeedsToNotifyIMEOfSelectionChange || mNeedsToNotifyIMEOfPositionChange || mNeedsToNotifyIMEOfCompositionEventHandled; } /** * UpdateSelectionCache() updates mSelectionData with the latest selection. * This should be called only when IsSafeToNotifyIME() returns true. */ MOZ_CAN_RUN_SCRIPT bool UpdateSelectionCache(bool aRequireFlush = true); nsCOMPtr mWidget; // mFocusedWidget has the editor observed by the instance. E.g., if the // focused editor is in XUL panel, this should be the widget of the panel. // On the other hand, mWidget is its parent which handles IME. nsCOMPtr mFocusedWidget; RefPtr mSelection; RefPtr mRootElement; nsCOMPtr mEditableNode; nsCOMPtr mDocShell; RefPtr mEditorBase; /** * Helper classes to notify IME. */ class AChangeEvent : public Runnable { protected: enum ChangeEventType { eChangeEventType_Focus, eChangeEventType_Selection, eChangeEventType_Text, eChangeEventType_Position, eChangeEventType_CompositionEventHandled }; explicit AChangeEvent(const char* aName, IMEContentObserver* aIMEContentObserver) : Runnable(aName), mIMEContentObserver(do_GetWeakReference( static_cast(aIMEContentObserver))) { MOZ_ASSERT(aIMEContentObserver); } already_AddRefed GetObserver() const { nsCOMPtr observer = do_QueryReferent(mIMEContentObserver); return observer.forget().downcast(); } nsWeakPtr mIMEContentObserver; /** * CanNotifyIME() checks if mIMEContentObserver can and should notify IME. */ bool CanNotifyIME(ChangeEventType aChangeEventType) const; /** * IsSafeToNotifyIME() checks if it's safe to noitify IME. */ bool IsSafeToNotifyIME(ChangeEventType aChangeEventType) const; }; class IMENotificationSender : public AChangeEvent { public: explicit IMENotificationSender(IMEContentObserver* aIMEContentObserver) : AChangeEvent("IMENotificationSender", aIMEContentObserver), mIsRunning(false) {} MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override; void Dispatch(nsIDocShell* aDocShell); private: MOZ_CAN_RUN_SCRIPT void SendFocusSet(); MOZ_CAN_RUN_SCRIPT void SendSelectionChange(); void SendTextChange(); void SendPositionChange(); void SendCompositionEventHandled(); bool mIsRunning; }; // mQueuedSender is, it was put into the event queue but not run yet. RefPtr mQueuedSender; /** * IMEContentObserver is a mutation observer of mRootContent. However, * it needs to know the beginning of content changes and end of it too for * reducing redundant computation of text offset with ContentEventHandler. * Therefore, it needs helper class to listen only them since if * both mutations were observed by IMEContentObserver directly, each * methods need to check if the changing node is in mRootContent but it's * too expensive. */ class DocumentObserver final : public nsStubDocumentObserver { public: DocumentObserver() = delete; explicit DocumentObserver(IMEContentObserver& aIMEContentObserver) : mIMEContentObserver(&aIMEContentObserver), mDocumentUpdating(0) { SetEnabledCallbacks(nsIMutationObserver::kBeginUpdate | nsIMutationObserver::kEndUpdate); } NS_DECL_CYCLE_COLLECTION_CLASS(DocumentObserver) NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE void Observe(dom::Document*); void StopObserving(); void Destroy(); bool Destroyed() const { return !mIMEContentObserver; } bool IsObserving() const { return mDocument != nullptr; } bool IsUpdating() const { return mDocumentUpdating != 0; } private: virtual ~DocumentObserver() { Destroy(); } RefPtr mIMEContentObserver; RefPtr mDocument; uint32_t mDocumentUpdating; }; RefPtr mDocumentObserver; /** * FlatTextCache stores length of flattened text starting from start of * the observing node (typically editing host or the anonymous
of * TextEditor) to: * - end of mContent if it's set (IsCachingToEndOfContent() returns true) * - before first content of mContainerNode if mContent is not set * (IsCachingToStartOfContainer() returns true). In this case, the text * length includes a line break length which is caused by the open tag of * mContainerNode if and only if it's an element node and the open tag causes * a line break. */ struct FlatTextCache { public: explicit FlatTextCache(const char* aInstanceName) : mInstanceName(aInstanceName) {} void Clear(const char* aCallerName); [[nodiscard]] bool HasCache() const { return !!mContainerNode; } /** * Return true if mFlatTextLength caches flattened text length starting from * start of the observing node to the end of mContent. */ [[nodiscard]] bool IsCachingToEndOfContent() const { return mContainerNode && mContent; } /** * Return true if mFlatTextLength caches flattened text length starting from * start of the observing node to the start of mContainerNode. Note that if * mContainerNode is an element and whose open tag causes a line break, * mFlatTextLength includes the line break length too. */ [[nodiscard]] bool IsCachingToStartOfContainer() const { return mContainerNode && !mContent; } /** * Compute flattened text length starting from first content of aRootElement * and ending at end of aContent. * * @param aContent This will be set to mContent which points the * last child content node which participates in * the computed mFlatTextLength. * @param aRootElement The root element of the editor, i.e., editing * host or the anonymous
in a text control. * (This is required to suppress * ContentEventHandler to generate a line break * caused by open tag of the editable root element * due to not editable. Therefore, we need to call * ContentEventHandler methods with this.) */ [[nodiscard]] nsresult ComputeAndCacheFlatTextLengthBeforeEndOfContent( const char* aCallerName, const nsIContent& aContent, const dom::Element* aRootElement); void CacheFlatTextLengthBeforeEndOfContent( const char* aCallerName, const nsIContent& aContent, uint32_t aFlatTextLength, const dom::Element* aRootElement); /** * Compute flattened text length starting from first content of aRootElement * and ending at start of the first content of aContainer. * * @param aContainer This will be set to mContainer and mContent will * be set to nullptr. * @param aRootElement The root element of the editor, i.e., editing * host or the anonymous
in a text control. * (This is required to suppress * ContentEventHandler to generate a line break * caused by open tag of the editable root element * due to not editable. Therefore, we need to call * ContentEventHandler methods with this.) */ [[nodiscard]] nsresult ComputeAndCacheFlatTextLengthBeforeFirstContent( const char* aCallerName, const nsINode& aContainer, const dom::Element* aRootElement); void CacheFlatTextLengthBeforeFirstContent( const char* aCallerName, const nsINode& aContainer, uint32_t aFlatTextLength, const dom::Element* aRootElement); /** * Return flattened text length of aContent. I.e., the length includes a * line break caused by the open tag of aContent if it's an element node. * * @param aRemovingContent The content node which is being removed. * @param aRootElement The root element of the editor, i.e., editing * host or the anonymous
in a text control. * For avoiding to generate a redundant line break * at open tag of this element, this is required * to call methods of ContentEventHandler. * @param aForRemoval Whether aContent is about to be removed. */ [[nodiscard]] static Result ComputeTextLengthOfContent( const nsIContent& aContent, const dom::Element* aRootElement, ForRemoval = ForRemoval::No); /** * Return flattened text length of starting from first content of * aRootElement and ending at before aContent (if ContentEventHandler * generates a line break at open tag of aContent, the result does not * contain the line break length). * * @param aContent The content node which is immediately after a * content which you want to compute the flattened * text length before end of it. * @param aRootElement The root element of the editor, i.e., editing * host or the anonymous
in a text control. * For avoiding to generate a redundant line break * at open tag of this element, this is required * to call methods of ContentEventHandler. */ [[nodiscard]] static Result ComputeTextLengthBeforeContent(const nsIContent& aContent, const dom::Element* aRootElement); /** * Return flattened text length starting from first content of aRootElement * and ending at start of the first content of aContainer. This means that * if ContentEventHandler generates a line break at the open tag of * aContainer, the result includes the line break length. * NOTE: The difference from ComputeTextLengthBeforeContent() is, result of * this method includes a line break caused by the open tag of aContainer * if and only if it's an element node and ContentEventHandler generates * a line break for its open tag. * * @param aContainer The container node which you want to compute the * flattened text length before the first content * of. * @param aRootElement The root element of the editor, i.e., editing * host or the anonymous
in a text control. * For avoiding to generate a redundant line break * at open tag of this element, this is required * to call methods of ContentEventHandler. */ [[nodiscard]] static Result ComputeTextLengthBeforeFirstContentOf(const nsINode& aContainer, const dom::Element* aRootElement); /** * Return flattened text length of starting from start of aStartContent and * ending at end of aEndContent. If ContentEventHandler generates a line * break at open tag of aStartContent, the result includes the line break * length. * * @param aStartContent The first content node of consecutive nodes * which you want to compute flattened text length * starting from. * @param aEndContent The last content node of consecutive nodes * which you want to compute flattened text length * ending at. * @param aRootElement The root element of the editor, i.e., editing * host or the anonymous
in a text control. * For avoiding to generate a redundant line break * at open tag of this element, this is required * to call methods of ContentEventHandler. */ [[nodiscard]] static Result ComputeTextLengthStartOfContentToEndOfContent( const nsIContent& aStartContent, const nsIContent& aEndContent, const dom::Element* aRootElement); [[nodiscard]] uint32_t GetFlatTextLength() const { return mFlatTextLength; } /** * Return text length if it's exactly cached or can compute it quickly from * the cached data. aContent must not be new node which is inserted before * mContent because the cached text length does not include the text length * of aContent in such case. */ [[nodiscard]] Maybe GetFlatTextLengthBeforeContent( const nsIContent& aContent, const dom::Element* aRootElement, ForRemoval = ForRemoval::No) const; /** * Return text length before aFirstContent if it's exactly cached or can * compute it quickly from the caching data. This is called when the nodes * between aFirstContent and aLastContent are inserted into the tree. */ [[nodiscard]] Maybe GetFlatTextOffsetOnInsertion( const nsIContent& aFirstContent, const nsIContent& aLastContent, const dom::Element* aRootElement) const; /** * This works only in the debug build and * test.ime_content_observer.assert_valid_cache pref is enabled. This * checks with expensive computation, therefore, the pref is enabled only * when running automated tests for editors. */ void AssertValidCache(const dom::Element* aRootElement) const; /** * Called when content nodes from aFirstContent to aLastContent are added. * aAddedFlatTextLength may be flattened text length from start of * aFirstContent to end of aLastContent if it's computed by the caller. * Note that aFirstContent and aLastContent can be in different container * nodes, but this method is currently called with (maybe indirect) siblings * in the same container. */ void ContentAdded(const char* aCallerName, const nsIContent& aFirstContent, const nsIContent& aLastContent, const Maybe& aAddedFlatTextLength, const dom::Element* aRootElement); /** * Called when aContent will be removed. aFlatTextLengthOfContent is * flattened text length of aContent. */ void ContentWillBeRemoved(const nsIContent& aContent, uint32_t aFlatTextLengthOfContent, const dom::Element* aRootElement); public: // mContainerNode is parent node of mContent when it's cached. nsCOMPtr mContainerNode; // mContent points to the last child which participates in the current // mFlatTextLength. If this is nullptr, mFlatTextLength means that it // length before the first content of mContainerNode, i.e., including the // line break of that caused by the open tag of mContainerNode. nsCOMPtr mContent; private: // Length of flat text generated from contents between the start of the // observing node (typically editing host or the anonymous
of // TextEditor) and the end of mContent. uint32_t mFlatTextLength = 0; MOZ_DEFINE_DBG(FlatTextCache, mContainerNode, mContent, mFlatTextLength); const char* mInstanceName; }; friend std::ostream& operator<<(std::ostream& aStream, const FlatTextCache& aCache); // mEndOfAddedTextCache caches text length from the start of the observing // node 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 = FlatTextCache("mEndOfAddedTextCache"); // mStartOfRemovingTextRangeCache caches text length from the start of the // observing node 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. In other words, this caches text length before end of // mContent or before first child of mContainerNode. FlatTextCache mStartOfRemovingTextRangeCache = FlatTextCache("mStartOfRemovingTextRangeCache"); /** * Caches the DOM node ranges with storing the first node and the last node. * This is designed for mAddedContentCache. See comment at declaration of it * for the detail. */ struct AddedContentCache { /** * Clear the range. Callers should call this with __FUNCTION__ which will be * used to log which caller did it. */ void Clear(const char* aCallerName); [[nodiscard]] bool HasCache() const { return mFirst && mLast; } /** * Return true if aFirstContent and aLastContent can be merged into the * cached range. This should be called only when the instance caches * something. */ [[nodiscard]] bool CanMergeWith(const nsIContent& aFirstContent, const nsIContent& aLastContent, const dom::Element* aRootElement) const; /** * Return true if aContent is in the cached range. aContent can be not * a child of the common container of the caching range. */ [[nodiscard]] bool IsInRange(const nsIContent& aContent, const dom::Element* aRootElement) const; /** * Try to cache the range represented by aFirstContent and aLastContent. * If there is a cache, this will extend the caching range to contain * the new range. * * @return true if cached, otherwise, false. */ bool TryToCache(const nsIContent& aFirstContent, const nsIContent& aLastContent, const dom::Element* aRootElement); /** * Compute offset and length of the cached range before the nodes between * aNewFirstContent and aNewLastContent are inserted. * * @return The first one is offset, the other is length. */ [[nodiscard]] Result, nsresult> ComputeFlatTextRangeBeforeInsertingNewContent( const nsIContent& aNewFirstContent, const nsIContent& aNewLastContent, const dom::Element* aRootElement, OffsetAndLengthAdjustments& aDifferences) const; MOZ_DEFINE_DBG(AddedContentCache, mFirst, mLast); nsCOMPtr mFirst; nsCOMPtr mLast; }; // Caches the first node and the last node of new inserted nodes while editor // handles an editing command/operation. Therefore, the range is always in // the same container node. So, the range means that the direct siblings // between the first node and the last node are the inserted nodes, but not // yet post a text change notification. // FYI: This is cleared when editor ends handling current edit // operation/command. Therefore, the strong pointers in this member don't // need to be added to the cycle collection. AddedContentCache mAddedContentCache; TextChangeData mTextChangeData; // mSelectionData is the last selection data which was notified. The // selection information is modified by UpdateSelectionCache(). The reason // of the selection change is modified by MaybeNotifyIMEOfSelectionChange(). SelectionChangeData mSelectionData; EventStateManager* mESM = nullptr; const IMENotificationRequests* mIMENotificationRequests = nullptr; int64_t mPreCharacterDataChangeLength = -1; uint32_t mSuppressNotifications = 0; // If the observing editor is a text control's one, this is set to the value // length. uint32_t mTextControlValueLength = 0; // mSendingNotification is a notification which is now sending from // IMENotificationSender. When the value is NOTIFY_IME_OF_NOTHING, it's // not sending any notification. IMEMessage mSendingNotification = widget::NOTIFY_IME_OF_NOTHING; bool mIsObserving = false; bool mIsTextControl = false; bool mIMEHasFocus = false; bool mNeedsToNotifyIMEOfFocusSet = false; bool mNeedsToNotifyIMEOfTextChange = false; bool mNeedsToNotifyIMEOfSelectionChange = false; bool mNeedsToNotifyIMEOfPositionChange = false; bool mNeedsToNotifyIMEOfCompositionEventHandled = false; // mIsHandlingQueryContentEvent is true when IMEContentObserver is handling // WidgetQueryContentEvent with ContentEventHandler. bool mIsHandlingQueryContentEvent = false; }; } // namespace mozilla #endif // mozilla_IMEContentObserver_h