Commit b61740a1 authored by Bruce Long's avatar Bruce Long Committed by Commit Bot

Windows Spellcheck: Method to retrieve tags for dictionaries on system

A new method is added to asynchronously retrieve the BCP47 language
tags for registered Windows OS spellcheckers on the system, wrapping
the system API ISpellCheckerFactory::get_SupportedLanguages. The method
is for now only used in a unit test, but upcoming work will rely on
this method to allow languages without Hunspell support to still have
 Windows spellchecking enabled on the languages settings page.

Bug: 1000443
Change-Id: I03507650ec355e8c282695d8f25364bccb91449a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2144234Reviewed-by: default avatarGuillaume Jenkins <gujen@google.com>
Reviewed-by: default avatarRouslan Solomakhin <rouslan@chromium.org>
Commit-Queue: Bruce Long <brlong@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#759306}
parent 180094f9
...@@ -38,11 +38,26 @@ typedef base::OnceCallback<void(const spellcheck::PerLanguageSuggestions&)> ...@@ -38,11 +38,26 @@ typedef base::OnceCallback<void(const spellcheck::PerLanguageSuggestions&)>
GetSuggestionsCallback; GetSuggestionsCallback;
#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) #endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
typedef base::OnceCallback<void(const std::vector<std::string>& /* results */)>
RetrieveSpellcheckLanguagesCompleteCallback;
// Get the languages supported by the platform spellchecker and store them in // Get the languages supported by the platform spellchecker and store them in
// |spellcheck_languages|. Note that they must be converted to // |spellcheck_languages|. Note that they must be converted to
// Chromium style codes (en-US not en_US). See spellchecker.cc for a full list. // Chromium style codes (en-US not en_US). See spellchecker.cc for a full list.
void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages); void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages);
// Retrieve BCP47 language tags for registered platform spellcheckers
// on the system. Callback will pass an empty vector of language tags if the OS
// does not support spellcheck or this functionality is not yet implemented.
void RetrieveSpellcheckLanguages(
PlatformSpellChecker* spell_checker_instance,
RetrieveSpellcheckLanguagesCompleteCallback callback);
// Test-only method for adding fake list of platform spellcheck languages.
void AddSpellcheckLanguagesForTesting(
PlatformSpellChecker* spell_checker_instance,
const std::vector<std::string>& languages);
// Returns the language used for spellchecking on the platform. // Returns the language used for spellchecking on the platform.
std::string GetSpellCheckerLanguage(); std::string GetSpellCheckerLanguage();
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/logging.h"
#include "components/spellcheck/common/spellcheck_features.h" #include "components/spellcheck/common/spellcheck_features.h"
class PlatformSpellChecker; class PlatformSpellChecker;
...@@ -15,6 +16,19 @@ namespace spellcheck_platform { ...@@ -15,6 +16,19 @@ namespace spellcheck_platform {
void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages) { void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages) {
} }
void RetrieveSpellcheckLanguages(
PlatformSpellChecker* spell_checker_instance,
RetrieveSpellcheckLanguagesCompleteCallback callback) {
NOTIMPLEMENTED();
std::move(callback).Run(std::vector<std::string>());
}
void AddSpellcheckLanguagesForTesting(
PlatformSpellChecker* spell_checker_instance,
const std::vector<std::string>& languages) {
NOTIMPLEMENTED();
}
std::string GetSpellCheckerLanguage() { std::string GetSpellCheckerLanguage() {
return std::string(); return std::string();
} }
......
...@@ -109,6 +109,19 @@ void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages) { ...@@ -109,6 +109,19 @@ void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages) {
} }
} }
void RetrieveSpellcheckLanguages(
PlatformSpellChecker* spell_checker_instance,
RetrieveSpellcheckLanguagesCompleteCallback callback) {
NOTIMPLEMENTED();
std::move(callback).Run(std::vector<std::string>());
}
void AddSpellcheckLanguagesForTesting(
PlatformSpellChecker* spell_checker_instance,
const std::vector<std::string>& languages) {
NOTIMPLEMENTED();
}
std::string GetSpellCheckerLanguage() { std::string GetSpellCheckerLanguage() {
return ConvertLanguageCodeFromMac([SharedSpellChecker() language]); return ConvertLanguageCodeFromMac([SharedSpellChecker() language]);
} }
......
...@@ -92,6 +92,20 @@ void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages) { ...@@ -92,6 +92,20 @@ void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages) {
// Not used in Windows // Not used in Windows
} }
void RetrieveSpellcheckLanguages(
PlatformSpellChecker* spell_checker_instance,
RetrieveSpellcheckLanguagesCompleteCallback callback) {
reinterpret_cast<WindowsSpellChecker*>(spell_checker_instance)
->RetrieveSpellcheckLanguages(std::move(callback));
}
void AddSpellcheckLanguagesForTesting(
PlatformSpellChecker* spell_checker_instance,
const std::vector<std::string>& languages) {
reinterpret_cast<WindowsSpellChecker*>(spell_checker_instance)
->AddSpellcheckLanguagesForTesting(languages);
}
int GetDocumentTag() { int GetDocumentTag() {
return 1; // Not used in Windows return 1; // Not used in Windows
} }
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
#include <windows.foundation.collections.h> #include <windows.foundation.collections.h>
#include <windows.globalization.h> #include <windows.globalization.h>
#include <windows.system.userprofile.h> #include <windows.system.userprofile.h>
#include <winnls.h> // ResolveLocaleName
#include <wrl/client.h> #include <wrl/client.h>
#include <algorithm> #include <algorithm>
...@@ -38,15 +37,126 @@ ...@@ -38,15 +37,126 @@
#include "components/spellcheck/common/spellcheck_result.h" #include "components/spellcheck/common/spellcheck_result.h"
#include "components/spellcheck/spellcheck_buildflags.h" #include "components/spellcheck/spellcheck_buildflags.h"
WindowsSpellChecker::BackgroundHelper::BackgroundHelper( namespace windows_spell_checker {
// Helper class that handles calls to the native Windows APIs. All
// invocations of these methods must be posted to the same COM
// |SingleThreadTaskRunner|. This is enforced by checks that all methods run
// on the given |SingleThreadTaskRunner|.
class BackgroundHelper {
public:
explicit BackgroundHelper(
scoped_refptr<base::SingleThreadTaskRunner> background_task_runner);
~BackgroundHelper();
// Creates the native spell check factory, which is the main entry point to
// the native spell checking APIs.
void CreateSpellCheckerFactory();
// Creates a native |ISpellchecker| for the given language |lang_tag| and
// returns a boolean indicating success.
bool CreateSpellChecker(const std::string& lang_tag);
// Removes the native spell checker for the given language |lang_tag| from
// the map of active spell checkers.
void DisableSpellChecker(const std::string& lang_tag);
// Requests spell checking of string |text| for all active spell checkers
// (all languages) and returns a vector of |SpellCheckResult| containing the
// results.
std::vector<SpellCheckResult> RequestTextCheckForAllLanguages(
int document_tag,
const base::string16& text);
// Gets spelling suggestions for |word| from all active spell checkers (all
// languages), keeping the suggestions separate per language, and returns
// the results in a vector of vector of strings.
spellcheck::PerLanguageSuggestions GetPerLanguageSuggestions(
const base::string16& word);
// Fills the given vector |optional_suggestions| with a number (up to
// kMaxSuggestions) of suggestions for the string |wrong_word| using the
// native spell checker for language |lang_tag|.
void FillSuggestionList(const std::string& lang_tag,
const base::string16& wrong_word,
std::vector<base::string16>* optional_suggestions);
// Adds |word| to the native dictionary of all active spell checkers (all
// languages).
void AddWordForAllLanguages(const base::string16& word);
// Removes |word| from the native dictionary of all active spell checkers
// (all languages). This requires a newer version of the native spell
// check APIs, so it may be a no-op on older Windows versions.
void RemoveWordForAllLanguages(const base::string16& word);
// Adds |word| to the ignore list of all active spell checkers (all
// languages).
void IgnoreWordForAllLanguages(const base::string16& word);
// Returns |true| if a native spell checker is available for the given
// language |lang_tag|. This is based on the installed language packs in the
// OS settings.
bool IsLanguageSupported(const std::string& lang_tag);
// Returns |true| if an |ISpellCheckerFactory| has been initialized.
bool IsSpellCheckerFactoryInitialized();
// Returns |true| if an |ISpellChecker| has been initialized for the given
// language |lang_tag|.
bool SpellCheckerReady(const std::string& lang_tag);
// Returns the |ISpellChecker| pointer for the given language |lang_tag|.
Microsoft::WRL::ComPtr<ISpellChecker> GetSpellChecker(
const std::string& lang_tag);
// Records metrics about spell check support for the user's Chrome locales.
void RecordChromeLocalesStats(const std::vector<std::string> chrome_locales,
SpellCheckHostMetrics* metrics);
// Records metrics about spell check support for the user's enabled spell
// check locales.
void RecordSpellcheckLocalesStats(
const std::vector<std::string> spellcheck_locales,
SpellCheckHostMetrics* metrics);
// Retrieve language tags for registered Windows OS
// spellcheckers on the system.
std::vector<std::string> RetrieveSpellcheckLanguages();
// Test-only method for adding fake list of Windows spellcheck languages.
void AddSpellcheckLanguagesForTesting(
const std::vector<std::string>& languages);
// Sorts the given locales into four buckets based on spell check support
// (both native and Hunspell, Hunspell only, native only, none).
LocalesSupportInfo DetermineLocalesSupport(
const std::vector<std::string>& locales);
private:
// The native factory to interact with spell check APIs.
Microsoft::WRL::ComPtr<ISpellCheckerFactory> spell_checker_factory_;
// The map of active spell checkers. Each entry maps a language tag to an
// |ISpellChecker| (there is one |ISpellChecker| per language).
std::map<std::string, Microsoft::WRL::ComPtr<ISpellChecker>>
spell_checker_map_;
std::vector<std::string> windows_spellcheck_languages_for_testing_;
// Task runner only used to enforce valid sequencing.
scoped_refptr<base::SingleThreadTaskRunner> background_task_runner_;
}; // class BackgroundHelper
BackgroundHelper::BackgroundHelper(
scoped_refptr<base::SingleThreadTaskRunner> background_task_runner) scoped_refptr<base::SingleThreadTaskRunner> background_task_runner)
: background_task_runner_(std::move(background_task_runner)) {} : background_task_runner_(std::move(background_task_runner)) {}
WindowsSpellChecker::BackgroundHelper::~BackgroundHelper() { BackgroundHelper::~BackgroundHelper() {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
} }
void WindowsSpellChecker::BackgroundHelper::CreateSpellCheckerFactory() { void BackgroundHelper::CreateSpellCheckerFactory() {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
base::win::AssertComApartmentType(base::win::ComApartmentType::STA); base::win::AssertComApartmentType(base::win::ComApartmentType::STA);
...@@ -58,8 +168,7 @@ void WindowsSpellChecker::BackgroundHelper::CreateSpellCheckerFactory() { ...@@ -58,8 +168,7 @@ void WindowsSpellChecker::BackgroundHelper::CreateSpellCheckerFactory() {
} }
} }
bool WindowsSpellChecker::BackgroundHelper::CreateSpellChecker( bool BackgroundHelper::CreateSpellChecker(const std::string& lang_tag) {
const std::string& lang_tag) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
if (!IsSpellCheckerFactoryInitialized()) if (!IsSpellCheckerFactoryInitialized())
...@@ -84,8 +193,7 @@ bool WindowsSpellChecker::BackgroundHelper::CreateSpellChecker( ...@@ -84,8 +193,7 @@ bool WindowsSpellChecker::BackgroundHelper::CreateSpellChecker(
return false; return false;
} }
void WindowsSpellChecker::BackgroundHelper::DisableSpellChecker( void BackgroundHelper::DisableSpellChecker(const std::string& lang_tag) {
const std::string& lang_tag) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
if (!IsSpellCheckerFactoryInitialized()) if (!IsSpellCheckerFactoryInitialized())
...@@ -97,8 +205,7 @@ void WindowsSpellChecker::BackgroundHelper::DisableSpellChecker( ...@@ -97,8 +205,7 @@ void WindowsSpellChecker::BackgroundHelper::DisableSpellChecker(
} }
} }
std::vector<SpellCheckResult> std::vector<SpellCheckResult> BackgroundHelper::RequestTextCheckForAllLanguages(
WindowsSpellChecker::BackgroundHelper::RequestTextCheckForAllLanguages(
int document_tag, int document_tag,
const base::string16& text) { const base::string16& text) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
...@@ -164,8 +271,7 @@ WindowsSpellChecker::BackgroundHelper::RequestTextCheckForAllLanguages( ...@@ -164,8 +271,7 @@ WindowsSpellChecker::BackgroundHelper::RequestTextCheckForAllLanguages(
return final_results; return final_results;
} }
spellcheck::PerLanguageSuggestions spellcheck::PerLanguageSuggestions BackgroundHelper::GetPerLanguageSuggestions(
WindowsSpellChecker::BackgroundHelper::GetPerLanguageSuggestions(
const base::string16& word) { const base::string16& word) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
spellcheck::PerLanguageSuggestions suggestions; spellcheck::PerLanguageSuggestions suggestions;
...@@ -181,7 +287,7 @@ WindowsSpellChecker::BackgroundHelper::GetPerLanguageSuggestions( ...@@ -181,7 +287,7 @@ WindowsSpellChecker::BackgroundHelper::GetPerLanguageSuggestions(
return suggestions; return suggestions;
} }
void WindowsSpellChecker::BackgroundHelper::FillSuggestionList( void BackgroundHelper::FillSuggestionList(
const std::string& lang_tag, const std::string& lang_tag,
const base::string16& wrong_word, const base::string16& wrong_word,
std::vector<base::string16>* optional_suggestions) { std::vector<base::string16>* optional_suggestions) {
...@@ -207,8 +313,7 @@ void WindowsSpellChecker::BackgroundHelper::FillSuggestionList( ...@@ -207,8 +313,7 @@ void WindowsSpellChecker::BackgroundHelper::FillSuggestionList(
} }
} }
void WindowsSpellChecker::BackgroundHelper::AddWordForAllLanguages( void BackgroundHelper::AddWordForAllLanguages(const base::string16& word) {
const base::string16& word) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
for (auto it = spell_checker_map_.begin(); it != spell_checker_map_.end(); for (auto it = spell_checker_map_.begin(); it != spell_checker_map_.end();
++it) { ++it) {
...@@ -217,8 +322,7 @@ void WindowsSpellChecker::BackgroundHelper::AddWordForAllLanguages( ...@@ -217,8 +322,7 @@ void WindowsSpellChecker::BackgroundHelper::AddWordForAllLanguages(
} }
} }
void WindowsSpellChecker::BackgroundHelper::RemoveWordForAllLanguages( void BackgroundHelper::RemoveWordForAllLanguages(const base::string16& word) {
const base::string16& word) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
for (auto it = spell_checker_map_.begin(); it != spell_checker_map_.end(); for (auto it = spell_checker_map_.begin(); it != spell_checker_map_.end();
++it) { ++it) {
...@@ -231,8 +335,7 @@ void WindowsSpellChecker::BackgroundHelper::RemoveWordForAllLanguages( ...@@ -231,8 +335,7 @@ void WindowsSpellChecker::BackgroundHelper::RemoveWordForAllLanguages(
} }
} }
void WindowsSpellChecker::BackgroundHelper::IgnoreWordForAllLanguages( void BackgroundHelper::IgnoreWordForAllLanguages(const base::string16& word) {
const base::string16& word) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
for (auto it = spell_checker_map_.begin(); it != spell_checker_map_.end(); for (auto it = spell_checker_map_.begin(); it != spell_checker_map_.end();
++it) { ++it) {
...@@ -241,8 +344,7 @@ void WindowsSpellChecker::BackgroundHelper::IgnoreWordForAllLanguages( ...@@ -241,8 +344,7 @@ void WindowsSpellChecker::BackgroundHelper::IgnoreWordForAllLanguages(
} }
} }
bool WindowsSpellChecker::BackgroundHelper::IsLanguageSupported( bool BackgroundHelper::IsLanguageSupported(const std::string& lang_tag) {
const std::string& lang_tag) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
if (!IsSpellCheckerFactoryInitialized()) { if (!IsSpellCheckerFactoryInitialized()) {
...@@ -258,8 +360,44 @@ bool WindowsSpellChecker::BackgroundHelper::IsLanguageSupported( ...@@ -258,8 +360,44 @@ bool WindowsSpellChecker::BackgroundHelper::IsLanguageSupported(
return SUCCEEDED(hr) && is_language_supported; return SUCCEEDED(hr) && is_language_supported;
} }
LocalesSupportInfo std::vector<std::string> BackgroundHelper::RetrieveSpellcheckLanguages() {
WindowsSpellChecker::BackgroundHelper::DetermineLocalesSupport( DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
std::vector<std::string> spellcheck_languages;
if (!windows_spellcheck_languages_for_testing_.empty())
return windows_spellcheck_languages_for_testing_;
if (!IsSpellCheckerFactoryInitialized())
return spellcheck_languages;
Microsoft::WRL::ComPtr<IEnumString> supported_languages;
HRESULT hr =
spell_checker_factory_->get_SupportedLanguages(&supported_languages);
DVLOG_IF(1, FAILED(hr)) << "Call to get_SupportedLanguages failed, hr="
<< logging::SystemErrorCodeToString(hr);
if (!SUCCEEDED(hr))
return spellcheck_languages;
while (hr == S_OK) {
base::win::ScopedCoMem<wchar_t> supported_language;
hr = supported_languages->Next(
1 /* items to retrieve */, &supported_language,
nullptr /* number of items retrieved, unneeded if only 1 requested */);
if (hr == S_OK) {
spellcheck_languages.push_back(
base::WideToUTF8(supported_language.get()));
}
}
return spellcheck_languages;
}
void BackgroundHelper::AddSpellcheckLanguagesForTesting(
const std::vector<std::string>& languages) {
windows_spellcheck_languages_for_testing_ = languages;
}
LocalesSupportInfo BackgroundHelper::DetermineLocalesSupport(
const std::vector<std::string>& locales) { const std::vector<std::string>& locales) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
size_t locales_supported_by_hunspell_and_native = 0; size_t locales_supported_by_hunspell_and_native = 0;
...@@ -289,26 +427,24 @@ WindowsSpellChecker::BackgroundHelper::DetermineLocalesSupport( ...@@ -289,26 +427,24 @@ WindowsSpellChecker::BackgroundHelper::DetermineLocalesSupport(
unsupported_locales}; unsupported_locales};
} }
bool WindowsSpellChecker::BackgroundHelper::IsSpellCheckerFactoryInitialized() { bool BackgroundHelper::IsSpellCheckerFactoryInitialized() {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
return spell_checker_factory_ != nullptr; return spell_checker_factory_ != nullptr;
} }
bool WindowsSpellChecker::BackgroundHelper::SpellCheckerReady( bool BackgroundHelper::SpellCheckerReady(const std::string& lang_tag) {
const std::string& lang_tag) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
return spell_checker_map_.find(lang_tag) != spell_checker_map_.end(); return spell_checker_map_.find(lang_tag) != spell_checker_map_.end();
} }
Microsoft::WRL::ComPtr<ISpellChecker> Microsoft::WRL::ComPtr<ISpellChecker> BackgroundHelper::GetSpellChecker(
WindowsSpellChecker::BackgroundHelper::GetSpellChecker(
const std::string& lang_tag) { const std::string& lang_tag) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
DCHECK(SpellCheckerReady(lang_tag)); DCHECK(SpellCheckerReady(lang_tag));
return spell_checker_map_.find(lang_tag)->second; return spell_checker_map_.find(lang_tag)->second;
} }
void WindowsSpellChecker::BackgroundHelper::RecordChromeLocalesStats( void BackgroundHelper::RecordChromeLocalesStats(
const std::vector<std::string> chrome_locales, const std::vector<std::string> chrome_locales,
SpellCheckHostMetrics* metrics) { SpellCheckHostMetrics* metrics) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
...@@ -323,7 +459,7 @@ void WindowsSpellChecker::BackgroundHelper::RecordChromeLocalesStats( ...@@ -323,7 +459,7 @@ void WindowsSpellChecker::BackgroundHelper::RecordChromeLocalesStats(
metrics->RecordAcceptLanguageStats(locales_info); metrics->RecordAcceptLanguageStats(locales_info);
} }
void WindowsSpellChecker::BackgroundHelper::RecordSpellcheckLocalesStats( void BackgroundHelper::RecordSpellcheckLocalesStats(
const std::vector<std::string> spellcheck_locales, const std::vector<std::string> spellcheck_locales,
SpellCheckHostMetrics* metrics) { SpellCheckHostMetrics* metrics) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
...@@ -338,18 +474,25 @@ void WindowsSpellChecker::BackgroundHelper::RecordSpellcheckLocalesStats( ...@@ -338,18 +474,25 @@ void WindowsSpellChecker::BackgroundHelper::RecordSpellcheckLocalesStats(
metrics->RecordSpellcheckLanguageStats(locales_info); metrics->RecordSpellcheckLanguageStats(locales_info);
} }
} // namespace windows_spell_checker
WindowsSpellChecker::WindowsSpellChecker( WindowsSpellChecker::WindowsSpellChecker(
scoped_refptr<base::SingleThreadTaskRunner> background_task_runner) scoped_refptr<base::SingleThreadTaskRunner> background_task_runner)
: background_task_runner_(background_task_runner) { : background_task_runner_(background_task_runner) {
background_helper_ = std::make_unique<WindowsSpellChecker::BackgroundHelper>( background_helper_ =
std::make_unique<windows_spell_checker::BackgroundHelper>(
std::move(background_task_runner)); std::move(background_task_runner));
background_task_runner_->PostTask( background_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&BackgroundHelper::CreateSpellCheckerFactory, FROM_HERE,
base::BindOnce(
&windows_spell_checker::BackgroundHelper::CreateSpellCheckerFactory,
base::Unretained(background_helper_.get()))); base::Unretained(background_helper_.get())));
} }
WindowsSpellChecker::~WindowsSpellChecker() { WindowsSpellChecker::~WindowsSpellChecker() {
// |background_helper_| is deleted on the background thread after all other
// background tasks complete.
background_task_runner_->DeleteSoon(FROM_HERE, std::move(background_helper_)); background_task_runner_->DeleteSoon(FROM_HERE, std::move(background_helper_));
} }
...@@ -358,7 +501,8 @@ void WindowsSpellChecker::CreateSpellChecker( ...@@ -358,7 +501,8 @@ void WindowsSpellChecker::CreateSpellChecker(
base::OnceCallback<void(bool)> callback) { base::OnceCallback<void(bool)> callback) {
base::PostTaskAndReplyWithResult( base::PostTaskAndReplyWithResult(
background_task_runner_.get(), FROM_HERE, background_task_runner_.get(), FROM_HERE,
base::BindOnce(&BackgroundHelper::CreateSpellChecker, base::BindOnce(
&windows_spell_checker::BackgroundHelper::CreateSpellChecker,
base::Unretained(background_helper_.get()), lang_tag), base::Unretained(background_helper_.get()), lang_tag),
std::move(callback)); std::move(callback));
} }
...@@ -366,7 +510,8 @@ void WindowsSpellChecker::CreateSpellChecker( ...@@ -366,7 +510,8 @@ void WindowsSpellChecker::CreateSpellChecker(
void WindowsSpellChecker::DisableSpellChecker(const std::string& lang_tag) { void WindowsSpellChecker::DisableSpellChecker(const std::string& lang_tag) {
background_task_runner_->PostTask( background_task_runner_->PostTask(
FROM_HERE, FROM_HERE,
base::BindOnce(&BackgroundHelper::DisableSpellChecker, base::BindOnce(
&windows_spell_checker::BackgroundHelper::DisableSpellChecker,
base::Unretained(background_helper_.get()), lang_tag)); base::Unretained(background_helper_.get()), lang_tag));
} }
...@@ -376,7 +521,8 @@ void WindowsSpellChecker::RequestTextCheck( ...@@ -376,7 +521,8 @@ void WindowsSpellChecker::RequestTextCheck(
spellcheck_platform::TextCheckCompleteCallback callback) { spellcheck_platform::TextCheckCompleteCallback callback) {
base::PostTaskAndReplyWithResult( base::PostTaskAndReplyWithResult(
background_task_runner_.get(), FROM_HERE, background_task_runner_.get(), FROM_HERE,
base::BindOnce(&BackgroundHelper::RequestTextCheckForAllLanguages, base::BindOnce(&windows_spell_checker::BackgroundHelper::
RequestTextCheckForAllLanguages,
base::Unretained(background_helper_.get()), document_tag, base::Unretained(background_helper_.get()), document_tag,
text), text),
std::move(callback)); std::move(callback));
...@@ -387,7 +533,8 @@ void WindowsSpellChecker::GetPerLanguageSuggestions( ...@@ -387,7 +533,8 @@ void WindowsSpellChecker::GetPerLanguageSuggestions(
spellcheck_platform::GetSuggestionsCallback callback) { spellcheck_platform::GetSuggestionsCallback callback) {
base::PostTaskAndReplyWithResult( base::PostTaskAndReplyWithResult(
background_task_runner_.get(), FROM_HERE, background_task_runner_.get(), FROM_HERE,
base::BindOnce(&BackgroundHelper::GetPerLanguageSuggestions, base::BindOnce(
&windows_spell_checker::BackgroundHelper::GetPerLanguageSuggestions,
base::Unretained(background_helper_.get()), word), base::Unretained(background_helper_.get()), word),
std::move(callback)); std::move(callback));
} }
...@@ -395,7 +542,8 @@ void WindowsSpellChecker::GetPerLanguageSuggestions( ...@@ -395,7 +542,8 @@ void WindowsSpellChecker::GetPerLanguageSuggestions(
void WindowsSpellChecker::AddWordForAllLanguages(const base::string16& word) { void WindowsSpellChecker::AddWordForAllLanguages(const base::string16& word) {
background_task_runner_->PostTask( background_task_runner_->PostTask(
FROM_HERE, FROM_HERE,
base::BindOnce(&BackgroundHelper::AddWordForAllLanguages, base::BindOnce(
&windows_spell_checker::BackgroundHelper::AddWordForAllLanguages,
base::Unretained(background_helper_.get()), word)); base::Unretained(background_helper_.get()), word));
} }
...@@ -403,7 +551,8 @@ void WindowsSpellChecker::RemoveWordForAllLanguages( ...@@ -403,7 +551,8 @@ void WindowsSpellChecker::RemoveWordForAllLanguages(
const base::string16& word) { const base::string16& word) {
background_task_runner_->PostTask( background_task_runner_->PostTask(
FROM_HERE, FROM_HERE,
base::BindOnce(&BackgroundHelper::RemoveWordForAllLanguages, base::BindOnce(
&windows_spell_checker::BackgroundHelper::RemoveWordForAllLanguages,
base::Unretained(background_helper_.get()), word)); base::Unretained(background_helper_.get()), word));
} }
...@@ -411,34 +560,54 @@ void WindowsSpellChecker::IgnoreWordForAllLanguages( ...@@ -411,34 +560,54 @@ void WindowsSpellChecker::IgnoreWordForAllLanguages(
const base::string16& word) { const base::string16& word) {
background_task_runner_->PostTask( background_task_runner_->PostTask(
FROM_HERE, FROM_HERE,
base::BindOnce(&BackgroundHelper::IgnoreWordForAllLanguages, base::BindOnce(
&windows_spell_checker::BackgroundHelper::IgnoreWordForAllLanguages,
base::Unretained(background_helper_.get()), word)); base::Unretained(background_helper_.get()), word));
} }
void WindowsSpellChecker::IsLanguageSupported(
const std::string& lang_tag,
base::OnceCallback<void(bool)> callback) {
base::PostTaskAndReplyWithResult(
background_task_runner_.get(), FROM_HERE,
base::BindOnce(&BackgroundHelper::IsLanguageSupported,
base::Unretained(background_helper_.get()), lang_tag),
std::move(callback));
}
void WindowsSpellChecker::RecordChromeLocalesStats( void WindowsSpellChecker::RecordChromeLocalesStats(
const std::vector<std::string> chrome_locales, const std::vector<std::string> chrome_locales,
SpellCheckHostMetrics* metrics) { SpellCheckHostMetrics* metrics) {
background_task_runner_->PostTask( background_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&BackgroundHelper::RecordChromeLocalesStats, FROM_HERE,
base::Unretained(background_helper_.get()), base::BindOnce(
std::move(chrome_locales), metrics)); &windows_spell_checker::BackgroundHelper::RecordChromeLocalesStats,
base::Unretained(background_helper_.get()), std::move(chrome_locales),
metrics));
} }
void WindowsSpellChecker::RecordSpellcheckLocalesStats( void WindowsSpellChecker::RecordSpellcheckLocalesStats(
const std::vector<std::string> spellcheck_locales, const std::vector<std::string> spellcheck_locales,
SpellCheckHostMetrics* metrics) { SpellCheckHostMetrics* metrics) {
background_task_runner_->PostTask( background_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&BackgroundHelper::RecordSpellcheckLocalesStats, FROM_HERE, base::BindOnce(&windows_spell_checker::BackgroundHelper::
RecordSpellcheckLocalesStats,
base::Unretained(background_helper_.get()), base::Unretained(background_helper_.get()),
std::move(spellcheck_locales), metrics)); std::move(spellcheck_locales), metrics));
} }
void WindowsSpellChecker::IsLanguageSupported(
const std::string& lang_tag,
base::OnceCallback<void(bool)> callback) {
base::PostTaskAndReplyWithResult(
background_task_runner_.get(), FROM_HERE,
base::BindOnce(
&windows_spell_checker::BackgroundHelper::IsLanguageSupported,
base::Unretained(background_helper_.get()), lang_tag),
std::move(callback));
}
void WindowsSpellChecker::RetrieveSpellcheckLanguages(
spellcheck_platform::RetrieveSpellcheckLanguagesCompleteCallback callback) {
base::PostTaskAndReplyWithResult(
background_task_runner_.get(), FROM_HERE,
base::BindOnce(
&windows_spell_checker::BackgroundHelper::RetrieveSpellcheckLanguages,
base::Unretained(background_helper_.get())),
std::move(callback));
}
void WindowsSpellChecker::AddSpellcheckLanguagesForTesting(
const std::vector<std::string>& languages) {
background_helper_->AddSpellcheckLanguagesForTesting(languages);
}
...@@ -22,12 +22,16 @@ namespace base { ...@@ -22,12 +22,16 @@ namespace base {
class SingleThreadTaskRunner; class SingleThreadTaskRunner;
} }
namespace windows_spell_checker {
class BackgroundHelper;
}
// Class used to store all the COM objects and control their lifetime. The class // Class used to store all the COM objects and control their lifetime. The class
// also provides wrappers for ISpellCheckerFactory and ISpellChecker APIs. All // also provides wrappers for ISpellCheckerFactory and ISpellChecker APIs. All
// COM calls are executed on the background thread. // COM calls are executed on the background thread.
class WindowsSpellChecker : public PlatformSpellChecker { class WindowsSpellChecker : public PlatformSpellChecker {
public: public:
WindowsSpellChecker( explicit WindowsSpellChecker(
scoped_refptr<base::SingleThreadTaskRunner> background_task_runner); scoped_refptr<base::SingleThreadTaskRunner> background_task_runner);
WindowsSpellChecker(const WindowsSpellChecker&) = delete; WindowsSpellChecker(const WindowsSpellChecker&) = delete;
...@@ -66,113 +70,26 @@ class WindowsSpellChecker : public PlatformSpellChecker { ...@@ -66,113 +70,26 @@ class WindowsSpellChecker : public PlatformSpellChecker {
void IsLanguageSupported(const std::string& lang_tag, void IsLanguageSupported(const std::string& lang_tag,
base::OnceCallback<void(bool)> callback); base::OnceCallback<void(bool)> callback);
private: // Asynchronously retrieve language tags for registered Windows OS
// Private inner class that handles calls to the native Windows APIs. All // spellcheckers on the system. Callback will pass an empty vector of language
// invocations of these methods must be posted to the same COM // tags if the OS does not support spellcheck.
// |SingleThreadTaskRunner|. This is enforced by checks that all methods run void RetrieveSpellcheckLanguages(
// on the given |SingleThreadTaskRunner|. spellcheck_platform::RetrieveSpellcheckLanguagesCompleteCallback
class BackgroundHelper { callback);
public:
BackgroundHelper(
scoped_refptr<base::SingleThreadTaskRunner> background_task_runner);
~BackgroundHelper();
// Creates the native spell check factory, which is the main entry point to
// the native spell checking APIs.
void CreateSpellCheckerFactory();
// Creates a native |ISpellchecker| for the given language |lang_tag| and
// returns a boolean indicating success.
bool CreateSpellChecker(const std::string& lang_tag);
// Removes the native spell checker for the given language |lang_tag| from
// the map of active spell checkers.
void DisableSpellChecker(const std::string& lang_tag);
// Requests spell checking of string |text| for all active spell checkers
// (all languages) and returns a vector of |SpellCheckResult| containing the
// results.
std::vector<SpellCheckResult> RequestTextCheckForAllLanguages(
int document_tag,
const base::string16& text);
// Gets spelling suggestions for |word| from all active spell checkers (all
// languages), keeping the suggestions separate per language, and returns
// the results in a vector of vector of strings.
spellcheck::PerLanguageSuggestions GetPerLanguageSuggestions(
const base::string16& word);
// Fills the given vector |optional_suggestions| with a number (up to
// kMaxSuggestions) of suggestions for the string |wrong_word| using the
// native spell checker for language |lang_tag|.
void FillSuggestionList(const std::string& lang_tag,
const base::string16& wrong_word,
std::vector<base::string16>* optional_suggestions);
// Adds |word| to the native dictionary of all active spell checkers (all
// languages).
void AddWordForAllLanguages(const base::string16& word);
// Removes |word| from the native dictionary of all active spell checkers
// (all languages). This requires a newer version of the native spell
// check APIs, so it may be a no-op on older Windows versions.
void RemoveWordForAllLanguages(const base::string16& word);
// Adds |word| to the ignore list of all active spell checkers (all
// languages).
void IgnoreWordForAllLanguages(const base::string16& word);
// Returns |true| if a native spell checker is available for the given // Test-only method for adding fake list of Windows spellcheck languages.
// language |lang_tag|. This is based on the installed language packs in the void AddSpellcheckLanguagesForTesting(
// OS settings. const std::vector<std::string>& languages);
bool IsLanguageSupported(const std::string& lang_tag);
// Returns |true| if an |ISpellCheckerFactory| has been initialized.
bool IsSpellCheckerFactoryInitialized();
// Returns |true| if an |ISpellChecker| has been initialized for the given
// language |lang_tag|.
bool SpellCheckerReady(const std::string& lang_tag);
// Returns the |ISpellChecker| pointer for the given language |lang_tag|.
Microsoft::WRL::ComPtr<ISpellChecker> GetSpellChecker(
const std::string& lang_tag);
// Records metrics about spell check support for the user's Chrome locales.
void RecordChromeLocalesStats(const std::vector<std::string> chrome_locales,
SpellCheckHostMetrics* metrics);
// Records metrics about spell check support for the user's enabled spell
// check locales.
void RecordSpellcheckLocalesStats(
const std::vector<std::string> spellcheck_locales,
SpellCheckHostMetrics* metrics);
// Sorts the given locales into four buckets based on spell check support
// (both native and Hunspell, Hunspell only, native only, none).
LocalesSupportInfo DetermineLocalesSupport(
const std::vector<std::string>& locales);
private: private:
// The native factory to interact with spell check APIs.
Microsoft::WRL::ComPtr<ISpellCheckerFactory> spell_checker_factory_;
// The map of active spell checkers. Each entry maps a language tag to an
// |ISpellChecker| (there is one |ISpellChecker| per language).
std::map<std::string, Microsoft::WRL::ComPtr<ISpellChecker>>
spell_checker_map_;
// Task runner only used to enforce valid sequencing.
scoped_refptr<base::SingleThreadTaskRunner> background_task_runner_;
}; // class BackgroundHelper
// COM-enabled, single-thread task runner used to post invocations of // COM-enabled, single-thread task runner used to post invocations of
// BackgroundHelper methods to interact with spell check native APIs. // BackgroundHelper methods to interact with spell check native APIs.
scoped_refptr<base::SingleThreadTaskRunner> background_task_runner_; scoped_refptr<base::SingleThreadTaskRunner> background_task_runner_;
// Instance of the background helper to invoke native APIs on the COM-enabled // Instance of the background helper to invoke native APIs on the COM-enabled
// background thread. // background thread. |background_helper_| is deleted on the background thread
std::unique_ptr<BackgroundHelper> background_helper_; // after all other background tasks complete.
std::unique_ptr<windows_spell_checker::BackgroundHelper> background_helper_;
}; };
#endif // COMPONENTS_SPELLCHECK_BROWSER_WINDOWS_SPELL_CHECKER_H_ #endif // COMPONENTS_SPELLCHECK_BROWSER_WINDOWS_SPELL_CHECKER_H_
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <stddef.h> #include <stddef.h>
#include "base/bind.h" #include "base/bind.h"
#include "base/logging.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/single_thread_task_runner.h" #include "base/single_thread_task_runner.h"
#include "base/stl_util.h" #include "base/stl_util.h"
...@@ -30,21 +31,30 @@ namespace { ...@@ -30,21 +31,30 @@ namespace {
class WindowsSpellCheckerTest : public testing::Test { class WindowsSpellCheckerTest : public testing::Test {
public: public:
WindowsSpellCheckerTest() { WindowsSpellCheckerTest() {
if (spellcheck::WindowsVersionSupportsSpellchecker()) { #if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
feature_list_.InitAndEnableFeature( // Force hybrid spellchecking to be enabled.
spellcheck::kWinUseBrowserSpellChecker); feature_list_.InitWithFeatures(
/*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker,
spellcheck::kWinUseHybridSpellChecker},
/*disabled_features=*/{});
#else
feature_list_.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker);
#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
// The WindowsSpellchecker object can be created even on Windows versions
// that don't support platform spellchecking. However, the spellcheck
// factory won't be instantiated and the result returned in the
// CreateSpellChecker callback will be false.
win_spell_checker_ = std::make_unique<WindowsSpellChecker>( win_spell_checker_ = std::make_unique<WindowsSpellChecker>(
base::ThreadPool::CreateCOMSTATaskRunner({base::MayBlock()})); base::ThreadPool::CreateCOMSTATaskRunner({base::MayBlock()}));
win_spell_checker_->CreateSpellChecker( win_spell_checker_->CreateSpellChecker(
"en-US", base::BindOnce( "en-US",
&WindowsSpellCheckerTest::SetLanguageCompletionCallback, base::BindOnce(&WindowsSpellCheckerTest::SetLanguageCompletionCallback,
base::Unretained(this))); base::Unretained(this)));
RunUntilResultReceived(); RunUntilResultReceived();
} }
}
void RunUntilResultReceived() { void RunUntilResultReceived() {
if (callback_finished_) if (callback_finished_)
...@@ -82,6 +92,17 @@ class WindowsSpellCheckerTest : public testing::Test { ...@@ -82,6 +92,17 @@ class WindowsSpellCheckerTest : public testing::Test {
} }
#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) #endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
void RetrieveSpellcheckLanguagesCompletionCallback(
const std::vector<std::string>& spellcheck_languages) {
callback_finished_ = true;
spellcheck_languages_ = spellcheck_languages;
DVLOG(2) << "RetrieveSpellcheckLanguagesCompletionCallback: Dictionary "
"found for following language tags: "
<< base::JoinString(spellcheck_languages_, ", ");
if (quit_)
std::move(quit_).Run();
}
protected: protected:
std::unique_ptr<WindowsSpellChecker> win_spell_checker_; std::unique_ptr<WindowsSpellChecker> win_spell_checker_;
...@@ -94,17 +115,15 @@ class WindowsSpellCheckerTest : public testing::Test { ...@@ -94,17 +115,15 @@ class WindowsSpellCheckerTest : public testing::Test {
#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) #if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
spellcheck::PerLanguageSuggestions per_language_suggestions_; spellcheck::PerLanguageSuggestions per_language_suggestions_;
#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) #endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
std::vector<std::string> spellcheck_languages_;
base::test::TaskEnvironment task_environment_{ base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::MainThreadType::UI}; base::test::TaskEnvironment::MainThreadType::UI};
}; };
TEST_F(WindowsSpellCheckerTest, RequestTextCheck) { TEST_F(WindowsSpellCheckerTest, RequestTextCheck) {
if (!spellcheck::WindowsVersionSupportsSpellchecker()) { ASSERT_EQ(set_language_result_,
return; spellcheck::WindowsVersionSupportsSpellchecker());
}
ASSERT_TRUE(set_language_result_);
static const struct { static const struct {
const char* text_to_check; const char* text_to_check;
...@@ -131,6 +150,13 @@ TEST_F(WindowsSpellCheckerTest, RequestTextCheck) { ...@@ -131,6 +150,13 @@ TEST_F(WindowsSpellCheckerTest, RequestTextCheck) {
base::Unretained(this))); base::Unretained(this)));
RunUntilResultReceived(); RunUntilResultReceived();
if (!spellcheck::WindowsVersionSupportsSpellchecker()) {
// On Windows versions that don't support platform spellchecking, the
// returned vector of results should be empty.
ASSERT_TRUE(spell_check_results_.empty());
continue;
}
ASSERT_EQ(1u, spell_check_results_.size()) ASSERT_EQ(1u, spell_check_results_.size())
<< "RequestTextCheckTests case " << i << ": Wrong number of results"; << "RequestTextCheckTests case " << i << ": Wrong number of results";
...@@ -149,13 +175,52 @@ TEST_F(WindowsSpellCheckerTest, RequestTextCheck) { ...@@ -149,13 +175,52 @@ TEST_F(WindowsSpellCheckerTest, RequestTextCheck) {
} }
} }
#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) TEST_F(WindowsSpellCheckerTest, RetrieveSpellcheckLanguages) {
TEST_F(WindowsSpellCheckerTest, GetPerLanguageSuggestions) { // Test retrieval of real dictionary on system (useful for debug logging
// other registered dictionaries).
win_spell_checker_->RetrieveSpellcheckLanguages(base::BindOnce(
&WindowsSpellCheckerTest::RetrieveSpellcheckLanguagesCompletionCallback,
base::Unretained(this)));
RunUntilResultReceived();
if (!spellcheck::WindowsVersionSupportsSpellchecker()) { if (!spellcheck::WindowsVersionSupportsSpellchecker()) {
// On Windows versions that don't support platform spellchecking, the
// returned vector of results should be empty.
ASSERT_TRUE(spellcheck_languages_.empty());
return; return;
} }
ASSERT_TRUE(set_language_result_); ASSERT_LE(1u, spellcheck_languages_.size());
ASSERT_TRUE(base::Contains(spellcheck_languages_, "en-US"));
}
TEST_F(WindowsSpellCheckerTest, RetrieveSpellcheckLanguagesFakeDictionaries) {
// Test retrieval of fake dictionaries added using
// AddSpellcheckLanguagesForTesting. If fake dictionaries are used,
// instantiation of the spellchecker factory is not required for
// RetrieveSpellcheckLanguages, so the test should pass even on Windows
// versions that don't support platform spellchecking.
std::vector<std::string> spellcheck_languages_for_testing = {
"ar-SA", "es-419", "fr-CA"};
win_spell_checker_->AddSpellcheckLanguagesForTesting(
spellcheck_languages_for_testing);
DVLOG(2) << "Calling RetrieveSpellcheckLanguages after fake dictionaries "
"added using AddSpellcheckLanguagesForTesting...";
win_spell_checker_->RetrieveSpellcheckLanguages(base::BindOnce(
&WindowsSpellCheckerTest::RetrieveSpellcheckLanguagesCompletionCallback,
base::Unretained(this)));
RunUntilResultReceived();
ASSERT_EQ(spellcheck_languages_for_testing, spellcheck_languages_);
}
#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
TEST_F(WindowsSpellCheckerTest, GetPerLanguageSuggestions) {
ASSERT_EQ(set_language_result_,
spellcheck::WindowsVersionSupportsSpellchecker());
win_spell_checker_->GetPerLanguageSuggestions( win_spell_checker_->GetPerLanguageSuggestions(
base::ASCIIToUTF16("tihs"), base::ASCIIToUTF16("tihs"),
...@@ -164,6 +229,13 @@ TEST_F(WindowsSpellCheckerTest, GetPerLanguageSuggestions) { ...@@ -164,6 +229,13 @@ TEST_F(WindowsSpellCheckerTest, GetPerLanguageSuggestions) {
base::Unretained(this))); base::Unretained(this)));
RunUntilResultReceived(); RunUntilResultReceived();
if (!spellcheck::WindowsVersionSupportsSpellchecker()) {
// On Windows versions that don't support platform spellchecking, the
// returned vector of results should be empty.
ASSERT_TRUE(per_language_suggestions_.empty());
return;
}
ASSERT_EQ(per_language_suggestions_.size(), 1u); ASSERT_EQ(per_language_suggestions_.size(), 1u);
ASSERT_GT(per_language_suggestions_[0].size(), 0u); ASSERT_GT(per_language_suggestions_[0].size(), 0u);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment