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