void TextComposition::DispatchCompositionEvent( WidgetCompositionEvent* aCompositionEvent, nsEventStatus* aStatus, EventDispatchingCallback* aCallBack, bool aIsSynthesized) { mWasCompositionStringEmpty = mString.IsEmpty(); if (aCompositionEvent->IsFollowedByCompositionEnd()) { mHasReceivedCommitEvent = true; } // If this instance has requested to commit or cancel composition but // is not synthesizing commit event, that means that the IME commits or // cancels the composition asynchronously. Typically, iBus behaves so. // Then, synthesized events which were dispatched immediately after // the request has already committed our editor's composition string and // told it to web apps. Therefore, we should ignore the delayed events. if (mRequestedToCommitOrCancel && !aIsSynthesized) { *aStatus = nsEventStatus_eConsumeNoDefault; return; } // If the content is a container of TabParent, composition should be in the // remote process. if (mTabParent) { Unused << mTabParent->SendCompositionEvent(*aCompositionEvent); aCompositionEvent->StopPropagation(); if (aCompositionEvent->CausesDOMTextEvent()) { mLastData = aCompositionEvent->mData; mLastRanges = aCompositionEvent->mRanges; // Although, the composition event hasn't been actually handled yet, // emulate an editor to be handling the composition event. EditorWillHandleCompositionChangeEvent(aCompositionEvent); EditorDidHandleCompositionChangeEvent(); } return; } if (!mAllowControlCharacters) { RemoveControlCharactersFrom(aCompositionEvent->mData, aCompositionEvent->mRanges); } if (aCompositionEvent->mMessage == eCompositionCommitAsIs) { NS_ASSERTION(!aCompositionEvent->mRanges, "mRanges of eCompositionCommitAsIs should be null"); aCompositionEvent->mRanges = nullptr; NS_ASSERTION(aCompositionEvent->mData.IsEmpty(), "mData of eCompositionCommitAsIs should be empty string"); bool removePlaceholderCharacter = Preferences::GetBool("intl.ime.remove_placeholder_character_at_commit", false); if (removePlaceholderCharacter && mLastData == IDEOGRAPHIC_SPACE) { // If the last data is an ideographic space (FullWidth space), it might be // a placeholder character of some Chinese IME. So, committing with // this data might not be expected by users. Let's use empty string. aCompositionEvent->mData.Truncate(); } else { aCompositionEvent->mData = mLastData; } } else if (aCompositionEvent->mMessage == eCompositionCommit) { NS_ASSERTION(!aCompositionEvent->mRanges, "mRanges of eCompositionCommit should be null"); aCompositionEvent->mRanges = nullptr; } if (!IsValidStateForComposition(aCompositionEvent->mWidget)) { *aStatus = nsEventStatus_eConsumeNoDefault; return; } // IME may commit composition with empty string for a commit request or // with non-empty string for a cancel request. We should prevent such // unexpected result. E.g., web apps may be confused if they implement // autocomplete which attempts to commit composition forcibly when the user // selects one of suggestions but composition string is cleared by IME. // Note that most Chinese IMEs don't expose actual composition string to us. // They typically tell us an IDEOGRAPHIC SPACE or empty string as composition // string. Therefore, we should hack it only when: // 1. committing string is empty string at requesting commit but the last // data isn't IDEOGRAPHIC SPACE. // 2. non-empty string is committed at requesting cancel. if (!aIsSynthesized && (mIsRequestingCommit || mIsRequestingCancel)) { nsString* committingData = nullptr; switch (aCompositionEvent->mMessage) { case eCompositionEnd: case eCompositionChange: case eCompositionCommitAsIs: case eCompositionCommit: committingData = &aCompositionEvent->mData; break; default: NS_WARNING("Unexpected event comes during committing or " "canceling composition"); break; } if (committingData) { if (mIsRequestingCommit && committingData->IsEmpty() && mLastData != IDEOGRAPHIC_SPACE) { committingData->Assign(mLastData); } else if (mIsRequestingCancel && !committingData->IsEmpty()) { committingData->Truncate(); } } } bool dispatchEvent = true; bool dispatchDOMTextEvent = aCompositionEvent->CausesDOMTextEvent(); // When mIsComposing is false but the committing string is different from // the last data (E.g., previous eCompositionChange event made the // composition string empty or didn't have clause information), we don't // need to dispatch redundant DOM text event. (But note that we need to // dispatch eCompositionChange event if we have not dispatched // eCompositionChange event yet and commit string replaces selected string // with empty string since selected string hasn't been replaced with empty // string yet.) if (dispatchDOMTextEvent && aCompositionEvent->mMessage != eCompositionChange && !mIsComposing && mHasDispatchedDOMTextEvent && mLastData == aCompositionEvent->mData) { dispatchEvent = dispatchDOMTextEvent = false; } // widget may dispatch redundant eCompositionChange event // which modifies neither composition string, clauses nor caret // position. In such case, we shouldn't dispatch DOM events. if (dispatchDOMTextEvent && aCompositionEvent->mMessage == eCompositionChange && mLastData == aCompositionEvent->mData && mRanges && aCompositionEvent->mRanges && mRanges->Equals(*aCompositionEvent->mRanges)) { dispatchEvent = dispatchDOMTextEvent = false; } if (dispatchDOMTextEvent) { if (!MaybeDispatchCompositionUpdate(aCompositionEvent)) { return; } } if (dispatchEvent) { // If the composition event should cause a DOM text event, we should // overwrite the event message as eCompositionChange because due to // the limitation of mapping between event messages and DOM event types, // we cannot map multiple event messages to a DOM event type. if (dispatchDOMTextEvent && aCompositionEvent->mMessage != eCompositionChange) { mHasDispatchedDOMTextEvent = true; aCompositionEvent->mFlags = CloneAndDispatchAs(aCompositionEvent, eCompositionChange, aStatus, aCallBack); } else { if (aCompositionEvent->mMessage == eCompositionChange) { mHasDispatchedDOMTextEvent = true; } DispatchEvent(aCompositionEvent, aStatus, aCallBack); } } else { *aStatus = nsEventStatus_eConsumeNoDefault; } if (!IsValidStateForComposition(aCompositionEvent->mWidget)) { return; } // Emulate editor behavior of compositionchange event (DOM text event) handler // if no editor handles composition events. if (dispatchDOMTextEvent && !HasEditor()) { EditorWillHandleCompositionChangeEvent(aCompositionEvent); EditorDidHandleCompositionChangeEvent(); } if (aCompositionEvent->CausesDOMCompositionEndEvent()) { // Dispatch a compositionend event if it's necessary. if (aCompositionEvent->mMessage != eCompositionEnd) { CloneAndDispatchAs(aCompositionEvent, eCompositionEnd); } MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?"); MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?"); } MaybeNotifyIMEOfCompositionEventHandled(aCompositionEvent); }
void TextComposition::DispatchEvent(WidgetGUIEvent* aEvent, nsEventStatus* aStatus, EventDispatchingCallback* aCallBack, bool aIsSynthesized) { if (Destroyed()) { *aStatus = nsEventStatus_eConsumeNoDefault; return; } // If this instance has requested to commit or cancel composition but // is not synthesizing commit event, that means that the IME commits or // cancels the composition asynchronously. Typically, iBus behaves so. // Then, synthesized events which were dispatched immediately after // the request has already committed our editor's composition string and // told it to web apps. Therefore, we should ignore the delayed events. if (mRequestedToCommitOrCancel && !aIsSynthesized) { *aStatus = nsEventStatus_eConsumeNoDefault; return; } // IME may commit composition with empty string for a commit request or // with non-empty string for a cancel request. We should prevent such // unexpected result. E.g., web apps may be confused if they implement // autocomplete which attempts to commit composition forcibly when the user // selects one of suggestions but composition string is cleared by IME. // Note that most Chinese IMEs don't expose actual composition string to us. // They typically tell us an IDEOGRAPHIC SPACE or empty string as composition // string. Therefore, we should hack it only when: // 1. committing string is empty string at requesting commit but the last // data isn't IDEOGRAPHIC SPACE. // 2. non-empty string is committed at requesting cancel. if (!aIsSynthesized && (mIsRequestingCommit || mIsRequestingCancel)) { nsString* committingData = nullptr; switch (aEvent->message) { case NS_COMPOSITION_UPDATE: case NS_COMPOSITION_END: committingData = &aEvent->AsCompositionEvent()->data; break; case NS_TEXT_TEXT: committingData = &aEvent->AsTextEvent()->theText; break; default: NS_WARNING("Unexpected event comes during committing or " "canceling composition"); break; } if (committingData) { if (mIsRequestingCommit && committingData->IsEmpty() && mLastData != IDEOGRAPHIC_SPACE) { committingData->Assign(mLastData); } else if (mIsRequestingCancel && !committingData->IsEmpty()) { committingData->Truncate(); } if (aEvent->message == NS_COMPOSITION_UPDATE) { // If committing string is not different from the last data, // we don't need to dispatch this. if (committingData->Equals(mLastData)) { return; } } else if (aEvent->message == NS_TEXT_TEXT) { // If committing string is different from the last data, // we need to dispatch compositionupdate before dispatching text event. if (!MaybeDispatchCompositionUpdate(aEvent->AsTextEvent())) { NS_WARNING("Dispatching compositionupdate caused destroying"); return; } } } } if (aEvent->message == NS_COMPOSITION_UPDATE) { mLastData = aEvent->AsCompositionEvent()->data; } EventDispatcher::Dispatch(mNode, mPresContext, aEvent, nullptr, aStatus, aCallBack); if (NS_WARN_IF(Destroyed())) { return; } // Emulate editor behavior of text event handler if no editor handles // composition/text events. if (aEvent->message == NS_TEXT_TEXT && !HasEditor()) { EditorWillHandleTextEvent(aEvent->AsTextEvent()); EditorDidHandleTextEvent(); } #ifdef DEBUG else if (aEvent->message == NS_COMPOSITION_END) { MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?"); MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?"); } #endif // #ifdef DEBUG // Notify composition update to widget if possible NotityUpdateComposition(aEvent); }
nsresult TextComposition::RequestToCommit(nsIWidget* aWidget, bool aDiscard) { // If this composition is already requested to be committed or canceled, // we don't need to request it again because even if the first request // failed, new request won't success, probably. And we shouldn't synthesize // events for committing or canceling composition twice or more times. if (mRequestedToCommitOrCancel) { return NS_OK; } nsRefPtr<TextComposition> kungFuDeathGrip(this); const nsAutoString lastData(mLastData); { AutoRestore<bool> saveRequestingCancel(mIsRequestingCancel); AutoRestore<bool> saveRequestingCommit(mIsRequestingCommit); if (aDiscard) { mIsRequestingCancel = true; mIsRequestingCommit = false; } else { mIsRequestingCancel = false; mIsRequestingCommit = true; } if (!mIsSynthesizedForTests) { // FYI: CompositionEvent and TextEvent caused by a call of NotifyIME() // may be discarded by PresShell if it's not safe to dispatch the // event. nsresult rv = aWidget->NotifyIME(IMENotification(aDiscard ? REQUEST_TO_CANCEL_COMPOSITION : REQUEST_TO_COMMIT_COMPOSITION)); if (rv == NS_ERROR_NOT_IMPLEMENTED) { return rv; } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { // Emulates to commit or cancel the composition // FYI: These events may be discarded by PresShell if it's not safe to // dispatch the event. nsCOMPtr<nsIWidget> widget(aWidget); nsAutoString commitData(aDiscard ? EmptyString() : lastData); bool changingData = lastData != commitData; WidgetTextEvent textEvent(true, NS_TEXT_TEXT, widget); textEvent.theText = commitData; textEvent.mFlags.mIsSynthesizedForTests = true; MaybeDispatchCompositionUpdate(&textEvent); // If changing the data or committing string isn't empty, we need to // dispatch text event for setting the composition string without // IME selection. if (!Destroyed() && !widget->Destroyed() && (changingData || !commitData.IsEmpty())) { nsEventStatus status = nsEventStatus_eIgnore; widget->DispatchEvent(&textEvent, status); } if (!Destroyed() && !widget->Destroyed()) { nsEventStatus status = nsEventStatus_eIgnore; WidgetCompositionEvent endEvent(true, NS_COMPOSITION_END, widget); endEvent.data = commitData; endEvent.mFlags.mIsSynthesizedForTests = true; widget->DispatchEvent(&endEvent, status); } } } mRequestedToCommitOrCancel = true; // If the request is performed synchronously, this must be already destroyed. if (Destroyed()) { return NS_OK; } // Otherwise, synthesize the commit in content. nsAutoString data(aDiscard ? EmptyString() : lastData); bool changingData = lastData != data; if (changingData) { DispatchCompositionEventRunnable(NS_COMPOSITION_UPDATE, data, true); } // If the last composition string and new data are different, we need to // dispatch text event for removing IME selection. However, if the commit // string is empty string and it's not changed from the last data, we don't // need to dispatch text event. if (changingData || !data.IsEmpty()) { DispatchCompositionEventRunnable(NS_TEXT_TEXT, data, true); } DispatchCompositionEventRunnable(NS_COMPOSITION_END, data, true); return NS_OK; }
void TextComposition::DispatchCompositionEvent( WidgetCompositionEvent* aCompositionEvent, nsEventStatus* aStatus, EventDispatchingCallback* aCallBack, bool aIsSynthesized) { if (!mAllowControlCharacters) { RemoveControlCharactersFrom(aCompositionEvent->mData, aCompositionEvent->mRanges); } if (aCompositionEvent->message == NS_COMPOSITION_COMMIT_AS_IS) { NS_ASSERTION(!aCompositionEvent->mRanges, "mRanges of NS_COMPOSITION_COMMIT_AS_IS should be null"); aCompositionEvent->mRanges = nullptr; NS_ASSERTION(aCompositionEvent->mData.IsEmpty(), "mData of NS_COMPOSITION_COMMIT_AS_IS should be empty string"); if (mLastData == IDEOGRAPHIC_SPACE) { // If the last data is an ideographic space (FullWidth space), it must be // a placeholder character of some Chinese IME. So, committing with // this data must not be expected by users. Let's use empty string. aCompositionEvent->mData.Truncate(); } else { aCompositionEvent->mData = mLastData; } } else if (aCompositionEvent->message == NS_COMPOSITION_COMMIT) { NS_ASSERTION(!aCompositionEvent->mRanges, "mRanges of NS_COMPOSITION_COMMIT should be null"); aCompositionEvent->mRanges = nullptr; } if (!IsValidStateForComposition(aCompositionEvent->widget)) { *aStatus = nsEventStatus_eConsumeNoDefault; return; } // If this instance has requested to commit or cancel composition but // is not synthesizing commit event, that means that the IME commits or // cancels the composition asynchronously. Typically, iBus behaves so. // Then, synthesized events which were dispatched immediately after // the request has already committed our editor's composition string and // told it to web apps. Therefore, we should ignore the delayed events. if (mRequestedToCommitOrCancel && !aIsSynthesized) { *aStatus = nsEventStatus_eConsumeNoDefault; return; } // IME may commit composition with empty string for a commit request or // with non-empty string for a cancel request. We should prevent such // unexpected result. E.g., web apps may be confused if they implement // autocomplete which attempts to commit composition forcibly when the user // selects one of suggestions but composition string is cleared by IME. // Note that most Chinese IMEs don't expose actual composition string to us. // They typically tell us an IDEOGRAPHIC SPACE or empty string as composition // string. Therefore, we should hack it only when: // 1. committing string is empty string at requesting commit but the last // data isn't IDEOGRAPHIC SPACE. // 2. non-empty string is committed at requesting cancel. if (!aIsSynthesized && (mIsRequestingCommit || mIsRequestingCancel)) { nsString* committingData = nullptr; switch (aCompositionEvent->message) { case NS_COMPOSITION_END: case NS_COMPOSITION_CHANGE: case NS_COMPOSITION_COMMIT_AS_IS: case NS_COMPOSITION_COMMIT: committingData = &aCompositionEvent->mData; break; default: NS_WARNING("Unexpected event comes during committing or " "canceling composition"); break; } if (committingData) { if (mIsRequestingCommit && committingData->IsEmpty() && mLastData != IDEOGRAPHIC_SPACE) { committingData->Assign(mLastData); } else if (mIsRequestingCancel && !committingData->IsEmpty()) { committingData->Truncate(); } } } bool dispatchEvent = true; bool dispatchDOMTextEvent = aCompositionEvent->CausesDOMTextEvent(); // When mIsComposing is false but the committing string is different from // the last data (E.g., previous NS_COMPOSITION_CHANGE event made the // composition string empty or didn't have clause information), we don't // need to dispatch redundant DOM text event. if (dispatchDOMTextEvent && aCompositionEvent->message != NS_COMPOSITION_CHANGE && !mIsComposing && mLastData == aCompositionEvent->mData) { dispatchEvent = dispatchDOMTextEvent = false; } // widget may dispatch redundant NS_COMPOSITION_CHANGE event // which modifies neither composition string, clauses nor caret // position. In such case, we shouldn't dispatch DOM events. if (dispatchDOMTextEvent && aCompositionEvent->message == NS_COMPOSITION_CHANGE && mLastData == aCompositionEvent->mData && mRanges && aCompositionEvent->mRanges && mRanges->Equals(*aCompositionEvent->mRanges)) { dispatchEvent = dispatchDOMTextEvent = false; } if (dispatchDOMTextEvent) { if (!MaybeDispatchCompositionUpdate(aCompositionEvent)) { return; } } if (dispatchEvent) { // If the composition event should cause a DOM text event, we should // overwrite the event message as NS_COMPOSITION_CHANGE because due to // the limitation of mapping between event messages and DOM event types, // we cannot map multiple event messages to a DOM event type. if (dispatchDOMTextEvent && aCompositionEvent->message != NS_COMPOSITION_CHANGE) { aCompositionEvent->mFlags = CloneAndDispatchAs(aCompositionEvent, NS_COMPOSITION_CHANGE, aStatus, aCallBack); } else { EventDispatcher::Dispatch(mNode, mPresContext, aCompositionEvent, nullptr, aStatus, aCallBack); } } else { *aStatus = nsEventStatus_eConsumeNoDefault; } if (!IsValidStateForComposition(aCompositionEvent->widget)) { return; } // Emulate editor behavior of compositionchange event (DOM text event) handler // if no editor handles composition events. if (dispatchDOMTextEvent && !HasEditor()) { EditorWillHandleCompositionChangeEvent(aCompositionEvent); EditorDidHandleCompositionChangeEvent(); } if (aCompositionEvent->CausesDOMCompositionEndEvent()) { // Dispatch a compositionend event if it's necessary. if (aCompositionEvent->message != NS_COMPOSITION_END) { CloneAndDispatchAs(aCompositionEvent, NS_COMPOSITION_END); } MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?"); MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?"); } // Notify composition update to widget if possible NotityUpdateComposition(aCompositionEvent); }