| OLD | NEW |
| (Empty) |
| 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/Element.h" | |
| 33 #include "core/dom/NodeTraversal.h" | |
| 34 #include "core/editing/EditingUtilities.h" | |
| 35 #include "core/editing/Editor.h" | |
| 36 #include "core/editing/EphemeralRange.h" | |
| 37 #include "core/editing/SpellCheckRequester.h" | |
| 38 #include "core/editing/TextCheckingHelper.h" | |
| 39 #include "core/editing/VisibleUnits.h" | |
| 40 #include "core/editing/iterators/CharacterIterator.h" | |
| 41 #include "core/editing/markers/DocumentMarkerController.h" | |
| 42 #include "core/frame/LocalFrame.h" | |
| 43 #include "core/frame/Settings.h" | |
| 44 #include "core/html/HTMLInputElement.h" | |
| 45 #include "core/layout/LayoutTextControl.h" | |
| 46 #include "core/loader/EmptyClients.h" | |
| 47 #include "core/page/Page.h" | |
| 48 #include "core/page/SpellCheckerClient.h" | |
| 49 #include "platform/text/TextCheckerClient.h" | |
| 50 | |
| 51 namespace blink { | |
| 52 | |
| 53 using namespace HTMLNames; | |
| 54 | |
| 55 namespace { | |
| 56 | |
| 57 bool isSelectionInTextField(const VisibleSelection& selection) | |
| 58 { | |
| 59 HTMLTextFormControlElement* textControl = enclosingTextFormControl(selection
.start()); | |
| 60 return isHTMLInputElement(textControl) && toHTMLInputElement(textControl)->i
sTextField(); | |
| 61 } | |
| 62 | |
| 63 bool isSelectionInTextArea(const VisibleSelection& selection) | |
| 64 { | |
| 65 HTMLTextFormControlElement* textControl = enclosingTextFormControl(selection
.start()); | |
| 66 return isHTMLTextAreaElement(textControl); | |
| 67 } | |
| 68 | |
| 69 bool isSelectionInTextFormControl(const VisibleSelection& selection) | |
| 70 { | |
| 71 return !!enclosingTextFormControl(selection.start()); | |
| 72 } | |
| 73 | |
| 74 } // namespace | |
| 75 | |
| 76 PassOwnPtrWillBeRawPtr<SpellChecker> SpellChecker::create(LocalFrame& frame) | |
| 77 { | |
| 78 return adoptPtrWillBeNoop(new SpellChecker(frame)); | |
| 79 } | |
| 80 | |
| 81 static SpellCheckerClient& emptySpellCheckerClient() | |
| 82 { | |
| 83 DEFINE_STATIC_LOCAL(EmptySpellCheckerClient, client, ()); | |
| 84 return client; | |
| 85 } | |
| 86 | |
| 87 SpellCheckerClient& SpellChecker::spellCheckerClient() const | |
| 88 { | |
| 89 if (Page* page = frame().page()) | |
| 90 return page->spellCheckerClient(); | |
| 91 return emptySpellCheckerClient(); | |
| 92 } | |
| 93 | |
| 94 TextCheckerClient& SpellChecker::textChecker() const | |
| 95 { | |
| 96 return spellCheckerClient().textChecker(); | |
| 97 } | |
| 98 | |
| 99 SpellChecker::SpellChecker(LocalFrame& frame) | |
| 100 : m_frame(&frame) | |
| 101 , m_spellCheckRequester(SpellCheckRequester::create(frame)) | |
| 102 { | |
| 103 } | |
| 104 | |
| 105 SpellChecker::~SpellChecker() | |
| 106 { | |
| 107 } | |
| 108 | |
| 109 bool SpellChecker::isContinuousSpellCheckingEnabled() const | |
| 110 { | |
| 111 return spellCheckerClient().isContinuousSpellCheckingEnabled(); | |
| 112 } | |
| 113 | |
| 114 void SpellChecker::toggleContinuousSpellChecking() | |
| 115 { | |
| 116 spellCheckerClient().toggleContinuousSpellChecking(); | |
| 117 if (isContinuousSpellCheckingEnabled()) | |
| 118 return; | |
| 119 for (Frame* frame = this->frame().page()->mainFrame(); frame; frame = frame-
>tree().traverseNext()) { | |
| 120 if (!frame->isLocalFrame()) | |
| 121 continue; | |
| 122 for (Node& node : NodeTraversal::startsAt(&toLocalFrame(frame)->document
()->rootNode())) | |
| 123 node.setAlreadySpellChecked(false); | |
| 124 } | |
| 125 } | |
| 126 | |
| 127 bool SpellChecker::isGrammarCheckingEnabled() | |
| 128 { | |
| 129 return spellCheckerClient().isGrammarCheckingEnabled(); | |
| 130 } | |
| 131 | |
| 132 void SpellChecker::didBeginEditing(Element* element) | |
| 133 { | |
| 134 if (isContinuousSpellCheckingEnabled() && unifiedTextCheckerEnabled()) { | |
| 135 bool isTextField = false; | |
| 136 HTMLTextFormControlElement* enclosingHTMLTextFormControlElement = 0; | |
| 137 if (!isHTMLTextFormControlElement(*element)) | |
| 138 enclosingHTMLTextFormControlElement = enclosingTextFormControl(first
PositionInNode(element)); | |
| 139 element = enclosingHTMLTextFormControlElement ? enclosingHTMLTextFormCon
trolElement : element; | |
| 140 Element* parent = element; | |
| 141 if (isHTMLTextFormControlElement(*element)) { | |
| 142 HTMLTextFormControlElement* textControl = toHTMLTextFormControlEleme
nt(element); | |
| 143 parent = textControl; | |
| 144 element = textControl->innerEditorElement(); | |
| 145 if (!element) | |
| 146 return; | |
| 147 isTextField = isHTMLInputElement(*textControl) && toHTMLInputElement
(*textControl).isTextField(); | |
| 148 } | |
| 149 | |
| 150 if (isTextField || !parent->isAlreadySpellChecked()) { | |
| 151 // We always recheck textfields because markers are removed from the
m on blur. | |
| 152 VisibleSelection selection = VisibleSelection::selectionFromContents
OfNode(element); | |
| 153 markMisspellingsAndBadGrammar(selection); | |
| 154 if (!isTextField) | |
| 155 parent->setAlreadySpellChecked(true); | |
| 156 } | |
| 157 } | |
| 158 } | |
| 159 | |
| 160 void SpellChecker::ignoreSpelling() | |
| 161 { | |
| 162 removeMarkers(frame().selection().selection(), DocumentMarker::Spelling); | |
| 163 } | |
| 164 | |
| 165 void SpellChecker::advanceToNextMisspelling(bool startBeforeSelection) | |
| 166 { | |
| 167 // The basic approach is to search in two phases - from the selection end to
the end of the doc, and | |
| 168 // then we wrap and search from the doc start to (approximately) where we st
arted. | |
| 169 | |
| 170 // Start at the end of the selection, search to edge of document. Starting a
t the selection end makes | |
| 171 // repeated "check spelling" commands work. | |
| 172 VisibleSelection selection(frame().selection().selection()); | |
| 173 Position spellingSearchStart, spellingSearchEnd; | |
| 174 Range::selectNodeContents(frame().document(), spellingSearchStart, spellingS
earchEnd); | |
| 175 | |
| 176 bool startedWithSelection = false; | |
| 177 if (selection.start().anchorNode()) { | |
| 178 startedWithSelection = true; | |
| 179 if (startBeforeSelection) { | |
| 180 VisiblePosition start(selection.visibleStart()); | |
| 181 // We match AppKit's rule: Start 1 character before the selection. | |
| 182 VisiblePosition oneBeforeStart = start.previous(); | |
| 183 spellingSearchStart = (oneBeforeStart.isNotNull() ? oneBeforeStart :
start).toParentAnchoredPosition(); | |
| 184 } else { | |
| 185 spellingSearchStart = selection.visibleEnd().toParentAnchoredPositio
n(); | |
| 186 } | |
| 187 } | |
| 188 | |
| 189 Position position = spellingSearchStart; | |
| 190 if (!isEditablePosition(position)) { | |
| 191 // This shouldn't happen in very often because the Spelling menu items a
ren't enabled unless the | |
| 192 // selection is editable. | |
| 193 // This can happen in Mail for a mix of non-editable and editable conten
t (like Stationary), | |
| 194 // when spell checking the whole document before sending the message. | |
| 195 // In that case the document might not be editable, but there are editab
le pockets that need to be spell checked. | |
| 196 | |
| 197 position = firstEditableVisiblePositionAfterPositionInRoot(position, fra
me().document()->documentElement()).deepEquivalent(); | |
| 198 if (position.isNull()) | |
| 199 return; | |
| 200 | |
| 201 spellingSearchStart = position.parentAnchoredEquivalent(); | |
| 202 startedWithSelection = false; // won't need to wrap | |
| 203 } | |
| 204 | |
| 205 // topNode defines the whole range we want to operate on | |
| 206 ContainerNode* topNode = highestEditableRoot(position); | |
| 207 // TODO(yosin): |lastOffsetForEditing()| is wrong here if | |
| 208 // |editingIgnoresContent(highestEditableRoot())| returns true, e.g. <table> | |
| 209 spellingSearchEnd = createLegacyEditingPosition(topNode, EditingStrategy::la
stOffsetForEditing(topNode)); | |
| 210 | |
| 211 // If spellingSearchRange starts in the middle of a word, advance to the nex
t word so we start checking | |
| 212 // at a word boundary. Going back by one char and then forward by a word doe
s the trick. | |
| 213 if (startedWithSelection) { | |
| 214 VisiblePosition oneBeforeStart = VisiblePosition(spellingSearchStart, DOWNST
REAM).previous(); | |
| 215 if (oneBeforeStart.isNotNull()) | |
| 216 spellingSearchStart = endOfWord(oneBeforeStart).toParentAnchoredPosi
tion(); | |
| 217 // else we were already at the start of the editable node | |
| 218 } | |
| 219 | |
| 220 if (spellingSearchStart == spellingSearchEnd) | |
| 221 return; // nothing to search in | |
| 222 | |
| 223 // We go to the end of our first range instead of the start of it, just to b
e sure | |
| 224 // we don't get foiled by any word boundary problems at the start. It means
we might | |
| 225 // do a tiny bit more searching. | |
| 226 Node* searchEndNodeAfterWrap = spellingSearchEnd.computeContainerNode(); | |
| 227 int searchEndOffsetAfterWrap = spellingSearchEnd.offsetInContainerNode(); | |
| 228 | |
| 229 int misspellingOffset = 0; | |
| 230 GrammarDetail grammarDetail; | |
| 231 int grammarPhraseOffset = 0; | |
| 232 Position grammarSearchStart, grammarSearchEnd; | |
| 233 String badGrammarPhrase; | |
| 234 String misspelledWord; | |
| 235 | |
| 236 bool isSpelling = true; | |
| 237 int foundOffset = 0; | |
| 238 String foundItem; | |
| 239 RefPtrWillBeRawPtr<Range> firstMisspellingRange = nullptr; | |
| 240 if (unifiedTextCheckerEnabled()) { | |
| 241 grammarSearchStart = spellingSearchStart; | |
| 242 grammarSearchEnd = spellingSearchEnd; | |
| 243 foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchStart
, spellingSearchEnd).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled()
, isSpelling, foundOffset, grammarDetail); | |
| 244 if (isSpelling) { | |
| 245 misspelledWord = foundItem; | |
| 246 misspellingOffset = foundOffset; | |
| 247 } else { | |
| 248 badGrammarPhrase = foundItem; | |
| 249 grammarPhraseOffset = foundOffset; | |
| 250 } | |
| 251 } else { | |
| 252 misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSearch
Start, spellingSearchEnd).findFirstMisspelling(misspellingOffset, false, firstMi
sspellingRange); | |
| 253 grammarSearchStart = spellingSearchStart; | |
| 254 grammarSearchEnd = spellingSearchEnd; | |
| 255 if (!misspelledWord.isEmpty()) { | |
| 256 // Stop looking at start of next misspelled word | |
| 257 CharacterIterator chars(grammarSearchStart, grammarSearchEnd); | |
| 258 chars.advance(misspellingOffset); | |
| 259 grammarSearchEnd = chars.startPosition(); | |
| 260 } | |
| 261 | |
| 262 if (isGrammarCheckingEnabled()) | |
| 263 badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), grammarS
earchStart, grammarSearchEnd).findFirstBadGrammar(grammarDetail, grammarPhraseOf
fset, false); | |
| 264 } | |
| 265 | |
| 266 // 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 | |
| 267 // block rather than at a selection). | |
| 268 if (startedWithSelection && !misspelledWord && !badGrammarPhrase) { | |
| 269 spellingSearchStart = createLegacyEditingPosition(topNode, 0); | |
| 270 // going until the end of the very first chunk we tested is far enough | |
| 271 spellingSearchEnd = createLegacyEditingPosition(searchEndNodeAfterWrap,
searchEndOffsetAfterWrap); | |
| 272 | |
| 273 if (unifiedTextCheckerEnabled()) { | |
| 274 grammarSearchStart = spellingSearchStart; | |
| 275 grammarSearchEnd = spellingSearchEnd; | |
| 276 foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchS
tart, spellingSearchEnd).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabl
ed(), isSpelling, foundOffset, grammarDetail); | |
| 277 if (isSpelling) { | |
| 278 misspelledWord = foundItem; | |
| 279 misspellingOffset = foundOffset; | |
| 280 } else { | |
| 281 badGrammarPhrase = foundItem; | |
| 282 grammarPhraseOffset = foundOffset; | |
| 283 } | |
| 284 } else { | |
| 285 misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSe
archStart, spellingSearchEnd).findFirstMisspelling(misspellingOffset, false, fir
stMisspellingRange); | |
| 286 grammarSearchStart = spellingSearchStart; | |
| 287 grammarSearchEnd = spellingSearchEnd; | |
| 288 if (!misspelledWord.isEmpty()) { | |
| 289 // Stop looking at start of next misspelled word | |
| 290 CharacterIterator chars(grammarSearchStart, grammarSearchEnd); | |
| 291 chars.advance(misspellingOffset); | |
| 292 grammarSearchEnd = chars.startPosition(); | |
| 293 } | |
| 294 | |
| 295 if (isGrammarCheckingEnabled()) | |
| 296 badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), gram
marSearchStart, grammarSearchEnd).findFirstBadGrammar(grammarDetail, grammarPhra
seOffset, false); | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 if (!badGrammarPhrase.isEmpty()) { | |
| 301 // We found bad grammar. Since we only searched for bad grammar up to th
e first misspelled word, the bad grammar | |
| 302 // takes precedence and we ignore any potential misspelled word. Select
the grammar detail, update the spelling | |
| 303 // panel, and store a marker so we draw the green squiggle later. | |
| 304 | |
| 305 ASSERT(badGrammarPhrase.length() > 0); | |
| 306 ASSERT(grammarDetail.location != -1 && grammarDetail.length > 0); | |
| 307 | |
| 308 // FIXME 4859190: This gets confused with doubled punctuation at the end
of a paragraph | |
| 309 const EphemeralRange badGrammarRange = calculateCharacterSubrange(Epheme
ralRange(grammarSearchStart, grammarSearchEnd), grammarPhraseOffset + grammarDet
ail.location, grammarDetail.length); | |
| 310 frame().selection().setSelection(VisibleSelection(badGrammarRange)); | |
| 311 frame().selection().revealSelection(); | |
| 312 | |
| 313 frame().document()->markers().addMarker(badGrammarRange.startPosition(),
badGrammarRange.endPosition(), DocumentMarker::Grammar, grammarDetail.userDescr
iption); | |
| 314 } else if (!misspelledWord.isEmpty()) { | |
| 315 // We found a misspelling, but not any earlier bad grammar. Select the m
isspelling, update the spelling panel, and store | |
| 316 // a marker so we draw the red squiggle later. | |
| 317 | |
| 318 const EphemeralRange misspellingRange = calculateCharacterSubrange(Ephem
eralRange(spellingSearchStart, spellingSearchEnd), misspellingOffset, misspelled
Word.length()); | |
| 319 frame().selection().setSelection(VisibleSelection(misspellingRange, DOWN
STREAM)); | |
| 320 frame().selection().revealSelection(); | |
| 321 | |
| 322 spellCheckerClient().updateSpellingUIWithMisspelledWord(misspelledWord); | |
| 323 frame().document()->markers().addMarker(misspellingRange.startPosition()
, misspellingRange.endPosition(), DocumentMarker::Spelling); | |
| 324 } | |
| 325 } | |
| 326 | |
| 327 void SpellChecker::showSpellingGuessPanel() | |
| 328 { | |
| 329 if (spellCheckerClient().spellingUIIsShowing()) { | |
| 330 spellCheckerClient().showSpellingUI(false); | |
| 331 return; | |
| 332 } | |
| 333 | |
| 334 advanceToNextMisspelling(true); | |
| 335 spellCheckerClient().showSpellingUI(true); | |
| 336 } | |
| 337 | |
| 338 void SpellChecker::clearMisspellingsAndBadGrammar(const VisibleSelection &moving
Selection) | |
| 339 { | |
| 340 removeMarkers(movingSelection, DocumentMarker::MisspellingMarkers()); | |
| 341 } | |
| 342 | |
| 343 void SpellChecker::markMisspellingsAndBadGrammar(const VisibleSelection &movingS
election) | |
| 344 { | |
| 345 markMisspellingsAndBadGrammar(movingSelection, isContinuousSpellCheckingEnab
led() && isGrammarCheckingEnabled(), movingSelection); | |
| 346 } | |
| 347 | |
| 348 void SpellChecker::markMisspellingsAfterLineBreak(const VisibleSelection& wordSe
lection) | |
| 349 { | |
| 350 TRACE_EVENT0("blink", "SpellChecker::markMisspellingsAfterLineBreak"); | |
| 351 | |
| 352 if (unifiedTextCheckerEnabled()) { | |
| 353 TextCheckingTypeMask textCheckingOptions = 0; | |
| 354 | |
| 355 if (isContinuousSpellCheckingEnabled()) | |
| 356 textCheckingOptions |= TextCheckingTypeSpelling; | |
| 357 | |
| 358 if (isGrammarCheckingEnabled()) | |
| 359 textCheckingOptions |= TextCheckingTypeGrammar; | |
| 360 | |
| 361 VisibleSelection wholeParagraph( | |
| 362 startOfParagraph(wordSelection.visibleStart()), | |
| 363 endOfParagraph(wordSelection.visibleEnd())); | |
| 364 | |
| 365 markAllMisspellingsAndBadGrammarInRanges( | |
| 366 textCheckingOptions, wordSelection.toNormalizedRange().get(), | |
| 367 wholeParagraph.toNormalizedRange().get()); | |
| 368 } else { | |
| 369 RefPtrWillBeRawPtr<Range> misspellingRange = nullptr; | |
| 370 markMisspellings(wordSelection, misspellingRange); | |
| 371 } | |
| 372 } | |
| 373 | |
| 374 void SpellChecker::markMisspellingsAfterTypingToWord(const VisiblePosition &word
Start, const VisibleSelection& selectionAfterTyping) | |
| 375 { | |
| 376 TRACE_EVENT0("blink", "SpellChecker::markMisspellingsAfterTypingToWord"); | |
| 377 | |
| 378 if (unifiedTextCheckerEnabled()) { | |
| 379 TextCheckingTypeMask textCheckingOptions = 0; | |
| 380 | |
| 381 if (isContinuousSpellCheckingEnabled()) | |
| 382 textCheckingOptions |= TextCheckingTypeSpelling; | |
| 383 | |
| 384 if (!(textCheckingOptions & TextCheckingTypeSpelling)) | |
| 385 return; | |
| 386 | |
| 387 if (isGrammarCheckingEnabled()) | |
| 388 textCheckingOptions |= TextCheckingTypeGrammar; | |
| 389 | |
| 390 VisibleSelection adjacentWords = VisibleSelection(startOfWord(wordStart,
LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary)); | |
| 391 if (textCheckingOptions & TextCheckingTypeGrammar) { | |
| 392 VisibleSelection selectedSentence = VisibleSelection(startOfSentence
(wordStart), endOfSentence(wordStart)); | |
| 393 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjace
ntWords.toNormalizedRange().get(), selectedSentence.toNormalizedRange().get()); | |
| 394 } else { | |
| 395 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjace
ntWords.toNormalizedRange().get(), adjacentWords.toNormalizedRange().get()); | |
| 396 } | |
| 397 return; | |
| 398 } | |
| 399 | |
| 400 if (!isContinuousSpellCheckingEnabled()) | |
| 401 return; | |
| 402 | |
| 403 // Check spelling of one word | |
| 404 RefPtrWillBeRawPtr<Range> misspellingRange = nullptr; | |
| 405 markMisspellings(VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundar
y), endOfWord(wordStart, RightWordIfOnBoundary)), misspellingRange); | |
| 406 | |
| 407 if (!misspellingRange || !isGrammarCheckingEnabled()) | |
| 408 return; | |
| 409 | |
| 410 // Check grammar of entire sentence | |
| 411 markBadGrammar(VisibleSelection(startOfSentence(wordStart), endOfSentence(wo
rdStart))); | |
| 412 } | |
| 413 | |
| 414 void SpellChecker::markMisspellingsOrBadGrammar(const VisibleSelection& selectio
n, bool checkSpelling, RefPtrWillBeRawPtr<Range>& firstMisspellingRange) | |
| 415 { | |
| 416 // This function is called with a selection already expanded to word boundar
ies. | |
| 417 // Might be nice to assert that here. | |
| 418 | |
| 419 // This function is used only for as-you-type checking, so if that's off we
do nothing. Note that | |
| 420 // grammar checking can only be on if spell checking is also on. | |
| 421 if (!isContinuousSpellCheckingEnabled()) | |
| 422 return; | |
| 423 | |
| 424 TRACE_EVENT0("blink", "SpellChecker::markMisspellingsOrBadGrammar"); | |
| 425 | |
| 426 const EphemeralRange range = selection.toNormalizedEphemeralRange(); | |
| 427 if (range.isNull()) | |
| 428 return; | |
| 429 | |
| 430 // If we're not in an editable node, bail. | |
| 431 Node* editableNode = range.startPosition().computeContainerNode(); | |
| 432 if (!editableNode || !editableNode->hasEditableStyle()) | |
| 433 return; | |
| 434 | |
| 435 if (!isSpellCheckingEnabledFor(editableNode)) | |
| 436 return; | |
| 437 | |
| 438 TextCheckingHelper checker(spellCheckerClient(), range.startPosition(), rang
e.endPosition()); | |
| 439 if (checkSpelling) | |
| 440 checker.markAllMisspellings(firstMisspellingRange); | |
| 441 else if (isGrammarCheckingEnabled()) | |
| 442 checker.markAllBadGrammar(); | |
| 443 } | |
| 444 | |
| 445 bool SpellChecker::isSpellCheckingEnabledFor(Node* node) const | |
| 446 { | |
| 447 if (!node) | |
| 448 return false; | |
| 449 const Element* focusedElement = node->isElementNode() ? toElement(node) : no
de->parentElement(); | |
| 450 if (!focusedElement) | |
| 451 return false; | |
| 452 return focusedElement->isSpellCheckingEnabled(); | |
| 453 } | |
| 454 | |
| 455 bool SpellChecker::isSpellCheckingEnabledInFocusedNode() const | |
| 456 { | |
| 457 return isSpellCheckingEnabledFor(frame().selection().start().anchorNode()); | |
| 458 } | |
| 459 | |
| 460 void SpellChecker::markMisspellings(const VisibleSelection& selection, RefPtrWil
lBeRawPtr<Range>& firstMisspellingRange) | |
| 461 { | |
| 462 markMisspellingsOrBadGrammar(selection, true, firstMisspellingRange); | |
| 463 } | |
| 464 | |
| 465 void SpellChecker::markBadGrammar(const VisibleSelection& selection) | |
| 466 { | |
| 467 RefPtrWillBeRawPtr<Range> firstMisspellingRange = nullptr; | |
| 468 markMisspellingsOrBadGrammar(selection, false, firstMisspellingRange); | |
| 469 } | |
| 470 | |
| 471 void SpellChecker::markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask
textCheckingOptions, Range* spellingRange, Range* grammarRange) | |
| 472 { | |
| 473 ASSERT(unifiedTextCheckerEnabled()); | |
| 474 | |
| 475 bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar; | |
| 476 | |
| 477 // This function is called with selections already expanded to word boundari
es. | |
| 478 if (!spellingRange || (shouldMarkGrammar && !grammarRange)) | |
| 479 return; | |
| 480 | |
| 481 // If we're not in an editable node, bail. | |
| 482 Node* editableNode = spellingRange->startContainer(); | |
| 483 if (!editableNode || !editableNode->hasEditableStyle()) | |
| 484 return; | |
| 485 | |
| 486 if (!isSpellCheckingEnabledFor(editableNode)) | |
| 487 return; | |
| 488 | |
| 489 Range* rangeToCheck = shouldMarkGrammar ? grammarRange : spellingRange; | |
| 490 TextCheckingParagraph fullParagraphToCheck(rangeToCheck); | |
| 491 | |
| 492 bool asynchronous = frame().settings() && frame().settings()->asynchronousSp
ellCheckingEnabled(); | |
| 493 chunkAndMarkAllMisspellingsAndBadGrammar(textCheckingOptions, fullParagraphT
oCheck, asynchronous); | |
| 494 } | |
| 495 | |
| 496 void SpellChecker::chunkAndMarkAllMisspellingsAndBadGrammar(Node* node) | |
| 497 { | |
| 498 TRACE_EVENT0("blink", "SpellChecker::chunkAndMarkAllMisspellingsAndBadGramma
r"); | |
| 499 if (!node) | |
| 500 return; | |
| 501 RefPtrWillBeRawPtr<Range> rangeToCheck = Range::create(*frame().document(),
firstPositionInNode(node), lastPositionInNode(node)); | |
| 502 TextCheckingParagraph textToCheck(rangeToCheck, rangeToCheck); | |
| 503 bool asynchronous = true; | |
| 504 chunkAndMarkAllMisspellingsAndBadGrammar(resolveTextCheckingTypeMask(TextChe
ckingTypeSpelling | TextCheckingTypeGrammar), textToCheck, asynchronous); | |
| 505 } | |
| 506 | |
| 507 void SpellChecker::chunkAndMarkAllMisspellingsAndBadGrammar(TextCheckingTypeMask
textCheckingOptions, const TextCheckingParagraph& fullParagraphToCheck, bool as
ynchronous) | |
| 508 { | |
| 509 if (fullParagraphToCheck.isRangeEmpty() || fullParagraphToCheck.isEmpty()) | |
| 510 return; | |
| 511 | |
| 512 // Since the text may be quite big chunk it up and adjust to the sentence bo
undary. | |
| 513 const int kChunkSize = 16 * 1024; | |
| 514 int start = fullParagraphToCheck.checkingStart(); | |
| 515 int end = fullParagraphToCheck.checkingEnd(); | |
| 516 start = std::min(start, end); | |
| 517 end = std::max(start, end); | |
| 518 const int kNumChunksToCheck = asynchronous ? (end - start + kChunkSize - 1)
/ (kChunkSize) : 1; | |
| 519 int currentChunkStart = start; | |
| 520 RefPtrWillBeRawPtr<Range> checkRange = fullParagraphToCheck.checkingRange(); | |
| 521 if (kNumChunksToCheck == 1 && asynchronous) { | |
| 522 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, checkRange
.get(), checkRange.get(), asynchronous, 0); | |
| 523 return; | |
| 524 } | |
| 525 | |
| 526 for (int iter = 0; iter < kNumChunksToCheck; ++iter) { | |
| 527 checkRange = fullParagraphToCheck.subrange(currentChunkStart, kChunkSize
); | |
| 528 expandRangeToSentenceBoundary(*checkRange); | |
| 529 | |
| 530 int checkingLength = 0; | |
| 531 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, checkRange
.get(), checkRange.get(), asynchronous, iter, &checkingLength); | |
| 532 currentChunkStart += checkingLength; | |
| 533 } | |
| 534 } | |
| 535 | |
| 536 void SpellChecker::markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask
textCheckingOptions, Range* checkRange, Range* paragraphRange, bool asynchronou
s, int requestNumber, int* checkingLength) | |
| 537 { | |
| 538 TextCheckingParagraph sentenceToCheck(checkRange, paragraphRange); | |
| 539 if (checkingLength) | |
| 540 *checkingLength = sentenceToCheck.checkingLength(); | |
| 541 | |
| 542 RefPtrWillBeRawPtr<SpellCheckRequest> request = SpellCheckRequest::create(re
solveTextCheckingTypeMask(textCheckingOptions), TextCheckingProcessBatch, checkR
ange, paragraphRange, requestNumber); | |
| 543 if (!request) | |
| 544 return; | |
| 545 | |
| 546 if (asynchronous) { | |
| 547 m_spellCheckRequester->requestCheckingFor(request); | |
| 548 } else { | |
| 549 Vector<TextCheckingResult> results; | |
| 550 checkTextOfParagraph(textChecker(), sentenceToCheck.text(), resolveTextC
heckingTypeMask(textCheckingOptions), results); | |
| 551 markAndReplaceFor(request, results); | |
| 552 } | |
| 553 } | |
| 554 | |
| 555 void SpellChecker::markAndReplaceFor(PassRefPtrWillBeRawPtr<SpellCheckRequest> r
equest, const Vector<TextCheckingResult>& results) | |
| 556 { | |
| 557 TRACE_EVENT0("blink", "SpellChecker::markAndReplaceFor"); | |
| 558 ASSERT(request); | |
| 559 | |
| 560 TextCheckingTypeMask textCheckingOptions = request->data().mask(); | |
| 561 TextCheckingParagraph paragraph(request->checkingRange(), request->paragraph
Range()); | |
| 562 | |
| 563 bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling; | |
| 564 bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar; | |
| 565 | |
| 566 // Expand the range to encompass entire paragraphs, since text checking need
s that much context. | |
| 567 int selectionOffset = 0; | |
| 568 int ambiguousBoundaryOffset = -1; | |
| 569 bool selectionChanged = false; | |
| 570 bool restoreSelectionAfterChange = false; | |
| 571 bool adjustSelectionForParagraphBoundaries = false; | |
| 572 | |
| 573 if (shouldMarkSpelling) { | |
| 574 if (frame().selection().isCaret()) { | |
| 575 // Attempt to save the caret position so we can restore it later if
needed | |
| 576 Position caretPosition = frame().selection().end(); | |
| 577 selectionOffset = paragraph.offsetTo(caretPosition, ASSERT_NO_EXCEPT
ION); | |
| 578 restoreSelectionAfterChange = true; | |
| 579 if (selectionOffset > 0 && (static_cast<unsigned>(selectionOffset) >
paragraph.text().length() || paragraph.textCharAt(selectionOffset - 1) == newli
neCharacter)) | |
| 580 adjustSelectionForParagraphBoundaries = true; | |
| 581 if (selectionOffset > 0 && static_cast<unsigned>(selectionOffset) <=
paragraph.text().length() && isAmbiguousBoundaryCharacter(paragraph.textCharAt(
selectionOffset - 1))) | |
| 582 ambiguousBoundaryOffset = selectionOffset - 1; | |
| 583 } | |
| 584 } | |
| 585 | |
| 586 for (unsigned i = 0; i < results.size(); i++) { | |
| 587 int spellingRangeEndOffset = paragraph.checkingEnd(); | |
| 588 const TextCheckingResult* result = &results[i]; | |
| 589 int resultLocation = result->location + paragraph.checkingStart(); | |
| 590 int resultLength = result->length; | |
| 591 bool resultEndsAtAmbiguousBoundary = ambiguousBoundaryOffset >= 0 && res
ultLocation + resultLength == ambiguousBoundaryOffset; | |
| 592 | |
| 593 // Only mark misspelling if: | |
| 594 // 1. Current text checking isn't done for autocorrection, in which case
shouldMarkSpelling is false. | |
| 595 // 2. Result falls within spellingRange. | |
| 596 // 3. The word in question doesn't end at an ambiguous boundary. For ins
tance, we would not mark | |
| 597 // "wouldn'" as misspelled right after apostrophe is typed. | |
| 598 if (shouldMarkSpelling && result->decoration == TextDecorationTypeSpelli
ng && resultLocation >= paragraph.checkingStart() && resultLocation + resultLeng
th <= spellingRangeEndOffset && !resultEndsAtAmbiguousBoundary) { | |
| 599 ASSERT(resultLength > 0 && resultLocation >= 0); | |
| 600 const EphemeralRange misspellingRange = calculateCharacterSubrange(E
phemeralRange(paragraph.paragraphRange().get()), resultLocation, resultLength); | |
| 601 frame().document()->markers().addMarker(misspellingRange.startPositi
on(), misspellingRange.endPosition(), DocumentMarker::Spelling, result->replacem
ent, result->hash); | |
| 602 } else if (shouldMarkGrammar && result->decoration == TextDecorationType
Grammar && paragraph.checkingRangeCovers(resultLocation, resultLength)) { | |
| 603 ASSERT(resultLength > 0 && resultLocation >= 0); | |
| 604 for (unsigned j = 0; j < result->details.size(); j++) { | |
| 605 const GrammarDetail* detail = &result->details[j]; | |
| 606 ASSERT(detail->length > 0 && detail->location >= 0); | |
| 607 if (paragraph.checkingRangeCovers(resultLocation + detail->locat
ion, detail->length)) { | |
| 608 const EphemeralRange badGrammarRange = calculateCharacterSub
range(EphemeralRange(paragraph.paragraphRange().get()), resultLocation + detail-
>location, detail->length); | |
| 609 frame().document()->markers().addMarker(badGrammarRange.star
tPosition(), badGrammarRange.endPosition(), DocumentMarker::Grammar, detail->use
rDescription, result->hash); | |
| 610 } | |
| 611 } | |
| 612 } else if (result->decoration == TextDecorationTypeInvisibleSpellcheck &
& resultLocation >= paragraph.checkingStart() && resultLocation + resultLength <
= spellingRangeEndOffset) { | |
| 613 ASSERT(resultLength > 0 && resultLocation >= 0); | |
| 614 const EphemeralRange invisibleSpellcheckRange = calculateCharacterSu
brange(EphemeralRange(paragraph.paragraphRange().get()), resultLocation, resultL
ength); | |
| 615 frame().document()->markers().addMarker(invisibleSpellcheckRange.sta
rtPosition(), invisibleSpellcheckRange.endPosition(), DocumentMarker::InvisibleS
pellcheck, result->replacement, result->hash); | |
| 616 } | |
| 617 } | |
| 618 | |
| 619 if (selectionChanged) { | |
| 620 TextCheckingParagraph extendedParagraph(paragraph); | |
| 621 // Restore the caret position if we have made any replacements | |
| 622 extendedParagraph.expandRangeToNextEnd(); | |
| 623 if (restoreSelectionAfterChange && selectionOffset >= 0 && selectionOffs
et <= extendedParagraph.rangeLength()) { | |
| 624 RefPtrWillBeRawPtr<Range> selectionRange = extendedParagraph.subrang
e(0, selectionOffset); | |
| 625 frame().selection().moveTo(selectionRange->endPosition(), DOWNSTREAM
); | |
| 626 if (adjustSelectionForParagraphBoundaries) | |
| 627 frame().selection().modify(FrameSelection::AlterationMove, Direc
tionForward, CharacterGranularity); | |
| 628 } else { | |
| 629 // If this fails for any reason, the fallback is to go one position
beyond the last replacement | |
| 630 frame().selection().moveTo(frame().selection().selection().visibleEn
d()); | |
| 631 frame().selection().modify(FrameSelection::AlterationMove, Direction
Forward, CharacterGranularity); | |
| 632 } | |
| 633 } | |
| 634 } | |
| 635 | |
| 636 void SpellChecker::markMisspellingsAndBadGrammar(const VisibleSelection& spellin
gSelection, bool markGrammar, const VisibleSelection& grammarSelection) | |
| 637 { | |
| 638 if (unifiedTextCheckerEnabled()) { | |
| 639 if (!isContinuousSpellCheckingEnabled()) | |
| 640 return; | |
| 641 | |
| 642 // markMisspellingsAndBadGrammar() is triggered by selection change, in
which case we check spelling and grammar, but don't autocorrect misspellings. | |
| 643 TextCheckingTypeMask textCheckingOptions = TextCheckingTypeSpelling; | |
| 644 if (markGrammar && isGrammarCheckingEnabled()) | |
| 645 textCheckingOptions |= TextCheckingTypeGrammar; | |
| 646 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, spellingSe
lection.toNormalizedRange().get(), grammarSelection.toNormalizedRange().get()); | |
| 647 return; | |
| 648 } | |
| 649 | |
| 650 RefPtrWillBeRawPtr<Range> firstMisspellingRange = nullptr; | |
| 651 markMisspellings(spellingSelection, firstMisspellingRange); | |
| 652 if (markGrammar) | |
| 653 markBadGrammar(grammarSelection); | |
| 654 } | |
| 655 | |
| 656 void SpellChecker::updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSele
ctionAtWordBoundary) | |
| 657 { | |
| 658 if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTypeSpe
lling)) | |
| 659 return; | |
| 660 | |
| 661 TRACE_EVENT0("blink", "SpellChecker::updateMarkersForWordsAffectedByEditing"
); | |
| 662 | |
| 663 // We want to remove the markers from a word if an editing command will chan
ge the word. This can happen in one of | |
| 664 // several scenarios: | |
| 665 // 1. Insert in the middle of a word. | |
| 666 // 2. Appending non whitespace at the beginning of word. | |
| 667 // 3. Appending non whitespace at the end of word. | |
| 668 // Note that, appending only whitespaces at the beginning or end of word won
't change the word, so we don't need to | |
| 669 // remove the markers on that word. | |
| 670 // Of course, if current selection is a range, we potentially will edit two
words that fall on the boundaries of | |
| 671 // selection, and remove words between the selection boundaries. | |
| 672 // | |
| 673 VisiblePosition startOfSelection = frame().selection().selection().visibleSt
art(); | |
| 674 VisiblePosition endOfSelection = frame().selection().selection().visibleEnd(
); | |
| 675 if (startOfSelection.isNull()) | |
| 676 return; | |
| 677 // First word is the word that ends after or on the start of selection. | |
| 678 VisiblePosition startOfFirstWord = startOfWord(startOfSelection, LeftWordIfO
nBoundary); | |
| 679 VisiblePosition endOfFirstWord = endOfWord(startOfSelection, LeftWordIfOnBou
ndary); | |
| 680 // Last word is the word that begins before or on the end of selection | |
| 681 VisiblePosition startOfLastWord = startOfWord(endOfSelection, RightWordIfOnB
oundary); | |
| 682 VisiblePosition endOfLastWord = endOfWord(endOfSelection, RightWordIfOnBound
ary); | |
| 683 | |
| 684 if (startOfFirstWord.isNull()) { | |
| 685 startOfFirstWord = startOfWord(startOfSelection, RightWordIfOnBoundary); | |
| 686 endOfFirstWord = endOfWord(startOfSelection, RightWordIfOnBoundary); | |
| 687 } | |
| 688 | |
| 689 if (endOfLastWord.isNull()) { | |
| 690 startOfLastWord = startOfWord(endOfSelection, LeftWordIfOnBoundary); | |
| 691 endOfLastWord = endOfWord(endOfSelection, LeftWordIfOnBoundary); | |
| 692 } | |
| 693 | |
| 694 // If doNotRemoveIfSelectionAtWordBoundary is true, and first word ends at t
he start of selection, | |
| 695 // we choose next word as the first word. | |
| 696 if (doNotRemoveIfSelectionAtWordBoundary && endOfFirstWord.deepEquivalent()
== startOfSelection.deepEquivalent()) { | |
| 697 startOfFirstWord = nextWordPosition(startOfFirstWord); | |
| 698 endOfFirstWord = endOfWord(startOfFirstWord, RightWordIfOnBoundary); | |
| 699 if (startOfFirstWord.deepEquivalent() == endOfSelection.deepEquivalent()
) | |
| 700 return; | |
| 701 } | |
| 702 | |
| 703 // If doNotRemoveIfSelectionAtWordBoundary is true, and last word begins at
the end of selection, | |
| 704 // we choose previous word as the last word. | |
| 705 if (doNotRemoveIfSelectionAtWordBoundary && startOfLastWord.deepEquivalent()
== endOfSelection.deepEquivalent()) { | |
| 706 startOfLastWord = previousWordPosition(startOfLastWord); | |
| 707 endOfLastWord = endOfWord(startOfLastWord, RightWordIfOnBoundary); | |
| 708 if (endOfLastWord.deepEquivalent() == startOfSelection.deepEquivalent()) | |
| 709 return; | |
| 710 } | |
| 711 | |
| 712 if (startOfFirstWord.isNull() || endOfFirstWord.isNull() || startOfLastWord.
isNull() || endOfLastWord.isNull()) | |
| 713 return; | |
| 714 | |
| 715 // Now we remove markers on everything between startOfFirstWord and endOfLas
tWord. | |
| 716 // However, if an autocorrection change a single word to multiple words, we
want to remove correction mark from all the | |
| 717 // resulted words even we only edit one of them. For example, assuming autoc
orrection changes "avantgarde" to "avant | |
| 718 // garde", we will have CorrectionIndicator marker on both words and on the
whitespace between them. If we then edit garde, | |
| 719 // we would like to remove the marker from word "avant" and whitespace as we
ll. So we need to get the continous range of | |
| 720 // of marker that contains the word in question, and remove marker on that w
hole range. | |
| 721 Document* document = frame().document(); | |
| 722 ASSERT(document); | |
| 723 Node* startNode = startOfFirstWord.deepEquivalent().computeContainerNode(); | |
| 724 int startOffset = startOfFirstWord.deepEquivalent().computeOffsetInContainer
Node(); | |
| 725 int endOffset = endOfLastWord.deepEquivalent().computeOffsetInContainerNode(
); | |
| 726 document->markers().removeMarkers(startNode, startOffset, endOffset - startO
ffset, DocumentMarker::MisspellingMarkers(), DocumentMarkerController::RemovePar
tiallyOverlappingMarker); | |
| 727 } | |
| 728 | |
| 729 void SpellChecker::didEndEditingOnTextField(Element* e) | |
| 730 { | |
| 731 TRACE_EVENT0("blink", "SpellChecker::didEndEditingOnTextField"); | |
| 732 | |
| 733 // Remove markers when deactivating a selection in an <input type="text"/>. | |
| 734 // Prevent new ones from appearing too. | |
| 735 m_spellCheckRequester->cancelCheck(); | |
| 736 HTMLTextFormControlElement* textFormControlElement = toHTMLTextFormControlEl
ement(e); | |
| 737 HTMLElement* innerEditor = textFormControlElement->innerEditorElement(); | |
| 738 DocumentMarker::MarkerTypes markerTypes(DocumentMarker::Spelling); | |
| 739 if (isGrammarCheckingEnabled() || unifiedTextCheckerEnabled()) | |
| 740 markerTypes.add(DocumentMarker::Grammar); | |
| 741 for (Node& node : NodeTraversal::inclusiveDescendantsOf(*innerEditor)) | |
| 742 frame().document()->markers().removeMarkers(&node, markerTypes); | |
| 743 } | |
| 744 | |
| 745 void SpellChecker::replaceMisspelledRange(const String& text) | |
| 746 { | |
| 747 EphemeralRange caretRange = frame().selection().selection().toNormalizedEphe
meralRange(); | |
| 748 if (caretRange.isNull()) | |
| 749 return; | |
| 750 DocumentMarkerVector markers = frame().document()->markers().markersInRange(
caretRange, DocumentMarker::MisspellingMarkers()); | |
| 751 if (markers.size() < 1 || markers[0]->startOffset() >= markers[0]->endOffset
()) | |
| 752 return; | |
| 753 EphemeralRange markerRange = EphemeralRange(Position(caretRange.startPositio
n().computeContainerNode(), markers[0]->startOffset()), Position(caretRange.endP
osition().computeContainerNode(), markers[0]->endOffset())); | |
| 754 if (markerRange.isNull()) | |
| 755 return; | |
| 756 frame().selection().setSelection(VisibleSelection(markerRange), CharacterGra
nularity); | |
| 757 frame().editor().replaceSelectionWithText(text, false, false); | |
| 758 } | |
| 759 | |
| 760 void SpellChecker::respondToChangedSelection(const VisibleSelection& oldSelectio
n, FrameSelection::SetSelectionOptions options) | |
| 761 { | |
| 762 TRACE_EVENT0("blink", "SpellChecker::respondToChangedSelection"); | |
| 763 | |
| 764 bool closeTyping = options & FrameSelection::CloseTyping; | |
| 765 bool isContinuousSpellCheckingEnabled = this->isContinuousSpellCheckingEnabl
ed(); | |
| 766 bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled &
& isGrammarCheckingEnabled(); | |
| 767 if (isContinuousSpellCheckingEnabled) { | |
| 768 VisibleSelection newAdjacentWords; | |
| 769 VisibleSelection newSelectedSentence; | |
| 770 bool caretBrowsing = frame().settings() && frame().settings()->caretBrow
singEnabled(); | |
| 771 const VisibleSelection newSelection = frame().selection().selection(); | |
| 772 if (isSelectionInTextFormControl(newSelection)) { | |
| 773 Position newStart = newSelection.start(); | |
| 774 newAdjacentWords.setWithoutValidation(HTMLTextFormControlElement::st
artOfWord(newStart), HTMLTextFormControlElement::endOfWord(newStart)); | |
| 775 if (isContinuousGrammarCheckingEnabled) | |
| 776 newSelectedSentence.setWithoutValidation(HTMLTextFormControlElem
ent::startOfSentence(newStart), HTMLTextFormControlElement::endOfSentence(newSta
rt)); | |
| 777 } else if (newSelection.isContentEditable() || caretBrowsing) { | |
| 778 VisiblePosition newStart(newSelection.visibleStart()); | |
| 779 newAdjacentWords = VisibleSelection(startOfWord(newStart, LeftWordIf
OnBoundary), endOfWord(newStart, RightWordIfOnBoundary)); | |
| 780 if (isContinuousGrammarCheckingEnabled) | |
| 781 newSelectedSentence = VisibleSelection(startOfSentence(newStart)
, endOfSentence(newStart)); | |
| 782 } | |
| 783 | |
| 784 // Don't check spelling and grammar if the change of selection is trigge
red by spelling correction itself. | |
| 785 bool shouldCheckSpellingAndGrammar = !(options & FrameSelection::SpellCo
rrectionTriggered); | |
| 786 | |
| 787 // When typing we check spelling elsewhere, so don't redo it here. | |
| 788 // If this is a change in selection resulting from a delete operation, | |
| 789 // oldSelection may no longer be in the document. | |
| 790 // FIXME(http://crbug.com/382809): if oldSelection is on a textarea | |
| 791 // element, we cause synchronous layout. | |
| 792 if (shouldCheckSpellingAndGrammar | |
| 793 && closeTyping | |
| 794 && !isSelectionInTextField(oldSelection) | |
| 795 && (isSelectionInTextArea(oldSelection) || oldSelection.isContentEdi
table()) | |
| 796 && oldSelection.start().inDocument()) { | |
| 797 spellCheckOldSelection(oldSelection, newAdjacentWords); | |
| 798 } | |
| 799 | |
| 800 // FIXME(http://crbug.com/382809): | |
| 801 // shouldEraseMarkersAfterChangeSelection is true, we cause synchronous | |
| 802 // layout. | |
| 803 if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTyp
eSpelling)) | |
| 804 removeMarkers(newAdjacentWords, DocumentMarker::Spelling); | |
| 805 if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTyp
eGrammar)) | |
| 806 removeMarkers(newSelectedSentence, DocumentMarker::Grammar); | |
| 807 } | |
| 808 | |
| 809 // When continuous spell checking is off, existing markers disappear after t
he selection changes. | |
| 810 if (!isContinuousSpellCheckingEnabled) | |
| 811 frame().document()->markers().removeMarkers(DocumentMarker::Spelling); | |
| 812 if (!isContinuousGrammarCheckingEnabled) | |
| 813 frame().document()->markers().removeMarkers(DocumentMarker::Grammar); | |
| 814 } | |
| 815 | |
| 816 void SpellChecker::removeSpellingMarkers() | |
| 817 { | |
| 818 frame().document()->markers().removeMarkers(DocumentMarker::MisspellingMarke
rs()); | |
| 819 } | |
| 820 | |
| 821 void SpellChecker::removeSpellingMarkersUnderWords(const Vector<String>& words) | |
| 822 { | |
| 823 MarkerRemoverPredicate removerPredicate(words); | |
| 824 | |
| 825 DocumentMarkerController& markerController = frame().document()->markers(); | |
| 826 markerController.removeMarkers(removerPredicate); | |
| 827 markerController.repaintMarkers(); | |
| 828 } | |
| 829 | |
| 830 void SpellChecker::spellCheckAfterBlur() | |
| 831 { | |
| 832 if (!frame().selection().selection().isContentEditable()) | |
| 833 return; | |
| 834 | |
| 835 if (isSelectionInTextField(frame().selection().selection())) { | |
| 836 // textFieldDidEndEditing() and textFieldDidBeginEditing() handle this. | |
| 837 return; | |
| 838 } | |
| 839 | |
| 840 VisibleSelection empty; | |
| 841 spellCheckOldSelection(frame().selection().selection(), empty); | |
| 842 } | |
| 843 | |
| 844 void SpellChecker::spellCheckOldSelection(const VisibleSelection& oldSelection,
const VisibleSelection& newAdjacentWords) | |
| 845 { | |
| 846 TRACE_EVENT0("blink", "SpellChecker::spellCheckOldSelection"); | |
| 847 | |
| 848 VisiblePosition oldStart(oldSelection.visibleStart()); | |
| 849 VisibleSelection oldAdjacentWords = VisibleSelection(startOfWord(oldStart, L
eftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary)); | |
| 850 if (!VisibleSelection::InDOMTree::equalSelections(oldAdjacentWords, newAdjac
entWords)) { | |
| 851 if (isContinuousSpellCheckingEnabled() && isGrammarCheckingEnabled()) { | |
| 852 VisibleSelection selectedSentence = VisibleSelection(startOfSentence
(oldStart), endOfSentence(oldStart)); | |
| 853 markMisspellingsAndBadGrammar(oldAdjacentWords, true, selectedSenten
ce); | |
| 854 } else { | |
| 855 markMisspellingsAndBadGrammar(oldAdjacentWords, false, oldAdjacentWo
rds); | |
| 856 } | |
| 857 } | |
| 858 } | |
| 859 | |
| 860 static Node* findFirstMarkable(Node* node) | |
| 861 { | |
| 862 while (node) { | |
| 863 if (!node->layoutObject()) | |
| 864 return 0; | |
| 865 if (node->layoutObject()->isText()) | |
| 866 return node; | |
| 867 if (node->layoutObject()->isTextControl()) | |
| 868 node = toLayoutTextControl(node->layoutObject())->textFormControlEle
ment()->visiblePositionForIndex(1).deepEquivalent().anchorNode(); | |
| 869 else if (node->hasChildren()) | |
| 870 node = node->firstChild(); | |
| 871 else | |
| 872 node = node->nextSibling(); | |
| 873 } | |
| 874 | |
| 875 return 0; | |
| 876 } | |
| 877 | |
| 878 bool SpellChecker::selectionStartHasMarkerFor(DocumentMarker::MarkerType markerT
ype, int from, int length) const | |
| 879 { | |
| 880 Node* node = findFirstMarkable(frame().selection().start().anchorNode()); | |
| 881 if (!node) | |
| 882 return false; | |
| 883 | |
| 884 unsigned startOffset = static_cast<unsigned>(from); | |
| 885 unsigned endOffset = static_cast<unsigned>(from + length); | |
| 886 DocumentMarkerVector markers = frame().document()->markers().markersFor(node
); | |
| 887 for (size_t i = 0; i < markers.size(); ++i) { | |
| 888 DocumentMarker* marker = markers[i]; | |
| 889 if (marker->startOffset() <= startOffset && endOffset <= marker->endOffs
et() && marker->type() == markerType) | |
| 890 return true; | |
| 891 } | |
| 892 | |
| 893 return false; | |
| 894 } | |
| 895 | |
| 896 bool SpellChecker::selectionStartHasSpellingMarkerFor(int from, int length) cons
t | |
| 897 { | |
| 898 return selectionStartHasMarkerFor(DocumentMarker::Spelling, from, length); | |
| 899 } | |
| 900 | |
| 901 TextCheckingTypeMask SpellChecker::resolveTextCheckingTypeMask(TextCheckingTypeM
ask textCheckingOptions) | |
| 902 { | |
| 903 bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling; | |
| 904 bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar; | |
| 905 | |
| 906 TextCheckingTypeMask checkingTypes = 0; | |
| 907 if (shouldMarkSpelling) | |
| 908 checkingTypes |= TextCheckingTypeSpelling; | |
| 909 if (shouldMarkGrammar) | |
| 910 checkingTypes |= TextCheckingTypeGrammar; | |
| 911 | |
| 912 return checkingTypes; | |
| 913 } | |
| 914 | |
| 915 void SpellChecker::removeMarkers(const VisibleSelection& selection, DocumentMark
er::MarkerTypes markerTypes) | |
| 916 { | |
| 917 const EphemeralRange range = selection.toNormalizedEphemeralRange(); | |
| 918 if (range.isNull()) | |
| 919 return; | |
| 920 frame().document()->markers().removeMarkers(range, markerTypes); | |
| 921 } | |
| 922 | |
| 923 bool SpellChecker::unifiedTextCheckerEnabled() const | |
| 924 { | |
| 925 return blink::unifiedTextCheckerEnabled(m_frame); | |
| 926 } | |
| 927 | |
| 928 void SpellChecker::cancelCheck() | |
| 929 { | |
| 930 m_spellCheckRequester->cancelCheck(); | |
| 931 } | |
| 932 | |
| 933 void SpellChecker::requestTextChecking(const Element& element) | |
| 934 { | |
| 935 const EphemeralRange range = EphemeralRange::rangeOfContents(element); | |
| 936 RefPtrWillBeRawPtr<Range> rangeToCheck = Range::create(element.document(), r
ange.startPosition(), range.endPosition()); | |
| 937 m_spellCheckRequester->requestCheckingFor(SpellCheckRequest::create(TextChec
kingTypeSpelling | TextCheckingTypeGrammar, TextCheckingProcessBatch, rangeToChe
ck, rangeToCheck)); | |
| 938 } | |
| 939 | |
| 940 DEFINE_TRACE(SpellChecker) | |
| 941 { | |
| 942 visitor->trace(m_frame); | |
| 943 visitor->trace(m_spellCheckRequester); | |
| 944 } | |
| 945 | |
| 946 } // namespace blink | |
| OLD | NEW |