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