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