feat: enable builtin spellchecker (#20692)

* chore: add code required to use chromes spellchecker

* chore: fix linting

* chore: manifests needs buildflags now

* chore: add dictionarySuggestions to the context menu event when the spellchecker is active

* chore: enable by default for windows builds

* chore: add patch to remove incognito usage in the spellchecker

* chore: add dependencies on spellcheck common and flags

* chore: conditionally include spell check panel impl

* chore: fix deps for spellcheck feature flags

* chore: add patch for electron resources

* chore: add dependency on //components/language/core/browser

* chore: patches to make hunspell work on windows

* build: collect hunspell dictionaries into a zip file and publish

* chore: clean up patches

* chore: add docs and set spell checker url method

* chore: fix error handling

* chore: fix hash logic

* build: update hunspell filename generator

* fix: default spellchecker list to the current system locale if we can

* docs: document the language getter

* chore: patch IDS_ resources for linux builds

* feat: add spellcheck webpref flag to disable the builtin spellchecker

* chore: fix docs typo

* chore: clean up spellchecker impl as per feedback

* remove unneeded deps
This commit is contained in:
Samuel Attard 2019-10-31 13:11:51 -07:00 committed by GitHub
parent 23ca7e3733
commit 6bcf67e051
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 560 additions and 10 deletions

View file

@ -898,9 +898,14 @@ void App::SetPath(gin_helper::ErrorThrower thrower,
bool succeed = false;
int key = GetPathConstant(name);
if (key >= 0)
if (key >= 0) {
succeed =
base::PathService::OverrideAndCreateIfNeeded(key, path, true, false);
if (key == DIR_USER_DATA) {
succeed |= base::PathService::OverrideAndCreateIfNeeded(
chrome::DIR_USER_DATA, path, true, false);
}
}
if (!succeed)
thrower.ThrowError("Failed to set path");
}

View file

@ -68,6 +68,12 @@
#include "shell/browser/extensions/atom_extension_system.h"
#endif
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
#include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h"
#include "components/spellcheck/browser/pref_names.h"
#include "components/spellcheck/common/spellcheck_common.h"
#endif
using content::BrowserThread;
using content::StoragePartition;
@ -646,6 +652,42 @@ void Session::Preconnect(const gin_helper::Dictionary& options,
url, num_sockets_to_preconnect));
}
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
base::Value Session::GetSpellCheckerLanguages() {
return browser_context_->prefs()
->Get(spellcheck::prefs::kSpellCheckDictionaries)
->Clone();
}
void Session::SetSpellCheckerLanguages(
gin_helper::ErrorThrower thrower,
const std::vector<std::string>& languages) {
base::ListValue language_codes;
for (const std::string& lang : languages) {
std::string code = spellcheck::GetCorrespondingSpellCheckLanguage(lang);
if (code.empty()) {
thrower.ThrowError("Invalid language code provided: \"" + lang +
"\" is not a valid language code");
return;
}
language_codes.AppendString(code);
}
browser_context_->prefs()->Set(spellcheck::prefs::kSpellCheckDictionaries,
language_codes);
}
void SetSpellCheckerDictionaryDownloadURL(gin_helper::ErrorThrower thrower,
const GURL& url) {
if (!url.is_valid()) {
thrower.ThrowError(
"The URL you provided to setSpellCheckerDictionaryDownloadURL is not a "
"valid URL");
return;
}
SpellcheckHunspellDictionary::SetDownloadURLForTesting(url);
}
#endif
// static
gin::Handle<Session> Session::CreateFrom(v8::Isolate* isolate,
AtomBrowserContext* browser_context) {
@ -716,6 +758,14 @@ void Session::BuildPrototype(v8::Isolate* isolate,
.SetMethod("getPreloads", &Session::GetPreloads)
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
.SetMethod("loadChromeExtension", &Session::LoadChromeExtension)
#endif
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
.SetMethod("getSpellCheckerLanguages", &Session::GetSpellCheckerLanguages)
.SetMethod("setSpellCheckerLanguages", &Session::SetSpellCheckerLanguages)
.SetProperty("availableSpellCheckerLanguages",
&spellcheck::SpellCheckLanguages)
.SetMethod("setSpellCheckerDictionaryDownloadURL",
&SetSpellCheckerDictionaryDownloadURL)
#endif
.SetMethod("preconnect", &Session::Preconnect)
.SetProperty("cookies", &Session::Cookies)

View file

@ -88,6 +88,11 @@ class Session : public gin_helper::TrackableObject<Session>,
v8::Local<v8::Value> NetLog(v8::Isolate* isolate);
void Preconnect(const gin_helper::Dictionary& options,
gin_helper::Arguments* args);
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
base::Value GetSpellCheckerLanguages();
void SetSpellCheckerLanguages(gin_helper::ErrorThrower thrower,
const std::vector<std::string>& languages);
#endif
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
void LoadChromeExtension(const base::FilePath extension_path);

View file

@ -100,6 +100,11 @@
#include "net/ssl/client_cert_store.h"
#endif
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
#include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h" // nogncheck
#include "components/spellcheck/common/spellcheck.mojom.h" // nogncheck
#endif
#if BUILDFLAG(ENABLE_PEPPER_FLASH)
#include "chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.h"
#endif // BUILDFLAG(ENABLE_PEPPER_FLASH)
@ -758,6 +763,18 @@ network::mojom::NetworkContext* AtomBrowserClient::GetSystemNetworkContext() {
return g_browser_process->system_network_context_manager()->GetContext();
}
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
void AtomBrowserClient::BindHostReceiverForRenderer(
content::RenderProcessHost* render_process_host,
mojo::GenericPendingReceiver receiver) {
if (auto host_receiver = receiver.As<spellcheck::mojom::SpellCheckHost>()) {
SpellCheckHostChromeImpl::Create(render_process_host->GetID(),
std::move(host_receiver));
return;
}
}
#endif
base::Optional<service_manager::Manifest>
AtomBrowserClient::GetServiceManifestOverlay(base::StringPiece name) {
if (name == content::mojom::kBrowserServiceName)

View file

@ -149,6 +149,11 @@ class AtomBrowserClient : public content::ContentBrowserClient,
bool in_memory,
const base::FilePath& relative_partition_path) override;
network::mojom::NetworkContext* GetSystemNetworkContext() override;
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
void BindHostReceiverForRenderer(
content::RenderProcessHost* render_process_host,
mojo::GenericPendingReceiver receiver) override;
#endif
base::Optional<service_manager::Manifest> GetServiceManifestOverlay(
base::StringPiece name) override;
content::MediaObserver* GetMediaObserver() override;

View file

@ -48,8 +48,6 @@
#include "shell/common/options_switches.h"
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/user_prefs/user_prefs.h"
#include "extensions/browser/browser_context_keyed_service_factories.h"
#include "extensions/browser/extension_pref_store.h"
#include "extensions/browser/extension_pref_value_map_factory.h"
@ -63,6 +61,19 @@
#include "shell/common/extensions/atom_extensions_client.h"
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) || \
BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/user_prefs/user_prefs.h"
#endif
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
#include "base/i18n/rtl.h"
#include "components/language/core/browser/language_prefs.h"
#include "components/spellcheck/browser/pref_names.h"
#include "components/spellcheck/common/spellcheck_common.h"
#endif
using content::BrowserThread;
namespace electron {
@ -102,6 +113,7 @@ AtomBrowserContext::AtomBrowserContext(const std::string& partition,
base::PathService::Get(DIR_APP_DATA, &path_);
path_ = path_.Append(base::FilePath::FromUTF8Unsafe(GetApplicationName()));
base::PathService::Override(DIR_USER_DATA, path_);
base::PathService::Override(chrome::DIR_USER_DATA, path_);
}
if (!in_memory && !partition.empty())
@ -156,7 +168,10 @@ void AtomBrowserContext::InitPrefs() {
ExtensionPrefValueMapFactory::GetForBrowserContext(this),
IsOffTheRecord());
prefs_factory.set_extension_prefs(ext_pref_store);
#endif
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) || \
BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
auto registry = WrapRefCounted(new user_prefs::PrefRegistrySyncable);
#else
auto registry = WrapRefCounted(new PrefRegistrySimple);
@ -177,13 +192,36 @@ void AtomBrowserContext::InitPrefs() {
extensions::ExtensionPrefs::RegisterProfilePrefs(registry.get());
#endif
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
BrowserContextDependencyManager::GetInstance()
->RegisterProfilePrefsForServices(registry.get());
language::LanguagePrefs::RegisterProfilePrefs(registry.get());
#endif
prefs_ = prefs_factory.Create(
registry.get(),
std::make_unique<PrefStoreDelegate>(weak_factory_.GetWeakPtr()));
prefs_->UpdateCommandLinePrefStore(new ValueMapPrefStore);
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) || \
BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
user_prefs::UserPrefs::Set(this, prefs_.get());
#endif
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
auto* current_dictionaries =
prefs()->Get(spellcheck::prefs::kSpellCheckDictionaries);
// No configured dictionaries, the default will be en-US
if (current_dictionaries->GetList().size() == 0) {
std::string default_code = spellcheck::GetCorrespondingSpellCheckLanguage(
base::i18n::GetConfiguredLocale());
if (!default_code.empty()) {
base::ListValue language_codes;
language_codes.AppendString(default_code);
prefs()->Set(spellcheck::prefs::kSpellCheckDictionaries, language_codes);
}
}
#endif
}
void AtomBrowserContext::SetUserAgent(const std::string& user_agent) {

View file

@ -103,6 +103,10 @@
#include "shell/common/extensions/atom_extensions_client.h"
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
#include "chrome/browser/spellchecker/spellcheck_factory.h" // nogncheck
#endif
namespace electron {
namespace {
@ -442,6 +446,10 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() {
extensions::electron::EnsureBrowserContextKeyedServiceFactoriesBuilt();
#endif
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
SpellcheckServiceFactory::GetInstance();
#endif
// url::Add*Scheme are not threadsafe, this helps prevent data races.
url::LockSchemeRegistries();

View file

@ -144,6 +144,9 @@ WebContentsPreferences::WebContentsPreferences(
SetDefaultBoolIfUndefined(options::kScrollBounce, false);
#endif
SetDefaultBoolIfUndefined(options::kOffscreen, false);
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
SetDefaultBoolIfUndefined(options::kSpellcheck, true);
#endif
// If this is a <webview> tag, and the embedder is offscreen-rendered, then
// this WebContents is also offscreen-rendered.
@ -414,6 +417,12 @@ void WebContentsPreferences::AppendCommandLineSwitches(
if (IsEnabled(options::kNodeIntegrationInSubFrames))
command_line->AppendSwitch(switches::kNodeIntegrationInSubFrames);
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
if (IsEnabled(options::kSpellcheck)) {
command_line->AppendSwitch(switches::kEnableSpellcheck);
}
#endif
// We are appending args to a webContents so let's save the current state
// of our preferences object so that during the lifetime of the WebContents
// we can fetch the options used to initally configure the WebContents

View file

@ -138,6 +138,9 @@ v8::Local<v8::Value> Converter<ContextMenuParamsWithWebContents>::ToV8(
dict.Set("selectionText", params.selection_text);
dict.Set("titleText", params.title_text);
dict.Set("misspelledWord", params.misspelled_word);
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
dict.Set("dictionarySuggestions", params.dictionary_suggestions);
#endif
dict.Set("frameCharset", params.frame_charset);
dict.Set("inputFieldType", params.input_field_type);
dict.Set("menuSourceType", params.source_type);

View file

@ -173,6 +173,10 @@ const char kWebGL[] = "webgl";
// navigation.
const char kNavigateOnDragDrop[] = "navigateOnDragDrop";
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
const char kSpellcheck[] = "spellcheck";
#endif
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
const char kEnableRemoteModule[] = "enableRemoteModule";
#endif
@ -267,6 +271,10 @@ const char kAuthNegotiateDelegateWhitelist[] =
// If set, include the port in generated Kerberos SPNs.
const char kEnableAuthNegotiatePort[] = "enable-auth-negotiate-port";
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
const char kEnableSpellcheck[] = "enable-spellcheck";
#endif
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
const char kEnableRemoteModule[] = "enable-remote-module";
#endif

View file

@ -84,6 +84,10 @@ extern const char kTextAreasAreResizable[];
extern const char kWebGL[];
extern const char kNavigateOnDragDrop[];
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
extern const char kSpellcheck[];
#endif
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
extern const char kEnableRemoteModule[];
#endif
@ -134,6 +138,10 @@ extern const char kAuthServerWhitelist[];
extern const char kAuthNegotiateDelegateWhitelist[];
extern const char kEnableAuthNegotiatePort[];
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
extern const char kEnableSpellcheck[];
#endif
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
extern const char kEnableRemoteModule[];
#endif

View file

@ -6,6 +6,7 @@
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/command_line.h"
@ -45,6 +46,11 @@
#include <shlobj.h>
#endif
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
#include "components/spellcheck/renderer/spellcheck.h"
#include "components/spellcheck/renderer/spellcheck_provider.h"
#endif
#if BUILDFLAG(ENABLE_PDF_VIEWER)
#include "shell/common/atom_constants.h"
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
@ -148,6 +154,11 @@ void RendererClientBase::RenderThreadStarted() {
thread->AddObserver(extensions_renderer_client_->GetDispatcher());
#endif
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
if (command_line->HasSwitch(switches::kEnableSpellcheck))
spellcheck_ = std::make_unique<SpellCheck>(&registry_, this);
#endif
blink::WebCustomElement::AddEmbedderCustomElementName("webview");
blink::WebCustomElement::AddEmbedderCustomElementName("browserplugin");
@ -262,8 +273,37 @@ void RendererClientBase::RenderFrameCreated(
dispatcher->OnRenderFrameCreated(render_frame);
#endif
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
auto* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kEnableSpellcheck))
new SpellCheckProvider(render_frame, spellcheck_.get(), this);
#endif
}
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
void RendererClientBase::BindReceiverOnMainThread(
mojo::GenericPendingReceiver receiver) {
// TODO(crbug.com/977637): Get rid of the use of BinderRegistry here. This is
// only used to bind a spellcheck interface.
std::string interface_name = *receiver.interface_name();
auto pipe = receiver.PassPipe();
registry_.TryBindInterface(interface_name, &pipe);
}
void RendererClientBase::GetInterface(
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) {
// TODO(crbug.com/977637): Get rid of the use of this implementation of
// |service_manager::LocalInterfaceProvider|. This was done only to avoid
// churning spellcheck code while eliminating the "chrome" and
// "chrome_renderer" services. Spellcheck is (and should remain) the only
// consumer of this implementation.
content::RenderThread::Get()->BindHostReceiver(
mojo::GenericPendingReceiver(interface_name, std::move(interface_pipe)));
}
#endif
void RendererClientBase::DidClearWindowObject(
content::RenderFrame* render_frame) {
// Make sure every page will get a script context created.

View file

@ -19,6 +19,13 @@
#include "chrome/renderer/media/chrome_key_systems_provider.h" // nogncheck
#endif
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
#include "services/service_manager/public/cpp/binder_registry.h"
#include "services/service_manager/public/cpp/local_interface_provider.h"
class SpellCheck;
#endif
namespace network_hints {
class PrescientNetworkingDispatcher;
}
@ -35,11 +42,24 @@ namespace electron {
class AtomExtensionsRendererClient;
#endif
class RendererClientBase : public content::ContentRendererClient {
class RendererClientBase : public content::ContentRendererClient
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
,
public service_manager::LocalInterfaceProvider
#endif
{
public:
RendererClientBase();
~RendererClientBase() override;
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
// service_manager::LocalInterfaceProvider implementation.
void GetInterface(const std::string& name,
mojo::ScopedMessagePipeHandle request_handle) override;
void BindReceiverOnMainThread(mojo::GenericPendingReceiver receiver) override;
#endif
virtual void DidCreateScriptContext(v8::Handle<v8::Context> context,
content::RenderFrame* render_frame);
virtual void WillReleaseScriptContext(v8::Handle<v8::Context> context,
@ -108,6 +128,11 @@ class RendererClientBase : public content::ContentRendererClient {
std::string renderer_client_id_;
// An increasing ID used for indentifying an V8 context in this process.
int64_t next_context_id_ = 0;
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
std::unique_ptr<SpellCheck> spellcheck_;
service_manager::BinderRegistry registry_;
#endif
};
} // namespace electron