summaryrefslogtreecommitdiffstats
path: root/extensions/spellcheck/src/mozSpellChecker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/spellcheck/src/mozSpellChecker.cpp')
-rw-r--r--extensions/spellcheck/src/mozSpellChecker.cpp565
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;
+}