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