• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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