1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_TextComposition_h
8 #define mozilla_TextComposition_h
12 #include "nsIWidget.h"
14 #include "nsThreadUtils.h"
15 #include "nsPresContext.h"
16 #include "mozilla/AlreadyAddRefed.h"
17 #include "mozilla/Attributes.h"
18 #include "mozilla/EventForwards.h"
19 #include "mozilla/RangeBoundary.h"
20 #include "mozilla/TextRange.h"
21 #include "mozilla/dom/BrowserParent.h"
22 #include "mozilla/dom/Text.h"
26 struct CharacterDataChangeInfo
;
31 class EventDispatchingCallback
;
32 class IMEStateManager
;
35 * TextComposition represents a text composition. This class stores the
36 * composition event target and its presContext. At dispatching the event via
37 * this class, the instances use the stored event target.
40 class TextComposition final
{
41 friend class IMEStateManager
;
43 NS_INLINE_DECL_REFCOUNTING(TextComposition
)
46 typedef dom::BrowserParent BrowserParent
;
47 typedef dom::Text Text
;
49 static bool IsHandlingSelectionEvent() { return sHandlingSelectionEvent
; }
51 TextComposition(nsPresContext
* aPresContext
, nsINode
* aNode
,
52 BrowserParent
* aBrowserParent
,
53 WidgetCompositionEvent
* aCompositionEvent
);
55 bool Destroyed() const { return !mPresContext
; }
56 nsPresContext
* GetPresContext() const { return mPresContext
; }
57 nsINode
* GetEventTargetNode() const { return mNode
; }
58 // The text node which includes composition string.
59 Text
* GetContainerTextNode() const { return mContainerTextNode
; }
60 // The latest CompositionEvent.data value except compositionstart event.
61 // This value is modified at dispatching compositionupdate.
62 const nsString
& LastData() const { return mLastData
; }
63 // Returns commit string if it'll be commited as-is.
64 nsString
CommitStringIfCommittedAsIs() const;
65 // The composition string which is already handled by the focused editor.
66 // I.e., this value must be same as the composition string on the focused
67 // editor. This value is modified at a call of
68 // EditorDidHandleCompositionChangeEvent().
69 // Note that mString and mLastData are different between dispatcing
70 // compositionupdate and compositionchange event handled by focused editor.
71 const nsString
& String() const { return mString
; }
72 // The latest clauses range of the composition string.
73 // During compositionupdate event, GetRanges() returns old ranges.
74 // So if getting on compositionupdate, Use GetLastRange instead of GetRange().
75 TextRangeArray
* GetLastRanges() const { return mLastRanges
; }
76 // Returns the clauses and/or caret range of the composition string.
77 // This is modified at a call of EditorWillHandleCompositionChangeEvent().
78 // This may return null if there is no clauses and caret.
79 // XXX We should return |const TextRangeArray*| here, but it causes compile
80 // error due to inaccessible Release() method.
81 TextRangeArray
* GetRanges() const { return mRanges
; }
82 // Returns the widget which is proper to call NotifyIME().
83 already_AddRefed
<nsIWidget
> GetWidget() const {
87 return do_AddRef(mPresContext
->GetRootWidget());
89 // Returns the tab parent which has this composition in its remote process.
90 BrowserParent
* GetBrowserParent() const { return mBrowserParent
; }
91 // Returns true if the composition is started with synthesized event which
92 // came from nsDOMWindowUtils.
93 bool IsSynthesizedForTests() const { return mIsSynthesizedForTests
; }
95 const widget::NativeIMEContext
& GetNativeIMEContext() const {
96 return mNativeContext
;
100 * This is called when IMEStateManager stops managing the instance.
105 * Request to commit (or cancel) the composition to IME. This method should
106 * be called only by IMEStateManager::NotifyIME().
108 nsresult
RequestToCommit(nsIWidget
* aWidget
, bool aDiscard
);
111 * IsRequestingCommitOrCancelComposition() returns true if the instance is
112 * requesting widget to commit or cancel composition.
114 bool IsRequestingCommitOrCancelComposition() const {
115 return mIsRequestingCancel
|| mIsRequestingCommit
;
119 * Send a notification to IME. It depends on the IME or platform spec what
120 * will occur (or not occur).
122 nsresult
NotifyIME(widget::IMEMessage aMessage
);
125 * the offset of first composition string
127 uint32_t NativeOffsetOfStartComposition() const {
128 return mCompositionStartOffset
;
132 * the offset of first selected clause or start of composition
134 uint32_t NativeOffsetOfTargetClause() const {
135 return mCompositionStartOffset
+ mTargetClauseOffsetInComposition
;
139 * Return current composition start and end point in the DOM tree.
140 * Note that one of or both of those result container may be different
141 * from GetContainerTextNode() if the DOM tree was modified by the web
142 * app. If there is no composition string the DOM tree, these return
143 * unset range boundaries.
145 RawRangeBoundary
FirstIMESelectionStartRef() const;
146 RawRangeBoundary
LastIMESelectionEndRef() const;
149 * The offset of composition string in the text node. If composition string
150 * hasn't been inserted in any text node yet, this returns UINT32_MAX.
152 uint32_t XPOffsetInTextNode() const {
153 return mCompositionStartOffsetInTextNode
;
157 * The length of composition string in the text node. If composition string
158 * hasn't been inserted in any text node yet, this returns 0.
160 uint32_t XPLengthInTextNode() const {
161 return mCompositionLengthInTextNode
== UINT32_MAX
163 : mCompositionLengthInTextNode
;
167 * The end offset of composition string in the text node. If composition
168 * string hasn't been inserted in any text node yet, this returns UINT32_MAX.
170 uint32_t XPEndOffsetInTextNode() const {
171 if (mCompositionStartOffsetInTextNode
== UINT32_MAX
||
172 mCompositionLengthInTextNode
== UINT32_MAX
) {
175 return mCompositionStartOffsetInTextNode
+ mCompositionLengthInTextNode
;
179 * Returns true if there is non-empty composition string and it's not fixed.
182 bool IsComposing() const { return mIsComposing
; }
185 * Returns true while editor is handling an event which is modifying the
186 * composition string.
188 bool IsEditorHandlingEvent() const { return mIsEditorHandlingEvent
; }
191 * IsMovingToNewTextNode() returns true if editor detects the text node
192 * has been removed and still not insert the composition string into
195 bool IsMovingToNewTextNode() const {
196 return !mContainerTextNode
&& mCompositionLengthInTextNode
&&
197 mCompositionLengthInTextNode
!= UINT32_MAX
;
201 * StartHandlingComposition() and EndHandlingComposition() are called by
202 * editor when it holds a TextComposition instance and release it.
204 void StartHandlingComposition(EditorBase
* aEditorBase
);
205 void EndHandlingComposition(EditorBase
* aEditorBase
);
208 * OnEditorDestroyed() is called when the editor is destroyed but there is
209 * active composition.
211 void OnEditorDestroyed();
214 * CompositionChangeEventHandlingMarker class should be created at starting
215 * to handle text event in focused editor. This calls
216 * EditorWillHandleCompositionChangeEvent() and
217 * EditorDidHandleCompositionChangeEvent() automatically.
219 class MOZ_STACK_CLASS CompositionChangeEventHandlingMarker
{
221 CompositionChangeEventHandlingMarker(
222 TextComposition
* aComposition
,
223 const WidgetCompositionEvent
* aCompositionChangeEvent
)
224 : mComposition(aComposition
) {
225 mComposition
->EditorWillHandleCompositionChangeEvent(
226 aCompositionChangeEvent
);
229 ~CompositionChangeEventHandlingMarker() {
230 mComposition
->EditorDidHandleCompositionChangeEvent();
234 RefPtr
<TextComposition
> mComposition
;
235 CompositionChangeEventHandlingMarker();
236 CompositionChangeEventHandlingMarker(
237 const CompositionChangeEventHandlingMarker
& aOther
);
241 * OnUpdateCompositionInEditor() is called when editor updates composition
242 * string in the DOM tree.
244 * @param aStringToInsert The string to insert the text node actually.
245 * This may be different from the data of
246 * dispatching composition event because it may
247 * be replaced with different character for
248 * passwords, or truncated due to maxlength.
249 * @param aTextNode The text node which includes composition string.
250 * @param aOffset The offset of composition string in aTextNode.
252 void OnUpdateCompositionInEditor(const nsAString
& aStringToInsert
,
253 Text
& aTextNode
, uint32_t aOffset
) {
254 mContainerTextNode
= &aTextNode
;
255 mCompositionStartOffsetInTextNode
= aOffset
;
256 NS_WARNING_ASSERTION(mCompositionStartOffsetInTextNode
!= UINT32_MAX
,
257 "The text node is really too long.");
258 mCompositionLengthInTextNode
= aStringToInsert
.Length();
259 NS_WARNING_ASSERTION(mCompositionLengthInTextNode
!= UINT32_MAX
,
260 "The string to insert is really too long.");
264 * OnTextNodeRemoved() is called when focused editor is reframed and
265 * mContainerTextNode may be (or have been) replaced with different text
266 * node, or just removes the text node due to empty.
268 void OnTextNodeRemoved() {
269 mContainerTextNode
= nullptr;
270 // Don't reset mCompositionStartOffsetInTextNode nor
271 // mCompositionLengthInTextNode because editor needs them to restore
272 // composition in new text node.
276 * OnCharacterDataChanged() is called when IMEContentObserver receives
277 * character data change notifications.
279 void OnCharacterDataChanged(Text
& aText
,
280 const CharacterDataChangeInfo
& aInfo
);
283 // Private destructor, to discourage deletion outside of Release():
285 // WARNING: mPresContext may be destroying, so, be careful if you touch it.
288 // sHandlingSelectionEvent is true while TextComposition sends a selection
289 // event to ContentEventHandler.
290 static bool sHandlingSelectionEvent
;
292 // This class holds nsPresContext weak. This instance shouldn't block
293 // destroying it. When the presContext is being destroyed, it's notified to
294 // IMEStateManager::OnDestroyPresContext(), and then, it destroy
296 nsPresContext
* mPresContext
;
297 RefPtr
<nsINode
> mNode
;
298 RefPtr
<BrowserParent
> mBrowserParent
;
300 // The text node which includes the composition string.
301 RefPtr
<Text
> mContainerTextNode
;
303 // This is the clause and caret range information which is managed by
304 // the focused editor. This may be null if there is no clauses or caret.
305 RefPtr
<TextRangeArray
> mRanges
;
306 // Same as mRange, but mRange will have old data during compositionupdate.
307 // So this will be valied during compositionupdate.
308 RefPtr
<TextRangeArray
> mLastRanges
;
310 // mNativeContext stores a opaque pointer. This works as the "ID" for this
311 // composition. Don't access the instance, it may not be available.
312 widget::NativeIMEContext mNativeContext
;
314 // mEditorBaseWeak is a weak reference to the focused editor handling
316 nsWeakPtr mEditorBaseWeak
;
318 // mLastData stores the data attribute of the latest composition event (except
319 // the compositionstart event).
322 // mString stores the composition text which has been handled by the focused
326 // Offset of the composition string from start of the editor
327 uint32_t mCompositionStartOffset
;
328 // Offset of the selected clause of the composition string from
329 // mCompositionStartOffset
330 uint32_t mTargetClauseOffsetInComposition
;
331 // Offset of the composition string in mContainerTextNode.
332 // NOTE: This is NOT valid in the main process if focused editor is in a
334 uint32_t mCompositionStartOffsetInTextNode
;
335 // Length of the composition string in mContainerTextNode. If this instance
336 // has already dispatched eCompositionCommit(AsIs) and
337 // EditorDidHandleCompositionChangeEvent() has already been called,
338 // this may be different from length of mString because committed string
339 // may be truncated by maxlength attribute of <input> or <textarea>.
340 // NOTE: This is NOT valid in the main process if focused editor is in a
342 uint32_t mCompositionLengthInTextNode
;
344 // See the comment for IsSynthesizedForTests().
345 bool mIsSynthesizedForTests
;
347 // See the comment for IsComposing().
350 // mIsEditorHandlingEvent is true while editor is modifying the composition
352 bool mIsEditorHandlingEvent
;
354 // mIsRequestingCommit or mIsRequestingCancel is true *only* while we're
355 // requesting commit or canceling the composition. In other words, while
356 // one of these values is true, we're handling the request.
357 bool mIsRequestingCommit
;
358 bool mIsRequestingCancel
;
360 // mRequestedToCommitOrCancel is true *after* we requested IME to commit or
361 // cancel the composition. In other words, we already requested of IME that
362 // it commits or cancels current composition.
363 // NOTE: Before this is set to true, both mIsRequestingCommit and
364 // mIsRequestingCancel are set to false.
365 bool mRequestedToCommitOrCancel
;
367 // Set to true if the instance dispatches an eCompositionChange event.
368 bool mHasDispatchedDOMTextEvent
;
370 // Before this dispatches commit event into the tree, this is set to true.
371 // So, this means if native IME already commits the composition.
372 bool mHasReceivedCommitEvent
;
374 // mWasNativeCompositionEndEventDiscarded is true if this composition was
375 // requested commit or cancel itself but native compositionend event is
376 // discarded by PresShell due to not safe to dispatch events.
377 bool mWasNativeCompositionEndEventDiscarded
;
379 // Allow control characters appear in composition string.
380 // When this is false, control characters except
381 // CHARACTER TABULATION (horizontal tab) are removed from
382 // both composition string and data attribute of compositionupdate
383 // and compositionend events.
384 bool mAllowControlCharacters
;
386 // mWasCompositionStringEmpty is true if the composition string was empty
387 // when DispatchCompositionEvent() is called.
388 bool mWasCompositionStringEmpty
;
390 // Hide the default constructor and copy constructor.
392 : mPresContext(nullptr),
393 mNativeContext(nullptr),
394 mCompositionStartOffset(0),
395 mTargetClauseOffsetInComposition(0),
396 mCompositionStartOffsetInTextNode(UINT32_MAX
),
397 mCompositionLengthInTextNode(UINT32_MAX
),
398 mIsSynthesizedForTests(false),
400 mIsEditorHandlingEvent(false),
401 mIsRequestingCommit(false),
402 mIsRequestingCancel(false),
403 mRequestedToCommitOrCancel(false),
404 mHasReceivedCommitEvent(false),
405 mWasNativeCompositionEndEventDiscarded(false),
406 mAllowControlCharacters(false),
407 mWasCompositionStringEmpty(true) {}
408 TextComposition(const TextComposition
& aOther
);
411 * If we're requesting IME to commit or cancel composition, or we've already
412 * requested it, or we've already known this composition has been ended in
413 * IME, we don't need to request commit nor cancel composition anymore and
414 * shouldn't do so if we're in content process for not committing/canceling
415 * "current" composition in native IME. So, when this returns true,
416 * RequestIMEToCommit() does nothing.
418 bool CanRequsetIMEToCommitOrCancelComposition() const {
419 return !mIsRequestingCommit
&& !mIsRequestingCancel
&&
420 !mRequestedToCommitOrCancel
&& !mHasReceivedCommitEvent
;
424 * GetEditorBase() returns EditorBase pointer of mEditorBaseWeak.
426 already_AddRefed
<EditorBase
> GetEditorBase() const;
429 * HasEditor() returns true if mEditorBaseWeak holds EditorBase instance
430 * which is alive. Otherwise, false.
432 bool HasEditor() const;
435 * EditorWillHandleCompositionChangeEvent() must be called before the focused
436 * editor handles the compositionchange event.
438 void EditorWillHandleCompositionChangeEvent(
439 const WidgetCompositionEvent
* aCompositionChangeEvent
);
442 * EditorDidHandleCompositionChangeEvent() must be called after the focused
443 * editor handles a compositionchange event.
445 void EditorDidHandleCompositionChangeEvent();
448 * IsValidStateForComposition() returns true if it's safe to dispatch an event
449 * to the DOM tree. Otherwise, false.
450 * WARNING: This doesn't check script blocker state. It should be checked
451 * before dispatching the first event.
453 bool IsValidStateForComposition(nsIWidget
* aWidget
) const;
456 * DispatchCompositionEvent() dispatches the aCompositionEvent to the mContent
457 * synchronously. The caller must ensure that it's safe to dispatch the event.
459 MOZ_CAN_RUN_SCRIPT
void DispatchCompositionEvent(
460 WidgetCompositionEvent
* aCompositionEvent
, nsEventStatus
* aStatus
,
461 EventDispatchingCallback
* aCallBack
, bool aIsSynthesized
);
464 * Simply calling EventDispatcher::Dispatch() with plugin event.
465 * If dispatching event has no orginal clone, aOriginalEvent can be null.
467 MOZ_CAN_RUN_SCRIPT
void DispatchEvent(
468 WidgetCompositionEvent
* aDispatchEvent
, nsEventStatus
* aStatus
,
469 EventDispatchingCallback
* aCallback
,
470 const WidgetCompositionEvent
* aOriginalEvent
= nullptr);
473 * HandleSelectionEvent() sends the selection event to ContentEventHandler
474 * or dispatches it to the focused child process.
477 void HandleSelectionEvent(WidgetSelectionEvent
* aSelectionEvent
) {
478 RefPtr
<nsPresContext
> presContext(mPresContext
);
479 RefPtr
<BrowserParent
> browserParent(mBrowserParent
);
480 HandleSelectionEvent(presContext
, browserParent
, aSelectionEvent
);
483 static void HandleSelectionEvent(nsPresContext
* aPresContext
,
484 BrowserParent
* aBrowserParent
,
485 WidgetSelectionEvent
* aSelectionEvent
);
488 * MaybeDispatchCompositionUpdate() may dispatch a compositionupdate event
489 * if aCompositionEvent changes composition string.
490 * @return Returns false if dispatching the compositionupdate event caused
491 * destroying this composition.
493 MOZ_CAN_RUN_SCRIPT
bool MaybeDispatchCompositionUpdate(
494 const WidgetCompositionEvent
* aCompositionEvent
);
497 * CloneAndDispatchAs() dispatches a composition event which is
498 * duplicateed from aCompositionEvent and set the aMessage.
500 * @return Returns BaseEventFlags which is the result of dispatched event.
502 MOZ_CAN_RUN_SCRIPT BaseEventFlags
503 CloneAndDispatchAs(const WidgetCompositionEvent
* aCompositionEvent
,
504 EventMessage aMessage
, nsEventStatus
* aStatus
= nullptr,
505 EventDispatchingCallback
* aCallBack
= nullptr);
508 * If IME has already dispatched compositionend event but it was discarded
509 * by PresShell due to not safe to dispatch, this returns true.
511 bool WasNativeCompositionEndEventDiscarded() const {
512 return mWasNativeCompositionEndEventDiscarded
;
516 * OnCompositionEventDiscarded() is called when PresShell discards
517 * compositionupdate, compositionend or compositionchange event due to not
518 * safe to dispatch event.
520 void OnCompositionEventDiscarded(WidgetCompositionEvent
* aCompositionEvent
);
523 * OnCompositionEventDispatched() is called after a composition event is
526 MOZ_CAN_RUN_SCRIPT
void OnCompositionEventDispatched(
527 const WidgetCompositionEvent
* aDispatchEvent
);
530 * MaybeNotifyIMEOfCompositionEventHandled() notifies IME of composition
531 * event handled. This should be called after dispatching a composition
532 * event which came from widget.
534 void MaybeNotifyIMEOfCompositionEventHandled(
535 const WidgetCompositionEvent
* aCompositionEvent
);
538 * GetSelectionStartOffset() returns normal selection start offset in the
539 * editor which has this composition.
540 * If it failed or lost focus, this would return 0.
542 MOZ_CAN_RUN_SCRIPT
uint32_t GetSelectionStartOffset();
545 * OnStartOffsetUpdatedInChild() is called when composition start offset
546 * is updated in the child process. I.e., this is called and never called
547 * if the composition is in this process.
548 * @param aStartOffset New composition start offset with native
551 void OnStartOffsetUpdatedInChild(uint32_t aStartOffset
);
554 * CompositionEventDispatcher dispatches the specified composition (or text)
557 class CompositionEventDispatcher
: public Runnable
{
559 CompositionEventDispatcher(TextComposition
* aTextComposition
,
560 nsINode
* aEventTarget
,
561 EventMessage aEventMessage
,
562 const nsAString
& aData
,
563 bool aIsSynthesizedEvent
= false);
564 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
;
567 RefPtr
<TextComposition
> mTextComposition
;
568 nsCOMPtr
<nsINode
> mEventTarget
;
570 EventMessage mEventMessage
;
571 bool mIsSynthesizedEvent
;
573 CompositionEventDispatcher()
574 : Runnable("TextComposition::CompositionEventDispatcher"),
575 mEventMessage(eVoidEvent
),
576 mIsSynthesizedEvent(false){};
580 * DispatchCompositionEventRunnable() dispatches a composition event to the
581 * content. Be aware, if you use this method, nsPresShellEventCB isn't used.
582 * That means that nsIFrame::HandleEvent() is never called.
583 * WARNING: The instance which is managed by IMEStateManager may be
584 * destroyed by this method call.
586 * @param aEventMessage Must be one of composition events.
587 * @param aData Used for mData value.
588 * @param aIsSynthesizingCommit true if this is called for synthesizing
589 * commit or cancel composition. Otherwise,
592 void DispatchCompositionEventRunnable(EventMessage aEventMessage
,
593 const nsAString
& aData
,
594 bool aIsSynthesizingCommit
= false);
598 * TextCompositionArray manages the instances of TextComposition class.
599 * Managing with array is enough because only one composition is typically
600 * there. Even if user switches native IME context, it's very rare that
601 * second or more composition is started.
602 * It's assumed that this is used by IMEStateManager for storing all active
603 * compositions in the process. If the instance is it, each TextComposition
604 * in the array can be destroyed by calling some methods of itself.
607 class TextCompositionArray final
608 : public AutoTArray
<RefPtr
<TextComposition
>, 2> {
610 // Looking for per native IME context.
611 index_type
IndexOf(const widget::NativeIMEContext
& aNativeIMEContext
);
612 index_type
IndexOf(nsIWidget
* aWidget
);
614 TextComposition
* GetCompositionFor(nsIWidget
* aWidget
);
615 TextComposition
* GetCompositionFor(
616 const WidgetCompositionEvent
* aCompositionEvent
);
618 // Looking for per nsPresContext
619 index_type
IndexOf(nsPresContext
* aPresContext
);
620 index_type
IndexOf(nsPresContext
* aPresContext
, nsINode
* aNode
);
622 TextComposition
* GetCompositionFor(nsPresContext
* aPresContext
);
623 TextComposition
* GetCompositionFor(nsPresContext
* aPresContext
,
625 TextComposition
* GetCompositionInContent(nsPresContext
* aPresContext
,
626 nsIContent
* aContent
);
629 } // namespace mozilla
631 #endif // #ifndef mozilla_TextComposition_h