mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-06-01 17:19:15 +00:00
041fff53f9
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. Stop using the (broken) spellchecker.dictionary "holding pref"
905 lines
28 KiB
C++
905 lines
28 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=2 sts=2 sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include <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 "nsIVariant.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
|
|
|
|
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() {}
|
|
|
|
nsRefPtr<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);
|
|
|
|
nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
|
|
NS_ENSURE_TRUE(prefValue, NS_ERROR_OUT_OF_MEMORY);
|
|
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 nsRunnable
|
|
{
|
|
public:
|
|
explicit CallbackCaller(nsIEditorSpellCheckCallback* aCallback)
|
|
: mCallback(aCallback) {}
|
|
|
|
~CallbackCaller()
|
|
{
|
|
Run();
|
|
}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
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));
|
|
nsRefPtr<Selection> selection = static_cast<Selection*>(domSelection.get());
|
|
NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
|
|
|
|
int32_t count = 0;
|
|
|
|
rv = selection->GetRangeCount(&count);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (count > 0) {
|
|
nsRefPtr<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.
|
|
|
|
nsRefPtr<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.
|
|
nsRefPtr<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;
|
|
if ( mSuggestedWordIndex < 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)
|
|
{
|
|
if ( mDictionaryIndex < 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.Length() < 1)
|
|
{
|
|
// If there are no dictionaries, return an array containing
|
|
// one element and a count of one.
|
|
|
|
tmpPtr = (char16_t **)nsMemory::Alloc(sizeof(char16_t *));
|
|
|
|
NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
*tmpPtr = 0;
|
|
*aDictionaryList = tmpPtr;
|
|
*aCount = 0;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
tmpPtr = (char16_t **)nsMemory::Alloc(sizeof(char16_t *) * dictList.Length());
|
|
|
|
NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
*aDictionaryList = tmpPtr;
|
|
*aCount = dictList.Length();
|
|
|
|
uint32_t i;
|
|
|
|
for (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);
|
|
|
|
nsRefPtr<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 (meaning: 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::CheckCurrentDictionary()
|
|
{
|
|
mSpellChecker->CheckCurrentDictionary();
|
|
|
|
// Check if our current dictionary is still available.
|
|
nsAutoString currentDictionary;
|
|
nsresult rv = GetCurrentDictionary(currentDictionary);
|
|
if (NS_SUCCEEDED(rv) && !currentDictionary.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// If our preferred current dictionary has gone, pick another one.
|
|
nsTArray<nsString> dictList;
|
|
rv = mSpellChecker->GetDictionaryList(&dictList);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (dictList.Length() > 0) {
|
|
rv = SetCurrentDictionary(dictList[0]);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsEditorSpellCheck::UninitSpellChecker()
|
|
{
|
|
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
// Cleanup - kill the spell checker
|
|
DeleteSuggestedWordList();
|
|
mDictionaryList.Clear();
|
|
mDictionaryIndex = 0;
|
|
mSpellChecker = 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
/* void setFilter (in nsITextServicesFilter filter); */
|
|
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)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsRefPtr<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);
|
|
}
|
|
NS_ENSURE_TRUE(rootContent, NS_ERROR_FAILURE);
|
|
|
|
nsRefPtr<DictionaryFetcher> fetcher =
|
|
new DictionaryFetcher(this, aCallback, mDictionaryFetcherGroup);
|
|
rootContent->GetLang(fetcher->mRootContentLang);
|
|
nsCOMPtr<nsIDocument> doc = rootContent->GetCurrentDoc();
|
|
NS_ENSURE_STATE(doc);
|
|
doc->GetContentLanguage(fetcher->mRootDocContentLang);
|
|
|
|
rv = fetcher->Fetch(mEditor);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
|
|
{
|
|
MOZ_ASSERT(aFetcher);
|
|
nsRefPtr<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"),
|
|
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;
|
|
}
|