Line data Source code
1 : /* vim: set ts=2 sts=2 sw=2 tw=80: */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "mozSpellChecker.h"
7 : #include "nsIServiceManager.h"
8 : #include "mozISpellI18NManager.h"
9 : #include "nsIStringEnumerator.h"
10 : #include "nsICategoryManager.h"
11 : #include "nsISupportsPrimitives.h"
12 : #include "nsISimpleEnumerator.h"
13 : #include "mozilla/PRemoteSpellcheckEngineChild.h"
14 : #include "mozilla/dom/ContentChild.h"
15 : #include "nsXULAppAPI.h"
16 :
17 : using mozilla::dom::ContentChild;
18 : using mozilla::GenericPromise;
19 : using mozilla::PRemoteSpellcheckEngineChild;
20 : using mozilla::RemoteSpellcheckEngineChild;
21 :
22 : #define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1"
23 :
24 5 : NS_IMPL_CYCLE_COLLECTING_ADDREF(mozSpellChecker)
25 4 : NS_IMPL_CYCLE_COLLECTING_RELEASE(mozSpellChecker)
26 :
27 4 : NS_INTERFACE_MAP_BEGIN(mozSpellChecker)
28 4 : NS_INTERFACE_MAP_ENTRY(nsISpellChecker)
29 2 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpellChecker)
30 1 : NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozSpellChecker)
31 0 : NS_INTERFACE_MAP_END
32 :
33 0 : NS_IMPL_CYCLE_COLLECTION(mozSpellChecker,
34 : mTsDoc,
35 : mPersonalDictionary)
36 :
37 1 : mozSpellChecker::mozSpellChecker()
38 1 : : mEngine(nullptr)
39 : {
40 1 : }
41 :
42 0 : mozSpellChecker::~mozSpellChecker()
43 : {
44 0 : if (mPersonalDictionary) {
45 : // mPersonalDictionary->Save();
46 0 : mPersonalDictionary->EndSession();
47 : }
48 0 : mSpellCheckingEngine = nullptr;
49 0 : mPersonalDictionary = nullptr;
50 :
51 0 : if (mEngine) {
52 0 : MOZ_ASSERT(XRE_IsContentProcess());
53 0 : mEngine->Send__delete__(mEngine);
54 0 : MOZ_ASSERT(!mEngine);
55 : }
56 0 : }
57 :
58 : nsresult
59 1 : mozSpellChecker::Init()
60 : {
61 1 : mSpellCheckingEngine = nullptr;
62 1 : if (XRE_IsContentProcess()) {
63 0 : mozilla::dom::ContentChild* contentChild = mozilla::dom::ContentChild::GetSingleton();
64 0 : MOZ_ASSERT(contentChild);
65 0 : mEngine = new RemoteSpellcheckEngineChild(this);
66 0 : contentChild->SendPRemoteSpellcheckEngineConstructor(mEngine);
67 : } else {
68 1 : mPersonalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
69 : }
70 :
71 1 : return NS_OK;
72 : }
73 :
74 : NS_IMETHODIMP
75 0 : mozSpellChecker::SetDocument(nsITextServicesDocument *aDoc, bool aFromStartofDoc)
76 : {
77 0 : mTsDoc = aDoc;
78 0 : mFromStart = aFromStartofDoc;
79 0 : return NS_OK;
80 : }
81 :
82 :
83 : NS_IMETHODIMP
84 0 : mozSpellChecker::NextMisspelledWord(nsAString &aWord, nsTArray<nsString> *aSuggestions)
85 : {
86 0 : if(!aSuggestions||!mConverter)
87 0 : return NS_ERROR_NULL_POINTER;
88 :
89 : int32_t selOffset;
90 : int32_t begin,end;
91 : nsresult result;
92 0 : result = SetupDoc(&selOffset);
93 : bool isMisspelled,done;
94 0 : if (NS_FAILED(result))
95 0 : return result;
96 :
97 0 : while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done )
98 : {
99 0 : nsString str;
100 0 : result = mTsDoc->GetCurrentTextBlock(&str);
101 :
102 0 : if (NS_FAILED(result))
103 0 : return result;
104 0 : do{
105 0 : result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
106 0 : if(NS_SUCCEEDED(result)&&(begin != -1)){
107 0 : const nsAString &currWord = Substring(str, begin, end - begin);
108 0 : result = CheckWord(currWord, &isMisspelled, aSuggestions);
109 0 : if(isMisspelled){
110 0 : aWord = currWord;
111 0 : mTsDoc->SetSelection(begin, end-begin);
112 : // After ScrollSelectionIntoView(), the pending notifications might
113 : // be flushed and PresShell/PresContext/Frames may be dead.
114 : // See bug 418470.
115 0 : mTsDoc->ScrollSelectionIntoView();
116 0 : return NS_OK;
117 : }
118 : }
119 0 : selOffset = end;
120 0 : }while(end != -1);
121 0 : mTsDoc->NextBlock();
122 0 : selOffset=0;
123 : }
124 0 : return NS_OK;
125 : }
126 :
127 : NS_IMETHODIMP
128 0 : mozSpellChecker::CheckWord(const nsAString &aWord, bool *aIsMisspelled, nsTArray<nsString> *aSuggestions)
129 : {
130 : nsresult result;
131 : bool correct;
132 :
133 0 : if (XRE_IsContentProcess()) {
134 0 : nsString wordwrapped = nsString(aWord);
135 : bool rv;
136 0 : if (aSuggestions) {
137 0 : rv = mEngine->SendCheckAndSuggest(wordwrapped, aIsMisspelled, aSuggestions);
138 : } else {
139 0 : rv = mEngine->SendCheck(wordwrapped, aIsMisspelled);
140 : }
141 0 : return rv ? NS_OK : NS_ERROR_NOT_AVAILABLE;
142 : }
143 :
144 0 : if(!mSpellCheckingEngine) {
145 0 : return NS_ERROR_NULL_POINTER;
146 : }
147 0 : *aIsMisspelled = false;
148 0 : result = mSpellCheckingEngine->Check(PromiseFlatString(aWord).get(), &correct);
149 0 : NS_ENSURE_SUCCESS(result, result);
150 0 : if(!correct){
151 0 : if(aSuggestions){
152 : uint32_t count,i;
153 : char16_t **words;
154 :
155 0 : result = mSpellCheckingEngine->Suggest(PromiseFlatString(aWord).get(), &words, &count);
156 0 : NS_ENSURE_SUCCESS(result, result);
157 0 : nsString* suggestions = aSuggestions->AppendElements(count);
158 0 : for(i=0;i<count;i++){
159 0 : suggestions[i].Assign(words[i]);
160 : }
161 :
162 0 : if (count)
163 0 : NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
164 : }
165 0 : *aIsMisspelled = true;
166 : }
167 0 : return NS_OK;
168 : }
169 :
170 : NS_IMETHODIMP
171 0 : mozSpellChecker::Replace(const nsAString &aOldWord, const nsAString &aNewWord, bool aAllOccurrences)
172 : {
173 0 : if(!mConverter)
174 0 : return NS_ERROR_NULL_POINTER;
175 :
176 0 : nsAutoString newWord(aNewWord); // sigh
177 :
178 0 : if(aAllOccurrences){
179 : int32_t selOffset;
180 : int32_t startBlock,currentBlock,currOffset;
181 : int32_t begin,end;
182 : bool done;
183 : nsresult result;
184 0 : nsAutoString str;
185 :
186 : // find out where we are
187 0 : result = SetupDoc(&selOffset);
188 0 : if(NS_FAILED(result))
189 0 : return result;
190 0 : result = GetCurrentBlockIndex(mTsDoc,&startBlock);
191 0 : if(NS_FAILED(result))
192 0 : return result;
193 :
194 : //start at the beginning
195 0 : result = mTsDoc->FirstBlock();
196 0 : currOffset=0;
197 0 : currentBlock = 0;
198 0 : while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done )
199 : {
200 0 : result = mTsDoc->GetCurrentTextBlock(&str);
201 0 : do{
202 0 : result = mConverter->FindNextWord(str.get(),str.Length(),currOffset,&begin,&end);
203 0 : if(NS_SUCCEEDED(result)&&(begin != -1)){
204 0 : if (aOldWord.Equals(Substring(str, begin, end-begin))) {
205 : // if we are before the current selection point but in the same block
206 : // move the selection point forwards
207 0 : if((currentBlock == startBlock)&&(begin < selOffset)){
208 0 : selOffset +=
209 0 : int32_t(aNewWord.Length()) - int32_t(aOldWord.Length());
210 0 : if(selOffset < begin) selOffset=begin;
211 : }
212 0 : mTsDoc->SetSelection(begin, end-begin);
213 0 : mTsDoc->InsertText(&newWord);
214 0 : mTsDoc->GetCurrentTextBlock(&str);
215 0 : end += (aNewWord.Length() - aOldWord.Length()); // recursion was cute in GEB, not here.
216 : }
217 : }
218 0 : currOffset = end;
219 0 : }while(currOffset != -1);
220 0 : mTsDoc->NextBlock();
221 0 : currentBlock++;
222 0 : currOffset=0;
223 : }
224 :
225 : // We are done replacing. Put the selection point back where we found it (or equivalent);
226 0 : result = mTsDoc->FirstBlock();
227 0 : currentBlock = 0;
228 0 : while(( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) &&(currentBlock < startBlock)){
229 0 : mTsDoc->NextBlock();
230 : }
231 :
232 : //After we have moved to the block where the first occurrence of replace was done, put the
233 : //selection to the next word following it. In case there is no word following it i.e if it happens
234 : //to be the last word in that block, then move to the next block and put the selection to the
235 : //first word in that block, otherwise when the Setupdoc() is called, it queries the LastSelectedBlock()
236 : //and the selection offset of the last occurrence of the replaced word is taken instead of the first
237 : //occurrence and things get messed up as reported in the bug 244969
238 :
239 0 : if( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ){
240 0 : nsString str;
241 0 : result = mTsDoc->GetCurrentTextBlock(&str);
242 0 : result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
243 0 : if(end == -1)
244 : {
245 0 : mTsDoc->NextBlock();
246 0 : selOffset=0;
247 0 : result = mTsDoc->GetCurrentTextBlock(&str);
248 0 : result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
249 0 : mTsDoc->SetSelection(begin, 0);
250 : }
251 : else
252 0 : mTsDoc->SetSelection(begin, 0);
253 : }
254 : }
255 : else{
256 0 : mTsDoc->InsertText(&newWord);
257 : }
258 0 : return NS_OK;
259 : }
260 :
261 : NS_IMETHODIMP
262 0 : mozSpellChecker::IgnoreAll(const nsAString &aWord)
263 : {
264 0 : if(mPersonalDictionary){
265 0 : mPersonalDictionary->IgnoreWord(PromiseFlatString(aWord).get());
266 : }
267 0 : return NS_OK;
268 : }
269 :
270 : NS_IMETHODIMP
271 0 : mozSpellChecker::AddWordToPersonalDictionary(const nsAString &aWord)
272 : {
273 : nsresult res;
274 0 : char16_t empty=0;
275 0 : if (!mPersonalDictionary)
276 0 : return NS_ERROR_NULL_POINTER;
277 0 : res = mPersonalDictionary->AddWord(PromiseFlatString(aWord).get(),&empty);
278 0 : return res;
279 : }
280 :
281 : NS_IMETHODIMP
282 0 : mozSpellChecker::RemoveWordFromPersonalDictionary(const nsAString &aWord)
283 : {
284 : nsresult res;
285 0 : char16_t empty=0;
286 0 : if (!mPersonalDictionary)
287 0 : return NS_ERROR_NULL_POINTER;
288 0 : res = mPersonalDictionary->RemoveWord(PromiseFlatString(aWord).get(),&empty);
289 0 : return res;
290 : }
291 :
292 : NS_IMETHODIMP
293 0 : mozSpellChecker::GetPersonalDictionary(nsTArray<nsString> *aWordList)
294 : {
295 0 : if(!aWordList || !mPersonalDictionary)
296 0 : return NS_ERROR_NULL_POINTER;
297 :
298 0 : nsCOMPtr<nsIStringEnumerator> words;
299 0 : mPersonalDictionary->GetWordList(getter_AddRefs(words));
300 :
301 : bool hasMore;
302 0 : nsAutoString word;
303 0 : while (NS_SUCCEEDED(words->HasMore(&hasMore)) && hasMore) {
304 0 : words->GetNext(word);
305 0 : aWordList->AppendElement(word);
306 : }
307 0 : return NS_OK;
308 : }
309 :
310 : NS_IMETHODIMP
311 2 : mozSpellChecker::GetDictionaryList(nsTArray<nsString> *aDictionaryList)
312 : {
313 2 : if (XRE_IsContentProcess()) {
314 0 : ContentChild *child = ContentChild::GetSingleton();
315 0 : child->GetAvailableDictionaries(*aDictionaryList);
316 0 : return NS_OK;
317 : }
318 :
319 : nsresult rv;
320 :
321 : // For catching duplicates
322 4 : nsTHashtable<nsStringHashKey> dictionaries;
323 :
324 4 : nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines;
325 2 : rv = GetEngineList(&spellCheckingEngines);
326 2 : NS_ENSURE_SUCCESS(rv, rv);
327 :
328 4 : for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) {
329 4 : nsCOMPtr<mozISpellCheckingEngine> engine = spellCheckingEngines[i];
330 :
331 2 : uint32_t count = 0;
332 2 : char16_t **words = nullptr;
333 2 : engine->GetDictionaryList(&words, &count);
334 4 : for (uint32_t k = 0; k < count; k++) {
335 4 : nsAutoString dictName;
336 :
337 2 : dictName.Assign(words[k]);
338 :
339 : // Skip duplicate dictionaries. Only take the first one
340 : // for each name.
341 2 : if (dictionaries.Contains(dictName))
342 0 : continue;
343 :
344 2 : dictionaries.PutEntry(dictName);
345 :
346 2 : if (!aDictionaryList->AppendElement(dictName)) {
347 0 : NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
348 0 : return NS_ERROR_OUT_OF_MEMORY;
349 : }
350 : }
351 :
352 2 : NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
353 : }
354 :
355 2 : return NS_OK;
356 : }
357 :
358 : NS_IMETHODIMP
359 0 : mozSpellChecker::GetCurrentDictionary(nsAString &aDictionary)
360 : {
361 0 : if (XRE_IsContentProcess()) {
362 0 : aDictionary = mCurrentDictionary;
363 0 : return NS_OK;
364 : }
365 :
366 0 : if (!mSpellCheckingEngine) {
367 0 : aDictionary.Truncate();
368 0 : return NS_OK;
369 : }
370 :
371 0 : nsXPIDLString dictname;
372 0 : mSpellCheckingEngine->GetDictionary(getter_Copies(dictname));
373 0 : aDictionary = dictname;
374 0 : return NS_OK;
375 : }
376 :
377 : NS_IMETHODIMP
378 0 : mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary)
379 : {
380 0 : if (XRE_IsContentProcess()) {
381 0 : nsString wrappedDict = nsString(aDictionary);
382 : bool isSuccess;
383 0 : mEngine->SendSetDictionary(wrappedDict, &isSuccess);
384 0 : if (!isSuccess) {
385 0 : mCurrentDictionary.Truncate();
386 0 : return NS_ERROR_NOT_AVAILABLE;
387 : }
388 :
389 0 : mCurrentDictionary = wrappedDict;
390 0 : return NS_OK;
391 : }
392 :
393 : // Calls to mozISpellCheckingEngine::SetDictionary might destroy us
394 0 : RefPtr<mozSpellChecker> kungFuDeathGrip = this;
395 :
396 0 : mSpellCheckingEngine = nullptr;
397 :
398 0 : if (aDictionary.IsEmpty()) {
399 0 : return NS_OK;
400 : }
401 :
402 : nsresult rv;
403 0 : nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines;
404 0 : rv = GetEngineList(&spellCheckingEngines);
405 0 : NS_ENSURE_SUCCESS(rv, rv);
406 :
407 0 : for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) {
408 : // We must set mSpellCheckingEngine before we call SetDictionary, since
409 : // SetDictionary calls back to this spell checker to check if the
410 : // dictionary was set
411 0 : mSpellCheckingEngine = spellCheckingEngines[i];
412 :
413 0 : rv = mSpellCheckingEngine->SetDictionary(PromiseFlatString(aDictionary).get());
414 :
415 0 : if (NS_SUCCEEDED(rv)) {
416 0 : nsCOMPtr<mozIPersonalDictionary> personalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
417 0 : mSpellCheckingEngine->SetPersonalDictionary(personalDictionary.get());
418 :
419 0 : nsXPIDLString language;
420 0 : nsCOMPtr<mozISpellI18NManager> serv(do_GetService("@mozilla.org/spellchecker/i18nmanager;1", &rv));
421 0 : NS_ENSURE_SUCCESS(rv, rv);
422 0 : return serv->GetUtil(language.get(),getter_AddRefs(mConverter));
423 : }
424 : }
425 :
426 0 : mSpellCheckingEngine = nullptr;
427 :
428 : // We could not find any engine with the requested dictionary
429 0 : return NS_ERROR_NOT_AVAILABLE;
430 : }
431 :
432 : NS_IMETHODIMP_(RefPtr<GenericPromise>)
433 0 : mozSpellChecker::SetCurrentDictionaryFromList(const nsTArray<nsString>& aList)
434 : {
435 0 : if (aList.IsEmpty()) {
436 0 : return GenericPromise::CreateAndReject(NS_ERROR_INVALID_ARG, __func__);
437 : }
438 :
439 0 : if (XRE_IsContentProcess()) {
440 : // mCurrentDictionary will be set by RemoteSpellCheckEngineChild
441 0 : return mEngine->SetCurrentDictionaryFromList(aList);
442 : }
443 :
444 0 : for (auto& dictionary : aList) {
445 0 : nsresult rv = SetCurrentDictionary(dictionary);
446 0 : if (NS_SUCCEEDED(rv)) {
447 0 : return GenericPromise::CreateAndResolve(true, __func__);
448 : }
449 : }
450 : // We could not find any engine with the requested dictionary
451 0 : return GenericPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
452 : }
453 :
454 : nsresult
455 0 : mozSpellChecker::SetupDoc(int32_t *outBlockOffset)
456 : {
457 : nsresult rv;
458 :
459 : nsITextServicesDocument::TSDBlockSelectionStatus blockStatus;
460 : int32_t selOffset;
461 : int32_t selLength;
462 0 : *outBlockOffset = 0;
463 :
464 0 : if (!mFromStart)
465 : {
466 0 : rv = mTsDoc->LastSelectedBlock(&blockStatus, &selOffset, &selLength);
467 0 : if (NS_SUCCEEDED(rv) && (blockStatus != nsITextServicesDocument::eBlockNotFound))
468 : {
469 0 : switch (blockStatus)
470 : {
471 : case nsITextServicesDocument::eBlockOutside: // No TB in S, but found one before/after S.
472 : case nsITextServicesDocument::eBlockPartial: // S begins or ends in TB but extends outside of TB.
473 : // the TS doc points to the block we want.
474 0 : *outBlockOffset = selOffset + selLength;
475 0 : break;
476 :
477 : case nsITextServicesDocument::eBlockInside: // S extends beyond the start and end of TB.
478 : // we want the block after this one.
479 0 : rv = mTsDoc->NextBlock();
480 0 : *outBlockOffset = 0;
481 0 : break;
482 :
483 : case nsITextServicesDocument::eBlockContains: // TB contains entire S.
484 0 : *outBlockOffset = selOffset + selLength;
485 0 : break;
486 :
487 : case nsITextServicesDocument::eBlockNotFound: // There is no text block (TB) in or before the selection (S).
488 : default:
489 0 : NS_NOTREACHED("Shouldn't ever get this status");
490 : }
491 : }
492 : else //failed to get last sel block. Just start at beginning
493 : {
494 0 : rv = mTsDoc->FirstBlock();
495 0 : *outBlockOffset = 0;
496 : }
497 :
498 : }
499 : else // we want the first block
500 : {
501 0 : rv = mTsDoc->FirstBlock();
502 0 : mFromStart = false;
503 : }
504 0 : return rv;
505 : }
506 :
507 :
508 : // utility method to discover which block we're in. The TSDoc interface doesn't give
509 : // us this, because it can't assume a read-only document.
510 : // shamelessly stolen from nsTextServicesDocument
511 : nsresult
512 0 : mozSpellChecker::GetCurrentBlockIndex(nsITextServicesDocument *aDoc, int32_t *outBlockIndex)
513 : {
514 0 : int32_t blockIndex = 0;
515 0 : bool isDone = false;
516 0 : nsresult result = NS_OK;
517 :
518 0 : do
519 : {
520 0 : aDoc->PrevBlock();
521 :
522 0 : result = aDoc->IsDone(&isDone);
523 :
524 0 : if (!isDone)
525 0 : blockIndex ++;
526 :
527 0 : } while (NS_SUCCEEDED(result) && !isDone);
528 :
529 0 : *outBlockIndex = blockIndex;
530 :
531 0 : return result;
532 : }
533 :
534 : nsresult
535 2 : mozSpellChecker::GetEngineList(nsCOMArray<mozISpellCheckingEngine>* aSpellCheckingEngines)
536 : {
537 2 : MOZ_ASSERT(!XRE_IsContentProcess());
538 :
539 : nsresult rv;
540 : bool hasMoreEngines;
541 :
542 4 : nsCOMPtr<nsICategoryManager> catMgr = do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
543 2 : if (!catMgr)
544 0 : return NS_ERROR_NULL_POINTER;
545 :
546 4 : nsCOMPtr<nsISimpleEnumerator> catEntries;
547 :
548 : // Get contract IDs of registrated external spell-check engines and
549 : // append one of HunSpell at the end.
550 2 : rv = catMgr->EnumerateCategory("spell-check-engine", getter_AddRefs(catEntries));
551 2 : if (NS_FAILED(rv))
552 0 : return rv;
553 :
554 2 : while (NS_SUCCEEDED(catEntries->HasMoreElements(&hasMoreEngines)) &&
555 : hasMoreEngines) {
556 0 : nsCOMPtr<nsISupports> elem;
557 0 : rv = catEntries->GetNext(getter_AddRefs(elem));
558 :
559 0 : nsCOMPtr<nsISupportsCString> entry = do_QueryInterface(elem, &rv);
560 0 : if (NS_FAILED(rv))
561 0 : return rv;
562 :
563 0 : nsCString contractId;
564 0 : rv = entry->GetData(contractId);
565 0 : if (NS_FAILED(rv))
566 0 : return rv;
567 :
568 : // Try to load spellchecker engine. Ignore errors silently
569 : // except for the last one (HunSpell).
570 : nsCOMPtr<mozISpellCheckingEngine> engine =
571 0 : do_GetService(contractId.get(), &rv);
572 0 : if (NS_SUCCEEDED(rv)) {
573 0 : aSpellCheckingEngines->AppendObject(engine);
574 : }
575 : }
576 :
577 : // Try to load HunSpell spellchecker engine.
578 : nsCOMPtr<mozISpellCheckingEngine> engine =
579 4 : do_GetService(DEFAULT_SPELL_CHECKER, &rv);
580 2 : if (NS_FAILED(rv)) {
581 : // Fail if not succeeded to load HunSpell. Ignore errors
582 : // for external spellcheck engines.
583 0 : return rv;
584 : }
585 2 : aSpellCheckingEngines->AppendObject(engine);
586 :
587 2 : return NS_OK;
588 : }
|