# Global Metrics

path: .metrics.nom.total
old: 2.0
new: 30.0
path: .metrics.nom.functions
old: 2.0
new: 30.0
path: .metrics.mi.mi_sei
old: 33.03605355750832
new: -124.69524650787864
path: .metrics.mi.mi_visual_studio
old: 30.59485049806655
new: 0.0
path: .metrics.mi.mi_original
old: 52.3171943516938
new: -71.83259790057227
path: .metrics.cyclomatic.average
old: 1.0
new: 8.84375
path: .metrics.cyclomatic.sum
old: 13.0
new: 283.0
path: .metrics.halstead.vocabulary
old: 102.0
new: 484.0
path: .metrics.halstead.difficulty
old: 8.988636363636363
new: 116.67002237136464
path: .metrics.halstead.bugs
old: 0.20912115191343464
new: 12.199956660734454
path: .metrics.halstead.purity_ratio
old: 2.373026531865987
new: 0.6134932888983823
path: .metrics.halstead.n2
old: 88.0
new: 447.0
path: .metrics.halstead.n1
old: 14.0
new: 37.0
path: .metrics.halstead.N1
old: 149.0
new: 3910.0
path: .metrics.halstead.N2
old: 113.0
new: 2819.0
path: .metrics.halstead.time
old: 872.9840736874095
new: 388997.49873016495
path: .metrics.halstead.effort
old: 15713.71332637337
new: 7001954.977142969
path: .metrics.halstead.volume
old: 1748.1754395965318
new: 60015.030723620745
path: .metrics.halstead.estimated_program_length
old: 621.7329513488886
new: 4128.196340997214
path: .metrics.halstead.level
old: 0.11125158027812895
new: 0.00857118203695004
path: .metrics.halstead.length
old: 262.0
new: 6729.0
path: .metrics.cognitive.average
old: 0.0
new: 10.266666666666667
path: .metrics.cognitive.sum
old: 0.0
new: 308.0
path: .metrics.loc.ploc
old: 71.0
new: 1366.0
path: .metrics.loc.blank
old: 21.0
new: 128.0
path: .metrics.loc.cloc
old: 23.0
new: 209.0
path: .metrics.loc.lloc
old: 2.0
new: 589.0
path: .metrics.loc.sloc
old: 115.0
new: 1703.0
path: .metrics.nexits.average
old: 1.0
new: 2.7333333333333334
path: .metrics.nexits.sum
old: 2.0
new: 82.0
path: .metrics.nargs.average
old: 0.0
new: 1.9333333333333331
path: .metrics.nargs.sum
old: 0.0
new: 58.0

# Spaces Data

## Minimal test - lines (22, 1703)

path: .spaces[0].metrics.loc.ploc
old: 59.0
new: 1355.0
path: .spaces[0].metrics.loc.lloc
old: 2.0
new: 589.0
path: .spaces[0].metrics.loc.cloc
old: 18.0
new: 202.0
path: .spaces[0].metrics.loc.sloc
old: 96.0
new: 1682.0
path: .spaces[0].metrics.loc.blank
old: 19.0
new: 125.0
path: .spaces[0].metrics.nexits.average
old: 1.0
new: 2.7333333333333334
path: .spaces[0].metrics.nexits.sum
old: 2.0
new: 82.0
path: .spaces[0].metrics.cognitive.average
old: 0.0
new: 10.266666666666667
path: .spaces[0].metrics.cognitive.sum
old: 0.0
new: 308.0
path: .spaces[0].metrics.halstead.length
old: 251.0
new: 6719.0
path: .spaces[0].metrics.halstead.purity_ratio
old: 2.165594987968374
new: 0.5991800376097682
path: .spaces[0].metrics.halstead.bugs
old: 0.2026376591544046
new: 12.316011931326354
path: .spaces[0].metrics.halstead.level
old: 0.1092436974789916
new: 0.008409263660242656
path: .spaces[0].metrics.halstead.vocabulary
old: 92.0
new: 474.0
path: .spaces[0].metrics.halstead.n2
old: 78.0
new: 437.0
path: .spaces[0].metrics.halstead.volume
old: 1637.4140509703102
new: 59723.4658893474
path: .spaces[0].metrics.halstead.N1
old: 149.0
new: 3910.0
path: .spaces[0].metrics.halstead.n1
old: 14.0
new: 37.0
path: .spaces[0].metrics.halstead.time
old: 832.7020173737902
new: 394561.3386904108
path: .spaces[0].metrics.halstead.effort
old: 14988.636312728224
new: 7102104.096427394
path: .spaces[0].metrics.halstead.estimated_program_length
old: 543.5643419800618
new: 4025.890672700033
path: .spaces[0].metrics.halstead.difficulty
old: 9.153846153846152
new: 118.91647597254004
path: .spaces[0].metrics.halstead.N2
old: 102.0
new: 2809.0
path: .spaces[0].metrics.cyclomatic.average
old: 1.0
new: 9.096774193548388
path: .spaces[0].metrics.cyclomatic.sum
old: 12.0
new: 282.0
path: .spaces[0].metrics.nom.functions
old: 2.0
new: 30.0
path: .spaces[0].metrics.nom.total
old: 2.0
new: 30.0
path: .spaces[0].metrics.nargs.sum
old: 0.0
new: 58.0
path: .spaces[0].metrics.nargs.average
old: 0.0
new: 1.9333333333333331
path: .spaces[0].metrics.mi.mi_sei
old: 37.12359046885311
new: -124.38943908486084
path: .spaces[0].metrics.mi.mi_visual_studio
old: 32.63919135187842
new: 0.0
path: .spaces[0].metrics.mi.mi_original
old: 55.81301721171209
new: -71.376266701881

### Code

    namespace mozilla {

    using namespace dom;
    using namespace widget;

    static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }

    static const char* GetNotificationName(const IMENotification* aNotification) {
      if (!aNotification) {
        return "Not notification";
      }
      return ToChar(aNotification->mMessage);
    }

    /*****************************************************************************
     * mozilla::ContentCache
     *****************************************************************************/

    LazyLogModule sContentCacheLog("ContentCacheWidgets");

    /*****************************************************************************
     * mozilla::ContentCacheInChild
     *****************************************************************************/

    void ContentCacheInChild::Clear() {
      MOZ_LOG(sContentCacheLog, LogLevel::Info, ("0x%p Clear()", this));

      mCompositionStart.reset();
      mLastCommit.reset();
      mText.Truncate();
      mSelection.reset();
      mFirstCharRect.SetEmpty();
      mCaret.reset();
      mTextRectArray.reset();
      mLastCommitStringTextRectArray.reset();
      mEditorRect.SetEmpty();
    }

    void ContentCacheInChild::OnCompositionEvent(
        const WidgetCompositionEvent& aCompositionEvent) {
      if (aCompositionEvent.CausesDOMCompositionEndEvent()) {
        RefPtr composition =
            IMEStateManager::GetTextCompositionFor(aCompositionEvent.mWidget);
        if (composition) {
          nsAutoString lastCommitString;
          if (aCompositionEvent.mMessage == eCompositionCommitAsIs) {
            lastCommitString = composition->CommitStringIfCommittedAsIs();
          } else {
            lastCommitString = aCompositionEvent.mData;
          }
          // We don't need to store canceling information because this is required
          // by undoing of last commit (Kakutei-Undo of Japanese IME).
          if (!lastCommitString.IsEmpty()) {
            mLastCommit = Some(OffsetAndData(
                composition->NativeOffsetOfStartComposition(), lastCommitString));
            MOZ_LOG(
                sContentCacheLog, LogLevel::Debug,
                ("0x%p OnCompositionEvent(), stored last composition string data "
                 "(aCompositionEvent={ mMessage=%s, mData=\"%s\"}, mLastCommit=%s)",
                 this, ToChar(aCompositionEvent.mMessage),
                 PrintStringDetail(
                     aCompositionEvent.mData,
                     PrintStringDetail::kMaxLengthForCompositionString)
                     .get(),
                 ToString(mLastCommit).c_str()));
            return;
          }
        }
      }
      if (mLastCommit.isSome()) {
        MOZ_LOG(
            sContentCacheLog, LogLevel::Debug,
            ("0x%p OnCompositionEvent(), resetting the last composition string "
             "data (aCompositionEvent={ mMessage=%s, mData=\"%s\"}, "
             "mLastCommit=%s)",
             this, ToChar(aCompositionEvent.mMessage),
             PrintStringDetail(aCompositionEvent.mData,
                               PrintStringDetail::kMaxLengthForCompositionString)
                 .get(),
             ToString(mLastCommit).c_str()));
        mLastCommit.reset();
      }
    }

    bool ContentCacheInChild::CacheAll(nsIWidget* aWidget,
                                       const IMENotification* aNotification) {
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
              ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)", this, aWidget,
               GetNotificationName(aNotification)));

      if (NS_WARN_IF(!CacheText(aWidget, aNotification)) ||
          NS_WARN_IF(!CacheEditorRect(aWidget, aNotification))) {
        return false;
      }
      return true;
    }

    bool ContentCacheInChild::CacheSelection(nsIWidget* aWidget,
                                             const IMENotification* aNotification) {
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
              ("0x%p CacheSelection(aWidget=0x%p, aNotification=%s)", this, aWidget,
               GetNotificationName(aNotification)));

      mCaret.reset();
      mSelection.reset();

      nsEventStatus status = nsEventStatus_eIgnore;
      WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
                                                     aWidget);
      aWidget->DispatchEvent(&querySelectedTextEvent, status);
      if (NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection())) {
        MOZ_LOG(
            sContentCacheLog, LogLevel::Error,
            ("0x%p CacheSelection(), FAILED, couldn't retrieve the selected text",
             this));
        return false;
      }
      MOZ_ASSERT(querySelectedTextEvent.mReply->mOffsetAndData.isSome());
      if (querySelectedTextEvent.mReply->mReversed) {
        mSelection.emplace(querySelectedTextEvent.mReply->EndOffset(),
                           querySelectedTextEvent.mReply->StartOffset(),
                           querySelectedTextEvent.mReply->WritingModeRef());
      } else {
        mSelection.emplace(querySelectedTextEvent.mReply->StartOffset(),
                           querySelectedTextEvent.mReply->EndOffset(),
                           querySelectedTextEvent.mReply->WritingModeRef());
      }

      return CacheCaret(aWidget, aNotification) &&
             CacheTextRects(aWidget, aNotification);
    }

    bool ContentCacheInChild::CacheCaret(nsIWidget* aWidget,
                                         const IMENotification* aNotification) {
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
              ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)", this, aWidget,
               GetNotificationName(aNotification)));

      mCaret.reset();

      if (NS_WARN_IF(mSelection.isNothing())) {
        return false;
      }

      // XXX Should be mSelection.mFocus?
      uint32_t offset = mSelection->StartOffset();

      nsEventStatus status = nsEventStatus_eIgnore;
      WidgetQueryContentEvent queryCaretRectEvet(true, eQueryCaretRect, aWidget);
      queryCaretRectEvet.InitForQueryCaretRect(offset);
      aWidget->DispatchEvent(&queryCaretRectEvet, status);
      if (NS_WARN_IF(queryCaretRectEvet.Failed())) {
        MOZ_LOG(sContentCacheLog, LogLevel::Error,
                ("0x%p CacheCaret(), FAILED, couldn't retrieve the caret rect at "
                 "offset=%u",
                 this, offset));
        return false;
      }
      mCaret.emplace(offset, queryCaretRectEvet.mReply->mRect);
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
              ("0x%p CacheCaret(), Succeeded, "
               "mSelection=%s, mCaret=%s",
               this, ToString(mSelection).c_str(), ToString(mCaret).c_str()));
      return true;
    }

    bool ContentCacheInChild::CacheEditorRect(
        nsIWidget* aWidget, const IMENotification* aNotification) {
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
              ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)", this,
               aWidget, GetNotificationName(aNotification)));

      nsEventStatus status = nsEventStatus_eIgnore;
      WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, aWidget);
      aWidget->DispatchEvent(&queryEditorRectEvent, status);
      if (NS_WARN_IF(queryEditorRectEvent.Failed())) {
        MOZ_LOG(sContentCacheLog, LogLevel::Error,
                ("0x%p CacheEditorRect(), FAILED, "
                 "couldn't retrieve the editor rect",
                 this));
        return false;
      }
      mEditorRect = queryEditorRectEvent.mReply->mRect;
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
              ("0x%p CacheEditorRect(), Succeeded, "
               "mEditorRect=%s",
               this, ToString(mEditorRect).c_str()));
      return true;
    }

    bool ContentCacheInChild::CacheText(nsIWidget* aWidget,
                                        const IMENotification* aNotification) {
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
              ("0x%p CacheText(aWidget=0x%p, aNotification=%s)", this, aWidget,
               GetNotificationName(aNotification)));

      nsEventStatus status = nsEventStatus_eIgnore;
      WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
                                                    aWidget);
      queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
      aWidget->DispatchEvent(&queryTextContentEvent, status);
      if (NS_WARN_IF(queryTextContentEvent.Failed())) {
        MOZ_LOG(sContentCacheLog, LogLevel::Error,
                ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
        mText.Truncate();
        return false;
      }
      mText = queryTextContentEvent.mReply->DataRef();
      MOZ_LOG(
          sContentCacheLog, LogLevel::Info,
          ("0x%p CacheText(), Succeeded, mText.Length()=%u", this, mText.Length()));

      // Forget last commit range if string in the range is different from the
      // last commit string.
      if (mLastCommit.isSome() &&
          nsDependentSubstring(mText, mLastCommit->StartOffset(),
                               mLastCommit->Length()) != mLastCommit->DataRef()) {
        MOZ_LOG(sContentCacheLog, LogLevel::Debug,
                ("0x%p CacheText(), resetting the last composition string data "
                 "(mLastCommit=%s, current string=\"%s\")",
                 this, ToString(mLastCommit).c_str(),
                 PrintStringDetail(
                     nsDependentSubstring(mText, mLastCommit->StartOffset(),
                                          mLastCommit->Length()),
                     PrintStringDetail::kMaxLengthForCompositionString)
                     .get()));
        mLastCommit.reset();
      }

      return CacheSelection(aWidget, aNotification);
    }

    bool ContentCacheInChild::QueryCharRect(nsIWidget* aWidget, uint32_t aOffset,
                                            LayoutDeviceIntRect& aCharRect) const {
      aCharRect.SetEmpty();

      nsEventStatus status = nsEventStatus_eIgnore;
      WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWidget);
      queryTextRectEvent.InitForQueryTextRect(aOffset, 1);
      aWidget->DispatchEvent(&queryTextRectEvent, status);
      if (NS_WARN_IF(queryTextRectEvent.Failed())) {
        return false;
      }
      aCharRect = queryTextRectEvent.mReply->mRect;

      // Guarantee the rect is not empty.
      if (NS_WARN_IF(!aCharRect.Height())) {
        aCharRect.SetHeight(1);
      }
      if (NS_WARN_IF(!aCharRect.Width())) {
        aCharRect.SetWidth(1);
      }
      return true;
    }

    bool ContentCacheInChild::QueryCharRectArray(nsIWidget* aWidget,
                                                 uint32_t aOffset, uint32_t aLength,
                                                 RectArray& aCharRectArray) const {
      nsEventStatus status = nsEventStatus_eIgnore;
      WidgetQueryContentEvent queryTextRectsEvent(true, eQueryTextRectArray,
                                                  aWidget);
      queryTextRectsEvent.InitForQueryTextRectArray(aOffset, aLength);
      aWidget->DispatchEvent(&queryTextRectsEvent, status);
      if (NS_WARN_IF(queryTextRectsEvent.Failed())) {
        aCharRectArray.Clear();
        return false;
      }
      aCharRectArray = std::move(queryTextRectsEvent.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=%s", this,
           aWidget, GetNotificationName(aNotification), ToString(mCaret).c_str()));

      mCompositionStart.reset();
      mTextRectArray.reset();
      mLastCommitStringTextRectArray.reset();
      mFirstCharRect.SetEmpty();

      if (NS_WARN_IF(mSelection.isNothing())) {
        return false;
      }

      mSelection->ClearRects();

      // Retrieve text rects in composition string if there is.
      RefPtr textComposition =
          IMEStateManager::GetTextCompositionFor(aWidget);
      if (textComposition) {
        // mCompositionStart may be updated by some composition event handlers.
        // So, let's update it with the latest information.
        mCompositionStart = Some(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.
        // FYI: For supporting IME which commits composition and restart new
        //      composition immediately, we should cache next character of current
        //      composition too.
        uint32_t length = textComposition->LastData().Length() + 1;
        mTextRectArray.emplace(mCompositionStart.value());
        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));
          mTextRectArray.reset();
        }
      }

      if (mTextRectArray.isSome() &&
          mTextRectArray->IsOffsetInRange(mSelection->mAnchor) &&
          (!mSelection->mAnchor ||
           mTextRectArray->IsOffsetInRange(mSelection->mAnchor - 1))) {
        mSelection->mAnchorCharRects[eNextCharRect] =
            mTextRectArray->GetRect(mSelection->mAnchor);
        if (mSelection->mAnchor) {
          mSelection->mAnchorCharRects[ePrevCharRect] =
              mTextRectArray->GetRect(mSelection->mAnchor - 1);
        }
      } else {
        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 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());
          }
        }
      }

      if (mSelection->Collapsed()) {
        mSelection->mFocusCharRects[0] = mSelection->mAnchorCharRects[0];
        mSelection->mFocusCharRects[1] = mSelection->mAnchorCharRects[1];
      } else if (mTextRectArray.isSome() &&
                 mTextRectArray->IsOffsetInRange(mSelection->mFocus) &&
                 (!mSelection->mFocus ||
                  mTextRectArray->IsOffsetInRange(mSelection->mFocus - 1))) {
        mSelection->mFocusCharRects[eNextCharRect] =
            mTextRectArray->GetRect(mSelection->mFocus);
        if (mSelection->mFocus) {
          mSelection->mFocusCharRects[ePrevCharRect] =
              mTextRectArray->GetRect(mSelection->mFocus - 1);
        }
      } else {
        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 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());
          }
        }
      }

      if (!mSelection->Collapsed()) {
        nsEventStatus status = nsEventStatus_eIgnore;
        WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWidget);
        queryTextRectEvent.InitForQueryTextRect(mSelection->StartOffset(),
                                                mSelection->Length());
        aWidget->DispatchEvent(&queryTextRectEvent, status);
        if (NS_WARN_IF(queryTextRectEvent.Failed())) {
          MOZ_LOG(sContentCacheLog, LogLevel::Error,
                  ("0x%p CacheTextRects(), FAILED, "
                   "couldn't retrieve text rect of whole selected text",
                   this));
        } else {
          mSelection->mRect = queryTextRectEvent.mReply->mRect;
        }
      }

      if (!mSelection->mFocus) {
        mFirstCharRect = mSelection->mFocusCharRects[eNextCharRect];
      } else if (mSelection->mFocus == 1) {
        mFirstCharRect = mSelection->mFocusCharRects[ePrevCharRect];
      } else if (!mSelection->mAnchor) {
        mFirstCharRect = mSelection->mAnchorCharRects[eNextCharRect];
      } else if (mSelection->mAnchor == 1) {
        mFirstCharRect = mSelection->mFocusCharRects[ePrevCharRect];
      } else if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(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;
        }
      }

      if (mLastCommit.isSome()) {
        mLastCommitStringTextRectArray.emplace(mLastCommit->StartOffset());
        if (mLastCommit->Length() == 1) {
          MOZ_ASSERT(mSelection->Collapsed());
          MOZ_ASSERT(mSelection->mAnchor - 1 == mLastCommit->StartOffset());
          mLastCommitStringTextRectArray->mRects.AppendElement(
              mSelection->mAnchorCharRects[ePrevCharRect]);
        } else if (NS_WARN_IF(!QueryCharRectArray(
                       aWidget, mLastCommit->StartOffset(), mLastCommit->Length(),
                       mLastCommitStringTextRectArray->mRects))) {
          MOZ_LOG(sContentCacheLog, LogLevel::Error,
                  ("0x%p CacheTextRects(), FAILED, "
                   "couldn't retrieve text rect array of the last commit string",
                   this));
          mLastCommitStringTextRectArray.reset();
          mLastCommit.reset();
        }
        MOZ_ASSERT((mLastCommitStringTextRectArray.isSome()
                        ? mLastCommitStringTextRectArray->mRects.Length()
                        : 0) == (mLastCommit.isSome() ? mLastCommit->Length() : 0));
      }

      MOZ_LOG(sContentCacheLog, LogLevel::Info,
              ("0x%p CacheTextRects(), Succeeded, "
               "mText.Length()=%x, mTextRectArray=%s, mSelection=%s, "
               "mFirstCharRect=%s, mLastCommitStringTextRectArray=%s",
               this, mText.Length(), ToString(mTextRectArray).c_str(),
               ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str(),
               ToString(mLastCommitStringTextRectArray).c_str()));
      return true;
    }

    void ContentCacheInChild::SetSelection(nsIWidget* aWidget,
                                           uint32_t aStartOffset, uint32_t aLength,
                                           bool aReversed,
                                           const WritingMode& aWritingMode) {
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
              ("0x%p SetSelection(aStartOffset=%u, "
               "aLength=%u, aReversed=%s, aWritingMode=%s), mText.Length()=%u",
               this, aStartOffset, aLength, GetBoolName(aReversed),
               ToString(aWritingMode).c_str(), mText.Length()));

      mSelection = Some(Selection(
          !aReversed ? aStartOffset : aStartOffset + aLength,
          !aReversed ? aStartOffset + aLength : aStartOffset, aWritingMode));

      if (mLastCommit.isSome()) {
        // Forget last commit string range if selection is not collapsed
        // at end of the last commit string.
        if (!mSelection->Collapsed() ||
            mSelection->mAnchor != mLastCommit->EndOffset()) {
          MOZ_LOG(
              sContentCacheLog, LogLevel::Debug,
              ("0x%p SetSelection(), forgetting last commit composition data "
               "(mSelection=%s, mLastCommit=%s)",
               this, ToString(mSelection).c_str(), ToString(mLastCommit).c_str()));
          mLastCommit.reset();
        }
      }

      if (NS_WARN_IF(!CacheCaret(aWidget))) {
        return;
      }
      Unused << NS_WARN_IF(!CacheTextRects(aWidget));
    }

    /*****************************************************************************
     * mozilla::ContentCacheInParent
     *****************************************************************************/

    ContentCacheInParent::ContentCacheInParent(BrowserParent& aBrowserParent)
        : ContentCache(),
          mBrowserParent(aBrowserParent),
          mCommitStringByRequest(nullptr),
          mPendingEventsNeedingAck(0),
          mPendingCommitLength(0),
          mPendingCompositionCount(0),
          mPendingCommitCount(0),
          mWidgetHasComposition(false),
          mIsChildIgnoringCompositionEvents(false) {}

    void ContentCacheInParent::AssignContent(const ContentCache& aOther,
                                             nsIWidget* aWidget,
                                             const IMENotification* aNotification) {
      mText = aOther.mText;
      mSelection = aOther.mSelection;
      mFirstCharRect = aOther.mFirstCharRect;
      mCaret = aOther.mCaret;
      mTextRectArray = aOther.mTextRectArray;
      mLastCommitStringTextRectArray = aOther.mLastCommitStringTextRectArray;
      mEditorRect = aOther.mEditorRect;

      // Only when there is one composition, the TextComposition instance in this
      // process is managing the composition in the remote process.  Therefore,
      // we shouldn't update composition start offset of TextComposition with
      // old composition which is still being handled by the child process.
      if (mWidgetHasComposition && mPendingCompositionCount == 1 &&
          mCompositionStart.isSome()) {
        IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget,
                                                        mCompositionStart.value());
      }

      // When this instance allows to query content relative to composition string,
      // we should modify mCompositionStart with the latest information in the
      // remote process because now we have the information around the composition
      // string.
      mCompositionStartInChild = aOther.mCompositionStart;
      if (mWidgetHasComposition || mPendingCommitCount) {
        if (mCompositionStartInChild.isSome()) {
          if (mCompositionStart.valueOr(UINT32_MAX) !=
              mCompositionStartInChild.value()) {
            mCompositionStart = mCompositionStartInChild;
            mPendingCommitLength = 0;
          }
        } else if (mCompositionStart.isSome() && mSelection.isSome() &&
                   mCompositionStart.value() != mSelection->StartOffset()) {
          mCompositionStart = Some(mSelection->StartOffset());
          mPendingCommitLength = 0;
        }
      }

      MOZ_LOG(sContentCacheLog, LogLevel::Info,
              ("0x%p AssignContent(aNotification=%s), "
               "Succeeded, mText.Length()=%u, mSelection=%s, mFirstCharRect=%s, "
               "mCaret=%s, mTextRectArray=%s, mWidgetHasComposition=%s, "
               "mPendingCompositionCount=%u, mCompositionStart=%s, "
               "mPendingCommitLength=%u, mEditorRect=%s, "
               "mLastCommitStringTextRectArray=%s",
               this, GetNotificationName(aNotification), mText.Length(),
               ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str(),
               ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
               GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
               ToString(mCompositionStart).c_str(), mPendingCommitLength,
               ToString(mEditorRect).c_str(),
               ToString(mLastCommitStringTextRectArray).c_str()));
    }

    bool ContentCacheInParent::HandleQueryContentEvent(
        WidgetQueryContentEvent& aEvent, nsIWidget* aWidget) const {
      MOZ_ASSERT(aWidget);

      // ContentCache doesn't store offset of its start with XP linebreaks.
      // So, we don't support to query contents relative to composition start
      // offset with XP linebreaks.
      if (NS_WARN_IF(!aEvent.mUseNativeLineBreak)) {
        MOZ_LOG(sContentCacheLog, LogLevel::Error,
                ("0x%p HandleQueryContentEvent(), FAILED due to query with XP "
                 "linebreaks",
                 this));
        return false;
      }

      if (NS_WARN_IF(!aEvent.mInput.IsValidOffset())) {
        MOZ_LOG(
            sContentCacheLog, LogLevel::Error,
            ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
        return false;
      }

      if (NS_WARN_IF(!aEvent.mInput.IsValidEventMessage(aEvent.mMessage))) {
        MOZ_LOG(
            sContentCacheLog, LogLevel::Error,
            ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
             this));
        return false;
      }

      bool isRelativeToInsertionPoint = aEvent.mInput.mRelativeToInsertionPoint;
      if (isRelativeToInsertionPoint) {
        MOZ_LOG(sContentCacheLog, LogLevel::Debug,
                ("0x%p HandleQueryContentEvent(), "
                 "making offset absolute... aEvent={ mMessage=%s, mInput={ "
                 "mOffset=%" PRId64 ", mLength=%" PRIu32 " } }, "
                 "mWidgetHasComposition=%s, mPendingCommitCount=%" PRIu8
                 ", mCompositionStart=%" PRIu32 ", "
                 "mPendingCommitLength=%" PRIu32 ", mSelection=%s",
                 this, ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
                 aEvent.mInput.mLength, GetBoolName(mWidgetHasComposition),
                 mPendingCommitCount, mCompositionStart.valueOr(UINT32_MAX),
                 mPendingCommitLength, ToString(mSelection).c_str()));
        if (mWidgetHasComposition || mPendingCommitCount) {
          if (NS_WARN_IF(mCompositionStart.isNothing()) ||
              NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
                  mCompositionStart.value() + mPendingCommitLength))) {
            MOZ_LOG(
                sContentCacheLog, LogLevel::Error,
                ("0x%p HandleQueryContentEvent(), FAILED due to "
                 "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
                 "mPendingCommitLength) failure, "
                 "mCompositionStart=%" PRIu32 ", mPendingCommitLength=%" PRIu32 ", "
                 "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
                 ", mLength=%" PRIu32 " } }",
                 this, mCompositionStart.valueOr(UINT32_MAX), mPendingCommitLength,
                 ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
                 aEvent.mInput.mLength));
            return false;
          }
        } else if (NS_WARN_IF(mSelection.isNothing())) {
          MOZ_LOG(sContentCacheLog, LogLevel::Error,
                  ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
                   "not set",
                   this));
          return false;
        } else if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
                       mSelection->StartOffset() + mPendingCommitLength))) {
          MOZ_LOG(sContentCacheLog, LogLevel::Error,
                  ("0x%p HandleQueryContentEvent(), FAILED due to "
                   "aEvent.mInput.MakeOffsetAbsolute(mSelection.StartOffset() + "
                   "mPendingCommitLength) failure, mSelection=%s, "
                   "mPendingCommitLength=%" PRIu32 ", aEvent={ mMessage=%s, "
                   "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
                   this, ToString(mSelection).c_str(), mPendingCommitLength,
                   ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
                   aEvent.mInput.mLength));
          return false;
        }
      }

      switch (aEvent.mMessage) {
        case eQuerySelectedText:
          MOZ_LOG(sContentCacheLog, LogLevel::Info,
                  ("0x%p HandleQueryContentEvent(aEvent={ "
                   "mMessage=eQuerySelectedText }, aWidget=0x%p)",
                   this, aWidget));
          if (NS_WARN_IF(!IsSelectionValid())) {
            // If content cache hasn't been initialized properly, make the query
            // failed.
            MOZ_LOG(sContentCacheLog, LogLevel::Error,
                    ("0x%p HandleQueryContentEvent(), FAILED because mSelection is "
                     "not valid",
                     this));
            return false;
          }
          if (!mSelection->Collapsed() &&
              NS_WARN_IF(mSelection->EndOffset() > mText.Length())) {
            MOZ_LOG(sContentCacheLog, LogLevel::Error,
                    ("0x%p HandleQueryContentEvent(), FAILED because "
                     "mSelection->EndOffset()=%u is larger than mText.Length()=%u",
                     this, mSelection->EndOffset(), mText.Length()));
            return false;
          }
          aEvent.EmplaceReply();
          aEvent.mReply->mFocusedWidget = aWidget;
          aEvent.mReply->mOffsetAndData.emplace(
              mSelection->StartOffset(),
              Substring(mText, mSelection->StartOffset(), mSelection->Length()),
              OffsetAndDataFor::SelectedString);
          aEvent.mReply->mReversed = mSelection->Reversed();
          aEvent.mReply->mHasSelection = true;
          aEvent.mReply->mWritingMode = mSelection->mWritingMode;
          MOZ_LOG(sContentCacheLog, LogLevel::Info,
                  ("0x%p HandleQueryContentEvent(), "
                   "Succeeded, aEvent={ mMessage=eQuerySelectedText, mReply=%s }",
                   this, ToString(aEvent.mReply).c_str()));
          return true;
        case eQueryTextContent: {
          MOZ_LOG(sContentCacheLog, LogLevel::Info,
                  ("0x%p HandleQueryContentEvent("
                   "aEvent={ mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
                   ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
                   this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
                   mText.Length()));
          uint32_t inputOffset = aEvent.mInput.mOffset;
          uint32_t inputEndOffset =
              std::min(aEvent.mInput.EndOffset(), mText.Length());
          if (NS_WARN_IF(inputEndOffset < inputOffset)) {
            MOZ_LOG(sContentCacheLog, LogLevel::Error,
                    ("0x%p HandleQueryContentEvent(), FAILED because "
                     "inputOffset=%u is larger than inputEndOffset=%u",
                     this, inputOffset, inputEndOffset));
            return false;
          }
          aEvent.EmplaceReply();
          aEvent.mReply->mFocusedWidget = aWidget;
          aEvent.mReply->mOffsetAndData.emplace(
              inputOffset,
              Substring(mText, inputOffset, inputEndOffset - inputOffset),
              OffsetAndDataFor::EditorString);
          // TODO: Support font ranges
          MOZ_LOG(sContentCacheLog, LogLevel::Info,
                  ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
                   "mMessage=eQueryTextContent, mReply=%s }",
                   this, ToString(aEvent.mReply).c_str()));
          return true;
        }
        case eQueryTextRect: {
          MOZ_LOG(sContentCacheLog, LogLevel::Info,
                  ("0x%p HandleQueryContentEvent("
                   "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
                   ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
                   this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
                   mText.Length()));
          if (NS_WARN_IF(!IsSelectionValid())) {
            // If content cache hasn't been initialized properly, make the query
            // failed.
            MOZ_LOG(sContentCacheLog, LogLevel::Error,
                    ("0x%p HandleQueryContentEvent(), FAILED because mSelection is "
                     "not valid",
                     this));
            return false;
          }
          // 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.
          LayoutDeviceIntRect textRect;
          if (aEvent.mInput.mLength) {
            if (NS_WARN_IF(
                    !GetUnionTextRects(aEvent.mInput.mOffset, aEvent.mInput.mLength,
                                       isRelativeToInsertionPoint, textRect))) {
              // XXX We don't have cache for this request.
              MOZ_LOG(sContentCacheLog, LogLevel::Error,
                      ("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, textRect))) {
              MOZ_LOG(sContentCacheLog, LogLevel::Error,
                      ("0x%p HandleQueryContentEvent(), FAILED to get caret rect",
                       this));
              return false;
            }
          }
          aEvent.EmplaceReply();
          aEvent.mReply->mFocusedWidget = aWidget;
          aEvent.mReply->mRect = textRect;
          aEvent.mReply->mOffsetAndData.emplace(
              aEvent.mInput.mOffset,
              aEvent.mInput.mOffset < mText.Length()
                  ? static_cast(
                        Substring(mText, aEvent.mInput.mOffset,
                                  mText.Length() >= aEvent.mInput.EndOffset()
                                      ? aEvent.mInput.mLength
                                      : UINT32_MAX))
                  : static_cast(EmptyString()),
              OffsetAndDataFor::EditorString);
          // XXX This may be wrong if storing range isn't in the selection range.
          aEvent.mReply->mWritingMode = mSelection->mWritingMode;
          MOZ_LOG(sContentCacheLog, LogLevel::Info,
                  ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
                   "mMessage=eQueryTextRect mReply=%s }",
                   this, ToString(aEvent.mReply).c_str()));
          return true;
        }
        case eQueryCaretRect: {
          MOZ_LOG(
              sContentCacheLog, LogLevel::Info,
              ("0x%p HandleQueryContentEvent(aEvent={ mMessage=eQueryCaretRect, "
               "mInput={ mOffset=%" PRId64 " } }, aWidget=0x%p), mText.Length()=%u",
               this, aEvent.mInput.mOffset, aWidget, mText.Length()));
          if (NS_WARN_IF(!IsSelectionValid())) {
            // If content cache hasn't been initialized properly, make the query
            // failed.
            MOZ_LOG(sContentCacheLog, LogLevel::Error,
                    ("0x%p HandleQueryContentEvent(), FAILED because mSelection is "
                     "not valid",
                     this));
            return false;
          }
          // 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.
          LayoutDeviceIntRect caretRect;
          if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
                                       isRelativeToInsertionPoint, caretRect))) {
            MOZ_LOG(
                sContentCacheLog, LogLevel::Error,
                ("0x%p HandleQueryContentEvent(),FAILED to get caret rect", this));
            return false;
          }
          aEvent.EmplaceReply();
          aEvent.mReply->mFocusedWidget = aWidget;
          aEvent.mReply->mRect = caretRect;
          aEvent.mReply->mOffsetAndData.emplace(aEvent.mInput.mOffset,
                                                EmptyString(),
                                                OffsetAndDataFor::SelectedString);
          // TODO: Set mWritingMode here
          MOZ_LOG(sContentCacheLog, LogLevel::Info,
                  ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
                   "mMessage=eQueryCaretRect, mReply=%s }",
                   this, ToString(aEvent.mReply).c_str()));
          return true;
        }
        case eQueryEditorRect:
          MOZ_LOG(sContentCacheLog, LogLevel::Info,
                  ("0x%p HandleQueryContentEvent(aEvent={ "
                   "mMessage=eQueryEditorRect }, aWidget=0x%p)",
                   this, aWidget));
          aEvent.EmplaceReply();
          aEvent.mReply->mFocusedWidget = aWidget;
          aEvent.mReply->mRect = mEditorRect;
          MOZ_LOG(sContentCacheLog, LogLevel::Info,
                  ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
                   "mMessage=eQueryEditorRect, mReply=%s }",
                   this, ToString(aEvent.mReply).c_str()));
          return true;
        default:
          aEvent.EmplaceReply();
          aEvent.mReply->mFocusedWidget = aWidget;
          if (NS_WARN_IF(aEvent.Failed())) {
            MOZ_LOG(
                sContentCacheLog, LogLevel::Error,
                ("0x%p HandleQueryContentEvent(), FAILED due to not set enough "
                 "data, aEvent={ mMessage=%s, mReply=%s }",
                 this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
            return false;
          }
          MOZ_LOG(sContentCacheLog, LogLevel::Info,
                  ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
                   "mMessage=%s, mReply=%s }",
                   this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
          return true;
      }
    }

    bool ContentCacheInParent::GetTextRect(uint32_t aOffset,
                                           bool aRoundToExistingOffset,
                                           LayoutDeviceIntRect& aTextRect) const {
      MOZ_LOG(
          sContentCacheLog, LogLevel::Info,
          ("0x%p GetTextRect(aOffset=%u, aRoundToExistingOffset=%s), "
           "mTextRectArray=%s, mSelection=%s, mLastCommitStringTextRectArray=%s",
           this, aOffset, GetBoolName(aRoundToExistingOffset),
           ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
           ToString(mLastCommitStringTextRectArray).c_str()));

      if (!aOffset) {
        NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
        aTextRect = mFirstCharRect;
        return !aTextRect.IsEmpty();
      }
      if (mSelection.isSome()) {
        if (aOffset == mSelection->mAnchor) {
          NS_WARNING_ASSERTION(
              !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(), "empty rect");
          aTextRect = mSelection->mAnchorCharRects[eNextCharRect];
          return !aTextRect.IsEmpty();
        }
        if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
          NS_WARNING_ASSERTION(
              !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(), "empty rect");
          aTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
          return !aTextRect.IsEmpty();
        }
        if (aOffset == mSelection->mFocus) {
          NS_WARNING_ASSERTION(
              !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(), "empty rect");
          aTextRect = mSelection->mFocusCharRects[eNextCharRect];
          return !aTextRect.IsEmpty();
        }
        if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
          NS_WARNING_ASSERTION(
              !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(), "empty rect");
          aTextRect = mSelection->mFocusCharRects[ePrevCharRect];
          return !aTextRect.IsEmpty();
        }
      }

      if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(aOffset)) {
        aTextRect = mTextRectArray->GetRect(aOffset);
        return !aTextRect.IsEmpty();
      }

      if (mLastCommitStringTextRectArray.isSome() &&
          mLastCommitStringTextRectArray->IsOffsetInRange(aOffset)) {
        aTextRect = mLastCommitStringTextRectArray->GetRect(aOffset);
        return !aTextRect.IsEmpty();
      }

      if (!aRoundToExistingOffset) {
        aTextRect.SetEmpty();
        return false;
      }

      if (mTextRectArray.isNothing() || !mTextRectArray->HasRects()) {
        // 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();
      }

      // Although we may have mLastCommitStringTextRectArray here and it must have
      // previous character rects at selection.  However, we should stop using it
      // because it's stored really short time after commiting a composition.
      // So, multiple query may return different rect and it may cause flickerling
      // the IME UI.
      uint32_t offset = aOffset;
      if (offset < mTextRectArray->StartOffset()) {
        offset = mTextRectArray->StartOffset();
      } else {
        offset = mTextRectArray->EndOffset() - 1;
      }
      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,
              ("0x%p GetUnionTextRects(aOffset=%u, "
               "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray=%s, "
               "mSelection=%s, mLastCommitStringTextRectArray=%s",
               this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
               ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
               ToString(mLastCommitStringTextRectArray).c_str()));

      CheckedInt endOffset = CheckedInt(aOffset) + aLength;
      if (!endOffset.isValid()) {
        return false;
      }

      if (mSelection.isSome() && !mSelection->Collapsed() &&
          aOffset == mSelection->StartOffset() && aLength == mSelection->Length()) {
        NS_WARNING_ASSERTION(!mSelection->mRect.IsEmpty(), "empty rect");
        aUnionTextRect = mSelection->mRect;
        return !aUnionTextRect.IsEmpty();
      }

      if (aLength == 1) {
        if (!aOffset) {
          NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
          aUnionTextRect = mFirstCharRect;
          return !aUnionTextRect.IsEmpty();
        }
        if (mSelection.isSome()) {
          if (aOffset == mSelection->mAnchor) {
            NS_WARNING_ASSERTION(
                !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(),
                "empty rect");
            aUnionTextRect = mSelection->mAnchorCharRects[eNextCharRect];
            return !aUnionTextRect.IsEmpty();
          }
          if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
            NS_WARNING_ASSERTION(
                !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(),
                "empty rect");
            aUnionTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
            return !aUnionTextRect.IsEmpty();
          }
          if (aOffset == mSelection->mFocus) {
            NS_WARNING_ASSERTION(
                !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(),
                "empty rect");
            aUnionTextRect = mSelection->mFocusCharRects[eNextCharRect];
            return !aUnionTextRect.IsEmpty();
          }
          if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
            NS_WARNING_ASSERTION(
                !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(),
                "empty rect");
            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.

      if (!aOffset && mSelection.isSome() && aOffset != mSelection->mAnchor &&
          aOffset != mSelection->mFocus &&
          (mTextRectArray.isNothing() ||
           !mTextRectArray->IsOffsetInRange(aOffset)) &&
          (mLastCommitStringTextRectArray.isNothing() ||
           !mLastCommitStringTextRectArray->IsOffsetInRange(aOffset))) {
        // The first character rect isn't cached.
        return false;
      }

      // Use mLastCommitStringTextRectArray only when it overlaps with aOffset
      // even if aROundToExistingOffset is true for avoiding flickerling IME UI.
      // See the last comment in GetTextRect() for the detail.
      if (mLastCommitStringTextRectArray.isSome() &&
          mLastCommitStringTextRectArray->IsOverlappingWith(aOffset, aLength)) {
        aUnionTextRect =
            mLastCommitStringTextRectArray->GetUnionRectAsFarAsPossible(
                aOffset, aLength, aRoundToExistingOffset);
      } else {
        aUnionTextRect.SetEmpty();
      }

      if (mTextRectArray.isSome() &&
          ((aRoundToExistingOffset && mTextRectArray->HasRects()) ||
           mTextRectArray->IsOverlappingWith(aOffset, aLength))) {
        aUnionTextRect =
            aUnionTextRect.Union(mTextRectArray->GetUnionRectAsFarAsPossible(
                aOffset, aLength, aRoundToExistingOffset));
      }

      if (!aOffset) {
        aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
      }
      if (mSelection.isSome()) {
        if (aOffset <= mSelection->mAnchor &&
            mSelection->mAnchor < endOffset.value()) {
          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->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=%s, mTextRectArray=%s, mSelection=%s, mFirstCharRect=%s",
               this, aOffset, GetBoolName(aRoundToExistingOffset),
               ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
               ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str()));

      if (mCaret.isSome() && mCaret->mOffset == aOffset) {
        aCaretRect = mCaret->mRect;
        return true;
      }

      // Guess caret rect from the text rect if it's stored.
      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, aRoundToExistingOffset, aCaretRect)) {
          aCaretRect.SetEmpty();
          return false;
        }

        if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
          aCaretRect.MoveToY(aCaretRect.YMost());
        } else {
          // XXX bidi-unaware.
          aCaretRect.MoveToX(aCaretRect.XMost());
        }
      }

      // XXX This is not bidi aware because we don't cache each character's
      //     direction.  However, this is usually used by IME, so, assuming the
      //     character is in LRT context must not cause any problem.
      if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
        aCaretRect.SetHeight(mCaret.isSome() ? mCaret->mRect.Height() : 1);
      } else {
        aCaretRect.SetWidth(mCaret.isSome() ? mCaret->mRect.Width() : 1);
      }
      return true;
    }

    bool ContentCacheInParent::OnCompositionEvent(
        const WidgetCompositionEvent& aEvent) {
      MOZ_LOG(
          sContentCacheLog, LogLevel::Info,
          ("0x%p OnCompositionEvent(aEvent={ "
           "mMessage=%s, mData=\"%s\" (Length()=%u), mRanges->Length()=%zu }), "
           "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
           "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
           "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
           this, ToChar(aEvent.mMessage),
           PrintStringDetail(aEvent.mData,
                             PrintStringDetail::kMaxLengthForCompositionString)
               .get(),
           aEvent.mData.Length(), aEvent.mRanges ? aEvent.mRanges->Length() : 0,
           mPendingEventsNeedingAck, GetBoolName(mWidgetHasComposition),
           mPendingCompositionCount, mPendingCommitCount,
           GetBoolName(mIsChildIgnoringCompositionEvents), mCommitStringByRequest));

    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
      mDispatchedEventMessages.AppendElement(aEvent.mMessage);
    #endif  // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED

      // We must be able to simulate the selection because
      // we might not receive selection updates in time
      if (!mWidgetHasComposition) {
        if (mCompositionStartInChild.isSome()) {
          // If there is pending composition in the remote process, let's use
          // its start offset temporarily because this stores a lot of information
          // around it and the user must look around there, so, showing some UI
          // around it must make sense.
          mCompositionStart = mCompositionStartInChild;
        } else {
          mCompositionStart =
              Some(mSelection.isSome() ? mSelection->StartOffset() : 0);
        }
        MOZ_ASSERT(aEvent.mMessage == eCompositionStart);
        MOZ_RELEASE_ASSERT(mPendingCompositionCount < UINT8_MAX);
        mPendingCompositionCount++;
      }

      mWidgetHasComposition = !aEvent.CausesDOMCompositionEndEvent();

      if (!mWidgetHasComposition) {
        // mCompositionStart will be reset when commit event is completely handled
        // in the remote process.
        if (mPendingCompositionCount == 1) {
          mPendingCommitLength = aEvent.mData.Length();
        }
        mPendingCommitCount++;
      } else if (aEvent.mMessage != eCompositionStart) {
        mCompositionString = aEvent.mData;
      }

      // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
      // widget usually sends a eCompositionChange and/or eCompositionCommit event
      // to finalize or clear the composition, respectively.  In this time,
      // we need to intercept all composition events here and pass the commit
      // string for returning to the remote process as a result of
      // RequestIMEToCommitComposition().  Then, eCommitComposition event will
      // be dispatched with the committed string in the remote process internally.
      if (mCommitStringByRequest) {
        if (aEvent.mMessage == eCompositionCommitAsIs) {
          *mCommitStringByRequest = mCompositionString;
        } else {
          MOZ_ASSERT(aEvent.mMessage == eCompositionChange ||
                     aEvent.mMessage == eCompositionCommit);
          *mCommitStringByRequest = aEvent.mData;
        }
        // We need to wait eCompositionCommitRequestHandled from the remote process
        // in this case.  Therefore, mPendingEventsNeedingAck needs to be
        // incremented here.  Additionally, we stop sending eCompositionCommit(AsIs)
        // event.  Therefore, we need to decrement mPendingCommitCount which has
        // been incremented above.
        if (!mWidgetHasComposition) {
          mPendingEventsNeedingAck++;
          MOZ_DIAGNOSTIC_ASSERT(mPendingCommitCount);
          if (mPendingCommitCount) {
            mPendingCommitCount--;
          }
        }
        return false;
      }

      mPendingEventsNeedingAck++;
      return true;
    }

    void ContentCacheInParent::OnSelectionEvent(
        const WidgetSelectionEvent& aSelectionEvent) {
      MOZ_LOG(
          sContentCacheLog, LogLevel::Info,
          ("0x%p OnSelectionEvent(aEvent={ "
           "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
           "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
           "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
           "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
           "mIsChildIgnoringCompositionEvents=%s",
           this, ToChar(aSelectionEvent.mMessage), aSelectionEvent.mOffset,
           aSelectionEvent.mLength, GetBoolName(aSelectionEvent.mReversed),
           GetBoolName(aSelectionEvent.mExpandToClusterBoundary),
           GetBoolName(aSelectionEvent.mUseNativeLineBreak),
           mPendingEventsNeedingAck, GetBoolName(mWidgetHasComposition),
           mPendingCompositionCount, mPendingCommitCount,
           GetBoolName(mIsChildIgnoringCompositionEvents)));

    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
      mDispatchedEventMessages.AppendElement(aSelectionEvent.mMessage);
    #endif  // MOZ_DIAGNOSTIC_ASSERT_ENABLED

      mPendingEventsNeedingAck++;
    }

    void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
                                                        EventMessage aMessage) {
      // This is called when the child process receives WidgetCompositionEvent or
      // WidgetSelectionEvent.

      MOZ_LOG(
          sContentCacheLog, LogLevel::Info,
          ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, "
           "aMessage=%s), mPendingEventsNeedingAck=%u, "
           "mWidgetHasComposition=%s, mPendingCompositionCount=%" PRIu8 ", "
           "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s",
           this, aWidget, ToChar(aMessage), mPendingEventsNeedingAck,
           GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
           mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents)));

    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
      mReceivedEventMessages.AppendElement(aMessage);
    #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED

      bool isCommittedInChild =
          // Commit requester in the remote process has committed the composition.
          aMessage == eCompositionCommitRequestHandled ||
          // The commit event has been handled normally in the remote process.
          (!mIsChildIgnoringCompositionEvents &&
           WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage));

      if (isCommittedInChild) {
    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
        if (mPendingCompositionCount == 1) {
          RemoveUnnecessaryEventMessageLog();
        }
    #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED

        if (NS_WARN_IF(!mPendingCompositionCount)) {
    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
          nsPrintfCString info(
              "\nThere is no pending composition but received %s "
              "message from the remote child\n\n",
              ToChar(aMessage));
          AppendEventMessageLog(info);
          CrashReporter::AppendAppNotesToCrashReport(info);
          MOZ_DIAGNOSTIC_ASSERT(
              false, "No pending composition but received unexpected commit event");
    #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED

          // Prevent odd behavior in release channel.
          mPendingCompositionCount = 1;
        }

        mPendingCompositionCount--;

        // Forget composition string only when the latest composition string is
        // handled in the remote process because if there is 2 or more pending
        // composition, this value shouldn't be referred.
        if (!mPendingCompositionCount) {
          mCompositionString.Truncate();
        }

        // Forget pending commit string length if it's handled in the remote
        // process.  Note that this doesn't care too old composition's commit
        // string because in such case, we cannot return proper information
        // to IME synchornously.
        mPendingCommitLength = 0;
      }

      if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage)) {
        // After the remote process receives eCompositionCommit(AsIs) event,
        // it'll restart to handle composition events.
        mIsChildIgnoringCompositionEvents = false;

        if (NS_WARN_IF(!mPendingCommitCount)) {
    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
          nsPrintfCString info(
              "\nThere is no pending comment events but received "
              "%s message from the remote child\n\n",
              ToChar(aMessage));
          AppendEventMessageLog(info);
          CrashReporter::AppendAppNotesToCrashReport(info);
          MOZ_DIAGNOSTIC_ASSERT(
              false,
              "No pending commit events but received unexpected commit event");
    #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED

          // Prevent odd behavior in release channel.
          mPendingCommitCount = 1;
        }

        mPendingCommitCount--;
      } else if (aMessage == eCompositionCommitRequestHandled &&
                 mPendingCommitCount) {
        // If the remote process commits composition synchronously after
        // requesting commit composition and we've already sent commit composition,
        // it starts to ignore following composition events until receiving
        // eCompositionStart event.
        mIsChildIgnoringCompositionEvents = true;
      }

      // If neither widget (i.e., IME) nor the remote process has composition,
      // now, we can forget composition string informations.
      if (!mWidgetHasComposition && !mPendingCompositionCount &&
          !mPendingCommitCount) {
        mCompositionStart.reset();
      }

      if (NS_WARN_IF(!mPendingEventsNeedingAck)) {
    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
        nsPrintfCString info(
            "\nThere is no pending events but received %s "
            "message from the remote child\n\n",
            ToChar(aMessage));
        AppendEventMessageLog(info);
        CrashReporter::AppendAppNotesToCrashReport(info);
    #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
        MOZ_DIAGNOSTIC_ASSERT(
            false, "No pending event message but received unexpected event");
        mPendingEventsNeedingAck = 1;
      }
      if (--mPendingEventsNeedingAck) {
        return;
      }

      FlushPendingNotifications(aWidget);
    }

    bool ContentCacheInParent::RequestIMEToCommitComposition(
        nsIWidget* aWidget, bool aCancel, nsAString& aCommittedString) {
      MOZ_LOG(
          sContentCacheLog, LogLevel::Info,
          ("0x%p RequestToCommitComposition(aWidget=%p, "
           "aCancel=%s), mPendingCompositionCount=%" PRIu8 ", "
           "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s, "
           "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
           "mWidgetHasComposition=%s, mCommitStringByRequest=%p",
           this, aWidget, GetBoolName(aCancel), mPendingCompositionCount,
           mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents),
           GetBoolName(
               IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)),
           GetBoolName(mWidgetHasComposition), mCommitStringByRequest));

      MOZ_ASSERT(!mCommitStringByRequest);

      // If there are 2 or more pending compositions, we already sent
      // eCompositionCommit(AsIs) to the remote process.  So, this request is
      // too late for IME.  The remote process should wait following
      // composition events for cleaning up TextComposition and handle the
      // request as it's handled asynchronously.
      if (mPendingCompositionCount > 1) {
    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
        mRequestIMEToCommitCompositionResults.AppendElement(
            RequestIMEToCommitCompositionResult::eToOldCompositionReceived);
    #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
        return false;
      }

      // If there is no pending composition, we may have already sent
      // eCompositionCommit(AsIs) event for the active composition.  If so, the
      // remote process will receive composition events which causes cleaning up
      // TextComposition.  So, this shouldn't do nothing and TextComposition
      // should handle the request as it's handled asynchronously.
      // XXX Perhaps, this is wrong because TextComposition in child process
      //     may commit the composition with current composition string in the
      //     remote process.  I.e., it may be different from actual commit string
      //     which user typed.  So, perhaps, we should return true and the commit
      //     string.
      if (mPendingCommitCount) {
    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
        mRequestIMEToCommitCompositionResults.AppendElement(
            RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived);
    #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
        return false;
      }

      // If BrowserParent which has IME focus was already changed to different one,
      // the request shouldn't be sent to IME because it's too late.
      if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)) {
        // Use the latest composition string which may not be handled in the
        // remote process for avoiding data loss.
    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
        mRequestIMEToCommitCompositionResults.AppendElement(
            RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur);
    #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
        aCommittedString = mCompositionString;
        // After we return true from here, i.e., without actually requesting IME
        // to commit composition, we will receive eCompositionCommitRequestHandled
        // pseudo event message from the remote process.  So, we need to increment
        // mPendingEventsNeedingAck here.
        mPendingEventsNeedingAck++;
        return true;
      }

      RefPtr composition =
          IMEStateManager::GetTextCompositionFor(aWidget);
      if (NS_WARN_IF(!composition)) {
        MOZ_LOG(sContentCacheLog, LogLevel::Warning,
                ("  0x%p RequestToCommitComposition(), "
                 "does nothing due to no composition",
                 this));
    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
        mRequestIMEToCommitCompositionResults.AppendElement(
            RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition);
    #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
        return false;
      }

      mCommitStringByRequest = &aCommittedString;

      // Request commit or cancel composition with TextComposition because we may
      // have already requested to commit or cancel the composition or we may
      // have already received eCompositionCommit(AsIs) event.  Those status are
      // managed by composition.  So, if we don't request commit composition,
      // we should do nothing with native IME here.
      composition->RequestToCommit(aWidget, aCancel);

      mCommitStringByRequest = nullptr;

      MOZ_LOG(
          sContentCacheLog, LogLevel::Info,
          ("  0x%p RequestToCommitComposition(), "
           "mWidgetHasComposition=%s, the composition %s committed synchronously",
           this, GetBoolName(mWidgetHasComposition),
           composition->Destroyed() ? "WAS" : "has NOT been"));

      if (!composition->Destroyed()) {
        // When the composition isn't committed synchronously, the remote process's
        // TextComposition instance will synthesize commit events and wait to
        // receive delayed composition events.  When TextComposition instances both
        // in this process and the remote process will be destroyed when delayed
        // composition events received. TextComposition instance in the parent
        // process will dispatch following composition events and be destroyed
        // normally. On the other hand, TextComposition instance in the remote
        // process won't dispatch following composition events and will be
        // destroyed by IMEStateManager::DispatchCompositionEvent().
    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
        mRequestIMEToCommitCompositionResults.AppendElement(
            RequestIMEToCommitCompositionResult::eHandledAsynchronously);
    #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
        return false;
      }

      // When the composition is committed synchronously, the commit string will be
      // returned to the remote process.  Then, PuppetWidget will dispatch
      // eCompositionCommit event with the returned commit string (i.e., the value
      // is aCommittedString of this method) and that causes destroying
      // TextComposition instance in the remote process (Note that TextComposition
      // instance in this process was already destroyed).
    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
      mRequestIMEToCommitCompositionResults.AppendElement(
          RequestIMEToCommitCompositionResult::eHandledSynchronously);
    #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
      return true;
    }

    void ContentCacheInParent::MaybeNotifyIME(
        nsIWidget* aWidget, const IMENotification& aNotification) {
      if (!mPendingEventsNeedingAck) {
        IMEStateManager::NotifyIME(aNotification, aWidget, &mBrowserParent);
        return;
      }

      switch (aNotification.mMessage) {
        case NOTIFY_IME_OF_SELECTION_CHANGE:
          mPendingSelectionChange.MergeWith(aNotification);
          break;
        case NOTIFY_IME_OF_TEXT_CHANGE:
          mPendingTextChange.MergeWith(aNotification);
          break;
        case NOTIFY_IME_OF_POSITION_CHANGE:
          mPendingLayoutChange.MergeWith(aNotification);
          break;
        case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
          mPendingCompositionUpdate.MergeWith(aNotification);
          break;
        default:
          MOZ_CRASH("Unsupported notification");
          break;
      }
    }

    void ContentCacheInParent::FlushPendingNotifications(nsIWidget* aWidget) {
      MOZ_ASSERT(!mPendingEventsNeedingAck);

      // If the BrowserParent's widget has already gone, this can do nothing since
      // widget is necessary to notify IME of something.
      if (!aWidget) {
        return;
      }

      // New notifications which are notified during flushing pending notifications
      // should be merged again.
      mPendingEventsNeedingAck++;

      nsCOMPtr widget = aWidget;

      // First, text change notification should be sent because selection change
      // notification notifies IME of current selection range in the latest content.
      // So, IME may need the latest content before that.
      if (mPendingTextChange.HasNotification()) {
        IMENotification notification(mPendingTextChange);
        if (!widget->Destroyed()) {
          mPendingTextChange.Clear();
          IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
        }
      }

      if (mPendingSelectionChange.HasNotification()) {
        IMENotification notification(mPendingSelectionChange);
        if (!widget->Destroyed()) {
          mPendingSelectionChange.Clear();
          IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
        }
      }

      // Layout change notification should be notified after selection change
      // notification because IME may want to query position of new caret position.
      if (mPendingLayoutChange.HasNotification()) {
        IMENotification notification(mPendingLayoutChange);
        if (!widget->Destroyed()) {
          mPendingLayoutChange.Clear();
          IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
        }
      }

      // Finally, send composition update notification because it notifies IME of
      // finishing handling whole sending events.
      if (mPendingCompositionUpdate.HasNotification()) {
        IMENotification notification(mPendingCompositionUpdate);
        if (!widget->Destroyed()) {
          mPendingCompositionUpdate.Clear();
          IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
        }
      }

      if (!--mPendingEventsNeedingAck && !widget->Destroyed() &&
          (mPendingTextChange.HasNotification() ||
           mPendingSelectionChange.HasNotification() ||
           mPendingLayoutChange.HasNotification() ||
           mPendingCompositionUpdate.HasNotification())) {
        FlushPendingNotifications(widget);
      }
    }

    #if MOZ_DIAGNOSTIC_ASSERT_ENABLED

    void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
      bool foundLastCompositionStart = false;
      for (size_t i = mDispatchedEventMessages.Length(); i > 1; i--) {
        if (mDispatchedEventMessages[i - 1] != eCompositionStart) {
          continue;
        }
        if (!foundLastCompositionStart) {
          // Find previous eCompositionStart of the latest eCompositionStart.
          foundLastCompositionStart = true;
          continue;
        }
        // Remove the messages before the last 2 sets of composition events.
        mDispatchedEventMessages.RemoveElementsAt(0, i - 1);
        break;
      }
      uint32_t numberOfCompositionCommitRequestHandled = 0;
      foundLastCompositionStart = false;
      for (size_t i = mReceivedEventMessages.Length(); i > 1; i--) {
        if (mReceivedEventMessages[i - 1] == eCompositionCommitRequestHandled) {
          numberOfCompositionCommitRequestHandled++;
        }
        if (mReceivedEventMessages[i - 1] != eCompositionStart) {
          continue;
        }
        if (!foundLastCompositionStart) {
          // Find previous eCompositionStart of the latest eCompositionStart.
          foundLastCompositionStart = true;
          continue;
        }
        // Remove the messages before the last 2 sets of composition events.
        mReceivedEventMessages.RemoveElementsAt(0, i - 1);
        break;
      }

      if (!numberOfCompositionCommitRequestHandled) {
        // If there is no eCompositionCommitRequestHandled in
        // mReceivedEventMessages, we don't need to store log of
        // RequestIMEToCommmitComposition().
        mRequestIMEToCommitCompositionResults.Clear();
      } else {
        // We need to keep all reason of eCompositionCommitRequestHandled, which
        // is sent when mRequestIMEToCommitComposition() returns true.
        // So, we can discard older log than the first
        // eCompositionCommitRequestHandled in mReceivedEventMessages.
        for (size_t i = mRequestIMEToCommitCompositionResults.Length(); i > 1;
             i--) {
          if (mRequestIMEToCommitCompositionResults[i - 1] ==
                  RequestIMEToCommitCompositionResult::
                      eReceivedAfterBrowserParentBlur ||
              mRequestIMEToCommitCompositionResults[i - 1] ==
                  RequestIMEToCommitCompositionResult::eHandledSynchronously) {
            --numberOfCompositionCommitRequestHandled;
            if (!numberOfCompositionCommitRequestHandled) {
              mRequestIMEToCommitCompositionResults.RemoveElementsAt(0, i - 1);
              break;
            }
          }
        }
      }
    }

    void ContentCacheInParent::AppendEventMessageLog(nsACString& aLog) const {
      aLog.AppendLiteral("Dispatched Event Message Log:\n");
      for (EventMessage message : mDispatchedEventMessages) {
        aLog.AppendLiteral("  ");
        aLog.Append(ToChar(message));
        aLog.AppendLiteral("\n");
      }
      aLog.AppendLiteral("\nReceived Event Message Log:\n");
      for (EventMessage message : mReceivedEventMessages) {
        aLog.AppendLiteral("  ");
        aLog.Append(ToChar(message));
        aLog.AppendLiteral("\n");
      }
      aLog.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
      for (RequestIMEToCommitCompositionResult result :
           mRequestIMEToCommitCompositionResults) {
        aLog.AppendLiteral("  ");
        aLog.Append(ToReadableText(result));
        aLog.AppendLiteral("\n");
      }
      aLog.AppendLiteral("\n");
    }

    #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED

    /*****************************************************************************
     * mozilla::ContentCache::TextRectArray
     *****************************************************************************/

    LayoutDeviceIntRect ContentCache::TextRectArray::GetRect(
        uint32_t aOffset) const {
      LayoutDeviceIntRect rect;
      if (IsOffsetInRange(aOffset)) {
        rect = mRects[aOffset - mStart];
      }
      return rect;
    }

    LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRect(
        uint32_t aOffset, uint32_t aLength) const {
      LayoutDeviceIntRect rect;
      if (!IsRangeCompletelyInRange(aOffset, aLength)) {
        return rect;
      }
      for (uint32_t i = 0; i < aLength; i++) {
        rect = rect.Union(mRects[aOffset - mStart + i]);
      }
      return rect;
    }

    LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
        uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const {
      LayoutDeviceIntRect rect;
      if (!HasRects() ||
          (!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;
      }
      if (NS_WARN_IF(endOffset < startOffset)) {
        return rect;
      }
      for (uint32_t i = 0; i < endOffset - startOffset; i++) {
        rect = rect.Union(mRects[startOffset - mStart + i]);
      }
      return rect;
    }

    }  // namespace mozilla

## Minimal test - lines (29, 34)

path: .spaces[0].spaces[1].metrics.cognitive.sum
old: 0.0
new: 1.0
path: .spaces[0].spaces[1].metrics.cognitive.average
old: null
new: 1.0
path: .spaces[0].spaces[1].metrics.nom.functions
old: 0.0
new: 1.0
path: .spaces[0].spaces[1].metrics.nom.total
old: 0.0
new: 1.0
path: .spaces[0].spaces[1].metrics.nexits.sum
old: 0.0
new: 2.0
path: .spaces[0].spaces[1].metrics.nexits.average
old: null
new: 2.0
path: .spaces[0].spaces[1].metrics.mi.mi_visual_studio
old: 78.45287306850658
new: 69.07758435397778
path: .spaces[0].spaces[1].metrics.mi.mi_original
old: 134.15441294714626
new: 118.122669245302
path: .spaces[0].spaces[1].metrics.mi.mi_sei
old: 118.14851385841868
new: 94.91777686356068
path: .spaces[0].spaces[1].metrics.halstead.effort
old: 13.931568569324174
new: 539.1509021939755
path: .spaces[0].spaces[1].metrics.halstead.level
old: 1.0
new: 0.16666666666666666
path: .spaces[0].spaces[1].metrics.halstead.volume
old: 13.931568569324174
new: 89.85848369899593
path: .spaces[0].spaces[1].metrics.halstead.n1
old: 2.0
new: 9.0
path: .spaces[0].spaces[1].metrics.halstead.vocabulary
old: 5.0
new: 15.0
path: .spaces[0].spaces[1].metrics.halstead.difficulty
old: 1.0
new: 6.0
path: .spaces[0].spaces[1].metrics.halstead.N2
old: 3.0
new: 8.0
path: .spaces[0].spaces[1].metrics.halstead.estimated_program_length
old: 6.754887502163468
new: 44.039100017307746
path: .spaces[0].spaces[1].metrics.halstead.N1
old: 3.0
new: 15.0
path: .spaces[0].spaces[1].metrics.halstead.length
old: 6.0
new: 23.0
path: .spaces[0].spaces[1].metrics.halstead.bugs
old: 0.0019299471801733172
new: 0.02208101178140136
path: .spaces[0].spaces[1].metrics.halstead.purity_ratio
old: 1.1258145836939113
new: 1.91474347901338
path: .spaces[0].spaces[1].metrics.halstead.time
old: 0.7739760316291208
new: 29.95282789966531
path: .spaces[0].spaces[1].metrics.halstead.n2
old: 3.0
new: 6.0
path: .spaces[0].spaces[1].metrics.nargs.average
old: null
new: 0.0
path: .spaces[0].spaces[1].metrics.cyclomatic.average
old: 1.0
new: 2.0
path: .spaces[0].spaces[1].metrics.cyclomatic.sum
old: 3.0
new: 2.0
path: .spaces[0].spaces[1].metrics.loc.sloc
old: 4.0
new: 6.0
path: .spaces[0].spaces[1].metrics.loc.ploc
old: 4.0
new: 6.0
path: .spaces[0].spaces[1].metrics.loc.lloc
old: 0.0
new: 3.0

### Code

    static const char* GetNotificationName(const IMENotification* aNotification) {
      if (!aNotification) {
        return "Not notification";
      }
      return ToChar(aNotification->mMessage);
    }

## Minimal test - lines (27, 27)

path: .spaces[0].spaces[0].metrics.mi.mi_sei
old: 109.74001943270554
new: 141.16357265873245
path: .spaces[0].spaces[0].metrics.mi.mi_original
old: 128.25551259473795
new: 150.1778122134763
path: .spaces[0].spaces[0].metrics.mi.mi_visual_studio
old: 75.00322373961284
new: 87.82328199618497
path: .spaces[0].spaces[0].metrics.cognitive.average
old: null
new: 0.0
path: .spaces[0].spaces[0].metrics.halstead.difficulty
old: 1.0
new: 5.0
path: .spaces[0].spaces[0].metrics.halstead.n1
old: 2.0
new: 8.0
path: .spaces[0].spaces[0].metrics.halstead.N2
old: 4.0
new: 5.0
path: .spaces[0].spaces[0].metrics.halstead.bugs
old: 0.002511355733085703
new: 0.013261740231564564
path: .spaces[0].spaces[0].metrics.halstead.level
old: 1.0
new: 0.2
path: .spaces[0].spaces[0].metrics.halstead.purity_ratio
old: 1.25
new: 2.2857142857142856
path: .spaces[0].spaces[0].metrics.halstead.time
old: 1.148872222542736
new: 13.941520836137832
path: .spaces[0].spaces[0].metrics.halstead.estimated_program_length
old: 10.0
new: 32.0
path: .spaces[0].spaces[0].metrics.halstead.N1
old: 4.0
new: 9.0
path: .spaces[0].spaces[0].metrics.halstead.volume
old: 20.67970000576925
new: 50.18947501009619
path: .spaces[0].spaces[0].metrics.halstead.effort
old: 20.67970000576925
new: 250.94737505048096
path: .spaces[0].spaces[0].metrics.halstead.length
old: 8.0
new: 14.0
path: .spaces[0].spaces[0].metrics.halstead.vocabulary
old: 6.0
new: 12.0
path: .spaces[0].spaces[0].metrics.nom.functions
old: 0.0
new: 1.0
path: .spaces[0].spaces[0].metrics.nom.total
old: 0.0
new: 1.0
path: .spaces[0].spaces[0].metrics.nexits.average
old: null
new: 1.0
path: .spaces[0].spaces[0].metrics.nexits.sum
old: 0.0
new: 1.0
path: .spaces[0].spaces[0].metrics.nargs.average
old: null
new: 0.0
path: .spaces[0].spaces[0].metrics.loc.sloc
old: 5.0
new: 1.0
path: .spaces[0].spaces[0].metrics.loc.ploc
old: 5.0
new: 1.0
path: .spaces[0].spaces[0].metrics.loc.lloc
old: 0.0
new: 1.0
path: .spaces[0].spaces[0].metrics.cyclomatic.sum
old: 4.0
new: 2.0
path: .spaces[0].spaces[0].metrics.cyclomatic.average
old: 1.0
new: 2.0

### Code

    static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }

## Minimal test - lines (46, 58)

path: .spaces[0].spaces[2].metrics.nom.functions
old: 2.0
new: 1.0
path: .spaces[0].spaces[2].metrics.nom.total
old: 2.0
new: 1.0
path: .spaces[0].spaces[2].metrics.cyclomatic.sum
old: 4.0
new: 1.0
path: .spaces[0].spaces[2].metrics.loc.lloc
old: 2.0
new: 10.0
path: .spaces[0].spaces[2].metrics.loc.cloc
old: 15.0
new: 0.0
path: .spaces[0].spaces[2].metrics.loc.blank
old: 19.0
new: 1.0
path: .spaces[0].spaces[2].metrics.loc.ploc
old: 48.0
new: 12.0
path: .spaces[0].spaces[2].metrics.loc.sloc
old: 82.0
new: 13.0
path: .spaces[0].spaces[2].metrics.halstead.N2
old: 94.0
new: 23.0
path: .spaces[0].spaces[2].metrics.halstead.purity_ratio
old: 2.1821383743206333
new: 1.461284471404246
path: .spaces[0].spaces[2].metrics.halstead.time
old: 749.866299692106
new: 73.57666130650732
path: .spaces[0].spaces[2].metrics.halstead.vocabulary
old: 88.0
new: 24.0
path: .spaces[0].spaces[2].metrics.halstead.level
old: 0.11246200607902736
new: 0.2111801242236025
path: .spaces[0].spaces[2].metrics.halstead.n2
old: 74.0
new: 17.0
path: .spaces[0].spaces[2].metrics.halstead.bugs
old: 0.1889656965725519
new: 0.04019948289974308
path: .spaces[0].spaces[2].metrics.halstead.difficulty
old: 8.891891891891891
new: 4.735294117647059
path: .spaces[0].spaces[2].metrics.halstead.effort
old: 13497.593394457908
new: 1324.3799035171317
path: .spaces[0].spaces[2].metrics.halstead.estimated_program_length
old: 512.8025179653488
new: 89.138352755659
path: .spaces[0].spaces[2].metrics.halstead.n1
old: 14.0
new: 7.0
path: .spaces[0].spaces[2].metrics.halstead.volume
old: 1517.966430379765
new: 279.68271254399053
path: .spaces[0].spaces[2].metrics.halstead.N1
old: 141.0
new: 38.0
path: .spaces[0].spaces[2].metrics.halstead.length
old: 235.0
new: 61.0
path: .spaces[0].spaces[2].metrics.nexits.average
old: 1.0
new: 0.0
path: .spaces[0].spaces[2].metrics.nexits.sum
old: 2.0
new: 0.0
path: .spaces[0].spaces[2].metrics.mi.mi_original
old: 60.60048860896819
new: 99.9228102950832
path: .spaces[0].spaces[2].metrics.mi.mi_sei
old: 42.892462241457075
new: 68.5591107517969
path: .spaces[0].spaces[2].metrics.mi.mi_visual_studio
old: 35.43888222746678
new: 58.434391985428775

### Code

    void ContentCacheInChild::Clear() {
      MOZ_LOG(sContentCacheLog, LogLevel::Info, ("0x%p Clear()", this));

      mCompositionStart.reset();
      mLastCommit.reset();
      mText.Truncate();
      mSelection.reset();
      mFirstCharRect.SetEmpty();
      mCaret.reset();
      mTextRectArray.reset();
      mLastCommitStringTextRectArray.reset();
      mEditorRect.SetEmpty();
    }