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()
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 spellCheckSelection
->RemoveAllRanges();
585 rv
= UnregisterEventListeners();
592 // mozInlineSpellChecker::CanEnableInlineSpellChecking
594 // This function can be called to see if it seems likely that we can enable
595 // spellchecking before actually creating the InlineSpellChecking objects.
597 // The problem is that we can't get the dictionary list without actually
598 // creating a whole bunch of spellchecking objects. This function tries to
599 // do that and caches the result so we don't have to keep allocating those
600 // objects if there are no dictionaries or spellchecking.
602 // This caching will prevent adding dictionaries at runtime if we start out
603 // with no dictionaries! Installing dictionaries as extensions will require
604 // a restart anyway, so it shouldn't be a problem.
607 mozInlineSpellChecker::CanEnableInlineSpellChecking()
610 if (gCanEnableSpellChecking
== SpellCheck_Uninitialized
) {
611 gCanEnableSpellChecking
= SpellCheck_NotAvailable
;
613 nsCOMPtr
<nsIEditorSpellCheck
> spellchecker
=
614 do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &rv
);
615 NS_ENSURE_SUCCESS(rv
, PR_FALSE
);
617 PRBool canSpellCheck
= PR_FALSE
;
618 rv
= spellchecker
->CanSpellCheck(&canSpellCheck
);
619 NS_ENSURE_SUCCESS(rv
, PR_FALSE
);
622 gCanEnableSpellChecking
= SpellCheck_Available
;
624 return (gCanEnableSpellChecking
== SpellCheck_Available
);
627 // mozInlineSpellChecker::RegisterEventListeners
629 // The inline spell checker listens to mouse events and keyboard navigation+ // events.
632 mozInlineSpellChecker::RegisterEventListeners()
634 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
635 NS_ENSURE_TRUE(editor
, NS_ERROR_NULL_POINTER
);
637 editor
->AddEditActionListener(this);
639 nsCOMPtr
<nsIDOMDocument
> doc
;
640 nsresult rv
= editor
->GetDocument(getter_AddRefs(doc
));
641 NS_ENSURE_SUCCESS(rv
, rv
);
643 nsCOMPtr
<nsPIDOMEventTarget
> piTarget
= do_QueryInterface(doc
, &rv
);
644 NS_ENSURE_SUCCESS(rv
, rv
);
646 nsCOMPtr
<nsIEventListenerManager
> elmP
;
647 piTarget
->GetListenerManager(PR_TRUE
, getter_AddRefs(elmP
));
649 // Focus event doesn't bubble so adding the listener to capturing phase
650 elmP
->AddEventListenerByIID(static_cast<nsIDOMFocusListener
*>(this),
651 NS_GET_IID(nsIDOMFocusListener
),
652 NS_EVENT_FLAG_CAPTURE
);
655 piTarget
->AddEventListenerByIID(static_cast<nsIDOMMouseListener
*>(this),
656 NS_GET_IID(nsIDOMMouseListener
));
657 piTarget
->AddEventListenerByIID(static_cast<nsIDOMKeyListener
*>(this),
658 NS_GET_IID(nsIDOMKeyListener
));
663 // mozInlineSpellChecker::UnregisterEventListeners
666 mozInlineSpellChecker::UnregisterEventListeners()
668 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
669 NS_ENSURE_TRUE(editor
, NS_ERROR_NULL_POINTER
);
671 editor
->RemoveEditActionListener(this);
673 nsCOMPtr
<nsIDOMDocument
> doc
;
674 editor
->GetDocument(getter_AddRefs(doc
));
675 NS_ENSURE_TRUE(doc
, NS_ERROR_NULL_POINTER
);
677 nsCOMPtr
<nsPIDOMEventTarget
> piTarget
= do_QueryInterface(doc
);
678 NS_ENSURE_TRUE(piTarget
, NS_ERROR_NULL_POINTER
);
680 nsCOMPtr
<nsIEventListenerManager
> elmP
;
681 piTarget
->GetListenerManager(PR_TRUE
, getter_AddRefs(elmP
));
683 elmP
->RemoveEventListenerByIID(static_cast<nsIDOMFocusListener
*>(this),
684 NS_GET_IID(nsIDOMFocusListener
),
685 NS_EVENT_FLAG_CAPTURE
);
688 piTarget
->RemoveEventListenerByIID(static_cast<nsIDOMMouseListener
*>(this),
689 NS_GET_IID(nsIDOMMouseListener
));
690 piTarget
->RemoveEventListenerByIID(static_cast<nsIDOMKeyListener
*>(this),
691 NS_GET_IID(nsIDOMKeyListener
));
696 // mozInlineSpellChecker::GetEnableRealTimeSpell
699 mozInlineSpellChecker::GetEnableRealTimeSpell(PRBool
* aEnabled
)
701 NS_ENSURE_ARG_POINTER(aEnabled
);
702 *aEnabled
= mSpellCheck
!= nsnull
;
706 // mozInlineSpellChecker::SetEnableRealTimeSpell
709 mozInlineSpellChecker::SetEnableRealTimeSpell(PRBool aEnabled
)
712 mSpellCheck
= nsnull
;
717 nsresult res
= NS_OK
;
718 nsCOMPtr
<nsIEditorSpellCheck
> spellchecker
= do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &res
);
719 if (NS_SUCCEEDED(res
) && spellchecker
)
721 nsCOMPtr
<nsITextServicesFilter
> filter
= do_CreateInstance("@mozilla.org/editor/txtsrvfiltermail;1", &res
);
722 spellchecker
->SetFilter(filter
);
723 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
724 res
= spellchecker
->InitSpellChecker(editor
, PR_FALSE
);
725 NS_ENSURE_SUCCESS(res
, res
);
727 nsCOMPtr
<nsITextServicesDocument
> tsDoc
= do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &res
);
728 NS_ENSURE_SUCCESS(res
, res
);
730 res
= tsDoc
->SetFilter(filter
);
731 NS_ENSURE_SUCCESS(res
, res
);
733 res
= tsDoc
->InitWithEditor(editor
);
734 NS_ENSURE_SUCCESS(res
, res
);
736 mTextServicesDocument
= tsDoc
;
737 mSpellCheck
= spellchecker
;
739 // spell checking is enabled, register our event listeners to track navigation
740 RegisterEventListeners();
744 // spellcheck the current contents. SpellCheckRange doesn't supply a created
745 // range to DoSpellCheck, which in our case is the entire range. But this
746 // optimization doesn't matter because there is nothing in the spellcheck
747 // selection when starting, which triggers a better optimization.
748 return SpellCheckRange(nsnull
);
751 // mozInlineSpellChecker::SpellCheckAfterEditorChange
753 // Called by the editor when nearly anything happens to change the content.
755 // The start and end positions specify a range for the thing that happened,
756 // but these are usually NULL, even when you'd think they would be useful
757 // because you want the range (for example, pasting). We ignore them in
761 mozInlineSpellChecker::SpellCheckAfterEditorChange(
762 PRInt32 aAction
, nsISelection
*aSelection
,
763 nsIDOMNode
*aPreviousSelectedNode
, PRInt32 aPreviousSelectedOffset
,
764 nsIDOMNode
*aStartNode
, PRInt32 aStartOffset
,
765 nsIDOMNode
*aEndNode
, PRInt32 aEndOffset
)
768 NS_ENSURE_ARG_POINTER(aSelection
);
770 return NS_OK
; // disabling spell checking is not an error
772 // this means something has changed, and we never check the current word,
773 // therefore, we should spellcheck for subsequent caret navigations
774 mNeedsCheckAfterNavigation
= PR_TRUE
;
776 // the anchor node is the position of the caret
777 nsCOMPtr
<nsIDOMNode
> anchorNode
;
778 rv
= aSelection
->GetAnchorNode(getter_AddRefs(anchorNode
));
779 NS_ENSURE_SUCCESS(rv
, rv
);
780 PRInt32 anchorOffset
;
781 rv
= aSelection
->GetAnchorOffset(&anchorOffset
);
782 NS_ENSURE_SUCCESS(rv
, rv
);
784 mozInlineSpellStatus
status(this);
785 rv
= status
.InitForEditorChange(aAction
,
786 anchorNode
, anchorOffset
,
787 aPreviousSelectedNode
, aPreviousSelectedOffset
,
788 aStartNode
, aStartOffset
,
789 aEndNode
, aEndOffset
);
790 NS_ENSURE_SUCCESS(rv
, rv
);
791 rv
= ScheduleSpellCheck(status
);
792 NS_ENSURE_SUCCESS(rv
, rv
);
794 // remember the current caret position after every change
795 SaveCurrentSelectionPosition();
799 // mozInlineSpellChecker::SpellCheckRange
801 // Spellchecks all the words in the given range.
802 // Supply a NULL range and this will check the entire editor.
805 mozInlineSpellChecker::SpellCheckRange(nsIDOMRange
* aRange
)
807 NS_ENSURE_TRUE(mSpellCheck
, NS_ERROR_NOT_INITIALIZED
);
809 mozInlineSpellStatus
status(this);
810 nsresult rv
= status
.InitForRange(aRange
);
811 NS_ENSURE_SUCCESS(rv
, rv
);
812 return ScheduleSpellCheck(status
);
815 // mozInlineSpellChecker::GetMispelledWord
818 mozInlineSpellChecker::GetMispelledWord(nsIDOMNode
*aNode
, PRInt32 aOffset
,
819 nsIDOMRange
**newword
)
821 NS_ENSURE_ARG_POINTER(aNode
);
822 nsCOMPtr
<nsISelection
> spellCheckSelection
;
823 nsresult res
= GetSpellCheckSelection(getter_AddRefs(spellCheckSelection
));
824 NS_ENSURE_SUCCESS(res
, res
);
826 return IsPointInSelection(spellCheckSelection
, aNode
, aOffset
, newword
);
829 // mozInlineSpellChecker::ReplaceWord
832 mozInlineSpellChecker::ReplaceWord(nsIDOMNode
*aNode
, PRInt32 aOffset
,
833 const nsAString
&newword
)
835 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
836 NS_ENSURE_TRUE(editor
, NS_ERROR_NULL_POINTER
);
837 NS_ENSURE_TRUE(newword
.Length() != 0, NS_ERROR_FAILURE
);
839 nsCOMPtr
<nsIDOMRange
> range
;
840 nsresult res
= GetMispelledWord(aNode
, aOffset
, getter_AddRefs(range
));
841 NS_ENSURE_SUCCESS(res
, res
);
845 editor
->BeginTransaction();
847 nsCOMPtr
<nsISelection
> selection
;
848 res
= editor
->GetSelection(getter_AddRefs(selection
));
849 NS_ENSURE_SUCCESS(res
, res
);
850 selection
->RemoveAllRanges();
851 selection
->AddRange(range
);
852 editor
->DeleteSelection(nsIEditor::eNone
);
854 nsCOMPtr
<nsIPlaintextEditor
> textEditor(do_QueryReferent(mEditor
));
855 textEditor
->InsertText(newword
);
857 editor
->EndTransaction();
863 // mozInlineSpellChecker::AddWordToDictionary
866 mozInlineSpellChecker::AddWordToDictionary(const nsAString
&word
)
868 NS_ENSURE_TRUE(mSpellCheck
, NS_ERROR_NOT_INITIALIZED
);
870 nsAutoString
wordstr(word
);
871 nsresult rv
= mSpellCheck
->AddWordToDictionary(wordstr
.get());
872 NS_ENSURE_SUCCESS(rv
, rv
);
874 mozInlineSpellStatus
status(this);
875 rv
= status
.InitForSelection();
876 NS_ENSURE_SUCCESS(rv
, rv
);
877 return ScheduleSpellCheck(status
);
880 // mozInlineSpellChecker::IgnoreWord
883 mozInlineSpellChecker::IgnoreWord(const nsAString
&word
)
885 NS_ENSURE_TRUE(mSpellCheck
, NS_ERROR_NOT_INITIALIZED
);
887 nsAutoString
wordstr(word
);
888 nsresult rv
= mSpellCheck
->IgnoreWordAllOccurrences(wordstr
.get());
889 NS_ENSURE_SUCCESS(rv
, rv
);
891 mozInlineSpellStatus
status(this);
892 rv
= status
.InitForSelection();
893 NS_ENSURE_SUCCESS(rv
, rv
);
894 return ScheduleSpellCheck(status
);
897 // mozInlineSpellChecker::IgnoreWords
900 mozInlineSpellChecker::IgnoreWords(const PRUnichar
**aWordsToIgnore
,
903 // add each word to the ignore list and then recheck the document
904 for (PRUint32 index
= 0; index
< aCount
; index
++)
905 mSpellCheck
->IgnoreWordAllOccurrences(aWordsToIgnore
[index
]);
907 mozInlineSpellStatus
status(this);
908 nsresult rv
= status
.InitForSelection();
909 NS_ENSURE_SUCCESS(rv
, rv
);
910 return ScheduleSpellCheck(status
);
913 NS_IMETHODIMP
mozInlineSpellChecker::WillCreateNode(const nsAString
& aTag
, nsIDOMNode
*aParent
, PRInt32 aPosition
)
918 NS_IMETHODIMP
mozInlineSpellChecker::DidCreateNode(const nsAString
& aTag
, nsIDOMNode
*aNode
, nsIDOMNode
*aParent
,
919 PRInt32 aPosition
, nsresult aResult
)
924 NS_IMETHODIMP
mozInlineSpellChecker::WillInsertNode(nsIDOMNode
*aNode
, nsIDOMNode
*aParent
,
930 NS_IMETHODIMP
mozInlineSpellChecker::DidInsertNode(nsIDOMNode
*aNode
, nsIDOMNode
*aParent
,
931 PRInt32 aPosition
, nsresult aResult
)
937 NS_IMETHODIMP
mozInlineSpellChecker::WillDeleteNode(nsIDOMNode
*aChild
)
942 NS_IMETHODIMP
mozInlineSpellChecker::DidDeleteNode(nsIDOMNode
*aChild
, nsresult aResult
)
947 NS_IMETHODIMP
mozInlineSpellChecker::WillSplitNode(nsIDOMNode
*aExistingRightNode
, PRInt32 aOffset
)
953 mozInlineSpellChecker::DidSplitNode(nsIDOMNode
*aExistingRightNode
,
955 nsIDOMNode
*aNewLeftNode
, nsresult aResult
)
957 return SpellCheckBetweenNodes(aNewLeftNode
, 0, aNewLeftNode
, 0);
960 NS_IMETHODIMP
mozInlineSpellChecker::WillJoinNodes(nsIDOMNode
*aLeftNode
, nsIDOMNode
*aRightNode
, nsIDOMNode
*aParent
)
965 NS_IMETHODIMP
mozInlineSpellChecker::DidJoinNodes(nsIDOMNode
*aLeftNode
, nsIDOMNode
*aRightNode
,
966 nsIDOMNode
*aParent
, nsresult aResult
)
968 return SpellCheckBetweenNodes(aRightNode
, 0, aRightNode
, 0);
971 NS_IMETHODIMP
mozInlineSpellChecker::WillInsertText(nsIDOMCharacterData
*aTextNode
, PRInt32 aOffset
, const nsAString
& aString
)
976 NS_IMETHODIMP
mozInlineSpellChecker::DidInsertText(nsIDOMCharacterData
*aTextNode
, PRInt32 aOffset
,
977 const nsAString
& aString
, nsresult aResult
)
982 NS_IMETHODIMP
mozInlineSpellChecker::WillDeleteText(nsIDOMCharacterData
*aTextNode
, PRInt32 aOffset
, PRInt32 aLength
)
987 NS_IMETHODIMP
mozInlineSpellChecker::DidDeleteText(nsIDOMCharacterData
*aTextNode
, PRInt32 aOffset
, PRInt32 aLength
, nsresult aResult
)
992 NS_IMETHODIMP
mozInlineSpellChecker::WillDeleteSelection(nsISelection
*aSelection
)
997 NS_IMETHODIMP
mozInlineSpellChecker::DidDeleteSelection(nsISelection
*aSelection
)
1002 // mozInlineSpellChecker::MakeSpellCheckRange
1004 // Given begin and end positions, this function constructs a range as
1005 // required for ScheduleSpellCheck. If the start and end nodes are NULL,
1006 // then the entire range will be selected, and you can supply -1 as the
1007 // offset to the end range to select all of that node.
1009 // If the resulting range would be empty, NULL is put into *aRange and the
1010 // function succeeds.
1013 mozInlineSpellChecker::MakeSpellCheckRange(
1014 nsIDOMNode
* aStartNode
, PRInt32 aStartOffset
,
1015 nsIDOMNode
* aEndNode
, PRInt32 aEndOffset
,
1016 nsIDOMRange
** aRange
)
1021 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
1022 NS_ENSURE_TRUE(editor
, NS_ERROR_NULL_POINTER
);
1024 nsCOMPtr
<nsIDOMDocument
> doc
;
1025 rv
= editor
->GetDocument(getter_AddRefs(doc
));
1026 NS_ENSURE_SUCCESS(rv
, rv
);
1028 nsCOMPtr
<nsIDOMDocumentRange
> docrange
= do_QueryInterface(doc
);
1029 NS_ENSURE_TRUE(docrange
, NS_ERROR_FAILURE
);
1031 nsCOMPtr
<nsIDOMRange
> range
;
1032 rv
= docrange
->CreateRange(getter_AddRefs(range
));
1033 NS_ENSURE_SUCCESS(rv
, rv
);
1035 // possibly use full range of the editor
1036 nsCOMPtr
<nsIDOMElement
> rootElem
;
1037 if (! aStartNode
|| ! aEndNode
) {
1038 rv
= editor
->GetRootElement(getter_AddRefs(rootElem
));
1039 NS_ENSURE_SUCCESS(rv
, rv
);
1041 aStartNode
= rootElem
;
1044 aEndNode
= rootElem
;
1048 if (aEndOffset
== -1) {
1049 nsCOMPtr
<nsIDOMNodeList
> childNodes
;
1050 rv
= aEndNode
->GetChildNodes(getter_AddRefs(childNodes
));
1051 NS_ENSURE_SUCCESS(rv
, rv
);
1053 PRUint32 childCount
;
1054 rv
= childNodes
->GetLength(&childCount
);
1055 NS_ENSURE_SUCCESS(rv
, rv
);
1057 aEndOffset
= childCount
;
1060 // sometimes we are are requested to check an empty range (possibly an empty
1061 // document). This will result in assertions later.
1062 if (aStartNode
== aEndNode
&& aStartOffset
== aEndOffset
)
1065 rv
= range
->SetStart(aStartNode
, aStartOffset
);
1066 NS_ENSURE_SUCCESS(rv
, rv
);
1068 rv
= range
->SetEnd(aEndNode
, aEndOffset
);
1070 rv
= range
->SetEndAfter(aEndNode
);
1071 NS_ENSURE_SUCCESS(rv
, rv
);
1073 range
.swap(*aRange
);
1078 mozInlineSpellChecker::SpellCheckBetweenNodes(nsIDOMNode
*aStartNode
,
1079 PRInt32 aStartOffset
,
1080 nsIDOMNode
*aEndNode
,
1083 nsCOMPtr
<nsIDOMRange
> range
;
1084 nsresult rv
= MakeSpellCheckRange(aStartNode
, aStartOffset
,
1085 aEndNode
, aEndOffset
,
1086 getter_AddRefs(range
));
1087 NS_ENSURE_SUCCESS(rv
, rv
);
1090 return NS_OK
; // range is empty: nothing to do
1092 mozInlineSpellStatus
status(this);
1093 rv
= status
.InitForRange(range
);
1094 NS_ENSURE_SUCCESS(rv
, rv
);
1095 return ScheduleSpellCheck(status
);
1098 // mozInlineSpellChecker::SkipSpellCheckForNode
1100 // There are certain conditions when we don't want to spell check a node. In
1101 // particular quotations, moz signatures, etc. This routine returns false
1105 mozInlineSpellChecker::SkipSpellCheckForNode(nsIEditor
* aEditor
,
1107 PRBool
*checkSpelling
)
1109 *checkSpelling
= PR_TRUE
;
1110 NS_ENSURE_ARG_POINTER(aNode
);
1113 aEditor
->GetFlags(&flags
);
1114 if (flags
& nsIPlaintextEditor::eEditorMailMask
)
1116 nsCOMPtr
<nsIDOMNode
> parent
;
1117 aNode
->GetParentNode(getter_AddRefs(parent
));
1121 nsCOMPtr
<nsIDOMElement
> parentElement
= do_QueryInterface(parent
);
1125 nsAutoString parentTagName
;
1126 parentElement
->GetTagName(parentTagName
);
1128 if (parentTagName
.Equals(NS_LITERAL_STRING("blockquote"), nsCaseInsensitiveStringComparator()))
1130 *checkSpelling
= PR_FALSE
;
1133 else if (parentTagName
.Equals(NS_LITERAL_STRING("pre"), nsCaseInsensitiveStringComparator()))
1135 nsAutoString classname
;
1136 parentElement
->GetAttribute(NS_LITERAL_STRING("class"),classname
);
1137 if (classname
.Equals(NS_LITERAL_STRING("moz-signature")))
1138 *checkSpelling
= PR_FALSE
;
1141 nsCOMPtr
<nsIDOMNode
> nextParent
;
1142 parent
->GetParentNode(getter_AddRefs(nextParent
));
1143 parent
= nextParent
;
1147 // XXX Do we really want this for all read-write content?
1148 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aNode
);
1149 *checkSpelling
= !!(content
->IntrinsicState() & NS_EVENT_STATE_MOZ_READWRITE
);
1155 // mozInlineSpellChecker::ScheduleSpellCheck
1157 // This is called by code to do the actual spellchecking. We will set up
1158 // the proper structures for calls to DoSpellCheck.
1161 mozInlineSpellChecker::ScheduleSpellCheck(const mozInlineSpellStatus
& aStatus
)
1163 mozInlineSpellResume
* resume
= new mozInlineSpellResume(aStatus
);
1164 NS_ENSURE_TRUE(resume
, NS_ERROR_OUT_OF_MEMORY
);
1166 nsresult rv
= resume
->Post();
1172 // mozInlineSpellChecker::DoSpellCheckSelection
1174 // Called to re-check all misspelled words. We iterate over all ranges in
1175 // the selection and call DoSpellCheck on them. This is used when a word
1176 // is ignored or added to the dictionary: all instances of that word should
1177 // be removed from the selection.
1179 // FIXME-PERFORMANCE: This takes as long as it takes and is not resumable.
1180 // Typically, checking this small amount of text is relatively fast, but
1181 // for large numbers of words, a lag may be noticable.
1184 mozInlineSpellChecker::DoSpellCheckSelection(mozInlineSpellWordUtil
& aWordUtil
,
1185 nsISelection
* aSpellCheckSelection
,
1186 mozInlineSpellStatus
* aStatus
)
1190 // clear out mNumWordsInSpellSelection since we'll be rebuilding the ranges.
1191 mNumWordsInSpellSelection
= 0;
1193 // Since we could be modifying the ranges for the spellCheckSelection while
1194 // looping on the spell check selection, keep a separate array of range
1195 // elements inside the selection
1196 nsCOMArray
<nsIDOMRange
> ranges
;
1199 aSpellCheckSelection
->GetRangeCount(&count
);
1202 nsCOMPtr
<nsIDOMRange
> checkRange
;
1203 for (idx
= 0; idx
< count
; idx
++) {
1204 aSpellCheckSelection
->GetRangeAt(idx
, getter_AddRefs(checkRange
));
1206 if (! ranges
.AppendObject(checkRange
))
1207 return NS_ERROR_OUT_OF_MEMORY
;
1211 // We have saved the ranges above. Clearing the spellcheck selection here
1212 // isn't necessary (rechecking each word will modify it as necessary) but
1213 // provides better performance. By ensuring that no ranges need to be
1214 // removed in DoSpellCheck, we can save checking range inclusion which is
1216 aSpellCheckSelection
->RemoveAllRanges();
1218 // We use this state object for all calls, and just update its range. Note
1219 // that we don't need to call FinishInit since we will be filling in the
1220 // necessary information.
1221 mozInlineSpellStatus
status(this);
1222 rv
= status
.InitForRange(nsnull
);
1223 NS_ENSURE_SUCCESS(rv
, rv
);
1225 PRBool doneChecking
;
1226 for (idx
= 0; idx
< count
; idx
++) {
1227 checkRange
= ranges
[idx
];
1229 // We can consider this word as "added" since we know it has no spell
1230 // check range over it that needs to be deleted. All the old ranges
1231 // were cleared above. We also need to clear the word count so that we
1232 // check all words instead of stopping early.
1233 status
.mRange
= checkRange
;
1234 rv
= DoSpellCheck(aWordUtil
, aSpellCheckSelection
, &status
,
1236 NS_ENSURE_SUCCESS(rv
, rv
);
1237 NS_ASSERTION(doneChecking
, "We gave the spellchecker one word, but it didn't finish checking?!?!");
1239 status
.mWordCount
= 0;
1246 // mozInlineSpellChecker::DoSpellCheck
1248 // This function checks words intersecting the given range, excluding those
1249 // inside mStatus->mNoCheckRange (can be NULL). Words inside aNoCheckRange
1250 // will have any spell selection removed (this is used to hide the
1251 // underlining for the word that the caret is in). aNoCheckRange should be
1252 // on word boundaries.
1254 // mResume->mCreatedRange is a possibly NULL range of new text that was
1255 // inserted. Inside this range, we don't bother to check whether things are
1256 // inside the spellcheck selection, which speeds up large paste operations
1259 // Normal case when editing text by typing
1260 // h e l l o w o r k d h o w a r e y o u
1263 // [-------] mNoCheckRange
1264 // -> does nothing (range is the same as the no check range)
1266 // Case when pasting:
1267 // [---------- pasted text ----------]
1268 // h e l l o w o r k d h o w a r e y o u
1270 // [---] aNoCheckRange
1271 // -> recheck all words in range except those in aNoCheckRange
1273 // If checking is complete, *aDoneChecking will be set. If there is more
1274 // but we ran out of time, this will be false and the range will be
1275 // updated with the stuff that still needs checking.
1277 nsresult
mozInlineSpellChecker::DoSpellCheck(mozInlineSpellWordUtil
& aWordUtil
,
1278 nsISelection
*aSpellCheckSelection
,
1279 mozInlineSpellStatus
* aStatus
,
1280 PRBool
* aDoneChecking
)
1282 nsCOMPtr
<nsIDOMNode
> beginNode
, endNode
;
1283 PRInt32 beginOffset
, endOffset
;
1284 *aDoneChecking
= PR_TRUE
;
1286 // get the editor for SkipSpellCheckForNode, this may fail in reasonable
1287 // circumstances since the editor could have gone away
1288 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
1290 return NS_ERROR_FAILURE
;
1293 nsresult rv
= aStatus
->mRange
->GetCollapsed(&iscollapsed
);
1294 NS_ENSURE_SUCCESS(rv
, rv
);
1298 nsCOMPtr
<nsISelection2
> sel2
= do_QueryInterface(aSpellCheckSelection
, &rv
);
1299 NS_ENSURE_SUCCESS(rv
, rv
);
1301 // see if the selection has any ranges, if not, then we can optimize checking
1302 // range inclusion later (we have no ranges when we are initially checking or
1303 // when there are no misspelled words yet).
1304 PRInt32 originalRangeCount
;
1305 rv
= aSpellCheckSelection
->GetRangeCount(&originalRangeCount
);
1306 NS_ENSURE_SUCCESS(rv
, rv
);
1308 // set the starting DOM position to be the beginning of our range
1309 NS_ENSURE_SUCCESS(rv
, rv
);
1310 aStatus
->mRange
->GetStartContainer(getter_AddRefs(beginNode
));
1311 aStatus
->mRange
->GetStartOffset(&beginOffset
);
1312 aStatus
->mRange
->GetEndContainer(getter_AddRefs(endNode
));
1313 aStatus
->mRange
->GetEndOffset(&endOffset
);
1314 aWordUtil
.SetEnd(endNode
, endOffset
);
1315 aWordUtil
.SetPosition(beginNode
, beginOffset
);
1317 // aWordUtil.SetPosition flushes pending notifications, check editor again.
1318 editor
= do_QueryReferent(mEditor
);
1320 return NS_ERROR_FAILURE
;
1322 // we need to use IsPointInRange which is on a more specific interface
1323 nsCOMPtr
<nsIDOMNSRange
> noCheckRange
, createdRange
;
1324 if (aStatus
->mNoCheckRange
)
1325 noCheckRange
= do_QueryInterface(aStatus
->mNoCheckRange
);
1326 if (aStatus
->mCreatedRange
)
1327 createdRange
= do_QueryInterface(aStatus
->mCreatedRange
);
1329 PRInt32 wordsSinceTimeCheck
= 0;
1330 PRTime beginTime
= PR_Now();
1332 nsAutoString wordText
;
1333 nsCOMPtr
<nsIDOMRange
> wordRange
;
1334 PRBool dontCheckWord
;
1335 while (NS_SUCCEEDED(aWordUtil
.GetNextWord(wordText
,
1336 getter_AddRefs(wordRange
),
1339 wordsSinceTimeCheck
++;
1341 // get the range for the current word
1342 wordRange
->GetStartContainer(getter_AddRefs(beginNode
));
1343 wordRange
->GetEndContainer(getter_AddRefs(endNode
));
1344 wordRange
->GetStartOffset(&beginOffset
);
1345 wordRange
->GetEndOffset(&endOffset
);
1347 #ifdef DEBUG_INLINESPELL
1348 printf("->Got word \"%s\"", NS_ConvertUTF16toUTF8(wordText
).get());
1350 printf(" (not checking)");
1354 // see if there is a spellcheck range that already intersects the word
1355 // and remove it. We only need to remove old ranges, so don't bother if
1356 // there were no ranges when we started out.
1357 if (originalRangeCount
> 0) {
1358 // likewise, if this word is inside new text, we won't bother testing
1359 PRBool inCreatedRange
= PR_FALSE
;
1361 createdRange
->IsPointInRange(beginNode
, beginOffset
, &inCreatedRange
);
1362 if (! inCreatedRange
) {
1363 nsCOMArray
<nsIDOMRange
> ranges
;
1364 rv
= sel2
->GetRangesForIntervalCOMArray(beginNode
, beginOffset
,
1367 NS_ENSURE_SUCCESS(rv
, rv
);
1368 for (PRInt32 i
= 0; i
< ranges
.Count(); i
++)
1369 RemoveRange(aSpellCheckSelection
, ranges
[i
]);
1373 // some words are special and don't need checking
1377 // some nodes we don't spellcheck
1378 PRBool checkSpelling
;
1379 rv
= SkipSpellCheckForNode(editor
, beginNode
, &checkSpelling
);
1380 NS_ENSURE_SUCCESS(rv
, rv
);
1384 // Don't check spelling if we're inside the noCheckRange. This needs to
1385 // be done after we clear any old selection because the excluded word
1386 // might have been previously marked.
1388 // We do a simple check to see if the beginning of our word is in the
1389 // exclusion range. Because the exclusion range is a multiple of a word,
1390 // this is sufficient.
1392 PRBool inExclusion
= PR_FALSE
;
1393 noCheckRange
->IsPointInRange(beginNode
, beginOffset
, &inExclusion
);
1398 // check spelling and add to selection if misspelled
1399 PRBool isMisspelled
;
1400 aWordUtil
.NormalizeWord(wordText
);
1401 rv
= mSpellCheck
->CheckCurrentWordNoSuggest(wordText
.get(), &isMisspelled
);
1403 // misspelled words count extra toward the max
1404 wordsSinceTimeCheck
+= MISSPELLED_WORD_COUNT_PENALTY
;
1405 AddRange(aSpellCheckSelection
, wordRange
);
1407 aStatus
->mWordCount
++;
1408 if (aStatus
->mWordCount
>= mMaxMisspellingsPerCheck
||
1409 SpellCheckSelectionIsFull())
1413 // see if we've run out of time, only check every N words for perf
1414 if (wordsSinceTimeCheck
>= INLINESPELL_TIMEOUT_CHECK_FREQUENCY
) {
1415 wordsSinceTimeCheck
= 0;
1416 if (PR_Now() > PRTime(beginTime
+ INLINESPELL_CHECK_TIMEOUT
* PR_USEC_PER_MSEC
)) {
1417 // stop checking, our time limit has been exceeded
1419 // move the range to encompass the stuff that needs checking
1420 rv
= aStatus
->mRange
->SetStart(endNode
, endOffset
);
1421 if (NS_FAILED(rv
)) {
1422 // The range might be unhappy because the beginning is after the
1423 // end. This is possible when the requested end was in the middle
1424 // of a word, just ignore this situation and assume we're done.
1427 *aDoneChecking
= PR_FALSE
;
1436 // mozInlineSpellChecker::ResumeCheck
1438 // Called by the resume event when it fires. We will try to pick up where
1439 // the last resume left off.
1442 mozInlineSpellChecker::ResumeCheck(mozInlineSpellStatus
* aStatus
)
1445 return NS_OK
; // spell checking has been turned off
1447 nsCOMPtr
<nsIEditor
> editor
= do_QueryReferent(mEditor
);
1449 return NS_OK
; // editor is gone
1451 mozInlineSpellWordUtil wordUtil
;
1452 nsresult rv
= wordUtil
.Init(mEditor
);
1454 return NS_OK
; // editor doesn't like us, don't assert
1456 nsCOMPtr
<nsISelection
> spellCheckSelection
;
1457 rv
= GetSpellCheckSelection(getter_AddRefs(spellCheckSelection
));
1458 NS_ENSURE_SUCCESS(rv
, rv
);
1459 CleanupRangesInSelection(spellCheckSelection
);
1461 rv
= aStatus
->FinishInitOnEvent(wordUtil
);
1462 NS_ENSURE_SUCCESS(rv
, rv
);
1463 if (! aStatus
->mRange
)
1464 return NS_OK
; // empty range, nothing to do
1466 PRBool doneChecking
= PR_TRUE
;
1467 if (aStatus
->mOp
== mozInlineSpellStatus::eOpSelection
)
1468 rv
= DoSpellCheckSelection(wordUtil
, spellCheckSelection
, aStatus
);
1470 rv
= DoSpellCheck(wordUtil
, spellCheckSelection
, aStatus
, &doneChecking
);
1471 NS_ENSURE_SUCCESS(rv
, rv
);
1474 rv
= ScheduleSpellCheck(*aStatus
);
1478 // mozInlineSpellChecker::IsPointInSelection
1480 // Determines if a given (node,offset) point is inside the given
1481 // selection. If so, the specific range of the selection that
1482 // intersects is places in *aRange. (There may be multiple disjoint
1483 // ranges in a selection.)
1485 // If there is no intersection, *aRange will be NULL.
1488 mozInlineSpellChecker::IsPointInSelection(nsISelection
*aSelection
,
1491 nsIDOMRange
**aRange
)
1496 nsCOMPtr
<nsISelection2
> sel2
= do_QueryInterface(aSelection
, &rv
);
1497 NS_ENSURE_SUCCESS(rv
, rv
);
1499 nsCOMArray
<nsIDOMRange
> ranges
;
1500 rv
= sel2
->GetRangesForIntervalCOMArray(aNode
, aOffset
, aNode
, aOffset
,
1502 NS_ENSURE_SUCCESS(rv
, rv
);
1504 if (ranges
.Count() == 0)
1505 return NS_OK
; // no matches
1507 // there may be more than one range returned, and we don't know what do
1508 // do with that, so just get the first one
1509 NS_ADDREF(*aRange
= ranges
[0]);
1514 mozInlineSpellChecker::CleanupRangesInSelection(nsISelection
*aSelection
)
1516 // integrity check - remove ranges that have collapsed to nothing. This
1517 // can happen if the node containing a highlighted word was removed.
1518 NS_ENSURE_ARG_POINTER(aSelection
);
1521 aSelection
->GetRangeCount(&count
);
1523 for (PRInt32 index
= 0; index
< count
; index
++)
1525 nsCOMPtr
<nsIDOMRange
> checkRange
;
1526 aSelection
->GetRangeAt(index
, getter_AddRefs(checkRange
));
1531 checkRange
->GetCollapsed(&collapsed
);
1534 RemoveRange(aSelection
, checkRange
);
1545 // mozInlineSpellChecker::RemoveRange
1547 // For performance reasons, we have an upper bound on the number of word
1548 // ranges in the spell check selection. When removing a range from the
1549 // selection, we need to decrement mNumWordsInSpellSelection
1552 mozInlineSpellChecker::RemoveRange(nsISelection
* aSpellCheckSelection
,
1553 nsIDOMRange
* aRange
)
1555 NS_ENSURE_ARG_POINTER(aSpellCheckSelection
);
1556 NS_ENSURE_ARG_POINTER(aRange
);
1558 nsresult rv
= aSpellCheckSelection
->RemoveRange(aRange
);
1559 if (NS_SUCCEEDED(rv
) && mNumWordsInSpellSelection
)
1560 mNumWordsInSpellSelection
--;
1566 // mozInlineSpellChecker::AddRange
1568 // For performance reasons, we have an upper bound on the number of word
1569 // ranges we'll add to the spell check selection. Once we reach that upper
1570 // bound, stop adding the ranges
1573 mozInlineSpellChecker::AddRange(nsISelection
* aSpellCheckSelection
,
1574 nsIDOMRange
* aRange
)
1576 NS_ENSURE_ARG_POINTER(aSpellCheckSelection
);
1577 NS_ENSURE_ARG_POINTER(aRange
);
1579 nsresult rv
= NS_OK
;
1581 if (!SpellCheckSelectionIsFull())
1583 rv
= aSpellCheckSelection
->AddRange(aRange
);
1584 if (NS_SUCCEEDED(rv
))
1585 mNumWordsInSpellSelection
++;
1591 nsresult
mozInlineSpellChecker::GetSpellCheckSelection(nsISelection
** aSpellCheckSelection
)
1593 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
1594 NS_ENSURE_TRUE(editor
, NS_ERROR_NULL_POINTER
);
1596 nsCOMPtr
<nsISelectionController
> selcon
;
1597 nsresult rv
= editor
->GetSelectionController(getter_AddRefs(selcon
));
1598 NS_ENSURE_SUCCESS(rv
, rv
);
1600 nsCOMPtr
<nsISelection
> spellCheckSelection
;
1601 return selcon
->GetSelection(nsISelectionController::SELECTION_SPELLCHECK
, aSpellCheckSelection
);
1604 nsresult
mozInlineSpellChecker::SaveCurrentSelectionPosition()
1606 nsCOMPtr
<nsIEditor
> editor (do_QueryReferent(mEditor
));
1607 NS_ENSURE_TRUE(editor
, NS_OK
);
1609 // figure out the old caret position based on the current selection
1610 nsCOMPtr
<nsISelection
> selection
;
1611 nsresult rv
= editor
->GetSelection(getter_AddRefs(selection
));
1612 NS_ENSURE_SUCCESS(rv
, rv
);
1614 rv
= selection
->GetFocusNode(getter_AddRefs(mCurrentSelectionAnchorNode
));
1615 NS_ENSURE_SUCCESS(rv
, rv
);
1617 selection
->GetFocusOffset(&mCurrentSelectionOffset
);
1622 // This is a copy of nsContentUtils::ContentIsDescendantOf. Another crime
1623 // for XPCOM's rap sheet
1625 ContentIsDescendantOf(nsINode
* aPossibleDescendant
,
1626 nsINode
* aPossibleAncestor
)
1628 NS_PRECONDITION(aPossibleDescendant
, "The possible descendant is null!");
1629 NS_PRECONDITION(aPossibleAncestor
, "The possible ancestor is null!");
1632 if (aPossibleDescendant
== aPossibleAncestor
)
1634 aPossibleDescendant
= aPossibleDescendant
->GetNodeParent();
1635 } while (aPossibleDescendant
);
1640 // mozInlineSpellChecker::HandleNavigationEvent
1642 // Acts upon mouse clicks and keyboard navigation changes, spell checking
1643 // the previous word if the new navigation location moves us to another
1646 // This is complicated by the fact that our mouse events are happening after
1647 // selection has been changed to account for the mouse click. But keyboard
1648 // events are happening before the caret selection has changed. Working
1649 // around this by letting keyboard events setting forceWordSpellCheck to
1650 // true. aNewPositionOffset also tries to work around this for the
1651 // DOM_VK_RIGHT and DOM_VK_LEFT cases.
1654 mozInlineSpellChecker::HandleNavigationEvent(nsIDOMEvent
* aEvent
,
1655 PRBool aForceWordSpellCheck
,
1656 PRInt32 aNewPositionOffset
)
1660 // If we already handled the navigation event and there is no possibility
1661 // anything has changed since then, we don't have to do anything. This
1662 // optimization makes a noticable difference when you hold down a navigation
1663 // key like Page Down.
1664 if (! mNeedsCheckAfterNavigation
)
1667 nsCOMPtr
<nsIDOMNode
> currentAnchorNode
= mCurrentSelectionAnchorNode
;
1668 PRInt32 currentAnchorOffset
= mCurrentSelectionOffset
;
1670 // now remember the new focus position resulting from the event
1671 rv
= SaveCurrentSelectionPosition();
1672 NS_ENSURE_SUCCESS(rv
, rv
);
1675 mozInlineSpellStatus
status(this);
1676 rv
= status
.InitForNavigation(aForceWordSpellCheck
, aNewPositionOffset
,
1677 currentAnchorNode
, currentAnchorOffset
,
1678 mCurrentSelectionAnchorNode
, mCurrentSelectionOffset
,
1680 NS_ENSURE_SUCCESS(rv
, rv
);
1682 rv
= ScheduleSpellCheck(status
);
1683 NS_ENSURE_SUCCESS(rv
, rv
);
1689 NS_IMETHODIMP
mozInlineSpellChecker::HandleEvent(nsIDOMEvent
* aEvent
)
1694 NS_IMETHODIMP
mozInlineSpellChecker::Focus(nsIDOMEvent
* aEvent
)
1699 NS_IMETHODIMP
mozInlineSpellChecker::Blur(nsIDOMEvent
* aEvent
)
1701 // force spellcheck on blur, for instance when tabbing out of a textbox
1702 HandleNavigationEvent(aEvent
, PR_TRUE
);
1706 NS_IMETHODIMP
mozInlineSpellChecker::MouseClick(nsIDOMEvent
*aMouseEvent
)
1708 nsCOMPtr
<nsIDOMMouseEvent
>mouseEvent
= do_QueryInterface(aMouseEvent
);
1709 NS_ENSURE_TRUE(mouseEvent
, NS_OK
);
1711 // ignore any errors from HandleNavigationEvent as we don't want to prevent
1712 // anyone else from seeing this event.
1714 mouseEvent
->GetButton(&button
);
1716 HandleNavigationEvent(mouseEvent
, PR_FALSE
);
1718 HandleNavigationEvent(mouseEvent
, PR_TRUE
);
1722 NS_IMETHODIMP
mozInlineSpellChecker::MouseDown(nsIDOMEvent
* aMouseEvent
)
1727 NS_IMETHODIMP
mozInlineSpellChecker::MouseUp(nsIDOMEvent
* aMouseEvent
)
1732 NS_IMETHODIMP
mozInlineSpellChecker::MouseDblClick(nsIDOMEvent
* aMouseEvent
)
1737 NS_IMETHODIMP
mozInlineSpellChecker::MouseOver(nsIDOMEvent
* aMouseEvent
)
1742 NS_IMETHODIMP
mozInlineSpellChecker::MouseOut(nsIDOMEvent
* aMouseEvent
)
1747 NS_IMETHODIMP
mozInlineSpellChecker::KeyDown(nsIDOMEvent
* aKeyEvent
)
1753 NS_IMETHODIMP
mozInlineSpellChecker::KeyUp(nsIDOMEvent
* aKeyEvent
)
1759 NS_IMETHODIMP
mozInlineSpellChecker::KeyPress(nsIDOMEvent
* aKeyEvent
)
1761 nsCOMPtr
<nsIDOMKeyEvent
>keyEvent
= do_QueryInterface(aKeyEvent
);
1762 NS_ENSURE_TRUE(keyEvent
, NS_OK
);
1765 keyEvent
->GetKeyCode(&keyCode
);
1767 // we only care about navigation keys that moved selection
1770 case nsIDOMKeyEvent::DOM_VK_RIGHT
:
1771 case nsIDOMKeyEvent::DOM_VK_LEFT
:
1772 HandleNavigationEvent(aKeyEvent
, PR_FALSE
, keyCode
== nsIDOMKeyEvent::DOM_VK_RIGHT
? 1 : -1);
1774 case nsIDOMKeyEvent::DOM_VK_UP
:
1775 case nsIDOMKeyEvent::DOM_VK_DOWN
:
1776 case nsIDOMKeyEvent::DOM_VK_HOME
:
1777 case nsIDOMKeyEvent::DOM_VK_END
:
1778 case nsIDOMKeyEvent::DOM_VK_PAGE_UP
:
1779 case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN
:
1780 HandleNavigationEvent(aKeyEvent
, PR_TRUE
/* force a spelling correction */);