// 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();
}