michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include // for getenv michael@0: michael@0: #include "mozilla/Attributes.h" // for MOZ_FINAL michael@0: #include "mozilla/Preferences.h" // for Preferences michael@0: #include "mozilla/Services.h" // for GetXULChromeRegistryService michael@0: #include "mozilla/dom/Element.h" // for Element michael@0: #include "mozilla/mozalloc.h" // for operator delete, etc michael@0: #include "nsAString.h" // for nsAString_internal::IsEmpty, etc michael@0: #include "nsComponentManagerUtils.h" // for do_CreateInstance michael@0: #include "nsDebug.h" // for NS_ENSURE_TRUE, etc michael@0: #include "nsDependentSubstring.h" // for Substring michael@0: #include "nsEditorSpellCheck.h" michael@0: #include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc michael@0: #include "nsIChromeRegistry.h" // for nsIXULChromeRegistry michael@0: #include "nsIContent.h" // for nsIContent michael@0: #include "nsIContentPrefService.h" // for nsIContentPrefService, etc michael@0: #include "nsIContentPrefService2.h" // for nsIContentPrefService2, etc michael@0: #include "nsIDOMDocument.h" // for nsIDOMDocument michael@0: #include "nsIDOMElement.h" // for nsIDOMElement michael@0: #include "nsIDOMRange.h" // for nsIDOMRange michael@0: #include "nsIDocument.h" // for nsIDocument michael@0: #include "nsIEditor.h" // for nsIEditor michael@0: #include "nsIHTMLEditor.h" // for nsIHTMLEditor michael@0: #include "nsILoadContext.h" michael@0: #include "nsISelection.h" // for nsISelection michael@0: #include "nsISpellChecker.h" // for nsISpellChecker, etc michael@0: #include "nsISupportsBase.h" // for nsISupports michael@0: #include "nsISupportsUtils.h" // for NS_ADDREF michael@0: #include "nsITextServicesDocument.h" // for nsITextServicesDocument michael@0: #include "nsITextServicesFilter.h" // for nsITextServicesFilter michael@0: #include "nsIURI.h" // for nsIURI michael@0: #include "nsIVariant.h" // for nsIWritableVariant, etc michael@0: #include "nsLiteralString.h" // for NS_LITERAL_STRING, etc michael@0: #include "nsMemory.h" // for nsMemory michael@0: #include "nsReadableUtils.h" // for ToNewUnicode, EmptyString, etc michael@0: #include "nsServiceManagerUtils.h" // for do_GetService michael@0: #include "nsString.h" // for nsAutoString, nsString, etc michael@0: #include "nsStringFwd.h" // for nsAFlatString michael@0: #include "nsStyleUtil.h" // for nsStyleUtil michael@0: michael@0: using namespace mozilla; michael@0: michael@0: class UpdateDictionnaryHolder { michael@0: private: michael@0: nsEditorSpellCheck* mSpellCheck; michael@0: public: michael@0: UpdateDictionnaryHolder(nsEditorSpellCheck* esc): mSpellCheck(esc) { michael@0: if (mSpellCheck) { michael@0: mSpellCheck->BeginUpdateDictionary(); michael@0: } michael@0: } michael@0: ~UpdateDictionnaryHolder() { michael@0: if (mSpellCheck) { michael@0: mSpellCheck->EndUpdateDictionary(); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: #define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang") michael@0: michael@0: /** michael@0: * Gets the URI of aEditor's document. michael@0: */ michael@0: static nsresult michael@0: GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aEditor); michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: michael@0: nsCOMPtr domDoc; michael@0: aEditor->GetDocument(getter_AddRefs(domDoc)); michael@0: NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr doc = do_QueryInterface(domDoc); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr docUri = doc->GetDocumentURI(); michael@0: NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE); michael@0: michael@0: *aURI = docUri; michael@0: NS_ADDREF(*aURI); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static already_AddRefed michael@0: GetLoadContext(nsIEditor* aEditor) michael@0: { michael@0: nsCOMPtr domDoc; michael@0: aEditor->GetDocument(getter_AddRefs(domDoc)); michael@0: NS_ENSURE_TRUE(domDoc, nullptr); michael@0: michael@0: nsCOMPtr doc = do_QueryInterface(domDoc); michael@0: NS_ENSURE_TRUE(doc, nullptr); michael@0: michael@0: nsCOMPtr loadContext = doc->GetLoadContext(); michael@0: return loadContext.forget(); michael@0: } michael@0: michael@0: /** michael@0: * Fetches the dictionary stored in content prefs and maintains state during the michael@0: * fetch, which is asynchronous. michael@0: */ michael@0: class DictionaryFetcher MOZ_FINAL : public nsIContentPrefCallback2 michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: DictionaryFetcher(nsEditorSpellCheck* aSpellCheck, michael@0: nsIEditorSpellCheckCallback* aCallback, michael@0: uint32_t aGroup) michael@0: : mCallback(aCallback), mGroup(aGroup), mSpellCheck(aSpellCheck) {} michael@0: michael@0: NS_IMETHOD Fetch(nsIEditor* aEditor); michael@0: michael@0: NS_IMETHOD HandleResult(nsIContentPref* aPref) michael@0: { michael@0: nsCOMPtr value; michael@0: nsresult rv = aPref->GetValue(getter_AddRefs(value)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: value->GetAsAString(mDictionary); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD HandleCompletion(uint16_t reason) michael@0: { michael@0: mSpellCheck->DictionaryFetched(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD HandleError(nsresult error) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr mCallback; michael@0: uint32_t mGroup; michael@0: nsString mRootContentLang; michael@0: nsString mRootDocContentLang; michael@0: nsString mDictionary; michael@0: michael@0: private: michael@0: nsRefPtr mSpellCheck; michael@0: }; michael@0: NS_IMPL_ISUPPORTS(DictionaryFetcher, nsIContentPrefCallback2) michael@0: michael@0: NS_IMETHODIMP michael@0: DictionaryFetcher::Fetch(nsIEditor* aEditor) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aEditor); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr docUri; michael@0: rv = GetDocumentURI(aEditor, getter_AddRefs(docUri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString docUriSpec; michael@0: rv = docUri->GetSpec(docUriSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr contentPrefService = michael@0: do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); michael@0: NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: nsCOMPtr loadContext = GetLoadContext(aEditor); michael@0: rv = contentPrefService->GetByDomainAndName(NS_ConvertUTF8toUTF16(docUriSpec), michael@0: CPS_PREF_NAME, loadContext, michael@0: this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Stores the current dictionary for aEditor's document URL. michael@0: */ michael@0: static nsresult michael@0: StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aEditor); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr docUri; michael@0: rv = GetDocumentURI(aEditor, getter_AddRefs(docUri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString docUriSpec; michael@0: rv = docUri->GetSpec(docUriSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID); michael@0: NS_ENSURE_TRUE(prefValue, NS_ERROR_OUT_OF_MEMORY); michael@0: prefValue->SetAsAString(aDictionary); michael@0: michael@0: nsCOMPtr contentPrefService = michael@0: do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); michael@0: NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsCOMPtr loadContext = GetLoadContext(aEditor); michael@0: return contentPrefService->Set(NS_ConvertUTF8toUTF16(docUriSpec), michael@0: CPS_PREF_NAME, prefValue, loadContext, michael@0: nullptr); michael@0: } michael@0: michael@0: /** michael@0: * Forgets the current dictionary stored for aEditor's document URL. michael@0: */ michael@0: static nsresult michael@0: ClearCurrentDictionary(nsIEditor* aEditor) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aEditor); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr docUri; michael@0: rv = GetDocumentURI(aEditor, getter_AddRefs(docUri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString docUriSpec; michael@0: rv = docUri->GetSpec(docUriSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr contentPrefService = michael@0: do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); michael@0: NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsCOMPtr loadContext = GetLoadContext(aEditor); michael@0: return contentPrefService->RemoveByDomainAndName( michael@0: NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME, loadContext, nullptr); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditorSpellCheck) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditorSpellCheck) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsEditorSpellCheck) michael@0: NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck) michael@0: NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsEditorSpellCheck) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(nsEditorSpellCheck, michael@0: mEditor, michael@0: mSpellChecker, michael@0: mTxtSrvFilter) michael@0: michael@0: nsEditorSpellCheck::nsEditorSpellCheck() michael@0: : mSuggestedWordIndex(0) michael@0: , mDictionaryIndex(0) michael@0: , mEditor(nullptr) michael@0: , mDictionaryFetcherGroup(0) michael@0: , mUpdateDictionaryRunning(false) michael@0: { michael@0: } michael@0: michael@0: nsEditorSpellCheck::~nsEditorSpellCheck() michael@0: { michael@0: // Make sure we blow the spellchecker away, just in michael@0: // case it hasn't been destroyed already. michael@0: mSpellChecker = nullptr; michael@0: } michael@0: michael@0: // The problem is that if the spell checker does not exist, we can not tell michael@0: // which dictionaries are installed. This function works around the problem, michael@0: // allowing callers to ask if we can spell check without actually doing so (and michael@0: // enabling or disabling UI as necessary). This just creates a spellcheck michael@0: // object if needed and asks it for the dictionary list. michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::CanSpellCheck(bool* _retval) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr spellChecker; michael@0: if (! mSpellChecker) { michael@0: spellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: spellChecker = mSpellChecker; michael@0: } michael@0: nsTArray dictList; michael@0: rv = spellChecker->GetDictionaryList(&dictList); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *_retval = (dictList.Length() > 0); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Instances of this class can be used as either runnables or RAII helpers. michael@0: class CallbackCaller MOZ_FINAL : public nsRunnable michael@0: { michael@0: public: michael@0: explicit CallbackCaller(nsIEditorSpellCheckCallback* aCallback) michael@0: : mCallback(aCallback) {} michael@0: michael@0: ~CallbackCaller() michael@0: { michael@0: Run(); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mCallback) { michael@0: mCallback->EditorSpellCheckDone(); michael@0: mCallback = nullptr; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mCallback; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, bool aEnableSelectionChecking, nsIEditorSpellCheckCallback* aCallback) michael@0: { michael@0: NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER); michael@0: mEditor = aEditor; michael@0: michael@0: nsresult rv; michael@0: michael@0: // We can spell check with any editor type michael@0: nsCOMPtrtsDoc = michael@0: do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ENSURE_TRUE(tsDoc, NS_ERROR_NULL_POINTER); michael@0: michael@0: tsDoc->SetFilter(mTxtSrvFilter); michael@0: michael@0: // Pass the editor to the text services document michael@0: rv = tsDoc->InitWithEditor(aEditor); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (aEnableSelectionChecking) { michael@0: // Find out if the section is collapsed or not. michael@0: // If it isn't, we want to spellcheck just the selection. michael@0: michael@0: nsCOMPtr selection; michael@0: michael@0: rv = aEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); michael@0: michael@0: int32_t count = 0; michael@0: michael@0: rv = selection->GetRangeCount(&count); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (count > 0) { michael@0: nsCOMPtr range; michael@0: michael@0: rv = selection->GetRangeAt(0, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool collapsed = false; michael@0: rv = range->GetCollapsed(&collapsed); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!collapsed) { michael@0: // We don't want to touch the range in the selection, michael@0: // so create a new copy of it. michael@0: michael@0: nsCOMPtr rangeBounds; michael@0: rv = range->CloneRange(getter_AddRefs(rangeBounds)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(rangeBounds, NS_ERROR_FAILURE); michael@0: michael@0: // Make sure the new range spans complete words. michael@0: michael@0: rv = tsDoc->ExpandRangeToWordBoundaries(rangeBounds); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Now tell the text services that you only want michael@0: // to iterate over the text in this range. michael@0: michael@0: rv = tsDoc->SetExtent(rangeBounds); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: } michael@0: michael@0: mSpellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER); michael@0: michael@0: rv = mSpellChecker->SetDocument(tsDoc, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // do not fail if UpdateCurrentDictionary fails because this method may michael@0: // succeed later. michael@0: rv = UpdateCurrentDictionary(aCallback); michael@0: if (NS_FAILED(rv) && aCallback) { michael@0: // However, if it does fail, we still need to call the callback since we michael@0: // discard the failure. Do it asynchronously so that the caller is always michael@0: // guaranteed async behavior. michael@0: nsRefPtr caller = new CallbackCaller(aCallback); michael@0: NS_ENSURE_STATE(caller); michael@0: rv = NS_DispatchToMainThread(caller); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::GetNextMisspelledWord(char16_t **aNextMisspelledWord) michael@0: { michael@0: NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsAutoString nextMisspelledWord; michael@0: michael@0: DeleteSuggestedWordList(); michael@0: // Beware! This may flush notifications via synchronous michael@0: // ScrollSelectionIntoView. michael@0: nsresult rv = mSpellChecker->NextMisspelledWord(nextMisspelledWord, michael@0: &mSuggestedWordList); michael@0: michael@0: *aNextMisspelledWord = ToNewUnicode(nextMisspelledWord); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::GetSuggestedWord(char16_t **aSuggestedWord) michael@0: { michael@0: nsAutoString word; michael@0: if ( mSuggestedWordIndex < int32_t(mSuggestedWordList.Length())) michael@0: { michael@0: *aSuggestedWord = ToNewUnicode(mSuggestedWordList[mSuggestedWordIndex]); michael@0: mSuggestedWordIndex++; michael@0: } else { michael@0: // A blank string signals that there are no more strings michael@0: *aSuggestedWord = ToNewUnicode(EmptyString()); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::CheckCurrentWord(const char16_t *aSuggestedWord, michael@0: bool *aIsMisspelled) michael@0: { michael@0: NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: DeleteSuggestedWordList(); michael@0: return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord), michael@0: aIsMisspelled, &mSuggestedWordList); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::CheckCurrentWordNoSuggest(const char16_t *aSuggestedWord, michael@0: bool *aIsMisspelled) michael@0: { michael@0: NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord), michael@0: aIsMisspelled, nullptr); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::ReplaceWord(const char16_t *aMisspelledWord, michael@0: const char16_t *aReplaceWord, michael@0: bool allOccurrences) michael@0: { michael@0: NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: return mSpellChecker->Replace(nsDependentString(aMisspelledWord), michael@0: nsDependentString(aReplaceWord), allOccurrences); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::IgnoreWordAllOccurrences(const char16_t *aWord) michael@0: { michael@0: NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: return mSpellChecker->IgnoreAll(nsDependentString(aWord)); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::GetPersonalDictionary() michael@0: { michael@0: NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // We can spell check with any editor type michael@0: mDictionaryList.Clear(); michael@0: mDictionaryIndex = 0; michael@0: return mSpellChecker->GetPersonalDictionary(&mDictionaryList); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::GetPersonalDictionaryWord(char16_t **aDictionaryWord) michael@0: { michael@0: if ( mDictionaryIndex < int32_t( mDictionaryList.Length())) michael@0: { michael@0: *aDictionaryWord = ToNewUnicode(mDictionaryList[mDictionaryIndex]); michael@0: mDictionaryIndex++; michael@0: } else { michael@0: // A blank string signals that there are no more strings michael@0: *aDictionaryWord = ToNewUnicode(EmptyString()); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::AddWordToDictionary(const char16_t *aWord) michael@0: { michael@0: NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: return mSpellChecker->AddWordToPersonalDictionary(nsDependentString(aWord)); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::RemoveWordFromDictionary(const char16_t *aWord) michael@0: { michael@0: NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: return mSpellChecker->RemoveWordFromPersonalDictionary(nsDependentString(aWord)); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::GetDictionaryList(char16_t ***aDictionaryList, uint32_t *aCount) michael@0: { michael@0: NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: NS_ENSURE_TRUE(aDictionaryList && aCount, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aDictionaryList = 0; michael@0: *aCount = 0; michael@0: michael@0: nsTArray dictList; michael@0: michael@0: nsresult rv = mSpellChecker->GetDictionaryList(&dictList); michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: char16_t **tmpPtr = 0; michael@0: michael@0: if (dictList.Length() < 1) michael@0: { michael@0: // If there are no dictionaries, return an array containing michael@0: // one element and a count of one. michael@0: michael@0: tmpPtr = (char16_t **)nsMemory::Alloc(sizeof(char16_t *)); michael@0: michael@0: NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: *tmpPtr = 0; michael@0: *aDictionaryList = tmpPtr; michael@0: *aCount = 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: tmpPtr = (char16_t **)nsMemory::Alloc(sizeof(char16_t *) * dictList.Length()); michael@0: michael@0: NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: *aDictionaryList = tmpPtr; michael@0: *aCount = dictList.Length(); michael@0: michael@0: uint32_t i; michael@0: michael@0: for (i = 0; i < *aCount; i++) michael@0: { michael@0: tmpPtr[i] = ToNewUnicode(dictList[i]); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::GetCurrentDictionary(nsAString& aDictionary) michael@0: { michael@0: NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: return mSpellChecker->GetCurrentDictionary(aDictionary); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary) michael@0: { michael@0: NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsRefPtr kungFuDeathGrip = this; michael@0: michael@0: // The purpose of mUpdateDictionaryRunning is to avoid doing all of this if michael@0: // UpdateCurrentDictionary's helper method DictionaryFetched, which calls us, michael@0: // is on the stack. michael@0: if (!mUpdateDictionaryRunning) { michael@0: michael@0: // Ignore pending dictionary fetchers by increasing this number. michael@0: mDictionaryFetcherGroup++; michael@0: michael@0: nsDefaultStringComparator comparator; michael@0: nsAutoString langCode; michael@0: int32_t dashIdx = aDictionary.FindChar('-'); michael@0: if (dashIdx != -1) { michael@0: langCode.Assign(Substring(aDictionary, 0, dashIdx)); michael@0: } else { michael@0: langCode.Assign(aDictionary); michael@0: } michael@0: michael@0: if (mPreferredLang.IsEmpty() || !nsStyleUtil::DashMatchCompare(mPreferredLang, langCode, comparator)) { michael@0: // When user sets dictionary manually, we store this value associated michael@0: // with editor url. michael@0: StoreCurrentDictionary(mEditor, aDictionary); michael@0: } else { michael@0: // If user sets a dictionary matching (even partially), lang defined by michael@0: // document, we consider content pref has been canceled, and we clear it. michael@0: ClearCurrentDictionary(mEditor); michael@0: } michael@0: michael@0: // Also store it in as a preference. It will be used as a default value michael@0: // when everything else fails. michael@0: Preferences::SetString("spellchecker.dictionary", aDictionary); michael@0: } michael@0: return mSpellChecker->SetCurrentDictionary(aDictionary); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::CheckCurrentDictionary() michael@0: { michael@0: mSpellChecker->CheckCurrentDictionary(); michael@0: michael@0: // Check if our current dictionary is still available. michael@0: nsAutoString currentDictionary; michael@0: nsresult rv = GetCurrentDictionary(currentDictionary); michael@0: if (NS_SUCCEEDED(rv) && !currentDictionary.IsEmpty()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If our preferred current dictionary has gone, pick another one. michael@0: nsTArray dictList; michael@0: rv = mSpellChecker->GetDictionaryList(&dictList); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (dictList.Length() > 0) { michael@0: rv = SetCurrentDictionary(dictList[0]); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::UninitSpellChecker() michael@0: { michael@0: NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // Cleanup - kill the spell checker michael@0: DeleteSuggestedWordList(); michael@0: mDictionaryList.Clear(); michael@0: mDictionaryIndex = 0; michael@0: mSpellChecker = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /* void setFilter (in nsITextServicesFilter filter); */ michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::SetFilter(nsITextServicesFilter *filter) michael@0: { michael@0: mTxtSrvFilter = filter; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsEditorSpellCheck::DeleteSuggestedWordList() michael@0: { michael@0: mSuggestedWordList.Clear(); michael@0: mSuggestedWordIndex = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallback) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsRefPtr kungFuDeathGrip = this; michael@0: michael@0: // Get language with html5 algorithm michael@0: nsCOMPtr rootContent; michael@0: nsCOMPtr htmlEditor = do_QueryInterface(mEditor); michael@0: if (htmlEditor) { michael@0: rootContent = htmlEditor->GetActiveEditingHost(); michael@0: } else { michael@0: nsCOMPtr rootElement; michael@0: rv = mEditor->GetRootElement(getter_AddRefs(rootElement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rootContent = do_QueryInterface(rootElement); michael@0: } michael@0: NS_ENSURE_TRUE(rootContent, NS_ERROR_FAILURE); michael@0: michael@0: DictionaryFetcher* fetcher = new DictionaryFetcher(this, aCallback, michael@0: mDictionaryFetcherGroup); michael@0: rootContent->GetLang(fetcher->mRootContentLang); michael@0: nsCOMPtr doc = rootContent->GetCurrentDoc(); michael@0: NS_ENSURE_STATE(doc); michael@0: doc->GetContentLanguage(fetcher->mRootDocContentLang); michael@0: michael@0: rv = fetcher->Fetch(mEditor); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher) michael@0: { michael@0: nsRefPtr kungFuDeathGrip = this; michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: // Important: declare the holder after the callback caller so that the former michael@0: // is destructed first so that it's not active when the callback is called. michael@0: CallbackCaller callbackCaller(aFetcher->mCallback); michael@0: UpdateDictionnaryHolder holder(this); michael@0: michael@0: if (aFetcher->mGroup < mDictionaryFetcherGroup) { michael@0: // SetCurrentDictionary was called after the fetch started. Don't overwrite michael@0: // that dictionary with the fetched one. michael@0: return NS_OK; michael@0: } michael@0: michael@0: mPreferredLang.Assign(aFetcher->mRootContentLang); michael@0: michael@0: // If we successfully fetched a dictionary from content prefs, do not go michael@0: // further. Use this exact dictionary. michael@0: nsAutoString dictName; michael@0: dictName.Assign(aFetcher->mDictionary); michael@0: if (!dictName.IsEmpty()) { michael@0: if (NS_FAILED(SetCurrentDictionary(dictName))) { michael@0: // may be dictionary was uninstalled ? michael@0: ClearCurrentDictionary(mEditor); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mPreferredLang.IsEmpty()) { michael@0: mPreferredLang.Assign(aFetcher->mRootDocContentLang); michael@0: } michael@0: michael@0: // Then, try to use language computed from element michael@0: if (!mPreferredLang.IsEmpty()) { michael@0: dictName.Assign(mPreferredLang); michael@0: } michael@0: michael@0: // otherwise, get language from preferences michael@0: nsAutoString preferedDict(Preferences::GetLocalizedString("spellchecker.dictionary")); michael@0: if (dictName.IsEmpty()) { michael@0: dictName.Assign(preferedDict); michael@0: } michael@0: michael@0: if (dictName.IsEmpty()) michael@0: { michael@0: // Prefs didn't give us a dictionary name, so just get the current michael@0: // locale and use that as the default dictionary name! michael@0: michael@0: nsCOMPtr packageRegistry = michael@0: mozilla::services::GetXULChromeRegistryService(); michael@0: michael@0: if (packageRegistry) { michael@0: nsAutoCString utf8DictName; michael@0: rv = packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global"), michael@0: utf8DictName); michael@0: AppendUTF8toUTF16(utf8DictName, dictName); michael@0: } michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv) && !dictName.IsEmpty()) { michael@0: rv = SetCurrentDictionary(dictName); michael@0: if (NS_FAILED(rv)) { michael@0: // required dictionary was not available. Try to get a dictionary michael@0: // matching at least language part of dictName: michael@0: michael@0: nsAutoString langCode; michael@0: int32_t dashIdx = dictName.FindChar('-'); michael@0: if (dashIdx != -1) { michael@0: langCode.Assign(Substring(dictName, 0, dashIdx)); michael@0: } else { michael@0: langCode.Assign(dictName); michael@0: } michael@0: michael@0: nsDefaultStringComparator comparator; michael@0: michael@0: // try dictionary.spellchecker preference if it starts with langCode (and michael@0: // if we haven't tried it already) michael@0: if (!preferedDict.IsEmpty() && !dictName.Equals(preferedDict) && michael@0: nsStyleUtil::DashMatchCompare(preferedDict, langCode, comparator)) { michael@0: rv = SetCurrentDictionary(preferedDict); michael@0: } michael@0: michael@0: // Otherwise, try langCode (if we haven't tried it already) michael@0: if (NS_FAILED(rv)) { michael@0: if (!dictName.Equals(langCode) && !preferedDict.Equals(langCode)) { michael@0: rv = SetCurrentDictionary(langCode); michael@0: } michael@0: } michael@0: michael@0: // Otherwise, try any available dictionary aa-XX michael@0: if (NS_FAILED(rv)) { michael@0: // loop over avaible dictionaries; if we find one with required michael@0: // language, use it michael@0: nsTArray dictList; michael@0: rv = mSpellChecker->GetDictionaryList(&dictList); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: int32_t i, count = dictList.Length(); michael@0: for (i = 0; i < count; i++) { michael@0: nsAutoString dictStr(dictList.ElementAt(i)); michael@0: michael@0: if (dictStr.Equals(dictName) || michael@0: dictStr.Equals(preferedDict) || michael@0: dictStr.Equals(langCode)) { michael@0: // We have already tried it michael@0: continue; michael@0: } michael@0: michael@0: if (nsStyleUtil::DashMatchCompare(dictStr, langCode, comparator) && michael@0: NS_SUCCEEDED(SetCurrentDictionary(dictStr))) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If we have not set dictionary, and the editable element doesn't have a michael@0: // lang attribute, we try to get a dictionary. First try LANG environment variable, michael@0: // then en-US. If it does not work, pick the first one. michael@0: if (mPreferredLang.IsEmpty()) { michael@0: nsAutoString currentDictionary; michael@0: rv = GetCurrentDictionary(currentDictionary); michael@0: if (NS_FAILED(rv) || currentDictionary.IsEmpty()) { michael@0: // Try to get current dictionary from environment variable LANG michael@0: char* env_lang = getenv("LANG"); michael@0: if (env_lang != nullptr) { michael@0: nsString lang = NS_ConvertUTF8toUTF16(env_lang); michael@0: // Strip trailing charset if there is any michael@0: int32_t dot_pos = lang.FindChar('.'); michael@0: if (dot_pos != -1) { michael@0: lang = Substring(lang, 0, dot_pos - 1); michael@0: } michael@0: rv = SetCurrentDictionary(lang); michael@0: } michael@0: if (NS_FAILED(rv)) { michael@0: rv = SetCurrentDictionary(NS_LITERAL_STRING("en-US")); michael@0: if (NS_FAILED(rv)) { michael@0: nsTArray dictList; michael@0: rv = mSpellChecker->GetDictionaryList(&dictList); michael@0: if (NS_SUCCEEDED(rv) && dictList.Length() > 0) { michael@0: SetCurrentDictionary(dictList[0]); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If an error was thrown while setting the dictionary, just michael@0: // fail silently so that the spellchecker dialog is allowed to come michael@0: // up. The user can manually reset the language to their choice on michael@0: // the dialog if it is wrong. michael@0: michael@0: DeleteSuggestedWordList(); michael@0: michael@0: return NS_OK; michael@0: }