[Spellcheck] Finish Windows native spell checker integration

This CL finishes the Windows native spell checker integration by adding the Hunspell fallback logic. The Chrome spell checker will use the native spell checker for all supported languages, and use Hunspell for the rest of the languages. A language is supported by the native spell checker if the user has installed the necessary Windows language pack in the OS settings.

Hunspell fallback design doc (includes a summary of the changes):
https://docs.google.com/document/d/1YorylZ2lhOSnSraSULuqFZ-7xfXuVvd4THspFbJh6cM

Windows Spell Checker launch design doc:
https://docs.google.com/document/d/1a0sy94wQprEtcccDCzI65y8eIilFavFS4gp1RKYuMTQ

NOTE: Tests and metrics will follow in a separate CL

Bug: 978460, 463364
Change-Id: I58abd485f2face3b94eb8b872dca9e3df755b30e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1918277
Reviewed-by: Ken Buchanan <[email protected]>
Reviewed-by: Ɓukasz Anforowicz <[email protected]>
Reviewed-by: Rouslan Solomakhin <[email protected]>
Commit-Queue: Guillaume Jenkins <[email protected]>
Cr-Commit-Position: refs/heads/master@{#722198}
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 93746f1..0a024963 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4366,6 +4366,13 @@
      flag_descriptions::kWinUseBrowserSpellCheckerName,
      flag_descriptions::kWinUseBrowserSpellCheckerDescription, kOsWin,
      FEATURE_VALUE_TYPE(spellcheck::kWinUseBrowserSpellChecker)},
+
+#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+    {"win-use-hybrid-spellchecker",
+     flag_descriptions::kWinUseHybridSpellCheckerName,
+     flag_descriptions::kWinUseHybridSpellCheckerDescription, kOsWin,
+     FEATURE_VALUE_TYPE(spellcheck::kWinUseHybridSpellChecker)},
+#endif  // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
 #endif  // BUILDFLAG(ENABLE_SPELLCHECK) && defined(OS_WIN)
 
     {"safety-tips", flag_descriptions::kSafetyTipName,
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 8edcb62..d391900 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3690,9 +3690,14 @@
     "expiry_milestone": 82
   },
   {
+    "name": "win-use-hybrid-spellchecker",
+    "owners": [ "[email protected]", "[email protected]" ],
+    "expiry_milestone": 83
+  },
+  {
     "name": "win-use-native-spellchecker",
-    "owners": [ "[email protected]", "[email protected]" ],
-    "expiry_milestone": 81
+    "owners": [ "[email protected]", "[email protected]" ],
+    "expiry_milestone": 83
   },
   {
     "name": "windows-mixed-reality",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 1d717be9..959037c 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3093,10 +3093,23 @@
     "When enabled, use XPS printing API instead of the GDI print API.";
 
 #if BUILDFLAG(ENABLE_SPELLCHECK)
-const char kWinUseBrowserSpellCheckerName[] = "Use the Windows OS spellchecker";
+const char kWinUseBrowserSpellCheckerName[] =
+    "Use the Windows OS spell checker";
 const char kWinUseBrowserSpellCheckerDescription[] =
-    "Use the Windows OS spellchecker to find spelling mistakes and provide "
+    "Use the Windows OS spell checker to find spelling mistakes and provide "
     "spelling suggestions instead of using the Hunspell engine.";
+
+#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+const char kWinUseHybridSpellCheckerName[] =
+    "Use hybrid spell checking on Windows";
+const char kWinUseHybridSpellCheckerDescription[] =
+    "Use both the Windows OS spell checker and the Hunspell engine to find "
+    "spelling mistakes and provide spelling suggestions. Use the Windows OS "
+    "spell checker first, but if a language isn't supported, fall back to the "
+    "Hunspell engine. The \"Use the Windows OS spell checker\" feature flag "
+    "must be enabled, otherwise this will have no effect.";
+#endif  // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+
 #endif  // BUILDFLAG(ENABLE_SPELLCHECK)
 
 #endif  // defined(OS_WIN)
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 8571e4c8..7b91c62 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1811,6 +1811,12 @@
 #if BUILDFLAG(ENABLE_SPELLCHECK)
 extern const char kWinUseBrowserSpellCheckerName[];
 extern const char kWinUseBrowserSpellCheckerDescription[];
+
+#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+extern const char kWinUseHybridSpellCheckerName[];
+extern const char kWinUseHybridSpellCheckerDescription[];
+#endif  // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+
 #endif  // BUILDFLAG(ENABLE_SPELLCHECK)
 
 #endif  // defined(OS_WIN)
diff --git a/chrome/browser/site_isolation/spellcheck_per_process_browsertest.cc b/chrome/browser/site_isolation/spellcheck_per_process_browsertest.cc
index c539bca..1b5c8223 100644
--- a/chrome/browser/site_isolation/spellcheck_per_process_browsertest.cc
+++ b/chrome/browser/site_isolation/spellcheck_per_process_browsertest.cc
@@ -124,6 +124,26 @@
                           FillSuggestionListCallback) override {}
 #endif
 
+#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+  void GetPerLanguageSuggestions(
+      const base::string16& word,
+      GetPerLanguageSuggestionsCallback callback) override {
+    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+    std::move(callback).Run(std::vector<std::vector<base::string16>>());
+  }
+
+  void RequestPartialTextCheck(
+      const base::string16& text,
+      int route_id,
+      const std::vector<SpellCheckResult>& partial_results,
+      bool fill_suggestions,
+      RequestPartialTextCheckCallback callback) override {
+    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+    std::move(callback).Run(std::vector<SpellCheckResult>());
+    TextReceived(text);
+  }
+#endif  // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+
 #if defined(OS_ANDROID)
   // spellcheck::mojom::SpellCheckHost:
   void DisconnectSessionBridge() override {}
diff --git a/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc b/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
index 548a8dea..d13c36c5 100644
--- a/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
+++ b/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
@@ -21,6 +21,10 @@
 #include "chrome/browser/spellchecker/spelling_request.h"
 #endif
 
+#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+#include "components/spellcheck/common/spellcheck_common.h"
+#endif  // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+
 namespace {
 
 SpellCheckHostChromeImpl::Binder& GetSpellCheckHostBinderOverride() {
@@ -95,7 +99,7 @@
 
   if (text.empty()) {
     std::move(callback).Run(false, std::vector<SpellCheckResult>());
-    mojo::ReportBadMessage(__FUNCTION__);
+    mojo::ReportBadMessage("Requested spelling service with empty text");
     return;
   }
 
@@ -171,6 +175,42 @@
     int route_id,
     RequestTextCheckCallback callback) {
   DCHECK(!text.empty());
+
+  // OK to store unretained |this| in a |SpellingRequest| owned by |this|.
+  auto request = std::make_unique<SpellingRequest>(
+      &client_, text, render_process_id_, route_id, std::move(callback),
+      base::BindOnce(&SpellCheckHostChromeImpl::OnRequestFinished,
+                     base::Unretained(this)));
+  QueueRequest(std::move(request));
+}
+
+#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+void SpellCheckHostChromeImpl::GetPerLanguageSuggestions(
+    const base::string16& word,
+    GetPerLanguageSuggestionsCallback callback) {
+  spellcheck_platform::GetPerLanguageSuggestions(word, std::move(callback));
+}
+
+void SpellCheckHostChromeImpl::RequestPartialTextCheck(
+    const base::string16& text,
+    int route_id,
+    const std::vector<SpellCheckResult>& partial_results,
+    bool fill_suggestions,
+    RequestPartialTextCheckCallback callback) {
+  DCHECK(!text.empty());
+
+  // OK to store unretained |this| in a |SpellingRequest| owned by |this|.
+  auto request = std::make_unique<SpellingRequest>(
+      &client_, text, render_process_id_, route_id, partial_results,
+      fill_suggestions, std::move(callback),
+      base::BindOnce(&SpellCheckHostChromeImpl::OnRequestFinished,
+                     base::Unretained(this)));
+  QueueRequest(std::move(request));
+}
+#endif  // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+
+void SpellCheckHostChromeImpl::QueueRequest(
+    std::unique_ptr<SpellingRequest> request) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   // Initialize the spellcheck service if needed. The service will send the
@@ -179,12 +219,7 @@
   // happen on UI thread.
   GetSpellcheckService();
 
-  // |SpellingRequest| self-destructs on completion.
-  // OK to store unretained |this| in a |SpellingRequest| owned by |this|.
-  requests_.insert(std::make_unique<SpellingRequest>(
-      &client_, text, render_process_id_, route_id, std::move(callback),
-      base::BindOnce(&SpellCheckHostChromeImpl::OnRequestFinished,
-                     base::Unretained(this))));
+  requests_.insert(std::move(request));
 }
 
 void SpellCheckHostChromeImpl::OnRequestFinished(SpellingRequest* request) {
diff --git a/chrome/browser/spellchecker/spell_check_host_chrome_impl.h b/chrome/browser/spellchecker/spell_check_host_chrome_impl.h
index 5be01dd5..074b32a 100644
--- a/chrome/browser/spellchecker/spell_check_host_chrome_impl.h
+++ b/chrome/browser/spellchecker/spell_check_host_chrome_impl.h
@@ -78,6 +78,20 @@
                         int route_id,
                         RequestTextCheckCallback callback) override;
 
+#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+  void GetPerLanguageSuggestions(
+      const base::string16& word,
+      GetPerLanguageSuggestionsCallback callback) override;
+  void RequestPartialTextCheck(
+      const base::string16& text,
+      int route_id,
+      const std::vector<SpellCheckResult>& partial_results,
+      bool fill_suggestions,
+      RequestPartialTextCheckCallback callback) override;
+#endif  // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+
+  void QueueRequest(std::unique_ptr<SpellingRequest> request);
+
   // Clears a finished request from |requests_|. Exposed to SpellingRequest.
   void OnRequestFinished(SpellingRequest* request);
 
diff --git a/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc b/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc
index 78432896..8f1c8d5 100644
--- a/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc
+++ b/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc
@@ -139,27 +139,19 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
 #if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
-  if (spellcheck::UseBrowserSpellChecker()) {
-    if (spellcheck_platform::SpellCheckerAvailable() &&
-        spellcheck_platform::PlatformSupportsLanguage(language_)) {
-      spellcheck_platform::SetLanguage(
-          language_, base::BindOnce(&SpellcheckHunspellDictionary::
-                                        SpellCheckPlatformSetLanguageCompleted,
-                                    base::Unretained(this)));
-    }
+  if (spellcheck::UseBrowserSpellChecker() &&
+      spellcheck_platform::SpellCheckerAvailable()) {
+    spellcheck_platform::PlatformSupportsLanguage(
+        language_,
+        base::BindOnce(
+            &SpellcheckHunspellDictionary::PlatformSupportsLanguageComplete,
+            base::Unretained(this)));
     return;
   }
 #endif  // USE_BROWSER_SPELLCHECKER
 
-// Android does not support hunspell.
-#if !defined(OS_ANDROID)
-  base::PostTaskAndReplyWithResult(
-      task_runner_.get(), FROM_HERE,
-      base::BindOnce(&InitializeDictionaryLocation, language_),
-      base::BindOnce(
-          &SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete,
-          weak_ptr_factory_.GetWeakPtr()));
-#endif  // !OS_ANDROID
+  // Platform spellchecker isn't enabled, so the language is unsupported.
+  PlatformSupportsLanguageComplete(false);
 }
 
 void SpellcheckHunspellDictionary::RetryDownloadDictionary(
@@ -445,8 +437,49 @@
     observer.OnHunspellDictionaryDownloadFailure(language_);
 }
 
+void SpellcheckHunspellDictionary::PlatformSupportsLanguageComplete(
+    bool platform_supports_language) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  if (platform_supports_language) {
 #if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
-void SpellcheckHunspellDictionary::SpellCheckPlatformSetLanguageCompleted(
+    if (spellcheck::UseBrowserSpellChecker()) {
+      spellcheck_platform::SetLanguage(
+          language_, base::BindOnce(&SpellcheckHunspellDictionary::
+                                        SpellCheckPlatformSetLanguageComplete,
+                                    base::Unretained(this)));
+      return;
+    }
+#endif  // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+    NOTREACHED();
+  } else {
+#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+    // The platform spellchecker doesn't support this language. Fall back to
+    // Hunspell, unless the hybrid spellchecker is disabled.
+    if (spellcheck::UseBrowserSpellChecker() &&
+        !spellcheck::UseWinHybridSpellChecker()) {
+      // Can't fall back to Hunspell, because the hybrid spellchecker is not
+      // enabled. We can't spellcheck this language, so there's no further
+      // processing to do.
+      return;
+    }
+#endif  // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+    // Either the platform spellchecker is unavailable / disabled, or it doesn't
+    // support this language. In either case, we must use Hunspell for this
+    // language, unless we are on Android, which doesn't support Hunspell.
+#if !defined(OS_ANDROID) && BUILDFLAG(USE_RENDERER_SPELLCHECKER)
+    base::PostTaskAndReplyWithResult(
+        task_runner_.get(), FROM_HERE,
+        base::BindOnce(&InitializeDictionaryLocation, language_),
+        base::BindOnce(
+            &SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete,
+            weak_ptr_factory_.GetWeakPtr()));
+#endif  // !defined(OS_ANDROID) && BUILDFLAG(USE_RENDERER_SPELLCHECKER)
+  }
+}
+
+#if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+void SpellcheckHunspellDictionary::SpellCheckPlatformSetLanguageComplete(
     bool result) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
diff --git a/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h b/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h
index 9181170..074e951 100644
--- a/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h
+++ b/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h
@@ -136,7 +136,7 @@
 #endif
 
 #if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
-  void SpellCheckPlatformSetLanguageCompleted(bool result);
+  void SpellCheckPlatformSetLanguageComplete(bool result);
 #endif
 
   // The reply point for PostTaskAndReplyWithResult, called after the dictionary
@@ -149,6 +149,10 @@
   // Notify listeners that the dictionary download failed.
   void InformListenersOfDownloadFailure();
 
+  // Callback for asynchronously checking if the platform supports a language
+  // for spellchecking.
+  void PlatformSupportsLanguageComplete(bool platform_supports_language);
+
   // Task runner where the file operations takes place.
   scoped_refptr<base::SequencedTaskRunner> const task_runner_;
 
diff --git a/chrome/browser/spellchecker/spelling_request.cc b/chrome/browser/spellchecker/spelling_request.cc
index 73a1f30..e3ba12e 100644
--- a/chrome/browser/spellchecker/spelling_request.cc
+++ b/chrome/browser/spellchecker/spelling_request.cc
@@ -11,6 +11,7 @@
 #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"
@@ -35,16 +36,29 @@
       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(document_tag);
+  StartRequest(client, render_process_id, document_tag);
 }
 
+#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+SpellingRequest::SpellingRequest(
+    SpellingServiceClient* client,
+    const base::string16& text,
+    int render_process_id,
+    int document_tag,
+    const std::vector<SpellCheckResult>& partial_results,
+    bool fill_suggestions,
+    RequestPartialTextCheckCallback callback,
+    DestructionCallback destruction_callback)
+    : remote_success_(false),
+      text_(text),
+      partial_results_(partial_results),
+      fill_suggestions_(fill_suggestions),
+      callback_(std::move(callback)),
+      destruction_callback_(std::move(destruction_callback)) {
+  StartRequest(client, render_process_id, document_tag);
+}
+#endif  // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+
 SpellingRequest::~SpellingRequest() = default;
 
 // static
@@ -73,6 +87,19 @@
   }
 }
 
+void SpellingRequest::StartRequest(SpellingServiceClient* client,
+                                   int render_process_id,
+                                   int document_tag) {
+  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(document_tag);
+}
+
 void SpellingRequest::RequestRemoteCheck(SpellingServiceClient* client,
                                          int render_process_id) {
   auto* host = content::RenderProcessHost::FromID(render_process_id);
@@ -88,6 +115,16 @@
 
 void SpellingRequest::RequestLocalCheck(int document_tag) {
   // |this| may be gone at callback invocation if the owner has been removed.
+#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+  if (spellcheck::UseWinHybridSpellChecker()) {
+    spellcheck_platform::RequestTextCheck(
+        document_tag, text_, std::move(partial_results_), fill_suggestions_,
+        base::BindOnce(&SpellingRequest::OnLocalCheckCompletedOnAnyThread,
+                       weak_factory_.GetWeakPtr()));
+    return;
+  }
+#endif  // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+
   spellcheck_platform::RequestTextCheck(
       document_tag, text_,
       base::BindOnce(&SpellingRequest::OnLocalCheckCompletedOnAnyThread,
diff --git a/chrome/browser/spellchecker/spelling_request.h b/chrome/browser/spellchecker/spelling_request.h
index be895a47..511c2f5 100644
--- a/chrome/browser/spellchecker/spelling_request.h
+++ b/chrome/browser/spellchecker/spelling_request.h
@@ -20,6 +20,11 @@
       spellcheck::mojom::SpellCheckHost::RequestTextCheckCallback;
   using DestructionCallback = base::OnceCallback<void(SpellingRequest*)>;
 
+#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+  using RequestPartialTextCheckCallback =
+      spellcheck::mojom::SpellCheckHost::RequestPartialTextCheckCallback;
+#endif  // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+
   SpellingRequest(SpellingServiceClient* client,
                   const base::string16& text,
                   int render_process_id,
@@ -27,6 +32,17 @@
                   RequestTextCheckCallback callback,
                   DestructionCallback destruction_callback);
 
+#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+  SpellingRequest(SpellingServiceClient* client,
+                  const base::string16& text,
+                  int render_process_id,
+                  int document_tag,
+                  const std::vector<SpellCheckResult>& partial_results,
+                  bool fill_suggestions,
+                  RequestPartialTextCheckCallback callback,
+                  DestructionCallback destruction_callback);
+#endif  // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+
   ~SpellingRequest();
 
   // Exposed to tests only.
@@ -35,6 +51,11 @@
       const std::vector<SpellCheckResult>& local_results);
 
  private:
+  // Requests local and remote checks (starts the request).
+  void StartRequest(SpellingServiceClient* client,
+                    int render_process_id,
+                    int document_tag);
+
   // Request server-side checking for |text_|.
   void RequestRemoteCheck(SpellingServiceClient* client, int render_process_id);
 
@@ -67,7 +88,21 @@
   // The string to be spell-checked.
   base::string16 text_;
 
-  // Callback to send the results to renderer.
+#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+  // Partial results from Hunspell for locales unsupported by the native
+  // spellchecker.
+  std::vector<SpellCheckResult> partial_results_;
+
+  // Whether the local check should fill the suggestions at spellcheck time. If
+  // this is false, no suggestions will be looked up (it will instead happen
+  // when the user explicitly requests suggestions).
+  bool fill_suggestions_;
+#endif  // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER)
+
+  // Callback to send the results to renderer. Note that both RequestTextCheck
+  // and RequestPartialTextCheck have the same callback signatures, so both
+  // callback types can be assigned to this member (the generated
+  // base::OnceCallback types are the same).
   RequestTextCheckCallback callback_;
 
   // Callback to delete |this|. Called on |this| after everything is done.