electron_api_spell_check_client.cc 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. // Copyright (c) 2014 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/renderer/api/electron_api_spell_check_client.h"
  5. #include <iterator>
  6. #include <memory>
  7. #include <set>
  8. #include <string_view>
  9. #include <unordered_set>
  10. #include <utility>
  11. #include <vector>
  12. #include "base/containers/contains.h"
  13. #include "base/logging.h"
  14. #include "base/numerics/safe_conversions.h"
  15. #include "base/task/single_thread_task_runner.h"
  16. #include "components/spellcheck/renderer/spellcheck_worditerator.h"
  17. #include "shell/common/gin_helper/dictionary.h"
  18. #include "shell/common/gin_helper/function_template.h"
  19. #include "shell/common/gin_helper/microtasks_scope.h"
  20. #include "third_party/blink/public/web/web_text_checking_completion.h"
  21. #include "third_party/blink/public/web/web_text_checking_result.h"
  22. #include "third_party/icu/source/common/unicode/uscript.h"
  23. namespace electron::api {
  24. namespace {
  25. bool HasWordCharacters(const std::u16string& text, int index) {
  26. const char16_t* data = text.data();
  27. int length = text.length();
  28. while (index < length) {
  29. uint32_t code = 0;
  30. U16_NEXT(data, index, length, code);
  31. UErrorCode error = U_ZERO_ERROR;
  32. if (uscript_getScript(code, &error) != USCRIPT_COMMON)
  33. return true;
  34. }
  35. return false;
  36. }
  37. struct Word {
  38. blink::WebTextCheckingResult result;
  39. std::u16string text;
  40. std::vector<std::u16string> contraction_words;
  41. };
  42. } // namespace
  43. class SpellCheckClient::SpellcheckRequest {
  44. public:
  45. SpellcheckRequest(
  46. const std::u16string& text,
  47. std::unique_ptr<blink::WebTextCheckingCompletion> completion)
  48. : text_(text), completion_(std::move(completion)) {}
  49. SpellcheckRequest(const SpellcheckRequest&) = delete;
  50. SpellcheckRequest& operator=(const SpellcheckRequest&) = delete;
  51. ~SpellcheckRequest() = default;
  52. const std::u16string& text() const { return text_; }
  53. blink::WebTextCheckingCompletion* completion() { return completion_.get(); }
  54. std::vector<Word>& wordlist() { return word_list_; }
  55. private:
  56. std::u16string text_; // Text to be checked in this task.
  57. std::vector<Word> word_list_; // List of Words found in text
  58. // The interface to send the misspelled ranges to Blink.
  59. std::unique_ptr<blink::WebTextCheckingCompletion> completion_;
  60. };
  61. SpellCheckClient::SpellCheckClient(const std::string& language,
  62. v8::Isolate* isolate,
  63. v8::Local<v8::Object> provider)
  64. : isolate_(isolate),
  65. context_(isolate, isolate->GetCurrentContext()),
  66. provider_(isolate, provider) {
  67. DCHECK(!context_.IsEmpty());
  68. character_attributes_.SetDefaultLanguage(language);
  69. // Persistent the method.
  70. v8::Local<v8::Function> spell_check;
  71. gin_helper::Dictionary(isolate, provider).Get("spellCheck", &spell_check);
  72. spell_check_.Reset(isolate, spell_check);
  73. }
  74. SpellCheckClient::~SpellCheckClient() {
  75. context_.Reset();
  76. }
  77. void SpellCheckClient::RequestCheckingOfText(
  78. const blink::WebString& textToCheck,
  79. std::unique_ptr<blink::WebTextCheckingCompletion> completionCallback) {
  80. std::u16string text(textToCheck.Utf16());
  81. // Ignore invalid requests.
  82. if (text.empty() || !HasWordCharacters(text, 0)) {
  83. completionCallback->DidCancelCheckingText();
  84. return;
  85. }
  86. // Clean up the previous request before starting a new request.
  87. if (pending_request_param_) {
  88. pending_request_param_->completion()->DidCancelCheckingText();
  89. }
  90. pending_request_param_ =
  91. std::make_unique<SpellcheckRequest>(text, std::move(completionCallback));
  92. base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
  93. FROM_HERE,
  94. base::BindOnce(&SpellCheckClient::SpellCheckText, AsWeakPtr()));
  95. }
  96. bool SpellCheckClient::IsSpellCheckingEnabled() const {
  97. return true;
  98. }
  99. void SpellCheckClient::ShowSpellingUI(bool show) {}
  100. bool SpellCheckClient::IsShowingSpellingUI() {
  101. return false;
  102. }
  103. void SpellCheckClient::UpdateSpellingUIWithMisspelledWord(
  104. const blink::WebString& word) {}
  105. void SpellCheckClient::SpellCheckText() {
  106. const auto& text = pending_request_param_->text();
  107. if (text.empty() || spell_check_.IsEmpty()) {
  108. pending_request_param_->completion()->DidCancelCheckingText();
  109. pending_request_param_ = nullptr;
  110. return;
  111. }
  112. if (!text_iterator_.IsInitialized() &&
  113. !text_iterator_.Initialize(&character_attributes_, true)) {
  114. // We failed to initialize text_iterator_, return as spelled correctly.
  115. VLOG(1) << "Failed to initialize SpellcheckWordIterator";
  116. return;
  117. }
  118. if (!contraction_iterator_.IsInitialized() &&
  119. !contraction_iterator_.Initialize(&character_attributes_, false)) {
  120. // We failed to initialize the word iterator, return as spelled correctly.
  121. VLOG(1) << "Failed to initialize contraction_iterator_";
  122. return;
  123. }
  124. text_iterator_.SetText(text);
  125. SpellCheckScope scope(*this);
  126. std::u16string word;
  127. size_t word_start;
  128. size_t word_length;
  129. std::set<std::u16string> words;
  130. auto& word_list = pending_request_param_->wordlist();
  131. Word word_entry;
  132. for (;;) { // Run until end of text
  133. const auto status =
  134. text_iterator_.GetNextWord(&word, &word_start, &word_length);
  135. if (status == SpellcheckWordIterator::IS_END_OF_TEXT)
  136. break;
  137. if (status == SpellcheckWordIterator::IS_SKIPPABLE)
  138. continue;
  139. word_entry.result.location = base::checked_cast<int>(word_start);
  140. word_entry.result.length = base::checked_cast<int>(word_length);
  141. word_entry.text = word;
  142. word_entry.contraction_words.clear();
  143. word_list.push_back(word_entry);
  144. words.insert(word);
  145. // If the given word is a concatenated word of two or more valid words
  146. // (e.g. "hello:hello"), we should treat it as a valid word.
  147. if (IsContraction(scope, word, &word_entry.contraction_words)) {
  148. for (const auto& w : word_entry.contraction_words) {
  149. words.insert(w);
  150. }
  151. }
  152. }
  153. // Send out all the words data to the spellchecker to check
  154. SpellCheckWords(scope, words);
  155. }
  156. void SpellCheckClient::OnSpellCheckDone(
  157. const std::vector<std::u16string>& misspelled_words) {
  158. std::vector<blink::WebTextCheckingResult> results;
  159. std::unordered_set<std::u16string> misspelled(misspelled_words.begin(),
  160. misspelled_words.end());
  161. auto& word_list = pending_request_param_->wordlist();
  162. for (const auto& word : word_list) {
  163. if (base::Contains(misspelled, word.text)) {
  164. // If this is a contraction, iterate through parts and accept the word
  165. // if none of them are misspelled
  166. if (!word.contraction_words.empty()) {
  167. auto all_correct = true;
  168. for (const auto& contraction_word : word.contraction_words) {
  169. if (base::Contains(misspelled, contraction_word)) {
  170. all_correct = false;
  171. break;
  172. }
  173. }
  174. if (all_correct)
  175. continue;
  176. }
  177. results.push_back(word.result);
  178. }
  179. }
  180. pending_request_param_->completion()->DidFinishCheckingText(results);
  181. pending_request_param_ = nullptr;
  182. }
  183. void SpellCheckClient::SpellCheckWords(const SpellCheckScope& scope,
  184. const std::set<std::u16string>& words) {
  185. DCHECK(!scope.spell_check_.IsEmpty());
  186. auto context = isolate_->GetCurrentContext();
  187. gin_helper::MicrotasksScope microtasks_scope(
  188. isolate_, context->GetMicrotaskQueue(),
  189. v8::MicrotasksScope::kDoNotRunMicrotasks);
  190. v8::Local<v8::FunctionTemplate> templ = gin_helper::CreateFunctionTemplate(
  191. isolate_,
  192. base::BindRepeating(&SpellCheckClient::OnSpellCheckDone, AsWeakPtr()));
  193. v8::Local<v8::Value> args[] = {gin::ConvertToV8(isolate_, words),
  194. templ->GetFunction(context).ToLocalChecked()};
  195. // Call javascript with the words and the callback function
  196. scope.spell_check_->Call(context, scope.provider_, std::size(args), args)
  197. .IsEmpty();
  198. }
  199. // Returns whether or not the given string is a contraction.
  200. // This function is a fall-back when the SpellcheckWordIterator class
  201. // returns a concatenated word which is not in the selected dictionary
  202. // (e.g. "in'n'out") but each word is valid.
  203. // Output variable contraction_words will contain individual
  204. // words in the contraction.
  205. bool SpellCheckClient::IsContraction(
  206. const SpellCheckScope& scope,
  207. const std::u16string& contraction,
  208. std::vector<std::u16string>* contraction_words) {
  209. DCHECK(contraction_iterator_.IsInitialized());
  210. contraction_iterator_.SetText(contraction);
  211. std::u16string word;
  212. size_t word_start;
  213. size_t word_length;
  214. for (auto status =
  215. contraction_iterator_.GetNextWord(&word, &word_start, &word_length);
  216. status != SpellcheckWordIterator::IS_END_OF_TEXT;
  217. status = contraction_iterator_.GetNextWord(&word, &word_start,
  218. &word_length)) {
  219. if (status == SpellcheckWordIterator::IS_SKIPPABLE)
  220. continue;
  221. contraction_words->push_back(word);
  222. }
  223. return contraction_words->size() > 1;
  224. }
  225. SpellCheckClient::SpellCheckScope::SpellCheckScope(
  226. const SpellCheckClient& client)
  227. : handle_scope_(client.isolate_),
  228. context_scope_(
  229. v8::Local<v8::Context>::New(client.isolate_, client.context_)),
  230. provider_(v8::Local<v8::Object>::New(client.isolate_, client.provider_)),
  231. spell_check_(
  232. v8::Local<v8::Function>::New(client.isolate_, client.spell_check_)) {}
  233. SpellCheckClient::SpellCheckScope::~SpellCheckScope() = default;
  234. } // namespace electron::api