Mypal/editor/composer/nsEditorSpellCheck.cpp

935 lines
29 KiB
C++

/* -*- 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 <stdlib.h> // 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<nsIDOMDocument> domDoc;
aEditor->GetDocument(getter_AddRefs(domDoc));
NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI();
NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE);
*aURI = docUri;
NS_ADDREF(*aURI);
return NS_OK;
}
static already_AddRefed<nsILoadContext>
GetLoadContext(nsIEditor* aEditor)
{
nsCOMPtr<nsIDOMDocument> domDoc;
aEditor->GetDocument(getter_AddRefs(domDoc));
NS_ENSURE_TRUE(domDoc, nullptr);
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
NS_ENSURE_TRUE(doc, nullptr);
nsCOMPtr<nsILoadContext> 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<nsIVariant> 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<nsIEditorSpellCheckCallback> mCallback;
uint32_t mGroup;
nsString mRootContentLang;
nsString mRootDocContentLang;
nsString mDictionary;
private:
~DictionaryFetcher() {}
RefPtr<nsEditorSpellCheck> mSpellCheck;
};
NS_IMPL_ISUPPORTS(DictionaryFetcher, nsIContentPrefCallback2)
NS_IMETHODIMP
DictionaryFetcher::Fetch(nsIEditor* aEditor)
{
NS_ENSURE_ARG_POINTER(aEditor);
nsresult rv;
nsCOMPtr<nsIURI> docUri;
rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString docUriSpec;
rv = docUri->GetSpec(docUriSpec);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIContentPrefService2> contentPrefService =
do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE);
nsCOMPtr<nsILoadContext> 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<nsIURI> docUri;
rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString docUriSpec;
rv = docUri->GetSpec(docUriSpec);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<nsVariant> prefValue = new nsVariant();
prefValue->SetAsAString(aDictionary);
nsCOMPtr<nsIContentPrefService2> contentPrefService =
do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
nsCOMPtr<nsILoadContext> 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<nsIURI> docUri;
rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString docUriSpec;
rv = docUri->GetSpec(docUriSpec);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIContentPrefService2> contentPrefService =
do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
nsCOMPtr<nsILoadContext> 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<nsISpellChecker> spellChecker;
if (! mSpellChecker) {
spellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
} else {
spellChecker = mSpellChecker;
}
nsTArray<nsString> 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<nsIEditorSpellCheckCallback> 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
nsCOMPtr<nsITextServicesDocument>tsDoc =
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<nsISelection> domSelection;
aEditor->GetSelection(getter_AddRefs(domSelection));
if (NS_WARN_IF(!domSelection)) {
return NS_ERROR_FAILURE;
}
RefPtr<Selection> selection = domSelection->AsSelection();
int32_t count = 0;
rv = selection->GetRangeCount(&count);
NS_ENSURE_SUCCESS(rv, rv);
if (count > 0) {
RefPtr<nsRange> 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<nsRange> 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<CallbackCaller> 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<int32_t>(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<int32_t>(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<nsString> 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<nsEditorSpellCheck> 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<nsEditorSpellCheck> kungFuDeathGrip = this;
// Get language with html5 algorithm
nsCOMPtr<nsIContent> rootContent;
nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor);
if (htmlEditor) {
rootContent = htmlEditor->GetActiveEditingHost();
} else {
nsCOMPtr<nsIDOMElement> 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<nsIDocument> 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<DictionaryFetcher> fetcher =
new DictionaryFetcher(this, aCallback, mDictionaryFetcherGroup);
rootContent->GetLang(fetcher->mRootContentLang);
nsCOMPtr<nsIDocument> 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<nsString>& 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<nsEditorSpellCheck> 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<nsString> 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<nsIXULChromeRegistry> 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;
}