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
findBadGrammars(TextCheckerClient & client,const UChar * text,int start,int length,Vector<TextCheckingResult> & results)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
findMisspellings(TextCheckerClient & client,const UChar * text,int start,int length,Vector<TextCheckingResult> & results)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
expandToParagraphBoundary(PassRefPtrWillBeRawPtr<Range> range)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
TextCheckingParagraph(PassRefPtrWillBeRawPtr<Range> checkingRange)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
TextCheckingParagraph(PassRefPtrWillBeRawPtr<Range> checkingRange,PassRefPtrWillBeRawPtr<Range> paragraphRange)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
~TextCheckingParagraph()126 TextCheckingParagraph::~TextCheckingParagraph()
127 {
128 }
129
expandRangeToNextEnd()130 void TextCheckingParagraph::expandRangeToNextEnd()
131 {
132 ASSERT(m_checkingRange);
133 setEnd(paragraphRange().get(), endOfParagraph(startOfNextParagraph(VisiblePosition(paragraphRange()->startPosition()))));
134 invalidateParagraphRangeValues();
135 }
136
invalidateParagraphRangeValues()137 void TextCheckingParagraph::invalidateParagraphRangeValues()
138 {
139 m_checkingStart = m_checkingEnd = -1;
140 m_offsetAsRange = nullptr;
141 m_text = String();
142 }
143
rangeLength() const144 int TextCheckingParagraph::rangeLength() const
145 {
146 ASSERT(m_checkingRange);
147 return TextIterator::rangeLength(paragraphRange().get());
148 }
149
paragraphRange() const150 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
subrange(int characterOffset,int characterCount) const158 PassRefPtrWillBeRawPtr<Range> TextCheckingParagraph::subrange(int characterOffset, int characterCount) const
159 {
160 ASSERT(m_checkingRange);
161 return TextIterator::subrange(paragraphRange().get(), characterOffset, characterCount);
162 }
163
offsetTo(const Position & position,ExceptionState & exceptionState) const164 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
isEmpty() const174 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
offsetAsRange() const181 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
text() const190 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
checkingStart() const198 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
checkingEnd() const206 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
checkingLength() const214 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
TextCheckingHelper(SpellCheckerClient & client,const Position & start,const Position & end)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
~TextCheckingHelper()229 TextCheckingHelper::~TextCheckingHelper()
230 {
231 }
232
findFirstMisspelling(int & firstMisspellingOffset,bool markAll,RefPtrWillBeRawPtr<Range> & firstMisspellingRange)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
findFirstMisspellingOrBadGrammar(bool checkGrammar,bool & outIsSpelling,int & outFirstFoundOffset,GrammarDetail & outGrammarDetail)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
findFirstGrammarDetail(const Vector<GrammarDetail> & grammarDetails,int badGrammarPhraseLocation,int startOffset,int endOffset,bool markAll) const409 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
findFirstBadGrammar(GrammarDetail & outGrammarDetail,int & outGrammarPhraseOffset,bool markAll)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
markAllMisspellings(RefPtrWillBeRawPtr<Range> & firstMisspellingRange)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
markAllBadGrammar()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
unifiedTextCheckerEnabled() const522 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
checkTextOfParagraph(TextCheckerClient & client,const String & text,TextCheckingTypeMask checkingTypes,Vector<TextCheckingResult> & results)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
unifiedTextCheckerEnabled(const LocalFrame * frame)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