1 | /* |
2 | * Copyright (C) 2010 Apple Inc. All rights reserved. |
3 | * Portions Copyright (c) 2010 Motorola Mobility, Inc. All rights reserved. |
4 | * Copyright (C) 2011-2013 Samsung Electronics |
5 | * |
6 | * Redistribution and use in source and binary forms, with or without |
7 | * modification, are permitted provided that the following conditions |
8 | * are met: |
9 | * 1. Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * 2. Redistributions in binary form must reproduce the above copyright |
12 | * notice, this list of conditions and the following disclaimer in the |
13 | * documentation and/or other materials provided with the distribution. |
14 | * |
15 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
17 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
18 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
19 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
25 | * THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ |
27 | |
28 | #include "config.h" |
29 | #include "TextChecker.h" |
30 | |
31 | #include "TextCheckerState.h" |
32 | #include "WebProcessPool.h" |
33 | #include <WebCore/NotImplemented.h> |
34 | #include <WebCore/TextCheckerEnchant.h> |
35 | #include <unicode/ubrk.h> |
36 | #include <wtf/NeverDestroyed.h> |
37 | #include <wtf/text/TextBreakIterator.h> |
38 | |
39 | namespace WebKit { |
40 | using namespace WebCore; |
41 | |
42 | TextCheckerState& checkerState() |
43 | { |
44 | static TextCheckerState textCheckerState; |
45 | static std::once_flag onceFlag; |
46 | std::call_once(onceFlag, [] { |
47 | textCheckerState.isContinuousSpellCheckingEnabled = false; |
48 | textCheckerState.isGrammarCheckingEnabled = false; |
49 | }); |
50 | |
51 | return textCheckerState; |
52 | } |
53 | |
54 | const TextCheckerState& TextChecker::state() |
55 | { |
56 | return checkerState(); |
57 | } |
58 | |
59 | static bool testingModeEnabled = false; |
60 | |
61 | void TextChecker::setTestingMode(bool enabled) |
62 | { |
63 | testingModeEnabled = enabled; |
64 | } |
65 | |
66 | bool TextChecker::isTestingMode() |
67 | { |
68 | return testingModeEnabled; |
69 | } |
70 | |
71 | #if ENABLE(SPELLCHECK) |
72 | static void updateStateForAllProcessPools() |
73 | { |
74 | for (const auto& processPool : WebProcessPool::allProcessPools()) |
75 | processPool->textCheckerStateChanged(); |
76 | } |
77 | #endif |
78 | |
79 | bool TextChecker::isContinuousSpellCheckingAllowed() |
80 | { |
81 | #if ENABLE(SPELLCHECK) |
82 | return true; |
83 | #else |
84 | return false; |
85 | #endif |
86 | } |
87 | |
88 | void TextChecker::setContinuousSpellCheckingEnabled(bool isContinuousSpellCheckingEnabled) |
89 | { |
90 | #if ENABLE(SPELLCHECK) |
91 | if (checkerState().isContinuousSpellCheckingEnabled == isContinuousSpellCheckingEnabled) |
92 | return; |
93 | checkerState().isContinuousSpellCheckingEnabled = isContinuousSpellCheckingEnabled; |
94 | updateStateForAllProcessPools(); |
95 | #else |
96 | UNUSED_PARAM(isContinuousSpellCheckingEnabled); |
97 | #endif |
98 | } |
99 | |
100 | void TextChecker::setGrammarCheckingEnabled(bool isGrammarCheckingEnabled) |
101 | { |
102 | #if ENABLE(SPELLCHECK) |
103 | if (checkerState().isGrammarCheckingEnabled == isGrammarCheckingEnabled) |
104 | return; |
105 | checkerState().isGrammarCheckingEnabled = isGrammarCheckingEnabled; |
106 | updateStateForAllProcessPools(); |
107 | #else |
108 | UNUSED_PARAM(isGrammarCheckingEnabled); |
109 | #endif |
110 | } |
111 | |
112 | void TextChecker::continuousSpellCheckingEnabledStateChanged(bool enabled) |
113 | { |
114 | #if ENABLE(SPELLCHECK) |
115 | checkerState().isContinuousSpellCheckingEnabled = enabled; |
116 | #else |
117 | UNUSED_PARAM(enabled); |
118 | #endif |
119 | } |
120 | |
121 | void TextChecker::grammarCheckingEnabledStateChanged(bool enabled) |
122 | { |
123 | #if ENABLE(SPELLCHECK) |
124 | checkerState().isGrammarCheckingEnabled = enabled; |
125 | #else |
126 | UNUSED_PARAM(enabled); |
127 | #endif |
128 | } |
129 | |
130 | SpellDocumentTag TextChecker::uniqueSpellDocumentTag(WebPageProxy*) |
131 | { |
132 | return { }; |
133 | } |
134 | |
135 | void TextChecker::closeSpellDocumentWithTag(SpellDocumentTag) |
136 | { |
137 | } |
138 | |
139 | void TextChecker::checkSpellingOfString(SpellDocumentTag, StringView text, int32_t& misspellingLocation, int32_t& misspellingLength) |
140 | { |
141 | #if ENABLE(SPELLCHECK) |
142 | misspellingLocation = -1; |
143 | misspellingLength = 0; |
144 | TextCheckerEnchant::singleton().checkSpellingOfString(text.toStringWithoutCopying(), misspellingLocation, misspellingLength); |
145 | #else |
146 | UNUSED_PARAM(text); |
147 | UNUSED_PARAM(misspellingLocation); |
148 | UNUSED_PARAM(misspellingLength); |
149 | #endif |
150 | } |
151 | |
152 | void TextChecker::checkGrammarOfString(SpellDocumentTag, StringView /* text */, Vector<WebCore::GrammarDetail>& /* grammarDetails */, int32_t& /* badGrammarLocation */, int32_t& /* badGrammarLength */) |
153 | { |
154 | } |
155 | |
156 | bool TextChecker::spellingUIIsShowing() |
157 | { |
158 | return false; |
159 | } |
160 | |
161 | void TextChecker::toggleSpellingUIIsShowing() |
162 | { |
163 | } |
164 | |
165 | void TextChecker::updateSpellingUIWithMisspelledWord(SpellDocumentTag, const String& /* misspelledWord */) |
166 | { |
167 | } |
168 | |
169 | void TextChecker::updateSpellingUIWithGrammarString(SpellDocumentTag, const String& /* badGrammarPhrase */, const GrammarDetail& /* grammarDetail */) |
170 | { |
171 | } |
172 | |
173 | void TextChecker::getGuessesForWord(SpellDocumentTag, const String& word, const String& /* context */, int32_t /* insertionPoint */, Vector<String>& guesses, bool) |
174 | { |
175 | #if ENABLE(SPELLCHECK) |
176 | guesses = TextCheckerEnchant::singleton().getGuessesForWord(word); |
177 | #else |
178 | UNUSED_PARAM(word); |
179 | UNUSED_PARAM(guesses); |
180 | #endif |
181 | } |
182 | |
183 | void TextChecker::learnWord(SpellDocumentTag, const String& word) |
184 | { |
185 | #if ENABLE(SPELLCHECK) |
186 | TextCheckerEnchant::singleton().learnWord(word); |
187 | #else |
188 | UNUSED_PARAM(word); |
189 | #endif |
190 | } |
191 | |
192 | void TextChecker::ignoreWord(SpellDocumentTag, const String& word) |
193 | { |
194 | #if ENABLE(SPELLCHECK) |
195 | TextCheckerEnchant::singleton().ignoreWord(word); |
196 | #else |
197 | UNUSED_PARAM(word); |
198 | #endif |
199 | } |
200 | |
201 | void TextChecker::requestCheckingOfString(Ref<TextCheckerCompletion>&& completion, int32_t insertionPoint) |
202 | { |
203 | #if ENABLE(SPELLCHECK) |
204 | TextCheckingRequestData request = completion->textCheckingRequestData(); |
205 | ASSERT(request.sequence() != unrequestedTextCheckingSequence); |
206 | ASSERT(request.checkingTypes()); |
207 | |
208 | completion->didFinishCheckingText(checkTextOfParagraph(completion->spellDocumentTag(), request.text(), insertionPoint, request.checkingTypes(), false)); |
209 | #else |
210 | UNUSED_PARAM(completion); |
211 | #endif |
212 | } |
213 | |
214 | #if USE(UNIFIED_TEXT_CHECKING) && ENABLE(SPELLCHECK) |
215 | static unsigned nextWordOffset(StringView text, unsigned currentOffset) |
216 | { |
217 | // FIXME: avoid creating textIterator object here, it could be passed as a parameter. |
218 | // ubrk_isBoundary() leaves the iterator pointing to the first boundary position at |
219 | // or after "offset" (ubrk_isBoundary side effect). |
220 | // For many word separators, the method doesn't properly determine the boundaries |
221 | // without resetting the iterator. |
222 | UBreakIterator* textIterator = wordBreakIterator(text); |
223 | if (!textIterator) |
224 | return currentOffset; |
225 | |
226 | unsigned wordOffset = currentOffset; |
227 | while (wordOffset < text.length() && ubrk_isBoundary(textIterator, wordOffset)) |
228 | ++wordOffset; |
229 | |
230 | // Do not treat the word's boundary as a separator. |
231 | if (!currentOffset && wordOffset == 1) |
232 | return currentOffset; |
233 | |
234 | // Omit multiple separators. |
235 | if ((wordOffset - currentOffset) > 1) |
236 | --wordOffset; |
237 | |
238 | return wordOffset; |
239 | } |
240 | #endif |
241 | |
242 | #if USE(UNIFIED_TEXT_CHECKING) |
243 | Vector<TextCheckingResult> TextChecker::checkTextOfParagraph(SpellDocumentTag spellDocumentTag, StringView text, int32_t insertionPoint, OptionSet<TextCheckingType> checkingTypes, bool) |
244 | { |
245 | UNUSED_PARAM(insertionPoint); |
246 | #if ENABLE(SPELLCHECK) |
247 | if (!checkingTypes.contains(TextCheckingType::Spelling)) |
248 | return { }; |
249 | |
250 | UBreakIterator* textIterator = wordBreakIterator(text); |
251 | if (!textIterator) |
252 | return { }; |
253 | |
254 | // Omit the word separators at the beginning/end of the text to don't unnecessarily |
255 | // involve the client to check spelling for them. |
256 | unsigned offset = nextWordOffset(text, 0); |
257 | unsigned lengthStrip = text.length(); |
258 | while (lengthStrip > 0 && ubrk_isBoundary(textIterator, lengthStrip - 1)) |
259 | --lengthStrip; |
260 | |
261 | Vector<TextCheckingResult> paragraphCheckingResult; |
262 | while (offset < lengthStrip) { |
263 | int32_t misspellingLocation = -1; |
264 | int32_t misspellingLength = 0; |
265 | checkSpellingOfString(spellDocumentTag, text.substring(offset, lengthStrip - offset), misspellingLocation, misspellingLength); |
266 | if (!misspellingLength) |
267 | break; |
268 | |
269 | TextCheckingResult misspellingResult; |
270 | misspellingResult.type = TextCheckingType::Spelling; |
271 | misspellingResult.location = offset + misspellingLocation; |
272 | misspellingResult.length = misspellingLength; |
273 | paragraphCheckingResult.append(misspellingResult); |
274 | offset += misspellingLocation + misspellingLength; |
275 | // Generally, we end up checking at the word separator, move to the adjacent word. |
276 | offset = nextWordOffset(text.substring(0, lengthStrip), offset); |
277 | } |
278 | return paragraphCheckingResult; |
279 | #else |
280 | UNUSED_PARAM(spellDocumentTag); |
281 | UNUSED_PARAM(text); |
282 | UNUSED_PARAM(checkingTypes); |
283 | return Vector<TextCheckingResult>(); |
284 | #endif // ENABLE(SPELLCHECK) |
285 | } |
286 | #endif // USE(UNIFIED_TEXT_CHECKING) |
287 | |
288 | void TextChecker::setSpellCheckingLanguages(const Vector<String>& languages) |
289 | { |
290 | #if ENABLE(SPELLCHECK) |
291 | TextCheckerEnchant::singleton().updateSpellCheckingLanguages(languages); |
292 | #else |
293 | UNUSED_PARAM(languages); |
294 | #endif |
295 | } |
296 | |
297 | Vector<String> TextChecker::loadedSpellCheckingLanguages() |
298 | { |
299 | #if ENABLE(SPELLCHECK) |
300 | return TextCheckerEnchant::singleton().loadedSpellCheckingLanguages(); |
301 | #else |
302 | return Vector<String>(); |
303 | #endif |
304 | } |
305 | |
306 | } // namespace WebKit |
307 | |