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
39namespace WebKit {
40using namespace WebCore;
41
42TextCheckerState& 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
54const TextCheckerState& TextChecker::state()
55{
56 return checkerState();
57}
58
59static bool testingModeEnabled = false;
60
61void TextChecker::setTestingMode(bool enabled)
62{
63 testingModeEnabled = enabled;
64}
65
66bool TextChecker::isTestingMode()
67{
68 return testingModeEnabled;
69}
70
71#if ENABLE(SPELLCHECK)
72static void updateStateForAllProcessPools()
73{
74 for (const auto& processPool : WebProcessPool::allProcessPools())
75 processPool->textCheckerStateChanged();
76}
77#endif
78
79bool TextChecker::isContinuousSpellCheckingAllowed()
80{
81#if ENABLE(SPELLCHECK)
82 return true;
83#else
84 return false;
85#endif
86}
87
88void 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
100void 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
112void TextChecker::continuousSpellCheckingEnabledStateChanged(bool enabled)
113{
114#if ENABLE(SPELLCHECK)
115 checkerState().isContinuousSpellCheckingEnabled = enabled;
116#else
117 UNUSED_PARAM(enabled);
118#endif
119}
120
121void TextChecker::grammarCheckingEnabledStateChanged(bool enabled)
122{
123#if ENABLE(SPELLCHECK)
124 checkerState().isGrammarCheckingEnabled = enabled;
125#else
126 UNUSED_PARAM(enabled);
127#endif
128}
129
130SpellDocumentTag TextChecker::uniqueSpellDocumentTag(WebPageProxy*)
131{
132 return { };
133}
134
135void TextChecker::closeSpellDocumentWithTag(SpellDocumentTag)
136{
137}
138
139void 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
152void TextChecker::checkGrammarOfString(SpellDocumentTag, StringView /* text */, Vector<WebCore::GrammarDetail>& /* grammarDetails */, int32_t& /* badGrammarLocation */, int32_t& /* badGrammarLength */)
153{
154}
155
156bool TextChecker::spellingUIIsShowing()
157{
158 return false;
159}
160
161void TextChecker::toggleSpellingUIIsShowing()
162{
163}
164
165void TextChecker::updateSpellingUIWithMisspelledWord(SpellDocumentTag, const String& /* misspelledWord */)
166{
167}
168
169void TextChecker::updateSpellingUIWithGrammarString(SpellDocumentTag, const String& /* badGrammarPhrase */, const GrammarDetail& /* grammarDetail */)
170{
171}
172
173void 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
183void 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
192void 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
201void 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)
215static 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)
243Vector<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
288void 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
297Vector<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