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/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"
53 using namespace HTMLNames
;
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());
76 PassOwnPtrWillBeRawPtr
<SpellChecker
> SpellChecker::create(LocalFrame
& frame
)
78 return adoptPtrWillBeNoop(new SpellChecker(frame
));
81 static SpellCheckerClient
& emptySpellCheckerClient()
83 DEFINE_STATIC_LOCAL(EmptySpellCheckerClient
, 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
)
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())
119 for (Frame
* frame
= this->frame().page()->mainFrame(); frame
; frame
= frame
->tree().traverseNext()) {
120 if (!frame
->isLocalFrame())
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();
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
);
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();
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())
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;
240 if (unifiedTextCheckerEnabled()) {
241 grammarSearchStart
= spellingSearchStart
;
242 grammarSearchEnd
= spellingSearchEnd
;
243 foundItem
= TextCheckingHelper(spellCheckerClient(), spellingSearchStart
, spellingSearchEnd
).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling
, foundOffset
, grammarDetail
);
245 misspelledWord
= foundItem
;
246 misspellingOffset
= foundOffset
;
248 badGrammarPhrase
= foundItem
;
249 grammarPhraseOffset
= foundOffset
;
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
);
278 misspelledWord
= foundItem
;
279 misspellingOffset
= foundOffset
;
281 badGrammarPhrase
= foundItem
;
282 grammarPhraseOffset
= foundOffset
;
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);
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
);
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
))
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());
395 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions
, adjacentWords
.toNormalizedEphemeralRange(), adjacentWords
.toNormalizedEphemeralRange());
400 if (!isContinuousSpellCheckingEnabled())
403 // Check spelling of one word
404 bool result
= markMisspellings(VisibleSelection(startOfWord(wordStart
, LeftWordIfOnBoundary
), endOfWord(wordStart
, RightWordIfOnBoundary
)));
406 if (!result
|| !isGrammarCheckingEnabled())
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())
423 TRACE_EVENT0("blink", "SpellChecker::markMisspellingsOrBadGrammar");
425 const EphemeralRange range
= selection
.toNormalizedEphemeralRange();
429 // If we're not in an editable node, bail.
430 Node
* editableNode
= range
.startPosition().computeContainerNode();
431 if (!editableNode
|| !editableNode
->hasEditableStyle())
434 if (!isSpellCheckingEnabledFor(editableNode
))
437 TextCheckingHelper
checker(spellCheckerClient(), range
.startPosition(), range
.endPosition());
439 return checker
.markAllMisspellings();
441 if (isGrammarCheckingEnabled())
442 checker
.markAllBadGrammar();
446 bool SpellChecker::isSpellCheckingEnabledFor(Node
* node
) const
450 const Element
* focusedElement
= node
->isElementNode() ? toElement(node
) : node
->parentElement();
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()))
481 // If we're not in an editable node, bail.
482 Node
* editableNode
= spellingRange
.startPosition().computeContainerNode();
483 if (!editableNode
|| !editableNode
->hasEditableStyle())
486 if (!isSpellCheckingEnabledFor(editableNode
))
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");
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())
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);
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
);
538 *checkingLength
= sentenceToCheck
.checkingLength();
540 RefPtrWillBeRawPtr
<SpellCheckRequest
> request
= SpellCheckRequest::create(resolveTextCheckingTypeMask(textCheckingOptions
), TextCheckingProcessBatch
, createRange(checkRange
), createRange(paragraphRange
), requestNumber
);
545 m_spellCheckRequester
->requestCheckingFor(request
);
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");
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
);
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())
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());
649 markMisspellings(spellingSelection
);
651 markBadGrammar(grammarSelection
);
654 void SpellChecker::updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSelectionAtWordBoundary
)
656 if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTypeSpelling
))
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())
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())
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())
710 if (startOfFirstWord
.isNull() || endOfFirstWord
.isNull() || startOfLastWord
.isNull() || endOfLastWord
.isNull())
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();
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())
748 DocumentMarkerVector markers
= frame().document()->markers().markersInRange(caretRange
, DocumentMarker::MisspellingMarkers());
749 if (markers
.size() < 1 || markers
[0]->startOffset() >= markers
[0]->endOffset())
751 EphemeralRange markerRange
= EphemeralRange(Position(caretRange
.startPosition().computeContainerNode(), markers
[0]->startOffset()), Position(caretRange
.endPosition().computeContainerNode(), markers
[0]->endOffset()));
752 if (markerRange
.isNull())
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
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
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())
833 if (isSelectionInTextField(frame().selection().selection())) {
834 // textFieldDidEndEditing() and textFieldDidBeginEditing() handle this.
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
);
853 markMisspellingsAndBadGrammar(oldAdjacentWords
, false, oldAdjacentWords
);
858 static Node
* findFirstMarkable(Node
* node
)
861 if (!node
->layoutObject())
863 if (node
->layoutObject()->isText())
865 if (node
->layoutObject()->isTextControl())
866 node
= toLayoutTextControl(node
->layoutObject())->textFormControlElement()->visiblePositionForIndex(1).deepEquivalent().anchorNode();
867 else if (node
->hasChildren())
868 node
= node
->firstChild();
870 node
= node
->nextSibling();
876 bool SpellChecker::selectionStartHasMarkerFor(DocumentMarker::MarkerType markerType
, int from
, int length
) const
878 Node
* node
= findFirstMarkable(frame().selection().start().anchorNode());
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
)
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();
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
);