Cleanup: Use exceptionless Range::* methods rather than ignoring exceptions.
[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 "visible_units.h"
40
41 namespace WebCore {
42
43 #if !USE(UNIFIED_TEXT_CHECKING)
44 static void findBadGrammars(TextCheckerClient* client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
45 {
46     ASSERT(WTF_USE_GRAMMAR_CHECKING);
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
72 static void findMisspellings(TextCheckerClient* client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
73 {
74     TextBreakIterator* iterator = wordBreakIterator(text + start, length);
75     if (!iterator)
76         return;
77     int wordStart = textBreakCurrent(iterator);
78     while (0 <= wordStart) {
79         int wordEnd = textBreakNext(iterator);
80         if (wordEnd < 0)
81             break;
82         int wordLength = wordEnd - wordStart;
83         int misspellingLocation = -1;
84         int misspellingLength = 0;
85         client->checkSpellingOfString(text + start + wordStart, wordLength, &misspellingLocation, &misspellingLength);
86         if (0 < misspellingLength) {
87             ASSERT(0 <= misspellingLocation && misspellingLocation <= wordLength);
88             ASSERT(0 < misspellingLength && misspellingLocation + misspellingLength <= wordLength);
89             TextCheckingResult misspelling;
90             misspelling.type = TextCheckingTypeSpelling;
91             misspelling.location = start + wordStart + misspellingLocation;
92             misspelling.length = misspellingLength;
93             misspelling.replacement = client->getAutoCorrectSuggestionForMisspelledWord(String(text + misspelling.location, misspelling.length));
94             results.append(misspelling);
95         }
96
97         wordStart = wordEnd;
98     }
99 }
100 #endif
101
102 static PassRefPtr<Range> expandToParagraphBoundary(PassRefPtr<Range> range)
103 {
104     ExceptionCode ec = 0;
105     RefPtr<Range> paragraphRange = range->cloneRange(ec);
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     ExceptionCode ec = 0;
302     
303     // Initialize out parameters; these will be updated if we find something to return.
304     outIsSpelling = true;
305     outFirstFoundOffset = 0;
306     outGrammarDetail.location = -1;
307     outGrammarDetail.length = 0;
308     outGrammarDetail.guesses.clear();
309     outGrammarDetail.userDescription = "";
310     
311     // Expand the search range to encompass entire paragraphs, since text checking needs that much context.
312     // Determine the character offset from the start of the paragraph to the start of the original search range,
313     // since we will want to ignore results in this area.
314     RefPtr<Range> paragraphRange = m_range->cloneRange(ec);
315     setStart(paragraphRange.get(), startOfParagraph(m_range->startPosition()));
316     int totalRangeLength = TextIterator::rangeLength(paragraphRange.get());
317     setEnd(paragraphRange.get(), endOfParagraph(m_range->startPosition()));
318     
319     RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer()->document(), paragraphRange->startPosition(), m_range->startPosition());
320     int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
321     int totalLengthProcessed = 0;
322     
323     bool firstIteration = true;
324     bool lastIteration = false;
325     while (totalLengthProcessed < totalRangeLength) {
326         // Iterate through the search range by paragraphs, checking each one for spelling and grammar.
327         int currentLength = TextIterator::rangeLength(paragraphRange.get());
328         int currentStartOffset = firstIteration ? rangeStartOffset : 0;
329         int currentEndOffset = currentLength;
330         if (inSameParagraph(paragraphRange->startPosition(), m_range->endPosition())) {
331             // Determine the character offset from the end of the original search range to the end of the paragraph,
332             // since we will want to ignore results in this area.
333             RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), paragraphRange->startPosition(), m_range->endPosition());
334             currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get());
335             lastIteration = true;
336         }
337         if (currentStartOffset < currentEndOffset) {
338             String paragraphString = plainText(paragraphRange.get());
339             if (paragraphString.length() > 0) {
340                 bool foundGrammar = false;
341                 int spellingLocation = 0;
342                 int grammarPhraseLocation = 0;
343                 int grammarDetailLocation = 0;
344                 unsigned grammarDetailIndex = 0;
345                 
346                 Vector<TextCheckingResult> results;
347                 TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
348                 checkTextOfParagraph(m_client->textChecker(), paragraphString.characters(), paragraphString.length(), checkingTypes, results);
349                 
350                 for (unsigned i = 0; i < results.size(); i++) {
351                     const TextCheckingResult* result = &results[i];
352                     if (result->type == TextCheckingTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) {
353                         ASSERT(result->length > 0 && result->location >= 0);
354                         spellingLocation = result->location;
355                         misspelledWord = paragraphString.substring(result->location, result->length);
356                         ASSERT(misspelledWord.length());
357                         break;
358                     }
359                     if (checkGrammar && result->type == TextCheckingTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
360                         ASSERT(result->length > 0 && result->location >= 0);
361                         // We can't stop after the first grammar result, since there might still be a spelling result after
362                         // it begins but before the first detail in it, but we can stop if we find a second grammar result.
363                         if (foundGrammar)
364                             break;
365                         for (unsigned j = 0; j < result->details.size(); j++) {
366                             const GrammarDetail* detail = &result->details[j];
367                             ASSERT(detail->length > 0 && detail->location >= 0);
368                             if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) {
369                                 grammarDetailIndex = j;
370                                 grammarDetailLocation = result->location + detail->location;
371                                 foundGrammar = true;
372                             }
373                         }
374                         if (foundGrammar) {
375                             grammarPhraseLocation = result->location;
376                             outGrammarDetail = result->details[grammarDetailIndex];
377                             badGrammarPhrase = paragraphString.substring(result->location, result->length);
378                             ASSERT(badGrammarPhrase.length());
379                         }
380                     }
381                 }
382
383                 if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) {
384                     int spellingOffset = spellingLocation - currentStartOffset;
385                     if (!firstIteration) {
386                         RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), m_range->startPosition(), paragraphRange->startPosition());
387                         spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
388                     }
389                     outIsSpelling = true;
390                     outFirstFoundOffset = spellingOffset;
391                     firstFoundItem = misspelledWord;
392                     break;
393                 }
394                 if (checkGrammar && !badGrammarPhrase.isEmpty()) {
395                     int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset;
396                     if (!firstIteration) {
397                         RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), m_range->startPosition(), paragraphRange->startPosition());
398                         grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
399                     }
400                     outIsSpelling = false;
401                     outFirstFoundOffset = grammarPhraseOffset;
402                     firstFoundItem = badGrammarPhrase;
403                     break;
404                 }
405             }
406         }
407         if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength)
408             break;
409         VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition());
410         setStart(paragraphRange.get(), newParagraphStart);
411         setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart));
412         firstIteration = false;
413         totalLengthProcessed += currentLength;
414     }
415     return firstFoundItem;
416 }
417
418 int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int /*badGrammarPhraseLength*/, int startOffset, int endOffset, bool markAll)
419 {
420 #if USE(GRAMMAR_CHECKING)
421     // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
422     // Optionally add a DocumentMarker for each detail in the range.
423     int earliestDetailLocationSoFar = -1;
424     int earliestDetailIndex = -1;
425     for (unsigned i = 0; i < grammarDetails.size(); i++) {
426         const GrammarDetail* detail = &grammarDetails[i];
427         ASSERT(detail->length > 0 && detail->location >= 0);
428         
429         int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
430         
431         // Skip this detail if it starts before the original search range
432         if (detailStartOffsetInParagraph < startOffset)
433             continue;
434         
435         // Skip this detail if it starts after the original search range
436         if (detailStartOffsetInParagraph >= endOffset)
437             continue;
438         
439         if (markAll) {
440             RefPtr<Range> badGrammarRange = TextIterator::subrange(m_range.get(), badGrammarPhraseLocation - startOffset + detail->location, detail->length);
441             badGrammarRange->startContainer()->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
442         }
443         
444         // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order)
445         if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) {
446             earliestDetailIndex = i;
447             earliestDetailLocationSoFar = detail->location;
448         }
449     }
450     
451     return earliestDetailIndex;
452 #else
453     ASSERT_NOT_REACHED();
454     UNUSED_PARAM(grammarDetails);
455     UNUSED_PARAM(badGrammarPhraseLocation);
456     UNUSED_PARAM(startOffset);
457     UNUSED_PARAM(endOffset);
458     UNUSED_PARAM(markAll);
459     return 0;
460 #endif
461 }
462
463 String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
464 {
465     ASSERT(WTF_USE_GRAMMAR_CHECKING);
466     // Initialize out parameters; these will be updated if we find something to return.
467     outGrammarDetail.location = -1;
468     outGrammarDetail.length = 0;
469     outGrammarDetail.guesses.clear();
470     outGrammarDetail.userDescription = "";
471     outGrammarPhraseOffset = 0;
472     
473     String firstBadGrammarPhrase;
474
475     // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context.
476     // Determine the character offset from the start of the paragraph to the start of the original search range,
477     // since we will want to ignore results in this area.
478     TextCheckingParagraph paragraph(m_range);
479     
480     // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
481     int startOffset = 0;
482     while (startOffset < paragraph.checkingEnd()) {
483         Vector<GrammarDetail> grammarDetails;
484         int badGrammarPhraseLocation = -1;
485         int badGrammarPhraseLength = 0;
486         m_client->textChecker()->checkGrammarOfString(paragraph.textCharacters() + startOffset, paragraph.textLength() - startOffset, grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
487         
488         if (!badGrammarPhraseLength) {
489             ASSERT(badGrammarPhraseLocation == -1);
490             return String();
491         }
492
493         ASSERT(badGrammarPhraseLocation >= 0);
494         badGrammarPhraseLocation += startOffset;
495
496         
497         // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
498         int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarPhraseLocation, badGrammarPhraseLength, paragraph.checkingStart(), paragraph.checkingEnd(), markAll);
499         if (badGrammarIndex >= 0) {
500             ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size());
501             outGrammarDetail = grammarDetails[badGrammarIndex];
502         }
503
504         // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but
505         // kept going so we could mark all instances).
506         if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) {
507             outGrammarPhraseOffset = badGrammarPhraseLocation - paragraph.checkingStart();
508             firstBadGrammarPhrase = paragraph.textSubstring(badGrammarPhraseLocation, badGrammarPhraseLength);
509             
510             // Found one. We're done now, unless we're marking each instance.
511             if (!markAll)
512                 break;
513         }
514
515         // These results were all between the start of the paragraph and the start of the search range; look
516         // beyond this phrase.
517         startOffset = badGrammarPhraseLocation + badGrammarPhraseLength;
518     }
519     
520     return firstBadGrammarPhrase;
521 }
522
523
524 bool TextCheckingHelper::isUngrammatical(Vector<String>& guessesVector) const
525 {
526     ASSERT(WTF_USE_GRAMMAR_CHECKING);
527     if (!m_client)
528         return false;
529
530     ExceptionCode ec;
531     if (!m_range || m_range->collapsed(ec))
532         return false;
533     
534     // Returns true only if the passed range exactly corresponds to a bad grammar detail range. This is analogous
535     // to isSelectionMisspelled. It's not good enough for there to be some bad grammar somewhere in the range,
536     // or overlapping the range; the ranges must exactly match.
537     guessesVector.clear();
538     int grammarPhraseOffset;
539     
540     GrammarDetail grammarDetail;
541     String badGrammarPhrase = const_cast<TextCheckingHelper*>(this)->findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);    
542     
543     // No bad grammar in these parts at all.
544     if (badGrammarPhrase.isEmpty())
545         return false;
546     
547     // Bad grammar, but phrase (e.g. sentence) starts beyond start of range.
548     if (grammarPhraseOffset > 0)
549         return false;
550     
551     ASSERT(grammarDetail.location >= 0 && grammarDetail.length > 0);
552     
553     // Bad grammar, but start of detail (e.g. ungrammatical word) doesn't match start of range
554     if (grammarDetail.location + grammarPhraseOffset)
555         return false;
556     
557     // Bad grammar at start of range, but end of bad grammar is before or after end of range
558     if (grammarDetail.length != TextIterator::rangeLength(m_range.get()))
559         return false;
560     
561     // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen).
562     // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work
563     // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling
564     // or a grammar error.
565     m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
566     
567     return true;
568 }
569
570 Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const
571 {
572     if (!unifiedTextCheckerEnabled())
573         return Vector<String>();
574
575     Vector<String> guesses;
576     ExceptionCode ec;
577     misspelled = false;
578     ungrammatical = false;
579     
580     if (!m_client || !m_range || m_range->collapsed(ec))
581         return guesses;
582
583     // Expand the range to encompass entire paragraphs, since text checking needs that much context.
584     TextCheckingParagraph paragraph(m_range);
585     if (paragraph.isEmpty())
586         return guesses;
587
588     Vector<TextCheckingResult> results;
589     TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
590     checkTextOfParagraph(m_client->textChecker(), paragraph.textCharacters(), paragraph.textLength(), checkingTypes, results);
591     
592     for (unsigned i = 0; i < results.size(); i++) {
593         const TextCheckingResult* result = &results[i];
594         if (result->type == TextCheckingTypeSpelling && paragraph.checkingRangeMatches(result->location, result->length)) {
595             String misspelledWord = paragraph.checkingSubstring();
596             ASSERT(misspelledWord.length());
597             m_client->textChecker()->getGuessesForWord(misspelledWord, String(), guesses);
598             m_client->updateSpellingUIWithMisspelledWord(misspelledWord);
599             misspelled = true;
600             return guesses;
601         }
602     }
603     
604     if (!checkGrammar)
605         return guesses;
606         
607     for (unsigned i = 0; i < results.size(); i++) {
608         const TextCheckingResult* result = &results[i];
609         if (result->type == TextCheckingTypeGrammar && paragraph.isCheckingRangeCoveredBy(result->location, result->length)) {
610             for (unsigned j = 0; j < result->details.size(); j++) {
611                 const GrammarDetail* detail = &result->details[j];
612                 ASSERT(detail->length > 0 && detail->location >= 0);
613                 if (paragraph.checkingRangeMatches(result->location + detail->location, detail->length)) {
614                     String badGrammarPhrase = paragraph.textSubstring(result->location, result->length);
615                     ASSERT(badGrammarPhrase.length());
616                     for (unsigned k = 0; k < detail->guesses.size(); k++)
617                         guesses.append(detail->guesses[k]);
618                     m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail);
619                     ungrammatical = true;
620                     return guesses;
621                 }
622             }
623         }
624     }
625     return guesses;
626 }
627
628
629 void TextCheckingHelper::markAllMisspellings(RefPtr<Range>& firstMisspellingRange)
630 {
631     // Use the "markAll" feature of findFirstMisspelling. Ignore the return value and the "out parameter";
632     // all we need to do is mark every instance.
633     int ignoredOffset;
634     findFirstMisspelling(ignoredOffset, true, firstMisspellingRange);
635 }
636
637 void TextCheckingHelper::markAllBadGrammar()
638 {
639     ASSERT(WTF_USE_GRAMMAR_CHECKING);
640     // Use the "markAll" feature of ofindFirstBadGrammar. Ignore the return value and "out parameters"; all we need to
641     // do is mark every instance.
642     GrammarDetail ignoredGrammarDetail;
643     int ignoredOffset;
644     findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true);
645 }
646
647 bool TextCheckingHelper::unifiedTextCheckerEnabled() const
648 {
649     if (!m_range)
650         return false;
651
652     Document* doc = m_range->ownerDocument();
653     if (!doc)
654         return false;
655
656     return WebCore::unifiedTextCheckerEnabled(doc->frame());
657 }
658
659 void checkTextOfParagraph(TextCheckerClient* client, const UChar* text, int length,
660                           TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results)
661 {
662 #if USE(UNIFIED_TEXT_CHECKING)
663     client->checkTextOfParagraph(text, length, checkingTypes, results);
664 #else
665     Vector<TextCheckingResult> spellingResult;
666     if (checkingTypes & TextCheckingTypeSpelling)
667         findMisspellings(client, text, 0, length, spellingResult);
668
669     Vector<TextCheckingResult> grammarResult;
670     if (checkingTypes & TextCheckingTypeGrammar) {
671         // Only checks grammartical error before the first misspellings
672         int grammarCheckLength = length;
673         for (size_t i = 0; i < spellingResult.size(); ++i) {
674             if (spellingResult[i].location < grammarCheckLength)
675                 grammarCheckLength = spellingResult[i].location;
676         }
677
678         findBadGrammars(client, text, 0, grammarCheckLength, grammarResult);
679     }
680
681     if (grammarResult.size())
682         results.swap(grammarResult);
683
684     if (spellingResult.size()) {
685         if (results.isEmpty())
686             results.swap(spellingResult);
687         else
688             results.append(spellingResult);
689     }
690 #endif
691 }
692
693 bool unifiedTextCheckerEnabled(const Frame* frame)
694 {
695     if (!frame)
696         return false;
697
698     const Settings* settings = frame->settings();
699     if (!settings)
700         return false;
701
702     return settings->unifiedTextCheckerEnabled();
703 }
704
705 }