1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h"
6
7 #include "base/bind.h"
8 #include "base/no_destructor.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "build/build_config.h"
11 #include "chrome/browser/spellchecker/spellcheck_custom_dictionary.h"
12 #include "chrome/browser/spellchecker/spellcheck_factory.h"
13 #include "chrome/browser/spellchecker/spellcheck_service.h"
14 #include "components/spellcheck/browser/spellcheck_host_metrics.h"
15 #include "components/spellcheck/browser/spellcheck_platform.h"
16 #include "components/spellcheck/common/spellcheck_features.h"
17 #include "components/spellcheck/spellcheck_buildflags.h"
18 #include "content/public/browser/browser_context.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/browser/render_process_host.h"
21 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
22
23 #if BUILDFLAG(USE_BROWSER_SPELLCHECKER) && BUILDFLAG(ENABLE_SPELLING_SERVICE)
24 #include "chrome/browser/spellchecker/spelling_request.h"
25 #endif
26
27 namespace {
28
GetSpellCheckHostBinderOverride()29 SpellCheckHostChromeImpl::Binder& GetSpellCheckHostBinderOverride() {
30 static base::NoDestructor<SpellCheckHostChromeImpl::Binder> binder;
31 return *binder;
32 }
33
34 } // namespace
35
SpellCheckHostChromeImpl(int render_process_id)36 SpellCheckHostChromeImpl::SpellCheckHostChromeImpl(int render_process_id)
37 : render_process_id_(render_process_id) {}
38
39 SpellCheckHostChromeImpl::~SpellCheckHostChromeImpl() = default;
40
41 // static
Create(int render_process_id,mojo::PendingReceiver<spellcheck::mojom::SpellCheckHost> receiver)42 void SpellCheckHostChromeImpl::Create(
43 int render_process_id,
44 mojo::PendingReceiver<spellcheck::mojom::SpellCheckHost> receiver) {
45 auto& binder = GetSpellCheckHostBinderOverride();
46 if (binder) {
47 binder.Run(render_process_id, std::move(receiver));
48 return;
49 }
50
51 mojo::MakeSelfOwnedReceiver(
52 std::make_unique<SpellCheckHostChromeImpl>(render_process_id),
53 std::move(receiver));
54 }
55
56 // static
OverrideBinderForTesting(Binder binder)57 void SpellCheckHostChromeImpl::OverrideBinderForTesting(Binder binder) {
58 GetSpellCheckHostBinderOverride() = std::move(binder);
59 }
60
RequestDictionary()61 void SpellCheckHostChromeImpl::RequestDictionary() {
62 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
63
64 // The renderer has requested that we initialize its spellchecker. This
65 // generally should only be called once per session, as after the first
66 // call, future renderers will be passed the initialization information
67 // on startup (or when the dictionary changes in some way).
68 SpellcheckService* spellcheck = GetSpellcheckService();
69 if (!spellcheck)
70 return; // Teardown.
71
72 // The spellchecker initialization already started and finished; just
73 // send it to the renderer.
74 auto* host = content::RenderProcessHost::FromID(render_process_id_);
75 if (host)
76 spellcheck->InitForRenderer(host);
77
78 // TODO(rlp): Ensure that we do not initialize the hunspell dictionary
79 // more than once if we get requests from different renderers.
80 }
81
NotifyChecked(const base::string16 & word,bool misspelled)82 void SpellCheckHostChromeImpl::NotifyChecked(const base::string16& word,
83 bool misspelled) {
84 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
85
86 SpellcheckService* spellcheck = GetSpellcheckService();
87 if (!spellcheck)
88 return; // Teardown.
89 if (spellcheck->GetMetrics())
90 spellcheck->GetMetrics()->RecordCheckedWordStats(word, misspelled);
91 }
92
93 #if BUILDFLAG(USE_RENDERER_SPELLCHECKER)
CallSpellingService(const base::string16 & text,CallSpellingServiceCallback callback)94 void SpellCheckHostChromeImpl::CallSpellingService(
95 const base::string16& text,
96 CallSpellingServiceCallback callback) {
97 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
98
99 if (text.empty()) {
100 std::move(callback).Run(false, std::vector<SpellCheckResult>());
101 mojo::ReportBadMessage("Requested spelling service with empty text");
102 return;
103 }
104
105 // Checks the user profile and sends a JSON-RPC request to the Spelling
106 // service if a user enables the "Use enhanced spell check" option. When
107 // a response is received (including an error) from the remote Spelling
108 // service, calls CallSpellingServiceDone.
109 auto* host = content::RenderProcessHost::FromID(render_process_id_);
110 if (!host) {
111 std::move(callback).Run(false, std::vector<SpellCheckResult>());
112 return;
113 }
114 client_.RequestTextCheck(
115 host->GetBrowserContext(), SpellingServiceClient::SPELLCHECK, text,
116 base::BindOnce(&SpellCheckHostChromeImpl::CallSpellingServiceDone,
117 weak_factory_.GetWeakPtr(), std::move(callback)));
118 }
119
CallSpellingServiceDone(CallSpellingServiceCallback callback,bool success,const base::string16 & text,const std::vector<SpellCheckResult> & service_results) const120 void SpellCheckHostChromeImpl::CallSpellingServiceDone(
121 CallSpellingServiceCallback callback,
122 bool success,
123 const base::string16& text,
124 const std::vector<SpellCheckResult>& service_results) const {
125 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
126
127 SpellcheckService* spellcheck = GetSpellcheckService();
128 if (!spellcheck) { // Teardown.
129 std::move(callback).Run(false, std::vector<SpellCheckResult>());
130 return;
131 }
132
133 std::vector<SpellCheckResult> results = FilterCustomWordResults(
134 base::UTF16ToUTF8(text), *spellcheck->GetCustomDictionary(),
135 service_results);
136
137 std::move(callback).Run(success, results);
138 }
139
140 // static
FilterCustomWordResults(const std::string & text,const SpellcheckCustomDictionary & custom_dictionary,const std::vector<SpellCheckResult> & service_results)141 std::vector<SpellCheckResult> SpellCheckHostChromeImpl::FilterCustomWordResults(
142 const std::string& text,
143 const SpellcheckCustomDictionary& custom_dictionary,
144 const std::vector<SpellCheckResult>& service_results) {
145 std::vector<SpellCheckResult> results;
146 for (const auto& result : service_results) {
147 const std::string word = text.substr(result.location, result.length);
148 if (!custom_dictionary.HasWord(word))
149 results.push_back(result);
150 }
151
152 return results;
153 }
154 #endif // BUILDFLAG(USE_RENDERER_SPELLCHECKER)
155
156 #if BUILDFLAG(USE_BROWSER_SPELLCHECKER) && BUILDFLAG(ENABLE_SPELLING_SERVICE)
CheckSpelling(const base::string16 & word,int route_id,CheckSpellingCallback callback)157 void SpellCheckHostChromeImpl::CheckSpelling(const base::string16& word,
158 int route_id,
159 CheckSpellingCallback callback) {
160 bool correct = spellcheck_platform::CheckSpelling(word, route_id);
161 std::move(callback).Run(correct);
162 }
163
FillSuggestionList(const base::string16 & word,FillSuggestionListCallback callback)164 void SpellCheckHostChromeImpl::FillSuggestionList(
165 const base::string16& word,
166 FillSuggestionListCallback callback) {
167 std::vector<base::string16> suggestions;
168 spellcheck_platform::FillSuggestionList(word, &suggestions);
169 std::move(callback).Run(suggestions);
170 }
171
RequestTextCheck(const base::string16 & text,int route_id,RequestTextCheckCallback callback)172 void SpellCheckHostChromeImpl::RequestTextCheck(
173 const base::string16& text,
174 int route_id,
175 RequestTextCheckCallback callback) {
176 DCHECK(!text.empty());
177 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
178
179 // Initialize the spellcheck service if needed. The service will send the
180 // language code for text breaking to the renderer. (Text breaking is required
181 // for the context menu to show spelling suggestions.) Initialization must
182 // happen on UI thread.
183 SpellcheckService* spellcheck = GetSpellcheckService();
184
185 if (!spellcheck) { // Teardown.
186 std::move(callback).Run({});
187 return;
188 }
189
190 // OK to store unretained |this| in a |SpellingRequest| owned by |this|.
191 requests_.insert(std::make_unique<SpellingRequest>(
192 spellcheck->platform_spell_checker(), &client_, text, render_process_id_,
193 route_id, std::move(callback),
194 base::BindOnce(&SpellCheckHostChromeImpl::OnRequestFinished,
195 base::Unretained(this))));
196 }
197
198 #if defined(OS_WIN)
GetPerLanguageSuggestions(const base::string16 & word,GetPerLanguageSuggestionsCallback callback)199 void SpellCheckHostChromeImpl::GetPerLanguageSuggestions(
200 const base::string16& word,
201 GetPerLanguageSuggestionsCallback callback) {
202 SpellcheckService* spellcheck = GetSpellcheckService();
203
204 if (!spellcheck) { // Teardown.
205 std::move(callback).Run({});
206 return;
207 }
208
209 spellcheck_platform::GetPerLanguageSuggestions(
210 spellcheck->platform_spell_checker(), word, std::move(callback));
211 }
212
InitializeDictionaries(InitializeDictionariesCallback callback)213 void SpellCheckHostChromeImpl::InitializeDictionaries(
214 InitializeDictionariesCallback callback) {
215 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
216
217 if (base::FeatureList::IsEnabled(
218 spellcheck::kWinDelaySpellcheckServiceInit)) {
219 // Initialize the spellcheck service if needed. Initialization must
220 // happen on UI thread.
221 SpellcheckService* spellcheck = GetSpellcheckService();
222
223 if (!spellcheck) { // Teardown.
224 std::move(callback).Run(/*dictionaries=*/{}, /*custom_words=*/{},
225 /*enable=*/false);
226 return;
227 }
228
229 dictionaries_loaded_callback_ = std::move(callback);
230
231 spellcheck->InitializeDictionaries(
232 base::BindOnce(&SpellCheckHostChromeImpl::OnDictionariesInitialized,
233 weak_factory_.GetWeakPtr()));
234 return;
235 }
236
237 NOTREACHED();
238 std::move(callback).Run(/*dictionaries=*/{}, /*custom_words=*/{},
239 /*enable=*/false);
240 }
241
OnDictionariesInitialized()242 void SpellCheckHostChromeImpl::OnDictionariesInitialized() {
243 DCHECK(dictionaries_loaded_callback_);
244 SpellcheckService* spellcheck = GetSpellcheckService();
245
246 if (!spellcheck) { // Teardown.
247 std::move(dictionaries_loaded_callback_)
248 .Run(/*dictionaries=*/{}, /*custom_words=*/{},
249 /*enable=*/false);
250 return;
251 }
252
253 const bool enable = spellcheck->IsSpellcheckEnabled();
254
255 std::vector<spellcheck::mojom::SpellCheckBDictLanguagePtr> dictionaries;
256 std::vector<std::string> custom_words;
257 if (enable) {
258 for (const auto& hunspell_dictionary :
259 spellcheck->GetHunspellDictionaries()) {
260 dictionaries.push_back(spellcheck::mojom::SpellCheckBDictLanguage::New(
261 hunspell_dictionary->GetDictionaryFile().Duplicate(),
262 hunspell_dictionary->GetLanguage()));
263 }
264
265 SpellcheckCustomDictionary* custom_dictionary =
266 spellcheck->GetCustomDictionary();
267 custom_words.assign(custom_dictionary->GetWords().begin(),
268 custom_dictionary->GetWords().end());
269 }
270
271 std::move(dictionaries_loaded_callback_)
272 .Run(std::move(dictionaries), custom_words, enable);
273 }
274 #endif // defined(OS_WIN)
275
OnRequestFinished(SpellingRequest * request)276 void SpellCheckHostChromeImpl::OnRequestFinished(SpellingRequest* request) {
277 auto iterator = requests_.find(request);
278 requests_.erase(iterator);
279 }
280
281 // static
CombineResultsForTesting(std::vector<SpellCheckResult> * remote_results,const std::vector<SpellCheckResult> & local_results)282 void SpellCheckHostChromeImpl::CombineResultsForTesting(
283 std::vector<SpellCheckResult>* remote_results,
284 const std::vector<SpellCheckResult>& local_results) {
285 SpellingRequest::CombineResults(remote_results, local_results);
286 }
287 #endif // BUILDFLAG(USE_BROWSER_SPELLCHECKER) &&
288 // BUILDFLAG(ENABLE_SPELLING_SERVICE)
289
290 #if defined(OS_MAC)
ToDocumentTag(int route_id)291 int SpellCheckHostChromeImpl::ToDocumentTag(int route_id) {
292 if (!tag_map_.count(route_id))
293 tag_map_[route_id] = spellcheck_platform::GetDocumentTag();
294 return tag_map_[route_id];
295 }
296
297 // TODO(groby): We are currently not notified of retired tags. We need
298 // to track destruction of RenderViewHosts on the browser process side
299 // to update our mappings when a document goes away.
RetireDocumentTag(int route_id)300 void SpellCheckHostChromeImpl::RetireDocumentTag(int route_id) {
301 spellcheck_platform::CloseDocumentWithTag(ToDocumentTag(route_id));
302 tag_map_.erase(route_id);
303 }
304 #endif // defined(OS_MAC)
305
GetSpellcheckService() const306 SpellcheckService* SpellCheckHostChromeImpl::GetSpellcheckService() const {
307 auto* host = content::RenderProcessHost::FromID(render_process_id_);
308 if (!host)
309 return nullptr;
310 return SpellcheckServiceFactory::GetForContext(host->GetBrowserContext());
311 }
312