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