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
49namespace blink {
50
51using namespace HTMLNames;
52
53namespace {
54
55bool isSelectionInTextField(const VisibleSelection& selection)
56{
57    HTMLTextFormControlElement* textControl = enclosingTextFormControl(selection.start());
58    return isHTMLInputElement(textControl) && toHTMLInputElement(textControl)->isTextField();
59}
60
61bool isSelectionInTextArea(const VisibleSelection& selection)
62{
63    HTMLTextFormControlElement* textControl = enclosingTextFormControl(selection.start());
64    return isHTMLTextAreaElement(textControl);
65}
66
67bool isSelectionInTextFormControl(const VisibleSelection& selection)
68{
69    return !!enclosingTextFormControl(selection.start());
70}
71
72} // namespace
73
74PassOwnPtrWillBeRawPtr<SpellChecker> SpellChecker::create(LocalFrame& frame)
75{
76    return adoptPtrWillBeNoop(new SpellChecker(frame));
77}
78
79static SpellCheckerClient& emptySpellCheckerClient()
80{
81    DEFINE_STATIC_LOCAL(EmptySpellCheckerClient, client, ());
82    return client;
83}
84
85SpellCheckerClient& SpellChecker::spellCheckerClient() const
86{
87    if (Page* page = frame().page())
88        return page->spellCheckerClient();
89    return emptySpellCheckerClient();
90}
91
92TextCheckerClient& SpellChecker::textChecker() const
93{
94    return spellCheckerClient().textChecker();
95}
96
97SpellChecker::SpellChecker(LocalFrame& frame)
98    : m_frame(&frame)
99    , m_spellCheckRequester(SpellCheckRequester::create(frame))
100{
101}
102
103SpellChecker::~SpellChecker()
104{
105}
106
107bool SpellChecker::isContinuousSpellCheckingEnabled() const
108{
109    return spellCheckerClient().isContinuousSpellCheckingEnabled();
110}
111
112void 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
126bool SpellChecker::isGrammarCheckingEnabled()
127{
128    return spellCheckerClient().isGrammarCheckingEnabled();
129}
130
131void 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
157void SpellChecker::ignoreSpelling()
158{
159    if (RefPtrWillBeRawPtr<Range> selectedRange = frame().selection().toNormalizedRange())
160        frame().document()->markers().removeMarkers(selectedRange.get(), DocumentMarker::Spelling);
161}
162
163void 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
328void SpellChecker::showSpellingGuessPanel()
329{
330    if (spellCheckerClient().spellingUIIsShowing()) {
331        spellCheckerClient().showSpellingUI(false);
332        return;
333    }
334
335    advanceToNextMisspelling(true);
336    spellCheckerClient().showSpellingUI(true);
337}
338
339void 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
346void SpellChecker::markMisspellingsAndBadGrammar(const VisibleSelection &movingSelection)
347{
348    markMisspellingsAndBadGrammar(movingSelection, isContinuousSpellCheckingEnabled() && isGrammarCheckingEnabled(), movingSelection);
349}
350
351void 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
375void 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
435void 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
464bool 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
474bool SpellChecker::isSpellCheckingEnabledInFocusedNode() const
475{
476    return isSpellCheckingEnabledFor(frame().selection().start().deprecatedNode());
477}
478
479void SpellChecker::markMisspellings(const VisibleSelection& selection, RefPtrWillBeRawPtr<Range>& firstMisspellingRange)
480{
481    markMisspellingsOrBadGrammar(selection, true, firstMisspellingRange);
482}
483
484void SpellChecker::markBadGrammar(const VisibleSelection& selection)
485{
486    RefPtrWillBeRawPtr<Range> firstMisspellingRange = nullptr;
487    markMisspellingsOrBadGrammar(selection, false, firstMisspellingRange);
488}
489
490void 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
515void 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
525void 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
555void 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
572void 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
652void 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
672void 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
741void 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
756void 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
771void 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
831void SpellChecker::removeSpellingMarkers()
832{
833    frame().document()->markers().removeMarkers(DocumentMarker::MisspellingMarkers());
834}
835
836void 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
845void 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
859void 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
873static 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
891bool 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
909bool SpellChecker::selectionStartHasSpellingMarkerFor(int from, int length) const
910{
911    return selectionStartHasMarkerFor(DocumentMarker::Spelling, from, length);
912}
913
914TextCheckingTypeMask 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
928bool SpellChecker::unifiedTextCheckerEnabled() const
929{
930    return blink::unifiedTextCheckerEnabled(m_frame);
931}
932
933void SpellChecker::cancelCheck()
934{
935    m_spellCheckRequester->cancelCheck();
936}
937
938void 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
944void SpellChecker::trace(Visitor* visitor)
945{
946    visitor->trace(m_frame);
947    visitor->trace(m_spellCheckRequester);
948}
949
950} // namespace blink
951