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