tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }