Commit 5a85be43 authored by Jing Wang's avatar Jing Wang Committed by Chromium LUCI CQ

Integrate grammar check into spell check component.

Implemented a first version of grammar check client, which just throw
the entire requested text to grammar service and if it returns with any
suggestion other than the original text, we will mark the whole sentence
and show the suggestion in context menu.

Design doc of the feature: go/cros-grammar
Screenshot of the feature:
https://screenshot.googleplex.com/BqkRhx6BYC2LPiF

This feature is ChromeOS only and is hidden behind a by-default disabled
flag. When the flag is enabled, and when enhanced spell check is
enabled, we will call grammar check when enhanced spell checker detects
no spell errors and display grammar error if we find any.

It is not ideal to always check the entire input and mark the whole
sentence. The feature is to be improved in the follow-up CLs, but I
think this is good enough as a first version.

Bug: 1132699
Test: tested on DUT
Change-Id: Ie2a8ca99e2f6910e9eef6bb3c7eb5da980a4db92
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2585245
Commit-Queue: Jing Wang <jiwan@chromium.org>
Reviewed-by: default avatarTim Volodine <timvolodine@chromium.org>
Reviewed-by: default avatarDarren Shen <shend@chromium.org>
Cr-Commit-Position: refs/heads/master@{#840062}
parent 3324a056
......@@ -304,6 +304,8 @@ source_set("chromeos") {
"//components/session_manager/core",
"//components/signin/public/identity_manager",
"//components/signin/public/webdata",
"//components/spellcheck/browser:browser",
"//components/spellcheck/common:spellcheck_result",
"//components/storage_monitor",
"//components/strings",
"//components/sync",
......@@ -315,6 +317,7 @@ source_set("chromeos") {
"//components/ukm/content",
"//components/unified_consent",
"//components/user_manager",
"//components/user_prefs:user_prefs",
"//components/vector_icons",
"//media/capture/video/chromeos/public:public",
......@@ -1499,6 +1502,8 @@ source_set("chromeos") {
"input_method/component_extension_ime_manager_delegate_impl.h",
"input_method/emoji_suggester.cc",
"input_method/emoji_suggester.h",
"input_method/grammar_service_client.cc",
"input_method/grammar_service_client.h",
"input_method/ime_service_connector.cc",
"input_method/ime_service_connector.h",
"input_method/input_host_helper.cc",
......@@ -3624,6 +3629,7 @@ source_set("unit_tests") {
"input_method/assistive_window_controller_unittest.cc",
"input_method/autocorrect_manager_unittest.cc",
"input_method/emoji_suggester_unittest.cc",
"input_method/grammar_service_client_unittest.cc",
"input_method/input_method_configuration_unittest.cc",
"input_method/input_method_engine_unittest.cc",
"input_method/input_method_manager_impl_unittest.cc",
......
// Copyright (c) 2020 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/chromeos/input_method/grammar_service_client.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/services/machine_learning/public/cpp/service_connection.h"
#include "components/prefs/pref_service.h"
#include "components/spellcheck/browser/pref_names.h"
#include "components/spellcheck/common/spellcheck_result.h"
#include "components/user_prefs/user_prefs.h"
namespace chromeos {
namespace {
using chromeos::machine_learning::mojom::GrammarCheckerQuery;
using chromeos::machine_learning::mojom::GrammarCheckerResult;
using chromeos::machine_learning::mojom::GrammarCheckerResultPtr;
} // namespace
GrammarServiceClient::GrammarServiceClient() {
chromeos::machine_learning::ServiceConnection::GetInstance()
->LoadGrammarChecker(
grammar_checker_.BindNewPipeAndPassReceiver(),
base::BindOnce(
[](bool* grammar_checker_loaded_,
chromeos::machine_learning::mojom::LoadModelResult result) {
*grammar_checker_loaded_ =
result ==
chromeos::machine_learning::mojom::LoadModelResult::OK;
},
&grammar_checker_loaded_));
}
GrammarServiceClient::~GrammarServiceClient() = default;
bool GrammarServiceClient::RequestTextCheck(
Profile* profile,
const base::string16& text,
TextCheckCompleteCallback callback) const {
if (!profile || !IsAvailable(profile)) {
std::move(callback).Run(false, {});
return false;
}
auto query = GrammarCheckerQuery::New();
query->text = base::UTF16ToUTF8(text);
query->language = "en-US";
grammar_checker_->Check(
std::move(query),
base::BindOnce(&GrammarServiceClient::ParseGrammarCheckerResult,
base::Unretained(this), text, std::move(callback)));
return true;
}
void GrammarServiceClient::ParseGrammarCheckerResult(
const base::string16& text,
TextCheckCompleteCallback callback,
chromeos::machine_learning::mojom::GrammarCheckerResultPtr result) const {
if (result->status == GrammarCheckerResult::Status::OK &&
!result->candidates.empty()) {
std::vector<SpellCheckResult> grammar_results;
grammar_results.emplace_back(
SpellCheckResult::GRAMMAR, 0, text.size(),
base::UTF8ToUTF16(result->candidates.at(0)->text));
std::move(callback).Run(true, grammar_results);
}
}
bool GrammarServiceClient::IsAvailable(Profile* profile) const {
const PrefService* pref = profile->GetPrefs();
DCHECK(pref);
// If prefs don't allow spell checking, if enhanced spell check is disabled,
// or if the profile is off the record, the grammar service should be
// unavailable.
if (!pref->GetBoolean(spellcheck::prefs::kSpellCheckEnable) ||
!pref->GetBoolean(spellcheck::prefs::kSpellCheckUseSpellingService) ||
profile->IsOffTheRecord())
return false;
return grammar_checker_loaded_ && grammar_checker_.is_bound();
}
} // namespace chromeos
// Copyright 2020 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.
#ifndef CHROME_BROWSER_CHROMEOS_INPUT_METHOD_GRAMMAR_SERVICE_CLIENT_H_
#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_GRAMMAR_SERVICE_CLIENT_H_
#include <vector>
#include "base/callback.h"
#include "base/strings/string16.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/services/machine_learning/public/mojom/grammar_checker.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
struct SpellCheckResult;
namespace chromeos {
// A class that sends grammar check request to ML service, parses the reponse
// and calls a provided callback method. A simple usage is creating a
// GrammarServiceClient and call its RequestTextCheck method as listed in the
// following snippet.
//
// class MyClient {
// public:
// MyClient();
// virtual ~MyClient();
//
// void OnTextCheckComplete(
// int tag,
// bool success,
// const std::vector<SpellCheckResult>& results) {
// ...
// }
//
// void MyTextCheck(Profile* profile, const base::string16& text) {
// client_.reset(new GrammarServiceClient);
// client_->RequestTextCheck(profile, 0, text,
// base::BindOnce(&MyClient::OnTextCheckComplete,
// base::Unretained(this));
// }
// private:
// std::unique_ptr<GrammarServiceClient> client_;
// };
//
class GrammarServiceClient {
public:
GrammarServiceClient();
~GrammarServiceClient();
using TextCheckCompleteCallback = base::OnceCallback<void(
bool /* success */,
const std::vector<SpellCheckResult>& /* results */)>;
// Sends grammar check request to ML service, parses the reponse
// and calls a provided callback method.
bool RequestTextCheck(Profile* profile,
const base::string16& text,
TextCheckCompleteCallback callback) const;
private:
// Parse the result returned from grammar check service.
void ParseGrammarCheckerResult(
const base::string16& text,
TextCheckCompleteCallback callback,
chromeos::machine_learning::mojom::GrammarCheckerResultPtr result) const;
// Returns whether the grammar service is enabled by user settings and the
// service is ready to use.
bool IsAvailable(Profile* profile) const;
mojo::Remote<chromeos::machine_learning::mojom::GrammarChecker>
grammar_checker_;
bool grammar_checker_loaded_ = false;
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_GRAMMAR_SERVICE_CLIENT_H_
// Copyright 2020 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/chromeos/input_method/grammar_service_client.h"
#include <vector>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/services/machine_learning/public/cpp/fake_service_connection.h"
#include "chromeos/services/machine_learning/public/mojom/grammar_checker.mojom.h"
#include "components/prefs/pref_service.h"
#include "components/spellcheck/browser/pref_names.h"
#include "components/spellcheck/common/spellcheck_result.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace {
class GrammarServiceClientTest : public testing::Test {
public:
GrammarServiceClientTest() = default;
private:
content::BrowserTaskEnvironment task_environment_;
};
TEST_F(GrammarServiceClientTest, ReturnsEmptyResultWhenSpellCheckIsDiabled) {
machine_learning::FakeServiceConnectionImpl fake_service_connection;
machine_learning::ServiceConnection::UseFakeServiceConnectionForTesting(
&fake_service_connection);
auto profile = std::make_unique<TestingProfile>();
profile->GetPrefs()->SetBoolean(spellcheck::prefs::kSpellCheckEnable, false);
profile->GetPrefs()->SetBoolean(
spellcheck::prefs::kSpellCheckUseSpellingService, false);
GrammarServiceClient client;
base::RunLoop().RunUntilIdle();
client.RequestTextCheck(
profile.get(), base::UTF8ToUTF16("cat"),
base::BindOnce(
[](bool success, const std::vector<SpellCheckResult>& results) {
EXPECT_FALSE(success);
EXPECT_TRUE(results.empty());
}));
base::RunLoop().RunUntilIdle();
}
TEST_F(GrammarServiceClientTest, ParsesResults) {
machine_learning::FakeServiceConnectionImpl fake_service_connection;
machine_learning::ServiceConnection::UseFakeServiceConnectionForTesting(
&fake_service_connection);
auto profile = std::make_unique<TestingProfile>();
profile->GetPrefs()->SetBoolean(spellcheck::prefs::kSpellCheckEnable, true);
profile->GetPrefs()->SetBoolean(
spellcheck::prefs::kSpellCheckUseSpellingService, true);
// Construct fake output
const base::string16 input_text = base::UTF8ToUTF16("fake input");
const base::string16 expected_output = base::UTF8ToUTF16("fake output");
machine_learning::mojom::GrammarCheckerResultPtr result =
machine_learning::mojom::GrammarCheckerResult::New();
result->status = machine_learning::mojom::GrammarCheckerResult::Status::OK;
machine_learning::mojom::GrammarCheckerCandidatePtr candidate =
machine_learning::mojom::GrammarCheckerCandidate::New();
candidate->text = base::UTF16ToUTF8(expected_output);
candidate->score = 0.5f;
result->candidates.emplace_back(std::move(candidate));
fake_service_connection.SetOutputGrammarCheckerResult(result);
GrammarServiceClient client;
base::RunLoop().RunUntilIdle();
client.RequestTextCheck(
profile.get(), input_text,
base::BindOnce(
[](const base::string16& text, const base::string16& expected_output,
bool success, const std::vector<SpellCheckResult>& results) {
EXPECT_TRUE(success);
ASSERT_EQ(results.size(), 1U);
EXPECT_EQ(results[0].decoration, SpellCheckResult::GRAMMAR);
EXPECT_EQ(results[0].location, 0);
EXPECT_EQ(results[0].length, static_cast<int>(text.size()));
ASSERT_EQ(results[0].replacements.size(), 1U);
EXPECT_EQ(results[0].replacements[0], expected_output);
},
input_text, expected_output));
base::RunLoop().RunUntilIdle();
}
} // namespace
} // namespace chromeos
......@@ -8,6 +8,7 @@
#include "base/no_destructor.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/spellchecker/spellcheck_custom_dictionary.h"
#include "chrome/browser/spellchecker/spellcheck_factory.h"
#include "chrome/browser/spellchecker/spellcheck_service.h"
......@@ -24,6 +25,10 @@
#include "chrome/browser/spellchecker/spelling_request.h"
#endif
#if defined(OS_CHROMEOS)
#include "chromeos/constants/chromeos_features.h"
#endif
namespace {
SpellCheckHostChromeImpl::Binder& GetSpellCheckHostBinderOverride() {
......@@ -134,9 +139,40 @@ void SpellCheckHostChromeImpl::CallSpellingServiceDone(
base::UTF16ToUTF8(text), *spellcheck->GetCustomDictionary(),
service_results);
#if defined(OS_CHROMEOS)
if (base::FeatureList::IsEnabled(chromeos::features::kOnDeviceGrammarCheck) &&
results.empty()) {
auto* host = content::RenderProcessHost::FromID(render_process_id_);
if (!host) {
std::move(callback).Run(false, std::vector<SpellCheckResult>());
return;
}
grammar_client_.RequestTextCheck(
Profile::FromBrowserContext(host->GetBrowserContext()), text,
base::BindOnce(&SpellCheckHostChromeImpl::CallGrammarServiceDone,
weak_factory_.GetWeakPtr(), std::move(callback)));
return;
}
#endif
std::move(callback).Run(success, results);
}
#if defined(OS_CHROMEOS)
void SpellCheckHostChromeImpl::CallGrammarServiceDone(
CallSpellingServiceCallback callback,
bool success,
const std::vector<SpellCheckResult>& results) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SpellcheckService* spellcheck = GetSpellcheckService();
if (!spellcheck) { // Teardown.
std::move(callback).Run(false, std::vector<SpellCheckResult>());
return;
}
std::move(callback).Run(success, results);
}
#endif
// static
std::vector<SpellCheckResult> SpellCheckHostChromeImpl::FilterCustomWordResults(
const std::string& text,
......
......@@ -16,6 +16,10 @@
#include "components/spellcheck/browser/spelling_service_client.h"
#endif
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/input_method/grammar_service_client.h"
#endif
class SpellcheckCustomDictionary;
class SpellcheckService;
class SpellingRequest;
......@@ -127,6 +131,17 @@ class SpellCheckHostChromeImpl : public SpellCheckHostImpl {
SpellingServiceClient client_;
#endif
#if defined(OS_CHROMEOS)
// Invoked when the on-device grammar service has finished checking the
// text of a CallSpellingService request.
void CallGrammarServiceDone(
CallSpellingServiceCallback callback,
bool success,
const std::vector<SpellCheckResult>& service_results) const;
chromeos::GrammarServiceClient grammar_client_;
#endif
#if BUILDFLAG(USE_RENDERER_SPELLCHECKER)
base::WeakPtrFactory<SpellCheckHostChromeImpl> weak_factory_{this};
#endif
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment