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