Back to index

lightning-sunbird  0.9+nobinonly
mozInlineSpellChecker.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Real-time Spellchecking
00016  *
00017  * The Initial Developer of the Original Code is Mozdev Group, Inc.
00018  * Portions created by the Initial Developer are Copyright (C) 2004
00019  * the Initial Developer. All Rights Reserved.
00020  *
00021  * Contributor(s): Neil Deakin (neil@mozdevgroup.com)
00022  *                 Scott MacGregor (mscott@mozilla.org)
00023  *                 Brett Wilson <brettw@gmail.com>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00067 #include "mozInlineSpellChecker.h"
00068 #include "mozInlineSpellWordUtil.h"
00069 #include "mozISpellI18NManager.h"
00070 #include "nsCOMPtr.h"
00071 #include "nsIContent.h"
00072 #include "nsCRT.h"
00073 #include "nsIDocument.h"
00074 #include "nsIDOMDocument.h"
00075 #include "nsIDOMDocumentRange.h"
00076 #include "nsIDOMElement.h"
00077 #include "nsIDOMEventReceiver.h"
00078 #include "nsIDOMMouseEvent.h"
00079 #include "nsIDOMKeyEvent.h"
00080 #include "nsIDOMNode.h"
00081 #include "nsIDOMNodeList.h"
00082 #include "nsIDOMNSRange.h"
00083 #include "nsIDOMRange.h"
00084 #include "nsIDOMText.h"
00085 #include "nsIEventQueueService.h"
00086 #include "nsIPlaintextEditor.h"
00087 #include "nsIPrefBranch.h"
00088 #include "nsIPrefService.h"
00089 #include "nsISelection.h"
00090 #include "nsISelection2.h"
00091 #include "nsISelectionController.h"
00092 #include "nsIServiceManager.h"
00093 #include "nsITextServicesFilter.h"
00094 #include "nsString.h"
00095 #include "nsUnicharUtils.h"
00096 #include "plevent.h"
00097 
00098 //#define DEBUG_INLINESPELL
00099 
00100 // the number of milliseconds that we will take at once to do spellchecking
00101 #define INLINESPELL_CHECK_TIMEOUT 50
00102 
00103 // The number of words to check before we look at the time to see if
00104 // INLINESPELL_CHECK_TIMEOUT ms have elapsed. This prevents us from spending
00105 // too much time checking the clock. Note that misspelled words count for
00106 // more than one word in this calculation.
00107 #define INLINESPELL_TIMEOUT_CHECK_FREQUENCY 50
00108 
00109 // This number is the number of checked words a misspelled word counts for
00110 // when we're checking the time to see if the alloted time is up for
00111 // spellchecking. Misspelled words take longer to process since we have to
00112 // create a range, so they count more. The exact number isn't very important
00113 // since this just controls how often we check the current time.
00114 #define MISSPELLED_WORD_COUNT_PENALTY 4
00115 
00116 static PRBool ContentIsDescendantOf(nsIContent* aPossibleDescendant,
00117                                     nsIContent* aPossibleAncestor);
00118 
00119 static const char kMaxSpellCheckSelectionSize[] = "extensions.spellcheck.inline.max-misspellings";
00120 
00121 mozInlineSpellStatus::mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker)
00122     : mSpellChecker(aSpellChecker), mWordCount(0)
00123 {
00124 }
00125 
00126 // mozInlineSpellStatus::InitForEditorChange
00127 //
00128 //    This is the most complicated case. For changes, we need to compute the
00129 //    range of stuff that changed based on the old and new caret positions,
00130 //    as well as use a range possibly provided by the editor (start and end,
00131 //    which are usually NULL) to get a range with the union of these.
00132 
00133 nsresult
00134 mozInlineSpellStatus::InitForEditorChange(
00135     PRInt32 aAction,
00136     nsIDOMNode* aAnchorNode, PRInt32 aAnchorOffset,
00137     nsIDOMNode* aPreviousNode, PRInt32 aPreviousOffset,
00138     nsIDOMNode* aStartNode, PRInt32 aStartOffset,
00139     nsIDOMNode* aEndNode, PRInt32 aEndOffset)
00140 {
00141   nsresult rv;
00142 
00143   nsCOMPtr<nsIDOMDocumentRange> docRange;
00144   rv = GetDocumentRange(getter_AddRefs(docRange));
00145   NS_ENSURE_SUCCESS(rv, rv);
00146 
00147   // save the anchor point as a range so we can find the current word later
00148   rv = PositionToCollapsedRange(docRange, aAnchorNode, aAnchorOffset,
00149                                 getter_AddRefs(mAnchorRange));
00150   NS_ENSURE_SUCCESS(rv, rv);
00151 
00152   if (aAction == mozInlineSpellChecker::kOpDeleteSelection) {
00153     // Deletes are easy, the range is just the current anchor. We set the range
00154     // to check to be empty, FinishInitOnEvent will fill in the range to be
00155     // the current word.
00156     mOp = eOpChangeDelete;
00157     mRange = nsnull;
00158     return NS_OK;
00159   }
00160 
00161   mOp = eOpChange;
00162 
00163   // range to check
00164   rv = docRange->CreateRange(getter_AddRefs(mRange));
00165   NS_ENSURE_SUCCESS(rv, rv);
00166 
00167   // ...we need to put the start and end in the correct order
00168   nsCOMPtr<nsIDOMNSRange> nsrange = do_QueryInterface(mAnchorRange, &rv);
00169   NS_ENSURE_SUCCESS(rv, rv);
00170   PRInt16 cmpResult;
00171   rv = nsrange->ComparePoint(aPreviousNode, aPreviousOffset, &cmpResult);
00172   NS_ENSURE_SUCCESS(rv, rv);
00173   if (cmpResult < 0) {
00174     // previous anchor node is before the current anchor
00175     rv = mRange->SetStart(aPreviousNode, aPreviousOffset);
00176     NS_ENSURE_SUCCESS(rv, rv);
00177     rv = mRange->SetEnd(aAnchorNode, aAnchorOffset);
00178   } else {
00179     // previous anchor node is after (or the same as) the current anchor
00180     rv = mRange->SetStart(aAnchorNode, aAnchorOffset);
00181     NS_ENSURE_SUCCESS(rv, rv);
00182     rv = mRange->SetEnd(aPreviousNode, aPreviousOffset);
00183   }
00184   NS_ENSURE_SUCCESS(rv, rv);
00185 
00186   // On insert save this range: DoSpellCheck optimizes things in this range.
00187   // Otherwise, just leave this NULL.
00188   if (aAction == mozInlineSpellChecker::kOpInsertText)
00189     mCreatedRange = mRange;
00190 
00191   // if we were given a range, we need to expand our range to encompass it
00192   if (aStartNode && aEndNode) {
00193     nsrange = do_QueryInterface(mRange, &rv);
00194     NS_ENSURE_SUCCESS(rv, rv);
00195 
00196     rv = nsrange->ComparePoint(aStartNode, aStartOffset, &cmpResult);
00197     NS_ENSURE_SUCCESS(rv, rv);
00198     if (cmpResult < 0) { // given range starts before
00199       rv = mRange->SetStart(aStartNode, aStartOffset);
00200       NS_ENSURE_SUCCESS(rv, rv);
00201     }
00202 
00203     rv = nsrange->ComparePoint(aEndNode, aEndOffset, &cmpResult);
00204     NS_ENSURE_SUCCESS(rv, rv);
00205     if (cmpResult > 0) { // given range ends after
00206       rv = mRange->SetEnd(aEndNode, aEndOffset);
00207       NS_ENSURE_SUCCESS(rv, rv);
00208     }
00209   }
00210 
00211   return NS_OK;
00212 }
00213 
00214 // mozInlineSpellStatis::InitForNavigation
00215 //
00216 //    For navigation events, we just need to store the new and old positions.
00217 //
00218 //    In some cases, we detect that we shouldn't check. If this event should
00219 //    not be processed, *aContinue will be false.
00220 
00221 nsresult
00222 mozInlineSpellStatus::InitForNavigation(
00223     PRBool aForceCheck, PRInt32 aNewPositionOffset,
00224     nsIDOMNode* aOldAnchorNode, PRInt32 aOldAnchorOffset,
00225     nsIDOMNode* aNewAnchorNode, PRInt32 aNewAnchorOffset,
00226     PRBool* aContinue)
00227 {
00228   nsresult rv;
00229   mOp = eOpNavigation;
00230 
00231   mForceNavigationWordCheck = aForceCheck;
00232   mNewNavigationPositionOffset = aNewPositionOffset;
00233 
00234   // get the root node for checking
00235   nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor, &rv);
00236   NS_ENSURE_SUCCESS(rv, rv);
00237   nsCOMPtr<nsIDOMElement> rootElt;
00238   rv = editor->GetRootElement(getter_AddRefs(rootElt));
00239   NS_ENSURE_SUCCESS(rv, rv);
00240 
00241   // the anchor node might not be in the DOM anymore, check
00242   nsCOMPtr<nsIContent> root = do_QueryInterface(rootElt, &rv);
00243   NS_ENSURE_SUCCESS(rv, rv);
00244   nsCOMPtr<nsIContent> currentAnchor = do_QueryInterface(aOldAnchorNode, &rv);
00245   NS_ENSURE_SUCCESS(rv, rv);
00246   if (root && currentAnchor && ! ContentIsDescendantOf(currentAnchor, root)) {
00247     *aContinue = PR_FALSE;
00248     return NS_OK;
00249   }
00250 
00251   nsCOMPtr<nsIDOMDocumentRange> docRange;
00252   rv = GetDocumentRange(getter_AddRefs(docRange));
00253   NS_ENSURE_SUCCESS(rv, rv);
00254 
00255   rv = PositionToCollapsedRange(docRange, aOldAnchorNode, aOldAnchorOffset,
00256                                 getter_AddRefs(mOldNavigationAnchorRange));
00257   NS_ENSURE_SUCCESS(rv, rv);
00258   rv = PositionToCollapsedRange(docRange, aNewAnchorNode, aNewAnchorOffset,
00259                                 getter_AddRefs(mAnchorRange));
00260   NS_ENSURE_SUCCESS(rv, rv);
00261 
00262   *aContinue = PR_TRUE;
00263   return NS_OK;
00264 }
00265 
00266 // mozInlineSpellStatus::InitForSelection
00267 //
00268 //    It is easy for selections since we always re-check the spellcheck
00269 //    selection.
00270 
00271 nsresult
00272 mozInlineSpellStatus::InitForSelection()
00273 {
00274   mOp = eOpSelection;
00275   return NS_OK;
00276 }
00277 
00278 // mozInlineSpellStatus::InitForRange
00279 //
00280 //    Called to cause the spellcheck of the given range. This will look like
00281 //    a change operation over the given range.
00282 
00283 nsresult
00284 mozInlineSpellStatus::InitForRange(nsIDOMRange* aRange)
00285 {
00286   mOp = eOpChange;
00287   mRange = aRange;
00288   return NS_OK;
00289 }
00290 
00291 // mozInlineSpellStatus::FinishInitOnEvent
00292 //
00293 //    Called when the event is triggered to complete initialization that
00294 //    might require the WordUtil. This calls to the operation-specific
00295 //    initializer, and also sets the range to be the entire element if it
00296 //    is NULL.
00297 //
00298 //    Watch out: the range might still be NULL if there is nothing to do,
00299 //    the caller will have to check for this.
00300 
00301 nsresult
00302 mozInlineSpellStatus::FinishInitOnEvent(mozInlineSpellWordUtil& aWordUtil)
00303 {
00304   nsresult rv;
00305   if (! mRange) {
00306     rv = mSpellChecker->MakeSpellCheckRange(nsnull, 0, nsnull, 0,
00307                                             getter_AddRefs(mRange));
00308     NS_ENSURE_SUCCESS(rv, rv);
00309   }
00310 
00311   switch (mOp) {
00312     case eOpChange:
00313       if (mAnchorRange)
00314         return FillNoCheckRangeFromAnchor(aWordUtil);
00315       break;
00316     case eOpChangeDelete:
00317       if (mAnchorRange) {
00318         rv = FillNoCheckRangeFromAnchor(aWordUtil);
00319         NS_ENSURE_SUCCESS(rv, rv);
00320       }
00321       // Delete events will have no range for the changed text (because it was
00322       // deleted), and InitForEditorChange will set it to NULL. Here, we select
00323       // the entire word to cause any underlining to be removed.
00324       mRange = mNoCheckRange;
00325       break;
00326     case eOpNavigation:
00327       return FinishNavigationEvent(aWordUtil);
00328     case eOpSelection:
00329       // this gets special handling in ResumeCheck
00330       break;
00331     case eOpResume:
00332       // everything should be initialized already in this case
00333       break;
00334     default:
00335       NS_NOTREACHED("Bad operation");
00336       return NS_ERROR_NOT_INITIALIZED;
00337   }
00338   return NS_OK;
00339 }
00340 
00341 // mozInlineSpellStatus::FinishNavigationEvent
00342 //
00343 //    This verifies that we need to check the word at the previous caret
00344 //    position. Now that we have the word util, we can find the word belonging
00345 //    to the previous caret position. If the new position is inside that word,
00346 //    we don't want to do anything. In this case, we'll NULL out mRange so
00347 //    that the caller will know not to continue.
00348 //
00349 //    Notice that we don't set mNoCheckRange. We check here whether the cursor
00350 //    is in the word that needs checking, so it isn't necessary. Plus, the
00351 //    spellchecker isn't guaranteed to only check the given word, and it could
00352 //    remove the underline from the new word under the cursor.
00353 
00354 nsresult
00355 mozInlineSpellStatus::FinishNavigationEvent(mozInlineSpellWordUtil& aWordUtil)
00356 {
00357   NS_ASSERTION(mAnchorRange, "No anchor for navigation!");
00358   nsCOMPtr<nsIDOMNode> newAnchorNode, oldAnchorNode;
00359   PRInt32 newAnchorOffset, oldAnchorOffset;
00360 
00361   // get the DOM position of the old caret, the range should be collapsed
00362   nsresult rv = mOldNavigationAnchorRange->GetStartContainer(
00363       getter_AddRefs(oldAnchorNode));
00364   NS_ENSURE_SUCCESS(rv, rv);
00365   rv = mOldNavigationAnchorRange->GetStartOffset(&oldAnchorOffset);
00366   NS_ENSURE_SUCCESS(rv, rv);
00367 
00368   // find the word on the old caret position, this is the one that we MAY need
00369   // to check
00370   nsCOMPtr<nsIDOMRange> oldWord;
00371   rv = aWordUtil.GetRangeForWord(oldAnchorNode, oldAnchorOffset,
00372                                  getter_AddRefs(oldWord));
00373   NS_ENSURE_SUCCESS(rv, rv);
00374   nsCOMPtr<nsIDOMNSRange> oldWordNS = do_QueryInterface(oldWord, &rv);
00375   NS_ENSURE_SUCCESS(rv, rv);
00376 
00377   // get the DOM position of the new caret, the range should be collapsed
00378   rv = mAnchorRange->GetStartContainer(getter_AddRefs(newAnchorNode));
00379   NS_ENSURE_SUCCESS(rv, rv);
00380   rv = mAnchorRange->GetStartOffset(&newAnchorOffset);
00381   NS_ENSURE_SUCCESS(rv, rv);
00382 
00383   // see if the new cursor position is in the word of the old cursor position
00384   PRBool isInRange = PR_FALSE;
00385   if (! mForceNavigationWordCheck) {
00386     rv = oldWordNS->IsPointInRange(newAnchorNode,
00387                                    newAnchorOffset + mNewNavigationPositionOffset,
00388                                    &isInRange);
00389     NS_ENSURE_SUCCESS(rv, rv);
00390   }
00391 
00392   if (isInRange) {
00393     // caller should give up
00394     mRange = nsnull;
00395   } else {
00396     // check the old word
00397     mRange = oldWord;
00398 
00399     // Once we've spellchecked the current word, we don't need to spellcheck
00400     // for any more navigation events.
00401     mSpellChecker->mNeedsCheckAfterNavigation = PR_FALSE;
00402   }
00403   return NS_OK;
00404 }
00405 
00406 // mozInlineSpellStatus::FillNoCheckRangeFromAnchor
00407 //
00408 //    Given the mAnchorRange object, computes the range of the word it is on
00409 //    (if any) and fills that range into mNoCheckRange. This is used for
00410 //    change and navigation events to know which word we should skip spell
00411 //    checking on
00412 
00413 nsresult
00414 mozInlineSpellStatus::FillNoCheckRangeFromAnchor(
00415     mozInlineSpellWordUtil& aWordUtil)
00416 {
00417   nsCOMPtr<nsIDOMNode> anchorNode;
00418   nsresult rv = mAnchorRange->GetStartContainer(getter_AddRefs(anchorNode));
00419   NS_ENSURE_SUCCESS(rv, rv);
00420 
00421   PRInt32 anchorOffset;
00422   rv = mAnchorRange->GetStartOffset(&anchorOffset);
00423   NS_ENSURE_SUCCESS(rv, rv);
00424 
00425   return aWordUtil.GetRangeForWord(anchorNode, anchorOffset,
00426                                    getter_AddRefs(mNoCheckRange));
00427 }
00428 
00429 // mozInlineSpellStatus::GetDocumentRange
00430 //
00431 //    Returns the nsIDOMDocumentRange object for the document for the
00432 //    current spellchecker.
00433 
00434 nsresult
00435 mozInlineSpellStatus::GetDocumentRange(nsIDOMDocumentRange** aDocRange)
00436 {
00437   nsresult rv;
00438   *aDocRange = nsnull;
00439   if (! mSpellChecker->mEditor)
00440     return NS_ERROR_UNEXPECTED;
00441 
00442   nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor, &rv);
00443   NS_ENSURE_SUCCESS(rv, rv);
00444 
00445   nsCOMPtr<nsIDOMDocument> domDoc;
00446   rv = editor->GetDocument(getter_AddRefs(domDoc));
00447   NS_ENSURE_SUCCESS(rv, rv);
00448 
00449   nsCOMPtr<nsIDOMDocumentRange> docRange = do_QueryInterface(domDoc, &rv);
00450   NS_ENSURE_SUCCESS(rv, rv);
00451 
00452   docRange.swap(*aDocRange);
00453   return NS_OK;
00454 }
00455 
00456 // mozInlineSpellStatus::PositionToCollapsedRange
00457 //
00458 //    Converts a given DOM position to a collapsed range covering that
00459 //    position. We use ranges to store DOM positions becuase they stay
00460 //    updated as the DOM is changed.
00461 
00462 nsresult
00463 mozInlineSpellStatus::PositionToCollapsedRange(nsIDOMDocumentRange* aDocRange,
00464     nsIDOMNode* aNode, PRInt32 aOffset, nsIDOMRange** aRange)
00465 {
00466   *aRange = nsnull;
00467   nsCOMPtr<nsIDOMRange> range;
00468   nsresult rv = aDocRange->CreateRange(getter_AddRefs(range));
00469   NS_ENSURE_SUCCESS(rv, rv);
00470 
00471   rv = range->SetStart(aNode, aOffset);
00472   NS_ENSURE_SUCCESS(rv, rv);
00473   rv = range->SetEnd(aNode, aOffset);
00474   NS_ENSURE_SUCCESS(rv, rv);
00475 
00476   range.swap(*aRange);
00477   return NS_OK;
00478 }
00479 
00480 // mozInlineSpellResume
00481 
00482 class mozInlineSpellResume : public PLEvent
00483 {
00484 public:
00485   mozInlineSpellResume(const mozInlineSpellStatus& status);
00486   mozInlineSpellStatus mStatus;
00487   nsresult Post(nsCOMPtr<nsIEventQueueService>* aEventQueueService);
00488 };
00489  
00490 PR_STATIC_CALLBACK(void*)
00491 HandleSpellCheckResumePLEvent(PLEvent* aEvent)
00492 {
00493   mozInlineSpellResume* resume = NS_REINTERPRET_CAST(mozInlineSpellResume*,
00494                                                      aEvent);
00495   resume->mStatus.mSpellChecker->ResumeCheck(&resume->mStatus);
00496   return nsnull;
00497 }
00498 
00499 PR_STATIC_CALLBACK(void)
00500 DestroySpellCheckResumePLEvent(PLEvent* aEvent)
00501 {
00502   mozInlineSpellResume* resume = NS_REINTERPRET_CAST(mozInlineSpellResume*,
00503                                                      aEvent);
00504   delete resume;
00505 }
00506 
00507 mozInlineSpellResume::mozInlineSpellResume(const mozInlineSpellStatus& aStatus)
00508   : mStatus(aStatus)
00509 {
00510   PL_InitEvent(this, aStatus.mSpellChecker, HandleSpellCheckResumePLEvent,
00511                DestroySpellCheckResumePLEvent);
00512 }
00513 
00514 // mozInlineSpellResume::Post
00515 //
00516 //    Post this event to the given UI thread event queue. It takes a pointer
00517 //    to a COM pointer. The COM pointer will be filled automatically if its
00518 //    contents are NULL.
00519 
00520 nsresult
00521 mozInlineSpellResume::Post(nsCOMPtr<nsIEventQueueService>* aEventQueueService)
00522 {
00523   nsresult rv;
00524 
00525   // get the event queue, creating the service if necessary
00526   if (! *aEventQueueService) {
00527     *aEventQueueService = do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv);
00528     NS_ENSURE_SUCCESS(rv, rv);
00529   }
00530   nsCOMPtr<nsIEventQueue> eventQueue;
00531   (*aEventQueueService)->
00532     GetSpecialEventQueue(nsIEventQueueService::UI_THREAD_EVENT_QUEUE,
00533                          getter_AddRefs(eventQueue));
00534   if (!eventQueue) {
00535     return NS_ERROR_FAILURE;
00536   }
00537 
00538   // post
00539   rv = eventQueue->PostEvent(this);
00540   if (NS_FAILED(rv)) {
00541     PL_DestroyEvent(this);
00542     return rv;
00543   }
00544 
00545   return NS_OK;
00546 }
00547 
00548 
00549 NS_INTERFACE_MAP_BEGIN(mozInlineSpellChecker)
00550 NS_INTERFACE_MAP_ENTRY(nsIInlineSpellChecker)
00551 NS_INTERFACE_MAP_ENTRY(nsIEditActionListener)
00552 NS_INTERFACE_MAP_ENTRY(nsIDOMMouseListener)
00553 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
00554 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMKeyListener)
00555 NS_INTERFACE_MAP_ENTRY(nsIDOMKeyListener)
00556 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener, nsIDOMKeyListener)
00557 NS_INTERFACE_MAP_END
00558 
00559 NS_IMPL_ADDREF(mozInlineSpellChecker)
00560 NS_IMPL_RELEASE(mozInlineSpellChecker)
00561 
00562 mozInlineSpellChecker::SpellCheckingState
00563   mozInlineSpellChecker::gCanEnableSpellChecking =
00564   mozInlineSpellChecker::SpellCheck_Uninitialized;
00565 
00566 mozInlineSpellChecker::mozInlineSpellChecker() :
00567     mNumWordsInSpellSelection(0),
00568     mMaxNumWordsInSpellSelection(250),
00569     mNeedsCheckAfterNavigation(PR_FALSE)
00570 {
00571   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
00572   if (prefs)
00573     prefs->GetIntPref(kMaxSpellCheckSelectionSize, &mMaxNumWordsInSpellSelection); 
00574   mMaxMisspellingsPerCheck = mMaxNumWordsInSpellSelection * 3 / 4;
00575 }
00576 
00577 mozInlineSpellChecker::~mozInlineSpellChecker()
00578 {
00579 }
00580 
00581 NS_IMETHODIMP
00582 mozInlineSpellChecker::GetSpellChecker(nsIEditorSpellCheck **aSpellCheck)
00583 {
00584   *aSpellCheck = mSpellCheck;
00585   NS_IF_ADDREF(*aSpellCheck);
00586   return NS_OK;
00587 }
00588 
00589 NS_IMETHODIMP
00590 mozInlineSpellChecker::Init(nsIEditor *aEditor)
00591 {
00592   mEditor = do_GetWeakReference(aEditor);
00593   return NS_OK;
00594 }
00595 
00596 // mozInlineSpellChecker::Cleanup
00597 //
00598 //    Called by the editor when the editor is going away. This is important
00599 //    because we remove listeners. We do NOT clean up anything else in this
00600 //    function, because it can get called while DoSpellCheck is running!
00601 //
00602 //    Getting the style information there can cause DOM notifications to be
00603 //    flushed, which can cause editors to go away which will bring us here.
00604 //    We can not do anything that will cause DoSpellCheck to freak out.
00605 
00606 nsresult mozInlineSpellChecker::Cleanup()
00607 {
00608   mNumWordsInSpellSelection = 0;
00609   nsCOMPtr<nsISelection> spellCheckSelection;
00610   nsresult rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection));
00611   if (NS_FAILED(rv)) {
00612     // Ensure we still unregister event listeners (but return a failure code)
00613     UnregisterEventListeners();
00614   } else {
00615     spellCheckSelection->RemoveAllRanges();
00616 
00617     rv = UnregisterEventListeners();
00618   }
00619 
00620   return rv;
00621 }
00622 
00623 // mozInlineSpellChecker::CanEnableInlineSpellChecking
00624 //
00625 //    This function can be called to see if it seems likely that we can enable
00626 //    spellchecking before actually creating the InlineSpellChecking objects.
00627 //
00628 //    The problem is that we can't get the dictionary list without actually
00629 //    creating a whole bunch of spellchecking objects. This function tries to
00630 //    do that and caches the result so we don't have to keep allocating those
00631 //    objects if there are no dictionaries or spellchecking.
00632 //
00633 //    This caching will prevent adding dictionaries at runtime if we start out
00634 //    with no dictionaries! Installing dictionaries as extensions will require
00635 //    a restart anyway, so it shouldn't be a problem.
00636 
00637 PRBool // static
00638 mozInlineSpellChecker::CanEnableInlineSpellChecking()
00639 {
00640   nsresult rv;
00641   if (gCanEnableSpellChecking == SpellCheck_Uninitialized) {
00642     gCanEnableSpellChecking = SpellCheck_NotAvailable;
00643 
00644     nsCOMPtr<nsIEditorSpellCheck> spellchecker =
00645       do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &rv);
00646     NS_ENSURE_SUCCESS(rv, PR_FALSE);
00647 
00648     PRBool canSpellCheck = PR_TRUE;
00649     nsCOMPtr<nsIEditorSpellCheck_MOZILLA_1_8_BRANCH> spellcheckerBranch =
00650       do_QueryInterface(spellchecker, &rv);
00651     if (NS_SUCCEEDED(rv)) {
00652       rv = spellcheckerBranch->CanSpellCheck(&canSpellCheck);
00653       NS_ENSURE_SUCCESS(rv, PR_FALSE);
00654     }
00655 
00656     if (canSpellCheck)
00657       gCanEnableSpellChecking = SpellCheck_Available;
00658   }
00659   return (gCanEnableSpellChecking == SpellCheck_Available);
00660 }
00661 
00662 // mozInlineSpellChecker::RegisterEventListeners
00663 //
00664 //    The inline spell checker listens to mouse events and keyboard navigation
00665 //    events.
00666 
00667 nsresult
00668 mozInlineSpellChecker::RegisterEventListeners()
00669 {
00670   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
00671   NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
00672 
00673   editor->AddEditActionListener(this);
00674 
00675   nsCOMPtr<nsIDOMDocument> doc;
00676   nsresult rv = editor->GetDocument(getter_AddRefs(doc));
00677   NS_ENSURE_SUCCESS(rv, rv); 
00678 
00679   nsCOMPtr<nsIDOMEventReceiver> eventReceiver = do_QueryInterface(doc, &rv);
00680   NS_ENSURE_SUCCESS(rv, rv); 
00681 
00682   eventReceiver->AddEventListenerByIID(NS_STATIC_CAST(nsIDOMMouseListener*, this), NS_GET_IID(nsIDOMMouseListener));
00683   eventReceiver->AddEventListenerByIID(NS_STATIC_CAST(nsIDOMKeyListener*, this), NS_GET_IID(nsIDOMKeyListener));
00684 
00685   return NS_OK;
00686 }
00687 
00688 // mozInlineSpellChecker::UnregisterEventListeners
00689 
00690 nsresult
00691 mozInlineSpellChecker::UnregisterEventListeners()
00692 {
00693   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
00694   NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
00695 
00696   editor->RemoveEditActionListener(this);
00697 
00698   nsCOMPtr<nsIDOMDocument> doc;
00699   editor->GetDocument(getter_AddRefs(doc));
00700   NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER);
00701   
00702   nsCOMPtr<nsIDOMEventReceiver> eventReceiver = do_QueryInterface(doc);
00703   NS_ENSURE_TRUE(eventReceiver, NS_ERROR_NULL_POINTER);
00704 
00705   eventReceiver->RemoveEventListenerByIID(NS_STATIC_CAST(nsIDOMMouseListener*, this), NS_GET_IID(nsIDOMMouseListener));
00706   eventReceiver->RemoveEventListenerByIID(NS_STATIC_CAST(nsIDOMKeyListener*, this), NS_GET_IID(nsIDOMKeyListener));
00707   
00708   return NS_OK;
00709 }
00710 
00711 // mozInlineSpellChecker::GetEnableRealTimeSpell
00712  
00713 NS_IMETHODIMP
00714 mozInlineSpellChecker::GetEnableRealTimeSpell(PRBool* aEnabled)
00715 {
00716   NS_ENSURE_ARG_POINTER(aEnabled);
00717   *aEnabled = mSpellCheck != nsnull;
00718   return NS_OK;
00719 }
00720 
00721 // mozInlineSpellChecker::SetEnableRealTimeSpell
00722  
00723 NS_IMETHODIMP
00724 mozInlineSpellChecker::SetEnableRealTimeSpell(PRBool aEnabled)
00725 {
00726   if (!aEnabled) {
00727     mSpellCheck = nsnull;
00728     return Cleanup();
00729   }
00730 
00731   if (!mSpellCheck) {
00732     nsresult res = NS_OK;
00733     nsCOMPtr<nsIEditorSpellCheck> spellchecker = do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &res);
00734     if (NS_SUCCEEDED(res) && spellchecker)
00735     {
00736       nsCOMPtr<nsITextServicesFilter> filter = do_CreateInstance("@mozilla.org/editor/txtsrvfiltermail;1", &res);
00737       spellchecker->SetFilter(filter);
00738       nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
00739       res = spellchecker->InitSpellChecker(editor, PR_FALSE);
00740       NS_ENSURE_SUCCESS(res, res);
00741 
00742       nsCOMPtr<nsITextServicesDocument> tsDoc = do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &res);
00743       NS_ENSURE_SUCCESS(res, res);
00744 
00745       res = tsDoc->SetFilter(filter);
00746       NS_ENSURE_SUCCESS(res, res);
00747 
00748       res = tsDoc->InitWithEditor(editor);
00749       NS_ENSURE_SUCCESS(res, res);
00750 
00751       mTextServicesDocument = tsDoc;
00752       mSpellCheck = spellchecker;
00753 
00754       // spell checking is enabled, register our event listeners to track navigation
00755       RegisterEventListeners();
00756     }
00757   }
00758 
00759   // spellcheck the current contents. SpellCheckRange doesn't supply a created
00760   // range to DoSpellCheck, which in our case is the entire range. But this
00761   // optimization doesn't matter because there is nothing in the spellcheck
00762   // selection when starting, which triggers a better optimization.
00763   return SpellCheckRange(nsnull);
00764 }
00765 
00766 // mozInlineSpellChecker::SpellCheckAfterEditorChange
00767 //
00768 //    Called by the editor when nearly anything happens to change the content.
00769 //
00770 //    The start and end positions specify a range for the thing that happened,
00771 //    but these are usually NULL, even when you'd think they would be useful
00772 //    because you want the range (for example, pasting). We ignore them in
00773 //    this case.
00774  
00775 NS_IMETHODIMP
00776 mozInlineSpellChecker::SpellCheckAfterEditorChange(
00777     PRInt32 aAction, nsISelection *aSelection,
00778     nsIDOMNode *aPreviousSelectedNode, PRInt32 aPreviousSelectedOffset,
00779     nsIDOMNode *aStartNode, PRInt32 aStartOffset,
00780     nsIDOMNode *aEndNode, PRInt32 aEndOffset)
00781 {
00782   nsresult rv;
00783   NS_ENSURE_ARG_POINTER(aSelection);
00784   if (!mSpellCheck)
00785     return NS_OK; // disabling spell checking is not an error
00786 
00787   // this means something has changed, and we never check the current word,
00788   // therefore, we should spellcheck for subsequent caret navigations
00789   mNeedsCheckAfterNavigation = PR_TRUE;
00790 
00791   // the anchor node is the position of the caret
00792   nsCOMPtr<nsIDOMNode> anchorNode;
00793   rv = aSelection->GetAnchorNode(getter_AddRefs(anchorNode));
00794   NS_ENSURE_SUCCESS(rv, rv);
00795   PRInt32 anchorOffset;
00796   rv = aSelection->GetAnchorOffset(&anchorOffset);
00797   NS_ENSURE_SUCCESS(rv, rv);
00798 
00799   mozInlineSpellStatus status(this);
00800   rv = status.InitForEditorChange(aAction,
00801                                   anchorNode, anchorOffset,
00802                                   aPreviousSelectedNode, aPreviousSelectedOffset,
00803                                   aStartNode, aStartOffset,
00804                                   aEndNode, aEndOffset);
00805   NS_ENSURE_SUCCESS(rv, rv);
00806   rv = ScheduleSpellCheck(status);
00807   NS_ENSURE_SUCCESS(rv, rv);
00808 
00809   // remember the current caret position after every change
00810   SaveCurrentSelectionPosition();
00811   return NS_OK;
00812 }
00813 
00814 // mozInlineSpellChecker::SpellCheckRange
00815 //
00816 //    Spellchecks all the words in the given range.
00817 //    Supply a NULL range and this will check the entire editor.
00818 
00819 nsresult
00820 mozInlineSpellChecker::SpellCheckRange(nsIDOMRange* aRange)
00821 {
00822   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
00823 
00824   mozInlineSpellStatus status(this);
00825   nsresult rv = status.InitForRange(aRange);
00826   NS_ENSURE_SUCCESS(rv, rv);
00827   return ScheduleSpellCheck(status);
00828 }
00829 
00830 // mozInlineSpellChecker::GetMispelledWord
00831 
00832 NS_IMETHODIMP
00833 mozInlineSpellChecker::GetMispelledWord(nsIDOMNode *aNode, PRInt32 aOffset,
00834                                         nsIDOMRange **newword)
00835 {
00836   nsCOMPtr<nsISelection> spellCheckSelection;
00837   nsresult res = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection));
00838   NS_ENSURE_SUCCESS(res, res); 
00839 
00840   return IsPointInSelection(spellCheckSelection, aNode, aOffset, newword);
00841 }
00842 
00843 // mozInlineSpellChecker::ReplaceWord
00844  
00845 NS_IMETHODIMP
00846 mozInlineSpellChecker::ReplaceWord(nsIDOMNode *aNode, PRInt32 aOffset,
00847                                    const nsAString &newword)
00848 {
00849   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
00850   NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
00851   NS_ENSURE_TRUE(newword.Length() != 0, NS_ERROR_FAILURE);
00852 
00853   nsCOMPtr<nsIDOMRange> range;
00854   nsresult res = GetMispelledWord(aNode, aOffset, getter_AddRefs(range));
00855   NS_ENSURE_SUCCESS(res, res); 
00856 
00857   if (range)
00858   {
00859     editor->BeginTransaction();
00860   
00861     nsCOMPtr<nsISelection> selection;
00862     res = editor->GetSelection(getter_AddRefs(selection));
00863     NS_ENSURE_SUCCESS(res, res);
00864     selection->RemoveAllRanges();
00865     selection->AddRange(range);
00866     editor->DeleteSelection(nsIEditor::eNone);
00867 
00868     nsCOMPtr<nsIPlaintextEditor> textEditor(do_QueryReferent(mEditor));
00869     textEditor->InsertText(newword);
00870 
00871     editor->EndTransaction();
00872   }
00873 
00874   return NS_OK;
00875 }
00876 
00877 // mozInlineSpellChecker::AddWordToDictionary
00878 
00879 NS_IMETHODIMP
00880 mozInlineSpellChecker::AddWordToDictionary(const nsAString &word)
00881 {
00882   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
00883 
00884   nsAutoString wordstr(word);
00885   nsresult rv = mSpellCheck->AddWordToDictionary(wordstr.get());
00886   NS_ENSURE_SUCCESS(rv, rv); 
00887 
00888   mozInlineSpellStatus status(this);
00889   rv = status.InitForSelection();
00890   NS_ENSURE_SUCCESS(rv, rv);
00891   return ScheduleSpellCheck(status);
00892 }
00893 
00894 // mozInlineSpellChecker::IgnoreWord
00895 
00896 NS_IMETHODIMP
00897 mozInlineSpellChecker::IgnoreWord(const nsAString &word)
00898 {
00899   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
00900 
00901   nsAutoString wordstr(word);
00902   nsresult rv = mSpellCheck->IgnoreWordAllOccurrences(wordstr.get());
00903   NS_ENSURE_SUCCESS(rv, rv);
00904 
00905   mozInlineSpellStatus status(this);
00906   rv = status.InitForSelection();
00907   NS_ENSURE_SUCCESS(rv, rv);
00908   return ScheduleSpellCheck(status);
00909 }
00910 
00911 // mozInlineSpellChecker::IgnoreWords
00912 
00913 NS_IMETHODIMP
00914 mozInlineSpellChecker::IgnoreWords(const PRUnichar **aWordsToIgnore,
00915                                    PRUint32 aCount)
00916 {
00917   NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
00918 
00919   // add each word to the ignore list and then recheck the document
00920   for (PRUint32 index = 0; index < aCount; index++)
00921     mSpellCheck->IgnoreWordAllOccurrences(aWordsToIgnore[index]);
00922 
00923   mozInlineSpellStatus status(this);
00924   nsresult rv = status.InitForSelection();
00925   NS_ENSURE_SUCCESS(rv, rv);
00926   return ScheduleSpellCheck(status);
00927 }
00928 
00929 NS_IMETHODIMP mozInlineSpellChecker::WillCreateNode(const nsAString & aTag, nsIDOMNode *aParent, PRInt32 aPosition)
00930 {
00931   return NS_OK;
00932 }
00933 
00934 NS_IMETHODIMP mozInlineSpellChecker::DidCreateNode(const nsAString & aTag, nsIDOMNode *aNode, nsIDOMNode *aParent,
00935                                                    PRInt32 aPosition, nsresult aResult)
00936 {
00937   return NS_OK;
00938 }
00939 
00940 NS_IMETHODIMP mozInlineSpellChecker::WillInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent,
00941                                                     PRInt32 aPosition)
00942 {
00943   return NS_OK;
00944 }
00945 
00946 NS_IMETHODIMP mozInlineSpellChecker::DidInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent,
00947                                                    PRInt32 aPosition, nsresult aResult)
00948 {
00949 
00950   return NS_OK;
00951 }
00952 
00953 NS_IMETHODIMP mozInlineSpellChecker::WillDeleteNode(nsIDOMNode *aChild)
00954 {
00955   return NS_OK;
00956 }
00957 
00958 NS_IMETHODIMP mozInlineSpellChecker::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult)
00959 {
00960   return NS_OK;
00961 }
00962 
00963 NS_IMETHODIMP mozInlineSpellChecker::WillSplitNode(nsIDOMNode *aExistingRightNode, PRInt32 aOffset)
00964 {
00965   return NS_OK;
00966 }
00967 
00968 NS_IMETHODIMP
00969 mozInlineSpellChecker::DidSplitNode(nsIDOMNode *aExistingRightNode,
00970                                     PRInt32 aOffset,
00971                                     nsIDOMNode *aNewLeftNode, nsresult aResult)
00972 {
00973   return SpellCheckBetweenNodes(aNewLeftNode, 0, aNewLeftNode, 0);
00974 }
00975 
00976 NS_IMETHODIMP mozInlineSpellChecker::WillJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent)
00977 {
00978   return NS_OK;
00979 }
00980 
00981 NS_IMETHODIMP mozInlineSpellChecker::DidJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, 
00982                                                   nsIDOMNode *aParent, nsresult aResult)
00983 {
00984   return SpellCheckBetweenNodes(aRightNode, 0, aRightNode, 0);
00985 }
00986 
00987 NS_IMETHODIMP mozInlineSpellChecker::WillInsertText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, const nsAString & aString)
00988 {
00989   return NS_OK;
00990 }
00991 
00992 NS_IMETHODIMP mozInlineSpellChecker::DidInsertText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset,
00993                                                    const nsAString & aString, nsresult aResult)
00994 {
00995   return NS_OK;
00996 }
00997 
00998 NS_IMETHODIMP mozInlineSpellChecker::WillDeleteText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, PRInt32 aLength)
00999 {
01000   return NS_OK;
01001 }
01002 
01003 NS_IMETHODIMP mozInlineSpellChecker::DidDeleteText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, PRInt32 aLength, nsresult aResult)
01004 {
01005   return NS_OK;
01006 }
01007 
01008 NS_IMETHODIMP mozInlineSpellChecker::WillDeleteSelection(nsISelection *aSelection)
01009 {
01010   return NS_OK;
01011 }
01012 
01013 NS_IMETHODIMP mozInlineSpellChecker::DidDeleteSelection(nsISelection *aSelection)
01014 {
01015   return NS_OK;
01016 }
01017 
01018 // mozInlineSpellChecker::MakeSpellCheckRange
01019 //
01020 //    Given begin and end positions, this function constructs a range as
01021 //    required for ScheduleSpellCheck. If the start and end nodes are NULL,
01022 //    then the entire range will be selected, and you can supply -1 as the
01023 //    offset to the end range to select all of that node.
01024 //
01025 //    If the resulting range would be empty, NULL is put into *aRange and the
01026 //    function succeeds.
01027 
01028 nsresult
01029 mozInlineSpellChecker::MakeSpellCheckRange(
01030     nsIDOMNode* aStartNode, PRInt32 aStartOffset,
01031     nsIDOMNode* aEndNode, PRInt32 aEndOffset,
01032     nsIDOMRange** aRange)
01033 {
01034   nsresult rv;
01035   *aRange = nsnull;
01036 
01037   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
01038   NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
01039 
01040   nsCOMPtr<nsIDOMDocument> doc;
01041   rv = editor->GetDocument(getter_AddRefs(doc));
01042   NS_ENSURE_SUCCESS(rv, rv);
01043 
01044   nsCOMPtr<nsIDOMDocumentRange> docrange = do_QueryInterface(doc);
01045   NS_ENSURE_TRUE(docrange, NS_ERROR_FAILURE);
01046 
01047   nsCOMPtr<nsIDOMRange> range;
01048   rv = docrange->CreateRange(getter_AddRefs(range));
01049   NS_ENSURE_SUCCESS(rv, rv);
01050 
01051   // possibly use full range of the editor
01052   nsCOMPtr<nsIDOMElement> rootElem;
01053   if (! aStartNode || ! aEndNode) {
01054     rv = editor->GetRootElement(getter_AddRefs(rootElem));
01055     NS_ENSURE_SUCCESS(rv, rv);
01056 
01057     aStartNode = rootElem;
01058     aStartOffset = 0;
01059 
01060     aEndNode = rootElem;
01061     aEndOffset = -1;
01062   }
01063 
01064   if (aEndOffset == -1) {
01065     nsCOMPtr<nsIDOMNodeList> childNodes;
01066     rv = aEndNode->GetChildNodes(getter_AddRefs(childNodes));
01067     NS_ENSURE_SUCCESS(rv, rv);
01068 
01069     PRUint32 childCount;
01070     rv = childNodes->GetLength(&childCount);
01071     NS_ENSURE_SUCCESS(rv, rv);
01072 
01073     aEndOffset = childCount;
01074   }
01075 
01076   // sometimes we are are requested to check an empty range (possibly an empty
01077   // document). This will result in assertions later.
01078   if (aStartNode == aEndNode && aStartOffset == aEndOffset)
01079     return NS_OK;
01080 
01081   rv = range->SetStart(aStartNode, aStartOffset);
01082   NS_ENSURE_SUCCESS(rv, rv);
01083   if (aEndOffset)
01084     rv = range->SetEnd(aEndNode, aEndOffset);
01085   else
01086     rv = range->SetEndAfter(aEndNode);
01087   NS_ENSURE_SUCCESS(rv, rv);
01088 
01089   range.swap(*aRange);
01090   return NS_OK;
01091 }
01092 
01093 nsresult
01094 mozInlineSpellChecker::SpellCheckBetweenNodes(nsIDOMNode *aStartNode,
01095                                               PRInt32 aStartOffset,
01096                                               nsIDOMNode *aEndNode,
01097                                               PRInt32 aEndOffset)
01098 {
01099   nsCOMPtr<nsIDOMRange> range;
01100   nsresult rv = MakeSpellCheckRange(aStartNode, aStartOffset,
01101                                     aEndNode, aEndOffset,
01102                                     getter_AddRefs(range));
01103   NS_ENSURE_SUCCESS(rv, rv);
01104 
01105   if (! range)
01106     return NS_OK; // range is empty: nothing to do
01107 
01108   mozInlineSpellStatus status(this);
01109   rv = status.InitForRange(range);
01110   NS_ENSURE_SUCCESS(rv, rv);
01111   return ScheduleSpellCheck(status);
01112 }
01113 
01114 // mozInlineSpellChecker::SkipSpellCheckForNode
01115 //
01116 //    There are certain conditions when we don't want to spell check a node. In
01117 //    particular quotations, moz signatures, etc. This routine returns false
01118 //    for these cases.
01119 
01120 nsresult
01121 mozInlineSpellChecker::SkipSpellCheckForNode(nsIEditor* aEditor,
01122                                              nsIDOMNode *aNode,
01123                                              PRBool *checkSpelling)
01124 {
01125   *checkSpelling = PR_TRUE;
01126   NS_ENSURE_ARG_POINTER(aNode);
01127 
01128   PRUint32 flags;
01129   aEditor->GetFlags(&flags);
01130   if (flags & nsIPlaintextEditor::eEditorMailMask)
01131   {
01132     nsCOMPtr<nsIDOMNode> parent;
01133     aNode->GetParentNode(getter_AddRefs(parent));
01134 
01135     while (parent)
01136     {
01137       nsCOMPtr<nsIDOMElement> parentElement = do_QueryInterface(parent);
01138       if (!parentElement)
01139         break;
01140 
01141       nsAutoString parentTagName;
01142       parentElement->GetTagName(parentTagName);
01143 
01144       if (parentTagName.Equals(NS_LITERAL_STRING("blockquote"), nsCaseInsensitiveStringComparator()))
01145       {
01146         *checkSpelling = PR_FALSE;
01147         break;
01148       }
01149       else if (parentTagName.Equals(NS_LITERAL_STRING("pre"), nsCaseInsensitiveStringComparator()))
01150       {
01151         nsAutoString classname;
01152         parentElement->GetAttribute(NS_LITERAL_STRING("class"),classname);
01153         if (classname.Equals(NS_LITERAL_STRING("moz-signature")))
01154           *checkSpelling = PR_FALSE;
01155       }
01156 
01157       nsCOMPtr<nsIDOMNode> nextParent;
01158       parent->GetParentNode(getter_AddRefs(nextParent));
01159       parent = nextParent;
01160     }
01161   }
01162 
01163   return NS_OK;
01164 }
01165 
01166 
01167 // mozInlineSpellChecker::ScheduleSpellCheck
01168 //
01169 //    This is called by code to do the actual spellchecking. We will set up
01170 //    the proper structures for calls to DoSpellCheck.
01171 
01172 nsresult
01173 mozInlineSpellChecker::ScheduleSpellCheck(const mozInlineSpellStatus& aStatus)
01174 {
01175   mozInlineSpellResume* resume = new mozInlineSpellResume(aStatus);
01176   NS_ENSURE_TRUE(resume, NS_ERROR_OUT_OF_MEMORY);
01177 
01178   nsresult rv = resume->Post(&mEventQueueService);
01179   if (NS_FAILED(rv))
01180     delete resume;
01181   return rv;
01182 }
01183 
01184 // mozInlineSpellChecker::DoSpellCheckSelection
01185 //
01186 //    Called to re-check all misspelled words. We iterate over all ranges in
01187 //    the selection and call DoSpellCheck on them. This is used when a word
01188 //    is ignored or added to the dictionary: all instances of that word should
01189 //    be removed from the selection.
01190 //
01191 //    FIXME-PERFORMANCE: This takes as long as it takes and is not resumable.
01192 //    Typically, checking this small amount of text is relatively fast, but
01193 //    for large numbers of words, a lag may be noticable.
01194 
01195 nsresult
01196 mozInlineSpellChecker::DoSpellCheckSelection(mozInlineSpellWordUtil& aWordUtil,
01197                                              nsISelection* aSpellCheckSelection,
01198                                              mozInlineSpellStatus* aStatus)
01199 {
01200   nsresult rv;
01201 
01202   // clear out mNumWordsInSpellSelection since we'll be rebuilding the ranges.
01203   mNumWordsInSpellSelection = 0;
01204 
01205   // Since we could be modifying the ranges for the spellCheckSelection while
01206   // looping on the spell check selection, keep a separate array of range
01207   // elements inside the selection
01208   nsCOMArray<nsIDOMRange> ranges;
01209 
01210   PRInt32 count;
01211   aSpellCheckSelection->GetRangeCount(&count);
01212 
01213   PRInt32 idx;
01214   nsCOMPtr<nsIDOMRange> checkRange;
01215   for (idx = 0; idx < count; idx ++) {
01216     aSpellCheckSelection->GetRangeAt(idx, getter_AddRefs(checkRange));
01217     if (checkRange) {
01218       if (! ranges.AppendObject(checkRange))
01219         return NS_ERROR_OUT_OF_MEMORY;
01220     }
01221   }
01222 
01223   // We have saved the ranges above. Clearing the spellcheck selection here
01224   // isn't necessary (rechecking each word will modify it as necessary) but
01225   // provides better performance. By ensuring that no ranges need to be
01226   // removed in DoSpellCheck, we can save checking range inclusion which is
01227   // slow.
01228   aSpellCheckSelection->RemoveAllRanges();
01229 
01230   // We use this state object for all calls, and just update its range. Note
01231   // that we don't need to call FinishInit since we will be filling in the
01232   // necessary information.
01233   mozInlineSpellStatus status(this);
01234   rv = status.InitForRange(nsnull);
01235   NS_ENSURE_SUCCESS(rv, rv);
01236 
01237   PRBool doneChecking;
01238   for (idx = 0; idx < count; idx ++) {
01239     checkRange = ranges[idx];
01240     if (checkRange) {
01241       // We can consider this word as "added" since we know it has no spell
01242       // check range over it that needs to be deleted. All the old ranges
01243       // were cleared above. We also need to clear the word count so that we
01244       // check all words instead of stopping early.
01245       status.mRange = checkRange;
01246       rv = DoSpellCheck(aWordUtil, aSpellCheckSelection, &status,
01247                         &doneChecking);
01248       NS_ENSURE_SUCCESS(rv, rv);
01249       NS_ASSERTION(doneChecking, "We gave the spellchecker one word, but it didn't finish checking?!?!");
01250 
01251       status.mWordCount = 0;
01252     }
01253   }
01254 
01255   return NS_OK;
01256 }
01257 
01258 // mozInlineSpellChecker::DoSpellCheck
01259 //
01260 //    This function checks words intersecting the given range, excluding those
01261 //    inside mStatus->mNoCheckRange (can be NULL). Words inside aNoCheckRange
01262 //    will have any spell selection removed (this is used to hide the
01263 //    underlining for the word that the caret is in). aNoCheckRange should be
01264 //    on word boundaries.
01265 //
01266 //    mResume->mCreatedRange is a possibly NULL range of new text that was
01267 //    inserted.  Inside this range, we don't bother to check whether things are
01268 //    inside the spellcheck selection, which speeds up large paste operations
01269 //    considerably.
01270 //
01271 //    Normal case when editing text by typing
01272 //       h e l l o   w o r k d   h o w   a r e   y o u
01273 //                            ^ caret
01274 //                   [-------] mRange
01275 //                   [-------] mNoCheckRange
01276 //      -> does nothing (range is the same as the no check range)
01277 //
01278 //    Case when pasting:
01279 //             [---------- pasted text ----------]
01280 //       h e l l o   w o r k d   h o w   a r e   y o u
01281 //                                                ^ caret
01282 //                                               [---] aNoCheckRange
01283 //      -> recheck all words in range except those in aNoCheckRange
01284 //
01285 //    If checking is complete, *aDoneChecking will be set. If there is more
01286 //    but we ran out of time, this will be false and the range will be
01287 //    updated with the stuff that still needs checking.
01288 
01289 nsresult mozInlineSpellChecker::DoSpellCheck(mozInlineSpellWordUtil& aWordUtil,
01290                                              nsISelection *aSpellCheckSelection,
01291                                              mozInlineSpellStatus* aStatus,
01292                                              PRBool* aDoneChecking)
01293 {
01294   nsCOMPtr<nsIDOMNode> beginNode, endNode;
01295   PRInt32 beginOffset, endOffset;
01296   *aDoneChecking = PR_TRUE;
01297 
01298   // get the editor for SkipSpellCheckForNode, this may fail in reasonable
01299   // circumstances since the editor could have gone away
01300   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
01301   if (! editor)
01302     return NS_ERROR_FAILURE;
01303 
01304   PRBool iscollapsed;
01305   nsresult rv = aStatus->mRange->GetCollapsed(&iscollapsed);
01306   NS_ENSURE_SUCCESS(rv, rv);
01307   if (iscollapsed)
01308     return NS_OK;
01309 
01310   nsCOMPtr<nsISelection2> sel2 = do_QueryInterface(aSpellCheckSelection, &rv);
01311   NS_ENSURE_SUCCESS(rv, rv);
01312 
01313   // see if the selection has any ranges, if not, then we can optimize checking
01314   // range inclusion later (we have no ranges when we are initially checking or
01315   // when there are no misspelled words yet).
01316   PRInt32 originalRangeCount;
01317   rv = aSpellCheckSelection->GetRangeCount(&originalRangeCount);
01318   NS_ENSURE_SUCCESS(rv, rv);
01319 
01320   // set the starting DOM position to be the beginning of our range
01321   NS_ENSURE_SUCCESS(rv, rv);
01322   aStatus->mRange->GetStartContainer(getter_AddRefs(beginNode));
01323   aStatus->mRange->GetStartOffset(&beginOffset);
01324   aStatus->mRange->GetEndContainer(getter_AddRefs(endNode));
01325   aStatus->mRange->GetEndOffset(&endOffset);
01326   aWordUtil.SetEnd(endNode, endOffset);
01327   aWordUtil.SetPosition(beginNode, beginOffset);
01328 
01329   // we need to use IsPointInRange which is on a more specific interface
01330   nsCOMPtr<nsIDOMNSRange> noCheckRange, createdRange;
01331   if (aStatus->mNoCheckRange)
01332     noCheckRange = do_QueryInterface(aStatus->mNoCheckRange);
01333   if (aStatus->mCreatedRange)
01334     createdRange = do_QueryInterface(aStatus->mCreatedRange);
01335 
01336   PRInt32 wordsSinceTimeCheck = 0;
01337   PRTime beginTime = PR_Now();
01338 
01339   nsAutoString wordText;
01340   nsCOMPtr<nsIDOMRange> wordRange;
01341   PRBool dontCheckWord;
01342   while (NS_SUCCEEDED(aWordUtil.GetNextWord(wordText,
01343                                             getter_AddRefs(wordRange),
01344                                             &dontCheckWord)) &&
01345          wordRange) {
01346     wordsSinceTimeCheck ++;
01347 
01348     // get the range for the current word
01349     wordRange->GetStartContainer(getter_AddRefs(beginNode));
01350     wordRange->GetEndContainer(getter_AddRefs(endNode));
01351     wordRange->GetStartOffset(&beginOffset);
01352     wordRange->GetEndOffset(&endOffset);
01353 
01354 #ifdef DEBUG_INLINESPELL
01355     printf("->Got word \"%s\"", NS_ConvertUTF16toUTF8(wordText).get());
01356     if (dontCheckWord)
01357       printf(" (not checking)");
01358     printf("\n");
01359 #endif
01360 
01361     // see if there is a spellcheck range that already intersects the word
01362     // and remove it. We only need to remove old ranges, so don't bother if
01363     // there were no ranges when we started out.
01364     if (originalRangeCount > 0) {
01365       // likewise, if this word is inside new text, we won't bother testing
01366       PRBool inCreatedRange = PR_FALSE;
01367       if (createdRange)
01368         createdRange->IsPointInRange(beginNode, beginOffset, &inCreatedRange);
01369       if (! inCreatedRange) {
01370         nsCOMArray<nsIDOMRange> ranges;
01371         rv = sel2->GetRangesForIntervalCOMArray(beginNode, beginOffset,
01372                                                 endNode, endOffset,
01373                                                 PR_TRUE, &ranges);
01374         NS_ENSURE_SUCCESS(rv, rv);
01375         for (PRInt32 i = 0; i < ranges.Count(); i ++)
01376           RemoveRange(aSpellCheckSelection, ranges[i]);
01377       }
01378     }
01379 
01380     // some words are special and don't need checking
01381     if (dontCheckWord)
01382       continue;
01383 
01384     // some nodes we don't spellcheck
01385     PRBool checkSpelling;
01386     rv = SkipSpellCheckForNode(editor, beginNode, &checkSpelling);
01387     NS_ENSURE_SUCCESS(rv, rv);
01388     if (!checkSpelling)
01389       continue;
01390 
01391     // Don't check spelling if we're inside the noCheckRange. This needs to
01392     // be done after we clear any old selection because the excluded word
01393     // might have been previously marked.
01394     //
01395     // We do a simple check to see if the beginning of our word is in the
01396     // exclusion range. Because the exclusion range is a multiple of a word,
01397     // this is sufficient.
01398     if (noCheckRange) {
01399       PRBool inExclusion = PR_FALSE;
01400       noCheckRange->IsPointInRange(beginNode, beginOffset, &inExclusion);
01401       if (inExclusion)
01402         continue;
01403     }
01404 
01405     NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
01406 
01407     // check spelling and add to selection if misspelled
01408     PRBool isMisspelled;
01409     aWordUtil.NormalizeWord(wordText);
01410     rv = mSpellCheck->CheckCurrentWordNoSuggest(wordText.get(), &isMisspelled);
01411     if (isMisspelled) {
01412       // misspelled words count extra toward the max
01413       wordsSinceTimeCheck += MISSPELLED_WORD_COUNT_PENALTY;
01414       AddRange(aSpellCheckSelection, wordRange);
01415 
01416       aStatus->mWordCount ++;
01417       if (aStatus->mWordCount >= mMaxMisspellingsPerCheck ||
01418           SpellCheckSelectionIsFull())
01419         break;
01420     }
01421 
01422     // see if we've run out of time, only check every N words for perf
01423     if (wordsSinceTimeCheck >= INLINESPELL_TIMEOUT_CHECK_FREQUENCY) {
01424       wordsSinceTimeCheck = 0;
01425       if (PR_Now() > beginTime + INLINESPELL_CHECK_TIMEOUT * PR_USEC_PER_MSEC) {
01426         // stop checking, our time limit has been exceeded
01427 
01428         // move the range to encompass the stuff that needs checking
01429         rv = aStatus->mRange->SetStart(endNode, endOffset);
01430         if (NS_FAILED(rv)) {
01431           // The range might be unhappy because the beginning is after the
01432           // end. This is possible when the requested end was in the middle
01433           // of a word, just ignore this situation and assume we're done.
01434           return NS_OK;
01435         }
01436         *aDoneChecking = PR_FALSE;
01437         return NS_OK;
01438       }
01439     }
01440   }
01441 
01442   return NS_OK;
01443 }
01444 
01445 // mozInlineSpellChecker::ResumeCheck
01446 //
01447 //    Called by the resume event when it fires. We will try to pick up where
01448 //    the last resume left off.
01449 
01450 nsresult
01451 mozInlineSpellChecker::ResumeCheck(mozInlineSpellStatus* aStatus)
01452 {
01453   if (! mSpellCheck)
01454     return NS_OK; // spell checking has been turned off
01455 
01456   mozInlineSpellWordUtil wordUtil;
01457   nsresult rv = wordUtil.Init(mEditor);
01458   if (NS_FAILED(rv))
01459     return NS_OK; // editor doesn't like us, don't assert
01460 
01461   nsCOMPtr<nsISelection> spellCheckSelection;
01462   rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection));
01463   NS_ENSURE_SUCCESS(rv, rv);
01464   CleanupRangesInSelection(spellCheckSelection);
01465 
01466   rv = aStatus->FinishInitOnEvent(wordUtil);
01467   NS_ENSURE_SUCCESS(rv, rv);
01468   if (! aStatus->mRange)
01469     return NS_OK; // empty range, nothing to do
01470 
01471   PRBool doneChecking = PR_TRUE;
01472   if (aStatus->mOp == mozInlineSpellStatus::eOpSelection)
01473     rv = DoSpellCheckSelection(wordUtil, spellCheckSelection, aStatus);
01474   else
01475     rv = DoSpellCheck(wordUtil, spellCheckSelection, aStatus, &doneChecking);
01476   NS_ENSURE_SUCCESS(rv, rv);
01477 
01478   if (! doneChecking)
01479     rv = ScheduleSpellCheck(*aStatus);
01480   return rv;
01481 }
01482 
01483 // mozInlineSpellChecker::IsPointInSelection
01484 //
01485 //    Determines if a given (node,offset) point is inside the given
01486 //    selection. If so, the specific range of the selection that
01487 //    intersects is places in *aRange. (There may be multiple disjoint
01488 //    ranges in a selection.)
01489 //
01490 //    If there is no intersection, *aRange will be NULL.
01491 
01492 nsresult
01493 mozInlineSpellChecker::IsPointInSelection(nsISelection *aSelection,
01494                                           nsIDOMNode *aNode,
01495                                           PRInt32 aOffset,
01496                                           nsIDOMRange **aRange)
01497 {
01498   *aRange = nsnull;
01499 
01500   nsresult rv;
01501   nsCOMPtr<nsISelection2> sel2 = do_QueryInterface(aSelection, &rv);
01502   NS_ENSURE_SUCCESS(rv, rv);
01503 
01504   nsCOMArray<nsIDOMRange> ranges;
01505   rv = sel2->GetRangesForIntervalCOMArray(aNode, aOffset, aNode, aOffset,
01506                                           PR_TRUE, &ranges);
01507   NS_ENSURE_SUCCESS(rv, rv);
01508 
01509   if (ranges.Count() == 0)
01510     return NS_OK; // no matches
01511 
01512   // there may be more than one range returned, and we don't know what do
01513   // do with that, so just get the first one
01514   NS_ADDREF(*aRange = ranges[0]);
01515   return NS_OK;
01516 }
01517 
01518 nsresult
01519 mozInlineSpellChecker::CleanupRangesInSelection(nsISelection *aSelection)
01520 {
01521   // integrity check - remove ranges that have collapsed to nothing. This
01522   // can happen if the node containing a highlighted word was removed.
01523   NS_ENSURE_ARG_POINTER(aSelection);
01524 
01525   PRInt32 count;
01526   aSelection->GetRangeCount(&count);
01527 
01528   for (PRInt32 index = 0; index < count; index++)
01529   {
01530     nsCOMPtr<nsIDOMRange> checkRange;
01531     aSelection->GetRangeAt(index, getter_AddRefs(checkRange));
01532 
01533     if (checkRange)
01534     {
01535       PRBool collapsed;
01536       checkRange->GetCollapsed(&collapsed);
01537       if (collapsed)
01538       {
01539         RemoveRange(aSelection, checkRange);
01540         index--;
01541         count--;
01542       }
01543     }
01544   }
01545 
01546   return NS_OK;
01547 }
01548 
01549 
01550 // mozInlineSpellChecker::RemoveRange
01551 //
01552 //    For performance reasons, we have an upper bound on the number of word
01553 //    ranges  in the spell check selection. When removing a range from the
01554 //    selection, we need to decrement mNumWordsInSpellSelection
01555 
01556 nsresult
01557 mozInlineSpellChecker::RemoveRange(nsISelection* aSpellCheckSelection,
01558                                    nsIDOMRange* aRange)
01559 {
01560   NS_ENSURE_ARG_POINTER(aSpellCheckSelection);
01561   NS_ENSURE_ARG_POINTER(aRange);
01562 
01563   nsresult rv = aSpellCheckSelection->RemoveRange(aRange);
01564   if (NS_SUCCEEDED(rv) && mNumWordsInSpellSelection)
01565     mNumWordsInSpellSelection--;
01566 
01567   return rv;
01568 }
01569 
01570 
01571 // mozInlineSpellChecker::AddRange
01572 //
01573 //    For performance reasons, we have an upper bound on the number of word
01574 //    ranges we'll add to the spell check selection. Once we reach that upper
01575 //    bound, stop adding the ranges
01576 
01577 nsresult
01578 mozInlineSpellChecker::AddRange(nsISelection* aSpellCheckSelection,
01579                                 nsIDOMRange* aRange)
01580 {
01581   NS_ENSURE_ARG_POINTER(aSpellCheckSelection);
01582   NS_ENSURE_ARG_POINTER(aRange);
01583 
01584   nsresult rv = NS_OK;
01585 
01586   if (!SpellCheckSelectionIsFull())
01587   {
01588     rv = aSpellCheckSelection->AddRange(aRange);
01589     if (NS_SUCCEEDED(rv))
01590       mNumWordsInSpellSelection++;
01591   }
01592 
01593   return rv;
01594 }
01595 
01596 nsresult mozInlineSpellChecker::GetSpellCheckSelection(nsISelection ** aSpellCheckSelection)
01597 { 
01598   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
01599   NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
01600 
01601   nsCOMPtr<nsISelectionController> selcon;
01602   nsresult rv = editor->GetSelectionController(getter_AddRefs(selcon));
01603   NS_ENSURE_SUCCESS(rv, rv); 
01604 
01605   nsCOMPtr<nsISelection> spellCheckSelection;
01606   return selcon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, aSpellCheckSelection);
01607 }
01608 
01609 nsresult mozInlineSpellChecker::SaveCurrentSelectionPosition()
01610 {
01611   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
01612   NS_ENSURE_TRUE(editor, NS_OK);
01613 
01614   // figure out the old caret position based on the current selection
01615   nsCOMPtr<nsISelection> selection;
01616   nsresult rv = editor->GetSelection(getter_AddRefs(selection));
01617   NS_ENSURE_SUCCESS(rv, rv);
01618 
01619   rv = selection->GetFocusNode(getter_AddRefs(mCurrentSelectionAnchorNode));
01620   NS_ENSURE_SUCCESS(rv, rv);
01621   
01622   selection->GetFocusOffset(&mCurrentSelectionOffset);
01623 
01624   return NS_OK;
01625 }
01626 
01627 // This is a copy of nsContentUtils::ContentIsDescendantOf. Another crime
01628 // for XPCOM's rap sheet
01629 PRBool // static
01630 ContentIsDescendantOf(nsIContent* aPossibleDescendant,
01631                       nsIContent* aPossibleAncestor)
01632 {
01633   NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!");
01634   NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!");
01635 
01636   do {
01637     if (aPossibleDescendant == aPossibleAncestor)
01638       return PR_TRUE;
01639     aPossibleDescendant = aPossibleDescendant->GetParent();
01640   } while (aPossibleDescendant);
01641 
01642   return PR_FALSE;
01643 }
01644 
01645 // mozInlineSpellChecker::HandleNavigationEvent
01646 //
01647 //    Acts upon mouse clicks and keyboard navigation changes, spell checking
01648 //    the previous word if the new navigation location moves us to another
01649 //    word.
01650 //
01651 //    This is complicated by the fact that our mouse events are happening after
01652 //    selection has been changed to account for the mouse click. But keyboard
01653 //    events are happening before the caret selection has changed. Working
01654 //    around this by letting keyboard events setting forceWordSpellCheck to
01655 //    true. aNewPositionOffset also tries to work around this for the
01656 //    DOM_VK_RIGHT and DOM_VK_LEFT cases.
01657 
01658 nsresult
01659 mozInlineSpellChecker::HandleNavigationEvent(nsIDOMEvent* aEvent,
01660                                              PRBool aForceWordSpellCheck,
01661                                              PRInt32 aNewPositionOffset)
01662 {
01663   nsresult rv;
01664 
01665   // If we already handled the navigation event and there is no possibility
01666   // anything has changed since then, we don't have to do anything. This
01667   // optimization makes a noticable different when you hold down a navigation
01668   // key like Page Down.
01669   if (! mNeedsCheckAfterNavigation)
01670     return NS_OK;
01671 
01672   nsCOMPtr<nsIDOMNode> currentAnchorNode = mCurrentSelectionAnchorNode;
01673   PRInt32 currentAnchorOffset = mCurrentSelectionOffset;
01674 
01675   // now remember the new focus position resulting from the event
01676   rv = SaveCurrentSelectionPosition();
01677   NS_ENSURE_SUCCESS(rv, rv);
01678 
01679   PRBool shouldPost;
01680   mozInlineSpellStatus status(this);
01681   rv = status.InitForNavigation(aForceWordSpellCheck, aNewPositionOffset,
01682                                 currentAnchorNode, currentAnchorOffset,
01683                                 mCurrentSelectionAnchorNode, mCurrentSelectionOffset,
01684                                 &shouldPost);
01685   NS_ENSURE_SUCCESS(rv, rv);
01686   if (shouldPost) {
01687     rv = ScheduleSpellCheck(status);
01688     NS_ENSURE_SUCCESS(rv, rv);
01689   }
01690 
01691   return NS_OK;
01692 }
01693 
01694 NS_IMETHODIMP mozInlineSpellChecker::HandleEvent(nsIDOMEvent* aEvent)
01695 {
01696   return NS_OK;
01697 }
01698 
01699 NS_IMETHODIMP mozInlineSpellChecker::MouseClick(nsIDOMEvent *aMouseEvent)
01700 {
01701   nsCOMPtr<nsIDOMMouseEvent>mouseEvent = do_QueryInterface(aMouseEvent);
01702   NS_ENSURE_TRUE(mouseEvent, NS_OK);
01703 
01704   // ignore any errors from HandleNavigationEvent as we don't want to prevent 
01705   // anyone else from seeing this event.
01706   PRUint16 button;
01707   mouseEvent->GetButton(&button);
01708   if (button == 0)
01709     HandleNavigationEvent(mouseEvent, PR_FALSE);
01710   else
01711     HandleNavigationEvent(mouseEvent, PR_TRUE);
01712   return NS_OK;
01713 }
01714 
01715 NS_IMETHODIMP mozInlineSpellChecker::MouseDown(nsIDOMEvent* aMouseEvent)
01716 {
01717   return NS_OK;
01718 }
01719 
01720 NS_IMETHODIMP mozInlineSpellChecker::MouseUp(nsIDOMEvent* aMouseEvent)
01721 {
01722   return NS_OK;
01723 }
01724 
01725 NS_IMETHODIMP mozInlineSpellChecker::MouseDblClick(nsIDOMEvent* aMouseEvent)
01726 {
01727   return NS_OK;
01728 }
01729 
01730 NS_IMETHODIMP mozInlineSpellChecker::MouseOver(nsIDOMEvent* aMouseEvent)
01731 {
01732   return NS_OK;
01733 }
01734 
01735 NS_IMETHODIMP mozInlineSpellChecker::MouseOut(nsIDOMEvent* aMouseEvent)
01736 {
01737   return NS_OK;
01738 }
01739 
01740 NS_IMETHODIMP mozInlineSpellChecker::KeyDown(nsIDOMEvent* aKeyEvent)
01741 {
01742   return NS_OK;
01743 }
01744 
01745 
01746 NS_IMETHODIMP mozInlineSpellChecker::KeyUp(nsIDOMEvent* aKeyEvent)
01747 {
01748   return NS_OK;
01749 }
01750 
01751 
01752 NS_IMETHODIMP mozInlineSpellChecker::KeyPress(nsIDOMEvent* aKeyEvent)
01753 {
01754   nsCOMPtr<nsIDOMKeyEvent>keyEvent = do_QueryInterface(aKeyEvent);
01755   NS_ENSURE_TRUE(keyEvent, NS_OK);
01756 
01757   PRUint32 keyCode;
01758   keyEvent->GetKeyCode(&keyCode);
01759 
01760   // we only care about navigation keys that moved selection 
01761   switch (keyCode)
01762   {
01763     case nsIDOMKeyEvent::DOM_VK_RIGHT:
01764     case nsIDOMKeyEvent::DOM_VK_LEFT:
01765       HandleNavigationEvent(aKeyEvent, PR_FALSE, keyCode == nsIDOMKeyEvent::DOM_VK_RIGHT ? 1 : -1);
01766       break;
01767     case nsIDOMKeyEvent::DOM_VK_UP:
01768     case nsIDOMKeyEvent::DOM_VK_DOWN:
01769     case nsIDOMKeyEvent::DOM_VK_HOME:
01770     case nsIDOMKeyEvent::DOM_VK_END:
01771     case nsIDOMKeyEvent::DOM_VK_PAGE_UP:
01772     case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN:
01773       HandleNavigationEvent(aKeyEvent, PR_TRUE /* force a spelling correction */);
01774       break;
01775   }
01776 
01777   return NS_OK;
01778 }