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