LCOV - code coverage report
Current view: directory - extensions/spellcheck/src - mozInlineSpellChecker.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 733 2 0.3 %
Date: 2012-06-02 Functions: 79 2 2.5 %

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

Generated by: LCOV version 1.7