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