2 * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
28 #include "core/editing/SpellChecker.h"
30 #include "HTMLNames.h"
31 #include "core/dom/Document.h"
32 #include "core/dom/DocumentMarkerController.h"
33 #include "core/dom/Element.h"
34 #include "core/dom/NodeTraversal.h"
35 #include "core/editing/Editor.h"
36 #include "core/editing/SpellCheckRequester.h"
37 #include "core/editing/TextCheckingHelper.h"
38 #include "core/editing/VisibleUnits.h"
39 #include "core/editing/htmlediting.h"
40 #include "core/frame/LocalFrame.h"
41 #include "core/html/HTMLInputElement.h"
42 #include "core/loader/EmptyClients.h"
43 #include "core/page/Page.h"
44 #include "core/frame/Settings.h"
45 #include "core/page/SpellCheckerClient.h"
46 #include "core/rendering/RenderTextControl.h"
47 #include "platform/text/TextCheckerClient.h"
51 using namespace HTMLNames;
55 bool isSelectionInTextField(const VisibleSelection& selection)
57 HTMLTextFormControlElement* textControl = enclosingTextFormControl(selection.start());
58 return isHTMLInputElement(textControl) && toHTMLInputElement(textControl)->isTextField();
63 PassOwnPtr<SpellChecker> SpellChecker::create(LocalFrame& frame)
65 return adoptPtr(new SpellChecker(frame));
68 static SpellCheckerClient& emptySpellCheckerClient()
70 DEFINE_STATIC_LOCAL(EmptySpellCheckerClient, client, ());
74 SpellCheckerClient& SpellChecker::spellCheckerClient() const
76 if (Page* page = m_frame.page())
77 return page->spellCheckerClient();
78 return emptySpellCheckerClient();
81 TextCheckerClient& SpellChecker::textChecker() const
83 return spellCheckerClient().textChecker();
86 SpellChecker::SpellChecker(LocalFrame& frame)
88 , m_spellCheckRequester(adoptPtr(new SpellCheckRequester(frame)))
92 SpellChecker::~SpellChecker()
96 bool SpellChecker::isContinuousSpellCheckingEnabled() const
98 return spellCheckerClient().isContinuousSpellCheckingEnabled();
101 void SpellChecker::toggleContinuousSpellChecking()
103 spellCheckerClient().toggleContinuousSpellChecking();
104 if (isContinuousSpellCheckingEnabled())
106 for (LocalFrame* frame = m_frame.page()->mainFrame(); frame && frame->document(); frame = frame->tree().traverseNext()) {
107 for (Node* node = &frame->document()->rootNode(); node; node = NodeTraversal::next(*node)) {
108 node->setAlreadySpellChecked(false);
113 bool SpellChecker::isGrammarCheckingEnabled()
115 return spellCheckerClient().isGrammarCheckingEnabled();
118 void SpellChecker::didBeginEditing(Element* element)
120 if (isContinuousSpellCheckingEnabled() && unifiedTextCheckerEnabled()) {
121 bool isTextField = false;
122 HTMLTextFormControlElement* enclosingHTMLTextFormControlElement = 0;
123 if (!isHTMLTextFormControlElement(*element))
124 enclosingHTMLTextFormControlElement = enclosingTextFormControl(firstPositionInNode(element));
125 element = enclosingHTMLTextFormControlElement ? enclosingHTMLTextFormControlElement : element;
126 Element* parent = element;
127 if (isHTMLTextFormControlElement(*element)) {
128 HTMLTextFormControlElement* textControl = toHTMLTextFormControlElement(element);
129 parent = textControl;
130 element = textControl->innerTextElement();
131 isTextField = isHTMLInputElement(*textControl) && toHTMLInputElement(*textControl).isTextField();
134 if (isTextField || !parent->isAlreadySpellChecked()) {
135 // We always recheck textfields because markers are removed from them on blur.
136 VisibleSelection selection = VisibleSelection::selectionFromContentsOfNode(element);
137 markMisspellingsAndBadGrammar(selection);
139 parent->setAlreadySpellChecked(true);
144 void SpellChecker::ignoreSpelling()
146 if (RefPtrWillBeRawPtr<Range> selectedRange = m_frame.selection().toNormalizedRange())
147 m_frame.document()->markers().removeMarkers(selectedRange.get(), DocumentMarker::Spelling);
150 void SpellChecker::advanceToNextMisspelling(bool startBeforeSelection)
152 // The basic approach is to search in two phases - from the selection end to the end of the doc, and
153 // then we wrap and search from the doc start to (approximately) where we started.
155 // Start at the end of the selection, search to edge of document. Starting at the selection end makes
156 // repeated "check spelling" commands work.
157 VisibleSelection selection(m_frame.selection().selection());
158 RefPtrWillBeRawPtr<Range> spellingSearchRange(rangeOfContents(m_frame.document()));
160 bool startedWithSelection = false;
161 if (selection.start().deprecatedNode()) {
162 startedWithSelection = true;
163 if (startBeforeSelection) {
164 VisiblePosition start(selection.visibleStart());
165 // We match AppKit's rule: Start 1 character before the selection.
166 VisiblePosition oneBeforeStart = start.previous();
167 setStart(spellingSearchRange.get(), oneBeforeStart.isNotNull() ? oneBeforeStart : start);
169 setStart(spellingSearchRange.get(), selection.visibleEnd());
173 Position position = spellingSearchRange->startPosition();
174 if (!isEditablePosition(position)) {
175 // This shouldn't happen in very often because the Spelling menu items aren't enabled unless the
176 // selection is editable.
177 // This can happen in Mail for a mix of non-editable and editable content (like Stationary),
178 // when spell checking the whole document before sending the message.
179 // In that case the document might not be editable, but there are editable pockets that need to be spell checked.
181 position = firstEditablePositionAfterPositionInRoot(position, m_frame.document()->documentElement()).deepEquivalent();
182 if (position.isNull())
185 Position rangeCompliantPosition = position.parentAnchoredEquivalent();
186 spellingSearchRange->setStart(rangeCompliantPosition.deprecatedNode(), rangeCompliantPosition.deprecatedEditingOffset(), IGNORE_EXCEPTION);
187 startedWithSelection = false; // won't need to wrap
190 // topNode defines the whole range we want to operate on
191 Node* topNode = highestEditableRoot(position);
192 // FIXME: lastOffsetForEditing() is wrong here if editingIgnoresContent(highestEditableRoot()) returns true (e.g. a <table>)
193 spellingSearchRange->setEnd(topNode, lastOffsetForEditing(topNode), IGNORE_EXCEPTION);
195 // If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking
196 // at a word boundary. Going back by one char and then forward by a word does the trick.
197 if (startedWithSelection) {
198 VisiblePosition oneBeforeStart = startVisiblePosition(spellingSearchRange.get(), DOWNSTREAM).previous();
199 if (oneBeforeStart.isNotNull())
200 setStart(spellingSearchRange.get(), endOfWord(oneBeforeStart));
201 // else we were already at the start of the editable node
204 if (spellingSearchRange->collapsed())
205 return; // nothing to search in
207 // We go to the end of our first range instead of the start of it, just to be sure
208 // we don't get foiled by any word boundary problems at the start. It means we might
209 // do a tiny bit more searching.
210 Node* searchEndNodeAfterWrap = spellingSearchRange->endContainer();
211 int searchEndOffsetAfterWrap = spellingSearchRange->endOffset();
213 int misspellingOffset = 0;
214 GrammarDetail grammarDetail;
215 int grammarPhraseOffset = 0;
216 RefPtrWillBeRawPtr<Range> grammarSearchRange = nullptr;
217 String badGrammarPhrase;
218 String misspelledWord;
220 bool isSpelling = true;
223 RefPtrWillBeRawPtr<Range> firstMisspellingRange = nullptr;
224 if (unifiedTextCheckerEnabled()) {
225 grammarSearchRange = spellingSearchRange->cloneRange();
226 foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
228 misspelledWord = foundItem;
229 misspellingOffset = foundOffset;
231 badGrammarPhrase = foundItem;
232 grammarPhraseOffset = foundOffset;
235 misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange);
236 grammarSearchRange = spellingSearchRange->cloneRange();
237 if (!misspelledWord.isEmpty()) {
238 // Stop looking at start of next misspelled word
239 CharacterIterator chars(grammarSearchRange.get());
240 chars.advance(misspellingOffset);
241 grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset(), IGNORE_EXCEPTION);
244 if (isGrammarCheckingEnabled())
245 badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);
248 // 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
249 // block rather than at a selection).
250 if (startedWithSelection && !misspelledWord && !badGrammarPhrase) {
251 spellingSearchRange->setStart(topNode, 0, IGNORE_EXCEPTION);
252 // going until the end of the very first chunk we tested is far enough
253 spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap, IGNORE_EXCEPTION);
255 if (unifiedTextCheckerEnabled()) {
256 grammarSearchRange = spellingSearchRange->cloneRange();
257 foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
259 misspelledWord = foundItem;
260 misspellingOffset = foundOffset;
262 badGrammarPhrase = foundItem;
263 grammarPhraseOffset = foundOffset;
266 misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange);
267 grammarSearchRange = spellingSearchRange->cloneRange();
268 if (!misspelledWord.isEmpty()) {
269 // Stop looking at start of next misspelled word
270 CharacterIterator chars(grammarSearchRange.get());
271 chars.advance(misspellingOffset);
272 grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset(), IGNORE_EXCEPTION);
275 if (isGrammarCheckingEnabled())
276 badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);
280 if (!badGrammarPhrase.isEmpty()) {
281 // We found bad grammar. Since we only searched for bad grammar up to the first misspelled word, the bad grammar
282 // takes precedence and we ignore any potential misspelled word. Select the grammar detail, update the spelling
283 // panel, and store a marker so we draw the green squiggle later.
285 ASSERT(badGrammarPhrase.length() > 0);
286 ASSERT(grammarDetail.location != -1 && grammarDetail.length > 0);
288 // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph
289 RefPtrWillBeRawPtr<Range> badGrammarRange = TextIterator::subrange(grammarSearchRange.get(), grammarPhraseOffset + grammarDetail.location, grammarDetail.length);
290 m_frame.selection().setSelection(VisibleSelection(badGrammarRange.get(), SEL_DEFAULT_AFFINITY));
291 m_frame.selection().revealSelection();
293 m_frame.document()->markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, grammarDetail.userDescription);
294 } else if (!misspelledWord.isEmpty()) {
295 // We found a misspelling, but not any earlier bad grammar. Select the misspelling, update the spelling panel, and store
296 // a marker so we draw the red squiggle later.
298 RefPtrWillBeRawPtr<Range> misspellingRange = TextIterator::subrange(spellingSearchRange.get(), misspellingOffset, misspelledWord.length());
299 m_frame.selection().setSelection(VisibleSelection(misspellingRange.get(), DOWNSTREAM));
300 m_frame.selection().revealSelection();
302 spellCheckerClient().updateSpellingUIWithMisspelledWord(misspelledWord);
303 m_frame.document()->markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling);
307 void SpellChecker::showSpellingGuessPanel()
309 if (spellCheckerClient().spellingUIIsShowing()) {
310 spellCheckerClient().showSpellingUI(false);
314 advanceToNextMisspelling(true);
315 spellCheckerClient().showSpellingUI(true);
318 void SpellChecker::clearMisspellingsAndBadGrammar(const VisibleSelection &movingSelection)
320 RefPtrWillBeRawPtr<Range> selectedRange = movingSelection.toNormalizedRange();
322 m_frame.document()->markers().removeMarkers(selectedRange.get(), DocumentMarker::MisspellingMarkers());
325 void SpellChecker::markMisspellingsAndBadGrammar(const VisibleSelection &movingSelection)
327 markMisspellingsAndBadGrammar(movingSelection, isContinuousSpellCheckingEnabled() && isGrammarCheckingEnabled(), movingSelection);
330 void SpellChecker::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping)
332 if (unifiedTextCheckerEnabled()) {
333 TextCheckingTypeMask textCheckingOptions = 0;
335 if (isContinuousSpellCheckingEnabled())
336 textCheckingOptions |= TextCheckingTypeSpelling;
338 if (!(textCheckingOptions & TextCheckingTypeSpelling))
341 if (isGrammarCheckingEnabled())
342 textCheckingOptions |= TextCheckingTypeGrammar;
344 VisibleSelection adjacentWords = VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary));
345 if (textCheckingOptions & TextCheckingTypeGrammar) {
346 VisibleSelection selectedSentence = VisibleSelection(startOfSentence(wordStart), endOfSentence(wordStart));
347 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWords.toNormalizedRange().get(), selectedSentence.toNormalizedRange().get());
349 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWords.toNormalizedRange().get(), adjacentWords.toNormalizedRange().get());
354 if (!isContinuousSpellCheckingEnabled())
357 // Check spelling of one word
358 RefPtrWillBeRawPtr<Range> misspellingRange = nullptr;
359 markMisspellings(VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary)), misspellingRange);
361 // Autocorrect the misspelled word.
362 if (!misspellingRange)
365 // Get the misspelled word.
366 const String misspelledWord = plainText(misspellingRange.get());
367 String autocorrectedString = textChecker().getAutoCorrectSuggestionForMisspelledWord(misspelledWord);
369 // If autocorrected word is non empty, replace the misspelled word by this word.
370 if (!autocorrectedString.isEmpty()) {
371 VisibleSelection newSelection(misspellingRange.get(), DOWNSTREAM);
372 if (newSelection != m_frame.selection().selection()) {
373 m_frame.selection().setSelection(newSelection);
376 m_frame.editor().replaceSelectionWithText(autocorrectedString, false, false);
378 // Reset the charet one character further.
379 m_frame.selection().moveTo(m_frame.selection().selection().visibleEnd());
380 m_frame.selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity);
383 if (!isGrammarCheckingEnabled())
386 // Check grammar of entire sentence
387 markBadGrammar(VisibleSelection(startOfSentence(wordStart), endOfSentence(wordStart)));
390 void SpellChecker::markMisspellingsOrBadGrammar(const VisibleSelection& selection, bool checkSpelling, RefPtrWillBeRawPtr<Range>& firstMisspellingRange)
392 // This function is called with a selection already expanded to word boundaries.
393 // Might be nice to assert that here.
395 // This function is used only for as-you-type checking, so if that's off we do nothing. Note that
396 // grammar checking can only be on if spell checking is also on.
397 if (!isContinuousSpellCheckingEnabled())
400 RefPtrWillBeRawPtr<Range> searchRange(selection.toNormalizedRange());
404 // If we're not in an editable node, bail.
405 Node* editableNode = searchRange->startContainer();
406 if (!editableNode || !editableNode->rendererIsEditable())
409 if (!isSpellCheckingEnabledFor(editableNode))
412 TextCheckingHelper checker(spellCheckerClient(), searchRange);
414 checker.markAllMisspellings(firstMisspellingRange);
415 else if (isGrammarCheckingEnabled())
416 checker.markAllBadGrammar();
419 bool SpellChecker::isSpellCheckingEnabledFor(Node* node) const
423 const Element* focusedElement = node->isElementNode() ? toElement(node) : node->parentElement();
426 return focusedElement->isSpellCheckingEnabled();
429 bool SpellChecker::isSpellCheckingEnabledInFocusedNode() const
431 return isSpellCheckingEnabledFor(m_frame.selection().start().deprecatedNode());
434 void SpellChecker::markMisspellings(const VisibleSelection& selection, RefPtrWillBeRawPtr<Range>& firstMisspellingRange)
436 markMisspellingsOrBadGrammar(selection, true, firstMisspellingRange);
439 void SpellChecker::markBadGrammar(const VisibleSelection& selection)
441 RefPtrWillBeRawPtr<Range> firstMisspellingRange = nullptr;
442 markMisspellingsOrBadGrammar(selection, false, firstMisspellingRange);
445 void SpellChecker::markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask textCheckingOptions, Range* spellingRange, Range* grammarRange)
447 ASSERT(unifiedTextCheckerEnabled());
449 bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar;
451 // This function is called with selections already expanded to word boundaries.
452 if (!spellingRange || (shouldMarkGrammar && !grammarRange))
455 // If we're not in an editable node, bail.
456 Node* editableNode = spellingRange->startContainer();
457 if (!editableNode || !editableNode->rendererIsEditable())
460 if (!isSpellCheckingEnabledFor(editableNode))
463 Range* rangeToCheck = shouldMarkGrammar ? grammarRange : spellingRange;
464 TextCheckingParagraph fullParagraphToCheck(rangeToCheck);
466 bool asynchronous = m_frame.settings() && m_frame.settings()->asynchronousSpellCheckingEnabled();
467 chunkAndMarkAllMisspellingsAndBadGrammar(textCheckingOptions, fullParagraphToCheck, asynchronous);
470 void SpellChecker::chunkAndMarkAllMisspellingsAndBadGrammar(Node* node)
474 RefPtrWillBeRawPtr<Range> rangeToCheck = Range::create(*m_frame.document(), firstPositionInNode(node), lastPositionInNode(node));
475 TextCheckingParagraph textToCheck(rangeToCheck, rangeToCheck);
476 bool asynchronous = true;
477 chunkAndMarkAllMisspellingsAndBadGrammar(resolveTextCheckingTypeMask(TextCheckingTypeSpelling | TextCheckingTypeGrammar), textToCheck, asynchronous);
480 void SpellChecker::chunkAndMarkAllMisspellingsAndBadGrammar(TextCheckingTypeMask textCheckingOptions, const TextCheckingParagraph& fullParagraphToCheck, bool asynchronous)
482 if (fullParagraphToCheck.isRangeEmpty() || fullParagraphToCheck.isEmpty())
485 // Since the text may be quite big chunk it up and adjust to the sentence boundary.
486 const int kChunkSize = 16 * 1024;
487 int start = fullParagraphToCheck.checkingStart();
488 int end = fullParagraphToCheck.checkingEnd();
489 start = std::min(start, end);
490 end = std::max(start, end);
491 const int kNumChunksToCheck = asynchronous ? (end - start + kChunkSize - 1) / (kChunkSize) : 1;
492 int currentChunkStart = start;
493 RefPtrWillBeRawPtr<Range> checkRange = fullParagraphToCheck.checkingRange();
494 if (kNumChunksToCheck == 1 && asynchronous) {
495 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, checkRange.get(), checkRange.get(), asynchronous, 0);
499 for (int iter = 0; iter < kNumChunksToCheck; ++iter) {
500 checkRange = fullParagraphToCheck.subrange(currentChunkStart, kChunkSize);
501 setStart(checkRange.get(), startOfSentence(VisiblePosition(checkRange->startPosition())));
502 setEnd(checkRange.get(), endOfSentence(VisiblePosition(checkRange->endPosition())));
504 int checkingLength = 0;
505 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, checkRange.get(), checkRange.get(), asynchronous, iter, &checkingLength);
506 currentChunkStart += checkingLength;
510 void SpellChecker::markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask textCheckingOptions, Range* checkRange, Range* paragraphRange, bool asynchronous, int requestNumber, int* checkingLength)
512 TextCheckingParagraph sentenceToCheck(checkRange, paragraphRange);
514 *checkingLength = sentenceToCheck.checkingLength();
516 RefPtr<SpellCheckRequest> request = SpellCheckRequest::create(resolveTextCheckingTypeMask(textCheckingOptions), TextCheckingProcessBatch, checkRange, paragraphRange, requestNumber);
519 m_spellCheckRequester->requestCheckingFor(request);
521 Vector<TextCheckingResult> results;
522 checkTextOfParagraph(textChecker(), sentenceToCheck.text(), resolveTextCheckingTypeMask(textCheckingOptions), results);
523 markAndReplaceFor(request, results);
527 void SpellChecker::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vector<TextCheckingResult>& results)
531 TextCheckingTypeMask textCheckingOptions = request->data().mask();
532 TextCheckingParagraph paragraph(request->checkingRange(), request->paragraphRange());
534 bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling;
535 bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar;
537 // Expand the range to encompass entire paragraphs, since text checking needs that much context.
538 int selectionOffset = 0;
539 int ambiguousBoundaryOffset = -1;
540 bool selectionChanged = false;
541 bool restoreSelectionAfterChange = false;
542 bool adjustSelectionForParagraphBoundaries = false;
544 if (shouldMarkSpelling) {
545 if (m_frame.selection().isCaret()) {
546 // Attempt to save the caret position so we can restore it later if needed
547 Position caretPosition = m_frame.selection().end();
548 selectionOffset = paragraph.offsetTo(caretPosition, ASSERT_NO_EXCEPTION);
549 restoreSelectionAfterChange = true;
550 if (selectionOffset > 0 && (static_cast<unsigned>(selectionOffset) > paragraph.text().length() || paragraph.textCharAt(selectionOffset - 1) == newlineCharacter))
551 adjustSelectionForParagraphBoundaries = true;
552 if (selectionOffset > 0 && static_cast<unsigned>(selectionOffset) <= paragraph.text().length() && isAmbiguousBoundaryCharacter(paragraph.textCharAt(selectionOffset - 1)))
553 ambiguousBoundaryOffset = selectionOffset - 1;
557 for (unsigned i = 0; i < results.size(); i++) {
558 int spellingRangeEndOffset = paragraph.checkingEnd();
559 const TextCheckingResult* result = &results[i];
560 int resultLocation = result->location + paragraph.checkingStart();
561 int resultLength = result->length;
562 bool resultEndsAtAmbiguousBoundary = ambiguousBoundaryOffset >= 0 && resultLocation + resultLength == ambiguousBoundaryOffset;
564 // Only mark misspelling if:
565 // 1. Current text checking isn't done for autocorrection, in which case shouldMarkSpelling is false.
566 // 2. Result falls within spellingRange.
567 // 3. The word in question doesn't end at an ambiguous boundary. For instance, we would not mark
568 // "wouldn'" as misspelled right after apostrophe is typed.
569 if (shouldMarkSpelling && result->decoration == TextDecorationTypeSpelling && resultLocation >= paragraph.checkingStart() && resultLocation + resultLength <= spellingRangeEndOffset && !resultEndsAtAmbiguousBoundary) {
570 ASSERT(resultLength > 0 && resultLocation >= 0);
571 RefPtrWillBeRawPtr<Range> misspellingRange = paragraph.subrange(resultLocation, resultLength);
572 misspellingRange->startContainer()->document().markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling, result->replacement, result->hash);
573 } else if (shouldMarkGrammar && result->decoration == TextDecorationTypeGrammar && paragraph.checkingRangeCovers(resultLocation, resultLength)) {
574 ASSERT(resultLength > 0 && resultLocation >= 0);
575 for (unsigned j = 0; j < result->details.size(); j++) {
576 const GrammarDetail* detail = &result->details[j];
577 ASSERT(detail->length > 0 && detail->location >= 0);
578 if (paragraph.checkingRangeCovers(resultLocation + detail->location, detail->length)) {
579 RefPtrWillBeRawPtr<Range> badGrammarRange = paragraph.subrange(resultLocation + detail->location, detail->length);
580 badGrammarRange->startContainer()->document().markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription, result->hash);
583 } else if (result->decoration == TextDecorationTypeInvisibleSpellcheck && resultLocation >= paragraph.checkingStart() && resultLocation + resultLength <= spellingRangeEndOffset) {
584 ASSERT(resultLength > 0 && resultLocation >= 0);
585 RefPtrWillBeRawPtr<Range> invisibleSpellcheckRange = paragraph.subrange(resultLocation, resultLength);
586 invisibleSpellcheckRange->startContainer()->document().markers().addMarker(invisibleSpellcheckRange.get(), DocumentMarker::InvisibleSpellcheck, result->replacement, result->hash);
590 if (selectionChanged) {
591 TextCheckingParagraph extendedParagraph(paragraph);
592 // Restore the caret position if we have made any replacements
593 extendedParagraph.expandRangeToNextEnd();
594 if (restoreSelectionAfterChange && selectionOffset >= 0 && selectionOffset <= extendedParagraph.rangeLength()) {
595 RefPtrWillBeRawPtr<Range> selectionRange = extendedParagraph.subrange(0, selectionOffset);
596 m_frame.selection().moveTo(selectionRange->endPosition(), DOWNSTREAM);
597 if (adjustSelectionForParagraphBoundaries)
598 m_frame.selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity);
600 // If this fails for any reason, the fallback is to go one position beyond the last replacement
601 m_frame.selection().moveTo(m_frame.selection().selection().visibleEnd());
602 m_frame.selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity);
607 void SpellChecker::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection)
609 if (unifiedTextCheckerEnabled()) {
610 if (!isContinuousSpellCheckingEnabled())
613 // markMisspellingsAndBadGrammar() is triggered by selection change, in which case we check spelling and grammar, but don't autocorrect misspellings.
614 TextCheckingTypeMask textCheckingOptions = TextCheckingTypeSpelling;
615 if (markGrammar && isGrammarCheckingEnabled())
616 textCheckingOptions |= TextCheckingTypeGrammar;
617 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, spellingSelection.toNormalizedRange().get(), grammarSelection.toNormalizedRange().get());
621 RefPtrWillBeRawPtr<Range> firstMisspellingRange = nullptr;
622 markMisspellings(spellingSelection, firstMisspellingRange);
624 markBadGrammar(grammarSelection);
627 void SpellChecker::updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSelectionAtWordBoundary)
629 if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTypeSpelling))
632 // We want to remove the markers from a word if an editing command will change the word. This can happen in one of
633 // several scenarios:
634 // 1. Insert in the middle of a word.
635 // 2. Appending non whitespace at the beginning of word.
636 // 3. Appending non whitespace at the end of word.
637 // Note that, appending only whitespaces at the beginning or end of word won't change the word, so we don't need to
638 // remove the markers on that word.
639 // Of course, if current selection is a range, we potentially will edit two words that fall on the boundaries of
640 // selection, and remove words between the selection boundaries.
642 VisiblePosition startOfSelection = m_frame.selection().selection().visibleStart();
643 VisiblePosition endOfSelection = m_frame.selection().selection().visibleEnd();
644 if (startOfSelection.isNull())
646 // First word is the word that ends after or on the start of selection.
647 VisiblePosition startOfFirstWord = startOfWord(startOfSelection, LeftWordIfOnBoundary);
648 VisiblePosition endOfFirstWord = endOfWord(startOfSelection, LeftWordIfOnBoundary);
649 // Last word is the word that begins before or on the end of selection
650 VisiblePosition startOfLastWord = startOfWord(endOfSelection, RightWordIfOnBoundary);
651 VisiblePosition endOfLastWord = endOfWord(endOfSelection, RightWordIfOnBoundary);
653 if (startOfFirstWord.isNull()) {
654 startOfFirstWord = startOfWord(startOfSelection, RightWordIfOnBoundary);
655 endOfFirstWord = endOfWord(startOfSelection, RightWordIfOnBoundary);
658 if (endOfLastWord.isNull()) {
659 startOfLastWord = startOfWord(endOfSelection, LeftWordIfOnBoundary);
660 endOfLastWord = endOfWord(endOfSelection, LeftWordIfOnBoundary);
663 // If doNotRemoveIfSelectionAtWordBoundary is true, and first word ends at the start of selection,
664 // we choose next word as the first word.
665 if (doNotRemoveIfSelectionAtWordBoundary && endOfFirstWord == startOfSelection) {
666 startOfFirstWord = nextWordPosition(startOfFirstWord);
667 endOfFirstWord = endOfWord(startOfFirstWord, RightWordIfOnBoundary);
668 if (startOfFirstWord == endOfSelection)
672 // If doNotRemoveIfSelectionAtWordBoundary is true, and last word begins at the end of selection,
673 // we choose previous word as the last word.
674 if (doNotRemoveIfSelectionAtWordBoundary && startOfLastWord == endOfSelection) {
675 startOfLastWord = previousWordPosition(startOfLastWord);
676 endOfLastWord = endOfWord(startOfLastWord, RightWordIfOnBoundary);
677 if (endOfLastWord == startOfSelection)
681 if (startOfFirstWord.isNull() || endOfFirstWord.isNull() || startOfLastWord.isNull() || endOfLastWord.isNull())
684 // Now we remove markers on everything between startOfFirstWord and endOfLastWord.
685 // However, if an autocorrection change a single word to multiple words, we want to remove correction mark from all the
686 // resulted words even we only edit one of them. For example, assuming autocorrection changes "avantgarde" to "avant
687 // garde", we will have CorrectionIndicator marker on both words and on the whitespace between them. If we then edit garde,
688 // we would like to remove the marker from word "avant" and whitespace as well. So we need to get the continous range of
689 // of marker that contains the word in question, and remove marker on that whole range.
690 Document* document = m_frame.document();
692 RefPtrWillBeRawPtr<Range> wordRange = Range::create(*document, startOfFirstWord.deepEquivalent(), endOfLastWord.deepEquivalent());
694 document->markers().removeMarkers(wordRange.get(), DocumentMarker::MisspellingMarkers(), DocumentMarkerController::RemovePartiallyOverlappingMarker);
697 void SpellChecker::didEndEditingOnTextField(Element* e)
699 // Remove markers when deactivating a selection in an <input type="text"/>.
700 // Prevent new ones from appearing too.
701 m_spellCheckRequester->cancelCheck();
702 HTMLTextFormControlElement* textFormControlElement = toHTMLTextFormControlElement(e);
703 HTMLElement* innerText = textFormControlElement->innerTextElement();
704 DocumentMarker::MarkerTypes markerTypes(DocumentMarker::Spelling);
705 if (isGrammarCheckingEnabled() || unifiedTextCheckerEnabled())
706 markerTypes.add(DocumentMarker::Grammar);
707 for (Node* node = innerText; node; node = NodeTraversal::next(*node, innerText)) {
708 m_frame.document()->markers().removeMarkers(node, markerTypes);
712 void SpellChecker::respondToChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions options)
714 bool closeTyping = options & FrameSelection::CloseTyping;
715 bool isContinuousSpellCheckingEnabled = this->isContinuousSpellCheckingEnabled();
716 bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled && isGrammarCheckingEnabled();
717 if (isContinuousSpellCheckingEnabled) {
718 VisibleSelection newAdjacentWords;
719 VisibleSelection newSelectedSentence;
720 bool caretBrowsing = m_frame.settings() && m_frame.settings()->caretBrowsingEnabled();
721 if (m_frame.selection().selection().isContentEditable() || caretBrowsing) {
722 VisiblePosition newStart(m_frame.selection().selection().visibleStart());
723 newAdjacentWords = VisibleSelection(startOfWord(newStart, LeftWordIfOnBoundary), endOfWord(newStart, RightWordIfOnBoundary));
724 if (isContinuousGrammarCheckingEnabled)
725 newSelectedSentence = VisibleSelection(startOfSentence(newStart), endOfSentence(newStart));
728 // Don't check spelling and grammar if the change of selection is triggered by spelling correction itself.
729 bool shouldCheckSpellingAndGrammar = !(options & FrameSelection::SpellCorrectionTriggered);
731 // When typing we check spelling elsewhere, so don't redo it here.
732 // If this is a change in selection resulting from a delete operation,
733 // oldSelection may no longer be in the document.
734 if (shouldCheckSpellingAndGrammar
736 && oldSelection.isContentEditable()
737 && oldSelection.start().inDocument()
738 && !isSelectionInTextField(oldSelection)) {
739 spellCheckOldSelection(oldSelection, newAdjacentWords, newSelectedSentence);
742 if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTypeSpelling)) {
743 if (RefPtrWillBeRawPtr<Range> wordRange = newAdjacentWords.toNormalizedRange())
744 m_frame.document()->markers().removeMarkers(wordRange.get(), DocumentMarker::Spelling);
746 if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTypeGrammar)) {
747 if (RefPtrWillBeRawPtr<Range> sentenceRange = newSelectedSentence.toNormalizedRange())
748 m_frame.document()->markers().removeMarkers(sentenceRange.get(), DocumentMarker::Grammar);
752 // When continuous spell checking is off, existing markers disappear after the selection changes.
753 if (!isContinuousSpellCheckingEnabled)
754 m_frame.document()->markers().removeMarkers(DocumentMarker::Spelling);
755 if (!isContinuousGrammarCheckingEnabled)
756 m_frame.document()->markers().removeMarkers(DocumentMarker::Grammar);
759 void SpellChecker::spellCheckAfterBlur()
761 if (!m_frame.selection().selection().isContentEditable())
764 if (isSelectionInTextField(m_frame.selection().selection())) {
765 // textFieldDidEndEditing() and textFieldDidBeginEditing() handle this.
769 VisibleSelection empty;
770 spellCheckOldSelection(m_frame.selection().selection(), empty, empty);
773 void SpellChecker::spellCheckOldSelection(const VisibleSelection& oldSelection, const VisibleSelection& newAdjacentWords, const VisibleSelection& newSelectedSentence)
775 VisiblePosition oldStart(oldSelection.visibleStart());
776 VisibleSelection oldAdjacentWords = VisibleSelection(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary));
777 if (oldAdjacentWords != newAdjacentWords) {
778 if (isContinuousSpellCheckingEnabled() && isGrammarCheckingEnabled()) {
779 VisibleSelection selectedSentence = VisibleSelection(startOfSentence(oldStart), endOfSentence(oldStart));
780 markMisspellingsAndBadGrammar(oldAdjacentWords, true, selectedSentence);
782 markMisspellingsAndBadGrammar(oldAdjacentWords, false, oldAdjacentWords);
787 static Node* findFirstMarkable(Node* node)
790 if (!node->renderer())
792 if (node->renderer()->isText())
794 if (node->renderer()->isTextControl())
795 node = toRenderTextControl(node->renderer())->textFormControlElement()->visiblePositionForIndex(1).deepEquivalent().deprecatedNode();
796 else if (node->firstChild())
797 node = node->firstChild();
799 node = node->nextSibling();
805 bool SpellChecker::selectionStartHasMarkerFor(DocumentMarker::MarkerType markerType, int from, int length) const
807 Node* node = findFirstMarkable(m_frame.selection().start().deprecatedNode());
811 unsigned startOffset = static_cast<unsigned>(from);
812 unsigned endOffset = static_cast<unsigned>(from + length);
813 Vector<DocumentMarker*> markers = m_frame.document()->markers().markersFor(node);
814 for (size_t i = 0; i < markers.size(); ++i) {
815 DocumentMarker* marker = markers[i];
816 if (marker->startOffset() <= startOffset && endOffset <= marker->endOffset() && marker->type() == markerType)
823 TextCheckingTypeMask SpellChecker::resolveTextCheckingTypeMask(TextCheckingTypeMask textCheckingOptions)
825 bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling;
826 bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar;
828 TextCheckingTypeMask checkingTypes = 0;
829 if (shouldMarkSpelling)
830 checkingTypes |= TextCheckingTypeSpelling;
831 if (shouldMarkGrammar)
832 checkingTypes |= TextCheckingTypeGrammar;
834 return checkingTypes;
837 bool SpellChecker::unifiedTextCheckerEnabled() const
839 return WebCore::unifiedTextCheckerEnabled(&m_frame);
842 void SpellChecker::cancelCheck()
844 m_spellCheckRequester->cancelCheck();
847 void SpellChecker::requestTextChecking(const Element& element)
849 RefPtrWillBeRawPtr<Range> rangeToCheck = rangeOfContents(const_cast<Element*>(&element));
850 m_spellCheckRequester->requestCheckingFor(SpellCheckRequest::create(TextCheckingTypeSpelling | TextCheckingTypeGrammar, TextCheckingProcessBatch, rangeToCheck, rangeToCheck));
854 } // namespace WebCore