forked from mirrors/gecko-dev
		
	 b3f898a618
			
		
	
	
		b3f898a618
		
	
	
	
	
		
			
			We resolve editing commands on Linux and macOS to respect the system settings of every key press when an `eKeyPress` event is sent to a remote process, and it requires `WritingMode` at selection for arrow key handling for example. Therefore, the parent process try to get the information. However, in the case, the query succeeds only when IME has focus since `ContentCacheInParent` does not have any data when IME does not have focus. Therefore, `TextEventDispatcher::MaybeQueryWritingModeAtSelection` should check the IME state before dispatching `eQuerySelectedText` event to avoid the warnings and unnecessary runtime cost. Differential Revision: https://phabricator.services.mozilla.com/D143901
		
			
				
	
	
		
			990 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			990 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "TextEventDispatcher.h"
 | |
| 
 | |
| #include "IMEData.h"
 | |
| #include "PuppetWidget.h"
 | |
| #include "TextEvents.h"
 | |
| 
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "mozilla/StaticPrefs_dom.h"
 | |
| #include "nsIFrame.h"
 | |
| #include "nsIWidget.h"
 | |
| #include "nsPIDOMWindow.h"
 | |
| #include "nsView.h"
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace widget {
 | |
| 
 | |
| /******************************************************************************
 | |
|  * TextEventDispatcher
 | |
|  *****************************************************************************/
 | |
| TextEventDispatcher::TextEventDispatcher(nsIWidget* aWidget)
 | |
|     : mWidget(aWidget),
 | |
|       mDispatchingEvent(0),
 | |
|       mInputTransactionType(eNoInputTransaction),
 | |
|       mIsComposing(false),
 | |
|       mIsHandlingComposition(false),
 | |
|       mHasFocus(false) {
 | |
|   MOZ_RELEASE_ASSERT(mWidget, "aWidget must not be nullptr");
 | |
| 
 | |
|   ClearNotificationRequests();
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::BeginInputTransaction(
 | |
|     TextEventDispatcherListener* aListener) {
 | |
|   return BeginInputTransactionInternal(aListener,
 | |
|                                        eSameProcessSyncInputTransaction);
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::BeginTestInputTransaction(
 | |
|     TextEventDispatcherListener* aListener, bool aIsAPZAware) {
 | |
|   return BeginInputTransactionInternal(
 | |
|       aListener, aIsAPZAware ? eAsyncTestInputTransaction
 | |
|                              : eSameProcessSyncTestInputTransaction);
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::BeginNativeInputTransaction() {
 | |
|   if (NS_WARN_IF(!mWidget)) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
|   RefPtr<TextEventDispatcherListener> listener =
 | |
|       mWidget->GetNativeTextEventDispatcherListener();
 | |
|   if (NS_WARN_IF(!listener)) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
|   return BeginInputTransactionInternal(listener, eNativeInputTransaction);
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::BeginInputTransactionInternal(
 | |
|     TextEventDispatcherListener* aListener, InputTransactionType aType) {
 | |
|   if (NS_WARN_IF(!aListener)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
 | |
|   if (listener) {
 | |
|     if (listener == aListener && mInputTransactionType == aType) {
 | |
|       UpdateNotificationRequests();
 | |
|       return NS_OK;
 | |
|     }
 | |
|     // If this has composition or is dispatching an event, any other listener
 | |
|     // can steal ownership.  Especially, if the latter case is allowed,
 | |
|     // nobody cannot begin input transaction with this if a modal dialog is
 | |
|     // opened during dispatching an event.
 | |
|     if (IsComposing() || IsDispatchingEvent()) {
 | |
|       return NS_ERROR_ALREADY_INITIALIZED;
 | |
|     }
 | |
|   }
 | |
|   mListener = do_GetWeakReference(aListener);
 | |
|   mInputTransactionType = aType;
 | |
|   if (listener && listener != aListener) {
 | |
|     listener->OnRemovedFrom(this);
 | |
|   }
 | |
|   UpdateNotificationRequests();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::BeginInputTransactionFor(
 | |
|     const WidgetGUIEvent* aEvent, PuppetWidget* aPuppetWidget) {
 | |
|   MOZ_ASSERT(XRE_IsContentProcess());
 | |
|   MOZ_ASSERT(!IsDispatchingEvent());
 | |
| 
 | |
|   switch (aEvent->mMessage) {
 | |
|     case eKeyDown:
 | |
|     case eKeyPress:
 | |
|     case eKeyUp:
 | |
|       MOZ_ASSERT(aEvent->mClass == eKeyboardEventClass);
 | |
|       break;
 | |
|     case eCompositionStart:
 | |
|     case eCompositionChange:
 | |
|     case eCompositionCommit:
 | |
|     case eCompositionCommitAsIs:
 | |
|       MOZ_ASSERT(aEvent->mClass == eCompositionEventClass);
 | |
|       break;
 | |
|     default:
 | |
|       return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   if (aEvent->mFlags.mIsSynthesizedForTests) {
 | |
|     // If the event is for an automated test and this instance dispatched
 | |
|     // an event to the parent process, we can assume that this is already
 | |
|     // initialized properly.
 | |
|     if (mInputTransactionType == eAsyncTestInputTransaction) {
 | |
|       return NS_OK;
 | |
|     }
 | |
|     // Even if the event coming from the parent process is synthesized for
 | |
|     // tests, this process should treat it as "sync" test here because
 | |
|     // it won't be go back to the parent process.
 | |
|     nsresult rv = BeginInputTransactionInternal(
 | |
|         static_cast<TextEventDispatcherListener*>(aPuppetWidget),
 | |
|         eSameProcessSyncTestInputTransaction);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
|   } else {
 | |
|     nsresult rv = BeginNativeInputTransaction();
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Emulate modifying members which indicate the state of composition.
 | |
|   // If we need to manage more states and/or more complexly, we should create
 | |
|   // internal methods which are called by both here and each event dispatcher
 | |
|   // method of this class.
 | |
|   switch (aEvent->mMessage) {
 | |
|     case eKeyDown:
 | |
|     case eKeyPress:
 | |
|     case eKeyUp:
 | |
|       return NS_OK;
 | |
|     case eCompositionStart:
 | |
|       MOZ_ASSERT(!mIsComposing);
 | |
|       mIsComposing = mIsHandlingComposition = true;
 | |
|       return NS_OK;
 | |
|     case eCompositionChange:
 | |
|       MOZ_ASSERT(mIsComposing);
 | |
|       MOZ_ASSERT(mIsHandlingComposition);
 | |
|       mIsComposing = mIsHandlingComposition = true;
 | |
|       return NS_OK;
 | |
|     case eCompositionCommit:
 | |
|     case eCompositionCommitAsIs:
 | |
|       MOZ_ASSERT(mIsComposing);
 | |
|       MOZ_ASSERT(mIsHandlingComposition);
 | |
|       mIsComposing = false;
 | |
|       mIsHandlingComposition = true;
 | |
|       return NS_OK;
 | |
|     default:
 | |
|       MOZ_ASSERT_UNREACHABLE("You forgot to handle the event");
 | |
|       return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
| }
 | |
| void TextEventDispatcher::EndInputTransaction(
 | |
|     TextEventDispatcherListener* aListener) {
 | |
|   if (NS_WARN_IF(IsComposing()) || NS_WARN_IF(IsDispatchingEvent())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mInputTransactionType = eNoInputTransaction;
 | |
| 
 | |
|   nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
 | |
|   if (NS_WARN_IF(!listener)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (NS_WARN_IF(listener != aListener)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mListener = nullptr;
 | |
|   listener->OnRemovedFrom(this);
 | |
|   UpdateNotificationRequests();
 | |
| }
 | |
| 
 | |
| void TextEventDispatcher::OnDestroyWidget() {
 | |
|   mWidget = nullptr;
 | |
|   mHasFocus = false;
 | |
|   ClearNotificationRequests();
 | |
|   mPendingComposition.Clear();
 | |
|   nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
 | |
|   mListener = nullptr;
 | |
|   mWritingMode.reset();
 | |
|   mInputTransactionType = eNoInputTransaction;
 | |
|   if (listener) {
 | |
|     listener->OnRemovedFrom(this);
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::GetState() const {
 | |
|   nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
 | |
|   if (!listener) {
 | |
|     return NS_ERROR_NOT_INITIALIZED;
 | |
|   }
 | |
|   if (!mWidget || mWidget->Destroyed()) {
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void TextEventDispatcher::InitEvent(WidgetGUIEvent& aEvent) const {
 | |
|   aEvent.mTime = PR_IntervalNow();
 | |
|   aEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
 | |
|   aEvent.mFlags.mIsSynthesizedForTests = IsForTests();
 | |
|   if (aEvent.mClass != eCompositionEventClass) {
 | |
|     return;
 | |
|   }
 | |
|   void* pseudoIMEContext = GetPseudoIMEContext();
 | |
|   if (pseudoIMEContext) {
 | |
|     aEvent.AsCompositionEvent()->mNativeIMEContext.InitWithRawNativeIMEContext(
 | |
|         pseudoIMEContext);
 | |
|   }
 | |
| #ifdef DEBUG
 | |
|   else {
 | |
|     MOZ_ASSERT(!XRE_IsContentProcess(),
 | |
|                "Why did the content process start native event transaction?");
 | |
|     MOZ_ASSERT(aEvent.AsCompositionEvent()->mNativeIMEContext.IsValid(),
 | |
|                "Native IME context shouldn't be invalid");
 | |
|   }
 | |
| #endif  // #ifdef DEBUG
 | |
| }
 | |
| 
 | |
| Maybe<WritingMode> TextEventDispatcher::MaybeQueryWritingModeAtSelection()
 | |
|     const {
 | |
|   if (mHasFocus || mWritingMode.isSome()) {
 | |
|     return mWritingMode;
 | |
|   }
 | |
| 
 | |
|   if (NS_WARN_IF(!mWidget)) {
 | |
|     return Nothing();
 | |
|   }
 | |
| 
 | |
|   // If a remote content has focus and IME does not have focus, it's going to
 | |
|   // fail eQuerySelectedText in ContentCacheParent.  For avoiding to waste
 | |
|   // unnecessary runtime cost and to prevent unnecessary warnings, we should
 | |
|   // not dispatch the event in the case.
 | |
|   const InputContext inputContext = mWidget->GetInputContext();
 | |
|   if (XRE_IsE10sParentProcess() && inputContext.IsOriginContentProcess() &&
 | |
|       !inputContext.mIMEState.IsEditable()) {
 | |
|     return Nothing();
 | |
|   }
 | |
| 
 | |
|   WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
 | |
|                                                  mWidget);
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
|   const_cast<TextEventDispatcher*>(this)->DispatchEvent(
 | |
|       mWidget, querySelectedTextEvent, status);
 | |
|   if (!querySelectedTextEvent.FoundSelection()) {
 | |
|     return Nothing();
 | |
|   }
 | |
| 
 | |
|   return Some(querySelectedTextEvent.mReply->mWritingMode);
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::DispatchEvent(nsIWidget* aWidget,
 | |
|                                             WidgetGUIEvent& aEvent,
 | |
|                                             nsEventStatus& aStatus) {
 | |
|   MOZ_ASSERT(!aEvent.AsInputEvent(), "Use DispatchInputEvent()");
 | |
| 
 | |
|   RefPtr<TextEventDispatcher> kungFuDeathGrip(this);
 | |
|   nsCOMPtr<nsIWidget> widget(aWidget);
 | |
|   mDispatchingEvent++;
 | |
|   nsresult rv = widget->DispatchEvent(&aEvent, aStatus);
 | |
|   mDispatchingEvent--;
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::DispatchInputEvent(nsIWidget* aWidget,
 | |
|                                                  WidgetInputEvent& aEvent,
 | |
|                                                  nsEventStatus& aStatus) {
 | |
|   RefPtr<TextEventDispatcher> kungFuDeathGrip(this);
 | |
|   nsCOMPtr<nsIWidget> widget(aWidget);
 | |
|   mDispatchingEvent++;
 | |
| 
 | |
|   // If the event is dispatched via nsIWidget::DispatchInputEvent(), it
 | |
|   // sends the event to the parent process first since APZ needs to handle it
 | |
|   // first.  However, some callers (e.g., keyboard apps on B2G and tests
 | |
|   // expecting synchronous dispatch) don't want this to do that.
 | |
|   nsresult rv = NS_OK;
 | |
|   if (ShouldSendInputEventToAPZ()) {
 | |
|     aStatus = widget->DispatchInputEvent(&aEvent).mContentStatus;
 | |
|   } else {
 | |
|     rv = widget->DispatchEvent(&aEvent, aStatus);
 | |
|   }
 | |
| 
 | |
|   mDispatchingEvent--;
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::StartComposition(
 | |
|     nsEventStatus& aStatus, const WidgetEventTime* aEventTime) {
 | |
|   aStatus = nsEventStatus_eIgnore;
 | |
| 
 | |
|   nsresult rv = GetState();
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (NS_WARN_IF(mIsComposing)) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   // When you change some members from here, you may need same change in
 | |
|   // BeginInputTransactionFor().
 | |
|   mIsComposing = mIsHandlingComposition = true;
 | |
|   WidgetCompositionEvent compositionStartEvent(true, eCompositionStart,
 | |
|                                                mWidget);
 | |
|   InitEvent(compositionStartEvent);
 | |
|   if (aEventTime) {
 | |
|     compositionStartEvent.AssignEventTime(*aEventTime);
 | |
|   }
 | |
|   rv = DispatchEvent(mWidget, compositionStartEvent, aStatus);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::StartCompositionAutomaticallyIfNecessary(
 | |
|     nsEventStatus& aStatus, const WidgetEventTime* aEventTime) {
 | |
|   if (IsComposing()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = StartComposition(aStatus, aEventTime);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // If started composition has already been committed, we shouldn't dispatch
 | |
|   // the compositionchange event.
 | |
|   if (!IsComposing()) {
 | |
|     aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Note that the widget might be destroyed during a call of
 | |
|   // StartComposition().  In such case, we shouldn't keep dispatching next
 | |
|   // event.
 | |
|   rv = GetState();
 | |
|   if (NS_FAILED(rv)) {
 | |
|     MOZ_ASSERT(rv != NS_ERROR_NOT_INITIALIZED,
 | |
|                "aDispatcher must still be initialized in this case");
 | |
|     aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|     return NS_OK;  // Don't throw exception in this case
 | |
|   }
 | |
| 
 | |
|   aStatus = nsEventStatus_eIgnore;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::CommitComposition(
 | |
|     nsEventStatus& aStatus, const nsAString* aCommitString,
 | |
|     const WidgetEventTime* aEventTime) {
 | |
|   aStatus = nsEventStatus_eIgnore;
 | |
| 
 | |
|   nsresult rv = GetState();
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // When there is no composition, caller shouldn't try to commit composition
 | |
|   // with non-existing composition string nor commit composition with empty
 | |
|   // string.
 | |
|   if (NS_WARN_IF(!IsComposing() &&
 | |
|                  (!aCommitString || aCommitString->IsEmpty()))) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIWidget> widget(mWidget);
 | |
|   rv = StartCompositionAutomaticallyIfNecessary(aStatus, aEventTime);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
|   if (aStatus == nsEventStatus_eConsumeNoDefault) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // When you change some members from here, you may need same change in
 | |
|   // BeginInputTransactionFor().
 | |
| 
 | |
|   // End current composition and make this free for other IMEs.
 | |
|   mIsComposing = false;
 | |
| 
 | |
|   EventMessage message =
 | |
|       aCommitString ? eCompositionCommit : eCompositionCommitAsIs;
 | |
|   WidgetCompositionEvent compositionCommitEvent(true, message, widget);
 | |
|   InitEvent(compositionCommitEvent);
 | |
|   if (aEventTime) {
 | |
|     compositionCommitEvent.AssignEventTime(*aEventTime);
 | |
|   }
 | |
|   if (message == eCompositionCommit) {
 | |
|     compositionCommitEvent.mData = *aCommitString;
 | |
|     // If aCommitString comes from TextInputProcessor, it may be void, but
 | |
|     // editor requires non-void string even when it's empty.
 | |
|     compositionCommitEvent.mData.SetIsVoid(false);
 | |
|     // Don't send CRLF nor CR, replace it with LF here.
 | |
|     compositionCommitEvent.mData.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
 | |
|     compositionCommitEvent.mData.ReplaceSubstring(u"\r"_ns, u"\n"_ns);
 | |
|   }
 | |
|   rv = DispatchEvent(widget, compositionCommitEvent, aStatus);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::NotifyIME(
 | |
|     const IMENotification& aIMENotification) {
 | |
|   nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
 | |
| 
 | |
|   switch (aIMENotification.mMessage) {
 | |
|     case NOTIFY_IME_OF_FOCUS: {
 | |
|       mWritingMode = MaybeQueryWritingModeAtSelection();
 | |
|       break;
 | |
|     }
 | |
|     case NOTIFY_IME_OF_BLUR:
 | |
|       mHasFocus = false;
 | |
|       mWritingMode.reset();
 | |
|       ClearNotificationRequests();
 | |
|       break;
 | |
|     case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
 | |
|       // If content handles composition events when native IME doesn't have
 | |
|       // composition, that means that we completely finished handling
 | |
|       // composition(s).  Note that when focused content is in a remote
 | |
|       // process, this is sent when all dispatched composition events
 | |
|       // have been handled in the remote process.
 | |
|       if (!IsComposing()) {
 | |
|         mIsHandlingComposition = false;
 | |
|       }
 | |
|       break;
 | |
|     case NOTIFY_IME_OF_SELECTION_CHANGE:
 | |
|       if (mHasFocus && aIMENotification.mSelectionChangeData.HasRange()) {
 | |
|         mWritingMode =
 | |
|             Some(aIMENotification.mSelectionChangeData.GetWritingMode());
 | |
|       }
 | |
|       break;
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   // First, send the notification to current input transaction's listener.
 | |
|   nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
 | |
|   if (listener) {
 | |
|     rv = listener->NotifyIME(this, aIMENotification);
 | |
|   }
 | |
| 
 | |
|   if (!mWidget) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // If current input transaction isn't for native event handler, we should
 | |
|   // send the notification to the native text event dispatcher listener
 | |
|   // since native event handler may need to do something from
 | |
|   // TextEventDispatcherListener::NotifyIME() even before there is no
 | |
|   // input transaction yet.  For example, native IME handler may need to
 | |
|   // create new context at receiving NOTIFY_IME_OF_FOCUS.  In this case,
 | |
|   // mListener may not be initialized since input transaction should be
 | |
|   // initialized immediately before dispatching every WidgetKeyboardEvent
 | |
|   // and WidgetCompositionEvent (dispatching events always occurs after
 | |
|   // focus move).
 | |
|   nsCOMPtr<TextEventDispatcherListener> nativeListener =
 | |
|       mWidget->GetNativeTextEventDispatcherListener();
 | |
|   if (listener != nativeListener && nativeListener) {
 | |
|     switch (aIMENotification.mMessage) {
 | |
|       case REQUEST_TO_COMMIT_COMPOSITION:
 | |
|       case REQUEST_TO_CANCEL_COMPOSITION:
 | |
|         // It's not necessary to notify native IME of requests.
 | |
|         break;
 | |
|       default: {
 | |
|         // Even if current input transaction's listener returns NS_OK or
 | |
|         // something, we need to notify native IME of notifications because
 | |
|         // when user typing after TIP does something, the changed information
 | |
|         // is necessary for them.
 | |
|         nsresult rv2 = nativeListener->NotifyIME(this, aIMENotification);
 | |
|         // But return the result from current listener except when the
 | |
|         // notification isn't handled.
 | |
|         if (rv == NS_ERROR_NOT_IMPLEMENTED) {
 | |
|           rv = rv2;
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS) {
 | |
|     mHasFocus = true;
 | |
|     UpdateNotificationRequests();
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| void TextEventDispatcher::ClearNotificationRequests() {
 | |
|   mIMENotificationRequests = IMENotificationRequests();
 | |
| }
 | |
| 
 | |
| void TextEventDispatcher::UpdateNotificationRequests() {
 | |
|   ClearNotificationRequests();
 | |
| 
 | |
|   // If it doesn't has focus, no notifications are available.
 | |
|   if (!mHasFocus || !mWidget) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If there is a listener, its requests are necessary.
 | |
|   nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
 | |
|   if (listener) {
 | |
|     mIMENotificationRequests = listener->GetIMENotificationRequests();
 | |
|   }
 | |
| 
 | |
|   // Even if this is in non-native input transaction, native IME needs
 | |
|   // requests.  So, add native IME requests too.
 | |
|   if (!IsInNativeInputTransaction()) {
 | |
|     nsCOMPtr<TextEventDispatcherListener> nativeListener =
 | |
|         mWidget->GetNativeTextEventDispatcherListener();
 | |
|     if (nativeListener) {
 | |
|       mIMENotificationRequests |= nativeListener->GetIMENotificationRequests();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TextEventDispatcher::DispatchKeyboardEvent(
 | |
|     EventMessage aMessage, const WidgetKeyboardEvent& aKeyboardEvent,
 | |
|     nsEventStatus& aStatus, void* aData) {
 | |
|   return DispatchKeyboardEventInternal(aMessage, aKeyboardEvent, aStatus,
 | |
|                                        aData);
 | |
| }
 | |
| 
 | |
| bool TextEventDispatcher::DispatchKeyboardEventInternal(
 | |
|     EventMessage aMessage, const WidgetKeyboardEvent& aKeyboardEvent,
 | |
|     nsEventStatus& aStatus, void* aData, uint32_t aIndexOfKeypress,
 | |
|     bool aNeedsCallback) {
 | |
|   // Note that this method is also used for dispatching key events on a plugin
 | |
|   // because key events on a plugin should be dispatched same as normal key
 | |
|   // events.  Then, only some handlers which need to intercept key events
 | |
|   // before the focused plugin (e.g., reserved shortcut key handlers) can
 | |
|   // consume the events.
 | |
|   MOZ_ASSERT(
 | |
|       aMessage == eKeyDown || aMessage == eKeyUp || aMessage == eKeyPress,
 | |
|       "Invalid aMessage value");
 | |
|   nsresult rv = GetState();
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // If the key shouldn't cause keypress events, don't this patch them.
 | |
|   if (aMessage == eKeyPress && !aKeyboardEvent.ShouldCauseKeypressEvents()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Basically, key events shouldn't be dispatched during composition.
 | |
|   // Note that plugin process has different IME context.  Therefore, we don't
 | |
|   // need to check our composition state when the key event is fired on a
 | |
|   // plugin.
 | |
|   if (IsComposing()) {
 | |
|     // However, if we need to behave like other browsers, we need the keydown
 | |
|     // and keyup events.  Note that this behavior is also allowed by D3E spec.
 | |
|     // FYI: keypress events must not be fired during composition.
 | |
|     if (!StaticPrefs::dom_keyboardevent_dispatch_during_composition() ||
 | |
|         aMessage == eKeyPress) {
 | |
|       return false;
 | |
|     }
 | |
|     // XXX If there was mOnlyContentDispatch for this case, it might be useful
 | |
|     //     because our chrome doesn't assume that key events are fired during
 | |
|     //     composition.
 | |
|   }
 | |
| 
 | |
|   WidgetKeyboardEvent keyEvent(true, aMessage, mWidget);
 | |
|   InitEvent(keyEvent);
 | |
|   keyEvent.AssignKeyEventData(aKeyboardEvent, false);
 | |
|   // Command arrays are not duplicated by AssignKeyEventData() due to
 | |
|   // both performance and footprint reasons.  So, when TextInputProcessor
 | |
|   // emulates real text input or synthesizing keyboard events for tests,
 | |
|   // the arrays may be initialized all commands already.  If so, we need to
 | |
|   // duplicate the arrays here, but we should do this only when we're
 | |
|   // dispatching eKeyPress events because BrowserParent::SendRealKeyEvent()
 | |
|   // does this only for eKeyPress event.  Note that this is not required if
 | |
|   // we're in the main process because in the parent process, the edit commands
 | |
|   // will be initialized by `ExecuteEditCommands()` (when the event is handled
 | |
|   // by editor event listener) or `InitAllEditCommands()` (when the event is
 | |
|   // set to a content process).  We should test whether these pathes work or
 | |
|   // not too.
 | |
|   if (XRE_IsContentProcess() && keyEvent.mIsSynthesizedByTIP) {
 | |
|     if (aMessage == eKeyPress) {
 | |
|       keyEvent.AssignCommands(aKeyboardEvent);
 | |
|     } else {
 | |
|       // Prevent retriving native edit commands if we're in a content process
 | |
|       // because only `eKeyPress` events coming from the main process have
 | |
|       // edit commands (See `BrowserParent::SendRealKeyEvent`).  And also
 | |
|       // retriving edit commands from a content process requires synchonous
 | |
|       // IPC and that makes running tests slower.  Therefore, we should mark
 | |
|       // the `eKeyPress` event does not need to retrieve edit commands anymore.
 | |
|       keyEvent.PreventNativeKeyBindings();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aStatus == nsEventStatus_eConsumeNoDefault) {
 | |
|     // If the key event should be dispatched as consumed event, marking it here.
 | |
|     // This is useful to prevent double action.  This is intended to the system
 | |
|     // has already consumed the event but we need to dispatch the event for
 | |
|     // compatibility with older version and other browsers.  So, we should not
 | |
|     // stop cross process forwarding of them.
 | |
|     keyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow);
 | |
|   }
 | |
| 
 | |
|   // Corrects each member for the specific key event type.
 | |
|   if (keyEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
 | |
|     MOZ_ASSERT(!aIndexOfKeypress,
 | |
|                "aIndexOfKeypress must be 0 for non-printable key");
 | |
|     // If the keyboard event isn't caused by printable key, its charCode should
 | |
|     // be 0.
 | |
|     keyEvent.SetCharCode(0);
 | |
|   } else {
 | |
|     MOZ_DIAGNOSTIC_ASSERT_IF(aMessage == eKeyDown || aMessage == eKeyUp,
 | |
|                              !aIndexOfKeypress);
 | |
|     MOZ_DIAGNOSTIC_ASSERT_IF(
 | |
|         aMessage == eKeyPress,
 | |
|         aIndexOfKeypress < std::max<size_t>(keyEvent.mKeyValue.Length(), 1));
 | |
|     char16_t ch =
 | |
|         keyEvent.mKeyValue.IsEmpty() ? 0 : keyEvent.mKeyValue[aIndexOfKeypress];
 | |
|     keyEvent.SetCharCode(static_cast<uint32_t>(ch));
 | |
|     if (aMessage == eKeyPress) {
 | |
|       // keyCode of eKeyPress events of printable keys should be always 0.
 | |
|       keyEvent.mKeyCode = 0;
 | |
|       // eKeyPress events are dispatched for every character.
 | |
|       // So, each key value of eKeyPress events should be a character.
 | |
|       if (ch) {
 | |
|         keyEvent.mKeyValue.Assign(ch);
 | |
|       } else {
 | |
|         keyEvent.mKeyValue.Truncate();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (aMessage == eKeyUp) {
 | |
|     // mIsRepeat of keyup event must be false.
 | |
|     keyEvent.mIsRepeat = false;
 | |
|   }
 | |
|   // mIsComposing should be initialized later.
 | |
|   keyEvent.mIsComposing = false;
 | |
|   if (mInputTransactionType == eNativeInputTransaction) {
 | |
|     // Copy mNativeKeyEvent here because for safety for other users of
 | |
|     // AssignKeyEventData(), it doesn't copy this.
 | |
|     keyEvent.mNativeKeyEvent = aKeyboardEvent.mNativeKeyEvent;
 | |
|   } else {
 | |
|     // If it's not a keyboard event for native key event, we should ensure that
 | |
|     // mNativeKeyEvent is null.
 | |
|     keyEvent.mNativeKeyEvent = nullptr;
 | |
|   }
 | |
|   // TODO: Manage mUniqueId here.
 | |
| 
 | |
|   // Request the alternative char codes for the key event.
 | |
|   // eKeyDown also needs alternative char codes because nsXBLWindowKeyHandler
 | |
|   // needs to check if a following keypress event is reserved by chrome for
 | |
|   // stopping propagation of its preceding keydown event.
 | |
|   keyEvent.mAlternativeCharCodes.Clear();
 | |
|   if ((aMessage == eKeyDown || aMessage == eKeyPress) &&
 | |
|       (aNeedsCallback || keyEvent.IsControl() || keyEvent.IsAlt() ||
 | |
|        keyEvent.IsMeta() || keyEvent.IsOS())) {
 | |
|     nsCOMPtr<TextEventDispatcherListener> listener =
 | |
|         do_QueryReferent(mListener);
 | |
|     if (listener) {
 | |
|       DebugOnly<WidgetKeyboardEvent> original(keyEvent);
 | |
|       listener->WillDispatchKeyboardEvent(this, keyEvent, aIndexOfKeypress,
 | |
|                                           aData);
 | |
|       MOZ_ASSERT(keyEvent.mMessage ==
 | |
|                  static_cast<WidgetKeyboardEvent&>(original).mMessage);
 | |
|       MOZ_ASSERT(keyEvent.mKeyCode ==
 | |
|                  static_cast<WidgetKeyboardEvent&>(original).mKeyCode);
 | |
|       MOZ_ASSERT(keyEvent.mLocation ==
 | |
|                  static_cast<WidgetKeyboardEvent&>(original).mLocation);
 | |
|       MOZ_ASSERT(keyEvent.mIsRepeat ==
 | |
|                  static_cast<WidgetKeyboardEvent&>(original).mIsRepeat);
 | |
|       MOZ_ASSERT(keyEvent.mIsComposing ==
 | |
|                  static_cast<WidgetKeyboardEvent&>(original).mIsComposing);
 | |
|       MOZ_ASSERT(keyEvent.mKeyNameIndex ==
 | |
|                  static_cast<WidgetKeyboardEvent&>(original).mKeyNameIndex);
 | |
|       MOZ_ASSERT(keyEvent.mCodeNameIndex ==
 | |
|                  static_cast<WidgetKeyboardEvent&>(original).mCodeNameIndex);
 | |
|       MOZ_ASSERT(keyEvent.mKeyValue ==
 | |
|                  static_cast<WidgetKeyboardEvent&>(original).mKeyValue);
 | |
|       MOZ_ASSERT(keyEvent.mCodeValue ==
 | |
|                  static_cast<WidgetKeyboardEvent&>(original).mCodeValue);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (StaticPrefs::
 | |
|           dom_keyboardevent_keypress_dispatch_non_printable_keys_only_system_group_in_content() &&
 | |
|       keyEvent.mMessage == eKeyPress &&
 | |
|       !keyEvent.ShouldKeyPressEventBeFiredOnContent()) {
 | |
|     // Note that even if we set it to true, this may be overwritten by
 | |
|     // PresShell::DispatchEventToDOM().
 | |
|     keyEvent.mFlags.mOnlySystemGroupDispatchInContent = true;
 | |
|   }
 | |
| 
 | |
|   // If an editable element has focus and we're in the parent process, we should
 | |
|   // retrieve native key bindings right now because even if it matches with a
 | |
|   // reserved shortcut key, it should be handled by the editor.
 | |
|   if (XRE_IsParentProcess() && mHasFocus &&
 | |
|       (aMessage == eKeyDown || aMessage == eKeyPress)) {
 | |
|     keyEvent.InitAllEditCommands(mWritingMode);
 | |
|   }
 | |
| 
 | |
|   DispatchInputEvent(mWidget, keyEvent, aStatus);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool TextEventDispatcher::MaybeDispatchKeypressEvents(
 | |
|     const WidgetKeyboardEvent& aKeyboardEvent, nsEventStatus& aStatus,
 | |
|     void* aData, bool aNeedsCallback) {
 | |
|   // If the key event was consumed, keypress event shouldn't be fired.
 | |
|   if (aStatus == nsEventStatus_eConsumeNoDefault) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // If the key shouldn't cause keypress events, don't fire them.
 | |
|   if (!aKeyboardEvent.ShouldCauseKeypressEvents()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // If the key isn't a printable key or just inputting one character or
 | |
|   // no character, we should dispatch only one keypress.  Otherwise, i.e.,
 | |
|   // if the key is a printable key and inputs multiple characters, keypress
 | |
|   // event should be dispatched the count of inputting characters times.
 | |
|   size_t keypressCount =
 | |
|       aKeyboardEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING
 | |
|           ? 1
 | |
|           : std::max(static_cast<nsAString::size_type>(1),
 | |
|                      aKeyboardEvent.mKeyValue.Length());
 | |
|   bool isDispatched = false;
 | |
|   bool consumed = false;
 | |
|   for (size_t i = 0; i < keypressCount; i++) {
 | |
|     aStatus = nsEventStatus_eIgnore;
 | |
|     if (!DispatchKeyboardEventInternal(eKeyPress, aKeyboardEvent, aStatus,
 | |
|                                        aData, i, aNeedsCallback)) {
 | |
|       // The widget must have been gone.
 | |
|       break;
 | |
|     }
 | |
|     isDispatched = true;
 | |
|     if (!consumed) {
 | |
|       consumed = (aStatus == nsEventStatus_eConsumeNoDefault);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If one of the keypress event was consumed, return ConsumeNoDefault.
 | |
|   if (consumed) {
 | |
|     aStatus = nsEventStatus_eConsumeNoDefault;
 | |
|   }
 | |
| 
 | |
|   return isDispatched;
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
|  * TextEventDispatcher::PendingComposition
 | |
|  *****************************************************************************/
 | |
| 
 | |
| TextEventDispatcher::PendingComposition::PendingComposition() { Clear(); }
 | |
| 
 | |
| void TextEventDispatcher::PendingComposition::Clear() {
 | |
|   mString.Truncate();
 | |
|   mClauses = nullptr;
 | |
|   mCaret.mRangeType = TextRangeType::eUninitialized;
 | |
|   mReplacedNativeLineBreakers = false;
 | |
| }
 | |
| 
 | |
| void TextEventDispatcher::PendingComposition::EnsureClauseArray() {
 | |
|   if (mClauses) {
 | |
|     return;
 | |
|   }
 | |
|   mClauses = new TextRangeArray();
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::PendingComposition::SetString(
 | |
|     const nsAString& aString) {
 | |
|   MOZ_ASSERT(!mReplacedNativeLineBreakers);
 | |
|   mString = aString;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::PendingComposition::AppendClause(
 | |
|     uint32_t aLength, TextRangeType aTextRangeType) {
 | |
|   MOZ_ASSERT(!mReplacedNativeLineBreakers);
 | |
| 
 | |
|   if (NS_WARN_IF(!aLength)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   switch (aTextRangeType) {
 | |
|     case TextRangeType::eRawClause:
 | |
|     case TextRangeType::eSelectedRawClause:
 | |
|     case TextRangeType::eConvertedClause:
 | |
|     case TextRangeType::eSelectedClause: {
 | |
|       EnsureClauseArray();
 | |
|       TextRange textRange;
 | |
|       textRange.mStartOffset =
 | |
|           mClauses->IsEmpty() ? 0 : mClauses->LastElement().mEndOffset;
 | |
|       textRange.mEndOffset = textRange.mStartOffset + aLength;
 | |
|       textRange.mRangeType = aTextRangeType;
 | |
|       mClauses->AppendElement(textRange);
 | |
|       return NS_OK;
 | |
|     }
 | |
|     default:
 | |
|       return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::PendingComposition::SetCaret(uint32_t aOffset,
 | |
|                                                            uint32_t aLength) {
 | |
|   MOZ_ASSERT(!mReplacedNativeLineBreakers);
 | |
| 
 | |
|   mCaret.mStartOffset = aOffset;
 | |
|   mCaret.mEndOffset = mCaret.mStartOffset + aLength;
 | |
|   mCaret.mRangeType = TextRangeType::eCaret;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::PendingComposition::Set(
 | |
|     const nsAString& aString, const TextRangeArray* aRanges) {
 | |
|   Clear();
 | |
| 
 | |
|   nsresult rv = SetString(aString);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (!aRanges || aRanges->IsEmpty()) {
 | |
|     // Create dummy range if mString isn't empty.
 | |
|     if (!mString.IsEmpty()) {
 | |
|       rv = AppendClause(mString.Length(), TextRangeType::eRawClause);
 | |
|       if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|         return rv;
 | |
|       }
 | |
|       ReplaceNativeLineBreakers();
 | |
|     }
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Adjust offsets in the ranges for XP linefeed character (only \n).
 | |
|   for (uint32_t i = 0; i < aRanges->Length(); ++i) {
 | |
|     TextRange range = aRanges->ElementAt(i);
 | |
|     if (range.mRangeType == TextRangeType::eCaret) {
 | |
|       mCaret = range;
 | |
|     } else {
 | |
|       EnsureClauseArray();
 | |
|       mClauses->AppendElement(range);
 | |
|     }
 | |
|   }
 | |
|   ReplaceNativeLineBreakers();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void TextEventDispatcher::PendingComposition::ReplaceNativeLineBreakers() {
 | |
|   mReplacedNativeLineBreakers = true;
 | |
| 
 | |
|   // If the composition string is empty, we don't need to do anything.
 | |
|   if (mString.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoString nativeString(mString);
 | |
|   // Don't expose CRLF nor CR to web contents, instead, use LF.
 | |
|   mString.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
 | |
|   mString.ReplaceSubstring(u"\r"_ns, u"\n"_ns);
 | |
| 
 | |
|   // If the length isn't changed, we don't need to adjust any offset and length
 | |
|   // of mClauses nor mCaret.
 | |
|   if (nativeString.Length() == mString.Length()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mClauses) {
 | |
|     for (TextRange& clause : *mClauses) {
 | |
|       AdjustRange(clause, nativeString);
 | |
|     }
 | |
|   }
 | |
|   if (mCaret.mRangeType == TextRangeType::eCaret) {
 | |
|     AdjustRange(mCaret, nativeString);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // static
 | |
| void TextEventDispatcher::PendingComposition::AdjustRange(
 | |
|     TextRange& aRange, const nsAString& aNativeString) {
 | |
|   TextRange nativeRange = aRange;
 | |
|   // XXX Following code wastes runtime cost because this causes computing
 | |
|   //     mStartOffset for each clause from the start of composition string.
 | |
|   //     If we'd make TextRange have only its length, we don't need to do
 | |
|   //     this.  However, this must not be so serious problem because
 | |
|   //     composition string is usually short and separated as a few clauses.
 | |
|   if (nativeRange.mStartOffset > 0) {
 | |
|     nsAutoString preText(Substring(aNativeString, 0, nativeRange.mStartOffset));
 | |
|     preText.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
 | |
|     aRange.mStartOffset = preText.Length();
 | |
|   }
 | |
|   if (nativeRange.Length() == 0) {
 | |
|     aRange.mEndOffset = aRange.mStartOffset;
 | |
|   } else {
 | |
|     nsAutoString clause(Substring(aNativeString, nativeRange.mStartOffset,
 | |
|                                   nativeRange.Length()));
 | |
|     clause.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
 | |
|     aRange.mEndOffset = aRange.mStartOffset + clause.Length();
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult TextEventDispatcher::PendingComposition::Flush(
 | |
|     TextEventDispatcher* aDispatcher, nsEventStatus& aStatus,
 | |
|     const WidgetEventTime* aEventTime) {
 | |
|   aStatus = nsEventStatus_eIgnore;
 | |
| 
 | |
|   nsresult rv = aDispatcher->GetState();
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (mClauses && !mClauses->IsEmpty() &&
 | |
|       mClauses->LastElement().mEndOffset != mString.Length()) {
 | |
|     NS_WARNING(
 | |
|         "Sum of length of the all clauses must be same as the string "
 | |
|         "length");
 | |
|     Clear();
 | |
|     return NS_ERROR_ILLEGAL_VALUE;
 | |
|   }
 | |
|   if (mCaret.mRangeType == TextRangeType::eCaret) {
 | |
|     if (mCaret.mEndOffset > mString.Length()) {
 | |
|       NS_WARNING("Caret position is out of the composition string");
 | |
|       Clear();
 | |
|       return NS_ERROR_ILLEGAL_VALUE;
 | |
|     }
 | |
|     EnsureClauseArray();
 | |
|     mClauses->AppendElement(mCaret);
 | |
|   }
 | |
| 
 | |
|   // If the composition string is set without Set(), we need to replace native
 | |
|   // line breakers in the composition string with XP line breaker.
 | |
|   if (!mReplacedNativeLineBreakers) {
 | |
|     ReplaceNativeLineBreakers();
 | |
|   }
 | |
| 
 | |
|   RefPtr<TextEventDispatcher> kungFuDeathGrip(aDispatcher);
 | |
|   nsCOMPtr<nsIWidget> widget(aDispatcher->mWidget);
 | |
|   WidgetCompositionEvent compChangeEvent(true, eCompositionChange, widget);
 | |
|   aDispatcher->InitEvent(compChangeEvent);
 | |
|   if (aEventTime) {
 | |
|     compChangeEvent.AssignEventTime(*aEventTime);
 | |
|   }
 | |
|   compChangeEvent.mData = mString;
 | |
|   // If mString comes from TextInputProcessor, it may be void, but editor
 | |
|   // requires non-void string even when it's empty.
 | |
|   compChangeEvent.mData.SetIsVoid(false);
 | |
|   if (mClauses) {
 | |
|     MOZ_ASSERT(!mClauses->IsEmpty(),
 | |
|                "mClauses must be non-empty array when it's not nullptr");
 | |
|     compChangeEvent.mRanges = mClauses;
 | |
|   }
 | |
| 
 | |
|   // While this method dispatches a composition event, some other event handler
 | |
|   // cause more clauses to be added.  So, we should clear pending composition
 | |
|   // before dispatching the event.
 | |
|   Clear();
 | |
| 
 | |
|   rv = aDispatcher->StartCompositionAutomaticallyIfNecessary(aStatus,
 | |
|                                                              aEventTime);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
|   if (aStatus == nsEventStatus_eConsumeNoDefault) {
 | |
|     return NS_OK;
 | |
|   }
 | |
|   rv = aDispatcher->DispatchEvent(widget, compChangeEvent, aStatus);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| }  // namespace widget
 | |
| }  // namespace mozilla
 |