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
);
54 TextComposition() = delete;
55 TextComposition(const TextComposition
& aOther
) = delete;
57 bool Destroyed() const { return !mPresContext
; }
58 nsPresContext
* GetPresContext() const { return mPresContext
; }
59 nsINode
* GetEventTargetNode() const { return mNode
; }
60 // The text node which includes composition string.
61 Text
* GetContainerTextNode() const { return mContainerTextNode
; }
62 // The latest CompositionEvent.data value except compositionstart event.
63 // This value is modified at dispatching compositionupdate.
64 const nsString
& LastData() const { return mLastData
; }
65 // Returns commit string if it'll be commited as-is.
66 nsString
CommitStringIfCommittedAsIs() const;
67 // The composition string which is already handled by the focused editor.
68 // I.e., this value must be same as the composition string on the focused
69 // editor. This value is modified at a call of
70 // EditorDidHandleCompositionChangeEvent().
71 // Note that mString and mLastData are different between dispatcing
72 // compositionupdate and compositionchange event handled by focused editor.
73 const nsString
& String() const { return mString
; }
74 // The latest clauses range of the composition string.
75 // During compositionupdate event, GetRanges() returns old ranges.
76 // So if getting on compositionupdate, Use GetLastRange instead of GetRange().
77 TextRangeArray
* GetLastRanges() const { return mLastRanges
; }
78 // Returns the clauses and/or caret range of the composition string.
79 // This is modified at a call of EditorWillHandleCompositionChangeEvent().
80 // This may return null if there is no clauses and caret.
81 // XXX We should return |const TextRangeArray*| here, but it causes compile
82 // error due to inaccessible Release() method.
83 TextRangeArray
* GetRanges() const { return mRanges
; }
84 // Returns the widget which is proper to call NotifyIME().
85 already_AddRefed
<nsIWidget
> GetWidget() const {
89 return do_AddRef(mPresContext
->GetRootWidget());
91 // Returns the tab parent which has this composition in its remote process.
92 BrowserParent
* GetBrowserParent() const { return mBrowserParent
; }
93 // Returns true if the composition is started with synthesized event which
94 // came from nsDOMWindowUtils.
95 bool IsSynthesizedForTests() const { return mIsSynthesizedForTests
; }
97 // Returns the composition ID. It must be 0 if the composition is synthesized
98 // in a content process. Otherwise, returns 1 or larger value.
99 uint32_t Id() const { return mCompositionId
; }
101 const widget::NativeIMEContext
& GetNativeIMEContext() const {
102 return mNativeContext
;
106 * This is called when IMEStateManager stops managing the instance.
111 * Request to commit (or cancel) the composition to IME. This method should
112 * be called only by IMEStateManager::NotifyIME().
114 nsresult
RequestToCommit(nsIWidget
* aWidget
, bool aDiscard
);
117 * IsRequestingCommitOrCancelComposition() returns true if the instance is
118 * requesting widget to commit or cancel composition.
120 bool IsRequestingCommitOrCancelComposition() const {
121 return mIsRequestingCancel
|| mIsRequestingCommit
;
125 * Send a notification to IME. It depends on the IME or platform spec what
126 * will occur (or not occur).
128 nsresult
NotifyIME(widget::IMEMessage aMessage
);
131 * the offset of first composition string
133 uint32_t NativeOffsetOfStartComposition() const {
134 return mCompositionStartOffset
;
138 * the offset of first selected clause or start of composition
140 uint32_t NativeOffsetOfTargetClause() const {
141 return mCompositionStartOffset
+ mTargetClauseOffsetInComposition
;
145 * Return current composition start and end point in the DOM tree.
146 * Note that one of or both of those result container may be different
147 * from GetContainerTextNode() if the DOM tree was modified by the web
148 * app. If there is no composition string the DOM tree, these return
149 * unset range boundaries.
151 RawRangeBoundary
FirstIMESelectionStartRef() const;
152 RawRangeBoundary
LastIMESelectionEndRef() const;
155 * The offset of composition string in the text node. If composition string
156 * hasn't been inserted in any text node yet, this returns UINT32_MAX.
158 uint32_t XPOffsetInTextNode() const {
159 return mCompositionStartOffsetInTextNode
;
163 * The length of composition string in the text node. If composition string
164 * hasn't been inserted in any text node yet, this returns 0.
166 uint32_t XPLengthInTextNode() const {
167 return mCompositionLengthInTextNode
== UINT32_MAX
169 : mCompositionLengthInTextNode
;
173 * The end offset of composition string in the text node. If composition
174 * string hasn't been inserted in any text node yet, this returns UINT32_MAX.
176 uint32_t XPEndOffsetInTextNode() const {
177 if (mCompositionStartOffsetInTextNode
== UINT32_MAX
||
178 mCompositionLengthInTextNode
== UINT32_MAX
) {
181 return mCompositionStartOffsetInTextNode
+ mCompositionLengthInTextNode
;
185 * Returns true if there is non-empty composition string and it's not fixed.
188 bool IsComposing() const { return mIsComposing
; }
191 * Returns true while editor is handling an event which is modifying the
192 * composition string.
194 bool IsEditorHandlingEvent() const { return mIsEditorHandlingEvent
; }
197 * IsMovingToNewTextNode() returns true if editor detects the text node
198 * has been removed and still not insert the composition string into
201 bool IsMovingToNewTextNode() const {
202 return !mContainerTextNode
&& mCompositionLengthInTextNode
&&
203 mCompositionLengthInTextNode
!= UINT32_MAX
;
207 * StartHandlingComposition() and EndHandlingComposition() are called by
208 * editor when it holds a TextComposition instance and release it.
210 void StartHandlingComposition(EditorBase
* aEditorBase
);
211 void EndHandlingComposition(EditorBase
* aEditorBase
);
214 * OnEditorDestroyed() is called when the editor is destroyed but there is
215 * active composition.
217 void OnEditorDestroyed();
220 * CompositionChangeEventHandlingMarker class should be created at starting
221 * to handle text event in focused editor. This calls
222 * EditorWillHandleCompositionChangeEvent() and
223 * EditorDidHandleCompositionChangeEvent() automatically.
225 class MOZ_STACK_CLASS CompositionChangeEventHandlingMarker
{
227 CompositionChangeEventHandlingMarker(
228 TextComposition
* aComposition
,
229 const WidgetCompositionEvent
* aCompositionChangeEvent
)
230 : mComposition(aComposition
) {
231 mComposition
->EditorWillHandleCompositionChangeEvent(
232 aCompositionChangeEvent
);
235 ~CompositionChangeEventHandlingMarker() {
236 mComposition
->EditorDidHandleCompositionChangeEvent();
240 RefPtr
<TextComposition
> mComposition
;
241 CompositionChangeEventHandlingMarker();
242 CompositionChangeEventHandlingMarker(
243 const CompositionChangeEventHandlingMarker
& aOther
);
247 * OnUpdateCompositionInEditor() is called when editor updates composition
248 * string in the DOM tree.
250 * @param aStringToInsert The string to insert the text node actually.
251 * This may be different from the data of
252 * dispatching composition event because it may
253 * be replaced with different character for
254 * passwords, or truncated due to maxlength.
255 * @param aTextNode The text node which includes composition string.
256 * @param aOffset The offset of composition string in aTextNode.
258 void OnUpdateCompositionInEditor(const nsAString
& aStringToInsert
,
259 Text
& aTextNode
, uint32_t aOffset
) {
260 mContainerTextNode
= &aTextNode
;
261 mCompositionStartOffsetInTextNode
= aOffset
;
262 NS_WARNING_ASSERTION(mCompositionStartOffsetInTextNode
!= UINT32_MAX
,
263 "The text node is really too long.");
264 mCompositionLengthInTextNode
= aStringToInsert
.Length();
265 NS_WARNING_ASSERTION(mCompositionLengthInTextNode
!= UINT32_MAX
,
266 "The string to insert is really too long.");
270 * OnTextNodeRemoved() is called when focused editor is reframed and
271 * mContainerTextNode may be (or have been) replaced with different text
272 * node, or just removes the text node due to empty.
274 void OnTextNodeRemoved() {
275 mContainerTextNode
= nullptr;
276 // Don't reset mCompositionStartOffsetInTextNode nor
277 // mCompositionLengthInTextNode because editor needs them to restore
278 // composition in new text node.
282 * OnCharacterDataChanged() is called when IMEContentObserver receives
283 * character data change notifications.
285 void OnCharacterDataChanged(Text
& aText
,
286 const CharacterDataChangeInfo
& aInfo
);
289 // Private destructor, to discourage deletion outside of Release():
291 // WARNING: mPresContext may be destroying, so, be careful if you touch it.
294 // sHandlingSelectionEvent is true while TextComposition sends a selection
295 // event to ContentEventHandler.
296 static bool sHandlingSelectionEvent
;
298 // This class holds nsPresContext weak. This instance shouldn't block
299 // destroying it. When the presContext is being destroyed, it's notified to
300 // IMEStateManager::OnDestroyPresContext(), and then, it destroy
302 nsPresContext
* mPresContext
;
303 RefPtr
<nsINode
> mNode
;
304 RefPtr
<BrowserParent
> mBrowserParent
;
306 // The text node which includes the composition string.
307 RefPtr
<Text
> mContainerTextNode
;
309 // This is the clause and caret range information which is managed by
310 // the focused editor. This may be null if there is no clauses or caret.
311 RefPtr
<TextRangeArray
> mRanges
;
312 // Same as mRange, but mRange will have old data during compositionupdate.
313 // So this will be valied during compositionupdate.
314 RefPtr
<TextRangeArray
> mLastRanges
;
316 // mNativeContext stores a opaque pointer. This works as the "ID" for this
317 // composition. Don't access the instance, it may not be available.
318 widget::NativeIMEContext mNativeContext
;
320 // mEditorBaseWeak is a weak reference to the focused editor handling
322 nsWeakPtr mEditorBaseWeak
;
324 // mLastData stores the data attribute of the latest composition event (except
325 // the compositionstart event).
328 // mString stores the composition text which has been handled by the focused
332 // Composition ID of this composition. If this is in a parent process,
333 // this is 1 or larger. If the composition is created for managing a
334 // composition synthesized in a content process, this is 0.
335 const uint32_t mCompositionId
= 0;
337 // Offset of the composition string from start of the editor
338 uint32_t mCompositionStartOffset
;
339 // Offset of the selected clause of the composition string from
340 // mCompositionStartOffset
341 uint32_t mTargetClauseOffsetInComposition
;
342 // Offset of the composition string in mContainerTextNode.
343 // NOTE: This is NOT valid in the main process if focused editor is in a
345 uint32_t mCompositionStartOffsetInTextNode
;
346 // Length of the composition string in mContainerTextNode. If this instance
347 // has already dispatched eCompositionCommit(AsIs) and
348 // EditorDidHandleCompositionChangeEvent() has already been called,
349 // this may be different from length of mString because committed string
350 // may be truncated by maxlength attribute of <input> or <textarea>.
351 // NOTE: This is NOT valid in the main process if focused editor is in a
353 uint32_t mCompositionLengthInTextNode
;
355 // See the comment for IsSynthesizedForTests().
356 bool mIsSynthesizedForTests
;
358 // See the comment for IsComposing().
361 // mIsEditorHandlingEvent is true while editor is modifying the composition
363 bool mIsEditorHandlingEvent
;
365 // mIsRequestingCommit or mIsRequestingCancel is true *only* while we're
366 // requesting commit or canceling the composition. In other words, while
367 // one of these values is true, we're handling the request.
368 bool mIsRequestingCommit
;
369 bool mIsRequestingCancel
;
371 // mRequestedToCommitOrCancel is true *after* we requested IME to commit or
372 // cancel the composition. In other words, we already requested of IME that
373 // it commits or cancels current composition.
374 // NOTE: Before this is set to true, both mIsRequestingCommit and
375 // mIsRequestingCancel are set to false.
376 bool mRequestedToCommitOrCancel
;
378 // Set to true if the instance dispatches an eCompositionChange event.
379 bool mHasDispatchedDOMTextEvent
;
381 // Before this dispatches commit event into the tree, this is set to true.
382 // So, this means if native IME already commits the composition.
383 bool mHasReceivedCommitEvent
;
385 // mWasNativeCompositionEndEventDiscarded is true if this composition was
386 // requested commit or cancel itself but native compositionend event is
387 // discarded by PresShell due to not safe to dispatch events.
388 bool mWasNativeCompositionEndEventDiscarded
;
390 // Allow control characters appear in composition string.
391 // When this is false, control characters except
392 // CHARACTER TABULATION (horizontal tab) are removed from
393 // both composition string and data attribute of compositionupdate
394 // and compositionend events.
395 bool mAllowControlCharacters
;
397 // mWasCompositionStringEmpty is true if the composition string was empty
398 // when DispatchCompositionEvent() is called.
399 bool mWasCompositionStringEmpty
;
402 * If we're requesting IME to commit or cancel composition, or we've already
403 * requested it, or we've already known this composition has been ended in
404 * IME, we don't need to request commit nor cancel composition anymore and
405 * shouldn't do so if we're in content process for not committing/canceling
406 * "current" composition in native IME. So, when this returns true,
407 * RequestIMEToCommit() does nothing.
409 bool CanRequsetIMEToCommitOrCancelComposition() const {
410 return !mIsRequestingCommit
&& !mIsRequestingCancel
&&
411 !mRequestedToCommitOrCancel
&& !mHasReceivedCommitEvent
;
415 * GetEditorBase() returns EditorBase pointer of mEditorBaseWeak.
417 already_AddRefed
<EditorBase
> GetEditorBase() const;
420 * HasEditor() returns true if mEditorBaseWeak holds EditorBase instance
421 * which is alive. Otherwise, false.
423 bool HasEditor() const;
426 * EditorWillHandleCompositionChangeEvent() must be called before the focused
427 * editor handles the compositionchange event.
429 void EditorWillHandleCompositionChangeEvent(
430 const WidgetCompositionEvent
* aCompositionChangeEvent
);
433 * EditorDidHandleCompositionChangeEvent() must be called after the focused
434 * editor handles a compositionchange event.
436 void EditorDidHandleCompositionChangeEvent();
439 * IsValidStateForComposition() returns true if it's safe to dispatch an event
440 * to the DOM tree. Otherwise, false.
441 * WARNING: This doesn't check script blocker state. It should be checked
442 * before dispatching the first event.
444 bool IsValidStateForComposition(nsIWidget
* aWidget
) const;
447 * DispatchCompositionEvent() dispatches the aCompositionEvent to the mContent
448 * synchronously. The caller must ensure that it's safe to dispatch the event.
450 MOZ_CAN_RUN_SCRIPT
void DispatchCompositionEvent(
451 WidgetCompositionEvent
* aCompositionEvent
, nsEventStatus
* aStatus
,
452 EventDispatchingCallback
* aCallBack
, bool aIsSynthesized
);
455 * Simply calling EventDispatcher::Dispatch() with plugin event.
456 * If dispatching event has no orginal clone, aOriginalEvent can be null.
458 MOZ_CAN_RUN_SCRIPT
void DispatchEvent(
459 WidgetCompositionEvent
* aDispatchEvent
, nsEventStatus
* aStatus
,
460 EventDispatchingCallback
* aCallback
,
461 const WidgetCompositionEvent
* aOriginalEvent
= nullptr);
464 * HandleSelectionEvent() sends the selection event to ContentEventHandler
465 * or dispatches it to the focused child process.
468 void HandleSelectionEvent(WidgetSelectionEvent
* aSelectionEvent
) {
469 RefPtr
<nsPresContext
> presContext(mPresContext
);
470 RefPtr
<BrowserParent
> browserParent(mBrowserParent
);
471 HandleSelectionEvent(presContext
, browserParent
, aSelectionEvent
);
474 static void HandleSelectionEvent(nsPresContext
* aPresContext
,
475 BrowserParent
* aBrowserParent
,
476 WidgetSelectionEvent
* aSelectionEvent
);
479 * MaybeDispatchCompositionUpdate() may dispatch a compositionupdate event
480 * if aCompositionEvent changes composition string.
481 * @return Returns false if dispatching the compositionupdate event caused
482 * destroying this composition.
484 MOZ_CAN_RUN_SCRIPT
bool MaybeDispatchCompositionUpdate(
485 const WidgetCompositionEvent
* aCompositionEvent
);
488 * CloneAndDispatchAs() dispatches a composition event which is
489 * duplicateed from aCompositionEvent and set the aMessage.
491 * @return Returns BaseEventFlags which is the result of dispatched event.
493 MOZ_CAN_RUN_SCRIPT BaseEventFlags
494 CloneAndDispatchAs(const WidgetCompositionEvent
* aCompositionEvent
,
495 EventMessage aMessage
, nsEventStatus
* aStatus
= nullptr,
496 EventDispatchingCallback
* aCallBack
= nullptr);
499 * If IME has already dispatched compositionend event but it was discarded
500 * by PresShell due to not safe to dispatch, this returns true.
502 bool WasNativeCompositionEndEventDiscarded() const {
503 return mWasNativeCompositionEndEventDiscarded
;
507 * OnCompositionEventDiscarded() is called when PresShell discards
508 * compositionupdate, compositionend or compositionchange event due to not
509 * safe to dispatch event.
511 void OnCompositionEventDiscarded(WidgetCompositionEvent
* aCompositionEvent
);
514 * OnCompositionEventDispatched() is called after a composition event is
517 MOZ_CAN_RUN_SCRIPT
void OnCompositionEventDispatched(
518 const WidgetCompositionEvent
* aDispatchEvent
);
521 * MaybeNotifyIMEOfCompositionEventHandled() notifies IME of composition
522 * event handled. This should be called after dispatching a composition
523 * event which came from widget.
525 void MaybeNotifyIMEOfCompositionEventHandled(
526 const WidgetCompositionEvent
* aCompositionEvent
);
529 * GetSelectionStartOffset() returns normal selection start offset in the
530 * editor which has this composition.
531 * If it failed or lost focus, this would return 0.
533 MOZ_CAN_RUN_SCRIPT
uint32_t GetSelectionStartOffset();
536 * OnStartOffsetUpdatedInChild() is called when composition start offset
537 * is updated in the child process. I.e., this is called and never called
538 * if the composition is in this process.
539 * @param aStartOffset New composition start offset with native
542 void OnStartOffsetUpdatedInChild(uint32_t aStartOffset
);
545 * CompositionEventDispatcher dispatches the specified composition (or text)
548 class CompositionEventDispatcher
: public Runnable
{
550 CompositionEventDispatcher(TextComposition
* aTextComposition
,
551 nsINode
* aEventTarget
,
552 EventMessage aEventMessage
,
553 const nsAString
& aData
,
554 bool aIsSynthesizedEvent
= false);
555 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
;
558 RefPtr
<TextComposition
> mTextComposition
;
559 nsCOMPtr
<nsINode
> mEventTarget
;
561 EventMessage mEventMessage
;
562 bool mIsSynthesizedEvent
;
564 CompositionEventDispatcher()
565 : Runnable("TextComposition::CompositionEventDispatcher"),
566 mEventMessage(eVoidEvent
),
567 mIsSynthesizedEvent(false){};
571 * DispatchCompositionEventRunnable() dispatches a composition event to the
572 * content. Be aware, if you use this method, nsPresShellEventCB isn't used.
573 * That means that nsIFrame::HandleEvent() is never called.
574 * WARNING: The instance which is managed by IMEStateManager may be
575 * destroyed by this method call.
577 * @param aEventMessage Must be one of composition events.
578 * @param aData Used for mData value.
579 * @param aIsSynthesizingCommit true if this is called for synthesizing
580 * commit or cancel composition. Otherwise,
583 void DispatchCompositionEventRunnable(EventMessage aEventMessage
,
584 const nsAString
& aData
,
585 bool aIsSynthesizingCommit
= false);
589 * TextCompositionArray manages the instances of TextComposition class.
590 * Managing with array is enough because only one composition is typically
591 * there. Even if user switches native IME context, it's very rare that
592 * second or more composition is started.
593 * It's assumed that this is used by IMEStateManager for storing all active
594 * compositions in the process. If the instance is it, each TextComposition
595 * in the array can be destroyed by calling some methods of itself.
598 class TextCompositionArray final
599 : public AutoTArray
<RefPtr
<TextComposition
>, 2> {
601 // Looking for per native IME context.
602 index_type
IndexOf(const widget::NativeIMEContext
& aNativeIMEContext
);
603 index_type
IndexOf(nsIWidget
* aWidget
);
605 TextComposition
* GetCompositionFor(nsIWidget
* aWidget
);
606 TextComposition
* GetCompositionFor(
607 const WidgetCompositionEvent
* aCompositionEvent
);
609 // Looking for per nsPresContext
610 index_type
IndexOf(nsPresContext
* aPresContext
);
611 index_type
IndexOf(nsPresContext
* aPresContext
, nsINode
* aNode
);
613 TextComposition
* GetCompositionFor(nsPresContext
* aPresContext
);
614 TextComposition
* GetCompositionFor(nsPresContext
* aPresContext
,
616 TextComposition
* GetCompositionInContent(nsPresContext
* aPresContext
,
617 nsIContent
* aContent
);
620 } // namespace mozilla
622 #endif // #ifndef mozilla_TextComposition_h