Bug 1208043 - If composition is started with InsertTextAtSelection() and OnStartComposition() is called later, RecordCompositionStartAction() should cancel the last pending compositionend. r=emk, a=sylvestre
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 02 Oct 2015 10:51:47 +0900
changeset 296235 1db50a858c60248a94e20208f10673d159e51dc2
parent 296234 3fdb63e523f9ed7b749fca3f70d205bcb086a8e2
child 296236 0fca8425e231ec11ec5cfc736751db0d98a05e48
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemk, sylvestre
bugs1208043
milestone43.0a2
Bug 1208043 - If composition is started with InsertTextAtSelection() and OnStartComposition() is called later, RecordCompositionStartAction() should cancel the last pending compositionend. r=emk, a=sylvestre
widget/windows/TSFTextStore.cpp
widget/windows/TSFTextStore.h
--- a/widget/windows/TSFTextStore.cpp
+++ b/widget/windows/TSFTextStore.cpp
@@ -3935,20 +3935,32 @@ TSFTextStore::InsertTextAtSelectionInter
   TS_SELECTION_ACP oldSelection = lockedContent.Selection().ACP();
   if (!mComposition.IsComposing()) {
     // Use a temporary composition to contain the text
     PendingAction* compositionStart = mPendingActions.AppendElement();
     compositionStart->mType = PendingAction::COMPOSITION_START;
     compositionStart->mSelectionStart = oldSelection.acpStart;
     compositionStart->mSelectionLength =
       oldSelection.acpEnd - oldSelection.acpStart;
+    compositionStart->mAdjustSelection = false;
 
     PendingAction* compositionEnd = mPendingActions.AppendElement();
     compositionEnd->mType = PendingAction::COMPOSITION_END;
     compositionEnd->mData = aInsertStr;
+
+    MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+            ("TSF: 0x%p   TSFTextStore::InsertTextAtSelectionInternal() "
+             "appending pending compositionstart and compositionend... "
+             "PendingCompositionStart={ mSelectionStart=%d, "
+             "mSelectionLength=%d }, PendingCompositionEnd={ mData=\"%s\" "
+             "(Length()=%u) }",
+             this, compositionStart->mSelectionStart,
+             compositionStart->mSelectionLength,
+             NS_ConvertUTF16toUTF8(compositionEnd->mData).get(),
+             compositionEnd->mData.Length()));
   }
 
   lockedContent.ReplaceSelectedTextWith(aInsertStr);
 
   if (aTextChange) {
     aTextChange->acpStart = oldSelection.acpStart;
     aTextChange->acpOldEnd = oldSelection.acpEnd;
     aTextChange->acpNewEnd = lockedContent.Selection().EndOffset();
@@ -4023,16 +4035,44 @@ TSFTextStore::RecordCompositionStartActi
   if (!lockedContent.IsInitialized()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::RecordCompositionStartAction() FAILED "
             "due to LockedContent() failure", this));
     return E_FAIL;
   }
 
   CompleteLastActionIfStillIncomplete();
+
+  // TIP may have inserted text at selection before calling
+  // OnStartComposition().  In this case, we've already created a pair of
+  // pending compositionstart and pending compositionend.  If the pending
+  // compositionstart occurred same range as this composition, it was the
+  // start of this composition.  In such case, we should cancel the pending
+  // compositionend and start composition normally.
+  if (!aPreserveSelection &&
+      WasTextInsertedWithoutCompositionAt(aStart, aLength)) {
+    const PendingAction& pendingCompositionEnd = mPendingActions.LastElement();
+    const PendingAction& pendingCompositionStart =
+      mPendingActions[mPendingActions.Length() - 2];
+    lockedContent.RestoreCommittedComposition(
+      aComposition, pendingCompositionStart, pendingCompositionEnd);
+    mPendingActions.RemoveElementAt(mPendingActions.Length() - 1);
+    MOZ_LOG(sTextStoreLog, LogLevel::Info,
+           ("TSF: 0x%p   TSFTextStore::RecordCompositionStartAction() "
+            "succeeded: restoring the committed string as composing string, "
+            "mComposition={ mStart=%ld, mString.Length()=%ld, "
+            "mSelection={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
+            "style.fInterimChar=%s } }",
+            this, mComposition.mStart, mComposition.mString.Length(),
+            mSelection.StartOffset(), mSelection.EndOffset(),
+            GetActiveSelEndName(mSelection.ActiveSelEnd()),
+            GetBoolName(mSelection.IsInterimChar())));
+    return S_OK;
+  }
+
   PendingAction* action = mPendingActions.AppendElement();
   action->mType = PendingAction::COMPOSITION_START;
   action->mSelectionStart = aStart;
   action->mSelectionLength = aLength;
 
   Selection& currentSel = CurrentSelection();
   if (currentSel.IsDirty()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
@@ -5539,16 +5579,40 @@ TSFTextStore::Content::StartComposition(
     // XXX Do we need to set a new writing-mode here when setting a new
     // selection? Currently, we just preserve the existing value.
     mSelection.SetSelection(mComposition.mStart, mComposition.mString.Length(),
                             false, mSelection.GetWritingMode());
   }
 }
 
 void
+TSFTextStore::Content::RestoreCommittedComposition(
+                         ITfCompositionView* aCompositionView,
+                         const PendingAction& aPendingCompositionStart,
+                         const PendingAction& aCanceledCompositionEnd)
+{
+  MOZ_ASSERT(mInitialized);
+  MOZ_ASSERT(aCompositionView);
+  MOZ_ASSERT(!mComposition.mView);
+  MOZ_ASSERT(aPendingCompositionStart.mType ==
+               PendingAction::COMPOSITION_START);
+  MOZ_ASSERT(aCanceledCompositionEnd.mType ==
+               PendingAction::COMPOSITION_END);
+  MOZ_ASSERT(GetSubstring(
+               static_cast<uint32_t>(aPendingCompositionStart.mSelectionStart),
+               static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
+               aCanceledCompositionEnd.mData);
+
+  // Restore the committed string as composing string.
+  mComposition.Start(aCompositionView,
+                     aPendingCompositionStart.mSelectionStart,
+                     aCanceledCompositionEnd.mData);
+}
+
+void
 TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd)
 {
   MOZ_ASSERT(mInitialized);
   MOZ_ASSERT(mComposition.mView);
   MOZ_ASSERT(aCompEnd.mType == PendingAction::COMPOSITION_END);
 
   mSelection.CollapseAt(mComposition.mStart + aCompEnd.mData.Length());
   mComposition.End();
--- a/widget/windows/TSFTextStore.h
+++ b/widget/windows/TSFTextStore.h
@@ -544,16 +544,42 @@ protected:
     }
     PendingAction* newAction = mPendingActions.AppendElement();
     newAction->mType = PendingAction::COMPOSITION_UPDATE;
     newAction->mRanges = new TextRangeArray();
     newAction->mIncomplete = true;
     return newAction;
   }
 
+  /**
+   * WasTextInsertedWithoutCompositionAt() checks if text was inserted without
+   * composition immediately before (e.g., see InsertTextAtSelectionInternal()).
+   *
+   * @param aStart              The inserted offset you expected.
+   * @param aLength             The inserted text length you expected.
+   * @return                    true if the last pending actions are
+   *                            COMPOSITION_START and COMPOSITION_END and
+   *                            aStart and aLength match their information.
+   */
+  bool WasTextInsertedWithoutCompositionAt(LONG aStart, LONG aLength) const
+  {
+    if (mPendingActions.Length() < 2) {
+      return false;
+    }
+    const PendingAction& pendingLastAction = mPendingActions.LastElement();
+    if (pendingLastAction.mType != PendingAction::COMPOSITION_END ||
+        pendingLastAction.mData.Length() != aLength) {
+      return false;
+    }
+    const PendingAction& pendingPreLastAction =
+      mPendingActions[mPendingActions.Length() - 2];
+    return pendingPreLastAction.mType == PendingAction::COMPOSITION_START &&
+           pendingPreLastAction.mSelectionStart == aStart;
+  }
+
   bool IsPendingCompositionUpdateIncomplete() const
   {
     if (mPendingActions.IsEmpty()) {
       return false;
     }
     const PendingAction& lastAction = mPendingActions.LastElement();
     return lastAction.mType == PendingAction::COMPOSITION_UPDATE &&
            lastAction.mIncomplete;
@@ -637,16 +663,33 @@ protected:
     const nsDependentSubstring GetSubstring(uint32_t aStart,
                                             uint32_t aLength) const;
     void ReplaceSelectedTextWith(const nsAString& aString);
     void ReplaceTextWith(LONG aStart, LONG aLength, const nsAString& aString);
 
     void StartComposition(ITfCompositionView* aCompositionView,
                           const PendingAction& aCompStart,
                           bool aPreserveSelection);
+    /**
+     * RestoreCommittedComposition() restores the committed string as
+     * composing string.  If InsertTextAtSelection() or something is called
+     * before a call of OnStartComposition(), there is a pending
+     * compositionstart and a pending compositionend.  In this case, we
+     * need to cancel the pending compositionend and continue the composition.
+     *
+     * @param aCompositionView          The composition view.
+     * @param aPendingCompositionStart  The pending compositionstart which
+     *                                  started the committed composition.
+     * @param aCanceledCompositionEnd   The pending compositionend which is
+     *                                  canceled for restarting the composition.
+     */
+    void RestoreCommittedComposition(
+                         ITfCompositionView* aCompositionView,
+                         const PendingAction& aPendingCompositionStart,
+                         const PendingAction& aCanceledCompositionEnd);
     void EndComposition(const PendingAction& aCompEnd);
 
     const nsString& Text() const
     {
       MOZ_ASSERT(mInitialized);
       return mText;
     }
     const nsString& LastCompositionString() const