Coverage Report

Created: 2018-09-25 14:53

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