- add third_party src.
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / editing / TextCheckingHelper.cpp
1 /*
2  * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
3  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "core/editing/TextCheckingHelper.h"
29
30 #include "bindings/v8/ExceptionState.h"
31 #include "bindings/v8/ExceptionStatePlaceholder.h"
32 #include "core/dom/Document.h"
33 #include "core/dom/DocumentMarkerController.h"
34 #include "core/dom/Range.h"
35 #include "core/editing/TextIterator.h"
36 #include "core/editing/VisiblePosition.h"
37 #include "core/editing/VisibleUnits.h"
38 #include "core/frame/Frame.h"
39 #include "core/page/Settings.h"
40 #include "platform/text/TextBreakIterator.h"
41 #include "platform/text/TextCheckerClient.h"
42
43 namespace WebCore {
44
45 static void findBadGrammars(TextCheckerClient& client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
46 {
47     int checkLocation = start;
48     int checkLength = length;
49
50     while (0 < checkLength) {
51         int badGrammarLocation = -1;
52         int badGrammarLength = 0;
53         Vector<GrammarDetail> badGrammarDetails;
54         client.checkGrammarOfString(String(text + checkLocation, checkLength), badGrammarDetails, &badGrammarLocation, &badGrammarLength);
55         if (!badGrammarLength)
56             break;
57         ASSERT(0 <= badGrammarLocation && badGrammarLocation <= checkLength);
58         ASSERT(0 < badGrammarLength && badGrammarLocation + badGrammarLength <= checkLength);
59         TextCheckingResult badGrammar;
60         badGrammar.decoration = TextDecorationTypeGrammar;
61         badGrammar.location = checkLocation + badGrammarLocation;
62         badGrammar.length = badGrammarLength;
63         badGrammar.details.swap(badGrammarDetails);
64         results.append(badGrammar);
65
66         checkLocation += (badGrammarLocation + badGrammarLength);
67         checkLength -= (badGrammarLocation + badGrammarLength);
68     }
69 }
70
71 static void findMisspellings(TextCheckerClient& client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
72 {
73     TextBreakIterator* iterator = wordBreakIterator(text + start, length);
74     if (!iterator)
75         return;
76     int wordStart = iterator->current();
77     while (0 <= wordStart) {
78         int wordEnd = iterator->next();
79         if (wordEnd < 0)
80             break;
81         int wordLength = wordEnd - wordStart;
82         int misspellingLocation = -1;
83         int misspellingLength = 0;
84         client.checkSpellingOfString(String(text + start + wordStart, wordLength), &misspellingLocation, &misspellingLength);
85         if (0 < misspellingLength) {
86             ASSERT(0 <= misspellingLocation && misspellingLocation <= wordLength);
87             ASSERT(0 < misspellingLength && misspellingLocation + misspellingLength <= wordLength);
88             TextCheckingResult misspelling;
89             misspelling.decoration = TextDecorationTypeSpelling;
90             misspelling.location = start + wordStart + misspellingLocation;
91             misspelling.length = misspellingLength;
92             misspelling.replacement = client.getAutoCorrectSuggestionForMisspelledWord(String(text + misspelling.location, misspelling.length));
93             results.append(misspelling);
94         }
95
96         wordStart = wordEnd;
97     }
98 }
99
100 static PassRefPtr<Range> expandToParagraphBoundary(PassRefPtr<Range> range)
101 {
102     RefPtr<Range> paragraphRange = range->cloneRange(IGNORE_EXCEPTION);
103     setStart(paragraphRange.get(), startOfParagraph(range->startPosition()));
104     setEnd(paragraphRange.get(), endOfParagraph(range->endPosition()));
105     return paragraphRange;
106 }
107
108 TextCheckingParagraph::TextCheckingParagraph(PassRefPtr<Range> checkingRange)
109     : m_checkingRange(checkingRange)
110     , m_checkingStart(-1)
111     , m_checkingEnd(-1)
112     , m_checkingLength(-1)
113 {
114 }
115
116 TextCheckingParagraph::TextCheckingParagraph(PassRefPtr<Range> checkingRange, PassRefPtr<Range> paragraphRange)
117     : m_checkingRange(checkingRange)
118     , m_paragraphRange(paragraphRange)
119     , m_checkingStart(-1)
120     , m_checkingEnd(-1)
121     , m_checkingLength(-1)
122 {
123 }
124
125 TextCheckingParagraph::~TextCheckingParagraph()
126 {
127 }
128
129 void TextCheckingParagraph::expandRangeToNextEnd()
130 {
131     ASSERT(m_checkingRange);
132     setEnd(paragraphRange().get(), endOfParagraph(startOfNextParagraph(paragraphRange()->startPosition())));
133     invalidateParagraphRangeValues();
134 }
135
136 void TextCheckingParagraph::invalidateParagraphRangeValues()
137 {
138     m_checkingStart = m_checkingEnd = -1;
139     m_offsetAsRange = 0;
140     m_text = String();
141 }
142
143 int TextCheckingParagraph::rangeLength() const
144 {
145     ASSERT(m_checkingRange);
146     return TextIterator::rangeLength(paragraphRange().get());
147 }
148
149 PassRefPtr<Range> TextCheckingParagraph::paragraphRange() const
150 {
151     ASSERT(m_checkingRange);
152     if (!m_paragraphRange)
153         m_paragraphRange = expandToParagraphBoundary(checkingRange());
154     return m_paragraphRange;
155 }
156
157 PassRefPtr<Range> TextCheckingParagraph::subrange(int characterOffset, int characterCount) const
158 {
159     ASSERT(m_checkingRange);
160     return TextIterator::subrange(paragraphRange().get(), characterOffset, characterCount);
161 }
162
163 int TextCheckingParagraph::offsetTo(const Position& position, ExceptionState& es) const
164 {
165     ASSERT(m_checkingRange);
166     RefPtr<Range> range = offsetAsRange()->cloneRange(ASSERT_NO_EXCEPTION);
167     range->setEnd(position.containerNode(), position.computeOffsetInContainerNode(), es);
168     if (es.hadException())
169         return 0;
170     return TextIterator::rangeLength(range.get());
171 }
172
173 bool TextCheckingParagraph::isEmpty() const
174 {
175     // Both predicates should have same result, but we check both just for sure.
176     // We need to investigate to remove this redundancy.
177     return isRangeEmpty() || isTextEmpty();
178 }
179
180 PassRefPtr<Range> TextCheckingParagraph::offsetAsRange() const
181 {
182     ASSERT(m_checkingRange);
183     if (!m_offsetAsRange)
184         m_offsetAsRange = Range::create(paragraphRange()->startContainer()->document(), paragraphRange()->startPosition(), checkingRange()->startPosition());
185
186     return m_offsetAsRange;
187 }
188
189 const String& TextCheckingParagraph::text() const
190 {
191     ASSERT(m_checkingRange);
192     if (m_text.isEmpty())
193         m_text = plainText(paragraphRange().get());
194     return m_text;
195 }
196
197 int TextCheckingParagraph::checkingStart() const
198 {
199     ASSERT(m_checkingRange);
200     if (m_checkingStart == -1)
201         m_checkingStart = TextIterator::rangeLength(offsetAsRange().get());
202     return m_checkingStart;
203 }
204
205 int TextCheckingParagraph::checkingEnd() const
206 {
207     ASSERT(m_checkingRange);
208     if (m_checkingEnd == -1)
209         m_checkingEnd = checkingStart() + TextIterator::rangeLength(checkingRange().get());
210     return m_checkingEnd;
211 }
212
213 int TextCheckingParagraph::checkingLength() const
214 {
215     ASSERT(m_checkingRange);
216     if (-1 == m_checkingLength)
217         m_checkingLength = TextIterator::rangeLength(checkingRange().get());
218     return m_checkingLength;
219 }
220
221 TextCheckingHelper::TextCheckingHelper(EditorClient& client, PassRefPtr<Range> range)
222     : m_client(&client)
223     , m_range(range)
224 {
225     ASSERT_ARG(m_range, m_range);
226 }
227
228 TextCheckingHelper::~TextCheckingHelper()
229 {
230 }
231
232 String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange)
233 {
234     WordAwareIterator it(m_range.get());
235     firstMisspellingOffset = 0;
236
237     String firstMisspelling;
238     int currentChunkOffset = 0;
239
240     while (!it.atEnd()) {
241         int length = it.length();
242
243         // Skip some work for one-space-char hunks
244         if (!(length == 1 && it.characterAt(0) == ' ')) {
245
246             int misspellingLocation = -1;
247             int misspellingLength = 0;
248             m_client->textChecker().checkSpellingOfString(it.substring(0, length), &misspellingLocation, &misspellingLength);
249
250             // 5490627 shows that there was some code path here where the String constructor below crashes.
251             // We don't know exactly what combination of bad input caused this, so we're making this much
252             // more robust against bad input on release builds.
253             ASSERT(misspellingLength >= 0);
254             ASSERT(misspellingLocation >= -1);
255             ASSERT(!misspellingLength || misspellingLocation >= 0);
256             ASSERT(misspellingLocation < length);
257             ASSERT(misspellingLength <= length);
258             ASSERT(misspellingLocation + misspellingLength <= length);
259
260             if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < length && misspellingLength <= length && misspellingLocation + misspellingLength <= length) {
261
262                 // Compute range of misspelled word
263                 RefPtr<Range> misspellingRange = TextIterator::subrange(m_range.get(), currentChunkOffset + misspellingLocation, misspellingLength);
264
265                 // Remember first-encountered misspelling and its offset.
266                 if (!firstMisspelling) {
267                     firstMisspellingOffset = currentChunkOffset + misspellingLocation;
268                     firstMisspelling = it.substring(misspellingLocation, misspellingLength);
269                     firstMisspellingRange = misspellingRange;
270                 }
271
272                 // Store marker for misspelled word.
273                 misspellingRange->startContainer()->document().markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
274
275                 // Bail out if we're marking only the first misspelling, and not all instances.
276                 if (!markAll)
277                     break;
278             }
279         }
280
281         currentChunkOffset += length;
282         it.advance();
283     }
284
285     return firstMisspelling;
286 }
287
288 String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail)
289 {
290     if (!unifiedTextCheckerEnabled())
291         return "";
292
293     String firstFoundItem;
294     String misspelledWord;
295     String badGrammarPhrase;
296
297     // Initialize out parameters; these will be updated if we find something to return.
298     outIsSpelling = true;
299     outFirstFoundOffset = 0;
300     outGrammarDetail.location = -1;
301     outGrammarDetail.length = 0;
302     outGrammarDetail.guesses.clear();
303     outGrammarDetail.userDescription = "";
304
305     // Expand the search range to encompass entire paragraphs, since text checking needs that much context.
306     // Determine the character offset from the start of the paragraph to the start of the original search range,
307     // since we will want to ignore results in this area.
308     RefPtr<Range> paragraphRange = m_range->cloneRange(IGNORE_EXCEPTION);
309     setStart(paragraphRange.get(), startOfParagraph(m_range->startPosition()));
310     int totalRangeLength = TextIterator::rangeLength(paragraphRange.get());
311     setEnd(paragraphRange.get(), endOfParagraph(m_range->startPosition()));
312
313     RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer()->document(), paragraphRange->startPosition(), m_range->startPosition());
314     int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
315     int totalLengthProcessed = 0;
316
317     bool firstIteration = true;
318     bool lastIteration = false;
319     while (totalLengthProcessed < totalRangeLength) {
320         // Iterate through the search range by paragraphs, checking each one for spelling and grammar.
321         int currentLength = TextIterator::rangeLength(paragraphRange.get());
322         int currentStartOffset = firstIteration ? rangeStartOffset : 0;
323         int currentEndOffset = currentLength;
324         if (inSameParagraph(paragraphRange->startPosition(), m_range->endPosition())) {
325             // Determine the character offset from the end of the original search range to the end of the paragraph,
326             // since we will want to ignore results in this area.
327             RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), paragraphRange->startPosition(), m_range->endPosition());
328             currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get());
329             lastIteration = true;
330         }
331         if (currentStartOffset < currentEndOffset) {
332             String paragraphString = plainText(paragraphRange.get());
333             if (paragraphString.length() > 0) {
334                 bool foundGrammar = false;
335                 int spellingLocation = 0;
336                 int grammarPhraseLocation = 0;
337                 int grammarDetailLocation = 0;
338                 unsigned grammarDetailIndex = 0;
339
340                 Vector<TextCheckingResult> results;
341                 TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
342                 checkTextOfParagraph(m_client->textChecker(), paragraphString, checkingTypes, results);
343
344                 for (unsigned i = 0; i < results.size(); i++) {
345                     const TextCheckingResult* result = &results[i];
346                     if (result->decoration == TextDecorationTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) {
347                         ASSERT(result->length > 0 && result->location >= 0);
348                         spellingLocation = result->location;
349                         misspelledWord = paragraphString.substring(result->location, result->length);
350                         ASSERT(misspelledWord.length());
351                         break;
352                     }
353                     if (checkGrammar && result->decoration == TextDecorationTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
354                         ASSERT(result->length > 0 && result->location >= 0);
355                         // We can't stop after the first grammar result, since there might still be a spelling result after
356                         // it begins but before the first detail in it, but we can stop if we find a second grammar result.
357                         if (foundGrammar)
358                             break;
359                         for (unsigned j = 0; j < result->details.size(); j++) {
360                             const GrammarDetail* detail = &result->details[j];
361                             ASSERT(detail->length > 0 && detail->location >= 0);
362                             if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) {
363                                 grammarDetailIndex = j;
364                                 grammarDetailLocation = result->location + detail->location;
365                                 foundGrammar = true;
366                             }
367                         }
368                         if (foundGrammar) {
369                             grammarPhraseLocation = result->location;
370                             outGrammarDetail = result->details[grammarDetailIndex];
371                             badGrammarPhrase = paragraphString.substring(result->location, result->length);
372                             ASSERT(badGrammarPhrase.length());
373                         }
374                     }
375                 }
376
377                 if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) {
378                     int spellingOffset = spellingLocation - currentStartOffset;
379                     if (!firstIteration) {
380                         RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), m_range->startPosition(), paragraphRange->startPosition());
381                         spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
382                     }
383                     outIsSpelling = true;
384                     outFirstFoundOffset = spellingOffset;
385                     firstFoundItem = misspelledWord;
386                     break;
387                 }
388                 if (checkGrammar && !badGrammarPhrase.isEmpty()) {
389                     int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset;
390                     if (!firstIteration) {
391                         RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), m_range->startPosition(), paragraphRange->startPosition());
392                         grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
393                     }
394                     outIsSpelling = false;
395                     outFirstFoundOffset = grammarPhraseOffset;
396                     firstFoundItem = badGrammarPhrase;
397                     break;
398                 }
399             }
400         }
401         if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength)
402             break;
403         VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition());
404         setStart(paragraphRange.get(), newParagraphStart);
405         setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart));
406         firstIteration = false;
407         totalLengthProcessed += currentLength;
408     }
409     return firstFoundItem;
410 }
411
412 int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int startOffset, int endOffset, bool markAll) const
413 {
414     // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
415     // Optionally add a DocumentMarker for each detail in the range.
416     int earliestDetailLocationSoFar = -1;
417     int earliestDetailIndex = -1;
418     for (unsigned i = 0; i < grammarDetails.size(); i++) {
419         const GrammarDetail* detail = &grammarDetails[i];
420         ASSERT(detail->length > 0 && detail->location >= 0);
421
422         int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
423
424         // Skip this detail if it starts before the original search range
425         if (detailStartOffsetInParagraph < startOffset)
426             continue;
427
428         // Skip this detail if it starts after the original search range
429         if (detailStartOffsetInParagraph >= endOffset)
430             continue;
431
432         if (markAll) {
433             RefPtr<Range> badGrammarRange = TextIterator::subrange(m_range.get(), badGrammarPhraseLocation - startOffset + detail->location, detail->length);
434             badGrammarRange->startContainer()->document().markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
435         }
436
437         // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order)
438         if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) {
439             earliestDetailIndex = i;
440             earliestDetailLocationSoFar = detail->location;
441         }
442     }
443
444     return earliestDetailIndex;
445 }
446
447 String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
448 {
449     // Initialize out parameters; these will be updated if we find something to return.
450     outGrammarDetail.location = -1;
451     outGrammarDetail.length = 0;
452     outGrammarDetail.guesses.clear();
453     outGrammarDetail.userDescription = "";
454     outGrammarPhraseOffset = 0;
455
456     String firstBadGrammarPhrase;
457
458     // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context.
459     // Determine the character offset from the start of the paragraph to the start of the original search range,
460     // since we will want to ignore results in this area.
461     TextCheckingParagraph paragraph(m_range);
462
463     // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
464     int startOffset = 0;
465     while (startOffset < paragraph.checkingEnd()) {
466         Vector<GrammarDetail> grammarDetails;
467         int badGrammarPhraseLocation = -1;
468         int badGrammarPhraseLength = 0;
469         m_client->textChecker().checkGrammarOfString(paragraph.textSubstring(startOffset), grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
470
471         if (!badGrammarPhraseLength) {
472             ASSERT(badGrammarPhraseLocation == -1);
473             return String();
474         }
475
476         ASSERT(badGrammarPhraseLocation >= 0);
477         badGrammarPhraseLocation += startOffset;
478
479
480         // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
481         int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarPhraseLocation, paragraph.checkingStart(), paragraph.checkingEnd(), markAll);
482         if (badGrammarIndex >= 0) {
483             ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size());
484             outGrammarDetail = grammarDetails[badGrammarIndex];
485         }
486
487         // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but
488         // kept going so we could mark all instances).
489         if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) {
490             outGrammarPhraseOffset = badGrammarPhraseLocation - paragraph.checkingStart();
491             firstBadGrammarPhrase = paragraph.textSubstring(badGrammarPhraseLocation, badGrammarPhraseLength);
492
493             // Found one. We're done now, unless we're marking each instance.
494             if (!markAll)
495                 break;
496         }
497
498         // These results were all between the start of the paragraph and the start of the search range; look
499         // beyond this phrase.
500         startOffset = badGrammarPhraseLocation + badGrammarPhraseLength;
501     }
502
503     return firstBadGrammarPhrase;
504 }
505
506 void TextCheckingHelper::markAllMisspellings(RefPtr<Range>& firstMisspellingRange)
507 {
508     // Use the "markAll" feature of findFirstMisspelling. Ignore the return value and the "out parameter";
509     // all we need to do is mark every instance.
510     int ignoredOffset;
511     findFirstMisspelling(ignoredOffset, true, firstMisspellingRange);
512 }
513
514 void TextCheckingHelper::markAllBadGrammar()
515 {
516     // Use the "markAll" feature of ofindFirstBadGrammar. Ignore the return value and "out parameters"; all we need to
517     // do is mark every instance.
518     GrammarDetail ignoredGrammarDetail;
519     int ignoredOffset;
520     findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true);
521 }
522
523 bool TextCheckingHelper::unifiedTextCheckerEnabled() const
524 {
525     if (!m_range)
526         return false;
527
528     Document& doc = m_range->ownerDocument();
529     return WebCore::unifiedTextCheckerEnabled(doc.frame());
530 }
531
532 void checkTextOfParagraph(TextCheckerClient& client, const String& text, TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results)
533 {
534     Vector<UChar> characters;
535     text.appendTo(characters);
536     unsigned length = text.length();
537
538     Vector<TextCheckingResult> spellingResult;
539     if (checkingTypes & TextCheckingTypeSpelling)
540         findMisspellings(client, characters.data(), 0, length, spellingResult);
541
542     Vector<TextCheckingResult> grammarResult;
543     if (checkingTypes & TextCheckingTypeGrammar) {
544         // Only checks grammartical error before the first misspellings
545         int grammarCheckLength = length;
546         for (size_t i = 0; i < spellingResult.size(); ++i) {
547             if (spellingResult[i].location < grammarCheckLength)
548                 grammarCheckLength = spellingResult[i].location;
549         }
550
551         findBadGrammars(client, characters.data(), 0, grammarCheckLength, grammarResult);
552     }
553
554     if (grammarResult.size())
555         results.swap(grammarResult);
556
557     if (spellingResult.size()) {
558         if (results.isEmpty())
559             results.swap(spellingResult);
560         else
561             results.append(spellingResult);
562     }
563 }
564
565 bool unifiedTextCheckerEnabled(const Frame* frame)
566 {
567     if (!frame)
568         return false;
569
570     const Settings* settings = frame->settings();
571     if (!settings)
572         return false;
573
574     return settings->unifiedTextCheckerEnabled();
575 }
576
577 }