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
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/
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
15 * The Original Code is Real-time Spellchecking
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.
21 * Contributor(s): Neil Deakin (neil@mozdevgroup.com)
22 * Scott MacGregor (mscott@mozilla.org)
23 * Brett Wilson <brettw@gmail.com>
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.
37 * ***** END LICENSE BLOCK ***** */
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.
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.
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.
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.
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.
67 #include "mozInlineSpellChecker.h"
68 #include "mozInlineSpellWordUtil.h"
69 #include "mozISpellI18NManager.h"
72 #include "nsIDocument.h"
73 #include "nsIDOMDocument.h"
74 #include "nsIDOMDocumentRange.h"
75 #include "nsIDOMElement.h"
76 #include "nsIDOMEventTarget.h"
77 #include "nsPIDOMEventTarget.h"
78 #include "nsIDOMMouseEvent.h"
79 #include "nsIDOMKeyEvent.h"
80 #include "nsIDOMNode.h"
81 #include "nsIDOMNodeList.h"
82 #include "nsIDOMNSRange.h"
83 #include "nsIDOMRange.h"
84 #include "nsIDOMText.h"
85 #include "nsIPlaintextEditor.h"
86 #include "nsIPrefBranch.h"
87 #include "nsIPrefService.h"
88 #include "nsIRunnable.h"
89 #include "nsISelection.h"
90 #include "nsISelection2.h"
91 #include "nsISelectionController.h"
92 #include "nsIServiceManager.h"
93 #include "nsITextServicesFilter.h"
95 #include "nsThreadUtils.h"
96 #include "nsUnicharUtils.h"
97 #include "nsIContent.h"
98 #include "nsIEventStateManager.h"
99 #include "nsIEventListenerManager.h"
100 #include "nsGUIEvent.h"
102 // Set to spew messages to the console about what is happening.
103 //#define DEBUG_INLINESPELL
105 // the number of milliseconds that we will take at once to do spellchecking
106 #define INLINESPELL_CHECK_TIMEOUT 50
108 // The number of words to check before we look at the time to see if
109 // INLINESPELL_CHECK_TIMEOUT ms have elapsed. This prevents us from spending
110 // too much time checking the clock. Note that misspelled words count for
111 // more than one word in this calculation.
112 #define INLINESPELL_TIMEOUT_CHECK_FREQUENCY 50
114 // This number is the number of checked words a misspelled word counts for
115 // when we're checking the time to see if the alloted time is up for
116 // spellchecking. Misspelled words take longer to process since we have to
117 // create a range, so they count more. The exact number isn't very important
118 // since this just controls how often we check the current time.
119 #define MISSPELLED_WORD_COUNT_PENALTY 4
122 static PRBool
ContentIsDescendantOf(nsINode
* aPossibleDescendant
,
123 nsINode
* aPossibleAncestor
);
125 static const char kMaxSpellCheckSelectionSize
[] = "extensions.spellcheck.inline.max-misspellings";
127 mozInlineSpellStatus::mozInlineSpellStatus(mozInlineSpellChecker
* aSpellChecker
)
128 : mSpellChecker(aSpellChecker
), mWordCount(0)
132 // mozInlineSpellStatus::InitForEditorChange
134 // This is the most complicated case. For changes, we need to compute the
135 // range of stuff that changed based on the old and new caret positions,
136 // as well as use a range possibly provided by the editor (start and end,
137 // which are usually NULL) to get a range with the union of these.
140 mozInlineSpellStatus::InitForEditorChange(
142 nsIDOMNode
* aAnchorNode
, PRInt32 aAnchorOffset
,
143 nsIDOMNode
* aPreviousNode
, PRInt32 aPreviousOffset
,
144 nsIDOMNode
* aStartNode
, PRInt32 aStartOffset
,
145 nsIDOMNode
* aEndNode
, PRInt32 aEndOffset
)
149 nsCOMPtr
<nsIDOMDocumentRange
> docRange
;
150 rv
= GetDocumentRange(getter_AddRefs(docRange
));
151 NS_ENSURE_SUCCESS(rv
, rv
);
153 // save the anchor point as a range so we can find the current word later
154 rv
= PositionToCollapsedRange(docRange
, aAnchorNode
, aAnchorOffset
,
155 getter_AddRefs(mAnchorRange
));
156 NS_ENSURE_SUCCESS(rv
, rv
);
158 if (aAction
== mozInlineSpellChecker::kOpDeleteSelection
) {
159 // Deletes are easy, the range is just the current anchor. We set the range
160 // to check to be empty, FinishInitOnEvent will fill in the range to be
162 mOp
= eOpChangeDelete
;
170 rv
= docRange
->CreateRange(getter_AddRefs(mRange
));
171 NS_ENSURE_SUCCESS(rv
, rv
);
173 // ...we need to put the start and end in the correct order
174 nsCOMPtr
<nsIDOMNSRange
> nsrange
= do_QueryInterface(mAnchorRange
, &rv
);
175 NS_ENSURE_SUCCESS(rv
, rv
);
177 rv
= nsrange
->ComparePoint(aPreviousNode
, aPreviousOffset
, &cmpResult
);
178 NS_ENSURE_SUCCESS(rv
, rv
);
180 // previous anchor node is before the current anchor
181 rv
= mRange
->SetStart(aPreviousNode
, aPreviousOffset
);
182 NS_ENSURE_SUCCESS(rv
, rv
);
183 rv
= mRange
->SetEnd(aAnchorNode
, aAnchorOffset
);
185 // previous anchor node is after (or the same as) the current anchor
186 rv
= mRange
->SetStart(aAnchorNode
, aAnchorOffset
);
187 NS_ENSURE_SUCCESS(rv
, rv
);
188 rv
= mRange
->SetEnd(aPreviousNode
, aPreviousOffset
);
190 NS_ENSURE_SUCCESS(rv
, rv
);
192 // On insert save this range: DoSpellCheck optimizes things in this range.
193 // Otherwise, just leave this NULL.
194 if (aAction
== mozInlineSpellChecker::kOpInsertText
)
195 mCreatedRange
= mRange
;
197 // if we were given a range, we need to expand our range to encompass it
198 if (aStartNode
&& aEndNode
) {
199 nsrange
= do_QueryInterface(mRange
, &rv
);
200 NS_ENSURE_SUCCESS(rv
, rv
);
202 rv
= nsrange
->ComparePoint(aStartNode
, aStartOffset
, &cmpResult
);
203 NS_ENSURE_SUCCESS(rv
, rv
);
204 if (cmpResult
< 0) { // given range starts before
205 rv
= mRange
->SetStart(aStartNode
, aStartOffset
);
206 NS_ENSURE_SUCCESS(rv
, rv
);
209 rv
= nsrange
->ComparePoint(aEndNode
, aEndOffset
, &cmpResult
);
210 NS_ENSURE_SUCCESS(rv
, rv
);
211 if (cmpResult
> 0) { // given range ends after
212 rv
= mRange
->SetEnd(aEndNode
, aEndOffset
);
213 NS_ENSURE_SUCCESS(rv
, rv
);
220 // mozInlineSpellStatis::InitForNavigation
222 // For navigation events, we just need to store the new and old positions.
224 // In some cases, we detect that we shouldn't check. If this event should
225 // not be processed, *aContinue will be false.
228 mozInlineSpellStatus::InitForNavigation(
229 PRBool aForceCheck
, PRInt32 aNewPositionOffset
,
230 nsIDOMNode
* aOldAnchorNode
, PRInt32 aOldAnchorOffset
,
231 nsIDOMNode
* aNewAnchorNode
, PRInt32 aNewAnchorOffset
,
237 mForceNavigationWordCheck
= aForceCheck
;
238 mNewNavigationPositionOffset
= aNewPositionOffset
;
240 // get the root node for checking
241 nsCOMPtr
<nsIEditor
> editor
= do_QueryReferent(mSpellChecker
->mEditor
, &rv
);
242 NS_ENSURE_SUCCESS(rv
, rv
);
243 nsCOMPtr
<nsIDOMElement
> rootElt
;
244 rv
= editor
->GetRootElement(getter_AddRefs(rootElt
));
245 NS_ENSURE_SUCCESS(rv
, rv
);
247 // the anchor node might not be in the DOM anymore, check
248 nsCOMPtr
<nsINode
> root
= do_QueryInterface(rootElt
, &rv
);
249 NS_ENSURE_SUCCESS(rv
, rv
);
250 nsCOMPtr
<nsINode
> currentAnchor
= do_QueryInterface(aOldAnchorNode
, &rv
);
251 NS_ENSURE_SUCCESS(rv
, rv
);
252 if (root
&& currentAnchor
&& ! ContentIsDescendantOf(currentAnchor
, root
)) {
253 *aContinue
= PR_FALSE
;
257 nsCOMPtr
<nsIDOMDocumentRange
> docRange
;
258 rv
= GetDocumentRange(getter_AddRefs(docRange
));
259 NS_ENSURE_SUCCESS(rv
, rv
);
261 rv
= PositionToCollapsedRange(docRange
, aOldAnchorNode
, aOldAnchorOffset
,
262 getter_AddRefs(mOldNavigationAnchorRange
));
263 NS_ENSURE_SUCCESS(rv
, rv
);
264 rv
= PositionToCollapsedRange(docRange
, aNewAnchorNode
, aNewAnchorOffset
,
265 getter_AddRefs(mAnchorRange
));
266 NS_ENSURE_SUCCESS(rv
, rv
);
268 *aContinue
= PR_TRUE
;
272 // mozInlineSpellStatus::InitForSelection
274 // It is easy for selections since we always re-check the spellcheck
278 mozInlineSpellStatus::InitForSelection()
284 // mozInlineSpellStatus::InitForRange
286 // Called to cause the spellcheck of the given range. This will look like
287 // a change operation over the given range.
290 mozInlineSpellStatus::InitForRange(nsIDOMRange
* aRange
)
297 // mozInlineSpellStatus::FinishInitOnEvent
299 // Called when the event is triggered to complete initialization that
300 // might require the WordUtil. This calls to the operation-specific
301 // initializer, and also sets the range to be the entire element if it
304 // Watch out: the range might still be NULL if there is nothing to do,
305 // the caller will have to check for this.
308 mozInlineSpellStatus::FinishInitOnEvent(mozInlineSpellWordUtil
& aWordUtil
)
312 rv
= mSpellChecker
->MakeSpellCheckRange(nsnull
, 0, nsnull
, 0,
313 getter_AddRefs(mRange
));
314 NS_ENSURE_SUCCESS(rv
, rv
);
320 return FillNoCheckRangeFromAnchor(aWordUtil
);
322 case eOpChangeDelete
:
324 rv
= FillNoCheckRangeFromAnchor(aWordUtil
);
325 NS_ENSURE_SUCCESS(rv
, rv
);
327 // Delete events will have no range for the changed text (because it was
328 // deleted), and InitForEditorChange will set it to NULL. Here, we select
329 // the entire word to cause any underlining to be removed.
330 mRange
= mNoCheckRange
;
333 return FinishNavigationEvent(aWordUtil
);
335 // this gets special handling in ResumeCheck
338 // everything should be initialized already in this case
341 NS_NOTREACHED("Bad operation");
342 return NS_ERROR_NOT_INITIALIZED
;
347 // mozInlineSpellStatus::FinishNavigationEvent
349 // This verifies that we need to check the word at the previous caret
350 // position. Now that we have the word util, we can find the word belonging
351 // to the previous caret position. If the new position is inside that word,
352 // we don't want to do anything. In this case, we'll NULL out mRange so
353 // that the caller will know not to continue.
355 // Notice that we don't set mNoCheckRange. We check here whether the cursor
356 // is in the word that needs checking, so it isn't necessary. Plus, the
357 // spellchecker isn't guaranteed to only check the given word, and it could
358 // remove the underline from the new word under the cursor.
361 mozInlineSpellStatus::FinishNavigationEvent(mozInlineSpellWordUtil
& aWordUtil
)
363 nsCOMPtr
<nsIEditor
> editor
= do_QueryReferent(mSpellChecker
->mEditor
);
365 return NS_ERROR_FAILURE
; // editor is gone
367 NS_ASSERTION(mAnchorRange
, "No anchor for navigation!");
368 nsCOMPtr
<nsIDOMNode
> newAnchorNode
, oldAnchorNode
;
369 PRInt32 newAnchorOffset
, oldAnchorOffset
;
371 // get the DOM position of the old caret, the range should be collapsed
372 nsresult rv
= mOldNavigationAnchorRange
->GetStartContainer(
373 getter_AddRefs(oldAnchorNode
));
374 NS_ENSURE_SUCCESS(rv
, rv
);
375 rv
= mOldNavigationAnchorRange
->GetStartOffset(&oldAnchorOffset
);
376 NS_ENSURE_SUCCESS(rv
, rv
);
378 // find the word on the old caret position, this is the one that we MAY need
380 nsCOMPtr
<nsIDOMRange
> oldWord
;
381 rv
= aWordUtil
.GetRangeForWord(oldAnchorNode
, oldAnchorOffset
,
382 getter_AddRefs(oldWord
));
383 NS_ENSURE_SUCCESS(rv
, rv
);
385 // aWordUtil.GetRangeForWord flushes pending notifications, check editor again.
386 editor
= do_QueryReferent(mSpellChecker
->mEditor
);
388 return NS_ERROR_FAILURE
; // editor is gone
390 nsCOMPtr
<nsIDOMNSRange
> oldWordNS
= do_QueryInterface(oldWord
, &rv
);
391 NS_ENSURE_SUCCESS(rv
, rv
);
393 // get the DOM position of the new caret, the range should be collapsed
394 rv
= mAnchorRange
->GetStartContainer(getter_AddRefs(newAnchorNode
));
395 NS_ENSURE_SUCCESS(rv
, rv
);
396 rv
= mAnchorRange
->GetStartOffset(&newAnchorOffset
);
397 NS_ENSURE_SUCCESS(rv
, rv
);
399 // see if the new cursor position is in the word of the old cursor position
400 PRBool isInRange
= PR_FALSE
;
401 if (! mForceNavigationWordCheck
) {
402 rv
= oldWordNS
->IsPointInRange(newAnchorNode
,
403 newAnchorOffset
+ mNewNavigationPositionOffset
,
405 NS_ENSURE_SUCCESS(rv
, rv
);
409 // caller should give up
412 // check the old word
415 // Once we've spellchecked the current word, we don't need to spellcheck
416 // for any more navigation events.
417 mSpellChecker
->mNeedsCheckAfterNavigation
= PR_FALSE
;
422 // mozInlineSpellStatus::FillNoCheckRangeFromAnchor
424 // Given the mAnchorRange object, computes the range of the word it is on
425 // (if any) and fills that range into mNoCheckRange. This is used for
426 // change and navigation events to know which word we should skip spell
430 mozInlineSpellStatus::FillNoCheckRangeFromAnchor(
431 mozInlineSpellWordUtil
& aWordUtil
)
433 nsCOMPtr
<nsIDOMNode
> anchorNode
;
434 nsresult rv
= mAnchorRange
->GetStartContainer(getter_AddRefs(anchorNode
));
435 NS_ENSURE_SUCCESS(rv
, rv
);
437 PRInt32 anchorOffset
;
438 rv
= mAnchorRange
->GetStartOffset(&anchorOffset
);
439 NS_ENSURE_SUCCESS(rv
, rv
);
441 return aWordUtil
.GetRangeForWord(anchorNode
, anchorOffset
,
442 getter_AddRefs(mNoCheckRange
));
445 // mozInlineSpellStatus::GetDocumentRange
447 // Returns the nsIDOMDocumentRange object for the document for the
448 // current spellchecker.
451 mozInlineSpellStatus::GetDocumentRange(nsIDOMDocumentRange
** aDocRange
)
455 if (! mSpellChecker
->mEditor
)
456 return NS_ERROR_UNEXPECTED
;
458 nsCOMPtr
<nsIEditor
> editor
= do_QueryReferent(mSpellChecker
->mEditor
, &rv
);
459 NS_ENSURE_SUCCESS(rv
, rv
);
461 nsCOMPtr
<nsIDOMDocument
> domDoc
;
462 rv
= editor
->GetDocument(getter_AddRefs(domDoc
));
463 NS_ENSURE_SUCCESS(rv
, rv
);
465 nsCOMPtr
<nsIDOMDocumentRange
> docRange
= do_QueryInterface(domDoc
, &rv
);
466 NS_ENSURE_SUCCESS(rv
, rv
);
468 docRange
.swap(*aDocRange
);
472 // mozInlineSpellStatus::PositionToCollapsedRange
474 // Converts a given DOM position to a collapsed range covering that
475 // position. We use ranges to store DOM positions becuase they stay
476 // updated as the DOM is changed.
479 mozInlineSpellStatus::PositionToCollapsedRange(nsIDOMDocumentRange
* aDocRange
,
480 nsIDOMNode
* aNode
, PRInt32 aOffset
, nsIDOMRange
** aRange
)
483 nsCOMPtr
<nsIDOMRange
> range
;
484 nsresult rv
= aDocRange
->CreateRange(getter_AddRefs(range
));
485 NS_ENSURE_SUCCESS(rv
, rv
);
487 rv
= range
->SetStart(aNode
, aOffset
);
488 NS_ENSURE_SUCCESS(rv
, rv
);
489 rv
= range
->SetEnd(aNode
, aOffset
);
490 NS_ENSURE_SUCCESS(rv
, rv
);
496 // mozInlineSpellResume
498 class mozInlineSpellResume
: public nsRunnable
501 mozInlineSpellResume(const mozInlineSpellStatus
& aStatus
) : mStatus(aStatus
) {}
502 mozInlineSpellStatus mStatus
;
505 return NS_DispatchToMainThread(this);
510 mStatus
.mSpellChecker
->ResumeCheck(&mStatus
);
516 NS_INTERFACE_MAP_BEGIN(mozInlineSpellChecker
)
517 NS_INTERFACE_MAP_ENTRY(nsIInlineSpellChecker
)
518 NS_INTERFACE_MAP_ENTRY(nsIEditActionListener
)
519 NS_INTERFACE_MAP_ENTRY(nsIDOMFocusListener
)
520 NS_INTERFACE_MAP_ENTRY(nsIDOMMouseListener
)
521 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
522 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIDOMKeyListener
)
523 NS_INTERFACE_MAP_ENTRY(nsIDOMKeyListener
)
524 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener
, nsIDOMKeyListener
)
527 NS_IMPL_ADDREF(mozInlineSpellChecker
)
528 NS_IMPL_RELEASE(mozInlineSpellChecker
)
530 mozInlineSpellChecker::SpellCheckingState
531 mozInlineSpellChecker::gCanEnableSpellChecking
=
532 mozInlineSpellChecker::SpellCheck_Uninitialized
;
534 mozInlineSpellChecker::mozInlineSpellChecker() :
535 mNumWordsInSpellSelection(0),
536 mMaxNumWordsInSpellSelection(250),
537 mNeedsCheckAfterNavigation(PR_FALSE
)
539 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
541 prefs
->GetIntPref(kMaxSpellCheckSelectionSize
, &mMaxNumWordsInSpellSelection
);
542 mMaxMisspellingsPerCheck
= mMaxNumWordsInSpellSelection
* 3 / 4;
545 mozInlineSpellChecker::~mozInlineSpellChecker()
550 mozInlineSpellChecker::GetSpellChecker(nsIEditorSpellCheck
**aSpellCheck
)
552 *aSpellCheck
= mSpellCheck
;
553 NS_IF_ADDREF(*aSpellCheck
);
558 mozInlineSpellChecker::Init(nsIEditor
*aEditor
)
560 mEditor
= do_GetWeakReference(aEditor
);
564 // mozInlineSpellChecker::Cleanup
566 // Called by the editor when the editor is going away. This is important
567 // because we remove listeners. We do NOT clean up anything else in this
568 // function, because it can get called while DoSpellCheck is running!
570 // Getting the style information there can cause DOM notifications to be
571 // flushed, which can cause editors to go away which will bring us here.
572 // We can not do anything that will cause DoSpellCheck to freak out.
574 nsresult
mozInlineSpellChecker::Cleanup(PRBool aDestroyingFrames
)
576 mNumWordsInSpellSelection
= 0;
577 nsCOMPtr
<nsISelection
> spellCheckSelection
;
578 nsresult rv
= GetSpellCheckSelection(getter_AddRefs(spellCheckSelection
));
580 // Ensure we still unregister event listeners (but return a failure code)
581 UnregisterEventListeners();
583 if (!aDestroyingFrames
) {
584 spellCheckSelection
->RemoveAllRanges();
587 rv
= UnregisterEventListeners();
594 // mozInlineSpellChecker::CanEnableInlineSpellChecking
596 // This function can be called to see if it seems likely that we can enable
597 // spellchecking before actually creating the InlineSpellChecking objects.
599 // The problem is that we can't get the dictionary list without actually
600 // creating a whole bunch of spellchecking objects. This function tries to
601 // do that and caches the result so we don't have to keep allocating those
602 // objects if there are no dictionaries or spellchecking.
604 // This caching will prevent adding dictionaries at runtime if we start out
605 // with no dictionaries! Installing dictionaries as extensions will require
606 // a restart anyway, so it shouldn't be a problem.
609 mozInlineSpellChecker::CanEnableInlineSpellChecking()
612 if (gCanEnableSpellChecking
== SpellCheck_Uninitialized
) {
613 gCanEnableSpellChecking
= SpellCheck_NotAvailable
;
615 nsCOMPtr
<nsIEditorSpellCheck
> spellchecker
=
616 do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &rv
);
617 NS_ENSURE_SUCCESS(rv
, PR_FALSE
);
619 PRBool canSpellCheck
= PR_FALSE
;
620 rv
= spellchecker
->CanSpellCheck(&canSpellCheck
);
621 NS_ENSURE_SUCCESS(rv
, PR_FALSE
);
624 gCanEnableSpellChecking
= SpellCheck_Available
;
626 return (gCanEnableSpellChecking
== SpellCheck_Available
);
629 // mozInlineSpellChecker::RegisterEventListeners
631 // The inline spell checker listens to mouse events and keyboard navigation+ // events.
634 mozInlineSpellChecker::RegisterEventListeners()
636 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
637 NS_ENSURE_TRUE(editor
, NS_ERROR_NULL_POINTER
);
639 editor
->AddEditActionListener(this);
641 nsCOMPtr
<nsIDOMDocument
> doc
;
642 nsresult rv
= editor
->GetDocument(getter_AddRefs(doc
));
643 NS_ENSURE_SUCCESS(rv
, rv
);
645 nsCOMPtr
<nsPIDOMEventTarget
> piTarget
= do_QueryInterface(doc
, &rv
);
646 NS_ENSURE_SUCCESS(rv
, rv
);
648 nsCOMPtr
<nsIEventListenerManager
> elmP
;
649 piTarget
->GetListenerManager(PR_TRUE
, getter_AddRefs(elmP
));
651 // Focus event doesn't bubble so adding the listener to capturing phase
652 elmP
->AddEventListenerByIID(static_cast<nsIDOMFocusListener
*>(this),
653 NS_GET_IID(nsIDOMFocusListener
),
654 NS_EVENT_FLAG_CAPTURE
);
657 piTarget
->AddEventListenerByIID(static_cast<nsIDOMMouseListener
*>(this),
658 NS_GET_IID(nsIDOMMouseListener
));
659 piTarget
->AddEventListenerByIID(static_cast<nsIDOMKeyListener
*>(this),
660 NS_GET_IID(nsIDOMKeyListener
));
665 // mozInlineSpellChecker::UnregisterEventListeners
668 mozInlineSpellChecker::UnregisterEventListeners()
670 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
671 NS_ENSURE_TRUE(editor
, NS_ERROR_NULL_POINTER
);
673 editor
->RemoveEditActionListener(this);
675 nsCOMPtr
<nsIDOMDocument
> doc
;
676 editor
->GetDocument(getter_AddRefs(doc
));
677 NS_ENSURE_TRUE(doc
, NS_ERROR_NULL_POINTER
);
679 nsCOMPtr
<nsPIDOMEventTarget
> piTarget
= do_QueryInterface(doc
);
680 NS_ENSURE_TRUE(piTarget
, NS_ERROR_NULL_POINTER
);
682 nsCOMPtr
<nsIEventListenerManager
> elmP
;
683 piTarget
->GetListenerManager(PR_TRUE
, getter_AddRefs(elmP
));
685 elmP
->RemoveEventListenerByIID(static_cast<nsIDOMFocusListener
*>(this),
686 NS_GET_IID(nsIDOMFocusListener
),
687 NS_EVENT_FLAG_CAPTURE
);
690 piTarget
->RemoveEventListenerByIID(static_cast<nsIDOMMouseListener
*>(this),
691 NS_GET_IID(nsIDOMMouseListener
));
692 piTarget
->RemoveEventListenerByIID(static_cast<nsIDOMKeyListener
*>(this),
693 NS_GET_IID(nsIDOMKeyListener
));
698 // mozInlineSpellChecker::GetEnableRealTimeSpell
701 mozInlineSpellChecker::GetEnableRealTimeSpell(PRBool
* aEnabled
)
703 NS_ENSURE_ARG_POINTER(aEnabled
);
704 *aEnabled
= mSpellCheck
!= nsnull
;
708 // mozInlineSpellChecker::SetEnableRealTimeSpell
711 mozInlineSpellChecker::SetEnableRealTimeSpell(PRBool aEnabled
)
714 mSpellCheck
= nsnull
;
715 return Cleanup(PR_FALSE
);
719 nsresult res
= NS_OK
;
720 nsCOMPtr
<nsIEditorSpellCheck
> spellchecker
= do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &res
);
721 if (NS_SUCCEEDED(res
) && spellchecker
)
723 nsCOMPtr
<nsITextServicesFilter
> filter
= do_CreateInstance("@mozilla.org/editor/txtsrvfiltermail;1", &res
);
724 spellchecker
->SetFilter(filter
);
725 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
726 res
= spellchecker
->InitSpellChecker(editor
, PR_FALSE
);
727 NS_ENSURE_SUCCESS(res
, res
);
729 nsCOMPtr
<nsITextServicesDocument
> tsDoc
= do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &res
);
730 NS_ENSURE_SUCCESS(res
, res
);
732 res
= tsDoc
->SetFilter(filter
);
733 NS_ENSURE_SUCCESS(res
, res
);
735 res
= tsDoc
->InitWithEditor(editor
);
736 NS_ENSURE_SUCCESS(res
, res
);
738 mTextServicesDocument
= tsDoc
;
739 mSpellCheck
= spellchecker
;
741 // spell checking is enabled, register our event listeners to track navigation
742 RegisterEventListeners();
746 // spellcheck the current contents. SpellCheckRange doesn't supply a created
747 // range to DoSpellCheck, which in our case is the entire range. But this
748 // optimization doesn't matter because there is nothing in the spellcheck
749 // selection when starting, which triggers a better optimization.
750 return SpellCheckRange(nsnull
);
753 // mozInlineSpellChecker::SpellCheckAfterEditorChange
755 // Called by the editor when nearly anything happens to change the content.
757 // The start and end positions specify a range for the thing that happened,
758 // but these are usually NULL, even when you'd think they would be useful
759 // because you want the range (for example, pasting). We ignore them in
763 mozInlineSpellChecker::SpellCheckAfterEditorChange(
764 PRInt32 aAction
, nsISelection
*aSelection
,
765 nsIDOMNode
*aPreviousSelectedNode
, PRInt32 aPreviousSelectedOffset
,
766 nsIDOMNode
*aStartNode
, PRInt32 aStartOffset
,
767 nsIDOMNode
*aEndNode
, PRInt32 aEndOffset
)
770 NS_ENSURE_ARG_POINTER(aSelection
);
772 return NS_OK
; // disabling spell checking is not an error
774 // this means something has changed, and we never check the current word,
775 // therefore, we should spellcheck for subsequent caret navigations
776 mNeedsCheckAfterNavigation
= PR_TRUE
;
778 // the anchor node is the position of the caret
779 nsCOMPtr
<nsIDOMNode
> anchorNode
;
780 rv
= aSelection
->GetAnchorNode(getter_AddRefs(anchorNode
));
781 NS_ENSURE_SUCCESS(rv
, rv
);
782 PRInt32 anchorOffset
;
783 rv
= aSelection
->GetAnchorOffset(&anchorOffset
);
784 NS_ENSURE_SUCCESS(rv
, rv
);
786 mozInlineSpellStatus
status(this);
787 rv
= status
.InitForEditorChange(aAction
,
788 anchorNode
, anchorOffset
,
789 aPreviousSelectedNode
, aPreviousSelectedOffset
,
790 aStartNode
, aStartOffset
,
791 aEndNode
, aEndOffset
);
792 NS_ENSURE_SUCCESS(rv
, rv
);
793 rv
= ScheduleSpellCheck(status
);
794 NS_ENSURE_SUCCESS(rv
, rv
);
796 // remember the current caret position after every change
797 SaveCurrentSelectionPosition();
801 // mozInlineSpellChecker::SpellCheckRange
803 // Spellchecks all the words in the given range.
804 // Supply a NULL range and this will check the entire editor.
807 mozInlineSpellChecker::SpellCheckRange(nsIDOMRange
* aRange
)
809 NS_ENSURE_TRUE(mSpellCheck
, NS_ERROR_NOT_INITIALIZED
);
811 mozInlineSpellStatus
status(this);
812 nsresult rv
= status
.InitForRange(aRange
);
813 NS_ENSURE_SUCCESS(rv
, rv
);
814 return ScheduleSpellCheck(status
);
817 // mozInlineSpellChecker::GetMispelledWord
820 mozInlineSpellChecker::GetMispelledWord(nsIDOMNode
*aNode
, PRInt32 aOffset
,
821 nsIDOMRange
**newword
)
823 NS_ENSURE_ARG_POINTER(aNode
);
824 nsCOMPtr
<nsISelection
> spellCheckSelection
;
825 nsresult res
= GetSpellCheckSelection(getter_AddRefs(spellCheckSelection
));
826 NS_ENSURE_SUCCESS(res
, res
);
828 return IsPointInSelection(spellCheckSelection
, aNode
, aOffset
, newword
);
831 // mozInlineSpellChecker::ReplaceWord
834 mozInlineSpellChecker::ReplaceWord(nsIDOMNode
*aNode
, PRInt32 aOffset
,
835 const nsAString
&newword
)
837 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
838 NS_ENSURE_TRUE(editor
, NS_ERROR_NULL_POINTER
);
839 NS_ENSURE_TRUE(newword
.Length() != 0, NS_ERROR_FAILURE
);
841 nsCOMPtr
<nsIDOMRange
> range
;
842 nsresult res
= GetMispelledWord(aNode
, aOffset
, getter_AddRefs(range
));
843 NS_ENSURE_SUCCESS(res
, res
);
847 editor
->BeginTransaction();
849 nsCOMPtr
<nsISelection
> selection
;
850 res
= editor
->GetSelection(getter_AddRefs(selection
));
851 NS_ENSURE_SUCCESS(res
, res
);
852 selection
->RemoveAllRanges();
853 selection
->AddRange(range
);
854 editor
->DeleteSelection(nsIEditor::eNone
);
856 nsCOMPtr
<nsIPlaintextEditor
> textEditor(do_QueryReferent(mEditor
));
857 textEditor
->InsertText(newword
);
859 editor
->EndTransaction();
865 // mozInlineSpellChecker::AddWordToDictionary
868 mozInlineSpellChecker::AddWordToDictionary(const nsAString
&word
)
870 NS_ENSURE_TRUE(mSpellCheck
, NS_ERROR_NOT_INITIALIZED
);
872 nsAutoString
wordstr(word
);
873 nsresult rv
= mSpellCheck
->AddWordToDictionary(wordstr
.get());
874 NS_ENSURE_SUCCESS(rv
, rv
);
876 mozInlineSpellStatus
status(this);
877 rv
= status
.InitForSelection();
878 NS_ENSURE_SUCCESS(rv
, rv
);
879 return ScheduleSpellCheck(status
);
882 // mozInlineSpellChecker::IgnoreWord
885 mozInlineSpellChecker::IgnoreWord(const nsAString
&word
)
887 NS_ENSURE_TRUE(mSpellCheck
, NS_ERROR_NOT_INITIALIZED
);
889 nsAutoString
wordstr(word
);
890 nsresult rv
= mSpellCheck
->IgnoreWordAllOccurrences(wordstr
.get());
891 NS_ENSURE_SUCCESS(rv
, rv
);
893 mozInlineSpellStatus
status(this);
894 rv
= status
.InitForSelection();
895 NS_ENSURE_SUCCESS(rv
, rv
);
896 return ScheduleSpellCheck(status
);
899 // mozInlineSpellChecker::IgnoreWords
902 mozInlineSpellChecker::IgnoreWords(const PRUnichar
**aWordsToIgnore
,
905 // add each word to the ignore list and then recheck the document
906 for (PRUint32 index
= 0; index
< aCount
; index
++)
907 mSpellCheck
->IgnoreWordAllOccurrences(aWordsToIgnore
[index
]);
909 mozInlineSpellStatus
status(this);
910 nsresult rv
= status
.InitForSelection();
911 NS_ENSURE_SUCCESS(rv
, rv
);
912 return ScheduleSpellCheck(status
);
915 NS_IMETHODIMP
mozInlineSpellChecker::WillCreateNode(const nsAString
& aTag
, nsIDOMNode
*aParent
, PRInt32 aPosition
)
920 NS_IMETHODIMP
mozInlineSpellChecker::DidCreateNode(const nsAString
& aTag
, nsIDOMNode
*aNode
, nsIDOMNode
*aParent
,
921 PRInt32 aPosition
, nsresult aResult
)
926 NS_IMETHODIMP
mozInlineSpellChecker::WillInsertNode(nsIDOMNode
*aNode
, nsIDOMNode
*aParent
,
932 NS_IMETHODIMP
mozInlineSpellChecker::DidInsertNode(nsIDOMNode
*aNode
, nsIDOMNode
*aParent
,
933 PRInt32 aPosition
, nsresult aResult
)
939 NS_IMETHODIMP
mozInlineSpellChecker::WillDeleteNode(nsIDOMNode
*aChild
)
944 NS_IMETHODIMP
mozInlineSpellChecker::DidDeleteNode(nsIDOMNode
*aChild
, nsresult aResult
)
949 NS_IMETHODIMP
mozInlineSpellChecker::WillSplitNode(nsIDOMNode
*aExistingRightNode
, PRInt32 aOffset
)
955 mozInlineSpellChecker::DidSplitNode(nsIDOMNode
*aExistingRightNode
,
957 nsIDOMNode
*aNewLeftNode
, nsresult aResult
)
959 return SpellCheckBetweenNodes(aNewLeftNode
, 0, aNewLeftNode
, 0);
962 NS_IMETHODIMP
mozInlineSpellChecker::WillJoinNodes(nsIDOMNode
*aLeftNode
, nsIDOMNode
*aRightNode
, nsIDOMNode
*aParent
)
967 NS_IMETHODIMP
mozInlineSpellChecker::DidJoinNodes(nsIDOMNode
*aLeftNode
, nsIDOMNode
*aRightNode
,
968 nsIDOMNode
*aParent
, nsresult aResult
)
970 return SpellCheckBetweenNodes(aRightNode
, 0, aRightNode
, 0);
973 NS_IMETHODIMP
mozInlineSpellChecker::WillInsertText(nsIDOMCharacterData
*aTextNode
, PRInt32 aOffset
, const nsAString
& aString
)
978 NS_IMETHODIMP
mozInlineSpellChecker::DidInsertText(nsIDOMCharacterData
*aTextNode
, PRInt32 aOffset
,
979 const nsAString
& aString
, nsresult aResult
)
984 NS_IMETHODIMP
mozInlineSpellChecker::WillDeleteText(nsIDOMCharacterData
*aTextNode
, PRInt32 aOffset
, PRInt32 aLength
)
989 NS_IMETHODIMP
mozInlineSpellChecker::DidDeleteText(nsIDOMCharacterData
*aTextNode
, PRInt32 aOffset
, PRInt32 aLength
, nsresult aResult
)
994 NS_IMETHODIMP
mozInlineSpellChecker::WillDeleteSelection(nsISelection
*aSelection
)
999 NS_IMETHODIMP
mozInlineSpellChecker::DidDeleteSelection(nsISelection
*aSelection
)
1004 // mozInlineSpellChecker::MakeSpellCheckRange
1006 // Given begin and end positions, this function constructs a range as
1007 // required for ScheduleSpellCheck. If the start and end nodes are NULL,
1008 // then the entire range will be selected, and you can supply -1 as the
1009 // offset to the end range to select all of that node.
1011 // If the resulting range would be empty, NULL is put into *aRange and the
1012 // function succeeds.
1015 mozInlineSpellChecker::MakeSpellCheckRange(
1016 nsIDOMNode
* aStartNode
, PRInt32 aStartOffset
,
1017 nsIDOMNode
* aEndNode
, PRInt32 aEndOffset
,
1018 nsIDOMRange
** aRange
)
1023 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
1024 NS_ENSURE_TRUE(editor
, NS_ERROR_NULL_POINTER
);
1026 nsCOMPtr
<nsIDOMDocument
> doc
;
1027 rv
= editor
->GetDocument(getter_AddRefs(doc
));
1028 NS_ENSURE_SUCCESS(rv
, rv
);
1030 nsCOMPtr
<nsIDOMDocumentRange
> docrange
= do_QueryInterface(doc
);
1031 NS_ENSURE_TRUE(docrange
, NS_ERROR_FAILURE
);
1033 nsCOMPtr
<nsIDOMRange
> range
;
1034 rv
= docrange
->CreateRange(getter_AddRefs(range
));
1035 NS_ENSURE_SUCCESS(rv
, rv
);
1037 // possibly use full range of the editor
1038 nsCOMPtr
<nsIDOMElement
> rootElem
;
1039 if (! aStartNode
|| ! aEndNode
) {
1040 rv
= editor
->GetRootElement(getter_AddRefs(rootElem
));
1041 NS_ENSURE_SUCCESS(rv
, rv
);
1043 aStartNode
= rootElem
;
1046 aEndNode
= rootElem
;
1050 if (aEndOffset
== -1) {
1051 nsCOMPtr
<nsIDOMNodeList
> childNodes
;
1052 rv
= aEndNode
->GetChildNodes(getter_AddRefs(childNodes
));
1053 NS_ENSURE_SUCCESS(rv
, rv
);
1055 PRUint32 childCount
;
1056 rv
= childNodes
->GetLength(&childCount
);
1057 NS_ENSURE_SUCCESS(rv
, rv
);
1059 aEndOffset
= childCount
;
1062 // sometimes we are are requested to check an empty range (possibly an empty
1063 // document). This will result in assertions later.
1064 if (aStartNode
== aEndNode
&& aStartOffset
== aEndOffset
)
1067 rv
= range
->SetStart(aStartNode
, aStartOffset
);
1068 NS_ENSURE_SUCCESS(rv
, rv
);
1070 rv
= range
->SetEnd(aEndNode
, aEndOffset
);
1072 rv
= range
->SetEndAfter(aEndNode
);
1073 NS_ENSURE_SUCCESS(rv
, rv
);
1075 range
.swap(*aRange
);
1080 mozInlineSpellChecker::SpellCheckBetweenNodes(nsIDOMNode
*aStartNode
,
1081 PRInt32 aStartOffset
,
1082 nsIDOMNode
*aEndNode
,
1085 nsCOMPtr
<nsIDOMRange
> range
;
1086 nsresult rv
= MakeSpellCheckRange(aStartNode
, aStartOffset
,
1087 aEndNode
, aEndOffset
,
1088 getter_AddRefs(range
));
1089 NS_ENSURE_SUCCESS(rv
, rv
);
1092 return NS_OK
; // range is empty: nothing to do
1094 mozInlineSpellStatus
status(this);
1095 rv
= status
.InitForRange(range
);
1096 NS_ENSURE_SUCCESS(rv
, rv
);
1097 return ScheduleSpellCheck(status
);
1100 // mozInlineSpellChecker::SkipSpellCheckForNode
1102 // There are certain conditions when we don't want to spell check a node. In
1103 // particular quotations, moz signatures, etc. This routine returns false
1107 mozInlineSpellChecker::SkipSpellCheckForNode(nsIEditor
* aEditor
,
1109 PRBool
*checkSpelling
)
1111 *checkSpelling
= PR_TRUE
;
1112 NS_ENSURE_ARG_POINTER(aNode
);
1115 aEditor
->GetFlags(&flags
);
1116 if (flags
& nsIPlaintextEditor::eEditorMailMask
)
1118 nsCOMPtr
<nsIDOMNode
> parent
;
1119 aNode
->GetParentNode(getter_AddRefs(parent
));
1123 nsCOMPtr
<nsIDOMElement
> parentElement
= do_QueryInterface(parent
);
1127 nsAutoString parentTagName
;
1128 parentElement
->GetTagName(parentTagName
);
1130 if (parentTagName
.Equals(NS_LITERAL_STRING("blockquote"), nsCaseInsensitiveStringComparator()))
1132 *checkSpelling
= PR_FALSE
;
1135 else if (parentTagName
.Equals(NS_LITERAL_STRING("pre"), nsCaseInsensitiveStringComparator()))
1137 nsAutoString classname
;
1138 parentElement
->GetAttribute(NS_LITERAL_STRING("class"),classname
);
1139 if (classname
.Equals(NS_LITERAL_STRING("moz-signature")))
1140 *checkSpelling
= PR_FALSE
;
1143 nsCOMPtr
<nsIDOMNode
> nextParent
;
1144 parent
->GetParentNode(getter_AddRefs(nextParent
));
1145 parent
= nextParent
;
1149 // XXX Do we really want this for all read-write content?
1150 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aNode
);
1151 *checkSpelling
= !!(content
->IntrinsicState() & NS_EVENT_STATE_MOZ_READWRITE
);
1157 // mozInlineSpellChecker::ScheduleSpellCheck
1159 // This is called by code to do the actual spellchecking. We will set up
1160 // the proper structures for calls to DoSpellCheck.
1163 mozInlineSpellChecker::ScheduleSpellCheck(const mozInlineSpellStatus
& aStatus
)
1165 mozInlineSpellResume
* resume
= new mozInlineSpellResume(aStatus
);
1166 NS_ENSURE_TRUE(resume
, NS_ERROR_OUT_OF_MEMORY
);
1168 nsresult rv
= resume
->Post();
1174 // mozInlineSpellChecker::DoSpellCheckSelection
1176 // Called to re-check all misspelled words. We iterate over all ranges in
1177 // the selection and call DoSpellCheck on them. This is used when a word
1178 // is ignored or added to the dictionary: all instances of that word should
1179 // be removed from the selection.
1181 // FIXME-PERFORMANCE: This takes as long as it takes and is not resumable.
1182 // Typically, checking this small amount of text is relatively fast, but
1183 // for large numbers of words, a lag may be noticable.
1186 mozInlineSpellChecker::DoSpellCheckSelection(mozInlineSpellWordUtil
& aWordUtil
,
1187 nsISelection
* aSpellCheckSelection
,
1188 mozInlineSpellStatus
* aStatus
)
1192 // clear out mNumWordsInSpellSelection since we'll be rebuilding the ranges.
1193 mNumWordsInSpellSelection
= 0;
1195 // Since we could be modifying the ranges for the spellCheckSelection while
1196 // looping on the spell check selection, keep a separate array of range
1197 // elements inside the selection
1198 nsCOMArray
<nsIDOMRange
> ranges
;
1201 aSpellCheckSelection
->GetRangeCount(&count
);
1204 nsCOMPtr
<nsIDOMRange
> checkRange
;
1205 for (idx
= 0; idx
< count
; idx
++) {
1206 aSpellCheckSelection
->GetRangeAt(idx
, getter_AddRefs(checkRange
));
1208 if (! ranges
.AppendObject(checkRange
))
1209 return NS_ERROR_OUT_OF_MEMORY
;
1213 // We have saved the ranges above. Clearing the spellcheck selection here
1214 // isn't necessary (rechecking each word will modify it as necessary) but
1215 // provides better performance. By ensuring that no ranges need to be
1216 // removed in DoSpellCheck, we can save checking range inclusion which is
1218 aSpellCheckSelection
->RemoveAllRanges();
1220 // We use this state object for all calls, and just update its range. Note
1221 // that we don't need to call FinishInit since we will be filling in the
1222 // necessary information.
1223 mozInlineSpellStatus
status(this);
1224 rv
= status
.InitForRange(nsnull
);
1225 NS_ENSURE_SUCCESS(rv
, rv
);
1227 PRBool doneChecking
;
1228 for (idx
= 0; idx
< count
; idx
++) {
1229 checkRange
= ranges
[idx
];
1231 // We can consider this word as "added" since we know it has no spell
1232 // check range over it that needs to be deleted. All the old ranges
1233 // were cleared above. We also need to clear the word count so that we
1234 // check all words instead of stopping early.
1235 status
.mRange
= checkRange
;
1236 rv
= DoSpellCheck(aWordUtil
, aSpellCheckSelection
, &status
,
1238 NS_ENSURE_SUCCESS(rv
, rv
);
1239 NS_ASSERTION(doneChecking
, "We gave the spellchecker one word, but it didn't finish checking?!?!");
1241 status
.mWordCount
= 0;
1248 // mozInlineSpellChecker::DoSpellCheck
1250 // This function checks words intersecting the given range, excluding those
1251 // inside mStatus->mNoCheckRange (can be NULL). Words inside aNoCheckRange
1252 // will have any spell selection removed (this is used to hide the
1253 // underlining for the word that the caret is in). aNoCheckRange should be
1254 // on word boundaries.
1256 // mResume->mCreatedRange is a possibly NULL range of new text that was
1257 // inserted. Inside this range, we don't bother to check whether things are
1258 // inside the spellcheck selection, which speeds up large paste operations
1261 // Normal case when editing text by typing
1262 // h e l l o w o r k d h o w a r e y o u
1265 // [-------] mNoCheckRange
1266 // -> does nothing (range is the same as the no check range)
1268 // Case when pasting:
1269 // [---------- pasted text ----------]
1270 // h e l l o w o r k d h o w a r e y o u
1272 // [---] aNoCheckRange
1273 // -> recheck all words in range except those in aNoCheckRange
1275 // If checking is complete, *aDoneChecking will be set. If there is more
1276 // but we ran out of time, this will be false and the range will be
1277 // updated with the stuff that still needs checking.
1279 nsresult
mozInlineSpellChecker::DoSpellCheck(mozInlineSpellWordUtil
& aWordUtil
,
1280 nsISelection
*aSpellCheckSelection
,
1281 mozInlineSpellStatus
* aStatus
,
1282 PRBool
* aDoneChecking
)
1284 nsCOMPtr
<nsIDOMNode
> beginNode
, endNode
;
1285 PRInt32 beginOffset
, endOffset
;
1286 *aDoneChecking
= PR_TRUE
;
1288 // get the editor for SkipSpellCheckForNode, this may fail in reasonable
1289 // circumstances since the editor could have gone away
1290 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
1292 return NS_ERROR_FAILURE
;
1295 nsresult rv
= aStatus
->mRange
->GetCollapsed(&iscollapsed
);
1296 NS_ENSURE_SUCCESS(rv
, rv
);
1300 nsCOMPtr
<nsISelection2
> sel2
= do_QueryInterface(aSpellCheckSelection
, &rv
);
1301 NS_ENSURE_SUCCESS(rv
, rv
);
1303 // see if the selection has any ranges, if not, then we can optimize checking
1304 // range inclusion later (we have no ranges when we are initially checking or
1305 // when there are no misspelled words yet).
1306 PRInt32 originalRangeCount
;
1307 rv
= aSpellCheckSelection
->GetRangeCount(&originalRangeCount
);
1308 NS_ENSURE_SUCCESS(rv
, rv
);
1310 // set the starting DOM position to be the beginning of our range
1311 NS_ENSURE_SUCCESS(rv
, rv
);
1312 aStatus
->mRange
->GetStartContainer(getter_AddRefs(beginNode
));
1313 aStatus
->mRange
->GetStartOffset(&beginOffset
);
1314 aStatus
->mRange
->GetEndContainer(getter_AddRefs(endNode
));
1315 aStatus
->mRange
->GetEndOffset(&endOffset
);
1316 aWordUtil
.SetEnd(endNode
, endOffset
);
1317 aWordUtil
.SetPosition(beginNode
, beginOffset
);
1319 // aWordUtil.SetPosition flushes pending notifications, check editor again.
1320 editor
= do_QueryReferent(mEditor
);
1322 return NS_ERROR_FAILURE
;
1324 // we need to use IsPointInRange which is on a more specific interface
1325 nsCOMPtr
<nsIDOMNSRange
> noCheckRange
, createdRange
;
1326 if (aStatus
->mNoCheckRange
)
1327 noCheckRange
= do_QueryInterface(aStatus
->mNoCheckRange
);
1328 if (aStatus
->mCreatedRange
)
1329 createdRange
= do_QueryInterface(aStatus
->mCreatedRange
);
1331 PRInt32 wordsSinceTimeCheck
= 0;
1332 PRTime beginTime
= PR_Now();
1334 nsAutoString wordText
;
1335 nsCOMPtr
<nsIDOMRange
> wordRange
;
1336 PRBool dontCheckWord
;
1337 while (NS_SUCCEEDED(aWordUtil
.GetNextWord(wordText
,
1338 getter_AddRefs(wordRange
),
1341 wordsSinceTimeCheck
++;
1343 // get the range for the current word
1344 wordRange
->GetStartContainer(getter_AddRefs(beginNode
));
1345 wordRange
->GetEndContainer(getter_AddRefs(endNode
));
1346 wordRange
->GetStartOffset(&beginOffset
);
1347 wordRange
->GetEndOffset(&endOffset
);
1349 #ifdef DEBUG_INLINESPELL
1350 printf("->Got word \"%s\"", NS_ConvertUTF16toUTF8(wordText
).get());
1352 printf(" (not checking)");
1356 // see if there is a spellcheck range that already intersects the word
1357 // and remove it. We only need to remove old ranges, so don't bother if
1358 // there were no ranges when we started out.
1359 if (originalRangeCount
> 0) {
1360 // likewise, if this word is inside new text, we won't bother testing
1361 PRBool inCreatedRange
= PR_FALSE
;
1363 createdRange
->IsPointInRange(beginNode
, beginOffset
, &inCreatedRange
);
1364 if (! inCreatedRange
) {
1365 nsCOMArray
<nsIDOMRange
> ranges
;
1366 rv
= sel2
->GetRangesForIntervalCOMArray(beginNode
, beginOffset
,
1369 NS_ENSURE_SUCCESS(rv
, rv
);
1370 for (PRInt32 i
= 0; i
< ranges
.Count(); i
++)
1371 RemoveRange(aSpellCheckSelection
, ranges
[i
]);
1375 // some words are special and don't need checking
1379 // some nodes we don't spellcheck
1380 PRBool checkSpelling
;
1381 rv
= SkipSpellCheckForNode(editor
, beginNode
, &checkSpelling
);
1382 NS_ENSURE_SUCCESS(rv
, rv
);
1386 // Don't check spelling if we're inside the noCheckRange. This needs to
1387 // be done after we clear any old selection because the excluded word
1388 // might have been previously marked.
1390 // We do a simple check to see if the beginning of our word is in the
1391 // exclusion range. Because the exclusion range is a multiple of a word,
1392 // this is sufficient.
1394 PRBool inExclusion
= PR_FALSE
;
1395 noCheckRange
->IsPointInRange(beginNode
, beginOffset
, &inExclusion
);
1400 // check spelling and add to selection if misspelled
1401 PRBool isMisspelled
;
1402 aWordUtil
.NormalizeWord(wordText
);
1403 rv
= mSpellCheck
->CheckCurrentWordNoSuggest(wordText
.get(), &isMisspelled
);
1405 // misspelled words count extra toward the max
1406 wordsSinceTimeCheck
+= MISSPELLED_WORD_COUNT_PENALTY
;
1407 AddRange(aSpellCheckSelection
, wordRange
);
1409 aStatus
->mWordCount
++;
1410 if (aStatus
->mWordCount
>= mMaxMisspellingsPerCheck
||
1411 SpellCheckSelectionIsFull())
1415 // see if we've run out of time, only check every N words for perf
1416 if (wordsSinceTimeCheck
>= INLINESPELL_TIMEOUT_CHECK_FREQUENCY
) {
1417 wordsSinceTimeCheck
= 0;
1418 if (PR_Now() > PRTime(beginTime
+ INLINESPELL_CHECK_TIMEOUT
* PR_USEC_PER_MSEC
)) {
1419 // stop checking, our time limit has been exceeded
1421 // move the range to encompass the stuff that needs checking
1422 rv
= aStatus
->mRange
->SetStart(endNode
, endOffset
);
1423 if (NS_FAILED(rv
)) {
1424 // The range might be unhappy because the beginning is after the
1425 // end. This is possible when the requested end was in the middle
1426 // of a word, just ignore this situation and assume we're done.
1429 *aDoneChecking
= PR_FALSE
;
1438 // mozInlineSpellChecker::ResumeCheck
1440 // Called by the resume event when it fires. We will try to pick up where
1441 // the last resume left off.
1444 mozInlineSpellChecker::ResumeCheck(mozInlineSpellStatus
* aStatus
)
1447 return NS_OK
; // spell checking has been turned off
1449 nsCOMPtr
<nsIEditor
> editor
= do_QueryReferent(mEditor
);
1451 return NS_OK
; // editor is gone
1453 mozInlineSpellWordUtil wordUtil
;
1454 nsresult rv
= wordUtil
.Init(mEditor
);
1456 return NS_OK
; // editor doesn't like us, don't assert
1458 nsCOMPtr
<nsISelection
> spellCheckSelection
;
1459 rv
= GetSpellCheckSelection(getter_AddRefs(spellCheckSelection
));
1460 NS_ENSURE_SUCCESS(rv
, rv
);
1461 CleanupRangesInSelection(spellCheckSelection
);
1463 rv
= aStatus
->FinishInitOnEvent(wordUtil
);
1464 NS_ENSURE_SUCCESS(rv
, rv
);
1465 if (! aStatus
->mRange
)
1466 return NS_OK
; // empty range, nothing to do
1468 PRBool doneChecking
= PR_TRUE
;
1469 if (aStatus
->mOp
== mozInlineSpellStatus::eOpSelection
)
1470 rv
= DoSpellCheckSelection(wordUtil
, spellCheckSelection
, aStatus
);
1472 rv
= DoSpellCheck(wordUtil
, spellCheckSelection
, aStatus
, &doneChecking
);
1473 NS_ENSURE_SUCCESS(rv
, rv
);
1476 rv
= ScheduleSpellCheck(*aStatus
);
1480 // mozInlineSpellChecker::IsPointInSelection
1482 // Determines if a given (node,offset) point is inside the given
1483 // selection. If so, the specific range of the selection that
1484 // intersects is places in *aRange. (There may be multiple disjoint
1485 // ranges in a selection.)
1487 // If there is no intersection, *aRange will be NULL.
1490 mozInlineSpellChecker::IsPointInSelection(nsISelection
*aSelection
,
1493 nsIDOMRange
**aRange
)
1498 nsCOMPtr
<nsISelection2
> sel2
= do_QueryInterface(aSelection
, &rv
);
1499 NS_ENSURE_SUCCESS(rv
, rv
);
1501 nsCOMArray
<nsIDOMRange
> ranges
;
1502 rv
= sel2
->GetRangesForIntervalCOMArray(aNode
, aOffset
, aNode
, aOffset
,
1504 NS_ENSURE_SUCCESS(rv
, rv
);
1506 if (ranges
.Count() == 0)
1507 return NS_OK
; // no matches
1509 // there may be more than one range returned, and we don't know what do
1510 // do with that, so just get the first one
1511 NS_ADDREF(*aRange
= ranges
[0]);
1516 mozInlineSpellChecker::CleanupRangesInSelection(nsISelection
*aSelection
)
1518 // integrity check - remove ranges that have collapsed to nothing. This
1519 // can happen if the node containing a highlighted word was removed.
1520 NS_ENSURE_ARG_POINTER(aSelection
);
1523 aSelection
->GetRangeCount(&count
);
1525 for (PRInt32 index
= 0; index
< count
; index
++)
1527 nsCOMPtr
<nsIDOMRange
> checkRange
;
1528 aSelection
->GetRangeAt(index
, getter_AddRefs(checkRange
));
1533 checkRange
->GetCollapsed(&collapsed
);
1536 RemoveRange(aSelection
, checkRange
);
1547 // mozInlineSpellChecker::RemoveRange
1549 // For performance reasons, we have an upper bound on the number of word
1550 // ranges in the spell check selection. When removing a range from the
1551 // selection, we need to decrement mNumWordsInSpellSelection
1554 mozInlineSpellChecker::RemoveRange(nsISelection
* aSpellCheckSelection
,
1555 nsIDOMRange
* aRange
)
1557 NS_ENSURE_ARG_POINTER(aSpellCheckSelection
);
1558 NS_ENSURE_ARG_POINTER(aRange
);
1560 nsresult rv
= aSpellCheckSelection
->RemoveRange(aRange
);
1561 if (NS_SUCCEEDED(rv
) && mNumWordsInSpellSelection
)
1562 mNumWordsInSpellSelection
--;
1568 // mozInlineSpellChecker::AddRange
1570 // For performance reasons, we have an upper bound on the number of word
1571 // ranges we'll add to the spell check selection. Once we reach that upper
1572 // bound, stop adding the ranges
1575 mozInlineSpellChecker::AddRange(nsISelection
* aSpellCheckSelection
,
1576 nsIDOMRange
* aRange
)
1578 NS_ENSURE_ARG_POINTER(aSpellCheckSelection
);
1579 NS_ENSURE_ARG_POINTER(aRange
);
1581 nsresult rv
= NS_OK
;
1583 if (!SpellCheckSelectionIsFull())
1585 rv
= aSpellCheckSelection
->AddRange(aRange
);
1586 if (NS_SUCCEEDED(rv
))
1587 mNumWordsInSpellSelection
++;
1593 nsresult
mozInlineSpellChecker::GetSpellCheckSelection(nsISelection
** aSpellCheckSelection
)
1595 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
1596 NS_ENSURE_TRUE(editor
, NS_ERROR_NULL_POINTER
);
1598 nsCOMPtr
<nsISelectionController
> selcon
;
1599 nsresult rv
= editor
->GetSelectionController(getter_AddRefs(selcon
));
1600 NS_ENSURE_SUCCESS(rv
, rv
);
1602 nsCOMPtr
<nsISelection
> spellCheckSelection
;
1603 return selcon
->GetSelection(nsISelectionController::SELECTION_SPELLCHECK
, aSpellCheckSelection
);
1606 nsresult
mozInlineSpellChecker::SaveCurrentSelectionPosition()
1608 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
1609 NS_ENSURE_TRUE(editor
, NS_OK
);
1611 // figure out the old caret position based on the current selection
1612 nsCOMPtr
<nsISelection
> selection
;
1613 nsresult rv
= editor
->GetSelection(getter_AddRefs(selection
));
1614 NS_ENSURE_SUCCESS(rv
, rv
);
1616 rv
= selection
->GetFocusNode(getter_AddRefs(mCurrentSelectionAnchorNode
));
1617 NS_ENSURE_SUCCESS(rv
, rv
);
1619 selection
->GetFocusOffset(&mCurrentSelectionOffset
);
1624 // This is a copy of nsContentUtils::ContentIsDescendantOf. Another crime
1625 // for XPCOM's rap sheet
1627 ContentIsDescendantOf(nsINode
* aPossibleDescendant
,
1628 nsINode
* aPossibleAncestor
)
1630 NS_PRECONDITION(aPossibleDescendant
, "The possible descendant is null!");
1631 NS_PRECONDITION(aPossibleAncestor
, "The possible ancestor is null!");
1634 if (aPossibleDescendant
== aPossibleAncestor
)
1636 aPossibleDescendant
= aPossibleDescendant
->GetNodeParent();
1637 } while (aPossibleDescendant
);
1642 // mozInlineSpellChecker::HandleNavigationEvent
1644 // Acts upon mouse clicks and keyboard navigation changes, spell checking
1645 // the previous word if the new navigation location moves us to another
1648 // This is complicated by the fact that our mouse events are happening after
1649 // selection has been changed to account for the mouse click. But keyboard
1650 // events are happening before the caret selection has changed. Working
1651 // around this by letting keyboard events setting forceWordSpellCheck to
1652 // true. aNewPositionOffset also tries to work around this for the
1653 // DOM_VK_RIGHT and DOM_VK_LEFT cases.
1656 mozInlineSpellChecker::HandleNavigationEvent(nsIDOMEvent
* aEvent
,
1657 PRBool aForceWordSpellCheck
,
1658 PRInt32 aNewPositionOffset
)
1662 // If we already handled the navigation event and there is no possibility
1663 // anything has changed since then, we don't have to do anything. This
1664 // optimization makes a noticable difference when you hold down a navigation
1665 // key like Page Down.
1666 if (! mNeedsCheckAfterNavigation
)
1669 nsCOMPtr
<nsIDOMNode
> currentAnchorNode
= mCurrentSelectionAnchorNode
;
1670 PRInt32 currentAnchorOffset
= mCurrentSelectionOffset
;
1672 // now remember the new focus position resulting from the event
1673 rv
= SaveCurrentSelectionPosition();
1674 NS_ENSURE_SUCCESS(rv
, rv
);
1677 mozInlineSpellStatus
status(this);
1678 rv
= status
.InitForNavigation(aForceWordSpellCheck
, aNewPositionOffset
,
1679 currentAnchorNode
, currentAnchorOffset
,
1680 mCurrentSelectionAnchorNode
, mCurrentSelectionOffset
,
1682 NS_ENSURE_SUCCESS(rv
, rv
);
1684 rv
= ScheduleSpellCheck(status
);
1685 NS_ENSURE_SUCCESS(rv
, rv
);
1691 NS_IMETHODIMP
mozInlineSpellChecker::HandleEvent(nsIDOMEvent
* aEvent
)
1696 NS_IMETHODIMP
mozInlineSpellChecker::Focus(nsIDOMEvent
* aEvent
)
1701 NS_IMETHODIMP
mozInlineSpellChecker::Blur(nsIDOMEvent
* aEvent
)
1703 // force spellcheck on blur, for instance when tabbing out of a textbox
1704 HandleNavigationEvent(aEvent
, PR_TRUE
);
1708 NS_IMETHODIMP
mozInlineSpellChecker::MouseClick(nsIDOMEvent
*aMouseEvent
)
1710 nsCOMPtr
<nsIDOMMouseEvent
>mouseEvent
= do_QueryInterface(aMouseEvent
);
1711 NS_ENSURE_TRUE(mouseEvent
, NS_OK
);
1713 // ignore any errors from HandleNavigationEvent as we don't want to prevent
1714 // anyone else from seeing this event.
1716 mouseEvent
->GetButton(&button
);
1718 HandleNavigationEvent(mouseEvent
, PR_FALSE
);
1720 HandleNavigationEvent(mouseEvent
, PR_TRUE
);
1724 NS_IMETHODIMP
mozInlineSpellChecker::MouseDown(nsIDOMEvent
* aMouseEvent
)
1729 NS_IMETHODIMP
mozInlineSpellChecker::MouseUp(nsIDOMEvent
* aMouseEvent
)
1734 NS_IMETHODIMP
mozInlineSpellChecker::MouseDblClick(nsIDOMEvent
* aMouseEvent
)
1739 NS_IMETHODIMP
mozInlineSpellChecker::MouseOver(nsIDOMEvent
* aMouseEvent
)
1744 NS_IMETHODIMP
mozInlineSpellChecker::MouseOut(nsIDOMEvent
* aMouseEvent
)
1749 NS_IMETHODIMP
mozInlineSpellChecker::KeyDown(nsIDOMEvent
* aKeyEvent
)
1755 NS_IMETHODIMP
mozInlineSpellChecker::KeyUp(nsIDOMEvent
* aKeyEvent
)
1761 NS_IMETHODIMP
mozInlineSpellChecker::KeyPress(nsIDOMEvent
* aKeyEvent
)
1763 nsCOMPtr
<nsIDOMKeyEvent
>keyEvent
= do_QueryInterface(aKeyEvent
);
1764 NS_ENSURE_TRUE(keyEvent
, NS_OK
);
1767 keyEvent
->GetKeyCode(&keyCode
);
1769 // we only care about navigation keys that moved selection
1772 case nsIDOMKeyEvent::DOM_VK_RIGHT
:
1773 case nsIDOMKeyEvent::DOM_VK_LEFT
:
1774 HandleNavigationEvent(aKeyEvent
, PR_FALSE
, keyCode
== nsIDOMKeyEvent::DOM_VK_RIGHT
? 1 : -1);
1776 case nsIDOMKeyEvent::DOM_VK_UP
:
1777 case nsIDOMKeyEvent::DOM_VK_DOWN
:
1778 case nsIDOMKeyEvent::DOM_VK_HOME
:
1779 case nsIDOMKeyEvent::DOM_VK_END
:
1780 case nsIDOMKeyEvent::DOM_VK_PAGE_UP
:
1781 case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN
:
1782 HandleNavigationEvent(aKeyEvent
, PR_TRUE
/* force a spelling correction */);