From 6d3a0633543c2f6dfa3cb82aab519ac5fbd33e35 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Tue, 1 Aug 2017 16:06:43 +0200 Subject: Fix spellchecker dictionary logic. This fixes #6. --- editor/composer/nsEditorSpellCheck.cpp | 300 +++++++++++++-------------------- modules/libpref/init/all.js | 3 + 2 files changed, 118 insertions(+), 185 deletions(-) diff --git a/editor/composer/nsEditorSpellCheck.cpp b/editor/composer/nsEditorSpellCheck.cpp index 7cae48c1f..790480034 100644 --- a/editor/composer/nsEditorSpellCheck.cpp +++ b/editor/composer/nsEditorSpellCheck.cpp @@ -594,45 +594,24 @@ nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary) // Ignore pending dictionary fetchers by increasing this number. mDictionaryFetcherGroup++; - uint32_t flags = 0; - mEditor->GetFlags(&flags); - if (!(flags & nsIPlaintextEditor::eEditorMailMask)) { - if (!aDictionary.IsEmpty() && (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); -#ifdef DEBUG_DICT - printf("***** Writing content preferences for |%s|\n", - NS_ConvertUTF16toUTF8(aDictionary).get()); -#endif - } 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); -#ifdef DEBUG_DICT - printf("***** Clearing content preferences for |%s|\n", - NS_ConvertUTF16toUTF8(aDictionary).get()); -#endif - } - - // Also store it in as a preference, so we can use it as a fallback. - // We don't want this for mail composer because it uses - // "spellchecker.dictionary" as a preference. - Preferences::SetString("spellchecker.dictionary", aDictionary); -#ifdef DEBUG_DICT - printf("***** Storing spellchecker.dictionary |%s|\n", - NS_ConvertUTF16toUTF8(aDictionary).get()); -#endif + 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() { @@ -771,23 +750,16 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher) /* * We try to derive the dictionary to use based on the following priorities: - * 1) Content preference, so the language the user set for the site before. - * (Introduced in bug 678842 and corrected in bug 717433.) - * 2) Language set by the website, or any other dictionary that partly - * matches that. (Introduced in bug 338427.) - * 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, (almost) at random. - * However, we prefer what is stored in "spellchecker.dictionary", - * so if the user chose "en-AU" before, they will get "en-AU" on a plain - * "en" site. (Introduced in bug 682564.) - * 3) The value of "spellchecker.dictionary" which reflects a previous - * language choice of the user (on another site). - * (This was the original behaviour before the aforementioned bugs - * landed). - * 4) The user's locale. - * 5) Use the current dictionary that is currently set. - * 6) The content of the "LANG" environment variable (if set). + * 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. */ @@ -795,131 +767,100 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher) // https://html.spec.whatwg.org/#attr-lang // This is used in SetCurrentDictionary. mPreferredLang.Assign(aFetcher->mRootContentLang); -#ifdef DEBUG_DICT - printf("***** mPreferredLang (element) |%s|\n", - NS_ConvertUTF16toUTF8(mPreferredLang).get()); -#endif // If no luck, try the "Content-Language" header. if (mPreferredLang.IsEmpty()) { mPreferredLang.Assign(aFetcher->mRootDocContentLang); -#ifdef DEBUG_DICT - printf("***** mPreferredLang (content-language) |%s|\n", - NS_ConvertUTF16toUTF8(mPreferredLang).get()); -#endif } - - // Auxiliary status. - nsresult rv2; - - // We obtain a list of available dictionaries. - nsTArray dictList; - rv2 = mSpellChecker->GetDictionaryList(&dictList); - NS_ENSURE_SUCCESS(rv2, rv2); - + // 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. - // Don't use content preferences for editor with eEditorMailMask flag. nsAutoString dictName; - uint32_t flags; - mEditor->GetFlags(&flags); - if (!(flags & nsIPlaintextEditor::eEditorMailMask)) { - dictName.Assign(aFetcher->mDictionary); - if (!dictName.IsEmpty()) { - if (NS_SUCCEEDED(TryDictionary(dictName, dictList, DICT_NORMAL_COMPARE))) { -#ifdef DEBUG_DICT - printf("***** Assigned from content preferences |%s|\n", - NS_ConvertUTF16toUTF8(dictName).get()); -#endif - - // We take an early exit here, so let's not forget to clear the word - // list. - DeleteSuggestedWordList(); - return NS_OK; - } - // May be dictionary was uninstalled ? - // Clear the content preference and continue. - ClearCurrentDictionary(mEditor); + 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: - // After checking the content preferences, we use the language of the element - // or document. - dictName.Assign(mPreferredLang); -#ifdef DEBUG_DICT - printf("***** Assigned from element/doc |%s|\n", - NS_ConvertUTF16toUTF8(dictName).get()); -#endif - - // Get the preference value. + // Get global preferred language from preferences, if set. nsAutoString preferredDict; - preferredDict = Preferences::GetLocalizedString("spellchecker.dictionary"); + preferredDict = Preferences::GetLocalizedString("spellchecker.dictionary.override"); + if (!preferredDict.IsEmpty()) { + dictName.Assign(preferredDict); + } - // The following will be driven by this status. Once we were able to set a + // 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_NOT_AVAILABLE; - + nsresult rv = NS_ERROR_FAILURE; + if (!dictName.IsEmpty()) { - // RFC 5646 explicitly states that matches should be case-insensitive. - rv = TryDictionary (dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE); - - if (NS_FAILED(rv)) { -#ifdef DEBUG_DICT - printf("***** Setting of |%s| failed (or it wasn't available)\n", - NS_ConvertUTF16toUTF8(dictName).get()); -#endif + 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 language part of dictName. + // matching at least the language part of dictName: nsAutoString langCode; int32_t dashIdx = dictName.FindChar('-'); - if (dashIdx != -1) { + if (dashIdx != -1) { langCode.Assign(Substring(dictName, 0, dashIdx)); } else { langCode.Assign(dictName); } - // Try dictionary.spellchecker preference, if it starts with langCode, - // so we don't just get any random dictionary matching the language. - if (!preferredDict.IsEmpty() && - nsStyleUtil::DashMatchCompare(preferredDict, langCode, nsDefaultStringComparator())) { -#ifdef DEBUG_DICT - printf("***** Trying preference value |%s| since it matches language code\n", - NS_ConvertUTF16toUTF8(preferredDict).get()); -#endif - rv = TryDictionary (preferredDict, dictList, - DICT_COMPARE_CASE_INSENSITIVE); - } + nsDefaultStringComparator comparator; - if (NS_FAILED(rv)) { - // Use any dictionary with the required language. -#ifdef DEBUG_DICT - printf("***** Trying to find match for language code |%s|\n", - NS_ConvertUTF16toUTF8(langCode).get()); -#endif - rv = TryDictionary (langCode, dictList, DICT_COMPARE_DASHMATCH); + // 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 3: - // If the document didn't supply a dictionary or the setting failed, - // try the user preference next. - if (NS_FAILED(rv)) { - if (!preferredDict.IsEmpty()) { -#ifdef DEBUG_DICT - printf("***** Trying preference value |%s|\n", - NS_ConvertUTF16toUTF8(preferredDict).get()); -#endif - rv = TryDictionary (preferredDict, dictList, DICT_NORMAL_COMPARE); - } - } - + // Priority 4: - // As next fallback, try the current locale. - if (NS_FAILED(rv)) { + // 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(); @@ -929,69 +870,57 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher) false, utf8DictName); dictName.Assign(EmptyString()); AppendUTF8toUTF16(utf8DictName, dictName); -#ifdef DEBUG_DICT - printf("***** Trying locale |%s|\n", - NS_ConvertUTF16toUTF8(dictName).get()); -#endif - rv = TryDictionary (dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE); + // 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. + // Still no success. Further fallback attempts required. - // Priority 5: - // If we have a current dictionary, don't try anything else. + // Priority 5: + // If we have a current dictionary, don't try anything else. nsAutoString currentDictionary; rv2 = GetCurrentDictionary(currentDictionary); -#ifdef DEBUG_DICT - if (NS_SUCCEEDED(rv2)) { - printf("***** Retrieved current dict |%s|\n", - NS_ConvertUTF16toUTF8(currentDictionary).get()); - } -#endif - + 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][.charset] + // LANG = language[_territory][.codeset] char* env_lang = getenv("LANG"); - if (env_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, '-'); -#ifdef DEBUG_DICT - printf("***** Trying LANG from environment |%s|\n", - NS_ConvertUTF16toUTF8(lang).get()); -#endif - nsAutoString lang2; - lang2.Assign(lang); - rv = TryDictionary(lang2, dictList, DICT_COMPARE_CASE_INSENSITIVE); + // Only attempt to set if a _territory is present. + rv = SetCurrentDictionary(lang); } } - + // Priority 7: - // If it does not work, pick the first one. - if (NS_FAILED(rv) && !dictList.IsEmpty()) { - nsAutoString firstInList; - firstInList.Assign(dictList[0]); - rv = TryDictionary(firstInList, dictList, DICT_NORMAL_COMPARE); -#ifdef DEBUG_DICT - printf("***** Trying first of list |%s|\n", - NS_ConvertUTF16toUTF8(dictList[0]).get()); - if (NS_SUCCEEDED(rv)) { - printf ("***** Setting worked.\n"); + // If LANG does not work either, pick the first one. + if (NS_FAILED(rv)) { + if (dictList.Length() > 0) { + rv = SetCurrentDictionary(dictList[0]); } -#endif } - } + } } // If an error was thrown while setting the dictionary, just @@ -999,6 +928,7 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher) // 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; diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 67c632e71..62aedead2 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -1117,6 +1117,9 @@ pref("print.print_via_parent", false); // in a document. pref("extensions.spellcheck.inline.max-misspellings", 500); +// Predefined convenience pref for overriding the dictionary +pref("spellchecker.dictionary.override", ""); + // Prefs used by libeditor. Prefs specific to seamonkey composer // belong in comm-central/editor/ui/composer.js -- cgit v1.2.3