// 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/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::CreateForTest( const std::u16string& text, RequestTextCheckCallback callback, DestructionCallback destruction_callback, base::RepeatingClosure completion_barrier) { return base::WrapUnique(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* remote_results, const std::vector& local_results) { std::vector::const_iterator local_iter( local_results.begin()); std::vector::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* 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& 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::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 request, const std::vector& 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& results) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); local_results_ = results; completion_barrier_.Run(); }