Mypal/dom/events/TextComposition.h

490 lines
17 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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