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