Bug 1166436 part.14 Store text rects after focus/anchor of selection r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 05 Jun 2015 18:28:20 +0900
changeset 278137 27bf218dee2bded4559f8160bb5670f41a2d4920
parent 278136 d5f5a8d204d1a69415f6bdaca43342966b475f4d
child 278138 cc9201883c30f2b20463f9ec6b77a2e8deea2412
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1166436
milestone41.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 1166436 part.14 Store text rects after focus/anchor of selection r=m_kato
dom/ipc/TabParent.cpp
widget/ContentCache.cpp
widget/ContentCache.h
widget/PuppetWidget.cpp
widget/nsGUIEventIPC.h
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -2202,20 +2202,20 @@ TabParent::SendCompositionEvent(WidgetCo
 }
 
 bool
 TabParent::SendSelectionEvent(WidgetSelectionEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
-  // XXX The writing mode is wrong, but this should cause a call of
-  //     RecvNotifyIMESelection().  If so, why do we need to modify the range
-  //     here??
-  mContentCache.SetSelection(event.mOffset, event.mLength, event.mReversed);
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (!widget) {
+    return true;
+  }
   return PBrowserParent::SendSelectionEvent(event);
 }
 
 /*static*/ TabParent*
 TabParent::GetFrom(nsFrameLoader* aFrameLoader)
 {
   if (!aFrameLoader) {
     return nullptr;
--- a/widget/ContentCache.cpp
+++ b/widget/ContentCache.cpp
@@ -136,21 +136,24 @@ ContentCache::AssignContent(const Conten
   mSelection = aOther.mSelection;
   mCaret = aOther.mCaret;
   mTextRectArray = aOther.mTextRectArray;
   mEditorRect = aOther.mEditorRect;
 
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
     ("ContentCache: 0x%p (mIsChrome=%s) AssignContent(aNotification=%s), "
      "Succeeded, mText.Length()=%u, mSelection={ mAnchor=%u, mFocus=%u, "
-     " mWritingMode=%s }, mCaret={ mOffset=%u, mRect=%s }, "
-     "mTextRectArray={ mStart=%u, mRects.Length()=%u }, mEditorRect=%s",
+     "mWritingMode=%s, mAnchorCharRect=%s, mFocusCharRect=%s }, "
+     "mCaret={ mOffset=%u, mRect=%s }, mTextRectArray={ mStart=%u, "
+     "mRects.Length()=%u }, mEditorRect=%s",
      this, GetBoolName(mIsChrome), GetNotificationName(aNotification),
      mText.Length(), mSelection.mAnchor, mSelection.mFocus,
-     GetWritingModeName(mSelection.mWritingMode).get(), mCaret.mOffset,
+     GetWritingModeName(mSelection.mWritingMode).get(),
+     GetRectText(mSelection.mAnchorCharRect).get(),
+     GetRectText(mSelection.mFocusCharRect).get(), mCaret.mOffset,
      GetRectText(mCaret.mRect).get(), mTextRectArray.mStart,
      mTextRectArray.mRects.Length(), GetRectText(mEditorRect).get()));
 }
 
 void
 ContentCache::Clear()
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
@@ -322,18 +325,16 @@ ContentCache::CacheAll(nsIWidget* aWidge
      GetNotificationName(aNotification)));
 
   // CacheAll() must be called in content process.
   if (NS_WARN_IF(mIsChrome)) {
     return false;
   }
 
   if (NS_WARN_IF(!CacheText(aWidget, aNotification)) ||
-      NS_WARN_IF(!CacheSelection(aWidget, aNotification)) ||
-      NS_WARN_IF(!CacheTextRects(aWidget, aNotification)) ||
       NS_WARN_IF(!CacheEditorRect(aWidget, aNotification))) {
     return false;
   }
   return true;
 }
 
 bool
 ContentCache::CacheSelection(nsIWidget* aWidget,
@@ -369,34 +370,55 @@ ContentCache::CacheSelection(nsIWidget* 
     mSelection.mFocus = selection.mReply.mOffset;
   } else {
     mSelection.mAnchor = selection.mReply.mOffset;
     mSelection.mFocus =
       selection.mReply.mOffset + selection.mReply.mString.Length();
   }
   mSelection.mWritingMode = selection.GetWritingMode();
 
+  return CacheCaret(aWidget, aNotification) &&
+         CacheTextRects(aWidget, aNotification);
+}
+
+bool
+ContentCache::CacheCaret(nsIWidget* aWidget,
+                         const IMENotification* aNotification)
+{
+  MOZ_LOG(sContentCacheLog, LogLevel::Info,
+    ("ContentCache: 0x%p (mIsChrome=%s) CacheCaret(aWidget=0x%p, "
+     "aNotification=%s)",
+     this, GetBoolName(mIsChrome), aWidget,
+     GetNotificationName(aNotification)));
+
+  // CacheCaret() must be called in content process.
+  if (NS_WARN_IF(mIsChrome)) {
+    return false;
+  }
+
+  mCaret.Clear();
+
   // XXX Should be mSelection.mFocus?
-  mCaret.mOffset = selection.mReply.mOffset;
+  mCaret.mOffset = mSelection.StartOffset();
 
-  status = nsEventStatus_eIgnore;
+  nsEventStatus status = nsEventStatus_eIgnore;
   WidgetQueryContentEvent caretRect(true, NS_QUERY_CARET_RECT, aWidget);
   caretRect.InitForQueryCaretRect(mCaret.mOffset);
   aWidget->DispatchEvent(&caretRect, status);
   if (NS_WARN_IF(!caretRect.mSucceeded)) {
     MOZ_LOG(sContentCacheLog, LogLevel::Error,
-      ("ContentCache: 0x%p (mIsChrome=%s) CacheSelection(), FAILED, "
+      ("ContentCache: 0x%p (mIsChrome=%s) CacheCaret(), FAILED, "
        "couldn't retrieve the caret rect at offset=%u",
        this, GetBoolName(mIsChrome), mCaret.mOffset));
     mCaret.Clear();
     return false;
   }
   mCaret.mRect = caretRect.mReply.mRect;
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) CacheSelection(), Succeeded, "
+    ("ContentCache: 0x%p (mIsChrome=%s) CacheCaret(), Succeeded, "
      "mSelection={ mAnchor=%u, mFocus=%u, mWritingMode=%s }, "
      "mCaret={ mOffset=%u, mRect=%s }",
      this, GetBoolName(mIsChrome), mSelection.mAnchor, mSelection.mFocus,
      GetWritingModeName(mSelection.mWritingMode).get(), mCaret.mOffset,
      GetRectText(mCaret.mRect).get()));
   return true;
 }
 
@@ -460,161 +482,241 @@ ContentCache::CacheText(nsIWidget* aWidg
     mText.Truncate();
     return false;
   }
   mText = queryText.mReply.mString;
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
     ("ContentCache: 0x%p (mIsChrome=%s) CacheText(), Succeeded, "
      "mText.Length()=%u",
      this, GetBoolName(mIsChrome), mText.Length()));
+
+  return CacheSelection(aWidget, aNotification);
+}
+
+bool
+ContentCache::QueryCharRect(nsIWidget* aWidget,
+                            uint32_t aOffset,
+                            LayoutDeviceIntRect& aCharRect) const
+{
+  aCharRect.SetEmpty();
+
+  nsEventStatus status = nsEventStatus_eIgnore;
+  WidgetQueryContentEvent textRect(true, NS_QUERY_TEXT_RECT, aWidget);
+  textRect.InitForQueryTextRect(aOffset, 1);
+  aWidget->DispatchEvent(&textRect, status);
+  if (NS_WARN_IF(!textRect.mSucceeded)) {
+    return false;
+  }
+  aCharRect = textRect.mReply.mRect;
+
+  // Guarantee the rect is not empty.
+  if (NS_WARN_IF(!aCharRect.height)) {
+    aCharRect.height = 1;
+  }
+  if (NS_WARN_IF(!aCharRect.width)) {
+    aCharRect.width = 1;
+  }
   return true;
 }
 
 bool
 ContentCache::CacheTextRects(nsIWidget* aWidget,
                              const IMENotification* aNotification)
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
     ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(aWidget=0x%p, "
-     "aNotification=%s)",
+     "aNotification=%s), mCaret={ mOffset=%u, IsValid()=%s }",
      this, GetBoolName(mIsChrome), aWidget,
-     GetNotificationName(aNotification)));
+     GetNotificationName(aNotification), mCaret.mOffset,
+     GetBoolName(mCaret.IsValid())));
 
   // CacheTextRects() must be called in content process.
   if (NS_WARN_IF(mIsChrome)) {
     return false;
   }
 
   mTextRectArray.Clear();
+  mSelection.mAnchorCharRect.SetEmpty();
+  mSelection.mFocusCharRect.SetEmpty();
 
+  // Retrieve text rects in composition string if there is.
   nsRefPtr<TextComposition> textComposition =
     IMEStateManager::GetTextCompositionFor(aWidget);
-  if (NS_WARN_IF(!textComposition)) {
-    MOZ_LOG(sContentCacheLog, LogLevel::Info,
-      ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), not caching "
-       "text rects because there is no composition string",
-       this, GetBoolName(mIsChrome), aWidget));
-    return true;
+  if (textComposition) {
+    uint32_t length = textComposition->String().Length();
+    mTextRectArray.mRects.SetCapacity(length);
+    mTextRectArray.mStart = textComposition->NativeOffsetOfStartComposition();
+    uint32_t endOffset = mTextRectArray.mStart + length;
+    for (uint32_t i = mTextRectArray.mStart; i < endOffset; i++) {
+      LayoutDeviceIntRect charRect;
+      if (NS_WARN_IF(!QueryCharRect(aWidget, i, charRect))) {
+        MOZ_LOG(sContentCacheLog, LogLevel::Error,
+          ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), FAILED, "
+           "couldn't retrieve text rect at offset=%u",
+           this, GetBoolName(mIsChrome), i));
+        mTextRectArray.Clear();
+        return false;
+      }
+      mTextRectArray.mRects.AppendElement(charRect);
+    }
   }
 
-  nsEventStatus status = nsEventStatus_eIgnore;
-  mTextRectArray.mRects.SetCapacity(textComposition->String().Length());
-  mTextRectArray.mStart = textComposition->NativeOffsetOfStartComposition();
-  uint32_t endOffset =
-    mTextRectArray.mStart + textComposition->String().Length();
-  for (uint32_t i = mTextRectArray.mStart; i < endOffset; i++) {
-    WidgetQueryContentEvent textRect(true, NS_QUERY_TEXT_RECT, aWidget);
-    textRect.InitForQueryTextRect(i, 1);
-    aWidget->DispatchEvent(&textRect, status);
-    if (NS_WARN_IF(!textRect.mSucceeded)) {
+  if (mTextRectArray.InRange(mSelection.mAnchor)) {
+    mSelection.mAnchorCharRect = mTextRectArray.GetRect(mSelection.mAnchor);
+  } else {
+    LayoutDeviceIntRect charRect;
+    if (NS_WARN_IF(!QueryCharRect(aWidget, mSelection.mAnchor, charRect))) {
       MOZ_LOG(sContentCacheLog, LogLevel::Error,
         ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), FAILED, "
-         "couldn't retrieve text rect at offset=%u",
-         this, GetBoolName(mIsChrome), i));
-      mTextRectArray.Clear();
-      return false;
+         "couldn't retrieve text rect at anchor of selection (%u)",
+         this, GetBoolName(mIsChrome), mSelection.mAnchor));
     }
-    mTextRectArray.mRects.AppendElement(textRect.mReply.mRect);
+    mSelection.mAnchorCharRect = charRect;
   }
+
+  if (mSelection.Collapsed()) {
+    mSelection.mFocusCharRect = mSelection.mAnchorCharRect;
+  } else if (mTextRectArray.InRange(mSelection.mFocus)) {
+    mSelection.mFocusCharRect = mTextRectArray.GetRect(mSelection.mFocus);
+  } else {
+    LayoutDeviceIntRect charRect;
+    if (NS_WARN_IF(!QueryCharRect(aWidget, mSelection.mFocus, charRect))) {
+      MOZ_LOG(sContentCacheLog, LogLevel::Error,
+        ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), FAILED, "
+         "couldn't retrieve text rect at focus of selection (%u)",
+         this, GetBoolName(mIsChrome), mSelection.mFocus));
+    }
+    mSelection.mFocusCharRect = charRect;
+  }
+
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
     ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), Succeeded, "
-     "mTextRectArray={ mStart=%u, mRects.Length()=%u }, mText.Length()=%u",
-     this, GetBoolName(mIsChrome), mTextRectArray.mStart,
-     mTextRectArray.mRects.Length(), mText.Length()));
+     "mText.Length()=%u, mTextRectArray={ mStart=%u, mRects.Length()=%u }, "
+     "mSelection={ mAnchor=%u, mAnchorCharRect=%s, mFocus=%u, "
+     "mFocusCharRect=%s }",
+     this, GetBoolName(mIsChrome), mText.Length(), mTextRectArray.mStart,
+     mTextRectArray.mRects.Length(), mSelection.mAnchor,
+     GetRectText(mSelection.mAnchorCharRect).get(), mSelection.mFocus,
+     GetRectText(mSelection.mFocusCharRect).get()));
   return true;
 }
 
 void
-ContentCache::SetSelection(uint32_t aStartOffset,
+ContentCache::SetSelection(nsIWidget* aWidget,
+                           uint32_t aStartOffset,
                            uint32_t aLength,
                            bool aReversed,
                            const WritingMode& aWritingMode)
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
     ("ContentCache: 0x%p (mIsChrome=%s) SetSelection(aStartOffset=%u, "
      "aLength=%u, aReversed=%s, aWritingMode=%s), mText.Length()=%u",
      this, GetBoolName(mIsChrome), aStartOffset, aLength,
      GetBoolName(aReversed), GetWritingModeName(aWritingMode).get(),
      mText.Length()));
 
+  if (NS_WARN_IF(mIsChrome)) {
+    return;
+  }
+
   if (!aReversed) {
     mSelection.mAnchor = aStartOffset;
     mSelection.mFocus = aStartOffset + aLength;
   } else {
     mSelection.mAnchor = aStartOffset + aLength;
     mSelection.mFocus = aStartOffset;
   }
   mSelection.mWritingMode = aWritingMode;
-}
 
-void
-ContentCache::SetSelection(uint32_t aAnchorOffset,
-                           uint32_t aFocusOffset,
-                           const WritingMode& aWritingMode)
-{
-  MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) SetSelection(aAnchorOffset=%u, "
-     "aFocusOffset=%u, aWritingMode=%s), mText.Length()=%u",
-     this, GetBoolName(mIsChrome), aAnchorOffset, aFocusOffset,
-     GetWritingModeName(aWritingMode).get(), mText.Length()));
-
-  mSelection.mAnchor = aAnchorOffset;
-  mSelection.mFocus = aFocusOffset;
-  mSelection.mWritingMode = aWritingMode;
+  if (NS_WARN_IF(!CacheCaret(aWidget))) {
+    return;
+  }
+  NS_WARN_IF(!CacheTextRects(aWidget));
 }
 
 bool
 ContentCache::GetTextRect(uint32_t aOffset,
                           LayoutDeviceIntRect& aTextRect) const
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
     ("ContentCache: 0x%p (mIsChrome=%s) GetTextRect(aOffset=%u), "
-     "mTextRectArray={ mStart=%u, mRects.Length()=%u }",
+     "mTextRectArray={ mStart=%u, mRects.Length()=%u }, "
+     "mSelection={ mAnchor=%u, mFocus=%u }",
      this, GetBoolName(mIsChrome), aOffset, mTextRectArray.mStart,
-     mTextRectArray.mRects.Length()));
+     mTextRectArray.mRects.Length(), mSelection.mAnchor, mSelection.mFocus));
+
+  if (aOffset == mSelection.mAnchor) {
+    NS_WARN_IF(mSelection.mAnchorCharRect.IsEmpty());
+    aTextRect = mSelection.mAnchorCharRect;
+    return !aTextRect.IsEmpty();
+  }
+  if (aOffset == mSelection.mFocus) {
+    NS_WARN_IF(mSelection.mFocusCharRect.IsEmpty());
+    aTextRect = mSelection.mFocusCharRect;
+    return !aTextRect.IsEmpty();
+  }
 
   if (NS_WARN_IF(!mTextRectArray.InRange(aOffset))) {
     aTextRect.SetEmpty();
     return false;
   }
   aTextRect = mTextRectArray.GetRect(aOffset);
   return true;
 }
 
 bool
 ContentCache::GetUnionTextRects(uint32_t aOffset,
                                 uint32_t aLength,
                                 LayoutDeviceIntRect& aUnionTextRect) const
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
     ("ContentCache: 0x%p (mIsChrome=%s) GetUnionTextRects(aOffset=%u, "
-     "aLength=%u), mTextRectArray={ mStart=%u, mRects.Length()=%u }",
+     "aLength=%u), mTextRectArray={ mStart=%u, mRects.Length()=%u }, "
+     "mSelection={ mAnchor=%u, mFocus=%u }",
      this, GetBoolName(mIsChrome), aOffset, aLength,
-     mTextRectArray.mStart, mTextRectArray.mRects.Length()));
+     mTextRectArray.mStart, mTextRectArray.mRects.Length(),
+     mSelection.mAnchor, mSelection.mFocus));
+
+  if (aLength == 1) {
+    if (aOffset == mSelection.mAnchor) {
+      NS_WARN_IF(mSelection.mAnchorCharRect.IsEmpty());
+      aUnionTextRect = mSelection.mAnchorCharRect;
+      return !aUnionTextRect.IsEmpty();
+    }
+    if (aOffset == mSelection.mFocus) {
+      NS_WARN_IF(mSelection.mFocusCharRect.IsEmpty());
+      aUnionTextRect = mSelection.mFocusCharRect;
+      return !aUnionTextRect.IsEmpty();
+    }
+  }
 
   if (NS_WARN_IF(!mTextRectArray.InRange(aOffset, aLength))) {
     aUnionTextRect.SetEmpty();
     return false;
   }
   aUnionTextRect = mTextRectArray.GetUnionRect(aOffset, aLength);
   return true;
 }
 
 bool
 ContentCache::GetCaretRect(uint32_t aOffset,
                            LayoutDeviceIntRect& aCaretRect) const
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
     ("ContentCache: 0x%p (mIsChrome=%s) GetCaretRect(aOffset=%u), "
      "mCaret={ mOffset=%u, mRect=%s, IsValid()=%s }, mTextRectArray={ "
-     "mStart=%u, mRects.Length()=%u }, mSelection={ mWritingMode=%s }",
+     "mStart=%u, mRects.Length()=%u }, mSelection={ mAnchor=%u, mFocus=%u, "
+     "mWritingMode=%s, mAnchorCharRect=%s, mFocusCharRect=%s }",
      this, GetBoolName(mIsChrome), aOffset, mCaret.mOffset,
      GetRectText(mCaret.mRect).get(), GetBoolName(mCaret.IsValid()),
      mTextRectArray.mStart, mTextRectArray.mRects.Length(),
-     GetWritingModeName(mSelection.mWritingMode).get()));
+     mSelection.mAnchor, mSelection.mFocus,
+     GetWritingModeName(mSelection.mWritingMode).get(),
+     GetRectText(mSelection.mAnchorCharRect).get(),
+     GetRectText(mSelection.mFocusCharRect).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)) {
@@ -673,21 +775,16 @@ ContentCache::OnCompositionEvent(const W
     return false;
   }
 
   // We must be able to simulate the selection because
   // we might not receive selection updates in time
   if (!mIsComposing) {
     mCompositionStart = mSelection.StartOffset();
   }
-  // XXX This causes different behavior from non-e10s mode.
-  //     Selection range should represent caret position in the composition
-  //     string but this means selection range is all of the composition string.
-  SetSelection(mCompositionStart + aEvent.mData.Length(),
-               mSelection.mWritingMode);
   mIsComposing = !aEvent.CausesDOMCompositionEndEvent();
   return true;
 }
 
 uint32_t
 ContentCache::RequestToCommitComposition(nsIWidget* aWidget,
                                          bool aCancel,
                                          nsAString& aLastString)
--- a/widget/ContentCache.h
+++ b/widget/ContentCache.h
@@ -77,18 +77,16 @@ public:
    * Cache*() retrieves the latest content information and store them.
    */
   bool CacheEditorRect(nsIWidget* aWidget,
                        const IMENotification* aNotification = nullptr);
   bool CacheSelection(nsIWidget* aWidget,
                       const IMENotification* aNotification = nullptr);
   bool CacheText(nsIWidget* aWidget,
                  const IMENotification* aNotification = nullptr);
-  bool CacheTextRects(nsIWidget* aWidget,
-                      const IMENotification* aNotification = nullptr);
 
   bool CacheAll(nsIWidget* aWidget,
                 const IMENotification* aNotification = nullptr);
 
   /**
    * OnCompositionEvent() should be called before sending composition string.
    * This returns true if the event should be sent.  Otherwise, false.
    */
@@ -113,33 +111,21 @@ public:
 
   /**
    * InitNotification() initializes aNotification with stored data.
    *
    * @param aNotification       Must be NOTIFY_IME_OF_SELECTION_CHANGE.
    */
   void InitNotification(IMENotification& aNotification) const;
 
-  void SetSelection(uint32_t aCaretOffset, const WritingMode& aWritingMode)
-  {
-    SetSelection(aCaretOffset, aCaretOffset, aWritingMode);
-  }
-  void SetSelection(uint32_t aStartOffset,
-                    uint32_t aLength,
-                    bool aReversed)
-  {
-    SetSelection(aStartOffset, aLength, aReversed, mSelection.mWritingMode);
-  }
-  void SetSelection(uint32_t aStartOffset,
+  void SetSelection(nsIWidget* aWidget,
+                    uint32_t aStartOffset,
                     uint32_t aLength,
                     bool aReversed,
                     const WritingMode& aWritingMode);
-  void SetSelection(uint32_t aAnchorOffset,
-                    uint32_t aFocusOffset,
-                    const WritingMode& aWritingMode);
 
 private:
   // Whole text in the target
   nsString mText;
   // This is commit string which is caused by our request.
   nsString mCommitStringByRequest;
   // Start offset of the composition string.
   uint32_t mCompositionStart;
@@ -150,26 +136,32 @@ private:
   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;
+
     Selection()
       : mAnchor(0)
       , mFocus(0)
     {
     }
 
     void Clear()
     {
       mAnchor = mFocus = 0;
       mWritingMode = WritingMode();
+      mAnchorCharRect.SetEmpty();
+      mFocusCharRect.SetEmpty();
     }
 
     bool Collapsed() const { return mFocus == mAnchor; }
     bool Reversed() const { return mFocus < mAnchor; }
     uint32_t StartOffset() const { return Reversed() ? mFocus : mAnchor; }
     uint32_t EndOffset() const { return Reversed() ? mAnchor : mFocus; }
     uint32_t Length() const
     {
@@ -249,16 +241,24 @@ private:
   } mTextRectArray;
 
   LayoutDeviceIntRect mEditorRect;
 
   bool mIsComposing;
   bool mRequestedToCommitOrCancelComposition;
   bool mIsChrome;
 
+  bool QueryCharRect(nsIWidget* aWidget,
+                     uint32_t aOffset,
+                     LayoutDeviceIntRect& aCharRect) const;
+  bool CacheCaret(nsIWidget* aWidget,
+                  const IMENotification* aNotification = nullptr);
+  bool CacheTextRects(nsIWidget* aWidget,
+                      const IMENotification* aNotification = nullptr);
+
   bool GetCaretRect(uint32_t aOffset, LayoutDeviceIntRect& aCaretRect) const;
   bool GetTextRect(uint32_t aOffset,
                    LayoutDeviceIntRect& aTextRect) const;
   bool GetUnionTextRects(uint32_t aOffset,
                          uint32_t aLength,
                          LayoutDeviceIntRect& aUnionTextRect) const;
 
   friend struct IPC::ParamTraits<ContentCache>;
--- a/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -696,18 +696,17 @@ PuppetWidget::NotifyIMEOfUpdateCompositi
                 const IMENotification& aIMENotification)
 {
 #ifndef MOZ_CROSS_PROCESS_IME
   return NS_OK;
 #endif
 
   NS_ENSURE_TRUE(mTabChild, NS_ERROR_FAILURE);
 
-  if (NS_WARN_IF(!mContentCache.CacheTextRects(this, &aIMENotification)) ||
-      NS_WARN_IF(!mContentCache.CacheSelection(this, &aIMENotification))) {
+  if (NS_WARN_IF(!mContentCache.CacheSelection(this, &aIMENotification))) {
     return NS_ERROR_FAILURE;
   }
   mTabChild->SendNotifyIMESelectedCompositionRect(mContentCache);
   return NS_OK;
 }
 
 nsIMEUpdatePreference
 PuppetWidget::GetIMEUpdatePreference()
@@ -736,20 +735,17 @@ PuppetWidget::NotifyIMEOfTextChange(cons
 
   if (!mTabChild)
     return NS_ERROR_FAILURE;
 
   // FYI: text change notification is the first notification after
   //      a user operation changes the content.  So, we need to modify
   //      the cache as far as possible here.
 
-  // When text is changed, selection and text rects must be changed too.
-  if (NS_WARN_IF(!mContentCache.CacheText(this, &aIMENotification)) ||
-      NS_WARN_IF(!mContentCache.CacheTextRects(this, &aIMENotification)) ||
-      NS_WARN_IF(!mContentCache.CacheSelection(this, &aIMENotification))) {
+  if (NS_WARN_IF(!mContentCache.CacheText(this, &aIMENotification))) {
     return NS_ERROR_FAILURE;
   }
 
   // TabParent doesn't this this to cache.  we don't send the notification
   // if parent process doesn't request NOTIFY_TEXT_CHANGE.
   if (mIMEPreferenceOfParent.WantTextChange() &&
       (mIMEPreferenceOfParent.WantChangesCausedByComposition() ||
        !aIMENotification.mTextChangeData.mCausedByComposition)) {
@@ -777,16 +773,17 @@ PuppetWidget::NotifyIMEOfSelectionChange
 #endif
 
   if (!mTabChild)
     return NS_ERROR_FAILURE;
 
   // Note that selection change must be notified after text change if it occurs.
   // Therefore, we don't need to query text content again here.
   mContentCache.SetSelection(
+    this,
     aIMENotification.mSelectionChangeData.mOffset,
     aIMENotification.mSelectionChangeData.mLength,
     aIMENotification.mSelectionChangeData.mReversed,
     aIMENotification.mSelectionChangeData.GetWritingMode());
 
   mTabChild->SendNotifyIMESelection(
     mContentCache, aIMENotification.mSelectionChangeData.mCausedByComposition);
   return NS_OK;
@@ -815,17 +812,16 @@ PuppetWidget::NotifyIMEOfPositionChange(
 #ifndef MOZ_CROSS_PROCESS_IME
   return NS_OK;
 #endif
   if (NS_WARN_IF(!mTabChild)) {
     return NS_ERROR_FAILURE;
   }
 
   if (NS_WARN_IF(!mContentCache.CacheEditorRect(this, &aIMENotification)) ||
-      NS_WARN_IF(!mContentCache.CacheTextRects(this, &aIMENotification)) ||
       NS_WARN_IF(!mContentCache.CacheSelection(this, &aIMENotification))) {
     return NS_ERROR_FAILURE;
   }
   if (!mTabChild->SendNotifyIMEPositionChange(mContentCache)) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -800,29 +800,33 @@ struct ParamTraits<mozilla::ContentCache
   typedef mozilla::ContentCache paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     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.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, void** aIter, paramType* aResult)
   {
     return 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->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);
   }
 };