electron_api_spell_check_client.cc 9.3 KB

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