diff --git a/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.cpp b/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.cpp index ae22eef897e..073bd4e2b42 100644 --- a/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.cpp +++ b/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.cpp @@ -38,6 +38,7 @@ #include "nsCOMPtr.h" #include "nsString.h" +#include "nsArray.h" #include "nsIServiceManager.h" #include "nsIEnumerator.h" #include "nsUnicharUtils.h" @@ -69,6 +70,10 @@ #include "nsIContentIterator.h" #include "nsCRT.h" #include "cattable.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" + +static const char kMaxSpellCheckSelectionSize[] = "extensions.spellcheck.inline.max-misspellings"; NS_INTERFACE_MAP_BEGIN(mozInlineSpellChecker) NS_INTERFACE_MAP_ENTRY(nsIInlineSpellChecker) @@ -87,8 +92,11 @@ mozInlineSpellChecker::SpellCheckingState mozInlineSpellChecker::gCanEnableSpellChecking = mozInlineSpellChecker::SpellCheck_Uninitialized; -mozInlineSpellChecker::mozInlineSpellChecker() +mozInlineSpellChecker::mozInlineSpellChecker():mNumWordsInSpellSelection(0), mMaxNumWordsInSpellSelection(250) { + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefs) + prefs->GetIntPref(kMaxSpellCheckSelectionSize, &mMaxNumWordsInSpellSelection); } mozInlineSpellChecker::~mozInlineSpellChecker() @@ -229,7 +237,7 @@ NS_IMETHODIMP mozInlineSpellChecker::SetEnableRealTimeSpell(PRBool aEnabled) } } - // spellcheck the current contents + // spellcheck the current content res = SpellCheckRange(nsnull); } else @@ -238,6 +246,7 @@ NS_IMETHODIMP mozInlineSpellChecker::SetEnableRealTimeSpell(PRBool aEnabled) res = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); NS_ENSURE_SUCCESS(res, res); spellCheckSelection->RemoveAllRanges(); + mNumWordsInSpellSelection = 0; UnregisterEventListeners(); mSpellCheck = nsnull; } @@ -329,7 +338,7 @@ NS_IMETHODIMP mozInlineSpellChecker::SpellCheckAfterEditorChange(PRInt32 action, case kOpRemoveTextProperty: case kOpResetTextProperties: - res = SpellCheckSelection(aSelection, spellCheckSelection); + res = SpellCheckSelection(aSelection); break; case kOpMakeBasicBlock: @@ -351,11 +360,11 @@ NS_IMETHODIMP mozInlineSpellChecker::SpellCheckAfterEditorChange(PRInt32 action, if (iscollapsed) res = AdjustSpellHighlighting(anchorNode, anchorOffset, spellCheckSelection, PR_FALSE); else - res = SpellCheckSelection(aSelection, spellCheckSelection); + res = SpellCheckSelection(aSelection); break; case kOpSetTextProperty: - res = SpellCheckSelection(aSelection,spellCheckSelection); + res = SpellCheckSelection(aSelection); break; case kOpUndo: @@ -393,7 +402,8 @@ NS_IMETHODIMP mozInlineSpellChecker::SpellCheckRange(nsIDOMRange *aRange) if(aRange) { // use the given range rv = SpellCheckRange(aRange,spellCheckSelection); - } else { + } else + { // use full range: SpellCheckBetweenNodes will do the somewhat complicated // task of creating a range over the element we give it and call // SpellCheckRange(range,selection) for us @@ -477,14 +487,11 @@ NS_IMETHODIMP mozInlineSpellChecker::AddWordToDictionary(const nsAString &word) nsresult res = mSpellCheck->AddWordToDictionary(wordstr.get()); NS_ENSURE_SUCCESS(res, res); - nsCOMPtr editor (do_QueryReferent(mEditor)); - NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); + nsCOMPtr spellCheckSelection; + nsresult rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); + NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr rootElem; - res = editor->GetRootElement(getter_AddRefs(rootElem)); - NS_ENSURE_SUCCESS(res, res); - - return SpellCheckBetweenNodes(rootElem, 0, rootElem, -1, NULL); + return SpellCheckSelection(spellCheckSelection); } NS_IMETHODIMP mozInlineSpellChecker::IgnoreWord(const nsAString &word) @@ -495,14 +502,10 @@ NS_IMETHODIMP mozInlineSpellChecker::IgnoreWord(const nsAString &word) nsresult res = mSpellCheck->IgnoreWordAllOccurrences(wordstr.get()); NS_ENSURE_SUCCESS(res, res); - nsCOMPtr editor (do_QueryReferent(mEditor)); - NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); - - nsCOMPtr rootElem; - res = editor->GetRootElement(getter_AddRefs(rootElem)); - NS_ENSURE_SUCCESS(res, res); - - return SpellCheckBetweenNodes(rootElem, 0, rootElem, -1, NULL); + nsCOMPtr spellCheckSelection; + nsresult rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); + NS_ENSURE_SUCCESS(rv, rv); + return SpellCheckSelection(spellCheckSelection); } NS_IMETHODIMP mozInlineSpellChecker::IgnoreWords(const PRUnichar **aWordsToIgnore, PRUint32 aCount) @@ -511,14 +514,68 @@ NS_IMETHODIMP mozInlineSpellChecker::IgnoreWords(const PRUnichar **aWordsToIgnor for (PRUint32 index = 0; index < aCount; index++) mSpellCheck->IgnoreWordAllOccurrences(aWordsToIgnore[index]); - nsCOMPtr editor (do_QueryReferent(mEditor)); - NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); + nsCOMPtr spellCheckSelection; + nsresult rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); + NS_ENSURE_SUCCESS(rv, rv); + return SpellCheckSelection(spellCheckSelection); +} - nsCOMPtr rootElem; - nsresult res = editor->GetRootElement(getter_AddRefs(rootElem)); - NS_ENSURE_SUCCESS(res, res); +// When the user ignores a word or adds a word to the dictionary, we used +// to re check the entire document, starting at the root element and walking through all the nodes. +// Optimization: The only words in the document that would change as a result of these actions +// are words that are already in the spell check selection (i.e. words previsouly marked as misspelled). +// Spell check the spell check selection instead of the entire document for ignore word and add word +nsresult mozInlineSpellChecker::SpellCheckSelection(nsISelection * aSelection) +{ + NS_ENSURE_ARG_POINTER(aSelection); - return SpellCheckBetweenNodes(rootElem, 0, rootElem, -1, NULL); + nsCOMPtr spellCheckSelection; + nsresult rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); + NS_ENSURE_SUCCESS(rv, rv); + + // if we are going to be checking the spell check selection, then + // clear out mNumWordsInSpellSelection since we'll be rebuilding the ranges. + if (aSelection == spellCheckSelection.get()) + mNumWordsInSpellSelection = 0; + + // Optimize for the case where aSelection is in fact the spell check selection. + // Since we could be modifying the ranges for the spellCheckSelection while looping + // on the spell check selection, keep a separate array of range elements inside the selection + PRInt32 count; + nsCOMPtr ranges = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + aSelection->GetRangeCount(&count); + PRInt32 index; + for (index = 0; index < count; index++) + { + nsCOMPtr checkRange; + aSelection->GetRangeAt(index, getter_AddRefs(checkRange)); + if (checkRange) + ranges->AppendElement(checkRange, PR_FALSE); + } + + // now loop over these ranges and spell check each range + nsCOMPtr startNode; + nsCOMPtr endNode; + PRInt32 startOffset, endOffset; + nsCOMPtr checkRange; + + for (index = 0; index < count; index++) + { + checkRange = do_QueryElementAt(ranges, index); + if (checkRange) + { + checkRange->GetStartContainer(getter_AddRefs(startNode)); + checkRange->GetEndContainer(getter_AddRefs(endNode)); + checkRange->GetStartOffset(&startOffset); + checkRange->GetEndOffset(&endOffset); + + rv |= SpellCheckBetweenNodes(startNode, startOffset, endNode, endOffset, spellCheckSelection); + } + } + + return rv; } NS_IMETHODIMP mozInlineSpellChecker::WillCreateNode(const nsAString & aTag, nsIDOMNode *aParent, PRInt32 aPosition) @@ -608,39 +665,6 @@ NS_IMETHODIMP mozInlineSpellChecker::DidDeleteSelection(nsISelection *aSelection return NS_OK; } -nsresult -mozInlineSpellChecker::SpellCheckSelection(nsISelection *aSelection, nsISelection *aSpellCheckSelection) -{ - NS_ENSURE_ARG_POINTER(aSelection); - if (!mSpellCheck) - return NS_ERROR_NOT_INITIALIZED; - - PRInt32 count; - aSelection->GetRangeCount(&count); - - for (PRInt32 index =0; index < count; index++) - { - nsCOMPtr checkRange; - aSelection->GetRangeAt(index, getter_AddRefs(checkRange)); - - if (checkRange) - { - nsCOMPtr startNode; - nsCOMPtr endNode; - PRInt32 startOffset, endOffset; - - checkRange->GetStartContainer(getter_AddRefs(startNode)); - checkRange->GetEndContainer(getter_AddRefs(endNode)); - checkRange->GetStartOffset(&startOffset); - checkRange->GetEndOffset(&endOffset); - - return SpellCheckBetweenNodes(startNode, startOffset, endNode, endOffset, aSpellCheckSelection); - } - } - - return NS_OK; -} - nsresult mozInlineSpellChecker::SpellCheckBetweenNodes(nsIDOMNode *aStartNode, PRInt32 aStartOffset, @@ -723,7 +747,7 @@ static inline PRBool IsNonwordChar(PRUnichar chr) // There are certain conditions when we don't want to spell check a node. In particular // quotations, moz signatures, etc. This routine returns false for these cases. -nsresult mozInlineSpellChecker::CheckShouldSpellCheck(nsIDOMNode *aNode, PRBool *checkSpelling) +nsresult mozInlineSpellChecker::SkipSpellCheckForNode(nsIDOMNode *aNode, PRBool *checkSpelling) { *checkSpelling = PR_TRUE; NS_ENSURE_ARG_POINTER(aNode); @@ -923,7 +947,7 @@ mozInlineSpellChecker::AdjustSpellHighlighting(nsIDOMNode *aNode, return NS_OK; PRBool checkSpelling; - CheckShouldSpellCheck(currentNode, &checkSpelling); + SkipSpellCheckForNode(currentNode, &checkSpelling); if (!checkSpelling) return NS_OK; @@ -948,7 +972,7 @@ mozInlineSpellChecker::AdjustSpellHighlighting(nsIDOMNode *aNode, NS_ENSURE_SUCCESS(rv, rv); if (isMisspelled) - aSpellCheckSelection->AddRange(wordRange); + AddRange(aSpellCheckSelection, wordRange); return NS_OK; } @@ -976,7 +1000,7 @@ nsresult mozInlineSpellChecker::SpellCheckRange(nsIDOMRange *aRange, nsISelectio nsCOMPtr startNode; nsCOMPtr endNode; - while (NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done) + while (!SpellCheckSelectionIsFull() && NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done) { nsAutoString textblock; res = mTextServicesDocument->GetCurrentTextBlock(&textblock); @@ -1002,7 +1026,7 @@ nsresult mozInlineSpellChecker::SpellCheckRange(nsIDOMRange *aRange, nsISelectio wordrange->GetEndOffset(&endOffset); PRBool checkSpelling; - CheckShouldSpellCheck(startNode, &checkSpelling); + SkipSpellCheckForNode(startNode, &checkSpelling); if (!checkSpelling) break; @@ -1013,12 +1037,12 @@ nsresult mozInlineSpellChecker::SpellCheckRange(nsIDOMRange *aRange, nsISelectio IsPointInSelection(aSpellCheckSelection, endNode, endOffset - 1, getter_AddRefs(currentRange)); if (currentRange) // remove the old range first - aSpellCheckSelection->RemoveRange(currentRange); + RemoveRange(aSpellCheckSelection, currentRange); if (isMisspelled) - aSpellCheckSelection->AddRange(wordrange); + AddRange(aSpellCheckSelection, wordrange); } selOffset = end; - } while (end != -1); + } while (end != -1 && !SpellCheckSelectionIsFull()); mTextServicesDocument->NextBlock(); selOffset = 0; @@ -1107,7 +1131,7 @@ mozInlineSpellChecker::CleanupRangesInSelection(nsISelection *aSelection) checkRange->GetCollapsed(&collapsed); if (collapsed) { - aSelection->RemoveRange(checkRange); + RemoveRange(aSelection, checkRange); index--; count--; } @@ -1117,6 +1141,40 @@ mozInlineSpellChecker::CleanupRangesInSelection(nsISelection *aSelection) return NS_OK; } +// For performance reasons, we have an upper bound on the number of word ranges +// in the spell check selection. When removing a range from the selection, we need to decrement +// mNumWordsInSpellSelection +nsresult mozInlineSpellChecker::RemoveRange(nsISelection *aSpellCheckSelection, nsIDOMRange * aRange) +{ + NS_ENSURE_ARG_POINTER(aSpellCheckSelection); + NS_ENSURE_ARG_POINTER(aRange); + + nsresult rv = aSpellCheckSelection->RemoveRange(aRange); + if (NS_SUCCEEDED(rv) && mNumWordsInSpellSelection) + mNumWordsInSpellSelection--; + + return rv; +} + +// For performance reasons, we have an upper bound on the number of word ranges we'll add +// to the spell check selection. Once we reach that upper bound, stop adding the ranges +nsresult mozInlineSpellChecker::AddRange(nsISelection *aSpellCheckSelection, nsIDOMRange * aRange) +{ + NS_ENSURE_ARG_POINTER(aSpellCheckSelection); + NS_ENSURE_ARG_POINTER(aRange); + + nsresult rv = NS_OK; + + if (!SpellCheckSelectionIsFull()) + { + rv = aSpellCheckSelection->AddRange(aRange); + if (NS_SUCCEEDED(rv)) + mNumWordsInSpellSelection++; + } + + return rv; +} + // a helper routine that expands the current position in the selection to the surrounding word // and then removes it from the spell checker selection nsresult mozInlineSpellChecker::RemoveCurrentWordFromSpellSelection(nsISelection *aSpellCheckSelection, nsIDOMRange * aWordRange) @@ -1137,11 +1195,11 @@ nsresult mozInlineSpellChecker::RemoveCurrentWordFromSpellSelection(nsISelection IsPointInSelection(aSpellCheckSelection, startNode, startOffset, getter_AddRefs(currentRange)); if (currentRange) - aSpellCheckSelection->RemoveRange(currentRange); + RemoveRange(aSpellCheckSelection, currentRange); IsPointInSelection(aSpellCheckSelection, endNode, endOffset - 1, getter_AddRefs(currentRange)); if (currentRange) - aSpellCheckSelection->RemoveRange(currentRange); + RemoveRange(aSpellCheckSelection, currentRange); return NS_OK; } diff --git a/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.h b/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.h index f981922b3d6..fcc26147137 100644 --- a/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.h +++ b/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.h @@ -69,6 +69,9 @@ private: nsCOMPtr mTreeWalker; nsCOMPtr mConverter; + PRInt32 mNumWordsInSpellSelection; + PRInt32 mMaxNumWordsInSpellSelection; + // we need to keep track of the current text position in the document // so we can spell check the old word when the user clicks around the document. nsCOMPtr mCurrentSelectionAnchorNode; @@ -143,8 +146,7 @@ public: virtual ~mozInlineSpellChecker(); // spell check the selected text specified by aSelection - nsresult SpellCheckSelection(nsISelection *aSelection, - nsISelection *aSpellCheckSelection); + nsresult SpellCheckSelection(nsISelection *aSelection); // spell checks all of the words between two nodes nsresult SpellCheckBetweenNodes(nsIDOMNode *aStartNode, @@ -152,10 +154,11 @@ public: nsIDOMNode *aEndNode, PRInt32 aEndOffset, nsISelection *aSpellCheckSelection); + // examines the dom node in question and returns true if the inline spell // checker should skip the node (i.e. the text is inside of a block quote // or an e-mail signature...) - nsresult CheckShouldSpellCheck(nsIDOMNode *aNode, PRBool * aCheckSpelling); + nsresult SkipSpellCheckForNode(nsIDOMNode *aNode, PRBool * aCheckSpelling); nsresult AdjustSpellHighlighting(nsIDOMNode *aNode, PRInt32 aOffset, @@ -174,12 +177,16 @@ public: nsresult CleanupRangesInSelection(nsISelection *aSelection); nsresult RemoveCurrentWordFromSpellSelection(nsISelection *aSpellCheckSelection, nsIDOMRange * aWordRange); + // helper routines used to control how many ranges we allow into the spell check selection + nsresult RemoveRange(nsISelection *aSpellCheckSelection, nsIDOMRange * aRange); + nsresult AddRange(nsISelection *aSpellCheckSelection, nsIDOMRange * aRange); + PRBool SpellCheckSelectionIsFull() { return mNumWordsInSpellSelection >= mMaxNumWordsInSpellSelection; } + // DOM and editor event registration helper routines nsresult RegisterEventListeners(); nsresult UnregisterEventListeners(); nsresult HandleNavigationEvent(nsIDOMEvent * aEvent, PRBool aForceWordSpellCheck, PRInt32 aNewPositionOffset = 0); - // helper routine which expands a point in a text node out to the range for the containing word. nsresult GenerateRangeForSurroundingWord(nsIDOMNode * aNode, PRInt32 aOffset, nsIDOMRange ** aWordRange); diff --git a/mozilla/mail/components/compose/content/MsgComposeCommands.js b/mozilla/mail/components/compose/content/MsgComposeCommands.js index 9ffbd179d9a..8d865420cb5 100644 --- a/mozilla/mail/components/compose/content/MsgComposeCommands.js +++ b/mozilla/mail/components/compose/content/MsgComposeCommands.js @@ -247,6 +247,7 @@ var gComposeRecyclingListener = { } //Reset editor + InlineSpellChecker.Init(GetCurrentEditor(), false); // unregister inline spell checking listeners and release the spell checker EditorResetFontAndColorAttributes(); EditorCleanup(); @@ -3551,5 +3552,4 @@ function InitEditor() var editor = GetCurrentEditor(); gMsgCompose.initEditor(editor, window.content); InlineSpellChecker.Init(editor, sPrefs.getBoolPref("mail.spellcheck.inline")); - InlineSpellChecker.checkDocument(window.content.document); } diff --git a/mozilla/modules/libpref/src/init/all.js b/mozilla/modules/libpref/src/init/all.js index 935e8aae4fe..4c8341ad494 100644 --- a/mozilla/modules/libpref/src/init/all.js +++ b/mozilla/modules/libpref/src/init/all.js @@ -225,6 +225,10 @@ pref("print.print_edge_left", 0); // 1/100 of an inch pref("print.print_edge_right", 0); // 1/100 of an inch pref("print.print_edge_bottom", 0); // 1/100 of an inch +// Pref used by the spellchecker extension to control the +// maximum number of misspelled words that will be underlined +// in a document. +pref("extensions.spellcheck.inline.max-misspellings", 250); // Prefs used by libeditor. Prefs specific to seamonkey composer // belong in mozilla/editor/ui/composer.js