text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #ifndef CHROME_BROWSER_SPELLCHECKER_SPELLCHECK_SERVICE_H_
  #define CHROME_BROWSER_SPELLCHECKER_SPELLCHECK_SERVICE_H_
  
  #include <stddef.h>
  
  #include <map>
  #include <memory>
  #include <string>
  #include <vector>
  
  #include "base/callback.h"
  #include "base/gtest_prod_util.h"
  #include "base/macros.h"
  #include "base/memory/weak_ptr.h"
  #include "build/build_config.h"
  #include "chrome/browser/spellchecker/spellcheck_custom_dictionary.h"
  #include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h"
  #include "components/keyed_service/core/keyed_service.h"
  #include "components/prefs/pref_change_registrar.h"
  #include "components/spellcheck/browser/platform_spell_checker.h"
  #include "components/spellcheck/common/spellcheck.mojom-forward.h"
  #include "components/spellcheck/spellcheck_buildflags.h"
  #include "content/public/browser/notification_observer.h"
  #include "content/public/browser/notification_registrar.h"
  #include "mojo/public/cpp/bindings/remote.h"
  
  class SpellCheckHostMetrics;
  
  namespace base {
  class WaitableEvent;
  }
  
  namespace content {
  class BrowserContext;
  class NotificationDetails;
  class NotificationSource;
  class RenderProcessHost;
  }
  
  #if defined(OS_WIN)
  namespace extensions {
  class LanguageSettingsPrivateApiTestDelayInit;
  }
  #endif  // defined(OS_WIN)
  
  // Encapsulates the browser side spellcheck service. There is one of these per
  // profile and each is created by the SpellCheckServiceFactory.  The
  // SpellcheckService maintains any per-profile information about spellcheck.
  class SpellcheckService : public KeyedService,
                            public content::NotificationObserver,
                            public SpellcheckCustomDictionary::Observer,
                            public SpellcheckHunspellDictionary::Observer {
   public:
    // Event types used for reporting the status of this class and its derived
    // classes to browser tests.
    enum EventType {
      BDICT_NOTINITIALIZED,
      BDICT_CORRUPTED,
    };
  
    // A dictionary that can be used for spellcheck.
    struct Dictionary {
      // The shortest unambiguous identifier for a language from
      // |g_supported_spellchecker_languages|. For example, "bg" for Bulgarian,
      // because Chrome has only one Bulgarian language. However, "en-US" for
      // English (United States), because Chrome has several versions of English.
      std::string language;
  
      // Whether |language| is currently used for spellcheck.
      bool used_for_spellcheck;
    };
  
    explicit SpellcheckService(content::BrowserContext* context);
    ~SpellcheckService() override;
  
    base::WeakPtr<SpellcheckService> GetWeakPtr();
  
  #if !defined(OS_MAC)
    // Returns all currently configured |dictionaries| to display in the context
    // menu over a text area. The context menu is used for selecting the
    // dictionaries used for spellcheck.
    static void GetDictionaries(content::BrowserContext* browser_context,
                                std::vector<Dictionary>* dictionaries);
  #endif  // !OS_MAC
  
    // Signals the event attached by AttachTestEvent() to report the specified
    // event to browser tests. This function is called by this class and its
    // derived classes to report their status. This function does not do anything
    // when we do not set an event to |status_event_|.
    static bool SignalStatusEvent(EventType type);
  
    // Get the best match of a supported accept language code for the provided
    // language tag. Returns an empty string if no match is found. Method cannot
    // be defined in spellcheck_common.h as it depends on l10n_util, and code
    // under components cannot depend on ui/base. If |generic_only| is true,
    // then only return the language subtag (first part of the full BCP47 tag)
    // if the generic accept language is supported by the browser.
    static std::string GetSupportedAcceptLanguageCode(
        const std::string& supported_language_full_tag,
        bool generic_only = false);
  
  #if defined(OS_WIN)
    // Since Windows platform dictionary support is determined asynchronously,
    // this method is used to assure that the first preferred language initially
    // has spellchecking enabled after first run. Spellchecking for the primary
    // language will be disabled later if there is no dictionary support.
    static void EnableFirstUserLanguageForSpellcheck(PrefService* prefs);
  #endif  // defined(OS_WIN)
  
    // Instantiates SpellCheckHostMetrics object and makes it ready for recording
    // metrics. This should be called only if the metrics recording is active.
    void StartRecordingMetrics(bool spellcheck_enabled);
  
    // Pass the renderer some basic initialization information. Note that the
    // renderer will not load Hunspell until it needs to.
    void InitForRenderer(content::RenderProcessHost* host);
  
    // Returns a metrics counter associated with this object,
    // or null when metrics recording is disabled.
    SpellCheckHostMetrics* GetMetrics() const;
  
    // Returns the instance of the custom dictionary.
    SpellcheckCustomDictionary* GetCustomDictionary();
  
    // Returns the instance of the vector of Hunspell dictionaries.
    const std::vector<std::unique_ptr<SpellcheckHunspellDictionary>>&
    GetHunspellDictionaries();
  
    // Returns whether spellchecking is enabled in preferences and if there are
    // dictionaries available.
    bool IsSpellcheckEnabled() const;
  
    // NotificationProfile implementation.
    void Observe(int type,
                 const content::NotificationSource& source,
                 const content::NotificationDetails& details) override;
  
    // SpellcheckCustomDictionary::Observer implementation.
    void OnCustomDictionaryLoaded() override;
    void OnCustomDictionaryChanged(
        const SpellcheckCustomDictionary::Change& change) override;
  
    // SpellcheckHunspellDictionary::Observer implementation.
    void OnHunspellDictionaryInitialized(const std::string& language) override;
    void OnHunspellDictionaryDownloadBegin(const std::string& language) override;
    void OnHunspellDictionaryDownloadSuccess(
        const std::string& language) override;
    void OnHunspellDictionaryDownloadFailure(
        const std::string& language) override;
  
    // One-time initialization of dictionaries if needed.
    void InitializeDictionaries(base::OnceClosure done);
  
  #if defined(OS_WIN)
    // Callback for spellcheck_platform::RetrieveSpellcheckLanguages. Populates
    // map of preferred languages to available platform dictionaries then
    // loads the dictionaries.
    void InitWindowsDictionaryLanguages(
        const std::vector<std::string>& windows_spellcheck_languages);
  
    // Indicates whether given accept language has Windows spellcheck platform
    // support.
    bool UsesWindowsDictionary(std::string accept_language) const;
  #endif  // defined(OS_WIN)
  
    // The returned pointer can be null if the current platform doesn't need a
    // per-profile, platform-specific spell check object. Currently, only Windows
    // requires one, and only on certain versions.
    PlatformSpellChecker* platform_spell_checker() {
      return platform_spell_checker_.get();
    }
  
    // Indicates whether dictionaries have been loaded initially.
    bool dictionaries_loaded() const { return dictionaries_loaded_; }
  
    // Allows tests to override how SpellcheckService binds its interface
    // receiver, instead of going through a RenderProcessHost by default.
    using SpellCheckerBinder = base::RepeatingCallback<void(
        mojo::PendingReceiver<spellcheck::mojom::SpellChecker>)>;
    static void OverrideBinderForTesting(SpellCheckerBinder binder);
  
   private:
    FRIEND_TEST_ALL_PREFIXES(SpellcheckServiceBrowserTest, DeleteCorruptedBDICT);
  #if defined(OS_WIN)
    FRIEND_TEST_ALL_PREFIXES(SpellcheckServiceWindowsHybridBrowserTest,
                             WindowsHybridSpellcheck);
    FRIEND_TEST_ALL_PREFIXES(SpellcheckServiceWindowsHybridBrowserTestDelayInit,
                             WindowsHybridSpellcheckDelayInit);
    friend class SpellcheckServiceHybridUnitTestBase;
    friend class SpellcheckServiceHybridUnitTestDelayInitBase;
    friend class extensions::LanguageSettingsPrivateApiTestDelayInit;
  #endif  // defined(OS_WIN)
  
    // Starts the process of loading the dictionaries (Hunspell and platform). Can
    // be called multiple times in a browser session if spellcheck settings
    // change.
    void LoadDictionaries();
  
    // Parses a full BCP47 language tag to return just the language subtag,
    // optionally with a hyphen and script subtag appended.
    static std::string GetLanguageAndScriptTag(const std::string& full_tag,
                                               bool include_script_tag);
  
  #if defined(OS_WIN)
    // Returns the language subtag (first part of the full BCP47 tag)
    // if the generic accept language is supported by the browser.
    static std::string GetSupportedAcceptLanguageCodeGenericOnly(
        const std::string& supported_language_full_tag,
        const std::vector<std::string>& accept_languages);
  
    // Returns true if full BCP47 language tag contains private use subtag (e.g in
    // the tag "ja-Latn-JP-x-ext"), indicating the tag is only for use by private
    // agreement.
    static bool HasPrivateUseSubTag(const std::string& full_tag);
  
    // Returns the BCP47 language tag to pass to the Windows spellcheck API, based
    // on the accept language and full tag, with special logic for languages that
    // can be written in different scripts.
    static std::string GetTagToPassToWindowsSpellchecker(
        const std::string& accept_language,
        const std::string& supported_language_full_tag);
  #endif  // defined(OS_WIN)
  
    // Attaches an event so browser tests can listen the status events.
    static void AttachStatusEvent(base::WaitableEvent* status_event);
  
    // Returns the status event type.
    static EventType GetStatusEvent();
  
    mojo::Remote<spellcheck::mojom::SpellChecker> GetSpellCheckerForProcess(
        content::RenderProcessHost* host);
  
    // Pass all renderers some basic initialization information.
    void InitForAllRenderers();
  
    // Reacts to a change in user preference on which languages should be used for
    // spellchecking.
    void OnSpellCheckDictionariesChanged();
  
    // Notification handler for changes to prefs::kSpellCheckUseSpellingService.
    void OnUseSpellingServiceChanged();
  
    // Notification handler for changes to prefs::kAcceptLanguages. Removes from
    // prefs::kSpellCheckDictionaries any languages that are not in
    // prefs::kAcceptLanguages.
    void OnAcceptLanguagesChanged();
  
    // Gets the user languages from the accept_languages pref and trims them of
    // leading and trailing whitespaces. If |normalize_for_spellcheck| is |true|,
    // also normalizes the format to xx or xx-YY based on the list of spell check
    // languages supported by Hunspell. Note that if |normalize_for_spellcheck| is
    // |true|, languages not supported by Hunspell will be returned as empty
    // strings.
    std::vector<std::string> GetNormalizedAcceptLanguages(
        bool normalize_for_spellcheck = true) const;
  
  #if defined(OS_WIN)
    // Initializes the platform spell checker.
    void InitializePlatformSpellchecker();
  
    // Records statistics about spell check support for the user's Chrome locales.
    void RecordChromeLocalesStats();
  
    // Records statistics about which spell checker supports which of the user's
    // enabled spell check locales.
    void RecordSpellcheckLocalesStats();
  
    // Adds an item to the cached collection mapping an accept language from
    // language settings to a BCP47 language tag to be passed to the Windows
    // spellchecker API, guarding against duplicate entries for the same accept
    // language.
    void AddWindowsSpellcheckDictionary(
        const std::string& accept_language,
        const std::string& supported_language_full_tag);
  
    // Gets the BCP47 language tag to pass to Windows spellcheck API, by
    // searching through the collection of languages already known to have
    // Windows spellchecker support on the system. Can return an empty string
    // if there is no Windows spellchecker support for this language on the
    // system.
    std::string GetSupportedWindowsDictionaryLanguage(
        const std::string& accept_language) const;
  
    // Test-only method for adding fake list of platform spellcheck languages
    // before calling InitializeDictionaries().
    void AddSpellcheckLanguagesForTesting(
        const std::vector<std::string>& languages);
  #endif  // defined(OS_WIN)
  
    // WindowsSpellChecker must be created before the dictionary instantiation and
    // destroyed after dictionary destruction.
    std::unique_ptr<PlatformSpellChecker> platform_spell_checker_;
  
    PrefChangeRegistrar pref_change_registrar_;
    content::NotificationRegistrar registrar_;
  
    // A pointer to the BrowserContext which this service refers to.
    content::BrowserContext* context_;
  
    std::unique_ptr<SpellCheckHostMetrics> metrics_;
  
    std::unique_ptr<SpellcheckCustomDictionary> custom_dictionary_;
  
    std::vector<std::unique_ptr<SpellcheckHunspellDictionary>>
        hunspell_dictionaries_;
  
  #if defined(OS_WIN)
    // Maps accept language tags to Windows spellcheck BCP47 tags, an analog
    // of the hardcoded kSupportedSpellCheckerLanguages used for Hunspell,
    // with the difference that only language packs installed on the system
    // with spellchecker support are included.
    std::map<std::string, std::string> windows_spellcheck_dictionary_map_;
  
    // Callback passed as argument to InitializeDictionaries, and invoked when
    // the dictionaries are loaded for the first time.
    base::OnceClosure dictionaries_loaded_callback_;
  #endif  // defined(OS_WIN)
  
    // Flag indicating dictionaries have been loaded initially.
    bool dictionaries_loaded_ = false;
  
    base::WeakPtrFactory<SpellcheckService> weak_ptr_factory_{this};
  
    DISALLOW_COPY_AND_ASSIGN(SpellcheckService);
  };
  
  #endif  // CHROME_BROWSER_SPELLCHECKER_SPELLCHECK_SERVICE_H_
  