mozSpellChecker.cpp (21824B)
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 "nsIStringEnumerator.h" 8 #include "nsICategoryManager.h" 9 #include "nsISupportsPrimitives.h" 10 #include "nsISimpleEnumerator.h" 11 #include "mozEnglishWordUtils.h" 12 #include "mozilla/dom/ContentChild.h" 13 #include "mozilla/Logging.h" 14 #include "mozilla/PRemoteSpellcheckEngineChild.h" 15 #include "mozilla/TextServicesDocument.h" 16 #include "nsXULAppAPI.h" 17 #include "RemoteSpellCheckEngineChild.h" 18 19 using mozilla::AssertedCast; 20 using mozilla::GenericPromise; 21 using mozilla::LogLevel; 22 using mozilla::RemoteSpellcheckEngineChild; 23 using mozilla::TextServicesDocument; 24 using mozilla::dom::ContentChild; 25 26 #define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1" 27 28 static mozilla::LazyLogModule sSpellChecker("SpellChecker"); 29 30 NS_IMPL_CYCLE_COLLECTION(mozSpellChecker, mTextServicesDocument, 31 mPersonalDictionary) 32 33 mozSpellChecker::mozSpellChecker() : mEngine(nullptr) {} 34 35 mozSpellChecker::~mozSpellChecker() { 36 if (mPersonalDictionary) { 37 // mPersonalDictionary->Save(); 38 mPersonalDictionary->EndSession(); 39 } 40 mSpellCheckingEngine = nullptr; 41 mPersonalDictionary = nullptr; 42 43 if (mEngine) { 44 MOZ_ASSERT(XRE_IsContentProcess()); 45 RemoteSpellcheckEngineChild::Send__delete__(mEngine); 46 MOZ_ASSERT(!mEngine); 47 } 48 } 49 50 nsresult mozSpellChecker::Init() { 51 mSpellCheckingEngine = nullptr; 52 if (XRE_IsContentProcess()) { 53 mozilla::dom::ContentChild* contentChild = 54 mozilla::dom::ContentChild::GetSingleton(); 55 MOZ_ASSERT(contentChild); 56 // mEngine is nulled in RemoteSpellcheckEngineChild(), so we don't need to 57 // worry about SendPRemoveSpellcheckEngineConstructor failing 58 mEngine = new RemoteSpellcheckEngineChild(this); 59 MOZ_ALWAYS_TRUE( 60 contentChild->SendPRemoteSpellcheckEngineConstructor(mEngine)); 61 } else { 62 mPersonalDictionary = 63 do_GetService("@mozilla.org/spellchecker/personaldictionary;1"); 64 } 65 66 return NS_OK; 67 } 68 69 TextServicesDocument* mozSpellChecker::GetTextServicesDocument() { 70 return mTextServicesDocument; 71 } 72 73 nsresult mozSpellChecker::SetDocument( 74 TextServicesDocument* aTextServicesDocument, bool aFromStartofDoc) { 75 MOZ_LOG(sSpellChecker, LogLevel::Debug, ("%s", __FUNCTION__)); 76 77 mTextServicesDocument = aTextServicesDocument; 78 mFromStart = aFromStartofDoc; 79 return NS_OK; 80 } 81 82 nsresult mozSpellChecker::NextMisspelledWord(nsAString& aWord, 83 nsTArray<nsString>& aSuggestions) { 84 if (NS_WARN_IF(!mConverter)) { 85 return NS_ERROR_NOT_INITIALIZED; 86 } 87 88 int32_t selOffset; 89 nsresult result; 90 result = SetupDoc(&selOffset); 91 if (NS_FAILED(result)) return result; 92 93 bool done; 94 while (NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done) { 95 int32_t begin, end; 96 nsAutoString str; 97 mTextServicesDocument->GetCurrentTextBlock(str); 98 while (mConverter->FindNextWord(str, selOffset, &begin, &end)) { 99 const nsDependentSubstring currWord(str, begin, end - begin); 100 bool isMisspelled; 101 result = CheckWord(currWord, &isMisspelled, &aSuggestions); 102 if (NS_WARN_IF(NS_FAILED(result))) { 103 return result; 104 } 105 if (isMisspelled) { 106 aWord = currWord; 107 MOZ_KnownLive(mTextServicesDocument) 108 ->SetSelection(AssertedCast<uint32_t>(begin), 109 AssertedCast<uint32_t>(end - begin)); 110 // After ScrollSelectionIntoView(), the pending notifications might 111 // be flushed and PresShell/PresContext/Frames may be dead. 112 // See bug 418470. 113 mTextServicesDocument->ScrollSelectionIntoView(); 114 return NS_OK; 115 } 116 selOffset = end; 117 } 118 mTextServicesDocument->NextBlock(); 119 selOffset = 0; 120 } 121 return NS_OK; 122 } 123 124 RefPtr<mozilla::CheckWordPromise> mozSpellChecker::CheckWords( 125 const nsTArray<nsString>& aWords) { 126 if (XRE_IsContentProcess()) { 127 return mEngine->CheckWords(aWords); 128 } 129 130 nsTArray<bool> misspells; 131 misspells.SetCapacity(aWords.Length()); 132 for (auto& word : aWords) { 133 bool misspelled; 134 nsresult rv = CheckWord(word, &misspelled, nullptr); 135 if (NS_WARN_IF(NS_FAILED(rv))) { 136 return mozilla::CheckWordPromise::CreateAndReject(rv, __func__); 137 } 138 misspells.AppendElement(misspelled); 139 } 140 return mozilla::CheckWordPromise::CreateAndResolve(std::move(misspells), 141 __func__); 142 } 143 144 nsresult mozSpellChecker::CheckWord(const nsAString& aWord, bool* aIsMisspelled, 145 nsTArray<nsString>* aSuggestions) { 146 if (XRE_IsContentProcess()) { 147 // Use async version (CheckWords or Suggest) on content process 148 return NS_ERROR_FAILURE; 149 } 150 151 nsresult result; 152 bool correct; 153 154 if (!mSpellCheckingEngine) { 155 return NS_ERROR_NULL_POINTER; 156 } 157 *aIsMisspelled = false; 158 result = mSpellCheckingEngine->Check(aWord, &correct); 159 NS_ENSURE_SUCCESS(result, result); 160 if (!correct) { 161 if (aSuggestions) { 162 result = mSpellCheckingEngine->Suggest(aWord, *aSuggestions); 163 NS_ENSURE_SUCCESS(result, result); 164 } 165 *aIsMisspelled = true; 166 } 167 return NS_OK; 168 } 169 170 RefPtr<mozilla::SuggestionsPromise> mozSpellChecker::Suggest( 171 const nsAString& aWord, uint32_t aMaxCount) { 172 if (XRE_IsContentProcess()) { 173 return mEngine->SendSuggest(aWord, aMaxCount) 174 ->Then( 175 mozilla::GetCurrentSerialEventTarget(), __func__, 176 [](nsTArray<nsString>&& aSuggestions) { 177 return mozilla::SuggestionsPromise::CreateAndResolve( 178 std::move(aSuggestions), __func__); 179 }, 180 [](mozilla::ipc::ResponseRejectReason&& aReason) { 181 return mozilla::SuggestionsPromise::CreateAndReject( 182 NS_ERROR_NOT_AVAILABLE, __func__); 183 }); 184 } 185 186 if (!mSpellCheckingEngine) { 187 return mozilla::SuggestionsPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, 188 __func__); 189 } 190 191 bool correct; 192 nsresult rv = mSpellCheckingEngine->Check(aWord, &correct); 193 if (NS_FAILED(rv)) { 194 return mozilla::SuggestionsPromise::CreateAndReject(rv, __func__); 195 } 196 nsTArray<nsString> suggestions; 197 if (!correct) { 198 rv = mSpellCheckingEngine->Suggest(aWord, suggestions); 199 if (NS_FAILED(rv)) { 200 return mozilla::SuggestionsPromise::CreateAndReject(rv, __func__); 201 } 202 if (suggestions.Length() > aMaxCount) { 203 suggestions.TruncateLength(aMaxCount); 204 } 205 } 206 return mozilla::SuggestionsPromise::CreateAndResolve(std::move(suggestions), 207 __func__); 208 } 209 210 nsresult mozSpellChecker::Replace(const nsAString& aOldWord, 211 const nsAString& aNewWord, 212 bool aAllOccurrences) { 213 if (NS_WARN_IF(!mConverter)) { 214 return NS_ERROR_NOT_INITIALIZED; 215 } 216 217 if (!aAllOccurrences) { 218 MOZ_KnownLive(mTextServicesDocument)->InsertText(aNewWord); 219 return NS_OK; 220 } 221 222 int32_t selOffset; 223 int32_t startBlock; 224 int32_t begin, end; 225 bool done; 226 nsresult result; 227 228 // find out where we are 229 result = SetupDoc(&selOffset); 230 if (NS_WARN_IF(NS_FAILED(result))) { 231 return result; 232 } 233 result = GetCurrentBlockIndex(mTextServicesDocument, &startBlock); 234 if (NS_WARN_IF(NS_FAILED(result))) { 235 return result; 236 } 237 238 // start at the beginning 239 result = mTextServicesDocument->FirstBlock(); 240 if (NS_WARN_IF(NS_FAILED(result))) { 241 return result; 242 } 243 int32_t currOffset = 0; 244 int32_t currentBlock = 0; 245 int32_t wordLengthDifference = 246 AssertedCast<int32_t>(static_cast<int64_t>(aNewWord.Length()) - 247 static_cast<int64_t>(aOldWord.Length())); 248 while (NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done) { 249 nsAutoString str; 250 mTextServicesDocument->GetCurrentTextBlock(str); 251 while (mConverter->FindNextWord(str, currOffset, &begin, &end)) { 252 if (aOldWord.Equals(Substring(str, begin, end - begin))) { 253 // if we are before the current selection point but in the same 254 // block move the selection point forwards 255 if (currentBlock == startBlock && begin < selOffset) { 256 selOffset += wordLengthDifference; 257 if (selOffset < begin) { 258 selOffset = begin; 259 } 260 } 261 // Don't keep running if selecting or inserting text fails because 262 // it may cause infinite loop. 263 if (NS_WARN_IF(NS_FAILED( 264 MOZ_KnownLive(mTextServicesDocument) 265 ->SetSelection(AssertedCast<uint32_t>(begin), 266 AssertedCast<uint32_t>(end - begin))))) { 267 return NS_ERROR_FAILURE; 268 } 269 if (NS_WARN_IF(NS_FAILED( 270 MOZ_KnownLive(mTextServicesDocument)->InsertText(aNewWord)))) { 271 return NS_ERROR_FAILURE; 272 } 273 mTextServicesDocument->GetCurrentTextBlock(str); 274 end += wordLengthDifference; // recursion was cute in GEB, not here. 275 } 276 currOffset = end; 277 } 278 mTextServicesDocument->NextBlock(); 279 currentBlock++; 280 currOffset = 0; 281 } 282 283 // We are done replacing. Put the selection point back where we found it 284 // (or equivalent); 285 result = mTextServicesDocument->FirstBlock(); 286 if (NS_WARN_IF(NS_FAILED(result))) { 287 return result; 288 } 289 currentBlock = 0; 290 while (NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done && 291 currentBlock < startBlock) { 292 mTextServicesDocument->NextBlock(); 293 } 294 295 // After we have moved to the block where the first occurrence of replace 296 // was done, put the selection to the next word following it. In case there 297 // is no word following it i.e if it happens to be the last word in that 298 // block, then move to the next block and put the selection to the first 299 // word in that block, otherwise when the Setupdoc() is called, it queries 300 // the LastSelectedBlock() and the selection offset of the last occurrence 301 // of the replaced word is taken instead of the first occurrence and things 302 // get messed up as reported in the bug 244969 303 304 if (NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done) { 305 nsAutoString str; 306 mTextServicesDocument->GetCurrentTextBlock(str); 307 if (mConverter->FindNextWord(str, selOffset, &begin, &end)) { 308 MOZ_KnownLive(mTextServicesDocument) 309 ->SetSelection(AssertedCast<uint32_t>(begin), 0); 310 return NS_OK; 311 } 312 mTextServicesDocument->NextBlock(); 313 mTextServicesDocument->GetCurrentTextBlock(str); 314 if (mConverter->FindNextWord(str, 0, &begin, &end)) { 315 MOZ_KnownLive(mTextServicesDocument) 316 ->SetSelection(AssertedCast<uint32_t>(begin), 0); 317 } 318 } 319 return NS_OK; 320 } 321 322 nsresult mozSpellChecker::IgnoreAll(const nsAString& aWord) { 323 if (mPersonalDictionary) { 324 mPersonalDictionary->IgnoreWord(aWord); 325 } 326 return NS_OK; 327 } 328 329 nsresult mozSpellChecker::AddWordToPersonalDictionary(const nsAString& aWord) { 330 nsresult res; 331 if (NS_WARN_IF(!mPersonalDictionary)) { 332 return NS_ERROR_NOT_INITIALIZED; 333 } 334 res = mPersonalDictionary->AddWord(aWord); 335 return res; 336 } 337 338 nsresult mozSpellChecker::RemoveWordFromPersonalDictionary( 339 const nsAString& aWord) { 340 nsresult res; 341 if (NS_WARN_IF(!mPersonalDictionary)) { 342 return NS_ERROR_NOT_INITIALIZED; 343 } 344 res = mPersonalDictionary->RemoveWord(aWord); 345 return res; 346 } 347 348 nsresult mozSpellChecker::GetDictionaryList( 349 nsTArray<nsCString>* aDictionaryList) { 350 MOZ_ASSERT(aDictionaryList->IsEmpty()); 351 if (XRE_IsContentProcess()) { 352 ContentChild* child = ContentChild::GetSingleton(); 353 child->GetAvailableDictionaries(*aDictionaryList); 354 return NS_OK; 355 } 356 357 nsresult rv; 358 359 // For catching duplicates 360 nsTHashSet<nsCString> dictionaries; 361 362 nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines; 363 rv = GetEngineList(&spellCheckingEngines); 364 NS_ENSURE_SUCCESS(rv, rv); 365 366 for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) { 367 nsCOMPtr<mozISpellCheckingEngine> engine = spellCheckingEngines[i]; 368 369 nsTArray<nsCString> dictNames; 370 engine->GetDictionaryList(dictNames); 371 for (auto& dictName : dictNames) { 372 // Skip duplicate dictionaries. Only take the first one 373 // for each name. 374 if (!dictionaries.EnsureInserted(dictName)) continue; 375 376 aDictionaryList->AppendElement(dictName); 377 } 378 } 379 380 return NS_OK; 381 } 382 383 nsresult mozSpellChecker::GetCurrentDictionaries( 384 nsTArray<nsCString>& aDictionaries) { 385 if (XRE_IsContentProcess()) { 386 aDictionaries = mCurrentDictionaries.Clone(); 387 return NS_OK; 388 } 389 390 if (!mSpellCheckingEngine) { 391 aDictionaries.Clear(); 392 return NS_OK; 393 } 394 395 return mSpellCheckingEngine->GetDictionaries(aDictionaries); 396 } 397 398 nsresult mozSpellChecker::SetCurrentDictionary(const nsACString& aDictionary) { 399 if (XRE_IsContentProcess()) { 400 mCurrentDictionaries.Clear(); 401 bool isSuccess; 402 mEngine->SendSetDictionary(aDictionary, &isSuccess); 403 if (!isSuccess) { 404 return NS_ERROR_NOT_AVAILABLE; 405 } 406 407 mCurrentDictionaries.AppendElement(aDictionary); 408 return NS_OK; 409 } 410 411 // Calls to mozISpellCheckingEngine::SetDictionary might destroy us 412 RefPtr<mozSpellChecker> kungFuDeathGrip = this; 413 414 mSpellCheckingEngine = nullptr; 415 416 if (aDictionary.IsEmpty()) { 417 return NS_OK; 418 } 419 420 nsresult rv; 421 nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines; 422 rv = GetEngineList(&spellCheckingEngines); 423 NS_ENSURE_SUCCESS(rv, rv); 424 425 nsTArray<nsCString> dictionaries; 426 dictionaries.AppendElement(aDictionary); 427 for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) { 428 // We must set mSpellCheckingEngine before we call SetDictionaries, since 429 // SetDictionaries calls back to this spell checker to check if the 430 // dictionary was set 431 mSpellCheckingEngine = spellCheckingEngines[i]; 432 rv = mSpellCheckingEngine->SetDictionaries(dictionaries); 433 434 if (NS_SUCCEEDED(rv)) { 435 nsCOMPtr<mozIPersonalDictionary> personalDictionary = 436 do_GetService("@mozilla.org/spellchecker/personaldictionary;1"); 437 mSpellCheckingEngine->SetPersonalDictionary(personalDictionary); 438 439 mConverter = new mozEnglishWordUtils; 440 return NS_OK; 441 } 442 } 443 444 mSpellCheckingEngine = nullptr; 445 446 // We could not find any engine with the requested dictionary 447 return NS_ERROR_NOT_AVAILABLE; 448 } 449 450 RefPtr<GenericPromise> mozSpellChecker::SetCurrentDictionaries( 451 const nsTArray<nsCString>& aDictionaries) { 452 if (XRE_IsContentProcess()) { 453 if (!mEngine) { 454 mCurrentDictionaries.Clear(); 455 return GenericPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__); 456 } 457 458 // mCurrentDictionaries will be set by RemoteSpellCheckEngineChild 459 return mEngine->SetCurrentDictionaries(aDictionaries); 460 } 461 462 // Calls to mozISpellCheckingEngine::SetDictionary might destroy us 463 RefPtr<mozSpellChecker> kungFuDeathGrip = this; 464 465 mSpellCheckingEngine = nullptr; 466 467 if (aDictionaries.IsEmpty()) { 468 return GenericPromise::CreateAndResolve(true, __func__); 469 } 470 471 nsresult rv; 472 nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines; 473 rv = GetEngineList(&spellCheckingEngines); 474 if (NS_FAILED(rv)) { 475 return GenericPromise::CreateAndReject(rv, __func__); 476 } 477 478 for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) { 479 // We must set mSpellCheckingEngine before we call SetDictionaries, since 480 // SetDictionaries calls back to this spell checker to check if the 481 // dictionary was set 482 mSpellCheckingEngine = spellCheckingEngines[i]; 483 rv = mSpellCheckingEngine->SetDictionaries(aDictionaries); 484 485 if (NS_SUCCEEDED(rv)) { 486 mCurrentDictionaries = aDictionaries.Clone(); 487 488 nsCOMPtr<mozIPersonalDictionary> personalDictionary = 489 do_GetService("@mozilla.org/spellchecker/personaldictionary;1"); 490 mSpellCheckingEngine->SetPersonalDictionary(personalDictionary); 491 492 mConverter = new mozEnglishWordUtils; 493 return GenericPromise::CreateAndResolve(true, __func__); 494 } 495 } 496 497 mSpellCheckingEngine = nullptr; 498 499 // We could not find any engine with the requested dictionary 500 return GenericPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__); 501 } 502 503 RefPtr<GenericPromise> mozSpellChecker::SetCurrentDictionaryFromList( 504 const nsTArray<nsCString>& aList) { 505 if (aList.IsEmpty()) { 506 return GenericPromise::CreateAndReject(NS_ERROR_INVALID_ARG, __func__); 507 } 508 509 if (XRE_IsContentProcess()) { 510 if (!mEngine) { 511 mCurrentDictionaries.Clear(); 512 return GenericPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__); 513 } 514 515 // mCurrentDictionaries will be set by RemoteSpellCheckEngineChild 516 return mEngine->SetCurrentDictionaryFromList(aList); 517 } 518 519 for (auto& dictionary : aList) { 520 nsresult rv = SetCurrentDictionary(dictionary); 521 if (NS_SUCCEEDED(rv)) { 522 return GenericPromise::CreateAndResolve(true, __func__); 523 } 524 } 525 // We could not find any engine with the requested dictionary 526 return GenericPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__); 527 } 528 529 nsresult mozSpellChecker::SetupDoc(int32_t* outBlockOffset) { 530 nsresult rv; 531 532 TextServicesDocument::BlockSelectionStatus blockStatus; 533 *outBlockOffset = 0; 534 535 if (!mFromStart) { 536 uint32_t selOffset, selLength; 537 rv = MOZ_KnownLive(mTextServicesDocument) 538 ->LastSelectedBlock(&blockStatus, &selOffset, &selLength); 539 if (NS_SUCCEEDED(rv) && 540 blockStatus != 541 TextServicesDocument::BlockSelectionStatus::eBlockNotFound) { 542 switch (blockStatus) { 543 // No TB in S, but found one before/after S. 544 case TextServicesDocument::BlockSelectionStatus::eBlockOutside: 545 // S begins or ends in TB but extends outside of TB. 546 case TextServicesDocument::BlockSelectionStatus::eBlockPartial: 547 // the TS doc points to the block we want. 548 if (NS_WARN_IF(selOffset == UINT32_MAX) || 549 NS_WARN_IF(selLength == UINT32_MAX)) { 550 rv = mTextServicesDocument->FirstBlock(); 551 *outBlockOffset = 0; 552 break; 553 } 554 *outBlockOffset = AssertedCast<int32_t>(selOffset + selLength); 555 break; 556 557 // S extends beyond the start and end of TB. 558 case TextServicesDocument::BlockSelectionStatus::eBlockInside: 559 // we want the block after this one. 560 rv = mTextServicesDocument->NextBlock(); 561 *outBlockOffset = 0; 562 break; 563 564 // TB contains entire S. 565 case TextServicesDocument::BlockSelectionStatus::eBlockContains: 566 if (NS_WARN_IF(selOffset == UINT32_MAX) || 567 NS_WARN_IF(selLength == UINT32_MAX)) { 568 rv = mTextServicesDocument->FirstBlock(); 569 *outBlockOffset = 0; 570 break; 571 } 572 *outBlockOffset = AssertedCast<int32_t>(selOffset + selLength); 573 break; 574 575 // There is no text block (TB) in or before the selection (S). 576 case TextServicesDocument::BlockSelectionStatus::eBlockNotFound: 577 default: 578 MOZ_ASSERT_UNREACHABLE("Shouldn't ever get this status"); 579 } 580 } 581 // Failed to get last sel block. Just start at beginning 582 else { 583 rv = mTextServicesDocument->FirstBlock(); 584 *outBlockOffset = 0; 585 } 586 587 } 588 // We want the first block 589 else { 590 rv = mTextServicesDocument->FirstBlock(); 591 mFromStart = false; 592 } 593 return rv; 594 } 595 596 // utility method to discover which block we're in. The TSDoc interface doesn't 597 // give us this, because it can't assume a read-only document. shamelessly 598 // stolen from nsTextServicesDocument 599 nsresult mozSpellChecker::GetCurrentBlockIndex( 600 TextServicesDocument* aTextServicesDocument, int32_t* aOutBlockIndex) { 601 int32_t blockIndex = 0; 602 bool isDone = false; 603 nsresult result = NS_OK; 604 605 do { 606 aTextServicesDocument->PrevBlock(); 607 result = aTextServicesDocument->IsDone(&isDone); 608 if (!isDone) { 609 blockIndex++; 610 } 611 } while (NS_SUCCEEDED(result) && !isDone); 612 613 *aOutBlockIndex = blockIndex; 614 615 return result; 616 } 617 618 nsresult mozSpellChecker::GetEngineList( 619 nsCOMArray<mozISpellCheckingEngine>* aSpellCheckingEngines) { 620 MOZ_ASSERT(!XRE_IsContentProcess()); 621 622 nsresult rv; 623 bool hasMoreEngines; 624 625 nsCOMPtr<nsICategoryManager> catMgr = 626 do_GetService(NS_CATEGORYMANAGER_CONTRACTID); 627 if (!catMgr) return NS_ERROR_NULL_POINTER; 628 629 nsCOMPtr<nsISimpleEnumerator> catEntries; 630 631 // Get contract IDs of registrated external spell-check engines and 632 // append one of HunSpell at the end. 633 rv = catMgr->EnumerateCategory("spell-check-engine", 634 getter_AddRefs(catEntries)); 635 if (NS_FAILED(rv)) return rv; 636 637 while (NS_SUCCEEDED(catEntries->HasMoreElements(&hasMoreEngines)) && 638 hasMoreEngines) { 639 nsCOMPtr<nsISupports> elem; 640 rv = catEntries->GetNext(getter_AddRefs(elem)); 641 642 nsCOMPtr<nsISupportsCString> entry = do_QueryInterface(elem, &rv); 643 if (NS_FAILED(rv)) return rv; 644 645 nsCString contractId; 646 rv = entry->GetData(contractId); 647 if (NS_FAILED(rv)) return rv; 648 649 // Try to load spellchecker engine. Ignore errors silently 650 // except for the last one (HunSpell). 651 nsCOMPtr<mozISpellCheckingEngine> engine = 652 do_GetService(contractId.get(), &rv); 653 if (NS_SUCCEEDED(rv)) { 654 aSpellCheckingEngines->AppendObject(engine); 655 } 656 } 657 658 // Try to load HunSpell spellchecker engine. 659 nsCOMPtr<mozISpellCheckingEngine> engine = 660 do_GetService(DEFAULT_SPELL_CHECKER, &rv); 661 if (NS_FAILED(rv)) { 662 // Fail if not succeeded to load HunSpell. Ignore errors 663 // for external spellcheck engines. 664 return rv; 665 } 666 aSpellCheckingEngines->AppendObject(engine); 667 668 return NS_OK; 669 }