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/core/v8/ExceptionState.h"
     31 #include "bindings/core/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/frame/LocalFrame.h"
     39 #include "core/frame/Settings.h"
     40 #include "core/page/SpellCheckerClient.h"
     41 #include "platform/text/TextBreakIterator.h"
     42 #include "platform/text/TextCheckerClient.h"
     43 
     44 namespace blink {
     45 
     46 static void findBadGrammars(TextCheckerClient& client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
     47 {
     48     int checkLocation = start;
     49     int checkLength = length;
     50 
     51     while (0 < checkLength) {
     52         int badGrammarLocation = -1;
     53         int badGrammarLength = 0;
     54         Vector<GrammarDetail> badGrammarDetails;
     55         client.checkGrammarOfString(String(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.decoration = TextDecorationTypeGrammar;
     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 = iterator->current();
     78     while (0 <= wordStart) {
     79         int wordEnd = iterator->next();
     80         if (wordEnd < 0)
     81             break;
     82         int wordLength = wordEnd - wordStart;
     83         int misspellingLocation = -1;
     84         int misspellingLength = 0;
     85         client.checkSpellingOfString(String(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.decoration = TextDecorationTypeSpelling;
     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 
    101 static PassRefPtrWillBeRawPtr<Range> expandToParagraphBoundary(PassRefPtrWillBeRawPtr<Range> range)
    102 {
    103     RefPtrWillBeRawPtr<Range> paragraphRange = range->cloneRange();
    104     setStart(paragraphRange.get(), startOfParagraph(VisiblePosition(range->startPosition())));
    105     setEnd(paragraphRange.get(), endOfParagraph(VisiblePosition(range->endPosition())));
    106     return paragraphRange;
    107 }
    108 
    109 TextCheckingParagraph::TextCheckingParagraph(PassRefPtrWillBeRawPtr<Range> checkingRange)
    110     : m_checkingRange(checkingRange)
    111     , m_checkingStart(-1)
    112     , m_checkingEnd(-1)
    113     , m_checkingLength(-1)
    114 {
    115 }
    116 
    117 TextCheckingParagraph::TextCheckingParagraph(PassRefPtrWillBeRawPtr<Range> checkingRange, PassRefPtrWillBeRawPtr<Range> paragraphRange)
    118     : m_checkingRange(checkingRange)
    119     , m_paragraphRange(paragraphRange)
    120     , m_checkingStart(-1)
    121     , m_checkingEnd(-1)
    122     , m_checkingLength(-1)
    123 {
    124 }
    125 
    126 TextCheckingParagraph::~TextCheckingParagraph()
    127 {
    128 }
    129 
    130 void TextCheckingParagraph::expandRangeToNextEnd()
    131 {
    132     ASSERT(m_checkingRange);
    133     setEnd(paragraphRange().get(), endOfParagraph(startOfNextParagraph(VisiblePosition(paragraphRange()->startPosition()))));
    134     invalidateParagraphRangeValues();
    135 }
    136 
    137 void TextCheckingParagraph::invalidateParagraphRangeValues()
    138 {
    139     m_checkingStart = m_checkingEnd = -1;
    140     m_offsetAsRange = nullptr;
    141     m_text = String();
    142 }
    143 
    144 int TextCheckingParagraph::rangeLength() const
    145 {
    146     ASSERT(m_checkingRange);
    147     return TextIterator::rangeLength(paragraphRange().get());
    148 }
    149 
    150 PassRefPtrWillBeRawPtr<Range> TextCheckingParagraph::paragraphRange() const
    151 {
    152     ASSERT(m_checkingRange);
    153     if (!m_paragraphRange)
    154         m_paragraphRange = expandToParagraphBoundary(checkingRange());
    155     return m_paragraphRange;
    156 }
    157 
    158 PassRefPtrWillBeRawPtr<Range> TextCheckingParagraph::subrange(int characterOffset, int characterCount) const
    159 {
    160     ASSERT(m_checkingRange);
    161     return TextIterator::subrange(paragraphRange().get(), characterOffset, characterCount);
    162 }
    163 
    164 int TextCheckingParagraph::offsetTo(const Position& position, ExceptionState& exceptionState) const
    165 {
    166     ASSERT(m_checkingRange);
    167     RefPtrWillBeRawPtr<Range> range = offsetAsRange()->cloneRange();
    168     range->setEnd(position.containerNode(), position.computeOffsetInContainerNode(), exceptionState);
    169     if (exceptionState.hadException())
    170         return 0;
    171     return TextIterator::rangeLength(range.get());
    172 }
    173 
    174 bool TextCheckingParagraph::isEmpty() const
    175 {
    176     // Both predicates should have same result, but we check both just for sure.
    177     // We need to investigate to remove this redundancy.
    178     return isRangeEmpty() || isTextEmpty();
    179 }
    180 
    181 PassRefPtrWillBeRawPtr<Range> TextCheckingParagraph::offsetAsRange() const
    182 {
    183     ASSERT(m_checkingRange);
    184     if (!m_offsetAsRange)
    185         m_offsetAsRange = Range::create(paragraphRange()->startContainer()->document(), paragraphRange()->startPosition(), checkingRange()->startPosition());
    186 
    187     return m_offsetAsRange;
    188 }
    189 
    190 const String& TextCheckingParagraph::text() const
    191 {
    192     ASSERT(m_checkingRange);
    193     if (m_text.isEmpty())
    194         m_text = plainText(paragraphRange().get());
    195     return m_text;
    196 }
    197 
    198 int TextCheckingParagraph::checkingStart() const
    199 {
    200     ASSERT(m_checkingRange);
    201     if (m_checkingStart == -1)
    202         m_checkingStart = TextIterator::rangeLength(offsetAsRange().get());
    203     return m_checkingStart;
    204 }
    205 
    206 int TextCheckingParagraph::checkingEnd() const
    207 {
    208     ASSERT(m_checkingRange);
    209     if (m_checkingEnd == -1)
    210         m_checkingEnd = checkingStart() + TextIterator::rangeLength(checkingRange().get());
    211     return m_checkingEnd;
    212 }
    213 
    214 int TextCheckingParagraph::checkingLength() const
    215 {
    216     ASSERT(m_checkingRange);
    217     if (-1 == m_checkingLength)
    218         m_checkingLength = TextIterator::rangeLength(checkingRange().get());
    219     return m_checkingLength;
    220 }
    221 
    222 TextCheckingHelper::TextCheckingHelper(SpellCheckerClient& client, const Position& start, const Position& end)
    223     : m_client(&client)
    224     , m_start(start)
    225     , m_end(end)
    226 {
    227 }
    228 
    229 TextCheckingHelper::~TextCheckingHelper()
    230 {
    231 }
    232 
    233 String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, bool markAll, RefPtrWillBeRawPtr<Range>& firstMisspellingRange)
    234 {
    235     WordAwareIterator it(m_start, m_end);
    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                 Position misspellingStart = m_start;
    265                 Position misspellingEnd = m_end;
    266                 TextIterator::subrange(misspellingStart, misspellingEnd, currentChunkOffset + misspellingLocation, misspellingLength);
    267 
    268                 // Remember first-encountered misspelling and its offset.
    269                 if (!firstMisspelling) {
    270                     firstMisspellingOffset = currentChunkOffset + misspellingLocation;
    271                     firstMisspelling = it.substring(misspellingLocation, misspellingLength);
    272                     firstMisspellingRange = Range::create(misspellingStart.containerNode()->document(), m_start, m_end);
    273                 }
    274 
    275                 // Store marker for misspelled word.
    276                 misspellingStart.containerNode()->document().markers().addMarker(misspellingStart, misspellingEnd, DocumentMarker::Spelling);
    277 
    278                 // Bail out if we're marking only the first misspelling, and not all instances.
    279                 if (!markAll)
    280                     break;
    281             }
    282         }
    283 
    284         currentChunkOffset += length;
    285         it.advance();
    286     }
    287 
    288     return firstMisspelling;
    289 }
    290 
    291 String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail)
    292 {
    293     if (!unifiedTextCheckerEnabled())
    294         return "";
    295 
    296     String firstFoundItem;
    297     String misspelledWord;
    298     String badGrammarPhrase;
    299 
    300     // Initialize out parameters; these will be updated if we find something to return.
    301     outIsSpelling = true;
    302     outFirstFoundOffset = 0;
    303     outGrammarDetail.location = -1;
    304     outGrammarDetail.length = 0;
    305     outGrammarDetail.guesses.clear();
    306     outGrammarDetail.userDescription = "";
    307 
    308     // Expand the search range to encompass entire paragraphs, since text checking needs that much context.
    309     // Determine the character offset from the start of the paragraph to the start of the original search range,
    310     // since we will want to ignore results in this area.
    311     Position paragraphStart = startOfParagraph(VisiblePosition(m_start)).toParentAnchoredPosition();
    312     Position paragraphEnd = m_end;
    313     int totalRangeLength = TextIterator::rangeLength(paragraphStart, paragraphEnd);
    314     paragraphEnd = endOfParagraph(VisiblePosition(m_start)).toParentAnchoredPosition();
    315 
    316     int rangeStartOffset = TextIterator::rangeLength(paragraphStart, m_start);
    317     int totalLengthProcessed = 0;
    318 
    319     bool firstIteration = true;
    320     bool lastIteration = false;
    321     while (totalLengthProcessed < totalRangeLength) {
    322         // Iterate through the search range by paragraphs, checking each one for spelling and grammar.
    323         int currentLength = TextIterator::rangeLength(paragraphStart, paragraphEnd);
    324         int currentStartOffset = firstIteration ? rangeStartOffset : 0;
    325         int currentEndOffset = currentLength;
    326         if (inSameParagraph(VisiblePosition(paragraphStart), VisiblePosition(m_end))) {
    327             // Determine the character offset from the end of the original search range to the end of the paragraph,
    328             // since we will want to ignore results in this area.
    329             currentEndOffset = TextIterator::rangeLength(paragraphStart, m_end);
    330             lastIteration = true;
    331         }
    332         if (currentStartOffset < currentEndOffset) {
    333             String paragraphString = plainText(paragraphStart, paragraphEnd);
    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->decoration == TextDecorationTypeSpelling && 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->decoration == TextDecorationTypeGrammar && 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                         spellingOffset += TextIterator::rangeLength(m_start, paragraphStart);
    382                     outIsSpelling = true;
    383                     outFirstFoundOffset = spellingOffset;
    384                     firstFoundItem = misspelledWord;
    385                     break;
    386                 }
    387                 if (checkGrammar && !badGrammarPhrase.isEmpty()) {
    388                     int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset;
    389                     if (!firstIteration)
    390                         grammarPhraseOffset += TextIterator::rangeLength(m_start, paragraphStart);
    391                     outIsSpelling = false;
    392                     outFirstFoundOffset = grammarPhraseOffset;
    393                     firstFoundItem = badGrammarPhrase;
    394                     break;
    395                 }
    396             }
    397         }
    398         if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength)
    399             break;
    400         VisiblePosition newParagraphStart = startOfNextParagraph(VisiblePosition(paragraphEnd));
    401         paragraphStart = newParagraphStart.toParentAnchoredPosition();
    402         paragraphEnd = endOfParagraph(newParagraphStart).toParentAnchoredPosition();
    403         firstIteration = false;
    404         totalLengthProcessed += currentLength;
    405     }
    406     return firstFoundItem;
    407 }
    408 
    409 int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int startOffset, int endOffset, bool markAll) const
    410 {
    411     // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
    412     // Optionally add a DocumentMarker for each detail in the range.
    413     int earliestDetailLocationSoFar = -1;
    414     int earliestDetailIndex = -1;
    415     for (unsigned i = 0; i < grammarDetails.size(); i++) {
    416         const GrammarDetail* detail = &grammarDetails[i];
    417         ASSERT(detail->length > 0 && detail->location >= 0);
    418 
    419         int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
    420 
    421         // Skip this detail if it starts before the original search range
    422         if (detailStartOffsetInParagraph < startOffset)
    423             continue;
    424 
    425         // Skip this detail if it starts after the original search range
    426         if (detailStartOffsetInParagraph >= endOffset)
    427             continue;
    428 
    429         if (markAll) {
    430             Position badGrammarStart = m_start;
    431             Position badGrammarEnd = m_end;
    432             TextIterator::subrange(badGrammarStart, badGrammarEnd, badGrammarPhraseLocation - startOffset + detail->location, detail->length);
    433             badGrammarStart.containerNode()->document().markers().addMarker(badGrammarStart, badGrammarEnd, DocumentMarker::Grammar, detail->userDescription);
    434         }
    435 
    436         // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order)
    437         if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) {
    438             earliestDetailIndex = i;
    439             earliestDetailLocationSoFar = detail->location;
    440         }
    441     }
    442 
    443     return earliestDetailIndex;
    444 }
    445 
    446 String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
    447 {
    448     // Initialize out parameters; these will be updated if we find something to return.
    449     outGrammarDetail.location = -1;
    450     outGrammarDetail.length = 0;
    451     outGrammarDetail.guesses.clear();
    452     outGrammarDetail.userDescription = "";
    453     outGrammarPhraseOffset = 0;
    454 
    455     String firstBadGrammarPhrase;
    456 
    457     // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context.
    458     // Determine the character offset from the start of the paragraph to the start of the original search range,
    459     // since we will want to ignore results in this area.
    460     TextCheckingParagraph paragraph(Range::create(m_start.containerNode()->document(), m_start, m_end));
    461 
    462     // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
    463     int startOffset = 0;
    464     while (startOffset < paragraph.checkingEnd()) {
    465         Vector<GrammarDetail> grammarDetails;
    466         int badGrammarPhraseLocation = -1;
    467         int badGrammarPhraseLength = 0;
    468         m_client->textChecker().checkGrammarOfString(paragraph.textSubstring(startOffset), grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
    469 
    470         if (!badGrammarPhraseLength) {
    471             ASSERT(badGrammarPhraseLocation == -1);
    472             return String();
    473         }
    474 
    475         ASSERT(badGrammarPhraseLocation >= 0);
    476         badGrammarPhraseLocation += startOffset;
    477 
    478 
    479         // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
    480         int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarPhraseLocation, paragraph.checkingStart(), paragraph.checkingEnd(), markAll);
    481         if (badGrammarIndex >= 0) {
    482             ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size());
    483             outGrammarDetail = grammarDetails[badGrammarIndex];
    484         }
    485 
    486         // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but
    487         // kept going so we could mark all instances).
    488         if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) {
    489             outGrammarPhraseOffset = badGrammarPhraseLocation - paragraph.checkingStart();
    490             firstBadGrammarPhrase = paragraph.textSubstring(badGrammarPhraseLocation, badGrammarPhraseLength);
    491 
    492             // Found one. We're done now, unless we're marking each instance.
    493             if (!markAll)
    494                 break;
    495         }
    496 
    497         // These results were all between the start of the paragraph and the start of the search range; look
    498         // beyond this phrase.
    499         startOffset = badGrammarPhraseLocation + badGrammarPhraseLength;
    500     }
    501 
    502     return firstBadGrammarPhrase;
    503 }
    504 
    505 void TextCheckingHelper::markAllMisspellings(RefPtrWillBeRawPtr<Range>& firstMisspellingRange)
    506 {
    507     // Use the "markAll" feature of findFirstMisspelling. Ignore the return value and the "out parameter";
    508     // all we need to do is mark every instance.
    509     int ignoredOffset;
    510     findFirstMisspelling(ignoredOffset, true, firstMisspellingRange);
    511 }
    512 
    513 void TextCheckingHelper::markAllBadGrammar()
    514 {
    515     // Use the "markAll" feature of ofindFirstBadGrammar. Ignore the return value and "out parameters"; all we need to
    516     // do is mark every instance.
    517     GrammarDetail ignoredGrammarDetail;
    518     int ignoredOffset;
    519     findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true);
    520 }
    521 
    522 bool TextCheckingHelper::unifiedTextCheckerEnabled() const
    523 {
    524     ASSERT(m_start.isNotNull());
    525     Document& doc = m_start.containerNode()->document();
    526     return blink::unifiedTextCheckerEnabled(doc.frame());
    527 }
    528 
    529 void checkTextOfParagraph(TextCheckerClient& client, const String& text, TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results)
    530 {
    531     Vector<UChar> characters;
    532     text.appendTo(characters);
    533     unsigned length = text.length();
    534 
    535     Vector<TextCheckingResult> spellingResult;
    536     if (checkingTypes & TextCheckingTypeSpelling)
    537         findMisspellings(client, characters.data(), 0, length, spellingResult);
    538 
    539     Vector<TextCheckingResult> grammarResult;
    540     if (checkingTypes & TextCheckingTypeGrammar) {
    541         // Only checks grammartical error before the first misspellings
    542         int grammarCheckLength = length;
    543         for (size_t i = 0; i < spellingResult.size(); ++i) {
    544             if (spellingResult[i].location < grammarCheckLength)
    545                 grammarCheckLength = spellingResult[i].location;
    546         }
    547 
    548         findBadGrammars(client, characters.data(), 0, grammarCheckLength, grammarResult);
    549     }
    550 
    551     if (grammarResult.size())
    552         results.swap(grammarResult);
    553 
    554     if (spellingResult.size()) {
    555         if (results.isEmpty())
    556             results.swap(spellingResult);
    557         else
    558             results.appendVector(spellingResult);
    559     }
    560 }
    561 
    562 bool unifiedTextCheckerEnabled(const LocalFrame* frame)
    563 {
    564     if (!frame)
    565         return false;
    566 
    567     const Settings* settings = frame->settings();
    568     if (!settings)
    569         return false;
    570 
    571     return settings->unifiedTextCheckerEnabled();
    572 }
    573 
    574 }
    575