Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / events / IMEContentObserver.h
blob4eba07eadcdc1e1edaea84dbe37d56398e0edb43
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"
14 #include "nsCOMPtr.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"
25 class nsIContent;
26 class nsINode;
27 class nsPresContext;
29 namespace mozilla {
31 class EventStateManager;
32 class TextComposition;
34 namespace dom {
35 class Selection;
36 } // namespace dom
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 {
44 public:
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;
51 IMEContentObserver();
53 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
54 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IMEContentObserver,
55 nsIReflowObserver)
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
63 // nsIScrollObserver
64 virtual void ScrollPositionChanged() override;
66 /**
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);
77 /**
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);
91 /**
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.
112 void Destroy();
115 * Returns false if the instance refers some objects and observing them.
116 * Otherwise, true.
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.
135 * Otherwise, false.
137 MOZ_CAN_RUN_SCRIPT bool MaybeReinitialize(nsIWidget& aWidget,
138 nsPresContext& aPresContext,
139 dom::Element* aElement,
140 EditorBase& aEditorBase);
143 * Return true if this is observing editable content and aElement has focus.
144 * If aElement is a text control, check if this is observing its anonymous
145 * subtree. Otherwise, check if this is observing the children of aElement in
146 * the DOM tree. If aElement is nullptr, this returns true if entire the
147 * document is editable, e.g., in the designMode.
149 [[nodiscard]] bool IsObserving(const nsPresContext& aPresContext,
150 const dom::Element* aElement) const;
152 [[nodiscard]] bool IsBeingInitializedFor(const nsPresContext& aPresContext,
153 const dom::Element* aElement,
154 const EditorBase& aEditorBase) const;
155 bool IsObserving(const TextComposition& aTextComposition) const;
156 bool WasInitializedWith(const EditorBase& aEditorBase) const {
157 return mEditorBase == &aEditorBase;
159 bool IsEditorHandlingEventForComposition() const;
160 bool KeepAliveDuringDeactive() const {
161 return mIMENotificationRequests &&
162 mIMENotificationRequests->WantDuringDeactive();
164 [[nodiscard]] bool EditorIsTextEditor() const {
165 return mEditorBase && mEditorBase->IsTextEditor();
167 nsIWidget* GetWidget() const { return mWidget; }
168 void SuppressNotifyingIME();
169 void UnsuppressNotifyingIME();
170 nsPresContext* GetPresContext() const;
171 nsresult GetSelectionAndRoot(dom::Selection** aSelection,
172 dom::Element** aRootElement) const;
175 * TryToFlushPendingNotifications() should be called when pending events
176 * should be flushed. This tries to run the queued IMENotificationSender.
177 * Doesn't do anything in child processes where flushing happens
178 * asynchronously unless aAllowAsync is false.
180 void TryToFlushPendingNotifications(bool aAllowAsync);
183 * MaybeNotifyCompositionEventHandled() posts composition event handled
184 * notification into the pseudo queue.
186 void MaybeNotifyCompositionEventHandled();
189 * Following methods are called when the editor:
190 * - an edit action handled.
191 * - before handling an edit action.
192 * - canceled handling an edit action after calling BeforeEditAction().
194 void OnEditActionHandled();
195 void BeforeEditAction();
196 void CancelEditAction();
199 * Called when text control value is changed while this is not observing
200 * mRootElement. This is typically there is no frame for the editor (i.e.,
201 * no proper anonymous <div> element for the editor yet) or the TextEditor
202 * has not been created (i.e., IMEStateManager has not been reinitialized
203 * this instance with new anonymous <div> element yet).
205 void OnTextControlValueChangedWhileNotObservable(const nsAString& aNewValue);
207 dom::Element* GetObservingElement() const {
208 return mIsObserving ? mRootElement.get() : nullptr;
211 private:
212 ~IMEContentObserver() = default;
214 enum State {
215 eState_NotObserving,
216 eState_Initializing,
217 eState_StoppedObserving,
218 eState_Observing
220 State GetState() const;
221 MOZ_CAN_RUN_SCRIPT bool InitWithEditor(nsPresContext& aPresContext,
222 dom::Element* aElement,
223 EditorBase& aEditorBase);
224 void OnIMEReceivedFocus();
225 void Clear();
226 [[nodiscard]] bool IsObservingContent(const nsPresContext& aPresContext,
227 const dom::Element* aElement) const;
228 [[nodiscard]] bool IsReflowLocked() const;
229 [[nodiscard]] bool IsSafeToNotifyIME() const;
230 [[nodiscard]] bool IsEditorComposing() const;
232 // Following methods are called by DocumentObserver when
233 // beginning to update the contents and ending updating the contents.
234 void BeginDocumentUpdate();
235 void EndDocumentUpdate();
237 // Following methods manages added nodes during a document change.
240 * MaybeNotifyIMEOfAddedTextDuringDocumentChange() may send text change
241 * notification caused by the nodes added between mFirstAddedContent in
242 * mFirstAddedContainer and mLastAddedContent in
243 * mLastAddedContainer and forgets the range.
245 void MaybeNotifyIMEOfAddedTextDuringDocumentChange();
248 * IsInDocumentChange() returns true while the DOM tree is being modified
249 * with mozAutoDocUpdate. E.g., it's being modified by setting innerHTML or
250 * insertAdjacentHTML(). This returns false when user types something in
251 * the focused editor editor.
253 bool IsInDocumentChange() const {
254 return mDocumentObserver && mDocumentObserver->IsUpdating();
258 * Forget the range of added nodes during a document change.
260 void ClearAddedNodesDuringDocumentChange();
263 * HasAddedNodesDuringDocumentChange() returns true when this stores range
264 * of nodes which were added into the DOM tree during a document change but
265 * have not been sent to IME. Note that this should always return false when
266 * IsInDocumentChange() returns false.
268 bool HasAddedNodesDuringDocumentChange() const {
269 return mFirstAddedContainer && mLastAddedContainer;
273 * Returns true if the passed-in node in aParent is the next node of
274 * mLastAddedContent in pre-order tree traversal of the DOM.
276 bool IsNextNodeOfLastAddedNode(nsINode* aParent, nsIContent* aChild) const;
278 void PostFocusSetNotification();
279 void MaybeNotifyIMEOfFocusSet();
280 void PostTextChangeNotification();
281 void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData);
282 void CancelNotifyingIMEOfTextChange();
283 void PostSelectionChangeNotification();
284 void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition,
285 bool aCausedBySelectionEvent,
286 bool aOccurredDuringComposition);
287 void PostPositionChangeNotification();
288 void MaybeNotifyIMEOfPositionChange();
289 void CancelNotifyingIMEOfPositionChange();
290 void PostCompositionEventHandledNotification();
292 void NotifyContentAdded(nsINode* aContainer, nsIContent* aFirstContent,
293 nsIContent* aLastContent);
294 void ObserveEditableNode();
296 * NotifyIMEOfBlur() notifies IME of blur.
298 void NotifyIMEOfBlur();
300 * UnregisterObservers() unregisters all listeners and observers.
302 void UnregisterObservers();
303 void FlushMergeableNotifications();
304 bool NeedsTextChangeNotification() const {
305 return mIMENotificationRequests &&
306 mIMENotificationRequests->WantTextChange();
308 bool NeedsPositionChangeNotification() const {
309 return mIMENotificationRequests &&
310 mIMENotificationRequests->WantPositionChanged();
312 void ClearPendingNotifications() {
313 mNeedsToNotifyIMEOfFocusSet = false;
314 mNeedsToNotifyIMEOfTextChange = false;
315 mNeedsToNotifyIMEOfSelectionChange = false;
316 mNeedsToNotifyIMEOfPositionChange = false;
317 mNeedsToNotifyIMEOfCompositionEventHandled = false;
318 mTextChangeData.Clear();
320 bool NeedsToNotifyIMEOfSomething() const {
321 return mNeedsToNotifyIMEOfFocusSet || mNeedsToNotifyIMEOfTextChange ||
322 mNeedsToNotifyIMEOfSelectionChange ||
323 mNeedsToNotifyIMEOfPositionChange ||
324 mNeedsToNotifyIMEOfCompositionEventHandled;
328 * UpdateSelectionCache() updates mSelectionData with the latest selection.
329 * This should be called only when IsSafeToNotifyIME() returns true.
331 MOZ_CAN_RUN_SCRIPT bool UpdateSelectionCache(bool aRequireFlush = true);
333 nsCOMPtr<nsIWidget> mWidget;
334 // mFocusedWidget has the editor observed by the instance. E.g., if the
335 // focused editor is in XUL panel, this should be the widget of the panel.
336 // On the other hand, mWidget is its parent which handles IME.
337 nsCOMPtr<nsIWidget> mFocusedWidget;
338 RefPtr<dom::Selection> mSelection;
339 RefPtr<dom::Element> mRootElement;
340 nsCOMPtr<nsINode> mEditableNode;
341 nsCOMPtr<nsIDocShell> mDocShell;
342 RefPtr<EditorBase> mEditorBase;
345 * Helper classes to notify IME.
348 class AChangeEvent : public Runnable {
349 protected:
350 enum ChangeEventType {
351 eChangeEventType_Focus,
352 eChangeEventType_Selection,
353 eChangeEventType_Text,
354 eChangeEventType_Position,
355 eChangeEventType_CompositionEventHandled
358 explicit AChangeEvent(const char* aName,
359 IMEContentObserver* aIMEContentObserver)
360 : Runnable(aName),
361 mIMEContentObserver(do_GetWeakReference(
362 static_cast<nsIReflowObserver*>(aIMEContentObserver))) {
363 MOZ_ASSERT(aIMEContentObserver);
366 already_AddRefed<IMEContentObserver> GetObserver() const {
367 nsCOMPtr<nsIReflowObserver> observer =
368 do_QueryReferent(mIMEContentObserver);
369 return observer.forget().downcast<IMEContentObserver>();
372 nsWeakPtr mIMEContentObserver;
375 * CanNotifyIME() checks if mIMEContentObserver can and should notify IME.
377 bool CanNotifyIME(ChangeEventType aChangeEventType) const;
380 * IsSafeToNotifyIME() checks if it's safe to noitify IME.
382 bool IsSafeToNotifyIME(ChangeEventType aChangeEventType) const;
385 class IMENotificationSender : public AChangeEvent {
386 public:
387 explicit IMENotificationSender(IMEContentObserver* aIMEContentObserver)
388 : AChangeEvent("IMENotificationSender", aIMEContentObserver),
389 mIsRunning(false) {}
390 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override;
392 void Dispatch(nsIDocShell* aDocShell);
394 private:
395 MOZ_CAN_RUN_SCRIPT void SendFocusSet();
396 MOZ_CAN_RUN_SCRIPT void SendSelectionChange();
397 void SendTextChange();
398 void SendPositionChange();
399 void SendCompositionEventHandled();
401 bool mIsRunning;
404 // mQueuedSender is, it was put into the event queue but not run yet.
405 RefPtr<IMENotificationSender> mQueuedSender;
408 * IMEContentObserver is a mutation observer of mRootContent. However,
409 * it needs to know the beginning of content changes and end of it too for
410 * reducing redundant computation of text offset with ContentEventHandler.
411 * Therefore, it needs helper class to listen only them since if
412 * both mutations were observed by IMEContentObserver directly, each
413 * methods need to check if the changing node is in mRootContent but it's
414 * too expensive.
416 class DocumentObserver final : public nsStubDocumentObserver {
417 public:
418 DocumentObserver() = delete;
419 explicit DocumentObserver(IMEContentObserver& aIMEContentObserver)
420 : mIMEContentObserver(&aIMEContentObserver), mDocumentUpdating(0) {
421 SetEnabledCallbacks(nsIMutationObserver::kBeginUpdate |
422 nsIMutationObserver::kEndUpdate);
425 NS_DECL_CYCLE_COLLECTION_CLASS(DocumentObserver)
426 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
427 NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE
428 NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE
430 void Observe(dom::Document*);
431 void StopObserving();
432 void Destroy();
434 bool Destroyed() const { return !mIMEContentObserver; }
435 bool IsObserving() const { return mDocument != nullptr; }
436 bool IsUpdating() const { return mDocumentUpdating != 0; }
438 private:
439 virtual ~DocumentObserver() { Destroy(); }
441 RefPtr<IMEContentObserver> mIMEContentObserver;
442 RefPtr<dom::Document> mDocument;
443 uint32_t mDocumentUpdating;
445 RefPtr<DocumentObserver> mDocumentObserver;
448 * FlatTextCache stores flat text length from start of the content to
449 * mNodeOffset of mContainerNode.
451 struct FlatTextCache {
452 // mContainerNode and mNode represent a point in DOM tree. E.g.,
453 // if mContainerNode is a div element, mNode is a child.
454 nsCOMPtr<nsINode> mContainerNode;
455 // mNode points to the last child which participates in the current
456 // mFlatTextLength. If mNode is null, then that means that the end point for
457 // mFlatTextLength is immediately before the first child of mContainerNode.
458 nsCOMPtr<nsINode> mNode;
459 // Length of flat text generated from contents between the start of content
460 // and a child node whose index is mNodeOffset of mContainerNode.
461 uint32_t mFlatTextLength;
463 FlatTextCache() : mFlatTextLength(0) {}
465 void Clear() {
466 mContainerNode = nullptr;
467 mNode = nullptr;
468 mFlatTextLength = 0;
471 void Cache(nsINode* aContainer, nsINode* aNode, uint32_t aFlatTextLength) {
472 MOZ_ASSERT(aContainer, "aContainer must not be null");
473 MOZ_ASSERT(!aNode || aNode->GetParentNode() == aContainer,
474 "aNode must be either null or a child of aContainer");
475 mContainerNode = aContainer;
476 mNode = aNode;
477 mFlatTextLength = aFlatTextLength;
480 bool Match(nsINode* aContainer, nsINode* aNode) const {
481 return aContainer == mContainerNode && aNode == mNode;
484 // mEndOfAddedTextCache caches text length from the start of content to
485 // the end of the last added content only while an edit action is being
486 // handled by the editor and no other mutation (e.g., removing node)
487 // occur.
488 FlatTextCache mEndOfAddedTextCache;
489 // mStartOfRemovingTextRangeCache caches text length from the start of content
490 // to the start of the last removed content only while an edit action is being
491 // handled by the editor and no other mutation (e.g., adding node) occur.
492 FlatTextCache mStartOfRemovingTextRangeCache;
494 // mFirstAddedContainer is parent node of first added node in current
495 // document change. So, this is not nullptr only when a node was added
496 // during a document change and the change has not been included into
497 // mTextChangeData yet.
498 // Note that this shouldn't be in cycle collection since this is not nullptr
499 // only during a document change.
500 nsCOMPtr<nsINode> mFirstAddedContainer;
501 // mLastAddedContainer is parent node of last added node in current
502 // document change. So, this is not nullptr only when a node was added
503 // during a document change and the change has not been included into
504 // mTextChangeData yet.
505 // Note that this shouldn't be in cycle collection since this is not nullptr
506 // only during a document change.
507 nsCOMPtr<nsINode> mLastAddedContainer;
509 // mFirstAddedContent is the first node added in mFirstAddedContainer.
510 nsCOMPtr<nsIContent> mFirstAddedContent;
511 // mLastAddedContent is the last node added in mLastAddedContainer;
512 nsCOMPtr<nsIContent> mLastAddedContent;
514 TextChangeData mTextChangeData;
516 // mSelectionData is the last selection data which was notified. The
517 // selection information is modified by UpdateSelectionCache(). The reason
518 // of the selection change is modified by MaybeNotifyIMEOfSelectionChange().
519 SelectionChangeData mSelectionData;
521 EventStateManager* mESM = nullptr;
523 const IMENotificationRequests* mIMENotificationRequests = nullptr;
524 int64_t mPreCharacterDataChangeLength = -1;
525 uint32_t mSuppressNotifications = 0;
527 // If the observing editor is a text control's one, this is set to the value
528 // length.
529 uint32_t mTextControlValueLength = 0;
531 // mSendingNotification is a notification which is now sending from
532 // IMENotificationSender. When the value is NOTIFY_IME_OF_NOTHING, it's
533 // not sending any notification.
534 IMEMessage mSendingNotification = widget::NOTIFY_IME_OF_NOTHING;
536 bool mIsObserving = false;
537 bool mIsTextControl = false;
538 bool mIMEHasFocus = false;
539 bool mNeedsToNotifyIMEOfFocusSet = false;
540 bool mNeedsToNotifyIMEOfTextChange = false;
541 bool mNeedsToNotifyIMEOfSelectionChange = false;
542 bool mNeedsToNotifyIMEOfPositionChange = false;
543 bool mNeedsToNotifyIMEOfCompositionEventHandled = false;
544 // mIsHandlingQueryContentEvent is true when IMEContentObserver is handling
545 // WidgetQueryContentEvent with ContentEventHandler.
546 bool mIsHandlingQueryContentEvent = false;
549 } // namespace mozilla
551 #endif // mozilla_IMEContentObserver_h