LCOV - code coverage report
Current view: top level - extensions/spellcheck/src - mozInlineSpellChecker.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 3 835 0.4 %
Date: 2017-07-14 16:53:18 Functions: 1 97 1.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set sw=2 ts=2 sts=2 tw=80: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : /**
       8             :  * This class is called by the editor to handle spellchecking after various
       9             :  * events. The main entrypoint is SpellCheckAfterEditorChange, which is called
      10             :  * when the text is changed.
      11             :  *
      12             :  * It is VERY IMPORTANT that we do NOT do any operations that might cause DOM
      13             :  * notifications to be flushed when we are called from the editor. This is
      14             :  * because the call might originate from a frame, and flushing the
      15             :  * notifications might cause that frame to be deleted.
      16             :  *
      17             :  * Using the WordUtil class to find words causes DOM notifications to be
      18             :  * flushed because it asks for style information. As a result, we post an event
      19             :  * and do all of the spellchecking in that event handler, which occurs later.
      20             :  * We store all DOM pointers in ranges because they are kept up-to-date with
      21             :  * DOM changes that may have happened while the event was on the queue.
      22             :  *
      23             :  * We also allow the spellcheck to be suspended and resumed later. This makes
      24             :  * large pastes or initializations with a lot of text not hang the browser UI.
      25             :  *
      26             :  * An optimization is the mNeedsCheckAfterNavigation flag. This is set to
      27             :  * true when we get any change, and false once there is no possibility
      28             :  * something changed that we need to check on navigation. Navigation events
      29             :  * tend to be a little tricky because we want to check the current word on
      30             :  * exit if something has changed. If we navigate inside the word, we don't want
      31             :  * to do anything. As a result, this flag is cleared in FinishNavigationEvent
      32             :  * when we know that we are checking as a result of navigation.
      33             :  */
      34             : 
      35             : #include "mozilla/EditorBase.h"
      36             : #include "mozilla/EditorUtils.h"
      37             : #include "mozilla/Services.h"
      38             : #include "mozilla/dom/Selection.h"
      39             : #include "mozInlineSpellChecker.h"
      40             : #include "mozInlineSpellWordUtil.h"
      41             : #include "mozISpellI18NManager.h"
      42             : #include "nsCOMPtr.h"
      43             : #include "nsCRT.h"
      44             : #include "nsIDOMNode.h"
      45             : #include "nsIDOMDocument.h"
      46             : #include "nsIDOMElement.h"
      47             : #include "nsIDOMHTMLElement.h"
      48             : #include "nsIDOMMouseEvent.h"
      49             : #include "nsIDOMKeyEvent.h"
      50             : #include "nsIDOMNode.h"
      51             : #include "nsIDOMNodeList.h"
      52             : #include "nsIDOMEvent.h"
      53             : #include "nsGenericHTMLElement.h"
      54             : #include "nsRange.h"
      55             : #include "nsIPlaintextEditor.h"
      56             : #include "nsIPrefBranch.h"
      57             : #include "nsIPrefService.h"
      58             : #include "nsIRunnable.h"
      59             : #include "nsISelection.h"
      60             : #include "nsISelectionPrivate.h"
      61             : #include "nsISelectionController.h"
      62             : #include "nsIServiceManager.h"
      63             : #include "nsITextServicesFilter.h"
      64             : #include "nsString.h"
      65             : #include "nsThreadUtils.h"
      66             : #include "nsUnicharUtils.h"
      67             : #include "nsIContent.h"
      68             : #include "nsRange.h"
      69             : #include "nsContentUtils.h"
      70             : #include "nsIObserverService.h"
      71             : #include "nsITextControlElement.h"
      72             : #include "prtime.h"
      73             : 
      74             : using namespace mozilla;
      75             : using namespace mozilla::dom;
      76             : 
      77             : // Set to spew messages to the console about what is happening.
      78             : //#define DEBUG_INLINESPELL
      79             : 
      80             : // the number of milliseconds that we will take at once to do spellchecking
      81             : #define INLINESPELL_CHECK_TIMEOUT 1
      82             : 
      83             : // The number of words to check before we look at the time to see if
      84             : // INLINESPELL_CHECK_TIMEOUT ms have elapsed. This prevents us from getting
      85             : // stuck and not moving forward because the INLINESPELL_CHECK_TIMEOUT might
      86             : // be too short to a low-end machine.
      87             : #define INLINESPELL_MINIMUM_WORDS_BEFORE_TIMEOUT 5
      88             : 
      89             : // These notifications are broadcast when spell check starts and ends.  STARTED
      90             : // must always be followed by ENDED.
      91             : #define INLINESPELL_STARTED_TOPIC "inlineSpellChecker-spellCheck-started"
      92             : #define INLINESPELL_ENDED_TOPIC "inlineSpellChecker-spellCheck-ended"
      93             : 
      94             : static bool ContentIsDescendantOf(nsINode* aPossibleDescendant,
      95             :                                     nsINode* aPossibleAncestor);
      96             : 
      97             : static const char kMaxSpellCheckSelectionSize[] =
      98             :   "extensions.spellcheck.inline.max-misspellings";
      99             : static const PRTime kMaxSpellCheckTimeInUsec =
     100             :   INLINESPELL_CHECK_TIMEOUT * PR_USEC_PER_MSEC;
     101             : 
     102           0 : mozInlineSpellStatus::mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker)
     103           0 :     : mSpellChecker(aSpellChecker), mWordCount(0)
     104             : {
     105           0 : }
     106             : 
     107             : // mozInlineSpellStatus::InitForEditorChange
     108             : //
     109             : //    This is the most complicated case. For changes, we need to compute the
     110             : //    range of stuff that changed based on the old and new caret positions,
     111             : //    as well as use a range possibly provided by the editor (start and end,
     112             : //    which are usually nullptr) to get a range with the union of these.
     113             : 
     114             : nsresult
     115           0 : mozInlineSpellStatus::InitForEditorChange(
     116             :     EditAction aAction,
     117             :     nsIDOMNode* aAnchorNode, int32_t aAnchorOffset,
     118             :     nsIDOMNode* aPreviousNode, int32_t aPreviousOffset,
     119             :     nsIDOMNode* aStartNode, int32_t aStartOffset,
     120             :     nsIDOMNode* aEndNode, int32_t aEndOffset)
     121             : {
     122             :   nsresult rv;
     123             : 
     124           0 :   nsCOMPtr<nsIDOMDocument> doc;
     125           0 :   rv = GetDocument(getter_AddRefs(doc));
     126           0 :   NS_ENSURE_SUCCESS(rv, rv);
     127             : 
     128             :   // save the anchor point as a range so we can find the current word later
     129           0 :   rv = PositionToCollapsedRange(doc, aAnchorNode, aAnchorOffset,
     130           0 :                                 getter_AddRefs(mAnchorRange));
     131           0 :   NS_ENSURE_SUCCESS(rv, rv);
     132             : 
     133           0 :   nsCOMPtr<nsINode> prevNode = do_QueryInterface(aPreviousNode);
     134           0 :   NS_ENSURE_STATE(prevNode);
     135             : 
     136           0 :   bool deleted = aAction == EditAction::deleteSelection;
     137           0 :   if (aAction == EditAction::insertIMEText) {
     138             :     // IME may remove the previous node if it cancels composition when
     139             :     // there is no text around the composition.
     140           0 :     deleted = !prevNode->IsInComposedDoc();
     141             :   }
     142             : 
     143           0 :   if (deleted) {
     144             :     // Deletes are easy, the range is just the current anchor. We set the range
     145             :     // to check to be empty, FinishInitOnEvent will fill in the range to be
     146             :     // the current word.
     147           0 :     mOp = eOpChangeDelete;
     148           0 :     mRange = nullptr;
     149           0 :     return NS_OK;
     150             :   }
     151             : 
     152           0 :   mOp = eOpChange;
     153             : 
     154             :   // range to check
     155           0 :   mRange = new nsRange(prevNode);
     156             : 
     157             :   // ...we need to put the start and end in the correct order
     158             :   int16_t cmpResult;
     159           0 :   rv = mAnchorRange->ComparePoint(aPreviousNode, aPreviousOffset, &cmpResult);
     160           0 :   NS_ENSURE_SUCCESS(rv, rv);
     161           0 :   if (cmpResult < 0) {
     162             :     // previous anchor node is before the current anchor
     163           0 :     nsCOMPtr<nsINode> previousNode = do_QueryInterface(aPreviousNode);
     164           0 :     nsCOMPtr<nsINode> anchorNode = do_QueryInterface(aAnchorNode);
     165           0 :     rv = mRange->SetStartAndEnd(previousNode, aPreviousOffset,
     166           0 :                                 anchorNode, aAnchorOffset);
     167           0 :     if (NS_WARN_IF(NS_FAILED(rv))) {
     168           0 :       return rv;
     169             :     }
     170             :   } else {
     171             :     // previous anchor node is after (or the same as) the current anchor
     172           0 :     nsCOMPtr<nsINode> previousNode = do_QueryInterface(aPreviousNode);
     173           0 :     nsCOMPtr<nsINode> anchorNode = do_QueryInterface(aAnchorNode);
     174           0 :     rv = mRange->SetStartAndEnd(anchorNode, aAnchorOffset,
     175           0 :                                 previousNode, aPreviousOffset);
     176           0 :     if (NS_WARN_IF(NS_FAILED(rv))) {
     177           0 :       return rv;
     178             :     }
     179             :   }
     180             : 
     181             :   // On insert save this range: DoSpellCheck optimizes things in this range.
     182             :   // Otherwise, just leave this nullptr.
     183           0 :   if (aAction == EditAction::insertText)
     184           0 :     mCreatedRange = mRange;
     185             : 
     186             :   // if we were given a range, we need to expand our range to encompass it
     187           0 :   if (aStartNode && aEndNode) {
     188           0 :     rv = mRange->ComparePoint(aStartNode, aStartOffset, &cmpResult);
     189           0 :     NS_ENSURE_SUCCESS(rv, rv);
     190           0 :     if (cmpResult < 0) { // given range starts before
     191           0 :       rv = mRange->SetStart(aStartNode, aStartOffset);
     192           0 :       NS_ENSURE_SUCCESS(rv, rv);
     193             :     }
     194             : 
     195           0 :     rv = mRange->ComparePoint(aEndNode, aEndOffset, &cmpResult);
     196           0 :     NS_ENSURE_SUCCESS(rv, rv);
     197           0 :     if (cmpResult > 0) { // given range ends after
     198           0 :       rv = mRange->SetEnd(aEndNode, aEndOffset);
     199           0 :       NS_ENSURE_SUCCESS(rv, rv);
     200             :     }
     201             :   }
     202             : 
     203           0 :   return NS_OK;
     204             : }
     205             : 
     206             : // mozInlineSpellStatis::InitForNavigation
     207             : //
     208             : //    For navigation events, we just need to store the new and old positions.
     209             : //
     210             : //    In some cases, we detect that we shouldn't check. If this event should
     211             : //    not be processed, *aContinue will be false.
     212             : 
     213             : nsresult
     214           0 : mozInlineSpellStatus::InitForNavigation(
     215             :     bool aForceCheck, int32_t aNewPositionOffset,
     216             :     nsIDOMNode* aOldAnchorNode, int32_t aOldAnchorOffset,
     217             :     nsIDOMNode* aNewAnchorNode, int32_t aNewAnchorOffset,
     218             :     bool* aContinue)
     219             : {
     220             :   nsresult rv;
     221           0 :   mOp = eOpNavigation;
     222             : 
     223           0 :   mForceNavigationWordCheck = aForceCheck;
     224           0 :   mNewNavigationPositionOffset = aNewPositionOffset;
     225             : 
     226             :   // get the root node for checking
     227           0 :   nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor, &rv);
     228           0 :   NS_ENSURE_SUCCESS(rv, rv);
     229           0 :   nsCOMPtr<nsIDOMElement> rootElt;
     230           0 :   rv = editor->GetRootElement(getter_AddRefs(rootElt));
     231           0 :   NS_ENSURE_SUCCESS(rv, rv);
     232             : 
     233             :   // the anchor node might not be in the DOM anymore, check
     234           0 :   nsCOMPtr<nsINode> root = do_QueryInterface(rootElt, &rv);
     235           0 :   NS_ENSURE_SUCCESS(rv, rv);
     236           0 :   nsCOMPtr<nsINode> currentAnchor = do_QueryInterface(aOldAnchorNode, &rv);
     237           0 :   NS_ENSURE_SUCCESS(rv, rv);
     238           0 :   if (root && currentAnchor && ! ContentIsDescendantOf(currentAnchor, root)) {
     239           0 :     *aContinue = false;
     240           0 :     return NS_OK;
     241             :   }
     242             : 
     243           0 :   nsCOMPtr<nsIDOMDocument> doc;
     244           0 :   rv = GetDocument(getter_AddRefs(doc));
     245           0 :   NS_ENSURE_SUCCESS(rv, rv);
     246             : 
     247           0 :   rv = PositionToCollapsedRange(doc, aOldAnchorNode, aOldAnchorOffset,
     248           0 :                                 getter_AddRefs(mOldNavigationAnchorRange));
     249           0 :   NS_ENSURE_SUCCESS(rv, rv);
     250           0 :   rv = PositionToCollapsedRange(doc, aNewAnchorNode, aNewAnchorOffset,
     251           0 :                                 getter_AddRefs(mAnchorRange));
     252           0 :   NS_ENSURE_SUCCESS(rv, rv);
     253             : 
     254           0 :   *aContinue = true;
     255           0 :   return NS_OK;
     256             : }
     257             : 
     258             : // mozInlineSpellStatus::InitForSelection
     259             : //
     260             : //    It is easy for selections since we always re-check the spellcheck
     261             : //    selection.
     262             : 
     263             : nsresult
     264           0 : mozInlineSpellStatus::InitForSelection()
     265             : {
     266           0 :   mOp = eOpSelection;
     267           0 :   return NS_OK;
     268             : }
     269             : 
     270             : // mozInlineSpellStatus::InitForRange
     271             : //
     272             : //    Called to cause the spellcheck of the given range. This will look like
     273             : //    a change operation over the given range.
     274             : 
     275             : nsresult
     276           0 : mozInlineSpellStatus::InitForRange(nsRange* aRange)
     277             : {
     278           0 :   mOp = eOpChange;
     279           0 :   mRange = aRange;
     280           0 :   return NS_OK;
     281             : }
     282             : 
     283             : // mozInlineSpellStatus::FinishInitOnEvent
     284             : //
     285             : //    Called when the event is triggered to complete initialization that
     286             : //    might require the WordUtil. This calls to the operation-specific
     287             : //    initializer, and also sets the range to be the entire element if it
     288             : //    is nullptr.
     289             : //
     290             : //    Watch out: the range might still be nullptr if there is nothing to do,
     291             : //    the caller will have to check for this.
     292             : 
     293             : nsresult
     294           0 : mozInlineSpellStatus::FinishInitOnEvent(mozInlineSpellWordUtil& aWordUtil)
     295             : {
     296             :   nsresult rv;
     297           0 :   if (! mRange) {
     298           0 :     rv = mSpellChecker->MakeSpellCheckRange(nullptr, 0, nullptr, 0,
     299           0 :                                             getter_AddRefs(mRange));
     300           0 :     NS_ENSURE_SUCCESS(rv, rv);
     301             :   }
     302             : 
     303           0 :   switch (mOp) {
     304             :     case eOpChange:
     305           0 :       if (mAnchorRange)
     306           0 :         return FillNoCheckRangeFromAnchor(aWordUtil);
     307           0 :       break;
     308             :     case eOpChangeDelete:
     309           0 :       if (mAnchorRange) {
     310           0 :         rv = FillNoCheckRangeFromAnchor(aWordUtil);
     311           0 :         NS_ENSURE_SUCCESS(rv, rv);
     312             :       }
     313             :       // Delete events will have no range for the changed text (because it was
     314             :       // deleted), and InitForEditorChange will set it to nullptr. Here, we select
     315             :       // the entire word to cause any underlining to be removed.
     316           0 :       mRange = mNoCheckRange;
     317           0 :       break;
     318             :     case eOpNavigation:
     319           0 :       return FinishNavigationEvent(aWordUtil);
     320             :     case eOpSelection:
     321             :       // this gets special handling in ResumeCheck
     322           0 :       break;
     323             :     case eOpResume:
     324             :       // everything should be initialized already in this case
     325           0 :       break;
     326             :     default:
     327           0 :       NS_NOTREACHED("Bad operation");
     328           0 :       return NS_ERROR_NOT_INITIALIZED;
     329             :   }
     330           0 :   return NS_OK;
     331             : }
     332             : 
     333             : // mozInlineSpellStatus::FinishNavigationEvent
     334             : //
     335             : //    This verifies that we need to check the word at the previous caret
     336             : //    position. Now that we have the word util, we can find the word belonging
     337             : //    to the previous caret position. If the new position is inside that word,
     338             : //    we don't want to do anything. In this case, we'll nullptr out mRange so
     339             : //    that the caller will know not to continue.
     340             : //
     341             : //    Notice that we don't set mNoCheckRange. We check here whether the cursor
     342             : //    is in the word that needs checking, so it isn't necessary. Plus, the
     343             : //    spellchecker isn't guaranteed to only check the given word, and it could
     344             : //    remove the underline from the new word under the cursor.
     345             : 
     346             : nsresult
     347           0 : mozInlineSpellStatus::FinishNavigationEvent(mozInlineSpellWordUtil& aWordUtil)
     348             : {
     349           0 :   nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor);
     350           0 :   if (! editor)
     351           0 :     return NS_ERROR_FAILURE; // editor is gone
     352             : 
     353           0 :   NS_ASSERTION(mAnchorRange, "No anchor for navigation!");
     354           0 :   nsCOMPtr<nsIDOMNode> newAnchorNode, oldAnchorNode;
     355             :   int32_t newAnchorOffset, oldAnchorOffset;
     356             : 
     357             :   // get the DOM position of the old caret, the range should be collapsed
     358           0 :   nsresult rv = mOldNavigationAnchorRange->GetStartContainer(
     359           0 :       getter_AddRefs(oldAnchorNode));
     360           0 :   NS_ENSURE_SUCCESS(rv, rv);
     361           0 :   rv = mOldNavigationAnchorRange->GetStartOffset(&oldAnchorOffset);
     362           0 :   NS_ENSURE_SUCCESS(rv, rv);
     363             : 
     364             :   // find the word on the old caret position, this is the one that we MAY need
     365             :   // to check
     366           0 :   RefPtr<nsRange> oldWord;
     367           0 :   rv = aWordUtil.GetRangeForWord(oldAnchorNode, oldAnchorOffset,
     368           0 :                                  getter_AddRefs(oldWord));
     369           0 :   NS_ENSURE_SUCCESS(rv, rv);
     370             : 
     371             :   // aWordUtil.GetRangeForWord flushes pending notifications, check editor again.
     372           0 :   editor = do_QueryReferent(mSpellChecker->mEditor);
     373           0 :   if (! editor)
     374           0 :     return NS_ERROR_FAILURE; // editor is gone
     375             : 
     376             :   // get the DOM position of the new caret, the range should be collapsed
     377           0 :   rv = mAnchorRange->GetStartContainer(getter_AddRefs(newAnchorNode));
     378           0 :   NS_ENSURE_SUCCESS(rv, rv);
     379           0 :   rv = mAnchorRange->GetStartOffset(&newAnchorOffset);
     380           0 :   NS_ENSURE_SUCCESS(rv, rv);
     381             : 
     382             :   // see if the new cursor position is in the word of the old cursor position
     383           0 :   bool isInRange = false;
     384           0 :   if (! mForceNavigationWordCheck) {
     385           0 :     rv = oldWord->IsPointInRange(newAnchorNode,
     386           0 :                                  newAnchorOffset + mNewNavigationPositionOffset,
     387           0 :                                  &isInRange);
     388           0 :     NS_ENSURE_SUCCESS(rv, rv);
     389             :   }
     390             : 
     391           0 :   if (isInRange) {
     392             :     // caller should give up
     393           0 :     mRange = nullptr;
     394             :   } else {
     395             :     // check the old word
     396           0 :     mRange = oldWord;
     397             : 
     398             :     // Once we've spellchecked the current word, we don't need to spellcheck
     399             :     // for any more navigation events.
     400           0 :     mSpellChecker->mNeedsCheckAfterNavigation = false;
     401             :   }
     402           0 :   return NS_OK;
     403             : }
     404             : 
     405             : // mozInlineSpellStatus::FillNoCheckRangeFromAnchor
     406             : //
     407             : //    Given the mAnchorRange object, computes the range of the word it is on
     408             : //    (if any) and fills that range into mNoCheckRange. This is used for
     409             : //    change and navigation events to know which word we should skip spell
     410             : //    checking on
     411             : 
     412             : nsresult
     413           0 : mozInlineSpellStatus::FillNoCheckRangeFromAnchor(
     414             :     mozInlineSpellWordUtil& aWordUtil)
     415             : {
     416           0 :   nsCOMPtr<nsIDOMNode> anchorNode;
     417           0 :   nsresult rv = mAnchorRange->GetStartContainer(getter_AddRefs(anchorNode));
     418           0 :   NS_ENSURE_SUCCESS(rv, rv);
     419             : 
     420             :   int32_t anchorOffset;
     421           0 :   rv = mAnchorRange->GetStartOffset(&anchorOffset);
     422           0 :   NS_ENSURE_SUCCESS(rv, rv);
     423             : 
     424           0 :   return aWordUtil.GetRangeForWord(anchorNode, anchorOffset,
     425           0 :                                    getter_AddRefs(mNoCheckRange));
     426             : }
     427             : 
     428             : // mozInlineSpellStatus::GetDocument
     429             : //
     430             : //    Returns the nsIDOMDocument object for the document for the
     431             : //    current spellchecker.
     432             : 
     433             : nsresult
     434           0 : mozInlineSpellStatus::GetDocument(nsIDOMDocument** aDocument)
     435             : {
     436             :   nsresult rv;
     437           0 :   *aDocument = nullptr;
     438           0 :   if (! mSpellChecker->mEditor)
     439           0 :     return NS_ERROR_UNEXPECTED;
     440             : 
     441           0 :   nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor, &rv);
     442           0 :   NS_ENSURE_SUCCESS(rv, rv);
     443             : 
     444           0 :   nsCOMPtr<nsIDOMDocument> domDoc;
     445           0 :   rv = editor->GetDocument(getter_AddRefs(domDoc));
     446           0 :   NS_ENSURE_SUCCESS(rv, rv);
     447           0 :   NS_ENSURE_TRUE(domDoc, NS_ERROR_NULL_POINTER);
     448           0 :   domDoc.forget(aDocument);
     449           0 :   return NS_OK;
     450             : }
     451             : 
     452             : // mozInlineSpellStatus::PositionToCollapsedRange
     453             : //
     454             : //    Converts a given DOM position to a collapsed range covering that
     455             : //    position. We use ranges to store DOM positions becuase they stay
     456             : //    updated as the DOM is changed.
     457             : 
     458             : nsresult
     459           0 : mozInlineSpellStatus::PositionToCollapsedRange(nsIDOMDocument* aDocument,
     460             :                                                nsIDOMNode* aNode,
     461             :                                                int32_t aOffset,
     462             :                                                nsRange** aRange)
     463             : {
     464           0 :   *aRange = nullptr;
     465           0 :   nsCOMPtr<nsINode> documentNode = do_QueryInterface(aDocument);
     466           0 :   RefPtr<nsRange> range = new nsRange(documentNode);
     467             : 
     468           0 :   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
     469           0 :   nsresult rv = range->CollapseTo(node, aOffset);
     470           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     471           0 :     return rv;
     472             :   }
     473             : 
     474           0 :   range.swap(*aRange);
     475           0 :   return NS_OK;
     476             : }
     477             : 
     478             : // mozInlineSpellResume
     479             : 
     480           0 : class mozInlineSpellResume : public Runnable
     481             : {
     482             : public:
     483           0 :   mozInlineSpellResume(UniquePtr<mozInlineSpellStatus>&& aStatus,
     484             :                        uint32_t aDisabledAsyncToken)
     485           0 :     : Runnable("mozInlineSpellResume")
     486             :     , mDisabledAsyncToken(aDisabledAsyncToken)
     487           0 :     , mStatus(Move(aStatus))
     488             :   {
     489           0 :   }
     490             : 
     491           0 :   nsresult Post()
     492             :   {
     493           0 :     nsCOMPtr<nsIRunnable> runnable(this);
     494           0 :     return NS_IdleDispatchToCurrentThread(runnable.forget(), 1000);
     495             :   }
     496             : 
     497           0 :   NS_IMETHOD Run() override
     498             :   {
     499             :     // Discard the resumption if the spell checker was disabled after the
     500             :     // resumption was scheduled.
     501           0 :     if (mDisabledAsyncToken == mStatus->mSpellChecker->mDisabledAsyncToken) {
     502           0 :       mStatus->mSpellChecker->ResumeCheck(Move(mStatus));
     503             :     }
     504           0 :     return NS_OK;
     505             :   }
     506             : 
     507             : private:
     508             :   uint32_t mDisabledAsyncToken;
     509             :   UniquePtr<mozInlineSpellStatus> mStatus;
     510             : };
     511             : 
     512             : // Used as the nsIEditorSpellCheck::InitSpellChecker callback.
     513             : class InitEditorSpellCheckCallback final : public nsIEditorSpellCheckCallback
     514             : {
     515           0 :   ~InitEditorSpellCheckCallback() {}
     516             : public:
     517             :   NS_DECL_ISUPPORTS
     518             : 
     519           0 :   explicit InitEditorSpellCheckCallback(mozInlineSpellChecker* aSpellChecker)
     520           0 :     : mSpellChecker(aSpellChecker) {}
     521             : 
     522           0 :   NS_IMETHOD EditorSpellCheckDone() override
     523             :   {
     524           0 :     return mSpellChecker ? mSpellChecker->EditorSpellCheckInited() : NS_OK;
     525             :   }
     526             : 
     527           0 :   void Cancel()
     528             :   {
     529           0 :     mSpellChecker = nullptr;
     530           0 :   }
     531             : 
     532             : private:
     533             :   RefPtr<mozInlineSpellChecker> mSpellChecker;
     534             : };
     535           0 : NS_IMPL_ISUPPORTS(InitEditorSpellCheckCallback, nsIEditorSpellCheckCallback)
     536             : 
     537             : 
     538           0 : NS_INTERFACE_MAP_BEGIN(mozInlineSpellChecker)
     539           0 :   NS_INTERFACE_MAP_ENTRY(nsIInlineSpellChecker)
     540           0 :   NS_INTERFACE_MAP_ENTRY(nsIEditActionListener)
     541           0 :   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
     542           0 :   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
     543           0 :   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
     544           0 :   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozInlineSpellChecker)
     545           0 : NS_INTERFACE_MAP_END
     546             : 
     547           0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(mozInlineSpellChecker)
     548           0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(mozInlineSpellChecker)
     549             : 
     550           0 : NS_IMPL_CYCLE_COLLECTION(mozInlineSpellChecker,
     551             :                          mSpellCheck,
     552             :                          mTreeWalker,
     553             :                          mCurrentSelectionAnchorNode)
     554             : 
     555             : mozInlineSpellChecker::SpellCheckingState
     556             :   mozInlineSpellChecker::gCanEnableSpellChecking =
     557             :   mozInlineSpellChecker::SpellCheck_Uninitialized;
     558             : 
     559           0 : mozInlineSpellChecker::mozInlineSpellChecker() :
     560             :     mNumWordsInSpellSelection(0),
     561             :     mMaxNumWordsInSpellSelection(250),
     562             :     mNumPendingSpellChecks(0),
     563             :     mNumPendingUpdateCurrentDictionary(0),
     564             :     mDisabledAsyncToken(0),
     565             :     mNeedsCheckAfterNavigation(false),
     566           0 :     mFullSpellCheckScheduled(false)
     567             : {
     568           0 :   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
     569           0 :   if (prefs)
     570           0 :     prefs->GetIntPref(kMaxSpellCheckSelectionSize, &mMaxNumWordsInSpellSelection);
     571           0 :   mMaxMisspellingsPerCheck = mMaxNumWordsInSpellSelection * 3 / 4;
     572           0 : }
     573             : 
     574           0 : mozInlineSpellChecker::~mozInlineSpellChecker()
     575             : {
     576           0 : }
     577             : 
     578             : NS_IMETHODIMP
     579           0 : mozInlineSpellChecker::GetSpellChecker(nsIEditorSpellCheck **aSpellCheck)
     580             : {
     581           0 :   *aSpellCheck = mSpellCheck;
     582           0 :   NS_IF_ADDREF(*aSpellCheck);
     583           0 :   return NS_OK;
     584             : }
     585             : 
     586             : NS_IMETHODIMP
     587           0 : mozInlineSpellChecker::Init(nsIEditor *aEditor)
     588             : {
     589           0 :   mEditor = do_GetWeakReference(aEditor);
     590           0 :   return NS_OK;
     591             : }
     592             : 
     593             : // mozInlineSpellChecker::Cleanup
     594             : //
     595             : //    Called by the editor when the editor is going away. This is important
     596             : //    because we remove listeners. We do NOT clean up anything else in this
     597             : //    function, because it can get called while DoSpellCheck is running!
     598             : //
     599             : //    Getting the style information there can cause DOM notifications to be
     600             : //    flushed, which can cause editors to go away which will bring us here.
     601             : //    We can not do anything that will cause DoSpellCheck to freak out.
     602             : 
     603           0 : nsresult mozInlineSpellChecker::Cleanup(bool aDestroyingFrames)
     604             : {
     605           0 :   mNumWordsInSpellSelection = 0;
     606           0 :   nsCOMPtr<nsISelection> spellCheckSelection;
     607           0 :   nsresult rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection));
     608           0 :   if (NS_FAILED(rv)) {
     609             :     // Ensure we still unregister event listeners (but return a failure code)
     610           0 :     UnregisterEventListeners();
     611             :   } else {
     612           0 :     if (!aDestroyingFrames) {
     613           0 :       spellCheckSelection->RemoveAllRanges();
     614             :     }
     615             : 
     616           0 :     rv = UnregisterEventListeners();
     617             :   }
     618             : 
     619             :   // Notify ENDED observers now.  If we wait to notify as we normally do when
     620             :   // these async operations finish, then in the meantime the editor may create
     621             :   // another inline spell checker and cause more STARTED and ENDED
     622             :   // notifications to be broadcast.  Interleaved notifications for the same
     623             :   // editor but different inline spell checkers could easily confuse
     624             :   // observers.  They may receive two consecutive STARTED notifications for
     625             :   // example, which we guarantee will not happen.
     626             : 
     627           0 :   nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor);
     628           0 :   if (mPendingSpellCheck) {
     629             :     // Cancel the pending editor spell checker initialization.
     630           0 :     mPendingSpellCheck = nullptr;
     631           0 :     mPendingInitEditorSpellCheckCallback->Cancel();
     632           0 :     mPendingInitEditorSpellCheckCallback = nullptr;
     633           0 :     ChangeNumPendingSpellChecks(-1, editor);
     634             :   }
     635             : 
     636             :   // Increment this token so that pending UpdateCurrentDictionary calls and
     637             :   // scheduled spell checks are discarded when they finish.
     638           0 :   mDisabledAsyncToken++;
     639             : 
     640           0 :   if (mNumPendingUpdateCurrentDictionary > 0) {
     641             :     // Account for pending UpdateCurrentDictionary calls.
     642           0 :     ChangeNumPendingSpellChecks(-mNumPendingUpdateCurrentDictionary, editor);
     643           0 :     mNumPendingUpdateCurrentDictionary = 0;
     644             :   }
     645           0 :   if (mNumPendingSpellChecks > 0) {
     646             :     // If mNumPendingSpellChecks is still > 0 at this point, the remainder is
     647             :     // pending scheduled spell checks.
     648           0 :     ChangeNumPendingSpellChecks(-mNumPendingSpellChecks, editor);
     649             :   }
     650             : 
     651           0 :   mEditor = nullptr;
     652           0 :   mFullSpellCheckScheduled = false;
     653             : 
     654           0 :   return rv;
     655             : }
     656             : 
     657             : // mozInlineSpellChecker::CanEnableInlineSpellChecking
     658             : //
     659             : //    This function can be called to see if it seems likely that we can enable
     660             : //    spellchecking before actually creating the InlineSpellChecking objects.
     661             : //
     662             : //    The problem is that we can't get the dictionary list without actually
     663             : //    creating a whole bunch of spellchecking objects. This function tries to
     664             : //    do that and caches the result so we don't have to keep allocating those
     665             : //    objects if there are no dictionaries or spellchecking.
     666             : //
     667             : //    Whenever dictionaries are added or removed at runtime, this value must be
     668             : //    updated before an observer notification is sent out about the change, to
     669             : //    avoid editors getting a wrong cached result.
     670             : 
     671             : bool // static
     672           0 : mozInlineSpellChecker::CanEnableInlineSpellChecking()
     673             : {
     674             :   nsresult rv;
     675           0 :   if (gCanEnableSpellChecking == SpellCheck_Uninitialized) {
     676           0 :     gCanEnableSpellChecking = SpellCheck_NotAvailable;
     677             : 
     678             :     nsCOMPtr<nsIEditorSpellCheck> spellchecker =
     679           0 :       do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &rv);
     680           0 :     NS_ENSURE_SUCCESS(rv, false);
     681             : 
     682           0 :     bool canSpellCheck = false;
     683           0 :     rv = spellchecker->CanSpellCheck(&canSpellCheck);
     684           0 :     NS_ENSURE_SUCCESS(rv, false);
     685             : 
     686           0 :     if (canSpellCheck)
     687           0 :       gCanEnableSpellChecking = SpellCheck_Available;
     688             :   }
     689           0 :   return (gCanEnableSpellChecking == SpellCheck_Available);
     690             : }
     691             : 
     692             : void // static
     693           1 : mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking()
     694             : {
     695           1 :   gCanEnableSpellChecking = SpellCheck_Uninitialized;
     696           1 : }
     697             : 
     698             : // mozInlineSpellChecker::RegisterEventListeners
     699             : //
     700             : //    The inline spell checker listens to mouse events and keyboard navigation
     701             : //    events.
     702             : 
     703             : nsresult
     704           0 : mozInlineSpellChecker::RegisterEventListeners()
     705             : {
     706           0 :   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
     707           0 :   NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
     708             : 
     709           0 :   editor->AddEditActionListener(this);
     710             : 
     711           0 :   nsCOMPtr<nsIDOMDocument> doc;
     712           0 :   nsresult rv = editor->GetDocument(getter_AddRefs(doc));
     713           0 :   NS_ENSURE_SUCCESS(rv, rv);
     714             : 
     715           0 :   nsCOMPtr<EventTarget> piTarget = do_QueryInterface(doc, &rv);
     716           0 :   NS_ENSURE_SUCCESS(rv, rv);
     717             : 
     718           0 :   piTarget->AddEventListener(NS_LITERAL_STRING("blur"), this,
     719           0 :                              true, false);
     720           0 :   piTarget->AddEventListener(NS_LITERAL_STRING("click"), this,
     721           0 :                              false, false);
     722           0 :   piTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this,
     723           0 :                              false, false);
     724           0 :   return NS_OK;
     725             : }
     726             : 
     727             : // mozInlineSpellChecker::UnregisterEventListeners
     728             : 
     729             : nsresult
     730           0 : mozInlineSpellChecker::UnregisterEventListeners()
     731             : {
     732           0 :   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
     733           0 :   NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
     734             : 
     735           0 :   editor->RemoveEditActionListener(this);
     736             : 
     737           0 :   nsCOMPtr<nsIDOMDocument> doc;
     738           0 :   editor->GetDocument(getter_AddRefs(doc));
     739           0 :   NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER);
     740             : 
     741           0 :   nsCOMPtr<EventTarget> piTarget = do_QueryInterface(doc);
     742           0 :   NS_ENSURE_TRUE(piTarget, NS_ERROR_NULL_POINTER);
     743             : 
     744           0 :   piTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true);
     745           0 :   piTarget->RemoveEventListener(NS_LITERAL_STRING("click"), this, false);
     746           0 :   piTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, false);
     747           0 :   return NS_OK;
     748             : }
     749             : 
     750             : // mozInlineSpellChecker::GetEnableRealTimeSpell
     751             : 
     752             : NS_IMETHODIMP
     753           0 : mozInlineSpellChecker::GetEnableRealTimeSpell(bool* aEnabled)
     754             : {
     755           0 :   NS_ENSURE_ARG_POINTER(aEnabled);
     756           0 :   *aEnabled = mSpellCheck != nullptr || mPendingSpellCheck != nullptr;
     757           0 :   return NS_OK;
     758             : }
     759             : 
     760             : // mozInlineSpellChecker::SetEnableRealTimeSpell
     761             : 
     762             : NS_IMETHODIMP
     763           0 : mozInlineSpellChecker::SetEnableRealTimeSpell(bool aEnabled)
     764             : {
     765           0 :   if (!aEnabled) {
     766           0 :     mSpellCheck = nullptr;
     767           0 :     return Cleanup(false);
     768             :   }
     769             : 
     770           0 :   if (mSpellCheck) {
     771             :     // spellcheck the current contents. SpellCheckRange doesn't supply a created
     772             :     // range to DoSpellCheck, which in our case is the entire range. But this
     773             :     // optimization doesn't matter because there is nothing in the spellcheck
     774             :     // selection when starting, which triggers a better optimization.
     775           0 :     return SpellCheckRange(nullptr);
     776             :   }
     777             : 
     778           0 :   if (mPendingSpellCheck) {
     779             :     // The editor spell checker is already being initialized.
     780           0 :     return NS_OK;
     781             :   }
     782             : 
     783             :   mPendingSpellCheck =
     784           0 :     do_CreateInstance("@mozilla.org/editor/editorspellchecker;1");
     785           0 :   NS_ENSURE_STATE(mPendingSpellCheck);
     786             : 
     787             :   nsCOMPtr<nsITextServicesFilter> filter =
     788           0 :     do_CreateInstance("@mozilla.org/editor/txtsrvfiltermail;1");
     789           0 :   if (!filter) {
     790           0 :     mPendingSpellCheck = nullptr;
     791           0 :     NS_ENSURE_STATE(filter);
     792             :   }
     793           0 :   mPendingSpellCheck->SetFilter(filter);
     794             : 
     795           0 :   mPendingInitEditorSpellCheckCallback = new InitEditorSpellCheckCallback(this);
     796           0 :   if (!mPendingInitEditorSpellCheckCallback) {
     797           0 :     mPendingSpellCheck = nullptr;
     798           0 :     NS_ENSURE_STATE(mPendingInitEditorSpellCheckCallback);
     799             :   }
     800             : 
     801           0 :   nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor);
     802           0 :   nsresult rv = mPendingSpellCheck->InitSpellChecker(
     803           0 :                   editor, false, mPendingInitEditorSpellCheckCallback);
     804           0 :   if (NS_FAILED(rv)) {
     805           0 :     mPendingSpellCheck = nullptr;
     806           0 :     mPendingInitEditorSpellCheckCallback = nullptr;
     807           0 :     NS_ENSURE_SUCCESS(rv, rv);
     808             :   }
     809             : 
     810           0 :   ChangeNumPendingSpellChecks(1);
     811             : 
     812           0 :   return NS_OK;
     813             : }
     814             : 
     815             : // Called when nsIEditorSpellCheck::InitSpellChecker completes.
     816             : nsresult
     817           0 : mozInlineSpellChecker::EditorSpellCheckInited()
     818             : {
     819           0 :   NS_ASSERTION(mPendingSpellCheck, "Spell check should be pending!");
     820             : 
     821             :   // spell checking is enabled, register our event listeners to track navigation
     822           0 :   RegisterEventListeners();
     823             : 
     824           0 :   mSpellCheck = mPendingSpellCheck;
     825           0 :   mPendingSpellCheck = nullptr;
     826           0 :   mPendingInitEditorSpellCheckCallback = nullptr;
     827           0 :   ChangeNumPendingSpellChecks(-1);
     828             : 
     829             :   // spellcheck the current contents. SpellCheckRange doesn't supply a created
     830             :   // range to DoSpellCheck, which in our case is the entire range. But this
     831             :   // optimization doesn't matter because there is nothing in the spellcheck
     832             :   // selection when starting, which triggers a better optimization.
     833           0 :   return SpellCheckRange(nullptr);
     834             : }
     835             : 
     836             : // Changes the number of pending spell checks by the given delta.  If the number
     837             : // becomes zero or nonzero, observers are notified.  See NotifyObservers for
     838             : // info on the aEditor parameter.
     839             : void
     840           0 : mozInlineSpellChecker::ChangeNumPendingSpellChecks(int32_t aDelta,
     841             :                                                    nsIEditor* aEditor)
     842             : {
     843           0 :   int8_t oldNumPending = mNumPendingSpellChecks;
     844           0 :   mNumPendingSpellChecks += aDelta;
     845           0 :   NS_ASSERTION(mNumPendingSpellChecks >= 0,
     846             :                "Unbalanced ChangeNumPendingSpellChecks calls!");
     847           0 :   if (oldNumPending == 0 && mNumPendingSpellChecks > 0) {
     848           0 :     NotifyObservers(INLINESPELL_STARTED_TOPIC, aEditor);
     849           0 :   } else if (oldNumPending > 0 && mNumPendingSpellChecks == 0) {
     850           0 :     NotifyObservers(INLINESPELL_ENDED_TOPIC, aEditor);
     851             :   }
     852           0 : }
     853             : 
     854             : // Broadcasts the given topic to observers.  aEditor is passed to observers if
     855             : // nonnull; otherwise mEditor is passed.
     856             : void
     857           0 : mozInlineSpellChecker::NotifyObservers(const char* aTopic, nsIEditor* aEditor)
     858             : {
     859           0 :   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
     860           0 :   if (!os)
     861           0 :     return;
     862           0 :   nsCOMPtr<nsIEditor> editor = aEditor;
     863           0 :   if (!editor) {
     864           0 :     editor = do_QueryReferent(mEditor);
     865             :   }
     866           0 :   os->NotifyObservers(editor, aTopic, nullptr);
     867             : }
     868             : 
     869             : // mozInlineSpellChecker::SpellCheckAfterEditorChange
     870             : //
     871             : //    Called by the editor when nearly anything happens to change the content.
     872             : //
     873             : //    The start and end positions specify a range for the thing that happened,
     874             : //    but these are usually nullptr, even when you'd think they would be useful
     875             : //    because you want the range (for example, pasting). We ignore them in
     876             : //    this case.
     877             : 
     878             : NS_IMETHODIMP
     879           0 : mozInlineSpellChecker::SpellCheckAfterEditorChange(
     880             :     int32_t aAction, nsISelection *aSelection,
     881             :     nsIDOMNode *aPreviousSelectedNode, int32_t aPreviousSelectedOffset,
     882             :     nsIDOMNode *aStartNode, int32_t aStartOffset,
     883             :     nsIDOMNode *aEndNode, int32_t aEndOffset)
     884             : {
     885             :   nsresult rv;
     886           0 :   NS_ENSURE_ARG_POINTER(aSelection);
     887           0 :   if (!mSpellCheck)
     888           0 :     return NS_OK; // disabling spell checking is not an error
     889             : 
     890             :   // this means something has changed, and we never check the current word,
     891             :   // therefore, we should spellcheck for subsequent caret navigations
     892           0 :   mNeedsCheckAfterNavigation = true;
     893             : 
     894             :   // the anchor node is the position of the caret
     895           0 :   nsCOMPtr<nsIDOMNode> anchorNode;
     896           0 :   rv = aSelection->GetAnchorNode(getter_AddRefs(anchorNode));
     897           0 :   NS_ENSURE_SUCCESS(rv, rv);
     898             :   int32_t anchorOffset;
     899           0 :   rv = aSelection->GetAnchorOffset(&anchorOffset);
     900           0 :   NS_ENSURE_SUCCESS(rv, rv);
     901             : 
     902           0 :   auto status = MakeUnique<mozInlineSpellStatus>(this);
     903           0 :   rv = status->InitForEditorChange((EditAction)aAction,
     904             :                                    anchorNode, anchorOffset,
     905             :                                    aPreviousSelectedNode,
     906             :                                    aPreviousSelectedOffset,
     907             :                                    aStartNode, aStartOffset,
     908           0 :                                    aEndNode, aEndOffset);
     909           0 :   NS_ENSURE_SUCCESS(rv, rv);
     910           0 :   rv = ScheduleSpellCheck(Move(status));
     911           0 :   NS_ENSURE_SUCCESS(rv, rv);
     912             : 
     913             :   // remember the current caret position after every change
     914           0 :   SaveCurrentSelectionPosition();
     915           0 :   return NS_OK;
     916             : }
     917             : 
     918             : // mozInlineSpellChecker::SpellCheckRange
     919             : //
     920             : //    Spellchecks all the words in the given range.
     921             : //    Supply a nullptr range and this will check the entire editor.
     922             : 
     923             : nsresult
     924           0 : mozInlineSpellChecker::SpellCheckRange(nsIDOMRange* aRange)
     925             : {
     926           0 :   if (!mSpellCheck) {
     927           0 :     NS_WARNING_ASSERTION(
     928             :       mPendingSpellCheck,
     929             :       "Trying to spellcheck, but checking seems to be disabled");
     930           0 :     return NS_ERROR_NOT_INITIALIZED;
     931             :   }
     932             : 
     933           0 :   auto status = MakeUnique<mozInlineSpellStatus>(this);
     934           0 :   nsRange* range = static_cast<nsRange*>(aRange);
     935           0 :   nsresult rv = status->InitForRange(range);
     936           0 :   NS_ENSURE_SUCCESS(rv, rv);
     937           0 :   return ScheduleSpellCheck(Move(status));
     938             : }
     939             : 
     940             : // mozInlineSpellChecker::GetMisspelledWord
     941             : 
     942             : NS_IMETHODIMP
     943           0 : mozInlineSpellChecker::GetMisspelledWord(nsIDOMNode *aNode, int32_t aOffset,
     944             :                                          nsIDOMRange **newword)
     945             : {
     946           0 :   NS_ENSURE_ARG_POINTER(aNode);
     947           0 :   nsCOMPtr<nsISelection> spellCheckSelection;
     948           0 :   nsresult res = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection));
     949           0 :   NS_ENSURE_SUCCESS(res, res);
     950             : 
     951           0 :   return IsPointInSelection(spellCheckSelection, aNode, aOffset, newword);
     952             : }
     953             : 
     954             : // mozInlineSpellChecker::ReplaceWord
     955             : 
     956             : NS_IMETHODIMP
     957           0 : mozInlineSpellChecker::ReplaceWord(nsIDOMNode *aNode, int32_t aOffset,
     958             :                                    const nsAString &newword)
     959             : {
     960           0 :   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
     961           0 :   NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
     962           0 :   NS_ENSURE_TRUE(newword.Length() != 0, NS_ERROR_FAILURE);
     963             : 
     964           0 :   nsCOMPtr<nsIDOMRange> range;
     965           0 :   nsresult res = GetMisspelledWord(aNode, aOffset, getter_AddRefs(range));
     966           0 :   NS_ENSURE_SUCCESS(res, res);
     967             : 
     968           0 :   if (range)
     969             :   {
     970             :     // This range was retrieved from the spellchecker selection. As
     971             :     // ranges cannot be shared between selections, we must clone it
     972             :     // before adding it to the editor's selection.
     973           0 :     nsCOMPtr<nsIDOMRange> editorRange;
     974           0 :     res = range->CloneRange(getter_AddRefs(editorRange));
     975           0 :     NS_ENSURE_SUCCESS(res, res);
     976             : 
     977           0 :     AutoPlaceHolderBatch phb(editor, nullptr);
     978             : 
     979           0 :     nsCOMPtr<nsISelection> selection;
     980           0 :     res = editor->GetSelection(getter_AddRefs(selection));
     981           0 :     NS_ENSURE_SUCCESS(res, res);
     982           0 :     selection->RemoveAllRanges();
     983           0 :     selection->AddRange(editorRange);
     984             : 
     985           0 :     nsCOMPtr<nsIPlaintextEditor> textEditor(do_QueryReferent(mEditor));
     986           0 :     if (textEditor)
     987           0 :       textEditor->InsertText(newword);
     988             :   }
     989             : 
     990           0 :   return NS_OK;
     991             : }
     992             : 
     993             : // mozInlineSpellChecker::AddWordToDictionary
     994             : 
     995             : NS_IMETHODIMP
     996           0 : mozInlineSpellChecker::AddWordToDictionary(const nsAString &word)
     997             : {
     998           0 :   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
     999             : 
    1000           0 :   nsAutoString wordstr(word);
    1001           0 :   nsresult rv = mSpellCheck->AddWordToDictionary(wordstr.get());
    1002           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1003             : 
    1004           0 :   auto status = MakeUnique<mozInlineSpellStatus>(this);
    1005           0 :   rv = status->InitForSelection();
    1006           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1007           0 :   return ScheduleSpellCheck(Move(status));
    1008             : }
    1009             : 
    1010             : //  mozInlineSpellChecker::RemoveWordFromDictionary
    1011             : 
    1012             : NS_IMETHODIMP
    1013           0 : mozInlineSpellChecker::RemoveWordFromDictionary(const nsAString &word)
    1014             : {
    1015           0 :   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
    1016             : 
    1017           0 :   nsAutoString wordstr(word);
    1018           0 :   nsresult rv = mSpellCheck->RemoveWordFromDictionary(wordstr.get());
    1019           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1020             : 
    1021           0 :   auto status = MakeUnique<mozInlineSpellStatus>(this);
    1022           0 :   rv = status->InitForRange(nullptr);
    1023           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1024           0 :   return ScheduleSpellCheck(Move(status));
    1025             : }
    1026             : 
    1027             : // mozInlineSpellChecker::IgnoreWord
    1028             : 
    1029             : NS_IMETHODIMP
    1030           0 : mozInlineSpellChecker::IgnoreWord(const nsAString &word)
    1031             : {
    1032           0 :   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
    1033             : 
    1034           0 :   nsAutoString wordstr(word);
    1035           0 :   nsresult rv = mSpellCheck->IgnoreWordAllOccurrences(wordstr.get());
    1036           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1037             : 
    1038           0 :   auto status = MakeUnique<mozInlineSpellStatus>(this);
    1039           0 :   rv = status->InitForSelection();
    1040           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1041           0 :   return ScheduleSpellCheck(Move(status));
    1042             : }
    1043             : 
    1044             : // mozInlineSpellChecker::IgnoreWords
    1045             : 
    1046             : NS_IMETHODIMP
    1047           0 : mozInlineSpellChecker::IgnoreWords(const char16_t **aWordsToIgnore,
    1048             :                                    uint32_t aCount)
    1049             : {
    1050           0 :   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
    1051             : 
    1052             :   // add each word to the ignore list and then recheck the document
    1053           0 :   for (uint32_t index = 0; index < aCount; index++)
    1054           0 :     mSpellCheck->IgnoreWordAllOccurrences(aWordsToIgnore[index]);
    1055             : 
    1056           0 :   auto status = MakeUnique<mozInlineSpellStatus>(this);
    1057           0 :   nsresult rv = status->InitForSelection();
    1058           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1059           0 :   return ScheduleSpellCheck(Move(status));
    1060             : }
    1061             : 
    1062           0 : NS_IMETHODIMP mozInlineSpellChecker::WillCreateNode(const nsAString & aTag, nsIDOMNode *aParent, int32_t aPosition)
    1063             : {
    1064           0 :   return NS_OK;
    1065             : }
    1066             : 
    1067           0 : NS_IMETHODIMP mozInlineSpellChecker::DidCreateNode(const nsAString & aTag, nsIDOMNode *aNode, nsIDOMNode *aParent,
    1068             :                                                    int32_t aPosition, nsresult aResult)
    1069             : {
    1070           0 :   return NS_OK;
    1071             : }
    1072             : 
    1073           0 : NS_IMETHODIMP mozInlineSpellChecker::WillInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent,
    1074             :                                                     int32_t aPosition)
    1075             : {
    1076           0 :   return NS_OK;
    1077             : }
    1078             : 
    1079           0 : NS_IMETHODIMP mozInlineSpellChecker::DidInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent,
    1080             :                                                    int32_t aPosition, nsresult aResult)
    1081             : {
    1082             : 
    1083           0 :   return NS_OK;
    1084             : }
    1085             : 
    1086           0 : NS_IMETHODIMP mozInlineSpellChecker::WillDeleteNode(nsIDOMNode *aChild)
    1087             : {
    1088           0 :   return NS_OK;
    1089             : }
    1090             : 
    1091           0 : NS_IMETHODIMP mozInlineSpellChecker::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult)
    1092             : {
    1093           0 :   return NS_OK;
    1094             : }
    1095             : 
    1096           0 : NS_IMETHODIMP mozInlineSpellChecker::WillSplitNode(nsIDOMNode *aExistingRightNode, int32_t aOffset)
    1097             : {
    1098           0 :   return NS_OK;
    1099             : }
    1100             : 
    1101             : NS_IMETHODIMP
    1102           0 : mozInlineSpellChecker::DidSplitNode(nsIDOMNode *aExistingRightNode,
    1103             :                                     int32_t aOffset,
    1104             :                                     nsIDOMNode *aNewLeftNode, nsresult aResult)
    1105             : {
    1106           0 :   return SpellCheckBetweenNodes(aNewLeftNode, 0, aNewLeftNode, 0);
    1107             : }
    1108             : 
    1109           0 : NS_IMETHODIMP mozInlineSpellChecker::WillJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent)
    1110             : {
    1111           0 :   return NS_OK;
    1112             : }
    1113             : 
    1114           0 : NS_IMETHODIMP mozInlineSpellChecker::DidJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode,
    1115             :                                                   nsIDOMNode *aParent, nsresult aResult)
    1116             : {
    1117           0 :   return SpellCheckBetweenNodes(aRightNode, 0, aRightNode, 0);
    1118             : }
    1119             : 
    1120           0 : NS_IMETHODIMP mozInlineSpellChecker::WillInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString & aString)
    1121             : {
    1122           0 :   return NS_OK;
    1123             : }
    1124             : 
    1125           0 : NS_IMETHODIMP mozInlineSpellChecker::DidInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset,
    1126             :                                                    const nsAString & aString, nsresult aResult)
    1127             : {
    1128           0 :   return NS_OK;
    1129             : }
    1130             : 
    1131           0 : NS_IMETHODIMP mozInlineSpellChecker::WillDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength)
    1132             : {
    1133           0 :   return NS_OK;
    1134             : }
    1135             : 
    1136           0 : NS_IMETHODIMP mozInlineSpellChecker::DidDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength, nsresult aResult)
    1137             : {
    1138           0 :   return NS_OK;
    1139             : }
    1140             : 
    1141           0 : NS_IMETHODIMP mozInlineSpellChecker::WillDeleteSelection(nsISelection *aSelection)
    1142             : {
    1143           0 :   return NS_OK;
    1144             : }
    1145             : 
    1146           0 : NS_IMETHODIMP mozInlineSpellChecker::DidDeleteSelection(nsISelection *aSelection)
    1147             : {
    1148           0 :   return NS_OK;
    1149             : }
    1150             : 
    1151             : // mozInlineSpellChecker::MakeSpellCheckRange
    1152             : //
    1153             : //    Given begin and end positions, this function constructs a range as
    1154             : //    required for ScheduleSpellCheck. If the start and end nodes are nullptr,
    1155             : //    then the entire range will be selected, and you can supply -1 as the
    1156             : //    offset to the end range to select all of that node.
    1157             : //
    1158             : //    If the resulting range would be empty, nullptr is put into *aRange and the
    1159             : //    function succeeds.
    1160             : 
    1161             : nsresult
    1162           0 : mozInlineSpellChecker::MakeSpellCheckRange(
    1163             :     nsIDOMNode* aStartNode, int32_t aStartOffset,
    1164             :     nsIDOMNode* aEndNode, int32_t aEndOffset,
    1165             :     nsRange** aRange)
    1166             : {
    1167             :   nsresult rv;
    1168           0 :   *aRange = nullptr;
    1169             : 
    1170           0 :   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
    1171           0 :   NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
    1172             : 
    1173           0 :   nsCOMPtr<nsIDOMDocument> doc;
    1174           0 :   rv = editor->GetDocument(getter_AddRefs(doc));
    1175           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1176           0 :   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
    1177             : 
    1178           0 :   nsCOMPtr<nsINode> documentNode = do_QueryInterface(doc);
    1179           0 :   RefPtr<nsRange> range = new nsRange(documentNode);
    1180             : 
    1181             :   // possibly use full range of the editor
    1182           0 :   nsCOMPtr<nsIDOMElement> rootElem;
    1183           0 :   if (! aStartNode || ! aEndNode) {
    1184           0 :     rv = editor->GetRootElement(getter_AddRefs(rootElem));
    1185           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1186             : 
    1187           0 :     aStartNode = rootElem;
    1188           0 :     aStartOffset = 0;
    1189             : 
    1190           0 :     aEndNode = rootElem;
    1191           0 :     aEndOffset = -1;
    1192             :   }
    1193             : 
    1194           0 :   if (aEndOffset == -1) {
    1195           0 :     nsCOMPtr<nsIDOMNodeList> childNodes;
    1196           0 :     rv = aEndNode->GetChildNodes(getter_AddRefs(childNodes));
    1197           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1198             : 
    1199             :     uint32_t childCount;
    1200           0 :     rv = childNodes->GetLength(&childCount);
    1201           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1202             : 
    1203           0 :     aEndOffset = childCount;
    1204             :   }
    1205             : 
    1206             :   // sometimes we are are requested to check an empty range (possibly an empty
    1207             :   // document). This will result in assertions later.
    1208           0 :   if (aStartNode == aEndNode && aStartOffset == aEndOffset)
    1209           0 :     return NS_OK;
    1210             : 
    1211           0 :   nsCOMPtr<nsINode> startNode = do_QueryInterface(aStartNode);
    1212           0 :   nsCOMPtr<nsINode> endNode = do_QueryInterface(aEndNode);
    1213           0 :   if (aEndOffset) {
    1214           0 :     rv = range->SetStartAndEnd(startNode, aStartOffset, endNode, aEndOffset);
    1215           0 :     if (NS_WARN_IF(NS_FAILED(rv))) {
    1216           0 :       return rv;
    1217             :     }
    1218             :   } else {
    1219           0 :     int32_t endOffset = -1;
    1220           0 :     endNode = nsRange::GetContainerAndOffsetAfter(endNode, &endOffset);
    1221           0 :     rv = range->SetStartAndEnd(startNode, aStartOffset, endNode, endOffset);
    1222           0 :     if (NS_WARN_IF(NS_FAILED(rv))) {
    1223           0 :       return rv;
    1224             :     }
    1225             :   }
    1226             : 
    1227           0 :   range.swap(*aRange);
    1228           0 :   return NS_OK;
    1229             : }
    1230             : 
    1231             : nsresult
    1232           0 : mozInlineSpellChecker::SpellCheckBetweenNodes(nsIDOMNode *aStartNode,
    1233             :                                               int32_t aStartOffset,
    1234             :                                               nsIDOMNode *aEndNode,
    1235             :                                               int32_t aEndOffset)
    1236             : {
    1237           0 :   RefPtr<nsRange> range;
    1238           0 :   nsresult rv = MakeSpellCheckRange(aStartNode, aStartOffset,
    1239             :                                     aEndNode, aEndOffset,
    1240           0 :                                     getter_AddRefs(range));
    1241           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1242             : 
    1243           0 :   if (! range)
    1244           0 :     return NS_OK; // range is empty: nothing to do
    1245             : 
    1246           0 :   auto status = MakeUnique<mozInlineSpellStatus>(this);
    1247           0 :   rv = status->InitForRange(range);
    1248           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1249           0 :   return ScheduleSpellCheck(Move(status));
    1250             : }
    1251             : 
    1252             : // mozInlineSpellChecker::ShouldSpellCheckNode
    1253             : //
    1254             : //    There are certain conditions when we don't want to spell check a node. In
    1255             : //    particular quotations, moz signatures, etc. This routine returns false
    1256             : //    for these cases.
    1257             : 
    1258             : bool
    1259           0 : mozInlineSpellChecker::ShouldSpellCheckNode(nsIEditor* aEditor,
    1260             :                                             nsINode *aNode)
    1261             : {
    1262           0 :   MOZ_ASSERT(aNode);
    1263           0 :   if (!aNode->IsContent())
    1264           0 :     return false;
    1265             : 
    1266           0 :   nsIContent *content = aNode->AsContent();
    1267             : 
    1268             :   uint32_t flags;
    1269           0 :   aEditor->GetFlags(&flags);
    1270           0 :   if (flags & nsIPlaintextEditor::eEditorMailMask) {
    1271           0 :     nsIContent *parent = content->GetParent();
    1272           0 :     while (parent) {
    1273           0 :       if (parent->IsHTMLElement(nsGkAtoms::blockquote) &&
    1274           0 :           parent->AttrValueIs(kNameSpaceID_None,
    1275             :                               nsGkAtoms::type,
    1276             :                               nsGkAtoms::cite,
    1277             :                               eIgnoreCase)) {
    1278           0 :         return false;
    1279             :       }
    1280           0 :       if (parent->IsHTMLElement(nsGkAtoms::pre) &&
    1281           0 :           parent->AttrValueIs(kNameSpaceID_None,
    1282             :                               nsGkAtoms::_class,
    1283             :                               nsGkAtoms::mozsignature,
    1284             :                               eIgnoreCase)) {
    1285           0 :         return false;
    1286             :       }
    1287             : 
    1288           0 :       parent = parent->GetParent();
    1289             :     }
    1290             :   } else {
    1291             :     // Check spelling only if the node is editable, and GetSpellcheck() is true
    1292             :     // on the nearest HTMLElement ancestor.
    1293           0 :     if (!content->IsEditable()) {
    1294           0 :       return false;
    1295             :     }
    1296             : 
    1297             :     // Make sure that we can always turn on spell checking for inputs/textareas.
    1298             :     // Note that because of the previous check, at this point we know that the
    1299             :     // node is editable.
    1300           0 :     if (content->IsInAnonymousSubtree()) {
    1301           0 :       nsIContent *node = content->GetParent();
    1302           0 :       while (node && node->IsInNativeAnonymousSubtree()) {
    1303           0 :         node = node->GetParent();
    1304             :       }
    1305           0 :       nsCOMPtr<nsITextControlElement> textControl = do_QueryInterface(node);
    1306           0 :       if (textControl) {
    1307           0 :         return true;
    1308             :       }
    1309             :     }
    1310             : 
    1311             :     // Get HTML element ancestor (might be aNode itself, although probably that
    1312             :     // has to be a text node in real life here)
    1313           0 :     nsIContent *parent = content;
    1314           0 :     while (!parent->IsHTMLElement()) {
    1315           0 :       parent = parent->GetParent();
    1316           0 :       if (!parent) {
    1317           0 :         return true;
    1318             :       }
    1319             :     }
    1320             : 
    1321             :     // See if it's spellcheckable
    1322           0 :     return static_cast<nsGenericHTMLElement *>(parent)->Spellcheck();
    1323             :   }
    1324             : 
    1325           0 :   return true;
    1326             : }
    1327             : 
    1328             : // mozInlineSpellChecker::ScheduleSpellCheck
    1329             : //
    1330             : //    This is called by code to do the actual spellchecking. We will set up
    1331             : //    the proper structures for calls to DoSpellCheck.
    1332             : 
    1333             : nsresult
    1334           0 : mozInlineSpellChecker::ScheduleSpellCheck(UniquePtr<mozInlineSpellStatus>&& aStatus)
    1335             : {
    1336           0 :   if (mFullSpellCheckScheduled) {
    1337             :     // Just ignore this; we're going to spell-check everything anyway
    1338           0 :     return NS_OK;
    1339             :   }
    1340             :   // Cache the value because we are going to move aStatus's ownership to
    1341             :   // the new created mozInlineSpellResume instance.
    1342           0 :   bool isFullSpellCheck = aStatus->IsFullSpellCheck();
    1343             : 
    1344             :   RefPtr<mozInlineSpellResume> resume =
    1345           0 :     new mozInlineSpellResume(Move(aStatus), mDisabledAsyncToken);
    1346           0 :   NS_ENSURE_TRUE(resume, NS_ERROR_OUT_OF_MEMORY);
    1347             : 
    1348           0 :   nsresult rv = resume->Post();
    1349           0 :   if (NS_SUCCEEDED(rv)) {
    1350           0 :     if (isFullSpellCheck) {
    1351             :       // We're going to check everything.  Suppress further spell-check attempts
    1352             :       // until that happens.
    1353           0 :       mFullSpellCheckScheduled = true;
    1354             :     }
    1355           0 :     ChangeNumPendingSpellChecks(1);
    1356             :   }
    1357           0 :   return rv;
    1358             : }
    1359             : 
    1360             : // mozInlineSpellChecker::DoSpellCheckSelection
    1361             : //
    1362             : //    Called to re-check all misspelled words. We iterate over all ranges in
    1363             : //    the selection and call DoSpellCheck on them. This is used when a word
    1364             : //    is ignored or added to the dictionary: all instances of that word should
    1365             : //    be removed from the selection.
    1366             : //
    1367             : //    FIXME-PERFORMANCE: This takes as long as it takes and is not resumable.
    1368             : //    Typically, checking this small amount of text is relatively fast, but
    1369             : //    for large numbers of words, a lag may be noticeable.
    1370             : 
    1371             : nsresult
    1372           0 : mozInlineSpellChecker::DoSpellCheckSelection(mozInlineSpellWordUtil& aWordUtil,
    1373             :                                              Selection* aSpellCheckSelection)
    1374             : {
    1375             :   nsresult rv;
    1376             : 
    1377             :   // clear out mNumWordsInSpellSelection since we'll be rebuilding the ranges.
    1378           0 :   mNumWordsInSpellSelection = 0;
    1379             : 
    1380             :   // Since we could be modifying the ranges for the spellCheckSelection while
    1381             :   // looping on the spell check selection, keep a separate array of range
    1382             :   // elements inside the selection
    1383           0 :   nsTArray<RefPtr<nsRange>> ranges;
    1384             : 
    1385           0 :   int32_t count = aSpellCheckSelection->RangeCount();
    1386             : 
    1387           0 :   for (int32_t idx = 0; idx < count; idx++) {
    1388           0 :     nsRange *range = aSpellCheckSelection->GetRangeAt(idx);
    1389           0 :     if (range) {
    1390           0 :       ranges.AppendElement(range);
    1391             :     }
    1392             :   }
    1393             : 
    1394             :   // We have saved the ranges above. Clearing the spellcheck selection here
    1395             :   // isn't necessary (rechecking each word will modify it as necessary) but
    1396             :   // provides better performance. By ensuring that no ranges need to be
    1397             :   // removed in DoSpellCheck, we can save checking range inclusion which is
    1398             :   // slow.
    1399           0 :   aSpellCheckSelection->RemoveAllRanges();
    1400             : 
    1401             :   // We use this state object for all calls, and just update its range. Note
    1402             :   // that we don't need to call FinishInit since we will be filling in the
    1403             :   // necessary information.
    1404           0 :   auto status = MakeUnique<mozInlineSpellStatus>(this);
    1405           0 :   rv = status->InitForRange(nullptr);
    1406           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1407             : 
    1408             :   bool doneChecking;
    1409           0 :   for (int32_t idx = 0; idx < count; idx++) {
    1410             :     // We can consider this word as "added" since we know it has no spell
    1411             :     // check range over it that needs to be deleted. All the old ranges
    1412             :     // were cleared above. We also need to clear the word count so that we
    1413             :     // check all words instead of stopping early.
    1414           0 :     status->mRange = ranges[idx];
    1415             :     rv = DoSpellCheck(aWordUtil, aSpellCheckSelection, status,
    1416           0 :                       &doneChecking);
    1417           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1418           0 :     MOZ_ASSERT(doneChecking,
    1419             :                "We gave the spellchecker one word, but it didn't finish checking?!?!");
    1420             : 
    1421           0 :     status->mWordCount = 0;
    1422             :   }
    1423             : 
    1424           0 :   return NS_OK;
    1425             : }
    1426             : 
    1427             : // mozInlineSpellChecker::DoSpellCheck
    1428             : //
    1429             : //    This function checks words intersecting the given range, excluding those
    1430             : //    inside mStatus->mNoCheckRange (can be nullptr). Words inside aNoCheckRange
    1431             : //    will have any spell selection removed (this is used to hide the
    1432             : //    underlining for the word that the caret is in). aNoCheckRange should be
    1433             : //    on word boundaries.
    1434             : //
    1435             : //    mResume->mCreatedRange is a possibly nullptr range of new text that was
    1436             : //    inserted.  Inside this range, we don't bother to check whether things are
    1437             : //    inside the spellcheck selection, which speeds up large paste operations
    1438             : //    considerably.
    1439             : //
    1440             : //    Normal case when editing text by typing
    1441             : //       h e l l o   w o r k d   h o w   a r e   y o u
    1442             : //                            ^ caret
    1443             : //                   [-------] mRange
    1444             : //                   [-------] mNoCheckRange
    1445             : //      -> does nothing (range is the same as the no check range)
    1446             : //
    1447             : //    Case when pasting:
    1448             : //             [---------- pasted text ----------]
    1449             : //       h e l l o   w o r k d   h o w   a r e   y o u
    1450             : //                                                ^ caret
    1451             : //                                               [---] aNoCheckRange
    1452             : //      -> recheck all words in range except those in aNoCheckRange
    1453             : //
    1454             : //    If checking is complete, *aDoneChecking will be set. If there is more
    1455             : //    but we ran out of time, this will be false and the range will be
    1456             : //    updated with the stuff that still needs checking.
    1457             : 
    1458           0 : nsresult mozInlineSpellChecker::DoSpellCheck(mozInlineSpellWordUtil& aWordUtil,
    1459             :                                              Selection *aSpellCheckSelection,
    1460             :                                              const UniquePtr<mozInlineSpellStatus>& aStatus,
    1461             :                                              bool* aDoneChecking)
    1462             : {
    1463           0 :   *aDoneChecking = true;
    1464             : 
    1465           0 :   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
    1466             : 
    1467             :   // get the editor for ShouldSpellCheckNode, this may fail in reasonable
    1468             :   // circumstances since the editor could have gone away
    1469           0 :   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
    1470           0 :   if (! editor)
    1471           0 :     return NS_ERROR_FAILURE;
    1472             : 
    1473           0 :   if (aStatus->mRange->Collapsed())
    1474           0 :     return NS_OK;
    1475             : 
    1476             :   // see if the selection has any ranges, if not, then we can optimize checking
    1477             :   // range inclusion later (we have no ranges when we are initially checking or
    1478             :   // when there are no misspelled words yet).
    1479           0 :   int32_t originalRangeCount = aSpellCheckSelection->RangeCount();
    1480             : 
    1481             :   // set the starting DOM position to be the beginning of our range
    1482             :   {
    1483             :     // Scope for the node/offset pairs here so they don't get
    1484             :     // accidentally used later
    1485           0 :     nsINode* beginNode = aStatus->mRange->GetStartContainer();
    1486           0 :     int32_t beginOffset = aStatus->mRange->StartOffset();
    1487           0 :     nsINode* endNode = aStatus->mRange->GetEndContainer();
    1488           0 :     int32_t endOffset = aStatus->mRange->EndOffset();
    1489             : 
    1490             :     // Now check that we're still looking at a range that's under
    1491             :     // aWordUtil.GetRootNode()
    1492           0 :     nsINode* rootNode = aWordUtil.GetRootNode();
    1493           0 :     if (!nsContentUtils::ContentIsDescendantOf(beginNode, rootNode) ||
    1494           0 :         !nsContentUtils::ContentIsDescendantOf(endNode, rootNode)) {
    1495             :       // Just bail out and don't try to spell-check this
    1496           0 :       return NS_OK;
    1497             :     }
    1498             : 
    1499           0 :     aWordUtil.SetEnd(endNode, endOffset);
    1500           0 :     aWordUtil.SetPosition(beginNode, beginOffset);
    1501             :   }
    1502             : 
    1503             :   // aWordUtil.SetPosition flushes pending notifications, check editor again.
    1504             :   // XXX Uhhh, *we're* holding a strong ref to the editor.
    1505           0 :   editor = do_QueryReferent(mEditor);
    1506           0 :   if (! editor)
    1507           0 :     return NS_ERROR_FAILURE;
    1508             : 
    1509           0 :   int32_t wordsChecked = 0;
    1510           0 :   PRTime beginTime = PR_Now();
    1511             : 
    1512           0 :   nsAutoString wordText;
    1513           0 :   RefPtr<nsRange> wordRange;
    1514             :   bool dontCheckWord;
    1515           0 :   while (NS_SUCCEEDED(aWordUtil.GetNextWord(wordText,
    1516             :                                             getter_AddRefs(wordRange),
    1517           0 :                                             &dontCheckWord)) &&
    1518           0 :          wordRange) {
    1519             : 
    1520             :     // get the range for the current word.
    1521             :     nsINode *beginNode;
    1522             :     nsINode *endNode;
    1523             :     int32_t beginOffset, endOffset;
    1524             : 
    1525           0 :     ErrorResult erv;
    1526           0 :     beginNode = wordRange->GetStartContainer(erv);
    1527           0 :     endNode = wordRange->GetEndContainer(erv);
    1528           0 :     beginOffset = wordRange->GetStartOffset(erv);
    1529           0 :     endOffset = wordRange->GetEndOffset(erv);
    1530             : 
    1531             :     // see if we've done enough words in this round and run out of time.
    1532           0 :     if (wordsChecked >= INLINESPELL_MINIMUM_WORDS_BEFORE_TIMEOUT &&
    1533           0 :         PR_Now() > PRTime(beginTime + kMaxSpellCheckTimeInUsec)) {
    1534             :       // stop checking, our time limit has been exceeded.
    1535             :       #ifdef DEBUG_INLINESPELL
    1536             :         printf("We have run out of the time, schedule next round.");
    1537             :       #endif
    1538             :       // move the range to encompass the stuff that needs checking.
    1539           0 :       nsresult rv = aStatus->mRange->SetStart(beginNode, beginOffset);
    1540           0 :       if (NS_FAILED(rv)) {
    1541             :         // The range might be unhappy because the beginning is after the
    1542             :         // end. This is possible when the requested end was in the middle
    1543             :         // of a word, just ignore this situation and assume we're done.
    1544           0 :         return NS_OK;
    1545             :       }
    1546           0 :       *aDoneChecking = false;
    1547           0 :       return NS_OK;
    1548             :     }
    1549             : 
    1550             : #ifdef DEBUG_INLINESPELL
    1551             :     printf("->Got word \"%s\"", NS_ConvertUTF16toUTF8(wordText).get());
    1552             :     if (dontCheckWord)
    1553             :       printf(" (not checking)");
    1554             :     printf("\n");
    1555             : #endif
    1556             : 
    1557             :     // see if there is a spellcheck range that already intersects the word
    1558             :     // and remove it. We only need to remove old ranges, so don't bother if
    1559             :     // there were no ranges when we started out.
    1560           0 :     if (originalRangeCount > 0) {
    1561             :       // likewise, if this word is inside new text, we won't bother testing
    1562           0 :       if (!aStatus->mCreatedRange ||
    1563           0 :           !aStatus->mCreatedRange->IsPointInRange(*beginNode, beginOffset, erv)) {
    1564           0 :         nsTArray<RefPtr<nsRange>> ranges;
    1565             :         aSpellCheckSelection->GetRangesForInterval(*beginNode, beginOffset,
    1566             :                                                    *endNode, endOffset,
    1567           0 :                                                    true, ranges, erv);
    1568           0 :         ENSURE_SUCCESS(erv, erv.StealNSResult());
    1569           0 :         for (uint32_t i = 0; i < ranges.Length(); i++)
    1570           0 :           RemoveRange(aSpellCheckSelection, ranges[i]);
    1571             :       }
    1572             :     }
    1573             : 
    1574             :     // some words are special and don't need checking
    1575           0 :     if (dontCheckWord)
    1576           0 :       continue;
    1577             : 
    1578             :     // some nodes we don't spellcheck
    1579           0 :     if (!ShouldSpellCheckNode(editor, beginNode))
    1580           0 :       continue;
    1581             : 
    1582             :     // Don't check spelling if we're inside the noCheckRange. This needs to
    1583             :     // be done after we clear any old selection because the excluded word
    1584             :     // might have been previously marked.
    1585             :     //
    1586             :     // We do a simple check to see if the beginning of our word is in the
    1587             :     // exclusion range. Because the exclusion range is a multiple of a word,
    1588             :     // this is sufficient.
    1589           0 :     if (aStatus->mNoCheckRange &&
    1590           0 :         aStatus->mNoCheckRange->IsPointInRange(*beginNode, beginOffset, erv)) {
    1591           0 :       continue;
    1592             :     }
    1593             : 
    1594             :     // check spelling and add to selection if misspelled
    1595             :     bool isMisspelled;
    1596           0 :     aWordUtil.NormalizeWord(wordText);
    1597           0 :     nsresult rv = mSpellCheck->CheckCurrentWordNoSuggest(wordText.get(), &isMisspelled);
    1598           0 :     if (NS_FAILED(rv))
    1599           0 :       continue;
    1600             : 
    1601           0 :     wordsChecked++;
    1602             : 
    1603           0 :     if (isMisspelled) {
    1604             :       // misspelled words count extra toward the max
    1605           0 :       AddRange(aSpellCheckSelection, wordRange);
    1606             : 
    1607           0 :       aStatus->mWordCount ++;
    1608           0 :       if (aStatus->mWordCount >= mMaxMisspellingsPerCheck ||
    1609           0 :           SpellCheckSelectionIsFull()) {
    1610           0 :         break;
    1611             :       }
    1612             :     }
    1613             :   }
    1614             : 
    1615           0 :   return NS_OK;
    1616             : }
    1617             : 
    1618             : // An RAII helper that calls ChangeNumPendingSpellChecks on destruction.
    1619             : class AutoChangeNumPendingSpellChecks
    1620             : {
    1621             : public:
    1622           0 :   AutoChangeNumPendingSpellChecks(mozInlineSpellChecker* aSpellChecker,
    1623             :                                   int32_t aDelta)
    1624           0 :     : mSpellChecker(aSpellChecker), mDelta(aDelta) {}
    1625             : 
    1626           0 :   ~AutoChangeNumPendingSpellChecks()
    1627           0 :   {
    1628           0 :     mSpellChecker->ChangeNumPendingSpellChecks(mDelta);
    1629           0 :   }
    1630             : 
    1631             : private:
    1632             :   RefPtr<mozInlineSpellChecker> mSpellChecker;
    1633             :   int32_t mDelta;
    1634             : };
    1635             : 
    1636             : // mozInlineSpellChecker::ResumeCheck
    1637             : //
    1638             : //    Called by the resume event when it fires. We will try to pick up where
    1639             : //    the last resume left off.
    1640             : 
    1641             : nsresult
    1642           0 : mozInlineSpellChecker::ResumeCheck(UniquePtr<mozInlineSpellStatus>&& aStatus)
    1643             : {
    1644             :   // Observers should be notified that spell check has ended only after spell
    1645             :   // check is done below, but since there are many early returns in this method
    1646             :   // and the number of pending spell checks must be decremented regardless of
    1647             :   // whether the spell check actually happens, use this RAII object.
    1648           0 :   AutoChangeNumPendingSpellChecks autoChangeNumPending(this, -1);
    1649             : 
    1650           0 :   if (aStatus->IsFullSpellCheck()) {
    1651             :     // Allow posting new spellcheck resume events from inside
    1652             :     // ResumeCheck, now that we're actually firing.
    1653           0 :     NS_ASSERTION(mFullSpellCheckScheduled,
    1654             :                  "How could this be false?  The full spell check is "
    1655             :                  "calling us!!");
    1656           0 :     mFullSpellCheckScheduled = false;
    1657             :   }
    1658             : 
    1659           0 :   if (! mSpellCheck)
    1660           0 :     return NS_OK; // spell checking has been turned off
    1661             : 
    1662           0 :   nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor);
    1663           0 :   if (! editor)
    1664           0 :     return NS_OK; // editor is gone
    1665             : 
    1666           0 :   mozInlineSpellWordUtil wordUtil;
    1667           0 :   nsresult rv = wordUtil.Init(mEditor);
    1668           0 :   if (NS_FAILED(rv))
    1669           0 :     return NS_OK; // editor doesn't like us, don't assert
    1670             : 
    1671           0 :   nsCOMPtr<nsISelection> spellCheckSelectionRef;
    1672           0 :   rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelectionRef));
    1673           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1674             : 
    1675             :   auto spellCheckSelection =
    1676           0 :     static_cast<Selection *>(spellCheckSelectionRef.get());
    1677             : 
    1678           0 :   nsAutoString currentDictionary;
    1679           0 :   rv = mSpellCheck->GetCurrentDictionary(currentDictionary);
    1680           0 :   if (NS_FAILED(rv)) {
    1681             :     // no active dictionary
    1682           0 :     int32_t count = spellCheckSelection->RangeCount();
    1683           0 :     for (int32_t index = count - 1; index >= 0; index--) {
    1684           0 :       nsRange *checkRange = spellCheckSelection->GetRangeAt(index);
    1685           0 :       if (checkRange) {
    1686           0 :         RemoveRange(spellCheckSelection, checkRange);
    1687             :       }
    1688             :     }
    1689           0 :     return NS_OK;
    1690             :   }
    1691             : 
    1692           0 :   CleanupRangesInSelection(spellCheckSelection);
    1693             : 
    1694           0 :   rv = aStatus->FinishInitOnEvent(wordUtil);
    1695           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1696           0 :   if (! aStatus->mRange)
    1697           0 :     return NS_OK; // empty range, nothing to do
    1698             : 
    1699           0 :   bool doneChecking = true;
    1700           0 :   if (aStatus->mOp == mozInlineSpellStatus::eOpSelection)
    1701           0 :     rv = DoSpellCheckSelection(wordUtil, spellCheckSelection);
    1702             :   else
    1703           0 :     rv = DoSpellCheck(wordUtil, spellCheckSelection, aStatus, &doneChecking);
    1704           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1705             : 
    1706           0 :   if (! doneChecking)
    1707           0 :     rv = ScheduleSpellCheck(Move(aStatus));
    1708           0 :   return rv;
    1709             : }
    1710             : 
    1711             : // mozInlineSpellChecker::IsPointInSelection
    1712             : //
    1713             : //    Determines if a given (node,offset) point is inside the given
    1714             : //    selection. If so, the specific range of the selection that
    1715             : //    intersects is places in *aRange. (There may be multiple disjoint
    1716             : //    ranges in a selection.)
    1717             : //
    1718             : //    If there is no intersection, *aRange will be nullptr.
    1719             : 
    1720             : nsresult
    1721           0 : mozInlineSpellChecker::IsPointInSelection(nsISelection *aSelection,
    1722             :                                           nsIDOMNode *aNode,
    1723             :                                           int32_t aOffset,
    1724             :                                           nsIDOMRange **aRange)
    1725             : {
    1726           0 :   *aRange = nullptr;
    1727             : 
    1728           0 :   nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(aSelection));
    1729             : 
    1730           0 :   nsTArray<nsRange*> ranges;
    1731           0 :   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
    1732           0 :   nsresult rv = privSel->GetRangesForIntervalArray(node, aOffset, node, aOffset,
    1733           0 :                                                    true, &ranges);
    1734           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1735             : 
    1736           0 :   if (ranges.Length() == 0)
    1737           0 :     return NS_OK; // no matches
    1738             : 
    1739             :   // there may be more than one range returned, and we don't know what do
    1740             :   // do with that, so just get the first one
    1741           0 :   NS_ADDREF(*aRange = ranges[0]);
    1742           0 :   return NS_OK;
    1743             : }
    1744             : 
    1745             : nsresult
    1746           0 : mozInlineSpellChecker::CleanupRangesInSelection(Selection *aSelection)
    1747             : {
    1748             :   // integrity check - remove ranges that have collapsed to nothing. This
    1749             :   // can happen if the node containing a highlighted word was removed.
    1750           0 :   if (!aSelection)
    1751           0 :     return NS_ERROR_FAILURE;
    1752             : 
    1753           0 :   int32_t count = aSelection->RangeCount();
    1754             : 
    1755           0 :   for (int32_t index = 0; index < count; index++)
    1756             :   {
    1757           0 :     nsRange *checkRange = aSelection->GetRangeAt(index);
    1758           0 :     if (checkRange)
    1759             :     {
    1760           0 :       if (checkRange->Collapsed())
    1761             :       {
    1762           0 :         RemoveRange(aSelection, checkRange);
    1763           0 :         index--;
    1764           0 :         count--;
    1765             :       }
    1766             :     }
    1767             :   }
    1768             : 
    1769           0 :   return NS_OK;
    1770             : }
    1771             : 
    1772             : 
    1773             : // mozInlineSpellChecker::RemoveRange
    1774             : //
    1775             : //    For performance reasons, we have an upper bound on the number of word
    1776             : //    ranges  in the spell check selection. When removing a range from the
    1777             : //    selection, we need to decrement mNumWordsInSpellSelection
    1778             : 
    1779             : nsresult
    1780           0 : mozInlineSpellChecker::RemoveRange(Selection *aSpellCheckSelection,
    1781             :                                    nsRange *aRange)
    1782             : {
    1783           0 :   NS_ENSURE_ARG_POINTER(aSpellCheckSelection);
    1784           0 :   NS_ENSURE_ARG_POINTER(aRange);
    1785             : 
    1786           0 :   ErrorResult rv;
    1787           0 :   aSpellCheckSelection->RemoveRange(*aRange, rv);
    1788           0 :   if (!rv.Failed() && mNumWordsInSpellSelection)
    1789           0 :     mNumWordsInSpellSelection--;
    1790             : 
    1791           0 :   return rv.StealNSResult();
    1792             : }
    1793             : 
    1794             : 
    1795             : // mozInlineSpellChecker::AddRange
    1796             : //
    1797             : //    For performance reasons, we have an upper bound on the number of word
    1798             : //    ranges we'll add to the spell check selection. Once we reach that upper
    1799             : //    bound, stop adding the ranges
    1800             : 
    1801             : nsresult
    1802           0 : mozInlineSpellChecker::AddRange(nsISelection* aSpellCheckSelection,
    1803             :                                 nsIDOMRange* aRange)
    1804             : {
    1805           0 :   NS_ENSURE_ARG_POINTER(aSpellCheckSelection);
    1806           0 :   NS_ENSURE_ARG_POINTER(aRange);
    1807             : 
    1808           0 :   nsresult rv = NS_OK;
    1809             : 
    1810           0 :   if (!SpellCheckSelectionIsFull())
    1811             :   {
    1812           0 :     rv = aSpellCheckSelection->AddRange(aRange);
    1813           0 :     if (NS_SUCCEEDED(rv))
    1814           0 :       mNumWordsInSpellSelection++;
    1815             :   }
    1816             : 
    1817           0 :   return rv;
    1818             : }
    1819             : 
    1820           0 : nsresult mozInlineSpellChecker::GetSpellCheckSelection(nsISelection ** aSpellCheckSelection)
    1821             : {
    1822           0 :   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
    1823           0 :   NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
    1824             : 
    1825           0 :   nsCOMPtr<nsISelectionController> selcon;
    1826           0 :   nsresult rv = editor->GetSelectionController(getter_AddRefs(selcon));
    1827           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1828             : 
    1829           0 :   return selcon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, aSpellCheckSelection);
    1830             : }
    1831             : 
    1832           0 : nsresult mozInlineSpellChecker::SaveCurrentSelectionPosition()
    1833             : {
    1834           0 :   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
    1835           0 :   NS_ENSURE_TRUE(editor, NS_OK);
    1836             : 
    1837             :   // figure out the old caret position based on the current selection
    1838           0 :   nsCOMPtr<nsISelection> selection;
    1839           0 :   nsresult rv = editor->GetSelection(getter_AddRefs(selection));
    1840           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1841             : 
    1842           0 :   rv = selection->GetFocusNode(getter_AddRefs(mCurrentSelectionAnchorNode));
    1843           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1844             : 
    1845           0 :   selection->GetFocusOffset(&mCurrentSelectionOffset);
    1846             : 
    1847           0 :   return NS_OK;
    1848             : }
    1849             : 
    1850             : // This is a copy of nsContentUtils::ContentIsDescendantOf. Another crime
    1851             : // for XPCOM's rap sheet
    1852             : bool // static
    1853           0 : ContentIsDescendantOf(nsINode* aPossibleDescendant,
    1854             :                       nsINode* aPossibleAncestor)
    1855             : {
    1856           0 :   NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!");
    1857           0 :   NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!");
    1858             : 
    1859           0 :   do {
    1860           0 :     if (aPossibleDescendant == aPossibleAncestor)
    1861           0 :       return true;
    1862           0 :     aPossibleDescendant = aPossibleDescendant->GetParentNode();
    1863           0 :   } while (aPossibleDescendant);
    1864             : 
    1865           0 :   return false;
    1866             : }
    1867             : 
    1868             : // mozInlineSpellChecker::HandleNavigationEvent
    1869             : //
    1870             : //    Acts upon mouse clicks and keyboard navigation changes, spell checking
    1871             : //    the previous word if the new navigation location moves us to another
    1872             : //    word.
    1873             : //
    1874             : //    This is complicated by the fact that our mouse events are happening after
    1875             : //    selection has been changed to account for the mouse click. But keyboard
    1876             : //    events are happening before the caret selection has changed. Working
    1877             : //    around this by letting keyboard events setting forceWordSpellCheck to
    1878             : //    true. aNewPositionOffset also tries to work around this for the
    1879             : //    DOM_VK_RIGHT and DOM_VK_LEFT cases.
    1880             : 
    1881             : nsresult
    1882           0 : mozInlineSpellChecker::HandleNavigationEvent(bool aForceWordSpellCheck,
    1883             :                                              int32_t aNewPositionOffset)
    1884             : {
    1885             :   nsresult rv;
    1886             : 
    1887             :   // If we already handled the navigation event and there is no possibility
    1888             :   // anything has changed since then, we don't have to do anything. This
    1889             :   // optimization makes a noticeable difference when you hold down a navigation
    1890             :   // key like Page Down.
    1891           0 :   if (! mNeedsCheckAfterNavigation)
    1892           0 :     return NS_OK;
    1893             : 
    1894           0 :   nsCOMPtr<nsIDOMNode> currentAnchorNode = mCurrentSelectionAnchorNode;
    1895           0 :   int32_t currentAnchorOffset = mCurrentSelectionOffset;
    1896             : 
    1897             :   // now remember the new focus position resulting from the event
    1898           0 :   rv = SaveCurrentSelectionPosition();
    1899           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1900             : 
    1901             :   bool shouldPost;
    1902           0 :   auto status = MakeUnique<mozInlineSpellStatus>(this);
    1903           0 :   rv = status->InitForNavigation(aForceWordSpellCheck, aNewPositionOffset,
    1904             :                                  currentAnchorNode, currentAnchorOffset,
    1905             :                                  mCurrentSelectionAnchorNode,
    1906             :                                  mCurrentSelectionOffset,
    1907           0 :                                  &shouldPost);
    1908           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1909           0 :   if (shouldPost) {
    1910           0 :     rv = ScheduleSpellCheck(Move(status));
    1911           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1912             :   }
    1913             : 
    1914           0 :   return NS_OK;
    1915             : }
    1916             : 
    1917           0 : NS_IMETHODIMP mozInlineSpellChecker::HandleEvent(nsIDOMEvent* aEvent)
    1918             : {
    1919           0 :   nsAutoString eventType;
    1920           0 :   aEvent->GetType(eventType);
    1921             : 
    1922           0 :   if (eventType.EqualsLiteral("blur")) {
    1923           0 :     return Blur(aEvent);
    1924             :   }
    1925           0 :   if (eventType.EqualsLiteral("click")) {
    1926           0 :     return MouseClick(aEvent);
    1927             :   }
    1928           0 :   if (eventType.EqualsLiteral("keypress")) {
    1929           0 :     return KeyPress(aEvent);
    1930             :   }
    1931             : 
    1932           0 :   return NS_OK;
    1933             : }
    1934             : 
    1935           0 : nsresult mozInlineSpellChecker::Blur(nsIDOMEvent* aEvent)
    1936             : {
    1937             :   // force spellcheck on blur, for instance when tabbing out of a textbox
    1938           0 :   HandleNavigationEvent(true);
    1939           0 :   return NS_OK;
    1940             : }
    1941             : 
    1942           0 : nsresult mozInlineSpellChecker::MouseClick(nsIDOMEvent *aMouseEvent)
    1943             : {
    1944           0 :   nsCOMPtr<nsIDOMMouseEvent>mouseEvent = do_QueryInterface(aMouseEvent);
    1945           0 :   NS_ENSURE_TRUE(mouseEvent, NS_OK);
    1946             : 
    1947             :   // ignore any errors from HandleNavigationEvent as we don't want to prevent
    1948             :   // anyone else from seeing this event.
    1949             :   int16_t button;
    1950           0 :   mouseEvent->GetButton(&button);
    1951           0 :   HandleNavigationEvent(button != 0);
    1952           0 :   return NS_OK;
    1953             : }
    1954             : 
    1955           0 : nsresult mozInlineSpellChecker::KeyPress(nsIDOMEvent* aKeyEvent)
    1956             : {
    1957           0 :   nsCOMPtr<nsIDOMKeyEvent>keyEvent = do_QueryInterface(aKeyEvent);
    1958           0 :   NS_ENSURE_TRUE(keyEvent, NS_OK);
    1959             : 
    1960             :   uint32_t keyCode;
    1961           0 :   keyEvent->GetKeyCode(&keyCode);
    1962             : 
    1963             :   // we only care about navigation keys that moved selection
    1964           0 :   switch (keyCode)
    1965             :   {
    1966             :     case nsIDOMKeyEvent::DOM_VK_RIGHT:
    1967             :     case nsIDOMKeyEvent::DOM_VK_LEFT:
    1968           0 :       HandleNavigationEvent(false, keyCode == nsIDOMKeyEvent::DOM_VK_RIGHT ? 1 : -1);
    1969           0 :       break;
    1970             :     case nsIDOMKeyEvent::DOM_VK_UP:
    1971             :     case nsIDOMKeyEvent::DOM_VK_DOWN:
    1972             :     case nsIDOMKeyEvent::DOM_VK_HOME:
    1973             :     case nsIDOMKeyEvent::DOM_VK_END:
    1974             :     case nsIDOMKeyEvent::DOM_VK_PAGE_UP:
    1975             :     case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN:
    1976           0 :       HandleNavigationEvent(true /* force a spelling correction */);
    1977           0 :       break;
    1978             :   }
    1979             : 
    1980           0 :   return NS_OK;
    1981             : }
    1982             : 
    1983             : // Used as the nsIEditorSpellCheck::UpdateCurrentDictionary callback.
    1984             : class UpdateCurrentDictionaryCallback final : public nsIEditorSpellCheckCallback
    1985             : {
    1986             : public:
    1987             :   NS_DECL_ISUPPORTS
    1988             : 
    1989           0 :   explicit UpdateCurrentDictionaryCallback(mozInlineSpellChecker* aSpellChecker,
    1990             :                                            uint32_t aDisabledAsyncToken)
    1991           0 :     : mSpellChecker(aSpellChecker), mDisabledAsyncToken(aDisabledAsyncToken) {}
    1992             : 
    1993           0 :   NS_IMETHOD EditorSpellCheckDone() override
    1994             :   {
    1995             :     // Ignore this callback if SetEnableRealTimeSpell(false) was called after
    1996             :     // the UpdateCurrentDictionary call that triggered it.
    1997           0 :     return mSpellChecker->mDisabledAsyncToken > mDisabledAsyncToken ?
    1998             :            NS_OK :
    1999           0 :            mSpellChecker->CurrentDictionaryUpdated();
    2000             :   }
    2001             : 
    2002             : private:
    2003           0 :   ~UpdateCurrentDictionaryCallback() {}
    2004             : 
    2005             :   RefPtr<mozInlineSpellChecker> mSpellChecker;
    2006             :   uint32_t mDisabledAsyncToken;
    2007             : };
    2008           0 : NS_IMPL_ISUPPORTS(UpdateCurrentDictionaryCallback, nsIEditorSpellCheckCallback)
    2009             : 
    2010           0 : NS_IMETHODIMP mozInlineSpellChecker::UpdateCurrentDictionary()
    2011             : {
    2012             :   // mSpellCheck is null and mPendingSpellCheck is nonnull while the spell
    2013             :   // checker is being initialized.  Calling UpdateCurrentDictionary on
    2014             :   // mPendingSpellCheck simply queues the dictionary update after the init.
    2015             :   nsCOMPtr<nsIEditorSpellCheck> spellCheck = mSpellCheck ? mSpellCheck :
    2016           0 :                                              mPendingSpellCheck;
    2017           0 :   if (!spellCheck) {
    2018           0 :     return NS_OK;
    2019             :   }
    2020             : 
    2021           0 :   if (NS_FAILED(spellCheck->GetCurrentDictionary(mPreviousDictionary))) {
    2022           0 :     mPreviousDictionary.Truncate();
    2023             :   }
    2024             : 
    2025             :   RefPtr<UpdateCurrentDictionaryCallback> cb =
    2026           0 :     new UpdateCurrentDictionaryCallback(this, mDisabledAsyncToken);
    2027           0 :   NS_ENSURE_STATE(cb);
    2028           0 :   nsresult rv = spellCheck->UpdateCurrentDictionary(cb);
    2029           0 :   if (NS_FAILED(rv)) {
    2030           0 :     cb = nullptr;
    2031           0 :     return rv;
    2032             :   }
    2033           0 :   mNumPendingUpdateCurrentDictionary++;
    2034           0 :   ChangeNumPendingSpellChecks(1);
    2035             : 
    2036           0 :   return NS_OK;
    2037             : }
    2038             : 
    2039             : // Called when nsIEditorSpellCheck::UpdateCurrentDictionary completes.
    2040           0 : nsresult mozInlineSpellChecker::CurrentDictionaryUpdated()
    2041             : {
    2042           0 :   mNumPendingUpdateCurrentDictionary--;
    2043           0 :   NS_ASSERTION(mNumPendingUpdateCurrentDictionary >= 0,
    2044             :                "CurrentDictionaryUpdated called without corresponding "
    2045             :                "UpdateCurrentDictionary call!");
    2046           0 :   ChangeNumPendingSpellChecks(-1);
    2047             : 
    2048           0 :   nsAutoString currentDictionary;
    2049           0 :   if (!mSpellCheck ||
    2050           0 :       NS_FAILED(mSpellCheck->GetCurrentDictionary(currentDictionary))) {
    2051           0 :     currentDictionary.Truncate();
    2052             :   }
    2053             : 
    2054           0 :   nsresult rv = SpellCheckRange(nullptr);
    2055           0 :   NS_ENSURE_SUCCESS(rv, rv);
    2056             : 
    2057           0 :   return NS_OK;
    2058             : }
    2059             : 
    2060             : NS_IMETHODIMP
    2061           0 : mozInlineSpellChecker::GetSpellCheckPending(bool* aPending)
    2062             : {
    2063           0 :   *aPending = mNumPendingSpellChecks > 0;
    2064           0 :   return NS_OK;
    2065             : }

Generated by: LCOV version 1.13