• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006, 2007, 2008 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 "SpellingCorrectionController.h"
29 
30 #include "DocumentMarkerController.h"
31 #include "EditCommand.h"
32 #include "EditorClient.h"
33 #include "Frame.h"
34 #include "FrameView.h"
35 #include "SpellingCorrectionCommand.h"
36 #include "TextCheckerClient.h"
37 #include "TextCheckingHelper.h"
38 #include "TextIterator.h"
39 #include "htmlediting.h"
40 #include "markup.h"
41 #include "visible_units.h"
42 
43 
44 namespace WebCore {
45 
46 using namespace std;
47 using namespace WTF;
48 
49 #if SUPPORT_AUTOCORRECTION_PANEL
50 
markerTypesForAutocorrection()51 static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection()
52 {
53     DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAutoCorrection, ());
54     if (markerTypesForAutoCorrection.isEmpty()) {
55         markerTypesForAutoCorrection.append(DocumentMarker::Replacement);
56         markerTypesForAutoCorrection.append(DocumentMarker::CorrectionIndicator);
57         markerTypesForAutoCorrection.append(DocumentMarker::SpellCheckingExemption);
58         markerTypesForAutoCorrection.append(DocumentMarker::Autocorrected);
59     }
60     return markerTypesForAutoCorrection;
61 }
62 
markerTypesForReplacement()63 static const Vector<DocumentMarker::MarkerType>& markerTypesForReplacement()
64 {
65     DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForReplacement, ());
66     if (markerTypesForReplacement.isEmpty()) {
67         markerTypesForReplacement.append(DocumentMarker::Replacement);
68         markerTypesForReplacement.append(DocumentMarker::SpellCheckingExemption);
69     }
70     return markerTypesForReplacement;
71 }
72 
markersHaveIdenticalDescription(const Vector<DocumentMarker> & markers)73 static bool markersHaveIdenticalDescription(const Vector<DocumentMarker>& markers)
74 {
75     if (markers.isEmpty())
76         return true;
77 
78     const String& description = markers[0].description;
79     for (size_t i = 1; i < markers.size(); ++i) {
80         if (description != markers[i].description)
81             return false;
82     }
83     return true;
84 }
85 
SpellingCorrectionController(Frame * frame)86 SpellingCorrectionController::SpellingCorrectionController(Frame* frame)
87     : m_frame(frame)
88     , m_correctionPanelTimer(this, &SpellingCorrectionController::correctionPanelTimerFired)
89 {
90 }
91 
~SpellingCorrectionController()92 SpellingCorrectionController::~SpellingCorrectionController()
93 {
94     dismiss(ReasonForDismissingCorrectionPanelIgnored);
95 }
96 
startCorrectionPanelTimer(CorrectionPanelInfo::PanelType type)97 void SpellingCorrectionController::startCorrectionPanelTimer(CorrectionPanelInfo::PanelType type)
98 {
99     const double correctionPanelTimerInterval = 0.3;
100     if (!isAutomaticSpellingCorrectionEnabled())
101         return;
102 
103     // If type is PanelTypeReversion, then the new range has been set. So we shouldn't clear it.
104     if (type == CorrectionPanelInfo::PanelTypeCorrection)
105         m_correctionPanelInfo.rangeToBeReplaced.clear();
106     m_correctionPanelInfo.panelType = type;
107     m_correctionPanelTimer.startOneShot(correctionPanelTimerInterval);
108 }
109 
stopCorrectionPanelTimer()110 void SpellingCorrectionController::stopCorrectionPanelTimer()
111 {
112     m_correctionPanelTimer.stop();
113     m_correctionPanelInfo.rangeToBeReplaced.clear();
114 }
115 
stopPendingCorrection(const VisibleSelection & oldSelection)116 void SpellingCorrectionController::stopPendingCorrection(const VisibleSelection& oldSelection)
117 {
118     // Make sure there's no pending autocorrection before we call markMisspellingsAndBadGrammar() below.
119     VisibleSelection currentSelection(m_frame->selection()->selection());
120     if (currentSelection == oldSelection)
121         return;
122 
123     stopCorrectionPanelTimer();
124     dismiss(ReasonForDismissingCorrectionPanelIgnored);
125 }
126 
applyPendingCorrection(const VisibleSelection & selectionAfterTyping)127 void SpellingCorrectionController::applyPendingCorrection(const VisibleSelection& selectionAfterTyping)
128 {
129     // Apply pending autocorrection before next round of spell checking.
130     bool doApplyCorrection = true;
131     VisiblePosition startOfSelection = selectionAfterTyping.visibleStart();
132     VisibleSelection currentWord = VisibleSelection(startOfWord(startOfSelection, LeftWordIfOnBoundary), endOfWord(startOfSelection, RightWordIfOnBoundary));
133     if (currentWord.visibleEnd() == startOfSelection) {
134         String wordText = plainText(currentWord.toNormalizedRange().get());
135         if (wordText.length() > 0 && isAmbiguousBoundaryCharacter(wordText[wordText.length() - 1]))
136             doApplyCorrection = false;
137     }
138     if (doApplyCorrection)
139         handleCorrectionPanelResult(dismissSoon(ReasonForDismissingCorrectionPanelAccepted));
140     else
141         m_correctionPanelInfo.rangeToBeReplaced.clear();
142 }
143 
hasPendingCorrection() const144 bool SpellingCorrectionController::hasPendingCorrection() const
145 {
146     return m_correctionPanelInfo.rangeToBeReplaced;
147 }
148 
isSpellingMarkerAllowed(PassRefPtr<Range> misspellingRange) const149 bool SpellingCorrectionController::isSpellingMarkerAllowed(PassRefPtr<Range> misspellingRange) const
150 {
151     return !m_frame->document()->markers()->hasMarkers(misspellingRange.get(), DocumentMarker::SpellCheckingExemption);
152 }
153 
show(PassRefPtr<Range> rangeToReplace,const String & replacement)154 void SpellingCorrectionController::show(PassRefPtr<Range> rangeToReplace, const String& replacement)
155 {
156     FloatRect boundingBox = windowRectForRange(rangeToReplace.get());
157     if (boundingBox.isEmpty())
158         return;
159     m_correctionPanelInfo.replacedString = plainText(rangeToReplace.get());
160     m_correctionPanelInfo.rangeToBeReplaced = rangeToReplace;
161     m_correctionPanelInfo.replacementString = replacement;
162     m_correctionPanelInfo.isActive = true;
163     client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, replacement, Vector<String>());
164 }
165 
handleCancelOperation()166 void SpellingCorrectionController::handleCancelOperation()
167 {
168     if (!m_correctionPanelInfo.isActive)
169         return;
170     m_correctionPanelInfo.isActive = false;
171     dismiss(ReasonForDismissingCorrectionPanelCancelled);
172 }
173 
dismiss(ReasonForDismissingCorrectionPanel reasonForDismissing)174 void SpellingCorrectionController::dismiss(ReasonForDismissingCorrectionPanel reasonForDismissing)
175 {
176     if (!m_correctionPanelInfo.isActive)
177         return;
178     m_correctionPanelInfo.isActive = false;
179     m_correctionPanelIsDismissedByEditor = true;
180     if (client())
181         client()->dismissCorrectionPanel(reasonForDismissing);
182 }
183 
dismissSoon(ReasonForDismissingCorrectionPanel reasonForDismissing)184 String SpellingCorrectionController::dismissSoon(ReasonForDismissingCorrectionPanel reasonForDismissing)
185 {
186     if (!m_correctionPanelInfo.isActive)
187         return String();
188     m_correctionPanelInfo.isActive = false;
189     m_correctionPanelIsDismissedByEditor = true;
190     if (!client())
191         return String();
192     return client()->dismissCorrectionPanelSoon(reasonForDismissing);
193 }
194 
applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType> & markerTypesToAdd)195 void SpellingCorrectionController::applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& markerTypesToAdd)
196 {
197     if (!m_correctionPanelInfo.rangeToBeReplaced)
198         return;
199 
200     ExceptionCode ec = 0;
201     RefPtr<Range> paragraphRangeContainingCorrection = m_correctionPanelInfo.rangeToBeReplaced->cloneRange(ec);
202     if (ec)
203         return;
204 
205     setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(m_correctionPanelInfo.rangeToBeReplaced->startPosition()));
206     setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(m_correctionPanelInfo.rangeToBeReplaced->endPosition()));
207 
208     // After we replace the word at range rangeToBeReplaced, we need to add markers to that range.
209     // However, once the replacement took place, the value of rangeToBeReplaced is not valid anymore.
210     // So before we carry out the replacement, we need to store the start position of rangeToBeReplaced
211     // relative to the start position of the containing paragraph. We use correctionStartOffsetInParagraph
212     // to store this value. In order to obtain this offset, we need to first create a range
213     // which spans from the start of paragraph to the start position of rangeToBeReplaced.
214     RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition());
215     if (ec)
216         return;
217 
218     Position startPositionOfRangeToBeReplaced = m_correctionPanelInfo.rangeToBeReplaced->startPosition();
219     correctionStartOffsetInParagraphAsRange->setEnd(startPositionOfRangeToBeReplaced.containerNode(), startPositionOfRangeToBeReplaced.computeOffsetInContainerNode(), ec);
220     if (ec)
221         return;
222 
223     // Take note of the location of autocorrection so that we can add marker after the replacement took place.
224     int correctionStartOffsetInParagraph = TextIterator::rangeLength(correctionStartOffsetInParagraphAsRange.get());
225 
226     // Clone the range, since the caller of this method may want to keep the original range around.
227     RefPtr<Range> rangeToBeReplaced = m_correctionPanelInfo.rangeToBeReplaced->cloneRange(ec);
228     applyCommand(SpellingCorrectionCommand::create(rangeToBeReplaced, m_correctionPanelInfo.replacementString));
229     setEnd(paragraphRangeContainingCorrection.get(), m_frame->selection()->selection().start());
230     RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph,  m_correctionPanelInfo.replacementString.length());
231     String newText = plainText(replacementRange.get());
232 
233     // Check to see if replacement succeeded.
234     if (newText != m_correctionPanelInfo.replacementString)
235         return;
236 
237     DocumentMarkerController* markers = replacementRange->startContainer()->document()->markers();
238     size_t size = markerTypesToAdd.size();
239     for (size_t i = 0; i < size; ++i) {
240         DocumentMarker::MarkerType markerType = markerTypesToAdd[i];
241         String description;
242         if (m_correctionPanelInfo.panelType != CorrectionPanelInfo::PanelTypeReversion && (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected))
243             description = m_correctionPanelInfo.replacedString;
244         markers->addMarker(replacementRange.get(), markerType, description);
245     }
246 }
247 
applyAutocorrectionBeforeTypingIfAppropriate()248 bool SpellingCorrectionController::applyAutocorrectionBeforeTypingIfAppropriate()
249 {
250     if (!m_correctionPanelInfo.rangeToBeReplaced || !m_correctionPanelInfo.isActive)
251         return false;
252 
253     if (m_correctionPanelInfo.panelType != CorrectionPanelInfo::PanelTypeCorrection)
254         return false;
255 
256     Position caretPosition = m_frame->selection()->selection().start();
257 
258     if (m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition) {
259         handleCorrectionPanelResult(dismissSoon(ReasonForDismissingCorrectionPanelAccepted));
260         return true;
261     }
262 
263     // Pending correction should always be where caret is. But in case this is not always true, we still want to dismiss the panel without accepting the correction.
264     ASSERT(m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition);
265     dismiss(ReasonForDismissingCorrectionPanelIgnored);
266     return false;
267 }
268 
respondToUnappliedSpellCorrection(const VisibleSelection & selectionOfCorrected,const String & corrected,const String & correction)269 void SpellingCorrectionController::respondToUnappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction)
270 {
271     client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, corrected, correction);
272     m_frame->document()->updateLayout();
273     m_frame->selection()->setSelection(selectionOfCorrected, SelectionController::CloseTyping | SelectionController::ClearTypingStyle | SelectionController::SpellCorrectionTriggered);
274     RefPtr<Range> range = Range::create(m_frame->document(), m_frame->selection()->selection().start(), m_frame->selection()->selection().end());
275 
276     DocumentMarkerController* markers = m_frame->document()->markers();
277     markers->removeMarkers(range.get(), DocumentMarker::Spelling | DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
278     markers->addMarker(range.get(), DocumentMarker::Replacement);
279     markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption);
280 }
281 
correctionPanelTimerFired(Timer<SpellingCorrectionController> *)282 void SpellingCorrectionController::correctionPanelTimerFired(Timer<SpellingCorrectionController>*)
283 {
284     m_correctionPanelIsDismissedByEditor = false;
285     switch (m_correctionPanelInfo.panelType) {
286     case CorrectionPanelInfo::PanelTypeCorrection: {
287         VisibleSelection selection(m_frame->selection()->selection());
288         VisiblePosition start(selection.start(), selection.affinity());
289         VisiblePosition p = startOfWord(start, LeftWordIfOnBoundary);
290         VisibleSelection adjacentWords = VisibleSelection(p, start);
291         m_frame->editor()->markAllMisspellingsAndBadGrammarInRanges(Editor::MarkSpelling | Editor::ShowCorrectionPanel, adjacentWords.toNormalizedRange().get(), 0);
292     }
293         break;
294     case CorrectionPanelInfo::PanelTypeReversion: {
295         m_correctionPanelInfo.isActive = true;
296         m_correctionPanelInfo.replacedString = plainText(m_correctionPanelInfo.rangeToBeReplaced.get());
297         FloatRect boundingBox = windowRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get());
298         if (!boundingBox.isEmpty())
299             client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, m_correctionPanelInfo.replacementString, Vector<String>());
300     }
301         break;
302     case CorrectionPanelInfo::PanelTypeSpellingSuggestions: {
303         if (plainText(m_correctionPanelInfo.rangeToBeReplaced.get()) != m_correctionPanelInfo.replacedString)
304             break;
305         String paragraphText = plainText(TextCheckingParagraph(m_correctionPanelInfo.rangeToBeReplaced).paragraphRange().get());
306         Vector<String> suggestions;
307         textChecker()->getGuessesForWord(m_correctionPanelInfo.replacedString, paragraphText, suggestions);
308         if (suggestions.isEmpty()) {
309             m_correctionPanelInfo.rangeToBeReplaced.clear();
310             break;
311         }
312         String topSuggestion = suggestions.first();
313         suggestions.remove(0);
314         m_correctionPanelInfo.isActive = true;
315         FloatRect boundingBox = windowRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get());
316         if (!boundingBox.isEmpty())
317             client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, topSuggestion, suggestions);
318     }
319         break;
320     }
321 }
322 
handleCorrectionPanelResult(const String & correction)323 void SpellingCorrectionController::handleCorrectionPanelResult(const String& correction)
324 {
325     Range* replacedRange = m_correctionPanelInfo.rangeToBeReplaced.get();
326     if (!replacedRange || m_frame->document() != replacedRange->ownerDocument())
327         return;
328 
329     String currentWord = plainText(m_correctionPanelInfo.rangeToBeReplaced.get());
330     // Check to see if the word we are about to correct has been changed between timer firing and callback being triggered.
331     if (currentWord != m_correctionPanelInfo.replacedString)
332         return;
333 
334     m_correctionPanelInfo.isActive = false;
335 
336     switch (m_correctionPanelInfo.panelType) {
337     case CorrectionPanelInfo::PanelTypeCorrection:
338         if (correction.length()) {
339             m_correctionPanelInfo.replacementString = correction;
340             applyCorrectionPanelInfo(markerTypesForAutocorrection());
341         } else if (!m_correctionPanelIsDismissedByEditor)
342             replacedRange->startContainer()->document()->markers()->addMarker(replacedRange, DocumentMarker::RejectedCorrection, m_correctionPanelInfo.replacedString);
343         break;
344     case CorrectionPanelInfo::PanelTypeReversion:
345     case CorrectionPanelInfo::PanelTypeSpellingSuggestions:
346         if (correction.length()) {
347             m_correctionPanelInfo.replacementString = correction;
348             applyCorrectionPanelInfo(markerTypesForReplacement());
349         }
350         break;
351     }
352 
353     m_correctionPanelInfo.rangeToBeReplaced.clear();
354 }
355 
isAutomaticSpellingCorrectionEnabled()356 bool SpellingCorrectionController::isAutomaticSpellingCorrectionEnabled()
357 {
358     return client() && client()->isAutomaticSpellingCorrectionEnabled();
359 }
360 
windowRectForRange(const Range * range) const361 FloatRect SpellingCorrectionController::windowRectForRange(const Range* range) const
362 {
363     FrameView* view = m_frame->view();
364     return view ? view->contentsToWindow(IntRect(range->boundingRect())) : FloatRect();
365 }
366 
respondToChangedSelection(const VisibleSelection & oldSelection)367 void SpellingCorrectionController::respondToChangedSelection(const VisibleSelection& oldSelection)
368 {
369     VisibleSelection currentSelection(m_frame->selection()->selection());
370     // When user moves caret to the end of autocorrected word and pauses, we show the panel
371     // containing the original pre-correction word so that user can quickly revert the
372     // undesired autocorrection. Here, we start correction panel timer once we confirm that
373     // the new caret position is at the end of a word.
374     if (!currentSelection.isCaret() || currentSelection == oldSelection)
375         return;
376 
377     VisiblePosition selectionPosition = currentSelection.start();
378     VisiblePosition endPositionOfWord = endOfWord(selectionPosition, LeftWordIfOnBoundary);
379     if (selectionPosition != endPositionOfWord)
380         return;
381 
382     Position position = endPositionOfWord.deepEquivalent();
383     if (position.anchorType() != Position::PositionIsOffsetInAnchor)
384         return;
385 
386     Node* node = position.containerNode();
387     int endOffset = position.offsetInContainerNode();
388     Vector<DocumentMarker> markers = node->document()->markers()->markersForNode(node);
389     size_t markerCount = markers.size();
390     for (size_t i = 0; i < markerCount; ++i) {
391         const DocumentMarker& marker = markers[i];
392         if (!shouldStartTimeFor(marker, endOffset))
393             continue;
394         RefPtr<Range> wordRange = Range::create(m_frame->document(), node, marker.startOffset, node, marker.endOffset);
395         String currentWord = plainText(wordRange.get());
396         if (!currentWord.length())
397             continue;
398 
399         m_correctionPanelInfo.rangeToBeReplaced = wordRange;
400         m_correctionPanelInfo.replacedString = currentWord;
401         if (marker.type == DocumentMarker::Spelling)
402             startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeSpellingSuggestions);
403         else {
404             m_correctionPanelInfo.replacementString = marker.description;
405             startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeReversion);
406         }
407 
408         break;
409     }
410 }
411 
respondToAppliedEditing(PassRefPtr<EditCommand> command)412 void SpellingCorrectionController::respondToAppliedEditing(PassRefPtr<EditCommand> command)
413 {
414     if (command->isTopLevelCommand() && !command->shouldRetainAutocorrectionIndicator())
415         m_frame->document()->markers()->removeMarkers(DocumentMarker::CorrectionIndicator);
416 }
417 
client()418 EditorClient* SpellingCorrectionController::client()
419 {
420     return m_frame->page() ? m_frame->page()->editorClient() : 0;
421 }
422 
textChecker()423 TextCheckerClient* SpellingCorrectionController::textChecker()
424 {
425     if (EditorClient* owner = client())
426         return owner->textChecker();
427     return 0;
428 }
429 
recordAutocorrectionResponseReversed(const String & replacedString,const String & replacementString)430 void SpellingCorrectionController::recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString)
431 {
432     client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, replacedString, replacementString);
433 }
434 
recordAutocorrectionResponseReversed(const String & replacedString,PassRefPtr<Range> replacementRange)435 void SpellingCorrectionController::recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange)
436 {
437     recordAutocorrectionResponseReversed(replacedString, plainText(replacementRange.get()));
438 }
439 
markReversed(PassRefPtr<Range> changedRange)440 void SpellingCorrectionController::markReversed(PassRefPtr<Range> changedRange)
441 {
442     changedRange->startContainer()->document()->markers()->removeMarkers(changedRange.get(), DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
443     changedRange->startContainer()->document()->markers()->addMarker(changedRange.get(), DocumentMarker::SpellCheckingExemption);
444 }
445 
markCorrection(PassRefPtr<Range> replacedRange,const String & replacedString)446 void SpellingCorrectionController::markCorrection(PassRefPtr<Range> replacedRange, const String& replacedString)
447 {
448     Vector<DocumentMarker::MarkerType> markerTypesToAdd = markerTypesForAutocorrection();
449     DocumentMarkerController* markers = replacedRange->startContainer()->document()->markers();
450     for (size_t i = 0; i < markerTypesToAdd.size(); ++i) {
451         DocumentMarker::MarkerType markerType = markerTypesToAdd[i];
452         if (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected)
453             markers->addMarker(replacedRange.get(), markerType, replacedString);
454         else
455             markers->addMarker(replacedRange.get(), markerType);
456     }
457 }
458 
recordSpellcheckerResponseForModifiedCorrection(Range * rangeOfCorrection,const String & corrected,const String & correction)459 void SpellingCorrectionController::recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction)
460 {
461     if (!rangeOfCorrection)
462         return;
463     DocumentMarkerController* markers = rangeOfCorrection->startContainer()->document()->markers();
464     Vector<DocumentMarker> correctedOnceMarkers = markers->markersInRange(rangeOfCorrection, DocumentMarker::Autocorrected);
465     if (correctedOnceMarkers.isEmpty())
466         return;
467 
468     // Spelling corrected text has been edited. We need to determine whether user has reverted it to original text or
469     // edited it to something else, and notify spellchecker accordingly.
470     if (markersHaveIdenticalDescription(correctedOnceMarkers) && correctedOnceMarkers[0].description == corrected)
471         client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, corrected, correction);
472     else
473         client()->recordAutocorrectionResponse(EditorClient::AutocorrectionEdited, corrected, correction);
474     markers->removeMarkers(rangeOfCorrection, DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
475 }
476 
477 #endif
478 
479 } // namespace WebCore
480