diff --git a/.circleci/config.yml b/.circleci/config.yml
index 17d6083b3a..51c9117308 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -319,6 +319,8 @@ step-gn-check: &step-gn-check
gn check out/Default //electron:electron_app
gn check out/Default //electron:manifests
gn check out/Default //electron/shell/common/api:mojo
+ # Check the hunspell filenames
+ node electron/script/gen-hunspell-filenames.js --check
step-electron-build: &step-electron-build
run:
@@ -534,6 +536,20 @@ step-mksnapshot-store: &step-mksnapshot-store
path: src/out/Default/mksnapshot.zip
destination: mksnapshot.zip
+step-hunspell-build: &step-hunspell-build
+ run:
+ name: hunspell build
+ command: |
+ cd src
+ if [ "$SKIP_DIST_ZIP" != "1" ]; then
+ ninja -C out/Default electron:hunspell_dictionaries_zip -j $NUMBER_OF_NINJA_PROCESSES
+ fi
+
+step-hunspell-store: &step-hunspell-store
+ store_artifacts:
+ path: src/out/Default/hunspell_dictionaries.zip
+ destination: hunspell_dictionaries.zip
+
step-maybe-generate-breakpad-symbols: &step-maybe-generate-breakpad-symbols
run:
name: Generate breakpad symbols
@@ -693,7 +709,6 @@ step-minimize-workspace-size-from-checkout: &step-minimize-workspace-size-from-c
rm -rf src/ios
rm -rf src/third_party/blink/web_tests
rm -rf src/third_party/blink/perf_tests
- rm -rf src/third_party/hunspell_dictionaries
rm -rf src/third_party/WebKit/LayoutTests
# Save the src cache based on the deps hash
@@ -912,6 +927,10 @@ steps-electron-build: &steps-electron-build
- *step-ffmpeg-build
- *step-ffmpeg-store
+ # hunspell
+ - *step-hunspell-build
+ - *step-hunspell-store
+
# Save all data needed for a further tests run.
- *step-persist-data-for-tests
@@ -990,6 +1009,10 @@ steps-electron-build-with-inline-checkout-for-tests: &steps-electron-build-with-
- *step-ffmpeg-build
- *step-ffmpeg-store
+ # hunspell
+ - *step-hunspell-build
+ - *step-hunspell-store
+
# Save all data needed for a further tests run.
- *step-persist-data-for-tests
@@ -1079,6 +1102,10 @@ steps-electron-build-for-publish: &steps-electron-build-for-publish
- *step-ffmpeg-build
- *step-ffmpeg-store
+ # hunspell
+ - *step-hunspell-build
+ - *step-hunspell-store
+
# typescript defs
- *step-maybe-generate-typescript-defs
diff --git a/BUILD.gn b/BUILD.gn
index c6a25e0756..2bb0b07685 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1,6 +1,7 @@
import("//build/config/locales.gni")
import("//build/config/ui.gni")
import("//build/config/win/manifest.gni")
+import("//components/spellcheck/spellcheck_build_features.gni")
import("//content/public/app/mac_helpers.gni")
import("//pdf/features.gni")
import("//printing/buildflags/buildflags.gni")
@@ -21,6 +22,7 @@ import("buildflags/buildflags.gni")
import("electron_paks.gni")
import("filenames.auto.gni")
import("filenames.gni")
+import("filenames.hunspell.gni")
if (is_mac) {
import("//build/config/mac/rules.gni")
@@ -358,12 +360,12 @@ source_set("electron_lib") {
"//chrome/app/resources:platform_locale_settings",
"//chrome/services/printing/public/mojom",
"//components/certificate_transparency",
+ "//components/language/core/browser",
"//components/net_log",
"//components/network_hints/common",
"//components/network_hints/renderer",
"//components/network_session_configurator/common",
"//components/prefs",
- "//components/spellcheck/renderer",
"//components/viz/host",
"//components/viz/service",
"//content/public/browser",
@@ -478,6 +480,10 @@ source_set("electron_lib") {
]
}
+ if (enable_builtin_spellchecker) {
+ deps += [ "chromium_src:chrome_spellchecker" ]
+ }
+
if (is_mac) {
deps += [
"//components/remote_cocoa/app_shim",
@@ -1268,9 +1274,14 @@ template("dist_zip") {
"outputs",
"testonly",
])
+ flatten = false
+ if (defined(invoker.flatten)) {
+ flatten = invoker.flatten
+ }
args = rebase_path(outputs + [ _runtime_deps_file ], root_build_dir) + [
target_cpu,
target_os,
+ "$flatten",
]
}
}
@@ -1356,6 +1367,24 @@ dist_zip("electron_mksnapshot_zip") {
]
}
+copy("hunspell_dictionaries") {
+ sources = hunspell_dictionaries + hunspell_licenses
+ outputs = [
+ "$target_gen_dir/electron_hunspell/{{source_file_part}}",
+ ]
+}
+
+dist_zip("hunspell_dictionaries_zip") {
+ data_deps = [
+ ":hunspell_dictionaries",
+ ]
+ flatten = true
+
+ outputs = [
+ "$root_build_dir/hunspell_dictionaries.zip",
+ ]
+}
+
group("electron") {
public_deps = [
":electron_app",
diff --git a/appveyor.yml b/appveyor.yml
index 618a25345e..26e29919b8 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -91,6 +91,7 @@ build_script:
- ninja -C out/Default electron:electron_dist_zip
- ninja -C out/Default shell_browser_ui_unittests
- ninja -C out/Default electron:electron_mksnapshot_zip
+ - ninja -C out/Default electron:hunspell_dictionaries_zip
- ninja -C out/Default electron:electron_chromedriver_zip
- ninja -C out/Default third_party/electron_node:headers
- appveyor PushArtifact out/Default/dist.zip
@@ -100,6 +101,7 @@ build_script:
- 7z a node_headers.zip out\Default\gen\node_headers
- appveyor PushArtifact node_headers.zip
- appveyor PushArtifact out/Default/mksnapshot.zip
+ - appveyor PushArtifact out/Default/hunspell_dictionaries.zip
- appveyor PushArtifact out/Default/electron.lib
- ps: >-
if ($env:GN_CONFIG -eq 'release') {
diff --git a/build/zip.py b/build/zip.py
index ff9c888d91..00a6bc9de0 100644
--- a/build/zip.py
+++ b/build/zip.py
@@ -46,13 +46,14 @@ def execute(argv):
raise e
def main(argv):
- dist_zip, runtime_deps, target_cpu, target_os = argv
+ dist_zip, runtime_deps, target_cpu, target_os, flatten_val = argv
+ should_flatten = flatten_val == "true"
dist_files = set()
with open(runtime_deps) as f:
for dep in f.readlines():
dep = dep.strip()
dist_files.add(dep)
- if sys.platform == 'darwin':
+ if sys.platform == 'darwin' and not should_flatten:
execute(['zip', '-r', '-y', dist_zip] + list(dist_files))
else:
with zipfile.ZipFile(dist_zip, 'w', zipfile.ZIP_DEFLATED, allowZip64=True) as z:
@@ -67,7 +68,7 @@ def main(argv):
basename = os.path.basename(dep)
dirname = os.path.dirname(dep)
arcname = os.path.join(dirname, 'chrome-sandbox') if basename == 'chrome_sandbox' else dep
- z.write(dep, arcname)
+ z.write(dep, os.path.basename(arcname) if should_flatten else arcname)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
diff --git a/buildflags/BUILD.gn b/buildflags/BUILD.gn
index 2da174f3e9..6209a7de40 100644
--- a/buildflags/BUILD.gn
+++ b/buildflags/BUILD.gn
@@ -19,6 +19,7 @@ buildflag_header("buildflags") {
"ENABLE_TTS=$enable_tts",
"ENABLE_COLOR_CHOOSER=$enable_color_chooser",
"ENABLE_ELECTRON_EXTENSIONS=$enable_electron_extensions",
+ "ENABLE_BUILTIN_SPELLCHECKER=$enable_builtin_spellchecker",
"ENABLE_PICTURE_IN_PICTURE=$enable_picture_in_picture",
"OVERRIDE_LOCATION_PROVIDER=$enable_fake_location_provider",
]
diff --git a/buildflags/buildflags.gni b/buildflags/buildflags.gni
index a4d4a48714..9bd6fe1ed6 100644
--- a/buildflags/buildflags.gni
+++ b/buildflags/buildflags.gni
@@ -33,4 +33,7 @@ declare_args() {
# Enable Chrome extensions support.
enable_electron_extensions = false
+
+ # Enable Spellchecker support
+ enable_builtin_spellchecker = true
}
diff --git a/chromium_src/BUILD.gn b/chromium_src/BUILD.gn
index e8313cd8df..d6796f796c 100644
--- a/chromium_src/BUILD.gn
+++ b/chromium_src/BUILD.gn
@@ -3,6 +3,7 @@
# found in the LICENSE file.
import("//build/config/ui.gni")
+import("//components/spellcheck/spellcheck_build_features.gni")
import("//electron/buildflags/buildflags.gni")
import("//printing/buildflags/buildflags.gni")
import("//third_party/widevine/cdm/widevine.gni")
@@ -225,3 +226,52 @@ static_library("chrome") {
]
}
}
+
+# This source set is just so we don't have to depend on all of //chrome/browser
+# You may have to add new files here during the upgrade if //chrome/browser/spellchecker
+# gets more files
+source_set("chrome_spellchecker") {
+ sources = [
+ "//chrome/browser/spellchecker/spell_check_host_chrome_impl.cc",
+ "//chrome/browser/spellchecker/spell_check_host_chrome_impl.h",
+ "//chrome/browser/spellchecker/spellcheck_custom_dictionary.cc",
+ "//chrome/browser/spellchecker/spellcheck_custom_dictionary.h",
+ "//chrome/browser/spellchecker/spellcheck_factory.cc",
+ "//chrome/browser/spellchecker/spellcheck_factory.h",
+ "//chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc",
+ "//chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h",
+ "//chrome/browser/spellchecker/spellcheck_language_blacklist_policy_handler.cc",
+ "//chrome/browser/spellchecker/spellcheck_language_blacklist_policy_handler.h",
+ "//chrome/browser/spellchecker/spellcheck_language_policy_handler.cc",
+ "//chrome/browser/spellchecker/spellcheck_language_policy_handler.h",
+ "//chrome/browser/spellchecker/spellcheck_service.cc",
+ "//chrome/browser/spellchecker/spellcheck_service.h",
+ ]
+
+ if (has_spellcheck_panel) {
+ sources += [
+ "//chrome/browser/spellchecker/spell_check_panel_host_impl.cc",
+ "//chrome/browser/spellchecker/spell_check_panel_host_impl.h",
+ ]
+ }
+
+ if (use_browser_spellchecker) {
+ sources += [
+ "//chrome/browser/spellchecker/spelling_request.cc",
+ "//chrome/browser/spellchecker/spelling_request.h",
+ ]
+ }
+
+ deps = [
+ "//base:base_static",
+ "//components/language/core/browser",
+ "//components/spellcheck:buildflags",
+ "//components/sync",
+ ]
+
+ public_deps = [
+ "//components/spellcheck/browser",
+ "//components/spellcheck/common",
+ "//components/spellcheck/renderer",
+ ]
+}
diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md
index 928eff7600..f74b25d089 100644
--- a/docs/api/browser-window.md
+++ b/docs/api/browser-window.md
@@ -385,6 +385,8 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
* `accessibleTitle` String (optional) - An alternative title string provided only
to accessibility tools such as screen readers. This string is not directly
visible to users.
+ * `spellcheck` Boolean (optional) - Whether to enable the builtin spellchecker.
+ Default is `true`.
When setting minimum or maximum window size with `minWidth`/`maxWidth`/
`minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from
diff --git a/docs/api/session.md b/docs/api/session.md
index 7eec76e226..27c6d96091 100644
--- a/docs/api/session.md
+++ b/docs/api/session.md
@@ -456,10 +456,38 @@ this session just before normal `preload` scripts run.
Returns `String[]` an array of paths to preload scripts that have been
registered.
+#### `ses.setSpellCheckerLanguages(languages)`
+
+* `languages` String[] - An array of language codes to enable the spellchecker for.
+
+The built in spellchecker does not automatically detect what language a user is typing in. In order for the
+spell checker to correctly check their words you must call this API with an array of language codes. You can
+get the list of supported language codes with the `ses.availableSpellCheckerLanguages` property.
+
+#### `ses.getSpellCheckerLanguages()`
+
+Returns `String[]` - An array of language codes the spellchecker is enabled for. If this list is empty the spellchecker
+will fallback to using `en-US`. By default on launch if this setting is an empty list Electron will try to populate this
+setting with the current OS locale. This setting is persisted across restarts.
+
+#### `ses.setSpellCheckerDictionaryDownloadURL(url)`
+
+* `url` String - A base URL for Electron to download hunspell dictionaries from.
+
+By default Electron will download hunspell dictionaries from the Chromium CDN. If you want to override this
+behavior you can use this API to point the dictionary downloader at your own hosted version of the hunspell
+dictionaries. We publish a `hunspell_dictionaries.zip` file with each release which contains the files you need
+to host here.
+
### Instance Properties
The following properties are available on instances of `Session`:
+#### `ses.availableSpellCheckerLanguages` _Readonly_
+
+A `String[]` array which consists of all the known available spell checker languages. Providing a language
+code to the `setSpellCheckerLanaguages` API that isn't in this array will result in an error.
+
#### `ses.cookies` _Readonly_
A [`Cookies`](cookies.md) object for this session.
diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md
index ec335b061f..69758f61d9 100644
--- a/docs/api/web-contents.md
+++ b/docs/api/web-contents.md
@@ -570,6 +570,9 @@ Returns:
* `titleText` String - Title or alt text of the selection that the context
was invoked on.
* `misspelledWord` String - The misspelled word under the cursor, if any.
+ * `dictionarySuggestions` String[] - An array of suggested words to show the
+ user to replace the `misspelledWord`. Only available if there is a misspelled
+ word and spellchecker is enabled.
* `frameCharset` String - The character encoding of the frame on which the
menu was invoked.
* `inputFieldType` String - If the context menu was invoked on an input
diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md
index 712aca4396..f39dc70a06 100644
--- a/docs/api/web-frame.md
+++ b/docs/api/web-frame.md
@@ -74,6 +74,17 @@ Sets the maximum and minimum layout-based (i.e. non-visual) zoom level.
Sets a provider for spell checking in input fields and text areas.
+If you want to use this method you must disable the builtin spellchecker when you
+construct the window.
+
+```js
+const mainWindow = new BrowserWindow({
+ webPreferences: {
+ spellcheck: false
+ }
+})
+```
+
The `provider` must be an object that has a `spellCheck` method that accepts
an array of individual words for spellchecking.
The `spellCheck` function runs asynchronously and calls the `callback` function
diff --git a/electron_strings.grdp b/electron_strings.grdp
index ca59124720..5b3f39afcf 100644
--- a/electron_strings.grdp
+++ b/electron_strings.grdp
@@ -69,4 +69,10 @@
Previous track
+
+ en-US
+
+
+ en-US,en
+
diff --git a/filenames.hunspell.gni b/filenames.hunspell.gni
new file mode 100644
index 0000000000..8662408760
--- /dev/null
+++ b/filenames.hunspell.gni
@@ -0,0 +1,60 @@
+hunspell_dictionaries = [
+ "//third_party/hunspell_dictionaries/af-ZA-3-0.bdic",
+ "//third_party/hunspell_dictionaries/bg-BG-3-0.bdic",
+ "//third_party/hunspell_dictionaries/ca-ES-3-0.bdic",
+ "//third_party/hunspell_dictionaries/cs-CZ-3-0.bdic",
+ "//third_party/hunspell_dictionaries/cy-GB-1-0.bdic",
+ "//third_party/hunspell_dictionaries/da-DK-3-0.bdic",
+ "//third_party/hunspell_dictionaries/de-DE-3-0.bdic",
+ "//third_party/hunspell_dictionaries/el-GR-3-0.bdic",
+ "//third_party/hunspell_dictionaries/en-AU-8-0.bdic",
+ "//third_party/hunspell_dictionaries/en-CA-8-0.bdic",
+ "//third_party/hunspell_dictionaries/en-GB-8-0.bdic",
+ "//third_party/hunspell_dictionaries/en-US-8-0.bdic",
+ "//third_party/hunspell_dictionaries/es-ES-3-0.bdic",
+ "//third_party/hunspell_dictionaries/et-EE-3-0.bdic",
+ "//third_party/hunspell_dictionaries/fa-IR-8-0.bdic",
+ "//third_party/hunspell_dictionaries/fo-FO-3-0.bdic",
+ "//third_party/hunspell_dictionaries/fr-FR-3-0.bdic",
+ "//third_party/hunspell_dictionaries/he-IL-3-0.bdic",
+ "//third_party/hunspell_dictionaries/hi-IN-3-0.bdic",
+ "//third_party/hunspell_dictionaries/hr-HR-3-0.bdic",
+ "//third_party/hunspell_dictionaries/hu-HU-3-0.bdic",
+ "//third_party/hunspell_dictionaries/hy-1-0.bdic",
+ "//third_party/hunspell_dictionaries/id-ID-3-0.bdic",
+ "//third_party/hunspell_dictionaries/it-IT-3-0.bdic",
+ "//third_party/hunspell_dictionaries/ko-3-0.bdic",
+ "//third_party/hunspell_dictionaries/lt-LT-3-0.bdic",
+ "//third_party/hunspell_dictionaries/lv-LV-3-0.bdic",
+ "//third_party/hunspell_dictionaries/nb-NO-3-0.bdic",
+ "//third_party/hunspell_dictionaries/nl-NL-3-0.bdic",
+ "//third_party/hunspell_dictionaries/pl-PL-3-0.bdic",
+ "//third_party/hunspell_dictionaries/pt-BR-3-0.bdic",
+ "//third_party/hunspell_dictionaries/pt-PT-3-0.bdic",
+ "//third_party/hunspell_dictionaries/ro-RO-3-0.bdic",
+ "//third_party/hunspell_dictionaries/ru-RU-3-0.bdic",
+ "//third_party/hunspell_dictionaries/sh-3-0.bdic",
+ "//third_party/hunspell_dictionaries/sh-4-0.bdic",
+ "//third_party/hunspell_dictionaries/sk-SK-3-0.bdic",
+ "//third_party/hunspell_dictionaries/sl-SI-3-0.bdic",
+ "//third_party/hunspell_dictionaries/sq-3-0.bdic",
+ "//third_party/hunspell_dictionaries/sr-3-0.bdic",
+ "//third_party/hunspell_dictionaries/sr-4-0.bdic",
+ "//third_party/hunspell_dictionaries/sv-SE-3-0.bdic",
+ "//third_party/hunspell_dictionaries/ta-IN-3-0.bdic",
+ "//third_party/hunspell_dictionaries/tg-TG-5-0.bdic",
+ "//third_party/hunspell_dictionaries/tr-TR-4-0.bdic",
+ "//third_party/hunspell_dictionaries/uk-UA-3-0.bdic",
+ "//third_party/hunspell_dictionaries/vi-VN-3-0.bdic",
+ "//third_party/hunspell_dictionaries/xx-XX-3-0.bdic",
+]
+
+hunspell_licenses = [
+ "//third_party/hunspell_dictionaries/COPYING",
+ "//third_party/hunspell_dictionaries/COPYING.Apache",
+ "//third_party/hunspell_dictionaries/COPYING.LESSER",
+ "//third_party/hunspell_dictionaries/COPYING.LGPL",
+ "//third_party/hunspell_dictionaries/COPYING.MIT",
+ "//third_party/hunspell_dictionaries/COPYING.MPL",
+ "//third_party/hunspell_dictionaries/LICENSE",
+]
diff --git a/package.json b/package.json
index 9f154f15bd..b17c2e4d69 100644
--- a/package.json
+++ b/package.json
@@ -129,6 +129,9 @@
"node script/gen-filenames.js",
"python script/check-trailing-whitespace.py --fix",
"git add filenames.auto.gni"
+ ],
+ "DEPS": [
+ "node script/gen-hunspell-filenames.js"
]
},
"dependencies": {
diff --git a/patches/chromium/.patches b/patches/chromium/.patches
index 666df63142..afd3a38643 100644
--- a/patches/chromium/.patches
+++ b/patches/chromium/.patches
@@ -81,3 +81,5 @@ revert_remove_contentrendererclient_shouldfork.patch
build_win_disable_zc_twophase.patch
ignore_rc_check.patch
build_win_fix_ambiguous_reference_with_msstl.patch
+remove_usage_of_incognito_apis_in_the_spellchecker.patch
+chore_use_electron_resources_not_chrome_for_spellchecker.patch
diff --git a/patches/chromium/chore_use_electron_resources_not_chrome_for_spellchecker.patch b/patches/chromium/chore_use_electron_resources_not_chrome_for_spellchecker.patch
new file mode 100644
index 0000000000..7a3ab1cbdb
--- /dev/null
+++ b/patches/chromium/chore_use_electron_resources_not_chrome_for_spellchecker.patch
@@ -0,0 +1,34 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Samuel Attard
+Date: Wed, 23 Oct 2019 14:17:18 -0700
+Subject: chore: use electron resources not chrome for spellchecker
+
+spellchecker uses a few IDS_ resources. We need to load these from
+Electrons grit header instead of Chromes
+
+diff --git a/chrome/browser/spellchecker/spellcheck_factory.cc b/chrome/browser/spellchecker/spellcheck_factory.cc
+index 48ac0a24efde0cb7d3ba71c8b8bdf5178f606e80..e2beefc276098fdc8f1cdab2e0edb8fae4ee67ca 100644
+--- a/chrome/browser/spellchecker/spellcheck_factory.cc
++++ b/chrome/browser/spellchecker/spellcheck_factory.cc
+@@ -6,7 +6,7 @@
+
+ #include "chrome/browser/profiles/incognito_helpers.h"
+ #include "chrome/browser/spellchecker/spellcheck_service.h"
+-#include "chrome/grit/locale_settings.h"
++#include "electron/grit/electron_resources.h"
+ #include "components/keyed_service/content/browser_context_dependency_manager.h"
+ #include "components/pref_registry/pref_registry_syncable.h"
+ #include "components/prefs/pref_service.h"
+diff --git a/components/language/core/browser/language_prefs.cc b/components/language/core/browser/language_prefs.cc
+index d5e4c09e1722232df44b112ce39cdacea03a4710..c6caf7eacd9eed439ab5167e51b9fcce5d6af664 100644
+--- a/components/language/core/browser/language_prefs.cc
++++ b/components/language/core/browser/language_prefs.cc
+@@ -21,7 +21,7 @@
+ #include "components/pref_registry/pref_registry_syncable.h"
+ #include "components/prefs/pref_service.h"
+ #include "components/prefs/scoped_user_pref_update.h"
+-#include "components/strings/grit/components_locale_settings.h"
++#include "electron/grit/electron_resources.h"
+ #include "ui/base/l10n/l10n_util.h"
+
+ namespace language {
diff --git a/patches/chromium/remove_usage_of_incognito_apis_in_the_spellchecker.patch b/patches/chromium/remove_usage_of_incognito_apis_in_the_spellchecker.patch
new file mode 100644
index 0000000000..80e1100f31
--- /dev/null
+++ b/patches/chromium/remove_usage_of_incognito_apis_in_the_spellchecker.patch
@@ -0,0 +1,25 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Samuel Attard
+Date: Wed, 23 Oct 2019 11:43:58 -0700
+Subject: remove usage of incognito APIs in the spellchecker
+
+chrome::GetBrowserContextRedirectedInIncognito is not available in
+Electron nor do we want it to be. We could potentially upstream a
+change to move more of //chrome spellchecker logic into //components so
+that we can further separate our dependency from //chrome.
+
+diff --git a/chrome/browser/spellchecker/spellcheck_factory.cc b/chrome/browser/spellchecker/spellcheck_factory.cc
+index 5253d1f48e188b0339834c876378677b459e718e..48ac0a24efde0cb7d3ba71c8b8bdf5178f606e80 100644
+--- a/chrome/browser/spellchecker/spellcheck_factory.cc
++++ b/chrome/browser/spellchecker/spellcheck_factory.cc
+@@ -78,7 +78,10 @@ void SpellcheckServiceFactory::RegisterProfilePrefs(
+
+ content::BrowserContext* SpellcheckServiceFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
++ return context;
++#if 0
+ return chrome::GetBrowserContextRedirectedInIncognito(context);
++#endif
+ }
+
+ bool SpellcheckServiceFactory::ServiceIsNULLWhileTesting() const {
diff --git a/script/gen-hunspell-filenames.js b/script/gen-hunspell-filenames.js
new file mode 100644
index 0000000000..e91f935018
--- /dev/null
+++ b/script/gen-hunspell-filenames.js
@@ -0,0 +1,35 @@
+const fs = require('fs')
+const path = require('path')
+
+const check = process.argv.includes('--check')
+
+const dictsPath = path.resolve(__dirname, '..', '..', 'third_party', 'hunspell_dictionaries')
+const gclientPath = 'third_party/hunspell_dictionaries'
+
+const allFiles = fs.readdirSync(dictsPath)
+
+const dictionaries = allFiles
+ .filter(file => path.extname(file) === '.bdic')
+
+const licenses = allFiles
+ .filter(file => file.startsWith('LICENSE') || file.startsWith('COPYING'))
+
+const content = `hunspell_dictionaries = [
+ ${dictionaries.map(f => `"//${path.posix.join(gclientPath, f)}"`).join(',\n ')},
+]
+
+hunspell_licenses = [
+ ${licenses.map(f => `"//${path.posix.join(gclientPath, f)}"`).join(',\n ')},
+]
+`
+
+const filenamesPath = path.resolve(__dirname, '..', 'filenames.hunspell.gni')
+
+if (check) {
+ const currentContent = fs.readFileSync(filenamesPath, 'utf8')
+ if (currentContent !== content) {
+ throw new Error('hunspell filenames need to be regenerated, latest generation does not match current file. Please run node gen-hunspell-filenames.js')
+ }
+} else {
+ fs.writeFileSync(filenamesPath, content)
+}
diff --git a/script/generate-deps-hash.js b/script/generate-deps-hash.js
index df5dc2a9f0..7cd5a84f3e 100644
--- a/script/generate-deps-hash.js
+++ b/script/generate-deps-hash.js
@@ -29,6 +29,7 @@ addAllFiles(path.resolve(__dirname, '../patches'))
// Create Hash
const hasher = crypto.createHash('SHA256')
+hasher.update(`HASH_VERSION:${HASH_VERSION}`)
for (const file of filesToHash) {
hasher.update(fs.readFileSync(file))
}
diff --git a/script/release/release.js b/script/release/release.js
index 43f0e647ca..f9e102ab96 100755
--- a/script/release/release.js
+++ b/script/release/release.js
@@ -130,6 +130,7 @@ function assetsForVersion (version, validatingRelease) {
`electron-${version}-win32-arm64.zip`,
`electron-api.json`,
`electron.d.ts`,
+ `hunspell_dictionaries.zip`,
`ffmpeg-${version}-darwin-x64.zip`,
`ffmpeg-${version}-linux-arm64.zip`,
`ffmpeg-${version}-linux-armv7l.zip`,
diff --git a/script/release/uploaders/upload.py b/script/release/uploaders/upload.py
index de0954a914..15f303db5f 100755
--- a/script/release/uploaders/upload.py
+++ b/script/release/uploaders/upload.py
@@ -106,6 +106,11 @@ def main():
shutil.copy2(os.path.join(OUT_DIR, 'mksnapshot.zip'), mksnapshot_zip)
upload_electron(release, mksnapshot_zip, args)
+ if PLATFORM == 'linux' and get_target_arch() == 'x64':
+ # Upload the hunspell dictionaries only from the linux x64 build
+ hunspell_dictionaries_zip = os.path.join(OUT_DIR, 'hunspell_dictionaries.zip')
+ upload_electron(release, hunspell_dictionaries_zip, args)
+
if not tag_exists and not args.upload_to_s3:
# Upload symbols to symbol server.
run_python_upload_script('upload-symbols.py')
diff --git a/shell/browser/api/atom_api_app.cc b/shell/browser/api/atom_api_app.cc
index 4dd6bd8c44..3f1d786bf7 100644
--- a/shell/browser/api/atom_api_app.cc
+++ b/shell/browser/api/atom_api_app.cc
@@ -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");
}
diff --git a/shell/browser/api/atom_api_session.cc b/shell/browser/api/atom_api_session.cc
index 424a8142de..9300c6b9d1 100644
--- a/shell/browser/api/atom_api_session.cc
+++ b/shell/browser/api/atom_api_session.cc
@@ -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& 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::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)
diff --git a/shell/browser/api/atom_api_session.h b/shell/browser/api/atom_api_session.h
index 8bd766b8bb..5ac404af3e 100644
--- a/shell/browser/api/atom_api_session.h
+++ b/shell/browser/api/atom_api_session.h
@@ -88,6 +88,11 @@ class Session : public gin_helper::TrackableObject,
v8::Local 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& languages);
+#endif
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
void LoadChromeExtension(const base::FilePath extension_path);
diff --git a/shell/browser/atom_browser_client.cc b/shell/browser/atom_browser_client.cc
index edb592c867..5c2a6fecc9 100644
--- a/shell/browser/atom_browser_client.cc
+++ b/shell/browser/atom_browser_client.cc
@@ -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()) {
+ SpellCheckHostChromeImpl::Create(render_process_host->GetID(),
+ std::move(host_receiver));
+ return;
+ }
+}
+#endif
+
base::Optional
AtomBrowserClient::GetServiceManifestOverlay(base::StringPiece name) {
if (name == content::mojom::kBrowserServiceName)
diff --git a/shell/browser/atom_browser_client.h b/shell/browser/atom_browser_client.h
index b4631b5620..7ce978d249 100644
--- a/shell/browser/atom_browser_client.h
+++ b/shell/browser/atom_browser_client.h
@@ -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 GetServiceManifestOverlay(
base::StringPiece name) override;
content::MediaObserver* GetMediaObserver() override;
diff --git a/shell/browser/atom_browser_context.cc b/shell/browser/atom_browser_context.cc
index 9175639509..aa83f66ed5 100644
--- a/shell/browser/atom_browser_context.cc
+++ b/shell/browser/atom_browser_context.cc
@@ -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(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) {
diff --git a/shell/browser/atom_browser_main_parts.cc b/shell/browser/atom_browser_main_parts.cc
index e54951818f..f59c144afa 100644
--- a/shell/browser/atom_browser_main_parts.cc
+++ b/shell/browser/atom_browser_main_parts.cc
@@ -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();
diff --git a/shell/browser/web_contents_preferences.cc b/shell/browser/web_contents_preferences.cc
index 45f9bf25a0..05e9bda244 100644
--- a/shell/browser/web_contents_preferences.cc
+++ b/shell/browser/web_contents_preferences.cc
@@ -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 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
diff --git a/shell/common/gin_converters/content_converter.cc b/shell/common/gin_converters/content_converter.cc
index 8c192a1863..dfc0cafb67 100644
--- a/shell/common/gin_converters/content_converter.cc
+++ b/shell/common/gin_converters/content_converter.cc
@@ -138,6 +138,9 @@ v8::Local Converter::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);
diff --git a/shell/common/options_switches.cc b/shell/common/options_switches.cc
index e409c875f6..a0b0de56e5 100644
--- a/shell/common/options_switches.cc
+++ b/shell/common/options_switches.cc
@@ -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
diff --git a/shell/common/options_switches.h b/shell/common/options_switches.h
index e24dfa2d8f..b678f03445 100644
--- a/shell/common/options_switches.h
+++ b/shell/common/options_switches.h
@@ -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
diff --git a/shell/renderer/renderer_client_base.cc b/shell/renderer/renderer_client_base.cc
index 50c7628014..a89cc5d8a7 100644
--- a/shell/renderer/renderer_client_base.cc
+++ b/shell/renderer/renderer_client_base.cc
@@ -6,6 +6,7 @@
#include
#include
+#include
#include
#include "base/command_line.h"
@@ -45,6 +46,11 @@
#include
#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(®istry_, 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.
diff --git a/shell/renderer/renderer_client_base.h b/shell/renderer/renderer_client_base.h
index d06cf2508d..c6a80ab53a 100644
--- a/shell/renderer/renderer_client_base.h
+++ b/shell/renderer/renderer_client_base.h
@@ -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 context,
content::RenderFrame* render_frame);
virtual void WillReleaseScriptContext(v8::Handle 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_;
+ service_manager::BinderRegistry registry_;
+#endif
};
} // namespace electron