summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/spellchecker
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-12 14:27:29 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-13 09:35:20 +0000
commitc30a6232df03e1efbd9f3b226777b07e087a1122 (patch)
treee992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/chrome/browser/spellchecker
parent7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff)
downloadqtwebengine-chromium-85-based.tar.gz
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/chrome/browser/spellchecker')
-rw-r--r--chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc4
-rw-r--r--chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.h4
-rw-r--r--chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc9
-rw-r--r--chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc6
-rw-r--r--chromium/chrome/browser/spellchecker/spellcheck_factory.cc7
-rw-r--r--chromium/chrome/browser/spellchecker/spellcheck_factory.h7
-rw-r--r--chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc82
-rw-r--r--chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h20
-rw-r--r--chromium/chrome/browser/spellchecker/spellcheck_language_policy_handlers_unittest.cc16
-rw-r--r--chromium/chrome/browser/spellchecker/spellcheck_service.cc352
-rw-r--r--chromium/chrome/browser/spellchecker/spellcheck_service.h98
-rw-r--r--chromium/chrome/browser/spellchecker/spellcheck_service_browsertest.cc175
-rw-r--r--chromium/chrome/browser/spellchecker/spellcheck_service_unittest.cc459
-rw-r--r--chromium/chrome/browser/spellchecker/spelling_request.cc5
14 files changed, 1078 insertions, 166 deletions
diff --git a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
index bce592b0662..aa5b7ab1095 100644
--- a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
+++ b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
@@ -194,7 +194,7 @@ void SpellCheckHostChromeImpl::RequestTextCheck(
base::Unretained(this))));
}
-#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+#if defined(OS_WIN)
void SpellCheckHostChromeImpl::GetPerLanguageSuggestions(
const base::string16& word,
GetPerLanguageSuggestionsCallback callback) {
@@ -208,7 +208,7 @@ void SpellCheckHostChromeImpl::GetPerLanguageSuggestions(
spellcheck_platform::GetPerLanguageSuggestions(
spellcheck->platform_spell_checker(), word, std::move(callback));
}
-#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+#endif // defined(OS_WIN)
void SpellCheckHostChromeImpl::OnRequestFinished(SpellingRequest* request) {
auto iterator = requests_.find(request);
diff --git a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.h b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.h
index 984b852311b..17bb3d80a82 100644
--- a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.h
+++ b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.h
@@ -79,11 +79,11 @@ class SpellCheckHostChromeImpl : public SpellCheckHostImpl {
int route_id,
RequestTextCheckCallback callback) override;
-#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+#if defined(OS_WIN)
void GetPerLanguageSuggestions(
const base::string16& word,
GetPerLanguageSuggestionsCallback callback) override;
-#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+#endif // defined(OS_WIN)
// Clears a finished request from |requests_|. Exposed to SpellingRequest.
void OnRequestFinished(SpellingRequest* request);
diff --git a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc
index d838e7028bc..6c8810f7135 100644
--- a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc
+++ b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc
@@ -28,14 +28,7 @@ class PlatformSpellChecker;
class SpellCheckHostChromeImplWinBrowserTest : public InProcessBrowserTest {
public:
SpellCheckHostChromeImplWinBrowserTest() {
-#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
- feature_list_.InitWithFeatures(
- /*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker,
- spellcheck::kWinUseHybridSpellChecker},
- /*disabled_features=*/{});
-#else
feature_list_.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker);
-#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
}
void SetUpOnMainThread() override {
@@ -123,7 +116,6 @@ IN_PROC_BROWSER_TEST_F(SpellCheckHostChromeImplWinBrowserTest,
EXPECT_EQ(result_[0].decoration, SpellCheckResult::SPELLING);
}
-#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
IN_PROC_BROWSER_TEST_F(SpellCheckHostChromeImplWinBrowserTest,
GetPerLanguageSuggestions) {
if (!spellcheck::WindowsVersionSupportsSpellchecker()) {
@@ -148,4 +140,3 @@ IN_PROC_BROWSER_TEST_F(SpellCheckHostChromeImplWinBrowserTest,
ASSERT_EQ(1U, suggestion_result_.size());
EXPECT_GT(suggestion_result_[0].size(), 0U);
}
-#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
diff --git a/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc b/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc
index cdda080ce7f..582654c85e6 100644
--- a/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc
+++ b/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc
@@ -453,10 +453,8 @@ void SpellcheckCustomDictionary::OnLoaded(
fix_invalid_file_.Reset(
base::BindOnce(&SpellcheckCustomDictionary::FixInvalidFile,
weak_ptr_factory_.GetWeakPtr(), std::move(result)));
- base::PostTask(
- FROM_HERE,
- {content::BrowserThread::UI, base::TaskPriority::BEST_EFFORT},
- fix_invalid_file_.callback());
+ content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
+ ->PostTask(FROM_HERE, fix_invalid_file_.callback());
}
}
diff --git a/chromium/chrome/browser/spellchecker/spellcheck_factory.cc b/chromium/chrome/browser/spellchecker/spellcheck_factory.cc
index 5253d1f48e1..229f29e47b3 100644
--- a/chromium/chrome/browser/spellchecker/spellcheck_factory.cc
+++ b/chromium/chrome/browser/spellchecker/spellcheck_factory.cc
@@ -43,13 +43,6 @@ KeyedService* SpellcheckServiceFactory::BuildServiceInstanceFor(
// Many variables are initialized from the |context| in the SpellcheckService.
SpellcheckService* spellcheck = new SpellcheckService(context);
- PrefService* prefs = user_prefs::UserPrefs::Get(context);
- DCHECK(prefs);
-
- // Instantiates Metrics object for spellchecking for use.
- spellcheck->StartRecordingMetrics(
- prefs->GetBoolean(spellcheck::prefs::kSpellCheckEnable));
-
return spellcheck;
}
diff --git a/chromium/chrome/browser/spellchecker/spellcheck_factory.h b/chromium/chrome/browser/spellchecker/spellcheck_factory.h
index 7adf9db4f84..66e05d429d7 100644
--- a/chromium/chrome/browser/spellchecker/spellcheck_factory.h
+++ b/chromium/chrome/browser/spellchecker/spellcheck_factory.h
@@ -8,6 +8,7 @@
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/singleton.h"
+#include "build/build_config.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
class SpellcheckService;
@@ -39,6 +40,12 @@ class SpellcheckServiceFactory : public BrowserContextKeyedServiceFactory {
bool ServiceIsNULLWhileTesting() const override;
FRIEND_TEST_ALL_PREFIXES(SpellcheckServiceBrowserTest, DeleteCorruptedBDICT);
+#if defined(OS_WIN)
+ FRIEND_TEST_ALL_PREFIXES(SpellcheckServiceWindowsHybridBrowserTest,
+ WindowsHybridSpellcheck);
+ FRIEND_TEST_ALL_PREFIXES(SpellcheckServiceWindowsHybridBrowserTestDelayInit,
+ WindowsHybridSpellcheckDelayInit);
+#endif // defined(OS_WIN)
DISALLOW_COPY_AND_ASSIGN(SpellcheckServiceFactory);
};
diff --git a/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc b/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc
index 25feeebf536..5272b99db1d 100644
--- a/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc
+++ b/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc
@@ -90,48 +90,54 @@ bool SaveDictionaryData(std::unique_ptr<std::string> data,
} // namespace
-SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile() {
-}
+SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile(
+ base::TaskRunner* task_runner) : task_runner_(task_runner) {}
SpellcheckHunspellDictionary::DictionaryFile::~DictionaryFile() {
+ if (file.IsValid()) {
+ task_runner_->PostTask(FROM_HERE,
+ base::BindOnce(&CloseDictionary, std::move(file)));
+ }
}
SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile(
DictionaryFile&& other)
- : path(other.path), file(std::move(other.file)) {}
+ : path(other.path),
+ file(std::move(other.file)),
+ task_runner_(std::move(other.task_runner_)) {}
SpellcheckHunspellDictionary::DictionaryFile&
SpellcheckHunspellDictionary::DictionaryFile::operator=(
DictionaryFile&& other) {
path = other.path;
file = std::move(other.file);
+ task_runner_ = std::move(other.task_runner_);
return *this;
}
SpellcheckHunspellDictionary::SpellcheckHunspellDictionary(
const std::string& language,
+ const std::string& platform_spellcheck_language,
content::BrowserContext* browser_context,
SpellcheckService* spellcheck_service)
: task_runner_(
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})),
language_(language),
+ platform_spellcheck_language_(platform_spellcheck_language),
use_browser_spellchecker_(false),
browser_context_(browser_context),
spellcheck_service_(spellcheck_service),
- download_status_(DOWNLOAD_NONE) {}
+ download_status_(DOWNLOAD_NONE),
+ dictionary_file_(task_runner_.get()) {}
SpellcheckHunspellDictionary::~SpellcheckHunspellDictionary() {
- if (dictionary_file_.file.IsValid()) {
- task_runner_->PostTask(
- FROM_HERE,
- base::BindOnce(&CloseDictionary, std::move(dictionary_file_.file)));
- }
-
#if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
// Disable the language from platform spellchecker.
- if (spellcheck::UseBrowserSpellChecker())
+ if (spellcheck::UseBrowserSpellChecker() && HasPlatformSupport()) {
spellcheck_platform::DisableLanguage(
- spellcheck_service_->platform_spell_checker(), language_);
+ spellcheck_service_->platform_spell_checker(),
+ GetPlatformSpellcheckLanguage());
+ }
#endif // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
}
@@ -140,9 +146,10 @@ void SpellcheckHunspellDictionary::Load() {
#if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
if (spellcheck::UseBrowserSpellChecker() &&
- spellcheck_platform::SpellCheckerAvailable()) {
+ spellcheck_platform::SpellCheckerAvailable() && HasPlatformSupport()) {
spellcheck_platform::PlatformSupportsLanguage(
- spellcheck_service_->platform_spell_checker(), language_,
+ spellcheck_service_->platform_spell_checker(),
+ GetPlatformSpellcheckLanguage(),
base::BindOnce(
&SpellcheckHunspellDictionary::PlatformSupportsLanguageComplete,
weak_ptr_factory_.GetWeakPtr()));
@@ -177,6 +184,21 @@ const std::string& SpellcheckHunspellDictionary::GetLanguage() const {
return language_;
}
+const std::string& SpellcheckHunspellDictionary::GetPlatformSpellcheckLanguage()
+ const {
+#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+ // Currently the platform spellcheck language is only distinguished for
+ // Windows.
+ return platform_spellcheck_language_;
+#else
+ return language_;
+#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+}
+
+bool SpellcheckHunspellDictionary::HasPlatformSupport() const {
+ return !GetPlatformSpellcheckLanguage().empty();
+}
+
bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const {
return use_browser_spellchecker_;
}
@@ -308,13 +330,14 @@ void SpellcheckHunspellDictionary::DownloadDictionary(GURL url) {
base::Unretained(this)));
// Attempt downloading the dictionary only once.
- browser_context_ = NULL;
+ browser_context_ = nullptr;
}
#if !defined(OS_ANDROID)
// static
SpellcheckHunspellDictionary::DictionaryFile
-SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) {
+SpellcheckHunspellDictionary::OpenDictionaryFile(base::TaskRunner* task_runner,
+ const base::FilePath& path) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
@@ -325,7 +348,7 @@ SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) {
// For systemwide installations on Windows, the default directory may not
// have permissions for download. In that case, the alternate directory for
// download is chrome::DIR_USER_DATA.
- DictionaryFile dictionary;
+ DictionaryFile dictionary(task_runner);
#if defined(OS_WIN)
// Check if the dictionary exists in the fallback location. If so, use it
@@ -367,7 +390,7 @@ SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) {
// static
SpellcheckHunspellDictionary::DictionaryFile
SpellcheckHunspellDictionary::InitializeDictionaryLocation(
- const std::string& language) {
+ base::TaskRunner* task_runner, const std::string& language) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
@@ -382,7 +405,7 @@ SpellcheckHunspellDictionary::InitializeDictionaryLocation(
base::FilePath dict_path =
spellcheck::GetVersionedFileName(language, dict_dir);
- return OpenDictionaryFile(dict_path);
+ return OpenDictionaryFile(task_runner, dict_path);
}
void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete(
@@ -396,7 +419,7 @@ void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete(
// TODO(rouslan): Remove this test-only case.
if (spellcheck_service_->SignalStatusEvent(
SpellcheckService::BDICT_CORRUPTED)) {
- browser_context_ = NULL;
+ browser_context_ = nullptr;
}
if (browser_context_) {
@@ -443,9 +466,10 @@ void SpellcheckHunspellDictionary::PlatformSupportsLanguageComplete(
if (platform_supports_language) {
#if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- if (spellcheck::UseBrowserSpellChecker()) {
+ if (spellcheck::UseBrowserSpellChecker() && HasPlatformSupport()) {
spellcheck_platform::SetLanguage(
- spellcheck_service_->platform_spell_checker(), language_,
+ spellcheck_service_->platform_spell_checker(),
+ GetPlatformSpellcheckLanguage(),
base::BindOnce(&SpellcheckHunspellDictionary::
SpellCheckPlatformSetLanguageComplete,
weak_ptr_factory_.GetWeakPtr()));
@@ -454,24 +478,14 @@ void SpellcheckHunspellDictionary::PlatformSupportsLanguageComplete(
#endif // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
NOTREACHED();
} else {
-#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- // The platform spellchecker doesn't support this language. Fall back to
- // Hunspell, unless the hybrid spellchecker is disabled.
- if (spellcheck::UseBrowserSpellChecker() &&
- !spellcheck::UseWinHybridSpellChecker()) {
- // Can't fall back to Hunspell, because the hybrid spellchecker is not
- // enabled. We can't spellcheck this language, so there's no further
- // processing to do.
- return;
- }
-#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
// Either the platform spellchecker is unavailable / disabled, or it doesn't
// support this language. In either case, we must use Hunspell for this
// language, unless we are on Android, which doesn't support Hunspell.
#if !defined(OS_ANDROID) && BUILDFLAG(USE_RENDERER_SPELLCHECKER)
base::PostTaskAndReplyWithResult(
task_runner_.get(), FROM_HERE,
- base::BindOnce(&InitializeDictionaryLocation, language_),
+ base::BindOnce(&InitializeDictionaryLocation,
+ base::RetainedRef(task_runner_.get()), language_),
base::BindOnce(
&SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete,
weak_ptr_factory_.GetWeakPtr()));
diff --git a/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h b/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h
index 4af3201238d..a8acd00dc44 100644
--- a/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h
+++ b/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h
@@ -53,6 +53,7 @@ class SpellcheckHunspellDictionary
};
SpellcheckHunspellDictionary(const std::string& language,
+ const std::string& platform_spellcheck_language,
content::BrowserContext* browser_context,
SpellcheckService* spellcheck_service);
~SpellcheckHunspellDictionary() override;
@@ -68,6 +69,8 @@ class SpellcheckHunspellDictionary
const base::File& GetDictionaryFile() const;
const std::string& GetLanguage() const;
+ const std::string& GetPlatformSpellcheckLanguage() const;
+ bool HasPlatformSupport() const;
bool IsUsingPlatformChecker() const;
// Add an observer for Hunspell dictionary events.
@@ -97,7 +100,7 @@ class SpellcheckHunspellDictionary
// blocking sequence.
struct DictionaryFile {
public:
- DictionaryFile();
+ explicit DictionaryFile(base::TaskRunner* task_runner);
~DictionaryFile();
DictionaryFile(DictionaryFile&& other);
@@ -110,6 +113,9 @@ class SpellcheckHunspellDictionary
base::File file;
private:
+ // Task runner where the file is created.
+ scoped_refptr<base::TaskRunner> task_runner_;
+
DISALLOW_COPY_AND_ASSIGN(DictionaryFile);
};
@@ -124,11 +130,12 @@ class SpellcheckHunspellDictionary
#if !defined(OS_ANDROID)
// Figures out the location for the dictionary, verifies its contents, and
// opens it.
- static DictionaryFile OpenDictionaryFile(const base::FilePath& path);
+ static DictionaryFile OpenDictionaryFile(base::TaskRunner* task_runner,
+ const base::FilePath& path);
// Gets the default location for the dictionary file.
static DictionaryFile InitializeDictionaryLocation(
- const std::string& language);
+ base::TaskRunner* task_runner, const std::string& language);
// The reply point for PostTaskAndReplyWithResult, called after the dictionary
// file has been initialized.
@@ -156,9 +163,14 @@ class SpellcheckHunspellDictionary
// Task runner where the file operations takes place.
scoped_refptr<base::SequencedTaskRunner> const task_runner_;
- // The language of the dictionary file.
+ // The language of the dictionary file (passed when loading Hunspell
+ // dictionaries).
const std::string language_;
+ // The spellcheck language passed to platform APIs may differ from the accept
+ // language (can be empty, indicating to use accept language and Hunspell).
+ const std::string platform_spellcheck_language_;
+
// Whether to use the platform spellchecker instead of Hunspell.
bool use_browser_spellchecker_;
diff --git a/chromium/chrome/browser/spellchecker/spellcheck_language_policy_handlers_unittest.cc b/chromium/chrome/browser/spellchecker/spellcheck_language_policy_handlers_unittest.cc
index fd5ff6c2907..4dcf37eeeca 100644
--- a/chromium/chrome/browser/spellchecker/spellcheck_language_policy_handlers_unittest.cc
+++ b/chromium/chrome/browser/spellchecker/spellcheck_language_policy_handlers_unittest.cc
@@ -17,6 +17,7 @@
#include "base/strings/string_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
+#include "build/build_config.h"
#include "chrome/common/pref_names.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/policy_constants.h"
@@ -102,22 +103,19 @@ class SpellcheckLanguagePolicyHandlersTest
};
TEST_P(SpellcheckLanguagePolicyHandlersTest, ApplyPolicySettings) {
-#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
base::test::ScopedFeatureList feature_list;
if (GetParam().windows_spellchecker_enabled) {
if (!spellcheck::WindowsVersionSupportsSpellchecker())
return;
- // Force hybrid spellchecking to be enabled.
- feature_list.InitWithFeatures(
- /*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker,
- spellcheck::kWinUseHybridSpellChecker},
- /*disabled_features=*/{});
+ // Force Windows native spellchecking to be enabled.
+ feature_list.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker);
} else {
// Hunspell-only spellcheck languages will be used.
feature_list.InitAndDisableFeature(spellcheck::kWinUseBrowserSpellChecker);
}
-#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
PrefValueMap prefs;
policy::PolicyMap policy;
@@ -169,7 +167,7 @@ INSTANTIATE_TEST_SUITE_P(
TestCases,
SpellcheckLanguagePolicyHandlersTest,
testing::Values(
-#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
// Test cases for Windows spellchecker (policy languages not restricted
// to Hunspell).
TestCase({"ar-SA", "es-MX", "fi", "fr",
@@ -185,7 +183,7 @@ INSTANTIATE_TEST_SUITE_P(
{""} /* expected forced languages */,
false /* spellcheck enabled */,
true /* windows spellchecker enabled */),
-#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
// Test cases for Hunspell only spellchecker. ar-SA and fi are
// non-Hunspell languages so are ignored for policy enforcement. If they
// ever obtain Hunspell support, the first test case below will fail.
diff --git a/chromium/chrome/browser/spellchecker/spellcheck_service.cc b/chromium/chrome/browser/spellchecker/spellcheck_service.cc
index af11ea50738..69a1dd21df9 100644
--- a/chromium/chrome/browser/spellchecker/spellcheck_service.cc
+++ b/chromium/chrome/browser/spellchecker/spellcheck_service.cc
@@ -18,11 +18,13 @@
#include "base/synchronization/waitable_event.h"
#include "base/values.h"
#include "build/build_config.h"
+#include "chrome/browser/first_run/first_run.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"
@@ -71,15 +73,6 @@ SpellcheckService::SpellcheckService(content::BrowserContext* context)
: context_(context) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
-#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- // The Windows spell checker must be created before the dictionaries are
- // initialized.
- if (spellcheck::WindowsVersionSupportsSpellchecker()) {
- platform_spell_checker_ = std::make_unique<WindowsSpellChecker>(
- base::ThreadPool::CreateCOMSTATaskRunner({base::MayBlock()}));
- }
-#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
-
PrefService* prefs = user_prefs::UserPrefs::Get(context);
pref_change_registrar_.Init(prefs);
StringListPrefMember dictionaries_pref;
@@ -114,6 +107,21 @@ SpellcheckService::SpellcheckService(content::BrowserContext* context)
single_dictionary_pref.SetValue("");
#endif // defined(OS_MACOSX) || defined(OS_ANDROID)
+#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+ if (!spellcheck::UseBrowserSpellChecker()) {
+ // A user may have disabled the Windows spellcheck feature after adding
+ // non-Hunspell supported languages on the language settings page. Remove
+ // preferences for non-Hunspell languages 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.
+ ListPrefUpdate update(prefs, spellcheck::prefs::kSpellCheckDictionaries);
+ update->EraseListValueIf([](const base::Value& entry) {
+ return spellcheck::GetCorrespondingSpellCheckLanguage(entry.GetString())
+ .empty();
+ });
+ }
+#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+
pref_change_registrar_.Add(
spellcheck::prefs::kSpellCheckDictionaries,
base::BindRepeating(&SpellcheckService::OnSpellCheckDictionariesChanged,
@@ -143,11 +151,21 @@ SpellcheckService::SpellcheckService(content::BrowserContext* context)
custom_dictionary_->AddObserver(this);
custom_dictionary_->Load();
- registrar_.Add(this,
- content::NOTIFICATION_RENDERER_PROCESS_CREATED,
+ registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
content::NotificationService::AllSources());
- LoadHunspellDictionaries();
+#if defined(OS_WIN)
+ if (spellcheck::UseBrowserSpellChecker() &&
+ base::FeatureList::IsEnabled(
+ spellcheck::kWinDelaySpellcheckServiceInit)) {
+ // If initialization of the spellcheck service is on-demand, it is up to the
+ // instantiator of the spellcheck service to call InitializeDictionaries
+ // with a callback.
+ return;
+ }
+#endif // defined(OS_WIN)
+
+ InitializeDictionaries(base::DoNothing());
}
SpellcheckService::~SpellcheckService() {
@@ -161,8 +179,9 @@ base::WeakPtr<SpellcheckService> SpellcheckService::GetWeakPtr() {
#if !defined(OS_MACOSX)
// static
-void SpellcheckService::GetDictionaries(base::SupportsUserData* browser_context,
- std::vector<Dictionary>* dictionaries) {
+void SpellcheckService::GetDictionaries(
+ content::BrowserContext* browser_context,
+ std::vector<Dictionary>* dictionaries) {
PrefService* prefs = user_prefs::UserPrefs::Get(browser_context);
std::set<std::string> spellcheck_dictionaries;
for (const auto& value :
@@ -178,8 +197,23 @@ void SpellcheckService::GetDictionaries(base::SupportsUserData* browser_context,
",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
for (const auto& accept_language : accept_languages) {
Dictionary dictionary;
+#if defined(OS_WIN)
+ if (spellcheck::UseBrowserSpellChecker()) {
+ SpellcheckService* spellcheck =
+ SpellcheckServiceFactory::GetForContext(browser_context);
+ if (spellcheck && spellcheck->UsesWindowsDictionary(accept_language))
+ dictionary.language = accept_language;
+ }
+
+ if (dictionary.language.empty()) {
+ dictionary.language =
+ spellcheck::GetCorrespondingSpellCheckLanguage(accept_language);
+ }
+#else
dictionary.language =
spellcheck::GetCorrespondingSpellCheckLanguage(accept_language);
+#endif // defined(OS_WIN)
+
if (dictionary.language.empty())
continue;
@@ -212,9 +246,13 @@ std::string SpellcheckService::GetSupportedAcceptLanguageCode(
supported_language_full_tag);
#if defined(OS_WIN)
- if (!spellcheck::UseWinHybridSpellChecker())
+ if (!spellcheck::UseBrowserSpellChecker())
return supported_accept_language;
+ // Exclude dictionaries that are for private use, such as "ja-Latn-JP-x-ext".
+ if (SpellcheckService::HasPrivateUseSubTag(supported_language_full_tag))
+ return "";
+
// Collect the hardcoded list of accept-languages supported by the browser,
// that is, languages that can be added as preferred languages in the
// languages settings page.
@@ -272,8 +310,18 @@ std::string SpellcheckService::GetSupportedAcceptLanguageCode(
/* include_script_tag */ false));
});
- if (iter != accept_languages.end())
+ 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;
+ }
#endif // defined(OS_WIN)
@@ -328,7 +376,7 @@ SpellcheckCustomDictionary* SpellcheckService::GetCustomDictionary() {
return custom_dictionary_.get();
}
-void SpellcheckService::LoadHunspellDictionaries() {
+void SpellcheckService::LoadDictionaries() {
hunspell_dictionaries_.clear();
PrefService* prefs = user_prefs::UserPrefs::Get(context_);
@@ -359,16 +407,50 @@ void SpellcheckService::LoadHunspellDictionaries() {
}
for (const auto& dictionary : dictionaries) {
+ // The spellcheck language passed to platform APIs may differ from the
+ // accept language.
+ std::string platform_spellcheck_language;
+#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+ if (spellcheck::UseBrowserSpellChecker()) {
+ std::string windows_dictionary_name =
+ GetSupportedWindowsDictionaryLanguage(dictionary);
+ if (!windows_dictionary_name.empty()) {
+ platform_spellcheck_language =
+ SpellcheckService::GetTagToPassToWindowsSpellchecker(
+ dictionary, windows_dictionary_name);
+ }
+ }
+#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+
hunspell_dictionaries_.push_back(
- std::make_unique<SpellcheckHunspellDictionary>(dictionary, context_,
- this));
+ std::make_unique<SpellcheckHunspellDictionary>(
+ dictionary, platform_spellcheck_language, context_, this));
hunspell_dictionaries_.back()->AddObserver(this);
hunspell_dictionaries_.back()->Load();
}
#if defined(OS_WIN)
RecordSpellcheckLocalesStats();
+
+#if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+ if (base::FeatureList::IsEnabled(
+ spellcheck::kWinDelaySpellcheckServiceInit) &&
+ spellcheck::UseBrowserSpellChecker()) {
+ // Only want to fire the callback on first call to LoadDictionaries
+ // originating from InitializeDictionaries, since supported platform
+ // dictionaries are cached throughout the browser session and not
+ // dynamically updated. LoadDictionaries can be called multiple times in a
+ // browser session, even before InitializeDictionaries is called, e.g. when
+ // language settings are changed.
+ if (!dictionaries_loaded() && dictionaries_loaded_callback_) {
+ dictionaries_loaded_ = true;
+ std::move(dictionaries_loaded_callback_).Run();
+ }
+ return;
+ }
+#endif // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
#endif // defined(OS_WIN)
+ dictionaries_loaded_ = true;
}
const std::vector<std::unique_ptr<SpellcheckHunspellDictionary>>&
@@ -435,6 +517,134 @@ void SpellcheckService::OnHunspellDictionaryDownloadFailure(
const std::string& language) {
}
+void SpellcheckService::InitializeDictionaries(base::OnceClosure done) {
+ // The dictionaries only need to be initialized once.
+ if (dictionaries_loaded()) {
+ std::move(done).Run();
+ return;
+ }
+
+#if defined(OS_WIN)
+ dictionaries_loaded_callback_ = std::move(done);
+ // Need to initialize the platform spellchecker in order to record platform
+ // locale stats even if the platform spellcheck feature is disabled.
+ InitializePlatformSpellchecker();
+#endif // defined(OS_WIN)
+
+ PrefService* prefs = user_prefs::UserPrefs::Get(context_);
+ DCHECK(prefs);
+
+ // Instantiates Metrics object for spellchecking to use.
+ StartRecordingMetrics(
+ prefs->GetBoolean(spellcheck::prefs::kSpellCheckEnable));
+
+#if defined(OS_WIN)
+ if (spellcheck::UseBrowserSpellChecker() && platform_spell_checker()) {
+ spellcheck_platform::RetrieveSpellcheckLanguages(
+ platform_spell_checker(),
+ base::BindOnce(&SpellcheckService::InitWindowsDictionaryLanguages,
+ GetWeakPtr()));
+ return;
+ }
+#endif // defined(OS_WIN)
+
+ // Using Hunspell.
+ LoadDictionaries();
+}
+
+#if defined(OS_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);
+ 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);
+ }
+ }
+
+ // 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.
+ 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).
+ ListPrefUpdate update(prefs, spellcheck::prefs::kSpellCheckDictionaries);
+ update->EraseListValueIf([this](const base::Value& entry) {
+ const std::string dictionary_name = entry.GetString();
+ return (!UsesWindowsDictionary(dictionary_name) &&
+ spellcheck::GetCorrespondingSpellCheckLanguage(dictionary_name)
+ .empty());
+ });
+
+ if (first_run::IsChromeFirstRun()) {
+ // Ensure that spellcheck is enabled for the first dialect of the
+ // accepted languages if there is a Windows dictionary installed for
+ // that dialect.
+ base::Value 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 Finnnish 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.GetList()) {
+ first_user_language_spellchecked =
+ base::Contains(dictionary_value.GetString(), first_user_language);
+ if (first_user_language_spellchecked)
+ break;
+ }
+
+ if (!first_user_language_spellchecked &&
+ UsesWindowsDictionary(first_user_language)) {
+ user_dictionaries.Insert(user_dictionaries.GetList().begin(),
+ base::Value(first_user_language));
+ prefs->Set(spellcheck::prefs::kSpellCheckDictionaries, user_dictionaries);
+ }
+ }
+
+ // No need to call LoadDictionaries() as when the ListPrefUpdate
+ // 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 // defined(OS_WIN)
+
// static
void SpellcheckService::OverrideBinderForTesting(SpellCheckerBinder binder) {
GetSpellCheckerBinderOverride() = std::move(binder);
@@ -468,6 +678,50 @@ std::string SpellcheckService::GetLanguageAndScriptTag(
}
// 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");
+}
+
+#if defined(OS_WIN)
+// 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 // defined(OS_WIN)
+
+// static
void SpellcheckService::AttachStatusEvent(base::WaitableEvent* status_event) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -495,7 +749,7 @@ SpellcheckService::GetSpellCheckerForProcess(content::RenderProcessHost* host) {
void SpellcheckService::InitForAllRenderers() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (content::RenderProcessHost::iterator i(
- content::RenderProcessHost::AllHostsIterator());
+ content::RenderProcessHost::AllHostsIterator());
!i.IsAtEnd(); i.Advance()) {
content::RenderProcessHost* process = i.GetCurrentValue();
if (process && process->GetProcess().Handle())
@@ -506,7 +760,7 @@ void SpellcheckService::InitForAllRenderers() {
void SpellcheckService::OnSpellCheckDictionariesChanged() {
// If there are hunspell dictionaries, then fire off notifications to the
// renderers after the dictionaries are finished loading.
- LoadHunspellDictionaries();
+ LoadDictionaries();
// If there are no hunspell dictionaries to load, then immediately let the
// renderers know the new state.
@@ -551,15 +805,35 @@ std::vector<std::string> SpellcheckService::GetNormalizedAcceptLanguages(
",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (normalize_for_spellcheck) {
- std::transform(accept_languages.begin(), accept_languages.end(),
- accept_languages.begin(),
- &spellcheck::GetCorrespondingSpellCheckLanguage);
+ std::transform(
+ accept_languages.begin(), accept_languages.end(),
+ accept_languages.begin(), [&](const std::string& language) {
+#if defined(OS_WIN)
+ if (spellcheck::UseBrowserSpellChecker() &&
+ UsesWindowsDictionary(language))
+ return language;
+#endif // defined(OS_WIN)
+ return spellcheck::GetCorrespondingSpellCheckLanguage(language);
+ });
}
return accept_languages;
}
#if defined(OS_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() &&
+ spellcheck::WindowsVersionSupportsSpellchecker()) {
+ platform_spell_checker_ = std::make_unique<WindowsSpellChecker>(
+ base::ThreadPool::CreateCOMSTATaskRunner({base::MayBlock()}));
+ }
+}
+
void SpellcheckService::RecordSpellcheckLocalesStats() {
if (spellcheck::WindowsVersionSupportsSpellchecker() && metrics_ &&
platform_spell_checker() && !hunspell_dictionaries_.empty()) {
@@ -581,4 +855,34 @@ void SpellcheckService::RecordChromeLocalesStats() {
platform_spell_checker(), std::move(accept_languages), metrics_.get());
}
}
+
+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 // defined(OS_WIN)
diff --git a/chromium/chrome/browser/spellchecker/spellcheck_service.h b/chromium/chrome/browser/spellchecker/spellcheck_service.h
index 97df131375c..4c3d45d9ce6 100644
--- a/chromium/chrome/browser/spellchecker/spellcheck_service.h
+++ b/chromium/chrome/browser/spellchecker/spellcheck_service.h
@@ -7,6 +7,7 @@
#include <stddef.h>
+#include <map>
#include <memory>
#include <string>
#include <vector>
@@ -31,7 +32,6 @@ class SpellCheckHostMetrics;
namespace base {
class WaitableEvent;
-class SupportsUserData;
}
namespace content {
@@ -41,6 +41,12 @@ 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.
@@ -84,7 +90,7 @@ class SpellcheckService : public KeyedService,
// 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(base::SupportsUserData* browser_context,
+ static void GetDictionaries(content::BrowserContext* browser_context,
std::vector<Dictionary>* dictionaries);
#endif // !OS_MACOSX
@@ -116,9 +122,6 @@ class SpellcheckService : public KeyedService,
// Returns the instance of the custom dictionary.
SpellcheckCustomDictionary* GetCustomDictionary();
- // Starts the process of loading the Hunspell dictionaries.
- void LoadHunspellDictionaries();
-
// Returns the instance of the vector of Hunspell dictionaries.
const std::vector<std::unique_ptr<SpellcheckHunspellDictionary>>&
GetHunspellDictionaries();
@@ -152,6 +155,21 @@ class SpellcheckService : public KeyedService,
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.
@@ -159,6 +177,9 @@ class SpellcheckService : public KeyedService,
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(
@@ -167,12 +188,40 @@ class SpellcheckService : public KeyedService,
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);
+ // 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);
+
+#if defined(OS_WIN)
+ // 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);
@@ -207,12 +256,36 @@ class SpellcheckService : public KeyedService,
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
@@ -232,6 +305,21 @@ class SpellcheckService : public KeyedService,
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);
diff --git a/chromium/chrome/browser/spellchecker/spellcheck_service_browsertest.cc b/chromium/chrome/browser/spellchecker/spellcheck_service_browsertest.cc
index ef602ba4d53..bf59a235f1b 100644
--- a/chromium/chrome/browser/spellchecker/spellcheck_service_browsertest.cc
+++ b/chromium/chrome/browser/spellchecker/spellcheck_service_browsertest.cc
@@ -4,6 +4,7 @@
#include "chrome/browser/spellchecker/spellcheck_service.h"
+#include <map>
#include <string>
#include <vector>
@@ -18,6 +19,7 @@
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "build/build_config.h"
@@ -33,6 +35,7 @@
#include "components/spellcheck/browser/pref_names.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/common/spellcheck_result.h"
#include "components/spellcheck/spellcheck_buildflags.h"
#include "components/user_prefs/user_prefs.h"
@@ -44,9 +47,9 @@
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
-#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
#include "components/spellcheck/common/spellcheck_features.h"
-#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
using content::BrowserContext;
using content::RenderProcessHost;
@@ -56,6 +59,19 @@ class SpellcheckServiceBrowserTest : public InProcessBrowserTest,
public:
SpellcheckServiceBrowserTest() = default;
+#if defined(OS_WIN)
+ void SetUp() override {
+ // Tests were designed assuming Hunspell dictionary used and many fail when
+ // Windows spellcheck is enabled by default. The feature flag needs to be
+ // disabled in SetUp() instead of the constructor because the derived class
+ // SpellcheckServiceWindowsHybridBrowserTest overrides the base class
+ // behavior and sets the spellcheck::kWinUseBrowserSpellChecker feature
+ // flag. You can't use ScopedFeatureList to initialize a feature flag twice.
+ feature_list_.InitAndDisableFeature(spellcheck::kWinUseBrowserSpellChecker);
+ InProcessBrowserTest::SetUp();
+ }
+#endif // defined(OS_WIN)
+
void SetUpOnMainThread() override {
renderer_.reset(new content::MockRenderProcessHost(GetContext()));
renderer_->Init();
@@ -104,12 +120,12 @@ class SpellcheckServiceBrowserTest : public InProcessBrowserTest,
SpellcheckService* spellcheck =
SpellcheckServiceFactory::GetForContext(renderer_->GetBrowserContext());
-#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
- if (spellcheck::UseWinHybridSpellChecker()) {
- // If the Windows hybrid spell checker is in use, initialization is async.
+#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+ if (spellcheck::UseBrowserSpellChecker()) {
+ // If the Windows native spell checker is in use, initialization is async.
RunTestRunLoop();
}
-#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
ASSERT_NE(nullptr, spellcheck);
}
@@ -224,6 +240,10 @@ class SpellcheckServiceBrowserTest : public InProcessBrowserTest,
// Quits the RunLoop on Mojo request flow completion.
base::OnceClosure quit_;
+#if defined(OS_WIN)
+ base::test::ScopedFeatureList feature_list_;
+#endif // defined(OS_WIN)
+
private:
// Mocked RenderProcessHost.
std::unique_ptr<content::MockRenderProcessHost> renderer_;
@@ -461,14 +481,14 @@ IN_PROC_BROWSER_TEST_F(SpellcheckServiceHostBrowserTest, CallSpellingService) {
// Tests that we can delete a corrupted BDICT file used by hunspell. We do not
// run this test on Mac because Mac does not use hunspell by default.
IN_PROC_BROWSER_TEST_F(SpellcheckServiceBrowserTest, DeleteCorruptedBDICT) {
-#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
- if (spellcheck::UseWinHybridSpellChecker()) {
- // If doing hybrid spell checking on Windows, Hunspell dictionaries are not
+#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+ if (spellcheck::UseBrowserSpellChecker()) {
+ // If doing native spell checking on Windows, Hunspell dictionaries are not
// used for en-US, so the corrupt dictionary event will never be raised.
// Skip this test.
return;
}
-#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
// Corrupted BDICT data: please do not use this BDICT data for other tests.
const uint8_t kCorruptedBDICT[] = {
@@ -509,13 +529,15 @@ IN_PROC_BROWSER_TEST_F(SpellcheckServiceBrowserTest, DeleteCorruptedBDICT) {
SpellcheckServiceFactory::GetInstance()->GetServiceForBrowserContext(
context,
false));
- ASSERT_EQ(NULL, service);
+ ASSERT_EQ(nullptr, service);
// Getting the spellcheck_service will initialize the SpellcheckService
// object with the corrupted BDICT file created above since the hunspell
// dictionary is loaded in the SpellcheckService constructor right now.
// The SpellCheckHost object will send a BDICT_CORRUPTED event.
- SpellcheckServiceFactory::GetForContext(context);
+ service = SpellcheckServiceFactory::GetForContext(context);
+ ASSERT_NE(nullptr, service);
+ ASSERT_TRUE(service->dictionaries_loaded());
// Check the received event. Also we check if Chrome has successfully deleted
// the corrupted dictionary. We delete the corrupted dictionary to avoid
@@ -613,3 +635,132 @@ IN_PROC_BROWSER_TEST_F(SpellcheckServiceBrowserTest,
->GetString(1, &pref));
EXPECT_EQ("fr", pref);
}
+
+#if defined(OS_WIN)
+class SpellcheckServiceWindowsHybridBrowserTest
+ : public SpellcheckServiceBrowserTest {
+ public:
+ SpellcheckServiceWindowsHybridBrowserTest() = default;
+
+ void SetUp() override {
+ feature_list_.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker);
+ InProcessBrowserTest::SetUp();
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(SpellcheckServiceWindowsHybridBrowserTest,
+ WindowsHybridSpellcheck) {
+ if (!spellcheck::WindowsVersionSupportsSpellchecker())
+ return;
+
+ ASSERT_TRUE(spellcheck::UseBrowserSpellChecker());
+
+ // Note that the base class forces dictionary sync to not be performed, which
+ // on its own would have created a SpellcheckService object. So testing here
+ // that we are still instantiating the SpellcheckService as a browser startup
+ // task to support hybrid spellchecking.
+ SpellcheckService* service = static_cast<SpellcheckService*>(
+ SpellcheckServiceFactory::GetInstance()->GetServiceForBrowserContext(
+ GetContext(), /* create */ false));
+ ASSERT_NE(nullptr, service);
+
+ // The list of Windows spellcheck languages should have been populated by at
+ // least one language. This assures that the spellcheck context menu will
+ // include Windows spellcheck languages that lack Hunspell support.
+ ASSERT_TRUE(service->dictionaries_loaded());
+ ASSERT_FALSE(service->windows_spellcheck_dictionary_map_.empty());
+}
+
+class SpellcheckServiceWindowsHybridBrowserTestDelayInit
+ : public SpellcheckServiceBrowserTest {
+ public:
+ SpellcheckServiceWindowsHybridBrowserTestDelayInit() = default;
+
+ void SetUp() override {
+ // Don't initialize the SpellcheckService on browser launch.
+ feature_list_.InitWithFeatures(
+ /*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker,
+ spellcheck::kWinDelaySpellcheckServiceInit},
+ /*disabled_features=*/{});
+ InProcessBrowserTest::SetUp();
+ }
+
+ void OnDictionariesInitialized() {
+ dictionaries_initialized_received_ = true;
+ if (quit_on_callback_)
+ std::move(quit_on_callback_).Run();
+ }
+
+ protected:
+ void RunUntilCallbackReceived() {
+ if (dictionaries_initialized_received_)
+ return;
+ base::RunLoop run_loop;
+ quit_on_callback_ = run_loop.QuitClosure();
+ run_loop.Run();
+
+ // reset status.
+ dictionaries_initialized_received_ = false;
+ }
+
+ private:
+ bool dictionaries_initialized_received_ = false;
+
+ // Quits the RunLoop on receiving the callback from InitializeDictionaries.
+ base::OnceClosure quit_on_callback_;
+};
+
+IN_PROC_BROWSER_TEST_F(SpellcheckServiceWindowsHybridBrowserTestDelayInit,
+ WindowsHybridSpellcheckDelayInit) {
+ if (!spellcheck::WindowsVersionSupportsSpellchecker())
+ return;
+
+ ASSERT_TRUE(spellcheck::UseBrowserSpellChecker());
+
+ // Note that the base class forces dictionary sync to not be performed, and
+ // the kWinDelaySpellcheckServiceInit flag is set, which together should
+ // prevent creation of a SpellcheckService object on browser startup. So
+ // testing here that this is indeed the case.
+ SpellcheckService* service = static_cast<SpellcheckService*>(
+ SpellcheckServiceFactory::GetInstance()->GetServiceForBrowserContext(
+ GetContext(), /* create */ false));
+ ASSERT_EQ(nullptr, service);
+
+ // Now create the SpellcheckService but don't call InitializeDictionaries().
+ service = static_cast<SpellcheckService*>(
+ SpellcheckServiceFactory::GetInstance()->GetServiceForBrowserContext(
+ GetContext(), /* create */ true));
+
+ ASSERT_NE(nullptr, service);
+
+ // The list of Windows spellcheck languages should not have been populated
+ // yet since InitializeDictionaries() has not been called.
+ ASSERT_FALSE(service->dictionaries_loaded());
+ ASSERT_TRUE(service->windows_spellcheck_dictionary_map_.empty());
+
+ service->InitializeDictionaries(
+ base::BindOnce(&SpellcheckServiceWindowsHybridBrowserTestDelayInit::
+ OnDictionariesInitialized,
+ base::Unretained(this)));
+
+ RunUntilCallbackReceived();
+ ASSERT_TRUE(service->dictionaries_loaded());
+ // The list of Windows spellcheck languages should now have been populated.
+ std::map<std::string, std::string>
+ windows_spellcheck_dictionary_map_first_call =
+ service->windows_spellcheck_dictionary_map_;
+ ASSERT_FALSE(windows_spellcheck_dictionary_map_first_call.empty());
+
+ // It should be safe to call InitializeDictionaries again (it should
+ // immediately run the callback).
+ service->InitializeDictionaries(
+ base::BindOnce(&SpellcheckServiceWindowsHybridBrowserTestDelayInit::
+ OnDictionariesInitialized,
+ base::Unretained(this)));
+
+ RunUntilCallbackReceived();
+ ASSERT_TRUE(service->dictionaries_loaded());
+ ASSERT_EQ(windows_spellcheck_dictionary_map_first_call,
+ service->windows_spellcheck_dictionary_map_);
+}
+#endif // defined(OS_WIN)
diff --git a/chromium/chrome/browser/spellchecker/spellcheck_service_unittest.cc b/chromium/chrome/browser/spellchecker/spellcheck_service_unittest.cc
index 3a4fa901884..c4ecd5f7f14 100644
--- a/chromium/chrome/browser/spellchecker/spellcheck_service_unittest.cc
+++ b/chromium/chrome/browser/spellchecker/spellcheck_service_unittest.cc
@@ -12,41 +12,38 @@
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/supports_user_data.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/first_run/first_run.h"
+#include "chrome/browser/spellchecker/spellcheck_factory.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
+#include "chrome/test/base/testing_profile.h"
#include "components/language/core/browser/pref_names.h"
-#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/spellcheck/browser/pref_names.h"
+#include "components/spellcheck/browser/spellcheck_platform.h"
+#include "components/spellcheck/common/spellcheck_features.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
struct TestCase {
- TestCase(const std::string& accept_languages,
- const std::string& unsplit_spellcheck_dictionaries,
- const std::string& unsplit_expected_languages,
- const std::string& unsplit_expected_languages_used_for_spellcheck)
+ TestCase(
+ const std::string& accept_languages,
+ const std::vector<std::string>& spellcheck_dictionaries,
+ const std::vector<std::string>& expected_languages,
+ const std::vector<std::string>& expected_languages_used_for_spellcheck)
: accept_languages(accept_languages),
- spellcheck_dictionaries(
- base::SplitString(unsplit_spellcheck_dictionaries,
- ",",
- base::TRIM_WHITESPACE,
- base::SPLIT_WANT_ALL)) {
- std::vector<std::string> languages =
- base::SplitString(unsplit_expected_languages, ",",
- base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
-
- std::vector<std::string> used_for_spellcheck =
- base::SplitString(unsplit_expected_languages_used_for_spellcheck, ",",
- base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
-
+ spellcheck_dictionaries(spellcheck_dictionaries) {
SpellcheckService::Dictionary dictionary;
- for (const auto& language : languages) {
- dictionary.language = language;
- dictionary.used_for_spellcheck =
- base::Contains(used_for_spellcheck, language);
- expected_dictionaries.push_back(dictionary);
+ for (const auto& language : expected_languages) {
+ if (!language.empty()) {
+ dictionary.language = language;
+ dictionary.used_for_spellcheck =
+ base::Contains(expected_languages_used_for_spellcheck, language);
+ expected_dictionaries.push_back(dictionary);
+ }
}
}
@@ -82,47 +79,74 @@ std::ostream& operator<<(std::ostream& out, const TestCase& test_case) {
return out;
}
-class SpellcheckServiceUnitTest : public testing::TestWithParam<TestCase> {
+static std::unique_ptr<KeyedService> BuildSpellcheckService(
+ content::BrowserContext* profile) {
+ return std::make_unique<SpellcheckService>(static_cast<Profile*>(profile));
+}
+
+class SpellcheckServiceUnitTestBase : public testing::Test {
public:
- SpellcheckServiceUnitTest() {
- user_prefs::UserPrefs::Set(&context_, &prefs_);
- }
- ~SpellcheckServiceUnitTest() override {}
+ SpellcheckServiceUnitTestBase() = default;
+ ~SpellcheckServiceUnitTestBase() override = default;
+
+ content::BrowserContext* browser_context() { return &profile_; }
+ PrefService* prefs() { return profile_.GetPrefs(); }
+ protected:
void SetUp() override {
- prefs()->registry()->RegisterListPref(
- spellcheck::prefs::kSpellCheckDictionaries);
- prefs()->registry()->RegisterStringPref(language::prefs::kAcceptLanguages,
- std::string());
+#if defined(OS_WIN)
+ // Tests were designed assuming Hunspell dictionary used and may fail when
+ // Windows spellcheck is enabled by default.
+ feature_list_.InitAndDisableFeature(spellcheck::kWinUseBrowserSpellChecker);
+#endif // defined(OS_WIN)
+
+ // Use SetTestingFactoryAndUse to force creation and initialization.
+ SpellcheckServiceFactory::GetInstance()->SetTestingFactoryAndUse(
+ &profile_, base::BindRepeating(&BuildSpellcheckService));
}
- base::SupportsUserData* context() { return &context_; }
- TestingPrefServiceSimple* prefs() { return &prefs_; }
+ content::BrowserTaskEnvironment task_environment_;
+
+#if defined(OS_WIN)
+ // feature_list_ needs to be destroyed after profile_.
+ base::test::ScopedFeatureList feature_list_;
+#endif // defined(OS_WIN)
+ TestingProfile profile_;
private:
- struct : public base::SupportsUserData {
- } context_;
- TestingPrefServiceSimple prefs_;
- content::BrowserTaskEnvironment task_environment_;
+ DISALLOW_COPY_AND_ASSIGN(SpellcheckServiceUnitTestBase);
+};
- DISALLOW_COPY_AND_ASSIGN(SpellcheckServiceUnitTest);
+class SpellcheckServiceUnitTest : public SpellcheckServiceUnitTestBase,
+ public testing::WithParamInterface<TestCase> {
};
INSTANTIATE_TEST_SUITE_P(
TestCases,
SpellcheckServiceUnitTest,
testing::Values(
- TestCase("en,aa", "aa", "", ""),
- TestCase("en,en-JP,fr,aa", "fr", "fr", "fr"),
- TestCase("en,en-JP,fr,zz,en-US", "fr", "fr,en-US", "fr"),
- TestCase("en,en-US,en-GB", "en-GB", "en-US,en-GB", "en-GB"),
- TestCase("en,en-US,en-AU", "en-AU", "en-US,en-AU", "en-AU"),
- TestCase("en,en-US,en-AU", "en-US", "en-US,en-AU", "en-US"),
- TestCase("en,en-US", "en-US", "en-US", "en-US"),
- TestCase("en,en-US,fr", "en-US", "en-US,fr", "en-US"),
- TestCase("en,fr,en-US,en-AU", "en-US,fr", "fr,en-US,en-AU", "fr,en-US"),
- TestCase("en-US,en", "en-US", "en-US", "en-US"),
- TestCase("hu-HU,hr-HR", "hr", "hu,hr", "hr")));
+ TestCase("en,aa", {"aa"}, {""}, {""}),
+ TestCase("en,en-JP,fr,aa", {"fr"}, {"fr"}, {"fr"}),
+ TestCase("en,en-JP,fr,zz,en-US", {"fr"}, {"fr", "en-US"}, {"fr"}),
+ TestCase("en,en-US,en-GB", {"en-GB"}, {"en-US", "en-GB"}, {"en-GB"}),
+ TestCase("en,en-US,en-AU", {"en-AU"}, {"en-US", "en-AU"}, {"en-AU"}),
+ TestCase("en,en-US,en-AU", {"en-US"}, {"en-US", "en-AU"}, {"en-US"}),
+ TestCase("en,en-US", {"en-US"}, {"en-US"}, {"en-US"}),
+ TestCase("en,en-US,fr", {"en-US"}, {"en-US", "fr"}, {"en-US"}),
+ TestCase("en,fr,en-US,en-AU",
+ {"en-US", "fr"},
+ {"fr", "en-US", "en-AU"},
+ {"fr", "en-US"}),
+ TestCase("en-US,en", {"en-US"}, {"en-US"}, {"en-US"}),
+#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+ // Scenario where user disabled the Windows spellcheck feature with some
+ // non-Hunspell languages set in preferences.
+ TestCase("fr,eu,en-US,ar",
+ {"fr", "eu", "en-US", "ar"},
+ {"fr", "en-US"},
+ {"fr", "en-US"}),
+#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+ TestCase("hu-HU,hr-HR", {"hr"}, {"hu", "hr"}, {"hr"})));
TEST_P(SpellcheckServiceUnitTest, GetDictionaries) {
prefs()->SetString(language::prefs::kAcceptLanguages,
@@ -133,7 +157,340 @@ TEST_P(SpellcheckServiceUnitTest, GetDictionaries) {
spellcheck_dictionaries);
std::vector<SpellcheckService::Dictionary> dictionaries;
- SpellcheckService::GetDictionaries(context(), &dictionaries);
+ SpellcheckService::GetDictionaries(browser_context(), &dictionaries);
EXPECT_EQ(GetParam().expected_dictionaries, dictionaries);
}
+
+#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+class SpellcheckServiceHybridUnitTestBase
+ : public SpellcheckServiceUnitTestBase {
+ public:
+ SpellcheckServiceHybridUnitTestBase() = default;
+
+ protected:
+ void SetUp() override {
+ InitFeatures();
+
+ // Add command line switch that forces first run state, since code path
+ // through SpellcheckService::InitWindowsDictionaryLanguages depends on
+ // whether this is first run.
+ first_run::ResetCachedSentinelDataForTesting();
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kForceFirstRun);
+
+ // Use SetTestingFactoryAndUse to force creation and initialization.
+ SpellcheckServiceFactory::GetInstance()->SetTestingFactoryAndUse(
+ &profile_, base::BindRepeating(&BuildSpellcheckService));
+ }
+
+ virtual void InitFeatures() {
+ feature_list_.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker);
+ }
+
+ virtual void InitializeSpellcheckService(
+ const std::vector<std::string>& spellcheck_languages_for_testing) {
+ // Fake the presence of Windows spellcheck dictionaries.
+ spellcheck_service_ =
+ SpellcheckServiceFactory::GetInstance()->GetForContext(
+ browser_context());
+
+ spellcheck_service_->InitWindowsDictionaryLanguages(
+ spellcheck_languages_for_testing);
+
+ ASSERT_TRUE(spellcheck_service_->dictionaries_loaded());
+ }
+
+ void RunGetDictionariesTest(
+ const std::string accept_languages,
+ const std::vector<std::string> spellcheck_dictionaries,
+ const std::vector<SpellcheckService::Dictionary> expected_dictionaries);
+
+ void RunDictionaryMappingTest(
+ const std::string full_tag,
+ const std::string expected_accept_language,
+ const std::string expected_tag_passed_to_spellcheck);
+
+ // Used for faking the presence of Windows spellcheck dictionaries.
+ static const std::vector<std::string>
+ windows_spellcheck_languages_for_testing_;
+
+ SpellcheckService* spellcheck_service_;
+};
+
+void SpellcheckServiceHybridUnitTestBase::RunGetDictionariesTest(
+ const std::string accept_languages,
+ const std::vector<std::string> spellcheck_dictionaries,
+ const std::vector<SpellcheckService::Dictionary> expected_dictionaries) {
+ if (!spellcheck::WindowsVersionSupportsSpellchecker())
+ return;
+
+ prefs()->SetString(language::prefs::kAcceptLanguages, accept_languages);
+ base::ListValue spellcheck_dictionaries_list;
+ spellcheck_dictionaries_list.AppendStrings(spellcheck_dictionaries);
+ prefs()->Set(spellcheck::prefs::kSpellCheckDictionaries,
+ spellcheck_dictionaries_list);
+
+ InitializeSpellcheckService(windows_spellcheck_languages_for_testing_);
+
+ std::vector<SpellcheckService::Dictionary> dictionaries;
+ SpellcheckService::GetDictionaries(browser_context(), &dictionaries);
+
+ EXPECT_EQ(expected_dictionaries, dictionaries);
+}
+
+void SpellcheckServiceHybridUnitTestBase::RunDictionaryMappingTest(
+ const std::string full_tag,
+ const std::string expected_accept_language,
+ const std::string expected_tag_passed_to_spellcheck) {
+ if (!spellcheck::WindowsVersionSupportsSpellchecker())
+ return;
+
+ InitializeSpellcheckService({full_tag});
+
+ std::string supported_accept_language =
+ SpellcheckService::GetSupportedAcceptLanguageCode(full_tag);
+
+ EXPECT_EQ(expected_accept_language, supported_accept_language);
+
+ if (!supported_accept_language.empty()) {
+ EXPECT_EQ(full_tag,
+ spellcheck_service_->GetSupportedWindowsDictionaryLanguage(
+ expected_accept_language));
+ } else {
+ // Unsupported language--should not be in map.
+ ASSERT_TRUE(
+ spellcheck_service_->windows_spellcheck_dictionary_map_.empty());
+ }
+
+ EXPECT_EQ(expected_tag_passed_to_spellcheck,
+ SpellcheckService::GetTagToPassToWindowsSpellchecker(
+ expected_accept_language, full_tag));
+
+ // Special case for Serbian. The "sr" accept language is interpreted as using
+ // Cyrillic script. There should be an extra entry in the windows dictionary
+ // map if Cyrillic windows dictionary is installed.
+ if (base::EqualsCaseInsensitiveASCII(
+ "sr-Cyrl", SpellcheckService::GetLanguageAndScriptTag(
+ full_tag,
+ /* include_script_tag */ true))) {
+ EXPECT_EQ(full_tag,
+ spellcheck_service_->GetSupportedWindowsDictionaryLanguage("sr"));
+ } else {
+ EXPECT_TRUE(spellcheck_service_->GetSupportedWindowsDictionaryLanguage("sr")
+ .empty());
+ }
+}
+
+// static
+const std::vector<std::string> SpellcheckServiceHybridUnitTestBase::
+ windows_spellcheck_languages_for_testing_ = {
+ "fr-FR", // Has both Windows and Hunspell support.
+ "es-MX", // Has both Windows and Hunspell support, but for Hunspell
+ // maps to es-ES.
+ "gl-ES", // (Galician) Has only Windows support, no Hunspell
+ // dictionary.
+ "fi-FI", // (Finnish) Has only Windows support, no Hunspell
+ // dictionary.
+ "haw-US", // (Hawaiian) No Hunspell dictionary. Note that first two
+ // letters of language code are "ha," the same as Hausa.
+ "ast", // (Asturian) Has only Windows support, no Hunspell
+ // dictionary. Note that last two letters of language
+ // code are "st," the same as Sesotho.
+ "kok-Deva-IN", // Konkani (Devanagari, India)--note presence of
+ // script subtag.
+ "sr-Cyrl-ME", // Serbian (Cyrillic, Montenegro)--note presence of
+ // script subtag.
+ "sr-Latn-ME", // Serbian (Latin, Montenegro)--note presence of
+ // script subtag.
+ "ja-Latn-JP-x-ext", // Japanese with Latin script--note presence of
+ // private use subtag. Ignore private use
+ // dictionaries.
+};
+
+class SpellcheckServiceHybridUnitTest
+ : public SpellcheckServiceHybridUnitTestBase,
+ public testing::WithParamInterface<TestCase> {};
+
+static const TestCase kHybridGetDictionariesParams[] = {
+ // Galician (gl) has only Windows support, no Hunspell dictionary. Croatian
+ // (hr) has only Hunspell support, no local Windows dictionary. First
+ // language is supported by windows and should be spellchecked
+ TestCase("gl", {""}, {"gl"}, {"gl"}),
+ TestCase("gl", {"gl"}, {"gl"}, {"gl"}),
+ TestCase("gl,hr", {""}, {"gl", "hr"}, {"gl"}),
+ TestCase("gl,hr", {"gl"}, {"gl", "hr"}, {"gl"}),
+ TestCase("gl,hr", {"hr"}, {"gl", "hr"}, {"gl", "hr"}),
+ TestCase("gl,hr", {"gl", "hr"}, {"gl", "hr"}, {"gl", "hr"}),
+ // First language is not supported by windows so nothing is changed
+ TestCase("hr", {""}, {"hr"}, {""}), TestCase("hr", {"hr"}, {"hr"}, {"hr"}),
+ TestCase("hr,gl", {"hr"}, {"hr", "gl"}, {"hr"}),
+ // Finnish has only "fi" in hard-coded list of accept languages.
+ TestCase("fi-FI,fi,en-US,en", {"en-US"}, {"fi", "en-US"}, {"fi", "en-US"}),
+ // First language is supported by Windows but private use dictionaries
+ // are ignored.
+ TestCase("ja,gl", {"gl"}, {"gl"}, {"gl"}),
+ // (Basque) No Hunspell support, has Windows support but
+ // language pack not present.
+ TestCase("eu", {"eu"}, {""}, {""}),
+ TestCase("es-419,es-MX",
+ {"es-419", "es-MX"},
+ {"es-419", "es-MX"},
+ {"es-419", "es-MX"}),
+ TestCase("fr-FR,es-MX,gl,pt-BR,hr,it",
+ {"fr-FR", "gl", "pt-BR", "it"},
+ {"fr-FR", "es-MX", "gl", "pt-BR", "hr", "it"},
+ {"fr-FR", "gl", "pt-BR", "it"}),
+ // Hausa with Hawaiian language pack (ha/haw string in string).
+ TestCase("ha", {"ha"}, {""}, {""}),
+ // Sesotho with Asturian language pack (st/ast string in string).
+ TestCase("st", {"st"}, {""}, {""}),
+ // User chose generic Serbian in languages preferences (which uses
+ // Cyrillic script).
+ TestCase("sr,sr-Latn-RS", {"sr", "sr-Latn-RS"}, {"sr"}, {"sr"})};
+
+INSTANTIATE_TEST_SUITE_P(TestCases,
+ SpellcheckServiceHybridUnitTest,
+ testing::ValuesIn(kHybridGetDictionariesParams));
+
+TEST_P(SpellcheckServiceHybridUnitTest, GetDictionaries) {
+ RunGetDictionariesTest(GetParam().accept_languages,
+ GetParam().spellcheck_dictionaries,
+ GetParam().expected_dictionaries);
+}
+
+struct DictionaryMappingTestCase {
+ std::string full_tag;
+ std::string expected_accept_language;
+ std::string expected_tag_passed_to_spellcheck;
+};
+
+std::ostream& operator<<(std::ostream& out,
+ const DictionaryMappingTestCase& test_case) {
+ out << "full_tag=" << test_case.full_tag
+ << ", expected_accept_language=" << test_case.expected_accept_language
+ << ", expected_tag_passed_to_spellcheck="
+ << test_case.expected_tag_passed_to_spellcheck;
+
+ return out;
+}
+
+class SpellcheckServiceWindowsDictionaryMappingUnitTest
+ : public SpellcheckServiceHybridUnitTestBase,
+ public testing::WithParamInterface<DictionaryMappingTestCase> {};
+
+static const DictionaryMappingTestCase kHybridDictionaryMappingsParams[] = {
+ DictionaryMappingTestCase({"en-CA", "en-CA", "en-CA"}),
+ DictionaryMappingTestCase({"en-PH", "en", "en"}),
+ DictionaryMappingTestCase({"es-MX", "es-MX", "es-MX"}),
+ DictionaryMappingTestCase({"ar-SA", "ar", "ar"}),
+ // Konkani not supported in Chromium.
+ DictionaryMappingTestCase({"kok-Deva-IN", "", "kok-Deva"}),
+ DictionaryMappingTestCase({"sr-Cyrl-RS", "sr", "sr-Cyrl"}),
+ DictionaryMappingTestCase({"sr-Cyrl-ME", "sr", "sr-Cyrl"}),
+ // Only sr with Cyrillic implied supported in Chromium.
+ DictionaryMappingTestCase({"sr-Latn-RS", "", "sr-Latn"}),
+ DictionaryMappingTestCase({"sr-Latn-ME", "", "sr-Latn"}),
+ DictionaryMappingTestCase({"ca-ES", "ca", "ca"}),
+ DictionaryMappingTestCase({"ca-ES-valencia", "ca", "ca"})};
+
+INSTANTIATE_TEST_SUITE_P(TestCases,
+ SpellcheckServiceWindowsDictionaryMappingUnitTest,
+ testing::ValuesIn(kHybridDictionaryMappingsParams));
+
+TEST_P(SpellcheckServiceWindowsDictionaryMappingUnitTest, CheckMappings) {
+ RunDictionaryMappingTest(GetParam().full_tag,
+ GetParam().expected_accept_language,
+ GetParam().expected_tag_passed_to_spellcheck);
+}
+
+class SpellcheckServiceHybridUnitTestDelayInitBase
+ : public SpellcheckServiceHybridUnitTestBase {
+ public:
+ SpellcheckServiceHybridUnitTestDelayInitBase() = default;
+
+ void OnDictionariesInitialized() {
+ dictionaries_initialized_received_ = true;
+ if (quit_)
+ std::move(quit_).Run();
+ }
+
+ protected:
+ void InitFeatures() override {
+ // Don't initialize the SpellcheckService on browser launch.
+ feature_list_.InitWithFeatures(
+ /*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker,
+ spellcheck::kWinDelaySpellcheckServiceInit},
+ /*disabled_features=*/{});
+ }
+
+ void InitializeSpellcheckService(
+ const std::vector<std::string>& spellcheck_languages_for_testing)
+ override {
+ // Fake the presence of Windows spellcheck dictionaries.
+ spellcheck_service_ =
+ SpellcheckServiceFactory::GetInstance()->GetForContext(
+ browser_context());
+
+ spellcheck_service_->AddSpellcheckLanguagesForTesting(
+ spellcheck_languages_for_testing);
+
+ // Asynchronously load the dictionaries.
+ ASSERT_FALSE(spellcheck_service_->dictionaries_loaded());
+ spellcheck_service_->InitializeDictionaries(
+ base::BindOnce(&SpellcheckServiceHybridUnitTestDelayInitBase::
+ OnDictionariesInitialized,
+ base::Unretained(this)));
+
+ RunUntilCallbackReceived();
+ ASSERT_TRUE(spellcheck_service_->dictionaries_loaded());
+ }
+
+ void RunUntilCallbackReceived() {
+ if (dictionaries_initialized_received_)
+ return;
+ base::RunLoop run_loop;
+ quit_ = run_loop.QuitClosure();
+ run_loop.Run();
+
+ // reset status.
+ dictionaries_initialized_received_ = false;
+ }
+
+ private:
+ bool dictionaries_initialized_received_ = false;
+
+ // Quits the RunLoop on receiving the callback from InitializeDictionaries.
+ base::OnceClosure quit_;
+};
+
+class SpellcheckServiceHybridUnitTestDelayInit
+ : public SpellcheckServiceHybridUnitTestDelayInitBase,
+ public testing::WithParamInterface<TestCase> {};
+
+INSTANTIATE_TEST_SUITE_P(TestCases,
+ SpellcheckServiceHybridUnitTestDelayInit,
+ testing::ValuesIn(kHybridGetDictionariesParams));
+
+TEST_P(SpellcheckServiceHybridUnitTestDelayInit, GetDictionaries) {
+ RunGetDictionariesTest(GetParam().accept_languages,
+ GetParam().spellcheck_dictionaries,
+ GetParam().expected_dictionaries);
+}
+
+class SpellcheckServiceWindowsDictionaryMappingUnitTestDelayInit
+ : public SpellcheckServiceHybridUnitTestDelayInitBase,
+ public testing::WithParamInterface<DictionaryMappingTestCase> {};
+
+INSTANTIATE_TEST_SUITE_P(
+ TestCases,
+ SpellcheckServiceWindowsDictionaryMappingUnitTestDelayInit,
+ testing::ValuesIn(kHybridDictionaryMappingsParams));
+
+TEST_P(SpellcheckServiceWindowsDictionaryMappingUnitTestDelayInit,
+ CheckMappings) {
+ RunDictionaryMappingTest(GetParam().full_tag,
+ GetParam().expected_accept_language,
+ GetParam().expected_tag_passed_to_spellcheck);
+}
+#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
diff --git a/chromium/chrome/browser/spellchecker/spelling_request.cc b/chromium/chrome/browser/spellchecker/spelling_request.cc
index 37e993acff9..d3029666f29 100644
--- a/chromium/chrome/browser/spellchecker/spelling_request.cc
+++ b/chromium/chrome/browser/spellchecker/spelling_request.cc
@@ -7,7 +7,6 @@
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
-#include "base/task/post_task.h"
#include "chrome/browser/spellchecker/spellcheck_custom_dictionary.h"
#include "chrome/browser/spellchecker/spellcheck_factory.h"
#include "components/spellcheck/browser/spellcheck_platform.h"
@@ -133,8 +132,8 @@ void SpellingRequest::OnLocalCheckCompletedOnAnyThread(
base::WeakPtr<SpellingRequest> request,
const std::vector<SpellCheckResult>& results) {
// Local checking can happen on any thread - don't DCHECK thread.
- base::PostTask(FROM_HERE, {content::BrowserThread::UI},
- base::BindOnce(&SpellingRequest::OnLocalCheckCompleted,
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE, base::BindOnce(&SpellingRequest::OnLocalCheckCompleted,
request, results));
}