Exemplo n.º 1
0
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);
}
Exemplo n.º 2
0
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);
}
Exemplo n.º 3
0
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;
}
Exemplo n.º 4
0
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);
}