Skip to content

Commit

Permalink
Quick Answers: Add spell checker in utility process
Browse files Browse the repository at this point in the history
This change adds a spell checker for Quick answers feature
in utility process. A follow up change will add browser side
implementation.

The spell checker is added because QA needs different set of
languages from spellcheck, and the two features are
controlled by different set of settings toggles.

Utility process is used because the third party Hunspell
library is unstable and we want to protect browser process.

DD: go/qa-spellcheck, go/qa-always-trigger

Bug: b/221967354
Test: Run existing tests
Change-Id: Ic92d1b5074a9456521ab8ccebbd0400a3f8fa50a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3528261
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: Josh Simmons <jds@google.com>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Commit-Queue: Yue Li <updowndota@chromium.org>
Cr-Commit-Position: refs/heads/main@{#984946}
  • Loading branch information
Yue Li authored and Chromium LUCI CQ committed Mar 24, 2022
1 parent e9e69a2 commit e378cf5
Show file tree
Hide file tree
Showing 12 changed files with 227 additions and 0 deletions.
2 changes: 2 additions & 0 deletions chrome/utility/BUILD.gn
Expand Up @@ -175,6 +175,8 @@ static_library("utility") {
"//chromeos/assistant:buildflags",
"//chromeos/components/local_search_service:local_search_service",
"//chromeos/components/local_search_service/public/mojom",
"//chromeos/components/quick_answers/public/cpp",
"//chromeos/components/quick_answers/public/mojom",
"//chromeos/services/tts",
"//chromeos/services/tts/public/mojom",
]
Expand Down
1 change: 1 addition & 0 deletions chrome/utility/DEPS
Expand Up @@ -30,6 +30,7 @@ include_rules = [
"+chromeos/assistant/buildflags.h",
"+chromeos/components/local_search_service/local_search_service.h",
"+chromeos/components/local_search_service/public/mojom",
"+chromeos/components/quick_answers/public",
"+chromeos/services/assistant",
"+chromeos/services/libassistant/libassistant_service.h",
"+chromeos/services/tts",
Expand Down
9 changes: 9 additions & 0 deletions chrome/utility/services.cc
Expand Up @@ -110,6 +110,8 @@
#include "chromeos/assistant/buildflags.h" // nogncheck
#include "chromeos/components/local_search_service/local_search_service.h"
#include "chromeos/components/local_search_service/public/mojom/local_search_service.mojom.h"
#include "chromeos/components/quick_answers/public/cpp/service/spell_check_service.h"
#include "chromeos/components/quick_answers/public/mojom/spell_check.mojom.h"
#include "chromeos/services/tts/public/mojom/tts_service.mojom.h"
#include "chromeos/services/tts/tts_service.h"

Expand Down Expand Up @@ -333,6 +335,12 @@ auto RunQuickPairService(
std::move(receiver));
}

auto RunQuickAnswersSpellCheckService(
mojo::PendingReceiver<quick_answers::mojom::SpellCheckService> receiver) {
return std::make_unique<quick_answers::SpellCheckService>(
std::move(receiver));
}

#if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
auto RunAssistantAudioDecoder(
mojo::PendingReceiver<
Expand Down Expand Up @@ -436,6 +444,7 @@ void RegisterMainThreadServices(mojo::ServiceFactory& services) {
services.Add(RunTtsService);
services.Add(RunLocalSearchService);
services.Add(RunQuickPairService);
services.Add(RunQuickAnswersSpellCheckService);
#if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
services.Add(RunAssistantAudioDecoder);
services.Add(RunLibassistantService);
Expand Down
1 change: 1 addition & 0 deletions chromeos/components/quick_answers/DEPS
Expand Up @@ -2,6 +2,7 @@ include_rules = [
"+ash/public",
"+components/language/core/browser",
"+services/data_decoder/public",
"+third_party/hunspell",
"+ui/base/l10n",
"+ui/base/resource/resource_bundle.h",
"+ui/color",
Expand Down
6 changes: 6 additions & 0 deletions chromeos/components/quick_answers/public/cpp/BUILD.gn
Expand Up @@ -8,14 +8,20 @@ source_set("cpp") {
"controller/quick_answers_controller.h",
"quick_answers_state.cc",
"quick_answers_state.h",
"service/spell_check_dictionary.cc",
"service/spell_check_dictionary.h",
"service/spell_check_service.cc",
"service/spell_check_service.h",
]

deps = [
"//base",
"//chromeos/components/quick_answers/public/cpp:prefs",
"//chromeos/components/quick_answers/public/mojom",
"//components/language/core/browser:browser",
"//components/pref_registry",
"//components/prefs",
"//third_party/hunspell",
"//ui/base",
"//ui/gfx",
]
Expand Down
@@ -0,0 +1,45 @@
// Copyright 2022 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 "chromeos/components/quick_answers/public/cpp/service/spell_check_dictionary.h"

#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "third_party/hunspell/src/hunspell/hunspell.hxx"

namespace quick_answers {

SpellCheckDictionary::SpellCheckDictionary() = default;

SpellCheckDictionary::~SpellCheckDictionary() = default;

bool SpellCheckDictionary::Initialize(base::File file) {
mapped_dict_file_ = std::make_unique<base::MemoryMappedFile>();

if (!mapped_dict_file_->Initialize(std::move(file))) {
LOG(ERROR) << "Failed to mmap dictionary file.";
return false;
}

if (!hunspell::BDict::Verify(
reinterpret_cast<const char*>(mapped_dict_file_->data()),
mapped_dict_file_->length())) {
LOG(ERROR) << "Failed to verify dictionary file.";
return false;
}

hunspell_ = std::make_unique<Hunspell>(mapped_dict_file_->data(),
mapped_dict_file_->length());

return true;
}

void SpellCheckDictionary::CheckSpelling(const std::string& word,
CheckSpellingCallback callback) {
DCHECK(hunspell_);

std::move(callback).Run(hunspell_->spell(word) != 0);
}

} // namespace quick_answers
@@ -0,0 +1,44 @@
// Copyright 2022 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 CHROMEOS_COMPONENTS_QUICK_ANSWERS_PUBLIC_CPP_SERVICE_SPELL_CHECK_DICTIONARY_H_
#define CHROMEOS_COMPONENTS_QUICK_ANSWERS_PUBLIC_CPP_SERVICE_SPELL_CHECK_DICTIONARY_H_

#include <memory>
#include <string>

#include "base/memory/weak_ptr.h"
#include "chromeos/components/quick_answers/public/mojom/spell_check.mojom.h"

class Hunspell;

namespace base {
class MemoryMappedFile;
} // namespace base

namespace quick_answers {

// Utility class for spell check ran in renderer process.
class SpellCheckDictionary : public mojom::SpellCheckDictionary {
public:
SpellCheckDictionary();

SpellCheckDictionary(const SpellCheckDictionary&) = delete;
SpellCheckDictionary& operator=(const SpellCheckDictionary&) = delete;

~SpellCheckDictionary() override;

bool Initialize(base::File file);

void CheckSpelling(const std::string& word,
CheckSpellingCallback callback) override;

private:
std::unique_ptr<base::MemoryMappedFile> mapped_dict_file_;
std::unique_ptr<Hunspell> hunspell_;
};

} // namespace quick_answers

#endif // CHROMEOS_COMPONENTS_QUICK_ANSWERS_PUBLIC_CPP_SERVICE_SPELL_CHECK_DICTIONARY_H_
@@ -0,0 +1,33 @@
// Copyright 2022 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 "chromeos/components/quick_answers/public/cpp/service/spell_check_service.h"

#include "base/logging.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"

namespace quick_answers {

SpellCheckService::SpellCheckService(
mojo::PendingReceiver<quick_answers::mojom::SpellCheckService>
pending_receiver)
: receiver_(this, std::move(pending_receiver)) {}

SpellCheckService::~SpellCheckService() = default;

void SpellCheckService::CreateDictionary(base::File file,
CreateDictionaryCallback callback) {
auto dictionary = std::make_unique<SpellCheckDictionary>();
if (!dictionary->Initialize(std::move(file))) {
std::move(callback).Run(std::move(mojo::NullRemote()));
return;
}

mojo::PendingRemote<mojom::SpellCheckDictionary> pending_remote;
mojo::MakeSelfOwnedReceiver(std::move(dictionary),
pending_remote.InitWithNewPipeAndPassReceiver());
std::move(callback).Run(std::move(pending_remote));
}

} // namespace quick_answers
@@ -0,0 +1,41 @@
// Copyright 2022 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 CHROMEOS_COMPONENTS_QUICK_ANSWERS_PUBLIC_CPP_SERVICE_SPELL_CHECK_SERVICE_H_
#define CHROMEOS_COMPONENTS_QUICK_ANSWERS_PUBLIC_CPP_SERVICE_SPELL_CHECK_SERVICE_H_

#include <memory>
#include <string>

#include "base/memory/weak_ptr.h"
#include "chromeos/components/quick_answers/public/cpp/service/spell_check_dictionary.h"
#include "chromeos/components/quick_answers/public/mojom/spell_check.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"

namespace quick_answers {

// Utility class for spell check ran in renderer process.
class SpellCheckService : public mojom::SpellCheckService {
public:
explicit SpellCheckService(
mojo::PendingReceiver<mojom::SpellCheckService> pending_receiver);

SpellCheckService(const SpellCheckService&) = delete;
SpellCheckService& operator=(const SpellCheckService&) = delete;

~SpellCheckService() override;

void CreateDictionary(base::File file,
CreateDictionaryCallback callback) override;

private:
std::unique_ptr<SpellCheckDictionary> dictionary_;

mojo::Receiver<mojom::SpellCheckService> receiver_;
};

} // namespace quick_answers

#endif // CHROMEOS_COMPONENTS_QUICK_ANSWERS_PUBLIC_CPP_SERVICE_SPELL_CHECK_SERVICE_H_
14 changes: 14 additions & 0 deletions chromeos/components/quick_answers/public/mojom/BUILD.gn
@@ -0,0 +1,14 @@
# Copyright 2022 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.

import("//mojo/public/tools/bindings/mojom.gni")

mojom("mojom") {
sources = [ "spell_check.mojom" ]

public_deps = [
"//mojo/public/mojom/base",
"//sandbox/policy/mojom",
]
}
2 changes: 2 additions & 0 deletions chromeos/components/quick_answers/public/mojom/OWNERS
@@ -0,0 +1,2 @@
per-file *.mojom=set noparent
per-file *.mojom=file://ipc/SECURITY_OWNERS
29 changes: 29 additions & 0 deletions chromeos/components/quick_answers/public/mojom/spell_check.mojom
@@ -0,0 +1,29 @@
// Copyright 2022 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.

module quick_answers.mojom;

import "mojo/public/mojom/base/read_only_file.mojom";
import "sandbox/policy/mojom/sandbox.mojom";

// Provides a way to query hunspell in a sandboxed utility process, since
// the inputs may be untrustworthy and hunspell library is somewhat prone
// to crashes.
[ServiceSandbox=sandbox.mojom.Sandbox.kService]
interface SpellCheckService {
// Creates a new SpellCheckerDictionary instance from |dictionary_file|.
// If hunspell initialization failed, returns a null remote.
// Can be called multiple times if the dictionary file changes or the
// previous call did not success.
CreateDictionary(mojo_base.mojom.ReadOnlyFile dictionary_file)
=> (pending_remote<SpellCheckDictionary>? dictionary);
};

// Handles spell check requests for a hunspell dictionary loaded via
// |CreateDictionary()|.
interface SpellCheckDictionary {
// Check spelling of the given word, |correctness| is true if the word is
// spelled correctly.
CheckSpelling(string word) => (bool correctness);
};

0 comments on commit e378cf5

Please sign in to comment.