Bug 1286157 TSFTextStore should use insertion point relative offset query when cached contents for TSF and actual content (or content cache) are different r=m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 12 Jul 2016 16:52:48 +0900
changeset 399824 9507ccd2dfdf4fe039435d04c1f79d490be785fd
parent 399698 233ab21b64b5d5e9f2f16ea2d4cfb4c8b293c5c4
child 399825 77d541f21b78b4270c109f2a4e9db59c19129e03
push id26005
push usermasayuki@d-toybox.com
push dateFri, 12 Aug 2016 06:25:43 +0000
reviewersm_kato
bugs1286157
milestone51.0a1
Bug 1286157 TSFTextStore should use insertion point relative offset query when cached contents for TSF and actual content (or content cache) are different r=m_kato MozReview-Commit-ID: 3Q9T3XVvyCj
widget/windows/TSFTextStore.cpp
widget/windows/TSFTextStore.h
--- a/widget/windows/TSFTextStore.cpp
+++ b/widget/windows/TSFTextStore.cpp
@@ -2093,16 +2093,39 @@ TSFTextStore::ContentForTSFRef()
      GetEscapedUTF8String(mContentForTSF.LastCompositionString()).get(),
      mContentForTSF.LastCompositionString().Length(),
      mContentForTSF.MinTextModifiedOffset()));
 
   return mContentForTSF;
 }
 
 bool
+TSFTextStore::CanAccessActualContentDirectly() const
+{
+  if (!mContentForTSF.IsInitialized() || mSelectionForTSF.IsDirty()) {
+    return true;
+  }
+
+  // If the cached content has been changed by something except composition,
+  // the content cache may be different from actual content.
+  if (mPendingTextChangeData.IsValid() &&
+      !mPendingTextChangeData.mCausedOnlyByComposition) {
+    return false;
+  }
+
+  // If the cached selection isn't changed, cached content and actual content
+  // should be same.
+  if (!mPendingSelectionChangeData.IsValid()) {
+    return true;
+  }
+
+  return mSelectionForTSF.EqualsExceptDirection(mPendingSelectionChangeData);
+}
+
+bool
 TSFTextStore::GetCurrentText(nsAString& aTextContent)
 {
   if (mContentForTSF.IsInitialized()) {
     aTextContent = mContentForTSF.Text();
     return true;
   }
 
   MOZ_ASSERT(!mDestroyed);
@@ -3702,16 +3725,23 @@ TSFTextStore::GetTextExt(TsViewCookie vc
   int64_t startOffset = acpStart;
   if (mComposition.IsComposing()) {
     // If there is a composition, TSF must want character rects related to
     // the composition.  Therefore, we should use insertion point relative
     // query because the composition might be at different position from
     // the position where TSFTextStore believes it at.
     options.mRelativeToInsertionPoint = true;
     startOffset -= mComposition.mStart;
+  } else if (!CanAccessActualContentDirectly()) {
+    // If TSF/TIP cannot access actual content directly, there may be pending
+    // text and/or selection changes which have not been notified TSF yet.
+    // Therefore, we should use relative to insertion point query since
+    // TSF/TIP computes the offset from the cached selection.
+    options.mRelativeToInsertionPoint = true;
+    startOffset -= mSelectionForTSF.StartOffset();
   }
   event.InitForQueryTextRect(startOffset, acpEnd - acpStart, options);
 
   DispatchEvent(event);
   if (NS_WARN_IF(!event.mSucceeded)) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("0x%p   TSFTextStore::GetTextExt() FAILED due to "
        "eQueryTextRect failure", this));
@@ -5165,16 +5195,23 @@ TSFTextStore::CreateNativeCaret()
   //     collapsed, is it OK?
   int64_t caretOffset = selectionForTSF.MaxOffset();
   if (mComposition.IsComposing()) {
     // If there is a composition, use insertion point relative query for
     // deciding caret position because composition might be at different
     // position where TSFTextStore believes it at.
     options.mRelativeToInsertionPoint = true;
     caretOffset -= mComposition.mStart;
+  } else if (!CanAccessActualContentDirectly()) {
+    // If TSF/TIP cannot access actual content directly, there may be pending
+    // text and/or selection changes which have not been notified TSF yet.
+    // Therefore, we should use relative to insertion point query since
+    // TSF/TIP computes the offset from the cached selection.
+    options.mRelativeToInsertionPoint = true;
+    caretOffset -= mSelectionForTSF.StartOffset();
   }
   queryCaretRect.InitForQueryCaretRect(caretOffset, options);
 
   DispatchEvent(queryCaretRect);
   if (NS_WARN_IF(!queryCaretRect.mSucceeded)) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("0x%p   TSFTextStore::CreateNativeCaret() FAILED due to "
        "eQueryCaretRect failure (offset=%d)", this, caretOffset));
--- a/widget/windows/TSFTextStore.h
+++ b/widget/windows/TSFTextStore.h
@@ -559,16 +559,25 @@ protected:
       if (mACP.style.ase == aACP.style.ase) {
         return mACP.acpStart == aACP.acpStart &&
                mACP.acpEnd == aACP.acpEnd;
       }
       return mACP.acpStart == aACP.acpEnd &&
              mACP.acpEnd == aACP.acpStart;
     }
 
+    bool EqualsExceptDirection(
+           const SelectionChangeDataBase& aChangedSelection) const
+    {
+      MOZ_ASSERT(!mDirty);
+      MOZ_ASSERT(aChangedSelection.IsValid());
+      return aChangedSelection.Length() == static_cast<uint32_t>(Length()) &&
+             aChangedSelection.mOffset == static_cast<uint32_t>(StartOffset());
+    }
+
   private:
     TS_SELECTION_ACP mACP;
     WritingMode mWritingMode;
     bool mDirty;
   };
   // Don't access mSelection directly except at calling MarkDirty().
   // Use SelectionForTSFRef() instead.  This is modified immediately when
   // TSF requests to set selection and not updated by selection change in
@@ -832,16 +841,22 @@ protected:
   //  - When there is a composition, all dispatched events are handled by
   //    the focused editor which may be in a remote process.
   // So, if two compositions are created very quickly, this cache may not be
   // cleared between eCompositionCommit(AsIs) and eCompositionStart.
   Content mContentForTSF;
 
   Content& ContentForTSFRef();
 
+  // CanAccessActualContentDirectly() returns true when TSF/TIP can access
+  // actual content directly.  In other words, mContentForTSF and/or
+  // mSelectionForTSF doesn't cache content or they matches with actual
+  // contents due to no pending text/selection change notifications.
+  bool CanAccessActualContentDirectly() const;
+
   // While mContentForTSF is valid, this returns the text stored by it.
   // Otherwise, return the current text content retrieved by eQueryTextContent.
   bool GetCurrentText(nsAString& aTextContent);
 
   class MouseTracker final
   {
   public:
     static const DWORD kInvalidCookie = static_cast<DWORD>(-1);