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 |