tor-browser

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

EditorSpellCheck.cpp (40409B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 sts=2 sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "EditorSpellCheck.h"
      8 
      9 #include "EditorBase.h"            // for EditorBase
     10 #include "HTMLEditor.h"            // for HTMLEditor
     11 #include "TextServicesDocument.h"  // for TextServicesDocument
     12 
     13 #include "mozilla/dom/Element.h"  // for Element
     14 #include "mozilla/dom/Promise.h"
     15 #include "mozilla/dom/Selection.h"
     16 #include "mozilla/dom/StaticRange.h"
     17 #include "mozilla/intl/Locale.h"         // for mozilla::intl::Locale
     18 #include "mozilla/intl/LocaleService.h"  // for retrieving app locale
     19 #include "mozilla/intl/OSPreferences.h"  // for mozilla::intl::OSPreferences
     20 #include "mozilla/Logging.h"             // for mozilla::LazyLogModule
     21 #include "mozilla/mozalloc.h"            // for operator delete, etc
     22 #include "mozilla/mozSpellChecker.h"     // for mozSpellChecker
     23 #include "mozilla/Preferences.h"         // for Preferences
     24 
     25 #include "nsAString.h"                // for nsAString::IsEmpty, etc
     26 #include "nsComponentManagerUtils.h"  // for do_CreateInstance
     27 #include "nsDebug.h"                  // for NS_ENSURE_TRUE, etc
     28 #include "nsDependentSubstring.h"     // for Substring
     29 #include "nsError.h"                  // for NS_ERROR_NOT_INITIALIZED, etc
     30 #include "nsIContent.h"               // for nsIContent
     31 #include "nsIContentPrefService2.h"   // for nsIContentPrefService2, etc
     32 #include "mozilla/dom/Document.h"     // for Document
     33 #include "nsIEditor.h"                // for nsIEditor
     34 #include "nsILoadContext.h"
     35 #include "nsISupports.h"       // for nsISupports
     36 #include "nsISupportsUtils.h"  // for NS_ADDREF
     37 #include "nsIURI.h"            // for nsIURI
     38 #include "nsThreadUtils.h"     // for GetMainThreadSerialEventTarget
     39 #include "nsVariant.h"         // for nsIWritableVariant, etc
     40 #include "nsLiteralString.h"   // for NS_LITERAL_STRING, etc
     41 #include "nsRange.h"
     42 #include "nsReadableUtils.h"        // for ToNewUnicode, EmptyString, etc
     43 #include "nsServiceManagerUtils.h"  // for do_GetService
     44 #include "nsString.h"               // for nsAutoString, nsString, etc
     45 #include "nsStringFwd.h"            // for nsAFlatString
     46 #include "nsStyleUtil.h"            // for nsStyleUtil
     47 #include "nsXULAppAPI.h"            // for XRE_GetProcessType
     48 
     49 namespace mozilla {
     50 
     51 using namespace dom;
     52 using intl::LocaleService;
     53 using intl::OSPreferences;
     54 
     55 static mozilla::LazyLogModule sEditorSpellChecker("EditorSpellChecker");
     56 
     57 class UpdateDictionaryHolder {
     58 private:
     59  EditorSpellCheck* mSpellCheck;
     60 
     61 public:
     62  explicit UpdateDictionaryHolder(EditorSpellCheck* esc) : mSpellCheck(esc) {
     63    if (mSpellCheck) {
     64      mSpellCheck->BeginUpdateDictionary();
     65    }
     66  }
     67 
     68  ~UpdateDictionaryHolder() {
     69    if (mSpellCheck) {
     70      mSpellCheck->EndUpdateDictionary();
     71    }
     72  }
     73 };
     74 
     75 #define CPS_PREF_NAME u"spellcheck.lang"_ns
     76 
     77 /**
     78 * Gets the URI of aEditor's document.
     79 */
     80 static nsIURI* GetDocumentURI(EditorBase* aEditor) {
     81  MOZ_ASSERT(aEditor);
     82 
     83  Document* doc = aEditor->AsEditorBase()->GetDocument();
     84  if (NS_WARN_IF(!doc)) {
     85    return nullptr;
     86  }
     87 
     88  return doc->GetDocumentURI();
     89 }
     90 
     91 static nsILoadContext* GetLoadContext(nsIEditor* aEditor) {
     92  Document* doc = aEditor->AsEditorBase()->GetDocument();
     93  if (NS_WARN_IF(!doc)) {
     94    return nullptr;
     95  }
     96 
     97  return doc->GetLoadContext();
     98 }
     99 
    100 static nsCString DictionariesToString(
    101    const nsTArray<nsCString>& aDictionaries) {
    102  nsCString asString;
    103  for (const auto& dictionary : aDictionaries) {
    104    asString.Append(dictionary);
    105    asString.Append(',');
    106  }
    107  return asString;
    108 }
    109 
    110 static void StringToDictionaries(const nsCString& aString,
    111                                 nsTArray<nsCString>& aDictionaries) {
    112  nsTArray<nsCString> asDictionaries;
    113  for (const nsACString& token :
    114       nsCCharSeparatedTokenizer(aString, ',').ToRange()) {
    115    if (token.IsEmpty()) {
    116      continue;
    117    }
    118    aDictionaries.AppendElement(token);
    119  }
    120 }
    121 
    122 /**
    123 * Fetches the dictionary stored in content prefs and maintains state during the
    124 * fetch, which is asynchronous.
    125 */
    126 class DictionaryFetcher final : public nsIContentPrefCallback2 {
    127 public:
    128  NS_DECL_ISUPPORTS
    129 
    130  DictionaryFetcher(EditorSpellCheck* aSpellCheck,
    131                    nsIEditorSpellCheckCallback* aCallback, uint32_t aGroup)
    132      : mCallback(aCallback), mGroup(aGroup), mSpellCheck(aSpellCheck) {}
    133 
    134  NS_IMETHOD Fetch(nsIEditor* aEditor);
    135 
    136  NS_IMETHOD HandleResult(nsIContentPref* aPref) override {
    137    nsCOMPtr<nsIVariant> value;
    138    nsresult rv = aPref->GetValue(getter_AddRefs(value));
    139    NS_ENSURE_SUCCESS(rv, rv);
    140    nsCString asString;
    141    value->GetAsACString(asString);
    142    StringToDictionaries(asString, mDictionaries);
    143    return NS_OK;
    144  }
    145 
    146  NS_IMETHOD HandleCompletion(uint16_t reason) override {
    147    mSpellCheck->DictionaryFetched(this);
    148    return NS_OK;
    149  }
    150 
    151  NS_IMETHOD HandleError(nsresult error) override { return NS_OK; }
    152 
    153  nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
    154  uint32_t mGroup;
    155  RefPtr<nsAtom> mRootContentLang;
    156  RefPtr<nsAtom> mRootDocContentLang;
    157  nsTArray<nsCString> mDictionaries;
    158 
    159 private:
    160  ~DictionaryFetcher() {}
    161 
    162  RefPtr<EditorSpellCheck> mSpellCheck;
    163 };
    164 
    165 NS_IMPL_ISUPPORTS(DictionaryFetcher, nsIContentPrefCallback2)
    166 
    167 class ContentPrefInitializerRunnable final : public Runnable {
    168 public:
    169  ContentPrefInitializerRunnable(nsIEditor* aEditor,
    170                                 nsIContentPrefCallback2* aCallback)
    171      : Runnable("ContentPrefInitializerRunnable"),
    172        mEditorBase(aEditor->AsEditorBase()),
    173        mCallback(aCallback) {}
    174 
    175  NS_IMETHOD Run() override {
    176    if (mEditorBase->Destroyed()) {
    177      mCallback->HandleError(NS_ERROR_NOT_AVAILABLE);
    178      return NS_OK;
    179    }
    180 
    181    nsCOMPtr<nsIContentPrefService2> contentPrefService =
    182        do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
    183    if (NS_WARN_IF(!contentPrefService)) {
    184      mCallback->HandleError(NS_ERROR_NOT_AVAILABLE);
    185      return NS_OK;
    186    }
    187 
    188    nsCOMPtr<nsIURI> docUri = GetDocumentURI(mEditorBase);
    189    if (NS_WARN_IF(!docUri)) {
    190      mCallback->HandleError(NS_ERROR_FAILURE);
    191      return NS_OK;
    192    }
    193 
    194    nsAutoCString docUriSpec;
    195    nsresult rv = docUri->GetSpec(docUriSpec);
    196    if (NS_WARN_IF(NS_FAILED(rv))) {
    197      mCallback->HandleError(rv);
    198      return NS_OK;
    199    }
    200 
    201    rv = contentPrefService->GetByDomainAndName(
    202        NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME,
    203        GetLoadContext(mEditorBase), mCallback);
    204    if (NS_WARN_IF(NS_FAILED(rv))) {
    205      mCallback->HandleError(rv);
    206      return NS_OK;
    207    }
    208    return NS_OK;
    209  }
    210 
    211 private:
    212  RefPtr<EditorBase> mEditorBase;
    213  nsCOMPtr<nsIContentPrefCallback2> mCallback;
    214 };
    215 
    216 NS_IMETHODIMP
    217 DictionaryFetcher::Fetch(nsIEditor* aEditor) {
    218  NS_ENSURE_ARG_POINTER(aEditor);
    219 
    220  nsCOMPtr<nsIRunnable> runnable =
    221      new ContentPrefInitializerRunnable(aEditor, this);
    222  NS_DispatchToCurrentThreadQueue(runnable.forget(), 1000,
    223                                  EventQueuePriority::Idle);
    224 
    225  return NS_OK;
    226 }
    227 
    228 /**
    229 * Stores the current dictionary for aEditor's document URL.
    230 */
    231 static nsresult StoreCurrentDictionaries(
    232    EditorBase* aEditorBase, const nsTArray<nsCString>& aDictionaries) {
    233  NS_ENSURE_ARG_POINTER(aEditorBase);
    234 
    235  nsresult rv;
    236 
    237  nsCOMPtr<nsIURI> docUri = GetDocumentURI(aEditorBase);
    238  if (NS_WARN_IF(!docUri)) {
    239    return NS_ERROR_FAILURE;
    240  }
    241 
    242  nsAutoCString docUriSpec;
    243  rv = docUri->GetSpec(docUriSpec);
    244  NS_ENSURE_SUCCESS(rv, rv);
    245 
    246  RefPtr<nsVariant> prefValue = new nsVariant();
    247 
    248  nsCString asString = DictionariesToString(aDictionaries);
    249  prefValue->SetAsAString(NS_ConvertUTF8toUTF16(asString));
    250 
    251  nsCOMPtr<nsIContentPrefService2> contentPrefService =
    252      do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
    253  NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
    254 
    255  return contentPrefService->Set(NS_ConvertUTF8toUTF16(docUriSpec),
    256                                 CPS_PREF_NAME, prefValue,
    257                                 GetLoadContext(aEditorBase), nullptr);
    258 }
    259 
    260 /**
    261 * Forgets the current dictionary stored for aEditor's document URL.
    262 */
    263 static nsresult ClearCurrentDictionaries(EditorBase* aEditorBase) {
    264  NS_ENSURE_ARG_POINTER(aEditorBase);
    265 
    266  nsresult rv;
    267 
    268  nsCOMPtr<nsIURI> docUri = GetDocumentURI(aEditorBase);
    269  if (NS_WARN_IF(!docUri)) {
    270    return NS_ERROR_FAILURE;
    271  }
    272 
    273  nsAutoCString docUriSpec;
    274  rv = docUri->GetSpec(docUriSpec);
    275  NS_ENSURE_SUCCESS(rv, rv);
    276 
    277  nsCOMPtr<nsIContentPrefService2> contentPrefService =
    278      do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
    279  NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
    280 
    281  return contentPrefService->RemoveByDomainAndName(
    282      NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME,
    283      GetLoadContext(aEditorBase), nullptr);
    284 }
    285 
    286 NS_IMPL_CYCLE_COLLECTING_ADDREF(EditorSpellCheck)
    287 NS_IMPL_CYCLE_COLLECTING_RELEASE(EditorSpellCheck)
    288 
    289 NS_INTERFACE_MAP_BEGIN(EditorSpellCheck)
    290  NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck)
    291  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck)
    292  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(EditorSpellCheck)
    293 NS_INTERFACE_MAP_END
    294 
    295 NS_IMPL_CYCLE_COLLECTION(EditorSpellCheck, mEditor, mSpellChecker)
    296 
    297 EditorSpellCheck::EditorSpellCheck()
    298    : mTxtSrvFilterType(0),
    299      mSuggestedWordIndex(0),
    300      mDictionaryFetcherGroup(0),
    301      mUpdateDictionaryRunning(false) {}
    302 
    303 EditorSpellCheck::~EditorSpellCheck() {
    304  // Make sure we blow the spellchecker away, just in
    305  // case it hasn't been destroyed already.
    306  mSpellChecker = nullptr;
    307 }
    308 
    309 mozSpellChecker* EditorSpellCheck::GetSpellChecker() { return mSpellChecker; }
    310 
    311 // The problem is that if the spell checker does not exist, we can not tell
    312 // which dictionaries are installed. This function works around the problem,
    313 // allowing callers to ask if we can spell check without actually doing so (and
    314 // enabling or disabling UI as necessary). This just creates a spellcheck
    315 // object if needed and asks it for the dictionary list.
    316 NS_IMETHODIMP
    317 EditorSpellCheck::CanSpellCheck(bool* aCanSpellCheck) {
    318  RefPtr<mozSpellChecker> spellChecker = mSpellChecker;
    319  if (!spellChecker) {
    320    spellChecker = mozSpellChecker::Create();
    321    MOZ_ASSERT(spellChecker);
    322  }
    323  nsTArray<nsCString> dictList;
    324  nsresult rv = spellChecker->GetDictionaryList(&dictList);
    325  if (NS_WARN_IF(NS_FAILED(rv))) {
    326    return rv;
    327  }
    328 
    329  *aCanSpellCheck = !dictList.IsEmpty();
    330  return NS_OK;
    331 }
    332 
    333 // Instances of this class can be used as either runnables or RAII helpers.
    334 class CallbackCaller final : public Runnable {
    335 public:
    336  explicit CallbackCaller(nsIEditorSpellCheckCallback* aCallback)
    337      : mozilla::Runnable("CallbackCaller"), mCallback(aCallback) {}
    338 
    339  ~CallbackCaller() { Run(); }
    340 
    341  NS_IMETHOD Run() override {
    342    if (mCallback) {
    343      mCallback->EditorSpellCheckDone();
    344      mCallback = nullptr;
    345    }
    346    return NS_OK;
    347  }
    348 
    349 private:
    350  nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
    351 };
    352 
    353 NS_IMETHODIMP
    354 EditorSpellCheck::InitSpellChecker(nsIEditor* aEditor,
    355                                   bool aEnableSelectionChecking,
    356                                   nsIEditorSpellCheckCallback* aCallback) {
    357  NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
    358  mEditor = aEditor->AsEditorBase();
    359 
    360  RefPtr<Document> doc = mEditor->GetDocument();
    361  if (NS_WARN_IF(!doc)) {
    362    return NS_ERROR_FAILURE;
    363  }
    364 
    365  nsresult rv;
    366 
    367  // We can spell check with any editor type
    368  RefPtr<TextServicesDocument> textServicesDocument =
    369      new TextServicesDocument();
    370  textServicesDocument->SetFilterType(mTxtSrvFilterType);
    371 
    372  // EditorBase::AddEditActionListener() needs to access mSpellChecker and
    373  // mSpellChecker->GetTextServicesDocument().  Therefore, we need to
    374  // initialize them before calling TextServicesDocument::InitWithEditor()
    375  // since it calls EditorBase::AddEditActionListener().
    376  mSpellChecker = mozSpellChecker::Create();
    377  MOZ_ASSERT(mSpellChecker);
    378  rv = mSpellChecker->SetDocument(textServicesDocument, true);
    379  if (NS_WARN_IF(NS_FAILED(rv))) {
    380    return rv;
    381  }
    382 
    383  // Pass the editor to the text services document
    384  rv = textServicesDocument->InitWithEditor(aEditor);
    385  NS_ENSURE_SUCCESS(rv, rv);
    386 
    387  if (aEnableSelectionChecking) {
    388    // Find out if the section is collapsed or not.
    389    // If it isn't, we want to spellcheck just the selection.
    390 
    391    RefPtr<Selection> selection;
    392    aEditor->GetSelection(getter_AddRefs(selection));
    393    if (NS_WARN_IF(!selection)) {
    394      return NS_ERROR_FAILURE;
    395    }
    396 
    397    if (selection->RangeCount()) {
    398      RefPtr<const nsRange> range = selection->GetRangeAt(0);
    399      NS_ENSURE_STATE(range);
    400 
    401      if (!range->Collapsed()) {
    402        // We don't want to touch the range in the selection,
    403        // so create a new copy of it.
    404        RefPtr<StaticRange> staticRange =
    405            StaticRange::Create(range, IgnoreErrors());
    406        if (NS_WARN_IF(!staticRange)) {
    407          return NS_ERROR_FAILURE;
    408        }
    409 
    410        // Make sure the new range spans complete words.
    411        rv = textServicesDocument->ExpandRangeToWordBoundaries(staticRange);
    412        if (NS_WARN_IF(NS_FAILED(rv))) {
    413          return rv;
    414        }
    415 
    416        // Now tell the text services that you only want
    417        // to iterate over the text in this range.
    418        rv = textServicesDocument->SetExtent(staticRange);
    419        if (NS_WARN_IF(NS_FAILED(rv))) {
    420          return rv;
    421        }
    422      }
    423    }
    424  }
    425  // do not fail if UpdateCurrentDictionary fails because this method may
    426  // succeed later.
    427  rv = UpdateCurrentDictionary(aCallback);
    428  if (NS_FAILED(rv) && aCallback) {
    429    // However, if it does fail, we still need to call the callback since we
    430    // discard the failure.  Do it asynchronously so that the caller is always
    431    // guaranteed async behavior.
    432    RefPtr<CallbackCaller> caller = new CallbackCaller(aCallback);
    433    rv = doc->Dispatch(caller.forget());
    434    NS_ENSURE_SUCCESS(rv, rv);
    435  }
    436 
    437  return NS_OK;
    438 }
    439 
    440 NS_IMETHODIMP
    441 EditorSpellCheck::GetNextMisspelledWord(nsAString& aNextMisspelledWord) {
    442  MOZ_LOG(sEditorSpellChecker, LogLevel::Debug, ("%s", __FUNCTION__));
    443 
    444  NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
    445 
    446  DeleteSuggestedWordList();
    447  // Beware! This may flush notifications via synchronous
    448  // ScrollSelectionIntoView.
    449  RefPtr<mozSpellChecker> spellChecker(mSpellChecker);
    450  return spellChecker->NextMisspelledWord(aNextMisspelledWord,
    451                                          mSuggestedWordList);
    452 }
    453 
    454 NS_IMETHODIMP
    455 EditorSpellCheck::GetSuggestedWord(nsAString& aSuggestedWord) {
    456  // XXX This is buggy if mSuggestedWordList.Length() is over INT32_MAX.
    457  if (mSuggestedWordIndex < static_cast<int32_t>(mSuggestedWordList.Length())) {
    458    aSuggestedWord = mSuggestedWordList[mSuggestedWordIndex];
    459    mSuggestedWordIndex++;
    460  } else {
    461    // A blank string signals that there are no more strings
    462    aSuggestedWord.Truncate();
    463  }
    464  return NS_OK;
    465 }
    466 
    467 NS_IMETHODIMP
    468 EditorSpellCheck::CheckCurrentWord(const nsAString& aSuggestedWord,
    469                                   bool* aIsMisspelled) {
    470  NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
    471 
    472  DeleteSuggestedWordList();
    473  return mSpellChecker->CheckWord(aSuggestedWord, aIsMisspelled,
    474                                  &mSuggestedWordList);
    475 }
    476 
    477 NS_IMETHODIMP
    478 EditorSpellCheck::Suggest(const nsAString& aSuggestedWord, uint32_t aCount,
    479                          JSContext* aCx, Promise** aPromise) {
    480  NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
    481 
    482  nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
    483  if (NS_WARN_IF(!globalObject)) {
    484    return NS_ERROR_UNEXPECTED;
    485  }
    486 
    487  ErrorResult result;
    488  RefPtr<Promise> promise = Promise::Create(globalObject, result);
    489  if (NS_WARN_IF(result.Failed())) {
    490    return result.StealNSResult();
    491  }
    492 
    493  mSpellChecker->Suggest(aSuggestedWord, aCount)
    494      ->Then(
    495          GetMainThreadSerialEventTarget(), __func__,
    496          [promise](const CopyableTArray<nsString>& aSuggestions) {
    497            promise->MaybeResolve(aSuggestions);
    498          },
    499          [promise](nsresult aError) {
    500            promise->MaybeReject(NS_ERROR_FAILURE);
    501          });
    502 
    503  promise.forget(aPromise);
    504  return NS_OK;
    505 }
    506 
    507 RefPtr<CheckWordPromise> EditorSpellCheck::CheckCurrentWordsNoSuggest(
    508    const nsTArray<nsString>& aSuggestedWords) {
    509  if (NS_WARN_IF(!mSpellChecker)) {
    510    return CheckWordPromise::CreateAndReject(NS_ERROR_NOT_INITIALIZED,
    511                                             __func__);
    512  }
    513 
    514  return mSpellChecker->CheckWords(aSuggestedWords);
    515 }
    516 
    517 NS_IMETHODIMP
    518 EditorSpellCheck::ReplaceWord(const nsAString& aMisspelledWord,
    519                              const nsAString& aReplaceWord,
    520                              bool aAllOccurrences) {
    521  NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
    522 
    523  RefPtr<mozSpellChecker> spellChecker(mSpellChecker);
    524  return spellChecker->Replace(aMisspelledWord, aReplaceWord, aAllOccurrences);
    525 }
    526 
    527 NS_IMETHODIMP
    528 EditorSpellCheck::IgnoreWordAllOccurrences(const nsAString& aWord) {
    529  NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
    530 
    531  return mSpellChecker->IgnoreAll(aWord);
    532 }
    533 
    534 NS_IMETHODIMP
    535 EditorSpellCheck::AddWordToDictionary(const nsAString& aWord) {
    536  NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
    537 
    538  return mSpellChecker->AddWordToPersonalDictionary(aWord);
    539 }
    540 
    541 NS_IMETHODIMP
    542 EditorSpellCheck::RemoveWordFromDictionary(const nsAString& aWord) {
    543  NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
    544 
    545  return mSpellChecker->RemoveWordFromPersonalDictionary(aWord);
    546 }
    547 
    548 NS_IMETHODIMP
    549 EditorSpellCheck::GetDictionaryList(nsTArray<nsCString>& aList) {
    550  NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
    551 
    552  return mSpellChecker->GetDictionaryList(&aList);
    553 }
    554 
    555 NS_IMETHODIMP
    556 EditorSpellCheck::GetCurrentDictionaries(nsTArray<nsCString>& aDictionaries) {
    557  NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
    558  return mSpellChecker->GetCurrentDictionaries(aDictionaries);
    559 }
    560 
    561 NS_IMETHODIMP
    562 EditorSpellCheck::SetCurrentDictionaries(
    563    const nsTArray<nsCString>& aDictionaries, JSContext* aCx,
    564    Promise** aPromise) {
    565  NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
    566 
    567  RefPtr<EditorSpellCheck> kungFuDeathGrip = this;
    568 
    569  // The purpose of mUpdateDictionaryRunning is to avoid doing all of this if
    570  // UpdateCurrentDictionary's helper method DictionaryFetched, which calls us,
    571  // is on the stack. In other words: Only do this, if the user manually
    572  // selected a dictionary to use.
    573  if (!mUpdateDictionaryRunning) {
    574    // Ignore pending dictionary fetchers by increasing this number.
    575    mDictionaryFetcherGroup++;
    576 
    577    uint32_t flags = 0;
    578    mEditor->GetFlags(&flags);
    579    if (!(flags & nsIEditor::eEditorMailMask)) {
    580      bool contentPrefMatchesUserPref = true;
    581      // Check if aDictionaries has the same languages as mPreferredLangs.
    582      if (!aDictionaries.IsEmpty()) {
    583        if (aDictionaries.Length() != mPreferredLangs.Length()) {
    584          contentPrefMatchesUserPref = false;
    585        } else {
    586          for (const auto& dictName : aDictionaries) {
    587            if (mPreferredLangs.IndexOf(dictName) ==
    588                nsTArray<nsCString>::NoIndex) {
    589              contentPrefMatchesUserPref = false;
    590              break;
    591            }
    592          }
    593        }
    594      }
    595      if (!contentPrefMatchesUserPref) {
    596        // When user sets dictionary manually, we store this value associated
    597        // with editor url, if it doesn't match the document language exactly.
    598        // For example on "en" sites, we need to store "en-GB", otherwise
    599        // the language might jump back to en-US although the user explicitly
    600        // chose otherwise.
    601        StoreCurrentDictionaries(mEditor, aDictionaries);
    602 #ifdef DEBUG_DICT
    603        printf("***** Writing content preferences for |%s|\n",
    604               DictionariesToString(aDictionaries).Data());
    605 #endif
    606      } else {
    607        // If user sets a dictionary matching the language defined by
    608        // document, we consider content pref has been canceled, and we clear
    609        // it.
    610        ClearCurrentDictionaries(mEditor);
    611 #ifdef DEBUG_DICT
    612        printf("***** Clearing content preferences for |%s|\n",
    613               DictionariesToString(aDictionaries).Data());
    614 #endif
    615      }
    616 
    617      // Also store it in as a preference, so we can use it as a fallback.
    618      // We don't want this for mail composer because it uses
    619      // "spellchecker.dictionary" as a preference.
    620      //
    621      // XXX: Prefs can only be set in the parent process, so this condition is
    622      // necessary to stop libpref from throwing errors. But this should
    623      // probably be handled in a better way.
    624      if (XRE_IsParentProcess()) {
    625        nsCString asString = DictionariesToString(aDictionaries);
    626        Preferences::SetCString("spellchecker.dictionary", asString);
    627 #ifdef DEBUG_DICT
    628        printf("***** Possibly storing spellchecker.dictionary |%s|\n",
    629               asString.Data());
    630 #endif
    631      }
    632    } else {
    633      MOZ_ASSERT(flags & nsIEditor::eEditorMailMask);
    634      // Since the mail editor can only influence the language selection by the
    635      // html lang attribute, set the content-language document to persist
    636      // multi language selections.
    637      // XXX Why doesn't here use the document of the editor directly?
    638      nsCOMPtr<nsIContent> anonymousDivOrEditingHost;
    639      if (HTMLEditor* htmlEditor = mEditor->GetAsHTMLEditor()) {
    640        anonymousDivOrEditingHost = htmlEditor->ComputeEditingHost();
    641      } else {
    642        anonymousDivOrEditingHost = mEditor->GetRoot();
    643      }
    644      RefPtr<Document> ownerDoc = anonymousDivOrEditingHost->OwnerDoc();
    645      Document* parentDoc = ownerDoc->GetInProcessParentDocument();
    646      if (parentDoc) {
    647        parentDoc->SetHeaderData(
    648            nsGkAtoms::headerContentLanguage,
    649            NS_ConvertUTF8toUTF16(DictionariesToString(aDictionaries)));
    650      }
    651    }
    652  }
    653 
    654  nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
    655  if (NS_WARN_IF(!globalObject)) {
    656    return NS_ERROR_UNEXPECTED;
    657  }
    658 
    659  ErrorResult result;
    660  RefPtr<Promise> promise = Promise::Create(globalObject, result);
    661  if (NS_WARN_IF(result.Failed())) {
    662    return result.StealNSResult();
    663  }
    664 
    665  mSpellChecker->SetCurrentDictionaries(aDictionaries)
    666      ->Then(
    667          GetMainThreadSerialEventTarget(), __func__,
    668          [promise]() { promise->MaybeResolveWithUndefined(); },
    669          [promise](nsresult aError) {
    670            promise->MaybeReject(NS_ERROR_FAILURE);
    671          });
    672 
    673  promise.forget(aPromise);
    674  return NS_OK;
    675 }
    676 
    677 NS_IMETHODIMP
    678 EditorSpellCheck::UninitSpellChecker() {
    679  NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
    680 
    681  // Cleanup - kill the spell checker
    682  DeleteSuggestedWordList();
    683  mDictionaryFetcherGroup++;
    684  mSpellChecker = nullptr;
    685  return NS_OK;
    686 }
    687 
    688 NS_IMETHODIMP
    689 EditorSpellCheck::SetFilterType(uint32_t aFilterType) {
    690  mTxtSrvFilterType = aFilterType;
    691  return NS_OK;
    692 }
    693 
    694 nsresult EditorSpellCheck::DeleteSuggestedWordList() {
    695  mSuggestedWordList.Clear();
    696  mSuggestedWordIndex = 0;
    697  return NS_OK;
    698 }
    699 
    700 NS_IMETHODIMP
    701 EditorSpellCheck::UpdateCurrentDictionary(
    702    nsIEditorSpellCheckCallback* aCallback) {
    703  if (NS_WARN_IF(!mSpellChecker)) {
    704    return NS_ERROR_NOT_INITIALIZED;
    705  }
    706 
    707  nsresult rv;
    708 
    709  RefPtr<EditorSpellCheck> kungFuDeathGrip = this;
    710 
    711  // Get language with html5 algorithm
    712  const RefPtr<Element> rootEditableElement =
    713      [](const EditorBase& aEditorBase) -> Element* {
    714    if (!aEditorBase.IsHTMLEditor()) {
    715      return aEditorBase.GetRoot();
    716    }
    717    if (aEditorBase.IsMailEditor()) {
    718      // Shouldn't run spellcheck in a mail editor without focus
    719      // (bug 1507543)
    720      // XXX Why doesn't here use the document of the editor directly?
    721      Element* const editingHost =
    722          aEditorBase.AsHTMLEditor()->ComputeEditingHost();
    723      if (!editingHost) {
    724        return nullptr;
    725      }
    726      // Try to get topmost document's document element for embedded mail
    727      // editor (bug 967494)
    728      Document* parentDoc =
    729          editingHost->OwnerDoc()->GetInProcessParentDocument();
    730      if (!parentDoc) {
    731        return editingHost;
    732      }
    733      return parentDoc->GetDocumentElement();
    734    }
    735    return aEditorBase.AsHTMLEditor()->GetFocusedElement();
    736  }(*mEditor);
    737 
    738  if (!rootEditableElement) {
    739    return NS_ERROR_FAILURE;
    740  }
    741 
    742  RefPtr<DictionaryFetcher> fetcher =
    743      new DictionaryFetcher(this, aCallback, mDictionaryFetcherGroup);
    744  fetcher->mRootContentLang = rootEditableElement->GetLang();
    745  RefPtr<Document> doc = rootEditableElement->GetComposedDoc();
    746  NS_ENSURE_STATE(doc);
    747  fetcher->mRootDocContentLang = doc->GetContentLanguage();
    748 
    749  rv = fetcher->Fetch(mEditor);
    750  NS_ENSURE_SUCCESS(rv, rv);
    751 
    752  return NS_OK;
    753 }
    754 
    755 // Helper function that iterates over the list of dictionaries and sets the one
    756 // that matches based on a given comparison type.
    757 bool EditorSpellCheck::BuildDictionaryList(const nsACString& aDictName,
    758                                           const nsTArray<nsCString>& aDictList,
    759                                           enum dictCompare aCompareType,
    760                                           nsTArray<nsCString>& aOutList) {
    761  for (const auto& dictStr : aDictList) {
    762    bool equals = false;
    763    switch (aCompareType) {
    764      case DICT_NORMAL_COMPARE:
    765        equals = aDictName.Equals(dictStr);
    766        break;
    767      case DICT_COMPARE_CASE_INSENSITIVE:
    768        equals = aDictName.Equals(dictStr, nsCaseInsensitiveCStringComparator);
    769        break;
    770      case DICT_COMPARE_DASHMATCH:
    771        equals = nsStyleUtil::DashMatchCompare(
    772            NS_ConvertUTF8toUTF16(dictStr), NS_ConvertUTF8toUTF16(aDictName),
    773            nsCaseInsensitiveStringComparator);
    774        break;
    775    }
    776    if (equals) {
    777      // Avoid adding duplicates to aOutList.
    778      if (aOutList.IndexOf(dictStr) == nsTArray<nsCString>::NoIndex) {
    779        aOutList.AppendElement(dictStr);
    780      }
    781 #ifdef DEBUG_DICT
    782      printf("***** Trying |%s|.\n", dictStr.get());
    783 #endif
    784      // We always break here. We tried to set the dictionary to an existing
    785      // dictionary from the list. This must work, if it doesn't, there is
    786      // no point trying another one.
    787      return true;
    788    }
    789  }
    790  return false;
    791 }
    792 
    793 nsresult EditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher) {
    794  MOZ_ASSERT(aFetcher);
    795  RefPtr<EditorSpellCheck> kungFuDeathGrip = this;
    796 
    797  BeginUpdateDictionary();
    798 
    799  if (aFetcher->mGroup < mDictionaryFetcherGroup) {
    800    // SetCurrentDictionary was called after the fetch started.  Don't overwrite
    801    // that dictionary with the fetched one.
    802    EndUpdateDictionary();
    803    if (aFetcher->mCallback) {
    804      aFetcher->mCallback->EditorSpellCheckDone();
    805    }
    806    return NS_OK;
    807  }
    808 
    809  /*
    810   * We try to derive the dictionary to use based on the following priorities:
    811   * 1) Content preference, so the language the user set for the site before.
    812   *    (Introduced in bug 678842 and corrected in bug 717433.)
    813   * 2) Language set by the website, or any other dictionary that partly
    814   *    matches that. (Introduced in bug 338427.)
    815   *    Eg. if the website is "en-GB", a user who only has "en-US" will get
    816   *    that. If the website is generic "en", the user will get one of the
    817   *    "en-*" installed. If application locale or system locale is "en-*",
    818   *    we get it. If others, it is (almost) random.
    819   *    However, we prefer what is stored in "spellchecker.dictionary",
    820   *    so if the user chose "en-AU" before, they will get "en-AU" on a plain
    821   *    "en" site. (Introduced in bug 682564.)
    822   *    If the site has multiple languages declared in its Content-Language
    823   *    header and there is no more specific lang tag in HTML, we try to
    824   *    enable a dictionary for every content language.
    825   * 3) The value of "spellchecker.dictionary" which reflects a previous
    826   *    language choice of the user (on another site).
    827   *    (This was the original behaviour before the aforementioned bugs
    828   *    landed).
    829   * 4) The user's locale.
    830   * 5) Use the current dictionary that is currently set.
    831   * 6) The content of the "LANG" environment variable (if set).
    832   * 7) The first spell check dictionary installed.
    833   */
    834 
    835  // Get the language from the element or its closest parent according to:
    836  // https://html.spec.whatwg.org/#attr-lang
    837  // This is used in SetCurrentDictionaries.
    838  nsCString contentLangs;
    839  // Reset mPreferredLangs so we only get the current state.
    840  mPreferredLangs.Clear();
    841  if (aFetcher->mRootContentLang) {
    842    aFetcher->mRootContentLang->ToUTF8String(contentLangs);
    843  }
    844 #ifdef DEBUG_DICT
    845  printf("***** mPreferredLangs (element) |%s|\n", contentLangs.get());
    846 #endif
    847  if (!contentLangs.IsEmpty()) {
    848    mPreferredLangs.AppendElement(contentLangs);
    849  } else {
    850    // If no luck, try the "Content-Language" header.
    851    if (aFetcher->mRootDocContentLang) {
    852      aFetcher->mRootDocContentLang->ToUTF8String(contentLangs);
    853    }
    854 #ifdef DEBUG_DICT
    855    printf("***** mPreferredLangs (content-language) |%s|\n",
    856           contentLangs.get());
    857 #endif
    858    StringToDictionaries(contentLangs, mPreferredLangs);
    859  }
    860 
    861  // We obtain a list of available dictionaries.
    862  AutoTArray<nsCString, 8> dictList;
    863  nsresult rv = mSpellChecker->GetDictionaryList(&dictList);
    864  if (NS_WARN_IF(NS_FAILED(rv))) {
    865    EndUpdateDictionary();
    866    if (aFetcher->mCallback) {
    867      aFetcher->mCallback->EditorSpellCheckDone();
    868    }
    869    return rv;
    870  }
    871 
    872  // Priority 1:
    873  // If we successfully fetched a dictionary from content prefs, do not go
    874  // further. Use this exact dictionary.
    875  // Don't use content preferences for editor with eEditorMailMask flag.
    876  nsAutoCString dictName;
    877  uint32_t flags;
    878  mEditor->GetFlags(&flags);
    879  if (!(flags & nsIEditor::eEditorMailMask)) {
    880    if (!aFetcher->mDictionaries.IsEmpty()) {
    881      RefPtr<EditorSpellCheck> self = this;
    882      RefPtr<DictionaryFetcher> fetcher = aFetcher;
    883      mSpellChecker->SetCurrentDictionaries(aFetcher->mDictionaries)
    884          ->Then(
    885              GetMainThreadSerialEventTarget(), __func__,
    886              [self, fetcher]() {
    887 #ifdef DEBUG_DICT
    888                printf("***** Assigned from content preferences |%s|\n",
    889                       DictionariesToString(fetcher->mDictionaries).Data());
    890 #endif
    891                // We take an early exit here, so let's not forget to clear
    892                // the word list.
    893                self->DeleteSuggestedWordList();
    894 
    895                self->EndUpdateDictionary();
    896                if (fetcher->mCallback) {
    897                  fetcher->mCallback->EditorSpellCheckDone();
    898                }
    899              },
    900              [self, fetcher](nsresult aError) {
    901                if (aError == NS_ERROR_ABORT) {
    902                  return;
    903                }
    904                // May be dictionary was uninstalled ?
    905                // Clear the content preference and continue.
    906                ClearCurrentDictionaries(self->mEditor);
    907 
    908                // Priority 2 or later will handled by the following
    909                self->SetFallbackDictionary(fetcher);
    910              });
    911      return NS_OK;
    912    }
    913  }
    914  SetFallbackDictionary(aFetcher);
    915  return NS_OK;
    916 }
    917 
    918 void EditorSpellCheck::SetDictionarySucceeded(DictionaryFetcher* aFetcher) {
    919  DeleteSuggestedWordList();
    920  EndUpdateDictionary();
    921  if (aFetcher->mCallback) {
    922    aFetcher->mCallback->EditorSpellCheckDone();
    923  }
    924 }
    925 
    926 void EditorSpellCheck::SetFallbackDictionary(DictionaryFetcher* aFetcher) {
    927  MOZ_ASSERT(mUpdateDictionaryRunning);
    928 
    929  AutoTArray<nsCString, 6> tryDictList;
    930 
    931  // We obtain a list of available dictionaries.
    932  AutoTArray<nsCString, 8> dictList;
    933  nsresult rv = mSpellChecker->GetDictionaryList(&dictList);
    934  if (NS_WARN_IF(NS_FAILED(rv))) {
    935    EndUpdateDictionary();
    936    if (aFetcher->mCallback) {
    937      aFetcher->mCallback->EditorSpellCheckDone();
    938    }
    939    return;
    940  }
    941 
    942  // Priority 2:
    943  // After checking the content preferences, we use the languages of the element
    944  // or document.
    945 
    946  // Get the preference value.
    947  nsAutoCString prefDictionariesAsString;
    948  Preferences::GetLocalizedCString("spellchecker.dictionary",
    949                                   prefDictionariesAsString);
    950  nsTArray<nsCString> prefDictionaries;
    951  StringToDictionaries(prefDictionariesAsString, prefDictionaries);
    952 
    953  nsAutoCString appLocaleStr;
    954  // We pick one dictionary for every language that the element or document
    955  // indicates it contains.
    956  for (const auto& dictName : mPreferredLangs) {
    957    // RFC 5646 explicitly states that matches should be case-insensitive.
    958    if (BuildDictionaryList(dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE,
    959                            tryDictList)) {
    960 #ifdef DEBUG_DICT
    961      printf("***** Trying from element/doc |%s| \n", dictName.get());
    962 #endif
    963      continue;
    964    }
    965 
    966    // Required dictionary was not available. Try to get a dictionary
    967    // matching at least language part of dictName.
    968    mozilla::intl::Locale loc;
    969    if (mozilla::intl::LocaleParser::TryParse(dictName, loc).isOk() &&
    970        loc.Canonicalize().isOk()) {
    971      Span<const char> language = loc.Language().Span();
    972      nsAutoCString langCode(language.data(), language.size());
    973 
    974      // Try dictionary.spellchecker preference, if it starts with langCode,
    975      // so we don't just get any random dictionary matching the language.
    976      bool didAppend = false;
    977      for (const auto& dictionary : prefDictionaries) {
    978        if (nsStyleUtil::DashMatchCompare(NS_ConvertUTF8toUTF16(dictionary),
    979                                          NS_ConvertUTF8toUTF16(langCode),
    980                                          nsTDefaultStringComparator)) {
    981 #ifdef DEBUG_DICT
    982          printf(
    983              "***** Trying preference value |%s| since it matches language "
    984              "code\n",
    985              dictionary.Data());
    986 #endif
    987          if (BuildDictionaryList(dictionary, dictList,
    988                                  DICT_COMPARE_CASE_INSENSITIVE, tryDictList)) {
    989            didAppend = true;
    990            break;
    991          }
    992        }
    993      }
    994      if (didAppend) {
    995        continue;
    996      }
    997 
    998      // Use the application locale dictionary when the required language
    999      // equals applocation locale language.
   1000      LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocaleStr);
   1001      if (!appLocaleStr.IsEmpty()) {
   1002        mozilla::intl::Locale appLoc;
   1003        auto result =
   1004            mozilla::intl::LocaleParser::TryParse(appLocaleStr, appLoc);
   1005        if (result.isOk() && appLoc.Canonicalize().isOk() &&
   1006            loc.Language().Span() == appLoc.Language().Span()) {
   1007          if (BuildDictionaryList(appLocaleStr, dictList,
   1008                                  DICT_COMPARE_CASE_INSENSITIVE, tryDictList)) {
   1009            continue;
   1010          }
   1011        }
   1012      }
   1013 
   1014      // Use the system locale dictionary when the required language equlas
   1015      // system locale language.
   1016      nsAutoCString sysLocaleStr;
   1017      OSPreferences::GetInstance()->GetSystemLocale(sysLocaleStr);
   1018      if (!sysLocaleStr.IsEmpty()) {
   1019        mozilla::intl::Locale sysLoc;
   1020        auto result =
   1021            mozilla::intl::LocaleParser::TryParse(sysLocaleStr, sysLoc);
   1022        if (result.isOk() && sysLoc.Canonicalize().isOk() &&
   1023            loc.Language().Span() == sysLoc.Language().Span()) {
   1024          if (BuildDictionaryList(sysLocaleStr, dictList,
   1025                                  DICT_COMPARE_CASE_INSENSITIVE, tryDictList)) {
   1026            continue;
   1027          }
   1028        }
   1029      }
   1030 
   1031      // Use any dictionary with the required language.
   1032 #ifdef DEBUG_DICT
   1033      printf("***** Trying to find match for language code |%s|\n",
   1034             langCode.get());
   1035 #endif
   1036      BuildDictionaryList(langCode, dictList, DICT_COMPARE_DASHMATCH,
   1037                          tryDictList);
   1038    }
   1039  }
   1040 
   1041  RefPtr<EditorSpellCheck> self = this;
   1042  RefPtr<DictionaryFetcher> fetcher = aFetcher;
   1043  RefPtr<GenericPromise> promise;
   1044 
   1045  if (tryDictList.IsEmpty()) {
   1046    // Proceed to priority 3 if the list of dictionaries is empty.
   1047    promise = GenericPromise::CreateAndReject(NS_ERROR_INVALID_ARG, __func__);
   1048  } else {
   1049    promise = mSpellChecker->SetCurrentDictionaries(tryDictList);
   1050  }
   1051 
   1052  // If an error was thrown while setting the dictionary, just
   1053  // fail silently so that the spellchecker dialog is allowed to come
   1054  // up. The user can manually reset the language to their choice on
   1055  // the dialog if it is wrong.
   1056  promise->Then(
   1057      GetMainThreadSerialEventTarget(), __func__,
   1058      [self, fetcher]() { self->SetDictionarySucceeded(fetcher); },
   1059      [prefDictionaries = prefDictionaries.Clone(), dictList = dictList.Clone(),
   1060       self, fetcher]() {
   1061        // Build tryDictList with dictionaries for priorities 4 through 7.
   1062        // We'll use this list if there is no user preference or trying
   1063        // the user preference fails.
   1064        AutoTArray<nsCString, 6> tryDictList;
   1065 
   1066        // Priority 4:
   1067        // As next fallback, try the current locale.
   1068        nsAutoCString appLocaleStr;
   1069        LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocaleStr);
   1070 #ifdef DEBUG_DICT
   1071        printf("***** Trying locale |%s|\n", appLocaleStr.get());
   1072 #endif
   1073        self->BuildDictionaryList(appLocaleStr, dictList,
   1074                                  DICT_COMPARE_CASE_INSENSITIVE, tryDictList);
   1075 
   1076        // Priority 5:
   1077        // If we have a current dictionary and we don't have no item in try
   1078        // list, don't try anything else.
   1079        nsTArray<nsCString> currentDictionaries;
   1080        self->GetCurrentDictionaries(currentDictionaries);
   1081        if (!currentDictionaries.IsEmpty() && tryDictList.IsEmpty()) {
   1082 #ifdef DEBUG_DICT
   1083          printf("***** Retrieved current dict |%s|\n",
   1084                 DictionariesToString(currentDictionaries).Data());
   1085 #endif
   1086          self->EndUpdateDictionary();
   1087          if (fetcher->mCallback) {
   1088            fetcher->mCallback->EditorSpellCheckDone();
   1089          }
   1090          return;
   1091        }
   1092 
   1093        // Priority 6:
   1094        // Try to get current dictionary from environment variable LANG.
   1095        // LANG = language[_territory][.charset]
   1096        char* env_lang = getenv("LANG");
   1097        if (env_lang) {
   1098          nsAutoCString lang(env_lang);
   1099          // Strip trailing charset, if there is any.
   1100          int32_t dot_pos = lang.FindChar('.');
   1101          if (dot_pos != -1) {
   1102            lang = Substring(lang, 0, dot_pos);
   1103          }
   1104 
   1105          int32_t underScore = lang.FindChar('_');
   1106          if (underScore != -1) {
   1107            lang.Replace(underScore, 1, '-');
   1108 #ifdef DEBUG_DICT
   1109            printf("***** Trying LANG from environment |%s|\n", lang.get());
   1110 #endif
   1111            self->BuildDictionaryList(
   1112                lang, dictList, DICT_COMPARE_CASE_INSENSITIVE, tryDictList);
   1113          }
   1114        }
   1115 
   1116        // Priority 7:
   1117        // If it does not work, pick the first one.
   1118        if (!dictList.IsEmpty()) {
   1119          self->BuildDictionaryList(dictList[0], dictList, DICT_NORMAL_COMPARE,
   1120                                    tryDictList);
   1121 #ifdef DEBUG_DICT
   1122          printf("***** Trying first of list |%s|\n", dictList[0].get());
   1123 #endif
   1124        }
   1125 
   1126        // Priority 3:
   1127        // If the document didn't supply a dictionary or the setting
   1128        // failed, try the user preference next.
   1129        if (!prefDictionaries.IsEmpty()) {
   1130          self->mSpellChecker->SetCurrentDictionaries(prefDictionaries)
   1131              ->Then(
   1132                  GetMainThreadSerialEventTarget(), __func__,
   1133                  [self, fetcher]() { self->SetDictionarySucceeded(fetcher); },
   1134                  // Priority 3 failed, we'll use the list we built of
   1135                  // priorities 4 to 7.
   1136                  [tryDictList = tryDictList.Clone(), self, fetcher]() {
   1137                    self->mSpellChecker
   1138                        ->SetCurrentDictionaryFromList(tryDictList)
   1139                        ->Then(GetMainThreadSerialEventTarget(), __func__,
   1140                               [self, fetcher]() {
   1141                                 self->SetDictionarySucceeded(fetcher);
   1142                               });
   1143                  });
   1144        } else {
   1145          // We don't have a user preference, so we'll try the list we
   1146          // built of priorities 4 to 7.
   1147          self->mSpellChecker->SetCurrentDictionaryFromList(tryDictList)
   1148              ->Then(
   1149                  GetMainThreadSerialEventTarget(), __func__,
   1150                  [self, fetcher]() { self->SetDictionarySucceeded(fetcher); });
   1151        }
   1152      });
   1153 }
   1154 
   1155 }  // namespace mozilla