279590096665571e32b2c925eb380cc3c21ed7fa
[WebKit-https.git] / Source / WebCore / 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 "TextCheckingHelper.h"
29
30 #include "Document.h"
31 #include "DocumentMarkerController.h"
32 #include "Frame.h"
33 #include "Range.h"
34 #include "Settings.h"
35 #include "TextBreakIterator.h"
36 #include "TextCheckerClient.h"
37 #include "TextIterator.h"
38 #include "VisiblePosition.h"
39 #include "VisibleUnits.h"
40
41 namespace WebCore {
42
43 #if !USE(UNIFIED_TEXT_CHECKING)
44
45 #if USE(GRAMMAR_CHECKING)
46 static void findBadGrammars(TextCheckerClient* client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
47 {
48     int checkLocation = start;
49     int checkLength = length;
50
51     while (0 < checkLength) {
52         int badGrammarLocation = -1;
53         int badGrammarLength = 0;
54         Vector<GrammarDetail> badGrammarDetails;
55         client->checkGrammarOfString(text + checkLocation, checkLength, badGrammarDetails, &badGrammarLocation, &badGrammarLength);
56         if (!badGrammarLength)
57             break;
58         ASSERT(0 <= badGrammarLocation && badGrammarLocation <= checkLength);
59         ASSERT(0 < badGrammarLength && badGrammarLocation + badGrammarLength <= checkLength);
60         TextCheckingResult badGrammar;
61         badGrammar.type = TextCheckingTypeGrammar;
62         badGrammar.location = checkLocation + badGrammarLocation;
63         badGrammar.length = badGrammarLength;
64         badGrammar.details.swap(badGrammarDetails);
65         results.append(badGrammar);
66
67         checkLocation += (badGrammarLocation + badGrammarLength);
68         checkLength -= (badGrammarLocation + badGrammarLength);
69     }
70 }
71 #endif
72
73 static void findMisspellings(TextCheckerClient* client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
74 {
75     TextBreakIterator* iterator = wordBreakIterator(text + start, length);
76     if (!iterator)
77         return;
78     int wordStart = textBreakCurrent(iterator);
79     while (0 <= wordStart) {
80         int wordEnd = textBreakNext(iterator);
81         if (wordEnd < 0)
82             break;
83         int wordLength = wordEnd - wordStart;
84         int misspellingLocation = -1;
85         int misspellingLength = 0;
86         client->checkSpellingOfString(text + start + wordStart, wordLength, &misspellingLocation, &misspellingLength);
87         if (0 < misspellingLength) {
88             ASSERT(0 <= misspellingLocation && misspellingLocation <= wordLength);
89             ASSERT(0 < misspellingLength && misspellingLocation + misspellingLength <= wordLength);
90             TextCheckingResult misspelling;
91             misspelling.type = TextCheckingTypeSpelling;
92             misspelling.location = start + wordStart + misspellingLocation;
93             misspelling.length = misspellingLength;
94             misspelling.replacement = client->getAutoCorrectSuggestionForMisspelledWord(String(text + misspelling.location, misspelling.length));
95             results.append(misspelling);
96         }
97
98         wordStart = wordEnd;
99     }
100 }
101 #endif
102
103 static PassRefPtr<Range> expandToParagraphBoundary(PassRefPtr<Range> range)
104 {
105     RefPtr<Range> paragraphRange = range->cloneRange(IGNORE_EXCEPTION);
106     setStart(paragraphRange.get(), startOfParagraph(range->startPosition()));
107     setEnd(paragraphRange.get(), endOfParagraph(range->endPosition()));
108     return paragraphRange;
109 }
110
111 TextCheckingParagraph::TextCheckingParagraph(PassRefPtr<Range> checkingRange)
112     : m_checkingRange(checkingRange)
113     , m_checkingStart(-1)
114     , m_checkingEnd(-1)
115     , m_checkingLength(-1)
116 {
117 }
118
119 TextCheckingParagraph::TextCheckingParagraph(PassRefPtr<Range> checkingRange, PassRefPtr<Range> paragraphRange)
120     : m_checkingRange(checkingRange)
121     , m_paragraphRange(paragraphRange)
122     , m_checkingStart(-1)
123     , m_checkingEnd(-1)
124     , m_checkingLength(-1)
125 {
126 }
127
128 TextCheckingParagraph::~TextCheckingParagraph()
129 {
130 }
131
132 void TextCheckingParagraph::expandRangeToNextEnd()
133 {
134     ASSERT(m_checkingRange);
135     setEnd(paragraphRange().get(), endOfParagraph(startOfNextParagraph(paragraphRange()->startPosition())));
136     invalidateParagraphRangeValues();
137 }
138
139 void TextCheckingParagraph::invalidateParagraphRangeValues()
140 {
141     m_checkingStart = m_checkingEnd = -1;
142     m_offsetAsRange = 0;
143     m_text = String();
144 }
145
146 int TextCheckingParagraph::rangeLength() const
147 {
148     ASSERT(m_checkingRange);
149     return TextIterator::rangeLength(paragraphRange().get());
150 }
151
152 PassRefPtr<Range> TextCheckingParagraph::paragraphRange() const
153 {
154     ASSERT(m_checkingRange);
155     if (!m_paragraphRange)
156         m_paragraphRange = expandToParagraphBoundary(checkingRange());
157     return m_paragraphRange;
158 }
159
160 PassRefPtr<Range> TextCheckingParagraph::subrange(int characterOffset, int characterCount) const
161 {
162     ASSERT(m_checkingRange);
163     return TextIterator::subrange(paragraphRange().get(), characterOffset, characterCount);
164 }
165
166 int TextCheckingParagraph::offsetTo(const Position& position, ExceptionCode& ec) const
167 {
168     ASSERT(m_checkingRange);
169     RefPtr<Range> range = offsetAsRange()->cloneRange(ASSERT_NO_EXCEPTION);
170     range->setEnd(position.containerNode(), position.computeOffsetInContainerNode(), ec);
171     if (ec)
172         return 0;
173     return TextIterator::rangeLength(range.get());
174 }
175
176 bool TextCheckingParagraph::isEmpty() const
177 {
178     // Both predicates should have same result, but we check both just for sure.
179     // We need to investigate to remove this redundancy.
180     return isRangeEmpty() || isTextEmpty();
181 }
182
183 PassRefPtr<Range> TextCheckingParagraph::offsetAsRange() const
184 {
185     ASSERT(m_checkingRange);
186     if (!m_offsetAsRange)
187         m_offsetAsRange = Range::create(&paragraphRange()->startContainer()->document(), paragraphRange()->startPosition(), checkingRange()->startPosition());
188
189     return m_offsetAsRange;
190 }
191
192 const String& TextCheckingParagraph::text() const
193 {
194     ASSERT(m_checkingRange);
195     if (m_text.isEmpty())
196         m_text = plainText(paragraphRange().get());
197     return m_text; 
198 }
199
200 int TextCheckingParagraph::checkingStart() const
201 {
202     ASSERT(m_checkingRange);
203     if (m_checkingStart == -1)
204         m_checkingStart = TextIterator::rangeLength(offsetAsRange().get());
205     return m_checkingStart;
206 }
207
208 int TextCheckingParagraph::checkingEnd() const
209 {
210     ASSERT(m_checkingRange);
211     if (m_checkingEnd == -1)
212         m_checkingEnd = checkingStart() + TextIterator::rangeLength(checkingRange().get());
213     return m_checkingEnd;
214 }
215
216 int TextCheckingParagraph::checkingLength() const
217 {
218     ASSERT(m_checkingRange);
219     if (-1 == m_checkingLength)
220         m_checkingLength = TextIterator::rangeLength(checkingRange().get());
221     return m_checkingLength;
222 }
223
224 TextCheckingHelper::TextCheckingHelper(EditorClient* client, PassRefPtr<Range> range)
225     : m_client(client)
226     , m_range(range)
227 {
228     ASSERT_ARG(m_client, m_client);
229     ASSERT_ARG(m_range, m_range);
230 }
231
232 TextCheckingHelper::~TextCheckingHelper()
233 {
234 }
235
236 String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange)
237 {
238     WordAwareIterator it(m_range.get());
239     firstMisspellingOffset = 0;
240     
241     String firstMisspelling;
242     int currentChunkOffset = 0;
243
244     while (!it.atEnd()) {
245         const UChar* chars = it.characters();
246         int len = it.length();
247         
248         // Skip some work for one-space-char hunks
249         if (!(len == 1 && chars[0] == ' ')) {
250             
251             int misspellingLocation = -1;
252             int misspellingLength = 0;
253             m_client->textChecker()->checkSpellingOfString(chars, len, &misspellingLocation, &misspellingLength);
254
255             // 5490627 shows that there was some code path here where the String constructor below crashes.
256             // We don't know exactly what combination of bad input caused this, so we're making this much
257             // more robust against bad input on release builds.
258             ASSERT(misspellingLength >= 0);
259             ASSERT(misspellingLocation >= -1);
260             ASSERT(!misspellingLength || misspellingLocation >= 0);
261             ASSERT(misspellingLocation < len);
262             ASSERT(misspellingLength <= len);
263             ASSERT(misspellingLocation + misspellingLength <= len);
264             
265             if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < len && misspellingLength <= len && misspellingLocation + misspellingLength <= len) {
266                 
267                 // Compute range of misspelled word
268                 RefPtr<Range> misspellingRange = TextIterator::subrange(m_range.get(), currentChunkOffset + misspellingLocation, misspellingLength);
269
270                 // Remember first-encountered misspelling and its offset.
271                 if (!firstMisspelling) {
272                     firstMisspellingOffset = currentChunkOffset + misspellingLocation;
273                     firstMisspelling = String(chars + misspellingLocation, misspellingLength);
274                     firstMisspellingRange = misspellingRange;
275                 }
276
277                 // Store marker for misspelled word.
278                 misspellingRange->startContainer()->document().markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling);
279
280                 // Bail out if we're marking only the first misspelling, and not all instances.
281                 if (!markAll)
282                     break;
283             }
284         }
285         
286         currentChunkOffset += len;
287         it.advance();
288     }
289     
290     return firstMisspelling;
291 }
292
293 String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail)
294 {
295     if (!unifiedTextCheckerEnabled())
296         return "";
297
298     String firstFoundItem;
299     String misspelledWord;
300     String badGrammarPhrase;
301     
302     // Initialize out parameters; these will be updated if we find something to return.
303     outIsSpelling = true;
304     outFirstFoundOffset = 0;
305     outGrammarDetail.location = -1;
306     outGrammarDetail.length = 0;
307     outGrammarDetail.guesses.clear();
308     outGrammarDetail.userDescription = "";
309     
310     // Expand the search range to encompass entire paragraphs, since text checking needs that much context.
311     // Determine the character offset from the start of the paragraph to the start of the original search range,
312     // since we will want to ignore results in this area.
313     RefPtr<Range> paragraphRange = m_range->cloneRange(IGNORE_EXCEPTION);
314     setStart(paragraphRange.get(), startOfParagraph(m_range->startPosition()));
315     int totalRangeLength = TextIterator::rangeLength(paragraphRange.get());
316     setEnd(paragraphRange.get(), endOfParagraph(m_range->startPosition()));
317     
318     RefPtr<Range> offsetAsRange = Range::create(&paragraphRange->startContainer()->document(), paragraphRange->startPosition(), m_range->startPosition());
319     int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
320     int totalLengthProcessed = 0;
321     
322     bool firstIteration = true;
323     bool lastIteration = false;
324     while (totalLengthProcessed < totalRangeLength) {
325         // Iterate through the search range by paragraphs, checking each one for spelling and grammar.
326         int currentLength = TextIterator::rangeLength(paragraphRange.get());
327         int currentStartOffset = firstIteration ? rangeStartOffset : 0;
328         int currentEndOffset = currentLength;
329         if (inSameParagraph(paragraphRange->startPosition(), m_range->endPosition())) {
330             // Determine the character offset from the end of the original search range to the end of the paragraph,
331             // since we will want to ignore results in this area.
332             RefPtr<Range> endOffsetAsRange = Range::create(&paragraphRange->startContainer()->document(), paragraphRange->startPosition(), m_range->endPosition());
333             currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get());
334             lastIteration = true;
335         }
336         if (currentStartOffset < currentEndOffset) {
337             String paragraphString = plainText(paragraphRange.get());
338             if (paragraphString.length() > 0) {
339                 bool foundGrammar = false;
340                 int spellingLocation = 0;
341                 int grammarPhraseLocation = 0;
342                 int grammarDetailLocation = 0;
343                 unsigned grammarDetailIndex = 0;
344                 
345                 Vector<TextCheckingResult> results;
346                 TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
347                 checkTextOfParagraph(m_client->textChecker(), paragraphString.characters(), paragraphString.length(), checkingTypes, results);
348                 
349                 for (unsigned i = 0; i < results.size(); i++) {
350                     const TextCheckingResult* result = &results[i];
351                     if (result->type == TextCheckingTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) {
352                         ASSERT(result->length > 0 && result->location >= 0);
353                         spellingLocation = result->location;
354                         misspelledWord = paragraphString.substring(result->location, result->length);
355                         ASSERT(misspelledWord.length());
356                         break;
357                     }
358                     if (checkGrammar && result->type == TextCheckingTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
359                         ASSERT(result->length > 0 && result->location >= 0);
360                         // We can't stop after the first grammar result, since there might still be a spelling result after
361                         // it begins but before the first detail in it, but we can stop if we find a second grammar result.
362                         if (foundGrammar)
363                             break;
364                         for (unsigned j = 0; j < result->details.size(); j++) {
365                             const GrammarDetail* detail = &result->details[j];
366                             ASSERT(detail->length > 0 && detail->location >= 0);
367                             if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) {
368                                 grammarDetailIndex = j;
369                                 grammarDetailLocation = result->location + detail->location;
370                                 foundGrammar = true;
371                             }
372                         }
373                         if (foundGrammar) {
374                             grammarPhraseLocation = result->location;
375                             outGrammarDetail = result->details[grammarDetailIndex];
376                             badGrammarPhrase = paragraphString.substring(result->location, result->length);
377                             ASSERT(badGrammarPhrase.length());
378                         }
379                     }
380                 }
381
382                 if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) {
383                     int spellingOffset = spellingLocation - currentStartOffset;
384                     if (!firstIteration) {
385                         RefPtr<Range> paragraphOffsetAsRange = Range::create(&paragraphRange->startContainer()->document(), m_range->startPosition(), paragraphRange->startPosition());
386                         spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
387                     }
388                     outIsSpelling = true;
389                     outFirstFoundOffset = spellingOffset;
390                     firstFoundItem = misspelledWord;
391                     break;
392                 }
393                 if (checkGrammar && !badGrammarPhrase.isEmpty()) {
394                     int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset;
395                     if (!firstIteration) {
396                         RefPtr<Range> paragraphOffsetAsRange = Range::create(&paragraphRange->startContainer()->document(), m_range->startPosition(), paragraphRange->startPosition());
397                         grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
398                     }
399                     outIsSpelling = false;
400                     outFirstFoundOffset = grammarPhraseOffset;
401                     firstFoundItem = badGrammarPhrase;
402                     break;
403                 }
404             }
405         }
406         if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength)
407             break;
408         VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition());
409         setStart(paragraphRange.get(), newParagraphStart);
410         setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart));
411         firstIteration = false;
412         totalLengthProcessed += currentLength;
413     }
414     return firstFoundItem;
415 }
416
417 #if USE(GRAMMAR_CHECKING)
418 int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int startOffset, int endOffset, bool markAll) const
419 {
420     // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
421     // Optionally add a DocumentMarker for each detail in the range.
422     int earliestDetailLocationSoFar = -1;
423     int earliestDetailIndex = -1;
424     for (unsigned i = 0; i < grammarDetails.size(); i++) {
425         const GrammarDetail* detail = &grammarDetails[i];
426         ASSERT(detail->length > 0 && detail->location >= 0);
427         
428         int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
429         
430         // Skip this detail if it starts before the original search range
431         if (detailStartOffsetInParagraph < startOffset)
432             continue;
433         
434         // Skip this detail if it starts after the original search range
435         if (detailStartOffsetInParagraph >= endOffset)
436             continue;
437         
438         if (markAll) {
439             RefPtr<Range> badGrammarRange = TextIterator::subrange(m_range.get(), badGrammarPhraseLocation - startOffset + detail->location, detail->length);
440             badGrammarRange->startContainer()->document().markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
441         }
442         
443         // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order)
444         if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) {
445             earliestDetailIndex = i;
446             earliestDetailLocationSoFar = detail->location;
447         }
448     }
449     
450     return earliestDetailIndex;
451 }
452
453 String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll) const
454 {
455     // Initialize out parameters; these will be updated if we find something to return.
456     outGrammarDetail.location = -1;
457     outGrammarDetail.length = 0;
458     outGrammarDetail.guesses.clear();
459     outGrammarDetail.userDescription = "";
460     outGrammarPhraseOffset = 0;
461     
462     String firstBadGrammarPhrase;
463
464     // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context.
465     // Determine the character offset from the start of the paragraph to the start of the original search range,
466     // since we will want to ignore results in this area.
467     TextCheckingParagraph paragraph(m_range);
468     
469     // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
470     int startOffset = 0;
471     while (startOffset < paragraph.checkingEnd()) {
472         Vector<GrammarDetail> grammarDetails;
473         int badGrammarPhraseLocation = -1;
474         int badGrammarPhraseLength = 0;
475         m_client->textChecker()->checkGrammarOfString(paragraph.textCharacters() + startOffset, paragraph.textLength() - startOffset, grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
476         
477         if (!badGrammarPhraseLength) {
478             ASSERT(badGrammarPhraseLocation == -1);
479             return String();
480         }
481
482         ASSERT(badGrammarPhraseLocation >= 0);
483         badGrammarPhraseLocation += startOffset;
484
485         
486         // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
487         int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarPhraseLocation, paragraph.checkingStart(), paragraph.checkingEnd(), markAll);
488         if (badGrammarIndex >= 0) {
489             ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size());
490             outGrammarDetail = grammarDetails[badGrammarIndex];
491         }
492
493         // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but
494         // kept going so we could mark all instances).
495         if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) {
496             outGrammarPhraseOffset = badGrammarPhraseLocation - paragraph.checkingStart();
497             firstBadGrammarPhrase = paragraph.textSubstring(badGrammarPhraseLocation, badGrammarPhraseLength);
498             
499             // Found one. We're done now, unless we're marking each instance.
500             if (!markAll)
501                 break;
502         }
503
504         // These results were all between the start of the paragraph and the start of the search range; look
505         // beyond this phrase.
506         startOffset = badGrammarPhraseLocation + badGrammarPhraseLength;
507     }
508     
509     return firstBadGrammarPhrase;
510 }
511
512
513 bool TextCheckingHelper::isUngrammatical() const
514 {
515     if (!m_client)
516         return false;
517
518     if (!m_range || m_range->collapsed(IGNORE_EXCEPTION))
519         return false;
520     
521     // Returns true only if the passed range exactly corresponds to a bad grammar detail range. This is analogous
522     // to isSelectionMisspelled. It's not good enough for there to be some bad grammar somewhere in the range,
523     // or overlapping the range; the ranges must exactly match.
524     int grammarPhraseOffset;
525     
526     GrammarDetail grammarDetail;
527     String badGrammarPhrase = findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);
528     
529     // No bad grammar in these parts at all.
530     if (badGrammarPhrase.isEmpty())
531         return false;
532     
533     // Bad grammar, but phrase (e.g. sentence) starts beyond start of range.
534     if (grammarPhraseOffset > 0)
535         return false;
536     
537     ASSERT(grammarDetail.location >= 0 && grammarDetail.length > 0);
538     
539     // Bad grammar, but start of detail (e.g. ungrammatical word) doesn't match start of range
540     if (grammarDetail.location + grammarPhraseOffset)
541         return false;
542     
543     // Bad grammar at start of range, but end of bad grammar is before or after end of range
544     if (grammarDetail.length != TextIterator::rangeLength(m_range.get()))
545         return false;
546     
547     // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen).
548     // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work
549     // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling
550     // or a grammar error.
551     m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
552     
553     return true;
554 }
555 #endif
556
557 Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const
558 {
559     if (!unifiedTextCheckerEnabled())
560         return Vector<String>();
561
562     Vector<String> guesses;
563     misspelled = false;
564     ungrammatical = false;
565     
566     if (!m_client || !m_range || m_range->collapsed(IGNORE_EXCEPTION))
567         return guesses;
568
569     // Expand the range to encompass entire paragraphs, since text checking needs that much context.
570     TextCheckingParagraph paragraph(m_range);
571     if (paragraph.isEmpty())
572         return guesses;
573
574     Vector<TextCheckingResult> results;
575     TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
576     checkTextOfParagraph(m_client->textChecker(), paragraph.textCharacters(), paragraph.textLength(), checkingTypes, results);
577     
578     for (unsigned i = 0; i < results.size(); i++) {
579         const TextCheckingResult* result = &results[i];
580         if (result->type == TextCheckingTypeSpelling && paragraph.checkingRangeMatches(result->location, result->length)) {
581             String misspelledWord = paragraph.checkingSubstring();
582             ASSERT(misspelledWord.length());
583             m_client->textChecker()->getGuessesForWord(misspelledWord, String(), guesses);
584             m_client->updateSpellingUIWithMisspelledWord(misspelledWord);
585             misspelled = true;
586             return guesses;
587         }
588     }
589     
590     if (!checkGrammar)
591         return guesses;
592         
593     for (unsigned i = 0; i < results.size(); i++) {
594         const TextCheckingResult* result = &results[i];
595         if (result->type == TextCheckingTypeGrammar && paragraph.isCheckingRangeCoveredBy(result->location, result->length)) {
596             for (unsigned j = 0; j < result->details.size(); j++) {
597                 const GrammarDetail* detail = &result->details[j];
598                 ASSERT(detail->length > 0 && detail->location >= 0);
599                 if (paragraph.checkingRangeMatches(result->location + detail->location, detail->length)) {
600                     String badGrammarPhrase = paragraph.textSubstring(result->location, result->length);
601                     ASSERT(badGrammarPhrase.length());
602                     for (unsigned k = 0; k < detail->guesses.size(); k++)
603                         guesses.append(detail->guesses[k]);
604                     m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail);
605                     ungrammatical = true;
606                     return guesses;
607                 }
608             }
609         }
610     }
611     return guesses;
612 }
613
614
615 void TextCheckingHelper::markAllMisspellings(RefPtr<Range>& firstMisspellingRange)
616 {
617     // Use the "markAll" feature of findFirstMisspelling. Ignore the return value and the "out parameter";
618     // all we need to do is mark every instance.
619     int ignoredOffset;
620     findFirstMisspelling(ignoredOffset, true, firstMisspellingRange);
621 }
622
623 #if USE(GRAMMAR_CHECKING)
624 void TextCheckingHelper::markAllBadGrammar()
625 {
626     // Use the "markAll" feature of ofindFirstBadGrammar. Ignore the return value and "out parameters"; all we need to
627     // do is mark every instance.
628     GrammarDetail ignoredGrammarDetail;
629     int ignoredOffset;
630     findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true);
631 }
632 #endif
633
634 bool TextCheckingHelper::unifiedTextCheckerEnabled() const
635 {
636     return m_range && WebCore::unifiedTextCheckerEnabled(m_range->ownerDocument().frame());
637 }
638
639 void checkTextOfParagraph(TextCheckerClient* client, const UChar* text, int length,
640                           TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results)
641 {
642 #if USE(UNIFIED_TEXT_CHECKING)
643     client->checkTextOfParagraph(text, length, checkingTypes, results);
644 #else
645     Vector<TextCheckingResult> spellingResult;
646     if (checkingTypes & TextCheckingTypeSpelling)
647         findMisspellings(client, text, 0, length, spellingResult);
648
649 #if USE(GRAMMAR_CHECKING)
650     Vector<TextCheckingResult> grammarResult;
651     if (checkingTypes & TextCheckingTypeGrammar) {
652         // Only checks grammartical error before the first misspellings
653         int grammarCheckLength = length;
654         for (size_t i = 0; i < spellingResult.size(); ++i) {
655             if (spellingResult[i].location < grammarCheckLength)
656                 grammarCheckLength = spellingResult[i].location;
657         }
658
659         findBadGrammars(client, text, 0, grammarCheckLength, grammarResult);
660     }
661
662     if (grammarResult.size())
663         results.swap(grammarResult);
664 #endif
665
666     if (spellingResult.size()) {
667         if (results.isEmpty())
668             results.swap(spellingResult);
669         else
670             results.appendVector(spellingResult);
671     }
672 #endif
673 }
674
675 bool unifiedTextCheckerEnabled(const Frame* frame)
676 {
677     if (!frame)
678         return false;
679     return frame->settings().unifiedTextCheckerEnabled();
680 }
681
682 }