| // 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 "chrome/browser/spellchecker/spell_check_host_chrome_impl.h" |
| |
| #include "base/barrier_closure.h" |
| #include "base/bind.h" |
| #include "base/task/post_task.h" |
| #include "chrome/browser/spellchecker/spellcheck_factory.h" |
| #include "chrome/browser/spellchecker/spellcheck_service.h" |
| #include "components/spellcheck/browser/spellcheck_platform.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "services/service_manager/public/cpp/identity.h" |
| |
| using content::BrowserThread; |
| using content::BrowserContext; |
| |
| namespace { |
| |
| bool CompareLocation(const SpellCheckResult& r1, const SpellCheckResult& r2) { |
| return r1.location < r2.location; |
| } |
| |
| // Adjusts remote_results by examining local_results. Any result that's both |
| // local and remote stays type SPELLING, all others are flagged GRAMMAR. |
| // (This is needed to force gray underline for remote-only results.) |
| void CombineResults(std::vector<SpellCheckResult>* remote_results, |
| const std::vector<SpellCheckResult>& local_results) { |
| std::vector<SpellCheckResult>::const_iterator local_iter( |
| local_results.begin()); |
| std::vector<SpellCheckResult>::iterator remote_iter; |
| |
| for (remote_iter = remote_results->begin(); |
| remote_iter != remote_results->end(); ++remote_iter) { |
| // Discard all local results occurring before remote result. |
| while (local_iter != local_results.end() && |
| local_iter->location < remote_iter->location) { |
| local_iter++; |
| } |
| |
| // Unless local and remote result coincide, result is GRAMMAR. |
| remote_iter->decoration = SpellCheckResult::GRAMMAR; |
| if (local_iter != local_results.end() && |
| local_iter->location == remote_iter->location && |
| local_iter->length == remote_iter->length) { |
| remote_iter->decoration = SpellCheckResult::SPELLING; |
| } |
| } |
| } |
| |
| } // namespace |
| |
| // SpellingRequest is owned by SpellCheckHostChromeImpl. |
| class SpellingRequest { |
| public: |
| using RequestTextCheckCallback = |
| spellcheck::mojom::SpellCheckHost::RequestTextCheckCallback; |
| using DestructionCallback = base::OnceCallback<void(SpellingRequest*)>; |
| |
| SpellingRequest(SpellingServiceClient* client, |
| const base::string16& text, |
| const service_manager::Identity& renderer_identity, |
| int document_tag, |
| RequestTextCheckCallback callback, |
| DestructionCallback destruction_callback); |
| |
| private: |
| // Request server-side checking for |text_|. |
| void RequestRemoteCheck(SpellingServiceClient* client, |
| const service_manager::Identity& renderer_identity); |
| |
| // Request a check for |text_| from local spell checker. |
| void RequestLocalCheck(int document_tag); |
| |
| // Check if all pending requests are done, send reply to render process if so. |
| void OnCheckCompleted(); |
| |
| // Called when server-side checking is complete. Must be called on UI thread. |
| void OnRemoteCheckCompleted(bool success, |
| const base::string16& text, |
| const std::vector<SpellCheckResult>& results); |
| |
| // Called when local checking is complete. Must be called on UI thread. |
| void OnLocalCheckCompleted(const std::vector<SpellCheckResult>& results); |
| |
| // Forwards the results back to UI thread when local checking completes. |
| static void OnLocalCheckCompletedOnAnyThread( |
| base::WeakPtr<SpellingRequest> request, |
| const std::vector<SpellCheckResult>& results); |
| |
| std::vector<SpellCheckResult> local_results_; |
| std::vector<SpellCheckResult> remote_results_; |
| |
| // Barrier closure for completion of both remote and local check. |
| base::RepeatingClosure completion_barrier_; |
| bool remote_success_; |
| |
| // The string to be spell-checked. |
| base::string16 text_; |
| |
| // Callback to send the results to renderer. |
| RequestTextCheckCallback callback_; |
| |
| // Callback to delete |this|. Called on |this| after everything is done. |
| DestructionCallback destruction_callback_; |
| |
| base::WeakPtrFactory<SpellingRequest> weak_factory_; |
| }; |
| |
| SpellingRequest::SpellingRequest( |
| SpellingServiceClient* client, |
| const base::string16& text, |
| const service_manager::Identity& renderer_identity, |
| int document_tag, |
| RequestTextCheckCallback callback, |
| DestructionCallback destruction_callback) |
| : remote_success_(false), |
| text_(text), |
| callback_(std::move(callback)), |
| destruction_callback_(std::move(destruction_callback)), |
| weak_factory_(this) { |
| DCHECK(!text_.empty()); |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| completion_barrier_ = |
| BarrierClosure(2, base::BindOnce(&SpellingRequest::OnCheckCompleted, |
| weak_factory_.GetWeakPtr())); |
| RequestRemoteCheck(client, renderer_identity); |
| RequestLocalCheck(document_tag); |
| } |
| |
| void SpellingRequest::RequestRemoteCheck( |
| SpellingServiceClient* client, |
| const service_manager::Identity& renderer_identity) { |
| BrowserContext* context = |
| content::BrowserContext::GetBrowserContextForServiceUserId( |
| renderer_identity.user_id()); |
| |
| // |this| may be gone at callback invocation if the owner has been removed. |
| client->RequestTextCheck( |
| context, SpellingServiceClient::SPELLCHECK, text_, |
| base::BindOnce(&SpellingRequest::OnRemoteCheckCompleted, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void SpellingRequest::RequestLocalCheck(int document_tag) { |
| // |this| may be gone at callback invocation if the owner has been removed. |
| spellcheck_platform::RequestTextCheck( |
| document_tag, text_, |
| base::BindOnce(&SpellingRequest::OnLocalCheckCompletedOnAnyThread, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void SpellingRequest::OnCheckCompleted() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| std::vector<SpellCheckResult>* check_results = &local_results_; |
| if (remote_success_) { |
| std::sort(remote_results_.begin(), remote_results_.end(), CompareLocation); |
| std::sort(local_results_.begin(), local_results_.end(), CompareLocation); |
| CombineResults(&remote_results_, local_results_); |
| check_results = &remote_results_; |
| } |
| |
| std::move(callback_).Run(*check_results); |
| |
| std::move(destruction_callback_).Run(this); |
| |
| // |destruction_callback_| removes |this|. No more operations allowed. |
| } |
| |
| void SpellingRequest::OnRemoteCheckCompleted( |
| bool success, |
| const base::string16& text, |
| const std::vector<SpellCheckResult>& results) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| remote_success_ = success; |
| remote_results_ = results; |
| completion_barrier_.Run(); |
| } |
| |
| // static |
| void SpellingRequest::OnLocalCheckCompletedOnAnyThread( |
| base::WeakPtr<SpellingRequest> request, |
| const std::vector<SpellCheckResult>& results) { |
| // Local checking can happen on any thread - don't DCHECK thread. |
| base::PostTaskWithTraits( |
| FROM_HERE, {content::BrowserThread::UI}, |
| base::BindOnce(&SpellingRequest::OnLocalCheckCompleted, request, |
| results)); |
| } |
| |
| void SpellingRequest::OnLocalCheckCompleted( |
| const std::vector<SpellCheckResult>& results) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| local_results_ = results; |
| completion_barrier_.Run(); |
| } |
| |
| SpellCheckHostChromeImpl::SpellCheckHostChromeImpl( |
| const service_manager::Identity& renderer_identity) |
| : renderer_identity_(renderer_identity), weak_factory_(this) {} |
| |
| SpellCheckHostChromeImpl::~SpellCheckHostChromeImpl() = default; |
| |
| // static |
| void SpellCheckHostChromeImpl::CombineResultsForTesting( |
| std::vector<SpellCheckResult>* remote_results, |
| const std::vector<SpellCheckResult>& local_results) { |
| CombineResults(remote_results, local_results); |
| } |
| |
| void SpellCheckHostChromeImpl::CheckSpelling(const base::string16& word, |
| int route_id, |
| CheckSpellingCallback callback) { |
| bool correct = |
| spellcheck_platform::CheckSpelling(word, ToDocumentTag(route_id)); |
| std::move(callback).Run(correct); |
| } |
| |
| void SpellCheckHostChromeImpl::FillSuggestionList( |
| const base::string16& word, |
| FillSuggestionListCallback callback) { |
| std::vector<base::string16> suggestions; |
| spellcheck_platform::FillSuggestionList(word, &suggestions); |
| std::move(callback).Run(suggestions); |
| } |
| |
| void SpellCheckHostChromeImpl::RequestTextCheck( |
| const base::string16& text, |
| int route_id, |
| RequestTextCheckCallback callback) { |
| DCHECK(!text.empty()); |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Initialize the spellcheck service if needed. The service will send the |
| // language code for text breaking to the renderer. (Text breaking is required |
| // for the context menu to show spelling suggestions.) Initialization must |
| // happen on UI thread. |
| GetSpellcheckService(); |
| |
| // |SpellingRequest| self-destructs on completion. |
| // OK to store unretained |this| in a |SpellingRequest| owned by |this|. |
| requests_.insert(std::make_unique<SpellingRequest>( |
| &client_, text, renderer_identity_, ToDocumentTag(route_id), |
| std::move(callback), |
| base::BindOnce(&SpellCheckHostChromeImpl::OnRequestFinished, |
| base::Unretained(this)))); |
| } |
| |
| void SpellCheckHostChromeImpl::OnRequestFinished(SpellingRequest* request) { |
| auto iterator = requests_.find(request); |
| requests_.erase(iterator); |
| } |
| |
| int SpellCheckHostChromeImpl::ToDocumentTag(int route_id) { |
| if (!tag_map_.count(route_id)) |
| tag_map_[route_id] = spellcheck_platform::GetDocumentTag(); |
| return tag_map_[route_id]; |
| } |
| |
| // TODO(groby): We are currently not notified of retired tags. We need |
| // to track destruction of RenderViewHosts on the browser process side |
| // to update our mappings when a document goes away. |
| void SpellCheckHostChromeImpl::RetireDocumentTag(int route_id) { |
| spellcheck_platform::CloseDocumentWithTag(ToDocumentTag(route_id)); |
| tag_map_.erase(route_id); |
| } |