void SpellChecker::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping) { if (unifiedTextCheckerEnabled()) { TextCheckingTypeMask textCheckingOptions = 0; if (isContinuousSpellCheckingEnabled()) textCheckingOptions |= TextCheckingTypeSpelling; if (!(textCheckingOptions & TextCheckingTypeSpelling)) return; if (isGrammarCheckingEnabled()) textCheckingOptions |= TextCheckingTypeGrammar; VisibleSelection adjacentWords = VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary)); if (textCheckingOptions & TextCheckingTypeGrammar) { VisibleSelection selectedSentence = VisibleSelection(startOfSentence(wordStart), endOfSentence(wordStart)); markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWords.toNormalizedRange().get(), selectedSentence.toNormalizedRange().get()); } else { markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWords.toNormalizedRange().get(), adjacentWords.toNormalizedRange().get()); } return; } if (!isContinuousSpellCheckingEnabled()) return; // Check spelling of one word RefPtr<Range> misspellingRange = nullptr; markMisspellings(VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary)), misspellingRange); // Autocorrect the misspelled word. if (!misspellingRange) return; // Get the misspelled word. const String misspelledWord = plainText(misspellingRange.get()); String autocorrectedString = textChecker().getAutoCorrectSuggestionForMisspelledWord(misspelledWord); // If autocorrected word is non empty, replace the misspelled word by this word. if (!autocorrectedString.isEmpty()) { VisibleSelection newSelection(misspellingRange.get(), DOWNSTREAM); if (newSelection != m_frame.selection().selection()) { m_frame.selection().setSelection(newSelection); } m_frame.editor().replaceSelectionWithText(autocorrectedString, false, false); // Reset the charet one character further. m_frame.selection().moveTo(m_frame.selection().selection().visibleEnd()); m_frame.selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity); } if (!isGrammarCheckingEnabled()) return; // Check grammar of entire sentence markBadGrammar(VisibleSelection(startOfSentence(wordStart), endOfSentence(wordStart))); }
Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const { if (!unifiedTextCheckerEnabled()) return Vector<String>(); Vector<String> guesses; misspelled = false; ungrammatical = false; if (!m_client || !m_range || m_range->collapsed(IGNORE_EXCEPTION)) return guesses; // Expand the range to encompass entire paragraphs, since text checking needs that much context. TextCheckingParagraph paragraph(m_range); if (paragraph.isEmpty()) return guesses; Vector<TextCheckingResult> results; TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling; checkTextOfParagraph(*m_client->textChecker(), paragraph.text(), checkingTypes, results); for (unsigned i = 0; i < results.size(); i++) { const TextCheckingResult* result = &results[i]; if (result->type == TextCheckingTypeSpelling && paragraph.checkingRangeMatches(result->location, result->length)) { String misspelledWord = paragraph.checkingSubstring(); ASSERT(misspelledWord.length()); m_client->textChecker()->getGuessesForWord(misspelledWord, String(), guesses); m_client->updateSpellingUIWithMisspelledWord(misspelledWord); misspelled = true; return guesses; } } if (!checkGrammar) return guesses; for (unsigned i = 0; i < results.size(); i++) { const TextCheckingResult* result = &results[i]; if (result->type == TextCheckingTypeGrammar && paragraph.isCheckingRangeCoveredBy(result->location, result->length)) { for (unsigned j = 0; j < result->details.size(); j++) { const GrammarDetail* detail = &result->details[j]; ASSERT(detail->length > 0); ASSERT(detail->location >= 0); if (paragraph.checkingRangeMatches(result->location + detail->location, detail->length)) { String badGrammarPhrase = paragraph.textSubstring(result->location, result->length); ASSERT(badGrammarPhrase.length()); for (unsigned k = 0; k < detail->guesses.size(); k++) guesses.append(detail->guesses[k]); m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail); ungrammatical = true; return guesses; } } } } return guesses; }
Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const { if (!unifiedTextCheckerEnabled()) return Vector<String>(); Vector<String> guesses; misspelled = false; ungrammatical = false; if (!m_client || !m_range || m_range->collapsed()) return guesses; // Expand the range to encompass entire paragraphs, since text checking needs that much context. TextCheckingParagraph paragraph(m_range); if (paragraph.isEmpty()) return guesses; Vector<TextCheckingResult> results; TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling; VisibleSelection currentSelection; if (Frame* frame = m_range->ownerDocument().frame()) currentSelection = frame->selection().selection(); checkTextOfParagraph(*m_client->textChecker(), paragraph.text(), checkingTypes, results, currentSelection); for (auto& result : results) { if (result.type == TextCheckingTypeSpelling && paragraph.checkingRangeMatches(result.location, result.length)) { String misspelledWord = paragraph.checkingSubstring(); ASSERT(misspelledWord.length()); m_client->textChecker()->getGuessesForWord(misspelledWord, String(), currentSelection, guesses); m_client->updateSpellingUIWithMisspelledWord(misspelledWord); misspelled = true; return guesses; } } if (!checkGrammar) return guesses; for (auto& result : results) { if (result.type == TextCheckingTypeGrammar && paragraph.isCheckingRangeCoveredBy(result.location, result.length)) { for (auto& detail : result.details) { ASSERT(detail.length > 0); ASSERT(detail.location >= 0); if (paragraph.checkingRangeMatches(result.location + detail.location, detail.length)) { String badGrammarPhrase = paragraph.textSubstring(result.location, result.length); ASSERT(badGrammarPhrase.length()); for (auto& guess : detail.guesses) guesses.append(guess); m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, detail); ungrammatical = true; return guesses; } } } } return guesses; }
void SpellChecker::didBeginEditing(Element* element) { if (isContinuousSpellCheckingEnabled() && unifiedTextCheckerEnabled()) { if (!element->isAlreadySpellChecked()) { // We always recheck textfields because markers are removed from them on blur. VisibleSelection selection = VisibleSelection::selectionFromContentsOfNode(element); markMisspellingsAndBadGrammar(selection); element->setAlreadySpellChecked(true); } } }
void SpellChecker::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection) { if (unifiedTextCheckerEnabled()) { if (!isContinuousSpellCheckingEnabled()) return; // markMisspellingsAndBadGrammar() is triggered by selection change, in which case we check spelling and grammar, but don't autocorrect misspellings. TextCheckingTypeMask textCheckingOptions = TextCheckingTypeSpelling; if (markGrammar && isGrammarCheckingEnabled()) textCheckingOptions |= TextCheckingTypeGrammar; markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, spellingSelection.toNormalizedRange().get(), grammarSelection.toNormalizedRange().get()); return; } RefPtr<Range> firstMisspellingRange = nullptr; markMisspellings(spellingSelection, firstMisspellingRange); if (markGrammar) markBadGrammar(grammarSelection); }
void SpellChecker::markMisspellingsAfterLineBreak(const VisibleSelection& wordSelection) { if (unifiedTextCheckerEnabled()) { TextCheckingTypeMask textCheckingOptions = 0; if (isContinuousSpellCheckingEnabled()) textCheckingOptions |= TextCheckingTypeSpelling; if (isGrammarCheckingEnabled()) textCheckingOptions |= TextCheckingTypeGrammar; VisibleSelection wholeParagraph( startOfParagraph(wordSelection.visibleStart()), endOfParagraph(wordSelection.visibleEnd())); markAllMisspellingsAndBadGrammarInRanges( textCheckingOptions, wordSelection.toNormalizedRange().get(), wholeParagraph.toNormalizedRange().get()); } else { RefPtr<Range> misspellingRange = wordSelection.firstRange(); markMisspellings(wordSelection, misspellingRange); } }
void SpellChecker::markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask textCheckingOptions, Range* spellingRange, Range* grammarRange) { ASSERT(unifiedTextCheckerEnabled()); bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar; // This function is called with selections already expanded to word boundaries. if (!spellingRange || (shouldMarkGrammar && !grammarRange)) return; // If we're not in an editable node, bail. Node* editableNode = spellingRange->startContainer(); if (!editableNode || !editableNode->hasEditableStyle()) return; if (!isSpellCheckingEnabledFor(editableNode)) return; Range* rangeToCheck = shouldMarkGrammar ? grammarRange : spellingRange; TextCheckingParagraph fullParagraphToCheck(rangeToCheck); bool asynchronous = m_frame.settings() && m_frame.settings()->asynchronousSpellCheckingEnabled(); chunkAndMarkAllMisspellingsAndBadGrammar(textCheckingOptions, fullParagraphToCheck, asynchronous); }
String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail) { if (!unifiedTextCheckerEnabled()) return ""; String firstFoundItem; String misspelledWord; String badGrammarPhrase; // Initialize out parameters; these will be updated if we find something to return. outIsSpelling = true; outFirstFoundOffset = 0; outGrammarDetail.location = -1; outGrammarDetail.length = 0; outGrammarDetail.guesses.clear(); outGrammarDetail.userDescription = ""; // Expand the search range to encompass entire paragraphs, since text checking needs that much context. // Determine the character offset from the start of the paragraph to the start of the original search range, // since we will want to ignore results in this area. Position paragraphStart = startOfParagraph(createVisiblePosition(m_start)).toParentAnchoredPosition(); Position paragraphEnd = m_end; int totalRangeLength = TextIterator::rangeLength(paragraphStart, paragraphEnd); paragraphEnd = endOfParagraph(createVisiblePosition(m_start)).toParentAnchoredPosition(); int rangeStartOffset = TextIterator::rangeLength(paragraphStart, m_start); int totalLengthProcessed = 0; bool firstIteration = true; bool lastIteration = false; while (totalLengthProcessed < totalRangeLength) { // Iterate through the search range by paragraphs, checking each one for spelling and grammar. int currentLength = TextIterator::rangeLength(paragraphStart, paragraphEnd); int currentStartOffset = firstIteration ? rangeStartOffset : 0; int currentEndOffset = currentLength; if (inSameParagraph(createVisiblePosition(paragraphStart), createVisiblePosition(m_end))) { // Determine the character offset from the end of the original search range to the end of the paragraph, // since we will want to ignore results in this area. currentEndOffset = TextIterator::rangeLength(paragraphStart, m_end); lastIteration = true; } if (currentStartOffset < currentEndOffset) { String paragraphString = plainText(EphemeralRange(paragraphStart, paragraphEnd)); if (paragraphString.length() > 0) { bool foundGrammar = false; int spellingLocation = 0; int grammarPhraseLocation = 0; int grammarDetailLocation = 0; unsigned grammarDetailIndex = 0; Vector<TextCheckingResult> results; TextCheckingTypeMask checkingTypes = TextCheckingTypeSpelling | TextCheckingTypeGrammar; checkTextOfParagraph(m_client->textChecker(), paragraphString, checkingTypes, results); for (unsigned i = 0; i < results.size(); i++) { const TextCheckingResult* result = &results[i]; if (result->decoration == TextDecorationTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) { ASSERT(result->length > 0 && result->location >= 0); spellingLocation = result->location; misspelledWord = paragraphString.substring(result->location, result->length); ASSERT(misspelledWord.length()); break; } if (result->decoration == TextDecorationTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) { ASSERT(result->length > 0 && result->location >= 0); // We can't stop after the first grammar result, since there might still be a spelling result after // it begins but before the first detail in it, but we can stop if we find a second grammar result. if (foundGrammar) break; for (unsigned j = 0; j < result->details.size(); j++) { const GrammarDetail* detail = &result->details[j]; ASSERT(detail->length > 0 && detail->location >= 0); if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) { grammarDetailIndex = j; grammarDetailLocation = result->location + detail->location; foundGrammar = true; } } if (foundGrammar) { grammarPhraseLocation = result->location; outGrammarDetail = result->details[grammarDetailIndex]; badGrammarPhrase = paragraphString.substring(result->location, result->length); ASSERT(badGrammarPhrase.length()); } } } if (!misspelledWord.isEmpty() && (badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) { int spellingOffset = spellingLocation - currentStartOffset; if (!firstIteration) spellingOffset += TextIterator::rangeLength(m_start, paragraphStart); outIsSpelling = true; outFirstFoundOffset = spellingOffset; firstFoundItem = misspelledWord; break; } if (!badGrammarPhrase.isEmpty()) { int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset; if (!firstIteration) grammarPhraseOffset += TextIterator::rangeLength(m_start, paragraphStart); outIsSpelling = false; outFirstFoundOffset = grammarPhraseOffset; firstFoundItem = badGrammarPhrase; break; } } } if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength) break; VisiblePosition newParagraphStart = startOfNextParagraph(createVisiblePosition(paragraphEnd)); paragraphStart = newParagraphStart.toParentAnchoredPosition(); paragraphEnd = endOfParagraph(newParagraphStart).toParentAnchoredPosition(); firstIteration = false; totalLengthProcessed += currentLength; } return firstFoundItem; }
void SpellChecker::advanceToNextMisspelling(bool startBeforeSelection) { // The basic approach is to search in two phases - from the selection end to the end of the doc, and // then we wrap and search from the doc start to (approximately) where we started. // Start at the end of the selection, search to edge of document. Starting at the selection end makes // repeated "check spelling" commands work. VisibleSelection selection(m_frame.selection().selection()); RefPtr<Range> spellingSearchRange(rangeOfContents(m_frame.document())); bool startedWithSelection = false; if (selection.start().deprecatedNode()) { startedWithSelection = true; if (startBeforeSelection) { VisiblePosition start(selection.visibleStart()); // We match AppKit's rule: Start 1 character before the selection. VisiblePosition oneBeforeStart = start.previous(); setStart(spellingSearchRange.get(), oneBeforeStart.isNotNull() ? oneBeforeStart : start); } else { setStart(spellingSearchRange.get(), selection.visibleEnd()); } } Position position = spellingSearchRange->startPosition(); if (!isEditablePosition(position)) { // This shouldn't happen in very often because the Spelling menu items aren't enabled unless the // selection is editable. // This can happen in Mail for a mix of non-editable and editable content (like Stationary), // when spell checking the whole document before sending the message. // In that case the document might not be editable, but there are editable pockets that need to be spell checked. position = firstEditableVisiblePositionAfterPositionInRoot(position, m_frame.document()).deepEquivalent(); if (position.isNull()) return; Position rangeCompliantPosition = position.parentAnchoredEquivalent(); spellingSearchRange->setStart(rangeCompliantPosition.deprecatedNode(), rangeCompliantPosition.deprecatedEditingOffset(), IGNORE_EXCEPTION); startedWithSelection = false; // won't need to wrap } // topNode defines the whole range we want to operate on ContainerNode* topNode = highestEditableRoot(position); // FIXME: lastOffsetForEditing() is wrong here if editingIgnoresContent(highestEditableRoot()) returns true (e.g. a <table>) spellingSearchRange->setEnd(topNode, lastOffsetForEditing(topNode), IGNORE_EXCEPTION); // If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking // at a word boundary. Going back by one char and then forward by a word does the trick. if (startedWithSelection) { VisiblePosition oneBeforeStart = startVisiblePosition(spellingSearchRange.get(), DOWNSTREAM).previous(); if (oneBeforeStart.isNotNull()) setStart(spellingSearchRange.get(), endOfWord(oneBeforeStart)); // else we were already at the start of the editable node } if (spellingSearchRange->collapsed()) return; // nothing to search in // We go to the end of our first range instead of the start of it, just to be sure // we don't get foiled by any word boundary problems at the start. It means we might // do a tiny bit more searching. Node* searchEndNodeAfterWrap = spellingSearchRange->endContainer(); int searchEndOffsetAfterWrap = spellingSearchRange->endOffset(); int misspellingOffset = 0; GrammarDetail grammarDetail; int grammarPhraseOffset = 0; RefPtr<Range> grammarSearchRange = nullptr; String badGrammarPhrase; String misspelledWord; bool isSpelling = true; int foundOffset = 0; String foundItem; RefPtr<Range> firstMisspellingRange = nullptr; if (unifiedTextCheckerEnabled()) { grammarSearchRange = spellingSearchRange->cloneRange(); foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); if (isSpelling) { misspelledWord = foundItem; misspellingOffset = foundOffset; } else { badGrammarPhrase = foundItem; grammarPhraseOffset = foundOffset; } } else { misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); grammarSearchRange = spellingSearchRange->cloneRange(); if (!misspelledWord.isEmpty()) { // Stop looking at start of next misspelled word CharacterIterator chars(grammarSearchRange.get()); chars.advance(misspellingOffset); grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset(), IGNORE_EXCEPTION); } if (isGrammarCheckingEnabled()) badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false); } // If we found neither bad grammar nor a misspelled word, wrap and try again (but don't bother if we started at the beginning of the // block rather than at a selection). if (startedWithSelection && !misspelledWord && !badGrammarPhrase) { spellingSearchRange->setStart(topNode, 0, IGNORE_EXCEPTION); // going until the end of the very first chunk we tested is far enough spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap, IGNORE_EXCEPTION); if (unifiedTextCheckerEnabled()) { grammarSearchRange = spellingSearchRange->cloneRange(); foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); if (isSpelling) { misspelledWord = foundItem; misspellingOffset = foundOffset; } else { badGrammarPhrase = foundItem; grammarPhraseOffset = foundOffset; } } else { misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); grammarSearchRange = spellingSearchRange->cloneRange(); if (!misspelledWord.isEmpty()) { // Stop looking at start of next misspelled word CharacterIterator chars(grammarSearchRange.get()); chars.advance(misspellingOffset); grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset(), IGNORE_EXCEPTION); } if (isGrammarCheckingEnabled()) badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false); } } if (!badGrammarPhrase.isEmpty()) { // We found bad grammar. Since we only searched for bad grammar up to the first misspelled word, the bad grammar // takes precedence and we ignore any potential misspelled word. Select the grammar detail, update the spelling // panel, and store a marker so we draw the green squiggle later. ASSERT(badGrammarPhrase.length() > 0); ASSERT(grammarDetail.location != -1 && grammarDetail.length > 0); // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarSearchRange.get(), grammarPhraseOffset + grammarDetail.location, grammarDetail.length); m_frame.selection().setSelection(VisibleSelection(badGrammarRange.get(), SEL_DEFAULT_AFFINITY)); m_frame.selection().revealSelection(); m_frame.document()->markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, grammarDetail.userDescription); } else if (!misspelledWord.isEmpty()) { // We found a misspelling, but not any earlier bad grammar. Select the misspelling, update the spelling panel, and store // a marker so we draw the red squiggle later. RefPtr<Range> misspellingRange = TextIterator::subrange(spellingSearchRange.get(), misspellingOffset, misspelledWord.length()); m_frame.selection().setSelection(VisibleSelection(misspellingRange.get(), DOWNSTREAM)); m_frame.selection().revealSelection(); spellCheckerClient().updateSpellingUIWithMisspelledWord(misspelledWord); m_frame.document()->markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling); } }
String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail) { if (!unifiedTextCheckerEnabled()) return ""; String firstFoundItem; String misspelledWord; String badGrammarPhrase; // Initialize out parameters; these will be updated if we find something to return. outIsSpelling = true; outFirstFoundOffset = 0; outGrammarDetail.location = -1; outGrammarDetail.length = 0; outGrammarDetail.guesses.clear(); outGrammarDetail.userDescription = ""; // Expand the search range to encompass entire paragraphs, since text checking needs that much context. // Determine the character offset from the start of the paragraph to the start of the original search range, // since we will want to ignore results in this area. Ref<Range> paragraphRange = m_range->cloneRange(); setStart(paragraphRange.ptr(), startOfParagraph(m_range->startPosition())); int totalRangeLength = TextIterator::rangeLength(paragraphRange.ptr()); setEnd(paragraphRange.ptr(), endOfParagraph(m_range->startPosition())); Ref<Range> offsetAsRange = Range::create(paragraphRange->startContainer().document(), paragraphRange->startPosition(), m_range->startPosition()); int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.ptr()); int totalLengthProcessed = 0; bool firstIteration = true; bool lastIteration = false; while (totalLengthProcessed < totalRangeLength) { // Iterate through the search range by paragraphs, checking each one for spelling and grammar. int currentLength = TextIterator::rangeLength(paragraphRange.ptr()); int currentStartOffset = firstIteration ? rangeStartOffset : 0; int currentEndOffset = currentLength; if (inSameParagraph(paragraphRange->startPosition(), m_range->endPosition())) { // Determine the character offset from the end of the original search range to the end of the paragraph, // since we will want to ignore results in this area. RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer().document(), paragraphRange->startPosition(), m_range->endPosition()); currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get()); lastIteration = true; } if (currentStartOffset < currentEndOffset) { String paragraphString = plainText(paragraphRange.ptr()); if (paragraphString.length() > 0) { bool foundGrammar = false; int spellingLocation = 0; int grammarPhraseLocation = 0; int grammarDetailLocation = 0; unsigned grammarDetailIndex = 0; Vector<TextCheckingResult> results; TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling; VisibleSelection currentSelection; if (Frame* frame = paragraphRange->ownerDocument().frame()) currentSelection = frame->selection().selection(); checkTextOfParagraph(*m_client->textChecker(), paragraphString, checkingTypes, results, currentSelection); for (auto& result : results) { if (result.type == TextCheckingTypeSpelling && result.location >= currentStartOffset && result.location + result.length <= currentEndOffset) { ASSERT(result.length > 0); ASSERT(result.location >= 0); spellingLocation = result.location; misspelledWord = paragraphString.substring(result.location, result.length); ASSERT(misspelledWord.length()); break; } if (checkGrammar && result.type == TextCheckingTypeGrammar && result.location < currentEndOffset && result.location + result.length > currentStartOffset) { ASSERT(result.length > 0); ASSERT(result.location >= 0); // We can't stop after the first grammar result, since there might still be a spelling result after // it begins but before the first detail in it, but we can stop if we find a second grammar result. if (foundGrammar) break; for (unsigned j = 0; j < result.details.size(); j++) { const GrammarDetail* detail = &result.details[j]; ASSERT(detail->length > 0); ASSERT(detail->location >= 0); if (result.location + detail->location >= currentStartOffset && result.location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result.location + detail->location < grammarDetailLocation)) { grammarDetailIndex = j; grammarDetailLocation = result.location + detail->location; foundGrammar = true; } } if (foundGrammar) { grammarPhraseLocation = result.location; outGrammarDetail = result.details[grammarDetailIndex]; badGrammarPhrase = paragraphString.substring(result.location, result.length); ASSERT(badGrammarPhrase.length()); } } } if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) { int spellingOffset = spellingLocation - currentStartOffset; if (!firstIteration) { RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer().document(), m_range->startPosition(), paragraphRange->startPosition()); spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get()); } outIsSpelling = true; outFirstFoundOffset = spellingOffset; firstFoundItem = misspelledWord; break; } if (checkGrammar && !badGrammarPhrase.isEmpty()) { int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset; if (!firstIteration) { RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer().document(), m_range->startPosition(), paragraphRange->startPosition()); grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get()); } outIsSpelling = false; outFirstFoundOffset = grammarPhraseOffset; firstFoundItem = badGrammarPhrase; break; } } } if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength) break; VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition()); setStart(paragraphRange.ptr(), newParagraphStart); setEnd(paragraphRange.ptr(), endOfParagraph(newParagraphStart)); firstIteration = false; totalLengthProcessed += currentLength; } return firstFoundItem; }