diff options
Diffstat (limited to 'extensions/spellcheck/src/mozSpellChecker.cpp')
-rw-r--r-- | extensions/spellcheck/src/mozSpellChecker.cpp | 565 |
1 files changed, 565 insertions, 0 deletions
diff --git a/extensions/spellcheck/src/mozSpellChecker.cpp b/extensions/spellcheck/src/mozSpellChecker.cpp new file mode 100644 index 000000000..771006be3 --- /dev/null +++ b/extensions/spellcheck/src/mozSpellChecker.cpp @@ -0,0 +1,565 @@ +/* vim: set ts=2 sts=2 sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "mozSpellChecker.h" +#include "nsIServiceManager.h" +#include "mozISpellI18NManager.h" +#include "nsIStringEnumerator.h" +#include "nsICategoryManager.h" +#include "nsISupportsPrimitives.h" +#include "nsISimpleEnumerator.h" +#include "mozilla/PRemoteSpellcheckEngineChild.h" +#include "mozilla/dom/ContentChild.h" +#include "nsXULAppAPI.h" + +using mozilla::dom::ContentChild; +using mozilla::PRemoteSpellcheckEngineChild; +using mozilla::RemoteSpellcheckEngineChild; + +#define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1" + +NS_IMPL_CYCLE_COLLECTING_ADDREF(mozSpellChecker) +NS_IMPL_CYCLE_COLLECTING_RELEASE(mozSpellChecker) + +NS_INTERFACE_MAP_BEGIN(mozSpellChecker) + NS_INTERFACE_MAP_ENTRY(nsISpellChecker) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpellChecker) + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozSpellChecker) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(mozSpellChecker, + mTsDoc, + mPersonalDictionary) + +mozSpellChecker::mozSpellChecker() + : mEngine(nullptr) +{ +} + +mozSpellChecker::~mozSpellChecker() +{ + if (mPersonalDictionary) { + // mPersonalDictionary->Save(); + mPersonalDictionary->EndSession(); + } + mSpellCheckingEngine = nullptr; + mPersonalDictionary = nullptr; + + if (mEngine) { + MOZ_ASSERT(XRE_IsContentProcess()); + mEngine->Send__delete__(mEngine); + MOZ_ASSERT(!mEngine); + } +} + +nsresult +mozSpellChecker::Init() +{ + mSpellCheckingEngine = nullptr; + if (XRE_IsContentProcess()) { + mozilla::dom::ContentChild* contentChild = mozilla::dom::ContentChild::GetSingleton(); + MOZ_ASSERT(contentChild); + mEngine = new RemoteSpellcheckEngineChild(this); + contentChild->SendPRemoteSpellcheckEngineConstructor(mEngine); + } else { + mPersonalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1"); + } + + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::SetDocument(nsITextServicesDocument *aDoc, bool aFromStartofDoc) +{ + mTsDoc = aDoc; + mFromStart = aFromStartofDoc; + return NS_OK; +} + + +NS_IMETHODIMP +mozSpellChecker::NextMisspelledWord(nsAString &aWord, nsTArray<nsString> *aSuggestions) +{ + if(!aSuggestions||!mConverter) + return NS_ERROR_NULL_POINTER; + + int32_t selOffset; + int32_t begin,end; + nsresult result; + result = SetupDoc(&selOffset); + bool isMisspelled,done; + if (NS_FAILED(result)) + return result; + + while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) + { + nsString str; + result = mTsDoc->GetCurrentTextBlock(&str); + + if (NS_FAILED(result)) + return result; + do{ + result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end); + if(NS_SUCCEEDED(result)&&(begin != -1)){ + const nsAString &currWord = Substring(str, begin, end - begin); + result = CheckWord(currWord, &isMisspelled, aSuggestions); + if(isMisspelled){ + aWord = currWord; + mTsDoc->SetSelection(begin, end-begin); + // After ScrollSelectionIntoView(), the pending notifications might + // be flushed and PresShell/PresContext/Frames may be dead. + // See bug 418470. + mTsDoc->ScrollSelectionIntoView(); + return NS_OK; + } + } + selOffset = end; + }while(end != -1); + mTsDoc->NextBlock(); + selOffset=0; + } + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::CheckWord(const nsAString &aWord, bool *aIsMisspelled, nsTArray<nsString> *aSuggestions) +{ + nsresult result; + bool correct; + + if (XRE_IsContentProcess()) { + nsString wordwrapped = nsString(aWord); + bool rv; + if (aSuggestions) { + rv = mEngine->SendCheckAndSuggest(wordwrapped, aIsMisspelled, aSuggestions); + } else { + rv = mEngine->SendCheck(wordwrapped, aIsMisspelled); + } + return rv ? NS_OK : NS_ERROR_NOT_AVAILABLE; + } + + if(!mSpellCheckingEngine) { + return NS_ERROR_NULL_POINTER; + } + *aIsMisspelled = false; + result = mSpellCheckingEngine->Check(PromiseFlatString(aWord).get(), &correct); + NS_ENSURE_SUCCESS(result, result); + if(!correct){ + if(aSuggestions){ + uint32_t count,i; + char16_t **words; + + result = mSpellCheckingEngine->Suggest(PromiseFlatString(aWord).get(), &words, &count); + NS_ENSURE_SUCCESS(result, result); + nsString* suggestions = aSuggestions->AppendElements(count); + for(i=0;i<count;i++){ + suggestions[i].Assign(words[i]); + } + + if (count) + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words); + } + *aIsMisspelled = true; + } + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::Replace(const nsAString &aOldWord, const nsAString &aNewWord, bool aAllOccurrences) +{ + if(!mConverter) + return NS_ERROR_NULL_POINTER; + + nsAutoString newWord(aNewWord); // sigh + + if(aAllOccurrences){ + int32_t selOffset; + int32_t startBlock,currentBlock,currOffset; + int32_t begin,end; + bool done; + nsresult result; + nsAutoString str; + + // find out where we are + result = SetupDoc(&selOffset); + if(NS_FAILED(result)) + return result; + result = GetCurrentBlockIndex(mTsDoc,&startBlock); + if(NS_FAILED(result)) + return result; + + //start at the beginning + result = mTsDoc->FirstBlock(); + currOffset=0; + currentBlock = 0; + while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) + { + result = mTsDoc->GetCurrentTextBlock(&str); + do{ + result = mConverter->FindNextWord(str.get(),str.Length(),currOffset,&begin,&end); + if(NS_SUCCEEDED(result)&&(begin != -1)){ + if (aOldWord.Equals(Substring(str, begin, end-begin))) { + // if we are before the current selection point but in the same block + // move the selection point forwards + if((currentBlock == startBlock)&&(begin < selOffset)){ + selOffset += + int32_t(aNewWord.Length()) - int32_t(aOldWord.Length()); + if(selOffset < begin) selOffset=begin; + } + mTsDoc->SetSelection(begin, end-begin); + mTsDoc->InsertText(&newWord); + mTsDoc->GetCurrentTextBlock(&str); + end += (aNewWord.Length() - aOldWord.Length()); // recursion was cute in GEB, not here. + } + } + currOffset = end; + }while(currOffset != -1); + mTsDoc->NextBlock(); + currentBlock++; + currOffset=0; + } + + // We are done replacing. Put the selection point back where we found it (or equivalent); + result = mTsDoc->FirstBlock(); + currentBlock = 0; + while(( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) &&(currentBlock < startBlock)){ + mTsDoc->NextBlock(); + } + +//After we have moved to the block where the first occurrence of replace was done, put the +//selection to the next word following it. In case there is no word following it i.e if it happens +//to be the last word in that block, then move to the next block and put the selection to the +//first word in that block, otherwise when the Setupdoc() is called, it queries the LastSelectedBlock() +//and the selection offset of the last occurrence of the replaced word is taken instead of the first +//occurrence and things get messed up as reported in the bug 244969 + + if( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ){ + nsString str; + result = mTsDoc->GetCurrentTextBlock(&str); + result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end); + if(end == -1) + { + mTsDoc->NextBlock(); + selOffset=0; + result = mTsDoc->GetCurrentTextBlock(&str); + result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end); + mTsDoc->SetSelection(begin, 0); + } + else + mTsDoc->SetSelection(begin, 0); + } + } + else{ + mTsDoc->InsertText(&newWord); + } + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::IgnoreAll(const nsAString &aWord) +{ + if(mPersonalDictionary){ + mPersonalDictionary->IgnoreWord(PromiseFlatString(aWord).get()); + } + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::AddWordToPersonalDictionary(const nsAString &aWord) +{ + nsresult res; + char16_t empty=0; + if (!mPersonalDictionary) + return NS_ERROR_NULL_POINTER; + res = mPersonalDictionary->AddWord(PromiseFlatString(aWord).get(),&empty); + return res; +} + +NS_IMETHODIMP +mozSpellChecker::RemoveWordFromPersonalDictionary(const nsAString &aWord) +{ + nsresult res; + char16_t empty=0; + if (!mPersonalDictionary) + return NS_ERROR_NULL_POINTER; + res = mPersonalDictionary->RemoveWord(PromiseFlatString(aWord).get(),&empty); + return res; +} + +NS_IMETHODIMP +mozSpellChecker::GetPersonalDictionary(nsTArray<nsString> *aWordList) +{ + if(!aWordList || !mPersonalDictionary) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr<nsIStringEnumerator> words; + mPersonalDictionary->GetWordList(getter_AddRefs(words)); + + bool hasMore; + nsAutoString word; + while (NS_SUCCEEDED(words->HasMore(&hasMore)) && hasMore) { + words->GetNext(word); + aWordList->AppendElement(word); + } + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::GetDictionaryList(nsTArray<nsString> *aDictionaryList) +{ + if (XRE_IsContentProcess()) { + ContentChild *child = ContentChild::GetSingleton(); + child->GetAvailableDictionaries(*aDictionaryList); + return NS_OK; + } + + nsresult rv; + + // For catching duplicates + nsTHashtable<nsStringHashKey> dictionaries; + + nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines; + rv = GetEngineList(&spellCheckingEngines); + NS_ENSURE_SUCCESS(rv, rv); + + for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) { + nsCOMPtr<mozISpellCheckingEngine> engine = spellCheckingEngines[i]; + + uint32_t count = 0; + char16_t **words = nullptr; + engine->GetDictionaryList(&words, &count); + for (uint32_t k = 0; k < count; k++) { + nsAutoString dictName; + + dictName.Assign(words[k]); + + // Skip duplicate dictionaries. Only take the first one + // for each name. + if (dictionaries.Contains(dictName)) + continue; + + dictionaries.PutEntry(dictName); + + if (!aDictionaryList->AppendElement(dictName)) { + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words); + return NS_ERROR_OUT_OF_MEMORY; + } + } + + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words); + } + + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::GetCurrentDictionary(nsAString &aDictionary) +{ + if (XRE_IsContentProcess()) { + aDictionary = mCurrentDictionary; + return NS_OK; + } + + if (!mSpellCheckingEngine) { + aDictionary.Truncate(); + return NS_OK; + } + + nsXPIDLString dictname; + mSpellCheckingEngine->GetDictionary(getter_Copies(dictname)); + aDictionary = dictname; + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary) +{ + if (XRE_IsContentProcess()) { + nsString wrappedDict = nsString(aDictionary); + bool isSuccess; + mEngine->SendSetDictionary(wrappedDict, &isSuccess); + if (!isSuccess) { + mCurrentDictionary.Truncate(); + return NS_ERROR_NOT_AVAILABLE; + } + + mCurrentDictionary = wrappedDict; + return NS_OK; + } + + // Calls to mozISpellCheckingEngine::SetDictionary might destroy us + RefPtr<mozSpellChecker> kungFuDeathGrip = this; + + mSpellCheckingEngine = nullptr; + + if (aDictionary.IsEmpty()) { + return NS_OK; + } + + nsresult rv; + nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines; + rv = GetEngineList(&spellCheckingEngines); + NS_ENSURE_SUCCESS(rv, rv); + + for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) { + // We must set mSpellCheckingEngine before we call SetDictionary, since + // SetDictionary calls back to this spell checker to check if the + // dictionary was set + mSpellCheckingEngine = spellCheckingEngines[i]; + + rv = mSpellCheckingEngine->SetDictionary(PromiseFlatString(aDictionary).get()); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<mozIPersonalDictionary> personalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1"); + mSpellCheckingEngine->SetPersonalDictionary(personalDictionary.get()); + + nsXPIDLString language; + nsCOMPtr<mozISpellI18NManager> serv(do_GetService("@mozilla.org/spellchecker/i18nmanager;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return serv->GetUtil(language.get(),getter_AddRefs(mConverter)); + } + } + + mSpellCheckingEngine = nullptr; + + // We could not find any engine with the requested dictionary + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult +mozSpellChecker::SetupDoc(int32_t *outBlockOffset) +{ + nsresult rv; + + nsITextServicesDocument::TSDBlockSelectionStatus blockStatus; + int32_t selOffset; + int32_t selLength; + *outBlockOffset = 0; + + if (!mFromStart) + { + rv = mTsDoc->LastSelectedBlock(&blockStatus, &selOffset, &selLength); + if (NS_SUCCEEDED(rv) && (blockStatus != nsITextServicesDocument::eBlockNotFound)) + { + switch (blockStatus) + { + case nsITextServicesDocument::eBlockOutside: // No TB in S, but found one before/after S. + case nsITextServicesDocument::eBlockPartial: // S begins or ends in TB but extends outside of TB. + // the TS doc points to the block we want. + *outBlockOffset = selOffset + selLength; + break; + + case nsITextServicesDocument::eBlockInside: // S extends beyond the start and end of TB. + // we want the block after this one. + rv = mTsDoc->NextBlock(); + *outBlockOffset = 0; + break; + + case nsITextServicesDocument::eBlockContains: // TB contains entire S. + *outBlockOffset = selOffset + selLength; + break; + + case nsITextServicesDocument::eBlockNotFound: // There is no text block (TB) in or before the selection (S). + default: + NS_NOTREACHED("Shouldn't ever get this status"); + } + } + else //failed to get last sel block. Just start at beginning + { + rv = mTsDoc->FirstBlock(); + *outBlockOffset = 0; + } + + } + else // we want the first block + { + rv = mTsDoc->FirstBlock(); + mFromStart = false; + } + return rv; +} + + +// utility method to discover which block we're in. The TSDoc interface doesn't give +// us this, because it can't assume a read-only document. +// shamelessly stolen from nsTextServicesDocument +nsresult +mozSpellChecker::GetCurrentBlockIndex(nsITextServicesDocument *aDoc, int32_t *outBlockIndex) +{ + int32_t blockIndex = 0; + bool isDone = false; + nsresult result = NS_OK; + + do + { + aDoc->PrevBlock(); + + result = aDoc->IsDone(&isDone); + + if (!isDone) + blockIndex ++; + + } while (NS_SUCCEEDED(result) && !isDone); + + *outBlockIndex = blockIndex; + + return result; +} + +nsresult +mozSpellChecker::GetEngineList(nsCOMArray<mozISpellCheckingEngine>* aSpellCheckingEngines) +{ + MOZ_ASSERT(!XRE_IsContentProcess()); + + nsresult rv; + bool hasMoreEngines; + + nsCOMPtr<nsICategoryManager> catMgr = do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (!catMgr) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr<nsISimpleEnumerator> catEntries; + + // Get contract IDs of registrated external spell-check engines and + // append one of HunSpell at the end. + rv = catMgr->EnumerateCategory("spell-check-engine", getter_AddRefs(catEntries)); + if (NS_FAILED(rv)) + return rv; + + while (catEntries->HasMoreElements(&hasMoreEngines), hasMoreEngines){ + nsCOMPtr<nsISupports> elem; + rv = catEntries->GetNext(getter_AddRefs(elem)); + + nsCOMPtr<nsISupportsCString> entry = do_QueryInterface(elem, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCString contractId; + rv = entry->GetData(contractId); + if (NS_FAILED(rv)) + return rv; + + // Try to load spellchecker engine. Ignore errors silently + // except for the last one (HunSpell). + nsCOMPtr<mozISpellCheckingEngine> engine = + do_GetService(contractId.get(), &rv); + if (NS_SUCCEEDED(rv)) { + aSpellCheckingEngines->AppendObject(engine); + } + } + + // Try to load HunSpell spellchecker engine. + nsCOMPtr<mozISpellCheckingEngine> engine = + do_GetService(DEFAULT_SPELL_CHECKER, &rv); + if (NS_FAILED(rv)) { + // Fail if not succeeded to load HunSpell. Ignore errors + // for external spellcheck engines. + return rv; + } + aSpellCheckingEngines->AppendObject(engine); + + return NS_OK; +} |