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