extensions/spellcheck/src/mozInlineSpellChecker.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2066 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +/**
    1.10 + * This class is called by the editor to handle spellchecking after various
    1.11 + * events. The main entrypoint is SpellCheckAfterEditorChange, which is called
    1.12 + * when the text is changed.
    1.13 + *
    1.14 + * It is VERY IMPORTANT that we do NOT do any operations that might cause DOM
    1.15 + * notifications to be flushed when we are called from the editor. This is
    1.16 + * because the call might originate from a frame, and flushing the
    1.17 + * notifications might cause that frame to be deleted.
    1.18 + *
    1.19 + * Using the WordUtil class to find words causes DOM notifications to be
    1.20 + * flushed because it asks for style information. As a result, we post an event
    1.21 + * and do all of the spellchecking in that event handler, which occurs later.
    1.22 + * We store all DOM pointers in ranges because they are kept up-to-date with
    1.23 + * DOM changes that may have happened while the event was on the queue.
    1.24 + *
    1.25 + * We also allow the spellcheck to be suspended and resumed later. This makes
    1.26 + * large pastes or initializations with a lot of text not hang the browser UI.
    1.27 + *
    1.28 + * An optimization is the mNeedsCheckAfterNavigation flag. This is set to
    1.29 + * true when we get any change, and false once there is no possibility
    1.30 + * something changed that we need to check on navigation. Navigation events
    1.31 + * tend to be a little tricky because we want to check the current word on
    1.32 + * exit if something has changed. If we navigate inside the word, we don't want
    1.33 + * to do anything. As a result, this flag is cleared in FinishNavigationEvent
    1.34 + * when we know that we are checking as a result of navigation.
    1.35 + */
    1.36 +
    1.37 +#include "mozInlineSpellChecker.h"
    1.38 +#include "mozInlineSpellWordUtil.h"
    1.39 +#include "mozISpellI18NManager.h"
    1.40 +#include "nsCOMPtr.h"
    1.41 +#include "nsCRT.h"
    1.42 +#include "nsIDOMNode.h"
    1.43 +#include "nsIDOMDocument.h"
    1.44 +#include "nsIDOMElement.h"
    1.45 +#include "nsIDOMHTMLElement.h"
    1.46 +#include "nsIDOMMouseEvent.h"
    1.47 +#include "nsIDOMKeyEvent.h"
    1.48 +#include "nsIDOMNode.h"
    1.49 +#include "nsIDOMNodeList.h"
    1.50 +#include "nsRange.h"
    1.51 +#include "nsIPlaintextEditor.h"
    1.52 +#include "nsIPrefBranch.h"
    1.53 +#include "nsIPrefService.h"
    1.54 +#include "nsIRunnable.h"
    1.55 +#include "nsISelection.h"
    1.56 +#include "nsISelectionPrivate.h"
    1.57 +#include "nsISelectionController.h"
    1.58 +#include "nsIServiceManager.h"
    1.59 +#include "nsITextServicesFilter.h"
    1.60 +#include "nsString.h"
    1.61 +#include "nsThreadUtils.h"
    1.62 +#include "nsUnicharUtils.h"
    1.63 +#include "nsIContent.h"
    1.64 +#include "nsRange.h"
    1.65 +#include "nsContentUtils.h"
    1.66 +#include "nsEditor.h"
    1.67 +#include "mozilla/Services.h"
    1.68 +#include "nsIObserverService.h"
    1.69 +#include "nsITextControlElement.h"
    1.70 +#include "prtime.h" 
    1.71 +
    1.72 +using namespace mozilla::dom;
    1.73 +
    1.74 +// Set to spew messages to the console about what is happening.
    1.75 +//#define DEBUG_INLINESPELL
    1.76 +
    1.77 +// the number of milliseconds that we will take at once to do spellchecking
    1.78 +#define INLINESPELL_CHECK_TIMEOUT 50
    1.79 +
    1.80 +// The number of words to check before we look at the time to see if
    1.81 +// INLINESPELL_CHECK_TIMEOUT ms have elapsed. This prevents us from spending
    1.82 +// too much time checking the clock. Note that misspelled words count for
    1.83 +// more than one word in this calculation.
    1.84 +#define INLINESPELL_TIMEOUT_CHECK_FREQUENCY 50
    1.85 +
    1.86 +// This number is the number of checked words a misspelled word counts for
    1.87 +// when we're checking the time to see if the alloted time is up for
    1.88 +// spellchecking. Misspelled words take longer to process since we have to
    1.89 +// create a range, so they count more. The exact number isn't very important
    1.90 +// since this just controls how often we check the current time.
    1.91 +#define MISSPELLED_WORD_COUNT_PENALTY 4
    1.92 +
    1.93 +// These notifications are broadcast when spell check starts and ends.  STARTED
    1.94 +// must always be followed by ENDED.
    1.95 +#define INLINESPELL_STARTED_TOPIC "inlineSpellChecker-spellCheck-started"
    1.96 +#define INLINESPELL_ENDED_TOPIC "inlineSpellChecker-spellCheck-ended"
    1.97 +
    1.98 +static bool ContentIsDescendantOf(nsINode* aPossibleDescendant,
    1.99 +                                    nsINode* aPossibleAncestor);
   1.100 +
   1.101 +static const char kMaxSpellCheckSelectionSize[] = "extensions.spellcheck.inline.max-misspellings";
   1.102 +
   1.103 +mozInlineSpellStatus::mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker)
   1.104 +    : mSpellChecker(aSpellChecker), mWordCount(0)
   1.105 +{
   1.106 +}
   1.107 +
   1.108 +// mozInlineSpellStatus::InitForEditorChange
   1.109 +//
   1.110 +//    This is the most complicated case. For changes, we need to compute the
   1.111 +//    range of stuff that changed based on the old and new caret positions,
   1.112 +//    as well as use a range possibly provided by the editor (start and end,
   1.113 +//    which are usually nullptr) to get a range with the union of these.
   1.114 +
   1.115 +nsresult
   1.116 +mozInlineSpellStatus::InitForEditorChange(
   1.117 +    EditAction aAction,
   1.118 +    nsIDOMNode* aAnchorNode, int32_t aAnchorOffset,
   1.119 +    nsIDOMNode* aPreviousNode, int32_t aPreviousOffset,
   1.120 +    nsIDOMNode* aStartNode, int32_t aStartOffset,
   1.121 +    nsIDOMNode* aEndNode, int32_t aEndOffset)
   1.122 +{
   1.123 +  nsresult rv;
   1.124 +
   1.125 +  nsCOMPtr<nsIDOMDocument> doc;
   1.126 +  rv = GetDocument(getter_AddRefs(doc));
   1.127 +  NS_ENSURE_SUCCESS(rv, rv);
   1.128 +
   1.129 +  // save the anchor point as a range so we can find the current word later
   1.130 +  rv = PositionToCollapsedRange(doc, aAnchorNode, aAnchorOffset,
   1.131 +                                getter_AddRefs(mAnchorRange));
   1.132 +  NS_ENSURE_SUCCESS(rv, rv);
   1.133 +
   1.134 +  if (aAction == EditAction::deleteSelection) {
   1.135 +    // Deletes are easy, the range is just the current anchor. We set the range
   1.136 +    // to check to be empty, FinishInitOnEvent will fill in the range to be
   1.137 +    // the current word.
   1.138 +    mOp = eOpChangeDelete;
   1.139 +    mRange = nullptr;
   1.140 +    return NS_OK;
   1.141 +  }
   1.142 +
   1.143 +  mOp = eOpChange;
   1.144 +
   1.145 +  // range to check
   1.146 +  nsCOMPtr<nsINode> prevNode = do_QueryInterface(aPreviousNode);
   1.147 +  NS_ENSURE_STATE(prevNode);
   1.148 +
   1.149 +  mRange = new nsRange(prevNode);
   1.150 +
   1.151 +  // ...we need to put the start and end in the correct order
   1.152 +  int16_t cmpResult;
   1.153 +  rv = mAnchorRange->ComparePoint(aPreviousNode, aPreviousOffset, &cmpResult);
   1.154 +  NS_ENSURE_SUCCESS(rv, rv);
   1.155 +  if (cmpResult < 0) {
   1.156 +    // previous anchor node is before the current anchor
   1.157 +    rv = mRange->SetStart(aPreviousNode, aPreviousOffset);
   1.158 +    NS_ENSURE_SUCCESS(rv, rv);
   1.159 +    rv = mRange->SetEnd(aAnchorNode, aAnchorOffset);
   1.160 +  } else {
   1.161 +    // previous anchor node is after (or the same as) the current anchor
   1.162 +    rv = mRange->SetStart(aAnchorNode, aAnchorOffset);
   1.163 +    NS_ENSURE_SUCCESS(rv, rv);
   1.164 +    rv = mRange->SetEnd(aPreviousNode, aPreviousOffset);
   1.165 +  }
   1.166 +  NS_ENSURE_SUCCESS(rv, rv);
   1.167 +
   1.168 +  // On insert save this range: DoSpellCheck optimizes things in this range.
   1.169 +  // Otherwise, just leave this nullptr.
   1.170 +  if (aAction == EditAction::insertText)
   1.171 +    mCreatedRange = mRange;
   1.172 +
   1.173 +  // if we were given a range, we need to expand our range to encompass it
   1.174 +  if (aStartNode && aEndNode) {
   1.175 +    rv = mRange->ComparePoint(aStartNode, aStartOffset, &cmpResult);
   1.176 +    NS_ENSURE_SUCCESS(rv, rv);
   1.177 +    if (cmpResult < 0) { // given range starts before
   1.178 +      rv = mRange->SetStart(aStartNode, aStartOffset);
   1.179 +      NS_ENSURE_SUCCESS(rv, rv);
   1.180 +    }
   1.181 +
   1.182 +    rv = mRange->ComparePoint(aEndNode, aEndOffset, &cmpResult);
   1.183 +    NS_ENSURE_SUCCESS(rv, rv);
   1.184 +    if (cmpResult > 0) { // given range ends after
   1.185 +      rv = mRange->SetEnd(aEndNode, aEndOffset);
   1.186 +      NS_ENSURE_SUCCESS(rv, rv);
   1.187 +    }
   1.188 +  }
   1.189 +
   1.190 +  return NS_OK;
   1.191 +}
   1.192 +
   1.193 +// mozInlineSpellStatis::InitForNavigation
   1.194 +//
   1.195 +//    For navigation events, we just need to store the new and old positions.
   1.196 +//
   1.197 +//    In some cases, we detect that we shouldn't check. If this event should
   1.198 +//    not be processed, *aContinue will be false.
   1.199 +
   1.200 +nsresult
   1.201 +mozInlineSpellStatus::InitForNavigation(
   1.202 +    bool aForceCheck, int32_t aNewPositionOffset,
   1.203 +    nsIDOMNode* aOldAnchorNode, int32_t aOldAnchorOffset,
   1.204 +    nsIDOMNode* aNewAnchorNode, int32_t aNewAnchorOffset,
   1.205 +    bool* aContinue)
   1.206 +{
   1.207 +  nsresult rv;
   1.208 +  mOp = eOpNavigation;
   1.209 +
   1.210 +  mForceNavigationWordCheck = aForceCheck;
   1.211 +  mNewNavigationPositionOffset = aNewPositionOffset;
   1.212 +
   1.213 +  // get the root node for checking
   1.214 +  nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor, &rv);
   1.215 +  NS_ENSURE_SUCCESS(rv, rv);
   1.216 +  nsCOMPtr<nsIDOMElement> rootElt;
   1.217 +  rv = editor->GetRootElement(getter_AddRefs(rootElt));
   1.218 +  NS_ENSURE_SUCCESS(rv, rv);
   1.219 +
   1.220 +  // the anchor node might not be in the DOM anymore, check
   1.221 +  nsCOMPtr<nsINode> root = do_QueryInterface(rootElt, &rv);
   1.222 +  NS_ENSURE_SUCCESS(rv, rv);
   1.223 +  nsCOMPtr<nsINode> currentAnchor = do_QueryInterface(aOldAnchorNode, &rv);
   1.224 +  NS_ENSURE_SUCCESS(rv, rv);
   1.225 +  if (root && currentAnchor && ! ContentIsDescendantOf(currentAnchor, root)) {
   1.226 +    *aContinue = false;
   1.227 +    return NS_OK;
   1.228 +  }
   1.229 +
   1.230 +  nsCOMPtr<nsIDOMDocument> doc;
   1.231 +  rv = GetDocument(getter_AddRefs(doc));
   1.232 +  NS_ENSURE_SUCCESS(rv, rv);
   1.233 +
   1.234 +  rv = PositionToCollapsedRange(doc, aOldAnchorNode, aOldAnchorOffset,
   1.235 +                                getter_AddRefs(mOldNavigationAnchorRange));
   1.236 +  NS_ENSURE_SUCCESS(rv, rv);
   1.237 +  rv = PositionToCollapsedRange(doc, aNewAnchorNode, aNewAnchorOffset,
   1.238 +                                getter_AddRefs(mAnchorRange));
   1.239 +  NS_ENSURE_SUCCESS(rv, rv);
   1.240 +
   1.241 +  *aContinue = true;
   1.242 +  return NS_OK;
   1.243 +}
   1.244 +
   1.245 +// mozInlineSpellStatus::InitForSelection
   1.246 +//
   1.247 +//    It is easy for selections since we always re-check the spellcheck
   1.248 +//    selection.
   1.249 +
   1.250 +nsresult
   1.251 +mozInlineSpellStatus::InitForSelection()
   1.252 +{
   1.253 +  mOp = eOpSelection;
   1.254 +  return NS_OK;
   1.255 +}
   1.256 +
   1.257 +// mozInlineSpellStatus::InitForRange
   1.258 +//
   1.259 +//    Called to cause the spellcheck of the given range. This will look like
   1.260 +//    a change operation over the given range.
   1.261 +
   1.262 +nsresult
   1.263 +mozInlineSpellStatus::InitForRange(nsRange* aRange)
   1.264 +{
   1.265 +  mOp = eOpChange;
   1.266 +  mRange = aRange;
   1.267 +  return NS_OK;
   1.268 +}
   1.269 +
   1.270 +// mozInlineSpellStatus::FinishInitOnEvent
   1.271 +//
   1.272 +//    Called when the event is triggered to complete initialization that
   1.273 +//    might require the WordUtil. This calls to the operation-specific
   1.274 +//    initializer, and also sets the range to be the entire element if it
   1.275 +//    is nullptr.
   1.276 +//
   1.277 +//    Watch out: the range might still be nullptr if there is nothing to do,
   1.278 +//    the caller will have to check for this.
   1.279 +
   1.280 +nsresult
   1.281 +mozInlineSpellStatus::FinishInitOnEvent(mozInlineSpellWordUtil& aWordUtil)
   1.282 +{
   1.283 +  nsresult rv;
   1.284 +  if (! mRange) {
   1.285 +    rv = mSpellChecker->MakeSpellCheckRange(nullptr, 0, nullptr, 0,
   1.286 +                                            getter_AddRefs(mRange));
   1.287 +    NS_ENSURE_SUCCESS(rv, rv);
   1.288 +  }
   1.289 +
   1.290 +  switch (mOp) {
   1.291 +    case eOpChange:
   1.292 +      if (mAnchorRange)
   1.293 +        return FillNoCheckRangeFromAnchor(aWordUtil);
   1.294 +      break;
   1.295 +    case eOpChangeDelete:
   1.296 +      if (mAnchorRange) {
   1.297 +        rv = FillNoCheckRangeFromAnchor(aWordUtil);
   1.298 +        NS_ENSURE_SUCCESS(rv, rv);
   1.299 +      }
   1.300 +      // Delete events will have no range for the changed text (because it was
   1.301 +      // deleted), and InitForEditorChange will set it to nullptr. Here, we select
   1.302 +      // the entire word to cause any underlining to be removed.
   1.303 +      mRange = mNoCheckRange;
   1.304 +      break;
   1.305 +    case eOpNavigation:
   1.306 +      return FinishNavigationEvent(aWordUtil);
   1.307 +    case eOpSelection:
   1.308 +      // this gets special handling in ResumeCheck
   1.309 +      break;
   1.310 +    case eOpResume:
   1.311 +      // everything should be initialized already in this case
   1.312 +      break;
   1.313 +    default:
   1.314 +      NS_NOTREACHED("Bad operation");
   1.315 +      return NS_ERROR_NOT_INITIALIZED;
   1.316 +  }
   1.317 +  return NS_OK;
   1.318 +}
   1.319 +
   1.320 +// mozInlineSpellStatus::FinishNavigationEvent
   1.321 +//
   1.322 +//    This verifies that we need to check the word at the previous caret
   1.323 +//    position. Now that we have the word util, we can find the word belonging
   1.324 +//    to the previous caret position. If the new position is inside that word,
   1.325 +//    we don't want to do anything. In this case, we'll nullptr out mRange so
   1.326 +//    that the caller will know not to continue.
   1.327 +//
   1.328 +//    Notice that we don't set mNoCheckRange. We check here whether the cursor
   1.329 +//    is in the word that needs checking, so it isn't necessary. Plus, the
   1.330 +//    spellchecker isn't guaranteed to only check the given word, and it could
   1.331 +//    remove the underline from the new word under the cursor.
   1.332 +
   1.333 +nsresult
   1.334 +mozInlineSpellStatus::FinishNavigationEvent(mozInlineSpellWordUtil& aWordUtil)
   1.335 +{
   1.336 +  nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor);
   1.337 +  if (! editor)
   1.338 +    return NS_ERROR_FAILURE; // editor is gone
   1.339 +
   1.340 +  NS_ASSERTION(mAnchorRange, "No anchor for navigation!");
   1.341 +  nsCOMPtr<nsIDOMNode> newAnchorNode, oldAnchorNode;
   1.342 +  int32_t newAnchorOffset, oldAnchorOffset;
   1.343 +
   1.344 +  // get the DOM position of the old caret, the range should be collapsed
   1.345 +  nsresult rv = mOldNavigationAnchorRange->GetStartContainer(
   1.346 +      getter_AddRefs(oldAnchorNode));
   1.347 +  NS_ENSURE_SUCCESS(rv, rv);
   1.348 +  rv = mOldNavigationAnchorRange->GetStartOffset(&oldAnchorOffset);
   1.349 +  NS_ENSURE_SUCCESS(rv, rv);
   1.350 +
   1.351 +  // find the word on the old caret position, this is the one that we MAY need
   1.352 +  // to check
   1.353 +  nsRefPtr<nsRange> oldWord;
   1.354 +  rv = aWordUtil.GetRangeForWord(oldAnchorNode, oldAnchorOffset,
   1.355 +                                 getter_AddRefs(oldWord));
   1.356 +  NS_ENSURE_SUCCESS(rv, rv);
   1.357 +
   1.358 +  // aWordUtil.GetRangeForWord flushes pending notifications, check editor again.
   1.359 +  editor = do_QueryReferent(mSpellChecker->mEditor);
   1.360 +  if (! editor)
   1.361 +    return NS_ERROR_FAILURE; // editor is gone
   1.362 +
   1.363 +  // get the DOM position of the new caret, the range should be collapsed
   1.364 +  rv = mAnchorRange->GetStartContainer(getter_AddRefs(newAnchorNode));
   1.365 +  NS_ENSURE_SUCCESS(rv, rv);
   1.366 +  rv = mAnchorRange->GetStartOffset(&newAnchorOffset);
   1.367 +  NS_ENSURE_SUCCESS(rv, rv);
   1.368 +
   1.369 +  // see if the new cursor position is in the word of the old cursor position
   1.370 +  bool isInRange = false;
   1.371 +  if (! mForceNavigationWordCheck) {
   1.372 +    rv = oldWord->IsPointInRange(newAnchorNode,
   1.373 +                                 newAnchorOffset + mNewNavigationPositionOffset,
   1.374 +                                 &isInRange);
   1.375 +    NS_ENSURE_SUCCESS(rv, rv);
   1.376 +  }
   1.377 +
   1.378 +  if (isInRange) {
   1.379 +    // caller should give up
   1.380 +    mRange = nullptr;
   1.381 +  } else {
   1.382 +    // check the old word
   1.383 +    mRange = oldWord;
   1.384 +
   1.385 +    // Once we've spellchecked the current word, we don't need to spellcheck
   1.386 +    // for any more navigation events.
   1.387 +    mSpellChecker->mNeedsCheckAfterNavigation = false;
   1.388 +  }
   1.389 +  return NS_OK;
   1.390 +}
   1.391 +
   1.392 +// mozInlineSpellStatus::FillNoCheckRangeFromAnchor
   1.393 +//
   1.394 +//    Given the mAnchorRange object, computes the range of the word it is on
   1.395 +//    (if any) and fills that range into mNoCheckRange. This is used for
   1.396 +//    change and navigation events to know which word we should skip spell
   1.397 +//    checking on
   1.398 +
   1.399 +nsresult
   1.400 +mozInlineSpellStatus::FillNoCheckRangeFromAnchor(
   1.401 +    mozInlineSpellWordUtil& aWordUtil)
   1.402 +{
   1.403 +  nsCOMPtr<nsIDOMNode> anchorNode;
   1.404 +  nsresult rv = mAnchorRange->GetStartContainer(getter_AddRefs(anchorNode));
   1.405 +  NS_ENSURE_SUCCESS(rv, rv);
   1.406 +
   1.407 +  int32_t anchorOffset;
   1.408 +  rv = mAnchorRange->GetStartOffset(&anchorOffset);
   1.409 +  NS_ENSURE_SUCCESS(rv, rv);
   1.410 +
   1.411 +  return aWordUtil.GetRangeForWord(anchorNode, anchorOffset,
   1.412 +                                   getter_AddRefs(mNoCheckRange));
   1.413 +}
   1.414 +
   1.415 +// mozInlineSpellStatus::GetDocument
   1.416 +//
   1.417 +//    Returns the nsIDOMDocument object for the document for the
   1.418 +//    current spellchecker.
   1.419 +
   1.420 +nsresult
   1.421 +mozInlineSpellStatus::GetDocument(nsIDOMDocument** aDocument)
   1.422 +{
   1.423 +  nsresult rv;
   1.424 +  *aDocument = nullptr;
   1.425 +  if (! mSpellChecker->mEditor)
   1.426 +    return NS_ERROR_UNEXPECTED;
   1.427 +
   1.428 +  nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor, &rv);
   1.429 +  NS_ENSURE_SUCCESS(rv, rv);
   1.430 +
   1.431 +  nsCOMPtr<nsIDOMDocument> domDoc;
   1.432 +  rv = editor->GetDocument(getter_AddRefs(domDoc));
   1.433 +  NS_ENSURE_SUCCESS(rv, rv);
   1.434 +  NS_ENSURE_TRUE(domDoc, NS_ERROR_NULL_POINTER);
   1.435 +  domDoc.forget(aDocument);
   1.436 +  return NS_OK;
   1.437 +}
   1.438 +
   1.439 +// mozInlineSpellStatus::PositionToCollapsedRange
   1.440 +//
   1.441 +//    Converts a given DOM position to a collapsed range covering that
   1.442 +//    position. We use ranges to store DOM positions becuase they stay
   1.443 +//    updated as the DOM is changed.
   1.444 +
   1.445 +nsresult
   1.446 +mozInlineSpellStatus::PositionToCollapsedRange(nsIDOMDocument* aDocument,
   1.447 +    nsIDOMNode* aNode, int32_t aOffset, nsIDOMRange** aRange)
   1.448 +{
   1.449 +  *aRange = nullptr;
   1.450 +  nsCOMPtr<nsIDOMRange> range;
   1.451 +  nsresult rv = aDocument->CreateRange(getter_AddRefs(range));
   1.452 +  NS_ENSURE_SUCCESS(rv, rv);
   1.453 +
   1.454 +  rv = range->SetStart(aNode, aOffset);
   1.455 +  NS_ENSURE_SUCCESS(rv, rv);
   1.456 +  rv = range->SetEnd(aNode, aOffset);
   1.457 +  NS_ENSURE_SUCCESS(rv, rv);
   1.458 +
   1.459 +  range.swap(*aRange);
   1.460 +  return NS_OK;
   1.461 +}
   1.462 +
   1.463 +// mozInlineSpellResume
   1.464 +
   1.465 +class mozInlineSpellResume : public nsRunnable
   1.466 +{
   1.467 +public:
   1.468 +  mozInlineSpellResume(const mozInlineSpellStatus& aStatus,
   1.469 +                       uint32_t aDisabledAsyncToken)
   1.470 +    : mDisabledAsyncToken(aDisabledAsyncToken), mStatus(aStatus) {}
   1.471 +
   1.472 +  nsresult Post()
   1.473 +  {
   1.474 +    return NS_DispatchToMainThread(this);
   1.475 +  }
   1.476 +
   1.477 +  NS_IMETHOD Run()
   1.478 +  {
   1.479 +    // Discard the resumption if the spell checker was disabled after the
   1.480 +    // resumption was scheduled.
   1.481 +    if (mDisabledAsyncToken == mStatus.mSpellChecker->mDisabledAsyncToken) {
   1.482 +      mStatus.mSpellChecker->ResumeCheck(&mStatus);
   1.483 +    }
   1.484 +    return NS_OK;
   1.485 +  }
   1.486 +
   1.487 +private:
   1.488 +  uint32_t mDisabledAsyncToken;
   1.489 +  mozInlineSpellStatus mStatus;
   1.490 +};
   1.491 +
   1.492 +// Used as the nsIEditorSpellCheck::InitSpellChecker callback.
   1.493 +class InitEditorSpellCheckCallback MOZ_FINAL : public nsIEditorSpellCheckCallback
   1.494 +{
   1.495 +public:
   1.496 +  NS_DECL_ISUPPORTS
   1.497 +
   1.498 +  explicit InitEditorSpellCheckCallback(mozInlineSpellChecker* aSpellChecker)
   1.499 +    : mSpellChecker(aSpellChecker) {}
   1.500 +
   1.501 +  NS_IMETHOD EditorSpellCheckDone()
   1.502 +  {
   1.503 +    return mSpellChecker ? mSpellChecker->EditorSpellCheckInited() : NS_OK;
   1.504 +  }
   1.505 +
   1.506 +  void Cancel()
   1.507 +  {
   1.508 +    mSpellChecker = nullptr;
   1.509 +  }
   1.510 +
   1.511 +private:
   1.512 +  nsRefPtr<mozInlineSpellChecker> mSpellChecker;
   1.513 +};
   1.514 +NS_IMPL_ISUPPORTS(InitEditorSpellCheckCallback, nsIEditorSpellCheckCallback)
   1.515 +
   1.516 +
   1.517 +NS_INTERFACE_MAP_BEGIN(mozInlineSpellChecker)
   1.518 +  NS_INTERFACE_MAP_ENTRY(nsIInlineSpellChecker)
   1.519 +  NS_INTERFACE_MAP_ENTRY(nsIEditActionListener)
   1.520 +  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   1.521 +  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
   1.522 +  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
   1.523 +  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozInlineSpellChecker)
   1.524 +NS_INTERFACE_MAP_END
   1.525 +
   1.526 +NS_IMPL_CYCLE_COLLECTING_ADDREF(mozInlineSpellChecker)
   1.527 +NS_IMPL_CYCLE_COLLECTING_RELEASE(mozInlineSpellChecker)
   1.528 +
   1.529 +NS_IMPL_CYCLE_COLLECTION(mozInlineSpellChecker,
   1.530 +                         mSpellCheck,
   1.531 +                         mTreeWalker,
   1.532 +                         mCurrentSelectionAnchorNode)
   1.533 +
   1.534 +mozInlineSpellChecker::SpellCheckingState
   1.535 +  mozInlineSpellChecker::gCanEnableSpellChecking =
   1.536 +  mozInlineSpellChecker::SpellCheck_Uninitialized;
   1.537 +
   1.538 +mozInlineSpellChecker::mozInlineSpellChecker() :
   1.539 +    mNumWordsInSpellSelection(0),
   1.540 +    mMaxNumWordsInSpellSelection(250),
   1.541 +    mNumPendingSpellChecks(0),
   1.542 +    mNumPendingUpdateCurrentDictionary(0),
   1.543 +    mDisabledAsyncToken(0),
   1.544 +    mNeedsCheckAfterNavigation(false),
   1.545 +    mFullSpellCheckScheduled(false)
   1.546 +{
   1.547 +  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   1.548 +  if (prefs)
   1.549 +    prefs->GetIntPref(kMaxSpellCheckSelectionSize, &mMaxNumWordsInSpellSelection); 
   1.550 +  mMaxMisspellingsPerCheck = mMaxNumWordsInSpellSelection * 3 / 4;
   1.551 +}
   1.552 +
   1.553 +mozInlineSpellChecker::~mozInlineSpellChecker()
   1.554 +{
   1.555 +}
   1.556 +
   1.557 +NS_IMETHODIMP
   1.558 +mozInlineSpellChecker::GetSpellChecker(nsIEditorSpellCheck **aSpellCheck)
   1.559 +{
   1.560 +  *aSpellCheck = mSpellCheck;
   1.561 +  NS_IF_ADDREF(*aSpellCheck);
   1.562 +  return NS_OK;
   1.563 +}
   1.564 +
   1.565 +NS_IMETHODIMP
   1.566 +mozInlineSpellChecker::Init(nsIEditor *aEditor)
   1.567 +{
   1.568 +  mEditor = do_GetWeakReference(aEditor);
   1.569 +  return NS_OK;
   1.570 +}
   1.571 +
   1.572 +// mozInlineSpellChecker::Cleanup
   1.573 +//
   1.574 +//    Called by the editor when the editor is going away. This is important
   1.575 +//    because we remove listeners. We do NOT clean up anything else in this
   1.576 +//    function, because it can get called while DoSpellCheck is running!
   1.577 +//
   1.578 +//    Getting the style information there can cause DOM notifications to be
   1.579 +//    flushed, which can cause editors to go away which will bring us here.
   1.580 +//    We can not do anything that will cause DoSpellCheck to freak out.
   1.581 +
   1.582 +nsresult mozInlineSpellChecker::Cleanup(bool aDestroyingFrames)
   1.583 +{
   1.584 +  mNumWordsInSpellSelection = 0;
   1.585 +  nsCOMPtr<nsISelection> spellCheckSelection;
   1.586 +  nsresult rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection));
   1.587 +  if (NS_FAILED(rv)) {
   1.588 +    // Ensure we still unregister event listeners (but return a failure code)
   1.589 +    UnregisterEventListeners();
   1.590 +  } else {
   1.591 +    if (!aDestroyingFrames) {
   1.592 +      spellCheckSelection->RemoveAllRanges();
   1.593 +    }
   1.594 +
   1.595 +    rv = UnregisterEventListeners();
   1.596 +  }
   1.597 +
   1.598 +  // Notify ENDED observers now.  If we wait to notify as we normally do when
   1.599 +  // these async operations finish, then in the meantime the editor may create
   1.600 +  // another inline spell checker and cause more STARTED and ENDED
   1.601 +  // notifications to be broadcast.  Interleaved notifications for the same
   1.602 +  // editor but different inline spell checkers could easily confuse
   1.603 +  // observers.  They may receive two consecutive STARTED notifications for
   1.604 +  // example, which we guarantee will not happen.
   1.605 +
   1.606 +  nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor);
   1.607 +  if (mPendingSpellCheck) {
   1.608 +    // Cancel the pending editor spell checker initialization.
   1.609 +    mPendingSpellCheck = nullptr;
   1.610 +    mPendingInitEditorSpellCheckCallback->Cancel();
   1.611 +    mPendingInitEditorSpellCheckCallback = nullptr;
   1.612 +    ChangeNumPendingSpellChecks(-1, editor);
   1.613 +  }
   1.614 +
   1.615 +  // Increment this token so that pending UpdateCurrentDictionary calls and
   1.616 +  // scheduled spell checks are discarded when they finish.
   1.617 +  mDisabledAsyncToken++;
   1.618 +
   1.619 +  if (mNumPendingUpdateCurrentDictionary > 0) {
   1.620 +    // Account for pending UpdateCurrentDictionary calls.
   1.621 +    ChangeNumPendingSpellChecks(-mNumPendingUpdateCurrentDictionary, editor);
   1.622 +    mNumPendingUpdateCurrentDictionary = 0;
   1.623 +  }
   1.624 +  if (mNumPendingSpellChecks > 0) {
   1.625 +    // If mNumPendingSpellChecks is still > 0 at this point, the remainder is
   1.626 +    // pending scheduled spell checks.
   1.627 +    ChangeNumPendingSpellChecks(-mNumPendingSpellChecks, editor);
   1.628 +  }
   1.629 +
   1.630 +  mEditor = nullptr;
   1.631 +  mFullSpellCheckScheduled = false;
   1.632 +
   1.633 +  return rv;
   1.634 +}
   1.635 +
   1.636 +// mozInlineSpellChecker::CanEnableInlineSpellChecking
   1.637 +//
   1.638 +//    This function can be called to see if it seems likely that we can enable
   1.639 +//    spellchecking before actually creating the InlineSpellChecking objects.
   1.640 +//
   1.641 +//    The problem is that we can't get the dictionary list without actually
   1.642 +//    creating a whole bunch of spellchecking objects. This function tries to
   1.643 +//    do that and caches the result so we don't have to keep allocating those
   1.644 +//    objects if there are no dictionaries or spellchecking.
   1.645 +//
   1.646 +//    Whenever dictionaries are added or removed at runtime, this value must be
   1.647 +//    updated before an observer notification is sent out about the change, to
   1.648 +//    avoid editors getting a wrong cached result.
   1.649 +
   1.650 +bool // static
   1.651 +mozInlineSpellChecker::CanEnableInlineSpellChecking()
   1.652 +{
   1.653 +  nsresult rv;
   1.654 +  if (gCanEnableSpellChecking == SpellCheck_Uninitialized) {
   1.655 +    gCanEnableSpellChecking = SpellCheck_NotAvailable;
   1.656 +
   1.657 +    nsCOMPtr<nsIEditorSpellCheck> spellchecker =
   1.658 +      do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &rv);
   1.659 +    NS_ENSURE_SUCCESS(rv, false);
   1.660 +
   1.661 +    bool canSpellCheck = false;
   1.662 +    rv = spellchecker->CanSpellCheck(&canSpellCheck);
   1.663 +    NS_ENSURE_SUCCESS(rv, false);
   1.664 +
   1.665 +    if (canSpellCheck)
   1.666 +      gCanEnableSpellChecking = SpellCheck_Available;
   1.667 +  }
   1.668 +  return (gCanEnableSpellChecking == SpellCheck_Available);
   1.669 +}
   1.670 +
   1.671 +void // static
   1.672 +mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking()
   1.673 +{
   1.674 +  gCanEnableSpellChecking = SpellCheck_Uninitialized;
   1.675 +}
   1.676 +
   1.677 +// mozInlineSpellChecker::RegisterEventListeners
   1.678 +//
   1.679 +//    The inline spell checker listens to mouse events and keyboard navigation+ //    events.
   1.680 +
   1.681 +nsresult
   1.682 +mozInlineSpellChecker::RegisterEventListeners()
   1.683 +{
   1.684 +  nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
   1.685 +  NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
   1.686 +
   1.687 +  editor->AddEditActionListener(this);
   1.688 +
   1.689 +  nsCOMPtr<nsIDOMDocument> doc;
   1.690 +  nsresult rv = editor->GetDocument(getter_AddRefs(doc));
   1.691 +  NS_ENSURE_SUCCESS(rv, rv);
   1.692 +
   1.693 +  nsCOMPtr<EventTarget> piTarget = do_QueryInterface(doc, &rv);
   1.694 +  NS_ENSURE_SUCCESS(rv, rv);
   1.695 +
   1.696 +  piTarget->AddEventListener(NS_LITERAL_STRING("blur"), this,
   1.697 +                             true, false);
   1.698 +  piTarget->AddEventListener(NS_LITERAL_STRING("click"), this,
   1.699 +                             false, false);
   1.700 +  piTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this,
   1.701 +                             false, false);
   1.702 +  return NS_OK;
   1.703 +}
   1.704 +
   1.705 +// mozInlineSpellChecker::UnregisterEventListeners
   1.706 +
   1.707 +nsresult
   1.708 +mozInlineSpellChecker::UnregisterEventListeners()
   1.709 +{
   1.710 +  nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
   1.711 +  NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
   1.712 +
   1.713 +  editor->RemoveEditActionListener(this);
   1.714 +
   1.715 +  nsCOMPtr<nsIDOMDocument> doc;
   1.716 +  editor->GetDocument(getter_AddRefs(doc));
   1.717 +  NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER);
   1.718 +
   1.719 +  nsCOMPtr<EventTarget> piTarget = do_QueryInterface(doc);
   1.720 +  NS_ENSURE_TRUE(piTarget, NS_ERROR_NULL_POINTER);
   1.721 +
   1.722 +  piTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true);
   1.723 +  piTarget->RemoveEventListener(NS_LITERAL_STRING("click"), this, false);
   1.724 +  piTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, false);
   1.725 +  return NS_OK;
   1.726 +}
   1.727 +
   1.728 +// mozInlineSpellChecker::GetEnableRealTimeSpell
   1.729 +
   1.730 +NS_IMETHODIMP
   1.731 +mozInlineSpellChecker::GetEnableRealTimeSpell(bool* aEnabled)
   1.732 +{
   1.733 +  NS_ENSURE_ARG_POINTER(aEnabled);
   1.734 +  *aEnabled = mSpellCheck != nullptr || mPendingSpellCheck != nullptr;
   1.735 +  return NS_OK;
   1.736 +}
   1.737 +
   1.738 +// mozInlineSpellChecker::SetEnableRealTimeSpell
   1.739 +
   1.740 +NS_IMETHODIMP
   1.741 +mozInlineSpellChecker::SetEnableRealTimeSpell(bool aEnabled)
   1.742 +{
   1.743 +  if (!aEnabled) {
   1.744 +    mSpellCheck = nullptr;
   1.745 +    return Cleanup(false);
   1.746 +  }
   1.747 +
   1.748 +  if (mSpellCheck) {
   1.749 +    // spellcheck the current contents. SpellCheckRange doesn't supply a created
   1.750 +    // range to DoSpellCheck, which in our case is the entire range. But this
   1.751 +    // optimization doesn't matter because there is nothing in the spellcheck
   1.752 +    // selection when starting, which triggers a better optimization.
   1.753 +    return SpellCheckRange(nullptr);
   1.754 +  }
   1.755 +
   1.756 +  if (mPendingSpellCheck) {
   1.757 +    // The editor spell checker is already being initialized.
   1.758 +    return NS_OK;
   1.759 +  }
   1.760 +
   1.761 +  mPendingSpellCheck =
   1.762 +    do_CreateInstance("@mozilla.org/editor/editorspellchecker;1");
   1.763 +  NS_ENSURE_STATE(mPendingSpellCheck);
   1.764 +
   1.765 +  nsCOMPtr<nsITextServicesFilter> filter =
   1.766 +    do_CreateInstance("@mozilla.org/editor/txtsrvfiltermail;1");
   1.767 +  if (!filter) {
   1.768 +    mPendingSpellCheck = nullptr;
   1.769 +    NS_ENSURE_STATE(filter);
   1.770 +  }
   1.771 +  mPendingSpellCheck->SetFilter(filter);
   1.772 +
   1.773 +  mPendingInitEditorSpellCheckCallback = new InitEditorSpellCheckCallback(this);
   1.774 +  if (!mPendingInitEditorSpellCheckCallback) {
   1.775 +    mPendingSpellCheck = nullptr;
   1.776 +    NS_ENSURE_STATE(mPendingInitEditorSpellCheckCallback);
   1.777 +  }
   1.778 +
   1.779 +  nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor);
   1.780 +  nsresult rv = mPendingSpellCheck->InitSpellChecker(
   1.781 +                  editor, false, mPendingInitEditorSpellCheckCallback);
   1.782 +  if (NS_FAILED(rv)) {
   1.783 +    mPendingSpellCheck = nullptr;
   1.784 +    mPendingInitEditorSpellCheckCallback = nullptr;
   1.785 +    NS_ENSURE_SUCCESS(rv, rv);
   1.786 +  }
   1.787 +
   1.788 +  ChangeNumPendingSpellChecks(1);
   1.789 +
   1.790 +  return NS_OK;
   1.791 +}
   1.792 +
   1.793 +// Called when nsIEditorSpellCheck::InitSpellChecker completes.
   1.794 +nsresult
   1.795 +mozInlineSpellChecker::EditorSpellCheckInited()
   1.796 +{
   1.797 +  NS_ASSERTION(mPendingSpellCheck, "Spell check should be pending!");
   1.798 +
   1.799 +  // spell checking is enabled, register our event listeners to track navigation
   1.800 +  RegisterEventListeners();
   1.801 +
   1.802 +  mSpellCheck = mPendingSpellCheck;
   1.803 +  mPendingSpellCheck = nullptr;
   1.804 +  mPendingInitEditorSpellCheckCallback = nullptr;
   1.805 +  ChangeNumPendingSpellChecks(-1);
   1.806 +
   1.807 +  // spellcheck the current contents. SpellCheckRange doesn't supply a created
   1.808 +  // range to DoSpellCheck, which in our case is the entire range. But this
   1.809 +  // optimization doesn't matter because there is nothing in the spellcheck
   1.810 +  // selection when starting, which triggers a better optimization.
   1.811 +  return SpellCheckRange(nullptr);
   1.812 +}
   1.813 +
   1.814 +// Changes the number of pending spell checks by the given delta.  If the number
   1.815 +// becomes zero or nonzero, observers are notified.  See NotifyObservers for
   1.816 +// info on the aEditor parameter.
   1.817 +void
   1.818 +mozInlineSpellChecker::ChangeNumPendingSpellChecks(int32_t aDelta,
   1.819 +                                                   nsIEditor* aEditor)
   1.820 +{
   1.821 +  int8_t oldNumPending = mNumPendingSpellChecks;
   1.822 +  mNumPendingSpellChecks += aDelta;
   1.823 +  NS_ASSERTION(mNumPendingSpellChecks >= 0,
   1.824 +               "Unbalanced ChangeNumPendingSpellChecks calls!");
   1.825 +  if (oldNumPending == 0 && mNumPendingSpellChecks > 0) {
   1.826 +    NotifyObservers(INLINESPELL_STARTED_TOPIC, aEditor);
   1.827 +  } else if (oldNumPending > 0 && mNumPendingSpellChecks == 0) {
   1.828 +    NotifyObservers(INLINESPELL_ENDED_TOPIC, aEditor);
   1.829 +  }
   1.830 +}
   1.831 +
   1.832 +// Broadcasts the given topic to observers.  aEditor is passed to observers if
   1.833 +// nonnull; otherwise mEditor is passed.
   1.834 +void
   1.835 +mozInlineSpellChecker::NotifyObservers(const char* aTopic, nsIEditor* aEditor)
   1.836 +{
   1.837 +  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   1.838 +  if (!os)
   1.839 +    return;
   1.840 +  nsCOMPtr<nsIEditor> editor = aEditor;
   1.841 +  if (!editor) {
   1.842 +    editor = do_QueryReferent(mEditor);
   1.843 +  }
   1.844 +  os->NotifyObservers(editor, aTopic, nullptr);
   1.845 +}
   1.846 +
   1.847 +// mozInlineSpellChecker::SpellCheckAfterEditorChange
   1.848 +//
   1.849 +//    Called by the editor when nearly anything happens to change the content.
   1.850 +//
   1.851 +//    The start and end positions specify a range for the thing that happened,
   1.852 +//    but these are usually nullptr, even when you'd think they would be useful
   1.853 +//    because you want the range (for example, pasting). We ignore them in
   1.854 +//    this case.
   1.855 +
   1.856 +NS_IMETHODIMP
   1.857 +mozInlineSpellChecker::SpellCheckAfterEditorChange(
   1.858 +    int32_t aAction, nsISelection *aSelection,
   1.859 +    nsIDOMNode *aPreviousSelectedNode, int32_t aPreviousSelectedOffset,
   1.860 +    nsIDOMNode *aStartNode, int32_t aStartOffset,
   1.861 +    nsIDOMNode *aEndNode, int32_t aEndOffset)
   1.862 +{
   1.863 +  nsresult rv;
   1.864 +  NS_ENSURE_ARG_POINTER(aSelection);
   1.865 +  if (!mSpellCheck)
   1.866 +    return NS_OK; // disabling spell checking is not an error
   1.867 +
   1.868 +  // this means something has changed, and we never check the current word,
   1.869 +  // therefore, we should spellcheck for subsequent caret navigations
   1.870 +  mNeedsCheckAfterNavigation = true;
   1.871 +
   1.872 +  // the anchor node is the position of the caret
   1.873 +  nsCOMPtr<nsIDOMNode> anchorNode;
   1.874 +  rv = aSelection->GetAnchorNode(getter_AddRefs(anchorNode));
   1.875 +  NS_ENSURE_SUCCESS(rv, rv);
   1.876 +  int32_t anchorOffset;
   1.877 +  rv = aSelection->GetAnchorOffset(&anchorOffset);
   1.878 +  NS_ENSURE_SUCCESS(rv, rv);
   1.879 +
   1.880 +  mozInlineSpellStatus status(this);
   1.881 +  rv = status.InitForEditorChange((EditAction)aAction,
   1.882 +                                  anchorNode, anchorOffset,
   1.883 +                                  aPreviousSelectedNode, aPreviousSelectedOffset,
   1.884 +                                  aStartNode, aStartOffset,
   1.885 +                                  aEndNode, aEndOffset);
   1.886 +  NS_ENSURE_SUCCESS(rv, rv);
   1.887 +  rv = ScheduleSpellCheck(status);
   1.888 +  NS_ENSURE_SUCCESS(rv, rv);
   1.889 +
   1.890 +  // remember the current caret position after every change
   1.891 +  SaveCurrentSelectionPosition();
   1.892 +  return NS_OK;
   1.893 +}
   1.894 +
   1.895 +// mozInlineSpellChecker::SpellCheckRange
   1.896 +//
   1.897 +//    Spellchecks all the words in the given range.
   1.898 +//    Supply a nullptr range and this will check the entire editor.
   1.899 +
   1.900 +nsresult
   1.901 +mozInlineSpellChecker::SpellCheckRange(nsIDOMRange* aRange)
   1.902 +{
   1.903 +  NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
   1.904 +
   1.905 +  mozInlineSpellStatus status(this);
   1.906 +  nsRange* range = static_cast<nsRange*>(aRange);
   1.907 +  nsresult rv = status.InitForRange(range);
   1.908 +  NS_ENSURE_SUCCESS(rv, rv);
   1.909 +  return ScheduleSpellCheck(status);
   1.910 +}
   1.911 +
   1.912 +// mozInlineSpellChecker::GetMisspelledWord
   1.913 +
   1.914 +NS_IMETHODIMP
   1.915 +mozInlineSpellChecker::GetMisspelledWord(nsIDOMNode *aNode, int32_t aOffset,
   1.916 +                                         nsIDOMRange **newword)
   1.917 +{
   1.918 +  NS_ENSURE_ARG_POINTER(aNode);
   1.919 +  nsCOMPtr<nsISelection> spellCheckSelection;
   1.920 +  nsresult res = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection));
   1.921 +  NS_ENSURE_SUCCESS(res, res); 
   1.922 +
   1.923 +  return IsPointInSelection(spellCheckSelection, aNode, aOffset, newword);
   1.924 +}
   1.925 +
   1.926 +// mozInlineSpellChecker::ReplaceWord
   1.927 +
   1.928 +NS_IMETHODIMP
   1.929 +mozInlineSpellChecker::ReplaceWord(nsIDOMNode *aNode, int32_t aOffset,
   1.930 +                                   const nsAString &newword)
   1.931 +{
   1.932 +  nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
   1.933 +  NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
   1.934 +  NS_ENSURE_TRUE(newword.Length() != 0, NS_ERROR_FAILURE);
   1.935 +
   1.936 +  nsCOMPtr<nsIDOMRange> range;
   1.937 +  nsresult res = GetMisspelledWord(aNode, aOffset, getter_AddRefs(range));
   1.938 +  NS_ENSURE_SUCCESS(res, res); 
   1.939 +
   1.940 +  if (range)
   1.941 +  {
   1.942 +    editor->BeginTransaction();
   1.943 +  
   1.944 +    nsCOMPtr<nsISelection> selection;
   1.945 +    res = editor->GetSelection(getter_AddRefs(selection));
   1.946 +    NS_ENSURE_SUCCESS(res, res);
   1.947 +    selection->RemoveAllRanges();
   1.948 +    selection->AddRange(range);
   1.949 +    editor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
   1.950 +
   1.951 +    nsCOMPtr<nsIPlaintextEditor> textEditor(do_QueryReferent(mEditor));
   1.952 +    if (textEditor)
   1.953 +      textEditor->InsertText(newword);
   1.954 +
   1.955 +    editor->EndTransaction();
   1.956 +  }
   1.957 +
   1.958 +  return NS_OK;
   1.959 +}
   1.960 +
   1.961 +// mozInlineSpellChecker::AddWordToDictionary
   1.962 +
   1.963 +NS_IMETHODIMP
   1.964 +mozInlineSpellChecker::AddWordToDictionary(const nsAString &word)
   1.965 +{
   1.966 +  NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
   1.967 +
   1.968 +  nsAutoString wordstr(word);
   1.969 +  nsresult rv = mSpellCheck->AddWordToDictionary(wordstr.get());
   1.970 +  NS_ENSURE_SUCCESS(rv, rv); 
   1.971 +
   1.972 +  mozInlineSpellStatus status(this);
   1.973 +  rv = status.InitForSelection();
   1.974 +  NS_ENSURE_SUCCESS(rv, rv);
   1.975 +  return ScheduleSpellCheck(status);
   1.976 +}
   1.977 +
   1.978 +//  mozInlineSpellChecker::RemoveWordFromDictionary
   1.979 +
   1.980 +NS_IMETHODIMP
   1.981 +mozInlineSpellChecker::RemoveWordFromDictionary(const nsAString &word)
   1.982 +{
   1.983 +  NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
   1.984 +
   1.985 +  nsAutoString wordstr(word);
   1.986 +  nsresult rv = mSpellCheck->RemoveWordFromDictionary(wordstr.get());
   1.987 +  NS_ENSURE_SUCCESS(rv, rv); 
   1.988 +  
   1.989 +  mozInlineSpellStatus status(this);
   1.990 +  rv = status.InitForRange(nullptr);
   1.991 +  NS_ENSURE_SUCCESS(rv, rv);
   1.992 +  return ScheduleSpellCheck(status);
   1.993 +}
   1.994 +
   1.995 +// mozInlineSpellChecker::IgnoreWord
   1.996 +
   1.997 +NS_IMETHODIMP
   1.998 +mozInlineSpellChecker::IgnoreWord(const nsAString &word)
   1.999 +{
  1.1000 +  NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
  1.1001 +
  1.1002 +  nsAutoString wordstr(word);
  1.1003 +  nsresult rv = mSpellCheck->IgnoreWordAllOccurrences(wordstr.get());
  1.1004 +  NS_ENSURE_SUCCESS(rv, rv); 
  1.1005 +
  1.1006 +  mozInlineSpellStatus status(this);
  1.1007 +  rv = status.InitForSelection();
  1.1008 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1009 +  return ScheduleSpellCheck(status);
  1.1010 +}
  1.1011 +
  1.1012 +// mozInlineSpellChecker::IgnoreWords
  1.1013 +
  1.1014 +NS_IMETHODIMP
  1.1015 +mozInlineSpellChecker::IgnoreWords(const char16_t **aWordsToIgnore,
  1.1016 +                                   uint32_t aCount)
  1.1017 +{
  1.1018 +  NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
  1.1019 +
  1.1020 +  // add each word to the ignore list and then recheck the document
  1.1021 +  for (uint32_t index = 0; index < aCount; index++)
  1.1022 +    mSpellCheck->IgnoreWordAllOccurrences(aWordsToIgnore[index]);
  1.1023 +
  1.1024 +  mozInlineSpellStatus status(this);
  1.1025 +  nsresult rv = status.InitForSelection();
  1.1026 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1027 +  return ScheduleSpellCheck(status);
  1.1028 +}
  1.1029 +
  1.1030 +NS_IMETHODIMP mozInlineSpellChecker::WillCreateNode(const nsAString & aTag, nsIDOMNode *aParent, int32_t aPosition)
  1.1031 +{
  1.1032 +  return NS_OK;
  1.1033 +}
  1.1034 +
  1.1035 +NS_IMETHODIMP mozInlineSpellChecker::DidCreateNode(const nsAString & aTag, nsIDOMNode *aNode, nsIDOMNode *aParent,
  1.1036 +                                                   int32_t aPosition, nsresult aResult)
  1.1037 +{
  1.1038 +  return NS_OK;
  1.1039 +}
  1.1040 +
  1.1041 +NS_IMETHODIMP mozInlineSpellChecker::WillInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent,
  1.1042 +                                                    int32_t aPosition)
  1.1043 +{
  1.1044 +  return NS_OK;
  1.1045 +}
  1.1046 +
  1.1047 +NS_IMETHODIMP mozInlineSpellChecker::DidInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent,
  1.1048 +                                                   int32_t aPosition, nsresult aResult)
  1.1049 +{
  1.1050 +
  1.1051 +  return NS_OK;
  1.1052 +}
  1.1053 +
  1.1054 +NS_IMETHODIMP mozInlineSpellChecker::WillDeleteNode(nsIDOMNode *aChild)
  1.1055 +{
  1.1056 +  return NS_OK;
  1.1057 +}
  1.1058 +
  1.1059 +NS_IMETHODIMP mozInlineSpellChecker::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult)
  1.1060 +{
  1.1061 +  return NS_OK;
  1.1062 +}
  1.1063 +
  1.1064 +NS_IMETHODIMP mozInlineSpellChecker::WillSplitNode(nsIDOMNode *aExistingRightNode, int32_t aOffset)
  1.1065 +{
  1.1066 +  return NS_OK;
  1.1067 +}
  1.1068 +
  1.1069 +NS_IMETHODIMP
  1.1070 +mozInlineSpellChecker::DidSplitNode(nsIDOMNode *aExistingRightNode,
  1.1071 +                                    int32_t aOffset,
  1.1072 +                                    nsIDOMNode *aNewLeftNode, nsresult aResult)
  1.1073 +{
  1.1074 +  return SpellCheckBetweenNodes(aNewLeftNode, 0, aNewLeftNode, 0);
  1.1075 +}
  1.1076 +
  1.1077 +NS_IMETHODIMP mozInlineSpellChecker::WillJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent)
  1.1078 +{
  1.1079 +  return NS_OK;
  1.1080 +}
  1.1081 +
  1.1082 +NS_IMETHODIMP mozInlineSpellChecker::DidJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, 
  1.1083 +                                                  nsIDOMNode *aParent, nsresult aResult)
  1.1084 +{
  1.1085 +  return SpellCheckBetweenNodes(aRightNode, 0, aRightNode, 0);
  1.1086 +}
  1.1087 +
  1.1088 +NS_IMETHODIMP mozInlineSpellChecker::WillInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString & aString)
  1.1089 +{
  1.1090 +  return NS_OK;
  1.1091 +}
  1.1092 +
  1.1093 +NS_IMETHODIMP mozInlineSpellChecker::DidInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset,
  1.1094 +                                                   const nsAString & aString, nsresult aResult)
  1.1095 +{
  1.1096 +  return NS_OK;
  1.1097 +}
  1.1098 +
  1.1099 +NS_IMETHODIMP mozInlineSpellChecker::WillDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength)
  1.1100 +{
  1.1101 +  return NS_OK;
  1.1102 +}
  1.1103 +
  1.1104 +NS_IMETHODIMP mozInlineSpellChecker::DidDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength, nsresult aResult)
  1.1105 +{
  1.1106 +  return NS_OK;
  1.1107 +}
  1.1108 +
  1.1109 +NS_IMETHODIMP mozInlineSpellChecker::WillDeleteSelection(nsISelection *aSelection)
  1.1110 +{
  1.1111 +  return NS_OK;
  1.1112 +}
  1.1113 +
  1.1114 +NS_IMETHODIMP mozInlineSpellChecker::DidDeleteSelection(nsISelection *aSelection)
  1.1115 +{
  1.1116 +  return NS_OK;
  1.1117 +}
  1.1118 +
  1.1119 +// mozInlineSpellChecker::MakeSpellCheckRange
  1.1120 +//
  1.1121 +//    Given begin and end positions, this function constructs a range as
  1.1122 +//    required for ScheduleSpellCheck. If the start and end nodes are nullptr,
  1.1123 +//    then the entire range will be selected, and you can supply -1 as the
  1.1124 +//    offset to the end range to select all of that node.
  1.1125 +//
  1.1126 +//    If the resulting range would be empty, nullptr is put into *aRange and the
  1.1127 +//    function succeeds.
  1.1128 +
  1.1129 +nsresult
  1.1130 +mozInlineSpellChecker::MakeSpellCheckRange(
  1.1131 +    nsIDOMNode* aStartNode, int32_t aStartOffset,
  1.1132 +    nsIDOMNode* aEndNode, int32_t aEndOffset,
  1.1133 +    nsRange** aRange)
  1.1134 +{
  1.1135 +  nsresult rv;
  1.1136 +  *aRange = nullptr;
  1.1137 +
  1.1138 +  nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
  1.1139 +  NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
  1.1140 +
  1.1141 +  nsCOMPtr<nsIDOMDocument> doc;
  1.1142 +  rv = editor->GetDocument(getter_AddRefs(doc));
  1.1143 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1144 +  NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
  1.1145 +
  1.1146 +  nsCOMPtr<nsIDOMRange> range;
  1.1147 +  rv = doc->CreateRange(getter_AddRefs(range));
  1.1148 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1149 +
  1.1150 +  // possibly use full range of the editor
  1.1151 +  nsCOMPtr<nsIDOMElement> rootElem;
  1.1152 +  if (! aStartNode || ! aEndNode) {
  1.1153 +    rv = editor->GetRootElement(getter_AddRefs(rootElem));
  1.1154 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1155 +
  1.1156 +    aStartNode = rootElem;
  1.1157 +    aStartOffset = 0;
  1.1158 +
  1.1159 +    aEndNode = rootElem;
  1.1160 +    aEndOffset = -1;
  1.1161 +  }
  1.1162 +
  1.1163 +  if (aEndOffset == -1) {
  1.1164 +    nsCOMPtr<nsIDOMNodeList> childNodes;
  1.1165 +    rv = aEndNode->GetChildNodes(getter_AddRefs(childNodes));
  1.1166 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1167 +
  1.1168 +    uint32_t childCount;
  1.1169 +    rv = childNodes->GetLength(&childCount);
  1.1170 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1171 +
  1.1172 +    aEndOffset = childCount;
  1.1173 +  }
  1.1174 +
  1.1175 +  // sometimes we are are requested to check an empty range (possibly an empty
  1.1176 +  // document). This will result in assertions later.
  1.1177 +  if (aStartNode == aEndNode && aStartOffset == aEndOffset)
  1.1178 +    return NS_OK;
  1.1179 +
  1.1180 +  rv = range->SetStart(aStartNode, aStartOffset);
  1.1181 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1182 +  if (aEndOffset)
  1.1183 +    rv = range->SetEnd(aEndNode, aEndOffset);
  1.1184 +  else
  1.1185 +    rv = range->SetEndAfter(aEndNode);
  1.1186 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1187 +
  1.1188 +  *aRange = static_cast<nsRange*>(range.forget().take());
  1.1189 +  return NS_OK;
  1.1190 +}
  1.1191 +
  1.1192 +nsresult
  1.1193 +mozInlineSpellChecker::SpellCheckBetweenNodes(nsIDOMNode *aStartNode,
  1.1194 +                                              int32_t aStartOffset,
  1.1195 +                                              nsIDOMNode *aEndNode,
  1.1196 +                                              int32_t aEndOffset)
  1.1197 +{
  1.1198 +  nsRefPtr<nsRange> range;
  1.1199 +  nsresult rv = MakeSpellCheckRange(aStartNode, aStartOffset,
  1.1200 +                                    aEndNode, aEndOffset,
  1.1201 +                                    getter_AddRefs(range));
  1.1202 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1203 +
  1.1204 +  if (! range)
  1.1205 +    return NS_OK; // range is empty: nothing to do
  1.1206 +
  1.1207 +  mozInlineSpellStatus status(this);
  1.1208 +  rv = status.InitForRange(range);
  1.1209 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1210 +  return ScheduleSpellCheck(status);
  1.1211 +}
  1.1212 +
  1.1213 +// mozInlineSpellChecker::SkipSpellCheckForNode
  1.1214 +//
  1.1215 +//    There are certain conditions when we don't want to spell check a node. In
  1.1216 +//    particular quotations, moz signatures, etc. This routine returns false
  1.1217 +//    for these cases.
  1.1218 +
  1.1219 +nsresult
  1.1220 +mozInlineSpellChecker::SkipSpellCheckForNode(nsIEditor* aEditor,
  1.1221 +                                             nsIDOMNode *aNode,
  1.1222 +                                             bool *checkSpelling)
  1.1223 +{
  1.1224 +  *checkSpelling = true;
  1.1225 +  NS_ENSURE_ARG_POINTER(aNode);
  1.1226 +
  1.1227 +  uint32_t flags;
  1.1228 +  aEditor->GetFlags(&flags);
  1.1229 +  if (flags & nsIPlaintextEditor::eEditorMailMask)
  1.1230 +  {
  1.1231 +    nsCOMPtr<nsIDOMNode> parent;
  1.1232 +    aNode->GetParentNode(getter_AddRefs(parent));
  1.1233 +
  1.1234 +    while (parent)
  1.1235 +    {
  1.1236 +      nsCOMPtr<nsIDOMElement> parentElement = do_QueryInterface(parent);
  1.1237 +      if (!parentElement)
  1.1238 +        break;
  1.1239 +
  1.1240 +      nsAutoString parentTagName;
  1.1241 +      parentElement->GetTagName(parentTagName);
  1.1242 +
  1.1243 +      if (parentTagName.Equals(NS_LITERAL_STRING("blockquote"), nsCaseInsensitiveStringComparator()))
  1.1244 +      {
  1.1245 +        nsAutoString quotetype;
  1.1246 +        parentElement->GetAttribute(NS_LITERAL_STRING("type"), quotetype);
  1.1247 +        if (quotetype.Equals(NS_LITERAL_STRING("cite"), nsCaseInsensitiveStringComparator()))
  1.1248 +        {
  1.1249 +          *checkSpelling = false;
  1.1250 +          break;
  1.1251 +        }
  1.1252 +      }
  1.1253 +      else if (parentTagName.Equals(NS_LITERAL_STRING("pre"), nsCaseInsensitiveStringComparator()))
  1.1254 +      {
  1.1255 +        nsAutoString classname;
  1.1256 +        parentElement->GetAttribute(NS_LITERAL_STRING("class"),classname);
  1.1257 +        if (classname.Equals(NS_LITERAL_STRING("moz-signature")))
  1.1258 +          *checkSpelling = false;
  1.1259 +      }
  1.1260 +
  1.1261 +      nsCOMPtr<nsIDOMNode> nextParent;
  1.1262 +      parent->GetParentNode(getter_AddRefs(nextParent));
  1.1263 +      parent = nextParent;
  1.1264 +    }
  1.1265 +  }
  1.1266 +  else {
  1.1267 +    // Check spelling only if the node is editable, and GetSpellcheck() is true
  1.1268 +    // on the nearest HTMLElement ancestor.
  1.1269 +    nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
  1.1270 +    if (!content->IsEditable()) {
  1.1271 +      *checkSpelling = false;
  1.1272 +      return NS_OK;
  1.1273 +    }
  1.1274 +
  1.1275 +    // Make sure that we can always turn on spell checking for inputs/textareas.
  1.1276 +    // Note that because of the previous check, at this point we know that the
  1.1277 +    // node is editable.
  1.1278 +    if (content->IsInAnonymousSubtree()) {
  1.1279 +      nsCOMPtr<nsIContent> node = content->GetParent();
  1.1280 +      while (node && node->IsInNativeAnonymousSubtree()) {
  1.1281 +        node = node->GetParent();
  1.1282 +      }
  1.1283 +      nsCOMPtr<nsITextControlElement> textControl = do_QueryInterface(node);
  1.1284 +      if (textControl) {
  1.1285 +        *checkSpelling = true;
  1.1286 +        return NS_OK;
  1.1287 +      }
  1.1288 +    }
  1.1289 +
  1.1290 +    // Get HTML element ancestor (might be aNode itself, although probably that
  1.1291 +    // has to be a text node in real life here)
  1.1292 +    nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(content);
  1.1293 +    while (content && !htmlElement) {
  1.1294 +      content = content->GetParent();
  1.1295 +      htmlElement = do_QueryInterface(content);
  1.1296 +    }
  1.1297 +    NS_ASSERTION(htmlElement, "Why do we have no htmlElement?");
  1.1298 +    if (!htmlElement) {
  1.1299 +      return NS_OK;
  1.1300 +    }
  1.1301 +
  1.1302 +    // See if it's spellcheckable
  1.1303 +    htmlElement->GetSpellcheck(checkSpelling);
  1.1304 +    return NS_OK;
  1.1305 +  }
  1.1306 +
  1.1307 +  return NS_OK;
  1.1308 +}
  1.1309 +
  1.1310 +// mozInlineSpellChecker::ScheduleSpellCheck
  1.1311 +//
  1.1312 +//    This is called by code to do the actual spellchecking. We will set up
  1.1313 +//    the proper structures for calls to DoSpellCheck.
  1.1314 +
  1.1315 +nsresult
  1.1316 +mozInlineSpellChecker::ScheduleSpellCheck(const mozInlineSpellStatus& aStatus)
  1.1317 +{
  1.1318 +  if (mFullSpellCheckScheduled) {
  1.1319 +    // Just ignore this; we're going to spell-check everything anyway
  1.1320 +    return NS_OK;
  1.1321 +  }
  1.1322 +
  1.1323 +  mozInlineSpellResume* resume =
  1.1324 +    new mozInlineSpellResume(aStatus, mDisabledAsyncToken);
  1.1325 +  NS_ENSURE_TRUE(resume, NS_ERROR_OUT_OF_MEMORY);
  1.1326 +
  1.1327 +  nsresult rv = resume->Post();
  1.1328 +  if (NS_FAILED(rv)) {
  1.1329 +    delete resume;
  1.1330 +  } else {
  1.1331 +    if (aStatus.IsFullSpellCheck()) {
  1.1332 +      // We're going to check everything.  Suppress further spell-check attempts
  1.1333 +      // until that happens.
  1.1334 +      mFullSpellCheckScheduled = true;
  1.1335 +    }
  1.1336 +    ChangeNumPendingSpellChecks(1);
  1.1337 +  }
  1.1338 +  return rv;
  1.1339 +}
  1.1340 +
  1.1341 +// mozInlineSpellChecker::DoSpellCheckSelection
  1.1342 +//
  1.1343 +//    Called to re-check all misspelled words. We iterate over all ranges in
  1.1344 +//    the selection and call DoSpellCheck on them. This is used when a word
  1.1345 +//    is ignored or added to the dictionary: all instances of that word should
  1.1346 +//    be removed from the selection.
  1.1347 +//
  1.1348 +//    FIXME-PERFORMANCE: This takes as long as it takes and is not resumable.
  1.1349 +//    Typically, checking this small amount of text is relatively fast, but
  1.1350 +//    for large numbers of words, a lag may be noticeable.
  1.1351 +
  1.1352 +nsresult
  1.1353 +mozInlineSpellChecker::DoSpellCheckSelection(mozInlineSpellWordUtil& aWordUtil,
  1.1354 +                                             nsISelection* aSpellCheckSelection,
  1.1355 +                                             mozInlineSpellStatus* aStatus)
  1.1356 +{
  1.1357 +  nsresult rv;
  1.1358 +
  1.1359 +  // clear out mNumWordsInSpellSelection since we'll be rebuilding the ranges.
  1.1360 +  mNumWordsInSpellSelection = 0;
  1.1361 +
  1.1362 +  // Since we could be modifying the ranges for the spellCheckSelection while
  1.1363 +  // looping on the spell check selection, keep a separate array of range
  1.1364 +  // elements inside the selection
  1.1365 +  nsCOMArray<nsIDOMRange> ranges;
  1.1366 +
  1.1367 +  int32_t count;
  1.1368 +  aSpellCheckSelection->GetRangeCount(&count);
  1.1369 +
  1.1370 +  int32_t idx;
  1.1371 +  nsCOMPtr<nsIDOMRange> checkRange;
  1.1372 +  for (idx = 0; idx < count; idx ++) {
  1.1373 +    aSpellCheckSelection->GetRangeAt(idx, getter_AddRefs(checkRange));
  1.1374 +    if (checkRange) {
  1.1375 +      if (! ranges.AppendObject(checkRange))
  1.1376 +        return NS_ERROR_OUT_OF_MEMORY;
  1.1377 +    }
  1.1378 +  }
  1.1379 +
  1.1380 +  // We have saved the ranges above. Clearing the spellcheck selection here
  1.1381 +  // isn't necessary (rechecking each word will modify it as necessary) but
  1.1382 +  // provides better performance. By ensuring that no ranges need to be
  1.1383 +  // removed in DoSpellCheck, we can save checking range inclusion which is
  1.1384 +  // slow.
  1.1385 +  aSpellCheckSelection->RemoveAllRanges();
  1.1386 +
  1.1387 +  // We use this state object for all calls, and just update its range. Note
  1.1388 +  // that we don't need to call FinishInit since we will be filling in the
  1.1389 +  // necessary information.
  1.1390 +  mozInlineSpellStatus status(this);
  1.1391 +  rv = status.InitForRange(nullptr);
  1.1392 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1393 +
  1.1394 +  bool doneChecking;
  1.1395 +  for (idx = 0; idx < count; idx ++) {
  1.1396 +    checkRange = ranges[idx];
  1.1397 +    if (checkRange) {
  1.1398 +      // We can consider this word as "added" since we know it has no spell
  1.1399 +      // check range over it that needs to be deleted. All the old ranges
  1.1400 +      // were cleared above. We also need to clear the word count so that we
  1.1401 +      // check all words instead of stopping early.
  1.1402 +      status.mRange = static_cast<nsRange*>(checkRange.get());
  1.1403 +      rv = DoSpellCheck(aWordUtil, aSpellCheckSelection, &status,
  1.1404 +                        &doneChecking);
  1.1405 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1406 +      NS_ASSERTION(doneChecking, "We gave the spellchecker one word, but it didn't finish checking?!?!");
  1.1407 +
  1.1408 +      status.mWordCount = 0;
  1.1409 +    }
  1.1410 +  }
  1.1411 +
  1.1412 +  return NS_OK;
  1.1413 +}
  1.1414 +
  1.1415 +// mozInlineSpellChecker::DoSpellCheck
  1.1416 +//
  1.1417 +//    This function checks words intersecting the given range, excluding those
  1.1418 +//    inside mStatus->mNoCheckRange (can be nullptr). Words inside aNoCheckRange
  1.1419 +//    will have any spell selection removed (this is used to hide the
  1.1420 +//    underlining for the word that the caret is in). aNoCheckRange should be
  1.1421 +//    on word boundaries.
  1.1422 +//
  1.1423 +//    mResume->mCreatedRange is a possibly nullptr range of new text that was
  1.1424 +//    inserted.  Inside this range, we don't bother to check whether things are
  1.1425 +//    inside the spellcheck selection, which speeds up large paste operations
  1.1426 +//    considerably.
  1.1427 +//
  1.1428 +//    Normal case when editing text by typing
  1.1429 +//       h e l l o   w o r k d   h o w   a r e   y o u
  1.1430 +//                            ^ caret
  1.1431 +//                   [-------] mRange
  1.1432 +//                   [-------] mNoCheckRange
  1.1433 +//      -> does nothing (range is the same as the no check range)
  1.1434 +//
  1.1435 +//    Case when pasting:
  1.1436 +//             [---------- pasted text ----------]
  1.1437 +//       h e l l o   w o r k d   h o w   a r e   y o u
  1.1438 +//                                                ^ caret
  1.1439 +//                                               [---] aNoCheckRange
  1.1440 +//      -> recheck all words in range except those in aNoCheckRange
  1.1441 +//
  1.1442 +//    If checking is complete, *aDoneChecking will be set. If there is more
  1.1443 +//    but we ran out of time, this will be false and the range will be
  1.1444 +//    updated with the stuff that still needs checking.
  1.1445 +
  1.1446 +nsresult mozInlineSpellChecker::DoSpellCheck(mozInlineSpellWordUtil& aWordUtil,
  1.1447 +                                             nsISelection *aSpellCheckSelection,
  1.1448 +                                             mozInlineSpellStatus* aStatus,
  1.1449 +                                             bool* aDoneChecking)
  1.1450 +{
  1.1451 +  *aDoneChecking = true;
  1.1452 +
  1.1453 +  NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
  1.1454 +
  1.1455 +  // get the editor for SkipSpellCheckForNode, this may fail in reasonable
  1.1456 +  // circumstances since the editor could have gone away
  1.1457 +  nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
  1.1458 +  if (! editor)
  1.1459 +    return NS_ERROR_FAILURE;
  1.1460 +
  1.1461 +  bool iscollapsed;
  1.1462 +  nsresult rv = aStatus->mRange->GetCollapsed(&iscollapsed);
  1.1463 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1464 +  if (iscollapsed)
  1.1465 +    return NS_OK;
  1.1466 +
  1.1467 +  nsCOMPtr<nsISelectionPrivate> privSel = do_QueryInterface(aSpellCheckSelection);
  1.1468 +
  1.1469 +  // see if the selection has any ranges, if not, then we can optimize checking
  1.1470 +  // range inclusion later (we have no ranges when we are initially checking or
  1.1471 +  // when there are no misspelled words yet).
  1.1472 +  int32_t originalRangeCount;
  1.1473 +  rv = aSpellCheckSelection->GetRangeCount(&originalRangeCount);
  1.1474 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1475 +
  1.1476 +  // set the starting DOM position to be the beginning of our range
  1.1477 +  {
  1.1478 +    // Scope for the node/offset pairs here so they don't get
  1.1479 +    // accidentally used later
  1.1480 +    nsINode* beginNode = aStatus->mRange->GetStartParent();
  1.1481 +    int32_t beginOffset = aStatus->mRange->StartOffset();
  1.1482 +    nsINode* endNode = aStatus->mRange->GetEndParent();
  1.1483 +    int32_t endOffset = aStatus->mRange->EndOffset();
  1.1484 +
  1.1485 +    // Now check that we're still looking at a range that's under
  1.1486 +    // aWordUtil.GetRootNode()
  1.1487 +    nsINode* rootNode = aWordUtil.GetRootNode();
  1.1488 +    if (!nsContentUtils::ContentIsDescendantOf(beginNode, rootNode) ||
  1.1489 +        !nsContentUtils::ContentIsDescendantOf(endNode, rootNode)) {
  1.1490 +      // Just bail out and don't try to spell-check this
  1.1491 +      return NS_OK;
  1.1492 +    }
  1.1493 +  
  1.1494 +    aWordUtil.SetEnd(endNode, endOffset);
  1.1495 +    aWordUtil.SetPosition(beginNode, beginOffset);
  1.1496 +  }
  1.1497 +
  1.1498 +  // aWordUtil.SetPosition flushes pending notifications, check editor again.
  1.1499 +  editor = do_QueryReferent(mEditor);
  1.1500 +  if (! editor)
  1.1501 +    return NS_ERROR_FAILURE;
  1.1502 +
  1.1503 +  int32_t wordsSinceTimeCheck = 0;
  1.1504 +  PRTime beginTime = PR_Now();
  1.1505 +
  1.1506 +  nsAutoString wordText;
  1.1507 +  nsRefPtr<nsRange> wordRange;
  1.1508 +  bool dontCheckWord;
  1.1509 +  while (NS_SUCCEEDED(aWordUtil.GetNextWord(wordText,
  1.1510 +                                            getter_AddRefs(wordRange),
  1.1511 +                                            &dontCheckWord)) &&
  1.1512 +         wordRange) {
  1.1513 +    wordsSinceTimeCheck ++;
  1.1514 +
  1.1515 +    // get the range for the current word.
  1.1516 +    // Not using nsINode here for now because we have to call into
  1.1517 +    // selection APIs that use nsIDOMNode. :(
  1.1518 +    nsCOMPtr<nsIDOMNode> beginNode, endNode;
  1.1519 +    int32_t beginOffset, endOffset;
  1.1520 +    wordRange->GetStartContainer(getter_AddRefs(beginNode));
  1.1521 +    wordRange->GetEndContainer(getter_AddRefs(endNode));
  1.1522 +    wordRange->GetStartOffset(&beginOffset);
  1.1523 +    wordRange->GetEndOffset(&endOffset);
  1.1524 +
  1.1525 +#ifdef DEBUG_INLINESPELL
  1.1526 +    printf("->Got word \"%s\"", NS_ConvertUTF16toUTF8(wordText).get());
  1.1527 +    if (dontCheckWord)
  1.1528 +      printf(" (not checking)");
  1.1529 +    printf("\n");
  1.1530 +#endif
  1.1531 +
  1.1532 +    // see if there is a spellcheck range that already intersects the word
  1.1533 +    // and remove it. We only need to remove old ranges, so don't bother if
  1.1534 +    // there were no ranges when we started out.
  1.1535 +    if (originalRangeCount > 0) {
  1.1536 +      // likewise, if this word is inside new text, we won't bother testing
  1.1537 +      bool inCreatedRange = false;
  1.1538 +      if (aStatus->mCreatedRange)
  1.1539 +        aStatus->mCreatedRange->IsPointInRange(beginNode, beginOffset, &inCreatedRange);
  1.1540 +      if (! inCreatedRange) {
  1.1541 +        nsTArray<nsRange*> ranges;
  1.1542 +        nsCOMPtr<nsINode> firstNode = do_QueryInterface(beginNode);
  1.1543 +        nsCOMPtr<nsINode> lastNode = do_QueryInterface(endNode);
  1.1544 +        rv = privSel->GetRangesForIntervalArray(firstNode, beginOffset,
  1.1545 +                                                lastNode, endOffset,
  1.1546 +                                                true, &ranges);
  1.1547 +        NS_ENSURE_SUCCESS(rv, rv);
  1.1548 +        for (uint32_t i = 0; i < ranges.Length(); i++)
  1.1549 +          RemoveRange(aSpellCheckSelection, ranges[i]);
  1.1550 +      }
  1.1551 +    }
  1.1552 +
  1.1553 +    // some words are special and don't need checking
  1.1554 +    if (dontCheckWord)
  1.1555 +      continue;
  1.1556 +
  1.1557 +    // some nodes we don't spellcheck
  1.1558 +    bool checkSpelling;
  1.1559 +    rv = SkipSpellCheckForNode(editor, beginNode, &checkSpelling);
  1.1560 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1561 +    if (!checkSpelling)
  1.1562 +      continue;
  1.1563 +
  1.1564 +    // Don't check spelling if we're inside the noCheckRange. This needs to
  1.1565 +    // be done after we clear any old selection because the excluded word
  1.1566 +    // might have been previously marked.
  1.1567 +    //
  1.1568 +    // We do a simple check to see if the beginning of our word is in the
  1.1569 +    // exclusion range. Because the exclusion range is a multiple of a word,
  1.1570 +    // this is sufficient.
  1.1571 +    if (aStatus->mNoCheckRange) {
  1.1572 +      bool inExclusion = false;
  1.1573 +      aStatus->mNoCheckRange->IsPointInRange(beginNode, beginOffset,
  1.1574 +                                             &inExclusion);
  1.1575 +      if (inExclusion)
  1.1576 +        continue;
  1.1577 +    }
  1.1578 +
  1.1579 +    // check spelling and add to selection if misspelled
  1.1580 +    bool isMisspelled;
  1.1581 +    aWordUtil.NormalizeWord(wordText);
  1.1582 +    rv = mSpellCheck->CheckCurrentWordNoSuggest(wordText.get(), &isMisspelled);
  1.1583 +    if (NS_FAILED(rv))
  1.1584 +      continue;
  1.1585 +
  1.1586 +    if (isMisspelled) {
  1.1587 +      // misspelled words count extra toward the max
  1.1588 +      wordsSinceTimeCheck += MISSPELLED_WORD_COUNT_PENALTY;
  1.1589 +      AddRange(aSpellCheckSelection, wordRange);
  1.1590 +
  1.1591 +      aStatus->mWordCount ++;
  1.1592 +      if (aStatus->mWordCount >= mMaxMisspellingsPerCheck ||
  1.1593 +          SpellCheckSelectionIsFull())
  1.1594 +        break;
  1.1595 +    }
  1.1596 +
  1.1597 +    // see if we've run out of time, only check every N words for perf
  1.1598 +    if (wordsSinceTimeCheck >= INLINESPELL_TIMEOUT_CHECK_FREQUENCY) {
  1.1599 +      wordsSinceTimeCheck = 0;
  1.1600 +      if (PR_Now() > PRTime(beginTime + INLINESPELL_CHECK_TIMEOUT * PR_USEC_PER_MSEC)) {
  1.1601 +        // stop checking, our time limit has been exceeded
  1.1602 +
  1.1603 +        // move the range to encompass the stuff that needs checking
  1.1604 +        rv = aStatus->mRange->SetStart(endNode, endOffset);
  1.1605 +        if (NS_FAILED(rv)) {
  1.1606 +          // The range might be unhappy because the beginning is after the
  1.1607 +          // end. This is possible when the requested end was in the middle
  1.1608 +          // of a word, just ignore this situation and assume we're done.
  1.1609 +          return NS_OK;
  1.1610 +        }
  1.1611 +        *aDoneChecking = false;
  1.1612 +        return NS_OK;
  1.1613 +      }
  1.1614 +    }
  1.1615 +  }
  1.1616 +
  1.1617 +  return NS_OK;
  1.1618 +}
  1.1619 +
  1.1620 +// An RAII helper that calls ChangeNumPendingSpellChecks on destruction.
  1.1621 +class AutoChangeNumPendingSpellChecks
  1.1622 +{
  1.1623 +public:
  1.1624 +  AutoChangeNumPendingSpellChecks(mozInlineSpellChecker* aSpellChecker,
  1.1625 +                                  int32_t aDelta)
  1.1626 +    : mSpellChecker(aSpellChecker), mDelta(aDelta) {}
  1.1627 +
  1.1628 +  ~AutoChangeNumPendingSpellChecks()
  1.1629 +  {
  1.1630 +    mSpellChecker->ChangeNumPendingSpellChecks(mDelta);
  1.1631 +  }
  1.1632 +
  1.1633 +private:
  1.1634 +  nsRefPtr<mozInlineSpellChecker> mSpellChecker;
  1.1635 +  int32_t mDelta;
  1.1636 +};
  1.1637 +
  1.1638 +// mozInlineSpellChecker::ResumeCheck
  1.1639 +//
  1.1640 +//    Called by the resume event when it fires. We will try to pick up where
  1.1641 +//    the last resume left off.
  1.1642 +
  1.1643 +nsresult
  1.1644 +mozInlineSpellChecker::ResumeCheck(mozInlineSpellStatus* aStatus)
  1.1645 +{
  1.1646 +  // Observers should be notified that spell check has ended only after spell
  1.1647 +  // check is done below, but since there are many early returns in this method
  1.1648 +  // and the number of pending spell checks must be decremented regardless of
  1.1649 +  // whether the spell check actually happens, use this RAII object.
  1.1650 +  AutoChangeNumPendingSpellChecks autoChangeNumPending(this, -1);
  1.1651 +
  1.1652 +  if (aStatus->IsFullSpellCheck()) {
  1.1653 +    // Allow posting new spellcheck resume events from inside
  1.1654 +    // ResumeCheck, now that we're actually firing.
  1.1655 +    NS_ASSERTION(mFullSpellCheckScheduled,
  1.1656 +                 "How could this be false?  The full spell check is "
  1.1657 +                 "calling us!!");
  1.1658 +    mFullSpellCheckScheduled = false;
  1.1659 +  }
  1.1660 +
  1.1661 +  if (! mSpellCheck)
  1.1662 +    return NS_OK; // spell checking has been turned off
  1.1663 +
  1.1664 +  nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor);
  1.1665 +  if (! editor)
  1.1666 +    return NS_OK; // editor is gone
  1.1667 +
  1.1668 +  mozInlineSpellWordUtil wordUtil;
  1.1669 +  nsresult rv = wordUtil.Init(mEditor);
  1.1670 +  if (NS_FAILED(rv))
  1.1671 +    return NS_OK; // editor doesn't like us, don't assert
  1.1672 +
  1.1673 +  nsCOMPtr<nsISelection> spellCheckSelection;
  1.1674 +  rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection));
  1.1675 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1676 +
  1.1677 +  nsAutoString currentDictionary;
  1.1678 +  rv = mSpellCheck->GetCurrentDictionary(currentDictionary);
  1.1679 +  if (NS_FAILED(rv)) {
  1.1680 +    // no active dictionary
  1.1681 +    int32_t count;
  1.1682 +    spellCheckSelection->GetRangeCount(&count);
  1.1683 +    for (int32_t index = count - 1; index >= 0; index--) {
  1.1684 +      nsCOMPtr<nsIDOMRange> checkRange;
  1.1685 +      spellCheckSelection->GetRangeAt(index, getter_AddRefs(checkRange));
  1.1686 +      if (checkRange) {
  1.1687 +        RemoveRange(spellCheckSelection, checkRange);
  1.1688 +      }
  1.1689 +    }
  1.1690 +    return NS_OK; 
  1.1691 +  }
  1.1692 + 
  1.1693 +  CleanupRangesInSelection(spellCheckSelection);
  1.1694 +
  1.1695 +  rv = aStatus->FinishInitOnEvent(wordUtil);
  1.1696 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1697 +  if (! aStatus->mRange)
  1.1698 +    return NS_OK; // empty range, nothing to do
  1.1699 +
  1.1700 +  bool doneChecking = true;
  1.1701 +  if (aStatus->mOp == mozInlineSpellStatus::eOpSelection)
  1.1702 +    rv = DoSpellCheckSelection(wordUtil, spellCheckSelection, aStatus);
  1.1703 +  else
  1.1704 +    rv = DoSpellCheck(wordUtil, spellCheckSelection, aStatus, &doneChecking);
  1.1705 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1706 +
  1.1707 +  if (! doneChecking)
  1.1708 +    rv = ScheduleSpellCheck(*aStatus);
  1.1709 +  return rv;
  1.1710 +}
  1.1711 +
  1.1712 +// mozInlineSpellChecker::IsPointInSelection
  1.1713 +//
  1.1714 +//    Determines if a given (node,offset) point is inside the given
  1.1715 +//    selection. If so, the specific range of the selection that
  1.1716 +//    intersects is places in *aRange. (There may be multiple disjoint
  1.1717 +//    ranges in a selection.)
  1.1718 +//
  1.1719 +//    If there is no intersection, *aRange will be nullptr.
  1.1720 +
  1.1721 +nsresult
  1.1722 +mozInlineSpellChecker::IsPointInSelection(nsISelection *aSelection,
  1.1723 +                                          nsIDOMNode *aNode,
  1.1724 +                                          int32_t aOffset,
  1.1725 +                                          nsIDOMRange **aRange)
  1.1726 +{
  1.1727 +  *aRange = nullptr;
  1.1728 +
  1.1729 +  nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(aSelection));
  1.1730 +
  1.1731 +  nsTArray<nsRange*> ranges;
  1.1732 +  nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
  1.1733 +  nsresult rv = privSel->GetRangesForIntervalArray(node, aOffset, node, aOffset,
  1.1734 +                                                   true, &ranges);
  1.1735 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1736 +
  1.1737 +  if (ranges.Length() == 0)
  1.1738 +    return NS_OK; // no matches
  1.1739 +
  1.1740 +  // there may be more than one range returned, and we don't know what do
  1.1741 +  // do with that, so just get the first one
  1.1742 +  NS_ADDREF(*aRange = ranges[0]);
  1.1743 +  return NS_OK;
  1.1744 +}
  1.1745 +
  1.1746 +nsresult
  1.1747 +mozInlineSpellChecker::CleanupRangesInSelection(nsISelection *aSelection)
  1.1748 +{
  1.1749 +  // integrity check - remove ranges that have collapsed to nothing. This
  1.1750 +  // can happen if the node containing a highlighted word was removed.
  1.1751 +  NS_ENSURE_ARG_POINTER(aSelection);
  1.1752 +
  1.1753 +  int32_t count;
  1.1754 +  aSelection->GetRangeCount(&count);
  1.1755 +
  1.1756 +  for (int32_t index = 0; index < count; index++)
  1.1757 +  {
  1.1758 +    nsCOMPtr<nsIDOMRange> checkRange;
  1.1759 +    aSelection->GetRangeAt(index, getter_AddRefs(checkRange));
  1.1760 +
  1.1761 +    if (checkRange)
  1.1762 +    {
  1.1763 +      bool collapsed;
  1.1764 +      checkRange->GetCollapsed(&collapsed);
  1.1765 +      if (collapsed)
  1.1766 +      {
  1.1767 +        RemoveRange(aSelection, checkRange);
  1.1768 +        index--;
  1.1769 +        count--;
  1.1770 +      }
  1.1771 +    }
  1.1772 +  }
  1.1773 +
  1.1774 +  return NS_OK;
  1.1775 +}
  1.1776 +
  1.1777 +
  1.1778 +// mozInlineSpellChecker::RemoveRange
  1.1779 +//
  1.1780 +//    For performance reasons, we have an upper bound on the number of word
  1.1781 +//    ranges  in the spell check selection. When removing a range from the
  1.1782 +//    selection, we need to decrement mNumWordsInSpellSelection
  1.1783 +
  1.1784 +nsresult
  1.1785 +mozInlineSpellChecker::RemoveRange(nsISelection* aSpellCheckSelection,
  1.1786 +                                   nsIDOMRange* aRange)
  1.1787 +{
  1.1788 +  NS_ENSURE_ARG_POINTER(aSpellCheckSelection);
  1.1789 +  NS_ENSURE_ARG_POINTER(aRange);
  1.1790 +
  1.1791 +  nsresult rv = aSpellCheckSelection->RemoveRange(aRange);
  1.1792 +  if (NS_SUCCEEDED(rv) && mNumWordsInSpellSelection)
  1.1793 +    mNumWordsInSpellSelection--;
  1.1794 +
  1.1795 +  return rv;
  1.1796 +}
  1.1797 +
  1.1798 +
  1.1799 +// mozInlineSpellChecker::AddRange
  1.1800 +//
  1.1801 +//    For performance reasons, we have an upper bound on the number of word
  1.1802 +//    ranges we'll add to the spell check selection. Once we reach that upper
  1.1803 +//    bound, stop adding the ranges
  1.1804 +
  1.1805 +nsresult
  1.1806 +mozInlineSpellChecker::AddRange(nsISelection* aSpellCheckSelection,
  1.1807 +                                nsIDOMRange* aRange)
  1.1808 +{
  1.1809 +  NS_ENSURE_ARG_POINTER(aSpellCheckSelection);
  1.1810 +  NS_ENSURE_ARG_POINTER(aRange);
  1.1811 +
  1.1812 +  nsresult rv = NS_OK;
  1.1813 +
  1.1814 +  if (!SpellCheckSelectionIsFull())
  1.1815 +  {
  1.1816 +    rv = aSpellCheckSelection->AddRange(aRange);
  1.1817 +    if (NS_SUCCEEDED(rv))
  1.1818 +      mNumWordsInSpellSelection++;
  1.1819 +  }
  1.1820 +
  1.1821 +  return rv;
  1.1822 +}
  1.1823 +
  1.1824 +nsresult mozInlineSpellChecker::GetSpellCheckSelection(nsISelection ** aSpellCheckSelection)
  1.1825 +{ 
  1.1826 +  nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
  1.1827 +  NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
  1.1828 +
  1.1829 +  nsCOMPtr<nsISelectionController> selcon;
  1.1830 +  nsresult rv = editor->GetSelectionController(getter_AddRefs(selcon));
  1.1831 +  NS_ENSURE_SUCCESS(rv, rv); 
  1.1832 +
  1.1833 +  nsCOMPtr<nsISelection> spellCheckSelection;
  1.1834 +  return selcon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, aSpellCheckSelection);
  1.1835 +}
  1.1836 +
  1.1837 +nsresult mozInlineSpellChecker::SaveCurrentSelectionPosition()
  1.1838 +{
  1.1839 +  nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
  1.1840 +  NS_ENSURE_TRUE(editor, NS_OK);
  1.1841 +
  1.1842 +  // figure out the old caret position based on the current selection
  1.1843 +  nsCOMPtr<nsISelection> selection;
  1.1844 +  nsresult rv = editor->GetSelection(getter_AddRefs(selection));
  1.1845 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1846 +
  1.1847 +  rv = selection->GetFocusNode(getter_AddRefs(mCurrentSelectionAnchorNode));
  1.1848 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1849 +  
  1.1850 +  selection->GetFocusOffset(&mCurrentSelectionOffset);
  1.1851 +
  1.1852 +  return NS_OK;
  1.1853 +}
  1.1854 +
  1.1855 +// This is a copy of nsContentUtils::ContentIsDescendantOf. Another crime
  1.1856 +// for XPCOM's rap sheet
  1.1857 +bool // static
  1.1858 +ContentIsDescendantOf(nsINode* aPossibleDescendant,
  1.1859 +                      nsINode* aPossibleAncestor)
  1.1860 +{
  1.1861 +  NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!");
  1.1862 +  NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!");
  1.1863 +
  1.1864 +  do {
  1.1865 +    if (aPossibleDescendant == aPossibleAncestor)
  1.1866 +      return true;
  1.1867 +    aPossibleDescendant = aPossibleDescendant->GetParentNode();
  1.1868 +  } while (aPossibleDescendant);
  1.1869 +
  1.1870 +  return false;
  1.1871 +}
  1.1872 +
  1.1873 +// mozInlineSpellChecker::HandleNavigationEvent
  1.1874 +//
  1.1875 +//    Acts upon mouse clicks and keyboard navigation changes, spell checking
  1.1876 +//    the previous word if the new navigation location moves us to another
  1.1877 +//    word.
  1.1878 +//
  1.1879 +//    This is complicated by the fact that our mouse events are happening after
  1.1880 +//    selection has been changed to account for the mouse click. But keyboard
  1.1881 +//    events are happening before the caret selection has changed. Working
  1.1882 +//    around this by letting keyboard events setting forceWordSpellCheck to
  1.1883 +//    true. aNewPositionOffset also tries to work around this for the
  1.1884 +//    DOM_VK_RIGHT and DOM_VK_LEFT cases.
  1.1885 +
  1.1886 +nsresult
  1.1887 +mozInlineSpellChecker::HandleNavigationEvent(bool aForceWordSpellCheck,
  1.1888 +                                             int32_t aNewPositionOffset)
  1.1889 +{
  1.1890 +  nsresult rv;
  1.1891 +
  1.1892 +  // If we already handled the navigation event and there is no possibility
  1.1893 +  // anything has changed since then, we don't have to do anything. This
  1.1894 +  // optimization makes a noticeable difference when you hold down a navigation
  1.1895 +  // key like Page Down.
  1.1896 +  if (! mNeedsCheckAfterNavigation)
  1.1897 +    return NS_OK;
  1.1898 +
  1.1899 +  nsCOMPtr<nsIDOMNode> currentAnchorNode = mCurrentSelectionAnchorNode;
  1.1900 +  int32_t currentAnchorOffset = mCurrentSelectionOffset;
  1.1901 +
  1.1902 +  // now remember the new focus position resulting from the event
  1.1903 +  rv = SaveCurrentSelectionPosition();
  1.1904 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1905 +
  1.1906 +  bool shouldPost;
  1.1907 +  mozInlineSpellStatus status(this);
  1.1908 +  rv = status.InitForNavigation(aForceWordSpellCheck, aNewPositionOffset,
  1.1909 +                                currentAnchorNode, currentAnchorOffset,
  1.1910 +                                mCurrentSelectionAnchorNode, mCurrentSelectionOffset,
  1.1911 +                                &shouldPost);
  1.1912 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1913 +  if (shouldPost) {
  1.1914 +    rv = ScheduleSpellCheck(status);
  1.1915 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1916 +  }
  1.1917 +
  1.1918 +  return NS_OK;
  1.1919 +}
  1.1920 +
  1.1921 +NS_IMETHODIMP mozInlineSpellChecker::HandleEvent(nsIDOMEvent* aEvent)
  1.1922 +{
  1.1923 +  nsAutoString eventType;
  1.1924 +  aEvent->GetType(eventType);
  1.1925 +
  1.1926 +  if (eventType.EqualsLiteral("blur")) {
  1.1927 +    return Blur(aEvent);
  1.1928 +  }
  1.1929 +  if (eventType.EqualsLiteral("click")) {
  1.1930 +    return MouseClick(aEvent);
  1.1931 +  }
  1.1932 +  if (eventType.EqualsLiteral("keypress")) {
  1.1933 +    return KeyPress(aEvent);
  1.1934 +  }
  1.1935 +
  1.1936 +  return NS_OK;
  1.1937 +}
  1.1938 +
  1.1939 +nsresult mozInlineSpellChecker::Blur(nsIDOMEvent* aEvent)
  1.1940 +{
  1.1941 +  // force spellcheck on blur, for instance when tabbing out of a textbox
  1.1942 +  HandleNavigationEvent(true);
  1.1943 +  return NS_OK;
  1.1944 +}
  1.1945 +
  1.1946 +nsresult mozInlineSpellChecker::MouseClick(nsIDOMEvent *aMouseEvent)
  1.1947 +{
  1.1948 +  nsCOMPtr<nsIDOMMouseEvent>mouseEvent = do_QueryInterface(aMouseEvent);
  1.1949 +  NS_ENSURE_TRUE(mouseEvent, NS_OK);
  1.1950 +
  1.1951 +  // ignore any errors from HandleNavigationEvent as we don't want to prevent 
  1.1952 +  // anyone else from seeing this event.
  1.1953 +  int16_t button;
  1.1954 +  mouseEvent->GetButton(&button);
  1.1955 +  HandleNavigationEvent(button != 0);
  1.1956 +  return NS_OK;
  1.1957 +}
  1.1958 +
  1.1959 +nsresult mozInlineSpellChecker::KeyPress(nsIDOMEvent* aKeyEvent)
  1.1960 +{
  1.1961 +  nsCOMPtr<nsIDOMKeyEvent>keyEvent = do_QueryInterface(aKeyEvent);
  1.1962 +  NS_ENSURE_TRUE(keyEvent, NS_OK);
  1.1963 +
  1.1964 +  uint32_t keyCode;
  1.1965 +  keyEvent->GetKeyCode(&keyCode);
  1.1966 +
  1.1967 +  // we only care about navigation keys that moved selection 
  1.1968 +  switch (keyCode)
  1.1969 +  {
  1.1970 +    case nsIDOMKeyEvent::DOM_VK_RIGHT:
  1.1971 +    case nsIDOMKeyEvent::DOM_VK_LEFT:
  1.1972 +      HandleNavigationEvent(false, keyCode == nsIDOMKeyEvent::DOM_VK_RIGHT ? 1 : -1);
  1.1973 +      break;
  1.1974 +    case nsIDOMKeyEvent::DOM_VK_UP:
  1.1975 +    case nsIDOMKeyEvent::DOM_VK_DOWN:
  1.1976 +    case nsIDOMKeyEvent::DOM_VK_HOME:
  1.1977 +    case nsIDOMKeyEvent::DOM_VK_END:
  1.1978 +    case nsIDOMKeyEvent::DOM_VK_PAGE_UP:
  1.1979 +    case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN:
  1.1980 +      HandleNavigationEvent(true /* force a spelling correction */);
  1.1981 +      break;
  1.1982 +  }
  1.1983 +
  1.1984 +  return NS_OK;
  1.1985 +}
  1.1986 +
  1.1987 +// Used as the nsIEditorSpellCheck::UpdateCurrentDictionary callback.
  1.1988 +class UpdateCurrentDictionaryCallback MOZ_FINAL : public nsIEditorSpellCheckCallback
  1.1989 +{
  1.1990 +public:
  1.1991 +  NS_DECL_ISUPPORTS
  1.1992 +
  1.1993 +  explicit UpdateCurrentDictionaryCallback(mozInlineSpellChecker* aSpellChecker,
  1.1994 +                                           uint32_t aDisabledAsyncToken)
  1.1995 +    : mSpellChecker(aSpellChecker), mDisabledAsyncToken(aDisabledAsyncToken) {}
  1.1996 +
  1.1997 +  NS_IMETHOD EditorSpellCheckDone()
  1.1998 +  {
  1.1999 +    // Ignore this callback if SetEnableRealTimeSpell(false) was called after
  1.2000 +    // the UpdateCurrentDictionary call that triggered it.
  1.2001 +    return mSpellChecker->mDisabledAsyncToken > mDisabledAsyncToken ?
  1.2002 +           NS_OK :
  1.2003 +           mSpellChecker->CurrentDictionaryUpdated();
  1.2004 +  }
  1.2005 +
  1.2006 +private:
  1.2007 +  nsRefPtr<mozInlineSpellChecker> mSpellChecker;
  1.2008 +  uint32_t mDisabledAsyncToken;
  1.2009 +};
  1.2010 +NS_IMPL_ISUPPORTS(UpdateCurrentDictionaryCallback, nsIEditorSpellCheckCallback)
  1.2011 +
  1.2012 +NS_IMETHODIMP mozInlineSpellChecker::UpdateCurrentDictionary()
  1.2013 +{
  1.2014 +  // mSpellCheck is null and mPendingSpellCheck is nonnull while the spell
  1.2015 +  // checker is being initialized.  Calling UpdateCurrentDictionary on
  1.2016 +  // mPendingSpellCheck simply queues the dictionary update after the init.
  1.2017 +  nsCOMPtr<nsIEditorSpellCheck> spellCheck = mSpellCheck ? mSpellCheck :
  1.2018 +                                             mPendingSpellCheck;
  1.2019 +  if (!spellCheck) {
  1.2020 +    return NS_OK;
  1.2021 +  }
  1.2022 +
  1.2023 +  if (NS_FAILED(spellCheck->GetCurrentDictionary(mPreviousDictionary))) {
  1.2024 +    mPreviousDictionary.Truncate();
  1.2025 +  }
  1.2026 +
  1.2027 +  nsRefPtr<UpdateCurrentDictionaryCallback> cb =
  1.2028 +    new UpdateCurrentDictionaryCallback(this, mDisabledAsyncToken);
  1.2029 +  NS_ENSURE_STATE(cb);
  1.2030 +  nsresult rv = spellCheck->UpdateCurrentDictionary(cb);
  1.2031 +  if (NS_FAILED(rv)) {
  1.2032 +    cb = nullptr;
  1.2033 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2034 +  }
  1.2035 +  mNumPendingUpdateCurrentDictionary++;
  1.2036 +  ChangeNumPendingSpellChecks(1);
  1.2037 +
  1.2038 +  return NS_OK;
  1.2039 +}
  1.2040 +
  1.2041 +// Called when nsIEditorSpellCheck::UpdateCurrentDictionary completes.
  1.2042 +nsresult mozInlineSpellChecker::CurrentDictionaryUpdated()
  1.2043 +{
  1.2044 +  mNumPendingUpdateCurrentDictionary--;
  1.2045 +  NS_ASSERTION(mNumPendingUpdateCurrentDictionary >= 0,
  1.2046 +               "CurrentDictionaryUpdated called without corresponding "
  1.2047 +               "UpdateCurrentDictionary call!");
  1.2048 +  ChangeNumPendingSpellChecks(-1);
  1.2049 +
  1.2050 +  nsAutoString currentDictionary;
  1.2051 +  if (!mSpellCheck ||
  1.2052 +      NS_FAILED(mSpellCheck->GetCurrentDictionary(currentDictionary))) {
  1.2053 +    currentDictionary.Truncate();
  1.2054 +  }
  1.2055 +
  1.2056 +  if (!mPreviousDictionary.Equals(currentDictionary)) {
  1.2057 +    nsresult rv = SpellCheckRange(nullptr);
  1.2058 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2059 +  }
  1.2060 +
  1.2061 +  return NS_OK;
  1.2062 +}
  1.2063 +
  1.2064 +NS_IMETHODIMP
  1.2065 +mozInlineSpellChecker::GetSpellCheckPending(bool* aPending)
  1.2066 +{
  1.2067 +  *aPending = mNumPendingSpellChecks > 0;
  1.2068 +  return NS_OK;
  1.2069 +}

mercurial