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