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