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