diff options
Diffstat (limited to 'dom/events/TextComposition.h')
-rw-r--r-- | dom/events/TextComposition.h | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/dom/events/TextComposition.h b/dom/events/TextComposition.h new file mode 100644 index 000000000..a4161f82f --- /dev/null +++ b/dom/events/TextComposition.h @@ -0,0 +1,490 @@ +/* -*- 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 "nsIWeakReference.h" +#include "nsIWidget.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "nsPresContext.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "mozilla/TextRange.h" +#include "mozilla/dom/TabParent.h" + +class nsIEditor; + +namespace mozilla { + +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::TabParent TabParent; + + static bool IsHandlingSelectionEvent() { return sHandlingSelectionEvent; } + + TextComposition(nsPresContext* aPresContext, + nsINode* aNode, + TabParent* aTabParent, + WidgetCompositionEvent* aCompositionEvent); + + bool Destroyed() const { return !mPresContext; } + nsPresContext* GetPresContext() const { return mPresContext; } + nsINode* GetEventTargetNode() const { return mNode; } + // The latest CompositionEvent.data value except compositionstart event. + // This value is modified at dispatching compositionupdate. + const nsString& LastData() const { return mLastData; } + // 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(). + nsIWidget* GetWidget() const + { + return mPresContext ? mPresContext->GetRootWidget() : nullptr; + } + // 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); + + /** + * 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; + } + + /** + * 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; + } + + /** + * StartHandlingComposition() and EndHandlingComposition() are called by + * editor when it holds a TextComposition instance and release it. + */ + void StartHandlingComposition(nsIEditor* aEditor); + void EndHandlingComposition(nsIEditor* aEditor); + + /** + * 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<TextComposition> mComposition; + CompositionChangeEventHandlingMarker(); + CompositionChangeEventHandlingMarker( + const CompositionChangeEventHandlingMarker& aOther); + }; + +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; + nsCOMPtr<nsINode> mNode; + RefPtr<TabParent> mTabParent; + + // 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<TextRangeArray> mRanges; + // Same as mRange, but mRange will have old data during compositionupdate. + // So this will be valied during compositionupdate. + RefPtr<TextRangeArray> 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; + + // mEditorWeak is a weak reference to the focused editor handling composition. + nsWeakPtr mEditorWeak; + + // 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; + + // See the comment for IsSynthesizedForTests(). + bool mIsSynthesizedForTests; + + // See the comment for IsComposing(). + bool mIsComposing; + + // mIsEditorHandlingEvent is true while editor is modifying the composition + // string. + bool mIsEditorHandlingEvent; + + // mIsRequestingCommit or mIsRequestingCancel is true *only* while we're + // requesting commit or canceling the composition. In other words, while + // one of these values is true, we're handling the request. + bool mIsRequestingCommit; + bool mIsRequestingCancel; + + // mRequestedToCommitOrCancel is true *after* we requested IME to commit or + // cancel the composition. In other words, we already requested of IME that + // it commits or cancels current composition. + // NOTE: Before this is set true, both mIsRequestingCommit and + // mIsRequestingCancel are set false. + bool mRequestedToCommitOrCancel; + + // mWasNativeCompositionEndEventDiscarded is true if this composition was + // requested commit or cancel itself but native compositionend event is + // discarded by PresShell due to not safe to dispatch events. + bool mWasNativeCompositionEndEventDiscarded; + + // Allow control characters appear in composition string. + // When this is false, control characters except + // CHARACTER TABULATION (horizontal tab) are removed from + // both composition string and data attribute of compositionupdate + // and compositionend events. + bool mAllowControlCharacters; + + // mWasCompositionStringEmpty is true if the composition string was empty + // when DispatchCompositionEvent() is called. + bool mWasCompositionStringEmpty; + + // Hide the default constructor and copy constructor. + TextComposition() + : mPresContext(nullptr) + , mNativeContext(nullptr) + , mCompositionStartOffset(0) + , mTargetClauseOffsetInComposition(0) + , mIsSynthesizedForTests(false) + , mIsComposing(false) + , mIsEditorHandlingEvent(false) + , mIsRequestingCommit(false) + , mIsRequestingCancel(false) + , mRequestedToCommitOrCancel(false) + , mWasNativeCompositionEndEventDiscarded(false) + , mAllowControlCharacters(false) + , mWasCompositionStringEmpty(true) + {} + TextComposition(const TextComposition& aOther); + + /** + * GetEditor() returns nsIEditor pointer of mEditorWeak. + */ + already_AddRefed<nsIEditor> GetEditor() const; + + /** + * HasEditor() returns true if mEditorWeak holds nsIEditor instance which is + * alive. Otherwise, false. + */ + bool HasEditor() const; + + /** + * EditorWillHandleCompositionChangeEvent() must be called before the focused + * editor handles the compositionchange event. + */ + void EditorWillHandleCompositionChangeEvent( + const WidgetCompositionEvent* aCompositionChangeEvent); + + /** + * EditorDidHandleCompositionChangeEvent() must be called after the focused + * editor handles a compositionchange event. + */ + void EditorDidHandleCompositionChangeEvent(); + + /** + * IsValidStateForComposition() returns true if it's safe to dispatch an event + * to the DOM tree. Otherwise, false. + * WARNING: This doesn't check script blocker state. It should be checked + * before dispatching the first event. + */ + bool IsValidStateForComposition(nsIWidget* aWidget) const; + + /** + * DispatchCompositionEvent() dispatches the aCompositionEvent to the mContent + * synchronously. The caller must ensure that it's safe to dispatch the event. + */ + void DispatchCompositionEvent(WidgetCompositionEvent* aCompositionEvent, + nsEventStatus* aStatus, + EventDispatchingCallback* aCallBack, + bool aIsSynthesized); + + /** + * Simply calling EventDispatcher::Dispatch() with plugin event. + * If dispatching event has no orginal clone, aOriginalEvent can be null. + */ + void DispatchEvent(WidgetCompositionEvent* aDispatchEvent, + nsEventStatus* aStatus, + EventDispatchingCallback* aCallback, + const WidgetCompositionEvent *aOriginalEvent = nullptr); + + /** + * HandleSelectionEvent() sends the selection event to ContentEventHandler + * or dispatches it to the focused child process. + */ + void HandleSelectionEvent(WidgetSelectionEvent* aSelectionEvent) + { + HandleSelectionEvent(mPresContext, mTabParent, aSelectionEvent); + } + static void HandleSelectionEvent(nsPresContext* aPresContext, + TabParent* aTabParent, + WidgetSelectionEvent* aSelectionEvent); + + /** + * MaybeDispatchCompositionUpdate() may dispatch a compositionupdate event + * if aCompositionEvent changes composition string. + * @return Returns false if dispatching the compositionupdate event caused + * destroying this composition. + */ + bool MaybeDispatchCompositionUpdate( + const WidgetCompositionEvent* aCompositionEvent); + + /** + * CloneAndDispatchAs() dispatches a composition event which is + * duplicateed from aCompositionEvent and set the aMessage. + * + * @return Returns BaseEventFlags which is the result of dispatched event. + */ + BaseEventFlags CloneAndDispatchAs( + const WidgetCompositionEvent* aCompositionEvent, + EventMessage aMessage, + nsEventStatus* aStatus = nullptr, + EventDispatchingCallback* aCallBack = nullptr); + + /** + * If IME has already dispatched compositionend event but it was discarded + * by PresShell due to not safe to dispatch, this returns true. + */ + bool WasNativeCompositionEndEventDiscarded() const + { + return mWasNativeCompositionEndEventDiscarded; + } + + /** + * OnCompositionEventDiscarded() is called when PresShell discards + * compositionupdate, compositionend or compositionchange event due to not + * safe to dispatch event. + */ + void OnCompositionEventDiscarded(WidgetCompositionEvent* aCompositionEvent); + + /** + * OnCompositionEventDispatched() is called after a composition event is + * dispatched. + */ + void OnCompositionEventDispatched( + const WidgetCompositionEvent* aDispatchEvent); + + /** + * MaybeNotifyIMEOfCompositionEventHandled() notifies IME of composition + * event handled. This should be called after dispatching a composition + * event which came from widget. + */ + void MaybeNotifyIMEOfCompositionEventHandled( + const WidgetCompositionEvent* aCompositionEvent); + + /** + * GetSelectionStartOffset() returns normal selection start offset in the + * editor which has this composition. + * If it failed or lost focus, this would return 0. + */ + uint32_t GetSelectionStartOffset(); + + /** + * OnStartOffsetUpdatedInChild() is called when composition start offset + * is updated in the child process. I.e., this is called and never called + * if the composition is in this process. + * @param aStartOffset New composition start offset with native + * linebreaks. + */ + void OnStartOffsetUpdatedInChild(uint32_t aStartOffset); + + /** + * CompositionEventDispatcher dispatches the specified composition (or text) + * event. + */ + class CompositionEventDispatcher : public Runnable + { + public: + CompositionEventDispatcher(TextComposition* aTextComposition, + nsINode* aEventTarget, + EventMessage aEventMessage, + const nsAString& aData, + bool aIsSynthesizedEvent = false); + NS_IMETHOD Run() override; + + private: + RefPtr<TextComposition> mTextComposition; + nsCOMPtr<nsINode> mEventTarget; + nsString mData; + EventMessage mEventMessage; + bool mIsSynthesizedEvent; + + CompositionEventDispatcher() : mIsSynthesizedEvent(false) {}; + }; + + /** + * DispatchCompositionEventRunnable() dispatches a composition event to the + * content. Be aware, if you use this method, nsPresShellEventCB isn't used. + * That means that nsIFrame::HandleEvent() is never called. + * WARNING: The instance which is managed by IMEStateManager may be + * destroyed by this method call. + * + * @param aEventMessage Must be one of composition events. + * @param aData Used for mData value. + * @param aIsSynthesizingCommit true if this is called for synthesizing + * commit or cancel composition. Otherwise, + * false. + */ + void DispatchCompositionEventRunnable(EventMessage aEventMessage, + const nsAString& aData, + bool aIsSynthesizingCommit = false); +}; + +/** + * TextCompositionArray manages the instances of TextComposition class. + * Managing with array is enough because only one composition is typically + * there. Even if user switches native IME context, it's very rare that + * second or more composition is started. + * It's assumed that this is used by IMEStateManager for storing all active + * compositions in the process. If the instance is it, each TextComposition + * in the array can be destroyed by calling some methods of itself. + */ + +class TextCompositionArray final : + public AutoTArray<RefPtr<TextComposition>, 2> +{ +public: + // Looking for per native IME context. + index_type IndexOf(const widget::NativeIMEContext& aNativeIMEContext); + index_type IndexOf(nsIWidget* aWidget); + + TextComposition* GetCompositionFor(nsIWidget* aWidget); + TextComposition* GetCompositionFor( + const WidgetCompositionEvent* aCompositionEvent); + + // Looking for per nsPresContext + index_type IndexOf(nsPresContext* aPresContext); + index_type IndexOf(nsPresContext* aPresContext, nsINode* aNode); + + TextComposition* GetCompositionFor(nsPresContext* aPresContext); + TextComposition* GetCompositionFor(nsPresContext* aPresContext, + nsINode* aNode); + TextComposition* GetCompositionInContent(nsPresContext* aPresContext, + nsIContent* aContent); +}; + +} // namespace mozilla + +#endif // #ifndef mozilla_TextComposition_h |