| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/spellchecker/spelling_request.h" |
| |
| #include "base/barrier_closure.h" |
| #include "base/functional/bind.h" |
| #include "base/i18n/char_iterator.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/spellchecker/spellcheck_custom_dictionary.h" |
| #include "chrome/browser/spellchecker/spellcheck_factory.h" |
| #include "components/spellcheck/browser/spellcheck_platform.h" |
| #include "components/spellcheck/common/spellcheck_features.h" |
| #include "components/spellcheck/common/spellcheck_result.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" |
| |
| namespace { |
| |
| bool CompareLocation(const SpellCheckResult& r1, const SpellCheckResult& r2) { |
| return r1.location < r2.location; |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<SpellingRequest> SpellingRequest::CreateForTest( |
| const std::u16string& text, |
| RequestTextCheckCallback callback, |
| DestructionCallback destruction_callback, |
| base::RepeatingClosure completion_barrier) { |
| return base::WrapUnique<SpellingRequest>(new SpellingRequest( |
| text, std::move(callback), std::move(destruction_callback), |
| std::move(completion_barrier))); |
| } |
| |
| SpellingRequest::SpellingRequest(PlatformSpellChecker* platform_spell_checker, |
| SpellingServiceClient* client, |
| const std::u16string& text, |
| int render_process_id, |
| int document_tag, |
| RequestTextCheckCallback callback, |
| DestructionCallback destruction_callback) |
| : remote_success_(false), |
| text_(text), |
| callback_(std::move(callback)), |
| destruction_callback_(std::move(destruction_callback)) { |
| DCHECK(!text_.empty()); |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| completion_barrier_ = |
| BarrierClosure(2, base::BindOnce(&SpellingRequest::OnCheckCompleted, |
| weak_factory_.GetWeakPtr())); |
| RequestRemoteCheck(client, render_process_id); |
| RequestLocalCheck(platform_spell_checker, document_tag); |
| } |
| |
| SpellingRequest::SpellingRequest(const std::u16string& text, |
| RequestTextCheckCallback callback, |
| DestructionCallback destruction_callback, |
| base::RepeatingClosure completion_barrier) |
| : completion_barrier_(std::move(completion_barrier)), |
| remote_success_(false), |
| text_(text), |
| callback_(std::move(callback)), |
| destruction_callback_(std::move(destruction_callback)) {} |
| |
| SpellingRequest::~SpellingRequest() = default; |
| |
| // static |
| void SpellingRequest::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++; |
| } |
| |
| remote_iter->spelling_service_used = true; |
| |
| // 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; |
| } |
| } |
| } |
| |
| void SpellingRequest::RequestRemoteCheck(SpellingServiceClient* client, |
| int render_process_id) { |
| auto* host = content::RenderProcessHost::FromID(render_process_id); |
| if (!host) |
| return; |
| |
| // |this| may be gone at callback invocation if the owner has been removed. |
| client->RequestTextCheck( |
| host->GetBrowserContext(), SpellingServiceClient::SPELLCHECK, text_, |
| base::BindOnce(&SpellingRequest::OnRemoteCheckCompleted, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void SpellingRequest::RequestLocalCheck( |
| PlatformSpellChecker* platform_spell_checker, |
| int document_tag) { |
| // |this| may be gone at callback invocation if the owner has been removed. |
| spellcheck_platform::RequestTextCheck( |
| platform_spell_checker, 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 std::u16string& text, |
| const std::vector<SpellCheckResult>& results) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| remote_success_ = success; |
| remote_results_ = results; |
| |
| if (results.size() > 0) { |
| // The spelling service uses "logical" character positions, whereas the |
| // Chromium spell check infrastructure uses positions based on code points, |
| // which causes mismatches when the text contains characters made of |
| // multiple code points (such as emojis). Use a UTF-16 char iterator on the |
| // text to replace the logical positions of the remote results with their |
| // corresponding code points position in the string. |
| std::sort(remote_results_.begin(), remote_results_.end(), CompareLocation); |
| base::i18n::UTF16CharIterator char_iter(text); |
| std::vector<SpellCheckResult>::iterator result_iter; |
| |
| for (result_iter = remote_results_.begin(); |
| result_iter != remote_results_.end(); ++result_iter) { |
| while (char_iter.char_offset() < result_iter->location) { |
| char_iter.Advance(); |
| } |
| |
| if (char_iter.char_offset() > result_iter->location) { |
| // This remote result has a logical position that somehow doesn't |
| // correspond to a code point boundary position in the string. This is |
| // undefined behavior, so leave the result as is. |
| continue; |
| } |
| |
| result_iter->location = char_iter.array_pos(); |
| |
| // Also fix the result's length. |
| base::i18n::UTF16CharIterator length_iter = |
| base::i18n::UTF16CharIterator::LowerBound(text, |
| char_iter.array_pos()); |
| int32_t initial_char_offset = length_iter.char_offset(); |
| |
| while (length_iter.char_offset() - initial_char_offset < |
| result_iter->length) { |
| length_iter.Advance(); |
| } |
| |
| if (length_iter.char_offset() - initial_char_offset > |
| result_iter->length) { |
| // The corrected position + logical length of this remote result does |
| // not match a code point boundary position in the string. This is |
| // undefined behavior, so leave the result as is. |
| continue; |
| } |
| |
| result_iter->length = length_iter.array_pos() - char_iter.array_pos(); |
| } |
| } |
| |
| 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. |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, 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(); |
| } |