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/. */
6 #ifndef mozilla_textcompositionsynthesizer_h_
7 #define mozilla_textcompositionsynthesizer_h_
9 #include "mozilla/RefPtr.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"
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
36 class TextEventDispatcher final
{
37 ~TextEventDispatcher() = default;
39 NS_INLINE_DECL_REFCOUNTING(TextEventDispatcher
)
42 explicit TextEventDispatcher(nsIWidget
* aWidget
);
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
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
58 nsresult
BeginInputTransaction(TextEventDispatcherListener
* aListener
);
59 nsresult
BeginTestInputTransaction(TextEventDispatcherListener
* aListener
,
61 nsresult
BeginNativeInputTransaction();
64 * BeginInputTransactionFor() should be used when aPuppetWidget dispatches
65 * a composition or keyboard event coming from its parent process.
67 nsresult
BeginInputTransactionFor(const WidgetGUIEvent
* aEvent
,
68 PuppetWidget
* aPuppetWidget
);
71 * EndInputTransaction() should be called when the listener stops using
72 * the TextEventDispatcher.
74 * @param aListener The listener using the TextEventDispatcher instance.
76 void EndInputTransaction(TextEventDispatcherListener
* aListener
);
79 * OnDestroyWidget() is called when mWidget is being destroyed.
81 void OnDestroyWidget();
83 nsIWidget
* GetWidget() const { return mWidget
; }
85 const IMENotificationRequests
& IMENotificationRequestsRef() const {
86 return mIMENotificationRequests
;
90 * OnWidgetChangeIMENotificationRequests() is called when aWidget's
91 * IMENotificationRequest is maybe modified by unusual path. E.g.,
92 * modified in an async path.
94 void OnWidgetChangeIMENotificationRequests(nsIWidget
* aWidget
) {
96 if (mWidget
== aWidget
) {
97 UpdateNotificationRequests();
102 * GetState() returns current state of this class.
104 * @return NS_OK: Fine to compose text.
105 * NS_ERROR_NOT_INITIALIZED: BeginInputTransaction() or
106 * BeginInputTransactionForTests()
108 * NS_ERROR_NOT_AVAILABLE: The widget isn't available for
111 nsresult
GetState() const;
114 * IsComposing() returns true after calling StartComposition() and before
115 * calling CommitComposition(). In other words, native IME has composition
116 * when this returns true.
118 bool IsComposing() const { return mIsComposing
; }
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.
125 bool IsHandlingComposition() const { return mIsHandlingComposition
; }
128 * IsInNativeInputTransaction() returns true if native IME handler began a
129 * transaction and it's not finished yet.
131 bool IsInNativeInputTransaction() const {
132 return mInputTransactionType
== eNativeInputTransaction
;
136 * IsDispatchingEvent() returns true while this instance dispatching an event.
138 bool IsDispatchingEvent() const { return mDispatchingEvent
> 0; }
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.
145 void* GetPseudoIMEContext() const {
146 if (mInputTransactionType
== eNoInputTransaction
||
147 mInputTransactionType
== eNativeInputTransaction
) {
150 return const_cast<TextEventDispatcher
*>(this);
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.
158 MOZ_CAN_RUN_SCRIPT Maybe
<WritingMode
> MaybeWritingModeAtSelection() const;
161 * StartComposition() starts composition explicitly.
163 * @param aEventTime If this is not nullptr, WidgetCompositionEvent will
164 * be initialized with this. Otherwise, initialized
165 * with the time at initializing.
167 nsresult
StartComposition(nsEventStatus
& aStatus
,
168 const WidgetEventTime
* aEventTime
= nullptr);
171 * CommitComposition() commits composition.
173 * @param aCommitString If this is null, commits with the last composition
174 * string. Otherwise, commits the composition with
176 * @param aEventTime If this is not nullptr, WidgetCompositionEvent will
177 * be initialized with this. Otherwise, initialized
178 * with the time at initializing.
180 nsresult
CommitComposition(nsEventStatus
& aStatus
,
181 const nsAString
* aCommitString
= nullptr,
182 const WidgetEventTime
* aEventTime
= nullptr);
185 * SetPendingCompositionString() sets new composition string which will be
186 * dispatched with eCompositionChange event by calling Flush().
188 * @param aString New composition string.
190 nsresult
SetPendingCompositionString(const nsAString
& aString
) {
191 return mPendingComposition
.SetString(aString
);
195 * AppendClauseToPendingComposition() appends a clause information to
196 * the pending composition string.
198 * @param aLength Length of the clause.
199 * @param aTextRangeType One of TextRangeType::eRawClause,
200 * TextRangeType::eSelectedRawClause,
201 * TextRangeType::eConvertedClause or
202 * TextRangeType::eSelectedClause.
204 nsresult
AppendClauseToPendingComposition(uint32_t aLength
,
205 TextRangeType aTextRangeType
) {
206 return mPendingComposition
.AppendClause(aLength
, aTextRangeType
);
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.
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.
221 nsresult
SetCaretInPendingComposition(uint32_t aOffset
, uint32_t aLength
) {
222 return mPendingComposition
.SetCaret(aOffset
, aLength
);
226 * SetPendingComposition() is useful if native IME handler already creates
227 * array of clauses and/or caret information.
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.
237 nsresult
SetPendingComposition(const nsAString
& aString
,
238 const TextRangeArray
* aRanges
) {
239 return mPendingComposition
.Set(aString
, aRanges
);
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().
249 * @param aEventTime If this is not nullptr, WidgetCompositionEvent will
250 * be initialized with this. Otherwise, initialized
251 * with the time at initializing.
253 nsresult
FlushPendingComposition(
254 nsEventStatus
& aStatus
, const WidgetEventTime
* aEventTime
= nullptr) {
255 return mPendingComposition
.Flush(this, aStatus
, aEventTime
);
259 * ClearPendingComposition() makes this instance forget pending composition.
261 void ClearPendingComposition() { mPendingComposition
.Clear(); }
264 * GetPendingCompositionClauses() returns text ranges which was appended by
265 * AppendClauseToPendingComposition() or SetPendingComposition().
267 const TextRangeArray
* GetPendingCompositionClauses() const {
268 return mPendingComposition
.GetClauses();
272 * @see nsIWidget::NotifyIME()
274 nsresult
NotifyIME(const IMENotification
& aIMENotification
);
277 * DispatchKeyboardEvent() maybe dispatches aKeyboardEvent.
279 * @param aMessage Must be eKeyDown or eKeyUp.
280 * Use MaybeDispatchKeypressEvents() for dispatching
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.
293 bool DispatchKeyboardEvent(EventMessage aMessage
,
294 const WidgetKeyboardEvent
& aKeyboardEvent
,
295 nsEventStatus
& aStatus
, void* aData
= nullptr);
298 * MaybeDispatchKeypressEvents() maybe dispatches a keypress event which is
299 * generated from aKeydownEvent.
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.
318 bool MaybeDispatchKeypressEvents(const WidgetKeyboardEvent
& aKeyboardEvent
,
319 nsEventStatus
& aStatus
,
320 void* aData
= nullptr,
321 bool aNeedsCallback
= false);
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
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.
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
;
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
346 class PendingComposition
{
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
);
355 const TextRangeArray
* GetClauses() const { return mClauses
; }
360 RefPtr
<TextRangeArray
> mClauses
;
362 bool mReplacedNativeLineBreakers
;
364 void EnsureClauseArray();
367 * ReplaceNativeLineBreakers() replaces "\r\n" and "\r" to "\n" and adjust
368 * each clause information and the caret information.
370 void ReplaceNativeLineBreakers();
373 * AdjustRange() adjusts aRange as in the string with XP line breakers.
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".
380 static void AdjustRange(TextRange
& aRange
, const nsAString
& aNativeString
);
382 PendingComposition mPendingComposition
;
384 // While dispatching an event, this is incremented.
385 uint16_t mDispatchingEvent
;
387 enum InputTransactionType
: uint8_t {
388 // No input transaction has been started.
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
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
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
412 eSameProcessSyncInputTransaction
415 InputTransactionType mInputTransactionType
;
417 bool IsForTests() const {
418 return mInputTransactionType
== eAsyncTestInputTransaction
||
419 mInputTransactionType
== eSameProcessSyncTestInputTransaction
;
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
427 bool ShouldSendInputEventToAPZ() const {
428 switch (mInputTransactionType
) {
429 case eNativeInputTransaction
:
430 case eAsyncTestInputTransaction
:
432 case eSameProcessSyncTestInputTransaction
:
433 case eSameProcessSyncInputTransaction
:
435 case eNoInputTransaction
:
437 "Why does the caller need to dispatch an event when "
438 "there is no input transaction?");
441 MOZ_CRASH("Define the behavior of new InputTransactionType");
445 // See IsComposing().
448 // See IsHandlingComposition().
449 bool mIsHandlingComposition
;
451 // true while NOTIFY_IME_OF_FOCUS is received but NOTIFY_IME_OF_BLUR has not
452 // received yet. Otherwise, false.
455 nsresult
BeginInputTransactionInternal(TextEventDispatcherListener
* aListener
,
456 InputTransactionType aType
);
459 * InitEvent() initializes aEvent. This must be called before dispatching
462 void InitEvent(WidgetGUIEvent
& aEvent
) const;
465 * DispatchEvent() dispatches aEvent on aWidget.
467 nsresult
DispatchEvent(nsIWidget
* aWidget
, WidgetGUIEvent
& aEvent
,
468 nsEventStatus
& aStatus
);
471 * DispatchInputEvent() dispatches aEvent on aWidget.
473 nsresult
DispatchInputEvent(nsIWidget
* aWidget
, WidgetInputEvent
& aEvent
,
474 nsEventStatus
& aStatus
);
477 * StartCompositionAutomaticallyIfNecessary() starts composition if it hasn't
478 * been started it yet.
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.
493 nsresult
StartCompositionAutomaticallyIfNecessary(
494 nsEventStatus
& aStatus
, const WidgetEventTime
* aEventTime
);
497 * DispatchKeyboardEventInternal() maybe dispatches aKeyboardEvent.
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.
523 bool DispatchKeyboardEventInternal(EventMessage aMessage
,
524 const WidgetKeyboardEvent
& aKeyboardEvent
,
525 nsEventStatus
& aStatus
, void* aData
,
526 uint32_t aIndexOfKeypress
= 0,
527 bool aNeedsCallback
= false);
530 * ClearNotificationRequests() clears mIMENotificationRequests.
532 void ClearNotificationRequests();
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.
540 void UpdateNotificationRequests();
543 } // namespace widget
544 } // namespace mozilla
546 #endif // #ifndef mozilla_widget_textcompositionsynthesizer_h_