summaryrefslogtreecommitdiffstats
path: root/dom/events/TextComposition.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/events/TextComposition.h')
-rw-r--r--dom/events/TextComposition.h490
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