Bug 1354641 - adjust the value of spell check heuristics. r=masayuki,ehsan draft
authorEvelyn Hung <jj.evelyn@gmail.com>
Tue, 06 Jun 2017 18:13:51 +0800
changeset 593191 072cb9e4cb7e22225f0ad4ecebb9c18aa09f0a43
parent 592979 2a3a253806d129c0bb6f2b76bf75630457a24492
child 633046 5442c3487fbe933f447ba3a893bd608fc1fdc635
push id63625
push userbmo:ehung@mozilla.com
push dateTue, 13 Jun 2017 09:25:06 +0000
reviewersmasayuki, ehsan
bugs1354641
milestone56.0a1
Bug 1354641 - adjust the value of spell check heuristics. r=masayuki,ehsan This patch is mainly for adjusting the value of INLINESPELL_CHECK_TIMEOUT from 50ms to 1ms. The value means how long the main thread is blocked for spelling check, and 50ms is too long. It causes significant delays when a rich content document is loading, and the user tries to type immediately before spell checking is done. With the INLINESPELL_CHECK_TIMEOUT setting to 1ms, it's possible to be too short to less powerful machines. Therefore we add INLINESPELL_MINIMUM_WORDS_BEFORE_TIMEOUT to ensure at least N words were checked. MozReview-Commit-ID: 2PmAOWs5qjn
extensions/spellcheck/src/mozInlineSpellChecker.cpp
extensions/spellcheck/src/mozInlineSpellChecker.h
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -73,40 +73,36 @@
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 // Set to spew messages to the console about what is happening.
 //#define DEBUG_INLINESPELL
 
 // the number of milliseconds that we will take at once to do spellchecking
-#define INLINESPELL_CHECK_TIMEOUT 50
+#define INLINESPELL_CHECK_TIMEOUT 1
 
 // The number of words to check before we look at the time to see if
-// INLINESPELL_CHECK_TIMEOUT ms have elapsed. This prevents us from spending
-// too much time checking the clock. Note that misspelled words count for
-// more than one word in this calculation.
-#define INLINESPELL_TIMEOUT_CHECK_FREQUENCY 50
-
-// This number is the number of checked words a misspelled word counts for
-// when we're checking the time to see if the alloted time is up for
-// spellchecking. Misspelled words take longer to process since we have to
-// create a range, so they count more. The exact number isn't very important
-// since this just controls how often we check the current time.
-#define MISSPELLED_WORD_COUNT_PENALTY 4
+// INLINESPELL_CHECK_TIMEOUT ms have elapsed. This prevents us from getting
+// stuck and not moving forward because the INLINESPELL_CHECK_TIMEOUT might
+// be too short to a low-end machine.
+#define INLINESPELL_MINIMUM_WORDS_BEFORE_TIMEOUT 5
 
 // These notifications are broadcast when spell check starts and ends.  STARTED
 // must always be followed by ENDED.
 #define INLINESPELL_STARTED_TOPIC "inlineSpellChecker-spellCheck-started"
 #define INLINESPELL_ENDED_TOPIC "inlineSpellChecker-spellCheck-ended"
 
 static bool ContentIsDescendantOf(nsINode* aPossibleDescendant,
                                     nsINode* aPossibleAncestor);
 
-static const char kMaxSpellCheckSelectionSize[] = "extensions.spellcheck.inline.max-misspellings";
+static const char kMaxSpellCheckSelectionSize[] =
+  "extensions.spellcheck.inline.max-misspellings";
+static const PRTime kMaxSpellCheckTimeInUsec =
+  INLINESPELL_CHECK_TIMEOUT * PR_USEC_PER_MSEC;
 
 mozInlineSpellStatus::mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker)
     : mSpellChecker(aSpellChecker), mWordCount(0)
 {
 }
 
 // mozInlineSpellStatus::InitForEditorChange
 //
@@ -479,42 +475,42 @@ mozInlineSpellStatus::PositionToCollapse
   return NS_OK;
 }
 
 // mozInlineSpellResume
 
 class mozInlineSpellResume : public Runnable
 {
 public:
-  mozInlineSpellResume(const mozInlineSpellStatus& aStatus,
+  mozInlineSpellResume(UniquePtr<mozInlineSpellStatus>&& aStatus,
                        uint32_t aDisabledAsyncToken)
     : Runnable("mozInlineSpellResume")
     , mDisabledAsyncToken(aDisabledAsyncToken)
-    , mStatus(aStatus)
+    , mStatus(Move(aStatus))
   {
   }
 
   nsresult Post()
   {
     return NS_DispatchToMainThread(this);
   }
 
   NS_IMETHOD Run() override
   {
     // Discard the resumption if the spell checker was disabled after the
     // resumption was scheduled.
-    if (mDisabledAsyncToken == mStatus.mSpellChecker->mDisabledAsyncToken) {
-      mStatus.mSpellChecker->ResumeCheck(&mStatus);
+    if (mDisabledAsyncToken == mStatus->mSpellChecker->mDisabledAsyncToken) {
+      mStatus->mSpellChecker->ResumeCheck(Move(mStatus));
     }
     return NS_OK;
   }
 
 private:
   uint32_t mDisabledAsyncToken;
-  mozInlineSpellStatus mStatus;
+  UniquePtr<mozInlineSpellStatus> mStatus;
 };
 
 // Used as the nsIEditorSpellCheck::InitSpellChecker callback.
 class InitEditorSpellCheckCallback final : public nsIEditorSpellCheckCallback
 {
   ~InitEditorSpellCheckCallback() {}
 public:
   NS_DECL_ISUPPORTS
@@ -695,17 +691,18 @@ mozInlineSpellChecker::CanEnableInlineSp
 void // static
 mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking()
 {
   gCanEnableSpellChecking = SpellCheck_Uninitialized;
 }
 
 // mozInlineSpellChecker::RegisterEventListeners
 //
-//    The inline spell checker listens to mouse events and keyboard navigation+ //    events.
+//    The inline spell checker listens to mouse events and keyboard navigation
+//    events.
 
 nsresult
 mozInlineSpellChecker::RegisterEventListeners()
 {
   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
   NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
 
   editor->AddEditActionListener(this);
@@ -896,24 +893,25 @@ mozInlineSpellChecker::SpellCheckAfterEd
   // the anchor node is the position of the caret
   nsCOMPtr<nsIDOMNode> anchorNode;
   rv = aSelection->GetAnchorNode(getter_AddRefs(anchorNode));
   NS_ENSURE_SUCCESS(rv, rv);
   int32_t anchorOffset;
   rv = aSelection->GetAnchorOffset(&anchorOffset);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mozInlineSpellStatus status(this);
-  rv = status.InitForEditorChange((EditAction)aAction,
-                                  anchorNode, anchorOffset,
-                                  aPreviousSelectedNode, aPreviousSelectedOffset,
-                                  aStartNode, aStartOffset,
-                                  aEndNode, aEndOffset);
+  auto status = MakeUnique<mozInlineSpellStatus>(this);
+  rv = status->InitForEditorChange((EditAction)aAction,
+                                   anchorNode, anchorOffset,
+                                   aPreviousSelectedNode,
+                                   aPreviousSelectedOffset,
+                                   aStartNode, aStartOffset,
+                                   aEndNode, aEndOffset);
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = ScheduleSpellCheck(status);
+  rv = ScheduleSpellCheck(Move(status));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // remember the current caret position after every change
   SaveCurrentSelectionPosition();
   return NS_OK;
 }
 
 // mozInlineSpellChecker::SpellCheckRange
@@ -926,21 +924,21 @@ mozInlineSpellChecker::SpellCheckRange(n
 {
   if (!mSpellCheck) {
     NS_WARNING_ASSERTION(
       mPendingSpellCheck,
       "Trying to spellcheck, but checking seems to be disabled");
     return NS_ERROR_NOT_INITIALIZED;
   }
 
-  mozInlineSpellStatus status(this);
+  auto status = MakeUnique<mozInlineSpellStatus>(this);
   nsRange* range = static_cast<nsRange*>(aRange);
-  nsresult rv = status.InitForRange(range);
+  nsresult rv = status->InitForRange(range);
   NS_ENSURE_SUCCESS(rv, rv);
-  return ScheduleSpellCheck(status);
+  return ScheduleSpellCheck(Move(status));
 }
 
 // mozInlineSpellChecker::GetMisspelledWord
 
 NS_IMETHODIMP
 mozInlineSpellChecker::GetMisspelledWord(nsIDOMNode *aNode, int32_t aOffset,
                                          nsIDOMRange **newword)
 {
@@ -997,72 +995,72 @@ NS_IMETHODIMP
 mozInlineSpellChecker::AddWordToDictionary(const nsAString &word)
 {
   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
 
   nsAutoString wordstr(word);
   nsresult rv = mSpellCheck->AddWordToDictionary(wordstr.get());
   NS_ENSURE_SUCCESS(rv, rv); 
 
-  mozInlineSpellStatus status(this);
-  rv = status.InitForSelection();
+  auto status = MakeUnique<mozInlineSpellStatus>(this);
+  rv = status->InitForSelection();
   NS_ENSURE_SUCCESS(rv, rv);
-  return ScheduleSpellCheck(status);
+  return ScheduleSpellCheck(Move(status));
 }
 
 //  mozInlineSpellChecker::RemoveWordFromDictionary
 
 NS_IMETHODIMP
 mozInlineSpellChecker::RemoveWordFromDictionary(const nsAString &word)
 {
   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
 
   nsAutoString wordstr(word);
   nsresult rv = mSpellCheck->RemoveWordFromDictionary(wordstr.get());
   NS_ENSURE_SUCCESS(rv, rv); 
   
-  mozInlineSpellStatus status(this);
-  rv = status.InitForRange(nullptr);
+  auto status = MakeUnique<mozInlineSpellStatus>(this);
+  rv = status->InitForRange(nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
-  return ScheduleSpellCheck(status);
+  return ScheduleSpellCheck(Move(status));
 }
 
 // mozInlineSpellChecker::IgnoreWord
 
 NS_IMETHODIMP
 mozInlineSpellChecker::IgnoreWord(const nsAString &word)
 {
   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
 
   nsAutoString wordstr(word);
   nsresult rv = mSpellCheck->IgnoreWordAllOccurrences(wordstr.get());
   NS_ENSURE_SUCCESS(rv, rv); 
 
-  mozInlineSpellStatus status(this);
-  rv = status.InitForSelection();
+  auto status = MakeUnique<mozInlineSpellStatus>(this);
+  rv = status->InitForSelection();
   NS_ENSURE_SUCCESS(rv, rv);
-  return ScheduleSpellCheck(status);
+  return ScheduleSpellCheck(Move(status));
 }
 
 // mozInlineSpellChecker::IgnoreWords
 
 NS_IMETHODIMP
 mozInlineSpellChecker::IgnoreWords(const char16_t **aWordsToIgnore,
                                    uint32_t aCount)
 {
   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
 
   // add each word to the ignore list and then recheck the document
   for (uint32_t index = 0; index < aCount; index++)
     mSpellCheck->IgnoreWordAllOccurrences(aWordsToIgnore[index]);
 
-  mozInlineSpellStatus status(this);
-  nsresult rv = status.InitForSelection();
+  auto status = MakeUnique<mozInlineSpellStatus>(this);
+  nsresult rv = status->InitForSelection();
   NS_ENSURE_SUCCESS(rv, rv);
-  return ScheduleSpellCheck(status);
+  return ScheduleSpellCheck(Move(status));
 }
 
 NS_IMETHODIMP mozInlineSpellChecker::WillCreateNode(const nsAString & aTag, nsIDOMNode *aParent, int32_t aPosition)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP mozInlineSpellChecker::DidCreateNode(const nsAString & aTag, nsIDOMNode *aNode, nsIDOMNode *aParent,
@@ -1239,20 +1237,20 @@ mozInlineSpellChecker::SpellCheckBetween
   nsresult rv = MakeSpellCheckRange(aStartNode, aStartOffset,
                                     aEndNode, aEndOffset,
                                     getter_AddRefs(range));
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (! range)
     return NS_OK; // range is empty: nothing to do
 
-  mozInlineSpellStatus status(this);
-  rv = status.InitForRange(range);
+  auto status = MakeUnique<mozInlineSpellStatus>(this);
+  rv = status->InitForRange(range);
   NS_ENSURE_SUCCESS(rv, rv);
-  return ScheduleSpellCheck(status);
+  return ScheduleSpellCheck(Move(status));
 }
 
 // mozInlineSpellChecker::ShouldSpellCheckNode
 //
 //    There are certain conditions when we don't want to spell check a node. In
 //    particular quotations, moz signatures, etc. This routine returns false
 //    for these cases.
 
@@ -1327,30 +1325,33 @@ mozInlineSpellChecker::ShouldSpellCheckN
 }
 
 // mozInlineSpellChecker::ScheduleSpellCheck
 //
 //    This is called by code to do the actual spellchecking. We will set up
 //    the proper structures for calls to DoSpellCheck.
 
 nsresult
-mozInlineSpellChecker::ScheduleSpellCheck(const mozInlineSpellStatus& aStatus)
+mozInlineSpellChecker::ScheduleSpellCheck(UniquePtr<mozInlineSpellStatus>&& aStatus)
 {
   if (mFullSpellCheckScheduled) {
     // Just ignore this; we're going to spell-check everything anyway
     return NS_OK;
   }
+  // Cache the value because we are going to move aStatus's ownership to
+  // the new created mozInlineSpellResume instance.
+  bool isFullSpellCheck = aStatus->IsFullSpellCheck();
 
   RefPtr<mozInlineSpellResume> resume =
-    new mozInlineSpellResume(aStatus, mDisabledAsyncToken);
+    new mozInlineSpellResume(Move(aStatus), mDisabledAsyncToken);
   NS_ENSURE_TRUE(resume, NS_ERROR_OUT_OF_MEMORY);
 
   nsresult rv = resume->Post();
   if (NS_SUCCEEDED(rv)) {
-    if (aStatus.IsFullSpellCheck()) {
+    if (isFullSpellCheck) {
       // We're going to check everything.  Suppress further spell-check attempts
       // until that happens.
       mFullSpellCheckScheduled = true;
     }
     ChangeNumPendingSpellChecks(1);
   }
   return rv;
 }
@@ -1363,18 +1364,17 @@ mozInlineSpellChecker::ScheduleSpellChec
 //    be removed from the selection.
 //
 //    FIXME-PERFORMANCE: This takes as long as it takes and is not resumable.
 //    Typically, checking this small amount of text is relatively fast, but
 //    for large numbers of words, a lag may be noticeable.
 
 nsresult
 mozInlineSpellChecker::DoSpellCheckSelection(mozInlineSpellWordUtil& aWordUtil,
-                                             Selection* aSpellCheckSelection,
-                                             mozInlineSpellStatus* aStatus)
+                                             Selection* aSpellCheckSelection)
 {
   nsresult rv;
 
   // clear out mNumWordsInSpellSelection since we'll be rebuilding the ranges.
   mNumWordsInSpellSelection = 0;
 
   // Since we could be modifying the ranges for the spellCheckSelection while
   // looping on the spell check selection, keep a separate array of range
@@ -1395,34 +1395,34 @@ mozInlineSpellChecker::DoSpellCheckSelec
   // provides better performance. By ensuring that no ranges need to be
   // removed in DoSpellCheck, we can save checking range inclusion which is
   // slow.
   aSpellCheckSelection->RemoveAllRanges();
 
   // We use this state object for all calls, and just update its range. Note
   // that we don't need to call FinishInit since we will be filling in the
   // necessary information.
-  mozInlineSpellStatus status(this);
-  rv = status.InitForRange(nullptr);
+  auto status = MakeUnique<mozInlineSpellStatus>(this);
+  rv = status->InitForRange(nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool doneChecking;
   for (int32_t idx = 0; idx < count; idx++) {
     // We can consider this word as "added" since we know it has no spell
     // check range over it that needs to be deleted. All the old ranges
     // were cleared above. We also need to clear the word count so that we
     // check all words instead of stopping early.
-    status.mRange = ranges[idx];
-    rv = DoSpellCheck(aWordUtil, aSpellCheckSelection, &status,
+    status->mRange = ranges[idx];
+    rv = DoSpellCheck(aWordUtil, aSpellCheckSelection, status,
                       &doneChecking);
     NS_ENSURE_SUCCESS(rv, rv);
     MOZ_ASSERT(doneChecking,
                "We gave the spellchecker one word, but it didn't finish checking?!?!");
 
-    status.mWordCount = 0;
+    status->mWordCount = 0;
   }
 
   return NS_OK;
 }
 
 // mozInlineSpellChecker::DoSpellCheck
 //
 //    This function checks words intersecting the given range, excluding those
@@ -1451,17 +1451,17 @@ mozInlineSpellChecker::DoSpellCheckSelec
 //      -> recheck all words in range except those in aNoCheckRange
 //
 //    If checking is complete, *aDoneChecking will be set. If there is more
 //    but we ran out of time, this will be false and the range will be
 //    updated with the stuff that still needs checking.
 
 nsresult mozInlineSpellChecker::DoSpellCheck(mozInlineSpellWordUtil& aWordUtil,
                                              Selection *aSpellCheckSelection,
-                                             mozInlineSpellStatus* aStatus,
+                                             const UniquePtr<mozInlineSpellStatus>& aStatus,
                                              bool* aDoneChecking)
 {
   *aDoneChecking = true;
 
   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
 
   // get the editor for ShouldSpellCheckNode, this may fail in reasonable
   // circumstances since the editor could have gone away
@@ -1500,39 +1500,57 @@ nsresult mozInlineSpellChecker::DoSpellC
   }
 
   // aWordUtil.SetPosition flushes pending notifications, check editor again.
   // XXX Uhhh, *we're* holding a strong ref to the editor.
   editor = do_QueryReferent(mEditor);
   if (! editor)
     return NS_ERROR_FAILURE;
 
-  int32_t wordsSinceTimeCheck = 0;
+  int32_t wordsChecked = 0;
   PRTime beginTime = PR_Now();
 
   nsAutoString wordText;
   RefPtr<nsRange> wordRange;
   bool dontCheckWord;
   while (NS_SUCCEEDED(aWordUtil.GetNextWord(wordText,
                                             getter_AddRefs(wordRange),
                                             &dontCheckWord)) &&
          wordRange) {
-    wordsSinceTimeCheck++;
 
     // get the range for the current word.
     nsINode *beginNode;
     nsINode *endNode;
     int32_t beginOffset, endOffset;
 
     ErrorResult erv;
     beginNode = wordRange->GetStartContainer(erv);
     endNode = wordRange->GetEndContainer(erv);
     beginOffset = wordRange->GetStartOffset(erv);
     endOffset = wordRange->GetEndOffset(erv);
 
+    // see if we've done enough words in this round and run out of time.
+    if (wordsChecked >= INLINESPELL_MINIMUM_WORDS_BEFORE_TIMEOUT &&
+        PR_Now() > PRTime(beginTime + kMaxSpellCheckTimeInUsec)) {
+      // stop checking, our time limit has been exceeded.
+      #ifdef DEBUG_INLINESPELL
+        printf("We have run out of the time, schedule next round.");
+      #endif
+      // move the range to encompass the stuff that needs checking.
+      nsresult rv = aStatus->mRange->SetStart(beginNode, beginOffset);
+      if (NS_FAILED(rv)) {
+        // The range might be unhappy because the beginning is after the
+        // end. This is possible when the requested end was in the middle
+        // of a word, just ignore this situation and assume we're done.
+        return NS_OK;
+      }
+      *aDoneChecking = false;
+      return NS_OK;
+    }
+
 #ifdef DEBUG_INLINESPELL
     printf("->Got word \"%s\"", NS_ConvertUTF16toUTF8(wordText).get());
     if (dontCheckWord)
       printf(" (not checking)");
     printf("\n");
 #endif
 
     // see if there is a spellcheck range that already intersects the word
@@ -1574,43 +1592,26 @@ nsresult mozInlineSpellChecker::DoSpellC
 
     // check spelling and add to selection if misspelled
     bool isMisspelled;
     aWordUtil.NormalizeWord(wordText);
     nsresult rv = mSpellCheck->CheckCurrentWordNoSuggest(wordText.get(), &isMisspelled);
     if (NS_FAILED(rv))
       continue;
 
+    wordsChecked++;
+
     if (isMisspelled) {
       // misspelled words count extra toward the max
-      wordsSinceTimeCheck += MISSPELLED_WORD_COUNT_PENALTY;
       AddRange(aSpellCheckSelection, wordRange);
 
       aStatus->mWordCount ++;
       if (aStatus->mWordCount >= mMaxMisspellingsPerCheck ||
-          SpellCheckSelectionIsFull())
+          SpellCheckSelectionIsFull()) {
         break;
-    }
-
-    // see if we've run out of time, only check every N words for perf
-    if (wordsSinceTimeCheck >= INLINESPELL_TIMEOUT_CHECK_FREQUENCY) {
-      wordsSinceTimeCheck = 0;
-      if (PR_Now() > PRTime(beginTime + INLINESPELL_CHECK_TIMEOUT * PR_USEC_PER_MSEC)) {
-        // stop checking, our time limit has been exceeded
-
-        // move the range to encompass the stuff that needs checking
-        rv = aStatus->mRange->SetStart(endNode, endOffset);
-        if (NS_FAILED(rv)) {
-          // The range might be unhappy because the beginning is after the
-          // end. This is possible when the requested end was in the middle
-          // of a word, just ignore this situation and assume we're done.
-          return NS_OK;
-        }
-        *aDoneChecking = false;
-        return NS_OK;
       }
     }
   }
 
   return NS_OK;
 }
 
 // An RAII helper that calls ChangeNumPendingSpellChecks on destruction.
@@ -1632,17 +1633,17 @@ private:
 };
 
 // mozInlineSpellChecker::ResumeCheck
 //
 //    Called by the resume event when it fires. We will try to pick up where
 //    the last resume left off.
 
 nsresult
-mozInlineSpellChecker::ResumeCheck(mozInlineSpellStatus* aStatus)
+mozInlineSpellChecker::ResumeCheck(UniquePtr<mozInlineSpellStatus>&& aStatus)
 {
   // Observers should be notified that spell check has ended only after spell
   // check is done below, but since there are many early returns in this method
   // and the number of pending spell checks must be decremented regardless of
   // whether the spell check actually happens, use this RAII object.
   AutoChangeNumPendingSpellChecks autoChangeNumPending(this, -1);
 
   if (aStatus->IsFullSpellCheck()) {
@@ -1691,23 +1692,23 @@ mozInlineSpellChecker::ResumeCheck(mozIn
 
   rv = aStatus->FinishInitOnEvent(wordUtil);
   NS_ENSURE_SUCCESS(rv, rv);
   if (! aStatus->mRange)
     return NS_OK; // empty range, nothing to do
 
   bool doneChecking = true;
   if (aStatus->mOp == mozInlineSpellStatus::eOpSelection)
-    rv = DoSpellCheckSelection(wordUtil, spellCheckSelection, aStatus);
+    rv = DoSpellCheckSelection(wordUtil, spellCheckSelection);
   else
     rv = DoSpellCheck(wordUtil, spellCheckSelection, aStatus, &doneChecking);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (! doneChecking)
-    rv = ScheduleSpellCheck(*aStatus);
+    rv = ScheduleSpellCheck(Move(aStatus));
   return rv;
 }
 
 // mozInlineSpellChecker::IsPointInSelection
 //
 //    Determines if a given (node,offset) point is inside the given
 //    selection. If so, the specific range of the selection that
 //    intersects is places in *aRange. (There may be multiple disjoint
@@ -1892,24 +1893,25 @@ mozInlineSpellChecker::HandleNavigationE
   nsCOMPtr<nsIDOMNode> currentAnchorNode = mCurrentSelectionAnchorNode;
   int32_t currentAnchorOffset = mCurrentSelectionOffset;
 
   // now remember the new focus position resulting from the event
   rv = SaveCurrentSelectionPosition();
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool shouldPost;
-  mozInlineSpellStatus status(this);
-  rv = status.InitForNavigation(aForceWordSpellCheck, aNewPositionOffset,
-                                currentAnchorNode, currentAnchorOffset,
-                                mCurrentSelectionAnchorNode, mCurrentSelectionOffset,
-                                &shouldPost);
+  auto status = MakeUnique<mozInlineSpellStatus>(this);
+  rv = status->InitForNavigation(aForceWordSpellCheck, aNewPositionOffset,
+                                 currentAnchorNode, currentAnchorOffset,
+                                 mCurrentSelectionAnchorNode,
+                                 mCurrentSelectionOffset,
+                                 &shouldPost);
   NS_ENSURE_SUCCESS(rv, rv);
   if (shouldPost) {
-    rv = ScheduleSpellCheck(status);
+    rv = ScheduleSpellCheck(Move(status));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP mozInlineSpellChecker::HandleEvent(nsIDOMEvent* aEvent)
 {
--- a/extensions/spellcheck/src/mozInlineSpellChecker.h
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.h
@@ -213,24 +213,23 @@ public:
   bool ShouldSpellCheckNode(nsIEditor* aEditor, nsINode *aNode);
 
   nsresult SpellCheckAfterChange(nsIDOMNode* aCursorNode, int32_t aCursorOffset,
                                  nsIDOMNode* aPreviousNode, int32_t aPreviousOffset,
                                  nsISelection* aSpellCheckSelection);
 
   // spell check the text contained within aRange, potentially scheduling
   // another check in the future if the time threshold is reached
-  nsresult ScheduleSpellCheck(const mozInlineSpellStatus& aStatus);
+  nsresult ScheduleSpellCheck(mozilla::UniquePtr<mozInlineSpellStatus>&& aStatus);
 
   nsresult DoSpellCheckSelection(mozInlineSpellWordUtil& aWordUtil,
-                                 mozilla::dom::Selection* aSpellCheckSelection,
-                                 mozInlineSpellStatus* aStatus);
+                                 mozilla::dom::Selection* aSpellCheckSelection);
   nsresult DoSpellCheck(mozInlineSpellWordUtil& aWordUtil,
                         mozilla::dom::Selection *aSpellCheckSelection,
-                        mozInlineSpellStatus* aStatus,
+                        const mozilla::UniquePtr<mozInlineSpellStatus>& aStatus,
                         bool* aDoneChecking);
 
   // helper routine to determine if a point is inside of the passed in selection.
   nsresult IsPointInSelection(nsISelection *aSelection,
                               nsIDOMNode *aNode,
                               int32_t aOffset,
                               nsIDOMRange **aRange);
 
@@ -248,17 +247,17 @@ public:
   // DOM and editor event registration helper routines
   nsresult RegisterEventListeners();
   nsresult UnregisterEventListeners();
   nsresult HandleNavigationEvent(bool aForceWordSpellCheck, int32_t aNewPositionOffset = 0);
 
   nsresult GetSpellCheckSelection(nsISelection ** aSpellCheckSelection);
   nsresult SaveCurrentSelectionPosition();
 
-  nsresult ResumeCheck(mozInlineSpellStatus* aStatus);
+  nsresult ResumeCheck(mozilla::UniquePtr<mozInlineSpellStatus>&& aStatus);
 
 protected:
   virtual ~mozInlineSpellChecker();
 
   // called when async nsIEditorSpellCheck methods complete
   nsresult EditorSpellCheckInited();
   nsresult CurrentDictionaryUpdated();