Bug 1826136 [wpt PR 39338] - Update wpt metadata, a=testonly
[gecko.git] / dom / events / TextComposition.h
blob7199171068cdbae5b2929de8626541a99dcba0ca
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/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"
24 class nsRange;
26 struct CharacterDataChangeInfo;
28 namespace mozilla {
30 class EditorBase;
31 class EventDispatchingCallback;
32 class IMEStateManager;
34 /**
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)
45 public:
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 {
84 if (!mPresContext) {
85 return nullptr;
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;
99 /**
100 * This is called when IMEStateManager stops managing the instance.
102 void Destroy();
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) {
173 return UINT32_MAX;
175 return mCompositionStartOffsetInTextNode + mCompositionLengthInTextNode;
179 * Returns true if there is non-empty composition string and it's not fixed.
180 * Otherwise, false.
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
193 * new text node.
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 {
220 public:
221 CompositionChangeEventHandlingMarker(
222 TextComposition* aComposition,
223 const WidgetCompositionEvent* aCompositionChangeEvent)
224 : mComposition(aComposition) {
225 mComposition->EditorWillHandleCompositionChangeEvent(
226 aCompositionChangeEvent);
229 ~CompositionChangeEventHandlingMarker() {
230 mComposition->EditorDidHandleCompositionChangeEvent();
233 private:
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);
282 private:
283 // Private destructor, to discourage deletion outside of Release():
284 ~TextComposition() {
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
295 // this instance.
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
315 // composition.
316 nsWeakPtr mEditorBaseWeak;
318 // mLastData stores the data attribute of the latest composition event (except
319 // the compositionstart event).
320 nsString mLastData;
322 // mString stores the composition text which has been handled by the focused
323 // editor.
324 nsString mString;
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
333 // remote process.
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
341 // remote process.
342 uint32_t mCompositionLengthInTextNode;
344 // See the comment for IsSynthesizedForTests().
345 bool mIsSynthesizedForTests;
347 // See the comment for IsComposing().
348 bool mIsComposing;
350 // mIsEditorHandlingEvent is true while editor is modifying the composition
351 // string.
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.
391 TextComposition()
392 : mPresContext(nullptr),
393 mNativeContext(nullptr),
394 mCompositionStartOffset(0),
395 mTargetClauseOffsetInComposition(0),
396 mCompositionStartOffsetInTextNode(UINT32_MAX),
397 mCompositionLengthInTextNode(UINT32_MAX),
398 mIsSynthesizedForTests(false),
399 mIsComposing(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.
476 MOZ_CAN_RUN_SCRIPT
477 void HandleSelectionEvent(WidgetSelectionEvent* aSelectionEvent) {
478 RefPtr<nsPresContext> presContext(mPresContext);
479 RefPtr<BrowserParent> browserParent(mBrowserParent);
480 HandleSelectionEvent(presContext, browserParent, aSelectionEvent);
482 MOZ_CAN_RUN_SCRIPT
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
524 * dispatched.
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
549 * linebreaks.
551 void OnStartOffsetUpdatedInChild(uint32_t aStartOffset);
554 * CompositionEventDispatcher dispatches the specified composition (or text)
555 * event.
557 class CompositionEventDispatcher : public Runnable {
558 public:
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;
566 private:
567 RefPtr<TextComposition> mTextComposition;
568 nsCOMPtr<nsINode> mEventTarget;
569 nsString mData;
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,
590 * false.
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> {
609 public:
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,
624 nsINode* aNode);
625 TextComposition* GetCompositionInContent(nsPresContext* aPresContext,
626 nsIContent* aContent);
629 } // namespace mozilla
631 #endif // #ifndef mozilla_TextComposition_h