blob: 1ac94e7fa392db9a2b9f4b78933e0fb6ca140b04 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/spellcheck/browser/spellcheck_platform.h"
#include <objidl.h>
#include <spellcheck.h>
#include <wrl/client.h>
#include <codecvt>
#include <locale>
#include <string>
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/no_destructor.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/win/com_init_util.h"
#include "base/win/windows_types.h"
#include "base/win/windows_version.h"
#include "components/spellcheck/common/spellcheck_common.h"
#include "components/spellcheck/common/spellcheck_features.h"
#include "components/spellcheck/common/spellcheck_result.h"
namespace spellcheck_platform {
namespace {
// WindowsSpellChecker class is used to store all the COM objects and
// control their lifetime. The class also provides wrappers for
// ISpellCheckerFactory and ISpellChecker APIs. All COM calls are on the
// background thread.
class WindowsSpellChecker {
public:
WindowsSpellChecker(
const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
const scoped_refptr<base::SingleThreadTaskRunner> background_task_runner);
void CreateSpellChecker(const std::string& lang_tag,
base::OnceCallback<void(bool)> callback);
void DisableSpellChecker(const std::string& lang_tag);
void RequestTextCheckForAllLanguages(int document_tag,
const base::string16& text,
TextCheckCompleteCallback callback);
void AddWordForAllLanguages(const base::string16& word);
void RemoveWordForAllLanguages(const base::string16& word);
void IgnoreWordForAllLanguages(const base::string16& word);
void RecordMissingLanguagePacksCount(
const std::vector<std::string> spellcheck_locales,
SpellCheckHostMetrics* metrics);
private:
void CreateSpellCheckerFactoryInBackgroundThread();
// Creates ISpellchecker for given language |lang_tag| and run callback with
// the creation result. This function must run on the background thread.
void CreateSpellCheckerWithCallbackInBackgroundThread(
const std::string& lang_tag,
base::OnceCallback<void(bool)> callback);
// Removes the Windows Spellchecker for the given language |lang_tag|. This
// function must run on the background thread.
void DisableSpellCheckerInBackgroundThread(const std::string& lang_tag);
// Request spell checking of string |text| for all available spellchecking
// languages and run callback with spellchecking results. This function must
// run on the background thread.
void RequestTextCheckForAllLanguagesInBackgroundThread(
int document_tag,
const base::string16& text,
TextCheckCompleteCallback callback);
// Fills the given vector |optional_suggestions| with a number (up to
// kMaxSuggestions) of suggestions for the string |wrong_word| of language
// |lang_tag|. This function must run on the background thread.
void FillSuggestionListInBackgroundThread(
const std::string& lang_tag,
const base::string16& wrong_word,
std::vector<base::string16>* optional_suggestions);
void AddWordForAllLanguagesInBackgroundThread(const base::string16& word);
void RemoveWordForAllLanguagesInBackgroundThread(const base::string16& word);
void IgnoreWordForAllLanguagesInBackgroundThread(const base::string16& word);
// Returns true if spellchecker is available for the given language
// |current_language|. This function must run on the background thread.
bool IsLanguageSupportedInBackgroundThread(
const std::string& current_language);
// Returns true if ISpellCheckerFactory has been initialized.
bool IsSpellCheckerFactoryInitialized();
// Returns true if the ISpellChecker has been initialized for given laugnage
// |lang_tag|.
bool SpellCheckerReady(const std::string& lang_tag);
// Returns the ISpellChecker pointer for given language |lang_tag|.
Microsoft::WRL::ComPtr<ISpellChecker> GetSpellChecker(
const std::string& lang_tag);
// Records how many user spellcheck languages are currently not supported by
// the Windows OS spellchecker due to missing language packs. Must run on the
// background thread.
void RecordMissingLanguagePacksCountInBackgroundThread(
const std::vector<std::string> spellcheck_locales,
SpellCheckHostMetrics* metrics);
// Spellchecker objects are owned by WindowsSpellChecker class.
Microsoft::WRL::ComPtr<ISpellCheckerFactory> spell_checker_factory_;
std::map<std::string, Microsoft::WRL::ComPtr<ISpellChecker>>
spell_checker_map_;
// |main_task_runner_| is running on the main thread, which is used to post
// task to the main thread from the background thread.
// |background_task_runner_| is running on the background thread, which is
// used to post task to the background thread from main thread.
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
scoped_refptr<base::SingleThreadTaskRunner> background_task_runner_;
base::WeakPtrFactory<WindowsSpellChecker> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(WindowsSpellChecker);
};
WindowsSpellChecker::WindowsSpellChecker(
const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
const scoped_refptr<base::SingleThreadTaskRunner> background_task_runner)
: main_task_runner_(main_task_runner),
background_task_runner_(background_task_runner) {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&WindowsSpellChecker::CreateSpellCheckerFactoryInBackgroundThread,
weak_ptr_factory_.GetWeakPtr()));
}
void WindowsSpellChecker::CreateSpellChecker(
const std::string& lang_tag,
base::OnceCallback<void(bool)> callback) {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&WindowsSpellChecker::
CreateSpellCheckerWithCallbackInBackgroundThread,
weak_ptr_factory_.GetWeakPtr(), lang_tag,
std::move(callback)));
}
void WindowsSpellChecker::DisableSpellChecker(const std::string& lang_tag) {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&WindowsSpellChecker::DisableSpellCheckerInBackgroundThread,
weak_ptr_factory_.GetWeakPtr(), lang_tag));
}
void WindowsSpellChecker::RequestTextCheckForAllLanguages(
int document_tag,
const base::string16& text,
TextCheckCompleteCallback callback) {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&WindowsSpellChecker::
RequestTextCheckForAllLanguagesInBackgroundThread,
weak_ptr_factory_.GetWeakPtr(), document_tag, text,
std::move(callback)));
}
void WindowsSpellChecker::AddWordForAllLanguages(const base::string16& word) {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&WindowsSpellChecker::AddWordForAllLanguagesInBackgroundThread,
weak_ptr_factory_.GetWeakPtr(), word));
}
void WindowsSpellChecker::RemoveWordForAllLanguages(
const base::string16& word) {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&WindowsSpellChecker::RemoveWordForAllLanguagesInBackgroundThread,
weak_ptr_factory_.GetWeakPtr(), word));
}
void WindowsSpellChecker::IgnoreWordForAllLanguages(
const base::string16& word) {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&WindowsSpellChecker::IgnoreWordForAllLanguagesInBackgroundThread,
weak_ptr_factory_.GetWeakPtr(), word));
}
void WindowsSpellChecker::RecordMissingLanguagePacksCount(
const std::vector<std::string> spellcheck_locales,
SpellCheckHostMetrics* metrics) {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&WindowsSpellChecker::
RecordMissingLanguagePacksCountInBackgroundThread,
weak_ptr_factory_.GetWeakPtr(),
std::move(spellcheck_locales), metrics));
}
void WindowsSpellChecker::CreateSpellCheckerFactoryInBackgroundThread() {
DCHECK(!main_task_runner_->BelongsToCurrentThread());
base::win::AssertComApartmentType(base::win::ComApartmentType::STA);
if (!spellcheck::WindowsVersionSupportsSpellchecker() ||
FAILED(::CoCreateInstance(__uuidof(::SpellCheckerFactory), nullptr,
(CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER),
IID_PPV_ARGS(&spell_checker_factory_)))) {
spell_checker_factory_ = nullptr;
}
}
void WindowsSpellChecker::CreateSpellCheckerWithCallbackInBackgroundThread(
const std::string& lang_tag,
base::OnceCallback<void(bool)> callback) {
DCHECK(!main_task_runner_->BelongsToCurrentThread());
bool result = false;
if (IsSpellCheckerFactoryInitialized()) {
if (SpellCheckerReady(lang_tag)) {
result = true;
} else if (IsLanguageSupportedInBackgroundThread(lang_tag)) {
Microsoft::WRL::ComPtr<ISpellChecker> spell_checker;
std::wstring bcp47_language_tag = base::UTF8ToWide(lang_tag);
HRESULT hr = spell_checker_factory_->CreateSpellChecker(
bcp47_language_tag.c_str(), &spell_checker);
if (SUCCEEDED(hr)) {
spell_checker_map_.insert({lang_tag, spell_checker});
result = true;
}
}
}
// Run the callback with result on the main thread.
main_task_runner_->PostTask(FROM_HERE,
base::BindOnce(std::move(callback), result));
}
void WindowsSpellChecker::DisableSpellCheckerInBackgroundThread(
const std::string& lang_tag) {
DCHECK(!main_task_runner_->BelongsToCurrentThread());
if (!IsSpellCheckerFactoryInitialized())
return;
auto it = spell_checker_map_.find(lang_tag);
if (it != spell_checker_map_.end()) {
spell_checker_map_.erase(it);
}
}
void WindowsSpellChecker::RequestTextCheckForAllLanguagesInBackgroundThread(
int document_tag,
const base::string16& text,
TextCheckCompleteCallback callback) {
DCHECK(!main_task_runner_->BelongsToCurrentThread());
// Construct a map to store spellchecking results. The key of the map is a
// tuple which contains the start index and the word length of the misspelled
// word. The value of the map is a vector which contains suggestion lists for
// each available language.
std::map<std::tuple<ULONG, ULONG>, std::vector<std::vector<base::string16>>>
result_map;
std::vector<SpellCheckResult> results;
for (auto it = spell_checker_map_.begin(); it != spell_checker_map_.end();
++it) {
std::wstring word_to_check_wide(base::UTF16ToWide(text));
Microsoft::WRL::ComPtr<IEnumSpellingError> spelling_errors;
HRESULT hr = it->second->ComprehensiveCheck(word_to_check_wide.c_str(),
&spelling_errors);
if (SUCCEEDED(hr) && spelling_errors) {
do {
Microsoft::WRL::ComPtr<ISpellingError> spelling_error;
ULONG start_index = 0;
ULONG error_length = 0;
CORRECTIVE_ACTION action = CORRECTIVE_ACTION_NONE;
hr = spelling_errors->Next(&spelling_error);
if (SUCCEEDED(hr) && spelling_error &&
SUCCEEDED(spelling_error->get_StartIndex(&start_index)) &&
SUCCEEDED(spelling_error->get_Length(&error_length)) &&
SUCCEEDED(spelling_error->get_CorrectiveAction(&action)) &&
(action == CORRECTIVE_ACTION_GET_SUGGESTIONS ||
action == CORRECTIVE_ACTION_REPLACE)) {
std::vector<base::string16> suggestions;
FillSuggestionListInBackgroundThread(
it->first, text.substr(start_index, error_length), &suggestions);
result_map[std::tuple<ULONG, ULONG>(start_index, error_length)]
.push_back(suggestions);
}
} while (hr == S_OK);
}
}
// Generates results vector from map. Remove entries if the word is not
// misspelled for all available laugages.
for (auto it = result_map.begin(); it != result_map.end();) {
if (it->second.size() < spell_checker_map_.size()) {
it = result_map.erase(it);
} else {
// Prepare results vector.
std::vector<base::string16> suggestions_result;
for (auto suggestions_list : it->second) {
for (auto suggestions : suggestions_list) {
suggestions_result.push_back(suggestions);
}
}
results.push_back(SpellCheckResult(
SpellCheckResult::Decoration::SPELLING, std::get<0>(it->first),
std::get<1>(it->first), suggestions_result));
++it;
}
}
// Runs the callback on the main thread after spellcheck completed.
main_task_runner_->PostTask(FROM_HERE,
base::BindOnce(std::move(callback), results));
}
void WindowsSpellChecker::FillSuggestionListInBackgroundThread(
const std::string& lang_tag,
const base::string16& wrong_word,
std::vector<base::string16>* optional_suggestions) {
DCHECK(!main_task_runner_->BelongsToCurrentThread());
std::wstring word_wide(base::UTF16ToWide(wrong_word));
Microsoft::WRL::ComPtr<IEnumString> suggestions;
HRESULT hr =
GetSpellChecker(lang_tag)->Suggest(word_wide.c_str(), &suggestions);
// Populate the vector of WideStrings.
while (hr == S_OK) {
wchar_t* suggestion = nullptr;
hr = suggestions->Next(1, &suggestion, nullptr);
if (hr == S_OK) {
base::string16 utf16_suggestion;
if (base::WideToUTF16(suggestion, wcslen(suggestion),
&utf16_suggestion)) {
optional_suggestions->push_back(utf16_suggestion);
}
CoTaskMemFree(suggestion);
}
}
}
void WindowsSpellChecker::AddWordForAllLanguagesInBackgroundThread(
const base::string16& word) {
DCHECK(!main_task_runner_->BelongsToCurrentThread());
for (auto it = spell_checker_map_.begin(); it != spell_checker_map_.end();
++it) {
std::wstring word_to_add_wide(base::UTF16ToWide(word));
it->second->Add(word_to_add_wide.c_str());
}
}
void WindowsSpellChecker::RemoveWordForAllLanguagesInBackgroundThread(
const base::string16& word) {
DCHECK(!main_task_runner_->BelongsToCurrentThread());
for (auto it = spell_checker_map_.begin(); it != spell_checker_map_.end();
++it) {
std::wstring word_to_remove_wide(base::UTF16ToWide(word));
Microsoft::WRL::ComPtr<ISpellChecker2> spell_checker_2;
it->second->QueryInterface(IID_PPV_ARGS(&spell_checker_2));
if (spell_checker_2 != nullptr) {
spell_checker_2->Remove(word_to_remove_wide.c_str());
}
}
}
void WindowsSpellChecker::IgnoreWordForAllLanguagesInBackgroundThread(
const base::string16& word) {
DCHECK(!main_task_runner_->BelongsToCurrentThread());
for (auto it = spell_checker_map_.begin(); it != spell_checker_map_.end();
++it) {
std::wstring word_to_ignore_wide(base::UTF16ToWide(word));
it->second->Ignore(word_to_ignore_wide.c_str());
}
}
bool WindowsSpellChecker::IsLanguageSupportedInBackgroundThread(
const std::string& current_language) {
DCHECK(!main_task_runner_->BelongsToCurrentThread());
if (!IsSpellCheckerFactoryInitialized()) {
// The native spellchecker creation failed; no language is supported.
return false;
}
BOOL is_language_supported = (BOOL) false;
std::wstring bcp47_language_tag = base::UTF8ToWide(current_language);
HRESULT hr = spell_checker_factory_->IsSupported(bcp47_language_tag.c_str(),
&is_language_supported);
return SUCCEEDED(hr) && is_language_supported;
}
bool WindowsSpellChecker::IsSpellCheckerFactoryInitialized() {
return spell_checker_factory_ != nullptr;
}
bool WindowsSpellChecker::SpellCheckerReady(const std::string& lang_tag) {
return spell_checker_map_.find(lang_tag) != spell_checker_map_.end();
}
Microsoft::WRL::ComPtr<ISpellChecker> WindowsSpellChecker::GetSpellChecker(
const std::string& lang_tag) {
DCHECK(SpellCheckerReady(lang_tag));
return spell_checker_map_.find(lang_tag)->second;
}
void WindowsSpellChecker::RecordMissingLanguagePacksCountInBackgroundThread(
const std::vector<std::string> spellcheck_locales,
SpellCheckHostMetrics* metrics) {
DCHECK(!main_task_runner_->BelongsToCurrentThread());
DCHECK(metrics);
if (!IsSpellCheckerFactoryInitialized()) {
// The native spellchecker creation failed. Do not record any metrics.
return;
}
metrics->RecordMissingLanguagePacksCount(
std::count_if(spellcheck_locales.begin(), spellcheck_locales.end(),
[this](const std::string& s) {
return !this->IsLanguageSupportedInBackgroundThread(s);
}));
}
// Create WindowsSpellChecker class with static storage duration that is only
// constructed on first access and never invokes the destructor.
std::unique_ptr<WindowsSpellChecker>& GetWindowsSpellChecker() {
static base::NoDestructor<std::unique_ptr<WindowsSpellChecker>>
win_spell_checker(std::make_unique<WindowsSpellChecker>(
base::ThreadTaskRunnerHandle::Get(),
base::CreateCOMSTATaskRunner(
{base::ThreadPool(), base::MayBlock()})));
return *win_spell_checker;
}
} // anonymous namespace
bool SpellCheckerAvailable() {
return true;
}
bool PlatformSupportsLanguage(const std::string& current_language) {
return true;
}
void SetLanguage(const std::string& lang_to_set,
base::OnceCallback<void(bool)> callback) {
GetWindowsSpellChecker()->CreateSpellChecker(lang_to_set,
std::move(callback));
}
void DisableLanguage(const std::string& lang_to_disable) {
GetWindowsSpellChecker()->DisableSpellChecker(lang_to_disable);
}
bool CheckSpelling(const base::string16& word_to_check, int tag) {
return true; // Not used in Windows
}
void FillSuggestionList(const base::string16& wrong_word,
std::vector<base::string16>* optional_suggestions) {
// Not used in Windows.
}
void RequestTextCheck(int document_tag,
const base::string16& text,
TextCheckCompleteCallback callback) {
GetWindowsSpellChecker()->RequestTextCheckForAllLanguages(
document_tag, text, std::move(callback));
}
void AddWord(const base::string16& word) {
GetWindowsSpellChecker()->AddWordForAllLanguages(word);
}
void RemoveWord(const base::string16& word) {
GetWindowsSpellChecker()->RemoveWordForAllLanguages(word);
}
void IgnoreWord(const base::string16& word) {
GetWindowsSpellChecker()->IgnoreWordForAllLanguages(word);
}
void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages) {
// Not used in Windows
}
int GetDocumentTag() {
return 1; // Not used in Windows
}
void CloseDocumentWithTag(int tag) {
// Not implemented since Windows spellchecker doesn't have this concept
}
bool SpellCheckerProvidesPanel() {
return false; // Windows doesn't have a spelling panel
}
bool SpellingPanelVisible() {
return false; // Windows doesn't have a spelling panel
}
void ShowSpellingPanel(bool show) {
// Not implemented since Windows doesn't have spelling panel like Mac
}
void UpdateSpellingPanelWithMisspelledWord(const base::string16& word) {
// Not implemented since Windows doesn't have spelling panel like Mac
}
void RecordMissingLanguagePacksCount(
const std::vector<std::string> spellcheck_locales,
SpellCheckHostMetrics* metrics) {
if (spellcheck::WindowsVersionSupportsSpellchecker()) {
GetWindowsSpellChecker()->RecordMissingLanguagePacksCount(
std::move(spellcheck_locales), metrics);
}
}
} // namespace spellcheck_platform