d5538303d0bc7b9f743f52f898ebab53b220371a
[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         ExceptionCode ec = 0;
188         m_offsetAsRange = Range::create(paragraphRange()->startContainer(ec)->document(), paragraphRange()->startPosition(), checkingRange()->startPosition());
189     }
190
191     return m_offsetAsRange;
192 }
193
194 const String& TextCheckingParagraph::text() const
195 {
196     ASSERT(m_checkingRange);
197     if (m_text.isEmpty())
198         m_text = plainText(paragraphRange().get());
199     return m_text; 
200 }
201
202 int TextCheckingParagraph::checkingStart() const
203 {
204     ASSERT(m_checkingRange);
205     if (m_checkingStart == -1)
206         m_checkingStart = TextIterator::rangeLength(offsetAsRange().get());
207     return m_checkingStart;
208 }
209
210 int TextCheckingParagraph::checkingEnd() const
211 {
212     ASSERT(m_checkingRange);
213     if (m_checkingEnd == -1)
214         m_checkingEnd = checkingStart() + TextIterator::rangeLength(checkingRange().get());
215     return m_checkingEnd;
216 }
217
218 int TextCheckingParagraph::checkingLength() const
219 {
220     ASSERT(m_checkingRange);
221     if (-1 == m_checkingLength)
222         m_checkingLength = TextIterator::rangeLength(checkingRange().get());
223     return m_checkingLength;
224 }
225
226 TextCheckingHelper::TextCheckingHelper(EditorClient* client, PassRefPtr<Range> range)
227     : m_client(client)
228     , m_range(range)
229 {
230     ASSERT_ARG(m_client, m_client);
231     ASSERT_ARG(m_range, m_range);
232 }
233
234 TextCheckingHelper::~TextCheckingHelper()
235 {
236 }
237
238 String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange)
239 {
240     WordAwareIterator it(m_range.get());
241     firstMisspellingOffset = 0;
242     
243     String firstMisspelling;
244     int currentChunkOffset = 0;
245
246     while (!it.atEnd()) {
247         const UChar* chars = it.characters();
248         int len = it.length();
249         
250         // Skip some work for one-space-char hunks
251         if (!(len == 1 && chars[0] == ' ')) {
252             
253             int misspellingLocation = -1;
254             int misspellingLength = 0;
255             m_client->textChecker()->checkSpellingOfString(chars, len, &misspellingLocation, &misspellingLength);
256
257             // 5490627 shows that there was some code path here where the String constructor below crashes.
258             // We don't know exactly what combination of bad input caused this, so we're making this much
259             // more robust against bad input on release builds.
260             ASSERT(misspellingLength >= 0);
261             ASSERT(misspellingLocation >= -1);
262             ASSERT(!misspellingLength || misspellingLocation >= 0);
263             ASSERT(misspellingLocation < len);
264             ASSERT(misspellingLength <= len);
265             ASSERT(misspellingLocation + misspellingLength <= len);
266             
267             if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < len && misspellingLength <= len && misspellingLocation + misspellingLength <= len) {
268                 
269                 // Compute range of misspelled word
270                 RefPtr<Range> misspellingRange = TextIterator::subrange(m_range.get(), currentChunkOffset + misspellingLocation, misspellingLength);
271
272                 // Remember first-encountered misspelling and its offset.
273                 if (!firstMisspelling) {
274                     firstMisspellingOffset = currentChunkOffset + misspellingLocation;
275                     firstMisspelling = String(chars + misspellingLocation, misspellingLength);
276                     firstMisspellingRange = misspellingRange;
277                 }
278
279                 // Store marker for misspelled word.
280                 ExceptionCode ec = 0;
281                 misspellingRange->startContainer(ec)->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
282                 ASSERT(!ec);
283
284                 // Bail out if we're marking only the first misspelling, and not all instances.
285                 if (!markAll)
286                     break;
287             }
288         }
289         
290         currentChunkOffset += len;
291         it.advance();
292     }
293     
294     return firstMisspelling;
295 }
296
297 String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail)
298 {
299     if (!unifiedTextCheckerEnabled())
300         return "";
301
302     String firstFoundItem;
303     String misspelledWord;
304     String badGrammarPhrase;
305     ExceptionCode ec = 0;
306     
307     // Initialize out parameters; these will be updated if we find something to return.
308     outIsSpelling = true;
309     outFirstFoundOffset = 0;
310     outGrammarDetail.location = -1;
311     outGrammarDetail.length = 0;
312     outGrammarDetail.guesses.clear();
313     outGrammarDetail.userDescription = "";
314     
315     // Expand the search range to encompass entire paragraphs, since text checking needs that much context.
316     // Determine the character offset from the start of the paragraph to the start of the original search range,
317     // since we will want to ignore results in this area.
318     RefPtr<Range> paragraphRange = m_range->cloneRange(ec);
319     setStart(paragraphRange.get(), startOfParagraph(m_range->startPosition()));
320     int totalRangeLength = TextIterator::rangeLength(paragraphRange.get());
321     setEnd(paragraphRange.get(), endOfParagraph(m_range->startPosition()));
322     
323     RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), m_range->startPosition());
324     int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
325     int totalLengthProcessed = 0;
326     
327     bool firstIteration = true;
328     bool lastIteration = false;
329     while (totalLengthProcessed < totalRangeLength) {
330         // Iterate through the search range by paragraphs, checking each one for spelling and grammar.
331         int currentLength = TextIterator::rangeLength(paragraphRange.get());
332         int currentStartOffset = firstIteration ? rangeStartOffset : 0;
333         int currentEndOffset = currentLength;
334         if (inSameParagraph(paragraphRange->startPosition(), m_range->endPosition())) {
335             // Determine the character offset from the end of the original search range to the end of the paragraph,
336             // since we will want to ignore results in this area.
337             RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), m_range->endPosition());
338             currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get());
339             lastIteration = true;
340         }
341         if (currentStartOffset < currentEndOffset) {
342             String paragraphString = plainText(paragraphRange.get());
343             if (paragraphString.length() > 0) {
344                 bool foundGrammar = false;
345                 int spellingLocation = 0;
346                 int grammarPhraseLocation = 0;
347                 int grammarDetailLocation = 0;
348                 unsigned grammarDetailIndex = 0;
349                 
350                 Vector<TextCheckingResult> results;
351                 TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
352                 checkTextOfParagraph(m_client->textChecker(), paragraphString.characters(), paragraphString.length(), checkingTypes, results);
353                 
354                 for (unsigned i = 0; i < results.size(); i++) {
355                     const TextCheckingResult* result = &results[i];
356                     if (result->type == TextCheckingTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) {
357                         ASSERT(result->length > 0 && result->location >= 0);
358                         spellingLocation = result->location;
359                         misspelledWord = paragraphString.substring(result->location, result->length);
360                         ASSERT(misspelledWord.length());
361                         break;
362                     }
363                     if (checkGrammar && result->type == TextCheckingTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
364                         ASSERT(result->length > 0 && result->location >= 0);
365                         // We can't stop after the first grammar result, since there might still be a spelling result after
366                         // it begins but before the first detail in it, but we can stop if we find a second grammar result.
367                         if (foundGrammar)
368                             break;
369                         for (unsigned j = 0; j < result->details.size(); j++) {
370                             const GrammarDetail* detail = &result->details[j];
371                             ASSERT(detail->length > 0 && detail->location >= 0);
372                             if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) {
373                                 grammarDetailIndex = j;
374                                 grammarDetailLocation = result->location + detail->location;
375                                 foundGrammar = true;
376                             }
377                         }
378                         if (foundGrammar) {
379                             grammarPhraseLocation = result->location;
380                             outGrammarDetail = result->details[grammarDetailIndex];
381                             badGrammarPhrase = paragraphString.substring(result->location, result->length);
382                             ASSERT(badGrammarPhrase.length());
383                         }
384                     }
385                 }
386
387                 if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) {
388                     int spellingOffset = spellingLocation - currentStartOffset;
389                     if (!firstIteration) {
390                         RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), m_range->startPosition(), paragraphRange->startPosition());
391                         spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
392                     }
393                     outIsSpelling = true;
394                     outFirstFoundOffset = spellingOffset;
395                     firstFoundItem = misspelledWord;
396                     break;
397                 }
398                 if (checkGrammar && !badGrammarPhrase.isEmpty()) {
399                     int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset;
400                     if (!firstIteration) {
401                         RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), m_range->startPosition(), paragraphRange->startPosition());
402                         grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
403                     }
404                     outIsSpelling = false;
405                     outFirstFoundOffset = grammarPhraseOffset;
406                     firstFoundItem = badGrammarPhrase;
407                     break;
408                 }
409             }
410         }
411         if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength)
412             break;
413         VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition());
414         setStart(paragraphRange.get(), newParagraphStart);
415         setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart));
416         firstIteration = false;
417         totalLengthProcessed += currentLength;
418     }
419     return firstFoundItem;
420 }
421
422 int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int /*badGrammarPhraseLength*/, int startOffset, int endOffset, bool markAll)
423 {
424 #if USE(GRAMMAR_CHECKING)
425     // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
426     // Optionally add a DocumentMarker for each detail in the range.
427     int earliestDetailLocationSoFar = -1;
428     int earliestDetailIndex = -1;
429     for (unsigned i = 0; i < grammarDetails.size(); i++) {
430         const GrammarDetail* detail = &grammarDetails[i];
431         ASSERT(detail->length > 0 && detail->location >= 0);
432         
433         int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
434         
435         // Skip this detail if it starts before the original search range
436         if (detailStartOffsetInParagraph < startOffset)
437             continue;
438         
439         // Skip this detail if it starts after the original search range
440         if (detailStartOffsetInParagraph >= endOffset)
441             continue;
442         
443         if (markAll) {
444             RefPtr<Range> badGrammarRange = TextIterator::subrange(m_range.get(), badGrammarPhraseLocation - startOffset + detail->location, detail->length);
445             ExceptionCode ec = 0;
446             badGrammarRange->startContainer(ec)->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
447             ASSERT(!ec);
448         }
449         
450         // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order)
451         if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) {
452             earliestDetailIndex = i;
453             earliestDetailLocationSoFar = detail->location;
454         }
455     }
456     
457     return earliestDetailIndex;
458 #else
459     ASSERT_NOT_REACHED();
460     UNUSED_PARAM(grammarDetails);
461     UNUSED_PARAM(badGrammarPhraseLocation);
462     UNUSED_PARAM(startOffset);
463     UNUSED_PARAM(endOffset);
464     UNUSED_PARAM(markAll);
465     return 0;
466 #endif
467 }
468
469 String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
470 {
471     ASSERT(WTF_USE_GRAMMAR_CHECKING);
472     // Initialize out parameters; these will be updated if we find something to return.
473     outGrammarDetail.location = -1;
474     outGrammarDetail.length = 0;
475     outGrammarDetail.guesses.clear();
476     outGrammarDetail.userDescription = "";
477     outGrammarPhraseOffset = 0;
478     
479     String firstBadGrammarPhrase;
480
481     // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context.
482     // Determine the character offset from the start of the paragraph to the start of the original search range,
483     // since we will want to ignore results in this area.
484     TextCheckingParagraph paragraph(m_range);
485     
486     // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
487     int startOffset = 0;
488     while (startOffset < paragraph.checkingEnd()) {
489         Vector<GrammarDetail> grammarDetails;
490         int badGrammarPhraseLocation = -1;
491         int badGrammarPhraseLength = 0;
492         m_client->textChecker()->checkGrammarOfString(paragraph.textCharacters() + startOffset, paragraph.textLength() - startOffset, grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
493         
494         if (!badGrammarPhraseLength) {
495             ASSERT(badGrammarPhraseLocation == -1);
496             return String();
497         }
498
499         ASSERT(badGrammarPhraseLocation >= 0);
500         badGrammarPhraseLocation += startOffset;
501
502         
503         // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
504         int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarPhraseLocation, badGrammarPhraseLength, paragraph.checkingStart(), paragraph.checkingEnd(), markAll);
505         if (badGrammarIndex >= 0) {
506             ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size());
507             outGrammarDetail = grammarDetails[badGrammarIndex];
508         }
509
510         // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but
511         // kept going so we could mark all instances).
512         if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) {
513             outGrammarPhraseOffset = badGrammarPhraseLocation - paragraph.checkingStart();
514             firstBadGrammarPhrase = paragraph.textSubstring(badGrammarPhraseLocation, badGrammarPhraseLength);
515             
516             // Found one. We're done now, unless we're marking each instance.
517             if (!markAll)
518                 break;
519         }
520
521         // These results were all between the start of the paragraph and the start of the search range; look
522         // beyond this phrase.
523         startOffset = badGrammarPhraseLocation + badGrammarPhraseLength;
524     }
525     
526     return firstBadGrammarPhrase;
527 }
528
529
530 bool TextCheckingHelper::isUngrammatical(Vector<String>& guessesVector) const
531 {
532     ASSERT(WTF_USE_GRAMMAR_CHECKING);
533     if (!m_client)
534         return false;
535
536     ExceptionCode ec;
537     if (!m_range || m_range->collapsed(ec))
538         return false;
539     
540     // Returns true only if the passed range exactly corresponds to a bad grammar detail range. This is analogous
541     // to isSelectionMisspelled. It's not good enough for there to be some bad grammar somewhere in the range,
542     // or overlapping the range; the ranges must exactly match.
543     guessesVector.clear();
544     int grammarPhraseOffset;
545     
546     GrammarDetail grammarDetail;
547     String badGrammarPhrase = const_cast<TextCheckingHelper*>(this)->findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);    
548     
549     // No bad grammar in these parts at all.
550     if (badGrammarPhrase.isEmpty())
551         return false;
552     
553     // Bad grammar, but phrase (e.g. sentence) starts beyond start of range.
554     if (grammarPhraseOffset > 0)
555         return false;
556     
557     ASSERT(grammarDetail.location >= 0 && grammarDetail.length > 0);
558     
559     // Bad grammar, but start of detail (e.g. ungrammatical word) doesn't match start of range
560     if (grammarDetail.location + grammarPhraseOffset)
561         return false;
562     
563     // Bad grammar at start of range, but end of bad grammar is before or after end of range
564     if (grammarDetail.length != TextIterator::rangeLength(m_range.get()))
565         return false;
566     
567     // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen).
568     // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work
569     // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling
570     // or a grammar error.
571     m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
572     
573     return true;
574 }
575
576 Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const
577 {
578     if (!unifiedTextCheckerEnabled())
579         return Vector<String>();
580
581     Vector<String> guesses;
582     ExceptionCode ec;
583     misspelled = false;
584     ungrammatical = false;
585     
586     if (!m_client || !m_range || m_range->collapsed(ec))
587         return guesses;
588
589     // Expand the range to encompass entire paragraphs, since text checking needs that much context.
590     TextCheckingParagraph paragraph(m_range);
591     if (paragraph.isEmpty())
592         return guesses;
593
594     Vector<TextCheckingResult> results;
595     TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
596     checkTextOfParagraph(m_client->textChecker(), paragraph.textCharacters(), paragraph.textLength(), checkingTypes, results);
597     
598     for (unsigned i = 0; i < results.size(); i++) {
599         const TextCheckingResult* result = &results[i];
600         if (result->type == TextCheckingTypeSpelling && paragraph.checkingRangeMatches(result->location, result->length)) {
601             String misspelledWord = paragraph.checkingSubstring();
602             ASSERT(misspelledWord.length());
603             m_client->textChecker()->getGuessesForWord(misspelledWord, String(), guesses);
604             m_client->updateSpellingUIWithMisspelledWord(misspelledWord);
605             misspelled = true;
606             return guesses;
607         }
608     }
609     
610     if (!checkGrammar)
611         return guesses;
612         
613     for (unsigned i = 0; i < results.size(); i++) {
614         const TextCheckingResult* result = &results[i];
615         if (result->type == TextCheckingTypeGrammar && paragraph.isCheckingRangeCoveredBy(result->location, result->length)) {
616             for (unsigned j = 0; j < result->details.size(); j++) {
617                 const GrammarDetail* detail = &result->details[j];
618                 ASSERT(detail->length > 0 && detail->location >= 0);
619                 if (paragraph.checkingRangeMatches(result->location + detail->location, detail->length)) {
620                     String badGrammarPhrase = paragraph.textSubstring(result->location, result->length);
621                     ASSERT(badGrammarPhrase.length());
622                     for (unsigned k = 0; k < detail->guesses.size(); k++)
623                         guesses.append(detail->guesses[k]);
624                     m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail);
625                     ungrammatical = true;
626                     return guesses;
627                 }
628             }
629         }
630     }
631     return guesses;
632 }
633
634
635 void TextCheckingHelper::markAllMisspellings(RefPtr<Range>& firstMisspellingRange)
636 {
637     // Use the "markAll" feature of findFirstMisspelling. Ignore the return value and the "out parameter";
638     // all we need to do is mark every instance.
639     int ignoredOffset;
640     findFirstMisspelling(ignoredOffset, true, firstMisspellingRange);
641 }
642
643 void TextCheckingHelper::markAllBadGrammar()
644 {
645     ASSERT(WTF_USE_GRAMMAR_CHECKING);
646     // Use the "markAll" feature of ofindFirstBadGrammar. Ignore the return value and "out parameters"; all we need to
647     // do is mark every instance.
648     GrammarDetail ignoredGrammarDetail;
649     int ignoredOffset;
650     findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true);
651 }
652
653 bool TextCheckingHelper::unifiedTextCheckerEnabled() const
654 {
655     if (!m_range)
656         return false;
657
658     Document* doc = m_range->ownerDocument();
659     if (!doc)
660         return false;
661
662     return WebCore::unifiedTextCheckerEnabled(doc->frame());
663 }
664
665 void checkTextOfParagraph(TextCheckerClient* client, const UChar* text, int length,
666                           TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results)
667 {
668 #if USE(UNIFIED_TEXT_CHECKING)
669     client->checkTextOfParagraph(text, length, checkingTypes, results);
670 #else
671     Vector<TextCheckingResult> spellingResult;
672     if (checkingTypes & TextCheckingTypeSpelling)
673         findMisspellings(client, text, 0, length, spellingResult);
674
675     Vector<TextCheckingResult> grammarResult;
676     if (checkingTypes & TextCheckingTypeGrammar) {
677         // Only checks grammartical error before the first misspellings
678         int grammarCheckLength = length;
679         for (size_t i = 0; i < spellingResult.size(); ++i) {
680             if (spellingResult[i].location < grammarCheckLength)
681                 grammarCheckLength = spellingResult[i].location;
682         }
683
684         findBadGrammars(client, text, 0, grammarCheckLength, grammarResult);
685     }
686
687     if (grammarResult.size())
688         results.swap(grammarResult);
689
690     if (spellingResult.size()) {
691         if (results.isEmpty())
692             results.swap(spellingResult);
693         else
694             results.append(spellingResult);
695     }
696 #endif
697 }
698
699 bool unifiedTextCheckerEnabled(const Frame* frame)
700 {
701     if (!frame)
702         return false;
703
704     const Settings* settings = frame->settings();
705     if (!settings)
706         return false;
707
708     return settings->unifiedTextCheckerEnabled();
709 }
710
711 }