Bug 1280182 ContentCache should return non-empty rect as far as possible if query content event is relative to insertion point r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 22 Jun 2016 14:16:59 +0900
changeset 344438 e3c00a0692367cc1ea6cf6f40813abfaa74eba8a
parent 344437 b486db198015732dd0dd79063a9304bc9496bfb9
child 344439 0ca871e39a20d94c5c8948beb41867d679f3709e
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1280182
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1280182 ContentCache should return non-empty rect as far as possible if query content event is relative to insertion point r=m_kato ContentCache may store composition string's rects which are inserted by one or more older composition event. Even in such case, native IME, especially TIP of TSF, expects non-empty rects. Therefore, if native IME handler uses "insertion point relative query", ContentCache should return non-empty rect as far as possible. For example, even if query offset or range is not in its rect array of composition string, ContentCache should adjust the offset into the stored range. MozReview-Commit-ID: 7hcIqxOWFk2
widget/ContentCache.cpp
widget/ContentCache.h
--- a/widget/ContentCache.cpp
+++ b/widget/ContentCache.cpp
@@ -490,17 +490,18 @@ ContentCacheInParent::HandleQueryContent
     return false;
   }
 
   if (NS_WARN_IF(!aEvent.mInput.IsValidOffset()) ||
       NS_WARN_IF(!aEvent.mInput.IsValidEventMessage(aEvent.mMessage))) {
     return false;
   }
 
-  if (aEvent.mInput.mRelativeToInsertionPoint) {
+  bool isRelativeToInsertionPoint = aEvent.mInput.mRelativeToInsertionPoint;
+  if (isRelativeToInsertionPoint) {
     if (aWidget->PluginHasFocus()) {
       if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(0))) {
         return false;
       }
     } else if (mIsComposing) {
       if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(mCompositionStart))) {
         return false;
       }
@@ -600,29 +601,35 @@ ContentCacheInParent::HandleQueryContent
       if (NS_WARN_IF(!IsSelectionValid())) {
         // If content cache hasn't been initialized properly, make the query
         // failed.
         MOZ_LOG(sContentCacheLog, LogLevel::Error,
           ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
            "FAILED because mSelection is not valid", this));
         return true;
       }
+      // Note that if the query is relative to insertion point, the query was
+      // probably requested by native IME.  In such case, we should return
+      // non-empty rect since returning failure causes IME showing its window
+      // at odd position.
       if (aEvent.mInput.mLength) {
         if (NS_WARN_IF(!GetUnionTextRects(aEvent.mInput.mOffset,
                                           aEvent.mInput.mLength,
+                                          isRelativeToInsertionPoint,
                                           aEvent.mReply.mRect))) {
           // XXX We don't have cache for this request.
           MOZ_LOG(sContentCacheLog, LogLevel::Error,
             ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
              "FAILED to get union rect", this));
           return false;
         }
       } else {
         // If the length is 0, we should return caret rect instead.
         if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
+                                     isRelativeToInsertionPoint,
                                      aEvent.mReply.mRect))) {
           MOZ_LOG(sContentCacheLog, LogLevel::Error,
             ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
              "FAILED to get caret rect", this));
           return false;
         }
       }
       if (aEvent.mInput.mOffset < mText.Length()) {
@@ -654,17 +661,22 @@ ContentCacheInParent::HandleQueryContent
       if (NS_WARN_IF(!IsSelectionValid())) {
         // If content cache hasn't been initialized properly, make the query
         // failed.
         MOZ_LOG(sContentCacheLog, LogLevel::Error,
           ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
            "FAILED because mSelection is not valid", this));
         return true;
       }
+      // Note that if the query is relative to insertion point, the query was
+      // probably requested by native IME.  In such case, we should return
+      // non-empty rect since returning failure causes IME showing its window
+      // at odd position.
       if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
+                                   isRelativeToInsertionPoint,
                                    aEvent.mReply.mRect))) {
         MOZ_LOG(sContentCacheLog, LogLevel::Error,
           ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
            "FAILED to get caret rect", this));
         return false;
       }
       aEvent.mReply.mOffset = aEvent.mInput.mOffset;
       MOZ_LOG(sContentCacheLog, LogLevel::Info,
@@ -687,23 +699,26 @@ ContentCacheInParent::HandleQueryContent
       break;
   }
   aEvent.mSucceeded = true;
   return true;
 }
 
 bool
 ContentCacheInParent::GetTextRect(uint32_t aOffset,
+                                  bool aRoundToExistingOffset,
                                   LayoutDeviceIntRect& aTextRect) const
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCacheInParent: 0x%p GetTextRect(aOffset=%u), "
+    ("ContentCacheInParent: 0x%p GetTextRect(aOffset=%u, "
+     "aRoundToExistingOffset=%s), "
      "mTextRectArray={ mStart=%u, mRects.Length()=%u }, "
      "mSelection={ mAnchor=%u, mFocus=%u }",
-     this, aOffset, mTextRectArray.mStart, mTextRectArray.mRects.Length(),
+     this, aOffset, GetBoolName(aRoundToExistingOffset),
+     mTextRectArray.mStart, mTextRectArray.mRects.Length(),
      mSelection.mAnchor, mSelection.mFocus));
 
   if (!aOffset) {
     NS_WARN_IF(mFirstCharRect.IsEmpty());
     aTextRect = mFirstCharRect;
     return !aTextRect.IsEmpty();
   }
   if (aOffset == mSelection.mAnchor) {
@@ -712,36 +727,54 @@ ContentCacheInParent::GetTextRect(uint32
     return !aTextRect.IsEmpty();
   }
   if (aOffset == mSelection.mFocus) {
     NS_WARN_IF(mSelection.mFocusCharRect.IsEmpty());
     aTextRect = mSelection.mFocusCharRect;
     return !aTextRect.IsEmpty();
   }
 
+  uint32_t offset = aOffset;
   if (!mTextRectArray.InRange(aOffset)) {
-    aTextRect.SetEmpty();
-    return false;
+    if (!aRoundToExistingOffset) {
+      aTextRect.SetEmpty();
+      return false;
+    }
+    if (!mTextRectArray.IsValid()) {
+      // If there are no rects in mTextRectArray, we should refer the start of
+      // the selection because IME must query a char rect around it if there is
+      // no composition.
+      aTextRect = mSelection.StartCharRect();
+      return !aTextRect.IsEmpty();
+    }
+    if (offset < mTextRectArray.StartOffset()) {
+      offset = mTextRectArray.StartOffset();
+    } else {
+      offset = mTextRectArray.EndOffset() - 1;
+    }
   }
-  aTextRect = mTextRectArray.GetRect(aOffset);
-  return true;
+  aTextRect = mTextRectArray.GetRect(offset);
+  return !aTextRect.IsEmpty();
 }
 
 bool
 ContentCacheInParent::GetUnionTextRects(
                         uint32_t aOffset,
                         uint32_t aLength,
+                        bool aRoundToExistingOffset,
                         LayoutDeviceIntRect& aUnionTextRect) const
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
     ("ContentCacheInParent: 0x%p GetUnionTextRects(aOffset=%u, "
-     "aLength=%u), mTextRectArray={ mStart=%u, mRects.Length()=%u }, "
+     "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray={ "
+     "mStart=%u, mRects.Length()=%u }, "
      "mSelection={ mAnchor=%u, mFocus=%u }",
-     this, aOffset, aLength, mTextRectArray.mStart,
-     mTextRectArray.mRects.Length(), mSelection.mAnchor, mSelection.mFocus));
+     this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
+     mTextRectArray.mStart, mTextRectArray.mRects.Length(),
+     mSelection.mAnchor, mSelection.mFocus));
 
   CheckedInt<uint32_t> endOffset =
     CheckedInt<uint32_t>(aOffset) + aLength;
   if (!endOffset.isValid()) {
     return false;
   }
 
   if (!mSelection.Collapsed() &&
@@ -775,19 +808,21 @@ ContentCacheInParent::GetUnionTextRects(
   // in most cases.
 
   if (!aOffset && aOffset != mSelection.mAnchor &&
       aOffset != mSelection.mFocus && !mTextRectArray.InRange(aOffset)) {
     // The first character rect isn't cached.
     return false;
   }
 
-  if (mTextRectArray.IsOverlappingWith(aOffset, aLength)) {
+  if ((aRoundToExistingOffset && mTextRectArray.IsValid()) ||
+      mTextRectArray.IsOverlappingWith(aOffset, aLength)) {
     aUnionTextRect =
-      mTextRectArray.GetUnionRectAsFarAsPossible(aOffset, aLength);
+      mTextRectArray.GetUnionRectAsFarAsPossible(aOffset, aLength,
+                                                 aRoundToExistingOffset);
   } else {
     aUnionTextRect.SetEmpty();
   }
 
   if (!aOffset) {
     aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
   }
   if (aOffset <= mSelection.mAnchor && mSelection.mAnchor < endOffset.value()) {
@@ -796,42 +831,46 @@ ContentCacheInParent::GetUnionTextRects(
   if (aOffset <= mSelection.mFocus && mSelection.mFocus < endOffset.value()) {
     aUnionTextRect = aUnionTextRect.Union(mSelection.mFocusCharRect);
   }
   return !aUnionTextRect.IsEmpty();
 }
 
 bool
 ContentCacheInParent::GetCaretRect(uint32_t aOffset,
+                                   bool aRoundToExistingOffset,
                                    LayoutDeviceIntRect& aCaretRect) const
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCacheInParent: 0x%p GetCaretRect(aOffset=%u), "
+    ("ContentCacheInParent: 0x%p GetCaretRect(aOffset=%u, "
+     "aRoundToExistingOffset=%s), "
      "mCaret={ mOffset=%u, mRect=%s, IsValid()=%s }, mTextRectArray={ "
      "mStart=%u, mRects.Length()=%u }, mSelection={ mAnchor=%u, mFocus=%u, "
      "mWritingMode=%s, mAnchorCharRect=%s, mFocusCharRect=%s }, "
      "mFirstCharRect=%s",
-     this, aOffset, mCaret.mOffset, GetRectText(mCaret.mRect).get(),
+     this, aOffset, GetBoolName(aRoundToExistingOffset),
+     mCaret.mOffset, GetRectText(mCaret.mRect).get(),
      GetBoolName(mCaret.IsValid()), mTextRectArray.mStart,
      mTextRectArray.mRects.Length(), mSelection.mAnchor, mSelection.mFocus,
      GetWritingModeName(mSelection.mWritingMode).get(),
      GetRectText(mSelection.mAnchorCharRect).get(),
      GetRectText(mSelection.mFocusCharRect).get(),
      GetRectText(mFirstCharRect).get()));
 
   if (mCaret.IsValid() && mCaret.mOffset == aOffset) {
     aCaretRect = mCaret.mRect;
     return true;
   }
 
   // Guess caret rect from the text rect if it's stored.
-  if (!GetTextRect(aOffset, aCaretRect)) {
+  if (!GetTextRect(aOffset, aRoundToExistingOffset, aCaretRect)) {
     // There might be previous character rect in the cache.  If so, we can
     // guess the caret rect with it.
-    if (!aOffset || !GetTextRect(aOffset - 1, aCaretRect)) {
+    if (!aOffset ||
+        !GetTextRect(aOffset - 1, aRoundToExistingOffset, aCaretRect)) {
       aCaretRect.SetEmpty();
       return false;
     }
 
     if (mSelection.mWritingMode.IsVertical()) {
       aCaretRect.y = aCaretRect.YMost();
     } else {
       // XXX bidi-unaware.
@@ -1109,23 +1148,30 @@ ContentCache::TextRectArray::GetUnionRec
     rect = rect.Union(mRects[aOffset - mStart + i]);
   }
   return rect;
 }
 
 LayoutDeviceIntRect
 ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
                                uint32_t aOffset,
-                               uint32_t aLength) const
+                               uint32_t aLength,
+                               bool aRoundToExistingOffset) const
 {
   LayoutDeviceIntRect rect;
-  if (!IsOverlappingWith(aOffset, aLength)) {
+  if (!aRoundToExistingOffset && !IsOverlappingWith(aOffset, aLength)) {
     return rect;
   }
   uint32_t startOffset = std::max(aOffset, mStart);
+  if (aRoundToExistingOffset && startOffset >= EndOffset()) {
+    startOffset = EndOffset() - 1;
+  }
   uint32_t endOffset = std::min(aOffset + aLength, EndOffset());
+  if (aRoundToExistingOffset && endOffset < mStart + 1) {
+    endOffset = mStart + 1;
+  }
   for (uint32_t i = 0; i < endOffset - startOffset; i++) {
     rect = rect.Union(mRects[startOffset - mStart + i]);
   }
   return rect;
 }
 
 } // namespace mozilla
--- a/widget/ContentCache.h
+++ b/widget/ContentCache.h
@@ -103,16 +103,28 @@ protected:
       return Reversed() ? mAnchor : mFocus;
     }
     uint32_t Length() const
     {
       NS_ASSERTION(IsValid(),
                    "The caller should check if the selection is valid");
       return Reversed() ? mAnchor - mFocus : mFocus - mAnchor;
     }
+    LayoutDeviceIntRect StartCharRect() const
+    {
+      NS_ASSERTION(IsValid(),
+                   "The caller should check if the selection is valid");
+      return Reversed() ? mFocusCharRect : mAnchorCharRect;
+    }
+    LayoutDeviceIntRect EndCharRect() const
+    {
+      NS_ASSERTION(IsValid(),
+                   "The caller should check if the selection is valid");
+      return Reversed() ? mAnchorCharRect : mFocusCharRect;
+    }
   } mSelection;
 
   bool IsSelectionValid() const
   {
     return mSelection.IsValid() && mSelection.EndOffset() <= mText.Length();
   }
 
   // Stores first char rect because Yosemite's Japanese IME sometimes tries
@@ -208,18 +220,19 @@ protected:
         CheckedInt<uint32_t>(aOffset) + aLength;
       if (NS_WARN_IF(!endOffset.isValid())) {
         return false;
       }
       return aOffset <= EndOffset() && endOffset.value() >= mStart;
     }
     LayoutDeviceIntRect GetRect(uint32_t aOffset) const;
     LayoutDeviceIntRect GetUnionRect(uint32_t aOffset, uint32_t aLength) const;
-    LayoutDeviceIntRect GetUnionRectAsFarAsPossible(uint32_t aOffset,
-                                                    uint32_t aLength) const;
+    LayoutDeviceIntRect GetUnionRectAsFarAsPossible(
+                          uint32_t aOffset, uint32_t aLength,
+                          bool aRoundToExistingOffset) const;
   } mTextRectArray;
 
   LayoutDeviceIntRect mEditorRect;
 
   friend class ContentCacheInParent;
   friend struct IPC::ParamTraits<ContentCache>;
 };
 
@@ -366,21 +379,30 @@ private:
   nsAString* mCommitStringByRequest;
   // mPendingEventsNeedingAck is increased before sending a composition event or
   // a selection event and decreased after they are received in the child
   // process.
   uint32_t mPendingEventsNeedingAck;
 
   bool mIsComposing;
 
-  bool GetCaretRect(uint32_t aOffset, LayoutDeviceIntRect& aCaretRect) const;
+  /**
+   * When following methods' aRoundToExistingOffset is true, even if specified
+   * offset or range is out of bounds, the result is computed with the existing
+   * cache forcibly.
+   */
+  bool GetCaretRect(uint32_t aOffset,
+                    bool aRoundToExistingOffset,
+                    LayoutDeviceIntRect& aCaretRect) const;
   bool GetTextRect(uint32_t aOffset,
+                   bool aRoundToExistingOffset,
                    LayoutDeviceIntRect& aTextRect) const;
   bool GetUnionTextRects(uint32_t aOffset,
                          uint32_t aLength,
+                         bool aRoundToExistingOffset,
                          LayoutDeviceIntRect& aUnionTextRect) const;
 
   void FlushPendingNotifications(nsIWidget* aWidget);
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ContentCache_h