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