Bug 1257446 part.1 ContentCache should store previous character of selection r=m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 22 Jul 2016 20:47:51 +0900
changeset 399826 403e4c993b48a832d50b4f44738c5b5c6d5ce085
parent 399825 77d541f21b78b4270c109f2a4e9db59c19129e03
child 399827 0343e2eecf5e25043d260157cf4d8b0874e0ceb6
push id26006
push usermasayuki@d-toybox.com
push dateFri, 12 Aug 2016 06:28:16 +0000
reviewersm_kato
bugs1257446
milestone51.0a1
Bug 1257446 part.1 ContentCache should store previous character of selection r=m_kato This patch makes ContentCache store previous character's rect of selection anchor and selection focus because if caret is at end of a line, IME may query the last character of the line. MozReview-Commit-ID: 5X1K8KtrYfl
widget/ContentCache.cpp
widget/ContentCache.h
widget/nsGUIEventIPC.h
--- a/widget/ContentCache.cpp
+++ b/widget/ContentCache.cpp
@@ -293,29 +293,47 @@ ContentCacheInChild::QueryCharRect(nsIWi
   }
   if (NS_WARN_IF(!aCharRect.width)) {
     aCharRect.width = 1;
   }
   return true;
 }
 
 bool
+ContentCacheInChild::QueryCharRectArray(nsIWidget* aWidget,
+                                        uint32_t aOffset,
+                                        uint32_t aLength,
+                                        RectArray& aCharRectArray) const
+{
+  nsEventStatus status = nsEventStatus_eIgnore;
+  WidgetQueryContentEvent textRects(true, eQueryTextRectArray, aWidget);
+  textRects.InitForQueryTextRectArray(aOffset, aLength);
+  aWidget->DispatchEvent(&textRects, status);
+  if (NS_WARN_IF(!textRects.mSucceeded)) {
+    aCharRectArray.Clear();
+    return false;
+  }
+  aCharRectArray = Move(textRects.mReply.mRectArray);
+  return true;
+}
+
+bool
 ContentCacheInChild::CacheTextRects(nsIWidget* aWidget,
                                     const IMENotification* aNotification)
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
     ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), "
      "mCaret={ mOffset=%u, IsValid()=%s }",
      this, aWidget, GetNotificationName(aNotification), mCaret.mOffset,
      GetBoolName(mCaret.IsValid())));
 
   mCompositionStart = UINT32_MAX;
   mTextRectArray.Clear();
-  mSelection.mAnchorCharRect.SetEmpty();
-  mSelection.mFocusCharRect.SetEmpty();
+  mSelection.ClearAnchorCharRects();
+  mSelection.ClearFocusCharRects();
   mSelection.mRect.SetEmpty();
   mFirstCharRect.SetEmpty();
 
   if (NS_WARN_IF(!mSelection.IsValid())) {
     return false;
   }
 
   // Retrieve text rects in composition string if there is.
@@ -325,51 +343,86 @@ ContentCacheInChild::CacheTextRects(nsIW
     // mCompositionStart may be updated by some composition event handlers.
     // So, let's update it with the latest information.
     mCompositionStart = textComposition->NativeOffsetOfStartComposition();
     // Note that TextComposition::String() may not be modified here because
     // it's modified after all edit action listeners are performed but this
     // is called while some of them are performed.
     uint32_t length = textComposition->LastData().Length();
     mTextRectArray.mStart = mCompositionStart;
-
-    nsEventStatus status = nsEventStatus_eIgnore;
-    WidgetQueryContentEvent textRects(true, eQueryTextRectArray, aWidget);
-    textRects.InitForQueryTextRectArray(mTextRectArray.mStart, length);
-    aWidget->DispatchEvent(&textRects, status);
-
-    mTextRectArray.mRects = Move(textRects.mReply.mRectArray);
+    if (NS_WARN_IF(!QueryCharRectArray(aWidget, mTextRectArray.mStart, length,
+                                       mTextRectArray.mRects))) {
+      MOZ_LOG(sContentCacheLog, LogLevel::Error,
+        ("0x%p CacheTextRects(), FAILED, "
+         "couldn't retrieve text rect array of the composition string", this));
+    }
   }
 
-  if (mTextRectArray.InRange(mSelection.mAnchor)) {
-    mSelection.mAnchorCharRect = mTextRectArray.GetRect(mSelection.mAnchor);
+  if (mTextRectArray.InRange(mSelection.mAnchor) &&
+      (!mSelection.mAnchor || mTextRectArray.InRange(mSelection.mAnchor - 1))) {
+    mSelection.mAnchorCharRects[eNextCharRect] =
+      mTextRectArray.GetRect(mSelection.mAnchor);
+    if (mSelection.mAnchor) {
+      mSelection.mAnchorCharRects[ePrevCharRect] =
+        mTextRectArray.GetRect(mSelection.mAnchor - 1);
+    }
   } else {
-    LayoutDeviceIntRect charRect;
-    if (NS_WARN_IF(!QueryCharRect(aWidget, mSelection.mAnchor, charRect))) {
+    RectArray rects;
+    uint32_t startOffset = mSelection.mAnchor ? mSelection.mAnchor - 1 : 0;
+    uint32_t length = mSelection.mAnchor ? 2 : 1;
+    if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
       MOZ_LOG(sContentCacheLog, LogLevel::Error,
         ("0x%p CacheTextRects(), FAILED, "
-         "couldn't retrieve text rect at anchor of selection (%u)",
+         "couldn't retrieve text rect array around the selection anchor (%u)",
          this, mSelection.mAnchor));
+      MOZ_ASSERT(mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty());
+      MOZ_ASSERT(mSelection.mAnchorCharRects[eNextCharRect].IsEmpty());
+    } else {
+      if (rects.Length() > 1) {
+        mSelection.mAnchorCharRects[ePrevCharRect] = rects[0];
+        mSelection.mAnchorCharRects[eNextCharRect] = rects[1];
+      } else if (rects.Length()) {
+        mSelection.mAnchorCharRects[eNextCharRect] = rects[0];
+        MOZ_ASSERT(mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty());
+      }
     }
-    mSelection.mAnchorCharRect = charRect;
   }
 
   if (mSelection.Collapsed()) {
-    mSelection.mFocusCharRect = mSelection.mAnchorCharRect;
-  } else if (mTextRectArray.InRange(mSelection.mFocus)) {
-    mSelection.mFocusCharRect = mTextRectArray.GetRect(mSelection.mFocus);
+    mSelection.mFocusCharRects[0] = mSelection.mAnchorCharRects[0];
+    mSelection.mFocusCharRects[1] = mSelection.mAnchorCharRects[1];
+  } else if (mTextRectArray.InRange(mSelection.mFocus) &&
+             (!mSelection.mFocus ||
+              mTextRectArray.InRange(mSelection.mFocus - 1))) {
+    mSelection.mFocusCharRects[eNextCharRect] =
+      mTextRectArray.GetRect(mSelection.mFocus);
+    if (mSelection.mFocus) {
+      mSelection.mFocusCharRects[ePrevCharRect] =
+        mTextRectArray.GetRect(mSelection.mFocus - 1);
+    }
   } else {
-    LayoutDeviceIntRect charRect;
-    if (NS_WARN_IF(!QueryCharRect(aWidget, mSelection.mFocus, charRect))) {
+    RectArray rects;
+    uint32_t startOffset = mSelection.mFocus ? mSelection.mFocus - 1 : 0;
+    uint32_t length = mSelection.mFocus ? 2 : 1;
+    if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
       MOZ_LOG(sContentCacheLog, LogLevel::Error,
         ("0x%p CacheTextRects(), FAILED, "
-         "couldn't retrieve text rect at focus of selection (%u)",
+         "couldn't retrieve text rect array around the selection focus (%u)",
          this, mSelection.mFocus));
+      MOZ_ASSERT(mSelection.mFocusCharRects[ePrevCharRect].IsEmpty());
+      MOZ_ASSERT(mSelection.mFocusCharRects[eNextCharRect].IsEmpty());
+    } else {
+      if (rects.Length() > 1) {
+        mSelection.mFocusCharRects[ePrevCharRect] = rects[0];
+        mSelection.mFocusCharRects[eNextCharRect] = rects[1];
+      } else if (rects.Length()) {
+        mSelection.mFocusCharRects[eNextCharRect] = rects[0];
+        MOZ_ASSERT(mSelection.mFocusCharRects[ePrevCharRect].IsEmpty());
+      }
     }
-    mSelection.mFocusCharRect = charRect;
   }
 
   if (!mSelection.Collapsed()) {
     nsEventStatus status = nsEventStatus_eIgnore;
     WidgetQueryContentEvent textRect(true, eQueryTextRect, aWidget);
     textRect.InitForQueryTextRect(mSelection.StartOffset(),
                                   mSelection.Length());
     aWidget->DispatchEvent(&textRect, status);
@@ -378,41 +431,50 @@ ContentCacheInChild::CacheTextRects(nsIW
         ("0x%p CacheTextRects(), FAILED, "
          "couldn't retrieve text rect of whole selected text", this));
     } else {
       mSelection.mRect = textRect.mReply.mRect;
     }
   }
 
   if (!mSelection.mFocus) {
-    mFirstCharRect = mSelection.mFocusCharRect;
+    mFirstCharRect = mSelection.mFocusCharRects[eNextCharRect];
+  } else if (mSelection.mFocus == 1) {
+    mFirstCharRect = mSelection.mFocusCharRects[ePrevCharRect];
   } else if (!mSelection.mAnchor) {
-    mFirstCharRect = mSelection.mAnchorCharRect;
+    mFirstCharRect = mSelection.mAnchorCharRects[eNextCharRect];
+  } else if (mSelection.mAnchor == 1) {
+    mFirstCharRect = mSelection.mFocusCharRects[ePrevCharRect];
   } else if (mTextRectArray.InRange(0)) {
     mFirstCharRect = mTextRectArray.GetRect(0);
   } else {
     LayoutDeviceIntRect charRect;
     if (NS_WARN_IF(!QueryCharRect(aWidget, 0, charRect))) {
       MOZ_LOG(sContentCacheLog, LogLevel::Error,
         ("0x%p CacheTextRects(), FAILED, "
          "couldn't retrieve first char rect", this));
     } else {
       mFirstCharRect = charRect;
     }
   }
 
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
     ("0x%p CacheTextRects(), Succeeded, "
      "mText.Length()=%u, mTextRectArray={ mStart=%u, mRects.Length()=%u }, "
-     "mSelection={ mAnchor=%u, mAnchorCharRect=%s, mFocus=%u, "
-     "mFocusCharRect=%s, mRect=%s }, mFirstCharRect=%s",
+     "mSelection={ mAnchor=%u, mAnchorCharRects[eNextCharRect]=%s, "
+     "mAnchorCharRects[ePrevCharRect]=%s, mFocus=%u, "
+     "mFocusCharRects[eNextCharRect]=%s, mFocusCharRects[ePrevCharRect]=%s, "
+     "mRect=%s }, mFirstCharRect=%s",
      this, mText.Length(), mTextRectArray.mStart,
      mTextRectArray.mRects.Length(), mSelection.mAnchor,
-     GetRectText(mSelection.mAnchorCharRect).get(), mSelection.mFocus,
-     GetRectText(mSelection.mFocusCharRect).get(),
+     GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
+     GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
+     mSelection.mFocus,
+     GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
+     GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
      GetRectText(mSelection.mRect).get(), GetRectText(mFirstCharRect).get()));
   return true;
 }
 
 void
 ContentCacheInChild::SetSelection(nsIWidget* aWidget,
                                   uint32_t aStartOffset,
                                   uint32_t aLength,
@@ -470,25 +532,29 @@ ContentCacheInParent::AssignContent(cons
     IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget, mCompositionStart);
   } else {
     NS_WARN_IF(mCompositionStart != UINT32_MAX);
   }
 
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
     ("0x%p AssignContent(aNotification=%s), "
      "Succeeded, mText.Length()=%u, mSelection={ mAnchor=%u, mFocus=%u, "
-     "mWritingMode=%s, mAnchorCharRect=%s, mFocusCharRect=%s, mRect=%s }, "
+     "mWritingMode=%s, mAnchorCharRects[eNextCharRect]=%s, "
+     "mAnchorCharRects[ePrevCharRect]=%s, mFocusCharRects[eNextCharRect]=%s, "
+     "mFocusCharRects[ePrevCharRect]=%s, mRect=%s }, "
      "mFirstCharRect=%s, mCaret={ mOffset=%u, mRect=%s }, mTextRectArray={ "
      "mStart=%u, mRects.Length()=%u }, mIsComposing=%s, mCompositionStart=%u, "
      "mEditorRect=%s",
      this, GetNotificationName(aNotification),
      mText.Length(), mSelection.mAnchor, mSelection.mFocus,
      GetWritingModeName(mSelection.mWritingMode).get(),
-     GetRectText(mSelection.mAnchorCharRect).get(),
-     GetRectText(mSelection.mFocusCharRect).get(),
+     GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
+     GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
+     GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
+     GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
      GetRectText(mSelection.mRect).get(), GetRectText(mFirstCharRect).get(),
      mCaret.mOffset, GetRectText(mCaret.mRect).get(), mTextRectArray.mStart,
      mTextRectArray.mRects.Length(), GetBoolName(mIsComposing),
      mCompositionStart, GetRectText(mEditorRect).get()));
 }
 
 bool
 ContentCacheInParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
@@ -770,23 +836,33 @@ ContentCacheInParent::GetTextRect(uint32
      mSelection.mAnchor, mSelection.mFocus));
 
   if (!aOffset) {
     NS_WARN_IF(mFirstCharRect.IsEmpty());
     aTextRect = mFirstCharRect;
     return !aTextRect.IsEmpty();
   }
   if (aOffset == mSelection.mAnchor) {
-    NS_WARN_IF(mSelection.mAnchorCharRect.IsEmpty());
-    aTextRect = mSelection.mAnchorCharRect;
+    NS_WARN_IF(mSelection.mAnchorCharRects[eNextCharRect].IsEmpty());
+    aTextRect = mSelection.mAnchorCharRects[eNextCharRect];
+    return !aTextRect.IsEmpty();
+  }
+  if (mSelection.mAnchor && aOffset == mSelection.mAnchor - 1) {
+    NS_WARN_IF(mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty());
+    aTextRect = mSelection.mAnchorCharRects[ePrevCharRect];
     return !aTextRect.IsEmpty();
   }
   if (aOffset == mSelection.mFocus) {
-    NS_WARN_IF(mSelection.mFocusCharRect.IsEmpty());
-    aTextRect = mSelection.mFocusCharRect;
+    NS_WARN_IF(mSelection.mFocusCharRects[eNextCharRect].IsEmpty());
+    aTextRect = mSelection.mFocusCharRects[eNextCharRect];
+    return !aTextRect.IsEmpty();
+  }
+  if (mSelection.mFocus && aOffset == mSelection.mFocus - 1) {
+    NS_WARN_IF(mSelection.mFocusCharRects[ePrevCharRect].IsEmpty());
+    aTextRect = mSelection.mFocusCharRects[ePrevCharRect];
     return !aTextRect.IsEmpty();
   }
 
   uint32_t offset = aOffset;
   if (!mTextRectArray.InRange(aOffset)) {
     if (!aRoundToExistingOffset) {
       aTextRect.SetEmpty();
       return false;
@@ -839,23 +915,33 @@ ContentCacheInParent::GetUnionTextRects(
 
   if (aLength == 1) {
     if (!aOffset) {
       NS_WARN_IF(mFirstCharRect.IsEmpty());
       aUnionTextRect = mFirstCharRect;
       return !aUnionTextRect.IsEmpty();
     }
     if (aOffset == mSelection.mAnchor) {
-      NS_WARN_IF(mSelection.mAnchorCharRect.IsEmpty());
-      aUnionTextRect = mSelection.mAnchorCharRect;
+      NS_WARN_IF(mSelection.mAnchorCharRects[eNextCharRect].IsEmpty());
+      aUnionTextRect = mSelection.mAnchorCharRects[eNextCharRect];
+      return !aUnionTextRect.IsEmpty();
+    }
+    if (mSelection.mAnchor && aOffset == mSelection.mAnchor - 1) {
+      NS_WARN_IF(mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty());
+      aUnionTextRect = mSelection.mAnchorCharRects[ePrevCharRect];
       return !aUnionTextRect.IsEmpty();
     }
     if (aOffset == mSelection.mFocus) {
-      NS_WARN_IF(mSelection.mFocusCharRect.IsEmpty());
-      aUnionTextRect = mSelection.mFocusCharRect;
+      NS_WARN_IF(mSelection.mFocusCharRects[eNextCharRect].IsEmpty());
+      aUnionTextRect = mSelection.mFocusCharRects[eNextCharRect];
+      return !aUnionTextRect.IsEmpty();
+    }
+    if (mSelection.mFocus && aOffset == mSelection.mFocus - 1) {
+      NS_WARN_IF(mSelection.mFocusCharRects[ePrevCharRect].IsEmpty());
+      aUnionTextRect = mSelection.mFocusCharRects[ePrevCharRect];
       return !aUnionTextRect.IsEmpty();
     }
   }
 
   // Even if some text rects are not cached of the queried range,
   // we should return union rect when the first character's rect is cached
   // since the first character rect is important and the others are not so
   // in most cases.
@@ -874,43 +960,59 @@ ContentCacheInParent::GetUnionTextRects(
   } else {
     aUnionTextRect.SetEmpty();
   }
 
   if (!aOffset) {
     aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
   }
   if (aOffset <= mSelection.mAnchor && mSelection.mAnchor < endOffset.value()) {
-    aUnionTextRect = aUnionTextRect.Union(mSelection.mAnchorCharRect);
+    aUnionTextRect =
+      aUnionTextRect.Union(mSelection.mAnchorCharRects[eNextCharRect]);
+  }
+  if (mSelection.mAnchor && aOffset <= mSelection.mAnchor - 1 &&
+      mSelection.mAnchor - 1 < endOffset.value()) {
+    aUnionTextRect =
+      aUnionTextRect.Union(mSelection.mAnchorCharRects[ePrevCharRect]);
   }
   if (aOffset <= mSelection.mFocus && mSelection.mFocus < endOffset.value()) {
-    aUnionTextRect = aUnionTextRect.Union(mSelection.mFocusCharRect);
+    aUnionTextRect =
+      aUnionTextRect.Union(mSelection.mFocusCharRects[eNextCharRect]);
   }
+  if (mSelection.mFocus && aOffset <= mSelection.mFocus - 1 &&
+      mSelection.mFocus - 1 < endOffset.value()) {
+    aUnionTextRect =
+      aUnionTextRect.Union(mSelection.mFocusCharRects[ePrevCharRect]);
+  }
+
   return !aUnionTextRect.IsEmpty();
 }
 
 bool
 ContentCacheInParent::GetCaretRect(uint32_t aOffset,
                                    bool aRoundToExistingOffset,
                                    LayoutDeviceIntRect& aCaretRect) const
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
     ("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",
+     "mWritingMode=%s, mAnchorCharRects[eNextCharRect]=%s, "
+     "mAnchorCharRects[ePrevCharRect]=%s, mFocusCharRects[eNextCharRect]=%s, "
+     "mFocusCharRects[ePrevCharRect]=%s }, mFirstCharRect=%s",
      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(mSelection.mAnchorCharRects[eNextCharRect]).get(),
+     GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
+     GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
+     GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).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.
--- a/widget/ContentCache.h
+++ b/widget/ContentCache.h
@@ -39,46 +39,69 @@ public:
 
 protected:
   // Whole text in the target
   nsString mText;
 
   // Start offset of the composition string.
   uint32_t mCompositionStart;
 
+  enum
+  {
+    ePrevCharRect = 1,
+    eNextCharRect = 0
+  };
+
   struct Selection final
   {
     // Following values are offset in "flat text".
     uint32_t mAnchor;
     uint32_t mFocus;
 
     WritingMode mWritingMode;
 
-    // Character rects at next character of mAnchor and mFocus.
-    LayoutDeviceIntRect mAnchorCharRect;
-    LayoutDeviceIntRect mFocusCharRect;
+    // Character rects at previous and next character of mAnchor and mFocus.
+    // The reason why ContentCache needs to store each previous character of
+    // them is IME may query character rect of the last character of a line
+    // when caret is at the end of the line.
+    // Note that use ePrevCharRect and eNextCharRect for accessing each item.
+    LayoutDeviceIntRect mAnchorCharRects[2];
+    LayoutDeviceIntRect mFocusCharRects[2];
 
     // Whole rect of selected text. This is empty if the selection is collapsed.
     LayoutDeviceIntRect mRect;
 
     Selection()
       : mAnchor(UINT32_MAX)
       , mFocus(UINT32_MAX)
     {
     }
 
     void Clear()
     {
       mAnchor = mFocus = UINT32_MAX;
       mWritingMode = WritingMode();
-      mAnchorCharRect.SetEmpty();
-      mFocusCharRect.SetEmpty();
+      ClearAnchorCharRects();
+      ClearFocusCharRects();
       mRect.SetEmpty();
     }
 
+    void ClearAnchorCharRects()
+    {
+      for (size_t i = 0; i < ArrayLength(mAnchorCharRects); i++) {
+        mAnchorCharRects[i].SetEmpty();
+      }
+    }
+    void ClearFocusCharRects()
+    {
+      for (size_t i = 0; i < ArrayLength(mFocusCharRects); i++) {
+        mFocusCharRects[i].SetEmpty();
+      }
+    }
+
     bool IsValid() const
     {
       return mAnchor != UINT32_MAX && mFocus != UINT32_MAX;
     }
     bool Collapsed() const
     {
       NS_ASSERTION(IsValid(),
                    "The caller should check if the selection is valid");
@@ -107,23 +130,25 @@ protected:
       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;
+      return Reversed() ? mFocusCharRects[eNextCharRect] :
+                          mAnchorCharRects[eNextCharRect];
     }
     LayoutDeviceIntRect EndCharRect() const
     {
       NS_ASSERTION(IsValid(),
                    "The caller should check if the selection is valid");
-      return Reversed() ? mAnchorCharRect : mFocusCharRect;
+      return Reversed() ? mAnchorCharRects[eNextCharRect] :
+                          mFocusCharRects[eNextCharRect];
     }
   } mSelection;
 
   bool IsSelectionValid() const
   {
     return mSelection.IsValid() && mSelection.EndOffset() <= mText.Length();
   }
 
@@ -275,16 +300,20 @@ public:
                     uint32_t aLength,
                     bool aReversed,
                     const WritingMode& aWritingMode);
 
 private:
   bool QueryCharRect(nsIWidget* aWidget,
                      uint32_t aOffset,
                      LayoutDeviceIntRect& aCharRect) const;
+  bool QueryCharRectArray(nsIWidget* aWidget,
+                          uint32_t aOffset,
+                          uint32_t aLength,
+                          RectArray& aCharRectArray) const;
   bool CacheCaret(nsIWidget* aWidget,
                   const IMENotification* aNotification = nullptr);
   bool CacheTextRects(nsIWidget* aWidget,
                       const IMENotification* aNotification = nullptr);
 };
 
 class ContentCacheInParent final : public ContentCache
 {
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -976,36 +976,40 @@ struct ParamTraits<mozilla::ContentCache
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.mCompositionStart);
     WriteParam(aMsg, aParam.mText);
     WriteParam(aMsg, aParam.mSelection.mAnchor);
     WriteParam(aMsg, aParam.mSelection.mFocus);
     WriteParam(aMsg, aParam.mSelection.mWritingMode);
-    WriteParam(aMsg, aParam.mSelection.mAnchorCharRect);
-    WriteParam(aMsg, aParam.mSelection.mFocusCharRect);
+    WriteParam(aMsg, aParam.mSelection.mAnchorCharRects[0]);
+    WriteParam(aMsg, aParam.mSelection.mAnchorCharRects[1]);
+    WriteParam(aMsg, aParam.mSelection.mFocusCharRects[0]);
+    WriteParam(aMsg, aParam.mSelection.mFocusCharRects[1]);
     WriteParam(aMsg, aParam.mSelection.mRect);
     WriteParam(aMsg, aParam.mFirstCharRect);
     WriteParam(aMsg, aParam.mCaret.mOffset);
     WriteParam(aMsg, aParam.mCaret.mRect);
     WriteParam(aMsg, aParam.mTextRectArray.mStart);
     WriteParam(aMsg, aParam.mTextRectArray.mRects);
     WriteParam(aMsg, aParam.mEditorRect);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     return ReadParam(aMsg, aIter, &aResult->mCompositionStart) &&
            ReadParam(aMsg, aIter, &aResult->mText) &&
            ReadParam(aMsg, aIter, &aResult->mSelection.mAnchor) &&
            ReadParam(aMsg, aIter, &aResult->mSelection.mFocus) &&
            ReadParam(aMsg, aIter, &aResult->mSelection.mWritingMode) &&
-           ReadParam(aMsg, aIter, &aResult->mSelection.mAnchorCharRect) &&
-           ReadParam(aMsg, aIter, &aResult->mSelection.mFocusCharRect) &&
+           ReadParam(aMsg, aIter, &aResult->mSelection.mAnchorCharRects[0]) &&
+           ReadParam(aMsg, aIter, &aResult->mSelection.mAnchorCharRects[1]) &&
+           ReadParam(aMsg, aIter, &aResult->mSelection.mFocusCharRects[0]) &&
+           ReadParam(aMsg, aIter, &aResult->mSelection.mFocusCharRects[1]) &&
            ReadParam(aMsg, aIter, &aResult->mSelection.mRect) &&
            ReadParam(aMsg, aIter, &aResult->mFirstCharRect) &&
            ReadParam(aMsg, aIter, &aResult->mCaret.mOffset) &&
            ReadParam(aMsg, aIter, &aResult->mCaret.mRect) &&
            ReadParam(aMsg, aIter, &aResult->mTextRectArray.mStart) &&
            ReadParam(aMsg, aIter, &aResult->mTextRectArray.mRects) &&
            ReadParam(aMsg, aIter, &aResult->mEditorRect);
   }