chromium/chrome/browser/spellchecker/spellcheck_service.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/spellchecker/spellcheck_service.h"

#include <iterator>
#include <memory>
#include <set>
#include <utility>

#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/no_destructor.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_split.h"
#include "base/supports_user_data.h"
#include "base/synchronization/waitable_event.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/spellchecker/spellcheck_factory.h"
#include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/pref_member.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/spellcheck/browser/pref_names.h"
#include "components/spellcheck/browser/spellcheck_host_metrics.h"
#include "components/spellcheck/browser/spellcheck_platform.h"
#include "components/spellcheck/browser/spelling_service_client.h"
#include "components/spellcheck/common/spellcheck.mojom.h"
#include "components/spellcheck/common/spellcheck_common.h"
#include "components/spellcheck/common/spellcheck_features.h"
#include "components/spellcheck/spellcheck_buildflags.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "ui/base/l10n/l10n_util.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_features.h"
#endif

#if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "components/spellcheck/browser/windows_spell_checker.h"
#endif  // BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)

BrowserThread;

namespace {

SpellcheckService::SpellCheckerBinder& GetSpellCheckerBinderOverride() {}

// Only record spelling-configuration metrics for profiles in which the user
// can configure spelling.
bool RecordSpellingConfigurationMetrics(content::BrowserContext* context) {}

}  // namespace

// TODO(rlp): I do not like globals, but keeping these for now during
// transition.
// An event used by browser tests to receive status events from this class and
// its derived classes.
base::WaitableEvent* g_status_event =;
SpellcheckService::EventType g_status_type =;

SpellcheckService::SpellcheckService(content::BrowserContext* context)
    :{}

SpellcheckService::~SpellcheckService() {}

base::WeakPtr<SpellcheckService> SpellcheckService::GetWeakPtr() {}

#if !BUILDFLAG(IS_MAC)
// static
void SpellcheckService::GetDictionaries(
    content::BrowserContext* browser_context,
    std::vector<Dictionary>* dictionaries) {}
#endif  // !BUILDFLAG(IS_MAC)

// static
bool SpellcheckService::SignalStatusEvent(
    SpellcheckService::EventType status_type) {}

// static
std::string SpellcheckService::GetSupportedAcceptLanguageCode(
    const std::string& supported_language_full_tag,
    bool generic_only) {}

#if BUILDFLAG(IS_WIN)
// static
void SpellcheckService::EnableFirstUserLanguageForSpellcheck(
    PrefService* prefs) {
  // Ensure that spellcheck is enabled for the first language in the
  // accept languages list.
  base::Value::List user_dictionaries =
      prefs->GetList(spellcheck::prefs::kSpellCheckDictionaries).Clone();
  std::vector<std::string> user_languages =
      base::SplitString(prefs->GetString(language::prefs::kAcceptLanguages),
                        ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);

  // Some first run scenarios will add an accept language to preferences that
  // is not found in the hard-coded list in kAcceptLanguageList. Only
  // languages in kAcceptLanguageList can be spellchecked. An example is an
  // installation on a device where Finnish is the Windows display
  // language--the initial accept language preferences are observed to be
  // "fi-FI,fi,en-US,en". Only "fi" is contained in kAcceptLanguageList.
  std::string first_user_language;
  std::vector<std::string> accept_languages;
  l10n_util::GetAcceptLanguages(&accept_languages);
  for (const auto& user_language : user_languages) {
    if (base::Contains(accept_languages, user_language)) {
      first_user_language = user_language;
      break;
    }
  }

  bool first_user_language_spellchecked = false;
  for (const auto& dictionary_value : user_dictionaries) {
    first_user_language_spellchecked =
        base::Contains(dictionary_value.GetString(), first_user_language);
    if (first_user_language_spellchecked)
      break;
  }

  if (!first_user_language_spellchecked) {
    user_dictionaries.Insert(user_dictionaries.begin(),
                             base::Value(first_user_language));
    prefs->SetList(spellcheck::prefs::kSpellCheckDictionaries,
                   std::move(user_dictionaries));
  }
}
#endif  // BUILDFLAG(IS_WIN)

void SpellcheckService::StartRecordingMetrics(bool spellcheck_enabled) {}

void SpellcheckService::InitForRenderer(content::RenderProcessHost* host) {}

SpellCheckHostMetrics* SpellcheckService::GetMetrics() const {}

SpellcheckCustomDictionary* SpellcheckService::GetCustomDictionary() {}

void SpellcheckService::LoadDictionaries() {}

const std::vector<std::unique_ptr<SpellcheckHunspellDictionary>>&
SpellcheckService::GetHunspellDictionaries() {}

bool SpellcheckService::IsSpellcheckEnabled() const {}

void SpellcheckService::OnRenderProcessHostCreated(
    content::RenderProcessHost* host) {}

void SpellcheckService::OnCustomDictionaryLoaded() {}

void SpellcheckService::OnCustomDictionaryChanged(
    const SpellcheckCustomDictionary::Change& change) {}

void SpellcheckService::OnHunspellDictionaryInitialized(
    const std::string& language) {}

void SpellcheckService::OnHunspellDictionaryDownloadBegin(
    const std::string& language) {}

void SpellcheckService::OnHunspellDictionaryDownloadSuccess(
    const std::string& language) {}

void SpellcheckService::OnHunspellDictionaryDownloadFailure(
    const std::string& language) {}

void SpellcheckService::InitializeDictionaries(base::OnceClosure done) {}

#if BUILDFLAG(IS_WIN)
void SpellcheckService::InitWindowsDictionaryLanguages(
    const std::vector<std::string>& windows_spellcheck_languages) {
  windows_spellcheck_dictionary_map_.clear();
  for (const auto& windows_spellcheck_language : windows_spellcheck_languages) {
    std::string accept_language =
        SpellcheckService::GetSupportedAcceptLanguageCode(
            windows_spellcheck_language, /* generic_only */ false);
    AddWindowsSpellcheckDictionary(accept_language,
                                   windows_spellcheck_language);

    // There is one unfortunate special case (so far the only one known). The
    // accept language "sr" is supported, and if you use it as a display
    // language you see Cyrillic script. If a Windows language pack is
    // installed that supports "sr-Cyrl-*", mark the "sr" accept language
    // as having Windows spellcheck support instead of using Hunspell.
    if (base::EqualsCaseInsensitiveASCII(
            "sr-Cyrl", SpellcheckService::GetLanguageAndScriptTag(
                           windows_spellcheck_language,
                           /* include_script_tag= */ true))) {
      AddWindowsSpellcheckDictionary("sr", windows_spellcheck_language);
    }

    // Add the generic language with the region subtag removed too if it exists
    // in the list of accept languages, and use it when calling the Windows
    // spellcheck APIs. For example, if the preferred language settings include
    // just generic Portuguese (pt), but the Portuguese (Brazil) platform
    // language pack (pt-BR) is installed, we want an entry for it so that the
    // generic Portuguese language can be enabled for spellchecking. The Windows
    // platform spellcheck API has logic to load the pt-BR dictionary if only pt
    // is specified as the BCP47 language tag. The use of a map in
    // AddWindowsSpellcheckDictionary ensures there won't be duplicate entries
    // if a generic language was already added above (ar-SA would already be
    // mapped to ar since the accept language ar-SA is not recognized by the
    // browser e.g.).
    accept_language = SpellcheckService::GetSupportedAcceptLanguageCode(
        windows_spellcheck_language, /* generic_only */ true);
    AddWindowsSpellcheckDictionary(accept_language, accept_language);
  }

  // A user may have removed a language pack for a non-Hunspell language after
  // enabling it for spellcheck on the language settings page. Remove
  // preferences for this language so that there is no attempt to load a
  // non-existent Hunspell dictionary, and so that Hunspell spellchecking isn't
  // broken because of the failed load. This also handles the case where the
  // primary preferred language is enabled for spellchecking during first run,
  // but it's now determined that there is neither Windows platform nor Hunspell
  // dictionary support for that language.
  PrefService* prefs = user_prefs::UserPrefs::Get(context_);
  DCHECK(prefs);
  // When following object goes out of scope, preference change observers will
  // be notified (even if there is no preference change).
  ScopedListPrefUpdate update(prefs,
                              spellcheck::prefs::kSpellCheckDictionaries);
  update->EraseIf([this](const base::Value& entry) {
    const std::string dictionary_name = entry.GetString();
    return (!UsesWindowsDictionary(dictionary_name) &&
            spellcheck::GetCorrespondingSpellCheckLanguage(dictionary_name)
                .empty());
  });

  // No need to call LoadDictionaries() as when the ScopedListPrefUpdate object
  // goes out of scope, the preference change handler will do this.
}

bool SpellcheckService::UsesWindowsDictionary(
    std::string accept_language) const {
  return !GetSupportedWindowsDictionaryLanguage(accept_language).empty();
}
#endif  // BUILDFLAG(IS_WIN)

// static
void SpellcheckService::OverrideBinderForTesting(SpellCheckerBinder binder) {}

// static
std::string SpellcheckService::GetLanguageAndScriptTag(
    const std::string& full_tag,
    bool include_script_tag) {}

#if BUILDFLAG(IS_WIN)
// static
std::string SpellcheckService::GetSupportedAcceptLanguageCodeGenericOnly(
    const std::string& supported_language_full_tag,
    const std::vector<std::string>& accept_languages) {
  auto iter = base::ranges::find_if(
      accept_languages,
      [supported_language_full_tag](const auto& accept_language) {
        return base::EqualsCaseInsensitiveASCII(
            SpellcheckService::GetLanguageAndScriptTag(
                supported_language_full_tag,
                /* include_script_tag= */ false),
            SpellcheckService::GetLanguageAndScriptTag(
                accept_language,
                /* include_script_tag= */ false));
      });

  if (iter != accept_languages.end()) {
    // Special case for Serbian--"sr" implies Cyrillic script. Don't mark it as
    // supported for sr-Latn*.
    if (base::EqualsCaseInsensitiveASCII(
            SpellcheckService::GetLanguageAndScriptTag(
                supported_language_full_tag,
                /* include_script_tag= */ true),
            "sr-Latn")) {
      return "";
    }
    return *iter;
  }

  return "";
}

// static
bool SpellcheckService::HasPrivateUseSubTag(const std::string& full_tag) {
  std::vector<std::string> subtags = base::SplitString(
      full_tag, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);

  // Private use subtags are separated from the other subtags by the reserved
  // single-character subtag 'x'.
  return base::Contains(subtags, "x");
}

// static
std::string SpellcheckService::GetTagToPassToWindowsSpellchecker(
    const std::string& accept_language,
    const std::string& supported_language_full_tag) {
  // First try exact match. Per BCP47, tags are in ASCII and should be treated
  // as case-insensitive (although there are conventions for the capitalization
  // of subtags, they are sometimes broken).
  if (base::EqualsCaseInsensitiveASCII(supported_language_full_tag,
                                       accept_language)) {
    // Unambiguous spellcheck dictionary to be used.
    return supported_language_full_tag;
  }

  // Accept language does not match script or region subtags.
  // If there is a script subtag, include it, to avoid for example passing
  // "sr" which is ambiguous as Serbian can use Cyrillic or Latin script.
  // There is one unfortunate special case (so far the only one known). The
  // accept language "sr" is supported, and if you use it as a display
  // language you see Cyrillic script. However, the Windows spellcheck API
  // returns "sr-Latn-*" dictionaries if the unqualified language tag is
  // passed. The following forces Windows spellchecking to use Cyrillic script
  // in this case, and if the language pack is not installed there will be a
  // fallback to Hunspell support when spellchecking is performed.
  if (base::EqualsCaseInsensitiveASCII("sr", accept_language))
    return "sr-Cyrl";

  return SpellcheckService::GetLanguageAndScriptTag(
      supported_language_full_tag,
      /* include_script_tag= */ true);
}

#endif  // BUILDFLAG(IS_WIN)

// static
void SpellcheckService::AttachStatusEvent(base::WaitableEvent* status_event) {}

// static
SpellcheckService::EventType SpellcheckService::GetStatusEvent() {}

mojo::Remote<spellcheck::mojom::SpellChecker>
SpellcheckService::GetSpellCheckerForProcess(content::RenderProcessHost* host) {}

void SpellcheckService::InitForAllRenderers() {}

void SpellcheckService::OnSpellCheckDictionariesChanged() {}

void SpellcheckService::OnUseSpellingServiceChanged() {}

void SpellcheckService::OnAcceptLanguagesChanged() {}

std::vector<std::string> SpellcheckService::GetNormalizedAcceptLanguages(
    bool normalize_for_spellcheck) const {}

#if BUILDFLAG(IS_WIN)
void SpellcheckService::InitializePlatformSpellchecker() {
  // The Windows spell checker must be created before the dictionaries are
  // initialized. Note it is instantiated even if only Hunspell is being used
  // since metrics on the availability of Windows platform language packs are
  // being recorded. Thus method should only be called once, except in test
  // code.
  if (!platform_spell_checker()) {
    platform_spell_checker_ = std::make_unique<WindowsSpellChecker>(
        base::ThreadPool::CreateCOMSTATaskRunner({base::MayBlock()}));
  }
}

void SpellcheckService::RecordSpellcheckLocalesStats() {
  if (metrics_ && platform_spell_checker() && !hunspell_dictionaries_.empty()) {
    std::vector<std::string> hunspell_locales;
    for (auto& dict : hunspell_dictionaries_) {
      hunspell_locales.push_back(dict->GetLanguage());
    }
    spellcheck_platform::RecordSpellcheckLocalesStats(
        platform_spell_checker(), std::move(hunspell_locales));
  }
}

void SpellcheckService::RecordChromeLocalesStats() {
  const auto& accept_languages =
      GetNormalizedAcceptLanguages(/* normalize_for_spellcheck */ false);
  if (metrics_ && platform_spell_checker() && !accept_languages.empty()) {
    spellcheck_platform::RecordChromeLocalesStats(platform_spell_checker(),
                                                  std::move(accept_languages));
  }
}

void SpellcheckService::AddWindowsSpellcheckDictionary(
    const std::string& accept_language,
    const std::string& supported_language_full_tag) {
  if (!accept_language.empty()) {
    windows_spellcheck_dictionary_map_.insert(
        {accept_language, supported_language_full_tag});
  }
}

std::string SpellcheckService::GetSupportedWindowsDictionaryLanguage(
    const std::string& accept_language) const {
  // BCP47 language tag used by the Windows spellchecker API.
  std::string spellcheck_language;

  auto it = windows_spellcheck_dictionary_map_.find(accept_language);
  if (it != windows_spellcheck_dictionary_map_.end())
    spellcheck_language = it->second;

  return spellcheck_language;
}

void SpellcheckService::AddSpellcheckLanguagesForTesting(
    const std::vector<std::string>& languages) {
  InitializePlatformSpellchecker();
  if (platform_spell_checker()) {
    spellcheck_platform::AddSpellcheckLanguagesForTesting(
        platform_spell_checker(), languages);
  }
}
#endif  // BUILDFLAG(IS_WIN)