chromium/chrome/browser/spellchecker/spelling_request.cc

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