Ejemplo n.º 1
0
bool
TextComposition::MaybeDispatchCompositionUpdate(
                   const WidgetCompositionEvent* aCompositionEvent)
{
  if (!IsValidStateForComposition(aCompositionEvent->widget)) {
    return false;
  }

  if (mLastData == aCompositionEvent->mData) {
    return true;
  }
  CloneAndDispatchAs(aCompositionEvent, NS_COMPOSITION_UPDATE);
  return IsValidStateForComposition(aCompositionEvent->widget);
}
Ejemplo n.º 2
0
void
TextComposition::OnCompositionEventDispatched(
                   const WidgetCompositionEvent* aCompositionEvent)
{
  MOZ_RELEASE_ASSERT(!mTabParent);

  if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
    return;
  }

  // Every composition event may cause changing composition start offset,
  // especially when there is no composition string.  Therefore, we need to
  // update mCompositionStartOffset with the latest offset.

  MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionStart ||
               mWasCompositionStringEmpty,
             "mWasCompositionStringEmpty should be true if the dispatched "
             "event is eCompositionStart");

  if (mWasCompositionStringEmpty &&
      !aCompositionEvent->CausesDOMCompositionEndEvent()) {
    // If there was no composition string, current selection start may be the
    // offset for inserting composition string.
    // Update composition start offset with current selection start.
    mCompositionStartOffset = GetSelectionStartOffset();
    mTargetClauseOffsetInComposition = 0;
  }

  if (aCompositionEvent->CausesDOMTextEvent()) {
    mTargetClauseOffsetInComposition = aCompositionEvent->TargetClauseOffset();
  }
}
Ejemplo n.º 3
0
BaseEventFlags
TextComposition::CloneAndDispatchAs(
                   const WidgetCompositionEvent* aCompositionEvent,
                   EventMessage aMessage,
                   nsEventStatus* aStatus,
                   EventDispatchingCallback* aCallBack)
{
  MOZ_RELEASE_ASSERT(!mTabParent);

  MOZ_ASSERT(IsValidStateForComposition(aCompositionEvent->mWidget),
             "Should be called only when it's safe to dispatch an event");

  WidgetCompositionEvent compositionEvent(aCompositionEvent->IsTrusted(),
                                          aMessage, aCompositionEvent->mWidget);
  compositionEvent.mTime = aCompositionEvent->mTime;
  compositionEvent.mTimeStamp = aCompositionEvent->mTimeStamp;
  compositionEvent.mData = aCompositionEvent->mData;
  compositionEvent.mNativeIMEContext = aCompositionEvent->mNativeIMEContext;
  compositionEvent.mOriginalMessage = aCompositionEvent->mMessage;
  compositionEvent.mFlags.mIsSynthesizedForTests =
    aCompositionEvent->mFlags.mIsSynthesizedForTests;

  nsEventStatus dummyStatus = nsEventStatus_eConsumeNoDefault;
  nsEventStatus* status = aStatus ? aStatus : &dummyStatus;
  if (aMessage == eCompositionUpdate) {
    mLastData = compositionEvent.mData;
    mLastRanges = aCompositionEvent->mRanges;
  }

  DispatchEvent(&compositionEvent, status, aCallBack, aCompositionEvent);
  return compositionEvent.mFlags;
}
Ejemplo n.º 4
0
bool
TextComposition::MaybeDispatchCompositionUpdate(
                   const WidgetCompositionEvent* aCompositionEvent)
{
  MOZ_RELEASE_ASSERT(!mTabParent);

  if (!IsValidStateForComposition(aCompositionEvent->widget)) {
    return false;
  }

  if (mLastData == aCompositionEvent->mData) {
    return true;
  }
  CloneAndDispatchAs(aCompositionEvent, eCompositionUpdate);
  return IsValidStateForComposition(aCompositionEvent->widget);
}
Ejemplo n.º 5
0
BaseEventFlags
TextComposition::CloneAndDispatchAs(
                   const WidgetCompositionEvent* aCompositionEvent,
                   uint32_t aMessage,
                   nsEventStatus* aStatus,
                   EventDispatchingCallback* aCallBack)
{
  MOZ_ASSERT(IsValidStateForComposition(aCompositionEvent->widget),
             "Should be called only when it's safe to dispatch an event");

  WidgetCompositionEvent compositionEvent(aCompositionEvent->mFlags.mIsTrusted,
                                          aMessage, aCompositionEvent->widget);
  compositionEvent.time = aCompositionEvent->time;
  compositionEvent.timeStamp = aCompositionEvent->timeStamp;
  compositionEvent.mData = aCompositionEvent->mData;
  compositionEvent.mFlags.mIsSynthesizedForTests =
    aCompositionEvent->mFlags.mIsSynthesizedForTests;

  nsEventStatus dummyStatus = nsEventStatus_eConsumeNoDefault;
  nsEventStatus* status = aStatus ? aStatus : &dummyStatus;
  if (aMessage == NS_COMPOSITION_UPDATE) {
    mLastData = compositionEvent.mData;
  }
  EventDispatcher::Dispatch(mNode, mPresContext,
                            &compositionEvent, nullptr, status, aCallBack);
  return compositionEvent.mFlags;
}
Ejemplo n.º 6
0
bool
TextComposition::MaybeDispatchCompositionUpdate(
                   const WidgetCompositionEvent* aCompositionEvent)
{
  MOZ_RELEASE_ASSERT(!mTabParent);

  if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
    return false;
  }

  // Note that we don't need to dispatch eCompositionUpdate event even if
  // mHasDispatchedDOMTextEvent is false and eCompositionCommit event is
  // dispatched with empty string immediately after eCompositionStart
  // because composition string has never been changed from empty string to
  // non-empty string in such composition even if selected string was not
  // empty string (mLastData isn't set to selected text when this receives
  // eCompositionStart).
  if (mLastData == aCompositionEvent->mData) {
    return true;
  }
  CloneAndDispatchAs(aCompositionEvent, eCompositionUpdate);
  return IsValidStateForComposition(aCompositionEvent->mWidget);
}
Ejemplo n.º 7
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);
}
Ejemplo n.º 8
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);
}