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