/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 // for getenv #include "mozilla/Attributes.h" // for final #include "mozilla/Preferences.h" // for Preferences #include "mozilla/Services.h" // for GetXULChromeRegistryService #include "mozilla/dom/Element.h" // for Element #include "mozilla/dom/Selection.h" #include "mozilla/mozalloc.h" // for operator delete, etc #include "nsAString.h" // for nsAString_internal::IsEmpty, etc #include "nsComponentManagerUtils.h" // for do_CreateInstance #include "nsDebug.h" // for NS_ENSURE_TRUE, etc #include "nsDependentSubstring.h" // for Substring #include "nsEditorSpellCheck.h" #include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc #include "nsIChromeRegistry.h" // for nsIXULChromeRegistry #include "nsIContent.h" // for nsIContent #include "nsIContentPrefService.h" // for nsIContentPrefService, etc #include "nsIContentPrefService2.h" // for nsIContentPrefService2, etc #include "nsIDOMDocument.h" // for nsIDOMDocument #include "nsIDOMElement.h" // for nsIDOMElement #include "nsIDocument.h" // for nsIDocument #include "nsIEditor.h" // for nsIEditor #include "nsIHTMLEditor.h" // for nsIHTMLEditor #include "nsILoadContext.h" #include "nsISelection.h" // for nsISelection #include "nsISpellChecker.h" // for nsISpellChecker, etc #include "nsISupportsBase.h" // for nsISupports #include "nsISupportsUtils.h" // for NS_ADDREF #include "nsITextServicesDocument.h" // for nsITextServicesDocument #include "nsITextServicesFilter.h" // for nsITextServicesFilter #include "nsIURI.h" // for nsIURI #include "nsVariant.h" // for nsIWritableVariant, etc #include "nsLiteralString.h" // for NS_LITERAL_STRING, etc #include "nsMemory.h" // for nsMemory #include "nsRange.h" #include "nsReadableUtils.h" // for ToNewUnicode, EmptyString, etc #include "nsServiceManagerUtils.h" // for do_GetService #include "nsString.h" // for nsAutoString, nsString, etc #include "nsStringFwd.h" // for nsAFlatString #include "nsStyleUtil.h" // for nsStyleUtil #include "nsXULAppAPI.h" // for XRE_GetProcessType #include "nsIPlaintextEditor.h" // for editor flags using namespace mozilla; using namespace mozilla::dom; class UpdateDictionaryHolder { private: nsEditorSpellCheck* mSpellCheck; public: explicit UpdateDictionaryHolder(nsEditorSpellCheck* esc): mSpellCheck(esc) { if (mSpellCheck) { mSpellCheck->BeginUpdateDictionary(); } } ~UpdateDictionaryHolder() { if (mSpellCheck) { mSpellCheck->EndUpdateDictionary(); } } }; #define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang") /** * Gets the URI of aEditor's document. */ static nsresult GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI) { NS_ENSURE_ARG_POINTER(aEditor); NS_ENSURE_ARG_POINTER(aURI); nsCOMPtr domDoc; aEditor->GetDocument(getter_AddRefs(domDoc)); NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE); nsCOMPtr doc = do_QueryInterface(domDoc); NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); nsCOMPtr docUri = doc->GetDocumentURI(); NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE); *aURI = docUri; NS_ADDREF(*aURI); return NS_OK; } static already_AddRefed GetLoadContext(nsIEditor* aEditor) { nsCOMPtr domDoc; aEditor->GetDocument(getter_AddRefs(domDoc)); NS_ENSURE_TRUE(domDoc, nullptr); nsCOMPtr doc = do_QueryInterface(domDoc); NS_ENSURE_TRUE(doc, nullptr); nsCOMPtr loadContext = doc->GetLoadContext(); return loadContext.forget(); } /** * Fetches the dictionary stored in content prefs and maintains state during the * fetch, which is asynchronous. */ class DictionaryFetcher final : public nsIContentPrefCallback2 { public: NS_DECL_ISUPPORTS DictionaryFetcher(nsEditorSpellCheck* aSpellCheck, nsIEditorSpellCheckCallback* aCallback, uint32_t aGroup) : mCallback(aCallback), mGroup(aGroup), mSpellCheck(aSpellCheck) {} NS_IMETHOD Fetch(nsIEditor* aEditor); NS_IMETHOD HandleResult(nsIContentPref* aPref) override { nsCOMPtr value; nsresult rv = aPref->GetValue(getter_AddRefs(value)); NS_ENSURE_SUCCESS(rv, rv); value->GetAsAString(mDictionary); return NS_OK; } NS_IMETHOD HandleCompletion(uint16_t reason) override { mSpellCheck->DictionaryFetched(this); return NS_OK; } NS_IMETHOD HandleError(nsresult error) override { return NS_OK; } nsCOMPtr mCallback; uint32_t mGroup; nsString mRootContentLang; nsString mRootDocContentLang; nsString mDictionary; private: ~DictionaryFetcher() {} RefPtr mSpellCheck; }; NS_IMPL_ISUPPORTS(DictionaryFetcher, nsIContentPrefCallback2) NS_IMETHODIMP DictionaryFetcher::Fetch(nsIEditor* aEditor) { NS_ENSURE_ARG_POINTER(aEditor); nsresult rv; nsCOMPtr docUri; rv = GetDocumentURI(aEditor, getter_AddRefs(docUri)); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString docUriSpec; rv = docUri->GetSpec(docUriSpec); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr contentPrefService = do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE); nsCOMPtr loadContext = GetLoadContext(aEditor); rv = contentPrefService->GetByDomainAndName(NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME, loadContext, this); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } /** * Stores the current dictionary for aEditor's document URL. */ static nsresult StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary) { NS_ENSURE_ARG_POINTER(aEditor); nsresult rv; nsCOMPtr docUri; rv = GetDocumentURI(aEditor, getter_AddRefs(docUri)); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString docUriSpec; rv = docUri->GetSpec(docUriSpec); NS_ENSURE_SUCCESS(rv, rv); RefPtr prefValue = new nsVariant(); prefValue->SetAsAString(aDictionary); nsCOMPtr contentPrefService = do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED); nsCOMPtr loadContext = GetLoadContext(aEditor); return contentPrefService->Set(NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME, prefValue, loadContext, nullptr); } /** * Forgets the current dictionary stored for aEditor's document URL. */ static nsresult ClearCurrentDictionary(nsIEditor* aEditor) { NS_ENSURE_ARG_POINTER(aEditor); nsresult rv; nsCOMPtr docUri; rv = GetDocumentURI(aEditor, getter_AddRefs(docUri)); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString docUriSpec; rv = docUri->GetSpec(docUriSpec); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr contentPrefService = do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED); nsCOMPtr loadContext = GetLoadContext(aEditor); return contentPrefService->RemoveByDomainAndName( NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME, loadContext, nullptr); } NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditorSpellCheck) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditorSpellCheck) NS_INTERFACE_MAP_BEGIN(nsEditorSpellCheck) NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck) NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsEditorSpellCheck) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION(nsEditorSpellCheck, mEditor, mSpellChecker, mTxtSrvFilter) nsEditorSpellCheck::nsEditorSpellCheck() : mSuggestedWordIndex(0) , mDictionaryIndex(0) , mEditor(nullptr) , mDictionaryFetcherGroup(0) , mUpdateDictionaryRunning(false) { } nsEditorSpellCheck::~nsEditorSpellCheck() { // Make sure we blow the spellchecker away, just in // case it hasn't been destroyed already. mSpellChecker = nullptr; } // The problem is that if the spell checker does not exist, we can not tell // which dictionaries are installed. This function works around the problem, // allowing callers to ask if we can spell check without actually doing so (and // enabling or disabling UI as necessary). This just creates a spellcheck // object if needed and asks it for the dictionary list. NS_IMETHODIMP nsEditorSpellCheck::CanSpellCheck(bool* _retval) { nsresult rv; nsCOMPtr spellChecker; if (! mSpellChecker) { spellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); } else { spellChecker = mSpellChecker; } nsTArray dictList; rv = spellChecker->GetDictionaryList(&dictList); NS_ENSURE_SUCCESS(rv, rv); *_retval = (dictList.Length() > 0); return NS_OK; } // Instances of this class can be used as either runnables or RAII helpers. class CallbackCaller final : public Runnable { public: explicit CallbackCaller(nsIEditorSpellCheckCallback* aCallback) : mCallback(aCallback) {} ~CallbackCaller() { Run(); } NS_IMETHOD Run() override { if (mCallback) { mCallback->EditorSpellCheckDone(); mCallback = nullptr; } return NS_OK; } private: nsCOMPtr mCallback; }; NS_IMETHODIMP nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, bool aEnableSelectionChecking, nsIEditorSpellCheckCallback* aCallback) { NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER); mEditor = aEditor; nsresult rv; // We can spell check with any editor type nsCOMPtrtsDoc = do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &rv); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(tsDoc, NS_ERROR_NULL_POINTER); tsDoc->SetFilter(mTxtSrvFilter); // Pass the editor to the text services document rv = tsDoc->InitWithEditor(aEditor); NS_ENSURE_SUCCESS(rv, rv); if (aEnableSelectionChecking) { // Find out if the section is collapsed or not. // If it isn't, we want to spellcheck just the selection. nsCOMPtr domSelection; aEditor->GetSelection(getter_AddRefs(domSelection)); if (NS_WARN_IF(!domSelection)) { return NS_ERROR_FAILURE; } RefPtr selection = domSelection->AsSelection(); int32_t count = 0; rv = selection->GetRangeCount(&count); NS_ENSURE_SUCCESS(rv, rv); if (count > 0) { RefPtr range = selection->GetRangeAt(0); NS_ENSURE_STATE(range); bool collapsed = false; rv = range->GetCollapsed(&collapsed); NS_ENSURE_SUCCESS(rv, rv); if (!collapsed) { // We don't want to touch the range in the selection, // so create a new copy of it. RefPtr rangeBounds = range->CloneRange(); // Make sure the new range spans complete words. rv = tsDoc->ExpandRangeToWordBoundaries(rangeBounds); NS_ENSURE_SUCCESS(rv, rv); // Now tell the text services that you only want // to iterate over the text in this range. rv = tsDoc->SetExtent(rangeBounds); NS_ENSURE_SUCCESS(rv, rv); } } } mSpellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER); rv = mSpellChecker->SetDocument(tsDoc, true); NS_ENSURE_SUCCESS(rv, rv); // do not fail if UpdateCurrentDictionary fails because this method may // succeed later. rv = UpdateCurrentDictionary(aCallback); if (NS_FAILED(rv) && aCallback) { // However, if it does fail, we still need to call the callback since we // discard the failure. Do it asynchronously so that the caller is always // guaranteed async behavior. RefPtr caller = new CallbackCaller(aCallback); NS_ENSURE_STATE(caller); rv = NS_DispatchToMainThread(caller); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } NS_IMETHODIMP nsEditorSpellCheck::GetNextMisspelledWord(char16_t **aNextMisspelledWord) { NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); nsAutoString nextMisspelledWord; DeleteSuggestedWordList(); // Beware! This may flush notifications via synchronous // ScrollSelectionIntoView. nsresult rv = mSpellChecker->NextMisspelledWord(nextMisspelledWord, &mSuggestedWordList); *aNextMisspelledWord = ToNewUnicode(nextMisspelledWord); return rv; } NS_IMETHODIMP nsEditorSpellCheck::GetSuggestedWord(char16_t **aSuggestedWord) { nsAutoString word; // XXX This is buggy if mSuggestedWordList.Length() is over INT32_MAX. if (mSuggestedWordIndex < static_cast(mSuggestedWordList.Length())) { *aSuggestedWord = ToNewUnicode(mSuggestedWordList[mSuggestedWordIndex]); mSuggestedWordIndex++; } else { // A blank string signals that there are no more strings *aSuggestedWord = ToNewUnicode(EmptyString()); } return NS_OK; } NS_IMETHODIMP nsEditorSpellCheck::CheckCurrentWord(const char16_t *aSuggestedWord, bool *aIsMisspelled) { NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); DeleteSuggestedWordList(); return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord), aIsMisspelled, &mSuggestedWordList); } NS_IMETHODIMP nsEditorSpellCheck::CheckCurrentWordNoSuggest(const char16_t *aSuggestedWord, bool *aIsMisspelled) { NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord), aIsMisspelled, nullptr); } NS_IMETHODIMP nsEditorSpellCheck::ReplaceWord(const char16_t *aMisspelledWord, const char16_t *aReplaceWord, bool allOccurrences) { NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); return mSpellChecker->Replace(nsDependentString(aMisspelledWord), nsDependentString(aReplaceWord), allOccurrences); } NS_IMETHODIMP nsEditorSpellCheck::IgnoreWordAllOccurrences(const char16_t *aWord) { NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); return mSpellChecker->IgnoreAll(nsDependentString(aWord)); } NS_IMETHODIMP nsEditorSpellCheck::GetPersonalDictionary() { NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); // We can spell check with any editor type mDictionaryList.Clear(); mDictionaryIndex = 0; return mSpellChecker->GetPersonalDictionary(&mDictionaryList); } NS_IMETHODIMP nsEditorSpellCheck::GetPersonalDictionaryWord(char16_t **aDictionaryWord) { // XXX This is buggy if mDictionaryList.Length() is over INT32_MAX. if (mDictionaryIndex < static_cast(mDictionaryList.Length())) { *aDictionaryWord = ToNewUnicode(mDictionaryList[mDictionaryIndex]); mDictionaryIndex++; } else { // A blank string signals that there are no more strings *aDictionaryWord = ToNewUnicode(EmptyString()); } return NS_OK; } NS_IMETHODIMP nsEditorSpellCheck::AddWordToDictionary(const char16_t *aWord) { NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); return mSpellChecker->AddWordToPersonalDictionary(nsDependentString(aWord)); } NS_IMETHODIMP nsEditorSpellCheck::RemoveWordFromDictionary(const char16_t *aWord) { NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); return mSpellChecker->RemoveWordFromPersonalDictionary(nsDependentString(aWord)); } NS_IMETHODIMP nsEditorSpellCheck::GetDictionaryList(char16_t ***aDictionaryList, uint32_t *aCount) { NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); NS_ENSURE_TRUE(aDictionaryList && aCount, NS_ERROR_NULL_POINTER); *aDictionaryList = 0; *aCount = 0; nsTArray dictList; nsresult rv = mSpellChecker->GetDictionaryList(&dictList); NS_ENSURE_SUCCESS(rv, rv); char16_t **tmpPtr = 0; if (dictList.IsEmpty()) { // If there are no dictionaries, return an array containing // one element and a count of one. tmpPtr = (char16_t **)moz_xmalloc(sizeof(char16_t *)); NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY); *tmpPtr = 0; *aDictionaryList = tmpPtr; *aCount = 0; return NS_OK; } tmpPtr = (char16_t **)moz_xmalloc(sizeof(char16_t *) * dictList.Length()); NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY); *aDictionaryList = tmpPtr; *aCount = dictList.Length(); for (uint32_t i = 0; i < *aCount; i++) { tmpPtr[i] = ToNewUnicode(dictList[i]); } return rv; } NS_IMETHODIMP nsEditorSpellCheck::GetCurrentDictionary(nsAString& aDictionary) { NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); return mSpellChecker->GetCurrentDictionary(aDictionary); } NS_IMETHODIMP nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary) { NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); RefPtr kungFuDeathGrip = this; // The purpose of mUpdateDictionaryRunning is to avoid doing all of this if // UpdateCurrentDictionary's helper method DictionaryFetched, which calls us, // is on the stack. In other words: Only do this, if the user manually selected a // dictionary to use. if (!mUpdateDictionaryRunning) { // Ignore pending dictionary fetchers by increasing this number. mDictionaryFetcherGroup++; if (mPreferredLang.IsEmpty() || !mPreferredLang.Equals(aDictionary, nsCaseInsensitiveStringComparator())) { // When user sets dictionary manually, we store this value associated // with editor url, if it doesn't match the document language exactly. // For example on "en" sites, we need to store "en-GB", otherwise // the language might jump back to en-US although the user explicitly // chose otherwise. StoreCurrentDictionary(mEditor, aDictionary); } else { // If user sets a dictionary matching the language defined by // document, we consider content pref has been canceled, and we clear it. ClearCurrentDictionary(mEditor); } } return mSpellChecker->SetCurrentDictionary(aDictionary); } NS_IMETHODIMP nsEditorSpellCheck::UninitSpellChecker() { NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED); // Cleanup - kill the spell checker DeleteSuggestedWordList(); mDictionaryList.Clear(); mDictionaryIndex = 0; mSpellChecker = nullptr; return NS_OK; } NS_IMETHODIMP nsEditorSpellCheck::SetFilter(nsITextServicesFilter *filter) { mTxtSrvFilter = filter; return NS_OK; } nsresult nsEditorSpellCheck::DeleteSuggestedWordList() { mSuggestedWordList.Clear(); mSuggestedWordIndex = 0; return NS_OK; } NS_IMETHODIMP nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallback) { if (NS_WARN_IF(!mSpellChecker)) { return NS_ERROR_NOT_INITIALIZED; } nsresult rv; RefPtr kungFuDeathGrip = this; // Get language with html5 algorithm nsCOMPtr rootContent; nsCOMPtr htmlEditor = do_QueryInterface(mEditor); if (htmlEditor) { rootContent = htmlEditor->GetActiveEditingHost(); } else { nsCOMPtr rootElement; rv = mEditor->GetRootElement(getter_AddRefs(rootElement)); NS_ENSURE_SUCCESS(rv, rv); rootContent = do_QueryInterface(rootElement); } // Try to get topmost document's document element for embedded mail editor. uint32_t flags = 0; mEditor->GetFlags(&flags); if (flags & nsIPlaintextEditor::eEditorMailMask) { nsCOMPtr ownerDoc = rootContent->OwnerDoc(); NS_ENSURE_TRUE(ownerDoc, NS_ERROR_FAILURE); nsIDocument* parentDoc = ownerDoc->GetParentDocument(); if (parentDoc) { rootContent = do_QueryInterface(parentDoc->GetDocumentElement()); } } if (!rootContent) { return NS_ERROR_FAILURE; } RefPtr fetcher = new DictionaryFetcher(this, aCallback, mDictionaryFetcherGroup); rootContent->GetLang(fetcher->mRootContentLang); nsCOMPtr doc = rootContent->GetUncomposedDoc(); NS_ENSURE_STATE(doc); doc->GetContentLanguage(fetcher->mRootDocContentLang); rv = fetcher->Fetch(mEditor); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } // Helper function that iterates over the list of dictionaries and sets the one // that matches based on a given comparison type. nsresult nsEditorSpellCheck::TryDictionary(const nsAString& aDictName, nsTArray& aDictList, enum dictCompare aCompareType) { nsresult rv = NS_ERROR_NOT_AVAILABLE; for (uint32_t i = 0; i < aDictList.Length(); i++) { nsAutoString dictStr(aDictList.ElementAt(i)); bool equals = false; switch (aCompareType) { case DICT_NORMAL_COMPARE: equals = aDictName.Equals(dictStr); break; case DICT_COMPARE_CASE_INSENSITIVE: equals = aDictName.Equals(dictStr, nsCaseInsensitiveStringComparator()); break; case DICT_COMPARE_DASHMATCH: equals = nsStyleUtil::DashMatchCompare(dictStr, aDictName, nsCaseInsensitiveStringComparator()); break; } if (equals) { rv = mSpellChecker->SetCurrentDictionary(dictStr); #ifdef DEBUG_DICT if (NS_SUCCEEDED(rv)) printf("***** Set |%s|.\n", NS_ConvertUTF16toUTF8(dictStr).get()); #endif // We always break here. We tried to set the dictionary to an existing // dictionary from the list. This must work, if it doesn't, there is // no point trying another one. break; } } return rv; } nsresult nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher) { MOZ_ASSERT(aFetcher); RefPtr kungFuDeathGrip = this; // Important: declare the holder after the callback caller so that the former // is destructed first so that it's not active when the callback is called. CallbackCaller callbackCaller(aFetcher->mCallback); UpdateDictionaryHolder holder(this); if (aFetcher->mGroup < mDictionaryFetcherGroup) { // SetCurrentDictionary was called after the fetch started. Don't overwrite // that dictionary with the fetched one. return NS_OK; } /* * We try to derive the dictionary to use based on the following priorities: * 1) Content preference: the language the user set for the site before. * 2) The value of "spellchecker.dictionary.override" which reflects a * global choice of language explicitly set by the user. * 3) Language set by the website, or any other dictionary that partly matches that. * Eg. if the website is "en-GB", a user who only has "en-US" will get that. * If the website is generic "en", the user will get one of the "en-*" installed, * pretty much at random. * 4) The user's locale * 5) Leave the current dictionary set. * 6) The content of the "LANG" environment variable (if set) * 7) The first spell check dictionary installed. */ // Get the language from the element or its closest parent according to: // https://html.spec.whatwg.org/#attr-lang // This is used in SetCurrentDictionary. mPreferredLang.Assign(aFetcher->mRootContentLang); // If no luck, try the "Content-Language" header. if (mPreferredLang.IsEmpty()) { mPreferredLang.Assign(aFetcher->mRootDocContentLang); } // Priority 1: // Get language from content prefs, if set. // If we successfully fetched a dictionary from content prefs, do not go // further. Use this exact dictionary. nsAutoString dictName; dictName.Assign(aFetcher->mDictionary); if (!dictName.IsEmpty()) { if (NS_SUCCEEDED(SetCurrentDictionary(dictName))) { // We take an early exit here, so clear the suggested word list. DeleteSuggestedWordList(); return NS_OK; } // Maybe the dictionary was uninstalled ? // Clear the content preference and continue. ClearCurrentDictionary(mEditor); } // Priority 2: // Get global preferred language from preferences, if set. nsAutoString preferredDict; preferredDict = Preferences::GetLocalizedString("spellchecker.dictionary.override"); if (!preferredDict.IsEmpty()) { dictName.Assign(preferredDict); } // Priority 3: // Get language from element/doc, if set. if (dictName.IsEmpty() && !mPreferredLang.IsEmpty()) { dictName.Assign(mPreferredLang); } // Auxiliary status value nsresult rv2; // Obtain a list of available dictionaries to check against. nsTArray dictList; rv2 = mSpellChecker->GetDictionaryList(&dictList); NS_ENSURE_SUCCESS(rv2, rv2); int32_t i, dictCount = dictList.Length(); // The following will be driven by this status. Once we are able to set a // dictionary successfully, we're done. So we start with a "failed" status. nsresult rv = NS_ERROR_FAILURE; if (!dictName.IsEmpty()) { for (i = 0; i < dictCount; i++) { nsAutoString dictStr(dictList.ElementAt(i)); if (dictName.Equals(dictStr, nsCaseInsensitiveStringComparator())) { // First let's correct any problems due to non-matching case. // This applies to both a user-set override and document language. // RFC 5646 explicitly states that matches should be case-insensitive. dictName.Assign(dictStr); // Try to set it. Doing this inside this loop avoids trying to set a // dictionary that isn't available. rv = SetCurrentDictionary(dictName); break; } } if (NS_FAILED(rv)) { // Required dictionary was not available. Try to get a dictionary // matching at least the language part of dictName: nsAutoString langCode; int32_t dashIdx = dictName.FindChar('-'); if (dashIdx != -1) { langCode.Assign(Substring(dictName, 0, dashIdx)); } else { langCode.Assign(dictName); } nsDefaultStringComparator comparator; // Loop over available dictionaries; if we find one with the required // language, use it. for (i = 0; i < dictCount; i++) { nsAutoString dictStr(dictList.ElementAt(i)); if (nsStyleUtil::DashMatchCompare(dictStr, langCode, comparator) && NS_SUCCEEDED(rv = SetCurrentDictionary(dictStr))) { break; } } } } // Priority 4: // Content prefs, override and document didn't give us a valid dictionary // name, so we just get the current locale and try to use that. if (NS_FAILED (rv)) { nsCOMPtr packageRegistry = mozilla::services::GetXULChromeRegistryService(); if (packageRegistry) { nsAutoCString utf8DictName; rv2 = packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global"), false, utf8DictName); dictName.Assign(EmptyString()); AppendUTF8toUTF16(utf8DictName, dictName); // Try to set it, if it's in the list. for (i = 0; i < dictCount; i++) { nsAutoString dictStr(dictList.ElementAt(i)); if (dictStr.Equals(dictName)) { rv = SetCurrentDictionary(dictName); break; } } } } if (NS_FAILED(rv)) { // Still no success. Further fallback attempts required. // Priority 5: // If we have a current dictionary, don't try anything else. nsAutoString currentDictionary; rv2 = GetCurrentDictionary(currentDictionary); if (NS_FAILED(rv2) || currentDictionary.IsEmpty()) { // We don't have a current dictionary. // Priority 6: // Try to get current dictionary from environment variable LANG. // LANG = language[_territory][.codeset] char* env_lang = getenv("LANG"); if (env_lang != nullptr) { nsString lang = NS_ConvertUTF8toUTF16(env_lang); // Strip trailing charset, if there is any. int32_t dot_pos = lang.FindChar('.'); if (dot_pos != -1) { lang = Substring(lang, 0, dot_pos); } // Convert underscore to dash. int32_t underScore = lang.FindChar('_'); if (underScore != -1) { lang.Replace(underScore, 1, '-'); // Only attempt to set if a _territory is present. rv = SetCurrentDictionary(lang); } } // Priority 7: // If LANG does not work either, pick the first one. if (NS_FAILED(rv)) { if (dictList.Length() > 0) { rv = SetCurrentDictionary(dictList[0]); } } } } // If an error was thrown while setting the dictionary, just // fail silently so that the spellchecker dialog is allowed to come // up. The user can manually reset the language to their choice on // the dialog if it is wrong. // Dictionary has changed, so delete the suggested word list. DeleteSuggestedWordList(); return NS_OK; }