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_IMEContentObserver_h
8 #define mozilla_IMEContentObserver_h
10 #include "mozilla/Attributes.h"
11 #include "mozilla/EditorBase.h"
12 #include "mozilla/dom/Element.h"
13 #include "mozilla/dom/Selection.h"
15 #include "nsCycleCollectionParticipant.h"
16 #include "nsIDocShell.h" // XXX Why does only this need to be included here?
17 #include "nsIReflowObserver.h"
18 #include "nsIScrollObserver.h"
19 #include "nsIWidget.h"
20 #include "nsStubDocumentObserver.h"
21 #include "nsStubMutationObserver.h"
22 #include "nsThreadUtils.h"
23 #include "nsWeakReference.h"
31 class EventStateManager
;
32 class TextComposition
;
38 // IMEContentObserver notifies widget of any text and selection changes
39 // in the currently focused editor
40 class IMEContentObserver final
: public nsStubMutationObserver
,
41 public nsIReflowObserver
,
42 public nsIScrollObserver
,
43 public nsSupportsWeakReference
{
45 using SelectionChangeData
= widget::IMENotification::SelectionChangeData
;
46 using TextChangeData
= widget::IMENotification::TextChangeData
;
47 using TextChangeDataBase
= widget::IMENotification::TextChangeDataBase
;
48 using IMENotificationRequests
= widget::IMENotificationRequests
;
49 using IMEMessage
= widget::IMEMessage
;
53 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
54 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IMEContentObserver
,
56 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
57 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
58 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
59 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
60 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
61 NS_DECL_NSIREFLOWOBSERVER
64 virtual void ScrollPositionChanged() override
;
67 * OnSelectionChange() is called when selection is changed in the editor.
69 void OnSelectionChange(dom::Selection
& aSelection
);
71 MOZ_CAN_RUN_SCRIPT
bool OnMouseButtonEvent(nsPresContext
& aPresContext
,
72 WidgetMouseEvent
& aMouseEvent
);
74 MOZ_CAN_RUN_SCRIPT nsresult
75 HandleQueryContentEvent(WidgetQueryContentEvent
* aEvent
);
78 * Handle eSetSelection event if and only if aEvent changes selection offset
79 * or length. Doing nothing when selection range is same is important to
80 * honer users' intention or web app's intention because ContentEventHandler
81 * does not support to put range boundaries to arbitrary side of element
82 * boundaries. E.g., `<b>bold[]</b> normal` vs. `<b>bold</b>[] normal`.
83 * Note that this compares given range with selection cache which has been
84 * notified IME via widget. Therefore, the caller needs to guarantee that
85 * pending notifications should've been flushed. If you test this, you need
86 * to wait 2 animation frames before sending eSetSelection event.
88 MOZ_CAN_RUN_SCRIPT nsresult
MaybeHandleSelectionEvent(
89 nsPresContext
* aPresContext
, WidgetSelectionEvent
* aEvent
);
92 * Init() initializes the instance, i.e., retrieving necessary objects and
93 * starts to observe something.
94 * Be aware, callers of this method need to guarantee that the instance
95 * won't be released during calling this.
97 * @param aWidget The widget which can access native IME.
98 * @param aPresContext The PresContext which has aContent.
99 * @param aElement An editable element or nullptr if this will observe
100 * design mode document.
101 * @param aEditorBase The editor which is associated with aContent.
103 MOZ_CAN_RUN_SCRIPT
void Init(nsIWidget
& aWidget
, nsPresContext
& aPresContext
,
104 dom::Element
* aElement
, EditorBase
& aEditorBase
);
107 * Destroy() finalizes the instance, i.e., stops observing contents and
108 * clearing the members.
109 * Be aware, callers of this method need to guarantee that the instance
110 * won't be released during calling this.
115 * Returns false if the instance refers some objects and observing them.
118 bool Destroyed() const;
121 * IMEContentObserver is stored by EventStateManager during observing.
122 * DisconnectFromEventStateManager() is called when EventStateManager stops
123 * storing the instance.
125 void DisconnectFromEventStateManager();
128 * MaybeReinitialize() tries to restart to observe the editor's root node.
129 * This is useful when the editor is reframed and all children are replaced
130 * with new node instances.
131 * Be aware, callers of this method need to guarantee that the instance
132 * won't be released during calling this.
134 * @return Returns true if the instance is managing the content.
137 MOZ_CAN_RUN_SCRIPT
bool MaybeReinitialize(nsIWidget
& aWidget
,
138 nsPresContext
& aPresContext
,
139 dom::Element
* aElement
,
140 EditorBase
& aEditorBase
);
142 bool IsManaging(const nsPresContext
& aPresContext
,
143 const dom::Element
* aElement
) const;
144 bool IsBeingInitializedFor(const nsPresContext
& aPresContext
,
145 const dom::Element
* aElement
) const;
146 bool IsManaging(const TextComposition
& aTextComposition
) const;
147 bool WasInitializedWith(const EditorBase
& aEditorBase
) const {
148 return mEditorBase
== &aEditorBase
;
150 bool IsEditorHandlingEventForComposition() const;
151 bool KeepAliveDuringDeactive() const {
152 return mIMENotificationRequests
&&
153 mIMENotificationRequests
->WantDuringDeactive();
155 nsIWidget
* GetWidget() const { return mWidget
; }
156 void SuppressNotifyingIME();
157 void UnsuppressNotifyingIME();
158 nsPresContext
* GetPresContext() const;
159 nsresult
GetSelectionAndRoot(dom::Selection
** aSelection
,
160 dom::Element
** aRootElement
) const;
163 * TryToFlushPendingNotifications() should be called when pending events
164 * should be flushed. This tries to run the queued IMENotificationSender.
165 * Doesn't do anything in child processes where flushing happens
166 * asynchronously unless aAllowAsync is false.
168 void TryToFlushPendingNotifications(bool aAllowAsync
);
171 * MaybeNotifyCompositionEventHandled() posts composition event handled
172 * notification into the pseudo queue.
174 void MaybeNotifyCompositionEventHandled();
177 * Following methods are called when the editor:
178 * - an edit action handled.
179 * - before handling an edit action.
180 * - canceled handling an edit action after calling BeforeEditAction().
182 void OnEditActionHandled();
183 void BeforeEditAction();
184 void CancelEditAction();
187 * Called when text control value is changed while this is not observing
188 * mRootElement. This is typically there is no frame for the editor (i.e.,
189 * no proper anonymous <div> element for the editor yet) or the TextEditor
190 * has not been created (i.e., IMEStateManager has not been reinitialized
191 * this instance with new anonymous <div> element yet).
193 void OnTextControlValueChangedWhileNotObservable(const nsAString
& aNewValue
);
195 dom::Element
* GetObservingElement() const {
196 return mIsObserving
? mRootElement
.get() : nullptr;
200 ~IMEContentObserver() = default;
205 eState_StoppedObserving
,
208 State
GetState() const;
209 MOZ_CAN_RUN_SCRIPT
bool InitWithEditor(nsPresContext
& aPresContext
,
210 dom::Element
* aElement
,
211 EditorBase
& aEditorBase
);
212 void OnIMEReceivedFocus();
214 [[nodiscard
]] bool IsObservingContent(const nsPresContext
& aPresContext
,
215 const dom::Element
* aElement
) const;
216 [[nodiscard
]] bool IsReflowLocked() const;
217 [[nodiscard
]] bool IsSafeToNotifyIME() const;
218 [[nodiscard
]] bool IsEditorComposing() const;
220 // Following methods are called by DocumentObserver when
221 // beginning to update the contents and ending updating the contents.
222 void BeginDocumentUpdate();
223 void EndDocumentUpdate();
225 // Following methods manages added nodes during a document change.
228 * MaybeNotifyIMEOfAddedTextDuringDocumentChange() may send text change
229 * notification caused by the nodes added between mFirstAddedContent in
230 * mFirstAddedContainer and mLastAddedContent in
231 * mLastAddedContainer and forgets the range.
233 void MaybeNotifyIMEOfAddedTextDuringDocumentChange();
236 * IsInDocumentChange() returns true while the DOM tree is being modified
237 * with mozAutoDocUpdate. E.g., it's being modified by setting innerHTML or
238 * insertAdjacentHTML(). This returns false when user types something in
239 * the focused editor editor.
241 bool IsInDocumentChange() const {
242 return mDocumentObserver
&& mDocumentObserver
->IsUpdating();
246 * Forget the range of added nodes during a document change.
248 void ClearAddedNodesDuringDocumentChange();
251 * HasAddedNodesDuringDocumentChange() returns true when this stores range
252 * of nodes which were added into the DOM tree during a document change but
253 * have not been sent to IME. Note that this should always return false when
254 * IsInDocumentChange() returns false.
256 bool HasAddedNodesDuringDocumentChange() const {
257 return mFirstAddedContainer
&& mLastAddedContainer
;
261 * Returns true if the passed-in node in aParent is the next node of
262 * mLastAddedContent in pre-order tree traversal of the DOM.
264 bool IsNextNodeOfLastAddedNode(nsINode
* aParent
, nsIContent
* aChild
) const;
266 void PostFocusSetNotification();
267 void MaybeNotifyIMEOfFocusSet();
268 void PostTextChangeNotification();
269 void MaybeNotifyIMEOfTextChange(const TextChangeDataBase
& aTextChangeData
);
270 void CancelNotifyingIMEOfTextChange();
271 void PostSelectionChangeNotification();
272 void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition
,
273 bool aCausedBySelectionEvent
,
274 bool aOccurredDuringComposition
);
275 void PostPositionChangeNotification();
276 void MaybeNotifyIMEOfPositionChange();
277 void CancelNotifyingIMEOfPositionChange();
278 void PostCompositionEventHandledNotification();
280 void NotifyContentAdded(nsINode
* aContainer
, nsIContent
* aFirstContent
,
281 nsIContent
* aLastContent
);
282 void ObserveEditableNode();
284 * NotifyIMEOfBlur() notifies IME of blur.
286 void NotifyIMEOfBlur();
288 * UnregisterObservers() unregisters all listeners and observers.
290 void UnregisterObservers();
291 void FlushMergeableNotifications();
292 bool NeedsTextChangeNotification() const {
293 return mIMENotificationRequests
&&
294 mIMENotificationRequests
->WantTextChange();
296 bool NeedsPositionChangeNotification() const {
297 return mIMENotificationRequests
&&
298 mIMENotificationRequests
->WantPositionChanged();
300 void ClearPendingNotifications() {
301 mNeedsToNotifyIMEOfFocusSet
= false;
302 mNeedsToNotifyIMEOfTextChange
= false;
303 mNeedsToNotifyIMEOfSelectionChange
= false;
304 mNeedsToNotifyIMEOfPositionChange
= false;
305 mNeedsToNotifyIMEOfCompositionEventHandled
= false;
306 mTextChangeData
.Clear();
308 bool NeedsToNotifyIMEOfSomething() const {
309 return mNeedsToNotifyIMEOfFocusSet
|| mNeedsToNotifyIMEOfTextChange
||
310 mNeedsToNotifyIMEOfSelectionChange
||
311 mNeedsToNotifyIMEOfPositionChange
||
312 mNeedsToNotifyIMEOfCompositionEventHandled
;
316 * UpdateSelectionCache() updates mSelectionData with the latest selection.
317 * This should be called only when IsSafeToNotifyIME() returns true.
319 MOZ_CAN_RUN_SCRIPT
bool UpdateSelectionCache(bool aRequireFlush
= true);
321 nsCOMPtr
<nsIWidget
> mWidget
;
322 // mFocusedWidget has the editor observed by the instance. E.g., if the
323 // focused editor is in XUL panel, this should be the widget of the panel.
324 // On the other hand, mWidget is its parent which handles IME.
325 nsCOMPtr
<nsIWidget
> mFocusedWidget
;
326 RefPtr
<dom::Selection
> mSelection
;
327 RefPtr
<dom::Element
> mRootElement
;
328 nsCOMPtr
<nsINode
> mEditableNode
;
329 nsCOMPtr
<nsIDocShell
> mDocShell
;
330 RefPtr
<EditorBase
> mEditorBase
;
333 * Helper classes to notify IME.
336 class AChangeEvent
: public Runnable
{
338 enum ChangeEventType
{
339 eChangeEventType_Focus
,
340 eChangeEventType_Selection
,
341 eChangeEventType_Text
,
342 eChangeEventType_Position
,
343 eChangeEventType_CompositionEventHandled
346 explicit AChangeEvent(const char* aName
,
347 IMEContentObserver
* aIMEContentObserver
)
349 mIMEContentObserver(do_GetWeakReference(
350 static_cast<nsIReflowObserver
*>(aIMEContentObserver
))) {
351 MOZ_ASSERT(aIMEContentObserver
);
354 already_AddRefed
<IMEContentObserver
> GetObserver() const {
355 nsCOMPtr
<nsIReflowObserver
> observer
=
356 do_QueryReferent(mIMEContentObserver
);
357 return observer
.forget().downcast
<IMEContentObserver
>();
360 nsWeakPtr mIMEContentObserver
;
363 * CanNotifyIME() checks if mIMEContentObserver can and should notify IME.
365 bool CanNotifyIME(ChangeEventType aChangeEventType
) const;
368 * IsSafeToNotifyIME() checks if it's safe to noitify IME.
370 bool IsSafeToNotifyIME(ChangeEventType aChangeEventType
) const;
373 class IMENotificationSender
: public AChangeEvent
{
375 explicit IMENotificationSender(IMEContentObserver
* aIMEContentObserver
)
376 : AChangeEvent("IMENotificationSender", aIMEContentObserver
),
378 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
;
380 void Dispatch(nsIDocShell
* aDocShell
);
383 MOZ_CAN_RUN_SCRIPT
void SendFocusSet();
384 MOZ_CAN_RUN_SCRIPT
void SendSelectionChange();
385 void SendTextChange();
386 void SendPositionChange();
387 void SendCompositionEventHandled();
392 // mQueuedSender is, it was put into the event queue but not run yet.
393 RefPtr
<IMENotificationSender
> mQueuedSender
;
396 * IMEContentObserver is a mutation observer of mRootContent. However,
397 * it needs to know the beginning of content changes and end of it too for
398 * reducing redundant computation of text offset with ContentEventHandler.
399 * Therefore, it needs helper class to listen only them since if
400 * both mutations were observed by IMEContentObserver directly, each
401 * methods need to check if the changing node is in mRootContent but it's
404 class DocumentObserver final
: public nsStubDocumentObserver
{
406 DocumentObserver() = delete;
407 explicit DocumentObserver(IMEContentObserver
& aIMEContentObserver
)
408 : mIMEContentObserver(&aIMEContentObserver
), mDocumentUpdating(0) {
409 SetEnabledCallbacks(nsIMutationObserver::kBeginUpdate
|
410 nsIMutationObserver::kEndUpdate
);
413 NS_DECL_CYCLE_COLLECTION_CLASS(DocumentObserver
)
414 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
415 NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE
416 NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE
418 void Observe(dom::Document
*);
419 void StopObserving();
422 bool Destroyed() const { return !mIMEContentObserver
; }
423 bool IsObserving() const { return mDocument
!= nullptr; }
424 bool IsUpdating() const { return mDocumentUpdating
!= 0; }
427 virtual ~DocumentObserver() { Destroy(); }
429 RefPtr
<IMEContentObserver
> mIMEContentObserver
;
430 RefPtr
<dom::Document
> mDocument
;
431 uint32_t mDocumentUpdating
;
433 RefPtr
<DocumentObserver
> mDocumentObserver
;
436 * FlatTextCache stores flat text length from start of the content to
437 * mNodeOffset of mContainerNode.
439 struct FlatTextCache
{
440 // mContainerNode and mNode represent a point in DOM tree. E.g.,
441 // if mContainerNode is a div element, mNode is a child.
442 nsCOMPtr
<nsINode
> mContainerNode
;
443 // mNode points to the last child which participates in the current
444 // mFlatTextLength. If mNode is null, then that means that the end point for
445 // mFlatTextLength is immediately before the first child of mContainerNode.
446 nsCOMPtr
<nsINode
> mNode
;
447 // Length of flat text generated from contents between the start of content
448 // and a child node whose index is mNodeOffset of mContainerNode.
449 uint32_t mFlatTextLength
;
451 FlatTextCache() : mFlatTextLength(0) {}
454 mContainerNode
= nullptr;
459 void Cache(nsINode
* aContainer
, nsINode
* aNode
, uint32_t aFlatTextLength
) {
460 MOZ_ASSERT(aContainer
, "aContainer must not be null");
461 MOZ_ASSERT(!aNode
|| aNode
->GetParentNode() == aContainer
,
462 "aNode must be either null or a child of aContainer");
463 mContainerNode
= aContainer
;
465 mFlatTextLength
= aFlatTextLength
;
468 bool Match(nsINode
* aContainer
, nsINode
* aNode
) const {
469 return aContainer
== mContainerNode
&& aNode
== mNode
;
472 // mEndOfAddedTextCache caches text length from the start of content to
473 // the end of the last added content only while an edit action is being
474 // handled by the editor and no other mutation (e.g., removing node)
476 FlatTextCache mEndOfAddedTextCache
;
477 // mStartOfRemovingTextRangeCache caches text length from the start of content
478 // to the start of the last removed content only while an edit action is being
479 // handled by the editor and no other mutation (e.g., adding node) occur.
480 FlatTextCache mStartOfRemovingTextRangeCache
;
482 // mFirstAddedContainer is parent node of first added node in current
483 // document change. So, this is not nullptr only when a node was added
484 // during a document change and the change has not been included into
485 // mTextChangeData yet.
486 // Note that this shouldn't be in cycle collection since this is not nullptr
487 // only during a document change.
488 nsCOMPtr
<nsINode
> mFirstAddedContainer
;
489 // mLastAddedContainer is parent node of last added node in current
490 // document change. So, this is not nullptr only when a node was added
491 // during a document change and the change has not been included into
492 // mTextChangeData yet.
493 // Note that this shouldn't be in cycle collection since this is not nullptr
494 // only during a document change.
495 nsCOMPtr
<nsINode
> mLastAddedContainer
;
497 // mFirstAddedContent is the first node added in mFirstAddedContainer.
498 nsCOMPtr
<nsIContent
> mFirstAddedContent
;
499 // mLastAddedContent is the last node added in mLastAddedContainer;
500 nsCOMPtr
<nsIContent
> mLastAddedContent
;
502 TextChangeData mTextChangeData
;
504 // mSelectionData is the last selection data which was notified. The
505 // selection information is modified by UpdateSelectionCache(). The reason
506 // of the selection change is modified by MaybeNotifyIMEOfSelectionChange().
507 SelectionChangeData mSelectionData
;
509 EventStateManager
* mESM
= nullptr;
511 const IMENotificationRequests
* mIMENotificationRequests
= nullptr;
512 int64_t mPreCharacterDataChangeLength
= -1;
513 uint32_t mSuppressNotifications
= 0;
515 // If the observing editor is a text control's one, this is set to the value
517 uint32_t mTextControlValueLength
= 0;
519 // mSendingNotification is a notification which is now sending from
520 // IMENotificationSender. When the value is NOTIFY_IME_OF_NOTHING, it's
521 // not sending any notification.
522 IMEMessage mSendingNotification
= widget::NOTIFY_IME_OF_NOTHING
;
524 bool mIsObserving
= false;
525 bool mIsTextControl
= false;
526 bool mIMEHasFocus
= false;
527 bool mNeedsToNotifyIMEOfFocusSet
= false;
528 bool mNeedsToNotifyIMEOfTextChange
= false;
529 bool mNeedsToNotifyIMEOfSelectionChange
= false;
530 bool mNeedsToNotifyIMEOfPositionChange
= false;
531 bool mNeedsToNotifyIMEOfCompositionEventHandled
= false;
532 // mIsHandlingQueryContentEvent is true when IMEContentObserver is handling
533 // WidgetQueryContentEvent with ContentEventHandler.
534 bool mIsHandlingQueryContentEvent
= false;
537 } // namespace mozilla
539 #endif // mozilla_IMEContentObserver_h