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