/* -*- 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_TextComposition_h #define mozilla_TextComposition_h #include "nsCOMPtr.h" #include "nsINode.h" #include "nsIWidget.h" #include "nsTArray.h" #include "nsThreadUtils.h" #include "nsPresContext.h" #include "mozilla/AlreadyAddRefed.h" #include "mozilla/Attributes.h" #include "mozilla/EventForwards.h" #include "mozilla/RangeBoundary.h" #include "mozilla/TextRange.h" #include "mozilla/dom/BrowserParent.h" #include "mozilla/dom/Text.h" class nsRange; struct CharacterDataChangeInfo; namespace mozilla { class EditorBase; class EventDispatchingCallback; class IMEStateManager; /** * TextComposition represents a text composition. This class stores the * composition event target and its presContext. At dispatching the event via * this class, the instances use the stored event target. */ class TextComposition final { friend class IMEStateManager; NS_INLINE_DECL_REFCOUNTING(TextComposition) public: typedef dom::BrowserParent BrowserParent; typedef dom::Text Text; static bool IsHandlingSelectionEvent() { return sHandlingSelectionEvent; } TextComposition(nsPresContext* aPresContext, nsINode* aNode, BrowserParent* aBrowserParent, WidgetCompositionEvent* aCompositionEvent); bool Destroyed() const { return !mPresContext; } nsPresContext* GetPresContext() const { return mPresContext; } nsINode* GetEventTargetNode() const { return mNode; } // The text node which includes composition string. Text* GetContainerTextNode() const { return mContainerTextNode; } // The latest CompositionEvent.data value except compositionstart event. // This value is modified at dispatching compositionupdate. const nsString& LastData() const { return mLastData; } // Returns commit string if it'll be commited as-is. nsString CommitStringIfCommittedAsIs() const; // The composition string which is already handled by the focused editor. // I.e., this value must be same as the composition string on the focused // editor. This value is modified at a call of // EditorDidHandleCompositionChangeEvent(). // Note that mString and mLastData are different between dispatcing // compositionupdate and compositionchange event handled by focused editor. const nsString& String() const { return mString; } // The latest clauses range of the composition string. // During compositionupdate event, GetRanges() returns old ranges. // So if getting on compositionupdate, Use GetLastRange instead of GetRange(). TextRangeArray* GetLastRanges() const { return mLastRanges; } // Returns the clauses and/or caret range of the composition string. // This is modified at a call of EditorWillHandleCompositionChangeEvent(). // This may return null if there is no clauses and caret. // XXX We should return |const TextRangeArray*| here, but it causes compile // error due to inaccessible Release() method. TextRangeArray* GetRanges() const { return mRanges; } // Returns the widget which is proper to call NotifyIME(). already_AddRefed GetWidget() const { if (!mPresContext) { return nullptr; } return do_AddRef(mPresContext->GetRootWidget()); } // Returns the tab parent which has this composition in its remote process. BrowserParent* GetBrowserParent() const { return mBrowserParent; } // Returns true if the composition is started with synthesized event which // came from nsDOMWindowUtils. bool IsSynthesizedForTests() const { return mIsSynthesizedForTests; } const widget::NativeIMEContext& GetNativeIMEContext() const { return mNativeContext; } /** * This is called when IMEStateManager stops managing the instance. */ void Destroy(); /** * Request to commit (or cancel) the composition to IME. This method should * be called only by IMEStateManager::NotifyIME(). */ nsresult RequestToCommit(nsIWidget* aWidget, bool aDiscard); /** * IsRequestingCommitOrCancelComposition() returns true if the instance is * requesting widget to commit or cancel composition. */ bool IsRequestingCommitOrCancelComposition() const { return mIsRequestingCancel || mIsRequestingCommit; } /** * Send a notification to IME. It depends on the IME or platform spec what * will occur (or not occur). */ nsresult NotifyIME(widget::IMEMessage aMessage); /** * the offset of first composition string */ uint32_t NativeOffsetOfStartComposition() const { return mCompositionStartOffset; } /** * the offset of first selected clause or start of composition */ uint32_t NativeOffsetOfTargetClause() const { return mCompositionStartOffset + mTargetClauseOffsetInComposition; } /** * Return current composition start and end point in the DOM tree. * Note that one of or both of those result container may be different * from GetContainerTextNode() if the DOM tree was modified by the web * app. If there is no composition string the DOM tree, these return * unset range boundaries. */ RawRangeBoundary FirstIMESelectionStartRef() const; RawRangeBoundary LastIMESelectionEndRef() const; /** * The offset of composition string in the text node. If composition string * hasn't been inserted in any text node yet, this returns UINT32_MAX. */ uint32_t XPOffsetInTextNode() const { return mCompositionStartOffsetInTextNode; } /** * The length of composition string in the text node. If composition string * hasn't been inserted in any text node yet, this returns 0. */ uint32_t XPLengthInTextNode() const { return mCompositionLengthInTextNode == UINT32_MAX ? 0 : mCompositionLengthInTextNode; } /** * The end offset of composition string in the text node. If composition * string hasn't been inserted in any text node yet, this returns UINT32_MAX. */ uint32_t XPEndOffsetInTextNode() const { if (mCompositionStartOffsetInTextNode == UINT32_MAX || mCompositionLengthInTextNode == UINT32_MAX) { return UINT32_MAX; } return mCompositionStartOffsetInTextNode + mCompositionLengthInTextNode; } /** * Returns true if there is non-empty composition string and it's not fixed. * Otherwise, false. */ bool IsComposing() const { return mIsComposing; } /** * Returns true while editor is handling an event which is modifying the * composition string. */ bool IsEditorHandlingEvent() const { return mIsEditorHandlingEvent; } /** * IsMovingToNewTextNode() returns true if editor detects the text node * has been removed and still not insert the composition string into * new text node. */ bool IsMovingToNewTextNode() const { return !mContainerTextNode && mCompositionLengthInTextNode && mCompositionLengthInTextNode != UINT32_MAX; } /** * StartHandlingComposition() and EndHandlingComposition() are called by * editor when it holds a TextComposition instance and release it. */ void StartHandlingComposition(EditorBase* aEditorBase); void EndHandlingComposition(EditorBase* aEditorBase); /** * OnEditorDestroyed() is called when the editor is destroyed but there is * active composition. */ void OnEditorDestroyed(); /** * CompositionChangeEventHandlingMarker class should be created at starting * to handle text event in focused editor. This calls * EditorWillHandleCompositionChangeEvent() and * EditorDidHandleCompositionChangeEvent() automatically. */ class MOZ_STACK_CLASS CompositionChangeEventHandlingMarker { public: CompositionChangeEventHandlingMarker( TextComposition* aComposition, const WidgetCompositionEvent* aCompositionChangeEvent) : mComposition(aComposition) { mComposition->EditorWillHandleCompositionChangeEvent( aCompositionChangeEvent); } ~CompositionChangeEventHandlingMarker() { mComposition->EditorDidHandleCompositionChangeEvent(); } private: RefPtr mComposition; CompositionChangeEventHandlingMarker(); CompositionChangeEventHandlingMarker( const CompositionChangeEventHandlingMarker& aOther); }; /** * OnUpdateCompositionInEditor() is called when editor updates composition * string in the DOM tree. * * @param aStringToInsert The string to insert the text node actually. * This may be different from the data of * dispatching composition event because it may * be replaced with different character for * passwords, or truncated due to maxlength. * @param aTextNode The text node which includes composition string. * @param aOffset The offset of composition string in aTextNode. */ void OnUpdateCompositionInEditor(const nsAString& aStringToInsert, Text& aTextNode, uint32_t aOffset) { mContainerTextNode = &aTextNode; mCompositionStartOffsetInTextNode = aOffset; NS_WARNING_ASSERTION(mCompositionStartOffsetInTextNode != UINT32_MAX, "The text node is really too long."); mCompositionLengthInTextNode = aStringToInsert.Length(); NS_WARNING_ASSERTION(mCompositionLengthInTextNode != UINT32_MAX, "The string to insert is really too long."); } /** * OnTextNodeRemoved() is called when focused editor is reframed and * mContainerTextNode may be (or have been) replaced with different text * node, or just removes the text node due to empty. */ void OnTextNodeRemoved() { mContainerTextNode = nullptr; // Don't reset mCompositionStartOffsetInTextNode nor // mCompositionLengthInTextNode because editor needs them to restore // composition in new text node. } /** * OnCharacterDataChanged() is called when IMEContentObserver receives * character data change notifications. */ void OnCharacterDataChanged(Text& aText, const CharacterDataChangeInfo& aInfo); private: // Private destructor, to discourage deletion outside of Release(): ~TextComposition() { // WARNING: mPresContext may be destroying, so, be careful if you touch it. } // sHandlingSelectionEvent is true while TextComposition sends a selection // event to ContentEventHandler. static bool sHandlingSelectionEvent; // This class holds nsPresContext weak. This instance shouldn't block // destroying it. When the presContext is being destroyed, it's notified to // IMEStateManager::OnDestroyPresContext(), and then, it destroy // this instance. nsPresContext* mPresContext; RefPtr mNode; RefPtr mBrowserParent; // The text node which includes the composition string. RefPtr mContainerTextNode; // This is the clause and caret range information which is managed by // the focused editor. This may be null if there is no clauses or caret. RefPtr mRanges; // Same as mRange, but mRange will have old data during compositionupdate. // So this will be valied during compositionupdate. RefPtr mLastRanges; // mNativeContext stores a opaque pointer. This works as the "ID" for this // composition. Don't access the instance, it may not be available. widget::NativeIMEContext mNativeContext; // mEditorBaseWeak is a weak reference to the focused editor handling // composition. nsWeakPtr mEditorBaseWeak; // mLastData stores the data attribute of the latest composition event (except // the compositionstart event). nsString mLastData; // mString stores the composition text which has been handled by the focused // editor. nsString mString; // Offset of the composition string from start of the editor uint32_t mCompositionStartOffset; // Offset of the selected clause of the composition string from // mCompositionStartOffset uint32_t mTargetClauseOffsetInComposition; // Offset of the composition string in mContainerTextNode. // NOTE: This is NOT valid in the main process if focused editor is in a // remote process. uint32_t mCompositionStartOffsetInTextNode; // Length of the composition string in mContainerTextNode. If this instance // has already dispatched eCompositionCommit(AsIs) and // EditorDidHandleCompositionChangeEvent() has already been called, // this may be different from length of mString because committed string // may be truncated by maxlength attribute of or