• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
3  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include "config.h"
28 #include "core/editing/SpellChecker.h"
29 
30 #include "core/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"
48 
49 namespace blink {
50 
51 using namespace HTMLNames;
52 
53 namespace {
54 
isSelectionInTextField(const VisibleSelection & selection)55 bool isSelectionInTextField(const VisibleSelection& selection)
56 {
57     HTMLTextFormControlElement* textControl = enclosingTextFormControl(selection.start());
58     return isHTMLInputElement(textControl) && toHTMLInputElement(textControl)->isTextField();
59 }
60 
isSelectionInTextArea(const VisibleSelection & selection)61 bool isSelectionInTextArea(const VisibleSelection& selection)
62 {
63     HTMLTextFormControlElement* textControl = enclosingTextFormControl(selection.start());
64     return isHTMLTextAreaElement(textControl);
65 }
66 
isSelectionInTextFormControl(const VisibleSelection & selection)67 bool isSelectionInTextFormControl(const VisibleSelection& selection)
68 {
69     return !!enclosingTextFormControl(selection.start());
70 }
71 
72 } // namespace
73 
create(LocalFrame & frame)74 PassOwnPtrWillBeRawPtr<SpellChecker> SpellChecker::create(LocalFrame& frame)
75 {
76     return adoptPtrWillBeNoop(new SpellChecker(frame));
77 }
78 
emptySpellCheckerClient()79 static SpellCheckerClient& emptySpellCheckerClient()
80 {
81     DEFINE_STATIC_LOCAL(EmptySpellCheckerClient, client, ());
82     return client;
83 }
84 
spellCheckerClient() const85 SpellCheckerClient& SpellChecker::spellCheckerClient() const
86 {
87     if (Page* page = frame().page())
88         return page->spellCheckerClient();
89     return emptySpellCheckerClient();
90 }
91 
textChecker() const92 TextCheckerClient& SpellChecker::textChecker() const
93 {
94     return spellCheckerClient().textChecker();
95 }
96 
SpellChecker(LocalFrame & frame)97 SpellChecker::SpellChecker(LocalFrame& frame)
98     : m_frame(&frame)
99     , m_spellCheckRequester(SpellCheckRequester::create(frame))
100 {
101 }
102 
~SpellChecker()103 SpellChecker::~SpellChecker()
104 {
105 }
106 
isContinuousSpellCheckingEnabled() const107 bool SpellChecker::isContinuousSpellCheckingEnabled() const
108 {
109     return spellCheckerClient().isContinuousSpellCheckingEnabled();
110 }
111 
toggleContinuousSpellChecking()112 void SpellChecker::toggleContinuousSpellChecking()
113 {
114     spellCheckerClient().toggleContinuousSpellChecking();
115     if (isContinuousSpellCheckingEnabled())
116         return;
117     for (Frame* frame = this->frame().page()->mainFrame(); frame; frame = frame->tree().traverseNext()) {
118         if (!frame->isLocalFrame())
119             continue;
120         for (Node* node = &toLocalFrame(frame)->document()->rootNode(); node; node = NodeTraversal::next(*node)) {
121             node->setAlreadySpellChecked(false);
122         }
123     }
124 }
125 
isGrammarCheckingEnabled()126 bool SpellChecker::isGrammarCheckingEnabled()
127 {
128     return spellCheckerClient().isGrammarCheckingEnabled();
129 }
130 
didBeginEditing(Element * element)131 void SpellChecker::didBeginEditing(Element* element)
132 {
133     if (isContinuousSpellCheckingEnabled() && unifiedTextCheckerEnabled()) {
134         bool isTextField = false;
135         HTMLTextFormControlElement* enclosingHTMLTextFormControlElement = 0;
136         if (!isHTMLTextFormControlElement(*element))
137             enclosingHTMLTextFormControlElement = enclosingTextFormControl(firstPositionInNode(element));
138         element = enclosingHTMLTextFormControlElement ? enclosingHTMLTextFormControlElement : element;
139         Element* parent = element;
140         if (isHTMLTextFormControlElement(*element)) {
141             HTMLTextFormControlElement* textControl = toHTMLTextFormControlElement(element);
142             parent = textControl;
143             element = textControl->innerEditorElement();
144             isTextField = isHTMLInputElement(*textControl) && toHTMLInputElement(*textControl).isTextField();
145         }
146 
147         if (isTextField || !parent->isAlreadySpellChecked()) {
148             // We always recheck textfields because markers are removed from them on blur.
149             VisibleSelection selection = VisibleSelection::selectionFromContentsOfNode(element);
150             markMisspellingsAndBadGrammar(selection);
151             if (!isTextField)
152                 parent->setAlreadySpellChecked(true);
153         }
154     }
155 }
156 
ignoreSpelling()157 void SpellChecker::ignoreSpelling()
158 {
159     if (RefPtrWillBeRawPtr<Range> selectedRange = frame().selection().toNormalizedRange())
160         frame().document()->markers().removeMarkers(selectedRange.get(), DocumentMarker::Spelling);
161 }
162 
advanceToNextMisspelling(bool startBeforeSelection)163 void SpellChecker::advanceToNextMisspelling(bool startBeforeSelection)
164 {
165     // The basic approach is to search in two phases - from the selection end to the end of the doc, and
166     // then we wrap and search from the doc start to (approximately) where we started.
167 
168     // Start at the end of the selection, search to edge of document. Starting at the selection end makes
169     // repeated "check spelling" commands work.
170     VisibleSelection selection(frame().selection().selection());
171     Position spellingSearchStart, spellingSearchEnd;
172     Range::selectNodeContents(frame().document(), spellingSearchStart, spellingSearchEnd);
173 
174     bool startedWithSelection = false;
175     if (selection.start().deprecatedNode()) {
176         startedWithSelection = true;
177         if (startBeforeSelection) {
178             VisiblePosition start(selection.visibleStart());
179             // We match AppKit's rule: Start 1 character before the selection.
180             VisiblePosition oneBeforeStart = start.previous();
181             spellingSearchStart = (oneBeforeStart.isNotNull() ? oneBeforeStart : start).toParentAnchoredPosition();
182         } else {
183             spellingSearchStart = selection.visibleEnd().toParentAnchoredPosition();
184         }
185     }
186 
187     Position position = spellingSearchStart;
188     if (!isEditablePosition(position)) {
189         // This shouldn't happen in very often because the Spelling menu items aren't enabled unless the
190         // selection is editable.
191         // This can happen in Mail for a mix of non-editable and editable content (like Stationary),
192         // when spell checking the whole document before sending the message.
193         // In that case the document might not be editable, but there are editable pockets that need to be spell checked.
194 
195         position = firstEditableVisiblePositionAfterPositionInRoot(position, frame().document()->documentElement()).deepEquivalent();
196         if (position.isNull())
197             return;
198 
199         spellingSearchStart = position.parentAnchoredEquivalent();
200         startedWithSelection = false; // won't need to wrap
201     }
202 
203     // topNode defines the whole range we want to operate on
204     ContainerNode* topNode = highestEditableRoot(position);
205     // FIXME: lastOffsetForEditing() is wrong here if editingIgnoresContent(highestEditableRoot()) returns true (e.g. a <table>)
206     spellingSearchEnd = createLegacyEditingPosition(topNode, lastOffsetForEditing(topNode));
207 
208     // If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking
209     // at a word boundary. Going back by one char and then forward by a word does the trick.
210     if (startedWithSelection) {
211     VisiblePosition oneBeforeStart = VisiblePosition(spellingSearchStart, DOWNSTREAM).previous();
212         if (oneBeforeStart.isNotNull())
213             spellingSearchStart = endOfWord(oneBeforeStart).toParentAnchoredPosition();
214         // else we were already at the start of the editable node
215     }
216 
217     if (spellingSearchStart == spellingSearchEnd)
218         return; // nothing to search in
219 
220     // We go to the end of our first range instead of the start of it, just to be sure
221     // we don't get foiled by any word boundary problems at the start. It means we might
222     // do a tiny bit more searching.
223     Node* searchEndNodeAfterWrap = spellingSearchEnd.containerNode();
224     int searchEndOffsetAfterWrap = spellingSearchEnd.offsetInContainerNode();
225 
226     int misspellingOffset = 0;
227     GrammarDetail grammarDetail;
228     int grammarPhraseOffset = 0;
229     Position grammarSearchStart, grammarSearchEnd;
230     String badGrammarPhrase;
231     String misspelledWord;
232 
233     bool isSpelling = true;
234     int foundOffset = 0;
235     String foundItem;
236     RefPtrWillBeRawPtr<Range> firstMisspellingRange = nullptr;
237     if (unifiedTextCheckerEnabled()) {
238         grammarSearchStart = spellingSearchStart;
239         grammarSearchEnd = spellingSearchEnd;
240         foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchStart, spellingSearchEnd).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
241         if (isSpelling) {
242             misspelledWord = foundItem;
243             misspellingOffset = foundOffset;
244         } else {
245             badGrammarPhrase = foundItem;
246             grammarPhraseOffset = foundOffset;
247         }
248     } else {
249         misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSearchStart, spellingSearchEnd).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange);
250         grammarSearchStart = spellingSearchStart;
251         grammarSearchEnd = spellingSearchEnd;
252         if (!misspelledWord.isEmpty()) {
253             // Stop looking at start of next misspelled word
254             CharacterIterator chars(grammarSearchStart, grammarSearchEnd);
255             chars.advance(misspellingOffset);
256             grammarSearchEnd = chars.startPosition();
257         }
258 
259         if (isGrammarCheckingEnabled())
260             badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), grammarSearchStart, grammarSearchEnd).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);
261     }
262 
263     // 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
264     // block rather than at a selection).
265     if (startedWithSelection && !misspelledWord && !badGrammarPhrase) {
266         spellingSearchStart = createLegacyEditingPosition(topNode, 0);
267         // going until the end of the very first chunk we tested is far enough
268         spellingSearchEnd = createLegacyEditingPosition(searchEndNodeAfterWrap, searchEndOffsetAfterWrap);
269 
270         if (unifiedTextCheckerEnabled()) {
271             grammarSearchStart = spellingSearchStart;
272             grammarSearchEnd = spellingSearchEnd;
273             foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchStart, spellingSearchEnd).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
274             if (isSpelling) {
275                 misspelledWord = foundItem;
276                 misspellingOffset = foundOffset;
277             } else {
278                 badGrammarPhrase = foundItem;
279                 grammarPhraseOffset = foundOffset;
280             }
281         } else {
282             misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSearchStart, spellingSearchEnd).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange);
283             grammarSearchStart = spellingSearchStart;
284             grammarSearchEnd = spellingSearchEnd;
285             if (!misspelledWord.isEmpty()) {
286                 // Stop looking at start of next misspelled word
287                 CharacterIterator chars(grammarSearchStart, grammarSearchEnd);
288                 chars.advance(misspellingOffset);
289                 grammarSearchEnd = chars.startPosition();
290             }
291 
292             if (isGrammarCheckingEnabled())
293                 badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), grammarSearchStart, grammarSearchEnd).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);
294         }
295     }
296 
297     if (!badGrammarPhrase.isEmpty()) {
298         // We found bad grammar. Since we only searched for bad grammar up to the first misspelled word, the bad grammar
299         // takes precedence and we ignore any potential misspelled word. Select the grammar detail, update the spelling
300         // panel, and store a marker so we draw the green squiggle later.
301 
302         ASSERT(badGrammarPhrase.length() > 0);
303         ASSERT(grammarDetail.location != -1 && grammarDetail.length > 0);
304 
305         // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph
306         Position badGrammarStart = grammarSearchStart;
307         Position badGrammarEnd = grammarSearchEnd;
308         TextIterator::subrange(badGrammarStart, badGrammarEnd, grammarPhraseOffset + grammarDetail.location, grammarDetail.length);
309         frame().selection().setSelection(VisibleSelection(badGrammarStart, badGrammarEnd));
310         frame().selection().revealSelection();
311 
312         frame().document()->markers().addMarker(badGrammarStart, badGrammarEnd, DocumentMarker::Grammar, grammarDetail.userDescription);
313     } else if (!misspelledWord.isEmpty()) {
314         // We found a misspelling, but not any earlier bad grammar. Select the misspelling, update the spelling panel, and store
315         // a marker so we draw the red squiggle later.
316 
317         Position misspellingStart = spellingSearchStart;
318         Position misspellingEnd = spellingSearchEnd;
319         TextIterator::subrange(misspellingStart, misspellingEnd, misspellingOffset, misspelledWord.length());
320         frame().selection().setSelection(VisibleSelection(misspellingStart, misspellingEnd, DOWNSTREAM));
321         frame().selection().revealSelection();
322 
323         spellCheckerClient().updateSpellingUIWithMisspelledWord(misspelledWord);
324         frame().document()->markers().addMarker(misspellingStart, misspellingEnd, DocumentMarker::Spelling);
325     }
326 }
327 
showSpellingGuessPanel()328 void SpellChecker::showSpellingGuessPanel()
329 {
330     if (spellCheckerClient().spellingUIIsShowing()) {
331         spellCheckerClient().showSpellingUI(false);
332         return;
333     }
334 
335     advanceToNextMisspelling(true);
336     spellCheckerClient().showSpellingUI(true);
337 }
338 
clearMisspellingsAndBadGrammar(const VisibleSelection & movingSelection)339 void SpellChecker::clearMisspellingsAndBadGrammar(const VisibleSelection &movingSelection)
340 {
341     RefPtrWillBeRawPtr<Range> selectedRange = movingSelection.toNormalizedRange();
342     if (selectedRange)
343         frame().document()->markers().removeMarkers(selectedRange.get(), DocumentMarker::MisspellingMarkers());
344 }
345 
markMisspellingsAndBadGrammar(const VisibleSelection & movingSelection)346 void SpellChecker::markMisspellingsAndBadGrammar(const VisibleSelection &movingSelection)
347 {
348     markMisspellingsAndBadGrammar(movingSelection, isContinuousSpellCheckingEnabled() && isGrammarCheckingEnabled(), movingSelection);
349 }
350 
markMisspellingsAfterLineBreak(const VisibleSelection & wordSelection)351 void SpellChecker::markMisspellingsAfterLineBreak(const VisibleSelection& wordSelection)
352 {
353     if (unifiedTextCheckerEnabled()) {
354         TextCheckingTypeMask textCheckingOptions = 0;
355 
356         if (isContinuousSpellCheckingEnabled())
357             textCheckingOptions |= TextCheckingTypeSpelling;
358 
359         if (isGrammarCheckingEnabled())
360             textCheckingOptions |= TextCheckingTypeGrammar;
361 
362         VisibleSelection wholeParagraph(
363             startOfParagraph(wordSelection.visibleStart()),
364             endOfParagraph(wordSelection.visibleEnd()));
365 
366         markAllMisspellingsAndBadGrammarInRanges(
367             textCheckingOptions, wordSelection.toNormalizedRange().get(),
368             wholeParagraph.toNormalizedRange().get());
369     } else {
370         RefPtrWillBeRawPtr<Range> misspellingRange = nullptr;
371         markMisspellings(wordSelection, misspellingRange);
372     }
373 }
374 
markMisspellingsAfterTypingToWord(const VisiblePosition & wordStart,const VisibleSelection & selectionAfterTyping)375 void SpellChecker::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping)
376 {
377     if (unifiedTextCheckerEnabled()) {
378         TextCheckingTypeMask textCheckingOptions = 0;
379 
380         if (isContinuousSpellCheckingEnabled())
381             textCheckingOptions |= TextCheckingTypeSpelling;
382 
383         if (!(textCheckingOptions & TextCheckingTypeSpelling))
384             return;
385 
386         if (isGrammarCheckingEnabled())
387             textCheckingOptions |= TextCheckingTypeGrammar;
388 
389         VisibleSelection adjacentWords = VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary));
390         if (textCheckingOptions & TextCheckingTypeGrammar) {
391             VisibleSelection selectedSentence = VisibleSelection(startOfSentence(wordStart), endOfSentence(wordStart));
392             markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWords.toNormalizedRange().get(), selectedSentence.toNormalizedRange().get());
393         } else {
394             markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWords.toNormalizedRange().get(), adjacentWords.toNormalizedRange().get());
395         }
396         return;
397     }
398 
399     if (!isContinuousSpellCheckingEnabled())
400         return;
401 
402     // Check spelling of one word
403     RefPtrWillBeRawPtr<Range> misspellingRange = nullptr;
404     markMisspellings(VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary)), misspellingRange);
405 
406     // Autocorrect the misspelled word.
407     if (!misspellingRange)
408         return;
409 
410     // Get the misspelled word.
411     const String misspelledWord = plainText(misspellingRange.get());
412     String autocorrectedString = textChecker().getAutoCorrectSuggestionForMisspelledWord(misspelledWord);
413 
414     // If autocorrected word is non empty, replace the misspelled word by this word.
415     if (!autocorrectedString.isEmpty()) {
416         VisibleSelection newSelection(misspellingRange.get(), DOWNSTREAM);
417         if (newSelection != frame().selection().selection()) {
418             frame().selection().setSelection(newSelection);
419         }
420 
421         frame().editor().replaceSelectionWithText(autocorrectedString, false, false);
422 
423         // Reset the charet one character further.
424         frame().selection().moveTo(frame().selection().selection().visibleEnd());
425         frame().selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity);
426     }
427 
428     if (!isGrammarCheckingEnabled())
429         return;
430 
431     // Check grammar of entire sentence
432     markBadGrammar(VisibleSelection(startOfSentence(wordStart), endOfSentence(wordStart)));
433 }
434 
markMisspellingsOrBadGrammar(const VisibleSelection & selection,bool checkSpelling,RefPtrWillBeRawPtr<Range> & firstMisspellingRange)435 void SpellChecker::markMisspellingsOrBadGrammar(const VisibleSelection& selection, bool checkSpelling, RefPtrWillBeRawPtr<Range>& firstMisspellingRange)
436 {
437     // This function is called with a selection already expanded to word boundaries.
438     // Might be nice to assert that here.
439 
440     // This function is used only for as-you-type checking, so if that's off we do nothing. Note that
441     // grammar checking can only be on if spell checking is also on.
442     if (!isContinuousSpellCheckingEnabled())
443         return;
444 
445     Position start, end;
446     if (!selection.toNormalizedPositions(start, end))
447         return;
448 
449     // If we're not in an editable node, bail.
450     Node* editableNode = start.containerNode();
451     if (!editableNode || !editableNode->hasEditableStyle())
452         return;
453 
454     if (!isSpellCheckingEnabledFor(editableNode))
455         return;
456 
457     TextCheckingHelper checker(spellCheckerClient(), start, end);
458     if (checkSpelling)
459         checker.markAllMisspellings(firstMisspellingRange);
460     else if (isGrammarCheckingEnabled())
461         checker.markAllBadGrammar();
462 }
463 
isSpellCheckingEnabledFor(Node * node) const464 bool SpellChecker::isSpellCheckingEnabledFor(Node* node) const
465 {
466     if (!node)
467         return false;
468     const Element* focusedElement = node->isElementNode() ? toElement(node) : node->parentElement();
469     if (!focusedElement)
470         return false;
471     return focusedElement->isSpellCheckingEnabled();
472 }
473 
isSpellCheckingEnabledInFocusedNode() const474 bool SpellChecker::isSpellCheckingEnabledInFocusedNode() const
475 {
476     return isSpellCheckingEnabledFor(frame().selection().start().deprecatedNode());
477 }
478 
markMisspellings(const VisibleSelection & selection,RefPtrWillBeRawPtr<Range> & firstMisspellingRange)479 void SpellChecker::markMisspellings(const VisibleSelection& selection, RefPtrWillBeRawPtr<Range>& firstMisspellingRange)
480 {
481     markMisspellingsOrBadGrammar(selection, true, firstMisspellingRange);
482 }
483 
markBadGrammar(const VisibleSelection & selection)484 void SpellChecker::markBadGrammar(const VisibleSelection& selection)
485 {
486     RefPtrWillBeRawPtr<Range> firstMisspellingRange = nullptr;
487     markMisspellingsOrBadGrammar(selection, false, firstMisspellingRange);
488 }
489 
markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask textCheckingOptions,Range * spellingRange,Range * grammarRange)490 void SpellChecker::markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask textCheckingOptions, Range* spellingRange, Range* grammarRange)
491 {
492     ASSERT(unifiedTextCheckerEnabled());
493 
494     bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar;
495 
496     // This function is called with selections already expanded to word boundaries.
497     if (!spellingRange || (shouldMarkGrammar && !grammarRange))
498         return;
499 
500     // If we're not in an editable node, bail.
501     Node* editableNode = spellingRange->startContainer();
502     if (!editableNode || !editableNode->hasEditableStyle())
503         return;
504 
505     if (!isSpellCheckingEnabledFor(editableNode))
506         return;
507 
508     Range* rangeToCheck = shouldMarkGrammar ? grammarRange : spellingRange;
509     TextCheckingParagraph fullParagraphToCheck(rangeToCheck);
510 
511     bool asynchronous = frame().settings() && frame().settings()->asynchronousSpellCheckingEnabled();
512     chunkAndMarkAllMisspellingsAndBadGrammar(textCheckingOptions, fullParagraphToCheck, asynchronous);
513 }
514 
chunkAndMarkAllMisspellingsAndBadGrammar(Node * node)515 void SpellChecker::chunkAndMarkAllMisspellingsAndBadGrammar(Node* node)
516 {
517     if (!node)
518         return;
519     RefPtrWillBeRawPtr<Range> rangeToCheck = Range::create(*frame().document(), firstPositionInNode(node), lastPositionInNode(node));
520     TextCheckingParagraph textToCheck(rangeToCheck, rangeToCheck);
521     bool asynchronous = true;
522     chunkAndMarkAllMisspellingsAndBadGrammar(resolveTextCheckingTypeMask(TextCheckingTypeSpelling | TextCheckingTypeGrammar), textToCheck, asynchronous);
523 }
524 
chunkAndMarkAllMisspellingsAndBadGrammar(TextCheckingTypeMask textCheckingOptions,const TextCheckingParagraph & fullParagraphToCheck,bool asynchronous)525 void SpellChecker::chunkAndMarkAllMisspellingsAndBadGrammar(TextCheckingTypeMask textCheckingOptions, const TextCheckingParagraph& fullParagraphToCheck, bool asynchronous)
526 {
527     if (fullParagraphToCheck.isRangeEmpty() || fullParagraphToCheck.isEmpty())
528         return;
529 
530     // Since the text may be quite big chunk it up and adjust to the sentence boundary.
531     const int kChunkSize = 16 * 1024;
532     int start = fullParagraphToCheck.checkingStart();
533     int end = fullParagraphToCheck.checkingEnd();
534     start = std::min(start, end);
535     end = std::max(start, end);
536     const int kNumChunksToCheck = asynchronous ? (end - start + kChunkSize - 1) / (kChunkSize) : 1;
537     int currentChunkStart = start;
538     RefPtrWillBeRawPtr<Range> checkRange = fullParagraphToCheck.checkingRange();
539     if (kNumChunksToCheck == 1 && asynchronous) {
540         markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, checkRange.get(), checkRange.get(), asynchronous, 0);
541         return;
542     }
543 
544     for (int iter = 0; iter < kNumChunksToCheck; ++iter) {
545         checkRange = fullParagraphToCheck.subrange(currentChunkStart, kChunkSize);
546         setStart(checkRange.get(), startOfSentence(VisiblePosition(checkRange->startPosition())));
547         setEnd(checkRange.get(), endOfSentence(VisiblePosition(checkRange->endPosition())));
548 
549         int checkingLength = 0;
550         markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, checkRange.get(), checkRange.get(), asynchronous, iter, &checkingLength);
551         currentChunkStart += checkingLength;
552     }
553 }
554 
markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask textCheckingOptions,Range * checkRange,Range * paragraphRange,bool asynchronous,int requestNumber,int * checkingLength)555 void SpellChecker::markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask textCheckingOptions, Range* checkRange, Range* paragraphRange, bool asynchronous, int requestNumber, int* checkingLength)
556 {
557     TextCheckingParagraph sentenceToCheck(checkRange, paragraphRange);
558     if (checkingLength)
559         *checkingLength = sentenceToCheck.checkingLength();
560 
561     RefPtrWillBeRawPtr<SpellCheckRequest> request = SpellCheckRequest::create(resolveTextCheckingTypeMask(textCheckingOptions), TextCheckingProcessBatch, checkRange, paragraphRange, requestNumber);
562 
563     if (asynchronous) {
564         m_spellCheckRequester->requestCheckingFor(request);
565     } else {
566         Vector<TextCheckingResult> results;
567         checkTextOfParagraph(textChecker(), sentenceToCheck.text(), resolveTextCheckingTypeMask(textCheckingOptions), results);
568         markAndReplaceFor(request, results);
569     }
570 }
571 
markAndReplaceFor(PassRefPtrWillBeRawPtr<SpellCheckRequest> request,const Vector<TextCheckingResult> & results)572 void SpellChecker::markAndReplaceFor(PassRefPtrWillBeRawPtr<SpellCheckRequest> request, const Vector<TextCheckingResult>& results)
573 {
574     ASSERT(request);
575 
576     TextCheckingTypeMask textCheckingOptions = request->data().mask();
577     TextCheckingParagraph paragraph(request->checkingRange(), request->paragraphRange());
578 
579     bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling;
580     bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar;
581 
582     // Expand the range to encompass entire paragraphs, since text checking needs that much context.
583     int selectionOffset = 0;
584     int ambiguousBoundaryOffset = -1;
585     bool selectionChanged = false;
586     bool restoreSelectionAfterChange = false;
587     bool adjustSelectionForParagraphBoundaries = false;
588 
589     if (shouldMarkSpelling) {
590         if (frame().selection().isCaret()) {
591             // Attempt to save the caret position so we can restore it later if needed
592             Position caretPosition = frame().selection().end();
593             selectionOffset = paragraph.offsetTo(caretPosition, ASSERT_NO_EXCEPTION);
594             restoreSelectionAfterChange = true;
595             if (selectionOffset > 0 && (static_cast<unsigned>(selectionOffset) > paragraph.text().length() || paragraph.textCharAt(selectionOffset - 1) == newlineCharacter))
596                 adjustSelectionForParagraphBoundaries = true;
597             if (selectionOffset > 0 && static_cast<unsigned>(selectionOffset) <= paragraph.text().length() && isAmbiguousBoundaryCharacter(paragraph.textCharAt(selectionOffset - 1)))
598                 ambiguousBoundaryOffset = selectionOffset - 1;
599         }
600     }
601 
602     for (unsigned i = 0; i < results.size(); i++) {
603         int spellingRangeEndOffset = paragraph.checkingEnd();
604         const TextCheckingResult* result = &results[i];
605         int resultLocation = result->location + paragraph.checkingStart();
606         int resultLength = result->length;
607         bool resultEndsAtAmbiguousBoundary = ambiguousBoundaryOffset >= 0 && resultLocation + resultLength == ambiguousBoundaryOffset;
608 
609         // Only mark misspelling if:
610         // 1. Current text checking isn't done for autocorrection, in which case shouldMarkSpelling is false.
611         // 2. Result falls within spellingRange.
612         // 3. The word in question doesn't end at an ambiguous boundary. For instance, we would not mark
613         //    "wouldn'" as misspelled right after apostrophe is typed.
614         if (shouldMarkSpelling && result->decoration == TextDecorationTypeSpelling && resultLocation >= paragraph.checkingStart() && resultLocation + resultLength <= spellingRangeEndOffset && !resultEndsAtAmbiguousBoundary) {
615             ASSERT(resultLength > 0 && resultLocation >= 0);
616             RefPtrWillBeRawPtr<Range> misspellingRange = paragraph.subrange(resultLocation, resultLength);
617             misspellingRange->startContainer()->document().markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling, result->replacement, result->hash);
618         } else if (shouldMarkGrammar && result->decoration == TextDecorationTypeGrammar && paragraph.checkingRangeCovers(resultLocation, resultLength)) {
619             ASSERT(resultLength > 0 && resultLocation >= 0);
620             for (unsigned j = 0; j < result->details.size(); j++) {
621                 const GrammarDetail* detail = &result->details[j];
622                 ASSERT(detail->length > 0 && detail->location >= 0);
623                 if (paragraph.checkingRangeCovers(resultLocation + detail->location, detail->length)) {
624                     RefPtrWillBeRawPtr<Range> badGrammarRange = paragraph.subrange(resultLocation + detail->location, detail->length);
625                     badGrammarRange->startContainer()->document().markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription, result->hash);
626                 }
627             }
628         } else if (result->decoration == TextDecorationTypeInvisibleSpellcheck && resultLocation >= paragraph.checkingStart() && resultLocation + resultLength <= spellingRangeEndOffset) {
629             ASSERT(resultLength > 0 && resultLocation >= 0);
630             RefPtrWillBeRawPtr<Range> invisibleSpellcheckRange = paragraph.subrange(resultLocation, resultLength);
631             invisibleSpellcheckRange->startContainer()->document().markers().addMarker(invisibleSpellcheckRange.get(), DocumentMarker::InvisibleSpellcheck, result->replacement, result->hash);
632         }
633     }
634 
635     if (selectionChanged) {
636         TextCheckingParagraph extendedParagraph(paragraph);
637         // Restore the caret position if we have made any replacements
638         extendedParagraph.expandRangeToNextEnd();
639         if (restoreSelectionAfterChange && selectionOffset >= 0 && selectionOffset <= extendedParagraph.rangeLength()) {
640             RefPtrWillBeRawPtr<Range> selectionRange = extendedParagraph.subrange(0, selectionOffset);
641             frame().selection().moveTo(selectionRange->endPosition(), DOWNSTREAM);
642             if (adjustSelectionForParagraphBoundaries)
643                 frame().selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity);
644         } else {
645             // If this fails for any reason, the fallback is to go one position beyond the last replacement
646             frame().selection().moveTo(frame().selection().selection().visibleEnd());
647             frame().selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity);
648         }
649     }
650 }
651 
markMisspellingsAndBadGrammar(const VisibleSelection & spellingSelection,bool markGrammar,const VisibleSelection & grammarSelection)652 void SpellChecker::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection)
653 {
654     if (unifiedTextCheckerEnabled()) {
655         if (!isContinuousSpellCheckingEnabled())
656             return;
657 
658         // markMisspellingsAndBadGrammar() is triggered by selection change, in which case we check spelling and grammar, but don't autocorrect misspellings.
659         TextCheckingTypeMask textCheckingOptions = TextCheckingTypeSpelling;
660         if (markGrammar && isGrammarCheckingEnabled())
661             textCheckingOptions |= TextCheckingTypeGrammar;
662         markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, spellingSelection.toNormalizedRange().get(), grammarSelection.toNormalizedRange().get());
663         return;
664     }
665 
666     RefPtrWillBeRawPtr<Range> firstMisspellingRange = nullptr;
667     markMisspellings(spellingSelection, firstMisspellingRange);
668     if (markGrammar)
669         markBadGrammar(grammarSelection);
670 }
671 
updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSelectionAtWordBoundary)672 void SpellChecker::updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSelectionAtWordBoundary)
673 {
674     if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTypeSpelling))
675         return;
676 
677     // We want to remove the markers from a word if an editing command will change the word. This can happen in one of
678     // several scenarios:
679     // 1. Insert in the middle of a word.
680     // 2. Appending non whitespace at the beginning of word.
681     // 3. Appending non whitespace at the end of word.
682     // Note that, appending only whitespaces at the beginning or end of word won't change the word, so we don't need to
683     // remove the markers on that word.
684     // Of course, if current selection is a range, we potentially will edit two words that fall on the boundaries of
685     // selection, and remove words between the selection boundaries.
686     //
687     VisiblePosition startOfSelection = frame().selection().selection().visibleStart();
688     VisiblePosition endOfSelection = frame().selection().selection().visibleEnd();
689     if (startOfSelection.isNull())
690         return;
691     // First word is the word that ends after or on the start of selection.
692     VisiblePosition startOfFirstWord = startOfWord(startOfSelection, LeftWordIfOnBoundary);
693     VisiblePosition endOfFirstWord = endOfWord(startOfSelection, LeftWordIfOnBoundary);
694     // Last word is the word that begins before or on the end of selection
695     VisiblePosition startOfLastWord = startOfWord(endOfSelection, RightWordIfOnBoundary);
696     VisiblePosition endOfLastWord = endOfWord(endOfSelection, RightWordIfOnBoundary);
697 
698     if (startOfFirstWord.isNull()) {
699         startOfFirstWord = startOfWord(startOfSelection, RightWordIfOnBoundary);
700         endOfFirstWord = endOfWord(startOfSelection, RightWordIfOnBoundary);
701     }
702 
703     if (endOfLastWord.isNull()) {
704         startOfLastWord = startOfWord(endOfSelection, LeftWordIfOnBoundary);
705         endOfLastWord = endOfWord(endOfSelection, LeftWordIfOnBoundary);
706     }
707 
708     // If doNotRemoveIfSelectionAtWordBoundary is true, and first word ends at the start of selection,
709     // we choose next word as the first word.
710     if (doNotRemoveIfSelectionAtWordBoundary && endOfFirstWord == startOfSelection) {
711         startOfFirstWord = nextWordPosition(startOfFirstWord);
712         endOfFirstWord = endOfWord(startOfFirstWord, RightWordIfOnBoundary);
713         if (startOfFirstWord == endOfSelection)
714             return;
715     }
716 
717     // If doNotRemoveIfSelectionAtWordBoundary is true, and last word begins at the end of selection,
718     // we choose previous word as the last word.
719     if (doNotRemoveIfSelectionAtWordBoundary && startOfLastWord == endOfSelection) {
720         startOfLastWord = previousWordPosition(startOfLastWord);
721         endOfLastWord = endOfWord(startOfLastWord, RightWordIfOnBoundary);
722         if (endOfLastWord == startOfSelection)
723             return;
724     }
725 
726     if (startOfFirstWord.isNull() || endOfFirstWord.isNull() || startOfLastWord.isNull() || endOfLastWord.isNull())
727         return;
728 
729     // Now we remove markers on everything between startOfFirstWord and endOfLastWord.
730     // However, if an autocorrection change a single word to multiple words, we want to remove correction mark from all the
731     // resulted words even we only edit one of them. For example, assuming autocorrection changes "avantgarde" to "avant
732     // garde", we will have CorrectionIndicator marker on both words and on the whitespace between them. If we then edit garde,
733     // we would like to remove the marker from word "avant" and whitespace as well. So we need to get the continous range of
734     // of marker that contains the word in question, and remove marker on that whole range.
735     Document* document = frame().document();
736     ASSERT(document);
737     RefPtrWillBeRawPtr<Range> wordRange = Range::create(*document, startOfFirstWord.deepEquivalent(), endOfLastWord.deepEquivalent());
738     document->markers().removeMarkers(wordRange.get(), DocumentMarker::MisspellingMarkers(), DocumentMarkerController::RemovePartiallyOverlappingMarker);
739 }
740 
didEndEditingOnTextField(Element * e)741 void SpellChecker::didEndEditingOnTextField(Element* e)
742 {
743     // Remove markers when deactivating a selection in an <input type="text"/>.
744     // Prevent new ones from appearing too.
745     m_spellCheckRequester->cancelCheck();
746     HTMLTextFormControlElement* textFormControlElement = toHTMLTextFormControlElement(e);
747     HTMLElement* innerEditor = textFormControlElement->innerEditorElement();
748     DocumentMarker::MarkerTypes markerTypes(DocumentMarker::Spelling);
749     if (isGrammarCheckingEnabled() || unifiedTextCheckerEnabled())
750         markerTypes.add(DocumentMarker::Grammar);
751     for (Node* node = innerEditor; node; node = NodeTraversal::next(*node, innerEditor)) {
752         frame().document()->markers().removeMarkers(node, markerTypes);
753     }
754 }
755 
replaceMisspelledRange(const String & text)756 void SpellChecker::replaceMisspelledRange(const String& text)
757 {
758     RefPtrWillBeRawPtr<Range> caretRange = frame().selection().toNormalizedRange();
759     if (!caretRange)
760         return;
761     DocumentMarkerVector markers = frame().document()->markers().markersInRange(caretRange.get(), DocumentMarker::MisspellingMarkers());
762     if (markers.size() < 1 || markers[0]->startOffset() >= markers[0]->endOffset())
763         return;
764     RefPtrWillBeRawPtr<Range> markerRange = Range::create(caretRange->ownerDocument(), caretRange->startContainer(), markers[0]->startOffset(), caretRange->endContainer(), markers[0]->endOffset());
765     if (!markerRange)
766         return;
767     frame().selection().setSelection(VisibleSelection(markerRange.get()), CharacterGranularity);
768     frame().editor().replaceSelectionWithText(text, false, false);
769 }
770 
respondToChangedSelection(const VisibleSelection & oldSelection,FrameSelection::SetSelectionOptions options)771 void SpellChecker::respondToChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions options)
772 {
773     bool closeTyping = options & FrameSelection::CloseTyping;
774     bool isContinuousSpellCheckingEnabled = this->isContinuousSpellCheckingEnabled();
775     bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled && isGrammarCheckingEnabled();
776     if (isContinuousSpellCheckingEnabled) {
777         VisibleSelection newAdjacentWords;
778         VisibleSelection newSelectedSentence;
779         bool caretBrowsing = frame().settings() && frame().settings()->caretBrowsingEnabled();
780         const VisibleSelection newSelection = frame().selection().selection();
781         if (isSelectionInTextFormControl(newSelection)) {
782             Position newStart = newSelection.start();
783             newAdjacentWords.setWithoutValidation(HTMLTextFormControlElement::startOfWord(newStart), HTMLTextFormControlElement::endOfWord(newStart));
784             if (isContinuousGrammarCheckingEnabled)
785                 newSelectedSentence.setWithoutValidation(HTMLTextFormControlElement::startOfSentence(newStart), HTMLTextFormControlElement::endOfSentence(newStart));
786         } else if (newSelection.isContentEditable() || caretBrowsing) {
787             VisiblePosition newStart(newSelection.visibleStart());
788             newAdjacentWords = VisibleSelection(startOfWord(newStart, LeftWordIfOnBoundary), endOfWord(newStart, RightWordIfOnBoundary));
789             if (isContinuousGrammarCheckingEnabled)
790                 newSelectedSentence = VisibleSelection(startOfSentence(newStart), endOfSentence(newStart));
791         }
792 
793         // Don't check spelling and grammar if the change of selection is triggered by spelling correction itself.
794         bool shouldCheckSpellingAndGrammar = !(options & FrameSelection::SpellCorrectionTriggered);
795 
796         // When typing we check spelling elsewhere, so don't redo it here.
797         // If this is a change in selection resulting from a delete operation,
798         // oldSelection may no longer be in the document.
799         // FIXME(http://crbug.com/382809): if oldSelection is on a textarea
800         // element, we cause synchronous layout.
801         if (shouldCheckSpellingAndGrammar
802             && closeTyping
803             && !isSelectionInTextField(oldSelection)
804             && (isSelectionInTextArea(oldSelection) || oldSelection.isContentEditable())
805             && oldSelection.start().inDocument()) {
806             spellCheckOldSelection(oldSelection, newAdjacentWords);
807         }
808 
809         // FIXME(http://crbug.com/382809):
810         // shouldEraseMarkersAfterChangeSelection is true, we cause synchronous
811         // layout.
812         if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTypeSpelling)) {
813             Position start, end;
814             if (newAdjacentWords.toNormalizedPositions(start, end))
815                 frame().document()->markers().removeMarkers(start, end, DocumentMarker::Spelling);
816         }
817         if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTypeGrammar)) {
818             Position start, end;
819             if (newSelectedSentence.toNormalizedPositions(start, end))
820                 frame().document()->markers().removeMarkers(start, end, DocumentMarker::Grammar);
821         }
822     }
823 
824     // When continuous spell checking is off, existing markers disappear after the selection changes.
825     if (!isContinuousSpellCheckingEnabled)
826         frame().document()->markers().removeMarkers(DocumentMarker::Spelling);
827     if (!isContinuousGrammarCheckingEnabled)
828         frame().document()->markers().removeMarkers(DocumentMarker::Grammar);
829 }
830 
removeSpellingMarkers()831 void SpellChecker::removeSpellingMarkers()
832 {
833     frame().document()->markers().removeMarkers(DocumentMarker::MisspellingMarkers());
834 }
835 
removeSpellingMarkersUnderWords(const Vector<String> & words)836 void SpellChecker::removeSpellingMarkersUnderWords(const Vector<String>& words)
837 {
838     MarkerRemoverPredicate removerPredicate(words);
839 
840     DocumentMarkerController& markerController = frame().document()->markers();
841     markerController.removeMarkers(removerPredicate);
842     markerController.repaintMarkers();
843 }
844 
spellCheckAfterBlur()845 void SpellChecker::spellCheckAfterBlur()
846 {
847     if (!frame().selection().selection().isContentEditable())
848         return;
849 
850     if (isSelectionInTextField(frame().selection().selection())) {
851         // textFieldDidEndEditing() and textFieldDidBeginEditing() handle this.
852         return;
853     }
854 
855     VisibleSelection empty;
856     spellCheckOldSelection(frame().selection().selection(), empty);
857 }
858 
spellCheckOldSelection(const VisibleSelection & oldSelection,const VisibleSelection & newAdjacentWords)859 void SpellChecker::spellCheckOldSelection(const VisibleSelection& oldSelection, const VisibleSelection& newAdjacentWords)
860 {
861     VisiblePosition oldStart(oldSelection.visibleStart());
862     VisibleSelection oldAdjacentWords = VisibleSelection(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary));
863     if (oldAdjacentWords  != newAdjacentWords) {
864         if (isContinuousSpellCheckingEnabled() && isGrammarCheckingEnabled()) {
865             VisibleSelection selectedSentence = VisibleSelection(startOfSentence(oldStart), endOfSentence(oldStart));
866             markMisspellingsAndBadGrammar(oldAdjacentWords, true, selectedSentence);
867         } else {
868             markMisspellingsAndBadGrammar(oldAdjacentWords, false, oldAdjacentWords);
869         }
870     }
871 }
872 
findFirstMarkable(Node * node)873 static Node* findFirstMarkable(Node* node)
874 {
875     while (node) {
876         if (!node->renderer())
877             return 0;
878         if (node->renderer()->isText())
879             return node;
880         if (node->renderer()->isTextControl())
881             node = toRenderTextControl(node->renderer())->textFormControlElement()->visiblePositionForIndex(1).deepEquivalent().deprecatedNode();
882         else if (node->hasChildren())
883             node = node->firstChild();
884         else
885             node = node->nextSibling();
886     }
887 
888     return 0;
889 }
890 
selectionStartHasMarkerFor(DocumentMarker::MarkerType markerType,int from,int length) const891 bool SpellChecker::selectionStartHasMarkerFor(DocumentMarker::MarkerType markerType, int from, int length) const
892 {
893     Node* node = findFirstMarkable(frame().selection().start().deprecatedNode());
894     if (!node)
895         return false;
896 
897     unsigned startOffset = static_cast<unsigned>(from);
898     unsigned endOffset = static_cast<unsigned>(from + length);
899     DocumentMarkerVector markers = frame().document()->markers().markersFor(node);
900     for (size_t i = 0; i < markers.size(); ++i) {
901         DocumentMarker* marker = markers[i];
902         if (marker->startOffset() <= startOffset && endOffset <= marker->endOffset() && marker->type() == markerType)
903             return true;
904     }
905 
906     return false;
907 }
908 
selectionStartHasSpellingMarkerFor(int from,int length) const909 bool SpellChecker::selectionStartHasSpellingMarkerFor(int from, int length) const
910 {
911     return selectionStartHasMarkerFor(DocumentMarker::Spelling, from, length);
912 }
913 
resolveTextCheckingTypeMask(TextCheckingTypeMask textCheckingOptions)914 TextCheckingTypeMask SpellChecker::resolveTextCheckingTypeMask(TextCheckingTypeMask textCheckingOptions)
915 {
916     bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling;
917     bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar;
918 
919     TextCheckingTypeMask checkingTypes = 0;
920     if (shouldMarkSpelling)
921         checkingTypes |= TextCheckingTypeSpelling;
922     if (shouldMarkGrammar)
923         checkingTypes |= TextCheckingTypeGrammar;
924 
925     return checkingTypes;
926 }
927 
unifiedTextCheckerEnabled() const928 bool SpellChecker::unifiedTextCheckerEnabled() const
929 {
930     return blink::unifiedTextCheckerEnabled(m_frame);
931 }
932 
cancelCheck()933 void SpellChecker::cancelCheck()
934 {
935     m_spellCheckRequester->cancelCheck();
936 }
937 
requestTextChecking(const Element & element)938 void SpellChecker::requestTextChecking(const Element& element)
939 {
940     RefPtrWillBeRawPtr<Range> rangeToCheck = rangeOfContents(const_cast<Element*>(&element));
941     m_spellCheckRequester->requestCheckingFor(SpellCheckRequest::create(TextCheckingTypeSpelling | TextCheckingTypeGrammar, TextCheckingProcessBatch, rangeToCheck, rangeToCheck));
942 }
943 
trace(Visitor * visitor)944 void SpellChecker::trace(Visitor* visitor)
945 {
946     visitor->trace(m_frame);
947     visitor->trace(m_spellCheckRequester);
948 }
949 
950 } // namespace blink
951