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/Assertions.h"
18 #include "mozilla/Attributes.h"
19 #include "mozilla/EventForwards.h"
20 #include "mozilla/RangeBoundary.h"
21 #include "mozilla/TextRange.h"
22 #include "mozilla/dom/BrowserParent.h"
23 #include "mozilla/dom/Text.h"
27 struct CharacterDataChangeInfo
;
32 class EventDispatchingCallback
;
33 class IMEStateManager
;
36 * TextComposition represents a text composition. This class stores the
37 * composition event target and its presContext. At dispatching the event via
38 * this class, the instances use the stored event target.
41 class TextComposition final
{
42 friend class IMEStateManager
;
44 NS_INLINE_DECL_REFCOUNTING(TextComposition
)
47 typedef dom::BrowserParent BrowserParent
;
48 typedef dom::Text Text
;
50 static bool IsHandlingSelectionEvent() { return sHandlingSelectionEvent
; }
52 TextComposition(nsPresContext
* aPresContext
, nsINode
* aNode
,
53 BrowserParent
* aBrowserParent
,
54 WidgetCompositionEvent
* aCompositionEvent
);
55 TextComposition() = delete;
56 TextComposition(const TextComposition
& aOther
) = delete;
58 bool Destroyed() const { return !mPresContext
; }
59 nsPresContext
* GetPresContext() const { return mPresContext
; }
60 nsINode
* GetEventTargetNode() const { return mNode
; }
61 // The text node which includes composition string.
62 Text
* GetContainerTextNode() const { return mContainerTextNode
; }
63 // The latest CompositionEvent.data value except compositionstart event.
64 // This value is modified at dispatching compositionupdate.
65 const nsString
& LastData() const { return mLastData
; }
66 // Returns commit string if it'll be commited as-is.
67 nsString
CommitStringIfCommittedAsIs() const;
68 // The composition string which is already handled by the focused editor.
69 // I.e., this value must be same as the composition string on the focused
70 // editor. This value is modified at a call of
71 // EditorDidHandleCompositionChangeEvent().
72 // Note that mString and mLastData are different between dispatcing
73 // compositionupdate and compositionchange event handled by focused editor.
74 const nsString
& String() const { return mString
; }
75 // The latest clauses range of the composition string.
76 // During compositionupdate event, GetRanges() returns old ranges.
77 // So if getting on compositionupdate, Use GetLastRange instead of GetRange().
78 TextRangeArray
* GetLastRanges() const { return mLastRanges
; }
79 // Returns the clauses and/or caret range of the composition string.
80 // This is modified at a call of EditorWillHandleCompositionChangeEvent().
81 // This may return null if there is no clauses and caret.
82 // XXX We should return |const TextRangeArray*| here, but it causes compile
83 // error due to inaccessible Release() method.
84 TextRangeArray
* GetRanges() const { return mRanges
; }
85 // Returns the widget which is proper to call NotifyIME().
86 already_AddRefed
<nsIWidget
> GetWidget() const {
90 return do_AddRef(mPresContext
->GetRootWidget());
92 // Returns the tab parent which has this composition in its remote process.
93 BrowserParent
* GetBrowserParent() const { return mBrowserParent
; }
94 // Returns true if the composition is started with synthesized event which
95 // came from nsDOMWindowUtils.
96 bool IsSynthesizedForTests() const { return mIsSynthesizedForTests
; }
98 // Returns the composition ID. It must be 0 if the composition is synthesized
99 // in a content process. Otherwise, returns 1 or larger value.
100 uint32_t Id() const { return mCompositionId
; }
102 const widget::NativeIMEContext
& GetNativeIMEContext() const {
103 return mNativeContext
;
107 * This is called when IMEStateManager stops managing the instance.
112 * Request to commit (or cancel) the composition to IME. This method should
113 * be called only by IMEStateManager::NotifyIME().
115 nsresult
RequestToCommit(nsIWidget
* aWidget
, bool aDiscard
);
118 * IsRequestingCommitOrCancelComposition() returns true if the instance is
119 * requesting widget to commit or cancel composition.
121 bool IsRequestingCommitOrCancelComposition() const {
122 return mIsRequestingCancel
|| mIsRequestingCommit
;
126 * Send a notification to IME. It depends on the IME or platform spec what
127 * will occur (or not occur).
129 nsresult
NotifyIME(widget::IMEMessage aMessage
);
132 * the offset of first composition string
134 uint32_t NativeOffsetOfStartComposition() const {
135 return mCompositionStartOffset
;
139 * the offset of first selected clause or start of composition
141 uint32_t NativeOffsetOfTargetClause() const {
142 return mCompositionStartOffset
+ mTargetClauseOffsetInComposition
;
146 * Return current composition start and end point in the DOM tree.
147 * Note that one of or both of those result container may be different
148 * from GetContainerTextNode() if the DOM tree was modified by the web
149 * app. If there is no composition string the DOM tree, these return
150 * unset range boundaries.
152 RawRangeBoundary
FirstIMESelectionStartRef() const;
153 RawRangeBoundary
LastIMESelectionEndRef() const;
156 * The offset of composition string in the text node. If composition string
157 * hasn't been inserted in any text node yet, this returns UINT32_MAX.
159 uint32_t XPOffsetInTextNode() const {
160 return mCompositionStartOffsetInTextNode
;
164 * The length of composition string in the text node. If composition string
165 * hasn't been inserted in any text node yet, this returns 0.
167 uint32_t XPLengthInTextNode() const {
168 return mCompositionLengthInTextNode
== UINT32_MAX
170 : mCompositionLengthInTextNode
;
174 * The end offset of composition string in the text node. If composition
175 * string hasn't been inserted in any text node yet, this returns UINT32_MAX.
177 uint32_t XPEndOffsetInTextNode() const {
178 if (mCompositionStartOffsetInTextNode
== UINT32_MAX
||
179 mCompositionLengthInTextNode
== UINT32_MAX
) {
182 return mCompositionStartOffsetInTextNode
+ mCompositionLengthInTextNode
;
186 * Returns true if there is non-empty composition string and it's not fixed.
189 bool IsComposing() const { return mIsComposing
; }
192 * Returns true if editor has started or already ended handling an event which
193 * is modifying the composition string and/or IME selections.
195 [[nodiscard
]] bool EditorHasHandledLatestChange() const {
196 return EditorIsHandlingLatestChange() ||
197 (mLastRanges
== mRanges
&& mLastData
== mString
);
201 * Returns true while editor is handling an event which is modifying the
202 * composition string and/or IME selections.
204 [[nodiscard
]] bool EditorIsHandlingLatestChange() const {
205 return mEditorIsHandlingEvent
;
209 * IsMovingToNewTextNode() returns true if editor detects the text node
210 * has been removed and still not insert the composition string into
213 bool IsMovingToNewTextNode() const {
214 return !mContainerTextNode
&& mCompositionLengthInTextNode
&&
215 mCompositionLengthInTextNode
!= UINT32_MAX
;
219 * StartHandlingComposition() and EndHandlingComposition() are called by
220 * editor when it holds a TextComposition instance and release it.
222 void StartHandlingComposition(EditorBase
* aEditorBase
);
223 void EndHandlingComposition(EditorBase
* aEditorBase
);
226 * OnEditorDestroyed() is called when the editor is destroyed but there is
227 * active composition.
229 void OnEditorDestroyed();
232 * CompositionChangeEventHandlingMarker class should be created at starting
233 * to handle text event in focused editor. This calls
234 * EditorWillHandleCompositionChangeEvent() and
235 * EditorDidHandleCompositionChangeEvent() automatically.
237 class MOZ_STACK_CLASS CompositionChangeEventHandlingMarker
{
239 CompositionChangeEventHandlingMarker(
240 TextComposition
* aComposition
,
241 const WidgetCompositionEvent
* aCompositionChangeEvent
)
242 : mComposition(aComposition
) {
243 mComposition
->EditorWillHandleCompositionChangeEvent(
244 aCompositionChangeEvent
);
247 ~CompositionChangeEventHandlingMarker() {
248 mComposition
->EditorDidHandleCompositionChangeEvent();
252 RefPtr
<TextComposition
> mComposition
;
253 CompositionChangeEventHandlingMarker();
254 CompositionChangeEventHandlingMarker(
255 const CompositionChangeEventHandlingMarker
& aOther
);
259 * OnUpdateCompositionInEditor() is called when editor updates composition
260 * string in the DOM tree.
262 * @param aStringToInsert The string to insert the text node actually.
263 * This may be different from the data of
264 * dispatching composition event because it may
265 * be replaced with different character for
266 * passwords, or truncated due to maxlength.
267 * @param aTextNode The text node which includes composition string.
268 * @param aOffset The offset of composition string in aTextNode.
270 void OnUpdateCompositionInEditor(const nsAString
& aStringToInsert
,
271 Text
& aTextNode
, uint32_t aOffset
) {
272 mContainerTextNode
= &aTextNode
;
273 mCompositionStartOffsetInTextNode
= aOffset
;
274 NS_WARNING_ASSERTION(mCompositionStartOffsetInTextNode
!= UINT32_MAX
,
275 "The text node is really too long.");
276 mCompositionLengthInTextNode
= aStringToInsert
.Length();
277 NS_WARNING_ASSERTION(mCompositionLengthInTextNode
!= UINT32_MAX
,
278 "The string to insert is really too long.");
282 * OnTextNodeRemoved() is called when focused editor is reframed and
283 * mContainerTextNode may be (or have been) replaced with different text
284 * node, or just removes the text node due to empty.
286 void OnTextNodeRemoved() {
287 mContainerTextNode
= nullptr;
288 // Don't reset mCompositionStartOffsetInTextNode nor
289 // mCompositionLengthInTextNode because editor needs them to restore
290 // composition in new text node.
294 * OnCharacterDataChanged() is called when IMEContentObserver receives
295 * character data change notifications.
297 void OnCharacterDataChanged(Text
& aText
,
298 const CharacterDataChangeInfo
& aInfo
);
301 // Private destructor, to discourage deletion outside of Release():
303 // WARNING: mPresContext may be destroying, so, be careful if you touch it.
306 // sHandlingSelectionEvent is true while TextComposition sends a selection
307 // event to ContentEventHandler.
308 static bool sHandlingSelectionEvent
;
310 // This class holds nsPresContext weak. This instance shouldn't block
311 // destroying it. When the presContext is being destroyed, it's notified to
312 // IMEStateManager::OnDestroyPresContext(), and then, it destroy
314 nsPresContext
* mPresContext
;
315 RefPtr
<nsINode
> mNode
;
316 RefPtr
<BrowserParent
> mBrowserParent
;
318 // The text node which includes the composition string.
319 RefPtr
<Text
> mContainerTextNode
;
321 // This is the clause and caret range information which is managed by
322 // the focused editor. This may be null if there is no clauses or caret.
323 RefPtr
<TextRangeArray
> mRanges
;
324 // Same as mRange, but mRange will have old ranges before editor starts
325 // handling the latest eCompositionChange. Therefore, this stores the latest
326 // ranges which is introduced by the latest eCompositionChange. So this may
327 // be useful during dispatching eCompositionUpdate or eCompositionChange.
328 RefPtr
<TextRangeArray
> mLastRanges
;
330 // mNativeContext stores a opaque pointer. This works as the "ID" for this
331 // composition. Don't access the instance, it may not be available.
332 widget::NativeIMEContext mNativeContext
;
334 // mEditorBaseWeak is a weak reference to the focused editor handling
336 nsWeakPtr mEditorBaseWeak
;
338 // mLastData stores the data attribute of the latest composition event (except
339 // the compositionstart event).
342 // mString stores the composition text which has been handled by the focused
346 // Composition ID of this composition. If this is in a parent process,
347 // this is 1 or larger. If the composition is created for managing a
348 // composition synthesized in a content process, this is 0.
349 const uint32_t mCompositionId
= 0;
351 // Offset of the composition string from start of the editor
352 uint32_t mCompositionStartOffset
;
353 // Offset of the selected clause of the composition string from
354 // mCompositionStartOffset
355 uint32_t mTargetClauseOffsetInComposition
;
356 // Offset of the composition string in mContainerTextNode.
357 // NOTE: This is NOT valid in the main process if focused editor is in a
359 uint32_t mCompositionStartOffsetInTextNode
;
360 // Length of the composition string in mContainerTextNode. If this instance
361 // has already dispatched eCompositionCommit(AsIs) and
362 // EditorDidHandleCompositionChangeEvent() has already been called,
363 // this may be different from length of mString because committed string
364 // may be truncated by maxlength attribute of <input> or <textarea>.
365 // NOTE: This is NOT valid in the main process if focused editor is in a
367 uint32_t mCompositionLengthInTextNode
;
369 // See the comment for IsSynthesizedForTests().
370 bool mIsSynthesizedForTests
;
372 // See the comment for IsComposing().
375 // mEditorIsHandlingEvent is true while editor is modifying the composition
377 bool mEditorIsHandlingEvent
= false;
379 // mIsRequestingCommit or mIsRequestingCancel is true *only* while we're
380 // requesting commit or canceling the composition. In other words, while
381 // one of these values is true, we're handling the request.
382 bool mIsRequestingCommit
;
383 bool mIsRequestingCancel
;
385 // mRequestedToCommitOrCancel is true *after* we requested IME to commit or
386 // cancel the composition. In other words, we already requested of IME that
387 // it commits or cancels current composition.
388 // NOTE: Before this is set to true, both mIsRequestingCommit and
389 // mIsRequestingCancel are set to false.
390 bool mRequestedToCommitOrCancel
;
392 // Set to true if the instance dispatches an eCompositionChange event.
393 bool mHasDispatchedDOMTextEvent
;
395 // Before this dispatches commit event into the tree, this is set to true.
396 // So, this means if native IME already commits the composition.
397 bool mHasReceivedCommitEvent
;
399 // mWasNativeCompositionEndEventDiscarded is true if this composition was
400 // requested commit or cancel itself but native compositionend event is
401 // discarded by PresShell due to not safe to dispatch events.
402 bool mWasNativeCompositionEndEventDiscarded
;
404 // Allow control characters appear in composition string.
405 // When this is false, control characters except
406 // CHARACTER TABULATION (horizontal tab) are removed from
407 // both composition string and data attribute of compositionupdate
408 // and compositionend events.
409 bool mAllowControlCharacters
;
411 // mWasCompositionStringEmpty is true if the composition string was empty
412 // when DispatchCompositionEvent() is called.
413 bool mWasCompositionStringEmpty
;
416 * If we're requesting IME to commit or cancel composition, or we've already
417 * requested it, or we've already known this composition has been ended in
418 * IME, we don't need to request commit nor cancel composition anymore and
419 * shouldn't do so if we're in content process for not committing/canceling
420 * "current" composition in native IME. So, when this returns true,
421 * RequestIMEToCommit() does nothing.
423 bool CanRequsetIMEToCommitOrCancelComposition() const {
424 return !mIsRequestingCommit
&& !mIsRequestingCancel
&&
425 !mRequestedToCommitOrCancel
&& !mHasReceivedCommitEvent
;
429 * GetEditorBase() returns EditorBase pointer of mEditorBaseWeak.
431 already_AddRefed
<EditorBase
> GetEditorBase() const;
434 * HasEditor() returns true if mEditorBaseWeak holds EditorBase instance
435 * which is alive. Otherwise, false.
437 bool HasEditor() const;
440 * EditorWillHandleCompositionChangeEvent() must be called before the focused
441 * editor handles the compositionchange event.
443 void EditorWillHandleCompositionChangeEvent(
444 const WidgetCompositionEvent
* aCompositionChangeEvent
);
447 * EditorDidHandleCompositionChangeEvent() must be called after the focused
448 * editor handles a compositionchange event.
450 void EditorDidHandleCompositionChangeEvent();
453 * IsValidStateForComposition() returns true if it's safe to dispatch an event
454 * to the DOM tree. Otherwise, false.
455 * WARNING: This doesn't check script blocker state. It should be checked
456 * before dispatching the first event.
458 bool IsValidStateForComposition(nsIWidget
* aWidget
) const;
461 * DispatchCompositionEvent() dispatches the aCompositionEvent to the mContent
462 * synchronously. The caller must ensure that it's safe to dispatch the event.
464 MOZ_CAN_RUN_SCRIPT
void DispatchCompositionEvent(
465 WidgetCompositionEvent
* aCompositionEvent
, nsEventStatus
* aStatus
,
466 EventDispatchingCallback
* aCallBack
, bool aIsSynthesized
);
469 * Simply calling EventDispatcher::Dispatch() with plugin event.
470 * If dispatching event has no orginal clone, aOriginalEvent can be null.
472 MOZ_CAN_RUN_SCRIPT
void DispatchEvent(
473 WidgetCompositionEvent
* aDispatchEvent
, nsEventStatus
* aStatus
,
474 EventDispatchingCallback
* aCallback
,
475 const WidgetCompositionEvent
* aOriginalEvent
= nullptr);
478 * HandleSelectionEvent() sends the selection event to ContentEventHandler
479 * or dispatches it to the focused child process.
482 void HandleSelectionEvent(WidgetSelectionEvent
* aSelectionEvent
) {
483 RefPtr
<nsPresContext
> presContext(mPresContext
);
484 RefPtr
<BrowserParent
> browserParent(mBrowserParent
);
485 HandleSelectionEvent(presContext
, browserParent
, aSelectionEvent
);
488 static void HandleSelectionEvent(nsPresContext
* aPresContext
,
489 BrowserParent
* aBrowserParent
,
490 WidgetSelectionEvent
* aSelectionEvent
);
493 * MaybeDispatchCompositionUpdate() may dispatch a compositionupdate event
494 * if aCompositionEvent changes composition string.
495 * @return Returns false if dispatching the compositionupdate event caused
496 * destroying this composition.
498 MOZ_CAN_RUN_SCRIPT
bool MaybeDispatchCompositionUpdate(
499 const WidgetCompositionEvent
* aCompositionEvent
);
502 * CloneAndDispatchAs() dispatches a composition event which is
503 * duplicateed from aCompositionEvent and set the aMessage.
505 * @return Returns BaseEventFlags which is the result of dispatched event.
507 MOZ_CAN_RUN_SCRIPT BaseEventFlags
508 CloneAndDispatchAs(const WidgetCompositionEvent
* aCompositionEvent
,
509 EventMessage aMessage
, nsEventStatus
* aStatus
= nullptr,
510 EventDispatchingCallback
* aCallBack
= nullptr);
513 * If IME has already dispatched compositionend event but it was discarded
514 * by PresShell due to not safe to dispatch, this returns true.
516 bool WasNativeCompositionEndEventDiscarded() const {
517 return mWasNativeCompositionEndEventDiscarded
;
521 * OnCompositionEventDiscarded() is called when PresShell discards
522 * compositionupdate, compositionend or compositionchange event due to not
523 * safe to dispatch event.
525 void OnCompositionEventDiscarded(WidgetCompositionEvent
* aCompositionEvent
);
528 * OnCompositionEventDispatched() is called after a composition event is
531 MOZ_CAN_RUN_SCRIPT
void OnCompositionEventDispatched(
532 const WidgetCompositionEvent
* aDispatchEvent
);
535 * MaybeNotifyIMEOfCompositionEventHandled() notifies IME of composition
536 * event handled. This should be called after dispatching a composition
537 * event which came from widget.
539 void MaybeNotifyIMEOfCompositionEventHandled(
540 const WidgetCompositionEvent
* aCompositionEvent
);
543 * GetSelectionStartOffset() returns normal selection start offset in the
544 * editor which has this composition.
545 * If it failed or lost focus, this would return 0.
547 MOZ_CAN_RUN_SCRIPT
uint32_t GetSelectionStartOffset();
550 * OnStartOffsetUpdatedInChild() is called when composition start offset
551 * is updated in the child process. I.e., this is called and never called
552 * if the composition is in this process.
553 * @param aStartOffset New composition start offset with native
556 void OnStartOffsetUpdatedInChild(uint32_t aStartOffset
);
559 * CompositionEventDispatcher dispatches the specified composition (or text)
562 class CompositionEventDispatcher
: public Runnable
{
564 CompositionEventDispatcher(TextComposition
* aTextComposition
,
565 nsINode
* aEventTarget
,
566 EventMessage aEventMessage
,
567 const nsAString
& aData
,
568 bool aIsSynthesizedEvent
= false);
569 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
;
572 RefPtr
<TextComposition
> mTextComposition
;
573 nsCOMPtr
<nsINode
> mEventTarget
;
575 EventMessage mEventMessage
;
576 bool mIsSynthesizedEvent
;
578 CompositionEventDispatcher()
579 : Runnable("TextComposition::CompositionEventDispatcher"),
580 mEventMessage(eVoidEvent
),
581 mIsSynthesizedEvent(false) {};
585 * DispatchCompositionEventRunnable() dispatches a composition event to the
586 * content. Be aware, if you use this method, nsPresShellEventCB isn't used.
587 * That means that nsIFrame::HandleEvent() is never called.
588 * WARNING: The instance which is managed by IMEStateManager may be
589 * destroyed by this method call.
591 * @param aEventMessage Must be one of composition events.
592 * @param aData Used for mData value.
593 * @param aIsSynthesizingCommit true if this is called for synthesizing
594 * commit or cancel composition. Otherwise,
597 void DispatchCompositionEventRunnable(EventMessage aEventMessage
,
598 const nsAString
& aData
,
599 bool aIsSynthesizingCommit
= false);
603 * TextCompositionArray manages the instances of TextComposition class.
604 * Managing with array is enough because only one composition is typically
605 * there. Even if user switches native IME context, it's very rare that
606 * second or more composition is started.
607 * It's assumed that this is used by IMEStateManager for storing all active
608 * compositions in the process. If the instance is it, each TextComposition
609 * in the array can be destroyed by calling some methods of itself.
612 class TextCompositionArray final
613 : public AutoTArray
<RefPtr
<TextComposition
>, 2> {
615 // Looking for per native IME context.
616 index_type
IndexOf(const widget::NativeIMEContext
& aNativeIMEContext
);
617 index_type
IndexOf(nsIWidget
* aWidget
);
619 TextComposition
* GetCompositionFor(nsIWidget
* aWidget
);
620 TextComposition
* GetCompositionFor(
621 const WidgetCompositionEvent
* aCompositionEvent
);
623 // Looking for per nsPresContext
624 index_type
IndexOf(nsPresContext
* aPresContext
);
625 index_type
IndexOf(nsPresContext
* aPresContext
, nsINode
* aNode
);
627 TextComposition
* GetCompositionFor(nsPresContext
* aPresContext
);
628 TextComposition
* GetCompositionFor(nsPresContext
* aPresContext
,
630 TextComposition
* GetCompositionInContent(nsPresContext
* aPresContext
,
631 nsIContent
* aContent
);
634 } // namespace mozilla
636 #endif // #ifndef mozilla_TextComposition_h