123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- // Copyright (c) 2012 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/renderer/spellcheck_provider.h"
- #include <unordered_map>
- #include "base/bind.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/strings/utf_string_conversions.h"
- #include "base/time/time.h"
- #include "build/build_config.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/renderer/spellcheck.h"
- #include "components/spellcheck/renderer/spellcheck_language.h"
- #include "components/spellcheck/renderer/spellcheck_renderer_metrics.h"
- #include "components/spellcheck/spellcheck_buildflags.h"
- #include "content/public/renderer/render_frame.h"
- #include "content/public/renderer/render_thread.h"
- #include "services/service_manager/public/cpp/local_interface_provider.h"
- #include "third_party/blink/public/platform/web_vector.h"
- #include "third_party/blink/public/web/web_document.h"
- #include "third_party/blink/public/web/web_element.h"
- #include "third_party/blink/public/web/web_local_frame.h"
- #include "third_party/blink/public/web/web_text_checking_completion.h"
- #include "third_party/blink/public/web/web_text_checking_result.h"
- #include "third_party/blink/public/web/web_text_decoration_type.h"
- using blink::WebElement;
- using blink::WebLocalFrame;
- using blink::WebString;
- using blink::WebTextCheckingCompletion;
- using blink::WebTextCheckingResult;
- using blink::WebTextDecorationType;
- using blink::WebVector;
- static_assert(int(blink::kWebTextDecorationTypeSpelling) ==
- int(SpellCheckResult::SPELLING),
- "mismatching enums");
- static_assert(int(blink::kWebTextDecorationTypeGrammar) ==
- int(SpellCheckResult::GRAMMAR),
- "mismatching enums");
- class SpellCheckProvider::DictionaryUpdateObserverImpl
- : public DictionaryUpdateObserver {
- public:
- explicit DictionaryUpdateObserverImpl(SpellCheckProvider* owner);
- ~DictionaryUpdateObserverImpl() override;
- // DictionaryUpdateObserver:
- void OnDictionaryUpdated(const WebVector<WebString>& words_added) override;
- private:
- SpellCheckProvider* owner_;
- };
- SpellCheckProvider::DictionaryUpdateObserverImpl::DictionaryUpdateObserverImpl(
- SpellCheckProvider* owner)
- : owner_(owner) {
- owner_->spellcheck_->AddDictionaryUpdateObserver(this);
- }
- SpellCheckProvider::DictionaryUpdateObserverImpl::
- ~DictionaryUpdateObserverImpl() {
- owner_->spellcheck_->RemoveDictionaryUpdateObserver(this);
- }
- void SpellCheckProvider::DictionaryUpdateObserverImpl::OnDictionaryUpdated(
- const WebVector<WebString>& words_added) {
- // Clear only cache. Current pending requests should continue as they are.
- owner_->last_request_.clear();
- owner_->last_results_.Assign(
- blink::WebVector<blink::WebTextCheckingResult>());
- // owner_->render_frame() is nullptr in unit tests.
- if (auto* render_frame = owner_->render_frame()) {
- DCHECK(render_frame->GetWebFrame());
- render_frame->GetWebFrame()->RemoveSpellingMarkersUnderWords(words_added);
- }
- }
- SpellCheckProvider::SpellCheckProvider(
- content::RenderFrame* render_frame,
- SpellCheck* spellcheck,
- service_manager::LocalInterfaceProvider* embedder_provider)
- : content::RenderFrameObserver(render_frame),
- spellcheck_(spellcheck),
- embedder_provider_(embedder_provider) {
- DCHECK(spellcheck_);
- DCHECK(embedder_provider);
- if (render_frame) // NULL in unit tests.
- render_frame->GetWebFrame()->SetTextCheckClient(this);
- dictionary_update_observer_ =
- std::make_unique<DictionaryUpdateObserverImpl>(this);
- }
- SpellCheckProvider::~SpellCheckProvider() {
- }
- void SpellCheckProvider::ResetDictionaryUpdateObserverForTesting() {
- dictionary_update_observer_.reset();
- }
- spellcheck::mojom::SpellCheckHost& SpellCheckProvider::GetSpellCheckHost() {
- if (spell_check_host_)
- return *spell_check_host_;
- embedder_provider_->GetInterface(
- spell_check_host_.BindNewPipeAndPassReceiver());
- return *spell_check_host_;
- }
- void SpellCheckProvider::RequestTextChecking(
- const std::u16string& text,
- std::unique_ptr<WebTextCheckingCompletion> completion) {
- // Ignore invalid requests.
- if (text.empty() || !HasWordCharacters(text, 0)) {
- completion->DidCancelCheckingText();
- return;
- }
- // Try to satisfy check from cache.
- if (SatisfyRequestFromCache(text, completion.get()))
- return;
- // Send this text to a browser. A browser checks the user profile and send
- // this text to the Spelling service only if a user enables this feature.
- last_request_.clear();
- last_results_.Assign(blink::WebVector<blink::WebTextCheckingResult>());
- last_identifier_ = text_check_completions_.Add(std::move(completion));
- #if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- if (spellcheck::UseBrowserSpellChecker()) {
- #if BUILDFLAG(IS_WIN)
- if (base::FeatureList::IsEnabled(
- spellcheck::kWinDelaySpellcheckServiceInit) &&
- !dictionaries_loaded_) {
- // Initialize the spellcheck service on demand (this spellcheck request
- // could be the result of the first click in editable content), then
- // complete the text check request when the dictionaries are loaded.
- // The delayed spell check service initialization sequence, starting from
- // when the user clicks in editable content, is as follows:
- // - SpellcheckProvider::RequestTextChecking (Renderer, this method)
- // - SpellCheckHostChromeImpl::InitializeDictionaries (Browser)
- // - SpellcheckService::InitializeDictionaries (Browser)
- // - SpellCheckHostChromeImpl::OnDictionariesInitialized (Browser)
- // - SpellcheckProvider::OnRespondInitializeDictionaries (Renderer)
- GetSpellCheckHost().InitializeDictionaries(
- base::BindOnce(&SpellCheckProvider::OnRespondInitializeDictionaries,
- weak_factory_.GetWeakPtr(), text));
- return;
- }
- #endif // BUILDFLAG(IS_WIN)
- RequestTextCheckingFromBrowser(text);
- }
- #endif // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- #if BUILDFLAG(USE_RENDERER_SPELLCHECKER)
- if (!spellcheck::UseBrowserSpellChecker()) {
- GetSpellCheckHost().CallSpellingService(
- text,
- base::BindOnce(&SpellCheckProvider::OnRespondSpellingService,
- weak_factory_.GetWeakPtr(), last_identifier_, text));
- }
- #endif // BUILDFLAG(USE_RENDERER_SPELLCHECKER)
- }
- #if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- void SpellCheckProvider::RequestTextCheckingFromBrowser(
- const std::u16string& text) {
- DCHECK(spellcheck::UseBrowserSpellChecker());
- #if BUILDFLAG(IS_WIN)
- // Determine whether a hybrid check is needed.
- bool use_hunspell = spellcheck_->EnabledLanguageCount() > 0;
- bool use_native =
- spellcheck_->EnabledLanguageCount() != spellcheck_->LanguageCount();
- if (!use_hunspell && !use_native) {
- OnRespondTextCheck(last_identifier_, text, /*results=*/{});
- return;
- }
- if (!use_native) {
- // No language can be handled by the native spell checker. Use the regular
- // Hunspell code path.
- GetSpellCheckHost().CallSpellingService(
- text,
- base::BindOnce(&SpellCheckProvider::OnRespondSpellingService,
- weak_factory_.GetWeakPtr(), last_identifier_, text));
- return;
- }
- // Some languages can be handled by the native spell checker. Use the
- // regular browser spell check code path. If hybrid spell check is
- // required (i.e. some locales must be checked by Hunspell), misspellings
- // from the native spell checker will be double-checked with Hunspell in
- // the |OnRespondTextCheck| callback.
- hybrid_requests_info_[last_identifier_] = {/*used_hunspell=*/use_hunspell,
- /*used_native=*/use_native,
- base::TimeTicks::Now()};
- #endif // BUILDFLAG(IS_WIN)
- // Text check (unified request for grammar and spell check) is only
- // available for browser process, so we ask the system spellchecker
- // over mojo or return an empty result if the checker is not available.
- GetSpellCheckHost().RequestTextCheck(
- text, routing_id(),
- base::BindOnce(&SpellCheckProvider::OnRespondTextCheck,
- weak_factory_.GetWeakPtr(), last_identifier_, text));
- }
- #if BUILDFLAG(IS_WIN)
- void SpellCheckProvider::OnRespondInitializeDictionaries(
- const std::u16string& text,
- std::vector<spellcheck::mojom::SpellCheckBDictLanguagePtr> dictionaries,
- const std::vector<std::string>& custom_words,
- bool enable) {
- DCHECK(!dictionaries_loaded_);
- dictionaries_loaded_ = true;
- // Because the SpellChecker and SpellCheckHost mojo interfaces use different
- // channels, there is no guarantee that the SpellChecker::Initialize response
- // will be received before the SpellCheckHost::InitializeDictionaries
- // callback. If the order is reversed, no spellcheck will be performed since
- // the renderer side thinks there are no dictionaries available. Ensure that
- // the SpellChecker is initialized before performing a spellcheck.
- spellcheck_->Initialize(std::move(dictionaries), custom_words, enable);
- RequestTextCheckingFromBrowser(text);
- }
- #endif // BUILDFLAG(IS_WIN)
- #endif // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- void SpellCheckProvider::FocusedElementChanged(
- const blink::WebElement& unused) {
- #if BUILDFLAG(IS_ANDROID)
- if (!spell_check_host_.is_bound())
- return;
- WebLocalFrame* frame = render_frame()->GetWebFrame();
- WebElement element = frame->GetDocument().IsNull()
- ? WebElement()
- : frame->GetDocument().FocusedElement();
- bool enabled = !element.IsNull() && element.IsEditable();
- if (!enabled)
- GetSpellCheckHost().DisconnectSessionBridge();
- #endif // BUILDFLAG(IS_ANDROID)
- }
- bool SpellCheckProvider::IsSpellCheckingEnabled() const {
- return spellcheck_->IsSpellcheckEnabled();
- }
- void SpellCheckProvider::CheckSpelling(
- const WebString& text,
- size_t& offset,
- size_t& length,
- blink::WebVector<blink::WebString>* optional_suggestions) {
- std::u16string word = text.Utf16();
- const int kWordStart = 0;
- if (optional_suggestions) {
- #if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- base::TimeTicks suggestions_start = base::TimeTicks::Now();
- #endif // BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- // Retrieve suggestions from Hunspell. Windows platform spellchecker
- // suggestions are retrieved in SpellingMenuObserver::InitMenu on the
- // browser process side to avoid a blocking IPC.
- spellcheck::PerLanguageSuggestions per_language_suggestions;
- spellcheck_->SpellCheckWord(word.c_str(), kWordStart, word.size(),
- routing_id(), &offset, &length,
- &per_language_suggestions);
- #if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- spellcheck_renderer_metrics::RecordHunspellSuggestionDuration(
- base::TimeTicks::Now() - suggestions_start);
- #endif // BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- std::vector<std::u16string> suggestions;
- spellcheck::FillSuggestions(per_language_suggestions, &suggestions);
- WebVector<WebString> web_suggestions(suggestions.size());
- std::transform(
- suggestions.begin(), suggestions.end(), web_suggestions.begin(),
- [](const std::u16string& s) { return WebString::FromUTF16(s); });
- *optional_suggestions = web_suggestions;
- spellcheck_renderer_metrics::RecordCheckedTextLengthWithSuggestions(
- base::saturated_cast<int>(word.size()));
- } else {
- spellcheck_->SpellCheckWord(word.c_str(), kWordStart, word.size(),
- routing_id(), &offset, &length,
- /* optional suggestions vector */ nullptr);
- spellcheck_renderer_metrics::RecordCheckedTextLengthNoSuggestions(
- base::saturated_cast<int>(word.size()));
- // If optional_suggestions is not requested, the API is called
- // for marking. So we use this for counting markable words.
- GetSpellCheckHost().NotifyChecked(word, 0 < length);
- }
- }
- void SpellCheckProvider::RequestCheckingOfText(
- const WebString& text,
- std::unique_ptr<WebTextCheckingCompletion> completion) {
- RequestTextChecking(text.Utf16(), std::move(completion));
- spellcheck_renderer_metrics::RecordAsyncCheckedTextLength(
- base::saturated_cast<int>(text.length()));
- }
- #if BUILDFLAG(USE_RENDERER_SPELLCHECKER)
- void SpellCheckProvider::OnRespondSpellingService(
- int identifier,
- const std::u16string& line,
- bool success,
- const std::vector<SpellCheckResult>& results) {
- if (!text_check_completions_.Lookup(identifier))
- return;
- std::unique_ptr<WebTextCheckingCompletion> completion(
- text_check_completions_.Replace(identifier, nullptr));
- text_check_completions_.Remove(identifier);
- // If |success| is false, we use local spellcheck as a fallback.
- if (!success) {
- spellcheck_->RequestTextChecking(line, std::move(completion));
- return;
- }
- // Double-check the returned spellchecking results with Hunspell to visualize
- // the differences between ours and the enhanced spell checker.
- blink::WebVector<blink::WebTextCheckingResult> textcheck_results;
- spellcheck_->CreateTextCheckingResults(SpellCheck::USE_HUNSPELL_FOR_GRAMMAR,
- /*line_offset=*/0, line, results,
- &textcheck_results);
- completion->DidFinishCheckingText(textcheck_results);
- // Cache the request and the converted results.
- last_request_ = line;
- last_results_.Swap(textcheck_results);
- }
- #endif
- bool SpellCheckProvider::HasWordCharacters(const std::u16string& text,
- size_t index) const {
- const char16_t* data = text.data();
- size_t length = text.length();
- while (index < length) {
- uint32_t code = 0;
- U16_NEXT(data, index, length, code);
- UErrorCode error = U_ZERO_ERROR;
- if (uscript_getScript(code, &error) != USCRIPT_COMMON)
- return true;
- }
- return false;
- }
- #if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- void SpellCheckProvider::OnRespondTextCheck(
- int identifier,
- const std::u16string& line,
- const std::vector<SpellCheckResult>& results) {
- DCHECK(spellcheck_);
- if (!text_check_completions_.Lookup(identifier))
- return;
- std::unique_ptr<WebTextCheckingCompletion> completion(
- text_check_completions_.Replace(identifier, nullptr));
- text_check_completions_.Remove(identifier);
- blink::WebVector<blink::WebTextCheckingResult> textcheck_results;
- SpellCheck::ResultFilter result_filter = SpellCheck::DO_NOT_MODIFY;
- #if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- const auto& request_info = hybrid_requests_info_.find(identifier);
- if (spellcheck::UseBrowserSpellChecker() &&
- request_info != hybrid_requests_info_.end() &&
- request_info->second.used_hunspell && request_info->second.used_native) {
- // Not all locales could be checked by the native spell checker. Verify each
- // mistake against Hunspell in the locales that weren't checked.
- result_filter = SpellCheck::USE_HUNSPELL_FOR_HYBRID_CHECK;
- }
- #endif // BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- spellcheck_->CreateTextCheckingResults(result_filter,
- /*line_offset=*/0, line, results,
- &textcheck_results);
- #if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- if (request_info != hybrid_requests_info_.end()) {
- spellcheck_renderer_metrics::RecordSpellcheckDuration(
- base::TimeTicks::Now() - request_info->second.request_start_ticks,
- request_info->second.used_hunspell, request_info->second.used_native);
- hybrid_requests_info_.erase(request_info);
- }
- #endif // BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- completion->DidFinishCheckingText(textcheck_results);
- // Cache the request and the converted results.
- last_request_ = line;
- last_results_.Swap(textcheck_results);
- }
- #endif // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
- bool SpellCheckProvider::SatisfyRequestFromCache(
- const std::u16string& text,
- WebTextCheckingCompletion* completion) {
- size_t last_length = last_request_.length();
- if (!last_length)
- return false;
- // Send back the |last_results_| if the |last_request_| is a substring of
- // |text| and |text| does not have more words to check. Provider cannot cancel
- // the spellcheck request here, because WebKit might have discarded the
- // previous spellcheck results and erased the spelling markers in response to
- // the user editing the text.
- std::u16string request(text);
- size_t text_length = request.length();
- if (text_length >= last_length &&
- !request.compare(0, last_length, last_request_)) {
- if (text_length == last_length || !HasWordCharacters(text, last_length)) {
- completion->DidFinishCheckingText(last_results_);
- return true;
- }
- }
- // Create a subset of the cached results and return it if the given text is a
- // substring of the cached text.
- if (text_length < last_length &&
- !last_request_.compare(0, text_length, request)) {
- size_t result_size = 0;
- for (size_t i = 0; i < last_results_.size(); ++i) {
- size_t start = last_results_[i].location;
- size_t end = start + last_results_[i].length;
- if (start <= text_length && end <= text_length)
- ++result_size;
- }
- blink::WebVector<blink::WebTextCheckingResult> results(last_results_.data(),
- result_size);
- completion->DidFinishCheckingText(results);
- return true;
- }
- return false;
- }
- void SpellCheckProvider::OnDestruct() {
- delete this;
- }
|