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