Bug 1257446 part.1 ContentCache should store previous character of selection r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 22 Jul 2016 20:47:51 +0900
changeset 309081 eaad665922a58e9d13ab1887f298a91581457dd7
parent 309080 b66e92eb8ed38f722a7d473bfec21e16aad3e8e1
child 309082 df84d061b1012bb5c8b1e636463c321d9a303745
push id30556
push userkwierso@gmail.com
push dateFri, 12 Aug 2016 16:45:27 +0000
treeherdermozilla-central@c4ad5f94a5bc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1257446
milestone51.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 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);
   }