Move parseFontFaceDescriptor to CSSPropertyParser.cpp
[chromium-blink-merge.git] / third_party / WebKit / Source / core / editing / spellcheck / SpellChecker.cpp
blob5913a2a0e977499d88af5819e257d577bd64c6fd
1 /*
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
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.
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.
27 #include "config.h"
28 #include "core/editing/spellcheck/SpellChecker.h"
30 #include "core/HTMLNames.h"
31 #include "core/dom/Document.h"
32 #include "core/dom/Element.h"
33 #include "core/dom/NodeTraversal.h"
34 #include "core/editing/EditingUtilities.h"
35 #include "core/editing/Editor.h"
36 #include "core/editing/EphemeralRange.h"
37 #include "core/editing/VisibleUnits.h"
38 #include "core/editing/iterators/CharacterIterator.h"
39 #include "core/editing/markers/DocumentMarkerController.h"
40 #include "core/editing/spellcheck/SpellCheckRequester.h"
41 #include "core/editing/spellcheck/TextCheckingHelper.h"
42 #include "core/frame/LocalFrame.h"
43 #include "core/frame/Settings.h"
44 #include "core/html/HTMLInputElement.h"
45 #include "core/layout/LayoutTextControl.h"
46 #include "core/loader/EmptyClients.h"
47 #include "core/page/Page.h"
48 #include "core/page/SpellCheckerClient.h"
49 #include "platform/text/TextCheckerClient.h"
51 namespace blink {
53 using namespace HTMLNames;
55 namespace {
57 bool isSelectionInTextField(const VisibleSelection& selection)
59 HTMLTextFormControlElement* textControl = enclosingTextFormControl(selection.start());
60 return isHTMLInputElement(textControl) && toHTMLInputElement(textControl)->isTextField();
63 bool isSelectionInTextArea(const VisibleSelection& selection)
65 HTMLTextFormControlElement* textControl = enclosingTextFormControl(selection.start());
66 return isHTMLTextAreaElement(textControl);
69 bool isSelectionInTextFormControl(const VisibleSelection& selection)
71 return !!enclosingTextFormControl(selection.start());
74 } // namespace
76 PassOwnPtrWillBeRawPtr<SpellChecker> SpellChecker::create(LocalFrame& frame)
78 return adoptPtrWillBeNoop(new SpellChecker(frame));
81 static SpellCheckerClient& emptySpellCheckerClient()
83 DEFINE_STATIC_LOCAL(EmptySpellCheckerClient, client, ());
84 return client;
87 SpellCheckerClient& SpellChecker::spellCheckerClient() const
89 if (Page* page = frame().page())
90 return page->spellCheckerClient();
91 return emptySpellCheckerClient();
94 TextCheckerClient& SpellChecker::textChecker() const
96 return spellCheckerClient().textChecker();
99 SpellChecker::SpellChecker(LocalFrame& frame)
100 : m_frame(&frame)
101 , m_spellCheckRequester(SpellCheckRequester::create(frame))
105 SpellChecker::~SpellChecker()
109 bool SpellChecker::isContinuousSpellCheckingEnabled() const
111 return spellCheckerClient().isContinuousSpellCheckingEnabled();
114 void SpellChecker::toggleContinuousSpellChecking()
116 spellCheckerClient().toggleContinuousSpellChecking();
117 if (isContinuousSpellCheckingEnabled())
118 return;
119 for (Frame* frame = this->frame().page()->mainFrame(); frame; frame = frame->tree().traverseNext()) {
120 if (!frame->isLocalFrame())
121 continue;
122 for (Node& node : NodeTraversal::startsAt(&toLocalFrame(frame)->document()->rootNode()))
123 node.setAlreadySpellChecked(false);
127 bool SpellChecker::isGrammarCheckingEnabled()
129 return spellCheckerClient().isGrammarCheckingEnabled();
132 void SpellChecker::didBeginEditing(Element* element)
134 if (isContinuousSpellCheckingEnabled() && unifiedTextCheckerEnabled()) {
135 bool isTextField = false;
136 HTMLTextFormControlElement* enclosingHTMLTextFormControlElement = 0;
137 if (!isHTMLTextFormControlElement(*element))
138 enclosingHTMLTextFormControlElement = enclosingTextFormControl(firstPositionInNode(element));
139 element = enclosingHTMLTextFormControlElement ? enclosingHTMLTextFormControlElement : element;
140 Element* parent = element;
141 if (isHTMLTextFormControlElement(*element)) {
142 HTMLTextFormControlElement* textControl = toHTMLTextFormControlElement(element);
143 parent = textControl;
144 element = textControl->innerEditorElement();
145 if (!element)
146 return;
147 isTextField = isHTMLInputElement(*textControl) && toHTMLInputElement(*textControl).isTextField();
150 if (isTextField || !parent->isAlreadySpellChecked()) {
151 // We always recheck textfields because markers are removed from them on blur.
152 VisibleSelection selection = VisibleSelection::selectionFromContentsOfNode(element);
153 markMisspellingsAndBadGrammar(selection);
154 if (!isTextField)
155 parent->setAlreadySpellChecked(true);
160 void SpellChecker::ignoreSpelling()
162 removeMarkers(frame().selection().selection(), DocumentMarker::Spelling);
165 void SpellChecker::advanceToNextMisspelling(bool startBeforeSelection)
167 // The basic approach is to search in two phases - from the selection end to the end of the doc, and
168 // then we wrap and search from the doc start to (approximately) where we started.
170 // Start at the end of the selection, search to edge of document. Starting at the selection end makes
171 // repeated "check spelling" commands work.
172 VisibleSelection selection(frame().selection().selection());
173 Position spellingSearchStart, spellingSearchEnd;
174 Range::selectNodeContents(frame().document(), spellingSearchStart, spellingSearchEnd);
176 bool startedWithSelection = false;
177 if (selection.start().anchorNode()) {
178 startedWithSelection = true;
179 if (startBeforeSelection) {
180 VisiblePosition start(selection.visibleStart());
181 // We match AppKit's rule: Start 1 character before the selection.
182 VisiblePosition oneBeforeStart = previousPositionOf(start);
183 spellingSearchStart = (oneBeforeStart.isNotNull() ? oneBeforeStart : start).toParentAnchoredPosition();
184 } else {
185 spellingSearchStart = selection.visibleEnd().toParentAnchoredPosition();
189 Position position = spellingSearchStart;
190 if (!isEditablePosition(position)) {
191 // This shouldn't happen in very often because the Spelling menu items aren't enabled unless the
192 // selection is editable.
193 // This can happen in Mail for a mix of non-editable and editable content (like Stationary),
194 // when spell checking the whole document before sending the message.
195 // In that case the document might not be editable, but there are editable pockets that need to be spell checked.
197 position = firstEditableVisiblePositionAfterPositionInRoot(position, frame().document()->documentElement()).deepEquivalent();
198 if (position.isNull())
199 return;
201 spellingSearchStart = position.parentAnchoredEquivalent();
202 startedWithSelection = false; // won't need to wrap
205 // topNode defines the whole range we want to operate on
206 ContainerNode* topNode = highestEditableRoot(position);
207 // TODO(yosin): |lastOffsetForEditing()| is wrong here if
208 // |editingIgnoresContent(highestEditableRoot())| returns true, e.g. <table>
209 spellingSearchEnd = Position::editingPositionOf(topNode, EditingStrategy::lastOffsetForEditing(topNode));
211 // If spellingSearchRange starts in the middle of a word, advance to the
212 // next word so we start checking at a word boundary. Going back by one char
213 // and then forward by a word does the trick.
214 if (startedWithSelection) {
215 VisiblePosition oneBeforeStart = previousPositionOf(createVisiblePosition(spellingSearchStart));
216 if (oneBeforeStart.isNotNull())
217 spellingSearchStart = endOfWord(oneBeforeStart).toParentAnchoredPosition();
218 // else we were already at the start of the editable node
221 if (spellingSearchStart == spellingSearchEnd)
222 return; // nothing to search in
224 // We go to the end of our first range instead of the start of it, just to be sure
225 // we don't get foiled by any word boundary problems at the start. It means we might
226 // do a tiny bit more searching.
227 Node* searchEndNodeAfterWrap = spellingSearchEnd.computeContainerNode();
228 int searchEndOffsetAfterWrap = spellingSearchEnd.offsetInContainerNode();
230 int misspellingOffset = 0;
231 GrammarDetail grammarDetail;
232 int grammarPhraseOffset = 0;
233 Position grammarSearchStart, grammarSearchEnd;
234 String badGrammarPhrase;
235 String misspelledWord;
237 bool isSpelling = true;
238 int foundOffset = 0;
239 String foundItem;
240 if (unifiedTextCheckerEnabled()) {
241 grammarSearchStart = spellingSearchStart;
242 grammarSearchEnd = spellingSearchEnd;
243 foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchStart, spellingSearchEnd).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
244 if (isSpelling) {
245 misspelledWord = foundItem;
246 misspellingOffset = foundOffset;
247 } else {
248 badGrammarPhrase = foundItem;
249 grammarPhraseOffset = foundOffset;
251 } else {
252 misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSearchStart, spellingSearchEnd).findFirstMisspelling(misspellingOffset, false);
253 grammarSearchStart = spellingSearchStart;
254 grammarSearchEnd = spellingSearchEnd;
255 if (!misspelledWord.isEmpty()) {
256 // Stop looking at start of next misspelled word
257 CharacterIterator chars(grammarSearchStart, grammarSearchEnd);
258 chars.advance(misspellingOffset);
259 grammarSearchEnd = chars.startPosition();
262 if (isGrammarCheckingEnabled())
263 badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), grammarSearchStart, grammarSearchEnd).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);
266 // 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
267 // block rather than at a selection).
268 if (startedWithSelection && !misspelledWord && !badGrammarPhrase) {
269 spellingSearchStart = Position::editingPositionOf(topNode, 0);
270 // going until the end of the very first chunk we tested is far enough
271 spellingSearchEnd = Position::editingPositionOf(searchEndNodeAfterWrap, searchEndOffsetAfterWrap);
273 if (unifiedTextCheckerEnabled()) {
274 grammarSearchStart = spellingSearchStart;
275 grammarSearchEnd = spellingSearchEnd;
276 foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchStart, spellingSearchEnd).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
277 if (isSpelling) {
278 misspelledWord = foundItem;
279 misspellingOffset = foundOffset;
280 } else {
281 badGrammarPhrase = foundItem;
282 grammarPhraseOffset = foundOffset;
284 } else {
285 misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSearchStart, spellingSearchEnd).findFirstMisspelling(misspellingOffset, false);
286 grammarSearchStart = spellingSearchStart;
287 grammarSearchEnd = spellingSearchEnd;
288 if (!misspelledWord.isEmpty()) {
289 // Stop looking at start of next misspelled word
290 CharacterIterator chars(grammarSearchStart, grammarSearchEnd);
291 chars.advance(misspellingOffset);
292 grammarSearchEnd = chars.startPosition();
295 if (isGrammarCheckingEnabled())
296 badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), grammarSearchStart, grammarSearchEnd).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);
300 if (!badGrammarPhrase.isEmpty()) {
301 // We found bad grammar. Since we only searched for bad grammar up to the first misspelled word, the bad grammar
302 // takes precedence and we ignore any potential misspelled word. Select the grammar detail, update the spelling
303 // panel, and store a marker so we draw the green squiggle later.
305 ASSERT(badGrammarPhrase.length() > 0);
306 ASSERT(grammarDetail.location != -1 && grammarDetail.length > 0);
308 // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph
309 const EphemeralRange badGrammarRange = calculateCharacterSubrange(EphemeralRange(grammarSearchStart, grammarSearchEnd), grammarPhraseOffset + grammarDetail.location, grammarDetail.length);
310 frame().selection().setSelection(VisibleSelection(badGrammarRange));
311 frame().selection().revealSelection();
313 frame().document()->markers().addMarker(badGrammarRange.startPosition(), badGrammarRange.endPosition(), DocumentMarker::Grammar, grammarDetail.userDescription);
314 } else if (!misspelledWord.isEmpty()) {
315 // We found a misspelling, but not any earlier bad grammar. Select the misspelling, update the spelling panel, and store
316 // a marker so we draw the red squiggle later.
318 const EphemeralRange misspellingRange = calculateCharacterSubrange(EphemeralRange(spellingSearchStart, spellingSearchEnd), misspellingOffset, misspelledWord.length());
319 frame().selection().setSelection(VisibleSelection(misspellingRange));
320 frame().selection().revealSelection();
322 spellCheckerClient().updateSpellingUIWithMisspelledWord(misspelledWord);
323 frame().document()->markers().addMarker(misspellingRange.startPosition(), misspellingRange.endPosition(), DocumentMarker::Spelling);
327 void SpellChecker::showSpellingGuessPanel()
329 if (spellCheckerClient().spellingUIIsShowing()) {
330 spellCheckerClient().showSpellingUI(false);
331 return;
334 advanceToNextMisspelling(true);
335 spellCheckerClient().showSpellingUI(true);
338 void SpellChecker::clearMisspellingsAndBadGrammar(const VisibleSelection &movingSelection)
340 removeMarkers(movingSelection, DocumentMarker::MisspellingMarkers());
343 void SpellChecker::markMisspellingsAndBadGrammar(const VisibleSelection &movingSelection)
345 markMisspellingsAndBadGrammar(movingSelection, isContinuousSpellCheckingEnabled() && isGrammarCheckingEnabled(), movingSelection);
348 void SpellChecker::markMisspellingsAfterLineBreak(const VisibleSelection& wordSelection)
350 TRACE_EVENT0("blink", "SpellChecker::markMisspellingsAfterLineBreak");
352 if (!unifiedTextCheckerEnabled()) {
353 markMisspellings(wordSelection);
354 return;
357 TextCheckingTypeMask textCheckingOptions = 0;
359 if (isContinuousSpellCheckingEnabled())
360 textCheckingOptions |= TextCheckingTypeSpelling;
362 if (isGrammarCheckingEnabled())
363 textCheckingOptions |= TextCheckingTypeGrammar;
365 VisibleSelection wholeParagraph(
366 startOfParagraph(wordSelection.visibleStart()),
367 endOfParagraph(wordSelection.visibleEnd()));
369 markAllMisspellingsAndBadGrammarInRanges(
370 textCheckingOptions, wordSelection.toNormalizedEphemeralRange(),
371 wholeParagraph.toNormalizedEphemeralRange());
374 void SpellChecker::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping)
376 TRACE_EVENT0("blink", "SpellChecker::markMisspellingsAfterTypingToWord");
378 if (unifiedTextCheckerEnabled()) {
379 TextCheckingTypeMask textCheckingOptions = 0;
381 if (isContinuousSpellCheckingEnabled())
382 textCheckingOptions |= TextCheckingTypeSpelling;
384 if (!(textCheckingOptions & TextCheckingTypeSpelling))
385 return;
387 if (isGrammarCheckingEnabled())
388 textCheckingOptions |= TextCheckingTypeGrammar;
390 VisibleSelection adjacentWords = VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary));
391 if (textCheckingOptions & TextCheckingTypeGrammar) {
392 VisibleSelection selectedSentence = VisibleSelection(startOfSentence(wordStart), endOfSentence(wordStart));
393 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWords.toNormalizedEphemeralRange(), selectedSentence.toNormalizedEphemeralRange());
394 } else {
395 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWords.toNormalizedEphemeralRange(), adjacentWords.toNormalizedEphemeralRange());
397 return;
400 if (!isContinuousSpellCheckingEnabled())
401 return;
403 // Check spelling of one word
404 bool result = markMisspellings(VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary)));
406 if (!result || !isGrammarCheckingEnabled())
407 return;
409 // Check grammar of entire sentence
410 markBadGrammar(VisibleSelection(startOfSentence(wordStart), endOfSentence(wordStart)));
413 bool SpellChecker::markMisspellingsOrBadGrammar(const VisibleSelection& selection, bool checkSpelling)
415 // This function is called with a selection already expanded to word boundaries.
416 // Might be nice to assert that here.
418 // This function is used only for as-you-type checking, so if that's off we do nothing. Note that
419 // grammar checking can only be on if spell checking is also on.
420 if (!isContinuousSpellCheckingEnabled())
421 return false;
423 TRACE_EVENT0("blink", "SpellChecker::markMisspellingsOrBadGrammar");
425 const EphemeralRange range = selection.toNormalizedEphemeralRange();
426 if (range.isNull())
427 return false;
429 // If we're not in an editable node, bail.
430 Node* editableNode = range.startPosition().computeContainerNode();
431 if (!editableNode || !editableNode->hasEditableStyle())
432 return false;
434 if (!isSpellCheckingEnabledFor(editableNode))
435 return false;
437 TextCheckingHelper checker(spellCheckerClient(), range.startPosition(), range.endPosition());
438 if (checkSpelling)
439 return checker.markAllMisspellings();
441 if (isGrammarCheckingEnabled())
442 checker.markAllBadGrammar();
443 return false;
446 bool SpellChecker::isSpellCheckingEnabledFor(Node* node) const
448 if (!node)
449 return false;
450 const Element* focusedElement = node->isElementNode() ? toElement(node) : node->parentElement();
451 if (!focusedElement)
452 return false;
453 return focusedElement->isSpellCheckingEnabled();
456 bool SpellChecker::isSpellCheckingEnabledInFocusedNode() const
458 return isSpellCheckingEnabledFor(frame().selection().start().anchorNode());
461 bool SpellChecker::markMisspellings(const VisibleSelection& selection)
463 return markMisspellingsOrBadGrammar(selection, true);
466 void SpellChecker::markBadGrammar(const VisibleSelection& selection)
468 markMisspellingsOrBadGrammar(selection, false);
471 void SpellChecker::markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask textCheckingOptions, const EphemeralRange& spellingRange, const EphemeralRange& grammarRange)
473 ASSERT(unifiedTextCheckerEnabled());
475 bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar;
477 // This function is called with selections already expanded to word boundaries.
478 if (spellingRange.isNull() || (shouldMarkGrammar && grammarRange.isNull()))
479 return;
481 // If we're not in an editable node, bail.
482 Node* editableNode = spellingRange.startPosition().computeContainerNode();
483 if (!editableNode || !editableNode->hasEditableStyle())
484 return;
486 if (!isSpellCheckingEnabledFor(editableNode))
487 return;
489 TextCheckingParagraph fullParagraphToCheck(shouldMarkGrammar ? grammarRange : spellingRange);
491 bool asynchronous = frame().settings() && frame().settings()->asynchronousSpellCheckingEnabled();
492 chunkAndMarkAllMisspellingsAndBadGrammar(textCheckingOptions, fullParagraphToCheck, asynchronous);
495 void SpellChecker::chunkAndMarkAllMisspellingsAndBadGrammar(Node* node)
497 TRACE_EVENT0("blink", "SpellChecker::chunkAndMarkAllMisspellingsAndBadGrammar");
498 if (!node)
499 return;
500 RefPtrWillBeRawPtr<Range> rangeToCheck = Range::create(*frame().document(), firstPositionInNode(node), lastPositionInNode(node));
501 TextCheckingParagraph textToCheck(rangeToCheck, rangeToCheck);
502 bool asynchronous = true;
503 chunkAndMarkAllMisspellingsAndBadGrammar(resolveTextCheckingTypeMask(TextCheckingTypeSpelling | TextCheckingTypeGrammar), textToCheck, asynchronous);
506 void SpellChecker::chunkAndMarkAllMisspellingsAndBadGrammar(TextCheckingTypeMask textCheckingOptions, const TextCheckingParagraph& fullParagraphToCheck, bool asynchronous)
508 if (fullParagraphToCheck.isEmpty())
509 return;
511 // Since the text may be quite big chunk it up and adjust to the sentence boundary.
512 const int kChunkSize = 16 * 1024;
513 int start = fullParagraphToCheck.checkingStart();
514 int end = fullParagraphToCheck.checkingEnd();
515 start = std::min(start, end);
516 end = std::max(start, end);
517 const int kNumChunksToCheck = asynchronous ? (end - start + kChunkSize - 1) / (kChunkSize) : 1;
518 int currentChunkStart = start;
519 if (kNumChunksToCheck == 1 && asynchronous) {
520 EphemeralRange checkRange = fullParagraphToCheck.checkingRange();
521 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, checkRange, checkRange, asynchronous, 0);
522 return;
525 for (int iter = 0; iter < kNumChunksToCheck; ++iter) {
526 EphemeralRange checkRange = expandRangeToSentenceBoundary(fullParagraphToCheck.subrange(currentChunkStart, kChunkSize));
528 int checkingLength = 0;
529 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, checkRange, checkRange, asynchronous, iter, &checkingLength);
530 currentChunkStart += checkingLength;
534 void SpellChecker::markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask textCheckingOptions, const EphemeralRange& checkRange, const EphemeralRange& paragraphRange, bool asynchronous, int requestNumber, int* checkingLength)
536 TextCheckingParagraph sentenceToCheck(checkRange, paragraphRange);
537 if (checkingLength)
538 *checkingLength = sentenceToCheck.checkingLength();
540 RefPtrWillBeRawPtr<SpellCheckRequest> request = SpellCheckRequest::create(resolveTextCheckingTypeMask(textCheckingOptions), TextCheckingProcessBatch, createRange(checkRange), createRange(paragraphRange), requestNumber);
541 if (!request)
542 return;
544 if (asynchronous) {
545 m_spellCheckRequester->requestCheckingFor(request);
546 return;
549 Vector<TextCheckingResult> results;
550 checkTextOfParagraph(textChecker(), sentenceToCheck.text(), resolveTextCheckingTypeMask(textCheckingOptions), results);
551 markAndReplaceFor(request, results);
554 void SpellChecker::markAndReplaceFor(PassRefPtrWillBeRawPtr<SpellCheckRequest> request, const Vector<TextCheckingResult>& results)
556 TRACE_EVENT0("blink", "SpellChecker::markAndReplaceFor");
557 ASSERT(request);
559 TextCheckingTypeMask textCheckingOptions = request->data().mask();
560 TextCheckingParagraph paragraph(request->checkingRange(), request->paragraphRange());
562 bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling;
563 bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar;
565 // Expand the range to encompass entire paragraphs, since text checking needs that much context.
566 int selectionOffset = 0;
567 int ambiguousBoundaryOffset = -1;
568 bool selectionChanged = false;
569 bool restoreSelectionAfterChange = false;
570 bool adjustSelectionForParagraphBoundaries = false;
572 if (shouldMarkSpelling) {
573 if (frame().selection().isCaret()) {
574 // Attempt to save the caret position so we can restore it later if needed
575 Position caretPosition = frame().selection().end();
576 selectionOffset = paragraph.offsetTo(caretPosition);
577 restoreSelectionAfterChange = true;
578 if (selectionOffset > 0 && (static_cast<unsigned>(selectionOffset) > paragraph.text().length() || paragraph.textCharAt(selectionOffset - 1) == newlineCharacter))
579 adjustSelectionForParagraphBoundaries = true;
580 if (selectionOffset > 0 && static_cast<unsigned>(selectionOffset) <= paragraph.text().length() && isAmbiguousBoundaryCharacter(paragraph.textCharAt(selectionOffset - 1)))
581 ambiguousBoundaryOffset = selectionOffset - 1;
585 for (unsigned i = 0; i < results.size(); i++) {
586 int spellingRangeEndOffset = paragraph.checkingEnd();
587 const TextCheckingResult* result = &results[i];
588 int resultLocation = result->location + paragraph.checkingStart();
589 int resultLength = result->length;
590 bool resultEndsAtAmbiguousBoundary = ambiguousBoundaryOffset >= 0 && resultLocation + resultLength == ambiguousBoundaryOffset;
592 // Only mark misspelling if:
593 // 1. Current text checking isn't done for autocorrection, in which case shouldMarkSpelling is false.
594 // 2. Result falls within spellingRange.
595 // 3. The word in question doesn't end at an ambiguous boundary. For instance, we would not mark
596 // "wouldn'" as misspelled right after apostrophe is typed.
597 if (shouldMarkSpelling && result->decoration == TextDecorationTypeSpelling && resultLocation >= paragraph.checkingStart() && resultLocation + resultLength <= spellingRangeEndOffset && !resultEndsAtAmbiguousBoundary) {
598 ASSERT(resultLength > 0 && resultLocation >= 0);
599 const EphemeralRange misspellingRange = calculateCharacterSubrange(paragraph.paragraphRange(), resultLocation, resultLength);
600 frame().document()->markers().addMarker(misspellingRange.startPosition(), misspellingRange.endPosition(), DocumentMarker::Spelling, result->replacement, result->hash);
601 } else if (shouldMarkGrammar && result->decoration == TextDecorationTypeGrammar && paragraph.checkingRangeCovers(resultLocation, resultLength)) {
602 ASSERT(resultLength > 0 && resultLocation >= 0);
603 for (unsigned j = 0; j < result->details.size(); j++) {
604 const GrammarDetail* detail = &result->details[j];
605 ASSERT(detail->length > 0 && detail->location >= 0);
606 if (paragraph.checkingRangeCovers(resultLocation + detail->location, detail->length)) {
607 const EphemeralRange badGrammarRange = calculateCharacterSubrange(paragraph.paragraphRange(), resultLocation + detail->location, detail->length);
608 frame().document()->markers().addMarker(badGrammarRange.startPosition(), badGrammarRange.endPosition(), DocumentMarker::Grammar, detail->userDescription, result->hash);
611 } else if (result->decoration == TextDecorationTypeInvisibleSpellcheck && resultLocation >= paragraph.checkingStart() && resultLocation + resultLength <= spellingRangeEndOffset) {
612 ASSERT(resultLength > 0 && resultLocation >= 0);
613 const EphemeralRange invisibleSpellcheckRange = calculateCharacterSubrange(paragraph.paragraphRange(), resultLocation, resultLength);
614 frame().document()->markers().addMarker(invisibleSpellcheckRange.startPosition(), invisibleSpellcheckRange.endPosition(), DocumentMarker::InvisibleSpellcheck, result->replacement, result->hash);
618 if (selectionChanged) {
619 TextCheckingParagraph extendedParagraph(paragraph);
620 // Restore the caret position if we have made any replacements
621 extendedParagraph.expandRangeToNextEnd();
622 if (restoreSelectionAfterChange && selectionOffset >= 0 && selectionOffset <= extendedParagraph.rangeLength()) {
623 EphemeralRange selectionRange = extendedParagraph.subrange(0, selectionOffset);
624 frame().selection().moveTo(selectionRange.endPosition(), TextAffinity::Downstream);
625 if (adjustSelectionForParagraphBoundaries)
626 frame().selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity);
627 } else {
628 // If this fails for any reason, the fallback is to go one position beyond the last replacement
629 frame().selection().moveTo(frame().selection().selection().visibleEnd());
630 frame().selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity);
635 void SpellChecker::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection)
637 if (unifiedTextCheckerEnabled()) {
638 if (!isContinuousSpellCheckingEnabled())
639 return;
641 // markMisspellingsAndBadGrammar() is triggered by selection change, in which case we check spelling and grammar, but don't autocorrect misspellings.
642 TextCheckingTypeMask textCheckingOptions = TextCheckingTypeSpelling;
643 if (markGrammar && isGrammarCheckingEnabled())
644 textCheckingOptions |= TextCheckingTypeGrammar;
645 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, spellingSelection.toNormalizedEphemeralRange(), grammarSelection.toNormalizedEphemeralRange());
646 return;
649 markMisspellings(spellingSelection);
650 if (markGrammar)
651 markBadGrammar(grammarSelection);
654 void SpellChecker::updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSelectionAtWordBoundary)
656 if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTypeSpelling))
657 return;
659 TRACE_EVENT0("blink", "SpellChecker::updateMarkersForWordsAffectedByEditing");
661 // We want to remove the markers from a word if an editing command will change the word. This can happen in one of
662 // several scenarios:
663 // 1. Insert in the middle of a word.
664 // 2. Appending non whitespace at the beginning of word.
665 // 3. Appending non whitespace at the end of word.
666 // Note that, appending only whitespaces at the beginning or end of word won't change the word, so we don't need to
667 // remove the markers on that word.
668 // Of course, if current selection is a range, we potentially will edit two words that fall on the boundaries of
669 // selection, and remove words between the selection boundaries.
671 VisiblePosition startOfSelection = frame().selection().selection().visibleStart();
672 VisiblePosition endOfSelection = frame().selection().selection().visibleEnd();
673 if (startOfSelection.isNull())
674 return;
675 // First word is the word that ends after or on the start of selection.
676 VisiblePosition startOfFirstWord = startOfWord(startOfSelection, LeftWordIfOnBoundary);
677 VisiblePosition endOfFirstWord = endOfWord(startOfSelection, LeftWordIfOnBoundary);
678 // Last word is the word that begins before or on the end of selection
679 VisiblePosition startOfLastWord = startOfWord(endOfSelection, RightWordIfOnBoundary);
680 VisiblePosition endOfLastWord = endOfWord(endOfSelection, RightWordIfOnBoundary);
682 if (startOfFirstWord.isNull()) {
683 startOfFirstWord = startOfWord(startOfSelection, RightWordIfOnBoundary);
684 endOfFirstWord = endOfWord(startOfSelection, RightWordIfOnBoundary);
687 if (endOfLastWord.isNull()) {
688 startOfLastWord = startOfWord(endOfSelection, LeftWordIfOnBoundary);
689 endOfLastWord = endOfWord(endOfSelection, LeftWordIfOnBoundary);
692 // If doNotRemoveIfSelectionAtWordBoundary is true, and first word ends at the start of selection,
693 // we choose next word as the first word.
694 if (doNotRemoveIfSelectionAtWordBoundary && endOfFirstWord.deepEquivalent() == startOfSelection.deepEquivalent()) {
695 startOfFirstWord = nextWordPosition(startOfFirstWord);
696 endOfFirstWord = endOfWord(startOfFirstWord, RightWordIfOnBoundary);
697 if (startOfFirstWord.deepEquivalent() == endOfSelection.deepEquivalent())
698 return;
701 // If doNotRemoveIfSelectionAtWordBoundary is true, and last word begins at the end of selection,
702 // we choose previous word as the last word.
703 if (doNotRemoveIfSelectionAtWordBoundary && startOfLastWord.deepEquivalent() == endOfSelection.deepEquivalent()) {
704 startOfLastWord = previousWordPosition(startOfLastWord);
705 endOfLastWord = endOfWord(startOfLastWord, RightWordIfOnBoundary);
706 if (endOfLastWord.deepEquivalent() == startOfSelection.deepEquivalent())
707 return;
710 if (startOfFirstWord.isNull() || endOfFirstWord.isNull() || startOfLastWord.isNull() || endOfLastWord.isNull())
711 return;
713 // Now we remove markers on everything between startOfFirstWord and endOfLastWord.
714 // However, if an autocorrection change a single word to multiple words, we want to remove correction mark from all the
715 // resulted words even we only edit one of them. For example, assuming autocorrection changes "avantgarde" to "avant
716 // garde", we will have CorrectionIndicator marker on both words and on the whitespace between them. If we then edit garde,
717 // we would like to remove the marker from word "avant" and whitespace as well. So we need to get the continous range of
718 // of marker that contains the word in question, and remove marker on that whole range.
719 Document* document = frame().document();
720 ASSERT(document);
721 Node* startNode = startOfFirstWord.deepEquivalent().computeContainerNode();
722 int startOffset = startOfFirstWord.deepEquivalent().computeOffsetInContainerNode();
723 int endOffset = endOfLastWord.deepEquivalent().computeOffsetInContainerNode();
724 document->markers().removeMarkers(startNode, startOffset, endOffset - startOffset, DocumentMarker::MisspellingMarkers(), DocumentMarkerController::RemovePartiallyOverlappingMarker);
727 void SpellChecker::didEndEditingOnTextField(Element* e)
729 TRACE_EVENT0("blink", "SpellChecker::didEndEditingOnTextField");
731 // Remove markers when deactivating a selection in an <input type="text"/>.
732 // Prevent new ones from appearing too.
733 m_spellCheckRequester->cancelCheck();
734 HTMLTextFormControlElement* textFormControlElement = toHTMLTextFormControlElement(e);
735 HTMLElement* innerEditor = textFormControlElement->innerEditorElement();
736 DocumentMarker::MarkerTypes markerTypes(DocumentMarker::Spelling);
737 if (isGrammarCheckingEnabled() || unifiedTextCheckerEnabled())
738 markerTypes.add(DocumentMarker::Grammar);
739 for (Node& node : NodeTraversal::inclusiveDescendantsOf(*innerEditor))
740 frame().document()->markers().removeMarkers(&node, markerTypes);
743 void SpellChecker::replaceMisspelledRange(const String& text)
745 EphemeralRange caretRange = frame().selection().selection().toNormalizedEphemeralRange();
746 if (caretRange.isNull())
747 return;
748 DocumentMarkerVector markers = frame().document()->markers().markersInRange(caretRange, DocumentMarker::MisspellingMarkers());
749 if (markers.size() < 1 || markers[0]->startOffset() >= markers[0]->endOffset())
750 return;
751 EphemeralRange markerRange = EphemeralRange(Position(caretRange.startPosition().computeContainerNode(), markers[0]->startOffset()), Position(caretRange.endPosition().computeContainerNode(), markers[0]->endOffset()));
752 if (markerRange.isNull())
753 return;
754 frame().selection().setSelection(VisibleSelection(markerRange), CharacterGranularity);
755 frame().editor().replaceSelectionWithText(text, false, false);
758 void SpellChecker::respondToChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions options)
760 TRACE_EVENT0("blink", "SpellChecker::respondToChangedSelection");
762 bool closeTyping = options & FrameSelection::CloseTyping;
763 bool isContinuousSpellCheckingEnabled = this->isContinuousSpellCheckingEnabled();
764 bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled && isGrammarCheckingEnabled();
765 if (isContinuousSpellCheckingEnabled) {
766 VisibleSelection newAdjacentWords;
767 VisibleSelection newSelectedSentence;
768 bool caretBrowsing = frame().settings() && frame().settings()->caretBrowsingEnabled();
769 const VisibleSelection newSelection = frame().selection().selection();
770 if (isSelectionInTextFormControl(newSelection)) {
771 Position newStart = newSelection.start();
772 newAdjacentWords.setWithoutValidation(HTMLTextFormControlElement::startOfWord(newStart), HTMLTextFormControlElement::endOfWord(newStart));
773 if (isContinuousGrammarCheckingEnabled)
774 newSelectedSentence.setWithoutValidation(HTMLTextFormControlElement::startOfSentence(newStart), HTMLTextFormControlElement::endOfSentence(newStart));
775 } else if (newSelection.isContentEditable() || caretBrowsing) {
776 VisiblePosition newStart(newSelection.visibleStart());
777 newAdjacentWords = VisibleSelection(startOfWord(newStart, LeftWordIfOnBoundary), endOfWord(newStart, RightWordIfOnBoundary));
778 if (isContinuousGrammarCheckingEnabled)
779 newSelectedSentence = VisibleSelection(startOfSentence(newStart), endOfSentence(newStart));
782 // Don't check spelling and grammar if the change of selection is triggered by spelling correction itself.
783 bool shouldCheckSpellingAndGrammar = !(options & FrameSelection::SpellCorrectionTriggered);
785 // When typing we check spelling elsewhere, so don't redo it here.
786 // If this is a change in selection resulting from a delete operation,
787 // oldSelection may no longer be in the document.
788 // FIXME(http://crbug.com/382809): if oldSelection is on a textarea
789 // element, we cause synchronous layout.
790 if (shouldCheckSpellingAndGrammar
791 && closeTyping
792 && !isSelectionInTextField(oldSelection)
793 && (isSelectionInTextArea(oldSelection) || oldSelection.isContentEditable())
794 && oldSelection.start().inDocument()) {
795 spellCheckOldSelection(oldSelection, newAdjacentWords);
798 // FIXME(http://crbug.com/382809):
799 // shouldEraseMarkersAfterChangeSelection is true, we cause synchronous
800 // layout.
801 if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTypeSpelling))
802 removeMarkers(newAdjacentWords, DocumentMarker::Spelling);
803 if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTypeGrammar))
804 removeMarkers(newSelectedSentence, DocumentMarker::Grammar);
807 // When continuous spell checking is off, existing markers disappear after the selection changes.
808 if (!isContinuousSpellCheckingEnabled)
809 frame().document()->markers().removeMarkers(DocumentMarker::Spelling);
810 if (!isContinuousGrammarCheckingEnabled)
811 frame().document()->markers().removeMarkers(DocumentMarker::Grammar);
814 void SpellChecker::removeSpellingMarkers()
816 frame().document()->markers().removeMarkers(DocumentMarker::MisspellingMarkers());
819 void SpellChecker::removeSpellingMarkersUnderWords(const Vector<String>& words)
821 MarkerRemoverPredicate removerPredicate(words);
823 DocumentMarkerController& markerController = frame().document()->markers();
824 markerController.removeMarkers(removerPredicate);
825 markerController.repaintMarkers();
828 void SpellChecker::spellCheckAfterBlur()
830 if (!frame().selection().selection().isContentEditable())
831 return;
833 if (isSelectionInTextField(frame().selection().selection())) {
834 // textFieldDidEndEditing() and textFieldDidBeginEditing() handle this.
835 return;
838 VisibleSelection empty;
839 spellCheckOldSelection(frame().selection().selection(), empty);
842 void SpellChecker::spellCheckOldSelection(const VisibleSelection& oldSelection, const VisibleSelection& newAdjacentWords)
844 TRACE_EVENT0("blink", "SpellChecker::spellCheckOldSelection");
846 VisiblePosition oldStart(oldSelection.visibleStart());
847 VisibleSelection oldAdjacentWords = VisibleSelection(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary));
848 if (!equalSelectionsInDOMTree(oldAdjacentWords, newAdjacentWords)) {
849 if (isContinuousSpellCheckingEnabled() && isGrammarCheckingEnabled()) {
850 VisibleSelection selectedSentence = VisibleSelection(startOfSentence(oldStart), endOfSentence(oldStart));
851 markMisspellingsAndBadGrammar(oldAdjacentWords, true, selectedSentence);
852 } else {
853 markMisspellingsAndBadGrammar(oldAdjacentWords, false, oldAdjacentWords);
858 static Node* findFirstMarkable(Node* node)
860 while (node) {
861 if (!node->layoutObject())
862 return 0;
863 if (node->layoutObject()->isText())
864 return node;
865 if (node->layoutObject()->isTextControl())
866 node = toLayoutTextControl(node->layoutObject())->textFormControlElement()->visiblePositionForIndex(1).deepEquivalent().anchorNode();
867 else if (node->hasChildren())
868 node = node->firstChild();
869 else
870 node = node->nextSibling();
873 return 0;
876 bool SpellChecker::selectionStartHasMarkerFor(DocumentMarker::MarkerType markerType, int from, int length) const
878 Node* node = findFirstMarkable(frame().selection().start().anchorNode());
879 if (!node)
880 return false;
882 unsigned startOffset = static_cast<unsigned>(from);
883 unsigned endOffset = static_cast<unsigned>(from + length);
884 DocumentMarkerVector markers = frame().document()->markers().markersFor(node);
885 for (size_t i = 0; i < markers.size(); ++i) {
886 DocumentMarker* marker = markers[i];
887 if (marker->startOffset() <= startOffset && endOffset <= marker->endOffset() && marker->type() == markerType)
888 return true;
891 return false;
894 bool SpellChecker::selectionStartHasSpellingMarkerFor(int from, int length) const
896 return selectionStartHasMarkerFor(DocumentMarker::Spelling, from, length);
899 TextCheckingTypeMask SpellChecker::resolveTextCheckingTypeMask(TextCheckingTypeMask textCheckingOptions)
901 bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling;
902 bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar;
904 TextCheckingTypeMask checkingTypes = 0;
905 if (shouldMarkSpelling)
906 checkingTypes |= TextCheckingTypeSpelling;
907 if (shouldMarkGrammar)
908 checkingTypes |= TextCheckingTypeGrammar;
910 return checkingTypes;
913 void SpellChecker::removeMarkers(const VisibleSelection& selection, DocumentMarker::MarkerTypes markerTypes)
915 const EphemeralRange range = selection.toNormalizedEphemeralRange();
916 if (range.isNull())
917 return;
918 frame().document()->markers().removeMarkers(range, markerTypes);
921 bool SpellChecker::unifiedTextCheckerEnabled() const
923 return blink::unifiedTextCheckerEnabled(m_frame);
926 void SpellChecker::cancelCheck()
928 m_spellCheckRequester->cancelCheck();
931 void SpellChecker::requestTextChecking(const Element& element)
933 const EphemeralRange range = EphemeralRange::rangeOfContents(element);
934 RefPtrWillBeRawPtr<Range> rangeToCheck = Range::create(element.document(), range.startPosition(), range.endPosition());
935 m_spellCheckRequester->requestCheckingFor(SpellCheckRequest::create(TextCheckingTypeSpelling | TextCheckingTypeGrammar, TextCheckingProcessBatch, rangeToCheck, rangeToCheck));
938 DEFINE_TRACE(SpellChecker)
940 visitor->trace(m_frame);
941 visitor->trace(m_spellCheckRequester);
944 } // namespace blink