Bug 1914004 - Part 1: Add RootedTuple and RootedField to allow rooting multiple thing...
[gecko.git] / dom / events / TextComposition.h
blobead0c4ea98720a7771661d4a5e291d716e57e150
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
10 #include "nsCOMPtr.h"
11 #include "nsINode.h"
12 #include "nsIWidget.h"
13 #include "nsTArray.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"
25 class nsRange;
27 struct CharacterDataChangeInfo;
29 namespace mozilla {
31 class EditorBase;
32 class EventDispatchingCallback;
33 class IMEStateManager;
35 /**
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)
46 public:
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 {
87 if (!mPresContext) {
88 return nullptr;
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.
109 void Destroy();
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) {
180 return UINT32_MAX;
182 return mCompositionStartOffsetInTextNode + mCompositionLengthInTextNode;
186 * Returns true if there is non-empty composition string and it's not fixed.
187 * Otherwise, false.
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
211 * new text node.
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 {
238 public:
239 CompositionChangeEventHandlingMarker(
240 TextComposition* aComposition,
241 const WidgetCompositionEvent* aCompositionChangeEvent)
242 : mComposition(aComposition) {
243 mComposition->EditorWillHandleCompositionChangeEvent(
244 aCompositionChangeEvent);
247 ~CompositionChangeEventHandlingMarker() {
248 mComposition->EditorDidHandleCompositionChangeEvent();
251 private:
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);
300 private:
301 // Private destructor, to discourage deletion outside of Release():
302 ~TextComposition() {
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
313 // this instance.
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
335 // composition.
336 nsWeakPtr mEditorBaseWeak;
338 // mLastData stores the data attribute of the latest composition event (except
339 // the compositionstart event).
340 nsString mLastData;
342 // mString stores the composition text which has been handled by the focused
343 // editor.
344 nsString mString;
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
358 // remote process.
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
366 // remote process.
367 uint32_t mCompositionLengthInTextNode;
369 // See the comment for IsSynthesizedForTests().
370 bool mIsSynthesizedForTests;
372 // See the comment for IsComposing().
373 bool mIsComposing;
375 // mEditorIsHandlingEvent is true while editor is modifying the composition
376 // string.
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.
481 MOZ_CAN_RUN_SCRIPT
482 void HandleSelectionEvent(WidgetSelectionEvent* aSelectionEvent) {
483 RefPtr<nsPresContext> presContext(mPresContext);
484 RefPtr<BrowserParent> browserParent(mBrowserParent);
485 HandleSelectionEvent(presContext, browserParent, aSelectionEvent);
487 MOZ_CAN_RUN_SCRIPT
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
529 * dispatched.
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
554 * linebreaks.
556 void OnStartOffsetUpdatedInChild(uint32_t aStartOffset);
559 * CompositionEventDispatcher dispatches the specified composition (or text)
560 * event.
562 class CompositionEventDispatcher : public Runnable {
563 public:
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;
571 private:
572 RefPtr<TextComposition> mTextComposition;
573 nsCOMPtr<nsINode> mEventTarget;
574 nsString mData;
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,
595 * false.
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> {
614 public:
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,
629 nsINode* aNode);
630 TextComposition* GetCompositionInContent(nsPresContext* aPresContext,
631 nsIContent* aContent);
634 } // namespace mozilla
636 #endif // #ifndef mozilla_TextComposition_h