1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #ifndef mozilla_textcompositionsynthesizer_h_
7 #define mozilla_textcompositionsynthesizer_h_
8 
9 #include "mozilla/RefPtr.h"
10 #include "nsString.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/EventForwards.h"
13 #include "mozilla/TextEventDispatcherListener.h"
14 #include "mozilla/TextRange.h"
15 #include "mozilla/widget/IMEData.h"
16 
17 class nsIWidget;
18 
19 namespace mozilla {
20 namespace widget {
21 
22 class PuppetWidget;
23 
24 /**
25  * TextEventDispatcher is a helper class for dispatching widget events defined
26  * in TextEvents.h.  Currently, this is a helper for dispatching
27  * WidgetCompositionEvent and WidgetKeyboardEvent.  This manages the behavior
28  * of them for conforming to DOM Level 3 Events.
29  * An instance of this class is created by nsIWidget instance and owned by it.
30  * This is typically created only by the top level widgets because only they
31  * handle IME.
32  */
33 
34 class TextEventDispatcher final {
35   ~TextEventDispatcher() = default;
36 
37   NS_INLINE_DECL_REFCOUNTING(TextEventDispatcher)
38 
39  public:
40   explicit TextEventDispatcher(nsIWidget* aWidget);
41 
42   /**
43    * Initializes the instance for IME or automated test.  Either IME or tests
44    * need to call one of them before starting composition.  If they return
45    * NS_ERROR_ALREADY_INITIALIZED, it means that the listener already listens
46    * notifications from TextEventDispatcher for same purpose (for IME or tests).
47    * If this returns another error, the caller shouldn't keep starting
48    * composition.
49    *
50    * @param aListener       Specify the listener to listen notifications and
51    *                        requests.  This must not be null.
52    *                        NOTE: aListener is stored as weak reference in
53    *                              TextEventDispatcher.  See mListener
54    *                              definition below.
55    */
56   nsresult BeginInputTransaction(TextEventDispatcherListener* aListener);
57   nsresult BeginTestInputTransaction(TextEventDispatcherListener* aListener,
58                                      bool aIsAPZAware);
59   nsresult BeginNativeInputTransaction();
60 
61   /**
62    * BeginInputTransactionFor() should be used when aPuppetWidget dispatches
63    * a composition or keyboard event coming from its parent process.
64    */
65   nsresult BeginInputTransactionFor(const WidgetGUIEvent* aEvent,
66                                     PuppetWidget* aPuppetWidget);
67 
68   /**
69    * EndInputTransaction() should be called when the listener stops using
70    * the TextEventDispatcher.
71    *
72    * @param aListener       The listener using the TextEventDispatcher instance.
73    */
74   void EndInputTransaction(TextEventDispatcherListener* aListener);
75 
76   /**
77    * OnDestroyWidget() is called when mWidget is being destroyed.
78    */
79   void OnDestroyWidget();
80 
GetWidget()81   nsIWidget* GetWidget() const { return mWidget; }
82 
IMENotificationRequestsRef()83   const IMENotificationRequests& IMENotificationRequestsRef() const {
84     return mIMENotificationRequests;
85   }
86 
87   /**
88    * OnWidgetChangeIMENotificationRequests() is called when aWidget's
89    * IMENotificationRequest is maybe modified by unusual path.  E.g.,
90    * modified in an async path.
91    */
OnWidgetChangeIMENotificationRequests(nsIWidget * aWidget)92   void OnWidgetChangeIMENotificationRequests(nsIWidget* aWidget) {
93     MOZ_ASSERT(aWidget);
94     if (mWidget == aWidget) {
95       UpdateNotificationRequests();
96     }
97   }
98 
99   /**
100    * GetState() returns current state of this class.
101    *
102    * @return        NS_OK: Fine to compose text.
103    *                NS_ERROR_NOT_INITIALIZED: BeginInputTransaction() or
104    *                                          BeginInputTransactionForTests()
105    *                                          should be called.
106    *                NS_ERROR_NOT_AVAILABLE: The widget isn't available for
107    *                                        composition.
108    */
109   nsresult GetState() const;
110 
111   /**
112    * IsComposing() returns true after calling StartComposition() and before
113    * calling CommitComposition().  In other words, native IME has composition
114    * when this returns true.
115    */
IsComposing()116   bool IsComposing() const { return mIsComposing; }
117 
118   /**
119    * IsHandlingComposition() returns true after calling StartComposition() and
120    * content has not handled eCompositionCommit(AsIs) event.  In other words,
121    * our content has composition when this returns true.
122    */
IsHandlingComposition()123   bool IsHandlingComposition() const { return mIsHandlingComposition; }
124 
125   /**
126    * IsInNativeInputTransaction() returns true if native IME handler began a
127    * transaction and it's not finished yet.
128    */
IsInNativeInputTransaction()129   bool IsInNativeInputTransaction() const {
130     return mInputTransactionType == eNativeInputTransaction;
131   }
132 
133   /**
134    * IsDispatchingEvent() returns true while this instance dispatching an event.
135    */
IsDispatchingEvent()136   bool IsDispatchingEvent() const { return mDispatchingEvent > 0; }
137 
138   /**
139    * GetPseudoIMEContext() returns pseudo native IME context if there is an
140    * input transaction whose type is not for native event handler.
141    * Otherwise, returns nullptr.
142    */
GetPseudoIMEContext()143   void* GetPseudoIMEContext() const {
144     if (mInputTransactionType == eNoInputTransaction ||
145         mInputTransactionType == eNativeInputTransaction) {
146       return nullptr;
147     }
148     return const_cast<TextEventDispatcher*>(this);
149   }
150 
151   /**
152    * StartComposition() starts composition explicitly.
153    *
154    * @param aEventTime  If this is not nullptr, WidgetCompositionEvent will
155    *                    be initialized with this.  Otherwise, initialized
156    *                    with the time at initializing.
157    */
158   nsresult StartComposition(nsEventStatus& aStatus,
159                             const WidgetEventTime* aEventTime = nullptr);
160 
161   /**
162    * CommitComposition() commits composition.
163    *
164    * @param aCommitString   If this is null, commits with the last composition
165    *                        string.  Otherwise, commits the composition with
166    *                        this value.
167    * @param aEventTime      If this is not nullptr, WidgetCompositionEvent will
168    *                        be initialized with this.  Otherwise, initialized
169    *                        with the time at initializing.
170    */
171   nsresult CommitComposition(nsEventStatus& aStatus,
172                              const nsAString* aCommitString = nullptr,
173                              const WidgetEventTime* aEventTime = nullptr);
174 
175   /**
176    * SetPendingCompositionString() sets new composition string which will be
177    * dispatched with eCompositionChange event by calling Flush().
178    *
179    * @param aString         New composition string.
180    */
SetPendingCompositionString(const nsAString & aString)181   nsresult SetPendingCompositionString(const nsAString& aString) {
182     return mPendingComposition.SetString(aString);
183   }
184 
185   /**
186    * AppendClauseToPendingComposition() appends a clause information to
187    * the pending composition string.
188    *
189    * @param aLength         Length of the clause.
190    * @param aTextRangeType  One of TextRangeType::eRawClause,
191    *                        TextRangeType::eSelectedRawClause,
192    *                        TextRangeType::eConvertedClause or
193    *                        TextRangeType::eSelectedClause.
194    */
AppendClauseToPendingComposition(uint32_t aLength,TextRangeType aTextRangeType)195   nsresult AppendClauseToPendingComposition(uint32_t aLength,
196                                             TextRangeType aTextRangeType) {
197     return mPendingComposition.AppendClause(aLength, aTextRangeType);
198   }
199 
200   /**
201    * SetCaretInPendingComposition() sets caret position in the pending
202    * composition string and its length.  This is optional.  If IME doesn't
203    * want to show caret, it shouldn't need to call this.
204    *
205    * @param aOffset         Offset of the caret in the pending composition
206    *                        string.  This should not be larger than the length
207    *                        of the pending composition string.
208    * @param aLength         Caret width.  If this is 0, caret will be collapsed.
209    *                        Note that Gecko doesn't supported wide caret yet,
210    *                        therefore, this is ignored for now.
211    */
SetCaretInPendingComposition(uint32_t aOffset,uint32_t aLength)212   nsresult SetCaretInPendingComposition(uint32_t aOffset, uint32_t aLength) {
213     return mPendingComposition.SetCaret(aOffset, aLength);
214   }
215 
216   /**
217    * SetPendingComposition() is useful if native IME handler already creates
218    * array of clauses and/or caret information.
219    *
220    * @param aString         Composition string.  This may include native line
221    *                        breakers since they will be replaced with XP line
222    *                        breakers automatically.
223    * @param aRanges         This should include the ranges of clauses and/or
224    *                        a range of caret.  Note that this method allows
225    *                        some ranges overlap each other and the range order
226    *                        is not from start to end.
227    */
SetPendingComposition(const nsAString & aString,const TextRangeArray * aRanges)228   nsresult SetPendingComposition(const nsAString& aString,
229                                  const TextRangeArray* aRanges) {
230     return mPendingComposition.Set(aString, aRanges);
231   }
232 
233   /**
234    * FlushPendingComposition() sends the pending composition string
235    * to the widget of the store DOM window.  Before calling this, IME needs to
236    * set pending composition string with SetPendingCompositionString(),
237    * AppendClauseToPendingComposition() and/or
238    * SetCaretInPendingComposition().
239    *
240    * @param aEventTime      If this is not nullptr, WidgetCompositionEvent will
241    *                        be initialized with this.  Otherwise, initialized
242    *                        with the time at initializing.
243    */
244   nsresult FlushPendingComposition(
245       nsEventStatus& aStatus, const WidgetEventTime* aEventTime = nullptr) {
246     return mPendingComposition.Flush(this, aStatus, aEventTime);
247   }
248 
249   /**
250    * ClearPendingComposition() makes this instance forget pending composition.
251    */
ClearPendingComposition()252   void ClearPendingComposition() { mPendingComposition.Clear(); }
253 
254   /**
255    * GetPendingCompositionClauses() returns text ranges which was appended by
256    * AppendClauseToPendingComposition() or SetPendingComposition().
257    */
GetPendingCompositionClauses()258   const TextRangeArray* GetPendingCompositionClauses() const {
259     return mPendingComposition.GetClauses();
260   }
261 
262   /**
263    * @see nsIWidget::NotifyIME()
264    */
265   nsresult NotifyIME(const IMENotification& aIMENotification);
266 
267   /**
268    * DispatchKeyboardEvent() maybe dispatches aKeyboardEvent.
269    *
270    * @param aMessage        Must be eKeyDown or eKeyUp.
271    *                        Use MaybeDispatchKeypressEvents() for dispatching
272    *                        eKeyPress.
273    * @param aKeyboardEvent  A keyboard event.
274    * @param aStatus         If dispatching event should be marked as consumed,
275    *                        set nsEventStatus_eConsumeNoDefault.  Otherwise,
276    *                        set nsEventStatus_eIgnore.  After dispatching
277    *                        a event and it's consumed this returns
278    *                        nsEventStatus_eConsumeNoDefault.
279    * @param aData           Calling this method may cause calling
280    *                        WillDispatchKeyboardEvent() of the listener.
281    *                        aData will be set to its argument.
282    * @return                true if an event is dispatched.  Otherwise, false.
283    */
284   bool DispatchKeyboardEvent(EventMessage aMessage,
285                              const WidgetKeyboardEvent& aKeyboardEvent,
286                              nsEventStatus& aStatus, void* aData = nullptr);
287 
288   /**
289    * MaybeDispatchKeypressEvents() maybe dispatches a keypress event which is
290    * generated from aKeydownEvent.
291    *
292    * @param aKeyboardEvent  A keyboard event.
293    * @param aStatus         Sets the result when the caller dispatches
294    *                        aKeyboardEvent.  Note that if the value is
295    *                        nsEventStatus_eConsumeNoDefault, this does NOT
296    *                        dispatch keypress events.
297    *                        When this method dispatches one or more keypress
298    *                        events and one of them is consumed, this returns
299    *                        nsEventStatus_eConsumeNoDefault.
300    * @param aData           Calling this method may cause calling
301    *                        WillDispatchKeyboardEvent() of the listener.
302    *                        aData will be set to its argument.
303    * @param aNeedsCallback  Set true when caller needs to initialize each
304    *                        eKeyPress event immediately before dispatch.
305    *                        Then, WillDispatchKeyboardEvent() is always called.
306    * @return                true if one or more events are dispatched.
307    *                        Otherwise, false.
308    */
309   bool MaybeDispatchKeypressEvents(const WidgetKeyboardEvent& aKeyboardEvent,
310                                    nsEventStatus& aStatus,
311                                    void* aData = nullptr,
312                                    bool aNeedsCallback = false);
313 
314  private:
315   // mWidget is owner of the instance.  When this is created, this is set.
316   // And when mWidget is released, this is cleared by OnDestroyWidget().
317   // Note that mWidget may be destroyed already (i.e., mWidget->Destroyed() may
318   // return true).
319   nsIWidget* mWidget;
320   // mListener is a weak reference to TextEventDispatcherListener.  That might
321   // be referred by JS.  Therefore, the listener might be difficult to release
322   // itself if this is a strong reference.  Additionally, it's difficult to
323   // check if a method to uninstall the listener is called by valid instance.
324   // So, using weak reference is the best way in this case.
325   nsWeakPtr mListener;
326   // mIMENotificationRequests should store current IME's notification requests.
327   // So, this may be invalid when IME doesn't have focus.
328   IMENotificationRequests mIMENotificationRequests;
329 
330   // mPendingComposition stores new composition string temporarily.
331   // These values will be used for dispatching eCompositionChange event
332   // in Flush().  When Flush() is called, the members will be cleared
333   // automatically.
334   class PendingComposition {
335    public:
336     PendingComposition();
337     nsresult SetString(const nsAString& aString);
338     nsresult AppendClause(uint32_t aLength, TextRangeType aTextRangeType);
339     nsresult SetCaret(uint32_t aOffset, uint32_t aLength);
340     nsresult Set(const nsAString& aString, const TextRangeArray* aRanges);
341     nsresult Flush(TextEventDispatcher* aDispatcher, nsEventStatus& aStatus,
342                    const WidgetEventTime* aEventTime);
GetClauses()343     const TextRangeArray* GetClauses() const { return mClauses; }
344     void Clear();
345 
346    private:
347     nsString mString;
348     RefPtr<TextRangeArray> mClauses;
349     TextRange mCaret;
350     bool mReplacedNativeLineBreakers;
351 
352     void EnsureClauseArray();
353 
354     /**
355      * ReplaceNativeLineBreakers() replaces "\r\n" and "\r" to "\n" and adjust
356      * each clause information and the caret information.
357      */
358     void ReplaceNativeLineBreakers();
359 
360     /**
361      * AdjustRange() adjusts aRange as in the string with XP line breakers.
362      *
363      * @param aRange            The reference to a range in aNativeString.
364      *                          This will be modified.
365      * @param aNativeString     The string with native line breakers.
366      *                          This may include "\r\n" and/or "\r".
367      */
368     static void AdjustRange(TextRange& aRange, const nsAString& aNativeString);
369   };
370   PendingComposition mPendingComposition;
371 
372   // While dispatching an event, this is incremented.
373   uint16_t mDispatchingEvent;
374 
375   enum InputTransactionType : uint8_t {
376     // No input transaction has been started.
377     eNoInputTransaction,
378     // Input transaction for native IME or keyboard event handler.  Note that
379     // keyboard events may be dispatched via parent process if there is.
380     // In remote processes, this is also used when events come from the parent
381     // process and are not for tests because we cannot distinguish if
382     // TextEventDispatcher has which type of transaction when it dispatches
383     // (eNativeInputTransaction or eSameProcessSyncInputTransaction).
384     eNativeInputTransaction,
385     // Input transaction for automated tests which are APZ-aware.  Note that
386     // keyboard events may be dispatched via parent process if there is.
387     eAsyncTestInputTransaction,
388     // Input transaction for automated tests which assume events are fired
389     // synchronously.  I.e., keyboard events are always dispatched in the
390     // current process.
391     // In remote processes, this is also used when events come from the parent
392     // process and are not dispatched by the instance itself for APZ-aware
393     // tests because this instance won't dispatch the events via the parent
394     // process again.
395     eSameProcessSyncTestInputTransaction,
396     // Input transaction for others (currently, only FuzzingFunctions).
397     // Events are fired synchronously in the process.
398     // XXX Should we make this async for testing default action handlers in
399     //     the main process?
400     eSameProcessSyncInputTransaction
401   };
402 
403   InputTransactionType mInputTransactionType;
404 
IsForTests()405   bool IsForTests() const {
406     return mInputTransactionType == eAsyncTestInputTransaction ||
407            mInputTransactionType == eSameProcessSyncTestInputTransaction;
408   }
409 
410   // ShouldSendInputEventToAPZ() returns true when WidgetInputEvent should
411   // be dispatched via its parent process (if there is) for APZ.  Otherwise,
412   // when the input transaction is for IME of B2G or automated tests which
413   // isn't APZ-aware, WidgetInputEvent should be dispatched form current
414   // process directly.
ShouldSendInputEventToAPZ()415   bool ShouldSendInputEventToAPZ() const {
416     switch (mInputTransactionType) {
417       case eNativeInputTransaction:
418       case eAsyncTestInputTransaction:
419         return true;
420       case eSameProcessSyncTestInputTransaction:
421       case eSameProcessSyncInputTransaction:
422         return false;
423       case eNoInputTransaction:
424         NS_WARNING(
425             "Why does the caller need to dispatch an event when "
426             "there is no input transaction?");
427         return true;
428       default:
429         MOZ_CRASH("Define the behavior of new InputTransactionType");
430     }
431   }
432 
433   // See IsComposing().
434   bool mIsComposing;
435 
436   // See IsHandlingComposition().
437   bool mIsHandlingComposition;
438 
439   // true while NOTIFY_IME_OF_FOCUS is received but NOTIFY_IME_OF_BLUR has not
440   // received yet.  Otherwise, false.
441   bool mHasFocus;
442 
443   nsresult BeginInputTransactionInternal(TextEventDispatcherListener* aListener,
444                                          InputTransactionType aType);
445 
446   /**
447    * InitEvent() initializes aEvent.  This must be called before dispatching
448    * the event.
449    */
450   void InitEvent(WidgetGUIEvent& aEvent) const;
451 
452   /**
453    * DispatchEvent() dispatches aEvent on aWidget.
454    */
455   nsresult DispatchEvent(nsIWidget* aWidget, WidgetGUIEvent& aEvent,
456                          nsEventStatus& aStatus);
457 
458   /**
459    * DispatchInputEvent() dispatches aEvent on aWidget.
460    */
461   nsresult DispatchInputEvent(nsIWidget* aWidget, WidgetInputEvent& aEvent,
462                               nsEventStatus& aStatus);
463 
464   /**
465    * StartCompositionAutomaticallyIfNecessary() starts composition if it hasn't
466    * been started it yet.
467    *
468    * @param aStatus         If it succeeded to start composition normally, this
469    *                        returns nsEventStatus_eIgnore.  Otherwise, e.g.,
470    *                        the composition is canceled during dispatching
471    *                        compositionstart event, this returns
472    *                        nsEventStatus_eConsumeNoDefault.  In this case,
473    *                        the caller shouldn't keep doing its job.
474    * @param aEventTime      If this is not nullptr, WidgetCompositionEvent will
475    *                        be initialized with this.  Otherwise, initialized
476    *                        with the time at initializing.
477    * @return                Only when something unexpected occurs, this returns
478    *                        an error.  Otherwise, returns NS_OK even if aStatus
479    *                        is nsEventStatus_eConsumeNoDefault.
480    */
481   nsresult StartCompositionAutomaticallyIfNecessary(
482       nsEventStatus& aStatus, const WidgetEventTime* aEventTime);
483 
484   /**
485    * DispatchKeyboardEventInternal() maybe dispatches aKeyboardEvent.
486    *
487    * @param aMessage        Must be eKeyDown, eKeyUp or eKeyPress.
488    * @param aKeyboardEvent  A keyboard event.  If aMessage is eKeyPress and
489    *                        the event is for second or later character, its
490    *                        mKeyValue should be empty string.
491    * @param aStatus         If dispatching event should be marked as consumed,
492    *                        set nsEventStatus_eConsumeNoDefault.  Otherwise,
493    *                        set nsEventStatus_eIgnore.  After dispatching
494    *                        a event and it's consumed this returns
495    *                        nsEventStatus_eConsumeNoDefault.
496    * @param aData           Calling this method may cause calling
497    *                        WillDispatchKeyboardEvent() of the listener.
498    *                        aData will be set to its argument.
499    * @param aIndexOfKeypress    This must be 0 if aMessage isn't eKeyPress or
500    *                            aKeyboard.mKeyNameIndex isn't
501    *                            KEY_NAME_INDEX_USE_STRING.  Otherwise, i.e.,
502    *                            when an eKeyPress event causes inputting
503    *                            text, this must be between 0 and
504    *                            mKeyValue.Length() - 1 since keypress events
505    *                            sending only one character per event.
506    * @param aNeedsCallback  Set true when caller needs to initialize each
507    *                        eKeyPress event immediately before dispatch.
508    *                        Then, WillDispatchKeyboardEvent() is always called.
509    * @return                true if an event is dispatched.  Otherwise, false.
510    */
511   bool DispatchKeyboardEventInternal(EventMessage aMessage,
512                                      const WidgetKeyboardEvent& aKeyboardEvent,
513                                      nsEventStatus& aStatus, void* aData,
514                                      uint32_t aIndexOfKeypress = 0,
515                                      bool aNeedsCallback = false);
516 
517   /**
518    * ClearNotificationRequests() clears mIMENotificationRequests.
519    */
520   void ClearNotificationRequests();
521 
522   /**
523    * UpdateNotificationRequests() updates mIMENotificationRequests with
524    * current state.  If the instance doesn't have focus, this clears
525    * mIMENotificationRequests.  Otherwise, updates it with both requests of
526    * current listener and native listener.
527    */
528   void UpdateNotificationRequests();
529 };
530 
531 }  // namespace widget
532 }  // namespace mozilla
533 
534 #endif  // #ifndef mozilla_widget_textcompositionsynthesizer_h_
535