mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-06-06 22:29:34 +00:00
566 lines
16 KiB
C++
566 lines
16 KiB
C++
/* 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 "mozSpellChecker.h"
|
|
#include "nsIServiceManager.h"
|
|
#include "mozISpellI18NManager.h"
|
|
#include "nsIStringEnumerator.h"
|
|
#include "nsICategoryManager.h"
|
|
#include "nsISupportsPrimitives.h"
|
|
#include "nsISimpleEnumerator.h"
|
|
#include "mozilla/PRemoteSpellcheckEngineChild.h"
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "nsXULAppAPI.h"
|
|
|
|
using mozilla::dom::ContentChild;
|
|
using mozilla::PRemoteSpellcheckEngineChild;
|
|
using mozilla::RemoteSpellcheckEngineChild;
|
|
|
|
#define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1"
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(mozSpellChecker)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(mozSpellChecker)
|
|
|
|
NS_INTERFACE_MAP_BEGIN(mozSpellChecker)
|
|
NS_INTERFACE_MAP_ENTRY(nsISpellChecker)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpellChecker)
|
|
NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozSpellChecker)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION(mozSpellChecker,
|
|
mTsDoc,
|
|
mPersonalDictionary)
|
|
|
|
mozSpellChecker::mozSpellChecker()
|
|
: mEngine(nullptr)
|
|
{
|
|
}
|
|
|
|
mozSpellChecker::~mozSpellChecker()
|
|
{
|
|
if (mPersonalDictionary) {
|
|
// mPersonalDictionary->Save();
|
|
mPersonalDictionary->EndSession();
|
|
}
|
|
mSpellCheckingEngine = nullptr;
|
|
mPersonalDictionary = nullptr;
|
|
|
|
if (mEngine) {
|
|
MOZ_ASSERT(XRE_IsContentProcess());
|
|
mEngine->Send__delete__(mEngine);
|
|
MOZ_ASSERT(!mEngine);
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
mozSpellChecker::Init()
|
|
{
|
|
mSpellCheckingEngine = nullptr;
|
|
if (XRE_IsContentProcess()) {
|
|
mozilla::dom::ContentChild* contentChild = mozilla::dom::ContentChild::GetSingleton();
|
|
MOZ_ASSERT(contentChild);
|
|
mEngine = new RemoteSpellcheckEngineChild(this);
|
|
contentChild->SendPRemoteSpellcheckEngineConstructor(mEngine);
|
|
} else {
|
|
mPersonalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozSpellChecker::SetDocument(nsITextServicesDocument *aDoc, bool aFromStartofDoc)
|
|
{
|
|
mTsDoc = aDoc;
|
|
mFromStart = aFromStartofDoc;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
mozSpellChecker::NextMisspelledWord(nsAString &aWord, nsTArray<nsString> *aSuggestions)
|
|
{
|
|
if(!aSuggestions||!mConverter)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
int32_t selOffset;
|
|
int32_t begin,end;
|
|
nsresult result;
|
|
result = SetupDoc(&selOffset);
|
|
bool isMisspelled,done;
|
|
if (NS_FAILED(result))
|
|
return result;
|
|
|
|
while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done )
|
|
{
|
|
nsString str;
|
|
result = mTsDoc->GetCurrentTextBlock(&str);
|
|
|
|
if (NS_FAILED(result))
|
|
return result;
|
|
do{
|
|
result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
|
|
if(NS_SUCCEEDED(result)&&(begin != -1)){
|
|
const nsAString &currWord = Substring(str, begin, end - begin);
|
|
result = CheckWord(currWord, &isMisspelled, aSuggestions);
|
|
if(isMisspelled){
|
|
aWord = currWord;
|
|
mTsDoc->SetSelection(begin, end-begin);
|
|
// After ScrollSelectionIntoView(), the pending notifications might
|
|
// be flushed and PresShell/PresContext/Frames may be dead.
|
|
// See bug 418470.
|
|
mTsDoc->ScrollSelectionIntoView();
|
|
return NS_OK;
|
|
}
|
|
}
|
|
selOffset = end;
|
|
}while(end != -1);
|
|
mTsDoc->NextBlock();
|
|
selOffset=0;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozSpellChecker::CheckWord(const nsAString &aWord, bool *aIsMisspelled, nsTArray<nsString> *aSuggestions)
|
|
{
|
|
nsresult result;
|
|
bool correct;
|
|
|
|
if (XRE_IsContentProcess()) {
|
|
nsString wordwrapped = nsString(aWord);
|
|
bool rv;
|
|
if (aSuggestions) {
|
|
rv = mEngine->SendCheckAndSuggest(wordwrapped, aIsMisspelled, aSuggestions);
|
|
} else {
|
|
rv = mEngine->SendCheck(wordwrapped, aIsMisspelled);
|
|
}
|
|
return rv ? NS_OK : NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if(!mSpellCheckingEngine) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
*aIsMisspelled = false;
|
|
result = mSpellCheckingEngine->Check(PromiseFlatString(aWord).get(), &correct);
|
|
NS_ENSURE_SUCCESS(result, result);
|
|
if(!correct){
|
|
if(aSuggestions){
|
|
uint32_t count,i;
|
|
char16_t **words;
|
|
|
|
result = mSpellCheckingEngine->Suggest(PromiseFlatString(aWord).get(), &words, &count);
|
|
NS_ENSURE_SUCCESS(result, result);
|
|
nsString* suggestions = aSuggestions->AppendElements(count);
|
|
for(i=0;i<count;i++){
|
|
suggestions[i].Assign(words[i]);
|
|
}
|
|
|
|
if (count)
|
|
NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
|
|
}
|
|
*aIsMisspelled = true;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozSpellChecker::Replace(const nsAString &aOldWord, const nsAString &aNewWord, bool aAllOccurrences)
|
|
{
|
|
if(!mConverter)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsAutoString newWord(aNewWord); // sigh
|
|
|
|
if(aAllOccurrences){
|
|
int32_t selOffset;
|
|
int32_t startBlock,currentBlock,currOffset;
|
|
int32_t begin,end;
|
|
bool done;
|
|
nsresult result;
|
|
nsAutoString str;
|
|
|
|
// find out where we are
|
|
result = SetupDoc(&selOffset);
|
|
if(NS_FAILED(result))
|
|
return result;
|
|
result = GetCurrentBlockIndex(mTsDoc,&startBlock);
|
|
if(NS_FAILED(result))
|
|
return result;
|
|
|
|
//start at the beginning
|
|
result = mTsDoc->FirstBlock();
|
|
currOffset=0;
|
|
currentBlock = 0;
|
|
while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done )
|
|
{
|
|
result = mTsDoc->GetCurrentTextBlock(&str);
|
|
do{
|
|
result = mConverter->FindNextWord(str.get(),str.Length(),currOffset,&begin,&end);
|
|
if(NS_SUCCEEDED(result)&&(begin != -1)){
|
|
if (aOldWord.Equals(Substring(str, begin, end-begin))) {
|
|
// if we are before the current selection point but in the same block
|
|
// move the selection point forwards
|
|
if((currentBlock == startBlock)&&(begin < selOffset)){
|
|
selOffset +=
|
|
int32_t(aNewWord.Length()) - int32_t(aOldWord.Length());
|
|
if(selOffset < begin) selOffset=begin;
|
|
}
|
|
mTsDoc->SetSelection(begin, end-begin);
|
|
mTsDoc->InsertText(&newWord);
|
|
mTsDoc->GetCurrentTextBlock(&str);
|
|
end += (aNewWord.Length() - aOldWord.Length()); // recursion was cute in GEB, not here.
|
|
}
|
|
}
|
|
currOffset = end;
|
|
}while(currOffset != -1);
|
|
mTsDoc->NextBlock();
|
|
currentBlock++;
|
|
currOffset=0;
|
|
}
|
|
|
|
// We are done replacing. Put the selection point back where we found it (or equivalent);
|
|
result = mTsDoc->FirstBlock();
|
|
currentBlock = 0;
|
|
while(( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) &&(currentBlock < startBlock)){
|
|
mTsDoc->NextBlock();
|
|
}
|
|
|
|
//After we have moved to the block where the first occurrence of replace was done, put the
|
|
//selection to the next word following it. In case there is no word following it i.e if it happens
|
|
//to be the last word in that block, then move to the next block and put the selection to the
|
|
//first word in that block, otherwise when the Setupdoc() is called, it queries the LastSelectedBlock()
|
|
//and the selection offset of the last occurrence of the replaced word is taken instead of the first
|
|
//occurrence and things get messed up as reported in the bug 244969
|
|
|
|
if( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ){
|
|
nsString str;
|
|
result = mTsDoc->GetCurrentTextBlock(&str);
|
|
result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
|
|
if(end == -1)
|
|
{
|
|
mTsDoc->NextBlock();
|
|
selOffset=0;
|
|
result = mTsDoc->GetCurrentTextBlock(&str);
|
|
result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
|
|
mTsDoc->SetSelection(begin, 0);
|
|
}
|
|
else
|
|
mTsDoc->SetSelection(begin, 0);
|
|
}
|
|
}
|
|
else{
|
|
mTsDoc->InsertText(&newWord);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozSpellChecker::IgnoreAll(const nsAString &aWord)
|
|
{
|
|
if(mPersonalDictionary){
|
|
mPersonalDictionary->IgnoreWord(PromiseFlatString(aWord).get());
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozSpellChecker::AddWordToPersonalDictionary(const nsAString &aWord)
|
|
{
|
|
nsresult res;
|
|
char16_t empty=0;
|
|
if (!mPersonalDictionary)
|
|
return NS_ERROR_NULL_POINTER;
|
|
res = mPersonalDictionary->AddWord(PromiseFlatString(aWord).get(),&empty);
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozSpellChecker::RemoveWordFromPersonalDictionary(const nsAString &aWord)
|
|
{
|
|
nsresult res;
|
|
char16_t empty=0;
|
|
if (!mPersonalDictionary)
|
|
return NS_ERROR_NULL_POINTER;
|
|
res = mPersonalDictionary->RemoveWord(PromiseFlatString(aWord).get(),&empty);
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozSpellChecker::GetPersonalDictionary(nsTArray<nsString> *aWordList)
|
|
{
|
|
if(!aWordList || !mPersonalDictionary)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIStringEnumerator> words;
|
|
mPersonalDictionary->GetWordList(getter_AddRefs(words));
|
|
|
|
bool hasMore;
|
|
nsAutoString word;
|
|
while (NS_SUCCEEDED(words->HasMore(&hasMore)) && hasMore) {
|
|
words->GetNext(word);
|
|
aWordList->AppendElement(word);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozSpellChecker::GetDictionaryList(nsTArray<nsString> *aDictionaryList)
|
|
{
|
|
if (XRE_IsContentProcess()) {
|
|
ContentChild *child = ContentChild::GetSingleton();
|
|
child->GetAvailableDictionaries(*aDictionaryList);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult rv;
|
|
|
|
// For catching duplicates
|
|
nsTHashtable<nsStringHashKey> dictionaries;
|
|
|
|
nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines;
|
|
rv = GetEngineList(&spellCheckingEngines);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) {
|
|
nsCOMPtr<mozISpellCheckingEngine> engine = spellCheckingEngines[i];
|
|
|
|
uint32_t count = 0;
|
|
char16_t **words = nullptr;
|
|
engine->GetDictionaryList(&words, &count);
|
|
for (uint32_t k = 0; k < count; k++) {
|
|
nsAutoString dictName;
|
|
|
|
dictName.Assign(words[k]);
|
|
|
|
// Skip duplicate dictionaries. Only take the first one
|
|
// for each name.
|
|
if (dictionaries.Contains(dictName))
|
|
continue;
|
|
|
|
dictionaries.PutEntry(dictName);
|
|
|
|
if (!aDictionaryList->AppendElement(dictName)) {
|
|
NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozSpellChecker::GetCurrentDictionary(nsAString &aDictionary)
|
|
{
|
|
if (XRE_IsContentProcess()) {
|
|
aDictionary = mCurrentDictionary;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!mSpellCheckingEngine) {
|
|
aDictionary.Truncate();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsXPIDLString dictname;
|
|
mSpellCheckingEngine->GetDictionary(getter_Copies(dictname));
|
|
aDictionary = dictname;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary)
|
|
{
|
|
if (XRE_IsContentProcess()) {
|
|
nsString wrappedDict = nsString(aDictionary);
|
|
bool isSuccess;
|
|
mEngine->SendSetDictionary(wrappedDict, &isSuccess);
|
|
if (!isSuccess) {
|
|
mCurrentDictionary.Truncate();
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
mCurrentDictionary = wrappedDict;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Calls to mozISpellCheckingEngine::SetDictionary might destroy us
|
|
RefPtr<mozSpellChecker> kungFuDeathGrip = this;
|
|
|
|
mSpellCheckingEngine = nullptr;
|
|
|
|
if (aDictionary.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult rv;
|
|
nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines;
|
|
rv = GetEngineList(&spellCheckingEngines);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) {
|
|
// We must set mSpellCheckingEngine before we call SetDictionary, since
|
|
// SetDictionary calls back to this spell checker to check if the
|
|
// dictionary was set
|
|
mSpellCheckingEngine = spellCheckingEngines[i];
|
|
|
|
rv = mSpellCheckingEngine->SetDictionary(PromiseFlatString(aDictionary).get());
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
nsCOMPtr<mozIPersonalDictionary> personalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
|
|
mSpellCheckingEngine->SetPersonalDictionary(personalDictionary.get());
|
|
|
|
nsXPIDLString language;
|
|
nsCOMPtr<mozISpellI18NManager> serv(do_GetService("@mozilla.org/spellchecker/i18nmanager;1", &rv));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
return serv->GetUtil(language.get(),getter_AddRefs(mConverter));
|
|
}
|
|
}
|
|
|
|
mSpellCheckingEngine = nullptr;
|
|
|
|
// We could not find any engine with the requested dictionary
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsresult
|
|
mozSpellChecker::SetupDoc(int32_t *outBlockOffset)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsITextServicesDocument::TSDBlockSelectionStatus blockStatus;
|
|
int32_t selOffset;
|
|
int32_t selLength;
|
|
*outBlockOffset = 0;
|
|
|
|
if (!mFromStart)
|
|
{
|
|
rv = mTsDoc->LastSelectedBlock(&blockStatus, &selOffset, &selLength);
|
|
if (NS_SUCCEEDED(rv) && (blockStatus != nsITextServicesDocument::eBlockNotFound))
|
|
{
|
|
switch (blockStatus)
|
|
{
|
|
case nsITextServicesDocument::eBlockOutside: // No TB in S, but found one before/after S.
|
|
case nsITextServicesDocument::eBlockPartial: // S begins or ends in TB but extends outside of TB.
|
|
// the TS doc points to the block we want.
|
|
*outBlockOffset = selOffset + selLength;
|
|
break;
|
|
|
|
case nsITextServicesDocument::eBlockInside: // S extends beyond the start and end of TB.
|
|
// we want the block after this one.
|
|
rv = mTsDoc->NextBlock();
|
|
*outBlockOffset = 0;
|
|
break;
|
|
|
|
case nsITextServicesDocument::eBlockContains: // TB contains entire S.
|
|
*outBlockOffset = selOffset + selLength;
|
|
break;
|
|
|
|
case nsITextServicesDocument::eBlockNotFound: // There is no text block (TB) in or before the selection (S).
|
|
default:
|
|
NS_NOTREACHED("Shouldn't ever get this status");
|
|
}
|
|
}
|
|
else //failed to get last sel block. Just start at beginning
|
|
{
|
|
rv = mTsDoc->FirstBlock();
|
|
*outBlockOffset = 0;
|
|
}
|
|
|
|
}
|
|
else // we want the first block
|
|
{
|
|
rv = mTsDoc->FirstBlock();
|
|
mFromStart = false;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
|
|
// utility method to discover which block we're in. The TSDoc interface doesn't give
|
|
// us this, because it can't assume a read-only document.
|
|
// shamelessly stolen from nsTextServicesDocument
|
|
nsresult
|
|
mozSpellChecker::GetCurrentBlockIndex(nsITextServicesDocument *aDoc, int32_t *outBlockIndex)
|
|
{
|
|
int32_t blockIndex = 0;
|
|
bool isDone = false;
|
|
nsresult result = NS_OK;
|
|
|
|
do
|
|
{
|
|
aDoc->PrevBlock();
|
|
|
|
result = aDoc->IsDone(&isDone);
|
|
|
|
if (!isDone)
|
|
blockIndex ++;
|
|
|
|
} while (NS_SUCCEEDED(result) && !isDone);
|
|
|
|
*outBlockIndex = blockIndex;
|
|
|
|
return result;
|
|
}
|
|
|
|
nsresult
|
|
mozSpellChecker::GetEngineList(nsCOMArray<mozISpellCheckingEngine>* aSpellCheckingEngines)
|
|
{
|
|
MOZ_ASSERT(!XRE_IsContentProcess());
|
|
|
|
nsresult rv;
|
|
bool hasMoreEngines;
|
|
|
|
nsCOMPtr<nsICategoryManager> catMgr = do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
|
|
if (!catMgr)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsISimpleEnumerator> catEntries;
|
|
|
|
// Get contract IDs of registrated external spell-check engines and
|
|
// append one of HunSpell at the end.
|
|
rv = catMgr->EnumerateCategory("spell-check-engine", getter_AddRefs(catEntries));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
while (catEntries->HasMoreElements(&hasMoreEngines), hasMoreEngines){
|
|
nsCOMPtr<nsISupports> elem;
|
|
rv = catEntries->GetNext(getter_AddRefs(elem));
|
|
|
|
nsCOMPtr<nsISupportsCString> entry = do_QueryInterface(elem, &rv);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
nsCString contractId;
|
|
rv = entry->GetData(contractId);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
// Try to load spellchecker engine. Ignore errors silently
|
|
// except for the last one (HunSpell).
|
|
nsCOMPtr<mozISpellCheckingEngine> engine =
|
|
do_GetService(contractId.get(), &rv);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
aSpellCheckingEngines->AppendObject(engine);
|
|
}
|
|
}
|
|
|
|
// Try to load HunSpell spellchecker engine.
|
|
nsCOMPtr<mozISpellCheckingEngine> engine =
|
|
do_GetService(DEFAULT_SPELL_CHECKER, &rv);
|
|
if (NS_FAILED(rv)) {
|
|
// Fail if not succeeded to load HunSpell. Ignore errors
|
|
// for external spellcheck engines.
|
|
return rv;
|
|
}
|
|
aSpellCheckingEngines->AppendObject(engine);
|
|
|
|
return NS_OK;
|
|
}
|