2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
28 #include "SpellingCorrectionController.h"
30 #include "DocumentMarkerController.h"
31 #include "EditCommand.h"
32 #include "EditorClient.h"
33 #include "FloatQuad.h"
35 #include "FrameView.h"
37 #include "SpellingCorrectionCommand.h"
38 #include "TextCheckerClient.h"
39 #include "TextCheckingHelper.h"
40 #include "TextIterator.h"
41 #include "VisibleSelection.h"
42 #include "htmlediting.h"
44 #include "visible_units.h"
51 #if USE(AUTOCORRECTION_PANEL)
53 static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection()
55 DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAutoCorrection, ());
56 if (markerTypesForAutoCorrection.isEmpty()) {
57 markerTypesForAutoCorrection.append(DocumentMarker::Replacement);
58 markerTypesForAutoCorrection.append(DocumentMarker::CorrectionIndicator);
59 markerTypesForAutoCorrection.append(DocumentMarker::SpellCheckingExemption);
60 markerTypesForAutoCorrection.append(DocumentMarker::Autocorrected);
62 return markerTypesForAutoCorrection;
65 static const Vector<DocumentMarker::MarkerType>& markerTypesForReplacement()
67 DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForReplacement, ());
68 if (markerTypesForReplacement.isEmpty()) {
69 markerTypesForReplacement.append(DocumentMarker::Replacement);
70 markerTypesForReplacement.append(DocumentMarker::SpellCheckingExemption);
72 return markerTypesForReplacement;
75 static bool markersHaveIdenticalDescription(const Vector<DocumentMarker*>& markers)
77 if (markers.isEmpty())
80 const String& description = markers[0]->description();
81 for (size_t i = 1; i < markers.size(); ++i) {
82 if (description != markers[i]->description())
88 SpellingCorrectionController::SpellingCorrectionController(Frame* frame)
90 , m_correctionPanelTimer(this, &SpellingCorrectionController::correctionPanelTimerFired)
94 SpellingCorrectionController::~SpellingCorrectionController()
96 dismiss(ReasonForDismissingCorrectionPanelIgnored);
99 void SpellingCorrectionController::startCorrectionPanelTimer(CorrectionPanelInfo::PanelType type)
101 const double correctionPanelTimerInterval = 0.3;
102 if (!isAutomaticSpellingCorrectionEnabled())
105 // If type is PanelTypeReversion, then the new range has been set. So we shouldn't clear it.
106 if (type == CorrectionPanelInfo::PanelTypeCorrection)
107 m_correctionPanelInfo.rangeToBeReplaced.clear();
108 m_correctionPanelInfo.panelType = type;
109 m_correctionPanelTimer.startOneShot(correctionPanelTimerInterval);
112 void SpellingCorrectionController::stopCorrectionPanelTimer()
114 m_correctionPanelTimer.stop();
115 m_correctionPanelInfo.rangeToBeReplaced.clear();
118 void SpellingCorrectionController::stopPendingCorrection(const VisibleSelection& oldSelection)
120 // Make sure there's no pending autocorrection before we call markMisspellingsAndBadGrammar() below.
121 VisibleSelection currentSelection(m_frame->selection()->selection());
122 if (currentSelection == oldSelection)
125 stopCorrectionPanelTimer();
126 dismiss(ReasonForDismissingCorrectionPanelIgnored);
129 void SpellingCorrectionController::applyPendingCorrection(const VisibleSelection& selectionAfterTyping)
131 // Apply pending autocorrection before next round of spell checking.
132 bool doApplyCorrection = true;
133 VisiblePosition startOfSelection = selectionAfterTyping.visibleStart();
134 VisibleSelection currentWord = VisibleSelection(startOfWord(startOfSelection, LeftWordIfOnBoundary), endOfWord(startOfSelection, RightWordIfOnBoundary));
135 if (currentWord.visibleEnd() == startOfSelection) {
136 String wordText = plainText(currentWord.toNormalizedRange().get());
137 if (wordText.length() > 0 && isAmbiguousBoundaryCharacter(wordText[wordText.length() - 1]))
138 doApplyCorrection = false;
140 if (doApplyCorrection)
141 handleCorrectionPanelResult(dismissSoon(ReasonForDismissingCorrectionPanelAccepted));
143 m_correctionPanelInfo.rangeToBeReplaced.clear();
146 bool SpellingCorrectionController::hasPendingCorrection() const
148 return m_correctionPanelInfo.rangeToBeReplaced;
151 bool SpellingCorrectionController::isSpellingMarkerAllowed(PassRefPtr<Range> misspellingRange) const
153 return !m_frame->document()->markers()->hasMarkers(misspellingRange.get(), DocumentMarker::SpellCheckingExemption);
156 void SpellingCorrectionController::show(PassRefPtr<Range> rangeToReplace, const String& replacement)
158 FloatRect boundingBox = windowRectForRange(rangeToReplace.get());
159 if (boundingBox.isEmpty())
161 m_correctionPanelInfo.replacedString = plainText(rangeToReplace.get());
162 m_correctionPanelInfo.rangeToBeReplaced = rangeToReplace;
163 m_correctionPanelInfo.replacementString = replacement;
164 m_correctionPanelInfo.isActive = true;
165 client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, replacement, Vector<String>());
168 void SpellingCorrectionController::handleCancelOperation()
170 if (!m_correctionPanelInfo.isActive)
172 m_correctionPanelInfo.isActive = false;
173 dismiss(ReasonForDismissingCorrectionPanelCancelled);
176 void SpellingCorrectionController::dismiss(ReasonForDismissingCorrectionPanel reasonForDismissing)
178 if (!m_correctionPanelInfo.isActive)
180 m_correctionPanelInfo.isActive = false;
181 m_correctionPanelIsDismissedByEditor = true;
183 client()->dismissCorrectionPanel(reasonForDismissing);
186 String SpellingCorrectionController::dismissSoon(ReasonForDismissingCorrectionPanel reasonForDismissing)
188 if (!m_correctionPanelInfo.isActive)
190 m_correctionPanelInfo.isActive = false;
191 m_correctionPanelIsDismissedByEditor = true;
194 return client()->dismissCorrectionPanelSoon(reasonForDismissing);
197 void SpellingCorrectionController::applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& markerTypesToAdd)
199 if (!m_correctionPanelInfo.rangeToBeReplaced)
202 ExceptionCode ec = 0;
203 RefPtr<Range> paragraphRangeContainingCorrection = m_correctionPanelInfo.rangeToBeReplaced->cloneRange(ec);
207 setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(m_correctionPanelInfo.rangeToBeReplaced->startPosition()));
208 setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(m_correctionPanelInfo.rangeToBeReplaced->endPosition()));
210 // After we replace the word at range rangeToBeReplaced, we need to add markers to that range.
211 // However, once the replacement took place, the value of rangeToBeReplaced is not valid anymore.
212 // So before we carry out the replacement, we need to store the start position of rangeToBeReplaced
213 // relative to the start position of the containing paragraph. We use correctionStartOffsetInParagraph
214 // to store this value. In order to obtain this offset, we need to first create a range
215 // which spans from the start of paragraph to the start position of rangeToBeReplaced.
216 RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition());
220 Position startPositionOfRangeToBeReplaced = m_correctionPanelInfo.rangeToBeReplaced->startPosition();
221 correctionStartOffsetInParagraphAsRange->setEnd(startPositionOfRangeToBeReplaced.containerNode(), startPositionOfRangeToBeReplaced.computeOffsetInContainerNode(), ec);
225 // Take note of the location of autocorrection so that we can add marker after the replacement took place.
226 int correctionStartOffsetInParagraph = TextIterator::rangeLength(correctionStartOffsetInParagraphAsRange.get());
228 // Clone the range, since the caller of this method may want to keep the original range around.
229 RefPtr<Range> rangeToBeReplaced = m_correctionPanelInfo.rangeToBeReplaced->cloneRange(ec);
230 applyCommand(SpellingCorrectionCommand::create(rangeToBeReplaced, m_correctionPanelInfo.replacementString));
231 setEnd(paragraphRangeContainingCorrection.get(), m_frame->selection()->selection().start());
232 RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, m_correctionPanelInfo.replacementString.length());
233 String newText = plainText(replacementRange.get());
235 // Check to see if replacement succeeded.
236 if (newText != m_correctionPanelInfo.replacementString)
239 DocumentMarkerController* markers = replacementRange->startContainer()->document()->markers();
240 size_t size = markerTypesToAdd.size();
241 for (size_t i = 0; i < size; ++i) {
242 DocumentMarker::MarkerType markerType = markerTypesToAdd[i];
244 if (m_correctionPanelInfo.panelType != CorrectionPanelInfo::PanelTypeReversion && (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected))
245 description = m_correctionPanelInfo.replacedString;
246 markers->addMarker(replacementRange.get(), markerType, description);
250 bool SpellingCorrectionController::applyAutocorrectionBeforeTypingIfAppropriate()
252 if (!m_correctionPanelInfo.rangeToBeReplaced || !m_correctionPanelInfo.isActive)
255 if (m_correctionPanelInfo.panelType != CorrectionPanelInfo::PanelTypeCorrection)
258 Position caretPosition = m_frame->selection()->selection().start();
260 if (m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition) {
261 handleCorrectionPanelResult(dismissSoon(ReasonForDismissingCorrectionPanelAccepted));
265 // 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.
266 ASSERT(m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition);
267 dismiss(ReasonForDismissingCorrectionPanelIgnored);
271 void SpellingCorrectionController::respondToUnappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction)
273 client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, corrected, correction);
274 m_frame->document()->updateLayout();
275 m_frame->selection()->setSelection(selectionOfCorrected, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle | FrameSelection::SpellCorrectionTriggered);
276 RefPtr<Range> range = Range::create(m_frame->document(), m_frame->selection()->selection().start(), m_frame->selection()->selection().end());
278 DocumentMarkerController* markers = m_frame->document()->markers();
279 markers->removeMarkers(range.get(), DocumentMarker::Spelling | DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
280 markers->addMarker(range.get(), DocumentMarker::Replacement);
281 markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption);
284 void SpellingCorrectionController::correctionPanelTimerFired(Timer<SpellingCorrectionController>*)
286 m_correctionPanelIsDismissedByEditor = false;
287 switch (m_correctionPanelInfo.panelType) {
288 case CorrectionPanelInfo::PanelTypeCorrection: {
289 VisibleSelection selection(m_frame->selection()->selection());
290 VisiblePosition start(selection.start(), selection.affinity());
291 VisiblePosition p = startOfWord(start, LeftWordIfOnBoundary);
292 VisibleSelection adjacentWords = VisibleSelection(p, start);
293 m_frame->editor()->markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeSpelling | TextCheckingTypeShowCorrectionPanel, adjacentWords.toNormalizedRange().get(), 0);
296 case CorrectionPanelInfo::PanelTypeReversion: {
297 if (!m_correctionPanelInfo.rangeToBeReplaced)
299 m_correctionPanelInfo.isActive = true;
300 m_correctionPanelInfo.replacedString = plainText(m_correctionPanelInfo.rangeToBeReplaced.get());
301 FloatRect boundingBox = windowRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get());
302 if (!boundingBox.isEmpty())
303 client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, m_correctionPanelInfo.replacementString, Vector<String>());
306 case CorrectionPanelInfo::PanelTypeSpellingSuggestions: {
307 if (!m_correctionPanelInfo.rangeToBeReplaced || plainText(m_correctionPanelInfo.rangeToBeReplaced.get()) != m_correctionPanelInfo.replacedString)
309 String paragraphText = plainText(TextCheckingParagraph(m_correctionPanelInfo.rangeToBeReplaced).paragraphRange().get());
310 Vector<String> suggestions;
311 textChecker()->getGuessesForWord(m_correctionPanelInfo.replacedString, paragraphText, suggestions);
312 if (suggestions.isEmpty()) {
313 m_correctionPanelInfo.rangeToBeReplaced.clear();
316 String topSuggestion = suggestions.first();
317 suggestions.remove(0);
318 m_correctionPanelInfo.isActive = true;
319 FloatRect boundingBox = windowRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get());
320 if (!boundingBox.isEmpty())
321 client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, topSuggestion, suggestions);
327 void SpellingCorrectionController::handleCorrectionPanelResult(const String& correction)
329 Range* replacedRange = m_correctionPanelInfo.rangeToBeReplaced.get();
330 if (!replacedRange || m_frame->document() != replacedRange->ownerDocument())
333 String currentWord = plainText(m_correctionPanelInfo.rangeToBeReplaced.get());
334 // Check to see if the word we are about to correct has been changed between timer firing and callback being triggered.
335 if (currentWord != m_correctionPanelInfo.replacedString)
338 m_correctionPanelInfo.isActive = false;
340 switch (m_correctionPanelInfo.panelType) {
341 case CorrectionPanelInfo::PanelTypeCorrection:
342 if (correction.length()) {
343 m_correctionPanelInfo.replacementString = correction;
344 applyCorrectionPanelInfo(markerTypesForAutocorrection());
345 } else if (!m_correctionPanelIsDismissedByEditor)
346 replacedRange->startContainer()->document()->markers()->addMarker(replacedRange, DocumentMarker::RejectedCorrection, m_correctionPanelInfo.replacedString);
348 case CorrectionPanelInfo::PanelTypeReversion:
349 case CorrectionPanelInfo::PanelTypeSpellingSuggestions:
350 if (correction.length()) {
351 m_correctionPanelInfo.replacementString = correction;
352 applyCorrectionPanelInfo(markerTypesForReplacement());
357 m_correctionPanelInfo.rangeToBeReplaced.clear();
360 bool SpellingCorrectionController::isAutomaticSpellingCorrectionEnabled()
362 return client() && client()->isAutomaticSpellingCorrectionEnabled();
365 FloatRect SpellingCorrectionController::windowRectForRange(const Range* range) const
367 FrameView* view = m_frame->view();
370 Vector<FloatQuad> textQuads;
371 range->textQuads(textQuads);
372 FloatRect boundingRect;
373 size_t size = textQuads.size();
374 for (size_t i = 0; i < size; ++i)
375 boundingRect.unite(textQuads[i].boundingBox());
376 return view->contentsToWindow(IntRect(boundingRect));
379 void SpellingCorrectionController::respondToChangedSelection(const VisibleSelection& oldSelection)
381 VisibleSelection currentSelection(m_frame->selection()->selection());
382 // When user moves caret to the end of autocorrected word and pauses, we show the panel
383 // containing the original pre-correction word so that user can quickly revert the
384 // undesired autocorrection. Here, we start correction panel timer once we confirm that
385 // the new caret position is at the end of a word.
386 if (!currentSelection.isCaret() || currentSelection == oldSelection)
389 VisiblePosition selectionPosition = currentSelection.start();
391 // Creating a Visible position triggers a layout and there is no
392 // guarantee that the selection is still valid.
393 if (selectionPosition.isNull())
396 VisiblePosition endPositionOfWord = endOfWord(selectionPosition, LeftWordIfOnBoundary);
397 if (selectionPosition != endPositionOfWord)
400 Position position = endPositionOfWord.deepEquivalent();
401 if (position.anchorType() != Position::PositionIsOffsetInAnchor)
404 Node* node = position.containerNode();
405 int endOffset = position.offsetInContainerNode();
406 Vector<DocumentMarker*> markers = node->document()->markers()->markersFor(node);
407 size_t markerCount = markers.size();
408 for (size_t i = 0; i < markerCount; ++i) {
409 const DocumentMarker* marker = markers[i];
410 if (!shouldStartTimerFor(marker, endOffset))
412 RefPtr<Range> wordRange = Range::create(m_frame->document(), node, marker->startOffset(), node, marker->endOffset());
413 String currentWord = plainText(wordRange.get());
414 if (!currentWord.length())
417 m_correctionPanelInfo.rangeToBeReplaced = wordRange;
418 m_correctionPanelInfo.replacedString = currentWord;
419 if (marker->type() == DocumentMarker::Spelling)
420 startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeSpellingSuggestions);
422 m_correctionPanelInfo.replacementString = marker->description();
423 startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeReversion);
430 void SpellingCorrectionController::respondToAppliedEditing(EditCommand* command)
432 if (command->isTopLevelCommand() && !command->shouldRetainAutocorrectionIndicator())
433 m_frame->document()->markers()->removeMarkers(DocumentMarker::CorrectionIndicator);
435 markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(command);
436 m_originalStringForLastDeletedAutocorrection = String();
439 void SpellingCorrectionController::respondToUnappliedEditing(EditCommand* command)
441 if (!command->isCreateLinkCommand())
443 RefPtr<Range> range = Range::create(m_frame->document(), command->startingSelection().start(), command->startingSelection().end());
446 DocumentMarkerController* markers = m_frame->document()->markers();
447 markers->addMarker(range.get(), DocumentMarker::Replacement);
448 markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption);
451 EditorClient* SpellingCorrectionController::client()
453 return m_frame->page() ? m_frame->page()->editorClient() : 0;
456 TextCheckerClient* SpellingCorrectionController::textChecker()
458 if (EditorClient* owner = client())
459 return owner->textChecker();
463 void SpellingCorrectionController::recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString)
465 client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, replacedString, replacementString);
468 void SpellingCorrectionController::recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange)
470 recordAutocorrectionResponseReversed(replacedString, plainText(replacementRange.get()));
473 void SpellingCorrectionController::markReversed(PassRefPtr<Range> changedRange)
475 changedRange->startContainer()->document()->markers()->removeMarkers(changedRange.get(), DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
476 changedRange->startContainer()->document()->markers()->addMarker(changedRange.get(), DocumentMarker::SpellCheckingExemption);
479 void SpellingCorrectionController::markCorrection(PassRefPtr<Range> replacedRange, const String& replacedString)
481 Vector<DocumentMarker::MarkerType> markerTypesToAdd = markerTypesForAutocorrection();
482 DocumentMarkerController* markers = replacedRange->startContainer()->document()->markers();
483 for (size_t i = 0; i < markerTypesToAdd.size(); ++i) {
484 DocumentMarker::MarkerType markerType = markerTypesToAdd[i];
485 if (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected)
486 markers->addMarker(replacedRange.get(), markerType, replacedString);
488 markers->addMarker(replacedRange.get(), markerType);
492 void SpellingCorrectionController::recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction)
494 if (!rangeOfCorrection)
496 DocumentMarkerController* markers = rangeOfCorrection->startContainer()->document()->markers();
497 Vector<DocumentMarker*> correctedOnceMarkers = markers->markersInRange(rangeOfCorrection, DocumentMarker::Autocorrected);
498 if (correctedOnceMarkers.isEmpty())
501 // Spelling corrected text has been edited. We need to determine whether user has reverted it to original text or
502 // edited it to something else, and notify spellchecker accordingly.
503 if (markersHaveIdenticalDescription(correctedOnceMarkers) && correctedOnceMarkers[0]->description() == corrected)
504 client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, corrected, correction);
506 client()->recordAutocorrectionResponse(EditorClient::AutocorrectionEdited, corrected, correction);
507 markers->removeMarkers(rangeOfCorrection, DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
510 void SpellingCorrectionController::deletedAutocorrectionAtPosition(const Position& position, const String& originalString)
512 m_originalStringForLastDeletedAutocorrection = originalString;
513 m_positionForLastDeletedAutocorrection = position;
516 void SpellingCorrectionController::markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(EditCommand* command)
518 Position endOfSelection = command->endingSelection().end();
519 if (endOfSelection != m_positionForLastDeletedAutocorrection)
522 Position precedingCharacterPosition = endOfSelection.previous();
523 if (endOfSelection == precedingCharacterPosition)
526 RefPtr<Range> precedingCharacterRange = Range::create(m_frame->document(), precedingCharacterPosition, endOfSelection);
527 String string = plainText(precedingCharacterRange.get());
528 if (string.isEmpty() || !isWhitespace(string[string.length() - 1]))
531 // Mark this whitespace to indicate we have deleted an autocorrection following this
532 // whitespace. So if the user types the same original word again at this position, we
533 // won't autocorrect it again.
534 m_frame->document()->markers()->addMarker(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection, m_originalStringForLastDeletedAutocorrection);
537 bool SpellingCorrectionController::processMarkersOnTextToBeReplacedByResult(const TextCheckingResult* result, Range* rangeToBeReplaced, const String& stringToBeReplaced)
539 DocumentMarkerController* markerController = m_frame->document()->markers();
540 if (markerController->hasMarkers(rangeToBeReplaced, DocumentMarker::Replacement)) {
541 if (result->type == TextCheckingTypeCorrection)
542 recordSpellcheckerResponseForModifiedCorrection(rangeToBeReplaced, stringToBeReplaced, result->replacement);
546 if (markerController->hasMarkers(rangeToBeReplaced, DocumentMarker::RejectedCorrection))
549 Position beginningOfRange = rangeToBeReplaced->startPosition();
550 Position precedingCharacterPosition = beginningOfRange.previous();
551 RefPtr<Range> precedingCharacterRange = Range::create(m_frame->document(), precedingCharacterPosition, beginningOfRange);
553 Vector<DocumentMarker*> markers = markerController->markersInRange(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection);
555 for (size_t i = 0; i < markers.size(); ++i) {
556 if (markers[i]->description() == stringToBeReplaced)
565 } // namespace WebCore