spellcheck_provider.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. // Copyright (c) 2012 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. #include "components/spellcheck/renderer/spellcheck_provider.h"
  5. #include <unordered_map>
  6. #include "base/bind.h"
  7. #include "base/metrics/histogram_macros.h"
  8. #include "base/strings/utf_string_conversions.h"
  9. #include "base/time/time.h"
  10. #include "build/build_config.h"
  11. #include "components/spellcheck/common/spellcheck.mojom.h"
  12. #include "components/spellcheck/common/spellcheck_common.h"
  13. #include "components/spellcheck/common/spellcheck_features.h"
  14. #include "components/spellcheck/common/spellcheck_result.h"
  15. #include "components/spellcheck/renderer/spellcheck.h"
  16. #include "components/spellcheck/renderer/spellcheck_language.h"
  17. #include "components/spellcheck/renderer/spellcheck_renderer_metrics.h"
  18. #include "components/spellcheck/spellcheck_buildflags.h"
  19. #include "content/public/renderer/render_frame.h"
  20. #include "content/public/renderer/render_thread.h"
  21. #include "services/service_manager/public/cpp/local_interface_provider.h"
  22. #include "third_party/blink/public/platform/web_vector.h"
  23. #include "third_party/blink/public/web/web_document.h"
  24. #include "third_party/blink/public/web/web_element.h"
  25. #include "third_party/blink/public/web/web_local_frame.h"
  26. #include "third_party/blink/public/web/web_text_checking_completion.h"
  27. #include "third_party/blink/public/web/web_text_checking_result.h"
  28. #include "third_party/blink/public/web/web_text_decoration_type.h"
  29. using blink::WebElement;
  30. using blink::WebLocalFrame;
  31. using blink::WebString;
  32. using blink::WebTextCheckingCompletion;
  33. using blink::WebTextCheckingResult;
  34. using blink::WebTextDecorationType;
  35. using blink::WebVector;
  36. static_assert(int(blink::kWebTextDecorationTypeSpelling) ==
  37. int(SpellCheckResult::SPELLING),
  38. "mismatching enums");
  39. static_assert(int(blink::kWebTextDecorationTypeGrammar) ==
  40. int(SpellCheckResult::GRAMMAR),
  41. "mismatching enums");
  42. class SpellCheckProvider::DictionaryUpdateObserverImpl
  43. : public DictionaryUpdateObserver {
  44. public:
  45. explicit DictionaryUpdateObserverImpl(SpellCheckProvider* owner);
  46. ~DictionaryUpdateObserverImpl() override;
  47. // DictionaryUpdateObserver:
  48. void OnDictionaryUpdated(const WebVector<WebString>& words_added) override;
  49. private:
  50. SpellCheckProvider* owner_;
  51. };
  52. SpellCheckProvider::DictionaryUpdateObserverImpl::DictionaryUpdateObserverImpl(
  53. SpellCheckProvider* owner)
  54. : owner_(owner) {
  55. owner_->spellcheck_->AddDictionaryUpdateObserver(this);
  56. }
  57. SpellCheckProvider::DictionaryUpdateObserverImpl::
  58. ~DictionaryUpdateObserverImpl() {
  59. owner_->spellcheck_->RemoveDictionaryUpdateObserver(this);
  60. }
  61. void SpellCheckProvider::DictionaryUpdateObserverImpl::OnDictionaryUpdated(
  62. const WebVector<WebString>& words_added) {
  63. // Clear only cache. Current pending requests should continue as they are.
  64. owner_->last_request_.clear();
  65. owner_->last_results_.Assign(
  66. blink::WebVector<blink::WebTextCheckingResult>());
  67. // owner_->render_frame() is nullptr in unit tests.
  68. if (auto* render_frame = owner_->render_frame()) {
  69. DCHECK(render_frame->GetWebFrame());
  70. render_frame->GetWebFrame()->RemoveSpellingMarkersUnderWords(words_added);
  71. }
  72. }
  73. SpellCheckProvider::SpellCheckProvider(
  74. content::RenderFrame* render_frame,
  75. SpellCheck* spellcheck,
  76. service_manager::LocalInterfaceProvider* embedder_provider)
  77. : content::RenderFrameObserver(render_frame),
  78. spellcheck_(spellcheck),
  79. embedder_provider_(embedder_provider) {
  80. DCHECK(spellcheck_);
  81. DCHECK(embedder_provider);
  82. if (render_frame) // NULL in unit tests.
  83. render_frame->GetWebFrame()->SetTextCheckClient(this);
  84. dictionary_update_observer_ =
  85. std::make_unique<DictionaryUpdateObserverImpl>(this);
  86. }
  87. SpellCheckProvider::~SpellCheckProvider() {
  88. }
  89. void SpellCheckProvider::ResetDictionaryUpdateObserverForTesting() {
  90. dictionary_update_observer_.reset();
  91. }
  92. spellcheck::mojom::SpellCheckHost& SpellCheckProvider::GetSpellCheckHost() {
  93. if (spell_check_host_)
  94. return *spell_check_host_;
  95. embedder_provider_->GetInterface(
  96. spell_check_host_.BindNewPipeAndPassReceiver());
  97. return *spell_check_host_;
  98. }
  99. void SpellCheckProvider::RequestTextChecking(
  100. const std::u16string& text,
  101. std::unique_ptr<WebTextCheckingCompletion> completion) {
  102. // Ignore invalid requests.
  103. if (text.empty() || !HasWordCharacters(text, 0)) {
  104. completion->DidCancelCheckingText();
  105. return;
  106. }
  107. // Try to satisfy check from cache.
  108. if (SatisfyRequestFromCache(text, completion.get()))
  109. return;
  110. // Send this text to a browser. A browser checks the user profile and send
  111. // this text to the Spelling service only if a user enables this feature.
  112. last_request_.clear();
  113. last_results_.Assign(blink::WebVector<blink::WebTextCheckingResult>());
  114. last_identifier_ = text_check_completions_.Add(std::move(completion));
  115. #if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  116. if (spellcheck::UseBrowserSpellChecker()) {
  117. #if BUILDFLAG(IS_WIN)
  118. if (base::FeatureList::IsEnabled(
  119. spellcheck::kWinDelaySpellcheckServiceInit) &&
  120. !dictionaries_loaded_) {
  121. // Initialize the spellcheck service on demand (this spellcheck request
  122. // could be the result of the first click in editable content), then
  123. // complete the text check request when the dictionaries are loaded.
  124. // The delayed spell check service initialization sequence, starting from
  125. // when the user clicks in editable content, is as follows:
  126. // - SpellcheckProvider::RequestTextChecking (Renderer, this method)
  127. // - SpellCheckHostChromeImpl::InitializeDictionaries (Browser)
  128. // - SpellcheckService::InitializeDictionaries (Browser)
  129. // - SpellCheckHostChromeImpl::OnDictionariesInitialized (Browser)
  130. // - SpellcheckProvider::OnRespondInitializeDictionaries (Renderer)
  131. GetSpellCheckHost().InitializeDictionaries(
  132. base::BindOnce(&SpellCheckProvider::OnRespondInitializeDictionaries,
  133. weak_factory_.GetWeakPtr(), text));
  134. return;
  135. }
  136. #endif // BUILDFLAG(IS_WIN)
  137. RequestTextCheckingFromBrowser(text);
  138. }
  139. #endif // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  140. #if BUILDFLAG(USE_RENDERER_SPELLCHECKER)
  141. if (!spellcheck::UseBrowserSpellChecker()) {
  142. GetSpellCheckHost().CallSpellingService(
  143. text,
  144. base::BindOnce(&SpellCheckProvider::OnRespondSpellingService,
  145. weak_factory_.GetWeakPtr(), last_identifier_, text));
  146. }
  147. #endif // BUILDFLAG(USE_RENDERER_SPELLCHECKER)
  148. }
  149. #if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  150. void SpellCheckProvider::RequestTextCheckingFromBrowser(
  151. const std::u16string& text) {
  152. DCHECK(spellcheck::UseBrowserSpellChecker());
  153. #if BUILDFLAG(IS_WIN)
  154. // Determine whether a hybrid check is needed.
  155. bool use_hunspell = spellcheck_->EnabledLanguageCount() > 0;
  156. bool use_native =
  157. spellcheck_->EnabledLanguageCount() != spellcheck_->LanguageCount();
  158. if (!use_hunspell && !use_native) {
  159. OnRespondTextCheck(last_identifier_, text, /*results=*/{});
  160. return;
  161. }
  162. if (!use_native) {
  163. // No language can be handled by the native spell checker. Use the regular
  164. // Hunspell code path.
  165. GetSpellCheckHost().CallSpellingService(
  166. text,
  167. base::BindOnce(&SpellCheckProvider::OnRespondSpellingService,
  168. weak_factory_.GetWeakPtr(), last_identifier_, text));
  169. return;
  170. }
  171. // Some languages can be handled by the native spell checker. Use the
  172. // regular browser spell check code path. If hybrid spell check is
  173. // required (i.e. some locales must be checked by Hunspell), misspellings
  174. // from the native spell checker will be double-checked with Hunspell in
  175. // the |OnRespondTextCheck| callback.
  176. hybrid_requests_info_[last_identifier_] = {/*used_hunspell=*/use_hunspell,
  177. /*used_native=*/use_native,
  178. base::TimeTicks::Now()};
  179. #endif // BUILDFLAG(IS_WIN)
  180. // Text check (unified request for grammar and spell check) is only
  181. // available for browser process, so we ask the system spellchecker
  182. // over mojo or return an empty result if the checker is not available.
  183. GetSpellCheckHost().RequestTextCheck(
  184. text, routing_id(),
  185. base::BindOnce(&SpellCheckProvider::OnRespondTextCheck,
  186. weak_factory_.GetWeakPtr(), last_identifier_, text));
  187. }
  188. #if BUILDFLAG(IS_WIN)
  189. void SpellCheckProvider::OnRespondInitializeDictionaries(
  190. const std::u16string& text,
  191. std::vector<spellcheck::mojom::SpellCheckBDictLanguagePtr> dictionaries,
  192. const std::vector<std::string>& custom_words,
  193. bool enable) {
  194. DCHECK(!dictionaries_loaded_);
  195. dictionaries_loaded_ = true;
  196. // Because the SpellChecker and SpellCheckHost mojo interfaces use different
  197. // channels, there is no guarantee that the SpellChecker::Initialize response
  198. // will be received before the SpellCheckHost::InitializeDictionaries
  199. // callback. If the order is reversed, no spellcheck will be performed since
  200. // the renderer side thinks there are no dictionaries available. Ensure that
  201. // the SpellChecker is initialized before performing a spellcheck.
  202. spellcheck_->Initialize(std::move(dictionaries), custom_words, enable);
  203. RequestTextCheckingFromBrowser(text);
  204. }
  205. #endif // BUILDFLAG(IS_WIN)
  206. #endif // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  207. void SpellCheckProvider::FocusedElementChanged(
  208. const blink::WebElement& unused) {
  209. #if BUILDFLAG(IS_ANDROID)
  210. if (!spell_check_host_.is_bound())
  211. return;
  212. WebLocalFrame* frame = render_frame()->GetWebFrame();
  213. WebElement element = frame->GetDocument().IsNull()
  214. ? WebElement()
  215. : frame->GetDocument().FocusedElement();
  216. bool enabled = !element.IsNull() && element.IsEditable();
  217. if (!enabled)
  218. GetSpellCheckHost().DisconnectSessionBridge();
  219. #endif // BUILDFLAG(IS_ANDROID)
  220. }
  221. bool SpellCheckProvider::IsSpellCheckingEnabled() const {
  222. return spellcheck_->IsSpellcheckEnabled();
  223. }
  224. void SpellCheckProvider::CheckSpelling(
  225. const WebString& text,
  226. size_t& offset,
  227. size_t& length,
  228. blink::WebVector<blink::WebString>* optional_suggestions) {
  229. std::u16string word = text.Utf16();
  230. const int kWordStart = 0;
  231. if (optional_suggestions) {
  232. #if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  233. base::TimeTicks suggestions_start = base::TimeTicks::Now();
  234. #endif // BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  235. // Retrieve suggestions from Hunspell. Windows platform spellchecker
  236. // suggestions are retrieved in SpellingMenuObserver::InitMenu on the
  237. // browser process side to avoid a blocking IPC.
  238. spellcheck::PerLanguageSuggestions per_language_suggestions;
  239. spellcheck_->SpellCheckWord(word.c_str(), kWordStart, word.size(),
  240. routing_id(), &offset, &length,
  241. &per_language_suggestions);
  242. #if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  243. spellcheck_renderer_metrics::RecordHunspellSuggestionDuration(
  244. base::TimeTicks::Now() - suggestions_start);
  245. #endif // BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  246. std::vector<std::u16string> suggestions;
  247. spellcheck::FillSuggestions(per_language_suggestions, &suggestions);
  248. WebVector<WebString> web_suggestions(suggestions.size());
  249. std::transform(
  250. suggestions.begin(), suggestions.end(), web_suggestions.begin(),
  251. [](const std::u16string& s) { return WebString::FromUTF16(s); });
  252. *optional_suggestions = web_suggestions;
  253. spellcheck_renderer_metrics::RecordCheckedTextLengthWithSuggestions(
  254. base::saturated_cast<int>(word.size()));
  255. } else {
  256. spellcheck_->SpellCheckWord(word.c_str(), kWordStart, word.size(),
  257. routing_id(), &offset, &length,
  258. /* optional suggestions vector */ nullptr);
  259. spellcheck_renderer_metrics::RecordCheckedTextLengthNoSuggestions(
  260. base::saturated_cast<int>(word.size()));
  261. // If optional_suggestions is not requested, the API is called
  262. // for marking. So we use this for counting markable words.
  263. GetSpellCheckHost().NotifyChecked(word, 0 < length);
  264. }
  265. }
  266. void SpellCheckProvider::RequestCheckingOfText(
  267. const WebString& text,
  268. std::unique_ptr<WebTextCheckingCompletion> completion) {
  269. RequestTextChecking(text.Utf16(), std::move(completion));
  270. spellcheck_renderer_metrics::RecordAsyncCheckedTextLength(
  271. base::saturated_cast<int>(text.length()));
  272. }
  273. #if BUILDFLAG(USE_RENDERER_SPELLCHECKER)
  274. void SpellCheckProvider::OnRespondSpellingService(
  275. int identifier,
  276. const std::u16string& line,
  277. bool success,
  278. const std::vector<SpellCheckResult>& results) {
  279. if (!text_check_completions_.Lookup(identifier))
  280. return;
  281. std::unique_ptr<WebTextCheckingCompletion> completion(
  282. text_check_completions_.Replace(identifier, nullptr));
  283. text_check_completions_.Remove(identifier);
  284. // If |success| is false, we use local spellcheck as a fallback.
  285. if (!success) {
  286. spellcheck_->RequestTextChecking(line, std::move(completion));
  287. return;
  288. }
  289. // Double-check the returned spellchecking results with Hunspell to visualize
  290. // the differences between ours and the enhanced spell checker.
  291. blink::WebVector<blink::WebTextCheckingResult> textcheck_results;
  292. spellcheck_->CreateTextCheckingResults(SpellCheck::USE_HUNSPELL_FOR_GRAMMAR,
  293. /*line_offset=*/0, line, results,
  294. &textcheck_results);
  295. completion->DidFinishCheckingText(textcheck_results);
  296. // Cache the request and the converted results.
  297. last_request_ = line;
  298. last_results_.Swap(textcheck_results);
  299. }
  300. #endif
  301. bool SpellCheckProvider::HasWordCharacters(const std::u16string& text,
  302. size_t index) const {
  303. const char16_t* data = text.data();
  304. size_t length = text.length();
  305. while (index < length) {
  306. uint32_t code = 0;
  307. U16_NEXT(data, index, length, code);
  308. UErrorCode error = U_ZERO_ERROR;
  309. if (uscript_getScript(code, &error) != USCRIPT_COMMON)
  310. return true;
  311. }
  312. return false;
  313. }
  314. #if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  315. void SpellCheckProvider::OnRespondTextCheck(
  316. int identifier,
  317. const std::u16string& line,
  318. const std::vector<SpellCheckResult>& results) {
  319. DCHECK(spellcheck_);
  320. if (!text_check_completions_.Lookup(identifier))
  321. return;
  322. std::unique_ptr<WebTextCheckingCompletion> completion(
  323. text_check_completions_.Replace(identifier, nullptr));
  324. text_check_completions_.Remove(identifier);
  325. blink::WebVector<blink::WebTextCheckingResult> textcheck_results;
  326. SpellCheck::ResultFilter result_filter = SpellCheck::DO_NOT_MODIFY;
  327. #if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  328. const auto& request_info = hybrid_requests_info_.find(identifier);
  329. if (spellcheck::UseBrowserSpellChecker() &&
  330. request_info != hybrid_requests_info_.end() &&
  331. request_info->second.used_hunspell && request_info->second.used_native) {
  332. // Not all locales could be checked by the native spell checker. Verify each
  333. // mistake against Hunspell in the locales that weren't checked.
  334. result_filter = SpellCheck::USE_HUNSPELL_FOR_HYBRID_CHECK;
  335. }
  336. #endif // BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  337. spellcheck_->CreateTextCheckingResults(result_filter,
  338. /*line_offset=*/0, line, results,
  339. &textcheck_results);
  340. #if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  341. if (request_info != hybrid_requests_info_.end()) {
  342. spellcheck_renderer_metrics::RecordSpellcheckDuration(
  343. base::TimeTicks::Now() - request_info->second.request_start_ticks,
  344. request_info->second.used_hunspell, request_info->second.used_native);
  345. hybrid_requests_info_.erase(request_info);
  346. }
  347. #endif // BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  348. completion->DidFinishCheckingText(textcheck_results);
  349. // Cache the request and the converted results.
  350. last_request_ = line;
  351. last_results_.Swap(textcheck_results);
  352. }
  353. #endif // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  354. bool SpellCheckProvider::SatisfyRequestFromCache(
  355. const std::u16string& text,
  356. WebTextCheckingCompletion* completion) {
  357. size_t last_length = last_request_.length();
  358. if (!last_length)
  359. return false;
  360. // Send back the |last_results_| if the |last_request_| is a substring of
  361. // |text| and |text| does not have more words to check. Provider cannot cancel
  362. // the spellcheck request here, because WebKit might have discarded the
  363. // previous spellcheck results and erased the spelling markers in response to
  364. // the user editing the text.
  365. std::u16string request(text);
  366. size_t text_length = request.length();
  367. if (text_length >= last_length &&
  368. !request.compare(0, last_length, last_request_)) {
  369. if (text_length == last_length || !HasWordCharacters(text, last_length)) {
  370. completion->DidFinishCheckingText(last_results_);
  371. return true;
  372. }
  373. }
  374. // Create a subset of the cached results and return it if the given text is a
  375. // substring of the cached text.
  376. if (text_length < last_length &&
  377. !last_request_.compare(0, text_length, request)) {
  378. size_t result_size = 0;
  379. for (size_t i = 0; i < last_results_.size(); ++i) {
  380. size_t start = last_results_[i].location;
  381. size_t end = start + last_results_[i].length;
  382. if (start <= text_length && end <= text_length)
  383. ++result_size;
  384. }
  385. blink::WebVector<blink::WebTextCheckingResult> results(last_results_.data(),
  386. result_size);
  387. completion->DidFinishCheckingText(results);
  388. return true;
  389. }
  390. return false;
  391. }
  392. void SpellCheckProvider::OnDestruct() {
  393. delete this;
  394. }