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/. */
6 
7 #ifndef mozilla_IMEContentObserver_h
8 #define mozilla_IMEContentObserver_h
9 
10 #include "mozilla/Attributes.h"
11 #include "mozilla/EditorBase.h"
12 #include "mozilla/dom/Selection.h"
13 #include "nsCOMPtr.h"
14 #include "nsCycleCollectionParticipant.h"
15 #include "nsIDocShell.h"  // XXX Why does only this need to be included here?
16 #include "nsIReflowObserver.h"
17 #include "nsIScrollObserver.h"
18 #include "nsIWidget.h"
19 #include "nsStubDocumentObserver.h"
20 #include "nsStubMutationObserver.h"
21 #include "nsThreadUtils.h"
22 #include "nsWeakReference.h"
23 
24 class nsIContent;
25 class nsINode;
26 class nsPresContext;
27 
28 namespace mozilla {
29 
30 class EventStateManager;
31 class TextComposition;
32 
33 namespace dom {
34 class Selection;
35 }  // namespace dom
36 
37 // IMEContentObserver notifies widget of any text and selection changes
38 // in the currently focused editor
39 class IMEContentObserver final : public nsStubMutationObserver,
40                                  public nsIReflowObserver,
41                                  public nsIScrollObserver,
42                                  public nsSupportsWeakReference {
43  public:
44   typedef widget::IMENotification::SelectionChangeData SelectionChangeData;
45   typedef widget::IMENotification::TextChangeData TextChangeData;
46   typedef widget::IMENotification::TextChangeDataBase TextChangeDataBase;
47   typedef widget::IMENotificationRequests IMENotificationRequests;
48   typedef widget::IMEMessage IMEMessage;
49 
50   IMEContentObserver();
51 
52   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
53   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IMEContentObserver,
54                                            nsIReflowObserver)
55   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
56   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
57   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
58   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
59   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
60   NS_DECL_NSIREFLOWOBSERVER
61 
62   // nsIScrollObserver
63   virtual void ScrollPositionChanged() override;
64 
65   /**
66    * OnSelectionChange() is called when selection is changed in the editor.
67    */
68   void OnSelectionChange(dom::Selection& aSelection);
69 
70   MOZ_CAN_RUN_SCRIPT bool OnMouseButtonEvent(nsPresContext* aPresContext,
71                                              WidgetMouseEvent* aMouseEvent);
72 
73   MOZ_CAN_RUN_SCRIPT nsresult
74   HandleQueryContentEvent(WidgetQueryContentEvent* aEvent);
75 
76   /**
77    * Init() initializes the instance, i.e., retrieving necessary objects and
78    * starts to observe something.
79    * Be aware, callers of this method need to guarantee that the instance
80    * won't be released during calling this.
81    *
82    * @param aWidget         The widget which can access native IME.
83    * @param aPresContext    The PresContext which has aContent.
84    * @param aContent        An editable element or nullptr if this will observe
85    *                        design mode document.
86    * @param aEditorBase     The editor which is associated with aContent.
87    */
88   MOZ_CAN_RUN_SCRIPT void Init(nsIWidget& aWidget, nsPresContext& aPresContext,
89                                nsIContent* aContent, EditorBase& aEditorBase);
90 
91   /**
92    * Destroy() finalizes the instance, i.e., stops observing contents and
93    * clearing the members.
94    * Be aware, callers of this method need to guarantee that the instance
95    * won't be released during calling this.
96    */
97   void Destroy();
98 
99   /**
100    * Returns false if the instance refers some objects and observing them.
101    * Otherwise, true.
102    */
103   bool Destroyed() const;
104 
105   /**
106    * IMEContentObserver is stored by EventStateManager during observing.
107    * DisconnectFromEventStateManager() is called when EventStateManager stops
108    * storing the instance.
109    */
110   void DisconnectFromEventStateManager();
111 
112   /**
113    * MaybeReinitialize() tries to restart to observe the editor's root node.
114    * This is useful when the editor is reframed and all children are replaced
115    * with new node instances.
116    * Be aware, callers of this method need to guarantee that the instance
117    * won't be released during calling this.
118    *
119    * @return            Returns true if the instance is managing the content.
120    *                    Otherwise, false.
121    */
122   MOZ_CAN_RUN_SCRIPT bool MaybeReinitialize(nsIWidget& aWidget,
123                                             nsPresContext& aPresContext,
124                                             nsIContent* aContent,
125                                             EditorBase& aEditorBase);
126 
127   bool IsManaging(nsPresContext* aPresContext, nsIContent* aContent) const;
128   bool IsManaging(const TextComposition* aTextComposition) const;
WasInitializedWith(const EditorBase & aEditorBase)129   bool WasInitializedWith(const EditorBase& aEditorBase) const {
130     return mEditorBase == &aEditorBase;
131   }
132   bool IsEditorHandlingEventForComposition() const;
KeepAliveDuringDeactive()133   bool KeepAliveDuringDeactive() const {
134     return mIMENotificationRequests &&
135            mIMENotificationRequests->WantDuringDeactive();
136   }
GetWidget()137   nsIWidget* GetWidget() const { return mWidget; }
138   void SuppressNotifyingIME();
139   void UnsuppressNotifyingIME();
140   nsPresContext* GetPresContext() const;
141   nsresult GetSelectionAndRoot(dom::Selection** aSelection,
142                                nsIContent** aRoot) const;
143 
144   /**
145    * TryToFlushPendingNotifications() should be called when pending events
146    * should be flushed.  This tries to run the queued IMENotificationSender.
147    * Doesn't do anything in child processes where flushing happens
148    * asynchronously unless aAllowAsync is false.
149    */
150   void TryToFlushPendingNotifications(bool aAllowAsync);
151 
152   /**
153    * MaybeNotifyCompositionEventHandled() posts composition event handled
154    * notification into the pseudo queue.
155    */
156   void MaybeNotifyCompositionEventHandled();
157 
158   /**
159    * Following methods are called when the editor:
160    *   - an edit action handled.
161    *   - before handling an edit action.
162    *   - canceled handling an edit action after calling BeforeEditAction().
163    */
164   void OnEditActionHandled();
165   void BeforeEditAction();
166   void CancelEditAction();
167 
168  private:
169   ~IMEContentObserver() = default;
170 
171   enum State {
172     eState_NotObserving,
173     eState_Initializing,
174     eState_StoppedObserving,
175     eState_Observing
176   };
177   State GetState() const;
178   MOZ_CAN_RUN_SCRIPT bool InitWithEditor(nsPresContext& aPresContext,
179                                          nsIContent* aContent,
180                                          EditorBase& aEditorBase);
181   void OnIMEReceivedFocus();
182   void Clear();
183   bool IsObservingContent(nsPresContext* aPresContext,
184                           nsIContent* aContent) const;
185   bool IsReflowLocked() const;
186   bool IsSafeToNotifyIME() const;
187   bool IsEditorComposing() const;
188 
189   // Following methods are called by DocumentObserver when
190   // beginning to update the contents and ending updating the contents.
191   void BeginDocumentUpdate();
192   void EndDocumentUpdate();
193 
194   // Following methods manages added nodes during a document change.
195 
196   /**
197    * MaybeNotifyIMEOfAddedTextDuringDocumentChange() may send text change
198    * notification caused by the nodes added between mFirstAddedContent in
199    * mFirstAddedContainer and mLastAddedContent in
200    * mLastAddedContainer and forgets the range.
201    */
202   void MaybeNotifyIMEOfAddedTextDuringDocumentChange();
203 
204   /**
205    * IsInDocumentChange() returns true while the DOM tree is being modified
206    * with mozAutoDocUpdate.  E.g., it's being modified by setting innerHTML or
207    * insertAdjacentHTML().  This returns false when user types something in
208    * the focused editor editor.
209    */
IsInDocumentChange()210   bool IsInDocumentChange() const {
211     return mDocumentObserver && mDocumentObserver->IsUpdating();
212   }
213 
214   /**
215    * Forget the range of added nodes during a document change.
216    */
217   void ClearAddedNodesDuringDocumentChange();
218 
219   /**
220    * HasAddedNodesDuringDocumentChange() returns true when this stores range
221    * of nodes which were added into the DOM tree during a document change but
222    * have not been sent to IME.  Note that this should always return false when
223    * IsInDocumentChange() returns false.
224    */
HasAddedNodesDuringDocumentChange()225   bool HasAddedNodesDuringDocumentChange() const {
226     return mFirstAddedContainer && mLastAddedContainer;
227   }
228 
229   /**
230    * Returns true if the passed-in node in aParent is the next node of
231    * mLastAddedContent in pre-order tree traversal of the DOM.
232    */
233   bool IsNextNodeOfLastAddedNode(nsINode* aParent, nsIContent* aChild) const;
234 
235   void PostFocusSetNotification();
236   void MaybeNotifyIMEOfFocusSet();
237   void PostTextChangeNotification();
238   void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData);
239   void CancelNotifyingIMEOfTextChange();
240   void PostSelectionChangeNotification();
241   void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition,
242                                        bool aCausedBySelectionEvent,
243                                        bool aOccurredDuringComposition);
244   void PostPositionChangeNotification();
245   void MaybeNotifyIMEOfPositionChange();
246   void CancelNotifyingIMEOfPositionChange();
247   void PostCompositionEventHandledNotification();
248 
249   void NotifyContentAdded(nsINode* aContainer, nsIContent* aFirstContent,
250                           nsIContent* aLastContent);
251   void ObserveEditableNode();
252   /**
253    *  NotifyIMEOfBlur() notifies IME of blur.
254    */
255   void NotifyIMEOfBlur();
256   /**
257    *  UnregisterObservers() unregisters all listeners and observers.
258    */
259   void UnregisterObservers();
260   void FlushMergeableNotifications();
NeedsTextChangeNotification()261   bool NeedsTextChangeNotification() const {
262     return mIMENotificationRequests &&
263            mIMENotificationRequests->WantTextChange();
264   }
NeedsPositionChangeNotification()265   bool NeedsPositionChangeNotification() const {
266     return mIMENotificationRequests &&
267            mIMENotificationRequests->WantPositionChanged();
268   }
ClearPendingNotifications()269   void ClearPendingNotifications() {
270     mNeedsToNotifyIMEOfFocusSet = false;
271     mNeedsToNotifyIMEOfTextChange = false;
272     mNeedsToNotifyIMEOfSelectionChange = false;
273     mNeedsToNotifyIMEOfPositionChange = false;
274     mNeedsToNotifyIMEOfCompositionEventHandled = false;
275     mTextChangeData.Clear();
276   }
NeedsToNotifyIMEOfSomething()277   bool NeedsToNotifyIMEOfSomething() const {
278     return mNeedsToNotifyIMEOfFocusSet || mNeedsToNotifyIMEOfTextChange ||
279            mNeedsToNotifyIMEOfSelectionChange ||
280            mNeedsToNotifyIMEOfPositionChange ||
281            mNeedsToNotifyIMEOfCompositionEventHandled;
282   }
283 
284   /**
285    * UpdateSelectionCache() updates mSelectionData with the latest selection.
286    * This should be called only when IsSafeToNotifyIME() returns true.
287    */
288   MOZ_CAN_RUN_SCRIPT bool UpdateSelectionCache(bool aRequireFlush = true);
289 
290   nsCOMPtr<nsIWidget> mWidget;
291   // mFocusedWidget has the editor observed by the instance.  E.g., if the
292   // focused editor is in XUL panel, this should be the widget of the panel.
293   // On the other hand, mWidget is its parent which handles IME.
294   nsCOMPtr<nsIWidget> mFocusedWidget;
295   RefPtr<dom::Selection> mSelection;
296   nsCOMPtr<nsIContent> mRootContent;
297   nsCOMPtr<nsINode> mEditableNode;
298   nsCOMPtr<nsIDocShell> mDocShell;
299   RefPtr<EditorBase> mEditorBase;
300 
301   /**
302    * Helper classes to notify IME.
303    */
304 
305   class AChangeEvent : public Runnable {
306    protected:
307     enum ChangeEventType {
308       eChangeEventType_Focus,
309       eChangeEventType_Selection,
310       eChangeEventType_Text,
311       eChangeEventType_Position,
312       eChangeEventType_CompositionEventHandled
313     };
314 
AChangeEvent(const char * aName,IMEContentObserver * aIMEContentObserver)315     explicit AChangeEvent(const char* aName,
316                           IMEContentObserver* aIMEContentObserver)
317         : Runnable(aName),
318           mIMEContentObserver(do_GetWeakReference(
319               static_cast<nsIReflowObserver*>(aIMEContentObserver))) {
320       MOZ_ASSERT(aIMEContentObserver);
321     }
322 
GetObserver()323     already_AddRefed<IMEContentObserver> GetObserver() const {
324       nsCOMPtr<nsIReflowObserver> observer =
325           do_QueryReferent(mIMEContentObserver);
326       return observer.forget().downcast<IMEContentObserver>();
327     }
328 
329     nsWeakPtr mIMEContentObserver;
330 
331     /**
332      * CanNotifyIME() checks if mIMEContentObserver can and should notify IME.
333      */
334     bool CanNotifyIME(ChangeEventType aChangeEventType) const;
335 
336     /**
337      * IsSafeToNotifyIME() checks if it's safe to noitify IME.
338      */
339     bool IsSafeToNotifyIME(ChangeEventType aChangeEventType) const;
340   };
341 
342   class IMENotificationSender : public AChangeEvent {
343    public:
IMENotificationSender(IMEContentObserver * aIMEContentObserver)344     explicit IMENotificationSender(IMEContentObserver* aIMEContentObserver)
345         : AChangeEvent("IMENotificationSender", aIMEContentObserver),
346           mIsRunning(false) {}
347     MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override;
348 
349     void Dispatch(nsIDocShell* aDocShell);
350 
351    private:
352     MOZ_CAN_RUN_SCRIPT void SendFocusSet();
353     MOZ_CAN_RUN_SCRIPT void SendSelectionChange();
354     void SendTextChange();
355     void SendPositionChange();
356     void SendCompositionEventHandled();
357 
358     bool mIsRunning;
359   };
360 
361   // mQueuedSender is, it was put into the event queue but not run yet.
362   RefPtr<IMENotificationSender> mQueuedSender;
363 
364   /**
365    * IMEContentObserver is a mutation observer of mRootContent.  However,
366    * it needs to know the beginning of content changes and end of it too for
367    * reducing redundant computation of text offset with ContentEventHandler.
368    * Therefore, it needs helper class to listen only them since if
369    * both mutations were observed by IMEContentObserver directly, each
370    * methods need to check if the changing node is in mRootContent but it's
371    * too expensive.
372    */
373   class DocumentObserver final : public nsStubDocumentObserver {
374    public:
DocumentObserver(IMEContentObserver & aIMEContentObserver)375     explicit DocumentObserver(IMEContentObserver& aIMEContentObserver)
376         : mIMEContentObserver(&aIMEContentObserver), mDocumentUpdating(0) {}
377 
378     NS_DECL_CYCLE_COLLECTION_CLASS(DocumentObserver)
379     NS_DECL_CYCLE_COLLECTING_ISUPPORTS
380     NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE
381     NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE
382 
383     void Observe(dom::Document*);
384     void StopObserving();
385     void Destroy();
386 
Destroyed()387     bool Destroyed() const { return !mIMEContentObserver; }
IsObserving()388     bool IsObserving() const { return mDocument != nullptr; }
IsUpdating()389     bool IsUpdating() const { return mDocumentUpdating != 0; }
390 
391    private:
392     DocumentObserver() = delete;
~DocumentObserver()393     virtual ~DocumentObserver() { Destroy(); }
394 
395     RefPtr<IMEContentObserver> mIMEContentObserver;
396     RefPtr<dom::Document> mDocument;
397     uint32_t mDocumentUpdating;
398   };
399   RefPtr<DocumentObserver> mDocumentObserver;
400 
401   /**
402    * FlatTextCache stores flat text length from start of the content to
403    * mNodeOffset of mContainerNode.
404    */
405   struct FlatTextCache {
406     // mContainerNode and mNode represent a point in DOM tree.  E.g.,
407     // if mContainerNode is a div element, mNode is a child.
408     nsCOMPtr<nsINode> mContainerNode;
409     // mNode points to the last child which participates in the current
410     // mFlatTextLength. If mNode is null, then that means that the end point for
411     // mFlatTextLength is immediately before the first child of mContainerNode.
412     nsCOMPtr<nsINode> mNode;
413     // Length of flat text generated from contents between the start of content
414     // and a child node whose index is mNodeOffset of mContainerNode.
415     uint32_t mFlatTextLength;
416 
FlatTextCacheFlatTextCache417     FlatTextCache() : mFlatTextLength(0) {}
418 
ClearFlatTextCache419     void Clear() {
420       mContainerNode = nullptr;
421       mNode = nullptr;
422       mFlatTextLength = 0;
423     }
424 
CacheFlatTextCache425     void Cache(nsINode* aContainer, nsINode* aNode, uint32_t aFlatTextLength) {
426       MOZ_ASSERT(aContainer, "aContainer must not be null");
427       MOZ_ASSERT(!aNode || aNode->GetParentNode() == aContainer,
428                  "aNode must be either null or a child of aContainer");
429       mContainerNode = aContainer;
430       mNode = aNode;
431       mFlatTextLength = aFlatTextLength;
432     }
433 
MatchFlatTextCache434     bool Match(nsINode* aContainer, nsINode* aNode) const {
435       return aContainer == mContainerNode && aNode == mNode;
436     }
437   };
438   // mEndOfAddedTextCache caches text length from the start of content to
439   // the end of the last added content only while an edit action is being
440   // handled by the editor and no other mutation (e.g., removing node)
441   // occur.
442   FlatTextCache mEndOfAddedTextCache;
443   // mStartOfRemovingTextRangeCache caches text length from the start of content
444   // to the start of the last removed content only while an edit action is being
445   // handled by the editor and no other mutation (e.g., adding node) occur.
446   FlatTextCache mStartOfRemovingTextRangeCache;
447 
448   // mFirstAddedContainer is parent node of first added node in current
449   // document change.  So, this is not nullptr only when a node was added
450   // during a document change and the change has not been included into
451   // mTextChangeData yet.
452   // Note that this shouldn't be in cycle collection since this is not nullptr
453   // only during a document change.
454   nsCOMPtr<nsINode> mFirstAddedContainer;
455   // mLastAddedContainer is parent node of last added node in current
456   // document change.  So, this is not nullptr only when a node was added
457   // during a document change and the change has not been included into
458   // mTextChangeData yet.
459   // Note that this shouldn't be in cycle collection since this is not nullptr
460   // only during a document change.
461   nsCOMPtr<nsINode> mLastAddedContainer;
462 
463   // mFirstAddedContent is the first node added in mFirstAddedContainer.
464   nsCOMPtr<nsIContent> mFirstAddedContent;
465   // mLastAddedContent is the last node added in mLastAddedContainer;
466   nsCOMPtr<nsIContent> mLastAddedContent;
467 
468   TextChangeData mTextChangeData;
469 
470   // mSelectionData is the last selection data which was notified.  The
471   // selection information is modified by UpdateSelectionCache().  The reason
472   // of the selection change is modified by MaybeNotifyIMEOfSelectionChange().
473   SelectionChangeData mSelectionData;
474 
475   EventStateManager* mESM;
476 
477   const IMENotificationRequests* mIMENotificationRequests;
478   uint32_t mSuppressNotifications;
479   int64_t mPreCharacterDataChangeLength;
480 
481   // mSendingNotification is a notification which is now sending from
482   // IMENotificationSender.  When the value is NOTIFY_IME_OF_NOTHING, it's
483   // not sending any notification.
484   IMEMessage mSendingNotification;
485 
486   bool mIsObserving;
487   bool mIMEHasFocus;
488   bool mNeedsToNotifyIMEOfFocusSet;
489   bool mNeedsToNotifyIMEOfTextChange;
490   bool mNeedsToNotifyIMEOfSelectionChange;
491   bool mNeedsToNotifyIMEOfPositionChange;
492   bool mNeedsToNotifyIMEOfCompositionEventHandled;
493   // mIsHandlingQueryContentEvent is true when IMEContentObserver is handling
494   // WidgetQueryContentEvent with ContentEventHandler.
495   bool mIsHandlingQueryContentEvent;
496 };
497 
498 }  // namespace mozilla
499 
500 #endif  // mozilla_IMEContentObserver_h
501